@geotab/zenith 3.10.0 → 3.11.0-beta.1
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.
- package/README.md +6 -0
- package/dist/index.css +8 -1
- package/dist/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
- package/dist/menu/components/controlledMenuList/controlledMenuList.js +123 -0
- package/dist/menu/components/createControlledMenuList.d.ts +37 -0
- package/dist/menu/components/createControlledMenuList.js +55 -0
- package/dist/menu/components/createMenuItem.d.ts +67 -0
- package/dist/menu/components/createMenuItem.js +97 -0
- package/dist/menu/components/menuButton.js +8 -2
- package/dist/menu/components/menuItem.d.ts +1 -2
- package/dist/menu/components/menuItem.js +20 -74
- package/dist/menu/contexts/usePathContext.d.ts +2 -0
- package/dist/menu/contexts/usePathContext.js +9 -0
- package/dist/menu/controlledMenu.js +8 -175
- package/dist/menu/utils/buildMenuContent.d.ts +2 -0
- package/dist/menu/utils/buildMenuContent.js +38 -0
- package/dist/menu/utils/findContent.d.ts +2 -2
- package/dist/menu/utils/findContent.js +4 -3
- package/dist/menu/utils/getItemLabel.d.ts +2 -0
- package/dist/menu/utils/getItemLabel.js +8 -0
- package/dist/menu/utils/getSafeRel.d.ts +1 -0
- package/dist/menu/utils/getSafeRel.js +14 -0
- package/dist/menu/utils/isMenuItem.d.ts +2 -0
- package/dist/menu/utils/isMenuItem.js +13 -0
- package/dist/menu/utils/isSafeHref.d.ts +1 -0
- package/dist/menu/utils/isSafeHref.js +10 -0
- package/dist/menu/utils/normalizeSeparators.d.ts +2 -0
- package/dist/menu/utils/normalizeSeparators.js +23 -0
- package/dist/menu/utils/resolveKeys.d.ts +12 -0
- package/dist/menu/utils/resolveKeys.js +22 -0
- package/dist/menu/utils/useLastValidSheet.d.ts +7 -0
- package/dist/menu/utils/useLastValidSheet.js +30 -0
- package/dist/menu/utils/useMenuItemCore.d.ts +31 -0
- package/dist/menu/utils/useMenuItemCore.js +51 -0
- package/dist/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
- package/dist/menu/utils/useMenuItemKeyboardNav.js +15 -0
- package/dist/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
- package/dist/menu/utils/useMenuListKeyboardNav.js +77 -0
- package/dist/menu/utils/useMenuPath.d.ts +6 -0
- package/dist/menu/utils/useMenuPath.js +35 -0
- package/dist/nav/navItem/navItem.js +6 -4
- package/dist/nav/navSection/navSection.js +7 -5
- package/esm/menu/components/controlledMenuList/controlledMenuList.d.ts +27 -0
- package/esm/menu/components/controlledMenuList/controlledMenuList.js +120 -0
- package/esm/menu/components/createControlledMenuList.d.ts +37 -0
- package/esm/menu/components/createControlledMenuList.js +51 -0
- package/esm/menu/components/createMenuItem.d.ts +67 -0
- package/esm/menu/components/createMenuItem.js +93 -0
- package/esm/menu/components/menuButton.js +8 -2
- package/esm/menu/components/menuItem.d.ts +1 -2
- package/esm/menu/components/menuItem.js +20 -74
- package/esm/menu/contexts/usePathContext.d.ts +2 -0
- package/esm/menu/contexts/usePathContext.js +5 -0
- package/esm/menu/controlledMenu.js +10 -177
- package/esm/menu/utils/buildMenuContent.d.ts +2 -0
- package/esm/menu/utils/buildMenuContent.js +34 -0
- package/esm/menu/utils/findContent.d.ts +2 -2
- package/esm/menu/utils/findContent.js +4 -3
- package/esm/menu/utils/getItemLabel.d.ts +2 -0
- package/esm/menu/utils/getItemLabel.js +4 -0
- package/esm/menu/utils/getSafeRel.d.ts +1 -0
- package/esm/menu/utils/getSafeRel.js +10 -0
- package/esm/menu/utils/isMenuItem.d.ts +2 -0
- package/esm/menu/utils/isMenuItem.js +9 -0
- package/esm/menu/utils/isSafeHref.d.ts +1 -0
- package/esm/menu/utils/isSafeHref.js +6 -0
- package/esm/menu/utils/normalizeSeparators.d.ts +2 -0
- package/esm/menu/utils/normalizeSeparators.js +19 -0
- package/esm/menu/utils/resolveKeys.d.ts +12 -0
- package/esm/menu/utils/resolveKeys.js +18 -0
- package/esm/menu/utils/useLastValidSheet.d.ts +7 -0
- package/esm/menu/utils/useLastValidSheet.js +26 -0
- package/esm/menu/utils/useMenuItemCore.d.ts +31 -0
- package/esm/menu/utils/useMenuItemCore.js +47 -0
- package/esm/menu/utils/useMenuItemKeyboardNav.d.ts +2 -0
- package/esm/menu/utils/useMenuItemKeyboardNav.js +11 -0
- package/esm/menu/utils/useMenuListKeyboardNav.d.ts +12 -0
- package/esm/menu/utils/useMenuListKeyboardNav.js +73 -0
- package/esm/menu/utils/useMenuPath.d.ts +6 -0
- package/esm/menu/utils/useMenuPath.js +31 -0
- package/esm/nav/navItem/navItem.js +6 -4
- package/esm/nav/navSection/navSection.js +7 -5
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePathContext = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const pathContext_1 = require("./pathContext");
|
|
6
|
+
function usePathContext() {
|
|
7
|
+
return (0, react_1.useContext)(pathContext_1.PathContext);
|
|
8
|
+
}
|
|
9
|
+
exports.usePathContext = usePathContext;
|
|
@@ -2,47 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TRANSLATIONS = exports.ControlledMenu = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
|
|
6
5
|
const react_1 = require("react");
|
|
7
|
-
const menuItem_1 = require("./components/menuItem");
|
|
8
6
|
const classNames_1 = require("../commonHelpers/classNames/classNames");
|
|
9
|
-
const findContent_1 = require("./utils/findContent");
|
|
10
|
-
const findFirstFocusable_1 = require("./utils/findFirstFocusable");
|
|
11
|
-
const findLastFocusable_1 = require("./utils/findLastFocusable");
|
|
12
|
-
const findNextFocusable_1 = require("./utils/findNextFocusable");
|
|
13
|
-
const isButton_1 = require("./utils/isButton");
|
|
14
|
-
const isLink_1 = require("./utils/isLink");
|
|
15
|
-
const findPrevFocusable_1 = require("./utils/findPrevFocusable");
|
|
16
|
-
const menuButton_1 = require("./components/menuButton");
|
|
17
|
-
const iconArrowLeft_1 = require("../icons/iconArrowLeft");
|
|
18
7
|
const controlledPopup_1 = require("../controlledPopup/controlledPopup");
|
|
19
8
|
const mobileSheet_1 = require("../mobileSheet/mobileSheet");
|
|
20
9
|
const deviceType_1 = require("../commonHelpers/hooks/deviceType");
|
|
21
10
|
const useDeviceType_1 = require("../commonHelpers/hooks/useDeviceType");
|
|
22
|
-
const generateId_1 = require("../commonHelpers/generateId");
|
|
23
|
-
const pathProvider_1 = require("./contexts/pathProvider");
|
|
24
|
-
const menuSeparator_1 = require("./components/menuSeparator");
|
|
25
11
|
const focusableSelector_1 = require("../utils/focusableSelector");
|
|
12
|
+
const controlledMenuList_1 = require("./components/controlledMenuList/controlledMenuList");
|
|
13
|
+
const EmbeddedMenuList = controlledMenuList_1.ControlledMenuList;
|
|
26
14
|
const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ariaLabelledby, id, title, className = "", listClassName = "", paddingX = 0, paddingY = 0, alignment, closeOnScroll = true }) => {
|
|
27
15
|
const [deviceType, setDeviceType] = (0, react_1.useState)(deviceType_1.DeviceType.Desktop);
|
|
28
16
|
const isMobile = deviceType === deviceType_1.DeviceType.Mobile;
|
|
29
17
|
const memoizedOnChange = (0, react_1.useCallback)(setIsOpen, [setIsOpen]);
|
|
30
18
|
(0, useDeviceType_1.useDeviceType)(setDeviceType);
|
|
31
19
|
const menuListRef = (0, react_1.useRef)(null);
|
|
32
|
-
const [path, setPath] = (0, react_1.useState)([]);
|
|
33
|
-
// Track if the trigger was activated via keyboard
|
|
34
20
|
const openedViaKeyboardRef = (0, react_1.useRef)(false);
|
|
35
|
-
// Track if submenu was navigated via keyboard (ArrowRight)
|
|
36
|
-
const navigatedViaKeyboardRef = (0, react_1.useRef)(false);
|
|
37
|
-
// Track if keyboard is actively being used for navigation (vs mouse/touch)
|
|
38
|
-
const keyboardActiveRef = (0, react_1.useRef)(false);
|
|
39
|
-
// Listen for keyboard activation on trigger
|
|
40
21
|
(0, react_1.useEffect)(() => {
|
|
41
22
|
const trigger = triggerRef.current;
|
|
42
23
|
if (!trigger)
|
|
43
24
|
return undefined;
|
|
44
25
|
const handleKeyDown = (e) => {
|
|
45
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
26
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowRight") {
|
|
46
27
|
openedViaKeyboardRef.current = true;
|
|
47
28
|
}
|
|
48
29
|
};
|
|
@@ -56,12 +37,6 @@ const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ar
|
|
|
56
37
|
trigger.removeEventListener("mousedown", handleMouseDown);
|
|
57
38
|
};
|
|
58
39
|
}, [triggerRef]);
|
|
59
|
-
(0, react_1.useEffect)(() => {
|
|
60
|
-
if (path.length && !isOpen) {
|
|
61
|
-
setPath([]);
|
|
62
|
-
}
|
|
63
|
-
}, [isOpen, path, setPath]);
|
|
64
|
-
// Focus the menu list container or first item when menu opens (for keyboard navigation)
|
|
65
40
|
(0, react_1.useEffect)(() => {
|
|
66
41
|
var _a;
|
|
67
42
|
if (isOpen && !isMobile && menuListRef.current) {
|
|
@@ -73,157 +48,15 @@ const ControlledMenu = ({ children, isOpen, setIsOpen, triggerRef, ariaLabel, ar
|
|
|
73
48
|
}
|
|
74
49
|
}
|
|
75
50
|
}, [isOpen, isMobile]);
|
|
76
|
-
const onOpenBranch = (0, react_1.useCallback)((branchId) => {
|
|
77
|
-
if (!branchId) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (!path.includes(branchId)) {
|
|
81
|
-
setPath([...path, branchId]);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (path.includes(branchId)) {
|
|
85
|
-
setPath(v => {
|
|
86
|
-
const newPath = [...v];
|
|
87
|
-
newPath.pop();
|
|
88
|
-
return newPath;
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}, [setPath, path]);
|
|
92
|
-
const closeBranch = (0, react_1.useCallback)(() => {
|
|
93
|
-
setPath(v => {
|
|
94
|
-
const newPath = [...v];
|
|
95
|
-
newPath.pop();
|
|
96
|
-
return newPath;
|
|
97
|
-
});
|
|
98
|
-
}, [setPath]);
|
|
99
|
-
const [content, parent] = (0, react_1.useMemo)(() => {
|
|
100
|
-
let par = null;
|
|
101
|
-
let currentChildren = children;
|
|
102
|
-
if (isMobile && path.length > 0) {
|
|
103
|
-
const el = (0, findContent_1.findContent)(children, exports.ControlledMenu.Item, path[path.length - 1]);
|
|
104
|
-
if (el || (0, react_1.isValidElement)(el)) {
|
|
105
|
-
const elProps = el.props;
|
|
106
|
-
currentChildren = elProps.children;
|
|
107
|
-
par = el;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
let cont = [];
|
|
111
|
-
react_1.Children.map(currentChildren, (child) => {
|
|
112
|
-
if (!child) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (typeof child === "string") {
|
|
116
|
-
cont.push((0, jsx_runtime_1.jsx)("li", { role: "presentation", className: (0, classNames_1.classNames)(["zen-menu-item__content"]), children: child }, (0, generateId_1.generateId)()));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if ((0, react_1.isValidElement)(child) && (0, menuSeparator_1.isSeparator)(child)) {
|
|
120
|
-
const clone = (0, react_1.cloneElement)(child, {
|
|
121
|
-
key: child.props.key || (0, generateId_1.generateId)()
|
|
122
|
-
});
|
|
123
|
-
cont.push(clone);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if ((0, react_1.isValidElement)(child) && (0, menuItem_1.isMenuItem)(child)) {
|
|
127
|
-
const clone = (0, react_1.cloneElement)(child, {
|
|
128
|
-
isMobile,
|
|
129
|
-
key: child.props.id,
|
|
130
|
-
setIsOpen,
|
|
131
|
-
onClick: child.props.onClick
|
|
132
|
-
});
|
|
133
|
-
cont.push(clone);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const childProps = child.props;
|
|
137
|
-
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content"]), role: "presentation", children: child }, childProps.id || childProps["data-id"] || (0, generateId_1.generateId)()));
|
|
138
|
-
});
|
|
139
|
-
while (cont[0] && (0, menuSeparator_1.isSeparator)(cont[0])) {
|
|
140
|
-
cont.shift();
|
|
141
|
-
}
|
|
142
|
-
while (cont[cont.length - 1] && (0, menuSeparator_1.isSeparator)(cont[cont.length - 1])) {
|
|
143
|
-
cont.pop();
|
|
144
|
-
}
|
|
145
|
-
cont = cont.filter((el, indx, arr) => {
|
|
146
|
-
if ((0, menuSeparator_1.isSeparator)(el) && arr[indx - 1] && (0, menuSeparator_1.isSeparator)(arr[indx - 1])) {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
return true;
|
|
150
|
-
});
|
|
151
|
-
return [cont, par];
|
|
152
|
-
}, [children, isMobile, path, setIsOpen]);
|
|
153
|
-
// Handle keyboard navigation when no menu item is focused (e.g., when menu just opened)
|
|
154
|
-
const handleUnfocusedKeyDown = (e, menuList) => {
|
|
155
|
-
var _a, _b;
|
|
156
|
-
if (e.key === "ArrowDown" || e.key === "Home") {
|
|
157
|
-
e.preventDefault();
|
|
158
|
-
(_a = (0, findFirstFocusable_1.findFirstFocusable)(menuList)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
if (e.key === "ArrowUp" || e.key === "End") {
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
(_b = (0, findLastFocusable_1.findLastFocusable)(menuList)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return false;
|
|
167
|
-
};
|
|
168
|
-
const onMouseDown = () => {
|
|
169
|
-
keyboardActiveRef.current = false;
|
|
170
|
-
};
|
|
171
|
-
const onKeyDown = e => {
|
|
172
|
-
var _a, _b, _c, _d;
|
|
173
|
-
keyboardActiveRef.current = true;
|
|
174
|
-
const target = e.target;
|
|
175
|
-
const currentTarget = e.currentTarget;
|
|
176
|
-
if (!(0, isButton_1.isButton)(target) && !(0, isLink_1.isLink)(target)) {
|
|
177
|
-
if (target === currentTarget) {
|
|
178
|
-
const menuList = currentTarget.querySelector("ul");
|
|
179
|
-
if (menuList) {
|
|
180
|
-
handleUnfocusedKeyDown(e, menuList);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (e.key === "ArrowDown") {
|
|
186
|
-
e.preventDefault();
|
|
187
|
-
(_a = (0, findNextFocusable_1.findNextFocusable)(target)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (e.key === "ArrowUp") {
|
|
191
|
-
e.preventDefault();
|
|
192
|
-
(_b = (0, findPrevFocusable_1.findPrevFocusable)(target)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (e.key === "Home") {
|
|
196
|
-
e.preventDefault();
|
|
197
|
-
(_c = (0, findFirstFocusable_1.findFirstFocusable)(target)) === null || _c === void 0 ? void 0 : _c.focus();
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (e.key === "End") {
|
|
201
|
-
e.preventDefault();
|
|
202
|
-
(_d = (0, findLastFocusable_1.findLastFocusable)(target)) === null || _d === void 0 ? void 0 : _d.focus();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if ((0, isButton_1.isButton)(target) &&
|
|
206
|
-
(e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") &&
|
|
207
|
-
target.classList.contains("zen-menu-button__action--has-children")) {
|
|
208
|
-
e.preventDefault();
|
|
209
|
-
navigatedViaKeyboardRef.current = true;
|
|
210
|
-
target.click();
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
const renderMenuList = () => ((0, jsx_runtime_1.jsx)("div", { ref: menuListRef, tabIndex: -1, onKeyDown: onKeyDown, onMouseDown: onMouseDown, className: (0, classNames_1.classNames)(["zen-action-list", className]), children: (0, jsx_runtime_1.jsxs)("ul", { role: "menu", className: (0, classNames_1.classNames)(["zen-menu-item", className, listClassName]), children: [parent ? ((0, jsx_runtime_1.jsx)(menuButton_1.MenuButton, { id: "root", name: parent.props.name || "", icon: iconArrowLeft_1.IconArrowLeft, onClick: closeBranch, hasChildren: false, disabled: false }, "root")) : null, content] }) }));
|
|
214
51
|
const hideMenu = (0, react_1.useCallback)(() => {
|
|
215
|
-
closeBranch();
|
|
216
52
|
setIsOpen(false);
|
|
217
|
-
}, [
|
|
53
|
+
}, [setIsOpen]);
|
|
218
54
|
if (isMobile) {
|
|
219
|
-
return ((0, jsx_runtime_1.
|
|
55
|
+
return ((0, jsx_runtime_1.jsxs)(mobileSheet_1.MobileSheet, { label: title, isOpen: isOpen, triggerRef: triggerRef, onHidePanel: hideMenu, onCloseClick: hideMenu, children: [(0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Title, { children: title }), (0, jsx_runtime_1.jsx)(mobileSheet_1.MobileSheet.Content, { children: (0, jsx_runtime_1.jsx)(EmbeddedMenuList, { ref: menuListRef, setIsOpen: setIsOpen, isOpen: isOpen, className: className, listClassName: listClassName, children: children }) })] }));
|
|
220
56
|
}
|
|
221
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
222
|
-
// focusOnOpen is false - ControlledMenu handles focus based on input method
|
|
223
|
-
// (keyboard vs mouse) in its own useEffect
|
|
224
|
-
focusOnOpen: false, children: renderMenuList() }) }) }));
|
|
57
|
+
return ((0, jsx_runtime_1.jsx)(controlledPopup_1.ControlledPopup, { id: id, useTrapFocusWithTrigger: "on", className: (0, classNames_1.classNames)(["zen-controlled-menu", className]), onOpenChange: memoizedOnChange, isOpen: isOpen, triggerRef: triggerRef, paddingX: paddingX, paddingY: paddingY, alignment: alignment, ariaLabelledby: ariaLabelledby, ariaLabel: ariaLabel || title, recalculateOnScroll: true, focusOnOpen: false, closeOnScroll: closeOnScroll, children: (0, jsx_runtime_1.jsx)(EmbeddedMenuList, { ref: menuListRef, setIsOpen: setIsOpen, isOpen: isOpen, className: className, listClassName: listClassName, children: children }) }));
|
|
225
58
|
};
|
|
226
59
|
exports.ControlledMenu = ControlledMenu;
|
|
227
|
-
exports.ControlledMenu.Item =
|
|
228
|
-
exports.ControlledMenu.Separator =
|
|
60
|
+
exports.ControlledMenu.Item = controlledMenuList_1.ControlledMenuList.Item;
|
|
61
|
+
exports.ControlledMenu.Separator = controlledMenuList_1.ControlledMenuList.Separator;
|
|
229
62
|
exports.TRANSLATIONS = ["Back"];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildMenuContent = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const classNames_1 = require("../../commonHelpers/classNames/classNames");
|
|
8
|
+
const menuSeparator_1 = require("../components/menuSeparator");
|
|
9
|
+
const isMenuItem_1 = require("./isMenuItem");
|
|
10
|
+
const generateId_1 = require("../../commonHelpers/generateId");
|
|
11
|
+
function buildMenuContent(children, isMobile, setIsOpen, className) {
|
|
12
|
+
const cont = [];
|
|
13
|
+
react_1.Children.map(children, (child) => {
|
|
14
|
+
if (!child) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (typeof child === "string") {
|
|
18
|
+
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content", className !== null && className !== void 0 ? className : ""]), role: "presentation", children: child }, (0, generateId_1.generateId)()));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if ((0, react_1.isValidElement)(child) && (0, menuSeparator_1.isSeparator)(child)) {
|
|
22
|
+
const clone = (0, react_1.cloneElement)(child, { key: child.props.key || (0, generateId_1.generateId)() });
|
|
23
|
+
cont.push(clone);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if ((0, isMenuItem_1.isMenuItem)(child)) {
|
|
27
|
+
const childProps = child.props;
|
|
28
|
+
const clone = (0, react_1.cloneElement)(child, { isMobile, key: childProps.id, setIsOpen });
|
|
29
|
+
cont.push(clone);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
33
|
+
const childProps = child.props;
|
|
34
|
+
cont.push((0, jsx_runtime_1.jsx)("li", { className: (0, classNames_1.classNames)(["zen-menu-item__content", className !== null && className !== void 0 ? className : ""]), role: "presentation", children: child }, childProps.id || childProps["data-id"] || (0, generateId_1.generateId)()));
|
|
35
|
+
});
|
|
36
|
+
return cont;
|
|
37
|
+
}
|
|
38
|
+
exports.buildMenuContent = buildMenuContent;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare const findContent: (children: ReactNode,
|
|
1
|
+
import { ReactNode, ReactElement } from "react";
|
|
2
|
+
export declare const findContent: (children: ReactNode, matcher: (child: ReactElement) => boolean, id?: string) => ReactElement | null;
|
|
@@ -5,19 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.findContent = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
|
-
const findContent = (children,
|
|
8
|
+
const findContent = (children, matcher, id) => {
|
|
9
9
|
let found = null;
|
|
10
10
|
react_1.default.Children.forEach(children, (child) => {
|
|
11
11
|
if (found || !react_1.default.isValidElement(child)) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const childProps = child.props;
|
|
15
|
-
|
|
15
|
+
const matches = matcher(child);
|
|
16
|
+
if (id ? matches && childProps.id === id : matches) {
|
|
16
17
|
found = child;
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
if (childProps.children) {
|
|
20
|
-
found = (0, exports.findContent)(childProps.children,
|
|
21
|
+
found = (0, exports.findContent)(childProps.children, matcher, id);
|
|
21
22
|
}
|
|
22
23
|
});
|
|
23
24
|
return found;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getItemLabel = void 0;
|
|
4
|
+
const getItemLabel = (el) => {
|
|
5
|
+
const props = el === null || el === void 0 ? void 0 : el.props;
|
|
6
|
+
return (props === null || props === void 0 ? void 0 : props.name) || (props === null || props === void 0 ? void 0 : props.title) || "";
|
|
7
|
+
};
|
|
8
|
+
exports.getItemLabel = getItemLabel;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getSafeRel: (rel?: string, target?: string) => string | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSafeRel = void 0;
|
|
4
|
+
const getSafeRel = (rel, target) => {
|
|
5
|
+
if (target !== "_blank")
|
|
6
|
+
return rel;
|
|
7
|
+
const parts = rel ? rel.split(/\s+/) : [];
|
|
8
|
+
if (!parts.includes("noopener"))
|
|
9
|
+
parts.push("noopener");
|
|
10
|
+
if (!parts.includes("noreferrer"))
|
|
11
|
+
parts.push("noreferrer");
|
|
12
|
+
return parts.join(" ");
|
|
13
|
+
};
|
|
14
|
+
exports.getSafeRel = getSafeRel;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isMenuItem = void 0;
|
|
4
|
+
const isMenuItem = (element) => {
|
|
5
|
+
if (!element || !element.type) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if ((typeof element.type === "object" || typeof element.type === "function") && "displayName" in element.type) {
|
|
9
|
+
return element.type.displayName === "MenuItem";
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
};
|
|
13
|
+
exports.isMenuItem = isMenuItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isSafeHref: (href?: string) => boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSafeHref = void 0;
|
|
4
|
+
const isSafeHref = (href) => {
|
|
5
|
+
if (!href)
|
|
6
|
+
return false;
|
|
7
|
+
const lower = href.toLowerCase().trimStart();
|
|
8
|
+
return !lower.startsWith("javascript:") && !lower.startsWith("data:") && !lower.startsWith("vbscript:");
|
|
9
|
+
};
|
|
10
|
+
exports.isSafeHref = isSafeHref;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeSeparators = void 0;
|
|
4
|
+
const menuSeparator_1 = require("../components/menuSeparator");
|
|
5
|
+
const normalizeSeparators = (items) => {
|
|
6
|
+
// Find the first non-separator index
|
|
7
|
+
let start = 0;
|
|
8
|
+
while (start < items.length && (0, menuSeparator_1.isSeparator)(items[start])) {
|
|
9
|
+
start++;
|
|
10
|
+
}
|
|
11
|
+
// Find the last non-separator index
|
|
12
|
+
let end = items.length - 1;
|
|
13
|
+
while (end >= start && (0, menuSeparator_1.isSeparator)(items[end])) {
|
|
14
|
+
end--;
|
|
15
|
+
}
|
|
16
|
+
// If all items are separators or array is empty
|
|
17
|
+
if (start > end) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
// Slice to remove leading/trailing separators and filter consecutive duplicates
|
|
21
|
+
return items.slice(start, end + 1).filter((el, i, arr) => !((0, menuSeparator_1.isSeparator)(el) && arr[i - 1] && (0, menuSeparator_1.isSeparator)(arr[i - 1])));
|
|
22
|
+
};
|
|
23
|
+
exports.normalizeSeparators = normalizeSeparators;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const verticalKeys: {
|
|
2
|
+
keyNext: string;
|
|
3
|
+
keyPrev: string;
|
|
4
|
+
keyOpenNested: string;
|
|
5
|
+
keyBack: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const resolveKeys: (target: HTMLElement, isHorizontal: boolean) => {
|
|
8
|
+
keyNext: string;
|
|
9
|
+
keyPrev: string;
|
|
10
|
+
keyOpenNested: string;
|
|
11
|
+
keyBack: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveKeys = exports.verticalKeys = void 0;
|
|
4
|
+
exports.verticalKeys = {
|
|
5
|
+
keyNext: "ArrowDown",
|
|
6
|
+
keyPrev: "ArrowUp",
|
|
7
|
+
keyOpenNested: "ArrowRight",
|
|
8
|
+
keyBack: "ArrowLeft"
|
|
9
|
+
};
|
|
10
|
+
const resolveKeys = (target, isHorizontal) => {
|
|
11
|
+
const inSubMenu = isHorizontal && !!target.closest('[class*="zen-controlled-menu-submenu"]');
|
|
12
|
+
if (inSubMenu) {
|
|
13
|
+
return exports.verticalKeys;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
keyNext: isHorizontal ? "ArrowRight" : "ArrowDown",
|
|
17
|
+
keyPrev: isHorizontal ? "ArrowLeft" : "ArrowUp",
|
|
18
|
+
keyOpenNested: isHorizontal ? "ArrowUp" : "ArrowRight",
|
|
19
|
+
keyBack: isHorizontal ? "ArrowDown" : "ArrowLeft"
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
exports.resolveKeys = resolveKeys;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
|
+
export declare const useLastValidSheet: (nestedContent: ReactElement[] | null, nestedParent: ReactElement | null, path: string[], children: ReactNode) => {
|
|
3
|
+
sheetContent: ReactElement<unknown, string | import("react").JSXElementConstructor<any>>[];
|
|
4
|
+
sheetParent: ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
5
|
+
sheetPathLength: number;
|
|
6
|
+
sheetParentName: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLastValidSheet = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const findContent_1 = require("./findContent");
|
|
6
|
+
const getItemLabel_1 = require("./getItemLabel");
|
|
7
|
+
const menuItem_1 = require("../components/menuItem");
|
|
8
|
+
// Preserves the last non-null nested sheet state so the MobileSheet has content
|
|
9
|
+
// to render during its closing animation after `nestedContent` goes null.
|
|
10
|
+
const useLastValidSheet = (nestedContent, nestedParent, path, children) => {
|
|
11
|
+
const lastNestedContentRef = (0, react_1.useRef)([]);
|
|
12
|
+
const lastNestedParentRef = (0, react_1.useRef)(null);
|
|
13
|
+
const lastPathLengthRef = (0, react_1.useRef)(0);
|
|
14
|
+
const lastSheetTitleRef = (0, react_1.useRef)("");
|
|
15
|
+
const rootEl = path.length > 0 ? (0, findContent_1.findContent)(children, menuItem_1.isMenuItem, path[0]) : null;
|
|
16
|
+
const currentSheetTitle = rootEl ? (0, getItemLabel_1.getItemLabel)(rootEl) : "";
|
|
17
|
+
if (nestedContent !== null) {
|
|
18
|
+
lastNestedContentRef.current = nestedContent;
|
|
19
|
+
lastNestedParentRef.current = nestedParent;
|
|
20
|
+
lastPathLengthRef.current = path.length;
|
|
21
|
+
lastSheetTitleRef.current = currentSheetTitle;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
sheetContent: nestedContent !== null ? nestedContent : lastNestedContentRef.current,
|
|
25
|
+
sheetParent: nestedContent !== null ? nestedParent : lastNestedParentRef.current,
|
|
26
|
+
sheetPathLength: nestedContent !== null ? path.length : lastPathLengthRef.current,
|
|
27
|
+
sheetParentName: nestedContent !== null ? currentSheetTitle : lastSheetTitleRef.current
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
exports.useLastValidSheet = useLastValidSheet;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { TAlignment } from "../../absolute/absolute";
|
|
3
|
+
export interface IUseMenuItemCoreParams {
|
|
4
|
+
id: string;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
alignment?: TAlignment;
|
|
8
|
+
isMobile?: boolean;
|
|
9
|
+
setIsOpen?: (v: boolean) => void;
|
|
10
|
+
onClick?: (id: string, e: React.MouseEvent) => void;
|
|
11
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Core menu item logic shared between MenuItem and createMenuItem.
|
|
15
|
+
* Consolidates context access, state computation, and callback memoization.
|
|
16
|
+
*/
|
|
17
|
+
export declare const useMenuItemCore: ({ id, children, className, alignment, isMobile, setIsOpen, onClick, onOpenChange }: IUseMenuItemCoreParams) => {
|
|
18
|
+
ref: import("react").RefObject<HTMLButtonElement | null>;
|
|
19
|
+
isOpen: boolean;
|
|
20
|
+
hasChildren: boolean;
|
|
21
|
+
content: ReactNode[];
|
|
22
|
+
openedViaKeyboard: boolean;
|
|
23
|
+
contentAlignment: TAlignment;
|
|
24
|
+
path: string[];
|
|
25
|
+
onOpenBranch: (id: string) => void;
|
|
26
|
+
closeBranch: () => void;
|
|
27
|
+
navigatedViaKeyboardRef: import("react").RefObject<boolean> | undefined;
|
|
28
|
+
handleDesktopActionClick: (e: React.MouseEvent) => void;
|
|
29
|
+
handleTriggerClick: () => void;
|
|
30
|
+
handleOpenChange: () => void;
|
|
31
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useMenuItemCore = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const pathContext_1 = require("../contexts/pathContext");
|
|
6
|
+
const headerContext_1 = require("../../header/headerContext");
|
|
7
|
+
const buildMenuContent_1 = require("./buildMenuContent");
|
|
8
|
+
const useMenuItemKeyboardNav_1 = require("./useMenuItemKeyboardNav");
|
|
9
|
+
/**
|
|
10
|
+
* Core menu item logic shared between MenuItem and createMenuItem.
|
|
11
|
+
* Consolidates context access, state computation, and callback memoization.
|
|
12
|
+
*/
|
|
13
|
+
const useMenuItemCore = ({ id, children, className, alignment, isMobile = false, setIsOpen, onClick, onOpenChange }) => {
|
|
14
|
+
const alignmentContext = (0, react_1.useContext)(headerContext_1.MenuAlignmentContext);
|
|
15
|
+
const contentAlignment = alignment || alignmentContext.alignment || "right-top";
|
|
16
|
+
const { path, onOpenBranch, closeBranch, navigatedViaKeyboardRef } = (0, react_1.useContext)(pathContext_1.PathContext);
|
|
17
|
+
const ref = (0, react_1.useRef)(null);
|
|
18
|
+
const content = (0, react_1.useMemo)(() => (0, buildMenuContent_1.buildMenuContent)(children, isMobile, setIsOpen, className), [children, isMobile, setIsOpen, className]);
|
|
19
|
+
const isOpen = (0, react_1.useMemo)(() => path.includes(id), [path, id]);
|
|
20
|
+
const hasChildren = content.length > 0;
|
|
21
|
+
const openedViaKeyboard = (0, useMenuItemKeyboardNav_1.useMenuItemKeyboardNav)(isOpen, navigatedViaKeyboardRef);
|
|
22
|
+
const handleDesktopActionClick = (0, react_1.useCallback)((e) => {
|
|
23
|
+
setIsOpen === null || setIsOpen === void 0 ? void 0 : setIsOpen(false);
|
|
24
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
25
|
+
}, [setIsOpen, onClick, id]);
|
|
26
|
+
const handleTriggerClick = (0, react_1.useCallback)(() => {
|
|
27
|
+
onOpenBranch(id);
|
|
28
|
+
}, [id, onOpenBranch]);
|
|
29
|
+
const handleOpenChange = (0, react_1.useCallback)(() => {
|
|
30
|
+
closeBranch();
|
|
31
|
+
}, [closeBranch]);
|
|
32
|
+
(0, react_1.useEffect)(() => {
|
|
33
|
+
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(isOpen);
|
|
34
|
+
}, [isOpen, onOpenChange]);
|
|
35
|
+
return {
|
|
36
|
+
ref,
|
|
37
|
+
isOpen,
|
|
38
|
+
hasChildren,
|
|
39
|
+
content,
|
|
40
|
+
openedViaKeyboard,
|
|
41
|
+
contentAlignment,
|
|
42
|
+
path,
|
|
43
|
+
onOpenBranch,
|
|
44
|
+
closeBranch,
|
|
45
|
+
navigatedViaKeyboardRef,
|
|
46
|
+
handleDesktopActionClick,
|
|
47
|
+
handleTriggerClick,
|
|
48
|
+
handleOpenChange
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
exports.useMenuItemCore = useMenuItemCore;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useMenuItemKeyboardNav = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useMenuItemKeyboardNav(isOpen, navigatedViaKeyboardRef) {
|
|
6
|
+
const wasOpenRef = (0, react_1.useRef)(false);
|
|
7
|
+
const localOpenedViaKeyboardRef = (0, react_1.useRef)(false);
|
|
8
|
+
if (isOpen && !wasOpenRef.current && navigatedViaKeyboardRef) {
|
|
9
|
+
localOpenedViaKeyboardRef.current = navigatedViaKeyboardRef.current;
|
|
10
|
+
navigatedViaKeyboardRef.current = false;
|
|
11
|
+
}
|
|
12
|
+
wasOpenRef.current = isOpen;
|
|
13
|
+
return localOpenedViaKeyboardRef.current;
|
|
14
|
+
}
|
|
15
|
+
exports.useMenuItemKeyboardNav = useMenuItemKeyboardNav;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MutableRefObject } from "react";
|
|
2
|
+
export interface IKeyMap {
|
|
3
|
+
keyNext: string;
|
|
4
|
+
keyPrev: string;
|
|
5
|
+
keyOpenNested: string;
|
|
6
|
+
keyBack: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const useMenuListKeyboardNav: (keyboardActiveRef: MutableRefObject<boolean>, navigatedViaKeyboardRef: MutableRefObject<boolean>, isHorizontal: boolean) => {
|
|
9
|
+
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
10
|
+
onKeyDownVertical: (e: React.KeyboardEvent) => void;
|
|
11
|
+
onMouseDown: () => void;
|
|
12
|
+
};
|