diff --git a/packages/components/table/__tests__/table.test.ts b/packages/components/table/__tests__/table.test.ts index 303cb5b6ae..41a36bd4ef 100644 --- a/packages/components/table/__tests__/table.test.ts +++ b/packages/components/table/__tests__/table.test.ts @@ -492,6 +492,68 @@ describe('Table.vue', () => { wrapper.unmount() }) + it('cell mouse enter on cell of which rowSpan > 2', async () => { + const wrapper = mount({ + components: { + ElTable, + ElTableColumn, + }, + template: ` + + + + + + + + `, + data() { + return { + testData: getTestData(), + } + }, + methods: { + objectSpanMethod({ rowIndex, columnIndex }) { + if (columnIndex === 0) { + if (rowIndex % 2 === 0) { + return { + rowspan: 2, + colspan: 1, + } + } else { + return { + rowspan: 0, + colspan: 0, + } + } + } + }, + }, + }) + const vm = wrapper.vm + await doubleWait() + const cell = vm.$el + .querySelectorAll('.el-table__body-wrapper tbody tr')[0] + .querySelector('.el-table__cell') + triggerEvent(cell, 'mouseenter', true, false) + await doubleWait() + await rAF() + await doubleWait() + const row = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr')[1] + expect([...row.classList]).toContain('hover-row') + await doubleWait() + triggerEvent(cell, 'mouseleave', true, false) + await rAF() + await doubleWait() + expect([...row.classList]).not.toContain('hover-row') + wrapper.unmount() + }) + it('cell-mouse-leave', async () => { const wrapper = createTable('cell-mouse-leave') await doubleWait() @@ -950,6 +1012,69 @@ describe('Table.vue', () => { wrapper.unmount() }) + it('hover on which rowSpan > 1', async () => { + const wrapper = mount({ + components: { + ElTable, + ElTableColumn, + }, + template: ` + + + + + + + + `, + data() { + return { + testData: getTestData(), + } + }, + methods: { + objectSpanMethod({ rowIndex, columnIndex }) { + if (columnIndex === 0) { + if (rowIndex % 2 === 0) { + return { + rowspan: 2, + colspan: 1, + } + } else { + return { + rowspan: 0, + colspan: 0, + } + } + } + }, + }, + }) + const vm = wrapper.vm + await doubleWait() + const rows = vm.$el.querySelectorAll('.el-table__body-wrapper tbody tr') + triggerEvent(rows[1], 'mouseenter', true, false) + await doubleWait() + await rAF() + await doubleWait() + const cell = vm.$el + .querySelectorAll('.el-table__body-wrapper tbody tr')[0] + .querySelector('.el-table__cell') + + expect([...cell.classList]).toContain('hover-cell') + await doubleWait() + triggerEvent(rows[1], 'mouseleave', true, false) + await rAF() + await doubleWait() + expect([...cell.classList]).not.toContain('hover-cell') + wrapper.unmount() + }) + it('highlight-current-row', async () => { const wrapper = mount({ components: { diff --git a/packages/components/table/src/table-body/events-helper.ts b/packages/components/table/src/table-body/events-helper.ts index 34df83abff..3070d4f145 100644 --- a/packages/components/table/src/table-body/events-helper.ts +++ b/packages/components/table/src/table-body/events-helper.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { h, inject, ref } from 'vue' import { debounce } from 'lodash-unified' -import { hasClass } from '@element-plus/utils' +import { addClass, hasClass, removeClass } from '@element-plus/utils' import { createTablePopper, getCell, getColumnByCell } from '../util' import { TABLE_INJECTION_KEY } from '../tokens' import type { TableColumnCtx } from '../table-column/defaults' @@ -60,6 +60,21 @@ function useEvents(props: Partial>) { bottom: paddingBottom, } } + + const toggleRowClassByCell = ( + rowSpan: number, + event: MouseEvent, + toggle: (el: Element, cls: string) => void + ) => { + let node = event.target.parentNode + while (rowSpan > 1) { + node = node?.nextSibling + if (!node || node.nodeName !== 'TR') break + toggle(node, 'hover-row hover-fixed-row') + rowSpan-- + } + } + const handleCellMouseEnter = ( event: MouseEvent, row: T, @@ -76,6 +91,9 @@ function useEvents(props: Partial>) { cell, namespace ) + if (cell.rowSpan > 1) { + toggleRowClassByCell(cell.rowSpan, event, addClass) + } const hoverState = (table.hoverState = { cell, column, row }) table?.emit( 'cell-mouse-enter', @@ -143,7 +161,9 @@ function useEvents(props: Partial>) { const handleCellMouseLeave = (event) => { const cell = getCell(event) if (!cell) return - + if (cell.rowSpan > 1) { + toggleRowClassByCell(cell.rowSpan, event, removeClass) + } const oldHoverState = parent?.hoverState parent?.emit( 'cell-mouse-leave', diff --git a/packages/components/table/src/table-body/index.ts b/packages/components/table/src/table-body/index.ts index 0df091f514..fa80c25102 100644 --- a/packages/components/table/src/table-body/index.ts +++ b/packages/components/table/src/table-body/index.ts @@ -28,18 +28,54 @@ export default defineComponent({ useRender(props) const { onColumnsChange, onScrollableChange } = useLayoutObserver(parent!) + const hoveredCellList = [] watch(props.store.states.hoverRow, (newVal: any, oldVal: any) => { + const el = instance?.vnode.el as HTMLElement + const rows = Array.from(el?.children || []).filter((e) => + e?.classList.contains(`${ns.e('row')}`) + ) + + // hover rowSpan > 1 choose the whole row + let rowNum = newVal + const childNodes = rows[rowNum]?.childNodes + if (childNodes?.length) { + const indexes = Array.from(childNodes).reduce((acc, item, index) => { + // drop colsSpan + const pre = childNodes[index - 1]?.colSpan > 1 + const next = childNodes[index + 1]?.colSpan > 1 + if (item.nodeName !== 'TD' && !pre && !next) { + acc.push(index) + } + return acc + }, []) + + indexes.forEach((rowIndex) => { + while (rowNum > 0) { + // find from previous + const preChildNodes = rows[rowNum - 1]?.childNodes + if ( + preChildNodes[rowIndex] && + preChildNodes[rowIndex].nodeName === 'TD' + ) { + addClass(preChildNodes[rowIndex], 'hover-cell') + hoveredCellList.push(preChildNodes[rowIndex]) + break + } + rowNum-- + } + }) + } else { + hoveredCellList.forEach((item) => removeClass(item, 'hover-cell')) + hoveredCellList.length = 0 + } if (!props.store.states.isComplex.value || !isClient) return rAF(() => { // just get first level children; fix #9723 - const el = instance?.vnode.el as HTMLElement - const rows = Array.from(el?.children || []).filter((e) => - e?.classList.contains(`${ns.e('row')}`) - ) const oldRow = rows[oldVal] const newRow = rows[newVal] - if (oldRow) { + // when there is fixed row, hover on rowSpan > 1 should not clear the class + if (oldRow && !oldRow.classList.contains('hover-fixed-row')) { removeClass(oldRow, 'hover-row') } if (newRow) { diff --git a/packages/theme-chalk/src/table.scss b/packages/theme-chalk/src/table.scss index a6f5a33bf9..6bc7e93cda 100755 --- a/packages/theme-chalk/src/table.scss +++ b/packages/theme-chalk/src/table.scss @@ -556,6 +556,10 @@ } } + tr > td.hover-cell { + background-color: getCssVar('table-row-hover-bg-color'); + } + tr.current-row > td.#{$namespace}-table__cell { background-color: getCssVar('table-current-row-bg-color'); }