Skip to content
Merged
8 changes: 8 additions & 0 deletions docs/demo/expandedSticky.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
title: expandedSticky
nav:
title: Demo
path: /demo
---

<code src="../examples/expandedSticky.tsx"></code>
59 changes: 59 additions & 0 deletions docs/examples/expandedSticky.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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<readonly React.Key[]>([]);

const data = [
{ key: 'a', a: '小二', d: '文零西路' },
{ key: 'b', a: '张三', d: '文一西路' },
{ key: 'c', a: '张三', d: '文二西路' },
];

const columns: ColumnType<Record<string, any>>[] = [
{
title: '手机号',
dataIndex: 'a',
width: 100,
onCell: (_, index) => {
if (index === 1) {
return {
rowSpan: 2,
};
} else if (index === 2) {
return {
rowSpan: 0,
};
}
},
},
{ title: 'key', dataIndex: 'key2', width: 100 },
Table.EXPAND_COLUMN,
{ title: 'key', dataIndex: 'key' },
{ title: 'Address', fixed: 'right', dataIndex: 'd', width: 200 },
];

return (
<div style={{ height: 10000 }}>
<h2>expanded & sticky</h2>
<Table<Record<string, any>>
rowKey="key"
sticky
scroll={{ x: 2000 }}
columns={columns}
data={data}
expandable={{
expandedRowOffset: 2,
expandedRowKeys,
onExpandedRowsChange: keys => setExpandedRowKeys(keys),
expandedRowRender: record => <p style={{ margin: 0 }}>expandedRowRender: {record.key}</p>,
}}
className="table"
/>
</div>
);
};

export default Demo;
43 changes: 38 additions & 5 deletions src/Body/BodyRow.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,14 @@ export interface BodyRowProps<RecordType> {
scopeCellComponent: CustomizeComponent;
indent?: number;
rowKey: React.Key;
rowKeys: React.Key[];

// Expanded Row
expandedRowInfo?: {
offset: number;
colSpan: number;
sticky: number;
};
}

