@valyrianjs/terminal 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +12 -0
- package/dist/ansi.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +6 -2
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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.map +1 -1
- package/dist/layout.js +2 -1
- 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 +30 -16
- package/dist/mouse.js.map +1 -1
- package/dist/primitives.d.ts +8 -3
- 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 +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +290 -51
- package/dist/render.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +13 -11
- package/dist/runtime.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +434 -65
- package/dist/session.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 +61 -13
- package/dist/types.d.ts.map +1 -1
- package/docs/api-reference.md +40 -28
- package/docs/cookbook.md +2 -2
- package/docs/core-concepts.md +1 -1
- package/docs/interaction-model.md +18 -6
- package/docs/primitive-gallery.md +19 -10
- package/llms-full.txt +80 -47
- package/package.json +1 -1
- package/src/ansi.ts +12 -0
- package/src/events.ts +4 -2
- package/src/index.ts +3 -0
- package/src/keymap.ts +4 -2
- package/src/layout.ts +2 -1
- package/src/mouse.ts +31 -15
- package/src/primitives.ts +15 -5
- package/src/render.ts +320 -52
- package/src/runtime.ts +13 -11
- package/src/session.ts +469 -59
- package/src/theme.ts +3 -0
- package/src/tree.ts +19 -4
- package/src/types.ts +72 -12
package/dist/render.js
CHANGED
|
@@ -5,6 +5,22 @@ import { isFocusable, textContent } from "./tree.js";
|
|
|
5
5
|
import { renderValyrianTerminal } from "./runtime.js";
|
|
6
6
|
import { plainText } from "./text.js";
|
|
7
7
|
import { resolveTerminalStyle } from "./theme.js";
|
|
8
|
+
function validateRenderContextDimension(name, value) {
|
|
9
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
10
|
+
throw new RangeError(`Invalid render context ${name}: expected an integer >= 1`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function validateRenderContext(context) {
|
|
15
|
+
if (typeof context === "undefined") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
cols: validateRenderContextDimension("cols", context.cols),
|
|
20
|
+
rows: validateRenderContextDimension("rows", context.rows),
|
|
21
|
+
theme: context.theme
|
|
22
|
+
};
|
|
23
|
+
}
|
|
8
24
|
const VISUAL_STATE_ORDER = [
|
|
9
25
|
"disabled", "readonly", "loading", "empty", "muted", "error", "warning", "success", "invalid", "valid", "placeholder", "selection", "selected", "current", "expanded", "collapsed", "checked", "unchecked", "indeterminate", "editing", "submitted", "dragging", "dropTarget", "capturing", "focus", "hover", "pressed"
|
|
10
26
|
];
|
|
@@ -86,6 +102,18 @@ function mergeStyleDefinitions(base, next) {
|
|
|
86
102
|
function styleSpan(kind, style) {
|
|
87
103
|
return typeof style === "undefined" ? { kind } : { kind, style };
|
|
88
104
|
}
|
|
105
|
+
const BASE_STYLE_KIND_BY_TAG = {
|
|
106
|
+
"terminal-button": "button.base",
|
|
107
|
+
"terminal-input": "input.base",
|
|
108
|
+
"terminal-editor": "editor.base",
|
|
109
|
+
"terminal-list": "list.base",
|
|
110
|
+
"terminal-scroll": "scroll.base",
|
|
111
|
+
"terminal-log-view": "log.base",
|
|
112
|
+
"terminal-overlay": "overlay.base"
|
|
113
|
+
};
|
|
114
|
+
function baseStyleKindForNode(node) {
|
|
115
|
+
return BASE_STYLE_KIND_BY_TAG[node.tag];
|
|
116
|
+
}
|
|
89
117
|
function nodeStates(node) {
|
|
90
118
|
const declared = Array.isArray(node.props.state) ? node.props.state : typeof node.props.state === "string" ? [node.props.state] : [];
|
|
91
119
|
const states = new Set();
|
|
@@ -109,7 +137,7 @@ function resolveLayoutStyle(baseKind, node, context) {
|
|
|
109
137
|
return mergeStyleDefinitions(resolveTerminalStyle(baseKind, context?.theme), resolveTerminalStyle(node.props.style, context?.theme));
|
|
110
138
|
}
|
|
111
139
|
function resolveNodeLayoutStyle(node, context) {
|
|
112
|
-
return resolveTerminalStyle(node.props.style, context?.theme);
|
|
140
|
+
return mergeStyleDefinitions(resolveTerminalStyle(baseStyleKindForNode(node), context?.theme), resolveTerminalStyle(node.props.style, context?.theme));
|
|
113
141
|
}
|
|
114
142
|
function decoratedControlFrame(content, style) {
|
|
115
143
|
const padding = normalizeSpacing(style?.padding, "Control padding");
|
|
@@ -125,11 +153,18 @@ function fullFrameSpans(kinds, width, height) {
|
|
|
125
153
|
}
|
|
126
154
|
return spans;
|
|
127
155
|
}
|
|
128
|
-
function resolveNodeStyle(node, context) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
function resolveNodeStyle(node, context, options = {}) {
|
|
157
|
+
const baseKind = options.includeBase === false ? undefined : baseStyleKindForNode(node);
|
|
158
|
+
let style = resolveTerminalStyle(baseKind, context?.theme);
|
|
159
|
+
const spanKinds = typeof baseKind === "undefined" ? [] : [styleSpan(baseKind)];
|
|
160
|
+
const explicitStyle = resolveTerminalStyle(node.props.style, context?.theme);
|
|
161
|
+
if (typeof node.props.style === "string") {
|
|
162
|
+
spanKinds.push(styleSpan(node.props.style));
|
|
163
|
+
}
|
|
164
|
+
else if (explicitStyle) {
|
|
165
|
+
spanKinds.push(styleSpan("#style", explicitStyle));
|
|
166
|
+
}
|
|
167
|
+
style = mergeStyleDefinitions(style, explicitStyle);
|
|
133
168
|
for (const state of nodeStates(node)) {
|
|
134
169
|
const stateStyle = node.props.styles?.[state];
|
|
135
170
|
if (stateStyle) {
|
|
@@ -196,21 +231,104 @@ function addFullFrameSpans(frame, kinds) {
|
|
|
196
231
|
}
|
|
197
232
|
return createFrame(frame.lines, frame.hitboxes, frame.cursor, spans);
|
|
198
233
|
}
|
|
199
|
-
function
|
|
234
|
+
function listViewportRows(node, itemCount, context) {
|
|
235
|
+
const explicitHeight = positiveDimension(node.props.height, "height");
|
|
236
|
+
const viewportSourceRows = explicitHeight ?? context?.rows ?? (itemCount || 1);
|
|
237
|
+
return Math.max(1, Math.min(itemCount || 1, positiveInteger(viewportSourceRows, "List viewport height")));
|
|
238
|
+
}
|
|
239
|
+
function clampListIndex(index, itemCount) {
|
|
240
|
+
if (itemCount <= 0) {
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
return Math.max(0, Math.min(itemCount - 1, index));
|
|
244
|
+
}
|
|
245
|
+
function listVirtualRange(node, itemCount, context) {
|
|
200
246
|
if (!node.props.virtualized) {
|
|
201
|
-
return { start: 0, end: itemCount };
|
|
247
|
+
return { start: 0, end: itemCount, visibleStart: 0, viewportRows: itemCount || 1 };
|
|
202
248
|
}
|
|
203
249
|
if (typeof node.props.itemHeight !== "undefined" && node.props.itemHeight !== 1) {
|
|
204
250
|
throw new RangeError("List itemHeight must be 1");
|
|
205
251
|
}
|
|
206
252
|
const overscan = nonNegativeInteger(node.props.overscan, "List overscan");
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
const visibleStart = Math.max(0, Math.min(selected, selected - viewportRows + 1));
|
|
253
|
+
const viewportRows = listViewportRows(node, itemCount, context);
|
|
254
|
+
const maxOffset = Math.max(0, itemCount - viewportRows);
|
|
255
|
+
let visibleStart = Math.max(0, Math.min(maxOffset, nonNegativeInteger(node.props.__scrollOffset, "List viewport offset")));
|
|
211
256
|
const start = Math.max(0, visibleStart - overscan);
|
|
212
257
|
const end = Math.min(itemCount, visibleStart + viewportRows + overscan);
|
|
213
|
-
return { start, end };
|
|
258
|
+
return { start, end, visibleStart, viewportRows };
|
|
259
|
+
}
|
|
260
|
+
function listItemKey(node, item, index) {
|
|
261
|
+
if (typeof node.props.itemKey === "function") {
|
|
262
|
+
const key = node.props.itemKey(item, index);
|
|
263
|
+
if (typeof key !== "string" && typeof key !== "number") {
|
|
264
|
+
throw new RangeError("List itemKey must return a string or number");
|
|
265
|
+
}
|
|
266
|
+
return String(key);
|
|
267
|
+
}
|
|
268
|
+
return String(index);
|
|
269
|
+
}
|
|
270
|
+
function listItemRenderer(node) {
|
|
271
|
+
if (typeof node.props.__childrenRenderer === "function") {
|
|
272
|
+
return { type: "children", render: node.props.__childrenRenderer };
|
|
273
|
+
}
|
|
274
|
+
if (typeof node.props.renderItem === "function") {
|
|
275
|
+
return { type: "renderItem", render: node.props.renderItem };
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
function wrapPlainText(value, width) {
|
|
280
|
+
if (!Number.isFinite(width) || !Number.isInteger(width) || width <= 0) {
|
|
281
|
+
return [""];
|
|
282
|
+
}
|
|
283
|
+
const rows = [];
|
|
284
|
+
const sourceRows = value.split("\n");
|
|
285
|
+
for (const sourceRow of sourceRows) {
|
|
286
|
+
if (sourceRow.length === 0) {
|
|
287
|
+
rows.push("");
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
let remaining = sourceRow;
|
|
291
|
+
while (remaining.length > width) {
|
|
292
|
+
const slice = remaining.slice(0, width);
|
|
293
|
+
const breakAt = slice.lastIndexOf(" ");
|
|
294
|
+
if (breakAt > 0 && breakAt >= Math.floor(width * 0.6)) {
|
|
295
|
+
rows.push(remaining.slice(0, breakAt));
|
|
296
|
+
remaining = remaining.slice(breakAt + 1);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
rows.push(slice);
|
|
300
|
+
remaining = remaining.slice(width);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
rows.push(remaining);
|
|
304
|
+
}
|
|
305
|
+
return rows.length ? rows : [""];
|
|
306
|
+
}
|
|
307
|
+
function renderListItemFrame(node, item, index, viewportIndex, activeIndex, selectedIndex, wrapWidth, context) {
|
|
308
|
+
const key = listItemKey(node, item, index);
|
|
309
|
+
const renderer = listItemRenderer(node);
|
|
310
|
+
if (!renderer) {
|
|
311
|
+
const label = plainText(item);
|
|
312
|
+
return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
|
|
313
|
+
}
|
|
314
|
+
const ctx = {
|
|
315
|
+
index,
|
|
316
|
+
key,
|
|
317
|
+
active: index === activeIndex,
|
|
318
|
+
selected: selectedIndex !== null && index === selectedIndex,
|
|
319
|
+
viewportIndex,
|
|
320
|
+
item
|
|
321
|
+
};
|
|
322
|
+
const rendered = renderer.type === "children" ? renderer.render(item, ctx) : renderer.render(item, index);
|
|
323
|
+
if (typeof rendered === "string" || typeof rendered === "number") {
|
|
324
|
+
const label = plainText(rendered);
|
|
325
|
+
return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
|
|
326
|
+
}
|
|
327
|
+
const frame = mergeVertical(renderValyrianTerminal(rendered).map((child) => renderTerminalFrame(child, context)));
|
|
328
|
+
if (node.props.wrap === true && frame.hitboxes.length === 0) {
|
|
329
|
+
return createFrame(frame.lines.flatMap((line) => wrapPlainText(line, wrapWidth)));
|
|
330
|
+
}
|
|
331
|
+
return frame;
|
|
214
332
|
}
|
|
215
333
|
function fixedPosition(value) {
|
|
216
334
|
if (value === "top" || value === "bottom" || value === "left" || value === "right") {
|
|
@@ -663,27 +781,73 @@ function renderStandaloneFixedFrame(node, context) {
|
|
|
663
781
|
? constrainFrame(frame, { height: size })
|
|
664
782
|
: constrainFrame(frame, { width: size });
|
|
665
783
|
}
|
|
666
|
-
function
|
|
784
|
+
function overlayMarginValue(value, axisSize, label) {
|
|
785
|
+
if (typeof value === "number") {
|
|
786
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
787
|
+
throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
|
|
788
|
+
}
|
|
789
|
+
return value;
|
|
790
|
+
}
|
|
791
|
+
if (typeof value === "string") {
|
|
792
|
+
const match = value.match(/^(\d+(?:\.\d+)?)%$/);
|
|
793
|
+
if (!match) {
|
|
794
|
+
throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
|
|
795
|
+
}
|
|
796
|
+
const percent = Number(match[1]);
|
|
797
|
+
if (!Number.isFinite(percent) || percent < 0) {
|
|
798
|
+
throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
|
|
799
|
+
}
|
|
800
|
+
return Math.round(axisSize * percent / 100);
|
|
801
|
+
}
|
|
802
|
+
throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
|
|
803
|
+
}
|
|
804
|
+
function overlayMargins(margin, width, height) {
|
|
805
|
+
if (typeof margin === "number" || typeof margin === "string") {
|
|
806
|
+
const x = overlayMarginValue(margin, width, "Overlay margin");
|
|
807
|
+
const y = overlayMarginValue(margin, height, "Overlay margin");
|
|
808
|
+
return { x, y };
|
|
809
|
+
}
|
|
810
|
+
if (margin && typeof margin === "object" && !Array.isArray(margin)) {
|
|
811
|
+
const axes = margin;
|
|
812
|
+
return {
|
|
813
|
+
x: overlayMarginValue(axes.x, width, "Overlay margin x"),
|
|
814
|
+
y: overlayMarginValue(axes.y, height, "Overlay margin y")
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
throw new RangeError("Overlay margin is required");
|
|
818
|
+
}
|
|
819
|
+
function overlayGeometry(node, width, height) {
|
|
820
|
+
const margin = overlayMargins(node.props.margin, width, height);
|
|
821
|
+
const overlayWidth = width - margin.x * 2;
|
|
822
|
+
const overlayHeight = height - margin.y * 2;
|
|
823
|
+
if (overlayWidth < 1 || overlayHeight < 1) {
|
|
824
|
+
throw new RangeError("Overlay margin leaves no renderable area");
|
|
825
|
+
}
|
|
667
826
|
return {
|
|
668
|
-
x:
|
|
669
|
-
y:
|
|
670
|
-
width:
|
|
671
|
-
height:
|
|
827
|
+
x: margin.x + 1,
|
|
828
|
+
y: margin.y + 1,
|
|
829
|
+
width: overlayWidth,
|
|
830
|
+
height: overlayHeight
|
|
672
831
|
};
|
|
673
832
|
}
|
|
674
|
-
function renderOverlayChildFrame(node, context) {
|
|
675
|
-
const geometry = overlayGeometry(node);
|
|
676
|
-
let frame = constrainFrame(renderBodyFrame(node.children, {}, { cols: geometry.width, rows: geometry.height }), { width: geometry.width, height: geometry.height });
|
|
833
|
+
function renderOverlayChildFrame(node, width, height, context) {
|
|
834
|
+
const geometry = overlayGeometry(node, width, height);
|
|
835
|
+
let frame = constrainFrame(renderBodyFrame(node.children, {}, { cols: geometry.width, rows: geometry.height, theme: context?.theme }), { width: geometry.width, height: geometry.height });
|
|
677
836
|
frame = addContainerStyleSpans(frame, node, resolveNodeStyle(node, context));
|
|
678
837
|
if (node.props.id && isFocusable(node)) {
|
|
679
838
|
frame = addFocusableHitbox(frame, node);
|
|
680
839
|
}
|
|
681
840
|
return { frame, geometry };
|
|
682
841
|
}
|
|
842
|
+
function orderedDirectOverlays(overlays) {
|
|
843
|
+
return overlays.map((overlay, sourceOrder) => ({ overlay, sourceOrder })).sort((a, b) => a.sourceOrder - b.sourceOrder).map(({ overlay }) => overlay);
|
|
844
|
+
}
|
|
683
845
|
function applyDirectOverlays(base, overlays, context) {
|
|
684
846
|
let frame = base;
|
|
685
|
-
|
|
686
|
-
|
|
847
|
+
const width = Math.max(1, getFrameWidth(base));
|
|
848
|
+
const height = Math.max(1, getFrameHeight(base));
|
|
849
|
+
for (const overlay of orderedDirectOverlays(overlays)) {
|
|
850
|
+
const rendered = renderOverlayChildFrame(overlay, width, height, context);
|
|
687
851
|
frame = overlayFrame(frame, rendered.frame, rendered.geometry);
|
|
688
852
|
}
|
|
689
853
|
return frame;
|
|
@@ -741,8 +905,11 @@ function renderPaneFrame(node, context) {
|
|
|
741
905
|
}
|
|
742
906
|
return overlays.length ? applyDirectOverlays(frame, overlays, context) : frame;
|
|
743
907
|
}
|
|
744
|
-
function renderStandaloneOverlayFrame(node) {
|
|
745
|
-
|
|
908
|
+
function renderStandaloneOverlayFrame(node, context) {
|
|
909
|
+
if (!context) {
|
|
910
|
+
throw new RangeError("Standalone Overlay requires exact render context dimensions");
|
|
911
|
+
}
|
|
912
|
+
const rendered = renderOverlayChildFrame(node, context.cols, context.rows, context);
|
|
746
913
|
return rendered.frame;
|
|
747
914
|
}
|
|
748
915
|
function renderLogViewFrame(node, context) {
|
|
@@ -882,46 +1049,117 @@ function renderElementFrame(node, context) {
|
|
|
882
1049
|
case "terminal-fixed":
|
|
883
1050
|
return renderStandaloneFixedFrame(node, context);
|
|
884
1051
|
case "terminal-overlay":
|
|
885
|
-
return renderStandaloneOverlayFrame(node);
|
|
1052
|
+
return renderStandaloneOverlayFrame(node, context);
|
|
886
1053
|
case "terminal-log-view":
|
|
887
1054
|
return renderLogViewFrame(node, context);
|
|
888
1055
|
case "terminal-list": {
|
|
889
1056
|
const items = Array.isArray(node.props.items) ? node.props.items : [];
|
|
890
|
-
const
|
|
1057
|
+
const activeIndex = clampListIndex(numeric(node.props.__activeIndex ?? node.props.__selectedIndex, 0), items.length);
|
|
1058
|
+
const selectedIndex = typeof node.props.__selectedIndex === "number" ? clampListIndex(Number(node.props.__selectedIndex), items.length) : null;
|
|
891
1059
|
const hoveredIndex = typeof node.props.__hoveredIndex === "number" ? Number(node.props.__hoveredIndex) : -1;
|
|
892
|
-
const range = listVirtualRange(node, items.length,
|
|
893
|
-
const lines = [];
|
|
894
|
-
for (let index = range.start; index < range.end; index += 1) {
|
|
895
|
-
const item = items[index];
|
|
896
|
-
const label = typeof node.props.renderItem === "function" ? plainText(node.props.renderItem(item, index)) : plainText(item);
|
|
897
|
-
lines.push(label);
|
|
898
|
-
}
|
|
899
|
-
const visibleLines = lines.length ? lines : [""];
|
|
1060
|
+
const range = listVirtualRange(node, items.length, context);
|
|
900
1061
|
const layoutStyle = resolveLayoutStyle("list.base", node, context);
|
|
901
1062
|
const padding = normalizeSpacing(layoutStyle.padding, "List padding");
|
|
902
1063
|
const border = normalizeBorder(layoutStyle.border);
|
|
903
|
-
const
|
|
1064
|
+
const horizontalDecoration = padding.left + padding.right + (border.left ? 1 : 0) + (border.right ? 1 : 0);
|
|
1065
|
+
const wrapWidth = typeof context?.cols === "number" ? Math.max(1, context.cols - horizontalDecoration) : 1;
|
|
1066
|
+
const visibleLines = [];
|
|
1067
|
+
const itemIndexes = [];
|
|
1068
|
+
const childHitboxes = [];
|
|
1069
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
1070
|
+
const item = items[index];
|
|
1071
|
+
const itemFrame = renderListItemFrame(node, item, index, index - range.visibleStart, activeIndex, selectedIndex, wrapWidth, context);
|
|
1072
|
+
const rowOffset = visibleLines.length;
|
|
1073
|
+
visibleLines.push(...itemFrame.lines);
|
|
1074
|
+
for (let row = 0; row < itemFrame.lines.length; row += 1) {
|
|
1075
|
+
itemIndexes.push(index);
|
|
1076
|
+
}
|
|
1077
|
+
childHitboxes.push(...shiftFrame(itemFrame, 0, rowOffset).hitboxes);
|
|
1078
|
+
}
|
|
1079
|
+
if (!visibleLines.length) {
|
|
1080
|
+
visibleLines.push("");
|
|
1081
|
+
itemIndexes.push(0);
|
|
1082
|
+
}
|
|
1083
|
+
const frameHeight = node.props.virtualized && node.props.wrap === true
|
|
1084
|
+
? positiveDimension(node.props.height, "height") ?? (typeof context?.rows === "number" ? Math.max(1, context.rows) : undefined)
|
|
1085
|
+
: undefined;
|
|
1086
|
+
const contentHeight = typeof frameHeight === "number"
|
|
1087
|
+
? Math.max(1, frameHeight - padding.top - padding.bottom - (border.top ? 1 : 0) - (border.bottom ? 1 : 0))
|
|
1088
|
+
: visibleLines.length;
|
|
1089
|
+
let visibleLineStart = 0;
|
|
1090
|
+
if (node.props.virtualized && node.props.wrap === true && visibleLines.length > contentHeight) {
|
|
1091
|
+
const activeLineIndex = itemIndexes.findIndex((sourceIndex) => sourceIndex === activeIndex);
|
|
1092
|
+
if (activeLineIndex >= contentHeight) {
|
|
1093
|
+
visibleLineStart = activeLineIndex - contentHeight + 1;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const frameLines = visibleLines.slice(visibleLineStart, visibleLineStart + contentHeight);
|
|
1097
|
+
const frameItemIndexes = itemIndexes.slice(visibleLineStart, visibleLineStart + contentHeight);
|
|
1098
|
+
const frameChildHitboxes = childHitboxes
|
|
1099
|
+
.filter((box) => box.y2 > visibleLineStart && box.y1 <= visibleLineStart + contentHeight)
|
|
1100
|
+
.map((box) => ({
|
|
1101
|
+
...box,
|
|
1102
|
+
y1: Math.max(1, box.y1 - visibleLineStart),
|
|
1103
|
+
y2: Math.min(contentHeight, box.y2 - visibleLineStart),
|
|
1104
|
+
contentY: typeof box.contentY === "number" ? Math.max(1, box.contentY - visibleLineStart) : undefined
|
|
1105
|
+
}));
|
|
1106
|
+
const decorated = addBorder(padFrameSides(createFrame(frameLines, frameChildHitboxes), padding), border);
|
|
904
1107
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
905
1108
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
906
1109
|
const itemY = 1 + (border.top ? 1 : 0) + padding.top;
|
|
907
1110
|
const spans = [];
|
|
908
|
-
for (let index = 0; index <
|
|
909
|
-
const sourceIndex =
|
|
1111
|
+
for (let index = 0; index < frameLines.length; index += 1) {
|
|
1112
|
+
const sourceIndex = frameItemIndexes[index];
|
|
910
1113
|
const y = itemY + index;
|
|
911
1114
|
spans.push({ kind: "list.base", x1: 1, x2: width + 1, y });
|
|
912
|
-
if (sourceIndex === selectedIndex) {
|
|
1115
|
+
if (selectedIndex !== null && selectedIndex !== activeIndex && sourceIndex === selectedIndex) {
|
|
1116
|
+
spans.push({ kind: "list.selected", x1: 1, x2: width + 1, y });
|
|
1117
|
+
}
|
|
1118
|
+
if (sourceIndex === activeIndex) {
|
|
913
1119
|
spans.push({ kind: "list.current", x1: 1, x2: width + 1, y });
|
|
914
1120
|
}
|
|
915
1121
|
if (sourceIndex === hoveredIndex) {
|
|
916
1122
|
spans.push({ kind: "list.hover", x1: 1, x2: width + 1, y });
|
|
917
1123
|
}
|
|
918
1124
|
}
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
922
|
-
|
|
1125
|
+
const listHitboxes = [];
|
|
1126
|
+
const itemHitboxes = [];
|
|
1127
|
+
if (node.props.id) {
|
|
1128
|
+
listHitboxes.push({
|
|
1129
|
+
id: node.props.id,
|
|
1130
|
+
tag: node.tag,
|
|
1131
|
+
x1: 1,
|
|
1132
|
+
x2: width,
|
|
1133
|
+
y1: 1,
|
|
1134
|
+
y2: Math.min(height, typeof frameHeight === "number" ? frameHeight : height),
|
|
1135
|
+
itemOffset: range.start,
|
|
1136
|
+
itemIndexes: frameItemIndexes,
|
|
1137
|
+
contentY: itemY
|
|
1138
|
+
});
|
|
1139
|
+
let itemStart = 0;
|
|
1140
|
+
while (itemStart < frameItemIndexes.length) {
|
|
1141
|
+
const sourceIndex = frameItemIndexes[itemStart];
|
|
1142
|
+
let itemEnd = itemStart;
|
|
1143
|
+
while (itemEnd + 1 < frameItemIndexes.length && frameItemIndexes[itemEnd + 1] === sourceIndex) {
|
|
1144
|
+
itemEnd += 1;
|
|
1145
|
+
}
|
|
1146
|
+
itemHitboxes.push({
|
|
1147
|
+
id: node.props.id,
|
|
1148
|
+
tag: node.tag,
|
|
1149
|
+
x1: 1,
|
|
1150
|
+
x2: width,
|
|
1151
|
+
y1: itemY + itemStart,
|
|
1152
|
+
y2: itemY + itemEnd,
|
|
1153
|
+
itemOffset: range.start,
|
|
1154
|
+
__listItemIndex: sourceIndex,
|
|
1155
|
+
itemIndexes: new Array(itemEnd - itemStart + 1).fill(sourceIndex)
|
|
1156
|
+
});
|
|
1157
|
+
itemStart = itemEnd + 1;
|
|
1158
|
+
}
|
|
923
1159
|
}
|
|
924
|
-
|
|
1160
|
+
const frame = createFrame(decorated.lines, [...listHitboxes, ...itemHitboxes, ...decorated.hitboxes], decorated.cursor, spans);
|
|
1161
|
+
const styled = addFullFrameSpans(frame, resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
1162
|
+
return typeof frameHeight === "number" ? constrainFrame(styled, { height: frameHeight }) : styled;
|
|
925
1163
|
}
|
|
926
1164
|
case "terminal-table":
|
|
927
1165
|
return renderTableFrame(node, context);
|
|
@@ -938,7 +1176,7 @@ function renderElementFrame(node, context) {
|
|
|
938
1176
|
case "terminal-text": {
|
|
939
1177
|
const value = typeof node.props.value !== "undefined" ? plainText(node.props.value) : plainText(node.children.map(textContent).join(""));
|
|
940
1178
|
const frame = createFrame(value.split("\n"));
|
|
941
|
-
return addFullFrameSpans(frame, resolveNodeStyle(node, context).spanKinds);
|
|
1179
|
+
return addFullFrameSpans(frame, resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
942
1180
|
}
|
|
943
1181
|
case "terminal-input": {
|
|
944
1182
|
const value = typeof node.props.value !== "undefined" ? node.props.value : node.props.placeholder || "";
|
|
@@ -957,7 +1195,7 @@ function renderElementFrame(node, context) {
|
|
|
957
1195
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
958
1196
|
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, textLength: stringValue.length }] : [];
|
|
959
1197
|
const spans = fullFrameSpans(["input.base"], width, height);
|
|
960
|
-
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context).spanKinds);
|
|
1198
|
+
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
961
1199
|
}
|
|
962
1200
|
const rendered = renderInputLine(stringValue, node.props.__inputState || { cursor: stringValue.length, anchor: stringValue.length }, inputPadding);
|
|
963
1201
|
const decorated = addBorder(createFrame([rendered.line], [], rendered.cursor, rendered.spans), inputBorder);
|
|
@@ -965,7 +1203,7 @@ function renderElementFrame(node, context) {
|
|
|
965
1203
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
966
1204
|
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, textLength: stringValue.length }] : [];
|
|
967
1205
|
const spans = [...fullFrameSpans(["input.base", "input.focus"], width, height), ...decorated.spans];
|
|
968
|
-
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context).spanKinds);
|
|
1206
|
+
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
969
1207
|
}
|
|
970
1208
|
case "terminal-editor":
|
|
971
1209
|
return addFullFrameSpans(renderEditorFrame(node), resolveNodeStyle(node, context).spanKinds);
|
|
@@ -975,10 +1213,10 @@ function renderElementFrame(node, context) {
|
|
|
975
1213
|
const decorated = decoratedControlFrame([String(label)], layoutStyle);
|
|
976
1214
|
const width = Math.max(1, getFrameWidth(decorated));
|
|
977
1215
|
const height = Math.max(1, getFrameHeight(decorated));
|
|
978
|
-
const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height }] : [];
|
|
1216
|
+
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 }] : [];
|
|
979
1217
|
const kinds = ["button.base", ...nodeStates(node).map((state) => `button.${state}`)];
|
|
980
1218
|
const spans = fullFrameSpans(kinds, width, height);
|
|
981
|
-
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context).spanKinds);
|
|
1219
|
+
return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
|
|
982
1220
|
}
|
|
983
1221
|
default:
|
|
984
1222
|
return mergeVertical(node.children.map((child) => renderTerminalFrame(child, context)));
|
|
@@ -993,9 +1231,10 @@ export function renderTerminalFrame(node, context) {
|
|
|
993
1231
|
export function renderTerminalNode(node, context) {
|
|
994
1232
|
return renderTerminalFrame(node, context).lines.join("\n");
|
|
995
1233
|
}
|
|
996
|
-
export function renderTerminal(input) {
|
|
1234
|
+
export function renderTerminal(input, context) {
|
|
1235
|
+
const renderContext = validateRenderContext(context);
|
|
997
1236
|
return renderValyrianTerminal(input)
|
|
998
|
-
.map((node) => renderTerminalNode(node))
|
|
1237
|
+
.map((node) => renderTerminalNode(node, renderContext))
|
|
999
1238
|
.filter(Boolean)
|
|
1000
1239
|
.join("\n")
|
|
1001
1240
|
.trimEnd();
|