From d8917715e11f13c571276de6dc9e143f9dcf710a Mon Sep 17 00:00:00 2001 From: guaijie <30885718+guaijie@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:27:31 +0000 Subject: [PATCH] feat: Table add prop "colHoverable" --- src/Body/BodyRow.tsx | 3 +- src/Cell/index.tsx | 30 ++++++++++++------ src/Cell/useHoverState.ts | 11 +++++-- src/Footer/Cell.tsx | 2 +- src/Table.tsx | 18 +++++++++-- src/VirtualTable/VirtualCell.tsx | 3 +- src/context/TableContext.tsx | 7 ++++- src/hooks/useHover.ts | 20 ++++++++++-- tests/Hover.spec.tsx | 54 +++++++++++++++++++++++++++++--- 9 files changed, 121 insertions(+), 27 deletions(-) diff --git a/src/Body/BodyRow.tsx b/src/Body/BodyRow.tsx index 46ca866f8..451b3ddfa 100644 --- a/src/Body/BodyRow.tsx +++ b/src/Body/BodyRow.tsx @@ -165,7 +165,8 @@ function BodyRow( prefixCls={prefixCls} key={key} record={record} - index={index} + colIndex={colIndex} + rowIndex={index} renderIndex={renderIndex} dataIndex={dataIndex} render={render} diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index 8b0d22162..e1defbfc1 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -20,8 +20,9 @@ export interface CellProps { prefixCls?: string; className?: string; record?: RecordType; + colIndex?: number; /** `column` index is the real show rowIndex */ - index?: number; + rowIndex?: number; /** the index of the record. For the render(value, record, renderIndex) */ renderIndex?: number; dataIndex?: DataIndex; @@ -95,8 +96,10 @@ function Cell(props: CellProps) { renderIndex, shouldCellUpdate, + colIndex, + // Row - index, + rowIndex, rowType, // Span @@ -118,10 +121,9 @@ function Cell(props: CellProps) { } = props; const cellPrefixCls = `${prefixCls}-cell`; - const { supportSticky, allColumnsFixedLeft, rowHoverable } = useContext(TableContext, [ + const { supportSticky, allColumnsFixedLeft } = useContext(TableContext, [ 'supportSticky', 'allColumnsFixedLeft', - 'rowHoverable', ]); // ====================== Value ======================= @@ -153,11 +155,17 @@ function Cell(props: CellProps) { const mergedRowSpan = legacyCellProps?.rowSpan ?? additionalProps.rowSpan ?? rowSpan ?? 1; // ====================== Hover ======================= - const [hovering, onHover] = useHoverState(index, mergedRowSpan); + const [rowHovering, colHovering, onRowHover, onColHover] = useHoverState( + rowIndex, + mergedRowSpan, + colIndex, + mergedColSpan, + ); const onMouseEnter: React.MouseEventHandler = useEvent(event => { if (record) { - onHover(index, index + mergedRowSpan - 1); + onRowHover(rowIndex, rowIndex + mergedRowSpan - 1); + onColHover(colIndex, colIndex + mergedColSpan - 1); } additionalProps?.onMouseEnter?.(event); @@ -165,7 +173,8 @@ function Cell(props: CellProps) { const onMouseLeave: React.MouseEventHandler = useEvent(event => { if (record) { - onHover(-1, -1); + onRowHover(-1, -1); + onColHover(-1, -1); } additionalProps?.onMouseLeave?.(event); @@ -200,7 +209,8 @@ function Cell(props: CellProps) { [`${cellPrefixCls}-ellipsis`]: ellipsis, [`${cellPrefixCls}-with-append`]: appendNode, [`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky && supportSticky, - [`${cellPrefixCls}-row-hover`]: !legacyCellProps && hovering, + [`${cellPrefixCls}-row-hover`]: !legacyCellProps && rowHovering, + [`${cellPrefixCls}-col-hover`]: !legacyCellProps && colHovering, }, additionalProps.className, legacyCellProps?.className, @@ -247,8 +257,8 @@ function Cell(props: CellProps) { title={title} scope={scope} // Hover - onMouseEnter={rowHoverable ? onMouseEnter : undefined} - onMouseLeave={rowHoverable ? onMouseLeave : undefined} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} //Span colSpan={mergedColSpan !== 1 ? mergedColSpan : null} rowSpan={mergedRowSpan !== 1 ? mergedRowSpan : null} diff --git a/src/Cell/useHoverState.ts b/src/Cell/useHoverState.ts index b9ca23913..8793ffc42 100644 --- a/src/Cell/useHoverState.ts +++ b/src/Cell/useHoverState.ts @@ -11,10 +11,15 @@ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: numbe export default function useHoverState( rowIndex: number, rowSpan: number, -): [hovering: boolean, onHover: OnHover] { + colIndex: number, + colSpan: number, +): [hovering: boolean, colHovering: boolean, onRowHover: OnHover, onColHover: OnHover] { return useContext(TableContext, ctx => { - const hovering = inHoverRange(rowIndex, rowSpan || 1, ctx.hoverStartRow, ctx.hoverEndRow); + const rowHovering = + ctx.rowHoverable && inHoverRange(rowIndex, rowSpan || 1, ctx.hoverStartRow, ctx.hoverEndRow); + const colHovering = + ctx.colHoverable && inHoverRange(colIndex, colSpan || 1, ctx.hoverStartCol, ctx.hoverEndCol); - return [hovering, ctx.onHover]; + return [rowHovering, colHovering, ctx.onRowHover, ctx.onColHover]; }); } diff --git a/src/Footer/Cell.tsx b/src/Footer/Cell.tsx index 2ecbd407a..2516c325f 100644 --- a/src/Footer/Cell.tsx +++ b/src/Footer/Cell.tsx @@ -39,7 +39,7 @@ export default function SummaryCell({ return ( sticky?: boolean | TableSticky; + colHoverable?: boolean; rowHoverable?: boolean; // Events @@ -221,6 +222,7 @@ function Table( getContainerWidth, sticky, + colHoverable = false, rowHoverable = true, } = props; @@ -274,7 +276,7 @@ function Table( const customizeScrollBody = getComponent(['body']) as CustomizeScrollBody; // ====================== Hover ======================= - const [startRow, endRow, onHover] = useHover(); + const [startRow, endRow, startCol, endCol, onRowHover, onColHover] = useHover(); // ====================== Expand ====================== const [ @@ -835,10 +837,14 @@ function Table( flattenColumns, onColumnResize, + hoverStartCol: startCol, + hoverEndCol: endCol, + onColHover, + // Row hoverStartRow: startRow, hoverEndRow: endRow, - onHover, + onRowHover, rowExpandable: expandableConfig.rowExpandable, onRow, @@ -846,6 +852,7 @@ function Table( expandedKeys: mergedExpandedKeys, childrenColumnName: mergedChildrenColumnName, + colHoverable, rowHoverable, }), [ @@ -884,10 +891,14 @@ function Table( flattenColumns, onColumnResize, + startCol, + endCol, + onColHover, + // Row startRow, endRow, - onHover, + onRowHover, expandableConfig.rowExpandable, onRow, @@ -895,6 +906,7 @@ function Table( mergedExpandedKeys, mergedChildrenColumnName, + colHoverable, rowHoverable, ], ); diff --git a/src/VirtualTable/VirtualCell.tsx b/src/VirtualTable/VirtualCell.tsx index 9b1b3ebe5..63c7164b7 100644 --- a/src/VirtualTable/VirtualCell.tsx +++ b/src/VirtualTable/VirtualCell.tsx @@ -120,7 +120,8 @@ function VirtualCell(props: VirtualCellProps) { prefixCls={rowInfo.prefixCls} key={key} record={record} - index={index} + rowIndex={index} + colIndex={colIndex} renderIndex={renderIndex} dataIndex={dataIndex} render={mergedRender} diff --git a/src/context/TableContext.tsx b/src/context/TableContext.tsx index f566c84f0..8d8d38b0d 100644 --- a/src/context/TableContext.tsx +++ b/src/context/TableContext.tsx @@ -57,16 +57,21 @@ export interface TableContextProps { flattenColumns: readonly ColumnType[]; onColumnResize: (columnKey: React.Key, width: number) => void; + hoverStartCol: number; + hoverEndCol: number; + onColHover: (start: number, end: number) => void; + // Row hoverStartRow: number; hoverEndRow: number; - onHover: (start: number, end: number) => void; + onRowHover: (start: number, end: number) => void; rowExpandable: (record: RecordType) => boolean; expandedKeys: Set; getRowKey: GetRowKey; childrenColumnName: string; + colHoverable?: boolean; rowHoverable?: boolean; } diff --git a/src/hooks/useHover.ts b/src/hooks/useHover.ts index f67cb0591..436d76642 100644 --- a/src/hooks/useHover.ts +++ b/src/hooks/useHover.ts @@ -2,14 +2,28 @@ import * as React from 'react'; export type OnHover = (start: number, end: number) => void; -export default function useHover(): [startRow: number, endRow: number, onHover: OnHover] { +export default function useHover(): [ + startRow: number, + endRow: number, + startCol: number, + endCol: number, + onRowHover: OnHover, + onColHover: OnHover, +] { const [startRow, setStartRow] = React.useState(-1); const [endRow, setEndRow] = React.useState(-1); + const [startCol, setStartCol] = React.useState(-1); + const [endCol, setEndCol] = React.useState(-1); - const onHover = React.useCallback((start, end) => { + const onRowHover = React.useCallback((start, end) => { setStartRow(start); setEndRow(end); }, []); - return [startRow, endRow, onHover]; + const onColHover = React.useCallback((start, end) => { + setStartCol(start); + setEndCol(end); + }, []); + + return [startRow, endRow, startCol, endCol, onRowHover, onColHover]; } diff --git a/tests/Hover.spec.tsx b/tests/Hover.spec.tsx index 623a11750..8221da92d 100644 --- a/tests/Hover.spec.tsx +++ b/tests/Hover.spec.tsx @@ -13,16 +13,18 @@ describe('Table.Hover', () => { const createTable = (props?: TableProps) => { const columns = [{ title: 'Name', dataIndex: 'name', key: 'name' }]; - return ; + return
; }; it('basic', () => { const wrapper = mount(createTable()); wrapper.find('tbody td').first().simulate('mouseEnter'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeTruthy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeTruthy(); wrapper.find('tbody td').first().simulate('mouseLeave'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeFalsy(); }); it('works on shouldCellUpdate', () => { @@ -34,9 +36,11 @@ describe('Table.Hover', () => { wrapper.find('tbody td').first().simulate('mouseEnter'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeTruthy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeTruthy(); wrapper.find('tbody td').first().simulate('mouseLeave'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeFalsy(); }); it('warning if use `render` for rowSpan', () => { @@ -151,7 +155,7 @@ describe('Table.Hover', () => { renderTimes = 0; wrapper.find('tbody td').at(0).simulate('mouseEnter'); expect(wrapper.find('td.rc-table-cell-row-hover')).toHaveLength(1); - expect(renderTimes).toBe(1); + expect(renderTimes).toBe(2); // Hover 0-1 renderTimes = 0; @@ -163,7 +167,7 @@ describe('Table.Hover', () => { renderTimes = 0; wrapper.find('tbody td').at(1).simulate('mouseLeave'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); - expect(renderTimes).toBe(1); + expect(renderTimes).toBe(2); }); it('perf mode to save render times', () => { @@ -204,7 +208,7 @@ describe('Table.Hover', () => { }); }); - it('perf', () => { + it('perf row', () => { const renderTimes: Record = {}; const TD = (props: any) => { @@ -217,6 +221,7 @@ describe('Table.Hover', () => { const wrapper = mount( createTable({ + colHoverable: false, components: { body: { cell: TD, @@ -229,11 +234,52 @@ describe('Table.Hover', () => { wrapper.find('tbody td').first().simulate('mouseEnter'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeTruthy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeFalsy(); wrapper.find('tbody td').first().simulate('mouseLeave'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeFalsy(); expect(firstMountTimes).toEqual(renderTimes.Jack); expect(renderTimes.Lucy).toBeGreaterThan(renderTimes.Jack); }); + + it('perf col', () => { + const renderTimes: Record = {}; + + const TD = (props: any) => { + const children = toArray(props.children); + const first = children[0] as unknown as string; + + renderTimes[first] = (renderTimes[first] || 0) + 1; + return
; + }; + + const columns = [ + { title: 'index', dataIndex: 'key', key: 'key' }, + { title: 'Name', dataIndex: 'name', key: 'name' }, + ]; + + const wrapper = mount( + createTable({ + columns, + rowHoverable: false, + components: { + body: { + cell: TD, + }, + }, + }), + ); + + wrapper.find('tbody td').at(1).simulate('mouseEnter'); + expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeTruthy(); + + wrapper.find('tbody td').at(1).simulate('mouseLeave'); + expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); + expect(wrapper.exists('.rc-table-cell-col-hover')).toBeFalsy(); + + expect(renderTimes.Lucy).toEqual(renderTimes.Jack); + }); });