UNPKG

@react-md/autocomplete

Version:

Create an accessible autocomplete component that allows a user to get real-time suggestions as they type within an input. This component can also be hooked up to a backend API that handles additional filtering or sorting.

314 lines (275 loc) 10.3 kB
import type { CSSProperties, ReactNode } from "react"; import type { ListboxOptionProps, TextFieldProps } from "@react-md/form"; import type { RenderConditionalPortalProps } from "@react-md/portal"; import type { CalculateFixedPositionOptions, CaseInsensitiveOptions, PositionWidth, } from "@react-md/utils"; /** * The supported autocompletion types. * * - "none" - The autocomplete will not filter any results and will just show a * dropdown list of suggestions instead. * - "inline" - The first match will appear inline as the user types by using a * selection range to highlight the remaining match characters. * - "list" - The autocomplete will filter the results and show a dropdown list * of matches based on the current text field's value. * - "both" - A combination of both the `"inline"` and `"list"` autocomplete * behaviors * * Note: "inline" versions still aren't actually supported... */ export type AutoCompletion = "none" | "inline" | "list" | "both"; /** * The supported data that can be filtered and autocompleted. When the data is * an object, the searchable value and display label can be extracted with the * provided getter functions and `labelKey`/`valueKey` props. */ export type AutoCompleteData = string | ListboxOptionProps; /** * The autocomplete supports a fuzzy filter and a case insensitive filter * function out of the box. If you don't want any filtering to happen because * the filtering is done through an API call or somewhere else, you can use the * `"none"` value instead. */ export type PreconfiguredFilterFunction = "fuzzy" | "case-insensitive" | "none"; /** * The filter options provided to the filter function. */ export type FilterFunctionOptions<O extends {} = {}> = O & CaseInsensitiveOptions<AutoCompleteData>; /** * The autocomplete works with a filter function that takes in the current * search query, the list of data, and search options to return a new filtered * list. If the default fuzzy filter and case insensitive filters don't match * your use cases, you can also provide your own function that matches this api * instead. */ export type FilterFunction<O extends {} = {}> = ( query: string, data: readonly AutoCompleteData[], options: FilterFunctionOptions<O> ) => readonly AutoCompleteData[]; /** * Either a preconfigured/provided filter function of the autocomplete or a * custom function to use. */ export type AutoCompleteFilterFunction<O extends {} = {}> = | PreconfiguredFilterFunction | FilterFunction<O>; /** * The shape of the autocomplete result. */ export interface AutoCompleteResult { /** * The stringified value of the autocomplete data by using `getResultValue` on * the `result`. This is really just used for the default behavior of * autocompleting the text field's value to this value. */ readonly value: string; /** * The index of the result in the **filtered data list**. */ readonly index: number; /** * The current autocomplete result. */ readonly result: Readonly<AutoCompleteData>; /** * The index of the result in the **original data list**. */ readonly dataIndex: number; /** * The list of data that has been filtered based on the current value. */ readonly filteredData: readonly AutoCompleteData[]; } /** * The function to call whenever the value is auto completed by: * - clicking an item with the mouse or touch * - keyboard focusing a result and pressing enter */ export type AutoCompleteHandler = (result: AutoCompleteResult) => void; export interface AutoCompleteListboxPositionOptions extends Omit<CalculateFixedPositionOptions, "width"> { /** * The sizing behavior for the listbox. It will default to have the same width * as the select button, but it is also possible to either have the * `min-width` be the width of the select button or just automatically * determine the width. * * The sizing behavior will always ensure that the left and right bounds of * the listbox appear within the viewport. */ listboxWidth?: PositionWidth; /** * An optional style to also apply to the listbox element showing all the * matches. */ listboxStyle?: CSSProperties; /** * Boolean if the select's listbox should not hide if the user resizes the * browser while it is visible. */ closeOnResize?: boolean; /** * Boolean if the select's listbox should not hide if the user scrolls the * page while it is visible. */ closeOnScroll?: boolean; } export interface AutoCompleteProps extends Omit<TextFieldProps, "type">, RenderConditionalPortalProps, AutoCompleteListboxPositionOptions { /** * The id to use for the AutoComplete and is required for a11y to fulfill the * `combobox` role. This id will be passed directly to the `<input>` element * and prefixed for all the other id-required elements. */ id: string; /** * @see AutoCompletion */ autoComplete?: AutoCompletion; /** * Boolean if the text field's value should be cleared when the value is * autocompleted. This is useful when also adding custom `onAutoComplete` * behavior. */ clearOnAutoComplete?: boolean; /** * Boolean if the list of suggestions should no longer appear immediately once * the text field is focused and there is at least one item in the `data` * list. If this is set to `false`, the menu will only be shown when: * * - a letter is added or removed from the text field * - the user clicks it again * - using the alt+arrow-down keyboard shortcut * * If this prop is omitted, the show on focus behavior will be disabled on * touch devices since touch device's soft keyboards do a lot of funky things * with the viewport and scroll behavior. This makes it so the native viewport * and scroll behavior actions are normally finished before the suggestions * appear. */ disableShowOnFocus?: boolean; /** * The list of data that should be autocompleted based on the provided filter. */ data: readonly AutoCompleteData[]; /** * @see AutoCompleteFilterFunction */ filter?: AutoCompleteFilterFunction; /** * An optional object of options to provide to the filter function. This will * be defaulted to work with the fuzzy filter and case-insensitive filter * functions to trim whitespace before doing the comparisons. */ filterOptions?: FilterFunctionOptions; /** * Boolean if the filter function should still be called when there is no * value in the text field. This normally defaults to `false` so that the * `data` is just returned, but it can be useful with a custom filter function * that returns different data while there is no value. */ filterOnNoValue?: boolean; /** * An optional className to also apply to the listbox element showing all the * matches. */ listboxClassName?: string; /** * Boolean if the result list labels should be updated so that each matching * letter is bolded. This only works when the data list is a list of strings, * or the `label` is a string and when the letters appear in order. This will * always be `false` if the `filter` prop is set to `"fuzzy"`. */ highlight?: boolean; /** * Boolean if the highlight functionality should no longer stop after the * first match and instead highlight all matches of the search string within * the label for an item. */ highlightReapeating?: boolean; /** * An optional style to apply to the `<span>` surrounding the matched text * when the `highlight` prop is enabled. */ highlightStyle?: CSSProperties; /** * An optional className to apply to the `<span>` surrounding the matched text * when the `highlight` prop is enabled. */ highlightClassName?: string; /** * The key to use to extract a label from a result when the provided data list * is a list of objects. */ labelKey?: string; /** * The key to use to extract a searchable value string from a result when the * provided data list is a list of objects. */ valueKey?: string; /** * A function to call that will generate an id for each result in the * autocomplete's listbox. These ids are required for a11y as it'll be used * with the `aria-activedescendant` movement within the autocomplete. */ getResultId?(id: string, index: number): string; /** * A function to call that will get a renderable label or children to display * for a result in the autocomplete's list of results. The default behavior * will be to return the result itself if it is a string, otherwise try to * return the `children` or `labelKey` attribute if it is an object. */ getResultLabel?( data: Readonly<AutoCompleteData>, labelKey: string, query: string ): ReactNode; /** * A function to call that will extract a searchable value string from each * result. This **must** return a string and will prevent the autocomplete * from filtering data with the built in filter functions. */ getResultValue?(datum: Readonly<AutoCompleteData>, valueKey: string): string; /** * @see AutoCompleteHandler */ onAutoComplete?: AutoCompleteHandler; /** * An optional list of keys that should be omitted from your `data` item * before passing it to the suggestion `Option`. This is useful if you have * additional metadata in your data objects that should not be passed as DOM * attributes. * * * ```ts * const item = { __id: 9432432, name: "Item", value: "guid" } * * // don't want to pass `__id` to the Option * const omitKeys = ["__id"]; *``` */ omitKeys?: readonly string[]; /** * Any optional children to display before the matched results in the * autocomplete's menu. This should normally be for any presentational data or * things that should not be searchable. * * @remarks \@since 2.1.0 */ beforeResultsChildren?: ReactNode; /** * Any optional children to display after the matched results in the * autocomplete's menu. This should normally be for any presentational data or * things that should not be searchable. * * @remarks \@since 2.1.0 */ afterResultsChildren?: ReactNode; }