in uui-components/src/navigation/ScrollSpy.tsx [17:84]
export function useScrollSpy(props?: IScrollSpyProps): IScrollSpyApi {
const ref: MutableRefObject<HTMLElement> = useRef();
const [observedNodes, setObservedNodes] = useState<HTMLElement[]>([]);
const [currentActive, setCurrentActive] = useState<string>(props.initialActive || (Array.isArray(props.elements) && props.elements.length > 0 && props.elements[0]));
const setRef = useCallback((selectedRef: HTMLElement) => (ref.current = selectedRef), [ref]);
const getElement = useCallback(
(id?: string): HTMLElement => {
return ref.current?.querySelector(`[data-spy=${id}]`);
},
[ref],
);
const scrollToElement = useCallback(
(item?: string) => {
const selected = props.elements && item && props.elements.includes(item) ? props.elements.find((element) => element === item) : null;
const element = selected ? getElement(selected) : null;
if (element) element.scrollIntoView({ block: 'start', behavior: 'smooth' });
else ref.current?.scrollIntoView({ block: 'start', behavior: 'smooth' });
},
[ref],
);
useEffect(() => {
if (!ref || !props.elements || !Array.isArray(props.elements) || props.elements.length === 0) return;
setObservedNodes(props.elements.map(getElement));
}, [ref]);
useEffect(() => {
if (observedNodes.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries.length === 1) {
const currentElementName = (entries[0]?.target as HTMLElement)?.dataset?.spy;
const isCurrentElementIntersecting = entries[0].isIntersecting;
setCurrentActive((prevElementName) => {
if (isCurrentElementIntersecting) return currentElementName;
// if previous 'currentActive' is not the one which exited the screen - don't change 'currentActive'
if (prevElementName !== currentElementName) return prevElementName;
return '';
});
} else {
const intersectingElement = entries.find((entry) => entry.isIntersecting);
setCurrentActive((intersectingElement?.target as HTMLElement)?.dataset?.spy);
}
},
{
...props.options,
root: props?.options?.root || document.querySelector('body'),
},
);
observedNodes.forEach((element) => (element ? observer.observe(element) : null));
return () => observer.disconnect();
}, [observedNodes]);
return {
scrollToElement,
currentActive,
setRef,
};
}