@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/text.ts
CHANGED
|
@@ -5,6 +5,20 @@ const C1_CSI_TERMINAL_CONTROL = /\u009b[0-?]*[ -/]*[@-~]/g;
|
|
|
5
5
|
const ESC_TERMINAL_CONTROL = /\u001b[ -/]*[0-~]/g;
|
|
6
6
|
const C1_TERMINAL_CONTROL = /[\u0080-\u009f]/g;
|
|
7
7
|
const C0_TERMINAL_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f]/g;
|
|
8
|
+
const COMBINING_MARK = /\p{Mark}/u;
|
|
9
|
+
const EMOJI_PRESENTATION = /\p{Extended_Pictographic}/u;
|
|
10
|
+
|
|
11
|
+
type GraphemeSegmenter = {
|
|
12
|
+
segment(value: string): Iterable<{ segment: string }>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const Segmenter = (Intl as unknown as {
|
|
16
|
+
Segmenter?: new (locale?: string, options?: { granularity: "grapheme" }) => GraphemeSegmenter;
|
|
17
|
+
}).Segmenter;
|
|
18
|
+
|
|
19
|
+
const GRAPHEME_SEGMENTER = typeof Segmenter === "function"
|
|
20
|
+
? new Segmenter(undefined, { granularity: "grapheme" })
|
|
21
|
+
: null;
|
|
8
22
|
|
|
9
23
|
export function stripTerminalControls(value: unknown) {
|
|
10
24
|
return String(value)
|
|
@@ -18,3 +32,137 @@ export function stripTerminalControls(value: unknown) {
|
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
export const plainText = stripTerminalControls;
|
|
35
|
+
|
|
36
|
+
export function terminalGraphemes(value: string): string[] {
|
|
37
|
+
if (GRAPHEME_SEGMENTER !== null) {
|
|
38
|
+
return Array.from(GRAPHEME_SEGMENTER.segment(value), (part) => part.segment);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return Array.from(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isWideCodePoint(codePoint: number) {
|
|
45
|
+
return (
|
|
46
|
+
codePoint >= 0x1100 && (
|
|
47
|
+
codePoint <= 0x115f
|
|
48
|
+
|| codePoint === 0x2329
|
|
49
|
+
|| codePoint === 0x232a
|
|
50
|
+
|| (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f)
|
|
51
|
+
|| (codePoint >= 0xac00 && codePoint <= 0xd7a3)
|
|
52
|
+
|| (codePoint >= 0xf900 && codePoint <= 0xfaff)
|
|
53
|
+
|| (codePoint >= 0xfe10 && codePoint <= 0xfe19)
|
|
54
|
+
|| (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
|
|
55
|
+
|| (codePoint >= 0xff00 && codePoint <= 0xff60)
|
|
56
|
+
|| (codePoint >= 0xffe0 && codePoint <= 0xffe6)
|
|
57
|
+
|| (codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isZeroWidthCodePoint(codePoint: number, char: string) {
|
|
63
|
+
return codePoint === 0x200d
|
|
64
|
+
|| (codePoint >= 0xfe00 && codePoint <= 0xfe0f)
|
|
65
|
+
|| (codePoint >= 0xe0100 && codePoint <= 0xe01ef)
|
|
66
|
+
|| COMBINING_MARK.test(char);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function graphemeCellWidth(grapheme: string) {
|
|
70
|
+
if (grapheme.length === 0) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (grapheme.includes("\u200d") || EMOJI_PRESENTATION.test(grapheme)) {
|
|
75
|
+
return 2;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let width = 0;
|
|
79
|
+
for (const char of Array.from(grapheme)) {
|
|
80
|
+
const codePoint = char.codePointAt(0);
|
|
81
|
+
if (typeof codePoint !== "number") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (isZeroWidthCodePoint(codePoint, char)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
width = Math.max(width, isWideCodePoint(codePoint) ? 2 : 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return width;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function terminalCellWidth(value: unknown) {
|
|
94
|
+
const text = stripTerminalControls(value);
|
|
95
|
+
return terminalGraphemes(text).reduce((width, grapheme) => width + graphemeCellWidth(grapheme), 0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function sliceTerminalCells(value: string, maxCells: number) {
|
|
99
|
+
const limit = Math.max(0, Math.trunc(Number(maxCells) || 0));
|
|
100
|
+
let width = 0;
|
|
101
|
+
let output = "";
|
|
102
|
+
|
|
103
|
+
for (const grapheme of terminalGraphemes(value)) {
|
|
104
|
+
const graphemeWidth = graphemeCellWidth(grapheme);
|
|
105
|
+
if (graphemeWidth > 0 && width + graphemeWidth > limit) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
output += grapheme;
|
|
109
|
+
width += graphemeWidth;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return output;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function dropTerminalCells(value: string, cells: number) {
|
|
116
|
+
const limit = Math.max(0, Math.trunc(Number(cells) || 0));
|
|
117
|
+
let width = 0;
|
|
118
|
+
let output = "";
|
|
119
|
+
let dropping = true;
|
|
120
|
+
|
|
121
|
+
for (const grapheme of terminalGraphemes(value)) {
|
|
122
|
+
if (dropping) {
|
|
123
|
+
const graphemeWidth = graphemeCellWidth(grapheme);
|
|
124
|
+
if (width < limit || (graphemeWidth === 0 && width <= limit)) {
|
|
125
|
+
width += graphemeWidth;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
dropping = false;
|
|
129
|
+
}
|
|
130
|
+
output += grapheme;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return output;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function padEndTerminalCells(value: string, width: number) {
|
|
137
|
+
const size = Math.max(0, Math.trunc(Number(width) || 0));
|
|
138
|
+
const visibleWidth = terminalCellWidth(value);
|
|
139
|
+
return visibleWidth >= size ? value : `${value}${" ".repeat(size - visibleWidth)}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function terminalCellToStringIndex(value: string) {
|
|
143
|
+
const indexes: number[] = [0];
|
|
144
|
+
let cellOffset = 0;
|
|
145
|
+
let stringIndex = 0;
|
|
146
|
+
|
|
147
|
+
for (const grapheme of terminalGraphemes(value)) {
|
|
148
|
+
const graphemeWidth = graphemeCellWidth(grapheme);
|
|
149
|
+
const nextStringIndex = stringIndex + grapheme.length;
|
|
150
|
+
|
|
151
|
+
if (graphemeWidth > 0) {
|
|
152
|
+
for (let cell = 1; cell < graphemeWidth; cell += 1) {
|
|
153
|
+
indexes[cellOffset + cell] = nextStringIndex;
|
|
154
|
+
}
|
|
155
|
+
cellOffset += graphemeWidth;
|
|
156
|
+
indexes[cellOffset] = nextStringIndex;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
stringIndex = nextStringIndex;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
indexes[cellOffset] = stringIndex;
|
|
163
|
+
return indexes;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function cursorCellOffset(value: string, cursor: number) {
|
|
167
|
+
return terminalCellWidth(value.slice(0, Math.max(0, Math.trunc(Number(cursor) || 0))));
|
|
168
|
+
}
|
package/src/theme.ts
CHANGED
package/src/tree.ts
CHANGED
|
@@ -54,12 +54,27 @@ export function resolveToTerminalNodes(input: any): TerminalNode[] {
|
|
|
54
54
|
return resolveToTerminalNodes(resolveComponent(input.tag, input.props || {}, input.children || []));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
const tag = input.tag as TerminalPrimitiveTag;
|
|
58
|
+
const props = { ...(input.props || {}) } as Record<string, any>;
|
|
59
|
+
const vnodeChildren = input.children || [];
|
|
60
|
+
if (tag === "terminal-list" && vnodeChildren.length === 1 && typeof vnodeChildren[0] === "function") {
|
|
61
|
+
props.__childrenRenderer = vnodeChildren[0];
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
type: "element",
|
|
65
|
+
tag,
|
|
66
|
+
props,
|
|
67
|
+
children: []
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
return [
|
|
58
73
|
{
|
|
59
74
|
type: "element",
|
|
60
|
-
tag
|
|
61
|
-
props
|
|
62
|
-
children: normalizeChildren(
|
|
75
|
+
tag,
|
|
76
|
+
props,
|
|
77
|
+
children: normalizeChildren(vnodeChildren)
|
|
63
78
|
}
|
|
64
79
|
];
|
|
65
80
|
}
|
|
@@ -106,7 +121,7 @@ export function collectDirectOverlayFocusableNodes(nodes: TerminalNode[], out: T
|
|
|
106
121
|
|
|
107
122
|
const overlayChildren = node.children.filter((child) => child.type === "element" && child.tag === "terminal-overlay" && child.props.trapFocus !== false);
|
|
108
123
|
if (overlayChildren.length) {
|
|
109
|
-
collectFocusableNodes(overlayChildren, out);
|
|
124
|
+
collectFocusableNodes(overlayChildren.slice().reverse(), out);
|
|
110
125
|
continue;
|
|
111
126
|
}
|
|
112
127
|
|
package/src/types.ts
CHANGED
|
@@ -28,6 +28,10 @@ export type TerminalCommandId =
|
|
|
28
28
|
| "button.press"
|
|
29
29
|
| "list.prev"
|
|
30
30
|
| "list.next"
|
|
31
|
+
| "list.pageUp"
|
|
32
|
+
| "list.pageDown"
|
|
33
|
+
| "list.home"
|
|
34
|
+
| "list.end"
|
|
31
35
|
| "list.press"
|
|
32
36
|
| "scroll.up"
|
|
33
37
|
| "scroll.down"
|
|
@@ -125,8 +129,13 @@ export interface TerminalHitbox {
|
|
|
125
129
|
y2: number;
|
|
126
130
|
textStartX?: number;
|
|
127
131
|
textLength?: number;
|
|
132
|
+
textCellToStringIndex?: number[];
|
|
128
133
|
itemOffset?: number;
|
|
134
|
+
itemIndexes?: number[];
|
|
135
|
+
contentY?: number;
|
|
129
136
|
pointerLayer?: number;
|
|
137
|
+
__listItemIndex?: number;
|
|
138
|
+
__pressHandler?: (event: TerminalButtonPressEventPayload) => void;
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
export interface CursorPosition {
|
|
@@ -290,14 +299,29 @@ export interface TerminalButtonPressEventPayload extends TerminalEventPayloadBas
|
|
|
290
299
|
type: "press" | "doublepress" | "contextpress";
|
|
291
300
|
}
|
|
292
301
|
|
|
293
|
-
export interface
|
|
302
|
+
export interface TerminalListStateEventPayload {
|
|
303
|
+
activeIndex: number;
|
|
304
|
+
selectedIndex: number | null;
|
|
305
|
+
viewportOffset: number;
|
|
306
|
+
viewportRows: number;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface TerminalListChangeEventPayload<T = any> extends TerminalValueEventPayloadBase<T>, TerminalListStateEventPayload {
|
|
294
310
|
type: "change";
|
|
295
311
|
index: number;
|
|
312
|
+
key?: string;
|
|
296
313
|
}
|
|
297
314
|
|
|
298
|
-
export interface
|
|
315
|
+
export interface TerminalListViewportChangeEventPayload extends TerminalEventPayloadBase, TerminalListStateEventPayload {
|
|
316
|
+
type: "viewportchange";
|
|
317
|
+
offset: number;
|
|
318
|
+
rows: number;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface TerminalListPressEventPayload<T = any> extends TerminalValueEventPayloadBase<T>, TerminalListStateEventPayload {
|
|
299
322
|
type: "press" | "doublepress" | "contextpress";
|
|
300
323
|
index: number;
|
|
324
|
+
key?: string;
|
|
301
325
|
}
|
|
302
326
|
|
|
303
327
|
export type TerminalChangeEventPayload =
|
|
@@ -334,6 +358,7 @@ export interface TerminalRowPointerEventPayloadBase extends TerminalPointerCoord
|
|
|
334
358
|
export interface TerminalListPointerEventPayload<T = any> extends TerminalRowPointerEventPayloadBase {
|
|
335
359
|
type: TerminalMouseEventType;
|
|
336
360
|
index: number;
|
|
361
|
+
key?: string;
|
|
337
362
|
value: T;
|
|
338
363
|
}
|
|
339
364
|
|
|
@@ -363,7 +388,9 @@ export interface TerminalInputProps extends TerminalFocusableProps, TerminalStyl
|
|
|
363
388
|
export interface TerminalEditorProps extends TerminalFocusableProps, TerminalStyleProps {
|
|
364
389
|
value?: string;
|
|
365
390
|
placeholder?: string;
|
|
391
|
+
width?: number;
|
|
366
392
|
height?: number;
|
|
393
|
+
fill?: boolean;
|
|
367
394
|
onchange?(event: TerminalEditorChangeEventPayload): void;
|
|
368
395
|
oninput?(event: TerminalEditorChangeEventPayload): void;
|
|
369
396
|
onsubmit?(event: TerminalEditorSubmitEventPayload): void;
|
|
@@ -377,13 +404,31 @@ export interface TerminalButtonProps extends TerminalFocusableProps, TerminalSty
|
|
|
377
404
|
oncontextpress?(event: TerminalButtonPressEventPayload): void;
|
|
378
405
|
}
|
|
379
406
|
|
|
407
|
+
export interface TerminalListRenderContext<T = any> {
|
|
408
|
+
index: number;
|
|
409
|
+
key: string;
|
|
410
|
+
active: boolean;
|
|
411
|
+
selected: boolean;
|
|
412
|
+
viewportIndex: number;
|
|
413
|
+
item: T;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export type TerminalListItemKey<T = any> = (item: T, index: number) => string | number;
|
|
417
|
+
export type TerminalListItemRenderer<T = any> = (item: T, ctx: TerminalListRenderContext<T>) => any;
|
|
418
|
+
|
|
380
419
|
export interface TerminalListProps<T = any> extends TerminalFocusableProps, TerminalPointerCaptureProps, TerminalStyleProps {
|
|
381
420
|
items?: readonly T[];
|
|
382
421
|
virtualized?: boolean;
|
|
422
|
+
height?: number;
|
|
383
423
|
itemHeight?: 1;
|
|
384
424
|
overscan?: number;
|
|
385
|
-
|
|
425
|
+
wrap?: boolean;
|
|
426
|
+
itemKey?: TerminalListItemKey<T>;
|
|
427
|
+
showActive?: boolean;
|
|
428
|
+
renderItem?(item: T, index: number): any;
|
|
429
|
+
children?: TerminalListItemRenderer<T>;
|
|
386
430
|
onchange?(event: TerminalListChangeEventPayload<T>): void;
|
|
431
|
+
onviewportchange?(event: TerminalListViewportChangeEventPayload): void;
|
|
387
432
|
onpress?(event: TerminalListPressEventPayload<T>): void;
|
|
388
433
|
ondoublepress?(event: TerminalListPressEventPayload<T>): void;
|
|
389
434
|
oncontextpress?(event: TerminalListPressEventPayload<T>): void;
|