Menu: move menu popup to body when collapse (#9263)

* change menu popup to body

* add menu-list

* Revert "add menu-list"

This reverts commit 5799df9bf2.

* fix menu popup

* Update yarn.lock

* Update submenu.vue
pull/9437/head
baiyaaaaa 2018-01-23 15:58:46 +08:00 committed by 杨奕
parent 68e0573db7
commit f0f75fb561
6 changed files with 199 additions and 92 deletions

View File

@ -1,4 +1,5 @@
export default {
inject: ['rootMenu'],
computed: {
indexPath() {
const path = [this.index];
@ -11,16 +12,6 @@ export default {
}
return path;
},
rootMenu() {
let parent = this.$parent;
while (
parent &&
parent.$options.componentName !== 'ElMenu'
) {
parent = parent.$parent;
}
return parent;
},
parentMenu() {
let parent = this.$parent;
while (

View File

@ -127,6 +127,9 @@
computed: {
hoverBackground() {
return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
},
isMenuPopup() {
return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
}
},
watch: {
@ -140,6 +143,7 @@
collapse(value) {
if (value) this.openedMenus = [];
this.broadcast('ElSubmenu', 'toggle-collapse', value);
}
},
methods: {

View File

@ -1,53 +1,31 @@
<template>
<li
:class="{
'el-submenu': true,
'is-active': active,
'is-opened': opened
}"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
@focus="handleMouseenter"
role="menuitem"
aria-haspopup="true"
:aria-expanded="opened"
>
<div
class="el-submenu__title"
ref="submenu-title"
@click="handleClick"
@mouseenter="handleTitleMouseenter"
@mouseleave="handleTitleMouseleave"
:style="[paddingStyle, titleStyle, { backgroundColor }]">
<slot name="title"></slot>
<i :class="{
'el-submenu__icon-arrow': true,
'el-icon-arrow-down': rootMenu.mode === 'horizontal' || rootMenu.mode === 'vertical' && !rootMenu.collapse,
'el-icon-arrow-right': rootMenu.mode === 'vertical' && rootMenu.collapse
}">
</i>
</div>
<template v-if="rootMenu.mode === 'horizontal' || (rootMenu.mode === 'vertical' && rootMenu.collapse)">
<transition :name="menuTransitionName">
<ul class="el-menu" v-show="opened" :style="{ backgroundColor: rootMenu.backgroundColor || '' }" role="menu"><slot></slot></ul>
</transition>
</template>
<el-collapse-transition v-else>
<ul class="el-menu" v-show="opened" :style="{ backgroundColor: rootMenu.backgroundColor || '' }" role="menu"><slot></slot></ul>
</el-collapse-transition>
</li>
</template>
<script>
import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
import menuMixin from './menu-mixin';
import Emitter from 'element-ui/src/mixins/emitter';
import Popper from 'element-ui/src/utils/vue-popper';
const poperMixins = {
props: {
transformOrigin: {
type: [Boolean, String],
default: false
},
offset: Popper.props.offset,
boundariesPadding: Popper.props.boundariesPadding,
popperOptions: Popper.props.popperOptions
},
data: Popper.data,
methods: Popper.methods,
beforeDestroy: Popper.beforeDestroy,
deactivated: Popper.deactivated
};
export default {
name: 'ElSubmenu',
componentName: 'ElSubmenu',
mixins: [menuMixin, Emitter],
mixins: [menuMixin, Emitter, poperMixins],
components: { ElCollapseTransition },
@ -68,12 +46,26 @@
data() {
return {
popperJS: null,
timeout: null,
items: {},
submenus: {}
};
},
watch: {
opened(val) {
if (this.isMenuPopup) {
this.$nextTick(_ => {
this.updatePopper();
});
}
}
},
computed: {
// popper option
appendToBody() {
return this.rootMenu === this.$parent;
},
menuTransitionName() {
return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
},
@ -114,6 +106,9 @@
mode() {
return this.rootMenu.mode;
},
isMenuPopup() {
return this.rootMenu.isMenuPopup;
},
titleStyle() {
if (this.mode !== 'horizontal') {
return {
@ -131,6 +126,13 @@
}
},
methods: {
handleCollapseToggle(value) {
if (value) {
this.initPopper();
} else {
this.doDestroy();
}
},
addItem(item) {
this.$set(this.items, item.index, item);
},
@ -188,15 +190,106 @@
if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
const title = this.$refs['submenu-title'];
title && (title.style.backgroundColor = this.rootMenu.backgroundColor || '');
},
updatePlacement() {
this.currentPlacement = this.mode === 'horizontal' ? 'bottom-start' : 'right-start';
},
initPopper() {
this.referenceElm = this.$el;
this.popperElm = this.$refs.menu;
this.updatePlacement();
}
},
created() {
this.parentMenu.addSubmenu(this);
this.rootMenu.addSubmenu(this);
this.$on('toggle-collapse', this.handleCollapseToggle);
},
mounted() {
this.initPopper();
},
beforeDestroy() {
this.parentMenu.removeSubmenu(this);
this.rootMenu.removeSubmenu(this);
},
render(h) {
const {
active,
opened,
paddingStyle,
titleStyle,
backgroundColor,
$slots,
rootMenu,
currentPlacement,
menuTransitionName,
mode
} = this;
const popupMenu = (
<transition name={menuTransitionName}>
<div
ref="menu"
v-show={opened}
class={[`el-menu--${mode}`]}
on-mouseenter={this.handleMouseenter}
on-mouseleave={this.handleMouseleave}
on-focus={this.handleMouseenter}>
<ul
role="menu"
class={['el-menu el-menu--popup', `el-menu--popup-${currentPlacement}`]}
style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
{$slots.default}
</ul>
</div>
</transition>
);
const inlineMenu = (
<el-collapse-transition>
<ul
role="menu"
class="el-menu el-menu--inline"
v-show={opened}
style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
{$slots.default}
</ul>
</el-collapse-transition>
);
return (
<li
class={{
'el-submenu': true,
'is-active': active,
'is-opened': opened
}}
role="menuitem"
aria-haspopup="true"
aria-expanded={opened}
on-mouseenter={this.handleMouseenter}
on-mouseleave={this.handleMouseleave}
on-focus={this.handleMouseenter}
>
<div
class="el-submenu__title"
ref="submenu-title"
on-click={this.handleClick}
on-mouseenter={this.handleTitleMouseenter}
on-mouseleave={this.handleTitleMouseleave}
style={[paddingStyle, titleStyle, { backgroundColor }]}
>
{$slots.title}
<i class={{
'el-submenu__icon-arrow': true,
'el-icon-arrow-down': rootMenu.mode === 'horizontal' || rootMenu.mode === 'vertical' && !rootMenu.collapse,
'el-icon-arrow-right': rootMenu.mode === 'vertical' && rootMenu.collapse
}}>
</i>
</div>
{this.isMenuPopup ? popupMenu : inlineMenu}
</li>
);
}
};
</script>

View File

@ -8,6 +8,7 @@
font-size: 14px;
color: $--menu-item-color;
padding: 0 20px;
list-style: none;
cursor: pointer;
position: relative;
transition: border-color .3s, background-color .3s, color .3s;
@ -24,21 +25,14 @@
background-color: $--menu-item-fill;
@include utils-clearfix;
& li {
list-style: none;
}
@include m(horizontal) {
border-right: none;
border-bottom: solid 1px #e6e6e6;
& .el-menu-item {
& > .el-menu-item {
float: left;
height: 60px;
line-height: 60px;
margin: 0;
cursor: pointer;
position: relative;
box-sizing: border-box;
border-bottom: 2px solid transparent;
color: $--color-text-secondary;
@ -51,26 +45,22 @@
background-color: #fff;
}
}
& .el-submenu {
& > .el-submenu {
float: left;
position: relative;
&:focus {
&:focus,
&:hover {
outline: none;
> .el-submenu__title {
.el-submenu__title {
color: $--color-text-primary;
}
}
> .el-menu {
position: absolute;
top: 65px;
left: 0;
border: none;
padding: 5px 0;
background-color: $--color-white;
z-index: 100;
min-width: 100%;
box-shadow: $--box-shadow-light;
border-radius: $--border-radius-small;
&.is-active {
.el-submenu__title {
border-bottom: 2px solid $--color-primary;
color: $--color-text-primary;
}
}
& .el-submenu__title {
@ -78,20 +68,11 @@
line-height: 60px;
border-bottom: 2px solid transparent;
color: $--color-text-secondary;
}
.el-submenu__title:hover {
background-color: #fff;
&:hover {
background-color: #fff;
}
}
& .el-menu-item {
background-color: $--color-white;
float: none;
height: 36px;
line-height: 36px;
padding: 0 10px;
}
& .el-submenu__icon-arrow {
position: static;
vertical-align: middle;
@ -99,14 +80,26 @@
margin-top: -3px;
}
}
& .el-menu {
& .el-menu-item {
background-color: $--color-white;
float: none;
height: 36px;
line-height: 36px;
padding: 0 10px;
color: $--color-text-secondary;
&.is-active {
color: $--color-text-primary;
}
}
}
& .el-menu-item:hover,
& .el-submenu__title:hover,
& .el-menu-item:focus {
outline: none;
color: $--color-text-primary;
}
& > .el-menu-item.is-active,
& > .el-submenu.is-active .el-submenu__title {
& > .el-menu-item.is-active {
border-bottom: 2px solid $--color-primary;
color: $--color-text-primary;
}
@ -162,6 +155,21 @@
}
}
}
@include m(popup) {
z-index: 100;
min-width: 200px;
border: none;
padding: 5px 0;
border-radius: $--border-radius-small;
box-shadow: $--box-shadow-light;
&-bottom-start {
margin-top: 5px;
}
&-right-start {
margin-left: 5px;
}
}
}
@include b(menu-item) {
@include menu-item;
@ -198,6 +206,10 @@
}
@include b(submenu) {
list-style: none;
margin: 0;
padding-left: 0;
@include e(title) {
position: relative;
@include menu-item;

View File

@ -16,6 +16,10 @@ const stop = e => e.stopPropagation();
*/
export default {
props: {
transformOrigin: {
type: [Boolean, String],
default: true
},
placement: {
type: String,
default: 'bottom'
@ -139,6 +143,7 @@ export default {
},
resetTransformOrigin() {
if (!this.transformOrigin) return;
let placementMap = {
top: 'bottom',
bottom: 'top',
@ -147,7 +152,9 @@ export default {
};
let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0];
let origin = placementMap[placement];
this.popperJS._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
this.popperJS._popper.style.transformOrigin = typeof this.transformOrigin === 'string'
? this.transformOrigin
: ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
},
appendArrow(element) {

View File

@ -270,7 +270,7 @@ describe('Menu', () => {
var submenu = vm.$refs.submenu;
triggerEvent(submenu.$el, 'mouseenter');
setTimeout(_ => {
expect(submenu.$el.querySelector('.el-menu').style.display).to.not.ok;
expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.not.ok;
done();
}, 500);
});
@ -301,10 +301,10 @@ describe('Menu', () => {
triggerElm.click();
setTimeout(_ => {
expect(submenu.$el.querySelector('.el-menu').style.display).to.not.ok;
expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.not.ok;
triggerElm.click();
setTimeout(_ => {
expect(submenu.$el.querySelector('.el-menu').style.display).to.be.equal('none');
expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.be.equal('none');
done();
}, 1000);
}, 500);