@nuxt/ui 3.0.0 → 3.0.2

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 (98) hide show
  1. package/.nuxt/ui/button.ts +36 -36
  2. package/.nuxt/ui/calendar.ts +1 -1
  3. package/.nuxt/ui/container.ts +1 -1
  4. package/.nuxt/ui/drawer.ts +8 -5
  5. package/.nuxt/ui/navigation-menu.ts +2 -2
  6. package/.nuxt/ui/switch.ts +1 -1
  7. package/.nuxt/ui/tabs.ts +1 -1
  8. package/.nuxt/ui.css +1 -1
  9. package/README.md +11 -11
  10. package/cli/package.json +1 -1
  11. package/dist/module.cjs +2 -2
  12. package/dist/module.d.cts +8 -7
  13. package/dist/module.d.mts +8 -7
  14. package/dist/module.d.ts +8 -7
  15. package/dist/module.json +2 -2
  16. package/dist/module.mjs +2 -2
  17. package/dist/runtime/components/Accordion.vue +11 -8
  18. package/dist/runtime/components/Alert.vue +1 -1
  19. package/dist/runtime/components/App.vue +1 -1
  20. package/dist/runtime/components/Avatar.vue +7 -5
  21. package/dist/runtime/components/Breadcrumb.vue +13 -10
  22. package/dist/runtime/components/Button.vue +1 -1
  23. package/dist/runtime/components/Calendar.vue +40 -11
  24. package/dist/runtime/components/Carousel.vue +7 -3
  25. package/dist/runtime/components/ColorPicker.vue +2 -0
  26. package/dist/runtime/components/CommandPalette.vue +14 -11
  27. package/dist/runtime/components/ContextMenu.vue +31 -17
  28. package/dist/runtime/components/ContextMenuContent.vue +22 -15
  29. package/dist/runtime/components/Drawer.vue +10 -15
  30. package/dist/runtime/components/DropdownMenu.vue +29 -16
  31. package/dist/runtime/components/DropdownMenuContent.vue +22 -15
  32. package/dist/runtime/components/Form.vue +22 -1
  33. package/dist/runtime/components/FormField.vue +6 -0
  34. package/dist/runtime/components/InputMenu.vue +93 -55
  35. package/dist/runtime/components/Link.vue +3 -0
  36. package/dist/runtime/components/LinkBase.vue +1 -0
  37. package/dist/runtime/components/Modal.vue +11 -8
  38. package/dist/runtime/components/NavigationMenu.vue +53 -27
  39. package/dist/runtime/components/PinInput.vue +2 -0
  40. package/dist/runtime/components/Popover.vue +3 -2
  41. package/dist/runtime/components/RadioGroup.vue +25 -14
  42. package/dist/runtime/components/Select.vue +70 -43
  43. package/dist/runtime/components/SelectMenu.vue +84 -49
  44. package/dist/runtime/components/Slideover.vue +11 -8
  45. package/dist/runtime/components/Stepper.vue +13 -10
  46. package/dist/runtime/components/Table.vue +8 -3
  47. package/dist/runtime/components/Tabs.vue +8 -5
  48. package/dist/runtime/components/Toast.vue +2 -2
  49. package/dist/runtime/components/Tooltip.vue +3 -2
  50. package/dist/runtime/components/Tree.vue +48 -32
  51. package/dist/runtime/composables/defineLocale.js +1 -0
  52. package/dist/runtime/composables/useFormField.js +1 -1
  53. package/dist/runtime/composables/useKbd.js +1 -1
  54. package/dist/runtime/composables/useLocale.js +2 -2
  55. package/dist/runtime/composables/useOverlay.d.ts +3 -2
  56. package/dist/runtime/composables/useOverlay.js +1 -1
  57. package/dist/runtime/locale/ca.d.ts +3 -0
  58. package/dist/runtime/locale/ca.js +54 -0
  59. package/dist/runtime/locale/ckb.d.ts +3 -0
  60. package/dist/runtime/locale/ckb.js +55 -0
  61. package/dist/runtime/locale/hy.d.ts +3 -0
  62. package/dist/runtime/locale/hy.js +54 -0
  63. package/dist/runtime/locale/index.d.ts +6 -0
  64. package/dist/runtime/locale/index.js +6 -0
  65. package/dist/runtime/locale/ro.d.ts +3 -0
  66. package/dist/runtime/locale/ro.js +54 -0
  67. package/dist/runtime/locale/ur.d.ts +3 -0
  68. package/dist/runtime/locale/ur.js +55 -0
  69. package/dist/runtime/locale/uz.d.ts +3 -0
  70. package/dist/runtime/locale/uz.js +54 -0
  71. package/dist/runtime/plugins/colors.js +19 -15
  72. package/dist/runtime/types/form.d.ts +2 -3
  73. package/dist/runtime/types/index.d.ts +2 -0
  74. package/dist/runtime/types/index.js +2 -0
  75. package/dist/runtime/types/utils.d.ts +28 -8
  76. package/dist/runtime/utils/form.d.ts +0 -4
  77. package/dist/runtime/utils/form.js +2 -47
  78. package/dist/runtime/utils/index.d.ts +1 -0
  79. package/dist/runtime/utils/index.js +3 -0
  80. package/dist/runtime/utils/link.d.ts +2 -25
  81. package/dist/runtime/utils/link.js +31 -1
  82. package/dist/runtime/utils/tv.js +1 -1
  83. package/dist/runtime/vue/components/Link.vue +3 -0
  84. package/dist/runtime/vue/plugins/head.js +3 -0
  85. package/dist/runtime/vue/stubs.d.ts +7 -3
  86. package/dist/runtime/vue/stubs.js +12 -4
  87. package/dist/shared/{ui.COBs1u5H.cjs → ui.BL-XB3u1.cjs} +26 -26
  88. package/dist/shared/{ui.yjbGE7Dx.mjs → ui.BkNUP964.mjs} +26 -26
  89. package/dist/types.d.mts +7 -1
  90. package/dist/types.d.ts +7 -1
  91. package/dist/unplugin.cjs +6 -2
  92. package/dist/unplugin.d.cts +2 -1
  93. package/dist/unplugin.d.mts +2 -1
  94. package/dist/unplugin.d.ts +2 -1
  95. package/dist/unplugin.mjs +6 -2
  96. package/dist/vite.cjs +1 -1
  97. package/dist/vite.mjs +1 -1
  98. package/package.json +46 -29
