feat(menu): add ellipsis in horizontal mode (#3083)

* feat(components): adjust types

* feat: popper add fallbackPlacements

* feat: update

* feat: update

* feat: update

* feat: update

* feat: u7pdate

* feat: update fallbackPlacements

* feat: update fallbackPlacements

* feat: when calculate, include padding

* feat: remove horizontal first level arrow

* feat: update

* feat: fix click item index mistake

* feat: fix item active

* feat: update

* feat: remove ele.me website
This commit is contained in:
kooriookami 2021-08-31 15:31:48 +08:00 committed by GitHub
parent e7f18fbb4f
commit b5c7914a05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 144 additions and 158 deletions

View File

@ -174,6 +174,10 @@ describe('menu', () => {
await nextTick()
expect(vm.$refs.menu.hoverBackground).toEqual('rgb(204, 0, 0)')
})
test('menu-overflow', async () => {
// TODO: jsdom not support `offsetWidth`.
})
})
describe('default active', () => {

View File

@ -29,5 +29,5 @@ export const ElMenuItem = MenuItem
export const ElMenuItemGroup = MenuItemGroup
export const ElSubMenu = SubMenu
export * from './src/menu'
export * from './src/menu.type'

View File

@ -1,32 +1,3 @@
<template>
<el-menu-collapse-transition v-if="collapseTransition">
<ul
:key="+collapse"
role="menubar"
:style="{ backgroundColor: backgroundColor || '' }"
:class="{
'el-menu': true,
'el-menu--horizontal': mode === 'horizontal',
'el-menu--collapse': collapse,
}"
>
<slot></slot>
</ul>
</el-menu-collapse-transition>
<ul
v-else
:key="+collapse"
role="menubar"
:style="{ backgroundColor: backgroundColor || '' }"
:class="{
'el-menu': true,
'el-menu--horizontal': mode === 'horizontal',
'el-menu--collapse': collapse,
}"
>
<slot></slot>
</ul>
</template>
<script lang="ts">
import {
defineComponent,
@ -39,23 +10,32 @@ import {
onMounted,
ComputedRef,
isRef,
h,
withDirectives,
nextTick,
} from 'vue'
import mitt from 'mitt'
import { Resize } from '@element-plus/directives'
import Menubar from '@element-plus/utils/menu/menu-bar'
import {
import ElMenuCollapseTransition from './menu-collapse-transition.vue'
import ElSubMenu from './submenu.vue'
import useMenuColor from './useMenuColor'
import type {
IMenuProps,
RootMenuProvider,
RegisterMenuItem,
SubMenuProvider,
} from './menu'
import ElMenuCollapseTransition from './menu-collapse-transition.vue'
import useMenuColor from './useMenuColor'
} from './menu.type'
export default defineComponent({
name: 'ElMenu',
componentName: 'ElMenu',
directives: {
Resize,
},
components: {
ElMenuCollapseTransition,
ElSubMenu,
},
props: {
mode: {
@ -83,7 +63,7 @@ export default defineComponent({
},
},
emits: ['close', 'open', 'select'],
setup(props: IMenuProps, ctx) {
setup(props: IMenuProps, { emit, slots }) {
// data
const openedMenus = ref(
props.defaultOpeneds && !props.collapse
@ -97,6 +77,8 @@ export default defineComponent({
const alteredCollapse = ref(false)
const rootMenuEmitter = mitt()
const router = instance.appContext.config.globalProperties.$router
const menu = ref(null)
const filteredSlot = ref(slots.default?.())
const hoverBackground = useMenuColor(props)
@ -178,10 +160,10 @@ export default defineComponent({
if (isOpened) {
closeMenu(index)
ctx.emit('close', index, indexPath.value)
emit('close', index, indexPath.value)
} else {
openMenu(index, indexPath)
ctx.emit('open', index, indexPath.value)
emit('open', index, indexPath.value)
}
}
@ -212,14 +194,14 @@ export default defineComponent({
}
return navigationResult
})
ctx.emit('select', ...emitParams.concat(routerResult))
emit('select', ...emitParams.concat(routerResult))
} else {
activeIndex.value = item.index
ctx.emit('select', ...emitParams)
emit('select', ...emitParams)
}
}
const updateActiveIndex = (val?: string) => {
const updateActiveIndex = (val: string) => {
const itemsInData = items.value
const item =
itemsInData[val] ||
@ -241,7 +223,51 @@ export default defineComponent({
}
}
const updateFilteredSlot = async () => {
filteredSlot.value = slots.default?.()
await nextTick()
if (props.mode === 'horizontal') {
const items = Array.from(menu.value.childNodes).filter((item: HTMLElement) => item.nodeName !== '#text' || item.nodeValue) as [HTMLElement]
if (items.length === slots.default?.().length) {
const moreItemWidth = 64
const paddingLeft = parseInt(getComputedStyle(menu.value).paddingLeft)
const paddingRight = parseInt(getComputedStyle(menu.value).paddingRight)
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 defaultSlot = slots.default?.().slice(0, sliceIndex)
const moreSlot = slots.default?.().slice(sliceIndex)
if (moreSlot?.length) {
filteredSlot.value = [
...defaultSlot,
h(ElSubMenu, {
index: 'sub-menu-more',
}, {
title: () => h('i', { class: ['el-icon-more', 'el-sub-menu__icon-more'] }),
default: () => moreSlot,
}),
]
}
}
}
}
const handleResize = () => {
if (props.mode === 'horizontal') {
updateFilteredSlot()
}
}
// watch
watch(() => slots.default?.(), () => {
updateFilteredSlot()
})
watch(
() => props.defaultActive,
@ -254,7 +280,7 @@ export default defineComponent({
)
watch(items.value, () => {
updateActiveIndex()
initializeMenu()
})
watch(
@ -311,12 +337,33 @@ export default defineComponent({
return {
hoverBackground,
isMenuPopup,
menu,
filteredSlot,
props,
open,
close,
handleResize,
}
},
render() {
const menu = withDirectives(h('ul', {
key: String(this.collapse),
role: 'menubar',
ref: 'menu',
style: { backgroundColor: this.backgroundColor || '' },
class: {
'el-menu': true,
'el-menu--horizontal': this.mode === 'horizontal',
'el-menu--collapse': this.collapse,
},
}, [this.filteredSlot]), [[Resize, this.handleResize]])
if (this.collapseTransition) {
return h(ElMenuCollapseTransition, () => menu)
}
return menu
},
})
</script>

View File

@ -49,9 +49,10 @@ import {
} from 'vue'
import ElTooltip from '@element-plus/components/tooltip'
import { Effect } from '@element-plus/components/popper'
import { RootMenuProvider, SubMenuProvider } from './menu'
import useMenu from './useMenu'
import type { RootMenuProvider, SubMenuProvider } from './menu.type'
export default defineComponent({
name: 'ElMenuItem',
@ -73,7 +74,7 @@ export default defineComponent({
const rootMenu = inject<RootMenuProvider>('rootMenu')
const { parentMenu, paddingStyle, indexPath } = useMenu(
instance,
props.index,
computed(() => props.index),
)
const { addSubMenu, removeSubMenu } = inject<SubMenuProvider>(
`subMenu:${parentMenu.value.uid}`,

View File

@ -22,7 +22,7 @@ import {
reactive,
} from 'vue'
import type { IMenuGroupProps, RootMenuProvider } from './menu'
import type { IMenuGroupProps, RootMenuProvider } from './menu.type'
export default defineComponent({
name: 'ElMenuItemGroup',

View File

@ -1,91 +1,3 @@
<!-- <template>
<li
:class="[
'el-sub-menu',
active && 'is-active',
opened && 'is-opened',
disabled && 'is-disabled',
]"
role="menuitem"
aria-haspopup="true"
aria-expanded="opened"
@mouseenter="handleMouseenter"
@mouseleave="() => handleMouseleave(false)"
@focus="handleMouseenter"
>
<el-popper
v-if="isMenuPopup"
ref="popperVnode"
v-model:visible="opened"
:manual-mode="true"
effect="light"
:pure="true"
:offset="6"
:show-arrow="false"
:popper-class="props.popperClass"
:placement="data.currentPlacement"
:append-to-body="appendToBody"
transition: this.menuTransitionName,
gpuAcceleration: false,
>
<template #default>
<div
ref="menu"
:class="[`el-menu--${mode}`, props.popperClass]"
@mouseenter="$event => handleMouseenter($event, 100)"
@mouseleave="() => handleMouseleave(true)"
@focus="$event => handleMouseenter($event, 100)"
>
<ul
role="menu"
:class="[
'el-menu el-menu--popup',
`el-menu--popup-${data.currentPlacement}`,
]"
:style="{ backgroundColor: rootProps.backgroundColor || '' }"
>
<slot name="default"></slot>
</ul>
</div>
</template>
<template #trigger>
<div
class="el-sub-menu__title"
:style="[paddingStyle, titleStyle, { backgroundColor }]"
@click="handleClick"
@mouseenter="handleTitleMouseenter"
@mouseleave="handleTitleMouseleave"
>
<slot name="title"></slot>
<i :class="['el-sub-menu__icon-arrow', submenuTitleIcon]"></i>
</div>
</template>
</el-popper>
<div
v-if="!isMenuPopup"
ref="verticalTitleRef"
class="el-sub-menu__title"
:style="[paddingStyle, titleStyle, { backgroundColor }]"
@click="handleClick"
@mouseenter="handleTitleMouseenter"
@mouseleave="handleTitleMouseleave"
>
<slot name="title"></slot>
<i :class="['el-sub-menu__icon-arrow', submenuTitleIcon]"></i>
</div>
<el-collapse-transition v-if="!isMenuPopup">
<ul
v-show="opened"
role="menu"
class="el-menu el-menu--inline"
:style="{ backgroundColor: rootProps.backgroundColor || '' }"
>
<slot></slot>
</ul>
</el-collapse-transition>
</li>
</template> -->
<script lang="ts">
import mitt from 'mitt'
import {
@ -106,12 +18,12 @@ import {
} from 'vue'
import ElCollapseTransition from '@element-plus/components/collapse-transition'
import ElPopper from '@element-plus/components/popper'
import { ISubMenuProps, RootMenuProvider, SubMenuProvider } from './menu'
import useMenu from './useMenu'
import type { ISubMenuProps, RootMenuProvider, SubMenuProvider } from './menu.type'
export default defineComponent({
name: 'ElSubMenu',
componentName: 'ElSubMenu',
props: {
index: {
type: String,
@ -149,7 +61,7 @@ export default defineComponent({
const instance = getCurrentInstance()
const { paddingStyle, indexPath, parentMenu } = useMenu(
instance,
props.index,
computed(() => props.index),
)
// inject
@ -173,10 +85,11 @@ export default defineComponent({
// computed
const submenuTitleIcon = computed(() => {
return (mode.value === 'horizontal' && isFirstLevel.value) ||
(mode.value === 'vertical' && !rootProps.collapse)
(mode.value === 'vertical' && !rootProps.collapse)
? 'el-icon-arrow-down'
: 'el-icon-arrow-right'
})
const showSubmenuTitleIcon = computed(() => mode.value === 'vertical' || !isFirstLevel.value)
const isFirstLevel = computed(() => {
let isFirstLevel = true
let parent = instance.parent
@ -198,6 +111,11 @@ export default defineComponent({
const menuTransitionName = computed(() => {
return rootProps.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top'
})
const fallbackPlacements = computed(() =>
mode.value === 'horizontal' && isFirstLevel.value
? ['bottom-start', 'bottom-end', 'top-start', 'top-end', 'right-start', 'left-start']
: ['right-start', 'left-start', 'bottom-start', 'bottom-end', 'top-start', 'top-end'],
)
const opened = computed(() => {
return openedMenus.value.includes(props.index)
})
@ -413,7 +331,9 @@ export default defineComponent({
backgroundColor,
rootProps,
menuTransitionName,
fallbackPlacements,
submenuTitleIcon,
showSubmenuTitleIcon,
appendToBody,
handleClick,
@ -432,12 +352,14 @@ export default defineComponent({
}
},
render() {
const titleTag = [
this.$slots.title?.(),
h('i', {
class: ['el-sub-menu__icon-arrow', this.submenuTitleIcon],
}, null)]
const titleTag = [this.$slots.title?.()]
if (this.showSubmenuTitleIcon) {
titleTag.push(
h('i', {
class: ['el-sub-menu__icon-arrow', this.submenuTitleIcon],
}, null),
)
}
const ulStyle = {
backgroundColor: this.rootProps.backgroundColor || '',
}
@ -457,6 +379,7 @@ export default defineComponent({
popperClass: this.popperClass,
placement: this.data.currentPlacement,
appendToBody: this.appendToBody,
fallbackPlacements: this.fallbackPlacements,
transition: this.menuTransitionName,
gpuAcceleration: false,
}, {

View File

@ -1,17 +1,17 @@
import { computed, inject } from 'vue'
import { computed, ComputedRef, inject } from 'vue'
import type { ComponentInternalInstance } from 'vue'
import type { RootMenuProvider } from './menu'
import type { RootMenuProvider } from './menu.type'
export default function useMenu(
instance: ComponentInternalInstance,
currentIndex: string,
currentIndex: ComputedRef<string>,
) {
const rootMenu = inject<RootMenuProvider>('rootMenu')
const indexPath = computed(() => {
let parent = instance.parent
const path = [currentIndex]
const path = [currentIndex.value]
while (parent.type.name !== 'ElMenu') {
if (parent.props.index) {
path.unshift(parent.props.index as string)

View File

@ -1,6 +1,6 @@
import { computed } from 'vue'
import type { IMenuProps } from './menu'
import type { IMenuProps } from './menu.type'
export default function useMenuColor(props: IMenuProps) {
const menuBarColor = computed(() => {

View File

@ -55,6 +55,8 @@
box-sizing: border-box;
@include utils-clearfix;
&.#{$namespace}-menu--horizontal {
display: flex;
flex-wrap: nowrap;
border-bottom: solid 1px var(--el-menu-border-color);
}
@ -109,6 +111,7 @@
background-color: #fff;
}
}
& .#{$namespace}-sub-menu__icon-arrow {
position: static;
vertical-align: middle;
@ -126,6 +129,7 @@
padding: 0 10px;
color: var(--el-text-color-secondary);
}
& .#{$namespace}-menu-item.is-active,
& .#{$namespace}-sub-menu.is-active > .#{$namespace}-sub-menu__title {
color: var(--el-text-color-primary);
@ -152,9 +156,11 @@
width: 24px;
text-align: center;
}
.#{$namespace}-sub-menu__icon-arrow {
display: none;
}
span {
height: 0;
width: 0;
@ -174,6 +180,7 @@
.#{$namespace}-sub-menu {
position: relative;
& .#{$namespace}-menu {
position: absolute;
margin-left: 5px;
@ -205,6 +212,7 @@
box-shadow: var(--el-box-shadow-light);
}
}
@include b(menu-item) {
@include menu-item;
@ -244,6 +252,9 @@
padding: 0 45px;
min-width: 200px;
}
@include e(icon-more) {
margin-right: 0 !important;
}
@include e(icon-arrow) {
position: absolute;
top: 50%;

View File

@ -23,7 +23,7 @@ Top bar NavMenu can be used in a variety of scenarios.
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<div class="line"></div>
<el-menu
@ -48,7 +48,7 @@ Top bar NavMenu can be used in a variety of scenarios.
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<script>

View File

@ -24,7 +24,7 @@ Top bar NavMenu puede ser usado en distinto escenarios.
</el-submenu>
</el-submenu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<div class="line"></div>
<el-menu
@ -49,7 +49,7 @@ Top bar NavMenu puede ser usado en distinto escenarios.
</el-submenu>
</el-submenu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<script>

View File

@ -23,7 +23,7 @@ La barre du haut peut être utilisée pour différents scénarios.
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Infos</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Commandes</a></el-menu-item>
<el-menu-item index="4">Commandes</el-menu-item>
</el-menu>
<div class="line"></div>
<el-menu
@ -48,7 +48,7 @@ La barre du haut peut être utilisée pour différents scénarios.
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Commandes</a></el-menu-item>
<el-menu-item index="4">Commandes</el-menu-item>
</el-menu>
<script>

View File

@ -23,7 +23,7 @@
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<div class="line"></div>
<el-menu
@ -48,7 +48,7 @@
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>Info</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">Orders</a></el-menu-item>
<el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<script>

View File

@ -24,7 +24,7 @@
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>消息中心</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
<el-menu-item index="4">订单管理</el-menu-item>
</el-menu>
<div class="line"></div>
<el-menu
@ -49,7 +49,7 @@
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3" disabled>消息中心</el-menu-item>
<el-menu-item index="4"><a href="https://www.ele.me" target="_blank">订单管理</a></el-menu-item>
<el-menu-item index="4">订单管理</el-menu-item>
</el-menu>
<script>