From 79828140a79cd299b2c87e0f0c9d4e12b30d8168 Mon Sep 17 00:00:00 2001 From: davideraccagni Date: Fri, 22 Apr 2022 01:40:27 +0200 Subject: [PATCH] AutoComplete updated to support REST service --- docs/src/pages/components/AutoComplete.svx | 84 ++-- docs/src/pages/components/ComboBox.svx | 9 + .../AutoComplete/AutoCompleteSlot.svelte | 35 +- .../AutoComplete/MultipleAutoComplete.svelte | 42 -- src/AutoComplete/AutoComplete.svelte | 384 ++++++++++-------- types/AutoComplete/AutoComplete.svelte.d.ts | 40 +- 6 files changed, 313 insertions(+), 281 deletions(-) delete mode 100644 docs/src/pages/framed/AutoComplete/MultipleAutoComplete.svelte diff --git a/docs/src/pages/components/AutoComplete.svx b/docs/src/pages/components/AutoComplete.svx index 07ec7a33..6f5980ab 100644 --- a/docs/src/pages/components/AutoComplete.svx +++ b/docs/src/pages/components/AutoComplete.svx @@ -5,6 +5,33 @@ components: ["AutoComplete", "AutoCompleteSkeleton"] `AutoComplete` is keyed for performance reasons. @@ -15,10 +42,7 @@ components: ["AutoComplete", "AutoCompleteSkeleton"] ### Default - + ### Custom slot @@ -28,74 +52,42 @@ Override the default slot to customize the display of each item. Access the item ### Hidden label - - -### Format item display text - -Use the `itemToString` prop to format the display of individual items. - - { - return item.text + ' (' + item.id +')' -}} titleText="Contact" selectedId="0" items="{[{id: "0", text: "Slack"}, - {id: "1", text: "Email"}, - {id: "2", text: "Fax"}]}" /> - -### Multiple dropdowns - - + ### Top direction Set `direction` to `"top"` for the dropdown menu to appear above the input. - + ### Light variant - + ### Inline variant - + ### Extra-large size - + ### Small size - + ### Invalid state - + ### Warning state - + ### Disabled state - + ### Skeleton - \ No newline at end of file + diff --git a/docs/src/pages/components/ComboBox.svx b/docs/src/pages/components/ComboBox.svx index 4b4e20f1..bc8f51a0 100644 --- a/docs/src/pages/components/ComboBox.svx +++ b/docs/src/pages/components/ComboBox.svx @@ -18,6 +18,15 @@ items={[ {id: "2", text: "Fax"} ]} /> +### `shouldFilterItem` + + item.text.startsWith(value)} titleText="Contact" placeholder="Select contact method" +items={[ + {id: "0", text: "Slack"}, + {id: "1", text: "Email"}, + {id: "2", text: "Fax"} + ]} /> + ### Custom slot Override the default slot to customize the display of each item. Access the item and index through the `let:` directive. diff --git a/docs/src/pages/framed/AutoComplete/AutoCompleteSlot.svelte b/docs/src/pages/framed/AutoComplete/AutoCompleteSlot.svelte index fa4edf0f..7058b4e3 100644 --- a/docs/src/pages/framed/AutoComplete/AutoCompleteSlot.svelte +++ b/docs/src/pages/framed/AutoComplete/AutoCompleteSlot.svelte @@ -1,15 +1,40 @@ diff --git a/docs/src/pages/framed/AutoComplete/MultipleAutoComplete.svelte b/docs/src/pages/framed/AutoComplete/MultipleAutoComplete.svelte deleted file mode 100644 index 8a7473e5..00000000 --- a/docs/src/pages/framed/AutoComplete/MultipleAutoComplete.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - -
Primary: {primary}
- - - -
Secondary: {secondary}
- - diff --git a/src/AutoComplete/AutoComplete.svelte b/src/AutoComplete/AutoComplete.svelte index 1b07701b..10fa8d9f 100644 --- a/src/AutoComplete/AutoComplete.svelte +++ b/src/AutoComplete/AutoComplete.svelte @@ -7,12 +7,6 @@ * @slot {{ item: AutoCompleteItem; index: number; }} */ - /** - * Set the full list of items - * @type {AutoCompleteItem[]} - */ - export let items = []; - /** * Override the display of a dropdown item * @type {(item: AutoCompleteItem) => string} @@ -32,10 +26,10 @@ export let selectedItem = undefined; /** - * Specify the type of dropdown - * @type {"default" | "inline"} + * Determine if an item should be filtered given the current combobox value + * @type {(value: string) => AutoCompleteItem[]} */ - export let type = "default"; + export let shouldFilterItem = () => []; /** * Specify the direction of the dropdown menu @@ -52,9 +46,6 @@ /** Set to `true` to open the dropdown */ export let open = false; - /** Set to `true` to use the inline variant */ - export let inline = false; - /** Set to `true` to enable the light variant */ export let light = false; @@ -103,80 +94,100 @@ /** Specify the placeholder text */ export let placeholder = null; - import { createEventDispatcher } from "svelte"; + /** + * Obtain a reference to the list HTML element + * @type {null | HTMLDivElement} + */ + export let listRef = null; + + import { createEventDispatcher, afterUpdate, tick } from "svelte"; + import Checkmark from "../icons/Checkmark.svelte"; import WarningFilled from "../icons/WarningFilled.svelte"; import WarningAltFilled from "../icons/WarningAltFilled.svelte"; - import { ListBox, ListBoxMenu, ListBoxMenuItem } from "../ListBox"; + import ListBox from "../ListBox/ListBox.svelte"; + import ListBoxField from "../ListBox/ListBoxField.svelte"; + import ListBoxMenu from "../ListBox/ListBoxMenu.svelte"; + import ListBoxMenuIcon from "../ListBox/ListBoxMenuIcon.svelte"; + import ListBoxMenuItem from "../ListBox/ListBoxMenuItem.svelte"; + import ListBoxSelection from "../ListBox/ListBoxSelection.svelte"; const dispatch = createEventDispatcher(); let filteredItems = []; - + let inputValue = value; + let prevSelectedId = null; let highlightedIndex = -1; - let innerValue = undefined; - function change(dir) { let index = highlightedIndex + dir; - if (index < 0) { index = filteredItems.length - 1; } else if (index >= filteredItems.length) { index = 0; } - highlightedIndex = index; } - function onKeydown(event) { - let key = event.key; - - if (["Enter", "ArrowDown", "ArrowUp"].includes(key)) { - event.preventDefault(); - } - - if (key === "Enter") { - open = !open; - if ( - highlightedIndex > -1 && - filteredItems[highlightedIndex].id !== selectedId - ) { - selectedItem = filteredItems[highlightedIndex]; - selectedId = selectedItem.id; - innerValue = selectedItem.text; - open = false; - } - } else if (key === "Backspace") { - selectedItem = undefined; - selectedId = undefined; - open = innerValue.length > 0 && filteredItems.length > 0; - } else if (key === "Tab") { - open = false; - ref.blur(); - } else if (key === "ArrowDown") { - change(1); - } else if (key === "ArrowUp") { - change(-1); - } else if (key === "Escape") { - innerValue = ""; - dispatch("clear"); - open = false; - } else { - if (!open) open = filteredItems.length > 0; - } + /** + * Clear the combo box programmatically + * @type {(options?: { focus?: boolean; }) => void} + */ + export function clear(options = {}) { + prevSelectedId = null; + highlightedIndex = -1; + highlightedId = undefined; + selectedId = undefined; + selectedItem = undefined; + open = false; + inputValue = ""; + if (options?.focus !== false) ref?.focus(); } + afterUpdate(() => { + if (open) { + ref.focus(); + filteredItems = shouldFilterItem(value); + } else { + highlightedIndex = -1; + filteredItems = []; + if (!selectedItem) { + selectedId = undefined; + inputValue = ""; + highlightedIndex = -1; + highlightedId = undefined; + } else { + // programmatically set inputValue + inputValue = selectedItem.text; + } + } + }); + $: if (selectedId !== undefined) { - dispatch("select", { selectedId, selectedItem }); + if (prevSelectedId !== selectedId) { + prevSelectedId = selectedId; + if (filteredItems?.length === 1 && open) { + selectedId = filteredItems[0].id; + selectedItem = filteredItems[0]; + highlightedIndex = -1; + highlightedId = undefined; + } else { + selectedItem = filteredItems.find((item) => item.id === selectedId); + } + dispatch("select", { selectedId, selectedItem }); + } + } else { + prevSelectedId = selectedId; + selectedItem = undefined; } - $: filteredItems = items.filter( - (item) => innerValue?.length > 0 && item.text.startsWith(innerValue) - ); - $: inline = type === "inline"; - $: if (!open) { - highlightedIndex = -1; - } + $: ariaLabel = $$props["aria-label"] || "Choose an item"; + $: menuId = `menu-${id}`; + $: comboId = `combo-${id}`; + $: highlightedId = filteredItems[highlightedIndex] + ? filteredItems[highlightedIndex].id + : 0; + $: filteredItems = shouldFilterItem(value); + $: value = inputValue; -
- {#if titleText} +
+ {#if titleText && !hideLabel} {/if} - {#if invalid} - - {/if} - {#if !invalid && warn} - - {/if} - + > + + {#if invalid} + + {/if} + {#if !invalid && warn} + + {/if} + {#if inputValue} + + {/if} + + {#if open} - + {#each filteredItems as item, i (item.id)} {itemToString(item)} + {#if selectedItem && selectedItem.id === item.id} + + {/if} {/each} {/if} - {#if !inline && !invalid && !warn && helperText} + {#if !invalid && helperText && !warn}
{/if}
- - diff --git a/types/AutoComplete/AutoComplete.svelte.d.ts b/types/AutoComplete/AutoComplete.svelte.d.ts index c1a147fc..48e70934 100644 --- a/types/AutoComplete/AutoComplete.svelte.d.ts +++ b/types/AutoComplete/AutoComplete.svelte.d.ts @@ -11,13 +11,7 @@ export interface AutoCompleteItem { } export interface AutoCompleteProps - extends svelte.JSX.HTMLAttributes { - /** - * Set the full list of items - * @default [] - */ - items?: AutoCompleteItem[]; - + extends svelte.JSX.HTMLAttributes { /** * Override the display of a dropdown item * @default (item) => item.text || item.id @@ -37,10 +31,10 @@ export interface AutoCompleteProps selectedItem?: AutoCompleteItem; /** - * Specify the type of dropdown - * @default "default" + * Determine if an item should be filtered given the current combobox value + * @default () => [] */ - type?: "default" | "inline"; + shouldFilterItem?: (value: string) => AutoCompleteItem[]; /** * Specify the direction of the dropdown menu @@ -60,12 +54,6 @@ export interface AutoCompleteProps */ open?: boolean; - /** - * Set to `true` to use the inline variant - * @default false - */ - inline?: boolean; - /** * Set to `true` to enable the light variant * @default false @@ -149,6 +137,12 @@ export interface AutoCompleteProps * @default null */ placeholder?: undefined; + + /** + * Obtain a reference to the list HTML element + * @default null + */ + listRef?: null | HTMLDivElement; } export default class AutoComplete extends SvelteComponentTyped< @@ -158,11 +152,17 @@ export default class AutoComplete extends SvelteComponentTyped< selectedId: AutoCompleteItemId; selectedItem: AutoCompleteItem; }>; - change: WindowEventMap["change"]; + keydown: WindowEventMap["keydown"]; + keyup: WindowEventMap["keyup"]; focus: WindowEventMap["focus"]; blur: WindowEventMap["blur"]; - input: WindowEventMap["input"]; - clear: CustomEvent; + clear: WindowEventMap["clear"]; + scroll: WindowEventMap["scroll"]; }, { default: { item: AutoCompleteItem; index: number } } -> {} +> { + /** + * Clear the combo box programmatically + */ + clear: (options?: { focus?: boolean }) => void; +}