@aplus-frontend/antdv
Version:
Vue basic component library maintained based on ant-design-vue
290 lines • 11.8 kB
JavaScript
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _extends from "@babel/runtime/helpers/esm/extends";
import { createVNode as _createVNode, Fragment as _Fragment } from "vue";
import PropTypes from '../../_util/vue-types';
import { computed, defineComponent, getCurrentInstance, shallowRef, watch, onBeforeUnmount } from 'vue';
import useProvideKeyPath, { useInjectKeyPath, useMeasure } from './hooks/useKeyPath';
import { useInjectMenu, useProvideFirstLevel, MenuContextProvider, useProvideForceRender, useInjectForceRender } from './hooks/useMenuContext';
import { getPropsSlot, isValidElement } from '../../_util/props-util';
import classNames from '../../_util/classNames';
import useDirectionStyle from './hooks/useDirectionStyle';
import PopupTrigger from './PopupTrigger';
import SubMenuList from './SubMenuList';
import InlineSubMenuList from './InlineSubMenuList';
import { cloneElement } from '../../_util/vnode';
import Overflow from '../../vc-overflow';
import devWarning from '../../vc-util/devWarning';
import isValid from '../../_util/isValid';
import { objectType } from '../../_util/type';
let indexGuid = 0;
export const subMenuProps = () => ({
icon: PropTypes.any,
title: PropTypes.any,
disabled: Boolean,
level: Number,
popupClassName: String,
popupOffset: Array,
internalPopupClose: Boolean,
eventKey: String,
expandIcon: Function,
theme: String,
onMouseenter: Function,
onMouseleave: Function,
onTitleClick: Function,
// Internal user prop
originItemValue: objectType()
});
export default defineComponent({
compatConfig: {
MODE: 3
},
name: 'ASubMenu',
inheritAttrs: false,
props: subMenuProps(),
slots: Object,
setup(props, _ref) {
let {
slots,
attrs,
emit
} = _ref;
var _a, _b;
useProvideFirstLevel(false);
const isMeasure = useMeasure();
const instance = getCurrentInstance();
const vnodeKey = typeof instance.vnode.key === 'symbol' ? String(instance.vnode.key) : instance.vnode.key;
devWarning(typeof instance.vnode.key !== 'symbol', 'SubMenu', `SubMenu \`:key="${String(vnodeKey)}"\` not support Symbol type`);
const key = isValid(vnodeKey) ? vnodeKey : `sub_menu_${++indexGuid}_$$_not_set_key`;
const eventKey = (_a = props.eventKey) !== null && _a !== void 0 ? _a : isValid(vnodeKey) ? `sub_menu_${++indexGuid}_$$_${vnodeKey}` : key;
const {
parentEventKeys,
parentInfo,
parentKeys
} = useInjectKeyPath();
const keysPath = computed(() => [...parentKeys.value, key]);
const childrenEventKeys = shallowRef([]);
const menuInfo = {
eventKey,
key,
parentEventKeys,
childrenEventKeys,
parentKeys
};
(_b = parentInfo.childrenEventKeys) === null || _b === void 0 ? void 0 : _b.value.push(eventKey);
onBeforeUnmount(() => {
var _a;
if (parentInfo.childrenEventKeys) {
parentInfo.childrenEventKeys.value = (_a = parentInfo.childrenEventKeys) === null || _a === void 0 ? void 0 : _a.value.filter(k => k != eventKey);
}
});
useProvideKeyPath(eventKey, key, menuInfo);
const {
prefixCls,
activeKeys,
disabled: contextDisabled,
changeActiveKeys,
mode,
inlineCollapsed,
openKeys,
overflowDisabled,
onOpenChange,
registerMenuInfo,
unRegisterMenuInfo,
selectedSubMenuKeys,
expandIcon: menuExpandIcon,
theme
} = useInjectMenu();
const hasKey = vnodeKey !== undefined && vnodeKey !== null;
// If not set key, use forceRender = true for children
// 如果没有 key,强制 render 子元素
const forceRender = !isMeasure && (useInjectForceRender() || !hasKey);
useProvideForceRender(forceRender);
if (isMeasure && hasKey || !isMeasure && !hasKey || forceRender) {
registerMenuInfo(eventKey, menuInfo);
onBeforeUnmount(() => {
unRegisterMenuInfo(eventKey);
});
}
const subMenuPrefixCls = computed(() => `${prefixCls.value}-submenu`);
const mergedDisabled = computed(() => contextDisabled.value || props.disabled);
const elementRef = shallowRef();
const popupRef = shallowRef();
// // ================================ Icon ================================
// const mergedItemIcon = itemIcon || contextItemIcon;
// const mergedExpandIcon = expandIcon || contextExpandIcon;
// ================================ Open ================================
const originOpen = computed(() => openKeys.value.includes(key));
const open = computed(() => !overflowDisabled.value && originOpen.value);
// =============================== Select ===============================
const childrenSelected = computed(() => {
return selectedSubMenuKeys.value.includes(key);
});
const isActive = shallowRef(false);
watch(activeKeys, () => {
isActive.value = !!activeKeys.value.find(val => val === key);
}, {
immediate: true
});
// =============================== Events ===============================
// >>>> Title click
const onInternalTitleClick = e => {
// Skip if disabled
if (mergedDisabled.value) {
return;
}
emit('titleClick', e, key);
// Trigger open by click when mode is `inline`
if (mode.value === 'inline') {
onOpenChange(key, !originOpen.value);
}
};
const onMouseEnter = event => {
if (!mergedDisabled.value) {
changeActiveKeys(keysPath.value);
emit('mouseenter', event);
}
};
const onMouseLeave = event => {
if (!mergedDisabled.value) {
changeActiveKeys([]);
emit('mouseleave', event);
}
};
// ========================== DirectionStyle ==========================
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));
// >>>>> Visible change
const onPopupVisibleChange = newVisible => {
if (mode.value !== 'inline') {
onOpenChange(key, newVisible);
}
};
/**
* Used for accessibility. Helper will focus element without key board.
* We should manually trigger an active
*/
const onInternalFocus = () => {
changeActiveKeys(keysPath.value);
};
// =============================== Render ===============================
const popupId = eventKey && `${eventKey}-popup`;
const popupClassName = computed(() => classNames(prefixCls.value, `${prefixCls.value}-${props.theme || theme.value}`, props.popupClassName));
const renderTitle = (title, icon) => {
if (!icon) {
return inlineCollapsed.value && !parentKeys.value.length && title && typeof title === 'string' ? _createVNode("div", {
"class": `${prefixCls.value}-inline-collapsed-noicon`
}, [title.charAt(0)]) : _createVNode("span", {
"class": `${prefixCls.value}-title-content`
}, [title]);
}
// inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span
// ref: https://github.com/ant-design/ant-design/pull/23456
const titleIsSpan = isValidElement(title) && title.type === 'span';
return _createVNode(_Fragment, null, [cloneElement(typeof icon === 'function' ? icon(props.originItemValue) : icon, {
class: `${prefixCls.value}-item-icon`
}, false), titleIsSpan ? title : _createVNode("span", {
"class": `${prefixCls.value}-title-content`
}, [title])]);
};
// Cache mode if it change to `inline` which do not have popup motion
const triggerModeRef = computed(() => {
return mode.value !== 'inline' && keysPath.value.length > 1 ? 'vertical' : mode.value;
});
const renderMode = computed(() => mode.value === 'horizontal' ? 'vertical' : mode.value);
const subMenuTriggerModeRef = computed(() => triggerModeRef.value === 'horizontal' ? 'vertical' : triggerModeRef.value);
const baseTitleNode = () => {
var _a, _b;
const subMenuPrefixClsValue = subMenuPrefixCls.value;
const icon = (_a = props.icon) !== null && _a !== void 0 ? _a : (_b = slots.icon) === null || _b === void 0 ? void 0 : _b.call(slots, props);
const expandIcon = props.expandIcon || slots.expandIcon || menuExpandIcon.value;
const originTitle = getPropsSlot(slots, props, 'title');
const title = renderTitle(originTitle, icon);
return _createVNode("div", {
"style": directionStyle.value,
"class": `${subMenuPrefixClsValue}-title`,
"tabindex": mergedDisabled.value ? null : -1,
"ref": elementRef,
"title": typeof originTitle === 'string' ? originTitle : null,
"data-menu-id": key,
"aria-expanded": open.value,
"aria-haspopup": true,
"aria-controls": popupId,
"aria-disabled": mergedDisabled.value,
"onClick": onInternalTitleClick,
"onFocus": onInternalFocus
}, [title, mode.value !== 'horizontal' && expandIcon ? expandIcon(_extends(_extends({}, props), {
isOpen: open.value
})) : _createVNode("i", {
"class": `${subMenuPrefixClsValue}-arrow`
}, null)]);
};
return () => {
var _a;
if (isMeasure) {
if (!hasKey) {
return null;
}
return (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots);
}
const subMenuPrefixClsValue = subMenuPrefixCls.value;
let titleNode = () => null;
if (!overflowDisabled.value && mode.value !== 'inline') {
const popupOffset = mode.value === 'horizontal' ? [0, 8] : [10, 0];
titleNode = () => _createVNode(PopupTrigger, {
"mode": triggerModeRef.value,
"prefixCls": subMenuPrefixClsValue,
"visible": !props.internalPopupClose && open.value,
"popupClassName": popupClassName.value,
"popupOffset": props.popupOffset || popupOffset,
"disabled": mergedDisabled.value,
"onVisibleChange": onPopupVisibleChange
}, {
default: () => [baseTitleNode()],
popup: () => _createVNode(MenuContextProvider, {
"mode": subMenuTriggerModeRef.value
}, {
default: () => [_createVNode(SubMenuList, {
"id": popupId,
"ref": popupRef
}, {
default: slots.default
})]
})
});
} else {
// 包裹一层,保持结构一致,防止动画丢失
// https://github.com/vueComponent/ant-design-vue/issues/4325
titleNode = () => _createVNode(PopupTrigger, null, {
default: baseTitleNode
});
}
return _createVNode(MenuContextProvider, {
"mode": renderMode.value
}, {
default: () => [_createVNode(Overflow.Item, _objectSpread(_objectSpread({
"component": "li"
}, attrs), {}, {
"role": "none",
"class": classNames(subMenuPrefixClsValue, `${subMenuPrefixClsValue}-${mode.value}`, attrs.class, {
[`${subMenuPrefixClsValue}-open`]: open.value,
[`${subMenuPrefixClsValue}-active`]: isActive.value,
[`${subMenuPrefixClsValue}-selected`]: childrenSelected.value,
[`${subMenuPrefixClsValue}-disabled`]: mergedDisabled.value
}),
"onMouseenter": onMouseEnter,
"onMouseleave": onMouseLeave,
"data-submenu-id": key
}), {
default: () => {
return _createVNode(_Fragment, null, [titleNode(), !overflowDisabled.value && _createVNode(InlineSubMenuList, {
"id": popupId,
"open": open.value,
"keyPath": keysPath.value
}, {
default: slots.default
})]);
}
})]
});
};
}
});