diff --git a/packages/components/menu/__tests__/menu.test.ts b/packages/components/menu/__tests__/menu.test.ts index 15a42dc5d7..f45aaafd66 100644 --- a/packages/components/menu/__tests__/menu.test.ts +++ b/packages/components/menu/__tests__/menu.test.ts @@ -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( - `
- - Item1 - Item2 - Item3 - Item4 - -
`, - { - 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( ` diff --git a/packages/components/menu/src/menu.ts b/packages/components/menu/src/menu.ts index 1b472d38ae..604a1e99ef 100644 --- a/packages/components/menu/src/menu.ts +++ b/packages/components/menu/src/menu.ts @@ -105,6 +105,8 @@ export default defineComponent({ const nsSubMenu = useNamespace('sub-menu') // data + const sliceIndex = ref(-1) + const openedMenus = ref( 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 | 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(