UNPKG

@yamada-ui/popover

Version:

Yamada UI popover component

291 lines (289 loc) • 8.53 kB
"use client" // src/popover.tsx import { omitThemeProps, useComponentMultiStyle } from "@yamada-ui/core"; import { useAnimationObserver } from "@yamada-ui/use-animation"; import { useDisclosure, useLazyDisclosure } from "@yamada-ui/use-disclosure"; import { useFocusOnHide, useFocusOnPointerDown, useFocusOnShow } from "@yamada-ui/use-focus"; import { popperProperties, usePopper } from "@yamada-ui/use-popper"; import { createContext, getEventRelatedTarget, handlerAll, isContains, mergeRefs, runIfFunc } from "@yamada-ui/utils"; import { useCallback, useEffect, useId, useRef } from "react"; import { jsx } from "react/jsx-runtime"; var popoverProperties = [ ...popperProperties, "open", "isOpen", "defaultOpen", "defaultIsOpen", "onOpen", "onClose", "initialFocusRef", "restoreFocus", "autoFocus", "closeOnBlur", "closeOnEsc", "closeOnButton", "trigger", "openDelay", "closeDelay", "lazy", "isLazy", "lazyBehavior", "animation", "duration" ]; var [PopoverProvider, usePopover] = createContext({ name: "PopoverContext", errorMessage: `usePopoverContext returned is 'undefined'. Seems you forgot to wrap the components in "<Popover />"` }); var Popover = (props) => { const [styles, mergedProps] = useComponentMultiStyle("Popover", props); const { animation = "scale", autoFocus = true, children, closeDelay = 200, closeOnBlur = true, closeOnButton = true, closeOnEsc = true, duration, initialFocusRef, isLazy, lazy = isLazy, lazyBehavior = "unmount", openDelay = 200, relatedRef, restoreFocus = true, trigger = "click", ...rest } = omitThemeProps(mergedProps); const id = useId(); const { open, onClose, onOpen, onToggle } = useDisclosure(mergedProps); const anchorRef = useRef(null); const triggerRef = useRef(null); const headerRef = useRef(null); const bodyRef = useRef(null); const popoverRef = useRef(null); const { present, onAnimationComplete } = useAnimationObserver({ ref: popoverRef, open }); const openTimeout = useRef(void 0); const closeTimeout = useRef(void 0); const hoveringRef = useRef(false); const hasBeenOpened = useRef(false); const { forceUpdate, referenceRef, transformOrigin, getPopperProps } = usePopper({ ...rest, enabled: open }); if (open) hasBeenOpened.current = true; useEffect(() => { return () => { if (openTimeout.current) clearTimeout(openTimeout.current); if (closeTimeout.current) clearTimeout(closeTimeout.current); }; }, []); useFocusOnPointerDown({ ref: triggerRef, enabled: open }); useFocusOnHide(popoverRef, { focusRef: triggerRef, shouldFocus: restoreFocus && (trigger === "click" || trigger === "contextmenu"), visible: open }); useFocusOnShow(popoverRef, { focusRef: initialFocusRef, shouldFocus: autoFocus && (trigger === "click" || trigger === "contextmenu"), visible: open }); const shouldRenderContent = useLazyDisclosure({ enabled: lazy, isSelected: present, mode: lazyBehavior, wasSelected: hasBeenOpened.current }); const getPopoverProps = useCallback( (props2 = {}, ref = null) => { var _a, _b; const popoverProps = { id, "aria-describedby": (_a = bodyRef.current) == null ? void 0 : _a.id, "aria-hidden": !open, "aria-labelledby": (_b = headerRef.current) == null ? void 0 : _b.id, role: "dialog", ...props2, ref: mergeRefs(popoverRef, ref), style: { ...props2.style, transformOrigin }, tabIndex: -1, onBlur: handlerAll(props2.onBlur, (ev) => { const relatedTarget = getEventRelatedTarget(ev); const targetIsPopover = isContains(popoverRef.current, relatedTarget); const targetIsTrigger = isContains(triggerRef.current, relatedTarget); const targetIsRelated = (relatedRef == null ? void 0 : relatedRef.current) ? isContains(relatedRef.current, relatedTarget) : false; const validBlur = !targetIsPopover && !targetIsTrigger && !targetIsRelated; if (open && closeOnBlur && validBlur) onClose(); }), onKeyDown: handlerAll(props2.onKeyDown, (ev) => { if (closeOnEsc && ev.key === "Escape") onClose(); }) }; if (trigger === "hover") { popoverProps.onMouseEnter = handlerAll(props2.onMouseEnter, () => { hoveringRef.current = true; }); popoverProps.onMouseLeave = handlerAll(props2.onMouseLeave, (ev) => { if (ev.nativeEvent.relatedTarget === null) return; hoveringRef.current = false; if (closeOnBlur) setTimeout(onClose, closeDelay); }); } return popoverProps; }, [ closeDelay, closeOnBlur, closeOnEsc, open, onClose, transformOrigin, trigger, relatedRef, id ] ); const maybeReferenceRef = useCallback( (node) => { if (anchorRef.current == null) referenceRef(node); }, [referenceRef] ); const getTriggerProps = useCallback( (props2 = {}, ref = null) => { const triggerProps = { "aria-controls": open ? id : void 0, "aria-expanded": open, role: "button", ...props2, ref: mergeRefs(triggerRef, ref, maybeReferenceRef) }; if (trigger === "click") { triggerProps.onClick = handlerAll(props2.onClick, onToggle); triggerProps.onBlur = handlerAll(props2.onBlur, (ev) => { const relatedTarget = getEventRelatedTarget(ev); const validBlur = !isContains(popoverRef.current, relatedTarget); if (open && closeOnBlur && validBlur) onClose(); }); } if (trigger === "contextmenu") { triggerProps.onContextMenu = handlerAll(props2.onContextMenu, (ev) => { ev.preventDefault(); onOpen(); }); triggerProps.onBlur = handlerAll(props2.onBlur, (ev) => { const relatedTarget = getEventRelatedTarget(ev); const validBlur = !isContains(popoverRef.current, relatedTarget); if (open && closeOnBlur && validBlur) onClose(); }); } if (trigger === "hover") { triggerProps.onFocus = handlerAll(props2.onFocus, () => { if (openTimeout.current === void 0) onOpen(); }); triggerProps.onBlur = handlerAll(props2.onBlur, (ev) => { const relatedTarget = getEventRelatedTarget(ev); const validBlur = !isContains(popoverRef.current, relatedTarget); if (open && closeOnBlur && validBlur) onClose(); }); triggerProps.onKeyDown = handlerAll(props2.onKeyDown, (ev) => { if (ev.key === "Escape") onClose(); }); triggerProps.onMouseEnter = handlerAll(props2.onMouseEnter, () => { hoveringRef.current = true; openTimeout.current = window.setTimeout(onOpen, openDelay); }); triggerProps.onMouseLeave = handlerAll(props2.onMouseLeave, () => { hoveringRef.current = false; if (openTimeout.current) { clearTimeout(openTimeout.current); openTimeout.current = void 0; } closeTimeout.current = window.setTimeout(() => { if (!hoveringRef.current) onClose(); }, closeDelay); }); } return triggerProps; }, [ closeDelay, closeOnBlur, open, maybeReferenceRef, onClose, onOpen, onToggle, openDelay, trigger, id ] ); const getAnchorProps = useCallback( (props2 = {}, ref = null) => { return { ...props2, ref: mergeRefs(ref, anchorRef, referenceRef) }; }, [anchorRef, referenceRef] ); return /* @__PURE__ */ jsx( PopoverProvider, { value: { id, animation, bodyRef, closeOnButton, duration, forceUpdate, headerRef, open, shouldRenderContent, styles, getAnchorProps, getPopoverProps, getPopperProps, getTriggerProps, onAnimationComplete, onClose }, children: runIfFunc(children, { forceUpdate, open, onClose }) } ); }; Popover.displayName = "Popover"; Popover.__ui__ = "Popover"; export { popoverProperties, usePopover, Popover }; //# sourceMappingURL=chunk-QNKQMZVS.mjs.map