@drivy/cobalt
Version:
Opinionated design system for Drivy's projects.
133 lines (132 loc) • 4.79 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
import { registerPopover } from "./popoverRegistry.js";
import { useDesktopPopoverCore } from "./useDesktopPopoverCore.js";
function normalizeDelay(delay) {
if ("number" == typeof delay) return {
open: delay,
close: delay
};
return {
open: delay?.open ?? 0,
close: delay?.close ?? 0
};
}
function useSingletonPopover({ trigger = "mouseenter", delay = 0, interactive = false, gracePeriod = 100, ...options }) {
const [isOpen, setIsOpen] = useState(false);
const [referenceElement, setReferenceElement] = useState(null);
const [content, setContent] = useState(null);
const openTimeoutRef = useRef(null);
const closeTimeoutRef = useRef(null);
const delays = normalizeDelay(delay);
const clearOpenTimeout = useCallback(()=>{
if (null != openTimeoutRef.current) {
window.clearTimeout(openTimeoutRef.current);
openTimeoutRef.current = null;
}
}, []);
const clearCloseTimeout = useCallback(()=>{
if (null != closeTimeoutRef.current) {
window.clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = null;
}
}, []);
useEffect(()=>()=>{
if (null != openTimeoutRef.current) window.clearTimeout(openTimeoutRef.current);
if (null != closeTimeoutRef.current) window.clearTimeout(closeTimeoutRef.current);
}, []);
const closePopover = useCallback((immediate = false, useGracePeriod = false)=>{
clearOpenTimeout();
const run = ()=>{
setIsOpen(false);
};
const closeDelay = useGracePeriod ? Math.max(delays.close, gracePeriod) : delays.close;
if (immediate || 0 === closeDelay) {
clearCloseTimeout();
run();
return;
}
clearCloseTimeout();
closeTimeoutRef.current = window.setTimeout(run, closeDelay);
}, [
clearOpenTimeout,
clearCloseTimeout,
delays.close,
gracePeriod
]);
useEffect(()=>registerPopover(()=>{
closePopover(true);
}), [
closePopover
]);
const core = useDesktopPopoverCore({
...options,
isOpen,
onOpenChange: setIsOpen,
referenceElement,
trigger: "manual",
interactive,
getFloatingExtraProps: interactive && "mouseenter" === trigger ? ()=>({
onMouseEnter: ()=>{
clearCloseTimeout();
},
onMouseLeave: ()=>{
closePopover(false, true);
}
}) : void 0
});
const openWithItem = (item, element, immediate = false)=>{
clearCloseTimeout();
const run = ()=>{
setReferenceElement(element);
core.refs.setReference(element);
setContent(item.content);
setIsOpen(true);
};
const shouldOpenImmediately = immediate || isOpen || 0 === delays.open;
if (shouldOpenImmediately) {
clearOpenTimeout();
run();
return;
}
clearOpenTimeout();
openTimeoutRef.current = window.setTimeout(run, delays.open);
};
const getReferenceProps = (item, userProps = {})=>{
const { onMouseEnter, onMouseLeave, onPointerEnter, onPointerLeave, onClick, ...restUserProps } = userProps;
return core.getReferenceProps({
...restUserProps,
onMouseEnter: (e)=>{
onMouseEnter?.(e);
if ("mouseenter" === trigger) openWithItem(item, e.currentTarget);
},
onPointerEnter: (e)=>{
onPointerEnter?.(e);
},
onMouseLeave: (e)=>{
onMouseLeave?.(e);
if ("mouseenter" === trigger && !interactive) closePopover(false, true);
},
onPointerLeave: (e)=>{
onPointerLeave?.(e);
},
onClick: (e)=>{
onClick?.(e);
if ("click" === trigger) {
const sameReference = referenceElement === e.currentTarget;
setReferenceElement(e.currentTarget);
core.refs.setReference(e.currentTarget);
setContent(item.content);
sameReference && isOpen ? setIsOpen(false) : setIsOpen(true);
}
}
});
};
return {
getReferenceProps,
renderFloating: ()=>null == content ? null : core.renderFloating(content),
close: ()=>closePopover(true),
isOpen
};
}
export { useSingletonPopover };
//# sourceMappingURL=useSingletonPopover.js.map