Skip to content

Commit

Permalink
feat(js): introduce Props API
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour committed Dec 6, 2020
1 parent 72d92b1 commit 04bef73
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 31 deletions.
61 changes: 47 additions & 14 deletions packages/autocomplete-js/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export function autocomplete<TItem extends BaseItem>({
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<TItem>): AutocompleteApi<TItem> {
const { runEffect, cleanupEffects } = createEffectWrapper();
Expand All @@ -41,6 +49,16 @@ export function autocomplete<TItem extends BaseItem>({
props.onStateChange?.(options);
},
});
const initialState: AutocompleteState<TItem> = {
collections: [],
completion: null,
context: {},
isOpen: false,
query: '',
selectedItemId: null,
status: 'idle',
...props.initialState,
};

const {
inputWrapper,
Expand All @@ -53,8 +71,17 @@ export function autocomplete<TItem extends BaseItem>({
root,
panel,
} = createAutocompleteDom({
...autocomplete,
state: initialState,
autocomplete,
classNames,
getEnvironmentProps,
getFormProps,
getInputProps,
getItemProps,
getLabelProps,
getListProps,
getPanelProps,
getRootProps,
});

function setPanelPosition() {
Expand Down Expand Up @@ -92,19 +119,17 @@ export function autocomplete<TItem extends BaseItem>({

runEffect(() => {
const panelRoot = getHTMLElement(panelContainer);
const state: AutocompleteState<TItem> = {
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,
Expand Down Expand Up @@ -136,7 +161,15 @@ export function autocomplete<TItem extends BaseItem>({
}>(({ state }) => {
unmountRef.current = render(renderer, {
state,
...autocomplete,
autocomplete,
getEnvironmentProps,
getFormProps,
getInputProps,
getItemProps,
getLabelProps,
getListProps,
getPanelProps,
getRootProps,
classNames,
panelRoot,
root,
Expand Down
13 changes: 11 additions & 2 deletions packages/autocomplete-js/src/components/Input.ts
Original file line number Diff line number Diff line change
@@ -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<any>['getInputProps'];
state: AutocompleteState<any>;
getInputProps: AutocompletePropGetters<any>['getInputProps'];
getInputPropsCore: AutocompleteCoreApi<any>['getInputProps'];
}>;

export const Input: Component<InputProps, HTMLInputElement> = ({
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]),
});

Expand Down
48 changes: 40 additions & 8 deletions packages/autocomplete-js/src/createAutocompleteDom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,61 @@ import {
Root,
SubmitButton,
} from './components';
import { AutocompleteClassNames, AutocompleteDom } from './types';
import {
AutocompleteClassNames,
AutocompleteDom,
AutocompletePropGetters,
AutocompleteState,
} from './types';

type CreateDomProps<TItem extends BaseItem> = AutocompleteCoreApi<TItem> & {
type CreateDomProps<TItem extends BaseItem> = AutocompletePropGetters<TItem> & {
classNames: Partial<AutocompleteClassNames>;
autocomplete: AutocompleteCoreApi<TItem>;
state: AutocompleteState<TItem>;
};

export function createAutocompleteDom<TItem extends BaseItem>({
autocomplete,
classNames,
getRootProps,
getFormProps,
getLabelProps,
getInputProps,
getPanelProps,
classNames,
state,
}: CreateDomProps<TItem>): 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);
Expand Down
28 changes: 22 additions & 6 deletions packages/autocomplete-js/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { renderTemplate } from './renderTemplate';
import {
AutocompleteClassNames,
AutocompleteDom,
AutocompletePropGetters,
AutocompleteRenderer,
AutocompleteState,
} from './types';
Expand All @@ -22,12 +23,14 @@ type RenderProps<TItem extends BaseItem> = {
state: AutocompleteState<TItem>;
classNames: Partial<AutocompleteClassNames>;
panelRoot: HTMLElement;
} & AutocompleteCoreApi<TItem> &
AutocompleteDom;
autocomplete: AutocompleteCoreApi<TItem>;
} & AutocompleteDom &
AutocompletePropGetters<TItem>;

export function render<TItem extends BaseItem>(
renderer: AutocompleteRenderer<TItem>,
{
autocomplete,
state,
getRootProps,
getInputProps,
Expand All @@ -43,8 +46,18 @@ export function render<TItem extends BaseItem>(
panel,
}: RenderProps<TItem>
): () => 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' });
Expand Down Expand Up @@ -88,14 +101,17 @@ export function render<TItem extends BaseItem>(
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({
Expand Down
4 changes: 3 additions & 1 deletion packages/autocomplete-js/src/types/AutocompleteOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -16,7 +17,8 @@ export type AutocompleteRenderer<TItem extends BaseItem> = (params: {
}) => void;

export interface AutocompleteOptions<TItem extends BaseItem>
extends AutocompleteCoreOptions<TItem> {
extends AutocompleteCoreOptions<TItem>,
Partial<AutocompletePropGetters<TItem>> {
/**
* The container for the Autocomplete search box.
*
Expand Down
42 changes: 42 additions & 0 deletions packages/autocomplete-js/src/types/AutocompletePropGetters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
BaseItem,
AutocompleteApi as AutocompleteCoreApi,
} from '@algolia/autocomplete-core';

import { AutocompleteState } from './AutocompleteState';

export type AutocompletePropGetters<TItem extends BaseItem> = {
getEnvironmentProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getEnvironmentProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getEnvironmentProps']>;
getFormProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getFormProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getFormProps']>;
getInputProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getInputProps']>;
inputElement: HTMLInputElement;
}): ReturnType<AutocompleteCoreApi<TItem>['getInputProps']>;
getItemProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getItemProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getItemProps']>;
getLabelProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getLabelProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getLabelProps']>;
getListProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getListProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getListProps']>;
getPanelProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getPanelProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getPanelProps']>;
getRootProps(params: {
state: AutocompleteState<TItem>;
props: ReturnType<AutocompleteCoreApi<TItem>['getRootProps']>;
}): ReturnType<AutocompleteCoreApi<TItem>['getRootProps']>;
};
1 change: 1 addition & 0 deletions packages/autocomplete-js/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

0 comments on commit 04bef73

Please sign in to comment.