@nuxt/ui 3.1.2 → 3.2.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 (150) hide show
  1. package/.nuxt/ui/avatar.ts +1 -1
  2. package/.nuxt/ui/card.ts +1 -1
  3. package/.nuxt/ui/carousel.ts +1 -1
  4. package/.nuxt/ui/checkbox-group.ts +1 -1
  5. package/.nuxt/ui/command-palette.ts +1 -0
  6. package/.nuxt/ui/context-menu.ts +2 -1
  7. package/.nuxt/ui/drawer.ts +1 -1
  8. package/.nuxt/ui/dropdown-menu.ts +2 -1
  9. package/.nuxt/ui/index.ts +2 -0
  10. package/.nuxt/ui/input-menu.ts +26 -12
  11. package/.nuxt/ui/input-tags.ts +310 -0
  12. package/.nuxt/ui/modal.ts +1 -1
  13. package/.nuxt/ui/navigation-menu.ts +43 -34
  14. package/.nuxt/ui/radio-group.ts +1 -1
  15. package/.nuxt/ui/select-menu.ts +13 -8
  16. package/.nuxt/ui/select.ts +13 -8
  17. package/.nuxt/ui/table.ts +3 -2
  18. package/.nuxt/ui/timeline.ts +321 -0
  19. package/.nuxt/ui/toaster.ts +1 -1
  20. package/.nuxt/ui/tooltip.ts +1 -1
  21. package/.nuxt/ui.css +4 -1
  22. package/cli/templates.mjs +7 -4
  23. package/dist/module.json +1 -1
  24. package/dist/module.mjs +2 -2
  25. package/dist/runtime/components/Alert.vue +0 -1
  26. package/dist/runtime/components/Avatar.vue +10 -2
  27. package/dist/runtime/components/Avatar.vue.d.ts +2 -0
  28. package/dist/runtime/components/Calendar.vue +4 -1
  29. package/dist/runtime/components/Carousel.vue +1 -2
  30. package/dist/runtime/components/Checkbox.vue +1 -1
  31. package/dist/runtime/components/CommandPalette.vue +80 -16
  32. package/dist/runtime/components/CommandPalette.vue.d.ts +32 -8
  33. package/dist/runtime/components/ContextMenuContent.vue +58 -56
  34. package/dist/runtime/components/ContextMenuContent.vue.d.ts +4 -20
  35. package/dist/runtime/components/Drawer.vue +15 -1
  36. package/dist/runtime/components/DropdownMenuContent.vue +60 -58
  37. package/dist/runtime/components/DropdownMenuContent.vue.d.ts +4 -20
  38. package/dist/runtime/components/Form.vue +6 -6
  39. package/dist/runtime/components/Form.vue.d.ts +12 -11
  40. package/dist/runtime/components/Input.vue +1 -0
  41. package/dist/runtime/components/Input.vue.d.ts +7 -0
  42. package/dist/runtime/components/InputMenu.vue +3 -3
  43. package/dist/runtime/components/InputMenu.vue.d.ts +4 -4
  44. package/dist/runtime/components/InputNumber.vue +3 -2
  45. package/dist/runtime/components/InputNumber.vue.d.ts +1 -1
  46. package/dist/runtime/components/InputTags.vue +159 -0
  47. package/dist/runtime/components/InputTags.vue.d.ts +183 -0
  48. package/dist/runtime/components/Modal.vue +16 -15
  49. package/dist/runtime/components/Modal.vue.d.ts +17 -4
  50. package/dist/runtime/components/NavigationMenu.vue +85 -26
  51. package/dist/runtime/components/NavigationMenu.vue.d.ts +30 -17
  52. package/dist/runtime/components/OverlayProvider.vue +2 -2
  53. package/dist/runtime/components/Pagination.vue +1 -1
  54. package/dist/runtime/components/Pagination.vue.d.ts +0 -1
  55. package/dist/runtime/components/PinInput.vue +4 -3
  56. package/dist/runtime/components/PinInput.vue.d.ts +22 -19
  57. package/dist/runtime/components/Popover.vue +4 -0
  58. package/dist/runtime/components/Popover.vue.d.ts +1 -0
  59. package/dist/runtime/components/Progress.vue +2 -1
  60. package/dist/runtime/components/Progress.vue.d.ts +1 -1
  61. package/dist/runtime/components/RadioGroup.vue +2 -2
  62. package/dist/runtime/components/Select.vue +26 -7
  63. package/dist/runtime/components/Select.vue.d.ts +107 -1
  64. package/dist/runtime/components/SelectMenu.vue +27 -8
  65. package/dist/runtime/components/SelectMenu.vue.d.ts +105 -1
  66. package/dist/runtime/components/Slideover.vue +23 -15
  67. package/dist/runtime/components/Slideover.vue.d.ts +17 -4
  68. package/dist/runtime/components/Table.vue +7 -1
  69. package/dist/runtime/components/Table.vue.d.ts +10 -8
  70. package/dist/runtime/components/Tabs.vue +13 -2
  71. package/dist/runtime/components/Tabs.vue.d.ts +4 -1
  72. package/dist/runtime/components/Textarea.vue +4 -1
  73. package/dist/runtime/components/Textarea.vue.d.ts +11 -4
  74. package/dist/runtime/components/Timeline.vue +92 -0
  75. package/dist/runtime/components/Timeline.vue.d.ts +70 -0
  76. package/dist/runtime/components/Toast.vue +4 -5
  77. package/dist/runtime/components/Tree.vue +3 -2
  78. package/dist/runtime/components/Tree.vue.d.ts +1 -1
  79. package/dist/runtime/composables/defineLocale.d.ts +2 -0
  80. package/dist/runtime/composables/defineLocale.js +4 -0
  81. package/dist/runtime/composables/defineShortcuts.js +2 -2
  82. package/dist/runtime/composables/useOverlay.d.ts +2 -1
  83. package/dist/runtime/composables/useOverlay.js +7 -6
  84. package/dist/runtime/locale/ar.js +2 -1
  85. package/dist/runtime/locale/az.js +2 -1
  86. package/dist/runtime/locale/bg.js +2 -1
  87. package/dist/runtime/locale/bn.js +2 -1
  88. package/dist/runtime/locale/ca.js +2 -1
  89. package/dist/runtime/locale/ckb.js +2 -1
  90. package/dist/runtime/locale/cs.js +2 -1
  91. package/dist/runtime/locale/da.js +2 -1
  92. package/dist/runtime/locale/de.js +2 -1
  93. package/dist/runtime/locale/el.js +2 -1
  94. package/dist/runtime/locale/en.js +2 -1
  95. package/dist/runtime/locale/es.js +2 -1
  96. package/dist/runtime/locale/et.js +2 -1
  97. package/dist/runtime/locale/fa_ir.js +2 -1
  98. package/dist/runtime/locale/fi.js +2 -1
  99. package/dist/runtime/locale/fr.js +2 -1
  100. package/dist/runtime/locale/he.js +2 -1
  101. package/dist/runtime/locale/hi.js +2 -1
  102. package/dist/runtime/locale/hu.js +2 -1
  103. package/dist/runtime/locale/hy.js +2 -1
  104. package/dist/runtime/locale/id.js +2 -1
  105. package/dist/runtime/locale/index.d.ts +7 -2
  106. package/dist/runtime/locale/index.js +7 -2
  107. package/dist/runtime/locale/it.js +2 -1
  108. package/dist/runtime/locale/ja.js +2 -1
  109. package/dist/runtime/locale/kk.js +2 -1
  110. package/dist/runtime/locale/km.js +2 -1
  111. package/dist/runtime/locale/ko.js +2 -1
  112. package/dist/runtime/locale/ky.d.ts +3 -0
  113. package/dist/runtime/locale/ky.js +55 -0
  114. package/dist/runtime/locale/lb.d.ts +3 -0
  115. package/dist/runtime/locale/lb.js +55 -0
  116. package/dist/runtime/locale/lt.d.ts +3 -0
  117. package/dist/runtime/locale/lt.js +55 -0
  118. package/dist/runtime/locale/mn.d.ts +3 -0
  119. package/dist/runtime/locale/mn.js +55 -0
  120. package/dist/runtime/locale/ms.d.ts +3 -0
  121. package/dist/runtime/locale/ms.js +55 -0
  122. package/dist/runtime/locale/nb_no.js +2 -1
  123. package/dist/runtime/locale/nl.js +2 -1
  124. package/dist/runtime/locale/pl.js +2 -1
  125. package/dist/runtime/locale/pt.js +2 -1
  126. package/dist/runtime/locale/pt_br.js +2 -1
  127. package/dist/runtime/locale/ro.js +2 -1
  128. package/dist/runtime/locale/ru.js +2 -1
  129. package/dist/runtime/locale/sk.js +2 -1
  130. package/dist/runtime/locale/sl.js +2 -1
  131. package/dist/runtime/locale/sv.js +2 -1
  132. package/dist/runtime/locale/th.js +2 -1
  133. package/dist/runtime/locale/tj.js +2 -1
  134. package/dist/runtime/locale/tr.js +2 -1
  135. package/dist/runtime/locale/ug_cn.js +2 -1
  136. package/dist/runtime/locale/uk.js +2 -1
  137. package/dist/runtime/locale/ur.js +2 -1
  138. package/dist/runtime/locale/uz.js +2 -1
  139. package/dist/runtime/locale/vi.js +2 -1
  140. package/dist/runtime/locale/zh_cn.js +2 -1
  141. package/dist/runtime/locale/zh_tw.js +2 -1
  142. package/dist/runtime/types/form.d.ts +11 -10
  143. package/dist/runtime/types/index.d.ts +2 -0
  144. package/dist/runtime/types/index.js +2 -0
  145. package/dist/runtime/types/locale.d.ts +1 -0
  146. package/dist/runtime/types/utils.d.ts +3 -0
  147. package/dist/shared/{ui.BvUP-pSb.mjs → ui.BPVxSPGY.mjs} +332 -62
  148. package/dist/unplugin.mjs +1 -1
  149. package/dist/vite.mjs +1 -1
  150. package/package.json +35 -36