@@ -2,15 +2,16 @@
2
2
  import type { ContextMenuContentProps as RekaContextMenuContentProps, ContextMenuContentEmits as RekaContextMenuContentEmits } from 'reka-ui'
3
3
  import theme from '#build/ui/context-menu'
4
4
  import { tv } from '../utils/tv'
5
- import type { KbdProps, AvatarProps, ContextMenuItem, ContextMenuSlots } from '../types'
5
+ import type { AvatarProps, ContextMenuItem, ContextMenuSlots, KbdProps } from '../types'
6
+ import type { ArrayOrNested, NestedItem } from '../types/utils'
6
7
 
7
8
  const _contextMenu = tv(theme)()
8
9
 
9
- interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
10
- items?: T[] | T[][]
10
+ interface ContextMenuContentProps<T extends ArrayOrNested<ContextMenuItem>> extends Omit<RekaContextMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
11
+ items?: T
11
12
  portal?: boolean
12
13
  sub?: boolean
13
- labelKey: string
14
+ labelKey: keyof NestedItem<T>
14
15
  /**
15
16
  * @IconifyIcon
16
17
  */
@@ -31,13 +32,13 @@ interface ContextMenuContentProps<T> extends Omit<RekaContextMenuContentProps, '
31
32
  interface ContextMenuContentEmits extends RekaContextMenuContentEmits {}
32
33
  </script>
33
34
 
34
- <script setup lang="ts" generic="T extends ContextMenuItem">
35
+ <script setup lang="ts" generic="T extends ArrayOrNested<ContextMenuItem>">
35
36
  import { computed } from 'vue'
36
37
  import { ContextMenu } from 'reka-ui/namespaced'
37
38
  import { useForwardPropsEmits } from 'reka-ui'
38
39
  import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
39
40
  import { useAppConfig } from '#imports'
40
- import { omit, get } from '../utils'
41
+ import { omit, get, isArrayOfArray } from '../utils'
41
42
  import { pickLinkProps } from '../utils/link'
42
43
  import ULinkBase from './LinkBase.vue'
43
44
  import ULink from './Link.vue'
@@ -53,24 +54,30 @@ const slots = defineSlots<ContextMenuSlots<T>>()
53
54
 
