UNPKG

@yamada-ui/react

Version:

React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion

469 lines (465 loc) • 14.3 kB
"use client"; import { createContext as createContext$1 } from "../../utils/context.js"; import { runKeyAction } from "../../utils/dom.js"; import { useUpdateEffect } from "../../utils/effect.js"; import { assignRef, mergeRefs } from "../../utils/ref.js"; import { utils_exports } from "../../utils/index.js"; import { useControllableState } from "../../hooks/use-controllable-state/index.js"; import { createDescendants } from "../../hooks/use-descendants/index.js"; import { useDisclosure } from "../../hooks/use-disclosure/use-disclosure.js"; import { useCallback, useId, useRef } from "react"; //#region src/components/menu/use-menu.ts const { DescendantsContext: MenuDescendantsContext, useDescendant: useMenuDescendant, useDescendantRegister: useMenuDescendantRegister, useDescendants: useMenuDescendants } = createDescendants(); const [MenuContext, useMenuContext] = createContext$1({ name: "MenuContext" }); const [MenuGroupContext, useMenuGroupContext] = createContext$1({ name: "MenuGroupContext" }); const [MainMenuContext, useMainMenuContext] = createContext$1({ name: "MainMenuContext", strict: false }); const [MenuOptionGroupContext, useMenuOptionGroupContext] = createContext$1({ name: "MenuOptionGroupContext" }); const useMenu = ({ closeOnSelect, defaultOpen, disabled = false, open: openProp, subMenuDirection = "end", onClose: onCloseProp, onOpen: onOpenProp, onSelect: onSelectProp } = {}) => { const triggerId = useId(); const contentId = useId(); const descendants = useMenuDescendants(); const updateRef = useRef(utils_exports.noop); const onCloseRef = useRef(utils_exports.noop); const contentRef = useRef(null); const { open, onClose, onOpen } = useDisclosure({ defaultOpen, open: openProp, onClose: onCloseProp, onOpen: onOpenProp }); const onCloseSubMenu = useCallback(() => onCloseRef.current(), []); const onActiveDescendant = useCallback((descendant, options = { preventScroll: true }) => { if (!contentRef.current || !descendant || disabled) return; contentRef.current.setAttribute("aria-activedescendant", descendant.id); descendants.active(descendant, options); }, [descendants, disabled]); const { mainCloseOnSelect, subMenu, getSubMenuProps, onMainSelect } = useSubMenu({ descendants, disabled, open, subMenuDirection, onActiveDescendant, onClose, onOpen }); closeOnSelect ??= mainCloseOnSelect ?? true; const onSelect = useCallback((value, closeOnSelectProp = closeOnSelect) => { if (disabled) return; onSelectProp?.(value); onMainSelect?.(value, closeOnSelectProp); if (!closeOnSelectProp) return; onClose(); }, [ closeOnSelect, disabled, onClose, onMainSelect, onSelectProp ]); const onClick = useCallback((ev) => { if (disabled) return; ev.preventDefault(); if (!open) onOpen(); else onClose(); }, [ disabled, onClose, onOpen, open ]); const onContextMenu = useCallback((ev) => { if (disabled) return; ev.preventDefault(); onOpen(); updateRef.current(); }, [disabled, onOpen]); const onKeyDown = useCallback((ev) => { if (disabled) return; runKeyAction(ev, { ArrowDown: () => { onOpen(); setTimeout(() => { onActiveDescendant(descendants.enabledFirstValue()); }); }, ArrowUp: () => { onOpen(); setTimeout(() => { onActiveDescendant(descendants.enabledLastValue()); }); }, Enter: () => { onOpen(); setTimeout(() => { onActiveDescendant(descendants.enabledFirstValue()); }); }, Space: () => { onOpen(); setTimeout(() => { onActiveDescendant(descendants.enabledFirstValue()); }); } }); }, [ descendants, disabled, onActiveDescendant, onOpen ]); const getTriggerProps = useCallback((props = {}) => ({ ...getSubMenuProps({ id: triggerId, "aria-controls": open ? contentId : void 0, "aria-disabled": (0, utils_exports.ariaAttr)(disabled), "aria-expanded": open, "aria-haspopup": "menu", "data-trigger": (0, utils_exports.dataAttr)(true), role: "button", tabIndex: disabled ? -1 : 0, ...props, onClick: (0, utils_exports.handlerAll)(props.onClick, onClick), onKeyDown: (0, utils_exports.handlerAll)(props.onKeyDown, onKeyDown) }) }), [ contentId, disabled, getSubMenuProps, onClick, onKeyDown, open, triggerId ]); const getContextTriggerProps = useCallback((props = {}) => ({ id: triggerId, "aria-controls": open ? contentId : void 0, "aria-disabled": (0, utils_exports.ariaAttr)(disabled), "aria-expanded": open, "aria-haspopup": "menu", "data-trigger": (0, utils_exports.dataAttr)(true), role: "application", ...props, onContextMenu: (0, utils_exports.handlerAll)(props.onContextMenu, onContextMenu) }), [ contentId, disabled, onContextMenu, open, triggerId ]); const getContentProps = useCallback(({ ref, "aria-labelledby": ariaLabelledby,...props } = {}) => ({ id: contentId, "aria-labelledby": (0, utils_exports.cx)(ariaLabelledby, triggerId), role: "menu", ...props, ref: mergeRefs(ref, contentRef) }), [contentId, triggerId]); const getSeparatorProps = useCallback((props) => ({ role: "separator", ...props }), []); return { closeOnSelect, descendants, open, subMenu, subMenuDirection, updateRef, getContentProps, getContextTriggerProps, getSeparatorProps, getTriggerProps, onActiveDescendant, onClose, onCloseRef, onCloseSubMenu, onOpen, onSelect }; }; const useSubMenu = ({ descendants, disabled = false, open, subMenuDirection = "end", onActiveDescendant, onClose, onOpen }) => { const uuid = useId(); const { closeOnSelect: mainCloseOnSelect, descendants: mainDescendants, onActiveDescendant: onActiveMainDescendant, onCloseRef, onSelect: onMainSelect } = useMainMenuContext() ?? {}; const subMenu = !!mainDescendants && !!onActiveMainDescendant; const createRegister = useMenuDescendantRegister(mainDescendants); const triggerRef = useRef(null); const dataDisabled = useCallback((node) => { node ??= triggerRef.current; if (!node) return false; return (0, utils_exports.isTruthyDataAttr)(node.getAttribute("data-disabled")); }, []); const ariaDisabled = useCallback((node) => { node ??= triggerRef.current; if (!node) return false; return (0, utils_exports.isTruthyDataAttr)(node.getAttribute("aria-disabled")); }, []); const onClick = useCallback((ev) => { if (!subMenu) return; ev.defaultPrevented = disabled || dataDisabled() || ariaDisabled(); }, [ ariaDisabled, dataDisabled, disabled, subMenu ]); const onMouseEnter = useCallback(() => { if (!subMenu || disabled || dataDisabled() || ariaDisabled()) return; onOpen(); }, [ ariaDisabled, dataDisabled, disabled, onOpen, subMenu ]); const onMouseMove = useCallback((ev) => { if (!subMenu || disabled || dataDisabled() || ariaDisabled()) return; onActiveMainDescendant(descendants.value(triggerRef.current)); ev.defaultPrevented = true; }, [ ariaDisabled, dataDisabled, descendants, disabled, onActiveMainDescendant, subMenu ]); const onKeyDown = useCallback((ev) => { if (!subMenu || disabled) return; const currentDescendant = open ? descendants : mainDescendants; const onActiveCurrentDescendant = open ? onActiveDescendant : onActiveMainDescendant; runKeyAction(ev, { ArrowDown: () => { onActiveCurrentDescendant(currentDescendant.enabledNextValue(triggerRef.current)); ev.defaultPrevented = true; }, ArrowUp: () => { onActiveCurrentDescendant(currentDescendant.enabledPrevValue(triggerRef.current)); ev.defaultPrevented = true; }, End: () => { onActiveCurrentDescendant(currentDescendant.enabledLastValue()); ev.defaultPrevented = true; }, Home: () => { onActiveCurrentDescendant(currentDescendant.enabledFirstValue()); ev.defaultPrevented = true; }, [subMenuDirection === "end" ? "ArrowRight" : "ArrowLeft"]: () => { onOpen(); setTimeout(() => { onActiveDescendant(descendants.enabledFirstValue()); }); ev.defaultPrevented = true; } }); }, [ subMenu, disabled, open, descendants, mainDescendants, onActiveDescendant, onActiveMainDescendant, subMenuDirection, onOpen ]); assignRef(onCloseRef, onClose); return { mainCloseOnSelect, subMenu, getSubMenuProps: useCallback(({ id = uuid, ref,...props } = {}) => { const getDisabled = (node) => disabled || dataDisabled(node) || ariaDisabled(node); const register = createRegister({ id, disabled: getDisabled }); return { role: subMenu ? "menuitem" : "button", ...props, ref: mergeRefs(ref, triggerRef, register), onClick: (0, utils_exports.handlerAll)(onClick, props.onClick), onKeyDown: (0, utils_exports.handlerAll)(onKeyDown, props.onKeyDown), onMouseEnter: (0, utils_exports.handlerAll)(onMouseEnter, props.onMouseEnter), onMouseMove: (0, utils_exports.handlerAll)(onMouseMove, props.onMouseMove) }; }, [ uuid, subMenu, createRegister, onClick, onKeyDown, onMouseEnter, onMouseMove, disabled, dataDisabled, ariaDisabled ]), onMainSelect }; }; const useMenuGroup = ({ "aria-labelledby": ariaLabelledbyProp,...rest }) => { const labelId = useId(); return { getGroupProps: useCallback(({ "aria-labelledby": ariaLabelledby,...props } = {}) => ({ "aria-labelledby": (0, utils_exports.cx)(ariaLabelledbyProp, ariaLabelledby, labelId), role: "group", ...rest, ...props }), [ ariaLabelledbyProp, labelId, rest ]), getLabelProps: useCallback((props) => ({ id: labelId, role: "presentation", ...props }), [labelId]) }; }; const useMenuItem = ({ id, "aria-disabled": ariaDisabled, "data-disabled": dataDisabled, "data-trigger": dataTrigger, closeOnSelect, disabled = false, value,...rest }) => { const trigger = (0, utils_exports.isTruthyDataAttr)(dataTrigger); const { subMenu, subMenuDirection, onActiveDescendant, onClose, onCloseSubMenu, onSelect } = useMenuContext(); const uuid = useId(); const itemRef = useRef(null); const subMenuTrigger = subMenu && trigger; id ??= uuid; const { descendants, register } = useMenuDescendant({ id, disabled: disabled || subMenuTrigger }); const onActive = useCallback(() => { if (disabled) return; onActiveDescendant(descendants.value(itemRef.current)); }, [ descendants, disabled, onActiveDescendant ]); const onKeyDown = useCallback((ev) => { runKeyAction(ev, { ArrowDown: () => { onActiveDescendant(descendants.enabledNextValue(itemRef.current)); }, ArrowUp: () => { onActiveDescendant(descendants.enabledPrevValue(itemRef.current)); }, End: () => { onActiveDescendant(descendants.enabledLastValue()); }, Enter: () => onSelect(value, closeOnSelect), Home: () => { onActiveDescendant(descendants.enabledFirstValue()); }, Space: () => onSelect(value, closeOnSelect), [subMenuDirection === "end" ? "ArrowLeft" : "ArrowRight"]: () => { if (!subMenu) return; onClose(); descendants.firstValue()?.node.focus(); } }); }, [ closeOnSelect, descendants, onActiveDescendant, onClose, onSelect, subMenu, subMenuDirection, value ]); return { subMenuTrigger, getItemProps: useCallback(({ ref,...props } = {}) => ({ id, "aria-disabled": ariaDisabled ?? (0, utils_exports.ariaAttr)(disabled), "data-disabled": dataDisabled ?? (0, utils_exports.dataAttr)(disabled), role: "menuitem", tabIndex: -1, ...rest, ...props, ref: mergeRefs(ref, rest.ref, itemRef, register), onClick: (0, utils_exports.handlerAll)(props.onClick, rest.onClick, () => onSelect(value, closeOnSelect)), onFocus: (0, utils_exports.handlerAll)(props.onFocus, rest.onFocus, onActive), onKeyDown: (0, utils_exports.handlerAll)(props.onKeyDown, rest.onKeyDown, onKeyDown), onMouseMove: (0, utils_exports.handlerAll)(props.onMouseMove, rest.onMouseMove, () => { onCloseSubMenu(); onActive(); }) }), [ id, ariaDisabled, disabled, dataDisabled, rest, register, onActive, onKeyDown, onSelect, value, closeOnSelect, onCloseSubMenu ]) }; }; const useMenuOptionGroup = ({ type = "checkbox", defaultValue = type === "checkbox" ? [] : "", value: valueProp, onChange: onChangeProp }) => { const [value, setValue] = useControllableState({ defaultValue, value: valueProp, onChange: onChangeProp }); const radio = type === "radio"; const onChange = useCallback((selectedValue) => { setValue((prev) => { if (radio && (0, utils_exports.isString)(prev)) return selectedValue; else if (!radio && (0, utils_exports.isArray)(prev)) return prev.includes(selectedValue) ? prev.filter((value$1) => value$1 !== selectedValue) : prev.concat(selectedValue); else return prev; }); }, [radio, setValue]); useUpdateEffect(() => { setValue(valueProp); }, [valueProp]); return { type, value, onChange }; }; const useMenuOptionItem = ({ disabled, value,...rest }) => { const { type, value: selectedValue, onChange } = useMenuOptionGroupContext(); const { getItemProps } = useMenuItem({ disabled, value, ...rest }); const radio = type === "radio" && (0, utils_exports.isString)(selectedValue); const checkbox = type === "checkbox" && (0, utils_exports.isArray)(selectedValue); const selected = radio ? value === selectedValue : checkbox ? selectedValue.includes(value) : false; return { type, selected, getIndicatorProps: useCallback(({ style,...props } = {}) => ({ style: { opacity: selected ? 1 : 0, ...style }, ...props }), [selected]), getOptionItemProps: useCallback((props = {}) => getItemProps({ role: radio ? "menuitemradio" : "menuitemcheckbox", ...props, onClick: (0, utils_exports.handlerAll)(props.onClick, () => !disabled ? onChange?.(value) : utils_exports.noop) }), [ disabled, getItemProps, onChange, radio, value ]) }; }; //#endregion export { MainMenuContext, MenuContext, MenuDescendantsContext, MenuGroupContext, MenuOptionGroupContext, useMainMenuContext, useMenu, useMenuContext, useMenuDescendant, useMenuDescendants, useMenuGroup, useMenuGroupContext, useMenuItem, useMenuOptionGroup, useMenuOptionGroupContext, useMenuOptionItem, useSubMenu }; //# sourceMappingURL=use-menu.js.map