@valyrianjs/terminal 0.2.0 → 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 (55) 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/keymap.d.ts.map +1 -1
  9. package/dist/keymap.js +4 -2
  10. package/dist/keymap.js.map +1 -1
  11. package/dist/layout.d.ts.map +1 -1
  12. package/dist/layout.js +2 -1
  13. package/dist/layout.js.map +1 -1
  14. package/dist/mouse.d.ts +6 -0
  15. package/dist/mouse.d.ts.map +1 -1
  16. package/dist/mouse.js +30 -16
  17. package/dist/mouse.js.map +1 -1
  18. package/dist/primitives.d.ts.map +1 -1
  19. package/dist/primitives.js +8 -1
  20. package/dist/primitives.js.map +1 -1
  21. package/dist/render.d.ts.map +1 -1
  22. package/dist/render.js +184 -27
  23. package/dist/render.js.map +1 -1
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +13 -5
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/session.d.ts.map +1 -1
  28. package/dist/session.js +323 -83
  29. package/dist/session.js.map +1 -1
  30. package/dist/theme.d.ts.map +1 -1
  31. package/dist/theme.js +3 -0
  32. package/dist/theme.js.map +1 -1
  33. package/dist/tree.d.ts.map +1 -1
  34. package/dist/tree.js +18 -4
  35. package/dist/tree.js.map +1 -1
  36. package/dist/types.d.ts +38 -4
  37. package/dist/types.d.ts.map +1 -1
  38. package/docs/api-reference.md +13 -6
  39. package/docs/cookbook.md +1 -1
  40. package/docs/interaction-model.md +7 -5
  41. package/docs/primitive-gallery.md +7 -3
  42. package/llms-full.txt +28 -15
  43. package/package.json +1 -1
  44. package/src/ansi.ts +12 -0
  45. package/src/events.ts +4 -2
  46. package/src/keymap.ts +4 -2
  47. package/src/layout.ts +2 -1
  48. package/src/mouse.ts +31 -15
  49. package/src/primitives.ts +8 -1
  50. package/src/render.ts +199 -28
  51. package/src/runtime.ts +13 -5
  52. package/src/session.ts +341 -79
  53. package/src/theme.ts +3 -0
  54. package/src/tree.ts +19 -4
  55. package/src/types.ts +45 -3
@@ -239,7 +239,7 @@ Props:
239
239
 
240
240
  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.
241
241
 
242
- Overlay calculates its size from the available frame, stays centered by its margins, and keeps app routing and pane movement in your app layer.
242
+ 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.
243
243
 
244
244
  ### `FocusScope`
245
245
 
@@ -336,11 +336,17 @@ Props:
336
336
  - `pointerCapture?: boolean`
337
337
  - `items?: T[]`
338
338
  - `virtualized?: boolean`
339
+ - `height?: number`
339
340
  - `itemHeight?: 1`
340
341
  - `overscan?: number`
342
+ - `wrap?: boolean`
343
+ - `itemKey?(item, index): string | number`
344
+ - `showActive?: boolean`
341
345
  - shared visual props: `style`, `styles`, `state`
342
- - `renderItem?(item, index): string`
346
+ - `children?(item, ctx): any`
347
+ - `renderItem?(item, index): any`
343
348
  - `onchange?(event)`
349
+ - `onviewportchange?(event)`
344
350
  - `onpress?(event)`
345
351
  - `ondoublepress?(event)`
346
352
  - `oncontextpress?(event)`
@@ -352,12 +358,13 @@ Props:
352
358
 
353
359
  Payloads:
354
360
 
355
- - `TerminalListChangeEventPayload<T>` - `{ type: "change", id, index, value }`
356
- - `TerminalListPressEventPayload<T>` - `{ type: "press" | "doublepress" | "contextpress", id, index, value }`
357
- - `TerminalListPointerEventPayload<T>` - `{ type: "hover" | "rowenter" | "rowleave", id, row, index, value, x, y }`
361
+ - `TerminalListChangeEventPayload<T>` - `{ type: "change", id, index, key?, value, activeIndex, selectedIndex, viewportOffset, viewportRows }`
362
+ - `TerminalListViewportChangeEventPayload` - `{ type: "viewportchange", id, offset, rows, activeIndex, selectedIndex, viewportOffset, viewportRows }`
363
+ - `TerminalListPressEventPayload<T>` - `{ type: "press" | "doublepress" | "contextpress", id, index, key?, value, activeIndex, selectedIndex, viewportOffset, viewportRows }`
364
+ - `TerminalListPointerEventPayload<T>` - `{ type: "hover" | "rowenter" | "rowleave", id, row, index, key?, value, x, y }`
358
365
  - `TerminalCaptureEventPayload` - `{ type: "capturestart" | "captureend", id, source, row, x, y }`
