@gx-design-vue/pro-layout 0.1.0-alpha.20 → 0.1.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/ProLayout.js +1 -1
  2. package/dist/components/Breadcrumb/index.js +3 -2
  3. package/dist/components/Breadcrumb/interface.d.ts +1 -1
  4. package/dist/components/Header/index.js +6 -14
  5. package/dist/components/Logo/index.js +6 -1
  6. package/dist/components/Menu/iconRender.d.ts +1 -1
  7. package/dist/components/Menu/iconRender.js +1 -1
  8. package/dist/components/Menu/index.d.ts +9 -10
  9. package/dist/components/Menu/index.js +670 -114
  10. package/dist/components/Menu/interface.d.ts +11 -11
  11. package/dist/components/Menu/interface.js +3 -3
  12. package/dist/components/Menu/overflow.d.ts +39 -0
  13. package/dist/components/Menu/overflow.js +81 -0
  14. package/dist/components/Menu/style/base.d.ts +12 -0
  15. package/dist/components/Menu/style/base.js +36 -0
  16. package/dist/components/Menu/style/horizontal.d.ts +19 -0
  17. package/dist/components/Menu/style/horizontal.js +303 -0
  18. package/dist/components/Menu/style/{siderMenu.d.ts → index.d.ts} +64 -5
  19. package/dist/components/Menu/style/index.js +147 -0
  20. package/dist/components/Menu/style/inline.d.ts +13 -0
  21. package/dist/components/Menu/style/{siderMenu.js → inline.js} +29 -115
  22. package/dist/components/PageContainer/index.js +2 -2
  23. package/dist/components/PageContainer/style.js +9 -6
  24. package/dist/components/Sider/index.js +18 -11
  25. package/dist/defaultConfig.js +1 -2
  26. package/dist/hooks/useLayoutBase.d.ts +15 -15
  27. package/dist/hooks/useMenu.d.ts +5 -5
  28. package/dist/hooks/useMenu.js +1 -1
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +3 -3
  31. package/dist/interface.d.ts +2 -6
  32. package/dist/interface.js +2 -2
  33. package/dist/pro-layout.esm.js +4777 -4469
  34. package/dist/pro-layout.js +3 -2
  35. package/dist/style/breadcrumb.js +2 -1
  36. package/dist/style/logo.js +6 -1
  37. package/dist/style/menu.d.ts +1 -1
  38. package/dist/style/menu.js +1 -1
  39. package/dist/theme/augment.d.ts +2 -2
  40. package/dist/theme/interface/components.d.ts +2 -2
  41. package/dist/utils/menu.d.ts +3 -3
  42. package/dist/utils/menu.js +2 -2
  43. package/dist/utils/themeComponents.d.ts +8 -4
  44. package/dist/utils/themeComponents.js +14 -4
  45. package/package.json +15 -15
  46. package/dist/components/Menu/SiderMenu.d.ts +0 -29
  47. package/dist/components/Menu/SiderMenu.js +0 -666
  48. package/dist/components/Menu/convert.d.ts +0 -14
  49. package/dist/components/Menu/convert.js +0 -50
@@ -1,156 +1,622 @@
1
1
  import { useLayoutBase } from "../../hooks/useLayoutBase.js";
2
2
  import { renderMenuIcon } from "./iconRender.js";
3
- import { buildHeaderMenuTokens, buildSiderMenuTokens } from "../../utils/themeComponents.js";
4
- import { buildMenuItems } from "./convert.js";
5
- import { LAYOUT_MENU_SLOT_KEYS } from "./interface.js";
6
- import { computed, createVNode, defineComponent } from "vue";
3
+ import { buildHeaderProMenuTokens, buildSiderProMenuTokens } from "../../utils/themeComponents.js";
4
+ import { PRO_MENU_SLOT_KEYS } from "./interface.js";
5
+ import { useMenuOverflow } from "./overflow.js";
6
+ import style_default from "./style/index.js";
7
+ import { Transition, computed, createTextVNode, createVNode, defineComponent, isVNode, mergeProps, nextTick, onBeforeUnmount, ref, vShow, watch, withDirectives } from "vue";
7
8
  import { useProToken } from "@gx-design-vue/pro-provider";
8
9
  import { classNames, getSlotsProps } from "@gx-design-vue/pro-utils";
9
- import { ConfigProvider, Menu } from "antdv-next";
10
+ import { ConfigProvider, Popover, Tooltip } from "antdv-next";
11
+ import { useBaseConfig } from "antdv-next/config-provider/context";
12
+ import useCSSVarCls from "antdv-next/config-provider/hooks/useCSSVarCls";
13
+ import { omit } from "es-toolkit";
10
14
  //#region src/components/Menu/index.tsx
