@launchpad-ui/menu 0.13.56 → 0.13.58

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/dist/index.js CHANGED
@@ -1,441 +1,403 @@
1
1
  require('./style.css');
2
2
  "use strict";
3
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
- const jsxRuntime = require("react/jsx-runtime");
5
- const classix = require("classix");
6
- const react = require("react");
7
- const separator = require("@react-aria/separator");
8
- const tooltip = require("@launchpad-ui/tooltip");
9
- const reactSlot = require("@radix-ui/react-slot");
10
- const focus = require("@react-aria/focus");
11
- const form = require("@launchpad-ui/form");
12
- const reactVirtual = require("react-virtual");
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ const classix = __toESM(require("classix"));
24
+ const react = __toESM(require("react"));
25
+ const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
26
+ const __react_aria_separator = __toESM(require("@react-aria/separator"));
27
+ const __launchpad_ui_tooltip = __toESM(require("@launchpad-ui/tooltip"));
28
+ const __radix_ui_react_slot = __toESM(require("@radix-ui/react-slot"));
29
+ const __react_aria_focus = __toESM(require("@react-aria/focus"));
30
+ const __launchpad_ui_form = __toESM(require("@launchpad-ui/form"));
31
+ const react_virtual = __toESM(require("react-virtual"));
13
32
  const Menu$1 = "C73R2W_Menu";
14
- const styles = {
15
- "has-focus": "C73R2W_has-focus",
16
- "is-highlighted": "C73R2W_is-highlighted",
17
- Menu: Menu$1,
18
- "Menu--isVirtual": "C73R2W_Menu--isVirtual",
19
- "Menu-divider": "C73R2W_Menu-divider",
20
- "Menu-item": "C73R2W_Menu-item",
21
- "Menu-item--header": "C73R2W_Menu-item--header",
22
- "Menu-item--nested": "C73R2W_Menu-item--nested",
23
- "Menu-item-icon": "C73R2W_Menu-item-icon",
24
- "Menu-item-list": "C73R2W_Menu-item-list",
25
- "Menu-search": "C73R2W_Menu-search",
26
- "Menu-search-hidden-placeholder": "C73R2W_Menu-search-hidden-placeholder",
27
- "MenuSize--lg": "C73R2W_MenuSize--lg",
28
- "MenuSize--md": "C73R2W_MenuSize--md",
29
- "MenuSize--sm": "C73R2W_MenuSize--sm",
30
- "MenuSize--xl": "C73R2W_MenuSize--xl",
31
- "VirtualMenu-item": "C73R2W_VirtualMenu-item",
32
- "VirtualMenu-item-list": "C73R2W_VirtualMenu-item-list"
33
+ var Menu_module_default = {
34
+ "has-focus": "C73R2W_has-focus",
35
+ "is-highlighted": "C73R2W_is-highlighted",
36
+ Menu: Menu$1,
37
+ "Menu--isVirtual": "C73R2W_Menu--isVirtual",
38
+ "Menu-divider": "C73R2W_Menu-divider",
39
+ "Menu-item": "C73R2W_Menu-item",
40
+ "Menu-item--header": "C73R2W_Menu-item--header",
41
+ "Menu-item--nested": "C73R2W_Menu-item--nested",
42
+ "Menu-item-icon": "C73R2W_Menu-item-icon",
43
+ "Menu-item-list": "C73R2W_Menu-item-list",
44
+ "Menu-search": "C73R2W_Menu-search",
45
+ "Menu-search-hidden-placeholder": "C73R2W_Menu-search-hidden-placeholder",
46
+ "MenuSize--lg": "C73R2W_MenuSize--lg",
47
+ "MenuSize--md": "C73R2W_MenuSize--md",
48
+ "MenuSize--sm": "C73R2W_MenuSize--sm",
49
+ "MenuSize--xl": "C73R2W_MenuSize--xl",
50
+ "VirtualMenu-item": "C73R2W_VirtualMenu-item",
51
+ "VirtualMenu-item-list": "C73R2W_VirtualMenu-item-list"
33
52
  };
34
- const MenuBase = /* @__PURE__ */ react.forwardRef(
35
- ({ children, size, isVirtual, ...props }, ref) => {
36
- const classes = classix.cx(
37
- styles.Menu,
38
- isVirtual && styles["Menu--isVirtual"],
39
- size && styles[`MenuSize--${size}`]
40
- );
41
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, role: "menu", className: classes, ref, children });
42
- }
43
- );
53
+ const MenuBase = /* @__PURE__ */ (0, react.forwardRef)(({ children, size, isVirtual,...props }, ref) => {
54
+ const classes = (0, classix.cx)(Menu_module_default.Menu, isVirtual && Menu_module_default["Menu--isVirtual"], size && Menu_module_default[`MenuSize--${size}`]);
55
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
56
+ ...props,
57
+ role: "menu",
58
+ className: classes,
59
+ ref,
60
+ children
61
+ });
62
+ });
44
63
  MenuBase.displayName = "MenuBase";
