@claude-code-kit/ink-renderer 0.1.0

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.
@@ -0,0 +1,2124 @@
1
+ import * as React from 'react';
2
+ import React__default, { ReactNode, PropsWithChildren, Ref } from 'react';
3
+ import { Boxes, BoxStyle } from 'cli-boxes';
4
+ import { Except } from 'type-fest';
5
+ import * as react_jsx_runtime from 'react/jsx-runtime';
6
+ import { EventEmitter as EventEmitter$1 } from 'events';
7
+
8
+ type Cursor = {
9
+ x: number;
10
+ y: number;
11
+ visible: boolean;
12
+ };
13
+
14
+ type Point$1 = {
15
+ x: number;
16
+ y: number;
17
+ };
18
+ type Size = {
19
+ width: number;
20
+ height: number;
21
+ };
22
+ type Rectangle = Point$1 & Size;
23
+ declare function clamp(value: number, min?: number, max?: number): number;
24
+
25
+ declare class Event {
26
+ private _didStopImmediatePropagation;
27
+ didStopImmediatePropagation(): boolean;
28
+ stopImmediatePropagation(): void;
29
+ }
30
+
31
+ type EventPhase = 'none' | 'capturing' | 'at_target' | 'bubbling';
32
+ type TerminalEventInit = {
33
+ bubbles?: boolean;
34
+ cancelable?: boolean;
35
+ };
36
+ /**
37
+ * Base class for all terminal events with DOM-style propagation.
38
+ *
39
+ * Extends Event so existing event types (ClickEvent, InputEvent,
40
+ * TerminalFocusEvent) share a common ancestor and can migrate later.
41
+ *
42
+ * Mirrors the browser's Event API: target, currentTarget, eventPhase,
43
+ * stopPropagation(), preventDefault(), timeStamp.
44
+ */
45
+ declare class TerminalEvent extends Event {
46
+ readonly type: string;
47
+ readonly timeStamp: number;
48
+ readonly bubbles: boolean;
49
+ readonly cancelable: boolean;
50
+ private _target;
51
+ private _currentTarget;
52
+ private _eventPhase;
53
+ private _propagationStopped;
54
+ private _defaultPrevented;
55
+ constructor(type: string, init?: TerminalEventInit);
56
+ get target(): EventTarget | null;
57
+ get currentTarget(): EventTarget | null;
58
+ get eventPhase(): EventPhase;
59
+ get defaultPrevented(): boolean;
60
+ stopPropagation(): void;
61
+ stopImmediatePropagation(): void;
62
+ preventDefault(): void;
63
+ /** @internal */
64
+ _setTarget(target: EventTarget): void;
65
+ /** @internal */
66
+ _setCurrentTarget(target: EventTarget | null): void;
67
+ /** @internal */
68
+ _setEventPhase(phase: EventPhase): void;
69
+ /** @internal */
70
+ _isPropagationStopped(): boolean;
71
+ /** @internal */
72
+ _isImmediatePropagationStopped(): boolean;
73
+ /**
74
+ * Hook for subclasses to do per-node setup before each handler fires.
75
+ * Default is a no-op.
76
+ */
77
+ _prepareForTarget(_target: EventTarget): void;
78
+ }
79
+ type EventTarget = {
80
+ parentNode: EventTarget | undefined;
81
+ _eventHandlers?: Record<string, unknown>;
82
+ };
83
+
84
+ /**
85
+ * Focus event for component focus changes.
86
+ *
87
+ * Dispatched when focus moves between elements. 'focus' fires on the
88
+ * newly focused element, 'blur' fires on the previously focused one.
89
+ * Both bubble, matching react-dom's use of focusin/focusout semantics
90
+ * so parent components can observe descendant focus changes.
91
+ */
92
+ declare class FocusEvent extends TerminalEvent {
93
+ readonly relatedTarget: EventTarget | null;
94
+ constructor(type: 'focus' | 'blur', relatedTarget?: EventTarget | null);
95
+ }
96
+
97
+ /**
98
+ * DOM-like focus manager for the Ink terminal UI.
99
+ *
100
+ * Pure state — tracks activeElement and a focus stack. Has no reference
101
+ * to the tree; callers pass the root when tree walks are needed.
102
+ *
103
+ * Stored on the root DOMElement so any node can reach it by walking
104
+ * parentNode (like browser's `node.ownerDocument`).
105
+ */
106
+ declare class FocusManager {
107
+ activeElement: DOMElement | null;
108
+ private dispatchFocusEvent;
109
+ private enabled;
110
+ private focusStack;
111
+ constructor(dispatchFocusEvent: (target: DOMElement, event: FocusEvent) => boolean);
112
+ focus(node: DOMElement): void;
113
+ blur(): void;
114
+ /**
115
+ * Called by the reconciler when a node is removed from the tree.
116
+ * Handles both the exact node and any focused descendant within
117
+ * the removed subtree. Dispatches blur and restores focus from stack.
118
+ */
119
+ handleNodeRemoved(node: DOMElement, root: DOMElement): void;
120
+ handleAutoFocus(node: DOMElement): void;
121
+ handleClickFocus(node: DOMElement): void;
122
+ enable(): void;
123
+ disable(): void;
124
+ focusNext(root: DOMElement): void;
125
+ focusPrevious(root: DOMElement): void;
126
+ private moveFocus;
127
+ }
128
+
129
+ declare const LayoutEdge: {
130
+ readonly All: "all";
131
+ readonly Horizontal: "horizontal";
132
+ readonly Vertical: "vertical";
133
+ readonly Left: "left";
134
+ readonly Right: "right";
135
+ readonly Top: "top";
136
+ readonly Bottom: "bottom";
137
+ readonly Start: "start";
138
+ readonly End: "end";
139
+ };
140
+ type LayoutEdge = (typeof LayoutEdge)[keyof typeof LayoutEdge];
141
+ declare const LayoutGutter: {
142
+ readonly All: "all";
143
+ readonly Column: "column";
144
+ readonly Row: "row";
145
+ };
146
+ type LayoutGutter = (typeof LayoutGutter)[keyof typeof LayoutGutter];
147
+ declare const LayoutDisplay: {
148
+ readonly Flex: "flex";
149
+ readonly None: "none";
150
+ };
151
+ type LayoutDisplay = (typeof LayoutDisplay)[keyof typeof LayoutDisplay];
152
+ declare const LayoutFlexDirection: {
153
+ readonly Row: "row";
154
+ readonly RowReverse: "row-reverse";
155
+ readonly Column: "column";
156
+ readonly ColumnReverse: "column-reverse";
157
+ };
158
+ type LayoutFlexDirection = (typeof LayoutFlexDirection)[keyof typeof LayoutFlexDirection];
159
+ declare const LayoutAlign: {
160
+ readonly Auto: "auto";
161
+ readonly Stretch: "stretch";
162
+ readonly FlexStart: "flex-start";
163
+ readonly Center: "center";
164
+ readonly FlexEnd: "flex-end";
165
+ };
166
+ type LayoutAlign = (typeof LayoutAlign)[keyof typeof LayoutAlign];
167
+ declare const LayoutJustify: {
168
+ readonly FlexStart: "flex-start";
169
+ readonly Center: "center";
170
+ readonly FlexEnd: "flex-end";
171
+ readonly SpaceBetween: "space-between";
172
+ readonly SpaceAround: "space-around";
173
+ readonly SpaceEvenly: "space-evenly";
174
+ };
175
+ type LayoutJustify = (typeof LayoutJustify)[keyof typeof LayoutJustify];
176
+ declare const LayoutWrap: {
177
+ readonly NoWrap: "nowrap";
178
+ readonly Wrap: "wrap";
179
+ readonly WrapReverse: "wrap-reverse";
180
+ };
181
+ type LayoutWrap = (typeof LayoutWrap)[keyof typeof LayoutWrap];
182
+ declare const LayoutPositionType: {
183
+ readonly Relative: "relative";
184
+ readonly Absolute: "absolute";
185
+ };
186
+ type LayoutPositionType = (typeof LayoutPositionType)[keyof typeof LayoutPositionType];
187
+ declare const LayoutOverflow: {
188
+ readonly Visible: "visible";
189
+ readonly Hidden: "hidden";
190
+ readonly Scroll: "scroll";
191
+ };
192
+ type LayoutOverflow = (typeof LayoutOverflow)[keyof typeof LayoutOverflow];
193
+ type LayoutMeasureFunc = (width: number, widthMode: LayoutMeasureMode) => {
194
+ width: number;
195
+ height: number;
196
+ };
197
+ declare const LayoutMeasureMode: {
198
+ readonly Undefined: "undefined";
199
+ readonly Exactly: "exactly";
200
+ readonly AtMost: "at-most";
201
+ };
202
+ type LayoutMeasureMode = (typeof LayoutMeasureMode)[keyof typeof LayoutMeasureMode];
203
+ type LayoutNode = {
204
+ insertChild(child: LayoutNode, index: number): void;
205
+ removeChild(child: LayoutNode): void;
206
+ getChildCount(): number;
207
+ getParent(): LayoutNode | null;
208
+ calculateLayout(width?: number, height?: number): void;
209
+ setMeasureFunc(fn: LayoutMeasureFunc): void;
210
+ unsetMeasureFunc(): void;
211
+ markDirty(): void;
212
+ getComputedLeft(): number;
213
+ getComputedTop(): number;
214
+ getComputedWidth(): number;
215
+ getComputedHeight(): number;
216
+ getComputedBorder(edge: LayoutEdge): number;
217
+ getComputedPadding(edge: LayoutEdge): number;
218
+ setWidth(value: number): void;
219
+ setWidthPercent(value: number): void;
220
+ setWidthAuto(): void;
221
+ setHeight(value: number): void;
222
+ setHeightPercent(value: number): void;
223
+ setHeightAuto(): void;
224
+ setMinWidth(value: number): void;
225
+ setMinWidthPercent(value: number): void;
226
+ setMinHeight(value: number): void;
227
+ setMinHeightPercent(value: number): void;
228
+ setMaxWidth(value: number): void;
229
+ setMaxWidthPercent(value: number): void;
230
+ setMaxHeight(value: number): void;
231
+ setMaxHeightPercent(value: number): void;
232
+ setFlexDirection(dir: LayoutFlexDirection): void;
233
+ setFlexGrow(value: number): void;
234
+ setFlexShrink(value: number): void;
235
+ setFlexBasis(value: number): void;
236
+ setFlexBasisPercent(value: number): void;
237
+ setFlexWrap(wrap: LayoutWrap): void;
238
+ setAlignItems(align: LayoutAlign): void;
239
+ setAlignSelf(align: LayoutAlign): void;
240
+ setJustifyContent(justify: LayoutJustify): void;
241
+ setDisplay(display: LayoutDisplay): void;
242
+ getDisplay(): LayoutDisplay;
243
+ setPositionType(type: LayoutPositionType): void;
244
+ setPosition(edge: LayoutEdge, value: number): void;
245
+ setPositionPercent(edge: LayoutEdge, value: number): void;
246
+ setOverflow(overflow: LayoutOverflow): void;
247
+ setMargin(edge: LayoutEdge, value: number): void;
248
+ setPadding(edge: LayoutEdge, value: number): void;
249
+ setBorder(edge: LayoutEdge, value: number): void;
250
+ setGap(gutter: LayoutGutter, value: number): void;
251
+ free(): void;
252
+ freeRecursive(): void;
253
+ };
254
+
255
+ declare class CharPool {
256
+ private strings;
257
+ private stringMap;
258
+ private ascii;
259
+ intern(char: string): number;
260
+ get(index: number): string;
261
+ }
262
+ declare class HyperlinkPool {
263
+ private strings;
264
+ private stringMap;
265
+ intern(hyperlink: string | undefined): number;
266
+ get(id: number): string | undefined;
267
+ }
268
+ /**
269
+ * Screen uses a packed Int32Array instead of Cell objects to eliminate GC
270
+ * pressure. For a 200x120 screen, this avoids allocating 24,000 objects.
271
+ *
272
+ * Cell data is stored as 2 Int32s per cell in a single contiguous array:
273
+ * word0: charId (full 32 bits — index into CharPool)
274
+ * word1: styleId[31:17] | hyperlinkId[16:2] | width[1:0]
275
+ *
276
+ * This layout halves memory accesses in diffEach (2 int loads vs 4) and
277
+ * enables future SIMD comparison via Bun.indexOfFirstDifference.
278
+ */
279
+ type Screen = Size & {
280
+ cells: Int32Array;
281
+ cells64: BigInt64Array;
282
+ charPool: CharPool;
283
+ hyperlinkPool: HyperlinkPool;
284
+ emptyStyleId: number;
285
+ /**
286
+ * Bounding box of cells that were written to (not blitted) during rendering.
287
+ * Used by diff() to limit iteration to only the region that could have changed.
288
+ */
289
+ damage: Rectangle | undefined;
290
+ /**
291
+ * Per-cell noSelect bitmap — 1 byte per cell, 1 = exclude from text
292
+ * selection (copy + highlight). Used by <NoSelect> to mark gutters
293
+ * (line numbers, diff sigils) so click-drag over a diff yields clean
294
+ * copyable code. Fully reset each frame in resetScreen; blitRegion
295
+ * copies it alongside cells so the blit optimization preserves marks.
296
+ */
297
+ noSelect: Uint8Array;
298
+ /**
299
+ * Per-ROW soft-wrap continuation marker. softWrap[r]=N>0 means row r
300
+ * is a word-wrap continuation of row r-1 (the `\n` before it was
301
+ * inserted by wrapAnsi, not in the source), and row r-1's written
302
+ * content ends at absolute column N (exclusive — cells [0..N) are the
303
+ * fragment, past N is unwritten padding). 0 means row r is NOT a
304
+ * continuation (hard newline or first row). Selection copy checks
305
+ * softWrap[r]>0 to join row r onto row r-1 without a newline, and
306
+ * reads softWrap[r+1] to know row r's content end when row r+1
307
+ * continues from it. The content-end column is needed because an
308
+ * unwritten cell and a written-unstyled-space are indistinguishable in
309
+ * the packed typed array (both all-zero) — without it we'd either drop
310
+ * the word-separator space (trim) or include trailing padding (no
311
+ * trim). This encoding (continuation-on-self, prev-content-end-here)
312
+ * is chosen so shiftRows preserves the is-continuation semantics: when
313
+ * row r scrolls off the top and row r+1 shifts to row r, sw[r] gets
314
+ * old sw[r+1] — which correctly says the new row r is a continuation
315
+ * of what's now in scrolledOffAbove. Reset each frame; copied by
316
+ * blitRegion/shiftRows.
317
+ */
318
+ softWrap: Int32Array;
319
+ };
320
+
321
+ type BorderTextOptions = {
322
+ content: string;
323
+ position: 'top' | 'bottom';
324
+ align: 'start' | 'end' | 'center';
325
+ offset?: number;
326
+ };
327
+ declare const CUSTOM_BORDER_STYLES: {
328
+ readonly dashed: {
329
+ readonly top: "╌";
330
+ readonly left: "╎";
331
+ readonly right: "╎";
332
+ readonly bottom: "╌";
333
+ readonly topLeft: " ";
334
+ readonly topRight: " ";
335
+ readonly bottomLeft: " ";
336
+ readonly bottomRight: " ";
337
+ };
338
+ };
339
+ type BorderStyle = keyof Boxes | keyof typeof CUSTOM_BORDER_STYLES | BoxStyle;
340
+
341
+ type RGBColor = `rgb(${number},${number},${number})`;
342
+ type HexColor = `#${string}`;
343
+ type Ansi256Color = `ansi256(${number})`;
344
+ type AnsiColor = 'ansi:black' | 'ansi:red' | 'ansi:green' | 'ansi:yellow' | 'ansi:blue' | 'ansi:magenta' | 'ansi:cyan' | 'ansi:white' | 'ansi:blackBright' | 'ansi:redBright' | 'ansi:greenBright' | 'ansi:yellowBright' | 'ansi:blueBright' | 'ansi:magentaBright' | 'ansi:cyanBright' | 'ansi:whiteBright';
345
+ /** Raw color value or theme key. The `(string & {})` allows theme keys like
346
+ * "suggestion", "success", etc. to pass through while preserving autocomplete
347
+ * for structured color formats. */
348
+ type Color = RGBColor | HexColor | Ansi256Color | AnsiColor | string;
349
+ /**
350
+ * Structured text styling properties.
351
+ * Used to style text without relying on ANSI string transforms.
352
+ * Colors are raw values - theme resolution happens at the component layer.
353
+ */
354
+ type TextStyles = {
355
+ readonly color?: Color;
356
+ readonly backgroundColor?: Color;
357
+ readonly dim?: boolean;
358
+ readonly bold?: boolean;
359
+ readonly italic?: boolean;
360
+ readonly underline?: boolean;
361
+ readonly strikethrough?: boolean;
362
+ readonly inverse?: boolean;
363
+ };
364
+ type Styles = {
365
+ readonly textWrap?: 'wrap' | 'wrap-trim' | 'end' | 'middle' | 'truncate-end' | 'truncate' | 'truncate-middle' | 'truncate-start';
366
+ readonly position?: 'absolute' | 'relative';
367
+ readonly top?: number | `${number}%`;
368
+ readonly bottom?: number | `${number}%`;
369
+ readonly left?: number | `${number}%`;
370
+ readonly right?: number | `${number}%`;
371
+ /**
372
+ * Size of the gap between an element's columns.
373
+ */
374
+ readonly columnGap?: number;
375
+ /**
376
+ * Size of the gap between element's rows.
377
+ */
378
+ readonly rowGap?: number;
379
+ /**
380
+ * Size of the gap between an element's columns and rows. Shorthand for `columnGap` and `rowGap`.
381
+ */
382
+ readonly gap?: number;
383
+ /**
384
+ * Margin on all sides. Equivalent to setting `marginTop`, `marginBottom`, `marginLeft` and `marginRight`.
385
+ */
386
+ readonly margin?: number;
387
+ /**
388
+ * Horizontal margin. Equivalent to setting `marginLeft` and `marginRight`.
389
+ */
390
+ readonly marginX?: number;
391
+ /**
392
+ * Vertical margin. Equivalent to setting `marginTop` and `marginBottom`.
393
+ */
394
+ readonly marginY?: number;
395
+ /**
396
+ * Top margin.
397
+ */
398
+ readonly marginTop?: number;
399
+ /**
400
+ * Bottom margin.
401
+ */
402
+ readonly marginBottom?: number;
403
+ /**
404
+ * Left margin.
405
+ */
406
+ readonly marginLeft?: number;
407
+ /**
408
+ * Right margin.
409
+ */
410
+ readonly marginRight?: number;
411
+ /**
412
+ * Padding on all sides. Equivalent to setting `paddingTop`, `paddingBottom`, `paddingLeft` and `paddingRight`.
413
+ */
414
+ readonly padding?: number;
415
+ /**
416
+ * Horizontal padding. Equivalent to setting `paddingLeft` and `paddingRight`.
417
+ */
418
+ readonly paddingX?: number;
419
+ /**
420
+ * Vertical padding. Equivalent to setting `paddingTop` and `paddingBottom`.
421
+ */
422
+ readonly paddingY?: number;
423
+ /**
424
+ * Top padding.
425
+ */
426
+ readonly paddingTop?: number;
427
+ /**
428
+ * Bottom padding.
429
+ */
430
+ readonly paddingBottom?: number;
431
+ /**
432
+ * Left padding.
433
+ */
434
+ readonly paddingLeft?: number;
435
+ /**
436
+ * Right padding.
437
+ */
438
+ readonly paddingRight?: number;
439
+ /**
440
+ * This property defines the ability for a flex item to grow if necessary.
441
+ * See [flex-grow](https://css-tricks.com/almanac/properties/f/flex-grow/).
442
+ */
443
+ readonly flexGrow?: number;
444
+ /**
445
+ * It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isn’t enough space on the row.
446
+ * See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
447
+ */
448
+ readonly flexShrink?: number;
449
+ /**
450
+ * It establishes the main-axis, thus defining the direction flex items are placed in the flex container.
451
+ * See [flex-direction](https://css-tricks.com/almanac/properties/f/flex-direction/).
452
+ */
453
+ readonly flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
454
+ /**
455
+ * It specifies the initial size of the flex item, before any available space is distributed according to the flex factors.
456
+ * See [flex-basis](https://css-tricks.com/almanac/properties/f/flex-basis/).
457
+ */
458
+ readonly flexBasis?: number | string;
459
+ /**
460
+ * It defines whether the flex items are forced in a single line or can be flowed into multiple lines. If set to multiple lines, it also defines the cross-axis which determines the direction new lines are stacked in.
461
+ * See [flex-wrap](https://css-tricks.com/almanac/properties/f/flex-wrap/).
462
+ */
463
+ readonly flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
464
+ /**
465
+ * The align-items property defines the default behavior for how items are laid out along the cross axis (perpendicular to the main axis).
466
+ * See [align-items](https://css-tricks.com/almanac/properties/a/align-items/).
467
+ */
468
+ readonly alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch';
469
+ /**
470
+ * It makes possible to override the align-items value for specific flex items.
471
+ * See [align-self](https://css-tricks.com/almanac/properties/a/align-self/).
472
+ */
473
+ readonly alignSelf?: 'flex-start' | 'center' | 'flex-end' | 'auto';
474
+ /**
475
+ * It defines the alignment along the main axis.
476
+ * See [justify-content](https://css-tricks.com/almanac/properties/j/justify-content/).
477
+ */
478
+ readonly justifyContent?: 'flex-start' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly' | 'center';
479
+ /**
480
+ * Width of the element in spaces.
481
+ * You can also set it in percent, which will calculate the width based on the width of parent element.
482
+ */
483
+ readonly width?: number | string;
484
+ /**
485
+ * Height of the element in lines (rows).
486
+ * You can also set it in percent, which will calculate the height based on the height of parent element.
487
+ */
488
+ readonly height?: number | string;
489
+ /**
490
+ * Sets a minimum width of the element.
491
+ */
492
+ readonly minWidth?: number | string;
493
+ /**
494
+ * Sets a minimum height of the element.
495
+ */
496
+ readonly minHeight?: number | string;
497
+ /**
498
+ * Sets a maximum width of the element.
499
+ */
500
+ readonly maxWidth?: number | string;
501
+ /**
502
+ * Sets a maximum height of the element.
503
+ */
504
+ readonly maxHeight?: number | string;
505
+ /**
506
+ * Set this property to `none` to hide the element.
507
+ */
508
+ readonly display?: 'flex' | 'none';
509
+ /**
510
+ * Add a border with a specified style.
511
+ * If `borderStyle` is `undefined` (which it is by default), no border will be added.
512
+ */
513
+ readonly borderStyle?: BorderStyle;
514
+ /**
515
+ * Determines whether top border is visible.
516
+ *
517
+ * @default true
518
+ */
519
+ readonly borderTop?: boolean;
520
+ /**
521
+ * Determines whether bottom border is visible.
522
+ *
523
+ * @default true
524
+ */
525
+ readonly borderBottom?: boolean;
526
+ /**
527
+ * Determines whether left border is visible.
528
+ *
529
+ * @default true
530
+ */
531
+ readonly borderLeft?: boolean;
532
+ /**
533
+ * Determines whether right border is visible.
534
+ *
535
+ * @default true
536
+ */
537
+ readonly borderRight?: boolean;
538
+ /**
539
+ * Change border color.
540
+ * Shorthand for setting `borderTopColor`, `borderRightColor`, `borderBottomColor` and `borderLeftColor`.
541
+ */
542
+ readonly borderColor?: Color;
543
+ /**
544
+ * Change top border color.
545
+ * Accepts raw color values (rgb, hex, ansi).
546
+ */
547
+ readonly borderTopColor?: Color;
548
+ /**
549
+ * Change bottom border color.
550
+ * Accepts raw color values (rgb, hex, ansi).
551
+ */
552
+ readonly borderBottomColor?: Color;
553
+ /**
554
+ * Change left border color.
555
+ * Accepts raw color values (rgb, hex, ansi).
556
+ */
557
+ readonly borderLeftColor?: Color;
558
+ /**
559
+ * Change right border color.
560
+ * Accepts raw color values (rgb, hex, ansi).
561
+ */
562
+ readonly borderRightColor?: Color;
563
+ /**
564
+ * Dim the border color.
565
+ * Shorthand for setting `borderTopDimColor`, `borderBottomDimColor`, `borderLeftDimColor` and `borderRightDimColor`.
566
+ *
567
+ * @default false
568
+ */
569
+ readonly borderDimColor?: boolean;
570
+ /**
571
+ * Dim the top border color.
572
+ *
573
+ * @default false
574
+ */
575
+ readonly borderTopDimColor?: boolean;
576
+ /**
577
+ * Dim the bottom border color.
578
+ *
579
+ * @default false
580
+ */
581
+ readonly borderBottomDimColor?: boolean;
582
+ /**
583
+ * Dim the left border color.
584
+ *
585
+ * @default false
586
+ */
587
+ readonly borderLeftDimColor?: boolean;
588
+ /**
589
+ * Dim the right border color.
590
+ *
591
+ * @default false
592
+ */
593
+ readonly borderRightDimColor?: boolean;
594
+ /**
595
+ * Add text within the border. Only applies to top or bottom borders.
596
+ */
597
+ readonly borderText?: BorderTextOptions;
598
+ /**
599
+ * Background color for the box. Fills the interior with background-colored
600
+ * spaces and is inherited by child text nodes as their default background.
601
+ */
602
+ readonly backgroundColor?: Color;
603
+ /**
604
+ * Fill the box's interior (padding included) with spaces before
605
+ * rendering children, so nothing behind it shows through. Like
606
+ * `backgroundColor` but without emitting any SGR — the terminal's
607
+ * default background is used. Useful for absolute-positioned overlays
608
+ * where Box padding/gaps would otherwise be transparent.
609
+ */
610
+ readonly opaque?: boolean;
611
+ /**
612
+ * Behavior for an element's overflow in both directions.
613
+ * 'scroll' constrains the container's size (children do not expand it)
614
+ * and enables scrollTop-based virtualized scrolling at render time.
615
+ *
616
+ * @default 'visible'
617
+ */
618
+ readonly overflow?: 'visible' | 'hidden' | 'scroll';
619
+ /**
620
+ * Behavior for an element's overflow in horizontal direction.
621
+ *
622
+ * @default 'visible'
623
+ */
624
+ readonly overflowX?: 'visible' | 'hidden' | 'scroll';
625
+ /**
626
+ * Behavior for an element's overflow in vertical direction.
627
+ *
628
+ * @default 'visible'
629
+ */
630
+ readonly overflowY?: 'visible' | 'hidden' | 'scroll';
631
+ /**
632
+ * Exclude this box's cells from text selection in fullscreen mode.
633
+ * Cells inside this region are skipped by both the selection highlight
634
+ * and the copied text — useful for fencing off gutters (line numbers,
635
+ * diff sigils) so click-drag over a diff yields clean copyable code.
636
+ * Only affects alt-screen text selection; no-op otherwise.
637
+ *
638
+ * `'from-left-edge'` extends the exclusion from column 0 to the box's
639
+ * right edge for every row it occupies — this covers any upstream
640
+ * indentation (tool message prefix, tree lines) so a multi-row drag
641
+ * doesn't pick up leading whitespace from middle rows.
642
+ */
643
+ readonly noSelect?: boolean | 'from-left-edge';
644
+ };
645
+
646
+ type InkNode = {
647
+ parentNode: DOMElement | undefined;
648
+ yogaNode?: LayoutNode;
649
+ style: Styles;
650
+ };
651
+ type TextName = '#text';
652
+ type ElementNames = 'ink-root' | 'ink-box' | 'ink-text' | 'ink-virtual-text' | 'ink-link' | 'ink-progress' | 'ink-raw-ansi';
653
+ type NodeNames = ElementNames | TextName;
654
+ type DOMElement = {
655
+ nodeName: ElementNames;
656
+ attributes: Record<string, DOMNodeAttribute>;
657
+ childNodes: DOMNode[];
658
+ textStyles?: TextStyles;
659
+ onComputeLayout?: () => void;
660
+ onRender?: () => void;
661
+ onImmediateRender?: () => void;
662
+ hasRenderedContent?: boolean;
663
+ dirty: boolean;
664
+ isHidden?: boolean;
665
+ _eventHandlers?: Record<string, unknown>;
666
+ scrollTop?: number;
667
+ pendingScrollDelta?: number;
668
+ scrollClampMin?: number;
669
+ scrollClampMax?: number;
670
+ scrollHeight?: number;
671
+ scrollViewportHeight?: number;
672
+ scrollViewportTop?: number;
673
+ stickyScroll?: boolean;
674
+ scrollAnchor?: {
675
+ el: DOMElement;
676
+ offset: number;
677
+ };
678
+ focusManager?: FocusManager;
679
+ debugOwnerChain?: string[];
680
+ } & InkNode;
681
+ type TextNode = {
682
+ nodeName: TextName;
683
+ nodeValue: string;
684
+ } & InkNode;
685
+ type DOMNode<T = {
686
+ nodeName: NodeNames;
687
+ }> = T extends {
688
+ nodeName: infer U;
689
+ } ? U extends '#text' ? TextNode : DOMElement : never;
690
+ type DOMNodeAttribute = boolean | string | number;
691
+
692
+ type ScrollHint = {
693
+ top: number;
694
+ bottom: number;
695
+ delta: number;
696
+ };
697
+
698
+ type Frame = {
699
+ readonly screen: Screen;
700
+ readonly viewport: Size;
701
+ readonly cursor: Cursor;
702
+ /** DECSTBM scroll optimization hint (alt-screen only, null otherwise). */
703
+ readonly scrollHint?: ScrollHint | null;
704
+ /** A ScrollBox has remaining pendingScrollDelta — schedule another frame. */
705
+ readonly scrollDrainPending?: boolean;
706
+ };
707
+ type FlickerReason = 'resize' | 'offscreen' | 'clear';
708
+ type FrameEvent = {
709
+ durationMs: number;
710
+ /** Phase breakdown in ms + patch count. Populated when the ink instance
711
+ * has frame-timing instrumentation enabled (via onFrame wiring). */
712
+ phases?: {
713
+ /** createRenderer output: DOM → yoga layout → screen buffer */
714
+ renderer: number;
715
+ /** LogUpdate.render(): screen diff → Patch[] (the hot path this PR optimizes) */
716
+ diff: number;
717
+ /** optimize(): patch merge/dedupe */
718
+ optimize: number;
719
+ /** writeDiffToTerminal(): serialize patches → ANSI → stdout */
720
+ write: number;
721
+ /** Pre-optimize patch count (proxy for how much changed this frame) */
722
+ patches: number;
723
+ /** yoga calculateLayout() time (runs in resetAfterCommit, before onRender) */
724
+ yoga: number;
725
+ /** React reconcile time: scrollMutated → resetAfterCommit. 0 if no commit. */
726
+ commit: number;
727
+ /** layoutNode() calls this frame (recursive, includes cache-hit returns) */
728
+ yogaVisited: number;
729
+ /** measureFunc (text wrap/width) calls — the expensive part */
730
+ yogaMeasured: number;
731
+ /** early returns via _hasL single-slot cache */
732
+ yogaCacheHits: number;
733
+ /** total yoga Node instances alive (create - free). Growth = leak. */
734
+ yogaLive: number;
735
+ };
736
+ flickers: Array<{
737
+ desiredHeight: number;
738
+ availableHeight: number;
739
+ reason: FlickerReason;
740
+ }>;
741
+ };
742
+
743
+ /**
744
+ * Keyboard input parser - converts terminal input to key events
745
+ *
746
+ * Uses the termio tokenizer for escape sequence boundary detection,
747
+ * then interprets sequences as keypresses.
748
+ */
749
+
750
+ /**
751
+ * A response sequence received from the terminal (not a keypress).
752
+ * Emitted in answer to queries like DECRQM, DA1, OSC 11, etc.
753
+ */
754
+ type TerminalResponse =
755
+ /** DECRPM: answer to DECRQM (request DEC private mode status) */
756
+ {
757
+ type: 'decrpm';
758
+ mode: number;
759
+ status: number;
760
+ }
761
+ /** DA1: primary device attributes (used as a universal sentinel) */
762
+ | {
763
+ type: 'da1';
764
+ params: number[];
765
+ }
766
+ /** DA2: secondary device attributes (terminal version info) */
767
+ | {
768
+ type: 'da2';
769
+ params: number[];
770
+ }
771
+ /** Kitty keyboard protocol: current flags (answer to CSI ? u) */
772
+ | {
773
+ type: 'kittyKeyboard';
774
+ flags: number;
775
+ }
776
+ /** DSR: cursor position report (answer to CSI 6 n) */
777
+ | {
778
+ type: 'cursorPosition';
779
+ row: number;
780
+ col: number;
781
+ }
782
+ /** OSC response: generic operating-system-command reply (e.g. OSC 11 bg color) */
783
+ | {
784
+ type: 'osc';
785
+ code: number;
786
+ data: string;
787
+ }
788
+ /** XTVERSION: terminal name/version string (answer to CSI > 0 q).
789
+ * Example values: "xterm.js(5.5.0)", "ghostty 1.2.0", "iTerm2 3.6". */
790
+ | {
791
+ type: 'xtversion';
792
+ name: string;
793
+ };
794
+ type ParsedKey = {
795
+ kind: 'key';
796
+ fn: boolean;
797
+ name: string | undefined;
798
+ ctrl: boolean;
799
+ meta: boolean;
800
+ shift: boolean;
801
+ option: boolean;
802
+ super: boolean;
803
+ sequence: string | undefined;
804
+ raw: string | undefined;
805
+ code?: string;
806
+ isPasted: boolean;
807
+ };
808
+
809
+ /** Position of a match within a rendered message, relative to the message's
810
+ * own bounding box (row 0 = message top). Stable across scroll — to
811
+ * highlight on the real screen, add the message's screen-row offset. */
812
+ type MatchPosition = {
813
+ row: number;
814
+ col: number;
815
+ /** Number of CELLS the match spans (= query.length for ASCII, more
816
+ * for wide chars in the query). */
817
+ len: number;
818
+ };
819
+
820
+ /**
821
+ * Text selection state for fullscreen mode.
822
+ *
823
+ * Tracks a linear selection in screen-buffer coordinates (0-indexed col/row).
824
+ * Selection is line-based: cells from (startCol, startRow) through
825
+ * (endCol, endRow) inclusive, wrapping across line boundaries. This matches
826
+ * terminal-native selection behavior (not rectangular/block).
827
+ *
828
+ * The selection is stored as ANCHOR (where the drag started) + FOCUS (where
829
+ * the cursor is now). The rendered highlight normalizes to start ≤ end.
830
+ */
831
+
832
+ type Point = {
833
+ col: number;
834
+ row: number;
835
+ };
836
+ type SelectionState = {
837
+ /** Where the mouse-down occurred. Null when no selection. */
838
+ anchor: Point | null;
839
+ /** Current drag position (updated on mouse-move while dragging). */
840
+ focus: Point | null;
841
+ /** True between mouse-down and mouse-up. */
842
+ isDragging: boolean;
843
+ /** For word/line mode: the initial word/line bounds from the first
844
+ * multi-click. Drag extends from this span to the word/line at the
845
+ * current mouse position so the original word/line stays selected
846
+ * even when dragging backward past it. Null ⇔ char mode. The kind
847
+ * tells extendSelection whether to snap to word or line boundaries. */
848
+ anchorSpan: {
849
+ lo: Point;
850
+ hi: Point;
851
+ kind: 'word' | 'line';
852
+ } | null;
853
+ /** Text from rows that scrolled out ABOVE the viewport during
854
+ * drag-to-scroll. The screen buffer only holds the current viewport,
855
+ * so without this accumulator, dragging down past the bottom edge
856
+ * loses the top of the selection once the anchor clamps. Prepended
857
+ * to the on-screen text by getSelectedText. Reset on start/clear. */
858
+ scrolledOffAbove: string[];
859
+ /** Symmetric: rows scrolled out BELOW when dragging up. Appended. */
860
+ scrolledOffBelow: string[];
861
+ /** Soft-wrap bits parallel to scrolledOffAbove — true means the row
862
+ * is a continuation of the one before it (the `\n` was inserted by
863
+ * word-wrap, not in the source). Captured alongside the text at
864
+ * scroll time since the screen's softWrap bitmap shifts with content.
865
+ * getSelectedText uses these to join wrapped rows back into logical
866
+ * lines. */
867
+ scrolledOffAboveSW: boolean[];
868
+ /** Parallel to scrolledOffBelow. */
869
+ scrolledOffBelowSW: boolean[];
870
+ /** Pre-clamp anchor row. Set when shiftSelection clamps anchor so a
871
+ * reverse scroll can restore the true position and pop accumulators.
872
+ * Without this, PgDn (clamps anchor) → PgUp leaves anchor at the wrong
873
+ * row AND scrolledOffAbove stale — highlight ≠ copy. Undefined when
874
+ * anchor is in-bounds (no clamp debt). Cleared on start/clear. */
875
+ virtualAnchorRow?: number;
876
+ /** Same for focus. */
877
+ virtualFocusRow?: number;
878
+ /** True if the mouse-down that started this selection had the alt
879
+ * modifier set (SGR button bit 0x08). On macOS xterm.js this is a
880
+ * signal that VS Code's macOptionClickForcesSelection is OFF — if it
881
+ * were on, xterm.js would have consumed the event for native selection
882
+ * and we'd never receive it. Used by the footer to show the right hint. */
883
+ lastPressHadAlt: boolean;
884
+ };
885
+ /** Semantic keyboard focus moves. See moveSelectionFocus in ink.tsx for
886
+ * how screen bounds + row-wrap are applied. */
887
+ type FocusMove = 'left' | 'right' | 'up' | 'down' | 'lineStart' | 'lineEnd';
888
+
889
+ type Options$1 = {
890
+ stdout: NodeJS.WriteStream;
891
+ stdin: NodeJS.ReadStream;
892
+ stderr: NodeJS.WriteStream;
893
+ exitOnCtrlC: boolean;
894
+ patchConsole: boolean;
895
+ waitUntilExit?: () => Promise<void>;
896
+ onFrame?: (event: FrameEvent) => void;
897
+ };
898
+ declare class Ink {
899
+ private readonly options;
900
+ private readonly log;
901
+ private readonly terminal;
902
+ private scheduleRender;
903
+ private isUnmounted;
904
+ private isPaused;
905
+ private readonly container;
906
+ private rootNode;
907
+ readonly focusManager: FocusManager;
908
+ private renderer;
909
+ private readonly stylePool;
910
+ private charPool;
911
+ private hyperlinkPool;
912
+ private exitPromise?;
913
+ private restoreConsole?;
914
+ private restoreStderr?;
915
+ private readonly unsubscribeTTYHandlers?;
916
+ private terminalColumns;
917
+ private terminalRows;
918
+ private currentNode;
919
+ private frontFrame;
920
+ private backFrame;
921
+ private lastPoolResetTime;
922
+ private drainTimer;
923
+ private lastYogaCounters;
924
+ private altScreenParkPatch;
925
+ readonly selection: SelectionState;
926
+ private searchHighlightQuery;
927
+ private searchPositions;
928
+ private readonly selectionListeners;
929
+ private readonly hoveredNodes;
930
+ private altScreenActive;
931
+ private altScreenMouseTracking;
932
+ private prevFrameContaminated;
933
+ private needsEraseBeforePaint;
934
+ private cursorDeclaration;
935
+ private displayCursor;
936
+ constructor(options: Options$1);
937
+ private handleResume;
938
+ private handleResize;
939
+ resolveExitPromise: () => void;
940
+ rejectExitPromise: (reason?: Error) => void;
941
+ unsubscribeExit: () => void;
942
+ /**
943
+ * Pause Ink and hand the terminal over to an external TUI (e.g. git
944
+ * commit editor). In non-fullscreen mode this enters the alt screen;
945
+ * in fullscreen mode we're already in alt so we just clear it.
946
+ * Call `exitAlternateScreen()` when done to restore Ink.
947
+ */
948
+ enterAlternateScreen(): void;
949
+ /**
950
+ * Resume Ink after an external TUI handoff with a full repaint.
951
+ * In non-fullscreen mode this exits the alt screen back to main;
952
+ * in fullscreen mode we re-enter alt and clear + repaint.
953
+ *
954
+ * The re-enter matters: terminal editors (vim, nano, less) write
955
+ * smcup/rmcup (?1049h/?1049l), so even though we started in alt,
956
+ * the editor's rmcup on exit drops us to main screen. Without
957
+ * re-entering, the 2J below wipes the user's main-screen scrollback
958
+ * and subsequent renders land in main — native terminal scroll
959
+ * returns, fullscreen scroll is dead.
960
+ */
961
+ exitAlternateScreen(): void;
962
+ onRender(): void;
963
+ pause(): void;
964
+ resume(): void;
965
+ /**
966
+ * Reset frame buffers so the next render writes the full screen from scratch.
967
+ * Call this before resume() when the terminal content has been corrupted by
968
+ * an external process (e.g. tmux, shell, full-screen TUI).
969
+ */
970
+ repaint(): void;
971
+ /**
972
+ * Clear the physical terminal and force a full redraw.
973
+ *
974
+ * The traditional readline ctrl+l — clears the visible screen and
975
+ * redraws the current content. Also the recovery path when the terminal
976
+ * was cleared externally (macOS Cmd+K) and Ink's diff engine thinks
977
+ * unchanged cells don't need repainting. Scrollback is preserved.
978
+ */
979
+ forceRedraw(): void;
980
+ /**
981
+ * Mark the previous frame as untrustworthy for blit, forcing the next
982
+ * render to do a full-damage diff instead of the per-node fast path.
983
+ *
984
+ * Lighter than forceRedraw() — no screen clear, no extra write. Call
985
+ * from a useLayoutEffect cleanup when unmounting a tall overlay: the
986
+ * blit fast path can copy stale cells from the overlay frame into rows
987
+ * the shrunken layout no longer reaches, leaving a ghost title/divider.
988
+ * onRender resets the flag at frame end so it's one-shot.
989
+ */
990
+ invalidatePrevFrame(): void;
991
+ /**
992
+ * Called by the <AlternateScreen> component on mount/unmount.
993
+ * Controls cursor.y clamping in the renderer and gates alt-screen-aware
994
+ * behavior in SIGCONT/resize/unmount handlers. Repaints on change so
995
+ * the first alt-screen frame (and first main-screen frame on exit) is
996
+ * a full redraw with no stale diff state.
997
+ */
998
+ setAltScreenActive(active: boolean, mouseTracking?: boolean): void;
999
+ get isAltScreenActive(): boolean;
1000
+ /**
1001
+ * Re-assert terminal modes after a gap (>5s stdin silence or event-loop
1002
+ * stall). Catches tmux detach→attach, ssh reconnect, and laptop
1003
+ * sleep/wake — none of which send SIGCONT. The terminal may reset DEC
1004
+ * private modes on reconnect; this method restores them.
1005
+ *
1006
+ * Always re-asserts extended key reporting and mouse tracking. Mouse
1007
+ * tracking is idempotent (DEC private mode set-when-set is a no-op). The
1008
+ * Kitty keyboard protocol is NOT — CSI >1u is a stack push, so we pop
1009
+ * first to keep depth balanced (pop on empty stack is a no-op per spec,
1010
+ * so after a terminal reset this still restores depth 0→1). Without the
1011
+ * pop, each >5s idle gap adds a stack entry, and the single pop on exit
1012
+ * or suspend can't drain them — the shell is left in CSI u mode where
1013
+ * Ctrl+C/Ctrl+D leak as escape sequences. The alt-screen
1014
+ * re-entry (ERASE_SCREEN + frame reset) is NOT idempotent — it blanks the
1015
+ * screen — so it's opt-in via includeAltScreen. The stdin-gap caller fires
1016
+ * on ordinary >5s idle + keypress and must not erase; the event-loop stall
1017
+ * detector fires on genuine sleep/wake and opts in. tmux attach / ssh
1018
+ * reconnect typically send a resize, which already covers alt-screen via
1019
+ * handleResize.
1020
+ */
1021
+ reassertTerminalModes: (includeAltScreen?: boolean) => void;
1022
+ /**
1023
+ * Mark this instance as unmounted so future unmount() calls early-return.
1024
+ * Called by gracefulShutdown's cleanupTerminalModes() after it has sent
1025
+ * EXIT_ALT_SCREEN but before the remaining terminal-reset sequences.
1026
+ * Without this, signal-exit's deferred ink.unmount() (triggered by
1027
+ * process.exit()) runs the full unmount path: onRender() + writeSync
1028
+ * cleanup block + updateContainerSync → AlternateScreen unmount cleanup.
1029
+ * The result is 2-3 redundant EXIT_ALT_SCREEN sequences landing on the
1030
+ * main screen AFTER printResumeHint(), which tmux (at least) interprets
1031
+ * as restoring the saved cursor position — clobbering the resume hint.
1032
+ */
1033
+ detachForShutdown(): void;
1034
+ /** @see drainStdin */
1035
+ drainStdin(): void;
1036
+ /**
1037
+ * Re-enter alt-screen, clear, home, re-enable mouse tracking, and reset
1038
+ * frame buffers so the next render repaints from scratch. Self-heal for
1039
+ * SIGCONT, resize, and stdin-gap/event-loop-stall (sleep/wake) — any of
1040
+ * which can leave the terminal in main-screen mode while altScreenActive
1041
+ * stays true. ENTER_ALT_SCREEN is a terminal-side no-op if already in alt.
1042
+ */
1043
+ private reenterAltScreen;
1044
+ /**
1045
+ * Seed prev/back frames with full-size BLANK screens (rows×cols of empty
1046
+ * cells, not 0×0). In alt-screen mode, next.screen.height is always
1047
+ * terminalRows; if prev.screen.height is 0 (emptyFrame's default),
1048
+ * log-update sees heightDelta > 0 ('growing') and calls renderFrameSlice,
1049
+ * whose trailing per-row CR+LF at the last row scrolls the alt screen,
1050
+ * permanently desyncing the virtual and physical cursors by 1 row.
1051
+ *
1052
+ * With a rows×cols blank prev, heightDelta === 0 → standard diffEach
1053
+ * → moveCursorTo (CSI cursorMove, no LF, no scroll).
1054
+ *
1055
+ * viewport.height = rows + 1 matches the renderer's alt-screen output,
1056
+ * preventing a spurious resize trigger on the first frame. cursor.y = 0
1057
+ * matches the physical cursor after ENTER_ALT_SCREEN + CSI H (home).
1058
+ */
1059
+ private resetFramesForAltScreen;
1060
+ /**
1061
+ * Copy the current selection to the clipboard without clearing the
1062
+ * highlight. Matches iTerm2's copy-on-select behavior where the selected
1063
+ * region stays visible after the automatic copy.
1064
+ */
1065
+ copySelectionNoClear(): string;
1066
+ /**
1067
+ * Copy the current text selection to the system clipboard via OSC 52
1068
+ * and clear the selection. Returns the copied text (empty if no selection).
1069
+ */
1070
+ copySelection(): string;
1071
+ /** Clear the current text selection without copying. */
1072
+ clearTextSelection(): void;
1073
+ /**
1074
+ * Set the search highlight query. Non-empty → all visible occurrences
1075
+ * are inverted (SGR 7) on the next frame; first one also underlined.
1076
+ * Empty → clears (prevFrameContaminated handles the frame after). Same
1077
+ * damage-tracking machinery as selection — setCellStyleId doesn't track
1078
+ * damage, so the overlay forces full-frame damage while active.
1079
+ */
1080
+ setSearchHighlight(query: string): void;
1081
+ /** Paint an EXISTING DOM subtree to a fresh Screen at its natural
1082
+ * height, scan for query. Returns positions relative to the element's
1083
+ * bounding box (row 0 = element top).
1084
+ *
1085
+ * The element comes from the MAIN tree — built with all real
1086
+ * providers, yoga already computed. We paint it to a fresh buffer
1087
+ * with offsets so it lands at (0,0). Same paint path as the main
1088
+ * render. Zero drift. No second React root, no context bridge.
1089
+ *
1090
+ * ~1-2ms (paint only, no reconcile — the DOM is already built). */
1091
+ scanElementSubtree(el: DOMElement): MatchPosition[];
1092
+ /** Set the position-based highlight state. Every frame, writes CURRENT
1093
+ * style at positions[currentIdx] + rowOffset. null clears. The scan-
1094
+ * highlight (inverse on all matches) still runs — this overlays yellow
1095
+ * on top. rowOffset changes as the user scrolls (= message's current
1096
+ * screen-top); positions stay stable (message-relative). */
1097
+ setSearchPositions(state: {
1098
+ positions: MatchPosition[];
1099
+ rowOffset: number;
1100
+ currentIdx: number;
1101
+ } | null): void;
1102
+ /**
1103
+ * Set the selection highlight background color. Replaces the per-cell
1104
+ * SGR-7 inverse with a solid theme-aware bg (matches native terminal
1105
+ * selection). Accepts the same color formats as Text backgroundColor
1106
+ * (rgb(), ansi:name, #hex, ansi256()) — colorize() routes through
1107
+ * chalk so the tmux/xterm.js level clamps in colorize.ts apply and
1108
+ * the emitted SGR is correct for the current terminal.
1109
+ *
1110
+ * Called by React-land once theme is known (ScrollKeybindingHandler's
1111
+ * useEffect watching useTheme). Before that call, withSelectionBg
1112
+ * falls back to withInverse so selection still renders on the first
1113
+ * frame; the effect fires before any mouse input so the fallback is
1114
+ * unobservable in practice.
1115
+ */
1116
+ setSelectionBgColor(color: string): void;
1117
+ /**
1118
+ * Capture text from rows about to scroll out of the viewport during
1119
+ * drag-to-scroll. Must be called BEFORE the ScrollBox scrolls so the
1120
+ * screen buffer still holds the outgoing content. Accumulated into
1121
+ * the selection state and joined back in by getSelectedText.
1122
+ */
1123
+ captureScrolledRows(firstRow: number, lastRow: number, side: 'above' | 'below'): void;
1124
+ /**
1125
+ * Shift anchor AND focus by dRow, clamped to [minRow, maxRow]. Used by
1126
+ * keyboard scroll handlers (PgUp/PgDn etc.) so the highlight tracks the
1127
+ * content instead of disappearing. Unlike shiftAnchor (drag-to-scroll),
1128
+ * this moves BOTH endpoints — the user isn't holding the mouse at one
1129
+ * edge. Supplies screen.width for the col-reset-on-clamp boundary.
1130
+ */
1131
+ shiftSelectionForScroll(dRow: number, minRow: number, maxRow: number): void;
1132
+ /**
1133
+ * Keyboard selection extension (shift+arrow/home/end). Moves focus;
1134
+ * anchor stays fixed so the highlight grows or shrinks relative to it.
1135
+ * Left/right wrap across row boundaries — native macOS text-edit
1136
+ * behavior: shift+left at col 0 wraps to end of the previous row.
1137
+ * Up/down clamp at viewport edges (no scroll-to-extend yet). Drops to
1138
+ * char mode. No-op outside alt-screen or without an active selection.
1139
+ */
1140
+ moveSelectionFocus(move: FocusMove): void;
1141
+ /** Whether there is an active text selection. */
1142
+ hasTextSelection(): boolean;
1143
+ /**
1144
+ * Subscribe to selection state changes. Fires whenever the selection
1145
+ * is started, updated, cleared, or copied. Returns an unsubscribe fn.
1146
+ */
1147
+ subscribeToSelectionChange(cb: () => void): () => void;
1148
+ private notifySelectionChange;
1149
+ /**
1150
+ * Hit-test the rendered DOM tree at (col, row) and bubble a ClickEvent
1151
+ * from the deepest hit node up through ancestors with onClick handlers.
1152
+ * Returns true if a DOM handler consumed the click. Gated on
1153
+ * altScreenActive — clicks only make sense with a fixed viewport where
1154
+ * nodeCache rects map 1:1 to terminal cells (no scrollback offset).
1155
+ */
1156
+ dispatchClick(col: number, row: number): boolean;
1157
+ dispatchHover(col: number, row: number): void;
1158
+ dispatchKeyboardEvent(parsedKey: ParsedKey): void;
1159
+ /**
1160
+ * Look up the URL at (col, row) in the current front frame. Checks for
1161
+ * an OSC 8 hyperlink first, then falls back to scanning the row for a
1162
+ * plain-text URL (mouse tracking intercepts the terminal's native
1163
+ * Cmd+Click URL detection, so we replicate it). This is a pure lookup
1164
+ * with no side effects — call it synchronously at click time so the
1165
+ * result reflects the screen the user actually clicked on, then defer
1166
+ * the browser-open action via a timer.
1167
+ */
1168
+ getHyperlinkAt(col: number, row: number): string | undefined;
1169
+ /**
1170
+ * Optional callback fired when clicking an OSC 8 hyperlink in fullscreen
1171
+ * mode. Set by FullscreenLayout via useLayoutEffect.
1172
+ */
1173
+ onHyperlinkClick: ((url: string) => void) | undefined;
1174
+ /**
1175
+ * Stable prototype wrapper for onHyperlinkClick. Passed to <App> as
1176
+ * onOpenHyperlink so the prop is a bound method (autoBind'd) that reads
1177
+ * the mutable field at call time — not the undefined-at-render value.
1178
+ */
1179
+ openHyperlink(url: string): void;
1180
+ /**
1181
+ * Handle a double- or triple-click at (col, row): select the word or
1182
+ * line under the cursor by reading the current screen buffer. Called on
1183
+ * PRESS (not release) so the highlight appears immediately and drag can
1184
+ * extend the selection word-by-word / line-by-line. Falls back to
1185
+ * char-mode startSelection if the click lands on a noSelect cell.
1186
+ */
1187
+ handleMultiClick(col: number, row: number, count: 2 | 3): void;
1188
+ /**
1189
+ * Handle a drag-motion at (col, row). In char mode updates focus to the
1190
+ * exact cell. In word/line mode snaps to word/line boundaries so the
1191
+ * selection extends by word/line like native macOS. Gated on
1192
+ * altScreenActive for the same reason as dispatchClick.
1193
+ */
1194
+ handleSelectionDrag(col: number, row: number): void;
1195
+ private stdinListeners;
1196
+ private wasRawMode;
1197
+ suspendStdin(): void;
1198
+ resumeStdin(): void;
1199
+ private writeRaw;
1200
+ private setCursorDeclaration;
1201
+ render(node: ReactNode): void;
1202
+ unmount(error?: Error | number | null): void;
1203
+ waitUntilExit(): Promise<void>;
1204
+ resetLineCount(): void;
1205
+ /**
1206
+ * Replace char/hyperlink pools with fresh instances to prevent unbounded
1207
+ * growth during long sessions. Migrates the front frame's screen IDs into
1208
+ * the new pools so diffing remains correct. The back frame doesn't need
1209
+ * migration — resetScreen zeros it before any reads.
1210
+ *
1211
+ * Call between conversation turns or periodically.
1212
+ */
1213
+ resetPools(): void;
1214
+ patchConsole(): () => void;
1215
+ /**
1216
+ * Intercept process.stderr.write so stray writes (config.ts, hooks.ts,
1217
+ * third-party deps) don't corrupt the alt-screen buffer. patchConsole only
1218
+ * hooks console.* methods — direct stderr writes bypass it, land at the
1219
+ * parked cursor, scroll the alt-screen, and desync frontFrame from the
1220
+ * physical terminal. Next diff writes only changed-in-React cells at
1221
+ * absolute coords → interleaved garbage.
1222
+ *
1223
+ * Swallows the write (routes text to the debug log) and, in alt-screen,
1224
+ * forces a full-damage repaint as a defensive recovery. Not patching
1225
+ * process.stdout — Ink itself writes there.
1226
+ */
1227
+ private patchStderr;
1228
+ }
1229
+
1230
+ type RenderOptions = {
1231
+ /**
1232
+ * Output stream where app will be rendered.
1233
+ *
1234
+ * @default process.stdout
1235
+ */
1236
+ stdout?: NodeJS.WriteStream;
1237
+ /**
1238
+ * Input stream where app will listen for input.
1239
+ *
1240
+ * @default process.stdin
1241
+ */
1242
+ stdin?: NodeJS.ReadStream;
1243
+ /**
1244
+ * Error stream.
1245
+ * @default process.stderr
1246
+ */
1247
+ stderr?: NodeJS.WriteStream;
1248
+ /**
1249
+ * Configure whether Ink should listen to Ctrl+C keyboard input and exit the app. This is needed in case `process.stdin` is in raw mode, because then Ctrl+C is ignored by default and process is expected to handle it manually.
1250
+ *
1251
+ * @default true
1252
+ */
1253
+ exitOnCtrlC?: boolean;
1254
+ /**
1255
+ * Patch console methods to ensure console output doesn't mix with Ink output.
1256
+ *
1257
+ * @default true
1258
+ */
1259
+ patchConsole?: boolean;
1260
+ /**
1261
+ * Called after each frame render with timing and flicker information.
1262
+ */
1263
+ onFrame?: (event: FrameEvent) => void;
1264
+ };
1265
+ type Instance = {
1266
+ /**
1267
+ * Replace previous root node with a new one or update props of the current root node.
1268
+ */
1269
+ rerender: Ink['render'];
1270
+ /**
1271
+ * Manually unmount the whole Ink app.
1272
+ */
1273
+ unmount: Ink['unmount'];
1274
+ /**
1275
+ * Returns a promise, which resolves when app is unmounted.
1276
+ */
1277
+ waitUntilExit: Ink['waitUntilExit'];
1278
+ cleanup: () => void;
1279
+ };
1280
+ /**
1281
+ * A managed Ink root, similar to react-dom's createRoot API.
1282
+ * Separates instance creation from rendering so the same root
1283
+ * can be reused for multiple sequential screens.
1284
+ */
1285
+ type Root = {
1286
+ render: (node: ReactNode) => void;
1287
+ unmount: () => void;
1288
+ waitUntilExit: () => Promise<void>;
1289
+ };
1290
+ /**
1291
+ * Mount a component and render the output.
1292
+ */
1293
+ declare const renderSync: (node: ReactNode, options?: NodeJS.WriteStream | RenderOptions) => Instance;
1294
+ declare const wrappedRender: (node: ReactNode, options?: NodeJS.WriteStream | RenderOptions) => Promise<Instance>;
1295
+
1296
+ /**
1297
+ * Create an Ink root without rendering anything yet.
1298
+ * Like react-dom's createRoot — call root.render() to mount a tree.
1299
+ */
1300
+ declare function createRoot({ stdout, stdin, stderr, exitOnCtrlC, patchConsole, onFrame, }?: RenderOptions): Promise<Root>;
1301
+
1302
+ /**
1303
+ * Mouse click event. Fired on left-button release without drag, only when
1304
+ * mouse tracking is enabled (i.e. inside <AlternateScreen>).
1305
+ *
1306
+ * Bubbles from the deepest hit node up through parentNode. Call
1307
+ * stopImmediatePropagation() to prevent ancestors' onClick from firing.
1308
+ */
1309
+ declare class ClickEvent extends Event {
1310
+ /** 0-indexed screen column of the click */
1311
+ readonly col: number;
1312
+ /** 0-indexed screen row of the click */
1313
+ readonly row: number;
1314
+ /**
1315
+ * Click column relative to the current handler's Box (col - box.x).
1316
+ * Recomputed by dispatchClick before each handler fires, so an onClick
1317
+ * on a container sees coords relative to that container, not to any
1318
+ * child the click landed on.
1319
+ */
1320
+ localCol: number;
1321
+ /** Click row relative to the current handler's Box (row - box.y). */
1322
+ localRow: number;
1323
+ /**
1324
+ * True if the clicked cell has no visible content (unwritten in the
1325
+ * screen buffer — both packed words are 0). Handlers can check this to
1326
+ * ignore clicks on blank space to the right of text, so accidental
1327
+ * clicks on empty terminal space don't toggle state.
1328
+ */
1329
+ readonly cellIsBlank: boolean;
1330
+ constructor(col: number, row: number, cellIsBlank: boolean);
1331
+ }
1332
+
1333
+ /**
1334
+ * Keyboard event dispatched through the DOM tree via capture/bubble.
1335
+ *
1336
+ * Follows browser KeyboardEvent semantics: `key` is the literal character
1337
+ * for printable keys ('a', '3', ' ', '/') and a multi-char name for
1338
+ * special keys ('down', 'return', 'escape', 'f1'). The idiomatic
1339
+ * printable-char check is `e.key.length === 1`.
1340
+ */
1341
+ declare class KeyboardEvent extends TerminalEvent {
1342
+ readonly key: string;
1343
+ readonly ctrl: boolean;
1344
+ readonly shift: boolean;
1345
+ readonly meta: boolean;
1346
+ readonly superKey: boolean;
1347
+ readonly fn: boolean;
1348
+ constructor(parsedKey: ParsedKey);
1349
+ }
1350
+
1351
+ type Props$b = Except<Styles, 'textWrap'> & {
1352
+ ref?: Ref<DOMElement>;
1353
+ /**
1354
+ * Tab order index. Nodes with `tabIndex >= 0` participate in
1355
+ * Tab/Shift+Tab cycling; `-1` means programmatically focusable only.
1356
+ */
1357
+ tabIndex?: number;
1358
+ /**
1359
+ * Focus this element when it mounts. Like the HTML `autofocus`
1360
+ * attribute — the FocusManager calls `focus(node)` during the
1361
+ * reconciler's `commitMount` phase.
1362
+ */
1363
+ autoFocus?: boolean;
1364
+ /**
1365
+ * Fired on left-button click (press + release without drag). Only works
1366
+ * inside `<AlternateScreen>` where mouse tracking is enabled — no-op
1367
+ * otherwise. The event bubbles from the deepest hit Box up through
1368
+ * ancestors; call `event.stopImmediatePropagation()` to stop bubbling.
1369
+ */
1370
+ onClick?: (event: ClickEvent) => void;
1371
+ onFocus?: (event: FocusEvent) => void;
1372
+ onFocusCapture?: (event: FocusEvent) => void;
1373
+ onBlur?: (event: FocusEvent) => void;
1374
+ onBlurCapture?: (event: FocusEvent) => void;
1375
+ onKeyDown?: (event: KeyboardEvent) => void;
1376
+ onKeyDownCapture?: (event: KeyboardEvent) => void;
1377
+ /**
1378
+ * Fired when the mouse moves into this Box's rendered rect. Like DOM
1379
+ * `mouseenter`, does NOT bubble — moving between children does not
1380
+ * re-fire on the parent. Only works inside `<AlternateScreen>` where
1381
+ * mode-1003 mouse tracking is enabled.
1382
+ */
1383
+ onMouseEnter?: () => void;
1384
+ /** Fired when the mouse moves out of this Box's rendered rect. */
1385
+ onMouseLeave?: () => void;
1386
+ };
1387
+ /**
1388
+ * `<Box>` is an essential Ink component to build your layout. It's like `<div style="display: flex">` in the browser.
1389
+ */
1390
+ declare function Box(t0: PropsWithChildren<Props$b>): any;
1391
+
1392
+ type BaseProps = {
1393
+ /**
1394
+ * Change text color. Accepts a raw color value (rgb, hex, ansi).
1395
+ */
1396
+ readonly color?: Color;
1397
+ /**
1398
+ * Same as `color`, but for background.
1399
+ */
1400
+ readonly backgroundColor?: Color;
1401
+ /**
1402
+ * Make the text italic.
1403
+ */
1404
+ readonly italic?: boolean;
1405
+ /**
1406
+ * Make the text underlined.
1407
+ */
1408
+ readonly underline?: boolean;
1409
+ /**
1410
+ * Make the text crossed with a line.
1411
+ */
1412
+ readonly strikethrough?: boolean;
1413
+ /**
1414
+ * Inverse background and foreground colors.
1415
+ */
1416
+ readonly inverse?: boolean;
1417
+ /**
1418
+ * This property tells Ink to wrap or truncate text if its width is larger than container.
1419
+ * If `wrap` is passed (by default), Ink will wrap text and split it into multiple lines.
1420
+ * If `truncate-*` is passed, Ink will truncate text instead, which will result in one line of text with the rest cut off.
1421
+ */
1422
+ readonly wrap?: Styles['textWrap'];
1423
+ readonly children?: ReactNode;
1424
+ };
1425
+ /**
1426
+ * Bold and dim are mutually exclusive in terminals.
1427
+ * This type ensures you can use one or the other, but not both.
1428
+ * `dimColor` is an alias for `dim` (compatibility with ink v3 API).
1429
+ */
1430
+ type WeightProps = {
1431
+ bold?: boolean;
1432
+ dim?: boolean;
1433
+ dimColor?: boolean;
1434
+ };
1435
+ type Props$a = BaseProps & WeightProps;
1436
+ /**
1437
+ * This component can display text, and change its style to make it colorful, bold, underline, italic or strikethrough.
1438
+ */
1439
+ declare function Text(t0: Props$a): any;
1440
+
1441
+ /**
1442
+ * A flexible space that expands along the major axis of its containing layout.
1443
+ * It's useful as a shortcut for filling all the available spaces between elements.
1444
+ */
1445
+ declare function Spacer(): any;
1446
+
1447
+ type Props$9 = {
1448
+ /**
1449
+ * Number of newlines to insert.
1450
+ *
1451
+ * @default 1
1452
+ */
1453
+ readonly count?: number;
1454
+ };
1455
+ /**
1456
+ * Adds one or more newline (\n) characters. Must be used within <Text> components.
1457
+ */
1458
+ declare function Newline(t0: Props$9): any;
1459
+
1460
+ type Props$8 = {
1461
+ readonly children?: ReactNode;
1462
+ readonly url: string;
1463
+ readonly fallback?: ReactNode;
1464
+ };
1465
+ declare function Link(t0: Props$8): any;
1466
+
1467
+ type ButtonState = {
1468
+ focused: boolean;
1469
+ hovered: boolean;
1470
+ active: boolean;
1471
+ };
1472
+ type Props$7 = Except<Styles, 'textWrap'> & {
1473
+ ref?: Ref<DOMElement>;
1474
+ /**
1475
+ * Called when the button is activated via Enter, Space, or click.
1476
+ */
1477
+ onAction: () => void;
1478
+ /**
1479
+ * Tab order index. Defaults to 0 (in tab order).
1480
+ * Set to -1 for programmatically focusable only.
1481
+ */
1482
+ tabIndex?: number;
1483
+ /**
1484
+ * Focus this button when it mounts.
1485
+ */
1486
+ autoFocus?: boolean;
1487
+ /**
1488
+ * Render prop receiving the interactive state. Use this to
1489
+ * style children based on focus/hover/active — Button itself
1490
+ * is intentionally unstyled.
1491
+ *
1492
+ * If not provided, children render as-is (no state-dependent styling).
1493
+ */
1494
+ children: ((state: ButtonState) => React__default.ReactNode) | React__default.ReactNode;
1495
+ };
1496
+ declare function Button(t0: Props$7): any;
1497
+
1498
+ type ScrollBoxHandle = {
1499
+ scrollTo: (y: number) => void;
1500
+ scrollBy: (dy: number) => void;
1501
+ /**
1502
+ * Scroll so `el`'s top is at the viewport top (plus `offset`). Unlike
1503
+ * scrollTo which bakes a number that's stale by the time the throttled
1504
+ * render fires, this defers the position read to render time —
1505
+ * render-node-to-output reads `el.yogaNode.getComputedTop()` in the
1506
+ * SAME Yoga pass that computes scrollHeight. Deterministic. One-shot.
1507
+ */
1508
+ scrollToElement: (el: DOMElement, offset?: number) => void;
1509
+ scrollToBottom: () => void;
1510
+ getScrollTop: () => number;
1511
+ getPendingDelta: () => number;
1512
+ getScrollHeight: () => number;
1513
+ /**
1514
+ * Like getScrollHeight, but reads Yoga directly instead of the cached
1515
+ * value written by render-node-to-output (throttled, up to 16ms stale).
1516
+ * Use when you need a fresh value in useLayoutEffect after a React commit
1517
+ * that grew content. Slightly more expensive (native Yoga call).
1518
+ */
1519
+ getFreshScrollHeight: () => number;
1520
+ getViewportHeight: () => number;
1521
+ /**
1522
+ * Absolute screen-buffer row of the first visible content line (inside
1523
+ * padding). Used for drag-to-scroll edge detection.
1524
+ */
1525
+ getViewportTop: () => number;
1526
+ /**
1527
+ * True when scroll is pinned to the bottom. Set by scrollToBottom, the
1528
+ * initial stickyScroll attribute, and by the renderer when positional
1529
+ * follow fires (scrollTop at prevMax, content grows). Cleared by
1530
+ * scrollTo/scrollBy. Stable signal for "at bottom" that doesn't depend on
1531
+ * layout values (unlike scrollTop+viewportH >= scrollHeight).
1532
+ */
1533
+ isSticky: () => boolean;
1534
+ /**
1535
+ * Subscribe to imperative scroll changes (scrollTo/scrollBy/scrollToBottom).
1536
+ * Does NOT fire for stickyScroll updates done by the Ink renderer — those
1537
+ * happen during Ink's render phase after React has committed. Callers that
1538
+ * care about the sticky case should treat "at bottom" as a fallback.
1539
+ */
1540
+ subscribe: (listener: () => void) => () => void;
1541
+ /**
1542
+ * Set the render-time scrollTop clamp to the currently-mounted children's
1543
+ * coverage span. Called by useVirtualScroll after computing its range;
1544
+ * render-node-to-output clamps scrollTop to [min, max] so burst scrollTo
1545
+ * calls that race past React's async re-render show the edge of mounted
1546
+ * content instead of blank spacer. Pass undefined to disable (sticky,
1547
+ * cold start).
1548
+ */
1549
+ setClampBounds: (min: number | undefined, max: number | undefined) => void;
1550
+ };
1551
+ type ScrollBoxProps = Except<Styles, 'textWrap' | 'overflow' | 'overflowX' | 'overflowY'> & {
1552
+ ref?: Ref<ScrollBoxHandle>;
1553
+ /**
1554
+ * When true, automatically pins scroll position to the bottom when content
1555
+ * grows. Unset manually via scrollTo/scrollBy to break the stickiness.
1556
+ */
1557
+ stickyScroll?: boolean;
1558
+ };
1559
+ /**
1560
+ * A Box with `overflow: scroll` and an imperative scroll API.
1561
+ *
1562
+ * Children are laid out at their full Yoga-computed height inside a
1563
+ * constrained container. At render time, only children intersecting the
1564
+ * visible window (scrollTop..scrollTop+height) are rendered (viewport
1565
+ * culling). Content is translated by -scrollTop and clipped to the box bounds.
1566
+ *
1567
+ * Works best inside a fullscreen (constrained-height root) Ink tree.
1568
+ */
1569
+ declare function ScrollBox({ children, ref, stickyScroll, ...style }: PropsWithChildren<ScrollBoxProps>): React__default.ReactNode;
1570
+
1571
+ type Props$6 = PropsWithChildren<{
1572
+ /** Enable SGR mouse tracking (wheel + click/drag). Default true. */
1573
+ mouseTracking?: boolean;
1574
+ }>;
1575
+ /**
1576
+ * Run children in the terminal's alternate screen buffer, constrained to
1577
+ * the viewport height. While mounted:
1578
+ *
1579
+ * - Enters the alt screen (DEC 1049), clears it, homes the cursor
1580
+ * - Constrains its own height to the terminal row count, so overflow must
1581
+ * be handled via `overflow: scroll` / flexbox (no native scrollback)
1582
+ * - Optionally enables SGR mouse tracking (wheel + click/drag) — events
1583
+ * surface as `ParsedKey` (wheel) and update the Ink instance's
1584
+ * selection state (click/drag)
1585
+ *
1586
+ * On unmount, disables mouse tracking and exits the alt screen, restoring
1587
+ * the main screen's content. Safe for use in ctrl-o transcript overlays
1588
+ * and similar temporary fullscreen views — the main screen is preserved.
1589
+ *
1590
+ * Notifies the Ink instance via `setAltScreenActive()` so the renderer
1591
+ * keeps the cursor inside the viewport (preventing the cursor-restore LF
1592
+ * from scrolling content) and so signal-exit cleanup can exit the alt
1593
+ * screen if the component's own unmount doesn't run.
1594
+ */
1595
+ declare function AlternateScreen(t0: Props$6): any;
1596
+
1597
+ type Props$5 = {
1598
+ /**
1599
+ * Pre-rendered ANSI lines. Each element must be exactly one terminal row
1600
+ * (already wrapped to `width` by the producer) with ANSI escape codes inline.
1601
+ */
1602
+ lines: string[];
1603
+ /** Column width the producer wrapped to. Sent to Yoga as the fixed leaf width. */
1604
+ width: number;
1605
+ };
1606
+ /**
1607
+ * Bypass the <Ansi> → React tree → Yoga → squash → re-serialize roundtrip for
1608
+ * content that is already terminal-ready.
1609
+ *
1610
+ * Use this when an external renderer (e.g. the ColorDiff NAPI module) has
1611
+ * already produced ANSI-escaped, width-wrapped output. A normal <Ansi> mount
1612
+ * reparses that output into one React <Text> per style span, lays out each
1613
+ * span as a Yoga flex child, then walks the tree to re-emit the same escape
1614
+ * codes it was given. For a long transcript full of syntax-highlighted diffs
1615
+ * that roundtrip is the dominant cost of the render.
1616
+ *
1617
+ * This component emits a single Yoga leaf with a constant-time measure func
1618
+ * (width × lines.length) and hands the joined string straight to output.write(),
1619
+ * which already splits on '\n' and parses ANSI into the screen buffer.
1620
+ */
1621
+ declare function RawAnsi(t0: Props$5): any;
1622
+
1623
+ type Props$4 = Omit<Props$b, 'noSelect'> & {
1624
+ /**
1625
+ * Extend the exclusion zone from column 0 to this box's right edge,
1626
+ * for every row this box occupies. Use for gutters rendered inside a
1627
+ * wider indented container (e.g. a diff inside a tool message row):
1628
+ * without this, a multi-row drag picks up the container's leading
1629
+ * indent on rows below the prefix.
1630
+ *
1631
+ * @default false
1632
+ */
1633
+ fromLeftEdge?: boolean;
1634
+ };
1635
+ /**
1636
+ * Marks its contents as non-selectable in fullscreen text selection.
1637
+ * Cells inside this box are skipped by both the selection highlight and
1638
+ * the copied text — the gutter stays visually unchanged while the user
1639
+ * drags, making it clear what will be copied.
1640
+ *
1641
+ * Use to fence off gutters (line numbers, diff +/- sigils, list bullets)
1642
+ * so click-drag over rendered code yields clean pasteable content:
1643
+ *
1644
+ * <Box flexDirection="row">
1645
+ * <NoSelect fromLeftEdge><Text dimColor> 42 +</Text></NoSelect>
1646
+ * <Text>const x = 1</Text>
1647
+ * </Box>
1648
+ *
1649
+ * Only affects alt-screen text selection (<AlternateScreen> with mouse
1650
+ * tracking). No-op in the main-screen scrollback render where the
1651
+ * terminal's native selection is used instead.
1652
+ */
1653
+ declare function NoSelect(t0: PropsWithChildren<Props$4>): any;
1654
+
1655
+ type Props$3 = {
1656
+ readonly error: Error;
1657
+ };
1658
+ declare function ErrorOverview({ error }: Props$3): react_jsx_runtime.JSX.Element;
1659
+
1660
+ type Props$2 = {
1661
+ children: string;
1662
+ /** When true, force all text to be rendered with dim styling */
1663
+ dimColor?: boolean;
1664
+ };
1665
+ /**
1666
+ * Component that parses ANSI escape codes and renders them using Text components.
1667
+ *
1668
+ * Use this as an escape hatch when you have pre-formatted ANSI strings from
1669
+ * external tools (like cli-highlight) that need to be rendered in Ink.
1670
+ *
1671
+ * Memoized to prevent re-renders when parent changes but children string is the same.
1672
+ */
1673
+ declare const Ansi: React__default.NamedExoticComponent<Props$2>;
1674
+
1675
+ type Key = {
1676
+ upArrow: boolean;
1677
+ downArrow: boolean;
1678
+ leftArrow: boolean;
1679
+ rightArrow: boolean;
1680
+ pageDown: boolean;
1681
+ pageUp: boolean;
1682
+ wheelUp: boolean;
1683
+ wheelDown: boolean;
1684
+ home: boolean;
1685
+ end: boolean;
1686
+ return: boolean;
1687
+ escape: boolean;
1688
+ ctrl: boolean;
1689
+ shift: boolean;
1690
+ fn: boolean;
1691
+ tab: boolean;
1692
+ backspace: boolean;
1693
+ delete: boolean;
1694
+ meta: boolean;
1695
+ super: boolean;
1696
+ };
1697
+ declare class InputEvent extends Event {
1698
+ readonly keypress: ParsedKey;
1699
+ readonly key: Key;
1700
+ readonly input: string;
1701
+ constructor(keypress: ParsedKey);
1702
+ }
1703
+
1704
+ type Handler = (input: string, key: Key, event: InputEvent) => void;
1705
+ type Options = {
1706
+ /**
1707
+ * Enable or disable capturing of user input.
1708
+ * Useful when there are multiple useInput hooks used at once to avoid handling the same input several times.
1709
+ *
1710
+ * @default true
1711
+ */
1712
+ isActive?: boolean;
1713
+ };
1714
+ /**
1715
+ * This hook is used for handling user input.
1716
+ * It's a more convenient alternative to using `StdinContext` and listening to `data` events.
1717
+ * The callback you pass to `useInput` is called for each character when user enters any input.
1718
+ * However, if user pastes text and it's more than one character, the callback will be called only once and the whole string will be passed as `input`.
1719
+ *
1720
+ * ```
1721
+ * import {useInput} from 'ink';
1722
+ *
1723
+ * const UserInput = () => {
1724
+ * useInput((input, key) => {
1725
+ * if (input === 'q') {
1726
+ * // Exit program
1727
+ * }
1728
+ *
1729
+ * if (key.leftArrow) {
1730
+ * // Left arrow key pressed
1731
+ * }
1732
+ * });
1733
+ *
1734
+ * return …
1735
+ * };
1736
+ * ```
1737
+ */
1738
+ declare const useInput: (inputHandler: Handler, options?: Options) => void;
1739
+
1740
+ type Props$1 = {
1741
+ /**
1742
+ * Exit (unmount) the whole Ink app.
1743
+ */
1744
+ readonly exit: (error?: Error) => void;
1745
+ };
1746
+ /**
1747
+ * `AppContext` is a React context, which exposes a method to manually exit the app (unmount).
1748
+ */
1749
+ declare const AppContext: React.Context<Props$1>;
1750
+
1751
+ /**
1752
+ * `useApp` is a React hook, which exposes a method to manually exit the app (unmount).
1753
+ */
1754
+ declare const useApp: () => Props$1;
1755
+
1756
+ declare class EventEmitter extends EventEmitter$1 {
1757
+ constructor();
1758
+ emit(type: string | symbol, ...args: unknown[]): boolean;
1759
+ }
1760
+
1761
+ /**
1762
+ * Query the terminal and await responses without timeouts.
1763
+ *
1764
+ * Terminal queries (DECRQM, DA1, OSC 11, etc.) share the stdin stream
1765
+ * with keyboard input. Response sequences are syntactically
1766
+ * distinguishable from key events, so the input parser recognizes them
1767
+ * and dispatches them here.
1768
+ *
1769
+ * To avoid timeouts, each query batch is terminated by a DA1 sentinel
1770
+ * (CSI c) — every terminal since VT100 responds to DA1, and terminals
1771
+ * answer queries in order. So: if your query's response arrives before
1772
+ * DA1's, the terminal supports it; if DA1 arrives first, it doesn't.
1773
+ *
1774
+ * Usage:
1775
+ * const [sync, grapheme] = await Promise.all([
1776
+ * querier.send(decrqm(2026)),
1777
+ * querier.send(decrqm(2027)),
1778
+ * querier.flush(),
1779
+ * ])
1780
+ * // sync and grapheme are DECRPM responses or undefined if unsupported
1781
+ */
1782
+
1783
+ /** A terminal query: an outbound request sequence paired with a matcher
1784
+ * that recognizes the expected inbound response. Built by `decrqm()`,
1785
+ * `oscColor()`, `kittyKeyboard()`, etc. */
1786
+ type TerminalQuery<T extends TerminalResponse = TerminalResponse> = {
1787
+ /** Escape sequence to write to stdout */
1788
+ request: string;
1789
+ /** Recognizes the expected response in the inbound stream */
1790
+ match: (r: TerminalResponse) => r is T;
1791
+ };
1792
+ declare class TerminalQuerier {
1793
+ private stdout;
1794
+ /**
1795
+ * Interleaved queue of queries and sentinels in send order. Terminals
1796
+ * respond in order, so each flush() barrier only drains queries queued
1797
+ * before it — concurrent batches from independent callers stay isolated.
1798
+ */
1799
+ private queue;
1800
+ constructor(stdout: NodeJS.WriteStream);
1801
+ /**
1802
+ * Send a query and wait for its response.
1803
+ *
1804
+ * Resolves with the response when `query.match` matches an incoming
1805
+ * TerminalResponse, or with `undefined` when a flush() sentinel arrives
1806
+ * before any matching response (meaning the terminal ignored the query).
1807
+ *
1808
+ * Never rejects; never times out on its own. If you never call flush()
1809
+ * and the terminal doesn't respond, the promise remains pending.
1810
+ */
1811
+ send<T extends TerminalResponse>(query: TerminalQuery<T>): Promise<T | undefined>;
1812
+ /**
1813
+ * Send the DA1 sentinel. Resolves when DA1's response arrives.
1814
+ *
1815
+ * As a side effect, all queries still pending when DA1 arrives are
1816
+ * resolved with `undefined` (terminal didn't respond → doesn't support
1817
+ * the query). This is the barrier that makes send() timeout-free.
1818
+ *
1819
+ * Safe to call with no pending queries — still waits for a round-trip.
1820
+ */
1821
+ flush(): Promise<void>;
1822
+ /**
1823
+ * Dispatch a response parsed from stdin. Called by App.tsx's
1824
+ * processKeysInBatch for every `kind: 'response'` item.
1825
+ *
1826
+ * Matching strategy:
1827
+ * - First, try to match a pending query (FIFO, first match wins).
1828
+ * This lets callers send(da1()) explicitly if they want the DA1
1829
+ * params — a separate DA1 write means the terminal sends TWO DA1
1830
+ * responses. The first matches the explicit query; the second
1831
+ * (unmatched) fires the sentinel.
1832
+ * - Otherwise, if this is a DA1, fire the FIRST pending sentinel:
1833
+ * resolve any queries queued before that sentinel with undefined
1834
+ * (the terminal answered DA1 without answering them → unsupported)
1835
+ * and signal its flush() completion. Only draining up to the first
1836
+ * sentinel keeps later batches intact when multiple callers have
1837
+ * concurrent queries in flight.
1838
+ * - Unsolicited responses (no match, no sentinel) are silently dropped.
1839
+ */
1840
+ onResponse(r: TerminalResponse): void;
1841
+ }
1842
+
1843
+ type Props = {
1844
+ /**
1845
+ * Stdin stream passed to `render()` in `options.stdin` or `process.stdin` by default. Useful if your app needs to handle user input.
1846
+ */
1847
+ readonly stdin: NodeJS.ReadStream;
1848
+ /**
1849
+ * Ink exposes this function via own `<StdinContext>` to be able to handle Ctrl+C, that's why you should use Ink's `setRawMode` instead of `process.stdin.setRawMode`.
1850
+ * If the `stdin` stream passed to Ink does not support setRawMode, this function does nothing.
1851
+ */
1852
+ readonly setRawMode: (value: boolean) => void;
1853
+ /**
1854
+ * A boolean flag determining if the current `stdin` supports `setRawMode`. A component using `setRawMode` might want to use `isRawModeSupported` to nicely fall back in environments where raw mode is not supported.
1855
+ */
1856
+ readonly isRawModeSupported: boolean;
1857
+ readonly internal_exitOnCtrlC: boolean;
1858
+ readonly internal_eventEmitter: EventEmitter;
1859
+ /** Query the terminal and await responses (DECRQM, OSC 11, etc.).
1860
+ * Null only in the never-reached default context value. */
1861
+ readonly internal_querier: TerminalQuerier | null;
1862
+ };
1863
+ /**
1864
+ * `StdinContext` is a React context, which exposes input stream.
1865
+ */
1866
+ declare const StdinContext: React.Context<Props>;
1867
+
1868
+ /**
1869
+ * `useStdin` is a React hook, which exposes stdin stream.
1870
+ */
1871
+ declare const useStdin: () => Props;
1872
+
1873
+ /**
1874
+ * Hook to check if the terminal has focus.
1875
+ *
1876
+ * Uses DECSET 1004 focus reporting - the terminal sends escape sequences
1877
+ * when it gains or loses focus. These are handled automatically
1878
+ * by Ink and filtered from useInput.
1879
+ *
1880
+ * @returns true if the terminal is focused (or focus state is unknown)
1881
+ */
1882
+ declare function useTerminalFocus(): boolean;
1883
+
1884
+ type ViewportEntry = {
1885
+ /**
1886
+ * Whether the element is currently within the terminal viewport
1887
+ */
1888
+ isVisible: boolean;
1889
+ };
1890
+ /**
1891
+ * Hook to detect if a component is within the terminal viewport.
1892
+ *
1893
+ * Returns a callback ref and a viewport entry object.
1894
+ * Attach the ref to the component you want to track.
1895
+ *
1896
+ * The entry is updated during the layout phase (useLayoutEffect) so callers
1897
+ * always read fresh values during render. Visibility changes do NOT trigger
1898
+ * re-renders on their own — callers that re-render for other reasons (e.g.
1899
+ * animation ticks, state changes) will pick up the latest value naturally.
1900
+ * This avoids infinite update loops when combined with other layout effects
1901
+ * that also call setState.
1902
+ *
1903
+ * @example
1904
+ * const [ref, entry] = useTerminalViewport()
1905
+ * return <Box ref={ref}><Animation enabled={entry.isVisible}>...</Animation></Box>
1906
+ */
1907
+ declare function useTerminalViewport(): [
1908
+ ref: (element: DOMElement | null) => void,
1909
+ entry: ViewportEntry
1910
+ ];
1911
+
1912
+ /**
1913
+ * Returns the clock time, updating at the given interval.
1914
+ * Subscribes as non-keepAlive — won't keep the clock alive on its own,
1915
+ * but updates whenever a keepAlive subscriber (e.g. the spinner)
1916
+ * is driving the clock.
1917
+ *
1918
+ * Use this to drive pure time-based computations (shimmer position,
1919
+ * frame index) from the shared clock.
1920
+ */
1921
+ declare function useAnimationTimer(intervalMs: number): number;
1922
+ /**
1923
+ * Interval hook backed by the shared Clock.
1924
+ *
1925
+ * Unlike `useInterval` from `usehooks-ts` (which creates its own setInterval),
1926
+ * this piggybacks on the single shared clock so all timers consolidate into
1927
+ * one wake-up. Pass `null` for intervalMs to pause.
1928
+ */
1929
+ declare function useInterval(callback: () => void, intervalMs: number | null): void;
1930
+
1931
+ /**
1932
+ * Hook for synchronized animations that pause when offscreen.
1933
+ *
1934
+ * Returns a ref to attach to the animated element and the current animation time.
1935
+ * All instances share the same clock, so animations stay in sync.
1936
+ * The clock only runs when at least one keepAlive subscriber exists.
1937
+ *
1938
+ * Pass `null` to pause — unsubscribes from the clock so no ticks fire.
1939
+ * Time freezes at the last value and resumes from the current clock time
1940
+ * when a number is passed again.
1941
+ *
1942
+ * @param intervalMs - How often to update, or null to pause
1943
+ * @returns [ref, time] - Ref to attach to element, elapsed time in ms
1944
+ *
1945
+ * @example
1946
+ * function Spinner() {
1947
+ * const [ref, time] = useAnimationFrame(120)
1948
+ * const frame = Math.floor(time / 120) % FRAMES.length
1949
+ * return <Box ref={ref}>{FRAMES[frame]}</Box>
1950
+ * }
1951
+ *
1952
+ * The clock automatically slows when the terminal is blurred,
1953
+ * so consumers don't need to handle focus state.
1954
+ */
1955
+ declare function useAnimationFrame(intervalMs?: number | null): [ref: (element: DOMElement | null) => void, time: number];
1956
+
1957
+ /**
1958
+ * Declaratively set the terminal tab/window title.
1959
+ *
1960
+ * Pass a string to set the title. ANSI escape sequences are stripped
1961
+ * automatically so callers don't need to know about terminal encoding.
1962
+ * Pass `null` to opt out — the hook becomes a no-op and leaves the
1963
+ * terminal title untouched.
1964
+ *
1965
+ * On Windows, uses `process.title` (classic conhost doesn't support OSC).
1966
+ * Elsewhere, writes OSC 0 (set title+icon) via Ink's stdout.
1967
+ */
1968
+ declare function useTerminalTitle(title: string | null): void;
1969
+
1970
+ type TabStatusKind = 'idle' | 'busy' | 'waiting';
1971
+ /**
1972
+ * Declaratively set the tab-status indicator (OSC 21337).
1973
+ *
1974
+ * Emits a colored dot + short status text to the tab sidebar. Terminals
1975
+ * that don't support OSC 21337 discard the sequence silently, so this is
1976
+ * safe to call unconditionally. Wrapped for tmux/screen passthrough.
1977
+ *
1978
+ * Pass `null` to opt out. If a status was previously set, transitioning to
1979
+ * `null` emits CLEAR_TAB_STATUS so toggling off mid-session doesn't leave
1980
+ * a stale dot. Process-exit cleanup is handled by ink.tsx's unmount path.
1981
+ */
1982
+ declare function useTabStatus(kind: TabStatusKind | null): void;
1983
+
1984
+ /**
1985
+ * Declares where the terminal cursor should be parked after each frame.
1986
+ *
1987
+ * Terminal emulators render IME preedit text at the physical cursor
1988
+ * position, and screen readers / screen magnifiers track the native
1989
+ * cursor — so parking it at the text input's caret makes CJK input
1990
+ * appear inline and lets accessibility tools follow the input.
1991
+ *
1992
+ * Returns a ref callback to attach to the Box that contains the input.
1993
+ * The declared (line, column) is interpreted relative to that Box's
1994
+ * nodeCache rect (populated by renderNodeToOutput).
1995
+ *
1996
+ * Timing: Both ref attach and useLayoutEffect fire in React's layout
1997
+ * phase — after resetAfterCommit calls scheduleRender. scheduleRender
1998
+ * defers onRender via queueMicrotask, so onRender runs AFTER layout
1999
+ * effects commit and reads the fresh declaration on the first frame
2000
+ * (no one-keystroke lag). Test env uses onImmediateRender (synchronous,
2001
+ * no microtask), so tests compensate by calling ink.onRender()
2002
+ * explicitly after render.
2003
+ */
2004
+ declare function useDeclaredCursor({ line, column, active, }: {
2005
+ line: number;
2006
+ column: number;
2007
+ active: boolean;
2008
+ }): (element: DOMElement | null) => void;
2009
+
2010
+ /**
2011
+ * Set the search highlight query on the Ink instance. Non-empty → all
2012
+ * visible occurrences are inverted on the next frame (SGR 7, screen-buffer
2013
+ * overlay, same damage machinery as selection). Empty → clears.
2014
+ *
2015
+ * This is a screen-space highlight — it matches the RENDERED text, not the
2016
+ * source message text. Works for anything visible (bash output, file paths,
2017
+ * error messages) regardless of where it came from in the message tree. A
2018
+ * query that matched in source but got truncated/ellipsized in rendering
2019
+ * won't highlight; that's acceptable — we highlight what you see.
2020
+ */
2021
+ declare function useSearchHighlight(): {
2022
+ setQuery: (query: string) => void;
2023
+ /** Paint an existing DOM subtree (from the MAIN tree) to a fresh
2024
+ * Screen at its natural height, scan. Element-relative positions
2025
+ * (row 0 = element top). Zero context duplication — the element
2026
+ * IS the one built with all real providers. */
2027
+ scanElement: (el: DOMElement) => MatchPosition[];
2028
+ /** Position-based CURRENT highlight. Every frame writes yellow at
2029
+ * positions[currentIdx] + rowOffset. The scan-highlight (inverse on
2030
+ * all matches) still runs — this overlays on top. rowOffset tracks
2031
+ * scroll; positions stay stable (message-relative). null clears. */
2032
+ setPositions: (state: {
2033
+ positions: MatchPosition[];
2034
+ rowOffset: number;
2035
+ currentIdx: number;
2036
+ } | null) => void;
2037
+ };
2038
+
2039
+ /**
2040
+ * Access to text selection operations on the Ink instance (fullscreen only).
2041
+ * Returns no-op functions when fullscreen mode is disabled.
2042
+ */
2043
+ declare function useSelection(): {
2044
+ copySelection: () => string;
2045
+ /** Copy without clearing the highlight (for copy-on-select). */
2046
+ copySelectionNoClear: () => string;
2047
+ clearSelection: () => void;
2048
+ hasSelection: () => boolean;
2049
+ /** Read the raw mutable selection state (for drag-to-scroll). */
2050
+ getState: () => SelectionState | null;
2051
+ /** Subscribe to selection mutations (start/update/finish/clear). */
2052
+ subscribe: (cb: () => void) => () => void;
2053
+ /** Shift the anchor row by dRow, clamped to [minRow, maxRow]. */
2054
+ shiftAnchor: (dRow: number, minRow: number, maxRow: number) => void;
2055
+ /** Shift anchor AND focus by dRow (keyboard scroll: whole selection
2056
+ * tracks content). Clamped points get col reset to the full-width edge
2057
+ * since their content was captured by captureScrolledRows. Reads
2058
+ * screen.width from the ink instance for the col-reset boundary. */
2059
+ shiftSelection: (dRow: number, minRow: number, maxRow: number) => void;
2060
+ /** Keyboard selection extension (shift+arrow): move focus, anchor fixed.
2061
+ * Left/right wrap across rows; up/down clamp at viewport edges. */
2062
+ moveFocus: (move: FocusMove) => void;
2063
+ /** Capture text from rows about to scroll out of the viewport (call
2064
+ * BEFORE scrollBy so the screen buffer still has the outgoing rows). */
2065
+ captureScrolledRows: (firstRow: number, lastRow: number, side: 'above' | 'below') => void;
2066
+ /** Set the selection highlight bg color (theme-piping; solid bg
2067
+ * replaces the old SGR-7 inverse so syntax highlighting stays readable
2068
+ * under selection). Call once on mount + whenever theme changes. */
2069
+ setSelectionBgColor: (color: string) => void;
2070
+ };
2071
+ /**
2072
+ * Reactive selection-exists state. Re-renders the caller when a text
2073
+ * selection is created or cleared. Always returns false outside
2074
+ * fullscreen mode (selection is only available in alt-screen).
2075
+ */
2076
+ declare function useHasSelection(): boolean;
2077
+
2078
+ type TerminalSize = {
2079
+ columns: number;
2080
+ rows: number;
2081
+ };
2082
+ declare const TerminalSizeContext: React.Context<TerminalSize | null>;
2083
+
2084
+ type Output = {
2085
+ /**
2086
+ * Element width.
2087
+ */
2088
+ width: number;
2089
+ /**
2090
+ * Element height.
2091
+ */
2092
+ height: number;
2093
+ };
2094
+ /**
2095
+ * Measure the dimensions of a particular `<Box>` element.
2096
+ */
2097
+ declare const measureElement: (node: DOMElement) => Output;
2098
+
2099
+ declare const stringWidth: (str: string) => number;
2100
+
2101
+ type WrapAnsiOptions = {
2102
+ hard?: boolean;
2103
+ wordWrap?: boolean;
2104
+ trim?: boolean;
2105
+ };
2106
+ declare const wrapAnsi: (input: string, columns: number, options?: WrapAnsiOptions) => string;
2107
+
2108
+ type ColorType = 'foreground' | 'background';
2109
+ declare const colorize: (str: string, color: string | undefined, type: ColorType) => string;
2110
+ /**
2111
+ * Apply TextStyles to a string using chalk.
2112
+ * This is the inverse of parsing ANSI codes - we generate them from structured styles.
2113
+ * Theme resolution happens at component layer, not here.
2114
+ */
2115
+ declare function applyTextStyles(text: string, styles: TextStyles): string;
2116
+ /**
2117
+ * Apply a raw color value to text.
2118
+ * Theme resolution should happen at component layer, not here.
2119
+ */
2120
+ declare function applyColor(text: string, color: Color | undefined): string;
2121
+
2122
+ declare function useTheme(): [string, (name: string) => void];
2123
+
2124
+ export { AlternateScreen, Ansi, AppContext, type BorderTextOptions, Box, Button, ClickEvent, type Color, type ColorType, type DOMElement, type DOMNode, ErrorOverview, FocusEvent, type Frame, type FrameEvent, InputEvent, type Instance, type Key, KeyboardEvent, Link, type MatchPosition, Newline, NoSelect, type ParsedKey, RawAnsi, type RenderOptions, type Root, ScrollBox, type ScrollBoxHandle, Spacer, StdinContext, type Styles, type TabStatusKind, TerminalSizeContext, Text, type Props$a as TextProps, type TextStyles, applyColor, applyTextStyles, clamp, colorize, createRoot, measureElement, wrappedRender as render, renderSync, stringWidth, useAnimationFrame, useAnimationTimer, useApp, useDeclaredCursor, useHasSelection, useInput, useInterval, useSearchHighlight, useSelection, useStdin, useTabStatus, useTerminalFocus, useTerminalTitle, useTerminalViewport, useTheme, wrapAnsi };