@valyrianjs/terminal 0.1.1 → 0.2.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.
Files changed (107) hide show
  1. package/README.md +105 -55
  2. package/dist/ansi.d.ts +20 -4
  3. package/dist/ansi.d.ts.map +1 -1
  4. package/dist/ansi.js +171 -47
  5. package/dist/ansi.js.map +1 -1
  6. package/dist/editor-state.d.ts +22 -0
  7. package/dist/editor-state.d.ts.map +1 -0
  8. package/dist/editor-state.js +110 -0
  9. package/dist/editor-state.js.map +1 -0
  10. package/dist/events.d.ts +1 -4
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js +15 -38
  13. package/dist/events.js.map +1 -1
  14. package/dist/index.d.ts +5 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/keymap.d.ts +7 -0
  19. package/dist/keymap.d.ts.map +1 -0
  20. package/dist/keymap.js +133 -0
  21. package/dist/keymap.js.map +1 -0
  22. package/dist/layout.d.ts +10 -1
  23. package/dist/layout.d.ts.map +1 -1
  24. package/dist/layout.js +97 -7
  25. package/dist/layout.js.map +1 -1
  26. package/dist/mouse.d.ts +1 -0
  27. package/dist/mouse.d.ts.map +1 -1
  28. package/dist/mouse.js +24 -1
  29. package/dist/mouse.js.map +1 -1
  30. package/dist/output-writer.d.ts +9 -0
  31. package/dist/output-writer.d.ts.map +1 -0
  32. package/dist/output-writer.js +79 -0
  33. package/dist/output-writer.js.map +1 -0
  34. package/dist/paste.d.ts +7 -0
  35. package/dist/paste.d.ts.map +1 -0
  36. package/dist/paste.js +18 -0
  37. package/dist/paste.js.map +1 -0
  38. package/dist/primitives.d.ts +15 -3
  39. package/dist/primitives.d.ts.map +1 -1
  40. package/dist/primitives.js +9 -1
  41. package/dist/primitives.js.map +1 -1
  42. package/dist/render.d.ts +9 -4
  43. package/dist/render.d.ts.map +1 -1
  44. package/dist/render.js +923 -68
  45. package/dist/render.js.map +1 -1
  46. package/dist/runtime.d.ts +29 -0
  47. package/dist/runtime.d.ts.map +1 -0
  48. package/dist/runtime.js +209 -0
  49. package/dist/runtime.js.map +1 -0
  50. package/dist/scheduler.d.ts +8 -0
  51. package/dist/scheduler.d.ts.map +1 -0
  52. package/dist/scheduler.js +24 -0
  53. package/dist/scheduler.js.map +1 -0
  54. package/dist/session.d.ts.map +1 -1
  55. package/dist/session.js +858 -199
  56. package/dist/session.js.map +1 -1
  57. package/dist/stream-log.d.ts +40 -0
  58. package/dist/stream-log.d.ts.map +1 -0
  59. package/dist/stream-log.js +73 -0
  60. package/dist/stream-log.js.map +1 -0
  61. package/dist/text.d.ts +3 -0
  62. package/dist/text.d.ts.map +1 -0
  63. package/dist/text.js +19 -0
  64. package/dist/text.js.map +1 -0
  65. package/dist/theme.d.ts +7 -0
  66. package/dist/theme.d.ts.map +1 -0
  67. package/dist/theme.js +254 -0
  68. package/dist/theme.js.map +1 -0
  69. package/dist/tree.d.ts +2 -0
  70. package/dist/tree.d.ts.map +1 -1
  71. package/dist/tree.js +42 -1
  72. package/dist/tree.js.map +1 -1
  73. package/dist/types.d.ts +203 -24
  74. package/dist/types.d.ts.map +1 -1
  75. package/docs/api-reference.md +313 -142
  76. package/docs/assets/quick-note.svg +13 -0
  77. package/docs/cookbook.md +296 -201
  78. package/docs/core-concepts.md +143 -55
  79. package/docs/getting-started.md +209 -90
  80. package/docs/interaction-model.md +98 -54
  81. package/docs/primitive-gallery.md +370 -0
  82. package/docs/session-runtime.md +131 -362
  83. package/docs/valyrian-modules.md +3196 -0
  84. package/llms-full.txt +5377 -0
  85. package/package.json +21 -8
  86. package/src/ansi.ts +269 -0
  87. package/src/clipboard.ts +76 -0
  88. package/src/editor-state.ts +162 -0
  89. package/src/events.ts +163 -0
  90. package/src/index.ts +95 -0
  91. package/src/keymap.ts +151 -0
  92. package/src/layout.ts +282 -0
  93. package/src/mouse.ts +68 -0
  94. package/src/output-writer.ts +93 -0
  95. package/src/paste.ts +23 -0
  96. package/src/primitives.ts +55 -0
  97. package/src/render.ts +1204 -0
  98. package/src/runtime.ts +267 -0
  99. package/src/scheduler.ts +33 -0
  100. package/src/session.ts +1408 -0
  101. package/src/stream-log.ts +96 -0
  102. package/src/text.ts +20 -0
  103. package/src/theme.ts +263 -0
  104. package/src/tree.ts +169 -0
  105. package/src/types.ts +541 -0
  106. package/tsconfig.json +4 -7
  107. package/docs/local-demo.md +0 -28