45
- const MenuDivider = ({
46
- elementType = "div",
47
- orientation,
48
- innerRef,
49
- "data-test-id": testId = "menu-divider"
50
- }) => {
51
- const { separatorProps } = separator.useSeparator({
52
- orientation,
53
- elementType
54
- });
55
- return /* @__PURE__ */ jsxRuntime.jsx(
56
- "div",
57
- {
58
- ...separatorProps,
59
- "data-test-id": testId,
60
- ref: innerRef,
61
- className: styles["Menu-divider"]
62
- }
63
- );
64
+ const MenuDivider = ({ elementType = "div", orientation, innerRef, "data-test-id": testId = "menu-divider" }) => {
65
+ const { separatorProps } = (0, __react_aria_separator.useSeparator)({
66
+ orientation,
67
+ elementType
68
+ });
69
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
70
+ ...separatorProps,
71
+ "data-test-id": testId,
72
+ ref: innerRef,
73
+ className: Menu_module_default["Menu-divider"]
74
+ });
64
75
  };
65
76
  const defaultElement = "button";
66
- const MenuItem = ({
67
- ...props
68
- }) => {
69
- const {
70
- // TODO: remove component prop once we migrate over to asChild format
71
- component,
72
- children,
73
- isHighlighted,
74
- icon,
75
- nested,
76
- groupHeader,
77
- item,
78
- disabled,
79
- className,
80
- tooltip: tooltip$1,
81
- role = "menuitem",
82
- tooltipPlacement,
83
- onKeyDown,
84
- tooltipOptions,
85
- asChild,
86
- "data-test-id": testId = "menu-item",
87
- ...rest
88
- } = props;
89
- const Component = component || (asChild ? reactSlot.Slot : defaultElement);
90
- const renderIcon = icon && /* @__PURE__ */ react.cloneElement(icon, { size: "small" });
91
- const renderedItem = /* @__PURE__ */ jsxRuntime.jsx(focus.FocusRing, { focusRingClass: styles["has-focus"], children: /* @__PURE__ */ jsxRuntime.jsx(
92
- Component,
93
- {
94
- ...rest,
95
- disabled,
96
- "aria-disabled": disabled ? disabled : void 0,
97
- className: classix.cx(
98
- styles["Menu-item"],
99
- className,
100
- isHighlighted && styles["is-highlighted"],
101
- nested && styles["Menu-item--nested"],
102
- groupHeader && styles["Menu-item--header"]
103
- ),
104
- "data-test-id": testId,
105
- role,
106
- onKeyDown,
107
- children: asChild ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
108
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles["Menu-item-icon"], children: renderIcon }),
109
- children
110
- ] })
111
- }
112
- ) });
113
- if (tooltip$1) {
114
- return /* @__PURE__ */ jsxRuntime.jsx(
115
- tooltip.Tooltip,
116
- {
117
- content: tooltip$1,
118
- rootElementStyle: { display: "block" },
119
- allowBoundaryElementOverflow: true,
120
- placement: tooltipPlacement ? tooltipPlacement : "bottom",
121
- ...tooltipOptions || {},
122
- children: renderedItem
123
- }
124
- );
125
- }
126
- return renderedItem;
77
+ const MenuItem = ({ ...props }) => {
78
+ const { component, children, isHighlighted, icon, nested, groupHeader, item, disabled, className, tooltip, role = "menuitem", tooltipPlacement, onKeyDown, tooltipOptions, asChild, "data-test-id": testId = "menu-item",...rest } = props;
79
+ const Component = component || (asChild ? __radix_ui_react_slot.Slot : defaultElement);
80
+ const renderIcon = icon && /* @__PURE__ */ (0, react.cloneElement)(icon, { size: "small" });
81
+ const renderedItem = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__react_aria_focus.FocusRing, {
82
+ focusRingClass: Menu_module_default["has-focus"],
83
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
84
+ ...rest,
85
+ disabled,
86
+ "aria-disabled": disabled ? disabled : void 0,
87
+ className: (0, classix.cx)(Menu_module_default["Menu-item"], className, isHighlighted && Menu_module_default["is-highlighted"], nested && Menu_module_default["Menu-item--nested"], groupHeader && Menu_module_default["Menu-item--header"]),
88
+ "data-test-id": testId,
89
+ role,
90
+ onKeyDown,
91
+ children: asChild ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
92
+ className: Menu_module_default["Menu-item-icon"],
93
+ children: renderIcon
94
+ }), children] })
95
+ })
96
+ });
97
+ if (tooltip) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__launchpad_ui_tooltip.Tooltip, {
98
+ content: tooltip,
99
+ rootElementStyle: { display: "block" },
100
+ allowBoundaryElementOverflow: true,
101
+ placement: tooltipPlacement ? tooltipPlacement : "bottom",
102
+ ...tooltipOptions || {},
103
+ children: renderedItem
104
+ });
105
+ return renderedItem;
127
106
  };
