UNPKG

polen

Version:

A framework for delightful GraphQL developer portals

176 lines 6.23 kB
/** * State management for GraphQL token popovers * * Handles hover delays, pinning, and multiple popover coordination */ import { React as ReactHooks } from '#dep/react/index'; export const usePopoverState = (options = {}) => { const { showDelay = 300, hideDelay = 200, allowMultiplePins = true, } = options; const [hoveredId, setHoveredId] = ReactHooks.useState(null); const [pinnedIds, setPinnedIds] = ReactHooks.useState(new Set()); const [pendingShowId, setPendingShowId] = ReactHooks.useState(null); const [pendingHideId, setPendingHideId] = ReactHooks.useState(null); // Timer refs const showTimerRef = ReactHooks.useRef(null); const hideTimerRef = ReactHooks.useRef(null); // Clear any pending timers const clearTimers = ReactHooks.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 popover should be visible const isOpen = ReactHooks.useCallback((id) => { return hoveredId === id || pinnedIds.has(id); }, [hoveredId, pinnedIds]); // Check if popover is pinned const isPinned = ReactHooks.useCallback((id) => { return pinnedIds.has(id); }, [pinnedIds]); // Handle hover start const onHoverStart = ReactHooks.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 existing timers clearTimers(); // Set pending show setPendingShowId(id); // Start show timer showTimerRef.current = setTimeout(() => { setHoveredId(id); setPendingShowId(null); showTimerRef.current = null; }, showDelay); }, [pinnedIds, pendingHideId, clearTimers, showDelay]); // Handle hover end const onHoverEnd = ReactHooks.useCallback((id) => { // Don't hide if pinned if (pinnedIds.has(id)) return; // Cancel any pending show if (pendingShowId === id) { clearTimeout(showTimerRef.current); showTimerRef.current = null; setPendingShowId(null); return; } // Only hide if currently hovered if (hoveredId === id) { setPendingHideId(id); hideTimerRef.current = setTimeout(() => { setHoveredId(null); setPendingHideId(null); hideTimerRef.current = null; }, hideDelay); } }, [pinnedIds, pendingShowId, hoveredId, hideDelay]); // Handle popover content hover (cancels hide) const onPopoverHover = ReactHooks.useCallback((id) => { if (pendingHideId === id) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; setPendingHideId(null); } }, [pendingHideId]); // Handle popover content leave (starts hide timer) const onPopoverLeave = ReactHooks.useCallback((id) => { // Don't hide if pinned if (pinnedIds.has(id)) return; // Only hide if currently hovered if (hoveredId === id) { setPendingHideId(id); hideTimerRef.current = setTimeout(() => { setHoveredId(null); setPendingHideId(null); hideTimerRef.current = null; }, hideDelay); } }, [pinnedIds, hoveredId, hideDelay]); // Toggle pin state const onTogglePin = ReactHooks.useCallback((id) => { setPinnedIds(prev => { const next = new Set(prev); if (next.has(id)) { // Unpin next.delete(id); } else { // Pin if (!allowMultiplePins) { // Clear other pins if multiple not allowed next.clear(); } next.add(id); // Clear any hover state for this ID if (hoveredId === id) { setHoveredId(null); } // Clear any pending operations if (pendingShowId === id) { clearTimeout(showTimerRef.current); showTimerRef.current = null; setPendingShowId(null); } if (pendingHideId === id) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; setPendingHideId(null); } } return next; }); }, [allowMultiplePins, hoveredId, pendingShowId, pendingHideId]); // Unpin specific popover const unpin = ReactHooks.useCallback((id) => { setPinnedIds(prev => { const next = new Set(prev); next.delete(id); return next; }); }, []); // Unpin all popovers const unpinAll = ReactHooks.useCallback(() => { setPinnedIds(new Set()); }, []); // Cleanup timers on unmount and when dependencies change ReactHooks.useEffect(() => { return () => { if (showTimerRef.current) clearTimeout(showTimerRef.current); if (hideTimerRef.current) clearTimeout(hideTimerRef.current); }; }, []); // Clear timers when delay options change to prevent stale timers ReactHooks.useEffect(() => { clearTimers(); }, [showDelay, hideDelay, clearTimers]); return { isOpen, isPinned, onHoverStart, onHoverEnd, onTogglePin, onPopoverHover, onPopoverLeave, unpin, unpinAll, }; }; //# sourceMappingURL=use-popover-state.js.map