import { useEffect, useRef } from "react"; export interface TimelineScrollSyncProps { rulerScrollRef: React.RefObject; tracksScrollRef: React.RefObject; trackLabelsScrollRef: React.RefObject; } export function useTimelineScrollSync({ rulerScrollRef, tracksScrollRef, trackLabelsScrollRef, }: TimelineScrollSyncProps) { const isUpdatingRef = useRef(false); const lastRulerSync = useRef(0); const lastTracksSync = useRef(0); const lastVerticalSync = useRef(0); // --- Scroll synchronization effect --- useEffect(() => { const rulerViewport = rulerScrollRef.current?.querySelector( "[data-radix-scroll-area-viewport]" ) as HTMLElement; const tracksViewport = tracksScrollRef.current?.querySelector( "[data-radix-scroll-area-viewport]" ) as HTMLElement; const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector( "[data-radix-scroll-area-viewport]" ) as HTMLElement; if (!rulerViewport || !tracksViewport) return; // Horizontal scroll synchronization between ruler and tracks const handleRulerScroll = () => { const now = Date.now(); if (isUpdatingRef.current || now - lastRulerSync.current < 16) return; lastRulerSync.current = now; isUpdatingRef.current = true; tracksViewport.scrollLeft = rulerViewport.scrollLeft; isUpdatingRef.current = false; }; const handleTracksScroll = () => { const now = Date.now(); if (isUpdatingRef.current || now - lastTracksSync.current < 16) return; lastTracksSync.current = now; isUpdatingRef.current = true; rulerViewport.scrollLeft = tracksViewport.scrollLeft; isUpdatingRef.current = false; }; rulerViewport.addEventListener("scroll", handleRulerScroll); tracksViewport.addEventListener("scroll", handleTracksScroll); // Vertical scroll synchronization between track labels and tracks content if (trackLabelsViewport) { const handleTrackLabelsScroll = () => { const now = Date.now(); if (isUpdatingRef.current || now - lastVerticalSync.current < 16) return; lastVerticalSync.current = now; isUpdatingRef.current = true; tracksViewport.scrollTop = trackLabelsViewport.scrollTop; isUpdatingRef.current = false; }; const handleTracksVerticalScroll = () => { const now = Date.now(); if (isUpdatingRef.current || now - lastVerticalSync.current < 16) return; lastVerticalSync.current = now; isUpdatingRef.current = true; trackLabelsViewport.scrollTop = tracksViewport.scrollTop; isUpdatingRef.current = false; }; trackLabelsViewport.addEventListener("scroll", handleTrackLabelsScroll); tracksViewport.addEventListener("scroll", handleTracksVerticalScroll); return () => { rulerViewport.removeEventListener("scroll", handleRulerScroll); tracksViewport.removeEventListener("scroll", handleTracksScroll); trackLabelsViewport.removeEventListener( "scroll", handleTrackLabelsScroll ); tracksViewport.removeEventListener( "scroll", handleTracksVerticalScroll ); }; } return () => { rulerViewport.removeEventListener("scroll", handleRulerScroll); tracksViewport.removeEventListener("scroll", handleTracksScroll); }; }, []); return null; }