128
- const MenuItemList = /* @__PURE__ */ react.forwardRef(({ children, ...rest }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ...rest, ref, "data-test-id": "menu-item-list", className: styles["Menu-item-list"], children }));
107
+ const MenuItemList = /* @__PURE__ */ (0, react.forwardRef)(({ children,...rest }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
108
+ ...rest,
109
+ ref,
110
+ "data-test-id": "menu-item-list",
111
+ className: Menu_module_default["Menu-item-list"],
112
+ children
113
+ }));
129
114
  MenuItemList.displayName = "MenuItemList";
130
- const MenuSearch = /* @__PURE__ */ react.forwardRef((props, ref) => {
131
- const {
132
- ariaLabel,
133
- placeholder,
134
- id,
135
- "data-test-id": testId = "menu-search",
136
- ...finalProps
137
- } = props;
138
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles["Menu-search"], children: /* @__PURE__ */ jsxRuntime.jsx(
139
- form.TextField,
140
- {
141
- ...finalProps,
142
- ref,
143
- className: styles["Menu-search-input"],
144
- tiny: true,
145
- id,
146
- type: "search",
147
- "data-test-id": testId,
148
- autoComplete: "off",
149
- placeholder,
150
- "aria-label": ariaLabel || "Search"
151
- }
152
- ) });
115
+ const MenuSearch = /* @__PURE__ */ (0, react.forwardRef)((props, ref) => {
116
+ const { ariaLabel, placeholder, id, "data-test-id": testId = "menu-search",...finalProps } = props;
117
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
118
+ className: Menu_module_default["Menu-search"],
119
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__launchpad_ui_form.TextField, {
120
+ ...finalProps,
121
+ ref,
122
+ className: Menu_module_default["Menu-search-input"],
123
+ tiny: true,
124
+ id,
125
+ type: "search",
126
+ "data-test-id": testId,
127
+ autoComplete: "off",
128
+ placeholder,
129
+ "aria-label": ariaLabel || "Search"
130
+ })
131
+ });
153
132
  });
154
133
  MenuSearch.displayName = "MenuSearch";
155
134
  const createItemId = (index, id) => `${id}-item-${index}`;
156
135
  const getNodeForIndex = (index, menuId) => index === null ? index : document.getElementById(createItemId(index, menuId));
157
136
  const handleKeyboardInteractions = (event, keyHandlers) => {
158
- var _a;
159
- const ops = {
160
- ArrowUp: keyHandlers.handleUp,
161
- ArrowDown: keyHandlers.handleDown,
162
- Enter: keyHandlers.handleEnter
163
- // biome-ignore lint/suspicious/noConfusingVoidType: <explanation>
164
- };
165
- if (ops[event.key]) {
166
- event.preventDefault();
167
- (_a = ops[event.key]) == null ? void 0 : _a.call(globalThis, event);
168
- }
137
+ const ops = {
138
+ ArrowUp: keyHandlers.handleUp,
139
+ ArrowDown: keyHandlers.handleDown,
140
+ Enter: keyHandlers.handleEnter
141
+ };
142
+ if (ops[event.key]) {
143
+ event.preventDefault();
144
+ ops[event.key]?.call(globalThis, event);
145
+ }
169
146
  };
170
147
  const chainEventHandlers = (...handlers) => (event) => {
171
- for (const h of handlers) {
172
- typeof h === "function" && h(event);
173
- }
148
+ for (const h of handlers) typeof h === "function" && h(event);
174
149
  };