@@ -1,451 +1,220 @@
1
1
  # Session Runtime
2
2
 
3
- This guide documents the interactive runtime of `valyrianjs-terminal`: when to use `mountTerminal()`, how to configure a session, and which capabilities `TerminalSession` exposes during its lifecycle.
3
+ This guide explains how to use `mountTerminal()` in common cases first, then how to integrate it with host streams and runtime features.
4
4
 
5
- If you are starting from scratch, read `docs/core-concepts.md` first. For the general per-primitive interaction model, see `docs/interaction-model.md`. The focus here is operational: mounting, rerendering, streams, focus, keyboard, clipboard, and mouse.
5
+ ## When to use the session runtime
6
6
 
7
- ## When to use `mountTerminal()`
7
+ Use `mountTerminal()` when the UI needs one or more of these capabilities:
8
8
 
9
- Use `mountTerminal()` when you need a live session that:
9
+ - focus and keyboard dispatch
10
+ - repeated renders after state changes
11
+ - clipboard integration
12
+ - mouse and coordinate-based interaction
13
+ - `stdin` and `stdout` connection
14
+ - ANSI output for a real terminal
10
15
 
11
- - reevaluates the tree when external state changes
12
- - preserves focus across renders
13
- - dispatches keyboard input per session
14
- - connects `stdin` and `stdout`
15
- - responds to coordinates and mouse events
16
+ Use `renderTerminal()` for deterministic static plain text.
16
17
 
17
- If you only want to inspect layout, generate snapshots, or produce static output, `renderTerminal()` is still the simplest option.
18
+ ## Practical recommendations
18
19
 
