Skip to content

fix expanded & sticky #1277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/demo/expandedSticky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: expandedSticky
nav:
title: Demo
path: /demo
---

<code src="../examples/expandedSticky.tsx"></code>
82 changes: 82 additions & 0 deletions docs/examples/expandedSticky.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState } from 'react';
import type { ColumnType } from 'rc-table';
import Table from 'rc-table';
import '../../assets/index.less';

// 合并单元格
export const getRowSpan = (source: (string | number | undefined)[] = []) => {
const list: { rowSpan?: number }[] = [];
let span = 0;
source.reverse().forEach((key, index) => {
span = span + 1;
if (key !== source[index + 1]) {
list.push({ rowSpan: span });
span = 0;
} else {
list.push({ rowSpan: 0 });
}
});
return list.reverse();
};

const Demo = () => {
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly React.Key[]>([]);

const data = [
{ key: 'a', a: '小二', d: '文零西路' },
{ key: 'b', a: '张三', d: '文一西路' },
{ key: 'c', a: '张三', d: '文二西路' },
];
const rowKeys = data.map(item => item.key);

const rowSpanList = getRowSpan(data.map(item => item.a));

const columns: ColumnType<Record<string, any>>[] = [
{
title: '手机号',
dataIndex: 'a',
width: 100,
fixed: 'left',
onCell: (_, index) => {
const { rowSpan = 1 } = rowSpanList[index];
const props: React.TdHTMLAttributes<HTMLTableCellElement> = {};
props.rowSpan = rowSpan;
if (rowSpan >= 1) {
let currentRowSpan = rowSpan;
for (let i = index; i < index + rowSpan; i += 1) {
const rowKey = rowKeys[i];
if (expandedRowKeys.includes(rowKey)) {
currentRowSpan += 1;
}
}
props.rowSpan = currentRowSpan;
}
return props;
},
},
Table.EXPAND_COLUMN,
{ 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: 800 }}
columns={columns}
data={data}
expandable={{
expandedRowOffset: 1,
expandedRowKeys,
onExpandedRowsChange: keys => setExpandedRowKeys(keys),
expandedRowRender: record => <p style={{ margin: 0 }}>{record.key}</p>,
}}
className="table"
/>
</div>
);
};

export default Demo;
15 changes: 13 additions & 2 deletions src/Body/BodyRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { responseImmutable } from '../context/TableContext';
import devRenderTimes from '../hooks/useRenderTimes';
import useRowInfo from '../hooks/useRowInfo';
import type { ColumnType, CustomizeComponent } from '../interface';
import type { ColumnType, CustomizeComponent, ExpandableConfig } from '../interface';
import ExpandedRow from './ExpandedRow';
import { computedExpandedClassName } from '../utils/expandUtil';
import { TableProps } from '..';
Expand All @@ -22,6 +22,7 @@
scopeCellComponent: CustomizeComponent;
indent?: number;
rowKey: React.Key;
expandedRowOffset?: ExpandableConfig<RecordType>['expandedRowOffset'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那个 rowSpan 的挪过来? expandedRowOffset 如果没有那个合并的能力,这里其实是个半吊子

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

合也行,主要是另外哪个我还想做个模式,支持合并单元格是 + 1,还是分割
image

}

// ==================================================================================
Expand Down Expand Up @@ -107,6 +108,7 @@
rowComponent: RowComponent,
cellComponent,
scopeCellComponent,
expandedRowOffset = 0,
} = props;

