diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 932896f4..bfc6512a 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -129,7 +129,6 @@ export function Timeline() { useTimelineScrollSync({ rulerScrollRef, tracksScrollRef, - trackLabelsScrollRef, }); // Wheel event handling diff --git a/apps/web/src/components/editor/timeline/index.ts b/apps/web/src/components/editor/timeline/index.ts deleted file mode 100644 index 984293ce..00000000 --- a/apps/web/src/components/editor/timeline/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { Timeline } from "../timeline"; -export { TimelineToolbar } from "./timeline-toolbar"; -export { TimelineContent } from "./timeline-content"; -export { TimelineRuler } from "./timeline-ruler"; -export { TimelineTracksArea } from "./timeline-tracks-area"; -export { TrackIcon } from "./track-icon"; -export { useTimelineDragHandlers } from "./timeline-drag-handlers"; -export { useTimelineActionHandlers } from "./timeline-action-handlers"; -export { useTimelineScrollSync } from "./timeline-scroll-sync"; -export { useTimelineContentClick } from "./timeline-content-click"; -export { useTimelineWheelHandler } from "./timeline-wheel-handler"; diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx index a4d035f6..9cd1429c 100644 --- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx @@ -59,27 +59,39 @@ export function TimelinePlayhead({ const leftPosition = playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; return ( -
- {/* The playhead line spanning full height */} + <> + {/* Playhead line container */}
+ ref={playheadRef} + className="absolute pointer-events-auto z-[95]" + style={{ + left: `${trackLabelsWidth + leftPosition}px`, + top: 0, + bottom: 0, + width: "2px", // Slightly wider for better click target + }} + onMouseDown={handlePlayheadMouseDown} + > + {/* The playhead line spanning full height */} +
+
- {/* Playhead dot indicator at the top (in ruler area) */} + {/* Playhead dot indicator - separate container with highest z-index */}
-
+ className="absolute pointer-events-none z-[100]" + style={{ + left: `${trackLabelsWidth + leftPosition}px`, + top: 0, + bottom: 0, + }} + > +
+
+ ); } diff --git a/apps/web/src/components/editor/timeline/timeline-ruler.tsx b/apps/web/src/components/editor/timeline/timeline-ruler.tsx index 07a4ad5d..a875fe0f 100644 --- a/apps/web/src/components/editor/timeline/timeline-ruler.tsx +++ b/apps/web/src/components/editor/timeline/timeline-ruler.tsx @@ -83,7 +83,7 @@ export function TimelineRuler({ markers.push(
{rulerMarkers} diff --git a/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx index 2318f977..473c6f3e 100644 --- a/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx +++ b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx @@ -3,20 +3,16 @@ 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); + const lastSyncRef = useRef(0); - // --- Scroll synchronization effect --- + // --- Horizontal scroll synchronization between ruler and tracks --- useEffect(() => { const rulerViewport = rulerScrollRef.current?.querySelector( "[data-radix-scroll-area-viewport]" @@ -24,76 +20,38 @@ export function useTimelineScrollSync({ 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 + // Throttled scroll handlers for better performance const handleRulerScroll = () => { const now = Date.now(); - if (isUpdatingRef.current || now - lastRulerSync.current < 16) return; - lastRulerSync.current = now; + if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling + lastSyncRef.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; + if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling + lastSyncRef.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 - ); - }; - } + rulerViewport.addEventListener("scroll", handleRulerScroll, { passive: true }); + tracksViewport.addEventListener("scroll", handleTracksScroll, { passive: true }); return () => { rulerViewport.removeEventListener("scroll", handleRulerScroll); tracksViewport.removeEventListener("scroll", handleTracksScroll); }; - }, []); + }, [rulerScrollRef, tracksScrollRef]); return null; } diff --git a/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx b/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx index 0e11f366..144eb0f3 100644 --- a/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx +++ b/apps/web/src/components/editor/timeline/timeline-tracks-area.tsx @@ -35,7 +35,7 @@ export function TimelineTracksArea({ {/* Left Column (Sticky Track Labels) */}
diff --git a/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx b/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx index 430d65ae..417cbbe8 100644 --- a/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx +++ b/apps/web/src/components/editor/timeline/timeline-wheel-handler.tsx @@ -11,17 +11,14 @@ export function useTimelineWheelHandler({ isInTimeline, handleWheel, }: TimelineWheelHandlerProps) { - // Add wheel event listeners with passive: false to allow preventDefault useEffect(() => { const timelineContainer = timelineRef.current; if (!timelineContainer || !isInTimeline) return; const handleWheelCapture = (e: WheelEvent) => { - // Call the existing handleWheel function handleWheel(e as any); }; - // Add wheel event listener with passive: false to allow preventDefault timelineContainer.addEventListener("wheel", handleWheelCapture, { passive: false }); return () => {