@huin-core/react-navigation-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.mjs ADDED
@@ -0,0 +1,970 @@
1
+ "use client";
2
+
3
+ // packages/react/navigation-menu/src/NavigationMenu.tsx
4
+ import * as React6 from "react";
5
+ import { createContextScope } from "@huin-core/react-context";
6
+ import { Primitive as Primitive6 } from "@huin-core/react-primitive";
7
+ import { useControllableState as useControllableState2 } from "@huin-core/react-use-controllable-state";
8
+ import { useComposedRefs as useComposedRefs3 } from "@huin-core/react-compose-refs";
9
+ import { useDirection } from "@huin-core/react-direction";
10
+ import { createCollection } from "@huin-core/react-collection";
11
+
12
+ // packages/react/navigation-menu/src/NavigationMenuSub.tsx
13
+ import React from "react";
14
+ import { Primitive } from "@huin-core/react-primitive";
15
+ import { useControllableState } from "@huin-core/react-use-controllable-state";
16
+ import { useId } from "@huin-core/react-id";
17
+ import { usePrevious } from "@huin-core/react-use-previous";
18
+ import { useCallbackRef } from "@huin-core/react-use-callback-ref";
19
+ import { jsx } from "react/jsx-runtime";
20
+ var SUB_NAME = "NavigationMenuSub";
21
+ var NavigationMenuSub = React.forwardRef((props, forwardedRef) => {
22
+ const {
23
+ __scopeNavigationMenu,
24
+ value: valueProp,
25
+ onValueChange,
26
+ defaultValue,
27
+ orientation = "horizontal",
28
+ ...subProps
29
+ } = props;
30
+ const context = useNavigationMenuContext(SUB_NAME, __scopeNavigationMenu);
31
+ const [value = "", setValue] = useControllableState({
32
+ prop: valueProp,
33
+ onChange: onValueChange,
34
+ defaultProp: defaultValue
35
+ });
36
+ return /* @__PURE__ */ jsx(
37
+ NavigationMenuProvider,
38
+ {
39
+ scope: __scopeNavigationMenu,
40
+ isRootMenu: false,
41
+ value,
42
+ dir: context.dir,
43
+ orientation,
44
+ rootNavigationMenu: context.rootNavigationMenu,
45
+ onTriggerEnter: (itemValue) => setValue(itemValue),
46
+ onItemSelect: (itemValue) => setValue(itemValue),
47
+ onItemDismiss: () => setValue(""),
48
+ children: /* @__PURE__ */ jsx(
49
+ Primitive.div,
50
+ {
51
+ "data-orientation": orientation,
52
+ ...subProps,
53
+ ref: forwardedRef
54
+ }
55
+ )
56
+ }
57
+ );
58
+ });
59
+ NavigationMenuSub.displayName = SUB_NAME;
60
+ var NavigationMenuProvider = (props) => {
61
+ const {
62
+ scope,
63
+ isRootMenu,
64
+ rootNavigationMenu,
65
+ dir,
66
+ orientation,
67
+ children,
68
+ value,
69
+ onItemSelect,
70
+ onItemDismiss,
71
+ onTriggerEnter,
72
+ onTriggerLeave,
73
+ onContentEnter,
74
+ onContentLeave
75
+ } = props;
76
+ const [viewport, setViewport] = React.useState(null);
77
+ const [viewportContent, setViewportContent] = React.useState(/* @__PURE__ */ new Map());
78
+ const [indicatorTrack, setIndicatorTrack] = React.useState(null);
79
+ return /* @__PURE__ */ jsx(
80
+ NavigationMenuProviderImpl,
81
+ {
82
+ scope,
83
+ isRootMenu,
84
+ rootNavigationMenu,
85
+ value,
86
+ previousValue: usePrevious(value),
87
+ baseId: useId(),
88
+ dir,
89
+ orientation,
90
+ viewport,
91
+ onViewportChange: setViewport,
92
+ indicatorTrack,
93
+ onIndicatorTrackChange: setIndicatorTrack,
94
+ onTriggerEnter: useCallbackRef(onTriggerEnter),
95
+ onTriggerLeave: useCallbackRef(onTriggerLeave),
96
+ onContentEnter: useCallbackRef(onContentEnter),
97
+ onContentLeave: useCallbackRef(onContentLeave),
98
+ onItemSelect: useCallbackRef(onItemSelect),
99
+ onItemDismiss: useCallbackRef(onItemDismiss),
100
+ onViewportContentChange: React.useCallback(
101
+ (contentValue, contentData) => {
102
+ setViewportContent((prevContent) => {
103
+ prevContent.set(contentValue, contentData);
104
+ return new Map(prevContent);
105
+ });
106
+ },
107
+ []
108
+ ),
109
+ onViewportContentRemove: React.useCallback((contentValue) => {
110
+ setViewportContent((prevContent) => {
111
+ if (!prevContent.has(contentValue)) return prevContent;
112
+ prevContent.delete(contentValue);
113
+ return new Map(prevContent);
114
+ });
115
+ }, []),
116
+ children: /* @__PURE__ */ jsx(Collection.Provider, { scope, children: /* @__PURE__ */ jsx(ViewportContentProvider, { scope, items: viewportContent, children }) })
117
+ }
118
+ );
119
+ };
120
+
121
+ // packages/react/navigation-menu/src/NavigationMenuItem.tsx
122
+ import React5 from "react";
123
+ import { Primitive as Primitive5 } from "@huin-core/react-primitive";
124
+ import { useId as useId2 } from "@huin-core/react-id";
125
+
126
+ // packages/react/navigation-menu/src/NavigationMenuLink.tsx
127
+ import React4 from "react";
128
+ import { dispatchDiscreteCustomEvent, Primitive as Primitive4 } from "@huin-core/react-primitive";
129
+ import { composeEventHandlers as composeEventHandlers3 } from "@huin-core/primitive";
130
+
131
+ // packages/react/navigation-menu/src/NavigationMenuContent.tsx
132
+ import React3 from "react";
133
+ import { useComposedRefs as useComposedRefs2 } from "@huin-core/react-compose-refs";
134
+ import { DismissableLayer } from "@huin-core/react-dismissable-layer";
135
+ import { Presence as Presence2 } from "@huin-core/react-presence";
136
+ import { composeEventHandlers as composeEventHandlers2 } from "@huin-core/primitive";
137
+ import { useLayoutEffect as useLayoutEffect2 } from "@huin-core/react-use-layout-effect";
138
+
139
+ // packages/react/navigation-menu/src/NavigationMenuViewport.tsx
140
+ import React2 from "react";
141
+ import { Primitive as Primitive2 } from "@huin-core/react-primitive";
142
+ import { Presence } from "@huin-core/react-presence";
143
+ import { useCallbackRef as useCallbackRef2 } from "@huin-core/react-use-callback-ref";
144
+ import { useLayoutEffect } from "@huin-core/react-use-layout-effect";
145
+ import { composeRefs, useComposedRefs } from "@huin-core/react-compose-refs";
146
+ import { composeEventHandlers } from "@huin-core/primitive";
147
+ import { jsx as jsx2 } from "react/jsx-runtime";
148
+ var VIEWPORT_NAME = "NavigationMenuViewport";
149
+ var NavigationMenuViewport = React2.forwardRef((props, forwardedRef) => {
150
+ const { forceMount, ...viewportProps } = props;
151
+ const context = useNavigationMenuContext(
152
+ VIEWPORT_NAME,
153
+ props.__scopeNavigationMenu
154
+ );
155
+ const open = Boolean(context.value);
156
+ return /* @__PURE__ */ jsx2(Presence, { present: forceMount || open, children: /* @__PURE__ */ jsx2(NavigationMenuViewportImpl, { ...viewportProps, ref: forwardedRef }) });
157
+ });
158
+ NavigationMenuViewport.displayName = VIEWPORT_NAME;
159
+ var NavigationMenuViewportImpl = React2.forwardRef((props, forwardedRef) => {
160
+ const { __scopeNavigationMenu, ...viewportImplProps } = props;
161
+ const context = useNavigationMenuContext(
162
+ VIEWPORT_NAME,
163
+ __scopeNavigationMenu
164
+ );
165
+ const composedRefs = useComposedRefs(forwardedRef, context.onViewportChange);
166
+ const viewportContentContext = useViewportContentContext(
167
+ CONTENT_NAME,
168
+ props.__scopeNavigationMenu
169
+ );
170
+ const [size, setSize] = React2.useState(null);
171
+ const [content, setContent] = React2.useState(null);
172
+ const viewportWidth = size ? size?.width + "px" : void 0;
173
+ const viewportHeight = size ? size?.height + "px" : void 0;
174
+ const open = Boolean(context.value);
175
+ const activeContentValue = open ? context.value : context.previousValue;
176
+ const handleSizeChange = () => {
177
+ if (content)
178
+ setSize({ width: content.offsetWidth, height: content.offsetHeight });
179
+ };
180
+ useResizeObserver(content, handleSizeChange);
181
+ return /* @__PURE__ */ jsx2(
182
+ Primitive2.div,
183
+ {
184
+ "data-state": getOpenState(open),
185
+ "data-orientation": context.orientation,
186
+ ...viewportImplProps,
187
+ ref: composedRefs,
188
+ style: {
189
+ // Prevent interaction when animating out
190
+ pointerEvents: !open && context.isRootMenu ? "none" : void 0,
191
+ ["--huin-core-navigation-menu-viewport-width"]: viewportWidth,
192
+ ["--huin-core-navigation-menu-viewport-height"]: viewportHeight,
193
+ ...viewportImplProps.style
194
+ },
195
+ onPointerEnter: composeEventHandlers(
196
+ props.onPointerEnter,
197
+ context.onContentEnter
198
+ ),
199
+ onPointerLeave: composeEventHandlers(
200
+ props.onPointerLeave,
201
+ whenMouse(context.onContentLeave)
202
+ ),
203
+ children: Array.from(viewportContentContext.items).map(
204
+ ([value, { ref, forceMount, ...props2 }]) => {
205
+ const isActive = activeContentValue === value;
206
+ return /* @__PURE__ */ jsx2(Presence, { present: forceMount || isActive, children: /* @__PURE__ */ jsx2(
207
+ NavigationMenuContentImpl,
208
+ {
209
+ ...props2,
210
+ ref: composeRefs(ref, (node) => {
211
+ if (isActive && node) setContent(node);
212
+ })
213
+ }
214
+ ) }, value);
215
+ }
216
+ )
217
+ }
218
+ );
219
+ });
220
+ function useResizeObserver(element, onResize) {
221
+ const handleResize = useCallbackRef2(onResize);
222
+ useLayoutEffect(() => {
223
+ let rAF = 0;
224
+ if (element) {
225
+ const resizeObserver = new ResizeObserver(() => {
226
+ cancelAnimationFrame(rAF);
227
+ rAF = window.requestAnimationFrame(handleResize);
228
+ });
229
+ resizeObserver.observe(element);
230
+ return () => {
231
+ window.cancelAnimationFrame(rAF);
232
+ resizeObserver.unobserve(element);
233
+ };
234
+ }
235
+ }, [element, handleResize]);
236
+ }
237
+ function getOpenState(open) {
238
+ return open ? "open" : "closed";
239
+ }
240
+ function whenMouse(handler) {
241
+ return (event) => event.pointerType === "mouse" ? handler(event) : void 0;
242
+ }
243
+
244
+ // packages/react/navigation-menu/src/NavigationMenuContent.tsx
245
+ import { Primitive as Primitive3 } from "@huin-core/react-primitive";
246
+ import { jsx as jsx3 } from "react/jsx-runtime";
247
+ var CONTENT_NAME = "NavigationMenuContent";
248
+ var NavigationMenuContent = React3.forwardRef((props, forwardedRef) => {
249
+ const { forceMount, ...contentProps } = props;
250
+ const context = useNavigationMenuContext(
251
+ CONTENT_NAME,
252
+ props.__scopeNavigationMenu
253
+ );
254
+ const itemContext = useNavigationMenuItemContext(
255
+ CONTENT_NAME,
256
+ props.__scopeNavigationMenu
257
+ );
258
+ const composedRefs = useComposedRefs2(itemContext.contentRef, forwardedRef);
259
+ const open = itemContext.value === context.value;
260
+ const commonProps = {
261
+ value: itemContext.value,
262
+ triggerRef: itemContext.triggerRef,
263
+ focusProxyRef: itemContext.focusProxyRef,
264
+ wasEscapeCloseRef: itemContext.wasEscapeCloseRef,
265
+ onContentFocusOutside: itemContext.onContentFocusOutside,
266
+ onRootContentClose: itemContext.onRootContentClose,
267
+ ...contentProps
268
+ };
269
+ return !context.viewport ? /* @__PURE__ */ jsx3(Presence2, { present: forceMount || open, children: /* @__PURE__ */ jsx3(
270
+ NavigationMenuContentImpl,
271
+ {
272
+ "data-state": getOpenState(open),
273
+ ...commonProps,
274
+ ref: composedRefs,
275
+ onPointerEnter: composeEventHandlers2(
276
+ props.onPointerEnter,
277
+ context.onContentEnter
278
+ ),
279
+ onPointerLeave: composeEventHandlers2(
280
+ props.onPointerLeave,
281
+ whenMouse(context.onContentLeave)
282
+ ),
283
+ style: {
284
+ // Prevent interaction when animating out
285
+ pointerEvents: !open && context.isRootMenu ? "none" : void 0,
286
+ ...commonProps.style
287
+ }
288
+ }
289
+ ) }) : /* @__PURE__ */ jsx3(
290
+ ViewportContentMounter,
291
+ {
292
+ forceMount,
293
+ ...commonProps,
294
+ ref: composedRefs
295
+ }
296
+ );
297
+ });
298
+ NavigationMenuContent.displayName = CONTENT_NAME;
299
+ var ViewportContentMounter = React3.forwardRef((props, forwardedRef) => {
300
+ const context = useNavigationMenuContext(
301
+ CONTENT_NAME,
302
+ props.__scopeNavigationMenu
303
+ );
304
+ const { onViewportContentChange, onViewportContentRemove } = context;
305
+ useLayoutEffect2(() => {
306
+ onViewportContentChange(props.value, {
307
+ ref: forwardedRef,
308
+ ...props
309
+ });
310
+ }, [props, forwardedRef, onViewportContentChange]);
311
+ useLayoutEffect2(() => {
312
+ return () => onViewportContentRemove(props.value);
313
+ }, [props.value, onViewportContentRemove]);
314
+ return null;
315
+ });
316
+ var ROOT_CONTENT_DISMISS = "navigationMenu.rootContentDismiss";
317
+ var NavigationMenuContentImpl = React3.forwardRef((props, forwardedRef) => {
318
+ const {
319
+ __scopeNavigationMenu,
320
+ value,
321
+ triggerRef,
322
+ focusProxyRef,
323
+ wasEscapeCloseRef,
324
+ onRootContentClose,
325
+ onContentFocusOutside,
326
+ ...contentProps
327
+ } = props;
328
+ const context = useNavigationMenuContext(CONTENT_NAME, __scopeNavigationMenu);
329
+ const ref = React3.useRef(null);
330
+ const composedRefs = useComposedRefs2(ref, forwardedRef);
331
+ const triggerId = makeTriggerId(context.baseId, value);
332
+ const contentId = makeContentId(context.baseId, value);
333
+ const getItems = useCollection(__scopeNavigationMenu);
334
+ const prevMotionAttributeRef = React3.useRef(null);
335
+ const { onItemDismiss } = context;
336
+ React3.useEffect(() => {
337
+ const content = ref.current;
338
+ if (context.isRootMenu && content) {
339
+ const handleClose = () => {
340
+ onItemDismiss();
341
+ onRootContentClose();
342
+ if (content.contains(document.activeElement))
343
+ triggerRef.current?.focus();
344
+ };
345
+ content.addEventListener(ROOT_CONTENT_DISMISS, handleClose);
346
+ return () => content.removeEventListener(ROOT_CONTENT_DISMISS, handleClose);
347
+ }
348
+ }, [
349
+ context.isRootMenu,
350
+ props.value,
351
+ triggerRef,
352
+ onItemDismiss,
353
+ onRootContentClose
354
+ ]);
355
+ const motionAttribute = React3.useMemo(() => {
356
+ const items = getItems();
357
+ const values = items.map((item) => item.value);
358
+ if (context.dir === "rtl") values.reverse();
359
+ const index = values.indexOf(context.value);
360
+ const prevIndex = values.indexOf(context.previousValue);
361
+ const isSelected = value === context.value;
362
+ const wasSelected = prevIndex === values.indexOf(value);
363
+ if (!isSelected && !wasSelected) return prevMotionAttributeRef.current;
364
+ const attribute = (() => {
365
+ if (index !== prevIndex) {
366
+ if (isSelected && prevIndex !== -1)
367
+ return index > prevIndex ? "from-end" : "from-start";
368
+ if (wasSelected && index !== -1)
369
+ return index > prevIndex ? "to-start" : "to-end";
370
+ }
371
+ return null;
372
+ })();
373
+ prevMotionAttributeRef.current = attribute;
374
+ return attribute;
375
+ }, [context.previousValue, context.value, context.dir, getItems, value]);
376
+ return /* @__PURE__ */ jsx3(FocusGroup, { asChild: true, children: /* @__PURE__ */ jsx3(
377
+ DismissableLayer,
378
+ {
379
+ id: contentId,
380
+ "aria-labelledby": triggerId,
381
+ "data-motion": motionAttribute,
382
+ "data-orientation": context.orientation,
383
+ ...contentProps,
384
+ ref: composedRefs,
385
+ disableOutsidePointerEvents: false,
386
+ onDismiss: () => {
387
+ const rootContentDismissEvent = new Event(ROOT_CONTENT_DISMISS, {
388
+ bubbles: true,
389
+ cancelable: true
390
+ });
391
+ ref.current?.dispatchEvent(rootContentDismissEvent);
392
+ },
393
+ onFocusOutside: composeEventHandlers2(props.onFocusOutside, (event) => {
394
+ onContentFocusOutside();
395
+ const target = event.target;
396
+ if (context.rootNavigationMenu?.contains(target))
397
+ event.preventDefault();
398
+ }),
399
+ onPointerDownOutside: composeEventHandlers2(
400
+ props.onPointerDownOutside,
401
+ (event) => {
402
+ const target = event.target;
403
+ const isTrigger = getItems().some(
404
+ (item) => item.ref.current?.contains(target)
405
+ );
406
+ const isRootViewport = context.isRootMenu && context.viewport?.contains(target);
407
+ if (isTrigger || isRootViewport || !context.isRootMenu)
408
+ event.preventDefault();
409
+ }
410
+ ),
411
+ onKeyDown: composeEventHandlers2(props.onKeyDown, (event) => {
412
+ const isMetaKey = event.altKey || event.ctrlKey || event.metaKey;
413
+ const isTabKey = event.key === "Tab" && !isMetaKey;
414
+ if (isTabKey) {
415
+ const candidates = getTabbableCandidates(event.currentTarget);
416
+ const focusedElement = document.activeElement;
417
+ const index = candidates.findIndex(
418
+ (candidate) => candidate === focusedElement
419
+ );
420
+ const isMovingBackwards = event.shiftKey;
421
+ const nextCandidates = isMovingBackwards ? candidates.slice(0, index).reverse() : candidates.slice(index + 1, candidates.length);
422
+ if (focusFirst(nextCandidates)) {
423
+ event.preventDefault();
424
+ } else {
425
+ focusProxyRef.current?.focus();
426
+ }
427
+ }
428
+ }),
429
+ onEscapeKeyDown: composeEventHandlers2(
430
+ props.onEscapeKeyDown,
431
+ (event) => {
432
+ wasEscapeCloseRef.current = true;
433
+ }
434
+ )
435
+ }
436
+ ) });
437
+ });
438
+ var FOCUS_GROUP_NAME = "FocusGroup";
439
+ var FocusGroup = React3.forwardRef(
440
+ (props, forwardedRef) => {
441
+ const { __scopeNavigationMenu, ...groupProps } = props;
442
+ const context = useNavigationMenuContext(
443
+ FOCUS_GROUP_NAME,
444
+ __scopeNavigationMenu
445
+ );
446
+ return /* @__PURE__ */ jsx3(FocusGroupCollection.Provider, { scope: __scopeNavigationMenu, children: /* @__PURE__ */ jsx3(FocusGroupCollection.Slot, { scope: __scopeNavigationMenu, children: /* @__PURE__ */ jsx3(Primitive3.div, { dir: context.dir, ...groupProps, ref: forwardedRef }) }) });
447
+ }
448
+ );
449
+ function makeTriggerId(baseId, value) {
450
+ return `${baseId}-trigger-${value}`;
451
+ }
452
+ function makeContentId(baseId, value) {
453
+ return `${baseId}-content-${value}`;
454
+ }
455
+
456
+ // packages/react/navigation-menu/src/NavigationMenuLink.tsx
457
+ import { jsx as jsx4 } from "react/jsx-runtime";
458
+ var LINK_NAME = "NavigationMenuLink";
459
+ var LINK_SELECT = "navigationMenu.linkSelect";
460
+ var NavigationMenuLink = React4.forwardRef((props, forwardedRef) => {
461
+ const { __scopeNavigationMenu, active, onSelect, ...linkProps } = props;
462
+ return /* @__PURE__ */ jsx4(FocusGroupItem, { asChild: true, children: /* @__PURE__ */ jsx4(
463
+ Primitive4.a,
464
+ {
465
+ "data-active": active ? "" : void 0,
466
+ "aria-current": active ? "page" : void 0,
467
+ ...linkProps,
468
+ ref: forwardedRef,
469
+ onClick: composeEventHandlers3(
470
+ props.onClick,
471
+ (event) => {
472
+ const target = event.target;
473
+ const linkSelectEvent = new CustomEvent(LINK_SELECT, {
474
+ bubbles: true,
475
+ cancelable: true
476
+ });
477
+ target.addEventListener(LINK_SELECT, (event2) => onSelect?.(event2), {
478
+ once: true
479
+ });
480
+ dispatchDiscreteCustomEvent(target, linkSelectEvent);
481
+ if (!linkSelectEvent.defaultPrevented && !event.metaKey) {
482
+ const rootContentDismissEvent = new CustomEvent(
483
+ ROOT_CONTENT_DISMISS,
484
+ {
485
+ bubbles: true,
486
+ cancelable: true
487
+ }
488
+ );
489
+ dispatchDiscreteCustomEvent(target, rootContentDismissEvent);
490
+ }
491
+ },
492
+ { checkForDefaultPrevented: false }
493
+ )
494
+ }
495
+ ) });
496
+ });
497
+ NavigationMenuLink.displayName = LINK_NAME;
498
+ var ARROW_KEYS = ["ArrowRight", "ArrowLeft", "ArrowUp", "ArrowDown"];
499
+ var FOCUS_GROUP_ITEM_NAME = "FocusGroupItem";
500
+ var FocusGroupItem = React4.forwardRef((props, forwardedRef) => {
501
+ const { __scopeNavigationMenu, ...groupProps } = props;
502
+ const getItems = useFocusGroupCollection(__scopeNavigationMenu);
503
+ const context = useNavigationMenuContext(
504
+ FOCUS_GROUP_ITEM_NAME,
505
+ __scopeNavigationMenu
506
+ );
507
+ return /* @__PURE__ */ jsx4(FocusGroupCollection.ItemSlot, { scope: __scopeNavigationMenu, children: /* @__PURE__ */ jsx4(
508
+ Primitive4.button,
509
+ {
510
+ ...groupProps,
511
+ ref: forwardedRef,
512
+ onKeyDown: composeEventHandlers3(props.onKeyDown, (event) => {
513
+ const isFocusNavigationKey = ["Home", "End", ...ARROW_KEYS].includes(
514
+ event.key
515
+ );
516
+ if (isFocusNavigationKey) {
517
+ let candidateNodes = getItems().map((item) => item.ref.current);
518
+ const prevItemKey = context.dir === "rtl" ? "ArrowRight" : "ArrowLeft";
519
+ const prevKeys = [prevItemKey, "ArrowUp", "End"];
520
+ if (prevKeys.includes(event.key)) candidateNodes.reverse();
521
+ if (ARROW_KEYS.includes(event.key)) {
522
+ const currentIndex = candidateNodes.indexOf(event.currentTarget);
523
+ candidateNodes = candidateNodes.slice(currentIndex + 1);
524
+ }
525
+ setTimeout(() => focusFirst(candidateNodes));
526
+ event.preventDefault();
527
+ }
528
+ })
529
+ }
530
+ ) });
531
+ });
532
+ function focusFirst(candidates) {
533
+ const previouslyFocusedElement = document.activeElement;
534
+ return candidates.some((candidate) => {
535
+ if (candidate === previouslyFocusedElement) return true;
536
+ candidate.focus();
537
+ return document.activeElement !== previouslyFocusedElement;
538
+ });
539
+ }
540
+
541
+ // packages/react/navigation-menu/src/NavigationMenuItem.tsx
542
+ import { jsx as jsx5 } from "react/jsx-runtime";
543
+ var ITEM_NAME = "NavigationMenuItem";
544
+ var NavigationMenuItem = React5.forwardRef((props, forwardedRef) => {
545
+ const { __scopeNavigationMenu, value: valueProp, ...itemProps } = props;
546
+ const autoValue = useId2();
547
+ const value = valueProp || autoValue || "LEGACY_REACT_AUTO_VALUE";
548
+ const contentRef = React5.useRef(null);
549
+ const triggerRef = React5.useRef(null);
550
+ const focusProxyRef = React5.useRef(null);
551
+ const restoreContentTabOrderRef = React5.useRef(() => {
552
+ });
553
+ const wasEscapeCloseRef = React5.useRef(false);
554
+ const handleContentEntry = React5.useCallback((side = "start") => {
555
+ if (contentRef.current) {
556
+ restoreContentTabOrderRef.current();
557
+ const candidates = getTabbableCandidates(contentRef.current);
558
+ if (candidates.length)
559
+ focusFirst(side === "start" ? candidates : candidates.reverse());
560
+ }
561
+ }, []);
562
+ const handleContentExit = React5.useCallback(() => {
563
+ if (contentRef.current) {
564
+ const candidates = getTabbableCandidates(contentRef.current);
565
+ if (candidates.length)
566
+ restoreContentTabOrderRef.current = removeFromTabOrder(candidates);
567
+ }
568
+ }, []);
569
+ return /* @__PURE__ */ jsx5(
570
+ NavigationMenuItemContextProvider,
571
+ {
572
+ scope: __scopeNavigationMenu,
573
+ value,
574
+ triggerRef,
575
+ contentRef,
576
+ focusProxyRef,
577
+ wasEscapeCloseRef,
578
+ onEntryKeyDown: handleContentEntry,
579
+ onFocusProxyEnter: handleContentEntry,
580
+ onRootContentClose: handleContentExit,
581
+ onContentFocusOutside: handleContentExit,
582
+ children: /* @__PURE__ */ jsx5(Primitive5.li, { ...itemProps, ref: forwardedRef })
583
+ }
584
+ );
585
+ });
586
+ NavigationMenuItem.displayName = ITEM_NAME;
587
+ function getTabbableCandidates(container) {
588
+ const nodes = [];
589
+ const walker = document.createTreeWalker(
590
+ container,
591
+ NodeFilter.SHOW_ELEMENT,
592
+ {
593
+ acceptNode: (node) => {
594
+ const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
595
+ if (node.disabled || node.hidden || isHiddenInput)
596
+ return NodeFilter.FILTER_SKIP;
597
+ return node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
598
+ }
599
+ }
600
+ );
601
+ while (walker.nextNode()) nodes.push(walker.currentNode);
602
+ return nodes;
603
+ }
604
+ function removeFromTabOrder(candidates) {
605
+ candidates.forEach((candidate) => {
606
+ candidate.dataset.tabindex = candidate.getAttribute("tabindex") || "";
607
+ candidate.setAttribute("tabindex", "-1");
608
+ });
609
+ return () => {
610
+ candidates.forEach((candidate) => {
611
+ const prevTabIndex = candidate.dataset.tabindex;
612
+ candidate.setAttribute("tabindex", prevTabIndex);
613
+ });
614
+ };
615
+ }
616
+
617
+ // packages/react/navigation-menu/src/NavigationMenu.tsx
618
+ import { jsx as jsx6 } from "react/jsx-runtime";
619
+ var NAVIGATION_MENU_NAME = "NavigationMenu";
620
+ var [Collection, useCollection, createCollectionScope] = createCollection(
621
+ NAVIGATION_MENU_NAME
622
+ );
623
+ var [
624
+ FocusGroupCollection,
625
+ useFocusGroupCollection,
626
+ createFocusGroupCollectionScope
627
+ ] = createCollection(NAVIGATION_MENU_NAME);
628
+ var [createNavigationMenuContext, createNavigationMenuScope] = createContextScope(NAVIGATION_MENU_NAME, [
629
+ createCollectionScope,
630
+ createFocusGroupCollectionScope
631
+ ]);
632
+ var [NavigationMenuItemContextProvider, useNavigationMenuItemContext] = createNavigationMenuContext(ITEM_NAME);
633
+ var [NavigationMenuProviderImpl, useNavigationMenuContext] = createNavigationMenuContext(NAVIGATION_MENU_NAME);
634
+ var [ViewportContentProvider, useViewportContentContext] = createNavigationMenuContext(NAVIGATION_MENU_NAME);
635
+ var NavigationMenu = React6.forwardRef((props, forwardedRef) => {
636
+ const {
637
+ __scopeNavigationMenu,
638
+ value: valueProp,
639
+ onValueChange,
640
+ defaultValue,
641
+ delayDuration = 200,
642
+ skipDelayDuration = 300,
643
+ orientation = "horizontal",
644
+ dir,
645
+ ...NavigationMenuProps
646
+ } = props;
647
+ const [navigationMenu, setNavigationMenu] = React6.useState(null);
648
+ const composedRef = useComposedRefs3(
649
+ forwardedRef,
650
+ (node) => setNavigationMenu(node)
651
+ );
652
+ const direction = useDirection(dir);
653
+ const openTimerRef = React6.useRef(0);
654
+ const closeTimerRef = React6.useRef(0);
655
+ const skipDelayTimerRef = React6.useRef(0);
656
+ const [isOpenDelayed, setIsOpenDelayed] = React6.useState(true);
657
+ const [value = "", setValue] = useControllableState2({
658
+ prop: valueProp,
659
+ onChange: (value2) => {
660
+ const isOpen = value2 !== "";
661
+ const hasSkipDelayDuration = skipDelayDuration > 0;
662
+ if (isOpen) {
663
+ window.clearTimeout(skipDelayTimerRef.current);
664
+ if (hasSkipDelayDuration) setIsOpenDelayed(false);
665
+ } else {
666
+ window.clearTimeout(skipDelayTimerRef.current);
667
+ skipDelayTimerRef.current = window.setTimeout(
668
+ () => setIsOpenDelayed(true),
669
+ skipDelayDuration
670
+ );
671
+ }
672
+ onValueChange?.(value2);
673
+ },
674
+ defaultProp: defaultValue
675
+ });
676
+ const startCloseTimer = React6.useCallback(() => {
677
+ window.clearTimeout(closeTimerRef.current);
678
+ closeTimerRef.current = window.setTimeout(() => setValue(""), 150);
679
+ }, [setValue]);
680
+ const handleOpen = React6.useCallback(
681
+ (itemValue) => {
682
+ window.clearTimeout(closeTimerRef.current);
683
+ setValue(itemValue);
684
+ },
685
+ [setValue]
686
+ );
687
+ const handleDelayedOpen = React6.useCallback(
688
+ (itemValue) => {
689
+ const isOpenItem = value === itemValue;
690
+ if (isOpenItem) {
691
+ window.clearTimeout(closeTimerRef.current);
692
+ } else {
693
+ openTimerRef.current = window.setTimeout(() => {
694
+ window.clearTimeout(closeTimerRef.current);
695
+ setValue(itemValue);
696
+ }, delayDuration);
697
+ }
698
+ },
699
+ [value, setValue, delayDuration]
700
+ );
701
+ React6.useEffect(() => {
702
+ return () => {
703
+ window.clearTimeout(openTimerRef.current);
704
+ window.clearTimeout(closeTimerRef.current);
705
+ window.clearTimeout(skipDelayTimerRef.current);
706
+ };
707
+ }, []);
708
+ return /* @__PURE__ */ jsx6(
709
+ NavigationMenuProvider,
710
+ {
711
+ scope: __scopeNavigationMenu,
712
+ isRootMenu: true,
713
+ value,
714
+ dir: direction,
715
+ orientation,
716
+ rootNavigationMenu: navigationMenu,
717
+ onTriggerEnter: (itemValue) => {
718
+ window.clearTimeout(openTimerRef.current);
719
+ if (isOpenDelayed) handleDelayedOpen(itemValue);
720
+ else handleOpen(itemValue);
721
+ },
722
+ onTriggerLeave: () => {
723
+ window.clearTimeout(openTimerRef.current);
724
+ startCloseTimer();
725
+ },
726
+ onContentEnter: () => window.clearTimeout(closeTimerRef.current),
727
+ onContentLeave: startCloseTimer,
728
+ onItemSelect: (itemValue) => {
729
+ setValue((prevValue) => prevValue === itemValue ? "" : itemValue);
730
+ },
731
+ onItemDismiss: () => setValue(""),
732
+ children: /* @__PURE__ */ jsx6(
733
+ Primitive6.nav,
734
+ {
735
+ "aria-label": "Main",
736
+ "data-orientation": orientation,
737
+ dir: direction,
738
+ ...NavigationMenuProps,
739
+ ref: composedRef
740
+ }
741
+ )
742
+ }
743
+ );
744
+ });
745
+ NavigationMenu.displayName = NAVIGATION_MENU_NAME;
746
+ var Root = NavigationMenu;
747
+
748
+ // packages/react/navigation-menu/src/NavigationMenuList.tsx
749
+ import React7 from "react";
750
+ import { Primitive as Primitive7 } from "@huin-core/react-primitive";
751
+ import { jsx as jsx7 } from "react/jsx-runtime";
752
+ var LIST_NAME = "NavigationMenuList";
753
+ var NavigationMenuList = React7.forwardRef((props, forwardedRef) => {
754
+ const { __scopeNavigationMenu, ...listProps } = props;
755
+ const context = useNavigationMenuContext(LIST_NAME, __scopeNavigationMenu);
756
+ const list = /* @__PURE__ */ jsx7(
757
+ Primitive7.ul,
758
+ {
759
+ "data-orientation": context.orientation,
760
+ ...listProps,
761
+ ref: forwardedRef
762
+ }
763
+ );
764
+ return /* @__PURE__ */ jsx7(
765
+ Primitive7.div,
766
+ {
767
+ style: { position: "relative" },
768
+ ref: context.onIndicatorTrackChange,
769
+ children: /* @__PURE__ */ jsx7(Collection.Slot, { scope: __scopeNavigationMenu, children: context.isRootMenu ? /* @__PURE__ */ jsx7(FocusGroup, { asChild: true, children: list }) : list })
770
+ }
771
+ );
772
+ });
773
+ NavigationMenuList.displayName = LIST_NAME;
774
+
775
+ // packages/react/navigation-menu/src/NavigationMenuTrigger.tsx
776
+ import React8 from "react";
777
+ import { Primitive as Primitive8 } from "@huin-core/react-primitive";
778
+ import { useComposedRefs as useComposedRefs4 } from "@huin-core/react-compose-refs";
779
+ import { composeEventHandlers as composeEventHandlers4 } from "@huin-core/primitive";
780
+ import * as VisuallyHiddenPrimitive from "@huin-core/react-visually-hidden";
781
+ import { Fragment, jsx as jsx8, jsxs } from "react/jsx-runtime";
782
+ var TRIGGER_NAME = "NavigationMenuTrigger";
783
+ var NavigationMenuTrigger = React8.forwardRef((props, forwardedRef) => {
784
+ const { __scopeNavigationMenu, disabled, ...triggerProps } = props;
785
+ const context = useNavigationMenuContext(
786
+ TRIGGER_NAME,
787
+ props.__scopeNavigationMenu
788
+ );
789
+ const itemContext = useNavigationMenuItemContext(
790
+ TRIGGER_NAME,
791
+ props.__scopeNavigationMenu
792
+ );
793
+ const ref = React8.useRef(null);
794
+ const composedRefs = useComposedRefs4(
795
+ ref,
796
+ itemContext.triggerRef,
797
+ forwardedRef
798
+ );
799
+ const triggerId = makeTriggerId(context.baseId, itemContext.value);
800
+ const contentId = makeContentId(context.baseId, itemContext.value);
801
+ const hasPointerMoveOpenedRef = React8.useRef(false);
802
+ const wasClickCloseRef = React8.useRef(false);
803
+ const open = itemContext.value === context.value;
804
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
805
+ /* @__PURE__ */ jsx8(
806
+ Collection.ItemSlot,
807
+ {
808
+ scope: __scopeNavigationMenu,
809
+ value: itemContext.value,
810
+ children: /* @__PURE__ */ jsx8(FocusGroupItem, { asChild: true, children: /* @__PURE__ */ jsx8(
811
+ Primitive8.button,
812
+ {
813
+ id: triggerId,
814
+ disabled,
815
+ "data-disabled": disabled ? "" : void 0,
816
+ "data-state": getOpenState(open),
817
+ "aria-expanded": open,
818
+ "aria-controls": contentId,
819
+ ...triggerProps,
820
+ ref: composedRefs,
821
+ onPointerEnter: composeEventHandlers4(props.onPointerEnter, () => {
822
+ wasClickCloseRef.current = false;
823
+ itemContext.wasEscapeCloseRef.current = false;
824
+ }),
825
+ onPointerMove: composeEventHandlers4(
826
+ props.onPointerMove,
827
+ whenMouse(() => {
828
+ if (disabled || wasClickCloseRef.current || itemContext.wasEscapeCloseRef.current || hasPointerMoveOpenedRef.current)
829
+ return;
830
+ context.onTriggerEnter(itemContext.value);
831
+ hasPointerMoveOpenedRef.current = true;
832
+ })
833
+ ),
834
+ onPointerLeave: composeEventHandlers4(
835
+ props.onPointerLeave,
836
+ whenMouse(() => {
837
+ if (disabled) return;
838
+ context.onTriggerLeave();
839
+ hasPointerMoveOpenedRef.current = false;
840
+ })
841
+ ),
842
+ onClick: composeEventHandlers4(props.onClick, () => {
843
+ context.onItemSelect(itemContext.value);
844
+ wasClickCloseRef.current = open;
845
+ }),
846
+ onKeyDown: composeEventHandlers4(props.onKeyDown, (event) => {
847
+ const verticalEntryKey = context.dir === "rtl" ? "ArrowLeft" : "ArrowRight";
848
+ const entryKey = {
849
+ horizontal: "ArrowDown",
850
+ vertical: verticalEntryKey
851
+ }[context.orientation];
852
+ if (open && event.key === entryKey) {
853
+ itemContext.onEntryKeyDown();
854
+ event.preventDefault();
855
+ }
856
+ })
857
+ }
858
+ ) })
859
+ }
860
+ ),
861
+ open && /* @__PURE__ */ jsxs(Fragment, { children: [
862
+ /* @__PURE__ */ jsx8(
863
+ VisuallyHiddenPrimitive.Root,
864
+ {
865
+ "aria-hidden": true,
866
+ tabIndex: 0,
867
+ ref: itemContext.focusProxyRef,
868
+ onFocus: (event) => {
869
+ const content = itemContext.contentRef.current;
870
+ const prevFocusedElement = event.relatedTarget;
871
+ const wasTriggerFocused = prevFocusedElement === ref.current;
872
+ const wasFocusFromContent = content?.contains(prevFocusedElement);
873
+ if (wasTriggerFocused || !wasFocusFromContent) {
874
+ itemContext.onFocusProxyEnter(
875
+ wasTriggerFocused ? "start" : "end"
876
+ );
877
+ }
878
+ }
879
+ }
880
+ ),
881
+ context.viewport && /* @__PURE__ */ jsx8("span", { "aria-owns": contentId })
882
+ ] })
883
+ ] });
884
+ });
885
+ NavigationMenuTrigger.displayName = TRIGGER_NAME;
886
+
887
+ // packages/react/navigation-menu/src/NavigationMenuIndicator.tsx
888
+ import React9 from "react";
889
+ import { Presence as Presence3 } from "@huin-core/react-presence";
890
+ import ReactDOM from "react-dom";
891
+ import { Primitive as Primitive9 } from "@huin-core/react-primitive";
892
+ import { jsx as jsx9 } from "react/jsx-runtime";
893
+ var INDICATOR_NAME = "NavigationMenuIndicator";
894
+ var NavigationMenuIndicator = React9.forwardRef((props, forwardedRef) => {
895
+ const { forceMount, ...indicatorProps } = props;
896
+ const context = useNavigationMenuContext(
897
+ INDICATOR_NAME,
898
+ props.__scopeNavigationMenu
899
+ );
900
+ const isVisible = Boolean(context.value);
901
+ return context.indicatorTrack ? ReactDOM.createPortal(
902
+ /* @__PURE__ */ jsx9(Presence3, { present: forceMount || isVisible, children: /* @__PURE__ */ jsx9(NavigationMenuIndicatorImpl, { ...indicatorProps, ref: forwardedRef }) }),
903
+ context.indicatorTrack
904
+ ) : null;
905
+ });
906
+ NavigationMenuIndicator.displayName = INDICATOR_NAME;
907
+ var NavigationMenuIndicatorImpl = React9.forwardRef((props, forwardedRef) => {
908
+ const { __scopeNavigationMenu, ...indicatorProps } = props;
909
+ const context = useNavigationMenuContext(
910
+ INDICATOR_NAME,
911
+ __scopeNavigationMenu
912
+ );
913
+ const getItems = useCollection(__scopeNavigationMenu);
914
+ const [activeTrigger, setActiveTrigger] = React9.useState(null);
915
+ const [position, setPosition] = React9.useState(null);
916
+ const isHorizontal = context.orientation === "horizontal";
917
+ const isVisible = Boolean(context.value);
918
+ React9.useEffect(() => {
919
+ const items = getItems();
920
+ const triggerNode = items.find((item) => item.value === context.value)?.ref.current;
921
+ if (triggerNode) setActiveTrigger(triggerNode);
922
+ }, [getItems, context.value]);
923
+ const handlePositionChange = () => {
924
+ if (activeTrigger) {
925
+ setPosition({
926
+ size: isHorizontal ? activeTrigger.offsetWidth : activeTrigger.offsetHeight,
927
+ offset: isHorizontal ? activeTrigger.offsetLeft : activeTrigger.offsetTop
928
+ });
929
+ }
930
+ };
931
+ useResizeObserver(activeTrigger, handlePositionChange);
932
+ useResizeObserver(context.indicatorTrack, handlePositionChange);
933
+ return position ? /* @__PURE__ */ jsx9(
934
+ Primitive9.div,
935
+ {
936
+ "aria-hidden": true,
937
+ "data-state": isVisible ? "visible" : "hidden",
938
+ "data-orientation": context.orientation,
939
+ ...indicatorProps,
940
+ ref: forwardedRef,
941
+ style: {
942
+ position: "absolute",
943
+ ...isHorizontal ? {
944
+ left: 0,
945
+ width: position.size + "px",
946
+ transform: `translateX(${position.offset}px)`
947
+ } : {
948
+ top: 0,
949
+ height: position.size + "px",
950
+ transform: `translateY(${position.offset}px)`
951
+ },
952
+ ...indicatorProps.style
953
+ }
954
+ }
955
+ ) : null;
956
+ });
957
+ export {
958
+ NavigationMenu,
959
+ NavigationMenuContent,
960
+ NavigationMenuIndicator,
961
+ NavigationMenuItem,
962
+ NavigationMenuLink,
963
+ NavigationMenuList,
964
+ NavigationMenuSub,
965
+ NavigationMenuTrigger,
966
+ NavigationMenuViewport,
967
+ Root,
968
+ createNavigationMenuScope
969
+ };
970
+ //# sourceMappingURL=index.mjs.map