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
19 changes: 18 additions & 1 deletion packages/vuetify/src/components/VDataTable/VDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export type VDataTableSlotProps<T> = {
setPage: (value: number) => void
someSelected: boolean
allSelected: boolean
selectedItemsCount: number
allItemsCount: number
isSelected: ReturnType<typeof provideSelection>['isSelected']
select: ReturnType<typeof provideSelection>['select']
selectAll: ReturnType<typeof provideSelection>['selectAll']
Expand Down Expand Up @@ -207,6 +209,8 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
toggleSelect,
someSelected,
allSelected,
allItemsCount,
selectedItemsCount,
} = provideSelection(props, { allItems: items, currentPage: paginatedItemsWithoutGroups })

const { isExpanded, toggleExpand } = provideExpanded(props)
Expand Down Expand Up @@ -240,6 +244,8 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
setPage,
someSelected: someSelected.value,
allSelected: allSelected.value,
selectedItemsCount: selectedItemsCount.value,
allItemsCount: allItemsCount.value,
isSelected,
select,
selectAll,
Expand All @@ -255,6 +261,17 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
headers: headers.value,
}))

const displayTableHeader = computed(() => {
if (props.hideDefaultHeader) {
return false
}
if (!props.mobile) {
return true
}
// display mobile table header if showSelect enabled, or sort enabled and some column is sortable
return props.showSelect || (!props.disableSort && columns.value.some(i => i.sortable))
})

