Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(js): rely on environment instead of global object #572

Merged
merged 5 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ module.exports = {
'eslint-comments/no-unlimited-disable': OFF,
},
},
{
files: [
'packages/autocomplete-core/**/*',
'packages/autocomplete-js/**/*',
],
rules: {
'no-restricted-globals': [
'error',
{
name: 'window',
message: 'Use the `environment` param to access this property.',
},
{
name: 'document',
message: 'Use the `environment` param to access this property.',
},
],
},
},
{
files: ['**/__tests__/**'],
rules: {
'no-restricted-globals': OFF,
},
},
{
files: ['**/rollup.config.js', 'stories/**/*', '**/__tests__/**'],
rules: {
Expand Down
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
{
"path": "packages/autocomplete-js/dist/umd/index.production.js",
"maxSize": "15.25 kB"
"maxSize": "15.5 kB"
},
{
"path": "packages/autocomplete-preset-algolia/dist/umd/index.production.js",
Expand Down
2 changes: 2 additions & 0 deletions packages/autocomplete-core/src/getDefaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export function getDefaultProps<TItem extends BaseItem>(
props: AutocompleteOptions<TItem>,
pluginSubscribers: AutocompleteSubscribers<TItem>
): InternalAutocompleteOptions<TItem> {
/* eslint-disable no-restricted-globals */
const environment: AutocompleteEnvironment = (typeof window !== 'undefined'
? window
: {}) as typeof window;
/* eslint-enable no-restricted-globals */
const plugins = props.plugins || [];

return {
Expand Down
21 changes: 15 additions & 6 deletions packages/autocomplete-js/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export function autocomplete<TItem extends BaseItem>(
autocomplete: autocomplete.value,
autocompleteScopeApi,
classNames: props.value.renderer.classNames,
environment: props.value.core.environment,
isDetached: isDetached.value,
placeholder: props.value.core.placeholder,
propGetters,
Expand Down Expand Up @@ -237,7 +238,7 @@ export function autocomplete<TItem extends BaseItem>(
// We scroll to the top of the panel whenever the query changes (i.e. new
// results come in) so that users don't have to.
if (state.query !== prevState.query) {
const scrollablePanels = document.querySelectorAll(
const scrollablePanels = props.value.core.environment.document.querySelectorAll(
'.aa-Panel--scrollable'
);
scrollablePanels.forEach((scrollablePanel) => {
Expand Down Expand Up @@ -336,19 +337,27 @@ export function autocomplete<TItem extends BaseItem>(

function setIsModalOpen(value: boolean) {
requestAnimationFrame(() => {
const prevValue = document.body.contains(dom.value.detachedOverlay);
const prevValue = props.value.core.environment.document.body.contains(
dom.value.detachedOverlay
);

if (value === prevValue) {
return;
}

if (value) {
document.body.appendChild(dom.value.detachedOverlay);
document.body.classList.add('aa-Detached');
props.value.core.environment.document.body.appendChild(
dom.value.detachedOverlay
);
props.value.core.environment.document.body.classList.add('aa-Detached');
dom.value.input.focus();
} else {
document.body.removeChild(dom.value.detachedOverlay);
document.body.classList.remove('aa-Detached');
props.value.core.environment.document.body.removeChild(
dom.value.detachedOverlay
);
props.value.core.environment.document.body.classList.remove(
'aa-Detached'
);
autocomplete.value.setQuery('');
autocomplete.value.refresh();
}
Expand Down
16 changes: 11 additions & 5 deletions packages/autocomplete-js/src/createAutocompleteDom.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {
AutocompleteApi as AutocompleteCoreApi,
AutocompleteEnvironment,
AutocompleteScopeApi,
BaseItem,
} from '@algolia/autocomplete-core';

import { createDomElement } from './createDomElement';
import { ClearIcon, Input, LoadingIcon, SearchIcon } from './elements';
import { getCreateDomElement } from './getCreateDomElement';
import {
AutocompleteClassNames,
AutocompleteDom,
Expand All @@ -18,6 +19,7 @@ type CreateDomProps<TItem extends BaseItem> = {
autocomplete: AutocompleteCoreApi<TItem>;
autocompleteScopeApi: AutocompleteScopeApi<TItem>;
classNames: AutocompleteClassNames;
environment: AutocompleteEnvironment;
isDetached: boolean;
placeholder?: string;
propGetters: AutocompletePropGetters<TItem>;
Expand All @@ -29,12 +31,15 @@ export function createAutocompleteDom<TItem extends BaseItem>({
autocomplete,
autocompleteScopeApi,
classNames,
environment,
isDetached,
placeholder = 'Search',
propGetters,
setIsModalOpen,
state,
}: CreateDomProps<TItem>): AutocompleteDom {
const createDomElement = getCreateDomElement(environment);

const rootProps = propGetters.getRootProps({
state,
props: autocomplete.getRootProps({}),
Expand Down Expand Up @@ -68,7 +73,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
class: classNames.submitButton,
type: 'submit',
title: 'Submit',
children: [SearchIcon({})],
children: [SearchIcon({ environment })],
});
const label = createDomElement('label', {
class: classNames.label,
Expand All @@ -79,15 +84,16 @@ export function createAutocompleteDom<TItem extends BaseItem>({
class: classNames.clearButton,
type: 'reset',
title: 'Clear',
children: [ClearIcon({})],
children: [ClearIcon({ environment })],
});
const loadingIndicator = createDomElement('div', {
class: classNames.loadingIndicator,
children: [LoadingIcon({})],
children: [LoadingIcon({ environment })],
});

const input = Input({
class: classNames.input,
environment,
state,
getInputProps: propGetters.getInputProps,
getInputPropsCore: autocomplete.getInputProps,
Expand Down Expand Up @@ -142,7 +148,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
if (isDetached) {
const detachedSearchButtonIcon = createDomElement('div', {
class: classNames.detachedSearchButtonIcon,
children: [SearchIcon({})],
children: [SearchIcon({ environment })],
});
const detachedSearchButtonPlaceholder = createDomElement('div', {
class: classNames.detachedSearchButtonPlaceholder,
Expand Down
16 changes: 0 additions & 16 deletions packages/autocomplete-js/src/createDomElement.ts

This file was deleted.

17 changes: 14 additions & 3 deletions packages/autocomplete-js/src/elements/ClearIcon.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { AutocompleteEnvironment } from '@algolia/autocomplete-core';

import { AutocompleteElement } from '../types/AutocompleteElement';

export const ClearIcon: AutocompleteElement<{}, SVGSVGElement> = () => {
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
export const ClearIcon: AutocompleteElement<
{ environment: AutocompleteEnvironment },
SVGSVGElement
> = ({ environment }) => {
const element = environment.document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
element.setAttribute('class', 'aa-ClearIcon');
element.setAttribute('viewBox', '0 0 24 24');
element.setAttribute('width', '18');
element.setAttribute('height', '18');
element.setAttribute('fill', 'currentColor');

const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const path = environment.document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
path.setAttribute(
'd',
'M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z'
Expand Down
6 changes: 5 additions & 1 deletion packages/autocomplete-js/src/elements/Input.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import {
AutocompleteApi as AutocompleteCoreApi,
AutocompleteEnvironment,
AutocompleteScopeApi,
} from '@algolia/autocomplete-core';

import { createDomElement } from '../createDomElement';
import { getCreateDomElement } from '../getCreateDomElement';
import { AutocompletePropGetters, AutocompleteState } from '../types';
import { AutocompleteElement } from '../types/AutocompleteElement';
import { setProperties } from '../utils';

type InputProps = {
autocompleteScopeApi: AutocompleteScopeApi<any>;
environment: AutocompleteEnvironment;
getInputProps: AutocompletePropGetters<any>['getInputProps'];
getInputPropsCore: AutocompleteCoreApi<any>['getInputProps'];
onDetachedEscape?(): void;
Expand All @@ -18,13 +20,15 @@ type InputProps = {

export const Input: AutocompleteElement<InputProps, HTMLInputElement> = ({
autocompleteScopeApi,
environment,
classNames,
getInputProps,
getInputPropsCore,
onDetachedEscape,
state,
...props
}) => {
const createDomElement = getCreateDomElement(environment);
const element = createDomElement('input', props);
const inputProps = getInputProps({
state,
Expand Down
12 changes: 10 additions & 2 deletions packages/autocomplete-js/src/elements/LoadingIcon.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { AutocompleteEnvironment } from '@algolia/autocomplete-core';

import { AutocompleteElement } from '../types/AutocompleteElement';

export const LoadingIcon: AutocompleteElement<{}, SVGSVGElement> = () => {
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
export const LoadingIcon: AutocompleteElement<
{ environment: AutocompleteEnvironment },
SVGSVGElement
> = ({ environment }) => {
const element = environment.document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
element.setAttribute('class', 'aa-LoadingIcon');
element.setAttribute('viewBox', '0 0 100 100');
element.setAttribute('width', '20');
Expand Down
17 changes: 14 additions & 3 deletions packages/autocomplete-js/src/elements/SearchIcon.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { AutocompleteEnvironment } from '@algolia/autocomplete-core';

import { AutocompleteElement } from '../types/AutocompleteElement';

export const SearchIcon: AutocompleteElement<{}, SVGSVGElement> = () => {
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
export const SearchIcon: AutocompleteElement<
{ environment: AutocompleteEnvironment },
SVGSVGElement
> = ({ environment }) => {
const element = environment.document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
element.setAttribute('class', 'aa-SubmitIcon');
element.setAttribute('viewBox', '0 0 24 24');
element.setAttribute('width', '20');
element.setAttribute('height', '20');
element.setAttribute('fill', 'currentColor');

const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const path = environment.document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
path.setAttribute(
'd',
'M16.041 15.856c-0.034 0.026-0.067 0.055-0.099 0.087s-0.060 0.064-0.087 0.099c-1.258 1.213-2.969 1.958-4.855 1.958-1.933 0-3.682-0.782-4.95-2.050s-2.050-3.017-2.050-4.95 0.782-3.682 2.050-4.95 3.017-2.050 4.95-2.050 3.682 0.782 4.95 2.050 2.050 3.017 2.050 4.95c0 1.886-0.745 3.597-1.959 4.856zM21.707 20.293l-3.675-3.675c1.231-1.54 1.968-3.493 1.968-5.618 0-2.485-1.008-4.736-2.636-6.364s-3.879-2.636-6.364-2.636-4.736 1.008-6.364 2.636-2.636 3.879-2.636 6.364 1.008 4.736 2.636 6.364 3.879 2.636 6.364 2.636c2.125 0 4.078-0.737 5.618-1.968l3.675 3.675c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414z'
Expand Down
20 changes: 20 additions & 0 deletions packages/autocomplete-js/src/getCreateDomElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AutocompleteEnvironment } from '@algolia/autocomplete-core';

import { setProperties } from './utils';

type CreateDomElementProps = Record<string, unknown> & {
children?: Node[];
};

export function getCreateDomElement(environment: AutocompleteEnvironment) {
return function createDomElement<KParam extends keyof HTMLElementTagNameMap>(
tagName: KParam,
{ children = [], ...props }: CreateDomElementProps
): HTMLElementTagNameMap[KParam] {
const element = environment.document.createElement<KParam>(tagName);
setProperties(element, props);
element.append(...children);

return element;
};
}
16 changes: 9 additions & 7 deletions packages/autocomplete-js/src/getDefaultOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BaseItem } from '@algolia/autocomplete-core';
import { AutocompleteEnvironment, BaseItem } from '@algolia/autocomplete-core';
import {
generateAutocompleteId,
invariant,
Expand Down Expand Up @@ -85,16 +85,18 @@ export function getDefaultOptions<TItem extends BaseItem>(
...core
} = options;

const containerElement = getHTMLElement(container);
/* eslint-disable no-restricted-globals */
const environment: AutocompleteEnvironment = (typeof window !== 'undefined'
? window
: {}) as typeof window;
/* eslint-enable no-restricted-globals */
const containerElement = getHTMLElement(environment, container);

invariant(
containerElement.tagName !== 'INPUT',
'The `container` option does not support `input` elements. You need to change the container to a `div`.'
);

const environment = (typeof window !== 'undefined'
? window
: {}) as typeof window;
const defaultedRenderer = renderer ?? defaultRenderer;
const defaultComponents: AutocompleteComponents = {
Highlight: createHighlightComponent(defaultedRenderer),
Expand All @@ -119,8 +121,8 @@ export function getDefaultOptions<TItem extends BaseItem>(
getPanelProps: getPanelProps ?? (({ props }) => props),
getRootProps: getRootProps ?? (({ props }) => props),
panelContainer: panelContainer
? getHTMLElement(panelContainer)
: document.body,
? getHTMLElement(environment, panelContainer)
: environment.document.body,
panelPlacement: panelPlacement ?? 'input-wrapper-width',
render: render ?? defaultRender,
renderNoResults,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ describe('getHTMLElement', () => {
const element = document.createElement('div');
document.body.appendChild(element);

expect(getHTMLElement(element)).toEqual(element);
expect(getHTMLElement(window, element)).toEqual(element);
});

test('with a string returns the element if exists', () => {
const element = document.createElement('div');
document.body.appendChild(element);

expect(getHTMLElement('div')).toEqual(element);
expect(getHTMLElement(window, 'div')).toEqual(element);
});

test('with a string throws invariant if does not exist', () => {
expect(() => {
getHTMLElement('div');
getHTMLElement(window, 'div');
}).toThrow('The element "div" is not in the document.');
});
});
Loading