mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
* refactor: migrate eslint to v9 * chore: lint * chore: update eslint command * chore: fix lint warnings * chore: separate lint and lint:fix * chore: exclude contentlayer generated code * fix(scripts): add missing await
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
import {useEffect, useRef, useState} from "react";
|
|
|
|
type State = {
|
|
isIntersecting: boolean;
|
|
entry?: IntersectionObserverEntry;
|
|
};
|
|
|
|
type UseIntersectionObserverOptions = {
|
|
root?: Element | Document | null;
|
|
isEnabled?: boolean;
|
|
rootMargin?: string;
|
|
threshold?: number | number[];
|
|
freezeOnceVisible?: boolean;
|
|
onChange?: (isIntersecting: boolean, entry: IntersectionObserverEntry) => void;
|
|
initialIsIntersecting?: boolean;
|
|
};
|
|
|
|
type IntersectionReturn = [
|
|
(node?: Element | null) => void,
|
|
boolean,
|
|
IntersectionObserverEntry | undefined,
|
|
] & {
|
|
ref: (node?: Element | null) => void;
|
|
isIntersecting: boolean;
|
|
entry?: IntersectionObserverEntry;
|
|
};
|
|
|
|
export function useIntersectionObserver({
|
|
threshold = 0,
|
|
root = null,
|
|
rootMargin = "0%",
|
|
isEnabled = true,
|
|
freezeOnceVisible = false,
|
|
initialIsIntersecting = false,
|
|
onChange,
|
|
}: UseIntersectionObserverOptions = {}): IntersectionReturn {
|
|
const [ref, setRef] = useState<Element | null>(null);
|
|
|
|
const [state, setState] = useState<State>(() => ({
|
|
isIntersecting: initialIsIntersecting,
|
|
entry: undefined,
|
|
}));
|
|
|
|
const callbackRef = useRef<UseIntersectionObserverOptions["onChange"]>();
|
|
|
|
callbackRef.current = onChange;
|
|
|
|
const frozen = state.entry?.isIntersecting && freezeOnceVisible;
|
|
|
|
useEffect(() => {
|
|
// Skip if not enabled
|
|
if (!isEnabled) return;
|
|
|
|
// Ensure we have a ref to observe
|
|
if (!ref) return;
|
|
|
|
// Ensure the browser supports the Intersection Observer API
|
|
if (!("IntersectionObserver" in window)) return;
|
|
|
|
// Skip if frozen
|
|
if (frozen) return;
|
|
|
|
let unobserve: (() => void) | undefined;
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries: IntersectionObserverEntry[]): void => {
|
|
const thresholds = Array.isArray(observer.thresholds)
|
|
? observer.thresholds
|
|
: [observer.thresholds];
|
|
|
|
entries.forEach((entry) => {
|
|
const isIntersecting =
|
|
entry.isIntersecting &&
|
|
thresholds.some((threshold) => entry.intersectionRatio >= threshold);
|
|
|
|
setState({isIntersecting, entry});
|
|
|
|
if (callbackRef.current) {
|
|
callbackRef.current(isIntersecting, entry);
|
|
}
|
|
|
|
if (isIntersecting && freezeOnceVisible && unobserve) {
|
|
unobserve();
|
|
unobserve = undefined;
|
|
}
|
|
});
|
|
},
|
|
{threshold, root, rootMargin},
|
|
);
|
|
|
|
observer.observe(ref);
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
};
|
|
}, [ref, isEnabled, JSON.stringify(threshold), root, rootMargin, frozen, freezeOnceVisible]);
|
|
|
|
// ensures that if the observed element changes, the intersection observer is reinitialized
|
|
const prevRef = useRef<Element | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
!ref &&
|
|
state.entry?.target &&
|
|
!freezeOnceVisible &&
|
|
!frozen &&
|
|
prevRef.current !== state.entry.target
|
|
) {
|
|
prevRef.current = state.entry.target;
|
|
setState({isIntersecting: initialIsIntersecting, entry: undefined});
|
|
}
|
|
}, [ref, state.entry, freezeOnceVisible, frozen, initialIsIntersecting]);
|
|
|
|
const result = [setRef, !!state.isIntersecting, state.entry] as IntersectionReturn;
|
|
|
|
// Support object destructuring, by adding the specific values.
|
|
result.ref = result[0];
|
|
result.isIntersecting = result[1];
|
|
result.entry = result[2];
|
|
|
|
return result;
|
|
}
|