@dxos/react-ui-searchlist 0.8.4-main.ae835ea → 0.8.4-main.bc674ce
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +669 -337
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +669 -337
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Combobox/Combobox.d.ts +48 -8
- package/dist/types/src/components/Combobox/Combobox.d.ts.map +1 -1
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts +1 -1
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/types/src/components/Listbox/Listbox.d.ts.map +1 -1
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts +1 -1
- package/dist/types/src/components/SearchList/SearchList.d.ts +83 -20
- package/dist/types/src/components/SearchList/SearchList.d.ts.map +1 -1
- package/dist/types/src/components/SearchList/SearchList.stories.d.ts +10 -7
- package/dist/types/src/components/SearchList/SearchList.stories.d.ts.map +1 -1
- package/dist/types/src/components/SearchList/context.d.ts +33 -0
- package/dist/types/src/components/SearchList/context.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/index.d.ts +5 -0
- package/dist/types/src/components/SearchList/hooks/index.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useGlobalFilter.d.ts +34 -0
- package/dist/types/src/components/SearchList/hooks/useGlobalFilter.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListInput.d.ts +12 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListInput.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListItem.d.ts +10 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListItem.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListResults.d.ts +36 -0
- package/dist/types/src/components/SearchList/hooks/useSearchListResults.d.ts.map +1 -0
- package/dist/types/src/components/SearchList/index.d.ts +1 -0
- package/dist/types/src/components/SearchList/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -17
- package/src/components/Combobox/Combobox.stories.tsx +9 -4
- package/src/components/Combobox/Combobox.tsx +35 -14
- package/src/components/Listbox/Listbox.stories.tsx +1 -1
- package/src/components/Listbox/Listbox.tsx +8 -3
- package/src/components/SearchList/SearchList.stories.tsx +500 -30
- package/src/components/SearchList/SearchList.tsx +458 -62
- package/src/components/SearchList/context.ts +43 -0
- package/src/components/SearchList/hooks/index.ts +8 -0
- package/src/components/SearchList/hooks/useGlobalFilter.tsx +61 -0
- package/src/components/SearchList/hooks/useSearchListInput.ts +14 -0
- package/src/components/SearchList/hooks/useSearchListItem.ts +14 -0
- package/src/components/SearchList/hooks/useSearchListResults.ts +104 -0
- package/src/components/SearchList/index.ts +1 -0
- package/src/translations.ts +1 -1
- package/src/types/command-score.d.ts +16 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type for a filter function that filters an array of objects.
|
|
9
|
+
*/
|
|
10
|
+
export type FilterFunction<T = any> = (objects: T[]) => T[];
|
|
11
|
+
|
|
12
|
+
type GlobalFilterContextType = {
|
|
13
|
+
/** The current filter function. */
|
|
14
|
+
filter?: FilterFunction;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const GlobalFilterContext = createContext<GlobalFilterContextType>({});
|
|
18
|
+
|
|
19
|
+
export type GlobalFilterProviderProps = PropsWithChildren<{
|
|
20
|
+
/** The filter function to apply globally. */
|
|
21
|
+
filter?: FilterFunction;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Provider that makes a filter function available globally.
|
|
26
|
+
* Used by plugin-search to provide its regex-based filtering.
|
|
27
|
+
*/
|
|
28
|
+
export const GlobalFilterProvider = ({ children, filter }: GlobalFilterProviderProps) => {
|
|
29
|
+
const value = useMemo(() => ({ filter }), [filter]);
|
|
30
|
+
return <GlobalFilterContext.Provider value={value}>{children}</GlobalFilterContext.Provider>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook to access the global filter context.
|
|
35
|
+
* Returns the filter function if one is provided.
|
|
36
|
+
*/
|
|
37
|
+
export const useGlobalFilter = () => {
|
|
38
|
+
return useContext(GlobalFilterContext);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hook that applies the global filter to an array of objects.
|
|
43
|
+
* If no filter is set, returns the original objects unchanged.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const objects = useQuery(db, Filter.everything());
|
|
47
|
+
* const filteredObjects = useGlobalFilteredObjects(objects);
|
|
48
|
+
*/
|
|
49
|
+
export const useGlobalFilteredObjects = <T extends Record<string, any>>(objects?: T[]): T[] => {
|
|
50
|
+
const { filter } = useGlobalFilter();
|
|
51
|
+
|
|
52
|
+
return useMemo(() => {
|
|
53
|
+
if (!objects) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
if (!filter) {
|
|
57
|
+
return objects;
|
|
58
|
+
}
|
|
59
|
+
return filter(objects);
|
|
60
|
+
}, [objects, filter]);
|
|
61
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useSearchListInputContext } from '../context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook to access the search context for custom input implementations.
|
|
9
|
+
*/
|
|
10
|
+
export const useSearchListInput = () => {
|
|
11
|
+
const { query, onQueryChange, selectedValue, onSelectedValueChange, getItemValues, triggerSelect } =
|
|
12
|
+
useSearchListInputContext('useSearchListInput');
|
|
13
|
+
return { query, onQueryChange, selectedValue, onSelectedValueChange, getItemValues, triggerSelect };
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useSearchListItemContext } from '../context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook to access selection state for custom item renderers.
|
|
9
|
+
* Returns the current selected value and registration functions.
|
|
10
|
+
*/
|
|
11
|
+
export const useSearchListItem = () => {
|
|
12
|
+
const { selectedValue, registerItem, unregisterItem } = useSearchListItemContext('useSearchListItem');
|
|
13
|
+
return { selectedValue, registerItem, unregisterItem };
|
|
14
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import commandScore from 'command-score';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
export type UseSearchListResultsOptions<T> = {
|
|
9
|
+
/** Items to filter. */
|
|
10
|
+
items: T[];
|
|
11
|
+
/** Custom filter function. Defaults to filtering by extracted string. */
|
|
12
|
+
filter?: (item: T, query: string) => boolean;
|
|
13
|
+
/** Enable fuzzy filtering using command-score algorithm. Defaults to true. */
|
|
14
|
+
fuzzy?: boolean;
|
|
15
|
+
/** Custom function to extract the searchable string from an item. Defaults to accessing 'label' property if it exists. */
|
|
16
|
+
extract?: (item: T) => string;
|
|
17
|
+
/** Minimum score threshold for fuzzy matches (0-1). Defaults to 0. */
|
|
18
|
+
minScore?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to manage search results with fuzzy filtering (enabled by default).
|
|
23
|
+
* Returns filtered results and a handleSearch function to pass to SearchList.Root.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Default fuzzy filtering using command-score (tries to extract from 'label' property)
|
|
27
|
+
* const { results, handleSearch } = useSearchListResults({ items });
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Disable fuzzy for basic case-insensitive substring match
|
|
31
|
+
* const { results, handleSearch } = useSearchListResults({ items, fuzzy: false });
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Custom extraction for fuzzy filtering
|
|
35
|
+
* const { results, handleSearch } = useSearchListResults({
|
|
36
|
+
* items,
|
|
37
|
+
* extract: (item) => `${item.name} ${item.description}`,
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
export const useSearchListResults = <T = unknown>({
|
|
41
|
+
items,
|
|
42
|
+
filter,
|
|
43
|
+
fuzzy = true,
|
|
44
|
+
extract,
|
|
45
|
+
minScore = 0,
|
|
46
|
+
}: UseSearchListResultsOptions<T>) => {
|
|
47
|
+
const [query, setQuery] = useState<string>('');
|
|
48
|
+
const queryRef = useRef<string>('');
|
|
49
|
+
|
|
50
|
+
// Update results when items change.
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
queryRef.current = '';
|
|
53
|
+
setQuery('');
|
|
54
|
+
}, [items]);
|
|
55
|
+
|
|
56
|
+
const defaultExtract = useCallback((item: T) => {
|
|
57
|
+
// If item is a string, return it directly
|
|
58
|
+
if (typeof item === 'string') {
|
|
59
|
+
return item;
|
|
60
|
+
}
|
|
61
|
+
// Otherwise, try to access 'label' property
|
|
62
|
+
return (item as any)?.label ?? '';
|
|
63
|
+
}, []);
|
|
64
|
+
const extractFn = extract ?? defaultExtract;
|
|
65
|
+
|
|
66
|
+
const defaultFilter = useCallback(
|
|
67
|
+
(item: T, query: string) => {
|
|
68
|
+
const searchable = extractFn(item);
|
|
69
|
+
return searchable.toLowerCase().includes(query.toLowerCase());
|
|
70
|
+
},
|
|
71
|
+
[extractFn],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const filterFn = filter ?? defaultFilter;
|
|
75
|
+
|
|
76
|
+
const handleSearch = useCallback((searchQuery: string) => {
|
|
77
|
+
queryRef.current = searchQuery;
|
|
78
|
+
setQuery(searchQuery);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const results = useMemo(() => {
|
|
82
|
+
const currentQuery = queryRef.current;
|
|
83
|
+
if (!currentQuery) {
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (fuzzy) {
|
|
88
|
+
// Score and filter items using command-score.
|
|
89
|
+
const scored = items
|
|
90
|
+
.map((item) => ({
|
|
91
|
+
item,
|
|
92
|
+
score: commandScore(extractFn(item), currentQuery) as number,
|
|
93
|
+
}))
|
|
94
|
+
.filter(({ score }) => score > minScore)
|
|
95
|
+
.sort((a, b) => b.score - a.score);
|
|
96
|
+
|
|
97
|
+
return scored.map(({ item }) => item);
|
|
98
|
+
} else {
|
|
99
|
+
return items.filter((item) => filterFn(item, currentQuery));
|
|
100
|
+
}
|
|
101
|
+
}, [items, query, filterFn, fuzzy, extractFn, minScore]);
|
|
102
|
+
|
|
103
|
+
return { results, handleSearch };
|
|
104
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
declare module 'command-score' {
|
|
6
|
+
/**
|
|
7
|
+
* Scores how well a string matches a query using a fuzzy matching algorithm.
|
|
8
|
+
* Used by cmdk for its built-in filtering.
|
|
9
|
+
*
|
|
10
|
+
* @param string - The string to score against.
|
|
11
|
+
* @param query - The search query.
|
|
12
|
+
* @returns A score between 0 and 1, where higher values indicate better matches.
|
|
13
|
+
*/
|
|
14
|
+
function commandScore(string: string, query: string): number;
|
|
15
|
+
export default commandScore;
|
|
16
|
+
}
|