@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.5

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 (207) hide show
  1. package/README.md +23 -13
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-D5HUf3Fj.d.ts +452 -0
  4. package/dist/ansi.d.ts +137 -143
  5. package/dist/ansi.js +87 -2
  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-BMzMwV7Q.d.ts +22 -0
  11. package/dist/color-policy-SVj1pYTA.js +560 -0
  12. package/dist/color-profile-DHhQHY55.js +36 -0
  13. package/dist/color-profile-u0Nhe9Nv.d.ts +97 -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-Bh4yGP6w.d.ts +186 -0
  18. package/dist/detect-BuTXtY6e.js +373 -0
  19. package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  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-Bmc2tRPk.d.ts +21 -0
  24. package/dist/index.d.ts +235 -754
  25. package/dist/index.js +1362 -3001
  26. package/dist/osc-CCH7xDoS.js +71 -0
  27. package/dist/osc-Cn0fw77g.d.ts +23 -0
  28. package/dist/paint-C19minOS.d.ts +81 -0
  29. package/dist/query-vaIeGOkH.d.ts +152 -0
  30. package/dist/router.d.ts +392 -0
  31. package/dist/router.js +709 -0
  32. package/dist/sample-Cqw1bjUL.js +445 -0
  33. package/dist/screen-BOh__-65.js +324 -0
  34. package/dist/screen-BReKIheE.d.ts +40 -0
  35. package/dist/screen.d.ts +5 -0
  36. package/dist/screen.js +5 -0
  37. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  38. package/dist/serialize-BTkAZgw1.js +79 -0
  39. package/dist/session-BJmzX2NJ.js +664 -0
  40. package/dist/sgr-BhwaWAJB.js +246 -0
  41. package/dist/store-CgrG9K4y.d.ts +72 -0
  42. package/dist/string-width-CijQwpIk.js +69 -0
  43. package/dist/strip-BvU4toXG.js +6 -0
  44. package/dist/terminal.d.ts +118 -0
  45. package/dist/terminal.js +2 -0
  46. package/dist/tokenize-AjqbvtiT.js +1242 -0
  47. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  48. package/dist/truncate-D31fhU6i.js +562 -0
  49. package/dist/use-focus-C2sciZOo.js +1335 -0
  50. package/package.json +43 -17
  51. package/src/ansi/chalk.ts +12 -53
  52. package/src/ansi/cursor.ts +2 -4
  53. package/src/ansi/east-asian-width.ts +44 -0
  54. package/src/ansi/escapes.ts +18 -26
  55. package/src/ansi/graphemes.ts +8 -0
  56. package/src/ansi/hyperlink.ts +44 -0
  57. package/src/ansi/index.ts +14 -14
  58. package/src/ansi/osc.ts +77 -0
  59. package/src/ansi/sgr.ts +2 -5
  60. package/src/ansi/slice.ts +1 -1
  61. package/src/ansi/string-width.ts +91 -206
  62. package/src/ansi/strip.ts +13 -33
  63. package/src/ansi/tokenize.ts +96 -169
  64. package/src/ansi/truncate.ts +5 -3
  65. package/src/ansi/wrap.ts +13 -14
  66. package/src/ansi-tokenizer.ts +1 -1
  67. package/src/capabilities/color-policy.ts +34 -0
  68. package/src/capabilities/detect.ts +594 -0
  69. package/src/capabilities/index.ts +37 -0
  70. package/src/capabilities/query.ts +657 -0
  71. package/src/capabilities/store.ts +379 -0
  72. package/src/color/index.ts +3 -0
  73. package/src/color/paint.ts +169 -0
  74. package/src/color/palette.ts +48 -0
  75. package/src/color/sample.ts +323 -0
  76. package/src/color.ts +1 -0
  77. package/src/components/AnsiText.tsx +42 -0
  78. package/src/components/App.tsx +109 -22
  79. package/src/components/BackgroundContext.ts +2 -3
  80. package/src/components/Box.tsx +43 -60
  81. package/src/components/CursorContext.ts +1 -1
  82. package/src/components/ErrorBoundary.tsx +1 -1
  83. package/src/components/ErrorOverview.tsx +8 -7
  84. package/src/components/Hyperlink.tsx +56 -0
  85. package/src/components/Spacer.tsx +1 -1
  86. package/src/components/Static.tsx +1 -1
  87. package/src/components/StderrContext.ts +0 -2
  88. package/src/components/StdinContext.ts +0 -1
  89. package/src/components/StdoutContext.ts +1 -3
  90. package/src/components/TerminalOscContext.ts +25 -0
  91. package/src/components/Text.tsx +23 -46
  92. package/src/components/Transform.tsx +2 -2
  93. package/src/cursor-position.ts +1 -1
  94. package/src/devtools.ts +84 -24
  95. package/src/dom.ts +11 -8
  96. package/src/env.ts +12 -0
  97. package/src/get-max-width.ts +1 -1
  98. package/src/global.d.ts +6 -4
  99. package/src/{boxes.ts → glyphs.ts} +17 -18
  100. package/src/hooks/use-animation.ts +1 -1
  101. package/src/hooks/use-app.ts +1 -1
  102. package/src/hooks/use-box-metrics.ts +1 -1
  103. package/src/hooks/use-capabilities.ts +73 -0
  104. package/src/hooks/use-cursor.ts +3 -3
  105. package/src/hooks/use-focus-manager.ts +1 -1
  106. package/src/hooks/use-focus.ts +5 -6
  107. package/src/hooks/use-input.ts +4 -4
  108. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  109. package/src/hooks/use-paste.ts +2 -2
  110. package/src/hooks/use-stderr.ts +1 -1
  111. package/src/hooks/use-stdin.ts +1 -1
  112. package/src/hooks/use-stdout.ts +1 -1
  113. package/src/hooks/use-terminal-osc.ts +59 -0
  114. package/src/hooks/use-window-size.ts +2 -2
  115. package/src/index.ts +88 -44
  116. package/src/ink.tsx +620 -745
  117. package/src/input-parser.ts +1 -1
  118. package/src/instances.ts +1 -1
  119. package/src/kitty-keyboard.ts +128 -0
  120. package/src/measure-element.ts +1 -1
  121. package/src/measure-text.ts +2 -2
  122. package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
  123. package/src/parse-keypress.ts +4 -5
  124. package/src/parse-stack-line.ts +0 -1
  125. package/src/patch-console.ts +44 -0
  126. package/src/reconciler.ts +36 -62
  127. package/src/render-background.ts +37 -16
  128. package/src/render-border.ts +97 -64
  129. package/src/render-frame.ts +83 -0
  130. package/src/render-to-string.ts +24 -9
  131. package/src/render.ts +24 -16
  132. package/src/router/components.tsx +343 -0
  133. package/src/router/context.ts +41 -0
  134. package/src/router/history.ts +194 -0
  135. package/src/router/hooks.tsx +391 -0
  136. package/src/router/index.ts +34 -0
  137. package/src/router/matcher.ts +571 -0
  138. package/src/sanitize-ansi.ts +1 -1
  139. package/src/screen/ansi.ts +184 -0
  140. package/src/screen/canvas.ts +137 -0
  141. package/src/screen/cell.ts +138 -0
  142. package/src/screen/color-profile.ts +47 -0
  143. package/src/screen/geometry.ts +9 -0
  144. package/src/screen/index.ts +6 -0
  145. package/src/screen/screen.ts +272 -0
  146. package/src/screen/serialize.ts +129 -0
  147. package/src/screen.ts +1 -0
  148. package/src/semantic-text-style.ts +118 -0
  149. package/src/signal-exit.ts +4 -5
  150. package/src/squash-text-nodes.ts +4 -7
  151. package/src/stream.ts +2 -4
  152. package/src/structured-text.ts +321 -0
  153. package/src/styles.ts +16 -17
  154. package/src/terminal/index.ts +2 -0
  155. package/src/terminal/inline-presenter.ts +120 -0
  156. package/src/terminal/input.ts +86 -0
  157. package/src/terminal/render-scheduler.ts +37 -0
  158. package/src/terminal/screen-presenter.ts +188 -0
  159. package/src/terminal/session.ts +407 -0
  160. package/src/terminal-size.ts +9 -8
  161. package/src/terminal.ts +1 -0
  162. package/src/testing/browser.ts +588 -0
  163. package/src/testing/emulators.ts +205 -0
  164. package/src/testing/explorer-app/index.html +12 -0
  165. package/src/testing/explorer-app/main.ts +381 -0
  166. package/src/testing/explorer-app/style.css +194 -0
  167. package/src/testing/explorer-app/tsconfig.json +15 -0
  168. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  169. package/src/testing/index.ts +26 -0
  170. package/src/testing/keys.ts +56 -0
  171. package/src/testing/live.ts +85 -0
  172. package/src/testing/matchers.ts +70 -0
  173. package/src/testing/public.ts +94 -0
  174. package/src/testing/terminal.ts +349 -0
  175. package/src/testing/vitest.ts +157 -0
  176. package/src/transform-adapter.ts +14 -0
  177. package/src/types.ts +1 -6
  178. package/src/utils.ts +2 -2
  179. package/src/wrap-text.ts +4 -4
  180. package/src/yoga/config.ts +2 -2
  181. package/src/yoga/core/absoluteLayout.ts +15 -15
  182. package/src/yoga/core/baseline.ts +3 -3
  183. package/src/yoga/core/cache.ts +4 -4
  184. package/src/yoga/core/calculateLayout.ts +33 -27
  185. package/src/yoga/core/config.ts +1 -1
  186. package/src/yoga/core/flexLine.ts +3 -3
  187. package/src/yoga/core/helpers.ts +3 -3
  188. package/src/yoga/core/layoutResults.ts +4 -4
  189. package/src/yoga/core/node.ts +16 -16
  190. package/src/yoga/core/pixelGrid.ts +4 -4
  191. package/src/yoga/core/style.ts +11 -11
  192. package/src/yoga/core/types.ts +2 -2
  193. package/src/yoga/index.ts +6 -6
  194. package/src/yoga/node.ts +8 -8
  195. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  196. package/dist/truncate-CBiyyZzw.js +0 -2156
  197. package/src/ansi/supports-color.ts +0 -213
  198. package/src/ansi/widest-line.ts +0 -12
  199. package/src/auto-bind.ts +0 -41
  200. package/src/colorize.ts +0 -60
  201. package/src/devtools-window-polyfill.ts +0 -73
  202. package/src/indent-string.ts +0 -16
  203. package/src/is-in-ci.ts +0 -7
  204. package/src/log-update.ts +0 -370
  205. package/src/output.ts +0 -308
  206. package/src/renderer.ts +0 -73
  207. package/src/write-synchronized.ts +0 -9
