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

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 (121) hide show
  1. package/README.md +299 -299
  2. package/dist/ansi.d.ts +223 -0
  3. package/dist/ansi.js +2 -0
  4. package/dist/{devtools-QpCMm9JH.mjs → devtools-BhYGjb7h.js} +1 -1
  5. package/dist/index-DDVME65c.d.ts +919 -0
  6. package/dist/index.d.ts +1657 -0
  7. package/dist/index.js +4643 -0
  8. package/dist/sgr-CMfEpjSk.d.ts +91 -0
  9. package/dist/truncate-CBiyyZzw.js +2156 -0
  10. package/dist/yoga-5jKhYCJC.js +3465 -0
  11. package/dist/yoga.d.ts +2 -0
  12. package/dist/yoga.js +2 -0
  13. package/package.json +37 -17
  14. package/src/ansi/chalk.ts +179 -0
  15. package/src/ansi/cursor.ts +48 -0
  16. package/src/ansi/east-asian-width.ts +215 -0
  17. package/src/ansi/escapes.ts +128 -0
  18. package/src/ansi/index.ts +27 -0
  19. package/src/ansi/sgr.ts +237 -0
  20. package/src/ansi/slice.ts +43 -0
  21. package/src/ansi/string-width.ts +236 -0
  22. package/src/ansi/strip.ts +33 -0
  23. package/src/ansi/supports-color.ts +213 -0
  24. package/src/ansi/tokenize.ts +453 -0
  25. package/src/ansi/truncate.ts +194 -0
  26. package/src/ansi/widest-line.ts +12 -0
  27. package/src/ansi/wrap.ts +766 -0
  28. package/src/ansi-tokenizer.ts +510 -0
  29. package/src/auto-bind.ts +41 -0
  30. package/src/boxes.ts +100 -0
  31. package/src/code-excerpt.ts +39 -0
  32. package/src/colorize.ts +60 -0
  33. package/src/components/AccessibilityContext.ts +5 -0
  34. package/src/components/AnimationContext.ts +24 -0
  35. package/src/components/App.tsx +781 -0
  36. package/src/components/AppContext.ts +111 -0
  37. package/src/components/BackgroundContext.ts +8 -0
  38. package/src/components/Box.tsx +116 -0
  39. package/src/components/CursorContext.ts +19 -0
  40. package/src/components/ErrorBoundary.tsx +38 -0
  41. package/src/components/ErrorOverview.tsx +133 -0
  42. package/src/components/FocusContext.ts +30 -0
  43. package/src/components/Newline.tsx +15 -0
  44. package/src/components/Spacer.tsx +10 -0
  45. package/src/components/Static.tsx +59 -0
  46. package/src/components/StderrContext.ts +26 -0
  47. package/src/components/StdinContext.ts +49 -0
  48. package/src/components/StdoutContext.ts +28 -0
  49. package/src/components/Text.tsx +144 -0
  50. package/src/components/Transform.tsx +37 -0
  51. package/src/cursor-position.ts +103 -0
  52. package/src/devtools-window-polyfill.ts +73 -0
  53. package/src/devtools.ts +43 -0
  54. package/src/dom.ts +292 -0
  55. package/src/get-max-width.ts +11 -0
  56. package/src/global.d.ts +36 -0
  57. package/src/hooks/use-animation.ts +142 -0
  58. package/src/hooks/use-app.ts +8 -0
  59. package/src/hooks/use-box-metrics.ts +134 -0
  60. package/src/hooks/use-cursor.ts +33 -0
  61. package/src/hooks/use-focus-manager.ts +62 -0
  62. package/src/hooks/use-focus.ts +83 -0
  63. package/src/hooks/use-input.ts +267 -0
  64. package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
  65. package/src/hooks/use-paste.ts +78 -0
  66. package/src/hooks/use-stderr.ts +8 -0
  67. package/src/hooks/use-stdin.ts +10 -0
  68. package/src/hooks/use-stdout.ts +8 -0
  69. package/src/hooks/use-window-size.ts +41 -0
  70. package/src/indent-string.ts +16 -0
  71. package/src/index.ts +44 -0
  72. package/src/ink.tsx +1506 -0
  73. package/src/input-parser.ts +283 -0
  74. package/src/instances.ts +9 -0
  75. package/src/is-in-ci.ts +7 -0
  76. package/src/kitty-keyboard.ts +57 -0
  77. package/src/log-update.ts +370 -0
  78. package/src/measure-element.ts +62 -0
  79. package/src/measure-text.ts +31 -0
  80. package/src/output.ts +308 -0
  81. package/src/parse-keypress.ts +516 -0
  82. package/src/parse-stack-line.ts +139 -0
  83. package/src/patch-console.ts +62 -0
  84. package/src/quick-lru.ts +85 -0
  85. package/src/reconciler.ts +451 -0
  86. package/src/render-background.ts +38 -0
  87. package/src/render-border.ts +134 -0
  88. package/src/render-node-to-output.ts +191 -0
  89. package/src/render-to-string.ts +131 -0
  90. package/src/render.ts +276 -0
  91. package/src/renderer.ts +73 -0
  92. package/src/sanitize-ansi.ts +33 -0
  93. package/src/signal-exit.ts +107 -0
  94. package/src/squash-text-nodes.ts +40 -0
  95. package/src/stream.ts +30 -0
  96. package/src/styles.ts +748 -0
  97. package/src/terminal-size.ts +57 -0
  98. package/src/throttle.ts +73 -0
  99. package/src/types.ts +15 -0
  100. package/src/utils.ts +40 -0
  101. package/src/wrap-text.ts +50 -0
  102. package/src/write-synchronized.ts +9 -0
  103. package/src/yoga/config.ts +57 -0
  104. package/src/yoga/core/absoluteLayout.ts +626 -0
  105. package/src/yoga/core/baseline.ts +66 -0
  106. package/src/yoga/core/cache.ts +136 -0
  107. package/src/yoga/core/calculateLayout.ts +2920 -0
  108. package/src/yoga/core/config.ts +104 -0
  109. package/src/yoga/core/flexLine.ts +177 -0
  110. package/src/yoga/core/helpers.ts +293 -0
  111. package/src/yoga/core/layoutResults.ts +167 -0
  112. package/src/yoga/core/node.ts +611 -0
  113. package/src/yoga/core/numeric.ts +44 -0
  114. package/src/yoga/core/pixelGrid.ts +151 -0
  115. package/src/yoga/core/style.ts +887 -0
  116. package/src/yoga/core/types.ts +224 -0
  117. package/src/yoga/generated/YGEnums.ts +263 -0
  118. package/src/yoga/index.ts +19 -0
  119. package/src/yoga/node.ts +1140 -0
  120. package/dist/index.d.mts +0 -2379
  121. package/dist/index.mjs +0 -10072
