@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
@@ -1,26 +1,46 @@
1
- import { chalk } from "./ansi/chalk.ts";
2
- import { boxes as cliBoxes } from "./boxes.ts";
3
- import { colorize } from "./colorize.ts";
4
- import { type DOMNode } from "./dom.ts";
5
- import type { Output } from "./output.ts";
6
-
7
- const stylePiece = (segment: string, fg?: string, bg?: string, dim?: boolean): string => {
8
- let styled = colorize(segment, fg, "foreground");
9
- styled = colorize(styled, bg, "background");
10
- if (dim) {
11
- styled = chalk.dim(styled);
12
- }
13
-
14
- return styled;
1
+ import { graphemes } from "#/ansi/graphemes.ts";
2
+ import { stringWidth } from "#/ansi/string-width.ts";
3
+ import type { Paint, PaintContext } from "#/color/paint.ts";
4
+ import { samplePaint } from "#/color/sample.ts";
5
+ import { type DOMNode } from "#/dom.ts";
6
+ import { BOXES } from "#/glyphs.ts";
7
+ import type { Canvas } from "#/screen/canvas.ts";
8
+ import type { Rect } from "#/screen/geometry.ts";
9
+ import { cellAttributes, createCell, type Cell } from "#/screen/index.ts";
10
+
11
+ const stylePiece = (
12
+ segment: string,
13
+ x: number,
14
+ y: number,
15
+ bounds: Rect,
16
+ context: PaintContext,
17
+ fg?: Paint,
18
+ bg?: Paint,
19
+ dim?: boolean,
20
+ ): Cell[] => {
21
+ let column = x;
22
+ return [...graphemes(segment)].map((grapheme) => {
23
+ const foreground = fg ? samplePaint(fg, column, y, bounds, context) : undefined;
24
+ const background = bg ? samplePaint(bg, column, y, bounds, context) : undefined;
25
+ const width = Math.max(1, stringWidth(grapheme));
26
+ column += width;
27
+ return createCell(grapheme, width, {
28
+ ...(foreground ? { foreground } : {}),
29
+ ...(background ? { background } : {}),
30
+ underline: "none",
31
+ attributes: dim ? cellAttributes.faint : cellAttributes.none,
32
+ });
33
+ });
15
34
  };
16
35
 
17
- export const renderBorder = (x: number, y: number, node: DOMNode, output: Output): void => {
36
+ export const renderBorder = (x: number, y: number, node: DOMNode, output: Canvas): void => {
18
37
  if (node.style.borderStyle) {
19
38
  const width = node.yogaNode!.getComputedWidth();
20
39
  const height = node.yogaNode!.getComputedHeight();
40
+ const bounds = { x, y, width, height };
21
41
  const box =
22
42
  typeof node.style.borderStyle === "string"
23
- ? cliBoxes[node.style.borderStyle]
43
+ ? BOXES[node.style.borderStyle]
24
44
  : node.style.borderStyle;
25
45
 
26
46
  const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
@@ -52,19 +72,12 @@ export const renderBorder = (x: number, y: number, node: DOMNode, output: Output
52
72
 
53
73
  const contentWidth = width - (showLeftBorder ? 1 : 0) - (showRightBorder ? 1 : 0);
54
74
 
55
- let topBorder = showTopBorder
75
+ const topBorder = showTopBorder
56
76
  ? (showLeftBorder ? box.topLeft : "") +
57
77
  box.top.repeat(contentWidth) +
58
78
  (showRightBorder ? box.topRight : "")
59
79
  : undefined;
60
80
 
61
- topBorder &&= stylePiece(
62
- topBorder,
63
- topBorderColor,
64
- topBorderBackgroundColor,
65
- dimTopBorderColor,
66
- );
67
-
68
81
  let verticalBorderHeight = height;
69
82
 
70
83
  if (showTopBorder) {
@@ -75,60 +88,80 @@ export const renderBorder = (x: number, y: number, node: DOMNode, output: Output
75
88
  verticalBorderHeight -= 1;
76
89
  }
77
90
 
78
- let leftBorder = "";
79
-
80
- if (showLeftBorder) {
81
- const one = stylePiece(
82
- box.left,
83
- leftBorderColor,
84
- leftBorderBackgroundColor,
85
- dimLeftBorderColor,
86
- );
87
- leftBorder = (one + "\n").repeat(verticalBorderHeight);
88
- }
89
-
90
- let rightBorder = "";
91
-
92
- if (showRightBorder) {
93
- const one = stylePiece(
94
- box.right,
95
- rightBorderColor,
96
- rightBorderBackgroundColor,
97
- dimRightBorderColor,
98
- );
99
- rightBorder = (one + "\n").repeat(verticalBorderHeight);
100
- }
91
+ const offsetY = showTopBorder ? 1 : 0;
101
92
 
102
- let bottomBorder = showBottomBorder
93
+ const bottomBorder = showBottomBorder
103
94
  ? (showLeftBorder ? box.bottomLeft : "") +
104
95
  box.bottom.repeat(contentWidth) +
105
96
  (showRightBorder ? box.bottomRight : "")
106
97
  : undefined;
107
- bottomBorder &&= stylePiece(
108
- bottomBorder,
109
- bottomBorderColor,
110
- bottomBorderBackgroundColor,
111
- dimBottomBorderColor,
112
- );
113
-
114
- const offsetY = showTopBorder ? 1 : 0;
115
98
 
116
99
  if (topBorder) {
117
- output.write(x, y, topBorder, { transformers: [] });
100
+ output.writeCells(x, y, [
101
+ stylePiece(
102
+ topBorder,
103
+ x,
104
+ y,
105
+ bounds,
106
+ output.paintContext,
107
+ topBorderColor,
108
+ topBorderBackgroundColor,
109
+ dimTopBorderColor,
110
+ ),
111
+ ]);
118
112
  }
119
113
 
120
- if (leftBorder) {
121
- output.write(x, y + offsetY, leftBorder, { transformers: [] });
114
+ if (showLeftBorder) {
115
+ output.writeCells(
116
+ x,
117
+ y + offsetY,
118
+ Array.from({ length: verticalBorderHeight }, (_, row) =>
119
+ stylePiece(
120
+ box.left,
121
+ x,
122
+ y + offsetY + row,
123
+ bounds,
124
+ output.paintContext,
125
+ leftBorderColor,
126
+ leftBorderBackgroundColor,
127
+ dimLeftBorderColor,
128
+ ),
129
+ ),
130
+ );
122
131
  }
123
132
 
124
- if (rightBorder) {
125
- output.write(x + width - 1, y + offsetY, rightBorder, {
126
- transformers: [],
127
- });
133
+ if (showRightBorder) {
134
+ output.writeCells(
135
+ x + width - 1,
136
+ y + offsetY,
137
+ Array.from({ length: verticalBorderHeight }, (_, row) =>
138
+ stylePiece(
139
+ box.right,
140
+ x + width - 1,
141
+ y + offsetY + row,
142
+ bounds,
143
+ output.paintContext,
144
+ rightBorderColor,
145
+ rightBorderBackgroundColor,
146
+ dimRightBorderColor,
147
+ ),
148
+ ),
149
+ );
128
150
  }
129
151
 
130
152
  if (bottomBorder) {
131
- output.write(x, y + height - 1, bottomBorder, { transformers: [] });
153
+ output.writeCells(x, y + height - 1, [
154
+ stylePiece(
155
+ bottomBorder,
156
+ x,
157
+ y + height - 1,
158
+ bounds,
159
+ output.paintContext,
160
+ bottomBorderColor,
161
+ bottomBorderBackgroundColor,
162
+ dimBottomBorderColor,
163
+ ),
164
+ ]);
132
165
  }
133
166
  }
134
167
  };
@@ -0,0 +1,83 @@
1
+ import type { PaintContext } from "#/color/paint.ts";
2
+ import { type DOMElement } from "#/dom.ts";
3
+ import { paintTree, renderAccessibleText } from "#/paint-tree.ts";
4
+ import { Canvas } from "#/screen/canvas.ts";
5
+ import { type ColorProfile } from "#/screen/index.ts";
6
+ import type { Screen } from "#/screen/screen.ts";
7
+
8
+ type Result = {
9
+ screen?: Screen;
10
+ staticScreen?: Screen;
11
+ accessibleText?: string;
12
+ staticAccessibleText?: string;
13
+ };
14
+
15
+ type RendererOptions = {
16
+ colorProfile?: ColorProfile;
17
+ paintContext?: PaintContext;
18
+ };
19
+
20
+ const renderDimension = (value: number): number =>
21
+ Number.isFinite(value) ? Math.max(0, Math.ceil(value)) : 0;
22
+
23
+ export const renderFrame = (
24
+ node: DOMElement,
25
+ isScreenReaderEnabled: boolean,
26
+ options: RendererOptions = {},
27
+ ): Result => {
28
+ if (node.yogaNode) {
29
+ if (isScreenReaderEnabled) {
30
+ const output = renderAccessibleText(node, {
31
+ skipStaticElements: true,
32
+ });
33
+
34
+ let staticOutput = "";
35
+
36
+ if (node.staticNode) {
37
+ staticOutput = renderAccessibleText(node.staticNode, {
38
+ skipStaticElements: false,
39
+ });
40
+ }
41
+
42
+ return {
43
+ accessibleText: output,
44
+ staticAccessibleText: staticOutput,
45
+ };
46
+ }
47
+
48
+ const output = new Canvas({
49
+ width: renderDimension(node.yogaNode.getComputedWidth()),
50
+ height: renderDimension(node.yogaNode.getComputedHeight()),
51
+ colorProfile: options.colorProfile,
52
+ paintContext: options.paintContext,
53
+ });
54
+
55
+ paintTree(node, output, {
56
+ skipStaticElements: true,
57
+ });
58
+
59
+ let staticOutput;
60
+
61
+ if (node.staticNode?.yogaNode) {
62
+ staticOutput = new Canvas({
63
+ width: renderDimension(node.staticNode.yogaNode.getComputedWidth()),
64
+ height: renderDimension(node.staticNode.yogaNode.getComputedHeight()),
65
+ colorProfile: options.colorProfile,
66
+ paintContext: options.paintContext,
67
+ });
68
+
69
+ paintTree(node.staticNode, staticOutput, {
70
+ skipStaticElements: false,
71
+ });
72
+ }
73
+
74
+ return {
75
+ screen: output.finish(),
76
+ ...(staticOutput ? { staticScreen: staticOutput.finish() } : {}),
77
+ };
78
+ }
79
+
80
+ return {
81
+ accessibleText: "",
82
+ };
83
+ };
@@ -1,10 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { LegacyRoot } from "react-reconciler/constants.js";
3
3
 
4
- import { createNode, type DOMElement } from "./dom.ts";
5
- import { reconciler } from "./reconciler.ts";
6
- import { renderer } from "./renderer.ts";
7
- import { Yoga } from "./yoga/index.ts";
4
+ import { detectColorLevel } from "#/capabilities/detect.ts";
5
+ import { createNode, type DOMElement } from "#/dom.ts";
6
+ import { reconciler } from "#/reconciler.ts";
7
+ import { renderFrame } from "#/render-frame.ts";
8
+ import { colorProfileFromLevel, type ColorProfile } from "#/screen/index.ts";
9
+ import { serializeScreen } from "#/screen/serialize.ts";
10
+ import { Yoga } from "#/yoga/index.ts";
8
11
 
9
12
  export type RenderToStringOptions = {
10
13
  /**
@@ -13,6 +16,12 @@ export type RenderToStringOptions = {
13
16
  @default 80
14
17
  */
15
18
  columns?: number;
19
+
20
+ /**
21
+ Color profile used for deterministic serialization. By default this retains
22
+ the process color level for Ink compatibility.
23
+ */
24
+ colorProfile?: ColorProfile;
16
25
  };
17
26
 
18
27
  /**
@@ -44,6 +53,7 @@ console.log(output);
44
53
  */
45
54
  export const renderToString = (node: ReactNode, options?: RenderToStringOptions): string => {
46
55
  const columns = options?.columns ?? 80;
56
+ const colorProfile = options?.colorProfile ?? colorProfileFromLevel(detectColorLevel());
47
57
 
48
58
  // Create a standalone root node — no stdout, stdin, or terminal bindings
49
59
  const rootNode: DOMElement = createNode("ink-root");
@@ -62,7 +72,10 @@ export const renderToString = (node: ReactNode, options?: RenderToStringOptions)
62
72
  };
63
73
 
64
74
  rootNode.onImmediateRender = () => {
65
- const { staticOutput } = renderer(rootNode, false);
75
+ const { staticScreen } = renderFrame(rootNode, false, { colorProfile });
76
+ const staticOutput = staticScreen
77
+ ? `${serializeScreen(staticScreen, { colorProfile, styles: colorProfile !== "none" })}\n`
78
+ : "";
66
79
  if (staticOutput && staticOutput !== "\n") {
67
80
  capturedStaticOutput += staticOutput;
68
81
  }
@@ -99,7 +112,10 @@ export const renderToString = (node: ReactNode, options?: RenderToStringOptions)
99
112
 
100
113
  // Yoga layout has already been calculated by onComputeLayout during commit.
101
114
  // Render the DOM tree to a string — this captures the dynamic (non-static) output.
102
- const { output } = renderer(rootNode, false);
115
+ const { screen } = renderFrame(rootNode, false, { colorProfile });
116
+ const output = screen
117
+ ? serializeScreen(screen, { colorProfile, styles: colorProfile !== "none" })
118
+ : "";
103
119
 
104
120
  // Tear down: unmount the tree so the reconciler cleans up child nodes
105
121
  // and runs effect cleanup functions. The reconciler detaches removed
@@ -116,9 +132,8 @@ export const renderToString = (node: ReactNode, options?: RenderToStringOptions)
116
132
  new Error(String(uncaughtError));
117
133
  }
118
134
 
119
- // The renderer appends a trailing newline to static output for terminal
120
- // rendering (so dynamic output starts on a fresh line). Strip it here
121
- // so renderToString returns clean output.
135
+ // Static terminal output ends with a newline so dynamic output starts on a
136
+ // fresh line. Strip it here so renderToString returns clean output.
122
137
  const normalizedStaticOutput = capturedStaticOutput.endsWith("\n")
123
138
  ? capturedStaticOutput.slice(0, -1)
124
139
  : capturedStaticOutput;
package/src/render.ts CHANGED
@@ -1,11 +1,12 @@
1
- import process from "node:process";
2
1
  import { Stream, type Writable } from "node:stream";
3
2
 
4
3
  import type { ReactNode } from "react";
5
4
 
6
- import { Ink, type Options as InkOptions, type RenderMetrics } from "./ink.tsx";
7
- import { instances } from "./instances.ts";
8
- import { type KittyKeyboardOptions } from "./kitty-keyboard.ts";
5
+ import type { ClipboardSelection, TerminalProgressState } from "#/ansi/osc.ts";
6
+ import { createInk, type Ink, type Options as InkOptions, type RenderMetrics } from "#/ink.tsx";
7
+ import { instances } from "#/instances.ts";
8
+ import { type KittyKeyboardOptions } from "#/kitty-keyboard.ts";
9
+ import { type ColorProfile } from "#/screen/color-profile.ts";
9
10
 
10
11
  export type RenderOptions = {
11
12
  /**
@@ -89,12 +90,12 @@ export type RenderOptions = {
89
90
  maxFps?: number;
90
91
 
91
92
  /**
92
- Enable incremental rendering mode which only updates changed lines instead of redrawing the entire output.
93
- This can reduce flickering and improve performance for frequently updating UIs.
94
-
95
- @default false
93
+ Override the output color profile for this render instance. When omitted,
94
+ the profile follows the capabilities of `stdout` and capability upgrades
95
+ redraw the live frame. Static content already written to scrollback is not
96
+ replayed or recolored.
96
97
  */
97
- incrementalRendering?: boolean;
98
+ colorProfile?: ColorProfile;
98
99
 
99
100
  /**
100
101
  Enable React Concurrent Rendering mode.
@@ -122,7 +123,7 @@ export type RenderOptions = {
122
123
  /**
123
124
  Override automatic interactive mode detection.
124
125
 
125
- By default, Ink detects whether the environment is interactive based on CI detection (via [`is-in-ci`](https://github.com/sindresorhus/is-in-ci)) and `stdout.isTTY`. Most users should not need to set this.
126
+ By default, Ink detects whether the environment is interactive based on CI detection (the `CI` environment variable) and `stdout.isTTY`. Most users should not need to set this.
126
127
 
127
128
  When non-interactive, Ink disables ANSI erase sequences, cursor manipulation, synchronized output, resize handling, and kitty keyboard auto-detection, writing only the final frame at unmount.
128
129
 
@@ -206,6 +207,12 @@ export type Instance = {
206
207
  Clear output.
207
208
  */
208
209
  clear: () => void;
210
+
211
+ /** Copy text through the renderer-owned terminal session. */
212
+ copyToClipboard: (text: string, selection?: ClipboardSelection) => boolean;
213
+
214
+ /** Update terminal-native progress through the renderer-owned session. */
215
+ setProgress: (state: TerminalProgressState, value?: number) => boolean;
209
216
  };
210
217
 
211
218
  /**
@@ -220,26 +227,27 @@ export const render = (node: ReactNode, options?: Writable | RenderOptions): Ins
220
227
  exitOnCtrlC: true,
221
228
  patchConsole: true,
222
229
  maxFps: 30,
223
- incrementalRendering: false,
224
230
  concurrent: false,
225
231
  alternateScreen: false,
226
232
  ...getOptions(options),
227
233
  };
228
234
 
229
- const instance: Ink = getInstance(inkOptions.stdout, () => new Ink(inkOptions));
235
+ const instance: Ink = getInstance(inkOptions.stdout, () => createInk(inkOptions));
230
236
  instance.render(node);
231
237
 
232
238
  return {
233
- rerender: instance.render.bind(instance),
239
+ rerender: instance.render,
234
240
  unmount() {
235
241
  instance.unmount();
236
242
  },
237
- waitUntilExit: instance.waitUntilExit.bind(instance),
238
- waitUntilRenderFlush: instance.waitUntilRenderFlush.bind(instance),
243
+ waitUntilExit: instance.waitUntilExit,
244
+ waitUntilRenderFlush: instance.waitUntilRenderFlush,
239
245
  cleanup() {
240
246
  instance.unmount();
241
247
  },
242
- clear: instance.clear.bind(instance),
248
+ clear: instance.clear,
249
+ copyToClipboard: instance.copyToClipboard,
250
+ setProgress: instance.setProgress,
243
251
  };
244
252
  };
245
253