UNPKG

asciitorium

Version:
270 lines (269 loc) 10.4 kB
import { Alignment, SizeValue, SizeContext, ComponentStyle, GapValue, PositionValue } from './types.js'; import type { State } from './State.js'; import { LayoutType, LayoutOptions } from './layouts/Layout.js'; /** * Configuration interface for Component initialization. * Supports both individual style properties and consolidated style objects. */ export interface ComponentProps { /** Optional label displayed at the top of the component */ label?: string; /** Optional comment for documentation purposes (not displayed) */ comment?: string; /** Whether to display the label when provided */ showLabel?: boolean; /** Consolidated style properties object */ style?: ComponentStyle; /** Component width (number, percentage string, or 'auto') */ width?: SizeValue; /** Component height (number, percentage string, or 'auto') */ height?: SizeValue; /** Whether to render a border around the component */ border?: boolean; /** Character used for component background */ background?: string; /** Groups and aligns all children within this component (Row/Column containers only) */ align?: Alignment; /** Exact coordinate placement - overrides layout positioning */ position?: PositionValue; /** Spacing around the component */ gap?: GapValue; /** State binding for reactive updates (deprecated) */ bind?: State<any> | ((state: State<any>) => void); /** Child components to be managed by this component */ children?: Component[]; /** Layout algorithm to use for positioning children */ layout?: LayoutType; /** Configuration options for the selected layout */ layoutOptions?: LayoutOptions; /** * Dynamic content switching system that allows runtime swapping of child components * based on a reactive state key. Supports component instances, classes, or factory functions. */ dynamicContent?: { /** State that determines which component to display */ selectedKey: State<string>; /** Map of keys to components (instances, classes, or factories) */ componentMap: Record<string, Component | (() => Component) | (new (...args: any[]) => Component)>; /** Fallback component when selectedKey doesn't match any map entry */ fallback?: Component | (() => Component) | (new (...args: any[]) => Component); }; /** Hotkey for quick access to this component. If not provided, one will be auto-assigned. */ hotkey?: string; /** Whether the component is visible (default: true). Must be a State object for reactive binding. */ visible?: State<boolean>; } /** * Abstract base class for all UI components in the asciitorium framework. * * Provides core functionality including: * - **Position and size management**: Supports absolute sizing, percentages, 'auto', and 'fill' * - **Child component management**: Automatic layout calculation via Row/Column layouts * - **Focus handling**: Visual indicators and keyboard navigation support * - **State binding**: Reactive updates when bound state changes * - **ASCII-based rendering**: Character-based 2D buffers with transparency * - **Dynamic content switching**: Runtime component replacement based on state * * ## Rendering System * * Components use a character-based rendering system where each component * renders to a 2D string array buffer. The transparent character '‽' allows * for overlay effects and complex compositions. * * ## Focus System * * Focus-enabled components automatically switch between single-line borders * (╭╮╰╯─│) and double-line borders (╔╗╚╝═║) when focused. * * ## Layout System * * Components can be positioned in two ways: * 1. **Relative positioning**: Using Row/Column layouts (default) * 2. **Absolute positioning**: Using the `position` prop with x/y/z coordinates * * @example * Creating a custom component: * ```typescript * class MyComponent extends Component { * constructor(options: ComponentProps) { * super({ * ...options, * width: options.width ?? 20, * height: options.height ?? 10, * border: true * }); * } * * override draw(): string[][] { * // Custom rendering logic * this.buffer = Array.from({ length: this.height }, () => * Array.from({ length: this.width }, () => ' ') * ); * return this.buffer; * } * } * ``` * * @example * Using component props with JSX: * ```tsx * <Button * width="50%" * height={5} * border * align="center" * position={{ x: 10, y: 5, z: 100 }} * gap={{ x: 2, y: 1 }} * > * Click Me * </Button> * ``` */ export declare abstract class Component { /** Optional label displayed at the top of the component */ label: string | undefined; /** Optional comment for documentation (not rendered) */ comment: string | undefined; /** Whether to display the label when provided */ showLabel: boolean; /** Current rendered width in characters */ width: number; /** Current rendered height in characters */ height: number; /** Whether to render a border around the component */ border: boolean; /** Character used to fill the component background */ fill: string; /** Groups and aligns all children within this component (Row/Column containers only) */ align?: Alignment; /** Whether the component uses fixed positioning */ fixed: boolean; /** Absolute X position */ x: number; /** Absolute Y position */ y: number; /** Z-index for rendering order (higher values on top) */ z: number; /** Spacing around the component */ gap: GapValue; /** Whether this component can receive keyboard focus */ focusable: boolean; /** Whether this component currently has focus */ hasFocus: boolean; /** * When true, component captures ALL keyboard input except bypass keys. * Used by input components (TextInput, etc.) to receive all keystrokes. */ captureModeActive: boolean; /** Keys that bypass capture mode and always work for navigation */ static readonly BYPASS_KEYS: string[]; /** Character used for transparency in rendering ('‽' allows overlays) */ transparentChar: string; /** Reference to parent component in the hierarchy */ parent?: Component; /** Hotkey assigned to this component for quick access */ hotkey?: string; /** State object controlling component visibility */ private visibleState?; /** Original size values for relative sizing calculations */ protected originalWidth?: SizeValue; protected originalHeight?: SizeValue; /** Current render buffer (2D character array) */ protected buffer: string[][]; /** Cleanup functions for state subscriptions */ private unbindFns; /** Cleanup functions registered via registerCleanup() */ private cleanupFns; /** Child components managed by this component */ protected children: Component[]; /** Layout algorithm used for positioning children */ protected layoutType: LayoutType; /** Configuration for the layout algorithm */ protected layoutOptions?: LayoutOptions; /** Cached layout instance for performance */ private layout?; /** * Initializes a new Component with the provided properties. * * @param props Configuration object containing style, layout, and behavior options */ constructor(props: ComponentProps); setParent(parent: Component): void; /** * Gets the current visibility state of the component. * @returns True if the component is visible (default), false if explicitly hidden */ get visible(): boolean; /** * Initializes child components from props and sets up layout. * * @param props Component properties containing potential children */ private initializeChildren; /** * Validates that a potential child is a valid Component. * * @param child Potential child component to validate * @returns True if the child is a valid Component */ private isValidChild; addChild(child: Component): void; removeChild(child: Component): void; getChildren(): Component[]; setChildren(children: Component[]): void; getAllDescendants(): Component[]; protected invalidateLayout(): void; protected recalculateLayout(): void; protected recalculateAutoSize(): void; bind<T>(state: State<T>, apply: (val: T) => void): void; /** * Registers a cleanup function to be called when the component is destroyed. * Use this for cleaning up timers, intervals, event listeners, and other resources. * * Example: * ```typescript * const intervalId = setInterval(() => {...}, 1000); * component.registerCleanup(() => clearInterval(intervalId)); * ``` * * Note: State subscriptions created via bind() are automatically cleaned up * and do not need to be registered with registerCleanup(). * * @param fn Cleanup function to execute on destroy */ registerCleanup(fn: () => void): void; destroy(): void; private static calculateAutoWidth; private static calculateAutoHeight; protected notifyAppOfFocusRefresh(): void; /** * Notifies the application's focus manager that the component tree has changed * and focus needs to be completely reset (e.g., when swapping out child components). */ protected notifyAppOfFocusReset(): void; getOriginalWidth(): SizeValue | undefined; getOriginalHeight(): SizeValue | undefined; resolveSize(context: SizeContext): void; handleEvent(_event: string): boolean; /** * Draws the border around the component with diamond corners for focus indication. * * @param drawChar Helper function for safe character drawing within bounds */ private drawBorder; /** * Renders all child components sorted by z-index and composites them into the buffer. */ private renderChildren; /** * Composites a single child component's buffer into this component's buffer. * * @param child The child component to composite */ private compositeChildBuffer; /** * Check if hotkey visibility is enabled by finding the App's FocusManager */ protected isHotkeyVisibilityEnabled(): boolean; draw(): string[][]; }