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, ); });