@sewadah/selaras 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "selaras",
3
3
  "configKey": "selaras",
4
- "version": "0.3.2",
4
+ "version": "0.5.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
@@ -12,8 +12,9 @@ const isMobile = computed(() => dashboard?.isMobile.value ?? false);
12
12
  const theme = useComponentTheme("dashboardResizeHandle", dashboardResizeHandleTheme);
13
13
  const ui = computed(() => theme.value());
14
14
  const rootUi = computed(() => resolveSlot(ui.value.root, props.ui?.root));
15
+ const lineUi = computed(() => resolveSlot(ui.value.line, props.ui?.line));
15
16
  </script>
16
17
 
17
18
  <template>
18
- <SplitterResizeHandle v-if="!isMobile" direction="horizontal" :ui="{ root: rootUi, line: props.ui?.line }" />
19
+ <SplitterResizeHandle v-if="!isMobile" direction="horizontal" :ui="{ root: rootUi, line: lineUi }" />
19
20
  </template>
@@ -60,21 +60,21 @@ export interface ComboboxSelectBaseEmits {
60
60
  }
61
61
  declare var __VLS_43: {
62
62
  item: SelectModelValue | SelectOption<SelectModelValue>;
63
- }, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_144: {
63
+ }, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_136: {}, __VLS_143: {}, __VLS_150: {}, __VLS_174: {
64
64
  item: SelectModelValue | SelectOption<SelectModelValue>;
65
- }, __VLS_164: {
65
+ }, __VLS_194: {
66
66
  selected: import("./combobox-select.js").ResolvedOption | undefined;
67
- }, __VLS_175: {}, __VLS_182: {}, __VLS_189: {}, __VLS_219: {}, __VLS_222: {}, __VLS_225: {}, __VLS_228: {}, __VLS_231: {
67
+ }, __VLS_205: {}, __VLS_212: {}, __VLS_219: {}, __VLS_249: {}, __VLS_252: {}, __VLS_255: {}, __VLS_258: {}, __VLS_261: {
68
68
  item: SelectModelValue | SelectOption<SelectModelValue>;
69
- }, __VLS_234: {
69
+ }, __VLS_264: {
70
70
  group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
71
- }, __VLS_237: {}, __VLS_273: string, __VLS_274: {}, __VLS_277: {
71
+ }, __VLS_267: {}, __VLS_305: string, __VLS_306: {}, __VLS_309: {}, __VLS_319: {
72
72
  item: SelectModelValue | SelectOption<SelectModelValue>;
73
- }, __VLS_280: {
73
+ }, __VLS_322: {
74
74
  group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
75
75
  };
