@intility/bifrost-react
Version:
React library for Intility's design system, Bifrost.
87 lines (86 loc) • 3.41 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */ "use client";
import { useEffect, useRef } from "react";
import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect.js";
// stolen from https://github.com/jaredLunde/react-hook/tree/master/packages/resize-observer
// reworked slightly since it caused crashes when building static site for next.js
// for some reason they think polyfilling should be the responsibility of the consumer for SSR/RSC
// but we want bifrost to be ssr friendly out of the box https://github.com/jaredLunde/react-hook/issues/310
const useLatest = (current)=>{
const storedValue = useRef(current);
useEffect(()=>{
storedValue.current = current;
});
return storedValue;
};
/**
* A React hook that fires a callback whenever ResizeObserver detects a change to its size
*
* @param target A React ref created by `useRef()` or an HTML element
* @param callback Invoked with a single `ResizeObserverEntry` any time
* the `target` resizes
*/ function useResizeObserver(target, callback, options = {}) {
const storedCallback = useLatest(callback);
useIsomorphicLayoutEffect(()=>{
const resizeObserver = getResizeObserver(options.polyfill);
let didUnsubscribe = false;
const targetEl = target && "current" in target ? target.current : target;
if (!targetEl) return;
function cb(entry, observer) {
if (didUnsubscribe) return;
storedCallback.current(entry, observer);
}
resizeObserver.subscribe(targetEl, cb);
return ()=>{
didUnsubscribe = true;
resizeObserver.unsubscribe(targetEl, cb);
};
}, [
target,
storedCallback
]);
}
function createResizeObserver(polyfill) {
let ticking = false;
let allEntries = [];
const callbacks = new Map();
const observer = new (polyfill || window.ResizeObserver)((entries, obs)=>{
allEntries = allEntries.concat(entries);
if (!ticking) {
window.requestAnimationFrame(()=>{
const triggered = new Set();
for(let i = 0; i < allEntries.length; i++){
if (triggered.has(allEntries[i].target)) continue;
triggered.add(allEntries[i].target);
const cbs = callbacks.get(allEntries[i].target);
cbs?.forEach((cb)=>cb(allEntries[i], obs));
}
allEntries = [];
ticking = false;
});
}
ticking = true;
});
return {
observer,
subscribe (target, callback) {
observer.observe(target);
const cbs = callbacks.get(target) ?? [];
cbs.push(callback);
callbacks.set(target, cbs);
},
unsubscribe (target, callback) {
const cbs = callbacks.get(target) ?? [];
if (cbs.length === 1) {
observer.unobserve(target);
callbacks.delete(target);
return;
}
const cbIndex = cbs.indexOf(callback);
if (cbIndex !== -1) cbs.splice(cbIndex, 1);
callbacks.set(target, cbs);
}
};
}
let _resizeObserver;
const getResizeObserver = (polyfill)=>!_resizeObserver ? _resizeObserver = createResizeObserver(polyfill) : _resizeObserver;
export default useResizeObserver;