11
- /** 取菜单项首字母(折叠态无图标时的兜底展示)。 */
15
+ function _isSlot(s) {
16
+ return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
17
+ }
18
+ const DEFAULT_INLINE_INDENT = 24;
19
+ const POPUP_CLOSE_DELAY = 120;
20
+ /** horizontal 模式溢出「...」submenu 的合成 key */
21
+ const OVERFLOW_MENU_KEY = "__overflow__";
22
+ /** horizontal 模式溢出隐藏态:脱离文档流(不挤占可见项)但保留 offsetWidth 可测(参考 @v-c/overflow Item) */
23
+ const OVERFLOW_HIDDEN_STYLE = {
24
+ opacity: 0,
25
+ position: "absolute",
26
+ height: 0,
27
+ overflow: "hidden",
28
+ pointerEvents: "none"
29
+ };
30
+ const SIDER_POPUP_BUILTIN_PLACEMENTS = { rightTop: {
31
+ points: ["tl", "tr"],
32
+ offset: [0, 0],
33
+ overflow: {
34
+ adjustX: true,
35
+ adjustY: true
36
+ },
37
+ autoArrow: false
38
+ } };
39
+ const SIDER_NESTED_POPUP_BUILTIN_PLACEMENTS = { rightTop: {
40
+ points: ["tl", "tr"],
41
+ offset: [-4, 0],
42
+ overflow: {
43
+ adjustX: true,
44
+ adjustY: true
45
+ },
46
+ autoArrow: false
47
+ } };
48
+ /** horizontal 模式一级子菜单向下展开(bottomLeft),参考 vue-components placements.ts */
49
+ const HEADER_POPUP_BUILTIN_PLACEMENTS = { bottomLeft: {
50
+ points: ["tl", "bl"],
51
+ offset: [0, 0],
52
+ overflow: {
53
+ adjustX: true,
54
+ adjustY: true
55
+ },
56
+ autoArrow: false
57
+ } };
58
+ let hasProMenuInitialAutoScrolled = false;
59
+ function isPathPrefix(prefix, path) {
60
+ return prefix.length <= path.length && prefix.every((key, index) => key === path[index]);
61
+ }
62
+ function isSamePath(path, target) {
63
+ return path.length === target.length && isPathPrefix(path, target);
64
+ }
65
+ function getPathKey(path) {
66
+ return path.join("\0");
67
+ }
12
68
  function getMenuTitleSymbol(title) {
13
69
  return title ? title.charAt(0).toUpperCase() : null;
14
70
  }
