@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/render.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { constrainFrame, createFrame, cropFrame, fitFrame, getFrameHeight, getFrameWidth, mergeHorizontal, mergeVertical, overlayFrame, shiftFrame } from "./layout.js";
|
|
2
2
|
import { getSelectionRange, normalizeInputState } from "./events.js";
|
|
3
|
+
import { markFullFrameSpan, markFullRowSpan } from "./frame-style.js";
|
|
3
4
|
import { createEditorState } from "./editor-state.js";
|
|
4
5
|
import { isFocusable, textContent } from "./tree.js";
|
|
5
6
|
import { renderValyrianTerminal } from "./runtime.js";
|
|
6
|
-
import { plainText } from "./text.js";
|
|
7
|
+
import { cursorCellOffset, padEndTerminalCells, plainText, sliceTerminalCells, terminalCellToStringIndex, terminalCellWidth, terminalGraphemes } from "./text.js";
|
|
7
8
|
import { resolveTerminalStyle } from "./theme.js";
|
|
8
9
|
|
|
9
|
-
import type { InputInteractionState, TerminalElementNode, TerminalFocusNode, TerminalFrame, TerminalNode, TerminalSpacing, TerminalSplitBreakpoint, TerminalSplitSize, TerminalStyleDefinition, TerminalStyleSpan, TerminalTheme, TerminalVisualState } from "./types.js";
|
|
10
|
+
import type { InputInteractionState, TerminalElementNode, TerminalFocusNode, TerminalFrame, TerminalHitbox, TerminalNode, TerminalSpacing, TerminalSplitBreakpoint, TerminalSplitSize, TerminalStyleDefinition, TerminalStyleSpan, TerminalTheme, TerminalVisualState } from "./types.js";
|
|
10
11
|
|
|
11
12
|
export interface TerminalRenderContext {
|
|
12
13
|
cols: number;
|
|
@@ -173,7 +174,7 @@ function fullFrameSpans(kinds: string[], width: number, height: number): Termina
|
|
|
173
174
|
const spans: TerminalStyleSpan[] = [];
|
|
174
175
|
for (const kind of kinds) {
|
|
175
176
|
for (let y = 1; y <= height; y += 1) {
|
|
176
|
-
spans.push({ kind, x1: 1, x2: width + 1, y });
|
|
177
|
+
spans.push(markFullFrameSpan({ kind, x1: 1, x2: width + 1, y }));
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
return spans;
|
|
@@ -209,7 +210,7 @@ function padFrameSides(frame: TerminalFrame, padding: SpacingSides) {
|
|
|
209
210
|
const bottomLines = new Array<string>(padding.bottom).fill(" ".repeat(contentWidth));
|
|
210
211
|
const lines = [
|
|
211
212
|
...topLines,
|
|
212
|
-
...frame.lines.map((line) => `${" ".repeat(padding.left)}${line
|
|
213
|
+
...frame.lines.map((line) => `${" ".repeat(padding.left)}${padEndTerminalCells(line, width)}${" ".repeat(padding.right)}`),
|
|
213
214
|
...bottomLines
|
|
214
215
|
];
|
|
215
216
|
return shiftFrame(createFrame(lines, frame.hitboxes, frame.cursor, frame.spans), padding.left, padding.top);
|
|
@@ -227,7 +228,7 @@ function addBorder(frame: TerminalFrame, border: BorderSides) {
|
|
|
227
228
|
lines.push(`${border.left ? chars.topLeft : chars.horizontal}${chars.horizontal.repeat(Math.max(0, width - left - right))}${border.right ? chars.topRight : chars.horizontal}`.slice(0, width));
|
|
228
229
|
}
|
|
229
230
|
for (const line of frame.lines) {
|
|
230
|
-
lines.push(`${border.left ? chars.vertical : ""}${line
|
|
231
|
+
lines.push(`${border.left ? chars.vertical : ""}${padEndTerminalCells(line, innerWidth)}${border.right ? chars.vertical : ""}`);
|
|
231
232
|
}
|
|
232
233
|
if (border.bottom) {
|
|
233
234
|
lines.push(`${border.left ? chars.bottomLeft : chars.horizontal}${chars.horizontal.repeat(Math.max(0, width - left - right))}${border.right ? chars.bottomRight : chars.horizontal}`.slice(0, width));
|
|
@@ -252,15 +253,28 @@ function addFullFrameSpans(frame: TerminalFrame, kinds: ResolvedStyleSpan[]) {
|
|
|
252
253
|
const spans = frame.spans.slice();
|
|
253
254
|
for (const span of kinds) {
|
|
254
255
|
for (let y = 1; y <= height; y += 1) {
|
|
255
|
-
spans.push({ ...span, x1: 1, x2: width + 1, y });
|
|
256
|
+
spans.push(markFullFrameSpan({ ...span, x1: 1, x2: width + 1, y }));
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
return createFrame(frame.lines, frame.hitboxes, frame.cursor, spans);
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
function
|
|
262
|
+
function listViewportRows(node: TerminalElementNode, itemCount: number, context?: TerminalRenderContext) {
|
|
263
|
+
const explicitHeight = positiveDimension(node.props.height, "height");
|
|
264
|
+
const viewportSourceRows = explicitHeight ?? context?.rows ?? (itemCount || 1);
|
|
265
|
+
return Math.max(1, Math.min(itemCount || 1, positiveInteger(viewportSourceRows, "List viewport height")));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function clampListIndex(index: number, itemCount: number) {
|
|
269
|
+
if (itemCount <= 0) {
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
return Math.max(0, Math.min(itemCount - 1, index));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function listVirtualRange(node: TerminalElementNode, itemCount: number, context?: TerminalRenderContext) {
|
|
262
276
|
if (!node.props.virtualized) {
|
|
263
|
-
return { start: 0, end: itemCount };
|
|
277
|
+
return { start: 0, end: itemCount, visibleStart: 0, viewportRows: itemCount || 1 };
|
|
264
278
|
}
|
|
265
279
|
|
|
266
280
|
if (typeof node.props.itemHeight !== "undefined" && node.props.itemHeight !== 1) {
|
|
@@ -268,14 +282,102 @@ function listVirtualRange(node: TerminalElementNode, itemCount: number, selected
|
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
const overscan = nonNegativeInteger(node.props.overscan, "List overscan");
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
const visibleStart = Math.max(0, Math.min(selected, selected - viewportRows + 1));
|
|
285
|
+
const viewportRows = listViewportRows(node, itemCount, context);
|
|
286
|
+
const maxOffset = Math.max(0, itemCount - viewportRows);
|
|
287
|
+
let visibleStart = Math.max(0, Math.min(maxOffset, nonNegativeInteger(node.props.__scrollOffset, "List viewport offset")));
|
|
275
288
|
const start = Math.max(0, visibleStart - overscan);
|
|
276
289
|
const end = Math.min(itemCount, visibleStart + viewportRows + overscan);
|
|
277
290
|
|
|
278
|
-
return { start, end };
|
|
291
|
+
return { start, end, visibleStart, viewportRows };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function listItemKey(node: TerminalElementNode, item: unknown, index: number) {
|
|
295
|
+
if (typeof node.props.itemKey === "function") {
|
|
296
|
+
const key = node.props.itemKey(item, index);
|
|
297
|
+
if (typeof key !== "string" && typeof key !== "number") {
|
|
298
|
+
throw new RangeError("List itemKey must return a string or number");
|
|
299
|
+
}
|
|
300
|
+
return String(key);
|
|
301
|
+
}
|
|
302
|
+
return String(index);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function listItemRenderer(node: TerminalElementNode) {
|
|
306
|
+
if (typeof node.props.__childrenRenderer === "function") {
|
|
307
|
+
return { type: "children" as const, render: node.props.__childrenRenderer };
|
|
308
|
+
}
|
|
309
|
+
if (typeof node.props.renderItem === "function") {
|
|
310
|
+
return { type: "renderItem" as const, render: node.props.renderItem };
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function wrapPlainText(value: string, width: number) {
|
|
316
|
+
if (!Number.isFinite(width) || !Number.isInteger(width) || width <= 0) {
|
|
317
|
+
return [""];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const rows: string[] = [];
|
|
321
|
+
const sourceRows = value.split("\n");
|
|
322
|
+
for (const sourceRow of sourceRows) {
|
|
323
|
+
if (sourceRow.length === 0) {
|
|
324
|
+
rows.push("");
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let remaining = sourceRow;
|
|
329
|
+
while (terminalCellWidth(remaining) > width) {
|
|
330
|
+
const slice = sliceTerminalCells(remaining, width);
|
|
331
|
+
if (slice.length === 0) {
|
|
332
|
+
const [firstGrapheme = ""] = terminalGraphemes(remaining);
|
|
333
|
+
rows.push(firstGrapheme);
|
|
334
|
+
remaining = remaining.slice(firstGrapheme.length);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const breakAt = slice.lastIndexOf(" ");
|
|
339
|
+
if (breakAt > 0 && breakAt >= Math.floor(width * 0.6)) {
|
|
340
|
+
rows.push(remaining.slice(0, breakAt));
|
|
341
|
+
remaining = remaining.slice(breakAt + 1);
|
|
342
|
+
} else {
|
|
343
|
+
rows.push(slice);
|
|
344
|
+
remaining = remaining.slice(slice.length);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
rows.push(remaining);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return rows.length ? rows : [""];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderListItemFrame(node: TerminalElementNode, item: unknown, index: number, viewportIndex: number, activeIndex: number, selectedIndex: number | null, wrapWidth: number, context?: TerminalRenderContext) {
|
|
354
|
+
const key = listItemKey(node, item, index);
|
|
355
|
+
const renderer = listItemRenderer(node);
|
|
356
|
+
if (!renderer) {
|
|
357
|
+
const label = plainText(item);
|
|
358
|
+
return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const ctx = {
|
|
362
|
+
index,
|
|
363
|
+
key,
|
|
364
|
+
active: index === activeIndex,
|
|
365
|
+
selected: selectedIndex !== null && index === selectedIndex,
|
|
366
|
+
viewportIndex,
|
|
367
|
+
item
|
|
368
|
+
};
|
|
369
|
+
const rendered = renderer.type === "children" ? renderer.render(item, ctx) : renderer.render(item, index);
|
|
370
|
+
|
|
371
|
+
if (typeof rendered === "string" || typeof rendered === "number") {
|
|
372
|
+
const label = plainText(rendered);
|
|
373
|
+
return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const frame = mergeVertical(renderValyrianTerminal(rendered).map((child) => renderTerminalFrame(child, context)));
|
|
377
|
+
if (node.props.wrap === true && frame.hitboxes.length === 0) {
|
|
378
|
+
return createFrame(frame.lines.flatMap((line) => wrapPlainText(line, wrapWidth)));
|
|
379
|
+
}
|
|
380
|
+
return frame;
|
|
279
381
|
}
|
|
280
382
|
|
|
281
383
|
function fixedPosition(value: unknown) {
|
|
@@ -371,7 +473,8 @@ function decorateContainerFrame(frame: TerminalFrame, node: TerminalElementNode,
|
|
|
371
473
|
if ((typeof width === "number" && width > 0) || (typeof height === "number" && height > 0)) {
|
|
372
474
|
next = constrainFrame(next, {
|
|
373
475
|
width: typeof width === "number" && width > 0 ? width : undefined,
|
|
374
|
-
height: typeof height === "number" && height > 0 ? height : undefined
|
|
476
|
+
height: typeof height === "number" && height > 0 ? height : undefined,
|
|
477
|
+
expandFullFrameSpans: true
|
|
375
478
|
});
|
|
376
479
|
}
|
|
377
480
|
}
|
|
@@ -380,7 +483,8 @@ function decorateContainerFrame(frame: TerminalFrame, node: TerminalElementNode,
|
|
|
380
483
|
if (options.constrain) {
|
|
381
484
|
next = constrainFrame(next, {
|
|
382
485
|
width: typeof options.width === "undefined" ? positiveDimension(node.props.width, "width") : options.width,
|
|
383
|
-
height: typeof options.height === "undefined" ? positiveDimension(node.props.height, "height") : options.height
|
|
486
|
+
height: typeof options.height === "undefined" ? positiveDimension(node.props.height, "height") : options.height,
|
|
487
|
+
expandFullFrameSpans: true
|
|
384
488
|
});
|
|
385
489
|
}
|
|
386
490
|
next = addContainerStyleSpans(next, node, resolved);
|
|
@@ -444,6 +548,19 @@ function resolveContainerChildContext(node: TerminalElementNode, dimensions: { w
|
|
|
444
548
|
};
|
|
445
549
|
}
|
|
446
550
|
|
|
551
|
+
function interactiveTextMetadata(value: string) {
|
|
552
|
+
const textLength = value.length;
|
|
553
|
+
const textCellToStringIndex = terminalCellToStringIndex(value);
|
|
554
|
+
const usesLinearIndexes = textCellToStringIndex.length === textLength + 1
|
|
555
|
+
&& textCellToStringIndex.every((index, cellOffset) => index === cellOffset);
|
|
556
|
+
|
|
557
|
+
if (usesLinearIndexes) {
|
|
558
|
+
return { textLength };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return { textLength, textCellToStringIndex };
|
|
562
|
+
}
|
|
563
|
+
|
|
447
564
|
function renderInputLine(value: string, inputState: InputInteractionState, padding: SpacingSides = { top: 0, right: 0, bottom: 0, left: 0 }) {
|
|
448
565
|
const state = normalizeInputState(inputState, value.length);
|
|
449
566
|
const { start, end } = getSelectionRange(state);
|
|
@@ -452,12 +569,29 @@ function renderInputLine(value: string, inputState: InputInteractionState, paddi
|
|
|
452
569
|
const textStart = padding.left + 1;
|
|
453
570
|
return {
|
|
454
571
|
line: paddedLine,
|
|
455
|
-
cursor: { x: textStart + state.cursor, y: 1 },
|
|
456
|
-
spans: start === end ? [] : [{ kind: "input.selection", x1: textStart + start, x2: textStart + end, y: 1 }]
|
|
572
|
+
cursor: { x: textStart + cursorCellOffset(value, state.cursor), y: 1 },
|
|
573
|
+
spans: start === end ? [] : [{ kind: "input.selection", x1: textStart + cursorCellOffset(value, start), x2: textStart + cursorCellOffset(value, end), y: 1 }]
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function resolveEditorDimensions(node: TerminalElementNode, context?: TerminalRenderContext) {
|
|
578
|
+
const explicitWidth = positiveDimension(node.props.width, "width");
|
|
579
|
+
const explicitHeight = positiveDimension(node.props.height, "height");
|
|
580
|
+
return {
|
|
581
|
+
width: typeof explicitWidth !== "undefined"
|
|
582
|
+
? explicitWidth
|
|
583
|
+
: node.props.fill === true
|
|
584
|
+
? fillContextDimension(context, "width", "Editor")
|
|
585
|
+
: undefined,
|
|
586
|
+
height: typeof explicitHeight !== "undefined"
|
|
587
|
+
? explicitHeight
|
|
588
|
+
: node.props.fill === true
|
|
589
|
+
? fillContextDimension(context, "height", "Editor")
|
|
590
|
+
: undefined
|
|
457
591
|
};
|
|
458
592
|
}
|
|
459
593
|
|
|
460
|
-
function renderEditorFrame(node: TerminalElementNode) {
|
|
594
|
+
function renderEditorFrame(node: TerminalElementNode, context?: TerminalRenderContext) {
|
|
461
595
|
const value = typeof node.props.value !== "undefined" ? plainText(node.props.value) : "";
|
|
462
596
|
const placeholder = typeof node.props.placeholder !== "undefined" ? plainText(node.props.placeholder) : "";
|
|
463
597
|
const displayValue = value.length === 0 && !node.props.__focused && placeholder ? placeholder : value;
|
|
@@ -472,23 +606,28 @@ function renderEditorFrame(node: TerminalElementNode) {
|
|
|
472
606
|
|
|
473
607
|
return `> ${line.slice(0, focusedColumn)}|${line.slice(focusedColumn)}`;
|
|
474
608
|
});
|
|
475
|
-
const width = lines.reduce((max, line) => Math.max(max, line
|
|
476
|
-
const cursor = node.props.__focused ? { x: 3 + focusedColumn, y: focusedLine + 1 } : null;
|
|
477
|
-
const spans: TerminalStyleSpan[] = node.props.__focused ? [{ kind: "focus", x1: 1, x2: Math.max(2, lines[focusedLine]
|
|
478
|
-
const
|
|
609
|
+
const width = lines.reduce((max, line) => Math.max(max, terminalCellWidth(line)), 0);
|
|
610
|
+
const cursor = node.props.__focused ? { x: 3 + cursorCellOffset(focusedState.lines[focusedLine], focusedColumn), y: focusedLine + 1 } : null;
|
|
611
|
+
const spans: TerminalStyleSpan[] = node.props.__focused ? [{ kind: "focus", x1: 1, x2: Math.max(2, terminalCellWidth(lines[focusedLine]) + 1), y: focusedLine + 1 }] : [];
|
|
612
|
+
const dimensions = resolveEditorDimensions(node, context);
|
|
479
613
|
const frame = createFrame(lines, [], cursor, spans);
|
|
480
|
-
const scrollOffset = node.props.__focused && typeof height !== "undefined"
|
|
481
|
-
? Math.min(Math.max(0, focusedLine - height + 1), Math.max(0, lines.length - height))
|
|
614
|
+
const scrollOffset = node.props.__focused && typeof dimensions.height !== "undefined"
|
|
615
|
+
? Math.min(Math.max(0, focusedLine - dimensions.height + 1), Math.max(0, lines.length - dimensions.height))
|
|
482
616
|
: 0;
|
|
483
|
-
const
|
|
617
|
+
const croppedFrame = typeof dimensions.height === "undefined"
|
|
484
618
|
? frame
|
|
485
|
-
:
|
|
619
|
+
: scrollOffset > 0
|
|
620
|
+
? cropFrame(frame, scrollOffset, dimensions.height)
|
|
621
|
+
: frame;
|
|
622
|
+
const constrainedFrame = typeof dimensions.width === "undefined" && typeof dimensions.height === "undefined"
|
|
623
|
+
? croppedFrame
|
|
624
|
+
: constrainFrame(croppedFrame, { ...dimensions, expandFullFrameSpans: true });
|
|
486
625
|
|
|
487
626
|
if (!node.props.id) {
|
|
488
627
|
return constrainedFrame;
|
|
489
628
|
}
|
|
490
629
|
|
|
491
|
-
return createFrame(constrainedFrame.lines, [{ id: node.props.id, tag: node.tag, x1: 1, x2: Math.max(1, getFrameWidth(constrainedFrame)
|
|
630
|
+
return createFrame(constrainedFrame.lines, [{ id: node.props.id, tag: node.tag, x1: 1, x2: Math.max(1, getFrameWidth(constrainedFrame)), y1: 1, y2: getFrameHeight(constrainedFrame), textStartX: 3, ...interactiveTextMetadata(value) }], constrainedFrame.cursor, constrainedFrame.spans);
|
|
492
631
|
}
|
|
493
632
|
|
|
494
633
|
function notifyLayoutContextProbe(node: TerminalElementNode, context?: TerminalRenderContext) {
|
|
@@ -524,13 +663,12 @@ function renderTableFrame(node: TerminalElementNode, context?: TerminalRenderCon
|
|
|
524
663
|
const cells = new Array<TerminalFrame>(columnCount).fill(createFrame([""])).map((cell, index) => row[index] || cell);
|
|
525
664
|
const rowHeight = cells.reduce((max, cell) => Math.max(max, getFrameHeight(cell)), 0);
|
|
526
665
|
const normalized = cells.map((cell, index) => ({
|
|
527
|
-
frame: cell,
|
|
528
|
-
width: columnWidths[index]
|
|
529
|
-
lines: [...cell.lines.map((line) => line.padEnd(columnWidths[index], " ")), ...new Array<string>(Math.max(0, rowHeight - getFrameHeight(cell))).fill(" ".repeat(columnWidths[index]))]
|
|
666
|
+
frame: fitFrame(cell, columnWidths[index], rowHeight, { expandFullFrameSpans: true }),
|
|
667
|
+
width: columnWidths[index]
|
|
530
668
|
}));
|
|
531
669
|
|
|
532
670
|
for (let rowIndex = 0; rowIndex < rowHeight; rowIndex += 1) {
|
|
533
|
-
lines.push(normalized.map((cell) => cell.lines[rowIndex]).join(" | "));
|
|
671
|
+
lines.push(normalized.map((cell) => cell.frame.lines[rowIndex]).join(" | "));
|
|
534
672
|
}
|
|
535
673
|
|
|
536
674
|
let xOffset = 0;
|
|
@@ -681,7 +819,7 @@ function renderSplitFrame(node: TerminalElementNode, context?: TerminalRenderCon
|
|
|
681
819
|
if (cellWidth <= 0 || cellHeight <= 0) {
|
|
682
820
|
continue;
|
|
683
821
|
}
|
|
684
|
-
frames.push(constrainFrame(renderSplitChildFrame(node.children[index], cellWidth, cellHeight, context), { width: cellWidth, height: cellHeight }));
|
|
822
|
+
frames.push(constrainFrame(renderSplitChildFrame(node.children[index], cellWidth, cellHeight, context), { width: cellWidth, height: cellHeight, expandFullFrameSpans: true }));
|
|
685
823
|
}
|
|
686
824
|
|
|
687
825
|
const frame = frames.length
|
|
@@ -722,7 +860,7 @@ function renderBodyFrame(children: TerminalNode[], props: TerminalElementNode["p
|
|
|
722
860
|
}
|
|
723
861
|
|
|
724
862
|
function renderFixedChildFrame(node: TerminalElementNode, width: number, height: number) {
|
|
725
|
-
return constrainFrame(renderBodyFrame(node.children, {}, { cols: width, rows: height }), { width, height });
|
|
863
|
+
return constrainFrame(renderBodyFrame(node.children, {}, { cols: width, rows: height }), { width, height, expandFullFrameSpans: true });
|
|
726
864
|
}
|
|
727
865
|
|
|
728
866
|
function renderFixedCompositionFrame(node: TerminalElementNode, width: number, height: number): TerminalFrame {
|
|
@@ -765,14 +903,14 @@ function renderFixedCompositionFrame(node: TerminalElementNode, width: number, h
|
|
|
765
903
|
const leftFrames = fixedNodes.left.map((fixed) => renderFixedChildFrame(fixed, positiveInteger(fixed.props.size, "Fixed size"), middleHeight));
|
|
766
904
|
const rightFrames = fixedNodes.right.map((fixed) => renderFixedChildFrame(fixed, positiveInteger(fixed.props.size, "Fixed size"), middleHeight));
|
|
767
905
|
const bodyFrames = bodyWidth > 0
|
|
768
|
-
? [constrainFrame(renderBodyFrame(bodyChildren, node.props, { cols: bodyWidth, rows: middleHeight }), { width: bodyWidth, height: middleHeight })]
|
|
906
|
+
? [constrainFrame(renderBodyFrame(bodyChildren, node.props, { cols: bodyWidth, rows: middleHeight }), { width: bodyWidth, height: middleHeight, expandFullRowSpans: true })]
|
|
769
907
|
: [];
|
|
770
908
|
|
|
771
|
-
middleFrames.push(constrainFrame(mergeHorizontal([...leftFrames, ...bodyFrames, ...rightFrames]), { width, height: middleHeight }));
|
|
909
|
+
middleFrames.push(constrainFrame(mergeHorizontal([...leftFrames, ...bodyFrames, ...rightFrames]), { width, height: middleHeight, expandFullRowSpans: true }));
|
|
772
910
|
}
|
|
773
911
|
|
|
774
912
|
const frame = mergeVertical([...topFrames, ...middleFrames, ...bottomFrames]);
|
|
775
|
-
return constrainFrame(frame, { width, height });
|
|
913
|
+
return constrainFrame(frame, { width, height, expandFullFrameSpans: true });
|
|
776
914
|
}
|
|
777
915
|
|
|
778
916
|
function renderStandaloneFixedFrame(node: TerminalElementNode, context?: TerminalRenderContext) {
|
|
@@ -846,7 +984,7 @@ function overlayGeometry(node: TerminalElementNode, width: number, height: numbe
|
|
|
846
984
|
|
|
847
985
|
function renderOverlayChildFrame(node: TerminalElementNode, width: number, height: number, context?: TerminalRenderContext) {
|
|
848
986
|
const geometry = overlayGeometry(node, width, height);
|
|
849
|
-
let frame = constrainFrame(renderBodyFrame(node.children, {}, { cols: geometry.width, rows: geometry.height, theme: context?.theme }), { width: geometry.width, height: geometry.height });
|
|
987
|
+
let frame = constrainFrame(renderBodyFrame(node.children, {}, { cols: geometry.width, rows: geometry.height, theme: context?.theme }), { width: geometry.width, height: geometry.height, expandFullRowSpans: true });
|
|
850
988
|
frame = addContainerStyleSpans(frame, node, resolveNodeStyle(node, context));
|
|
851
989
|
if (node.props.id && isFocusable(node)) {
|
|
852
990
|
frame = addFocusableHitbox(frame, node as TerminalFocusNode);
|
|
@@ -854,11 +992,15 @@ function renderOverlayChildFrame(node: TerminalElementNode, width: number, heigh
|
|
|
854
992
|
return { frame, geometry };
|
|
855
993
|
}
|
|
856
994
|
|
|
995
|
+
function orderedDirectOverlays(overlays: TerminalElementNode[]) {
|
|
996
|
+
return overlays.map((overlay, sourceOrder) => ({ overlay, sourceOrder })).sort((a, b) => a.sourceOrder - b.sourceOrder).map(({ overlay }) => overlay);
|
|
997
|
+
}
|
|
998
|
+
|
|
857
999
|
function applyDirectOverlays(base: TerminalFrame, overlays: TerminalElementNode[], context?: TerminalRenderContext) {
|
|
858
1000
|
let frame = base;
|
|
859
1001
|
const width = Math.max(1, getFrameWidth(base));
|
|
860
1002
|
const height = Math.max(1, getFrameHeight(base));
|
|
861
|
-
for (const overlay of overlays) {
|
|
1003
|
+
for (const overlay of orderedDirectOverlays(overlays)) {
|
|
862
1004
|
const rendered = renderOverlayChildFrame(overlay, width, height, context);
|
|
863
1005
|
frame = overlayFrame(frame, rendered.frame, rendered.geometry);
|
|
864
1006
|
}
|
|
@@ -895,7 +1037,7 @@ function renderScreenFrame(node: TerminalElementNode, context?: TerminalRenderCo
|
|
|
895
1037
|
}
|
|
896
1038
|
base = mergeVertical(parts);
|
|
897
1039
|
if (context && overlays.length) {
|
|
898
|
-
base = constrainFrame(base, { width: context.cols, height: context.rows });
|
|
1040
|
+
base = constrainFrame(base, { width: context.cols, height: context.rows, expandFullRowSpans: true });
|
|
899
1041
|
}
|
|
900
1042
|
}
|
|
901
1043
|
|
|
@@ -968,13 +1110,13 @@ function renderLogViewFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
968
1110
|
}
|
|
969
1111
|
}
|
|
970
1112
|
if (typeof innerWidth !== "undefined") {
|
|
971
|
-
frame = constrainFrame(frame, { width: innerWidth, height: innerHeight });
|
|
1113
|
+
frame = constrainFrame(frame, { width: innerWidth, height: innerHeight, expandFullFrameSpans: true });
|
|
972
1114
|
}
|
|
973
1115
|
}
|
|
974
1116
|
frame = padFrameSides(frame, padding);
|
|
975
1117
|
frame = addBorder(frame, border);
|
|
976
1118
|
if (typeof dimensions.width !== "undefined") {
|
|
977
|
-
frame = constrainFrame(frame, { width: dimensions.width, height: dimensions.height });
|
|
1119
|
+
frame = constrainFrame(frame, { width: dimensions.width, height: dimensions.height, expandFullFrameSpans: true });
|
|
978
1120
|
} else if (typeof dimensions.height !== "undefined") {
|
|
979
1121
|
frame = cropFrame(frame, 0, dimensions.height);
|
|
980
1122
|
while (typeof dimensions.height !== "undefined" && getFrameHeight(frame) < dimensions.height) {
|
|
@@ -1005,18 +1147,18 @@ function renderSeparatedRowFrame(frames: TerminalFrame[], separator = " | ") {
|
|
|
1005
1147
|
const frame = frames[index];
|
|
1006
1148
|
const width = widths[index];
|
|
1007
1149
|
for (let row = 0; row < height; row += 1) {
|
|
1008
|
-
lines[row] += (frame.lines[row] || ""
|
|
1150
|
+
lines[row] += padEndTerminalCells(frame.lines[row] || "", width);
|
|
1009
1151
|
if (index < frames.length - 1) {
|
|
1010
1152
|
lines[row] += separator;
|
|
1011
1153
|
}
|
|
1012
1154
|
}
|
|
1013
|
-
const shifted = shiftFrame(frame, xOffset, 0);
|
|
1155
|
+
const shifted = shiftFrame(fitFrame(frame, width, height, { expandFullFrameSpans: true }), xOffset, 0);
|
|
1014
1156
|
hitboxes.push(...shifted.hitboxes);
|
|
1015
1157
|
spans.push(...shifted.spans);
|
|
1016
1158
|
if (!cursor && frame.cursor) {
|
|
1017
1159
|
cursor = { x: frame.cursor.x + xOffset, y: frame.cursor.y };
|
|
1018
1160
|
}
|
|
1019
|
-
xOffset += width + (index < frames.length - 1 ? separator
|
|
1161
|
+
xOffset += width + (index < frames.length - 1 ? terminalCellWidth(separator) : 0);
|
|
1020
1162
|
}
|
|
1021
1163
|
|
|
1022
1164
|
return createFrame(lines, hitboxes, cursor, spans);
|
|
@@ -1052,7 +1194,7 @@ function renderElementFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
1052
1194
|
if (node.tag === "terminal-scroll") {
|
|
1053
1195
|
const offset = numeric(node.props.__scrollOffset, 0);
|
|
1054
1196
|
if (typeof dimensions.width !== "undefined") {
|
|
1055
|
-
frame = constrainFrame(frame, { width: dimensions.width });
|
|
1197
|
+
frame = constrainFrame(frame, { width: dimensions.width, expandFullFrameSpans: true });
|
|
1056
1198
|
}
|
|
1057
1199
|
const height = numeric(dimensions.height ?? node.props.height, getFrameHeight(frame));
|
|
1058
1200
|
frame = cropFrame(frame, offset, height || getFrameHeight(frame));
|
|
@@ -1061,7 +1203,7 @@ function renderElementFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
1061
1203
|
const spans = frame.spans.slice();
|
|
1062
1204
|
for (let index = 0; index < frame.lines.length; index += 1) {
|
|
1063
1205
|
const row = index + 1;
|
|
1064
|
-
const width = frame.lines[index]
|
|
1206
|
+
const width = terminalCellWidth(frame.lines[index]) + 1;
|
|
1065
1207
|
if (highlightRows.includes(row)) {
|
|
1066
1208
|
spans.push({ kind: "highlight", x1: 1, x2: width, y: row });
|
|
1067
1209
|
}
|
|
@@ -1084,41 +1226,114 @@ function renderElementFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
1084
1226
|
return renderLogViewFrame(node, context);
|
|
1085
1227
|
case "terminal-list": {
|
|
1086
1228
|
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
1087
|
-
const
|
|
1229
|
+
const activeIndex = clampListIndex(numeric(node.props.__activeIndex ?? node.props.__selectedIndex, 0), items.length);
|
|
1230
|
+
const selectedIndex = typeof node.props.__selectedIndex === "number" ? clampListIndex(Number(node.props.__selectedIndex), items.length) : null;
|
|
1088
1231
|
const hoveredIndex = typeof node.props.__hoveredIndex === "number" ? Number(node.props.__hoveredIndex) : -1;
|
|
1089
|
-
const range = listVirtualRange(node, items.length,
|
|
1090
|
-
const lines: string[] = [];
|
|
1091
|
-
for (let index = range.start; index < range.end; index += 1) {
|
|
1092
|
-
const item = items[index];
|
|
1093
|
-
const label = typeof node.props.renderItem === "function" ? plainText(node.props.renderItem(item, index)) : plainText(item);
|
|
1094
|
-
lines.push(label);
|
|
1095
|
-
}
|
|
1096
|
-
const visibleLines = lines.length ? lines : [""];
|
|
1232
|
+
const range = listVirtualRange(node, items.length, context);
|
|
1097
1233
|
const layoutStyle = resolveLayoutStyle("list.base", node, context);
|
|
1098
1234
|
const padding = normalizeSpacing(layoutStyle.padding, "List padding");
|
|
1099
1235
|
const border = normalizeBorder(layoutStyle.border);
|
|
1100
|
-
const
|
|
1236
|
+
const horizontalDecoration = padding.left + padding.right + (border.left ? 1 : 0) + (border.right ? 1 : 0);
|
|
1237
|
+
const wrapWidth = typeof context?.cols === "number" ? Math.max(1, context.cols - horizontalDecoration) : 1;
|
|
1238
|
+
const visibleLines: string[] = [];
|
|
1239
|
+
const itemIndexes: number[] = [];
|
|
1240
|
+
const childHitboxes: TerminalHitbox[] = [];
|
|
1241
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
1242
|
+
const item = items[index];
|
|
1243
|
+
const itemFrame = renderListItemFrame(node, item, index, index - range.visibleStart, activeIndex, selectedIndex, wrapWidth, context);
|
|
1244
|
+
const rowOffset = visibleLines.length;
|
|
1245
|
+
visibleLines.push(...itemFrame.lines);
|
|
1246
|
+
for (let row = 0; row < itemFrame.lines.length; row += 1) {
|
|
1247
|
+
itemIndexes.push(index);
|
|
1248
|
+
}
|
|
1249
|
+
childHitboxes.push(...shiftFrame(itemFrame, 0, rowOffset).hitboxes);
|
|
1250
|
+
}
|
|
1251
|
+
if (!visibleLines.length) {
|
|
1252
|
+
visibleLines.push("");
|
|
1253
|
+
itemIndexes.push(0);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const frameHeight = node.props.virtualized && node.props.wrap === true
|
|
1257
|
+
? positiveDimension(node.props.height, "height") ?? (typeof context?.rows === "number" ? Math.max(1, context.rows) : undefined)
|
|
1258
|
+
: undefined;
|
|
1259
|
+
const contentHeight = typeof frameHeight === "number"
|
|
1260
|
+
? Math.max(1, frameHeight - padding.top - padding.bottom - (border.top ? 1 : 0) - (border.bottom ? 1 : 0))
|
|
1261
|
+
: visibleLines.length;
|
|
1262
|
+
let visibleLineStart = 0;
|
|
1263
|
+
if (node.props.virtualized && node.props.wrap === true && visibleLines.length > contentHeight) {
|
|
1264
|
+
const activeLineIndex = itemIndexes.findIndex((sourceIndex) => sourceIndex === activeIndex);
|
|
1265
|
+
if (activeLineIndex >= contentHeight) {
|
|
1266
|
+
visibleLineStart = activeLineIndex - contentHeight + 1;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const frameLines = visibleLines.slice(visibleLineStart, visibleLineStart + contentHeight);
|
|
1270
|
+
const frameItemIndexes = itemIndexes.slice(visibleLineStart, visibleLineStart + contentHeight);
|
|
1271
|
+
const frameChildHitboxes = childHitboxes
|
|
1272
|
+
.filter((box) => box.y2 > visibleLineStart && box.y1 <= visibleLineStart + contentHeight)
|
|
1273
|
+
.map((box) => ({
|
|
1274
|
+
...box,
|
|
1275
|
+
y1: Math.max(1, box.y1 - visibleLineStart),
|
|
1276
|
+
y2: Math.min(contentHeight, box.y2 - visibleLineStart),
|
|
1277
|
+
contentY: typeof box.contentY === "number" ? Math.max(1, box.contentY - visibleLineStart) : undefined
|
|
1278
|
+
}));
|
|
1279
|
+
|
|
1280
|
+
const decorated = addBorder(padFrameSides(createFrame(frameLines, frameChildHitboxes), padding), border);
|
|
1101
1281
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
1102
1282
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
1103
1283
|
const itemY = 1 + (border.top ? 1 : 0) + padding.top;
|
|
1104
1284
|
const spans: TerminalStyleSpan[] = [];
|
|
1105
|
-
for (let index = 0; index <
|
|
1106
|
-
const sourceIndex =
|
|
1285
|
+
for (let index = 0; index < frameLines.length; index += 1) {
|
|
1286
|
+
const sourceIndex = frameItemIndexes[index];
|
|
1107
1287
|
const y = itemY + index;
|
|
1108
|
-
spans.push({ kind: "list.base", x1: 1, x2: width + 1, y });
|
|
1109
|
-
if (sourceIndex === selectedIndex) {
|
|
1110
|
-
spans.push({ kind: "list.
|
|
1288
|
+
spans.push(markFullRowSpan({ kind: "list.base", x1: 1, x2: width + 1, y }));
|
|
1289
|
+
if (selectedIndex !== null && selectedIndex !== activeIndex && sourceIndex === selectedIndex) {
|
|
1290
|
+
spans.push(markFullRowSpan({ kind: "list.selected", x1: 1, x2: width + 1, y }));
|
|
1291
|
+
}
|
|
1292
|
+
if (sourceIndex === activeIndex) {
|
|
1293
|
+
spans.push(markFullRowSpan({ kind: "list.current", x1: 1, x2: width + 1, y }));
|
|
1111
1294
|
}
|
|
1112
1295
|
if (sourceIndex === hoveredIndex) {
|
|
1113
|
-
spans.push({ kind: "list.hover", x1: 1, x2: width + 1, y });
|
|
1296
|
+
spans.push(markFullRowSpan({ kind: "list.hover", x1: 1, x2: width + 1, y }));
|
|
1114
1297
|
}
|
|
1115
1298
|
}
|
|
1116
|
-
const
|
|
1117
|
-
const
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1299
|
+
const listHitboxes: TerminalHitbox[] = [];
|
|
1300
|
+
const itemHitboxes: TerminalHitbox[] = [];
|
|
1301
|
+
if (node.props.id) {
|
|
1302
|
+
listHitboxes.push({
|
|
1303
|
+
id: node.props.id,
|
|
1304
|
+
tag: node.tag,
|
|
1305
|
+
x1: 1,
|
|
1306
|
+
x2: width,
|
|
1307
|
+
y1: 1,
|
|
1308
|
+
y2: Math.min(height, typeof frameHeight === "number" ? frameHeight : height),
|
|
1309
|
+
itemOffset: range.start,
|
|
1310
|
+
itemIndexes: frameItemIndexes,
|
|
1311
|
+
contentY: itemY
|
|
1312
|
+
});
|
|
1313
|
+
let itemStart = 0;
|
|
1314
|
+
while (itemStart < frameItemIndexes.length) {
|
|
1315
|
+
const sourceIndex = frameItemIndexes[itemStart];
|
|
1316
|
+
let itemEnd = itemStart;
|
|
1317
|
+
while (itemEnd + 1 < frameItemIndexes.length && frameItemIndexes[itemEnd + 1] === sourceIndex) {
|
|
1318
|
+
itemEnd += 1;
|
|
1319
|
+
}
|
|
1320
|
+
itemHitboxes.push({
|
|
1321
|
+
id: node.props.id,
|
|
1322
|
+
tag: node.tag,
|
|
1323
|
+
x1: 1,
|
|
1324
|
+
x2: width,
|
|
1325
|
+
y1: itemY + itemStart,
|
|
1326
|
+
y2: itemY + itemEnd,
|
|
1327
|
+
itemOffset: range.start,
|
|
1328
|
+
__listItemIndex: sourceIndex,
|
|
1329
|
+
itemIndexes: new Array(itemEnd - itemStart + 1).fill(sourceIndex)
|
|
1330
|
+
});
|
|
1331
|
+
itemStart = itemEnd + 1;
|
|
1332
|
+
}
|
|
1120
1333
|
}
|
|
1121
|
-
|
|
1334
|
+
const frame = createFrame(decorated.lines, [...listHitboxes, ...itemHitboxes, ...decorated.hitboxes], decorated.cursor, spans);
|
|
1335
|
+
const styled = addFullFrameSpans(frame, resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
1336
|
+
return typeof frameHeight === "number" ? constrainFrame(styled, { height: frameHeight, expandFullFrameSpans: true }) : styled;
|
|
1122
1337
|
}
|
|
1123
1338
|
case "terminal-table":
|
|
1124
1339
|
return renderTableFrame(node, context);
|
|
@@ -1152,7 +1367,7 @@ function renderElementFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
1152
1367
|
const decorated = addBorder(createFrame([line]), inputBorder);
|
|
1153
1368
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
1154
1369
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
1155
|
-
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX,
|
|
1370
|
+
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, ...interactiveTextMetadata(stringValue) }] : [];
|
|
1156
1371
|
const spans = fullFrameSpans(["input.base"], width, height);
|
|
1157
1372
|
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
1158
1373
|
}
|
|
@@ -1161,19 +1376,19 @@ function renderElementFrame(node: TerminalElementNode, context?: TerminalRenderC
|
|
|
1161
1376
|
const decorated = addBorder(createFrame([rendered.line], [], rendered.cursor, rendered.spans), inputBorder);
|
|
1162
1377
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
1163
1378
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
1164
|
-
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX,
|
|
1379
|
+
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, ...interactiveTextMetadata(stringValue) }] : [];
|
|
1165
1380
|
const spans = [...fullFrameSpans(["input.base", "input.focus"], width, height), ...decorated.spans];
|
|
1166
1381
|
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
1167
1382
|
}
|
|
1168
1383
|
case "terminal-editor":
|
|
1169
|
-
return addFullFrameSpans(renderEditorFrame(node), resolveNodeStyle(node, context).spanKinds);
|
|
1384
|
+
return addFullFrameSpans(renderEditorFrame(node, context), resolveNodeStyle(node, context).spanKinds);
|
|
1170
1385
|
case "terminal-button": {
|
|
1171
1386
|
const label = typeof node.props.label !== "undefined" ? plainText(node.props.label) : plainText(node.children.map(textContent).join(""));
|
|
1172
1387
|
const layoutStyle = resolveLayoutStyle("button.base", node, context);
|
|
1173
1388
|
const decorated = decoratedControlFrame([String(label)], layoutStyle);
|
|
1174
1389
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
1175
1390
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
1176
|
-
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height }] : [];
|
|
1391
|
+
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, __pressHandler: typeof node.props.onpress === "function" ? node.props.onpress : undefined }] : [];
|
|
1177
1392
|
const kinds = ["button.base", ...nodeStates(node).map((state) => `button.${state}`)];
|
|
1178
1393
|
const spans = fullFrameSpans(kinds, width, height);
|
|
1179
1394
|
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
package/src/runtime.ts
CHANGED
|
@@ -144,15 +144,23 @@ function normalizeValyrianInput(input: any): any {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
const props = { ...((input.props && input.props.__terminalProps) || input.props || {}) };
|
|
147
|
-
const
|
|
148
|
-
|
|
147
|
+
const rawChildren = Array.isArray(input.children) ? input.children : [];
|
|
148
|
+
const isListComponentWithRenderChild = typeof input.tag === "function" && input.tag.name === "TerminalList" && rawChildren.length === 1 && typeof rawChildren[0] === "function";
|
|
149
|
+
if (isListComponentWithRenderChild) {
|
|
150
|
+
props.__childrenRenderer = rawChildren[0];
|
|
151
|
+
}
|
|
152
|
+
const tag = typeof input.tag === "string" ? input.tag : wrapComponent(input.tag);
|
|
153
|
+
const children = isListComponentWithRenderChild || (tag === "terminal-list" && rawChildren.length === 1 && typeof rawChildren[0] === "function")
|
|
154
|
+
? []
|
|
155
|
+
: rawChildren.map((child) => {
|
|
149
156
|
if (props["v-for"] && typeof child === "function") {
|
|
150
157
|
return (...args: any[]) => normalizeValyrianInput(child(...args));
|
|
151
158
|
}
|
|
152
159
|
return normalizeValyrianInput(child);
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
});
|
|
161
|
+
if (tag === "terminal-list" && rawChildren.length === 1 && typeof rawChildren[0] === "function") {
|
|
162
|
+
props.__childrenRenderer = rawChildren[0];
|
|
163
|
+
}
|
|
156
164
|
if (typeof tag === "string" && isTerminalTag(tag)) {
|
|
157
165
|
const terminalProps = { ...props };
|
|
158
166
|
if (typeof props.style === "object") {
|