(null);
+ const collapsedText = children.replace(/\s+/g, " ").trim();
const measureRef = useCallback(
(body: HTMLDivElement | null) => {
observerRef.current?.disconnect();
observerRef.current = null;
if (!body || expanded) return;
- const measure = () => setTruncated(body.scrollHeight > body.clientHeight);
+ const measure = () => {
+ const text = measureTextRef.current;
+ const more = measureMoreRef.current;
+ if (!text || !more) return;
+
+ more.hidden = true;
+ text.textContent = collapsedText;
+ if (body.scrollHeight <= body.clientHeight) {
+ setTruncatedText(null);
+ return;
+ }
+
+ more.hidden = false;
+ let low = 0;
+ let high = collapsedText.length;
+ while (low < high) {
+ const middle = Math.ceil((low + high) / 2);
+ text.textContent = `${collapsedText.slice(0, middle).trimEnd()}... `;
+ if (body.scrollHeight <= body.clientHeight) {
+ low = middle;
+ } else {
+ high = middle - 1;
+ }
+ }
+ setTruncatedText(collapsedText.slice(0, low).trimEnd());
+ };
measure();
const observer = new ResizeObserver(measure);
- observer.observe(body);
+ observer.observe(body.parentElement ?? body);
observerRef.current = observer;
},
- [expanded],
+ [collapsedText, expanded],
);
return (
-
-
+
+ {expanded || truncatedText === null ? children : truncatedText}
+ {truncatedText !== null && !expanded && "... "}
+ {truncatedText !== null && (
+
)}
- >
- {children}
- {(truncated || expanded) && (
-
+ {collapsedText}
+
+ more
+
+
)}
);