import { useEffect, type RefObject } from 'react'; import type { MediaParticleHandle } from '@/features/particles/media-particle-view'; import { isTypingTarget } from '@/lib/keyboard'; const SEEK_DELTA_SEC = 5; interface UseStreamNavigationKeysOptions { next: () => void; prev: () => void; currentIndex: number; childrenLength: number; mediaRef: RefObject; onExit: () => void; onToggleViewMode: () => void; } /** * Arrow keys (with shift+arrow seek), Escape. Skipped while playback is * paused for any reason (overlay, hold-space, compose). */ export function useStreamNavigationKeys({ next, prev, currentIndex, childrenLength, mediaRef, onExit, onToggleViewMode, }: UseStreamNavigationKeysOptions) { useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (isTypingTarget(e)) return; const hasNext = currentIndex >= 0 && currentIndex < childrenLength - 1; switch (e.key) { case 'ArrowRight': e.preventDefault(); if (!e.shiftKey || !mediaRef.current?.seek(SEEK_DELTA_SEC)) { if (hasNext) next(); } break; case 'ArrowDown': e.preventDefault(); if (hasNext) next(); break; case 'ArrowLeft': e.preventDefault(); if (!e.shiftKey || !mediaRef.current?.seek(-SEEK_DELTA_SEC)) prev(); break; case 'ArrowUp': e.preventDefault(); prev(); break; case 'Escape': e.preventDefault(); onExit(); break; case 'l': case 'L': // Registered here (not in action keys) because the toggle must work // in list mode, where playback is suspended and action keys bail. e.preventDefault(); onToggleViewMode(); break; } }; window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, [ next, prev, currentIndex, childrenLength, mediaRef, onExit, onToggleViewMode, ]); }