All files / src/components/ui/hooks use-restore-scroll.ts

0% Statements 0/31
0% Branches 0/19
0% Functions 0/4
0% Lines 0/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60                                                                                                                       
import { useLayoutEffect, useRef } from "react";
 
const MAX_WAIT = 500;
 
interface UseRestoreScrollOptions {
  behavior?: ScrollBehavior;
}
 
export function useRestoreScroll<T extends HTMLElement>(
  ref: React.RefObject<T>,
  target: number | null | undefined,
  options?: UseRestoreScrollOptions,
) {
  const { behavior = "auto" } = options ?? {};
 
  const hasRestoredRef = useRef(false);
  const targetRef = useRef<number | null>(target ?? 0);
 
  useLayoutEffect(() => {
    const el = ref.current;
    Iif (!el || hasRestoredRef.current) return;
 
    const scrollTarget = targetRef.current ?? 0;
 
    let mounted = true;
    let raf: number;
    const start = performance.now();
 
    const check = () => {
      Iif (!mounted) return;
 
      const maxScrollable = el.scrollHeight - el.clientHeight;
 
      Iif (maxScrollable >= scrollTarget) {
        hasRestoredRef.current = true;
 
        const previousBehavior = el.style.scrollBehavior;
        el.style.scrollBehavior = behavior;
        el.scrollTop = scrollTarget;
        el.style.scrollBehavior = previousBehavior;
 
        return;
      }
 
      Iif (performance.now() - start > MAX_WAIT) {
        return;
      }
 
      raf = requestAnimationFrame(check);
    };
 
    raf = requestAnimationFrame(check);
 
    return () => {
      mounted = false;
      cancelAnimationFrame(raf);
    };
  }, [behavior, ref]);
}