@react-hookz/web
Version:
React hooks done right, for browser and SSR.
90 lines (89 loc) • 3.74 kB
JavaScript
import { useEffect } from 'react';
import { useSyncedRef } from '..';
import { isBrowser } from "../util/const.js";
var observerSingleton;
function getResizeObserver() {
if (!isBrowser)
return undefined;
if (observerSingleton)
return observerSingleton;
var callbacks = new Map();
var observer = new ResizeObserver(function (entries) {
entries.forEach(function (entry) { var _a; return (_a = callbacks.get(entry.target)) === null || _a === void 0 ? void 0 : _a.forEach(function (cb) { return setTimeout(function () { return cb(entry); }, 0); }); });
});
observerSingleton = {
observer: observer,
subscribe: function (target, callback) {
var cbs = callbacks.get(target);
if (!cbs) {
// if target has no observers yet - register it
cbs = new Set();
callbacks.set(target, cbs);
observer.observe(target);
}
// as Set is duplicate-safe - simply add callback on each call
cbs.add(callback);
},
unsubscribe: function (target, callback) {
var cbs = callbacks.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.delete(target);
observer.unobserve(target);
}
}
},
};
return observerSingleton;
}
/**
* Invokes a callback whenever ResizeObserver detects a change to target's size.
*
* @param target React reference or Element to track.
* @param callback Callback that will be invoked on resize.
* @param enabled Whether resize observer is enabled or not.
*/
export function useResizeObserver(target, callback, enabled) {
if (enabled === void 0) { enabled = true; }
var ro = enabled && getResizeObserver();
var cb = useSyncedRef(callback);
var tgt = target && 'current' in target ? target.current : target;
useEffect(function () {
// this secondary target resolve required for case when we receive ref object, which, most
// likely, contains null during render stage, but already populated with element during
// effect stage.
// eslint-disable-next-line @typescript-eslint/no-shadow
var tgt = target && 'current' in target ? target.current : target;
if (!ro || !tgt)
return;
// as unsubscription in internals of our ResizeObserver abstraction can
// happen a bit later than effect cleanup invocation - we need a marker,
// that this handler should not be invoked anymore
var subscribed = true;
var handler = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
// it is reinsurance for the highly asynchronous invocations, almost
// impossible to achieve in tests, thus excluding from LOC
/* istanbul ignore else */
if (subscribed) {
cb.current.apply(cb, args);
}
};
ro.subscribe(tgt, handler);
return function () {
subscribed = false;
ro.unsubscribe(tgt, handler);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tgt, ro]);
}