150
+ /**
151
+ * @deprecated use `Menu` or `ListBox` from `@launchpad-ui/components` instead
152
+ *
153
+ * https://launchpad.launchdarkly.com/?path=/docs/components-collections-menu--docs
154
+ */
175
155
  const Menu = (props) => {
176
- const {
177
- children,
178
- menuItemClassName,
179
- onSelect,
180
- enableVirtualization,
181
- itemHeight,
182
- size,
183
- overscan = 1,
184
- "data-test-id": testId = "menu"
185
- } = props;
186
- const focusManager = focus.useFocusManager();
187
- const handleArrowDown = react.useCallback(() => {
188
- focusManager == null ? void 0 : focusManager.focusNext({ wrap: true });
189
- }, [focusManager]);
190
- const handleArrowUp = react.useCallback(() => {
191
- focusManager == null ? void 0 : focusManager.focusPrevious({ wrap: true });
192
- }, [focusManager]);
193
- const reduceItems = react.useMemo(() => {
194
- const childrenProps = react.Children.toArray(children);
195
- if (enableVirtualization) {
196
- let searchElem = null;
197
- let elements = [];
198
- for (const child of childrenProps) {
199
- switch (child.type) {
200
- case MenuSearch:
201
- searchElem = child;
202
- break;
203
- case MenuItem:
204
- case MenuDivider:
205
- elements = elements.concat(child);
206
- break;
207
- }
208
- }
209
- return { items: elements, searchElement: searchElem };
210
- }
211
- return childrenProps.reduce(
212
- ({ items, searchElement }, child) => {
213
- switch (child.type) {
214
- case MenuSearch:
215
- return {
216
- items,
217
- searchElement: /* @__PURE__ */ react.cloneElement(child, {
218
- onKeyDown: (e) => handleKeyboardInteractions(e, {
219
- handleDown: handleArrowDown,
220
- handleUp: handleArrowUp
221
- })
222
- })
223
- };
224
- case MenuItem:
225
- return {
226
- items: items.concat(
227
- child.props.disabled ? /* @__PURE__ */ react.cloneElement(child, {
228
- className: classix.cx(child.props.className, menuItemClassName),
229
- onClick: () => void 0,
230
- onKeyDown: () => void 0,
231
- tabIndex: -1,
232
- disabled: true
233
- }) : /* @__PURE__ */ react.cloneElement(child, {
234
- className: classix.cx(child.props.className, menuItemClassName),
235
- item: child.props.item ?? items.length,
236
- // set focus on the first menu item if there is no search input, and set in the tab order
237
- onClick: chainEventHandlers(child.props.onClick, () => {
238
- onSelect == null ? void 0 : onSelect(child.props.item ?? items.length);
239
- }),
240
- onKeyDown: (e) => handleKeyboardInteractions(e, {
241
- handleDown: handleArrowDown,
242
- handleUp: handleArrowUp
243
- })
244
- })
245
- ),
246
- searchElement
247
- };
248
- case MenuDivider:
249
- return { items: items.concat(child), searchElement };
250
- default:
251
- return { items, searchElement };
252
- }
253
- },
254
- { items: [], searchElement: null }
255
- );
256
- }, [children, enableVirtualization, menuItemClassName, handleArrowDown, handleArrowUp, onSelect]);
257
- if (enableVirtualization) {
258
- return /* @__PURE__ */ jsxRuntime.jsx(MenuBase, { "data-test-id": testId, isVirtual: true, size, children: /* @__PURE__ */ jsxRuntime.jsx(
259
- ItemVirtualizer,
260
- {
261
- items: react.Children.toArray(reduceItems.items),
262
- searchElement: reduceItems.searchElement,
263
- overscan,
264
- menuItemClassName,
265
- onSelect,
266
- itemHeight,
267
- focusManager
268
- }
269
- ) });
270
- }
271
- return /* @__PURE__ */ jsxRuntime.jsxs(MenuBase, { "data-test-id": testId, size, children: [
272
- reduceItems.searchElement,
273
- /* @__PURE__ */ jsxRuntime.jsx(MenuItemList, { role: "presentation", children: reduceItems.items })
274
- ] });
156
+ const { children, menuItemClassName, onSelect, enableVirtualization, itemHeight, size, overscan = 1, "data-test-id": testId = "menu" } = props;
157
+ const focusManager = (0, __react_aria_focus.useFocusManager)();
158
+ const handleArrowDown = (0, react.useCallback)(() => {
159
+ focusManager?.focusNext({ wrap: true });
160
+ }, [focusManager]);
161
+ const handleArrowUp = (0, react.useCallback)(() => {
162
+ focusManager?.focusPrevious({ wrap: true });
163
+ }, [focusManager]);
164
+ const reduceItems = (0, react.useMemo)(() => {
165
+ const childrenProps = react.Children.toArray(children);
166
+ if (enableVirtualization) {
167
+ let searchElem = null;
168
+ let elements = [];
169
+ for (const child of childrenProps) switch (child.type) {
170
+ case MenuSearch:
171
+ searchElem = child;
172
+ break;
173
+ case MenuItem:
174
+ case MenuDivider:
175
+ elements = elements.concat(child);
176
+ break;
177
+ default: break;
178
+ }
179
+ return {
180
+ items: elements,
181
+ searchElement: searchElem
182
+ };
183
+ }
184
+ return childrenProps.reduce(({ items, searchElement }, child) => {
185
+ switch (child.type) {
186
+ case MenuSearch: return {
187
+ items,
188
+ searchElement: /* @__PURE__ */ (0, react.cloneElement)(child, { onKeyDown: (e) => handleKeyboardInteractions(e, {
189
+ handleDown: handleArrowDown,
190
+ handleUp: handleArrowUp
191
+ }) })
192
+ };
193
+ case MenuItem: return {
194
+ items: items.concat(child.props.disabled ? /* @__PURE__ */ (0, react.cloneElement)(child, {
195
+ className: (0, classix.cx)(child.props.className, menuItemClassName),
196
+ onClick: () => void 0,
197
+ onKeyDown: () => void 0,
198
+ tabIndex: -1,
199
+ disabled: true
200
+ }) : /* @__PURE__ */ (0, react.cloneElement)(child, {
201
+ className: (0, classix.cx)(child.props.className, menuItemClassName),
202
+ item: child.props.item ?? items.length,
203
+ onClick: chainEventHandlers(child.props.onClick, () => {
204
+ onSelect?.(child.props.item ?? items.length);
205
+ }),
206
+ onKeyDown: (e) => handleKeyboardInteractions(e, {
207
+ handleDown: handleArrowDown,
208
+ handleUp: handleArrowUp
209
+ })
210
+ })),
211
+ searchElement
212
+ };
213
+ case MenuDivider: return {
214
+ items: items.concat(child),
215
+ searchElement
216
+ };
217
+ default: return {
218
+ items,
219
+ searchElement
220
+ };
221
+ }
222
+ }, {
223
+ items: [],
224
+ searchElement: null
225
+ });
226
+ }, [
227
+ children,
228
+ enableVirtualization,
229
+ menuItemClassName,
230
+ handleArrowDown,
231
+ handleArrowUp,
232
+ onSelect
233
+ ]);
234
+ if (enableVirtualization) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuBase, {
235
+ "data-test-id": testId,
236
+ isVirtual: true,
237
+ size,
238
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemVirtualizer, {
239
+ items: react.Children.toArray(reduceItems.items),
240
+ searchElement: reduceItems.searchElement,
241
+ overscan,
242
+ menuItemClassName,
243
+ onSelect,
244
+ itemHeight,
245
+ focusManager
246
+ })
247
+ });
248
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(MenuBase, {
249
+ "data-test-id": testId,
250
+ size,
251
+ children: [reduceItems.searchElement, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItemList, {
252
+ role: "presentation",
253
+ children: reduceItems.items
254
+ })]
255
+ });
275
256
  };
