@yamada-ui/react
Version:
React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion
210 lines (206 loc) • 6.62 kB
JavaScript
"use client";
import { getEventRelatedTarget, runKeyAction } from "../../utils/dom.js";
import { useSafeLayoutEffect, useUnmountEffect } from "../../utils/effect.js";
import { assignRef, mergeRefs } from "../../utils/ref.js";
import { utils_exports } from "../../utils/index.js";
import { useEnvironment } from "../../core/system/environment-provider.js";
import { useSplitProps } from "../../core/components/props.js";
import { useDisclosure } from "../../hooks/use-disclosure/use-disclosure.js";
import { useEventListener } from "../../hooks/use-event-listener/index.js";
import { useFocusOnShow } from "../../hooks/use-focus/index.js";
import { useOutsideClick } from "../../hooks/use-outside-click/index.js";
import { popperProps, usePopper } from "../../hooks/use-popper/index.js";
import { useCallback, useEffect, useId, useRef } from "react";
//#region src/components/popover/use-popover.tsx
const usePopover = ({ autoFocus = true, autoUpdate, modal = false, blockScrollOnMount = modal, closeOnBlur = true, closeOnEsc = true, closeOnScroll, defaultOpen, disabled, elements, flip, gutter, initialFocusRef, matchWidth, middleware, offset, open: openProp, openOnClick = true, placement = "end", platform, preventOverflow, strategy, transferFocus = true, transform, updateRef, whileElementsMounted, onClose: onCloseProp, onOpen: onOpenProp } = {}) => {
const { getDocument } = useEnvironment();
const headerId = useId();
const bodyId = useId();
const contentId = useId();
const anchorRef = useRef(null);
const triggerRef = useRef(null);
const contentRef = useRef(null);
const openTimeout = useRef(void 0);
const closeTimeout = useRef(void 0);
const { open, onClose, onOpen } = useDisclosure({
defaultOpen,
open: openProp,
onClose: onCloseProp,
onOpen: onOpenProp
});
const { refs, update, getPopperProps } = usePopper({
autoUpdate,
elements,
flip,
gutter,
matchWidth,
middleware,
offset,
open,
placement,
platform,
preventOverflow,
strategy,
transform,
whileElementsMounted
});
assignRef(updateRef, update);
const onKeyDown = useCallback((ev) => {
runKeyAction(ev, { Escape: () => {
if (!closeOnEsc) return;
onClose();
triggerRef.current?.focus();
} });
}, [closeOnEsc, onClose]);
const onBlur = useCallback((ev) => {
const relatedTarget = getEventRelatedTarget(ev);
const popup = relatedTarget?.hasAttribute("data-popup");
if ((0, utils_exports.contains)(triggerRef.current, relatedTarget)) return;
if ((0, utils_exports.contains)(contentRef.current, relatedTarget)) return;
if ((0, utils_exports.contains)(contentRef.current, ev.target) && popup) return;
if (closeOnBlur) onClose();
}, [closeOnBlur, onClose]);
useEventListener(getDocument(), "scroll", () => {
if (open && closeOnScroll) onClose();
});
useFocusOnShow(contentRef, {
focusTarget: initialFocusRef,
shouldFocus: autoFocus,
visible: open
});
useOutsideClick({
ref: [contentRef, triggerRef],
enabled: open && closeOnBlur,
handler: onClose
});
useSafeLayoutEffect(() => {
const el = contentRef.current;
const hasHeader = !!getDocument()?.getElementById(headerId);
const hasBody = !!getDocument()?.getElementById(bodyId);
if (el && hasHeader) (0, utils_exports.setAttribute)(el, "aria-labelledby", headerId);
if (el && hasBody) (0, utils_exports.setAttribute)(el, "aria-describedby", bodyId);
}, [
open,
headerId,
bodyId
]);
useEffect(() => {
if (!open || !modal) return;
return (0, utils_exports.focusTrap)(contentRef.current);
}, [open, modal]);
useEffect(() => {
if (!open || !blockScrollOnMount) return;
return (0, utils_exports.scrollLock)(contentRef.current);
}, [
open,
modal,
blockScrollOnMount
]);
useEffect(() => {
if (!open || modal || !transferFocus) return;
return (0, utils_exports.focusTransfer)(contentRef.current, triggerRef.current);
}, [
open,
modal,
transferFocus
]);
useUnmountEffect(() => {
clearTimeout(openTimeout.current);
clearTimeout(closeTimeout.current);
});
const getTriggerProps = useCallback(({ ref,...props } = {}) => ({
"aria-controls": open ? contentId : void 0,
"aria-disabled": (0, utils_exports.ariaAttr)(disabled),
"aria-expanded": open,
"aria-haspopup": "dialog",
role: "button",
...props,
ref: mergeRefs(ref, triggerRef, (node) => {
if (anchorRef.current == null) refs.setReference(node);
}),
onBlur: (0, utils_exports.handlerAll)(props.onBlur, (ev) => !(0, utils_exports.contains)(contentRef.current, getEventRelatedTarget(ev)) && closeOnBlur ? onClose() : void 0),
onClick: (0, utils_exports.handlerAll)(props.onClick, !open ? !disabled && openOnClick ? onOpen : void 0 : onClose),
onKeyDown: (0, utils_exports.handlerAll)(props.onKeyDown, onKeyDown)
}), [
closeOnBlur,
contentId,
disabled,
onClose,
onKeyDown,
onOpen,
open,
openOnClick,
refs
]);
const getAnchorProps = useCallback(({ ref,...props } = {}) => ({
...props,
ref: mergeRefs(ref, anchorRef, refs.setReference)
}), [refs.setReference]);
const getPositionerProps = useCallback((props) => {
return getPopperProps(props);
}, [getPopperProps]);
const getContentProps = useCallback(({ ref,...props } = {}) => ({
id: contentId,
"aria-hidden": !open,
"aria-modal": modal ? "true" : void 0,
"data-close": (0, utils_exports.dataAttr)(!open),
"data-open": (0, utils_exports.dataAttr)(open),
"data-popup": (0, utils_exports.dataAttr)(true),
role: "dialog",
tabIndex: -1,
...props,
ref: mergeRefs(ref, contentRef),
onBlur: (0, utils_exports.handlerAll)(props.onBlur, onBlur),
onKeyDown: (0, utils_exports.handlerAll)(props.onKeyDown, onKeyDown)
}), [
contentId,
open,
modal,
onBlur,
onKeyDown
]);
const getHeaderProps = useCallback((props) => ({
id: headerId,
...props
}), [headerId]);
return {
open,
getAnchorProps,
getBodyProps: useCallback((props) => ({
id: bodyId,
...props
}), [bodyId]),
getContentProps,
getFooterProps: useCallback((props) => ({ ...props }), []),
getHeaderProps,
getPositionerProps,
getTriggerProps,
onClose,
onOpen
};
};
const popoverProps = [
...popperProps,
"autoFocus",
"transferFocus",
"blockScrollOnMount",
"closeOnBlur",
"closeOnEsc",
"closeOnScroll",
"openOnClick",
"disabled",
"initialFocusRef",
"modal",
"updateRef",
"defaultOpen",
"onOpen",
"onClose",
"animationScheme",
"duration"
];
const usePopoverProps = (props, omitKeys) => {
return useSplitProps(props, popoverProps.filter((key) => !omitKeys?.includes(key)));
};
//#endregion
export { popoverProps, usePopover, usePopoverProps };
//# sourceMappingURL=use-popover.js.map