fix(NavigationMenu): proxy `modelValue` / `defaultValue` in vertical … · nuxt/ui@cffaaaa
11<!-- eslint-disable vue/block-tag-newline -->
22<script lang="ts">
3-import type { NavigationMenuRootProps, NavigationMenuRootEmits, NavigationMenuContentProps, NavigationMenuContentEmits, AccordionRootProps } from 'reka-ui'
3+import type { NavigationMenuRootProps, NavigationMenuContentProps, NavigationMenuContentEmits, AccordionRootProps } from 'reka-ui'
44import type { AppConfig } from '@nuxt/schema'
55import theme from '#build/ui/navigation-menu'
66import type { AvatarProps, BadgeProps, IconProps, LinkProps, PopoverProps, TooltipProps } from '../types'
@@ -63,12 +63,49 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
6363 [key: string]: any
6464}
656566-export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | 'type' | 'collapsible'> {
66+type SingleOrMultipleType = 'single' | 'multiple'
67+type Orientation = NavigationMenuRootProps['orientation']
68+69+type NavigationMenuModelValue<
70+ K extends SingleOrMultipleType = SingleOrMultipleType,
71+ O extends Orientation = Orientation
72+> = O extends 'horizontal' ? string : K extends 'single' ? string : K extends 'multiple' ? string[] : string | string[]
73+74+export interface NavigationMenuProps<
75+ T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>,
76+ K extends SingleOrMultipleType = SingleOrMultipleType,
77+ O extends Orientation = Orientation
78+> extends Pick<NavigationMenuRootProps, 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'>, Pick<AccordionRootProps, 'disabled' | 'collapsible'> {
6779 /**
6880 * The element or component this component should render as.
6981 * @defaultValue 'div'
7082 */
7183 as?: any
84+ /**
85+ * Determines whether a "single" or "multiple" items can be selected at a time.
86+ *
87+ * Only works when `orientation` is `vertical`.
88+ * @defaultValue 'multiple'
89+ */
90+ type?: K
91+ /**
92+ * The controlled value of the active item(s).
93+ * - In horizontal orientation: always `string`
94+ * - In vertical orientation with `type="single"`: `string`
95+ * - In vertical orientation with `type="multiple"`: `string[]`
96+ *
97+ * Use this when you need to control the state of the items. Can be binded with `v-model`
98+ */
99+ modelValue?: NavigationMenuModelValue<K, O>
100+ /**
101+ * The default active value of the item(s).
102+ * - In horizontal orientation: always `string`
103+ * - In vertical orientation with `type="single"`: `string`
104+ * - In vertical orientation with `type="multiple"`: `string[]`
105+ *
106+ * Use when you do not need to control the state of the item(s).
107+ */
108+ defaultValue?: NavigationMenuModelValue<K, O>
72109 /**
73110 * The icon displayed to open the menu.
74111 * @defaultValue appConfig.ui.icons.chevronDown
@@ -95,7 +132,7 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
95132 * The orientation of the menu.
96133 * @defaultValue 'horizontal'
97134 */
98- orientation?: NavigationMenuRootProps['orientation']
135+ orientation?: O
99136 /**
100137 * Collapse the navigation menu to only show icons.
101138 * Only works when `orientation` is `vertical`.
@@ -142,7 +179,18 @@ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem>
142179 ui?: NavigationMenu['slots']
143180}
144181145-export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
182+export type NavigationMenuEmits<
183+ K extends SingleOrMultipleType = SingleOrMultipleType,
184+ O extends Orientation = Orientation
185+> = {
186+ /**
187+ * Event handler called when the value changes.
188+ * - In horizontal orientation: emits `string`
189+ * - In vertical orientation with `type="single"`: emits `string | undefined`
190+ * - In vertical orientation with `type="multiple"`: emits `string[] | undefined`
191+ */
192+ 'update:modelValue': [value: NavigationMenuModelValue<K, O> | undefined]
193+}
146194147195type SlotProps<T extends NavigationMenuItem> = (props: { item: T, index: number, active?: boolean, ui: NavigationMenu['ui'] }) => any
148196@@ -163,7 +211,7 @@ export type NavigationMenuSlots<
163211164212</script>
165213166-<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>">
214+<script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>, K extends SingleOrMultipleType = SingleOrMultipleType, O extends Orientation = Orientation">
167215import { computed, toRef } from 'vue'
168216import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, AccordionRoot, AccordionItem, AccordionTrigger, AccordionContent, useForwardPropsEmits } from 'reka-ui'
169217import { defu } from 'defu'
@@ -182,25 +230,23 @@ import UTooltip from './Tooltip.vue'
182230183231defineOptions({ inheritAttrs: false })
184232185-const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
186- orientation: 'horizontal',
233+const props = withDefaults(defineProps<NavigationMenuProps<T, K, O>>(), {
234+ orientation: 'horizontal' as never,
187235 contentOrientation: 'horizontal',
188236 externalIcon: true,
189237 delayDuration: 0,
190- type: 'multiple',
238+ type: 'multiple' as never,
191239 collapsible: true,
192240 unmountOnHide: true,
193241 labelKey: 'label'
194242})
195-const emits = defineEmits<NavigationMenuEmits>()
243+const emits = defineEmits<NavigationMenuEmits<K, O>>()
196244const slots = defineSlots<NavigationMenuSlots<T>>()
197245198246const appConfig = useAppConfig() as NavigationMenu['AppConfig']
199247200248const rootProps = useForwardPropsEmits(computed(() => ({
201249 as: props.as,
202- modelValue: props.modelValue,
203- defaultValue: props.defaultValue,
204250 delayDuration: props.delayDuration,
205251 skipDelayDuration: props.skipDelayDuration,
206252 orientation: props.orientation,
@@ -415,14 +461,27 @@ function getAccordionDefaultValue(list: NavigationMenuItem[], level = 0) {
415461</component>
416462</DefineItemTemplate>
417463418-<NavigationMenuRoot v-bind="{ ...rootProps, ...$attrs }" :data-collapsed="collapsed" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
464+<NavigationMenuRoot
465+v-bind="{
466+ ...rootProps,
467+ ...(orientation === 'horizontal' ? {
468+ modelValue: modelValue as string,
469+ defaultValue: defaultValue as string
470+ } : {}),
471+ ...$attrs
472+ }"
473+:data-collapsed="collapsed"
474+data-slot="root"
475+:class="ui.root({ class: [props.ui?.root, props.class] })"
476+>
419477<slot name="list-leading" />
420478421479<template v-for="(list, listIndex) in lists" :key="`list-${listIndex}`">
422480<component
423481v-bind="orientation === 'vertical' && !collapsed ? {
424482 ...accordionProps,
425- defaultValue: getAccordionDefaultValue(list)
483+ modelValue,
484+ defaultValue: defaultValue ?? getAccordionDefaultValue(list)
426485 } : {}"
427486:is="orientation === 'vertical' && !collapsed ? AccordionRoot : NavigationMenuList"
428487as="ul"