import { useEffect, useRef, RefObject } from 'react'; interface UseWheelNavigationProps { containerRef: RefObject; onScrollDown: () => void; onScrollUp: () => void; disabled?: boolean; /** * When the wheel event originates from inside this element, * skip navigation and let native scrolling happen. */ excludeRef?: RefObject; /** * Minimum accumulated deltaY (px) to trigger navigation. * Prevents accidental triggers from trackpad micro-scrolls. */ threshold?: number; /** * Cooldown in ms after a navigation fires before the next one can trigger. * Prevents rapid-fire scrolling from skipping multiple posts at once. */ cooldownMs?: number; } /** * Triggers vertical navigation when the user scrolls (trackpad or mouse wheel) * over the container. Scroll down → next post, scroll up → previous post. * * Designed for the vertical player mode so the experience feels like TikTok / Reels * on a desktop browser. */ const useWheelNavigation = ({ containerRef, onScrollDown, onScrollUp, disabled = false, excludeRef, threshold = 30, cooldownMs = 600, }: UseWheelNavigationProps) => { const onScrollDownRef = useRef(onScrollDown); const onScrollUpRef = useRef(onScrollUp); useEffect(() => { onScrollDownRef.current = onScrollDown; onScrollUpRef.current = onScrollUp; }, [onScrollDown, onScrollUp]); useEffect(() => { const container = containerRef.current; if (!container || disabled) return; let accumulated = 0; let lastFired = 0; const handleWheel = (e: WheelEvent) => { // Let the product list (or other excluded areas) scroll natively. if (excludeRef?.current && excludeRef.current.contains(e.target as Node)) { return; } // Prevent the page from scrolling while the player is open. e.preventDefault(); const now = Date.now(); if (now - lastFired < cooldownMs) { // Still in cooldown — reset accumulator so the next gesture starts fresh. accumulated = 0; return; } accumulated += e.deltaY; if (Math.abs(accumulated) >= threshold) { if (accumulated > 0) { onScrollDownRef.current(); } else { onScrollUpRef.current(); } accumulated = 0; lastFired = now; } }; // Must be non-passive so we can call preventDefault(). container.addEventListener('wheel', handleWheel, { passive: false }); return () => container.removeEventListener('wheel', handleWheel); }, [containerRef, disabled, excludeRef, threshold, cooldownMs]); }; export default useWheelNavigation;