UNPKG

react-fitter

Version:

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

1 lines 7.17 kB
{"version":3,"sources":["../lib/index.ts","../lib/Fitter.tsx","../lib/hooks.ts"],"sourcesContent":["export * from \"./Fitter.js\";\n","import { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { useDebounce, useResizeObserver } from \"./hooks.js\";\n\nexport type FitterProps = {\n\t/**\n\t * The content to fit.\n\t */\n\tchildren: ReactNode;\n\t/**\n\t * The minimum scale that the text will shrink to. E.g. 0.25 means the text\n\t * will shrink to no less than 25% of its original size.\n\t */\n\tminSize?: number;\n\t/**\n\t * The maximum number of lines that the text will shrink to fit.\n\t */\n\tmaxLines?: number;\n\t/**\n\t * The precision at which the text will settle. The component finds the best\n\t * size by halving the difference between the current size and the min/max\n\t * size. If the difference is less than the settle precision, the component\n\t * will stop and settle on that size. A value of 0.01 means the component will\n\t * settle when the difference is less than 1%.\n\t */\n\tsettlePrecision?: number;\n\t/**\n\t * Whether to update the text size when the size of the component changes.\n\t */\n\tupdateOnSizeChange?: boolean;\n\t/**\n\t * The time in milliseconds to wait before updating the text size when the\n\t * size of the component changes. This is useful when the component is being\n\t * resized frequently and you want to avoid updating the text size on every\n\t * resize event.\n\t */\n\tresizeDebounceMs?: number;\n};\n\n/**\n * A utility component that fits text to a container by finding the best text\n * size through binary search. The text will never grow larger than the original\n * size and will shrink to fit the container.\n */\nexport const Fitter = ({\n\tchildren,\n\tminSize = 0.25,\n\tmaxLines = 1,\n\tsettlePrecision = 0.01,\n\tupdateOnSizeChange = true,\n\tresizeDebounceMs = 50,\n}: FitterProps) => {\n\tconst wrapperRef = useRef<HTMLDivElement>(null);\n\tconst textRef = useRef<HTMLSpanElement>(null);\n\n\tconst [size, setSize] = useState(1);\n\tconst [settled, setSettled] = useState(false);\n\tconst [targetMax, setTargetMax] = useState(1);\n\n\tuseEffect(() => {\n\t\tif (settled) return;\n\t\tif (!textRef.current) throw new Error(\"Could not access wrapper ref\");\n\n\t\tconst lines = textRef.current.getClientRects().length;\n\n\t\tconst nextShrinkStep = (size - minSize) / 2;\n\t\tconst nextGrowStep = (targetMax - size) / 2;\n\n\t\tconst nextShrinkSize = size - nextShrinkStep;\n\t\tconst nextGrowSize = size + nextGrowStep;\n\n\t\tconst wantsToShrink = lines > maxLines;\n\n\t\tif (wantsToShrink) {\n\t\t\tif (nextShrinkStep < settlePrecision) {\n\t\t\t\tsetSettled(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetTargetMax(size);\n\t\t\tsetSize(nextShrinkSize);\n\t\t\treturn;\n\t\t}\n\n\t\tif (nextGrowStep < settlePrecision) {\n\t\t\tsetSettled(true);\n\t\t\treturn;\n\t\t}\n\n\t\tsetSize(nextGrowSize);\n\t}, [size, maxLines, minSize, targetMax, settlePrecision, settled]);\n\n\tconst start = useDebounce(() => {\n\t\tsetTargetMax(1);\n\t\tsetSettled(false);\n\t}, resizeDebounceMs);\n\n\tuseResizeObserver(wrapperRef, () => start(), updateOnSizeChange);\n\n\treturn (\n\t\t<div\n\t\t\tref={wrapperRef}\n\t\t\tclassName=\"react-fitter__wrapper\"\n\t\t\tstyle={{\n\t\t\t\tlineHeight: 0,\n\t\t\t}}\n\t\t>\n\t\t\t<span\n\t\t\t\tref={textRef}\n\t\t\t\tstyle={{\n\t\t\t\t\tfontSize: `${size * 100}%`,\n\t\t\t\t\tlineHeight: 1,\n\t\t\t\t}}\n\t\t\t\tclassName=\"react-fitter__text\"\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</span>\n\t\t</div>\n\t);\n};\n","import {\n\ttype RefObject,\n\tuseEffect,\n\tuseLayoutEffect,\n\tuseMemo,\n\tuseRef,\n} from \"react\";\n\nconst debounce = (callback: () => void, delay: number) => {\n\tlet timeout: NodeJS.Timeout;\n\treturn () => {\n\t\tclearTimeout(timeout);\n\t\ttimeout = setTimeout(callback, delay);\n\t};\n};\n\nexport const useDebounce = <T extends unknown[], U>(\n\tcallback: (...args: T) => U,\n\tdelay: number,\n) => {\n\tconst callbackRef = useRef(callback);\n\tuseLayoutEffect(() => {\n\t\tcallbackRef.current = callback;\n\t});\n\treturn useMemo(\n\t\t() => debounce((...args: T) => callbackRef.current(...args), delay),\n\t\t[delay],\n\t);\n};\n\nexport const useResizeObserver = (\n\tref: RefObject<HTMLSpanElement>,\n\tcallback: () => void,\n\tenabled: boolean,\n) => {\n\tconst width = useRef(0);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: Using a ref as a dependency doesn't work as\texpected.\n\tuseEffect(() => {\n\t\tif (!ref.current) return;\n\t\tif (!enabled) return;\n\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tfor (const entry of entries) {\n\t\t\t\t// Some older browsers don't return contentBoxSize\n\t\t\t\tconst newWidth = entry.contentBoxSize\n\t\t\t\t\t? entry.contentBoxSize[0].inlineSize\n\t\t\t\t\t: entry.contentRect.width;\n\n\t\t\t\tif (newWidth !== width.current) {\n\t\t\t\t\twidth.current = newWidth;\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tobserver.observe(ref.current);\n\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [enabled]);\n};\n"],"mappings":"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","names":["import_react"]}