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:
Yunfei He
2026-05-26 00:01:32 +08:00
parent 9e0bdfcb97
commit fa60d12f5a
5 changed files with 15 additions and 6 deletions
+1 -1
View File
@@ -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;
+3 -1
View File
@@ -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;
}
+5
View File
@@ -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.
+3 -1
View File
@@ -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;
}