polen
Version:
A framework for delightful GraphQL developer portals
132 lines • 4.53 kB
JavaScript
/**
* State management for GraphQL document tooltips
*
* Handles hover delays, pinning, and multiple tooltip coordination
*/
import { React } from '#dep/react/index';
export const useTooltipState = (options = {}) => {
const { showDelay = 300, hideDelay = 200, allowMultiplePins = true, } = options;
const [hoveredId, setHoveredId] = React.useState(null);
const [pinnedIds, setPinnedIds] = React.useState(new Set());
const [pendingShowId, setPendingShowId] = React.useState(null);
const [pendingHideId, setPendingHideId] = React.useState(null);
// Timer refs
const showTimerRef = React.useRef(null);
const hideTimerRef = React.useRef(null);
// Clear any pending timers
const clearTimers = React.useCallback(() => {
if (showTimerRef.current) {
clearTimeout(showTimerRef.current);
showTimerRef.current = null;
}
if (hideTimerRef.current) {
clearTimeout(hideTimerRef.current);
hideTimerRef.current = null;
}
setPendingShowId(null);
setPendingHideId(null);
}, []);
// Check if tooltip should be visible
const isOpen = React.useCallback((id) => {
return hoveredId === id || pinnedIds.has(id);
}, [hoveredId, pinnedIds]);
// Check if tooltip is pinned
const isPinned = React.useCallback((id) => {
return pinnedIds.has(id);
}, [pinnedIds]);
// Handle hover start
const onHoverStart = React.useCallback((id) => {
// Don't show if already pinned
if (pinnedIds.has(id))
return;
// Cancel any pending hide for this ID
if (pendingHideId === id) {
clearTimeout(hideTimerRef.current);
hideTimerRef.current = null;
setPendingHideId(null);
return;
}
// Clear any other pending operations
clearTimers();
// Schedule show
setPendingShowId(id);
showTimerRef.current = setTimeout(() => {
setHoveredId(id);
setPendingShowId(null);
}, showDelay);
}, [pinnedIds, pendingHideId, clearTimers, showDelay]);
// Handle hover end
const onHoverEnd = React.useCallback((id) => {
// Don't hide if pinned
if (pinnedIds.has(id))
return;
// Cancel pending show if still waiting
if (pendingShowId === id) {
clearTimeout(showTimerRef.current);
showTimerRef.current = null;
setPendingShowId(null);
return;
}
// Only hide if currently showing this tooltip
if (hoveredId === id) {
setPendingHideId(id);
hideTimerRef.current = setTimeout(() => {
// First set hovered to null to trigger close animation
setHoveredId(null);
setPendingHideId(null);
}, hideDelay);
}
}, [pinnedIds, pendingShowId, hoveredId, hideDelay]);
// Handle tooltip content hover (cancels hide)
const onTooltipHover = React.useCallback((id) => {
if (pendingHideId === id) {
clearTimeout(hideTimerRef.current);
hideTimerRef.current = null;
setPendingHideId(null);
}
}, [pendingHideId]);
// Toggle pin state
const onTogglePin = React.useCallback((id) => {
clearTimers();
setPinnedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) {
// Unpin
next.delete(id);
setHoveredId(null); // Also clear hover state
}
else {
// Pin
if (!allowMultiplePins) {
next.clear(); // Clear other pins
}
next.add(id);
setHoveredId(null); // Clear hover state since it's now pinned
}
return next;
});
}, [clearTimers, allowMultiplePins]);
// Unpin specific tooltip
const unpin = React.useCallback((id) => {
setPinnedIds((prev) => {
const next = new Set(prev);
next.delete(id);
return next;
});
}, []);
// Unpin all tooltips
const unpinAll = React.useCallback(() => {
setPinnedIds(new Set());
}, []);
return {
isOpen,
isPinned,
onHoverStart,
onHoverEnd,
onTogglePin,
onTooltipHover,
unpin,
unpinAll,
};
};
//# sourceMappingURL=use-tooltip-state.js.map