@nuxt/ui 4.6.1 → 4.7.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 (168) hide show
  1. package/README.md +7 -4
  2. package/cli/package.json +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +3 -2
  5. package/dist/runtime/components/Accordion.d.vue.ts +6 -1
  6. package/dist/runtime/components/Accordion.vue +1 -1
  7. package/dist/runtime/components/Accordion.vue.d.ts +6 -1
  8. package/dist/runtime/components/AuthForm.d.vue.ts +22 -29
  9. package/dist/runtime/components/AuthForm.vue +8 -6
  10. package/dist/runtime/components/AuthForm.vue.d.ts +22 -29
  11. package/dist/runtime/components/Calendar.vue +4 -4
  12. package/dist/runtime/components/Card.d.vue.ts +4 -0
  13. package/dist/runtime/components/Card.vue +16 -2
  14. package/dist/runtime/components/Card.vue.d.ts +4 -0
  15. package/dist/runtime/components/ChatMessage.d.vue.ts +27 -17
  16. package/dist/runtime/components/ChatMessage.vue +10 -16
  17. package/dist/runtime/components/ChatMessage.vue.d.ts +27 -17
  18. package/dist/runtime/components/ChatMessages.d.vue.ts +27 -21
  19. package/dist/runtime/components/ChatMessages.vue +22 -23
  20. package/dist/runtime/components/ChatMessages.vue.d.ts +27 -21
  21. package/dist/runtime/components/ChatPromptSubmit.vue +3 -1
  22. package/dist/runtime/components/Checkbox.vue +5 -3
  23. package/dist/runtime/components/CommandPalette.d.vue.ts +18 -3
  24. package/dist/runtime/components/CommandPalette.vue +11 -7
  25. package/dist/runtime/components/CommandPalette.vue.d.ts +18 -3
  26. package/dist/runtime/components/ContextMenuContent.vue +68 -65
  27. package/dist/runtime/components/DashboardSearch.d.vue.ts +8 -37
  28. package/dist/runtime/components/DashboardSearch.vue +11 -10
  29. package/dist/runtime/components/DashboardSearch.vue.d.ts +8 -37
  30. package/dist/runtime/components/DashboardSidebar.vue +1 -5
  31. package/dist/runtime/components/Drawer.vue +44 -41
  32. package/dist/runtime/components/DropdownMenuContent.vue +90 -87
  33. package/dist/runtime/components/EditorEmojiMenu.d.vue.ts +1 -1
  34. package/dist/runtime/components/EditorEmojiMenu.vue +2 -0
  35. package/dist/runtime/components/EditorEmojiMenu.vue.d.ts +1 -1
  36. package/dist/runtime/components/EditorMentionMenu.d.vue.ts +1 -1
  37. package/dist/runtime/components/EditorMentionMenu.vue +2 -0
  38. package/dist/runtime/components/EditorMentionMenu.vue.d.ts +1 -1
  39. package/dist/runtime/components/EditorSuggestionMenu.d.vue.ts +1 -1
  40. package/dist/runtime/components/EditorSuggestionMenu.vue +2 -0
  41. package/dist/runtime/components/EditorSuggestionMenu.vue.d.ts +1 -1
  42. package/dist/runtime/components/FileUpload.vue +7 -5
  43. package/dist/runtime/components/Header.vue +1 -5
  44. package/dist/runtime/components/Input.vue +2 -2
  45. package/dist/runtime/components/InputDate.vue +6 -6
  46. package/dist/runtime/components/InputMenu.d.vue.ts +3 -3
  47. package/dist/runtime/components/InputMenu.vue +40 -38
  48. package/dist/runtime/components/InputMenu.vue.d.ts +3 -3
  49. package/dist/runtime/components/InputNumber.vue +2 -2
  50. package/dist/runtime/components/InputTags.vue +2 -2
  51. package/dist/runtime/components/InputTime.vue +2 -2
  52. package/dist/runtime/components/Link.vue +64 -8
  53. package/dist/runtime/components/Listbox.d.vue.ts +228 -0
  54. package/dist/runtime/components/Listbox.vue +242 -0
  55. package/dist/runtime/components/Listbox.vue.d.ts +228 -0
  56. package/dist/runtime/components/Modal.vue +13 -10
  57. package/dist/runtime/components/Popover.vue +7 -4
  58. package/dist/runtime/components/PricingTable.vue +60 -58
  59. package/dist/runtime/components/RadioGroup.vue +5 -3
  60. package/dist/runtime/components/Select.d.vue.ts +1 -1
  61. package/dist/runtime/components/Select.vue +84 -73
  62. package/dist/runtime/components/Select.vue.d.ts +1 -1
  63. package/dist/runtime/components/SelectMenu.d.vue.ts +3 -3
  64. package/dist/runtime/components/SelectMenu.vue +54 -52
  65. package/dist/runtime/components/SelectMenu.vue.d.ts +3 -3
  66. package/dist/runtime/components/Sidebar.vue +1 -4
  67. package/dist/runtime/components/Slideover.vue +67 -64
  68. package/dist/runtime/components/Table.d.vue.ts +1 -2
  69. package/dist/runtime/components/Table.vue +17 -29
  70. package/dist/runtime/components/Table.vue.d.ts +1 -2
  71. package/dist/runtime/components/Tabs.d.vue.ts +5 -1
  72. package/dist/runtime/components/Tabs.vue +2 -2
  73. package/dist/runtime/components/Tabs.vue.d.ts +5 -1
  74. package/dist/runtime/components/Textarea.d.vue.ts +1 -0
  75. package/dist/runtime/components/Textarea.vue +2 -1
  76. package/dist/runtime/components/Textarea.vue.d.ts +1 -0
  77. package/dist/runtime/components/Tooltip.vue +12 -9
  78. package/dist/runtime/components/content/ContentSearch.d.vue.ts +19 -35
  79. package/dist/runtime/components/content/ContentSearch.vue +10 -9
  80. package/dist/runtime/components/content/ContentSearch.vue.d.ts +19 -35
  81. package/dist/runtime/components/content/ContentToc.vue +19 -9
  82. package/dist/runtime/components/prose/Img.vue +2 -1
  83. package/dist/runtime/components/prose/Pre.vue +8 -3
  84. package/dist/runtime/components/prose/Prompt.d.vue.ts +32 -0
  85. package/dist/runtime/components/prose/Prompt.vue +88 -0
  86. package/dist/runtime/components/prose/Prompt.vue.d.ts +32 -0
  87. package/dist/runtime/composables/index.d.ts +1 -0
  88. package/dist/runtime/composables/index.js +1 -0
  89. package/dist/runtime/composables/useContentSearch.js +19 -5
  90. package/dist/runtime/composables/useEditorMenu.d.ts +5 -0
  91. package/dist/runtime/composables/useEditorMenu.js +3 -1
  92. package/dist/runtime/composables/useFieldGroup.d.ts +3 -0
  93. package/dist/runtime/composables/useFieldGroup.js +11 -1
  94. package/dist/runtime/composables/useResolvedVariants.d.ts +21 -0
  95. package/dist/runtime/composables/useResolvedVariants.js +14 -0
  96. package/dist/runtime/locale/ar.js +9 -0
  97. package/dist/runtime/locale/az.js +9 -0
  98. package/dist/runtime/locale/be.js +9 -0
  99. package/dist/runtime/locale/bg.js +9 -0
  100. package/dist/runtime/locale/bn.js +9 -0
  101. package/dist/runtime/locale/ca.js +9 -0
  102. package/dist/runtime/locale/ckb.js +10 -1
  103. package/dist/runtime/locale/cs.js +9 -0
  104. package/dist/runtime/locale/da.js +9 -0
  105. package/dist/runtime/locale/de.js +9 -0
  106. package/dist/runtime/locale/de_ch.js +9 -0
  107. package/dist/runtime/locale/el.js +9 -0
  108. package/dist/runtime/locale/en.js +10 -1
  109. package/dist/runtime/locale/es.js +9 -0
  110. package/dist/runtime/locale/et.js +9 -0
  111. package/dist/runtime/locale/eu.js +11 -2
  112. package/dist/runtime/locale/fa_ir.js +9 -0
  113. package/dist/runtime/locale/fi.js +9 -0
  114. package/dist/runtime/locale/fr.js +9 -0
  115. package/dist/runtime/locale/gl.js +9 -0
  116. package/dist/runtime/locale/he.js +9 -0
  117. package/dist/runtime/locale/hi.js +9 -0
  118. package/dist/runtime/locale/hr.js +9 -0
  119. package/dist/runtime/locale/hu.js +9 -0
  120. package/dist/runtime/locale/hy.js +9 -0
  121. package/dist/runtime/locale/id.js +9 -0
  122. package/dist/runtime/locale/is.js +9 -0
  123. package/dist/runtime/locale/it.js +9 -0
  124. package/dist/runtime/locale/ja.js +9 -0
  125. package/dist/runtime/locale/ka.js +9 -0
  126. package/dist/runtime/locale/kk.js +9 -0
  127. package/dist/runtime/locale/km.js +9 -0
  128. package/dist/runtime/locale/ko.js +9 -0
  129. package/dist/runtime/locale/ky.js +9 -0
  130. package/dist/runtime/locale/lb.js +11 -2
  131. package/dist/runtime/locale/lo.js +15 -6
  132. package/dist/runtime/locale/lt.js +9 -0
  133. package/dist/runtime/locale/mn.js +9 -0
  134. package/dist/runtime/locale/ms.js +9 -0
  135. package/dist/runtime/locale/nb_no.js +9 -0
  136. package/dist/runtime/locale/nl.js +9 -0
  137. package/dist/runtime/locale/pl.js +9 -0
  138. package/dist/runtime/locale/pt.js +9 -0
  139. package/dist/runtime/locale/pt_br.js +9 -0
  140. package/dist/runtime/locale/ro.js +9 -0
  141. package/dist/runtime/locale/ru.js +9 -0
  142. package/dist/runtime/locale/sk.js +9 -0
  143. package/dist/runtime/locale/sl.js +9 -0
  144. package/dist/runtime/locale/sq.js +9 -0
  145. package/dist/runtime/locale/sv.js +9 -0
  146. package/dist/runtime/locale/th.js +9 -0
  147. package/dist/runtime/locale/tj.js +9 -0
  148. package/dist/runtime/locale/tr.js +9 -0
  149. package/dist/runtime/locale/ug_cn.js +9 -0
  150. package/dist/runtime/locale/uk.js +9 -0
  151. package/dist/runtime/locale/ur.js +9 -0
  152. package/dist/runtime/locale/uz.js +9 -0
  153. package/dist/runtime/locale/vi.js +9 -0
  154. package/dist/runtime/locale/zh_cn.js +9 -0
  155. package/dist/runtime/locale/zh_tw.js +9 -0
  156. package/dist/runtime/plugins/colors.js +1 -1
  157. package/dist/runtime/types/index.d.ts +1 -0
  158. package/dist/runtime/types/index.js +1 -0
  159. package/dist/runtime/types/locale.d.ts +9 -0
  160. package/dist/runtime/types/prose.d.ts +1 -0
  161. package/dist/runtime/types/prose.js +1 -0
  162. package/dist/runtime/vue/overrides/inertia/Link.vue +3 -3
  163. package/dist/runtime/vue/overrides/none/Link.vue +3 -2
  164. package/dist/runtime/vue/overrides/vue-router/Link.vue +27 -31
  165. package/dist/shared/{ui.DJUbBpE2.mjs → ui.BIQuJqNV.mjs} +186 -34
  166. package/dist/unplugin.mjs +1 -1
  167. package/dist/vite.mjs +1 -1
  168. package/package.json +48 -39
