fix(components): [table] row has rowspan when hover it only has background on the first row of this rowspan (#15529)

* fix(components): table

table row has rowspan when hover it only has background on the first row of this colspan

* fix(components): table

fixed when hover on a rowspan > 1 cell give the whole rows background

* fix(components): [table]

fixed hover on merged cell give the background of the whole rows

* Update events-helper.ts

import addClass removeClass

* Update events-helper.ts

fixed rowspan > 2 and data not enough

* fix(components): [table]

add test case for hover on rowspan > 2

* fix(components): [table]

use getTestData is better

* Update table.test.ts

use getTestData instead

* Update table.test.ts

change templete label prop

* fix(components): [table]

drop comments and clear hoveredCell array after use

* fix(components): [table]

when there is fixed row, hover on rowSpan > 1 should not clear the class

---------

Co-authored-by: dwaynewdong <dwaynewdong@tencent.com>
This commit is contained in:
Wayne
2024-02-04 21:41:05 +08:00
committed by GitHub
parent b6987e9705
commit d2fbff70eb
4 changed files with 192 additions and 7 deletions

View File

@@ -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: `
<el-table
:data="testData"
:span-method="objectSpanMethod"
border
style="width: 100%; margin-top: 20px"
>
<el-table-column prop="id" label="ID" width="180" />
<el-table-column prop="name" label="片名" />
<el-table-column prop="release" label="发行日期" />
<el-table-column prop="director" label="导演" />
<el-table-column prop="runtime" label="时长(分)" />
</el-table>
`,
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: `
<el-table
:data="testData"
:span-method="objectSpanMethod"
border
style="width: 100%; margin-top: 20px"
>
<el-table-column prop="id" label="ID" width="180" />
<el-table-column prop="name" label="片名" />
<el-table-column prop="release" label="发行日期" />
<el-table-column prop="director" label="导演" />
<el-table-column prop="runtime" label="时长(分)" />
</el-table>
`,
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: {

View File

@@ -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<T>(props: Partial<TableBodyProps<T>>) {
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<T>(props: Partial<TableBodyProps<T>>) {
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<T>(props: Partial<TableBodyProps<T>>) {
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',

View File

@@ -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) {

View File

@@ -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');
}