// ==================================================================================
Expand All@@ -30,6 +38,8 @@ export function getCellProps<RecordType>(
colIndex: number,
indent: number,
index: number,
rowKeys: React.Key[] = [],
expandedRowOffset = 0,
) {
const {
record,
Expand All@@ -43,6 +53,8 @@ export function getCellProps<RecordType>(
expanded,
hasNestChildren,
onTriggerExpand,
expandable,
expandedKeys,
} = rowInfo;

const key = columnsKey[colIndex];
Expand All@@ -68,16 +80,32 @@ export function getCellProps<RecordType>(
);
}

let additionalCellProps: React.TdHTMLAttributes<HTMLElement>;
if (column.onCell) {
additionalCellProps = column.onCell(record, index);
const additionalCellProps = column.onCell?.(record, index) || {};

// Expandable row has offset
if (expandedRowOffset) {
const { rowSpan = 1 } = additionalCellProps;

// For expandable row with rowSpan,
// We should increase the rowSpan if the row is expanded
if (expandable && rowSpan && colIndex < expandedRowOffset) {
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 {
key,
fixedInfo,
appendCellNode,
additionalCellProps: additionalCellProps || {},
additionalCellProps: additionalCellProps,
};
}

Expand All@@ -98,10 +126,12 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
index,
renderIndex,
rowKey,
rowKeys,
indent = 0,
rowComponent: RowComponent,
cellComponent,
scopeCellComponent,
expandedRowInfo,
} = props;
const rowInfo = useRowInfo(record, rowKey, index, indent);
const {
Expand DownExpand Up@@ -153,6 +183,8 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
colIndex,
indent,
index,
rowKeys,
expandedRowInfo?.offset,
);

return (
Expand DownExpand Up@@ -195,7 +227,8 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
prefixCls={prefixCls}
component={RowComponent}
cellComponent={cellComponent}
colSpan={flattenColumns.length}
colSpan={expandedRowInfo ? expandedRowInfo.colSpan : flattenColumns.length}
stickyOffset={expandedRowInfo?.sticky}
isEmpty={false}
>
{expandContent}
Expand Down
6 changes: 4 additions & 2 deletions src/Body/ExpandedRow.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ export interface ExpandedRowProps {
children: React.ReactNode;
colSpan: number;
isEmpty: boolean;
stickyOffset?: number;
}

function ExpandedRow(props: ExpandedRowProps) {
Expand All@@ -30,6 +31,7 @@ function ExpandedRow(props: ExpandedRowProps) {
expanded,
colSpan,
isEmpty,
stickyOffset = 0,
} = props;

const { scrollbarSize, fixHeader, fixColumn, componentWidth, horizonScroll } = useContext(
Expand All@@ -44,9 +46,9 @@ function ExpandedRow(props: ExpandedRowProps) {
contentNode = (
<div
style={{
width: componentWidth - (fixHeader && !isEmpty ? scrollbarSize : 0),
width: componentWidth - stickyOffset - (fixHeader && !isEmpty ? scrollbarSize : 0),
position: 'sticky',
left: 0,
left: stickyOffset,
overflow: 'hidden',
}}
className={`${prefixCls}-expanded-row-fixed`}
Expand Down
43 changes: 36 additions & 7 deletions src/Body/index.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
expandedKeys,
childrenColumnName,
emptyNode,
expandedRowOffset = 0,
colWidths,
} = useContext(TableContext, [
'prefixCls',
'getComponent',
Expand All@@ -40,16 +42,42 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
'expandedKeys',
'childrenColumnName',
'emptyNode',
'expandedRowOffset',
'fixedInfoList',
'colWidths',
]);

const flattenData: { record: RecordType; indent: number; index: number }[] =
useFlattenRecords<RecordType>(data, childrenColumnName, expandedKeys, getRowKey);
const flattenData = useFlattenRecords<RecordType>(
data,
childrenColumnName,
expandedKeys,
getRowKey,
);
const rowKeys = React.useMemo(() => flattenData.map(item => item.rowKey), [flattenData]);

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

// ===================== Expanded =====================
// `expandedRowOffset` data is same for all the rows.
// Let's calc on Body side to save performance.
const expandedRowInfo = React.useMemo(() => {
const expandedColSpan = flattenColumns.length - expandedRowOffset;

let expandedStickyStart = 0;
for (let i = 0; i < expandedRowOffset; i += 1) {
expandedStickyStart += colWidths[i] || 0;
}

return {
offset: expandedRowOffset,
colSpan: expandedColSpan,
sticky: expandedStickyStart,
};
}, [flattenColumns.length, expandedRowOffset, colWidths]);

// ====================== Render ======================
const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody');
const trComponent = getComponent(['body', 'row'], 'tr');
Expand All@@ -59,21 +87,22 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
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 (
<BodyRow
key={key}
rowKey={key}
key={rowKey}
rowKey={rowKey}
rowKeys={rowKeys}
record={record}
index={idx}
renderIndex={renderIndex}
rowComponent={trComponent}
cellComponent={tdComponent}
scopeCellComponent={thComponent}
indent={indent}
// Expanded row info
expandedRowInfo={expandedRowInfo}
/>
);
});
Expand Down
4 changes: 4 additions & 0 deletions src/Table.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -822,6 +822,7 @@ function Table<RecordType extends DefaultRecordType>(
expandableType,
expandRowByClick: expandableConfig.expandRowByClick,
expandedRowRender: expandableConfig.expandedRowRender,
expandedRowOffset: expandableConfig.expandedRowOffset,
onTriggerExpand,
expandIconColumnIndex: expandableConfig.expandIconColumnIndex,
indentSize: expandableConfig.indentSize,
Expand All@@ -832,6 +833,7 @@ function Table<RecordType extends DefaultRecordType>(
columns,
flattenColumns,
onColumnResize,
colWidths,

// Row
hoverStartRow: startRow,
Expand DownExpand Up@@ -872,6 +874,7 @@ function Table<RecordType extends DefaultRecordType>(
expandableType,
expandableConfig.expandRowByClick,
expandableConfig.expandedRowRender,
expandableConfig.expandedRowOffset,
onTriggerExpand,
expandableConfig.expandIconColumnIndex,
expandableConfig.indentSize,
Expand All@@ -881,6 +884,7 @@ function Table<RecordType extends DefaultRecordType>(
columns,
flattenColumns,
onColumnResize,
colWidths,

// Row
startRow,
Expand Down
1 change: 1 addition & 0 deletions src/VirtualTable/VirtualCell.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@ function VirtualCell<RecordType = any>(props: VirtualCellProps<RecordType>) {

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

// TODO: support `expandableRowOffset`
const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps(
rowInfo,
column,
Expand Down
4 changes: 4 additions & 0 deletions src/context/TableContext.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import type {
ColumnsType,
ColumnType,
Direction,
ExpandableConfig,
ExpandableType,
ExpandedRowRender,
GetComponent,
Expand DownExpand Up@@ -56,6 +57,7 @@ export interface TableContextProps<RecordType = any> {
columns: ColumnsType<RecordType>;
flattenColumns: readonly ColumnType<RecordType>[];
onColumnResize: (columnKey: React.Key, width: number) => void;
colWidths: number[];

// Row
hoverStartRow: number;
Expand All@@ -68,6 +70,8 @@ export interface TableContextProps<RecordType = any> {
childrenColumnName: string;

rowHoverable?: boolean;

expandedRowOffset: ExpandableConfig<RecordType>['expandedRowOffset'];
}

const TableContext = createContext<TableContextProps>();
Expand Down
15 changes: 13 additions & 2 deletions src/hooks/useColumns/index.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -122,6 +122,7 @@ function useColumns<RecordType>(
expandIcon,
rowExpandable,
expandIconColumnIndex,
expandedRowOffset = 0,
direction,
expandRowByClick,
columnWidth,
Expand All@@ -146,6 +147,7 @@ function useColumns<RecordType>(
clientWidth: number;
fixed?: FixedType;
scrollWidth?: number;
expandedRowOffset?: number;
},
transformColumns: (columns: ColumnsType<RecordType>) => ColumnsType<RecordType>,
): [
Expand DownExpand Up@@ -236,7 +238,16 @@ function useColumns<RecordType>(
},
};

return cloneColumns.map(col => (col === EXPAND_COLUMN ? expandColumn : col));
return cloneColumns.map((col, index) => {
const column = col === EXPAND_COLUMN ? expandColumn : col;
if (index < expandedRowOffset) {
return {
...column,
fixed: column.fixed || 'left',
};
}
return column;
});
}

if (process.env.NODE_ENV !== 'production' && baseColumns.includes(EXPAND_COLUMN)) {
Expand All@@ -245,7 +256,7 @@ function useColumns<RecordType>(

return baseColumns.filter(col => col !== EXPAND_COLUMN);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [expandable, baseColumns, getRowKey, expandedKeys, expandIcon, direction]);
}, [expandable, baseColumns, getRowKey, expandedKeys, expandIcon, direction, expandedRowOffset]);

// ========================= Transform ========================
const mergedColumns = React.useMemo(() => {
Expand Down
Loading