@@ -57,10 +57,10 @@ const slots = defineSlots();
57
57
  const appConfig = useAppConfig();
58
58
  const uiProp = useComponentUI("inputTags", props);
59
59
  const rootProps = useForwardPropsEmits(reactivePick(props, "as", "addOnPaste", "addOnTab", "addOnBlur", "duplicate", "delimiter", "max", "convertValue", "displayValue", "required"), emits);
60
- const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField(props);
60
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField(props);
61
61
  const { orientation, size: fieldGroupSize } = useFieldGroup(props);
62
62
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props);
63
- const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value);
63
+ const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value);
64
64
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.inputTags || {} })({
65
65
  color: color.value,
66
66
  variant: props.variant,
@@ -60,10 +60,10 @@ const slots = defineSlots();
60
60
  const appConfig = useAppConfig();
61
61
  const uiProp = useComponentUI("inputTime", props);
62
62
  const rootProps = useForwardPropsEmits(reactiveOmit(props, "id", "name", "range", "modelValue", "defaultValue", "color", "variant", "size", "highlight", "fixed", "disabled", "autofocus", "autofocusDelay", "icon", "avatar", "leading", "leadingIcon", "trailing", "trailingIcon", "loading", "loadingIcon", "separatorIcon", "class", "ui"), emits);
63
- const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formGroupSize, name, highlight, disabled, ariaAttrs } = useFormField(props);
63
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formFieldSize, name, highlight, disabled, ariaAttrs } = useFormField(props);
64
64
  const { orientation, size: fieldGroupSize } = useFieldGroup(props);
