@bsuite/ui 1.3.2 → 1.3.4-next.0
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/entity-selector.d.ts.map +1 -1
- package/dist/entity-selector.js +79 -14
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-selector.d.ts","sourceRoot":"","sources":["../src/entity-selector.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"entity-selector.d.ts","sourceRoot":"","sources":["../src/entity-selector.tsx"],"names":[],"mappings":"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"}
|
package/dist/entity-selector.js
CHANGED
|
@@ -8,6 +8,45 @@ import { Popover, PopoverContent, PopoverTrigger } from './popover.js';
|
|
|
8
8
|
import { StatusBadge } from './status-badge.js';
|
|
9
9
|
import { cn } from './utils.js';
|
|
10
10
|
// ============================================================================
|
|
11
|
+
// SEARCH WORDS
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* What separates one search word from the next.
|
|
15
|
+
*
|
|
16
|
+
* PostgREST ends an unquoted `or` value at `,` or `)`, and a backslash escapes
|
|
17
|
+
* nothing outside double quotes. So an escaped `\)` reaches Postgres as a bare
|
|
18
|
+
* `\` at the end of the pattern ("LIKE pattern must not end with escape
|
|
19
|
+
* character"), and a search for "Acme Pty Ltd (WA)" showed that raw error. These
|
|
20
|
+
* characters cannot be escaped in an unquoted value, so they separate words,
|
|
21
|
+
* the same rule crm7's `escapeSearchTerm` follows. The LIKE wildcards and escape
|
|
22
|
+
* a person types by accident (`%` `_` `*` `\`) separate words for the same
|
|
23
|
+
* reason: they are never meant as patterns here.
|
|
24
|
+
*/
|
|
25
|
+
const SEARCH_WORD_SEPARATORS = ',()"%_*\\';
|
|
26
|
+
/** A pasted paragraph would otherwise add one `or` parameter per word, times the columns. */
|
|
27
|
+
const MAX_SEARCH_WORDS = 8;
|
|
28
|
+
/** The words of a search, each of which must match some column. Empty when there is nothing to match. */
|
|
29
|
+
function searchWords(term) {
|
|
30
|
+
const words = [];
|
|
31
|
+
let word = '';
|
|
32
|
+
for (const character of term) {
|
|
33
|
+
if (character.trim() === '' || SEARCH_WORD_SEPARATORS.includes(character)) {
|
|
34
|
+
if (word) {
|
|
35
|
+
words.push(word);
|
|
36
|
+
if (words.length === MAX_SEARCH_WORDS)
|
|
37
|
+
return words;
|
|
38
|
+
word = '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
word += character;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (word)
|
|
46
|
+
words.push(word);
|
|
47
|
+
return words;
|
|
48
|
+
}
|
|
49
|
+
// ============================================================================
|
|
11
50
|
// COMPONENT
|
|
12
51
|
// ============================================================================
|
|
13
52
|
export function EntitySelector({ supabaseClient, logger = console, table, schema, value, onSelect, displayField, secondaryField, searchColumns, selectColumns = '*', filterFn, placeholder = 'Search...', disabled = false, onQuickAdd, quickAddLabel = 'Create new', minChars = 0, debounceMs = 250, limit = 50, orderBy = 'created_at', orderAscending = false, clearable = true, className, selectedLabel, onQueryStateChange, renderRow, id, name, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-labelledby': ariaLabelledBy, resolveValueTable, resolveValueSchema, resolveValueSelectColumns, suggestedIds, suggestedLabel = 'Already linked', }) {
|
|
@@ -29,6 +68,10 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
29
68
|
const [error, setError] = useState(null);
|
|
30
69
|
const debounceRef = useRef(null);
|
|
31
70
|
const hasLoadedInitial = useRef(false);
|
|
71
|
+
// Search requests can finish out of order. Increment on every query change,
|
|
72
|
+
// including the debounce interval, so an earlier result cannot repaint the
|
|
73
|
+
// options for the text currently shown in the input.
|
|
74
|
+
const searchGeneration = useRef(0);
|
|
32
75
|
/**
|
|
33
76
|
* Returns a schema-scoped `.from(table)` builder. Defaults to the public
|
|
34
77
|
* schema (identical to the pre-existing `supabaseClient.from(table)`
|
|
@@ -97,6 +140,7 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
97
140
|
}, [value, resolveValueTable, resolveTableName, resolveSelectColumns, fromResolveTable]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
98
141
|
// ── Search / load items ────────────────────────────────────────────────
|
|
99
142
|
const fetchItems = useCallback(async (searchTerm) => {
|
|
143
|
+
const generation = ++searchGeneration.current;
|
|
100
144
|
setIsLoading(true);
|
|
101
145
|
setError(null);
|
|
102
146
|
try {
|
|
@@ -121,17 +165,16 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
121
165
|
if (filterFn) {
|
|
122
166
|
q = filterFn(q);
|
|
123
167
|
}
|
|
124
|
-
// Apply search if term is long enough
|
|
168
|
+
// Apply search if term is long enough.
|
|
169
|
+
// EVERY WORD must match SOME column. One `.or()` per word: PostgREST
|
|
170
|
+
// ANDs separate `or` params. Matching the whole phrase against each
|
|
171
|
+
// column alone found "Nguyen" but not "Steve Nguyen", because no
|
|
172
|
+
// single column holds a full name (crm7#1127 F12). Commas separate
|
|
173
|
+
// words too, so "Nguyen, Steve" works.
|
|
125
174
|
if (searchTerm.length >= Math.max(minChars, 1)) {
|
|
126
|
-
const
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
.replace(/,/g, '\\,')
|
|
130
|
-
.replace(/\(/g, '\\(')
|
|
131
|
-
.replace(/\)/g, '\\)');
|
|
132
|
-
const term = `%${escaped}%`;
|
|
133
|
-
const orConditions = searchColumns.map((col) => `${col}.ilike.${term}`).join(',');
|
|
134
|
-
q = q.or(orConditions);
|
|
175
|
+
for (const word of searchWords(searchTerm)) {
|
|
176
|
+
q = q.or(searchColumns.map((col) => `${col}.ilike.%${word}%`).join(','));
|
|
177
|
+
}
|
|
135
178
|
}
|
|
136
179
|
// ORDER BEFORE LIMIT. Without this the picker returns an arbitrary
|
|
137
180
|
// `limit`-sized slice of the matches, so a freshly created row is
|
|
@@ -158,6 +201,8 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
158
201
|
({ data, error: queryError } = await build(false));
|
|
159
202
|
}
|
|
160
203
|
}
|
|
204
|
+
if (generation !== searchGeneration.current)
|
|
205
|
+
return;
|
|
161
206
|
if (queryError) {
|
|
162
207
|
logger.warn(`EntitySelector(${table}): search error`, queryError.message);
|
|
163
208
|
setError(queryError.message || 'Failed to load results.');
|
|
@@ -167,12 +212,15 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
167
212
|
setItems(data ?? []);
|
|
168
213
|
}
|
|
169
214
|
catch (err) {
|
|
215
|
+
if (generation !== searchGeneration.current)
|
|
216
|
+
return;
|
|
170
217
|
logger.error(`EntitySelector(${table}): unexpected error`, err);
|
|
171
218
|
setError(err instanceof Error ? err.message : 'Failed to load results.');
|
|
172
219
|
setItems([]);
|
|
173
220
|
}
|
|
174
221
|
finally {
|
|
175
|
-
|
|
222
|
+
if (generation === searchGeneration.current)
|
|
223
|
+
setIsLoading(false);
|
|
176
224
|
}
|
|
177
225
|
}, [table, selectColumns, searchColumns, filterFn, minChars, limit, fromTable, logger]);
|
|
178
226
|
/**
|
|
@@ -244,7 +292,11 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
244
292
|
}, [open, fetchItems]);
|
|
245
293
|
// ── Debounced search on query change ───────────────────────────────────
|
|
246
294
|
const handleQueryChange = useCallback((val) => {
|
|
295
|
+
++searchGeneration.current;
|
|
247
296
|
setQuery(val);
|
|
297
|
+
setItems([]);
|
|
298
|
+
setError(null);
|
|
299
|
+
setIsLoading(true);
|
|
248
300
|
if (debounceRef.current)
|
|
249
301
|
clearTimeout(debounceRef.current);
|
|
250
302
|
debounceRef.current = setTimeout(() => fetchItems(val), debounceMs);
|
|
@@ -300,8 +352,21 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
300
352
|
if (!result || typeof result.then !== 'function') {
|
|
301
353
|
setSelectedItem(item);
|
|
302
354
|
}
|
|
303
|
-
|
|
304
|
-
|
|
355
|
+
handleOpenChange(false);
|
|
356
|
+
};
|
|
357
|
+
const handleOpenChange = (nextOpen) => {
|
|
358
|
+
if (!nextOpen) {
|
|
359
|
+
++searchGeneration.current;
|
|
360
|
+
if (debounceRef.current)
|
|
361
|
+
clearTimeout(debounceRef.current);
|
|
362
|
+
debounceRef.current = null;
|
|
363
|
+
hasLoadedInitial.current = false;
|
|
364
|
+
setQuery('');
|
|
365
|
+
setItems([]);
|
|
366
|
+
setError(null);
|
|
367
|
+
setIsLoading(false);
|
|
368
|
+
}
|
|
369
|
+
setOpen(nextOpen);
|
|
305
370
|
};
|
|
306
371
|
const handleClear = (e) => {
|
|
307
372
|
e.stopPropagation();
|
|
@@ -310,7 +375,7 @@ export function EntitySelector({ supabaseClient, logger = console, table, schema
|
|
|
310
375
|
};
|
|
311
376
|
// ── Render ─────────────────────────────────────────────────────────────
|
|
312
377
|
const triggerLabel = selectedItem ? (selectedLabel ?? displayField(selectedItem)) : placeholder;
|
|
313
|
-
return (_jsxs("div", { className: "flex gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange:
|
|
378
|
+
return (_jsxs("div", { className: "flex gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", role: "combobox", "aria-expanded": open, id: id, name: name, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, disabled: disabled, className: cn('w-full justify-between font-normal', !selectedItem && 'text-muted-foreground', className), children: [_jsx("span", { className: "truncate min-w-0", children: triggerLabel }), _jsxs("div", { className: "flex items-center gap-1 ml-2 shrink-0", children: [clearable && selectedItem && (_jsx(X, { className: "h-3.5 w-3.5 opacity-50 hover:opacity-100", onClick: handleClear })), _jsx(ChevronsUpDown, { className: "h-3.5 w-3.5 opacity-50" })] })] }) }), _jsx(PopoverContent, { className: "w-[--radix-popover-trigger-width] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: placeholder, value: query, onValueChange: handleQueryChange }), _jsxs(CommandList, { children: [isLoading ? (_jsxs("div", { className: "flex items-center justify-center py-6", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }), _jsx("span", { className: "ml-2 text-sm text-muted-foreground", children: "Searching..." })] })) : error ? (_jsxs("div", { className: "text-center py-6 px-4 text-sm text-error-text", role: "alert", children: ["Couldn't load results: ", error] })) : items.length === 0 && (query.length > 0 || suggestedItems.length === 0) ? (_jsx(CommandEmpty, { children: query.length > 0 ? `No results for "${query}"` : 'No items found' })) : (_jsxs(_Fragment, { children: [suggestedItems.length > 0 && query.length === 0 && (_jsx(CommandGroup, { heading: suggestedLabel, children: suggestedItems.map((item) => renderItem(item)) })), (() => {
|
|
314
379
|
// De-dupe so a suggested row is not offered twice; showing
|
|
315
380
|
// the same entity in both groups reads as two different
|
|
316
381
|
// records.
|