@@ -0,0 +1,323 @@
1
+ import {
2
+ rgb,
3
+ type GradientStop,
4
+ type InterpolationSpace,
5
+ type LinearGradient,
6
+ type Paint,
7
+ type PaintContext,
8
+ type PerimeterGradient,
9
+ } from "#/color/paint.ts";
10
+ import { colorToRgb } from "#/color/palette.ts";
11
+ import type { Color, RgbColor } from "#/screen/cell.ts";
12
+ import type { Rect } from "#/screen/geometry.ts";
13
+ import { parseSemanticColor } from "#/semantic-text-style.ts";
14
+
15
+ export function samplePaint(
16
+ paint: Paint,
17
+ x: number,
18
+ y: number,
19
+ bounds: Rect,
20
+ context: PaintContext = {},
21
+ ): Color | undefined {
22
+ const resolved = resolveConditional(paint, context);
23
+ if (resolved && typeof resolved === "object" && "type" in resolved) {
24
+ if (resolved.type === "perimeter-gradient") {
25
+ return samplePerimeterGradient(resolved, x, y, bounds, context);
26
+ }
27
+ }
28
+ if (isLinearGradient(resolved)) {
29
+ return sampleGradient(resolved, x, y, bounds, context);
30
+ }
31
+ return resolveColor(resolved);
32
+ }
33
+
34
+ export function blend(source: Color, destination: Color, palette?: PaintContext["palette"]): Color {
35
+ const foreground = colorToRgb(source, palette);
36
+ if (foreground.alpha >= 255) return foreground;
37
+ const background = colorToRgb(destination, palette);
38
+ const alpha = foreground.alpha / 255;
39
+ return rgb(
40
+ foreground.red * alpha + background.red * (1 - alpha),
41
+ foreground.green * alpha + background.green * (1 - alpha),
42
+ foreground.blue * alpha + background.blue * (1 - alpha),
43
+ );
44
+ }
45
+
46
+ /** Progressively moves a color toward white in perceptual OKLab space. */
47
+ export function lighten(color: Color, amount: number, palette?: PaintContext["palette"]): RgbColor {
48
+ return interpolateColor(color, rgb(255, 255, 255), amount, "oklab", palette);
49
+ }
50
+
51
+ /** Progressively moves a color toward black in perceptual OKLab space. */
52
+ export function darken(color: Color, amount: number, palette?: PaintContext["palette"]): RgbColor {
53
+ return interpolateColor(color, rgb(0, 0, 0), amount, "oklab", palette);
54
+ }
55
+
56
+ function sampleGradient(
57
+ gradient: LinearGradient,
58
+ x: number,
59
+ y: number,
60
+ bounds: Rect,
61
+ context: PaintContext,
62
+ ): Color | undefined {
63
+ const radians = (gradient.angle * Math.PI) / 180;
64
+ const dx = Math.cos(radians);
65
+ const dy = Math.sin(radians);
66
+ const width = Math.max(1, bounds.width - 1);
67
+ const height = Math.max(1, bounds.height - 1);
68
+ const projections = [0, width * dx, height * dy, width * dx + height * dy];
69
+ const minimum = Math.min(...projections);
70
+ const maximum = Math.max(...projections);
71
+ const projection = (x - bounds.x) * dx + (y - bounds.y) * dy;
72
+ const position = maximum === minimum ? 0 : (projection - minimum) / (maximum - minimum);
73
+ return sampleStops(gradient.stops, position, gradient.space, x, y, bounds, context);
74
+ }
75
+
76
+ function samplePerimeterGradient(
77
+ gradient: PerimeterGradient,
78
+ x: number,
79
+ y: number,
80
+ bounds: Rect,
81
+ context: PaintContext,
82
+ ): Color | undefined {
83
+ const width = Math.max(1, bounds.width);
84
+ const height = Math.max(1, bounds.height);
85
+ const steps = width === 1 || height === 1 ? Math.max(width, height) : 2 * width + 2 * height - 4;
86
+ if (steps <= 1) return samplePaint(gradient.stops[0]!.color, x, y, bounds, context);
87
+
88
+ const left = bounds.x;
89
+ const top = bounds.y;
90
+ const right = left + width - 1;
91
+ const bottom = top + height - 1;
92
+ let index: number;
93
+
94
+ if (height === 1) index = x - left;
95
+ else if (width === 1) index = y - top;
96
+ else if (y <= top) index = Math.max(0, Math.min(width - 1, x - left));
97
+ else if (x >= right) index = width + Math.max(0, Math.min(height - 2, y - top - 1));
98
+ else if (y >= bottom) index = width + height - 2 + Math.max(0, Math.min(width - 1, right - x));
99
+ else index = 2 * width + height - 2 + Math.max(0, Math.min(height - 2, bottom - y - 1));
100
+
101
+ index = modulo(index - gradient.offset, steps);
102
+ const position = blendPosition(index, steps, gradient.stops.length);
103
+ return sampleStops(gradient.stops, position, gradient.space, x, y, bounds, context);
104
+ }
105
+
106
+ /** Matches Lip Gloss Blend1D's even, discrete distribution of stops. */
107
+ function blendPosition(index: number, steps: number, stopCount: number): number {
108
+ if (steps <= stopCount) return index / Math.max(1, stopCount - 1);
109
+ const segments = stopCount - 1;
110
+ const baseSize = Math.floor(steps / segments);
111
+ const remainder = steps % segments;
112
+ let start = 0;
113
+ for (let segment = 0; segment < segments; segment++) {
114
+ const size = baseSize + (segment < remainder ? 1 : 0);
115
+ if (index < start + size) {
116
+ const local = size <= 1 ? 0 : (index - start) / (size - 1);
117
+ return (segment + local) / segments;
118
+ }
119
+ start += size;
120
+ }
121
+ return 1;
122
+ }
123
+
124
+ function sampleStops(
125
+ stops: readonly GradientStop[],
126
+ position: number,
127
+ space: InterpolationSpace,
128
+ x: number,
129
+ y: number,
130
+ bounds: Rect,
131
+ context: PaintContext,
132
+ ): Color | undefined {
133
+ let right = 1;
134
+ while (right < stops.length - 1 && position > stops[right]!.offset!) right++;
135
+ const left = right - 1;
136
+ const start = stops[left]!;
137
+ const end = stops[right]!;
138
+ const span = end.offset! - start.offset!;
139
+ const amount = span === 0 ? 0 : Math.max(0, Math.min(1, (position - start.offset!) / span));
140
+ const startColor = samplePaint(start.color, x, y, bounds, context);
141
+ const endColor = samplePaint(end.color, x, y, bounds, context);
142
+ if (!startColor || !endColor) return startColor ?? endColor;
143
+ return interpolateColor(startColor, endColor, amount, space, context.palette);
144
+ }
145
+
146
+ export function interpolateColor(
147
+ from: Color,
148
+ to: Color,
149
+ amount: number,
150
+ space: InterpolationSpace = "oklab",
151
+ palette?: PaintContext["palette"],
152
+ ): RgbColor {
153
+ const a = colorToRgb(from, palette);
154
+ const b = colorToRgb(to, palette);
155
+ const t = Math.max(0, Math.min(1, amount));
156
+ if (space === "rgb")
157
+ return rgb(
158
+ mix(a.red, b.red, t),
159
+ mix(a.green, b.green, t),
160
+ mix(a.blue, b.blue, t),
161
+ mix(a.alpha, b.alpha, t),
162
+ );
163
+ if (space === "hsv") {
164
+ const ah = toHsv(a),
165
+ bh = toHsv(b);
166
+ let delta = bh[0] - ah[0];
167
+ if (delta > 180) delta -= 360;
168
+ if (delta < -180) delta += 360;
169
+ return fromHsv(
170
+ ah[0] + delta * t,
171
+ mix(ah[1], bh[1], t),
172
+ mix(ah[2], bh[2], t),
173
+ mix(a.alpha, b.alpha, t),
174
+ );
175
+ }
176
+ if (space === "lab") {
177
+ const al = toLab(a),
178
+ bl = toLab(b);
179
+ return fromLab(
180
+ mix(al[0], bl[0], t),
181
+ mix(al[1], bl[1], t),
182
+ mix(al[2], bl[2], t),
183
+ mix(a.alpha, b.alpha, t),
184
+ );
185
+ }
186
+ const al = toOklab(a),
187
+ bl = toOklab(b);
188
+ return fromOklab(
189
+ mix(al[0], bl[0], t),
190
+ mix(al[1], bl[1], t),
191
+ mix(al[2], bl[2], t),
192
+ mix(a.alpha, b.alpha, t),
193
+ );
194
+ }
195
+
196
+ const D65 = [0.95047, 1, 1.08883] as const;
197
+ function labCurve(value: number): number {
198
+ return value > (6 / 29) ** 3 ? Math.cbrt(value) : (value / 3) * (29 / 6) ** 2 + 4 / 29;
199
+ }
200
+ function inverseLabCurve(value: number): number {
201
+ return value > 6 / 29 ? value ** 3 : 3 * (6 / 29) ** 2 * (value - 4 / 29);
202
+ }
203
+ function toLab(color: RgbColor): [number, number, number] {
204
+ const r = linear(color.red),
205
+ g = linear(color.green),
206
+ b = linear(color.blue);
207
+ const x = 0.4123907992659595 * r + 0.357584339383878 * g + 0.1804807884018343 * b;
208
+ const y = 0.2126390058715104 * r + 0.7151686787677559 * g + 0.0721923153607337 * b;
209
+ const z = 0.0193308187155919 * r + 0.119194779794626 * g + 0.9505321522496606 * b;
210
+ const fy = labCurve(y / D65[1]);
211
+ return [1.16 * fy - 0.16, 5 * (labCurve(x / D65[0]) - fy), 2 * (fy - labCurve(z / D65[2]))];
212
+ }
213
+ function fromLab(l: number, a: number, b: number, alpha: number): RgbColor {
214
+ const center = (l + 0.16) / 1.16;
215
+ const x = D65[0] * inverseLabCurve(center + a / 5);
216
+ const y = D65[1] * inverseLabCurve(center);
217
+ const z = D65[2] * inverseLabCurve(center - b / 2);
218
+ return rgb(
219
+ colorfulChannel(3.240969941904521 * x - 1.5373831775700935 * y - 0.4986107602930033 * z),
220
+ colorfulChannel(-0.9692436362808798 * x + 1.8759675015077206 * y + 0.0415550574071756 * z),
221
+ colorfulChannel(0.0556300796969936 * x - 0.2039769588889766 * y + 1.0569715142428786 * z),
222
+ alpha,
223
+ );
224
+ }
225
+
226
+ const mix = (a: number, b: number, t: number) => a + (b - a) * t;
227
+ const linear = (value: number) => {
228
+ const x = value / 255;
229
+ return x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
230
+ };
231
+ const gamma = (value: number) =>
232
+ 255 * (value <= 0.0031308 ? 12.92 * value : 1.055 * value ** (1 / 2.4) - 0.055);
233
+ const colorfulChannel = (value: number) => {
234
+ const encoded = value <= 0.0031308 ? 12.92 * value : 1.055 * value ** (1 / 2.4) - 0.055;
235
+ return Math.floor(Math.round(Math.max(0, Math.min(1, encoded)) * 65_535) / 256);
236
+ };
237
+ function toOklab(c: RgbColor): [number, number, number] {
238
+ const r = linear(c.red),
239
+ g = linear(c.green),
240
+ b = linear(c.blue);
241
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b),
242
+ m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b),
243
+ s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
244
+ return [
245
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
246
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
247
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
248
+ ];
249
+ }
250
+ function fromOklab(L: number, a: number, b: number, alpha: number): RgbColor {
251
+ const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3,
252
+ m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3,
253
+ s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
254
+ return rgb(
255
+ gamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
256
+ gamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
257
+ gamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
258
+ alpha,
259
+ );
260
+ }
261
+ function toHsv(c: RgbColor): [number, number, number] {
262
+ const r = c.red / 255,
263
+ g = c.green / 255,
264
+ b = c.blue / 255,
265
+ max = Math.max(r, g, b),
266
+ min = Math.min(r, g, b),
267
+ d = max - min;
268
+ let h = 0;
269
+ if (d)
270
+ h =
271
+ max === r
272
+ ? 60 * (((g - b) / d) % 6)
273
+ : max === g
274
+ ? 60 * ((b - r) / d + 2)
275
+ : 60 * ((r - g) / d + 4);
276
+ return [(h + 360) % 360, max === 0 ? 0 : d / max, max];
277
+ }
278
+ function fromHsv(h: number, s: number, v: number, alpha: number): RgbColor {
279
+ const c = v * s,
280
+ x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
281
+ m = v - c;
282
+ const [r, g, b] =
283
+ h < 60
284
+ ? [c, x, 0]
285
+ : h < 120
286
+ ? [x, c, 0]
287
+ : h < 180
288
+ ? [0, c, x]
289
+ : h < 240
290
+ ? [0, x, c]
291
+ : h < 300
292
+ ? [x, 0, c]
293
+ : [c, 0, x];
294
+ return rgb((r + m) * 255, (g + m) * 255, (b + m) * 255, alpha);
295
+ }
296
+ function resolveConditional(paint: Paint, context: PaintContext): Paint {
297
+ if (typeof paint !== "object" || paint === null || "model" in paint) return paint;
298
+ if (paint.type === "adaptive-color")
299
+ return resolveConditional(context.appearance === "light" ? paint.light : paint.dark, context);
300
+ if (paint.type === "profile-color")
301
+ return resolveConditional(
302
+ (context.profile && paint.colors[context.profile]) ?? paint.fallback,
303
+ context,
304
+ );
305
+ return paint;
306
+ }
307
+ function resolveColor(value: Paint): Color | undefined {
308
+ return typeof value === "string"
309
+ ? parseSemanticColor(value)
310
+ : typeof value === "object" && value !== null && "model" in value
311
+ ? value
312
+ : undefined;
313
+ }
314
+ function isLinearGradient(value: Paint): value is LinearGradient {
315
+ return (
316
+ typeof value === "object" &&
317
+ value !== null &&
318
+ "type" in value &&
319
+ value.type === "linear-gradient"
320
+ );
321
+ }
322
+
323
+ const modulo = (value: number, divisor: number) => ((value % divisor) + divisor) % divisor;
package/src/color.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "#/color/index.ts";
@@ -0,0 +1,42 @@
1
+ /** @jsxImportSource react */
2
+ import { useContext } from "react";
3
+
4
+ import { stripAnsi } from "#/ansi/strip.ts";
5
+ import { accessibilityContext } from "#/components/AccessibilityContext.ts";
6
+ import { Text } from "#/components/Text.tsx";
7
+ import type { Styles } from "#/styles.ts";
8
+
9
+ export type Props = {
10
+ /** External text containing ANSI SGR styling or OSC 8 hyperlinks. */
11
+ readonly children: string;
12
+ readonly wrap?: Styles["textWrap"];
13
+ readonly "aria-label"?: string;
14
+ readonly "aria-hidden"?: boolean;
15
+ };
16
+
17
+ /**
18
+ * Renders explicitly trusted ANSI-styled output as structured terminal cells.
19
+ * Ordinary `Text` continues to strip terminal control sequences.
20
+ */
21
+ export function AnsiText({
22
+ children,
23
+ wrap = "wrap",
24
+ "aria-label": ariaLabel,
25
+ "aria-hidden": ariaHidden = false,
26
+ }: Props) {
27
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
28
+
29
+ if (isScreenReaderEnabled) {
30
+ if (ariaHidden) return null;
31
+ return <Text>{ariaLabel ?? stripAnsi(children)}</Text>;
32
+ }
33
+
34
+ return (
35
+ <ink-text
36
+ style={{ flexGrow: 0, flexShrink: 1, flexDirection: "row", textWrap: wrap }}
37
+ internal_ansi
38
+ >
39
+ {children}
40
+ </ink-text>
41
+ );
42
+ }
@@ -1,6 +1,5 @@
1
1
  /** @jsxImportSource react */
