// @subframe/sync-disable import { useCallback, useEffect, useRef, type PointerEventHandler } from "react"; const SIDEBAR_HOVER_ITEM_SELECTOR = '[data-sidebar-hover-item="true"]'; const SIDEBAR_HOVER_SURFACE_SELECTOR = '[data-sidebar-hover-surface="true"]'; const SIDEBAR_SWEEP_HOVER_ATTRIBUTE = "data-sidebar-sweep-hover"; const SIDEBAR_SWEEP_HOVER_DURATION_MS = 150; type SidebarHoverSweepOptions = { onPointerMove?: PointerEventHandler; onPointerLeave?: PointerEventHandler; }; export function useSidebarHoverSweep({ onPointerMove, onPointerLeave }: SidebarHoverSweepOptions) { const previousClientYRef = useRef(null); const cleanupTimersRef = useRef(new Map>()); const pulseSurface = useCallback((surface: HTMLElement) => { const existingTimer = cleanupTimersRef.current.get(surface); if (existingTimer !== undefined) clearTimeout(existingTimer); surface.setAttribute(SIDEBAR_SWEEP_HOVER_ATTRIBUTE, "true"); const timer = setTimeout(() => { surface.removeAttribute(SIDEBAR_SWEEP_HOVER_ATTRIBUTE); cleanupTimersRef.current.delete(surface); }, SIDEBAR_SWEEP_HOVER_DURATION_MS); cleanupTimersRef.current.set(surface, timer); }, []); useEffect( () => () => { for (const timer of cleanupTimersRef.current.values()) clearTimeout(timer); cleanupTimersRef.current.clear(); }, [], ); const handlePointerMove = useCallback>( (event) => { onPointerMove?.(event); const currentClientY = event.clientY; const previousClientY = previousClientYRef.current; previousClientYRef.current = currentClientY; if (previousClientY === null || previousClientY === currentClientY) return; const lowerBound = Math.min(previousClientY, currentClientY); const upperBound = Math.max(previousClientY, currentClientY); const items = event.currentTarget.querySelectorAll(SIDEBAR_HOVER_ITEM_SELECTOR); for (const item of items) { const bounds = item.getBoundingClientRect(); const centerY = bounds.top + bounds.height / 2; if (centerY <= lowerBound || centerY >= upperBound) continue; const surface = item.querySelector(SIDEBAR_HOVER_SURFACE_SELECTOR); if (surface) pulseSurface(surface); } }, [onPointerMove, pulseSurface], ); const handlePointerLeave = useCallback>( (event) => { previousClientYRef.current = null; onPointerLeave?.(event); }, [onPointerLeave], ); return { handlePointerMove, handlePointerLeave }; }