@huin-core/react-menu 1.0.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.js ADDED
@@ -0,0 +1,901 @@
1
+ "use strict";
2
+ "use client";
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 __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // packages/react/menu/src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ Anchor: () => Anchor2,
35
+ Arrow: () => Arrow2,
36
+ CheckboxItem: () => CheckboxItem,
37
+ Content: () => Content2,
38
+ Group: () => Group,
39
+ Item: () => Item2,
40
+ ItemIndicator: () => ItemIndicator,
41
+ Label: () => Label,
42
+ Menu: () => Menu,
43
+ MenuAnchor: () => MenuAnchor,
44
+ MenuArrow: () => MenuArrow,
45
+ MenuCheckboxItem: () => MenuCheckboxItem,
46
+ MenuContent: () => MenuContent,
47
+ MenuGroup: () => MenuGroup,
48
+ MenuItem: () => MenuItem,
49
+ MenuItemIndicator: () => MenuItemIndicator,
50
+ MenuLabel: () => MenuLabel,
51
+ MenuPortal: () => MenuPortal,
52
+ MenuRadioGroup: () => MenuRadioGroup,
53
+ MenuRadioItem: () => MenuRadioItem,
54
+ MenuSeparator: () => MenuSeparator,
55
+ MenuSub: () => MenuSub,
56
+ MenuSubContent: () => MenuSubContent,
57
+ MenuSubTrigger: () => MenuSubTrigger,
58
+ Portal: () => Portal,
59
+ RadioGroup: () => RadioGroup,
60
+ RadioItem: () => RadioItem,
61
+ Root: () => Root3,
62
+ Separator: () => Separator,
63
+ Sub: () => Sub,
64
+ SubContent: () => SubContent,
65
+ SubTrigger: () => SubTrigger,
66
+ createMenuScope: () => createMenuScope
67
+ });
68
+ module.exports = __toCommonJS(src_exports);
69
+
70
+ // packages/react/menu/src/Menu.tsx
71
+ var React = __toESM(require("react"));
72
+ var import_primitive = require("@huin-core/primitive");
73
+ var import_react_collection = require("@huin-core/react-collection");
74
+ var import_react_compose_refs = require("@huin-core/react-compose-refs");
75
+ var import_react_context = require("@huin-core/react-context");
76
+ var import_react_direction = require("@huin-core/react-direction");
77
+ var import_react_dismissable_layer = require("@huin-core/react-dismissable-layer");
78
+ var import_react_focus_guards = require("@huin-core/react-focus-guards");
79
+ var import_react_focus_scope = require("@huin-core/react-focus-scope");
80
+ var import_react_id = require("@huin-core/react-id");
81
+ var PopperPrimitive = __toESM(require("@huin-core/react-popper"));
82
+ var import_react_popper = require("@huin-core/react-popper");
83
+ var import_react_portal = require("@huin-core/react-portal");
84
+ var import_react_presence = require("@huin-core/react-presence");
85
+ var import_react_primitive = require("@huin-core/react-primitive");
86
+ var RovingFocusGroup = __toESM(require("@huin-core/react-roving-focus"));
87
+ var import_react_roving_focus = require("@huin-core/react-roving-focus");
88
+ var import_react_slot = require("@huin-core/react-slot");
89
+ var import_react_use_callback_ref = require("@huin-core/react-use-callback-ref");
90
+ var import_aria_hidden = require("aria-hidden");
91
+ var import_react_remove_scroll = require("react-remove-scroll");
92
+ var import_jsx_runtime = require("react/jsx-runtime");
93
+ var SELECTION_KEYS = ["Enter", " "];
94
+ var FIRST_KEYS = ["ArrowDown", "PageUp", "Home"];
95
+ var LAST_KEYS = ["ArrowUp", "PageDown", "End"];
96
+ var FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];
97
+ var SUB_OPEN_KEYS = {
98
+ ltr: [...SELECTION_KEYS, "ArrowRight"],
99
+ rtl: [...SELECTION_KEYS, "ArrowLeft"]
100
+ };
101
+ var SUB_CLOSE_KEYS = {
102
+ ltr: ["ArrowLeft"],
103
+ rtl: ["ArrowRight"]
104
+ };
105
+ var MENU_NAME = "Menu";
106
+ var [Collection, useCollection, createCollectionScope] = (0, import_react_collection.createCollection)(MENU_NAME);
107
+ var [createMenuContext, createMenuScope] = (0, import_react_context.createContextScope)(MENU_NAME, [
108
+ createCollectionScope,
109
+ import_react_popper.createPopperScope,
110
+ import_react_roving_focus.createRovingFocusGroupScope
111
+ ]);
112
+ var usePopperScope = (0, import_react_popper.createPopperScope)();
113
+ var useRovingFocusGroupScope = (0, import_react_roving_focus.createRovingFocusGroupScope)();
114
+ var [MenuProvider, useMenuContext] = createMenuContext(MENU_NAME);
115
+ var [MenuRootProvider, useMenuRootContext] = createMenuContext(MENU_NAME);
116
+ var Menu = (props) => {
117
+ const { __scopeMenu, open = false, children, dir, onOpenChange, modal = true } = props;
118
+ const popperScope = usePopperScope(__scopeMenu);
119
+ const [content, setContent] = React.useState(null);
120
+ const isUsingKeyboardRef = React.useRef(false);
121
+ const handleOpenChange = (0, import_react_use_callback_ref.useCallbackRef)(onOpenChange);
122
+ const direction = (0, import_react_direction.useDirection)(dir);
123
+ React.useEffect(() => {
124
+ const handleKeyDown = () => {
125
+ isUsingKeyboardRef.current = true;
126
+ document.addEventListener("pointerdown", handlePointer, { capture: true, once: true });
127
+ document.addEventListener("pointermove", handlePointer, { capture: true, once: true });
128
+ };
129
+ const handlePointer = () => isUsingKeyboardRef.current = false;
130
+ document.addEventListener("keydown", handleKeyDown, { capture: true });
131
+ return () => {
132
+ document.removeEventListener("keydown", handleKeyDown, { capture: true });
133
+ document.removeEventListener("pointerdown", handlePointer, { capture: true });
134
+ document.removeEventListener("pointermove", handlePointer, { capture: true });
135
+ };
136
+ }, []);
137
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
138
+ MenuProvider,
139
+ {
140
+ scope: __scopeMenu,
141
+ open,
142
+ onOpenChange: handleOpenChange,
143
+ content,
144
+ onContentChange: setContent,
145
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
146
+ MenuRootProvider,
147
+ {
148
+ scope: __scopeMenu,
149
+ onClose: React.useCallback(() => handleOpenChange(false), [handleOpenChange]),
150
+ isUsingKeyboardRef,
151
+ dir: direction,
152
+ modal,
153
+ children
154
+ }
155
+ )
156
+ }
157
+ ) });
158
+ };
159
+ Menu.displayName = MENU_NAME;
160
+ var ANCHOR_NAME = "MenuAnchor";
161
+ var MenuAnchor = React.forwardRef(
162
+ (props, forwardedRef) => {
163
+ const { __scopeMenu, ...anchorProps } = props;
164
+ const popperScope = usePopperScope(__scopeMenu);
165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Anchor, { ...popperScope, ...anchorProps, ref: forwardedRef });
166
+ }
167
+ );
168
+ MenuAnchor.displayName = ANCHOR_NAME;
169
+ var PORTAL_NAME = "MenuPortal";
170
+ var [PortalProvider, usePortalContext] = createMenuContext(PORTAL_NAME, {
171
+ forceMount: void 0
172
+ });
173
+ var MenuPortal = (props) => {
174
+ const { __scopeMenu, forceMount, children, container } = props;
175
+ const context = useMenuContext(PORTAL_NAME, __scopeMenu);
176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PortalProvider, { scope: __scopeMenu, forceMount, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_presence.Presence, { present: forceMount || context.open, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_portal.Portal, { asChild: true, container, children }) }) });
177
+ };
178
+ MenuPortal.displayName = PORTAL_NAME;
179
+ var CONTENT_NAME = "MenuContent";
180
+ var [MenuContentProvider, useMenuContentContext] = createMenuContext(CONTENT_NAME);
181
+ var MenuContent = React.forwardRef(
182
+ (props, forwardedRef) => {
183
+ const portalContext = usePortalContext(CONTENT_NAME, props.__scopeMenu);
184
+ const { forceMount = portalContext.forceMount, ...contentProps } = props;
185
+ const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
186
+ const rootContext = useMenuRootContext(CONTENT_NAME, props.__scopeMenu);
187
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Provider, { scope: props.__scopeMenu, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_presence.Presence, { present: forceMount || context.open, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Slot, { scope: props.__scopeMenu, children: rootContext.modal ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuRootContentNonModal, { ...contentProps, ref: forwardedRef }) }) }) });
188
+ }
189
+ );
190
+ var MenuRootContentModal = React.forwardRef(
191
+ (props, forwardedRef) => {
192
+ const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
193
+ const ref = React.useRef(null);
194
+ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, ref);
195
+ React.useEffect(() => {
196
+ const content = ref.current;
197
+ if (content) return (0, import_aria_hidden.hideOthers)(content);
198
+ }, []);
199
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
200
+ MenuContentImpl,
201
+ {
202
+ ...props,
203
+ ref: composedRefs,
204
+ trapFocus: context.open,
205
+ disableOutsidePointerEvents: context.open,
206
+ disableOutsideScroll: true,
207
+ onFocusOutside: (0, import_primitive.composeEventHandlers)(
208
+ props.onFocusOutside,
209
+ (event) => event.preventDefault(),
210
+ { checkForDefaultPrevented: false }
211
+ ),
212
+ onDismiss: () => context.onOpenChange(false)
213
+ }
214
+ );
215
+ }
216
+ );
217
+ var MenuRootContentNonModal = React.forwardRef((props, forwardedRef) => {
218
+ const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
219
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
220
+ MenuContentImpl,
221
+ {
222
+ ...props,
223
+ ref: forwardedRef,
224
+ trapFocus: false,
225
+ disableOutsidePointerEvents: false,
226
+ disableOutsideScroll: false,
227
+ onDismiss: () => context.onOpenChange(false)
228
+ }
229
+ );
230
+ });
231
+ var MenuContentImpl = React.forwardRef(
232
+ (props, forwardedRef) => {
233
+ const {
234
+ __scopeMenu,
235
+ loop = false,
236
+ trapFocus,
237
+ onOpenAutoFocus,
238
+ onCloseAutoFocus,
239
+ disableOutsidePointerEvents,
240
+ onEntryFocus,
241
+ onEscapeKeyDown,
242
+ onPointerDownOutside,
243
+ onFocusOutside,
244
+ onInteractOutside,
245
+ onDismiss,
246
+ disableOutsideScroll,
247
+ ...contentProps
248
+ } = props;
249
+ const context = useMenuContext(CONTENT_NAME, __scopeMenu);
250
+ const rootContext = useMenuRootContext(CONTENT_NAME, __scopeMenu);
251
+ const popperScope = usePopperScope(__scopeMenu);
252
+ const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu);
253
+ const getItems = useCollection(__scopeMenu);
254
+ const [currentItemId, setCurrentItemId] = React.useState(null);
255
+ const contentRef = React.useRef(null);
256
+ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, contentRef, context.onContentChange);
257
+ const timerRef = React.useRef(0);
258
+ const searchRef = React.useRef("");
259
+ const pointerGraceTimerRef = React.useRef(0);
260
+ const pointerGraceIntentRef = React.useRef(null);
261
+ const pointerDirRef = React.useRef("right");
262
+ const lastPointerXRef = React.useRef(0);
263
+ const ScrollLockWrapper = disableOutsideScroll ? import_react_remove_scroll.RemoveScroll : React.Fragment;
264
+ const scrollLockWrapperProps = disableOutsideScroll ? { as: import_react_slot.Slot, allowPinchZoom: true } : void 0;
265
+ const handleTypeaheadSearch = (key) => {
266
+ const search = searchRef.current + key;
267
+ const items = getItems().filter((item) => !item.disabled);
268
+ const currentItem = document.activeElement;
269
+ const currentMatch = items.find((item) => item.ref.current === currentItem)?.textValue;
270
+ const values = items.map((item) => item.textValue);
271
+ const nextMatch = getNextMatch(values, search, currentMatch);
272
+ const newItem = items.find((item) => item.textValue === nextMatch)?.ref.current;
273
+ (function updateSearch(value) {
274
+ searchRef.current = value;
275
+ window.clearTimeout(timerRef.current);
276
+ if (value !== "") timerRef.current = window.setTimeout(() => updateSearch(""), 1e3);
277
+ })(search);
278
+ if (newItem) {
279
+ setTimeout(() => newItem.focus());
280
+ }
281
+ };
282
+ React.useEffect(() => {
283
+ return () => window.clearTimeout(timerRef.current);
284
+ }, []);
285
+ (0, import_react_focus_guards.useFocusGuards)();
286
+ const isPointerMovingToSubmenu = React.useCallback((event) => {
287
+ const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side;
288
+ return isMovingTowards && isPointerInGraceArea(event, pointerGraceIntentRef.current?.area);
289
+ }, []);
290
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
291
+ MenuContentProvider,
292
+ {
293
+ scope: __scopeMenu,
294
+ searchRef,
295
+ onItemEnter: React.useCallback(
296
+ (event) => {
297
+ if (isPointerMovingToSubmenu(event)) event.preventDefault();
298
+ },
299
+ [isPointerMovingToSubmenu]
300
+ ),
301
+ onItemLeave: React.useCallback(
302
+ (event) => {
303
+ if (isPointerMovingToSubmenu(event)) return;
304
+ contentRef.current?.focus();
305
+ setCurrentItemId(null);
306
+ },
307
+ [isPointerMovingToSubmenu]
308
+ ),
309
+ onTriggerLeave: React.useCallback(
310
+ (event) => {
311
+ if (isPointerMovingToSubmenu(event)) event.preventDefault();
312
+ },
313
+ [isPointerMovingToSubmenu]
314
+ ),
315
+ pointerGraceTimerRef,
316
+ onPointerGraceIntentChange: React.useCallback((intent) => {
317
+ pointerGraceIntentRef.current = intent;
318
+ }, []),
319
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollLockWrapper, { ...scrollLockWrapperProps, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
320
+ import_react_focus_scope.FocusScope,
321
+ {
322
+ asChild: true,
323
+ trapped: trapFocus,
324
+ onMountAutoFocus: (0, import_primitive.composeEventHandlers)(onOpenAutoFocus, (event) => {
325
+ event.preventDefault();
326
+ contentRef.current?.focus({ preventScroll: true });
327
+ }),
328
+ onUnmountAutoFocus: onCloseAutoFocus,
329
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
330
+ import_react_dismissable_layer.DismissableLayer,
331
+ {
332
+ asChild: true,
333
+ disableOutsidePointerEvents,
334
+ onEscapeKeyDown,
335
+ onPointerDownOutside,
336
+ onFocusOutside,
337
+ onInteractOutside,
338
+ onDismiss,
339
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
340
+ RovingFocusGroup.Root,
341
+ {
342
+ asChild: true,
343
+ ...rovingFocusGroupScope,
344
+ dir: rootContext.dir,
345
+ orientation: "vertical",
346
+ loop,
347
+ currentTabStopId: currentItemId,
348
+ onCurrentTabStopIdChange: setCurrentItemId,
349
+ onEntryFocus: (0, import_primitive.composeEventHandlers)(onEntryFocus, (event) => {
350
+ if (!rootContext.isUsingKeyboardRef.current) event.preventDefault();
351
+ }),
352
+ preventScrollOnEntryFocus: true,
353
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
354
+ PopperPrimitive.Content,
355
+ {
356
+ role: "menu",
357
+ "aria-orientation": "vertical",
358
+ "data-state": getOpenState(context.open),
359
+ "data-huin-core-menu-content": "",
360
+ dir: rootContext.dir,
361
+ ...popperScope,
362
+ ...contentProps,
363
+ ref: composedRefs,
364
+ style: { outline: "none", ...contentProps.style },
365
+ onKeyDown: (0, import_primitive.composeEventHandlers)(contentProps.onKeyDown, (event) => {
366
+ const target = event.target;
367
+ const isKeyDownInside = target.closest("[data-huin-core-menu-content]") === event.currentTarget;
368
+ const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
369
+ const isCharacterKey = event.key.length === 1;
370
+ if (isKeyDownInside) {
371
+ if (event.key === "Tab") event.preventDefault();
372
+ if (!isModifierKey && isCharacterKey) handleTypeaheadSearch(event.key);
373
+ }
374
+ const content = contentRef.current;
375
+ if (event.target !== content) return;
376
+ if (!FIRST_LAST_KEYS.includes(event.key)) return;
377
+ event.preventDefault();
378
+ const items = getItems().filter((item) => !item.disabled);
379
+ const candidateNodes = items.map((item) => item.ref.current);
380
+ if (LAST_KEYS.includes(event.key)) candidateNodes.reverse();
381
+ focusFirst(candidateNodes);
382
+ }),
383
+ onBlur: (0, import_primitive.composeEventHandlers)(props.onBlur, (event) => {
384
+ if (!event.currentTarget.contains(event.target)) {
385
+ window.clearTimeout(timerRef.current);
386
+ searchRef.current = "";
387
+ }
388
+ }),
389
+ onPointerMove: (0, import_primitive.composeEventHandlers)(
390
+ props.onPointerMove,
391
+ whenMouse((event) => {
392
+ const target = event.target;
393
+ const pointerXHasChanged = lastPointerXRef.current !== event.clientX;
394
+ if (event.currentTarget.contains(target) && pointerXHasChanged) {
395
+ const newDir = event.clientX > lastPointerXRef.current ? "right" : "left";
396
+ pointerDirRef.current = newDir;
397
+ lastPointerXRef.current = event.clientX;
398
+ }
399
+ })
400
+ )
401
+ }
402
+ )
403
+ }
404
+ )
405
+ }
406
+ )
407
+ }
408
+ ) })
409
+ }
410
+ );
411
+ }
412
+ );
413
+ MenuContent.displayName = CONTENT_NAME;
414
+ var GROUP_NAME = "MenuGroup";
415
+ var MenuGroup = React.forwardRef(
416
+ (props, forwardedRef) => {
417
+ const { __scopeMenu, ...groupProps } = props;
418
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_primitive.Primitive.div, { role: "group", ...groupProps, ref: forwardedRef });
419
+ }
420
+ );
421
+ MenuGroup.displayName = GROUP_NAME;
422
+ var LABEL_NAME = "MenuLabel";
423
+ var MenuLabel = React.forwardRef(
424
+ (props, forwardedRef) => {
425
+ const { __scopeMenu, ...labelProps } = props;
426
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_primitive.Primitive.div, { ...labelProps, ref: forwardedRef });
427
+ }
428
+ );
429
+ MenuLabel.displayName = LABEL_NAME;
430
+ var ITEM_NAME = "MenuItem";
431
+ var ITEM_SELECT = "menu.itemSelect";
432
+ var MenuItem = React.forwardRef(
433
+ (props, forwardedRef) => {
434
+ const { disabled = false, onSelect, ...itemProps } = props;
435
+ const ref = React.useRef(null);
436
+ const rootContext = useMenuRootContext(ITEM_NAME, props.__scopeMenu);
437
+ const contentContext = useMenuContentContext(ITEM_NAME, props.__scopeMenu);
438
+ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, ref);
439
+ const isPointerDownRef = React.useRef(false);
440
+ const handleSelect = () => {
441
+ const menuItem = ref.current;
442
+ if (!disabled && menuItem) {
443
+ const itemSelectEvent = new CustomEvent(ITEM_SELECT, { bubbles: true, cancelable: true });
444
+ menuItem.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { once: true });
445
+ (0, import_react_primitive.dispatchDiscreteCustomEvent)(menuItem, itemSelectEvent);
446
+ if (itemSelectEvent.defaultPrevented) {
447
+ isPointerDownRef.current = false;
448
+ } else {
449
+ rootContext.onClose();
450
+ }
451
+ }
452
+ };
453
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
454
+ MenuItemImpl,
455
+ {
456
+ ...itemProps,
457
+ ref: composedRefs,
458
+ disabled,
459
+ onClick: (0, import_primitive.composeEventHandlers)(props.onClick, handleSelect),
460
+ onPointerDown: (event) => {
461
+ props.onPointerDown?.(event);
462
+ isPointerDownRef.current = true;
463
+ },
464
+ onPointerUp: (0, import_primitive.composeEventHandlers)(props.onPointerUp, (event) => {
465
+ if (!isPointerDownRef.current) event.currentTarget?.click();
466
+ }),
467
+ onKeyDown: (0, import_primitive.composeEventHandlers)(props.onKeyDown, (event) => {
468
+ const isTypingAhead = contentContext.searchRef.current !== "";
469
+ if (disabled || isTypingAhead && event.key === " ") return;
470
+ if (SELECTION_KEYS.includes(event.key)) {
471
+ event.currentTarget.click();
472
+ event.preventDefault();
473
+ }
474
+ })
475
+ }
476
+ );
477
+ }
478
+ );
479
+ MenuItem.displayName = ITEM_NAME;
480
+ var MenuItemImpl = React.forwardRef(
481
+ (props, forwardedRef) => {
482
+ const { __scopeMenu, disabled = false, textValue, ...itemProps } = props;
483
+ const contentContext = useMenuContentContext(ITEM_NAME, __scopeMenu);
484
+ const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu);
485
+ const ref = React.useRef(null);
486
+ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, ref);
487
+ const [isFocused, setIsFocused] = React.useState(false);
488
+ const [textContent, setTextContent] = React.useState("");
489
+ React.useEffect(() => {
490
+ const menuItem = ref.current;
491
+ if (menuItem) {
492
+ setTextContent((menuItem.textContent ?? "").trim());
493
+ }
494
+ }, [itemProps.children]);
495
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
496
+ Collection.ItemSlot,
497
+ {
498
+ scope: __scopeMenu,
499
+ disabled,
500
+ textValue: textValue ?? textContent,
501
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RovingFocusGroup.Item, { asChild: true, ...rovingFocusGroupScope, focusable: !disabled, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
502
+ import_react_primitive.Primitive.div,
503
+ {
504
+ role: "menuitem",
505
+ "data-highlighted": isFocused ? "" : void 0,
506
+ "aria-disabled": disabled || void 0,
507
+ "data-disabled": disabled ? "" : void 0,
508
+ ...itemProps,
509
+ ref: composedRefs,
510
+ onPointerMove: (0, import_primitive.composeEventHandlers)(
511
+ props.onPointerMove,
512
+ whenMouse((event) => {
513
+ if (disabled) {
514
+ contentContext.onItemLeave(event);
515
+ } else {
516
+ contentContext.onItemEnter(event);
517
+ if (!event.defaultPrevented) {
518
+ const item = event.currentTarget;
519
+ item.focus({ preventScroll: true });
520
+ }
521
+ }
522
+ })
523
+ ),
524
+ onPointerLeave: (0, import_primitive.composeEventHandlers)(
525
+ props.onPointerLeave,
526
+ whenMouse((event) => contentContext.onItemLeave(event))
527
+ ),
528
+ onFocus: (0, import_primitive.composeEventHandlers)(props.onFocus, () => setIsFocused(true)),
529
+ onBlur: (0, import_primitive.composeEventHandlers)(props.onBlur, () => setIsFocused(false))
530
+ }
531
+ ) })
532
+ }
533
+ );
534
+ }
535
+ );
536
+ var CHECKBOX_ITEM_NAME = "MenuCheckboxItem";
537
+ var MenuCheckboxItem = React.forwardRef(
538
+ (props, forwardedRef) => {
539
+ const { checked = false, onCheckedChange, ...checkboxItemProps } = props;
540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemIndicatorProvider, { scope: props.__scopeMenu, checked, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
541
+ MenuItem,
542
+ {
543
+ role: "menuitemcheckbox",
544
+ "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
545
+ ...checkboxItemProps,
546
+ ref: forwardedRef,
547
+ "data-state": getCheckedState(checked),
548
+ onSelect: (0, import_primitive.composeEventHandlers)(
549
+ checkboxItemProps.onSelect,
550
+ () => onCheckedChange?.(isIndeterminate(checked) ? true : !checked),
551
+ { checkForDefaultPrevented: false }
552
+ )
553
+ }
554
+ ) });
555
+ }
556
+ );
557
+ MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME;
558
+ var RADIO_GROUP_NAME = "MenuRadioGroup";
559
+ var [RadioGroupProvider, useRadioGroupContext] = createMenuContext(
560
+ RADIO_GROUP_NAME,
561
+ { value: void 0, onValueChange: () => {
562
+ } }
563
+ );
564
+ var MenuRadioGroup = React.forwardRef(
565
+ (props, forwardedRef) => {
566
+ const { value, onValueChange, ...groupProps } = props;
567
+ const handleValueChange = (0, import_react_use_callback_ref.useCallbackRef)(onValueChange);
568
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadioGroupProvider, { scope: props.__scopeMenu, value, onValueChange: handleValueChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuGroup, { ...groupProps, ref: forwardedRef }) });
569
+ }
570
+ );
571
+ MenuRadioGroup.displayName = RADIO_GROUP_NAME;
572
+ var RADIO_ITEM_NAME = "MenuRadioItem";
573
+ var MenuRadioItem = React.forwardRef(
574
+ (props, forwardedRef) => {
575
+ const { value, ...radioItemProps } = props;
576
+ const context = useRadioGroupContext(RADIO_ITEM_NAME, props.__scopeMenu);
577
+ const checked = value === context.value;
578
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemIndicatorProvider, { scope: props.__scopeMenu, checked, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
579
+ MenuItem,
580
+ {
581
+ role: "menuitemradio",
582
+ "aria-checked": checked,
583
+ ...radioItemProps,
584
+ ref: forwardedRef,
585
+ "data-state": getCheckedState(checked),
586
+ onSelect: (0, import_primitive.composeEventHandlers)(
587
+ radioItemProps.onSelect,
588
+ () => context.onValueChange?.(value),
589
+ { checkForDefaultPrevented: false }
590
+ )
591
+ }
592
+ ) });
593
+ }
594
+ );
595
+ MenuRadioItem.displayName = RADIO_ITEM_NAME;
596
+ var ITEM_INDICATOR_NAME = "MenuItemIndicator";
597
+ var [ItemIndicatorProvider, useItemIndicatorContext] = createMenuContext(
598
+ ITEM_INDICATOR_NAME,
599
+ { checked: false }
600
+ );
601
+ var MenuItemIndicator = React.forwardRef(
602
+ (props, forwardedRef) => {
603
+ const { __scopeMenu, forceMount, ...itemIndicatorProps } = props;
604
+ const indicatorContext = useItemIndicatorContext(ITEM_INDICATOR_NAME, __scopeMenu);
605
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
606
+ import_react_presence.Presence,
607
+ {
608
+ present: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === true,
609
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
610
+ import_react_primitive.Primitive.span,
611
+ {
612
+ ...itemIndicatorProps,
613
+ ref: forwardedRef,
614
+ "data-state": getCheckedState(indicatorContext.checked)
615
+ }
616
+ )
617
+ }
618
+ );
619
+ }
620
+ );
621
+ MenuItemIndicator.displayName = ITEM_INDICATOR_NAME;
622
+ var SEPARATOR_NAME = "MenuSeparator";
623
+ var MenuSeparator = React.forwardRef(
624
+ (props, forwardedRef) => {
625
+ const { __scopeMenu, ...separatorProps } = props;
626
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
627
+ import_react_primitive.Primitive.div,
628
+ {
629
+ role: "separator",
630
+ "aria-orientation": "horizontal",
631
+ ...separatorProps,
632
+ ref: forwardedRef
633
+ }
634
+ );
635
+ }
636
+ );
637
+ MenuSeparator.displayName = SEPARATOR_NAME;
638
+ var ARROW_NAME = "MenuArrow";
639
+ var MenuArrow = React.forwardRef(
640
+ (props, forwardedRef) => {
641
+ const { __scopeMenu, ...arrowProps } = props;
642
+ const popperScope = usePopperScope(__scopeMenu);
643
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Arrow, { ...popperScope, ...arrowProps, ref: forwardedRef });
644
+ }
645
+ );
646
+ MenuArrow.displayName = ARROW_NAME;
647
+ var SUB_NAME = "MenuSub";
648
+ var [MenuSubProvider, useMenuSubContext] = createMenuContext(SUB_NAME);
649
+ var MenuSub = (props) => {
650
+ const { __scopeMenu, children, open = false, onOpenChange } = props;
651
+ const parentMenuContext = useMenuContext(SUB_NAME, __scopeMenu);
652
+ const popperScope = usePopperScope(__scopeMenu);
653
+ const [trigger, setTrigger] = React.useState(null);
654
+ const [content, setContent] = React.useState(null);
655
+ const handleOpenChange = (0, import_react_use_callback_ref.useCallbackRef)(onOpenChange);
656
+ React.useEffect(() => {
657
+ if (parentMenuContext.open === false) handleOpenChange(false);
658
+ return () => handleOpenChange(false);
659
+ }, [parentMenuContext.open, handleOpenChange]);
660
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
661
+ MenuProvider,
662
+ {
663
+ scope: __scopeMenu,
664
+ open,
665
+ onOpenChange: handleOpenChange,
666
+ content,
667
+ onContentChange: setContent,
668
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
669
+ MenuSubProvider,
670
+ {
671
+ scope: __scopeMenu,
672
+ contentId: (0, import_react_id.useId)(),
673
+ triggerId: (0, import_react_id.useId)(),
674
+ trigger,
675
+ onTriggerChange: setTrigger,
676
+ children
677
+ }
678
+ )
679
+ }
680
+ ) });
681
+ };
682
+ MenuSub.displayName = SUB_NAME;
683
+ var SUB_TRIGGER_NAME = "MenuSubTrigger";
684
+ var MenuSubTrigger = React.forwardRef(
685
+ (props, forwardedRef) => {
686
+ const context = useMenuContext(SUB_TRIGGER_NAME, props.__scopeMenu);
687
+ const rootContext = useMenuRootContext(SUB_TRIGGER_NAME, props.__scopeMenu);
688
+ const subContext = useMenuSubContext(SUB_TRIGGER_NAME, props.__scopeMenu);
689
+ const contentContext = useMenuContentContext(SUB_TRIGGER_NAME, props.__scopeMenu);
690
+ const openTimerRef = React.useRef(null);
691
+ const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext;
692
+ const scope = { __scopeMenu: props.__scopeMenu };
693
+ const clearOpenTimer = React.useCallback(() => {
694
+ if (openTimerRef.current) window.clearTimeout(openTimerRef.current);
695
+ openTimerRef.current = null;
696
+ }, []);
697
+ React.useEffect(() => clearOpenTimer, [clearOpenTimer]);
698
+ React.useEffect(() => {
699
+ const pointerGraceTimer = pointerGraceTimerRef.current;
700
+ return () => {
701
+ window.clearTimeout(pointerGraceTimer);
702
+ onPointerGraceIntentChange(null);
703
+ };
704
+ }, [pointerGraceTimerRef, onPointerGraceIntentChange]);
705
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MenuAnchor, { asChild: true, ...scope, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
706
+ MenuItemImpl,
707
+ {
708
+ id: subContext.triggerId,
709
+ "aria-haspopup": "menu",
710
+ "aria-expanded": context.open,
711
+ "aria-controls": subContext.contentId,
712
+ "data-state": getOpenState(context.open),
713
+ ...props,
714
+ ref: (0, import_react_compose_refs.composeRefs)(forwardedRef, subContext.onTriggerChange),
715
+ onClick: (event) => {
716
+ props.onClick?.(event);
717
+ if (props.disabled || event.defaultPrevented) return;
718
+ event.currentTarget.focus();
719
+ if (!context.open) context.onOpenChange(true);
720
+ },
721
+ onPointerMove: (0, import_primitive.composeEventHandlers)(
722
+ props.onPointerMove,
723
+ whenMouse((event) => {
724
+ contentContext.onItemEnter(event);
725
+ if (event.defaultPrevented) return;
726
+ if (!props.disabled && !context.open && !openTimerRef.current) {
727
+ contentContext.onPointerGraceIntentChange(null);
728
+ openTimerRef.current = window.setTimeout(() => {
729
+ context.onOpenChange(true);
730
+ clearOpenTimer();
731
+ }, 100);
732
+ }
733
+ })
734
+ ),
735
+ onPointerLeave: (0, import_primitive.composeEventHandlers)(
736
+ props.onPointerLeave,
737
+ whenMouse((event) => {
738
+ clearOpenTimer();
739
+ const contentRect = context.content?.getBoundingClientRect();
740
+ if (contentRect) {
741
+ const side = context.content?.dataset.side;
742
+ const rightSide = side === "right";
743
+ const bleed = rightSide ? -5 : 5;
744
+ const contentNearEdge = contentRect[rightSide ? "left" : "right"];
745
+ const contentFarEdge = contentRect[rightSide ? "right" : "left"];
746
+ contentContext.onPointerGraceIntentChange({
747
+ area: [
748
+ // Apply a bleed on clientX to ensure that our exit point is
749
+ // consistently within polygon bounds
750
+ { x: event.clientX + bleed, y: event.clientY },
751
+ { x: contentNearEdge, y: contentRect.top },
752
+ { x: contentFarEdge, y: contentRect.top },
753
+ { x: contentFarEdge, y: contentRect.bottom },
754
+ { x: contentNearEdge, y: contentRect.bottom }
755
+ ],
756
+ side
757
+ });
758
+ window.clearTimeout(pointerGraceTimerRef.current);
759
+ pointerGraceTimerRef.current = window.setTimeout(
760
+ () => contentContext.onPointerGraceIntentChange(null),
761
+ 300
762
+ );
763
+ } else {
764
+ contentContext.onTriggerLeave(event);
765
+ if (event.defaultPrevented) return;
766
+ contentContext.onPointerGraceIntentChange(null);
767
+ }
768
+ })
769
+ ),
770
+ onKeyDown: (0, import_primitive.composeEventHandlers)(props.onKeyDown, (event) => {
771
+ const isTypingAhead = contentContext.searchRef.current !== "";
772
+ if (props.disabled || isTypingAhead && event.key === " ") return;
773
+ if (SUB_OPEN_KEYS[rootContext.dir].includes(event.key)) {
774
+ context.onOpenChange(true);
775
+ context.content?.focus();
776
+ event.preventDefault();
777
+ }
778
+ })
779
+ }
780
+ ) });
781
+ }
782
+ );
783
+ MenuSubTrigger.displayName = SUB_TRIGGER_NAME;
784
+ var SUB_CONTENT_NAME = "MenuSubContent";
785
+ var MenuSubContent = React.forwardRef(
786
+ (props, forwardedRef) => {
787
+ const portalContext = usePortalContext(CONTENT_NAME, props.__scopeMenu);
788
+ const { forceMount = portalContext.forceMount, ...subContentProps } = props;
789
+ const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
790
+ const rootContext = useMenuRootContext(CONTENT_NAME, props.__scopeMenu);
791
+ const subContext = useMenuSubContext(SUB_CONTENT_NAME, props.__scopeMenu);
792
+ const ref = React.useRef(null);
793
+ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, ref);
794
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Provider, { scope: props.__scopeMenu, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_presence.Presence, { present: forceMount || context.open, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Collection.Slot, { scope: props.__scopeMenu, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
795
+ MenuContentImpl,
796
+ {
797
+ id: subContext.contentId,
798
+ "aria-labelledby": subContext.triggerId,
799
+ ...subContentProps,
800
+ ref: composedRefs,
801
+ align: "start",
802
+ side: rootContext.dir === "rtl" ? "left" : "right",
803
+ disableOutsidePointerEvents: false,
804
+ disableOutsideScroll: false,
805
+ trapFocus: false,
806
+ onOpenAutoFocus: (event) => {
807
+ if (rootContext.isUsingKeyboardRef.current) ref.current?.focus();
808
+ event.preventDefault();
809
+ },
810
+ onCloseAutoFocus: (event) => event.preventDefault(),
811
+ onFocusOutside: (0, import_primitive.composeEventHandlers)(props.onFocusOutside, (event) => {
812
+ if (event.target !== subContext.trigger) context.onOpenChange(false);
813
+ }),
814
+ onEscapeKeyDown: (0, import_primitive.composeEventHandlers)(props.onEscapeKeyDown, (event) => {
815
+ rootContext.onClose();
816
+ event.preventDefault();
817
+ }),
818
+ onKeyDown: (0, import_primitive.composeEventHandlers)(props.onKeyDown, (event) => {
819
+ const isKeyDownInside = event.currentTarget.contains(event.target);
820
+ const isCloseKey = SUB_CLOSE_KEYS[rootContext.dir].includes(event.key);
821
+ if (isKeyDownInside && isCloseKey) {
822
+ context.onOpenChange(false);
823
+ subContext.trigger?.focus();
824
+ event.preventDefault();
825
+ }
826
+ })
827
+ }
828
+ ) }) }) });
829
+ }
830
+ );
831
+ MenuSubContent.displayName = SUB_CONTENT_NAME;
832
+ function getOpenState(open) {
833
+ return open ? "open" : "closed";
834
+ }
835
+ function isIndeterminate(checked) {
836
+ return checked === "indeterminate";
837
+ }
838
+ function getCheckedState(checked) {
839
+ return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
840
+ }
841
+ function focusFirst(candidates) {
842
+ const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
843
+ for (const candidate of candidates) {
844
+ if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
845
+ candidate.focus();
846
+ if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
847
+ }
848
+ }
849
+ function wrapArray(array, startIndex) {
850
+ return array.map((_, index) => array[(startIndex + index) % array.length]);
851
+ }
852
+ function getNextMatch(values, search, currentMatch) {
853
+ const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
854
+ const normalizedSearch = isRepeated ? search[0] : search;
855
+ const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
856
+ let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
857
+ const excludeCurrentMatch = normalizedSearch.length === 1;
858
+ if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch);
859
+ const nextMatch = wrappedValues.find(
860
+ (value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase())
861
+ );
862
+ return nextMatch !== currentMatch ? nextMatch : void 0;
863
+ }
864
+ function isPointInPolygon(point, polygon) {
865
+ const { x, y } = point;
866
+ let inside = false;
867
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
868
+ const xi = polygon[i].x;
869
+ const yi = polygon[i].y;
870
+ const xj = polygon[j].x;
871
+ const yj = polygon[j].y;
872
+ const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
873
+ if (intersect) inside = !inside;
874
+ }
875
+ return inside;
876
+ }
877
+ function isPointerInGraceArea(event, area) {
878
+ if (!area) return false;
879
+ const cursorPos = { x: event.clientX, y: event.clientY };
880
+ return isPointInPolygon(cursorPos, area);
881
+ }
882
+ function whenMouse(handler) {
883
+ return (event) => event.pointerType === "mouse" ? handler(event) : void 0;
884
+ }
885
+ var Root3 = Menu;
886
+ var Anchor2 = MenuAnchor;
887
+ var Portal = MenuPortal;
888
+ var Content2 = MenuContent;
889
+ var Group = MenuGroup;
890
+ var Label = MenuLabel;
891
+ var Item2 = MenuItem;
892
+ var CheckboxItem = MenuCheckboxItem;
893
+ var RadioGroup = MenuRadioGroup;
894
+ var RadioItem = MenuRadioItem;
895
+ var ItemIndicator = MenuItemIndicator;
896
+ var Separator = MenuSeparator;
897
+ var Arrow2 = MenuArrow;
898
+ var Sub = MenuSub;
899
+ var SubTrigger = MenuSubTrigger;
900
+ var SubContent = MenuSubContent;
901
+ //# sourceMappingURL=index.js.map