/** * Minimal TUI implementation with differential rendering */ import type { Terminal } from "./terminal.js"; import { visibleWidth } from "./utils.js"; /** * Component interface - all components must implement this */ export interface Component { /** * Render the component to lines for the given viewport width * @param width - Current viewport width * @returns Array of strings, each representing a line */ render(width: number): string[]; /** * Optional handler for keyboard input when component has focus */ handleInput?(data: string): void; /** * If true, component receives key release events (Kitty protocol). * Default is false - release events are filtered out. */ wantsKeyRelease?: boolean; /** * Invalidate any cached rendering state. * Called when theme changes or when component needs to re-render from scratch. */ invalidate(): void; } type InputListenerResult = { consume?: boolean; data?: string; } | undefined; type InputListener = (data: string) => InputListenerResult; /** * Interface for components that can receive focus and display a hardware cursor. * When focused, the component should emit CURSOR_MARKER at the cursor position * in its render output. TUI will find this marker and position the hardware * cursor there for proper IME candidate window positioning. */ export interface Focusable { /** Set by TUI when focus changes. Component should emit CURSOR_MARKER when true. */ focused: boolean; } /** Type guard to check if a component implements Focusable */ export declare function isFocusable(component: Component | null): component is Component & Focusable; /** * Cursor position marker - APC (Application Program Command) sequence. * This is a zero-width escape sequence that terminals ignore. * Components emit this at the cursor position when focused. * TUI finds and strips this marker, then positions the hardware cursor there. */ export declare const CURSOR_MARKER = "\u001B_pi:c\u0007"; export { visibleWidth }; /** * Anchor position for overlays */ export type OverlayAnchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" | "left-center" | "right-center"; /** * Options for side panel positioning and sizing. */ export interface SidePanelOptions { /** Width in columns, or percentage of terminal width (e.g., "40%"). Default: "40%" */ width?: SizeValue; /** Which side to place the panel. Default: "right" */ side?: "left" | "right"; } /** * Handle returned by showSidePanel for controlling the panel */ export interface SidePanelHandle { /** Remove the side panel and restore focus */ hide(): void; } /** * Margin configuration for overlays */ export interface OverlayMargin { top?: number; right?: number; bottom?: number; left?: number; } /** Value that can be absolute (number) or percentage (string like "50%") */ export type SizeValue = number | `${number}%`; /** * Options for overlay positioning and sizing. * Values can be absolute numbers or percentage strings (e.g., "50%"). */ export interface OverlayOptions { /** Width in columns, or percentage of terminal width (e.g., "50%") */ width?: SizeValue; /** Minimum width in columns */ minWidth?: number; /** Maximum height in rows, or percentage of terminal height (e.g., "50%") */ maxHeight?: SizeValue; /** Anchor point for positioning (default: 'center') */ anchor?: OverlayAnchor; /** Horizontal offset from anchor position (positive = right) */ offsetX?: number; /** Vertical offset from anchor position (positive = down) */ offsetY?: number; /** Row position: absolute number, or percentage (e.g., "25%" = 25% from top) */ row?: SizeValue; /** Column position: absolute number, or percentage (e.g., "50%" = centered horizontally) */ col?: SizeValue; /** Margin from terminal edges. Number applies to all sides. */ margin?: OverlayMargin | number; /** * Control overlay visibility based on terminal dimensions. * If provided, overlay is only rendered when this returns true. * Called each render cycle with current terminal dimensions. */ visible?: (termWidth: number, termHeight: number) => boolean; /** If true, don't capture keyboard focus when shown */ nonCapturing?: boolean; } /** * Handle returned by showOverlay for controlling the overlay */ export interface OverlayHandle { /** Permanently remove the overlay (cannot be shown again) */ hide(): void; /** Temporarily hide or show the overlay */ setHidden(hidden: boolean): void; /** Check if overlay is temporarily hidden */ isHidden(): boolean; /** Focus this overlay and bring it to the visual front */ focus(): void; /** Release focus to the previous target */ unfocus(): void; /** Check if this overlay currently has focus */ isFocused(): boolean; } /** * Container - a component that contains other components */ export declare class Container implements Component { children: Component[]; addChild(component: Component): void; removeChild(component: Component): void; clear(): void; invalidate(): void; render(width: number): string[]; } /** * TUI - Main class for managing terminal UI with differential rendering */ export declare class TUI extends Container { terminal: Terminal; private previousLines; private previousWidth; private previousHeight; private focusedComponent; private inputListeners; /** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */ onDebug?: () => void; private renderRequested; private renderTimer; private lastRenderAt; private static readonly MIN_RENDER_INTERVAL_MS; private cursorRow; private hardwareCursorRow; private showHardwareCursor; private clearOnShrink; private maxLinesRendered; private previousViewportTop; private fullRedrawCount; private stopped; private globalBgFn; private bottomPinnedChildren; private mainScrollBuffer; private started; private sidePanelEntry; private focusOrderCounter; private overlayStack; constructor(terminal: Terminal, showHardwareCursor?: boolean); get fullRedraws(): number; getShowHardwareCursor(): boolean; setShowHardwareCursor(enabled: boolean): void; getClearOnShrink(): boolean; /** * Set whether to trigger full re-render when content shrinks. * When true (default), empty rows are cleared when content shrinks. * When false, empty rows remain (reduces redraws on slower terminals). */ setClearOnShrink(enabled: boolean): void; setGlobalBackground(bgFn: ((text: string) => string) | null): void; /** * Pin the last N children to the bottom of the viewport. * When content is shorter than the terminal, empty padding is inserted * between main content and the pinned children to push them down. */ setBottomPinnedChildren(count: number): void; /** Enable in-app paging for content above bottom-pinned children. */ setMainScroll(enabled: boolean): void; scrollMainBy(rows: number): void; scrollMainPageUp(): void; scrollMainPageDown(): void; scrollMainToTail(): void; setFocus(component: Component | null): void; /** * Show an overlay component with configurable positioning and sizing. * Returns a handle to control the overlay's visibility. */ showOverlay(component: Component, options?: OverlayOptions): OverlayHandle; /** Hide the topmost overlay and restore previous focus. */ hideOverlay(): void; /** Show a side panel alongside the main content in a column layout. */ showSidePanel(component: Component, options?: SidePanelOptions): SidePanelHandle; /** Hide the side panel and restore previous focus. */ hideSidePanel(): void; /** Check if a side panel is currently shown. */ hasSidePanel(): boolean; /** Check if there are any visible overlays */ hasOverlay(): boolean; /** Check if an overlay entry is currently visible */ private isOverlayVisible; /** Find the topmost visible capturing overlay, if any */ private getTopmostVisibleOverlay; invalidate(): void; start(): void; addInputListener(listener: InputListener): () => void; removeInputListener(listener: InputListener): void; private queryCellSize; stop(): void; requestRender(force?: boolean): void; private scheduleRender; private handleInput; private handleMainScrollWheel; private consumeCellSizeResponse; /** * Resolve overlay layout from options. * Returns { width, row, col, maxHeight } for rendering. */ private resolveOverlayLayout; private resolveAnchorRow; private resolveAnchorCol; /** Render main content and side panel in a two-column layout. */ private renderWithSidePanel; /** Composite all overlays into content lines (sorted by focusOrder, higher = on top). */ private compositeOverlays; /** * Render with bottom-pinned children pushed to the bottom of the viewport. */ private renderWithBottomPin; private static readonly SEGMENT_RESET; private applyLineResets; /** Splice overlay content into a base line at a specific column. Single-pass optimized. */ private compositeLineAt; /** * Find and extract cursor position from rendered lines. * Searches for CURSOR_MARKER, calculates its position, and strips it from the output. * Only scans the bottom terminal height lines (visible viewport). * @param lines - Rendered lines to search * @param height - Terminal height (visible viewport size) * @returns Cursor position { row, col } or null if no marker found */ private extractCursorPosition; /** * Minimum terminal dimensions for a normal render. * Below these, we fall back to a degenerate-size placeholder per * cavekit-fullscreen-viewport R2 AC4 and R3 AC4. */ private static readonly MIN_ROWS; private static readonly MIN_COLS; private renderDegenerateFrame; private doRender; /** * Position the hardware cursor for IME candidate window. * @param cursorPos The cursor position extracted from rendered output, or null * @param totalLines Total number of rendered lines */ private positionHardwareCursor; } //# sourceMappingURL=tui.d.ts.map