Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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: 6 additions & 2 deletions src/Body/BodyRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export function getCellProps<RecordType>(
);
}

const additionalCellProps = column.onCell?.(record, index) || {};
const additionalCellProps = { ...(column.onCell?.(record, index) || {}) };
let hoverRowSpan: number | undefined;

// Expandable row has offset
if (expandedRowOffset) {
Expand All @@ -93,6 +94,7 @@ export function getCellProps<RecordType>(
// For expandable row with rowSpan,
// We should increase the rowSpan if the row is expanded
if (expandable && rowSpan && colIndex < expandedRowOffset) {
hoverRowSpan = rowSpan;
let currentRowSpan = rowSpan;

for (let i = index; i < index + rowSpan; i += 1) {
Expand All @@ -110,6 +112,7 @@ export function getCellProps<RecordType>(
fixedInfo,
appendCellNode,
additionalCellProps: additionalCellProps,
hoverRowSpan,
};
}

Expand Down Expand Up @@ -188,7 +191,7 @@ const BodyRow = <RecordType extends { children?: readonly RecordType[] }>(
{flattenColumns.map((column: ColumnType<RecordType>, colIndex) => {
const { render, dataIndex, className: columnClassName } = column;

const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps(
const { key, fixedInfo, appendCellNode, additionalCellProps, hoverRowSpan } = getCellProps(
rowInfo,
column,
colIndex,
Expand Down Expand Up @@ -217,6 +220,7 @@ const BodyRow = <RecordType extends { children?: readonly RecordType[] }>(
{...fixedInfo}
appendNode={appendCellNode}
additionalProps={additionalCellProps}
hoverRowSpan={hoverRowSpan}
/>
);
})}
Expand Down
8 changes: 6 additions & 2 deletions src/Cell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export interface CellProps<RecordType extends DefaultRecordType> {
/** @private Used for `expandable` with nest tree */
appendNode?: React.ReactNode;
additionalProps?: React.TdHTMLAttributes<HTMLTableCellElement>;
/** @private Keep hover range independent from layout rowSpan patched by expanded row */
hoverRowSpan?: number;

rowType?: 'header' | 'body' | 'footer';

Expand Down Expand Up @@ -123,6 +125,7 @@ const Cell = <RecordType,>(props: CellProps<RecordType>) => {
// Private
appendNode,
additionalProps = {},
hoverRowSpan,
isSticky,
} = props;

Expand Down Expand Up @@ -183,13 +186,14 @@ const Cell = <RecordType,>(props: CellProps<RecordType>) => {
// ================ RowSpan & ColSpan =================
const mergedColSpan = legacyCellProps?.colSpan ?? additionalProps.colSpan ?? colSpan ?? 1;
const mergedRowSpan = legacyCellProps?.rowSpan ?? additionalProps.rowSpan ?? rowSpan ?? 1;
const mergedHoverRowSpan = legacyCellProps?.rowSpan ?? hoverRowSpan ?? mergedRowSpan;

// ====================== Hover =======================
const [hovering, onHover] = useHoverState(index, mergedRowSpan);
const [hovering, onHover] = useHoverState(index, mergedHoverRowSpan);

const onMouseEnter: React.MouseEventHandler<HTMLTableCellElement> = useEvent(event => {
if (record) {
onHover(index, index + mergedRowSpan - 1);
onHover(index, index + mergedHoverRowSpan - 1);
}

additionalProps?.onMouseEnter?.(event);
Expand Down
3 changes: 2 additions & 1 deletion src/VirtualTable/VirtualCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const VirtualCell = <RecordType,>(props: VirtualCellProps<RecordType>) => {
const { columnsOffset } = useContext(GridContext, ['columnsOffset']);

// TODO: support `expandableRowOffset`
const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps(
const { key, fixedInfo, appendCellNode, additionalCellProps, hoverRowSpan } = getCellProps(
rowInfo,
column,
colIndex,
Expand Down Expand Up @@ -128,6 +128,7 @@ const VirtualCell = <RecordType,>(props: VirtualCellProps<RecordType>) => {
shouldCellUpdate={column.shouldCellUpdate}
{...fixedInfo}
appendNode={appendCellNode}
hoverRowSpan={hoverRowSpan}
additionalProps={{
...additionalCellProps,
style: mergedStyle,
Expand Down
160 changes: 160 additions & 0 deletions tests/Hover.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Table from '../src';
import type { TableProps } from '../src/Table';

describe('Table.Hover', () => {
const hoverClassName = 'rc-table-cell-row-hover';
const data = [
{ key: 'key0', name: 'Lucy' },
{ key: 'key1', name: 'Jack' },
Expand Down Expand Up @@ -127,6 +128,165 @@ describe('Table.Hover', () => {
expect(container.querySelector('.rc-table-cell-row-hover')).toBeFalsy();
});

it('does not let expanded row offset rowSpan affect hover range', () => {
const { container } = render(
<Table
rowKey="key"
columns={[
{
dataIndex: 'group',
onCell: (_, index) => {
if (index === 0) {
return { rowSpan: 2 };
}
if (index === 1) {
return { rowSpan: 0 };
}
return {};
},
},
Table.EXPAND_COLUMN,
{
dataIndex: 'name',
},
]}
data={[
{ key: 'a', group: 'Group 1', name: 'Alpha' },
{ key: 'b', group: 'Group 1', name: 'Beta' },
{ key: 'c', group: 'Group 2', name: 'Gamma' },
]}
expandable={{
expandedRowOffset: 1,
expandedRowKeys: ['a'],
expandedRowRender: record => <span>expanded {record.key}</span>,
}}
/>,
);

const getCell = (text: string) => {
const cell = Array.from(container.querySelectorAll<HTMLTableCellElement>('tbody td')).find(
cell => cell.textContent === text,
);
expect(cell).toBeTruthy();
return cell!;
};

const groupCell = getCell('Group 1');
const betaCell = getCell('Beta');
const gammaCell = getCell('Gamma');

expect(groupCell.getAttribute('rowspan')).toBe('3');

fireEvent.mouseEnter(groupCell);
expect(groupCell.classList.contains(hoverClassName)).toBe(true);
expect(betaCell.classList.contains(hoverClassName)).toBe(true);
expect(gammaCell.classList.contains(hoverClassName)).toBe(false);

fireEvent.mouseEnter(gammaCell);
expect(groupCell.classList.contains(hoverClassName)).toBe(false);
expect(gammaCell.classList.contains(hoverClassName)).toBe(true);
});

it('keeps legacy render rowSpan priority for hover range', () => {
const { container } = render(
<Table
rowKey="key"
columns={[
{
dataIndex: 'group',
render: (value, _, index) => ({
children: value,
props: { rowSpan: index === 0 ? 2 : 0 },
}),
},
Table.EXPAND_COLUMN,
{
dataIndex: 'name',
},
]}
data={[
{ key: 'a', group: 'Group 1', name: 'Alpha' },
{ key: 'b', group: 'Group 1', name: 'Beta' },
{ key: 'c', group: 'Group 2', name: 'Gamma' },
]}
expandable={{
expandedRowOffset: 1,
expandedRowRender: record => <span>expanded {record.key}</span>,
}}
/>,
);

const getCell = (text: string) => {
const cell = Array.from(container.querySelectorAll<HTMLTableCellElement>('tbody td')).find(
item => item.textContent === text,
);
expect(cell).toBeTruthy();
return cell!;
};

const groupCell = getCell('Group 1');
const alphaCell = getCell('Alpha');
const betaCell = getCell('Beta');
const gammaCell = getCell('Gamma');

expect(groupCell.getAttribute('rowspan')).toBe('2');

fireEvent.mouseEnter(groupCell);
expect(alphaCell.classList.contains(hoverClassName)).toBe(true);
expect(betaCell.classList.contains(hoverClassName)).toBe(true);
expect(gammaCell.classList.contains(hoverClassName)).toBe(false);
});
Comment thread
jiangrong-devops marked this conversation as resolved.

it('does not mutate stable onCell props across expanded row renders', () => {
const rowSpanProps = [{ rowSpan: 2 }, { rowSpan: 0 }, {}];
const dataSource = [
{ key: 'a', group: 'Group 1', name: 'Alpha' },
{ key: 'b', group: 'Group 1', name: 'Beta' },
{ key: 'c', group: 'Group 2', name: 'Gamma' },
];

const createTableWithExpandedKeys = (expandedRowKeys: React.Key[]) => (
<React.StrictMode>
<Table
rowKey="key"
columns={[
{
dataIndex: 'group',
onCell: (_, index) => rowSpanProps[index],
},
Table.EXPAND_COLUMN,
{
dataIndex: 'name',
},
]}
data={dataSource}
expandable={{
expandedRowOffset: 1,
expandedRowKeys,
expandedRowRender: record => <span>expanded {record.key}</span>,
}}
/>
</React.StrictMode>
);

const { container, rerender } = render(createTableWithExpandedKeys([]));
const getGroupCell = () =>
Array.from(container.querySelectorAll<HTMLTableCellElement>('tbody td')).find(
cell => cell.textContent === 'Group 1',
)!;

expect(rowSpanProps[0].rowSpan).toBe(2);
expect(getGroupCell().getAttribute('rowspan')).toBe('2');

rerender(createTableWithExpandedKeys(['a']));
expect(rowSpanProps[0].rowSpan).toBe(2);
expect(getGroupCell().getAttribute('rowspan')).toBe('3');

rerender(createTableWithExpandedKeys([]));
expect(rowSpanProps[0].rowSpan).toBe(2);
expect(getGroupCell().getAttribute('rowspan')).toBe('2');
});

describe('perf', () => {
it('legacy mode should render every time', () => {
let renderTimes = 0;
Expand Down