@@ -0,0 +1,111 @@
1
+ import { createContext } from "react";
2
+
3
+ /**
4
+ A handle returned by `suspendTerminal()` when called without a callback.
5
+
6
+ Call `resume()` to give terminal ownership back to Ink, or use `await using`
7
+ so the suspension is resumed automatically when it leaves scope.
8
+ */
9
+ export type TerminalSuspension = {
10
+ readonly resume: () => Promise<void>;
11
+ readonly [Symbol.asyncDispose]: () => Promise<void>;
12
+ };
13
+
14
+ /**
15
+ Temporarily hand the terminal over to a child process (e.g. `$EDITOR`, `less`,
16
+ `fzf`), then restore Ink's terminal state and force a full redraw.
17
+ */
18
+ export type SuspendTerminal = {
19
+ (callback: () => void | Promise<void>): Promise<void>;
20
+ (): Promise<TerminalSuspension>;
21
+ };
22
+
23
+ export type Props = {
24
+ /**
25
+ Exit (unmount) the whole Ink app.
26
+
27
+ - `exit()` — resolves `waitUntilExit()` with `undefined`.
28
+ - `exit(new Error('…'))` — rejects `waitUntilExit()` with the error.
29
+ - `exit(value)` — resolves `waitUntilExit()` with `value`.
30
+ */
31
+ readonly exit: (errorOrResult?: unknown) => void;
32
+
33
+ /**
34
+ Returns a promise that settles after pending render output is flushed to stdout.
35
+
36
+ @example
37
+ ```jsx
38
+ import {useEffect} from 'react';
39
+ import {useApp} from 'ink';
40
+
41
+ const Example = () => {
42
+ const {waitUntilRenderFlush} = useApp();
43
+
44
+ useEffect(() => {
45
+ void (async () => {
46
+ await waitUntilRenderFlush();
47
+ runNextCommand();
48
+ })();
49
+ }, [waitUntilRenderFlush]);
50
+
51
+ return …;
52
+ };
53
+ ```
54
+ */
55
+ readonly waitUntilRenderFlush: () => Promise<void>;
56
+
57
+ /**
58
+ Temporarily release the terminal so a child process can take it over, then
59
+ restore Ink's terminal state and force a full redraw.
60
+
61
+ Use the callback form for the common case — Ink restores the terminal even
62
+ if the callback throws:
63
+
64
+ @example
65
+ ```jsx
66
+ import {useApp} from 'ink';
67
+
68
+ const {suspendTerminal} = useApp();
69
+
70
+ await suspendTerminal(async () => {
71
+ await runEditor();
72
+ });
73
+ ```
74
+
75
+ Or hold a suspension and resume it yourself:
76
+
77
+ @example
78
+ ```jsx
79
+ await using suspension = await suspendTerminal();
80
+ await runEditor();
81
+ ```
82
+ */
83
+ readonly suspendTerminal: SuspendTerminal;
84
+ };
85
+
86
+ /**
87
+ `AppContext` is a React context that exposes lifecycle methods for the app.
88
+ */
89
+ // Keep the default value typed so `useApp()` preserves the public `exit(errorOrResult?)` signature.
90
+ const noopSuspension: TerminalSuspension = {
91
+ async resume() {},
92
+ async [Symbol.asyncDispose]() {},
93
+ };
94
+
95
+ const defaultValue: Props = {
96
+ exit(_errorOrResult?: unknown) {},
97
+ async waitUntilRenderFlush() {},
98
+ suspendTerminal: (async (callback?: () => void | Promise<void>) => {
99
+ if (callback) {
100
+ await callback();
101
+ return;
102
+ }
103
+
104
+ return noopSuspension;
105
+ }) as SuspendTerminal,
106
+ };
107
+
108
+ // eslint-disable-next-line @typescript-eslint/naming-convention
109
+ export const AppContext = createContext(defaultValue);
110
+
111
+ AppContext.displayName = "InternalAppContext";
@@ -0,0 +1,8 @@
1
+ import { createContext } from "react";
2
+
3
+ import { type ForegroundColorName } from "../ansi/sgr.ts";
4
+ import { type LiteralUnion } from "../types.ts";
5
+
6
+ export type BackgroundColor = LiteralUnion<ForegroundColorName, string>;
7
+
8
+ export const backgroundContext = createContext<BackgroundColor | undefined>(undefined);
@@ -0,0 +1,116 @@
1
+ import { forwardRef, useContext, type PropsWithChildren } from "react";
2
+
3
+ import { type DOMElement } from "../dom.ts";
4
+ import { type Styles } from "../styles.ts";
5
+ import { type Except } from "../types.ts";
6
+ import { accessibilityContext } from "./AccessibilityContext.ts";
7
+ import { backgroundContext } from "./BackgroundContext.ts";
8
+
9
+ export type Props = Except<Styles, "textWrap"> & {
10
+ /**
11
+ A label for the element for screen readers.
12
+ */
13
+ readonly "aria-label"?: string;
14
+
15
+ /**
16
+ Hide the element from screen readers.
17
+ */
18
+ readonly "aria-hidden"?: boolean;
19
+
20
+ /**
21
+ The role of the element.
22
+ */
23
+ readonly "aria-role"?:
24
+ | "button"
25
+ | "checkbox"
26
+ | "combobox"
27
+ | "list"
28
+ | "listbox"
29
+ | "listitem"
30
+ | "menu"
31
+ | "menuitem"
32
+ | "option"
33
+ | "progressbar"
34
+ | "radio"
35
+ | "radiogroup"
36
+ | "tab"
37
+ | "tablist"
38
+ | "table"
39
+ | "textbox"
40
+ | "timer"
41
+ | "toolbar";
42
+
43
+ /**
44
+ The state of the element.
45
+ */
46
+ readonly "aria-state"?: {
47
+ readonly busy?: boolean;
48
+ readonly checked?: boolean;
49
+ readonly disabled?: boolean;
50
+ readonly expanded?: boolean;
51
+ readonly multiline?: boolean;
52
+ readonly multiselectable?: boolean;
53
+ readonly readonly?: boolean;
54
+ readonly required?: boolean;
55
+ readonly selected?: boolean;
56
+ };
57
+ };
58
+
59
+ /**
60
+ `<Box>` is an essential Ink component to build your layout. It's like `<div style="display: flex">` in the browser.
61
+ */
62
+ export const Box = forwardRef<DOMElement, PropsWithChildren<Props>>(
63
+ (
64
+ {
65
+ children,
66
+ backgroundColor,
67
+ "aria-label": ariaLabel,
68
+ "aria-hidden": ariaHidden,
69
+ "aria-role": role,
70
+ "aria-state": ariaState,
71
+ ...style
72
+ },
73
+ ref,
74
+ ) => {
75
+ const { isScreenReaderEnabled } = useContext(accessibilityContext);
76
+ const label = ariaLabel ? <ink-text>{ariaLabel}</ink-text> : undefined;
77
+ if (isScreenReaderEnabled && ariaHidden) {
78
+ return null;
79
+ }
80
+
81
+ const boxElement = (
82
+ <ink-box
83
+ ref={ref}
84
+ style={{
85
+ flexWrap: "nowrap",
86
+ flexDirection: "row",
87
+ flexGrow: 0,
88
+ flexShrink: 1,
89
+ ...style,
90
+ backgroundColor,
91
+ overflowX: style.overflowX ?? style.overflow ?? "visible",
92
+ overflowY: style.overflowY ?? style.overflow ?? "visible",
93
+ }}
94
+ internal_accessibility={{
95
+ role,
96
+ state: ariaState,
97
+ }}
98
+ >
99
+ {isScreenReaderEnabled && label ? label : children}
100
+ </ink-box>
101
+ );
102
+
103
+ // If this Box has a background color, provide it to children via context
104
+ if (backgroundColor) {
105
+ return (
106
+ <backgroundContext.Provider value={backgroundColor}>
107
+ {boxElement}
108
+ </backgroundContext.Provider>
109
+ );
110
+ }
111
+
112
+ return boxElement;
113
+ },
114
+ );
115
+
116
+ Box.displayName = "Box";
@@ -0,0 +1,19 @@
1
+ import { createContext } from "react";
2
+
3
+ import { type CursorPosition } from "../log-update.ts";
4
+
5
+ export type Props = {
6
+ /**
7
+ Set the cursor position relative to the Ink output.
8
+
9
+ Pass `undefined` to hide the cursor.
10
+ */
11
+ readonly setCursorPosition: (position: CursorPosition | undefined) => void;
12
+ };
13
+
14
+ // eslint-disable-next-line @typescript-eslint/naming-convention
15
+ export const CursorContext = createContext<Props>({
16
+ setCursorPosition() {},
17
+ });
18
+
19
+ CursorContext.displayName = "InternalCursorContext";
@@ -0,0 +1,38 @@
1
+ import { PureComponent, type ReactNode } from "react";
2
+
3
+ import { ErrorOverview } from "./ErrorOverview.tsx";
4
+
5
+ type Props = {
6
+ readonly children: ReactNode;
7
+ readonly onError: (error: Error) => void;
8
+ };
9
+
10
+ type State = {
11
+ readonly error?: Error;
12
+ };
13
+
14
+ // Error boundary must be a class component since getDerivedStateFromError
15
+ // and componentDidCatch are not available as hooks
16
+ export class ErrorBoundary extends PureComponent<Props, State> {
17
+ static displayName = "InternalErrorBoundary";
18
+
19
+ static getDerivedStateFromError(error: Error) {
20
+ return { error };
21
+ }
22
+
23
+ override state: State = {
24
+ error: undefined,
25
+ };
26
+
27
+ override componentDidCatch(error: Error): void {
28
+ this.props.onError(error);
29
+ }
30
+
31
+ override render(): ReactNode {
32
+ if (this.state.error) {
33
+ return <ErrorOverview error={this.state.error} />;
34
+ }
35
+
36
+ return this.props.children;
37
+ }
38
+ }
@@ -0,0 +1,133 @@
1
+ import * as fs from "node:fs";
2
+ import { cwd } from "node:process";
3
+
4
+ import { codeExcerpt, type CodeExcerpt } from "../code-excerpt.ts";
5
+ import { parseStackLine } from "../parse-stack-line.ts";
6
+ import { Box } from "./Box.tsx";
7
+ import { Text } from "./Text.tsx";
8
+
9
+ // Error's source file is reported as file:///home/user/file.js
10
+ // This function removes the file://[cwd] part
11
+ const cleanupPath = (path: string | undefined): string | undefined => {
12
+ return path?.replace(`file://${cwd()}/`, "");
13
+ };
14
+
15
+ type Props = {
16
+ readonly error: Error;
17
+ };
18
+
19
+ export function ErrorOverview({ error }: Props) {
20
+ const stack = error.stack ? error.stack.split("\n").slice(1) : undefined;
21
+ const origin = stack ? parseStackLine(stack[0]!) : undefined;
22
+ const filePath = cleanupPath(origin?.file);
23
+ let excerpt: CodeExcerpt[] | undefined;
24
+ let lineWidth = 0;
25
+ const stackLineCounts = new Map<string, number>();
26
+
27
+ if (filePath && origin?.line && fs.existsSync(filePath)) {
28
+ const sourceCode = fs.readFileSync(filePath, "utf8");
29
+ excerpt = codeExcerpt(sourceCode, origin.line);
30
+
31
+ if (excerpt) {
32
+ for (const { line } of excerpt) {
33
+ lineWidth = Math.max(lineWidth, String(line).length);
34
+ }
35
+ }
36
+ }
37
+
38
+ return (
39
+ <Box flexDirection="column" padding={1}>
40
+ <Box>
41
+ <Text backgroundColor="red" color="white">
42
+ {" "}
43
+ ERROR{" "}
44
+ </Text>
45
+
46
+ <Text> {error.message}</Text>
47
+ </Box>
48
+
49
+ {origin && filePath ? (
50
+ <Box marginTop={1}>
51
+ <Text dimColor>
52
+ {filePath}:{origin.line}:{origin.column}
53
+ </Text>
54
+ </Box>
55
+ ) : null}
56
+
57
+ {origin && excerpt ? (
58
+ <Box marginTop={1} flexDirection="column">
59
+ {excerpt.map(({ line, value }) => (
60
+ <Box key={line}>
61
+ <Box width={lineWidth + 1}>
62
+ <Text
63
+ dimColor={line !== origin.line}
64
+ backgroundColor={line === origin.line ? "red" : undefined}
65
+ color={line === origin.line ? "white" : undefined}
66
+ aria-label={line === origin.line ? `Line ${line}, error` : `Line ${line}`}
67
+ >
68
+ {String(line).padStart(lineWidth, " ")}:
69
+ </Text>
70
+ </Box>
71
+
72
+ <Text
73
+ key={line}
74
+ backgroundColor={line === origin.line ? "red" : undefined}
75
+ color={line === origin.line ? "white" : undefined}
76
+ >
77
+ {" " + value}
78
+ </Text>
79
+ </Box>
80
+ ))}
81
+ </Box>
82
+ ) : null}
83
+
84
+ {error.stack ? (
85
+ <Box marginTop={1} flexDirection="column">
86
+ {error.stack
87
+ .split("\n")
88
+ .slice(1)
89
+ .map((line) => {
90
+ const parsedLine = parseStackLine(line);
91
+ const lineCount = stackLineCounts.get(line) ?? 0;
92
+ stackLineCounts.set(line, lineCount + 1);
93
+ const key = `${line}-${lineCount}`;
94
+
95
+ // If the line from the stack cannot be parsed, or parsed into an incomplete
96
+ // frame without source location data (for example, "at native"), we print
97
+ // out the unparsed line.
98
+ if (!parsedLine?.file || !parsedLine.line || !parsedLine.column) {
99
+ return (
100
+ <Box key={key}>
101
+ <Text dimColor>- </Text>
102
+ <Text dimColor bold>
103
+ {line}
104
+ \t{" "}
105
+ </Text>
106
+ </Box>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <Box key={key}>
112
+ <Text dimColor>- </Text>
113
+ <Text dimColor bold>
114
+ {parsedLine.function}
115
+ </Text>
116
+ <Text
117
+ dimColor
118
+ color="gray"
119
+ aria-label={`at ${
120
+ cleanupPath(parsedLine.file) ?? ""
121
+ } line ${parsedLine.line} column ${parsedLine.column}`}
122
+ >
123
+ {" "}
124
+ ({cleanupPath(parsedLine.file) ?? ""}:{parsedLine.line}:{parsedLine.column})
125
+ </Text>
126
+ </Box>
127
+ );
128
+ })}
129
+ </Box>
130
+ ) : null}
131
+ </Box>
132
+ );
133
+ }
@@ -0,0 +1,30 @@
1
+ import { createContext } from "react";
2
+
3
+ export type Props = {
4
+ readonly activeId?: string;
5
+ readonly add: (id: string, options: { autoFocus: boolean }) => void;
6
+ readonly remove: (id: string) => void;
7
+ readonly activate: (id: string) => void;
8
+ readonly deactivate: (id: string) => void;
9
+ readonly enableFocus: () => void;
10
+ readonly disableFocus: () => void;
11
+ readonly focusNext: () => void;
12
+ readonly focusPrevious: () => void;
13
+ readonly focus: (id: string) => void;
14
+ };
15
+
16
+ // eslint-disable-next-line @typescript-eslint/naming-convention
17
+ export const FocusContext = createContext<Props>({
18
+ activeId: undefined,
19
+ add() {},
20
+ remove() {},
21
+ activate() {},
22
+ deactivate() {},
23
+ enableFocus() {},
24
+ disableFocus() {},
25
+ focusNext() {},
26
+ focusPrevious() {},
27
+ focus() {},
28
+ });
29
+
30
+ FocusContext.displayName = "InternalFocusContext";
@@ -0,0 +1,15 @@
1
+ export type Props = {
2
+ /**
3
+ Number of newlines to insert.
4
+
5
+ @default 1
6
+ */
7
+ readonly count?: number;
8
+ };
9
+
10
+ /**
11
+ Adds one or more newline (`\n`) characters. Must be used within `<Text>` components.
12
+ */
13
+ export function Newline({ count = 1 }: Props) {
14
+ return <ink-text>{"\n".repeat(count)}</ink-text>;
15
+ }
@@ -0,0 +1,10 @@
1
+ import { Box } from "./Box.tsx";
2
+
3
+ /**
4
+ A flexible space that expands along the major axis of its containing layout.
5
+
6
+ It's useful as a shortcut for filling all the available space between elements.
7
+ */
8
+ export function Spacer() {
9
+ return <Box flexGrow={1} />;
10
+ }
@@ -0,0 +1,59 @@
1
+ import { useMemo, useState, useLayoutEffect, type ReactNode } from "react";
2
+
3
+ import { type Styles } from "../styles.ts";
4
+
5
+ export type Props<T> = {
6
+ /**
7
+ Array of items of any type to render using the function you pass as a component child.
8
+ */
9
+ readonly items: T[];
10
+
11
+ /**
12
+ Styles to apply to a container of child elements. See <Box> for supported properties.
13
+ */
14
+ readonly style?: Styles;
15
+
16
+ /**
17
+ Function that is called to render every item in the `items` array. The first argument is the item itself, and the second argument is the index of that item in the `items` array. Note that a `key` must be assigned to the root component.
18
+ */
19
+ readonly children: (item: T, index: number) => ReactNode;
20
+ };
21
+
22
+ /**
23
+ `<Static>` component permanently renders its output above everything else. It's useful for displaying activity like completed tasks or logs—things that don't change after they're rendered (hence the name "Static").
24
+
25
+ It's preferred to use `<Static>` for use cases like these when you can't know or control the number of items that need to be rendered.
26
+
27
+ For example, [Tap](https://github.com/tapjs/node-tap) uses `<Static>` to display a list of completed tests. [Gatsby](https://github.com/gatsbyjs/gatsby) uses it to display a list of generated pages while still displaying a live progress bar.
28
+ */
29
+ export function Static<T>(props: Props<T>) {
30
+ const { items, children: render, style: customStyle } = props;
31
+ const [index, setIndex] = useState(0);
32
+
33
+ const itemsToRender: T[] = useMemo(() => {
34
+ return items.slice(index);
35
+ }, [items, index]);
36
+
37
+ useLayoutEffect(() => {
38
+ setIndex(items.length);
39
+ }, [items.length]);
40
+
41
+ const children = itemsToRender.map((item, itemIndex): ReactNode => {
42
+ return render(item, index + itemIndex);
43
+ });
44
+
45
+ const style: Styles = useMemo(
46
+ () => ({
47
+ position: "absolute",
48
+ flexDirection: "column",
49
+ ...customStyle,
50
+ }),
51
+ [customStyle],
52
+ );
53
+
54
+ return (
55
+ <ink-box internal_static style={style}>
56
+ {children}
57
+ </ink-box>
58
+ );
59
+ }
@@ -0,0 +1,26 @@
1
+ import process from "node:process";
2
+
3
+ import { createContext } from "react";
4
+
5
+ export type Props = {
6
+ /**
7
+ Stderr stream passed to `render()` in `options.stderr` or `process.stderr` by default.
8
+ */
9
+ readonly stderr: NodeJS.WritableStream;
10
+
11
+ /**
12
+ Write any string to stderr while preserving Ink's output. It's useful when you want to display external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `<Static>`, except it can't accept components; it only works with strings.
13
+ */
14
+ readonly write: (data: string) => void;
15
+ };
16
+
17
+ /**
18
+ `StderrContext` is a React context that exposes the stderr stream.
19
+ */
20
+ // eslint-disable-next-line @typescript-eslint/naming-convention
21
+ export const StderrContext = createContext<Props>({
22
+ stderr: process.stderr,
23
+ write() {},
24
+ });
25
+
26
+ StderrContext.displayName = "InternalStderrContext";
@@ -0,0 +1,49 @@
1
+ import { EventEmitter } from "node:events";
2
+ import process from "node:process";
3
+
4
+ import { createContext } from "react";
5
+
6
+ export type PublicProps = {
7
+ /**
8
+ The stdin stream passed to `render()` in `options.stdin`, or `process.stdin` by default. Useful if your app needs to handle user input.
9
+ */
10
+ readonly stdin: NodeJS.ReadableStream;
11
+
12
+ /**
13
+ Ink exposes this function via own `<StdinContext>` to be able to handle Ctrl+C, that's why you should use Ink's `setRawMode` instead of `process.stdin.setRawMode`. If the `stdin` stream passed to Ink does not support setRawMode, this function does nothing.
14
+ */
15
+ readonly setRawMode: (value: boolean) => void;
16
+
17
+ /**
18
+ A boolean flag determining if the current `stdin` supports `setRawMode`. A component using `setRawMode` might want to use `isRawModeSupported` to nicely fall back in environments where raw mode is not supported.
19
+ */
20
+ readonly isRawModeSupported: boolean;
21
+ };
22
+
23
+ export type Props = PublicProps & {
24
+ /**
25
+ Enable or disable bracketed paste mode on the terminal. When enabled, pasted text is wrapped in escape sequences that allow it to be distinguished from typed input.
26
+ */
27
+ readonly setBracketedPasteMode: (value: boolean) => void;
28
+
29
+ readonly internal_exitOnCtrlC: boolean;
30
+
31
+ readonly internal_eventEmitter: EventEmitter;
32
+ };
33
+
34
+ /**
35
+ `StdinContext` is a React context that exposes the input stream.
36
+ */
37
+ // eslint-disable-next-line @typescript-eslint/naming-convention
38
+ export const StdinContext = createContext<Props>({
39
+ stdin: process.stdin,
40
+ // eslint-disable-next-line @typescript-eslint/naming-convention
41
+ internal_eventEmitter: new EventEmitter(),
42
+ setRawMode() {},
43
+ setBracketedPasteMode() {},
44
+ isRawModeSupported: false,
45
+ // eslint-disable-next-line @typescript-eslint/naming-convention
46
+ internal_exitOnCtrlC: true,
47
+ });
48
+
49
+ StdinContext.displayName = "InternalStdinContext";
@@ -0,0 +1,28 @@
1
+ import process from "node:process";
2
+
3
+ import { createContext } from "react";
4
+
5
+ import type { OutputStream } from "../stream.ts";
6
+
7
+ export type Props = {
8
+ /**
9
+ Stdout stream passed to `render()` in `options.stdout` or `process.stdout` by default.
10
+ */
11
+ readonly stdout: OutputStream;
12
+
13
+ /**
14
+ Write any string to stdout while preserving Ink's output. It's useful when you want to display external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `<Static>`, except it can't accept components; it only works with strings.
15
+ */
16
+ readonly write: (data: string) => void;
17
+ };
18
+
19
+ /**
20
+ `StdoutContext` is a React context that exposes the stdout stream where Ink renders your app.
21
+ */
22
+ // eslint-disable-next-line @typescript-eslint/naming-convention
23
+ export const StdoutContext = createContext<Props>({
24
+ stdout: process.stdout,
25
+ write() {},
26
+ });
27
+
28
+ StdoutContext.displayName = "InternalStdoutContext";