UNPKG

@react-hookz/web

Version:

React hooks done right, for browser and SSR.

90 lines (89 loc) 3.74 kB
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]); }