UNPKG

polen

Version:

A framework for delightful GraphQL developer portals

132 lines 4.53 kB
/** * 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