Skip to content
Merged
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
1 change: 0 additions & 1 deletion assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@

padding: @cell-padding;
white-space: normal;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The removal of word-break: break-word; could lead to layout issues where long words in table cells overflow their containers. It's advisable to re-introduce word wrapping. Consider using overflow-wrap: break-word;, which is the current standard for this purpose.

    white-space: normal;
    overflow-wrap: break-word;

word-break: break-word;
border: @border;
border-top: 0;
border-left: 0;
Expand Down
47 changes: 47 additions & 0 deletions docs/examples/measureRowRender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import Table from 'rc-table';

// 示例:使用 measureRowRender 来隐藏 MeasureRow 中的弹层
const MeasureRowRenderExample = () => {
const columns = [
{
title: (
<div>
Name
<div className="filter-dropdown" style={{ display: 'none' }}>
Filter Content
</div>
</div>
),
dataIndex: 'name',
key: 'name',
width: 100,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 80,
},
];

const data = [
{ key: 1, name: 'John', age: 25 },
{ key: 2, name: 'Jane', age: 30 },
];

// 自定义 MeasureRow 渲染,隐藏弹层内容
const measureRowRender = measureRow => <div style={{ display: 'none' }}>{measureRow}</div>;

Comment on lines +33 to +35
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

不要用 display: none 包裹测量行,会导致列宽测量为 0

测量行需要参与布局才能被 ResizeObserver 正确观测。建议使用 visibility: hidden 或 “视觉隐藏” 技术。

-  // 自定义 MeasureRow 渲染,隐藏弹层内容
-  const measureRowRender = measureRow => <div style={{ display: 'none' }}>{measureRow}</div>;
+  // 自定义 MeasureRow 渲染,隐藏但仍参与布局,保证可测量
+  const measureRowRender = measureRow => (
+    <div style={{ visibility: 'hidden', pointerEvents: 'none' }}>{measureRow}</div>
+  );
📝 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
// 自定义 MeasureRow 渲染,隐藏弹层内容
const measureRowRender = measureRow => <div style={{ display: 'none' }}>{measureRow}</div>;
// 自定义 MeasureRow 渲染,隐藏但仍参与布局,保证可测量
const measureRowRender = measureRow => (
<div style={{ visibility: 'hidden', pointerEvents: 'none' }}>{measureRow}</div>
);
🤖 Prompt for AI Agents
In docs/examples/measureRowRender.tsx around lines 33 to 35, the measurement row
is wrapped with style display: 'none' which removes it from layout and causes
column width measurements to be 0; replace display: 'none' with style that
preserves layout (e.g., visibility: 'hidden') or apply a "visually hidden" CSS
utility so the element remains in the flow for ResizeObserver while remaining
invisible to users.

return (
<Table
columns={columns}
data={data}
sticky
scroll={{ x: true }}
measureRowRender={measureRowRender}
/>
);
};

export default MeasureRowRenderExample;
3 changes: 3 additions & 0 deletions docs/examples/scrollY.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const Test = () => {
rowKey={record => record.key}
onRow={(record, index) => ({ style: { backgroundColor: 'red' } })}
/>
<h3>Column align issue</h3>
<p>https://github.com/ant-design/ant-design/issues/54889</p>
<Table columns={columns} data={data} sticky scroll={{ y: 300, x: 2000 }} />
</div>
);
};
Expand Down
50 changes: 46 additions & 4 deletions docs/examples/stickyHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,34 @@ const data = [
{ a: '1333', c: 'eee', d: 2, key: '20' },
];

