From 18c7474b74b4e023dd6bcaa6c05040b5680ba926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Mon, 7 Dec 2020 14:27:50 +0100 Subject: [PATCH] feat(js): pass scope API to prop getters --- packages/autocomplete-js/src/autocomplete.ts | 102 +++++++---------- .../autocomplete-js/src/components/Input.ts | 19 +++- .../src/createAutocompleteDom.ts | 53 ++++----- packages/autocomplete-js/src/render.ts | 72 ++++++------ .../src/types/AutocompletePropGetters.ts | 103 ++++++++++++------ 5 files changed, 189 insertions(+), 160 deletions(-) diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 1c797267c..c7148f844 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -1,4 +1,8 @@ -import { BaseItem, createAutocomplete } from '@algolia/autocomplete-core'; +import { + AutocompleteScopeApi, + BaseItem, + createAutocomplete, +} from '@algolia/autocomplete-core'; import { createRef, invariant } from '@algolia/autocomplete-shared'; import { createAutocompleteDom } from './createAutocompleteDom'; @@ -8,6 +12,7 @@ import { render } from './render'; import { AutocompleteApi, AutocompleteOptions, + AutocompletePropGetters, AutocompleteState, } from './types'; import { debounce, getHTMLElement, setProperties } from './utils'; @@ -60,20 +65,7 @@ export function autocomplete({ ...props.initialState, }; - const { - inputWrapper, - form, - label, - input, - submitButton, - resetButton, - loadingIndicator, - root, - panel, - } = createAutocompleteDom({ - state: initialState, - autocomplete, - classNames, + const propGetters: AutocompletePropGetters = { getEnvironmentProps, getFormProps, getInputProps, @@ -82,14 +74,30 @@ export function autocomplete({ getListProps, getPanelProps, getRootProps, + }; + const autocompleteScopeApi: AutocompleteScopeApi = { + setSelectedItemId: autocomplete.setSelectedItemId, + setQuery: autocomplete.setQuery, + setCollections: autocomplete.setCollections, + setIsOpen: autocomplete.setIsOpen, + setStatus: autocomplete.setStatus, + setContext: autocomplete.setContext, + refresh: autocomplete.refresh, + }; + const dom = createAutocompleteDom({ + state: initialState, + autocomplete, + classNames, + propGetters, + autocompleteScopeApi, }); function setPanelPosition() { - setProperties(panel, { + setProperties(dom.panel, { style: getPanelPositionStyle({ panelPlacement, - container: root, - form, + container: dom.root, + form: dom.form, environment: props.environment, }), }); @@ -97,9 +105,9 @@ export function autocomplete({ runEffect(() => { const environmentProps = autocomplete.getEnvironmentProps({ - formElement: form, - panelElement: panel, - inputElement: input, + formElement: dom.form, + panelElement: dom.panel, + inputElement: dom.input, }); setProperties(window as any, environmentProps); @@ -122,25 +130,11 @@ export function autocomplete({ render(renderer, { state: initialState, autocomplete, - getEnvironmentProps, - getFormProps, - getInputProps, - getItemProps, - getLabelProps, - getListProps, - getPanelProps, - getRootProps, + propGetters, + dom, classNames, panelRoot, - root, - form, - input, - inputWrapper, - label, - panel, - submitButton, - resetButton, - loadingIndicator, + autocompleteScopeApi, }); return () => {}; @@ -162,25 +156,11 @@ export function autocomplete({ unmountRef.current = render(renderer, { state, autocomplete, - getEnvironmentProps, - getFormProps, - getInputProps, - getItemProps, - getLabelProps, - getListProps, - getPanelProps, - getRootProps, + propGetters, + dom, classNames, panelRoot, - root, - form, - input, - inputWrapper, - label, - panel, - submitButton, - resetButton, - loadingIndicator, + autocompleteScopeApi, }); }, 0); @@ -208,10 +188,10 @@ export function autocomplete({ containerElement.tagName !== 'INPUT', 'The `container` option does not support `input` elements. You need to change the container to a `div`.' ); - containerElement.appendChild(root); + containerElement.appendChild(dom.root); return () => { - containerElement.removeChild(root); + containerElement.removeChild(dom.root); }; }); @@ -232,13 +212,7 @@ export function autocomplete({ }); return { - setSelectedItemId: autocomplete.setSelectedItemId, - setQuery: autocomplete.setQuery, - setCollections: autocomplete.setCollections, - setIsOpen: autocomplete.setIsOpen, - setStatus: autocomplete.setStatus, - setContext: autocomplete.setContext, - refresh: autocomplete.refresh, + ...autocompleteScopeApi, destroy() { cleanupEffects(); }, diff --git a/packages/autocomplete-js/src/components/Input.ts b/packages/autocomplete-js/src/components/Input.ts index 85b47e5bf..2199eb103 100644 --- a/packages/autocomplete-js/src/components/Input.ts +++ b/packages/autocomplete-js/src/components/Input.ts @@ -1,4 +1,7 @@ -import { AutocompleteApi as AutocompleteCoreApi } from '@algolia/autocomplete-core'; +import { + AutocompleteApi as AutocompleteCoreApi, + AutocompleteScopeApi, +} from '@algolia/autocomplete-core'; import { AutocompletePropGetters, AutocompleteState } from '../types'; import { Component, WithClassNames } from '../types/Component'; @@ -8,6 +11,7 @@ type InputProps = WithClassNames<{ state: AutocompleteState; getInputProps: AutocompletePropGetters['getInputProps']; getInputPropsCore: AutocompleteCoreApi['getInputProps']; + autocompleteScopeApi: AutocompleteScopeApi; }>; export const Input: Component = ({ @@ -15,14 +19,17 @@ export const Input: Component = ({ getInputProps, getInputPropsCore, state, + autocompleteScopeApi, }) => { const element = document.createElement('input'); + const inputProps = getInputProps({ + state, + props: getInputPropsCore({ inputElement: element }), + inputElement: element, + ...autocompleteScopeApi, + }); setProperties(element, { - ...getInputProps({ - state, - props: getInputPropsCore({ inputElement: element }), - inputElement: element, - }), + ...inputProps, class: concatClassNames(['aa-Input', classNames.input]), }); diff --git a/packages/autocomplete-js/src/createAutocompleteDom.ts b/packages/autocomplete-js/src/createAutocompleteDom.ts index 36681668f..e4e844f77 100644 --- a/packages/autocomplete-js/src/createAutocompleteDom.ts +++ b/packages/autocomplete-js/src/createAutocompleteDom.ts @@ -1,5 +1,6 @@ import { AutocompleteApi as AutocompleteCoreApi, + AutocompleteScopeApi, BaseItem, } from '@algolia/autocomplete-core'; @@ -21,54 +22,56 @@ import { AutocompleteState, } from './types'; -type CreateDomProps = AutocompletePropGetters & { +type CreateDomProps = { classNames: Partial; autocomplete: AutocompleteCoreApi; state: AutocompleteState; + propGetters: AutocompletePropGetters; + autocompleteScopeApi: AutocompleteScopeApi; }; export function createAutocompleteDom({ autocomplete, classNames, - getRootProps, - getFormProps, - getLabelProps, - getInputProps, - getPanelProps, + propGetters, state, + autocompleteScopeApi, }: CreateDomProps): AutocompleteDom { - const root = Root({ - classNames, - ...getRootProps({ - state, - props: autocomplete.getRootProps({}), - }), + const rootProps = propGetters.getRootProps({ + state, + props: autocomplete.getRootProps({}), + ...autocompleteScopeApi, }); + const root = Root({ classNames, ...rootProps }); const inputWrapper = InputWrapper({ classNames }); - const label = Label({ - classNames, - ...getLabelProps({ state, props: autocomplete.getLabelProps({}) }), + const labelProps = propGetters.getLabelProps({ + state, + props: autocomplete.getLabelProps({}), + ...autocompleteScopeApi, }); + const label = Label({ classNames, ...labelProps }); const input = Input({ classNames, state, - getInputProps, + getInputProps: propGetters.getInputProps, getInputPropsCore: autocomplete.getInputProps, + autocompleteScopeApi, }); const submitButton = SubmitButton({ classNames }); const resetButton = ResetButton({ classNames }); const loadingIndicator = LoadingIndicator({ classNames }); - const form = Form({ - classNames, - ...getFormProps({ - state, - props: autocomplete.getFormProps({ inputElement: input }), - }), + const formProps = propGetters.getFormProps({ + state, + props: autocomplete.getFormProps({ inputElement: input }), + ...autocompleteScopeApi, }); - const panel = Panel({ - classNames, - ...getPanelProps({ state, props: autocomplete.getPanelProps({}) }), + const form = Form({ classNames, ...formProps }); + const panelProps = propGetters.getPanelProps({ + state, + props: autocomplete.getPanelProps({}), + ...autocompleteScopeApi, }); + const panel = Panel({ classNames, ...panelProps }); label.appendChild(submitButton); inputWrapper.appendChild(input); diff --git a/packages/autocomplete-js/src/render.ts b/packages/autocomplete-js/src/render.ts index e39145fb9..371bb4336 100644 --- a/packages/autocomplete-js/src/render.ts +++ b/packages/autocomplete-js/src/render.ts @@ -1,4 +1,7 @@ -import { AutocompleteApi as AutocompleteCoreApi } from '@algolia/autocomplete-core'; +import { + AutocompleteApi as AutocompleteCoreApi, + AutocompleteScopeApi, +} from '@algolia/autocomplete-core'; import { BaseItem } from '@algolia/autocomplete-core/src'; import { @@ -24,49 +27,49 @@ type RenderProps = { classNames: Partial; panelRoot: HTMLElement; autocomplete: AutocompleteCoreApi; -} & AutocompleteDom & - AutocompletePropGetters; + propGetters: AutocompletePropGetters; + dom: AutocompleteDom; + autocompleteScopeApi: AutocompleteScopeApi; +}; export function render( renderer: AutocompleteRenderer, { autocomplete, state, - getRootProps, - getInputProps, - getListProps, - getItemProps, + propGetters, classNames, panelRoot, - root, - input, - resetButton, - submitButton, - loadingIndicator, - panel, + dom, + autocompleteScopeApi, }: RenderProps ): () => void { setPropertiesWithoutEvents( - root, - getRootProps({ state, props: autocomplete.getRootProps({}) }) + dom.root, + propGetters.getRootProps({ + state, + props: autocomplete.getRootProps({}), + ...autocompleteScopeApi, + }) ); setPropertiesWithoutEvents( - input, - getInputProps({ + dom.input, + propGetters.getInputProps({ state, - props: autocomplete.getInputProps({ inputElement: input }), - inputElement: input, + props: autocomplete.getInputProps({ inputElement: dom.input }), + inputElement: dom.input, + ...autocompleteScopeApi, }) ); - setPropertiesWithoutEvents(resetButton, { hidden: !state.query }); - setProperties(submitButton, { hidden: state.status === 'stalled' }); - setProperties(loadingIndicator, { hidden: state.status !== 'stalled' }); + setPropertiesWithoutEvents(dom.resetButton, { hidden: !state.query }); + setProperties(dom.submitButton, { hidden: state.status === 'stalled' }); + setProperties(dom.loadingIndicator, { hidden: state.status !== 'stalled' }); - panel.innerHTML = ''; + dom.panel.innerHTML = ''; if (!state.isOpen) { - if (panelRoot.contains(panel)) { - panelRoot.removeChild(panel); + if (panelRoot.contains(dom.panel)) { + panelRoot.removeChild(dom.panel); } return () => {}; @@ -74,11 +77,11 @@ export function render( // We add the panel element to the DOM when it's not yet appended and that the // items are fetched. - if (!panelRoot.contains(panel) && state.status !== 'loading') { - panelRoot.appendChild(panel); + if (!panelRoot.contains(dom.panel) && state.status !== 'loading') { + panelRoot.appendChild(dom.panel); } - panel.classList.toggle('aa-Panel--stalled', state.status === 'stalled'); + dom.panel.classList.toggle('aa-Panel--stalled', state.status === 'stalled'); const sections = state.collections.map(({ source, items }) => { const sectionElement = SourceContainer({ classNames }); @@ -101,16 +104,21 @@ export function render( if (items.length > 0) { const listElement = SourceList({ classNames, - ...getListProps({ state, props: autocomplete.getListProps({}) }), + ...propGetters.getListProps({ + state, + props: autocomplete.getListProps({}), + ...autocompleteScopeApi, + }), }); const listFragment = document.createDocumentFragment(); items.forEach((item) => { const itemElement = SourceItem({ classNames, - ...getItemProps({ + ...propGetters.getItemProps({ state, props: autocomplete.getItemProps({ item, source }), + ...autocompleteScopeApi, }), }); @@ -146,11 +154,11 @@ export function render( }); const panelLayoutElement = PanelLayout({ classNames }); - panel.appendChild(panelLayoutElement); + dom.panel.appendChild(panelLayoutElement); renderer({ root: panelLayoutElement, sections, state }); return () => { - panelRoot.removeChild(panel); + panelRoot.removeChild(dom.panel); }; } diff --git a/packages/autocomplete-js/src/types/AutocompletePropGetters.ts b/packages/autocomplete-js/src/types/AutocompletePropGetters.ts index 6cc3a432a..e90548f7c 100644 --- a/packages/autocomplete-js/src/types/AutocompletePropGetters.ts +++ b/packages/autocomplete-js/src/types/AutocompletePropGetters.ts @@ -1,42 +1,79 @@ import { BaseItem, AutocompleteApi as AutocompleteCoreApi, + AutocompleteScopeApi, } from '@algolia/autocomplete-core'; import { AutocompleteState } from './AutocompleteState'; +type PropsGetterParams = TParam & { + state: AutocompleteState; +} & AutocompleteScopeApi; + export type AutocompletePropGetters = { - getEnvironmentProps(params: { - state: AutocompleteState; - props: ReturnType['getEnvironmentProps']>; - }): ReturnType['getEnvironmentProps']>; - getFormProps(params: { - state: AutocompleteState; - props: ReturnType['getFormProps']>; - }): ReturnType['getFormProps']>; - getInputProps(params: { - state: AutocompleteState; - props: ReturnType['getInputProps']>; - inputElement: HTMLInputElement; - }): ReturnType['getInputProps']>; - getItemProps(params: { - state: AutocompleteState; - props: ReturnType['getItemProps']>; - }): ReturnType['getItemProps']>; - getLabelProps(params: { - state: AutocompleteState; - props: ReturnType['getLabelProps']>; - }): ReturnType['getLabelProps']>; - getListProps(params: { - state: AutocompleteState; - props: ReturnType['getListProps']>; - }): ReturnType['getListProps']>; - getPanelProps(params: { - state: AutocompleteState; - props: ReturnType['getPanelProps']>; - }): ReturnType['getPanelProps']>; - getRootProps(params: { - state: AutocompleteState; - props: ReturnType['getRootProps']>; - }): ReturnType['getRootProps']>; + getEnvironmentProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getEnvironmentProps']>; + } + > + ): ReturnType['getEnvironmentProps']>; + getFormProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getFormProps']>; + } + > + ): ReturnType['getFormProps']>; + getInputProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getInputProps']>; + inputElement: HTMLInputElement; + } + > + ): ReturnType['getInputProps']>; + getItemProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getItemProps']>; + } + > + ): ReturnType['getItemProps']>; + getLabelProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getLabelProps']>; + } + > + ): ReturnType['getLabelProps']>; + getListProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getListProps']>; + } + > + ): ReturnType['getListProps']>; + getPanelProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getPanelProps']>; + } + > + ): ReturnType['getPanelProps']>; + getRootProps( + params: PropsGetterParams< + TItem, + { + props: ReturnType['getRootProps']>; + } + > + ): ReturnType['getRootProps']>; };