UNPKG

react-text-row-count

Version:

React helper that adds a data-row-count attribute based on measured text rows.

1 lines 9.12 kB
{"version":3,"sources":["../src/react/RowCount.tsx"],"names":["useRef","useCallback","useLayoutEffect","useEffect","_a","React"],"mappings":"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","file":"index.cjs","sourcesContent":["import React, { useCallback, useEffect, useLayoutEffect, useRef } from \"react\";\n\n// Inline the calculateRowCount function since it's no longer exported\nfunction calculateRowCount(element: HTMLElement): number {\n\tconst computedStyle = window.getComputedStyle(element);\n\tconst lineHeight = computedStyle.lineHeight;\n\tconst height = element.scrollHeight;\n\tconst paddingTop = parseFloat(computedStyle.paddingTop);\n\tconst paddingBottom = parseFloat(computedStyle.paddingBottom);\n\tconst borderTop = parseFloat(computedStyle.borderTopWidth);\n\tconst borderBottom = parseFloat(computedStyle.borderBottomWidth);\n\t\n\t// Calculate effective height (content height minus padding and borders)\n\tconst effectiveHeight = height - paddingTop - paddingBottom - borderTop - borderBottom;\n\t\n\t// Parse line height\n\tlet lineHeightValue: number;\n\tif (lineHeight === 'normal') {\n\t\t// Fallback for normal line-height\n\t\tconst fontSize = parseFloat(computedStyle.fontSize);\n\t\tlineHeightValue = fontSize * 1.2;\n\t} else {\n\t\tlineHeightValue = parseFloat(lineHeight);\n\t}\n\t\n\t// Calculate row count\n\treturn Math.max(1, Math.round(effectiveHeight / lineHeightValue));\n}\n\nexport type RowCountProps = {\n\tchildren: React.ReactElement;\n\tonRowCountChanged?: (rowCount: number) => void; // preferred callback when value changes\n};\n\nfunction setDataAttribute(node: HTMLElement, value: number) {\n\tnode.setAttribute(\"data-row-count\", String(value));\n}\n\nfunction dispatchRowCountEvent(node: HTMLElement, rowCount: number) {\n\ttry {\n\t\tnode.dispatchEvent(\n\t\t\tnew CustomEvent(\"rowcountchanged\", { detail: { rowCount } })\n\t\t);\n\t} catch {\n\t\t// ignore if CustomEvent unsupported\n\t}\n}\n\nexport function RowCount({ children, onRowCountChanged }: RowCountProps) {\n\tconst innerRef = useRef<HTMLElement | null>(null);\n\tconst rowCountRef = useRef<number>(0);\n\tconst rafRef = useRef<number | null>(null);\n\tconst mutationObserverRef = useRef<MutationObserver | null>(null);\n\tconst resizeObserverRef = useRef<ResizeObserver | null>(null);\n\n\tconst rafeRefCleanup = () => {\n\t\tif (rafRef.current != null) {\n\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\trafRef.current = null;\n\t\t}\n\t};\n\n\tconst recalc = useCallback(() => {\n\t\tif (!innerRef.current) return;\n\t\tconst count = calculateRowCount(innerRef.current);\n\t\tconst changed = rowCountRef.current !== count;\n\n\t\tsetDataAttribute(innerRef.current, count);\n\n\t\tif (changed) {\n\t\t\trowCountRef.current = count;\n\t\t\tonRowCountChanged?.(count);\n\t\t\tdispatchRowCountEvent(innerRef.current, count);\n\t\t}\n\t}, [onRowCountChanged]);\n\n\tconst scheduleRecalc = useCallback(() => {\n\t\tif (rafRef.current != null) cancelAnimationFrame(rafRef.current);\n\t\trafRef.current = requestAnimationFrame(() => {\n\t\t\trafeRefCleanup();\n\t\t\trecalc();\n\t\t});\n\t}, [recalc]);\n\n\tuseLayoutEffect(() => {\n\t\tscheduleRecalc();\n\t\treturn () => {\n\t\t\trafeRefCleanup();\n\t\t};\n\t}, [scheduleRecalc]);\n\n\tuseEffect(() => {\n\t\tconst node = innerRef.current;\n\t\tif (!node) return;\n\n\t\t// Observe size changes\n\t\tif (typeof ResizeObserver !== \"undefined\") {\n\t\t\tconst resizeObserver = new ResizeObserver(() => scheduleRecalc());\n\t\t\tresizeObserver.observe(node);\n\t\t\tresizeObserverRef.current = resizeObserver;\n\t\t}\n\n\t\t// Observe text/content mutations\n\t\tif (typeof MutationObserver !== \"undefined\") {\n\t\t\tconst mutationObserver = new MutationObserver(() => scheduleRecalc());\n\t\t\tmutationObserver.observe(node, {\n\t\t\t\tcharacterData: true,\n\t\t\t\tsubtree: true,\n\t\t\t\tchildList: true\n\t\t\t});\n\t\t\tmutationObserverRef.current = mutationObserver;\n\t\t}\n\n\t\t// Window resize\n\t\tconst onWindowResize = () => scheduleRecalc();\n\t\twindow.addEventListener(\"resize\", onWindowResize);\n\n\t\t// Font loading can alter metrics\n\t\tconst fonts = (document as any).fonts as FontFaceSet | undefined;\n\t\tconst onFontsLoadingDone = () => scheduleRecalc();\n\t\tif (fonts) {\n\t\t\t// When all fonts initially load\n\t\t\tfonts.ready.then(onFontsLoadingDone).catch(() => {});\n\t\t\t// When subsequent fonts load\n\t\t\tfonts.addEventListener?.(\"loadingdone\", onFontsLoadingDone as any);\n\t\t}\n\n\t\tscheduleRecalc();\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"resize\", onWindowResize);\n\t\t\tresizeObserverRef.current?.disconnect();\n\t\t\tmutationObserverRef.current?.disconnect();\n\t\t\tfonts?.removeEventListener?.(\"loadingdone\", onFontsLoadingDone as any);\n\t\t};\n\t}, [scheduleRecalc]);\n\n\t// Clone the child to inject ref only\n\tconst child = React.Children.only(children);\n\tconst attachRef = (node: HTMLElement | null) => {\n\t\tinnerRef.current = node;\n\t};\n\n\tconst cloned = (React.cloneElement as any)(child, {\n\t\tref: attachRef\n\t});\n\n\treturn cloned;\n}\n"]}