@alchemy.run/sigil 0.0.0-alpha.2 → 0.0.0-alpha.4

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 (107) hide show
  1. package/README.md +2 -4
  2. package/THIRD_PARTY_NOTICES.md +31 -22
  3. package/dist/ansi.d.ts +17 -14
  4. package/dist/ansi.js +7 -2
  5. package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  6. package/dist/index.d.ts +125 -288
  7. package/dist/index.js +2460 -2864
  8. package/dist/{truncate-CBiyyZzw.js → truncate-Cr6xVFMa.js} +447 -273
  9. package/package.json +3 -9
  10. package/src/ansi/chalk.ts +8 -51
  11. package/src/ansi/cursor.ts +2 -4
  12. package/src/ansi/east-asian-width.ts +44 -0
  13. package/src/ansi/escapes.ts +4 -26
  14. package/src/ansi/index.ts +12 -14
  15. package/src/ansi/sgr.ts +2 -5
  16. package/src/ansi/slice.ts +1 -1
  17. package/src/ansi/string-width.ts +91 -206
  18. package/src/ansi/strip.ts +13 -33
  19. package/src/ansi/supports-color.ts +8 -14
  20. package/src/ansi/tokenize.ts +94 -166
  21. package/src/ansi/truncate.ts +5 -3
  22. package/src/ansi/wrap.ts +13 -14
  23. package/src/ansi-tokenizer.ts +1 -1
  24. package/src/colorize.ts +1 -1
  25. package/src/components/App.tsx +14 -14
  26. package/src/components/BackgroundContext.ts +2 -2
  27. package/src/components/Box.tsx +51 -59
  28. package/src/components/CursorContext.ts +1 -1
  29. package/src/components/ErrorBoundary.tsx +2 -1
  30. package/src/components/ErrorOverview.tsx +9 -7
  31. package/src/components/Newline.tsx +1 -0
  32. package/src/components/Spacer.tsx +2 -1
  33. package/src/components/Static.tsx +2 -1
  34. package/src/components/StderrContext.ts +0 -2
  35. package/src/components/StdinContext.ts +0 -1
  36. package/src/components/StdoutContext.ts +1 -3
  37. package/src/components/Text.tsx +7 -6
  38. package/src/components/Transform.tsx +2 -1
  39. package/src/cursor-position.ts +1 -1
  40. package/src/devtools.ts +84 -24
  41. package/src/dom.ts +7 -7
  42. package/src/env.ts +12 -0
  43. package/src/get-max-width.ts +1 -1
  44. package/src/global.d.ts +3 -4
  45. package/src/{boxes.ts → glyphs.ts} +17 -18
  46. package/src/hooks/use-animation.ts +1 -1
  47. package/src/hooks/use-app.ts +1 -1
  48. package/src/hooks/use-box-metrics.ts +1 -1
  49. package/src/hooks/use-cursor.ts +2 -2
  50. package/src/hooks/use-focus-manager.ts +1 -1
  51. package/src/hooks/use-focus.ts +5 -6
  52. package/src/hooks/use-input.ts +4 -4
  53. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  54. package/src/hooks/use-paste.ts +2 -2
  55. package/src/hooks/use-stderr.ts +1 -1
  56. package/src/hooks/use-stdin.ts +1 -1
  57. package/src/hooks/use-stdout.ts +1 -1
  58. package/src/hooks/use-window-size.ts +2 -2
  59. package/src/index.ts +44 -44
  60. package/src/ink.tsx +536 -720
  61. package/src/input-parser.ts +1 -1
  62. package/src/instances.ts +1 -1
  63. package/src/kitty-keyboard.ts +128 -0
  64. package/src/log-update.ts +4 -4
  65. package/src/measure-element.ts +1 -1
  66. package/src/measure-text.ts +2 -2
  67. package/src/output.ts +4 -4
  68. package/src/parse-keypress.ts +4 -5
  69. package/src/parse-stack-line.ts +0 -1
  70. package/src/patch-console.ts +44 -0
  71. package/src/reconciler.ts +13 -60
  72. package/src/render-background.ts +3 -3
  73. package/src/render-border.ts +6 -6
  74. package/src/render-node-to-output.ts +13 -11
  75. package/src/render-to-string.ts +4 -4
  76. package/src/render.ts +9 -10
  77. package/src/renderer.ts +3 -3
  78. package/src/sanitize-ansi.ts +1 -1
  79. package/src/signal-exit.ts +4 -5
  80. package/src/squash-text-nodes.ts +2 -2
  81. package/src/stream.ts +2 -4
  82. package/src/styles.ts +6 -6
  83. package/src/terminal-size.ts +9 -8
  84. package/src/types.ts +1 -6
  85. package/src/utils.ts +2 -2
  86. package/src/wrap-text.ts +4 -4
  87. package/src/yoga/config.ts +2 -2
  88. package/src/yoga/core/absoluteLayout.ts +15 -15
  89. package/src/yoga/core/baseline.ts +3 -3
  90. package/src/yoga/core/cache.ts +4 -4
  91. package/src/yoga/core/calculateLayout.ts +33 -27
  92. package/src/yoga/core/config.ts +1 -1
  93. package/src/yoga/core/flexLine.ts +3 -3
  94. package/src/yoga/core/helpers.ts +3 -3
  95. package/src/yoga/core/layoutResults.ts +4 -4
  96. package/src/yoga/core/node.ts +16 -16
  97. package/src/yoga/core/pixelGrid.ts +4 -4
  98. package/src/yoga/core/style.ts +11 -11
  99. package/src/yoga/core/types.ts +2 -2
  100. package/src/yoga/index.ts +6 -6
  101. package/src/yoga/node.ts +8 -8
  102. package/src/ansi/widest-line.ts +0 -12
  103. package/src/auto-bind.ts +0 -41
  104. package/src/devtools-window-polyfill.ts +0 -73
  105. package/src/indent-string.ts +0 -16
  106. package/src/is-in-ci.ts +0 -7
  107. package/src/write-synchronized.ts +0 -9
