UNPKG

react-resize-detector

Version:
163 lines (162 loc) 7.21 kB
import*as React from'react';import {useRef,useState,useCallback,useEffect}from'react';import debounce from'lodash/debounce';import throttle from'lodash/throttle';/** * Wraps the resize callback with a lodash debounce / throttle based on the refresh mode */ const patchResizeCallback = (resizeCallback, refreshMode, refreshRate, refreshOptions) => { switch (refreshMode) { case 'debounce': return debounce(resizeCallback, refreshRate, refreshOptions); case 'throttle': return throttle(resizeCallback, refreshRate, refreshOptions); default: return resizeCallback; } }; /** * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a * prop or avoid re-executing effects when passed as a dependency */ const useCallbackRef = // eslint-disable-next-line @typescript-eslint/no-explicit-any (callback) => { const callbackRef = React.useRef(callback); React.useEffect(() => { callbackRef.current = callback; }); return React.useMemo(() => ((...args) => { var _a; return (_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, ...args); }), []); }; /** `useRef` hook doesn't handle conditional rendering or dynamic ref changes. * This hook creates a proxy that ensures that `refElement` is updated whenever the ref is changed. */ const useRefProxy = // eslint-disable-next-line @typescript-eslint/no-explicit-any (targetRef) => { // we are going to use this ref to store the last element that was passed to the hook const [refElement, setRefElement] = React.useState((targetRef === null || targetRef === void 0 ? void 0 : targetRef.current) || null); // if targetRef is passed, we need to update the refElement // we have to use setTimeout because ref get assigned after the hook is called // in the future releases we are going to remove targetRef and force users to use ref returned by the hook if (targetRef) { setTimeout(() => { if (targetRef.current !== refElement) { setRefElement(targetRef.current); } }, 0); } // this is a memo that will be called every time the ref is changed // This proxy will properly call setState either when the ref is called as a function or when `.current` is set // we call setState inside to trigger rerender const refProxy = React.useMemo(() => new Proxy(node => { if (node !== refElement) { setRefElement(node); } }, { get(target, prop) { if (prop === 'current') { return refElement; } return target[prop]; }, set(target, prop, value) { if (prop === 'current') { setRefElement(value); } else { target[prop] = value; } return true; } }), [refElement]); return { refProxy, refElement, setRefElement }; }; /** Calculates the dimensions of the element based on the current box model. * @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model */ const getDimensions = (entry, box) => { // Value Border Padding Inner Content // --------------------------------------------------- // 'border-box' Yes Yes Yes // 'content-box' No No Yes // undefined No No? Yes if (box === 'border-box') { return { width: entry.borderBoxSize[0].inlineSize, height: entry.borderBoxSize[0].blockSize }; } if (box === 'content-box') { return { width: entry.contentBoxSize[0].inlineSize, height: entry.contentBoxSize[0].blockSize }; } return { width: entry.contentRect.width, height: entry.contentRect.height }; };// eslint-disable-next-line @typescript-eslint/no-explicit-any function useResizeDetector({ skipOnMount = false, refreshMode, refreshRate = 1000, refreshOptions, handleWidth = true, handleHeight = true, targetRef, observerOptions, onResize } = {}) { // If `skipOnMount` is enabled, skip the first resize event const skipResize = useRef(skipOnMount); // Wrap the `onResize` callback with a ref to avoid re-renders const onResizeRef = useCallbackRef(onResize); const [size, setSize] = useState({ width: undefined, height: undefined }); // Create a proxy ref to handle conditional rendering and dynamic ref changes of the target element const { refProxy, refElement } = useRefProxy(targetRef); const { box } = observerOptions || {}; const resizeCallback = useCallback((entries) => { if (!handleWidth && !handleHeight) return; if (skipResize.current) { skipResize.current = false; return; } // Only update the size if one of the observed dimensions has changed const shouldSetSize = (prevSize, nextSize) => (handleWidth && prevSize.width !== nextSize.width) || (handleHeight && prevSize.height !== nextSize.height); entries.forEach(entry => { const dimensions = getDimensions(entry, box); setSize(prevSize => { if (!shouldSetSize(prevSize, dimensions)) return prevSize; onResizeRef === null || onResizeRef === void 0 ? void 0 : onResizeRef({ width: dimensions.width, height: dimensions.height, entry }); return dimensions; }); }); }, [handleWidth, handleHeight, skipResize, box]); // Throttle/Debounce the resize event if refreshMode is configured const resizeHandler = useCallback(patchResizeCallback(resizeCallback, refreshMode, refreshRate, refreshOptions), [ resizeCallback, refreshMode, refreshRate, refreshOptions ]); // Attach ResizeObserver to the element useEffect(() => { let resizeObserver; if (refElement) { resizeObserver = new window.ResizeObserver(resizeHandler); resizeObserver.observe(refElement, observerOptions); } // If refElement is not available, reset the size else if (size.width || size.height) { onResizeRef === null || onResizeRef === void 0 ? void 0 : onResizeRef({ width: null, height: null, entry: null }); setSize({ width: undefined, height: undefined }); } // Disconnect the ResizeObserver when the component is unmounted return () => { var _a, _b, _c; (_a = resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect) === null || _a === void 0 ? void 0 : _a.call(resizeObserver); (_c = (_b = resizeHandler).cancel) === null || _c === void 0 ? void 0 : _c.call(_b); }; }, [resizeHandler, refElement]); return Object.assign({ ref: refProxy }, size); }export{useResizeDetector};//# sourceMappingURL=index.esm.js.map