UNPKG

@mantine/hooks

Version:

A collection of 50+ hooks for state and UI management

138 lines (137 loc) 4.75 kB
"use client"; import { useWindowEvent } from "../use-window-event/use-window-event.mjs"; import { useReducedMotion } from "../use-reduced-motion/use-reduced-motion.mjs"; import { useCallback, useEffect, useRef } from "react"; //#region packages/@mantine/hooks/src/use-scroll-into-view/use-scroll-into-view.ts function useScrollIntoView({ duration = 1250, axis = "y", onScrollFinish, easing = easeInOutQuad, offset = 0, cancelable = true, isList = false } = {}) { const frameID = useRef(0); const startTime = useRef(0); const shouldStop = useRef(false); const scrollableRef = useRef(null); const targetRef = useRef(null); const reducedMotion = useReducedMotion(); const cancel = () => { if (frameID.current) cancelAnimationFrame(frameID.current); }; const scrollIntoView = useCallback(({ alignment = "start" } = {}) => { shouldStop.current = false; if (frameID.current) cancel(); const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0; const change = getRelativePosition({ parent: scrollableRef.current, target: targetRef.current, axis, alignment, offset, isList }) - (scrollableRef.current ? 0 : start); function animateScroll() { if (startTime.current === 0) startTime.current = performance.now(); const elapsed = performance.now() - startTime.current; const t = reducedMotion || duration === 0 ? 1 : elapsed / duration; const distance = start + change * easing(t); setScrollParam({ parent: scrollableRef.current, axis, distance }); if (!shouldStop.current && t < 1) frameID.current = requestAnimationFrame(animateScroll); else { typeof onScrollFinish === "function" && onScrollFinish(); startTime.current = 0; frameID.current = 0; cancel(); } } animateScroll(); }, [ axis, duration, easing, isList, offset, onScrollFinish, reducedMotion ]); const handleStop = () => { if (cancelable) shouldStop.current = true; }; /** * Detection of one of these events stops scroll animation * wheel - mouse wheel / touch pad * touchmove - any touchable device */ useWindowEvent("wheel", handleStop, { passive: true }); useWindowEvent("touchmove", handleStop, { passive: true }); useEffect(() => cancel, []); return { scrollableRef, targetRef, scrollIntoView, cancel }; } function easeInOutQuad(t) { return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; } function getRelativePosition({ axis, target, parent, alignment, offset, isList }) { if (!target || !parent && typeof document === "undefined") return 0; const isCustomParent = !!parent; const parentPosition = (parent || document.body).getBoundingClientRect(); const targetPosition = target.getBoundingClientRect(); const getDiff = (property) => targetPosition[property] - parentPosition[property]; if (axis === "y") { const diff = getDiff("top"); if (diff === 0) return 0; if (alignment === "start") { const distance = diff - offset; return distance <= targetPosition.height * (isList ? 0 : 1) || !isList ? distance : 0; } const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight; if (alignment === "end") { const distance = diff + offset - parentHeight + targetPosition.height; return distance >= -targetPosition.height * (isList ? 0 : 1) || !isList ? distance : 0; } if (alignment === "center") return diff - parentHeight / 2 + targetPosition.height / 2; return 0; } if (axis === "x") { const diff = getDiff("left"); if (diff === 0) return 0; if (alignment === "start") { const distance = diff - offset; return distance <= targetPosition.width || !isList ? distance : 0; } const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth; if (alignment === "end") { const distance = diff + offset - parentWidth + targetPosition.width; return distance >= -targetPosition.width || !isList ? distance : 0; } if (alignment === "center") return diff - parentWidth / 2 + targetPosition.width / 2; return 0; } return 0; } function getScrollStart({ axis, parent }) { if (!parent && typeof document === "undefined") return 0; const method = axis === "y" ? "scrollTop" : "scrollLeft"; if (parent) return parent[method]; const { body, documentElement } = document; return body[method] + documentElement[method]; } function setScrollParam({ axis, parent, distance }) { if (!parent && typeof document === "undefined") return; const method = axis === "y" ? "scrollTop" : "scrollLeft"; if (parent) parent[method] = distance; else { const { body, documentElement } = document; body[method] = distance; documentElement[method] = distance; } } //#endregion export { useScrollIntoView }; //# sourceMappingURL=use-scroll-into-view.mjs.map