useRender(() => {
const dataTableFooterProps = VDataTableFooter.filterProps(props)
const dataTableHeadersProps = VDataTableHeaders.filterProps(omit(props, ['multiSort']))
Expand All @@ -280,7 +297,7 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
default: () => slots.default ? slots.default(slotProps.value) : (
<>
{ slots.colgroup?.(slotProps.value) }
{ !props.hideDefaultHeader && (
{ displayTableHeader.value && (
<thead key="thead">
<VDataTableHeaders
{ ...dataTableHeadersProps }
Expand Down
134 changes: 88 additions & 46 deletions packages/vuetify/src/components/VDataTable/VDataTableHeaders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ export const makeVDataTableHeadersProps = propsFactory({
headerProps: {
type: Object as PropType<Record<string, any>>,
},
selectAllText: {
type: String,
default: '$vuetify.dataTable.ariaLabel.selectAll',
},
deselectAllText: {
type: String,
default: '$vuetify.dataTable.ariaLabel.deselectAll',
},
selectAllSelectedText: {
type: String,
default: '$vuetify.dataTable.ariaLabel.selectAllSelected',
},

/** @deprecated */
sticky: Boolean,
Expand All @@ -96,7 +108,7 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
setup (props, { slots }) {
const { t } = useLocale()
const { toggleSort, sortBy, isSorted } = useSort()
const { someSelected, allSelected, selectAll, showSelectAll } = useSelection()
const { someSelected, allSelected, selectAll, showSelectAll, selectedItemsCount, allItemsCount } = useSelection()
const { columns, headers } = useHeaders()
const { loaderClasses } = useLoader(props)

Expand Down Expand Up @@ -159,6 +171,16 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
loaderClasses.value,
]))

const showSelectLabel = computed(() => {
const [key, count] = allSelected.value
? [props.deselectAllText, allItemsCount.value]
: someSelected.value
? [props.selectAllSelectedText, selectedItemsCount.value]
: [props.selectAllText, 0]

return t(key, [count])
})

const VDataTableHeaderCell = ({ column, x, y }: { column: InternalDataTableHeader, x: number, y: number }) => {
const noPadding = column.key === 'data-table-select' || column.key === 'data-table-expand'
const isEmpty = column.key === 'data-table-group' && column.width === 0 && !column.title
Expand Down Expand Up @@ -217,6 +239,7 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
if (column.key === 'data-table-select') {
return slots['header.data-table-select']?.(columnSlotProps) ?? (showSelectAll.value && (
<VCheckboxBtn
aria-label={ showSelectLabel.value }
color={ props.color }
density={ props.density }
modelValue={ allSelected.value }
Expand Down Expand Up @@ -258,7 +281,7 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({

const VDataTableMobileHeaderCell = () => {
const sortableColumns = computed<ItemProps['items']>(() => {
return columns.value.filter(column => column?.sortable && !props.disableSort)
return props.disableSort ? [] : columns.value.filter(column => column?.sortable)
})
const showSelectColumn = columns.value.find(column => column.key === 'data-table-select')
const sortingChips = computed<InternalDataTableHeader | InternalDataTableHeader[] | null>({
Expand All @@ -283,51 +306,70 @@ export const VDataTableHeaders = genericComponent<VDataTableHeadersSlots>()({
{ ...props.headerProps }
>
<div class="v-data-table-header__content">
<VSelect
v-model={ sortingChips.value }
chips
color={ props.color }
class="v-data-table__td-sort-select"
clearable
density="default"
items={ sortableColumns.value }
label={ t('$vuetify.dataTable.sortBy') }
multiple={ props.multiSort }
variant="underlined"
returnObject
onClick:clear={ () => sortBy.value = [] }
>
{{
append: showSelectColumn ? () => (
<VCheckboxBtn
color={ props.color }
density="compact"
modelValue={ allSelected.value }
indeterminate={ someSelected.value && !allSelected.value }
onUpdate:modelValue={ () => selectAll(!allSelected.value) }
/>
) : undefined,
chip: ({ internalItem }) => (
<VChip
onClick={ internalItem.raw.sortable ? () => toggleSort(internalItem.raw, undefined, true) : undefined }
onMousedown={ (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
}}
>
{ internalItem.title }
<VIcon
class={[
'v-data-table__td-sort-icon',
isSorted(internalItem.raw) && 'v-data-table__td-sort-icon-active',
]}
icon={ getSortIcon(internalItem.raw) }
size="small"
{ sortableColumns.value.length > 0 && (
<VSelect
key="sort-selector"
v-model={ sortingChips.value }
data-testid="v-data-table-thead-mobile-sort-selector"
chips
color={ props.color }
class="v-data-table__td-sort-select"
clearable
density="default"
items={ sortableColumns.value }
label={ t('$vuetify.dataTable.sortBy') }
multiple={ props.multiSort }
variant="underlined"
returnObject
onClick:clear={ () => sortBy.value = [] }
>
{{
append: showSelectColumn ? () => (
<VCheckboxBtn
data-testid="v-data-table-thead-mobile-select-checkbox"
color={ props.color }
density="compact"
aria-label={ showSelectLabel.value }
modelValue={ allSelected.value }
indeterminate={ someSelected.value && !allSelected.value }
onUpdate:modelValue={ () => selectAll(!allSelected.value) }
/>
</VChip>
),
}}
</VSelect>
) : undefined,
chip: ({ internalItem }) => (
<VChip
onClick={ internalItem.raw.sortable ? () => toggleSort(internalItem.raw, undefined, true) : undefined }
onMousedown={ (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
}}
>
{ internalItem.title }
<VIcon
class={[
'v-data-table__td-sort-icon',
isSorted(internalItem.raw) && 'v-data-table__td-sort-icon-active',
]}
icon={ getSortIcon(internalItem.raw) }
size="small"
/>
</VChip>
),
}}
</VSelect>
)}
{ showSelectColumn && sortableColumns.value.length === 0 && (
<VCheckboxBtn
key="select-checkbox"
class="flex-row-reverse"
data-testid="v-data-table-thead-mobile-select-checkbox"
color={ props.color }
density="compact"
label={ showSelectLabel.value }
modelValue={ allSelected.value }
indeterminate={ someSelected.value && !allSelected.value }
onUpdate:modelValue={ () => selectAll(!allSelected.value) }
/>
)}
</div>
</VDataTableColumn>
)
Expand Down
26 changes: 20 additions & 6 deletions packages/vuetify/src/components/VDataTable/VDataTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { useExpanded } from './composables/expand'
import { useHeaders } from './composables/headers'
import { useSelection } from './composables/select'
import { useSort } from './composables/sort'
import { useLocale } from '@/composables'
import { makeDensityProps } from '@/composables/density'
import { makeDisplayProps, useDisplay } from '@/composables/display'
import { IconValue } from '@/composables/icons'

// Utilities
import { toDisplayString, withModifiers } from 'vue'
import { computed, toDisplayString, withModifiers } from 'vue'
import { EventProp, genericComponent, getObjectValueByPath, propsFactory, useRender } from '@/util'

// Types
Expand Down Expand Up @@ -49,6 +50,14 @@ export const makeVDataTableRowProps = propsFactory({
type: IconValue,
default: '$expand',
},
selectRowText: {
type: String,
default: '$vuetify.dataTable.ariaLabel.selectRow',
},
deselectRowText: {
type: String,
default: '$vuetify.dataTable.ariaLabel.deselectRow',
},

onClick: EventProp<[MouseEvent]>(),
onContextmenu: EventProp<[MouseEvent]>(),
Expand All @@ -70,12 +79,16 @@ export const VDataTableRow = genericComponent<new <T>(
props: makeVDataTableRowProps(),

setup (props, { slots }) {
const { t } = useLocale()
const { displayClasses, mobile } = useDisplay(props, 'v-data-table__tr')
const { isSelected, toggleSelect, someSelected, allSelected, selectAll } = useSelection()
const { isExpanded, toggleExpand } = useExpanded()
const { toggleSort, sortBy, isSorted } = useSort()
const { columns } = useHeaders()

const selectedRow = computed(() => !!props.item && isSelected([props.item]))
const expandedRow = computed(() => !!props.item && isExpanded(props.item))

useRender(() => (
<tr
class={[
Expand All @@ -89,7 +102,7 @@ export const VDataTableRow = genericComponent<new <T>(
onContextmenu={ props.onContextmenu as any }
onDblclick={ props.onDblclick as any }
>
{ props.item && columns.value.map((column, i) => {
{ props.item && columns.value.map(column => {
const item = props.item!
const slotName = `item.${column.key}` as const
const headerSlotName = `header.${column.key}` as const
Expand Down Expand Up @@ -166,15 +179,16 @@ export const VDataTableRow = genericComponent<new <T>(
props: {
color: props.color,
disabled: !item.selectable,
modelValue: isSelected([item]),
modelValue: selectedRow.value,
onClick: withModifiers(() => toggleSelect(item), ['stop']),
},
}) ?? (
<VCheckboxBtn
color={ props.color }
disabled={ !item.selectable }
density={ props.density }
modelValue={ isSelected([item]) }
aria-label={ selectedRow.value ? t(props.deselectRowText) : t(props.selectRowText) }
modelValue={ selectedRow.value }
onClick={ withModifiers(
(event: Event) => toggleSelect(item, props.index, event as PointerEvent),
['stop']
Expand All @@ -187,14 +201,14 @@ export const VDataTableRow = genericComponent<new <T>(
return slots['item.data-table-expand']?.({
...slotProps,
props: {
icon: isExpanded(item) ? props.collapseIcon : props.expandIcon,
icon: expandedRow.value ? props.collapseIcon : props.expandIcon,
size: 'small',
variant: 'text',
onClick: withModifiers(() => toggleExpand(item), ['stop']),
},
}) ?? (
<VBtn
icon={ isExpanded(item) ? props.collapseIcon : props.expandIcon }
icon={ expandedRow.value ? props.collapseIcon : props.expandIcon }
size="small"
variant="text"
onClick={ withModifiers(() => toggleExpand(item), ['stop']) }
Expand Down
26 changes: 24 additions & 2 deletions packages/vuetify/src/components/VDataTable/VDataTableServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,16 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],

const { flatItems } = useGroupedItems(items, groupBy, opened, () => !!slots['group-summary'])

const { isSelected, select, selectAll, toggleSelect, someSelected, allSelected } = provideSelection(props, {
const {
isSelected,
select,
selectAll,
toggleSelect,
someSelected,
allSelected,
allItemsCount,
selectedItemsCount,
} = provideSelection(props, {
allItems: items,
currentPage: items,
})
Expand Down Expand Up @@ -133,6 +142,8 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],
setPage,
someSelected: someSelected.value,
allSelected: allSelected.value,
allItemsCount: allItemsCount.value,
selectedItemsCount: selectedItemsCount.value,
isSelected,
select,
selectAll,
Expand All @@ -148,6 +159,17 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],
headers: headers.value,
}))

const displayTableHeader = computed(() => {
if (props.hideDefaultHeader) {
return false
}
if (!props.mobile) {
return true
}
// display mobile table header if showSelect enabled, or sort enabled and some column is sortable
return props.showSelect || (!props.disableSort && columns.value.some(i => i.sortable))
})

useRender(() => {
const dataTableFooterProps = VDataTableFooter.filterProps(props)
const dataTableHeadersProps = VDataTableHeaders.filterProps(omit(props, ['multiSort']))
Expand All @@ -172,7 +194,7 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],
default: () => slots.default ? slots.default(slotProps.value) : (
<>
{ slots.colgroup?.(slotProps.value) }
{ !props.hideDefaultHeader && (
{ displayTableHeader.value && (
<thead key="thead" class="v-data-table__thead" role="rowgroup">
<VDataTableHeaders
{ ...dataTableHeadersProps }
Expand Down
Loading
Loading