element-plus
Version:
A Component Library for Vue3.0
429 lines (423 loc) • 16.2 kB
JavaScript
import mitt from 'mitt';
import { inject, computed, defineComponent, reactive, ref, getCurrentInstance, provide, onBeforeMount, onMounted, onBeforeUnmount, h, Transition, withDirectives, vShow, Fragment } from 'vue';
import ElCollapseTransition from '../el-collapse-transition';
import ElPopper from '../el-popper';
function useMenu(instance, currentIndex) {
const rootMenu = inject('rootMenu');
const indexPath = computed(() => {
let parent = instance.parent;
const path = [currentIndex];
while (parent.type.name !== 'ElMenu') {
if (parent.props.index) {
path.unshift(parent.props.index);
}
parent = parent.parent;
}
return path;
});
const parentMenu = computed(() => {
let parent = instance.parent;
while (parent && ['ElMenu', 'ElSubmenu'].indexOf(parent.type.name) === -1) {
parent = parent.parent;
}
return parent;
});
const paddingStyle = computed(() => {
let parent = instance.parent;
if (rootMenu.props.mode !== 'vertical')
return {};
let padding = 20;
if (rootMenu.props.collapse) {
padding = 20;
}
else {
while (parent && parent.type.name !== 'ElMenu') {
if (parent.type.name === 'ElSubmenu') {
padding += 20;
}
parent = parent.parent;
}
}
return { paddingLeft: padding + 'px' };
});
return { parentMenu, paddingStyle, indexPath };
}
var script = defineComponent({
name: 'ElSubmenu',
componentName: 'ElSubmenu',
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,
},
},
setup(props) {
const data = reactive({
popperJS: null,
timeout: null,
items: {},
submenus: {},
currentPlacement: '',
mouseInChild: false,
opened: false,
});
const verticalTitleRef = ref(null);
const popperVnode = ref(null);
const instance = getCurrentInstance();
const { paddingStyle, indexPath, parentMenu } = useMenu(instance, props.index);
const { openedMenus, isMenuPopup, hoverBackground: rootHoverBackground, methods: rootMethods, props: rootProps, methods: { closeMenu }, rootMenuOn, rootMenuEmit, } = inject('rootMenu');
const { addSubMenu: parentAddSubmenu, removeSubMenu: parentRemoveSubmenu, handleMouseleave: parentHandleMouseleave, } = inject(`subMenu:${parentMenu.value.uid}`);
const submenuTitleIcon = computed(() => {
return (mode.value === 'horizontal' && isFirstLevel.value) ||
(mode.value === 'vertical' && !rootProps.collapse)
? 'el-icon-arrow-down'
: 'el-icon-arrow-right';
});
const isFirstLevel = computed(() => {
let isFirstLevel = true;
let parent = instance.parent;
while (parent && parent.type.name !== 'ElMenu') {
if (['ElSubmenu', 'ElMenuItemGroup'].includes(parent.type.name)) {
isFirstLevel = false;
break;
}
else {
parent = parent.parent;
}
}
return isFirstLevel;
});
const appendToBody = computed(() => {
return props.popperAppendToBody === undefined
? isFirstLevel.value
: Boolean(props.popperAppendToBody);
});
const menuTransitionName = computed(() => {
return rootProps.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
});
const opened = computed(() => {
return openedMenus.value.includes(props.index);
});
const active = computed(() => {
let isActive = false;
const submenus = data.submenus;
const items = data.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;
});
const backgroundColor = computed(() => {
return rootProps.backgroundColor || '';
});
const activeTextColor = computed(() => {
return rootProps.activeTextColor || '';
});
const textColor = computed(() => {
return rootProps.textColor || '';
});
const mode = computed(() => {
return rootProps.mode;
});
const titleStyle = computed(() => {
if (mode.value !== 'horizontal') {
return {
color: textColor.value,
};
}
return {
borderBottomColor: active.value
? rootProps.activeTextColor
? activeTextColor.value
: ''
: 'transparent',
color: active.value ? activeTextColor.value : textColor.value,
};
});
const subMenuEmitter = mitt();
const doDestroy = () => {
var _a;
(_a = popperVnode.value) === null || _a === void 0 ? void 0 : _a.doDestroy();
};
const handleCollapseToggle = value => {
if (value) {
updatePlacement();
}
else {
doDestroy();
}
};
const addItem = item => {
data.items[item.index] = item;
};
const removeItem = item => {
delete data.items[item.index];
};
const addSubMenu = item => {
data.submenus[item.index] = item;
};
const removeSubMenu = item => {
delete data.submenus[item.index];
};
const handleClick = () => {
const disabled = props.disabled;
if ((rootProps.menuTrigger === 'hover' &&
rootProps.mode === 'horizontal') ||
(rootProps.collapse && rootProps.mode === 'vertical') ||
disabled) {
return;
}
rootMenuEmit('submenu:submenu-click', { index: props.index, indexPath });
};
const handleMouseenter = (event, showTimeout = props.showTimeout) => {
if (!('ActiveXObject' in window) &&
event.type === 'focus' &&
!event.relatedTarget) {
return;
}
const disabled = props.disabled;
if ((rootProps.menuTrigger === 'click' &&
rootProps.mode === 'horizontal') ||
(!rootProps.collapse && rootProps.mode === 'vertical') ||
disabled) {
return;
}
subMenuEmitter.emit('submenu:mouse-enter-child');
clearTimeout(data.timeout);
data.timeout = setTimeout(() => {
rootMethods.openMenu(props.index, indexPath);
}, showTimeout);
if (appendToBody.value) {
parentMenu.value.vnode.el.dispatchEvent(new MouseEvent('mouseenter'));
}
};
const handleMouseleave = (deepDispatch = false) => {
if ((rootProps.menuTrigger === 'click' &&
rootProps.mode === 'horizontal') ||
(!rootProps.collapse && rootProps.mode === 'vertical')) {
return;
}
subMenuEmitter.emit('submenu:mouse-leave-child');
clearTimeout(data.timeout);
data.timeout = setTimeout(() => {
!data.mouseInChild && closeMenu(props.index);
}, props.hideTimeout);
if (appendToBody.value && deepDispatch) {
if (instance.parent.type.name === 'ElSubmenu') {
parentHandleMouseleave(true);
}
}
};
const handleTitleMouseenter = () => {
var _a;
if (mode.value === 'horizontal' && !rootProps.backgroundColor)
return;
const title = ((_a = popperVnode.value) === null || _a === void 0 ? void 0 : _a.triggerRef) || verticalTitleRef.value;
title && (title.style.backgroundColor = rootHoverBackground.value);
};
const handleTitleMouseleave = () => {
var _a;
if (mode.value === 'horizontal' && !rootProps.backgroundColor)
return;
const title = ((_a = popperVnode.value) === null || _a === void 0 ? void 0 : _a.triggerRef) || verticalTitleRef.value;
title && (title.style.backgroundColor = rootProps.backgroundColor || '');
};
const updatePlacement = () => {
data.currentPlacement =
mode.value === 'horizontal' && isFirstLevel.value
? 'bottom-start'
: 'right-start';
};
provide(`subMenu:${instance.uid}`, {
addSubMenu,
removeSubMenu,
handleMouseleave,
});
onBeforeMount(() => {
rootMenuOn('rootMenu:toggle-collapse', (val) => {
handleCollapseToggle(val);
});
subMenuEmitter.on('submenu:mouse-enter-child', () => {
data.mouseInChild = true;
clearTimeout(data.timeout);
});
subMenuEmitter.on('submenu:mouse-leave-child', () => {
data.mouseInChild = false;
clearTimeout(data.timeout);
});
});
onMounted(() => {
rootMethods.addSubMenu({
index: props.index,
indexPath,
active,
});
parentAddSubmenu({
index: props.index,
indexPath,
active,
});
updatePlacement();
});
onBeforeUnmount(() => {
parentRemoveSubmenu({
index: props.index,
indexPath,
active,
});
rootMethods.removeSubMenu({
index: props.index,
indexPath,
active,
});
});
return {
data,
props,
mode,
active,
isMenuPopup,
opened,
paddingStyle,
titleStyle,
backgroundColor,
rootProps,
menuTransitionName,
submenuTitleIcon,
appendToBody,
handleClick,
handleMouseenter,
handleMouseleave,
handleTitleMouseenter,
handleTitleMouseleave,
addItem,
removeItem,
addSubMenu,
removeSubMenu,
popperVnode,
verticalTitleRef,
};
},
render() {
var _a, _b;
const titleTag = [
(_b = (_a = this.$slots).title) === null || _b === void 0 ? void 0 : _b.call(_a),
h('i', {
class: ['el-submenu__icon-arrow', this.submenuTitleIcon],
}, null)
];
const ulStyle = {
backgroundColor: this.rootProps.backgroundColor || '',
};
const child = this.isMenuPopup
? h(ElPopper, {
ref: 'popperVNode',
manualMode: true,
visible: this.opened,
'onUpdate:visible': (val) => this.opened = val,
effect: 'light',
pure: true,
offset: 6,
showArrow: false,
popperClass: this.popperClass,
placement: this.data.currentPlacement,
appendToBody: this.appendToBody,
}, {
default: () => h(Transition, {
name: this.menuTransitionName,
}, {
default: () => {
var _a, _b;
return withDirectives(h('div', {
ref: 'menu',
class: [
`el-menu--${this.mode}`,
this.popperClass,
],
onMouseenter: ($event) => this.handleMouseenter($event, 100),
onMouseleave: () => this.handleMouseleave(true),
onFocus: ($event) => this.handleMouseenter($event, 100),
}, [
h('ul', {
class: [
'el-menu el-menu--popup',
`el-menu--popup-${this.data.currentPlacement}`,
],
style: ulStyle,
}, [(_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a)]),
]), [[vShow, this.opened]]);
},
}),
trigger: () => h('div', {
class: 'el-submenu__title',
style: [this.paddingStyle, this.titleStyle, { backgroundColor: this.backgroundColor }],
onClick: this.handleClick,
onMouseenter: this.handleTitleMouseenter,
onMouseleave: this.handleTitleMouseleave,
}, titleTag),
})
: h(Fragment, {}, [
h('div', {
class: 'el-submenu__title',
style: [this.paddingStyle, this.titleStyle, { backgroundColor: this.backgroundColor }],
ref: 'verticalTitleRef',
onClick: this.handleClick,
onMouseenter: this.handleTitleMouseenter,
onMouseleave: this.handleTitleMouseleave,
}, titleTag),
h(ElCollapseTransition, {}, {
default: () => {
var _a, _b;
return withDirectives(h('ul', {
role: 'menu',
class: 'el-menu el-menu--inline',
style: ulStyle,
}, [(_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a)]), [[vShow, this.opened]]);
},
}),
]);
return h('li', {
class: [
'el-submenu',
{
'is-active': this.active,
'is-opened': this.opened,
'is-disabled': this.disabled,
},
],
role: 'menuitem',
ariaHaspopup: true,
ariaExpanded: this.opened,
onMouseenter: this.handleMouseenter,
onMouseleave: () => this.handleMouseleave(true),
onFocus: this.handleMouseenter,
}, [child]);
},
});
script.__file = "packages/menu/src/submenu.vue";
script.install = (app) => {
app.component(script.name, script);
};
const _Submenu = script;
export default _Submenu;