import { useRef, RefCallback, useCallback } from "react"; import { useForceUpdate, useIsMounted } from "hooks"; export type ElementRect = Omit; const initialElementRect: ElementRect = { width: 0, height: 0, y: 0, x: 0, top: 0, bottom: 0, left: 0, right: 0, }; type SubscriberResponse = VoidFunction | void; function useResolvedElement(subscriber: (element: T) => SubscriberResponse): RefCallback { const lastRef = useRef(null); const cleanupRef = useRef(); const refCallback = useCallback( (element: T) => { if (lastRef.current === element) return; if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = null; } lastRef.current = element; if (element) { cleanupRef.current = subscriber(element); } }, [subscriber], ); return refCallback; } type ResizeHandler = (elementRect: ElementRect, element: T) => void; type HookResponse = { ref: RefCallback; } & ElementRect; /** * * @kind 07-Misc */ export const useResize = (options?: { rerender: boolean; onResize?: ResizeHandler; }): HookResponse => { const onResizeRef = useRef | undefined>(undefined); onResizeRef.current = options?.onResize; const forceUpdate = useForceUpdate(); const resizeObserverRef = useRef(); const rectRef = useRef(initialElementRect); const isMounted = useIsMounted(); const onResize = useCallback((rect: DOMRect, element: T) => { const newRect = { width: rect.width, height: rect.height, y: rect.y, x: rect.x, top: rect.top, bottom: rect.bottom, left: rect.left, right: rect.right, }; if (JSON.stringify(rectRef.current) !== JSON.stringify(newRect)) { rectRef.current = newRect; if (options?.rerender) forceUpdate(); if (onResizeRef.current) onResizeRef.current(newRect, element); } }, []); const ref = useResolvedElement((element) => { const currentSize = element.getBoundingClientRect(); onResize(currentSize, element); if (!resizeObserverRef.current) { resizeObserverRef.current = new ResizeObserver((entries: ResizeObserverEntry[]) => { if (!Array.isArray(entries) || !entries.length || isMounted.current) return; const newSize = element.getBoundingClientRect(); onResize(newSize, element); }); } resizeObserverRef.current.observe(element); return () => { resizeObserverRef.current?.unobserve(element); }; }); return { ...rectRef.current, ref, }; };