@ibobbyts/svelte-ui-utils 0.4.7 → 0.5.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/README.md +3 -110
- package/dist/data-table/FilterControl.svelte +135 -27
- package/dist/data-table/FilterControl.svelte.d.ts.map +1 -1
- package/dist/data-table/types.d.ts +36 -1
- package/dist/data-table/types.d.ts.map +1 -1
- package/dist/dropdown/Dropdown.svelte +27 -11
- package/dist/dropdown/Dropdown.svelte.d.ts +1 -1
- package/dist/dropdown/Dropdown.svelte.d.ts.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/style.css +12 -282
- package/package.json +2 -11
- package/dist/dropdown-search/DropdownSearch.svelte +0 -725
- package/dist/dropdown-search/DropdownSearch.svelte.d.ts +0 -62
- package/dist/dropdown-search/DropdownSearch.svelte.d.ts.map +0 -1
- package/dist/dropdown-search/DropdownSearchMultiSelect.svelte +0 -94
- package/dist/dropdown-search/DropdownSearchMultiSelect.svelte.d.ts +0 -54
- package/dist/dropdown-search/DropdownSearchMultiSelect.svelte.d.ts.map +0 -1
- package/dist/dropdown-search/index.d.ts +0 -7
- package/dist/dropdown-search/index.d.ts.map +0 -1
- package/dist/dropdown-search/index.js +0 -4
- package/dist/dropdown-search/state.d.ts +0 -17
- package/dist/dropdown-search/state.d.ts.map +0 -1
- package/dist/dropdown-search/state.js +0 -51
- package/dist/dropdown-search/types.d.ts +0 -39
- package/dist/dropdown-search/types.d.ts.map +0 -1
- package/dist/dropdown-search/types.js +0 -1
|
@@ -1,725 +0,0 @@
|
|
|
1
|
-
<svelte:options runes={false} />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import { onDestroy, onMount } from 'svelte';
|
|
5
|
-
import { getUiMessages, type UiLanguage } from '../i18n.js';
|
|
6
|
-
import {
|
|
7
|
-
formatParamDict,
|
|
8
|
-
normalizeDropdownSearchValue,
|
|
9
|
-
resolveDropdownSearchStatus,
|
|
10
|
-
} from './state.js';
|
|
11
|
-
import type {
|
|
12
|
-
DropdownSearchChangeDetail,
|
|
13
|
-
DropdownSearchEnterDetail,
|
|
14
|
-
DropdownSearchItem,
|
|
15
|
-
DropdownSearchItemLabelGetter,
|
|
16
|
-
DropdownSearchLoadOptions,
|
|
17
|
-
DropdownSearchSelectedItemLabelGetter,
|
|
18
|
-
DropdownSearchSelectedItemsChangeHandler,
|
|
19
|
-
DropdownSearchStatus,
|
|
20
|
-
} from './types.js';
|
|
21
|
-
|
|
22
|
-
export let value = '';
|
|
23
|
-
export let selectedItem: DropdownSearchItem | null = null;
|
|
24
|
-
export let selectedItems: DropdownSearchItem[] = [];
|
|
25
|
-
export let status: DropdownSearchStatus = 'empty';
|
|
26
|
-
export let multiselect = false;
|
|
27
|
-
export let placeholder = '';
|
|
28
|
-
export let debounceMs = 500;
|
|
29
|
-
export let limit = 10;
|
|
30
|
-
export let minLength = 1;
|
|
31
|
-
export let validate = true;
|
|
32
|
-
export let closeOnValid = false;
|
|
33
|
-
export let showOptionsOnFocus = false;
|
|
34
|
-
export let focusOptions: DropdownSearchItem[] = [];
|
|
35
|
-
export let footerText: string | undefined = undefined;
|
|
36
|
-
export let loadOptions: DropdownSearchLoadOptions;
|
|
37
|
-
export let id: string | undefined = undefined;
|
|
38
|
-
export let name: string | undefined = undefined;
|
|
39
|
-
export let ariaLabel: string | undefined = undefined;
|
|
40
|
-
export let listboxId: string | undefined = undefined;
|
|
41
|
-
export let disabled = false;
|
|
42
|
-
export let language: UiLanguage = 'en_us';
|
|
43
|
-
export let noResultsText: string | undefined = undefined;
|
|
44
|
-
export let loadingText: string | undefined = undefined;
|
|
45
|
-
export let clearLabel: string | undefined = undefined;
|
|
46
|
-
export let searchOnExternalValueChange = false;
|
|
47
|
-
export let width: string | undefined = undefined;
|
|
48
|
-
export let minWidth: string | undefined = undefined;
|
|
49
|
-
export let maxWidth: string | undefined = undefined;
|
|
50
|
-
export let selectedItemsLabel = 'Selected items';
|
|
51
|
-
export let removeSelectedItemLabel:
|
|
52
|
-
| DropdownSearchSelectedItemLabelGetter
|
|
53
|
-
| undefined = undefined;
|
|
54
|
-
// The input text for a selected item defaults to its display label, never to
|
|
55
|
-
// the submitted `value`.
|
|
56
|
-
export let getItemLabel: DropdownSearchItemLabelGetter = (item) => item.label;
|
|
57
|
-
export let onChange:
|
|
58
|
-
| ((detail: DropdownSearchChangeDetail) => void)
|
|
59
|
-
| undefined = undefined;
|
|
60
|
-
export let onSelect: ((item: DropdownSearchItem) => void) | undefined =
|
|
61
|
-
undefined;
|
|
62
|
-
export let onDeselect: ((item: DropdownSearchItem) => void) | undefined =
|
|
63
|
-
undefined;
|
|
64
|
-
export let onSelectedItemsChange:
|
|
65
|
-
| DropdownSearchSelectedItemsChangeHandler
|
|
66
|
-
| undefined = undefined;
|
|
67
|
-
export let onStatusChange:
|
|
68
|
-
| ((status: DropdownSearchStatus) => void)
|
|
69
|
-
| undefined = undefined;
|
|
70
|
-
export let onInputBlur: ((event: FocusEvent) => void) | undefined = undefined;
|
|
71
|
-
export let onEnter: ((detail: DropdownSearchEnterDetail) => void) | undefined =
|
|
72
|
-
undefined;
|
|
73
|
-
|
|
74
|
-
let options: DropdownSearchItem[] = [];
|
|
75
|
-
let exactMatch: DropdownSearchItem | null = null;
|
|
76
|
-
let focused = false;
|
|
77
|
-
let inputValue = value;
|
|
78
|
-
let inputElement: HTMLInputElement | undefined;
|
|
79
|
-
let searchTimer: ReturnType<typeof setTimeout> | undefined;
|
|
80
|
-
let blurTimer: ReturnType<typeof setTimeout> | undefined;
|
|
81
|
-
let activeController: AbortController | undefined;
|
|
82
|
-
let requestId = 0;
|
|
83
|
-
let lastHandledValue = value;
|
|
84
|
-
let showingFocusOptions = false;
|
|
85
|
-
|
|
86
|
-
$: messages = getUiMessages(language);
|
|
87
|
-
$: resolvedNoResultsText = noResultsText?.trim()
|
|
88
|
-
? noResultsText
|
|
89
|
-
: messages.dropdownSearch.noResultsText;
|
|
90
|
-
$: resolvedLoadingText = loadingText?.trim()
|
|
91
|
-
? loadingText
|
|
92
|
-
: messages.dropdownSearch.loadingText;
|
|
93
|
-
$: resolvedClearLabel = clearLabel?.trim()
|
|
94
|
-
? clearLabel
|
|
95
|
-
: messages.dropdownSearch.clearLabel;
|
|
96
|
-
$: resolvedListboxId = listboxId ?? (id ? `${id}-options` : undefined);
|
|
97
|
-
$: hasQuery = normalizeDropdownSearchValue(value).length > 0;
|
|
98
|
-
$: hideOptionsOnValid =
|
|
99
|
-
closeOnValid && !showingFocusOptions && !multiselect && validate && status === 'valid';
|
|
100
|
-
$: showOptions =
|
|
101
|
-
focused &&
|
|
102
|
-
!disabled &&
|
|
103
|
-
!hideOptionsOnValid &&
|
|
104
|
-
((showingFocusOptions && (options.length > 0 || Boolean(footerText?.trim()))) ||
|
|
105
|
-
hasQuery ||
|
|
106
|
-
(multiselect && options.length > 0)) &&
|
|
107
|
-
(options.length > 0 ||
|
|
108
|
-
status === 'loading' ||
|
|
109
|
-
(validate && hasQuery && options.length === 0) ||
|
|
110
|
-
(showingFocusOptions && Boolean(footerText?.trim())));
|
|
111
|
-
$: if (!validate && status !== 'empty' && status !== 'loading') {
|
|
112
|
-
setStatus('empty');
|
|
113
|
-
}
|
|
114
|
-
$: if (value !== lastHandledValue && value !== inputValue) {
|
|
115
|
-
handleIncomingValue(value);
|
|
116
|
-
}
|
|
117
|
-
$: if (showOptionsOnFocus && showingFocusOptions && focused) {
|
|
118
|
-
options = [...focusOptions];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function emitChange() {
|
|
122
|
-
const detail = { value, selectedItem, selectedItems, status };
|
|
123
|
-
onChange?.(detail);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function setStatus(nextStatus: DropdownSearchStatus) {
|
|
127
|
-
if (status !== nextStatus) {
|
|
128
|
-
status = nextStatus;
|
|
129
|
-
onStatusChange?.(status);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function resetSearchState(nextValue: string) {
|
|
134
|
-
lastHandledValue = nextValue;
|
|
135
|
-
value = nextValue;
|
|
136
|
-
if (!multiselect) {
|
|
137
|
-
selectedItem = null;
|
|
138
|
-
}
|
|
139
|
-
exactMatch = null;
|
|
140
|
-
options = [];
|
|
141
|
-
const trimmed = normalizeDropdownSearchValue(nextValue);
|
|
142
|
-
if (trimmed && trimmed.length >= minLength) {
|
|
143
|
-
abortActiveSearch();
|
|
144
|
-
setStatus('loading');
|
|
145
|
-
} else {
|
|
146
|
-
abortActiveSearch();
|
|
147
|
-
setStatus(
|
|
148
|
-
resolveDropdownSearchStatus({
|
|
149
|
-
value,
|
|
150
|
-
selectedItem,
|
|
151
|
-
selectedItems,
|
|
152
|
-
exactMatch,
|
|
153
|
-
minLength,
|
|
154
|
-
validate,
|
|
155
|
-
multiselect,
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
emitChange();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function clearSearchTimer() {
|
|
163
|
-
if (searchTimer) {
|
|
164
|
-
clearTimeout(searchTimer);
|
|
165
|
-
searchTimer = undefined;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function clearBlurTimer() {
|
|
170
|
-
if (blurTimer) {
|
|
171
|
-
clearTimeout(blurTimer);
|
|
172
|
-
blurTimer = undefined;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function abortActiveSearch() {
|
|
177
|
-
if (activeController) {
|
|
178
|
-
activeController.abort();
|
|
179
|
-
activeController = undefined;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function showFocusOptions() {
|
|
184
|
-
if (!showOptionsOnFocus) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
clearSearchTimer();
|
|
188
|
-
abortActiveSearch();
|
|
189
|
-
options = [...focusOptions];
|
|
190
|
-
showingFocusOptions = true;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function runSearch(query: string) {
|
|
194
|
-
showingFocusOptions = false;
|
|
195
|
-
const trimmed = normalizeDropdownSearchValue(query);
|
|
196
|
-
|
|
197
|
-
if (!trimmed || trimmed.length < minLength) {
|
|
198
|
-
abortActiveSearch();
|
|
199
|
-
options = [];
|
|
200
|
-
exactMatch = null;
|
|
201
|
-
setStatus(
|
|
202
|
-
resolveDropdownSearchStatus({
|
|
203
|
-
value: query,
|
|
204
|
-
selectedItems,
|
|
205
|
-
minLength,
|
|
206
|
-
validate,
|
|
207
|
-
multiselect,
|
|
208
|
-
}),
|
|
209
|
-
);
|
|
210
|
-
emitChange();
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
abortActiveSearch();
|
|
215
|
-
const currentRequestId = ++requestId;
|
|
216
|
-
const controller = new AbortController();
|
|
217
|
-
activeController = controller;
|
|
218
|
-
setStatus('loading');
|
|
219
|
-
emitChange();
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const result = await loadOptions(trimmed, {
|
|
223
|
-
limit,
|
|
224
|
-
signal: controller.signal,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (controller.signal.aborted || currentRequestId !== requestId) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
options = result.options ?? [];
|
|
232
|
-
exactMatch = validate ? (result.exactMatch ?? null) : null;
|
|
233
|
-
selectedItem =
|
|
234
|
-
!multiselect && validate && exactMatch && !exactMatch.disabled
|
|
235
|
-
? exactMatch
|
|
236
|
-
: null;
|
|
237
|
-
setStatus(
|
|
238
|
-
resolveDropdownSearchStatus({
|
|
239
|
-
value,
|
|
240
|
-
selectedItem,
|
|
241
|
-
selectedItems,
|
|
242
|
-
exactMatch: multiselect ? null : exactMatch,
|
|
243
|
-
minLength,
|
|
244
|
-
validate,
|
|
245
|
-
multiselect,
|
|
246
|
-
}),
|
|
247
|
-
);
|
|
248
|
-
emitChange();
|
|
249
|
-
} catch (error) {
|
|
250
|
-
if (controller.signal.aborted || currentRequestId !== requestId) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
options = [];
|
|
255
|
-
exactMatch = null;
|
|
256
|
-
selectedItem = null;
|
|
257
|
-
setStatus(
|
|
258
|
-
resolveDropdownSearchStatus({
|
|
259
|
-
value: query,
|
|
260
|
-
selectedItems,
|
|
261
|
-
errored: true,
|
|
262
|
-
minLength,
|
|
263
|
-
validate,
|
|
264
|
-
multiselect,
|
|
265
|
-
}),
|
|
266
|
-
);
|
|
267
|
-
emitChange();
|
|
268
|
-
} finally {
|
|
269
|
-
if (activeController === controller) {
|
|
270
|
-
activeController = undefined;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function scheduleSearch(query: string) {
|
|
276
|
-
clearSearchTimer();
|
|
277
|
-
searchTimer = setTimeout(() => {
|
|
278
|
-
void runSearch(query);
|
|
279
|
-
}, debounceMs);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function handleInputValue(nextValue: string) {
|
|
283
|
-
clearBlurTimer();
|
|
284
|
-
focused = true;
|
|
285
|
-
showingFocusOptions = false;
|
|
286
|
-
inputValue = nextValue;
|
|
287
|
-
resetSearchState(nextValue);
|
|
288
|
-
scheduleSearch(nextValue);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function handleInput(event: Event) {
|
|
292
|
-
const input = event.currentTarget as HTMLInputElement;
|
|
293
|
-
handleInputValue(input.value);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function handleBeforeInput() {
|
|
297
|
-
setTimeout(() => {
|
|
298
|
-
if (!inputElement || inputElement.value === lastHandledValue) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
handleInputValue(inputElement.value);
|
|
303
|
-
}, 0);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function handleFormReset() {
|
|
307
|
-
clearSearchTimer();
|
|
308
|
-
clearBlurTimer();
|
|
309
|
-
abortActiveSearch();
|
|
310
|
-
value = '';
|
|
311
|
-
inputValue = '';
|
|
312
|
-
lastHandledValue = '';
|
|
313
|
-
if (!multiselect) {
|
|
314
|
-
selectedItem = null;
|
|
315
|
-
}
|
|
316
|
-
exactMatch = null;
|
|
317
|
-
options = [];
|
|
318
|
-
showingFocusOptions = false;
|
|
319
|
-
setStatus(
|
|
320
|
-
resolveDropdownSearchStatus({
|
|
321
|
-
value,
|
|
322
|
-
selectedItem,
|
|
323
|
-
selectedItems,
|
|
324
|
-
exactMatch,
|
|
325
|
-
minLength,
|
|
326
|
-
validate,
|
|
327
|
-
multiselect,
|
|
328
|
-
}),
|
|
329
|
-
);
|
|
330
|
-
emitChange();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function handleFocus() {
|
|
334
|
-
clearBlurTimer();
|
|
335
|
-
focused = true;
|
|
336
|
-
showFocusOptions();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function handleBlur(event: FocusEvent) {
|
|
340
|
-
onInputBlur?.(event);
|
|
341
|
-
clearBlurTimer();
|
|
342
|
-
blurTimer = setTimeout(() => {
|
|
343
|
-
focused = document.activeElement === inputElement;
|
|
344
|
-
if (!focused) {
|
|
345
|
-
showingFocusOptions = false;
|
|
346
|
-
}
|
|
347
|
-
blurTimer = undefined;
|
|
348
|
-
}, 120);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function handleKeydown(event: KeyboardEvent) {
|
|
352
|
-
if (event.key !== 'Enter' || multiselect) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
onEnter?.({
|
|
357
|
-
event,
|
|
358
|
-
value,
|
|
359
|
-
selectedItem,
|
|
360
|
-
selectedItems,
|
|
361
|
-
status,
|
|
362
|
-
exactMatch,
|
|
363
|
-
options,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function selectItem(item: DropdownSearchItem) {
|
|
368
|
-
if (item.disabled) {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (multiselect) {
|
|
373
|
-
toggleSelectedItem(item);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
clearSearchTimer();
|
|
378
|
-
abortActiveSearch();
|
|
379
|
-
showingFocusOptions = false;
|
|
380
|
-
selectedItem = item;
|
|
381
|
-
exactMatch = validate ? item : null;
|
|
382
|
-
value = getItemLabel(item);
|
|
383
|
-
inputValue = value;
|
|
384
|
-
lastHandledValue = value;
|
|
385
|
-
setStatus(validate ? 'valid' : 'empty');
|
|
386
|
-
focused = false;
|
|
387
|
-
emitChange();
|
|
388
|
-
onSelect?.(item);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function selectedItemKey(item: DropdownSearchItem): string {
|
|
392
|
-
return item.value;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function isSelectedItem(item: DropdownSearchItem): boolean {
|
|
396
|
-
const key = selectedItemKey(item);
|
|
397
|
-
return selectedItems.some((selected) => selectedItemKey(selected) === key);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function resolveRemoveSelectedItemLabel(item: DropdownSearchItem): string {
|
|
401
|
-
return removeSelectedItemLabel?.(item) ?? `Remove ${item.label}`;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function setSelectedItems(nextItems: DropdownSearchItem[]) {
|
|
405
|
-
selectedItems = nextItems;
|
|
406
|
-
void onSelectedItemsChange?.(selectedItems);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function toggleSelectedItem(item: DropdownSearchItem) {
|
|
410
|
-
const selected = isSelectedItem(item);
|
|
411
|
-
const nextItems = selected
|
|
412
|
-
? selectedItems.filter(
|
|
413
|
-
(selectedItem) =>
|
|
414
|
-
selectedItemKey(selectedItem) !== selectedItemKey(item),
|
|
415
|
-
)
|
|
416
|
-
: [...selectedItems, item];
|
|
417
|
-
|
|
418
|
-
clearSearchTimer();
|
|
419
|
-
abortActiveSearch();
|
|
420
|
-
setSelectedItems(nextItems);
|
|
421
|
-
selectedItem = null;
|
|
422
|
-
exactMatch = null;
|
|
423
|
-
value = '';
|
|
424
|
-
inputValue = value;
|
|
425
|
-
lastHandledValue = value;
|
|
426
|
-
setStatus(
|
|
427
|
-
resolveDropdownSearchStatus({
|
|
428
|
-
value,
|
|
429
|
-
selectedItems,
|
|
430
|
-
minLength,
|
|
431
|
-
validate,
|
|
432
|
-
multiselect,
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
focused = true;
|
|
436
|
-
emitChange();
|
|
437
|
-
|
|
438
|
-
if (selected) {
|
|
439
|
-
onDeselect?.(item);
|
|
440
|
-
} else {
|
|
441
|
-
onSelect?.(item);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
inputElement?.focus();
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function removeSelectedItem(item: DropdownSearchItem) {
|
|
448
|
-
if (disabled || !multiselect) {
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const nextItems = selectedItems.filter(
|
|
453
|
-
(selectedItem) => selectedItemKey(selectedItem) !== selectedItemKey(item),
|
|
454
|
-
);
|
|
455
|
-
setSelectedItems(nextItems);
|
|
456
|
-
setStatus(
|
|
457
|
-
resolveDropdownSearchStatus({
|
|
458
|
-
value,
|
|
459
|
-
selectedItems,
|
|
460
|
-
minLength,
|
|
461
|
-
validate,
|
|
462
|
-
multiselect,
|
|
463
|
-
}),
|
|
464
|
-
);
|
|
465
|
-
emitChange();
|
|
466
|
-
onDeselect?.(item);
|
|
467
|
-
inputElement?.focus();
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function clearSearch() {
|
|
471
|
-
if (!hasQuery || disabled) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
clearSearchTimer();
|
|
476
|
-
abortActiveSearch();
|
|
477
|
-
value = '';
|
|
478
|
-
inputValue = value;
|
|
479
|
-
lastHandledValue = value;
|
|
480
|
-
if (!multiselect) {
|
|
481
|
-
selectedItem = null;
|
|
482
|
-
}
|
|
483
|
-
exactMatch = null;
|
|
484
|
-
options = [];
|
|
485
|
-
showingFocusOptions = false;
|
|
486
|
-
setStatus(
|
|
487
|
-
resolveDropdownSearchStatus({
|
|
488
|
-
value,
|
|
489
|
-
selectedItem,
|
|
490
|
-
selectedItems,
|
|
491
|
-
exactMatch,
|
|
492
|
-
minLength,
|
|
493
|
-
validate,
|
|
494
|
-
multiselect,
|
|
495
|
-
}),
|
|
496
|
-
);
|
|
497
|
-
focused = true;
|
|
498
|
-
showFocusOptions();
|
|
499
|
-
emitChange();
|
|
500
|
-
inputElement?.focus();
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function handleIncomingValue(nextValue: string) {
|
|
504
|
-
inputValue = nextValue;
|
|
505
|
-
|
|
506
|
-
if (showOptionsOnFocus && focused) {
|
|
507
|
-
showFocusOptions();
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (searchOnExternalValueChange) {
|
|
512
|
-
handleExternalValue(nextValue);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
lastHandledValue = nextValue;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function handleExternalValue(nextValue: string) {
|
|
520
|
-
lastHandledValue = nextValue;
|
|
521
|
-
inputValue = nextValue;
|
|
522
|
-
if (!multiselect) {
|
|
523
|
-
selectedItem = null;
|
|
524
|
-
}
|
|
525
|
-
exactMatch = null;
|
|
526
|
-
options = [];
|
|
527
|
-
|
|
528
|
-
clearSearchTimer();
|
|
529
|
-
if (
|
|
530
|
-
!normalizeDropdownSearchValue(nextValue) ||
|
|
531
|
-
normalizeDropdownSearchValue(nextValue).length < minLength
|
|
532
|
-
) {
|
|
533
|
-
setStatus(
|
|
534
|
-
resolveDropdownSearchStatus({
|
|
535
|
-
value: nextValue,
|
|
536
|
-
selectedItem,
|
|
537
|
-
selectedItems,
|
|
538
|
-
exactMatch,
|
|
539
|
-
minLength,
|
|
540
|
-
validate,
|
|
541
|
-
multiselect,
|
|
542
|
-
}),
|
|
543
|
-
);
|
|
544
|
-
emitChange();
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
void runSearch(nextValue);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
onDestroy(() => {
|
|
552
|
-
clearSearchTimer();
|
|
553
|
-
clearBlurTimer();
|
|
554
|
-
abortActiveSearch();
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
onMount(() => {
|
|
558
|
-
const formElement = inputElement?.form;
|
|
559
|
-
formElement?.addEventListener('reset', handleFormReset);
|
|
560
|
-
if (showOptionsOnFocus && focused) {
|
|
561
|
-
showFocusOptions();
|
|
562
|
-
}
|
|
563
|
-
if (searchOnExternalValueChange && normalizeDropdownSearchValue(value)) {
|
|
564
|
-
handleExternalValue(value);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const visibilityObserver =
|
|
568
|
-
typeof IntersectionObserver === 'undefined'
|
|
569
|
-
? undefined
|
|
570
|
-
: new IntersectionObserver(([entry]) => {
|
|
571
|
-
if (!entry?.isIntersecting) {
|
|
572
|
-
focused = false;
|
|
573
|
-
showingFocusOptions = false;
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
if (inputElement && visibilityObserver) {
|
|
577
|
-
visibilityObserver.observe(inputElement);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
return () => {
|
|
581
|
-
formElement?.removeEventListener('reset', handleFormReset);
|
|
582
|
-
visibilityObserver?.disconnect();
|
|
583
|
-
};
|
|
584
|
-
});
|
|
585
|
-
</script>
|
|
586
|
-
|
|
587
|
-
<div
|
|
588
|
-
class={`suu-dropdown-search suu-dropdown-search--${status}`}
|
|
589
|
-
class:suu-dropdown-search--multi={multiselect}
|
|
590
|
-
class:suu-dropdown-search--has-clear={hasQuery && !disabled}
|
|
591
|
-
data-status={status}
|
|
592
|
-
data-multiselect={multiselect}
|
|
593
|
-
style:width
|
|
594
|
-
style:min-width={minWidth}
|
|
595
|
-
style:max-width={maxWidth}
|
|
596
|
-
>
|
|
597
|
-
{#if multiselect && selectedItems.length > 0}
|
|
598
|
-
<div
|
|
599
|
-
class="suu-dropdown-search__selected-items"
|
|
600
|
-
aria-label={selectedItemsLabel}
|
|
601
|
-
>
|
|
602
|
-
{#each selectedItems as item (item.value)}
|
|
603
|
-
<span class="suu-dropdown-search__selected-item">
|
|
604
|
-
<span class="suu-dropdown-search__selected-title">{item.label}</span>
|
|
605
|
-
{#if !disabled}
|
|
606
|
-
<button
|
|
607
|
-
type="button"
|
|
608
|
-
class="suu-dropdown-search__selected-remove"
|
|
609
|
-
aria-label={resolveRemoveSelectedItemLabel(item)}
|
|
610
|
-
title={resolveRemoveSelectedItemLabel(item)}
|
|
611
|
-
on:mousedown|preventDefault
|
|
612
|
-
on:click={() => removeSelectedItem(item)}
|
|
613
|
-
>
|
|
614
|
-
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
615
|
-
<path d="m6 6 8 8M14 6l-8 8"></path>
|
|
616
|
-
</svg>
|
|
617
|
-
</button>
|
|
618
|
-
{/if}
|
|
619
|
-
</span>
|
|
620
|
-
{/each}
|
|
621
|
-
</div>
|
|
622
|
-
{/if}
|
|
623
|
-
|
|
624
|
-
<div class="suu-dropdown-search__field">
|
|
625
|
-
<svg
|
|
626
|
-
class="suu-dropdown-search__icon"
|
|
627
|
-
viewBox="0 0 24 24"
|
|
628
|
-
aria-hidden="true"
|
|
629
|
-
>
|
|
630
|
-
<circle cx="11" cy="11" r="7"></circle>
|
|
631
|
-
<path d="m16 16 4 4"></path>
|
|
632
|
-
</svg>
|
|
633
|
-
<input
|
|
634
|
-
bind:this={inputElement}
|
|
635
|
-
class="suu-dropdown-search__input"
|
|
636
|
-
{id}
|
|
637
|
-
{name}
|
|
638
|
-
bind:value={inputValue}
|
|
639
|
-
{placeholder}
|
|
640
|
-
aria-label={ariaLabel}
|
|
641
|
-
{disabled}
|
|
642
|
-
autocomplete="off"
|
|
643
|
-
aria-autocomplete="list"
|
|
644
|
-
aria-controls={resolvedListboxId}
|
|
645
|
-
aria-expanded={showOptions}
|
|
646
|
-
aria-invalid={status === 'invalid' || status === 'error'}
|
|
647
|
-
on:beforeinput={handleBeforeInput}
|
|
648
|
-
on:input={handleInput}
|
|
649
|
-
on:keydown={handleKeydown}
|
|
650
|
-
on:focus={handleFocus}
|
|
651
|
-
on:blur={handleBlur}
|
|
652
|
-
/>
|
|
653
|
-
{#if hasQuery && !disabled}
|
|
654
|
-
<button
|
|
655
|
-
type="button"
|
|
656
|
-
class="suu-dropdown-search__clear"
|
|
657
|
-
aria-label={resolvedClearLabel}
|
|
658
|
-
title={resolvedClearLabel}
|
|
659
|
-
on:mousedown|preventDefault
|
|
660
|
-
on:click={clearSearch}
|
|
661
|
-
>
|
|
662
|
-
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
663
|
-
<path d="m6 6 8 8M14 6l-8 8"></path>
|
|
664
|
-
</svg>
|
|
665
|
-
</button>
|
|
666
|
-
{/if}
|
|
667
|
-
</div>
|
|
668
|
-
|
|
669
|
-
{#if showOptions}
|
|
670
|
-
<div class="suu-dropdown-search__menu">
|
|
671
|
-
<div
|
|
672
|
-
class="suu-dropdown-search__menu-panel"
|
|
673
|
-
id={resolvedListboxId}
|
|
674
|
-
role="listbox"
|
|
675
|
-
aria-multiselectable={multiselect}
|
|
676
|
-
>
|
|
677
|
-
{#if status === 'loading'}
|
|
678
|
-
<div
|
|
679
|
-
class="suu-dropdown-search__empty suu-dropdown-search__empty--loading"
|
|
680
|
-
aria-live="polite"
|
|
681
|
-
>
|
|
682
|
-
<span class="suu-dropdown-search__spinner" aria-hidden="true"
|
|
683
|
-
></span>
|
|
684
|
-
<span>{resolvedLoadingText}</span>
|
|
685
|
-
</div>
|
|
686
|
-
{:else if options.length > 0}
|
|
687
|
-
{#each options as option (option.value)}
|
|
688
|
-
<button
|
|
689
|
-
type="button"
|
|
690
|
-
class="suu-dropdown-search__option"
|
|
691
|
-
class:suu-dropdown-search__option--disabled={option.disabled}
|
|
692
|
-
class:suu-dropdown-search__option--selected={multiselect &&
|
|
693
|
-
isSelectedItem(option)}
|
|
694
|
-
role="option"
|
|
695
|
-
aria-selected={multiselect
|
|
696
|
-
? isSelectedItem(option)
|
|
697
|
-
: selectedItem?.value === option.value}
|
|
698
|
-
disabled={option.disabled}
|
|
699
|
-
on:mousedown|preventDefault={() => selectItem(option)}
|
|
700
|
-
>
|
|
701
|
-
{#if multiselect}
|
|
702
|
-
<span class="suu-dropdown-search__check" aria-hidden="true">
|
|
703
|
-
<svg viewBox="0 0 20 20">
|
|
704
|
-
<path d="m4.5 10.5 3.5 3.5 7.5-8"></path>
|
|
705
|
-
</svg>
|
|
706
|
-
</span>
|
|
707
|
-
{/if}
|
|
708
|
-
<span class="suu-dropdown-search__title">{option.label}</span>
|
|
709
|
-
{#if formatParamDict(option.param_dict).length > 0}
|
|
710
|
-
<span class="suu-dropdown-search__meta"
|
|
711
|
-
>{formatParamDict(option.param_dict).join(' · ')}</span
|
|
712
|
-
>
|
|
713
|
-
{/if}
|
|
714
|
-
</button>
|
|
715
|
-
{/each}
|
|
716
|
-
{:else}
|
|
717
|
-
<div class="suu-dropdown-search__empty">{resolvedNoResultsText}</div>
|
|
718
|
-
{/if}
|
|
719
|
-
</div>
|
|
720
|
-
{#if footerText?.trim()}
|
|
721
|
-
<div class="suu-dropdown-search__footer" role="note">{footerText}</div>
|
|
722
|
-
{/if}
|
|
723
|
-
</div>
|
|
724
|
-
{/if}
|
|
725
|
-
</div>
|