package/.nuxt/ui.css CHANGED
@@ -1,6 +1,6 @@
1
1
  @source "./ui";
2
2
 
3
- @theme default inline {
3
+ @theme static {
4
4
  --color-old-neutral-50: oklch(98.5% 0 0);
5
5
  --color-old-neutral-100: oklch(97% 0 0);
6
6
  --color-old-neutral-200: oklch(92.2% 0 0);
@@ -12,6 +12,9 @@
12
12
  --color-old-neutral-800: oklch(26.9% 0 0);
13
13
  --color-old-neutral-900: oklch(20.5% 0 0);
14
14
  --color-old-neutral-950: oklch(14.5% 0 0);
15
+ }
16
+
17
+ @theme default inline {
15
18
  --color-primary-50: var(--ui-color-primary-50);
16
19
  --color-primary-100: var(--ui-color-primary-100);
17
20
  --color-primary-200: var(--ui-color-primary-200);
package/cli/templates.mjs CHANGED
@@ -31,8 +31,9 @@ const component = ({ name, primitive, pro, prose, content }) => {
31
31
  ? `
32
32
  <script lang="ts">
33
33
  import type { AppConfig } from '@nuxt/schema'
34
+ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
34
35
  import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
35
- import type { ComponentConfig } from '../types/utils'
36
+ ${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
36
37
 
37
38
  type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
38
39
 
@@ -62,7 +63,7 @@ defineSlots<${upperName}Slots>()
62
63
 
63
64
  const appConfig = useAppConfig() as ${upperName}['AppConfig']
64
65
 
65
- const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
66
+ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })())
66
67
  </script>
67
68
 
68
69
  <template>
@@ -75,8 +76,9 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
75
76
  <script lang="ts">
76
77
  import type { ${upperName}RootProps, ${upperName}RootEmits } from 'reka-ui'
77
78
  import type { AppConfig } from '@nuxt/schema'
79
+ ${pro ? `import type { ComponentConfig } from '@nuxt/ui'` : ''}
78
80
  import theme from '#build/${path}/${prose ? 'prose/' : ''}${content ? 'content/' : ''}${kebabName}'
79
- import type { ComponentConfig } from '../types/utils'
81
+ ${!pro ? `import type { ComponentConfig } from '../types/utils'` : ''}
80
82
 
81
83
  type ${upperName} = ComponentConfig<typeof theme, AppConfig, ${upperName}${pro ? `, '${key}'` : ''}>
82
84
 
@@ -105,7 +107,7 @@ const appConfig = useAppConfig() as ${upperName}['AppConfig']
105
107
 
106
108
  const rootProps = useForwardPropsEmits(reactivePick(props), emits)
107
109
 
108
- const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName} || {}) })())
110
+ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.${pro ? 'uiPro' : 'ui'}?.${camelName} || {}) })())
109
111
  </script>
110
112
 
111
113
  <template>
@@ -186,6 +188,7 @@ links:${primitive
186
188
  - label: GitHub
187
189
  icon: i-simple-icons-github
188
190
  to: https://github.com/nuxt/${pro ? 'ui-pro' : 'ui'}/tree/v3/src/runtime/components/${upperName}.vue
191
+ navigation.badge: Soon
189
192
  ---
190
193
 
191
194
  ## Usage
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/ui",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "docs": "https://ui.nuxt.com/getting-started/installation/nuxt",
5
5
  "configKey": "ui",
6
6
  "compatibility": {
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defu } from 'defu';
2
2
  import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addComponentsDir, addImportsDir, hasNuxtModule, installModule } from '@nuxt/kit';
3
- import { d as defaultOptions, r as resolveColors, a as getDefaultUiConfig, b as addTemplates } from './shared/ui.BvUP-pSb.mjs';
3
+ import { d as defaultOptions, r as resolveColors, a as getDefaultUiConfig, b as addTemplates } from './shared/ui.BPVxSPGY.mjs';
4
4
  import '../dist/runtime/utils/index.js';
5
5
  import 'node:url';
6
6
  import 'scule';
@@ -8,7 +8,7 @@ import 'tailwindcss/colors';
8
8
  import 'knitwork';
9
9
 
10
10
  const name = "@nuxt/ui";
11
- const version = "3.1.2";
11
+ const version = "3.2.0";
12
12
 
13
13
  const module = defineNuxtModule({
14
14
  meta: {
@@ -75,7 +75,6 @@ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.alert || {} }
75
75
  <UButton
76
76
  v-if="close"
77
77
  :icon="closeIcon || appConfig.ui.icons.close"
78
- size="md"
79
78
  color="neutral"
80
79
  variant="link"
81
80
  :aria-label="t('alert.close')"
@@ -10,6 +10,7 @@ import ImageComponent from "#build/ui-image-component";
10
10
  import { useAvatarGroup } from "../composables/useAvatarGroup";
11
11
  import { tv } from "../utils/tv";
12
12
  import UIcon from "./Icon.vue";
13
+ import UChip from "./Chip.vue";
13
14
  defineOptions({ inheritAttrs: false });
14
15
  const props = defineProps({
15
16
  as: { type: null, required: false, default: "span" },
@@ -18,6 +19,7 @@ const props = defineProps({
18
19
  icon: { type: String, required: false },
19
20
  text: { type: String, required: false },
20
21
  size: { type: null, required: false },
22
+ chip: { type: [Boolean, Object], required: false },
21
23
  class: { type: null, required: false },
22
24
  style: { type: null, required: false },
23
25
  ui: { type: null, required: false }
@@ -51,7 +53,13 @@ function onError() {
51
53
  </script>
52
54
 
53
55
  <template>
54
- <Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })" :style="props.style">
56
+ <component
57
+ :is="props.chip ? UChip : Primitive"
58
+ :as="as"
59
+ v-bind="props.chip ? typeof props.chip === 'object' ? { inset: true, ...props.chip } : { inset: true } : {}"
60
+ :class="ui.root({ class: [props.ui?.root, props.class] })"
61
+ :style="props.style"
62
+ >
55
63
  <component
56
64
  :is="ImageComponent"
57
65
  v-if="src && !error"
@@ -71,5 +79,5 @@ function onError() {
71
79
  <span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || "\xA0" }}</span>
72
80
  </slot>
73
81
  </Slot>
74
- </Primitive>
82
+ </component>
75
83
  </template>
@@ -1,5 +1,6 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import theme from '#build/ui/avatar';
3
+ import type { ChipProps } from '../types';
3
4
  import type { ComponentConfig } from '../types/utils';
4
5
  type Avatar = ComponentConfig<typeof theme, AppConfig, 'avatar'>;
5
6
  export interface AvatarProps {
@@ -19,6 +20,7 @@ export interface AvatarProps {
19
20
  * @defaultValue 'md'
20
21
  */
21
22
  size?: Avatar['variants']['size'];
23
+ chip?: boolean | ChipProps;
22
24
  class?: any;
23
25
  style?: any;
24
26
  ui?: Avatar['slots'];
@@ -36,6 +36,7 @@ const props = defineProps({
36
36
  allowNonContiguousRanges: { type: Boolean, required: false },
37
37
  pagedNavigation: { type: Boolean, required: false },
38
38
  preventDeselect: { type: Boolean, required: false },
39
+ maximumDays: { type: Number, required: false },
39
40
  weekStartsOn: { type: Number, required: false },
40
41
  weekdayFormat: { type: String, required: false },
41
42
  fixedWeeks: { type: Boolean, required: false, default: true },
@@ -49,7 +50,9 @@ const props = defineProps({
49
50
  isDateUnavailable: { type: Function, required: false },
50
51
  isDateHighlightable: { type: Function, required: false },
51
52
  nextPage: { type: Function, required: false },
52
- prevPage: { type: Function, required: false }
53
+ prevPage: { type: Function, required: false },
54
+ disableDaysOutsideCurrentView: { type: Boolean, required: false },
55
+ fixedDate: { type: String, required: false }
53
56
  });
54
57
  const emits = defineEmits(["update:modelValue", "update:placeholder", "update:startValue"]);
55
58
  defineSlots();
@@ -176,7 +176,6 @@ defineExpose({
176
176
  <UButton
177
177
  :disabled="!canScrollPrev"
178
178
  :icon="prevIcon"
179
- size="md"
180
179
  color="neutral"
181
180
  variant="outline"
182
181
  :aria-label="t('carousel.prev')"
@@ -187,7 +186,6 @@ defineExpose({
187
186
  <UButton
188
187
  :disabled="!canScrollNext"
189
188
  :icon="nextIcon"
190
- size="md"
191
189
  color="neutral"
192
190
  variant="outline"
193
191
  :aria-label="t('carousel.next')"
@@ -202,6 +200,7 @@ defineExpose({
202
200
  <button
203
201
  :aria-label="t('carousel.goto', { slide: index + 1 })"
204
202
  :class="ui.dot({ class: props.ui?.dot, active: selectedIndex === index })"
203
+ :data-state="selectedIndex === index ? 'active' : void 0"
205
204
  @click="scrollTo(index)"
206
205
  />
207
206
  </template>
@@ -26,7 +26,7 @@ const props = defineProps({
26
26
  disabled: { type: Boolean, required: false },
27
27
  required: { type: Boolean, required: false },
28
28
  name: { type: String, required: false },
29
- value: { type: [String, Number, Object, null], required: false },
29
+ value: { type: null, required: false },
30
30
  id: { type: String, required: false },
31
31
  defaultValue: { type: [Boolean, String], required: false }
32
32
  });
@@ -3,7 +3,7 @@ import theme from "#build/ui/command-palette";
3
3
  </script>
4
4
 
5
5
  <script setup>
6
- import { computed } from "vue";
6
+ import { computed, ref, useTemplateRef } from "vue";
7
7
  import { ListboxRoot, ListboxFilter, ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, useForwardProps, useForwardPropsEmits } from "reka-ui";
8
8
  import { defu } from "defu";
9
9
  import { reactivePick } from "@vueuse/core";
@@ -26,10 +26,13 @@ const props = defineProps({
26
26
  as: { type: null, required: false },
27
27
  icon: { type: String, required: false },
28
28
  selectedIcon: { type: String, required: false },
29
+ trailingIcon: { type: String, required: false },
29
30
  placeholder: { type: String, required: false },
30
31
  autofocus: { type: Boolean, required: false, default: true },
31
32
  close: { type: [Boolean, Object], required: false },
32
33
  closeIcon: { type: String, required: false },
34
+ back: { type: [Boolean, Object], required: false, default: true },
35
+ backIcon: { type: String, required: false },
33
36
  groups: { type: Array, required: false },
34
37
  fuse: { type: Object, required: false },
35
38
  labelKey: { type: String, required: false, default: "label" },
@@ -40,6 +43,7 @@ const props = defineProps({
40
43
  modelValue: { type: null, required: false, default: "" },
41
44
  defaultValue: { type: null, required: false },
42
45
  highlightOnHover: { type: Boolean, required: false },
46
+ selectionBehavior: { type: String, required: false },
43
47
  loading: { type: Boolean, required: false },
44
48
  loadingIcon: { type: String, required: false }
45
49
  });
@@ -48,7 +52,7 @@ const slots = defineSlots();
48
52
  const searchTerm = defineModel("searchTerm", { type: String, ...{ default: "" } });
49
53
  const { t } = useLocale();
50
54
  const appConfig = useAppConfig();
51
- const rootProps = useForwardPropsEmits(reactivePick(props, "as", "disabled", "multiple", "modelValue", "defaultValue", "highlightOnHover"), emits);
55
+ const rootProps = useForwardPropsEmits(reactivePick(props, "as", "disabled", "multiple", "modelValue", "defaultValue", "highlightOnHover", "selectionBehavior"), emits);
52
56
  const inputProps = useForwardProps(reactivePick(props, "loading", "loadingIcon"));
53
57
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.commandPalette || {} })());
54
58
  const fuse = computed(() => defu({}, props.fuse, {
@@ -60,7 +64,10 @@ const fuse = computed(() => defu({}, props.fuse, {
60
64
  resultLimit: 12,
61
65
  matchAllWhenSearchEmpty: true
62
66
  }));
63
- const items = computed(() => props.groups?.filter((group) => {
67
+ const history = ref([]);
68
+ const placeholder = computed(() => history.value[history.value.length - 1]?.placeholder || props.placeholder || t("commandPalette.placeholder"));
69
+ const groups = computed(() => history.value?.length ? [history.value[history.value.length - 1]] : props.groups);
70
+ const items = computed(() => groups.value?.filter((group) => {
64
71
  if (!group.id) {
65
72
  console.warn(`[@nuxt/ui] CommandPalette group is missing an \`id\` property`);
66
73
  return false;
@@ -69,7 +76,7 @@ const items = computed(() => props.groups?.filter((group) => {
69
76
  return false;
70
77
  }
71
78
  return true;
72
- }).flatMap((group) => group.items?.map((item) => ({ ...item, group: group.id })) || []) || []);
79
+ })?.flatMap((group) => group.items?.map((item) => ({ ...item, group: group.id })) || []) || []);
73
80
  const { results: fuseResults } = useFuse(searchTerm, items, fuse);
