@sewadah/selaras 0.3.1 → 0.4.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 +10 -1
- package/agent/catalog.mjs +73 -0
- package/agent/mcp.mjs +15 -0
- package/agent/server.mjs +58 -0
- package/dist/agent/catalog.json +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +7 -5
- package/dist/runtime/components/AlertDialog.d.vue.ts +5 -3
- package/dist/runtime/components/AlertDialog.vue +17 -9
- package/dist/runtime/components/AlertDialog.vue.d.ts +5 -3
- package/dist/runtime/components/App.vue +2 -0
- package/dist/runtime/components/Autocomplete.d.vue.ts +2 -2
- package/dist/runtime/components/Autocomplete.vue +1 -1
- package/dist/runtime/components/Autocomplete.vue.d.ts +2 -2
- package/dist/runtime/components/Button.d.vue.ts +7 -5
- package/dist/runtime/components/Button.vue +14 -3
- package/dist/runtime/components/Button.vue.d.ts +7 -5
- package/dist/runtime/components/ColorModeSwitch.d.vue.ts +18 -0
- package/dist/runtime/components/ColorModeSwitch.vue +59 -0
- package/dist/runtime/components/ColorModeSwitch.vue.d.ts +18 -0
- package/dist/runtime/components/ConfirmRenderer.d.vue.ts +3 -0
- package/dist/runtime/components/ConfirmRenderer.vue +39 -0
- package/dist/runtime/components/ConfirmRenderer.vue.d.ts +3 -0
- package/dist/runtime/components/ContextMenu.vue +4 -2
- package/dist/runtime/components/Drawer.vue +3 -2
- package/dist/runtime/components/Dropdown.vue +4 -2
- package/dist/runtime/components/Modal.vue +3 -2
- package/dist/runtime/components/Popover.vue +4 -2
- package/dist/runtime/components/Select.d.vue.ts +8 -4
- package/dist/runtime/components/Select.vue +1 -1
- package/dist/runtime/components/Select.vue.d.ts +8 -4
- package/dist/runtime/components/Slideover.vue +3 -2
- package/dist/runtime/components/Tooltip.vue +4 -2
- package/dist/runtime/composables/use-confirm.d.ts +14 -0
- package/dist/runtime/composables/use-confirm.js +28 -0
- package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +41 -32
- package/dist/runtime/internal/ComboboxSelectBase.vue +154 -68
- package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +41 -32
- package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +9 -7
- package/dist/runtime/internal/ComboboxSelectBody.vue +6 -6
- package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +9 -7
- package/dist/runtime/internal/combobox-select.d.ts +19 -18
- package/dist/runtime/internal/combobox-select.js +36 -14
- package/dist/runtime/internal/programmatic-services.d.ts +11 -0
- package/dist/runtime/internal/programmatic-services.js +1 -0
- package/dist/runtime/theme/badge.d.ts +9 -0
- package/dist/runtime/theme/badge.js +2 -1
- package/dist/runtime/theme/button.d.ts +18 -0
- package/dist/runtime/theme/button.js +4 -1
- package/dist/runtime/theme/select.js +1 -4
- package/dist/runtime/theme/toast.js +1 -1
- package/dist/runtime/theme-config.d.ts +1 -1
- package/dist/runtime/types/index.d.ts +4 -1
- package/dist/runtime/utils/autocomplete-contracts.d.ts +4 -4
- package/dist/runtime/utils/file-icons.js +1 -0
- package/dist/runtime/utils/messages.d.ts +1 -0
- package/dist/runtime/utils/messages.js +1 -0
- package/dist/runtime/utils/select-contracts.d.ts +18 -10
- package/dist/runtime/utils/ui.d.ts +11 -0
- package/dist/runtime/utils/ui.js +54 -1
- package/package.json +19 -9
- package/skills/selaras/SKILL.md +40 -0
- package/skills/selaras/agents/openai.yaml +4 -0
|
@@ -44,8 +44,8 @@ const props = defineProps({
|
|
|
44
44
|
labelKey: { type: String, required: false },
|
|
45
45
|
open: { type: Boolean, required: false },
|
|
46
46
|
defaultOpen: { type: Boolean, required: false },
|
|
47
|
-
modelValue: { type: [String, Number, Array], required: false },
|
|
48
|
-
defaultValue: { type: [String, Number, Array], required: false },
|
|
47
|
+
modelValue: { type: [String, Number, Object, Array], required: false },
|
|
48
|
+
defaultValue: { type: [String, Number, Object, Array], required: false },
|
|
49
49
|
multiple: { type: Boolean, required: false },
|
|
50
50
|
searchable: { type: Boolean, required: false },
|
|
51
51
|
virtualize: { type: [Boolean, Object], required: false },
|
|
@@ -117,7 +117,16 @@ function updateSelection(event, value) {
|
|
|
117
117
|
localValue.value = value;
|
|
118
118
|
emit(event, value);
|
|
119
119
|
}
|
|
120
|
+
const localOpen = ref(props.defaultOpen ?? false);
|
|
121
|
+
const isOpenControlled = () => Object.hasOwn(instance.vnode.props ?? {}, "open");
|
|
122
|
+
const open = computed(() => isOpenControlled() ? props.open ?? false : localOpen.value);
|
|
123
|
+
const isMobile = useIsMobile();
|
|
124
|
+
const mobilePresentation = ref(false);
|
|
125
|
+
const showMobileModal = computed(() => mobilePresentation.value);
|
|
126
|
+
const showMobileAutocompleteModal = computed(() => mobilePresentation.value && !!props.creatable);
|
|
127
|
+
const modalBody = ref();
|
|
120
128
|
const editableInput = ref();
|
|
129
|
+
const activeEditableInput = computed(() => showMobileAutocompleteModal.value ? modalBody.value?.searchElement : editableInput.value);
|
|
121
130
|
const comboboxRootKey = ref(0);
|
|
122
131
|
function setEditableInput(element) {
|
|
123
132
|
const candidate = typeof HTMLInputElement !== "undefined" && element instanceof HTMLInputElement ? element : element?.$el;
|
|
@@ -241,13 +250,13 @@ function revertUnmatchedText() {
|
|
|
241
250
|
return true;
|
|
242
251
|
}
|
|
243
252
|
function onSearchKeydown(event) {
|
|
244
|
-
if (event.target !==
|
|
253
|
+
if (event.target !== activeEditableInput.value)
|
|
245
254
|
return;
|
|
246
255
|
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key) && !event.isComposing && !isSearchComposing.value) {
|
|
247
256
|
keyboardNavigatedOptions.value = true;
|
|
248
257
|
return;
|
|
249
258
|
}
|
|
250
|
-
if (event.key === "Backspace" && props.creatable && props.multiple && props.displayMode === "comma" && !event.isComposing && !isSearchComposing.value && event.keyCode !== 229 && !searchText.value && !
|
|
259
|
+
if (event.key === "Backspace" && props.creatable && props.multiple && props.displayMode === "comma" && !event.isComposing && !isSearchComposing.value && event.keyCode !== 229 && !searchText.value && !activeEditableInput.value?.value && selectedValues.value.length) {
|
|
251
260
|
removeValue(selectedValues.value.at(-1));
|
|
252
261
|
event.preventDefault();
|
|
253
262
|
return;
|
|
@@ -266,18 +275,20 @@ function onSearchKeydown(event) {
|
|
|
266
275
|
if (props.forceSelection)
|
|
267
276
|
return;
|
|
268
277
|
if (props.creatable && !searchText.value.trim()) {
|
|
269
|
-
const activeId2 =
|
|
278
|
+
const activeId2 = activeEditableInput.value?.getAttribute("aria-activedescendant");
|
|
270
279
|
const activeOption2 = activeId2 ? document.getElementById(activeId2) : null;
|
|
271
280
|
if (keyboardNavigatedOptions.value && activeOption2?.getAttribute("aria-selected") === "false")
|
|
272
281
|
return;
|
|
273
282
|
event.stopPropagation();
|
|
274
283
|
return;
|
|
275
284
|
}
|
|
276
|
-
const activeId =
|
|
285
|
+
const activeId = activeEditableInput.value?.getAttribute("aria-activedescendant");
|
|
277
286
|
const activeOption = activeId ? document.getElementById(activeId) : null;
|
|
278
287
|
if ((!hasMatchingOption(searchText.value) || !activeOption || activeOption.hasAttribute("data-selaras-create-option")) && commitCreatableText(searchText.value)) {
|
|
279
288
|
if (props.multiple)
|
|
280
289
|
searchText.value = "";
|
|
290
|
+
else if (showMobileAutocompleteModal.value)
|
|
291
|
+
updateOpen(false);
|
|
281
292
|
event.preventDefault();
|
|
282
293
|
event.stopPropagation();
|
|
283
294
|
}
|
|
@@ -297,7 +308,9 @@ onUnmounted(() => {
|
|
|
297
308
|
if (typeof window !== "undefined")
|
|
298
309
|
endSuggestionPointer();
|
|
299
310
|
});
|
|
300
|
-
function onSearchBlur() {
|
|
311
|
+
function onSearchBlur(event) {
|
|
312
|
+
if (props.creatable && event?.target !== editableInput.value)
|
|
313
|
+
return;
|
|
301
314
|
if (pointerInSuggestionContent.value)
|
|
302
315
|
return;
|
|
303
316
|
if (revertUnmatchedText())
|
|
@@ -329,7 +342,7 @@ watch(
|
|
|
329
342
|
([multiple, currentValue], [previousMultiple, previousValue]) => {
|
|
330
343
|
if (multiple === previousMultiple)
|
|
331
344
|
return;
|
|
332
|
-
const wasEditableFocused = props.creatable && typeof document !== "undefined" && document.activeElement ===
|
|
345
|
+
const wasEditableFocused = props.creatable && typeof document !== "undefined" && document.activeElement === activeEditableInput.value;
|
|
333
346
|
const abortingComposition = !!props.creatable && isSearchComposing.value;
|
|
334
347
|
const previouslyIdle = props.creatable && props.searchTerm === void 0 && searchText.value === (previousMultiple || !props.resetSearchTermOnSelect ? "" : singleSelectionLabel(previousValue));
|
|
335
348
|
const savedSearchText = searchText.value;
|
|
@@ -354,7 +367,7 @@ watch(
|
|
|
354
367
|
if (props.searchTerm === void 0)
|
|
355
368
|
internalSearchText.value = nextSearchText;
|
|
356
369
|
}
|
|
357
|
-
const priorInput =
|
|
370
|
+
const priorInput = activeEditableInput.value;
|
|
358
371
|
nextTick(() => {
|
|
359
372
|
if (props.creatable || props.searchable) {
|
|
360
373
|
if (props.searchTerm === void 0)
|
|
@@ -369,7 +382,7 @@ watch(
|
|
|
369
382
|
return;
|
|
370
383
|
if (document.activeElement !== document.body && document.activeElement !== priorInput)
|
|
371
384
|
return;
|
|
372
|
-
|
|
385
|
+
activeEditableInput.value?.focus();
|
|
373
386
|
});
|
|
374
387
|
},
|
|
375
388
|
{ flush: "pre" }
|
|
@@ -441,20 +454,10 @@ const groupProps = computed(() => resolveSlot(ui.value.group, props.ui?.group));
|
|
|
441
454
|
const itemProps = computed(() => resolveSlot(ui.value.item, props.ui?.item));
|
|
442
455
|
const itemIndicatorProps = computed(() => resolveSlot(ui.value.itemIndicator, props.ui?.itemIndicator));
|
|
443
456
|
const emptyProps = computed(() => resolveSlot(ui.value.empty, props.ui?.empty));
|
|
444
|
-
const mobileContentProps = computed(() =>
|
|
445
|
-
|
|
446
|
-
{
|
|
447
|
-
style: {
|
|
448
|
-
width: "min(calc(100vw - 2rem), 36rem)",
|
|
449
|
-
minWidth: "0",
|
|
450
|
-
maxHeight: "min(50vh, 28rem)"
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
resolveSlot(ui.value.mobilePanel, props.ui?.mobilePanel)
|
|
457
|
+
const mobileContentProps = computed(() => mergeProps(
|
|
458
|
+
resolveSlot(ui.value.mobileContent, props.ui?.mobileContent),
|
|
459
|
+
props.creatable ? resolveSlot(ui.value.mobilePanel, props.ui?.mobilePanel) : {}
|
|
454
460
|
));
|
|
455
|
-
const localOpen = ref(props.defaultOpen ?? false);
|
|
456
|
-
const isOpenControlled = () => Object.hasOwn(instance.vnode.props ?? {}, "open");
|
|
457
|
-
const open = computed(() => isOpenControlled() ? props.open ?? false : localOpen.value);
|
|
458
461
|
watch(flatOptions, (options, previousOptions) => {
|
|
459
462
|
const value = selection.value;
|
|
460
463
|
if (!props.creatable || props.multiple || open.value || props.searchTerm !== void 0 || !props.resetSearchTermOnSelect || value === void 0 || Array.isArray(value))
|
|
@@ -479,52 +482,79 @@ function resetSelection(event) {
|
|
|
479
482
|
updateOpen(false);
|
|
480
483
|
});
|
|
481
484
|
}
|
|
482
|
-
const isMobile = useIsMobile();
|
|
483
|
-
const mobilePresentation = ref(false);
|
|
484
485
|
watch(open, (open2) => {
|
|
485
|
-
if (!open2 && mobilePresentation.value &&
|
|
486
|
-
searchText.value = "";
|
|
486
|
+
if (!open2 && mobilePresentation.value && props.resetSearchTermOnBlur)
|
|
487
|
+
searchText.value = props.creatable && !props.multiple ? singleSelectionLabel(selection.value) : "";
|
|
487
488
|
mobilePresentation.value = open2 && !!props.adaptive && isMobile.value;
|
|
488
489
|
});
|
|
489
490
|
onMounted(() => {
|
|
490
491
|
if (!open.value)
|
|
491
492
|
return;
|
|
492
493
|
mobilePresentation.value = !!props.adaptive && isMobile.value;
|
|
493
|
-
if (mobilePresentation.value && props.creatable && !props.disabled) {
|
|
494
|
-
nextTick(() => {
|
|
495
|
-
editableInput.value?.focus();
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
494
|
});
|
|
499
|
-
const
|
|
500
|
-
const showMobileAutocompletePanel = computed(() => mobilePresentation.value && !!props.creatable);
|
|
495
|
+
const useMobileAutocompleteTrigger = computed(() => props.creatable && (open.value ? showMobileAutocompleteModal.value : props.adaptive && isMobile.value));
|
|
501
496
|
const modalId = `selaras-select-modal-${useId()}`;
|
|
502
497
|
const selectTrigger = ref();
|
|
503
|
-
const
|
|
504
|
-
const modalTriggerAttrs = computed(() => !props.creatable && (open.value ? showMobileSelectModal.value : props.adaptive && isMobile.value) ? { "aria-haspopup": "dialog", "aria-controls": modalId } : {});
|
|
498
|
+
const modalTriggerAttrs = computed(() => !props.creatable && (open.value ? showMobileModal.value : props.adaptive && isMobile.value) ? { "aria-haspopup": "dialog", "aria-controls": modalId } : {});
|
|
505
499
|
function onModalSelection(value) {
|
|
506
500
|
setValue(value == null ? props.multiple ? [] : void 0 : value);
|
|
501
|
+
if (props.creatable) {
|
|
502
|
+
nextTick(() => {
|
|
503
|
+
if (props.resetSearchTermOnSelect)
|
|
504
|
+
searchText.value = props.multiple ? "" : singleSelectionLabel(selection.value);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
507
|
if (!props.multiple)
|
|
508
508
|
updateOpen(false);
|
|
509
509
|
}
|
|
510
|
+
watch(selection, () => {
|
|
511
|
+
if (useMobileAutocompleteTrigger.value && !open.value && props.resetSearchTermOnSelect && props.searchTerm === void 0)
|
|
512
|
+
internalSearchText.value = props.multiple ? "" : singleSelectionLabel(selection.value);
|
|
513
|
+
});
|
|
514
|
+
function openMobileAutocomplete(event) {
|
|
515
|
+
if (props.disabled || event && !["ArrowDown", "ArrowUp", "Enter", " "].includes(event.key))
|
|
516
|
+
return;
|
|
517
|
+
event?.preventDefault();
|
|
518
|
+
updateOpen(true);
|
|
519
|
+
}
|
|
520
|
+
function finishMobileAutocomplete() {
|
|
521
|
+
if (isSearchComposing.value)
|
|
522
|
+
return;
|
|
523
|
+
if (!revertUnmatchedText() && !props.forceSelection)
|
|
524
|
+
commitCreatableText(searchText.value);
|
|
525
|
+
updateOpen(false);
|
|
526
|
+
}
|
|
527
|
+
function serializeFormValue(value) {
|
|
528
|
+
if (typeof value !== "object")
|
|
529
|
+
return String(value);
|
|
530
|
+
try {
|
|
531
|
+
return JSON.stringify(value) ?? "";
|
|
532
|
+
} catch {
|
|
533
|
+
return String(value);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
510
536
|
const adaptiveUi = computed(() => ({
|
|
511
537
|
content: {
|
|
512
|
-
class: "rounded-[var(--selaras-resolved-radius-md)]",
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (props.searchable && modalBody.value?.focusSearch())
|
|
518
|
-
event.preventDefault();
|
|
519
|
-
},
|
|
520
|
-
"onCloseAutoFocus": (event) => {
|
|
538
|
+
"class": "rounded-[var(--selaras-resolved-radius-md)]",
|
|
539
|
+
"id": modalId,
|
|
540
|
+
"aria-modal": true,
|
|
541
|
+
"onOpenAutoFocus": (event) => {
|
|
542
|
+
if (props.searchable && modalBody.value?.focusSearch())
|
|
521
543
|
event.preventDefault();
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
544
|
+
},
|
|
545
|
+
"onCloseAutoFocus": (event) => {
|
|
546
|
+
event.preventDefault();
|
|
547
|
+
if (props.disabled)
|
|
548
|
+
return;
|
|
549
|
+
nextTick(() => {
|
|
550
|
+
const target = props.creatable ? editableInput.value : selectTrigger.value?.$el;
|
|
551
|
+
if (target?.isConnected)
|
|
552
|
+
target.focus();
|
|
553
|
+
});
|
|
554
|
+
}
|
|
526
555
|
}
|
|
527
556
|
}));
|
|
557
|
+
const modalSearchInputProps = computed(() => mergeProps(searchInputProps.value, props.creatable ? { "id": `${selectId.value ?? modalId}-input`, "aria-label": nativeSearchInputAttrs.value["aria-label"] ?? props.placeholder ?? messages.value.search } : {}));
|
|
528
558
|
const bodyProps = computed(() => ({
|
|
529
559
|
searchable: props.searchable,
|
|
530
560
|
creatable: props.creatable,
|
|
@@ -554,7 +584,7 @@ const bodyProps = computed(() => ({
|
|
|
554
584
|
<template>
|
|
555
585
|
<ComboboxRoot
|
|
556
586
|
:key="comboboxRootKey"
|
|
557
|
-
:open="open"
|
|
587
|
+
:open="useMobileAutocompleteTrigger ? false : open"
|
|
558
588
|
:model-value="rekaSelection"
|
|
559
589
|
:multiple="multiple"
|
|
560
590
|
:disabled="disabled"
|
|
@@ -574,7 +604,7 @@ const bodyProps = computed(() => ({
|
|
|
574
604
|
typing is always available without opening anything first.
|
|
575
605
|
-->
|
|
576
606
|
<div
|
|
577
|
-
v-if="creatable"
|
|
607
|
+
v-if="creatable && !useMobileAutocompleteTrigger"
|
|
578
608
|
data-ui-group-item
|
|
579
609
|
:data-selaras-field-filled="selectedValues.length ? '' : void 0"
|
|
580
610
|
:data-selaras-field-active="open ? '' : void 0"
|
|
@@ -608,7 +638,7 @@ const bodyProps = computed(() => ({
|
|
|
608
638
|
-->
|
|
609
639
|
<TagsInputItem
|
|
610
640
|
v-for="option in visibleOptions"
|
|
611
|
-
:key="option.
|
|
641
|
+
:key="option.key"
|
|
612
642
|
:value="option.value"
|
|
613
643
|
as-child
|
|
614
644
|
>
|
|
@@ -620,7 +650,7 @@ const bodyProps = computed(() => ({
|
|
|
620
650
|
:ui="{ root: 'data-[state=active]:ring-2 data-[state=active]:ring-[var(--_selaras-color-fill)]' }"
|
|
621
651
|
>
|
|
622
652
|
<TagsInputItemText as="span">
|
|
623
|
-
<slot v-if="option.raw" name="item" :item="option.raw">
|
|
653
|
+
<slot v-if="option.raw !== void 0" name="item" :item="option.raw">
|
|
624
654
|
{{ option.label }}
|
|
625
655
|
</slot>
|
|
626
656
|
<template v-else>
|
|
@@ -710,6 +740,50 @@ const bodyProps = computed(() => ({
|
|
|
710
740
|
</ComboboxTrigger>
|
|
711
741
|
</div>
|
|
712
742
|
|
|
743
|
+
<div
|
|
744
|
+
v-else-if="creatable"
|
|
745
|
+
data-ui-group-item
|
|
746
|
+
:data-selaras-field-filled="selectedValues.length ? '' : void 0"
|
|
747
|
+
:data-selaras-field-active="open ? '' : void 0"
|
|
748
|
+
v-bind="triggerProps"
|
|
749
|
+
@click="openMobileAutocomplete()"
|
|
750
|
+
>
|
|
751
|
+
<span v-if="multiple && selectedOptions.length" v-bind="commaValueProps">{{ commaText }}</span>
|
|
752
|
+
<input
|
|
753
|
+
:id="selectId"
|
|
754
|
+
:ref="setEditableInput"
|
|
755
|
+
:value="multiple ? '' : searchText"
|
|
756
|
+
:placeholder="multiple && selectedOptions.length ? void 0 : placeholder"
|
|
757
|
+
:disabled="disabled"
|
|
758
|
+
:aria-invalid="selectInvalid || void 0"
|
|
759
|
+
:aria-describedby="searchDescribedBy"
|
|
760
|
+
:aria-busy="loading || void 0"
|
|
761
|
+
v-bind="searchInputProps"
|
|
762
|
+
readonly
|
|
763
|
+
aria-haspopup="dialog"
|
|
764
|
+
:aria-expanded="open"
|
|
765
|
+
:aria-controls="modalId"
|
|
766
|
+
@keydown="openMobileAutocomplete"
|
|
767
|
+
>
|
|
768
|
+
<Button v-if="clearable && !disabled && selectedOptions.length" :size="clearSize" variant="text" color="neutral" tabindex="-1" :aria-label="messages.clear" v-bind="clearProps" @click.stop="clear">
|
|
769
|
+
<template #icon>
|
|
770
|
+
<slot name="clear-icon">
|
|
771
|
+
<Icon :name="icons.close" class="size-4" />
|
|
772
|
+
</slot>
|
|
773
|
+
</template>
|
|
774
|
+
</Button>
|
|
775
|
+
<slot v-if="loading" name="loading-icon">
|
|
776
|
+
<Icon :name="icons.loading" class="size-4 animate-spin" v-bind="iconProps" />
|
|
777
|
+
</slot>
|
|
778
|
+
<span v-if="loading" class="sr-only">{{ messages.loading }}</span>
|
|
779
|
+
<button v-if="dropdown" type="button" tabindex="-1" :disabled="disabled" :aria-label="messages.search" v-bind="dropdownProps" @click.stop="onDropdownClick();
|
|
780
|
+
openMobileAutocomplete()">
|
|
781
|
+
<slot name="dropdown-icon">
|
|
782
|
+
<Icon :name="icons.chevronDown" class="size-4" />
|
|
783
|
+
</slot>
|
|
784
|
+
</button>
|
|
785
|
+
</div>
|
|
786
|
+
|
|
713
787
|
<ComboboxTrigger
|
|
714
788
|
v-else
|
|
715
789
|
:id="selectId"
|
|
@@ -736,7 +810,7 @@ const bodyProps = computed(() => ({
|
|
|
736
810
|
<div v-if="displayMode === 'chip'" class="flex flex-1 flex-wrap items-center gap-1.5">
|
|
737
811
|
<Chip
|
|
738
812
|
v-for="option in visibleOptions"
|
|
739
|
-
:key="option.
|
|
813
|
+
:key="option.key"
|
|
740
814
|
size="sm"
|
|
741
815
|
color="primary"
|
|
742
816
|
variant="soft"
|
|
@@ -745,7 +819,7 @@ const bodyProps = computed(() => ({
|
|
|
745
819
|
:aria-current="option.value === selectedChipValue || void 0"
|
|
746
820
|
:ui="{ root: 'data-[state=active]:ring-2 data-[state=active]:ring-[var(--_selaras-color-fill)]' }"
|
|
747
821
|
>
|
|
748
|
-
<slot v-if="option.raw" name="item" :item="option.raw">
|
|
822
|
+
<slot v-if="option.raw !== void 0" name="item" :item="option.raw">
|
|
749
823
|
{{ option.label }}
|
|
750
824
|
</slot>
|
|
751
825
|
<template v-else>
|
|
@@ -822,12 +896,12 @@ const bodyProps = computed(() => ({
|
|
|
822
896
|
</ComboboxTrigger>
|
|
823
897
|
</ComboboxAnchor>
|
|
824
898
|
|
|
825
|
-
<ComboboxPortal v-if="!
|
|
899
|
+
<ComboboxPortal v-if="!showMobileModal" v-bind="portalProps">
|
|
826
900
|
<ComboboxContent
|
|
827
901
|
position="popper" :side-offset="4"
|
|
828
902
|
:hide-when-empty="creatable && !multiple && !forceSelection"
|
|
829
903
|
:data-selaras-theme="themeBindings['data-selaras-theme']" :data-selaras-mode="themeBindings['data-selaras-mode']" :style="themeBindings.style" :data-selaras-color="colorRoleMarker"
|
|
830
|
-
v-bind="
|
|
904
|
+
v-bind="contentProps"
|
|
831
905
|
@pointerdown.capture="startSuggestionPointer"
|
|
832
906
|
>
|
|
833
907
|
<ComboboxSelectBody v-bind="bodyProps" @update:search-text="searchText = $event">
|
|
@@ -857,10 +931,8 @@ const bodyProps = computed(() => ({
|
|
|
857
931
|
</ComboboxContent>
|
|
858
932
|
</ComboboxPortal>
|
|
859
933
|
<!--
|
|
860
|
-
Modal owns
|
|
861
|
-
|
|
862
|
-
Autocomplete stays in the nonmodal ComboboxContent branch above: its
|
|
863
|
-
editable input is the one focus and dismissal owner for that surface.
|
|
934
|
+
Modal owns focus and dismissal; Listbox supplies selection and the
|
|
935
|
+
Autocomplete editor inside the same accessible dialog surface.
|
|
864
936
|
-->
|
|
865
937
|
<Modal
|
|
866
938
|
v-else :open="open" :title="placeholder || messages.search" :description="messages.searchDescription"
|
|
@@ -869,15 +941,26 @@ const bodyProps = computed(() => ({
|
|
|
869
941
|
>
|
|
870
942
|
<template #content>
|
|
871
943
|
<ListboxRoot
|
|
872
|
-
v-if="!creatable"
|
|
873
944
|
:model-value="rekaSelection" :multiple="multiple" :disabled="disabled"
|
|
874
945
|
:data-selaras-theme="themeBindings['data-selaras-theme']" :data-selaras-mode="themeBindings['data-selaras-mode']" :style="themeBindings.style" :data-selaras-color="colorRoleMarker" v-bind="mobileContentProps"
|
|
946
|
+
@pointerdown.capture="startSuggestionPointer"
|
|
947
|
+
@keydown.capture="onSearchKeydown"
|
|
875
948
|
@update:model-value="onModalSelection"
|
|
876
949
|
>
|
|
877
|
-
<ComboboxSelectBody ref="modalBody" v-bind="bodyProps" listbox @update:search-text="searchText = $event">
|
|
878
|
-
<template v-for="slotName in ['
|
|
950
|
+
<ComboboxSelectBody ref="modalBody" v-bind="bodyProps" :search-input-props="modalSearchInputProps" listbox @update:search-text="searchText = $event">
|
|
951
|
+
<template v-for="slotName in ['filter-icon', 'empty', 'empty-filter', 'footer'].filter((slotName) => $slots[slotName])" #[slotName]>
|
|
879
952
|
<slot :name="slotName" />
|
|
880
953
|
</template>
|
|
954
|
+
<template #header>
|
|
955
|
+
<slot name="header" />
|
|
956
|
+
<div v-if="creatable && multiple && selectedOptions.length" class="flex flex-wrap gap-1.5 px-3 pt-3">
|
|
957
|
+
<template v-if="displayMode === 'chip'">
|
|
958
|
+
<Chip v-for="option in visibleOptions" :key="option.key" :label="option.label" size="sm" removable @remove="removeValue(option.value)" />
|
|
959
|
+
<span v-if="overflowOptions.length" v-bind="chipOverflowProps">{{ messages.moreItems(overflowOptions.length) }}</span>
|
|
960
|
+
</template>
|
|
961
|
+
<span v-else v-bind="valueProps">{{ commaText }}</span>
|
|
962
|
+
</div>
|
|
963
|
+
</template>
|
|
881
964
|
<template v-if="$slots.item" #item="scope">
|
|
882
965
|
<slot name="item" v-bind="scope" />
|
|
883
966
|
</template>
|
|
@@ -886,10 +969,13 @@ const bodyProps = computed(() => ({
|
|
|
886
969
|
</template>
|
|
887
970
|
</ComboboxSelectBody>
|
|
888
971
|
</ListboxRoot>
|
|
889
|
-
<div v-if="
|
|
890
|
-
<
|
|
972
|
+
<div v-if="creatable || multiple" class="flex justify-end p-2">
|
|
973
|
+
<Button v-if="creatable" variant="ghost" color="neutral" @click="finishMobileAutocomplete">
|
|
974
|
+
{{ messages.done }}
|
|
975
|
+
</Button>
|
|
976
|
+
<DialogClose v-else as-child>
|
|
891
977
|
<Button variant="ghost" color="neutral">
|
|
892
|
-
{{
|
|
978
|
+
{{ messages.done }}
|
|
893
979
|
</Button>
|
|
894
980
|
</DialogClose>
|
|
895
981
|
</div>
|
|
@@ -913,7 +999,7 @@ const bodyProps = computed(() => ({
|
|
|
913
999
|
type="hidden"
|
|
914
1000
|
:name="name ?? field?.name"
|
|
915
1001
|
:form="form"
|
|
916
|
-
:value="
|
|
1002
|
+
:value="serializeFormValue(value)"
|
|
917
1003
|
:disabled="disabled"
|
|
918
1004
|
:required="required && !disabled && index === 0"
|
|
919
1005
|
>
|
|
@@ -3,6 +3,7 @@ import type { SelectThemeSlots } from '../theme/select.js';
|
|
|
3
3
|
import type { RoundedArrowConfig } from '../utils/arrow.js';
|
|
4
4
|
import type { ColorRole } from '../utils/color-registry.js';
|
|
5
5
|
import type { OverlayPortal, OverlayPositioning } from '../utils/overlay.js';
|
|
6
|
+
import type { SelectModelValue } from '../utils/select-contracts.js';
|
|
6
7
|
import type { UiProp } from '../utils/ui.js';
|
|
7
8
|
import type { SelectItems, SelectOption } from './combobox-select.js';
|
|
8
9
|
import { selectTheme } from '../theme/select.js';
|
|
@@ -18,9 +19,9 @@ export interface ComboboxSelectBaseProps {
|
|
|
18
19
|
open?: boolean;
|
|
19
20
|
/** Initial uncontrolled open state. */
|
|
20
21
|
defaultOpen?: boolean;
|
|
21
|
-
modelValue?:
|
|
22
|
+
modelValue?: SelectModelValue | SelectModelValue[];
|
|
22
23
|
/** Initial uncontrolled selection and native form reset target. */
|
|
23
|
-
defaultValue?:
|
|
24
|
+
defaultValue?: SelectModelValue | SelectModelValue[];
|
|
24
25
|
multiple?: boolean;
|
|
25
26
|
searchable?: boolean;
|
|
26
27
|
virtualize?: boolean | {
|
|
@@ -54,26 +55,26 @@ export interface ComboboxSelectBaseProps {
|
|
|
54
55
|
}
|
|
55
56
|
export interface ComboboxSelectBaseEmits {
|
|
56
57
|
'update:open': [value: boolean];
|
|
57
|
-
'update:modelValue': [value:
|
|
58
|
+
'update:modelValue': [value: SelectModelValue | SelectModelValue[] | undefined];
|
|
58
59
|
'update:searchTerm': [value: string];
|
|
59
60
|
}
|
|
60
61
|
declare var __VLS_43: {
|
|
61
|
-
item: SelectOption<
|
|
62
|
-
}, __VLS_98: {}, __VLS_105: {}, __VLS_120: {},
|
|
63
|
-
item: SelectOption<
|
|
64
|
-
},
|
|
62
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
63
|
+
}, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_136: {}, __VLS_143: {}, __VLS_150: {}, __VLS_174: {
|
|
64
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
65
|
+
}, __VLS_194: {
|
|
65
66
|
selected: import("./combobox-select.js").ResolvedOption | undefined;
|
|
66
|
-
},
|
|
67
|
-
item: SelectOption<
|
|
68
|
-
},
|
|
69
|
-
group: import("./combobox-select.js").SelectOptionGroup<
|
|
70
|
-
},
|
|
71
|
-
item: SelectOption<
|
|
72
|
-
},
|
|
73
|
-
group: import("./combobox-select.js").SelectOptionGroup<
|
|
67
|
+
}, __VLS_205: {}, __VLS_212: {}, __VLS_219: {}, __VLS_249: {}, __VLS_252: {}, __VLS_255: {}, __VLS_258: {}, __VLS_261: {
|
|
68
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
69
|
+
}, __VLS_264: {
|
|
70
|
+
group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
|
|
71
|
+
}, __VLS_267: {}, __VLS_305: string, __VLS_306: {}, __VLS_309: {}, __VLS_319: {
|
|
72
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
73
|
+
}, __VLS_322: {
|
|
74
|
+
group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
|
|
74
75
|
};
|
|
75
76
|
type __VLS_Slots = {} & {
|
|
76
|
-
[K in NonNullable<typeof
|
|
77
|
+
[K in NonNullable<typeof __VLS_305>]?: (props: typeof __VLS_306) => any;
|
|
77
78
|
} & {
|
|
78
79
|
item?: (props: typeof __VLS_43) => any;
|
|
79
80
|
} & {
|
|
@@ -83,40 +84,48 @@ type __VLS_Slots = {} & {
|
|
|
83
84
|
} & {
|
|
84
85
|
'dropdown-icon'?: (props: typeof __VLS_120) => any;
|
|
85
86
|
} & {
|
|
86
|
-
|
|
87
|
+
'clear-icon'?: (props: typeof __VLS_136) => any;
|
|
87
88
|
} & {
|
|
88
|
-
|
|
89
|
+
'loading-icon'?: (props: typeof __VLS_143) => any;
|
|
89
90
|
} & {
|
|
90
|
-
'
|
|
91
|
+
'dropdown-icon'?: (props: typeof __VLS_150) => any;
|
|
91
92
|
} & {
|
|
92
|
-
|
|
93
|
+
item?: (props: typeof __VLS_174) => any;
|
|
93
94
|
} & {
|
|
94
|
-
|
|
95
|
+
value?: (props: typeof __VLS_194) => any;
|
|
95
96
|
} & {
|
|
96
|
-
|
|
97
|
+
'clear-icon'?: (props: typeof __VLS_205) => any;
|
|
97
98
|
} & {
|
|
98
|
-
'
|
|
99
|
+
'loading-icon'?: (props: typeof __VLS_212) => any;
|
|
99
100
|
} & {
|
|
100
|
-
|
|
101
|
+
'dropdown-icon'?: (props: typeof __VLS_219) => any;
|
|
101
102
|
} & {
|
|
102
|
-
|
|
103
|
+
header?: (props: typeof __VLS_249) => any;
|
|
103
104
|
} & {
|
|
104
|
-
|
|
105
|
+
'filter-icon'?: (props: typeof __VLS_252) => any;
|
|
105
106
|
} & {
|
|
106
|
-
|
|
107
|
+
empty?: (props: typeof __VLS_255) => any;
|
|
107
108
|
} & {
|
|
108
|
-
|
|
109
|
+
'empty-filter'?: (props: typeof __VLS_258) => any;
|
|
109
110
|
} & {
|
|
110
|
-
item?: (props: typeof
|
|
111
|
+
item?: (props: typeof __VLS_261) => any;
|
|
111
112
|
} & {
|
|
112
|
-
group?: (props: typeof
|
|
113
|
+
group?: (props: typeof __VLS_264) => any;
|
|
114
|
+
} & {
|
|
115
|
+
footer?: (props: typeof __VLS_267) => any;
|
|
116
|
+
} & {
|
|
117
|
+
header?: (props: typeof __VLS_309) => any;
|
|
118
|
+
} & {
|
|
119
|
+
item?: (props: typeof __VLS_319) => any;
|
|
120
|
+
} & {
|
|
121
|
+
group?: (props: typeof __VLS_322) => any;
|
|
113
122
|
};
|
|
114
123
|
declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBaseProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
115
|
-
"update:modelValue": (value:
|
|
124
|
+
"update:modelValue": (value: SelectModelValue | SelectModelValue[] | undefined) => any;
|
|
116
125
|
"update:open": (value: boolean) => any;
|
|
117
126
|
"update:searchTerm": (value: string) => any;
|
|
118
127
|
}, string, import("vue").PublicProps, Readonly<ComboboxSelectBaseProps> & Readonly<{
|
|
119
|
-
"onUpdate:modelValue"?: ((value:
|
|
128
|
+
"onUpdate:modelValue"?: ((value: SelectModelValue | SelectModelValue[] | undefined) => any) | undefined;
|
|
120
129
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
121
130
|
"onUpdate:searchTerm"?: ((value: string) => any) | undefined;
|
|
122
131
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SelectModelValue } from '../utils/select-contracts.js';
|
|
1
2
|
import type { ResolvedItemOption, SelectItems, SelectOption } from './combobox-select.js';
|
|
2
3
|
export interface ComboboxSelectBodyProps {
|
|
3
4
|
listbox?: boolean;
|
|
@@ -5,7 +6,7 @@ export interface ComboboxSelectBodyProps {
|
|
|
5
6
|
creatable?: boolean;
|
|
6
7
|
multiple?: boolean;
|
|
7
8
|
forceSelection?: boolean;
|
|
8
|
-
selectedValues?:
|
|
9
|
+
selectedValues?: SelectModelValue[];
|
|
9
10
|
searchText: string;
|
|
10
11
|
displayValue: (value: unknown) => string;
|
|
11
12
|
items: SelectItems;
|
|
@@ -15,9 +16,9 @@ export interface ComboboxSelectBodyProps {
|
|
|
15
16
|
overscan: number;
|
|
16
17
|
} | null;
|
|
17
18
|
virtualizedOptions: ResolvedItemOption[];
|
|
18
|
-
toOption: (entry: SelectOption) => ResolvedItemOption;
|
|
19
|
+
toOption: (entry: SelectOption | SelectModelValue) => ResolvedItemOption;
|
|
19
20
|
groupOptions: (group: {
|
|
20
|
-
items: readonly SelectOption[];
|
|
21
|
+
items: readonly (SelectOption | SelectModelValue)[];
|
|
21
22
|
}) => ResolvedItemOption[];
|
|
22
23
|
onSearchKeydown: (event: KeyboardEvent) => void;
|
|
23
24
|
onSearchBlur: () => void;
|
|
@@ -34,13 +35,13 @@ export interface ComboboxSelectBodyEmits {
|
|
|
34
35
|
}
|
|
35
36
|
declare function focusSearch(): boolean;
|
|
36
37
|
declare var __VLS_1: {}, __VLS_3: {}, __VLS_21: {}, __VLS_29: {}, __VLS_31: {}, __VLS_39: {}, __VLS_61: {
|
|
37
|
-
item: SelectOption<
|
|
38
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
38
39
|
}, __VLS_92: {
|
|
39
|
-
group: import("./combobox-select.js").SelectOptionGroup<
|
|
40
|
+
group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
|
|
40
41
|
}, __VLS_102: {
|
|
41
|
-
item: SelectOption<
|
|
42
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
42
43
|
}, __VLS_123: {
|
|
43
|
-
item: SelectOption<
|
|
44
|
+
item: SelectModelValue | SelectOption<SelectModelValue>;
|
|
44
45
|
}, __VLS_136: {};
|
|
45
46
|
type __VLS_Slots = {} & {
|
|
46
47
|
header?: (props: typeof __VLS_1) => any;
|
|
@@ -67,6 +68,7 @@ type __VLS_Slots = {} & {
|
|
|
67
68
|
};
|
|
68
69
|
declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBodyProps, {
|
|
69
70
|
focusSearch: typeof focusSearch;
|
|
71
|
+
searchElement: import("vue").ComputedRef<HTMLInputElement | undefined>;
|
|
70
72
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
71
73
|
"update:searchText": (value: string) => any;
|
|
72
74
|
}, string, import("vue").PublicProps, Readonly<ComboboxSelectBodyProps> & Readonly<{
|
|
@@ -71,7 +71,7 @@ const createText = computed(() => {
|
|
|
71
71
|
});
|
|
72
72
|
const virtualOptions = computed(() => {
|
|
73
73
|
const options = props.listbox ? visibleOptions.value : props.virtualizedOptions;
|
|
74
|
-
return createText.value ? [{ value: createText.value, label: messages.value.createOption(createText.value), disabled: false, raw: void 0 }, ...options] : options;
|
|
74
|
+
return createText.value ? [{ value: createText.value, key: `create:${createText.value}`, label: messages.value.createOption(createText.value), disabled: false, raw: void 0 }, ...options] : options;
|
|
75
75
|
});
|
|
76
76
|
function onItemSelect(event, value) {
|
|
77
77
|
if (!props.creatable || !props.multiple || !props.selectedValues?.includes(value))
|
|
@@ -90,13 +90,13 @@ function focusSearch() {
|
|
|
90
90
|
element.focus();
|
|
91
91
|
return true;
|
|
92
92
|
}
|
|
93
|
-
defineExpose({ focusSearch });
|
|
93
|
+
defineExpose({ focusSearch, searchElement: computed(() => searchInput.value?.$el) });
|
|
94
94
|
</script>
|
|
95
95
|
|
|
96
96
|
<template>
|
|
97
97
|
<slot name="header" />
|
|
98
98
|
|
|
99
|
-
<div v-if="searchable && !creatable" v-bind="searchWrapperProps">
|
|
99
|
+
<div v-if="searchable && (!creatable || listbox)" v-bind="searchWrapperProps">
|
|
100
100
|
<slot name="filter-icon">
|
|
101
101
|
<Icon :name="icons.search" class="size-4 text-[var(--selaras-resolved-text-muted)]" />
|
|
102
102
|
</slot>
|
|
@@ -148,12 +148,12 @@ defineExpose({ focusSearch });
|
|
|
148
148
|
:key="String(virtualItem.key)"
|
|
149
149
|
:value="option.value"
|
|
150
150
|
:disabled="option.disabled"
|
|
151
|
-
:data-selaras-create-option="
|
|
151
|
+
:data-selaras-create-option="option.raw === void 0 || void 0"
|
|
152
152
|
:style="{ position: 'absolute', top: 0, left: 0, right: 0, transform: `translateY(${virtualItem.start}px)`, height: `${virtualItem.size}px` }"
|
|
153
153
|
v-bind="itemProps"
|
|
154
154
|
@select="onItemSelect($event, option.value)"
|
|
155
155
|
>
|
|
156
|
-
<template v-if="
|
|
156
|
+
<template v-if="option.raw === void 0">
|
|
157
157
|
{{ option.label }}
|
|
158
158
|
</template>
|
|
159
159
|
<template v-else>
|
|
@@ -182,7 +182,7 @@ defineExpose({ focusSearch });
|
|
|
182
182
|
<component
|
|
183
183
|
:is="primitives.item"
|
|
184
184
|
v-for="option in groupOptions(entry).filter(matches)"
|
|
185
|
-
:key="option.
|
|
185
|
+
:key="option.key"
|
|
186
186
|
:value="option.value"
|
|
187
187
|
:disabled="option.disabled"
|
|
188
188
|
v-bind="itemProps"
|