fix: handle null/undefined Text children without crashing yoga
Guard flattenLeaves and renderTextWithInlineStyles to skip comment nodes (produced by Vue for null/undefined children) and return empty strings for childless text nodes. Also guard bindTextMeasure to return zero dimensions for empty text. Expand TuiInlineNode to include TuiComment since Vue inserts comments into text containers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@ export interface TuiTransform extends NodeBase {
|
||||
transform: (line: string, lineIndex: number) => string;
|
||||
}
|
||||
|
||||
export type TuiInlineNode = TuiVirtualText | TuiTextLeaf;
|
||||
export type TuiInlineNode = TuiVirtualText | TuiTextLeaf | TuiComment;
|
||||
export type TuiContainer = TuiRoot | TuiBox | TuiStatic | TuiTransform | TuiText | TuiVirtualText;
|
||||
export type TuiNode = TuiContainer | TuiTextLeaf | TuiComment;
|
||||
|
||||
|
||||
@@ -4,13 +4,15 @@ import wrapAnsi from "wrap-ansi";
|
||||
import type { TextProps, TuiText, TuiVirtualText } from "./nodes.ts";
|
||||
|
||||
export function flattenLeaves(node: TuiText | TuiVirtualText): string {
|
||||
if (!node.children || node.children.length === 0) return "";
|
||||
let out = "";
|
||||
for (const child of node.children) {
|
||||
if (child.type === "text-leaf") {
|
||||
out += child.value;
|
||||
} else {
|
||||
} else if (child.type === "virtual-text") {
|
||||
out += flattenLeaves(child);
|
||||
}
|
||||
// Skip comments inserted by Vue for null/undefined renders
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -264,6 +264,11 @@ export function bindTextMeasure(text: TuiText): void {
|
||||
text.yoga.setMeasureFunc((availableWidth) => {
|
||||
const raw = flattenLeaves(text);
|
||||
text.measuredCache = raw;
|
||||
|
||||
// Empty text (no children or all-null children) — return zero dimensions
|
||||
// so yoga doesn't crash trying to measure an empty string.
|
||||
if (raw === "") return { width: 0, height: 0 };
|
||||
|
||||
const natural = measureText(raw, Infinity, text.props.wrap ?? "wrap");
|
||||
|
||||
// Text fits into container, no need to wrap.
|
||||
|
||||
@@ -191,15 +191,17 @@ function placeLine(grid: string[][], x: number, y: number, line: string): void {
|
||||
}
|
||||
|
||||
function renderTextWithInlineStyles(node: TuiText | TuiVirtualText, acc: TextProps = {}): string {
|
||||
if (!node.children || node.children.length === 0) return "";
|
||||
const defined = Object.fromEntries(Object.entries(node.props).filter(([, v]) => v !== undefined));
|
||||
const merged: TextProps = { ...acc, ...defined };
|
||||
let out = "";
|
||||
for (const child of node.children) {
|
||||
if (child.type === "text-leaf") {
|
||||
out += applyChalk(child.value, merged);
|
||||
} else {
|
||||
} else if (child.type === "virtual-text") {
|
||||
out += renderTextWithInlineStyles(child, merged);
|
||||
}
|
||||
// Skip comments inserted by Vue for null/undefined renders
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user