@promptctl/rich-js 0.18.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +11 -8
  2. package/dist/core/box.d.ts +3 -1
  3. package/dist/core/box.js +6 -13
  4. package/dist/core/cells.d.ts +61 -28
  5. package/dist/core/cells.js +171 -124
  6. package/dist/core/clock.d.ts +48 -0
  7. package/dist/core/clock.js +42 -0
  8. package/dist/core/color.d.ts +99 -18
  9. package/dist/core/color.js +334 -146
  10. package/dist/core/console.d.ts +38 -7
  11. package/dist/core/console.js +159 -90
  12. package/dist/core/easing.d.ts +86 -0
  13. package/dist/core/easing.js +171 -0
  14. package/dist/core/env.d.ts +16 -0
  15. package/dist/core/env.js +16 -0
  16. package/dist/core/export-html.js +1 -4
  17. package/dist/core/export-lines.d.ts +22 -13
  18. package/dist/core/export-lines.js +37 -19
  19. package/dist/core/export-svg.d.ts +49 -0
  20. package/dist/core/export-svg.js +213 -0
  21. package/dist/core/fnv1a.d.ts +10 -0
  22. package/dist/core/fnv1a.js +17 -0
  23. package/dist/core/highlighter.d.ts +2 -2
  24. package/dist/core/highlighter.js +113 -47
  25. package/dist/core/json.js +1 -1
  26. package/dist/core/markup.d.ts +28 -7
  27. package/dist/core/markup.js +235 -312
  28. package/dist/core/measure.d.ts +3 -3
  29. package/dist/core/measure.js +12 -7
  30. package/dist/core/memo.d.ts +19 -0
  31. package/dist/core/memo.js +34 -0
  32. package/dist/core/noise.d.ts +31 -0
  33. package/dist/core/noise.js +94 -0
  34. package/dist/core/osc8.d.ts +6 -6
  35. package/dist/core/osc8.js +9 -21
  36. package/dist/core/paint.d.ts +107 -0
  37. package/dist/core/paint.js +161 -0
  38. package/dist/core/place.d.ts +1 -1
  39. package/dist/core/place.js +4 -6
  40. package/dist/core/pretty.d.ts +126 -2
  41. package/dist/core/pretty.js +571 -105
  42. package/dist/core/protocol.d.ts +43 -3
  43. package/dist/core/protocol.js +23 -2
  44. package/dist/core/render.d.ts +33 -30
  45. package/dist/core/render.js +43 -83
  46. package/dist/core/segment.d.ts +6 -5
  47. package/dist/core/segment.js +11 -15
  48. package/dist/core/selection.d.ts +12 -0
  49. package/dist/core/selection.js +14 -0
  50. package/dist/core/strip.d.ts +116 -74
  51. package/dist/core/strip.js +266 -217
  52. package/dist/core/style.d.ts +27 -7
  53. package/dist/core/style.js +106 -41
  54. package/dist/core/text.d.ts +43 -16
  55. package/dist/core/text.js +180 -59
  56. package/dist/core/wrap.d.ts +5 -0
  57. package/dist/core/wrap.js +16 -3
  58. package/dist/host/app.d.ts +95 -17
  59. package/dist/host/app.js +201 -61
  60. package/dist/host/index.d.ts +4 -1
  61. package/dist/host/index.js +1 -0
  62. package/dist/host/input.d.ts +84 -0
  63. package/dist/host/input.js +498 -0
  64. package/dist/host/terminal-host.d.ts +15 -2
  65. package/dist/host/terminal-host.js +2 -0
  66. package/dist/index.d.ts +26 -14
  67. package/dist/index.js +17 -7
  68. package/dist/node/prompt.d.ts +13 -0
  69. package/dist/node/prompt.js +30 -9
  70. package/dist/node/save.d.ts +4 -3
  71. package/dist/node/save.js +6 -3
  72. package/dist/node/terminal-host.d.ts +10 -2
  73. package/dist/node/terminal-host.js +7 -0
  74. package/dist/node/traceback.d.ts +2 -1
  75. package/dist/node/traceback.js +3 -39
  76. package/dist/renderables/align.js +2 -9
  77. package/dist/renderables/columns.d.ts +45 -29
  78. package/dist/renderables/columns.js +142 -103
  79. package/dist/renderables/constrain.js +6 -12
  80. package/dist/renderables/effect.d.ts +113 -0
  81. package/dist/renderables/effect.js +182 -0
  82. package/dist/renderables/effects.d.ts +252 -0
  83. package/dist/renderables/effects.js +565 -0
  84. package/dist/renderables/embed.d.ts +94 -31
  85. package/dist/renderables/embed.js +141 -45
  86. package/dist/renderables/group.d.ts +6 -3
  87. package/dist/renderables/group.js +5 -0
  88. package/dist/renderables/layout.d.ts +6 -23
  89. package/dist/renderables/layout.js +24 -113
  90. package/dist/renderables/list-view.d.ts +42 -0
  91. package/dist/renderables/list-view.js +78 -0
  92. package/dist/renderables/live.d.ts +40 -16
  93. package/dist/renderables/live.js +96 -83
  94. package/dist/renderables/markdown.d.ts +16 -2
  95. package/dist/renderables/markdown.js +521 -118
  96. package/dist/renderables/padding.d.ts +22 -1
  97. package/dist/renderables/padding.js +30 -24
  98. package/dist/renderables/panel.d.ts +31 -33
  99. package/dist/renderables/panel.js +137 -141
  100. package/dist/renderables/progress.d.ts +110 -16
  101. package/dist/renderables/progress.js +212 -81
  102. package/dist/renderables/progressBar.d.ts +24 -4
  103. package/dist/renderables/progressBar.js +78 -23
  104. package/dist/renderables/prompt.d.ts +37 -15
  105. package/dist/renderables/prompt.js +125 -85
  106. package/dist/renderables/ratio.d.ts +123 -0
  107. package/dist/renderables/ratio.js +213 -0
  108. package/dist/renderables/rule.d.ts +3 -2
  109. package/dist/renderables/rule.js +50 -52
  110. package/dist/renderables/spinner.d.ts +28 -2
  111. package/dist/renderables/spinner.js +52 -19
  112. package/dist/renderables/status.d.ts +13 -7
  113. package/dist/renderables/status.js +15 -29
  114. package/dist/renderables/syntax.d.ts +37 -7
  115. package/dist/renderables/syntax.js +134 -83
  116. package/dist/renderables/table.d.ts +58 -20
  117. package/dist/renderables/table.js +373 -197
  118. package/dist/renderables/traceback.d.ts +5 -4
  119. package/dist/renderables/traceback.js +239 -68
  120. package/dist/renderables/tree.d.ts +10 -2
  121. package/dist/renderables/tree.js +61 -34
  122. package/dist/renderables/viewport.d.ts +30 -0
  123. package/dist/renderables/viewport.js +61 -7
  124. package/dist/template-bindings/color-funcs.d.ts +31 -9
  125. package/dist/template-bindings/color-funcs.js +40 -14
  126. package/dist/template-bindings/helpers.d.ts +4 -2
  127. package/dist/template-bindings/helpers.js +5 -20
  128. package/dist/template-bindings/index.d.ts +29 -17
  129. package/dist/template-bindings/index.js +36 -20
  130. package/dist/template-bindings/palette-funcs.d.ts +3 -1
  131. package/dist/template-bindings/palette-funcs.js +11 -7
  132. package/dist/template-bindings/style-funcs.js +4 -2
  133. package/dist/themes/buildPalette.d.ts +24 -3
  134. package/dist/themes/buildPalette.js +52 -8
  135. package/dist/themes/colorMath.d.ts +29 -17
  136. package/dist/themes/colorMath.js +74 -48
  137. package/dist/themes/colorRef.d.ts +2 -17
  138. package/dist/themes/colorRef.js +13 -31
  139. package/dist/themes/palette.d.ts +24 -0
  140. package/dist/themes/palette.js +46 -3
  141. package/dist/themes/ramp.d.ts +21 -34
  142. package/dist/themes/ramp.js +23 -42
  143. package/dist/themes/registry.js +39 -28
  144. package/dist/widgets/button.d.ts +2 -6
  145. package/dist/widgets/button.js +9 -40
  146. package/dist/widgets/checkbox.d.ts +2 -5
  147. package/dist/widgets/checkbox.js +9 -31
  148. package/dist/widgets/dropdown.d.ts +2 -5
  149. package/dist/widgets/dropdown.js +17 -51
  150. package/dist/widgets/event-router.d.ts +31 -40
  151. package/dist/widgets/event-router.js +52 -483
  152. package/dist/widgets/index.d.ts +2 -1
  153. package/dist/widgets/ink.d.ts +22 -0
  154. package/dist/widgets/ink.js +31 -0
  155. package/dist/widgets/slider.d.ts +2 -5
  156. package/dist/widgets/slider.js +11 -34
  157. package/dist/widgets/text-input.d.ts +19 -10
  158. package/dist/widgets/text-input.js +156 -147
  159. package/dist/widgets/themed-widget.d.ts +20 -0
  160. package/dist/widgets/themed-widget.js +89 -0
  161. package/dist/widgets/toggle.d.ts +2 -5
  162. package/dist/widgets/toggle.js +8 -31
  163. package/dist/widgets/types.d.ts +8 -25
  164. package/dist/widgets/types.js +8 -0
  165. package/dist/widgets/widget-app.d.ts +24 -7
  166. package/dist/widgets/widget-app.js +35 -18
  167. package/dist/widgets/widget-base.d.ts +2 -0
  168. package/dist/widgets/widget-base.js +9 -2
  169. package/package.json +31 -17
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Terminal input: the bytes a terminal sends for keys and the pointer,
3
+ * parsed into `KeyPress` and `ScreenMouseEvent` values, and the one name
4
+ * each key goes by.
5
+ *
6
+ * [LAW:single-enforcer] Escape-sequence parsing lives only here. `App` owns
7
+ * the one parser over its host's input; a `WidgetApp` routes the events `App`
8
+ * hears, and no program reads its terminal's bytes itself.
9
+ *
10
+ * [LAW:dataflow-not-control-flow] The same parse pipeline runs for every
11
+ * input chunk: append → consume one event at a time until the buffer can't
12
+ * yield more. The byte's value, not a side-mode, decides which event is
13
+ * emitted. There is no "are we mid-sequence" mode flag — the buffer head
14
+ * carries all the state we need, so a UTF-8 character or an escape sequence
15
+ * split across two chunks is completed by the second.
16
+ *
17
+ * Lone-ESC handling: a bare `\x1b` is ambiguous (escape key vs. start of a
18
+ * CSI sequence). When the buffer is drained but ends with a lone ESC, the
19
+ * parser defers emission via setTimeout(0). The next chunk cancels the timer
20
+ * if it extends the sequence. `flush()` drains it synchronously.
21
+ */
22
+ /**
23
+ * The one name a key goes by — what a keymap is keyed by and what
24
+ * `quitKeys` lists. A key that types a character is named by it (`q`, `G`,
25
+ * `>`); any other is named by its key (`up`, `enter`, `space`, `f5`). Held
26
+ * modifiers come first, always in the order `ctrl+`, `meta+`, `shift+`:
27
+ * `ctrl+c`, `shift+tab`, `meta+left`. Shift is not named on a character,
28
+ * which already carries it: Shift+G is `G`.
29
+ */
30
+ export function keyName(press) {
31
+ const typed = press.character !== "" && press.key !== "space";
32
+ const modifiers = [
33
+ press.ctrl ? "ctrl+" : "",
34
+ press.meta ? "meta+" : "",
35
+ press.shift && !typed ? "shift+" : "",
36
+ ];
37
+ return modifiers.join("") + (typed ? press.character : press.key);
38
+ }
39
+ // Single-byte → key name table for the trivial cases.
40
+ const SINGLE_BYTE_KEYS = {
41
+ 0x09: "tab",
42
+ 0x0a: "enter",
43
+ 0x0d: "enter",
44
+ 0x1b: "escape",
45
+ 0x08: "backspace",
46
+ 0x7f: "backspace",
47
+ 0x20: "space",
48
+ };
49
+ // Final-byte → key name for the simple `ESC[<X>` arrow / nav forms.
50
+ const CSI_LETTER_KEYS = {
51
+ A: "up",
52
+ B: "down",
53
+ C: "right",
54
+ D: "left",
55
+ H: "home",
56
+ F: "end",
57
+ Z: "tab", // shift+tab encodes as ESC[Z
58
+ };
59
+ // `ESC[<n>~` numeric forms for navigation / function keys.
60
+ const CSI_TILDE_KEYS = {
61
+ "1": "home",
62
+ "2": "insert",
63
+ "3": "delete",
64
+ "4": "end",
65
+ "5": "pageup",
66
+ "6": "pagedown",
67
+ "7": "home",
68
+ "8": "end",
69
+ "11": "f1",
70
+ "12": "f2",
71
+ "13": "f3",
72
+ "14": "f4",
73
+ "15": "f5",
74
+ "17": "f6",
75
+ "18": "f7",
76
+ "19": "f8",
77
+ "20": "f9",
78
+ "21": "f10",
79
+ "23": "f11",
80
+ "24": "f12",
81
+ };
82
+ // SS3 form `ESC O X` for F1–F4 (and arrow keys on some terminals).
83
+ const SS3_KEYS = {
84
+ P: "f1",
85
+ Q: "f2",
86
+ R: "f3",
87
+ S: "f4",
88
+ A: "up",
89
+ B: "down",
90
+ C: "right",
91
+ D: "left",
92
+ H: "home",
93
+ F: "end",
94
+ };
95
+ // Every name a key with no glyph goes by.
96
+ const NAMED_KEYS = new Set([
97
+ ...Object.values(SINGLE_BYTE_KEYS),
98
+ ...Object.values(CSI_LETTER_KEYS),
99
+ ...Object.values(CSI_TILDE_KEYS),
100
+ ...Object.values(SS3_KEYS),
101
+ ]);
102
+ // [LAW:one-source-of-truth] The glyphs a Ctrl chord is named by are what
103
+ // `ctrlKey` makes of the control bytes no named key claims.
104
+ const CTRL_GLYPHS = new Set(Array.from({ length: 0x20 }, (_, byte) => byte)
105
+ .filter((byte) => byte !== 0 && !(byte in SINGLE_BYTE_KEYS))
106
+ .map((byte) => ctrlKey(byte).key));
107
+ /**
108
+ * `name`, if `keyName` can produce it: modifiers in its order, then a named
109
+ * key or one glyph, with no `shift+` on a glyph and, after `ctrl+`, only a
110
+ * glyph a control byte stands for.
111
+ * [LAW:parse-dont-validate] A name no key can produce throws here, at the
112
+ * one place a program hands names in, rather than binding a key that never fires.
113
+ */
114
+ export function parseKeyName(name) {
115
+ const [, ctrl, , shift, base] = /^(ctrl\+)?(meta\+)?(shift\+)?(.*)$/u.exec(name);
116
+ const glyph = [...base].length === 1 && !shift && (!ctrl || CTRL_GLYPHS.has(base));
117
+ const producible = NAMED_KEYS.has(base) || glyph;
118
+ if (!producible)
119
+ throw new Error(`no key is named ${JSON.stringify(name)}; see keyName`);
120
+ return name;
121
+ }
122
+ const ESC = 0x1b;
123
+ const LBRACKET = 0x5b; // [
124
+ const LANGLE = 0x3c; // <
125
+ const O_BYTE = 0x4f; // O
126
+ const M_UPPER = 0x4d; // M
127
+ const M_LOWER = 0x6d; // m
128
+ // [LAW:single-enforcer] Shared encode/decode singletons. The parser
129
+ // receives `Uint8Array | string` from the host and walks bytes through
130
+ // its state machine; conversion happens at the two boundaries (string
131
+ // input → bytes via TextEncoder; printable-byte run → JS string via
132
+ // TextDecoder) and only there. Module-level instances keep the seam
133
+ // allocation-free and runtime-agnostic — both APIs are universal across
134
+ // node and browser.
135
+ const UTF8_ENCODER = new TextEncoder();
136
+ const UTF8_DECODER = new TextDecoder("utf-8");
137
+ const ASCII_DECODER = new TextDecoder("ascii");
138
+ const EMPTY_BYTES = new Uint8Array(0);
139
+ // Concatenate two byte arrays into a fresh Uint8Array. Replaces
140
+ // `Buffer.concat([...])` so the parser holds no node-specific globals.
141
+ function concatBytes(a, b) {
142
+ if (a.length === 0)
143
+ return b;
144
+ if (b.length === 0)
145
+ return a;
146
+ const merged = new Uint8Array(a.length + b.length);
147
+ merged.set(a, 0);
148
+ merged.set(b, a.length);
149
+ return merged;
150
+ }
151
+ export class InputParser {
152
+ sink;
153
+ // [LAW:types-are-the-program] The parse buffer is `Uint8Array`, not
154
+ // `Buffer` — node's Buffer extends Uint8Array, so Buffer chunks from
155
+ // process.stdin satisfy this type, while a browser host can emit native
156
+ // Uint8Array without depending on a Buffer global polyfill.
157
+ buffer = EMPTY_BYTES;
158
+ // [LAW:types-are-the-program] `setTimeout`/`clearTimeout` are universal
159
+ // across node and browser; `setImmediate` is node-only.
160
+ escTimer;
161
+ constructor(sink) {
162
+ this.sink = sink;
163
+ }
164
+ /** Feed a chunk of bytes (or a string of them) into the parser. */
165
+ feed(chunk) {
166
+ const next = typeof chunk === "string" ? UTF8_ENCODER.encode(chunk) : chunk;
167
+ this.buffer = concatBytes(this.buffer, next);
168
+ this.cancelEscape();
169
+ this.drain();
170
+ // [LAW:dataflow-not-control-flow] If a lone ESC remains, defer emission
171
+ // until the next macrotask so a follow-up chunk can extend it.
172
+ if (this.buffer.length === 1 && this.buffer[0] === ESC) {
173
+ this.escTimer = setTimeout(() => {
174
+ this.escTimer = undefined;
175
+ this.flush();
176
+ }, 0);
177
+ }
178
+ }
179
+ /** Force any pending lone ESC out of the buffer. */
180
+ flush() {
181
+ this.cancelEscape();
182
+ if (this.buffer.length === 1 && this.buffer[0] === ESC) {
183
+ this.buffer = EMPTY_BYTES;
184
+ this.sink.key(({ key: "escape", character: "", shift: false, ctrl: false, meta: false }));
185
+ }
186
+ }
187
+ /** Drop what is buffered and any pending lone ESC: the parser as constructed. */
188
+ reset() {
189
+ this.cancelEscape();
190
+ this.buffer = EMPTY_BYTES;
191
+ }
192
+ cancelEscape() {
193
+ clearTimeout(this.escTimer);
194
+ this.escTimer = undefined;
195
+ }
196
+ drain() {
197
+ while (this.buffer.length > 0) {
198
+ const result = this.consumeOne(this.buffer);
199
+ if (result.kind === "incomplete")
200
+ return;
201
+ this.buffer = this.buffer.subarray(result.bytes);
202
+ if (result.kind === "key")
203
+ this.sink.key(result.event);
204
+ else if (result.kind === "mouse")
205
+ this.sink.mouse(result.event);
206
+ // skip → drop bytes silently
207
+ }
208
+ }
209
+ consumeOne(buf) {
210
+ const b0 = buf[0];
211
+ if (b0 === ESC)
212
+ return this.consumeEscape(buf);
213
+ // Ctrl+C
214
+ if (b0 === 0x03) {
215
+ return {
216
+ kind: "key",
217
+ bytes: 1,
218
+ event: ({ key: "c", character: "", shift: false, ctrl: true, meta: false }),
219
+ };
220
+ }
221
+ // Named single-byte keys (tab/enter/space/backspace).
222
+ const named = SINGLE_BYTE_KEYS[b0];
223
+ if (named && named !== "escape") {
224
+ return {
225
+ kind: "key",
226
+ bytes: 1,
227
+ event: ({
228
+ key: named,
229
+ character: named === "space" ? " " : "",
230
+ shift: false,
231
+ ctrl: false,
232
+ meta: false,
233
+ }),
234
+ };
235
+ }
236
+ if (b0 < 0x20)
237
+ return { kind: "key", bytes: 1, event: ctrlKey(b0) };
238
+ // Printable ASCII / start of a UTF-8 multibyte character.
239
+ return this.consumePrintable(buf);
240
+ }
241
+ consumePrintable(buf) {
242
+ const b0 = buf[0];
243
+ let len = 1;
244
+ if (b0 >= 0xc0 && b0 < 0xe0)
245
+ len = 2;
246
+ else if (b0 >= 0xe0 && b0 < 0xf0)
247
+ len = 3;
248
+ else if (b0 >= 0xf0)
249
+ len = 4;
250
+ if (buf.length < len)
251
+ return { kind: "incomplete" };
252
+ const character = UTF8_DECODER.decode(buf.subarray(0, len));
253
+ return {
254
+ kind: "key",
255
+ bytes: len,
256
+ event: ({
257
+ key: character.toLowerCase(),
258
+ character,
259
+ shift: character.length === 1 && character !== character.toLowerCase(),
260
+ ctrl: false,
261
+ meta: false,
262
+ }),
263
+ };
264
+ }
265
+ consumeEscape(buf) {
266
+ if (buf.length === 1)
267
+ return { kind: "incomplete" };
268
+ const b1 = buf[1];
269
+ // ESC [ ... → CSI
270
+ if (b1 === LBRACKET)
271
+ return this.consumeCSI(buf);
272
+ // ESC O X → SS3 (F1–F4 / arrows on some terms)
273
+ if (b1 === O_BYTE)
274
+ return this.consumeSS3(buf);
275
+ // ESC ESC [ … / ESC ESC O … → Alt on a key the terminal sends as a
276
+ // sequence (macOS Terminal and iTerm send Option+Up as `ESC ESC [ A`): the
277
+ // inner sequence, with meta held.
278
+ if (b1 === ESC && buf.length > 2 && (buf[2] === LBRACKET || buf[2] === O_BYTE)) {
279
+ // A pointer report is never Alt-prefixed (SGR carries modifiers in its
280
+ // button byte), so before anything but a key the first ESC is Escape.
281
+ const inner = this.consumeEscape(buf.subarray(1));
282
+ if (inner.kind === "incomplete")
283
+ return inner;
284
+ if (inner.kind === "key") {
285
+ return { kind: "key", bytes: inner.bytes + 1, event: { ...inner.event, meta: true } };
286
+ }
287
+ return { kind: "key", bytes: 1, event: ESCAPE };
288
+ }
289
+ // ESC ESC → escape key (some terminals double-send). Consume BOTH bytes
290
+ // so the user sees one escape event, not two. Without this, the second
291
+ // ESC remained in the buffer and produced a second escape via the
292
+ // lone-ESC flush path.
293
+ if (b1 === ESC) {
294
+ return {
295
+ kind: "key",
296
+ bytes: 2,
297
+ event: ({ key: "escape", character: "", shift: false, ctrl: false, meta: false }),
298
+ };
299
+ }
300
+ // ESC <named> / ESC <printable> → Alt-modified key.
301
+ // Terminals encode Alt+<key> as the literal byte preceded by ESC, so
302
+ // `ESC b` is Alt+B, `ESC <0x7f>` is Alt+Backspace, `ESC <0x20>` is
303
+ // Alt+Space, etc. The parser surfaces this as `{ key, meta: true }` —
304
+ // matching the shape used for Ctrl-modified keys, so handlers can
305
+ // branch on `event.meta` without parsing escape sequences themselves.
306
+ //
307
+ // [LAW:one-source-of-truth] Named single-byte keys (space, tab,
308
+ // enter, backspace) use the SAME canonical key names on the Alt path
309
+ // as on the non-Alt path. Without this, `Alt+Space` decoded as
310
+ // `key: " "` while a bare space decoded as `key: "space"`, so a
311
+ // handler couldn't match Alt+Space via `event.key === "space" &&
312
+ // event.meta`. Routing through SINGLE_BYTE_KEYS first preserves the
313
+ // canonical naming.
314
+ //
315
+ // [LAW:one-source-of-truth] Modifier decoding for *all* navigation keys
316
+ // lives in this file: CSI param-style modifiers (e.g. `ESC[1;3D` for
317
+ // Alt+Left) flow through `decodeModifier`, and ESC-prefixed Alt forms
318
+ // flow through this branch. Widgets read `event.meta` and never re-parse.
319
+ const namedAlt = SINGLE_BYTE_KEYS[b1];
320
+ if (namedAlt && namedAlt !== "escape") {
321
+ return {
322
+ kind: "key",
323
+ bytes: 2,
324
+ event: ({
325
+ key: namedAlt,
326
+ character: "",
327
+ shift: false,
328
+ ctrl: false,
329
+ meta: true,
330
+ }),
331
+ };
332
+ }
333
+ // The key is the glyph the terminal sent, as for Alt+`>`: the glyph
334
+ // already carries shift, so Alt+G is `meta+G` and never `meta+shift+g`.
335
+ if (b1 >= 0x20 && b1 <= 0x7e) {
336
+ const ch = String.fromCharCode(b1);
337
+ return {
338
+ kind: "key",
339
+ bytes: 2,
340
+ event: ({ key: ch, character: "", shift: false, ctrl: false, meta: true }),
341
+ };
342
+ }
343
+ // ESC <control byte> is read as Escape, then the Ctrl chord — never as
344
+ // Alt+Ctrl. The bytes cannot tell the two apart, and Escape then Ctrl+C
345
+ // typed during a stalled frame must still reach the quit keys as Ctrl+C.
346
+ // Any other byte after ESC (a UTF-8 lead) is likewise re-read on its own.
347
+ return {
348
+ kind: "key",
349
+ bytes: 1,
350
+ event: ({ key: "escape", character: "", shift: false, ctrl: false, meta: false }),
351
+ };
352
+ }
353
+ consumeSS3(buf) {
354
+ if (buf.length < 3)
355
+ return { kind: "incomplete" };
356
+ const final = String.fromCharCode(buf[2]);
357
+ const name = SS3_KEYS[final];
358
+ if (!name)
359
+ return { kind: "skip", bytes: 3 };
360
+ return {
361
+ kind: "key",
362
+ bytes: 3,
363
+ event: ({ key: name, character: "", shift: false, ctrl: false, meta: false }),
364
+ };
365
+ }
366
+ consumeCSI(buf) {
367
+ // buf starts with ESC [
368
+ const next = buf[2];
369
+ if (next === undefined)
370
+ return { kind: "incomplete" };
371
+ // X10 mouse: ESC [ M b x y (3 raw bytes after M)
372
+ if (next === M_UPPER) {
373
+ if (buf.length < 6)
374
+ return { kind: "incomplete" };
375
+ const cb = buf[3] - 32;
376
+ const col = buf[4] - 32 - 1;
377
+ const row = buf[5] - 32 - 1;
378
+ return { kind: "mouse", bytes: 6, event: decodeMouseFromCb(cb, col, row, true) };
379
+ }
380
+ // SGR mouse: ESC [ < cb ; x ; y M|m
381
+ if (next === LANGLE) {
382
+ // Find terminator M or m.
383
+ for (let i = 3; i < buf.length; i++) {
384
+ const b = buf[i];
385
+ if (b === M_UPPER || b === M_LOWER) {
386
+ const params = ASCII_DECODER.decode(buf.subarray(3, i));
387
+ const parts = params.split(";");
388
+ if (parts.length !== 3)
389
+ return { kind: "skip", bytes: i + 1 };
390
+ const cb = parseInt(parts[0], 10);
391
+ const col = parseInt(parts[1], 10) - 1;
392
+ const row = parseInt(parts[2], 10) - 1;
393
+ if (Number.isNaN(cb) || Number.isNaN(col) || Number.isNaN(row)) {
394
+ return { kind: "skip", bytes: i + 1 };
395
+ }
396
+ return {
397
+ kind: "mouse",
398
+ bytes: i + 1,
399
+ event: decodeMouseFromCb(cb, col, row, b === M_UPPER),
400
+ };
401
+ }
402
+ }
403
+ return { kind: "incomplete" };
404
+ }
405
+ // Generic CSI: collect parameter / intermediate bytes until a final byte
406
+ // in the range 0x40–0x7e is found.
407
+ let params = "";
408
+ for (let i = 2; i < buf.length; i++) {
409
+ const b = buf[i];
410
+ if (b >= 0x40 && b <= 0x7e) {
411
+ const final = String.fromCharCode(b);
412
+ return decodeCSI(params, final, i + 1);
413
+ }
414
+ params += String.fromCharCode(b);
415
+ }
416
+ return { kind: "incomplete" };
417
+ }
418
+ }
419
+ // --- Helpers ---
420
+ // The wheel's button bits: vertical, then the horizontal wheel or tilt.
421
+ const SCROLL = ["scroll_up", "scroll_down", "scroll_left", "scroll_right"];
422
+ // A C0 control byte as the Ctrl chord that types it, with Alt held or not.
423
+ // 0x00 is Ctrl+Space; 0x01–0x1a are Ctrl+A–Z; 0x1c–0x1f are Ctrl+\ ] ^ _.
424
+ // The bytes SINGLE_BYTE_KEYS names (tab, enter, backspace, escape) never reach here.
425
+ function ctrlKey(byte) {
426
+ const key = byte === 0 ? "space" : String.fromCharCode(byte + (byte <= 0x1a ? 0x60 : 0x40));
427
+ return { key, character: "", shift: false, ctrl: true, meta: false };
428
+ }
429
+ const ESCAPE = { key: "escape", character: "", shift: false, ctrl: false, meta: false };
430
+ function decodeMouseFromCb(cb, x, y, isPress) {
431
+ const isMotion = (cb & 32) !== 0;
432
+ const isScroll = (cb & 64) !== 0;
433
+ const button = cb & 3;
434
+ const shift = !!(cb & 4);
435
+ const ctrl = !!(cb & 16); // bit 16 = ctrl in xterm encoding
436
+ if (isScroll)
437
+ return { type: SCROLL[button], x, y, button: 0, shift, ctrl };
438
+ if (isMotion) {
439
+ return { type: "mouse_move", x, y, button, shift, ctrl };
440
+ }
441
+ return {
442
+ type: isPress ? "mouse_down" : "mouse_up",
443
+ x,
444
+ y,
445
+ button,
446
+ shift,
447
+ ctrl,
448
+ };
449
+ }
450
+ function decodeCSI(params, final, bytes) {
451
+ // Handle modifier suffixes like `1;2A` (shift+up). We split params on `;`.
452
+ const parts = params.split(";");
453
+ const first = parts[0] ?? "";
454
+ const mod = parseInt(parts[1] ?? "", 10);
455
+ const { shift, ctrl, meta } = decodeModifier(mod);
456
+ if (final === "~") {
457
+ const name = CSI_TILDE_KEYS[first];
458
+ if (!name)
459
+ return { kind: "skip", bytes };
460
+ return {
461
+ kind: "key",
462
+ bytes,
463
+ event: ({ key: name, character: "", shift, ctrl, meta }),
464
+ };
465
+ }
466
+ const name = CSI_LETTER_KEYS[final];
467
+ if (!name)
468
+ return { kind: "skip", bytes };
469
+ // Special case: ESC[Z is shift+tab regardless of explicit modifier.
470
+ // [LAW:one-source-of-truth] `character` is the printable character the
471
+ // key would produce, or "" for non-printable / modifier-only events.
472
+ // The non-shifted tab path emits character: "" (Tab is named, not
473
+ // printed) — keep shift+tab consistent. Distinguish via key === "tab"
474
+ // + shift, not character.
475
+ if (final === "Z") {
476
+ return {
477
+ kind: "key",
478
+ bytes,
479
+ event: ({ key: "tab", character: "", shift: true, ctrl: false, meta: false }),
480
+ };
481
+ }
482
+ return {
483
+ kind: "key",
484
+ bytes,
485
+ event: ({ key: name, character: "", shift, ctrl, meta }),
486
+ };
487
+ }
488
+ function decodeModifier(mod) {
489
+ if (!Number.isFinite(mod) || mod <= 1) {
490
+ return { shift: false, ctrl: false, meta: false };
491
+ }
492
+ const bits = mod - 1;
493
+ return {
494
+ shift: (bits & 1) !== 0,
495
+ meta: (bits & 2) !== 0,
496
+ ctrl: (bits & 4) !== 0,
497
+ };
498
+ }
@@ -37,6 +37,7 @@
37
37
  * not an omniscient handle to "the process."