const rowInfo = useRowInfo(record, rowKey, index, indent);
Expand Down Expand Up @@ -196,6 +198,14 @@
if (rowSupportExpand && (expandedRef.current || expanded)) {
const expandContent = expandedRowRender(record, index, indent + 1, expanded);

const offsetColumns = flattenColumns.filter((_, idx) => idx < expandedRowOffset);
let offsetWidth = 0;
offsetColumns.forEach(item => {
if (typeof item.width === 'number') {
offsetWidth = offsetWidth + (item.width ?? 0);
}

Check warning on line 206 in src/Body/BodyRow.tsx

View check run for this annotation

Codecov / codecov/patch

src/Body/BodyRow.tsx#L204-L206

Added lines #L204 - L206 were not covered by tests
});
Comment on lines +201 to +207
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

改进偏移宽度计算逻辑

当前的宽度计算实现存在几个问题:

  1. 只处理数字类型的宽度,忽略了字符串类型(如 "100px")和百分比宽度
  2. 没有验证 expandedRowOffset 是否在有效范围内
  3. 使用 forEach 而不是更简洁的 reduce 方法

建议改进为:

-    const offsetColumns = flattenColumns.filter((_, idx) => idx < expandedRowOffset);
-    let offsetWidth = 0;
-    offsetColumns.forEach(item => {
-      if (typeof item.width === 'number') {
-        offsetWidth = offsetWidth + (item.width ?? 0);
-      }
-    });
+    // 验证偏移值的有效性
+    const validOffset = Math.min(Math.max(0, expandedRowOffset), flattenColumns.length);
+    const offsetColumns = flattenColumns.slice(0, validOffset);
+    
+    const offsetWidth = offsetColumns.reduce((total, column) => {
+      if (typeof column.width === 'number') {
+        return total + column.width;
+      }
+      // 处理字符串宽度(可选:解析 px 值)
+      if (typeof column.width === 'string' && column.width.endsWith('px')) {
+        const numValue = parseInt(column.width, 10);
+        return isNaN(numValue) ? total : total + numValue;
+      }
+      return total;
+    }, 0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const offsetColumns = flattenColumns.filter((_, idx) => idx < expandedRowOffset);
let offsetWidth = 0;
offsetColumns.forEach(item => {
if (typeof item.width === 'number') {
offsetWidth = offsetWidth + (item.width ?? 0);
}
});
// 验证偏移值的有效性
const validOffset = Math.min(Math.max(0, expandedRowOffset), flattenColumns.length);
const offsetColumns = flattenColumns.slice(0, validOffset);
const offsetWidth = offsetColumns.reduce((total, column) => {
if (typeof column.width === 'number') {
return total + column.width;
}
// 处理字符串宽度(可选:解析 px 值)
if (typeof column.width === 'string' && column.width.endsWith('px')) {
const numValue = parseInt(column.width, 10);
return isNaN(numValue) ? total : total + numValue;
}
return total;
}, 0);
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 204-206: src/Body/BodyRow.tsx#L204-L206
Added lines #L204 - L206 were not covered by tests

🤖 Prompt for AI Agents
In src/Body/BodyRow.tsx around lines 201 to 207, improve the offset width
calculation by first validating that expandedRowOffset is within the valid range
of flattenColumns indices. Then, replace the forEach loop with a reduce method
to accumulate the total width. Modify the logic to handle width values that are
numbers, strings with pixel units (e.g., "100px"), and percentage strings by
parsing and converting them appropriately before summing. This will make the
calculation more robust and concise.


expandRowNode = (
<ExpandedRow
expanded={expanded}
Expand All @@ -207,7 +217,8 @@
prefixCls={prefixCls}
component={RowComponent}
cellComponent={cellComponent}
colSpan={flattenColumns.length}
offsetWidth={offsetWidth}
colSpan={flattenColumns.length - expandedRowOffset}
isEmpty={false}
>
{expandContent}
Expand Down
5 changes: 3 additions & 2 deletions src/Body/ExpandedRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ExpandedRowProps {
children: React.ReactNode;
colSpan: number;
isEmpty: boolean;
offsetWidth?: number;
}

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

const { scrollbarSize, fixHeader, fixColumn, componentWidth, horizonScroll } = useContext(
Expand All @@ -39,12 +41,11 @@ function ExpandedRow(props: ExpandedRowProps) {

// Cache render node
let contentNode = children;

if (isEmpty ? horizonScroll && componentWidth : fixColumn) {
contentNode = (
<div
style={{
width: componentWidth - (fixHeader && !isEmpty ? scrollbarSize : 0),
width: componentWidth - offsetWidth - (fixHeader && !isEmpty ? scrollbarSize : 0),
position: 'sticky',
left: 0,
overflow: 'hidden',
Expand Down
3 changes: 3 additions & 0 deletions src/Body/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
emptyNode,
classNames,
styles,
expandedRowOffset,
} = useContext(TableContext, [
'prefixCls',
'getComponent',
Expand All @@ -45,6 +46,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
'emptyNode',
'classNames',
'styles',
'expandedRowOffset',
]);
const { body: bodyCls = {} } = classNames || {};
const { body: bodyStyles = {} } = styles || {};
Expand Down Expand Up @@ -83,6 +85,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
cellComponent={tdComponent}
scopeCellComponent={thComponent}
indent={indent}
expandedRowOffset={expandedRowOffset}
/>
);
});
Expand Down
2 changes: 2 additions & 0 deletions src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,7 @@ function Table<RecordType extends DefaultRecordType>(
expandedRowRender: expandableConfig.expandedRowRender,
onTriggerExpand,
expandIconColumnIndex: expandableConfig.expandIconColumnIndex,
expandedRowOffset: expandableConfig.expandedRowOffset,
indentSize: expandableConfig.indentSize,
allColumnsFixedLeft: flattenColumns.every(col => col.fixed === 'start'),
emptyNode,
Expand Down Expand Up @@ -922,6 +923,7 @@ function Table<RecordType extends DefaultRecordType>(
expandableConfig.expandedRowRender,
onTriggerExpand,
expandableConfig.expandIconColumnIndex,
expandableConfig.expandedRowOffset,
expandableConfig.indentSize,
emptyNode,

Expand Down
2 changes: 2 additions & 0 deletions src/context/TableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
ColumnsType,
ColumnType,
Direction,
ExpandableConfig,
ExpandableType,
ExpandedRowRender,
GetComponent,
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface TableContextProps<RecordType = any> {
expandIcon: RenderExpandIcon<RecordType>;
onTriggerExpand: TriggerEventHandler<RecordType>;
expandIconColumnIndex: number;
expandedRowOffset: ExpandableConfig<RecordType>['expandedRowOffset'];
allColumnsFixedLeft: boolean;

// Column
Expand Down
1 change: 1 addition & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export interface ExpandableConfig<RecordType> {
rowExpandable?: (record: RecordType) => boolean;
columnWidth?: number | string;
fixed?: FixedType;
expandedRowOffset?: number;
}

// =================== Render ===================
Expand Down