@joewinke/jatui 0.20.0 → 0.21.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/CLAUDE.md
CHANGED
|
@@ -26,7 +26,7 @@ Then import:
|
|
|
26
26
|
| `SelectInput` | Dropdown select | `label`, `value` (bindable), `options: SelectOption[]`, `placeholder` |
|
|
27
27
|
| `PhoneInput` | Intl phone (country dropdown + tel input, outputs E.164; caps national digits per country). Fires `onChange(value)` on every change. | `value` (bindable), `name`, `defaultCountry`, `required`, `onChange` |
|
|
28
28
|
| `ChipInput` | Tag/chip entry with suggestions | `chips` (bindable), `suggestions: SuggestionGroup[]`, `placeholder` |
|
|
29
|
-
| `SearchDropdown` | Searchable dropdown with grouped results, keyboard nav, avatar/initials options. `appendToBody` escapes table/overflow stacking contexts; `variant='chip'` renders a compact pill; `size='sm'` a smaller trigger; `dropup` opens upward; `clearable` shows an × when a value is set. Exposes `openDropdown()`. | `groups: SearchDropdownGroup[]`, `value`, `onChange`, `placeholder`, `variant`, `size`, `dropup`, `appendToBody`, `clearable`, `colorFn` |
|
|
29
|
+
| `SearchDropdown` | Searchable dropdown with grouped results, keyboard nav, avatar/initials options. `appendToBody` escapes table/overflow stacking contexts; `variant='chip'` renders a compact pill; `size='sm'` a smaller trigger; `dropup` opens upward; `clearable` shows an × when a value is set. `onSearch` turns the built-in search box into a debounced server-side search hook (fires on open/clear with `''`; internal filtering is bypassed — the consumer updates `groups`). Exposes `openDropdown()`. | `groups: SearchDropdownGroup[]`, `value`, `onChange`, `placeholder`, `variant`, `size`, `dropup`, `appendToBody`, `clearable`, `colorFn`, `onSearch`, `searchDebounceMs` |
|
|
30
30
|
| `FilterDropdown` | Multi-select filter dropdown | `label`, `options: string[]`, `selected: Set<string>` (bindable) |
|
|
31
31
|
| `SortDropdown` | Sort by column with direction | `options: SortOption[]`, `sortBy` (bindable), `sortDir` (bindable) |
|
|
32
32
|
| `InlineEdit` | Click-to-edit text | `value` (bindable), `segments: DisplaySegment[]`, `onSave` |
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
triggerAttrs,
|
|
37
37
|
embedded = false,
|
|
38
38
|
onChange,
|
|
39
|
+
onSearch,
|
|
40
|
+
searchDebounceMs = 250,
|
|
39
41
|
onCreate,
|
|
40
42
|
createLabel = (q: string) => `Create "${q}"`,
|
|
41
43
|
}: {
|
|
@@ -65,6 +67,21 @@
|
|
|
65
67
|
/** Render inline without a trigger button — panel content is always visible, no dropdown behavior */
|
|
66
68
|
embedded?: boolean;
|
|
67
69
|
onChange: (value: string) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Server-side search hook (mos-ibafm). When set, the built-in search box
|
|
72
|
+
* drives ASYNC results instead of the client-side substring filter: every
|
|
73
|
+
* keystroke (debounced to `searchDebounceMs`) calls onSearch with the
|
|
74
|
+
* current query, and the consumer updates `groups` with the server's
|
|
75
|
+
* response. Also fires on panel open (typically with '') so the consumer
|
|
76
|
+
* can load the initial result set, and again when the query is cleared —
|
|
77
|
+
* so '' means "reset to the default list". Internal filtering is bypassed
|
|
78
|
+
* entirely while this is set: the consumer owns exactly what the list
|
|
79
|
+
* shows (a fuzzy server match must not be re-filtered by substring).
|
|
80
|
+
* With it absent, behavior is unchanged (client-side filter).
|
|
81
|
+
*/
|
|
82
|
+
onSearch?: (query: string) => void;
|
|
83
|
+
/** Debounce applied to onSearch keystrokes before the callback fires. */
|
|
84
|
+
searchDebounceMs?: number;
|
|
68
85
|
/**
|
|
69
86
|
* When set, an always-present "create" row is rendered as the last option
|
|
70
87
|
* whenever the search box is non-empty — it survives a zero-match filter (so
|
|
@@ -108,6 +125,10 @@
|
|
|
108
125
|
const activeColor = $derived(colorFn ? colorFn(value) : undefined);
|
|
109
126
|
|
|
110
127
|
const filteredGroups = $derived.by(() => {
|
|
128
|
+
// Server-search mode: the consumer owns the result set (groups arrive
|
|
129
|
+
// from onSearch responses) — re-filtering them client-side would hide
|
|
130
|
+
// fuzzy server matches that don't substring-match the query.
|
|
131
|
+
if (onSearch) return groups;
|
|
111
132
|
if (!searchQuery.trim()) return groups;
|
|
112
133
|
const q = searchQuery.toLowerCase();
|
|
113
134
|
const result: SearchDropdownGroup[] = [];
|
|
@@ -166,6 +187,19 @@
|
|
|
166
187
|
});
|
|
167
188
|
}
|
|
168
189
|
|
|
190
|
+
// Debounced onSearch: one timer per open panel; each keystroke reruns the
|
|
191
|
+
// effect, which clears the pending timer and re-arms it — a burst of
|
|
192
|
+
// keystrokes collapses into a single callback firing `searchDebounceMs`
|
|
193
|
+
// after the last one. Firing on open too (query is '' after a reset) is
|
|
194
|
+
// what lets the consumer load the initial result set from the server.
|
|
195
|
+
$effect(() => {
|
|
196
|
+
if (!onSearch) return;
|
|
197
|
+
if (!open && !embedded) return;
|
|
198
|
+
const q = searchQuery;
|
|
199
|
+
const t = setTimeout(() => onSearch(q), searchDebounceMs);
|
|
200
|
+
return () => clearTimeout(t);
|
|
201
|
+
});
|
|
202
|
+
|
|
169
203
|
function handleSearchKeydown(e: KeyboardEvent) {
|
|
170
204
|
if (e.key === 'Escape') {
|
|
171
205
|
e.stopPropagation();
|
|
@@ -36,6 +36,21 @@ type $$ComponentProps = {
|
|
|
36
36
|
/** Render inline without a trigger button — panel content is always visible, no dropdown behavior */
|
|
37
37
|
embedded?: boolean;
|
|
38
38
|
onChange: (value: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Server-side search hook (mos-ibafm). When set, the built-in search box
|
|
41
|
+
* drives ASYNC results instead of the client-side substring filter: every
|
|
42
|
+
* keystroke (debounced to `searchDebounceMs`) calls onSearch with the
|
|
43
|
+
* current query, and the consumer updates `groups` with the server's
|
|
44
|
+
* response. Also fires on panel open (typically with '') so the consumer
|
|
45
|
+
* can load the initial result set, and again when the query is cleared —
|
|
46
|
+
* so '' means "reset to the default list". Internal filtering is bypassed
|
|
47
|
+
* entirely while this is set: the consumer owns exactly what the list
|
|
48
|
+
* shows (a fuzzy server match must not be re-filtered by substring).
|
|
49
|
+
* With it absent, behavior is unchanged (client-side filter).
|
|
50
|
+
*/
|
|
51
|
+
onSearch?: (query: string) => void;
|
|
52
|
+
/** Debounce applied to onSearch keystrokes before the callback fires. */
|
|
53
|
+
searchDebounceMs?: number;
|
|
39
54
|
/**
|
|
40
55
|
* When set, an always-present "create" row is rendered as the last option
|
|
41
56
|
* whenever the search box is non-empty — it survives a zero-match filter (so
|