UNPKG

asciitorium

Version:

an ASCII ui framework for web + cli

156 lines (155 loc) 5.41 kB
import { LayoutRegistry } from './layouts/LayoutStrategy'; import './layouts'; // Register layout strategies export class Component { constructor(props) { this.showLabel = true; this.fixed = false; this.x = 0; this.y = 0; this.z = 0; this.gap = 0; this.focusable = false; this.hasFocus = false; this.transparentChar = '‽'; // ‽ = transparent character this.unbindFns = []; // Children support this.children = []; // Default dimensions if not provided this.width = props.width ?? 1; this.height = props.height ?? 1; if (this.width < 1) throw new Error('Component width must be > 0'); if (this.height < 1) throw new Error('Component height must be > 0'); this.label = props.label; this.comment = props.comment; this.showLabel = props.showLabel ?? true; this.border = props.border ?? false; this.fill = props.fill ?? ' '; this.align = props.align; this.fixed = props.fixed ?? false; this.x = props.x ?? 0; this.y = props.y ?? 0; this.z = props.z ?? 0; this.gap = props.gap ?? 0; this.buffer = []; // Setup children and layout this.layoutType = props.layout ?? 'horizontal'; // Default to horizontal layout this.layoutOptions = props.layoutOptions; // Store children for later addition (to avoid calling addChild during construction) if (props.children) { const childList = Array.isArray(props.children) ? props.children : [props.children]; for (const child of childList) { child.setParent(this); this.children.push(child); } this.recalculateLayout(); } } setParent(parent) { this.parent = parent; } // Child management methods addChild(child) { child.setParent(this); this.children.push(child); this.recalculateLayout(); } removeChild(child) { const index = this.children.indexOf(child); if (index !== -1) { this.children.splice(index, 1); this.recalculateLayout(); } } getChildren() { return this.children; } getAllDescendants() { const result = []; for (const child of this.children) { result.push(child); const grandChildren = child.getAllDescendants(); result.push(...grandChildren); } return result; } invalidateLayout() { this.layoutStrategy = undefined; } recalculateLayout() { if (this.children.length === 0) return; if (!this.layoutStrategy) { this.layoutStrategy = LayoutRegistry.create(this.layoutType, this.layoutOptions); } this.layoutStrategy.layout(this, this.children); } bind(state, apply) { const listener = (val) => { apply(val); }; state.subscribe(listener); this.unbindFns.push(() => state.unsubscribe(listener)); } destroy() { for (const unbind of this.unbindFns) unbind(); this.unbindFns = []; } handleEvent(event) { return false; } draw() { // Recalculate layout for children this.recalculateLayout(); // Create buffer and fill only if not transparent this.buffer = Array.from({ length: this.height }, () => Array.from({ length: this.width }, () => this.fill === this.transparentChar ? '‽' : this.fill)); const drawChar = (x, y, char) => { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.buffer[y][x] = char; } }; if (this.border) { const w = this.width; const h = this.height; drawChar(0, 0, '╭'); drawChar(w - 1, 0, '╮'); drawChar(0, h - 1, '╰'); drawChar(w - 1, h - 1, '╯'); for (let x = 1; x < w - 1; x++) { drawChar(x, 0, '─'); drawChar(x, h - 1, '─'); } for (let y = 1; y < h - 1; y++) { drawChar(0, y, '│'); drawChar(w - 1, y, '│'); } } if (this.label && this.showLabel) { const label = ` ${this.label} `; const start = 1; for (let i = 0; i < label.length && i + start < this.width - 1; i++) { drawChar(i + start, 0, label[i]); } } // Render children sorted by z-index const sorted = [...this.children].sort((a, b) => a.z - b.z); for (const child of sorted) { const buf = child.draw(); for (let j = 0; j < buf.length; j++) { for (let i = 0; i < buf[j].length; i++) { const px = child.x + i; const py = child.y + j; if (px >= 0 && px < this.width && py >= 0 && py < this.height) { const char = buf[j][i]; if (char !== child.transparentChar) { this.buffer[py][px] = char; } } } } } return this.buffer; } }