const columns3: ColumnType<RecordType>[] = [
{ title: '', dataIndex: 'name', key: '0' },
{ title: 'First column', dataIndex: 'name', key: '1' },
{ title: 'Second column', dataIndex: 'address', key: '2' },
{ title: 'Third column', dataIndex: 'age', key: '3' },
{ title: 'Another column', dataIndex: 'address', key: '4' },
{ title: 'Extra column', dataIndex: 'name', key: '5' },
{ title: 'And yet another column', dataIndex: 'address', key: '6' },
{
title: 'Column 7 with extraaaaaaaaa long word',
dataIndex: 'age',
key: '7',
},
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{ title: 'Column 9', dataIndex: 'name', key: '9' },
{ title: 'Column 10', dataIndex: 'address', key: '10' },
{ title: 'Column 11', dataIndex: 'address', key: '11' },
{ title: 'Column 12', dataIndex: 'age', key: '12' },
{ title: 'Column 13', dataIndex: 'address', key: '13' },
{ title: 'Column 14', dataIndex: 'name', key: '14' },
{ title: 'Column 15', dataIndex: 'address', key: '15' },
{ title: 'Column 16', dataIndex: 'address', key: '16' },
{ title: 'Column 17', dataIndex: 'name', key: '17' },
{ title: 'Column 18', dataIndex: 'address', key: '18' },
{ title: 'Column 19', dataIndex: 'address', key: '19' },
{ title: 'Column 20', dataIndex: 'age', key: '20' },
];

