menu.mjs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { defineComponent, getCurrentInstance, ref, computed, nextTick, watch, watchEffect, provide, reactive, onMounted, h } from 'vue';
  2. import { useResizeObserver } from '@vueuse/core';
  3. import { ElIcon } from '../../icon/index.mjs';
  4. import { More } from '@element-plus/icons-vue';
  5. import '../../../utils/index.mjs';
  6. import '../../../hooks/index.mjs';
  7. import Menu$1 from './utils/menu-bar.mjs';
  8. import ElMenuCollapseTransition from './menu-collapse-transition.mjs';
  9. import SubMenu from './sub-menu.mjs';
  10. import { useMenuCssVar } from './use-menu-css-var.mjs';
  11. import { buildProps, definePropType } from '../../../utils/vue/props/runtime.mjs';
  12. import { mutable } from '../../../utils/typescript.mjs';
  13. import { isString, isObject } from '@vue/shared';
  14. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  15. import { flattedChildren } from '../../../utils/vue/vnode.mjs';
  16. const menuProps = buildProps({
  17. mode: {
  18. type: String,
  19. values: ["horizontal", "vertical"],
  20. default: "vertical"
  21. },
  22. defaultActive: {
  23. type: String,
  24. default: ""
  25. },
  26. defaultOpeneds: {
  27. type: definePropType(Array),
  28. default: () => mutable([])
  29. },
  30. uniqueOpened: Boolean,
  31. router: Boolean,
  32. menuTrigger: {
  33. type: String,
  34. values: ["hover", "click"],
  35. default: "hover"
  36. },
  37. collapse: Boolean,
  38. backgroundColor: String,
  39. textColor: String,
  40. activeTextColor: String,
  41. collapseTransition: {
  42. type: Boolean,
  43. default: true
  44. },
  45. ellipsis: {
  46. type: Boolean,
  47. default: true
  48. },
  49. popperEffect: {
  50. type: String,
  51. values: ["dark", "light"],
  52. default: "dark"
  53. }
  54. });
  55. const checkIndexPath = (indexPath) => Array.isArray(indexPath) && indexPath.every((path) => isString(path));
  56. const menuEmits = {
  57. close: (index, indexPath) => isString(index) && checkIndexPath(indexPath),
  58. open: (index, indexPath) => isString(index) && checkIndexPath(indexPath),
  59. select: (index, indexPath, item, routerResult) => isString(index) && checkIndexPath(indexPath) && isObject(item) && (routerResult === void 0 || routerResult instanceof Promise)
  60. };
  61. var Menu = defineComponent({
  62. name: "ElMenu",
  63. props: menuProps,
  64. emits: menuEmits,
  65. setup(props, { emit, slots, expose }) {
  66. const instance = getCurrentInstance();
  67. const router = instance.appContext.config.globalProperties.$router;
  68. const menu = ref();
  69. const nsMenu = useNamespace("menu");
  70. const nsSubMenu = useNamespace("sub-menu");
  71. const sliceIndex = ref(-1);
  72. const openedMenus = ref(props.defaultOpeneds && !props.collapse ? props.defaultOpeneds.slice(0) : []);
  73. const activeIndex = ref(props.defaultActive);
  74. const items = ref({});
  75. const subMenus = ref({});
  76. const isMenuPopup = computed(() => {
  77. return props.mode === "horizontal" || props.mode === "vertical" && props.collapse;
  78. });
  79. const initMenu = () => {
  80. const activeItem = activeIndex.value && items.value[activeIndex.value];
  81. if (!activeItem || props.mode === "horizontal" || props.collapse)
  82. return;
  83. const indexPath = activeItem.indexPath;
  84. indexPath.forEach((index) => {
  85. const subMenu = subMenus.value[index];
  86. subMenu && openMenu(index, subMenu.indexPath);
  87. });
  88. };
  89. const openMenu = (index, indexPath) => {
  90. if (openedMenus.value.includes(index))
  91. return;
  92. if (props.uniqueOpened) {
  93. openedMenus.value = openedMenus.value.filter((index2) => indexPath.includes(index2));
  94. }
  95. openedMenus.value.push(index);
  96. emit("open", index, indexPath);
  97. };
  98. const closeMenu = (index, indexPath) => {
  99. const i = openedMenus.value.indexOf(index);
  100. if (i !== -1) {
  101. openedMenus.value.splice(i, 1);
  102. }
  103. emit("close", index, indexPath);
  104. };
  105. const handleSubMenuClick = ({
  106. index,
  107. indexPath
  108. }) => {
  109. const isOpened = openedMenus.value.includes(index);
  110. if (isOpened) {
  111. closeMenu(index, indexPath);
  112. } else {
  113. openMenu(index, indexPath);
  114. }
  115. };
  116. const handleMenuItemClick = (menuItem) => {
  117. if (props.mode === "horizontal" || props.collapse) {
  118. openedMenus.value = [];
  119. }
  120. const { index, indexPath } = menuItem;
  121. if (index === void 0 || indexPath === void 0)
  122. return;
  123. if (props.router && router) {
  124. const route = menuItem.route || index;
  125. const routerResult = router.push(route).then((res) => {
  126. if (!res)
  127. activeIndex.value = index;
  128. return res;
  129. });
  130. emit("select", index, indexPath, { index, indexPath, route }, routerResult);
  131. } else {
  132. activeIndex.value = index;
  133. emit("select", index, indexPath, { index, indexPath });
  134. }
  135. };
  136. const updateActiveIndex = (val) => {
  137. const itemsInData = items.value;
  138. const item = itemsInData[val] || activeIndex.value && itemsInData[activeIndex.value] || itemsInData[props.defaultActive];
  139. if (item) {
  140. activeIndex.value = item.index;
  141. } else {
  142. activeIndex.value = val;
  143. }
  144. };
  145. const calcSliceIndex = () => {
  146. var _a, _b;
  147. if (!menu.value)
  148. return -1;
  149. const items2 = Array.from((_b = (_a = menu.value) == null ? void 0 : _a.childNodes) != null ? _b : []).filter((item) => item.nodeName !== "#text" || item.nodeValue);
  150. const moreItemWidth = 64;
  151. const paddingLeft = Number.parseInt(getComputedStyle(menu.value).paddingLeft, 10);
  152. const paddingRight = Number.parseInt(getComputedStyle(menu.value).paddingRight, 10);
  153. const menuWidth = menu.value.clientWidth - paddingLeft - paddingRight;
  154. let calcWidth = 0;
  155. let sliceIndex2 = 0;
  156. items2.forEach((item, index) => {
  157. calcWidth += item.offsetWidth || 0;
  158. if (calcWidth <= menuWidth - moreItemWidth) {
  159. sliceIndex2 = index + 1;
  160. }
  161. });
  162. return sliceIndex2 === items2.length ? -1 : sliceIndex2;
  163. };
  164. const debounce = (fn, wait = 33.34) => {
  165. let timmer;
  166. return () => {
  167. timmer && clearTimeout(timmer);
  168. timmer = setTimeout(() => {
  169. fn();
  170. }, wait);
  171. };
  172. };
  173. let isFirstTimeRender = true;
  174. const handleResize = () => {
  175. const callback = () => {
  176. sliceIndex.value = -1;
  177. nextTick(() => {
  178. sliceIndex.value = calcSliceIndex();
  179. });
  180. };
  181. isFirstTimeRender ? callback() : debounce(callback)();
  182. isFirstTimeRender = false;
  183. };
  184. watch(() => props.defaultActive, (currentActive) => {
  185. if (!items.value[currentActive]) {
  186. activeIndex.value = "";
  187. }
  188. updateActiveIndex(currentActive);
  189. });
  190. watch(() => props.collapse, (value) => {
  191. if (value)
  192. openedMenus.value = [];
  193. });
  194. watch(items.value, initMenu);
  195. let resizeStopper;
  196. watchEffect(() => {
  197. if (props.mode === "horizontal" && props.ellipsis)
  198. resizeStopper = useResizeObserver(menu, handleResize).stop;
  199. else
  200. resizeStopper == null ? void 0 : resizeStopper();
  201. });
  202. {
  203. const addSubMenu = (item) => {
  204. subMenus.value[item.index] = item;
  205. };
  206. const removeSubMenu = (item) => {
  207. delete subMenus.value[item.index];
  208. };
  209. const addMenuItem = (item) => {
  210. items.value[item.index] = item;
  211. };
  212. const removeMenuItem = (item) => {
  213. delete items.value[item.index];
  214. };
  215. provide("rootMenu", reactive({
  216. props,
  217. openedMenus,
  218. items,
  219. subMenus,
  220. activeIndex,
  221. isMenuPopup,
  222. addMenuItem,
  223. removeMenuItem,
  224. addSubMenu,
  225. removeSubMenu,
  226. openMenu,
  227. closeMenu,
  228. handleMenuItemClick,
  229. handleSubMenuClick
  230. }));
  231. provide(`subMenu:${instance.uid}`, {
  232. addSubMenu,
  233. removeSubMenu,
  234. mouseInChild: ref(false),
  235. level: 0
  236. });
  237. }
  238. onMounted(() => {
  239. if (props.mode === "horizontal") {
  240. new Menu$1(instance.vnode.el, nsMenu.namespace.value);
  241. }
  242. });
  243. {
  244. const open = (index) => {
  245. const { indexPath } = subMenus.value[index];
  246. indexPath.forEach((i) => openMenu(i, indexPath));
  247. };
  248. expose({
  249. open,
  250. close: closeMenu,
  251. handleResize
  252. });
  253. }
  254. return () => {
  255. var _a, _b;
  256. let slot = (_b = (_a = slots.default) == null ? void 0 : _a.call(slots)) != null ? _b : [];
  257. const vShowMore = [];
  258. if (props.mode === "horizontal" && menu.value) {
  259. const originalSlot = flattedChildren(slot);
  260. const slotDefault = sliceIndex.value === -1 ? originalSlot : originalSlot.slice(0, sliceIndex.value);
  261. const slotMore = sliceIndex.value === -1 ? [] : originalSlot.slice(sliceIndex.value);
  262. if ((slotMore == null ? void 0 : slotMore.length) && props.ellipsis) {
  263. slot = slotDefault;
  264. vShowMore.push(h(SubMenu, {
  265. index: "sub-menu-more",
  266. class: nsSubMenu.e("hide-arrow")
  267. }, {
  268. title: () => h(ElIcon, {
  269. class: nsSubMenu.e("icon-more")
  270. }, { default: () => h(More) }),
  271. default: () => slotMore
  272. }));
  273. }
  274. }
  275. const ulStyle = useMenuCssVar(props, 0);
  276. const vMenu = h("ul", {
  277. key: String(props.collapse),
  278. role: "menubar",
  279. ref: menu,
  280. style: ulStyle.value,
  281. class: {
  282. [nsMenu.b()]: true,
  283. [nsMenu.m(props.mode)]: true,
  284. [nsMenu.m("collapse")]: props.collapse
  285. }
  286. }, [...slot, ...vShowMore]);
  287. if (props.collapseTransition && props.mode === "vertical") {
  288. return h(ElMenuCollapseTransition, () => vMenu);
  289. }
  290. return vMenu;
  291. };
  292. }
  293. });
  294. export { Menu as default, menuEmits, menuProps };
  295. //# sourceMappingURL=menu.mjs.map