@valyrianjs/terminal 0.1.2 → 0.2.1
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/dist/ansi.d.ts +2 -0
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +12 -0
- package/dist/ansi.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +6 -2
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keymap.d.ts.map +1 -1
- package/dist/keymap.js +4 -2
- package/dist/keymap.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +2 -1
- package/dist/layout.js.map +1 -1
- package/dist/mouse.d.ts +6 -0
- package/dist/mouse.d.ts.map +1 -1
- package/dist/mouse.js +30 -16
- package/dist/mouse.js.map +1 -1
- package/dist/primitives.d.ts +8 -3
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +8 -1
- package/dist/primitives.js.map +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +290 -51
- package/dist/render.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -11
- package/dist/runtime.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +434 -65
- package/dist/session.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +3 -0
- package/dist/theme.js.map +1 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +18 -4
- package/dist/tree.js.map +1 -1
- package/dist/types.d.ts +61 -13
- package/dist/types.d.ts.map +1 -1
- package/docs/api-reference.md +40 -28
- package/docs/cookbook.md +2 -2
- package/docs/core-concepts.md +1 -1
- package/docs/interaction-model.md +18 -6
- package/docs/primitive-gallery.md +19 -10
- package/llms-full.txt +80 -47
- package/package.json +1 -1
- package/src/ansi.ts +12 -0
- package/src/events.ts +4 -2
- package/src/index.ts +3 -0
- package/src/keymap.ts +4 -2
- package/src/layout.ts +2 -1
- package/src/mouse.ts +31 -15
- package/src/primitives.ts +15 -5
- package/src/render.ts +320 -52
- package/src/runtime.ts +13 -11
- package/src/session.ts +469 -59
- package/src/theme.ts +3 -0
- package/src/tree.ts +19 -4
- package/src/types.ts +72 -12
package/llms-full.txt
CHANGED
|
@@ -530,7 +530,7 @@ Focus belongs to the `TerminalSession`; app state remains yours. `FocusScope` ca
|
|
|
530
530
|
|
|
531
531
|
## Styling and visual states
|
|
532
532
|
|
|
533
|
-
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.
|
|
533
|
+
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. When a primitive has a matching `<element>.base` recipe, the renderer applies it automatically before instance styles. 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.
|
|
534
534
|
|
|
535
535
|
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`.
|
|
536
536
|
|
|
@@ -666,7 +666,7 @@ console.log(session.output());
|
|
|
666
666
|
session.destroy();
|
|
667
667
|
```
|
|
668
668
|
|
|
669
|
-
For large lists, add `virtualized` and place the list in a bounded region such as a filled pane or split.
|
|
669
|
+
For large lists, add `virtualized` and place the list in a bounded region such as a filled pane or split. The virtualized workbench shows the normal uncontrolled API with `itemKey`, the `children` callback, event observation, and internal active, selected, and viewport state: [`examples/docs/virtualized-list-workbench.tsx`](../examples/docs/virtualized-list-workbench.tsx). Run it with `bun examples/docs/virtualized-list-workbench.tsx`, move active rows with `J/K` or `Up`/`Down`, jump with `PageUp`, `PageDown`, `Home`, or `End`, select with `Enter`, and quit with `Ctrl+C`.
|
|
670
670
|
|
|
671
671
|
## Local state to Valyrian state module bridge
|
|
672
672
|
|
|
@@ -756,7 +756,7 @@ function App() {
|
|
|
756
756
|
return (
|
|
757
757
|
<Screen title="Overlay recipe">
|
|
758
758
|
<Text>{state.executed ? `Executed: ${state.executed}` : "Choose a command"}</Text>
|
|
759
|
-
<Overlay
|
|
759
|
+
<Overlay margin={{ x: 2, y: 2 }} trapFocus>
|
|
760
760
|
<FocusScope>
|
|
761
761
|
<Input
|
|
762
762
|
id="query"
|
|
@@ -1101,14 +1101,19 @@ Use `Split` for resizable shells and master/detail layouts.
|
|
|
1101
1101
|
|
|
1102
1102
|
Use `Fixed` for headers, footers, rails, and persistent status bars.
|
|
1103
1103
|
|
|
1104
|
-
**What it is:** A reserved region attached to an edge of
|
|
1104
|
+
**What it is:** A reserved region attached to an edge of a parent frame.
|
|
1105
1105
|
**Use it for:** headers, footers, rails, and persistent status bars.
|
|
1106
1106
|
**Core props:** `position`, `size`.
|
|
1107
1107
|
|
|
1108
|
+
Inside `Screen` or `Pane`, `Fixed` uses the parent frame. If you pass a standalone `Fixed` root to `renderTerminal()`, pass `{ cols, rows }` so it has a viewport.
|
|
1109
|
+
|
|
1108
1110
|
**Minimal example:**
|
|
1109
1111
|
|
|
1110
1112
|
```tsx
|
|
1111
|
-
<
|
|
1113
|
+
<Screen>
|
|
1114
|
+
<Fixed position="bottom" size={1}><Text>Ctrl+C: quit</Text></Fixed>
|
|
1115
|
+
<Pane><Text>Work area</Text></Pane>
|
|
1116
|
+
</Screen>
|
|
1112
1117
|
```
|
|
1113
1118
|
|
|
1114
1119
|
**Complete demo:** [`examples/docs/primitive-layout-shell.tsx`](../examples/docs/primitive-layout-shell.tsx). Run it with `bun examples/docs/primitive-layout-shell.tsx` and quit with `Ctrl+C`.
|
|
@@ -1123,7 +1128,7 @@ Use `Input` for names, filters, prompts, and short commands.
|
|
|
1123
1128
|
|
|
1124
1129
|
**What it is:** A single-line editable field.
|
|
1125
1130
|
**Use it for:** names, filters, prompts, and short commands.
|
|
1126
|
-
**Core props:** `id`, `value`, `placeholder`, `onchange`, `oninput`, `onsubmit`.
|
|
1131
|
+
**Core props:** `id`, `value`, `placeholder`, `onchange`, `oninput`, `onsubmit`, `oncontextpress`.
|
|
1127
1132
|
|
|
1128
1133
|
**Minimal example:**
|
|
1129
1134
|
|
|
@@ -1155,7 +1160,7 @@ Use `Button` for save actions, confirmations, command triggers, and app actions.
|
|
|
1155
1160
|
|
|
1156
1161
|
**What it is:** A focusable action control.
|
|
1157
1162
|
**Use it for:** save actions, confirmations, command triggers, and app actions.
|
|
1158
|
-
**Core props:** `id`, `label`, `onpress`, `
|
|
1163
|
+
**Core props:** `id`, `label`, `onpress`, `ondoublepress`, `oncontextpress`, `style`, `styles`, `state`.
|
|
1159
1164
|
|
|
1160
1165
|
**Minimal example:**
|
|
1161
1166
|
|
|
@@ -1191,16 +1196,20 @@ Use `List` for menus, choosers, command lists, and item browsers.
|
|
|
1191
1196
|
|
|
1192
1197
|
**What it is:** A focusable collection control with selection and press events.
|
|
1193
1198
|
**Use it for:** menus, choosers, command lists, and item browsers.
|
|
1194
|
-
**Core props:** `id`, `items`, `renderItem`, `virtualized`, `onchange`, `onpress`.
|
|
1199
|
+
**Core props:** `id`, `items`, `children` callback, `renderItem`, `itemKey`, `virtualized`, `showActive`, `onchange`, `onviewportchange`, `onpress`, `ondoublepress`, `oncontextpress`. `List` owns its active row, selected row, and viewport state internally; observe those values from event payloads.
|
|
1195
1200
|
|
|
1196
1201
|
**Minimal example:**
|
|
1197
1202
|
|
|
1198
1203
|
```tsx
|
|
1199
|
-
<List id="menu" items={["Inbox", "Done"]}
|
|
1204
|
+
<List id="menu" items={["Inbox", "Done"]} itemKey={(item) => item}>
|
|
1205
|
+
{(item, ctx) => `${ctx.active ? "› " : " "}${item}`}
|
|
1206
|
+
</List>
|
|
1200
1207
|
```
|
|
1201
1208
|
|
|
1202
1209
|
**Complete demo:** [`examples/docs/primitive-data-explorer.tsx`](../examples/docs/primitive-data-explorer.tsx). Run it with `bun examples/docs/primitive-data-explorer.tsx` and quit with `Ctrl+C`.
|
|
1203
1210
|
|
|
1211
|
+
**Virtualized demo:** [`examples/docs/virtualized-list-workbench.tsx`](../examples/docs/virtualized-list-workbench.tsx). Run it with `bun examples/docs/virtualized-list-workbench.tsx`, move active rows with `J/K` or `Up`/`Down`, jump with `PageUp`, `PageDown`, `Home`, or `End`, select with `Enter`, and quit with `Ctrl+C`.
|
|
1212
|
+
|
|
1204
1213
|
### `Table`
|
|
1205
1214
|
|
|
1206
1215
|
Use `Table` for compact data detail and aligned text pairs.
|
|
@@ -1259,7 +1268,7 @@ Use `ScrollView` for long instructions, timelines, and scrollable panels.
|
|
|
1259
1268
|
|
|
1260
1269
|
**What it is:** A bounded viewport for child content.
|
|
1261
1270
|
**Use it for:** long instructions, timelines, and scrollable panels.
|
|
1262
|
-
**Core props:** `id`, `height`, `highlightRows`, `pointerCapture`, row pointer events.
|
|
1271
|
+
**Core props:** `id`, `height`, `highlightRows`, `pointerCapture`, `oncontextpress`, row pointer events.
|
|
1263
1272
|
|
|
1264
1273
|
**Minimal example:**
|
|
1265
1274
|
|
|
@@ -1295,12 +1304,12 @@ Use `Overlay` for command panels, dialogs, palettes, and focused chooser surface
|
|
|
1295
1304
|
|
|
1296
1305
|
**What it is:** A positioned layer over the current frame.
|
|
1297
1306
|
**Use it for:** command panels, dialogs, palettes, and focused chooser surfaces.
|
|
1298
|
-
**Core props:** `id`, `
|
|
1307
|
+
**Core props:** `id`, `margin`, `trapFocus`, `style`. `margin` is required and accepts a number, a percentage string, or `{ x, y }` with number or percentage-string values. When `Overlay` is the root passed to `renderTerminal()`, pass exact `{ cols, rows }` as the second argument so margin can resolve against terminal dimensions. Later sibling overlays paint and receive pointer/focus routing above earlier overlays. Overlay background styles cover their full assigned surface, not just text cells.
|
|
1299
1308
|
|
|
1300
1309
|
**Minimal example:**
|
|
1301
1310
|
|
|
1302
1311
|
```tsx
|
|
1303
|
-
<Overlay
|
|
1312
|
+
<Overlay margin={{ x: 4, y: 2 }} trapFocus><Text>Command Panel</Text></Overlay>
|
|
1304
1313
|
```
|
|
1305
1314
|
|
|
1306
1315
|
**Complete demo:** [`examples/docs/primitive-command-panel.tsx`](../examples/docs/primitive-command-panel.tsx). Run it with `bun examples/docs/primitive-command-panel.tsx` and quit with `Ctrl+C`.
|
|
@@ -1316,7 +1325,7 @@ Use `FocusScope` to keep input, lists, and actions together inside a panel.
|
|
|
1316
1325
|
**Minimal example:**
|
|
1317
1326
|
|
|
1318
1327
|
```tsx
|
|
1319
|
-
<Overlay
|
|
1328
|
+
<Overlay margin={{ x: 2, y: 2 }} trapFocus><FocusScope><Input id="query" /><List id="commands" items={commands} /></FocusScope></Overlay>
|
|
1320
1329
|
```
|
|
1321
1330
|
|
|
1322
1331
|
**Complete demo:** [`examples/docs/primitive-command-panel.tsx`](../examples/docs/primitive-command-panel.tsx). Run it with `bun examples/docs/primitive-command-panel.tsx` and quit with `Ctrl+C`.
|
|
@@ -1379,12 +1388,14 @@ Supported behavior includes:
|
|
|
1379
1388
|
- selection with `SHIFT_LEFT`, `SHIFT_RIGHT`, and `CTRL_A`
|
|
1380
1389
|
- copy, cut, and paste through `CTRL_C`, `CTRL_X`, and `CTRL_V`
|
|
1381
1390
|
- deletion with `BACKSPACE` and `DELETE`
|
|
1391
|
+
- context press through pointer context input
|
|
1382
1392
|
|
|
1383
1393
|
Main handlers:
|
|
1384
1394
|
|
|
1385
1395
|
- `onchange`
|
|
1386
1396
|
- `oninput`
|
|
1387
1397
|
- `onsubmit`
|
|
1398
|
+
- `oncontextpress`
|
|
1388
1399
|
|
|
1389
1400
|
### `Editor`
|
|
1390
1401
|
|
|
@@ -1422,24 +1433,32 @@ Activation paths:
|
|
|
1422
1433
|
Main handlers:
|
|
1423
1434
|
|
|
1424
1435
|
- `onpress`
|
|
1425
|
-
- `
|
|
1426
|
-
- `
|
|
1436
|
+
- `ondoublepress`
|
|
1437
|
+
- `oncontextpress`
|
|
1438
|
+
|
|
1439
|
+
A quick second primary mouse press keeps the normal activation behavior and also emits `ondoublepress` when the handler exists. A secondary mouse press emits `oncontextpress`.
|
|
1427
1440
|
|
|
1428
1441
|
### `List`
|
|
1429
1442
|
|
|
1430
|
-
`List` models row selection and activation.
|
|
1443
|
+
`List` models active row movement, optional visible selection, viewport scroll, and activation. In virtualized mode the component receives the full `items` array and owns the viewport; wheel input moves the viewport, while keyboard list commands move the active row and emit `change`. A JSX `children` callback receives `(item, ctx)` with `ctx.active`, `ctx.selected`, `ctx.index`, `ctx.key`, and `ctx.viewportIndex`; it takes precedence over `renderItem`. Use `itemKey` when rows have stable identities across reorder or filtering.
|
|
1431
1444
|
|
|
1432
1445
|
Supported behavior includes:
|
|
1433
1446
|
|
|
1434
|
-
- `UP` and `
|
|
1435
|
-
- `
|
|
1436
|
-
- `
|
|
1447
|
+
- `UP` and `DOWN` move the active row and emit `onchange`
|
|
1448
|
+
- `PageUp`, `PageDown`, `Home`, and `End` jump the active row and keep it visible
|
|
1449
|
+
- `LEFT` and `RIGHT` have no default list command, so applications can bind them
|
|
1450
|
+
- `ENTER` selects and activates the active row
|
|
1451
|
+
- mouse click selects and activates the target row
|
|
1437
1452
|
- mouse hover reports row details when mouse input is connected
|
|
1453
|
+
- quick primary mouse clicks on the same row emit `ondoublepress` without a second `onpress`
|
|
1454
|
+
- secondary mouse presses emit `oncontextpress` for the target row
|
|
1438
1455
|
|
|
1439
1456
|
Main handlers:
|
|
1440
1457
|
|
|
1441
1458
|
- `onchange`
|
|
1442
1459
|
- `onpress`
|
|
1460
|
+
- `ondoublepress`
|
|
1461
|
+
- `oncontextpress`
|
|
1443
1462
|
- `onhover`
|
|
1444
1463
|
- `onrowenter`
|
|
1445
1464
|
- `onrowleave`
|
|
@@ -1458,12 +1477,14 @@ Supported behavior includes:
|
|
|
1458
1477
|
- `height` as the visible viewport
|
|
1459
1478
|
- `highlightRows` for marking visible rows
|
|
1460
1479
|
- mouse hover details when mouse input is connected
|
|
1480
|
+
- context press reports the target visible row
|
|
1461
1481
|
|
|
1462
1482
|
Main handlers:
|
|
1463
1483
|
|
|
1464
1484
|
- `onhover`
|
|
1465
1485
|
- `onrowenter`
|
|
1466
1486
|
- `onrowleave`
|
|
1487
|
+
- `oncontextpress`
|
|
1467
1488
|
- `oncapturestart`
|
|
1468
1489
|
- `oncaptureend`
|
|
1469
1490
|
|
|
@@ -4936,9 +4957,9 @@ import { Screen, Text, mountTerminal, renderTerminal } from "@valyrianjs/termina
|
|
|
4936
4957
|
|
|
4937
4958
|
Rendering-only subpath for lower-level rendering utilities:
|
|
4938
4959
|
|
|
4939
|
-
- `renderTerminal(input): string`
|
|
4940
|
-
- `renderTerminalNode(node): string`
|
|
4941
|
-
- `renderTerminalFrame(node): TerminalFrame`
|
|
4960
|
+
- `renderTerminal(input, context?): string`
|
|
4961
|
+
- `renderTerminalNode(node, context?): string`
|
|
4962
|
+
- `renderTerminalFrame(node, context?): TerminalFrame`
|
|
4942
4963
|
|
|
4943
4964
|
Use this subpath for rendering-focused integrations that produce output without mounting an interactive session.
|
|
4944
4965
|
|
|
@@ -4948,9 +4969,9 @@ The root entrypoint exports the public primitives (`Screen`, `Box`, `View`, `Pan
|
|
|
4948
4969
|
|
|
4949
4970
|
## Main functions
|
|
4950
4971
|
|
|
4951
|
-
### `renderTerminal(input): string`
|
|
4972
|
+
### `renderTerminal(input, context?): string`
|
|
4952
4973
|
|
|
4953
|
-
Resolves the provided terminal tree and returns plain text.
|
|
4974
|
+
Resolves the provided terminal tree and returns plain text. Pass `{ cols, rows }` when the root tree uses viewport-dependent primitives such as standalone `Overlay` with `margin` or standalone `Fixed`.
|
|
4954
4975
|
|
|
4955
4976
|
Use it for:
|
|
4956
4977
|
|
|
@@ -4959,6 +4980,8 @@ Use it for:
|
|
|
4959
4980
|
- non-interactive command output
|
|
4960
4981
|
- layout checks without session state
|
|
4961
4982
|
|
|
4983
|
+
`context` accepts exact terminal dimensions as positive integers and an optional theme: `{ cols, rows, theme? }`. Mounted sessions infer dimensions from `mountTerminal()` options or the host terminal.
|
|
4984
|
+
|
|
4962
4985
|
### `mountTerminal(input, options?): TerminalSession`
|
|
4963
4986
|
|
|
4964
4987
|
Creates an interactive session around a terminal tree.
|
|
@@ -5013,7 +5036,7 @@ Theme recipes belong in `theme.styles`. Use `color`, `background`, padding, and
|
|
|
5013
5036
|
|
|
5014
5037
|
## Visual style system
|
|
5015
5038
|
|
|
5016
|
-
`theme.styles` stores named style recipes. A recipe may define color, background, padding, border, and nested visual-state recipes. The bundled default theme
|
|
5039
|
+
`theme.styles` stores named style recipes. A recipe may define color, background, padding, border, and nested visual-state recipes. The renderer applies `theme.styles.<element>.base` automatically to public primitives that have a matching `.base` recipe, before any instance `style`. The bundled default theme uses that rule for app-like core controls such as `button.base`, `button.focus`, `input.base`, `input.focus`, `input.selection`, `list.base`, `list.current`, `list.selected`, `list.hover`, and `list.empty`. Components also resolve recipes by dot path, so `style="panel.queue"` reads `theme.styles.panel.queue`. Inline style objects work too when the style belongs to one component instance.
|
|
5017
5040
|
|
|
5018
5041
|
Use `style`, `styles`, and `state` for normal component styling:
|
|
5019
5042
|
|
|
@@ -5023,7 +5046,7 @@ Use `style`, `styles`, and `state` for normal component styling:
|
|
|
5023
5046
|
|
|
5024
5047
|
Renderer-owned states come from the runtime when the renderer has the fact itself. Focus, hover, selection, current rows, and pointer capture fall in this group when the primitive supports them. App-owned state should describe product state that the app already knows. Do not mark a ready button as `loading` or an editable field as `readonly`; that teaches a lie to the UI and then the UI repeats it with confidence.
|
|
5025
5048
|
|
|
5026
|
-
Precedence: the renderer starts with the
|
|
5049
|
+
Precedence: the renderer starts with the automatic `<element>.base` recipe when the primitive has one, applies the instance `style`, then applies `styles` entries for current states in stable visual-state order. Geometry fields such as supported padding and border come from the automatic base recipe plus the instance `style` so layout, hitboxes, and cursors stay stable across focus, hover, and pressed state changes. State styles affect the rendered spans after layout; later current states override earlier visual fields when they set the same property. Inline style objects and dot path recipes use the same merge path after resolution.
|
|
5027
5050
|
|
|
5028
5051
|
Renderer notes:
|
|
5029
5052
|
|
|
@@ -5141,24 +5164,21 @@ Props:
|
|
|
5141
5164
|
- `position: "top" | "bottom" | "left" | "right"`
|
|
5142
5165
|
- `size: number`
|
|
5143
5166
|
|
|
5144
|
-
|
|
5167
|
+
Reserves stable rows or columns at a frame edge. When `Fixed` is a direct child of `Screen` or `Pane`, that parent supplies the frame and consumes the fixed region before it lays out the remaining content. When `Fixed` is rendered as the root tree passed directly to `renderTerminal()`, it is viewport-dependent and the render call must pass exact dimensions with `{ cols, rows }`. Mounted sessions get those dimensions from `mountTerminal()` options or the host terminal.
|
|
5145
5168
|
|
|
5146
5169
|
### `Overlay`
|
|
5147
5170
|
|
|
5148
5171
|
Props:
|
|
5149
5172
|
|
|
5150
|
-
- `x: number`
|
|
5151
|
-
- `y: number`
|
|
5152
|
-
- `width: number`
|
|
5153
|
-
- `height: number`
|
|
5173
|
+
- `margin: number | "<number>%" | { x: number | "<number>%"; y: number | "<number>%" }`
|
|
5154
5174
|
- shared visual props: `style`, `styles`, `state`
|
|
5155
5175
|
- `trapFocus?: boolean`
|
|
5156
5176
|
- `id?: string`
|
|
5157
5177
|
- `focusable?: boolean`
|
|
5158
5178
|
|
|
5159
|
-
Draws a clipped region over the current frame.
|
|
5179
|
+
Draws a clipped region over the current frame. `margin` sets the distance from the overlay to the frame edges; numbers use cells and percentage strings such as `"10%"` resolve per axis. Use `{ x, y }` when the two axes need different margins. `trapFocus` keeps traversal inside the overlay while it is active.
|
|
5160
5180
|
|
|
5161
|
-
Overlay
|
|
5181
|
+
Overlay calculates its size from the available frame, stays centered by its margins, and keeps app routing and pane movement in your app layer. Direct sibling overlays use source order: later overlays paint above earlier overlays, receive pointer hits first, and lead trapped focus traversal. Overlay styles paint the full assigned surface, including empty cells behind child controls.
|
|
5162
5182
|
|
|
5163
5183
|
### `FocusScope`
|
|
5164
5184
|
|
|
@@ -5195,13 +5215,15 @@ Props:
|
|
|
5195
5215
|
- `onchange?(event)`
|
|
5196
5216
|
- `oninput?(event)`
|
|
5197
5217
|
- `onsubmit?(event)`
|
|
5218
|
+
- `oncontextpress?(event)`
|
|
5198
5219
|
|
|
5199
5220
|
Payloads:
|
|
5200
5221
|
|
|
5201
5222
|
- `TerminalInputChangeEventPayload` - `{ type: "change" | "input", id, value }`
|
|
5202
5223
|
- `TerminalInputSubmitEventPayload` - `{ type: "submit", id, value }`
|
|
5224
|
+
- `TerminalInputContextPressEventPayload` - `{ type: "contextpress", id, value, cursor, x, y }`
|
|
5203
5225
|
|
|
5204
|
-
Supports single-line editing, cursor movement, selection, copy/cut/paste, deletion, and
|
|
5226
|
+
Supports single-line editing, cursor movement, selection, copy/cut/paste, deletion, submit, and context press.
|
|
5205
5227
|
|
|
5206
5228
|
### `Editor`
|
|
5207
5229
|
|
|
@@ -5235,14 +5257,14 @@ Props:
|
|
|
5235
5257
|
- `label?: string`
|
|
5236
5258
|
- shared visual props: `style`, `styles`, `state`
|
|
5237
5259
|
- `onpress?(event)`
|
|
5238
|
-
- `
|
|
5239
|
-
- `
|
|
5260
|
+
- `ondoublepress?(event)`
|
|
5261
|
+
- `oncontextpress?(event)`
|
|
5240
5262
|
|
|
5241
5263
|
Payload:
|
|
5242
5264
|
|
|
5243
|
-
- `TerminalButtonPressEventPayload` - `{ type: "press" | "
|
|
5265
|
+
- `TerminalButtonPressEventPayload` - `{ type: "press" | "doublepress" | "contextpress", id }`
|
|
5244
5266
|
|
|
5245
|
-
Activation paths include focused `ENTER`, focused `SPACE`, `session.click(id)`, and `session.clickAt(x, y)`.
|
|
5267
|
+
Activation paths include focused `ENTER`, focused `SPACE`, `session.click(id)`, and `session.clickAt(x, y)`. A quick second primary mouse press still dispatches the normal activation path and also dispatches `ondoublepress` when present. A secondary mouse press dispatches `oncontextpress`.
|
|
5246
5268
|
|
|
5247
5269
|
### `List<T>`
|
|
5248
5270
|
|
|
@@ -5253,12 +5275,20 @@ Props:
|
|
|
5253
5275
|
- `pointerCapture?: boolean`
|
|
5254
5276
|
- `items?: T[]`
|
|
5255
5277
|
- `virtualized?: boolean`
|
|
5278
|
+
- `height?: number`
|
|
5256
5279
|
- `itemHeight?: 1`
|
|
5257
5280
|
- `overscan?: number`
|
|
5281
|
+
- `wrap?: boolean`
|
|
5282
|
+
- `itemKey?(item, index): string | number`
|
|
5283
|
+
- `showActive?: boolean`
|
|
5258
5284
|
- shared visual props: `style`, `styles`, `state`
|
|
5259
|
-
- `
|
|
5285
|
+
- `children?(item, ctx): any`
|
|
5286
|
+
- `renderItem?(item, index): any`
|
|
5260
5287
|
- `onchange?(event)`
|
|
5288
|
+
- `onviewportchange?(event)`
|
|
5261
5289
|
- `onpress?(event)`
|
|
5290
|
+
- `ondoublepress?(event)`
|
|
5291
|
+
- `oncontextpress?(event)`
|
|
5262
5292
|
- `onhover?(event)`
|
|
5263
5293
|
- `onrowenter?(event)`
|
|
5264
5294
|
- `onrowleave?(event)`
|
|
@@ -5267,12 +5297,13 @@ Props:
|
|
|
5267
5297
|
|
|
5268
5298
|
Payloads:
|
|
5269
5299
|
|
|
5270
|
-
- `TerminalListChangeEventPayload<T>` - `{ type: "change", id, index, value }`
|
|
5271
|
-
- `
|
|
5272
|
-
- `
|
|
5300
|
+
- `TerminalListChangeEventPayload<T>` - `{ type: "change", id, index, key?, value, activeIndex, selectedIndex, viewportOffset, viewportRows }`
|
|
5301
|
+
- `TerminalListViewportChangeEventPayload` - `{ type: "viewportchange", id, offset, rows, activeIndex, selectedIndex, viewportOffset, viewportRows }`
|
|
5302
|
+
- `TerminalListPressEventPayload<T>` - `{ type: "press" | "doublepress" | "contextpress", id, index, key?, value, activeIndex, selectedIndex, viewportOffset, viewportRows }`
|
|
5303
|
+
- `TerminalListPointerEventPayload<T>` - `{ type: "hover" | "rowenter" | "rowleave", id, row, index, key?, value, x, y }`
|
|
5273
5304
|
- `TerminalCaptureEventPayload` - `{ type: "capturestart" | "captureend", id, source, row, x, y }`
|
|
5274
5305
|
|
|
5275
|
-
Supports selection, activation, hover payloads, optional virtualization, and pointer capture.
|
|
5306
|
+
Supports internal active row movement, internal selection, activation, double press on the same row, context press for the target row, hover payloads, optional wrapping, optional virtualization, viewport scroll, and pointer capture. `Up`/`Down`, `PageUp`, `PageDown`, `Home`, and `End` are built-in list navigation keys; `Left` and `Right` stay available for application bindings. Keyboard navigation moves the active row and emits `change`; `Enter` selects and activates the active row. Pointer click selects and activates the clicked row. A quick second primary click on the same row emits `doublepress` without a second `press`. The default renderer displays the active row as `list.current`; when the selected row differs from the active row, it displays the selected row as `list.selected`. A JSX `children` callback is the recommended item renderer and wins over `renderItem` when both are present. `children` receives `ctx.active` and `ctx.selected` separately. `renderItem` remains as a compatibility alias with the legacy `(item, index)` signature. `wrap` wraps long default-rendered item text to the list width. Virtualized lists receive the full `items` array and calculate the visible viewport internally; use `itemKey` for stable event identity when items can reorder. Observe the resulting active row, selected row, and viewport through the list event payloads. Run [`examples/docs/virtualized-list-workbench.tsx`](../examples/docs/virtualized-list-workbench.tsx) with `bun examples/docs/virtualized-list-workbench.tsx` to inspect those values from events in one bounded split layout.
|
|
5276
5307
|
|
|
5277
5308
|
### `ScrollView`
|
|
5278
5309
|
|
|
@@ -5284,15 +5315,17 @@ Props:
|
|
|
5284
5315
|
- `onhover?(event)`
|
|
5285
5316
|
- `onrowenter?(event)`
|
|
5286
5317
|
- `onrowleave?(event)`
|
|
5318
|
+
- `oncontextpress?(event)`
|
|
5287
5319
|
- `oncapturestart?(event)`
|
|
5288
5320
|
- `oncaptureend?(event)`
|
|
5289
5321
|
|
|
5290
5322
|
Payloads:
|
|
5291
5323
|
|
|
5292
5324
|
- `TerminalScrollPointerEventPayload` - `{ type: "hover" | "rowenter" | "rowleave", id, row, value, x, y }`
|
|
5325
|
+
- `TerminalScrollContextPressEventPayload` - `{ type: "contextpress", id, row, value, x, y }`
|
|
5293
5326
|
- `TerminalCaptureEventPayload` - `{ type: "capturestart" | "captureend", id, source, row, x, y }`
|
|
5294
5327
|
|
|
5295
|
-
Clips vertical content and supports keyboard scrolling, highlighted rows, hover payloads, and pointer capture.
|
|
5328
|
+
Clips vertical content and supports keyboard scrolling, highlighted rows, hover payloads, context press for the target visible row, and pointer capture.
|
|
5296
5329
|
|
|
5297
5330
|
### `LogView`
|
|
5298
5331
|
|
|
@@ -5314,9 +5347,9 @@ Renders append-only logs and transcript-like panes. `followTail` keeps the visib
|
|
|
5314
5347
|
|
|
5315
5348
|
The `@valyrianjs/terminal/render` subpath exports:
|
|
5316
5349
|
|
|
5317
|
-
- `renderTerminal(input): string`
|
|
5318
|
-
- `renderTerminalNode(node): string`
|
|
5319
|
-
- `renderTerminalFrame(node): TerminalFrame`
|
|
5350
|
+
- `renderTerminal(input, context?): string`
|
|
5351
|
+
- `renderTerminalNode(node, context?): string`
|
|
5352
|
+
- `renderTerminalFrame(node, context?): TerminalFrame`
|
|
5320
5353
|
|
|
5321
5354
|
These functions are useful for render-only integrations that should not import session runtime helpers.
|
|
5322
5355
|
|
package/package.json
CHANGED
package/src/ansi.ts
CHANGED
|
@@ -5,6 +5,8 @@ export const ANSI_ENTER_ALTERNATE_SCREEN = "\u001b[?1049h";
|
|
|
5
5
|
export const ANSI_EXIT_ALTERNATE_SCREEN = "\u001b[?1049l";
|
|
6
6
|
export const ANSI_HIDE_CURSOR = "\u001b[?25l";
|
|
7
7
|
export const ANSI_SHOW_CURSOR = "\u001b[?25h";
|
|
8
|
+
export const ANSI_ENABLE_MOUSE_REPORTING = "\u001b[?1000h\u001b[?1002h\u001b[?1006h";
|
|
9
|
+
export const ANSI_DISABLE_MOUSE_REPORTING = "\u001b[?1002l\u001b[?1000l\u001b[?1006l";
|
|
8
10
|
|
|
9
11
|
type AnsiFrameOptions = {
|
|
10
12
|
showCursor?: boolean;
|
|
@@ -112,16 +114,26 @@ function renderAnsiLine(line: string, spans: TerminalStyleSpan[], y: number, the
|
|
|
112
114
|
output += char;
|
|
113
115
|
visibleColumn += 1;
|
|
114
116
|
|
|
117
|
+
let closedSpan = false;
|
|
115
118
|
for (const span of rowSpans) {
|
|
116
119
|
if (span.x2 === visibleColumn) {
|
|
117
120
|
if (span.kind !== "focus") {
|
|
118
121
|
output += spanAnsiClose(span, theme);
|
|
122
|
+
closedSpan = true;
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
for (const span of rowSpans) {
|
|
123
127
|
if (span.x2 === visibleColumn && span.kind === "focus") {
|
|
124
128
|
output += spanAnsiClose(span, theme);
|
|
129
|
+
closedSpan = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (closedSpan && visibleColumn <= line.length && line[visibleColumn - 1] === " ") {
|
|
133
|
+
for (const span of rowSpans) {
|
|
134
|
+
if (span.x1 < visibleColumn && span.x2 > visibleColumn) {
|
|
135
|
+
output += spanAnsiOpen(span, theme);
|
|
136
|
+
}
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
139
|
}
|
package/src/events.ts
CHANGED
|
@@ -151,9 +151,11 @@ export function parseTerminalKey(chunk: string | Uint8Array) {
|
|
|
151
151
|
if (value === "\u001b[1;3C" || value === "\u001bf") return "ALT_RIGHT";
|
|
152
152
|
if (value === "\u001b[1;3D" || value === "\u001bb") return "ALT_LEFT";
|
|
153
153
|
if (value === "\u001b[3~") return "DELETE";
|
|
154
|
+
if (value === "\u001b[5~") return "PAGEUP";
|
|
155
|
+
if (value === "\u001b[6~") return "PAGEDOWN";
|
|
154
156
|
if (value === "\u0008" || value === "\u007f") return "BACKSPACE";
|
|
155
|
-
if (value === "\u001b[H") return "HOME";
|
|
156
|
-
if (value === "\u001b[F") return "END";
|
|
157
|
+
if (value === "\u001b[H" || value === "\u001b[1~") return "HOME";
|
|
158
|
+
if (value === "\u001b[F" || value === "\u001b[4~") return "END";
|
|
157
159
|
if (value === "\u0001") return "CTRL_A";
|
|
158
160
|
if (value === "\u0003") return "CTRL_C";
|
|
159
161
|
if (value === "\u000b") return "CTRL_K";
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
TerminalFrame,
|
|
20
20
|
TerminalHitbox,
|
|
21
21
|
TerminalInputChangeEventPayload,
|
|
22
|
+
TerminalInputContextPressEventPayload,
|
|
22
23
|
TerminalInputProps,
|
|
23
24
|
TerminalInputSubmitEventPayload,
|
|
24
25
|
TerminalKeyBinding,
|
|
@@ -46,6 +47,7 @@ export type {
|
|
|
46
47
|
TerminalRowProps,
|
|
47
48
|
TerminalScreenProps,
|
|
48
49
|
TerminalScrollPointerEventPayload,
|
|
50
|
+
TerminalScrollContextPressEventPayload,
|
|
49
51
|
TerminalScrollViewProps,
|
|
50
52
|
TerminalSemanticStyleKind,
|
|
51
53
|
TerminalSession,
|
|
@@ -89,4 +91,5 @@ export {
|
|
|
89
91
|
} from "./keymap.js";
|
|
90
92
|
export { defaultTerminalTheme, highContrastTerminalTheme, mergeTerminalTheme, resolveTerminalStyle, resolveTerminalStyleToken } from "./theme.js";
|
|
91
93
|
export { renderTerminal } from "./render.js";
|
|
94
|
+
export type { TerminalRenderContext } from "./render.js";
|
|
92
95
|
export { mountTerminal } from "./session.js";
|
package/src/keymap.ts
CHANGED
|
@@ -32,9 +32,11 @@ export const defaultTerminalKeyBindings: TerminalKeyBinding[] = [
|
|
|
32
32
|
{ key: "ENTER", command: { id: "button.press" }, scope: "button", when: { focusedTag: "terminal-button" } },
|
|
33
33
|
{ key: "SPACE", command: { id: "button.press" }, scope: "button", when: { focusedTag: "terminal-button" } },
|
|
34
34
|
{ key: "UP", command: { id: "list.prev" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
35
|
-
{ key: "LEFT", command: { id: "list.prev" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
36
35
|
{ key: "DOWN", command: { id: "list.next" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
37
|
-
{ key: "
|
|
36
|
+
{ key: "PAGEUP", command: { id: "list.pageUp" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
37
|
+
{ key: "PAGEDOWN", command: { id: "list.pageDown" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
38
|
+
{ key: "HOME", command: { id: "list.home" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
39
|
+
{ key: "END", command: { id: "list.end" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
38
40
|
{ key: "ENTER", command: { id: "list.press" }, scope: "list", when: { focusedTag: "terminal-list" } },
|
|
39
41
|
{ key: "UP", command: { id: "scroll.up" }, scope: "scroll", when: { focusedTag: "terminal-scroll" } },
|
|
40
42
|
{ key: "DOWN", command: { id: "scroll.down" }, scope: "scroll", when: { focusedTag: "terminal-scroll" } }
|
package/src/layout.ts
CHANGED
|
@@ -25,7 +25,8 @@ export function shiftFrame(frame: TerminalFrame, dx: number, dy: number): Termin
|
|
|
25
25
|
x2: box.x2 + dx,
|
|
26
26
|
y1: box.y1 + dy,
|
|
27
27
|
y2: box.y2 + dy,
|
|
28
|
-
textStartX: typeof box.textStartX === "number" ? box.textStartX + dx : undefined
|
|
28
|
+
textStartX: typeof box.textStartX === "number" ? box.textStartX + dx : undefined,
|
|
29
|
+
contentY: typeof box.contentY === "number" ? box.contentY + dy : undefined
|
|
29
30
|
})),
|
|
30
31
|
cursor: frame.cursor ? { x: frame.cursor.x + dx, y: frame.cursor.y + dy } : null,
|
|
31
32
|
spans: frame.spans.map((span) => ({ ...span, x1: span.x1 + dx, x2: span.x2 + dx, y: span.y + dy }))
|
package/src/mouse.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { parseTerminalKey } from "./events.js";
|
|
|
2
2
|
|
|
3
3
|
import type { ParsedTerminalInput, TerminalHitbox } from "./types.js";
|
|
4
4
|
|
|
5
|
-
const nestedParentTags = new Set(["terminal-box", "terminal-view"]);
|
|
5
|
+
const nestedParentTags = new Set(["terminal-box", "terminal-view", "terminal-list"]);
|
|
6
6
|
|
|
7
7
|
function containsHitbox(parent: TerminalHitbox, child: TerminalHitbox) {
|
|
8
8
|
return child.x1 >= parent.x1 && child.x2 <= parent.x2 && child.y1 >= parent.y1 && child.y2 <= parent.y2;
|
|
@@ -12,24 +12,40 @@ function samePointerLayer(left: TerminalHitbox, right: TerminalHitbox) {
|
|
|
12
12
|
return (left.pointerLayer ?? 0) === (right.pointerLayer ?? 0);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function parseMouseMatch(match: RegExpExecArray): Extract<ParsedTerminalInput, { type: "mouse" }> {
|
|
16
|
+
const code = Number(match[1]);
|
|
17
|
+
let action: "press" | "release" | "drag" | "wheel-up" | "wheel-down" = match[4] === "M" ? "press" : "release";
|
|
18
|
+
if (code >= 64) {
|
|
19
|
+
action = code === 64 ? "wheel-up" : "wheel-down";
|
|
20
|
+
} else if (code >= 32 && match[4] === "M") {
|
|
21
|
+
action = "drag";
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
type: "mouse",
|
|
25
|
+
button: code,
|
|
26
|
+
x: Number(match[2]),
|
|
27
|
+
y: Number(match[3]),
|
|
28
|
+
action
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parseTerminalMousePrefix(chunk: string | Uint8Array): { input: Extract<ParsedTerminalInput, { type: "mouse" }>; rest: string } | null {
|
|
33
|
+
const value = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
34
|
+
const match = /^\u001b\[<(\d+);(\d+);(\d+)([Mm])/.exec(value);
|
|
35
|
+
if (!match) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
input: parseMouseMatch(match),
|
|
40
|
+
rest: value.slice(match[0].length)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
15
44
|
export function parseTerminalInput(chunk: string | Uint8Array): ParsedTerminalInput {
|
|
16
45
|
const value = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
17
46
|
const match = /^\u001b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(value);
|
|
18
47
|
if (match) {
|
|
19
|
-
|
|
20
|
-
let action: "press" | "release" | "drag" | "wheel-up" | "wheel-down" = match[4] === "M" ? "press" : "release";
|
|
21
|
-
if (code >= 64) {
|
|
22
|
-
action = code === 64 ? "wheel-up" : "wheel-down";
|
|
23
|
-
} else if (code >= 32 && match[4] === "M") {
|
|
24
|
-
action = "drag";
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
type: "mouse",
|
|
28
|
-
button: code,
|
|
29
|
-
x: Number(match[2]),
|
|
30
|
-
y: Number(match[3]),
|
|
31
|
-
action
|
|
32
|
-
};
|
|
48
|
+
return parseMouseMatch(match);
|
|
33
49
|
}
|
|
34
50
|
return { type: "key", key: parseTerminalKey(value) };
|
|
35
51
|
}
|
package/src/primitives.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { v } from "valyrian.js";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
AnyProps,
|
|
5
4
|
TerminalBoxProps,
|
|
6
5
|
TerminalButtonProps,
|
|
7
6
|
TerminalEditorProps,
|
|
@@ -23,10 +22,14 @@ import type {
|
|
|
23
22
|
TerminalViewProps
|
|
24
23
|
} from "./types.js";
|
|
25
24
|
|
|
26
|
-
type
|
|
25
|
+
type TerminalPrimitiveComponentProps<P> = P & { children?: any; key?: any };
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
type TerminalPrimitiveComponent<P> = (props: TerminalPrimitiveComponentProps<P>, children: any[]) => any;
|
|
28
|
+
|
|
29
|
+
type TerminalListComponent = <T>(props: TerminalPrimitiveComponentProps<TerminalListProps<T>>, children: any[]) => any;
|
|
30
|
+
|
|
31
|
+
function primitive<P>(tag: TerminalPrimitiveTag): TerminalPrimitiveComponent<P> {
|
|
32
|
+
return function TerminalPrimitive(props: TerminalPrimitiveComponentProps<P> | null | undefined, children: any[]) {
|
|
30
33
|
const nextProps = { ...(props || {}) } as Record<string, any>;
|
|
31
34
|
return v(tag, nextProps, ...children);
|
|
32
35
|
};
|
|
@@ -46,7 +49,14 @@ export const Editor = primitive<TerminalEditorProps>("terminal-editor");
|
|
|
46
49
|
export const Button = primitive<TerminalButtonProps>("terminal-button");
|
|
47
50
|
export const ScrollView = primitive<TerminalScrollViewProps>("terminal-scroll");
|
|
48
51
|
export const LogView = primitive<TerminalLogViewProps>("terminal-log-view");
|
|
49
|
-
export const List =
|
|
52
|
+
export const List = function TerminalList(props: TerminalPrimitiveComponentProps<TerminalListProps<any>> | null | undefined, children: any[]) {
|
|
53
|
+
const nextProps = { ...(props || {}) } as Record<string, any>;
|
|
54
|
+
if (children.length === 1 && typeof children[0] === "function") {
|
|
55
|
+
nextProps.__childrenRenderer = children[0];
|
|
56
|
+
return v("terminal-list", nextProps);
|
|
57
|
+
}
|
|
58
|
+
return v("terminal-list", nextProps, ...children);
|
|
59
|
+
} as TerminalListComponent;
|
|
50
60
|
export const Table = primitive<TerminalTableProps>("terminal-table");
|
|
51
61
|
export const Row = primitive<TerminalRowProps>("terminal-row");
|
|
52
62
|
export const Td = primitive<TerminalTdProps>("terminal-td");
|