diff --git a/src/Body/BodyRow.tsx b/src/Body/BodyRow.tsx index aae9f4c7b..7a091a1bc 100644 --- a/src/Body/BodyRow.tsx +++ b/src/Body/BodyRow.tsx @@ -85,6 +85,7 @@ export function getCellProps( } const additionalCellProps = column.onCell?.(record, index) || {}; + let hoverRowSpan: number | undefined; // Expandable row has offset if (expandedRowOffset) { @@ -93,6 +94,7 @@ export function getCellProps( // 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) { @@ -110,6 +112,7 @@ export function getCellProps( fixedInfo, appendCellNode, additionalCellProps: additionalCellProps, + hoverRowSpan, }; } @@ -187,7 +190,7 @@ const BodyRow = ( {flattenColumns.map((column: ColumnType, colIndex) => { const { render, dataIndex, className: columnClassName } = column; - const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps( + const { key, fixedInfo, appendCellNode, additionalCellProps, hoverRowSpan } = getCellProps( rowInfo, column, colIndex, @@ -216,6 +219,7 @@ const BodyRow = ( {...fixedInfo} appendNode={appendCellNode} additionalProps={additionalCellProps} + hoverRowSpan={hoverRowSpan} /> ); })} diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index 3d07772b5..b557dab69 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -52,6 +52,8 @@ export interface CellProps { /** @private Used for `expandable` with nest tree */ appendNode?: React.ReactNode; additionalProps?: React.TdHTMLAttributes; + /** @private Keep hover range independent from layout rowSpan patched by expanded row */ + hoverRowSpan?: number; rowType?: 'header' | 'body' | 'footer'; @@ -123,6 +125,7 @@ const Cell = (props: CellProps) => { // Private appendNode, additionalProps = {}, + hoverRowSpan, isSticky, } = props; @@ -183,13 +186,14 @@ const Cell = (props: CellProps) => { // ================ RowSpan & ColSpan ================= const mergedColSpan = legacyCellProps?.colSpan ?? additionalProps.colSpan ?? colSpan ?? 1; const mergedRowSpan = legacyCellProps?.rowSpan ?? additionalProps.rowSpan ?? rowSpan ?? 1; + const mergedHoverRowSpan = hoverRowSpan ?? mergedRowSpan; // ====================== Hover ======================= - const [hovering, onHover] = useHoverState(index, mergedRowSpan); + const [hovering, onHover] = useHoverState(index, mergedHoverRowSpan); const onMouseEnter: React.MouseEventHandler = useEvent(event => { if (record) { - onHover(index, index + mergedRowSpan - 1); + onHover(index, index + mergedHoverRowSpan - 1); } additionalProps?.onMouseEnter?.(event); diff --git a/src/VirtualTable/VirtualCell.tsx b/src/VirtualTable/VirtualCell.tsx index ebcfff4ad..3fe2bb180 100644 --- a/src/VirtualTable/VirtualCell.tsx +++ b/src/VirtualTable/VirtualCell.tsx @@ -57,7 +57,7 @@ const VirtualCell = (props: VirtualCellProps) => { const { columnsOffset } = useContext(GridContext, ['columnsOffset']); // TODO: support `expandableRowOffset` - const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps( + const { key, fixedInfo, appendCellNode, additionalCellProps, hoverRowSpan } = getCellProps( rowInfo, column, colIndex, @@ -128,6 +128,7 @@ const VirtualCell = (props: VirtualCellProps) => { shouldCellUpdate={column.shouldCellUpdate} {...fixedInfo} appendNode={appendCellNode} + hoverRowSpan={hoverRowSpan} additionalProps={{ ...additionalCellProps, style: mergedStyle, diff --git a/tests/ExpandedOffset.spec.tsx b/tests/ExpandedOffset.spec.tsx index 12d345369..5f20106f9 100644 --- a/tests/ExpandedOffset.spec.tsx +++ b/tests/ExpandedOffset.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook'; import { render, act } from '@testing-library/react'; import { _rs } from '@rc-component/resize-observer'; import Table, { type ColumnsType } from '../src'; diff --git a/tests/Hover.spec.tsx b/tests/Hover.spec.tsx index 813f09a6b..341440b1a 100644 --- a/tests/Hover.spec.tsx +++ b/tests/Hover.spec.tsx @@ -6,6 +6,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' }, @@ -128,6 +129,65 @@ 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( + { + 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 => expanded {record.key}, + }} + />, + ); + + const getCell = (text: string) => { + const cell = Array.from(container.querySelectorAll('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); + }); + describe('perf', () => { it('legacy mode should render every time', () => { let renderTimes = 0;