feat(Table): add support for colspan and rowspan (#4460)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Artem Alesenko
2025-07-14 13:18:17 +03:00
committed by GitHub
parent d983af93b3
commit 7ef19333f0

View File

@@ -49,6 +49,13 @@ declare module '@tanstack/table-core' {
th?: string | Record<string, string> | ((cell: Header<TData, TValue>) => string | Record<string, string>) th?: string | Record<string, string> | ((cell: Header<TData, TValue>) => string | Record<string, string>)
td?: string | Record<string, string> | ((cell: Cell<TData, TValue>) => string | Record<string, string>) td?: string | Record<string, string> | ((cell: Cell<TData, TValue>) => string | Record<string, string>)
} }
colspan?: {
td?: string | ((cell: Cell<TData, TValue>) => string)
}
rowspan?: {
td?: string | ((cell: Cell<TData, TValue>) => string)
}
} }
interface TableMeta<TData> { interface TableMeta<TData> {
@@ -376,6 +383,14 @@ function onRowContextmenu(e: Event, row: TableRow<T>) {
} }
} }
function resolveValue<T, A = undefined>(prop: T | ((arg: A) => T), arg?: A): T | undefined {
if (typeof prop === 'function') {
// @ts-expect-error: TS can't know if prop is a function here
return prop(arg)
}
return prop
}
watch( watch(
() => props.data, () => { () => props.data, () => {
data.value = props.data ? [...props.data] : [] data.value = props.data ? [...props.data] : []
@@ -405,10 +420,11 @@ defineExpose({
:data-pinned="header.column.getIsPinned()" :data-pinned="header.column.getIsPinned()"
:scope="header.colSpan > 1 ? 'colgroup' : 'col'" :scope="header.colSpan > 1 ? 'colgroup' : 'col'"
:colspan="header.colSpan > 1 ? header.colSpan : undefined" :colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({ :class="ui.th({
class: [ class: [
props.ui?.th, props.ui?.th,
typeof header.column.columnDef.meta?.class?.th === 'function' ? header.column.columnDef.meta.class.th(header) : header.column.columnDef.meta?.class?.th resolveValue(header.column.columnDef.meta?.class?.th, header)
], ],
pinned: !!header.column.getIsPinned() pinned: !!header.column.getIsPinned()
})" })"
@@ -436,10 +452,10 @@ defineExpose({
:class="ui.tr({ :class="ui.tr({
class: [ class: [
props.ui?.tr, props.ui?.tr,
typeof tableApi.options.meta?.class?.tr === 'function' ? tableApi.options.meta.class.tr(row) : tableApi.options.meta?.class?.tr resolveValue(tableApi.options.meta?.class?.tr, row)
] ]
})" })"
:style="typeof tableApi.options.meta?.style?.tr === 'function' ? tableApi.options.meta?.style?.tr(row) : tableApi.options.meta?.style?.tr" :style="resolveValue(tableApi.options.meta?.style?.tr, row)"
@click="onRowSelect($event, row)" @click="onRowSelect($event, row)"
@pointerenter="onRowHover($event, row)" @pointerenter="onRowHover($event, row)"
@pointerleave="onRowHover($event, null)" @pointerleave="onRowHover($event, null)"
@@ -449,14 +465,16 @@ defineExpose({
v-for="cell in row.getVisibleCells()" v-for="cell in row.getVisibleCells()"
:key="cell.id" :key="cell.id"
:data-pinned="cell.column.getIsPinned()" :data-pinned="cell.column.getIsPinned()"
:colspan="resolveValue(cell.column.columnDef.meta?.colspan?.td, cell)"
:rowspan="resolveValue(cell.column.columnDef.meta?.rowspan?.td, cell)"
:class="ui.td({ :class="ui.td({
class: [ class: [
props.ui?.td, props.ui?.td,
typeof cell.column.columnDef.meta?.class?.td === 'function' ? cell.column.columnDef.meta.class.td(cell) : cell.column.columnDef.meta?.class?.td resolveValue(cell.column.columnDef.meta?.class?.td, cell)
], ],
pinned: !!cell.column.getIsPinned() pinned: !!cell.column.getIsPinned()
})" })"
:style="typeof cell.column.columnDef.meta?.style?.td === 'function' ? cell.column.columnDef.meta.style.td(cell) : cell.column.columnDef.meta?.style?.td" :style="resolveValue(cell.column.columnDef.meta?.style?.td, cell)"
> >
<slot :name="`${cell.column.id}-cell`" v-bind="cell.getContext()"> <slot :name="`${cell.column.id}-cell`" v-bind="cell.getContext()">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" /> <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
@@ -497,14 +515,15 @@ defineExpose({
:key="header.id" :key="header.id"
:data-pinned="header.column.getIsPinned()" :data-pinned="header.column.getIsPinned()"
:colspan="header.colSpan > 1 ? header.colSpan : undefined" :colspan="header.colSpan > 1 ? header.colSpan : undefined"
:rowspan="header.rowSpan > 1 ? header.rowSpan : undefined"
:class="ui.th({ :class="ui.th({
class: [ class: [
props.ui?.th, props.ui?.th,
typeof header.column.columnDef.meta?.class?.th === 'function' ? header.column.columnDef.meta.class.th(header) : header.column.columnDef.meta?.class?.th resolveValue(header.column.columnDef.meta?.class?.th, header)
], ],
pinned: !!header.column.getIsPinned() pinned: !!header.column.getIsPinned()
})" })"
:style="typeof header.column.columnDef.meta?.style?.th === 'function' ? header.column.columnDef.meta.style.th(header) : header.column.columnDef.meta?.style?.th" :style="resolveValue(header.column.columnDef.meta?.style?.th, header)"
> >
<slot :name="`${header.id}-footer`" v-bind="header.getContext()"> <slot :name="`${header.id}-footer`" v-bind="header.getContext()">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.footer" :props="header.getContext()" /> <FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.footer" :props="header.getContext()" />