import { debounce } from 'lodash' import { RefObject, useMemo, useLayoutEffect, useRef, useState } from 'react' type elem_size = | { width: number; height: number } | { width: undefined; height: undefined } const getSize = (elem: HTMLElement | null) => elem ? { width: elem.offsetWidth, height: elem.offsetHeight } : { width: undefined, height: undefined } export function useListenElemSize( elemRef: RefObject, callback: (size: elem_size) => void, debounceMs: number | undefined = undefined ) { const handleResize = useMemo(() => { const updateSize = () => { if (elemRef.current) callback(getSize(elemRef.current)) } return debounceMs ? debounce(updateSize, debounceMs, { leading: false, trailing: true }) : updateSize }, [callback, elemRef, debounceMs]) const elem = elemRef.current useLayoutEffect(() => { if (!elemRef.current) return const resizeObserver = new ResizeObserver(handleResize) resizeObserver.observe(elemRef.current) return () => resizeObserver.disconnect() }, [elemRef, elem, handleResize]) } export function useMeasureSize(debounceMs: number | undefined = undefined) { const elemRef = useRef(null) const [size, setSize] = useState(() => getSize(null)) const sizeRef = useRef(size) const setSizeIfDifferent = (newSize: typeof size) => { if (newSize?.height !== size?.height || newSize?.width !== size?.width) { sizeRef.current = newSize setSize(newSize) } } useListenElemSize(elemRef, setSizeIfDifferent, debounceMs) const setElem = (elem: HTMLElement | null) => { elemRef.current = elem if (elem) { setSizeIfDifferent(getSize(elem)) } } return { setElem, elemRef, sizeRef, ...size } }