Skip to content

Commit

Permalink
feat(js): introduce dropdownPlacement API (#314)
Browse files Browse the repository at this point in the history
  • Loading branch information
francoischalifour authored Sep 4, 2020
1 parent 0510b1b commit 4a52ce5
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 13 deletions.
4 changes: 2 additions & 2 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
{
"path": "packages/autocomplete-js/dist/umd/index.js",
"maxSize": "6.5 kB"
},
"maxSize": "7 kB"
}
]
}
1 change: 1 addition & 0 deletions examples/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const searchClient = algoliasearch(
autocomplete({
container: '#autocomplete',
debug: true,
// dropdownPlacement: 'start',
getSources() {
return [
{
Expand Down
6 changes: 2 additions & 4 deletions examples/js/autocomplete.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,15 @@
z-index: 3;
}

.aa-Autocomplete {
width: 100%;
}

.aa-Dropdown {
background-color: #fff;
border: 1px solid rgba(150, 150, 150, 0.16);
border-radius: 3px;
box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05),
0 8px 16px -4px rgba(35, 38, 59, 0.25);
margin-top: 5px;
max-width: 480px;
position: absolute;
width: 100%;
}

Expand Down
7 changes: 4 additions & 3 deletions examples/js/style.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
body {
margin: 0;
padding: 0;
* {
box-sizing: border-box;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
margin: 0;
padding: 0;
}
96 changes: 96 additions & 0 deletions packages/autocomplete-js/src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
PublicAutocompleteOptions as PublicAutocompleteCoreOptions,
} from '@algolia/autocomplete-core';

import { debounce } from './debounce';
import { getHTMLElement } from './getHTMLElement';
import { setProperties, setPropertiesWithoutEvents } from './setProperties';

Expand Down Expand Up @@ -57,6 +58,10 @@ export interface AutocompleteOptions<TItem>
state: AutocompleteState<TItem>;
}): void;
getSources: GetSources<TItem>;
/**
* @default "input-wrapper-width"
*/
dropdownPlacement: 'start' | 'end' | 'full-width' | 'input-wrapper-width';
}

export interface AutocompleteApi<TItem> extends AutocompleteSetters<TItem> {
Expand All @@ -70,9 +75,75 @@ export interface AutocompleteApi<TItem> extends AutocompleteSetters<TItem> {
destroy(): void;
}

export function getDropdownPositionStyle({
dropdownPlacement,
container,
inputWrapper,
environment = window,
}: Partial<AutocompleteOptions<any>> & {
container: HTMLElement;
inputWrapper: HTMLElement;
}) {
const containerRect = container.getBoundingClientRect();
const top = containerRect.top + containerRect.height;

switch (dropdownPlacement) {
case 'start': {
return {
top,
left: containerRect.left,
};
}

case 'end': {
return {
top,
right:
environment.document.documentElement.clientWidth -
(containerRect.left + containerRect.width),
};
}

case 'full-width': {
return {
top,
left: 0,
right: 0,
// @TODO [IE support] IE doesn't support `"unset"`
// See https://caniuse.com/#feat=css-unset-value
width: 'unset',
maxWidth: 'unset',
};
}

case 'input-wrapper-width': {
const inputWrapperRect = inputWrapper.getBoundingClientRect();

return {
top,
left: inputWrapperRect.left,
right:
environment.document.documentElement.clientWidth -
(inputWrapperRect.left + inputWrapperRect.width),
// @TODO [IE support] IE doesn't support `"unset"`
// See https://caniuse.com/#feat=css-unset-value
width: 'unset',
maxWidth: 'unset',
};
}

default: {
throw new Error(
`The \`dropdownPlacement\` value "${dropdownPlacement}" is not valid.`
);
}
}
}

export function autocomplete<TItem>({
container,
render: renderDropdown = defaultRender,
dropdownPlacement = 'input-wrapper-width',
...props
}: AutocompleteOptions<TItem>): AutocompleteApi<TItem> {
const containerElement = getHTMLElement(container);
Expand All @@ -97,12 +168,32 @@ export function autocomplete<TItem>({
...props,
});

function onResize() {
return debounce(() => {
if (!dropdown.hasAttribute('hidden')) {
setDropdownPosition();
}
}, 100);
}

function setDropdownPosition() {
setProperties(dropdown, {
style: getDropdownPositionStyle({
dropdownPlacement,
container: root,
inputWrapper,
environment: props.environment,
}),
});
}

setProperties(window as any, {
...autocomplete.getEnvironmentProps({
searchBoxElement: form,
dropdownElement: dropdown,
inputElement: input,
}),
onResize,
});
setProperties(root, {
...autocomplete.getRootProps(),
Expand Down Expand Up @@ -239,8 +330,13 @@ export function autocomplete<TItem>({
root.appendChild(dropdown);
containerElement.appendChild(root);

setDropdownPosition();

function destroy() {
containerElement.innerHTML = '';
setProperties(window as any, {
onResize: null,
});
}

return {
Expand Down
11 changes: 11 additions & 0 deletions packages/autocomplete-js/src/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function debounce(fn: Function, time: number) {
let timerId: ReturnType<typeof setTimeout> | undefined = undefined;

return function () {
if (timerId) {
clearTimeout(timerId);
}

timerId = setTimeout(fn(), time);
};
}
11 changes: 7 additions & 4 deletions packages/website/docs/autocomplete-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ This function creates a JavaScript autocomplete experience.

```js title="JavaScript"
import algoliasearch from 'algoliasearch/lite';
import {
autocomplete,
getAlgoliaHits,
} from '@algolia/autocomplete-js';
import { autocomplete, getAlgoliaHits } from '@algolia/autocomplete-js';

const searchClient = algoliasearch(
'latency',
Expand Down Expand Up @@ -63,6 +60,12 @@ import CreateAutocompleteProps from './partials/createAutocomplete-props.md'

<CreateAutocompleteProps />

### `dropdownPlacement`

> `"start" | "end" | "full-width" | "input-wrapper-width" | defaults to `"input-wrapper-width"`
The dropdown horizontal position.

### `render`

> `(params: { root: HTMLElement, sections: HTMLElement[], state: AutocompleteState<TItem> }) => void`
Expand Down

0 comments on commit 4a52ce5

Please sign in to comment.