@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.
Files changed (62) hide show
  1. package/dist/ansi.d.ts +2 -0
  2. package/dist/ansi.d.ts.map +1 -1
  3. package/dist/ansi.js +12 -0
  4. package/dist/ansi.js.map +1 -1
  5. package/dist/events.d.ts.map +1 -1
  6. package/dist/events.js +6 -2
  7. package/dist/events.js.map +1 -1
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/keymap.d.ts.map +1 -1
  12. package/dist/keymap.js +4 -2
  13. package/dist/keymap.js.map +1 -1
  14. package/dist/layout.d.ts.map +1 -1
  15. package/dist/layout.js +2 -1
  16. package/dist/layout.js.map +1 -1
  17. package/dist/mouse.d.ts +6 -0
  18. package/dist/mouse.d.ts.map +1 -1
  19. package/dist/mouse.js +30 -16
  20. package/dist/mouse.js.map +1 -1
  21. package/dist/primitives.d.ts +8 -3
  22. package/dist/primitives.d.ts.map +1 -1
  23. package/dist/primitives.js +8 -1
  24. package/dist/primitives.js.map +1 -1
  25. package/dist/render.d.ts +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +290 -51
  28. package/dist/render.js.map +1 -1
  29. package/dist/runtime.d.ts.map +1 -1
  30. package/dist/runtime.js +13 -11
  31. package/dist/runtime.js.map +1 -1
  32. package/dist/session.d.ts.map +1 -1
  33. package/dist/session.js +434 -65
  34. package/dist/session.js.map +1 -1
  35. package/dist/theme.d.ts.map +1 -1
  36. package/dist/theme.js +3 -0
  37. package/dist/theme.js.map +1 -1
  38. package/dist/tree.d.ts.map +1 -1
  39. package/dist/tree.js +18 -4
  40. package/dist/tree.js.map +1 -1
  41. package/dist/types.d.ts +61 -13
  42. package/dist/types.d.ts.map +1 -1
  43. package/docs/api-reference.md +40 -28
  44. package/docs/cookbook.md +2 -2
  45. package/docs/core-concepts.md +1 -1
  46. package/docs/interaction-model.md +18 -6
  47. package/docs/primitive-gallery.md +19 -10
  48. package/llms-full.txt +80 -47
  49. package/package.json +1 -1
  50. package/src/ansi.ts +12 -0
  51. package/src/events.ts +4 -2
  52. package/src/index.ts +3 -0
  53. package/src/keymap.ts +4 -2
  54. package/src/layout.ts +2 -1
  55. package/src/mouse.ts +31 -15
  56. package/src/primitives.ts +15 -5
  57. package/src/render.ts +320 -52
  58. package/src/runtime.ts +13 -11
  59. package/src/session.ts +469 -59
  60. package/src/theme.ts +3 -0
  61. package/src/tree.ts +19 -4
  62. 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 x={2} y={2} width={40} height={6} trapFocus>
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 `Screen` or `Pane`.
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
- <Fixed position="bottom" size={1}><Text>Ctrl+C: quit</Text></Fixed>
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`, `onclick`, `action`, `style`, `styles`, `state`.
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"]} onchange={(event) => { selected = event.value; }} />
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`, `x`, `y`, `width`, `height`, `trapFocus`, `style`.
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 x={4} y={2} width={40} height={8} trapFocus><Text>Command Panel</Text></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 x={2} y={2} width={32} height={8} trapFocus><FocusScope><Input id="query" /><List id="commands" items={commands} /></FocusScope></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
- - `onclick`
1426
- - `action`
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 `LEFT` move selection up
1435
- - `DOWN` and `RIGHT` move selection down
1436
- - `ENTER` activates the selected row
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 includes app-like recipes for 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 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.
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 component default recipe, 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 component default 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.
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
- Direct-child marker consumed by `Screen` and `Pane` to reserve stable rows or columns.
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. Use it for popovers and lightweight dialog-like surfaces. `trapFocus` keeps traversal inside the overlay while it is active.
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 keeps focused surfaces clipped and positioned; app routing and pane movement stay in your app layer.
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 submit.
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
- - `onclick?(event)`
5239
- - `action?(event)`
5260
+ - `ondoublepress?(event)`
5261
+ - `oncontextpress?(event)`
5240
5262
 
5241
5263
  Payload:
5242
5264
 
5243
- - `TerminalButtonPressEventPayload` - `{ type: "press" | "click", id }`
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
- - `renderItem?(item, index): string`
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
- - `TerminalListPressEventPayload<T>` - `{ type: "press", id, index, value }`
5272
- - `TerminalListPointerEventPayload<T>` - `{ type: "hover" | "rowenter" | "rowleave", id, row, index, value, x, y }`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valyrianjs/terminal",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Terminal adapter for valyrian.js",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
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: "RIGHT", command: { id: "list.next" }, scope: "list", when: { focusedTag: "terminal-list" } },
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
- const code = Number(match[1]);
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 TerminalPrimitiveComponent<P> = (props: P | AnyProps, children: any[]) => any;
25
+ type TerminalPrimitiveComponentProps<P> = P & { children?: any; key?: any };
27
26
 
28
- function primitive<P = Record<string, any>>(tag: TerminalPrimitiveTag): TerminalPrimitiveComponent<P> {
29
- return function TerminalPrimitive(props: P | AnyProps, children: any[]) {
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 = primitive<TerminalListProps<any>>("terminal-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");