Skip to content

Commit 2ad5512

Browse files
zombieJlinxianxiafc163crazyair
authored
feat: Table support expandedRowOffset (#1284)
* fix: cherry pick of fixing sticky issue (#1232) * fix: sticky event loop * chore: adjust script * 7.50.3 * fix: virtual scroll logic (#1240) * fix: scroll logic (#1239) * fix: scroll logic * fix: ci * chore: fix lint * 7.50.4 * fix: 7.x header blank when using sticky or scroll.y (#1268) * 7.50.5 * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * feat: test * test: update snapshot * chore: perf * test: update test case * chore: back of part * chore: fix ts --------- Co-authored-by: daisy <[email protected]> Co-authored-by: afc163 <[email protected]> Co-authored-by: 黄建峰 <[email protected]>
1 parent d696f75 commit 2ad5512

File tree

12 files changed

+302
-19
lines changed

12 files changed

+302
-19
lines changed

docs/demo/expandedSticky.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: expandedSticky
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../examples/expandedSticky.tsx"></code>

docs/examples/expandedSticky.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React, { useState } from 'react';
2+
import type { ColumnType } from 'rc-table';
3+
import Table from 'rc-table';
4+
import '../../assets/index.less';
5+
6+
// 合并单元格
7+
export const getRowSpan = (source: (string | number | undefined)[] = []) => {
8+
const list: { rowSpan?: number }[] = [];
9+
let span = 0;
10+
source.reverse().forEach((key, index) => {
11+
span = span + 1;
12+
if (key !== source[index + 1]) {
13+
list.push({ rowSpan: span });
14+
span = 0;
15+
} else {
16+
list.push({ rowSpan: 0 });
17+
}
18+
});
19+
return list.reverse();
20+
};
21+
22+
const Demo = () => {
23+
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly React.Key[]>([]);
24+
25+
const data = [
26+
{ key: 'a', a: '小二', d: '文零西路' },
27+
{ key: 'b', a: '张三', d: '文一西路' },
28+
{ key: 'c', a: '张三', d: '文二西路' },
29+
];
30+
// const rowKeys = data.map(item => item.key);
31+
32+
// const rowSpanList = getRowSpan(data.map(item => item.a));
33+
34+
const columns: ColumnType<Record<string, any>>[] = [
35+
{
36+
title: '手机号',
37+
dataIndex: 'a',
38+
width: 100,
39+
colSpan: 2,
40+
// fixed: 'left',
41+
onCell: (_, index) => {
42+
// const { rowSpan = 1 } = rowSpanList[index];
43+
// const props: React.TdHTMLAttributes<HTMLTableCellElement> = {};
44+
// props.rowSpan = rowSpan;
45+
// if (rowSpan >= 1) {
46+
// let currentRowSpan = rowSpan;
47+
// for (let i = index; i < index + rowSpan; i += 1) {
48+
// const rowKey = rowKeys[i];
49+
// if (expandedRowKeys.includes(rowKey)) {
50+
// currentRowSpan += 1;
51+
// }
52+
// }
53+
// props.rowSpan = currentRowSpan;
54+
// }
55+
// return props;
56+
57+
if (index === 1) {
58+
return {
59+
rowSpan: 2,
60+
};
61+
} else if (index === 2) {
62+
return {
63+
rowSpan: 0,
64+
};
65+
}
66+
},
67+
},
68+
{ title: 'key', dataIndex: 'key2', colSpan: 0, width: 100 },
69+
Table.EXPAND_COLUMN,
70+
{ title: 'key', dataIndex: 'key' },
71+
{ title: 'Address', fixed: 'right', dataIndex: 'd', width: 200 },
72+
];
73+
74+
return (
75+
<div style={{ height: 10000 }}>
76+
<h2>expanded & sticky</h2>
77+
<Table<Record<string, any>>
78+
rowKey="key"
79+
sticky
80+
scroll={{ x: 1000 }}
81+
columns={columns}
82+
data={data}
83+
expandable={{
84+
expandedRowOffset: 2,
85+
expandedRowKeys,
86+
onExpandedRowsChange: keys => setExpandedRowKeys(keys),
87+
expandedRowRender: record => <p style={{ margin: 0 }}>expandedRowRender: {record.key}</p>,
88+
}}
89+
className="table"
90+
/>
91+
</div>
92+
);
93+
};
94+
95+
export default Demo;

src/Body/BodyRow.tsx

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import useRowInfo from '../hooks/useRowInfo';
77
import type { ColumnType, CustomizeComponent } from '../interface';
88
import ExpandedRow from './ExpandedRow';
99
import { computedExpandedClassName } from '../utils/expandUtil';
10-
import { TableProps } from '..';
10+
import type { TableProps } from '..';
1111

1212
export interface BodyRowProps<RecordType> {
1313
record: RecordType;
@@ -22,6 +22,14 @@ export interface BodyRowProps<RecordType> {
2222
scopeCellComponent: CustomizeComponent;
2323
indent?: number;
2424
rowKey: React.Key;
25+
rowKeys: React.Key[];
26+
27+
// Expanded Row
28+
expandedRowInfo?: {
29+
offset: number;
30+
colSpan: number;
31+
sticky: number;
32+
};
2533
}
2634

2735
// ==================================================================================
@@ -33,6 +41,8 @@ export function getCellProps<RecordType>(
3341
colIndex: number,
3442
indent: number,
3543
index: number,
44+
rowKeys: React.Key[] = [],
45+
expandedRowOffset = 0,
3646
) {
3747
const {
3848
record,
@@ -46,6 +56,8 @@ export function getCellProps<RecordType>(
4656
expanded,
4757
hasNestChildren,
4858
onTriggerExpand,
59+
expandable,
60+
expandedKeys,
4961
} = rowInfo;
5062

5163
const key = columnsKey[colIndex];
@@ -71,16 +83,32 @@ export function getCellProps<RecordType>(
7183
);
7284
}
7385

74-
let additionalCellProps: React.TdHTMLAttributes<HTMLElement>;
75-
if (column.onCell) {
76-
additionalCellProps = column.onCell(record, index);
86+
const additionalCellProps = column.onCell?.(record, index) || {};
87+
88+
// Expandable row has offset
89+
if (expandedRowOffset) {
90+
const { rowSpan = 1 } = additionalCellProps;
91+
92+
// For expandable row with rowSpan,
93+
// We should increase the rowSpan if the row is expanded
94+
if (expandable && rowSpan && colIndex < expandedRowOffset) {
95+
let currentRowSpan = rowSpan;
96+
97+
for (let i = index; i < index + rowSpan; i += 1) {
98+
const rowKey = rowKeys[i];
99+
if (expandedKeys.has(rowKey)) {
100+
currentRowSpan += 1;
101+
}
102+
}
103+
additionalCellProps.rowSpan = currentRowSpan;
104+
}
77105
}
78106

79107
return {
80108
key,
81109
fixedInfo,
82110
appendCellNode,
83-
additionalCellProps: additionalCellProps || {},
111+
additionalCellProps: additionalCellProps,
84112
};
85113
}
86114

@@ -103,10 +131,12 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
103131
index,
104132
renderIndex,
105133
rowKey,
134+
rowKeys,
106135
indent = 0,
107136
rowComponent: RowComponent,
108137
cellComponent,
109138
scopeCellComponent,
139+
expandedRowInfo,
110140
} = props;
111141

112142
const rowInfo = useRowInfo(record, rowKey, index, indent);
@@ -164,6 +194,8 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
164194
colIndex,
165195
indent,
166196
index,
197+
rowKeys,
198+
expandedRowInfo?.offset,
167199
);
168200

169201
return (
@@ -207,8 +239,9 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
207239
prefixCls={prefixCls}
208240
component={RowComponent}
209241
cellComponent={cellComponent}
210-
colSpan={flattenColumns.length}
242+
colSpan={expandedRowInfo ? expandedRowInfo.colSpan : flattenColumns.length}
211243
isEmpty={false}
244+
stickyOffset={expandedRowInfo?.sticky}
212245
>
213246
{expandContent}
214247
</ExpandedRow>

src/Body/ExpandedRow.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ExpandedRowProps {
1414
children: React.ReactNode;
1515
colSpan: number;
1616
isEmpty: boolean;
17+
stickyOffset?: number;
1718
}
1819

1920
function ExpandedRow(props: ExpandedRowProps) {
@@ -30,6 +31,7 @@ function ExpandedRow(props: ExpandedRowProps) {
3031
expanded,
3132
colSpan,
3233
isEmpty,
34+
stickyOffset = 0,
3335
} = props;
3436

3537
const { scrollbarSize, fixHeader, fixColumn, componentWidth, horizonScroll } = useContext(
@@ -44,9 +46,9 @@ function ExpandedRow(props: ExpandedRowProps) {
4446
contentNode = (
4547
<div
4648
style={{
47-
width: componentWidth - (fixHeader && !isEmpty ? scrollbarSize : 0),
49+
width: componentWidth - stickyOffset - (fixHeader && !isEmpty ? scrollbarSize : 0),
4850
position: 'sticky',
49-
left: 0,
51+
left: stickyOffset,
5052
overflow: 'hidden',
5153
}}
5254
className={`${prefixCls}-expanded-row-fixed`}

src/Body/index.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
3434
emptyNode,
3535
classNames,
3636
styles,
37+
expandedRowOffset = 0,
38+
colWidths,
3739
} = useContext(TableContext, [
3840
'prefixCls',
3941
'getComponent',
@@ -45,18 +47,45 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
4547
'emptyNode',
4648
'classNames',
4749
'styles',
50+
'expandedRowOffset',
51+
'fixedInfoList',
52+
'colWidths',
4853
]);
4954
const { body: bodyCls = {} } = classNames || {};
5055
const { body: bodyStyles = {} } = styles || {};
5156

52-
const flattenData: { record: RecordType; indent: number; index: number }[] =
53-
useFlattenRecords<RecordType>(data, childrenColumnName, expandedKeys, getRowKey);
57+
const flattenData = useFlattenRecords<RecordType>(
58+
data,
59+
childrenColumnName,
60+
expandedKeys,
61+
getRowKey,
62+
);
63+
64+
const rowKeys = React.useMemo(() => flattenData.map(item => item.rowKey), [flattenData]);
5465

5566
// =================== Performance ====================
5667
const perfRef = React.useRef<PerfRecord>({
5768
renderWithProps: false,
5869
});
5970

71+
// ===================== Expanded =====================
72+
// `expandedRowOffset` data is same for all the rows.
73+
// Let's calc on Body side to save performance.
74+
const expandedRowInfo = React.useMemo(() => {
75+
const expandedColSpan = flattenColumns.length - expandedRowOffset;
76+
77+
let expandedStickyStart = 0;
78+
for (let i = 0; i < expandedRowOffset; i += 1) {
79+
expandedStickyStart += colWidths[i] || 0;
80+
}
81+
82+
return {
83+
offset: expandedRowOffset,
84+
colSpan: expandedColSpan,
85+
sticky: expandedStickyStart,
86+
};
87+
}, [flattenColumns.length, expandedRowOffset, colWidths]);
88+
6089
// ====================== Render ======================
6190
const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody');
6291
const trComponent = getComponent(['body', 'row'], 'tr');
@@ -66,23 +95,24 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
6695
let rows: React.ReactNode;
6796
if (data.length) {
6897
rows = flattenData.map((item, idx) => {
69-
const { record, indent, index: renderIndex } = item;
70-
71-
const key = getRowKey(record, idx);
98+
const { record, indent, index: renderIndex, rowKey } = item;
7299

73100
return (
74101
<BodyRow
75102
classNames={bodyCls}
76103
styles={bodyStyles}
77-
key={key}
78-
rowKey={key}
104+
key={rowKey}
105+
rowKey={rowKey}
106+
rowKeys={rowKeys}
79107
record={record}
80108
index={idx}
81109
renderIndex={renderIndex}
82110
rowComponent={trComponent}
83111
cellComponent={tdComponent}
84112
scopeCellComponent={thComponent}
85113
indent={indent}
114+
// Expanded row info
115+
expandedRowInfo={expandedRowInfo}
86116
/>
87117
);
88118
});

src/Table.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ function Table<RecordType extends DefaultRecordType>(
870870
expandableType,
871871
expandRowByClick: expandableConfig.expandRowByClick,
872872
expandedRowRender: expandableConfig.expandedRowRender,
873+
expandedRowOffset: expandableConfig.expandedRowOffset,
873874
onTriggerExpand,
874875
expandIconColumnIndex: expandableConfig.expandIconColumnIndex,
875876
indentSize: expandableConfig.indentSize,
@@ -880,6 +881,7 @@ function Table<RecordType extends DefaultRecordType>(
880881
columns,
881882
flattenColumns,
882883
onColumnResize,
884+
colWidths,
883885

884886
// Row
885887
hoverStartRow: startRow,
@@ -920,6 +922,7 @@ function Table<RecordType extends DefaultRecordType>(
920922
expandableType,
921923
expandableConfig.expandRowByClick,
922924
expandableConfig.expandedRowRender,
925+
expandableConfig.expandedRowOffset,
923926
onTriggerExpand,
924927
expandableConfig.expandIconColumnIndex,
925928
expandableConfig.indentSize,
@@ -929,6 +932,7 @@ function Table<RecordType extends DefaultRecordType>(
929932
columns,
930933
flattenColumns,
931934
onColumnResize,
935+
colWidths,
932936

933937
// Row
934938
startRow,

src/VirtualTable/VirtualCell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function VirtualCell<RecordType = any>(props: VirtualCellProps<RecordType>) {
5656

5757
const { columnsOffset } = useContext(GridContext, ['columnsOffset']);
5858

59+
// TODO: support `expandableRowOffset`
5960
const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps(
6061
rowInfo,
6162
column,

src/context/TableContext.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
ColumnsType,
44
ColumnType,
55
Direction,
6+
ExpandableConfig,
67
ExpandableType,
78
ExpandedRowRender,
89
GetComponent,
@@ -61,6 +62,7 @@ export interface TableContextProps<RecordType = any> {
6162
columns: ColumnsType<RecordType>;
6263
flattenColumns: readonly ColumnType<RecordType>[];
6364
onColumnResize: (columnKey: React.Key, width: number) => void;
65+
colWidths: number[];
6466

6567
// Row
6668
hoverStartRow: number;
@@ -73,6 +75,8 @@ export interface TableContextProps<RecordType = any> {
7375
childrenColumnName: string;
7476

7577
rowHoverable?: boolean;
78+
79+
expandedRowOffset: ExpandableConfig<RecordType>['expandedRowOffset'];
7680
}
7781

7882
const TableContext = createContext<TableContextProps>();

0 commit comments

Comments
 (0)