@@ -1,4 +1,4 @@
1
- import { ESC, pasteStart, pasteEnd } from "./ansi/escapes.ts";
1
+ import { ESC, pasteStart, pasteEnd } from "#/ansi/escapes.ts";
2
2
 
3
3
  const escape = ESC;
4
4
 
package/src/instances.ts CHANGED
@@ -4,6 +4,6 @@
4
4
  // This map has to be stored in a separate file, because render.js creates instances,
5
5
  // but instance.js should delete itself from the map on unmount
6
6
 
7
- import type { Ink } from "./ink.tsx";
7
+ import type { Ink } from "#/ink.tsx";
8
8
 
9
9
  export const instances = new WeakMap<NodeJS.WritableStream, Ink>();
@@ -1,3 +1,5 @@
1
+ import { kittyQuery } from "#/ansi/escapes.ts";
2
+
1
3
  // Kitty keyboard protocol flags.
2
4
  // @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/
3
5
  export const kittyFlags = {
@@ -55,3 +57,129 @@ export type KittyKeyboardOptions = {
55
57
  // - 'reportAssociatedText' - Report associated text with key events
56
58
  flags?: KittyFlagName[];
57
59
  };
60
+
61
+ const textEncoder = new TextEncoder();
62
+
63
+ const kittyQueryEscapeByte = 0x1b;
64
+ const kittyQueryOpenBracketByte = 0x5b;
65
+ const kittyQueryQuestionMarkByte = 0x3f;
66
+ const kittyQueryLetterByte = 0x75;
67
+ const zeroByte = 0x30;
68
+ const nineByte = 0x39;
69
+
70
+ type KittyQueryResponseMatch = { state: "complete"; endIndex: number } | { state: "partial" };
71
+
72
+ const isDigitByte = (byte: number): boolean => byte >= zeroByte && byte <= nineByte;
73
+
74
+ const matchKittyQueryResponse = (
75
+ buffer: number[],
76
+ startIndex: number,
77
+ ): KittyQueryResponseMatch | undefined => {
78
+ if (
79
+ buffer[startIndex] !== kittyQueryEscapeByte ||
80
+ buffer[startIndex + 1] !== kittyQueryOpenBracketByte ||
81
+ buffer[startIndex + 2] !== kittyQueryQuestionMarkByte
82
+ ) {
83
+ return;
84
+ }
85
+
86
+ let index = startIndex + 3;
87
+ const digitsStartIndex = index;
88
+ while (index < buffer.length && isDigitByte(buffer[index]!)) {
89
+ index++;
90
+ }
91
+
92
+ if (index === digitsStartIndex) {
93
+ return;
94
+ }
95
+
96
+ if (index === buffer.length) {
97
+ return { state: "partial" };
98
+ }
99
+
100
+ if (buffer[index] === kittyQueryLetterByte) {
101
+ return { state: "complete", endIndex: index };
102
+ }
103
+
104
+ return;
105
+ };
106
+
107
+ const hasCompleteKittyQueryResponse = (buffer: number[]): boolean => {
108
+ for (let index = 0; index < buffer.length; index++) {
109
+ const match = matchKittyQueryResponse(buffer, index);
110
+ if (match?.state === "complete") {
111
+ return true;
112
+ }
113
+ }
114
+
115
+ return false;
116
+ };
117
+
118
+ const stripKittyQueryResponsesAndTrailingPartial = (buffer: number[]): number[] => {
119
+ const keptBytes: number[] = [];
120
+ let index = 0;
121
+ while (index < buffer.length) {
122
+ const match = matchKittyQueryResponse(buffer, index);
123
+ if (match?.state === "complete") {
124
+ index = match.endIndex + 1;
125
+ continue;
126
+ }
127
+
128
+ if (match?.state === "partial") {
129
+ break;
130
+ }
131
+
132
+ keptBytes.push(buffer[index]!);
133
+ index++;
134
+ }
135
+
136
+ return keptBytes;
137
+ };
138
+
139
+ // Query the terminal for kitty keyboard protocol support. The CSI ? u query is
140
+ // safe to send to any terminal — unsupporting terminals simply won't respond,
141
+ // and the 200ms timeout handles that. Stdin bytes that aren't part of the
142
+ // protocol response are re-emitted so they aren't lost from the caller's input
143
+ // pipeline. Returns a cancel function; cancelling (or the timeout firing)
144
+ // means `onSupported` never runs.
145
+ export const detectKittySupport = (
146
+ stdin: NodeJS.ReadableStream,
147
+ stdout: { write: (data: string) => unknown },
148
+ onSupported: () => void,
149
+ ): (() => void) => {
150
+ let responseBuffer: number[] = [];
151
+
152
+ const cleanup = (): void => {
153
+ clearTimeout(timer);
154
+ stdin.removeListener("data", onData);
155
+
156
+ // Re-emit any buffered data that wasn't the protocol response.
157
+ // Clear responseBuffer afterwards to make cleanup idempotent.
158
+ const remaining = stripKittyQueryResponsesAndTrailingPartial(responseBuffer);
159
+ responseBuffer = [];
160
+ if (remaining.length > 0) {
161
+ stdin.unshift(Uint8Array.from(remaining));
162
+ }
163
+ };
164
+
165
+ const onData = (data: Uint8Array | string): void => {
166
+ const chunk = typeof data === "string" ? textEncoder.encode(data) : data;
167
+ for (const byte of chunk) {
168
+ responseBuffer.push(byte);
169
+ }
170
+
171
+ if (hasCompleteKittyQueryResponse(responseBuffer)) {
172
+ cleanup();
173
+ onSupported();
174
+ }
175
+ };
176
+
177
+ // Attach listener before writing the query so that synchronous
178
+ // or immediate responses are not missed.
179
+ stdin.on("data", onData);
180
+ const timer = setTimeout(cleanup, 200);
181
+
182
+ stdout.write(kittyQuery);
183
+
184
+ return cleanup;
185
+ };
package/src/log-update.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { cliCursor } from "./ansi/cursor.ts";
2
- import { ansiEscapes } from "./ansi/escapes.ts";
1
+ import { cliCursor } from "#/ansi/cursor.ts";
2
+ import { ansiEscapes } from "#/ansi/escapes.ts";
3
3
  import {
4
4
  type CursorPosition,
5
5
  cursorPositionChanged,
@@ -7,9 +7,9 @@ import {
7
7
  buildCursorOnlySequence,
8
8
  buildReturnToBottomPrefix,
9
9
  hideCursorEscape,
10
- } from "./cursor-position.ts";
10
+ } from "#/cursor-position.ts";
11
11
 
12
- export type { CursorPosition } from "./cursor-position.ts";
12
+ export type { CursorPosition } from "#/cursor-position.ts";
13
13
 
14
14
  export type LogUpdate = {
15
15
  clear: () => void;
@@ -1,4 +1,4 @@
1
- import { type DOMElement } from "./dom.ts";
1
+ import { type DOMElement } from "#/dom.ts";
2
2
 
3
3
  type Output = {
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { widestLine } from "./ansi/widest-line.ts";
2
- import { QuickLru as QuickLRU } from "./quick-lru.ts";
1
+ import { widestLine } from "#/ansi/string-width.ts";
2
+ import { QuickLru as QuickLRU } from "#/quick-lru.ts";
3
3
 
4
4
  const cache = new QuickLRU<string, Output>({ maxSize: 4096 });
5
5
 
package/src/output.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { sliceAnsi } from "./ansi/slice.ts";
2
- import { stringWidth } from "./ansi/string-width.ts";
1
+ import { sliceAnsi } from "#/ansi/slice.ts";
2
+ import { stringWidth } from "#/ansi/string-width.ts";
3
3
  import {
4
4
  type StyledChar,
5
5
  styledCharsFromTokens,
6
6
  styledCharsToString,
7
7
  tokenize,
8
- } from "./ansi/tokenize.ts";
9
- import { type OutputTransformer } from "./render-node-to-output.ts";
8
+ } from "#/ansi/tokenize.ts";
9
+ import { type OutputTransformer } from "#/render-node-to-output.ts";
10
10
 
11
11
  /**
12
12
  "Virtual" output class
@@ -1,6 +1,6 @@
1
1
  // Copied from https://github.com/enquirer/enquirer/blob/36785f3399a41cd61e9d28d1eb9c2fcd73d69b4c/lib/keypress.js
2
- import { DEL, ESC } from "./ansi/escapes.ts";
3
- import { kittyModifiers } from "./kitty-keyboard.ts";
2
+ import { DEL, ESC } from "#/ansi/escapes.ts";
3
+ import { kittyModifiers } from "#/kitty-keyboard.ts";
4
4
 
5
5
  const textDecoder = new TextDecoder();
6
6
 
@@ -396,8 +396,7 @@ export const parseKeypress = (s: Uint8Array | string = ""): ParsedKey => {
396
396
 
397
397
  if (s instanceof Uint8Array) {
398
398
  if (s[0]! > 127 && s[1] === undefined) {
399
- (s[0] as unknown as number) -= 128;
400
- s = ESC + textDecoder.decode(s);
399
+ s = ESC + textDecoder.decode(Uint8Array.of(s[0]! - 128));
401
400
  } else {
402
401
  s = textDecoder.decode(s);
403
402
  }
@@ -499,7 +498,7 @@ export const parseKeypress = (s: Uint8Array | string = ""): ParsedKey => {
499
498
  // the modifier key bitflag and any meaningless "1;" sequence
500
499
  const code = [parts[1], parts[2], parts[4], parts[6]].filter(Boolean).join("");
501
500
 
502
- const modifier = ((parts[3] || parts[5] || 1) as number) - 1;
501
+ const modifier = Number(parts[3] || parts[5] || 1) - 1;
503
502
 
504
503
  // Parse the key modifier
505
504
  key.ctrl = !!(modifier & 4);
@@ -1,6 +1,5 @@
1
1
  // Derived from `stack-utils` (MIT, Isaac Z. Schlueter, James Talmage),
2
2
  // reduced to the `parseLine()` surface Ink's error overview uses.
3
- import process from "node:process";
4
3
 
5
4
  export type ParsedStackLine = {
6
5
  line?: number;
@@ -60,3 +60,47 @@ export const patchConsole = (callback: Callback): Restore => {
60
60
  originalMethods.clear();
61
61
  };
62
62
  };
63
+
64
+ // Intercept direct `write` calls on a stream. The write is swallowed and the
65
+ // decoded chunk is handed to `onData`; write callbacks still fire so callers
66
+ // awaiting flushes don't hang.
67
+ export const patchStreamWrite = (
68
+ stream: NodeJS.WritableStream,
69
+ onData: (data: string) => void,
70
+ ): Restore => {
71
+ // Keep the unbound reference: restore below must reassign the exact
72
+ // original function object, not a bound copy.
73
+ // oxlint-disable-next-line typescript/unbound-method
74
+ const originalWrite = stream.write;
75
+
76
+ const patchedWrite = (
77
+ chunk: unknown,
78
+ encodingOrCallback?: unknown,
79
+ callback?: unknown,
80
+ ): boolean => {
81
+ const data =
82
+ typeof chunk === "string"
83
+ ? chunk
84
+ : chunk instanceof Uint8Array
85
+ ? Buffer.from(chunk).toString()
86
+ : String(chunk);
87
+
88
+ onData(data);
89
+
90
+ const done =
91
+ typeof encodingOrCallback === "function"
92
+ ? encodingOrCallback
93
+ : typeof callback === "function"
94
+ ? callback
95
+ : undefined;
96
+ done?.();
97
+
98
+ return true;
99
+ };
100
+
101
+ stream.write = patchedWrite;
102
+
103
+ return () => {
104
+ stream.write = originalWrite;
105
+ };
106
+ };
package/src/reconciler.ts CHANGED
@@ -1,6 +1,4 @@
1
- import process from "node:process";
2
-
3
- import { createContext, version as reactVersion } from "react";
1
+ import { createContext } from "react";
4
2
  import createReconciler, { type ReactContext } from "react-reconciler";
5
3
  import { DefaultEventPriority, NoEventPriority } from "react-reconciler/constants.js";
6
4
  import * as Scheduler from "scheduler";
@@ -20,27 +18,22 @@ import {
20
18
  type TextNode,
21
19
  type ElementNames,
22
20
  type DOMElement,
23
- } from "./dom.ts";
24
- import { type OutputTransformer } from "./render-node-to-output.ts";
25
- import { styles as applyStyles, type Styles } from "./styles.ts";
26
- import { Yoga } from "./yoga/index.ts";
21
+ } from "#/dom.ts";
22
+ import { isSigilDev } from "#/env.ts";
23
+ import { type OutputTransformer } from "#/render-node-to-output.ts";
24
+ import { styles as applyStyles, type Styles } from "#/styles.ts";
25
+ import { Yoga } from "#/yoga/index.ts";
26
+
27
+ import pkg from "../package.json" with { type: "json" };
27
28
 
28
29
  // We need to conditionally perform devtools connection to avoid
29
30
  // accidentally breaking other third-party code.
30
31
  // See https://github.com/vadimdemedes/ink/issues/384
31
32
  // See https://github.com/vadimdemedes/ink/issues/648
32
- if (process.env["SIGIL_DEV"] === "true") {
33
+ if (isSigilDev) {
33
34
  // Intentionally no warning when the package is missing.
34
35
  // SIGIL_DEV may be set for other reasons; devtools is opt-in via installing the package.
35
- let isDevtoolsInstalled = false;
36
- try {
37
- import.meta.resolve("react-devtools-core");
38
- isDevtoolsInstalled = true;
39
- } catch {}
40
-
41
- if (isDevtoolsInstalled) {
42
- await import("./devtools.ts");
43
- }
36
+ await import("#/devtools.ts").catch(() => {});
44
37
  }
45
38
 
46
39
  type AnyObject = Record<string, unknown>;
@@ -138,46 +131,6 @@ type HostContext = {
138
131
 
139
132
  let currentUpdatePriority = NoEventPriority;
140
133
 
141
- async function loadPackageJson() {
142
- const fs = await import("node:fs");
143
- const content = fs.readFileSync(new URL("../package.json", import.meta.url), "utf8");
144
-
145
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
146
- const parsedContent = JSON.parse(content) as
147
- | {
148
- name?: string;
149
- version?: string;
150
- }
151
- | undefined;
152
-
153
- return {
154
- name: parsedContent?.name,
155
- version: parsedContent?.version,
156
- };
157
- }
158
-
159
- let packageInfo = {
160
- name: "ink",
161
- version: reactVersion,
162
- };
163
-
164
- if (process.env["DEV"] === "true") {
165
- try {
166
- const loaded = await loadPackageJson();
167
- packageInfo = {
168
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
169
- name: loaded.name || packageInfo.name,
170
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
171
- version: loaded.version || packageInfo.version,
172
- };
173
- } catch (error) {
174
- console.warn(
175
- "Failed to load package.json in development mode. Falling back to default renderer metadata.",
176
- error,
177
- );
178
- }
179
- }
180
-
181
134
  export const reconciler = createReconciler<
182
135
  ElementNames,
183
136
  Props,
@@ -246,7 +199,7 @@ export const reconciler = createReconciler<
246
199
  shouldSetTextContent: () => false,
247
200
  createInstance(originalType, newProps, rootNode, hostContext) {
248
201
  if (hostContext.isInsideText && originalType === "ink-box") {
249
- throw new Error(`<Box> cant be nested inside <Text> component`);
202
+ throw new Error(`<Box> can't be nested inside <Text> component`);
250
203
  }
251
204
 
252
205
  const type =
@@ -446,6 +399,6 @@ export const reconciler = createReconciler<
446
399
  waitForCommitToBeReady() {
447
400
  return null;
448
401
  },
449
- rendererPackageName: packageInfo.name,
450
- rendererVersion: packageInfo.version,
402
+ rendererPackageName: pkg.name,
403
+ rendererVersion: pkg.version,
451
404
  });
@@ -1,6 +1,6 @@
1
- import { colorize } from "./colorize.ts";
2
- import { type DOMNode } from "./dom.ts";
3
- import type { Output } from "./output.ts";
1
+ import { colorize } from "#/colorize.ts";
2
+ import { type DOMNode } from "#/dom.ts";
3
+ import type { Output } from "#/output.ts";
4
4
 
5
5
  export const renderBackground = (x: number, y: number, node: DOMNode, output: Output): void => {
6
6
  if (!node.style.backgroundColor) {
@@ -1,8 +1,8 @@
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";
1
+ import { chalk } from "#/ansi/chalk.ts";
2
+ import { colorize } from "#/colorize.ts";
3
+ import { type DOMNode } from "#/dom.ts";
4
+ import { BOXES } from "#/glyphs.ts";
5
+ import type { Output } from "#/output.ts";
6
6
 
7
7
  const stylePiece = (segment: string, fg?: string, bg?: string, dim?: boolean): string => {
8
8
  let styled = colorize(segment, fg, "foreground");
@@ -20,7 +20,7 @@ export const renderBorder = (x: number, y: number, node: DOMNode, output: Output
20
20
  const height = node.yogaNode!.getComputedHeight();
21
21
  const box =
22
22
  typeof node.style.borderStyle === "string"
23
- ? cliBoxes[node.style.borderStyle]
23
+ ? BOXES[node.style.borderStyle]
24
24
  : node.style.borderStyle;
25
25
 
26
26
  const topBorderColor = node.style.borderTopColor ?? node.style.borderColor;
@@ -1,13 +1,12 @@
1
- import { widestLine } from "./ansi/widest-line.ts";
2
- import { type DOMElement } from "./dom.ts";
3
- import { getMaxWidth } from "./get-max-width.ts";
4
- import { indentString } from "./indent-string.ts";
5
- import type { Output } from "./output.ts";
6
- import { renderBackground } from "./render-background.ts";
7
- import { renderBorder } from "./render-border.ts";
8
- import { squashTextNodes } from "./squash-text-nodes.ts";
9
- import { wrapText } from "./wrap-text.ts";
10
- import { Yoga } from "./yoga/index.ts";
1
+ import { widestLine } from "#/ansi/string-width.ts";
2
+ import { type DOMElement } from "#/dom.ts";
3
+ import { getMaxWidth } from "#/get-max-width.ts";
4
+ import type { Output } from "#/output.ts";
5
+ import { renderBackground } from "#/render-background.ts";
6
+ import { renderBorder } from "#/render-border.ts";
7
+ import { squashTextNodes } from "#/squash-text-nodes.ts";
8
+ import { wrapText } from "#/wrap-text.ts";
9
+ import { Yoga } from "#/yoga/index.ts";
11
10
 
12
11
  // If parent container is `<Box>`, text nodes will be treated as separate nodes in
13
12
  // the tree and will have their own coordinates in the layout.
@@ -21,7 +20,10 @@ const applyPaddingToText = (node: DOMElement, text: string): string => {
21
20
  if (yogaNode) {
22
21
  const offsetX = yogaNode.getComputedLeft();
23
22
  const offsetY = yogaNode.getComputedTop();
24
- text = "\n".repeat(offsetY) + indentString(text, offsetX);
23
+
24
+ text =
25
+ "\n".repeat(offsetY) +
26
+ (offsetX > 0 ? text.replace(/^(?!\s*$)/gm, " ".repeat(offsetX)) : text);
25
27
  }
26
28
 
27
29
  return text;
@@ -1,10 +1,10 @@
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 { 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";
8
8
 
9
9
  export type RenderToStringOptions = {
10
10
  /**
package/src/render.ts CHANGED
@@ -1,11 +1,10 @@
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 { createInk, type Ink, type Options as InkOptions, type RenderMetrics } from "#/ink.tsx";
6
+ import { instances } from "#/instances.ts";
7
+ import { type KittyKeyboardOptions } from "#/kitty-keyboard.ts";
9
8
 
10
9
  export type RenderOptions = {
11
10
  /**
@@ -122,7 +121,7 @@ export type RenderOptions = {
122
121
  /**
123
122
  Override automatic interactive mode detection.
124
123
 
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.
124
+ 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
125
 
127
126
  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
127
 
@@ -226,20 +225,20 @@ export const render = (node: ReactNode, options?: Writable | RenderOptions): Ins
226
225
  ...getOptions(options),
227
226
  };
228
227
 
229
- const instance: Ink = getInstance(inkOptions.stdout, () => new Ink(inkOptions));
228
+ const instance: Ink = getInstance(inkOptions.stdout, () => createInk(inkOptions));
230
229
  instance.render(node);
231
230
 
232
231
  return {
233
- rerender: instance.render.bind(instance),
232
+ rerender: instance.render,
234
233
  unmount() {
235
234
  instance.unmount();
236
235
  },
237
- waitUntilExit: instance.waitUntilExit.bind(instance),
238
- waitUntilRenderFlush: instance.waitUntilRenderFlush.bind(instance),
236
+ waitUntilExit: instance.waitUntilExit,
237
+ waitUntilRenderFlush: instance.waitUntilRenderFlush,
239
238
  cleanup() {
240
239
  instance.unmount();
241
240
  },
242
- clear: instance.clear.bind(instance),
241
+ clear: instance.clear,
243
242
  };
244
243
  };
245
244
 
package/src/renderer.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type DOMElement } from "./dom.ts";
2
- import { Output } from "./output.ts";
3
- import { renderNodeToOutput, renderNodeToScreenReaderOutput } from "./render-node-to-output.ts";
1
+ import { type DOMElement } from "#/dom.ts";
2
+ import { Output } from "#/output.ts";
3
+ import { renderNodeToOutput, renderNodeToScreenReaderOutput } from "#/render-node-to-output.ts";
4
4
 
5
5
  type Result = {
6
6
  output: string;
@@ -1,4 +1,4 @@
1
- import { hasAnsiControlCharacters, tokenizeAnsi } from "./ansi-tokenizer.ts";
1
+ import { hasAnsiControlCharacters, tokenizeAnsi } from "#/ansi-tokenizer.ts";
2
2
 
3
3
  const sgrParametersRegex = /^[\d:;]*$/;
4
4
 
@@ -4,7 +4,7 @@
4
4
  // Handlers receive `null` (not `undefined`) like the original package did —
5
5
  // Ink's unmount() relies on that to detect process shutdown.
6
6
  /* eslint-disable @typescript-eslint/no-restricted-types */
7
- import process from "node:process";
7
+ import { isWindows } from "#/env.ts";
8
8
 
9
9
  type ExitHandler = (code: number | null, signal: NodeJS.Signals | null) => void;
10
10
 
@@ -13,10 +13,9 @@ type Registration = {
13
13
  alwaysLast: boolean;
14
14
  };
15
15
 
16
- const signals: NodeJS.Signals[] =
17
- process.platform === "win32"
18
- ? ["SIGHUP", "SIGINT", "SIGTERM", "SIGBREAK"]
19
- : ["SIGHUP", "SIGINT", "SIGTERM", "SIGQUIT", "SIGUSR2"];
16
+ const signals: NodeJS.Signals[] = isWindows
17
+ ? ["SIGHUP", "SIGINT", "SIGTERM", "SIGBREAK"]
18
+ : ["SIGHUP", "SIGINT", "SIGTERM", "SIGQUIT", "SIGUSR2"];
20
19
 
21
20
  const registrations = new Set<Registration>();
22
21
  const signalListeners = new Map<NodeJS.Signals, () => void>();
@@ -1,5 +1,5 @@
1
- import { type DOMElement } from "./dom.ts";
2
- import { sanitizeAnsi } from "./sanitize-ansi.ts";
1
+ import { type DOMElement } from "#/dom.ts";
2
+ import { sanitizeAnsi } from "#/sanitize-ansi.ts";
3
3
 
4
4
  // Squashing text nodes allows to combine multiple text nodes into one and write
5
5
  // to `Output` instance only once. For example, <Text>hello{' '}world</Text>
package/src/stream.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { isTty } from "#/env.ts";
2
+
1
3
  export type OutputStream = NodeJS.WritableStream & {
2
4
  isTTY?: boolean;
3
5
  columns?: number;
@@ -13,10 +15,6 @@ type RawModeStream = NodeJS.ReadableStream & {
13
15
  unref?: () => void;
14
16
  };
15
17
 
16
- export const isTty = (stream: NodeJS.ReadableStream): boolean => {
17
- return "isTTY" in stream && stream.isTTY === true;
18
- };
19
-
20
18
  const isRawModeStream = (stdin: NodeJS.ReadableStream): stdin is RawModeStream => {
21
19
  return isTty(stdin) && "setRawMode" in stdin && typeof stdin.setRawMode === "function";
22
20
  };
package/src/styles.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type ForegroundColorName } from "./ansi/sgr.ts"; // Note: We import directly from `ansi-styles` to avoid a bug in TypeScript.
2
- import { type Boxes, type BoxStyle } from "./boxes.ts";
3
- import { type LiteralUnion } from "./types.ts";
4
- import { Yoga, type Node as YogaNode, type PositionType } from "./yoga/index.ts";
1
+ import { type ForegroundColorName } from "#/ansi/sgr.ts";
2
+ import { type BoxGlyphs, type BoxStyle } from "#/glyphs.ts";
3
+ import { type LiteralUnion } from "#/types.ts";
4
+ import { Yoga, type Node as YogaNode, type PositionType } from "#/yoga/index.ts";
5
5
 
6
6
  export type Styles = {
7
7
  /*
@@ -134,7 +134,7 @@ export type Styles = {
134
134
  readonly flexGrow?: number;
135
135
 
136
136
  /**
137
- It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isnt enough space on the row.
137
+ It specifies the “flex shrink factor”, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when there isn't enough space on the row.
138
138
  See [flex-shrink](https://css-tricks.com/almanac/properties/f/flex-shrink/).
139
139
  */
140
140
  readonly flexShrink?: number;
@@ -241,7 +241,7 @@ export type Styles = {
241
241
  /**
242
242
  Add a border with a specified style. If `borderStyle` is `undefined` (the default), no border will be added.
243
243
  */
244
- readonly borderStyle?: keyof Boxes | BoxStyle;
244
+ readonly borderStyle?: BoxStyle | BoxGlyphs;
245
245
 
246
246
  /**
247
247
  Determines whether the top border is visible.