15
- /**
16
- * 将携带 `route` 的菜单项扁平为 `key route`,供 `select` 事件回填 `LayoutSelectInfo.route`。
17
- */
18
- function flattenItemRoutes(items) {
19
- const routeByKey = /* @__PURE__ */ new Map();
20
- function walk(list) {
21
- for (const item of list) {
22
- const route = item.route;
23
- if (route && item.key != null) routeByKey.set(String(item.key), route);
24
- if (Array.isArray(item.children)) walk(item.children);
25
- }
26
- }
27
- walk(items);
28
- return routeByKey;
71
+ function getVisibleChildren(route) {
72
+ return route.children?.filter((child) => !child.meta?.hideInMenu) ?? [];
73
+ }
74
+ function hasSelectedDescendant(route, selectedKeys) {
75
+ if (selectedKeys.has(route.name)) return true;
76
+ return getVisibleChildren(route).some((child) => hasSelectedDescendant(child, selectedKeys));
77
+ }
78
+ function toMenuRenderItem(route, type) {
79
+ return {
80
+ key: route.name,
81
+ label: route.meta?.title,
82
+ icon: route.meta?.icon,
83
+ disabled: route.meta?.disabled,
84
+ title: route.meta?.title,
85
+ type,
86
+ route
87
+ };
88
+ }
89
+ function getMenuRenderItemIcon(item, route) {
90
+ return item.icon ?? route.meta?.icon;
91
+ }
92
+ function resetCollapseStyle(el) {
93
+ el.style.maxHeight = "";
94
+ el.style.overflow = el.dataset.oldOverflow ?? "";
29
95
  }
30
96
  /**
31
- * LayoutMenu —— 自洽的布局菜单组件。
32
- *
33
- * 直接吃路由分支 `menus`,内部经 `buildMenuItems` 转 antdv-next 菜单项;
34
- * 视觉定制通过 `menuItemIcon`/`menuItemLabel`/`menuItemExtra`(prop 或同名 slot,prop 优先)
35
- * 注入 antdv-next 的 `iconRender`/`labelRender`/`extraRender`。可脱离 GProLayout 独立使用。
97
+ * Vue 3 <Transition> 在 JSX 中只认 onXxx 前缀的 props。
98
+ * 参考 Vue 源码 getTransitionRawProps —— 只收集 key.startsWith('on') 的函数。
36
99
  */
37
- const LayoutMenu = /* @__PURE__ */ defineComponent((props, { emit, slots }) => {
38
- const { prefixCls, hashId, cssVarCls, rootCls, proClasses, proStyles, collapsed } = useLayoutBase(props);
100
+ const collapseTransitionHooks = {
101
+ onBeforeEnter(el) {
102
+ const target = el;
103
+ target.dataset.oldOverflow = target.style.overflow;
104
+ target.style.maxHeight = "0";
105
+ target.style.overflow = "hidden";
106
+ },
107
+ onEnter(el) {
108
+ const target = el;
109
+ requestAnimationFrame(() => {
110
+ target.style.maxHeight = target.scrollHeight === 0 ? "0" : `${target.scrollHeight}px`;
111
+ target.style.overflow = "hidden";
112
+ });
113
+ },
114
+ onAfterEnter(el) {
115
+ resetCollapseStyle(el);
116
+ },
117
+ onEnterCancelled(el) {
118
+ resetCollapseStyle(el);
119
+ },
120
+ onBeforeLeave(el) {
121
+ const target = el;
122
+ target.dataset.oldOverflow = target.style.overflow;
123
+ target.style.maxHeight = `${target.scrollHeight}px`;
124
+ target.style.overflow = "hidden";
125
+ },
126
+ onLeave(el) {
127
+ const target = el;
128
+ if (target.scrollHeight !== 0) target.style.maxHeight = "0";
129
+ },
130
+ onAfterLeave(el) {
131
+ resetCollapseStyle(el);
132
+ },
133
+ onLeaveCancelled(el) {
134
+ resetCollapseStyle(el);
135
+ }
136
+ };
137
+ const ProMenuContent = /* @__PURE__ */ defineComponent((props, { emit, slots }) => {
138
+ const { proClasses, proStyles, collapsed } = useLayoutBase(props);
139
+ const { prefixCls } = useBaseConfig("pro-menu", props);
140
+ const rootCls = useCSSVarCls(prefixCls);
141
+ const [hashId, cssVarCls] = style_default(prefixCls, rootCls);
39
142
  const { proLayoutToken } = useProToken();
40
143
  const mode = computed(() => props.mode ?? (props.placement === "header" ? "horizontal" : "inline"));
41
- const menuThemeTokens = computed(() => props.placement === "header" ? buildHeaderMenuTokens(proLayoutToken.value) : buildSiderMenuTokens(proLayoutToken.value));
42
- const menuClassNames = computed(() => classNames(`${prefixCls.value}-menu`, `${prefixCls.value}-menu-${props.placement ?? "sider"}`, cssVarCls.value, rootCls.value, proClasses.value.menu, props.class));
43
- const menuPopupClassName = computed(() => classNames(`${prefixCls.value}-menu-popup`, hashId.value, cssVarCls.value, rootCls.value));
44
- const inlineCollapsed = computed(() => props.placement === "sider" && mode.value === "inline" && collapsed.value);
45
- /**
46
- * 收起态时不要把 openKeys 受控给底层 Menu:
47
- * - UI 库在 collapsed / hover popup 场景自行维护临时展开态
48
- * - 避免它把 openKeys 清空后写回上层状态,导致再次展开时丢失原展开菜单
49
- */
50
- const mergedOpenKeys = computed(() => inlineCollapsed.value ? void 0 : props.openKeys);
51
- const menuItems = computed(() => buildMenuItems(props.menus ?? []));
52
- const routeByKey = computed(() => flattenItemRoutes(menuItems.value));
144
+ const isHorizontal = computed(() => mode.value === "horizontal");
145
+ const inlineCollapsed = computed(() => !isHorizontal.value && collapsed.value);
146
+ const popupOpenPath = ref([]);
147
+ const menuRef = ref();
148
+ const overflow = useMenuOverflow({
149
+ enabled: isHorizontal,
150
+ totalCount: computed(() => (props.menus ?? []).filter((route) => !route.meta?.hideInMenu).length)
151
+ });
152
+ const hasInitialScrolled = ref(false);
153
+ const popupCloseTimers = /* @__PURE__ */ new Map();
154
+ const pendingPopupCloseKeys = /* @__PURE__ */ new Set();
155
+ let initialScrollFrame;
156
+ const selectedKeySet = computed(() => new Set(props.selectedKeys ?? []));
157
+ const openKeySet = computed(() => new Set(props.openKeys ?? []));
53
158
  const customRenders = computed(() => getSlotsProps({
54
159
  slots,
55
160
  props,
56
- keys: LAYOUT_MENU_SLOT_KEYS,
161
+ keys: PRO_MENU_SLOT_KEYS,
57
162
  render: false
58
163
  }));
59
- function getRouteFromItem(item) {
60
- return item.route;
164
+ const menuClassNames = computed(() => classNames(`${prefixCls.value}`, `${prefixCls.value}-root`, proClasses.value.menu, hashId.value, cssVarCls.value, rootCls.value, {
165
+ [`${prefixCls.value}-collapsed`]: inlineCollapsed.value,
166
+ [`${prefixCls.value}-inline`]: !isHorizontal.value,
167
+ [`${prefixCls.value}-horizontal`]: isHorizontal.value
168
+ }, props.class));
169
+ const popupClassName = computed(() => classNames(`${prefixCls.value}-popover`, hashId.value, cssVarCls.value, rootCls.value));
170
+ function isPopupInteraction(ctx) {
171
+ return inlineCollapsed.value || ctx.popup;
172
+ }
173
+ function getPopupPath(route, ctx) {
174
+ return ctx.popup ? [...ctx.popupPath, route.name] : [route.name];
175
+ }
176
+ function isPopupPathOpen(path) {
177
+ return isPathPrefix(path, popupOpenPath.value);
178
+ }
179
+ function isSubMenuOpen(route, ctx) {
180
+ if (ctx.popup) return isPopupPathOpen(getPopupPath(route, ctx));
181
+ if (inlineCollapsed.value && ctx.depth === 0) return isPopupPathOpen([route.name]);
182
+ return openKeySet.value.has(route.name);
183
+ }
184
+ function getInlineIndent(route, ctx) {
185
+ return route.meta?.inlineIndent ?? ctx.inlineIndent;
186
+ }
187
+ function getItemStyle(route, ctx) {
188
+ const indent = getInlineIndent(route, ctx);
189
+ return {
190
+ "--pro-menu-level": ctx.depth,
191
+ "--pro-menu-indent": `${indent}px`
192
+ };
193
+ }
194
+ function emitOpenKeys(nextKeys) {
195
+ emit("openChange", nextKeys);
196
+ emit("update:openKeys", nextKeys);
197
+ }
198
+ function cancelPopupClose(path) {
199
+ (path ? Array.from({ length: path.length }, (_, index) => getPathKey(path.slice(0, index + 1))) : Array.from(popupCloseTimers.keys())).forEach((key) => {
200
+ const timer = popupCloseTimers.get(key);
201
+ if (timer !== void 0) {
202
+ clearTimeout(timer);
203
+ popupCloseTimers.delete(key);
204
+ }
205
+ pendingPopupCloseKeys.delete(key);
206
+ });
207
+ }
208
+ function openPopupPath(path) {
209
+ cancelPopupClose(path);
210
+ popupOpenPath.value = path;
211
+ }
212
+ function closePopupPath(path) {
213
+ const key = getPathKey(path);
214
+ pendingPopupCloseKeys.delete(key);
215
+ if (isSamePath(path, popupOpenPath.value)) {
216
+ const nextPath = path.slice(0, -1);
217
+ popupOpenPath.value = nextPath;
218
+ const nextKey = getPathKey(nextPath);
219
+ if (nextPath.length > 0 && pendingPopupCloseKeys.has(nextKey)) closePopupPath(nextPath);
220
+ return;
221
+ }
222
+ if (isPathPrefix(path, popupOpenPath.value)) pendingPopupCloseKeys.add(key);
223
+ }
224
+ function schedulePopupPathClose(path) {
225
+ const key = getPathKey(path);
226
+ cancelPopupClose(path);
227
+ const timer = setTimeout(() => {
228
+ popupCloseTimers.delete(key);
229
+ closePopupPath(path);
230
+ }, POPUP_CLOSE_DELAY);
231
+ popupCloseTimers.set(key, timer);
232
+ }
233
+ function clearPopupPath() {
234
+ cancelPopupClose();
235
+ pendingPopupCloseKeys.clear();
236
+ popupOpenPath.value = [];
61
237
  }
62
- const iconRender = (item) => {
63
- const route = getRouteFromItem(item);
64
- if (customRenders.value.menuItemIcon && route) return customRenders.value.menuItemIcon({
238
+ function handleSubMenuClick(route, ctx) {
239
+ if (route.meta?.disabled) return;
240
+ if (isPopupInteraction(ctx)) {
241
+ openPopupPath(getPopupPath(route, ctx));
242
+ return;
243
+ }
244
+ emitOpenKeys(openKeySet.value.has(route.name) ? (props.openKeys ?? []).filter((key) => key !== route.name) : [...props.openKeys ?? [], route.name]);
245
+ }
246
+ function handleItemClick(route, ctx) {
247
+ if (route.meta?.disabled) return;
248
+ const keyPath = [route.name, ...ctx.parentKeys].filter(Boolean);
249
+ const selectedKeys = [route.name];
250
+ emit("select", {
251
+ key: route.name,
252
+ keys: selectedKeys,
253
+ keyPath,
254
+ route
255
+ });
256
+ emit("update:selectedKeys", selectedKeys);
257
+ if (ctx.popup) clearPopupPath();
258
+ }
259
+ function handleKeydown(event, action) {
260
+ if (event.key !== "Enter" && event.key !== " ") return;
261
+ event.preventDefault();
262
+ action();
263
+ }
264
+ function scrollSelectedIntoView() {
265
+ if (hasInitialScrolled.value || hasProMenuInitialAutoScrolled || inlineCollapsed.value) return;
266
+ const menuEl = menuRef.value;
267
+ if (!menuEl) return;
268
+ const scrollContainer = props.getScrollContainer?.() ?? menuEl.closest(`.${prefixCls.value}-container`);
269
+ const selectedItem = menuEl.querySelector(`.${prefixCls.value}-item-selected`);
270
+ if (!scrollContainer || !selectedItem) return;
271
+ hasInitialScrolled.value = true;
272
+ hasProMenuInitialAutoScrolled = true;
273
+ const containerRect = scrollContainer.getBoundingClientRect();
274
+ const selectedRect = selectedItem.getBoundingClientRect();
275
+ if (selectedRect.top >= containerRect.top && selectedRect.bottom <= containerRect.bottom) return;
276
+ const nextTop = selectedRect.top - containerRect.top + scrollContainer.scrollTop - scrollContainer.clientHeight / 2 + selectedItem.offsetHeight / 2;
277
+ scrollContainer.scrollTo({
278
+ top: Math.max(0, nextTop),
279
+ behavior: "auto"
280
+ });
281
+ }
282
+ function scheduleInitialScroll() {
283
+ if (hasInitialScrolled.value || hasProMenuInitialAutoScrolled) return;
284
+ nextTick(() => {
285
+ if (hasInitialScrolled.value || hasProMenuInitialAutoScrolled) return;
286
+ if (initialScrollFrame !== void 0) cancelAnimationFrame(initialScrollFrame);
287
+ initialScrollFrame = requestAnimationFrame(() => {
288
+ initialScrollFrame = void 0;
289
+ scrollSelectedIntoView();
290
+ });
291
+ });
292
+ }
293
+ watch(() => [
294
+ props.menus?.length ?? 0,
295
+ props.selectedKeys?.join("|") ?? "",
296
+ props.openKeys?.join("|") ?? ""
297
+ ], scheduleInitialScroll, {
298
+ immediate: true,
299
+ flush: "post"
300
+ });
301
+ onBeforeUnmount(() => {
302
+ if (initialScrollFrame !== void 0) cancelAnimationFrame(initialScrollFrame);
303
+ cancelPopupClose();
304
+ });
305
+ function renderIcon(route, item, firstLevelCollapsed) {
306
+ if (customRenders.value.menuItemIcon) return customRenders.value.menuItemIcon({
65
307
  item,
66
308
  route
67
309
  });
68
- const icon = renderMenuIcon(item.icon ?? route?.meta?.icon);
69
- const iconNode = !icon && collapsed.value ? getMenuTitleSymbol(route?.meta?.title) : icon;
310
+ const icon = renderMenuIcon(getMenuRenderItemIcon(item, route));
311
+ const iconNode = !icon && firstLevelCollapsed ? getMenuTitleSymbol(route.meta?.title) : icon;
70
312
  if (!iconNode) return null;
71
- const iconClass = route?.meta?.classNames?.itemIcon;
72
- const iconStyle = route?.meta?.styles?.itemIcon;
73
- if (iconClass || iconStyle) return createVNode("span", {
74
- "class": iconClass,
75
- "style": iconStyle
313
+ return createVNode("span", {
314
+ "class": classNames(`${prefixCls.value}-icon`, route.meta?.classNames?.itemIcon),
315
+ "style": route.meta?.styles?.itemIcon
76
316
  }, [iconNode]);
77
- return iconNode;
78
- };
79
- const labelRender = (item) => {
80
- const route = getRouteFromItem(item);
81
- if (customRenders.value.menuItemLabel && route) return customRenders.value.menuItemLabel({
317
+ }
318
+ function renderLabel(route, item) {
319
+ const label = customRenders.value.menuItemLabel ? customRenders.value.menuItemLabel({
82
320
  item,
83
321
  route
84
- });
85
- const titleClass = route?.meta?.classNames?.itemTitle;
86
- const titleStyle = route?.meta?.styles?.itemTitle;
87
- const textClass = route?.meta?.classNames?.itemTitleText;
88
- const textStyle = route?.meta?.styles?.itemTitleText;
89
- const label = textClass || textStyle ? createVNode("span", {
90
- "class": textClass,
91
- "style": textStyle
322
+ }) : route.meta?.classNames?.itemTitleText || route.meta?.styles?.itemTitleText ? createVNode("span", {
323
+ "class": route.meta?.classNames?.itemTitleText,
324
+ "style": route.meta?.styles?.itemTitleText
92
325
  }, [item.label]) : item.label;
93
- if (titleClass || titleStyle) return createVNode("span", {
94
- "class": titleClass,
95
- "style": titleStyle
326
+ return createVNode("span", {
327
+ "class": classNames(`${prefixCls.value}-title`, route.meta?.classNames?.itemTitle),
328
+ "style": route.meta?.styles?.itemTitle
96
329
  }, [label]);
97
- return label;
98
- };
99
- const extraRender = (item) => {
100
- const route = getRouteFromItem(item);
101
- if (customRenders.value.menuItemExtra && route) return customRenders.value.menuItemExtra({
330
+ }
331
+ function renderExtra(route, item) {
332
+ if (customRenders.value.menuItemExtra) return customRenders.value.menuItemExtra({
102
333
  item,
103
334
  route
104
335
  });
105
- if (route?.meta?.rightContent) {
106
- const extra = route.meta.rightContent(route.meta);
107
- if (extra == null || extra === false) return null;
108
- return createVNode("span", {
109
- "class": route.meta.classNames?.itemRight,
110
- "style": route.meta.styles?.itemRight
111
- }, [extra]);
336
+ if (!route.meta?.rightContent) return null;
337
+ const extra = route.meta.rightContent(route.meta);
338
+ if (extra == null || extra === false) return null;
339
+ return createVNode("span", {
340
+ "class": classNames(`${prefixCls.value}-extra`, route.meta.classNames?.itemRight),
341
+ "style": route.meta.styles?.itemRight
342
+ }, [extra]);
343
+ }
344
+ function renderMenuItem(route, ctx) {
345
+ const item = toMenuRenderItem(route, "item");
346
+ const firstLevelCollapsed = inlineCollapsed.value && !ctx.popup && ctx.depth === 0;
347
+ const selected = selectedKeySet.value.has(route.name);
348
+ const disabled = !!route.meta?.disabled;
349
+ const showCollapsedTooltip = firstLevelCollapsed && !!route.meta?.title;
350
+ const itemNode = createVNode("div", {
351
+ "class": classNames(`${prefixCls.value}-item`, route.meta?.classNames?.item, {
352
+ [`${prefixCls.value}-item-selected`]: selected,
353
+ [`${prefixCls.value}-item-disabled`]: disabled,
354
+ [`${prefixCls.value}-item-collapsed`]: firstLevelCollapsed
355
+ }),
356
+ "style": {
357
+ ...getItemStyle(route, ctx),
358
+ ...route.meta?.styles?.item
359
+ },
360
+ "role": "menuitem",
361
+ "tabindex": disabled ? -1 : 0,
362
+ "onClick": () => handleItemClick(route, ctx),
363
+ "onKeydown": (event) => handleKeydown(event, () => handleItemClick(route, ctx))
364
+ }, [
365
+ renderIcon(route, item, firstLevelCollapsed),
366
+ !firstLevelCollapsed && renderLabel(route, item),
367
+ !firstLevelCollapsed && renderExtra(route, item)
368
+ ]);
369
+ return createVNode("li", {
370
+ "key": route.name,
371
+ "class": `${prefixCls.value}-node`,
372
+ "data-overflow-item": ctx.overflowTopLevel ? "" : void 0,
373
+ "style": ctx.overflowHidden ? OVERFLOW_HIDDEN_STYLE : void 0,
374
+ "role": "none"
375
+ }, [showCollapsedTooltip ? createVNode(Tooltip, {
376
+ "title": route.meta?.title,
377
+ "placement": "right",
378
+ "classes": { root: `${prefixCls.value}-inline-collapsed-tooltip` }
379
+ }, _isSlot(itemNode) ? itemNode : { default: () => [itemNode] }) : itemNode]);
380
+ }
381
+ function renderSubMenuTitle(route, ctx, opened, active) {
382
+ const item = toMenuRenderItem(route, "submenu");
383
+ const firstLevelCollapsed = inlineCollapsed.value && !ctx.popup && ctx.depth === 0;
384
+ const disabled = !!route.meta?.disabled;
385
+ return createVNode("div", {
386
+ "class": classNames(`${prefixCls.value}-item`, `${prefixCls.value}-submenu-title`, {
387
+ [`${prefixCls.value}-item-open`]: opened,
388
+ [`${prefixCls.value}-item-active`]: active,
389
+ [`${prefixCls.value}-item-disabled`]: disabled,
390
+ [`${prefixCls.value}-item-collapsed`]: firstLevelCollapsed,
391
+ [`${prefixCls.value}-submenu-title-popup`]: ctx.popup
392
+ }),
393
+ "style": getItemStyle(route, ctx),
394
+ "role": "menuitem",
395
+ "aria-expanded": opened,
396
+ "tabindex": disabled ? -1 : 0,
397
+ "title": firstLevelCollapsed ? route.meta?.title : void 0,
398
+ "onClick": () => handleSubMenuClick(route, ctx),
399
+ "onKeydown": (event) => handleKeydown(event, () => handleSubMenuClick(route, ctx))
400
+ }, [
401
+ renderIcon(route, item, firstLevelCollapsed),
402
+ !firstLevelCollapsed && renderLabel(route, item),
403
+ !firstLevelCollapsed && renderExtra(route, item),
404
+ !firstLevelCollapsed && createVNode("i", { "class": `${prefixCls.value}-arrow` }, null)
405
+ ]);
406
+ }
407
+ function renderSubMenu(route, children, ctx) {
408
+ let _slot;
409
+ const opened = isSubMenuOpen(route, ctx);
410
+ const active = hasSelectedDescendant(route, selectedKeySet.value);
411
+ const disabled = !!route.meta?.disabled;
412
+ const titleNode = renderSubMenuTitle(route, ctx, opened, active);
413
+ const popupPlacement = ctx.horizontal && ctx.depth === 0 && !ctx.popup ? "bottomLeft" : "rightTop";
414
+ const popupBuiltinPlacements = popupPlacement === "bottomLeft" ? HEADER_POPUP_BUILTIN_PLACEMENTS : ctx.popup ? SIDER_NESTED_POPUP_BUILTIN_PLACEMENTS : SIDER_POPUP_BUILTIN_PLACEMENTS;
415
+ const usePopup = ctx.popup || !ctx.popup && ctx.depth === 0 && (inlineCollapsed.value || ctx.horizontal);
416
+ const nextCtx = {
417
+ depth: ctx.depth + 1,
418
+ parentKeys: [route.name, ...ctx.parentKeys],
419
+ popup: ctx.popup,
420
+ popupPath: ctx.popupPath,
421
+ inlineIndent: getInlineIndent(route, ctx),
422
+ horizontal: ctx.horizontal
423
+ };
424
+ if (usePopup) {
425
+ const popupPath = getPopupPath(route, ctx);
426
+ const popoverOpen = isPopupPathOpen(popupPath);
427
+ return createVNode("li", {
428
+ "key": route.name,
429
+ "class": classNames(`${prefixCls.value}-node`, `${prefixCls.value}-submenu`, route.meta?.classNames?.subMenu, {
430
+ [`${prefixCls.value}-submenu-open`]: popoverOpen,
431
+ [`${prefixCls.value}-submenu-active`]: active
432
+ }),
433
+ "data-overflow-item": ctx.overflowTopLevel ? "" : void 0,
434
+ "style": {
435
+ ...ctx.overflowHidden ? OVERFLOW_HIDDEN_STYLE : null,
436
+ ...route.meta?.styles?.subMenu
437
+ },
438
+ "role": "none"
439
+ }, [createVNode(Popover, {
440
+ "trigger": "hover",
441
+ "placement": popupPlacement,
442
+ "builtinPlacements": popupBuiltinPlacements,
443
+ "arrow": false,
444
+ "open": popoverOpen,
445
+ "classes": {
446
+ root: classNames(popupClassName.value, `${prefixCls.value}-submenu-placement-${popupPlacement}`, `${prefixCls.value}-${ctx.horizontal ? "horizontal" : "inline"}-popover`, { [`${prefixCls.value}-submenu-placement-nested`]: ctx.popup }, route.meta?.classNames?.subMenuPopup),
447
+ container: `${prefixCls.value}-popover-container`,
448
+ content: `${prefixCls.value}-popover-content`
449
+ },
450
+ "onOpenChange": (open) => {
451
+ if (disabled) return;
452
+ if (open) openPopupPath(popupPath);
453
+ else schedulePopupPathClose(popupPath);
454
+ }
455
+ }, {
456
+ default: () => [titleNode],
457
+ content: () => createVNode("div", { "class": `${prefixCls.value}-submenu-popup` }, [renderMenuList(children, {
458
+ depth: 0,
459
+ parentKeys: [route.name, ...ctx.parentKeys],
460
+ popup: true,
461
+ popupPath,
462
+ inlineIndent: getInlineIndent(route, ctx),
463
+ horizontal: ctx.horizontal
464
+ })])
465
+ })]);
112
466
  }
113
- return null;
114
- };
467
+ return createVNode("li", {
468
+ "key": route.name,
469
+ "class": classNames(`${prefixCls.value}-node`, `${prefixCls.value}-submenu`, route.meta?.classNames?.subMenu, {
470
+ [`${prefixCls.value}-submenu-open`]: opened,
471
+ [`${prefixCls.value}-submenu-active`]: active
472
+ }),
473
+ "style": route.meta?.styles?.subMenu,
474
+ "role": "none"
475
+ }, [titleNode, createVNode(Transition, mergeProps({ "name": `${prefixCls.value}-collapse` }, collapseTransitionHooks), _isSlot(_slot = withDirectives(renderMenuList(children, nextCtx), [[vShow, opened]])) ? _slot : { default: () => [_slot] })]);
476
+ }
477
+ function renderGroup(route, children, ctx) {
478
+ const item = toMenuRenderItem(route, "group");
479
+ return createVNode("li", {
480
+ "key": route.name,
481
+ "class": classNames(`${prefixCls.value}-group`, route.meta?.classNames?.subMenu),
482
+ "style": route.meta?.styles?.subMenu,
483
+ "role": "presentation"
484
+ }, [createVNode("div", {
485
+ "class": `${prefixCls.value}-group-title`,
486
+ "style": getItemStyle(route, ctx)
487
+ }, [renderLabel(route, item)]), renderMenuList(children, {
488
+ depth: ctx.depth + 1,
489
+ parentKeys: [route.name, ...ctx.parentKeys],
490
+ popup: ctx.popup,
491
+ popupPath: ctx.popupPath,
492
+ inlineIndent: getInlineIndent(route, ctx),
493
+ horizontal: ctx.horizontal
494
+ })]);
495
+ }
496
+ function renderRoute(route, ctx) {
497
+ if (route.meta?.hideInMenu) return null;
498
+ const children = getVisibleChildren(route);
499
+ const divider = route.meta?.divider ? createVNode("li", {
500
+ "key": `${route.name}-divider`,
501
+ "class": `${prefixCls.value}-divider`,
502
+ "role": "separator"
503
+ }, null) : null;
504
+ const node = children.length > 0 ? route.meta?.menuItemType === "group" ? renderGroup(route, children, ctx) : renderSubMenu(route, children, ctx) : renderMenuItem(route, ctx);
505
+ return divider ? [divider, node] : node;
506
+ }
507
+ function renderMenuList(menus, ctx) {
508
+ return createVNode("ul", {
509
+ "class": classNames(`${prefixCls.value}`, {
510
+ [`${prefixCls.value}-inline`]: !ctx.horizontal,
511
+ [`${prefixCls.value}-horizontal`]: ctx.horizontal && !ctx.popup,
512
+ [`${prefixCls.value}-sub`]: ctx.depth > 0 || ctx.popup,
513
+ [`${prefixCls.value}-popup-list`]: ctx.popup,
514
+ [`${prefixCls.value}-${ctx.horizontal ? "horizontal" : "inline"}-popup-list`]: ctx.popup
515
+ }),
516
+ "role": ctx.popup ? "menu" : void 0
517
+ }, [menus.map((route) => renderRoute(route, ctx))]);
518
+ }
519
+ /** horizontal 模式溢出「...」触发器,popup 内容为被收起的一级项 */
520
+ function renderOverflowRest(allRoutes) {
521
+ const hiddenRoutes = overflow.hiddenIndexes.value.map((index) => allRoutes[index]).filter(Boolean);
522
+ if (hiddenRoutes.length === 0) return null;
523
+ const restActive = hiddenRoutes.some((route) => selectedKeySet.value.has(route.name) || hasSelectedDescendant(route, selectedKeySet.value));
524
+ const popupPath = [OVERFLOW_MENU_KEY];
525
+ const popoverOpen = isPopupPathOpen(popupPath);
526
+ return createVNode("li", {
527
+ "key": OVERFLOW_MENU_KEY,
528
+ "class": classNames(`${prefixCls.value}-node`, `${prefixCls.value}-submenu`, {
529
+ [`${prefixCls.value}-submenu-open`]: popoverOpen,
530
+ [`${prefixCls.value}-submenu-active`]: restActive
531
+ }),
532
+ "data-overflow-rest": "",
533
+ "role": "none"
534
+ }, [createVNode(Popover, {
535
+ "trigger": "hover",
536
+ "placement": "bottomLeft",
537
+ "builtinPlacements": HEADER_POPUP_BUILTIN_PLACEMENTS,
538
+ "arrow": false,
539
+ "open": popoverOpen,
540
+ "classes": {
541
+ root: classNames(popupClassName.value, `${prefixCls.value}-submenu-placement-bottomLeft`, `${prefixCls.value}-horizontal-popover`),
542
+ container: `${prefixCls.value}-popover-container`,
543
+ content: `${prefixCls.value}-popover-content`
544
+ },
545
+ "onOpenChange": (open) => {
546
+ if (open) openPopupPath(popupPath);
547
+ else schedulePopupPathClose(popupPath);
548
+ }
549
+ }, {
550
+ default: () => [createVNode("div", {
551
+ "class": classNames(`${prefixCls.value}-item`, `${prefixCls.value}-submenu-title`, { [`${prefixCls.value}-item-active`]: restActive }),
552
+ "role": "menuitem",
553
+ "tabindex": 0
554
+ }, [createVNode("span", { "class": `${prefixCls.value}-title` }, [createTextVNode("···")])])],
555
+ content: () => createVNode("div", { "class": `${prefixCls.value}-submenu-popup` }, [renderMenuList(hiddenRoutes, {
556
+ depth: 0,
557
+ parentKeys: [],
558
+ popup: true,
559
+ popupPath,
560
+ inlineIndent: DEFAULT_INLINE_INDENT,
561
+ horizontal: true
562
+ })])
563
+ })]);
564
+ }
115
565
  return () => {
116
- const items = menuItems.value;
117
- if (items.length === 0) return null;
118
- return createVNode(ConfigProvider, { "theme": { components: { Menu: menuThemeTokens.value } } }, { default: () => [createVNode(Menu, {
566
+ const menus = props.menus ?? [];
567
+ if (menus.length === 0) return null;
568
+ if (!isHorizontal.value) return createVNode("ul", {
569
+ "ref": menuRef,
119
570
  "class": menuClassNames.value,
120
571
  "style": {
121
572
  ...props.style,
122
573
  ...proStyles.value.menu
123
574
  },
124
- "classes": { popup: { root: menuPopupClassName.value } },
125
- "mode": mode.value,
126
- "theme": props.theme,
127
- "items": items,
128
- "selectedKeys": props.selectedKeys,
129
- "openKeys": mergedOpenKeys.value,
130
- "inlineCollapsed": inlineCollapsed.value,
131
- "iconRender": iconRender,
132
- "labelRender": labelRender,
133
- "extraRender": extraRender,
134
- "onSelect": (info) => {
135
- const key = String(info.key);
136
- const route = routeByKey.value.get(key);
137
- emit("select", {
138
- key,
139
- keys: info.selectedKeys,
140
- keyPath: info.keyPath,
141
- route
142
- });
143
- emit("update:selectedKeys", info.selectedKeys);
575
+ "role": "menu",
576
+ "data-pro-menu-token-key": proLayoutToken.value.key
577
+ }, [menus.map((route) => renderRoute(route, {
578
+ depth: 0,
579
+ parentKeys: [],
580
+ popup: false,
581
+ popupPath: [],
582
+ inlineIndent: DEFAULT_INLINE_INDENT,
583
+ horizontal: false
584
+ }))]);
585
+ const visibleMenus = menus.filter((route) => !route.meta?.hideInMenu);
586
+ return createVNode("ul", {
587
+ "ref": overflow.containerRef,
588
+ "class": classNames(menuClassNames.value, `${prefixCls.value}-overflow`),
589
+ "style": {
590
+ ...props.style,
591
+ ...proStyles.value.menu
144
592
  },
145
- "onOpenChange": (openKeys) => {
146
- if (inlineCollapsed.value) return;
147
- emit("openChange", openKeys);
148
- emit("update:openKeys", openKeys);
149
- }
150
- }, null)] });
593
+ "role": "menubar",
594
+ "data-pro-menu-token-key": proLayoutToken.value.key
595
+ }, [visibleMenus.map((route, index) => renderRoute(route, {
596
+ depth: 0,
597
+ parentKeys: [],
598
+ popup: false,
599
+ popupPath: [],
600
+ inlineIndent: DEFAULT_INLINE_INDENT,
601
+ horizontal: true,
602
+ overflowTopLevel: true,
603
+ overflowHidden: overflow.isItemHidden(index)
604
+ })), overflow.restVisible.value && renderOverflowRest(visibleMenus)]);
151
605
  };
152
606
  }, {
153
607
  props: {
608
+ prefixCls: {
609
+ type: String,
610
+ required: false
611
+ },
612
+ placement: {
613
+ type: String,
614
+ required: false
615
+ },
616
+ getScrollContainer: {
617
+ type: Function,
618
+ required: false
619
+ },
154
620
  class: {
155
621
  type: String,
156
622
  required: false
@@ -159,9 +625,99 @@ const LayoutMenu = /* @__PURE__ */ defineComponent((props, { emit, slots }) => {
159
625
  type: Object,
160
626
  required: false
161
627
  },
628
+ menus: {
629
+ type: Array,
630
+ required: false
631
+ },
632
+ selectedKeys: {
633
+ type: Array,
634
+ required: false
635
+ },
636
+ openKeys: {
637
+ type: Array,
638
+ required: false
639
+ },
640
+ theme: {
641
+ type: String,
642
+ required: false
643
+ },
644
+ mode: {
645
+ type: String,
646
+ required: false
647
+ },
648
+ collapsed: {
649
+ type: Boolean,
650
+ required: false,
651
+ default: void 0
652
+ },
653
+ proClasses: {
654
+ type: Object,
655
+ required: false
656
+ },
657
+ proStyles: {
658
+ type: Object,
659
+ required: false
660
+ },
661
+ menuItemIcon: {
662
+ type: Function,
663
+ required: false
664
+ },
665
+ menuItemLabel: {
666
+ type: Function,
667
+ required: false
668
+ },
669
+ menuItemExtra: {
670
+ type: Function,
671
+ required: false
672
+ }
673
+ },
674
+ emits: [
675
+ "select",
676
+ "openChange",
677
+ "update:selectedKeys",
678
+ "update:openKeys"
679
+ ],
680
+ name: "ProMenuContent",
681
+ inheritAttrs: false
682
+ });
683
+ const ProMenu = /* @__PURE__ */ defineComponent((props, { emit, slots }) => {
684
+ const { token, proLayoutToken } = useProToken();
685
+ const mode = computed(() => props.mode ?? (props.placement === "header" ? "horizontal" : "inline"));
686
+ const isHorizontal = computed(() => mode.value === "horizontal");
687
+ const proMenuTokens = computed(() => isHorizontal.value ? buildHeaderProMenuTokens(proLayoutToken.value, token.value.Menu) : buildSiderProMenuTokens(proLayoutToken.value, token.value.Menu));
688
+ return () => createVNode(ConfigProvider, { "theme": { components: { ProMenu: proMenuTokens.value } } }, { default: () => [createVNode(ProMenuContent, mergeProps(omit(props, [
689
+ "onSelect",
690
+ "onOpenChange",
691
+ "onUpdate:selectedKeys",
692
+ "onUpdate:openKeys"
693
+ ]), {
694
+ "onSelect": (info) => emit("select", info),
695
+ "onOpenChange": (keys) => emit("openChange", keys)
696
+ }, {
697
+ "onUpdate:selectedKeys": (keys) => emit("update:selectedKeys", keys),
698
+ "onUpdate:openKeys": (keys) => emit("update:openKeys", keys)
699
+ }), slots)] });
700
+ }, {
701
+ props: {
702
+ prefixCls: {
703
+ type: String,
704
+ required: false
705
+ },
162
706
  placement: {
163
707
  type: String,
164
- required: true
708
+ required: false
709
+ },
710
+ getScrollContainer: {
711
+ type: Function,
712
+ required: false
713
+ },
714
+ class: {
715
+ type: String,
716
+ required: false
717
+ },
718
+ style: {
719
+ type: Object,
720
+ required: false
165
721
  },
166
722
  menus: {
167
723
  type: Array,
@@ -215,8 +771,8 @@ const LayoutMenu = /* @__PURE__ */ defineComponent((props, { emit, slots }) => {
215
771
  "update:selectedKeys",
216
772
  "update:openKeys"
217
773
  ],
218
- name: "LayoutMenu",
774
+ name: "ProMenu",
219
775
  inheritAttrs: false
220
776
  });
221
777
  //#endregion
222
- export { LayoutMenu as default };
778
+ export { ProMenu as default };