UNPKG

react-fitter

Version:

<h1 align="center">🫸 𝚛𝚎𝚊𝚌𝚝-𝚏𝚒𝚝𝚝𝚎𝚛 🫷</h1>

119 lines (117 loc) 3.07 kB
// lib/Fitter.tsx import { useEffect as useEffect2, useRef as useRef2, useState } from "react"; // lib/hooks.ts import { useEffect, useLayoutEffect, useMemo, useRef } from "react"; var debounce = (callback, delay) => { let timeout; return () => { clearTimeout(timeout); timeout = setTimeout(callback, delay); }; }; var useDebounce = (callback, delay) => { const callbackRef = useRef(callback); useLayoutEffect(() => { callbackRef.current = callback; }); return useMemo( () => debounce((...args) => callbackRef.current(...args), delay), [delay] ); }; var useResizeObserver = (ref, callback, enabled) => { const width = useRef(0); useEffect(() => { if (!ref.current) return; if (!enabled) return; const observer = new ResizeObserver((entries) => { for (const entry of entries) { const newWidth = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width; if (newWidth !== width.current) { width.current = newWidth; callback(); } } }); observer.observe(ref.current); return () => { observer.disconnect(); }; }, [enabled]); }; // lib/Fitter.tsx import { jsx } from "react/jsx-runtime"; var Fitter = ({ children, minSize = 0.25, maxLines = 1, settlePrecision = 0.01, updateOnSizeChange = true, resizeDebounceMs = 50 }) => { const wrapperRef = useRef2(null); const textRef = useRef2(null); const [size, setSize] = useState(1); const [settled, setSettled] = useState(false); const [targetMax, setTargetMax] = useState(1); useEffect2(() => { if (settled) return; if (!textRef.current) throw new Error("Could not access wrapper ref"); const lines = textRef.current.getClientRects().length; const nextShrinkStep = (size - minSize) / 2; const nextGrowStep = (targetMax - size) / 2; const nextShrinkSize = size - nextShrinkStep; const nextGrowSize = size + nextGrowStep; const wantsToShrink = lines > maxLines; if (wantsToShrink) { if (nextShrinkStep < settlePrecision) { setSettled(true); return; } setTargetMax(size); setSize(nextShrinkSize); return; } if (nextGrowStep < settlePrecision) { setSettled(true); return; } setSize(nextGrowSize); }, [size, maxLines, minSize, targetMax, settlePrecision, settled]); const start = useDebounce(() => { setTargetMax(1); setSettled(false); }, resizeDebounceMs); useResizeObserver(wrapperRef, () => start(), updateOnSizeChange); return /* @__PURE__ */ jsx( "div", { ref: wrapperRef, className: "react-fitter__wrapper", style: { lineHeight: 0 }, children: /* @__PURE__ */ jsx( "span", { ref: textRef, style: { fontSize: `${size * 100}%`, lineHeight: 1 }, className: "react-fitter__text", children } ) } ); }; export { Fitter }; //# sourceMappingURL=index.js.map