import { useLayoutEffect, useRef } from "react"; import ResizeObserver from "resize-observer-polyfill"; export function useResizeNotifier( element: HTMLElement | undefined, callback: () => void ) { const callBackRef = useRef(callback); useLayoutEffect(() => { callBackRef.current = callback; }, [callback]); useLayoutEffect(() => { if (!element) return; const resizeObserver = new ResizeObserver( withResizeLoopDetection(() => { callBackRef.current!(); }) ); resizeObserver.observe(element); return () => { resizeObserver.disconnect(); }; }, [element]); } function withResizeLoopDetection(callback: () => void) { return (entries: ResizeObserverEntry[], resizeObserver: ResizeObserver) => { const elements = entries.map((entry) => entry.target); const rectsBefore = elements.map((element) => element.getBoundingClientRect() ); callback(); const rectsAfter = elements.map((element) => element.getBoundingClientRect() ); const changedElements = elements.filter( (_, i) => !areRectSizesEqual(rectsBefore[i], rectsAfter[i]) ); changedElements.forEach((element) => unobserveUntilNextFrame(element, resizeObserver) ); }; } function unobserveUntilNextFrame( element: Element, resizeObserver: ResizeObserver ) { resizeObserver.unobserve(element); requestAnimationFrame(() => { resizeObserver.observe(element); }); } function areRectSizesEqual(rect1: DOMRect, rect2: DOMRect) { return rect1.width === rect2.width && rect1.height === rect2.height; }