2
2
  import { EventEmitter } from "node:events";
3
- import process from "node:process";
4
3
 
5
4
  import React, {
6
5
  type ReactNode,
@@ -12,19 +11,26 @@ import React, {
12
11
  useInsertionEffect,
13
12
  } from "react";
14
13
 
15
- import { cliCursor } from "../ansi/cursor.ts";
16
- import { ansiEscapes, CSI, ESC } from "../ansi/escapes.ts";
17
- import { createInputParser } from "../input-parser.ts";
18
- import { type CursorPosition } from "../log-update.ts";
19
- import { getRawModeStream, type OutputStream } from "../stream.ts";
20
- import { animationContext as AnimationContext } from "./AnimationContext.ts";
21
- import { AppContext, type SuspendTerminal } from "./AppContext.ts";
22
- import { CursorContext } from "./CursorContext.ts";
23
- import { ErrorBoundary } from "./ErrorBoundary.tsx";
24
- import { FocusContext } from "./FocusContext.ts";
25
- import { StderrContext } from "./StderrContext.ts";
26
- import { StdinContext } from "./StdinContext.ts";
27
- import { StdoutContext } from "./StdoutContext.ts";
14
+ import { cliCursor } from "#/ansi/cursor.ts";
15
+ import { ansiEscapes, CSI, ESC } from "#/ansi/escapes.ts";
16
+ import {
17
+ ensureTerminalQuery,
18
+ getTerminalQueryPromise,
19
+ refreshTerminalQuery,
20
+ type TerminalQueryResult,
21
+ } from "#/capabilities/query.ts";
22
+ import { registerTerminalIntegration } from "#/capabilities/store.ts";
23
+ import { animationContext as AnimationContext } from "#/components/AnimationContext.ts";
24
+ import { AppContext, type SuspendTerminal } from "#/components/AppContext.ts";
25
+ import { CursorContext } from "#/components/CursorContext.ts";
26
+ import { ErrorBoundary } from "#/components/ErrorBoundary.tsx";
27
+ import { FocusContext } from "#/components/FocusContext.ts";
28
+ import { StderrContext } from "#/components/StderrContext.ts";
29
+ import { StdinContext } from "#/components/StdinContext.ts";
30
+ import { StdoutContext } from "#/components/StdoutContext.ts";
31
+ import { type CursorPosition } from "#/cursor-position.ts";
32
+ import { getRawModeStream, type OutputStream } from "#/stream.ts";
33
+ import type { TerminalInput } from "#/terminal/input.ts";
28
34
 
29
35
  const tab = "\t";
30
36
  const shiftTab = `${CSI}Z`;
@@ -52,6 +58,7 @@ type Props = {
52
58
  readonly setCursorPosition: (position: CursorPosition | undefined) => void;
53
59
  readonly interactive: boolean;
54
60
  readonly renderThrottleMs: number;
61
+ readonly terminalInput: TerminalInput;
55
62
  };
56
63
 
57
64
  type Focusable = {
@@ -77,6 +84,7 @@ export function App({
77
84
  setCursorPosition,
78
85
  interactive,
79
86
  renderThrottleMs,
87
+ terminalInput,
80
88
  }: Props): React.ReactNode {
81
89
  const [isFocusEnabled, setIsFocusEnabled] = useState(true);
82
90
  const [activeFocusId, setActiveFocusId] = useState<string | undefined>(undefined);
@@ -101,7 +109,6 @@ export function App({
101
109
  internal_eventEmitter.current.setMaxListeners(Infinity);
102
110
  // Store the currently attached readable listener to avoid stale closure issues
103
111
  const readableListenerRef = useRef<(() => void) | undefined>(undefined);
104
- const inputParserRef = useRef(createInputParser());
105
112
  const pendingInputFlushRef = useRef<NodeJS.Timeout | undefined>(undefined);
106
113
  // Small delay to let chunked escape sequences complete before flushing as literal input.
107
114
  const pendingInputFlushDelayMilliseconds = 20;
@@ -215,10 +222,10 @@ export function App({
215
222
  }, [stdin]);
216
223
 
217
224
  const clearInputState = useCallback((): void => {
218
- inputParserRef.current.reset();
225
+ terminalInput.reset();
219
226
  clearPendingInputFlush();
220
227
  detachReadableListener();
221
- }, [clearPendingInputFlush, detachReadableListener]);
228
+ }, [clearPendingInputFlush, detachReadableListener, terminalInput]);
222
229
 
223
230
  const disableRawMode = useCallback((): void => {
224
231
  if (!rawModeStdin) {
@@ -275,21 +282,21 @@ export function App({
275
282
  clearPendingInputFlush();
276
283
  pendingInputFlushRef.current = setTimeout(() => {
277
284
  pendingInputFlushRef.current = undefined;
278
- const pendingEscape = inputParserRef.current.flushPendingEscape();
285
+ const pendingEscape = terminalInput.flushPendingEscape();
279
286
  if (!pendingEscape) {
280
287
  return;
281
288
  }
282
289
 
283
290
  emitInput(pendingEscape);
284
291
  }, pendingInputFlushDelayMilliseconds);
285
- }, [clearPendingInputFlush, emitInput]);
292
+ }, [clearPendingInputFlush, emitInput, terminalInput]);
286
293
 
287
294
  const handleReadable = useCallback((): void => {
288
295
  clearPendingInputFlush();
289
296
  let chunk;
290
297
  // eslint-disable-next-line @typescript-eslint/no-restricted-types
291
298
  while ((chunk = stdin.read() as string | null) !== null) {
292
- const inputEvents = inputParserRef.current.push(chunk);
299
+ const inputEvents = terminalInput.push(chunk);
293
300
  for (const event of inputEvents) {
294
301
  if (typeof event === "string") {
295
302
  emitInput(event);
@@ -306,10 +313,10 @@ export function App({
306
313
  }
307
314
  }
308
315
 
309
- if (inputParserRef.current.hasPendingEscape()) {
316
+ if (terminalInput.hasPendingEscape()) {
310
317
  schedulePendingInputFlush();
311
318
  }
312
- }, [stdin, emitInput, clearPendingInputFlush, schedulePendingInputFlush]);
319
+ }, [stdin, emitInput, clearPendingInputFlush, schedulePendingInputFlush, terminalInput]);
313
320
 
314
321
  const attachReadableListener = useCallback((): void => {
315
322
  if (readableListenerRef.current) {
@@ -380,6 +387,86 @@ export function App({
380
387
  [rawModeStdin, stdin, attachReadableListener, clearInputState, disableRawMode],
381
388
  );
382
389
 
390
+ const handleQueryTerminal = useCallback(
391
+ async ({ refresh = false } = {}): Promise<TerminalQueryResult | undefined> => {
392
+ if (!interactive || !isRawModeSupported || !stdout.isTTY) {
393
+ return undefined;
394
+ }
395
+
396
+ // Without a refresh, an existing (or in-flight) query answers directly.
397
+ if (!refresh) {
398
+ const existing = getTerminalQueryPromise(stdout);
399
+ if (existing) {
400
+ return existing;
401
+ }
402
+ }
403
+
404
+ // Own the input stream for the duration of the query: raw mode on so
405
+ // responses arrive unbuffered, and the readable listener detached so they
406
+ // flow to the query's own data listener instead of being parsed as key
407
+ // presses. queryTerminal buffers real user input typed during the query
408
+ // and unshifts it back; reattaching the readable listener delivers it.
409
+ //
410
+ // Note: if kitty keyboard auto-detection is in flight at the same time,
411
+ // both data listeners see the same bytes; that detector answers the same
412
+ // CSI ? u probe this query sends, so it converges to the same result.
413
+ handleSetRawMode(true);
414
+ detachReadableListener();
415
+ try {
416
+ return await (refresh
417
+ ? refreshTerminalQuery(stdin, stdout)
418
+ : ensureTerminalQuery(stdin, stdout));
419
+ } finally {
420
+ attachReadableListener();
421
+ handleSetRawMode(false);
422
+ }
423
+ },
424
+ [
425
+ interactive,
426
+ isRawModeSupported,
427
+ stdin,
428
+ stdout,
429
+ handleSetRawMode,
430
+ detachReadableListener,
431
+ attachReadableListener,
432
+ ],
433
+ );
434
+
435
+ // While the store has push reporting enabled, someone must actually read
436
+ // stdin so the reports reach `ingest` — even in apps without a single
437
+ // `useInput`. Holding raw mode (ref-counted, like `useInput` does) keeps
438
+ // the readable pipeline attached for the duration.
439
+ const reportFeedActiveRef = useRef(false);
440
+ const handleSetReportFeed = useCallback(
441
+ (active: boolean): void => {
442
+ if (active === reportFeedActiveRef.current || !isRawModeSupported) {
443
+ return;
444
+ }
445
+
446
+ reportFeedActiveRef.current = active;
447
+ handleSetRawMode(active);
448
+ },
449
+ [isRawModeSupported, handleSetRawMode],
450
+ );
451
+
452
+ // Hand the capabilities store this instance's integration: queries go
453
+ // through the input pipeline above instead of competing with it, and the
454
+ // report feed keeps that pipeline running while reports are enabled. An
455
+ // insertion effect runs before every passive effect — parent and child —
456
+ // so a child calling store.query() from its own mount effect always finds
457
+ // the integration already registered.
458
+ useInsertionEffect(() => {
459
+ const unregister = registerTerminalIntegration(stdout, {
460
+ runQuery: handleQueryTerminal,
461
+ setReportFeed: handleSetReportFeed,
462
+ });
463
+
464
+ return () => {
465
+ unregister();
466
+ handleSetReportFeed(false);
467
+ };
468
+ }, [stdout, handleQueryTerminal, handleSetReportFeed]);
469
+
383
470
  const handleSetBracketedPasteMode = useCallback(
384
471
  (isEnabled: boolean): void => {
385
472
  if (!stdout.isTTY) {
@@ -1,8 +1,7 @@
1
1
  import { createContext } from "react";
2
2
 
3
- import { type ForegroundColorName } from "../ansi/sgr.ts";
4
- import { type LiteralUnion } from "../types.ts";
3
+ import type { Paint } from "#/color/paint.ts";
5
4
 
6
- export type BackgroundColor = LiteralUnion<ForegroundColorName, string>;
5
+ export type BackgroundColor = Paint;
7
6
 
8
7
  export const backgroundContext = createContext<BackgroundColor | undefined>(undefined);