1*adfb0600SCedric van Puttenimport { useEffect, useRef } from 'react';
2*adfb0600SCedric van Putten
3*adfb0600SCedric van Puttenconst UPPER_THRESHOLD = 1 / 4;
4*adfb0600SCedric van Puttenconst LOWER_THRESHOLD = 3 / 4;
5*adfb0600SCedric van Putten
6*adfb0600SCedric van Putten/**
7*adfb0600SCedric van Putten * Automatically scroll to a child element, by selector.
8*adfb0600SCedric van Putten * Whenever the selector changes, it tries to detect if the selector is out of view.
9*adfb0600SCedric van Putten * If it is, it will scroll to the closest upper or lower bound within the ref's scroll area.
10*adfb0600SCedric van Putten */
11*adfb0600SCedric van Puttenexport function useAutoScrollTo<T extends HTMLElement = HTMLDivElement>(selector: string) {
12*adfb0600SCedric van Putten  const ref = useRef<T>(null);
13*adfb0600SCedric van Putten
14*adfb0600SCedric van Putten  useEffect(
15*adfb0600SCedric van Putten    function onMaybeScroll() {
16*adfb0600SCedric van Putten      if (!selector || !ref.current) return;
17*adfb0600SCedric van Putten      const target = ref.current.querySelector<HTMLElement>(selector);
18*adfb0600SCedric van Putten      if (target) {
19*adfb0600SCedric van Putten        const bounds = ref.current.getBoundingClientRect();
20*adfb0600SCedric van Putten        const position = ref.current.scrollTop;
21*adfb0600SCedric van Putten        const targetPosition = target.offsetTop;
22*adfb0600SCedric van Putten        const upperThreshold = bounds.height * UPPER_THRESHOLD;
23*adfb0600SCedric van Putten        const lowerThreshold = bounds.height * LOWER_THRESHOLD;
24*adfb0600SCedric van Putten
25*adfb0600SCedric van Putten        if (targetPosition < position + upperThreshold) {
26*adfb0600SCedric van Putten          ref.current.scrollTo({ top: Math.max(0, targetPosition - upperThreshold) });
27*adfb0600SCedric van Putten        } else if (targetPosition > position + lowerThreshold) {
28*adfb0600SCedric van Putten          ref.current.scrollTo({ top: targetPosition - lowerThreshold });
29*adfb0600SCedric van Putten        }
30*adfb0600SCedric van Putten      }
31*adfb0600SCedric van Putten    },
32*adfb0600SCedric van Putten    [ref.current, selector]
33*adfb0600SCedric van Putten  );
34*adfb0600SCedric van Putten
35*adfb0600SCedric van Putten  return { ref };
36*adfb0600SCedric van Putten}
37