diff --git a/package-lock.json b/package-lock.json index 9dae798b71..929edc1854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "dompurify": "^3.1.3", "dotenv": "^16.0.3", "formik": "^2.2.9", + "fuse.js": "^7.0.0", "hex-color-regex": "^1.1.0", "history": "^5.3.0", "i18next": "^22.1.4", @@ -5955,6 +5956,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuse.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/package.json b/package.json index 458480c4f2..70d863dbd5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dompurify": "^3.1.3", "dotenv": "^16.0.3", "formik": "^2.2.9", + "fuse.js": "^7.0.0", "hex-color-regex": "^1.1.0", "history": "^5.3.0", "i18next": "^22.1.4", diff --git a/src/components/forms/Combobox.tsx b/src/components/forms/Combobox.tsx index db2bea8a83..dcb22a547f 100644 --- a/src/components/forms/Combobox.tsx +++ b/src/components/forms/Combobox.tsx @@ -22,6 +22,7 @@ import { Alert } from '../Alert'; import { useColorScheme } from '$app/common/colors'; import { styled } from 'styled-components'; import { Spinner } from '../Spinner'; +import Fuse from 'fuse.js'; export interface Entry { id: number | string; @@ -66,6 +67,7 @@ export interface ComboboxStaticProps { isDataLoading?: boolean; onInputValueChange?: (value: string) => void; compareOnlyByValue?: boolean; + onFilter?: (entries: Entry[]) => unknown; } export type Nullable = T | null; @@ -107,6 +109,7 @@ export function Combobox({ onEmptyValues, onFocus, onInputValueChange, + onFilter, }: ComboboxStaticProps) { const [inputValue, setInputValue] = useState( String(inputOptions.value ?? '') @@ -122,15 +125,9 @@ export function Combobox({ let filteredOptions = inputValue === '' ? entries - : entries.filter( - (entry) => - entry.label?.toLowerCase()?.includes(inputValue?.toLowerCase()) || - entry.value - ?.toString() - ?.toLowerCase() - ?.includes(inputValue?.toLowerCase()) || - entry.searchable.toLowerCase().includes(inputValue?.toLowerCase()) - ); + : new Fuse(entries, { keys: ['id', 'label', 'searchable'] }) + .search(inputValue) + .map((v) => v.item); filteredOptions = filteredOptions.filter((entry) => exclude.length > 0 ? !exclude.includes(entry.value) : true @@ -291,6 +288,10 @@ export function Combobox({ useDebounce( () => { + if (onFilter) { + onFilter(filteredOptions); + } + if (!onEmptyValues) { return; } @@ -471,6 +472,7 @@ export function ComboboxStatic({ clearInputAfterSelection, isDataLoading, compareOnlyByValue, + onFilter, }: ComboboxStaticProps) { const [t] = useTranslation(); const [selectedValue, setSelectedValue] = useState(null); @@ -480,15 +482,9 @@ export function ComboboxStatic({ let filteredValues = query === '' ? entries - : entries.filter( - (entry) => - entry.label?.toLowerCase()?.includes(query?.toLowerCase()) || - entry.value - ?.toString() - ?.toLowerCase() - ?.includes(query?.toLowerCase()) || - entry.searchable.toLowerCase().includes(query?.toLowerCase()) - ); + : new Fuse(entries, { keys: ['id', 'label', 'value', 'searchable'] }) + .search(query) + .map((v) => v.item); filteredValues = filteredValues.filter((entry) => exclude.length > 0 ? !exclude.includes(entry.value) : true @@ -509,6 +505,10 @@ export function ComboboxStatic({ useDebounce( () => { + if (onFilter) { + onFilter(filteredValues); + } + if (!onEmptyValues) { return; } @@ -836,6 +836,7 @@ export function ComboboxAsync({ const [entries, setEntries] = useState[]>([]); const [url, setUrl] = useState(endpoint); const [enableQuery, setEnableQuery] = useState(false); + const [filtered, setFiltered] = useState[]>([]); useEffect(() => { setUrl(endpoint); @@ -973,7 +974,11 @@ export function ComboboxAsync({ setUrl((c) => { const url = new URL(c); - url.searchParams.set('filter', query); + console.log({ query, filtered }); + + if (filtered.length === 0 || query === '') { + url.searchParams.set('filter', query); + } return url.href; }); @@ -1001,6 +1006,7 @@ export function ComboboxAsync({ onInputValueChange={onInputValueChange} onEmptyValues={onEmptyValues} compareOnlyByValue={compareOnlyByValue} + onFilter={setFiltered} /> ); } @@ -1025,6 +1031,7 @@ export function ComboboxAsync({ isDataLoading={isLoading} onInputValueChange={onInputValueChange} compareOnlyByValue={compareOnlyByValue} + onFilter={setFiltered} /> ); }