Commit f0f75fb5 authored by baiyaaaaa's avatar baiyaaaaa Committed by 杨奕

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

* change menu popup to body

* add menu-list

* Revert "add menu-list"

This reverts commit 5799df9bf202eb17f7b784be7eb79404fce68e8f.

* fix menu popup

* Update yarn.lock

* Update submenu.vue
parent 68e0573d
export default { export default {
inject: ['rootMenu'],
computed: { computed: {
indexPath() { indexPath() {
const path = [this.index]; const path = [this.index];
...@@ -11,16 +12,6 @@ export default { ...@@ -11,16 +12,6 @@ export default {
} }
return path; return path;
}, },
rootMenu() {
let parent = this.$parent;
while (
parent &&
parent.$options.componentName !== 'ElMenu'
) {
parent = parent.$parent;
}
return parent;
},
parentMenu() { parentMenu() {
let parent = this.$parent; let parent = this.$parent;
while ( while (
......
...@@ -127,6 +127,9 @@ ...@@ -127,6 +127,9 @@
computed: { computed: {
hoverBackground() { hoverBackground() {
return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : ''; return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
},
isMenuPopup() {
return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
} }
}, },
watch: { watch: {
...@@ -140,6 +143,7 @@ ...@@ -140,6 +143,7 @@
collapse(value) { collapse(value) {
if (value) this.openedMenus = []; if (value) this.openedMenus = [];
this.broadcast('ElSubmenu', 'toggle-collapse', value);
} }
}, },
methods: { methods: {
......
<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> <script>
import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition'; import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
import menuMixin from './menu-mixin'; import menuMixin from './menu-mixin';
import Emitter from 'element-ui/src/mixins/emitter'; 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 { export default {
name: 'ElSubmenu', name: 'ElSubmenu',
componentName: 'ElSubmenu', componentName: 'ElSubmenu',
mixins: [menuMixin, Emitter], mixins: [menuMixin, Emitter, poperMixins],
components: { ElCollapseTransition }, components: { ElCollapseTransition },
...@@ -68,12 +46,26 @@ ...@@ -68,12 +46,26 @@
data() { data() {
return { return {
popperJS: null,
timeout: null, timeout: null,
items: {}, items: {},
submenus: {} submenus: {}
}; };
}, },
watch: {
opened(val) {
if (this.isMenuPopup) {
this.$nextTick(_ => {
this.updatePopper();
});
}
}
},
computed: { computed: {
// popper option
appendToBody() {
return this.rootMenu === this.$parent;
},
menuTransitionName() { menuTransitionName() {
return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top'; return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
}, },
...@@ -114,6 +106,9 @@ ...@@ -114,6 +106,9 @@
mode() { mode() {
return this.rootMenu.mode; return this.rootMenu.mode;
}, },
isMenuPopup() {
return this.rootMenu.isMenuPopup;
},
titleStyle() { titleStyle() {
if (this.mode !== 'horizontal') { if (this.mode !== 'horizontal') {
return { return {
...@@ -131,6 +126,13 @@ ...@@ -131,6 +126,13 @@
} }
}, },
methods: { methods: {
handleCollapseToggle(value) {
if (value) {
this.initPopper();
} else {
this.doDestroy();
}
},
addItem(item) { addItem(item) {
this.$set(this.items, item.index, item); this.$set(this.items, item.index, item);
}, },
...@@ -188,15 +190,106 @@ ...@@ -188,15 +190,106 @@
if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return; if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
const title = this.$refs['submenu-title']; const title = this.$refs['submenu-title'];
title && (title.style.backgroundColor = this.rootMenu.backgroundColor || ''); 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() { created() {
this.parentMenu.addSubmenu(this); this.parentMenu.addSubmenu(this);
this.rootMenu.addSubmenu(this); this.rootMenu.addSubmenu(this);
this.$on('toggle-collapse', this.handleCollapseToggle);
},
mounted() {
this.initPopper();
}, },
beforeDestroy() { beforeDestroy() {
this.parentMenu.removeSubmenu(this); this.parentMenu.removeSubmenu(this);
this.rootMenu.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> </script>
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
font-size: 14px; font-size: 14px;
color: $--menu-item-color; color: $--menu-item-color;
padding: 0 20px; padding: 0 20px;
list-style: none;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
transition: border-color .3s, background-color .3s, color .3s; transition: border-color .3s, background-color .3s, color .3s;
...@@ -24,21 +25,14 @@ ...@@ -24,21 +25,14 @@
background-color: $--menu-item-fill; background-color: $--menu-item-fill;
@include utils-clearfix; @include utils-clearfix;
& li {
list-style: none;
}
@include m(horizontal) { @include m(horizontal) {
border-right: none; border-right: none;
border-bottom: solid 1px #e6e6e6; border-bottom: solid 1px #e6e6e6;
& .el-menu-item { & > .el-menu-item {
float: left; float: left;
height: 60px; height: 60px;
line-height: 60px; line-height: 60px;
margin: 0; margin: 0;
cursor: pointer;
position: relative;
box-sizing: border-box;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
color: $--color-text-secondary; color: $--color-text-secondary;
...@@ -51,26 +45,22 @@ ...@@ -51,26 +45,22 @@
background-color: #fff; background-color: #fff;
} }
} }
& .el-submenu { & > .el-submenu {
float: left; float: left;
position: relative;
&:focus { &:focus,
&:hover {
outline: none; outline: none;
> .el-submenu__title { .el-submenu__title {
color: $--color-text-primary; color: $--color-text-primary;
} }
} }
> .el-menu {
position: absolute; &.is-active {
top: 65px; .el-submenu__title {
left: 0; border-bottom: 2px solid $--color-primary;
border: none; color: $--color-text-primary;
padding: 5px 0; }
background-color: $--color-white;
z-index: 100;
min-width: 100%;
box-shadow: $--box-shadow-light;
border-radius: $--border-radius-small;
} }
& .el-submenu__title { & .el-submenu__title {
...@@ -78,35 +68,38 @@ ...@@ -78,35 +68,38 @@
line-height: 60px; line-height: 60px;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
color: $--color-text-secondary; color: $--color-text-secondary;
}
.el-submenu__title:hover { &:hover {
background-color: #fff; background-color: #fff;
} }
}
& .el-submenu__icon-arrow {
position: static;
vertical-align: middle;
margin-left: 8px;
margin-top: -3px;
}
}
& .el-menu {
& .el-menu-item { & .el-menu-item {
background-color: $--color-white; background-color: $--color-white;
float: none; float: none;
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
padding: 0 10px; padding: 0 10px;
} color: $--color-text-secondary;
& .el-submenu__icon-arrow { &.is-active {
position: static; color: $--color-text-primary;
vertical-align: middle; }
margin-left: 8px;
margin-top: -3px;
} }
} }
& .el-menu-item:hover, & .el-menu-item:hover,
& .el-submenu__title:hover,
& .el-menu-item:focus { & .el-menu-item:focus {
outline: none; outline: none;
color: $--color-text-primary; color: $--color-text-primary;
} }
& > .el-menu-item.is-active, & > .el-menu-item.is-active {
& > .el-submenu.is-active .el-submenu__title {
border-bottom: 2px solid $--color-primary; border-bottom: 2px solid $--color-primary;
color: $--color-text-primary; color: $--color-text-primary;
} }
...@@ -162,6 +155,21 @@ ...@@ -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 b(menu-item) {
@include menu-item; @include menu-item;
...@@ -198,6 +206,10 @@ ...@@ -198,6 +206,10 @@
} }
@include b(submenu) { @include b(submenu) {
list-style: none;
margin: 0;
padding-left: 0;
@include e(title) { @include e(title) {
position: relative; position: relative;
@include menu-item; @include menu-item;
......
...@@ -16,6 +16,10 @@ const stop = e => e.stopPropagation(); ...@@ -16,6 +16,10 @@ const stop = e => e.stopPropagation();
*/ */
export default { export default {
props: { props: {
transformOrigin: {
type: [Boolean, String],
default: true
},
placement: { placement: {
type: String, type: String,
default: 'bottom' default: 'bottom'
...@@ -139,6 +143,7 @@ export default { ...@@ -139,6 +143,7 @@ export default {
}, },
resetTransformOrigin() { resetTransformOrigin() {
if (!this.transformOrigin) return;
let placementMap = { let placementMap = {
top: 'bottom', top: 'bottom',
bottom: 'top', bottom: 'top',
...@@ -147,7 +152,9 @@ export default { ...@@ -147,7 +152,9 @@ export default {
}; };
let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0]; let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0];
let origin = placementMap[placement]; 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) { appendArrow(element) {
......
...@@ -270,7 +270,7 @@ describe('Menu', () => { ...@@ -270,7 +270,7 @@ describe('Menu', () => {
var submenu = vm.$refs.submenu; var submenu = vm.$refs.submenu;
triggerEvent(submenu.$el, 'mouseenter'); triggerEvent(submenu.$el, 'mouseenter');
setTimeout(_ => { 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(); done();
}, 500); }, 500);
}); });
...@@ -301,10 +301,10 @@ describe('Menu', () => { ...@@ -301,10 +301,10 @@ describe('Menu', () => {
triggerElm.click(); triggerElm.click();
setTimeout(_ => { 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(); triggerElm.click();
setTimeout(_ => { 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(); done();
}, 1000); }, 1000);
}, 500); }, 500);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment