mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
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:
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user