import { useEffect, useState } from 'react'; const DEFAULT_THRESHOLD = [0]; const DEFAULT_ROOT_MARGIN = '0px'; const observers = new Map(); const getObserverEntry = (options) => { const root = options.root ?? document; let rootObservers = observers.get(root); if (!rootObservers) { rootObservers = new Map(); observers.set(root, rootObservers); } const opt = JSON.stringify([options.rootMargin, options.threshold]); let entry = rootObservers.get(opt); if (!entry) { const callbacks = new Map(); const observer = new IntersectionObserver((entries) => { for (const e of entries) callbacks.get(e.target)?.forEach((cb) => setTimeout(() => { cb(e); }, 0)); }, options); entry = { observer, observe(target, callback) { let cbs = callbacks.get(target); if (!cbs) { cbs = new Set(); callbacks.set(target, cbs); observer.observe(target); } cbs.add(callback); }, unobserve(target, callback) { const cbs = callbacks.get(target); if (cbs) { cbs.delete(callback); if (cbs.size === 0) { callbacks.delete(target); observer.unobserve(target); if (callbacks.size === 0) { observer.disconnect(); rootObservers.delete(opt); if (rootObservers.size === 0) { observers.delete(root); } } } } }, }; rootObservers.set(opt, entry); } return entry; }; export function useIntersectionObserver(target, { threshold = DEFAULT_THRESHOLD, root: r, rootMargin = DEFAULT_ROOT_MARGIN, } = {}) { const [state, setState] = useState(); useEffect(() => { const tgt = target && 'current' in target ? target.current : target; if (!tgt) return; let subscribed = true; const observerEntry = getObserverEntry({ root: r && 'current' in r ? r.current : r, rootMargin, threshold, }); const handler = (entry) => { if (subscribed) { setState(entry); } }; observerEntry.observe(tgt, handler); return () => { subscribed = false; observerEntry.unobserve(tgt, handler); }; }, [target, r, rootMargin, ...threshold]); return state; }