54
55
  const appConfig = useAppConfig()
55
56
  const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
56
- const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
57
+ const proxySlots = omit(slots, ['default'])
57
58
 
58
59
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
59
60
 
60
- const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as T[][] : [])
61
+ const groups = computed<ContextMenuItem[][]>(() =>
62
+ props.items?.length
63
+ ? isArrayOfArray(props.items)
64
+ ? props.items
65
+ : [props.items]
66
+ : []
67
+ )
61
68
  </script>
62
69
 
63
70
  <template>
64
71
  <DefineItemTemplate v-slot="{ item, active, index }">
65
- <slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
66
- <slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
72
+ <slot :name="((item.slot || 'item') as keyof ContextMenuSlots<T>)" :item="item" :index="index">
73
+ <slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
67
74
  <UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, loading: true })" />
68
75
  <UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, active })" />
69
76
  <UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
70
77
  </slot>
71
78
 
72
- <span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
73
- <slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
79
+ <span v-if="get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof ContextMenuSlots<T>]" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
80
+ <slot :name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
74
81
  {{ get(item, props.labelKey as string) }}
75
82
  </slot>
76
83
 
@@ -78,7 +85,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
78
85
  </span>
79
86
 
80
87
  <span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
81
- <slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
88
+ <slot :name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof ContextMenuSlots<T>)" :item="item" :active="active" :index="index">
82
89
  <UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, color: item?.color, active })" />
83
90
  <span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
84
91
  <UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.uiOverride?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
@@ -117,7 +124,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
117
124
  :ui="ui"
118
125
  :ui-override="uiOverride"
119
126
  :portal="portal"
120
- :items="item.children"
127
+ :items="(item.children as T)"
121
128
  :align-offset="-4"
122
129
  :label-key="labelKey"
123
130
  :checked-icon="checkedIcon"
@@ -126,7 +133,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
126
133
  v-bind="item.content"
127
134
  >
128
135
  <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
129
- <slot :name="name" v-bind="slotData" />
136
+ <slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
130
137
  </template>
131
138
  </UContextMenuContent>
132
139
  </ContextMenu.Sub>
@@ -1,16 +1,17 @@
1
1
  <script lang="ts">
2
2
  import type { DrawerRootProps, DrawerRootEmits } from 'vaul-vue'
3
- import type { DialogContentProps } from 'reka-ui'
3
+ import type { DialogContentProps, DialogContentEmits } from 'reka-ui'
4
4
  import type { AppConfig } from '@nuxt/schema'
5
5
  import _appConfig from '#build/app.config'
6
6
  import theme from '#build/ui/drawer'
7
7
  import { tv } from '../utils/tv'
8
+ import type { EmitsToProps } from '../types/utils'
8
9
 
9
10
  const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
10
11
 
11
12
  const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
12
13
 
13
- export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
14
+ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'shouldScaleBackground' | 'setBackgroundColorOnScale' | 'scrollLockTimeout' | 'fixed' | 'dismissible' | 'modal' | 'open' | 'defaultOpen' | 'nested' | 'direction' | 'noBodyStyles' | 'handleOnly' | 'preventScrollRestoration' | 'snapPoints'> {
14
15
  /**
15
16
  * The element or component this component should render as.
16
17
  * @defaultValue 'div'
@@ -24,7 +25,7 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
24
25
  */
25
26
  inset?: boolean
26
27
  /** The content of the drawer. */
27
- content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'>
28
+ content?: Omit<DialogContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DialogContentEmits>>
28
29
  /**
29
30
  * Render an overlay behind the drawer.
30
31
  * @defaultValue true
@@ -40,11 +41,6 @@ export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | '
40
41
  * @defaultValue true
41
42
  */
42
43
  portal?: boolean
43
- /**
44
- * When `false`, the drawer will not close when clicking outside or pressing escape.
45
- * @defaultValue true
46
- */
47
- dismissible?: boolean
48
44
  class?: any
49
45
  ui?: Partial<typeof drawer.slots>
50
46
  }
@@ -53,7 +49,6 @@ export interface DrawerEmits extends DrawerRootEmits {}
53
49
 
