@ibobbyts/svelte-ui-utils 0.4.6 → 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.
Files changed (37) hide show
  1. package/README.md +37 -125
  2. package/dist/data-table/DateRangeFilter.svelte +119 -47
  3. package/dist/data-table/DateRangeFilter.svelte.d.ts +4 -3
  4. package/dist/data-table/DateRangeFilter.svelte.d.ts.map +1 -1
  5. package/dist/data-table/FilterControl.svelte +136 -27
  6. package/dist/data-table/FilterControl.svelte.d.ts.map +1 -1
  7. package/dist/data-table/date-range.d.ts +1 -0
  8. package/dist/data-table/date-range.d.ts.map +1 -1
  9. package/dist/data-table/date-range.js +12 -0
  10. package/dist/data-table/index.d.ts +2 -2
  11. package/dist/data-table/index.d.ts.map +1 -1
  12. package/dist/data-table/index.js +1 -1
  13. package/dist/data-table/types.d.ts +76 -4
  14. package/dist/data-table/types.d.ts.map +1 -1
  15. package/dist/dropdown/Dropdown.svelte +27 -11
  16. package/dist/dropdown/Dropdown.svelte.d.ts +1 -1
  17. package/dist/dropdown/Dropdown.svelte.d.ts.map +1 -1
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +0 -1
  21. package/dist/style.css +12 -282
  22. package/package.json +4 -12
  23. package/dist/dropdown-search/DropdownSearch.svelte +0 -725
  24. package/dist/dropdown-search/DropdownSearch.svelte.d.ts +0 -62
  25. package/dist/dropdown-search/DropdownSearch.svelte.d.ts.map +0 -1
  26. package/dist/dropdown-search/DropdownSearchMultiSelect.svelte +0 -94
  27. package/dist/dropdown-search/DropdownSearchMultiSelect.svelte.d.ts +0 -54
  28. package/dist/dropdown-search/DropdownSearchMultiSelect.svelte.d.ts.map +0 -1
  29. package/dist/dropdown-search/index.d.ts +0 -7
  30. package/dist/dropdown-search/index.d.ts.map +0 -1
  31. package/dist/dropdown-search/index.js +0 -4
  32. package/dist/dropdown-search/state.d.ts +0 -17
  33. package/dist/dropdown-search/state.d.ts.map +0 -1
  34. package/dist/dropdown-search/state.js +0 -51
  35. package/dist/dropdown-search/types.d.ts +0 -39
  36. package/dist/dropdown-search/types.d.ts.map +0 -1
  37. 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>