UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

216 lines (214 loc) 7.31 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.NavigationMenuRoot = void 0; var React = _interopRequireWildcard(require("react")); var _dom = require("@floating-ui/utils/dom"); var _useControlled = require("@base-ui-components/utils/useControlled"); var _useEventCallback = require("@base-ui-components/utils/useEventCallback"); var _owner = require("@base-ui-components/utils/owner"); var _floatingUiReact = require("../../floating-ui-react"); var _utils = require("../../floating-ui-react/utils"); var _useRenderElement = require("../../utils/useRenderElement"); var _NavigationMenuRootContext = require("./NavigationMenuRootContext"); var _useOpenChangeComplete = require("../../utils/useOpenChangeComplete"); var _useTransitionStatus = require("../../utils/useTransitionStatus"); var _setFixedSize = require("../utils/setFixedSize"); var _jsxRuntime = require("react/jsx-runtime"); /** * Groups all parts of the navigation menu. * Renders a `<nav>` element at the root, or `<div>` element when nested. * * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu) */ const NavigationMenuRoot = exports.NavigationMenuRoot = /*#__PURE__*/React.forwardRef(function NavigationMenuRoot(componentProps, forwardedRef) { const { defaultValue = null, value: valueParam, onValueChange, actionsRef, delay = 50, closeDelay = 50, orientation = 'horizontal', onOpenChangeComplete } = componentProps; const nested = (0, _floatingUiReact.useFloatingParentNodeId)() != null; const [value, setValueUnwrapped] = (0, _useControlled.useControlled)({ controlled: valueParam, default: defaultValue, name: 'NavigationMenu', state: 'value' }); // Derive open state from value being non-nullish const open = value != null; const closeReasonRef = React.useRef(undefined); const rootRef = React.useRef(null); const [positionerElement, setPositionerElement] = React.useState(null); const [popupElement, setPopupElement] = React.useState(null); const [viewportElement, setViewportElement] = React.useState(null); const [viewportTargetElement, setViewportTargetElement] = React.useState(null); const [activationDirection, setActivationDirection] = React.useState(null); const [floatingRootContext, setFloatingRootContext] = React.useState(undefined); const [viewportInert, setViewportInert] = React.useState(false); const prevTriggerElementRef = React.useRef(null); const currentContentRef = React.useRef(null); const beforeInsideRef = React.useRef(null); const afterInsideRef = React.useRef(null); const beforeOutsideRef = React.useRef(null); const afterOutsideRef = React.useRef(null); const { mounted, setMounted, transitionStatus } = (0, _useTransitionStatus.useTransitionStatus)(open); React.useEffect(() => { setViewportInert(false); }, [value]); const setValue = (0, _useEventCallback.useEventCallback)((nextValue, eventDetails) => { if (!nextValue) { closeReasonRef.current = eventDetails.reason; setActivationDirection(null); setFloatingRootContext(undefined); if (positionerElement && popupElement) { (0, _setFixedSize.setFixedSize)(popupElement, 'popup'); (0, _setFixedSize.setFixedSize)(positionerElement, 'positioner'); } } if (nextValue !== value) { onValueChange?.(nextValue, eventDetails); } if (eventDetails.isCanceled) { return; } setValueUnwrapped(nextValue); }); const handleUnmount = (0, _useEventCallback.useEventCallback)(() => { const doc = (0, _owner.ownerDocument)(rootRef.current); const activeEl = (0, _utils.activeElement)(doc); if (closeReasonRef.current !== 'trigger-hover' && (0, _dom.isHTMLElement)(prevTriggerElementRef.current) && (0, _utils.contains)(popupElement, activeEl) && popupElement) { prevTriggerElementRef.current.focus({ preventScroll: true }); prevTriggerElementRef.current = undefined; } setMounted(false); onOpenChangeComplete?.(false); setActivationDirection(null); setFloatingRootContext(undefined); currentContentRef.current = null; closeReasonRef.current = undefined; }); (0, _useOpenChangeComplete.useOpenChangeComplete)({ enabled: !actionsRef, open, ref: { current: popupElement }, onComplete() { if (!open) { handleUnmount(); } } }); (0, _useOpenChangeComplete.useOpenChangeComplete)({ enabled: !actionsRef, open, ref: { current: viewportTargetElement }, onComplete() { if (!open) { handleUnmount(); } } }); const contextValue = React.useMemo(() => ({ open, value, setValue, mounted, transitionStatus, positionerElement, setPositionerElement, popupElement, setPopupElement, viewportElement, setViewportElement, viewportTargetElement, setViewportTargetElement, activationDirection, setActivationDirection, floatingRootContext, setFloatingRootContext, currentContentRef, nested, rootRef, beforeInsideRef, afterInsideRef, beforeOutsideRef, afterOutsideRef, prevTriggerElementRef, delay, closeDelay, orientation, viewportInert, setViewportInert }), [open, value, setValue, mounted, transitionStatus, positionerElement, popupElement, viewportElement, viewportTargetElement, activationDirection, floatingRootContext, nested, delay, closeDelay, orientation, viewportInert]); const jsx = /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationMenuRootContext.NavigationMenuRootContext.Provider, { value: contextValue, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TreeContext, { componentProps: componentProps, forwardedRef: forwardedRef, children: componentProps.children }) }); if (!nested) { // FloatingTree provides context to nested menus return /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingUiReact.FloatingTree, { children: jsx }); } return jsx; }); if (process.env.NODE_ENV !== "production") NavigationMenuRoot.displayName = "NavigationMenuRoot"; function TreeContext(props) { const { className, render, defaultValue, value: valueParam, onValueChange, actionsRef, delay, closeDelay, orientation, onOpenChangeComplete, ...elementProps } = props.componentProps; const nodeId = (0, _floatingUiReact.useFloatingNodeId)(); const { rootRef, nested } = (0, _NavigationMenuRootContext.useNavigationMenuRootContext)(); const { open } = (0, _NavigationMenuRootContext.useNavigationMenuRootContext)(); const state = React.useMemo(() => ({ open, nested }), [open, nested]); const element = (0, _useRenderElement.useRenderElement)(nested ? 'div' : 'nav', props.componentProps, { state, ref: [props.forwardedRef, rootRef], props: [{ 'aria-orientation': orientation }, elementProps] }); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationMenuRootContext.NavigationMenuTreeContext.Provider, { value: nodeId, children: element }); }