UNPKG

@react-hookz/web

Version:

React hooks done right, for browser and SSR.

112 lines (111 loc) 4.86 kB
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import { useEffect } from 'react'; import { useSafeState } from '..'; var DEFAULT_THRESHOLD = [0]; var DEFAULT_ROOT_MARGIN = '0px'; var observers = new Map(); var getObserverEntry = function (options) { var _a; var root = (_a = options.root) !== null && _a !== void 0 ? _a : document; var rootObservers = observers.get(root); if (!rootObservers) { rootObservers = new Map(); observers.set(root, rootObservers); } var opt = JSON.stringify([options.rootMargin, options.threshold]); var entry = rootObservers.get(opt); if (!entry) { var callbacks_1 = new Map(); var observer_1 = new IntersectionObserver(function (entries) { return entries.forEach(function (e) { var _a; return (_a = callbacks_1.get(e.target)) === null || _a === void 0 ? void 0 : _a.forEach(function (cb) { return setTimeout(function () { return cb(e); }, 0); }); }); }, options); entry = { observer: observer_1, observe: function (target, callback) { var cbs = callbacks_1.get(target); if (!cbs) { // if target has no observers yet - register it cbs = new Set(); callbacks_1.set(target, cbs); observer_1.observe(target); } // as Set is duplicate-safe - simply add callback on each call cbs.add(callback); }, unobserve: function (target, callback) { var cbs = callbacks_1.get(target); // else branch should never occur in case of normal execution // because callbacks map is hidden in closure - it is impossible to // simulate situation with non-existent `cbs` Set /* istanbul ignore else */ if (cbs) { // remove current observer cbs.delete(callback); if (!cbs.size) { // if no observers left unregister target completely callbacks_1.delete(target); observer_1.unobserve(target); // if not tracked elements left - disconnect observer if (!callbacks_1.size) { observer_1.disconnect(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion rootObservers.delete(opt); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if (!rootObservers.size) { observers.delete(root); } } } } }, }; rootObservers.set(opt, entry); } return entry; }; /** * Tracks intersection of a target element with an ancestor element or with a * top-level document's viewport. * * @param target React reference or Element to track. * @param options Like `IntersectionObserver` options but `root` can also be * react reference */ export function useIntersectionObserver(target, _a) { var _b = _a === void 0 ? {} : _a, _c = _b.threshold, threshold = _c === void 0 ? DEFAULT_THRESHOLD : _c, r = _b.root, _d = _b.rootMargin, rootMargin = _d === void 0 ? DEFAULT_ROOT_MARGIN : _d; var _e = useSafeState(), state = _e[0], setState = _e[1]; useEffect(function () { var tgt = target && 'current' in target ? target.current : target; if (!tgt) return; var subscribed = true; var observerEntry = getObserverEntry({ root: r && 'current' in r ? r.current : r, rootMargin: rootMargin, threshold: threshold, }); var handler = function (entry) { // it is reinsurance for the highly asynchronous invocations, almost // impossible to achieve in tests, thus excluding from LOC /* istanbul ignore else */ if (subscribed) { setState(entry); } }; observerEntry.observe(tgt, handler); return function () { subscribed = false; observerEntry.unobserve(tgt, handler); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, __spreadArray([target, r, rootMargin], threshold, true)); return state; }