mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-18 08:57:24 +02:00
97 lines
2.4 KiB
TypeScript
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;
|
|
}
|