From 04bef73d7b8b23fa2d0d1a0256dc794dab5a8422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Sun, 6 Dec 2020 16:02:31 +0100 Subject: [PATCH] feat(js): introduce Props API --- packages/autocomplete-js/src/autocomplete.ts | 61 ++++++++++++++----- .../autocomplete-js/src/components/Input.ts | 13 +++- .../src/createAutocompleteDom.ts | 48 ++++++++++++--- packages/autocomplete-js/src/render.ts | 28 +++++++-- .../src/types/AutocompleteOptions.ts | 4 +- .../src/types/AutocompletePropGetters.ts | 42 +++++++++++++ packages/autocomplete-js/src/types/index.ts | 1 + 7 files changed, 166 insertions(+), 31 deletions(-) create mode 100644 packages/autocomplete-js/src/types/AutocompletePropGetters.ts diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index ccd2fd100..3ca9ad6fe 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -24,6 +24,14 @@ export function autocomplete({ render: renderer = defaultRenderer, panelPlacement = 'input-wrapper-width', classNames = {}, + getEnvironmentProps = ({ props }) => props, + getFormProps = ({ props }) => props, + getInputProps = ({ props }) => props, + getItemProps = ({ props }) => props, + getLabelProps = ({ props }) => props, + getListProps = ({ props }) => props, + getPanelProps = ({ props }) => props, + getRootProps = ({ props }) => props, ...props }: AutocompleteOptions): AutocompleteApi { const { runEffect, cleanupEffects } = createEffectWrapper(); @@ -41,6 +49,16 @@ export function autocomplete({ props.onStateChange?.(options); }, }); + const initialState: AutocompleteState = { + collections: [], + completion: null, + context: {}, + isOpen: false, + query: '', + selectedItemId: null, + status: 'idle', + ...props.initialState, + }; const { inputWrapper, @@ -53,8 +71,17 @@ export function autocomplete({ root, panel, } = createAutocompleteDom({ - ...autocomplete, + state: initialState, + autocomplete, classNames, + getEnvironmentProps, + getFormProps, + getInputProps, + getItemProps, + getLabelProps, + getListProps, + getPanelProps, + getRootProps, }); function setPanelPosition() { @@ -92,19 +119,17 @@ export function autocomplete({ runEffect(() => { const panelRoot = getHTMLElement(panelContainer); - const state: AutocompleteState = { - collections: [], - completion: null, - context: {}, - isOpen: false, - query: '', - selectedItemId: null, - status: 'idle', - ...props.initialState, - }; render(renderer, { - state, - ...autocomplete, + state: initialState, + autocomplete, + getEnvironmentProps, + getFormProps, + getInputProps, + getItemProps, + getLabelProps, + getListProps, + getPanelProps, + getRootProps, classNames, panelRoot, root, @@ -136,7 +161,15 @@ export function autocomplete({ }>(({ state }) => { unmountRef.current = render(renderer, { state, - ...autocomplete, + autocomplete, + getEnvironmentProps, + getFormProps, + getInputProps, + getItemProps, + getLabelProps, + getListProps, + getPanelProps, + getRootProps, classNames, panelRoot, root, diff --git a/packages/autocomplete-js/src/components/Input.ts b/packages/autocomplete-js/src/components/Input.ts index dac6122f8..85b47e5bf 100644 --- a/packages/autocomplete-js/src/components/Input.ts +++ b/packages/autocomplete-js/src/components/Input.ts @@ -1,19 +1,28 @@ import { AutocompleteApi as AutocompleteCoreApi } from '@algolia/autocomplete-core'; +import { AutocompletePropGetters, AutocompleteState } from '../types'; import { Component, WithClassNames } from '../types/Component'; import { concatClassNames, setProperties } from '../utils'; type InputProps = WithClassNames<{ - getInputProps: AutocompleteCoreApi['getInputProps']; + state: AutocompleteState; + getInputProps: AutocompletePropGetters['getInputProps']; + getInputPropsCore: AutocompleteCoreApi['getInputProps']; }>; export const Input: Component = ({ classNames, getInputProps, + getInputPropsCore, + state, }) => { const element = document.createElement('input'); setProperties(element, { - ...getInputProps({ inputElement: element }), + ...getInputProps({ + state, + props: getInputPropsCore({ inputElement: element }), + inputElement: element, + }), class: concatClassNames(['aa-Input', classNames.input]), }); diff --git a/packages/autocomplete-js/src/createAutocompleteDom.ts b/packages/autocomplete-js/src/createAutocompleteDom.ts index f3a14688f..36681668f 100644 --- a/packages/autocomplete-js/src/createAutocompleteDom.ts +++ b/packages/autocomplete-js/src/createAutocompleteDom.ts @@ -14,29 +14,61 @@ import { Root, SubmitButton, } from './components'; -import { AutocompleteClassNames, AutocompleteDom } from './types'; +import { + AutocompleteClassNames, + AutocompleteDom, + AutocompletePropGetters, + AutocompleteState, +} from './types'; -type CreateDomProps = AutocompleteCoreApi & { +type CreateDomProps = AutocompletePropGetters & { classNames: Partial; + autocomplete: AutocompleteCoreApi; + state: AutocompleteState; }; export function createAutocompleteDom({ + autocomplete, + classNames, getRootProps, getFormProps, getLabelProps, getInputProps, getPanelProps, - classNames, + state, }: CreateDomProps): AutocompleteDom { - const root = Root({ classNames, ...getRootProps({}) }); + const root = Root({ + classNames, + ...getRootProps({ + state, + props: autocomplete.getRootProps({}), + }), + }); const inputWrapper = InputWrapper({ classNames }); - const label = Label({ classNames, ...getLabelProps({}) }); - const input = Input({ classNames, getInputProps }); + const label = Label({ + classNames, + ...getLabelProps({ state, props: autocomplete.getLabelProps({}) }), + }); + const input = Input({ + classNames, + state, + getInputProps, + getInputPropsCore: autocomplete.getInputProps, + }); const submitButton = SubmitButton({ classNames }); const resetButton = ResetButton({ classNames }); const loadingIndicator = LoadingIndicator({ classNames }); - const form = Form({ classNames, ...getFormProps({ inputElement: input }) }); - const panel = Panel({ classNames, ...getPanelProps({}) }); + const form = Form({ + classNames, + ...getFormProps({ + state, + props: autocomplete.getFormProps({ inputElement: input }), + }), + }); + const panel = Panel({ + classNames, + ...getPanelProps({ state, props: autocomplete.getPanelProps({}) }), + }); label.appendChild(submitButton); inputWrapper.appendChild(input); diff --git a/packages/autocomplete-js/src/render.ts b/packages/autocomplete-js/src/render.ts index 332e09e59..e39145fb9 100644 --- a/packages/autocomplete-js/src/render.ts +++ b/packages/autocomplete-js/src/render.ts @@ -13,6 +13,7 @@ import { renderTemplate } from './renderTemplate'; import { AutocompleteClassNames, AutocompleteDom, + AutocompletePropGetters, AutocompleteRenderer, AutocompleteState, } from './types'; @@ -22,12 +23,14 @@ type RenderProps = { state: AutocompleteState; classNames: Partial; panelRoot: HTMLElement; -} & AutocompleteCoreApi & - AutocompleteDom; + autocomplete: AutocompleteCoreApi; +} & AutocompleteDom & + AutocompletePropGetters; export function render( renderer: AutocompleteRenderer, { + autocomplete, state, getRootProps, getInputProps, @@ -43,8 +46,18 @@ export function render( panel, }: RenderProps ): () => void { - setPropertiesWithoutEvents(root, getRootProps({})); - setPropertiesWithoutEvents(input, getInputProps({ inputElement: input })); + setPropertiesWithoutEvents( + root, + getRootProps({ state, props: autocomplete.getRootProps({}) }) + ); + setPropertiesWithoutEvents( + input, + getInputProps({ + state, + props: autocomplete.getInputProps({ inputElement: input }), + inputElement: input, + }) + ); setPropertiesWithoutEvents(resetButton, { hidden: !state.query }); setProperties(submitButton, { hidden: state.status === 'stalled' }); setProperties(loadingIndicator, { hidden: state.status !== 'stalled' }); @@ -88,14 +101,17 @@ export function render( if (items.length > 0) { const listElement = SourceList({ classNames, - ...getListProps(), + ...getListProps({ state, props: autocomplete.getListProps({}) }), }); const listFragment = document.createDocumentFragment(); items.forEach((item) => { const itemElement = SourceItem({ classNames, - ...getItemProps({ item, source }), + ...getItemProps({ + state, + props: autocomplete.getItemProps({ item, source }), + }), }); renderTemplate({ diff --git a/packages/autocomplete-js/src/types/AutocompleteOptions.ts b/packages/autocomplete-js/src/types/AutocompleteOptions.ts index ee4f2ce9f..fbb515f68 100644 --- a/packages/autocomplete-js/src/types/AutocompleteOptions.ts +++ b/packages/autocomplete-js/src/types/AutocompleteOptions.ts @@ -6,6 +6,7 @@ import { import { MaybePromise } from '@algolia/autocomplete-shared'; import { AutocompleteClassNames } from './AutocompleteClassNames'; +import { AutocompletePropGetters } from './AutocompletePropGetters'; import { AutocompleteSource } from './AutocompleteSource'; import { AutocompleteState } from './AutocompleteState'; @@ -16,7 +17,8 @@ export type AutocompleteRenderer = (params: { }) => void; export interface AutocompleteOptions - extends AutocompleteCoreOptions { + extends AutocompleteCoreOptions, + Partial> { /** * The container for the Autocomplete search box. * diff --git a/packages/autocomplete-js/src/types/AutocompletePropGetters.ts b/packages/autocomplete-js/src/types/AutocompletePropGetters.ts new file mode 100644 index 000000000..6cc3a432a --- /dev/null +++ b/packages/autocomplete-js/src/types/AutocompletePropGetters.ts @@ -0,0 +1,42 @@ +import { + BaseItem, + AutocompleteApi as AutocompleteCoreApi, +} from '@algolia/autocomplete-core'; + +import { AutocompleteState } from './AutocompleteState'; + +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']>; +}; diff --git a/packages/autocomplete-js/src/types/index.ts b/packages/autocomplete-js/src/types/index.ts index 24c8f123c..79ee22387 100644 --- a/packages/autocomplete-js/src/types/index.ts +++ b/packages/autocomplete-js/src/types/index.ts @@ -3,5 +3,6 @@ export * from './AutocompleteClassNames'; export * from './AutocompleteCollection'; export * from './AutocompleteDom'; export * from './AutocompleteOptions'; +export * from './AutocompletePropGetters'; export * from './AutocompleteSource'; export * from './AutocompleteState';