74
81
  function getGroupWithItems(group, items2) {
75
82
  if (group?.postFilter && typeof group.postFilter === "function") {
@@ -86,7 +93,7 @@ function getGroupWithItems(group, items2) {
86
93
  })
87
94
  };
88
95
  }
89
- const groups = computed(() => {
96
+ const filteredGroups = computed(() => {
90
97
  const groupsById = fuseResults.value.reduce((acc, result) => {
91
98
  const { item, matches } = result;
92
99
  if (!item.group) {
@@ -97,37 +104,87 @@ const groups = computed(() => {
97
104
  return acc;
98
105
  }, {});
99
106
  const fuseGroups = Object.entries(groupsById).map(([id, items2]) => {
100
- const group = props.groups?.find((group2) => group2.id === id);
107
+ const group = groups.value?.find((group2) => group2.id === id);
101
108
  if (!group) {
102
109
  return;
103
110
  }
104
111
  return getGroupWithItems(group, items2);
105
112
  }).filter((group) => !!group);
106
- const nonFuseGroups = props.groups?.map((group, index) => ({ ...group, index }))?.filter((group) => group.ignoreFilter && group.items?.length)?.map((group) => ({ ...getGroupWithItems(group, group.items || []), index: group.index })) || [];
113
+ const nonFuseGroups = groups.value?.map((group, index) => ({ ...group, index }))?.filter((group) => group.ignoreFilter && group.items?.length)?.map((group) => ({ ...getGroupWithItems(group, group.items || []), index: group.index })) || [];
107
114
  return nonFuseGroups.reduce((acc, group) => {
108
115
  acc.splice(group.index, 0, group);
109
116
  return acc;
110
117
  }, [...fuseGroups]);
111
118
  });
119
+ const listboxRootRef = useTemplateRef("listboxRootRef");
120
+ function navigate(item) {
121
+ if (!item.children?.length) {
122
+ return;
123
+ }
124
+ history.value.push({
125
+ id: `history-${history.value.length}`,
126
+ label: item.label,
127
+ slot: item.slot,
128
+ placeholder: item.placeholder,
129
+ items: item.children
130
+ });
131
+ searchTerm.value = "";
132
+ listboxRootRef.value?.highlightFirstItem();
133
+ }
134
+ function navigateBack() {
135
+ if (!history.value.length) {
136
+ return;
137
+ }
138
+ history.value.pop();
139
+ searchTerm.value = "";
140
+ listboxRootRef.value?.highlightFirstItem();
141
+ }
142
+ function onBackspace() {
143
+ if (!searchTerm.value) {
144
+ navigateBack();
145
+ }
146
+ }
147
+ function onSelect(e, item) {
148
+ if (item.children?.length) {
149
+ e.preventDefault();
150
+ navigate(item);
151
+ } else {
152
+ item.onSelect?.(e);
153
+ }
154
+ }
112
155
  </script>
113
156
 
114
157
  <template>
115
- <ListboxRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
158
+ <ListboxRoot v-bind="rootProps" ref="listboxRootRef" :class="ui.root({ class: [props.ui?.root, props.class] })">
116
159
  <ListboxFilter v-model="searchTerm" as-child>
117
160
  <UInput
118
- :placeholder="placeholder || t('commandPalette.placeholder')"
161
+ :placeholder="placeholder"
119
162
  variant="none"
120
163
  :autofocus="autofocus"
121
164
  v-bind="inputProps"
122
165
  :icon="icon || appConfig.ui.icons.search"
123
166
  :class="ui.input({ class: props.ui?.input })"
167
+ @keydown.backspace="onBackspace"
124
168
  >
169
+ <template v-if="history?.length && (back || !!slots.back)" #leading>
170
+ <slot name="back" :ui="ui">
171
+ <UButton
172
+ :icon="backIcon || appConfig.ui.icons.arrowLeft"
173
+ color="neutral"
174
+ variant="link"
175
+ :aria-label="t('commandPalette.back')"
176
+ v-bind="typeof back === 'object' ? back : {}"
177
+ :class="ui.back({ class: props.ui?.back })"
178
+ @click="navigateBack"
179
+ />
180
+ </slot>
181
+ </template>
182
+
125
183
  <template v-if="close || !!slots.close" #trailing>
126
184
  <slot name="close" :ui="ui">
127
185
  <UButton
128
186
  v-if="close"
129
187
  :icon="closeIcon || appConfig.ui.icons.close"
130
- size="md"
131
188
  color="neutral"
132
189
  variant="ghost"
133
190
  :aria-label="t('commandPalette.close')"
@@ -141,8 +198,8 @@ const groups = computed(() => {
141
198
  </ListboxFilter>
142
199
 
143
200
  <ListboxContent :class="ui.content({ class: props.ui?.content })">
144
- <div v-if="groups?.length" :class="ui.viewport({ class: props.ui?.viewport })">
145
- <ListboxGroup v-for="group in groups" :key="`group-${group.id}`" :class="ui.group({ class: props.ui?.group })">
201
+ <div v-if="filteredGroups?.length" role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
202
+ <ListboxGroup v-for="group in filteredGroups" :key="`group-${group.id}`" :class="ui.group({ class: props.ui?.group })">
146
203
  <ListboxGroupLabel v-if="get(group, props.labelKey)" :class="ui.label({ class: props.ui?.label })">
147
204
  {{ get(group, props.labelKey) }}
148
205
  </ListboxGroupLabel>
@@ -150,10 +207,10 @@ const groups = computed(() => {
150
207
  <ListboxItem
151
208
  v-for="(item, index) in group.items"
152
209
  :key="`group-${group.id}-${index}`"
153
- :value="omit(item, ['matches', 'group', 'onSelect', 'labelHtml', 'suffixHtml'])"
210
+ :value="omit(item, ['matches', 'group', 'onSelect', 'labelHtml', 'suffixHtml', 'children'])"
154
211
  :disabled="item.disabled"
155
212
  as-child
156
- @select="item.onSelect"
213
+ @select="onSelect($event, item)"
157
214
  >
158
215
  <ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
159
216
  <ULinkBase v-bind="slotProps" :class="ui.item({ class: [props.ui?.item, item.ui?.item, item.class], active: active || item.active })">
@@ -184,13 +241,20 @@ const groups = computed(() => {
184
241
 
185
242
  <span :class="ui.itemTrailing({ class: [props.ui?.itemTrailing, item.ui?.itemTrailing] })">
186
243
  <slot :name="item.slot ? `${item.slot}-trailing` : group.slot ? `${group.slot}-trailing` : `item-trailing`" :item="item" :index="index">
187
- <span v-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: [props.ui?.itemTrailingKbds, item.ui?.itemTrailingKbds] })">
244
+ <UIcon
245
+ v-if="item.children && item.children.length > 0"
246
+ :name="trailingIcon || appConfig.ui.icons.chevronRight"
247
+ :class="ui.itemTrailingIcon({ class: [props.ui?.itemTrailingIcon, item.ui?.itemTrailingIcon] })"
248
+ />
249
+
250
+ <span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: [props.ui?.itemTrailingKbds, item.ui?.itemTrailingKbds] })">
188
251
  <UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="item.ui?.itemTrailingKbdsSize || props.ui?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
189
252
  </span>
253
+
190
254
  <UIcon v-else-if="group.highlightedIcon" :name="group.highlightedIcon" :class="ui.itemTrailingHighlightedIcon({ class: [props.ui?.itemTrailingHighlightedIcon, item.ui?.itemTrailingHighlightedIcon] })" />
191
255
  </slot>
192
256
 
193
- <ListboxItemIndicator as-child>
257
+ <ListboxItemIndicator v-if="!item.children?.length" as-child>
194
258
  <UIcon :name="selectedIcon || appConfig.ui.icons.check" :class="ui.itemTrailingIcon({ class: [props.ui?.itemTrailingIcon, item.ui?.itemTrailingIcon] })" />
195
259
  </ListboxItemIndicator>
196
260
  </span>
@@ -21,12 +21,17 @@ export interface CommandPaletteItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
21
21
  loading?: boolean;
22
22
  disabled?: boolean;
23
23
  slot?: string;
24
+ /**
25
+ * The placeholder to display when the item has children.
26
+ */
27
+ placeholder?: string;
28
+ children?: CommandPaletteItem[];
24
29
  onSelect?(e?: Event): void;
25
30
  class?: any;
26
31
  ui?: Pick<CommandPalette['slots'], 'item' | 'itemLeadingIcon' | 'itemLeadingAvatarSize' | 'itemLeadingAvatar' | 'itemLeadingChipSize' | 'itemLeadingChip' | 'itemLabel' | 'itemLabelPrefix' | 'itemLabelBase' | 'itemLabelSuffix' | 'itemTrailing' | 'itemTrailingKbds' | 'itemTrailingKbdsSize' | 'itemTrailingHighlightedIcon' | 'itemTrailingIcon'>;
27
32
  [key: string]: any;
28
33
  }
29
- export interface CommandPaletteGroup<T> {
34
+ export interface CommandPaletteGroup<T extends CommandPaletteItem = CommandPaletteItem> {
30
35
  id: string;
31
36
  label?: string;
32
37
  slot?: string;
@@ -45,7 +50,7 @@ export interface CommandPaletteGroup<T> {
45
50
  */
46
51
  highlightedIcon?: string;
47
52
  }
48
- export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
53
+ export interface CommandPaletteProps<G extends CommandPaletteGroup<T> = CommandPaletteGroup<any>, T extends CommandPaletteItem = CommandPaletteItem> extends Pick<ListboxRootProps, 'multiple' | 'disabled' | 'modelValue' | 'defaultValue' | 'highlightOnHover' | 'selectionBehavior'>, Pick<UseComponentIconsProps, 'loading' | 'loadingIcon'> {
49
54
  /**
50
55
  * The element or component this component should render as.
51
56
  * @defaultValue 'div'
@@ -63,6 +68,12 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
63
68
  * @IconifyIcon
64
69
  */
65
70
  selectedIcon?: string;
71
+ /**
72
+ * The icon displayed when an item has children.
73
+ * @defaultValue appConfig.ui.icons.chevronRight
74
+ * @IconifyIcon
75
+ */
76
+ trailingIcon?: string;
66
77
  /**
67
78
  * The placeholder text for the input.
68
79
  * @defaultValue t('commandPalette.placeholder')
@@ -86,6 +97,18 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
86
97
  * @IconifyIcon
87
98
  */
88
99
  closeIcon?: string;
100
+ /**
101
+ * Display a button to navigate back in history.
102
+ * `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"}
103
+ * @defaultValue true
104
+ */
105
+ back?: boolean | ButtonProps;
106
+ /**
107
+ * The icon displayed in the back button.
108
+ * @defaultValue appConfig.ui.icons.arrowLeft
109
+ * @IconifyIcon
110
+ */
111
+ backIcon?: string;
89
112
  groups?: G[];
90
113
  /**
91
114
  * Options for [useFuse](https://vueuse.org/integrations/useFuse).
@@ -108,21 +131,22 @@ export interface CommandPaletteProps<G, T> extends Pick<ListboxRootProps, 'multi
108
131
  class?: any;
109
132
  ui?: CommandPalette['slots'];
110
133
  }
111
- export type CommandPaletteEmits<T> = ListboxRootEmits<T> & {
134
+ export type CommandPaletteEmits<T extends CommandPaletteItem = CommandPaletteItem> = ListboxRootEmits<T> & {
112
135
  'update:open': [value: boolean];
113
136
  };
114
137
  type SlotProps<T> = (props: {
115
138
  item: T;
116
139
  index: number;
117
140
  }) => any;
118
- export type CommandPaletteSlots<G extends {
119
- slot?: string;
120
- }, T extends {
121
- slot?: string;
122
- }> = {
141
+ export type CommandPaletteSlots<G extends CommandPaletteGroup<T> = CommandPaletteGroup<any>, T extends CommandPaletteItem = CommandPaletteItem> = {
123
142
  'empty'(props: {
124
143
  searchTerm?: string;
125
144
  }): any;
145
+ 'back'(props: {
146
+ ui: {
147
+ [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string;
148
+ };
149
+ }): any;
126
150
  'close'(props: {
127
151
  ui: {
128
152
  [K in keyof Required<CommandPalette['slots']>]: (props?: Record<string, any>) => string;
@@ -91,68 +91,70 @@ const groups = computed(
91
91
  <component :is="sub ? ContextMenu.SubContent : ContextMenu.Content" :class="props.class" v-bind="contentProps">
92
92
  <slot name="content-top" />
93
93
 
94
- <ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
95
- <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
96
- <ContextMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: [uiOverride?.label, item.ui?.label, item.class] })">
97
- <ReuseItemTemplate :item="item" :index="index" />
98
- </ContextMenu.Label>
99
- <ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
100
- <ContextMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
101
- <ContextMenu.SubTrigger
102
- as="button"
103
- type="button"
94
+ <div role="presentation" :class="ui.viewport({ class: props.ui?.viewport })">
95
+ <ContextMenu.Group v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: uiOverride?.group })">
96
+ <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
97
+ <ContextMenu.Label v-if="item.type === 'label'" :class="ui.label({ class: [uiOverride?.label, item.ui?.label, item.class] })">
98
+ <ReuseItemTemplate :item="item" :index="index" />
99
+ </ContextMenu.Label>
100
+ <ContextMenu.Separator v-else-if="item.type === 'separator'" :class="ui.separator({ class: [uiOverride?.separator, item.ui?.separator, item.class] })" />
101
+ <ContextMenu.Sub v-else-if="item?.children?.length" :open="item.open" :default-open="item.defaultOpen">
102
+ <ContextMenu.SubTrigger
103
+ as="button"
104
+ type="button"
105
+ :disabled="item.disabled"
106
+ :text-value="get(item, props.labelKey)"
107
+ :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
108
+ >
109
+ <ReuseItemTemplate :item="item" :index="index" />
110
+ </ContextMenu.SubTrigger>
111
+
112
+ <UContextMenuContent
113
+ sub
114
+ :class="props.class"
115
+ :ui="ui"
116
+ :ui-override="uiOverride"
117
+ :portal="portal"
118
+ :items="item.children"
119
+ :align-offset="-4"
120
+ :label-key="labelKey"
121
+ :checked-icon="checkedIcon"
122
+ :loading-icon="loadingIcon"
123
+ :external-icon="externalIcon"
124
+ v-bind="item.content"
125
+ >
126
+ <template v-for="(_, name) in proxySlots" #[name]="slotData">
127
+ <slot :name="name" v-bind="slotData" />
128
+ </template>
129
+ </UContextMenuContent>
130
+ </ContextMenu.Sub>
131
+ <ContextMenu.CheckboxItem
132
+ v-else-if="item.type === 'checkbox'"
133
+ :model-value="item.checked"
104
134
  :disabled="item.disabled"
105
135
  :text-value="get(item, props.labelKey)"
106
136
  :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
137
+ @update:model-value="item.onUpdateChecked"
138
+ @select="item.onSelect"
107
139
  >
108
140
  <ReuseItemTemplate :item="item" :index="index" />
109
- </ContextMenu.SubTrigger>
110
-
111
- <UContextMenuContent
112
- sub
113
- :class="props.class"
114
- :ui="ui"
115
- :ui-override="uiOverride"
116
- :portal="portal"
117
- :items="item.children"
118
- :align-offset="-4"
119
- :label-key="labelKey"
120
- :checked-icon="checkedIcon"
121
- :loading-icon="loadingIcon"
122
- :external-icon="externalIcon"
123
- v-bind="item.content"
141
+ </ContextMenu.CheckboxItem>
142
+ <ContextMenu.Item
143
+ v-else
144
+ as-child
145
+ :disabled="item.disabled"
146
+ :text-value="get(item, props.labelKey)"
147
+ @select="item.onSelect"
124
148
  >
125
- <template v-for="(_, name) in proxySlots" #[name]="slotData">
126
- <slot :name="name" v-bind="slotData" />
127
- </template>
128
- </UContextMenuContent>
129
- </ContextMenu.Sub>
130
- <ContextMenu.CheckboxItem
131
- v-else-if="item.type === 'checkbox'"
132
- :model-value="item.checked"
133
- :disabled="item.disabled"
134
- :text-value="get(item, props.labelKey)"
135
- :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], color: item?.color })"
136
- @update:model-value="item.onUpdateChecked"
137
- @select="item.onSelect"
138
- >
139
- <ReuseItemTemplate :item="item" :index="index" />
140
- </ContextMenu.CheckboxItem>
141
- <ContextMenu.Item
142
- v-else
143
- as-child
144
- :disabled="item.disabled"
145
- :text-value="get(item, props.labelKey)"
146
- @select="item.onSelect"
147
- >
148
- <ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
149
- <ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], active, color: item?.color })">
150
- <ReuseItemTemplate :item="item" :active="active" :index="index" />
151
- </ULinkBase>
152
- </ULink>
153
- </ContextMenu.Item>
154
- </template>
155
- </ContextMenu.Group>
149
+ <ULink v-slot="{ active, ...slotProps }" v-bind="pickLinkProps(item)" custom>
150
+ <ULinkBase v-bind="slotProps" :class="ui.item({ class: [uiOverride?.item, item.ui?.item, item.class], active, color: item?.color })">
151
+ <ReuseItemTemplate :item="item" :active="active" :index="index" />
152
+ </ULinkBase>
153
+ </ULink>
154
+ </ContextMenu.Item>
155
+ </template>
156
+ </ContextMenu.Group>
157
+ </div>
156
158
 
157
159
  <slot />
158
160