276
257
  const ItemVirtualizer = (props) => {
277
- const {
278
- overscan,
279
- searchElement,
280
- itemHeight = 31.5,
281
- menuItemClassName,
282
- items,
283
- focusManager,
284
- onSelect
285
- } = props;
286
- const menuId = react.useRef(`menu-ctrl-${react.useId()}`);
287
- const focusedItemIndex = react.useRef(null);
288
- const parentRef = react.useRef(null);
289
- const searchRef = react.useRef(null);
290
- const [nextFocusValue, setNextFocusValue] = react.useState(null);
291
- const hasSearch = !!searchElement;
292
- const lastVirtualItemIndex = items ? items.length - 1 : 0;
293
- const rowVirtualizer = reactVirtual.useVirtual({
294
- size: items !== null ? items.length : 0,
295
- parentRef,
296
- estimateSize: react.useCallback(() => itemHeight, [itemHeight]),
297
- overscan
298
- });
299
- const focusSearchBar = react.useCallback(() => {
300
- var _a, _b;
301
- rowVirtualizer.scrollToIndex(0);
302
- (_b = (_a = searchRef.current) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a);
303
- }, [rowVirtualizer]);
304
- const focusMenuItem = react.useCallback(
305
- (index) => {
306
- rowVirtualizer.scrollToIndex(index);
307
- setNextFocusValue(index);
308
- },
309
- [rowVirtualizer]
310
- );
311
- const handleKeyboardFocusInteraction = react.useCallback(
312
- (direction) => {
313
- if (focusedItemIndex.current === null || focusedItemIndex.current === void 0) {
314
- return;
315
- }
316
- const nextIndex = direction === "next" ? focusedItemIndex.current + 1 : focusedItemIndex.current - 1;
317
- const shouldWrap = direction === "next" && focusedItemIndex.current === lastVirtualItemIndex || direction === "previous" && focusedItemIndex.current === 0;
318
- if (shouldWrap) {
319
- if (hasSearch) {
320
- focusSearchBar();
321
- } else {
322
- focusMenuItem(direction === "next" ? 0 : lastVirtualItemIndex);
323
- }
324
- return;
325
- }
326
- switch (direction) {
327
- case "next":
328
- rowVirtualizer.scrollToIndex(nextIndex);
329
- focusManager == null ? void 0 : focusManager.focusNext();
330
- break;
331
- case "previous":
332
- rowVirtualizer.scrollToIndex(nextIndex);
333
- focusManager == null ? void 0 : focusManager.focusPrevious();
334
- break;
335
- }
336
- },
337
- [focusManager, focusMenuItem, focusSearchBar, hasSearch, lastVirtualItemIndex, rowVirtualizer]
338
- );
339
- const getItemProps = react.useCallback(
340
- (itemElem, index) => {
341
- const childProps = itemElem.props;
342
- switch (itemElem.type) {
343
- case MenuItem:
344
- return {
345
- className: classix.cx(childProps.className, menuItemClassName),
346
- // set focus on the first menu item if there is no search input, and set in the tab order
347
- onKeyDown: childProps.disabled ? () => void 0 : (e) => handleKeyboardFocusKeydown(e, {
348
- handleFocusBackward: handleKeyboardFocusInteraction,
349
- handleFocusForward: handleKeyboardFocusInteraction
350
- }),
351
- onFocus: chainEventHandlers(childProps.onFocus, () => {
352
- focusedItemIndex.current = index;
353
- }),
354
- id: createItemId(index, menuId.current),
355
- onBlur: chainEventHandlers(childProps.onBlur, () => {
356
- focusedItemIndex.current = null;
357
- }),
358
- onClick: childProps.disabled ? () => void 0 : chainEventHandlers(childProps.onClick, () => {
359
- onSelect == null ? void 0 : onSelect(childProps.item);
360
- })
361
- };
362
- default:
363
- return {};
364
- }
365
- },
366
- [handleKeyboardFocusInteraction, menuItemClassName, onSelect]
367
- );
368
- react.useEffect(() => {
369
- if (nextFocusValue !== null) {
370
- requestAnimationFrame(() => {
371
- const element = getNodeForIndex(nextFocusValue, menuId.current);
372
- element == null ? void 0 : element.focus();
373
- });
374
- setNextFocusValue(null);
375
- }
376
- }, [nextFocusValue]);
377
- const handleKeyboardFocusKeydown = (e, callbacks) => {
378
- var _a, _b;
379
- const keyOps = ["Tab", "ArrowUp", "ArrowDown"];
380
- if (keyOps.includes(e.key)) {
381
- e.preventDefault();
382
- e.stopPropagation();
383
- if (e.key === "Tab" && e.shiftKey || e.key === "ArrowUp") {
384
- (_a = callbacks.handleFocusBackward) == null ? void 0 : _a.call(callbacks, "previous");
385
- } else if (e.key === "ArrowDown" || e.key === "Tab") {
386
- (_b = callbacks.handleFocusForward) == null ? void 0 : _b.call(callbacks, "next");
387
- }
388
- }
389
- };
390
- const renderSearch = react.useMemo(
391
- () => searchElement ? (
392
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
393
- /* @__PURE__ */ react.cloneElement(searchElement, {
394
- onKeyDown: (e) => handleKeyboardFocusKeydown(e, {
395
- handleFocusBackward: () => focusMenuItem(lastVirtualItemIndex),
396
- handleFocusForward: () => focusMenuItem(0)
397
- }),
398
- ref: searchRef
399
- })
400
- ) : null,
401
- [searchElement, lastVirtualItemIndex, focusMenuItem]
402
- );
403
- const renderItems = react.useMemo(
404
- () => rowVirtualizer.virtualItems.map((virtualRow) => {
405
- if (!items) {
406
- return null;
407
- }
408
- const elem = items[virtualRow.index];
409
- return /* @__PURE__ */ jsxRuntime.jsx(
410
- "div",
411
- {
412
- ref: virtualRow.measureRef,
413
- role: "presentation",
414
- className: styles["VirtualMenu-item"],
415
- style: {
416
- transform: `translateY(${virtualRow.start}px)`
417
- },
418
- children: /* @__PURE__ */ react.cloneElement(elem, getItemProps(elem, virtualRow.index))
419
- },
420
- virtualRow.index
421
- );
422
- }),
423
- [rowVirtualizer.virtualItems, items, getItemProps]
424
- );
425
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
426
- renderSearch,
427
- /* @__PURE__ */ jsxRuntime.jsx(MenuItemList, { ref: parentRef, role: "presentation", children: /* @__PURE__ */ jsxRuntime.jsx(
428
- "div",
429
- {
430
- role: "presentation",
431
- className: styles["VirtualMenu-item-list"],
432
- style: {
433
- height: `${rowVirtualizer.totalSize}px`
434
- },
435
- children: renderItems
436
- }
437
- ) })
438
- ] });
258
+ const { overscan, searchElement, itemHeight = 31.5, menuItemClassName, items, focusManager, onSelect } = props;
259
+ const menuId = (0, react.useRef)(`menu-ctrl-${(0, react.useId)()}`);
260
+ const focusedItemIndex = (0, react.useRef)(null);
261
+ const parentRef = (0, react.useRef)(null);
262
+ const searchRef = (0, react.useRef)(null);
263
+ const [nextFocusValue, setNextFocusValue] = (0, react.useState)(null);
264
+ const hasSearch = !!searchElement;
265
+ const lastVirtualItemIndex = items ? items.length - 1 : 0;
266
+ const rowVirtualizer = (0, react_virtual.useVirtual)({
267
+ size: items !== null ? items.length : 0,
268
+ parentRef,
269
+ estimateSize: (0, react.useCallback)(() => itemHeight, [itemHeight]),
270
+ overscan
271
+ });
272
+ const focusSearchBar = (0, react.useCallback)(() => {
273
+ rowVirtualizer.scrollToIndex(0);
274
+ searchRef.current?.focus?.();
275
+ }, [rowVirtualizer]);
276
+ /**
277
+ * Scrolls to the menu item with the index provided and
278
+ * then manually focuses it using a side effect in useEffect
279
+ */
280
+ const focusMenuItem = (0, react.useCallback)((index) => {
281
+ rowVirtualizer.scrollToIndex(index);
282
+ setNextFocusValue(index);
283
+ }, [rowVirtualizer]);
284
+ const handleKeyboardFocusInteraction = (0, react.useCallback)((direction) => {
285
+ if (focusedItemIndex.current === null || focusedItemIndex.current === void 0) return;
286
+ const nextIndex = direction === "next" ? focusedItemIndex.current + 1 : focusedItemIndex.current - 1;
287
+ const shouldWrap = direction === "next" && focusedItemIndex.current === lastVirtualItemIndex || direction === "previous" && focusedItemIndex.current === 0;
288
+ if (shouldWrap) {
289
+ if (hasSearch) focusSearchBar();
290
+ else focusMenuItem(direction === "next" ? 0 : lastVirtualItemIndex);
291
+ return;
292
+ }
293
+ switch (direction) {
294
+ case "next":
295
+ rowVirtualizer.scrollToIndex(nextIndex);
296
+ focusManager?.focusNext();
297
+ break;
298
+ case "previous":
299
+ rowVirtualizer.scrollToIndex(nextIndex);
300
+ focusManager?.focusPrevious();
301
+ break;
302
+ default: break;
303
+ }
304
+ }, [
305
+ focusManager,
306
+ focusMenuItem,
307
+ focusSearchBar,
308
+ hasSearch,
309
+ lastVirtualItemIndex,
310
+ rowVirtualizer
311
+ ]);
312
+ const getItemProps = (0, react.useCallback)((itemElem, index) => {
313
+ const childProps = itemElem.props;
314
+ switch (itemElem.type) {
315
+ case MenuItem: return {
316
+ className: (0, classix.cx)(childProps.className, menuItemClassName),
317
+ onKeyDown: childProps.disabled ? () => void 0 : (e) => handleKeyboardFocusKeydown(e, {
318
+ handleFocusBackward: handleKeyboardFocusInteraction,
319
+ handleFocusForward: handleKeyboardFocusInteraction
320
+ }),
321
+ onFocus: chainEventHandlers(childProps.onFocus, () => {
322
+ focusedItemIndex.current = index;
323
+ }),
324
+ id: createItemId(index, menuId.current),
325
+ onBlur: chainEventHandlers(childProps.onBlur, () => {
326
+ focusedItemIndex.current = null;
327
+ }),
328
+ onClick: childProps.disabled ? () => void 0 : chainEventHandlers(childProps.onClick, () => {
329
+ onSelect?.(childProps.item);
330
+ })
331
+ };
332
+ default: return {};
333
+ }
334
+ }, [
335
+ handleKeyboardFocusInteraction,
336
+ menuItemClassName,
337
+ onSelect
338
+ ]);
339
+ (0, react.useEffect)(() => {
340
+ if (nextFocusValue !== null) {
341
+ requestAnimationFrame(() => {
342
+ const element = getNodeForIndex(nextFocusValue, menuId.current);
343
+ element?.focus();
344
+ });
345
+ setNextFocusValue(null);
346
+ }
347
+ }, [nextFocusValue]);
348
+ /**
349
+ * Calls handleFocusForward when the user is attempting to focus forward using
350
+ * tab or arrow keys. Calls handleFocusBackward when the users wants to move backward.
351
+ */
352
+ const handleKeyboardFocusKeydown = (e, callbacks) => {
353
+ const keyOps = [
354
+ "Tab",
355
+ "ArrowUp",
356
+ "ArrowDown"
357
+ ];
358
+ if (keyOps.includes(e.key)) {
359
+ e.preventDefault();
360
+ e.stopPropagation();
361
+ if (e.key === "Tab" && e.shiftKey || e.key === "ArrowUp") callbacks.handleFocusBackward?.("previous");
362
+ else if (e.key === "ArrowDown" || e.key === "Tab") callbacks.handleFocusForward?.("next");
363
+ }
364
+ };
365
+ const renderSearch = (0, react.useMemo)(() => searchElement ? /* @__PURE__ */ (0, react.cloneElement)(searchElement, {
366
+ onKeyDown: (e) => handleKeyboardFocusKeydown(e, {
367
+ handleFocusBackward: () => focusMenuItem(lastVirtualItemIndex),
368
+ handleFocusForward: () => focusMenuItem(0)
369
+ }),
370
+ ref: searchRef
371
+ }) : null, [
372
+ searchElement,
373
+ lastVirtualItemIndex,
374
+ focusMenuItem
375
+ ]);
376
+ const renderItems = (0, react.useMemo)(() => rowVirtualizer.virtualItems.map((virtualRow) => {
377
+ if (!items) return null;
378
+ const elem = items[virtualRow.index];
379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
380
+ ref: virtualRow.measureRef,
381
+ role: "presentation",
382
+ className: Menu_module_default["VirtualMenu-item"],
383
+ style: { transform: `translateY(${virtualRow.start}px)` },
384
+ children: /* @__PURE__ */ (0, react.cloneElement)(elem, getItemProps(elem, virtualRow.index))
385
+ }, virtualRow.index);
386
+ }), [
387
+ rowVirtualizer.virtualItems,
388
+ items,
389
+ getItemProps
390
+ ]);
391
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [renderSearch, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItemList, {
392
+ ref: parentRef,
393
+ role: "presentation",
394
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
395
+ role: "presentation",
396
+ className: Menu_module_default["VirtualMenu-item-list"],
397
+ style: { height: `${rowVirtualizer.totalSize}px` },
398
+ children: renderItems
399
+ })
400
+ })] });
439
401
  };
440
402
  exports.Menu = Menu;
441
403
  exports.MenuBase = MenuBase;
@@ -443,4 +405,5 @@ exports.MenuDivider = MenuDivider;
443
405
  exports.MenuItem = MenuItem;
444
406
  exports.MenuItemList = MenuItemList;
445
407
  exports.MenuSearch = MenuSearch;
446
- //# sourceMappingURL=index.js.map
408
+
409
+ //# sourceMappingURL=index.js.map