@yamada-ui/popover
Version:
Yamada UI popover component
291 lines (289 loc) • 8.53 kB
JavaScript
"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