@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.10

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.
Files changed (212) hide show
  1. package/README.md +499 -313
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-DV9CuzAT.d.ts +452 -0
  4. package/dist/ansi.d.ts +217 -0
  5. package/dist/ansi.js +87 -0
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BAC9-TZX.js +592 -0
  11. package/dist/color-policy-CCxuHIdD.d.ts +22 -0
  12. package/dist/color-profile-CyeHnG1T.d.ts +97 -0
  13. package/dist/color-profile-DHhQHY55.js +36 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-B3dL4Q11.js +374 -0
  18. package/dist/detect-Db6GbKOm.d.ts +195 -0
  19. package/dist/{devtools-QpCMm9JH.mjs → devtools-DbthxoD1.js} +23 -24
  20. package/dist/env-YVw64yZS.js +9 -0
  21. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  22. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  23. package/dist/index-D48vQhhe.d.ts +21 -0
  24. package/dist/index-DDVME65c.d.ts +919 -0
  25. package/dist/index.d.ts +1310 -0
  26. package/dist/index.js +3209 -0
  27. package/dist/osc-BFKKSqpg.js +71 -0
  28. package/dist/osc-Cn0fw77g.d.ts +23 -0
  29. package/dist/paint-Cx-zC_sX.d.ts +81 -0
  30. package/dist/query-BNc2B8GD.d.ts +152 -0
  31. package/dist/router.d.ts +392 -0
  32. package/dist/router.js +709 -0
  33. package/dist/sample-Cqw1bjUL.js +445 -0
  34. package/dist/screen-CgC2WlVM.d.ts +49 -0
  35. package/dist/screen-CiPytswf.js +342 -0
  36. package/dist/screen.d.ts +5 -0
  37. package/dist/screen.js +5 -0
  38. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  39. package/dist/serialize-BTkAZgw1.js +79 -0
  40. package/dist/session-DDQ5V300.js +723 -0
  41. package/dist/sgr-BhwaWAJB.js +246 -0
  42. package/dist/store-C1P5fOUi.d.ts +72 -0
  43. package/dist/string-width-CijQwpIk.js +69 -0
  44. package/dist/strip-BvU4toXG.js +6 -0
  45. package/dist/terminal.d.ts +121 -0
  46. package/dist/terminal.js +2 -0
  47. package/dist/tokenize-AjqbvtiT.js +1242 -0
  48. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  49. package/dist/truncate-D31fhU6i.js +562 -0
  50. package/dist/use-focus-CcCIdCWA.js +1337 -0
  51. package/dist/yoga-5jKhYCJC.js +3465 -0
  52. package/dist/yoga.d.ts +2 -0
  53. package/dist/yoga.js +2 -0
  54. package/package.json +70 -24
  55. package/src/ansi/chalk.ts +138 -0
  56. package/src/ansi/cursor.ts +46 -0
  57. package/src/ansi/east-asian-width.ts +259 -0
  58. package/src/ansi/escapes.ts +120 -0
  59. package/src/ansi/graphemes.ts +8 -0
  60. package/src/ansi/hyperlink.ts +44 -0
  61. package/src/ansi/index.ts +27 -0
  62. package/src/ansi/osc.ts +77 -0
  63. package/src/ansi/sgr.ts +234 -0
  64. package/src/ansi/slice.ts +43 -0
  65. package/src/ansi/string-width.ts +121 -0
  66. package/src/ansi/strip.ts +13 -0
  67. package/src/ansi/tokenize.ts +380 -0
  68. package/src/ansi/truncate.ts +196 -0
  69. package/src/ansi/wrap.ts +765 -0
  70. package/src/ansi-tokenizer.ts +510 -0
  71. package/src/capabilities/color-policy.ts +34 -0
  72. package/src/capabilities/detect.ts +608 -0
  73. package/src/capabilities/index.ts +37 -0
  74. package/src/capabilities/query.ts +679 -0
  75. package/src/capabilities/store.ts +394 -0
  76. package/src/code-excerpt.ts +39 -0
  77. package/src/color/index.ts +3 -0
  78. package/src/color/paint.ts +169 -0
  79. package/src/color/palette.ts +48 -0
  80. package/src/color/sample.ts +323 -0
  81. package/src/color.ts +1 -0
  82. package/src/components/AccessibilityContext.ts +5 -0
  83. package/src/components/AnimationContext.ts +24 -0
  84. package/src/components/AnsiText.tsx +42 -0
  85. package/src/components/App.tsx +878 -0
  86. package/src/components/AppContext.ts +111 -0
  87. package/src/components/BackgroundContext.ts +7 -0
  88. package/src/components/Box.tsx +100 -0
  89. package/src/components/CursorContext.ts +19 -0
  90. package/src/components/ErrorBoundary.tsx +39 -0
  91. package/src/components/ErrorOverview.tsx +135 -0
  92. package/src/components/FocusContext.ts +30 -0
  93. package/src/components/Hyperlink.tsx +56 -0
  94. package/src/components/Newline.tsx +16 -0
  95. package/src/components/Spacer.tsx +11 -0
  96. package/src/components/Static.tsx +60 -0
  97. package/src/components/StderrContext.ts +24 -0
  98. package/src/components/StdinContext.ts +48 -0
  99. package/src/components/StdoutContext.ts +26 -0
  100. package/src/components/TerminalOscContext.ts +25 -0
  101. package/src/components/Text.tsx +122 -0
  102. package/src/components/Transform.tsx +38 -0
  103. package/src/components/VirtualList.tsx +128 -0
  104. package/src/cursor-position.ts +103 -0
  105. package/src/devtools.ts +103 -0
  106. package/src/dom.ts +301 -0
  107. package/src/env.ts +12 -0
  108. package/src/get-max-width.ts +11 -0
  109. package/src/global.d.ts +38 -0
  110. package/src/glyphs.ts +99 -0
  111. package/src/hooks/use-animation.ts +142 -0
  112. package/src/hooks/use-app.ts +8 -0
  113. package/src/hooks/use-box-metrics.ts +134 -0
  114. package/src/hooks/use-capabilities.ts +73 -0
  115. package/src/hooks/use-cursor.ts +33 -0
  116. package/src/hooks/use-focus-manager.ts +62 -0
  117. package/src/hooks/use-focus.ts +82 -0
  118. package/src/hooks/use-input.ts +267 -0
  119. package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
  120. package/src/hooks/use-paste.ts +78 -0
  121. package/src/hooks/use-stderr.ts +8 -0
  122. package/src/hooks/use-stdin.ts +10 -0
  123. package/src/hooks/use-stdout.ts +8 -0
  124. package/src/hooks/use-terminal-osc.ts +59 -0
  125. package/src/hooks/use-virtual-scroll.ts +84 -0
  126. package/src/hooks/use-window-size.ts +37 -0
  127. package/src/index.ts +96 -0
  128. package/src/ink.tsx +1452 -0
  129. package/src/input-parser.ts +303 -0
  130. package/src/instances.ts +9 -0
  131. package/src/kitty-keyboard.ts +185 -0
  132. package/src/measure-element.ts +62 -0
  133. package/src/measure-text.ts +31 -0
  134. package/src/paint-tree.ts +220 -0
  135. package/src/parse-keypress.ts +515 -0
  136. package/src/parse-stack-line.ts +138 -0
  137. package/src/patch-console.ts +106 -0
  138. package/src/quick-lru.ts +85 -0
  139. package/src/reconciler.ts +425 -0
  140. package/src/render-background.ts +59 -0
  141. package/src/render-border.ts +167 -0
  142. package/src/render-frame.ts +83 -0
  143. package/src/render-to-string.ts +146 -0
  144. package/src/render.ts +284 -0
  145. package/src/router/components.tsx +343 -0
  146. package/src/router/context.ts +41 -0
  147. package/src/router/history.ts +194 -0
  148. package/src/router/hooks.tsx +391 -0
  149. package/src/router/index.ts +34 -0
  150. package/src/router/matcher.ts +571 -0
  151. package/src/sanitize-ansi.ts +33 -0
  152. package/src/screen/ansi.ts +184 -0
  153. package/src/screen/canvas.ts +160 -0
  154. package/src/screen/cell.ts +138 -0
  155. package/src/screen/color-profile.ts +47 -0
  156. package/src/screen/geometry.ts +9 -0
  157. package/src/screen/index.ts +6 -0
  158. package/src/screen/screen.ts +305 -0
  159. package/src/screen/serialize.ts +129 -0
  160. package/src/screen.ts +1 -0
  161. package/src/semantic-text-style.ts +118 -0
  162. package/src/signal-exit.ts +106 -0
  163. package/src/squash-text-nodes.ts +37 -0
  164. package/src/stream.ts +28 -0
  165. package/src/structured-text.ts +325 -0
  166. package/src/styles.ts +753 -0
  167. package/src/terminal/index.ts +2 -0
  168. package/src/terminal/inline-presenter.ts +120 -0
  169. package/src/terminal/input.ts +92 -0
  170. package/src/terminal/render-scheduler.ts +37 -0
  171. package/src/terminal/screen-presenter.ts +242 -0
  172. package/src/terminal/session.ts +408 -0
  173. package/src/terminal-size.ts +58 -0
  174. package/src/terminal.ts +1 -0
  175. package/src/testing/browser.ts +588 -0
  176. package/src/testing/emulators.ts +205 -0
  177. package/src/testing/explorer-app/index.html +12 -0
  178. package/src/testing/explorer-app/main.ts +381 -0
  179. package/src/testing/explorer-app/style.css +194 -0
  180. package/src/testing/explorer-app/tsconfig.json +15 -0
  181. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  182. package/src/testing/index.ts +26 -0
  183. package/src/testing/keys.ts +56 -0
  184. package/src/testing/live.ts +85 -0
  185. package/src/testing/matchers.ts +70 -0
  186. package/src/testing/public.ts +94 -0
  187. package/src/testing/terminal.ts +361 -0
  188. package/src/testing/vitest.ts +157 -0
  189. package/src/throttle.ts +73 -0
  190. package/src/transform-adapter.ts +14 -0
  191. package/src/types.ts +10 -0
  192. package/src/virtual-scroll.ts +133 -0
  193. package/src/wrap-text.ts +54 -0
  194. package/src/yoga/config.ts +57 -0
  195. package/src/yoga/core/absoluteLayout.ts +626 -0
  196. package/src/yoga/core/baseline.ts +66 -0
  197. package/src/yoga/core/cache.ts +136 -0
  198. package/src/yoga/core/calculateLayout.ts +2926 -0
  199. package/src/yoga/core/config.ts +104 -0
  200. package/src/yoga/core/flexLine.ts +177 -0
  201. package/src/yoga/core/helpers.ts +293 -0
  202. package/src/yoga/core/layoutResults.ts +167 -0
  203. package/src/yoga/core/node.ts +611 -0
  204. package/src/yoga/core/numeric.ts +44 -0
  205. package/src/yoga/core/pixelGrid.ts +151 -0
  206. package/src/yoga/core/style.ts +887 -0
  207. package/src/yoga/core/types.ts +224 -0
  208. package/src/yoga/generated/YGEnums.ts +263 -0
  209. package/src/yoga/index.ts +19 -0
  210. package/src/yoga/node.ts +1140 -0
  211. package/dist/index.d.mts +0 -2379
  212. package/dist/index.mjs +0 -10072
