From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- apps/web/src/timeline/hooks/use-scroll-position.ts | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 apps/web/src/timeline/hooks/use-scroll-position.ts (limited to 'apps/web/src/timeline/hooks/use-scroll-position.ts') diff --git a/apps/web/src/timeline/hooks/use-scroll-position.ts b/apps/web/src/timeline/hooks/use-scroll-position.ts new file mode 100644 index 00000000..ad2c6374 --- /dev/null +++ b/apps/web/src/timeline/hooks/use-scroll-position.ts @@ -0,0 +1,52 @@ +import { useEffect, useState, useRef } from "react"; + +interface UseScrollPositionReturn { + scrollLeft: number; + viewportWidth: number; +} + +export function useScrollPosition({ + scrollRef, +}: { + scrollRef: React.RefObject; +}): UseScrollPositionReturn { + const [scrollLeft, setScrollLeft] = useState(0); + const [viewportWidth, setViewportWidth] = useState(0); + const rafIdRef = useRef(null); + + useEffect(() => { + const scrollElement = scrollRef.current; + if (!scrollElement) return; + + const updatePosition = () => { + if (rafIdRef.current !== null) { + cancelAnimationFrame(rafIdRef.current); + } + + rafIdRef.current = requestAnimationFrame(() => { + setScrollLeft(scrollElement.scrollLeft); + setViewportWidth(scrollElement.clientWidth); + rafIdRef.current = null; + }); + }; + + const resizeObserver = new ResizeObserver(() => { + updatePosition(); + }); + + updatePosition(); + + scrollElement.addEventListener("scroll", updatePosition, { passive: true }); + resizeObserver.observe(scrollElement); + + return () => { + scrollElement.removeEventListener("scroll", updatePosition); + resizeObserver.disconnect(); + if (rafIdRef.current !== null) { + cancelAnimationFrame(rafIdRef.current); + } + }; + }, [scrollRef]); + + return { scrollLeft, viewportWidth }; +} -- cgit v1.3.1