const Demo = () => {
const container = useRef();
return (
Expand Down Expand Up @@ -274,7 +302,7 @@ const Demo = () => {
<br />
<Table
columns={fixedColumns}
data={[{}]}
data={[{ key: '1' }]}
scroll={{
x: 'max-content',
}}
Expand All @@ -283,7 +311,7 @@ const Demo = () => {
<br />
<Table
columns={columnsWithWidth}
data={[{}]}
data={[{ key: '1' }]}
scroll={{
x: 1200,
}}
Expand All @@ -301,7 +329,7 @@ const Demo = () => {
<br />
<Table
columns={fixedColumns.map(column => ({ ...column, width: undefined }))}
data={[{}]}
data={[{ key: '1' }]}
scroll={{
x: 'max-content',
}}
Expand All @@ -310,12 +338,26 @@ const Demo = () => {
<br />
<Table
columns={columnsGrouped}
data={[{}, {}]}
data={[{ key: '1' }, { key: '2' }]}
scroll={{
x: 'max-content',
}}
sticky
/>
<br />
<h3>scroll.x is true</h3>
<p>https://github.com/ant-design/ant-design/issues/54894</p>
<Table
columns={columns3}
data={
[
{ key: '1', name: 'Test', age: 1, address: '2' },
{ key: '2', name: '0', age: 1, address: '2' },
] as any
}
Comment on lines +352 to +357
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using as any bypasses TypeScript's type checking and should be avoided. The issue stems from columns3 being typed with RecordType, which doesn't include properties like name, age, or address used in the data. To fix this properly, you should:

  1. Define a new interface for this specific data structure (e.g., StickyData).
  2. Update the type of columns3 to use this new interface (e.g., ColumnType<StickyData>[]).
  3. Remove the as any cast here.

sticky
scroll={{ x: true }}
/>
</div>
);
};
Expand Down
7 changes: 6 additions & 1 deletion src/Body/MeasureRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as React from 'react';
import ResizeObserver from '@rc-component/resize-observer';
import MeasureCell from './MeasureCell';
import isVisible from '@rc-component/util/lib/Dom/isVisible';
import { useContext } from '@rc-component/context';
import TableContext from '../context/TableContext';
import type { ColumnType } from '../interface';

export interface MeasureRowProps {
Expand All @@ -18,8 +20,9 @@ export default function MeasureRow({
columns,
}: MeasureRowProps) {
const ref = React.useRef<HTMLTableRowElement>(null);
const { measureRowRender } = useContext(TableContext, ['measureRowRender']);

return (
const measureRow = (
<tr aria-hidden="true" className={`${prefixCls}-measure-row`} style={{ height: 0 }} ref={ref}>
<ResizeObserver.Collection
onBatchResize={infoList => {
Expand All @@ -44,4 +47,6 @@ export default function MeasureRow({
</ResizeObserver.Collection>
</tr>
);

return measureRowRender ? measureRowRender(measureRow) : measureRow;
}
2 changes: 1 addition & 1 deletion src/ColGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function ColGroup<RecordType>({ colWidths, columns, columCount }: ColGroupProps<
}
}

return <colgroup>{cols}</colgroup>;
return cols.length > 0 ? <colgroup>{cols}</colgroup> : null;
}

export default ColGroup;
9 changes: 5 additions & 4 deletions src/FixedHolder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface FixedHeaderProps<RecordType> extends HeaderProps<RecordType> {
stickyTopOffset?: number;
stickyBottomOffset?: number;
stickyClassName?: string;
scrollTableStyle?: React.CSSProperties;
scrollX?: number | string | true;
tableLayout?: TableLayout;
onScroll: (info: { currentTarget: HTMLDivElement; scrollLeft?: number }) => void;
children: (info: HeaderProps<RecordType>) => React.ReactNode;
Expand All @@ -61,7 +61,7 @@ const FixedHolder = React.forwardRef<HTMLDivElement, FixedHeaderProps<any>>((pro
stickyTopOffset,
stickyBottomOffset,
stickyClassName,
scrollTableStyle,
scrollX,
tableLayout = 'fixed',
onScroll,
maxContentScroll,
Expand Down Expand Up @@ -197,8 +197,9 @@ const FixedHolder = React.forwardRef<HTMLDivElement, FixedHeaderProps<any>>((pro
<TableComponent
style={{
tableLayout,
visibility: noData || mergedColumnWidth ? null : 'hidden',
...scrollTableStyle,
minWidth: '100%',
// https://github.com/ant-design/ant-design/issues/54894
width: scrollX,
}}
Comment on lines +200 to 203
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

严重:width: scrollX 可能为 boolean,类型不兼容且会生成无效 CSS

scrollX === true 时传入 boolean,将导致 TS 类型错误并在浏览器生成 width: true 的无效样式。与 Table.tsx 的逻辑保持一致应使用 'auto'

       <TableComponent
         style={{
           tableLayout,
           minWidth: '100%',
           // https://github.com/ant-design/ant-design/issues/54894
-          width: scrollX,
+          width: scrollX === true ? 'auto' : scrollX,
         }}
       >
📝 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
minWidth: '100%',
// https://github.com/ant-design/ant-design/issues/54894
width: scrollX,
}}
<TableComponent
style={{
tableLayout,
minWidth: '100%',
// https://github.com/ant-design/ant-design/issues/54894
width: scrollX === true ? 'auto' : scrollX,
}}
>
🤖 Prompt for AI Agents
In src/FixedHolder/index.tsx around lines 200 to 203, the code sets width:
scrollX which can be a boolean and will produce invalid CSS (e.g. width: true)
and TypeScript errors; change the width expression to mirror Table.tsx by using
'auto' when scrollX is true (e.g. width: typeof scrollX === 'boolean' ? 'auto' :
scrollX) so width is always a valid string/number and update typing if needed to
satisfy CSSProperties.

>
{colGroupNode}
Expand Down
16 changes: 15 additions & 1 deletion src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ export interface TableProps<RecordType = any>
internalRefs?: {
body: React.MutableRefObject<HTMLDivElement>;
};
/**
* @private Internal usage, may remove by refactor.
*
* !!! DO NOT USE IN PRODUCTION ENVIRONMENT !!!
*/
measureRowRender?: (measureRow: React.ReactNode) => React.ReactNode;
}

function defaultEmpty() {
Expand Down Expand Up @@ -222,6 +228,9 @@ function Table<RecordType extends DefaultRecordType>(
onRow,
onHeaderRow,

// Measure Row
measureRowRender,

// Events
onScroll,

Expand Down Expand Up @@ -719,7 +728,7 @@ function Table<RecordType extends DefaultRecordType>(
...columnContext,
direction,
stickyClassName,
scrollTableStyle,
scrollX: mergedScrollX,
tableLayout: mergedTableLayout,
onScroll: onInternalScroll,
};
Expand Down Expand Up @@ -911,6 +920,9 @@ function Table<RecordType extends DefaultRecordType>(
childrenColumnName: mergedChildrenColumnName,

rowHoverable,

// Measure Row
measureRowRender,
}),
[
// Scroll
Expand Down Expand Up @@ -962,6 +974,8 @@ function Table<RecordType extends DefaultRecordType>(
mergedChildrenColumnName,

rowHoverable,

measureRowRender,
],
);

Expand Down
3 changes: 3 additions & 0 deletions src/context/TableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export interface TableContextProps<RecordType = any> {
rowHoverable?: boolean;

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

// Measure Row
measureRowRender?: (measureRow: React.ReactNode) => React.ReactNode;
}

const TableContext = createContext<TableContextProps>();
Expand Down
87 changes: 66 additions & 21 deletions tests/FixedHeader.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, act, fireEvent } from '@testing-library/react';
import RcResizeObserver, { _rs } from '@rc-component/resize-observer';
import { spyElementPrototype, spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
import { render, act } from '@testing-library/react';
import { _rs } from '@rc-component/resize-observer';
import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
import React from 'react';
import Table, { INTERNAL_COL_DEFINE } from '../src';
import { safeAct } from './utils';
Expand Down Expand Up @@ -42,7 +42,7 @@ describe('Table.FixedHeader', () => {
const col1 = { dataIndex: 'light', width: 100 };
const col2 = { dataIndex: 'bamboo', width: 200 };
const col3 = { dataIndex: 'empty', width: 0 };
const { container, rerender } = render(
const { container } = render(
<Table
columns={[col1, col2, col3]}
data={[{ light: 'bamboo', bamboo: 'light', key: 1 }]}
Expand Down Expand Up @@ -91,26 +91,13 @@ describe('Table.FixedHeader', () => {
expect(tables[0].querySelector('colgroup col').className).toEqual('test-internal');
});

it('show header when data is null', async () => {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
];

it('rtl', async () => {
const { container } = render(
<Table
columns={columns}
data={[]}
columns={[{ dataIndex: 'light', width: 100 }]}
data={[{ key: 0, light: 'bamboo' }]}
direction="rtl"
scroll={{
x: true,
y: 100,
}}
/>,
Expand Down Expand Up @@ -208,4 +195,62 @@ describe('Table.FixedHeader', () => {
expect(container.querySelectorAll('th.rc-table-cell-fix-start')).toHaveLength(2);
expect(container.querySelectorAll('th.rc-table-cell-fix-end')).toHaveLength(1);
});

it('should support measureRowRender to wrap MeasureRow with custom provider', async () => {
const FilterDropdown = ({ visible, onVisibleChange }) => (
<div className="test-filter-dropdown" style={{ display: visible ? 'block' : 'none' }}>
Filter Content
<button onClick={() => onVisibleChange && onVisibleChange(!visible)}>Toggle</button>
</div>
);

const columns = [
{
title: (
<div>
Name
<FilterDropdown visible={true} onVisibleChange={() => {}} />
</div>
),
dataIndex: 'name',
key: 'name',
width: 100,
},
];

const data = [
{
key: 1,
name: 'Jack',
},
];

// Mock ConfigProvider-like wrapper
const measureRowRender = measureRow => (
<div data-testid="measure-row-wrapper" style={{ display: 'none' }}>
{measureRow}
</div>
);

const { container } = render(
<Table
columns={columns}
data={data}
sticky
scroll={{ x: true }}
measureRowRender={measureRowRender}
/>,
);

await safeAct(container);

// Check that measureRowRender wrapper is applied
const measureRowWrapper = container.querySelectorAll('[data-testid="measure-row-wrapper"]');
expect(measureRowWrapper).toHaveLength(1);
expect(measureRowWrapper[0].style.display).toBe('none');

// Check that MeasureRow is inside the wrapper
const measureRowInWrapper = measureRowWrapper[0].querySelectorAll('.rc-table-measure-row');
expect(measureRowInWrapper).toHaveLength(1);
});
});
Loading