import type { Component } from "../tui.js"; import { applyBackgroundToLine, visibleWidth } from "../utils.js"; /** * Box component - a container that applies padding and background to all children */ export class Box implements Component { children: Component[] = []; private paddingX: number; private paddingY: number; private bgFn?: (text: string) => string; // Cache for rendered output private cachedWidth?: number; private cachedChildLines?: string; private cachedBgSample?: string; private cachedLines?: string[]; constructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) { this.paddingX = paddingX; this.paddingY = paddingY; this.bgFn = bgFn; } addChild(component: Component): void { this.children.push(component); this.invalidateCache(); } removeChild(component: Component): void { const index = this.children.indexOf(component); if (index !== -1) { this.children.splice(index, 1); this.invalidateCache(); } } clear(): void { this.children = []; this.invalidateCache(); } setBgFn(bgFn?: (text: string) => string): void { this.bgFn = bgFn; // Don't invalidate here - we'll detect bgFn changes by sampling output } private invalidateCache(): void { this.cachedWidth = undefined; this.cachedChildLines = undefined; this.cachedBgSample = undefined; this.cachedLines = undefined; } invalidate(): void { this.invalidateCache(); for (const child of this.children) { child.invalidate?.(); } } render(width: number): string[] { if (this.children.length === 0) { return []; } const contentWidth = Math.max(1, width - this.paddingX * 2); const leftPad = " ".repeat(this.paddingX); // Render all children const childLines: string[] = []; for (const child of this.children) { const lines = child.render(contentWidth); for (const line of lines) { childLines.push(leftPad + line); } } if (childLines.length === 0) { return []; } // Check if bgFn output changed by sampling const bgSample = this.bgFn ? this.bgFn("test") : undefined; // Check cache validity const childLinesKey = childLines.join("\n"); if ( this.cachedLines && this.cachedWidth === width && this.cachedChildLines === childLinesKey && this.cachedBgSample === bgSample ) { return this.cachedLines; } // Apply background and padding const result: string[] = []; // Top padding for (let i = 0; i < this.paddingY; i++) { result.push(this.applyBg("", width)); } // Content for (const line of childLines) { result.push(this.applyBg(line, width)); } // Bottom padding for (let i = 0; i < this.paddingY; i++) { result.push(this.applyBg("", width)); } // Update cache this.cachedWidth = width; this.cachedChildLines = childLinesKey; this.cachedBgSample = bgSample; this.cachedLines = result; return result; } private applyBg(line: string, width: number): string { const visLen = visibleWidth(line); const padNeeded = Math.max(0, width - visLen); const padded = line + " ".repeat(padNeeded); if (this.bgFn) { return applyBackgroundToLine(padded, width, this.bgFn); } return padded; } }