76
76
  type __VLS_Slots = {} & {
77
- [K in NonNullable<typeof __VLS_273>]?: (props: typeof __VLS_274) => any;
77
+ [K in NonNullable<typeof __VLS_305>]?: (props: typeof __VLS_306) => any;
78
78
  } & {
79
79
  item?: (props: typeof __VLS_43) => any;
80
80
  } & {
@@ -84,33 +84,41 @@ type __VLS_Slots = {} & {
84
84
  } & {
85
85
  'dropdown-icon'?: (props: typeof __VLS_120) => any;
86
86
  } & {
87
- item?: (props: typeof __VLS_144) => any;
87
+ 'clear-icon'?: (props: typeof __VLS_136) => any;
88
88
  } & {
89
- value?: (props: typeof __VLS_164) => any;
89
+ 'loading-icon'?: (props: typeof __VLS_143) => any;
90
90
  } & {
91
- 'clear-icon'?: (props: typeof __VLS_175) => any;
91
+ 'dropdown-icon'?: (props: typeof __VLS_150) => any;
92
92
  } & {
93
- 'loading-icon'?: (props: typeof __VLS_182) => any;
93
+ item?: (props: typeof __VLS_174) => any;
94
94
  } & {
95
- 'dropdown-icon'?: (props: typeof __VLS_189) => any;
95
+ value?: (props: typeof __VLS_194) => any;
96
96
  } & {
97
- header?: (props: typeof __VLS_219) => any;
97
+ 'clear-icon'?: (props: typeof __VLS_205) => any;
98
98
  } & {
99
- 'filter-icon'?: (props: typeof __VLS_222) => any;
99
+ 'loading-icon'?: (props: typeof __VLS_212) => any;
100
100
  } & {
101
- empty?: (props: typeof __VLS_225) => any;
101
+ 'dropdown-icon'?: (props: typeof __VLS_219) => any;
102
102
  } & {
103
- 'empty-filter'?: (props: typeof __VLS_228) => any;
103
+ header?: (props: typeof __VLS_249) => any;
104
104
  } & {
105
- item?: (props: typeof __VLS_231) => any;
105
+ 'filter-icon'?: (props: typeof __VLS_252) => any;
106
106
  } & {
107
- group?: (props: typeof __VLS_234) => any;
107
+ empty?: (props: typeof __VLS_255) => any;
108
108
  } & {
109
- footer?: (props: typeof __VLS_237) => any;
109
+ 'empty-filter'?: (props: typeof __VLS_258) => any;
110
110
  } & {
111
- item?: (props: typeof __VLS_277) => any;
111
+ item?: (props: typeof __VLS_261) => any;
112
112
  } & {
113
- 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;
114
122
  };
115
123
  declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBaseProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
116
124
  "update:modelValue": (value: SelectModelValue | SelectModelValue[] | undefined) => any;
@@ -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,34 +482,48 @@ 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
+ }
510
527
  function serializeFormValue(value) {
511
528
  if (typeof value !== "object")
512
529
  return String(value);
@@ -518,22 +535,26 @@ function serializeFormValue(value) {
518
535
  }
519
536
  const adaptiveUi = computed(() => ({
520
537
  content: {
521
- class: "rounded-[var(--selaras-resolved-radius-md)]",
522
- ...!props.creatable ? {
523
- "id": modalId,
524
- "aria-modal": true,
525
- "onOpenAutoFocus": (event) => {
526
- if (props.searchable && modalBody.value?.focusSearch())
527
- event.preventDefault();
528
- },
529
- "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())
530
543
  event.preventDefault();
531
- if (!props.disabled && selectTrigger.value?.$el?.isConnected)
532
- selectTrigger.value.$el.focus();
533
- }
534
- } : {}
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
+ }
535
555
  }
536
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 } : {}));
537
558
  const bodyProps = computed(() => ({
538
559
  searchable: props.searchable,
539
560
  creatable: props.creatable,
@@ -563,7 +584,7 @@ const bodyProps = computed(() => ({
563
584
  <template>
564
585
  <ComboboxRoot
565
586
  :key="comboboxRootKey"
566
- :open="open"
587
+ :open="useMobileAutocompleteTrigger ? false : open"
567
588
  :model-value="rekaSelection"
568
589
  :multiple="multiple"
569
590
  :disabled="disabled"
@@ -583,7 +604,7 @@ const bodyProps = computed(() => ({
583
604
  typing is always available without opening anything first.
584
605
  -->
585
606
  <div
586
- v-if="creatable"
607
+ v-if="creatable && !useMobileAutocompleteTrigger"
587
608
  data-ui-group-item
588
609
  :data-selaras-field-filled="selectedValues.length ? '' : void 0"
589
610
  :data-selaras-field-active="open ? '' : void 0"
@@ -719,6 +740,50 @@ const bodyProps = computed(() => ({
719
740
  </ComboboxTrigger>
720
741
  </div>
721
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
+
722
787
  <ComboboxTrigger
723
788
  v-else
724
789
  :id="selectId"
@@ -831,12 +896,12 @@ const bodyProps = computed(() => ({
831
896
  </ComboboxTrigger>
832
897
  </ComboboxAnchor>
833
898
 
834
- <ComboboxPortal v-if="!showMobileSelectModal" v-bind="portalProps">
899
+ <ComboboxPortal v-if="!showMobileModal" v-bind="portalProps">
835
900
  <ComboboxContent
836
901
  position="popper" :side-offset="4"
837
902
  :hide-when-empty="creatable && !multiple && !forceSelection"
838
903
  :data-selaras-theme="themeBindings['data-selaras-theme']" :data-selaras-mode="themeBindings['data-selaras-mode']" :style="themeBindings.style" :data-selaras-color="colorRoleMarker"
839
- v-bind="showMobileAutocompletePanel ? mergeProps(contentProps, mobilePanelProps) : contentProps"
904
+ v-bind="contentProps"
840
905
  @pointerdown.capture="startSuggestionPointer"
841
906
  >
842
907
  <ComboboxSelectBody v-bind="bodyProps" @update:search-text="searchText = $event">
@@ -866,10 +931,8 @@ const bodyProps = computed(() => ({
866
931
  </ComboboxContent>
867
932
  </ComboboxPortal>
868
933
  <!--
869
- Modal owns Select's focus/dismissal; Listbox only supplies selection.
870
- The content slot retains Modal's registered title/description.
871
- Autocomplete stays in the nonmodal ComboboxContent branch above: its
872
- 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.
873
936
  -->
874
937
  <Modal
875
938
  v-else :open="open" :title="placeholder || messages.search" :description="messages.searchDescription"
@@ -878,15 +941,26 @@ const bodyProps = computed(() => ({
878
941
  >
879
942
  <template #content>
880
943
  <ListboxRoot
881
- v-if="!creatable"
882
944
  :model-value="rekaSelection" :multiple="multiple" :disabled="disabled"
883
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"
884
948
  @update:model-value="onModalSelection"
885
949
  >
886
- <ComboboxSelectBody ref="modalBody" v-bind="bodyProps" listbox @update:search-text="searchText = $event">
887
- <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]>
888
952
  <slot :name="slotName" />
889
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>
890
964
  <template v-if="$slots.item" #item="scope">
891
965
  <slot name="item" v-bind="scope" />
892
966
  </template>
@@ -895,10 +969,13 @@ const bodyProps = computed(() => ({
895
969
  </template>
896
970
  </ComboboxSelectBody>
897
971
  </ListboxRoot>
898
- <div v-if="!creatable" class="flex justify-end p-2">
899
- <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>
900
977
  <Button variant="ghost" color="neutral">
901
- {{ multiple ? messages.done : messages.cancel }}
978
+ {{ messages.done }}
902
979
  </Button>
903
980
  </DialogClose>
904
981
  </div>
@@ -60,21 +60,21 @@ export interface ComboboxSelectBaseEmits {
60
60
  }
61
61
  declare var __VLS_43: {
62
62
  item: SelectModelValue | SelectOption<SelectModelValue>;
63
- }, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_144: {
63
+ }, __VLS_98: {}, __VLS_105: {}, __VLS_120: {}, __VLS_136: {}, __VLS_143: {}, __VLS_150: {}, __VLS_174: {
64
64
  item: SelectModelValue | SelectOption<SelectModelValue>;
65
- }, __VLS_164: {
65
+ }, __VLS_194: {
66
66
  selected: import("./combobox-select.js").ResolvedOption | undefined;
67
- }, __VLS_175: {}, __VLS_182: {}, __VLS_189: {}, __VLS_219: {}, __VLS_222: {}, __VLS_225: {}, __VLS_228: {}, __VLS_231: {
67
+ }, __VLS_205: {}, __VLS_212: {}, __VLS_219: {}, __VLS_249: {}, __VLS_252: {}, __VLS_255: {}, __VLS_258: {}, __VLS_261: {
68
68
  item: SelectModelValue | SelectOption<SelectModelValue>;
69
- }, __VLS_234: {
69
+ }, __VLS_264: {
70
70
  group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
71
- }, __VLS_237: {}, __VLS_273: string, __VLS_274: {}, __VLS_277: {
71
+ }, __VLS_267: {}, __VLS_305: string, __VLS_306: {}, __VLS_309: {}, __VLS_319: {
72
72
  item: SelectModelValue | SelectOption<SelectModelValue>;
73
- }, __VLS_280: {
73
+ }, __VLS_322: {
74
74
  group: import("./combobox-select.js").SelectOptionGroup<SelectModelValue>;
75
75
  };
76
76
  type __VLS_Slots = {} & {
77
- [K in NonNullable<typeof __VLS_273>]?: (props: typeof __VLS_274) => any;
77
+ [K in NonNullable<typeof __VLS_305>]?: (props: typeof __VLS_306) => any;
78
78
  } & {
79
79
  item?: (props: typeof __VLS_43) => any;
80
80
  } & {
@@ -84,33 +84,41 @@ type __VLS_Slots = {} & {
84
84
  } & {
85
85
  'dropdown-icon'?: (props: typeof __VLS_120) => any;
86
86
  } & {
87
- item?: (props: typeof __VLS_144) => any;
87
+ 'clear-icon'?: (props: typeof __VLS_136) => any;
88
88
  } & {
89
- value?: (props: typeof __VLS_164) => any;
89
+ 'loading-icon'?: (props: typeof __VLS_143) => any;
90
90
  } & {
91
- 'clear-icon'?: (props: typeof __VLS_175) => any;
91
+ 'dropdown-icon'?: (props: typeof __VLS_150) => any;
92
92
  } & {
93
- 'loading-icon'?: (props: typeof __VLS_182) => any;
93
+ item?: (props: typeof __VLS_174) => any;
94
94
  } & {
95
- 'dropdown-icon'?: (props: typeof __VLS_189) => any;
95
+ value?: (props: typeof __VLS_194) => any;
96
96
  } & {
97
- header?: (props: typeof __VLS_219) => any;
97
+ 'clear-icon'?: (props: typeof __VLS_205) => any;
98
98
  } & {
99
- 'filter-icon'?: (props: typeof __VLS_222) => any;
99
+ 'loading-icon'?: (props: typeof __VLS_212) => any;
100
100
  } & {
101
- empty?: (props: typeof __VLS_225) => any;
101
+ 'dropdown-icon'?: (props: typeof __VLS_219) => any;
102
102
  } & {
103
- 'empty-filter'?: (props: typeof __VLS_228) => any;
103
+ header?: (props: typeof __VLS_249) => any;
104
104
  } & {
105
- item?: (props: typeof __VLS_231) => any;
105
+ 'filter-icon'?: (props: typeof __VLS_252) => any;
106
106
  } & {
107
- group?: (props: typeof __VLS_234) => any;
107
+ empty?: (props: typeof __VLS_255) => any;
108
108
  } & {
109
- footer?: (props: typeof __VLS_237) => any;
109
+ 'empty-filter'?: (props: typeof __VLS_258) => any;
110
110
  } & {
111
- item?: (props: typeof __VLS_277) => any;
111
+ item?: (props: typeof __VLS_261) => any;
112
112
  } & {
113
- 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;
114
122
  };
115
123
  declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBaseProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
116
124
  "update:modelValue": (value: SelectModelValue | SelectModelValue[] | undefined) => any;
@@ -68,6 +68,7 @@ type __VLS_Slots = {} & {
68
68
  };
69
69
  declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBodyProps, {
70
70
  focusSearch: typeof focusSearch;
71
+ searchElement: import("vue").ComputedRef<HTMLInputElement | undefined>;
71
72
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
72
73
  "update:searchText": (value: string) => any;
73
74
  }, string, import("vue").PublicProps, Readonly<ComboboxSelectBodyProps> & Readonly<{
@@ -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>
@@ -68,6 +68,7 @@ type __VLS_Slots = {} & {
68
68
  };
69
69
  declare const __VLS_base: import("vue").DefineComponent<ComboboxSelectBodyProps, {
70
70
  focusSearch: typeof focusSearch;
71
+ searchElement: import("vue").ComputedRef<HTMLInputElement | undefined>;
71
72
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
72
73
  "update:searchText": (value: string) => any;
73
74
  }, string, import("vue").PublicProps, Readonly<ComboboxSelectBodyProps> & Readonly<{
@@ -2,25 +2,31 @@ export declare const dashboardResizeHandleTheme: import("tailwind-variants").TVR
2
2
  [key: string]: {
3
3
  [key: string]: import("tailwind-variants").ClassValue | {
4
4
  root?: import("tailwind-variants").ClassValue;
5
+ line?: import("tailwind-variants").ClassValue;
5
6
  };
6
7
  };
7
8
  } | {
8
9
  [x: string]: {
9
10
  [x: string]: import("tailwind-variants").ClassValue | {
10
11
  root?: import("tailwind-variants").ClassValue;
12
+ line?: import("tailwind-variants").ClassValue;
11
13
  };
12
14
  };
13
15
  } | {}, {
14
16
  root: string;
17
+ line: string;
15
18
  }, undefined, {
16
19
  [key: string]: {
17
20
  [key: string]: import("tailwind-variants").ClassValue | {
18
21
  root?: import("tailwind-variants").ClassValue;
22
+ line?: import("tailwind-variants").ClassValue;
19
23
  };
20
24
  };
21
25
  } | {}, {
22
26
  root: string;
27
+ line: string;
23
28
  }, import("tailwind-variants").TVReturnTypeLike<unknown, {
24
29
  root: string;
30
+ line: string;
25
31
  }>>;
26
32
  export type DashboardResizeHandleThemeSlots = keyof (typeof dashboardResizeHandleTheme)['slots'];
@@ -1,17 +1,9 @@
1
1
  import { tv } from "tailwind-variants";
2
2
  export const dashboardResizeHandleTheme = tv({
3
3
  slots: {
4
- // SplitterResizeHandle's own hit area is deliberately wider than its
5
- // visible line (a comfortable grab target, invisible padding on
6
- // either side) - harmless when both adjacent panels share one
7
- // background, but once DashboardSidebar's own root reads as
8
- // --selaras-resolved-surface-elevated (a step apart from DashboardPanel's own plain
9
- // --selaras-resolved-surface-default), that "invisible" padding stops being invisible: it reads
10
- // as a sliver of the wrong background bleeding through right at the
11
- // sidebar's own edge. A background split exactly down the middle -
12
- // matching each side's real background - makes the hit area blend
13
- // into whichever panel it happens to be sitting over instead of
14
- // showing through as its own separate strip.
15
- root: "bg-[linear-gradient(to_right,var(--selaras-resolved-surface-elevated)_50%,var(--selaras-resolved-surface-default)_50%)]"
4
+ // Overlap the panel boundary without consuming layout space or painting
5
+ // either surface. Both panels keep their own backgrounds beneath the target.
6
+ root: "z-10 -mx-1 w-2 bg-transparent",
7
+ line: "rtl:translate-x-1/2 group-focus-visible:bg-[var(--_selaras-color-indicator)]"
16
8
  }
17
9
  });