diff --git a/docs/demo/expandedRowSpan.md b/docs/demo/expandedRowSpan.md
new file mode 100644
index 00000000..046c3f8b
--- /dev/null
+++ b/docs/demo/expandedRowSpan.md
@@ -0,0 +1,8 @@
+---
+title: expandedRowSpan
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/demo/expandedSticky.md b/docs/demo/expandedSticky.md
new file mode 100644
index 00000000..df58f578
--- /dev/null
+++ b/docs/demo/expandedSticky.md
@@ -0,0 +1,8 @@
+---
+title: expandedSticky
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/expandedRowSpan.tsx b/docs/examples/expandedRowSpan.tsx
new file mode 100644
index 00000000..5aeba748
--- /dev/null
+++ b/docs/examples/expandedRowSpan.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import Table from 'rc-table';
+import '../../assets/index.less';
+import type { ColumnsType } from '@/interface';
+
+const columns: ColumnsType = [
+ {
+ title: '手机号',
+ dataIndex: 'a',
+ colSpan: 2,
+ width: 100,
+ onCell: (_, index) => {
+ const props: React.TdHTMLAttributes = {};
+ if (index === 0) props.rowSpan = 1;
+ if (index === 1) props.rowSpan = 4;
+ if (index === 2) props.rowSpan = 0;
+ if (index === 3) props.rowSpan = 0;
+ if (index === 4) props.rowSpan = 0;
+ if (index === 5) props.rowSpan = undefined;
+ return props;
+ },
+ },
+ { title: '电话', dataIndex: 'b', colSpan: 0, width: 100 },
+ Table.EXPAND_COLUMN,
+ { title: 'Name', dataIndex: 'c', width: 100 },
+ { title: 'Address', dataIndex: 'd', width: 200 },
+];
+
+const data = [
+ { a: '12313132132', b: '0571-43243256', c: '小二', d: '文零西路', e: 'Male', key: 'z' },
+ { a: '13812340987', b: '0571-12345678', c: '张三', d: '文一西路', e: 'Male', key: 'a' },
+ { a: '13812340987', b: '0571-12345678', c: '张夫人', d: '文一西路', e: 'Female', key: 'b' },
+ { a: '13812340987', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: 'c' },
+ { a: '13812340987', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: 'd' },
+ { a: '1381200008888', b: '0571-099877', c: '王五', d: '文二西路', e: 'Male', key: 'e' },
+];
+
+const Demo = () => (
+
+
expanded & rowSpan
+
>
+ rowKey="key"
+ columns={columns}
+ data={data}
+ expandable={{ expandedRowRender: record => {record.key}
}}
+ className="table"
+ />
+
+);
+
+export default Demo;
diff --git a/docs/examples/expandedSticky.tsx b/docs/examples/expandedSticky.tsx
new file mode 100644
index 00000000..1f516b2b
--- /dev/null
+++ b/docs/examples/expandedSticky.tsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+import type { ColumnType } from 'rc-table';
+import Table from 'rc-table';
+import '../../assets/index.less';
+
+const Demo = () => {
+ const [expandedRowKeys, setExpandedRowKeys] = useState([]);
+
+ const columns: ColumnType>[] = [
+ // { title: '分割', dataIndex: 'ca' },
+ {
+ title: '手机号',
+ dataIndex: 'a',
+ width: 100,
+ fixed: 'left',
+ onCell: (_, index) => {
+ const props: React.TdHTMLAttributes = {};
+ if (index === 0) props.rowSpan = 1;
+ if (index === 1) props.rowSpan = 2;
+ if (index === 2) props.rowSpan = 0;
+ return props;
+ },
+ },
+ Table.EXPAND_COLUMN,
+ { title: 'Name', dataIndex: 'c' },
+ { title: 'Address', fixed: 'right', dataIndex: 'd', width: 200 },
+ ];
+
+ return (
+
+
expanded & sticky
+
>
+ rowKey="key"
+ sticky
+ scroll={{ x: 800 }}
+ columns={columns}
+ data={[
+ { key: 'a', a: '12313132132', c: '小二', d: '文零西路' },
+ { key: 'b', a: '13812340987', c: '张三', d: '文一西路' },
+ { key: 'c', a: '13812340987', c: '张夫', d: '文二西路' },
+ ]}
+ expandable={{
+ expandedRowKeys,
+ onExpandedRowsChange: keys => setExpandedRowKeys(keys),
+ expandedRowRender: record => {record.key}
,
+ }}
+ className="table"
+ />
+
+ );
+};
+
+export default Demo;
diff --git a/src/Body/BodyRow.tsx b/src/Body/BodyRow.tsx
index 7e320eaa..97ae9472 100644
--- a/src/Body/BodyRow.tsx
+++ b/src/Body/BodyRow.tsx
@@ -7,7 +7,7 @@ import useRowInfo from '../hooks/useRowInfo';
import type { ColumnType, CustomizeComponent } from '../interface';
import ExpandedRow from './ExpandedRow';
import { computedExpandedClassName } from '../utils/expandUtil';
-import { TableProps } from '..';
+import type { TableProps } from '..';
export interface BodyRowProps {
record: RecordType;
@@ -22,6 +22,7 @@ export interface BodyRowProps {
scopeCellComponent: CustomizeComponent;
indent?: number;
rowKey: React.Key;
+ rowKeys: React.Key[];
}
// ==================================================================================
@@ -33,6 +34,7 @@ export function getCellProps(
colIndex: number,
indent: number,
index: number,
+ rowKeys: React.Key[],
) {
const {
record,
@@ -46,6 +48,8 @@ export function getCellProps(
expanded,
hasNestChildren,
onTriggerExpand,
+ expandable,
+ expandedKeys,
} = rowInfo;
const key = columnsKey[colIndex];
@@ -74,6 +78,21 @@ export function getCellProps(
let additionalCellProps: React.TdHTMLAttributes;
if (column.onCell) {
additionalCellProps = column.onCell(record, index);
+ const { rowSpan } = additionalCellProps;
+
+ // For expandable row with rowSpan,
+ // We should increase the rowSpan if the row is expanded
+ if (expandable && rowSpan !== undefined) {
+ let currentRowSpan = rowSpan;
+
+ for (let i = index; i < index + rowSpan; i += 1) {
+ const rowKey = rowKeys[i];
+ if (expandedKeys.has(rowKey)) {
+ currentRowSpan += 1;
+ }
+ }
+ additionalCellProps.rowSpan = currentRowSpan;
+ }
}
return {
@@ -84,9 +103,31 @@ export function getCellProps(
};
}
-// ==================================================================================
-// == getCellProps ==
-// ==================================================================================
+const getOffsetData = (
+ columnsData: {
+ column: ColumnType;
+ cell: { additionalCellProps: React.TdHTMLAttributes };
+ }[],
+) => {
+ let offsetWidth = 0;
+ let offsetColumn = 0;
+ let isRowSpanEnd = false;
+ columnsData.forEach(item => {
+ if (!isRowSpanEnd) {
+ const { column, cell } = item;
+ if (cell.additionalCellProps.rowSpan !== undefined) {
+ offsetColumn += 1;
+ if (typeof column.width === 'number') {
+ offsetWidth = offsetWidth + (column.width ?? 0);
+ }
+ } else {
+ isRowSpanEnd = true;
+ }
+ }
+ });
+ return { offsetWidth, offsetColumn };
+};
+
function BodyRow(
props: BodyRowProps,
) {
@@ -107,9 +148,11 @@ function BodyRow(
rowComponent: RowComponent,
cellComponent,
scopeCellComponent,
+ rowKeys,
} = props;
const rowInfo = useRowInfo(record, rowKey, index, indent);
+
const {
prefixCls,
flattenColumns,
@@ -134,6 +177,17 @@ function BodyRow(
// 此时如果 level > 1 则说明是 expandedRow, 一样需要附加 computedExpandedRowClassName
const expandedClsName = computedExpandedClassName(expandedRowClassName, record, index, indent);
+ const { columnsData, offsetData } = React.useMemo(() => {
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ const columnsData = flattenColumns.map((column: ColumnType, colIndex) => {
+ const cell = getCellProps(rowInfo, column, colIndex, indent, index, rowKeys);
+ return { column, cell };
+ });
+ // eslint-disable-next-line @typescript-eslint/no-shadow
+ const offsetData = getOffsetData(columnsData);
+ return { columnsData, offsetData };
+ }, [flattenColumns, indent, index, rowInfo, rowKeys]);
+
// ======================== Base tr row ========================
const baseRowNode = (
(
...styles.row,
}}
>
- {flattenColumns.map((column: ColumnType, colIndex) => {
+ {columnsData.map(item => {
+ const { column, cell } = item;
const { render, dataIndex, className: columnClassName } = column;
- const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps(
- rowInfo,
- column,
- colIndex,
- indent,
- index,
- );
+ const { key, fixedInfo, appendCellNode, additionalCellProps } = cell;
return (
@@ -207,7 +256,8 @@ function BodyRow(
prefixCls={prefixCls}
component={RowComponent}
cellComponent={cellComponent}
- colSpan={flattenColumns.length}
+ offsetWidth={offsetData.offsetWidth}
+ colSpan={flattenColumns.length - offsetData.offsetColumn}
isEmpty={false}
>
{expandContent}
diff --git a/src/Body/ExpandedRow.tsx b/src/Body/ExpandedRow.tsx
index b4009601..77e757c6 100644
--- a/src/Body/ExpandedRow.tsx
+++ b/src/Body/ExpandedRow.tsx
@@ -14,6 +14,7 @@ export interface ExpandedRowProps {
children: React.ReactNode;
colSpan: number;
isEmpty: boolean;
+ offsetWidth?: number;
}
function ExpandedRow(props: ExpandedRowProps) {
@@ -30,6 +31,7 @@ function ExpandedRow(props: ExpandedRowProps) {
expanded,
colSpan,
isEmpty,
+ offsetWidth = 0,
} = props;
const { scrollbarSize, fixHeader, fixColumn, componentWidth, horizonScroll } = useContext(
@@ -44,7 +46,7 @@ function ExpandedRow(props: ExpandedRowProps) {
contentNode = (
(props: BodyProps ) {
const { body: bodyCls = {} } = classNames || {};
const { body: bodyStyles = {} } = styles || {};
- const flattenData: { record: RecordType; indent: number; index: number }[] =
- useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey);
+ const flattenData = useFlattenRecords(
+ data,
+ childrenColumnName,
+ expandedKeys,
+ getRowKey,
+ );
+
+ const rowKeys = React.useMemo(() => flattenData.map(item => item.rowKey), [flattenData]);
// =================== Performance ====================
const perfRef = React.useRef({
@@ -66,16 +72,15 @@ function Body(props: BodyProps) {
let rows: React.ReactNode;
if (data.length) {
rows = flattenData.map((item, idx) => {
- const { record, indent, index: renderIndex } = item;
-
- const key = getRowKey(record, idx);
+ const { record, indent, index: renderIndex, rowKey } = item;
return (
{children} ;
+ return (
+
+ {children}
+
+ );
}
export default Panel;
diff --git a/src/Table.tsx b/src/Table.tsx
index d5a8b196..9c2a682f 100644
--- a/src/Table.tsx
+++ b/src/Table.tsx
@@ -942,6 +942,8 @@ function Table(
mergedChildrenColumnName,
rowHoverable,
+ classNames,
+ styles,
],
);
diff --git a/src/VirtualTable/VirtualCell.tsx b/src/VirtualTable/VirtualCell.tsx
index 9b1b3ebe..e4a3c6bf 100644
--- a/src/VirtualTable/VirtualCell.tsx
+++ b/src/VirtualTable/VirtualCell.tsx
@@ -62,6 +62,7 @@ function VirtualCell(props: VirtualCellProps) {
colIndex,
indent,
index,
+ [],
);
const { style: cellStyle, colSpan = 1, rowSpan = 1 } = additionalCellProps;
diff --git a/src/context/TableContext.tsx b/src/context/TableContext.tsx
index b9cae4dd..cc1c132e 100644
--- a/src/context/TableContext.tsx
+++ b/src/context/TableContext.tsx
@@ -14,7 +14,7 @@ import type {
TriggerEventHandler,
} from '../interface';
import type { FixedInfo } from '../utils/fixUtil';
-import { TableProps } from '../Table';
+import type { TableProps } from '../Table';
const { makeImmutable, responseImmutable, useImmutableMark } = createImmutable();
export { makeImmutable, responseImmutable, useImmutableMark };
diff --git a/src/hooks/useFlattenRecords.ts b/src/hooks/useFlattenRecords.ts
index ff67f5d9..bd5e0c54 100644
--- a/src/hooks/useFlattenRecords.ts
+++ b/src/hooks/useFlattenRecords.ts
@@ -15,6 +15,7 @@ function fillRecords(
record,
indent,
index,
+ rowKey: getRowKey(record, index),
});
const key = getRowKey(record);
@@ -41,6 +42,7 @@ export interface FlattenData {
record: RecordType;
indent: number;
index: number;
+ rowKey: Key;
}
/**
@@ -80,6 +82,7 @@ export default function useFlattenRecords(
record: item,
indent: 0,
index,
+ rowKey: getRowKey(item, index),
};
});
}, [data, childrenColumnName, expandedKeys, getRowKey]);
diff --git a/tests/Expanded.spec.tsx b/tests/Expanded.spec.tsx
new file mode 100644
index 00000000..b3106cca
--- /dev/null
+++ b/tests/Expanded.spec.tsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import { render, act } from '@testing-library/react';
+import Table, { type ColumnsType } from '../src';
+import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
+
+describe('Table.Expanded', () => {
+ let domSpy: ReturnType;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ beforeAll(() => {
+ domSpy = spyElementPrototypes(HTMLElement, {
+ offsetParent: {
+ get: () => ({}),
+ },
+ offsetWidth: {
+ get: () => 1000,
+ },
+ });
+ });
+
+ afterAll(() => {
+ domSpy.mockRestore();
+ });
+
+ it('expanded + rowSpan', async () => {
+ const columns: ColumnsType = [
+ {
+ title: 'key',
+ dataIndex: 'key',
+ width: 100,
+ onCell: (_, index) => {
+ const props: React.TdHTMLAttributes = {};
+ if (index === 0) props.rowSpan = 1;
+ if (index === 1) props.rowSpan = 2;
+ if (index === 2) props.rowSpan = 0;
+ if (index === 3) props.rowSpan = undefined;
+ return props;
+ },
+ },
+ Table.EXPAND_COLUMN,
+ { title: 'key2', dataIndex: 'key2', width: 100 },
+ ];
+ const data = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }];
+ const { container } = render(
+ >
+ columns={columns}
+ data={data}
+ expandable={{
+ defaultExpandAllRows: true,
+ expandedRowRender: record => {record.key} ,
+ }}
+ />,
+ );
+
+ await act(async () => {
+ vi.runAllTimers();
+ await Promise.resolve();
+ });
+ const trList = container.querySelector('tbody').querySelectorAll('tr');
+ // row 1
+ expect(trList[0].querySelectorAll('td').length).toBe(3);
+ expect(trList[0].querySelectorAll('td')[0].getAttribute('rowspan')).toBe('2');
+ // expand 1
+ expect(trList[1].querySelectorAll('td').length).toBe(1);
+ expect(trList[1].querySelectorAll('td')[0].getAttribute('colspan')).toBe('2');
+ // row 2
+ expect(trList[2].querySelectorAll('td').length).toBe(3);
+ expect(trList[2].querySelectorAll('td')[0].getAttribute('rowspan')).toBe('4');
+ // expand 2
+ expect(trList[3].querySelectorAll('td').length).toBe(1);
+ expect(trList[3].querySelectorAll('td')[0].getAttribute('colspan')).toBe('2');
+ // row 3
+ expect(trList[4].querySelectorAll('td').length).toBe(2);
+ // expand 3
+ expect(trList[5].querySelectorAll('td').length).toBe(1);
+ expect(trList[5].querySelectorAll('td')[0].getAttribute('colspan')).toBe('2');
+ // row 4
+ expect(trList[6].querySelectorAll('td').length).toBe(3);
+ // expand 4
+ expect(trList[7].querySelectorAll('td').length).toBe(1);
+ expect(trList[7].querySelectorAll('td')[0].getAttribute('colspan')).toBe('3');
+ });
+
+ it('expanded + sticky', async () => {
+ const columns: ColumnsType = [
+ {
+ title: '手机号',
+ dataIndex: 'a',
+ width: 100,
+ fixed: 'left',
+ onCell: (_, index) => {
+ const props: React.TdHTMLAttributes = {};
+ if (index === 0) props.rowSpan = 1;
+ if (index === 1) props.rowSpan = 2;
+ if (index === 2) props.rowSpan = 0;
+ return props;
+ },
+ },
+ Table.EXPAND_COLUMN,
+ { title: 'Name', dataIndex: 'c' },
+ ];
+ const data = [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }];
+ const { container } = render(
+ >
+ columns={columns}
+ data={data}
+ sticky
+ expandable={{
+ defaultExpandAllRows: true,
+ expandedRowRender: record => {record.key} ,
+ }}
+ />,
+ );
+ console.log('container', container);
+ await act(async () => {
+ vi.runAllTimers();
+ await Promise.resolve();
+ });
+ });
+});
|