@valyrianjs/terminal 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +23 -13
- package/dist/ansi.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +10 -2
- package/dist/events.js.map +1 -1
- package/dist/frame-style.d.ts +7 -0
- package/dist/frame-style.d.ts.map +1 -0
- package/dist/frame-style.js +27 -0
- package/dist/frame-style.js.map +1 -0
- 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 +5 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +55 -24
- 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 +38 -17
- package/dist/mouse.js.map +1 -1
- 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.map +1 -1
- package/dist/render.js +266 -70
- package/dist/render.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -5
- package/dist/runtime.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +325 -83
- package/dist/session.js.map +1 -1
- package/dist/text.d.ts +7 -0
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +114 -0
- package/dist/text.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 +41 -4
- package/dist/types.d.ts.map +1 -1
- package/docs/api-reference.md +18 -8
- package/docs/cookbook.md +1 -1
- package/docs/interaction-model.md +10 -8
- package/docs/primitive-gallery.md +9 -5
- package/examples/basic.tsx +22 -0
- package/examples/cli.tsx +55 -0
- package/examples/demo.tsx +98 -0
- package/examples/docs/background-fill.tsx +107 -0
- package/examples/docs/component-composition.tsx +140 -0
- package/examples/docs/cursor.tsx +121 -0
- package/examples/docs/employees-list.tsx +138 -0
- package/examples/docs/hello.tsx +98 -0
- package/examples/docs/interactive-note.tsx +111 -0
- package/examples/docs/module-api-dashboard.tsx +307 -0
- package/examples/docs/module-flux-store.tsx +181 -0
- package/examples/docs/module-form-workflow.tsx +339 -0
- package/examples/docs/module-forms.tsx +218 -0
- package/examples/docs/module-money.tsx +175 -0
- package/examples/docs/module-native-store.tsx +188 -0
- package/examples/docs/module-pulses.tsx +142 -0
- package/examples/docs/module-query.tsx +209 -0
- package/examples/docs/module-request.tsx +194 -0
- package/examples/docs/module-state-workbench.tsx +283 -0
- package/examples/docs/module-tasks.tsx +223 -0
- package/examples/docs/module-translate.tsx +194 -0
- package/examples/docs/module-utils.tsx +168 -0
- package/examples/docs/module-valyrian-core.tsx +159 -0
- package/examples/docs/pizza-builder.tsx +463 -0
- package/examples/docs/primitive-activity-console.tsx +113 -0
- package/examples/docs/primitive-command-panel.tsx +186 -0
- package/examples/docs/primitive-data-explorer.tsx +155 -0
- package/examples/docs/primitive-input-workbench.tsx +128 -0
- package/examples/docs/primitive-layout-shell.tsx +115 -0
- package/examples/docs/responsive-split.tsx +186 -0
- package/examples/docs/style-system.tsx +209 -0
- package/examples/docs/theme-colors.tsx +225 -0
- package/examples/docs/virtualized-list-workbench.tsx +232 -0
- package/examples/opencode-dogfood-app.tsx +215 -0
- package/examples/opencode-dogfood-lifecycle.tsx +194 -0
- package/examples/opencode-dogfood.tsx +11 -0
- package/llms-full.txt +38 -22
- package/package.json +3 -2
- package/src/ansi.ts +23 -13
- package/src/events.ts +6 -2
- package/src/frame-style.ts +36 -0
- package/src/keymap.ts +4 -2
- package/src/layout.ts +59 -25
- package/src/mouse.ts +41 -16
- package/src/primitives.ts +8 -1
- package/src/render.ts +286 -71
- package/src/runtime.ts +13 -5
- package/src/session.ts +343 -79
- package/src/text.ts +148 -0
- package/src/theme.ts +3 -0
- package/src/tree.ts +19 -4
- package/src/types.ts +48 -3
package/src/session.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ANSI_ENTER_ALTERNATE_SCREEN, ANSI_EXIT_ALTERNATE_SCREEN, ANSI_HIDE_CURSOR, ANSI_SHOW_CURSOR, createAnsiDiffWriter, formatPlainFrame, toAnsiFrame } from "./ansi.js";
|
|
1
|
+
import { ANSI_DISABLE_MOUSE_REPORTING, ANSI_ENABLE_MOUSE_REPORTING, ANSI_ENTER_ALTERNATE_SCREEN, ANSI_EXIT_ALTERNATE_SCREEN, ANSI_HIDE_CURSOR, ANSI_SHOW_CURSOR, createAnsiDiffWriter, formatPlainFrame, toAnsiFrame } from "./ansi.js";
|
|
2
2
|
import { createSystemClipboardAdapter } from "./clipboard.js";
|
|
3
3
|
import { createEditorState, insertEditorText, moveEditorCursor, removeEditorBackward, removeEditorForward } from "./editor-state.js";
|
|
4
4
|
import { copySelection, hasSelection, insertText, moveCursorEnd, moveCursorHome, moveCursorLeft, moveCursorRight, moveCursorWordLeft, moveCursorWordRight, normalizeInputState, parseTerminalKey, removeBackward, removeForward, selectAll } from "./events.js";
|
|
5
5
|
import { createResolvedTerminalKeymap, resolveTerminalKeyBinding } from "./keymap.js";
|
|
6
6
|
import { mergeVertical } from "./layout.js";
|
|
7
|
-
import { cursorFromHitbox, parseTerminalInput, resolvePointerTarget } from "./mouse.js";
|
|
7
|
+
import { cursorFromHitbox, parseTerminalInput, parseTerminalMousePrefix, resolvePointerTarget } from "./mouse.js";
|
|
8
8
|
import { createOutputWriter } from "./output-writer.js";
|
|
9
9
|
import { parseBracketedPaste } from "./paste.js";
|
|
10
10
|
import { renderTerminalFrame } from "./render.js";
|
|
@@ -27,9 +27,11 @@ import type {
|
|
|
27
27
|
TerminalHitbox,
|
|
28
28
|
TerminalListChangeEventPayload,
|
|
29
29
|
TerminalListPressEventPayload,
|
|
30
|
+
TerminalListViewportChangeEventPayload,
|
|
30
31
|
TerminalMountOptions,
|
|
31
32
|
TerminalOutputStream,
|
|
32
33
|
TerminalNode,
|
|
34
|
+
ParsedTerminalInput,
|
|
33
35
|
TerminalPointerSource,
|
|
34
36
|
TerminalRowPointerEventPayload,
|
|
35
37
|
TerminalSession,
|
|
@@ -44,6 +46,7 @@ interface ResolvedRuntimeOptions {
|
|
|
44
46
|
stdout?: TerminalOutputStream;
|
|
45
47
|
alternateScreen: boolean;
|
|
46
48
|
hideCursor: boolean;
|
|
49
|
+
mouseReporting: boolean;
|
|
47
50
|
writesAnsi: boolean;
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -55,11 +58,17 @@ const KNOWN_TERMINAL_KEY_SEQUENCES = [
|
|
|
55
58
|
"\u001b[13;129u",
|
|
56
59
|
"\u001b[27;2;13~",
|
|
57
60
|
"\u001b[13;2~",
|
|
61
|
+
"\u001b[1;2A",
|
|
62
|
+
"\u001b[1;2B",
|
|
58
63
|
"\u001b[1;2C",
|
|
59
64
|
"\u001b[1;2D",
|
|
60
65
|
"\u001b[1;3C",
|
|
61
66
|
"\u001b[1;3D",
|
|
62
67
|
"\u001b[3~",
|
|
68
|
+
"\u001b[5~",
|
|
69
|
+
"\u001b[6~",
|
|
70
|
+
"\u001b[1~",
|
|
71
|
+
"\u001b[4~",
|
|
63
72
|
"\u001b[Z",
|
|
64
73
|
"\u001b[A",
|
|
65
74
|
"\u001b[B",
|
|
@@ -131,6 +140,7 @@ function resolveRuntimeOptions(options: TerminalMountOptions): ResolvedRuntimeOp
|
|
|
131
140
|
stdout,
|
|
132
141
|
alternateScreen: options.alternateScreen ?? ownsInteractiveTTY,
|
|
133
142
|
hideCursor: options.hideCursor ?? ownsInteractiveTTY,
|
|
143
|
+
mouseReporting: ownsInteractiveTTY,
|
|
134
144
|
writesAnsi: runtime === "app" && Boolean(stdout)
|
|
135
145
|
};
|
|
136
146
|
}
|
|
@@ -150,6 +160,8 @@ function applyInteractiveState(
|
|
|
150
160
|
inputStateById: Map<string, InputInteractionState>,
|
|
151
161
|
editorStateById: Map<string, EditorState>,
|
|
152
162
|
listIndexById: Map<string, number>,
|
|
163
|
+
listSelectedIndexById: Map<string, number | null>,
|
|
164
|
+
listViewportOffsetById: Map<string, number>,
|
|
153
165
|
scrollOffsetById: Map<string, number>,
|
|
154
166
|
listHoverById: Map<string, number>,
|
|
155
167
|
scrollHoverRowById: Map<string, number>
|
|
@@ -176,7 +188,23 @@ function applyInteractiveState(
|
|
|
176
188
|
node.props.__editorState = current;
|
|
177
189
|
}
|
|
178
190
|
if (node.tag === "terminal-list" && id) {
|
|
179
|
-
node.props.
|
|
191
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
192
|
+
const activeIndex = listIndexById.get(id) || 0;
|
|
193
|
+
const clampedActiveIndex = Math.max(0, Math.min(Math.max(0, items.length - 1), activeIndex));
|
|
194
|
+
if (!listIndexById.has(id)) {
|
|
195
|
+
listIndexById.set(id, clampedActiveIndex);
|
|
196
|
+
}
|
|
197
|
+
const selectedIndex = node.props.showActive === false
|
|
198
|
+
? null
|
|
199
|
+
: listSelectedIndexById.has(id)
|
|
200
|
+
? listSelectedIndexById.get(id)!
|
|
201
|
+
: clampedActiveIndex;
|
|
202
|
+
if (!listSelectedIndexById.has(id)) {
|
|
203
|
+
listSelectedIndexById.set(id, selectedIndex === null ? null : Math.max(0, Math.min(Math.max(0, items.length - 1), selectedIndex)));
|
|
204
|
+
}
|
|
205
|
+
node.props.__activeIndex = clampedActiveIndex;
|
|
206
|
+
node.props.__selectedIndex = selectedIndex === null ? null : Math.max(0, Math.min(Math.max(0, items.length - 1), selectedIndex));
|
|
207
|
+
node.props.__scrollOffset = listViewportOffsetById.get(id) || 0;
|
|
180
208
|
if (listHoverById.has(id)) {
|
|
181
209
|
node.props.__hoveredIndex = listHoverById.get(id);
|
|
182
210
|
}
|
|
@@ -187,7 +215,7 @@ function applyInteractiveState(
|
|
|
187
215
|
node.props.__hoveredRow = scrollHoverRowById.get(id);
|
|
188
216
|
}
|
|
189
217
|
}
|
|
190
|
-
applyInteractiveState(node.children, focusedId, inputStateById, editorStateById, listIndexById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
218
|
+
applyInteractiveState(node.children, focusedId, inputStateById, editorStateById, listIndexById, listSelectedIndexById, listViewportOffsetById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
191
219
|
}
|
|
192
220
|
}
|
|
193
221
|
|
|
@@ -208,6 +236,8 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
208
236
|
const inputStateById = new Map<string, InputInteractionState>();
|
|
209
237
|
const editorStateById = new Map<string, EditorState>();
|
|
210
238
|
const listIndexById = new Map<string, number>();
|
|
239
|
+
const listSelectedIndexById = new Map<string, number | null>();
|
|
240
|
+
const listViewportOffsetById = new Map<string, number>();
|
|
211
241
|
const scrollOffsetById = new Map<string, number>();
|
|
212
242
|
const listHoverById = new Map<string, number>();
|
|
213
243
|
const scrollHoverRowById = new Map<string, number>();
|
|
@@ -221,7 +251,7 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
221
251
|
renderNow();
|
|
222
252
|
});
|
|
223
253
|
let currentTree = terminalRuntime.project();
|
|
224
|
-
applyInteractiveState(currentTree, focusedId, inputStateById, editorStateById, listIndexById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
254
|
+
applyInteractiveState(currentTree, focusedId, inputStateById, editorStateById, listIndexById, listSelectedIndexById, listViewportOffsetById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
225
255
|
let currentFrame = renderTreeFrame(currentTree);
|
|
226
256
|
let currentOutput = formatPlainFrame(currentFrame, { theme: options.theme }).trimEnd();
|
|
227
257
|
let currentHitboxes = currentFrame.hitboxes;
|
|
@@ -250,6 +280,9 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
250
280
|
if (runtimeOptions.hideCursor) {
|
|
251
281
|
writes.push(ANSI_HIDE_CURSOR);
|
|
252
282
|
}
|
|
283
|
+
if (runtimeOptions.mouseReporting) {
|
|
284
|
+
writes.push(ANSI_ENABLE_MOUSE_REPORTING);
|
|
285
|
+
}
|
|
253
286
|
if (writes.length > 0) {
|
|
254
287
|
outputWriter.write(writes.join(""), { force: true });
|
|
255
288
|
}
|
|
@@ -260,6 +293,9 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
260
293
|
return;
|
|
261
294
|
}
|
|
262
295
|
const writes: string[] = [];
|
|
296
|
+
if (runtimeOptions.mouseReporting) {
|
|
297
|
+
writes.push(ANSI_DISABLE_MOUSE_REPORTING);
|
|
298
|
+
}
|
|
263
299
|
if (runtimeOptions.hideCursor) {
|
|
264
300
|
writes.push(ANSI_SHOW_CURSOR);
|
|
265
301
|
}
|
|
@@ -290,7 +326,7 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
290
326
|
focusedId = activeFocusables[0]?.props.id || null;
|
|
291
327
|
}
|
|
292
328
|
skipFocusContainmentOnce = false;
|
|
293
|
-
applyInteractiveState(currentTree, focusedId, inputStateById, editorStateById, listIndexById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
329
|
+
applyInteractiveState(currentTree, focusedId, inputStateById, editorStateById, listIndexById, listSelectedIndexById, listViewportOffsetById, scrollOffsetById, listHoverById, scrollHoverRowById);
|
|
294
330
|
currentFrame = renderTreeFrame(currentTree);
|
|
295
331
|
currentOutput = formatPlainFrame(currentFrame, { theme: options.theme }).trimEnd();
|
|
296
332
|
currentHitboxes = currentFrame.hitboxes;
|
|
@@ -444,12 +480,22 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
444
480
|
return 1;
|
|
445
481
|
}
|
|
446
482
|
|
|
447
|
-
function sourceRowFromHitbox(node: TerminalFocusNode, hitbox: { itemOffset?: number; y1: number }, y: number) {
|
|
448
|
-
|
|
483
|
+
function sourceRowFromHitbox(node: TerminalFocusNode, hitbox: { itemOffset?: number; itemIndexes?: number[]; y1: number; y2: number; contentY?: number; __listItemIndex?: number }, y: number) {
|
|
484
|
+
if (node.tag === "terminal-list" && typeof hitbox.__listItemIndex === "number" && y >= hitbox.y1 && y <= hitbox.y2) {
|
|
485
|
+
return Math.max(1, Math.min(rowCountForNode(node), hitbox.__listItemIndex + 1));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const sourceY = hitbox.contentY ?? hitbox.y1;
|
|
489
|
+
const visibleRow = Math.max(1, y - sourceY + 1);
|
|
449
490
|
if (node.tag !== "terminal-list") {
|
|
450
491
|
return Math.max(1, Math.min(rowCountForNode(node), visibleRow));
|
|
451
492
|
}
|
|
452
493
|
|
|
494
|
+
const mappedIndex = hitbox.itemIndexes?.[visibleRow - 1];
|
|
495
|
+
if (typeof mappedIndex === "number") {
|
|
496
|
+
return Math.max(1, Math.min(rowCountForNode(node), mappedIndex + 1));
|
|
497
|
+
}
|
|
498
|
+
|
|
453
499
|
return Math.max(1, Math.min(rowCountForNode(node), visibleRow + (hitbox.itemOffset || 0)));
|
|
454
500
|
}
|
|
455
501
|
|
|
@@ -496,6 +542,110 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
496
542
|
}
|
|
497
543
|
|
|
498
544
|
|
|
545
|
+
function listItemKey(node: TerminalFocusNode, index: number) {
|
|
546
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
547
|
+
const item = items[index];
|
|
548
|
+
if (typeof node.props.itemKey === "function" && typeof item !== "undefined") {
|
|
549
|
+
const key = node.props.itemKey(item, index);
|
|
550
|
+
if (typeof key === "string" || typeof key === "number") {
|
|
551
|
+
return String(key);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function listViewportRows(node: TerminalFocusNode) {
|
|
558
|
+
const context = renderContext();
|
|
559
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
560
|
+
const hitbox = node.props.id ? currentHitboxes.find((box) => box.id === node.props.id) : null;
|
|
561
|
+
const overscan = typeof node.props.overscan === "number" ? Math.max(0, Math.floor(node.props.overscan)) : 0;
|
|
562
|
+
const renderedRows = hitbox ? Math.max(1, hitbox.y2 - hitbox.y1 + 1 - overscan * 2) : null;
|
|
563
|
+
const sourceRows = Number(node.props.height || renderedRows || context.rows || items.length || 1);
|
|
564
|
+
if (!Number.isFinite(sourceRows) || !Number.isInteger(sourceRows) || sourceRows <= 0) {
|
|
565
|
+
return Math.max(1, items.length || 1);
|
|
566
|
+
}
|
|
567
|
+
return Math.max(1, Math.min(items.length || 1, sourceRows));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function clampListIndex(node: TerminalFocusNode, index: number) {
|
|
571
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
572
|
+
if (items.length === 0) {
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
575
|
+
return Math.max(0, Math.min(items.length - 1, index));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function currentListActiveIndex(node: TerminalFocusNode) {
|
|
579
|
+
return clampListIndex(node, listIndexById.get(node.props.id || "") || 0);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function currentListSelectedIndex(node: TerminalFocusNode) {
|
|
583
|
+
const id = node.props.id || "";
|
|
584
|
+
if (node.props.showActive === false) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
if (listSelectedIndexById.has(id)) {
|
|
588
|
+
const selectedIndex = listSelectedIndexById.get(id);
|
|
589
|
+
return typeof selectedIndex === "number" ? clampListIndex(node, selectedIndex) : null;
|
|
590
|
+
}
|
|
591
|
+
return currentListActiveIndex(node);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function currentListViewportOffset(node: TerminalFocusNode) {
|
|
595
|
+
const id = node.props.id || "";
|
|
596
|
+
return Math.max(0, Math.min(listMaxViewportOffset(node), listViewportOffsetById.get(id) || 0));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function listStatePayload(node: TerminalFocusNode) {
|
|
600
|
+
return {
|
|
601
|
+
activeIndex: currentListActiveIndex(node),
|
|
602
|
+
selectedIndex: currentListSelectedIndex(node),
|
|
603
|
+
viewportOffset: currentListViewportOffset(node),
|
|
604
|
+
viewportRows: listViewportRows(node)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function listMaxViewportOffset(node: TerminalFocusNode) {
|
|
609
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
610
|
+
return Math.max(0, items.length - listViewportRows(node));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function setListViewportOffset(node: TerminalFocusNode, offset: number, emit = true) {
|
|
614
|
+
const id = node.props.id;
|
|
615
|
+
if (!id) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const nextOffset = Math.max(0, Math.min(listMaxViewportOffset(node), offset));
|
|
619
|
+
const previous = listViewportOffsetById.get(id) || 0;
|
|
620
|
+
listViewportOffsetById.set(id, nextOffset);
|
|
621
|
+
if (emit && nextOffset !== previous) {
|
|
622
|
+
const payload: TerminalListViewportChangeEventPayload = {
|
|
623
|
+
type: "viewportchange",
|
|
624
|
+
id,
|
|
625
|
+
offset: nextOffset,
|
|
626
|
+
rows: listViewportRows(node),
|
|
627
|
+
...listStatePayload(node)
|
|
628
|
+
};
|
|
629
|
+
dispatchNodeEvent(node, "viewportchange", payload);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function ensureListActiveVisible(node: TerminalFocusNode, activeIndex: number) {
|
|
634
|
+
const id = node.props.id;
|
|
635
|
+
if (!id || !node.props.virtualized) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const currentOffset = listViewportOffsetById.get(id) || 0;
|
|
639
|
+
const rows = listViewportRows(node);
|
|
640
|
+
if (activeIndex < currentOffset) {
|
|
641
|
+
setListViewportOffset(node, activeIndex);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (activeIndex >= currentOffset + rows) {
|
|
645
|
+
setListViewportOffset(node, activeIndex - rows + 1);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
499
649
|
function dispatchListPressEvent(node: TerminalFocusNode, type: TerminalListPressEventPayload["type"], index: number) {
|
|
500
650
|
const id = node.props.id;
|
|
501
651
|
if (!id) {
|
|
@@ -505,7 +655,10 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
505
655
|
if (typeof items[index] === "undefined") {
|
|
506
656
|
return false;
|
|
507
657
|
}
|
|
508
|
-
const
|
|
658
|
+
const key = listItemKey(node, index);
|
|
659
|
+
const payload: TerminalListPressEventPayload = typeof key === "undefined"
|
|
660
|
+
? { type, id, index, value: items[index], ...listStatePayload(node) }
|
|
661
|
+
: { type, id, index, key, value: items[index], ...listStatePayload(node) };
|
|
509
662
|
return dispatchNodeEvent(node, type, payload);
|
|
510
663
|
}
|
|
511
664
|
|
|
@@ -517,6 +670,14 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
517
670
|
return dispatchNodeEvent(node, type, { type, id });
|
|
518
671
|
}
|
|
519
672
|
|
|
673
|
+
function dispatchHitboxButtonPressEvent(hitbox: TerminalHitbox, type: "press" | "doublepress" | "contextpress") {
|
|
674
|
+
if (type !== "press" || typeof hitbox.__pressHandler !== "function") {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
hitbox.__pressHandler({ type, id: hitbox.id });
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
|
|
520
681
|
function dispatchListPointerPressEvent(node: TerminalFocusNode, type: "doublepress" | "contextpress", row: number) {
|
|
521
682
|
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
522
683
|
const index = Math.max(0, Math.min(items.length - 1, row - 1));
|
|
@@ -557,7 +718,10 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
557
718
|
if (typeof items[index] === "undefined") {
|
|
558
719
|
return;
|
|
559
720
|
}
|
|
560
|
-
const
|
|
721
|
+
const key = listItemKey(node, index);
|
|
722
|
+
const payload: TerminalRowPointerEventPayload = typeof key === "undefined"
|
|
723
|
+
? { type, id: node.props.id, row: index + 1, index, value: items[index], x, y }
|
|
724
|
+
: { type, id: node.props.id, row: index + 1, index, key, value: items[index], x, y };
|
|
561
725
|
dispatchNodeEvent(node, type, payload);
|
|
562
726
|
return;
|
|
563
727
|
}
|
|
@@ -656,30 +820,80 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
656
820
|
return rerender();
|
|
657
821
|
}
|
|
658
822
|
|
|
659
|
-
function
|
|
823
|
+
function moveListActiveTo(node: TerminalFocusNode, nextIndex: number) {
|
|
660
824
|
const id = node.props.id;
|
|
661
825
|
if (!id) {
|
|
662
826
|
return currentOutput;
|
|
663
827
|
}
|
|
664
828
|
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
665
|
-
const
|
|
666
|
-
|
|
829
|
+
const clampedIndex = clampListIndex(node, nextIndex);
|
|
830
|
+
listIndexById.set(id, clampedIndex);
|
|
831
|
+
ensureListActiveVisible(node, clampedIndex);
|
|
832
|
+
const key = listItemKey(node, clampedIndex);
|
|
833
|
+
const payload: TerminalListChangeEventPayload = typeof key === "undefined"
|
|
834
|
+
? { type: "change", id, index: clampedIndex, value: items[clampedIndex], ...listStatePayload(node) }
|
|
835
|
+
: { type: "change", id, index: clampedIndex, key, value: items[clampedIndex], ...listStatePayload(node) };
|
|
836
|
+
dispatchNodeEvent(node, "change", payload);
|
|
837
|
+
return rerender();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function changeListSelection(node: TerminalFocusNode, direction: -1 | 1) {
|
|
841
|
+
return moveListActiveTo(node, currentListActiveIndex(node) + direction);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function pageListSelection(node: TerminalFocusNode, direction: -1 | 1) {
|
|
845
|
+
const id = node.props.id;
|
|
846
|
+
if (!id) {
|
|
847
|
+
return currentOutput;
|
|
848
|
+
}
|
|
849
|
+
const nextIndex = clampListIndex(node, currentListActiveIndex(node) + direction * listViewportRows(node));
|
|
667
850
|
listIndexById.set(id, nextIndex);
|
|
668
|
-
|
|
851
|
+
setListViewportOffset(node, nextIndex);
|
|
852
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
853
|
+
const key = listItemKey(node, nextIndex);
|
|
854
|
+
const payload: TerminalListChangeEventPayload = typeof key === "undefined"
|
|
855
|
+
? { type: "change", id, index: nextIndex, value: items[nextIndex], ...listStatePayload(node) }
|
|
856
|
+
: { type: "change", id, index: nextIndex, key, value: items[nextIndex], ...listStatePayload(node) };
|
|
669
857
|
dispatchNodeEvent(node, "change", payload);
|
|
670
858
|
return rerender();
|
|
671
859
|
}
|
|
672
860
|
|
|
861
|
+
function moveListSelectionToBoundary(node: TerminalFocusNode, boundary: "start" | "end") {
|
|
862
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
863
|
+
return moveListActiveTo(node, boundary === "start" ? 0 : Math.max(0, items.length - 1));
|
|
864
|
+
}
|
|
865
|
+
|
|
673
866
|
function pressListSelection(node: TerminalFocusNode) {
|
|
674
867
|
const id = node.props.id;
|
|
675
868
|
if (!id) {
|
|
676
869
|
return currentOutput;
|
|
677
870
|
}
|
|
678
|
-
const currentIndex =
|
|
871
|
+
const currentIndex = currentListActiveIndex(node);
|
|
872
|
+
if (node.props.showActive !== false) {
|
|
873
|
+
listSelectedIndexById.set(id, currentIndex);
|
|
874
|
+
}
|
|
679
875
|
dispatchListPressEvent(node, "press", currentIndex);
|
|
680
876
|
return rerender();
|
|
681
877
|
}
|
|
682
878
|
|
|
879
|
+
function pressListPointerSelection(node: TerminalFocusNode, row: number) {
|
|
880
|
+
const id = node.props.id;
|
|
881
|
+
if (!id) {
|
|
882
|
+
return currentOutput;
|
|
883
|
+
}
|
|
884
|
+
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
885
|
+
const index = Math.max(0, Math.min(items.length - 1, row - 1));
|
|
886
|
+
if (typeof items[index] === "undefined") {
|
|
887
|
+
return currentOutput;
|
|
888
|
+
}
|
|
889
|
+
listIndexById.set(id, index);
|
|
890
|
+
if (node.props.showActive !== false) {
|
|
891
|
+
listSelectedIndexById.set(id, index);
|
|
892
|
+
}
|
|
893
|
+
dispatchListPressEvent(node, "press", index);
|
|
894
|
+
return rerender();
|
|
895
|
+
}
|
|
896
|
+
|
|
683
897
|
function scrollFocusedNode(node: TerminalFocusNode, direction: -1 | 1) {
|
|
684
898
|
const id = node.props.id;
|
|
685
899
|
if (!id) {
|
|
@@ -706,7 +920,9 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
706
920
|
return scrollFocusedNode(node, direction);
|
|
707
921
|
}
|
|
708
922
|
if (node?.tag === "terminal-list" && node.props.virtualized) {
|
|
709
|
-
|
|
923
|
+
const currentOffset = listViewportOffsetById.get(node.props.id || "") || 0;
|
|
924
|
+
setListViewportOffset(node, currentOffset + direction);
|
|
925
|
+
return rerender();
|
|
710
926
|
}
|
|
711
927
|
return rerender();
|
|
712
928
|
}
|
|
@@ -881,6 +1097,14 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
881
1097
|
return node?.tag === "terminal-list" ? changeListSelection(node, -1) : currentOutput;
|
|
882
1098
|
case "list.next":
|
|
883
1099
|
return node?.tag === "terminal-list" ? changeListSelection(node, 1) : currentOutput;
|
|
1100
|
+
case "list.pageUp":
|
|
1101
|
+
return node?.tag === "terminal-list" ? pageListSelection(node, -1) : currentOutput;
|
|
1102
|
+
case "list.pageDown":
|
|
1103
|
+
return node?.tag === "terminal-list" ? pageListSelection(node, 1) : currentOutput;
|
|
1104
|
+
case "list.home":
|
|
1105
|
+
return node?.tag === "terminal-list" ? moveListSelectionToBoundary(node, "start") : currentOutput;
|
|
1106
|
+
case "list.end":
|
|
1107
|
+
return node?.tag === "terminal-list" ? moveListSelectionToBoundary(node, "end") : currentOutput;
|
|
884
1108
|
case "list.press":
|
|
885
1109
|
return node?.tag === "terminal-list" ? pressListSelection(node) : currentOutput;
|
|
886
1110
|
case "scroll.up":
|
|
@@ -917,6 +1141,18 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
917
1141
|
return;
|
|
918
1142
|
}
|
|
919
1143
|
terminalSize = nextSize;
|
|
1144
|
+
const focusedNode = findFocused(currentTree, focusedId);
|
|
1145
|
+
if (focusedNode?.tag === "terminal-list" && focusedNode.props.id && focusedNode.props.virtualized) {
|
|
1146
|
+
const items = Array.isArray(focusedNode.props.items) ? focusedNode.props.items : [];
|
|
1147
|
+
const rows = Math.max(1, Math.min(items.length || 1, Number(focusedNode.props.height || terminalSize.rows || items.length || 1)));
|
|
1148
|
+
const activeIndex = currentListActiveIndex(focusedNode);
|
|
1149
|
+
const currentOffset = listViewportOffsetById.get(focusedNode.props.id) || 0;
|
|
1150
|
+
if (activeIndex < currentOffset) {
|
|
1151
|
+
listViewportOffsetById.set(focusedNode.props.id, activeIndex);
|
|
1152
|
+
} else if (activeIndex >= currentOffset + rows) {
|
|
1153
|
+
listViewportOffsetById.set(focusedNode.props.id, Math.max(0, activeIndex - rows + 1));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
920
1156
|
rerender();
|
|
921
1157
|
},
|
|
922
1158
|
update() {
|
|
@@ -1007,7 +1243,14 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1007
1243
|
return currentOutput;
|
|
1008
1244
|
}
|
|
1009
1245
|
if (hitbox.tag === "terminal-button") {
|
|
1010
|
-
|
|
1246
|
+
const node = findFocusableById(currentTree, hitbox.id);
|
|
1247
|
+
if (node?.tag === "terminal-button") {
|
|
1248
|
+
return this.click(hitbox.id);
|
|
1249
|
+
}
|
|
1250
|
+
if (dispatchHitboxButtonPressEvent(hitbox, "press")) {
|
|
1251
|
+
return rerender();
|
|
1252
|
+
}
|
|
1253
|
+
return currentOutput;
|
|
1011
1254
|
}
|
|
1012
1255
|
setSemanticHoverFromHitbox(hitbox.id, x, y);
|
|
1013
1256
|
focusedId = hitbox.id;
|
|
@@ -1015,6 +1258,12 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1015
1258
|
mouseSelectionId = hitbox.id;
|
|
1016
1259
|
return setCursorFromHitbox(hitbox.id, x, false);
|
|
1017
1260
|
}
|
|
1261
|
+
if (hitbox.tag === "terminal-list") {
|
|
1262
|
+
const node = findFocusableById(currentTree, hitbox.id);
|
|
1263
|
+
if (node?.tag === "terminal-list") {
|
|
1264
|
+
return pressListPointerSelection(node, sourceRowFromHitbox(node, hitbox, y));
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1018
1267
|
rerender();
|
|
1019
1268
|
return currentOutput;
|
|
1020
1269
|
},
|
|
@@ -1173,7 +1422,7 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1173
1422
|
return isDouble;
|
|
1174
1423
|
}
|
|
1175
1424
|
|
|
1176
|
-
function doublePressAt(hitbox:
|
|
1425
|
+
function doublePressAt(hitbox: TerminalHitbox, x: number, y: number) {
|
|
1177
1426
|
const node = findFocusableById(currentTree, hitbox.id);
|
|
1178
1427
|
if (!node) {
|
|
1179
1428
|
return currentOutput;
|
|
@@ -1222,65 +1471,7 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1222
1471
|
return currentOutput;
|
|
1223
1472
|
}
|
|
1224
1473
|
|
|
1225
|
-
function
|
|
1226
|
-
if (!value) {
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
if (pendingPasteChunk) {
|
|
1231
|
-
pendingPasteChunk += value;
|
|
1232
|
-
const paste = parseBracketedPaste(pendingPasteChunk);
|
|
1233
|
-
if (!paste && isBracketedPasteStartPrefix(pendingPasteChunk)) {
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (!paste) {
|
|
1237
|
-
const buffered = pendingPasteChunk;
|
|
1238
|
-
pendingPasteChunk = "";
|
|
1239
|
-
processKeyStream(buffered);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
pendingPasteChunk = "";
|
|
1243
|
-
dispatchPasteText(paste.text);
|
|
1244
|
-
processInputStream(paste.rest);
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (pendingKeyChunk) {
|
|
1249
|
-
if (pendingKeyChunk === ESCAPE && !canContinueEscapeSequence(value)) {
|
|
1250
|
-
flushPendingEscape();
|
|
1251
|
-
processInputStream(value);
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
const buffered = pendingKeyChunk + value;
|
|
1255
|
-
cancelPendingEscapeFlush();
|
|
1256
|
-
pendingKeyChunk = "";
|
|
1257
|
-
processKeyStream(buffered);
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
if (value === ESCAPE) {
|
|
1262
|
-
processKeyStream(value);
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
if (isBracketedPasteStartPrefix(value)) {
|
|
1267
|
-
pendingPasteChunk = value;
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
if (value.startsWith(BRACKETED_PASTE_START)) {
|
|
1272
|
-
const paste = parseBracketedPaste(value);
|
|
1273
|
-
if (!paste) {
|
|
1274
|
-
pendingPasteChunk = value;
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
dispatchPasteText(paste.text);
|
|
1278
|
-
processInputStream(paste.rest);
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
const parsed = parseTerminalInput(value);
|
|
1283
|
-
if (parsed.type === "mouse") {
|
|
1474
|
+
function processParsedMouseInput(parsed: Extract<ParsedTerminalInput, { type: "mouse" }>) {
|
|
1284
1475
|
if (parsed.action === "press") {
|
|
1285
1476
|
const hitbox = resolvePointerTarget(currentHitboxes, parsed.x, parsed.y);
|
|
1286
1477
|
if (isContextMouseButton(parsed.button)) {
|
|
@@ -1313,9 +1504,13 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1313
1504
|
if (isPrimaryPress && !isDoublePressEligible) {
|
|
1314
1505
|
lastPrimaryPress = null;
|
|
1315
1506
|
}
|
|
1316
|
-
|
|
1317
|
-
if (hitbox && shouldDispatchDoublePress) {
|
|
1507
|
+
if (hitbox && shouldDispatchDoublePress && hitbox.tag === "terminal-list") {
|
|
1318
1508
|
doublePressAt(hitbox, parsed.x, parsed.y);
|
|
1509
|
+
} else {
|
|
1510
|
+
session.clickAt(parsed.x, parsed.y);
|
|
1511
|
+
if (hitbox && shouldDispatchDoublePress) {
|
|
1512
|
+
doublePressAt(hitbox, parsed.x, parsed.y);
|
|
1513
|
+
}
|
|
1319
1514
|
}
|
|
1320
1515
|
} else if (parsed.action === "drag") {
|
|
1321
1516
|
if (mouseSelectionId) {
|
|
@@ -1345,6 +1540,75 @@ export function mountTerminal(input: any, options: TerminalMountOptions = {}): T
|
|
|
1345
1540
|
} else if (parsed.action === "wheel-down") {
|
|
1346
1541
|
wheelAt(parsed.x, parsed.y, 1);
|
|
1347
1542
|
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function processInputStream(value: string) {
|
|
1546
|
+
if (!value) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
if (pendingPasteChunk) {
|
|
1551
|
+
pendingPasteChunk += value;
|
|
1552
|
+
const paste = parseBracketedPaste(pendingPasteChunk);
|
|
1553
|
+
if (!paste && isBracketedPasteStartPrefix(pendingPasteChunk)) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
if (!paste) {
|
|
1557
|
+
const buffered = pendingPasteChunk;
|
|
1558
|
+
pendingPasteChunk = "";
|
|
1559
|
+
processKeyStream(buffered);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
pendingPasteChunk = "";
|
|
1563
|
+
dispatchPasteText(paste.text);
|
|
1564
|
+
processInputStream(paste.rest);
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (pendingKeyChunk) {
|
|
1569
|
+
if (pendingKeyChunk === ESCAPE && !canContinueEscapeSequence(value)) {
|
|
1570
|
+
flushPendingEscape();
|
|
1571
|
+
processInputStream(value);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const buffered = pendingKeyChunk + value;
|
|
1575
|
+
cancelPendingEscapeFlush();
|
|
1576
|
+
pendingKeyChunk = "";
|
|
1577
|
+
processKeyStream(buffered);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
if (value === ESCAPE) {
|
|
1582
|
+
processKeyStream(value);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
if (isBracketedPasteStartPrefix(value)) {
|
|
1587
|
+
pendingPasteChunk = value;
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
if (value.startsWith(BRACKETED_PASTE_START)) {
|
|
1592
|
+
const paste = parseBracketedPaste(value);
|
|
1593
|
+
if (!paste) {
|
|
1594
|
+
pendingPasteChunk = value;
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
dispatchPasteText(paste.text);
|
|
1598
|
+
processInputStream(paste.rest);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const parsedMouse = parseTerminalMousePrefix(value);
|
|
1603
|
+
if (parsedMouse) {
|
|
1604
|
+
processParsedMouseInput(parsedMouse.input);
|
|
1605
|
+
processInputStream(parsedMouse.rest);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const parsed = parseTerminalInput(value);
|
|
1610
|
+
if (parsed.type === "mouse") {
|
|
1611
|
+
processParsedMouseInput(parsed);
|
|
1348
1612
|
return;
|
|
1349
1613
|
}
|
|
1350
1614
|
|