diff --git a/packages/table/src/Body/BodyRow.tsx b/packages/table/src/Body/BodyRow.tsx index 8dee2917..e6ec2db5 100644 --- a/packages/table/src/Body/BodyRow.tsx +++ b/packages/table/src/Body/BodyRow.tsx @@ -55,6 +55,7 @@ export function getCellProps( const fixedInfo = fixedInfoList[colIndex] let appendCellNode: any + let hoverRowSpan: number | undefined if (colIndex === (expandIconColumnIndex || 0) && nestExpandable.value) { appendCellNode = ( <> @@ -78,6 +79,7 @@ export function getCellProps( if (expandedRowOffset) { const { rowSpan = 1 } = additionalCellProps if (expandable.value && rowSpan && colIndex < expandedRowOffset) { + hoverRowSpan = rowSpan let currentRowSpan = rowSpan for (let i = index; i < index + rowSpan; i += 1) { const keyInRow = rowKeys[i] @@ -94,6 +96,7 @@ export function getCellProps( fixedInfo, appendCellNode, additionalCellProps, + hoverRowSpan, } } @@ -188,7 +191,7 @@ const BodyRow = defineComponent>({ {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, record, column, @@ -219,6 +222,7 @@ const BodyRow = defineComponent>({ rowType="body" {...fixedInfo} additionalProps={additionalCellProps} + hoverRowSpan={hoverRowSpan} column={column} appendNode={appendCellNode} /> diff --git a/packages/table/src/Cell/index.tsx b/packages/table/src/Cell/index.tsx index fa2cf747..f216564e 100644 --- a/packages/table/src/Cell/index.tsx +++ b/packages/table/src/Cell/index.tsx @@ -37,6 +37,7 @@ export interface CellProps { children?: any colSpan?: number rowSpan?: number + hoverRowSpan?: number scope?: ScopeType ellipsis?: CellEllipsisType align?: AlignType @@ -166,6 +167,7 @@ const Cell = defineComponent>({ 'children', 'colSpan', 'rowSpan', + 'hoverRowSpan', 'scope', 'ellipsis', 'align', @@ -233,6 +235,7 @@ const Cell = defineComponent>({ rowType, colSpan, rowSpan, + hoverRowSpan, fixStart, fixEnd, fixedStartShadow, @@ -270,12 +273,13 @@ const Cell = defineComponent>({ const mergedColSpan = legacyCellProps?.colSpan ?? additionalProps.colSpan ?? colSpan ?? 1 const mergedRowSpan = legacyCellProps?.rowSpan ?? additionalProps.rowSpan ?? rowSpan ?? 1 + const mergedHoverRowSpan = hoverRowSpan ?? mergedRowSpan - const [hovering, onHover] = useHoverState(index!, mergedRowSpan, tableContext) + const [hovering, onHover] = useHoverState(index!, mergedHoverRowSpan, tableContext) const onMouseEnter = (event: MouseEvent) => { if (record) { - onHover(index!, index! + mergedRowSpan - 1) + onHover(index!, index! + mergedHoverRowSpan - 1) } const onMouseEnterHandler = additionalProps.onMouseEnter || additionalProps.onMouseenter onMouseEnterHandler?.(event) diff --git a/packages/table/src/VirtualTable/VirtualCell.tsx b/packages/table/src/VirtualTable/VirtualCell.tsx index be8dfad7..48533bf5 100644 --- a/packages/table/src/VirtualTable/VirtualCell.tsx +++ b/packages/table/src/VirtualTable/VirtualCell.tsx @@ -67,7 +67,7 @@ const VirtualCell = defineComponent>({ const { render, dataIndex, className: columnClassName, width: colWidth } = column const columnsOffset = gridContext.columnsOffset || [] - const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps( + const { key, fixedInfo, appendCellNode, additionalCellProps, hoverRowSpan } = getCellProps( rowInfo, record, column, @@ -132,6 +132,7 @@ const VirtualCell = defineComponent>({ shouldCellUpdate={column.shouldCellUpdate} {...fixedInfo} appendNode={appendCellNode} + hoverRowSpan={hoverRowSpan} additionalProps={{ ...additionalCellProps, style: mergedStyle, diff --git a/packages/table/tests/hover-expand-offset.test.tsx b/packages/table/tests/hover-expand-offset.test.tsx new file mode 100644 index 00000000..48930cc2 --- /dev/null +++ b/packages/table/tests/hover-expand-offset.test.tsx @@ -0,0 +1,99 @@ +// @vitest-environment jsdom + +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import { h, nextTick } from 'vue' +import Table from '../src' + +describe('table hover with expandedRowOffset', () => { + it('does not include expanded rows in rowSpan hover range', async () => { + const columns = [ + { + title: 'Team', + dataIndex: 'team', + key: 'team', + onCell: (_record: any, index = 0) => index % 2 === 0 ? { rowSpan: 2 } : { rowSpan: 0 }, + }, + Table.EXPAND_COLUMN, + { title: 'Name', dataIndex: 'name', key: 'name' }, + { title: 'Age', dataIndex: 'age', key: 'age' }, + ] + const data = [ + { key: '1', team: 'Team A', name: 'John', age: 32, description: 'John details' }, + { key: '2', team: 'Team A', name: 'Jim', age: 42, description: 'Jim details' }, + { key: '3', team: 'Team B', name: 'Joe', age: 22, description: 'Joe details' }, + ] + + const wrapper = mount(Table, { + props: { + columns, + data, + expandable: { defaultExpandedRowKeys: ['1'], expandedRowOffset: 3 }, + }, + slots: { + expandedRowRender: ({ record }: any) => h('div', { class: 'expanded-content' }, record.description), + }, + }) + + const firstRow = wrapper.find('tbody tr[data-row-key="1"]') + const thirdRow = wrapper.find('tbody tr[data-row-key="3"]') + + await firstRow.find('td').trigger('mouseenter') + await nextTick() + + expect(firstRow.findAll('td').some(cell => cell.classes().includes('vc-table-cell-row-hover'))).toBe(true) + expect(thirdRow.findAll('td').some(cell => cell.classes().includes('vc-table-cell-row-hover'))).toBe(false) + + wrapper.unmount() + }) + + it('does not hover previous offset cells when moving into the next grouped row', async () => { + const columns = [ + { + title: 'Team', + dataIndex: 'team', + key: 'team', + onCell: (_record: any, index = 0) => index % 2 === 0 ? { rowSpan: 2 } : { rowSpan: 0 }, + }, + Table.EXPAND_COLUMN, + { title: 'Name', dataIndex: 'name', key: 'name' }, + { title: 'Age', dataIndex: 'age', key: 'age' }, + { title: 'Address', dataIndex: 'address', key: 'address' }, + ] + const data = [ + { key: '1', team: 'Team A', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', description: 'John details' }, + { key: '2', team: 'Team A', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', description: 'Jim details' }, + { key: '3', team: 'Team B', name: 'Not Expandable', age: 29, address: 'Jiangsu No. 1 Lake Park', description: 'This not expandable' }, + { key: '4', team: 'Team B', name: 'Joe Black', age: 32, address: 'Sydney No. 1 Lake Park', description: 'Joe details' }, + ] + + const wrapper = mount(Table, { + props: { + columns, + data, + expandable: { defaultExpandedRowKeys: ['1', '2', '3', '4'], expandedRowOffset: 3 }, + }, + slots: { + expandedRowRender: ({ record }: any) => h('div', { class: 'expanded-content' }, record.description), + }, + }) + + const allBodyCells = wrapper.findAll('tbody td') + const teamACell = allBodyCells.find(cell => cell.text() === 'Team A') + const jimNameCell = allBodyCells.find(cell => cell.text() === 'Jim Green') + const notExpandableAgeCell = allBodyCells.find(cell => cell.text() === '29') + + expect(teamACell).toBeTruthy() + expect(jimNameCell).toBeTruthy() + expect(notExpandableAgeCell).toBeTruthy() + + await notExpandableAgeCell!.trigger('mouseenter') + await nextTick() + + expect(notExpandableAgeCell!.classes()).toContain('vc-table-cell-row-hover') + expect(teamACell!.classes()).not.toContain('vc-table-cell-row-hover') + expect(jimNameCell!.classes()).not.toContain('vc-table-cell-row-hover') + + wrapper.unmount() + }) +})