359
366
 
360
- Supports selection, activation, double press on the same row, context press for the target row, hover payloads, optional virtualization, and pointer capture.
367
+ 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.
361
368
 
362
369
  ### `ScrollView`
363
370
 
package/docs/cookbook.md CHANGED
@@ -84,7 +84,7 @@ console.log(session.output());
84
84
  session.destroy();
85
85
  ```
86
86
 
87
- For large lists, add `virtualized` and place the list in a bounded region such as a filled pane or split.
87
+ 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`.
88
88
 
89
89
  ## Local state to Valyrian state module bridge
90
90
 
@@ -93,15 +93,17 @@ A quick second primary mouse press keeps the normal activation behavior and also
93
93
 
94
94
  ### `List`
95
95
 
96
- `List` models row selection and activation.
96
+ `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.
97
97
 
98
98
  Supported behavior includes:
99
99
 
100
- - `UP` and `LEFT` move selection up
101
- - `DOWN` and `RIGHT` move selection down
102
- - `ENTER` activates the selected row
100
+ - `UP` and `DOWN` move the active row and emit `onchange`
101
+ - `PageUp`, `PageDown`, `Home`, and `End` jump the active row and keep it visible
102
+ - `LEFT` and `RIGHT` have no default list command, so applications can bind them
103
+ - `ENTER` selects and activates the active row
104
+ - mouse click selects and activates the target row
103
105
  - mouse hover reports row details when mouse input is connected
104
- - quick primary mouse presses on the same row emit `ondoublepress`
106
+ - quick primary mouse clicks on the same row emit `ondoublepress` without a second `onpress`
105
107
  - secondary mouse presses emit `oncontextpress` for the target row
106
108
 
107
109
  Main handlers:
@@ -228,16 +228,20 @@ Use `List` for menus, choosers, command lists, and item browsers.
228
228
 
229
229
  **What it is:** A focusable collection control with selection and press events.
230
230
  **Use it for:** menus, choosers, command lists, and item browsers.
231
- **Core props:** `id`, `items`, `renderItem`, `virtualized`, `onchange`, `onpress`, `ondoublepress`, `oncontextpress`.
231
+ **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.
232
232
 
233
233
  **Minimal example:**
234
234
 
235
235
  ```tsx
236
- <List id="menu" items={["Inbox", "Done"]} onchange={(event) => { selected = event.value; }} />
236
+ <List id="menu" items={["Inbox", "Done"]} itemKey={(item) => item}>
237
+ {(item, ctx) => `${ctx.active ? "› " : " "}${item}`}
238
+ </List>
237
239
  ```
238
240
 
239
241
  **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`.
240
242
 
243
+ **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`.
244
+
241
245
  ### `Table`
242
246
 
243
247
  Use `Table` for compact data detail and aligned text pairs.
@@ -332,7 +336,7 @@ Use `Overlay` for command panels, dialogs, palettes, and focused chooser surface
332
336
 
333
337
  **What it is:** A positioned layer over the current frame.
334
338
  **Use it for:** command panels, dialogs, palettes, and focused chooser surfaces.
335
- **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.
339
+ **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.
336
340
 
337
341
  **Minimal example:**
338
342
 
package/llms-full.txt CHANGED
@@ -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
 
@@ -1196,16 +1196,20 @@ Use `List` for menus, choosers, command lists, and item browsers.
1196
1196
 
1197
1197
  **What it is:** A focusable collection control with selection and press events.
1198
1198
  **Use it for:** menus, choosers, command lists, and item browsers.
1199
- **Core props:** `id`, `items`, `renderItem`, `virtualized`, `onchange`, `onpress`, `ondoublepress`, `oncontextpress`.
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.
1200
1200
 
1201
1201
  **Minimal example:**
1202
1202
 
1203
1203
  ```tsx
