@nuxt/ui 4.10.0 → 4.11.1

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 (117) hide show
  1. package/.nuxt/ui.static.css +11 -11
  2. package/README.md +1 -0
  3. package/cli/commands/make/locale.mjs +40 -11
  4. package/cli/templates.mjs +2 -2
  5. package/dist/module.d.mts +1 -1
  6. package/dist/module.json +2 -2
  7. package/dist/module.mjs +5 -2
  8. package/dist/runtime/components/AuthForm.d.vue.ts +7 -7
  9. package/dist/runtime/components/AuthForm.vue.d.ts +7 -7
  10. package/dist/runtime/components/Button.vue +11 -2
  11. package/dist/runtime/components/Carousel.d.vue.ts +6 -6
  12. package/dist/runtime/components/Carousel.vue.d.ts +6 -6
  13. package/dist/runtime/components/ChatMessages.d.vue.ts +2 -2
  14. package/dist/runtime/components/ChatMessages.vue.d.ts +2 -2
  15. package/dist/runtime/components/ChatPromptSubmit.d.vue.ts +2 -2
  16. package/dist/runtime/components/ChatPromptSubmit.vue.d.ts +2 -2
  17. package/dist/runtime/components/Checkbox.d.vue.ts +1 -1
  18. package/dist/runtime/components/Checkbox.vue +12 -6
  19. package/dist/runtime/components/Checkbox.vue.d.ts +1 -1
  20. package/dist/runtime/components/CheckboxGroup.d.vue.ts +6 -0
  21. package/dist/runtime/components/CheckboxGroup.vue +10 -4
  22. package/dist/runtime/components/CheckboxGroup.vue.d.ts +6 -0
  23. package/dist/runtime/components/Editor.vue +4 -1
  24. package/dist/runtime/components/Error.d.vue.ts +1 -1
  25. package/dist/runtime/components/Error.vue.d.ts +1 -1
  26. package/dist/runtime/components/FileUpload.vue +17 -10
  27. package/dist/runtime/components/Form.vue +1 -1
  28. package/dist/runtime/components/Input.vue +12 -7
  29. package/dist/runtime/components/InputDate.vue +12 -8
  30. package/dist/runtime/components/InputMenu.vue +25 -11
  31. package/dist/runtime/components/InputNumber.vue +22 -14
  32. package/dist/runtime/components/InputRating.vue +10 -7
  33. package/dist/runtime/components/InputTags.vue +12 -7
  34. package/dist/runtime/components/InputTime.vue +12 -7
  35. package/dist/runtime/components/Link.vue +61 -7
  36. package/dist/runtime/components/Listbox.vue +10 -6
  37. package/dist/runtime/components/Modal.vue +12 -5
  38. package/dist/runtime/components/NavigationMenu.vue +10 -10
  39. package/dist/runtime/components/PinInput.vue +13 -8
  40. package/dist/runtime/components/Popover.vue +0 -1
  41. package/dist/runtime/components/PricingTable.d.vue.ts +1 -1
  42. package/dist/runtime/components/PricingTable.vue.d.ts +1 -1
  43. package/dist/runtime/components/Progress.d.vue.ts +2 -1
  44. package/dist/runtime/components/Progress.vue +5 -6
  45. package/dist/runtime/components/Progress.vue.d.ts +2 -1
  46. package/dist/runtime/components/ProgressGroup.d.vue.ts +91 -0
  47. package/dist/runtime/components/ProgressGroup.vue +99 -0
  48. package/dist/runtime/components/ProgressGroup.vue.d.ts +91 -0
  49. package/dist/runtime/components/RadioGroup.d.vue.ts +7 -1
  50. package/dist/runtime/components/RadioGroup.vue +14 -5
  51. package/dist/runtime/components/RadioGroup.vue.d.ts +7 -1
  52. package/dist/runtime/components/Select.d.vue.ts +2 -0
  53. package/dist/runtime/components/Select.vue +23 -8
  54. package/dist/runtime/components/Select.vue.d.ts +2 -0
  55. package/dist/runtime/components/SelectMenu.d.vue.ts +3 -0
  56. package/dist/runtime/components/SelectMenu.vue +35 -14
  57. package/dist/runtime/components/SelectMenu.vue.d.ts +3 -0
  58. package/dist/runtime/components/Slider.vue +15 -8
  59. package/dist/runtime/components/Splitter.d.vue.ts +266 -0
  60. package/dist/runtime/components/Splitter.vue +99 -0
  61. package/dist/runtime/components/Splitter.vue.d.ts +266 -0
  62. package/dist/runtime/components/Switch.vue +8 -4
  63. package/dist/runtime/components/Table.vue +5 -5
  64. package/dist/runtime/components/Textarea.vue +17 -7
  65. package/dist/runtime/components/Theme.d.vue.ts +1 -1
  66. package/dist/runtime/components/Theme.vue +4 -3
  67. package/dist/runtime/components/Theme.vue.d.ts +1 -1
  68. package/dist/runtime/components/Tree.vue +1 -1
  69. package/dist/runtime/components/color-mode/ColorModeSelect.vue +1 -0
  70. package/dist/runtime/components/content/ContentNavigation.vue +8 -1
  71. package/dist/runtime/components/content/ContentToc.vue +4 -1
  72. package/dist/runtime/components/locale/LocaleSelect.vue +1 -0
  73. package/dist/runtime/components/prose/CodeTree.vue +31 -18
  74. package/dist/runtime/composables/useComponentProps.d.ts +1 -1
  75. package/dist/runtime/composables/useComponentProps.js +6 -0
  76. package/dist/runtime/composables/useFormField.d.ts +10 -4
  77. package/dist/runtime/composables/useFormField.js +10 -3
  78. package/dist/runtime/keyframes.css +1 -1
  79. package/dist/runtime/locale/da.js +1 -1
  80. package/dist/runtime/locale/de_at.js +7 -0
  81. package/dist/runtime/locale/de_ch.js +1 -1
  82. package/dist/runtime/locale/eu.js +1 -1
  83. package/dist/runtime/locale/fi.js +1 -1
  84. package/dist/runtime/locale/he.js +1 -1
  85. package/dist/runtime/locale/hi.js +1 -1
  86. package/dist/runtime/locale/index.d.ts +2 -1
  87. package/dist/runtime/locale/index.js +2 -1
  88. package/dist/runtime/locale/is.js +1 -1
  89. package/dist/runtime/locale/lb.js +1 -1
  90. package/dist/runtime/locale/tg.d.ts +3 -0
  91. package/dist/runtime/locale/{tj.js → tg.js} +1 -1
  92. package/dist/runtime/locale/ur.js +1 -1
  93. package/dist/runtime/types/form.d.ts +1 -1
  94. package/dist/runtime/types/html.d.ts +3 -1
  95. package/dist/runtime/types/index.d.ts +2 -0
  96. package/dist/runtime/types/index.js +2 -0
  97. package/dist/runtime/types/theme.d.ts +2 -0
  98. package/dist/runtime/types/tv.d.ts +6 -2
  99. package/dist/runtime/utils/form.d.ts +1 -1
  100. package/dist/runtime/utils/form.js +3 -1
  101. package/dist/runtime/utils/link-keys.d.ts +1 -0
  102. package/dist/runtime/utils/link-keys.js +40 -0
  103. package/dist/runtime/utils/link.d.ts +2 -1
  104. package/dist/runtime/utils/link.js +2 -40
  105. package/dist/runtime/utils/prefetch.d.ts +5 -0
  106. package/dist/runtime/utils/prefetch.js +45 -0
  107. package/dist/runtime/utils/search.js +11 -18
  108. package/dist/runtime/utils/tv.d.ts +1 -1
  109. package/dist/runtime/utils/tv.js +103 -25
  110. package/dist/runtime/vue/components/Icon.vue +5 -2
  111. package/dist/runtime/vue/components/color-mode/ColorModeSelect.vue +1 -0
  112. package/dist/shared/{ui.XkBrgXFP.mjs → ui.DCJ9twx8.mjs} +597 -220
  113. package/dist/unplugin.d.mts +1 -1
  114. package/dist/unplugin.mjs +22 -6
  115. package/dist/vite.mjs +4 -4
  116. package/package.json +54 -53
  117. /package/dist/runtime/locale/{tj.d.ts → de_at.d.ts} +0 -0
