@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
196 lines (195 loc) • 6.14 kB
JavaScript
"use client";
import _pushInstanceProperty from "core-js-pure/stable/instance/push.js";
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import clsx from 'clsx';
import Popover from "../popover/Popover.js";
import { MenuContext, MenuTriggerContext, useMenuContext } from "./MenuContext.js";
import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js";
import whatInput from "../../shared/helpers/whatInput.js";
import MenuButton from "./MenuButton.js";
import MenuAction from "./MenuAction.js";
import useIsomorphicLayoutEffect from "../../shared/helpers/useIsomorphicLayoutEffect.js";
import { jsx as _jsx } from "react/jsx-runtime";
export default function MenuRoot(props) {
var _MenuContext;
const {
id,
className,
children,
placement = 'bottom',
arrowPosition = 'center',
open,
onOpenChange,
skipPortal = false,
noAnimation = false,
autoAlignMode = 'initial'
} = props;
let triggerChild = null;
const contentChildren = [];
React.Children.forEach(children, child => {
if (!triggerChild && React.isValidElement(child) && (child.type === MenuButton || child.type === MenuAction)) {
triggerChild = child;
} else {
_pushInstanceProperty(contentChildren).call(contentChildren, child);
}
});
const parentContext = useMenuContext();
const level = parentContext ? parentContext.level + 1 : 0;
const activeIndexRef = useRef(-1);
const [activeIndex, setActiveIndexState] = useState(-1);
const itemRefsRef = useRef([]);
const nextIndexRef = useRef(0);
const menuRef = useRef(null);
const [isOpenInternal, setIsOpenInternal] = useState(false);
const isControlled = typeof open === 'boolean';
const isOpen = isControlled ? open : isOpenInternal;
const popoverCloseRef = useRef(null);
const setOpenState = useCallback(next => {
if (!isControlled) {
setIsOpenInternal(next);
}
onOpenChange === null || onOpenChange === void 0 || onOpenChange(next);
}, [isControlled, onOpenChange]);
const closeAll = useCallback(() => {
if (popoverCloseRef.current) {
popoverCloseRef.current();
} else {
setOpenState(false);
}
parentContext === null || parentContext === void 0 || parentContext.closeAll();
}, [setOpenState, parentContext]);
const closeSelf = useCallback(() => {
if (popoverCloseRef.current) {
popoverCloseRef.current();
} else {
setOpenState(false);
}
}, [setOpenState]);
const setActiveIndex = useCallback(index => {
activeIndexRef.current = index;
setActiveIndexState(index);
}, []);
const registerItem = useCallback(ref => {
const index = nextIndexRef.current;
nextIndexRef.current += 1;
itemRefsRef.current[index] = ref;
return index;
}, []);
const unregisterItem = useCallback(index => {
itemRefsRef.current[index] = undefined;
}, []);
const contextValue = useMemo(() => ({
level,
closeAll,
closeSelf,
activeIndex,
setActiveIndex,
registerItem,
unregisterItem,
itemRefs: itemRefsRef,
menuRef,
isOpen
}), [level, closeAll, closeSelf, activeIndex, setActiveIndex, registerItem, unregisterItem, isOpen]);
useEffect(() => {
if (!isOpen) {
nextIndexRef.current = 0;
itemRefsRef.current = [];
setActiveIndex(-1);
}
}, [isOpen, setActiveIndex]);
useIsomorphicLayoutEffect(() => {
if (isOpen) {
whatInput.specificKeys([9, 37, 38, 39, 40, 33, 34, 35, 36]);
}
return () => {
whatInput.specificKeys([9]);
};
}, [isOpen]);
const focusOnOpenElement = useCallback(() => {
var _menuRef$current;
return (_menuRef$current = menuRef.current) !== null && _menuRef$current !== void 0 ? _menuRef$current : null;
}, []);
const handleFocusComplete = useCallback(() => {
if (level === 0) {
return;
}
const firstRef = itemRefsRef.current[0];
if (firstRef !== null && firstRef !== void 0 && firstRef.current) {
firstRef.current.focus();
setActiveIndex(0);
}
}, [level, setActiveIndex]);
const handleOpenChange = useCallback(next => {
setOpenState(next);
}, [setOpenState]);
const handleTriggerKeyDown = useCallback(event => {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'ArrowRight') {
event.preventDefault();
if (!isOpen) {
handleOpenChange(true);
}
requestAnimationFrame(() => {
const firstRef = itemRefsRef.current[0];
if (firstRef !== null && firstRef !== void 0 && firstRef.current) {
firstRef.current.focus();
setActiveIndex(0);
}
});
}
}, [isOpen, handleOpenChange, setActiveIndex]);
const resolvedTrigger = triggerChild ? renderProps => {
const domProps = {
...renderProps
};
const {
active,
open: openFn,
close: closeFn,
toggle: toggleFn
} = renderProps;
return _jsx(MenuTriggerContext, {
value: {
active,
triggerProps: domProps,
open: openFn,
close: closeFn,
toggle: toggleFn
},
children: triggerChild
});
} : undefined;
return _jsx(Popover, {
id: id,
className: clsx('dnb-menu', className),
trigger: resolvedTrigger,
triggerAttributes: {
'aria-haspopup': 'menu',
onKeyDown: handleTriggerKeyDown
},
placement: placement,
open: isOpen,
onOpenChange: handleOpenChange,
skipPortal: level > 0 ? true : skipPortal,
autoAlignMode: autoAlignMode,
arrowPosition: arrowPosition,
noAnimation: noAnimation,
hideCloseButton: true,
noInnerSpace: true,
focusOnOpenElement: focusOnOpenElement,
onFocusComplete: handleFocusComplete,
contentClassName: "dnb-menu__popover-content",
children: ({
close
}) => {
popoverCloseRef.current = close;
return _MenuContext || (_MenuContext = _jsx(MenuContext, {
value: contextValue,
children: contentChildren
}));
}
});
}
withComponentMarkers(MenuRoot, {
_supportsSpacingProps: true
});
//# sourceMappingURL=MenuRoot.js.map