@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,13 +1,11 @@
1
1
  /** @jsxImportSource react */
2
- import { forwardRef, useContext, type PropsWithChildren } from "react";
2
+ import { useContext, type PropsWithChildren, type Ref } from "react";
3
3
 
4
- import { type DOMElement } from "../dom.ts";
5
- import { type Styles } from "../styles.ts";
6
- import { type Except } from "../types.ts";
7
- import { accessibilityContext } from "./AccessibilityContext.ts";
8
- import { backgroundContext } from "./BackgroundContext.ts";
4
+ import { accessibilityContext } from "#/components/AccessibilityContext.ts";
5
+ import { type DOMElement } from "#/dom.ts";
6
+ import { type Styles } from "#/styles.ts";
9
7
 
10
- export type Props = Except<Styles, "textWrap"> & {
8
+ export type Props = Omit<Styles, "textWrap"> & {
11
9
  /**
12
10
  A label for the element for screen readers.
13
11
  */
@@ -60,58 +58,43 @@ export type Props = Except<Styles, "textWrap"> & {
60
58
  /**
61
59
  `<Box>` is an essential Ink component to build your layout. It's like `<div style="display: flex">` in the browser.
62
60
  */
63
- export const Box = forwardRef<DOMElement, PropsWithChildren<Props>>(
64
- (
65
- {
66
- children,
67
- backgroundColor,
68
- "aria-label": ariaLabel,
69
- "aria-hidden": ariaHidden,
70
- "aria-role": role,
71
- "aria-state": ariaState,
72
- ...style
73
- },
74
- ref,
75
- ) => {
76
- const { isScreenReaderEnabled } = useContext(accessibilityContext);
77
- const label = ariaLabel ? <ink-text>{ariaLabel}</ink-text> : undefined;
78
- if (isScreenReaderEnabled && ariaHidden) {
79
- return null;
80
- }
61
+ export function Box({
62
+ children,
63
+ ref,
64
+ backgroundColor,
65
+ "aria-label": ariaLabel,
66
+ "aria-hidden": ariaHidden,
67
+ "aria-role": role,
68
+ "aria-state": ariaState,
69
+ ...style
70
+ }: PropsWithChildren<Props> & { readonly ref?: Ref<DOMElement> }) {
71
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
72
+ const label = ariaLabel ? <ink-text>{ariaLabel}</ink-text> : undefined;
73
+ if (isScreenReaderEnabled && ariaHidden) {
74
+ return null;
75
+ }
81
76
 
82
- const boxElement = (
83
- <ink-box
84
- ref={ref}
85
- style={{
86
- flexWrap: "nowrap",
87
- flexDirection: "row",
88
- flexGrow: 0,
89
- flexShrink: 1,
90
- ...style,
91
- backgroundColor,
92
- overflowX: style.overflowX ?? style.overflow ?? "visible",
93
- overflowY: style.overflowY ?? style.overflow ?? "visible",
94
- }}
95
- internal_accessibility={{
96
- role,
97
- state: ariaState,
98
- }}
99
- >
100
- {isScreenReaderEnabled && label ? label : children}
101
- </ink-box>
102
- );
77
+ const boxElement = (
78
+ <ink-box
79
+ ref={ref}
80
+ style={{
81
+ flexWrap: "nowrap",
82
+ flexDirection: "row",
83
+ flexGrow: 0,
84
+ flexShrink: 1,
85
+ ...style,
86
+ backgroundColor,
87
+ overflowX: style.overflowX ?? style.overflow ?? "visible",
88
+ overflowY: style.overflowY ?? style.overflow ?? "visible",
89
+ }}
90
+ internal_accessibility={{
91
+ role,
92
+ state: ariaState,
93
+ }}
94
+ >
95
+ {isScreenReaderEnabled && label ? label : children}
96
+ </ink-box>
97
+ );
103
98
 
104
- // If this Box has a background color, provide it to children via context
105
- if (backgroundColor) {
106
- return (
107
- <backgroundContext.Provider value={backgroundColor}>
108
- {boxElement}
109
- </backgroundContext.Provider>
110
- );
111
- }
112
-
113
- return boxElement;
114
- },
115
- );
116
-
117
- Box.displayName = "Box";
99
+ return boxElement;
100
+ }
@@ -1,6 +1,6 @@
1
1
  import { createContext } from "react";
2
2
 
3
- import { type CursorPosition } from "../log-update.ts";
3
+ import { type CursorPosition } from "#/cursor-position.ts";
4
4
 
5
5
  export type Props = {
6
6
  /**
@@ -1,7 +1,7 @@
1
1
  /** @jsxImportSource react */
2
2
  import { PureComponent, type ReactNode } from "react";
3
3
 
4
- import { ErrorOverview } from "./ErrorOverview.tsx";
4
+ import { ErrorOverview } from "#/components/ErrorOverview.tsx";
5
5
 
6
6
  type Props = {
7
7
  readonly children: ReactNode;
@@ -1,11 +1,12 @@
1
1
  /** @jsxImportSource react */
2
- import * as fs from "node:fs";
2
+
3
+ import { existsSync, readFileSync } from "node:fs";
3
4
  import { cwd } from "node:process";
4
5
 
5
- import { codeExcerpt, type CodeExcerpt } from "../code-excerpt.ts";
6
- import { parseStackLine } from "../parse-stack-line.ts";
7
- import { Box } from "./Box.tsx";
8
- import { Text } from "./Text.tsx";
6
+ import { codeExcerpt, type CodeExcerpt } from "#/code-excerpt.ts";
7
+ import { Box } from "#/components/Box.tsx";
8
+ import { Text } from "#/components/Text.tsx";
9
+ import { parseStackLine } from "#/parse-stack-line.ts";
9
10
 
10
11
  // Error's source file is reported as file:///home/user/file.js
11
12
  // This function removes the file://[cwd] part
@@ -25,8 +26,8 @@ export function ErrorOverview({ error }: Props) {
25
26
  let lineWidth = 0;
26
27
  const stackLineCounts = new Map<string, number>();
27
28
 
28
- if (filePath && origin?.line && fs.existsSync(filePath)) {
29
- const sourceCode = fs.readFileSync(filePath, "utf8");
29
+ if (filePath && origin?.line && existsSync(filePath)) {
30
+ const sourceCode = readFileSync(filePath, "utf8");
30
31
  excerpt = codeExcerpt(sourceCode, origin.line);
31
32
 
32
33
  if (excerpt) {
@@ -0,0 +1,56 @@
1
+ /** @jsxImportSource react */
2
+ import { type ReactNode } from "react";
3
+
4
+ import { link } from "#/ansi/escapes.ts";
5
+ import { detectHyperlinkSupport } from "#/capabilities/detect.ts";
6
+ import { Text, type Props as TextProps } from "#/components/Text.tsx";
7
+ import { Transform } from "#/components/Transform.tsx";
8
+ import { useStdout } from "#/hooks/use-stdout.ts";
9
+
10
+ export type Props = Omit<TextProps, "children"> & {
11
+ /**
12
+ The URL the hyperlink points to.
13
+ */
14
+ readonly url: string;
15
+
16
+ /**
17
+ When the terminal does not support OSC 8 hyperlinks, append the URL in
18
+ parentheses after the text so it stays reachable. Set to `false` to render
19
+ the text alone.
20
+
21
+ @default true
22
+ */
23
+ readonly fallback?: boolean;
24
+
25
+ readonly children?: ReactNode;
26
+ };
27
+
28
+ /**
29
+ A clickable OSC 8 hyperlink — the counterpart to the router's `<Link>`, which
30
+ navigates between screens. On terminals without hyperlink support it falls
31
+ back to `text (url)`.
32
+
33
+ ```tsx
34
+ <Hyperlink url="https://example.com">Documentation</Hyperlink>
35
+ ```
36
+ */
37
+ export function Hyperlink({ url, fallback = true, children, ...textProps }: Props) {
38
+ const { stdout } = useStdout();
39
+
40
+ if (!detectHyperlinkSupport(stdout)) {
41
+ return (
42
+ <Text {...textProps}>
43
+ {children}
44
+ {fallback ? <Text dimColor> ({url})</Text> : null}
45
+ </Text>
46
+ );
47
+ }
48
+
49
+ // OSC 8 sequences are zero-width, so wrapping after layout is safe. The
50
+ // transform runs per output line, giving every line its own complete pair.
51
+ return (
52
+ <Transform transform={(text) => link(text, url)}>
53
+ <Text {...textProps}>{children}</Text>
54
+ </Transform>
55
+ );
56
+ }
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource react */
2
- import { Box } from "./Box.tsx";
2
+ import { Box } from "#/components/Box.tsx";
3
3
 
4
4
  /**
5
5
  A flexible space that expands along the major axis of its containing layout.
@@ -1,7 +1,7 @@
1
1
  /** @jsxImportSource react */
2
2
  import { useMemo, useState, useLayoutEffect, type ReactNode } from "react";
3
3
 
4
- import { type Styles } from "../styles.ts";
4
+ import { type Styles } from "#/styles.ts";
5
5
 
6
6
  export type Props<T> = {
7
7
  /**
@@ -1,5 +1,3 @@
1
- import process from "node:process";
2
-
3
1
  import { createContext } from "react";
4
2
 
5
3
  export type Props = {
@@ -1,5 +1,4 @@
1
1
  import { EventEmitter } from "node:events";
2
- import process from "node:process";
3
2
 
4
3
  import { createContext } from "react";
5
4
 
@@ -1,8 +1,6 @@
1
- import process from "node:process";
2
-
3
1
  import { createContext } from "react";
4
2
 
5
- import type { OutputStream } from "../stream.ts";
3
+ import type { OutputStream } from "#/stream.ts";
6
4
 
7
5
  export type Props = {
8
6
  /**
@@ -0,0 +1,25 @@
1
+ import { createContext } from "react";
2
+
3
+ import type { ClipboardSelection, TerminalProgressState } from "#/ansi/osc.ts";
4
+
5
+ export type TerminalOsc = {
6
+ readonly publishProgress: (owner: symbol, state: TerminalProgressState, value?: number) => void;
7
+ readonly copyToClipboard: (text: string, selection?: ClipboardSelection) => void;
8
+ readonly publishTitle: (owner: symbol, title?: string) => void;
9
+ readonly setWorkingDirectory: (directory: URL | string) => void;
10
+ readonly notify: (title: string) => void;
11
+ readonly setPointerShape: (shape: string) => void;
12
+ };
13
+
14
+ const noop = () => {};
15
+
16
+ export const TerminalOscContext = createContext<TerminalOsc>({
17
+ publishProgress: noop,
18
+ copyToClipboard: noop,
19
+ publishTitle: noop,
20
+ setWorkingDirectory: noop,
21
+ notify: noop,
22
+ setPointerShape: noop,
23
+ });
24
+
25
+ TerminalOscContext.displayName = "InternalTerminalOscContext";
@@ -1,12 +1,11 @@
1
1
  /** @jsxImportSource react */
2
2
  import { useContext, type ReactNode } from "react";
3
3
 
4
- import { chalk, type ForegroundColorName } from "../ansi/chalk.ts";
5
- import { colorize } from "../colorize.ts";
6
- import { type Styles } from "../styles.ts";
7
- import { type LiteralUnion } from "../types.ts";
8
- import { accessibilityContext } from "./AccessibilityContext.ts";
9
- import { backgroundContext } from "./BackgroundContext.ts";
4
+ import type { Paint } from "#/color/paint.ts";
5
+ import { accessibilityContext } from "#/components/AccessibilityContext.ts";
6
+ import { cellAttributes } from "#/screen/cell.ts";
7
+ import { parseSemanticColor } from "#/semantic-text-style.ts";
8
+ import { type Styles } from "#/styles.ts";
10
9
 
11
10
  export type Props = {
12
11
  /**
@@ -22,12 +21,12 @@ export type Props = {
22
21
  /**
23
22
  Change text color. Ink uses Chalk under the hood, so all its functionality is supported.
24
23
  */
25
- readonly color?: LiteralUnion<ForegroundColorName, string>;
24
+ readonly color?: Paint;
26
25
 
27
26
  /**
28
27
  Same as `color`, but for the background.
29
28
  */
30
- readonly backgroundColor?: LiteralUnion<ForegroundColorName, string>;
29
+ readonly backgroundColor?: Paint;
31
30
 
32
31
  /**
33
32
  Dim the color (make it less bright).
@@ -85,49 +84,27 @@ export function Text({
85
84
  "aria-hidden": ariaHidden = false,
86
85
  }: Props) {
87
86
  const { isScreenReaderEnabled } = useContext(accessibilityContext);
88
- const inheritedBackgroundColor = useContext(backgroundContext);
89
87
  const childrenOrAriaLabel = isScreenReaderEnabled && ariaLabel ? ariaLabel : children;
90
88
 
91
89
  if (childrenOrAriaLabel === undefined || childrenOrAriaLabel === null) {
92
90
  return null;
93
91
  }
94
92
 
95
- const transform = (text: string): string => {
96
- if (dimColor) {
97
- text = chalk.dim(text);
98
- }
99
-
100
- if (color) {
101
- text = colorize(text, color, "foreground");
102
- }
103
-
104
- // Use explicit backgroundColor if provided, otherwise use inherited from parent Box
105
- const effectiveBackgroundColor = backgroundColor ?? inheritedBackgroundColor;
106
- if (effectiveBackgroundColor) {
107
- text = colorize(text, effectiveBackgroundColor, "background");
108
- }
109
-
110
- if (bold) {
111
- text = chalk.bold(text);
112
- }
113
-
114
- if (italic) {
115
- text = chalk.italic(text);
116
- }
117
-
118
- if (underline) {
119
- text = chalk.underline(text);
120
- }
121
-
122
- if (strikethrough) {
123
- text = chalk.strikethrough(text);
124
- }
125
-
126
- if (inverse) {
127
- text = chalk.inverse(text);
128
- }
129
-
130
- return text;
93
+ const foreground = typeof color === "object" ? color : parseSemanticColor(color);
94
+ const background =
95
+ typeof backgroundColor === "object" ? backgroundColor : parseSemanticColor(backgroundColor);
96
+ const semanticStyle = {
97
+ ...(foreground ? { foreground } : {}),
98
+ ...(background ? { background } : {}),
99
+ ...(color === "" ? { resetForeground: true } : {}),
100
+ ...(backgroundColor === "" ? { resetBackground: true } : {}),
101
+ ...(underline ? { underline: "single" as const } : {}),
102
+ attributes:
103
+ (dimColor ? cellAttributes.faint : 0) +
104
+ (bold ? cellAttributes.bold : 0) +
105
+ (italic ? cellAttributes.italic : 0) +
106
+ (strikethrough ? cellAttributes.strikethrough : 0) +
107
+ (inverse ? cellAttributes.inverse : 0),
131
108
  };
132
109
 
133
110
  if (isScreenReaderEnabled && ariaHidden) {
@@ -137,7 +114,7 @@ export function Text({
137
114
  return (
138
115
  <ink-text
139
116
  style={{ flexGrow: 0, flexShrink: 1, flexDirection: "row", textWrap: wrap }}
140
- internal_transform={transform}
117
+ internal_textStyle={semanticStyle}
141
118
  >
142
119
  {childrenOrAriaLabel}
143
120
  </ink-text>
@@ -1,7 +1,7 @@
1
1
  /** @jsxImportSource react */
2
2
  import { useContext, type ReactNode } from "react";
3
3
 
4
- import { accessibilityContext } from "./AccessibilityContext.ts";
4
+ import { accessibilityContext } from "#/components/AccessibilityContext.ts";
5
5
 
6
6
  export type Props = {
7
7
  /**
@@ -10,7 +10,7 @@ export type Props = {
10
10
  readonly accessibilityLabel?: string;
11
11
 
12
12
  /**
13
- Function that transforms children output. It accepts children and must return transformed children as well. Note that when children use `<Text>` styling props (e.g. `color`, `bold`), the string will contain ANSI escape codes.
13
+ Compatibility function that transforms the ANSI serialization of this subtree.
14
14
  */
15
15
  readonly transform: (children: string, index: number) => string;
16
16
 
@@ -1,4 +1,4 @@
1
- import { ansiEscapes } from "./ansi/escapes.ts";
1
+ import { ansiEscapes } from "#/ansi/escapes.ts";
2
2
 
3
3
  export type CursorPosition = {
4
4
  x: number;
package/src/devtools.ts CHANGED
@@ -1,41 +1,101 @@
1
- /* eslint-disable import-x/order */
1
+ import { setTimeout } from "node:timers";
2
2
 
3
- // eslint-disable-next-line import-x/no-unassigned-import
4
- import "./devtools-window-polyfill.ts";
5
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6
- // @ts-expect-error
7
- import devtools from "react-devtools-core";
8
- import WebSocket from "ws";
3
+ // Node 22.4+ ships a stable browser-compatible WebSocket client, so no `ws`
4
+ // dependency is needed. react-devtools-core resolves the constructor via the
5
+ // `window`/`ws` globals defined here.
6
+ Object.defineProperties(globalThis, {
7
+ ws: { value: WebSocket },
8
+ window: { value: globalThis },
9
+ self: { value: globalThis },
10
+ });
9
11
 
10
- const isDevToolsReachable = async (): Promise<boolean> =>
11
- new Promise((resolve) => {
12
+ // Filter out Ink's internal components from devtools for a cleaner view.
13
+ // Also, since `react-devtools-shared` package isn't published on npm, we can't
14
+ // use its types, that's why there are hard-coded values in `type` fields below.
15
+ // See https://github.com/facebook/react/blob/edf6eac8a181860fd8a2d076a43806f1237495a1/packages/react-devtools-shared/src/types.js#L24
16
+ Object.defineProperty(globalThis, "__REACT_DEVTOOLS_COMPONENT_FILTERS__", {
17
+ value: [
18
+ {
19
+ // ComponentFilterElementType
20
+ type: 1,
21
+ // ElementTypeHostComponent
22
+ value: 7,
23
+ isEnabled: true,
24
+ },
25
+ {
26
+ // ComponentFilterDisplayName
27
+ type: 2,
28
+ value: "InternalApp",
29
+ isEnabled: true,
30
+ isValid: true,
31
+ },
32
+ {
33
+ // ComponentFilterDisplayName
34
+ type: 2,
35
+ value: "InternalAppContext",
36
+ isEnabled: true,
37
+ isValid: true,
38
+ },
39
+ {
40
+ // ComponentFilterDisplayName
41
+ type: 2,
42
+ value: "InternalStdoutContext",
43
+ isEnabled: true,
44
+ isValid: true,
45
+ },
46
+ {
47
+ // ComponentFilterDisplayName
48
+ type: 2,
49
+ value: "InternalStderrContext",
50
+ isEnabled: true,
51
+ isValid: true,
52
+ },
53
+ {
54
+ // ComponentFilterDisplayName
55
+ type: 2,
56
+ value: "InternalStdinContext",
57
+ isEnabled: true,
58
+ isValid: true,
59
+ },
60
+ {
61
+ // ComponentFilterDisplayName
62
+ type: 2,
63
+ value: "InternalFocusContext",
64
+ isEnabled: true,
65
+ isValid: true,
66
+ },
67
+ ],
68
+ });
69
+
70
+ const isDevToolsReachable = () =>
71
+ new Promise<boolean>((resolve) => {
12
72
  const socket = new WebSocket("ws://localhost:8097");
13
73
 
74
+ const settle = (reachable: boolean) => {
75
+ clearTimeout(timeout);
76
+ socket.close();
77
+ resolve(reachable);
78
+ };
79
+
14
80
  const timeout = setTimeout(() => {
15
- socket.terminate();
16
- resolve(false);
81
+ settle(false);
17
82
  }, 2000);
18
83
  // Don't let the timeout keep the process alive on its own
19
84
  timeout.unref();
20
85
 
21
- socket.on("open", () => {
22
- clearTimeout(timeout);
23
- socket.terminate();
24
- resolve(true);
86
+ socket.addEventListener("open", () => {
87
+ settle(true);
25
88
  });
26
-
27
- socket.on("error", () => {
28
- clearTimeout(timeout);
29
- socket.terminate();
30
- resolve(false);
89
+ socket.addEventListener("error", () => {
90
+ settle(false);
31
91
  });
32
92
  });
33
93
 
34
94
  if (await isDevToolsReachable()) {
35
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
36
- (devtools as any).initialize();
37
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
38
- (devtools as any).connectToDevTools();
95
+ // @ts-expect-error: react-devtools-core is not typed
96
+ const devtools = await import("react-devtools-core");
97
+ devtools.initialize();
98
+ devtools.connectToDevTools();
39
99
  } else {
40
100
  console.warn(
41
101
  "SIGIL_DEV is set to true, but the React DevTools server is not running. Start it with:\n\n$ npx react-devtools\n",
package/src/dom.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { measureText } from "./measure-text.ts";
2
- import { type OutputTransformer } from "./render-node-to-output.ts";
3
- import { squashTextNodes } from "./squash-text-nodes.ts";
4
- import { type Styles } from "./styles.ts";
5
- import { wrapText } from "./wrap-text.ts";
6
- import { Yoga, type Node as YogaNode } from "./yoga/index.ts";
1
+ import { measureText } from "#/measure-text.ts";
2
+ import type { SemanticTextStyle } from "#/semantic-text-style.ts";
3
+ import { squashTextNodes } from "#/squash-text-nodes.ts";
4
+ import { type Styles } from "#/styles.ts";
5
+ import { type AnsiTransformer } from "#/transform-adapter.ts";
6
+ import { wrapText } from "#/wrap-text.ts";
7
+ import { Yoga, type Node as YogaNode } from "#/yoga/index.ts";
7
8
 
8
9
  type InkNode = {
9
10
  parentNode: DOMElement | undefined;
@@ -24,7 +25,9 @@ export type DOMElement = {
24
25
  nodeName: ElementNames;
25
26
  attributes: Record<string, DOMNodeAttribute>;
26
27
  childNodes: DOMNode[];
27
- internal_transform?: OutputTransformer;
28
+ internal_ansi?: boolean;
29
+ internal_transform?: AnsiTransformer;
30
+ internal_textStyle?: SemanticTextStyle;
28
31
 
29
32
  internal_accessibility?: {
30
33
  role?:
@@ -101,7 +104,7 @@ export const createNode = (nodeName: ElementNames): DOMElement => {
101
104
  };
102
105
 
103
106
  if (nodeName === "ink-text") {
104
- node.yogaNode?.setMeasureFunc(measureTextNode.bind(null, node));
107
+ node.yogaNode?.setMeasureFunc((width) => measureTextNode(node, width));
105
108
  }
106
109
 
107
110
  return node;
package/src/env.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Every modern CI provider sets CI=1 (or CI=true), so any non-empty value
2
+ export const isInCi = Boolean(process.env.CI);
3
+
4
+ export const isSigilDev = process.env.SIGIL_DEV === "true";
5
+
6
+ export const isScreenReader = process.env.SIGIL_SCREEN_READER === "true";
7
+
8
+ export const isWindows = process.platform === "win32";
9
+
10
+ export const isMacos = process.platform === "darwin";
11
+
12
+ export const isTty = (stream: NodeJS.ReadableStream) => "isTTY" in stream && stream.isTTY === true;
@@ -1,4 +1,4 @@
1
- import { Yoga, type Node as YogaNode } from "./yoga/index.ts";
1
+ import { Yoga, type Node as YogaNode } from "#/yoga/index.ts";
2
2
 
3
3
  export const getMaxWidth = (yogaNode: YogaNode) => {
4
4
  return (
package/src/global.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { type ReactNode, type Key, type Ref } from "react";
2
2
 
3
- import { type DOMElement } from "./dom.ts";
4
- import { type Styles } from "./styles.ts";
5
- import { type Except } from "./types.ts";
3
+ import { type DOMElement } from "#/dom.ts";
4
+ import type { SemanticTextStyle } from "#/semantic-text-style.ts";
5
+ import { type Styles } from "#/styles.ts";
6
6
 
7
7
  declare module "react" {
8
8
  namespace JSX {
@@ -20,7 +20,7 @@ declare namespace Ink {
20
20
  children?: ReactNode;
21
21
  key?: Key;
22
22
  ref?: Ref<DOMElement>;
23
- style?: Except<Styles, "textWrap">;
23
+ style?: Omit<Styles, "textWrap">;
24
24
  internal_accessibility?: DOMElement["internal_accessibility"];
25
25
  };
26
26
 
@@ -30,7 +30,9 @@ declare namespace Ink {
30
30
  style?: Styles;
31
31
 
32
32
  // eslint-disable-next-line @typescript-eslint/naming-convention
33
+ internal_ansi?: boolean;
33
34
  internal_transform?: (children: string, index: number) => string;
35
+ internal_textStyle?: SemanticTextStyle;
34
36
  internal_accessibility?: DOMElement["internal_accessibility"];
35
37
  };
36
38
  }