@termuijs/core 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -89,6 +89,161 @@ interface ContrastFailure {
89
89
  */
90
90
  declare function validateThemeContrast(theme: Record<string, string>): ContrastFailure[];
91
91
 
92
+ /**
93
+ * Core terminal utility functions for ANSI escape sequences.
94
+ * Handles layout formatting, terminal bells, and notification configurations.
95
+ */
96
+ /** CSI (Control Sequence Introducer) prefix */
97
+ declare const CSI = "\u001B[";
98
+ /** OSC (Operating System Command) prefix */
99
+ declare const OSC = "\u001B]";
100
+ /** ESC character */
101
+ declare const ESC = "\u001B";
102
+ declare const hideCursor = "\u001B[?25l";
103
+ declare const showCursor = "\u001B[?25h";
104
+ declare const saveCursorPosition = "\u001B[s";
105
+ declare const restoreCursorPosition = "\u001B[u";
106
+ type CursorShape = 'block' | 'bar' | 'underline';
107
+ /** DECSCUSR: CSI Ps SP q. blink toggles steady vs blinking. */
108
+ declare function cursorShape(shape: CursorShape, blink?: boolean): string;
109
+ declare function moveTo(col: number, row: number): string;
110
+ declare function moveUp(n?: number): string;
111
+ declare function moveDown(n?: number): string;
112
+ declare function moveRight(n?: number): string;
113
+ declare function moveLeft(n?: number): string;
114
+ declare const requestCursorPosition = "\u001B[6n";
115
+ declare const clearScreen = "\u001B[2J";
116
+ declare const clearLine = "\u001B[2K";
117
+ declare const clearLineToEnd = "\u001B[0K";
118
+ declare const clearLineToStart = "\u001B[1K";
119
+ declare const clearDown = "\u001B[J";
120
+ declare const clearUp = "\u001B[1J";
121
+ declare const enterAltScreen = "\u001B[?1049h";
122
+ declare const exitAltScreen = "\u001B[?1049l";
123
+ /** Begin synchronized update — terminal holds display until end marker */
124
+ declare const beginSyncUpdate = "\u001B[?2026h";
125
+ /** End synchronized update — terminal flushes all pending changes atomically */
126
+ declare const endSyncUpdate = "\u001B[?2026l";
127
+ /** Enable SGR mouse tracking (most compatible modern mode) */
128
+ declare const enableMouse = "\u001B[?1000h\u001B[?1002h\u001B[?1006h";
129
+ /** Disable mouse tracking */
130
+ declare const disableMouse = "\u001B[?1000l\u001B[?1002l\u001B[?1006l";
131
+ declare const enableBracketedPaste = "\u001B[?2004h";
132
+ declare const disableBracketedPaste = "\u001B[?2004l";
133
+ declare const enableFocusTracking = "\u001B[?1004h";
134
+ declare const disableFocusTracking = "\u001B[?1004l";
135
+ declare const reset = "\u001B[0m";
136
+ declare const bold = "\u001B[1m";
137
+ declare const dim = "\u001B[2m";
138
+ declare const italic = "\u001B[3m";
139
+ declare const underline = "\u001B[4m";
140
+ declare const blink = "\u001B[5m";
141
+ declare const inverse = "\u001B[7m";
142
+ declare const strikethrough = "\u001B[9m";
143
+ declare const resetBold = "\u001B[22m";
144
+ declare const resetDim = "\u001B[22m";
145
+ declare const resetItalic = "\u001B[23m";
146
+ declare const resetUnderline = "\u001B[24m";
147
+ declare const resetBlink = "\u001B[25m";
148
+ declare const resetInverse = "\u001B[27m";
149
+ declare const resetStrikethrough = "\u001B[29m";
150
+ declare function setScrollRegion(top: number, bottom: number): string;
151
+ declare const resetScrollRegion = "\u001B[r";
152
+ declare function setTitle(title: string): string;
153
+ /** OSC 8 open: ESC ] 8 ; ; <url> ST. */
154
+ declare function hyperlinkOpen(url: string): string;
155
+ /** OSC 8 close: ESC ] 8 ; ; ST. */
156
+ declare const hyperlinkClose: string;
157
+ /** The BEL control byte. */
158
+ declare const bell$1 = "\u0007";
159
+ /** OSC 9 desktop notification: ESC ] 9 ; <text> BEL. */
160
+ declare function notify(text: string): string;
161
+ /**
162
+ * Strip ANSI escape sequences and dangerous C0/C1 control characters from a
163
+ * string, while keeping printable Unicode intact.
164
+ *
165
+ * Removes:
166
+ * - All ESC-introduced sequences (CSI, OSC, DCS, PM, APC, SS2/SS3, etc.)
167
+ * - Bare C0 controls (0x00-0x1F) except TAB (0x09) and LF (0x0A)
168
+ * - C1 controls / DEL (0x7F-0x9F)
169
+ *
170
+ * Safe for use on user-supplied or file-read strings before they are rendered
171
+ * to the terminal.
172
+ */
173
+ declare function stripAnsiControl(str: string): string;
174
+ /**
175
+ * Write text to the system clipboard via OSC 52.
176
+ * Supported by: xterm, iTerm2, Kitty, WezTerm, Alacritty, Windows Terminal.
177
+ * @param text Plain text to copy to clipboard
178
+ * @param stdout Target stream (default: process.stdout)
179
+ */
180
+ declare function writeClipboard(text: string, stdout?: NodeJS.WriteStream): void;
181
+ declare function readClipboard(stdin?: NodeJS.ReadStream, stdout?: NodeJS.WriteStream): Promise<string>;
182
+ declare const clipboard: {
183
+ write: typeof writeClipboard;
184
+ read: typeof readClipboard;
185
+ };
186
+
187
+ declare const ansi_CSI: typeof CSI;
188
+ type ansi_CursorShape = CursorShape;
189
+ declare const ansi_ESC: typeof ESC;
190
+ declare const ansi_OSC: typeof OSC;
191
+ declare const ansi_beginSyncUpdate: typeof beginSyncUpdate;
192
+ declare const ansi_blink: typeof blink;
193
+ declare const ansi_bold: typeof bold;
194
+ declare const ansi_clearDown: typeof clearDown;
195
+ declare const ansi_clearLine: typeof clearLine;
196
+ declare const ansi_clearLineToEnd: typeof clearLineToEnd;
197
+ declare const ansi_clearLineToStart: typeof clearLineToStart;
198
+ declare const ansi_clearScreen: typeof clearScreen;
199
+ declare const ansi_clearUp: typeof clearUp;
200
+ declare const ansi_clipboard: typeof clipboard;
201
+ declare const ansi_cursorShape: typeof cursorShape;
202
+ declare const ansi_dim: typeof dim;
203
+ declare const ansi_disableBracketedPaste: typeof disableBracketedPaste;
204
+ declare const ansi_disableFocusTracking: typeof disableFocusTracking;
205
+ declare const ansi_disableMouse: typeof disableMouse;
206
+ declare const ansi_enableBracketedPaste: typeof enableBracketedPaste;
207
+ declare const ansi_enableFocusTracking: typeof enableFocusTracking;
208
+ declare const ansi_enableMouse: typeof enableMouse;
209
+ declare const ansi_endSyncUpdate: typeof endSyncUpdate;
210
+ declare const ansi_enterAltScreen: typeof enterAltScreen;
211
+ declare const ansi_exitAltScreen: typeof exitAltScreen;
212
+ declare const ansi_hideCursor: typeof hideCursor;
213
+ declare const ansi_hyperlinkClose: typeof hyperlinkClose;
214
+ declare const ansi_hyperlinkOpen: typeof hyperlinkOpen;
215
+ declare const ansi_inverse: typeof inverse;
216
+ declare const ansi_italic: typeof italic;
217
+ declare const ansi_moveDown: typeof moveDown;
218
+ declare const ansi_moveLeft: typeof moveLeft;
219
+ declare const ansi_moveRight: typeof moveRight;
220
+ declare const ansi_moveTo: typeof moveTo;
221
+ declare const ansi_moveUp: typeof moveUp;
222
+ declare const ansi_notify: typeof notify;
223
+ declare const ansi_readClipboard: typeof readClipboard;
224
+ declare const ansi_requestCursorPosition: typeof requestCursorPosition;
225
+ declare const ansi_reset: typeof reset;
226
+ declare const ansi_resetBlink: typeof resetBlink;
227
+ declare const ansi_resetBold: typeof resetBold;
228
+ declare const ansi_resetDim: typeof resetDim;
229
+ declare const ansi_resetInverse: typeof resetInverse;
230
+ declare const ansi_resetItalic: typeof resetItalic;
231
+ declare const ansi_resetScrollRegion: typeof resetScrollRegion;
232
+ declare const ansi_resetStrikethrough: typeof resetStrikethrough;
233
+ declare const ansi_resetUnderline: typeof resetUnderline;
234
+ declare const ansi_restoreCursorPosition: typeof restoreCursorPosition;
235
+ declare const ansi_saveCursorPosition: typeof saveCursorPosition;
236
+ declare const ansi_setScrollRegion: typeof setScrollRegion;
237
+ declare const ansi_setTitle: typeof setTitle;
238
+ declare const ansi_showCursor: typeof showCursor;
239
+ declare const ansi_strikethrough: typeof strikethrough;
240
+ declare const ansi_stripAnsiControl: typeof stripAnsiControl;
241
+ declare const ansi_underline: typeof underline;
242
+ declare const ansi_writeClipboard: typeof writeClipboard;
243
+ declare namespace ansi {
244
+ export { ansi_CSI as CSI, type ansi_CursorShape as CursorShape, ansi_ESC as ESC, ansi_OSC as OSC, ansi_beginSyncUpdate as beginSyncUpdate, bell$1 as bell, ansi_blink as blink, ansi_bold as bold, ansi_clearDown as clearDown, ansi_clearLine as clearLine, ansi_clearLineToEnd as clearLineToEnd, ansi_clearLineToStart as clearLineToStart, ansi_clearScreen as clearScreen, ansi_clearUp as clearUp, ansi_clipboard as clipboard, ansi_cursorShape as cursorShape, ansi_dim as dim, ansi_disableBracketedPaste as disableBracketedPaste, ansi_disableFocusTracking as disableFocusTracking, ansi_disableMouse as disableMouse, ansi_enableBracketedPaste as enableBracketedPaste, ansi_enableFocusTracking as enableFocusTracking, ansi_enableMouse as enableMouse, ansi_endSyncUpdate as endSyncUpdate, ansi_enterAltScreen as enterAltScreen, ansi_exitAltScreen as exitAltScreen, ansi_hideCursor as hideCursor, ansi_hyperlinkClose as hyperlinkClose, ansi_hyperlinkOpen as hyperlinkOpen, ansi_inverse as inverse, ansi_italic as italic, ansi_moveDown as moveDown, ansi_moveLeft as moveLeft, ansi_moveRight as moveRight, ansi_moveTo as moveTo, ansi_moveUp as moveUp, ansi_notify as notify, ansi_readClipboard as readClipboard, ansi_requestCursorPosition as requestCursorPosition, ansi_reset as reset, ansi_resetBlink as resetBlink, ansi_resetBold as resetBold, ansi_resetDim as resetDim, ansi_resetInverse as resetInverse, ansi_resetItalic as resetItalic, ansi_resetScrollRegion as resetScrollRegion, ansi_resetStrikethrough as resetStrikethrough, ansi_resetUnderline as resetUnderline, ansi_restoreCursorPosition as restoreCursorPosition, ansi_saveCursorPosition as saveCursorPosition, ansi_setScrollRegion as setScrollRegion, ansi_setTitle as setTitle, ansi_showCursor as showCursor, ansi_strikethrough as strikethrough, ansi_stripAnsiControl as stripAnsiControl, ansi_underline as underline, ansi_writeClipboard as writeClipboard };
245
+ }
246
+
92
247
  interface TerminalOptions {
93
248
  /** Override stdout stream (useful for testing) */
94
249
  stdout?: NodeJS.WriteStream;
@@ -100,6 +255,10 @@ interface TerminalOptions {
100
255
  mouse?: boolean;
101
256
  /** Use alternate screen buffer for full-screen apps */
102
257
  altScreen?: boolean;
258
+ /** Enable bracketed-paste mode so InputParser receives paste events. */
259
+ bracketedPaste?: boolean;
260
+ /** Debounce window in ms for resize dispatch. Default 16. */
261
+ resizeDebounceMs?: number;
103
262
  }
104
263
  /**
105
264
  * Terminal adapter — wraps process.stdout/stdin and manages
@@ -114,16 +273,20 @@ declare class Terminal {
114
273
  private _isRawMode;
115
274
  private _isAltScreen;
116
275
  private _isMouseEnabled;
276
+ private _isBracketedPasteEnabled;
117
277
  private _resizeHandlers;
118
278
  private _cleanupHandlers;
119
279
  private _originalRawMode;
280
+ private _resizeDebounceMs;
281
+ private _resizeTimer;
282
+ private _lastDispatchedCols;
283
+ private _lastDispatchedRows;
120
284
  private _resizeHandler;
121
285
  private _exitHandler;
122
- private _sigintHandler;
123
- private _sigtermHandler;
124
- private _uncaughtExceptionHandler;
125
- private _unhandledRejectionHandler;
126
286
  private _restored;
287
+ private _restoring;
288
+ private _writeQueue;
289
+ private _isWriting;
127
290
  constructor(options?: TerminalOptions);
128
291
  /** Current terminal width in columns */
129
292
  get cols(): number;
@@ -139,9 +302,41 @@ declare class Terminal {
139
302
  exitAltScreen(): void;
140
303
  enableMouse(): void;
141
304
  disableMouse(): void;
305
+ /** Emit the enable sequence (CSI ?2004h). Idempotent. */
306
+ enableBracketedPaste(): void;
307
+ /** Emit the disable sequence (CSI ?2004l). Idempotent. */
308
+ disableBracketedPaste(): void;
142
309
  hideCursor(): void;
143
310
  showCursor(): void;
311
+ /** Set the cursor shape via DECSCUSR. Default blink = true. */
312
+ setCursorShape(shape: CursorShape, blink?: boolean): void;
313
+ /** Ring the terminal bell (BEL). */
314
+ bell(): void;
315
+ /** Send an OSC 9 desktop notification. Body is appended after a separator. */
316
+ notify(title: string, body?: string): void;
317
+ /**
318
+ * Writes chunked string data to stdout.
319
+ * Enforces queue serialization to ensure atomic ANSI escape execution.
320
+ */
144
321
  write(data: string): void;
322
+ /**
323
+ * Writes data to stdout synchronously, bypassing the write queue.
324
+ * Used by the renderer during frame flush to avoid races with the
325
+ * async queue lifecycle. Only use for render-path output.
326
+ */
327
+ writeSync(data: string): void;
328
+ /**
329
+ * Sequentially unshifts and drains string frames to stdout safely.
330
+ */
331
+ private _processWriteQueue;
332
+ /**
333
+ * Read text from the system clipboard via OSC 52.
334
+ */
335
+ readClipboard(): Promise<string>;
336
+ /**
337
+ * Write text to the system clipboard via OSC 52.
338
+ */
339
+ writeClipboard(text: string): void;
145
340
  onResize(handler: (cols: number, rows: number) => void): () => void;
146
341
  /**
147
342
  * Restore terminal to its original state.
@@ -185,6 +380,8 @@ interface Cell {
185
380
  * - 0 for continuation cells (second half of a wide char)
186
381
  */
187
382
  width: number;
383
+ /** Optional OSC 8 hyperlink target for this cell. */
384
+ link?: string;
188
385
  }
189
386
  /** Create a blank cell with default attributes */
190
387
  declare function emptyCell(): Cell;
@@ -202,14 +399,48 @@ declare function cellsEqual(a: Cell, b: Cell): boolean;
202
399
  declare class Screen {
203
400
  private _cols;
204
401
  private _rows;
402
+ private _previousLines;
403
+ private _lastRenderedHeight;
404
+ get lastRenderedHeight(): number;
405
+ set lastRenderedHeight(value: number);
406
+ private _previousStyleLines;
205
407
  front: Cell[][];
206
408
  back: Cell[][];
409
+ /**
410
+ * Render epoch counter. Incremented on every swap so downstream consumers
411
+ * (e.g. Renderer._flush) can detect and skip stale frames from a previous
412
+ * epoch, preventing double-swap corruption.
413
+ */
414
+ private _epoch;
415
+ /** True while swap() is executing to prevent re-entrant double-swap corruption. */
416
+ private _swapping;
417
+ /** The epoch captured at the start of the current flush cycle. */
418
+ private _flushEpoch;
207
419
  /**
208
420
  * Stack of clipping regions. When non-empty, setCell/writeString
209
421
  * only write to cells within the topmost clip rectangle.
210
422
  */
211
423
  private _clipStack;
424
+ private _translateYStack;
425
+ private _translateY;
212
426
  constructor(cols: number, rows: number);
427
+ /** Retrieve a read-only copy of the cell at (x, y) from the back buffer. */
428
+ getCell(x: number, y: number): Readonly<Cell> | undefined;
429
+ /** Serialize a back-buffer row to a plain string (skips continuation cells). */
430
+ getLine(row: number): string;
431
+ /**
432
+ * Serialize the style attributes of a back-buffer row into a
433
+ * fingerprint string. When the characters are identical but the
434
+ * styles differ (color, bold, italic, etc.), this fingerprint
435
+ * changes, allowing the diff renderer to detect style-only updates.
436
+ */
437
+ getStyleLine(row: number): string;
438
+ /** Return the saved line string for the given row (empty before first saveLines call). */
439
+ getPreviousLine(row: number): string;
440
+ /** Return the saved style fingerprint for the given row. */
441
+ getPreviousStyleLine(row: number): string;
442
+ /** Snapshot the current back-buffer line strings for use by diffRenderer. */
443
+ saveLines(): void;
213
444
  get cols(): number;
214
445
  get rows(): number;
215
446
  /**
@@ -237,6 +468,8 @@ declare class Screen {
237
468
  width: number;
238
469
  height: number;
239
470
  } | null;
471
+ pushTranslateY(offset: number): void;
472
+ popTranslateY(): void;
240
473
  /**
241
474
  * Write a cell to the back buffer at position (col, row).
242
475
  */
@@ -250,8 +483,16 @@ declare class Screen {
250
483
  * Clear the back buffer to all empty cells.
251
484
  */
252
485
  clear(): void;
486
+ /** Current render epoch — incremented after each swap. */
487
+ get epoch(): number;
488
+ /** The epoch captured at the start of the current flush cycle. */
489
+ get flushEpoch(): number;
490
+ set flushEpoch(value: number);
253
491
  /**
254
492
  * Swap front and back buffers. Called after rendering diffs.
493
+ * Uses mutual exclusion to prevent double-swap corruption when
494
+ * _flush() is called concurrently (e.g. from duplicate setImmediate
495
+ * callbacks).
255
496
  */
256
497
  swap(): void;
257
498
  /**
@@ -260,12 +501,47 @@ declare class Screen {
260
501
  resize(cols: number, rows: number): void;
261
502
  /**
262
503
  * Clear the front buffer (marks everything as "needs redraw").
504
+ * Mutates cells in-place to avoid GC pressure from object allocation.
263
505
  */
264
506
  invalidate(): void;
507
+ /**
508
+ * Export current screen as ANSI snapshot text.
509
+ */
510
+ exportANSI(): string;
511
+ /**
512
+ * Export current screen as SVG.
513
+ */
514
+ exportSVG(): string;
265
515
  private _createGrid;
266
- private _isWideCodePoint;
267
516
  }
268
517
 
518
+ declare class RenderHook {
519
+ private _buffer;
520
+ private _isActive;
521
+ private _originalConsole;
522
+ /** Check if the hook is currently intercepting console output */
523
+ get isActive(): boolean;
524
+ /** Wrap console.log/warn/error to buffer external logs instead of writing to stdout */
525
+ start(): void;
526
+ /** Restore original console methods */
527
+ stop(): void;
528
+ /** Retrieve and clear the buffered logs */
529
+ flush(): string;
530
+ /** Write directly to process.stdout, bypassing any buffering */
531
+ writeRaw(text: string): void;
532
+ }
533
+
534
+ /**
535
+ * Render frame statistics.
536
+ */
537
+ interface FrameStats {
538
+ /** Number of cells that differed and were redrawn this frame. */
539
+ cellsChanged: number;
540
+ /** Total bytes written to the terminal this frame. */
541
+ bytesWritten: number;
542
+ /** Wall-clock duration of the flush in milliseconds. */
543
+ durationMs: number;
544
+ }
269
545
  /**
270
546
  * Differential renderer — compares front/back screen buffers and
271
547
  * outputs only the changed cells. Uses synchronized output (CSI 2026)
@@ -278,8 +554,12 @@ declare class Renderer {
278
554
  private _frameTimer;
279
555
  private _renderRequested;
280
556
  private _colorDepth;
557
+ private _diffRenderer;
281
558
  private _onTick;
282
- constructor(terminal: Terminal, screen: Screen, fps?: number);
559
+ private _callbacks;
560
+ /** The stdout interceptor hook for buffering external logs */
561
+ readonly hook: RenderHook;
562
+ constructor(terminal: Terminal, screen: Screen, fps?: number, diffRenderer?: boolean);
283
563
  /** Change the rendering frame rate cap */
284
564
  setFPS(fps: number): void;
285
565
  /** Start the render loop */
@@ -290,19 +570,43 @@ declare class Renderer {
290
570
  requestFrame(): void;
291
571
  /** Force an immediate render (bypass frame rate) */
292
572
  renderNow(): void;
573
+ /** Register a per-frame profiling callback. Returns an unsubscribe function. */
574
+ onFrame(cb: (stats: FrameStats) => void): () => void;
293
575
  /**
294
576
  * Full-screen clear and redraw (first render or after resize).
295
577
  */
296
578
  fullRender(): void;
579
+ /** ANSI sequence to save cursor position */
580
+ private static _CURSOR_SAVE;
581
+ /** ANSI sequence to restore cursor position */
582
+ private static _CURSOR_RESTORE;
297
583
  /**
298
584
  * Core diff and flush: compare front vs back buffer,
299
585
  * emit only changed cells.
300
586
  */
301
587
  private _flush;
588
+ /** Style fingerprint of the last rendered cell (to suppress redundant ANSI reset/apply). */
589
+ private _lastStyleFingerprint;
590
+ /** Build a stable style fingerprint string for a cell (avoids allocation-heavy object comparison). */
591
+ private _styleFingerprint;
302
592
  /**
303
593
  * Generate the ANSI escape sequence to render a single cell.
594
+ * Skips ansiReset + re-apply when the adjacent cell has identical style.
304
595
  */
305
596
  private _renderCell;
597
+ /**
598
+ * If a span starts at a width-0 continuation cell (the second half of a
599
+ * wide character), adjust backward to the preceding cell so the cursor
600
+ * is placed at a valid column boundary.
601
+ */
602
+ private static _adjustSpanStart;
603
+ /**
604
+ * Render only the changed spans within a single row (cell-level granularity).
605
+ * Uses moveTo to position the cursor at the start of each changed span.
606
+ */
607
+ private _renderDiffLine;
608
+ private _renderLine;
609
+ private _emitStats;
306
610
  }
307
611
 
308
612
  /**
@@ -375,6 +679,8 @@ declare class LayerManager {
375
679
  private _layers;
376
680
  private _cols;
377
681
  private _rows;
682
+ private _hitWidgetGrid;
683
+ private _hitZGrid;
378
684
  constructor(cols: number, rows: number);
379
685
  get cols(): number;
380
686
  get rows(): number;
@@ -408,6 +714,10 @@ declare class LayerManager {
408
714
  * Write a string to a specific layer at position (col, row).
409
715
  */
410
716
  writeString(layerId: string, col: number, row: number, str: string, style?: Partial<Omit<Cell, 'char' | 'width'>>): void;
717
+ /**
718
+ * Check whether any visible layer has pending dirty changes.
719
+ */
720
+ hasDirtyLayers(): boolean;
411
721
  /**
412
722
  * Clear all cells in a specific layer.
413
723
  */
@@ -420,16 +730,31 @@ declare class LayerManager {
420
730
  * Composite all overlay layers onto the Screen's back buffer.
421
731
  * Layers are applied in z-index order (lowest first).
422
732
  * Transparent cells (empty with no colors) are skipped.
733
+ * Writes directly to screen.back to avoid setCell overhead
734
+ * (bounds/clip checks are already satisfied by dirtyRegion).
423
735
  */
424
736
  composite(screen: Screen): void;
425
737
  /**
426
738
  * Resize all layers when the terminal is resized.
427
739
  */
428
740
  resize(cols: number, rows: number): void;
741
+ /** Reset the hit grid. Call once at the start of each frame. */
742
+ clearHitGrid(): void;
743
+ /**
744
+ * Mark a rectangular region as owned by a widget at a given z-index.
745
+ * Higher z wins when regions overlap.
746
+ */
747
+ setHitRegion(widgetId: string, x: number, y: number, w: number, h: number, z?: number): void;
748
+ /** Return the topmost widget id at a cell, or null. */
749
+ hitTest(col: number, row: number): string | null;
429
750
  /**
430
751
  * Create an empty cell grid.
431
752
  */
432
753
  private _createGrid;
754
+ /**
755
+ * Allocate parallel hit grid and z-index grid.
756
+ */
757
+ private _allocateHitGrids;
433
758
  /**
434
759
  * Expand the dirty region of a layer to include the given cell.
435
760
  */
@@ -441,7 +766,49 @@ declare const caps: {
441
766
  readonly unicode: boolean;
442
767
  readonly motion: boolean;
443
768
  readonly ci: boolean;
769
+ readonly background: "light" | "dark";
770
+ readonly keybindingMode: "vim" | "emacs" | "default";
444
771
  };
772
+ /**
773
+ * Returns `true` when animation should be suppressed.
774
+ *
775
+ * True when `caps.motion` is `false` — i.e. when `NO_MOTION=1` is set
776
+ * or when running in a CI environment (`CI=1`).
777
+ *
778
+ * Animated widgets **must** check this function and render their static
779
+ * end-state (a single final frame) when it returns `true`, rather than
780
+ * playing through intermediate animation frames.
781
+ *
782
+ * @example
783
+ * if (prefersReducedMotion()) {
784
+ * renderStaticFrame();
785
+ * } else {
786
+ * startAnimation();
787
+ * }
788
+ */
789
+ declare function prefersReducedMotion(): boolean;
790
+ /**
791
+ * Returns `true` when color output should be used.
792
+ *
793
+ * Returns `false` when `NO_COLOR=1` is set or `TERM=dumb`,
794
+ * as per <https://no-color.org>.
795
+ *
796
+ * All widgets that emit ANSI color codes **must** check this function
797
+ * and emit plain text (no escape sequences) when it returns `false`.
798
+ *
799
+ * @example
800
+ * if (shouldUseColor()) {
801
+ * output += colorToAnsiFg(cell.fg, depth);
802
+ * }
803
+ */
804
+ declare function shouldUseColor(): boolean;
805
+ /**
806
+ * Returns `true` when the user prefers high-contrast output.
807
+ *
808
+ * Widgets that render text on colored backgrounds **may** check this
809
+ * to use more distinct color combinations.
810
+ */
811
+ declare function prefersHighContrast(): boolean;
445
812
 
446
813
  declare const BOX: Record<string, string>;
447
814
  declare const BRAILLE_SPIN: readonly ["|", "/", "-", "\\"];
@@ -451,6 +818,15 @@ declare const BLOCK: {
451
818
  readonly partial: "-";
452
819
  };
453
820
 
821
+ /**
822
+ * Triggers the terminal bell using the BEL control character (\x07).
823
+ * This will usually cause a system beep or a visual flash depending on
824
+ * the terminal emulator's configuration.
825
+ */
826
+ declare function bell(): void;
827
+
828
+ declare function mergeBorders(screen: Screen): void;
829
+
454
830
  /**
455
831
  * Keyboard event emitted when a key is pressed.
456
832
  */
@@ -490,7 +866,7 @@ declare function createKeyEvent(base: {
490
866
  /**
491
867
  * Mouse event types.
492
868
  */
493
- type MouseEventType = 'mousedown' | 'mouseup' | 'mousemove' | 'scroll';
869
+ type MouseEventType = 'mousedown' | 'mouseup' | 'mousemove' | 'scroll' | 'dblclick' | 'drag' | 'dragend';
494
870
  type MouseButton = 'left' | 'middle' | 'right' | 'none';
495
871
  /**
496
872
  * Mouse event emitted when mouse activity is detected.
@@ -504,8 +880,18 @@ interface MouseEvent {
504
880
  button: MouseButton;
505
881
  /** Type of mouse event */
506
882
  type: MouseEventType;
507
- /** Scroll direction (-1 = up, 1 = down) */
883
+ /** Vertical scroll delta: -1 = up, 1 = down. Set for vertical wheel events. */
508
884
  scrollDelta?: number;
885
+ /** Horizontal scroll delta: -1 = left, 1 = right. Set for horizontal wheel events. */
886
+ scrollDeltaX?: number;
887
+ /** Which axis a scroll event used. Set only for type 'scroll'. */
888
+ scrollAxis?: 'vertical' | 'horizontal';
889
+ /** Ctrl modifier held during the mouse event. SGR bit 0b10000. */
890
+ ctrl?: boolean;
891
+ /** Alt/Meta modifier held during the mouse event. SGR bit 0b1000. */
892
+ alt?: boolean;
893
+ /** Shift modifier held during the mouse event. SGR bit 0b100. */
894
+ shift?: boolean;
509
895
  }
510
896
  /**
511
897
  * Resize event emitted when the terminal is resized.
@@ -522,6 +908,8 @@ interface FocusEvent {
522
908
  targetId: string;
523
909
  /** Type of focus event */
524
910
  type: 'focus' | 'blur';
911
+ /** Monotonically increasing sequence number for ordering / staleness detection */
912
+ epoch?: number;
525
913
  }
526
914
  /**
527
915
  * Map of all event types to their data shapes.
@@ -536,8 +924,13 @@ interface EventMap {
536
924
  'mount': void;
537
925
  'unmount': void;
538
926
  'tick': number;
927
+ 'paste': string;
539
928
  }
540
929
 
930
+ interface CursorPosition {
931
+ row: number;
932
+ col: number;
933
+ }
541
934
  /**
542
935
  * Reads raw stdin bytes and parses them into typed KeyEvent / MouseEvent objects.
543
936
  * Handles escape sequences, multi-byte keys, ctrl+key, and SGR mouse events.
@@ -548,11 +941,18 @@ declare class InputParser {
548
941
  private _handler;
549
942
  private _escapeTimeout;
550
943
  private _escapeBuffer;
944
+ private _isPasting;
945
+ private _pasteBuffer;
946
+ private _cursorRequests;
551
947
  constructor(stdin: NodeJS.ReadStream);
552
948
  /** Subscribe to key events */
553
949
  onKey(handler: (event: KeyEvent) => void): () => void;
554
950
  /** Subscribe to mouse events */
555
951
  onMouse(handler: (event: MouseEvent) => void): () => void;
952
+ /** Subscribe to terminal focus-in (true) / focus-out (false) reports. */
953
+ onFocusChange(handler: (focused: boolean) => void): () => void;
954
+ onPaste(handler: (text: string) => void): () => void;
955
+ requestCursorPosition(timeoutMs?: number): Promise<CursorPosition>;
556
956
  /** Start listening for input */
557
957
  start(): void;
558
958
  /** Stop listening for input */
@@ -581,6 +981,15 @@ declare const CTRL_KEYS: Record<number, string>;
581
981
  * Special single-byte key codes.
582
982
  */
583
983
  declare const SPECIAL_KEYS: Record<number, string>;
984
+ /**
985
+ * Normalizes navigation key names based on the active keybinding mode.
986
+ * Useful for mapping vim (j/k/h/l) or emacs (ctrl+n/ctrl+p) keys to standard
987
+ * arrow key intents (up/down/left/right).
988
+ *
989
+ * @param keyName The raw key name from KeyEvent.name
990
+ * @returns The normalized directional key name, or the original key name
991
+ */
992
+ declare function normalizeNavigationKey(keyName: string): string;
584
993
 
585
994
  /**
586
995
  * Parse an SGR mouse event escape sequence.
@@ -598,6 +1007,57 @@ declare function parseMouseEvent(data: string): MouseEvent | null;
598
1007
  */
599
1008
  declare function isMouseSequence(data: string): boolean;
600
1009
 
1010
+ interface MouseGesturesOptions {
1011
+ /** Max ms between two clicks to count as a double-click. Default 300. */
1012
+ doubleClickMs?: number;
1013
+ }
1014
+ declare class MouseGestures {
1015
+ private doubleClickMs;
1016
+ private lastMouseDown;
1017
+ private activeDragButton;
1018
+ private wasDragging;
1019
+ constructor(opts?: MouseGesturesOptions);
1020
+ /**
1021
+ * Feed a raw MouseEvent. Returns synthesized events to emit
1022
+ * (may be empty). Does not mutate the input event.
1023
+ */
1024
+ feed(event: MouseEvent): MouseEvent[];
1025
+ }
1026
+
1027
+ interface Chord {
1028
+ keys: string[];
1029
+ handler: () => void;
1030
+ }
1031
+ interface ChordMatcherOptions {
1032
+ timeoutMs?: number;
1033
+ }
1034
+ declare class ChordMatcher {
1035
+ private _bindings;
1036
+ private _nextId;
1037
+ private _buffer;
1038
+ private _timeoutMs;
1039
+ private _timer;
1040
+ constructor(opts?: ChordMatcherOptions);
1041
+ bind(keys: string[], handler: () => void): () => void;
1042
+ feed(event: KeyEvent): boolean;
1043
+ private _getMatchingBindings;
1044
+ }
1045
+
1046
+ declare class LiveRender {
1047
+ private readonly terminal;
1048
+ private readonly screen;
1049
+ constructor(terminal: Terminal, screen: Screen);
1050
+ private getHeight;
1051
+ /**
1052
+ * Renders a serialized screen buffer.
1053
+ *
1054
+ * Widgets render into a Screen object first.
1055
+ * Callers should serialize the Screen contents into a string
1056
+ * before passing it to LiveRender.render().
1057
+ */
1058
+ render(frame: string): void;
1059
+ }
1060
+
601
1061
  /**
602
1062
  * Supported border styles.
603
1063
  */
@@ -628,7 +1088,7 @@ declare const BORDER_CHARS: Record<Exclude<BorderStyle, 'none' | 'custom'>, Bord
628
1088
  /**
629
1089
  * Get the border characters for a given style.
630
1090
  */
631
- declare function getBorderChars(style: BorderStyle, customChars?: Partial<BorderChars>): BorderChars | null;
1091
+ declare function getBorderChars(style: BorderStyle, customChars?: Partial<BorderChars>, asciiOnly?: boolean): BorderChars | null;
632
1092
  /**
633
1093
  * Calculate the total space a border takes up.
634
1094
  * Returns { horizontal, vertical } representing how many columns/rows
@@ -639,6 +1099,107 @@ declare function borderSize(style: BorderStyle): {
639
1099
  vertical: number;
640
1100
  };
641
1101
 
1102
+ interface LayoutContext {
1103
+ parentWidth: number;
1104
+ parentHeight: number;
1105
+ contentWidth: number;
1106
+ contentHeight: number;
1107
+ readonly elementWidth: number;
1108
+ readonly elementHeight: number;
1109
+ readonly elementX: number;
1110
+ readonly elementY: number;
1111
+ axis: 'horizontal' | 'vertical';
1112
+ getGroupSize(groupId: string): number;
1113
+ }
1114
+
1115
+ declare abstract class Pos {
1116
+ /** List of variables this Pos depends on, e.g. ['elementSize', 'parentSize'] */
1117
+ abstract dependencies(): string[];
1118
+ /** Computes the final numeric coordinate */
1119
+ abstract evaluate(ctx: LayoutContext): number;
1120
+ /** Center the element within its parent */
1121
+ static center(): Pos;
1122
+ /** Anchor the element `margin` units away from the end (right/bottom) */
1123
+ static anchorEnd(margin?: number): Pos;
1124
+ /** Align multiple siblings as a group */
1125
+ static align(alignment: 'start' | 'center' | 'end', groupId: string): Pos;
1126
+ }
1127
+
1128
+ declare abstract class Dim {
1129
+ /** List of variables this Dim depends on, e.g. ['contentWidth'] */
1130
+ abstract dependencies(): string[];
1131
+ /** Computes the final numeric size */
1132
+ abstract evaluate(ctx: LayoutContext): number;
1133
+ /** Size to the intrinsic content of the element */
1134
+ static auto(): Dim;
1135
+ /** Fill remaining space in the parent, minus an optional margin */
1136
+ static fill(margin?: number): Dim;
1137
+ /** Custom function to determine size */
1138
+ static func(fn: (ctx: LayoutContext) => number): Dim;
1139
+ }
1140
+
1141
+ declare enum Flex {
1142
+ Start = "start",
1143
+ Center = "center",
1144
+ End = "end",
1145
+ SpaceBetween = "space-between",
1146
+ SpaceAround = "space-around"
1147
+ }
1148
+ declare abstract class Constraint {
1149
+ /** Fixed length in columns/rows */
1150
+ static Length(n: number): Constraint;
1151
+ /** Percentage of available space (0-100) */
1152
+ static Percentage(n: number): Constraint;
1153
+ /** Minimum length */
1154
+ static Min(n: number): Constraint;
1155
+ /** Maximum length */
1156
+ static Max(n: number): Constraint;
1157
+ /** Fills remaining space, with a flex weight */
1158
+ static Fill(weight?: number): Constraint;
1159
+ }
1160
+ declare class LengthConstraint extends Constraint {
1161
+ value: number;
1162
+ constructor(value: number);
1163
+ }
1164
+ declare class PercentageConstraint extends Constraint {
1165
+ value: number;
1166
+ constructor(value: number);
1167
+ }
1168
+ declare class MinConstraint extends Constraint {
1169
+ value: number;
1170
+ constructor(value: number);
1171
+ }
1172
+ declare class MaxConstraint extends Constraint {
1173
+ value: number;
1174
+ constructor(value: number);
1175
+ }
1176
+ declare class FillConstraint extends Constraint {
1177
+ weight: number;
1178
+ constructor(weight: number);
1179
+ }
1180
+ declare function resolveConstraints(available: number, constraints: Constraint[], flex?: Flex, gap?: number): {
1181
+ offset: number;
1182
+ size: number;
1183
+ }[];
1184
+
1185
+ interface ResolvableNode {
1186
+ id: string;
1187
+ x?: number | Pos;
1188
+ y?: number | Pos;
1189
+ width?: number | Dim;
1190
+ height?: number | Dim;
1191
+ contentWidth: number;
1192
+ contentHeight: number;
1193
+ computed: {
1194
+ x: number;
1195
+ y: number;
1196
+ width: number;
1197
+ height: number;
1198
+ };
1199
+ groupId?: string;
1200
+ }
1201
+ declare function resolveLayoutVariables(nodes: ResolvableNode[], parentWidth: number, parentHeight: number): void;
1202
+
642
1203
  /**
643
1204
  * Edge values (padding, margin) — top, right, bottom, left.
644
1205
  */
@@ -663,13 +1224,18 @@ interface Style {
663
1224
  padding?: Partial<Edges> | number;
664
1225
  margin?: Partial<Edges> | number;
665
1226
  border?: BorderStyle;
1227
+ asciiOnly?: boolean;
666
1228
  borderColor?: Color;
667
- width?: number | string;
668
- height?: number | string;
1229
+ width?: number | string | Dim;
1230
+ height?: number | string | Dim;
669
1231
  minWidth?: number;
670
1232
  minHeight?: number;
671
1233
  maxWidth?: number;
672
1234
  maxHeight?: number;
1235
+ x?: number | Pos;
1236
+ y?: number | Pos;
1237
+ groupId?: string;
1238
+ constraints?: Constraint[];
673
1239
  flexDirection?: 'row' | 'column';
674
1240
  justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
675
1241
  alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch';
@@ -699,6 +1265,10 @@ declare function mergeStyles(base: Style, override: Style): Style;
699
1265
  * Create a default (empty) style.
700
1266
  */
701
1267
  declare function defaultStyle(): Style;
1268
+ /**
1269
+ * Returns true when any layout-affecting property differs between old and new style.
1270
+ */
1271
+ declare function hasLayoutChanges(oldStyle: Style, newStyle: Style): boolean;
702
1272
  /**
703
1273
  * Extract the cell-level style attributes from a Style object.
704
1274
  * Used when rendering text into the screen buffer.
@@ -728,6 +1298,16 @@ interface LayoutNode {
728
1298
  computed: Rect;
729
1299
  /** Dirty flag — true when this node needs to be re-laid-out. Foundation for layout caching. */
730
1300
  _dirty: boolean;
1301
+ /** Last container dimensions used — separate from computed so manual computed edits don't confuse sizeChanged detection */
1302
+ _lastContainerWidth: number;
1303
+ _lastContainerHeight: number;
1304
+ /** Last computed dimensions — used to detect size changes in non-dirty nodes
1305
+ * so grandchildren are re-laid out when a parent recomputes this node's rect. */
1306
+ _lastComputedWidth: number;
1307
+ _lastComputedHeight: number;
1308
+ /** Drag and drop support */
1309
+ _draggable?: boolean;
1310
+ _dragging?: boolean;
731
1311
  }
732
1312
  /**
733
1313
  * Create a LayoutNode with default values.
@@ -747,52 +1327,7 @@ declare function createLayoutNode(id: string, style: Style, children?: LayoutNod
747
1327
  * - gap between children
748
1328
  */
749
1329
  declare function computeLayout(root: LayoutNode, containerWidth: number, containerHeight: number): void;
750
-
751
- type Constraint = {
752
- type: 'length';
753
- value: number;
754
- } | {
755
- type: 'percentage';
756
- value: number;
757
- } | {
758
- type: 'ratio';
759
- num: number;
760
- den: number;
761
- } | {
762
- type: 'min';
763
- value: number;
764
- } | {
765
- type: 'max';
766
- value: number;
767
- } | {
768
- type: 'fill';
769
- weight: number;
770
- };
771
- /** Exactly n cells. */
772
- declare const length: (n: number) => Constraint;
773
- /** n% of available space. */
774
- declare const percentage: (n: number) => Constraint;
775
- /** num/den of available space. */
776
- declare const ratio: (num: number, den: number) => Constraint;
777
- /** At least n cells. */
778
- declare const min: (n: number) => Constraint;
779
- /** At most n cells. */
780
- declare const max: (n: number) => Constraint;
781
- /** Fill remaining space with the given weight. Default weight: 1. */
782
- declare const fill: (weight?: number) => Constraint;
783
- /**
784
- * Split a rectangle into sub-rectangles using constraints.
785
- *
786
- * Example:
787
- * ```ts
788
- * const [header, body, footer] = splitRect(
789
- * area,
790
- * [length(3), fill(), length(1)],
791
- * 'vertical',
792
- * );
793
- * ```
794
- */
795
- declare function splitRect(rect: Rect, constraints: Constraint[], direction?: 'horizontal' | 'vertical', gap?: number): Rect[];
1330
+ declare function invalidateLayout(node: LayoutNode): void;
796
1331
 
797
1332
  /**
798
1333
  * Strongly-typed event emitter using TypeScript generics.
@@ -841,6 +1376,7 @@ interface Focusable {
841
1376
  * - Tab-order cycling (focusNext/focusPrev)
842
1377
  * - Focus trapping (for modals — limits Tab to a container)
843
1378
  * - Focus groups (for arrow-key navigation within a group)
1379
+ * - 2D Spatial navigation (focusUp/Down/Left/Right)
844
1380
  */
845
1381
  declare class FocusManager {
846
1382
  private _focusables;
@@ -861,13 +1397,30 @@ declare class FocusManager {
861
1397
  * Maps groupId → ordered list of widget IDs.
862
1398
  */
863
1399
  private _groups;
1400
+ /**
1401
+ * Record of on-screen rects for widgets, used for spatial navigation.
1402
+ */
1403
+ private _rects;
1404
+ /** Monotonically increasing epoch for ordered event sequencing */
1405
+ private _epoch;
1406
+ /** Queue of focus state changes accumulated before start() is called */
1407
+ private _pendingQueue;
1408
+ /** True once start() has been called — enables event emission */
1409
+ private _started;
864
1410
  /** Currently focused widget ID, or null if none */
865
1411
  get currentId(): string | null;
866
1412
  /** Subscribe to focus/blur events */
867
1413
  on<K extends 'focus' | 'blur'>(event: K, handler: (data: FocusEvent) => void): () => void;
1414
+ /**
1415
+ * Enable event emission and replay any queued focus events.
1416
+ * Call this from App.mount() after _subscribeFocusEvents().
1417
+ */
1418
+ start(): void;
868
1419
  /**
869
1420
  * Register a focusable widget.
870
1421
  * Widgets are ordered by tabIndex (ascending), then insertion order.
1422
+ * Before start() is called, events are queued rather than emitted so
1423
+ * they are not lost when App has not yet subscribed to them.
871
1424
  */
872
1425
  register(focusable: Focusable): void;
873
1426
  /**
@@ -933,6 +1486,17 @@ declare class FocusManager {
933
1486
  * Move focus to the previous widget within the same group.
934
1487
  */
935
1488
  focusPrevInGroup(): boolean;
1489
+ /** Record the on-screen rect for a widget, used for spatial navigation. */
1490
+ setRect(id: string, rect: Rect): void;
1491
+ private _spatialFocus;
1492
+ /** Move focus to the nearest focusable widget above the current one. */
1493
+ focusUp(): boolean;
1494
+ /** Move focus to the nearest focusable widget below the current one. */
1495
+ focusDown(): boolean;
1496
+ /** Move focus to the nearest focusable widget to the left of the current one. */
1497
+ focusLeft(): boolean;
1498
+ /** Move focus to the nearest focusable widget to the right of the current one. */
1499
+ focusRight(): boolean;
936
1500
  /**
937
1501
  * Get the active focusables, filtered by the current trap if any.
938
1502
  */
@@ -1189,6 +1753,12 @@ interface AppOptions extends TerminalOptions {
1189
1753
  fps?: number;
1190
1754
  /** Use alternate screen (full-screen mode). Default: true */
1191
1755
  fullscreen?: boolean;
1756
+ /** Merge adjacent borders into junction characters */
1757
+ dockBorders?: boolean;
1758
+ /** Screen mode: 'alternate' = alt screen (default), 'main' = render to main screen, 'inline' = render N rows at cursor */
1759
+ screenMode?: 'alternate' | 'main' | 'inline';
1760
+ /** Number of rows to render in inline mode (only used when screenMode='inline') */
1761
+ inlineRows?: number;
1192
1762
  /** Enable mouse support. Default: false */
1193
1763
  mouse?: boolean;
1194
1764
  /** Force fallback (static) rendering */
@@ -1197,6 +1767,7 @@ interface AppOptions extends TerminalOptions {
1197
1767
  skipFallback?: boolean;
1198
1768
  /** Title to set on the terminal window */
1199
1769
  title?: string;
1770
+ diffRenderer?: boolean;
1200
1771
  }
1201
1772
  /**
1202
1773
  * Widget interface that App expects for the root widget.
@@ -1216,14 +1787,6 @@ interface RootWidget {
1216
1787
  }
1217
1788
  /**
1218
1789
  * Application lifecycle manager.
1219
- *
1220
- * Manages:
1221
- * - Terminal setup/teardown (alt screen, raw mode, cursor, mouse)
1222
- * - Screen buffer and renderer initialization
1223
- * - Input parsing and event dispatch
1224
- * - Layout computation and rect sync
1225
- * - Render loop
1226
- * - Graceful shutdown
1227
1790
  */
1228
1791
  declare class App {
1229
1792
  readonly terminal: Terminal;
@@ -1239,22 +1802,30 @@ declare class App {
1239
1802
  private _exitResolve;
1240
1803
  private _unsubKey;
1241
1804
  private _unsubMouse;
1805
+ private _unsubPaste;
1806
+ private _unsubFocus;
1807
+ private _unsubBlur;
1808
+ private _unsubSigInt;
1809
+ private _unsubSigTerm;
1810
+ private _unsubUncaughtException;
1811
+ private _unsubUnhandledRejection;
1242
1812
  private _widgetById;
1813
+ private _pendingFocusState;
1814
+ private _consecutiveRenderFailures;
1815
+ private static readonly MAX_RENDER_FAILURES;
1816
+ private _insertBefore;
1817
+ private _isRenderPending;
1243
1818
  constructor(rootWidget: RootWidget, options?: AppOptions);
1244
1819
  /**
1245
1820
  * Start the application.
1246
- * Sets up the terminal, starts the render loop, and mounts the root widget.
1247
- * Returns a promise that resolves when exit() is called.
1248
1821
  */
1249
1822
  mount(): Promise<number>;
1250
1823
  /**
1251
1824
  * Stop the application and restore terminal state.
1252
1825
  */
1253
- unmount(): void;
1826
+ unmount(exitCode?: number): void;
1254
1827
  /**
1255
1828
  * Create an overlay layer for rendering above normal widgets.
1256
- * @param id Unique layer identifier (e.g. 'modal', 'select-dropdown', 'toast')
1257
- * @param zIndex Stacking order (higher = rendered on top). Default: 100
1258
1829
  */
1259
1830
  addOverlay(id: string, zIndex?: number): void;
1260
1831
  /**
@@ -1263,29 +1834,44 @@ declare class App {
1263
1834
  removeOverlay(id: string): void;
1264
1835
  /**
1265
1836
  * Request a re-render on the next frame.
1266
- * Skips layout + render pass when the root widget reports no dirty state.
1837
+ * Batches rapid structural updates via setImmediate scheduling so that
1838
+ * multiple synchronous state mutations collapse into a single render frame.
1267
1839
  */
1268
1840
  requestRender(): void;
1269
1841
  /**
1270
1842
  * Exit the app (convenience method).
1271
1843
  */
1272
1844
  exit(code?: number): void;
1845
+ /**
1846
+ * Read from the system clipboard.
1847
+ */
1848
+ readClipboard(): Promise<string>;
1849
+ /**
1850
+ * Write to the system clipboard.
1851
+ */
1852
+ writeClipboard(text: string): void;
1853
+ /**
1854
+ * Register a persistent line to be written above inline viewport output.
1855
+ */
1856
+ insertBefore(line: string): () => void;
1273
1857
  /**
1274
1858
  * Render in fallback (static) mode for non-interactive environments.
1275
1859
  */
1276
1860
  private _renderFallback;
1277
1861
  /**
1278
1862
  * Build the bubble chain for keyboard events.
1279
- * Returns an array: [focused widget, parent, grandparent, ..., root]
1280
- * Uses the cached _widgetById map for O(1) lookup instead of DFS.
1281
1863
  */
1282
1864
  private _buildBubbleChain;
1283
1865
  /**
1284
1866
  * Rebuild the widget ID cache by walking the entire widget tree.
1285
- * Called after syncLayout() so the map stays current.
1286
1867
  */
1287
1868
  private _buildWidgetMap;
1288
1869
  private _walkWidget;
1870
+ private _handleFocusEvent;
1871
+ private _setWidgetFocused;
1872
+ private _subscribeFocusEvents;
1873
+ private _applyPendingFocusState;
1874
+ private _isFocusAwareWidget;
1289
1875
  }
1290
1876
 
1291
1877
  /**
@@ -1306,15 +1892,18 @@ declare function shouldUseFallback(): boolean;
1306
1892
  */
1307
1893
  declare function renderFallback(screen: Screen): string;
1308
1894
 
1895
+ interface InlineViewportOptions {
1896
+ rows: number;
1897
+ }
1309
1898
  /**
1310
- * Calculate the visual width of a string in terminal columns.
1311
- *
1312
- * - CJK characters count as 2 columns
1313
- * - Emoji count as 2 columns
1314
- * - Combining/zero-width characters count as 0
1315
- * - ANSI escape sequences count as 0
1316
- * - Regular characters count as 1
1899
+ * Render the bottom `rows` of the provided `screen` to the terminal using plain text.
1900
+ * Preserves scrollback by writing lines to stdout rather than taking over the alternate screen.
1317
1901
  */
1902
+ declare function renderInlineToTerminal(terminal: Terminal, screen: Screen, rows: number): void;
1903
+ declare function createInlineViewport(opts: InlineViewportOptions): {
1904
+ rows: number;
1905
+ };
1906
+
1318
1907
  declare function stringWidth(str: string): number;
1319
1908
  /**
1320
1909
  * Truncate a string to the given visual width, preserving ANSI codes.
@@ -1331,112 +1920,34 @@ declare function stripAnsi(str: string): string;
1331
1920
  */
1332
1921
  declare function wordWrap(str: string, width: number): string;
1333
1922
 
1334
- /** CSI (Control Sequence Introducer) prefix */
1335
- declare const CSI = "\u001B[";
1336
- /** OSC (Operating System Command) prefix */
1337
- declare const OSC = "\u001B]";
1338
- /** ESC character */
1339
- declare const ESC = "\u001B";
1340
- declare const hideCursor = "\u001B[?25l";
1341
- declare const showCursor = "\u001B[?25h";
1342
- declare const saveCursorPosition = "\u001B[s";
1343
- declare const restoreCursorPosition = "\u001B[u";
1344
- declare function moveTo(col: number, row: number): string;
1345
- declare function moveUp(n?: number): string;
1346
- declare function moveDown(n?: number): string;
1347
- declare function moveRight(n?: number): string;
1348
- declare function moveLeft(n?: number): string;
1349
- declare const clearScreen = "\u001B[2J";
1350
- declare const clearLine = "\u001B[2K";
1351
- declare const clearLineToEnd = "\u001B[0K";
1352
- declare const clearLineToStart = "\u001B[1K";
1353
- declare const clearDown = "\u001B[J";
1354
- declare const clearUp = "\u001B[1J";
1355
- declare const enterAltScreen = "\u001B[?1049h";
1356
- declare const exitAltScreen = "\u001B[?1049l";
1357
- /** Begin synchronized update — terminal holds display until end marker */
1358
- declare const beginSyncUpdate = "\u001B[?2026h";
1359
- /** End synchronized update — terminal flushes all pending changes atomically */
1360
- declare const endSyncUpdate = "\u001B[?2026l";
1361
- /** Enable SGR mouse tracking (most compatible modern mode) */
1362
- declare const enableMouse = "\u001B[?1000h\u001B[?1002h\u001B[?1006h";
1363
- /** Disable mouse tracking */
1364
- declare const disableMouse = "\u001B[?1000l\u001B[?1002l\u001B[?1006l";
1365
- declare const enableBracketedPaste = "\u001B[?2004h";
1366
- declare const disableBracketedPaste = "\u001B[?2004l";
1367
- declare const reset = "\u001B[0m";
1368
- declare const bold = "\u001B[1m";
1369
- declare const dim = "\u001B[2m";
1370
- declare const italic = "\u001B[3m";
1371
- declare const underline = "\u001B[4m";
1372
- declare const blink = "\u001B[5m";
1373
- declare const inverse = "\u001B[7m";
1374
- declare const strikethrough = "\u001B[9m";
1375
- declare const resetBold = "\u001B[22m";
1376
- declare const resetDim = "\u001B[22m";
1377
- declare const resetItalic = "\u001B[23m";
1378
- declare const resetUnderline = "\u001B[24m";
1379
- declare const resetBlink = "\u001B[25m";
1380
- declare const resetInverse = "\u001B[27m";
1381
- declare const resetStrikethrough = "\u001B[29m";
1382
- declare function setScrollRegion(top: number, bottom: number): string;
1383
- declare const resetScrollRegion = "\u001B[r";
1384
- declare function setTitle(title: string): string;
1923
+ interface DebounceOptions {
1924
+ /** Invoke on the leading edge of the timeout */
1925
+ leading?: boolean;
1926
+ /** Invoke on the trailing edge of the timeout */
1927
+ trailing?: boolean;
1928
+ }
1385
1929
  /**
1386
- * Write text to the system clipboard via OSC 52.
1387
- * Supported by: xterm, iTerm2, Kitty, WezTerm, Alacritty, Windows Terminal.
1388
- * @param text Plain text to copy to clipboard
1389
- * @param stdout Target stream (default: process.stdout)
1930
+ * Creates a debounced function that delays invoking `func` until after
1931
+ * `wait` milliseconds have elapsed since the last time it was invoked.
1932
+ *
1933
+ * @param func The function to debounce
1934
+ * @param wait The number of milliseconds to delay
1935
+ * @param options.leading Invoke on the leading edge
1936
+ * @param options.trailing Invoke on the trailing edge (default: true)
1937
+ * @returns The debounced function with a `cancel()` method
1938
+ *
1939
+ * @example
1940
+ * ```ts
1941
+ * const search = debounce((query: string) => {
1942
+ * performSearch(query);
1943
+ * }, 300);
1944
+ *
1945
+ * search('term'); // will execute 300ms after last call
1946
+ * search.cancel(); // cancel pending execution
1947
+ * ```
1390
1948
  */
1391
- declare function writeClipboard(text: string, stdout?: NodeJS.WriteStream): void;
1392
-
1393
- declare const ansi_CSI: typeof CSI;
1394
- declare const ansi_ESC: typeof ESC;
1395
- declare const ansi_OSC: typeof OSC;
1396
- declare const ansi_beginSyncUpdate: typeof beginSyncUpdate;
1397
- declare const ansi_blink: typeof blink;
1398
- declare const ansi_bold: typeof bold;
1399
- declare const ansi_clearDown: typeof clearDown;
1400
- declare const ansi_clearLine: typeof clearLine;
1401
- declare const ansi_clearLineToEnd: typeof clearLineToEnd;
1402
- declare const ansi_clearLineToStart: typeof clearLineToStart;
1403
- declare const ansi_clearScreen: typeof clearScreen;
1404
- declare const ansi_clearUp: typeof clearUp;
1405
- declare const ansi_dim: typeof dim;
1406
- declare const ansi_disableBracketedPaste: typeof disableBracketedPaste;
1407
- declare const ansi_disableMouse: typeof disableMouse;
1408
- declare const ansi_enableBracketedPaste: typeof enableBracketedPaste;
1409
- declare const ansi_enableMouse: typeof enableMouse;
1410
- declare const ansi_endSyncUpdate: typeof endSyncUpdate;
1411
- declare const ansi_enterAltScreen: typeof enterAltScreen;
1412
- declare const ansi_exitAltScreen: typeof exitAltScreen;
1413
- declare const ansi_hideCursor: typeof hideCursor;
1414
- declare const ansi_inverse: typeof inverse;
1415
- declare const ansi_italic: typeof italic;
1416
- declare const ansi_moveDown: typeof moveDown;
1417
- declare const ansi_moveLeft: typeof moveLeft;
1418
- declare const ansi_moveRight: typeof moveRight;
1419
- declare const ansi_moveTo: typeof moveTo;
1420
- declare const ansi_moveUp: typeof moveUp;
1421
- declare const ansi_reset: typeof reset;
1422
- declare const ansi_resetBlink: typeof resetBlink;
1423
- declare const ansi_resetBold: typeof resetBold;
1424
- declare const ansi_resetDim: typeof resetDim;
1425
- declare const ansi_resetInverse: typeof resetInverse;
1426
- declare const ansi_resetItalic: typeof resetItalic;
1427
- declare const ansi_resetScrollRegion: typeof resetScrollRegion;
1428
- declare const ansi_resetStrikethrough: typeof resetStrikethrough;
1429
- declare const ansi_resetUnderline: typeof resetUnderline;
1430
- declare const ansi_restoreCursorPosition: typeof restoreCursorPosition;
1431
- declare const ansi_saveCursorPosition: typeof saveCursorPosition;
1432
- declare const ansi_setScrollRegion: typeof setScrollRegion;
1433
- declare const ansi_setTitle: typeof setTitle;
1434
- declare const ansi_showCursor: typeof showCursor;
1435
- declare const ansi_strikethrough: typeof strikethrough;
1436
- declare const ansi_underline: typeof underline;
1437
- declare const ansi_writeClipboard: typeof writeClipboard;
1438
- declare namespace ansi {
1439
- export { ansi_CSI as CSI, ansi_ESC as ESC, ansi_OSC as OSC, ansi_beginSyncUpdate as beginSyncUpdate, ansi_blink as blink, ansi_bold as bold, ansi_clearDown as clearDown, ansi_clearLine as clearLine, ansi_clearLineToEnd as clearLineToEnd, ansi_clearLineToStart as clearLineToStart, ansi_clearScreen as clearScreen, ansi_clearUp as clearUp, ansi_dim as dim, ansi_disableBracketedPaste as disableBracketedPaste, ansi_disableMouse as disableMouse, ansi_enableBracketedPaste as enableBracketedPaste, ansi_enableMouse as enableMouse, ansi_endSyncUpdate as endSyncUpdate, ansi_enterAltScreen as enterAltScreen, ansi_exitAltScreen as exitAltScreen, ansi_hideCursor as hideCursor, ansi_inverse as inverse, ansi_italic as italic, ansi_moveDown as moveDown, ansi_moveLeft as moveLeft, ansi_moveRight as moveRight, ansi_moveTo as moveTo, ansi_moveUp as moveUp, ansi_reset as reset, ansi_resetBlink as resetBlink, ansi_resetBold as resetBold, ansi_resetDim as resetDim, ansi_resetInverse as resetInverse, ansi_resetItalic as resetItalic, ansi_resetScrollRegion as resetScrollRegion, ansi_resetStrikethrough as resetStrikethrough, ansi_resetUnderline as resetUnderline, ansi_restoreCursorPosition as restoreCursorPosition, ansi_saveCursorPosition as saveCursorPosition, ansi_setScrollRegion as setScrollRegion, ansi_setTitle as setTitle, ansi_showCursor as showCursor, ansi_strikethrough as strikethrough, ansi_underline as underline, ansi_writeClipboard as writeClipboard };
1440
- }
1949
+ declare function debounce<T extends (...args: unknown[]) => unknown>(func: T, wait: number, options?: DebounceOptions): T & {
1950
+ cancel: () => void;
1951
+ };
1441
1952
 
1442
- export { App, type AppOptions, BLOCK, BORDER_CHARS, BOX, BRAILLE_DOTS, BRAILLE_OFFSET, BRAILLE_SPIN, type BarSet, BarSets, type BorderChars, type BorderSet, BorderSets, type BorderStyle, CTRL_KEYS, type Cell, type Color, ColorDepth, type Constraint, type ContrastFailure, ESCAPE_SEQUENCES, type Edges, EventEmitter, type EventMap, type FocusEvent, FocusManager, type Focusable, HORIZONTAL_BAR_SYMBOLS, InputParser, type KeyEvent, type Layer, LayerManager, type LayoutNode, type LineSet, LineSets, type MouseEvent, type NamedColor, type Rect, Renderer, type ResizeEvent, type RootWidget, SPECIAL_KEYS, Screen, type ScrollbarSet, ScrollbarSets, Shade, type Size, type Style, Terminal, type TerminalOptions, type TestScreen, VERTICAL_BAR_SYMBOLS, ansi, borderSize, caps, cellsEqual, colorToAnsiBg, colorToAnsiFg, colorToRgb, computeLayout, containsPoint, contrastRatio, createKeyEvent, createLayoutNode, createTestScreen, defaultStyle, detectColorDepth, emptyCell, emptyRect, fill, getBorderChars, intersectRect, isMouseSequence, length, max, mergeStyles, min, normalizeEdges, parseColor, parseMouseEvent, percentage, ratio, relativeLuminance, renderFallback, shouldUseFallback, shrinkRect, splitRect, stringWidth, stripAnsi, styleToCellAttrs, testScreenClear, testScreenGetCell, testScreenSetCell, testScreenSetString, testScreenToString, truncate, unionRect, validateThemeContrast, wcagLevel, wordWrap, writeClipboard };
1953
+ export { App, type AppOptions, BLOCK, BORDER_CHARS, BOX, BRAILLE_DOTS, BRAILLE_OFFSET, BRAILLE_SPIN, type BarSet, BarSets, type BorderChars, type BorderSet, BorderSets, type BorderStyle, CTRL_KEYS, type Cell, type Chord, ChordMatcher, type ChordMatcherOptions, type Color, ColorDepth, Constraint, type ContrastFailure, type DebounceOptions, Dim, ESCAPE_SEQUENCES, type Edges, EventEmitter, type EventMap, FillConstraint, Flex, type FocusEvent, FocusManager, type Focusable, HORIZONTAL_BAR_SYMBOLS, InputParser, type KeyEvent, type Layer, LayerManager, type LayoutNode, LengthConstraint, type LineSet, LineSets, LiveRender, MaxConstraint, MinConstraint, type MouseEvent, MouseGestures, type MouseGesturesOptions, type NamedColor, PercentageConstraint, Pos, type Rect, RenderHook, Renderer, type ResizeEvent, type ResolvableNode, type RootWidget, SPECIAL_KEYS, Screen, type ScrollbarSet, ScrollbarSets, Shade, type Size, type Style, Terminal, type TerminalOptions, type TestScreen, VERTICAL_BAR_SYMBOLS, ansi, bell, borderSize, caps, cellsEqual, clipboard, colorToAnsiBg, colorToAnsiFg, colorToRgb, computeLayout, containsPoint, contrastRatio, createInlineViewport, createKeyEvent, createLayoutNode, createTestScreen, debounce, defaultStyle, detectColorDepth, emptyCell, emptyRect, getBorderChars, hasLayoutChanges, intersectRect, invalidateLayout, isMouseSequence, mergeBorders, mergeStyles, normalizeEdges, normalizeNavigationKey, parseColor, parseMouseEvent, prefersHighContrast, prefersReducedMotion, readClipboard, relativeLuminance, renderFallback, renderInlineToTerminal, resolveConstraints, resolveLayoutVariables, shouldUseColor, shouldUseFallback, shrinkRect, stringWidth, stripAnsi, styleToCellAttrs, testScreenClear, testScreenGetCell, testScreenSetCell, testScreenSetString, testScreenToString, truncate, unionRect, validateThemeContrast, wcagLevel, wordWrap, writeClipboard };