@valyrianjs/terminal 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.
- package/README.md +71 -0
- package/dist/ansi.d.ts +6 -0
- package/dist/ansi.d.ts.map +1 -0
- package/dist/ansi.js +105 -0
- package/dist/ansi.js.map +1 -0
- package/dist/clipboard.d.ts +3 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +71 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/events.d.ts +62 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +189 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +16 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +153 -0
- package/dist/layout.js.map +1 -0
- package/dist/mouse.d.ts +5 -0
- package/dist/mouse.d.ts.map +1 -0
- package/dist/mouse.js +34 -0
- package/dist/mouse.js.map +1 -0
- package/dist/primitives.d.ts +15 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +18 -0
- package/dist/primitives.js.map +1 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +230 -0
- package/dist/render.js.map +1 -0
- package/dist/session.d.ts +3 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +599 -0
- package/dist/session.js.map +1 -0
- package/dist/tree.d.ts +9 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +103 -0
- package/dist/tree.js.map +1 -0
- package/dist/types.d.ts +224 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/api-reference.md +273 -0
- package/docs/cookbook.md +286 -0
- package/docs/core-concepts.md +93 -0
- package/docs/getting-started.md +148 -0
- package/docs/interaction-model.md +115 -0
- package/docs/local-demo.md +28 -0
- package/docs/session-runtime.md +451 -0
- package/package.json +44 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# Session Runtime
|
|
2
|
+
|
|
3
|
+
Esta guia documenta el runtime interactivo de `valyrianjs-terminal`: cuando conviene usar `mountTerminal()`, como configurar una sesion y que capacidades expone `TerminalSession` durante su ciclo de vida.
|
|
4
|
+
|
|
5
|
+
Si vienes desde cero, lee antes `docs/core-concepts.md`. Para el modelo general de interaccion por primitiva, consulta `docs/interaction-model.md`. Aqui el foco es operativo: montaje, rerender, streams, foco, teclado, clipboard y mouse.
|
|
6
|
+
|
|
7
|
+
## Cuando usar `mountTerminal()`
|
|
8
|
+
|
|
9
|
+
Usa `mountTerminal()` cuando necesites una sesion viva que:
|
|
10
|
+
|
|
11
|
+
- reevalua el arbol al cambiar estado externo
|
|
12
|
+
- mantiene foco entre renders
|
|
13
|
+
- despacha teclado por sesion
|
|
14
|
+
- conecta `stdin` y `stdout`
|
|
15
|
+
- responde a coordenadas y eventos de mouse
|
|
16
|
+
|
|
17
|
+
Si solo quieres inspeccionar layout, generar snapshots o producir una salida estatica, `renderTerminal()` sigue siendo la opcion mas simple.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
/** @jsx v */
|
|
21
|
+
/** @jsxFrag v.fragment */
|
|
22
|
+
|
|
23
|
+
import { v } from "valyrian.js";
|
|
24
|
+
import { Input, Screen, Text, mountTerminal } from "valyrianjs-terminal";
|
|
25
|
+
|
|
26
|
+
const state = { value: "" };
|
|
27
|
+
|
|
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
|
+
));
|
|
40
|
+
|
|
41
|
+
session.focus("name");
|
|
42
|
+
session.dispatchKey("A");
|
|
43
|
+
session.dispatchKey("B");
|
|
44
|
+
|
|
45
|
+
console.log(session.output());
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## `TerminalMountOptions`
|
|
49
|
+
|
|
50
|
+
`mountTerminal(input, options?)` acepta un `TerminalMountOptions` con cuatro puntos de integracion:
|
|
51
|
+
|
|
52
|
+
### `ansi?: boolean`
|
|
53
|
+
|
|
54
|
+
Activa salida ANSI incremental hacia `stdout`. `session.ansiOutput()` sigue disponible aunque no actives esta opcion; lo que cambia es la escritura automatica de la sesion cuando hace rerender. Si no lo activas, la salida principal enviada a `stdout` es texto plano.
|
|
55
|
+
|
|
56
|
+
### `clipboard?: TerminalClipboardAdapter | false`
|
|
57
|
+
|
|
58
|
+
- si pasas un adapter, la sesion usa `readText()` y `writeText()` cuando ejecuta atajos como `CTRL_C` o `CTRL_V`
|
|
59
|
+
- si pasas `false`, desactivas la integracion con clipboard del sistema
|
|
60
|
+
- aun con `clipboard: false`, `session.clipboard()` y `session.setClipboard()` siguen funcionando como buffer de la sesion
|
|
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
|
+
Stream de entrada para teclado y mouse. La sesion se suscribe a `data`, intenta activar `raw mode` si existe `setRawMode(true)` y llama `resume()` si el stream lo soporta.
|
|
99
|
+
|
|
100
|
+
Contrato esperado:
|
|
101
|
+
|
|
102
|
+
- `on("data", listener)` obligatorio
|
|
103
|
+
- `off(...)` o `removeListener(...)` opcional para cleanup
|
|
104
|
+
- `setRawMode?(boolean)` opcional
|
|
105
|
+
- `resume?()` y `pause?()` opcionales
|
|
106
|
+
|
|
107
|
+
### `stdout?`
|
|
108
|
+
|
|
109
|
+
Destino de escritura para la salida producida en cada rerender.
|
|
110
|
+
|
|
111
|
+
- con `ansi: false`, escribe texto plano
|
|
112
|
+
- con `ansi: true`, escribe diffs ANSI incrementales
|
|
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
|
+
## Lifecycle de `TerminalSession`
|
|
136
|
+
|
|
137
|
+
La sesion mantiene un frame actual, una salida actual y el estado interactivo asociado al arbol enfocable. El ciclo practico es:
|
|
138
|
+
|
|
139
|
+
1. montar con `mountTerminal()`
|
|
140
|
+
2. leer con `output()` o `ansiOutput()`
|
|
141
|
+
3. mutar estado externo o despachar interacciones
|
|
142
|
+
4. rerender con `update()` o mediante eventos que ya disparan rerender
|
|
143
|
+
5. cerrar con `destroy()`
|
|
144
|
+
|
|
145
|
+
### `update()`
|
|
146
|
+
|
|
147
|
+
Vuelve a evaluar `input`, recalcula el frame y devuelve la salida plana actual.
|
|
148
|
+
|
|
149
|
+
Usalo cuando mutaste estado fuera de handlers de la sesion.
|
|
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
|
+
```
|
|
165
|
+
|
|
166
|
+
### `output()`
|
|
167
|
+
|
|
168
|
+
Devuelve el frame actual en texto plano. Es la forma mas directa de:
|
|
169
|
+
|
|
170
|
+
- inspeccionar estado actual
|
|
171
|
+
- validar snapshots
|
|
172
|
+
- probar foco y contenido sin ANSI
|
|
173
|
+
|
|
174
|
+
### `ansiOutput()`
|
|
175
|
+
|
|
176
|
+
Devuelve el frame actual serializado como ANSI completo, incluyendo cursor y spans visuales. Es util cuando quieres:
|
|
177
|
+
|
|
178
|
+
- inspeccionar posicion de cursor
|
|
179
|
+
- validar seleccion o foco en una terminal ANSI
|
|
180
|
+
- integrar una capa que consume escape sequences completas
|
|
181
|
+
|
|
182
|
+
```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
|
+
```
|
|
195
|
+
|
|
196
|
+
### `destroy()`
|
|
197
|
+
|
|
198
|
+
Desmonta listeners de `stdin`, intenta salir de raw mode con `setRawMode(false)` y llama `pause()` si existe.
|
|
199
|
+
|
|
200
|
+
Llamalo siempre cuando montaste la sesion con `stdin` real o un emisor conectado.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
session.destroy();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Foco y coordenadas
|
|
207
|
+
|
|
208
|
+
El foco vive a nivel de sesion. Para participar en este flujo, los nodos interactivos necesitan `id`.
|
|
209
|
+
|
|
210
|
+
### `focus(id)`
|
|
211
|
+
|
|
212
|
+
Enfoca un nodo por `id`. Devuelve `true` si lo encontro.
|
|
213
|
+
|
|
214
|
+
### `focusNext()` y `focusPrev()`
|
|
215
|
+
|
|
216
|
+
Recorren el orden actual de elementos enfocables. `dispatchKey("TAB")` y `dispatchKey("SHIFT_TAB")` usan este mismo flujo.
|
|
217
|
+
|
|
218
|
+
### `focusAt(x, y)`
|
|
219
|
+
|
|
220
|
+
Busca el hitbox en las coordenadas actuales, actualiza hover semantico cuando aplica y enfoca ese nodo.
|
|
221
|
+
|
|
222
|
+
### `clickAt(x, y)`
|
|
223
|
+
|
|
224
|
+
- si cae sobre un `Button`, dispara su accion
|
|
225
|
+
- si cae sobre un `Input`, enfoca y coloca cursor segun la coordenada
|
|
226
|
+
- si cae sobre otra superficie enfocable, mueve el foco
|
|
227
|
+
|
|
228
|
+
```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
|
+
## Keyboard dispatch por sesion
|
|
241
|
+
|
|
242
|
+
`dispatchKey(key)` procesa una tecla ya normalizada contra el nodo enfocado y devuelve la salida plana actual.
|
|
243
|
+
|
|
244
|
+
Atajos soportados por la API publica observables en pruebas:
|
|
245
|
+
|
|
246
|
+
- globales: `TAB`, `SHIFT_TAB`
|
|
247
|
+
- `Input`: caracteres de un solo byte, `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`
|
|
251
|
+
|
|
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
|
+
));
|
|
269
|
+
|
|
270
|
+
session.focus("name");
|
|
271
|
+
session.dispatchKey("A");
|
|
272
|
+
session.dispatchKey("B");
|
|
273
|
+
session.dispatchKey("LEFT");
|
|
274
|
+
session.dispatchKey("C");
|
|
275
|
+
session.dispatchKey("ENTER");
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Clipboard adapter y `clipboard: false`
|
|
279
|
+
|
|
280
|
+
La sesion separa dos conceptos:
|
|
281
|
+
|
|
282
|
+
- buffer de clipboard de la sesion: `clipboard()` y `setClipboard()`
|
|
283
|
+
- integracion con el sistema: `options.clipboard`
|
|
284
|
+
|
|
285
|
+
Con un adapter, los atajos de input leen y escriben contra ese adapter. Sin adapter, la sesion conserva un valor local. Con `clipboard: false`, desactivas la integracion externa pero puedes seguir usando el buffer local de la sesion para pruebas o integraciones manuales.
|
|
286
|
+
|
|
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 });
|
|
301
|
+
|
|
302
|
+
session.focus("name");
|
|
303
|
+
session.setClipboard("XY");
|
|
304
|
+
session.dispatchKey("END");
|
|
305
|
+
session.dispatchKey("CTRL_V");
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Streams `stdin` y `stdout`
|
|
309
|
+
|
|
310
|
+
Conectar streams vuelve a la sesion util como runtime de CLI, no solo como helper de pruebas.
|
|
311
|
+
|
|
312
|
+
Comportamiento observable:
|
|
313
|
+
|
|
314
|
+
- al montar, la sesion se suscribe a `stdin.on("data", listener)`
|
|
315
|
+
- si `stdin.setRawMode` existe, intenta `setRawMode(true)`
|
|
316
|
+
- si `stdin.resume` existe, intenta `resume()`
|
|
317
|
+
- en cada rerender, si existe `stdout.write`, la sesion escribe el frame actual
|
|
318
|
+
- al destruir, intenta desuscribirse y restaurar `raw mode`
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
/** @jsx v */
|
|
322
|
+
/** @jsxFrag v.fragment */
|
|
323
|
+
|
|
324
|
+
import { EventEmitter } from "node:events";
|
|
325
|
+
import { v } from "valyrian.js";
|
|
326
|
+
import { Screen, mountTerminal } from "valyrianjs-terminal";
|
|
327
|
+
|
|
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));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
stdin.emit("data", "A");
|
|
348
|
+
stdin.emit("data", "\r");
|
|
349
|
+
session.destroy();
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## ANSI output
|
|
353
|
+
|
|
354
|
+
Hay dos formas practicas de trabajar con ANSI:
|
|
355
|
+
|
|
356
|
+
- `ansi: true` + `stdout`: la sesion emite diffs incrementales en cada rerender
|
|
357
|
+
- `session.ansiOutput()`: recupera el frame ANSI completo del estado actual
|
|
358
|
+
|
|
359
|
+
Esto es especialmente util cuando la terminal consumidora necesita minimizar repaints o cuando quieres verificar cursor, foco y seleccion con escape sequences reales.
|
|
360
|
+
|
|
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
|
+
```
|
|
381
|
+
|
|
382
|
+
## Mouse SGR, wheel y pointer capture
|
|
383
|
+
|
|
384
|
+
Cuando conectas `stdin`, la sesion acepta secuencias SGR de mouse y las traduce a interaccion practica sobre el frame actual.
|
|
385
|
+
|
|
386
|
+
### Press, drag y release
|
|
387
|
+
|
|
388
|
+
- `press`: enfoca y activa el hitbox en la coordenada
|
|
389
|
+
- `drag`: si hay un `Input` activo por mouse, extiende seleccion; en `List` y `ScrollView`, actualiza hover por fila
|
|
390
|
+
- `release`: termina seleccion por mouse y cierra captura cuando aplica
|
|
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
|
+
```
|
|
397
|
+
|
|
398
|
+
Ese patron es suficiente para seleccionar texto dentro de un `Input` por coordenadas.
|
|
399
|
+
|
|
400
|
+
### Wheel
|
|
401
|
+
|
|
402
|
+
El wheel se despacha como navegacion vertical sobre el nodo bajo el puntero:
|
|
403
|
+
|
|
404
|
+
- `wheel-up` equivale a `dispatchKey("UP")`
|
|
405
|
+
- `wheel-down` equivale a `dispatchKey("DOWN")`
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
stdin.emit("data", "\u001b[<65;2;1M");
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
En un `ScrollView` enfocado o localizado por coordenadas, eso desplaza el viewport.
|
|
412
|
+
|
|
413
|
+
### Pointer capture a nivel practico
|
|
414
|
+
|
|
415
|
+
`pointerCapture` hoy aplica a `List` y `ScrollView`.
|
|
416
|
+
|
|
417
|
+
Sin `pointerCapture`, el hover semantico depende de que el drag siga dentro del hitbox visible. Con `pointerCapture`, la sesion mantiene la interaccion durante el drag aunque el puntero salga del area inicial, y dispara:
|
|
418
|
+
|
|
419
|
+
- `oncapturestart`
|
|
420
|
+
- `oncaptureend`
|
|
421
|
+
|
|
422
|
+
Esto es util para:
|
|
423
|
+
|
|
424
|
+
- listas que deben seguir rastreando la fila activa durante drag
|
|
425
|
+
- scroll views que conservan hover o release aunque el puntero termine fuera del viewport
|
|
426
|
+
|
|
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
|
+
## Recomendaciones practicas
|
|
446
|
+
|
|
447
|
+
- usa `renderTerminal()` para snapshots y `mountTerminal()` para runtime interactivo
|
|
448
|
+
- da `id` estables a `Input`, `Button`, `List` y `ScrollView` si vas a usar foco, coordenadas o dispatch programatico
|
|
449
|
+
- llama `update()` solo cuando mutaste estado fuera de handlers ya conectados a la sesion
|
|
450
|
+
- llama `destroy()` siempre que montes con `stdin`
|
|
451
|
+
- usa `clipboard: false` en pruebas o entornos donde no quieres depender del clipboard del sistema
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valyrianjs/terminal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Terminal adapter for valyrian.js",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./render": {
|
|
18
|
+
"types": "./dist/render.d.ts",
|
|
19
|
+
"import": "./dist/render.js",
|
|
20
|
+
"default": "./dist/render.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"docs",
|
|
26
|
+
"README.md",
|
|
27
|
+
"tsconfig.json",
|
|
28
|
+
"tsconfig.build.json"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "bun test",
|
|
32
|
+
"typecheck": "bunx tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"build": "rm -rf dist && bunx tsc -p tsconfig.build.json"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.3.3",
|
|
37
|
+
"bun-types": "latest",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"valyrian.js": "^9.1.3"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"valyrian.js": "^9.1.3"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": false,
|
|
5
|
+
"allowImportingTsExtensions": false,
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"emitDeclarationOnly": false,
|
|
12
|
+
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["esnext", "dom"],
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"jsx": "react",
|
|
10
|
+
"jsxFactory": "v",
|
|
11
|
+
"jsxFragmentFactory": "v.fragment",
|
|
12
|
+
"allowJs": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"types": ["bun-types", "node"],
|
|
18
|
+
"downlevelIteration": true,
|
|
19
|
+
"allowSyntheticDefaultImports": true,
|
|
20
|
+
"allowImportingTsExtensions": true,
|
|
21
|
+
"resolveJsonModule": true,
|
|
22
|
+
"baseUrl": "."
|
|
23
|
+
},
|
|
24
|
+
"include": ["src", "test", "examples"]
|
|
25
|
+
}
|