@redocly/theme
Version:
Shared UI components lib
99 lines (83 loc) • 2.73 kB
text/typescript
import { useState, useCallback, RefObject, useLayoutEffect, useRef } from 'react';
type Size = {
width: number;
height: number;
};
const getInitialSize = (ref?: RefObject<HTMLElement | null>): Size => {
if (ref?.current) {
return {
width: ref.current.offsetWidth,
height: ref.current.offsetHeight,
};
}
if (ref?.current === null) {
return {
width: 0,
height: 0,
};
}
return {
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
};
};
export function useElementSize<T extends HTMLElement = HTMLElement>({
delay = 50,
detectSizes = 'both',
}: { delay?: number; detectSizes?: 'width' | 'height' | 'both' } = {}): [
Size,
RefObject<T | null>,
] {
const ref = useRef<T | null>(null);
const previousSize = useRef<Size>(getInitialSize(ref));
const [size, setSize] = useState<Size>(getInitialSize(ref));
const isFirstUpdate = useRef(true);
const updateSize = useCallback(
(newSize: Size) => {
const shouldUpdateWidth = detectSizes === 'both' || detectSizes === 'width';
const shouldUpdateHeight = detectSizes === 'both' || detectSizes === 'height';
const widthChanged = shouldUpdateWidth && newSize.width !== previousSize.current.width;
const heightChanged = shouldUpdateHeight && newSize.height !== previousSize.current.height;
if (widthChanged || heightChanged) {
const updatedSize = {
width: shouldUpdateWidth ? newSize.width : previousSize.current.width,
height: shouldUpdateHeight ? newSize.height : previousSize.current.height,
};
setSize(updatedSize);
previousSize.current = updatedSize;
}
},
[detectSizes],
);
useLayoutEffect(() => {
let timeoutId: number | null = null;
const triggerUpdateWithThrottling = (newSize: Size) => {
if (isFirstUpdate.current) {
updateSize(newSize);
isFirstUpdate.current = false;
return;
}
if (timeoutId !== null) return;
timeoutId = window.setTimeout(() => {
updateSize(newSize);
timeoutId = null;
}, delay);
};
if (ref?.current && typeof ResizeObserver !== 'undefined') {
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
triggerUpdateWithThrottling({ width, height });
}
});
observer.observe(ref.current);
return () => {
observer.disconnect();
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
};
}
}, [ref, updateSize, delay]);
return [size, ref];
}