@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
103 lines (102 loc) • 4.21 kB
JavaScript
import { findFirstVisibleElement, getScrollTop, isScrolledToTheBottom, scrollElement, verticalScrollOffset } from "../utils/scroll.js";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
//#region src/hooks/useScrollTo.ts
var useScrollTo = (selectedIndexProp = 0, scrollElementId = void 0, navigationMode = "push", relativeLinks = false, offset = 0, options = [], onChange = void 0, direction = "column") => {
const RETRY_MAX = 5;
const [selectedIndex, setSelectedIndex] = useState(selectedIndexProp);
const scrollEle = useRef(typeof window !== "undefined" ? window : null);
const requestedAnimationFrame = useRef(0);
const lastContainerScrollTop = useRef(0);
const selectedIndexRef = useRef(selectedIndex);
useEffect(() => {
selectedIndexRef.current = selectedIndex;
}, [selectedIndex]);
useEffect(() => {
if (typeof window !== "undefined") {
scrollEle.current = scrollElementId && document.getElementById(scrollElementId) || window;
lastContainerScrollTop.current = verticalScrollOffset(scrollEle.current);
}
}, [scrollElementId]);
const checkScroll = useCallback((event) => {
if (requestedAnimationFrame.current === 0 && window?.requestAnimationFrame) requestedAnimationFrame.current = window.requestAnimationFrame(() => {
requestedAnimationFrame.current = 0;
const firstVisibleElementIndex = findFirstVisibleElement(scrollEle.current, options, offset);
let newSelectedIndex = firstVisibleElementIndex;
if (firstVisibleElementIndex < 0) newSelectedIndex = 0;
if (newSelectedIndex < options.length - 1 && isScrolledToTheBottom(scrollEle.current)) newSelectedIndex += 1;
const containerScrollTop = getScrollTop(scrollEle.current);
const isScrollingDown = containerScrollTop > lastContainerScrollTop.current;
lastContainerScrollTop.current = containerScrollTop;
if (isScrollingDown) {
if (newSelectedIndex < selectedIndexRef.current) newSelectedIndex = selectedIndexRef.current;
} else if (newSelectedIndex > selectedIndexRef.current) newSelectedIndex = selectedIndexRef.current;
setSelectedIndex(newSelectedIndex);
onChange?.(event, newSelectedIndex);
});
}, [
offset,
options,
onChange
]);
useEffect(() => {
if (scrollEle.current) scrollEle.current.addEventListener("scroll", checkScroll, false);
return () => {
if (scrollEle.current) scrollEle.current.removeEventListener("scroll", checkScroll);
if (requestedAnimationFrame.current !== 0) {
window.cancelAnimationFrame(requestedAnimationFrame.current);
requestedAnimationFrame.current = 0;
}
};
}, [checkScroll]);
useEffect(() => {
let checkRenderedInterval;
if (navigationMode !== "none") {
const hashValue = document.location.hash.split("#")[1] || "";
const option = options.find((o) => o.value === hashValue);
if (option) {
let retry = 0;
checkRenderedInterval = setInterval(() => {
const ele = document.getElementById(option.value);
if (ele) {
scrollElement(ele, scrollEle.current, option.offset || offset);
clearInterval(checkRenderedInterval);
} else {
retry += 1;
if (retry === RETRY_MAX) clearInterval(checkRenderedInterval);
}
}, 1e3);
}
}
return () => {
clearInterval(checkRenderedInterval);
};
}, []);
const baseUrl = relativeLinks || typeof window === "undefined" || window == null ? "" : window.location.href.split("#")[0];
const elements = useMemo(() => options.map((o) => ({
...o,
href: `${baseUrl}#${o.value}`
})), [options, baseUrl]);
return [
selectedIndex,
useCallback((event, id, index, wrappedOnChange) => {
const option = elements.find((o) => o.value === id);
if (option) {
const ele = document.getElementById(id);
if (ele) scrollElement(ele, scrollEle.current, option.offset || offset, direction);
if (navigationMode === "push") window.history.pushState({}, "", option.href);
else if (navigationMode === "replace") window.history.replaceState({}, "", option.href);
setSelectedIndex(index);
wrappedOnChange?.(index);
selectedIndexRef.current = index;
}
}, [
elements,
navigationMode,
direction,
offset
]),
elements
];
};
//#endregion
export { useScrollTo };