sub-menu.mjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { defineComponent, getCurrentInstance, computed, inject, ref, reactive, watch, provide, onMounted, onBeforeUnmount, h, Fragment, withDirectives, vShow } from 'vue';
  2. import { useTimeoutFn } from '@vueuse/core';
  3. import { ElCollapseTransition } from '../../collapse-transition/index.mjs';
  4. import { ElTooltip } from '../../tooltip/index.mjs';
  5. import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
  6. import { ElIcon } from '../../icon/index.mjs';
  7. import useMenu from './use-menu.mjs';
  8. import { useMenuCssVar } from './use-menu-css-var.mjs';
  9. import { buildProps } from '../../../utils/vue/props/runtime.mjs';
  10. import { iconPropType } from '../../../utils/vue/icon.mjs';
  11. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  12. import { throwError } from '../../../utils/error.mjs';
  13. import { isUndefined } from '../../../utils/types.mjs';
  14. import { isString } from '@vue/shared';
  15. const subMenuProps = buildProps({
  16. index: {
  17. type: String,
  18. required: true
  19. },
  20. showTimeout: Number,
  21. hideTimeout: Number,
  22. popperClass: String,
  23. disabled: Boolean,
  24. teleported: {
  25. type: Boolean,
  26. default: void 0
  27. },
  28. popperOffset: Number,
  29. expandCloseIcon: {
  30. type: iconPropType
  31. },
  32. expandOpenIcon: {
  33. type: iconPropType
  34. },
  35. collapseCloseIcon: {
  36. type: iconPropType
  37. },
  38. collapseOpenIcon: {
  39. type: iconPropType
  40. }
  41. });
  42. const COMPONENT_NAME = "ElSubMenu";
  43. var SubMenu = defineComponent({
  44. name: COMPONENT_NAME,
  45. props: subMenuProps,
  46. setup(props, { slots, expose }) {
  47. const instance = getCurrentInstance();
  48. const { indexPath, parentMenu } = useMenu(instance, computed(() => props.index));
  49. const nsMenu = useNamespace("menu");
  50. const nsSubMenu = useNamespace("sub-menu");
  51. const rootMenu = inject("rootMenu");
  52. if (!rootMenu)
  53. throwError(COMPONENT_NAME, "can not inject root menu");
  54. const subMenu = inject(`subMenu:${parentMenu.value.uid}`);
  55. if (!subMenu)
  56. throwError(COMPONENT_NAME, "can not inject sub menu");
  57. const items = ref({});
  58. const subMenus = ref({});
  59. let timeout;
  60. const mouseInChild = ref(false);
  61. const verticalTitleRef = ref();
  62. const vPopper = ref();
  63. const currentPlacement = computed(() => mode.value === "horizontal" && isFirstLevel.value ? "bottom-start" : "right-start");
  64. const subMenuTitleIcon = computed(() => {
  65. return mode.value === "horizontal" && isFirstLevel.value || mode.value === "vertical" && !rootMenu.props.collapse ? props.expandCloseIcon && props.expandOpenIcon ? opened.value ? props.expandOpenIcon : props.expandCloseIcon : ArrowDown : props.collapseCloseIcon && props.collapseOpenIcon ? opened.value ? props.collapseOpenIcon : props.collapseCloseIcon : ArrowRight;
  66. });
  67. const isFirstLevel = computed(() => subMenu.level === 0);
  68. const appendToBody = computed(() => {
  69. const value = props.teleported;
  70. return isUndefined(value) ? isFirstLevel.value : value;
  71. });
  72. const menuTransitionName = computed(() => rootMenu.props.collapse ? `${nsMenu.namespace.value}-zoom-in-left` : `${nsMenu.namespace.value}-zoom-in-top`);
  73. const fallbackPlacements = computed(() => mode.value === "horizontal" && isFirstLevel.value ? [
  74. "bottom-start",
  75. "bottom-end",
  76. "top-start",
  77. "top-end",
  78. "right-start",
  79. "left-start"
  80. ] : [
  81. "right-start",
  82. "right",
  83. "right-end",
  84. "left-start",
  85. "bottom-start",
  86. "bottom-end",
  87. "top-start",
  88. "top-end"
  89. ]);
  90. const opened = computed(() => rootMenu.openedMenus.includes(props.index));
  91. const active = computed(() => [...Object.values(items.value), ...Object.values(subMenus.value)].some(({ active: active2 }) => active2));
  92. const mode = computed(() => rootMenu.props.mode);
  93. const persistent = computed(() => rootMenu.props.persistent);
  94. const item = reactive({
  95. index: props.index,
  96. indexPath,
  97. active
  98. });
  99. const ulStyle = useMenuCssVar(rootMenu.props, subMenu.level + 1);
  100. const subMenuPopperOffset = computed(() => {
  101. var _a;
  102. return (_a = props.popperOffset) != null ? _a : rootMenu.props.popperOffset;
  103. });
  104. const subMenuPopperClass = computed(() => {
  105. var _a;
  106. return (_a = props.popperClass) != null ? _a : rootMenu.props.popperClass;
  107. });
  108. const subMenuShowTimeout = computed(() => {
  109. var _a;
  110. return (_a = props.showTimeout) != null ? _a : rootMenu.props.showTimeout;
  111. });
  112. const subMenuHideTimeout = computed(() => {
  113. var _a;
  114. return (_a = props.hideTimeout) != null ? _a : rootMenu.props.hideTimeout;
  115. });
  116. const doDestroy = () => {
  117. var _a, _b, _c;
  118. return (_c = (_b = (_a = vPopper.value) == null ? void 0 : _a.popperRef) == null ? void 0 : _b.popperInstanceRef) == null ? void 0 : _c.destroy();
  119. };
  120. const handleCollapseToggle = (value) => {
  121. if (!value) {
  122. doDestroy();
  123. }
  124. };
  125. const handleClick = () => {
  126. if (rootMenu.props.menuTrigger === "hover" && rootMenu.props.mode === "horizontal" || rootMenu.props.collapse && rootMenu.props.mode === "vertical" || props.disabled)
  127. return;
  128. rootMenu.handleSubMenuClick({
  129. index: props.index,
  130. indexPath: indexPath.value,
  131. active: active.value
  132. });
  133. };
  134. const handleMouseenter = (event, showTimeout = subMenuShowTimeout.value) => {
  135. var _a;
  136. if (event.type === "focus")
  137. return;
  138. if (rootMenu.props.menuTrigger === "click" && rootMenu.props.mode === "horizontal" || !rootMenu.props.collapse && rootMenu.props.mode === "vertical" || props.disabled) {
  139. subMenu.mouseInChild.value = true;
  140. return;
  141. }
  142. subMenu.mouseInChild.value = true;
  143. timeout == null ? void 0 : timeout();
  144. ({ stop: timeout } = useTimeoutFn(() => {
  145. rootMenu.openMenu(props.index, indexPath.value);
  146. }, showTimeout));
  147. if (appendToBody.value) {
  148. (_a = parentMenu.value.vnode.el) == null ? void 0 : _a.dispatchEvent(new MouseEvent("mouseenter"));
  149. }
  150. };
  151. const handleMouseleave = (deepDispatch = false) => {
  152. var _a;
  153. if (rootMenu.props.menuTrigger === "click" && rootMenu.props.mode === "horizontal" || !rootMenu.props.collapse && rootMenu.props.mode === "vertical") {
  154. subMenu.mouseInChild.value = false;
  155. return;
  156. }
  157. timeout == null ? void 0 : timeout();
  158. subMenu.mouseInChild.value = false;
  159. ({ stop: timeout } = useTimeoutFn(() => !mouseInChild.value && rootMenu.closeMenu(props.index, indexPath.value), subMenuHideTimeout.value));
  160. if (appendToBody.value && deepDispatch) {
  161. (_a = subMenu.handleMouseleave) == null ? void 0 : _a.call(subMenu, true);
  162. }
  163. };
  164. watch(() => rootMenu.props.collapse, (value) => handleCollapseToggle(Boolean(value)));
  165. {
  166. const addSubMenu = (item2) => {
  167. subMenus.value[item2.index] = item2;
  168. };
  169. const removeSubMenu = (item2) => {
  170. delete subMenus.value[item2.index];
  171. };
  172. provide(`subMenu:${instance.uid}`, {
  173. addSubMenu,
  174. removeSubMenu,
  175. handleMouseleave,
  176. mouseInChild,
  177. level: subMenu.level + 1
  178. });
  179. }
  180. expose({
  181. opened
  182. });
  183. onMounted(() => {
  184. rootMenu.addSubMenu(item);
  185. subMenu.addSubMenu(item);
  186. });
  187. onBeforeUnmount(() => {
  188. subMenu.removeSubMenu(item);
  189. rootMenu.removeSubMenu(item);
  190. });
  191. return () => {
  192. var _a;
  193. const titleTag = [
  194. (_a = slots.title) == null ? void 0 : _a.call(slots),
  195. h(ElIcon, {
  196. class: nsSubMenu.e("icon-arrow"),
  197. style: {
  198. transform: opened.value ? props.expandCloseIcon && props.expandOpenIcon || props.collapseCloseIcon && props.collapseOpenIcon && rootMenu.props.collapse ? "none" : "rotateZ(180deg)" : "none"
  199. }
  200. }, {
  201. default: () => isString(subMenuTitleIcon.value) ? h(instance.appContext.components[subMenuTitleIcon.value]) : h(subMenuTitleIcon.value)
  202. })
  203. ];
  204. const child = rootMenu.isMenuPopup ? h(ElTooltip, {
  205. ref: vPopper,
  206. visible: opened.value,
  207. effect: "light",
  208. pure: true,
  209. offset: subMenuPopperOffset.value,
  210. showArrow: false,
  211. persistent: persistent.value,
  212. popperClass: subMenuPopperClass.value,
  213. placement: currentPlacement.value,
  214. teleported: appendToBody.value,
  215. fallbackPlacements: fallbackPlacements.value,
  216. transition: menuTransitionName.value,
  217. gpuAcceleration: false
  218. }, {
  219. content: () => {
  220. var _a2;
  221. return h("div", {
  222. class: [
  223. nsMenu.m(mode.value),
  224. nsMenu.m("popup-container"),
  225. subMenuPopperClass.value
  226. ],
  227. onMouseenter: (evt) => handleMouseenter(evt, 100),
  228. onMouseleave: () => handleMouseleave(true),
  229. onFocus: (evt) => handleMouseenter(evt, 100)
  230. }, [
  231. h("ul", {
  232. class: [
  233. nsMenu.b(),
  234. nsMenu.m("popup"),
  235. nsMenu.m(`popup-${currentPlacement.value}`)
  236. ],
  237. style: ulStyle.value
  238. }, [(_a2 = slots.default) == null ? void 0 : _a2.call(slots)])
  239. ]);
  240. },
  241. default: () => h("div", {
  242. class: nsSubMenu.e("title"),
  243. onClick: handleClick
  244. }, titleTag)
  245. }) : h(Fragment, {}, [
  246. h("div", {
  247. class: nsSubMenu.e("title"),
  248. ref: verticalTitleRef,
  249. onClick: handleClick
  250. }, titleTag),
  251. h(ElCollapseTransition, {}, {
  252. default: () => {
  253. var _a2;
  254. return withDirectives(h("ul", {
  255. role: "menu",
  256. class: [nsMenu.b(), nsMenu.m("inline")],
  257. style: ulStyle.value
  258. }, [(_a2 = slots.default) == null ? void 0 : _a2.call(slots)]), [[vShow, opened.value]]);
  259. }
  260. })
  261. ]);
  262. return h("li", {
  263. class: [
  264. nsSubMenu.b(),
  265. nsSubMenu.is("active", active.value),
  266. nsSubMenu.is("opened", opened.value),
  267. nsSubMenu.is("disabled", props.disabled)
  268. ],
  269. role: "menuitem",
  270. ariaHaspopup: true,
  271. ariaExpanded: opened.value,
  272. onMouseenter: handleMouseenter,
  273. onMouseleave: () => handleMouseleave(),
  274. onFocus: handleMouseenter
  275. }, [child]);
  276. };
  277. }
  278. });
  279. export { SubMenu as default, subMenuProps };
  280. //# sourceMappingURL=sub-menu.mjs.map