@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
JavaScript
'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
});
}
;