@react-hookz/web
Version:
React hooks done right, for browser and SSR.
112 lines (111 loc) • 4.86 kB
JavaScript
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;
}