mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [menu] resize event infinite invoke (#9342)
* fix(components): [menu] resize event infinite invoke * chore: update * Update menu.ts * Update menu.ts * chore: update * chore: update * chore: update test case * chore: optimize * Update menu.ts Co-authored-by: RealityBoy <1923740402@qq.com>
This commit is contained in:
@@ -191,7 +191,7 @@ describe('menu', () => {
|
||||
})
|
||||
|
||||
test('menu-overflow', async () => {
|
||||
// TODO: jsdom not support `offsetWidth`.
|
||||
// TODO: jsdom not support `offsetWidth` and `ResizeObserver`.
|
||||
})
|
||||
})
|
||||
|
||||
@@ -423,30 +423,6 @@ describe('other', () => {
|
||||
|
||||
expect(onOpen).toHaveBeenCalled()
|
||||
})
|
||||
test('ellipsis', async () => {
|
||||
const wrapper = _mount(
|
||||
`<div :style="{ width: width + 'px' }">
|
||||
<el-menu mode="horizontal" ellipsis>
|
||||
<el-menu-item index="1">Item1</el-menu-item>
|
||||
<el-menu-item index="2">Item2</el-menu-item>
|
||||
<el-menu-item index="3">Item3</el-menu-item>
|
||||
<el-menu-item index="4">Item4</el-menu-item>
|
||||
</el-menu>
|
||||
</div>`,
|
||||
{
|
||||
data() {
|
||||
return {
|
||||
width: 1000,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(wrapper.find('.el-sub-menu__icon-more').exists()).toBe(false)
|
||||
wrapper.vm.width = 200
|
||||
await nextTick()
|
||||
expect(wrapper.find('.el-sub-menu__icon-more').exists()).toBe(true)
|
||||
})
|
||||
test('menu group', async () => {
|
||||
const wrapper = _mount(
|
||||
`<el-menu mode="vertical" default-active="1">
|
||||
|
||||
@@ -105,6 +105,8 @@ export default defineComponent({
|
||||
const nsSubMenu = useNamespace('sub-menu')
|
||||
|
||||
// data
|
||||
const sliceIndex = ref(-1)
|
||||
|
||||
const openedMenus = ref<MenuProvider['openedMenus']>(
|
||||
props.defaultOpeneds && !props.collapse
|
||||
? props.defaultOpeneds.slice(0)
|
||||
@@ -214,8 +216,53 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const calcSliceIndex = () => {
|
||||
const items = Array.from(menu.value!.childNodes ?? []).filter(
|
||||
(item) => item.nodeName !== '#text' || item.nodeValue
|
||||
) as HTMLElement[]
|
||||
const moreItemWidth = 64
|
||||
const paddingLeft = Number.parseInt(
|
||||
getComputedStyle(menu.value!).paddingLeft,
|
||||
10
|
||||
)
|
||||
const paddingRight = Number.parseInt(
|
||||
getComputedStyle(menu.value!).paddingRight,
|
||||
10
|
||||
)
|
||||
const menuWidth = menu.value!.clientWidth - paddingLeft - paddingRight
|
||||
let calcWidth = 0
|
||||
let sliceIndex = 0
|
||||
items.forEach((item, index) => {
|
||||
calcWidth += item.offsetWidth || 0
|
||||
if (calcWidth <= menuWidth - moreItemWidth) {
|
||||
sliceIndex = index + 1
|
||||
}
|
||||
})
|
||||
return sliceIndex === items.length ? -1 : sliceIndex
|
||||
}
|
||||
|
||||
// Common computer monitor FPS is 60Hz, which means 60 redraws per second. Calculation formula: 1000ms/60 ≈ 16.67ms, In order to avoid a certain chance of repeated triggering when `resize`, set wait to 16.67 * 2 = 33.34
|
||||
const debounce = (fn: () => void, wait = 33.34) => {
|
||||
let timmer: ReturnType<typeof setTimeout> | null
|
||||
return () => {
|
||||
timmer && clearTimeout(timmer)
|
||||
timmer = setTimeout(() => {
|
||||
fn()
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstTimeRender = true
|
||||
const handleResize = () => {
|
||||
nextTick(() => instance.proxy!.$forceUpdate())
|
||||
const callback = () => {
|
||||
sliceIndex.value = -1
|
||||
nextTick(() => {
|
||||
sliceIndex.value = calcSliceIndex()
|
||||
})
|
||||
}
|
||||
// execute callback directly when first time resize to avoid shaking
|
||||
isFirstTimeRender ? callback() : debounce(callback)()
|
||||
isFirstTimeRender = false
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -326,30 +373,15 @@ export default defineComponent({
|
||||
const vShowMore: VNode[] = []
|
||||
|
||||
if (props.mode === 'horizontal' && menu.value) {
|
||||
const items = Array.from(menu.value?.childNodes ?? []).filter(
|
||||
(item) => item.nodeName !== '#text' || item.nodeValue
|
||||
) as HTMLElement[]
|
||||
const originalSlot = flattedChildren(slot)
|
||||
const moreItemWidth = 64
|
||||
const paddingLeft = Number.parseInt(
|
||||
getComputedStyle(menu.value).paddingLeft,
|
||||
10
|
||||
)
|
||||
const paddingRight = Number.parseInt(
|
||||
getComputedStyle(menu.value).paddingRight,
|
||||
10
|
||||
)
|
||||
const menuWidth = menu.value.clientWidth - paddingLeft - paddingRight
|
||||
let calcWidth = 0
|
||||
let sliceIndex = 0
|
||||
items.forEach((item, index) => {
|
||||
calcWidth += item.offsetWidth || 0
|
||||
if (calcWidth <= menuWidth - moreItemWidth) {
|
||||
sliceIndex = index + 1
|
||||
}
|
||||
})
|
||||
const slotDefault = originalSlot.slice(0, sliceIndex)
|
||||
const slotMore = originalSlot.slice(sliceIndex)
|
||||
const slotDefault =
|
||||
sliceIndex.value === -1
|
||||
? originalSlot
|
||||
: originalSlot.slice(0, sliceIndex.value)
|
||||
|
||||
const slotMore =
|
||||
sliceIndex.value === -1 ? [] : originalSlot.slice(sliceIndex.value)
|
||||
|
||||
if (slotMore?.length && props.ellipsis) {
|
||||
slot = slotDefault
|
||||
vShowMore.push(
|
||||
|
||||
Reference in New Issue
Block a user