Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/vuetify/src/components/VList/VList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const makeVListProps = propsFactory({
},
slim: Boolean,
nav: Boolean,
cascade: Boolean,

'onClick:open': EventProp<[{ id: unknown, value: boolean, path: unknown[] }]>(),
'onClick:select': EventProp<[{ id: unknown, value: boolean, path: unknown[] }]>(),
Expand Down Expand Up @@ -285,6 +286,7 @@ export const VList = genericComponent<new <
<VListChildren
items={ items.value }
returnObject={ props.returnObject }
cascade={ props.cascade }
v-slots={ slots }
/>
</props.tag>
Expand Down
5 changes: 4 additions & 1 deletion packages/vuetify/src/components/VList/VListChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type VListChildrenSlots<T> = {
export const makeVListChildrenProps = propsFactory({
items: Array as PropType<readonly InternalListItem[]>,
returnObject: Boolean,
cascade: Boolean,
}, 'VListChildren')

export const VListChildren = genericComponent<new <T extends InternalListItem>(
Expand Down Expand Up @@ -65,7 +66,7 @@ export const VListChildren = genericComponent<new <T extends InternalListItem>(

const listGroupProps = VListGroup.filterProps(itemProps)

return children ? (
return children && !props.cascade ? (
<VListGroup
{ ...listGroupProps }
value={ props.returnObject ? item : itemProps?.value }
Expand Down Expand Up @@ -99,6 +100,8 @@ export const VListChildren = genericComponent<new <T extends InternalListItem>(
<VListItem
{ ...itemProps }
value={ props.returnObject ? item : itemProps.value }
cascade={ props.cascade }
children={ props.cascade ? item.children : undefined }
v-slots={ slotsWithItem }
/>
)
Expand Down
48 changes: 41 additions & 7 deletions packages/vuetify/src/components/VList/VListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable complexity */
// Styles
import './VListItem.sass'

Expand Down Expand Up @@ -27,11 +28,13 @@ import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant
import vRipple from '@/directives/ripple'

// Utilities
import { computed, onBeforeMount, toDisplayString, toRef, watch } from 'vue'
import { computed, onBeforeMount, shallowRef, toDisplayString, toRef, watch } from 'vue'
import { deprecate, EventProp, genericComponent, keyCodes, propsFactory, useRender } from '@/util'

// Types
import type { PropType } from 'vue'
import { VList } from './VList'
import { VMenu } from '../VMenu'
import type { RippleDirectiveBinding } from '@/directives/ripple'

export type ListItemSlot = {
Expand All @@ -44,6 +47,7 @@ export type ListItemSlot = {
isOpen: boolean
isSelected: boolean
isIndeterminate: boolean
cascade: boolean
select: (value: boolean) => void
}

Expand Down Expand Up @@ -81,6 +85,9 @@ export const makeVListItemProps = propsFactory({
default: undefined,
},
nav: Boolean,
cascade: Boolean,
children: Array as PropType<any[]>,

prependAvatar: String,
prependIcon: IconValue,
ripple: {
Expand Down Expand Up @@ -146,6 +153,9 @@ export const VListItem = genericComponent<VListItemSlots>()({
(props.active || link.isActive?.value || (root.activatable.value ? isActivated.value : isSelected.value))
)
const isLink = toRef(() => props.link !== false && link.isLink.value)
const hasSubmenu = toRef(props.cascade && !!props.children)
const submenu = shallowRef(false)

const isSelectable = computed(() => (!!list && (root.selectable.value || root.activatable.value || props.value != null)))
const isClickable = computed(() =>
!props.disabled &&
Expand Down Expand Up @@ -200,17 +210,23 @@ export const VListItem = genericComponent<VListItemSlots>()({
isOpen: isOpen.value,
isSelected: isSelected.value,
isIndeterminate: isIndeterminate.value,
cascade: props.cascade,
} satisfies ListItemSlot))

function onClick (e: MouseEvent) {
emit('click', e)
if (hasSubmenu.value) {
submenu.value = true
} else {
emit('click', e)
}

if (['INPUT', 'TEXTAREA'].includes((e.target as Element)?.tagName)) return

if (!isClickable.value) return

link.navigate?.(e)

if (isGroupActivator) return
if (isGroupActivator || hasSubmenu.value) return

if (root.activatable.value) {
activate(!isActivated.value, e)
Expand Down Expand Up @@ -238,9 +254,10 @@ export const VListItem = genericComponent<VListItemSlots>()({
const hasTitle = (slots.title || props.title != null)
const hasSubtitle = (slots.subtitle || props.subtitle != null)
const hasAppendMedia = !!(props.appendAvatar || props.appendIcon)
const hasAppend = !!(hasAppendMedia || slots.append)
const hasAppend = !!(hasAppendMedia || hasSubmenu.value || slots.append)
const hasPrependMedia = !!(props.prependAvatar || props.prependIcon)
const hasPrepend = !!(hasPrependMedia || slots.prepend)
const submenuIcon = !props.appendIcon && !props.appendAvatar && hasSubmenu.value ? '$submenuExpand' : undefined

list?.updateHasPrepend(hasPrepend)

Expand Down Expand Up @@ -353,15 +370,32 @@ export const VListItem = genericComponent<VListItemSlots>()({
{ slots.default?.(slotProps.value) }
</div>

{ hasSubmenu.value && (
<VMenu
key="submenu"
v-model={ submenu.value }
close-on-content-click={ false }
offset={[0, 8]}
open-on-focus={ false }
activator="parent"
open-on-hover
submenu
>
<VList
items={ props.children }
cascade
/>
</VMenu>
)}
{ hasAppend && (
<div key="append" class="v-list-item__append">
{ !slots.append ? (
<>
{ props.appendIcon && (
{ (props.appendIcon || submenuIcon) && (
<VIcon
key="append-icon"
density={ props.density }
icon={ props.appendIcon }
icon={ props.appendIcon || submenuIcon }
/>
)}

Expand All @@ -384,7 +418,7 @@ export const VListItem = genericComponent<VListItemSlots>()({
},
VIcon: {
density: props.density,
icon: props.appendIcon,
icon: props.appendIcon || submenuIcon,
},
VListItemAction: {
end: true,
Expand Down
4 changes: 3 additions & 1 deletion packages/vuetify/src/components/VMenu/VMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ export const VMenu = genericComponent<OverlaySlots>()({
}, { immediate: true })

function onClickOutside (e: MouseEvent) {
parent?.closeParents(e)
if (!props.submenu) {
parent?.closeParents(e)
}
}

function onKeydown (e: KeyboardEvent) {
Expand Down
4 changes: 3 additions & 1 deletion packages/vuetify/src/components/VSelect/VSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const VSelect = genericComponent<new <
T extends readonly any[],
Item = ItemType<T>,
ReturnObject extends boolean = false,
Cascade extends boolean = false,
Multiple extends boolean = false,
V extends Value<Item, ReturnObject, Multiple> = Value<Item, ReturnObject, Multiple>
>(
Expand All @@ -119,6 +120,7 @@ export const VSelect = genericComponent<new <
itemValue?: SelectItemKey<ItemType<T>>
itemProps?: SelectItemKey<ItemType<T>>
returnObject?: ReturnObject
cascade?: Cascade
multiple?: Multiple
modelValue?: V | null
'onUpdate:modelValue'?: (value: V) => void
Expand Down Expand Up @@ -487,7 +489,7 @@ export const VSelect = genericComponent<new <
index,
props: itemProps,
}) ?? (
<VListItem { ...itemProps } role="option">
<VListItem { ...itemProps } cascade={ props.cascade } role="option">
{{
prepend: ({ isSelected }) => (
<>
Expand Down
8 changes: 7 additions & 1 deletion packages/vuetify/src/composables/list-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ItemProps {
itemChildren: SelectItemKey
itemProps: SelectItemKey
returnObject: boolean
cascade: boolean
valueComparator: typeof deepEqual | undefined
}

Expand All @@ -50,6 +51,7 @@ export const makeItemsProps = propsFactory({
default: 'props',
},
returnObject: Boolean,
cascade: Boolean,
valueComparator: Function as PropType<typeof deepEqual>,
}, 'list-items')

Expand All @@ -59,7 +61,7 @@ export function transformItem (props: Omit<ItemProps, 'items'>, item: any): List
const children = getPropertyFromItem(item, props.itemChildren)
const itemProps = props.itemProps === true
? typeof item === 'object' && item != null && !Array.isArray(item)
? 'children' in item
? 'children' in item && !props.cascade
? omit(item, ['children'])
: item
: undefined
Expand All @@ -68,6 +70,8 @@ export function transformItem (props: Omit<ItemProps, 'items'>, item: any): List
const _props = {
title,
value,
cascade: props.cascade,
children: props.cascade ? children : [],
...itemProps,
}

Expand All @@ -87,6 +91,7 @@ export function transformItems (props: Omit<ItemProps, 'items'>, items: ItemProp
'itemChildren',
'itemProps',
'returnObject',
'cascade',
'valueComparator',
])

Expand Down Expand Up @@ -141,6 +146,7 @@ export function useItems (props: ItemProps) {
'itemChildren',
'itemProps',
'returnObject',
'cascade',
'valueComparator',
])

Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/iconsets/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const aliases: IconAliases = {
plus: 'fas fa-plus',
minus: 'fas fa-minus',
calendar: 'fas fa-calendar',
submenuExpand: 'fas fa-caret-right',
treeviewCollapse: 'fas fa-caret-down',
treeviewExpand: 'fas fa-caret-right',
eyeDropper: 'fas fa-eye-dropper',
Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/iconsets/fa4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const aliases: IconAliases = {
plus: 'fa-plus',
minus: 'fa-minus',
calendar: 'fa-calendar',
submenuExpand: 'fa-caret-right',
treeviewCollapse: 'fa-caret-down',
treeviewExpand: 'fa-caret-right',
eyeDropper: 'fa-eye-dropper',
Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/iconsets/md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const aliases: IconAliases = {
plus: 'add',
minus: 'remove',
calendar: 'event',
submenuExpand: 'arrow_right',
treeviewCollapse: 'arrow_drop_down',
treeviewExpand: 'arrow_right',
eyeDropper: 'colorize',
Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/iconsets/mdi-svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const aliases: IconAliases = {
plus: 'svg:M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z',
minus: 'svg:M19,13H5V11H19V13Z',
calendar: 'svg:M19,19H5V8H19M16,1V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3H18V1M17,12H12V17H17V12Z',
submenuExpand: 'svg:M10,17L15,12L10,7V17Z',
treeviewCollapse: 'svg:M7,10L12,15L17,10H7Z',
treeviewExpand: 'svg:M10,17L15,12L10,7V17Z',
eyeDropper: 'svg:M19.35,11.72L17.22,13.85L15.81,12.43L8.1,20.14L3.5,22L2,20.5L3.86,15.9L11.57,8.19L10.15,6.78L12.28,4.65L19.35,11.72M16.76,3C17.93,1.83 19.83,1.83 21,3C22.17,4.17 22.17,6.07 21,7.24L19.08,9.16L14.84,4.92L16.76,3M5.56,17.03L4.5,19.5L6.97,18.44L14.4,11L13,9.6L5.56,17.03Z',
Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/iconsets/mdi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const aliases: IconAliases = {
plus: 'mdi-plus',
minus: 'mdi-minus',
calendar: 'mdi-calendar',
submenuExpand: 'mdi-menu-right',
treeviewCollapse: 'mdi-menu-down',
treeviewExpand: 'mdi-menu-right',
eyeDropper: 'mdi-eyedropper',
Expand Down
Loading