38
38
  */
39
39
  import type { Unsubscribe } from "../core/subscription.js";
40
+ import type { Env } from "../core/env.js";
40
41
  export interface TerminalSize {
41
42
  readonly cols: number;
42
43
  readonly rows: number;
@@ -90,7 +91,13 @@ export interface TerminalHost {
90
91
  * rather than being read off whatever `process` the renderer happens to
91
92
  * share a runtime with, which in a browser is none.
92
93
  */
93
- readonly env: NodeJS.ProcessEnv;
94
+ readonly env: Env;
95
+ /**
96
+ * Scroll whatever holds this terminal by `lines` (positive towards the end
97
+ * of its content): a wheel turn that moved nothing in the program, passed
98
+ * outward. A terminal that is the outermost thing does nothing with it.
99
+ */
100
+ passWheel(lines: number): void;
94
101
  /**
95
102
  * Run `handler` once, when the program ends by any path this host can
96
103
  * observe, so whoever changed the terminal can hand it back. The program
@@ -156,6 +163,11 @@ export interface BrowserTerminalHostOptions {
156
163
  * `NodeTerminalHost`'s relationship to `process.stdin`/`process.stdout`.
157
164
  */
158
165
  terminal: XtermTerminal;
166
+ /**
167
+ * Scroll the page by `lines` of this terminal's rows: what the host does
168
+ * with a wheel turn the program passed outward (`TerminalHost.passWheel`).
169
+ */
170
+ passWheel: (lines: number) => void;
159
171
  }
160
172
  export declare class BrowserTerminalHost implements TerminalHost {
161
173
  private readonly terminal;
@@ -163,10 +175,11 @@ export declare class BrowserTerminalHost implements TerminalHost {
163
175
  private readonly resizeHandlers;
164
176
  private dataSubscription;
165
177
  private resizeSubscription;
178
+ readonly passWheel: (lines: number) => void;
166
179
  constructor(options: BrowserTerminalHostOptions);
167
180
  get isTTY(): boolean;
168
181
  get writesToTerminal(): boolean;
169
- get env(): NodeJS.ProcessEnv;
182
+ get env(): Env;
170
183
  write(data: Uint8Array | string): void;
171
184
  size(): TerminalSize;
172
185
  setRawMode(_raw: boolean): void;
@@ -46,8 +46,10 @@ export class BrowserTerminalHost {
46
46
  resizeHandlers = new Set();
47
47
  dataSubscription;
48
48
  resizeSubscription;
49
+ passWheel;
49
50
  constructor(options) {
50
51
  this.terminal = options.terminal;
52
+ this.passWheel = options.passWheel;
51
53
  }
52
54
  // [LAW:dataflow-not-control-flow] xterm.js is always an interactive
53
55
  // terminal — there is no "non-TTY" code path in the browser. The
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- export { cellLen, setCellSize, splitText, chopCells, cellFit, asCellCol, asCodePoint, } from "./core/cells.js";
1
+ export { cellLen, setCellSize, splitText, chopCells, cellFit, graphemes, asCellCol, asCodePoint, } from "./core/cells.js";
2
2
  export type { CellCol, CodeUnit, CodePoint } from "./core/cells.js";
3
- export { ColorRgba, ColorTable, ColorDepth, ColorSpec, ColorParseError, TerminalTheme, parseRgbHex, parseRgbaHex, blendRgb, resolveColorSystem, detectColorSystem, resolveDestination, STANDARD_TABLE, EIGHT_BIT_TABLE, WINDOWS_TABLE, ANSI_COLOR_NAMES, } from "./core/color.js";
4
- export type { DetectColorOptions, Destination } from "./core/color.js";
3
+ export { ColorRgba, ColorTable, ColorDepth, ColorSpec, ColorParseError, TerminalTheme, parseRgbHex, parseRgbaHex, parseHexColor, blendRgb, resolveColorSystem, detectColorSystem, resolveDestination, STANDARD_TABLE, EIGHT_BIT_TABLE, WINDOWS_TABLE, ANSI_COLOR_NAMES, } from "./core/color.js";
4
+ export type { ColorSystemName, DetectColorOptions, Destination } from "./core/color.js";
5
5
  export { Oklch, IDENTITY, INVERT_LIGHTNESS, isIdentityKey, } from "./core/oklch.js";
6
6
  export type { ThemeKey, OklchWeights } from "./core/oklch.js";
7
7
  export { Palette } from "./themes/palette.js";
8
- export { resolveColorRef, parseHexColor, ColorRefError, HEX_COLOR_RE, } from "./themes/colorRef.js";
8
+ export { resolveColorRef, ColorRefError } from "./themes/colorRef.js";
9
9
  export { buildPalette } from "./themes/buildPalette.js";
10
10
  export type { BaseColors } from "./themes/buildPalette.js";
11
11
  export { DEFAULT_TERMINAL_THEME, MONOKAI, SVG_EXPORT_THEME, NORD, GRUVBOX, DRACULA, TOKYO_NIGHT, FLEXOKI, CYBERPUNK, CATPPUCCIN_MOCHA, CATPPUCCIN_LATTE, CATPPUCCIN_FRAPPE, CATPPUCCIN_MACCHIATO, SOLARIZED_DARK, SOLARIZED_LIGHT, ROSE_PINE, ROSE_PINE_MOON, ROSE_PINE_DAWN, ATOM_ONE_DARK, ATOM_ONE_LIGHT, TEXTUAL_DARK, TEXTUAL_LIGHT, TEXTUAL_ANSI, } from "./themes/terminalThemes.js";
@@ -13,10 +13,14 @@ export { getThemePalette, listThemePalettes } from "./themes/registry.js";
13
13
  export type { ThemeName } from "./themes/registry.js";
14
14
  export { transposePalette, themeKeyForRoot, isAnchored, ANCHORED_ROOTS, } from "./themes/transpose.js";
15
15
  export { lighten, darken, relativeLuminance, contrastRatio, contrastFor, ensureContrast, ensureDrawn, drawnColour, } from "./themes/colorMath.js";
16
- export { ColorRamp, RAMP_EASING_NAMES, parseRampEasing } from "./themes/ramp.js";
17
- export type { ColorStop, RampEasing } from "./themes/ramp.js";
16
+ export { ColorRamp } from "./themes/ramp.js";
17
+ export type { ColorStop } from "./themes/ramp.js";
18
+ export { EASES, Phase, cubicBezier, parseEase, steps } from "./core/easing.js";
19
+ export type { Ease, StepPosition } from "./core/easing.js";
20
+ export { frameRate, systemClock } from "./core/clock.js";
21
+ export type { Clock, FrameRate } from "./core/clock.js";
18
22
  export { Style, StyleSyntaxError, StyleStack, Theme, NULL_STYLE, DEFAULT_STYLES, } from "./core/style.js";
19
- export type { StyleOptions } from "./core/style.js";
23
+ export type { StyleOptions, TextStyle, LayeredStyle } from "./core/style.js";
20
24
  export { Segment, ControlType, } from "./core/segment.js";
21
25
  export type { ControlCode } from "./core/segment.js";
22
26
  export type { Anchor } from "./core/anchor.js";
@@ -29,8 +33,8 @@ export type { Unsubscribe } from "./core/subscription.js";
29
33
  export { Measurement, measureRenderables } from "./core/measure.js";
30
34
  export { Span, RichText } from "./core/text.js";
31
35
  export type { RichTextOptions } from "./core/text.js";
32
- export { Strip, PowerlineJoiner, SEAM_MIN_DELTA_E, POWERLINE_JOINER_GLYPHS, CapsuleJoiner, PlainJoiner, GradientJoiner, } from "./core/strip.js";
33
- export type { StyledRenderable, Joiner, PowerlineJoinerOptions, CapsuleJoinerOptions, PlainJoinerOptions, GradientJoinerOptions, } from "./core/strip.js";
36
+ export { Strip, CellJoiner, SEAM_MIN_DELTA_E, POWERLINE_JOINER, CAPSULE_JOINER, PLAIN_JOINER, gradientJoiner, } from "./core/strip.js";
37
+ export type { StyledRenderable, Joiner, JoinShape, JoinCell, CellColor, Paint } from "./core/strip.js";
34
38
  export { renderToString, segmentToString, segmentsToString, } from "./core/render.js";
35
39
  export type { RenderToStringOptions } from "./core/render.js";
36
40
  export { OSC8, osc8Sequences } from "./core/osc8.js";
@@ -48,13 +52,21 @@ export type { JSONOptions } from "./core/json.js";
48
52
  export { SPINNERS, DEFAULT_SPINNER } from "./core/spinnerData.js";
49
53
  export type { SpinnerData } from "./core/spinnerData.js";
50
54
  export { Console } from "./core/console.js";
51
- export type { ConsoleOptions, ConsoleSink, ConsoleStream, ConsoleEnvironment, PrintOptions, } from "./core/console.js";
55
+ export type { ConsoleOptions, ConsoleSink, ConsoleStream, ConsoleEnvironment, LiveRegion, PrintOptions, } from "./core/console.js";
56
+ export type { Env } from "./core/env.js";
52
57
  export { Constrain } from "./renderables/constrain.js";
58
+ export { Effected } from "./renderables/effect.js";
59
+ export type { CellColors, Effect, EffectCell, EffectedOptions } from "./renderables/effect.js";
60
+ export { EFFECT_CURVES, EFFECT_LIGHTS, SHIMMER_WIDTH, dissolveOut, fadeIn, onColors, pulse, shares, shimmer, sparkle, wheel, } from "./renderables/effects.js";
61
+ export type { Curve, Field, Loop, Pair, Touch, Transition } from "./renderables/effects.js";
53
62
  export { Align } from "./renderables/align.js";
54
63
  export type { Alignment } from "./core/place.js";
55
64
  export { Padding } from "./renderables/padding.js";
56
65
  export type { PaddingDimensions } from "./renderables/padding.js";
57
- export { Viewport, SCROLLBAR } from "./renderables/viewport.js";
66
+ export { Viewport, ViewportPerKey, SCROLLBAR } from "./renderables/viewport.js";
67
+ export { ListView } from "./renderables/list-view.js";
68
+ export { moveSelection } from "./core/selection.js";
69
+ export type { ListViewOptions } from "./renderables/list-view.js";
58
70
  export type { Scrollbar, ScrollbarPart, ViewportOptions } from "./renderables/viewport.js";
59
71
  export { Rule } from "./renderables/rule.js";
60
72
  export type { RuleAlign, RuleOptions } from "./renderables/rule.js";
@@ -78,13 +90,13 @@ export type { LiveOptions } from "./renderables/live.js";
78
90
  export { Status } from "./renderables/status.js";
79
91
  export type { StatusOptions } from "./renderables/status.js";
80
92
  export { Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn, SpinnerColumn, MofNCompleteColumn, track, } from "./renderables/progress.js";
81
- export type { ProgressOptions, TaskOptions, TaskUpdateOptions } from "./renderables/progress.js";
93
+ export type { MofNCompleteColumnOptions, ProgressColumn, ProgressOptions, SpinnerColumnOptions, Task, TaskOptions, TaskUpdateOptions, TimeRemainingColumnOptions, } from "./renderables/progress.js";
82
94
  export { Prompt, IntPrompt, FloatPrompt, Confirm } from "./renderables/prompt.js";
83
- export type { PromptInput, PromptOptions } from "./renderables/prompt.js";
95
+ export type { ConfirmOptions, PromptInput, PromptOptions } from "./renderables/prompt.js";
84
96
  export { Traceback } from "./renderables/traceback.js";
85
97
  export type { TracebackOptions } from "./renderables/traceback.js";
86
98
  export { Syntax } from "./renderables/syntax.js";
87
- export type { SyntaxOptions } from "./renderables/syntax.js";
99
+ export type { SyntaxLanguage, SyntaxOptions } from "./renderables/syntax.js";
88
100
  export { Markdown } from "./renderables/markdown.js";
89
101
  export type { MarkdownOptions } from "./renderables/markdown.js";
90
102
  export { Layout } from "./renderables/layout.js";