@marianmeres/stuic 2.61.0 → 2.63.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/actions/popover/popover.svelte.js +18 -4
- package/dist/actions/tooltip/tooltip.svelte.js +15 -11
- package/dist/components/AssetsPreview/AssetsPreview.svelte +15 -17
- package/dist/components/AssetsPreview/AssetsPreview.svelte.d.ts +2 -4
- package/dist/components/CommandMenu/CommandMenu.svelte +172 -148
- package/dist/components/CommandMenu/README.md +6 -0
- package/dist/components/DropdownMenu/README.md +2 -2
- package/dist/components/Input/FieldOptions.svelte +279 -286
- package/dist/components/Input/FieldOptions.svelte.d.ts +4 -5
- package/dist/components/Modal/Modal.svelte +37 -48
- package/dist/components/Modal/Modal.svelte.d.ts +3 -13
- package/dist/components/Modal/README.md +17 -6
- package/dist/components/ModalDialog/ModalDialog.svelte +50 -13
- package/dist/components/ModalDialog/ModalDialog.svelte.d.ts +9 -0
- package/dist/components/ModalDialog/README.md +27 -8
- package/dist/components/Notifications/Notifications.svelte +135 -96
- package/dist/components/Notifications/notifications-icons.js +4 -4
- package/dist/components/X/X.svelte +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { iconSearch, iconCheck, iconCircle, iconSquare } from "../../icons/index.js";
|
|
4
4
|
import { ItemCollection, type Item } from "@marianmeres/item-collection";
|
|
5
5
|
import { Debounced, watch } from "runed";
|
|
6
|
-
import { tick, type Snippet } from "svelte";
|
|
6
|
+
import { onDestroy, tick, type Snippet } from "svelte";
|
|
7
7
|
import { tooltip } from "../../actions/index.js";
|
|
8
8
|
import { type ValidateOptions } from "../../actions/validate.svelte.js";
|
|
9
9
|
import type { TranslateFn } from "../../types.js";
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { strHash } from "../../utils/str-hash.js";
|
|
17
17
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
18
18
|
import Button from "../Button/Button.svelte";
|
|
19
|
-
import
|
|
19
|
+
import { ModalDialog } from "../ModalDialog/index.js";
|
|
20
20
|
import { NotificationsStack } from "../Notifications/index.js";
|
|
21
21
|
import Spinner from "../Spinner/Spinner.svelte";
|
|
22
22
|
import type { THC } from "../Thc/Thc.svelte";
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
33
33
|
|
|
34
34
|
export interface Props extends Record<string, any> {
|
|
35
|
-
trigger?: Snippet<[{ value: string; modal:
|
|
35
|
+
trigger?: Snippet<[{ value: string; modal: ModalDialog }]>;
|
|
36
36
|
input?: HTMLInputElement;
|
|
37
37
|
value: string;
|
|
38
38
|
label?: SnippetWithId | THC;
|
|
@@ -65,11 +65,8 @@
|
|
|
65
65
|
noScrollLock?: boolean;
|
|
66
66
|
style?: string;
|
|
67
67
|
t?: TranslateFn;
|
|
68
|
-
renderValue?: (
|
|
69
|
-
getOptions: (
|
|
70
|
-
q: string,
|
|
71
|
-
current: Item[]
|
|
72
|
-
) => Promise<{ coll?: ItemCollection<Item>; found: Item[] }>;
|
|
68
|
+
renderValue?: (stringifiedItems: string) => string;
|
|
69
|
+
getOptions: (q: string, current: Item[]) => Promise<{ found: Item[] }>;
|
|
73
70
|
notifications?: NotificationsStack;
|
|
74
71
|
cardinality?: number;
|
|
75
72
|
renderOptionLabel?: (item: Item) => string;
|
|
@@ -134,7 +131,7 @@
|
|
|
134
131
|
tabindex = 0,
|
|
135
132
|
description,
|
|
136
133
|
class: classProp,
|
|
137
|
-
renderSize = "
|
|
134
|
+
renderSize = "lg",
|
|
138
135
|
useTrim = true,
|
|
139
136
|
//
|
|
140
137
|
required = false,
|
|
@@ -183,9 +180,13 @@
|
|
|
183
180
|
...rest
|
|
184
181
|
}: Props = $props();
|
|
185
182
|
|
|
186
|
-
let
|
|
183
|
+
let modalDialog: ModalDialog = $state()!;
|
|
187
184
|
let innerValue = $state("");
|
|
188
185
|
let isFetching = $state(false);
|
|
186
|
+
let isUnmounted = false;
|
|
187
|
+
onDestroy(() => {
|
|
188
|
+
isUnmounted = true;
|
|
189
|
+
});
|
|
189
190
|
let cardinality = $derived(_cardinality === -1 ? Infinity : _cardinality);
|
|
190
191
|
let isMultiple = $derived(cardinality > 1);
|
|
191
192
|
let showIcons = $derived(isMultiple ? showIconsCheckbox : showIconsRadio);
|
|
@@ -246,6 +247,11 @@
|
|
|
246
247
|
// reconfigure if the prop ever changes during runtime (most likely will NOT)
|
|
247
248
|
$effect(() => {
|
|
248
249
|
_selectedColl.configure({ cardinality });
|
|
250
|
+
// trim excess selections if cardinality was reduced
|
|
251
|
+
if (_selectedColl.size > cardinality) {
|
|
252
|
+
const trimmed = _selectedColl.items.slice(0, cardinality);
|
|
253
|
+
_selectedColl.clear().addMany(trimmed);
|
|
254
|
+
}
|
|
249
255
|
});
|
|
250
256
|
|
|
251
257
|
// now, create the reactive, subscribed variants
|
|
@@ -259,54 +265,32 @@
|
|
|
259
265
|
);
|
|
260
266
|
}
|
|
261
267
|
|
|
262
|
-
// $inspect("options", options);
|
|
263
|
-
// $inspect("selected", selected);
|
|
264
|
-
// $inspect("lastQuery", lastQuery, innerValue);
|
|
265
|
-
|
|
266
268
|
// hidden input which holds the final value (upon which validation happens)
|
|
267
269
|
let parentHiddenInputEl: HTMLInputElement | undefined = $state();
|
|
268
270
|
|
|
269
271
|
let activeEl: HTMLButtonElement | undefined = $state();
|
|
270
272
|
let optionsBox: HTMLDivElement | undefined = $state();
|
|
271
|
-
let modalEl: HTMLDivElement | undefined = $state();
|
|
272
273
|
|
|
273
274
|
// add_new dance...
|
|
274
275
|
let addNewBtn: HTMLButtonElement | undefined = $state();
|
|
275
276
|
let isAddNewBtnActive = $state(false);
|
|
276
277
|
let touch = $state(new Date());
|
|
277
278
|
|
|
278
|
-
// set value on open
|
|
279
|
-
// watch(
|
|
280
|
-
// () => modal.visibility().visible,
|
|
281
|
-
// (isVisible, wasVisible) => {
|
|
282
|
-
// // modal was just opened
|
|
283
|
-
// if (isVisible) {
|
|
284
|
-
// _selectedColl.clear().addMany(maybeJsonParse(value));
|
|
285
|
-
// console.log(_selectedColl.dump());
|
|
286
|
-
// // IMPORTANT: focus first selected so it scrolls into view on open
|
|
287
|
-
// if (_selectedColl.size) {
|
|
288
|
-
// console.log(1111);
|
|
289
|
-
// waitForNextRepaint().then(() => {
|
|
290
|
-
// _optionsColl.setActive(_selectedColl.items[0]);
|
|
291
|
-
// waitForNextRepaint().then(() => {
|
|
292
|
-
// scrollIntoViewTrigger = new Date();
|
|
293
|
-
// });
|
|
294
|
-
// });
|
|
295
|
-
// }
|
|
296
|
-
// }
|
|
297
|
-
// }
|
|
298
|
-
// );
|
|
299
|
-
|
|
300
279
|
// suggest options as a typeahead feature
|
|
301
280
|
const debounced = new Debounced(() => innerValue, 150);
|
|
281
|
+
let fetchRequestId = 0;
|
|
302
282
|
watch(
|
|
303
|
-
[() =>
|
|
283
|
+
[() => modalDialog.visibility().visible, () => debounced.current],
|
|
304
284
|
([isVisible, currVal]) => {
|
|
305
285
|
if (!isVisible) return;
|
|
306
286
|
isFetching = true;
|
|
287
|
+
const currentRequest = ++fetchRequestId;
|
|
307
288
|
getOptions(currVal, selected.items)
|
|
308
289
|
.then((res) => {
|
|
309
|
-
|
|
290
|
+
// ignore stale responses
|
|
291
|
+
if (currentRequest !== fetchRequestId) return;
|
|
292
|
+
|
|
293
|
+
const { found } = res;
|
|
310
294
|
|
|
311
295
|
// continue normally, with (server) provided options...
|
|
312
296
|
_optionsColl.clear().addMany(found);
|
|
@@ -325,12 +309,12 @@
|
|
|
325
309
|
);
|
|
326
310
|
|
|
327
311
|
$effect(() => {
|
|
328
|
-
if (
|
|
312
|
+
if (modalDialog.visibility().visible && touch) {
|
|
329
313
|
_selectedColl.clear().addMany(maybeJsonParse(value) as Item[]);
|
|
330
314
|
// IMPORTANT: focus first selected so it scrolls into view on open
|
|
331
315
|
if (_selectedColl.size) {
|
|
332
316
|
waitForNextRepaint().then(() => {
|
|
333
|
-
_optionsColl.setActive(_selectedColl.items[0]);
|
|
317
|
+
if (!isUnmounted) _optionsColl.setActive(_selectedColl.items[0]);
|
|
334
318
|
});
|
|
335
319
|
}
|
|
336
320
|
}
|
|
@@ -409,16 +393,15 @@
|
|
|
409
393
|
value = JSON.stringify(selected.items);
|
|
410
394
|
innerValue = "";
|
|
411
395
|
_optionsColl.clear();
|
|
412
|
-
|
|
396
|
+
modalDialog.close();
|
|
413
397
|
_dispatch_change_to_owner();
|
|
414
398
|
onChange?.(value);
|
|
415
399
|
}
|
|
416
400
|
|
|
417
|
-
// clears
|
|
401
|
+
// clears state and dispatches change; close is handled by ModalDialog's preEscapeClose
|
|
418
402
|
function escape() {
|
|
419
403
|
innerValue = "";
|
|
420
404
|
_optionsColl.clear();
|
|
421
|
-
modal?.close();
|
|
422
405
|
_dispatch_change_to_owner();
|
|
423
406
|
}
|
|
424
407
|
|
|
@@ -433,6 +416,8 @@
|
|
|
433
416
|
return groupped;
|
|
434
417
|
}
|
|
435
418
|
|
|
419
|
+
let groupedOptions = $derived(_normalize_and_group_options(options.items));
|
|
420
|
+
|
|
436
421
|
const BTN_CLS = [
|
|
437
422
|
"no-focus-visible",
|
|
438
423
|
"text-left rounded-md py-2 px-2.5 flex items-center space-x-2",
|
|
@@ -483,7 +468,7 @@
|
|
|
483
468
|
<!-- this must be on window as we're catching any typing anywhere -->
|
|
484
469
|
<svelte:window
|
|
485
470
|
onkeydown={(e) => {
|
|
486
|
-
if (
|
|
471
|
+
if (modalDialog.visibility().visible) {
|
|
487
472
|
// arrow navigation
|
|
488
473
|
if (["ArrowDown", "ArrowUp"].includes(e.key)) {
|
|
489
474
|
e.preventDefault();
|
|
@@ -514,7 +499,7 @@
|
|
|
514
499
|
<!-- must wrap both -->
|
|
515
500
|
<div>
|
|
516
501
|
{#if trigger}
|
|
517
|
-
{@render trigger({ value, modal })}
|
|
502
|
+
{@render trigger({ value, modal: modalDialog })}
|
|
518
503
|
{:else}
|
|
519
504
|
<FieldLikeButton
|
|
520
505
|
bind:value
|
|
@@ -553,7 +538,7 @@
|
|
|
553
538
|
let extra = '';
|
|
554
539
|
if (vals.length > limit) {
|
|
555
540
|
vals = vals.slice(0, limit);
|
|
556
|
-
extra = `, ... <span class="text-sm opacity-
|
|
541
|
+
extra = `, ... <span class="text-sm opacity-75">(+${(origLength - limit)})</span>`;
|
|
557
542
|
}
|
|
558
543
|
return vals.filter(v => v != null).map(_renderOptionLabel).join(", ") + extra;
|
|
559
544
|
} catch (e) {
|
|
@@ -561,274 +546,282 @@
|
|
|
561
546
|
return `${e}`; // either invalid json or not array...
|
|
562
547
|
}
|
|
563
548
|
}}
|
|
564
|
-
onclick={
|
|
549
|
+
onclick={modalDialog?.open}
|
|
565
550
|
/>
|
|
566
551
|
{/if}
|
|
567
552
|
|
|
568
|
-
<
|
|
569
|
-
bind:this={
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
553
|
+
<ModalDialog
|
|
554
|
+
bind:this={modalDialog}
|
|
555
|
+
preEscapeClose={escape}
|
|
556
|
+
classDialog="items-start"
|
|
557
|
+
class="w-full max-w-2xl bg-transparent pointer-events-none"
|
|
558
|
+
ariaLabelledby={id}
|
|
574
559
|
{noScrollLock}
|
|
575
560
|
>
|
|
576
|
-
<
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
e
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
_selectedColl.clear();
|
|
630
|
-
input?.focus();
|
|
631
|
-
}}
|
|
632
|
-
class={twMerge(
|
|
633
|
-
"control flex items-center p-1 m-1 text-sm opacity-75 underline rounded",
|
|
634
|
-
"hover:opacity-100 focus-visible:outline-neutral-400 focus-visible:opacity-100"
|
|
635
|
-
)}
|
|
636
|
-
class:opacity-20={!selected.items.length}
|
|
637
|
-
tabindex={5}
|
|
638
|
-
disabled={!selected.items.length}
|
|
639
|
-
>
|
|
640
|
-
{@html t(cardinality === 1 ? "clear" : "clear_all")}
|
|
641
|
-
</button>
|
|
642
|
-
|
|
643
|
-
<span class="p-1 m-1 text-sm"> </span>
|
|
644
|
-
<span class="flex-1 block justify-end opacity-50 text-right text-sm p-1 pr-2">
|
|
645
|
-
{selected.items.length}
|
|
646
|
-
{#if cardinality > 0 && cardinality < Infinity}
|
|
647
|
-
{@html t("cardinality_of")} {cardinality}
|
|
648
|
-
{/if}
|
|
649
|
-
{@html t("cardinality_selected")}
|
|
650
|
-
</span>
|
|
651
|
-
</div>
|
|
652
|
-
|
|
653
|
-
<!-- {#if options.items.length} -->
|
|
654
|
-
<div
|
|
655
|
-
class={[
|
|
656
|
-
"options overflow-y-auto overflow-x-hidden space-y-1 scrollbar-thin",
|
|
657
|
-
"h-55 max-h-55",
|
|
658
|
-
]}
|
|
659
|
-
bind:this={optionsBox}
|
|
660
|
-
tabindex="-1"
|
|
661
|
-
>
|
|
662
|
-
{#if isFetching && !options.items.length}
|
|
663
|
-
<!-- <div class="p-4 opacity-50"> -->
|
|
664
|
-
<div class="flex opacity-50 text-sm h-full items-center justify-center">
|
|
665
|
-
<Spinner class="w-4" />
|
|
666
|
-
</div>
|
|
667
|
-
{:else if !options.items.length && !allowUnknown}
|
|
668
|
-
<div class="flex opacity-50 text-sm h-full items-center justify-center">
|
|
669
|
-
{@html t("no_results")}
|
|
670
|
-
</div>
|
|
671
|
-
{/if}
|
|
672
|
-
|
|
673
|
-
{#if !isFetching && allowUnknown && innerValue && !have_option_label_like(options.items, innerValue)}
|
|
674
|
-
<div class="px-1">
|
|
561
|
+
<div class="pt-0 md:pt-[20vh] w-full">
|
|
562
|
+
<div class="pointer-events-auto">
|
|
563
|
+
<InputWrap
|
|
564
|
+
size={renderSize}
|
|
565
|
+
class={twMerge("m-2 mb-12 shadow-xl", classModalField)}
|
|
566
|
+
classInputBoxWrap={twMerge(
|
|
567
|
+
// always look like focused
|
|
568
|
+
`border border-input-accent dark:border-input-accent-dark`,
|
|
569
|
+
`ring-input-accent/20 dark:ring-input-accent-dark/20 ring-4`
|
|
570
|
+
)}
|
|
571
|
+
{id}
|
|
572
|
+
{required}
|
|
573
|
+
>
|
|
574
|
+
<input
|
|
575
|
+
bind:value={innerValue}
|
|
576
|
+
bind:this={input}
|
|
577
|
+
{type}
|
|
578
|
+
{id}
|
|
579
|
+
class={twMerge("form-input", renderSize, classInput)}
|
|
580
|
+
tabindex={1}
|
|
581
|
+
{required}
|
|
582
|
+
{disabled}
|
|
583
|
+
placeholder={searchPlaceholder ??
|
|
584
|
+
t(allowUnknown ? "search_submit_placeholder" : "search_placeholder")}
|
|
585
|
+
onkeydown={(e) => {
|
|
586
|
+
if (e.key === "Enter") {
|
|
587
|
+
e.preventDefault();
|
|
588
|
+
try_submit();
|
|
589
|
+
}
|
|
590
|
+
}}
|
|
591
|
+
autocomplete="off"
|
|
592
|
+
aria-controls={`${id}-options`}
|
|
593
|
+
name={`field-${id}`}
|
|
594
|
+
{...rest}
|
|
595
|
+
/>
|
|
596
|
+
|
|
597
|
+
{#snippet inputBelow()}
|
|
598
|
+
<div class="h-full border-t p-2 border-black/20">
|
|
599
|
+
<div class="text-sm -mt-1 flex items-center">
|
|
600
|
+
{#if isMultiple}
|
|
601
|
+
<button
|
|
602
|
+
type="button"
|
|
603
|
+
onclick={() => _selectedColl.addMany(options.items)}
|
|
604
|
+
class={twMerge(
|
|
605
|
+
"control flex items-center p-1 m-1 text-sm opacity-75 underline rounded",
|
|
606
|
+
"hover:opacity-100 focus-visible:outline-neutral-400 focus-visible:opacity-100"
|
|
607
|
+
)}
|
|
608
|
+
tabindex={4}
|
|
609
|
+
disabled={!options.size}
|
|
610
|
+
>
|
|
611
|
+
{@html t("select_all")}
|
|
612
|
+
</button>
|
|
613
|
+
{/if}
|
|
675
614
|
<button
|
|
676
615
|
type="button"
|
|
677
|
-
|
|
678
|
-
|
|
616
|
+
onclick={() => {
|
|
617
|
+
_selectedColl.clear();
|
|
618
|
+
input?.focus();
|
|
619
|
+
}}
|
|
679
620
|
class={twMerge(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
isAddNewBtnActive && classOptionActive
|
|
621
|
+
"control flex items-center p-1 m-1 text-sm opacity-75 underline rounded",
|
|
622
|
+
"hover:opacity-100 focus-visible:outline-neutral-400 focus-visible:opacity-100"
|
|
683
623
|
)}
|
|
624
|
+
class:opacity-20={!selected.items.length}
|
|
625
|
+
tabindex={5}
|
|
626
|
+
disabled={!selected.items.length}
|
|
684
627
|
>
|
|
685
|
-
{t("
|
|
628
|
+
{@html t(cardinality === 1 ? "clear" : "clear_all")}
|
|
686
629
|
</button>
|
|
687
|
-
</div>
|
|
688
|
-
{/if}
|
|
689
630
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
class={twMerge(
|
|
694
|
-
"text-sm capitalize opacity-50 border-b border-black/10 mb-0.5 p-1 mx-1",
|
|
695
|
-
classOptgroup
|
|
696
|
-
)}
|
|
631
|
+
<span class="p-1 m-1 text-sm"> </span>
|
|
632
|
+
<span
|
|
633
|
+
class="flex-1 block justify-end opacity-75 text-right text-xs p-1 pr-2"
|
|
697
634
|
>
|
|
698
|
-
{
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
635
|
+
{selected.items.length}
|
|
636
|
+
{#if cardinality > 0 && cardinality < Infinity}
|
|
637
|
+
{@html t("cardinality_of")} {cardinality}
|
|
638
|
+
{/if}
|
|
639
|
+
{@html t("cardinality_selected")}
|
|
640
|
+
</span>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<!-- {#if options.items.length} -->
|
|
644
|
+
<div
|
|
645
|
+
id={`${id}-options`}
|
|
646
|
+
class={[
|
|
647
|
+
"options overflow-y-auto overflow-x-hidden space-y-1 scrollbar-thin",
|
|
648
|
+
"h-55 max-h-55",
|
|
649
|
+
]}
|
|
650
|
+
bind:this={optionsBox}
|
|
651
|
+
tabindex="-1"
|
|
652
|
+
>
|
|
653
|
+
{#if isFetching && !options.items.length}
|
|
654
|
+
<div class="flex opacity-50 text-sm h-full items-center justify-center">
|
|
655
|
+
<Spinner class="w-4" />
|
|
656
|
+
</div>
|
|
657
|
+
{:else if !options.items.length && !allowUnknown}
|
|
658
|
+
<div class="flex opacity-50 text-sm h-full items-center justify-center">
|
|
659
|
+
{@html t("no_results")}
|
|
660
|
+
</div>
|
|
661
|
+
{/if}
|
|
662
|
+
|
|
663
|
+
{#if !isFetching && allowUnknown && innerValue && !have_option_label_like(options.items, innerValue)}
|
|
664
|
+
<div class="px-1">
|
|
709
665
|
<button
|
|
710
666
|
type="button"
|
|
711
|
-
|
|
712
|
-
onclick={
|
|
713
|
-
if (isMultiple) {
|
|
714
|
-
if (selected.isFull && !_selectedColl.exists(item)) {
|
|
715
|
-
return notifications?.error(t("cardinality_full"), {
|
|
716
|
-
ttl: 1000,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
_selectedColl.toggleAdd(item);
|
|
720
|
-
} else {
|
|
721
|
-
_selectedColl.clear();
|
|
722
|
-
_selectedColl.add(item);
|
|
723
|
-
submit();
|
|
724
|
-
}
|
|
725
|
-
}}
|
|
726
|
-
class:active
|
|
727
|
-
class:selected={isSelected}
|
|
667
|
+
bind:this={addNewBtn}
|
|
668
|
+
onclick={add_new}
|
|
728
669
|
class={twMerge(
|
|
729
670
|
BTN_CLS,
|
|
730
|
-
isSelected && "bg-neutral-200 dark:bg-neutral-800",
|
|
731
671
|
classOption,
|
|
732
|
-
|
|
733
|
-
active && classOptionActive
|
|
672
|
+
isAddNewBtnActive && classOptionActive
|
|
734
673
|
)}
|
|
735
|
-
tabindex="-1"
|
|
736
|
-
role="checkbox"
|
|
737
|
-
aria-checked={isSelected}
|
|
738
674
|
>
|
|
739
|
-
{
|
|
740
|
-
<span class={isSelected ? "opacity-100" : "opacity-25"}>
|
|
741
|
-
{#if isMultiple}
|
|
742
|
-
{#if isSelected}
|
|
743
|
-
{@html iconCheckboxCheck()}
|
|
744
|
-
{:else}
|
|
745
|
-
{@html iconCheckboxEmpty()}
|
|
746
|
-
{/if}
|
|
747
|
-
{:else if isSelected}
|
|
748
|
-
{@html iconRadioCheck()}
|
|
749
|
-
{:else}
|
|
750
|
-
{@html iconRadioEmpty()}
|
|
751
|
-
{/if}
|
|
752
|
-
</span>
|
|
753
|
-
{/if}
|
|
754
|
-
<span
|
|
755
|
-
class={twMerge(
|
|
756
|
-
"min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap"
|
|
757
|
-
)}>{_renderOptionLabel(item)}</span
|
|
758
|
-
>
|
|
675
|
+
{t("add_new", { value: innerValue })}
|
|
759
676
|
</button>
|
|
760
|
-
</
|
|
677
|
+
</div>
|
|
678
|
+
{/if}
|
|
679
|
+
|
|
680
|
+
{#each groupedOptions as [_optgroup, _opts]}
|
|
681
|
+
{#if _optgroup}
|
|
682
|
+
<div
|
|
683
|
+
class={twMerge(
|
|
684
|
+
"text-sm capitalize opacity-50 border-b border-black/10 mb-0.5 p-1 mx-1",
|
|
685
|
+
classOptgroup
|
|
686
|
+
)}
|
|
687
|
+
>
|
|
688
|
+
{_optgroup}
|
|
689
|
+
</div>
|
|
690
|
+
{/if}
|
|
691
|
+
<ul class="space-y-0.5">
|
|
692
|
+
<!-- {#each options.items as item} -->
|
|
693
|
+
{#each _opts as item (item[itemIdPropName])}
|
|
694
|
+
{@const active =
|
|
695
|
+
item[itemIdPropName] === options.active?.[itemIdPropName]}
|
|
696
|
+
{@const isSelected =
|
|
697
|
+
selected.items && _selectedColl.exists(item[itemIdPropName])}
|
|
698
|
+
<li class:active class="px-1">
|
|
699
|
+
<button
|
|
700
|
+
type="button"
|
|
701
|
+
id={btn_id(item[itemIdPropName])}
|
|
702
|
+
onclick={() => {
|
|
703
|
+
if (isMultiple) {
|
|
704
|
+
if (selected.isFull && !_selectedColl.exists(item)) {
|
|
705
|
+
return notifications?.error(t("cardinality_full"), {
|
|
706
|
+
ttl: 1000,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
_selectedColl.toggleAdd(item);
|
|
710
|
+
} else {
|
|
711
|
+
_selectedColl.clear();
|
|
712
|
+
_selectedColl.add(item);
|
|
713
|
+
submit();
|
|
714
|
+
}
|
|
715
|
+
}}
|
|
716
|
+
class:active
|
|
717
|
+
class:selected={isSelected}
|
|
718
|
+
class={twMerge(
|
|
719
|
+
BTN_CLS,
|
|
720
|
+
isSelected && "bg-neutral-200 dark:bg-neutral-800",
|
|
721
|
+
classOption,
|
|
722
|
+
// active && "border-neutral-400",
|
|
723
|
+
active && classOptionActive
|
|
724
|
+
)}
|
|
725
|
+
tabindex="-1"
|
|
726
|
+
role={isMultiple ? "checkbox" : "radio"}
|
|
727
|
+
aria-checked={isSelected}
|
|
728
|
+
>
|
|
729
|
+
{#if showIcons}
|
|
730
|
+
<span class={isSelected ? "opacity-100" : "opacity-25"}>
|
|
731
|
+
{#if isMultiple}
|
|
732
|
+
{#if isSelected}
|
|
733
|
+
{@html iconCheckboxCheck()}
|
|
734
|
+
{:else}
|
|
735
|
+
{@html iconCheckboxEmpty()}
|
|
736
|
+
{/if}
|
|
737
|
+
{:else if isSelected}
|
|
738
|
+
{@html iconRadioCheck()}
|
|
739
|
+
{:else}
|
|
740
|
+
{@html iconRadioEmpty()}
|
|
741
|
+
{/if}
|
|
742
|
+
</span>
|
|
743
|
+
{/if}
|
|
744
|
+
<span
|
|
745
|
+
class={twMerge(
|
|
746
|
+
"min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap"
|
|
747
|
+
)}>{_renderOptionLabel(item)}</span
|
|
748
|
+
>
|
|
749
|
+
</button>
|
|
750
|
+
</li>
|
|
751
|
+
{/each}
|
|
752
|
+
</ul>
|
|
761
753
|
{/each}
|
|
762
|
-
</
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
754
|
+
</div>
|
|
755
|
+
<!-- {/if} -->
|
|
756
|
+
<div class="p-2 px-3 flex items-end justify-between">
|
|
757
|
+
<div class="text-xs opacity-75">
|
|
758
|
+
<!-- Use arrows to navigate. Spacebar and Enter to select and/or submit. -->
|
|
759
|
+
{#if allowUnknown}
|
|
760
|
+
{@html t("unknown_allowed")}
|
|
761
|
+
{:else}
|
|
762
|
+
{@html t("unknown_not_allowed")}
|
|
763
|
+
{/if}
|
|
764
|
+
</div>
|
|
765
|
+
<div>
|
|
766
|
+
<Button
|
|
767
|
+
class="control"
|
|
768
|
+
type="button"
|
|
769
|
+
variant="primary"
|
|
770
|
+
onclick={async (e) => {
|
|
771
|
+
e.preventDefault();
|
|
772
|
+
try_submit(true);
|
|
773
|
+
}}
|
|
774
|
+
tabindex={3}
|
|
775
|
+
>
|
|
776
|
+
{@html t("submit")}
|
|
777
|
+
</Button>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
{/snippet}
|
|
782
|
+
|
|
783
|
+
{#snippet inputAfter()}
|
|
784
|
+
<div class="flex pl-2 items-center justify-center opacity-50">
|
|
785
|
+
{#if isFetching}
|
|
786
|
+
<Spinner class="w-4" />
|
|
773
787
|
{/if}
|
|
774
788
|
</div>
|
|
775
|
-
<div>
|
|
776
|
-
<
|
|
777
|
-
class="control"
|
|
789
|
+
<div class="flex pl-2 pr-1 items-center justify-center">
|
|
790
|
+
<button
|
|
778
791
|
type="button"
|
|
779
|
-
|
|
780
|
-
|
|
792
|
+
class={twMerge(
|
|
793
|
+
"opacity-75 rounded",
|
|
794
|
+
"hover:opacity-100 hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
|
795
|
+
"focus-visible:opacity-100 focus-visible:outline-0",
|
|
796
|
+
"focus-visible:bg-neutral-200 dark:focus-visible:bg-neutral-800"
|
|
797
|
+
)}
|
|
798
|
+
use:tooltip
|
|
799
|
+
aria-label={t("x_close")}
|
|
800
|
+
onclick={(e) => {
|
|
781
801
|
e.preventDefault();
|
|
782
|
-
|
|
802
|
+
if (innerValue.trim() == "") {
|
|
803
|
+
escape();
|
|
804
|
+
return modalDialog.close();
|
|
805
|
+
}
|
|
806
|
+
innerValue = "";
|
|
807
|
+
input?.focus();
|
|
783
808
|
}}
|
|
784
|
-
tabindex={
|
|
809
|
+
tabindex={2}
|
|
785
810
|
>
|
|
786
|
-
|
|
787
|
-
</
|
|
811
|
+
<X class="m-2 size-6" />
|
|
812
|
+
</button>
|
|
788
813
|
</div>
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
{
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
<button
|
|
801
|
-
type="button"
|
|
802
|
-
class={twMerge(
|
|
803
|
-
"opacity-50 rounded",
|
|
804
|
-
"hover:opacity-100 hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
|
805
|
-
"focus-visible:opacity-100 focus-visible:outline-0",
|
|
806
|
-
"focus-visible:bg-neutral-200 dark:focus-visible:bg-neutral-800"
|
|
807
|
-
)}
|
|
808
|
-
use:tooltip
|
|
809
|
-
aria-label={t("x_close")}
|
|
810
|
-
onclick={(e) => {
|
|
811
|
-
e.preventDefault();
|
|
812
|
-
if (innerValue.trim() == "") {
|
|
813
|
-
return escape();
|
|
814
|
-
}
|
|
815
|
-
innerValue = "";
|
|
816
|
-
input?.focus();
|
|
817
|
-
}}
|
|
818
|
-
tabindex={2}
|
|
819
|
-
>
|
|
820
|
-
<X class="m-2 size-4 " />
|
|
821
|
-
</button>
|
|
822
|
-
</div>
|
|
823
|
-
{/snippet}
|
|
824
|
-
|
|
825
|
-
{#snippet inputBefore()}
|
|
826
|
-
<div class="flex flex-col items-center justify-center pl-3 opacity-50">
|
|
827
|
-
{@html iconSearch({ size: 14 })}
|
|
828
|
-
</div>
|
|
829
|
-
{/snippet}
|
|
830
|
-
</InputWrap>
|
|
831
|
-
</Modal>
|
|
814
|
+
{/snippet}
|
|
815
|
+
|
|
816
|
+
{#snippet inputBefore()}
|
|
817
|
+
<div class="flex flex-col items-center justify-center pl-3 opacity-75">
|
|
818
|
+
{@html iconSearch({ size: 19, strokeWidth: 3 })}
|
|
819
|
+
</div>
|
|
820
|
+
{/snippet}
|
|
821
|
+
</InputWrap>
|
|
822
|
+
</div>
|
|
823
|
+
</div>
|
|
824
|
+
</ModalDialog>
|
|
832
825
|
</div>
|
|
833
826
|
|
|
834
827
|
<style>
|