package/dist/index.js ADDED
@@ -0,0 +1,3209 @@
1
+ import { C as bsu, S as ansiEscapes, V as esu, W as link, _ as CSI } from "./sgr-BhwaWAJB.js";
2
+ import { h as tokenizeAnsi, p as graphemes } from "./tokenize-AjqbvtiT.js";
3
+ import { t as stringWidth } from "./string-width-CijQwpIk.js";
4
+ import { n as sliceAnsi, r as wrapAnsi } from "./truncate-D31fhU6i.js";
5
+ import { _ as squashTextNodes, a as reconciler, c as kittyModifiers, d as FocusContext, f as Text, g as transformAnsiLine, h as emitLayoutListeners, i as useStdinContext, l as resolveFlags, m as createNode, n as useInput, o as detectKittySupport, p as addLayoutListener, r as useStdin, s as kittyFlags, t as useFocus, u as StdinContext, v as accessibilityContext } from "./use-focus-CcCIdCWA.js";
6
+ import { a as isTty, i as isSigilDev, o as isWindows, r as isScreenReader, t as isInCi } from "./env-YVw64yZS.js";
7
+ import { a as detectTerminal, i as detectHyperlinkSupport, n as detectCapabilities, o as detectUnicodeSupport, r as detectColorLevel, s as signalExit, t as createSupportsColor } from "./detect-B3dL4Q11.js";
8
+ import { u as cliCursor } from "./osc-BFKKSqpg.js";
9
+ import { a as registerTerminalIntegration, c as ensureTerminalQuery, f as queryTerminal, i as getCapabilities, l as getTerminalQuery, o as getRawModeStream, p as refreshTerminalQuery, r as capabilities, s as applyTerminalQuery, u as getTerminalQueryPromise } from "./color-policy-BAC9-TZX.js";
10
+ import { a as buildCursorSuffix, c as hideCursorEscape, i as buildCursorOnlySequence, o as buildReturnToBottomPrefix, s as cursorPositionChanged, t as TerminalSession } from "./session-DDQ5V300.js";
11
+ import { t as Yoga } from "./yoga-5jKhYCJC.js";
12
+ import { r as createCell, t as cellAttributes } from "./cell-_ZVhbfl0.js";
13
+ import { i as semanticTextStyleToCellStyle, n as mergeSemanticTextStyles, t as emptySemanticTextStyle } from "./semantic-text-style-DIMzC7xt.js";
14
+ import { a as samplePaint } from "./sample-Cqw1bjUL.js";
15
+ import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-CiPytswf.js";
16
+ import { t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
17
+ import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
18
+ import { t as stripAnsi } from "./strip-BvU4toXG.js";
19
+ import "./color-CkbalRqK.js";
20
+ import { PassThrough, Stream } from "node:stream";
21
+ import { setImmediate as setImmediate$1 } from "node:timers/promises";
22
+ import { isNativeError } from "node:util/types";
23
+ import { PureComponent, createContext, useCallback, useContext, useEffect, useEffectEvent, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
24
+ import "react-reconciler";
25
+ import { ConcurrentRoot, LegacyRoot } from "react-reconciler/constants.js";
26
+ import { EventEmitter } from "node:events";
27
+ import { existsSync, readFileSync } from "node:fs";
28
+ import { cwd } from "node:process";
29
+ import { jsx, jsxs } from "react/jsx-runtime";
30
+ import { Console } from "node:console";
31
+ //#region src/components/AnimationContext.ts
32
+ const animationContext = createContext({
33
+ renderThrottleMs: 0,
34
+ subscribe() {
35
+ return {
36
+ startTime: 0,
37
+ unsubscribe() {}
38
+ };
39
+ }
40
+ });
41
+ animationContext.displayName = "InternalAnimationContext";
42
+ //#endregion
43
+ //#region src/components/AppContext.ts
44
+ /**
45
+ `AppContext` is a React context that exposes lifecycle methods for the app.
46
+ */
47
+ const noopSuspension = {
48
+ async resume() {},
49
+ async [Symbol.asyncDispose]() {}
50
+ };
51
+ const AppContext = createContext({
52
+ exit(_errorOrResult) {},
53
+ async waitUntilRenderFlush() {},
54
+ suspendTerminal: (async (callback) => {
55
+ if (callback) {
56
+ await callback();
57
+ return;
58
+ }
59
+ return noopSuspension;
60
+ })
61
+ });
62
+ AppContext.displayName = "InternalAppContext";
63
+ //#endregion
64
+ //#region src/components/CursorContext.ts
65
+ const CursorContext = createContext({ setCursorPosition() {} });
66
+ CursorContext.displayName = "InternalCursorContext";
67
+ //#endregion
68
+ //#region src/code-excerpt.ts
69
+ const tabsToSpaces = (input, spaces = 2) => input.replaceAll(/^\t+/gm, (tabs) => " ".repeat(tabs.length * spaces));
70
+ const generateLineNumbers = (line, around) => {
71
+ const lineNumbers = [];
72
+ for (let lineNumber = line - around; lineNumber <= line + around; lineNumber++) lineNumbers.push(lineNumber);
73
+ return lineNumbers;
74
+ };
75
+ const codeExcerpt = (source, line, options = {}) => {
76
+ if (!line || line < 1) throw new TypeError("Line number must start from `1`.");
77
+ const lines = tabsToSpaces(source).split(/\r?\n/);
78
+ if (line > lines.length) return;
79
+ return generateLineNumbers(line, options.around ?? 3).filter((lineNumber) => lines[lineNumber - 1] !== void 0).map((lineNumber) => ({
80
+ line: lineNumber,
81
+ value: lines[lineNumber - 1]
82
+ }));
83
+ };
84
+ //#endregion
85
+ //#region src/glyphs.ts
86
+ const BOXES = {
87
+ single: {
88
+ topLeft: "┌",
89
+ top: "─",
90
+ topRight: "┐",
91
+ right: "│",
92
+ bottomRight: "┘",
93
+ bottom: "─",
94
+ bottomLeft: "└",
95
+ left: "│"
96
+ },
97
+ double: {
98
+ topLeft: "╔",
99
+ top: "═",
100
+ topRight: "╗",
101
+ right: "║",
102
+ bottomRight: "╝",
103
+ bottom: "═",
104
+ bottomLeft: "╚",
105
+ left: "║"
106
+ },
107
+ round: {
108
+ topLeft: "╭",
109
+ top: "─",
110
+ topRight: "╮",
111
+ right: "│",
112
+ bottomRight: "╯",
113
+ bottom: "─",
114
+ bottomLeft: "╰",
115
+ left: "│"
116
+ },
117
+ bold: {
118
+ topLeft: "┏",
119
+ top: "━",
120
+ topRight: "┓",
121
+ right: "┃",
122
+ bottomRight: "┛",
123
+ bottom: "━",
124
+ bottomLeft: "┗",
125
+ left: "┃"
126
+ },
127
+ singleDouble: {
128
+ topLeft: "╓",
129
+ top: "─",
130
+ topRight: "╖",
131
+ right: "║",
132
+ bottomRight: "╜",
133
+ bottom: "─",
134
+ bottomLeft: "╙",
135
+ left: "║"
136
+ },
137
+ doubleSingle: {
138
+ topLeft: "╒",
139
+ top: "═",
140
+ topRight: "╕",
141
+ right: "│",
142
+ bottomRight: "╛",
143
+ bottom: "═",
144
+ bottomLeft: "╘",
145
+ left: "│"
146
+ },
147
+ classic: {
148
+ topLeft: "+",
149
+ top: "-",
150
+ topRight: "+",
151
+ right: "|",
152
+ bottomRight: "+",
153
+ bottom: "-",
154
+ bottomLeft: "+",
155
+ left: "|"
156
+ },
157
+ arrow: {
158
+ topLeft: "↘",
159
+ top: "↓",
160
+ topRight: "↙",
161
+ right: "←",
162
+ bottomRight: "↖",
163
+ bottom: "↑",
164
+ bottomLeft: "↗",
165
+ left: "→"
166
+ }
167
+ };
168
+ //#endregion
169
+ //#region src/components/Box.tsx
170
+ /** @jsxImportSource react */
171
+ /**
172
+ `<Box>` is an essential Ink component to build your layout. It's like `<div style="display: flex">` in the browser.
173
+ */
174
+ function Box({ children, ref, backgroundColor, "aria-label": ariaLabel, "aria-hidden": ariaHidden, "aria-role": role, "aria-state": ariaState, ...style }) {
175
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
176
+ const label = ariaLabel ? /* @__PURE__ */ jsx("ink-text", { children: ariaLabel }) : void 0;
177
+ if (isScreenReaderEnabled && ariaHidden) return null;
178
+ return /* @__PURE__ */ jsx("ink-box", {
179
+ ref,
180
+ style: {
181
+ flexWrap: "nowrap",
182
+ flexDirection: "row",
183
+ flexGrow: 0,
184
+ flexShrink: 1,
185
+ ...style,
186
+ backgroundColor,
187
+ overflowX: style.overflowX ?? style.overflow ?? "visible",
188
+ overflowY: style.overflowY ?? style.overflow ?? "visible"
189
+ },
190
+ internal_accessibility: {
191
+ role,
192
+ state: ariaState
193
+ },
194
+ children: isScreenReaderEnabled && label ? label : children
195
+ });
196
+ }
197
+ //#endregion
198
+ //#region src/parse-stack-line.ts
199
+ const lineRegExp = new RegExp("^" + String.raw`(?:\s*at )?` + "(?:(new) )?" + String.raw`(?:(.*?) \()?` + String.raw`(?:eval at ([^ ]+) \((.+?):(\d+):(\d+)\), )?` + String.raw`(?:(.+?):(\d+):(\d+)|(native))` + String.raw`(\)?)$`);
200
+ const methodRegExp = /^(.*?) \[as (.*?)]$/;
201
+ const cwd$1 = process.cwd().replaceAll("\\", "/");
202
+ const setFile = (result, filename) => {
203
+ if (filename) {
204
+ filename = filename.replaceAll("\\", "/");
205
+ if (filename.startsWith(`${cwd$1}/`)) filename = filename.slice(cwd$1.length + 1);
206
+ result.file = filename;
207
+ }
208
+ };
209
+ const parseStackLine = (line) => {
210
+ const match = lineRegExp.exec(line);
211
+ if (!match) return;
212
+ let functionName = match[2];
213
+ const evalOrigin = match[3];
214
+ const evalFile = match[4];
215
+ const evalLine = Number(match[5]);
216
+ const evalColumn = Number(match[6]);
217
+ let file = match[7];
218
+ const lineNumber = match[8];
219
+ const columnNumber = match[9];
220
+ const native = match[10] === "native";
221
+ const closeParen = match[11] === ")";
222
+ let method;
223
+ const result = {};
224
+ if (lineNumber) result.line = Number(lineNumber);
225
+ if (columnNumber) result.column = Number(columnNumber);
226
+ if (closeParen && file) {
227
+ let closes = 0;
228
+ for (let index = file.length - 1; index > 0; index--) if (file.charAt(index) === ")") closes++;
229
+ else if (file.charAt(index) === "(" && file.charAt(index - 1) === " ") {
230
+ closes--;
231
+ if (closes === -1 && file.charAt(index - 1) === " ") {
232
+ const before = file.slice(0, index - 1);
233
+ file = file.slice(index + 1);
234
+ functionName += ` (${before}`;
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ if (functionName) {
240
+ const methodMatch = methodRegExp.exec(functionName);
241
+ if (methodMatch) {
242
+ functionName = methodMatch[1];
243
+ method = methodMatch[2];
244
+ }
245
+ }
246
+ if (file) setFile(result, file);
247
+ if (evalOrigin) {
248
+ result.evalOrigin = evalOrigin;
249
+ result.evalLine = evalLine;
250
+ result.evalColumn = evalColumn;
251
+ result.evalFile = evalFile?.replaceAll("\\", "/");
252
+ }
253
+ if (native) result.native = true;
254
+ if (functionName) result.function = functionName;
255
+ if (method && functionName !== method) result.method = method;
256
+ return result;
257
+ };
258
+ //#endregion
259
+ //#region src/components/ErrorOverview.tsx
260
+ /** @jsxImportSource react */
261
+ const cleanupPath = (path) => {
262
+ return path?.replace(`file://${cwd()}/`, "");
263
+ };
264
+ function ErrorOverview({ error }) {
265
+ const stack = error.stack ? error.stack.split("\n").slice(1) : void 0;
266
+ const origin = stack ? parseStackLine(stack[0]) : void 0;
267
+ const filePath = cleanupPath(origin?.file);
268
+ let excerpt;
269
+ let lineWidth = 0;
270
+ const stackLineCounts = /* @__PURE__ */ new Map();
271
+ if (filePath && origin?.line && existsSync(filePath)) {
272
+ const sourceCode = readFileSync(filePath, "utf8");
273
+ excerpt = codeExcerpt(sourceCode, origin.line);
274
+ if (excerpt) for (const { line } of excerpt) lineWidth = Math.max(lineWidth, String(line).length);
275
+ }
276
+ return /* @__PURE__ */ jsxs(Box, {
277
+ flexDirection: "column",
278
+ padding: 1,
279
+ children: [
280
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, {
281
+ backgroundColor: "red",
282
+ color: "white",
283
+ children: [
284
+ " ",
285
+ "ERROR",
286
+ " "
287
+ ]
288
+ }), /* @__PURE__ */ jsxs(Text, { children: [" ", error.message] })] }),
289
+ origin && filePath ? /* @__PURE__ */ jsx(Box, {
290
+ marginTop: 1,
291
+ children: /* @__PURE__ */ jsxs(Text, {
292
+ dimColor: true,
293
+ children: [
294
+ filePath,
295
+ ":",
296
+ origin.line,
297
+ ":",
298
+ origin.column
299
+ ]
300
+ })
301
+ }) : null,
302
+ origin && excerpt ? /* @__PURE__ */ jsx(Box, {
303
+ marginTop: 1,
304
+ flexDirection: "column",
305
+ children: excerpt.map(({ line, value }) => /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Box, {
306
+ width: lineWidth + 1,
307
+ children: /* @__PURE__ */ jsxs(Text, {
308
+ dimColor: line !== origin.line,
309
+ backgroundColor: line === origin.line ? "red" : void 0,
310
+ color: line === origin.line ? "white" : void 0,
311
+ "aria-label": line === origin.line ? `Line ${line}, error` : `Line ${line}`,
312
+ children: [String(line).padStart(lineWidth, " "), ":"]
313
+ })
314
+ }), /* @__PURE__ */ jsx(Text, {
315
+ backgroundColor: line === origin.line ? "red" : void 0,
316
+ color: line === origin.line ? "white" : void 0,
317
+ children: " " + value
318
+ }, line)] }, line))
319
+ }) : null,
320
+ error.stack ? /* @__PURE__ */ jsx(Box, {
321
+ marginTop: 1,
322
+ flexDirection: "column",
323
+ children: error.stack.split("\n").slice(1).map((line) => {
324
+ const parsedLine = parseStackLine(line);
325
+ const lineCount = stackLineCounts.get(line) ?? 0;
326
+ stackLineCounts.set(line, lineCount + 1);
327
+ const key = `${line}-${lineCount}`;
328
+ if (!parsedLine?.file || !parsedLine.line || !parsedLine.column) return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
329
+ dimColor: true,
330
+ children: "- "
331
+ }), /* @__PURE__ */ jsxs(Text, {
332
+ dimColor: true,
333
+ bold: true,
334
+ children: [
335
+ line,
336
+ "\\t",
337
+ " "
338
+ ]
339
+ })] }, key);
340
+ return /* @__PURE__ */ jsxs(Box, { children: [
341
+ /* @__PURE__ */ jsx(Text, {
342
+ dimColor: true,
343
+ children: "- "
344
+ }),
345
+ /* @__PURE__ */ jsx(Text, {
346
+ dimColor: true,
347
+ bold: true,
348
+ children: parsedLine.function
349
+ }),
350
+ /* @__PURE__ */ jsxs(Text, {
351
+ dimColor: true,
352
+ color: "gray",
353
+ "aria-label": `at ${cleanupPath(parsedLine.file) ?? ""} line ${parsedLine.line} column ${parsedLine.column}`,
354
+ children: [
355
+ " ",
356
+ "(",
357
+ cleanupPath(parsedLine.file) ?? "",
358
+ ":",
359
+ parsedLine.line,
360
+ ":",
361
+ parsedLine.column,
362
+ ")"
363
+ ]
364
+ })
365
+ ] }, key);
366
+ })
367
+ }) : null
368
+ ]
369
+ });
370
+ }
371
+ //#endregion
372
+ //#region src/components/ErrorBoundary.tsx
373
+ /** @jsxImportSource react */
374
+ var ErrorBoundary = class extends PureComponent {
375
+ static displayName = "InternalErrorBoundary";
376
+ static getDerivedStateFromError(error) {
377
+ return { error };
378
+ }
379
+ state = { error: void 0 };
380
+ componentDidCatch(error) {
381
+ this.props.onError(error);
382
+ }
383
+ render() {
384
+ if (this.state.error) return /* @__PURE__ */ jsx(ErrorOverview, { error: this.state.error });
385
+ return this.props.children;
386
+ }
387
+ };
388
+ //#endregion
389
+ //#region src/components/StderrContext.ts
390
+ /**
391
+ `StderrContext` is a React context that exposes the stderr stream.
392
+ */
393
+ const StderrContext = createContext({
394
+ stderr: process.stderr,
395
+ write() {}
396
+ });
397
+ StderrContext.displayName = "InternalStderrContext";
398
+ //#endregion
399
+ //#region src/components/StdoutContext.ts
400
+ /**
401
+ `StdoutContext` is a React context that exposes the stdout stream where Ink renders your app.
402
+ */
403
+ const StdoutContext = createContext({
404
+ stdout: process.stdout,
405
+ write() {}
406
+ });
407
+ StdoutContext.displayName = "InternalStdoutContext";
408
+ //#endregion
409
+ //#region src/components/App.tsx
410
+ /** @jsxImportSource react */
411
+ const tab = " ";
412
+ const shiftTab = `${CSI}Z`;
413
+ const escape = "\x1B";
414
+ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, exitOnCtrlC, onExit, onWaitUntilRenderFlush, onSuspendTerminal, onRegisterInputControl, setCursorPosition, interactive, renderThrottleMs, terminalInput }) {
415
+ const [isFocusEnabled, setIsFocusEnabled] = useState(true);
416
+ const [activeFocusId, setActiveFocusId] = useState(void 0);
417
+ const [, setFocusables] = useState([]);
418
+ const focusablesCountRef = useRef(0);
419
+ const animationSubscribersRef = useRef(/* @__PURE__ */ new Map());
420
+ const animationTimerRef = useRef(void 0);
421
+ const rawModeEnabledCount = useRef(0);
422
+ const pendingDisableRawModeRef = useRef(false);
423
+ const bracketedPasteModeEnabledCount = useRef(0);
424
+ const internal_eventEmitter = useRef(new EventEmitter());
425
+ internal_eventEmitter.current.setMaxListeners(Infinity);
426
+ const readableListenerRef = useRef(void 0);
427
+ const isMountedRef = useRef(true);
428
+ const pendingInputFlushRef = useRef(void 0);
429
+ const pendingInputFlushDelayMilliseconds = 20;
430
+ const clearPendingInputFlush = useCallback(() => {
431
+ if (!pendingInputFlushRef.current) return;
432
+ clearTimeout(pendingInputFlushRef.current);
433
+ pendingInputFlushRef.current = void 0;
434
+ }, []);
435
+ const clearAnimationTimer = useCallback(() => {
436
+ if (!animationTimerRef.current) return;
437
+ clearTimeout(animationTimerRef.current);
438
+ animationTimerRef.current = void 0;
439
+ }, []);
440
+ const scheduleAnimationTick = useCallback(() => {
441
+ clearAnimationTimer();
442
+ if (animationSubscribersRef.current.size === 0) return;
443
+ let nextDueTime = Number.POSITIVE_INFINITY;
444
+ for (const subscriber of animationSubscribersRef.current.values()) nextDueTime = Math.min(nextDueTime, subscriber.nextDueTime);
445
+ const delay = Math.max(0, nextDueTime - performance.now());
446
+ animationTimerRef.current = setTimeout(() => {
447
+ animationTimerRef.current = void 0;
448
+ const currentTime = performance.now();
449
+ for (const subscriber of animationSubscribersRef.current.values()) {
450
+ if (currentTime < subscriber.nextDueTime) continue;
451
+ subscriber.callback(currentTime);
452
+ const elapsedTime = currentTime - subscriber.startTime;
453
+ const elapsedFrames = Math.floor(elapsedTime / subscriber.interval) + 1;
454
+ subscriber.nextDueTime = subscriber.startTime + elapsedFrames * subscriber.interval;
455
+ }
456
+ scheduleAnimationTick();
457
+ }, delay);
458
+ }, [clearAnimationTimer]);
459
+ const animationSubscribe = useCallback((callback, interval) => {
460
+ const startTime = performance.now();
461
+ animationSubscribersRef.current.set(callback, {
462
+ callback,
463
+ interval,
464
+ startTime,
465
+ nextDueTime: startTime + interval
466
+ });
467
+ scheduleAnimationTick();
468
+ return {
469
+ startTime,
470
+ unsubscribe() {
471
+ animationSubscribersRef.current.delete(callback);
472
+ if (animationSubscribersRef.current.size === 0) {
473
+ clearAnimationTimer();
474
+ return;
475
+ }
476
+ scheduleAnimationTick();
477
+ }
478
+ };
479
+ }, [clearAnimationTimer, scheduleAnimationTick]);
480
+ useEffect(() => {
481
+ return () => {
482
+ clearAnimationTimer();
483
+ };
484
+ }, [clearAnimationTimer]);
485
+ const rawModeStdin = getRawModeStream(stdin);
486
+ const isRawModeSupported = rawModeStdin !== void 0;
487
+ const detachReadableListener = useCallback(() => {
488
+ if (!readableListenerRef.current) return;
489
+ stdin.removeListener("readable", readableListenerRef.current);
490
+ readableListenerRef.current = void 0;
491
+ }, [stdin]);
492
+ const clearInputState = useCallback(() => {
493
+ terminalInput.reset();
494
+ clearPendingInputFlush();
495
+ detachReadableListener();
496
+ }, [
497
+ clearPendingInputFlush,
498
+ detachReadableListener,
499
+ terminalInput
500
+ ]);
501
+ const disableRawMode = useCallback(() => {
502
+ if (!rawModeStdin) return;
503
+ pendingDisableRawModeRef.current = false;
504
+ rawModeStdin.setRawMode(false);
505
+ rawModeStdin.unref?.();
506
+ rawModeEnabledCount.current = 0;
507
+ clearInputState();
508
+ }, [rawModeStdin, clearInputState]);
509
+ const handleExit = useCallback((errorOrResult) => {
510
+ if (isRawModeSupported && (rawModeEnabledCount.current > 0 || pendingDisableRawModeRef.current)) disableRawMode();
511
+ onExit(errorOrResult);
512
+ }, [
513
+ isRawModeSupported,
514
+ disableRawMode,
515
+ onExit
516
+ ]);
517
+ const handleInput = useCallback((input) => {
518
+ if (input === "" && exitOnCtrlC) {
519
+ handleExit();
520
+ return;
521
+ }
522
+ if (input === escape && isFocusEnabled) setActiveFocusId(void 0);
523
+ }, [
524
+ exitOnCtrlC,
525
+ handleExit,
526
+ isFocusEnabled
527
+ ]);
528
+ const emitInput = useCallback((input) => {
529
+ handleInput(input);
530
+ internal_eventEmitter.current.emit("input", input);
531
+ }, [handleInput]);
532
+ const schedulePendingInputFlush = useCallback(() => {
533
+ clearPendingInputFlush();
534
+ pendingInputFlushRef.current = setTimeout(() => {
535
+ pendingInputFlushRef.current = void 0;
536
+ const pendingEscape = terminalInput.flushPendingEscape();
537
+ if (!pendingEscape) return;
538
+ emitInput(pendingEscape);
539
+ }, pendingInputFlushDelayMilliseconds);
540
+ }, [
541
+ clearPendingInputFlush,
542
+ emitInput,
543
+ terminalInput
544
+ ]);
545
+ const handleReadable = useCallback(() => {
546
+ clearPendingInputFlush();
547
+ let chunk;
548
+ while ((chunk = stdin.read()) !== null) {
549
+ const inputEvents = terminalInput.push(chunk);
550
+ for (const event of inputEvents) if (typeof event === "string") emitInput(event);
551
+ else {
552
+ if (internal_eventEmitter.current.listenerCount("paste") === 0) {
553
+ emitInput(event.paste);
554
+ continue;
555
+ }
556
+ internal_eventEmitter.current.emit("paste", event.paste);
557
+ }
558
+ }
559
+ if (terminalInput.hasPendingEscape()) schedulePendingInputFlush();
560
+ }, [
561
+ stdin,
562
+ emitInput,
563
+ clearPendingInputFlush,
564
+ schedulePendingInputFlush,
565
+ terminalInput
566
+ ]);
567
+ const attachReadableListener = useCallback(() => {
568
+ if (readableListenerRef.current) return;
569
+ readableListenerRef.current = handleReadable;
570
+ stdin.addListener("readable", handleReadable);
571
+ }, [stdin, handleReadable]);
572
+ const handleSetRawMode = useCallback((isEnabled) => {
573
+ if (!rawModeStdin) {
574
+ if (stdin === process.stdin) throw new Error("Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported");
575
+ else throw new Error("Raw mode is not supported on the stdin provided to Ink.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported");
576
+ }
577
+ rawModeStdin.setEncoding("utf8");
578
+ if (isEnabled) {
579
+ if (rawModeEnabledCount.current === 0) {
580
+ const isRawModeAlreadyEnabled = pendingDisableRawModeRef.current;
581
+ pendingDisableRawModeRef.current = false;
582
+ if (!isRawModeAlreadyEnabled) {
583
+ rawModeStdin.ref?.();
584
+ rawModeStdin.setRawMode(true);
585
+ }
586
+ attachReadableListener();
587
+ }
588
+ rawModeEnabledCount.current++;
589
+ return;
590
+ }
591
+ if (rawModeEnabledCount.current === 0) return;
592
+ if (--rawModeEnabledCount.current === 0) {
593
+ clearInputState();
594
+ pendingDisableRawModeRef.current = true;
595
+ queueMicrotask(() => {
596
+ if (!pendingDisableRawModeRef.current) return;
597
+ disableRawMode();
598
+ });
599
+ }
600
+ }, [
601
+ rawModeStdin,
602
+ stdin,
603
+ attachReadableListener,
604
+ clearInputState,
605
+ disableRawMode
606
+ ]);
607
+ const handleQueryTerminal = useCallback(async ({ refresh = false } = {}) => {
608
+ if (!interactive || !isRawModeSupported || !stdout.isTTY) return;
609
+ if (!refresh) {
610
+ const existing = getTerminalQueryPromise(stdout);
611
+ if (existing) return existing;
612
+ }
613
+ handleSetRawMode(true);
614
+ detachReadableListener();
615
+ try {
616
+ return await (refresh ? refreshTerminalQuery(stdin, stdout) : ensureTerminalQuery(stdin, stdout));
617
+ } finally {
618
+ if (isMountedRef.current) attachReadableListener();
619
+ handleSetRawMode(false);
620
+ }
621
+ }, [
622
+ interactive,
623
+ isRawModeSupported,
624
+ stdin,
625
+ stdout,
626
+ handleSetRawMode,
627
+ detachReadableListener,
628
+ attachReadableListener
629
+ ]);
630
+ const reportFeedActiveRef = useRef(false);
631
+ const handleSetReportFeed = useCallback((active) => {
632
+ if (active === reportFeedActiveRef.current || !isRawModeSupported) return;
633
+ reportFeedActiveRef.current = active;
634
+ handleSetRawMode(active);
635
+ }, [isRawModeSupported, handleSetRawMode]);
636
+ useInsertionEffect(() => {
637
+ const unregister = registerTerminalIntegration(stdout, {
638
+ runQuery: handleQueryTerminal,
639
+ setReportFeed: handleSetReportFeed
640
+ });
641
+ return () => {
642
+ unregister();
643
+ handleSetReportFeed(false);
644
+ };
645
+ }, [
646
+ stdout,
647
+ handleQueryTerminal,
648
+ handleSetReportFeed
649
+ ]);
650
+ const handleSetBracketedPasteMode = useCallback((isEnabled) => {
651
+ if (!stdout.isTTY) return;
652
+ if (isEnabled) {
653
+ if (bracketedPasteModeEnabledCount.current === 0) stdout.write(ansiEscapes.enableBracketedPaste);
654
+ bracketedPasteModeEnabledCount.current++;
655
+ return;
656
+ }
657
+ if (bracketedPasteModeEnabledCount.current === 0) return;
658
+ if (--bracketedPasteModeEnabledCount.current === 0) stdout.write(ansiEscapes.disableBracketedPaste);
659
+ }, [stdout]);
660
+ const suspendedInputStateRef = useRef({
661
+ rawMode: false,
662
+ bracketedPaste: false
663
+ });
664
+ const pauseInput = useCallback(() => {
665
+ const wasRawMode = isRawModeSupported && rawModeEnabledCount.current > 0;
666
+ const wasBracketedPaste = bracketedPasteModeEnabledCount.current > 0;
667
+ suspendedInputStateRef.current = {
668
+ rawMode: wasRawMode,
669
+ bracketedPaste: wasBracketedPaste
670
+ };
671
+ if (wasBracketedPaste && stdout.isTTY) try {
672
+ stdout.write(ansiEscapes.disableBracketedPaste);
673
+ } catch {}
674
+ if (wasRawMode) {
675
+ rawModeStdin?.setRawMode(false);
676
+ rawModeStdin?.unref?.();
677
+ clearInputState();
678
+ }
679
+ }, [
680
+ isRawModeSupported,
681
+ rawModeStdin,
682
+ stdout,
683
+ clearInputState
684
+ ]);
685
+ const resumeInput = useCallback(() => {
686
+ const { rawMode, bracketedPaste } = suspendedInputStateRef.current;
687
+ if (rawMode) {
688
+ rawModeStdin?.setEncoding("utf8");
689
+ rawModeStdin?.ref?.();
690
+ rawModeStdin?.setRawMode(true);
691
+ attachReadableListener();
692
+ }
693
+ if (bracketedPaste && stdout.isTTY) try {
694
+ stdout.write(ansiEscapes.enableBracketedPaste);
695
+ } catch {}
696
+ }, [
697
+ rawModeStdin,
698
+ stdout,
699
+ attachReadableListener
700
+ ]);
701
+ useInsertionEffect(() => {
702
+ onRegisterInputControl(pauseInput, resumeInput);
703
+ }, [
704
+ onRegisterInputControl,
705
+ pauseInput,
706
+ resumeInput
707
+ ]);
708
+ const findNextFocusable = useCallback((currentFocusables, currentActiveFocusId) => {
709
+ const activeIndex = currentFocusables.findIndex((focusable) => {
710
+ return focusable.id === currentActiveFocusId;
711
+ });
712
+ for (let index = activeIndex + 1; index < currentFocusables.length; index++) {
713
+ const focusable = currentFocusables[index];
714
+ if (focusable?.isActive) return focusable.id;
715
+ }
716
+ }, []);
717
+ const findPreviousFocusable = useCallback((currentFocusables, currentActiveFocusId) => {
718
+ const activeIndex = currentFocusables.findIndex((focusable) => {
719
+ return focusable.id === currentActiveFocusId;
720
+ });
721
+ for (let index = activeIndex - 1; index >= 0; index--) {
722
+ const focusable = currentFocusables[index];
723
+ if (focusable?.isActive) return focusable.id;
724
+ }
725
+ }, []);
726
+ const focusNext = useCallback(() => {
727
+ setFocusables((currentFocusables) => {
728
+ setActiveFocusId((currentActiveFocusId) => {
729
+ const firstFocusableId = currentFocusables.find((focusable) => focusable.isActive)?.id;
730
+ return findNextFocusable(currentFocusables, currentActiveFocusId) ?? firstFocusableId;
731
+ });
732
+ return currentFocusables;
733
+ });
734
+ }, [findNextFocusable]);
735
+ const focusPrevious = useCallback(() => {
736
+ setFocusables((currentFocusables) => {
737
+ setActiveFocusId((currentActiveFocusId) => {
738
+ const lastFocusableId = currentFocusables.findLast((focusable) => focusable.isActive)?.id;
739
+ return findPreviousFocusable(currentFocusables, currentActiveFocusId) ?? lastFocusableId;
740
+ });
741
+ return currentFocusables;
742
+ });
743
+ }, [findPreviousFocusable]);
744
+ useEffect(() => {
745
+ const handleTabNavigation = (input) => {
746
+ if (!isFocusEnabled || focusablesCountRef.current === 0) return;
747
+ if (input === tab) focusNext();
748
+ if (input === shiftTab) focusPrevious();
749
+ };
750
+ internal_eventEmitter.current.on("input", handleTabNavigation);
751
+ const emitter = internal_eventEmitter.current;
752
+ return () => {
753
+ emitter.off("input", handleTabNavigation);
754
+ };
755
+ }, [
756
+ isFocusEnabled,
757
+ focusNext,
758
+ focusPrevious
759
+ ]);
760
+ const enableFocus = useCallback(() => {
761
+ setIsFocusEnabled(true);
762
+ }, []);
763
+ const disableFocus = useCallback(() => {
764
+ setIsFocusEnabled(false);
765
+ }, []);
766
+ const focus = useCallback((id) => {
767
+ setFocusables((currentFocusables) => {
768
+ if (currentFocusables.some((focusable) => focusable?.id === id)) setActiveFocusId(id);
769
+ return currentFocusables;
770
+ });
771
+ }, []);
772
+ const addFocusable = useCallback((id, { autoFocus }) => {
773
+ setFocusables((currentFocusables) => {
774
+ focusablesCountRef.current = currentFocusables.length + 1;
775
+ return [...currentFocusables, {
776
+ id,
777
+ isActive: true
778
+ }];
779
+ });
780
+ if (autoFocus) setActiveFocusId((currentActiveFocusId) => {
781
+ if (!currentActiveFocusId) return id;
782
+ return currentActiveFocusId;
783
+ });
784
+ }, []);
785
+ const removeFocusable = useCallback((id) => {
786
+ setActiveFocusId((currentActiveFocusId) => {
787
+ if (currentActiveFocusId === id) return;
788
+ return currentActiveFocusId;
789
+ });
790
+ setFocusables((currentFocusables) => {
791
+ const filtered = currentFocusables.filter((focusable) => {
792
+ return focusable.id !== id;
793
+ });
794
+ focusablesCountRef.current = filtered.length;
795
+ return filtered;
796
+ });
797
+ }, []);
798
+ const activateFocusable = useCallback((id) => {
799
+ setFocusables((currentFocusables) => currentFocusables.map((focusable) => {
800
+ if (focusable.id !== id) return focusable;
801
+ return {
802
+ id,
803
+ isActive: true
804
+ };
805
+ }));
806
+ }, []);
807
+ const deactivateFocusable = useCallback((id) => {
808
+ setActiveFocusId((currentActiveFocusId) => {
809
+ if (currentActiveFocusId === id) return;
810
+ return currentActiveFocusId;
811
+ });
812
+ setFocusables((currentFocusables) => currentFocusables.map((focusable) => {
813
+ if (focusable.id !== id) return focusable;
814
+ return {
815
+ id,
816
+ isActive: false
817
+ };
818
+ }));
819
+ }, []);
820
+ useEffect(() => {
821
+ isMountedRef.current = true;
822
+ return () => {
823
+ isMountedRef.current = false;
824
+ const canWriteToStdout = !stdout.destroyed && !stdout.writableEnded;
825
+ if (interactive && canWriteToStdout) cliCursor.show(stdout);
826
+ if (isRawModeSupported && (rawModeEnabledCount.current > 0 || pendingDisableRawModeRef.current)) disableRawMode();
827
+ if (bracketedPasteModeEnabledCount.current > 0) {
828
+ if (stdout.isTTY && canWriteToStdout) stdout.write(ansiEscapes.disableBracketedPaste);
829
+ bracketedPasteModeEnabledCount.current = 0;
830
+ }
831
+ };
832
+ }, [
833
+ stdout,
834
+ isRawModeSupported,
835
+ disableRawMode,
836
+ interactive
837
+ ]);
838
+ const appContextValue = useMemo(() => ({
839
+ exit: handleExit,
840
+ waitUntilRenderFlush: onWaitUntilRenderFlush,
841
+ suspendTerminal: onSuspendTerminal
842
+ }), [
843
+ handleExit,
844
+ onWaitUntilRenderFlush,
845
+ onSuspendTerminal
846
+ ]);
847
+ const stdinContextValue = useMemo(() => ({
848
+ stdin,
849
+ setRawMode: handleSetRawMode,
850
+ setBracketedPasteMode: handleSetBracketedPasteMode,
851
+ isRawModeSupported,
852
+ internal_exitOnCtrlC: exitOnCtrlC,
853
+ internal_eventEmitter: internal_eventEmitter.current
854
+ }), [
855
+ stdin,
856
+ handleSetRawMode,
857
+ handleSetBracketedPasteMode,
858
+ isRawModeSupported,
859
+ exitOnCtrlC
860
+ ]);
861
+ const stdoutContextValue = useMemo(() => ({
862
+ stdout,
863
+ write: writeToStdout
864
+ }), [stdout, writeToStdout]);
865
+ const stderrContextValue = useMemo(() => ({
866
+ stderr,
867
+ write: writeToStderr
868
+ }), [stderr, writeToStderr]);
869
+ const cursorContextValue = useMemo(() => ({ setCursorPosition }), [setCursorPosition]);
870
+ const focusContextValue = useMemo(() => ({
871
+ activeId: activeFocusId,
872
+ add: addFocusable,
873
+ remove: removeFocusable,
874
+ activate: activateFocusable,
875
+ deactivate: deactivateFocusable,
876
+ enableFocus,
877
+ disableFocus,
878
+ focusNext,
879
+ focusPrevious,
880
+ focus
881
+ }), [
882
+ activeFocusId,
883
+ addFocusable,
884
+ removeFocusable,
885
+ activateFocusable,
886
+ deactivateFocusable,
887
+ enableFocus,
888
+ disableFocus,
889
+ focusNext,
890
+ focusPrevious,
891
+ focus
892
+ ]);
893
+ const animationContextValue = useMemo(() => ({
894
+ renderThrottleMs,
895
+ subscribe: animationSubscribe
896
+ }), [animationSubscribe, renderThrottleMs]);
897
+ return /* @__PURE__ */ jsx(AppContext.Provider, {
898
+ value: appContextValue,
899
+ children: /* @__PURE__ */ jsx(StdinContext.Provider, {
900
+ value: stdinContextValue,
901
+ children: /* @__PURE__ */ jsx(StdoutContext.Provider, {
902
+ value: stdoutContextValue,
903
+ children: /* @__PURE__ */ jsx(StderrContext.Provider, {
904
+ value: stderrContextValue,
905
+ children: /* @__PURE__ */ jsx(FocusContext.Provider, {
906
+ value: focusContextValue,
907
+ children: /* @__PURE__ */ jsx(animationContext.Provider, {
908
+ value: animationContextValue,
909
+ children: /* @__PURE__ */ jsx(CursorContext.Provider, {
910
+ value: cursorContextValue,
911
+ children: /* @__PURE__ */ jsx(ErrorBoundary, {
912
+ onError: handleExit,
913
+ children
914
+ })
915
+ })
916
+ })
917
+ })
918
+ })
919
+ })
920
+ })
921
+ });
922
+ }
923
+ App.displayName = "InternalApp";
924
+ //#endregion
925
+ //#region src/components/TerminalOscContext.ts
926
+ const noop$1 = () => {};
927
+ const TerminalOscContext = createContext({
928
+ publishProgress: noop$1,
929
+ copyToClipboard: noop$1,
930
+ publishTitle: noop$1,
931
+ setWorkingDirectory: noop$1,
932
+ notify: noop$1,
933
+ setPointerShape: noop$1
934
+ });
935
+ TerminalOscContext.displayName = "InternalTerminalOscContext";
936
+ //#endregion
937
+ //#region src/instances.ts
938
+ const instances = /* @__PURE__ */ new WeakMap();
939
+ //#endregion
940
+ //#region src/patch-console.ts
941
+ const consoleMethods = [
942
+ "assert",
943
+ "count",
944
+ "countReset",
945
+ "debug",
946
+ "dir",
947
+ "dirxml",
948
+ "error",
949
+ "group",
950
+ "groupCollapsed",
951
+ "groupEnd",
952
+ "info",
953
+ "log",
954
+ "table",
955
+ "time",
956
+ "timeEnd",
957
+ "timeLog",
958
+ "trace",
959
+ "warn"
960
+ ];
961
+ const patchConsole = (callback) => {
962
+ const stdout = new PassThrough();
963
+ const stderr = new PassThrough();
964
+ stdout.write = (data) => {
965
+ callback("stdout", String(data));
966
+ return true;
967
+ };
968
+ stderr.write = (data) => {
969
+ callback("stderr", String(data));
970
+ return true;
971
+ };
972
+ const internalConsole = new Console(stdout, stderr);
973
+ const originalMethods = /* @__PURE__ */ new Map();
974
+ for (const method of consoleMethods) {
975
+ originalMethods.set(method, console[method]);
976
+ console[method] = internalConsole[method];
977
+ }
978
+ return () => {
979
+ for (const method of consoleMethods) console[method] = originalMethods.get(method);
980
+ originalMethods.clear();
981
+ };
982
+ };
983
+ const patchStreamWrite = (stream, onData) => {
984
+ const originalWrite = stream.write;
985
+ const patchedWrite = (chunk, encodingOrCallback, callback) => {
986
+ onData(typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString() : String(chunk));
987
+ (typeof encodingOrCallback === "function" ? encodingOrCallback : typeof callback === "function" ? callback : void 0)?.();
988
+ return true;
989
+ };
990
+ stream.write = patchedWrite;
991
+ return () => {
992
+ stream.write = originalWrite;
993
+ };
994
+ };
995
+ //#endregion
996
+ //#region src/get-max-width.ts
997
+ const getMaxWidth = (yogaNode) => {
998
+ return yogaNode.getComputedWidth() - yogaNode.getComputedPadding(Yoga.EDGE_LEFT) - yogaNode.getComputedPadding(Yoga.EDGE_RIGHT) - yogaNode.getComputedBorder(Yoga.EDGE_LEFT) - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
999
+ };
1000
+ //#endregion
1001
+ //#region src/render-background.ts
1002
+ const renderBackground = (x, y, node, output) => {
1003
+ if (node.style.backgroundColor === void 0) return;
1004
+ const width = node.yogaNode.getComputedWidth();
1005
+ const height = node.yogaNode.getComputedHeight();
1006
+ const leftBorderWidth = node.style.borderStyle && node.style.borderLeft !== false ? 1 : 0;
1007
+ const rightBorderWidth = node.style.borderStyle && node.style.borderRight !== false ? 1 : 0;
1008
+ const topBorderHeight = node.style.borderStyle && node.style.borderTop !== false ? 1 : 0;
1009
+ const bottomBorderHeight = node.style.borderStyle && node.style.borderBottom !== false ? 1 : 0;
1010
+ const contentWidth = width - leftBorderWidth - rightBorderWidth;
1011
+ const contentHeight = height - topBorderHeight - bottomBorderHeight;
1012
+ if (!(contentWidth > 0 && contentHeight > 0)) return;
1013
+ const bounds = {
1014
+ x: x + leftBorderWidth,
1015
+ y: y + topBorderHeight,
1016
+ width: contentWidth,
1017
+ height: contentHeight
1018
+ };
1019
+ const lines = Array.from({ length: contentHeight }, (_unused, row) => Array.from({ length: contentWidth }, (_empty, column) => {
1020
+ const resetBackground = node.style.backgroundColor === "";
1021
+ const background = resetBackground ? void 0 : samplePaint(node.style.backgroundColor, bounds.x + column, bounds.y + row, bounds, output.paintContext);
1022
+ return createCell(" ", 1, {
1023
+ ...background ? { background } : {},
1024
+ underline: "none",
1025
+ attributes: cellAttributes.none
1026
+ }, void 0, resetBackground ? { background: true } : void 0);
1027
+ }));
1028
+ output.writeCells(bounds.x, bounds.y, lines);
1029
+ };
1030
+ //#endregion
1031
+ //#region src/render-border.ts
1032
+ const stylePiece = (segment, x, y, bounds, context, fg, bg, dim) => {
1033
+ let column = x;
1034
+ return [...graphemes(segment)].map((grapheme) => {
1035
+ const foreground = fg ? samplePaint(fg, column, y, bounds, context) : void 0;
1036
+ const background = bg ? samplePaint(bg, column, y, bounds, context) : void 0;
1037
+ const width = Math.max(1, stringWidth(grapheme));
1038
+ column += width;
1039
+ return createCell(grapheme, width, {
1040
+ ...foreground ? { foreground } : {},
1041
+ ...background ? { background } : {},
1042
+ underline: "none",
1043
+ attributes: dim ? cellAttributes.faint : cellAttributes.none
1044
+ });
1045
+ });
1046
+ };
1047
+ const renderBorder = (x, y, node, output) => {
1048
+ if (node.style.borderStyle) {
1049
+ const width = node.yogaNode.getComputedWidth();
1050
+ const height = node.yogaNode.getComputedHeight();
1051
+ const bounds = {
1052
+ x,
1053
+ y,
1054
+ width,
1055
+ height
1056
+ };
1057
+ const box = typeof node.style.borderStyle === "string" ? BOXES[node.style.borderStyle] : node.style.borderStyle;
1058
+ const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
1059
+ const bottomBorderColor = node.style.borderBottomColor ?? node.style.borderColor;
1060
+ const leftBorderColor = node.style.borderLeftColor ?? node.style.borderColor;
1061
+ const rightBorderColor = node.style.borderRightColor ?? node.style.borderColor;
1062
+ const topBorderBackgroundColor = node.style.borderTopBackgroundColor ?? node.style.borderBackgroundColor;
1063
+ const bottomBorderBackgroundColor = node.style.borderBottomBackgroundColor ?? node.style.borderBackgroundColor;
1064
+ const leftBorderBackgroundColor = node.style.borderLeftBackgroundColor ?? node.style.borderBackgroundColor;
1065
+ const rightBorderBackgroundColor = node.style.borderRightBackgroundColor ?? node.style.borderBackgroundColor;
1066
+ const dimTopBorderColor = node.style.borderTopDimColor ?? node.style.borderDimColor;
1067
+ const dimBottomBorderColor = node.style.borderBottomDimColor ?? node.style.borderDimColor;
1068
+ const dimLeftBorderColor = node.style.borderLeftDimColor ?? node.style.borderDimColor;
1069
+ const dimRightBorderColor = node.style.borderRightDimColor ?? node.style.borderDimColor;
1070
+ const showTopBorder = node.style.borderTop !== false;
1071
+ const showBottomBorder = node.style.borderBottom !== false;
1072
+ const showLeftBorder = node.style.borderLeft !== false;
1073
+ const showRightBorder = node.style.borderRight !== false;
1074
+ const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
1075
+ const topBorder = showTopBorder ? (showLeftBorder ? box.topLeft : "") + box.top.repeat(contentWidth) + (showRightBorder ? box.topRight : "") : void 0;
1076
+ let verticalBorderHeight = height;
1077
+ if (showTopBorder) verticalBorderHeight -= 1;
1078
+ if (showBottomBorder) verticalBorderHeight -= 1;
1079
+ const offsetY = showTopBorder ? 1 : 0;
1080
+ const bottomBorder = showBottomBorder ? (showLeftBorder ? box.bottomLeft : "") + box.bottom.repeat(contentWidth) + (showRightBorder ? box.bottomRight : "") : void 0;
1081
+ if (topBorder) output.writeCells(x, y, [stylePiece(topBorder, x, y, bounds, output.paintContext, topBorderColor, topBorderBackgroundColor, dimTopBorderColor)]);
1082
+ if (showLeftBorder) output.writeCells(x, y + offsetY, Array.from({ length: verticalBorderHeight }, (_, row) => stylePiece(box.left, x, y + offsetY + row, bounds, output.paintContext, leftBorderColor, leftBorderBackgroundColor, dimLeftBorderColor)));
1083
+ if (showRightBorder) output.writeCells(x + width - 1, y + offsetY, Array.from({ length: verticalBorderHeight }, (_, row) => stylePiece(box.right, x + width - 1, y + offsetY + row, bounds, output.paintContext, rightBorderColor, rightBorderBackgroundColor, dimRightBorderColor)));
1084
+ if (bottomBorder) output.writeCells(x, y + height - 1, [stylePiece(bottomBorder, x, y + height - 1, bounds, output.paintContext, bottomBorderColor, bottomBorderBackgroundColor, dimBottomBorderColor)]);
1085
+ }
1086
+ };
1087
+ //#endregion
1088
+ //#region src/structured-text.ts
1089
+ /** Whether a text subtree must retain the ANSI compatibility pipeline. */
1090
+ function hasCompatibilityText(node) {
1091
+ if (node.internal_transform && !node.internal_textStyle) return true;
1092
+ return node.childNodes.some((child) => child.nodeName !== "#text" && hasCompatibilityText(child));
1093
+ }
1094
+ /** Rasterizes an unwrapped native Text subtree into semantic cell lines. */
1095
+ function structuredTextLines(node) {
1096
+ const lines = [[]];
1097
+ appendNode(node, emptySemanticTextStyle, lines, false);
1098
+ return lines;
1099
+ }
1100
+ /** Samples semantic paints only after wrapping and final layout are known. */
1101
+ function sampleStructuredText(lines, bounds, context) {
1102
+ return lines.map((line, row) => {
1103
+ let column = 0;
1104
+ return line.map((cell) => {
1105
+ const foreground = cell.foregroundPaint ? samplePaint(cell.foregroundPaint, bounds.x + column, bounds.y + row, bounds, context) : cell.style.foreground;
1106
+ const background = cell.backgroundPaint ? samplePaint(cell.backgroundPaint, bounds.x + column, bounds.y + row, bounds, context) : cell.style.background;
1107
+ column += cell.width;
1108
+ return createCell(cell.grapheme, cell.width, {
1109
+ ...cell.style,
1110
+ ...foreground ? { foreground } : {},
1111
+ ...background ? { background } : {}
1112
+ }, cell.hyperlink, cell.reset);
1113
+ });
1114
+ });
1115
+ }
1116
+ function structuredTextBaseStyle(node) {
1117
+ return semanticTextStyleToCellStyle(mergeSemanticTextStyles(emptySemanticTextStyle, node.internal_textStyle));
1118
+ }
1119
+ /** Wraps or truncates semantic cells without converting them through ANSI. */
1120
+ function wrapStructuredText(input, maxWidth, wrap, baseStyle) {
1121
+ if (wrap === "none") return input.map((line) => [...line]);
1122
+ if (maxWidth < 1) return [[]];
1123
+ return input.flatMap((line) => {
1124
+ if (lineWidth(line) <= maxWidth) return [[...line]];
1125
+ if (wrap === "hard") return hardWrap(line, maxWidth);
1126
+ if (wrap === "wrap") return wordWrap(line, maxWidth);
1127
+ return [truncate(line, maxWidth, wrap, baseStyle)];
1128
+ });
1129
+ }
1130
+ function wordWrap(line, maxWidth) {
1131
+ const words = [[]];
1132
+ const spaces = [];
1133
+ for (const cell of line) if (cell.grapheme === " ") {
1134
+ spaces.push(cell);
1135
+ words.push([]);
1136
+ } else words.at(-1).push(cell);
1137
+ const output = [[]];
1138
+ appendHard(output, words[0], maxWidth);
1139
+ for (let index = 1; index < words.length; index++) {
1140
+ appendHard(output, [spaces[index - 1]], maxWidth);
1141
+ const word = words[index];
1142
+ const current = output.at(-1);
1143
+ const wordWidth = lineWidth(word);
1144
+ const currentWidth = lineWidth(current);
1145
+ if (wordWidth <= maxWidth && currentWidth + wordWidth > maxWidth) output.push([]);
1146
+ else if (wordWidth > maxWidth && currentWidth > 0) {
1147
+ const remaining = maxWidth - currentWidth;
1148
+ const breaksHere = 1 + Math.floor((wordWidth - remaining - 1) / maxWidth);
1149
+ if (Math.floor((wordWidth - 1) / maxWidth) < breaksHere) output.push([]);
1150
+ }
1151
+ appendHard(output, word, maxWidth);
1152
+ }
1153
+ return output;
1154
+ }
1155
+ function hardWrap(line, maxWidth) {
1156
+ const output = [[]];
1157
+ appendHard(output, line, maxWidth);
1158
+ return output;
1159
+ }
1160
+ function appendHard(output, cells, maxWidth) {
1161
+ for (const cell of cells) {
1162
+ const current = output.at(-1);
1163
+ if (current.length > 0 && lineWidth(current) + cell.width > maxWidth) output.push([]);
1164
+ output.at(-1).push(cell);
1165
+ }
1166
+ }
1167
+ function truncate(line, maxWidth, wrap, baseStyle) {
1168
+ if (maxWidth === 1) return [ellipsis(baseStyle ?? cellStyleIntersection(line.at(0), line.at(-1)))];
1169
+ if (wrap === "truncate-start") {
1170
+ const right = sliceCells(line, lineWidth(line) - maxWidth + 1, lineWidth(line));
1171
+ return [ellipsis(right[0]?.style), ...right];
1172
+ }
1173
+ if (wrap === "truncate-middle") {
1174
+ const half = Math.min(Math.floor(maxWidth / 2), maxWidth - 1);
1175
+ const left = sliceCells(line, 0, half);
1176
+ const rightWidth = maxWidth - half - 1;
1177
+ const right = sliceCells(line, lineWidth(line) - rightWidth, lineWidth(line));
1178
+ return [
1179
+ ...left,
1180
+ ellipsis(baseStyle ?? cellStyleIntersection(left.at(-1), right.at(0))),
1181
+ ...right
1182
+ ];
1183
+ }
1184
+ const left = sliceCells(line, 0, maxWidth - 1);
1185
+ return [...left, ellipsis(left.at(-1)?.style)];
1186
+ }
1187
+ function sliceCells(line, start, end) {
1188
+ const output = [];
1189
+ let column = 0;
1190
+ for (const cell of line) {
1191
+ const nextColumn = column + cell.width;
1192
+ if (column >= start && nextColumn <= end) output.push(cell);
1193
+ column = nextColumn;
1194
+ }
1195
+ return output;
1196
+ }
1197
+ function ellipsis(style = semanticTextStyleToCellStyle(emptySemanticTextStyle)) {
1198
+ return createCell("…", 1, style);
1199
+ }
1200
+ function cellStyleIntersection(left, right) {
1201
+ if (!left) return right?.style ?? semanticTextStyleToCellStyle(emptySemanticTextStyle);
1202
+ if (!right) return left.style;
1203
+ return {
1204
+ ...sameColor(left.style.foreground, right.style.foreground) ? { foreground: left.style.foreground } : {},
1205
+ ...sameColor(left.style.background, right.style.background) ? { background: left.style.background } : {},
1206
+ ...sameColor(left.style.underlineColor, right.style.underlineColor) ? { underlineColor: left.style.underlineColor } : {},
1207
+ underline: left.style.underline === right.style.underline ? left.style.underline : "none",
1208
+ attributes: left.style.attributes & right.style.attributes
1209
+ };
1210
+ }
1211
+ function sameColor(left, right) {
1212
+ return JSON.stringify(left) === JSON.stringify(right);
1213
+ }
1214
+ function lineWidth(line) {
1215
+ return line.reduce((width, cell) => width + cell.width, 0);
1216
+ }
1217
+ function appendNode(node, inherited, lines, ansi) {
1218
+ if (node.nodeName === "#text") {
1219
+ if (ansi) appendAnsiText(node.nodeValue, inherited, lines);
1220
+ else appendText(node.nodeValue, inherited, lines);
1221
+ return;
1222
+ }
1223
+ const style = mergeSemanticTextStyles(inherited, node.internal_textStyle);
1224
+ for (const child of node.childNodes) appendNode(child, style, lines, ansi || node.internal_ansi === true);
1225
+ }
1226
+ function appendAnsiText(text, semantic, lines) {
1227
+ const base = semanticTextStyleToCellStyle(semantic);
1228
+ for (const cell of cellsFromAnsi(text)) {
1229
+ if (cell.grapheme === "\n" || cell.grapheme === "\r\n") {
1230
+ lines.push([]);
1231
+ continue;
1232
+ }
1233
+ const hasForeground = cell.style.foreground !== void 0;
1234
+ const hasBackground = cell.style.background !== void 0;
1235
+ lines.at(-1).push({
1236
+ ...cell,
1237
+ style: {
1238
+ ...cell.style.foreground ?? base.foreground ? { foreground: cell.style.foreground ?? base.foreground } : {},
1239
+ ...cell.style.background ?? base.background ? { background: cell.style.background ?? base.background } : {},
1240
+ ...cell.style.underlineColor ? { underlineColor: cell.style.underlineColor } : {},
1241
+ underline: cell.style.underline === "none" ? base.underline : cell.style.underline,
1242
+ attributes: base.attributes | cell.style.attributes
1243
+ },
1244
+ ...!hasForeground && semantic.foreground ? { foregroundPaint: semantic.foreground } : {},
1245
+ ...!hasBackground && semantic.background ? { backgroundPaint: semantic.background } : {}
1246
+ });
1247
+ }
1248
+ }
1249
+ function appendText(text, style, lines) {
1250
+ const cellStyle = semanticTextStyleToCellStyle(style);
1251
+ const plainText = tokenizeAnsi(text).filter((token) => token.type === "text").map((token) => token.value).join("");
1252
+ for (const segment of graphemes(stripAnsi(plainText))) {
1253
+ if (segment === "\n" || segment === "\r\n") {
1254
+ lines.push([]);
1255
+ continue;
1256
+ }
1257
+ lines.at(-1).push({
1258
+ ...createCell(segment, Math.max(1, stringWidth(segment)), cellStyle, void 0, style.resetForeground || style.resetBackground ? {
1259
+ foreground: style.resetForeground,
1260
+ background: style.resetBackground
1261
+ } : void 0),
1262
+ ...style.foreground ? { foregroundPaint: style.foreground } : {},
1263
+ ...style.background ? { backgroundPaint: style.background } : {}
1264
+ });
1265
+ }
1266
+ }
1267
+ //#endregion
1268
+ //#region src/paint-tree.ts
1269
+ const renderAccessibleText = (node, options = {}) => {
1270
+ if (options.skipStaticElements && node.internal_static) return "";
1271
+ if (node.yogaNode?.getDisplay() === Yoga.DISPLAY_NONE) return "";
1272
+ let output = "";
1273
+ if (node.nodeName === "ink-text") output = squashTextNodes(node);
1274
+ else if (node.nodeName === "ink-box" || node.nodeName === "ink-root") {
1275
+ const separator = node.style.flexDirection === "row" || node.style.flexDirection === "row-reverse" ? " " : "\n";
1276
+ output = (node.style.flexDirection === "row-reverse" || node.style.flexDirection === "column-reverse" ? [...node.childNodes].reverse() : [...node.childNodes]).map((childNode) => {
1277
+ return renderAccessibleText(childNode, {
1278
+ parentRole: node.internal_accessibility?.role,
1279
+ skipStaticElements: options.skipStaticElements
1280
+ });
1281
+ }).filter(Boolean).join(separator);
1282
+ }
1283
+ if (node.internal_accessibility) {
1284
+ const { role, state } = node.internal_accessibility;
1285
+ if (state) {
1286
+ const stateDescription = Object.keys(state).filter((key) => state[key]).join(", ");
1287
+ if (stateDescription) output = `(${stateDescription}) ${output}`;
1288
+ }
1289
+ if (role && role !== options.parentRole) output = `${role}: ${output}`;
1290
+ }
1291
+ return output;
1292
+ };
1293
+ const paintTree = (node, output, options) => {
1294
+ const { offsetX = 0, offsetY = 0, transformers = [], skipStaticElements } = options;
1295
+ if (skipStaticElements && node.internal_static) return;
1296
+ const { yogaNode } = node;
1297
+ if (yogaNode) {
1298
+ if (yogaNode.getDisplay() === Yoga.DISPLAY_NONE) return;
1299
+ const x = offsetX + yogaNode.getComputedLeft();
1300
+ const y = offsetY + yogaNode.getComputedTop();
1301
+ const newTransformers = node.internal_transform ? [node.internal_transform, ...transformers] : transformers;
1302
+ if (node.nodeName === "ink-text") {
1303
+ if (squashTextNodes(node).length > 0) {
1304
+ const maxWidth = getMaxWidth(yogaNode);
1305
+ const firstChildYoga = node.childNodes[0]?.yogaNode;
1306
+ const paddingX = firstChildYoga?.getComputedLeft() ?? 0;
1307
+ const paddingY = firstChildYoga?.getComputedTop() ?? 0;
1308
+ const textWrap = node.style.textWrap ?? "wrap";
1309
+ const overflow = textWrap === "none";
1310
+ const lines = wrapStructuredText(structuredTextLines(node), maxWidth, textWrap, structuredTextBaseStyle(node));
1311
+ const paintBounds = {
1312
+ x: x + paddingX,
1313
+ y: y + paddingY,
1314
+ width: Math.max(1, ...lines.map((line) => line.reduce((width, cell) => width + cell.width, 0))),
1315
+ height: lines.length
1316
+ };
1317
+ const sampled = sampleStructuredText(lines, paintBounds, output.paintContext);
1318
+ if (hasCompatibilityText(node)) {
1319
+ const textTransformers = collectTransformers(node, transformers);
1320
+ output.writeAnsi(paintBounds.x, paintBounds.y, sampled.map((line) => serializeLine(line, {
1321
+ colorProfile: output.paintContext.profile ?? "truecolor",
1322
+ trimEnd: false
1323
+ })).join("\n"), {
1324
+ transformers: textTransformers,
1325
+ overflow
1326
+ });
1327
+ } else output.writeCells(paintBounds.x, paintBounds.y, sampled, { overflow });
1328
+ }
1329
+ return;
1330
+ }
1331
+ let clipped = false;
1332
+ if (node.nodeName === "ink-box") {
1333
+ renderBackground(x, y, node, output);
1334
+ renderBorder(x, y, node, output);
1335
+ const clipHorizontally = node.style.overflowX === "hidden" || node.style.overflow === "hidden";
1336
+ const clipVertically = node.style.overflowY === "hidden" || node.style.overflow === "hidden";
1337
+ if (clipHorizontally || clipVertically) {
1338
+ const x1 = clipHorizontally ? x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT) : void 0;
1339
+ const x2 = clipHorizontally ? x + yogaNode.getComputedWidth() - yogaNode.getComputedBorder(Yoga.EDGE_RIGHT) : void 0;
1340
+ const y1 = clipVertically ? y + yogaNode.getComputedBorder(Yoga.EDGE_TOP) : void 0;
1341
+ const y2 = clipVertically ? y + yogaNode.getComputedHeight() - yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM) : void 0;
1342
+ output.clip({
1343
+ x1,
1344
+ x2,
1345
+ y1,
1346
+ y2
1347
+ });
1348
+ clipped = true;
1349
+ }
1350
+ }
1351
+ if (node.nodeName === "ink-root" || node.nodeName === "ink-box") {
1352
+ for (const childNode of node.childNodes) paintTree(childNode, output, {
1353
+ offsetX: x,
1354
+ offsetY: y,
1355
+ transformers: newTransformers,
1356
+ skipStaticElements
1357
+ });
1358
+ if (clipped) output.unclip();
1359
+ }
1360
+ }
1361
+ };
1362
+ function collectTransformers(node, inherited) {
1363
+ return [
1364
+ ...node.childNodes.flatMap((child) => child.nodeName === "#text" ? [] : collectTransformers(child, [])),
1365
+ ...node.internal_transform ? [node.internal_transform] : [],
1366
+ ...inherited
1367
+ ];
1368
+ }
1369
+ //#endregion
1370
+ //#region src/screen/canvas.ts
1371
+ /** Immediate structured drawing canvas with one explicit ANSI compatibility adapter. */
1372
+ var Canvas = class {
1373
+ width;
1374
+ height;
1375
+ paintContext;
1376
+ #screen;
1377
+ #clips = [];
1378
+ constructor(options) {
1379
+ this.width = options.width;
1380
+ this.height = options.height;
1381
+ this.paintContext = {
1382
+ profile: options.colorProfile ?? "truecolor",
1383
+ ...options.paintContext
1384
+ };
1385
+ this.#screen = new Screen(this.width, this.height);
1386
+ }
1387
+ writeCells(x, y, lines, options) {
1388
+ const clip = this.#clips.at(-1);
1389
+ const overflow = options?.overflow === true;
1390
+ for (const [rowOffset, line] of lines.entries()) {
1391
+ const currentY = y + rowOffset;
1392
+ if (!this.#insideY(currentY, clip)) continue;
1393
+ let currentX = x;
1394
+ for (const cell of line) {
1395
+ const endX = currentX + cell.width;
1396
+ if (!(clip?.x1 !== void 0 && currentX < clip.x1 || clip?.x2 !== void 0 && endX > clip.x2)) this.#composeNativeCell(currentX, currentY, cell, overflow);
1397
+ currentX = endX;
1398
+ }
1399
+ }
1400
+ }
1401
+ writeAnsi(x, y, text, options) {
1402
+ if (!text) return;
1403
+ let lines = text.split("\n");
1404
+ const clip = this.#clips.at(-1);
1405
+ const overflow = options.overflow === true;
1406
+ if (clip?.y1 !== void 0 && y < clip.y1) {
1407
+ lines = lines.slice(clip.y1 - y);
1408
+ y = clip.y1;
1409
+ }
1410
+ if (clip?.y2 !== void 0) lines = lines.slice(0, Math.max(0, clip.y2 - y));
1411
+ for (const [lineIndex, original] of lines.entries()) {
1412
+ const currentY = y + lineIndex;
1413
+ if (!this.#insideY(currentY, clip)) continue;
1414
+ let line = original;
1415
+ let currentX = x;
1416
+ if (clip?.x1 !== void 0 && currentX < clip.x1) {
1417
+ line = sliceAnsi(line, clip.x1 - currentX);
1418
+ currentX = clip.x1;
1419
+ }
1420
+ if (clip?.x2 !== void 0) line = sliceAnsi(line, 0, Math.max(0, clip.x2 - currentX));
1421
+ for (const character of transformAnsiLine(line, lineIndex, options.transformers)) {
1422
+ const width = Math.max(1, stringWidth(character.value));
1423
+ if (clip?.x2 !== void 0 && currentX + width > clip.x2) break;
1424
+ this.#writeCompatibilityCell(currentX, currentY, character, width, overflow);
1425
+ currentX += width;
1426
+ }
1427
+ }
1428
+ }
1429
+ clip(clip) {
1430
+ this.#clips.push(clip);
1431
+ }
1432
+ unclip() {
1433
+ this.#clips.pop();
1434
+ }
1435
+ finish() {
1436
+ return this.#screen;
1437
+ }
1438
+ #insideY(y, clip) {
1439
+ return y >= 0 && y < this.#screen.height && (clip?.y1 === void 0 || y >= clip.y1) && (clip?.y2 === void 0 || y < clip.y2);
1440
+ }
1441
+ #composeNativeCell(x, y, cell, overflow = false) {
1442
+ if (!overflow && x >= this.#screen.width) return;
1443
+ this.#screen.composeCell(x, y, {
1444
+ content: {
1445
+ grapheme: cell.grapheme,
1446
+ width: cell.width
1447
+ },
1448
+ foreground: cell.reset?.foreground ? null : cell.style.foreground ?? null,
1449
+ background: cell.reset?.background ? null : cell.style.background,
1450
+ underlineColor: cell.style.underlineColor ?? null,
1451
+ underline: cell.style.underline,
1452
+ attributes: cell.style.attributes,
1453
+ hyperlink: cell.hyperlink ?? null
1454
+ }, { overflow });
1455
+ }
1456
+ #writeCompatibilityCell(x, y, character, width, overflow = false) {
1457
+ if (!overflow && x >= this.#screen.width) return;
1458
+ const cell = cellFromStyledChar(character, width);
1459
+ this.#screen.composeCell(x, y, {
1460
+ content: {
1461
+ grapheme: cell.grapheme,
1462
+ width: cell.width
1463
+ },
1464
+ foreground: cell.style.foreground ?? null,
1465
+ background: cell.style.background,
1466
+ underlineColor: cell.style.underlineColor ?? null,
1467
+ underline: cell.style.underline,
1468
+ attributes: cell.style.attributes,
1469
+ hyperlink: cell.hyperlink ?? null
1470
+ }, { overflow });
1471
+ }
1472
+ };
1473
+ //#endregion
1474
+ //#region src/render-frame.ts
1475
+ const renderDimension = (value) => Number.isFinite(value) ? Math.max(0, Math.ceil(value)) : 0;
1476
+ const renderFrame = (node, isScreenReaderEnabled, options = {}) => {
1477
+ if (node.yogaNode) {
1478
+ if (isScreenReaderEnabled) {
1479
+ const output = renderAccessibleText(node, { skipStaticElements: true });
1480
+ let staticOutput = "";
1481
+ if (node.staticNode) staticOutput = renderAccessibleText(node.staticNode, { skipStaticElements: false });
1482
+ return {
1483
+ accessibleText: output,
1484
+ staticAccessibleText: staticOutput
1485
+ };
1486
+ }
1487
+ const output = new Canvas({
1488
+ width: renderDimension(node.yogaNode.getComputedWidth()),
1489
+ height: renderDimension(node.yogaNode.getComputedHeight()),
1490
+ colorProfile: options.colorProfile,
1491
+ paintContext: options.paintContext
1492
+ });
1493
+ paintTree(node, output, { skipStaticElements: true });
1494
+ let staticOutput;
1495
+ if (node.staticNode?.yogaNode) {
1496
+ staticOutput = new Canvas({
1497
+ width: renderDimension(node.staticNode.yogaNode.getComputedWidth()),
1498
+ height: renderDimension(node.staticNode.yogaNode.getComputedHeight()),
1499
+ colorProfile: options.colorProfile,
1500
+ paintContext: options.paintContext
1501
+ });
1502
+ paintTree(node.staticNode, staticOutput, { skipStaticElements: false });
1503
+ }
1504
+ return {
1505
+ screen: output.finish(),
1506
+ ...staticOutput ? { staticScreen: staticOutput.finish() } : {}
1507
+ };
1508
+ }
1509
+ return { accessibleText: "" };
1510
+ };
1511
+ //#endregion
1512
+ //#region src/terminal/inline-presenter.ts
1513
+ /** Full-rewrite fallback and cursor state for the structured inline renderer. */
1514
+ function createInlinePresenter(stream, options = {}) {
1515
+ let previousLineCount = 0;
1516
+ let previousOutput = "";
1517
+ let hasHiddenCursor = false;
1518
+ let cursorPosition;
1519
+ let cursorDirty = false;
1520
+ let previousCursorPosition;
1521
+ let cursorWasShown = false;
1522
+ const showCursor = options.showCursor ?? false;
1523
+ const activeCursor = () => cursorDirty ? cursorPosition : void 0;
1524
+ const hasChanges = (output, cursor) => output !== previousOutput || cursorPositionChanged(cursor, previousCursorPosition);
1525
+ const present = (output) => {
1526
+ if (!showCursor && !hasHiddenCursor) {
1527
+ cliCursor.hide(stream);
1528
+ hasHiddenCursor = true;
1529
+ }
1530
+ const cursor = activeCursor();
1531
+ cursorDirty = false;
1532
+ const cursorChanged = cursorPositionChanged(cursor, previousCursorPosition);
1533
+ if (!hasChanges(output, cursor)) return false;
1534
+ const lines = output.split("\n");
1535
+ const suffix = buildCursorSuffix(lines.length - 1, cursor);
1536
+ if (output === previousOutput && cursorChanged) stream.write(buildCursorOnlySequence({
1537
+ cursorWasShown,
1538
+ previousLineCount,
1539
+ previousCursorPosition,
1540
+ cursorPosition: cursor
1541
+ }));
1542
+ else {
1543
+ previousOutput = output;
1544
+ stream.write(buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition) + ansiEscapes.eraseLines(previousLineCount) + output + suffix);
1545
+ previousLineCount = lines.length;
1546
+ }
1547
+ previousCursorPosition = cursor ? { ...cursor } : void 0;
1548
+ cursorWasShown = cursor !== void 0;
1549
+ return true;
1550
+ };
1551
+ present.clear = () => {
1552
+ stream.write(buildReturnToBottomPrefix(cursorWasShown, previousLineCount, previousCursorPosition) + ansiEscapes.eraseLines(previousLineCount));
1553
+ previousOutput = "";
1554
+ previousLineCount = 0;
1555
+ previousCursorPosition = void 0;
1556
+ cursorWasShown = false;
1557
+ };
1558
+ present.done = () => {
1559
+ previousOutput = "";
1560
+ previousLineCount = 0;
1561
+ previousCursorPosition = void 0;
1562
+ cursorWasShown = false;
1563
+ if (!showCursor) {
1564
+ cliCursor.show(stream);
1565
+ hasHiddenCursor = false;
1566
+ }
1567
+ };
1568
+ present.reset = () => {
1569
+ previousOutput = "";
1570
+ previousLineCount = 0;
1571
+ previousCursorPosition = void 0;
1572
+ cursorWasShown = false;
1573
+ };
1574
+ present.sync = (output) => {
1575
+ const cursor = activeCursor();
1576
+ cursorDirty = false;
1577
+ const lines = output.split("\n");
1578
+ previousOutput = output;
1579
+ previousLineCount = lines.length;
1580
+ if (!cursor && cursorWasShown) stream.write(hideCursorEscape);
1581
+ if (cursor) stream.write(buildCursorSuffix(lines.length - 1, cursor));
1582
+ previousCursorPosition = cursor ? { ...cursor } : void 0;
1583
+ cursorWasShown = cursor !== void 0;
1584
+ };
1585
+ present.setCursorPosition = (position) => {
1586
+ cursorPosition = position;
1587
+ cursorDirty = true;
1588
+ };
1589
+ present.isCursorDirty = () => cursorDirty;
1590
+ present.willRender = (output) => hasChanges(output, activeCursor());
1591
+ return present;
1592
+ }
1593
+ //#endregion
1594
+ //#region src/throttle.ts
1595
+ /**
1596
+ Invokes `fn` at most once per `wait` milliseconds.
1597
+
1598
+ The first call in a window fires immediately (leading edge). Calls made while
1599
+ the window is open are coalesced into a single trailing invocation with the
1600
+ latest arguments. Matches the lodash semantics Ink relied on: a single call
1601
+ produces only a leading invocation, no trailing one.
1602
+ */
1603
+ const throttle = (fn, wait = 0) => {
1604
+ let timer;
1605
+ let pendingArgs;
1606
+ const invokePending = () => {
1607
+ const args = pendingArgs;
1608
+ pendingArgs = void 0;
1609
+ fn(...args);
1610
+ };
1611
+ const onTimer = () => {
1612
+ if (pendingArgs) {
1613
+ invokePending();
1614
+ timer = setTimeout(onTimer, wait);
1615
+ } else timer = void 0;
1616
+ };
1617
+ const throttled = (...args) => {
1618
+ if (timer) {
1619
+ pendingArgs = args;
1620
+ return;
1621
+ }
1622
+ fn(...args);
1623
+ timer = setTimeout(onTimer, wait);
1624
+ };
1625
+ throttled.cancel = () => {
1626
+ if (timer) {
1627
+ clearTimeout(timer);
1628
+ timer = void 0;
1629
+ }
1630
+ pendingArgs = void 0;
1631
+ };
1632
+ throttled.flush = () => {
1633
+ if (!pendingArgs) return;
1634
+ if (timer) {
1635
+ clearTimeout(timer);
1636
+ timer = void 0;
1637
+ }
1638
+ invokePending();
1639
+ };
1640
+ return throttled;
1641
+ };
1642
+ //#endregion
1643
+ //#region src/terminal/render-scheduler.ts
1644
+ /** Owns frame cadence independently of React reconciliation and terminal presentation. */
1645
+ function createRenderScheduler(render, options) {
1646
+ const frameInterval = options.maxFps > 0 ? Math.max(1, Math.ceil(1e3 / options.maxFps)) : 0;
1647
+ let pending = false;
1648
+ const throttled = options.unthrottled ? void 0 : throttle(render, frameInterval);
1649
+ return {
1650
+ intervalMs: options.unthrottled ? 0 : frameInterval,
1651
+ throttled,
1652
+ get pending() {
1653
+ return pending;
1654
+ },
1655
+ schedule: options.unthrottled ? render : () => {
1656
+ pending = true;
1657
+ throttled();
1658
+ },
1659
+ immediate: render,
1660
+ markRendered() {
1661
+ pending = false;
1662
+ }
1663
+ };
1664
+ }
1665
+ //#endregion
1666
+ //#region src/ink.tsx
1667
+ /** @jsxImportSource react */
1668
+ const noop = () => {};
1669
+ const beforeExitCallbacks = /* @__PURE__ */ new Set();
1670
+ const runBeforeExitCallbacks = () => {
1671
+ for (const callback of beforeExitCallbacks) callback();
1672
+ };
1673
+ function registerBeforeExit(callback) {
1674
+ if (beforeExitCallbacks.size === 0) process.on("beforeExit", runBeforeExitCallbacks);
1675
+ beforeExitCallbacks.add(callback);
1676
+ return () => {
1677
+ beforeExitCallbacks.delete(callback);
1678
+ if (beforeExitCallbacks.size === 0) process.off("beforeExit", runBeforeExitCallbacks);
1679
+ };
1680
+ }
1681
+ function bottomRows(screen, height) {
1682
+ if (screen.height <= height) return screen;
1683
+ const cropped = new Screen(screen.width, height);
1684
+ const offset = screen.height - height;
1685
+ for (let y = 0; y < height; y++) for (let x = 0; x < screen.width; x++) {
1686
+ const cell = screen.cellAt(x, y + offset);
1687
+ if (cell && cell.width > 0) cropped.setCell(x, y, cell);
1688
+ }
1689
+ return cropped;
1690
+ }
1691
+ const RESIZE_SETTLE_MS = 50;
1692
+ const shouldClearTerminalForFrame = ({ isTTY, viewportRows, previousOutputHeight, nextOutputHeight, isUnmounting }) => {
1693
+ if (!isTTY) return false;
1694
+ const hadPreviousFrame = previousOutputHeight > 0;
1695
+ const wasFullscreen = previousOutputHeight >= viewportRows;
1696
+ const wasOverflowing = previousOutputHeight > viewportRows;
1697
+ const isOverflowing = nextOutputHeight > viewportRows;
1698
+ const isFullscreen = nextOutputHeight >= viewportRows;
1699
+ const isLeavingFullscreen = wasOverflowing && nextOutputHeight < viewportRows;
1700
+ const shouldClearOnUnmount = isUnmounting && wasFullscreen;
1701
+ if (isWindows && (wasFullscreen || isFullscreen)) return true;
1702
+ return wasOverflowing || isOverflowing && hadPreviousFrame || isLeavingFullscreen || shouldClearOnUnmount;
1703
+ };
1704
+ const isErrorInput = (value) => {
1705
+ return value instanceof Error || isNativeError(value);
1706
+ };
1707
+ const getWritableStreamState = (stdout) => {
1708
+ return { canWriteToStdout: !stdout.destroyed && !stdout.writableEnded && (stdout.writable ?? true) };
1709
+ };
1710
+ const settleThrottle = (throttled, canWriteToStdout) => {
1711
+ if (!throttled) return;
1712
+ if (canWriteToStdout) throttled.flush();
1713
+ else throttled.cancel();
1714
+ };
1715
+ const writeBestEffort = (stream, data) => {
1716
+ try {
1717
+ stream.write(data);
1718
+ } catch {}
1719
+ };
1720
+ const createRenderPassthrough = (stream) => {
1721
+ const passthrough = Object.create(stream);
1722
+ passthrough.write = stream.write.bind(stream);
1723
+ passthrough.on = stream.on.bind(stream);
1724
+ passthrough.off = stream.off.bind(stream);
1725
+ passthrough.once = stream.once.bind(stream);
1726
+ passthrough.addListener = stream.addListener.bind(stream);
1727
+ passthrough.removeListener = stream.removeListener.bind(stream);
1728
+ passthrough.emit = stream.emit.bind(stream);
1729
+ return passthrough;
1730
+ };
1731
+ const createInk = (options) => {
1732
+ let captureTargets;
1733
+ if (options.patchConsole === "stdio") {
1734
+ captureTargets = {
1735
+ stdout: options.stdout,
1736
+ stderr: options.stderr
1737
+ };
1738
+ options = {
1739
+ ...options,
1740
+ stdout: createRenderPassthrough(options.stdout),
1741
+ stderr: createRenderPassthrough(options.stderr)
1742
+ };
1743
+ }
1744
+ const rootNode = createNode("ink-root");
1745
+ rootNode.onComputeLayout = calculateLayout;
1746
+ const isScreenReaderEnabled = options.isScreenReaderEnabled ?? isScreenReader;
1747
+ const interactive = options.interactive ?? (!isInCi && Boolean(options.stdout.isTTY));
1748
+ const terminal = new TerminalSession({
1749
+ stdin: options.stdin,
1750
+ stdout: options.stdout,
1751
+ stderr: options.stderr,
1752
+ colorPolicy: options.colorProfile ?? "auto",
1753
+ onCapabilitiesChange: () => rootNode.onRender?.()
1754
+ });
1755
+ const terminalOsc = {
1756
+ publishProgress: (owner, state, value) => {
1757
+ terminal.publishProgress(owner, state, value);
1758
+ },
1759
+ copyToClipboard: (text, selection) => {
1760
+ terminal.copyToClipboard(text, selection);
1761
+ },
1762
+ publishTitle: (owner, title) => terminal.publishTitle(owner, title),
1763
+ setWorkingDirectory: (directory) => terminal.setWorkingDirectory(directory),
1764
+ notify: (title) => terminal.notify(title),
1765
+ setPointerShape: (shape) => terminal.setPointerShape(shape)
1766
+ };
1767
+ const capabilitiesStore = terminal.capabilities;
1768
+ const renderScheduler = createRenderScheduler(onRender, {
1769
+ unthrottled: options.debug || isScreenReaderEnabled,
1770
+ maxFps: options.maxFps ?? 30
1771
+ });
1772
+ rootNode.onRender = renderScheduler.schedule;
1773
+ rootNode.onImmediateRender = renderScheduler.immediate;
1774
+ rootNode.onStaticChange = handleStaticChange;
1775
+ const accessiblePresenter = isScreenReaderEnabled ? createInlinePresenter(options.stdout, { showCursor: true }) : void 0;
1776
+ let isUnmounted = false;
1777
+ let isUnmounting = false;
1778
+ const isConcurrent = options.concurrent ?? false;
1779
+ let lastOutput = "";
1780
+ let lastOutputToRender = "";
1781
+ let lastOutputHeight = 0;
1782
+ let lastScreen;
1783
+ const windowSize = () => capabilitiesStore.current.size;
1784
+ let lastTerminalWidth = windowSize().columns;
1785
+ let lastTerminalHeight = windowSize().rows;
1786
+ let resizeSettle;
1787
+ let fullStaticOutput = "";
1788
+ let exitResult;
1789
+ let unsubscribeBeforeExit;
1790
+ let restoreConsole;
1791
+ const capturedStdioTails = {
1792
+ stdout: "",
1793
+ stderr: ""
1794
+ };
1795
+ let unsubscribeResize;
1796
+ let kittyProtocolEnabled = false;
1797
+ let kittyFlags;
1798
+ let cancelKittyDetection;
1799
+ let nextRenderCommit;
1800
+ let pauseInput;
1801
+ let resumeInput;
1802
+ const rootTag = isConcurrent ? ConcurrentRoot : LegacyRoot;
1803
+ const container = reconciler.createContainer(rootNode, rootTag, null, false, null, "id", () => {}, () => {}, () => {}, () => {});
1804
+ const unsubscribeExit = signalExit(unmount, { alwaysLast: false });
1805
+ setAlternateScreen(Boolean(options.alternateScreen));
1806
+ if (isSigilDev) reconciler.injectIntoDevTools();
1807
+ if (options.patchConsole) installConsolePatch();
1808
+ if (interactive) {
1809
+ let seenColumns = lastTerminalWidth;
1810
+ let seenRows = lastTerminalHeight;
1811
+ unsubscribeResize = capabilitiesStore.subscribe(({ size }) => {
1812
+ if (size.columns === seenColumns && size.rows === seenRows) return;
1813
+ seenColumns = size.columns;
1814
+ seenRows = size.rows;
1815
+ resized(size.source);
1816
+ });
1817
+ }
1818
+ initKittyKeyboard();
1819
+ const { promise: exitPromise, resolve: resolveExitPromise, reject: rejectExitPromise } = Promise.withResolvers();
1820
+ exitPromise.catch(noop);
1821
+ function resized(source) {
1822
+ if (resizeSettle !== void 0) {
1823
+ clearTimeout(resizeSettle);
1824
+ resizeSettle = void 0;
1825
+ }
1826
+ if (source === "terminal") {
1827
+ settleResize();
1828
+ return;
1829
+ }
1830
+ resizeSettle = setTimeout(settleResize, RESIZE_SETTLE_MS);
1831
+ }
1832
+ function settleResize() {
1833
+ resizeSettle = void 0;
1834
+ if (isUnmounted || isUnmounting) return;
1835
+ const { columns: currentWidth, rows: currentHeight } = windowSize();
1836
+ if (currentWidth < lastTerminalWidth || currentHeight !== lastTerminalHeight) {
1837
+ clearLiveOutput(rewrapsOnResize() ? currentWidth : void 0);
1838
+ resetLiveOutput();
1839
+ lastOutput = "";
1840
+ lastOutputToRender = "";
1841
+ lastOutputHeight = 0;
1842
+ }
1843
+ lastTerminalWidth = currentWidth;
1844
+ lastTerminalHeight = currentHeight;
1845
+ calculateLayout();
1846
+ emitLayoutListeners(rootNode);
1847
+ onRender();
1848
+ }
1849
+ function rewrapsOnResize() {
1850
+ return !isWindows && capabilitiesStore.current.terminal.name !== "apple-terminal";
1851
+ }
1852
+ function handleAppExit(errorOrResult) {
1853
+ if (isUnmounted || isUnmounting) return;
1854
+ if (isErrorInput(errorOrResult)) {
1855
+ unmount(errorOrResult);
1856
+ return;
1857
+ }
1858
+ exitResult = errorOrResult;
1859
+ unmount();
1860
+ }
1861
+ function setCursorPosition(position) {
1862
+ terminal.setCursor(position);
1863
+ accessiblePresenter?.setCursorPosition(position);
1864
+ }
1865
+ function restoreLastOutput() {
1866
+ if (!interactive) return;
1867
+ if (isScreenReaderEnabled) {
1868
+ accessiblePresenter.setCursorPosition(terminal.cursor.position);
1869
+ accessiblePresenter(lastOutputToRender || lastOutput + "\n");
1870
+ } else if (lastScreen) terminal.present(lastScreen, {
1871
+ fullscreen: lastOutputToRender === lastOutput,
1872
+ forceRewrite: true
1873
+ });
1874
+ }
1875
+ function clearLiveOutput(columns) {
1876
+ if (isScreenReaderEnabled) accessiblePresenter.clear();
1877
+ else terminal.clearFrame({ columns });
1878
+ }
1879
+ function finishLiveOutput() {
1880
+ if (isScreenReaderEnabled) accessiblePresenter.done();
1881
+ else terminal.finishFrame();
1882
+ }
1883
+ function resetLiveOutput() {
1884
+ if (isScreenReaderEnabled) accessiblePresenter.reset();
1885
+ else terminal.resetFrame();
1886
+ }
1887
+ function calculateLayout() {
1888
+ const terminalWidth = windowSize().columns;
1889
+ rootNode.yogaNode.setWidth(terminalWidth);
1890
+ rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
1891
+ }
1892
+ function handleStaticChange() {
1893
+ fullStaticOutput = "";
1894
+ }
1895
+ function onRender() {
1896
+ renderScheduler.markRendered();
1897
+ if (isUnmounted) return;
1898
+ if (terminal.suspended) {
1899
+ if (nextRenderCommit) {
1900
+ nextRenderCommit.resolve();
1901
+ nextRenderCommit = void 0;
1902
+ }
1903
+ return;
1904
+ }
1905
+ if (resizeSettle !== void 0 && !isUnmounting) {
1906
+ if (nextRenderCommit) {
1907
+ nextRenderCommit.resolve();
1908
+ nextRenderCommit = void 0;
1909
+ }
1910
+ return;
1911
+ }
1912
+ if (nextRenderCommit) {
1913
+ nextRenderCommit.resolve();
1914
+ nextRenderCommit = void 0;
1915
+ }
1916
+ const startTime = performance.now();
1917
+ const rendered = renderFrame(rootNode, isScreenReaderEnabled, {
1918
+ colorProfile: terminal.colorProfile,
1919
+ paintContext: {
1920
+ appearance: capabilitiesStore.current.theme.appearance,
1921
+ palette: capabilitiesStore.current.theme.palette
1922
+ }
1923
+ });
1924
+ const screen = rendered.screen;
1925
+ const output = rendered.accessibleText ?? (screen ? terminal.encode(screen) : "");
1926
+ const outputHeight = rendered.accessibleText === void 0 ? screen?.height ?? 0 : rendered.accessibleText === "" ? 0 : rendered.accessibleText.split("\n").length;
1927
+ const staticBody = rendered.staticAccessibleText ?? (rendered.staticScreen ? terminal.encode(rendered.staticScreen) : "");
1928
+ const staticOutput = staticBody ? `${staticBody}\n` : "";
1929
+ options.onRender?.({ renderTime: performance.now() - startTime });
1930
+ const hasStaticOutput = staticOutput && staticOutput !== "\n";
1931
+ if (options.debug) {
1932
+ if (hasStaticOutput) fullStaticOutput += staticOutput;
1933
+ lastOutput = output;
1934
+ lastOutputToRender = output;
1935
+ lastOutputHeight = outputHeight;
1936
+ options.stdout.write(fullStaticOutput + output);
1937
+ return;
1938
+ }
1939
+ if (!interactive) {
1940
+ if (hasStaticOutput) options.stdout.write(staticOutput);
1941
+ lastOutput = output;
1942
+ lastOutputToRender = output + "\n";
1943
+ lastOutputHeight = outputHeight;
1944
+ return;
1945
+ }
1946
+ if (isScreenReaderEnabled) {
1947
+ const sync = shouldSync();
1948
+ if (sync) options.stdout.write(bsu);
1949
+ if (hasStaticOutput) {
1950
+ const erase = lastOutputHeight > 0 ? ansiEscapes.eraseLines(lastOutputHeight) : "";
1951
+ options.stdout.write(erase + staticOutput);
1952
+ lastOutputHeight = 0;
1953
+ }
1954
+ if (output === lastOutput && !hasStaticOutput) {
1955
+ if (sync) options.stdout.write(esu);
1956
+ return;
1957
+ }
1958
+ const terminalWidth = windowSize().columns;
1959
+ const wrappedOutput = wrapAnsi(output, terminalWidth, {
1960
+ trim: false,
1961
+ hard: true
1962
+ });
1963
+ if (hasStaticOutput) options.stdout.write(wrappedOutput);
1964
+ else {
1965
+ const erase = lastOutputHeight > 0 ? ansiEscapes.eraseLines(lastOutputHeight) : "";
1966
+ options.stdout.write(erase + wrappedOutput);
1967
+ }
1968
+ lastOutput = output;
1969
+ lastOutputToRender = wrappedOutput;
1970
+ lastOutputHeight = wrappedOutput === "" ? 0 : wrappedOutput.split("\n").length;
1971
+ if (sync) options.stdout.write(esu);
1972
+ return;
1973
+ }
1974
+ if (hasStaticOutput) fullStaticOutput += staticOutput;
1975
+ renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : "", screen, false);
1976
+ }
1977
+ function render(node) {
1978
+ const tree = /* @__PURE__ */ jsx(accessibilityContext.Provider, {
1979
+ value: { isScreenReaderEnabled },
1980
+ children: /* @__PURE__ */ jsx(TerminalOscContext.Provider, {
1981
+ value: terminalOsc,
1982
+ children: /* @__PURE__ */ jsx(App, {
1983
+ stdin: options.stdin,
1984
+ stdout: options.stdout,
1985
+ stderr: options.stderr,
1986
+ exitOnCtrlC: options.exitOnCtrlC,
1987
+ interactive,
1988
+ renderThrottleMs: renderScheduler.intervalMs,
1989
+ terminalInput: terminal.input,
1990
+ writeToStdout,
1991
+ writeToStderr,
1992
+ setCursorPosition,
1993
+ onExit: handleAppExit,
1994
+ onWaitUntilRenderFlush: waitUntilRenderFlush,
1995
+ onSuspendTerminal: suspendTerminal,
1996
+ onRegisterInputControl: registerInputControl,
1997
+ children: node
1998
+ })
1999
+ })
2000
+ });
2001
+ if (isConcurrent) reconciler.updateContainer(tree, container, null, noop);
2002
+ else {
2003
+ reconciler.updateContainerSync(tree, container, null, noop);
2004
+ reconciler.flushSyncWork();
2005
+ }
2006
+ }
2007
+ function writeToStdout(data) {
2008
+ if (isUnmounted) return;
2009
+ if (terminal.suspended) return;
2010
+ if (options.debug) {
2011
+ options.stdout.write(data + fullStaticOutput + lastOutput);
2012
+ return;
2013
+ }
2014
+ if (!interactive) {
2015
+ options.stdout.write(data);
2016
+ return;
2017
+ }
2018
+ const sync = shouldSync();
2019
+ if (sync) options.stdout.write(bsu);
2020
+ clearLiveOutput();
2021
+ options.stdout.write(data);
2022
+ restoreLastOutput();
2023
+ if (sync) options.stdout.write(esu);
2024
+ }
2025
+ function writeToStderr(data) {
2026
+ if (isUnmounted) return;
2027
+ if (terminal.suspended) return;
2028
+ if (options.debug) {
2029
+ options.stderr.write(data);
2030
+ options.stdout.write(fullStaticOutput + lastOutput);
2031
+ return;
2032
+ }
2033
+ if (!interactive) {
2034
+ options.stderr.write(data);
2035
+ return;
2036
+ }
2037
+ const sync = shouldSync();
2038
+ if (sync) options.stdout.write(bsu);
2039
+ clearLiveOutput();
2040
+ options.stderr.write(data);
2041
+ restoreLastOutput();
2042
+ if (sync) options.stdout.write(esu);
2043
+ }
2044
+ function unmount(error) {
2045
+ if (isUnmounted || isUnmounting) return;
2046
+ isUnmounting = true;
2047
+ if (resizeSettle !== void 0) {
2048
+ clearTimeout(resizeSettle);
2049
+ resizeSettle = void 0;
2050
+ }
2051
+ unsubscribeBeforeExit?.();
2052
+ unsubscribeBeforeExit = void 0;
2053
+ const { canWriteToStdout } = getWritableStreamState(options.stdout);
2054
+ if (canWriteToStdout) flushCapturedStdio();
2055
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
2056
+ if (canWriteToStdout) {
2057
+ if (!renderScheduler.throttled || !renderScheduler.pending && fullStaticOutput === "") {
2058
+ calculateLayout();
2059
+ onRender();
2060
+ }
2061
+ }
2062
+ isUnmounted = true;
2063
+ unsubscribeExit();
2064
+ terminal.cleanup();
2065
+ if (typeof restoreConsole === "function") restoreConsole();
2066
+ const finishUnmount = () => {
2067
+ if (typeof unsubscribeResize === "function") unsubscribeResize();
2068
+ if (cancelKittyDetection) cancelKittyDetection();
2069
+ if (canWriteToStdout) {
2070
+ if (kittyProtocolEnabled) writeBestEffort(options.stdout, ansiEscapes.popKittyKeyboard);
2071
+ if (terminal.alternateScreen) {
2072
+ terminal.setAlternateScreen(false);
2073
+ terminal.setCursorAppearance({ visible: true });
2074
+ }
2075
+ if (!interactive) options.stdout.write(options.debug ? "\n" : lastOutput + "\n");
2076
+ else if (!options.debug) finishLiveOutput();
2077
+ }
2078
+ kittyProtocolEnabled = false;
2079
+ instances.delete(captureTargets?.stdout ?? options.stdout);
2080
+ const finalExitResult = exitResult;
2081
+ const resolveOrReject = () => {
2082
+ if (isErrorInput(error)) rejectExitPromise(error);
2083
+ else resolveExitPromise(finalExitResult);
2084
+ };
2085
+ if (error !== void 0 && !isErrorInput(error)) resolveOrReject();
2086
+ else if (canWriteToStdout) options.stdout.write("", resolveOrReject);
2087
+ else setImmediate(resolveOrReject);
2088
+ };
2089
+ const concurrentReconciler = reconciler;
2090
+ if (isConcurrent) {
2091
+ reconciler.updateContainerSync(null, container, null, noop);
2092
+ reconciler.flushSyncWork();
2093
+ concurrentReconciler.flushPassiveEffects?.();
2094
+ finishUnmount();
2095
+ } else {
2096
+ reconciler.updateContainerSync(null, container, null, noop);
2097
+ reconciler.flushSyncWork();
2098
+ finishUnmount();
2099
+ }
2100
+ }
2101
+ async function waitUntilExit() {
2102
+ if (!unsubscribeBeforeExit) unsubscribeBeforeExit = registerBeforeExit(() => {
2103
+ unmount();
2104
+ });
2105
+ return exitPromise;
2106
+ }
2107
+ async function waitUntilRenderFlush() {
2108
+ if (isUnmounted || isUnmounting) {
2109
+ await awaitExit();
2110
+ return;
2111
+ }
2112
+ await setImmediate$1();
2113
+ if (isUnmounted || isUnmounting) {
2114
+ await awaitExit();
2115
+ return;
2116
+ }
2117
+ if (isConcurrent && hasPendingConcurrentWork()) {
2118
+ await Promise.race([awaitNextRender(), awaitExit()]);
2119
+ if (isUnmounted || isUnmounting) {
2120
+ nextRenderCommit = void 0;
2121
+ await awaitExit();
2122
+ return;
2123
+ }
2124
+ }
2125
+ reconciler.flushSyncWork();
2126
+ const { canWriteToStdout } = getWritableStreamState(options.stdout);
2127
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
2128
+ if (canWriteToStdout) {
2129
+ await new Promise((resolve) => {
2130
+ options.stdout.write("", () => {
2131
+ resolve();
2132
+ });
2133
+ });
2134
+ return;
2135
+ }
2136
+ await setImmediate$1();
2137
+ }
2138
+ function clear() {
2139
+ if (interactive && !options.debug) {
2140
+ clearLiveOutput();
2141
+ if (isScreenReaderEnabled) accessiblePresenter.sync(lastOutputToRender || lastOutput + "\n");
2142
+ }
2143
+ }
2144
+ function installConsolePatch() {
2145
+ if (options.debug) return;
2146
+ const restoreConsoleMethods = patchConsole((stream, data) => {
2147
+ if (options.onCapturedOutput?.(stream, data, "console") === true) return;
2148
+ if (stream === "stdout") writeToStdout(data);
2149
+ if (stream === "stderr") {
2150
+ if (!data.startsWith("The above error occurred")) writeToStderr(data);
2151
+ }
2152
+ });
2153
+ const restoreDirectStdio = patchDirectStdio();
2154
+ restoreConsole = () => {
2155
+ restoreConsoleMethods();
2156
+ restoreDirectStdio?.();
2157
+ };
2158
+ }
2159
+ function patchDirectStdio() {
2160
+ if (!captureTargets) return;
2161
+ const restoreStdout = patchStreamWrite(captureTargets.stdout, (data) => {
2162
+ handleCapturedStdio("stdout", data);
2163
+ });
2164
+ const restoreStderr = patchStreamWrite(captureTargets.stderr, (data) => {
2165
+ handleCapturedStdio("stderr", data);
2166
+ });
2167
+ return () => {
2168
+ restoreStdout();
2169
+ restoreStderr();
2170
+ };
2171
+ }
2172
+ function handleCapturedStdio(stream, data) {
2173
+ if (options.onCapturedOutput?.(stream, data, "stdio") === true) return;
2174
+ const parts = (capturedStdioTails[stream] + data).split(/\r?\n/);
2175
+ capturedStdioTails[stream] = parts.pop() ?? "";
2176
+ if (parts.length === 0) return;
2177
+ const payload = parts.join("\n") + "\n";
2178
+ if (stream === "stdout") writeToStdout(payload);
2179
+ else writeToStderr(payload);
2180
+ }
2181
+ function flushCapturedStdio() {
2182
+ for (const stream of ["stdout", "stderr"]) {
2183
+ const tail = capturedStdioTails[stream];
2184
+ if (tail === "") continue;
2185
+ capturedStdioTails[stream] = "";
2186
+ if (stream === "stdout") writeToStdout(tail + "\n");
2187
+ else writeToStderr(tail + "\n");
2188
+ }
2189
+ }
2190
+ function registerInputControl(pause, resume) {
2191
+ pauseInput = pause;
2192
+ resumeInput = resume;
2193
+ }
2194
+ async function suspendTerminal(callback) {
2195
+ beginSuspend();
2196
+ if (callback) {
2197
+ try {
2198
+ await callback();
2199
+ } finally {
2200
+ await endSuspend();
2201
+ }
2202
+ return;
2203
+ }
2204
+ const resume = async () => {
2205
+ await endSuspend();
2206
+ };
2207
+ return {
2208
+ resume,
2209
+ [Symbol.asyncDispose]: resume
2210
+ };
2211
+ }
2212
+ function setAlternateScreen(enabled) {
2213
+ terminal.setAlternateScreen(enabled && interactive && Boolean(options.stdout.isTTY), { hideCursor: true });
2214
+ }
2215
+ function shouldSync() {
2216
+ return Boolean(options.stdout.isTTY) && interactive;
2217
+ }
2218
+ async function awaitExit() {
2219
+ try {
2220
+ await exitPromise;
2221
+ } catch {}
2222
+ }
2223
+ function hasPendingConcurrentWork() {
2224
+ const concurrentContainer = container;
2225
+ return (concurrentContainer.pendingLanes ?? 0) !== 0 && concurrentContainer.callbackNode !== void 0 && concurrentContainer.callbackNode !== null;
2226
+ }
2227
+ async function awaitNextRender() {
2228
+ nextRenderCommit ??= Promise.withResolvers();
2229
+ return nextRenderCommit.promise;
2230
+ }
2231
+ function renderInteractiveFrame(output, outputHeight, staticOutput, screen, hasOverflow) {
2232
+ if (!screen) return;
2233
+ const hasStaticOutput = staticOutput !== "";
2234
+ const isTTY = Boolean(options.stdout.isTTY);
2235
+ const viewportRows = isTTY ? windowSize().rows : 24;
2236
+ if (isTTY && outputHeight > viewportRows) {
2237
+ const lines = output.split("\n");
2238
+ output = lines.slice(lines.length - viewportRows).join("\n");
2239
+ outputHeight = viewportRows;
2240
+ screen = bottomRows(screen, viewportRows);
2241
+ }
2242
+ const isFullscreen = isTTY && outputHeight >= viewportRows;
2243
+ const outputToRender = isFullscreen ? output : output + "\n";
2244
+ if (shouldClearTerminalForFrame({
2245
+ isTTY,
2246
+ viewportRows,
2247
+ previousOutputHeight: lastOutputHeight,
2248
+ nextOutputHeight: outputHeight,
2249
+ isUnmounting
2250
+ })) {
2251
+ const sync = shouldSync();
2252
+ if (sync) options.stdout.write(bsu);
2253
+ options.stdout.write(ansiEscapes.clearTerminal + fullStaticOutput + outputToRender);
2254
+ lastOutput = output;
2255
+ lastOutputToRender = outputToRender;
2256
+ lastOutputHeight = outputHeight;
2257
+ lastScreen = screen;
2258
+ terminal.resetFrame();
2259
+ if (sync) options.stdout.write(esu);
2260
+ return;
2261
+ }
2262
+ const willPresent = terminal.willPresent(screen, {
2263
+ fullscreen: isFullscreen,
2264
+ forceRewrite: hasStaticOutput || hasOverflow
2265
+ });
2266
+ const sync = shouldSync() && willPresent;
2267
+ if (sync) terminal.write(bsu);
2268
+ if (hasStaticOutput) {
2269
+ terminal.clearFrame();
2270
+ terminal.write(staticOutput);
2271
+ }
2272
+ terminal.present(screen, {
2273
+ fullscreen: isFullscreen,
2274
+ forceRewrite: hasStaticOutput || hasOverflow
2275
+ });
2276
+ if (sync) terminal.write(esu);
2277
+ lastOutput = output;
2278
+ lastOutputToRender = outputToRender;
2279
+ lastOutputHeight = outputHeight;
2280
+ lastScreen = screen;
2281
+ }
2282
+ function initKittyKeyboard() {
2283
+ if (!options.kittyKeyboard) return;
2284
+ const opts = options.kittyKeyboard;
2285
+ const mode = opts.mode ?? "auto";
2286
+ if (mode === "disabled") return;
2287
+ const flags = opts.flags ?? ["disambiguateEscapeCodes"];
2288
+ if (mode === "enabled") {
2289
+ if (isTty(options.stdin) && options.stdout.isTTY) enableKittyProtocol(flags);
2290
+ return;
2291
+ }
2292
+ if (!interactive || !isTty(options.stdin) || !options.stdout.isTTY) return;
2293
+ cancelKittyDetection = detectKittySupport(options.stdin, options.stdout, () => {
2294
+ cancelKittyDetection = void 0;
2295
+ if (!isUnmounted) enableKittyProtocol(flags);
2296
+ });
2297
+ }
2298
+ function enableKittyProtocol(flags) {
2299
+ options.stdout.write(ansiEscapes.pushKittyKeyboard(resolveFlags(flags)));
2300
+ kittyProtocolEnabled = true;
2301
+ kittyFlags = flags;
2302
+ }
2303
+ function beginSuspend() {
2304
+ terminal.beginSuspension();
2305
+ if (!interactive || isUnmounted || isUnmounting) return;
2306
+ try {
2307
+ const { canWriteToStdout } = getWritableStreamState(options.stdout);
2308
+ settleThrottle(renderScheduler.throttled, canWriteToStdout);
2309
+ if (canWriteToStdout) flushCapturedStdio();
2310
+ if (canWriteToStdout) {
2311
+ clearLiveOutput();
2312
+ finishLiveOutput();
2313
+ if (kittyProtocolEnabled) writeBestEffort(options.stdout, ansiEscapes.popKittyKeyboard);
2314
+ if (terminal.alternateScreen) writeBestEffort(options.stdout, ansiEscapes.exitAlternativeScreen);
2315
+ }
2316
+ pauseInput?.();
2317
+ } catch (error) {
2318
+ terminal.resume();
2319
+ try {
2320
+ resumeInput?.();
2321
+ } catch {}
2322
+ throw error;
2323
+ }
2324
+ }
2325
+ async function endSuspend() {
2326
+ if (!terminal.suspended) return;
2327
+ terminal.resume();
2328
+ resumeInput?.();
2329
+ if (!interactive || isUnmounted || isUnmounting) return;
2330
+ const { canWriteToStdout } = getWritableStreamState(options.stdout);
2331
+ if (canWriteToStdout) {
2332
+ if (terminal.alternateScreen) writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
2333
+ if (kittyProtocolEnabled && kittyFlags) writeBestEffort(options.stdout, ansiEscapes.pushKittyKeyboard(resolveFlags(kittyFlags)));
2334
+ }
2335
+ lastOutput = "";
2336
+ lastOutputToRender = "";
2337
+ lastOutputHeight = 0;
2338
+ resetLiveOutput();
2339
+ try {
2340
+ calculateLayout();
2341
+ onRender();
2342
+ await waitUntilRenderFlush();
2343
+ } catch {}
2344
+ }
2345
+ return {
2346
+ render,
2347
+ unmount,
2348
+ waitUntilExit,
2349
+ waitUntilRenderFlush,
2350
+ clear,
2351
+ copyToClipboard: (text, selection) => terminal.copyToClipboard(text, selection),
2352
+ setProgress: (state, value) => terminal.setProgress(state, value)
2353
+ };
2354
+ };
2355
+ //#endregion
2356
+ //#region src/render.ts
2357
+ /**
2358
+ Mount a component and render the output.
2359
+ */
2360
+ const render = (node, options) => {
2361
+ const inkOptions = {
2362
+ stdout: process.stdout,
2363
+ stdin: process.stdin,
2364
+ stderr: process.stderr,
2365
+ debug: false,
2366
+ exitOnCtrlC: true,
2367
+ patchConsole: true,
2368
+ maxFps: 30,
2369
+ concurrent: false,
2370
+ alternateScreen: false,
2371
+ ...getOptions(options)
2372
+ };
2373
+ const instance = getInstance(inkOptions.stdout, () => createInk(inkOptions));
2374
+ instance.render(node);
2375
+ return {
2376
+ rerender: instance.render,
2377
+ unmount() {
2378
+ instance.unmount();
2379
+ },
2380
+ waitUntilExit: instance.waitUntilExit,
2381
+ waitUntilRenderFlush: instance.waitUntilRenderFlush,
2382
+ cleanup() {
2383
+ instance.unmount();
2384
+ },
2385
+ clear: instance.clear,
2386
+ copyToClipboard: instance.copyToClipboard,
2387
+ setProgress: instance.setProgress
2388
+ };
2389
+ };
2390
+ const getOptions = (stdout = {}) => {
2391
+ if (stdout instanceof Stream) return {
2392
+ stdout,
2393
+ stdin: process.stdin
2394
+ };
2395
+ return stdout;
2396
+ };
2397
+ const getInstance = (stdout, createInstance) => {
2398
+ const instance = instances.get(stdout);
2399
+ if (instance === void 0) {
2400
+ const newInstance = createInstance();
2401
+ instances.set(stdout, newInstance);
2402
+ return newInstance;
2403
+ }
2404
+ process.stderr.write("Warning: render() was called again for the same stdout before the previous Ink instance was unmounted. Reusing stdout across multiple render() calls is unsupported. Call unmount() first.\n");
2405
+ return instance;
2406
+ };
2407
+ //#endregion
2408
+ //#region src/render-to-string.ts
2409
+ /**
2410
+ Render a React element to a string synchronously. Unlike `render()`, this function does not write to stdout, does not set up any terminal event listeners, and returns the rendered output as a string.
2411
+
2412
+ Useful for generating documentation, writing output to files, testing, or any scenario where you need the rendered output as a string without starting a persistent terminal application.
2413
+
2414
+ **Notes:**
2415
+
2416
+ - Terminal-specific hooks (`useInput`, `useStdin`, `useStdout`, `useStderr`, `useApp`, `useFocus`, `useFocusManager`) return default no-op values since there is no terminal session. They will not throw, but they will not function as in a live terminal.
2417
+ - `useEffect` callbacks will execute during rendering (due to synchronous rendering mode), but state updates they trigger will not affect the returned output, which reflects the initial render.
2418
+ - `useLayoutEffect` callbacks fire synchronously during commit, so state updates they trigger **will** be reflected in the output.
2419
+ - The `<Static>` component is supported — its output is prepended to the dynamic output.
2420
+ - If a component throws during rendering, the error is propagated to the caller after cleanup.
2421
+
2422
+ @example
2423
+ ```
2424
+ import {renderToString, Text, Box} from 'ink';
2425
+
2426
+ const output = renderToString(
2427
+ <Box padding={1}>
2428
+ <Text color="green">Hello World</Text>
2429
+ </Box>,
2430
+ {columns: 40}
2431
+ );
2432
+
2433
+ console.log(output);
2434
+ ```
2435
+ */
2436
+ const renderToString = (node, options) => {
2437
+ const columns = options?.columns ?? 80;
2438
+ const colorProfile = options?.colorProfile ?? colorProfileFromLevel(detectColorLevel());
2439
+ const rootNode = createNode("ink-root");
2440
+ let capturedStaticOutput = "";
2441
+ rootNode.onComputeLayout = () => {
2442
+ rootNode.yogaNode.setWidth(columns);
2443
+ rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
2444
+ };
2445
+ rootNode.onImmediateRender = () => {
2446
+ const { staticScreen } = renderFrame(rootNode, false, { colorProfile });
2447
+ const staticOutput = staticScreen ? `${serializeScreen(staticScreen, {
2448
+ colorProfile,
2449
+ styles: colorProfile !== "none"
2450
+ })}\n` : "";
2451
+ if (staticOutput && staticOutput !== "\n") capturedStaticOutput += staticOutput;
2452
+ };
2453
+ let uncaughtError;
2454
+ const container = reconciler.createContainer(rootNode, LegacyRoot, null, false, null, "render-to-string", (error) => {
2455
+ uncaughtError ??= error;
2456
+ }, () => {}, () => {}, () => {});
2457
+ reconciler.updateContainerSync(node, container, null, () => {});
2458
+ reconciler.flushSyncWork();
2459
+ const { screen } = renderFrame(rootNode, false, { colorProfile });
2460
+ const output = screen ? serializeScreen(screen, {
2461
+ colorProfile,
2462
+ styles: colorProfile !== "none"
2463
+ }) : "";
2464
+ reconciler.updateContainerSync(null, container, null, () => {});
2465
+ reconciler.flushSyncWork();
2466
+ if (uncaughtError !== void 0) throw uncaughtError instanceof Error ? uncaughtError : new Error(String(uncaughtError));
2467
+ const normalizedStaticOutput = capturedStaticOutput.endsWith("\n") ? capturedStaticOutput.slice(0, -1) : capturedStaticOutput;
2468
+ if (normalizedStaticOutput && output) return normalizedStaticOutput + "\n" + output;
2469
+ return normalizedStaticOutput || output;
2470
+ };
2471
+ //#endregion
2472
+ //#region src/components/AnsiText.tsx
2473
+ /** @jsxImportSource react */
2474
+ /**
2475
+ * Renders explicitly trusted ANSI-styled output as structured terminal cells.
2476
+ * Ordinary `Text` continues to strip terminal control sequences.
2477
+ */
2478
+ function AnsiText({ children, wrap = "wrap", "aria-label": ariaLabel, "aria-hidden": ariaHidden = false }) {
2479
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
2480
+ if (isScreenReaderEnabled) {
2481
+ if (ariaHidden) return null;
2482
+ return /* @__PURE__ */ jsx(Text, { children: ariaLabel ?? stripAnsi(children) });
2483
+ }
2484
+ return /* @__PURE__ */ jsx("ink-text", {
2485
+ style: {
2486
+ flexGrow: 0,
2487
+ flexShrink: 1,
2488
+ flexDirection: "row",
2489
+ textWrap: wrap
2490
+ },
2491
+ internal_ansi: true,
2492
+ children
2493
+ });
2494
+ }
2495
+ //#endregion
2496
+ //#region src/components/Static.tsx
2497
+ /** @jsxImportSource react */
2498
+ /**
2499
+ `<Static>` component permanently renders its output above everything else. It's useful for displaying activity like completed tasks or logs—things that don't change after they're rendered (hence the name "Static").
2500
+
2501
+ It's preferred to use `<Static>` for use cases like these when you can't know or control the number of items that need to be rendered.
2502
+
2503
+ For example, [Tap](https://github.com/tapjs/node-tap) uses `<Static>` to display a list of completed tests. [Gatsby](https://github.com/gatsbyjs/gatsby) uses it to display a list of generated pages while still displaying a live progress bar.
2504
+ */
2505
+ function Static(props) {
2506
+ const { items, children: render, style: customStyle } = props;
2507
+ const [index, setIndex] = useState(0);
2508
+ const itemsToRender = useMemo(() => {
2509
+ return items.slice(index);
2510
+ }, [items, index]);
2511
+ useLayoutEffect(() => {
2512
+ setIndex(items.length);
2513
+ }, [items.length]);
2514
+ const children = itemsToRender.map((item, itemIndex) => {
2515
+ return render(item, index + itemIndex);
2516
+ });
2517
+ const style = useMemo(() => ({
2518
+ position: "absolute",
2519
+ flexDirection: "column",
2520
+ ...customStyle
2521
+ }), [customStyle]);
2522
+ return /* @__PURE__ */ jsx("ink-box", {
2523
+ internal_static: true,
2524
+ style,
2525
+ children
2526
+ });
2527
+ }
2528
+ //#endregion
2529
+ //#region src/components/Transform.tsx
2530
+ /** @jsxImportSource react */
2531
+ /**
2532
+ Transform a string representation of React components before they're written to output. For example, you might want to apply a gradient to text, add a clickable link, or create some text effects. These use cases can't accept React nodes as input; they expect a string. That's what the <Transform> component does: it gives you an output string of its child components and lets you transform it in any way.
2533
+ */
2534
+ function Transform({ children, transform, accessibilityLabel }) {
2535
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
2536
+ if (children === void 0 || children === null) return null;
2537
+ return /* @__PURE__ */ jsx("ink-text", {
2538
+ style: {
2539
+ flexGrow: 0,
2540
+ flexShrink: 1,
2541
+ flexDirection: "row"
2542
+ },
2543
+ internal_transform: transform,
2544
+ children: isScreenReaderEnabled && accessibilityLabel ? accessibilityLabel : children
2545
+ });
2546
+ }
2547
+ //#endregion
2548
+ //#region src/hooks/use-stdout.ts
2549
+ /**
2550
+ A React hook that returns the stdout stream where Ink renders your app.
2551
+ */
2552
+ const useStdout = () => useContext(StdoutContext);
2553
+ //#endregion
2554
+ //#region src/components/Hyperlink.tsx
2555
+ /**
2556
+ A clickable OSC 8 hyperlink — the counterpart to the router's `<Link>`, which
2557
+ navigates between screens. On terminals without hyperlink support it falls
2558
+ back to `text (url)`.
2559
+
2560
+ ```tsx
2561
+ <Hyperlink url="https://example.com">Documentation</Hyperlink>
2562
+ ```
2563
+ */
2564
+ function Hyperlink({ url, fallback = true, children, ...textProps }) {
2565
+ const { stdout } = useStdout();
2566
+ if (!detectHyperlinkSupport(stdout)) return /* @__PURE__ */ jsxs(Text, {
2567
+ ...textProps,
2568
+ children: [children, fallback ? /* @__PURE__ */ jsxs(Text, {
2569
+ dimColor: true,
2570
+ children: [
2571
+ " (",
2572
+ url,
2573
+ ")"
2574
+ ]
2575
+ }) : null]
2576
+ });
2577
+ return /* @__PURE__ */ jsx(Transform, {
2578
+ transform: (text) => link(text, url),
2579
+ children: /* @__PURE__ */ jsx(Text, {
2580
+ ...textProps,
2581
+ children
2582
+ })
2583
+ });
2584
+ }
2585
+ //#endregion
2586
+ //#region src/components/Newline.tsx
2587
+ /**
2588
+ Adds one or more newline (`\n`) characters. Must be used within `<Text>` components.
2589
+ */
2590
+ function Newline({ count = 1 }) {
2591
+ return /* @__PURE__ */ jsx("ink-text", { children: "\n".repeat(count) });
2592
+ }
2593
+ //#endregion
2594
+ //#region src/components/Spacer.tsx
2595
+ /** @jsxImportSource react */
2596
+ /**
2597
+ A flexible space that expands along the major axis of its containing layout.
2598
+
2599
+ It's useful as a shortcut for filling all the available space between elements.
2600
+ */
2601
+ function Spacer() {
2602
+ return /* @__PURE__ */ jsx(Box, { flexGrow: 1 });
2603
+ }
2604
+ //#endregion
2605
+ //#region src/virtual-scroll.ts
2606
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
2607
+ const wholeRows = (value) => Math.max(0, Math.floor(value)) || 0;
2608
+ /**
2609
+ Compute which items intersect a viewport over a vertical stack of items.
2610
+ */
2611
+ const virtualScrollWindow = (options) => {
2612
+ const count = wholeRows(options.count);
2613
+ const viewportHeight = wholeRows(options.viewportHeight);
2614
+ const tops = [];
2615
+ let totalHeight = 0;
2616
+ for (let index = 0; index < count; index++) {
2617
+ tops.push(totalHeight);
2618
+ totalHeight += wholeRows(options.itemHeight(index));
2619
+ }
2620
+ const bottomOf = (index) => index + 1 < count ? tops[index + 1] : totalHeight;
2621
+ const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
2622
+ let scrollTop = clamp(Math.floor(options.scrollTop) || 0, 0, maxScrollTop);
2623
+ const focused = options.focusedIndex;
2624
+ if (focused !== void 0 && Number.isInteger(focused) && focused >= 0 && focused < count) {
2625
+ const top = tops[focused];
2626
+ const bottom = bottomOf(focused);
2627
+ if (top < scrollTop) scrollTop = top;
2628
+ else if (bottom > scrollTop + viewportHeight) scrollTop = Math.min(top, bottom - viewportHeight);
2629
+ scrollTop = clamp(scrollTop, 0, maxScrollTop);
2630
+ }
2631
+ const viewportBottom = scrollTop + viewportHeight;
2632
+ let start = 0;
2633
+ while (start < count && bottomOf(start) <= scrollTop) start++;
2634
+ let end = start;
2635
+ while (end < count && tops[end] < viewportBottom) end++;
2636
+ return {
2637
+ start,
2638
+ end,
2639
+ scrollTop,
2640
+ maxScrollTop,
2641
+ totalHeight,
2642
+ offset: start < end ? tops[start] - scrollTop : 0,
2643
+ hiddenAbove: scrollTop,
2644
+ hiddenBelow: Math.max(0, totalHeight - viewportBottom)
2645
+ };
2646
+ };
2647
+ //#endregion
2648
+ //#region src/hooks/use-virtual-scroll.ts
2649
+ /**
2650
+ A React hook that owns the scroll position of a windowed list and returns which
2651
+ items to render for it. The position is clamped to the scrollable range and,
2652
+ while `focusedIndex` is set, moved as little as necessary to keep that item
2653
+ fully visible. Render the items in `[start, end)` inside an `overflowY="hidden"`
2654
+ box of `viewportHeight` rows, shifted up by `offset` rows.
2655
+
2656
+ `<VirtualList>` wraps this hook; use it directly to draw your own chrome such
2657
+ as overflow markers or a scrollbar around the window.
2658
+
2659
+ @example
2660
+ ```tsx
2661
+ import { Box, Text, useVirtualScroll } from "@alchemy.run/sigil";
2662
+
2663
+ const Example = ({ lines, cursor }: { lines: string[]; cursor: number }) => {
2664
+ const { start, end, offset, hiddenAbove, hiddenBelow } = useVirtualScroll({
2665
+ count: lines.length,
2666
+ itemHeight: () => 1,
2667
+ viewportHeight: 10,
2668
+ focusedIndex: cursor,
2669
+ });
2670
+
2671
+ return (
2672
+ <Box flexDirection="column">
2673
+ <Text dimColor>{hiddenAbove > 0 ? `↑ ${hiddenAbove} more` : ""}</Text>
2674
+ <Box flexDirection="column" height={10} overflowY="hidden">
2675
+ <Box flexDirection="column" flexShrink={0} marginTop={offset}>
2676
+ {lines.slice(start, end).map((line, index) => (
2677
+ <Text key={start + index} inverse={start + index === cursor}>
2678
+ {line}
2679
+ </Text>
2680
+ ))}
2681
+ </Box>
2682
+ </Box>
2683
+ <Text dimColor>{hiddenBelow > 0 ? `↓ ${hiddenBelow} more` : ""}</Text>
2684
+ </Box>
2685
+ );
2686
+ };
2687
+ ```
2688
+ */
2689
+ const useVirtualScroll = (options) => {
2690
+ const [position, setPosition] = useState(0);
2691
+ const window = virtualScrollWindow({
2692
+ ...options,
2693
+ scrollTop: position
2694
+ });
2695
+ useEffect(() => {
2696
+ if (window.scrollTop !== position) setPosition(window.scrollTop);
2697
+ }, [window.scrollTop, position]);
2698
+ const scrollTo = useCallback((top) => {
2699
+ setPosition(top);
2700
+ }, []);
2701
+ const scrollBy = useCallback((delta) => {
2702
+ setPosition((current) => current + delta);
2703
+ }, []);
2704
+ return {
2705
+ ...window,
2706
+ scrollTo,
2707
+ scrollBy
2708
+ };
2709
+ };
2710
+ //#endregion
2711
+ //#region src/components/VirtualList.tsx
2712
+ /** @jsxImportSource react */
2713
+ const rootOf = (node) => {
2714
+ let current = node;
2715
+ while (current?.parentNode) current = current.parentNode;
2716
+ return current?.nodeName === "ink-root" ? current : void 0;
2717
+ };
2718
+ /**
2719
+ A vertically windowed list: only the items intersecting the viewport are
2720
+ rendered, inside a clipped box that scrolls by whole rows. Items may have
2721
+ different heights, and the item at the top edge may be partially visible.
2722
+
2723
+ Until the first layout pass has measured the viewport, every item is rendered
2724
+ inside the clipped box so the first frame already looks right.
2725
+ */
2726
+ function VirtualList({ items, itemHeight, renderItem, getKey, focusedIndex, height }) {
2727
+ const ref = useRef(null);
2728
+ const [measured, setMeasured] = useState();
2729
+ const measure = () => {
2730
+ const next = ref.current?.yogaNode?.getComputedHeight();
2731
+ if (next !== void 0) setMeasured((previous) => previous === next ? previous : next);
2732
+ };
2733
+ useLayoutEffect(() => {
2734
+ if (height === void 0) measure();
2735
+ });
2736
+ useEffect(() => {
2737
+ if (height !== void 0) return;
2738
+ const root = rootOf(ref.current);
2739
+ return root ? addLayoutListener(root, measure) : void 0;
2740
+ });
2741
+ const viewportHeight = height ?? measured;
2742
+ const windowed = viewportHeight !== void 0;
2743
+ const window = useVirtualScroll({
2744
+ count: items.length,
2745
+ itemHeight: (index) => itemHeight(items[index], index),
2746
+ viewportHeight: viewportHeight ?? 0,
2747
+ focusedIndex: windowed ? focusedIndex : void 0
2748
+ });
2749
+ const start = windowed ? window.start : 0;
2750
+ const end = windowed ? window.end : items.length;
2751
+ const offset = windowed ? window.offset : 0;
2752
+ return /* @__PURE__ */ jsx(Box, {
2753
+ ref,
2754
+ flexDirection: "column",
2755
+ overflowY: "hidden",
2756
+ ...height === void 0 ? {
2757
+ height: window.totalHeight,
2758
+ minHeight: 0,
2759
+ flexShrink: 1
2760
+ } : {
2761
+ height,
2762
+ flexShrink: 0
2763
+ },
2764
+ children: /* @__PURE__ */ jsx(Box, {
2765
+ flexDirection: "column",
2766
+ flexShrink: 0,
2767
+ marginTop: offset,
2768
+ children: items.slice(start, end).map((item, sliceIndex) => {
2769
+ const index = start + sliceIndex;
2770
+ return /* @__PURE__ */ jsx(Box, {
2771
+ flexDirection: "column",
2772
+ flexShrink: 0,
2773
+ children: renderItem(item, index)
2774
+ }, getKey ? getKey(item, index) : index);
2775
+ })
2776
+ })
2777
+ });
2778
+ }
2779
+ //#endregion
2780
+ //#region src/hooks/use-capabilities.ts
2781
+ /**
2782
+ Returns everything knowable about the terminal: size, identity, platform,
2783
+ color depth, theme, and feature support.
2784
+
2785
+ A thin wrapper over the framework-free capabilities store (`getCapabilities`):
2786
+ environment-derived facts are available immediately; facts only the terminal
2787
+ itself can answer fill in after a lazy one-time query, and re-mounting
2788
+ consumers refreshes the dynamic facts (theme colors, pixel geometry).
2789
+ Re-renders on terminal resize and whenever query answers arrive.
2790
+ */
2791
+ const useCapabilities = () => {
2792
+ const { stdout } = useStdout();
2793
+ const { stdin } = useStdinContext();
2794
+ const store = getCapabilities(stdin, stdout);
2795
+ const capabilities = useSyncExternalStore(store.subscribe, () => store.current);
2796
+ useEffect(() => {
2797
+ store.query();
2798
+ }, [store]);
2799
+ return capabilities;
2800
+ };
2801
+ /**
2802
+ Calls `onChange` whenever the terminal changes: resizes (including in-band
2803
+ pixel geometry), color scheme switches, window focus, and query answers
2804
+ arriving. The React wrapper over `capabilities.subscribe()` for side effects —
2805
+ for rendering, use `useCapabilities` instead.
2806
+
2807
+ The callback always sees the latest render's closure and changing it does not
2808
+ resubscribe. Both the new and previous snapshot are passed, so handlers can
2809
+ react to the specific change:
2810
+
2811
+ ```tsx
2812
+ useCapabilitiesChange((next, previous) => {
2813
+ if (next.theme.appearance !== previous.theme.appearance) {
2814
+ // re-theme
2815
+ }
2816
+ });
2817
+ ```
2818
+ */
2819
+ const useCapabilitiesChange = (onChange) => {
2820
+ const { stdout } = useStdout();
2821
+ const { stdin } = useStdinContext();
2822
+ const store = getCapabilities(stdin, stdout);
2823
+ const handleChange = useEffectEvent(onChange);
2824
+ useEffect(() => {
2825
+ let previous = store.current;
2826
+ const unsubscribe = store.subscribe((next) => {
2827
+ const before = previous;
2828
+ previous = next;
2829
+ handleChange(next, before);
2830
+ });
2831
+ store.query();
2832
+ return unsubscribe;
2833
+ }, [store]);
2834
+ };
2835
+ //#endregion
2836
+ //#region src/hooks/use-paste.ts
2837
+ /**
2838
+ A React hook that calls `handler` whenever the user pastes text in the terminal. Bracketed paste mode (`\x1b[?2004h`) is automatically enabled while the hook is active, so pasted text arrives as a single string rather than being misinterpreted as individual key presses.
2839
+
2840
+ `usePaste` and `useInput` can be used together in the same component. They operate on separate event channels, so paste content is never forwarded to `useInput` handlers when `usePaste` is active.
2841
+
2842
+ ```
2843
+ import {useInput, usePaste} from 'ink';
2844
+
2845
+ const MyInput = () => {
2846
+ useInput((input, key) => {
2847
+ // Only receives typed characters and key events, not pasted text.
2848
+ if (key.return) {
2849
+ // Submit
2850
+ }
2851
+ });
2852
+
2853
+ usePaste((text) => {
2854
+ // Receives the full pasted string, including newlines.
2855
+ console.log('Pasted:', text);
2856
+ });
2857
+
2858
+ return …
2859
+ };
2860
+ ```
2861
+ */
2862
+ const usePaste = (handler, options = {}) => {
2863
+ const { setRawMode, setBracketedPasteMode, internal_eventEmitter } = useStdinContext();
2864
+ useEffect(() => {
2865
+ if (options.isActive === false) return;
2866
+ setRawMode(true);
2867
+ setBracketedPasteMode(true);
2868
+ return () => {
2869
+ setRawMode(false);
2870
+ setBracketedPasteMode(false);
2871
+ };
2872
+ }, [
2873
+ options.isActive,
2874
+ setRawMode,
2875
+ setBracketedPasteMode
2876
+ ]);
2877
+ const handlePaste = useEffectEvent((text) => {
2878
+ reconciler.discreteUpdates(() => {
2879
+ handler(text);
2880
+ });
2881
+ });
2882
+ useEffect(() => {
2883
+ if (options.isActive === false) return;
2884
+ internal_eventEmitter.on("paste", handlePaste);
2885
+ return () => {
2886
+ internal_eventEmitter.removeListener("paste", handlePaste);
2887
+ };
2888
+ }, [options.isActive, internal_eventEmitter]);
2889
+ };
2890
+ //#endregion
2891
+ //#region src/hooks/use-app.ts
2892
+ /**
2893
+ A React hook that returns app lifecycle methods like `exit()` and `waitUntilRenderFlush()`.
2894
+ */
2895
+ const useApp = () => useContext(AppContext);
2896
+ //#endregion
2897
+ //#region src/hooks/use-stderr.ts
2898
+ /**
2899
+ A React hook that returns the stderr stream.
2900
+ */
2901
+ const useStderr = () => useContext(StderrContext);
2902
+ //#endregion
2903
+ //#region src/hooks/use-focus-manager.ts
2904
+ /**
2905
+ A React hook that returns methods to enable or disable focus management for all components or manually switch focus to the next or previous components.
2906
+ */
2907
+ const useFocusManager = () => {
2908
+ const focusContext = useContext(FocusContext);
2909
+ return {
2910
+ enableFocus: focusContext.enableFocus,
2911
+ disableFocus: focusContext.disableFocus,
2912
+ focusNext: focusContext.focusNext,
2913
+ focusPrevious: focusContext.focusPrevious,
2914
+ focus: focusContext.focus,
2915
+ activeId: focusContext.activeId
2916
+ };
2917
+ };
2918
+ //#endregion
2919
+ //#region src/hooks/use-is-screen-reader-enabled.ts
2920
+ /**
2921
+ A React hook that returns whether a screen reader is enabled.
2922
+ This is useful when you want to render different output for screen readers.
2923
+ */
2924
+ const useIsScreenReaderEnabled = () => {
2925
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
2926
+ return isScreenReaderEnabled;
2927
+ };
2928
+ //#endregion
2929
+ //#region src/hooks/use-cursor.ts
2930
+ /**
2931
+ A React hook that returns methods to control the terminal cursor position.
2932
+
2933
+ Setting a cursor position makes the cursor visible at the specified coordinates (relative to the Ink output origin). This is useful for IME (Input Method Editor) support, where the composing character is displayed at the cursor location.
2934
+
2935
+ Pass `undefined` to hide the cursor.
2936
+ */
2937
+ const useCursor = () => {
2938
+ const context = useContext(CursorContext);
2939
+ const positionRef = useRef(void 0);
2940
+ const setCursorPosition = useCallback((position) => {
2941
+ positionRef.current = position;
2942
+ }, []);
2943
+ useInsertionEffect(() => {
2944
+ context.setCursorPosition(positionRef.current);
2945
+ return () => {
2946
+ context.setCursorPosition(void 0);
2947
+ };
2948
+ });
2949
+ return { setCursorPosition };
2950
+ };
2951
+ //#endregion
2952
+ //#region src/hooks/use-animation.ts
2953
+ const defaultAnimationInterval = 100;
2954
+ const maximumTimerInterval = 2147483647;
2955
+ const zeroAnimState = {
2956
+ frame: 0,
2957
+ time: 0,
2958
+ delta: 0
2959
+ };
2960
+ /**
2961
+ A React hook that drives animations. Returns a frame counter, elapsed time, frame delta, and a reset function. All animations share a single timer internally, so multiple animated components consolidate into one render cycle.
2962
+
2963
+ @example
2964
+ ```
2965
+ import {Text, useAnimation} from 'ink';
2966
+
2967
+ const Spinner = () => {
2968
+ const {frame} = useAnimation({interval: 80});
2969
+ const characters = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
2970
+
2971
+ return <Text>{characters[frame % characters.length]}</Text>;
2972
+ };
2973
+ ```
2974
+ */
2975
+ function useAnimation(options) {
2976
+ const { interval = defaultAnimationInterval, isActive = true } = options ?? {};
2977
+ const safeInterval = normalizeAnimationInterval(interval);
2978
+ const { subscribe, renderThrottleMs } = useContext(animationContext);
2979
+ const [resetKey, setResetKey] = useState(0);
2980
+ const [animState, setAnimState] = useState(zeroAnimState);
2981
+ const nextRenderTimeRef = useRef(0);
2982
+ const lastRenderTimeRef = useRef(0);
2983
+ const previousOptionsRef = useRef({
2984
+ isActive,
2985
+ safeInterval,
2986
+ resetKey
2987
+ });
2988
+ const previousOptions = previousOptionsRef.current;
2989
+ const shouldReset = isActive && (safeInterval !== previousOptions.safeInterval || !previousOptions.isActive || resetKey !== previousOptions.resetKey);
2990
+ const reset = useCallback(() => {
2991
+ setResetKey((k) => k + 1);
2992
+ }, []);
2993
+ useLayoutEffect(() => {
2994
+ if (!isActive) return;
2995
+ setAnimState(zeroAnimState);
2996
+ let startTime = 0;
2997
+ const { startTime: subscriberStartTime, unsubscribe } = subscribe((currentTime) => {
2998
+ if (renderThrottleMs > 0 && currentTime < nextRenderTimeRef.current) return;
2999
+ const elapsed = currentTime - startTime;
3000
+ const nextDelta = currentTime - lastRenderTimeRef.current;
3001
+ lastRenderTimeRef.current = currentTime;
3002
+ nextRenderTimeRef.current = currentTime + renderThrottleMs;
3003
+ setAnimState({
3004
+ frame: Math.floor(elapsed / safeInterval),
3005
+ time: elapsed,
3006
+ delta: nextDelta
3007
+ });
3008
+ }, safeInterval);
3009
+ startTime = subscriberStartTime;
3010
+ lastRenderTimeRef.current = subscriberStartTime;
3011
+ nextRenderTimeRef.current = startTime + renderThrottleMs;
3012
+ return unsubscribe;
3013
+ }, [
3014
+ safeInterval,
3015
+ isActive,
3016
+ subscribe,
3017
+ renderThrottleMs,
3018
+ resetKey
3019
+ ]);
3020
+ useLayoutEffect(() => {
3021
+ previousOptionsRef.current = {
3022
+ isActive,
3023
+ safeInterval,
3024
+ resetKey
3025
+ };
3026
+ }, [
3027
+ isActive,
3028
+ safeInterval,
3029
+ resetKey
3030
+ ]);
3031
+ if (shouldReset) return {
3032
+ ...zeroAnimState,
3033
+ reset
3034
+ };
3035
+ return {
3036
+ ...animState,
3037
+ reset
3038
+ };
3039
+ }
3040
+ function normalizeAnimationInterval(interval) {
3041
+ if (!Number.isFinite(interval)) return defaultAnimationInterval;
3042
+ return Math.min(maximumTimerInterval, Math.max(1, interval));
3043
+ }
3044
+ //#endregion
3045
+ //#region src/hooks/use-terminal-osc.ts
3046
+ const useProgress = ({ state, value }) => {
3047
+ const terminal = useContext(TerminalOscContext);
3048
+ const owner = useRef(void 0);
3049
+ owner.current ??= Symbol("terminal-progress");
3050
+ useEffect(() => {
3051
+ terminal.publishProgress(owner.current, state, value);
3052
+ }, [
3053
+ state,
3054
+ terminal,
3055
+ value
3056
+ ]);
3057
+ useEffect(() => {
3058
+ return () => {
3059
+ terminal.publishProgress(owner.current, "inactive");
3060
+ };
3061
+ }, [terminal]);
3062
+ };
3063
+ const useClipboard = () => {
3064
+ const terminal = useContext(TerminalOscContext);
3065
+ return useCallback((text, selection) => terminal.copyToClipboard(text, selection), [terminal]);
3066
+ };
3067
+ const useTitle = (title) => {
3068
+ const terminal = useContext(TerminalOscContext);
3069
+ const owner = useRef(void 0);
3070
+ owner.current ??= Symbol("terminal-title");
3071
+ useEffect(() => {
3072
+ terminal.publishTitle(owner.current, title);
3073
+ }, [terminal, title]);
3074
+ useEffect(() => () => terminal.publishTitle(owner.current, void 0), [terminal]);
3075
+ };
3076
+ const useWorkingDirectory = (directory) => {
3077
+ const terminal = useContext(TerminalOscContext);
3078
+ useEffect(() => {
3079
+ terminal.setWorkingDirectory(directory);
3080
+ }, [directory, terminal]);
3081
+ };
3082
+ const useNotification = () => {
3083
+ const terminal = useContext(TerminalOscContext);
3084
+ return useCallback((title) => terminal.notify(title), [terminal]);
3085
+ };
3086
+ const usePointerShape = (shape) => {
3087
+ const terminal = useContext(TerminalOscContext);
3088
+ useEffect(() => {
3089
+ terminal.setPointerShape(shape);
3090
+ }, [shape, terminal]);
3091
+ };
3092
+ //#endregion
3093
+ //#region src/hooks/use-window-size.ts
3094
+ /**
3095
+ A React hook that returns the current terminal window dimensions and re-renders the component whenever the terminal is resized.
3096
+
3097
+ Reads the capabilities store, so on terminals that send in-band size reports
3098
+ (mode 2048) the dimensions are the emulator's own, arriving after it has
3099
+ rewrapped its screen; elsewhere they are the stream's `columns`/`rows`.
3100
+ Subscribing to dimensions does not initiate capability queries. In-band reports
3101
+ are used when another consumer has explicitly enabled capability discovery.
3102
+ */
3103
+ const useWindowSize = () => {
3104
+ const { stdin } = useStdinContext();
3105
+ const { stdout } = useStdout();
3106
+ const store = getCapabilities(stdin, stdout);
3107
+ const { size } = useSyncExternalStore(store.subscribe, () => store.current);
3108
+ return useMemo(() => ({
3109
+ columns: size.columns,
3110
+ rows: size.rows
3111
+ }), [size.columns, size.rows]);
3112
+ };
3113
+ //#endregion
3114
+ //#region src/hooks/use-box-metrics.ts
3115
+ const emptyMetrics = {
3116
+ width: 0,
3117
+ height: 0,
3118
+ left: 0,
3119
+ top: 0
3120
+ };
3121
+ const findRootNode = (node) => {
3122
+ if (!node) return;
3123
+ if (!node.parentNode) return node.nodeName === "ink-root" ? node : void 0;
3124
+ return findRootNode(node.parentNode);
3125
+ };
3126
+ /**
3127
+ A React hook that returns the current layout metrics for a tracked box element.
3128
+ It updates when layout changes (for example terminal resize, sibling/content changes, or position changes).
3129
+
3130
+ The hook returns `{width: 0, height: 0, left: 0, top: 0}` until the first layout pass completes. It also returns zeros when the tracked ref is detached.
3131
+
3132
+ Use `hasMeasured` to detect when the currently tracked element has been measured.
3133
+
3134
+ @example
3135
+ ```tsx
3136
+ import {useRef} from 'react';
3137
+ import {Box, Text, useBoxMetrics} from 'ink';
3138
+
3139
+ const Example = () => {
3140
+ const ref = useRef(null);
3141
+ const {width, height, left, top, hasMeasured} = useBoxMetrics(ref);
3142
+ return (
3143
+ <Box ref={ref}>
3144
+ <Text>
3145
+ {hasMeasured ? `${width}x${height} at ${left},${top}` : 'Measuring...'}
3146
+ </Text>
3147
+ </Box>
3148
+ );
3149
+ };
3150
+ ```
3151
+ */
3152
+ const useBoxMetrics = (ref) => {
3153
+ const [metrics, setMetrics] = useState(emptyMetrics);
3154
+ const [hasMeasured, setHasMeasured] = useState(false);
3155
+ const updateMetrics = useCallback(() => {
3156
+ const layout = ref.current?.yogaNode?.getComputedLayout() ?? emptyMetrics;
3157
+ setMetrics((previousMetrics) => {
3158
+ return previousMetrics.width !== layout.width || previousMetrics.height !== layout.height || previousMetrics.left !== layout.left || previousMetrics.top !== layout.top ? layout : previousMetrics;
3159
+ });
3160
+ setHasMeasured(Boolean(ref.current));
3161
+ }, [ref]);
3162
+ useEffect(updateMetrics);
3163
+ useEffect(() => {
3164
+ const rootNode = findRootNode(ref.current);
3165
+ if (!rootNode) return;
3166
+ return addLayoutListener(rootNode, updateMetrics);
3167
+ });
3168
+ return useMemo(() => ({
3169
+ ...metrics,
3170
+ hasMeasured
3171
+ }), [metrics, hasMeasured]);
3172
+ };
3173
+ //#endregion
3174
+ //#region src/measure-element.ts
3175
+ /**
3176
+ Measure the layout metrics of a particular `<Box>` element.
3177
+ Returns an object with `x`, `y`, `width`, and `height` properties.
3178
+
3179
+ `x` and `y` are the element's position within the live layout region, computed by walking up the layout tree and accumulating each ancestor's offset. These are layout-tree coordinates, not terminal viewport coordinates. To compare them with mouse events, convert the event coordinates using the live region's viewport position. This is necessary even in alternate-screen mode when output, such as `<Static>` content, appears above the live region.
3180
+
3181
+ Note: `measureElement()` returns `{x: 0, y: 0, width: 0, height: 0}` when called during render (before layout is calculated). Call it from post-render code, such as `useEffect`, `useLayoutEffect`, input handlers, or timer callbacks. When content changes, pass the relevant dependency to your effect so it re-measures after each update.
3182
+ */
3183
+ const measureElement = (node) => {
3184
+ const { yogaNode } = node;
3185
+ if (!yogaNode) return {
3186
+ x: 0,
3187
+ y: 0,
3188
+ width: 0,
3189
+ height: 0
3190
+ };
3191
+ let x = yogaNode.getComputedLeft();
3192
+ let y = yogaNode.getComputedTop();
3193
+ let current = node.parentNode;
3194
+ while (current) {
3195
+ if (current.yogaNode) {
3196
+ x += current.yogaNode.getComputedLeft();
3197
+ y += current.yogaNode.getComputedTop();
3198
+ }
3199
+ current = current.parentNode;
3200
+ }
3201
+ return {
3202
+ x,
3203
+ y,
3204
+ width: yogaNode.getComputedWidth(),
3205
+ height: yogaNode.getComputedHeight()
3206
+ };
3207
+ };
3208
+ //#endregion
3209
+ export { AnsiText, Box, Hyperlink, Newline, Spacer, Static, Text, Transform, VirtualList, applyTerminalQuery, capabilities, createSupportsColor, detectCapabilities, detectColorLevel, detectHyperlinkSupport, detectTerminal, detectUnicodeSupport, getCapabilities, getTerminalQuery, kittyFlags, kittyModifiers, measureElement, queryTerminal, refreshTerminalQuery, render, renderToString, useAnimation, useApp, useBoxMetrics, useCapabilities, useCapabilitiesChange, useClipboard, useCursor, useFocus, useFocusManager, useInput, useIsScreenReaderEnabled, useNotification, usePaste, usePointerShape, useProgress, useStderr, useStdin, useStdout, useTitle, useVirtualScroll, useWindowSize, useWorkingDirectory };