first
This commit is contained in:
8
packages/menu/index.js
Normal file
8
packages/menu/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import ElMenu from './src/menu';
|
||||
|
||||
/* istanbul ignore next */
|
||||
ElMenu.install = function(Vue) {
|
||||
Vue.component(ElMenu.name, ElMenu);
|
||||
};
|
||||
|
||||
export default ElMenu;
|
45
packages/menu/src/menu-item-group.vue
Normal file
45
packages/menu/src/menu-item-group.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<li class="el-menu-item-group">
|
||||
<div class="el-menu-item-group__title" :style="{paddingLeft: levelPadding + 'px'}">
|
||||
<template v-if="!$slots.title">{{title}}</template>
|
||||
<slot v-else name="title"></slot>
|
||||
</div>
|
||||
<ul>
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ElMenuItemGroup',
|
||||
|
||||
componentName: 'ElMenuItemGroup',
|
||||
|
||||
inject: ['rootMenu'],
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
paddingLeft: 20
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
levelPadding() {
|
||||
let padding = 20;
|
||||
let parent = this.$parent;
|
||||
if (this.rootMenu.collapse) return 20;
|
||||
while (parent && parent.$options.componentName !== 'ElMenu') {
|
||||
if (parent.$options.componentName === 'ElSubmenu') {
|
||||
padding += 20;
|
||||
}
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return padding;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
112
packages/menu/src/menu-item.vue
Normal file
112
packages/menu/src/menu-item.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<li class="el-menu-item"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
:style="[paddingStyle, itemStyle, { backgroundColor }]"
|
||||
:class="{
|
||||
'is-active': active,
|
||||
'is-disabled': disabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
@mouseenter="onMouseEnter"
|
||||
@focus="onMouseEnter"
|
||||
@blur="onMouseLeave"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<el-tooltip
|
||||
v-if="parentMenu.$options.componentName === 'ElMenu' && rootMenu.collapse && $slots.title"
|
||||
effect="dark"
|
||||
placement="right">
|
||||
<div slot="content"><slot name="title"></slot></div>
|
||||
<div style="position: absolute;left: 0;top: 0;height: 100%;width: 100%;display: inline-block;box-sizing: border-box;padding: 0 20px;">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<template v-else>
|
||||
<slot></slot>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
import Menu from './menu-mixin';
|
||||
import ElTooltip from 'element-ui/packages/tooltip';
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
|
||||
export default {
|
||||
name: 'ElMenuItem',
|
||||
|
||||
componentName: 'ElMenuItem',
|
||||
|
||||
mixins: [Menu, Emitter],
|
||||
|
||||
components: { ElTooltip },
|
||||
|
||||
props: {
|
||||
index: {
|
||||
default: null,
|
||||
validator: val => typeof val === 'string' || val === null
|
||||
},
|
||||
route: [String, Object],
|
||||
disabled: Boolean
|
||||
},
|
||||
computed: {
|
||||
active() {
|
||||
return this.index === this.rootMenu.activeIndex;
|
||||
},
|
||||
hoverBackground() {
|
||||
return this.rootMenu.hoverBackground;
|
||||
},
|
||||
backgroundColor() {
|
||||
return this.rootMenu.backgroundColor || '';
|
||||
},
|
||||
activeTextColor() {
|
||||
return this.rootMenu.activeTextColor || '';
|
||||
},
|
||||
textColor() {
|
||||
return this.rootMenu.textColor || '';
|
||||
},
|
||||
mode() {
|
||||
return this.rootMenu.mode;
|
||||
},
|
||||
itemStyle() {
|
||||
const style = {
|
||||
color: this.active ? this.activeTextColor : this.textColor
|
||||
};
|
||||
if (this.mode === 'horizontal' && !this.isNested) {
|
||||
style.borderBottomColor = this.active
|
||||
? (this.rootMenu.activeTextColor ? this.activeTextColor : '')
|
||||
: 'transparent';
|
||||
}
|
||||
return style;
|
||||
},
|
||||
isNested() {
|
||||
return this.parentMenu !== this.rootMenu;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMouseEnter() {
|
||||
if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
|
||||
this.$el.style.backgroundColor = this.hoverBackground;
|
||||
},
|
||||
onMouseLeave() {
|
||||
if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
|
||||
this.$el.style.backgroundColor = this.backgroundColor;
|
||||
},
|
||||
handleClick() {
|
||||
if (!this.disabled) {
|
||||
this.dispatch('ElMenu', 'item-click', this);
|
||||
this.$emit('click', this);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.parentMenu.addItem(this);
|
||||
this.rootMenu.addItem(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.parentMenu.removeItem(this);
|
||||
this.rootMenu.removeItem(this);
|
||||
}
|
||||
};
|
||||
</script>
|
44
packages/menu/src/menu-mixin.js
Normal file
44
packages/menu/src/menu-mixin.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export default {
|
||||
inject: ['rootMenu'],
|
||||
computed: {
|
||||
indexPath() {
|
||||
const path = [this.index];
|
||||
let parent = this.$parent;
|
||||
while (parent.$options.componentName !== 'ElMenu') {
|
||||
if (parent.index) {
|
||||
path.unshift(parent.index);
|
||||
}
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
parentMenu() {
|
||||
let parent = this.$parent;
|
||||
while (
|
||||
parent &&
|
||||
['ElMenu', 'ElSubmenu'].indexOf(parent.$options.componentName) === -1
|
||||
) {
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
paddingStyle() {
|
||||
if (this.rootMenu.mode !== 'vertical') return {};
|
||||
|
||||
let padding = 20;
|
||||
let parent = this.$parent;
|
||||
|
||||
if (this.rootMenu.collapse) {
|
||||
padding = 20;
|
||||
} else {
|
||||
while (parent && parent.$options.componentName !== 'ElMenu') {
|
||||
if (parent.$options.componentName === 'ElSubmenu') {
|
||||
padding += 20;
|
||||
}
|
||||
parent = parent.$parent;
|
||||
}
|
||||
}
|
||||
return {paddingLeft: padding + 'px'};
|
||||
}
|
||||
}
|
||||
};
|
325
packages/menu/src/menu.vue
Normal file
325
packages/menu/src/menu.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<script type="text/jsx">
|
||||
import emitter from 'element-ui/src/mixins/emitter';
|
||||
import Migrating from 'element-ui/src/mixins/migrating';
|
||||
import Menubar from 'element-ui/src/utils/menu/aria-menubar';
|
||||
import { addClass, removeClass, hasClass } from 'element-ui/src/utils/dom';
|
||||
|
||||
export default {
|
||||
name: 'ElMenu',
|
||||
|
||||
render (h) {
|
||||
const component = (
|
||||
<ul
|
||||
role="menubar"
|
||||
key={ +this.collapse }
|
||||
style={{ backgroundColor: this.backgroundColor || '' }}
|
||||
class={{
|
||||
'el-menu--horizontal': this.mode === 'horizontal',
|
||||
'el-menu--collapse': this.collapse,
|
||||
"el-menu": true
|
||||
}}
|
||||
>
|
||||
{ this.$slots.default }
|
||||
</ul>
|
||||
);
|
||||
|
||||
if (this.collapseTransition) {
|
||||
return (
|
||||
<el-menu-collapse-transition>
|
||||
{ component }
|
||||
</el-menu-collapse-transition>
|
||||
);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
},
|
||||
|
||||
componentName: 'ElMenu',
|
||||
|
||||
mixins: [emitter, Migrating],
|
||||
|
||||
provide() {
|
||||
return {
|
||||
rootMenu: this
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
'el-menu-collapse-transition': {
|
||||
functional: true,
|
||||
render(createElement, context) {
|
||||
const data = {
|
||||
props: {
|
||||
mode: 'out-in'
|
||||
},
|
||||
on: {
|
||||
beforeEnter(el) {
|
||||
el.style.opacity = 0.2;
|
||||
},
|
||||
|
||||
enter(el) {
|
||||
addClass(el, 'el-opacity-transition');
|
||||
el.style.opacity = 1;
|
||||
},
|
||||
|
||||
afterEnter(el) {
|
||||
removeClass(el, 'el-opacity-transition');
|
||||
el.style.opacity = '';
|
||||
},
|
||||
|
||||
beforeLeave(el) {
|
||||
if (!el.dataset) el.dataset = {};
|
||||
|
||||
if (hasClass(el, 'el-menu--collapse')) {
|
||||
removeClass(el, 'el-menu--collapse');
|
||||
el.dataset.oldOverflow = el.style.overflow;
|
||||
el.dataset.scrollWidth = el.clientWidth;
|
||||
addClass(el, 'el-menu--collapse');
|
||||
} else {
|
||||
addClass(el, 'el-menu--collapse');
|
||||
el.dataset.oldOverflow = el.style.overflow;
|
||||
el.dataset.scrollWidth = el.clientWidth;
|
||||
removeClass(el, 'el-menu--collapse');
|
||||
}
|
||||
|
||||
el.style.width = el.scrollWidth + 'px';
|
||||
el.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
leave(el) {
|
||||
addClass(el, 'horizontal-collapse-transition');
|
||||
el.style.width = el.dataset.scrollWidth + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
return createElement('transition', data, context.children);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'vertical'
|
||||
},
|
||||
defaultActive: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
defaultOpeneds: Array,
|
||||
uniqueOpened: Boolean,
|
||||
router: Boolean,
|
||||
menuTrigger: {
|
||||
type: String,
|
||||
default: 'hover'
|
||||
},
|
||||
collapse: Boolean,
|
||||
backgroundColor: String,
|
||||
textColor: String,
|
||||
activeTextColor: String,
|
||||
collapseTransition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeIndex: this.defaultActive,
|
||||
openedMenus: (this.defaultOpeneds && !this.collapse) ? this.defaultOpeneds.slice(0) : [],
|
||||
items: {},
|
||||
submenus: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hoverBackground() {
|
||||
return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
|
||||
},
|
||||
isMenuPopup() {
|
||||
return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
defaultActive(value){
|
||||
if(!this.items[value]){
|
||||
this.activeIndex = null
|
||||
}
|
||||
this.updateActiveIndex(value)
|
||||
},
|
||||
|
||||
defaultOpeneds(value) {
|
||||
if (!this.collapse) {
|
||||
this.openedMenus = value;
|
||||
}
|
||||
},
|
||||
|
||||
collapse(value) {
|
||||
if (value) this.openedMenus = [];
|
||||
this.broadcast('ElSubmenu', 'toggle-collapse', value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateActiveIndex(val) {
|
||||
const item = this.items[val] || this.items[this.activeIndex] || this.items[this.defaultActive];
|
||||
if (item) {
|
||||
this.activeIndex = item.index;
|
||||
this.initOpenedMenu();
|
||||
} else {
|
||||
this.activeIndex = null;
|
||||
}
|
||||
},
|
||||
|
||||
getMigratingConfig() {
|
||||
return {
|
||||
props: {
|
||||
'theme': 'theme is removed.'
|
||||
}
|
||||
};
|
||||
},
|
||||
getColorChannels(color) {
|
||||
color = color.replace('#', '');
|
||||
if (/^[0-9a-fA-F]{3}$/.test(color)) {
|
||||
color = color.split('');
|
||||
for (let i = 2; i >= 0; i--) {
|
||||
color.splice(i, 0, color[i]);
|
||||
}
|
||||
color = color.join('');
|
||||
}
|
||||
if (/^[0-9a-fA-F]{6}$/.test(color)) {
|
||||
return {
|
||||
red: parseInt(color.slice(0, 2), 16),
|
||||
green: parseInt(color.slice(2, 4), 16),
|
||||
blue: parseInt(color.slice(4, 6), 16)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
};
|
||||
}
|
||||
},
|
||||
mixColor(color, percent) {
|
||||
let { red, green, blue } = this.getColorChannels(color);
|
||||
if (percent > 0) { // shade given color
|
||||
red *= 1 - percent;
|
||||
green *= 1 - percent;
|
||||
blue *= 1 - percent;
|
||||
} else { // tint given color
|
||||
red += (255 - red) * percent;
|
||||
green += (255 - green) * percent;
|
||||
blue += (255 - blue) * percent;
|
||||
}
|
||||
return `rgb(${ Math.round(red) }, ${ Math.round(green) }, ${ Math.round(blue) })`;
|
||||
},
|
||||
addItem(item) {
|
||||
this.$set(this.items, item.index, item);
|
||||
},
|
||||
removeItem(item) {
|
||||
delete this.items[item.index];
|
||||
},
|
||||
addSubmenu(item) {
|
||||
this.$set(this.submenus, item.index, item);
|
||||
},
|
||||
removeSubmenu(item) {
|
||||
delete this.submenus[item.index];
|
||||
},
|
||||
openMenu(index, indexPath) {
|
||||
let openedMenus = this.openedMenus;
|
||||
if (openedMenus.indexOf(index) !== -1) return;
|
||||
// 将不在该菜单路径下的其余菜单收起
|
||||
// collapse all menu that are not under current menu item
|
||||
if (this.uniqueOpened) {
|
||||
this.openedMenus = openedMenus.filter(index => {
|
||||
return indexPath.indexOf(index) !== -1;
|
||||
});
|
||||
}
|
||||
this.openedMenus.push(index);
|
||||
},
|
||||
closeMenu(index) {
|
||||
const i = this.openedMenus.indexOf(index);
|
||||
if (i !== -1) {
|
||||
this.openedMenus.splice(i, 1);
|
||||
}
|
||||
},
|
||||
handleSubmenuClick(submenu) {
|
||||
const { index, indexPath } = submenu;
|
||||
let isOpened = this.openedMenus.indexOf(index) !== -1;
|
||||
|
||||
if (isOpened) {
|
||||
this.closeMenu(index);
|
||||
this.$emit('close', index, indexPath);
|
||||
} else {
|
||||
this.openMenu(index, indexPath);
|
||||
this.$emit('open', index, indexPath);
|
||||
}
|
||||
},
|
||||
handleItemClick(item) {
|
||||
const { index, indexPath } = item;
|
||||
const oldActiveIndex = this.activeIndex;
|
||||
const hasIndex = item.index !== null;
|
||||
|
||||
if (hasIndex) {
|
||||
this.activeIndex = item.index;
|
||||
}
|
||||
|
||||
this.$emit('select', index, indexPath, item);
|
||||
|
||||
if (this.mode === 'horizontal' || this.collapse) {
|
||||
this.openedMenus = [];
|
||||
}
|
||||
|
||||
if (this.router && hasIndex) {
|
||||
this.routeToItem(item, (error) => {
|
||||
this.activeIndex = oldActiveIndex;
|
||||
if (error) {
|
||||
// vue-router 3.1.0+ push/replace cause NavigationDuplicated error
|
||||
// https://github.com/ElemeFE/element/issues/17044
|
||||
if (error.name === 'NavigationDuplicated') return
|
||||
console.error(error)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// 初始化展开菜单
|
||||
// initialize opened menu
|
||||
initOpenedMenu() {
|
||||
const index = this.activeIndex;
|
||||
const activeItem = this.items[index];
|
||||
if (!activeItem || this.mode === 'horizontal' || this.collapse) return;
|
||||
|
||||
let indexPath = activeItem.indexPath;
|
||||
|
||||
// 展开该菜单项的路径上所有子菜单
|
||||
// expand all submenus of the menu item
|
||||
indexPath.forEach(index => {
|
||||
let submenu = this.submenus[index];
|
||||
submenu && this.openMenu(index, submenu.indexPath);
|
||||
});
|
||||
},
|
||||
routeToItem(item, onError) {
|
||||
let route = item.route || item.index;
|
||||
try {
|
||||
this.$router.push(route, () => {}, onError);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
open(index) {
|
||||
const { indexPath } = this.submenus[index.toString()];
|
||||
indexPath.forEach(i => this.openMenu(i, indexPath));
|
||||
},
|
||||
close(index) {
|
||||
this.closeMenu(index);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initOpenedMenu();
|
||||
this.$on('item-click', this.handleItemClick);
|
||||
this.$on('submenu-click', this.handleSubmenuClick);
|
||||
if (this.mode === 'horizontal') {
|
||||
new Menubar(this.$el); // eslint-disable-line
|
||||
}
|
||||
this.$watch('items', this.updateActiveIndex);
|
||||
}
|
||||
};
|
||||
</script>
|
349
packages/menu/src/submenu.vue
Normal file
349
packages/menu/src/submenu.vue
Normal file
@@ -0,0 +1,349 @@
|
||||
<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, poperMixins],
|
||||
|
||||
components: { ElCollapseTransition },
|
||||
|
||||
props: {
|
||||
index: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showTimeout: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
hideTimeout: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
popperClass: String,
|
||||
disabled: Boolean,
|
||||
popperAppendToBody: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
popperJS: null,
|
||||
timeout: null,
|
||||
items: {},
|
||||
submenus: {},
|
||||
mouseInChild: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
opened(val) {
|
||||
if (this.isMenuPopup) {
|
||||
this.$nextTick(_ => {
|
||||
this.updatePopper();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// popper option
|
||||
appendToBody() {
|
||||
return this.popperAppendToBody === undefined
|
||||
? this.isFirstLevel
|
||||
: this.popperAppendToBody;
|
||||
},
|
||||
menuTransitionName() {
|
||||
return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
|
||||
},
|
||||
opened() {
|
||||
return this.rootMenu.openedMenus.indexOf(this.index) > -1;
|
||||
},
|
||||
active() {
|
||||
let isActive = false;
|
||||
const submenus = this.submenus;
|
||||
const items = this.items;
|
||||
|
||||
Object.keys(items).forEach(index => {
|
||||
if (items[index].active) {
|
||||
isActive = true;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(submenus).forEach(index => {
|
||||
if (submenus[index].active) {
|
||||
isActive = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isActive;
|
||||
},
|
||||
hoverBackground() {
|
||||
return this.rootMenu.hoverBackground;
|
||||
},
|
||||
backgroundColor() {
|
||||
return this.rootMenu.backgroundColor || '';
|
||||
},
|
||||
activeTextColor() {
|
||||
return this.rootMenu.activeTextColor || '';
|
||||
},
|
||||
textColor() {
|
||||
return this.rootMenu.textColor || '';
|
||||
},
|
||||
mode() {
|
||||
return this.rootMenu.mode;
|
||||
},
|
||||
isMenuPopup() {
|
||||
return this.rootMenu.isMenuPopup;
|
||||
},
|
||||
titleStyle() {
|
||||
if (this.mode !== 'horizontal') {
|
||||
return {
|
||||
color: this.textColor
|
||||
};
|
||||
}
|
||||
return {
|
||||
borderBottomColor: this.active
|
||||
? (this.rootMenu.activeTextColor ? this.activeTextColor : '')
|
||||
: 'transparent',
|
||||
color: this.active
|
||||
? this.activeTextColor
|
||||
: this.textColor
|
||||
};
|
||||
},
|
||||
isFirstLevel() {
|
||||
let isFirstLevel = true;
|
||||
let parent = this.$parent;
|
||||
while (parent && parent !== this.rootMenu) {
|
||||
if (['ElSubmenu', 'ElMenuItemGroup'].indexOf(parent.$options.componentName) > -1) {
|
||||
isFirstLevel = false;
|
||||
break;
|
||||
} else {
|
||||
parent = parent.$parent;
|
||||
}
|
||||
}
|
||||
return isFirstLevel;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCollapseToggle(value) {
|
||||
if (value) {
|
||||
this.initPopper();
|
||||
} else {
|
||||
this.doDestroy();
|
||||
}
|
||||
},
|
||||
addItem(item) {
|
||||
this.$set(this.items, item.index, item);
|
||||
},
|
||||
removeItem(item) {
|
||||
delete this.items[item.index];
|
||||
},
|
||||
addSubmenu(item) {
|
||||
this.$set(this.submenus, item.index, item);
|
||||
},
|
||||
removeSubmenu(item) {
|
||||
delete this.submenus[item.index];
|
||||
},
|
||||
handleClick() {
|
||||
const { rootMenu, disabled } = this;
|
||||
if (
|
||||
(rootMenu.menuTrigger === 'hover' && rootMenu.mode === 'horizontal') ||
|
||||
(rootMenu.collapse && rootMenu.mode === 'vertical') ||
|
||||
disabled
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.dispatch('ElMenu', 'submenu-click', this);
|
||||
},
|
||||
handleMouseenter(event, showTimeout = this.showTimeout) {
|
||||
|
||||
if (!('ActiveXObject' in window) && event.type === 'focus' && !event.relatedTarget) {
|
||||
return;
|
||||
}
|
||||
const { rootMenu, disabled } = this;
|
||||
if (
|
||||
(rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
|
||||
(!rootMenu.collapse && rootMenu.mode === 'vertical') ||
|
||||
disabled
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.dispatch('ElSubmenu', 'mouse-enter-child');
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.rootMenu.openMenu(this.index, this.indexPath);
|
||||
}, showTimeout);
|
||||
|
||||
if (this.appendToBody) {
|
||||
this.$parent.$el.dispatchEvent(new MouseEvent('mouseenter'));
|
||||
}
|
||||
},
|
||||
handleMouseleave(deepDispatch = false) {
|
||||
const {rootMenu} = this;
|
||||
if (
|
||||
(rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
|
||||
(!rootMenu.collapse && rootMenu.mode === 'vertical')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.dispatch('ElSubmenu', 'mouse-leave-child');
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
!this.mouseInChild && this.rootMenu.closeMenu(this.index);
|
||||
}, this.hideTimeout);
|
||||
|
||||
if (this.appendToBody && deepDispatch) {
|
||||
if (this.$parent.$options.name === 'ElSubmenu') {
|
||||
this.$parent.handleMouseleave(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTitleMouseenter() {
|
||||
if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
|
||||
const title = this.$refs['submenu-title'];
|
||||
title && (title.style.backgroundColor = this.rootMenu.hoverBackground);
|
||||
},
|
||||
handleTitleMouseleave() {
|
||||
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' && this.isFirstLevel
|
||||
? 'bottom-start'
|
||||
: 'right-start';
|
||||
},
|
||||
initPopper() {
|
||||
this.referenceElm = this.$el;
|
||||
this.popperElm = this.$refs.menu;
|
||||
this.updatePlacement();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$on('toggle-collapse', this.handleCollapseToggle);
|
||||
this.$on('mouse-enter-child', () => {
|
||||
this.mouseInChild = true;
|
||||
clearTimeout(this.timeout);
|
||||
});
|
||||
this.$on('mouse-leave-child', () => {
|
||||
this.mouseInChild = false;
|
||||
clearTimeout(this.timeout);
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.parentMenu.addSubmenu(this);
|
||||
this.rootMenu.addSubmenu(this);
|
||||
this.initPopper();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.parentMenu.removeSubmenu(this);
|
||||
this.rootMenu.removeSubmenu(this);
|
||||
},
|
||||
render(h) {
|
||||
const {
|
||||
active,
|
||||
opened,
|
||||
paddingStyle,
|
||||
titleStyle,
|
||||
backgroundColor,
|
||||
rootMenu,
|
||||
currentPlacement,
|
||||
menuTransitionName,
|
||||
mode,
|
||||
disabled,
|
||||
popperClass,
|
||||
$slots,
|
||||
isFirstLevel
|
||||
} = this;
|
||||
|
||||
const popupMenu = (
|
||||
<transition name={menuTransitionName}>
|
||||
<div
|
||||
ref="menu"
|
||||
v-show={opened}
|
||||
class={[`el-menu--${mode}`, popperClass]}
|
||||
on-mouseenter={($event) => this.handleMouseenter($event, 100)}
|
||||
on-mouseleave={() => this.handleMouseleave(true)}
|
||||
on-focus={($event) => this.handleMouseenter($event, 100)}>
|
||||
<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>
|
||||
);
|
||||
|
||||
const submenuTitleIcon = (
|
||||
rootMenu.mode === 'horizontal' && isFirstLevel ||
|
||||
rootMenu.mode === 'vertical' && !rootMenu.collapse
|
||||
) ? 'el-icon-arrow-down' : 'el-icon-arrow-right';
|
||||
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
'el-submenu': true,
|
||||
'is-active': active,
|
||||
'is-opened': opened,
|
||||
'is-disabled': disabled
|
||||
}}
|
||||
role="menuitem"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={opened}
|
||||
on-mouseenter={this.handleMouseenter}
|
||||
on-mouseleave={() => this.handleMouseleave(false)}
|
||||
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', submenuTitleIcon ]}></i>
|
||||
</div>
|
||||
{this.isMenuPopup ? popupMenu : inlineMenu}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user