65
65
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props);
66
- const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value);
66
+ const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value);
67
67
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.inputTime || {} })({
68
68
  color: color.value,
69
69
  variant: props.variant,
@@ -5,10 +5,11 @@ import theme from "#build/ui/link";
5
5
  <script setup>
6
6
  import { computed } from "vue";
7
7
  import { isEqual } from "ohash/utils";
8
- import { useForwardProps } from "reka-ui";
8
+ import { useForwardProps, Slot } from "reka-ui";
9
9
  import { defu } from "defu";
10
+ import { hasProtocol } from "ufo";
10
11
  import { reactiveOmit } from "@vueuse/core";
11
- import { useRoute, useAppConfig } from "#imports";
12
+ import { useRoute, useAppConfig, useNuxtApp } from "#imports";
12
13
  import { mergeClasses } from "../utils";
13
14
  import { tv } from "../utils/tv";
14
15
  import { isPartiallyEqual } from "../utils/link";
@@ -46,6 +47,7 @@ const props = defineProps({
46
47
  defineSlots();
47
48
  const route = useRoute();
48
49
  const appConfig = useAppConfig();
50
+ const nuxtApp = useNuxtApp();
49
51
  const nuxtLinkProps = useForwardProps(reactiveOmit(props, "as", "type", "disabled", "active", "exact", "exactQuery", "exactHash", "activeClass", "inactiveClass", "to", "href", "raw", "custom", "class"));
50
52
  const ui = computed(() => tv({
51
53
  extend: tv(theme),
@@ -58,11 +60,39 @@ const ui = computed(() => tv({
58
60
  }
59
61
  }, appConfig.ui?.link || {})
60
62
  }));
61
- const to = computed(() => props.to ?? props.href);
62
- function isLinkActive({ route: linkRoute, isActive, isExactActive }) {
63
+ const to = computed(() => {
64
+ const path = props.to ?? props.href;
65
+ if (!path) return path;
66
+ if (typeof path !== "string") return path;
67
+ if (props.external || hasProtocol(path, { acceptRelative: true })) {
68
+ return path;
69
+ }
70
+ const localePath = nuxtApp.$localePath;
71
+ if (localePath) {
72
+ return localePath(path);
73
+ }
74
+ return path;
75
+ });
76
+ const isInternalLink = computed(() => {
77
+ if (!to.value) return false;
78
+ if (props.external) return false;
79
+ if (typeof to.value !== "string") return true;
80
+ if (hasProtocol(to.value, { acceptRelative: true })) return false;
81
+ if (props.target && props.target !== "_self") return false;
82
+ return true;
83
+ });
84
+ const externalRel = computed(() => {
85
+ if (props.noRel) return null;
86
+ if (props.rel) return props.rel;
87
+ return "noopener noreferrer";
88
+ });
89
+ function isLinkActive({ route: linkRoute, isActive, isExactActive } = {}) {
63
90
  if (props.active !== void 0) {
64
91
  return props.active;
65
92
  }
93
+ if (!to.value) {
94
+ return false;
95
+ }
66
96
  if (props.exactQuery === "partial") {
67
97
  if (!isPartiallyEqual(linkRoute.query, route.query)) return false;
68
98
  } else if (props.exactQuery === true) {
@@ -79,7 +109,7 @@ function isLinkActive({ route: linkRoute, isActive, isExactActive }) {
79
109
  }
80
110
  return false;
81
111
  }
82
- function resolveLinkClass({ route: route2, isActive, isExactActive }) {
112
+ function resolveLinkClass({ route: route2, isActive, isExactActive } = {}) {
83
113
  const active = isLinkActive({ route: route2, isActive, isExactActive });
84
114
  if (props.raw) {
85
115
  return [props.class, active ? props.activeClass : props.inactiveClass];
@@ -89,8 +119,8 @@ function resolveLinkClass({ route: route2, isActive, isExactActive }) {
89
119
  </script>
90
120
 
91
121
  <template>
92
- <NuxtLink v-slot="{ href, navigate, route: linkRoute, isActive, isExactActive, ...rest }" v-bind="nuxtLinkProps" :to="to" custom>
93
- <template v-if="custom">
122
+ <NuxtLink v-if="isInternalLink" v-slot="{ href, navigate, route: linkRoute, isActive, isExactActive, ...rest }" v-bind="nuxtLinkProps" :to="to" custom>
123
+ <Slot v-if="custom">
94
124
  <slot
95
125
  v-bind="{
96
126
  ...$attrs,
@@ -106,7 +136,7 @@ function resolveLinkClass({ route: route2, isActive, isExactActive }) {
106
136
  active: isLinkActive({ route: linkRoute, isActive, isExactActive })
107
137
  }"
108
138
  />
109
- </template>
139
+ </Slot>
110
140
  <ULinkBase
111
141
  v-else
112
142
  v-bind="{
@@ -126,4 +156,30 @@ function resolveLinkClass({ route: route2, isActive, isExactActive }) {
126
156
  <slot :active="isLinkActive({ route: linkRoute, isActive, isExactActive })" />
127
157
  </ULinkBase>
128
158
  </NuxtLink>
159
+
160
+ <Slot v-else-if="custom">
161
+ <slot
162
+ v-bind="{
163
+ ...$attrs,
164
+ as,
165
+ type,
166
+ disabled,
167
+ ...to ? { href: String(to), target: props.target, rel: externalRel, isExternal: true } : {},
168
+ active: active ?? false
169
+ }"
170
+ />
171
+ </Slot>
172
+ <ULinkBase
173
+ v-else
174
+ v-bind="{
175
+ ...$attrs,
176
+ as,
177
+ type,
178
+ disabled,
179
+ ...to ? { href: String(to), target: props.target, rel: externalRel, isExternal: true } : {}
180
+ }"
181
+ :class="resolveLinkClass()"
182
+ >
183
+ <slot :active="active ?? false" />
184
+ </ULinkBase>
129
185
  </template>
@@ -0,0 +1,228 @@
1
+ import type { VNode } from 'vue';
2
+ import type { ListboxRootEmits, ListboxRootProps } from 'reka-ui';
3
+ import type { AppConfig } from '@nuxt/schema';
4
+ import theme from '#build/ui/listbox';
5
+ import type { AvatarProps, ChipProps, IconProps, InputProps } from '../types';
6
+ import type { ModelModifiers, ApplyModifiers } from '../types/input';
7
+ import type { ArrayOrNested, GetItemKeys, GetModelValue, NestedItem } from '../types/utils';
8
+ import type { ComponentConfig } from '../types/tv';
9
+ type Listbox = ComponentConfig<typeof theme, AppConfig, 'listbox'>;
10
+ export interface ListboxItem {
11
+ label?: string;
12
+ description?: string;
13
+ /**
14
+ * @IconifyIcon
15
+ */
16
+ icon?: IconProps['name'];
17
+ avatar?: AvatarProps;
18
+ chip?: ChipProps;
19
+ /**
20
+ * The item type.
21
+ * @defaultValue 'item'
22
+ */
23
+ type?: 'label' | 'separator' | 'item';
24
+ disabled?: boolean;
25
+ onSelect?: (e: Event) => void;
26
+ class?: any;
27
+ ui?: Pick<Listbox['slots'], 'label' | 'separator' | 'item' | 'itemLeadingIcon' | 'itemLeadingAvatar' | 'itemLeadingAvatarSize' | 'itemLeadingChip' | 'itemLeadingChipSize' | 'itemWrapper' | 'itemLabel' | 'itemDescription' | 'itemTrailing' | 'itemTrailingIcon'>;
28
+ [key: string]: any;
29
+ }
30
+ export interface ListboxProps<T extends ArrayOrNested<ListboxItem> = ArrayOrNested<ListboxItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false, Mod extends Omit<ModelModifiers, 'lazy'> = Omit<ModelModifiers, 'lazy'>> extends Pick<ListboxRootProps, 'by' | 'disabled' | 'highlightOnHover' | 'name' | 'orientation' | 'required' | 'selectionBehavior'> {
31
+ id?: string;
32
+ /**
33
+ * The element or component this component should render as.
34
+ * @defaultValue 'div'
35
+ */
36
+ as?: any;
37
+ /**
38
+ * @defaultValue 'md'
39
+ */
40
+ size?: Listbox['variants']['size'];
41
+ /**
42
+ * The items to display in the list.
43
+ */
44
+ items?: T;
45
+ /**
46
+ * The controlled value of the Listbox. Can be bound with `v-model`.
47
+ */
48
+ modelValue?: ApplyModifiers<GetModelValue<T, VK, M>, Mod>;
49
+ modelModifiers?: Mod;
50
+ /**
51
+ * The default value when not controlled.
52
+ */
53
+ defaultValue?: ApplyModifiers<GetModelValue<T, VK, M>, Mod>;
54
+ /**
55
+ * Whether multiple items can be selected.
56
+ * @defaultValue false
57
+ */
58
+ multiple?: M & boolean;
59
+ /**
60
+ * When `items` is an array of objects, select the field to use as the value instead of the object itself.
61
+ * @defaultValue undefined
62
+ */
63
+ valueKey?: VK;
64
+ /**
65
+ * The key used to get the label from the item.
66
+ * @defaultValue 'label'
67
+ */
68
+ labelKey?: GetItemKeys<T>;
69
+ /**
70
+ * The key used to get the description from the item.
71
+ * @defaultValue 'description'
72
+ */
73
+ descriptionKey?: GetItemKeys<T>;
74
+ /**
75
+ * Whether the list is in a loading state.
76
+ */
77
+ loading?: boolean;
78
+ /**
79
+ * The icon displayed when loading.
80
+ * @defaultValue appConfig.ui.icons.loading
81
+ * @IconifyIcon
82
+ */
83
+ loadingIcon?: IconProps['name'];
84
+ /**
85
+ * Whether to display a filter input or not.
86
+ * Can be an object to pass additional props to the input.
87
+ * `{ placeholder: 'Search...', variant: 'none' }`{lang="ts-type"}
88
+ * @defaultValue false
89
+ */
90
+ filter?: boolean | Omit<InputProps, 'modelValue' | 'defaultValue'>;
91
+ /**
92
+ * The fields to filter by.
93
+ * @defaultValue [labelKey]
94
+ */
95
+ filterFields?: string[];
96
+ /**
97
+ * When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
98
+ * @defaultValue false
99
+ */
100
+ ignoreFilter?: boolean;
101
+ /**
102
+ * The icon displayed when an item is selected.
103
+ * @defaultValue appConfig.ui.icons.check
104
+ * @IconifyIcon
105
+ */
106
+ selectedIcon?: IconProps['name'];
107
+ /**
108
+ * Enable virtualization for large lists.
109
+ * @defaultValue false
110
+ */
111
+ virtualize?: boolean | {
112
+ /**
113
+ * Number of items rendered outside the visible area
114
+ * @defaultValue 12
115
+ */
116
+ overscan?: number;
117
+ /**
118
+ * Estimated size (in px) of each item, or a function that returns the size for a given index
119
+ * @defaultValue 32
120
+ */
121
+ estimateSize?: number | ((index: number) => number);
122
+ };
123
+ /** Highlight the ring color like a focus state. */
124
+ highlight?: boolean;
125
+ autofocus?: boolean;
126
+ autofocusDelay?: number;
127
+ class?: any;
128
+ ui?: Listbox['slots'];
129
+ }
130
+ export type ListboxEmits<T extends ArrayOrNested<ListboxItem> = ArrayOrNested<ListboxItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false, Mod extends Omit<ModelModifiers, 'lazy'> = Omit<ModelModifiers, 'lazy'>> = Pick<ListboxRootEmits, 'entryFocus' | 'highlight' | 'leave'> & {
131
+ 'change': [event: Event];
132
+ 'update:modelValue': [value: ApplyModifiers<GetModelValue<T, VK, M>, Mod>];
133
+ };
134
+ type SlotProps<T> = (props: {
135
+ item: T;
136
+ index: number;
137
+ ui: Listbox['ui'];
138
+ }) => VNode[];
139
+ export type ListboxSlots<T extends ArrayOrNested<ListboxItem> = ArrayOrNested<ListboxItem>> = {
140
+ 'loading'?(props?: {}): VNode[];
141
+ 'empty'?(props: {
142
+ searchTerm: string;
143
+ }): VNode[];
144
+ 'item'?: SlotProps<NestedItem<T>>;
145
+ 'item-leading'?: SlotProps<NestedItem<T>>;
146
+ 'item-label'?(props: {
147
+ item: NestedItem<T>;
148
+ index: number;
149
+ }): VNode[];
150
+ 'item-description'?(props: {
151
+ item: NestedItem<T>;
152
+ index: number;
153
+ }): VNode[];
154
+ 'item-trailing'?: SlotProps<NestedItem<T>>;
155
+ };
156
+ declare const _default: typeof __VLS_export;
157
+ export default _default;
158
+ declare const __VLS_export: <T extends ArrayOrNested<ListboxItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false, Mod extends Omit<ModelModifiers, "lazy"> = Omit<ModelModifiers, "lazy">>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
159
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<(ListboxProps<T, VK, M, Mod> & {
160
+ searchTerm?: string;
161
+ }) & {
162
+ onChange?: ((event: Event) => any) | undefined;
163
+ onHighlight?: ((payload: {
164
+ ref: HTMLElement;
165
+ value: import("reka-ui").AcceptableValue;
166
+ } | undefined) => any) | undefined;
167
+ "onUpdate:modelValue"?: ((value: Mod extends {
168
+ number: true;
169
+ } ? (Mod extends {
170
+ optional: true;
171
+ } ? (Mod extends {
172
+ nullable: true;
173
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
174
+ nullable: true;
175
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) extends infer T_1 ? T_1 extends (Mod extends {
176
+ optional: true;
177
+ } ? (Mod extends {
178
+ nullable: true;
179
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
180
+ nullable: true;
181
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) ? T_1 extends string ? number | T_1 : T_1 : never : never : Mod extends {
182
+ optional: true;
183
+ } ? (Mod extends {
184
+ nullable: true;
185
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
186
+ nullable: true;
187
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) => any) | undefined;
188
+ "onUpdate:searchTerm"?: ((value: string) => any) | undefined;
189
+ onEntryFocus?: ((event: CustomEvent<any>) => any) | undefined;
190
+ onLeave?: ((event: Event) => any) | undefined;
191
+ }> & (typeof globalThis extends {
192
+ __VLS_PROPS_FALLBACK: infer P;
193
+ } ? P : {});
194
+ expose: (exposed: {}) => void;
195
+ attrs: any;
196
+ slots: ListboxSlots<T>;
197
+ emit: (((evt: "change", event: Event) => void) & ((evt: "highlight", payload: {
198
+ ref: HTMLElement;
199
+ value: import("reka-ui").AcceptableValue;
200
+ } | undefined) => void) & ((evt: "update:modelValue", value: Mod extends {
201
+ number: true;
202
+ } ? (Mod extends {
203
+ optional: true;
204
+ } ? (Mod extends {
205
+ nullable: true;
206
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
207
+ nullable: true;
208
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) extends infer T_1 ? T_1 extends (Mod extends {
209
+ optional: true;
210
+ } ? (Mod extends {
211
+ nullable: true;
212
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
213
+ nullable: true;
214
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) ? T_1 extends string ? number | T_1 : T_1 : never : never : Mod extends {
215
+ optional: true;
216
+ } ? (Mod extends {
217
+ nullable: true;
218
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) | undefined : Mod extends {
219
+ nullable: true;
220
+ } ? GetModelValue<T, VK, M, undefined> | null : GetModelValue<T, VK, M, undefined>) => void) & ((evt: "entryFocus", event: CustomEvent<any>) => void) & ((evt: "leave", event: Event) => void)) & ((event: "update:searchTerm", value: string) => void);
221
+ }>) => import("vue").VNode & {
222
+ __ctx?: Awaited<typeof __VLS_setup>;
223
+ };
224
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
225
+ [K in keyof T]: T[K];
226
+ } : {
227
+ [K in keyof T as K]: T[K];
228
+ }) & {};
@@ -0,0 +1,242 @@
1
+ <script>
2
+ import { computed, toRaw, toRef } from "vue";
3
+ import theme from "#build/ui/listbox";
4
+ </script>
5
+
6
+ <script setup>
7
+ import { ListboxRoot, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxVirtualizer, ListboxItem as RekaListboxItem, ListboxItemIndicator, ListboxFilter, useForwardPropsEmits } from "reka-ui";
8
+ import { createReusableTemplate, reactivePick } from "@vueuse/core";
9
+ import { defu } from "defu";
10
+ import { useAppConfig } from "#imports";
11
+ import { useComponentUI } from "../composables/useComponentUI";
12
+ import { useFilter } from "../composables/useFilter";
13
+ import { useFormField } from "../composables/useFormField";
14
+ import { useLocale } from "../composables/useLocale";
15
+ import { get, isArrayOfArray, looseToNumber } from "../utils";
16
+ import { getEstimateSize } from "../utils/virtualizer";
17
+ import { tv } from "../utils/tv";
18
+ import UIcon from "./Icon.vue";
19
+ import UAvatar from "./Avatar.vue";
20
+ import UChip from "./Chip.vue";
21
+ import UInput from "./Input.vue";
22
+ defineOptions({ inheritAttrs: false });
23
+ const props = defineProps({
24
+ id: { type: String, required: false },
25
+ as: { type: null, required: false },
26
+ size: { type: null, required: false },
27
+ items: { type: null, required: false },
28
+ modelValue: { type: null, required: false },
29
+ modelModifiers: { type: null, required: false },
30
+ defaultValue: { type: null, required: false },
31
+ multiple: { type: Boolean, required: false },
32
+ valueKey: { type: null, required: false },
33
+ labelKey: { type: null, required: false, default: "label" },
34
+ descriptionKey: { type: null, required: false, default: "description" },
35
+ loading: { type: Boolean, required: false },
36
+ loadingIcon: { type: null, required: false },
37
+ filter: { type: [Boolean, Object], required: false, default: false },
38
+ filterFields: { type: Array, required: false },
39
+ ignoreFilter: { type: Boolean, required: false },
40
+ selectedIcon: { type: null, required: false },
41
+ virtualize: { type: [Boolean, Object], required: false, default: false },
42
+ highlight: { type: Boolean, required: false },
43
+ autofocus: { type: Boolean, required: false },
44
+ autofocusDelay: { type: Number, required: false, default: 0 },
45
+ class: { type: null, required: false },
46
+ ui: { type: Object, required: false },
47
+ by: { type: [String, Function], required: false },
48
+ disabled: { type: Boolean, required: false },
49
+ highlightOnHover: { type: Boolean, required: false, default: true },
50
+ name: { type: String, required: false },
51
+ orientation: { type: String, required: false },
52
+ required: { type: Boolean, required: false },
53
+ selectionBehavior: { type: String, required: false }
54
+ });
55
+ const emits = defineEmits(["entryFocus", "highlight", "leave", "change", "update:modelValue"]);
56
+ const slots = defineSlots();
57
+ const searchTerm = defineModel("searchTerm", { type: String, ...{ default: "" } });
58
+ const { t } = useLocale();
59
+ const appConfig = useAppConfig();
60
+ const uiProp = useComponentUI("listbox", props);
61
+ const { filterGroups } = useFilter();
62
+ const rootProps = useForwardPropsEmits(reactivePick(props, "as", "modelValue", "defaultValue", "multiple", "selectionBehavior", "highlightOnHover", "by", "orientation", "required"), emits);
63
+ const virtualizerProps = toRef(() => {
64
+ if (!props.virtualize) return false;
65
+ return defu(typeof props.virtualize === "boolean" ? {} : props.virtualize, {
66
+ estimateSize: getEstimateSize(filteredItems.value, size.value || "md", props.descriptionKey, !!slots["item-description"])
67
+ });
68
+ });
69
+ const inputProps = toRef(() => defu(typeof props.filter === "object" ? props.filter : {}, { placeholder: t("listbox.search"), variant: "none" }));
70
+ const { emitFormChange, emitFormInput, name, size, color, id, highlight, disabled, ariaAttrs } = useFormField(props, { bind: false });
71
+ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate({
72
+ props: {
73
+ item: {
74
+ type: Object,
75
+ required: true
76
+ },
77
+ index: {
78
+ type: Number,
79
+ required: false
80
+ }
81
+ }
82
+ });
83
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.listbox || {} })({
84
+ color: color.value,
85
+ size: size.value,
86
+ highlight: highlight.value,
87
+ disabled: disabled.value,
88
+ virtualize: !!props.virtualize
89
+ }));
90
+ function onUpdate(value) {
91
+ if (toRaw(props.modelValue) === value) {
92
+ return;
93
+ }
94
+ if (props.modelModifiers?.trim && (typeof value === "string" || value === null || value === void 0)) {
95
+ value = value?.trim() ?? null;
96
+ }
97
+ if (props.modelModifiers?.number) {
98
+ value = looseToNumber(value);
99
+ }
100
+ if (props.modelModifiers?.nullable) {
101
+ value ??= null;
102
+ }
103
+ if (props.modelModifiers?.optional && !props.modelModifiers?.nullable && value !== null) {
104
+ value ??= void 0;
105
+ }
106
+ const event = new Event("change", { target: { value } });
107
+ emits("change", event);
108
+ emitFormChange();
109
+ emitFormInput();
110
+ }
111
+ function onSelect(e, item) {
112
+ if (item.disabled) {
113
+ e.preventDefault();
114
+ return;
115
+ }
116
+ item.onSelect?.(e);
117
+ }
118
+ const groups = computed(
119
+ () => props.items?.length ? isArrayOfArray(props.items) ? props.items : [props.items] : []
120
+ );
121
+ function isStructuralItem(item) {
122
+ return !!item.type && ["label", "separator"].includes(item.type);
123
+ }
124
+ const filteredGroups = computed(() => {
125
+ if (props.ignoreFilter || !searchTerm.value) {
126
+ return groups.value;
127
+ }
128
+ const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey];
129
+ return filterGroups(groups.value, searchTerm.value, {
130
+ fields,
131
+ isStructural: isStructuralItem
132
+ });
133
+ });
134
+ const filteredItems = computed(() => filteredGroups.value.flatMap((group) => group));
135
+ </script>
136
+
137
+ <template>
138
+ <DefineItemTemplate v-slot="{ item, index }">
139
+ <ListboxGroupLabel v-if="item.type === 'label'" data-slot="label" :class="ui.label({ class: [uiProp?.label, item.ui?.label, item.class] })">
140
+ {{ get(item, props.labelKey) }}
141
+ </ListboxGroupLabel>
142
+
143
+ <div v-else-if="item.type === 'separator'" role="separator" data-slot="separator" :class="ui.separator({ class: [uiProp?.separator, item.ui?.separator, item.class] })" />
144
+
145
+ <RekaListboxItem
146
+ v-else
147
+ :value="props.valueKey ? get(item, props.valueKey) : item"
148
+ :disabled="item.disabled"
149
+ data-slot="item"
150
+ :class="ui.item({ class: [uiProp?.item, item.ui?.item, item.class] })"
151
+ @select="onSelect($event, item)"
152
+ >
153
+ <slot name="item" :item="item" :index="index" :ui="ui">
154
+ <slot name="item-leading" :item="item" :index="index" :ui="ui">
155
+ <UIcon v-if="item.icon" :name="item.icon" data-slot="itemLeadingIcon" :class="ui.itemLeadingIcon({ class: [uiProp?.itemLeadingIcon, item.ui?.itemLeadingIcon] })" />
156
+ <UAvatar v-else-if="item.avatar" :size="item.ui?.itemLeadingAvatarSize || uiProp?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()" v-bind="item.avatar" data-slot="itemLeadingAvatar" :class="ui.itemLeadingAvatar({ class: [uiProp?.itemLeadingAvatar, item.ui?.itemLeadingAvatar] })" />
157
+ <UChip
158
+ v-else-if="item.chip"
159
+ :size="item.ui?.itemLeadingChipSize || uiProp?.itemLeadingChipSize || ui.itemLeadingChipSize()"
160
+ inset
161
+ standalone
162
+ v-bind="item.chip"
163
+ data-slot="itemLeadingChip"
164
+ :class="ui.itemLeadingChip({ class: [uiProp?.itemLeadingChip, item.ui?.itemLeadingChip] })"
165
+ />
166
+ </slot>
167
+
168
+ <span v-if="get(item, props.labelKey) || get(item, props.descriptionKey) || !!slots['item-label'] || !!slots['item-description']" data-slot="itemWrapper" :class="ui.itemWrapper({ class: [uiProp?.itemWrapper, item.ui?.itemWrapper] })">
169
+ <span v-if="get(item, props.labelKey) || !!slots['item-label']" data-slot="itemLabel" :class="ui.itemLabel({ class: [uiProp?.itemLabel, item.ui?.itemLabel] })">
170
+ <slot name="item-label" :item="item" :index="index">
171
+ {{ get(item, props.labelKey) }}
172
+ </slot>
173
+ </span>
174
+
175
+ <span v-if="get(item, props.descriptionKey) || !!slots['item-description']" data-slot="itemDescription" :class="ui.itemDescription({ class: [uiProp?.itemDescription, item.ui?.itemDescription] })">
176
+ <slot name="item-description" :item="item" :index="index">
177
+ {{ get(item, props.descriptionKey) }}
178
+ </slot>
179
+ </span>
180
+ </span>
181
+
182
+ <span data-slot="itemTrailing" :class="ui.itemTrailing({ class: [uiProp?.itemTrailing, item.ui?.itemTrailing] })">
183
+ <slot name="item-trailing" :item="item" :index="index" :ui="ui" />
184
+
185
+ <ListboxItemIndicator as-child>
186
+ <UIcon :name="selectedIcon || appConfig.ui.icons.check" data-slot="itemTrailingIcon" :class="ui.itemTrailingIcon({ class: [uiProp?.itemTrailingIcon, item.ui?.itemTrailingIcon] })" />
187
+ </ListboxItemIndicator>
188
+ </span>
189
+ </slot>
190
+ </RekaListboxItem>
191
+ </DefineItemTemplate>
192
+
193
+ <ListboxRoot
194
+ :id="id"
195
+ v-bind="{ ...rootProps, ...$attrs, ...ariaAttrs }"
196
+ :disabled="disabled"
197
+ :name="name"
198
+ data-slot="root"
199
+ :class="ui.root({ class: [uiProp?.root, props.class] })"
200
+ @update:model-value="onUpdate"
201
+ >
202
+ <ListboxFilter v-if="filter" v-model="searchTerm" as-child>
203
+ <UInput
204
+ :autofocus="autofocus"
205
+ :autofocus-delay="autofocusDelay"
206
+ :size="size"
207
+ v-bind="inputProps"
208
+ data-slot="input"
209
+ :class="ui.input({ class: uiProp?.input })"
210
+ />
211
+ </ListboxFilter>
212
+
213
+ <ListboxContent data-slot="content" :class="ui.content({ class: uiProp?.content })">
214
+ <div v-if="loading" data-slot="loading" :class="ui.loading({ class: uiProp?.loading })">
215
+ <slot name="loading">
216
+ <UIcon :name="loadingIcon || appConfig.ui.icons.loading" data-slot="loadingIcon" :class="ui.loadingIcon({ class: uiProp?.loadingIcon })" />
217
+ </slot>
218
+ </div>
219
+ <div v-else-if="!filteredItems.length" data-slot="empty" :class="ui.empty({ class: uiProp?.empty })">
220
+ <slot name="empty" :search-term="searchTerm">
221
+ {{ searchTerm ? t("listbox.noMatch", { searchTerm }) : t("listbox.noData") }}
222
+ </slot>
223
+ </div>
224
+
225
+ <ListboxVirtualizer
226
+ v-else-if="!!virtualize"
227
+ v-slot="{ option: item, virtualItem }"
228
+ :options="filteredItems"
229
+ :text-content="(item2) => get(item2, props.labelKey)"
230
+ v-bind="virtualizerProps"
231
+ >
232
+ <ReuseItemTemplate :item="item" :index="virtualItem.index" />
233
+ </ListboxVirtualizer>
234
+
235
+ <template v-else>
236
+ <ListboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" data-slot="group" :class="ui.group({ class: uiProp?.group })">
237
+ <ReuseItemTemplate v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`" :item="item" :index="index" />
238
+ </ListboxGroup>
239
+ </template>
240
+ </ListboxContent>
241
+ </ListboxRoot>
242
+ </template>