diff --git a/packages/tui/src/components/v-stack.ts b/packages/tui/src/components/v-stack.ts index 8e13b07c1..ce7b9292d 100644 --- a/packages/tui/src/components/v-stack.ts +++ b/packages/tui/src/components/v-stack.ts @@ -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; } diff --git a/packages/tui/test/layout.test.ts b/packages/tui/test/layout.test.ts index 147247338..856954271 100644 --- a/packages/tui/test/layout.test.ts +++ b/packages/tui/test/layout.test.ts @@ -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)],