fix(tui): honor nested stack minimum sizes

This commit is contained in:
Mario Zechner 2026-08-04 12:41:01 +02:00
parent 720f0e8eeb
commit f24ab6e14c
2 changed files with 41 additions and 2 deletions

View file

@ -1,4 +1,4 @@
import { Stack, type StackChild, type StackOptions, visibleStackEntries } from "./stack.ts";
import { allocateStackSizes, Stack, type StackChild, type StackOptions, visibleStackEntries } from "./stack.ts";
export class VStack extends Stack {
protected readonly layoutType = "vstack" as const;
@ -10,12 +10,21 @@ export class VStack extends Stack {
override render(width: number): string[] {
const viewport = { width: Math.max(1, width), height: Number.MAX_SAFE_INTEGER };
const entries = visibleStackEntries(this.entries, viewport);
const rendered = entries.map((entry) => entry.component.render(viewport.width));
const sizes = allocateStackSizes(
entries,
rendered.map((lines) => lines.length),
undefined,
this.gap,
);
const lines: string[] = [];
for (let index = 0; index < entries.length; index++) {
if (index > 0) {
for (let gap = 0; gap < this.gap; gap++) lines.push("");
}
lines.push(...entries[index]!.component.render(viewport.width));
const childLines = rendered[index]!.slice(0, sizes[index]);
lines.push(...childLines);
for (let padding = childLines.length; padding < sizes[index]!; padding++) lines.push("");
}
return lines;
}

View file

@ -86,6 +86,36 @@ describe("viewport layout", () => {
assert.deepStrictEqual(visibleLines(frame.lines), ["a1", "b1", "b2", "b3"]);
});
it("includes nested minimum sizes in intrinsic stack measurement", () => {
const dock = new VStack([
new Text("top1\ntop2\ntop3", 0, 0),
{ component: new Text("selector", 0, 0), minSize: 3 },
new Text("below", 0, 0),
{ component: new Text("footer", 0, 0), minSize: 1 },
]);
const frame = renderLayoutFrame(
new VStack([
{ component: new Text("body", 0, 0), basis: 0, grow: 1, minSize: 1 },
{ component: dock, basis: "auto", minSize: 1 },
]),
10,
9,
() => {},
);
assert.deepStrictEqual(visibleLines(frame.lines), [
"body",
"top1",
"top2",
"top3",
"selector",
"",
"",
"below",
"footer",
]);
});
it("omits gaps around invisible entries", () => {
const stack = new VStack(
[new Text("one", 0, 0), { component: new Text("hidden", 0, 0), visible: () => false }, new Text("two", 0, 0)],