diff --git a/docs/demo/grouping-columns-specified-colSpan-rowSpan.md b/docs/demo/grouping-columns-specified-colSpan-rowSpan.md
new file mode 100644
index 000000000..076b2d2cb
--- /dev/null
+++ b/docs/demo/grouping-columns-specified-colSpan-rowSpan.md
@@ -0,0 +1,8 @@
+---
+title: grouping-columns-specified-colSpan-rowSpan.tsx
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/grouping-columns-specified-colSpan-rowSpan.tsx b/docs/examples/grouping-columns-specified-colSpan-rowSpan.tsx
new file mode 100644
index 000000000..95639d0ff
--- /dev/null
+++ b/docs/examples/grouping-columns-specified-colSpan-rowSpan.tsx
@@ -0,0 +1,148 @@
+import React from 'react';
+import type { TableProps } from 'rc-table';
+import Table from 'rc-table';
+import '../../assets/index.less';
+
+const columns: TableProps['columns'] = [
+ {
+ title: '姓名',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '出勤',
+ rowSpan: 3,
+ children: [
+ {
+ title: '出勤',
+ dataIndex: 'attendance',
+ key: 'attendance',
+ },
+ {
+ title: '迟到',
+ dataIndex: 'late',
+ key: 'late',
+ },
+ {
+ title: '请假',
+ dataIndex: 'leave',
+ key: 'leave',
+ },
+ ],
+ },
+ {
+ title: '其它',
+ children: [
+ {
+ title: '年龄',
+ dataIndex: 'age',
+ key: 'age',
+ },
+ {
+ title: '住址',
+ children: [
+ {
+ title: '街道',
+ dataIndex: 'street',
+ key: 'street',
+ },
+ {
+ title: '小区',
+ children: [
+ {
+ title: '单元',
+ dataIndex: 'building',
+ key: 'building',
+ },
+ {
+ title: '门牌',
+ dataIndex: 'number',
+ key: 'number',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: '技能',
+ rowSpan: 2,
+ children: [
+ {
+ title: '前端',
+ dataIndex: 'frontend',
+ key: 'frontend',
+ },
+ {
+ title: '后端',
+ dataIndex: 'backend',
+ key: 'backend',
+ },
+ ],
+ },
+ {
+ title: '公司',
+ children: [
+ {
+ title: '地址',
+ dataIndex: 'companyAddress',
+ key: 'companyAddress',
+ },
+ {
+ title: '名称',
+ dataIndex: 'companyName',
+ key: 'companyName',
+ },
+ ],
+ },
+ {
+ title: '性别',
+ dataIndex: 'gender',
+ key: 'gender',
+ },
+];
+
+const data = [
+ {
+ key: '1',
+ name: '胡彦斌',
+ attendance: 20,
+ late: 0,
+ leave: 1,
+ age: 32,
+ street: '拱墅区和睦街道',
+ building: 1,
+ number: 2033,
+ frontend: 'S',
+ backend: 'S',
+ companyAddress: '西湖区湖底公园',
+ companyName: '湖底有限公司',
+ gender: '男',
+ },
+ {
+ key: '2',
+ name: '胡彦祖',
+ attendance: 20,
+ late: 0,
+ leave: 1,
+ age: 42,
+ street: '拱墅区和睦街道',
+ building: 3,
+ number: 2035,
+ frontend: 'S',
+ backend: 'S',
+ companyAddress: '西湖区湖底公园',
+ companyName: '湖底有限公司',
+ gender: '男',
+ },
+];
+
+const Demo = () => (
+
+
grouping columns specified colSpan & rowSpan
+
+
+);
+
+export default Demo;
diff --git a/src/Body/BodyRow.tsx b/src/Body/BodyRow.tsx
index 8cee145bf..846a111b4 100644
--- a/src/Body/BodyRow.tsx
+++ b/src/Body/BodyRow.tsx
@@ -106,6 +106,7 @@ function BodyRow(
const rowInfo = useRowInfo(record, rowKey, index, indent);
const {
prefixCls,
+ headMatrix,
flattenColumns,
expandedRowClassName,
expandedRowRender,
@@ -197,7 +198,7 @@ function BodyRow(
prefixCls={prefixCls}
component={RowComponent}
cellComponent={cellComponent}
- colSpan={flattenColumns.length}
+ colSpan={headMatrix[0]}
isEmpty={false}
>
{expandContent}
diff --git a/src/Body/index.tsx b/src/Body/index.tsx
index 3173fa22d..72d16c391 100644
--- a/src/Body/index.tsx
+++ b/src/Body/index.tsx
@@ -26,6 +26,7 @@ function Body(props: BodyProps) {
prefixCls,
getComponent,
onColumnResize,
+ headMatrix,
flattenColumns,
getRowKey,
expandedKeys,
@@ -35,6 +36,7 @@ function Body(props: BodyProps) {
'prefixCls',
'getComponent',
'onColumnResize',
+ 'headMatrix',
'flattenColumns',
'getRowKey',
'expandedKeys',
@@ -86,7 +88,7 @@ function Body(props: BodyProps) {
prefixCls={prefixCls}
component={trComponent}
cellComponent={tdComponent}
- colSpan={flattenColumns.length}
+ colSpan={headMatrix[0]}
isEmpty
>
{emptyNode}
diff --git a/src/FixedHolder/index.tsx b/src/FixedHolder/index.tsx
index 954cfaac4..50b9d33fa 100644
--- a/src/FixedHolder/index.tsx
+++ b/src/FixedHolder/index.tsx
@@ -7,7 +7,7 @@ import ColGroup from '../ColGroup';
import TableContext from '../context/TableContext';
import type { HeaderProps } from '../Header/Header';
import devRenderTimes from '../hooks/useRenderTimes';
-import type { ColumnsType, ColumnType, Direction } from '../interface';
+import type { CellType, ColumnsType, ColumnType, Direction } from '../interface';
function useColumnWidth(colWidths: readonly number[], columCount: number) {
return useMemo(() => {
@@ -48,6 +48,7 @@ const FixedHolder = React.forwardRef>((pro
className,
noData,
columns,
+ headCells,
flattenColumns,
colWidths,
columCount,
@@ -63,12 +64,10 @@ const FixedHolder = React.forwardRef>((pro
...restProps
} = props;
- const { prefixCls, scrollbarSize, isSticky, getComponent } = useContext(TableContext, [
- 'prefixCls',
- 'scrollbarSize',
- 'isSticky',
- 'getComponent',
- ]);
+ const { prefixCls, headMatrix, scrollbarSize, isSticky, getComponent } = useContext(
+ TableContext,
+ ['prefixCls', 'headMatrix', 'scrollbarSize', 'isSticky', 'getComponent'],
+ );
const TableComponent = getComponent(['header', 'table'], 'table');
const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;
@@ -111,12 +110,27 @@ const FixedHolder = React.forwardRef>((pro
className: `${prefixCls}-cell-scrollbar`,
}),
};
+ const ScrollBarColumnCell: CellType = {
+ column: ScrollBarColumn,
+ colSpan: 1,
+ colStart: headMatrix[0],
+ colEnd: headMatrix[0],
+ rowSpan: headMatrix[1],
+ };
const columnsWithScrollbar = useMemo>(
() => (combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns),
[combinationScrollBarSize, columns],
);
+ const headCellsWithScrollbar = useMemo[][]>(() => {
+ if (combinationScrollBarSize) {
+ const [cell, ...cells] = headCells;
+ return [[...cell, ScrollBarColumnCell], ...cells];
+ }
+ return headCells;
+ }, [combinationScrollBarSize, headCells]);
+
const flattenColumnsWithScrollbar = useMemo(
() => (combinationScrollBarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
[combinationScrollBarSize, flattenColumns],
@@ -165,6 +179,7 @@ const FixedHolder = React.forwardRef>((pro
...restProps,
stickyOffsets: headerStickyOffsets,
columns: columnsWithScrollbar,
+ headCells: headCellsWithScrollbar,
flattenColumns: flattenColumnsWithScrollbar,
})}
diff --git a/src/Header/Header.tsx b/src/Header/Header.tsx
index f21b817b2..8d705e0d5 100644
--- a/src/Header/Header.tsx
+++ b/src/Header/Header.tsx
@@ -4,7 +4,6 @@ import TableContext, { responseImmutable } from '../context/TableContext';
import devRenderTimes from '../hooks/useRenderTimes';
import type {
CellType,
- ColumnGroupType,
ColumnsType,
ColumnType,
GetComponentProps,
@@ -12,79 +11,9 @@ import type {
} from '../interface';
import HeaderRow from './HeaderRow';
-function parseHeaderRows(
- rootColumns: ColumnsType,
-): CellType[][] {
- const rows: CellType[][] = [];
-
- function fillRowCells(
- columns: ColumnsType,
- colIndex: number,
- rowIndex: number = 0,
- ): number[] {
- // Init rows
- rows[rowIndex] = rows[rowIndex] || [];
-
- let currentColIndex = colIndex;
- const colSpans: number[] = columns.filter(Boolean).map(column => {
- const cell: CellType = {
- key: column.key,
- className: column.className || '',
- children: column.title,
- column,
- colStart: currentColIndex,
- };
-
- let colSpan: number = 1;
-
- const subColumns = (column as ColumnGroupType).children;
- if (subColumns && subColumns.length > 0) {
- colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce(
- (total, count) => total + count,
- 0,
- );
- cell.hasSubColumns = true;
- }
-
- if ('colSpan' in column) {
- ({ colSpan } = column);
- }
-
- if ('rowSpan' in column) {
- cell.rowSpan = column.rowSpan;
- }
-
- cell.colSpan = colSpan;
- cell.colEnd = cell.colStart + colSpan - 1;
- rows[rowIndex].push(cell);
-
- currentColIndex += colSpan;
-
- return colSpan;
- });
-
- return colSpans;
- }
-
- // Generate `rows` cell data
- fillRowCells(rootColumns, 0);
-
- // Handle `rowSpan`
- const rowCount = rows.length;
- for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
- rows[rowIndex].forEach(cell => {
- if (!('rowSpan' in cell) && !cell.hasSubColumns) {
- // eslint-disable-next-line no-param-reassign
- cell.rowSpan = rowCount - rowIndex;
- }
- });
- }
-
- return rows;
-}
-
export interface HeaderProps {
columns: ColumnsType;
+ headCells: CellType[][];
flattenColumns: readonly ColumnType[];
stickyOffsets: StickyOffsets;
onHeaderRow: GetComponentProps[]>;
@@ -95,18 +24,16 @@ const Header = (props: HeaderProps) => {
devRenderTimes(props);
}
- const { stickyOffsets, columns, flattenColumns, onHeaderRow } = props;
+ const { stickyOffsets, headCells, flattenColumns, onHeaderRow } = props;
const { prefixCls, getComponent } = useContext(TableContext, ['prefixCls', 'getComponent']);
- const rows = React.useMemo[][]>(() => parseHeaderRows(columns), [columns]);
-
const WrapperComponent = getComponent(['header', 'wrapper'], 'thead');
const trComponent = getComponent(['header', 'row'], 'tr');
const thComponent = getComponent(['header', 'cell'], 'th');
return (
- {rows.map((row, rowIndex) => {
+ {headCells.map((row, rowIndex) => {
const rowNode = (
(
const scrollX = scroll?.x;
const [componentWidth, setComponentWidth] = React.useState(0);
- const [columns, flattenColumns, flattenScrollX, hasGapFixed] = useColumns(
+ const [columns, headCells, headMatrix, flattenColumns, flattenScrollX, hasGapFixed] = useColumns(
{
...props,
...expandableConfig,
@@ -312,9 +312,10 @@ function Table(
const columnContext = React.useMemo(
() => ({
columns,
+ headCells,
flattenColumns,
}),
- [columns, flattenColumns],
+ [columns, headCells, flattenColumns],
);
// ======================= Refs =======================
@@ -825,6 +826,7 @@ function Table(
// Column
columns,
+ headMatrix,
flattenColumns,
onColumnResize,
@@ -874,6 +876,7 @@ function Table(
// Column
columns,
+ headMatrix,
flattenColumns,
onColumnResize,
diff --git a/src/context/TableContext.tsx b/src/context/TableContext.tsx
index fd810b378..192379711 100644
--- a/src/context/TableContext.tsx
+++ b/src/context/TableContext.tsx
@@ -2,6 +2,7 @@ import { createContext, createImmutable } from '@rc-component/context';
import type {
ColumnsType,
ColumnType,
+ HeadMatrix,
Direction,
ExpandableType,
ExpandedRowRender,
@@ -54,6 +55,7 @@ export interface TableContextProps {
// Column
columns: ColumnsType;
+ headMatrix: HeadMatrix;
flattenColumns: readonly ColumnType[];
onColumnResize: (columnKey: React.Key, width: number) => void;
diff --git a/src/hooks/useColumns/index.tsx b/src/hooks/useColumns/index.tsx
index 99490db32..69427ffed 100644
--- a/src/hooks/useColumns/index.tsx
+++ b/src/hooks/useColumns/index.tsx
@@ -3,16 +3,19 @@ import warning from 'rc-util/lib/warning';
import * as React from 'react';
import { EXPAND_COLUMN } from '../../constant';
import type {
+ CellType,
ColumnGroupType,
ColumnsType,
ColumnType,
Direction,
+ HeadMatrix,
FixedType,
GetRowKey,
Key,
RenderExpandIcon,
TriggerEventHandler,
} from '../../interface';
+import { convertColumns } from '../../utils/convertUtil';
import { INTERNAL_COL_DEFINE } from '../../utils/legacyUtil';
import useWidthColumns from './useWidthColumns';
@@ -55,39 +58,6 @@ function filterHiddenColumns(
});
}
-function flatColumns(
- columns: ColumnsType,
- parentKey = 'key',
-): ColumnType[] {
- return columns
- .filter(column => column && typeof column === 'object')
- .reduce((list, column, index) => {
- const { fixed } = column;
- // Convert `fixed='true'` to `fixed='left'` instead
- const parsedFixed = fixed === true ? 'left' : fixed;
- const mergedKey = `${parentKey}-${index}`;
-
- const subColumns = (column as ColumnGroupType).children;
- if (subColumns && subColumns.length > 0) {
- return [
- ...list,
- ...flatColumns(subColumns, mergedKey).map(subColum => ({
- fixed: parsedFixed,
- ...subColum,
- })),
- ];
- }
- return [
- ...list,
- {
- key: mergedKey,
- ...column,
- fixed: parsedFixed,
- },
- ];
- }, []);
-}
-
function revertForRtl(columns: ColumnsType): ColumnsType {
return columns.map(column => {
const { fixed, ...restProps } = column;
@@ -150,6 +120,8 @@ function useColumns(
transformColumns: (columns: ColumnsType) => ColumnsType,
): [
columns: ColumnsType,
+ headCells: CellType[][],
+ headMatrix: HeadMatrix,
flattenColumns: readonly ColumnType[],
realScrollWidth: undefined | number,
hasGapFixed: boolean,
@@ -263,13 +235,17 @@ function useColumns(
return finalColumns;
}, [transformColumns, withExpandColumns, direction]);
+ const [headCells, headMatrix, lastColumns] = React.useMemo(() => {
+ return convertColumns(mergedColumns);
+ }, [mergedColumns]);
+
// ========================== Flatten =========================
const flattenColumns = React.useMemo(() => {
if (direction === 'rtl') {
- return revertForRtl(flatColumns(mergedColumns));
+ return revertForRtl(lastColumns);
}
- return flatColumns(mergedColumns);
- }, [mergedColumns, direction, scrollWidth]);
+ return lastColumns;
+ }, [lastColumns, direction, scrollWidth]);
// ========================= Gap Fixed ========================
const hasGapFixed = React.useMemo(() => {
@@ -313,7 +289,7 @@ function useColumns(
clientWidth,
);
- return [mergedColumns, filledColumns, realScrollWidth, hasGapFixed];
+ return [mergedColumns, headCells, headMatrix, filledColumns, realScrollWidth, hasGapFixed];
}
export default useColumns;
diff --git a/src/hooks/useRowInfo.tsx b/src/hooks/useRowInfo.tsx
index bba123a89..5b4085081 100644
--- a/src/hooks/useRowInfo.tsx
+++ b/src/hooks/useRowInfo.tsx
@@ -14,6 +14,7 @@ export default function useRowInfo(
TableContextProps,
| 'prefixCls'
| 'fixedInfoList'
+ | 'headMatrix'
| 'flattenColumns'
| 'expandableType'
| 'expandRowByClick'
@@ -40,6 +41,7 @@ export default function useRowInfo(
const context: TableContextProps = useContext(TableContext, [
'prefixCls',
'fixedInfoList',
+ 'headMatrix',
'flattenColumns',
'expandableType',
'expandRowByClick',
diff --git a/src/interface.ts b/src/interface.ts
index 06213c043..a38201ca6 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -26,6 +26,12 @@ export type DefaultRecordType = Record;
export type TableLayout = 'auto' | 'fixed';
+/**
+ * 0 => horizontal count of table head cells
+ * 1 => vertical count of table head cells
+ */
+export type HeadMatrix = [number, number];
+
export type ScrollConfig = {
index?: number;
key?: Key;
@@ -70,7 +76,11 @@ export type Direction = 'ltr' | 'rtl';
// SpecialString will be removed in antd@6
export type SpecialString = T | (string & {});
-export type DataIndex = DeepNamePath | SpecialString | number | (SpecialString | number)[];
+export type DataIndex =
+ | DeepNamePath
+ | SpecialString
+ | number
+ | (SpecialString | number)[];
export type CellEllipsisType = { showTitle?: boolean } | boolean;
diff --git a/src/utils/convertUtil.ts b/src/utils/convertUtil.ts
new file mode 100644
index 000000000..996305a05
--- /dev/null
+++ b/src/utils/convertUtil.ts
@@ -0,0 +1,240 @@
+import type { CellType, ColumnsType, ColumnType, FixedType, HeadMatrix, Key } from '../interface';
+
+interface Column extends ColumnType {
+ colSpanSpecified?: number;
+ rowSpanSpecified?: number;
+ colStart?: number;
+ colEnd?: number;
+ children?: Column[];
+}
+
+export function convertColumns>(
+ columns: ColumnsType,
+): [
+ headCells: CellType[][],
+ headMatrix: HeadMatrix,
+ lastColumns: readonly ColumnType[],
+] {
+ if (!Array.isArray(columns) || columns.length === 0) {
+ return [[[]], [0, 0], []];
+ }
+
+ let depthCurr = 0;
+ let depthNext = 0;
+ const nodePos: {
+ index: number;
+ total: number;
+ }[] = [{ index: columns.length, total: columns.length }];
+ const pKeys: Key[] = [];
+ const rowSpans: number[] = [];
+ const columnsMap = new Map[]>();
+ const flatMap = new Map<
+ Key,
+ /**
+ * 0 => current column
+ * 1 => parent key
+ * 2 => parents row count
+ */
+ [Column, undefined | Key, undefined | number]
+ >();
+ const treeMap = new Map, Column[]>();
+ const lastSet = new Set>();
+
+ let rowCountMax = 0;
+ let tree: Column[] = [...columns];
+
+ while (tree.length > 0) {
+ depthCurr = depthNext;
+
+ nodePos.splice(depthCurr + 1);
+ rowSpans.splice(depthCurr);
+
+ nodePos[depthCurr].index--;
+
+ if (nodePos[depthCurr].index <= 0) {
+ depthNext = 0;
+
+ for (let i = nodePos.length - 1; i >= 0; i--) {
+ if (nodePos[i].index > 0) {
+ depthNext = i;
+ break;
+ }
+ }
+ }
+
+ const node = tree.shift();
+
+ if (!node || typeof node !== 'object' || node.hidden) {
+ const { index, total } = nodePos[depthCurr];
+ const currIndex = total - 1 - index;
+ const parentKey = pKeys[depthCurr - 1];
+
+ if (parentKey === undefined) {
+ columns.splice(currIndex, 1);
+ } else {
+ const parent = flatMap.get(parentKey)?.[0];
+ parent?.children?.splice(currIndex, 1);
+ }
+
+ nodePos[depthCurr].total--;
+ continue;
+ }
+
+ const colSpanSpecified = node.colSpan;
+ const rowSpanSpecified = node.rowSpan;
+ const colSpan = node.colSpan ?? 1;
+ const rowSpan = node.rowSpan ?? 1;
+ node.colSpan = colSpan;
+ node.rowSpan = rowSpan;
+ node.fixed = node.fixed === true ? 'left' : node.fixed;
+
+ const parentsRowCount = rowSpans.reduce((acc, num) => acc + num, 0);
+ if (!columnsMap.has(parentsRowCount)) {
+ columnsMap.set(parentsRowCount, []);
+ }
+ columnsMap.get(parentsRowCount)!.push(node);
+ // // mark vertical position of cell in table head matrix
+ // node.rowStart = parentsRowCount;
+ // node.rowEnd = node.rowStart + rowSpan - 1;
+
+ const pathKey = nodePos.reduce((acc, { index, total }) => {
+ return `${acc}-${total - 1 - index}`;
+ }, 'key');
+ node.key = node.key && !flatMap.has(node.key) ? node.key : pathKey;
+ flatMap.set(node.key, [node, pKeys[depthCurr - 1], parentsRowCount]);
+
+ const leaf = node.children;
+ if (Array.isArray(leaf) && leaf.length > 0) {
+ depthNext = depthCurr + 1;
+ nodePos[depthNext] = { index: leaf.length, total: leaf.length };
+ rowSpans[depthCurr] = rowSpan;
+ pKeys[depthCurr] = node.key;
+
+ node.colSpanSpecified = colSpanSpecified;
+ if (!treeMap.has(node)) {
+ treeMap.set(node, []);
+ }
+ treeMap.get(node)!.push(...leaf);
+ tree = [...leaf, ...tree];
+ } else {
+ delete node.children;
+ node.rowSpanSpecified = rowSpanSpecified;
+ lastSet.add(node);
+
+ // correct vertical cells count of table head matrix
+ const rowCount = node.rowSpan + parentsRowCount;
+ if (rowCount > rowCountMax) {
+ rowCountMax = rowCount;
+ }
+ }
+ }
+
+ // correct colSpan of parent column in default state
+ [...treeMap.keys()].reverse().forEach(column => {
+ const { colSpanSpecified } = column;
+ delete column.colSpanSpecified;
+
+ if (Number.isInteger(colSpanSpecified)) {
+ return;
+ }
+
+ const children = treeMap.get(column);
+ column.colSpan = children!.reduce((acc, { colSpan = 1 }) => acc + colSpan, 0);
+ });
+
+ const lastColumns: ColumnType[] = [];
+ lastSet.forEach(column => {
+ // correct rowSpan of column in default state
+ const { rowSpanSpecified } = column;
+ const parentsRowCount = column.key ? flatMap.get(column.key)?.[2] ?? 0 : 0;
+ delete column.rowSpanSpecified;
+
+ if (!Number.isInteger(rowSpanSpecified)) {
+ column.rowSpan = rowCountMax - parentsRowCount;
+ // // correct vertical position of cell at last of branch
+ // column.rowEnd = column.rowStart ?? 0 + column.rowSpan - 1;
+ }
+
+ // collect column at last of branch and correct fixed prop
+ let size = flatMap.size;
+ let key = column.key;
+ const columnsFixed: (undefined | FixedType)[] = [column.fixed];
+
+ while (size-- > 0) {
+ const parentKey = key ? flatMap.get(key)?.[1] : null;
+
+ if (!parentKey) {
+ break;
+ }
+
+ const parentColumn = flatMap.get(parentKey)?.[0];
+ key = parentColumn?.key;
+ columnsFixed.unshift(parentColumn?.fixed);
+ }
+
+ let prev = 0,
+ next = 1;
+ while (next < columnsFixed.length) {
+ if (columnsFixed[prev]) {
+ columnsFixed[next] = columnsFixed[prev];
+ }
+ prev++;
+ next++;
+ }
+
+ column.fixed = columnsFixed.pop();
+ lastColumns.push(column as ColumnType);
+ });
+
+ let colCountMax = 0;
+ const headCells: CellType[][] = [];
+ // generate data of table head and handle horizontal related of table head matrix
+ [...columnsMap.keys()].sort().forEach(key => {
+ const group: Column[] = columnsMap.get(key) ?? [];
+
+ const cells = group.map((column, index) => {
+ // mark horizontal position of cell in table head matrix
+ const parentKey = column.key ? flatMap.get(column.key)?.[1] : null;
+ const parentColumn = parentKey ? flatMap.get(parentKey)?.[0] : null;
+ const parentColStart = parentColumn?.colStart ?? 0;
+
+ const previousIndex = index - 1;
+ const previousColumn = previousIndex >= 0 ? group[previousIndex] : null;
+ const previousColEnd = previousColumn?.colEnd ?? -1;
+
+ const colSpan = column.colSpan ?? 1;
+ const rowSpan = column.rowSpan ?? 1;
+ const colStart = Math.max(parentColStart, previousColEnd + 1);
+ const colEnd = colStart + colSpan - 1;
+
+ // correct horizontal cells count of table head matrix
+ if (colEnd + 1 > colCountMax) {
+ colCountMax = colEnd + 1;
+ }
+
+ // avoid fixed columns rendering bug, but does not exist on type ColumnType
+ column.colStart = colStart;
+ column.colEnd = colEnd;
+
+ // table head cell data
+ const item = {
+ column,
+ key: column.key,
+ className: column.className || '',
+ children: column.title,
+ colSpan,
+ rowSpan,
+ colStart,
+ colEnd,
+ };
+
+ return item as CellType;
+ });
+
+ headCells.push(cells);
+ });
+
+ const headMatrix: HeadMatrix = [colCountMax, rowCountMax];
+
+ return [headCells, headMatrix, lastColumns];
+}
diff --git a/tests/GroupingColumns.spec.jsx b/tests/GroupingColumns.spec.jsx
index 822fb4e7f..2fd3d9867 100644
--- a/tests/GroupingColumns.spec.jsx
+++ b/tests/GroupingColumns.spec.jsx
@@ -155,6 +155,261 @@ describe('Table with grouping columns', () => {
expect(titleA.prop('rowSpan')).toBe(3);
});
+ it('more strange layout', () => {
+ /**
+ * +---+---+-----------+-----------+-----------+
+ * | A | B | C | | E |
+ * +---+---+---+-------+ D +-----------+
+ * | | G | H | | I |
+ * | +---+---+---+---+---+---+---+---+---+
+ * | F | | | | | N | O | P | |
+ * | | J | K | L | M +---+---+---+ Q |
+ * | | | | | | R | S | T | |
+ * +-------+---+---+---+---+---+---+---+---+---+
+ */
+
+ const columns = [
+ {
+ title: 'A',
+ className: 'title-a',
+ colSpan: 1,
+ children: [
+ {
+ title: 'F',
+ className: 'title-f',
+ colSpan: 2,
+ dataIndex: 'F',
+ key: 'F',
+ onCell: () => ({ colSpan: 2 }),
+ },
+ ],
+ },
+ {
+ title: 'B',
+ className: 'title-b',
+ rowSpan: 1,
+ onCell: () => ({ colSpan: 0 }),
+ },
+ {
+ title: 'C',
+ className: 'title-c',
+ children: [
+ {
+ title: 'G',
+ className: 'title-g',
+ children: [
+ {
+ title: 'J',
+ className: 'title-j',
+ dataIndex: 'J',
+ key: 'J',
+ },
+ ],
+ },
+ {
+ title: 'H',
+ className: 'title-h',
+ colSpan: 2,
+ children: [
+ {
+ title: 'K',
+ className: 'title-k',
+ dataIndex: 'K',
+ key: 'K',
+ },
+ {
+ title: 'L',
+ className: 'title-l',
+ colSpan: 2,
+ dataIndex: 'L',
+ key: 'L',
+ onCell: () => ({ colSpan: 2 }),
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: 'D',
+ className: 'title-d',
+ colSpan: 3,
+ rowSpan: 2,
+ children: [
+ {
+ title: 'M',
+ className: 'title-m',
+ dataIndex: 'M',
+ key: 'M',
+ },
+ {
+ title: 'N',
+ className: 'title-n',
+ children: [
+ {
+ title: 'R',
+ className: 'title-r',
+ dataIndex: 'R',
+ key: 'R',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: 'E',
+ className: 'title-e',
+ children: [
+ {
+ title: 'I',
+ className: 'title-i',
+ children: [
+ {
+ title: 'O',
+ className: 'title-o',
+ children: [
+ {
+ title: 'S',
+ className: 'title-s',
+ dataIndex: 'S',
+ key: 'S',
+ },
+ ],
+ },
+ {
+ title: 'P',
+ className: 'title-p',
+ children: [
+ {
+ title: 'T',
+ className: 'title-t',
+ dataIndex: 'T',
+ key: 'T',
+ },
+ ],
+ },
+ {
+ title: 'Q',
+ className: 'title-q',
+ dataIndex: 'Q',
+ key: 'Q',
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ const data = [
+ {
+ key: '1',
+ F: 'F-1',
+ J: 'J-1',
+ K: 'K-1',
+ L: 'L-1',
+ M: 'M-1',
+ R: 'R-1',
+ S: 'S-1',
+ T: 'T-1',
+ Q: 'Q-1',
+ },
+ {
+ key: '2',
+ F: 'F-2',
+ J: 'J-2',
+ K: 'K-2',
+ L: 'L-2',
+ M: 'M-2',
+ R: 'R-2',
+ S: 'S-2',
+ T: 'T-2',
+ Q: 'Q-2',
+ },
+ ];
+
+ const wrapper = mount();
+ expect(wrapper.find('.rc-table-placeholder .rc-table-cell').prop('colSpan')).toEqual(11);
+
+ const titleA = wrapper.find('th.title-a');
+ expect(titleA.prop('colSpan')).toBe(null);
+ expect(titleA.prop('rowSpan')).toBe(null);
+
+ const titleB = wrapper.find('th.title-b');
+ expect(titleB.prop('colSpan')).toBe(null);
+ expect(titleB.prop('rowSpan')).toBe(null);
+
+ const titleC = wrapper.find('th.title-c');
+ expect(titleC.prop('colSpan')).toBe(3);
+ expect(titleC.prop('rowSpan')).toBe(null);
+
+ const titleD = wrapper.find('th.title-d');
+ expect(titleD.prop('colSpan')).toBe(3);
+ expect(titleD.prop('rowSpan')).toBe(2);
+
+ const titleE = wrapper.find('th.title-e');
+ expect(titleE.prop('colSpan')).toBe(3);
+ expect(titleE.prop('rowSpan')).toBe(null);
+
+ const titleF = wrapper.find('th.title-f');
+ expect(titleF.prop('colSpan')).toBe(2);
+ expect(titleF.prop('rowSpan')).toBe(3);
+
+ const titleG = wrapper.find('th.title-g');
+ expect(titleG.prop('colSpan')).toBe(null);
+ expect(titleG.prop('rowSpan')).toBe(null);
+
+ const titleH = wrapper.find('th.title-h');
+ expect(titleH.prop('colSpan')).toBe(2);
+ expect(titleH.prop('rowSpan')).toBe(null);
+
+ const titleI = wrapper.find('th.title-i');
+ expect(titleI.prop('colSpan')).toBe(3);
+ expect(titleI.prop('rowSpan')).toBe(null);
+
+ const titleJ = wrapper.find('th.title-j');
+ expect(titleJ.prop('colSpan')).toBe(null);
+ expect(titleJ.prop('rowSpan')).toBe(2);
+
+ const titleK = wrapper.find('th.title-k');
+ expect(titleK.prop('colSpan')).toBe(null);
+ expect(titleK.prop('rowSpan')).toBe(2);
+
+ const titleL = wrapper.find('th.title-l');
+ expect(titleL.prop('colSpan')).toBe(2);
+ expect(titleL.prop('rowSpan')).toBe(2);
+
+ const titleM = wrapper.find('th.title-m');
+ expect(titleM.prop('colSpan')).toBe(null);
+ expect(titleM.prop('rowSpan')).toBe(2);
+
+ const titleN = wrapper.find('th.title-n');
+ expect(titleN.prop('colSpan')).toBe(null);
+ expect(titleN.prop('rowSpan')).toBe(null);
+
+ const titleO = wrapper.find('th.title-o');
+ expect(titleO.prop('colSpan')).toBe(null);
+ expect(titleO.prop('rowSpan')).toBe(null);
+
+ const titleP = wrapper.find('th.title-p');
+ expect(titleP.prop('colSpan')).toBe(null);
+ expect(titleP.prop('rowSpan')).toBe(null);
+
+ const titleQ = wrapper.find('th.title-q');
+ expect(titleQ.prop('colSpan')).toBe(null);
+ expect(titleQ.prop('rowSpan')).toBe(2);
+
+ const titleR = wrapper.find('th.title-r');
+ expect(titleR.prop('colSpan')).toBe(null);
+ expect(titleR.prop('rowSpan')).toBe(null);
+
+ const titleS = wrapper.find('th.title-s');
+ expect(titleS.prop('colSpan')).toBe(null);
+ expect(titleS.prop('rowSpan')).toBe(null);
+
+ const titleT = wrapper.find('th.title-t');
+ expect(titleT.prop('colSpan')).toBe(null);
+ expect(titleT.prop('rowSpan')).toBe(null);
+ });
+
it('hidden column', () => {
const columns = [
{
diff --git a/tests/Table.spec.jsx b/tests/Table.spec.jsx
index 872cd89c2..48dde20d3 100644
--- a/tests/Table.spec.jsx
+++ b/tests/Table.spec.jsx
@@ -581,7 +581,17 @@ describe('Table.Basic', () => {
expect(wrapper.find('thead tr').props().id).toEqual('header-row-0');
expect(onHeaderRow).toHaveBeenCalledWith(
- [{ title: 'Name', dataIndex: 'name', key: 'name' }],
+ [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ colSpan: 1,
+ rowSpan: 1,
+ colStart: 0,
+ colEnd: 0,
+ },
+ ],
0,
);
});