@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.
Files changed (63) hide show
  1. package/README.md +10 -1
  2. package/agent/catalog.mjs +73 -0
  3. package/agent/mcp.mjs +15 -0
  4. package/agent/server.mjs +58 -0
  5. package/dist/agent/catalog.json +1 -0
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +7 -5
  8. package/dist/runtime/components/AlertDialog.d.vue.ts +5 -3
  9. package/dist/runtime/components/AlertDialog.vue +17 -9
  10. package/dist/runtime/components/AlertDialog.vue.d.ts +5 -3
  11. package/dist/runtime/components/App.vue +2 -0
  12. package/dist/runtime/components/Autocomplete.d.vue.ts +2 -2
  13. package/dist/runtime/components/Autocomplete.vue +1 -1
  14. package/dist/runtime/components/Autocomplete.vue.d.ts +2 -2
  15. package/dist/runtime/components/Button.d.vue.ts +7 -5
  16. package/dist/runtime/components/Button.vue +14 -3
  17. package/dist/runtime/components/Button.vue.d.ts +7 -5
  18. package/dist/runtime/components/ColorModeSwitch.d.vue.ts +18 -0
  19. package/dist/runtime/components/ColorModeSwitch.vue +59 -0
  20. package/dist/runtime/components/ColorModeSwitch.vue.d.ts +18 -0
  21. package/dist/runtime/components/ConfirmRenderer.d.vue.ts +3 -0
  22. package/dist/runtime/components/ConfirmRenderer.vue +39 -0
  23. package/dist/runtime/components/ConfirmRenderer.vue.d.ts +3 -0
  24. package/dist/runtime/components/ContextMenu.vue +4 -2
  25. package/dist/runtime/components/Drawer.vue +3 -2
  26. package/dist/runtime/components/Dropdown.vue +4 -2
  27. package/dist/runtime/components/Modal.vue +3 -2
  28. package/dist/runtime/components/Popover.vue +4 -2
  29. package/dist/runtime/components/Select.d.vue.ts +8 -4
  30. package/dist/runtime/components/Select.vue +1 -1
  31. package/dist/runtime/components/Select.vue.d.ts +8 -4
  32. package/dist/runtime/components/Slideover.vue +3 -2
  33. package/dist/runtime/components/Tooltip.vue +4 -2
  34. package/dist/runtime/composables/use-confirm.d.ts +14 -0
  35. package/dist/runtime/composables/use-confirm.js +28 -0
  36. package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +41 -32
  37. package/dist/runtime/internal/ComboboxSelectBase.vue +154 -68
  38. package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +41 -32
  39. package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +9 -7
  40. package/dist/runtime/internal/ComboboxSelectBody.vue +6 -6
  41. package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +9 -7
  42. package/dist/runtime/internal/combobox-select.d.ts +19 -18
  43. package/dist/runtime/internal/combobox-select.js +36 -14
  44. package/dist/runtime/internal/programmatic-services.d.ts +11 -0
  45. package/dist/runtime/internal/programmatic-services.js +1 -0
  46. package/dist/runtime/theme/badge.d.ts +9 -0
  47. package/dist/runtime/theme/badge.js +2 -1
  48. package/dist/runtime/theme/button.d.ts +18 -0
  49. package/dist/runtime/theme/button.js +4 -1
  50. package/dist/runtime/theme/select.js +1 -4
  51. package/dist/runtime/theme/toast.js +1 -1
  52. package/dist/runtime/theme-config.d.ts +1 -1
  53. package/dist/runtime/types/index.d.ts +4 -1
  54. package/dist/runtime/utils/autocomplete-contracts.d.ts +4 -4
  55. package/dist/runtime/utils/file-icons.js +1 -0
  56. package/dist/runtime/utils/messages.d.ts +1 -0
  57. package/dist/runtime/utils/messages.js +1 -0
  58. package/dist/runtime/utils/select-contracts.d.ts +18 -10
  59. package/dist/runtime/utils/ui.d.ts +11 -0
  60. package/dist/runtime/utils/ui.js +54 -1
  61. package/package.json +19 -9
  62. package/skills/selaras/SKILL.md +40 -0
  63. 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 !== editableInput.value)
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 && !editableInput.value.value && selectedValues.value.length) {
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 = editableInput.value?.getAttribute("aria-activedescendant");
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 = editableInput.value?.getAttribute("aria-activedescendant");
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 === editableInput.value;
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 = editableInput.value;
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
- editableInput.value?.focus();
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(() => resolveSlot(ui.value.mobileContent, props.ui?.mobileContent));
445
- const mobilePanelProps = computed(() => mergeProps(
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 && !props.creatable && props.resetSearchTermOnBlur)
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 showMobileSelectModal = computed(() => mobilePresentation.value && !props.creatable);
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 modalBody = ref();
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
- ...!props.creatable ? {
514
- "id": modalId,
515
- "aria-modal": true,
516
- "onOpenAutoFocus": (event) => {
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
- if (!props.disabled && selectTrigger.value?.$el?.isConnected)
523
- selectTrigger.value.$el.focus();
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.value"
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.value"
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="!showMobileSelectModal" v-bind="portalProps">
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="showMobileAutocompletePanel ? mergeProps(contentProps, mobilePanelProps) : contentProps"
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 Select's focus/dismissal; Listbox only supplies selection.
861
- The content slot retains Modal's registered title/description.
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 ['header', 'filter-icon', 'empty', 'empty-filter', 'footer'].filter((slotName) => $slots[slotName])" #[slotName]>
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="!creatable" class="flex justify-end p-2">
890
- <DialogClose as-child>
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
- {{ multiple ? messages.done : messages.cancel }}
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="String(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?: string | number | (string | number)[];
22
+ modelValue?: SelectModelValue | SelectModelValue[];
22
23
  /** Initial uncontrolled selection and native form reset target. */
23
- defaultValue?: string | number | (string | number)[];
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: string | number | (string | number)[] | undefined];
58
+ 'update:modelValue': [value: SelectModelValue | SelectModelValue[] | undefined];
58
59
  'update:searchTerm': [value: string];
59
60
  }
60
61
  declare var __VLS_43: {
61
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
62
- }, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_144: {
63
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
64
- }, __VLS_164: {
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
- }, __VLS_175: {}, __VLS_182: {}, __VLS_189: {}, __VLS_219: {}, __VLS_222: {}, __VLS_225: {}, __VLS_228: {}, __VLS_231: {
67
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
68
- }, __VLS_234: {
69
- group: import("./combobox-select.js").SelectOptionGroup<import("../utils/select-contracts.js").SelectValue>;
70
- }, __VLS_237: {}, __VLS_273: string, __VLS_274: {}, __VLS_277: {
71
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
72
- }, __VLS_280: {
73
- group: import("./combobox-select.js").SelectOptionGroup<import("../utils/select-contracts.js").SelectValue>;
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 __VLS_273>]?: (props: typeof __VLS_274) => any;
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
- item?: (props: typeof __VLS_144) => any;
87
+ 'clear-icon'?: (props: typeof __VLS_136) => any;
87
88
  } & {
88
- value?: (props: typeof __VLS_164) => any;
89
+ 'loading-icon'?: (props: typeof __VLS_143) => any;
89
90
  } & {
90
- 'clear-icon'?: (props: typeof __VLS_175) => any;
91
+ 'dropdown-icon'?: (props: typeof __VLS_150) => any;
91
92
  } & {
92
- 'loading-icon'?: (props: typeof __VLS_182) => any;
93
+ item?: (props: typeof __VLS_174) => any;
93
94
  } & {
94
- 'dropdown-icon'?: (props: typeof __VLS_189) => any;
95
+ value?: (props: typeof __VLS_194) => any;
95
96
  } & {
96
- header?: (props: typeof __VLS_219) => any;
97
+ 'clear-icon'?: (props: typeof __VLS_205) => any;
97
98
  } & {
98
- 'filter-icon'?: (props: typeof __VLS_222) => any;
99
+ 'loading-icon'?: (props: typeof __VLS_212) => any;
99
100
  } & {
100
- empty?: (props: typeof __VLS_225) => any;
101
+ 'dropdown-icon'?: (props: typeof __VLS_219) => any;
101
102
  } & {
102
- 'empty-filter'?: (props: typeof __VLS_228) => any;
103
+ header?: (props: typeof __VLS_249) => any;
103
104
  } & {
104
- item?: (props: typeof __VLS_231) => any;
105
+ 'filter-icon'?: (props: typeof __VLS_252) => any;
105
106
  } & {
106
- group?: (props: typeof __VLS_234) => any;
107
+ empty?: (props: typeof __VLS_255) => any;
107
108
  } & {
108
- footer?: (props: typeof __VLS_237) => any;
109
+ 'empty-filter'?: (props: typeof __VLS_258) => any;
109
110
  } & {
110
- item?: (props: typeof __VLS_277) => any;
111
+ item?: (props: typeof __VLS_261) => any;
111
112
  } & {
112
- group?: (props: typeof __VLS_280) => any;
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: string | number | (string | number)[] | undefined) => any;
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: string | number | (string | number)[] | undefined) => any) | undefined;
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?: (string | number)[];
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<import("../utils/select-contracts.js").SelectValue>;
38
+ item: SelectModelValue | SelectOption<SelectModelValue>;
38
39
  }, __VLS_92: {
39
- group: import("./combobox-select.js").SelectOptionGroup<import("../utils/select-contracts.js").SelectValue>;
40
+ group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
40
41
  }, __VLS_102: {
41
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
42
+ item: SelectModelValue | SelectOption<SelectModelValue>;
42
43
  }, __VLS_123: {
43
- item: SelectOption<import("../utils/select-contracts.js").SelectValue>;
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="!option.raw || void 0"
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="!option.raw">
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.value"
185
+ :key="option.key"
186
186
  :value="option.value"
187
187
  :disabled="option.disabled"
188
188
  v-bind="itemProps"