import type { Component } from "../tui.js"; import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.js"; /** * Text component - displays multi-line text with word wrapping */ export class Text implements Component { private text: string; private paddingX: number; // Left/right padding private paddingY: number; // Top/bottom padding private customBgFn?: (text: string) => string; private cachedText?: string; private cachedWidth?: number; private cachedLines?: string[]; constructor(text: string = "", paddingX: number = 1, paddingY: number = 1, customBgFn?: (text: string) => string) { this.text = text; this.paddingX = paddingX; this.paddingY = paddingY; this.customBgFn = customBgFn; } setText(text: string): void { this.text = text; this.cachedText = undefined; this.cachedWidth = undefined; this.cachedLines = undefined; } setCustomBgFn(customBgFn?: (text: string) => string): void { this.customBgFn = customBgFn; this.cachedText = undefined; this.cachedWidth = undefined; this.cachedLines = undefined; } invalidate(): void { this.cachedText = undefined; this.cachedWidth = undefined; this.cachedLines = undefined; } render(width: number): string[] { if (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) { return this.cachedLines; } if (!this.text || this.text.trim() === "") { const result: string[] = []; this.cachedText = this.text; this.cachedWidth = width; this.cachedLines = result; return result; } const normalizedText = this.text.replace(/\t/g, " "); const contentWidth = Math.max(1, width - this.paddingX * 2); const wrappedLines = wrapTextWithAnsi(normalizedText, contentWidth); const leftMargin = " ".repeat(this.paddingX); const rightMargin = " ".repeat(this.paddingX); const contentLines: string[] = []; for (const line of wrappedLines) { const lineWithMargins = leftMargin + line + rightMargin; if (this.customBgFn) { contentLines.push(applyBackgroundToLine(lineWithMargins, width, this.customBgFn)); } else { const visibleLen = visibleWidth(lineWithMargins); const paddingNeeded = Math.max(0, width - visibleLen); contentLines.push(lineWithMargins + " ".repeat(paddingNeeded)); } } const emptyLine = " ".repeat(width); const emptyLines: string[] = []; for (let i = 0; i < this.paddingY; i++) { const line = this.customBgFn ? applyBackgroundToLine(emptyLine, width, this.customBgFn) : emptyLine; emptyLines.push(line); } const result = [...emptyLines, ...contentLines, ...emptyLines]; this.cachedText = this.text; this.cachedWidth = width; this.cachedLines = result; return result.length > 0 ? result : [""]; } }