@valyrianjs/terminal 0.1.0 → 0.1.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.
- package/README.md +105 -55
- package/dist/ansi.d.ts +20 -4
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +171 -47
- package/dist/ansi.js.map +1 -1
- package/dist/editor-state.d.ts +22 -0
- package/dist/editor-state.d.ts.map +1 -0
- package/dist/editor-state.js +110 -0
- package/dist/editor-state.js.map +1 -0
- package/dist/events.d.ts +1 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +15 -38
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/keymap.d.ts +7 -0
- package/dist/keymap.d.ts.map +1 -0
- package/dist/keymap.js +133 -0
- package/dist/keymap.js.map +1 -0
- package/dist/layout.d.ts +10 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +97 -7
- package/dist/layout.js.map +1 -1
- package/dist/mouse.d.ts +1 -0
- package/dist/mouse.d.ts.map +1 -1
- package/dist/mouse.js +24 -1
- package/dist/mouse.js.map +1 -1
- package/dist/output-writer.d.ts +9 -0
- package/dist/output-writer.d.ts.map +1 -0
- package/dist/output-writer.js +79 -0
- package/dist/output-writer.js.map +1 -0
- package/dist/paste.d.ts +7 -0
- package/dist/paste.d.ts.map +1 -0
- package/dist/paste.js +18 -0
- package/dist/paste.js.map +1 -0
- package/dist/primitives.d.ts +8 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +9 -1
- package/dist/primitives.js.map +1 -1
- package/dist/render.d.ts +8 -3
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +840 -67
- package/dist/render.js.map +1 -1
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +215 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +24 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +729 -199
- package/dist/session.js.map +1 -1
- package/dist/stream-log.d.ts +40 -0
- package/dist/stream-log.d.ts.map +1 -0
- package/dist/stream-log.js +73 -0
- package/dist/stream-log.js.map +1 -0
- package/dist/text.d.ts +3 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +19 -0
- package/dist/text.js.map +1 -0
- package/dist/theme.d.ts +7 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +254 -0
- package/dist/theme.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +42 -1
- package/dist/tree.js.map +1 -1
- package/dist/types.d.ts +183 -18
- package/dist/types.d.ts.map +1 -1
- package/docs/api-reference.md +302 -136
- package/docs/assets/quick-note.svg +13 -0
- package/docs/cookbook.md +297 -202
- package/docs/core-concepts.md +143 -55
- package/docs/getting-started.md +209 -90
- package/docs/interaction-model.md +95 -61
- package/docs/primitive-gallery.md +365 -0
- package/docs/session-runtime.md +132 -363
- package/docs/valyrian-modules.md +3196 -0
- package/llms-full.txt +5357 -0
- package/package.json +21 -8
- package/src/ansi.ts +269 -0
- package/src/clipboard.ts +76 -0
- package/src/editor-state.ts +162 -0
- package/src/events.ts +163 -0
- package/src/index.ts +92 -0
- package/src/keymap.ts +151 -0
- package/src/layout.ts +282 -0
- package/src/mouse.ts +68 -0
- package/src/output-writer.ts +93 -0
- package/src/paste.ts +23 -0
- package/src/primitives.ts +52 -0
- package/src/render.ts +1107 -0
- package/src/runtime.ts +273 -0
- package/src/scheduler.ts +33 -0
- package/src/session.ts +1260 -0
- package/src/stream-log.ts +96 -0
- package/src/text.ts +20 -0
- package/src/theme.ts +263 -0
- package/src/tree.ts +169 -0
- package/src/types.ts +523 -0
- package/tsconfig.json +4 -7
- package/docs/local-demo.md +0 -28
package/docs/core-concepts.md
CHANGED
|
@@ -1,93 +1,181 @@
|
|
|
1
1
|
# Core Concepts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document explains the mental model for ValyrianJS Terminal. Read it after [Getting Started](./getting-started.md), then use the Cookbook, Primitive Gallery, and interaction guides before opening the API Reference as a lookup document.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Primitives
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
ValyrianJS Terminal renders terminal nodes, not HTML. The public primitives are grouped by role:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- layout: `Screen`, `Box`, `View`, `Pane`, `Split`, `Fixed`, `Overlay`, `FocusScope`, `Table`, `Row`, `Td`
|
|
10
|
+
- content: `Text`, `LogView`
|
|
11
|
+
- interaction: `Input`, `Editor`, `Button`, `List`, `ScrollView`
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- interaccion: `Input`, `Button`, `List`, `ScrollView`
|
|
13
|
+
`Screen` is the usual root. `Box`, `View`, `Pane`, and `Split` compose rows and columns. See [Primitive Gallery](./primitive-gallery.md) for a practical map of every primitive and complete mini apps such as [`examples/docs/primitive-layout-shell.tsx`](../examples/docs/primitive-layout-shell.tsx), which runs with `bun examples/docs/primitive-layout-shell.tsx` and exits with `Ctrl+C`. `Fixed` reserves top, bottom, left, or right regions when it is a direct child of `Screen` or `Pane`. `Overlay` draws a clipped region over the current frame.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
`Screen`, `Box`, `View`, `Pane`, and `Split` use block-like layout defaults inside a render or session context. `Box`, `View`, and `Pane` take available width by default and keep content height by default. `Split` takes the available render area by default because it needs both axes to divide cells. Explicit numeric `width` and `height` always win. Use `fill` when a block surface should consume the full available height instead of stacking at content height.
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## Static render vs mounted session
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
There are two main entry points:
|
|
19
20
|
|
|
20
|
-
- `renderTerminal()`
|
|
21
|
-
- `mountTerminal()`
|
|
21
|
+
- `renderTerminal()` returns plain text for a terminal tree.
|
|
22
|
+
- `mountTerminal()` creates a `TerminalSession` with focus, dispatch, output, lifecycle, and optional stream integration.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Use `renderTerminal()` for static output, examples, and snapshots. Use `mountTerminal()` when a UI needs interaction or repeated renders.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
- si necesitas interaccion o runtime vivo, usa `mountTerminal()`
|
|
26
|
+
Related example: [`examples/docs/hello.tsx`](../examples/docs/hello.tsx). Run it with `bun examples/docs/hello.tsx`, press `Enter` for details, and quit with `Ctrl+C`.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
```tsx
|
|
29
|
+
import { Screen, Text, renderTerminal } from "@valyrianjs/terminal";
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
const output = renderTerminal(
|
|
32
|
+
<Screen title="Static">
|
|
33
|
+
<Text>Hello terminal</Text>
|
|
34
|
+
</Screen>
|
|
35
|
+
);
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
console.log(output);
|
|
38
|
+
```
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
- `mountTerminal()` vuelve a evaluar la funcion de render
|
|
36
|
-
- `session.update()` solo hace falta cuando mutaste estado fuera de un handler que ya detona rerender
|
|
40
|
+
## State lives outside the renderer
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
Keep application state in your app layer. The renderer turns the tree returned by your function into terminal output using the state your app provides.
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
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`.
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
```tsx
|
|
47
|
+
import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
- activar con `session.click(id)`
|
|
46
|
-
- participar de hitboxes por coordenadas
|
|
47
|
-
- recibir bien flujos de foco y navegacion
|
|
49
|
+
const state = { count: 0 };
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
function App() {
|
|
52
|
+
return (
|
|
53
|
+
<Screen title="Counter">
|
|
54
|
+
<Text>Count: {state.count}</Text>
|
|
55
|
+
<Button id="increment" onpress={() => {
|
|
56
|
+
state.count += 1;
|
|
57
|
+
}}>
|
|
58
|
+
Increment
|
|
59
|
+
</Button>
|
|
60
|
+
</Screen>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
const session = mountTerminal(<App />);
|
|
52
65
|
|
|
53
|
-
|
|
66
|
+
session.focus("increment");
|
|
67
|
+
session.dispatchKey("ENTER");
|
|
54
68
|
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
console.log(session.output());
|
|
70
|
+
session.destroy();
|
|
71
|
+
```
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
For larger UIs, Valyrian state modules can hold shared state outside the terminal tree. `createPulseStore` is useful when named transitions make state changes clearer.
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
- `focusPrev()`
|
|
62
|
-
- `dispatchKey("TAB")`
|
|
63
|
-
- `dispatchKey("SHIFT_TAB")`
|
|
75
|
+
Related example: [`examples/docs/employees-list.tsx`](../examples/docs/employees-list.tsx). Run it with `bun examples/docs/employees-list.tsx`, choose a person with `J/K` or arrows, and quit with `Ctrl+C`.
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
```tsx
|
|
78
|
+
import { createPulseStore } from "valyrian.js/pulses";
|
|
79
|
+
import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
66
80
|
|
|
67
|
-
|
|
81
|
+
const counter = createPulseStore(
|
|
82
|
+
{ count: 0 },
|
|
83
|
+
{
|
|
84
|
+
increment(state) {
|
|
85
|
+
state.count += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
68
89
|
|
|
69
|
-
|
|
90
|
+
function App() {
|
|
91
|
+
return (
|
|
92
|
+
<Screen title="Pulse store">
|
|
93
|
+
<Text>Count: {counter.state.count}</Text>
|
|
94
|
+
<Button id="increment" onpress={() => counter.increment()}>
|
|
95
|
+
Increment
|
|
96
|
+
</Button>
|
|
97
|
+
</Screen>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
- salida ANSI: la devuelve `session.ansiOutput()` y, si activas `ansi: true`, tambien se escribe a `stdout`
|
|
101
|
+
const session = mountTerminal(<App />);
|
|
73
102
|
|
|
74
|
-
|
|
103
|
+
session.focus("increment");
|
|
104
|
+
session.dispatchKey("ENTER");
|
|
75
105
|
|
|
76
|
-
|
|
106
|
+
console.log(session.output());
|
|
107
|
+
session.destroy();
|
|
108
|
+
```
|
|
77
109
|
|
|
78
|
-
|
|
110
|
+
If components read Valyrian reactive state, the terminal refreshes when that state changes. Call `session.update()` for non-reactive state or for an explicit manual refresh.
|
|
79
111
|
|
|
80
|
-
|
|
112
|
+
## Valyrian.js modules as app-layer state and workflow
|
|
81
113
|
|
|
82
|
-
|
|
83
|
-
- intenta activar raw mode si el stream lo soporta
|
|
84
|
-
- intenta restaurarlo al llamar `destroy()`
|
|
114
|
+
Valyrian.js modules in terminal apps stay in the app layer. The terminal adapter renders terminal nodes, handles focus, dispatches input, and exposes session output. Your app still owns routing choices, command design, data loading, persistence, and state transitions.
|
|
85
115
|
|
|
86
|
-
|
|
116
|
+
Use `valyrian.js/pulses` and `valyrian.js/flux-store` for shared state, `valyrian.js/forms` for canonical input state, `valyrian.js/request` and `valyrian.js/query` for API and cached reads, `valyrian.js/tasks` for async actions, `valyrian.js/translate` and `valyrian.js/money` for user-facing formatting, and `valyrian.js/native-store` plus `valyrian.js/utils` for local settings and helper logic. When async work changes Valyrian reactive state read by components, the terminal refreshes automatically.
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
Read the dedicated guide at [`docs/valyrian-modules.md`](./valyrian-modules.md) for terminal-specific module patterns. Related complete demos: **Operations Workbench** ([`examples/docs/module-state-workbench.tsx`](../examples/docs/module-state-workbench.tsx)), **API Operations Dashboard** ([`examples/docs/module-api-dashboard.tsx`](../examples/docs/module-api-dashboard.tsx)), and **Billing/Settings Wizard** ([`examples/docs/module-form-workflow.tsx`](../examples/docs/module-form-workflow.tsx)). Run them with `bun examples/docs/module-state-workbench.tsx`, `bun examples/docs/module-api-dashboard.tsx`, or `bun examples/docs/module-form-workflow.tsx`, try the keys shown in each footer, and quit with `Ctrl+C`.
|
|
89
119
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
## Stable ids and focus
|
|
121
|
+
|
|
122
|
+
Interactive nodes can render without an `id`, but stable ids are required for most programmatic interaction.
|
|
123
|
+
|
|
124
|
+
Use stable ids when you need:
|
|
125
|
+
|
|
126
|
+
- `session.focus(id)`
|
|
127
|
+
- `session.click(id)`
|
|
128
|
+
- `focusAt(x, y)` or `clickAt(x, y)` to resolve a hitbox consistently
|
|
129
|
+
- keyboard traversal with predictable focus order
|
|
130
|
+
|
|
131
|
+
Practical rule: give every long-lived `Input`, `Editor`, `Button`, `List`, and `ScrollView` an `id`.
|
|
132
|
+
|
|
133
|
+
Focus belongs to the `TerminalSession`; app state remains yours. `FocusScope` can bound sequential traversal inside a local group, and `Overlay trapFocus` can keep traversal inside an overlay while it is present.
|
|
134
|
+
|
|
135
|
+
## Styling and visual states
|
|
136
|
+
|
|
137
|
+
Use `theme.styles` for named recipes with hex colors, border, and padding. The default theme already gives `Button`, `Input`, and `List` app-like base, focus, selection, current, hover, loading, and disabled styles, so `<Button><Text>Ok</Text></Button>` renders as a styled control without per-instance chrome. Components still use `style` for their base recipe, `styles` to map visual states to recipes, and `state` when the app owns a visual status. Raw span serialization tokens belong to low-level renderer integrations; normal styling uses semantic styles and hex colors.
|
|
138
|
+
|
|
139
|
+
Related example: [`examples/docs/style-system.tsx`](../examples/docs/style-system.tsx). Run it with `bun examples/docs/style-system.tsx`, try `Tab`, `Enter`, and `W`, and quit with `Ctrl+C`.
|
|
140
|
+
|
|
141
|
+
Common states include `focus`, `hover`, `disabled`, and `loading`. Controls can use `pressed`, `checked`, `unchecked`, and `indeterminate`. Navigation can use `selected`, `current`, `expanded`, and `collapsed`. Entry fields can use `invalid`, `valid`, `readonly`, `placeholder`, `selection`, `editing`, and `submitted`. Content can use `empty`, `error`, `warning`, `success`, and `muted`. Pointer-style composition can use `dragging`, `dropTarget`, and `capturing`.
|
|
142
|
+
|
|
143
|
+
Use `style={{ background: "#111111" }}` or a theme recipe to fill a surface. `margin` is not included; compose spacing with layout, padding, and explicit text when needed.
|
|
144
|
+
|
|
145
|
+
## Plain text vs ANSI output
|
|
146
|
+
|
|
147
|
+
The package exposes two output representations:
|
|
148
|
+
|
|
149
|
+
- plain text: `renderTerminal()` and `session.output()`
|
|
150
|
+
- ANSI output: `session.ansiOutput()` and app-runtime `stdout` writes
|
|
151
|
+
|
|
152
|
+
Plain text belongs to `renderTerminal()`, `session.output()`, snapshots, and scripted checks. ANSI output is for runtime integration when a terminal needs cursor movement or style spans.
|
|
153
|
+
|
|
154
|
+
Keep ordinary rendered content as text. Do not build normal app behavior by placing escape sequences in `Text`, input values, list rows, or log entries. Use semantic spans, themes, and the documented runtime output paths instead.
|
|
155
|
+
|
|
156
|
+
Style borders are opt-in through style recipes. Plain output remains readable, and ANSI output applies style spans when supported.
|
|
157
|
+
|
|
158
|
+
## Cleanup and lifecycle
|
|
159
|
+
|
|
160
|
+
`mountTerminal()` can run without streams, which is useful for examples and scripted interaction. When you connect real streams, cleanup matters.
|
|
161
|
+
|
|
162
|
+
The session lifecycle is:
|
|
163
|
+
|
|
164
|
+
1. mount with `mountTerminal()`
|
|
165
|
+
2. read `output()` or `ansiOutput()`
|
|
166
|
+
3. dispatch input or mutate state
|
|
167
|
+
4. call `update()` when needed
|
|
168
|
+
5. call `destroy()` when finished
|
|
169
|
+
|
|
170
|
+
When `stdin` is connected, the session may attach listeners, enable raw mode when supported, and resume the stream. `destroy()` removes listeners and restores terminal state where the connected streams support it.
|
|
171
|
+
|
|
172
|
+
Connected streams follow terminal and runtime capabilities. Use [Session Runtime](./session-runtime.md) when integrating with `stdin`, `stdout`, resize events, clipboard adapters, or mouse input.
|
|
173
|
+
|
|
174
|
+
## Where to go next
|
|
175
|
+
|
|
176
|
+
- [Cookbook](./cookbook.md) for practical recipes.
|
|
177
|
+
- [Primitive Gallery](./primitive-gallery.md) for choosing and composing public terminal primitives.
|
|
178
|
+
- [Interaction Model](./interaction-model.md) for focus, keys, mouse, and event payloads.
|
|
179
|
+
- [Session Runtime](./session-runtime.md) for common usage and advanced host integration.
|
|
180
|
+
- [Valyrian Modules](./valyrian-modules.md) for terminal app patterns with Valyrian.js modules.
|
|
181
|
+
- [API Reference](./api-reference.md) for props and session methods.
|
package/docs/getting-started.md
CHANGED
|
@@ -1,148 +1,267 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This guide creates a first functional terminal UI. You will configure JSX, create `main.tsx`, render a static frame, then mount an interactive session.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What you will build
|
|
6
|
+
|
|
7
|
+
You will build a small note UI with:
|
|
8
|
+
|
|
9
|
+
- a static render path for snapshots or one-off output
|
|
10
|
+
- an interactive input path for keyboard-driven sessions
|
|
11
|
+
- a simple bridge from local state to Valyrian state modules when the UI grows
|
|
12
|
+
|
|
13
|
+
## Install
|
|
6
14
|
|
|
7
15
|
```bash
|
|
8
|
-
npm install valyrianjs
|
|
16
|
+
npm install @valyrianjs/terminal valyrian.js
|
|
9
17
|
```
|
|
10
18
|
|
|
11
|
-
##
|
|
19
|
+
## Configure JSX
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
Add this JSX config to your TypeScript configuration so TSX files can use Valyrian's automatic JSX runtime:
|
|
14
22
|
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"compilerOptions": {
|
|
26
|
+
"jsx": "react-jsx",
|
|
27
|
+
"jsxImportSource": "valyrian.js"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Manual `v(...)` nodes still work, but the examples below use TSX.
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
## Create `main.tsx`
|
|
35
|
+
|
|
36
|
+
Create `main.tsx` with a render-only first pass:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Screen, Text, renderTerminal } from "@valyrianjs/terminal";
|
|
21
40
|
|
|
22
41
|
const output = renderTerminal(
|
|
23
|
-
<Screen title="
|
|
42
|
+
<Screen title="First terminal UI">
|
|
24
43
|
<Text>Hello terminal</Text>
|
|
25
|
-
<Text>Status:
|
|
44
|
+
<Text>Status: ready</Text>
|
|
26
45
|
</Screen>
|
|
27
46
|
);
|
|
28
47
|
|
|
29
48
|
console.log(output);
|
|
30
49
|
```
|
|
31
50
|
|
|
32
|
-
|
|
51
|
+
## Run it
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
- validar layout
|
|
36
|
-
- probar salida plana sin conectar `stdin` ni `stdout`
|
|
53
|
+
Use your TypeScript runner of choice for the run command. With Bun:
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
```bash
|
|
56
|
+
bun run main.tsx
|
|
57
|
+
```
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
Expected plain output:
|
|
41
60
|
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
```text
|
|
62
|
+
First terminal UI
|
|
63
|
+
Hello terminal
|
|
64
|
+
Status: ready
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Static rendering with `renderTerminal`
|
|
68
|
+
|
|
69
|
+
Use `renderTerminal()` for deterministic plain-text output:
|
|
70
|
+
|
|
71
|
+
- snapshots
|
|
72
|
+
- examples in docs
|
|
73
|
+
- layout checks
|
|
74
|
+
- non-interactive command output
|
|
75
|
+
|
|
76
|
+
This path fits snapshots, documentation examples, layout checks, and non-interactive command output. Move to `mountTerminal()` when the experience needs focus, keyboard input, clipboard behavior, mouse behavior, or repeated renders.
|
|
77
|
+
|
|
78
|
+
## Interactive sessions with `mountTerminal`
|
|
45
79
|
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
Switch to `mountTerminal()` when the UI needs focus, keyboard input, clipboard behavior, mouse behavior, or repeated renders.
|
|
81
|
+
|
|
82
|
+
Replace `main.tsx` with this interactive version:
|
|
83
|
+
|
|
84
|
+
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` to save, and quit with `Ctrl+C`.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Button, Input, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
48
88
|
|
|
49
89
|
const state = { value: "", saved: "" };
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
function App() {
|
|
92
|
+
return (
|
|
93
|
+
<Screen title="Quick note">
|
|
94
|
+
<Input
|
|
95
|
+
id="note"
|
|
96
|
+
value={state.value}
|
|
97
|
+
placeholder="Write a quick note"
|
|
98
|
+
onchange={(event) => {
|
|
99
|
+
state.value = event.value;
|
|
100
|
+
}}
|
|
101
|
+
onsubmit={(event) => {
|
|
102
|
+
state.saved = event.value;
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<Button
|
|
106
|
+
id="save"
|
|
107
|
+
onpress={() => {
|
|
108
|
+
state.saved = state.value;
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Save
|
|
112
|
+
</Button>
|
|
113
|
+
<Text>{state.saved || "Nothing saved yet"}</Text>
|
|
114
|
+
</Screen>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const session = mountTerminal(<App />);
|
|
75
119
|
|
|
76
120
|
session.focus("note");
|
|
77
121
|
session.dispatchKey("H");
|
|
78
122
|
session.dispatchKey("i");
|
|
123
|
+
session.dispatchKey("ENTER");
|
|
79
124
|
|
|
80
125
|
console.log(session.output());
|
|
126
|
+
session.destroy();
|
|
81
127
|
```
|
|
82
128
|
|
|
83
|
-
|
|
129
|
+
This example uses programmatic key dispatch for a complete first project. In a real terminal session, pass compatible `stdin` and `stdout` options and call `destroy()` when the session is done.
|
|
84
130
|
|
|
85
|
-
|
|
131
|
+
For styled surfaces and visual states, run `bun examples/docs/style-system.tsx`, try `Tab`, `Enter`, and `W`, and quit with `Ctrl+C`; or open [`examples/docs/style-system.tsx`](../examples/docs/style-system.tsx). For responsive columns and rows, run `bun examples/docs/responsive-split.tsx`, resize the terminal to change the layout, and quit with `Ctrl+C`; or open [`examples/docs/responsive-split.tsx`](../examples/docs/responsive-split.tsx).
|
|
86
132
|
|
|
87
|
-
|
|
133
|
+
## Add state when the UI grows
|
|
88
134
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
135
|
+
Small examples can use local state. As the UI grows, keep application state in your app layer and use Valyrian state modules such as `createPulseStore` when named transitions fit your app.
|
|
136
|
+
|
|
137
|
+
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`.
|
|
92
138
|
|
|
93
|
-
|
|
94
|
-
import {
|
|
139
|
+
```tsx
|
|
140
|
+
import { createPulseStore } from "valyrian.js/pulses";
|
|
141
|
+
import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
142
|
+
|
|
143
|
+
const counter = createPulseStore(
|
|
144
|
+
{ count: 0 },
|
|
145
|
+
{
|
|
146
|
+
increment(state) {
|
|
147
|
+
state.count += 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
95
151
|
|
|
96
|
-
|
|
152
|
+
function App() {
|
|
153
|
+
return (
|
|
154
|
+
<Screen title="Counter">
|
|
155
|
+
<Text>Count: {counter.state.count}</Text>
|
|
156
|
+
<Button id="increment" onpress={() => counter.increment()}>
|
|
157
|
+
Increment
|
|
158
|
+
</Button>
|
|
159
|
+
</Screen>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
97
162
|
|
|
98
|
-
const session = mountTerminal(
|
|
99
|
-
<Screen>
|
|
100
|
-
<Text>Count: {state.count}</Text>
|
|
101
|
-
</Screen>
|
|
102
|
-
));
|
|
163
|
+
const session = mountTerminal(<App />);
|
|
103
164
|
|
|
104
|
-
|
|
105
|
-
session.
|
|
165
|
+
session.focus("increment");
|
|
166
|
+
session.dispatchKey("ENTER");
|
|
106
167
|
|
|
107
168
|
console.log(session.output());
|
|
108
169
|
session.destroy();
|
|
109
170
|
```
|
|
110
171
|
|
|
111
|
-
|
|
172
|
+
ValyrianJS Terminal renders the current state and reports events while your app layer owns product state, routing, and workflow choices.
|
|
112
173
|
|
|
113
|
-
|
|
114
|
-
- `ansiOutput()` devuelve el frame actual como ANSI completo
|
|
115
|
-
- `update()` reevalua la funcion de render
|
|
116
|
-
- `destroy()` desmonta listeners y limpia `stdin` cuando aplica
|
|
174
|
+
## Compose reusable components
|
|
117
175
|
|
|
118
|
-
|
|
176
|
+
Terminal UIs are regular Valyrian component trees. Extract small components when a screen repeats a pattern, pass scalar props for simple labels, pass object props for domain rows, and render arrays as lists of components.
|
|
119
177
|
|
|
120
|
-
|
|
178
|
+
Full 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`.
|
|
121
179
|
|
|
122
180
|
```tsx
|
|
123
|
-
|
|
124
|
-
/** @jsxFrag v.fragment */
|
|
181
|
+
import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
125
182
|
|
|
126
|
-
|
|
127
|
-
|
|
183
|
+
function StatusBadge() {
|
|
184
|
+
return <Text>Status: Ready</Text>;
|
|
185
|
+
}
|
|
128
186
|
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
187
|
+
function Greeting({ name }: { name: string }) {
|
|
188
|
+
return <Text>Hello, {name}</Text>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function App() {
|
|
192
|
+
return (
|
|
193
|
+
<Screen title="Components">
|
|
194
|
+
<StatusBadge />
|
|
195
|
+
<Greeting name="Maya" />
|
|
196
|
+
</Screen>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const session = mountTerminal(<App />);
|
|
201
|
+
|
|
202
|
+
console.log(session.output());
|
|
203
|
+
session.destroy();
|
|
138
204
|
```
|
|
139
205
|
|
|
140
|
-
|
|
206
|
+
`StatusBadge` demonstrates a component with no props: it always renders the same terminal content. `Greeting` demonstrates a scalar prop: the parent keeps the value and passes a plain string into the reusable component.
|
|
207
|
+
|
|
208
|
+
Full example: [`examples/docs/employees-list.tsx`](../examples/docs/employees-list.tsx). Run it with `bun examples/docs/employees-list.tsx`, choose a person with `J/K` or arrows, and quit with `Ctrl+C`.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
|
|
212
|
+
|
|
213
|
+
type EmployeeData = {
|
|
214
|
+
name: string;
|
|
215
|
+
role: string;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const employees: EmployeeData[] = [
|
|
219
|
+
{ name: "Ana", role: "Design" },
|
|
220
|
+
{ name: "Luis", role: "Support" },
|
|
221
|
+
{ name: "Maya", role: "Engineering" }
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
function Employee({ data }: { data: EmployeeData }) {
|
|
225
|
+
return <Text>{`${data.name} — ${data.role}`}</Text>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function Employees({ items }: { items: EmployeeData[] }) {
|
|
229
|
+
return (
|
|
230
|
+
<>
|
|
231
|
+
{items.map((employee) => (
|
|
232
|
+
<Employee data={employee} />
|
|
233
|
+
))}
|
|
234
|
+
</>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function App() {
|
|
239
|
+
return (
|
|
240
|
+
<Screen title="Team">
|
|
241
|
+
<Employees items={employees} />
|
|
242
|
+
</Screen>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const session = mountTerminal(<App />);
|
|
247
|
+
|
|
248
|
+
console.log(session.output());
|
|
249
|
+
session.destroy();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`Employee data={...}` demonstrates an object prop for a domain row. `Employees items={...}` demonstrates list composition: the parent owns the array and maps each item into a reusable child component.
|
|
253
|
+
|
|
254
|
+
Use the same composition rules as any declarative terminal tree:
|
|
255
|
+
|
|
256
|
+
- Treat components as small JSX functions that receive props and return terminal elements.
|
|
257
|
+
- The app owns state, data loading, routing decisions, and effects. When components read Valyrian reactive state, external async changes refresh the terminal automatically. Use `session.update()` for non-reactive state or an explicit manual refresh.
|
|
258
|
+
- Use stable `id` values when focus or programmatic dispatch targets a specific interactive node.
|
|
259
|
+
- Keep terminal-specific behavior in public primitives, props, and events instead of hidden component side effects.
|
|
141
260
|
|
|
142
|
-
##
|
|
261
|
+
## Next steps
|
|
143
262
|
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
263
|
+
- [Core Concepts](./core-concepts.md) for the package mental model.
|
|
264
|
+
- [Cookbook](./cookbook.md) for practical recipes after the first project.
|
|
265
|
+
- [Interaction Model](./interaction-model.md) for focus, keyboard, mouse, and events.
|
|
266
|
+
- [Session Runtime](./session-runtime.md) for stream and host integration.
|
|
267
|
+
- [API Reference](./api-reference.md) for props, payloads, and session methods.
|