19
- ```tsx
20
- /** @jsx v */
21
- /** @jsxFrag v.fragment */
20
+ - Start without streams while building examples and scripted flows.
21
+ - Give stable `id` values to focusable primitives you want to control.
22
+ - Keep application state outside the renderer.
23
+ - Valyrian reactive state read by components refreshes the terminal when it changes, including external async changes. Call `session.update()` for non-reactive state or an explicit manual refresh.
24
+ - Call `session.destroy()` when a session is connected to real streams.
25
+ - Design keyboard-first flows and connect resize, modified keys, clipboard, and mouse through the host capabilities available in each terminal environment.
26
+
27
+ ## Common runtime usage
28
+
29
+ Full example: [`examples/docs/interactive-note.tsx`](../examples/docs/interactive-note.tsx). Run it with `bun examples/docs/interactive-note.tsx`, type a note, press `Enter`, and quit with `Ctrl+C`.
22
30
 
23
- import { v } from "valyrian.js";
24
- import { Input, Screen, Text, mountTerminal } from "valyrianjs-terminal";
31
+ ```tsx
32
+ import { Input, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
25
33
 
26
34
  const state = { value: "" };
27
35
 
28
- const session = mountTerminal(() => (
29
- <Screen title="Runtime Demo">
30
- <Input
31
- id="name"
32
- value={state.value}
33
- onchange={(event) => {
34
- state.value = event.value;
35
- }}
36
- />
37
- <Text>Current: {state.value || "(empty)"}</Text>
38
- </Screen>
39
- ));
36
+ function App() {
37
+ return (
38
+ <Screen title="Runtime demo">
39
+ <Input
40
+ id="name"
41
+ value={state.value}
42
+ placeholder="Name"
43
+ onchange={(event) => {
44
+ state.value = event.value;
45
+ }}
46
+ />
47
+ <Text>Current: {state.value || "(empty)"}</Text>
48
+ </Screen>
49
+ );
50
+ }
51
+
52
+ const session = mountTerminal(<App />);
40
53
 
41
54
  session.focus("name");
42
55
  session.dispatchKey("A");
43
56
  session.dispatchKey("B");
44
57
 
45
58
  console.log(session.output());
59
+ session.destroy();
46
60
  ```
47
61
 
48
- ## `TerminalMountOptions`
49
-
50
- `mountTerminal(input, options?)` accepts `TerminalMountOptions` with four integration points:
51
-
52
- ### `ansi?: boolean`
53
-
54
- Enables incremental ANSI output to `stdout`. `session.ansiOutput()` is still available even if you do not enable this option; what changes is the session's automatic write behavior when it rerenders. If you do not enable it, the primary output written to `stdout` is plain text.
55
-
56
- ### `clipboard?: TerminalClipboardAdapter | false`
57
-
58
- - if you pass an adapter, the session uses `readText()` and `writeText()` when handling shortcuts such as `CTRL_C` or `CTRL_V`
59
- - if you pass `false`, system clipboard integration is disabled
60
- - even with `clipboard: false`, `session.clipboard()` and `session.setClipboard()` still work as the session buffer
61
-
62
- ```tsx
63
- /** @jsx v */
64
- /** @jsxFrag v.fragment */
65
-
66
- import { v } from "valyrian.js";
67
- import { Screen, mountTerminal } from "valyrianjs-terminal";
68
-
69
- const app = () => <Screen title="Clipboard Demo" />;
70
-
71
- const session = mountTerminal(app, {
72
- clipboard: {
73
- readText() {
74
- return "pasted value";
75
- },
76
- writeText(value) {
77
- console.log("copied:", value);
78
- }
79
- }
80
- });
81
- ```
82
-
83
- ```tsx
84
- /** @jsx v */
85
- /** @jsxFrag v.fragment */
86
-
87
- import { v } from "valyrian.js";
88
- import { Screen, mountTerminal } from "valyrianjs-terminal";
89
-
90
- const app = () => <Screen title="Clipboard Disabled" />;
91
-
92
- const session = mountTerminal(app, { clipboard: false });
93
- session.setClipboard("fallback value");
94
- ```
95
-
96
- ### `stdin?`
97
-
98
- Input stream for keyboard and mouse. The session subscribes to `data`, tries to enable raw mode with `setRawMode(true)` if available, and calls `resume()` if the stream supports it.
99
-
100
- Expected contract:
101
-
102
- - `on("data", listener)` is required
103
- - `off(...)` or `removeListener(...)` is optional for cleanup
104
- - `setRawMode?(boolean)` is optional
105
- - `resume?()` and `pause?()` are optional
106
-
107
- ### `stdout?`
108
-
109
- Write target for the output produced on each rerender.
110
-
111
- - with `ansi: false`, writes plain text
112
- - with `ansi: true`, writes incremental ANSI diffs
113
-
114
- ```tsx
115
- /** @jsx v */
116
- /** @jsxFrag v.fragment */
117
-
118
- import { v } from "valyrian.js";
119
- import { Screen, mountTerminal } from "valyrianjs-terminal";
120
-
121
- const app = () => <Screen title="ANSI Stream Demo" />;
122
-
123
- const writes: string[] = [];
124
-
125
- const session = mountTerminal(app, {
126
- ansi: true,
127
- stdout: {
128
- write(chunk) {
129
- writes.push(String(chunk));
130
- }
131
- }
132
- });
133
- ```
134
-
135
- ## `TerminalSession` lifecycle
136
-
137
- The session keeps a current frame, a current output, and the interactive state associated with the focusable tree. In practice, the cycle is:
62
+ The session lifecycle is:
138
63
 
139
64
  1. mount with `mountTerminal()`
140
- 2. read with `output()` or `ansiOutput()`
141
- 3. mutate external state or dispatch interactions
142
- 4. rerender with `update()` or through events that already trigger rerenders
65
+ 2. read `output()` or `ansiOutput()`
66
+ 3. dispatch input or update external state
67
+ 4. rerender with `update()` when needed
143
68
  5. close with `destroy()`
144
69
 
145
- ### `update()`
146
-
147
- Reevaluates `input`, recalculates the frame, and returns the current plain-text output.
148
-
149
- Use it when you changed state outside the session handlers.
150
-
151
- ```tsx
152
- const state = { count: 0 };
153
-
154
- const session = mountTerminal(() => (
155
- <Screen>
156
- <Text>Count: {state.count}</Text>
157
- </Screen>
158
- ));
159
-
160
- state.count += 1;
161
- session.update();
162
-
163
- console.log(session.output());
164
- ```
70
+ ## Input, output, and cleanup
165
71
 
166
72
  ### `output()`
167
73
 
168
- Returns the current frame as plain text. It is the most direct way to:
169
-
170
- - inspect the current state
171
- - validate snapshots
172
- - test focus and content without ANSI
74
+ Returns the current plain-text frame. Use it for snapshots, examples, and content inspection.
173
75
 
174
76
  ### `ansiOutput()`
175
77
 
176
- Returns the current frame serialized as full ANSI output, including the cursor and visual spans. It is useful when you want to:
78
+ Returns the current frame serialized with ANSI cursor movement and style spans.
177
79
 
178
- - inspect cursor position
179
- - validate selection or focus in an ANSI terminal
180
- - integrate with a layer that consumes full escape sequences
80
+ Related example: [`examples/docs/theme-colors.tsx`](../examples/docs/theme-colors.tsx). Run it with `bun examples/docs/theme-colors.tsx`, press `Tab`, and quit with `Ctrl+C`.
181
81
 
182
82
  ```tsx
183
- /** @jsx v */
184
- /** @jsxFrag v.fragment */
185
-
186
- import { v } from "valyrian.js";
187
- import { Screen, mountTerminal } from "valyrianjs-terminal";
188
-
189
- const app = () => <Screen title="ANSI Frame Demo" />;
190
-
191
- const session = mountTerminal(app, { ansi: true });
192
- const ansi = session.ansiOutput();
193
- console.log(ansi);
194
- ```
83
+ import { Screen, mountTerminal } from "@valyrianjs/terminal";
195
84
 
196
- ### `destroy()`
197
-
198
- Removes `stdin` listeners, tries to exit raw mode with `setRawMode(false)`, and calls `pause()` if available.
85
+ function App() {
86
+ return <Screen title="ANSI frame" />;
87
+ }
199
88
 
200
- Always call it when you mounted the session with a real `stdin` or a connected emitter.
89
+ const session = mountTerminal(<App />, { runtime: "headless" });
201
90
 
202
- ```ts
91
+ console.log(session.ansiOutput());
203
92
  session.destroy();
204
93
  ```
205
94
 
206
- ## Focus and coordinates
207
-
208
- Focus lives at the session level. To participate in this flow, interactive nodes need an `id`.
209
-
210
- ### `focus(id)`
211
-
212
- Focuses a node by `id`. Returns `true` if it found one.
213
-
214
- ### `focusNext()` and `focusPrev()`
215
-
216
- Walk the current focusable-element order. `dispatchKey("TAB")` and `dispatchKey("SHIFT_TAB")` use the same flow.
95
+ `ansiOutput()` is an explicit inspection/export path. Keep ordinary `Text`, input values, list rows, and log entries as text content. Advanced span serialization tokens are documented in [Advanced: span serialization tokens](./api-reference.md#advanced-span-serialization-tokens) for low-level renderer integrations.
217
96
 
218
- ### `focusAt(x, y)`
219
-
220
- Looks up the hitbox at the current coordinates, updates semantic hover when applicable, and focuses that node.
97
+ ### `update()`
221
98
 
222
- ### `clickAt(x, y)`
99
+ Re-evaluates the session input and returns the current plain-text output.
223
100
 
224
- - if it lands on a `Button`, it triggers its action
225
- - if it lands on an `Input`, it focuses it and places the cursor based on the coordinate
226
- - if it lands on another focusable surface, it moves focus there
101
+ Related example: [`examples/docs/component-composition.tsx`](../examples/docs/component-composition.tsx). Run it with `bun examples/docs/component-composition.tsx`, switch cards with `N/P`, and quit with `Ctrl+C`.
227
102
 
228
103
  ```tsx
229
- const session = mountTerminal(() => (
230
- <Screen>
231
- <Input id="name" value="abc" />
232
- <Button id="save">Save</Button>
233
- </Screen>
234
- ));
235
-
236
- session.focusAt(2, 1);
237
- session.clickAt(3, 2);
238
- ```
239
-
240
- ## Per-session keyboard dispatch
104
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
241
105
 
242
- `dispatchKey(key)` processes a normalized key against the focused node and returns the current plain-text output.
106
+ const state = { count: 0 };
243
107
 
244
- Shortcuts supported by the public API and observable in tests:
108
+ function App() {
109
+ return (
110
+ <Screen title="Update demo">
111
+ <Text>Count: {state.count}</Text>
112
+ </Screen>
113
+ );
114
+ }
245
115
 
246
- - global: `TAB`, `SHIFT_TAB`
247
- - `Input`: single-byte characters, `ENTER`, `LEFT`, `RIGHT`, `SHIFT_LEFT`, `SHIFT_RIGHT`, `ALT_LEFT`, `ALT_RIGHT`, `HOME`, `END`, `CTRL_A`, `CTRL_C`, `CTRL_X`, `CTRL_V`, `BACKSPACE`, `DELETE`
248
- - `Button`: `ENTER`, `SPACE`
249
- - `List`: `UP`, `DOWN`, `LEFT`, `RIGHT`, `ENTER`
250
- - `ScrollView`: `UP`, `DOWN`
116
+ const session = mountTerminal(<App />);
251
117
 
252
- ```tsx
253
- const state = { value: "", saved: "" };
254
-
255
- const session = mountTerminal(() => (
256
- <Screen>
257
- <Input
258
- id="name"
259
- value={state.value}
260
- onchange={(event) => {
261
- state.value = event.value;
262
- }}
263
- onsubmit={(event) => {
264
- state.saved = event.value;
265
- }}
266
- />
267
- </Screen>
268
- ));
118
+ state.count += 1;
119
+ session.update();
269
120
 
270
- session.focus("name");
271
- session.dispatchKey("A");
272
- session.dispatchKey("B");
273
- session.dispatchKey("LEFT");
274
- session.dispatchKey("C");
275
- session.dispatchKey("ENTER");
121
+ console.log(session.output());
122
+ session.destroy();
276
123
  ```
277
124
 
278
- ## Clipboard adapter and `clipboard: false`
279
-
280
- The session separates two concepts:
281
-
282
- - the session clipboard buffer: `clipboard()` and `setClipboard()`
283
- - system integration: `options.clipboard`
125
+ ### `destroy()`
284
126
 
285
- With an adapter, input shortcuts read from and write to that adapter. Without an adapter, the session keeps a local value. With `clipboard: false`, external integration is disabled, but you can still use the local session buffer for tests or manual integrations.
127
+ Removes listeners and restores supported terminal state when a session connected to streams changed it. Always call it when the session owns real input or output resources.
286
128
 
287
- ```tsx
288
- const state = { value: "abcd" };
289
-
290
- const session = mountTerminal(() => (
291
- <Screen>
292
- <Input
293
- id="name"
294
- value={state.value}
295
- onchange={(event) => {
296
- state.value = event.value;
297
- }}
298
- />
299
- </Screen>
300
- ), { clipboard: false });
129
+ ## Advanced host integration
301
130
 
302
- session.focus("name");
303
- session.setClipboard("XY");
304
- session.dispatchKey("END");
305
- session.dispatchKey("CTRL_V");
306
- ```
131
+ ### Mount options
307
132
 
308
- ## `stdin` and `stdout` streams
133
+ `mountTerminal(input, options?)` accepts options for host integration. `mountTerminal(<App />)` is the normal interactive path; it uses process stdio, ANSI diffs, alternate screen, and a hidden cursor only when called from a real interactive TTY. Imports and non-TTY/test runs do not auto-attach stdio.
309
134
 
310
- Connecting streams makes the session useful as a CLI runtime, not just as a test helper.
135
+ - `runtime?: "app" | "headless"` - selects interactive app behavior or scriptable plain output. Omitted runtime uses app mode only for interactive TTY hosts and stays headless in non-TTY/CI contexts.
136
+ - `alternateScreen?: boolean` - enters the terminal alternate screen and restores it on destroy by default. Real interactive app mode enables it by default unless overridden.
137
+ - `hideCursor?: boolean` - expresses that the session should hide the terminal cursor while it owns the screen when the host supports it. Real interactive app mode enables it by default unless overridden.
138
+ - `restoreOnDestroy?: boolean` - controls whether destroy should restore supported cursor and alternate-screen state.
139
+ - `clipboard?: TerminalClipboardAdapter | false` - connects a custom clipboard adapter or disables external clipboard integration.
140
+ - `cols?: number` and `rows?: number` - set the initial terminal size.
141
+ - `stdin?` - receives keyboard, paste, and mouse input.
142
+ - `stdout?` - receives rendered output.
311
143
 
312
- Observable behavior:
144
+ ### Clipboard adapter
313
145
 
314
- - on mount, the session subscribes to `stdin.on("data", listener)`
315
- - if `stdin.setRawMode` exists, it tries `setRawMode(true)`
316
- - if `stdin.resume` exists, it tries `resume()`
317
- - on each rerender, if `stdout.write` exists, the session writes the current frame
318
- - on destroy, it tries to unsubscribe and restore raw mode
146
+ Related example: [`examples/docs/interactive-note.tsx`](../examples/docs/interactive-note.tsx). Run it with `bun examples/docs/interactive-note.tsx`, type a note, press `Enter`, and quit with `Ctrl+C`.
319
147
 
320
148
  ```tsx
321
- /** @jsx v */
322
- /** @jsxFrag v.fragment */
149
+ import { Screen, mountTerminal } from "@valyrianjs/terminal";
323
150
 
324
- import { EventEmitter } from "node:events";
325
- import { v } from "valyrian.js";
326
- import { Screen, mountTerminal } from "valyrianjs-terminal";
151
+ function App() {
152
+ return <Screen title="Clipboard" />;
153
+ }
327
154
 
328
- const app = () => <Screen title="Stream Demo" />;
329
-
330
- const stdin = new EventEmitter() as EventEmitter & {
331
- setRawMode?: (value: boolean) => void;
332
- resume?: () => void;
333
- pause?: () => void;
334
- };
335
-
336
- const writes: string[] = [];
337
-
338
- const session = mountTerminal(app, {
339
- stdin,
340
- stdout: {
341
- write(chunk) {
342
- writes.push(String(chunk));
155
+ const session = mountTerminal(<App />, {
156
+ clipboard: {
157
+ readText() {
158
+ return "pasted value";
159
+ },
160
+ writeText(value) {
161
+ console.log("copied:", value);
343
162
  }
344
163
  }
345
164
  });
346
165
 
347
- stdin.emit("data", "A");
348
- stdin.emit("data", "\r");
349
166
  session.destroy();
350
167
  ```
351
168
 
352
- ## ANSI output
353
-
354
- There are two practical ways to work with ANSI:
355
-
356
- - `ansi: true` + `stdout`: the session emits incremental diffs on each rerender
357
- - `session.ansiOutput()`: retrieves the full ANSI frame for the current state
169
+ Passing `clipboard: false` disables external clipboard integration while preserving the session clipboard buffer through `clipboard()` and `setClipboard()`.
358
170
 
359
- This is especially useful when the consuming terminal needs to minimize repaints or when you want to verify cursor, focus, and selection with real escape sequences.
171
+ ### Streams
360
172
 
361
- ```tsx
362
- const state = { value: "AB" };
363
-
364
- const session = mountTerminal(() => (
365
- <Screen title="ANSI Demo">
366
- <Input
367
- id="name"
368
- value={state.value}
369
- onchange={(event) => {
370
- state.value = event.value;
371
- }}
372
- />
373
- </Screen>
374
- ), { ansi: true });
375
-
376
- session.focus("name");
377
- session.dispatchKey("LEFT");
378
-
379
- console.log(session.ansiOutput());
380
- ```
173
+ `stdin` should provide `on("data", listener)`. Cleanup methods such as `off(...)` or `removeListener(...)` are used when available. `setRawMode`, `resume`, and `pause` are optional.
381
174
 
382
- ## SGR mouse, wheel, and pointer capture
175
+ `stdout` should provide `write(chunk: string)`. Optional `columns` and `rows` can provide initial dimensions. Optional `resize` and `drain` events are used only when the stream also provides listener cleanup.
383
176
 
384
- When you connect `stdin`, the session accepts SGR mouse sequences and translates them into practical interaction over the current frame.
177
+ ### Size and resize
385
178
 
386
- ### Press, drag, and release
179
+ `size()` returns `{ cols, rows }`. `resize(cols, rows)` validates positive integer dimensions, stores them, and rerenders once.
387
180
 
388
- - `press`: focuses and activates the hitbox at the coordinate
389
- - `drag`: if an `Input` is active through the mouse, it extends selection; in `List` and `ScrollView`, it updates row hover
390
- - `release`: ends mouse selection and closes capture when applicable
391
-
392
- ```ts
393
- stdin.emit("data", "\u001b[<0;4;1M");
394
- stdin.emit("data", "\u001b[<32;7;1M");
395
- stdin.emit("data", "\u001b[<0;7;1m");
396
- ```
181
+ Related example: [`examples/docs/background-fill.tsx`](../examples/docs/background-fill.tsx). Run it with `bun examples/docs/background-fill.tsx`, switch surfaces with `B`, and quit with `Ctrl+C`.
397
182
 
398
- That pattern is enough to select text inside an `Input` by coordinates.
399
-
400
- ### Wheel
183
+ ```tsx
184
+ import { Screen, mountTerminal } from "@valyrianjs/terminal";
401
185
 
402
- Wheel input is dispatched as vertical navigation on the node under the pointer:
186
+ function App() {
187
+ return <Screen title="Resizable" />;
188
+ }
403
189
 
404
- - `wheel-up` is equivalent to `dispatchKey("UP")`
405
- - `wheel-down` is equivalent to `dispatchKey("DOWN")`
190
+ const session = mountTerminal(<App />, { cols: 80, rows: 24 });
406
191
 
407
- ```ts
408
- stdin.emit("data", "\u001b[<65;2;1M");
192
+ console.log(session.size());
193
+ session.resize(100, 30);
194
+ console.log(session.size());
195
+ session.destroy();
409
196
  ```
410
197
 
411
- In a focused `ScrollView`, or one resolved by coordinates, that scrolls the viewport.
198
+ Automatic resize works with compatible output sources that emit supported resize events after updating their `columns` and `rows` values.
412
199
 
413
- ### Pointer capture in practice
200
+ ### Keyboard, paste, and mouse input
414
201
 
415
- `pointerCapture` currently applies to `List` and `ScrollView`.
202
+ `dispatchKey(key)` injects a normalized key directly. Connected `stdin` can also deliver keyboard input, terminal paste input, and supported SGR mouse input.
416
203
 
417
- Without `pointerCapture`, semantic hover depends on the drag staying inside the visible hitbox. With `pointerCapture`, the session keeps the interaction during the drag even if the pointer leaves the initial area, and triggers:
204
+ Bracketed paste is handled as terminal input: pasted text is inserted into the focused `Input` or `Editor` as content instead of being treated as separate key commands. Multiline `Editor` cursor columns are reported as JavaScript UTF-16 string columns, not as a complete terminal grapheme-width model.
418
205
 
419
- - `oncapturestart`
420
- - `oncaptureend`
206
+ Mouse input can focus or activate hitboxes, update hover state, and map wheel input to vertical navigation. Keep keyboard alternatives for important actions so every terminal workflow has a reliable input path.
421
207
 
422
- This is useful for:
208
+ ## Host integration behavior
423
209
 
424
- - lists that need to keep tracking the active row during a drag
425
- - scroll views that keep hover or release behavior even if the pointer ends outside the viewport
210
+ - Modified keys follow sequences reported by the connected terminal.
211
+ - Clipboard behavior uses the provided adapter or the session clipboard buffer.
212
+ - Mouse behavior uses supported terminal input sequences.
213
+ - Automatic resize works with compatible output sources and cleanup-capable listeners.
214
+ - Use session runtime for terminal rendering, focus, input, output, and cleanup. Keep routing, persistence, command orchestration, and process management in the app layer.
426
215
 
427
- ```tsx
428
- const session = mountTerminal(() => (
429
- <Screen>
430
- <List
431
- id="menu"
432
- pointerCapture
433
- items={["Open", "Save", "Exit"]}
434
- oncapturestart={(event) => console.log(event)}
435
- oncaptureend={(event) => console.log(event)}
436
- />
437
- </Screen>
438
- ), { stdin, clipboard: false });
439
-
440
- stdin.emit("data", "\u001b[<0;2;1M");
441
- stdin.emit("data", "\u001b[<32;99;99M");
442
- stdin.emit("data", "\u001b[<0;99;99m");
443
- ```
444
-
445
- ## Practical recommendations
216
+ ## Where to go next
446
217
 
447
- - use `renderTerminal()` for snapshots and `mountTerminal()` for interactive runtime
448
- - give stable `id` values to `Input`, `Button`, `List`, and `ScrollView` if you plan to use focus, coordinates, or programmatic dispatch
449
- - call `update()` only when you changed state outside handlers already connected to the session
450
- - always call `destroy()` when mounting with `stdin`
451
- - use `clipboard: false` in tests or environments where you do not want to depend on the system clipboard
218
+ - [Getting Started](./getting-started.md) for the first project.
219
+ - [Interaction Model](./interaction-model.md) for per-primitive behavior.
220
+ - [API Reference](./api-reference.md) for exact methods, props, and payloads.