54
50
  export interface DrawerSlots {
55
51
  default(props?: {}): any
56
- handle(props?: {}): any
57
52
  content(props?: {}): any
58
53
  header(props?: {}): any
59
54
  title(props?: {}): any
@@ -66,19 +61,21 @@ export interface DrawerSlots {
66
61
  <script setup lang="ts">
67
62
  import { computed, toRef } from 'vue'
68
63
  import { useForwardPropsEmits } from 'reka-ui'
69
- import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription } from 'vaul-vue'
64
+ import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerDescription, DrawerHandle } from 'vaul-vue'
70
65
  import { reactivePick } from '@vueuse/core'
71
66
 
72
67
  const props = withDefaults(defineProps<DrawerProps>(), {
73
68
  direction: 'bottom',
74
69
  portal: true,
75
70
  overlay: true,
76
- handle: true
71
+ handle: true,
72
+ modal: true,
73
+ dismissible: true
77
74
  })
78
75
  const emits = defineEmits<DrawerEmits>()
79
76
  const slots = defineSlots<DrawerSlots>()
80
77
 
81
- const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'defaultOpen', 'dismissible', 'fadeFromIndex', 'fixed', 'modal', 'nested', 'direction', 'open', 'scrollLockTimeout', 'shouldScaleBackground', 'snapPoints'), emits)
78
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'activeSnapPoint', 'closeThreshold', 'shouldScaleBackground', 'setBackgroundColorOnScale', 'scrollLockTimeout', 'fixed', 'dismissible', 'modal', 'open', 'defaultOpen', 'nested', 'direction', 'noBodyStyles', 'handleOnly', 'preventScrollRestoration', 'snapPoints'), emits)
82
79
  const contentProps = toRef(() => props.content)
83
80
  const contentEvents = {
84
81
  closeAutoFocus: (e: Event) => e.preventDefault()
@@ -100,9 +97,7 @@ const ui = computed(() => drawer({
100
97
  <DrawerOverlay v-if="overlay" :class="ui.overlay({ class: props.ui?.overlay })" />
101
98
 
102
99
  <DrawerContent :class="ui.content({ class: [!slots.default && props.class, props.ui?.content] })" v-bind="contentProps" v-on="contentEvents">
103
- <slot name="handle">
104
- <div v-if="handle" :class="ui.handle({ class: props.ui?.handle })" />
105
- </slot>
100
+ <DrawerHandle v-if="handle" :class="ui.handle({ class: props.ui?.handle })" />
106
101
 
107
102
  <slot name="content">
108
103
  <div :class="ui.container({ class: props.ui?.container })">
@@ -1,12 +1,20 @@
1
+ <!-- eslint-disable vue/block-tag-newline -->
1
2
  <script lang="ts">
2
3
  import type { VariantProps } from 'tailwind-variants'
3
- import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuArrowProps } from 'reka-ui'
4
+ import type { DropdownMenuRootProps, DropdownMenuRootEmits, DropdownMenuContentProps, DropdownMenuContentEmits, DropdownMenuArrowProps } from 'reka-ui'
4
5
  import type { AppConfig } from '@nuxt/schema'
5
6
  import _appConfig from '#build/app.config'
6
7
  import theme from '#build/ui/dropdown-menu'
7
8
  import { tv } from '../utils/tv'
8
9
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
9
- import type { DynamicSlots, PartialString } from '../types/utils'
10
+ import type {
11
+ ArrayOrNested,
12
+ DynamicSlots,
13
+ MergeTypes,
14
+ NestedItem,
15
+ PartialString,
16
+ EmitsToProps
17
+ } from '../types/utils'
10
18
 
11
19
  const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
12
20
 
@@ -22,7 +30,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
22
30
  icon?: string
23
31
  color?: DropdownMenuVariants['color']
24
32
  avatar?: AvatarProps
25
- content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'>
33
+ content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>>
26
34
  kbds?: KbdProps['value'][] | KbdProps[]
27
35
  /**
28
36
  * The item type.
@@ -35,17 +43,18 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
35
43
  checked?: boolean
36
44
  open?: boolean
37
45
  defaultOpen?: boolean
38
- children?: DropdownMenuItem[] | DropdownMenuItem[][]
46
+ children?: ArrayOrNested<DropdownMenuItem>
39
47
  onSelect?(e: Event): void
40
48
  onUpdateChecked?(checked: boolean): void
49
+ [key: string]: any
41
50
  }
42
51
 
43
- export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'> {
52
+ export interface DropdownMenuProps<T extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>> extends Omit<DropdownMenuRootProps, 'dir'> {
44
53
  /**
45
54
  * @defaultValue 'md'
46
55
  */
47
56
  size?: DropdownMenuVariants['size']
48
- items?: T[] | T[][]
57
+ items?: T
49
58
  /**
50
59
  * The icon displayed when an item is checked.
51
60
  * @defaultValue appConfig.ui.icons.check
@@ -69,7 +78,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
69
78
  * The content of the menu.
70
79
  * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8 }
71
80
  */
72
- content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'>
81
+ content?: Omit<DropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> & Partial<EmitsToProps<DropdownMenuContentEmits>>
73
82
  /**
74
83
  * Display an arrow alongside the menu.
75
84
  * @defaultValue false
@@ -84,7 +93,7 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
84
93
  * The key used to get the label from the item.
85
94
  * @defaultValue 'label'
86
95
  */
87
- labelKey?: string
96
+ labelKey?: keyof NestedItem<T>
88
97
  disabled?: boolean
89
98
  class?: any
90
99
  ui?: PartialString<typeof dropdownMenu.slots>
@@ -92,18 +101,22 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
92
101
 
93
102
  export interface DropdownMenuEmits extends DropdownMenuRootEmits {}
94
103
 
95
- type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
104
+ type SlotProps<T extends DropdownMenuItem> = (props: { item: T, active?: boolean, index: number }) => any
96
105
 
97
- export type DropdownMenuSlots<T extends { slot?: string }> = {
106
+ export type DropdownMenuSlots<
107
+ A extends ArrayOrNested<DropdownMenuItem> = ArrayOrNested<DropdownMenuItem>,
108
+ T extends NestedItem<A> = NestedItem<A>
109
+ > = {
98
110
  'default'(props: { open: boolean }): any
99
111
  'item': SlotProps<T>
100
112
  'item-leading': SlotProps<T>
101
113
  'item-label': SlotProps<T>
102
114
  'item-trailing': SlotProps<T>
103
- } & DynamicSlots<T, SlotProps<T>>
115
+ } & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
116
+
104
117
  </script>
105
118
 
106
- <script setup lang="ts" generic="T extends DropdownMenuItem">
119
+ <script setup lang="ts" generic="T extends ArrayOrNested<DropdownMenuItem>">
107
120
  import { computed, toRef } from 'vue'
108
121
  import { defu } from 'defu'
109
122
  import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuArrow, useForwardPropsEmits } from 'reka-ui'
@@ -123,7 +136,7 @@ const slots = defineSlots<DropdownMenuSlots<T>>()
123
136
  const rootProps = useForwardPropsEmits(reactivePick(props, 'defaultOpen', 'open', 'modal'), emits)
124
137
  const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8 }) as DropdownMenuContentProps)
