UNPKG

@hitachivantara/uikit-react-core

Version:
103 lines (102 loc) 4.21 kB
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 };