Files
donutbrowser/src/hooks/use-auto-height.tsx
T
2026-03-28 23:31:20 +04:00

97 lines
2.4 KiB
TypeScript

"use client";
import * as React from "react";
interface AutoHeightOptions {
includeParentBox?: boolean;
includeSelfBox?: boolean;
}
export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
deps: React.DependencyList = [],
options: AutoHeightOptions = {
includeParentBox: true,
includeSelfBox: false,
},
) {
const ref = React.useRef<T | null>(null);
const roRef = React.useRef<ResizeObserver | null>(null);
const [height, setHeight] = React.useState(0);
const measure = React.useCallback(() => {
const el = ref.current;
if (!el) return 0;
const base = el.getBoundingClientRect().height;
let extra = 0;
if (options.includeParentBox && el.parentElement) {
const cs = getComputedStyle(el.parentElement);
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
const borderY =
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
}
}
if (options.includeSelfBox) {
const cs = getComputedStyle(el);
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
const borderY =
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
const isBorderBox = cs.boxSizing === "border-box";
if (isBorderBox) {
extra += paddingY + borderY;
}
}
const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;
const total = Math.ceil((base + extra) * dpr) / dpr;
return total;
}, [options.includeParentBox, options.includeSelfBox]);
React.useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
setHeight(measure());
if (roRef.current) {
roRef.current.disconnect();
roRef.current = null;
}
const ro = new ResizeObserver(() => {
const next = measure();
requestAnimationFrame(() => {
setHeight(next);
});
});
ro.observe(el);
if (options.includeParentBox && el.parentElement) {
ro.observe(el.parentElement);
}
roRef.current = ro;
return () => {
ro.disconnect();
roRef.current = null;
};
}, [...deps, measure, options.includeParentBox]);
React.useLayoutEffect(() => {
if (height === 0) {
const next = measure();
if (next !== 0) setHeight(next);
}
}, [height, measure]);
return { ref, height } as const;
}