mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-27 19:00:56 +00:00
169 lines
5.1 KiB
JavaScript
169 lines
5.1 KiB
JavaScript
'use client';
|
|
import { useRef, useCallback, useEffect } from 'react';
|
|
import { useReducedMotion } from '../use-reduced-motion/use-reduced-motion.mjs';
|
|
import { useWindowEvent } from '../use-window-event/use-window-event.mjs';
|
|
|
|
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 now = performance.now();
|
|
const elapsed = 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;
|
|
}
|
|
};
|
|
useWindowEvent("wheel", handleStop, {
|
|
passive: true
|
|
});
|
|
useWindowEvent("touchmove", handleStop, {
|
|
passive: true
|
|
});
|
|
useEffect(() => cancel, []);
|
|
return {
|
|
scrollableRef,
|
|
targetRef,
|
|
scrollIntoView,
|
|
cancel
|
|
};
|
|
}
|
|
function easeInOutQuad(t) {
|
|
return t < 0.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 parentElement = parent || document.body;
|
|
const parentPosition = parentElement.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;
|
|
const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;
|
|
return shouldScroll ? distance : 0;
|
|
}
|
|
const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;
|
|
if (alignment === "end") {
|
|
const distance = diff + offset - parentHeight + targetPosition.height;
|
|
const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;
|
|
return shouldScroll ? 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;
|
|
const shouldScroll = distance <= targetPosition.width || !isList;
|
|
return shouldScroll ? distance : 0;
|
|
}
|
|
const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;
|
|
if (alignment === "end") {
|
|
const distance = diff + offset - parentWidth + targetPosition.width;
|
|
const shouldScroll = distance >= -targetPosition.width || !isList;
|
|
return shouldScroll ? 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;
|
|
}
|
|
}
|
|
|
|
export { useScrollIntoView };
|
|
//# sourceMappingURL=use-scroll-into-view.mjs.map
|