@solid-tui/runtime 0.1.0

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.
@@ -0,0 +1,333 @@
1
+ import { batch, catchError, children, createContext, createEffect, createMemo, createRenderEffect, createRoot as createRoot$1, createSignal, on, onCleanup, splitProps, untrack, useContext } from "solid-js/dist/solid.js";
2
+ import { Node } from "better-yoga-layout";
3
+ import { EventEmitter } from "node:events";
4
+ import { Accessor, Component, JSX as JSX$1, JSXElement, Setter } from "solid-js";
5
+
6
+ //#region src/context.d.ts
7
+ interface CursorPosition {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ interface AppContext {
12
+ exit: (errorOrResult?: unknown) => void;
13
+ waitUntilRenderFlush: () => Promise<void>;
14
+ stdout: NodeJS.WriteStream;
15
+ stderr: NodeJS.WriteStream;
16
+ stdin: NodeJS.ReadStream;
17
+ debug: boolean;
18
+ interactive: boolean;
19
+ isScreenReaderEnabled: boolean;
20
+ isRawModeSupported: boolean;
21
+ setRawMode: (mode: boolean) => void;
22
+ writeToStdout: (data: string) => void;
23
+ writeToStderr: (data: string) => void;
24
+ cursorPosition: CursorPosition | undefined;
25
+ setCursorPosition: (pos: CursorPosition | undefined) => void;
26
+ }
27
+ interface FocusContext {
28
+ activeId: string | null;
29
+ activeIdValue: Accessor<string | null>;
30
+ enabled: boolean;
31
+ enableFocus: () => void;
32
+ disableFocus: () => void;
33
+ focusNext: () => void;
34
+ focusPrevious: () => void;
35
+ focus: (id: string) => void;
36
+ blur: () => void;
37
+ add: (id: string, options: {
38
+ autoFocus?: boolean;
39
+ }) => void;
40
+ remove: (id: string) => void;
41
+ activate: (id: string) => void;
42
+ deactivate: (id: string) => void;
43
+ subscribe: (id: string, fn: (focused: boolean) => void) => () => void;
44
+ }
45
+ //#endregion
46
+ //#region src/host/nodes.d.ts
47
+ type YogaNodeRef = Node;
48
+ interface BoxProps {
49
+ [k: string]: unknown;
50
+ }
51
+ interface TextProps {
52
+ color?: unknown;
53
+ backgroundColor?: unknown;
54
+ dimColor?: boolean;
55
+ bold?: boolean;
56
+ italic?: boolean;
57
+ underline?: boolean;
58
+ strikethrough?: boolean;
59
+ inverse?: boolean;
60
+ wrap?: "wrap" | "hard" | "truncate" | "truncate-end" | "truncate-middle" | "truncate-start";
61
+ }
62
+ interface NodeBase {
63
+ parent: TuiContainer | null;
64
+ }
65
+ interface TuiRoot extends NodeBase {
66
+ type: "root";
67
+ parent: null;
68
+ children: TuiNode[];
69
+ yoga: YogaNodeRef;
70
+ appContext: AppContext;
71
+ /** Currently mounted <Static> node (if any). Updated on insert/remove. */
72
+ staticNode?: TuiStatic;
73
+ /** Previous commit's staticNode — used to detect identity changes. */
74
+ previousStaticNode?: TuiStatic;
75
+ /** Callback invoked when the <Static> identity changes (mount/unmount/remount). */
76
+ onStaticChange?: () => void;
77
+ /** Listeners invoked after every layout calculation (yoga.calculateLayout). */
78
+ layoutListeners: Set<() => void>;
79
+ }
80
+ interface TuiBox extends NodeBase {
81
+ type: "tui-box";
82
+ children: TuiNode[];
83
+ yoga: YogaNodeRef;
84
+ props: BoxProps;
85
+ paintDirty: boolean;
86
+ internal_accessibility?: {
87
+ role?: string;
88
+ state?: Record<string, boolean>;
89
+ };
90
+ }
91
+ interface TuiText extends NodeBase {
92
+ type: "tui-text";
93
+ children: TuiInlineNode[];
94
+ yoga: YogaNodeRef;
95
+ props: TextProps;
96
+ measuredCache?: string;
97
+ }
98
+ interface TuiVirtualText extends NodeBase {
99
+ type: "tui-virtual-text";
100
+ parent: TuiText | TuiVirtualText | TuiTransform | null;
101
+ children: TuiInlineNode[];
102
+ props: TextProps;
103
+ }
104
+ interface TuiTextLeaf extends NodeBase {
105
+ type: "text-leaf";
106
+ parent: TuiText | TuiVirtualText | TuiTransform | null;
107
+ value: string;
108
+ }
109
+ /** Placeholder comment node used by Solid's renderer for v-if / null renders. */
110
+ interface TuiComment extends NodeBase {
111
+ type: "comment";
112
+ value: string;
113
+ }
114
+ interface TuiStatic extends NodeBase {
115
+ type: "tui-static";
116
+ children: TuiNode[];
117
+ yoga: YogaNodeRef;
118
+ props: BoxProps;
119
+ /**
120
+ * Host child nodes already written to the static channel. Static items are
121
+ * write-once: each commit only paints the children NOT in this set. We track
122
+ * by node identity rather than a count because a single logical item expands
123
+ * to several host nodes (the <Text>/<Box> plus empty text-leaf fragment
124
+ * anchors Solid inserts), so a positional `writtenCount` would mis-slice. Once a
125
+ * child is painted it is recorded here, then the <Static> component advances
126
+ * its cursor and unmounts it (mirroring Ink's `setIndex(items.length)`).
127
+ */
128
+ writtenNodes: Set<TuiNode>;
129
+ /**
130
+ * Callback registered by the <Static> component, invoked by the renderer AFTER
131
+ * a commit has painted freshly-written items. It advances the component's
132
+ * reactive cursor (Ink's `index`) so the just-written items are sliced out and
133
+ * unmount on the next render — the solid-tui analogue of Ink's post-commit
134
+ * `useLayoutEffect(() => setIndex(items.length))`. Advancing AFTER paint (never
135
+ * during render) guarantees items are written before they are dropped.
136
+ */
137
+ onWritten?: () => void;
138
+ }
139
+ interface TuiTransform extends NodeBase {
140
+ type: "tui-transform";
141
+ children: TuiNode[];
142
+ yoga: YogaNodeRef;
143
+ transform: (line: string, lineIndex: number) => string;
144
+ }
145
+ type TuiInlineNode = TuiVirtualText | TuiTextLeaf | TuiComment | TuiTransform;
146
+ type TuiContainer = TuiRoot | TuiBox | TuiStatic | TuiTransform | TuiText | TuiVirtualText;
147
+ type TuiNode = TuiContainer | TuiTextLeaf | TuiComment;
148
+ declare function createRoot(appContext: AppContext): TuiRoot;
149
+ declare function createBox(): TuiBox;
150
+ declare function createText(): TuiText;
151
+ declare function createTextLeaf(value: string): TuiTextLeaf;
152
+ //#endregion
153
+ //#region src/io/kitty-keyboard.d.ts
154
+ declare const kittyFlags: {
155
+ readonly disambiguateEscapeCodes: 1;
156
+ readonly reportEventTypes: 2;
157
+ readonly reportAlternateKeys: 4;
158
+ readonly reportAllKeysAsEscapeCodes: 8;
159
+ readonly reportAssociatedText: 16;
160
+ };
161
+ type KittyFlagName = keyof typeof kittyFlags;
162
+ declare const kittyModifiers: {
163
+ readonly shift: 1;
164
+ readonly alt: 2;
165
+ readonly ctrl: 4;
166
+ readonly super: 8;
167
+ readonly hyper: 16;
168
+ readonly meta: 32;
169
+ readonly capsLock: 64;
170
+ readonly numLock: 128;
171
+ };
172
+ type KittyKeyboardOptions = {
173
+ mode?: "auto" | "enabled" | "disabled";
174
+ flags?: KittyFlagName[];
175
+ };
176
+ declare function resolveFlags(flags: KittyFlagName[]): number;
177
+ type KittyQueryMatch = {
178
+ state: "complete";
179
+ endIndex: number;
180
+ } | {
181
+ state: "partial";
182
+ };
183
+ declare function matchKittyQueryResponse(buffer: number[], startIndex: number): KittyQueryMatch | undefined;
184
+ declare function hasCompleteKittyQueryResponse(buffer: number[]): boolean;
185
+ declare function stripKittyQueryResponsesAndTrailingPartial(buffer: number[]): number[];
186
+ interface KittyKeyboardController {
187
+ init(options: KittyKeyboardOptions | undefined, interactive: boolean): void;
188
+ /**
189
+ * @param sync When true, write the disable-kitty escape synchronously
190
+ * (fs.writeSync) so it reaches the fd before an abrupt signal-driven exit
191
+ * re-raises the signal (G18, Finding A). Defaults to async stream.write for
192
+ * the normal unmount path.
193
+ */
194
+ dispose(sync?: boolean): void;
195
+ readonly isEnabled: boolean;
196
+ }
197
+ declare function createKittyKeyboardController(stdin: NodeJS.ReadStream, stdout: NodeJS.WriteStream): KittyKeyboardController;
198
+ //#endregion
199
+ //#region src/render.d.ts
200
+ interface MountOptions {
201
+ stdout?: NodeJS.WriteStream;
202
+ stdin?: NodeJS.ReadStream;
203
+ stderr?: NodeJS.WriteStream;
204
+ debug?: boolean;
205
+ exitOnCtrlC?: boolean;
206
+ rawMode?: "always" | "auto";
207
+ interactive?: boolean;
208
+ patchConsole?: boolean;
209
+ onRender?: (info: {
210
+ renderTime: number;
211
+ }) => void;
212
+ maxFps?: number;
213
+ isScreenReaderEnabled?: boolean;
214
+ incrementalRendering?: boolean;
215
+ alternateScreen?: boolean;
216
+ kittyKeyboard?: KittyKeyboardOptions;
217
+ }
218
+ interface TuiApp {
219
+ mount(options?: MountOptions): unknown;
220
+ unmount(): void;
221
+ waitUntilExit(): Promise<unknown>;
222
+ waitUntilRenderFlush(): Promise<void>;
223
+ clear(): void;
224
+ }
225
+ type RootProps = Record<string, unknown>;
226
+ declare function createApp(root: Component<RootProps>, rootProps?: RootProps | null): TuiApp;
227
+ interface FocusControllerForTest extends FocusContext {
228
+ __subscriberMapSize: () => number;
229
+ }
230
+ declare function createFocusController(): FocusControllerForTest;
231
+ //#endregion
232
+ //#region src/render-to-string.d.ts
233
+ interface RenderToStringOptions {
234
+ columns?: number;
235
+ }
236
+ interface RenderToStringInternalOptions extends RenderToStringOptions {
237
+ isScreenReaderEnabled?: boolean;
238
+ }
239
+ declare function renderToString(component: Component<Record<string, unknown>>, options?: RenderToStringOptions): string;
240
+ declare function renderToStringWithScreenReader(component: Component<Record<string, unknown>>, options?: RenderToStringInternalOptions): string;
241
+ //#endregion
242
+ //#region src/host/yoga.d.ts
243
+ type YogaCarrier = TuiRoot | TuiBox | TuiText | TuiStatic | TuiTransform;
244
+ declare const yogaNodeTracker: {
245
+ reset(): void;
246
+ snapshot(): {
247
+ created: number;
248
+ freed: number;
249
+ live: number;
250
+ };
251
+ };
252
+ declare function attachYoga(node: YogaCarrier): void;
253
+ //#endregion
254
+ //#region src/paint/screen-reader.d.ts
255
+ interface ScreenReaderOptions {
256
+ parentRole?: string;
257
+ skipStaticElements?: boolean;
258
+ }
259
+ /**
260
+ * Render a TUI node tree to a plain-text string suitable for screen readers.
261
+ *
262
+ * Ported from Ink's `renderNodeToScreenReaderOutput`.
263
+ *
264
+ * - `display: none` nodes are skipped.
265
+ * - Text nodes have their content squashed (no ANSI).
266
+ * - Box/root nodes recursively render children, joined by separator based on flexDirection.
267
+ * - Nodes with `internal_accessibility` get role and state info prepended.
268
+ */
269
+ declare function renderScreenReaderOutput(node: TuiNode, options?: ScreenReaderOptions): string;
270
+ //#endregion
271
+ //#region src/hmr.d.ts
272
+ interface DevErrorInfo {
273
+ message: string;
274
+ stack?: string;
275
+ loc?: {
276
+ file: string;
277
+ line: number;
278
+ column: number;
279
+ };
280
+ }
281
+ type DevState = {
282
+ type: "ok";
283
+ } | {
284
+ type: "error";
285
+ error: DevErrorInfo;
286
+ } | {
287
+ type: "update";
288
+ paths: string[];
289
+ };
290
+ interface HotContext {
291
+ on(event: string, cb: (payload: unknown) => void): void;
292
+ send(event: string, data?: unknown): void;
293
+ }
294
+ declare function isDevConnected(): boolean;
295
+ declare function connectDevtools(hot: HotContext): void;
296
+ //#endregion
297
+ //#region src/io/frame-sink.d.ts
298
+ /**
299
+ * Internal, test-only frame observer.
300
+ *
301
+ * The `@solid-tui/testing` `render()` helper needs to capture each committed
302
+ * frame's CONTENT, with no terminal-control escapes mixed in. Reverse-
303
+ * engineering frames out of the stdout byte stream is fragile: stdout must stay
304
+ * byte-faithful to Ink (cursor hide/show, bracketed-paste enable/disable, BSU/
305
+ * ESU, etc.), so any escape the runtime legitimately writes leaks into the
306
+ * captured frames.
307
+ *
308
+ * Instead, the runtime exposes this per-app frame sink: a callback that the
309
+ * commit path invokes with the EXACT content chunks it writes to stdout (the
310
+ * accumulated `<Static>` history chunk, then the dynamic frame), in write order
311
+ * — but NOT the escapes. The helper passes a sink via a Symbol-keyed mount
312
+ * option (so it never appears on the public `MountOptions` type, keeping that
313
+ * Ink-faithful) and builds `frames[]` / `lastFrame()` from the callbacks.
314
+ *
315
+ * The sink is closure-captured per `mount()` call — there is NO module-global
316
+ * mutable state — so concurrent test files / multiple apps are fully isolated.
317
+ *
318
+ * This is intentionally NOT a public API: it lives behind `@solid-tui/runtime/
319
+ * internal` and is keyed by a unique symbol the runtime reads off the loosely
320
+ * typed mount options.
321
+ */
322
+ type FrameSink = (chunk: string) => void;
323
+ /**
324
+ * Symbol key for the internal frame sink on the mount options object. Unique
325
+ * (created via `Symbol(...)`, not `Symbol.for(...)`) so it can never collide
326
+ * with a user-supplied key and is invisible to normal property enumeration.
327
+ */
328
+ declare const INTERNAL_FRAME_SINK: unique symbol;
329
+ //#endregion
330
+ //#region src/components/error-overview.d.ts
331
+ declare function messageForNonError(value: unknown): string;
332
+ //#endregion
333
+ export { createSignal as $, TuiBox as A, Accessor as B, createKittyKeyboardController as C, matchKittyQueryResponse as D, kittyModifiers as E, createRoot as F, batch as G, JSX$1 as H, createText as I, createContext as J, catchError as K, createTextLeaf as L, TuiRoot as M, TuiText as N, resolveFlags as O, createBox as P, createRoot$1 as Q, AppContext as R, KittyKeyboardOptions as S, kittyFlags as T, JSXElement as U, Component as V, Setter as W, createMemo as X, createEffect as Y, createRenderEffect as Z, TuiApp as _, DevState as a, KittyFlagName as b, ScreenReaderOptions as c, yogaNodeTracker as d, on as et, RenderToStringOptions as f, MountOptions as g, FocusControllerForTest as h, DevErrorInfo as i, useContext as it, TuiNode as j, stripKittyQueryResponsesAndTrailingPartial as k, renderScreenReaderOutput as l, renderToStringWithScreenReader as m, FrameSink as n, splitProps as nt, connectDevtools as o, renderToString as p, children as q, INTERNAL_FRAME_SINK as r, untrack as rt, isDevConnected as s, messageForNonError as t, onCleanup as tt, attachYoga as u, createApp as v, hasCompleteKittyQueryResponse as w, KittyKeyboardController as x, createFocusController as y, CursorPosition as z };
@@ -0,0 +1,2 @@
1
+ import { A as TuiBox, C as createKittyKeyboardController, D as matchKittyQueryResponse, F as createRoot, I as createText, L as createTextLeaf, M as TuiRoot, N as TuiText, O as resolveFlags, P as createBox, R as AppContext, a as DevState, c as ScreenReaderOptions, d as yogaNodeTracker, h as FocusControllerForTest, i as DevErrorInfo, j as TuiNode, k as stripKittyQueryResponsesAndTrailingPartial, l as renderScreenReaderOutput, m as renderToStringWithScreenReader, n as FrameSink, o as connectDevtools, r as INTERNAL_FRAME_SINK, s as isDevConnected, t as messageForNonError, u as attachYoga, w as hasCompleteKittyQueryResponse, x as KittyKeyboardController, y as createFocusController } from "./internal-BBq9eUYH.mjs";
2
+ export { type AppContext, type DevErrorInfo, type DevState, type FocusControllerForTest, type FrameSink, INTERNAL_FRAME_SINK, type KittyKeyboardController, type ScreenReaderOptions, type TuiBox, type TuiNode, type TuiRoot, type TuiText, attachYoga, connectDevtools, createBox, createFocusController, createKittyKeyboardController, createRoot, createText, createTextLeaf, hasCompleteKittyQueryResponse, isDevConnected, matchKittyQueryResponse, messageForNonError, renderScreenReaderOutput, renderToStringWithScreenReader, resolveFlags, stripKittyQueryResponsesAndTrailingPartial, yogaNodeTracker };
@@ -0,0 +1,2 @@
1
+ import { B as attachYoga, C as hasCompleteKittyQueryResponse, D as resolveFlags, E as matchKittyQueryResponse, G as createText, K as createTextLeaf, O as stripKittyQueryResponsesAndTrailingPartial, S as createKittyKeyboardController, U as createBox, V as yogaNodeTracker, W as createRoot, _ as renderScreenReaderOutput, a as connectDevtools, c as messageForNonError, g as INTERNAL_FRAME_SINK, i as createFocusController, n as renderToStringWithScreenReader, o as isDevConnected } from "./render-to-string-zweiEGvu.mjs";
2
+ export { INTERNAL_FRAME_SINK, attachYoga, connectDevtools, createBox, createFocusController, createKittyKeyboardController, createRoot, createText, createTextLeaf, hasCompleteKittyQueryResponse, isDevConnected, matchKittyQueryResponse, messageForNonError, renderScreenReaderOutput, renderToStringWithScreenReader, resolveFlags, stripKittyQueryResponsesAndTrailingPartial, yogaNodeTracker };