@valyrianjs/terminal 0.2.1 → 0.2.3
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.map +1 -1
- package/dist/ansi.js +177 -17
- package/dist/ansi.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +4 -0
- 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/layout.d.ts +5 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +53 -23
- package/dist/layout.js.map +1 -1
- package/dist/mouse.d.ts.map +1 -1
- package/dist/mouse.js +8 -1
- package/dist/mouse.js.map +1 -1
- package/dist/render-internal.d.ts +10 -0
- package/dist/render-internal.d.ts.map +1 -0
- package/dist/render-internal.js +1295 -0
- package/dist/render-internal.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +13 -1205
- package/dist/render.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +78 -4
- 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 +125 -0
- package/dist/text.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +18 -2
- package/dist/theme.js.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/api-reference.md +6 -3
- package/docs/cookbook.md +1 -1
- package/docs/interaction-model.md +5 -5
- package/docs/primitive-gallery.md +4 -4
- 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 +16 -13
- package/package.json +3 -2
- package/src/ansi.ts +207 -17
- package/src/events.ts +2 -0
- package/src/frame-style.ts +36 -0
- package/src/layout.ts +57 -24
- package/src/mouse.ts +10 -1
- package/src/render-internal.ts +1441 -0
- package/src/render.ts +14 -1324
- package/src/session.ts +99 -12
- package/src/text.ts +160 -0
- package/src/theme.ts +22 -2
- package/src/types.ts +3 -2
package/src/layout.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cloneStyleSpan, isFullFrameSpan, isFullRowSpan } from "./frame-style.js";
|
|
2
|
+
import { dropTerminalCells, padEndTerminalCells, sliceTerminalCells, terminalCellWidth } from "./text.js";
|
|
1
3
|
import type { CursorPosition, TerminalFrame, TerminalHitbox, TerminalStyleSpan } from "./types.js";
|
|
2
4
|
|
|
3
5
|
function repeat(char: string, count: number) {
|
|
@@ -9,7 +11,7 @@ export function createFrame(lines: string[], hitboxes: TerminalHitbox[] = [], cu
|
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function getFrameWidth(frame: TerminalFrame) {
|
|
12
|
-
return frame.lines.reduce((max, line) => Math.max(max, line
|
|
14
|
+
return frame.lines.reduce((max, line) => Math.max(max, terminalCellWidth(line)), 0);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function getFrameHeight(frame: TerminalFrame) {
|
|
@@ -29,12 +31,12 @@ export function shiftFrame(frame: TerminalFrame, dx: number, dy: number): Termin
|
|
|
29
31
|
contentY: typeof box.contentY === "number" ? box.contentY + dy : undefined
|
|
30
32
|
})),
|
|
31
33
|
cursor: frame.cursor ? { x: frame.cursor.x + dx, y: frame.cursor.y + dy } : null,
|
|
32
|
-
spans: frame.spans.map((span) => (
|
|
34
|
+
spans: frame.spans.map((span) => cloneStyleSpan(span, { x1: span.x1 + dx, x2: span.x2 + dx, y: span.y + dy }))
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function normalizeLines(frame: TerminalFrame, width: number, height: number) {
|
|
37
|
-
const lines = frame.lines.map((line) => line
|
|
39
|
+
const lines = frame.lines.map((line) => padEndTerminalCells(line, width));
|
|
38
40
|
while (lines.length < height) {
|
|
39
41
|
lines.push(repeat(" ", width));
|
|
40
42
|
}
|
|
@@ -57,15 +59,15 @@ export function mergeVertical(frames: TerminalFrame[], options: { gap?: number }
|
|
|
57
59
|
|
|
58
60
|
for (let i = 0; i < filtered.length; i += 1) {
|
|
59
61
|
const frame = filtered[i];
|
|
60
|
-
const
|
|
61
|
-
lines.push(...
|
|
62
|
-
const shifted = shiftFrame(
|
|
62
|
+
const normalizedFrame = fitFrame(frame, width, getFrameHeight(frame), { expandFullFrameSpans: true });
|
|
63
|
+
lines.push(...normalizedFrame.lines);
|
|
64
|
+
const shifted = shiftFrame(normalizedFrame, 0, rowOffset);
|
|
63
65
|
hitboxes.push(...shifted.hitboxes);
|
|
64
66
|
spans.push(...shifted.spans);
|
|
65
67
|
if (!cursor && frame.cursor) {
|
|
66
68
|
cursor = { x: frame.cursor.x, y: frame.cursor.y + rowOffset };
|
|
67
69
|
}
|
|
68
|
-
rowOffset +=
|
|
70
|
+
rowOffset += normalizedFrame.lines.length;
|
|
69
71
|
if (i < filtered.length - 1 && gap > 0) {
|
|
70
72
|
for (let j = 0; j < gap; j += 1) {
|
|
71
73
|
lines.push(repeat(" ", width));
|
|
@@ -87,9 +89,8 @@ export function mergeHorizontal(frames: TerminalFrame[], options: { gap?: number
|
|
|
87
89
|
const widths = filtered.map(getFrameWidth);
|
|
88
90
|
const height = filtered.reduce((max, frame) => Math.max(max, getFrameHeight(frame)), 0);
|
|
89
91
|
const normalizedFrames = filtered.map((frame, index) => ({
|
|
90
|
-
frame,
|
|
91
|
-
width: widths[index]
|
|
92
|
-
lines: normalizeLines(frame, widths[index], height)
|
|
92
|
+
frame: fitFrame(frame, widths[index], height, { expandFullFrameSpans: true }),
|
|
93
|
+
width: widths[index]
|
|
93
94
|
}));
|
|
94
95
|
const gapText = repeat(" ", gap);
|
|
95
96
|
const lines = new Array<string>(height).fill("");
|
|
@@ -101,7 +102,7 @@ export function mergeHorizontal(frames: TerminalFrame[], options: { gap?: number
|
|
|
101
102
|
for (let index = 0; index < normalizedFrames.length; index += 1) {
|
|
102
103
|
const part = normalizedFrames[index];
|
|
103
104
|
for (let row = 0; row < height; row += 1) {
|
|
104
|
-
lines[row] += part.lines[row];
|
|
105
|
+
lines[row] += part.frame.lines[row];
|
|
105
106
|
if (index < normalizedFrames.length - 1) {
|
|
106
107
|
lines[row] += gapText;
|
|
107
108
|
}
|
|
@@ -125,20 +126,20 @@ export function padFrame(frame: TerminalFrame, padding: number) {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
const width = getFrameWidth(frame);
|
|
128
|
-
const middle = frame.lines.map((line) => `${repeat(" ", amount)}${line
|
|
129
|
+
const middle = frame.lines.map((line) => `${repeat(" ", amount)}${padEndTerminalCells(line, width)}${repeat(" ", amount)}`);
|
|
129
130
|
const blank = repeat(" ", width + amount * 2);
|
|
130
131
|
const lines = [...new Array<string>(amount).fill(blank), ...middle, ...new Array<string>(amount).fill(blank)];
|
|
131
132
|
const shifted = shiftFrame(frame, amount, amount);
|
|
132
133
|
return createFrame(lines, shifted.hitboxes, frame.cursor ? { x: frame.cursor.x + amount, y: frame.cursor.y + amount } : null, shifted.spans);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
export function fitFrame(frame: TerminalFrame, width?: number, height?: number) {
|
|
136
|
+
export function fitFrame(frame: TerminalFrame, width?: number, height?: number, options: { expandFullFrameSpans?: boolean } = {}) {
|
|
136
137
|
const nextWidth = Math.max(getFrameWidth(frame), Number(width || 0));
|
|
137
138
|
const nextHeight = Math.max(getFrameHeight(frame), Number(height || 0));
|
|
138
139
|
if (nextWidth === getFrameWidth(frame) && nextHeight === getFrameHeight(frame)) {
|
|
139
140
|
return frame;
|
|
140
141
|
}
|
|
141
|
-
return
|
|
142
|
+
return constrainFrame(frame, { width: nextWidth, height: nextHeight, expandFullFrameSpans: options.expandFullFrameSpans });
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
function constraintSize(value: number | undefined, fallback: number) {
|
|
@@ -160,13 +161,13 @@ function clamp(value: number, min: number, max: number) {
|
|
|
160
161
|
return Math.min(max, Math.max(min, value));
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
export function constrainFrame(frame: TerminalFrame, options: { width?: number; height?: number } = {}): TerminalFrame {
|
|
164
|
+
export function constrainFrame(frame: TerminalFrame, options: { width?: number; height?: number; expandFullFrameSpans?: boolean; expandFullRowSpans?: boolean } = {}): TerminalFrame {
|
|
164
165
|
const width = constraintSize(options.width, getFrameWidth(frame));
|
|
165
166
|
const height = constraintSize(options.height, getFrameHeight(frame));
|
|
166
167
|
const lines: string[] = [];
|
|
167
168
|
|
|
168
169
|
for (let row = 0; row < height; row += 1) {
|
|
169
|
-
lines.push((frame.lines[row] || ""
|
|
170
|
+
lines.push(padEndTerminalCells(sliceTerminalCells(frame.lines[row] || "", width), width));
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
const hitboxes = frame.hitboxes
|
|
@@ -184,8 +185,26 @@ export function constrainFrame(frame: TerminalFrame, options: { width?: number;
|
|
|
184
185
|
? { x: frame.cursor.x, y: frame.cursor.y }
|
|
185
186
|
: null;
|
|
186
187
|
|
|
188
|
+
const originalWidth = getFrameWidth(frame);
|
|
189
|
+
const originalHeight = getFrameHeight(frame);
|
|
187
190
|
const spanRightEdge = width + 1;
|
|
188
191
|
const spans = frame.spans.flatMap((span) => {
|
|
192
|
+
const coversCurrentFrame = options.expandFullFrameSpans === true && isFullFrameSpan(span) && span.x1 === 1 && span.x2 === originalWidth + 1;
|
|
193
|
+
if (coversCurrentFrame) {
|
|
194
|
+
if (width <= 0 || span.y < 1 || span.y > Math.min(originalHeight, height)) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return [cloneStyleSpan(span, { x1: 1, x2: spanRightEdge })];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const coversCurrentRow = (options.expandFullFrameSpans === true || options.expandFullRowSpans === true) && isFullRowSpan(span) && span.x1 === 1 && span.x2 === originalWidth + 1;
|
|
201
|
+
if (coversCurrentRow) {
|
|
202
|
+
if (width <= 0 || span.y < 1 || span.y > Math.min(originalHeight, height)) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
return [cloneStyleSpan(span, { x1: 1, x2: spanRightEdge })];
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
if (width <= 0 || span.y < 1 || span.y > height || span.x2 <= 1 || span.x1 >= spanRightEdge) {
|
|
190
209
|
return [];
|
|
191
210
|
}
|
|
@@ -196,9 +215,22 @@ export function constrainFrame(frame: TerminalFrame, options: { width?: number;
|
|
|
196
215
|
return [];
|
|
197
216
|
}
|
|
198
217
|
|
|
199
|
-
return [
|
|
218
|
+
return [cloneStyleSpan(span, { x1: clippedX1, x2: clippedX2 })];
|
|
200
219
|
});
|
|
201
220
|
|
|
221
|
+
const fullFrameSpans = options.expandFullFrameSpans === true
|
|
222
|
+
? frame.spans.filter((span) => isFullFrameSpan(span) && span.x1 === 1 && span.x2 === originalWidth + 1)
|
|
223
|
+
: [];
|
|
224
|
+
const firstFullFrameRow = fullFrameSpans.reduce<number | null>((first, span) => first === null ? span.y : Math.min(first, span.y), null);
|
|
225
|
+
if (firstFullFrameRow !== null) {
|
|
226
|
+
const templates = fullFrameSpans.filter((span) => span.y === firstFullFrameRow);
|
|
227
|
+
for (let y = originalHeight + 1; y <= height; y += 1) {
|
|
228
|
+
for (const template of templates) {
|
|
229
|
+
spans.push(cloneStyleSpan(template, { x1: 1, x2: spanRightEdge, y }));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
202
234
|
return createFrame(lines, hitboxes, cursor, spans);
|
|
203
235
|
}
|
|
204
236
|
|
|
@@ -234,16 +266,20 @@ export function overlayFrame(base: TerminalFrame, overlay: TerminalFrame, option
|
|
|
234
266
|
|
|
235
267
|
const baseLine = lines[baseRow];
|
|
236
268
|
const start = x - 1;
|
|
237
|
-
if (start >= baseLine
|
|
269
|
+
if (start >= terminalCellWidth(baseLine)) {
|
|
238
270
|
continue;
|
|
239
271
|
}
|
|
240
272
|
|
|
241
|
-
const visibleWidth = Math.min(constrainedOverlay.lines[row]
|
|
273
|
+
const visibleWidth = Math.min(terminalCellWidth(constrainedOverlay.lines[row]), terminalCellWidth(baseLine) - start);
|
|
242
274
|
if (visibleWidth <= 0) {
|
|
243
275
|
continue;
|
|
244
276
|
}
|
|
245
277
|
|
|
246
|
-
|
|
278
|
+
const prefix = sliceTerminalCells(baseLine, start);
|
|
279
|
+
const suffix = dropTerminalCells(baseLine, start + visibleWidth);
|
|
280
|
+
const insert = sliceTerminalCells(constrainedOverlay.lines[row], visibleWidth);
|
|
281
|
+
const removedWidth = terminalCellWidth(baseLine) - terminalCellWidth(prefix) - terminalCellWidth(suffix);
|
|
282
|
+
lines[baseRow] = `${prefix}${padEndTerminalCells(insert, removedWidth)}${suffix}`;
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
const shiftedOverlay = shiftFrame(constrainedOverlay, x - 1, y - 1);
|
|
@@ -271,10 +307,7 @@ export function cropFrame(frame: TerminalFrame, offset: number, height: number)
|
|
|
271
307
|
}));
|
|
272
308
|
const spans = frame.spans
|
|
273
309
|
.filter((span) => span.y > start && span.y <= start + size)
|
|
274
|
-
.map((span) => ({
|
|
275
|
-
...span,
|
|
276
|
-
y: span.y - start
|
|
277
|
-
}));
|
|
310
|
+
.map((span) => cloneStyleSpan(span, { y: span.y - start }));
|
|
278
311
|
const cursor = frame.cursor && frame.cursor.y > start && frame.cursor.y <= start + size
|
|
279
312
|
? { x: frame.cursor.x, y: frame.cursor.y - start }
|
|
280
313
|
: null;
|
package/src/mouse.ts
CHANGED
|
@@ -79,6 +79,15 @@ export function cursorFromHitbox(hitbox: TerminalHitbox, x: number) {
|
|
|
79
79
|
if (typeof hitbox.textStartX !== "number") {
|
|
80
80
|
return 0;
|
|
81
81
|
}
|
|
82
|
+
|
|
82
83
|
const textLength = Number(hitbox.textLength || 0);
|
|
83
|
-
|
|
84
|
+
const cellOffset = Math.max(0, x - hitbox.textStartX);
|
|
85
|
+
if (Array.isArray(hitbox.textCellToStringIndex) && hitbox.textCellToStringIndex.length > 0) {
|
|
86
|
+
const index = hitbox.textCellToStringIndex[Math.min(cellOffset, hitbox.textCellToStringIndex.length - 1)];
|
|
87
|
+
if (typeof index === "number" && Number.isFinite(index)) {
|
|
88
|
+
return Math.max(0, Math.min(textLength, index));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Math.max(0, Math.min(textLength, cellOffset));
|
|
84
93
|
}
|