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