125
138
  const arrowProps = toRef(() => props.arrow as DropdownMenuArrowProps)
126
- const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuSlots<T>[string]>
139
+ const proxySlots = omit(slots, ['default'])
127
140
 
128
141
  const ui = computed(() => dropdownMenu({
129
142
  size: props.size
@@ -143,13 +156,13 @@ const ui = computed(() => dropdownMenu({
143
156
  v-bind="contentProps"
144
157
  :items="items"
145
158
  :portal="portal"
146
- :label-key="labelKey"
159
+ :label-key="(labelKey as keyof NestedItem<T>)"
147
160
  :checked-icon="checkedIcon"
148
161
  :loading-icon="loadingIcon"
149
162
  :external-icon="externalIcon"
150
163
  >
151
- <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
152
- <slot :name="name" v-bind="slotData" />
164
+ <template v-for="(_, name) in proxySlots" #[name]="slotData">
165
+ <slot :name="(name as keyof DropdownMenuSlots<T>)" v-bind="slotData" />
153
166
  </template>
154
167
 
155
168
  <DropdownMenuArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />
@@ -4,14 +4,15 @@ import type { DropdownMenuContentProps as RekaDropdownMenuContentProps, Dropdown
4
4
  import theme from '#build/ui/dropdown-menu'
5
5
  import { tv } from '../utils/tv'
6
6
  import type { KbdProps, AvatarProps, DropdownMenuItem, DropdownMenuSlots } from '../types'
7
+ import type { ArrayOrNested, NestedItem } from '../types/utils'
7
8
 
8
9
  const _dropdownMenu = tv(theme)()
9
10
 
10
- interface DropdownMenuContentProps<T> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
11
- items?: T[] | T[][]
11
+ interface DropdownMenuContentProps<T extends ArrayOrNested<DropdownMenuItem>> extends Omit<RekaDropdownMenuContentProps, 'as' | 'asChild' | 'forceMount'> {
12
+ items?: T
12
13
  portal?: boolean
13
14
  sub?: boolean
14
- labelKey: string
15
+ labelKey: keyof NestedItem<T>
15
16
  /**
16
17
  * @IconifyIcon
17
18
  */
@@ -31,19 +32,19 @@ interface DropdownMenuContentProps<T> extends Omit<RekaDropdownMenuContentProps,
31
32
 
32
33
  interface DropdownMenuContentEmits extends RekaDropdownMenuContentEmits {}
33
34
 
34
- type DropdownMenuContentSlots<T extends { slot?: string }> = Omit<DropdownMenuSlots<T>, 'default'> & {
35
+ type DropdownMenuContentSlots<T extends ArrayOrNested<DropdownMenuItem>> = Omit<DropdownMenuSlots<T>, 'default'> & {
35
36
  default(props?: {}): any
36
37
  }
37
38
 
38
39
  </script>
39
40
 
40
- <script setup lang="ts" generic="T extends DropdownMenuItem">
41
+ <script setup lang="ts" generic="T extends ArrayOrNested<DropdownMenuItem>">
41
42
  import { computed } from 'vue'
42
43
  import { DropdownMenu } from 'reka-ui/namespaced'
43
44
  import { useForwardPropsEmits } from 'reka-ui'
44
45
  import { reactiveOmit, createReusableTemplate } from '@vueuse/core'
45
46
  import { useAppConfig } from '#imports'
46
- import { omit, get } from '../utils'
47
+ import { omit, get, isArrayOfArray } from '../utils'
47
48
  import { pickLinkProps } from '../utils/link'
48
49
  import ULinkBase from './LinkBase.vue'
49
50
  import ULink from './Link.vue'
@@ -59,24 +60,30 @@ const slots = defineSlots<DropdownMenuContentSlots<T>>()
59
60
 
60
61
  const appConfig = useAppConfig()
61
62
  const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'externalIcon', 'class', 'ui', 'uiOverride'), emits)
62
- const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
63
+ const proxySlots = omit(slots, ['default'])
63
64
 
64
65
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>()
65
66
 
66
- const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as T[][] : [])
67
+ const groups = computed<DropdownMenuItem[][]>(() =>
68
+ props.items?.length
69
+ ? isArrayOfArray(props.items)
70
+ ? props.items
71
+ : [props.items]
72
+ : []
73
+ )
67
74
  </script>
68
75
 
69
76
  <template>
70
77
  <DefineItemTemplate v-slot="{ item, active, index }">
71
- <slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
72
- <slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
78
+ <slot :name="((item.slot || 'item') as keyof DropdownMenuContentSlots<T>)" :item="(item as Extract<NestedItem<T>, { slot: string; }>)" :index="index">
79
+ <slot :name="((item.slot ? `${item.slot}-leading`: 'item-leading') as keyof DropdownMenuContentSlots<T>)" :item="(item as Extract<NestedItem<T>, { slot: string; }>)" :active="active" :index="index">
73
80
  <UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, loading: true })" />
74
81
  <UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, color: item?.color, active })" />
75
82
  <UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
76
83
  </slot>
77
84
 
78
- <span v-if="get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label`: 'item-label']" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
79
- <slot :name="item.slot ? `${item.slot}-label`: 'item-label'" :item="(item as T)" :active="active" :index="index">
85
+ <span v-if="get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label`: 'item-label') as keyof DropdownMenuContentSlots<T>]" :class="ui.itemLabel({ class: uiOverride?.itemLabel, active })">
86
+ <slot :name="((item.slot ? `${item.slot}-label`: 'item-label') as keyof DropdownMenuContentSlots<T>)" :item="(item as Extract<NestedItem<T>, { slot: string; }>)" :active="active" :index="index">
80
87
  {{ get(item, props.labelKey as string) }}
81
88
  </slot>
82
89
 
@@ -84,7 +91,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
84
91
  </span>
85
92
 
86
93
  <span :class="ui.itemTrailing({ class: uiOverride?.itemTrailing })">
87
- <slot :name="item.slot ? `${item.slot}-trailing`: 'item-trailing'" :item="(item as T)" :active="active" :index="index">
94
+ <slot :name="((item.slot ? `${item.slot}-trailing`: 'item-trailing') as keyof DropdownMenuContentSlots<T>)" :item="(item as Extract<NestedItem<T>, { slot: string; }>)" :active="active" :index="index">
88
95
  <UIcon v-if="item.children?.length" :name="appConfig.ui.icons.chevronRight" :class="ui.itemTrailingIcon({ class: uiOverride?.itemTrailingIcon, color: item?.color, active })" />
89
96
  <span v-else-if="item.kbds?.length" :class="ui.itemTrailingKbds({ class: uiOverride?.itemTrailingKbds })">
90
97
  <UKbd v-for="(kbd, kbdIndex) in item.kbds" :key="kbdIndex" :size="((props.uiOverride?.itemTrailingKbdsSize || ui.itemTrailingKbdsSize()) as KbdProps['size'])" v-bind="typeof kbd === 'string' ? { value: kbd } : kbd" />
@@ -123,7 +130,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
123
130
  :ui="ui"
124
131
  :ui-override="uiOverride"
125
132
  :portal="portal"
126
- :items="item.children"
133
+ :items="(item.children as T)"
127
134
  side="right"
128
135
  align="start"
129
136
  :align-offset="-4"
@@ -135,7 +142,7 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
135
142
  v-bind="item.content"
136
143
  >
137
144
  <template v-for="(_, name) in proxySlots" #[name]="slotData: any">
138
- <slot :name="name" v-bind="slotData" />
145
+ <slot :name="(name as keyof DropdownMenuContentSlots<T>)" v-bind="slotData" />
139
146
  </template>
140
147
  </UDropdownMenuContent>
141
148
  </DropdownMenu.Sub>
@@ -12,14 +12,34 @@ const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
12
12
 
13
13
  export interface FormProps<T extends object> {
14
14
  id?: string | number
15
+ /** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
15
16
  schema?: FormSchema<T>
17
+ /** An object representing the current state of the form. */
16
18
  state: Partial<T>
19
+ /**
20
+ * Custom validation function to validate the form state.
21
+ * @param state - The current state of the form.
22
+ * @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
23
+ */
17
24
  validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
25
+ /**
26
+ * The list of input events that trigger the form validation.
27
+ * @defaultValue `['blur', 'change', 'input']`
28
+ */
18
29
  validateOn?: FormInputEvents[]
30
+ /** Disable all inputs inside the form. */
19
31
  disabled?: boolean
32
+ /**
33
+ * Delay in milliseconds before validating the form on input events.
34
+ * @defaultValue `300`
35
+ */
20
36
  validateOnInputDelay?: number
21
- class?: any
37
+ /**
38
+ * If true, schema transformations will be applied to the state on submit.
39
+ * @defaultValue `true`
40
+ */
22
41
  transform?: boolean
42
+ class?: any
23
43
  onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
24
44
  }
25
45
 
@@ -197,6 +217,7 @@ async function onSubmitWrapper(payload: Event) {
197
217
  try {
198
218
  event.data = await _validate({ nested: true, transform: props.transform })
199
219
  await props.onSubmit?.(event)
220
+ dirtyFields.clear()
200
221
  } catch (error) {
201
222
  if (!(error instanceof FormValidationException)) {
202
223
  throw error
@@ -31,7 +31,12 @@ export interface FormFieldProps {
31
31
  */
32
32
  size?: FormFieldVariants['size']
33
33
  required?: boolean
34
+ /** If true, validation on input will be active immediately instead of waiting for a blur event. */
34
35
  eagerValidation?: boolean
36
+ /**
37
+ * Delay in milliseconds before validating the form on input events.
38
+ * @defaultValue `300`
39
+ */
35
40
  validateOnInputDelay?: number
36
41
  class?: any
37
42
  ui?: Partial<typeof formField.slots>
@@ -81,6 +86,7 @@ provide(formFieldInjectionKey, computed(() => ({
81
86
  errorPattern: props.errorPattern,
82
87
  hint: props.hint,
83
88
  description: props.description,
89
+ help: props.help,
84
90
  ariaId
85
91
  }) as FormFieldInjectedOptions<FormFieldProps>))
86
92
  </script>