Spaces:
Runtime error
Runtime error
| "use client"; | |
| // src/menu.tsx | |
| import * as React from "react"; | |
| import { composeEventHandlers } from "@radix-ui/primitive"; | |
| import { createCollection } from "@radix-ui/react-collection"; | |
| import { useComposedRefs, composeRefs } from "@radix-ui/react-compose-refs"; | |
| import { createContextScope } from "@radix-ui/react-context"; | |
| import { useDirection } from "@radix-ui/react-direction"; | |
| import { DismissableLayer } from "@radix-ui/react-dismissable-layer"; | |
| import { useFocusGuards } from "@radix-ui/react-focus-guards"; | |
| import { FocusScope } from "@radix-ui/react-focus-scope"; | |
| import { useId } from "@radix-ui/react-id"; | |
| import * as PopperPrimitive from "@radix-ui/react-popper"; | |
| import { createPopperScope } from "@radix-ui/react-popper"; | |
| import { Portal as PortalPrimitive } from "@radix-ui/react-portal"; | |
| import { Presence } from "@radix-ui/react-presence"; | |
| import { Primitive, dispatchDiscreteCustomEvent } from "@radix-ui/react-primitive"; | |
| import * as RovingFocusGroup from "@radix-ui/react-roving-focus"; | |
| import { createRovingFocusGroupScope } from "@radix-ui/react-roving-focus"; | |
| import { createSlot } from "@radix-ui/react-slot"; | |
| import { useCallbackRef } from "@radix-ui/react-use-callback-ref"; | |
| import { hideOthers } from "aria-hidden"; | |
| import { RemoveScroll } from "react-remove-scroll"; | |
| import { jsx } from "react/jsx-runtime"; | |
| var SELECTION_KEYS = ["Enter", " "]; | |
| var FIRST_KEYS = ["ArrowDown", "PageUp", "Home"]; | |
| var LAST_KEYS = ["ArrowUp", "PageDown", "End"]; | |
| var FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS]; | |
| var SUB_OPEN_KEYS = { | |
| ltr: [...SELECTION_KEYS, "ArrowRight"], | |
| rtl: [...SELECTION_KEYS, "ArrowLeft"] | |
| }; | |
| var SUB_CLOSE_KEYS = { | |
| ltr: ["ArrowLeft"], | |
| rtl: ["ArrowRight"] | |
| }; | |
| var MENU_NAME = "Menu"; | |
| var [Collection, useCollection, createCollectionScope] = createCollection(MENU_NAME); | |
| var [createMenuContext, createMenuScope] = createContextScope(MENU_NAME, [ | |
| createCollectionScope, | |
| createPopperScope, | |
| createRovingFocusGroupScope | |
| ]); | |
| var usePopperScope = createPopperScope(); | |
| var useRovingFocusGroupScope = createRovingFocusGroupScope(); | |
| var [MenuProvider, useMenuContext] = createMenuContext(MENU_NAME); | |
| var [MenuRootProvider, useMenuRootContext] = createMenuContext(MENU_NAME); | |
| var Menu = (props) => { | |
| const { __scopeMenu, open = false, children, dir, onOpenChange, modal = true } = props; | |
| const popperScope = usePopperScope(__scopeMenu); | |
| const [content, setContent] = React.useState(null); | |
| const isUsingKeyboardRef = React.useRef(false); | |
| const handleOpenChange = useCallbackRef(onOpenChange); | |
| const direction = useDirection(dir); | |
| React.useEffect(() => { | |
| const handleKeyDown = () => { | |
| isUsingKeyboardRef.current = true; | |
| document.addEventListener("pointerdown", handlePointer, { capture: true, once: true }); | |
| document.addEventListener("pointermove", handlePointer, { capture: true, once: true }); | |
| }; | |
| const handlePointer = () => isUsingKeyboardRef.current = false; | |
| document.addEventListener("keydown", handleKeyDown, { capture: true }); | |
| return () => { | |
| document.removeEventListener("keydown", handleKeyDown, { capture: true }); | |
| document.removeEventListener("pointerdown", handlePointer, { capture: true }); | |
| document.removeEventListener("pointermove", handlePointer, { capture: true }); | |
| }; | |
| }, []); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ jsx( | |
| MenuProvider, | |
| { | |
| scope: __scopeMenu, | |
| open, | |
| onOpenChange: handleOpenChange, | |
| content, | |
| onContentChange: setContent, | |
| children: /* @__PURE__ */ jsx( | |
| MenuRootProvider, | |
| { | |
| scope: __scopeMenu, | |
| onClose: React.useCallback(() => handleOpenChange(false), [handleOpenChange]), | |
| isUsingKeyboardRef, | |
| dir: direction, | |
| modal, | |
| children | |
| } | |
| ) | |
| } | |
| ) }); | |
| }; | |
| Menu.displayName = MENU_NAME; | |
| var ANCHOR_NAME = "MenuAnchor"; | |
| var MenuAnchor = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, ...anchorProps } = props; | |
| const popperScope = usePopperScope(__scopeMenu); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Anchor, { ...popperScope, ...anchorProps, ref: forwardedRef }); | |
| } | |
| ); | |
| MenuAnchor.displayName = ANCHOR_NAME; | |
| var PORTAL_NAME = "MenuPortal"; | |
| var [PortalProvider, usePortalContext] = createMenuContext(PORTAL_NAME, { | |
| forceMount: void 0 | |
| }); | |
| var MenuPortal = (props) => { | |
| const { __scopeMenu, forceMount, children, container } = props; | |
| const context = useMenuContext(PORTAL_NAME, __scopeMenu); | |
| return /* @__PURE__ */ jsx(PortalProvider, { scope: __scopeMenu, forceMount, children: /* @__PURE__ */ jsx(Presence, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(PortalPrimitive, { asChild: true, container, children }) }) }); | |
| }; | |
| MenuPortal.displayName = PORTAL_NAME; | |
| var CONTENT_NAME = "MenuContent"; | |
| var [MenuContentProvider, useMenuContentContext] = createMenuContext(CONTENT_NAME); | |
| var MenuContent = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const portalContext = usePortalContext(CONTENT_NAME, props.__scopeMenu); | |
| const { forceMount = portalContext.forceMount, ...contentProps } = props; | |
| const context = useMenuContext(CONTENT_NAME, props.__scopeMenu); | |
| const rootContext = useMenuRootContext(CONTENT_NAME, props.__scopeMenu); | |
| return /* @__PURE__ */ jsx(Collection.Provider, { scope: props.__scopeMenu, children: /* @__PURE__ */ jsx(Presence, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(Collection.Slot, { scope: props.__scopeMenu, children: rootContext.modal ? /* @__PURE__ */ jsx(MenuRootContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsx(MenuRootContentNonModal, { ...contentProps, ref: forwardedRef }) }) }) }); | |
| } | |
| ); | |
| var MenuRootContentModal = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const context = useMenuContext(CONTENT_NAME, props.__scopeMenu); | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| React.useEffect(() => { | |
| const content = ref.current; | |
| if (content) return hideOthers(content); | |
| }, []); | |
| return /* @__PURE__ */ jsx( | |
| MenuContentImpl, | |
| { | |
| ...props, | |
| ref: composedRefs, | |
| trapFocus: context.open, | |
| disableOutsidePointerEvents: context.open, | |
| disableOutsideScroll: true, | |
| onFocusOutside: composeEventHandlers( | |
| props.onFocusOutside, | |
| (event) => event.preventDefault(), | |
| { checkForDefaultPrevented: false } | |
| ), | |
| onDismiss: () => context.onOpenChange(false) | |
| } | |
| ); | |
| } | |
| ); | |
| var MenuRootContentNonModal = React.forwardRef((props, forwardedRef) => { | |
| const context = useMenuContext(CONTENT_NAME, props.__scopeMenu); | |
| return /* @__PURE__ */ jsx( | |
| MenuContentImpl, | |
| { | |
| ...props, | |
| ref: forwardedRef, | |
| trapFocus: false, | |
| disableOutsidePointerEvents: false, | |
| disableOutsideScroll: false, | |
| onDismiss: () => context.onOpenChange(false) | |
| } | |
| ); | |
| }); | |
| var Slot = createSlot("MenuContent.ScrollLock"); | |
| var MenuContentImpl = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { | |
| __scopeMenu, | |
| loop = false, | |
| trapFocus, | |
| onOpenAutoFocus, | |
| onCloseAutoFocus, | |
| disableOutsidePointerEvents, | |
| onEntryFocus, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| onFocusOutside, | |
| onInteractOutside, | |
| onDismiss, | |
| disableOutsideScroll, | |
| ...contentProps | |
| } = props; | |
| const context = useMenuContext(CONTENT_NAME, __scopeMenu); | |
| const rootContext = useMenuRootContext(CONTENT_NAME, __scopeMenu); | |
| const popperScope = usePopperScope(__scopeMenu); | |
| const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu); | |
| const getItems = useCollection(__scopeMenu); | |
| const [currentItemId, setCurrentItemId] = React.useState(null); | |
| const contentRef = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, contentRef, context.onContentChange); | |
| const timerRef = React.useRef(0); | |
| const searchRef = React.useRef(""); | |
| const pointerGraceTimerRef = React.useRef(0); | |
| const pointerGraceIntentRef = React.useRef(null); | |
| const pointerDirRef = React.useRef("right"); | |
| const lastPointerXRef = React.useRef(0); | |
| const ScrollLockWrapper = disableOutsideScroll ? RemoveScroll : React.Fragment; | |
| const scrollLockWrapperProps = disableOutsideScroll ? { as: Slot, allowPinchZoom: true } : void 0; | |
| const handleTypeaheadSearch = (key) => { | |
| const search = searchRef.current + key; | |
| const items = getItems().filter((item) => !item.disabled); | |
| const currentItem = document.activeElement; | |
| const currentMatch = items.find((item) => item.ref.current === currentItem)?.textValue; | |
| const values = items.map((item) => item.textValue); | |
| const nextMatch = getNextMatch(values, search, currentMatch); | |
| const newItem = items.find((item) => item.textValue === nextMatch)?.ref.current; | |
| (function updateSearch(value) { | |
| searchRef.current = value; | |
| window.clearTimeout(timerRef.current); | |
| if (value !== "") timerRef.current = window.setTimeout(() => updateSearch(""), 1e3); | |
| })(search); | |
| if (newItem) { | |
| setTimeout(() => newItem.focus()); | |
| } | |
| }; | |
| React.useEffect(() => { | |
| return () => window.clearTimeout(timerRef.current); | |
| }, []); | |
| useFocusGuards(); | |
| const isPointerMovingToSubmenu = React.useCallback((event) => { | |
| const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side; | |
| return isMovingTowards && isPointerInGraceArea(event, pointerGraceIntentRef.current?.area); | |
| }, []); | |
| return /* @__PURE__ */ jsx( | |
| MenuContentProvider, | |
| { | |
| scope: __scopeMenu, | |
| searchRef, | |
| onItemEnter: React.useCallback( | |
| (event) => { | |
| if (isPointerMovingToSubmenu(event)) event.preventDefault(); | |
| }, | |
| [isPointerMovingToSubmenu] | |
| ), | |
| onItemLeave: React.useCallback( | |
| (event) => { | |
| if (isPointerMovingToSubmenu(event)) return; | |
| contentRef.current?.focus(); | |
| setCurrentItemId(null); | |
| }, | |
| [isPointerMovingToSubmenu] | |
| ), | |
| onTriggerLeave: React.useCallback( | |
| (event) => { | |
| if (isPointerMovingToSubmenu(event)) event.preventDefault(); | |
| }, | |
| [isPointerMovingToSubmenu] | |
| ), | |
| pointerGraceTimerRef, | |
| onPointerGraceIntentChange: React.useCallback((intent) => { | |
| pointerGraceIntentRef.current = intent; | |
| }, []), | |
| children: /* @__PURE__ */ jsx(ScrollLockWrapper, { ...scrollLockWrapperProps, children: /* @__PURE__ */ jsx( | |
| FocusScope, | |
| { | |
| asChild: true, | |
| trapped: trapFocus, | |
| onMountAutoFocus: composeEventHandlers(onOpenAutoFocus, (event) => { | |
| event.preventDefault(); | |
| contentRef.current?.focus({ preventScroll: true }); | |
| }), | |
| onUnmountAutoFocus: onCloseAutoFocus, | |
| children: /* @__PURE__ */ jsx( | |
| DismissableLayer, | |
| { | |
| asChild: true, | |
| disableOutsidePointerEvents, | |
| onEscapeKeyDown, | |
| onPointerDownOutside, | |
| onFocusOutside, | |
| onInteractOutside, | |
| onDismiss, | |
| children: /* @__PURE__ */ jsx( | |
| RovingFocusGroup.Root, | |
| { | |
| asChild: true, | |
| ...rovingFocusGroupScope, | |
| dir: rootContext.dir, | |
| orientation: "vertical", | |
| loop, | |
| currentTabStopId: currentItemId, | |
| onCurrentTabStopIdChange: setCurrentItemId, | |
| onEntryFocus: composeEventHandlers(onEntryFocus, (event) => { | |
| if (!rootContext.isUsingKeyboardRef.current) event.preventDefault(); | |
| }), | |
| preventScrollOnEntryFocus: true, | |
| children: /* @__PURE__ */ jsx( | |
| PopperPrimitive.Content, | |
| { | |
| role: "menu", | |
| "aria-orientation": "vertical", | |
| "data-state": getOpenState(context.open), | |
| "data-radix-menu-content": "", | |
| dir: rootContext.dir, | |
| ...popperScope, | |
| ...contentProps, | |
| ref: composedRefs, | |
| style: { outline: "none", ...contentProps.style }, | |
| onKeyDown: composeEventHandlers(contentProps.onKeyDown, (event) => { | |
| const target = event.target; | |
| const isKeyDownInside = target.closest("[data-radix-menu-content]") === event.currentTarget; | |
| const isModifierKey = event.ctrlKey || event.altKey || event.metaKey; | |
| const isCharacterKey = event.key.length === 1; | |
| if (isKeyDownInside) { | |
| if (event.key === "Tab") event.preventDefault(); | |
| if (!isModifierKey && isCharacterKey) handleTypeaheadSearch(event.key); | |
| } | |
| const content = contentRef.current; | |
| if (event.target !== content) return; | |
| if (!FIRST_LAST_KEYS.includes(event.key)) return; | |
| event.preventDefault(); | |
| const items = getItems().filter((item) => !item.disabled); | |
| const candidateNodes = items.map((item) => item.ref.current); | |
| if (LAST_KEYS.includes(event.key)) candidateNodes.reverse(); | |
| focusFirst(candidateNodes); | |
| }), | |
| onBlur: composeEventHandlers(props.onBlur, (event) => { | |
| if (!event.currentTarget.contains(event.target)) { | |
| window.clearTimeout(timerRef.current); | |
| searchRef.current = ""; | |
| } | |
| }), | |
| onPointerMove: composeEventHandlers( | |
| props.onPointerMove, | |
| whenMouse((event) => { | |
| const target = event.target; | |
| const pointerXHasChanged = lastPointerXRef.current !== event.clientX; | |
| if (event.currentTarget.contains(target) && pointerXHasChanged) { | |
| const newDir = event.clientX > lastPointerXRef.current ? "right" : "left"; | |
| pointerDirRef.current = newDir; | |
| lastPointerXRef.current = event.clientX; | |
| } | |
| }) | |
| ) | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| ) }) | |
| } | |
| ); | |
| } | |
| ); | |
| MenuContent.displayName = CONTENT_NAME; | |
| var GROUP_NAME = "MenuGroup"; | |
| var MenuGroup = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, ...groupProps } = props; | |
| return /* @__PURE__ */ jsx(Primitive.div, { role: "group", ...groupProps, ref: forwardedRef }); | |
| } | |
| ); | |
| MenuGroup.displayName = GROUP_NAME; | |
| var LABEL_NAME = "MenuLabel"; | |
| var MenuLabel = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, ...labelProps } = props; | |
| return /* @__PURE__ */ jsx(Primitive.div, { ...labelProps, ref: forwardedRef }); | |
| } | |
| ); | |
| MenuLabel.displayName = LABEL_NAME; | |
| var ITEM_NAME = "MenuItem"; | |
| var ITEM_SELECT = "menu.itemSelect"; | |
| var MenuItem = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { disabled = false, onSelect, ...itemProps } = props; | |
| const ref = React.useRef(null); | |
| const rootContext = useMenuRootContext(ITEM_NAME, props.__scopeMenu); | |
| const contentContext = useMenuContentContext(ITEM_NAME, props.__scopeMenu); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| const isPointerDownRef = React.useRef(false); | |
| const handleSelect = () => { | |
| const menuItem = ref.current; | |
| if (!disabled && menuItem) { | |
| const itemSelectEvent = new CustomEvent(ITEM_SELECT, { bubbles: true, cancelable: true }); | |
| menuItem.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { once: true }); | |
| dispatchDiscreteCustomEvent(menuItem, itemSelectEvent); | |
| if (itemSelectEvent.defaultPrevented) { | |
| isPointerDownRef.current = false; | |
| } else { | |
| rootContext.onClose(); | |
| } | |
| } | |
| }; | |
| return /* @__PURE__ */ jsx( | |
| MenuItemImpl, | |
| { | |
| ...itemProps, | |
| ref: composedRefs, | |
| disabled, | |
| onClick: composeEventHandlers(props.onClick, handleSelect), | |
| onPointerDown: (event) => { | |
| props.onPointerDown?.(event); | |
| isPointerDownRef.current = true; | |
| }, | |
| onPointerUp: composeEventHandlers(props.onPointerUp, (event) => { | |
| if (!isPointerDownRef.current) event.currentTarget?.click(); | |
| }), | |
| onKeyDown: composeEventHandlers(props.onKeyDown, (event) => { | |
| const isTypingAhead = contentContext.searchRef.current !== ""; | |
| if (disabled || isTypingAhead && event.key === " ") return; | |
| if (SELECTION_KEYS.includes(event.key)) { | |
| event.currentTarget.click(); | |
| event.preventDefault(); | |
| } | |
| }) | |
| } | |
| ); | |
| } | |
| ); | |
| MenuItem.displayName = ITEM_NAME; | |
| var MenuItemImpl = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, disabled = false, textValue, ...itemProps } = props; | |
| const contentContext = useMenuContentContext(ITEM_NAME, __scopeMenu); | |
| const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu); | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| const [isFocused, setIsFocused] = React.useState(false); | |
| const [textContent, setTextContent] = React.useState(""); | |
| React.useEffect(() => { | |
| const menuItem = ref.current; | |
| if (menuItem) { | |
| setTextContent((menuItem.textContent ?? "").trim()); | |
| } | |
| }, [itemProps.children]); | |
| return /* @__PURE__ */ jsx( | |
| Collection.ItemSlot, | |
| { | |
| scope: __scopeMenu, | |
| disabled, | |
| textValue: textValue ?? textContent, | |
| children: /* @__PURE__ */ jsx(RovingFocusGroup.Item, { asChild: true, ...rovingFocusGroupScope, focusable: !disabled, children: /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| role: "menuitem", | |
| "data-highlighted": isFocused ? "" : void 0, | |
| "aria-disabled": disabled || void 0, | |
| "data-disabled": disabled ? "" : void 0, | |
| ...itemProps, | |
| ref: composedRefs, | |
| onPointerMove: composeEventHandlers( | |
| props.onPointerMove, | |
| whenMouse((event) => { | |
| if (disabled) { | |
| contentContext.onItemLeave(event); | |
| } else { | |
| contentContext.onItemEnter(event); | |
| if (!event.defaultPrevented) { | |
| const item = event.currentTarget; | |
| item.focus({ preventScroll: true }); | |
| } | |
| } | |
| }) | |
| ), | |
| onPointerLeave: composeEventHandlers( | |
| props.onPointerLeave, | |
| whenMouse((event) => contentContext.onItemLeave(event)) | |
| ), | |
| onFocus: composeEventHandlers(props.onFocus, () => setIsFocused(true)), | |
| onBlur: composeEventHandlers(props.onBlur, () => setIsFocused(false)) | |
| } | |
| ) }) | |
| } | |
| ); | |
| } | |
| ); | |
| var CHECKBOX_ITEM_NAME = "MenuCheckboxItem"; | |
| var MenuCheckboxItem = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { checked = false, onCheckedChange, ...checkboxItemProps } = props; | |
| return /* @__PURE__ */ jsx(ItemIndicatorProvider, { scope: props.__scopeMenu, checked, children: /* @__PURE__ */ jsx( | |
| MenuItem, | |
| { | |
| role: "menuitemcheckbox", | |
| "aria-checked": isIndeterminate(checked) ? "mixed" : checked, | |
| ...checkboxItemProps, | |
| ref: forwardedRef, | |
| "data-state": getCheckedState(checked), | |
| onSelect: composeEventHandlers( | |
| checkboxItemProps.onSelect, | |
| () => onCheckedChange?.(isIndeterminate(checked) ? true : !checked), | |
| { checkForDefaultPrevented: false } | |
| ) | |
| } | |
| ) }); | |
| } | |
| ); | |
| MenuCheckboxItem.displayName = CHECKBOX_ITEM_NAME; | |
| var RADIO_GROUP_NAME = "MenuRadioGroup"; | |
| var [RadioGroupProvider, useRadioGroupContext] = createMenuContext( | |
| RADIO_GROUP_NAME, | |
| { value: void 0, onValueChange: () => { | |
| } } | |
| ); | |
| var MenuRadioGroup = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { value, onValueChange, ...groupProps } = props; | |
| const handleValueChange = useCallbackRef(onValueChange); | |
| return /* @__PURE__ */ jsx(RadioGroupProvider, { scope: props.__scopeMenu, value, onValueChange: handleValueChange, children: /* @__PURE__ */ jsx(MenuGroup, { ...groupProps, ref: forwardedRef }) }); | |
| } | |
| ); | |
| MenuRadioGroup.displayName = RADIO_GROUP_NAME; | |
| var RADIO_ITEM_NAME = "MenuRadioItem"; | |
| var MenuRadioItem = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { value, ...radioItemProps } = props; | |
| const context = useRadioGroupContext(RADIO_ITEM_NAME, props.__scopeMenu); | |
| const checked = value === context.value; | |
| return /* @__PURE__ */ jsx(ItemIndicatorProvider, { scope: props.__scopeMenu, checked, children: /* @__PURE__ */ jsx( | |
| MenuItem, | |
| { | |
| role: "menuitemradio", | |
| "aria-checked": checked, | |
| ...radioItemProps, | |
| ref: forwardedRef, | |
| "data-state": getCheckedState(checked), | |
| onSelect: composeEventHandlers( | |
| radioItemProps.onSelect, | |
| () => context.onValueChange?.(value), | |
| { checkForDefaultPrevented: false } | |
| ) | |
| } | |
| ) }); | |
| } | |
| ); | |
| MenuRadioItem.displayName = RADIO_ITEM_NAME; | |
| var ITEM_INDICATOR_NAME = "MenuItemIndicator"; | |
| var [ItemIndicatorProvider, useItemIndicatorContext] = createMenuContext( | |
| ITEM_INDICATOR_NAME, | |
| { checked: false } | |
| ); | |
| var MenuItemIndicator = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, forceMount, ...itemIndicatorProps } = props; | |
| const indicatorContext = useItemIndicatorContext(ITEM_INDICATOR_NAME, __scopeMenu); | |
| return /* @__PURE__ */ jsx( | |
| Presence, | |
| { | |
| present: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === true, | |
| children: /* @__PURE__ */ jsx( | |
| Primitive.span, | |
| { | |
| ...itemIndicatorProps, | |
| ref: forwardedRef, | |
| "data-state": getCheckedState(indicatorContext.checked) | |
| } | |
| ) | |
| } | |
| ); | |
| } | |
| ); | |
| MenuItemIndicator.displayName = ITEM_INDICATOR_NAME; | |
| var SEPARATOR_NAME = "MenuSeparator"; | |
| var MenuSeparator = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, ...separatorProps } = props; | |
| return /* @__PURE__ */ jsx( | |
| Primitive.div, | |
| { | |
| role: "separator", | |
| "aria-orientation": "horizontal", | |
| ...separatorProps, | |
| ref: forwardedRef | |
| } | |
| ); | |
| } | |
| ); | |
| MenuSeparator.displayName = SEPARATOR_NAME; | |
| var ARROW_NAME = "MenuArrow"; | |
| var MenuArrow = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const { __scopeMenu, ...arrowProps } = props; | |
| const popperScope = usePopperScope(__scopeMenu); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Arrow, { ...popperScope, ...arrowProps, ref: forwardedRef }); | |
| } | |
| ); | |
| MenuArrow.displayName = ARROW_NAME; | |
| var SUB_NAME = "MenuSub"; | |
| var [MenuSubProvider, useMenuSubContext] = createMenuContext(SUB_NAME); | |
| var MenuSub = (props) => { | |
| const { __scopeMenu, children, open = false, onOpenChange } = props; | |
| const parentMenuContext = useMenuContext(SUB_NAME, __scopeMenu); | |
| const popperScope = usePopperScope(__scopeMenu); | |
| const [trigger, setTrigger] = React.useState(null); | |
| const [content, setContent] = React.useState(null); | |
| const handleOpenChange = useCallbackRef(onOpenChange); | |
| React.useEffect(() => { | |
| if (parentMenuContext.open === false) handleOpenChange(false); | |
| return () => handleOpenChange(false); | |
| }, [parentMenuContext.open, handleOpenChange]); | |
| return /* @__PURE__ */ jsx(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ jsx( | |
| MenuProvider, | |
| { | |
| scope: __scopeMenu, | |
| open, | |
| onOpenChange: handleOpenChange, | |
| content, | |
| onContentChange: setContent, | |
| children: /* @__PURE__ */ jsx( | |
| MenuSubProvider, | |
| { | |
| scope: __scopeMenu, | |
| contentId: useId(), | |
| triggerId: useId(), | |
| trigger, | |
| onTriggerChange: setTrigger, | |
| children | |
| } | |
| ) | |
| } | |
| ) }); | |
| }; | |
| MenuSub.displayName = SUB_NAME; | |
| var SUB_TRIGGER_NAME = "MenuSubTrigger"; | |
| var MenuSubTrigger = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const context = useMenuContext(SUB_TRIGGER_NAME, props.__scopeMenu); | |
| const rootContext = useMenuRootContext(SUB_TRIGGER_NAME, props.__scopeMenu); | |
| const subContext = useMenuSubContext(SUB_TRIGGER_NAME, props.__scopeMenu); | |
| const contentContext = useMenuContentContext(SUB_TRIGGER_NAME, props.__scopeMenu); | |
| const openTimerRef = React.useRef(null); | |
| const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext; | |
| const scope = { __scopeMenu: props.__scopeMenu }; | |
| const clearOpenTimer = React.useCallback(() => { | |
| if (openTimerRef.current) window.clearTimeout(openTimerRef.current); | |
| openTimerRef.current = null; | |
| }, []); | |
| React.useEffect(() => clearOpenTimer, [clearOpenTimer]); | |
| React.useEffect(() => { | |
| const pointerGraceTimer = pointerGraceTimerRef.current; | |
| return () => { | |
| window.clearTimeout(pointerGraceTimer); | |
| onPointerGraceIntentChange(null); | |
| }; | |
| }, [pointerGraceTimerRef, onPointerGraceIntentChange]); | |
| return /* @__PURE__ */ jsx(MenuAnchor, { asChild: true, ...scope, children: /* @__PURE__ */ jsx( | |
| MenuItemImpl, | |
| { | |
| id: subContext.triggerId, | |
| "aria-haspopup": "menu", | |
| "aria-expanded": context.open, | |
| "aria-controls": subContext.contentId, | |
| "data-state": getOpenState(context.open), | |
| ...props, | |
| ref: composeRefs(forwardedRef, subContext.onTriggerChange), | |
| onClick: (event) => { | |
| props.onClick?.(event); | |
| if (props.disabled || event.defaultPrevented) return; | |
| event.currentTarget.focus(); | |
| if (!context.open) context.onOpenChange(true); | |
| }, | |
| onPointerMove: composeEventHandlers( | |
| props.onPointerMove, | |
| whenMouse((event) => { | |
| contentContext.onItemEnter(event); | |
| if (event.defaultPrevented) return; | |
| if (!props.disabled && !context.open && !openTimerRef.current) { | |
| contentContext.onPointerGraceIntentChange(null); | |
| openTimerRef.current = window.setTimeout(() => { | |
| context.onOpenChange(true); | |
| clearOpenTimer(); | |
| }, 100); | |
| } | |
| }) | |
| ), | |
| onPointerLeave: composeEventHandlers( | |
| props.onPointerLeave, | |
| whenMouse((event) => { | |
| clearOpenTimer(); | |
| const contentRect = context.content?.getBoundingClientRect(); | |
| if (contentRect) { | |
| const side = context.content?.dataset.side; | |
| const rightSide = side === "right"; | |
| const bleed = rightSide ? -5 : 5; | |
| const contentNearEdge = contentRect[rightSide ? "left" : "right"]; | |
| const contentFarEdge = contentRect[rightSide ? "right" : "left"]; | |
| contentContext.onPointerGraceIntentChange({ | |
| area: [ | |
| // Apply a bleed on clientX to ensure that our exit point is | |
| // consistently within polygon bounds | |
| { x: event.clientX + bleed, y: event.clientY }, | |
| { x: contentNearEdge, y: contentRect.top }, | |
| { x: contentFarEdge, y: contentRect.top }, | |
| { x: contentFarEdge, y: contentRect.bottom }, | |
| { x: contentNearEdge, y: contentRect.bottom } | |
| ], | |
| side | |
| }); | |
| window.clearTimeout(pointerGraceTimerRef.current); | |
| pointerGraceTimerRef.current = window.setTimeout( | |
| () => contentContext.onPointerGraceIntentChange(null), | |
| 300 | |
| ); | |
| } else { | |
| contentContext.onTriggerLeave(event); | |
| if (event.defaultPrevented) return; | |
| contentContext.onPointerGraceIntentChange(null); | |
| } | |
| }) | |
| ), | |
| onKeyDown: composeEventHandlers(props.onKeyDown, (event) => { | |
| const isTypingAhead = contentContext.searchRef.current !== ""; | |
| if (props.disabled || isTypingAhead && event.key === " ") return; | |
| if (SUB_OPEN_KEYS[rootContext.dir].includes(event.key)) { | |
| context.onOpenChange(true); | |
| context.content?.focus(); | |
| event.preventDefault(); | |
| } | |
| }) | |
| } | |
| ) }); | |
| } | |
| ); | |
| MenuSubTrigger.displayName = SUB_TRIGGER_NAME; | |
| var SUB_CONTENT_NAME = "MenuSubContent"; | |
| var MenuSubContent = React.forwardRef( | |
| (props, forwardedRef) => { | |
| const portalContext = usePortalContext(CONTENT_NAME, props.__scopeMenu); | |
| const { forceMount = portalContext.forceMount, ...subContentProps } = props; | |
| const context = useMenuContext(CONTENT_NAME, props.__scopeMenu); | |
| const rootContext = useMenuRootContext(CONTENT_NAME, props.__scopeMenu); | |
| const subContext = useMenuSubContext(SUB_CONTENT_NAME, props.__scopeMenu); | |
| const ref = React.useRef(null); | |
| const composedRefs = useComposedRefs(forwardedRef, ref); | |
| return /* @__PURE__ */ jsx(Collection.Provider, { scope: props.__scopeMenu, children: /* @__PURE__ */ jsx(Presence, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(Collection.Slot, { scope: props.__scopeMenu, children: /* @__PURE__ */ jsx( | |
| MenuContentImpl, | |
| { | |
| id: subContext.contentId, | |
| "aria-labelledby": subContext.triggerId, | |
| ...subContentProps, | |
| ref: composedRefs, | |
| align: "start", | |
| side: rootContext.dir === "rtl" ? "left" : "right", | |
| disableOutsidePointerEvents: false, | |
| disableOutsideScroll: false, | |
| trapFocus: false, | |
| onOpenAutoFocus: (event) => { | |
| if (rootContext.isUsingKeyboardRef.current) ref.current?.focus(); | |
| event.preventDefault(); | |
| }, | |
| onCloseAutoFocus: (event) => event.preventDefault(), | |
| onFocusOutside: composeEventHandlers(props.onFocusOutside, (event) => { | |
| if (event.target !== subContext.trigger) context.onOpenChange(false); | |
| }), | |
| onEscapeKeyDown: composeEventHandlers(props.onEscapeKeyDown, (event) => { | |
| rootContext.onClose(); | |
| event.preventDefault(); | |
| }), | |
| onKeyDown: composeEventHandlers(props.onKeyDown, (event) => { | |
| const isKeyDownInside = event.currentTarget.contains(event.target); | |
| const isCloseKey = SUB_CLOSE_KEYS[rootContext.dir].includes(event.key); | |
| if (isKeyDownInside && isCloseKey) { | |
| context.onOpenChange(false); | |
| subContext.trigger?.focus(); | |
| event.preventDefault(); | |
| } | |
| }) | |
| } | |
| ) }) }) }); | |
| } | |
| ); | |
| MenuSubContent.displayName = SUB_CONTENT_NAME; | |
| function getOpenState(open) { | |
| return open ? "open" : "closed"; | |
| } | |
| function isIndeterminate(checked) { | |
| return checked === "indeterminate"; | |
| } | |
| function getCheckedState(checked) { | |
| return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked"; | |
| } | |
| function focusFirst(candidates) { | |
| const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement; | |
| for (const candidate of candidates) { | |
| if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return; | |
| candidate.focus(); | |
| if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return; | |
| } | |
| } | |
| function wrapArray(array, startIndex) { | |
| return array.map((_, index) => array[(startIndex + index) % array.length]); | |
| } | |
| function getNextMatch(values, search, currentMatch) { | |
| const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); | |
| const normalizedSearch = isRepeated ? search[0] : search; | |
| const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1; | |
| let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0)); | |
| const excludeCurrentMatch = normalizedSearch.length === 1; | |
| if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch); | |
| const nextMatch = wrappedValues.find( | |
| (value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase()) | |
| ); | |
| return nextMatch !== currentMatch ? nextMatch : void 0; | |
| } | |
| function isPointInPolygon(point, polygon) { | |
| const { x, y } = point; | |
| let inside = false; | |
| for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { | |
| const ii = polygon[i]; | |
| const jj = polygon[j]; | |
| const xi = ii.x; | |
| const yi = ii.y; | |
| const xj = jj.x; | |
| const yj = jj.y; | |
| const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi; | |
| if (intersect) inside = !inside; | |
| } | |
| return inside; | |
| } | |
| function isPointerInGraceArea(event, area) { | |
| if (!area) return false; | |
| const cursorPos = { x: event.clientX, y: event.clientY }; | |
| return isPointInPolygon(cursorPos, area); | |
| } | |
| function whenMouse(handler) { | |
| return (event) => event.pointerType === "mouse" ? handler(event) : void 0; | |
| } | |
| var Root3 = Menu; | |
| var Anchor2 = MenuAnchor; | |
| var Portal = MenuPortal; | |
| var Content2 = MenuContent; | |
| var Group = MenuGroup; | |
| var Label = MenuLabel; | |
| var Item2 = MenuItem; | |
| var CheckboxItem = MenuCheckboxItem; | |
| var RadioGroup = MenuRadioGroup; | |
| var RadioItem = MenuRadioItem; | |
| var ItemIndicator = MenuItemIndicator; | |
| var Separator = MenuSeparator; | |
| var Arrow2 = MenuArrow; | |
| var Sub = MenuSub; | |
| var SubTrigger = MenuSubTrigger; | |
| var SubContent = MenuSubContent; | |
| export { | |
| Anchor2 as Anchor, | |
| Arrow2 as Arrow, | |
| CheckboxItem, | |
| Content2 as Content, | |
| Group, | |
| Item2 as Item, | |
| ItemIndicator, | |
| Label, | |
| Menu, | |
| MenuAnchor, | |
| MenuArrow, | |
| MenuCheckboxItem, | |
| MenuContent, | |
| MenuGroup, | |
| MenuItem, | |
| MenuItemIndicator, | |
| MenuLabel, | |
| MenuPortal, | |
| MenuRadioGroup, | |
| MenuRadioItem, | |
| MenuSeparator, | |
| MenuSub, | |
| MenuSubContent, | |
| MenuSubTrigger, | |
| Portal, | |
| RadioGroup, | |
| RadioItem, | |
| Root3 as Root, | |
| Separator, | |
| Sub, | |
| SubContent, | |
| SubTrigger, | |
| createMenuScope | |
| }; | |
| //# sourceMappingURL=index.mjs.map | |