1204
- <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>
1205
1207
  ```
1206
1208
 
1207
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`.
1208
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
+
1209
1213
  ### `Table`
1210
1214
 
1211
1215
  Use `Table` for compact data detail and aligned text pairs.
@@ -1300,7 +1304,7 @@ Use `Overlay` for command panels, dialogs, palettes, and focused chooser surface
1300
1304
 
1301
1305
  **What it is:** A positioned layer over the current frame.
1302
1306
  **Use it for:** command panels, dialogs, palettes, and focused chooser surfaces.
1303
- **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.
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.
1304
1308
 
1305
1309
  **Minimal example:**
1306
1310
 
@@ -1436,15 +1440,17 @@ A quick second primary mouse press keeps the normal activation behavior and also
1436
1440
 
1437
1441
  ### `List`
1438
1442
 
1439
- `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.
1440
1444
 
1441
1445
  Supported behavior includes:
1442
1446
 
1443
- - `UP` and `LEFT` move selection up
1444
- - `DOWN` and `RIGHT` move selection down
1445
- - `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
1446
1452
  - mouse hover reports row details when mouse input is connected
1447
- - quick primary mouse presses on the same row emit `ondoublepress`
1453
+ - quick primary mouse clicks on the same row emit `ondoublepress` without a second `onpress`
1448
1454
  - secondary mouse presses emit `oncontextpress` for the target row
1449
1455
 
1450
1456
  Main handlers:
@@ -5172,7 +5178,7 @@ Props:
5172
5178
 
5173
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.
5174
5180
 
5175
- Overlay calculates its size from the available frame, stays centered by its margins, and keeps app routing and pane movement 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.
5176
5182
 
5177
5183
  ### `FocusScope`
5178
5184
 
@@ -5269,11 +5275,17 @@ Props:
5269
5275
  - `pointerCapture?: boolean`
5270
5276
  - `items?: T[]`
5271
5277
  - `virtualized?: boolean`
5278
+ - `height?: number`
5272
5279
  - `itemHeight?: 1`
5273
5280
  - `overscan?: number`
5281
+ - `wrap?: boolean`
5282
+ - `itemKey?(item, index): string | number`
5283
+ - `showActive?: boolean`
5274
5284
  - shared visual props: `style`, `styles`, `state`
5275
- - `renderItem?(item, index): string`
5285
+ - `children?(item, ctx): any`
5286
+ - `renderItem?(item, index): any`
5276
5287
  - `onchange?(event)`
5288
+ - `onviewportchange?(event)`
5277
5289
  - `onpress?(event)`
5278
5290
  - `ondoublepress?(event)`
5279
5291
  - `oncontextpress?(event)`
@@ -5285,12 +5297,13 @@ Props:
5285
5297
 
5286
5298
  Payloads:
5287
5299
 
5288
- - `TerminalListChangeEventPayload<T>` - `{ type: "change", id, index, value }`
5289
- - `TerminalListPressEventPayload<T>` - `{ type: "press" | "doublepress" | "contextpress", id, index, value }`
5290
- - `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 }`
5291
5304
  - `TerminalCaptureEventPayload` - `{ type: "capturestart" | "captureend", id, source, row, x, y }`
5292
5305
 
5293
- Supports selection, activation, double press on the same row, context press for the target row, 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.
5294
5307
 
5295
5308
  ### `ScrollView`
5296
5309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valyrianjs/terminal",
3
- "version": "0.2.0",
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/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
@@ -49,7 +49,14 @@ export const Editor = primitive<TerminalEditorProps>("terminal-editor");
49
49
  export const Button = primitive<TerminalButtonProps>("terminal-button");
50
50
  export const ScrollView = primitive<TerminalScrollViewProps>("terminal-scroll");
51
51
  export const LogView = primitive<TerminalLogViewProps>("terminal-log-view");
52
- export const List = primitive("terminal-list") as TerminalListComponent;
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;
53
60
  export const Table = primitive<TerminalTableProps>("terminal-table");
54
61
  export const Row = primitive<TerminalRowProps>("terminal-row");
55
62
  export const Td = primitive<TerminalTdProps>("terminal-td");