@@ -12,6 +12,7 @@ import { useForwardProps } from "../composables/useForwardProps";
12
12
  import { useFormField } from "../composables/useFormField";
13
13
  import { get } from "../utils";
14
14
  import { tv } from "../utils/tv";
15
+ import UIcon from "./Icon.vue";
15
16
  const _props = defineProps({
16
17
  as: { type: null, required: false },
17
18
  legend: { type: String, required: false },
@@ -39,12 +40,16 @@ const slots = defineSlots();
39
40
  const props = useComponentProps("radioGroup", _props);
40
41
  const appConfig = useAppConfig();
41
42
  const rootProps = useForwardProps(reactivePick(props, "as", "loop", "required"), emits);
42
- const { emitFormChange, emitFormInput, color, name, size, highlight, id: _id, disabled, ariaAttrs } = useFormField(_props, { bind: false });
43
+ const { emitFormChange, emitFormInput, color: formFieldColor, name, size: formFieldSize, highlight: formFieldHighlight, id: _id, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props, { bind: false });
43
44
  const id = _id.value ?? useId();
45
+ const color = computed(() => formFieldColor.value ?? props.color);
46
+ const highlight = computed(() => formFieldHighlight.value ?? props.highlight);
47
+ const size = computed(() => formFieldSize.value ?? props.size);
48
+ const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
44
49
  const ui = computed(() => tv({ extend: theme, ...appConfig.ui?.radioGroup || {} })({
45
- size: size.value ?? props.size,
46
- color: color.value ?? props.color,
47
- highlight: highlight.value ?? props.highlight,
50
+ size: size.value,
51
+ color: color.value,
52
+ highlight: highlight.value,
48
53
  disabled: disabled.value,
49
54
  required: props.required,
50
55
  orientation: props.orientation,
@@ -83,6 +88,9 @@ const normalizedItems = computed(() => {
83
88
  }
84
89
  return props.items.map(normalizeItem);
85
90
  });
91
+ function labelIcon(item) {
92
+ return props.indicator === "hidden" ? item.icon : void 0;
93
+ }
86
94
  function onUpdate(value) {
87
95
  const event = new Event("change", { target: { value } });
88
96
  emits("change", event);
@@ -124,7 +132,8 @@ function onUpdate(value) {
124
132
  </RRadioGroupItem>
125
133
  </div>
126
134
 
127
- <div v-if="item.label || !!slots.label || (item.description || !!slots.description)" data-slot="wrapper" :class="ui.wrapper({ class: [props.ui?.wrapper, item.ui?.wrapper] })">
135
+ <div v-if="labelIcon(item) || (item.label || !!slots.label) || (item.description || !!slots.description)" data-slot="wrapper" :class="ui.wrapper({ class: [props.ui?.wrapper, item.ui?.wrapper] })">
136
+ <UIcon v-if="labelIcon(item)" :name="labelIcon(item)" data-slot="icon" :class="ui.icon({ class: [props.ui?.icon, item.ui?.icon] })" />
128
137
  <component :is="!props.variant || props.variant === 'list' ? Label : 'p'" v-if="item.label || !!slots.label" :for="item.id" data-slot="label" :class="ui.label({ class: [props.ui?.label, item.ui?.label], disabled: item.disabled || disabled })">
129
138
  <slot name="label" :item="item" :model-value="props.modelValue">
130
139
  {{ item.label }}
@@ -2,6 +2,7 @@ import type { RadioGroupRootProps, RadioGroupRootEmits } from 'reka-ui';
2
2
  import type { VNode } from 'vue';
3
3
  import type { AppConfig } from '@nuxt/schema';
4
4
  import theme from '#build/ui/radio-group';
5
+ import type { IconProps } from './Icon.vue';
5
6
  import type { AcceptableValue, GetItemKeys, GetModelValue, GetModelValueEmits } from '../types/utils';
6
7
  import type { ComponentConfig } from '../types/tv';
7
8
  type RadioGroup = ComponentConfig<typeof theme, AppConfig, 'radioGroup'>;
@@ -11,8 +12,13 @@ export type RadioGroupItem = RadioGroupValue | {
11
12
  description?: string;
12
13
  disabled?: boolean;
13
14
  value?: RadioGroupValue;
15
+ /**
16
+ * The icon displayed above the label when `indicator` is `hidden`.
17
+ * @IconifyIcon
18
+ */
19
+ icon?: IconProps['name'];
14
20
  class?: any;
15
- ui?: Pick<RadioGroup['slots'], 'item' | 'container' | 'base' | 'indicator' | 'wrapper' | 'label' | 'description'>;
21
+ ui?: Pick<RadioGroup['slots'], 'item' | 'container' | 'base' | 'indicator' | 'wrapper' | 'label' | 'icon' | 'description'>;
16
22
  [key: string]: any;
17
23
  };
18
24
  export interface RadioGroupProps<T extends RadioGroupItem[] = RadioGroupItem[], VK extends GetItemKeys<T> = 'value'> extends Pick<RadioGroupRootProps, 'disabled' | 'loop' | 'name' | 'required'> {
@@ -105,6 +105,8 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
105
105
  multiple?: M & boolean;
106
106
  /** Highlight the ring color like a focus state. */
107
107
  highlight?: boolean;
108
+ /** Keep the mobile text size on all breakpoints. */
109
+ fixed?: boolean;
108
110
  autofocus?: boolean;
109
111
  autofocusDelay?: number;
110
112
  class?: any;
@@ -3,7 +3,7 @@ import theme from "#build/ui/select";
3
3
  </script>
4
4
 
5
5
  <script setup>
6
- import { useTemplateRef, computed, onMounted, toRef } from "vue";
6
+ import { useTemplateRef, computed, onMounted, onScopeDispose, toRef } from "vue";
7
7
  import { SelectRoot, SelectArrow, SelectTrigger, SelectPortal, SelectContent, SelectViewport, SelectValue as RSelectValue, SelectLabel, SelectGroup, SelectItem as RSelectItem, SelectItemIndicator, SelectItemText, SelectSeparator } from "reka-ui";
8
8
  import { defu } from "defu";
9
9
  import { reactivePick } from "@vueuse/core";
@@ -40,6 +40,7 @@ const _props = defineProps({
40
40
  modelModifiers: { type: null, required: false },
41
41
  multiple: { type: Boolean, required: false },
42
42
  highlight: { type: Boolean, required: false },
43
+ fixed: { type: Boolean, required: false },
43
44
  autofocus: { type: Boolean, required: false },
44
45
  autofocusDelay: { type: Number, required: false, default: 0 },
45
46
  class: { type: null, required: false },
@@ -68,17 +69,29 @@ const portalProps = usePortal(toRef(() => props.portal));
68
69
  const position = computed(() => props.content?.position ?? appConfig.ui?.select?.defaultVariants?.position ?? theme.defaultVariants?.position);
69
70
  const contentProps = toRef(() => defu(props.content, { side: "bottom", sideOffset: 8, collisionPadding: 8, position: position.value }));
70
71
  const arrowProps = toRef(() => defu(props.arrow, { rounded: true }));
71
- const { emitFormChange, emitFormInput, emitFormBlur, emitFormFocus, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField(_props);
72
+ const { emitFormChange, emitFormInput, emitFormBlur, emitFormFocus, size: formFieldSize, color: formFieldColor, id, name, highlight: formFieldHighlight, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props);
72
73
  const { orientation, size: fieldGroupSize } = useFieldGroup(_props);
73
- const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })));
74
- const selectSize = computed(() => fieldGroupSize.value || formFieldSize.value);
74
+ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(computed(() => ({
75
+ icon: props.icon,
76
+ leading: props.leading,
77
+ leadingIcon: props.leadingIcon,
78
+ trailing: props.trailing,
79
+ trailingIcon: props.trailingIcon ?? appConfig.ui.icons.chevronDown,
80
+ loading: props.loading,
81
+ loadingIcon: props.loadingIcon
82
+ })));
83
+ const color = computed(() => formFieldColor.value ?? props.color);
84
+ const highlight = computed(() => formFieldHighlight.value ?? props.highlight);
85
+ const size = computed(() => fieldGroupSize.value ?? formFieldSize.value ?? props.size);
86
+ const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
75
87
  const isItemAligned = computed(() => position.value === "item-aligned");
76
88
  const ui = computed(() => tv({ extend: theme, ...appConfig.ui?.select || {} })({
77
- color: color.value ?? props.color,
89
+ color: color.value,
78
90
  variant: props.variant,
79
- size: selectSize.value ?? props.size,
91
+ size: size.value,
80
92
  loading: props.loading,
81
- highlight: highlight.value ?? props.highlight,
93
+ highlight: highlight.value,
94
+ fixed: props.fixed,
82
95
  leading: isLeading.value || !!props.avatar || !!slots.leading,
83
96
  trailing: isTrailing.value || !!slots.trailing,
84
97
  fieldGroup: orientation.value,
@@ -110,11 +123,13 @@ function autoFocus() {
110
123
  });
111
124
  }
112
125
  }
126
+ let autofocusTimeoutId;
113
127
  onMounted(() => {
114
- setTimeout(() => {
128
+ autofocusTimeoutId = setTimeout(() => {
115
129
  autoFocus();
116
130
  }, props.autofocusDelay);
117
131
  });
132
+ onScopeDispose(() => clearTimeout(autofocusTimeoutId));
118
133
  function onUpdate(value) {
119
134
  if (props.modelModifiers?.trim && (typeof value === "string" || value === null || value === void 0)) {
120
135
  value = value?.trim() ?? null;
@@ -105,6 +105,8 @@ export interface SelectProps<T extends ArrayOrNested<SelectItem> = ArrayOrNested
105
105
  multiple?: M & boolean;
106
106
  /** Highlight the ring color like a focus state. */
107
107
  highlight?: boolean;
108
+ /** Keep the mobile text size on all breakpoints. */
109
+ fixed?: boolean;
108
110
  autofocus?: boolean;
109
111
  autofocusDelay?: number;
110
112
  class?: any;
@@ -47,6 +47,7 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
47
47
  * Whether to display the search input or not.
48
48
  * Can be an object to pass additional props to the input.
49
49
  * `{ placeholder: 'Search...', variant: 'none' }`{lang="ts-type"}
50
+ * Set `autofocus: false` to prevent the search input from being focused when the menu opens (e.g. to avoid opening the virtual keyboard on touch devices).
50
51
  * @defaultValue true
51
52
  */
52
53
  searchInput?: boolean | Omit<InputProps, 'modelValue' | 'defaultValue'>;
@@ -145,6 +146,8 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
145
146
  multiple?: M & boolean;
146
147
  /** Highlight the ring color like a focus state. */
147
148
  highlight?: boolean;
149
+ /** Keep the mobile text size on all breakpoints. */
150
+ fixed?: boolean;
148
151
  /**
149
152
  * Determines if custom user input that does not exist in options can be added.
150
153
  * @defaultValue false
@@ -3,7 +3,7 @@ import theme from "#build/ui/select-menu";
3
3
  </script>
4
4
 
5
5
  <script setup>
6
- import { useTemplateRef, computed, ref, onMounted, toRef, toRaw, watch, nextTick } from "vue";
6
+ import { useTemplateRef, computed, ref, onMounted, onScopeDispose, toRef, toRaw, watch, nextTick } from "vue";
7
7
  import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxCancel, ComboboxPortal, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxVirtualizer, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, FocusScope } from "reka-ui";
8
8
  import { useForwardProps } from "../composables/useForwardProps";
9
9
  import { defu } from "defu";
@@ -50,6 +50,7 @@ const _props = defineProps({
50
50
  modelModifiers: { type: null, required: false },
51
51
  multiple: { type: Boolean, required: false },
52
52
  highlight: { type: Boolean, required: false },
53
+ fixed: { type: Boolean, required: false },
53
54
  createItem: { type: [Boolean, String, Object], required: false },
54
55
  filterFields: { type: Array, required: false },
55
56
  ignoreFilter: { type: Boolean, required: false },
@@ -89,14 +90,25 @@ const clearProps = computed(() => typeof props.clear === "object" ? props.clear
89
90
  const virtualizerProps = toRef(() => {
90
91
  if (!props.virtualize) return false;
91
92
  return defu(typeof props.virtualize === "boolean" ? {} : props.virtualize, {
92
- estimateSize: getEstimateSize(filteredItems.value, selectSize.value || "md", props.descriptionKey, !!slots["item-description"])
93
+ estimateSize: getEstimateSize(filteredItems.value, size.value ?? "md", props.descriptionKey, !!slots["item-description"])
93
94
  });
94
95
  });
95
- const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t("selectMenu.search"), variant: "none" }));
96
- const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField(_props);
96
+ const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t("selectMenu.search"), variant: "none", fixed: props.fixed }));
97
+ const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formFieldSize, color: formFieldColor, id, name, highlight: formFieldHighlight, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props);
97
98
  const { orientation, size: fieldGroupSize } = useFieldGroup(_props);
98
- const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })));
99
- const selectSize = computed(() => fieldGroupSize.value || formFieldSize.value);
99
+ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(computed(() => ({
100
+ icon: props.icon,
101
+ leading: props.leading,
102
+ leadingIcon: props.leadingIcon,
103
+ trailing: props.trailing,
104
+ trailingIcon: props.trailingIcon ?? appConfig.ui.icons.chevronDown,
105
+ loading: props.loading,
106
+ loadingIcon: props.loadingIcon
107
+ })));
108
+ const color = computed(() => formFieldColor.value ?? props.color);
109
+ const highlight = computed(() => formFieldHighlight.value ?? props.highlight);
110
+ const size = computed(() => fieldGroupSize.value ?? formFieldSize.value ?? props.size);
111
+ const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
100
112
  const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate();
101
113
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate({
102
114
  props: {
@@ -111,11 +123,12 @@ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate({
111
123
  }
112
124
  });
113
125
  const ui = computed(() => tv({ extend: theme, ...appConfig.ui?.selectMenu || {} })({
114
- color: color.value ?? props.color,
126
+ color: color.value,
115
127
  variant: props.variant,
116
- size: selectSize?.value ?? props.size,
128
+ size: size.value,
117
129
  loading: props.loading,
118
- highlight: highlight.value ?? props.highlight,
130
+ highlight: highlight.value,
131
+ fixed: props.fixed,
119
132
  leading: isLeading.value || !!props.avatar || !!slots.leading,
120
133
  trailing: isTrailing.value || !!slots.trailing,
121
134
  fieldGroup: orientation.value,
@@ -171,11 +184,13 @@ function autoFocus() {
171
184
  });
172
185
  }
173
186
  }
187
+ let autofocusTimeoutId;
174
188
  onMounted(() => {
175
- setTimeout(() => {
189
+ autofocusTimeoutId = setTimeout(() => {
176
190
  autoFocus();
177
191
  }, props.autofocusDelay);
178
192
  });
193
+ onScopeDispose(() => clearTimeout(autofocusTimeoutId));
179
194
  function onUpdate(value) {
180
195
  if (toRaw(props.modelValue) === value) {
181
196
  return;
@@ -201,9 +216,10 @@ function onUpdate(value) {
201
216
  }
202
217
  }
203
218
  const isOpen = ref(false);
219
+ let timeoutId;
220
+ onScopeDispose(() => clearTimeout(timeoutId));
204
221
  function onUpdateOpen(value) {
205
222
  isOpen.value = value;
206
- let timeoutId;
207
223
  if (!value) {
208
224
  const event = new FocusEvent("blur");
209
225
  emits("blur", event);
@@ -248,6 +264,11 @@ function isModelValueEmpty(modelValue) {
248
264
  function onClear() {
249
265
  emits("clear");
250
266
  }
267
+ function onMountAutoFocus(event) {
268
+ if (searchInputProps.value.autofocus === false) {
269
+ event.preventDefault();
270
+ }
271
+ }
251
272
  const viewportRef = useTemplateRef("viewportRef");
252
273
  const comboboxRootRef = useTemplateRef("comboboxRootRef");
253
274
  watch(() => props.items, async () => {
@@ -378,7 +399,7 @@ defineExpose({
378
399
  <UButton
379
400
  as="span"
380
401
  :icon="props.clearIcon || appConfig.ui.icons.close"
381
- :size="selectSize"
402
+ :size="size"
382
403
  variant="link"
383
404
  color="neutral"
384
405
  tabindex="-1"
@@ -398,14 +419,14 @@ defineExpose({
398
419
  <ComboboxPortal v-bind="portalProps">
399
420
  <FieldGroupReset>
400
421
  <ComboboxContent data-slot="content" :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
401
- <FocusScope trapped data-slot="focusScope" :class="ui.focusScope({ class: props.ui?.focusScope })">
422
+ <FocusScope trapped data-slot="focusScope" :class="ui.focusScope({ class: props.ui?.focusScope })" @mount-auto-focus="onMountAutoFocus">
402
423
  <slot name="content-top" />
403
424
 
404
425
  <ComboboxInput v-if="!!props.searchInput" v-model="searchTerm" :display-value="() => searchTerm" as-child>
405
426
  <UInput
406
427
  autofocus
407
428
  autocomplete="off"
408
- :size="selectSize"
429
+ :size="size"
409
430
  v-bind="searchInputProps"
410
431
  :model-modifiers="{
411
432
  trim: props.modelModifiers?.trim
@@ -47,6 +47,7 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
47
47
  * Whether to display the search input or not.
48
48
  * Can be an object to pass additional props to the input.
49
49
  * `{ placeholder: 'Search...', variant: 'none' }`{lang="ts-type"}
50
+ * Set `autofocus: false` to prevent the search input from being focused when the menu opens (e.g. to avoid opening the virtual keyboard on touch devices).
50
51
  * @defaultValue true
51
52
  */
52
53
  searchInput?: boolean | Omit<InputProps, 'modelValue' | 'defaultValue'>;
@@ -145,6 +146,8 @@ export interface SelectMenuProps<T extends ArrayOrNested<SelectMenuItem> = Array
145
146
  multiple?: M & boolean;
146
147
  /** Highlight the ring color like a focus state. */
147
148
  highlight?: boolean;
149
+ /** Keep the mobile text size on all breakpoints. */
150
+ fixed?: boolean;
148
151
  /**
149
152
  * Determines if custom user input that does not exist in options can be added.
150
153
  * @defaultValue false
@@ -10,6 +10,7 @@ import { reactivePick } from "@vueuse/core";
10
10
  import { useAppConfig } from "#imports";
11
11
  import { useComponentProps } from "../composables/useComponentProps";
12
12
  import { useFormField } from "../composables/useFormField";
13
+ import { pick, omit } from "../utils";
13
14
  import { tv } from "../utils/tv";
14
15
  import UTooltip from "./Tooltip.vue";
15
16
  const _props = defineProps({
@@ -30,11 +31,15 @@ const _props = defineProps({
30
31
  minStepsBetweenThumbs: { type: Number, required: false }
31
32
  });
32
33
  const emits = defineEmits(["change"]);
34
+ defineOptions({ inheritAttrs: false });
33
35
  const props = useComponentProps("slider", _props);
34
36
  const modelValue = defineModel({ type: null });
35
37
  const appConfig = useAppConfig();
36
- const rootProps = useForwardProps(reactivePick(props, "as", "orientation", "min", "max", "step", "minStepsBetweenThumbs", "inverted"), emits);
37
- const { id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField(_props);
38
+ const rootProps = useForwardProps(reactivePick(props, "as", "orientation", "min", "max", "step", "minStepsBetweenThumbs", "inverted"));
39
+ const { id, emitFormChange, emitFormInput, size: formFieldSize, color: formFieldColor, name, disabled: formFieldDisabled, ariaAttrs } = useFormField(_props);
40
+ const color = computed(() => formFieldColor.value ?? props.color);
41
+ const size = computed(() => formFieldSize.value ?? props.size);
42
+ const disabled = computed(() => formFieldDisabled.value ?? props.disabled);
38
43
  const defaultSliderValue = computed(() => {
39
44
  if (typeof props.defaultValue === "number") {
40
45
  return [props.defaultValue];
@@ -53,10 +58,11 @@ const sliderValue = computed({
53
58
  }
54
59
  });
55
60
  const thumbs = computed(() => sliderValue.value?.length ?? 1);
61
+ const thumbAttrs = ["aria-label", "aria-labelledby", "aria-describedby", "aria-valuetext", "aria-invalid", "aria-errormessage"];
56
62
  const ui = computed(() => tv({ extend: theme, ...appConfig.ui?.slider || {} })({
57
63
  disabled: disabled.value,
58
- size: size.value ?? props.size,
59
- color: color.value ?? props.color,
64
+ size: size.value,
65
+ color: color.value,
60
66
  orientation: props.orientation
61
67
  }));
62
68
  function onChange(value) {
@@ -68,12 +74,13 @@ function onChange(value) {
68
74
 
69
75
  <template>
70
76
  <SliderRoot
71
- v-bind="{ ...rootProps, ...ariaAttrs }"
72
77
  :id="id"
73
78
  v-model="sliderValue"
79
+ data-slot="root"
80
+ :role="thumbs > 1 && ($attrs['aria-label'] || $attrs['aria-labelledby']) ? 'group' : void 0"
81
+ v-bind="{ ...rootProps, ...thumbs > 1 ? $attrs : omit($attrs, thumbAttrs) }"
74
82
  :name="name"
75
83
  :disabled="disabled"
76
- data-slot="root"
77
84
  :class="ui.root({ class: [props.ui?.root, props.class] })"
78
85
  :default-value="defaultSliderValue"
79
86
  @update:model-value="emitFormInput()"
@@ -90,9 +97,9 @@ function onChange(value) {
90
97
  disable-closing-trigger
91
98
  v-bind="typeof props.tooltip === 'object' ? props.tooltip : {}"
92
99
  >
93
- <SliderThumb data-slot="thumb" :class="ui.thumb({ class: props.ui?.thumb })" :aria-label="thumbs === 1 ? 'Thumb' : `Thumb ${thumb} of ${thumbs}`" />
100
+ <SliderThumb data-slot="thumb" :class="ui.thumb({ class: props.ui?.thumb })" v-bind="{ ...thumbs === 1 ? pick($attrs, thumbAttrs) : {}, ...ariaAttrs }" :aria-label="thumbs > 1 || $attrs['aria-labelledby'] ? void 0 : $attrs['aria-label'] ?? 'Thumb'" />
94
101
  </UTooltip>
95
- <SliderThumb v-else data-slot="thumb" :class="ui.thumb({ class: props.ui?.thumb })" :aria-label="thumbs === 1 ? 'Thumb' : `Thumb ${thumb} of ${thumbs}`" />
102
+ <SliderThumb v-else data-slot="thumb" :class="ui.thumb({ class: props.ui?.thumb })" v-bind="{ ...thumbs === 1 ? pick($attrs, thumbAttrs) : {}, ...ariaAttrs }" :aria-label="thumbs > 1 || $attrs['aria-labelledby'] ? void 0 : $attrs['aria-label'] ?? 'Thumb'" />
96
103
  </template>
97
104
  </SliderRoot>
98
105
  </template>