@valyrianjs/terminal 0.2.2 → 0.2.4

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.
@@ -0,0 +1,1401 @@
1
+ import { constrainFrame, createFrame, cropFrame, fitFrame, getFrameHeight, getFrameWidth, mergeHorizontal, mergeVertical, overlayFrame, shiftFrame } from "./layout.js";
2
+ import { getSelectionRange, normalizeInputState } from "./events.js";
3
+ import { markFullFrameSpan, markFullRowSpan } from "./frame-style.js";
4
+ import { createEditorState } from "./editor-state.js";
5
+ import { isFocusable, textContent } from "./tree.js";
6
+ import { renderValyrianTerminal } from "./runtime.js";
7
+ import { cursorCellOffset, dropTerminalCells, padEndTerminalCells, plainText, sliceTerminalCells, terminalCellToStringIndex, terminalCellWidth, terminalGraphemes } from "./text.js";
8
+ import { resolveTerminalStyle } from "./theme.js";
9
+ function validateRenderContextDimension(name, value) {
10
+ if (!Number.isInteger(value) || value < 1) {
11
+ throw new RangeError(`Invalid render context ${name}: expected an integer >= 1`);
12
+ }
13
+ return value;
14
+ }
15
+ function validateRenderContext(context) {
16
+ if (typeof context === "undefined") {
17
+ return undefined;
18
+ }
19
+ return {
20
+ cols: validateRenderContextDimension("cols", context.cols),
21
+ rows: validateRenderContextDimension("rows", context.rows),
22
+ theme: context.theme
23
+ };
24
+ }
25
+ const VISUAL_STATE_ORDER = [
26
+ "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"
27
+ ];
28
+ function numeric(value, fallback = 0) {
29
+ const result = Number(value);
30
+ return Number.isFinite(result) ? result : fallback;
31
+ }
32
+ function addFocusableHitbox(frame, node) {
33
+ if (!node.props.id) {
34
+ return frame;
35
+ }
36
+ const width = Math.max(1, getFrameWidth(frame));
37
+ const height = Math.max(1, getFrameHeight(frame));
38
+ frame.hitboxes.unshift({ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height });
39
+ return frame;
40
+ }
41
+ function positiveDimension(value, prop) {
42
+ if (typeof value === "undefined") {
43
+ return undefined;
44
+ }
45
+ const size = Number(value);
46
+ if (!Number.isFinite(size) || size <= 0) {
47
+ throw new RangeError(`${prop} must be greater than zero`);
48
+ }
49
+ return size;
50
+ }
51
+ function positiveInteger(value, prop) {
52
+ const size = Number(value);
53
+ if (!Number.isFinite(size) || !Number.isInteger(size) || size <= 0) {
54
+ throw new RangeError(`${prop} must be a positive finite integer`);
55
+ }
56
+ return size;
57
+ }
58
+ function nonNegativeInteger(value, prop) {
59
+ const size = typeof value === "undefined" ? 0 : Number(value);
60
+ if (!Number.isFinite(size) || !Number.isInteger(size) || size < 0) {
61
+ throw new RangeError(`${prop} must be a non-negative finite integer`);
62
+ }
63
+ return size;
64
+ }
65
+ function normalizeSpacing(value, prop) {
66
+ if (typeof value === "undefined")
67
+ return { top: 0, right: 0, bottom: 0, left: 0 };
68
+ if (typeof value === "number") {
69
+ const size = nonNegativeInteger(value, prop);
70
+ return { top: size, right: size, bottom: size, left: size };
71
+ }
72
+ const x = nonNegativeInteger(value.x, `${prop}.x`);
73
+ const y = nonNegativeInteger(value.y, `${prop}.y`);
74
+ return {
75
+ top: typeof value.top === "undefined" ? y : nonNegativeInteger(value.top, `${prop}.top`),
76
+ right: typeof value.right === "undefined" ? x : nonNegativeInteger(value.right, `${prop}.right`),
77
+ bottom: typeof value.bottom === "undefined" ? y : nonNegativeInteger(value.bottom, `${prop}.bottom`),
78
+ left: typeof value.left === "undefined" ? x : nonNegativeInteger(value.left, `${prop}.left`)
79
+ };
80
+ }
81
+ function normalizeBorder(value) {
82
+ if (value === true)
83
+ return { top: true, right: true, bottom: true, left: true, style: "solid" };
84
+ if (typeof value === "string")
85
+ return { top: true, right: true, bottom: true, left: true, style: value };
86
+ if (value && typeof value === "object") {
87
+ return { top: value.top === true, right: value.right === true, bottom: value.bottom === true, left: value.left === true, style: value.style || "solid", color: value.color };
88
+ }
89
+ return { top: false, right: false, bottom: false, left: false, style: "solid" };
90
+ }
91
+ function borderChars(style) {
92
+ if (style === "solid")
93
+ return { topLeft: "┌", topRight: "┐", bottomLeft: "└", bottomRight: "┘", horizontal: "─", vertical: "│" };
94
+ if (style === "dotted")
95
+ return { topLeft: "+", topRight: "+", bottomLeft: "+", bottomRight: "+", horizontal: "·", vertical: "⋮" };
96
+ if (style === "double")
97
+ return { topLeft: "╔", topRight: "╗", bottomLeft: "╚", bottomRight: "╝", horizontal: "═", vertical: "║" };
98
+ throw new RangeError(`Unknown terminal border style: ${style}`);
99
+ }
100
+ function mergeStyleDefinitions(base, next) {
101
+ return { ...base, ...next };
102
+ }
103
+ function styleSpan(kind, style) {
104
+ return typeof style === "undefined" ? { kind } : { kind, style };
105
+ }
106
+ const BASE_STYLE_KIND_BY_TAG = {
107
+ "terminal-button": "button.base",
108
+ "terminal-input": "input.base",
109
+ "terminal-editor": "editor.base",
110
+ "terminal-list": "list.base",
111
+ "terminal-scroll": "scroll.base",
112
+ "terminal-log-view": "log.base",
113
+ "terminal-overlay": "overlay.base"
114
+ };
115
+ function baseStyleKindForNode(node) {
116
+ return BASE_STYLE_KIND_BY_TAG[node.tag];
117
+ }
118
+ function nodeStates(node) {
119
+ const declared = Array.isArray(node.props.state) ? node.props.state : typeof node.props.state === "string" ? [node.props.state] : [];
120
+ const states = new Set();
121
+ for (const state of declared)
122
+ states.add(state);
123
+ if (node.props.disabled === true)
124
+ states.add("disabled");
125
+ if (node.props.pressed === true)
126
+ states.add("pressed");
127
+ if (node.props.__focused === true)
128
+ states.add("focus");
129
+ if (typeof node.props.__hoveredIndex === "number" || typeof node.props.__hoveredRow === "number")
130
+ states.add("hover");
131
+ if (Array.isArray(node.props.items) && node.props.items.length === 0)
132
+ states.add("empty");
133
+ if (Array.isArray(node.props.entries) && node.props.entries.length === 0)
134
+ states.add("empty");
135
+ return VISUAL_STATE_ORDER.filter((state) => states.has(state));
136
+ }
137
+ function resolveLayoutStyle(baseKind, node, context) {
138
+ return mergeStyleDefinitions(resolveTerminalStyle(baseKind, context?.theme), resolveTerminalStyle(node.props.style, context?.theme));
139
+ }
140
+ function resolveNodeLayoutStyle(node, context) {
141
+ return mergeStyleDefinitions(resolveTerminalStyle(baseStyleKindForNode(node), context?.theme), resolveTerminalStyle(node.props.style, context?.theme));
142
+ }
143
+ function decoratedControlFrame(content, style) {
144
+ const padding = normalizeSpacing(style?.padding, "Control padding");
145
+ const border = normalizeBorder(style?.border);
146
+ return addBorder(padFrameSides(createFrame(content), padding), border);
147
+ }
148
+ function fullFrameSpans(kinds, width, height) {
149
+ const spans = [];
150
+ for (const kind of kinds) {
151
+ for (let y = 1; y <= height; y += 1) {
152
+ spans.push(markFullFrameSpan({ kind, x1: 1, x2: width + 1, y }));
153
+ }
154
+ }
155
+ return spans;
156
+ }
157
+ function resolveNodeStyle(node, context, options = {}) {
158
+ const baseKind = options.includeBase === false ? undefined : baseStyleKindForNode(node);
159
+ let style = resolveTerminalStyle(baseKind, context?.theme);
160
+ const spanKinds = typeof baseKind === "undefined" ? [] : [styleSpan(baseKind)];
161
+ const explicitStyle = resolveTerminalStyle(node.props.style, context?.theme);
162
+ if (typeof node.props.style === "string") {
163
+ spanKinds.push(styleSpan(node.props.style));
164
+ }
165
+ else if (explicitStyle) {
166
+ spanKinds.push(styleSpan("#style", explicitStyle));
167
+ }
168
+ style = mergeStyleDefinitions(style, explicitStyle);
169
+ for (const state of nodeStates(node)) {
170
+ const stateStyle = node.props.styles?.[state];
171
+ if (stateStyle) {
172
+ const resolvedStateStyle = resolveTerminalStyle(stateStyle, context?.theme);
173
+ style = mergeStyleDefinitions(style, resolvedStateStyle);
174
+ spanKinds.push(typeof stateStyle === "string" ? styleSpan(stateStyle) : styleSpan("#style", resolvedStateStyle));
175
+ }
176
+ }
177
+ return { style, spanKinds };
178
+ }
179
+ function padFrameSides(frame, padding) {
180
+ if (!padding.top && !padding.right && !padding.bottom && !padding.left)
181
+ return frame;
182
+ const width = Math.max(1, getFrameWidth(frame));
183
+ const contentWidth = width + padding.left + padding.right;
184
+ const topLines = new Array(padding.top).fill(" ".repeat(contentWidth));
185
+ const bottomLines = new Array(padding.bottom).fill(" ".repeat(contentWidth));
186
+ const lines = [
187
+ ...topLines,
188
+ ...frame.lines.map((line) => `${" ".repeat(padding.left)}${padEndTerminalCells(line, width)}${" ".repeat(padding.right)}`),
189
+ ...bottomLines
190
+ ];
191
+ return shiftFrame(createFrame(lines, frame.hitboxes, frame.cursor, frame.spans), padding.left, padding.top);
192
+ }
193
+ function addBorder(frame, border) {
194
+ if (!border.top && !border.right && !border.bottom && !border.left)
195
+ return frame;
196
+ const chars = borderChars(border.style);
197
+ const innerWidth = getFrameWidth(frame);
198
+ const left = border.left ? 1 : 0;
199
+ const right = border.right ? 1 : 0;
200
+ const width = innerWidth + left + right;
201
+ const lines = [];
202
+ if (border.top) {
203
+ 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));
204
+ }
205
+ for (const line of frame.lines) {
206
+ lines.push(`${border.left ? chars.vertical : ""}${padEndTerminalCells(line, innerWidth)}${border.right ? chars.vertical : ""}`);
207
+ }
208
+ if (border.bottom) {
209
+ 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));
210
+ }
211
+ return shiftFrame(createFrame(lines, frame.hitboxes, frame.cursor, frame.spans), left, border.top ? 1 : 0);
212
+ }
213
+ function containerDecorationSize(node, context) {
214
+ const layoutStyle = resolveNodeLayoutStyle(node, context);
215
+ const padding = normalizeSpacing(layoutStyle?.padding ?? node.props.padding, `${node.tag} padding`);
216
+ const border = normalizeBorder(layoutStyle?.border);
217
+ return {
218
+ horizontal: padding.left + padding.right + (border.left ? 1 : 0) + (border.right ? 1 : 0),
219
+ vertical: padding.top + padding.bottom + (border.top ? 1 : 0) + (border.bottom ? 1 : 0)
220
+ };
221
+ }
222
+ function addFullFrameSpans(frame, kinds) {
223
+ const width = Math.max(1, getFrameWidth(frame));
224
+ const height = getFrameHeight(frame);
225
+ if (width <= 0 || height <= 0 || kinds.length === 0)
226
+ return frame;
227
+ const spans = frame.spans.slice();
228
+ for (const span of kinds) {
229
+ for (let y = 1; y <= height; y += 1) {
230
+ spans.push(markFullFrameSpan({ ...span, x1: 1, x2: width + 1, y }));
231
+ }
232
+ }
233
+ return createFrame(frame.lines, frame.hitboxes, frame.cursor, spans);
234
+ }
235
+ function listViewportRows(node, itemCount, context) {
236
+ const explicitHeight = positiveDimension(node.props.height, "height");
237
+ const viewportSourceRows = explicitHeight ?? context?.rows ?? (itemCount || 1);
238
+ return Math.max(1, Math.min(itemCount || 1, positiveInteger(viewportSourceRows, "List viewport height")));
239
+ }
240
+ function clampListIndex(index, itemCount) {
241
+ if (itemCount <= 0) {
242
+ return 0;
243
+ }
244
+ return Math.max(0, Math.min(itemCount - 1, index));
245
+ }
246
+ function listVirtualRange(node, itemCount, context) {
247
+ if (!node.props.virtualized) {
248
+ return { start: 0, end: itemCount, visibleStart: 0, viewportRows: itemCount || 1 };
249
+ }
250
+ if (typeof node.props.itemHeight !== "undefined" && node.props.itemHeight !== 1) {
251
+ throw new RangeError("List itemHeight must be 1");
252
+ }
253
+ const overscan = nonNegativeInteger(node.props.overscan, "List overscan");
254
+ const viewportRows = listViewportRows(node, itemCount, context);
255
+ const maxOffset = Math.max(0, itemCount - viewportRows);
256
+ let visibleStart = Math.max(0, Math.min(maxOffset, nonNegativeInteger(node.props.__scrollOffset, "List viewport offset")));
257
+ const start = Math.max(0, visibleStart - overscan);
258
+ const end = Math.min(itemCount, visibleStart + viewportRows + overscan);
259
+ return { start, end, visibleStart, viewportRows };
260
+ }
261
+ function listItemKey(node, item, index) {
262
+ if (typeof node.props.itemKey === "function") {
263
+ const key = node.props.itemKey(item, index);
264
+ if (typeof key !== "string" && typeof key !== "number") {
265
+ throw new RangeError("List itemKey must return a string or number");
266
+ }
267
+ return String(key);
268
+ }
269
+ return String(index);
270
+ }
271
+ function listItemRenderer(node) {
272
+ if (typeof node.props.__childrenRenderer === "function") {
273
+ return { type: "children", render: node.props.__childrenRenderer };
274
+ }
275
+ if (typeof node.props.renderItem === "function") {
276
+ return { type: "renderItem", render: node.props.renderItem };
277
+ }
278
+ return undefined;
279
+ }
280
+ function wrapPlainText(value, width) {
281
+ if (!Number.isFinite(width) || !Number.isInteger(width) || width <= 0) {
282
+ return [""];
283
+ }
284
+ const rows = [];
285
+ const sourceRows = value.split("\n");
286
+ for (const sourceRow of sourceRows) {
287
+ if (sourceRow.length === 0) {
288
+ rows.push("");
289
+ continue;
290
+ }
291
+ let remaining = sourceRow;
292
+ while (terminalCellWidth(remaining) > width) {
293
+ const slice = sliceTerminalCells(remaining, width);
294
+ if (slice.length === 0) {
295
+ const [firstGrapheme = ""] = terminalGraphemes(remaining);
296
+ rows.push(firstGrapheme);
297
+ remaining = remaining.slice(firstGrapheme.length);
298
+ continue;
299
+ }
300
+ const breakAt = slice.lastIndexOf(" ");
301
+ if (breakAt > 0 && breakAt >= Math.floor(width * 0.6)) {
302
+ rows.push(remaining.slice(0, breakAt));
303
+ remaining = remaining.slice(breakAt + 1);
304
+ }
305
+ else {
306
+ rows.push(slice);
307
+ remaining = remaining.slice(slice.length);
308
+ }
309
+ }
310
+ rows.push(remaining);
311
+ }
312
+ return rows.length ? rows : [""];
313
+ }
314
+ function renderListItemFrame(node, item, index, viewportIndex, activeIndex, selectedIndex, wrapWidth, context) {
315
+ const key = listItemKey(node, item, index);
316
+ const renderer = listItemRenderer(node);
317
+ if (!renderer) {
318
+ const label = plainText(item);
319
+ return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
320
+ }
321
+ const ctx = {
322
+ index,
323
+ key,
324
+ active: index === activeIndex,
325
+ selected: selectedIndex !== null && index === selectedIndex,
326
+ viewportIndex,
327
+ item
328
+ };
329
+ const rendered = renderer.type === "children" ? renderer.render(item, ctx) : renderer.render(item, index);
330
+ if (typeof rendered === "string" || typeof rendered === "number") {
331
+ const label = plainText(rendered);
332
+ return createFrame(node.props.wrap === true ? wrapPlainText(label, wrapWidth) : label.split("\n"));
333
+ }
334
+ const frame = mergeVertical(renderValyrianTerminal(rendered).map((child) => renderTerminalFrame(child, context)));
335
+ if (node.props.wrap === true && frame.hitboxes.length === 0) {
336
+ return createFrame(frame.lines.flatMap((line) => wrapPlainText(line, wrapWidth)));
337
+ }
338
+ return frame;
339
+ }
340
+ function fixedPosition(value) {
341
+ if (value === "top" || value === "bottom" || value === "left" || value === "right") {
342
+ return value;
343
+ }
344
+ throw new RangeError("Fixed position must be top, bottom, left, or right");
345
+ }
346
+ function fillContextDimension(context, prop, label) {
347
+ if (!context) {
348
+ throw new RangeError(`${label} fill requires render context to resolve ${prop}`);
349
+ }
350
+ return positiveInteger(prop === "width" ? context.cols : context.rows, `${label} ${prop}`);
351
+ }
352
+ function resolveLayoutDimension(node, prop, label, context) {
353
+ const explicit = positiveDimension(node.props[prop], prop);
354
+ if (typeof explicit !== "undefined") {
355
+ return explicit;
356
+ }
357
+ if (node.props.fill === true) {
358
+ return fillContextDimension(context, prop, label);
359
+ }
360
+ return undefined;
361
+ }
362
+ function resolveLayoutDimensions(node, label, context) {
363
+ return {
364
+ width: resolveLayoutDimension(node, "width", label, context),
365
+ height: resolveLayoutDimension(node, "height", label, context)
366
+ };
367
+ }
368
+ function contextBackedDimension(context, prop, label) {
369
+ if (!context) {
370
+ return undefined;
371
+ }
372
+ return positiveInteger(prop === "width" ? context.cols : context.rows, `${label} ${prop}`);
373
+ }
374
+ function resolveBlockLayoutDimensions(node, label, context, options = {}) {
375
+ const explicitWidth = positiveDimension(node.props.width, "width");
376
+ const explicitHeight = positiveDimension(node.props.height, "height");
377
+ return {
378
+ width: typeof explicitWidth !== "undefined"
379
+ ? explicitWidth
380
+ : node.props.fill === true
381
+ ? fillContextDimension(context, "width", label)
382
+ : contextBackedDimension(context, "width", label),
383
+ height: typeof explicitHeight !== "undefined"
384
+ ? explicitHeight
385
+ : node.props.fill === true
386
+ ? fillContextDimension(context, "height", label)
387
+ : options.exactHeightFromContext === true
388
+ ? contextBackedDimension(context, "height", label)
389
+ : undefined
390
+ };
391
+ }
392
+ function resolveSplitDimension(node, prop, context) {
393
+ const explicit = positiveDimension(node.props[prop], prop);
394
+ if (typeof explicit !== "undefined") {
395
+ return explicit;
396
+ }
397
+ if (node.props.fill === true) {
398
+ return fillContextDimension(context, prop, "Split");
399
+ }
400
+ const fromContext = contextBackedDimension(context, prop, "Split");
401
+ if (typeof fromContext !== "undefined") {
402
+ return fromContext;
403
+ }
404
+ throw new RangeError("Split requires width/height or render context");
405
+ }
406
+ function decorateContainerFrame(frame, node, options = {}, context) {
407
+ let next = frame;
408
+ const resolved = resolveNodeStyle(node, context);
409
+ const layoutStyle = resolveNodeLayoutStyle(node, context);
410
+ const padding = normalizeSpacing(layoutStyle?.padding ?? node.props.padding, `${node.tag} padding`);
411
+ const border = normalizeBorder(layoutStyle?.border);
412
+ const decoration = containerDecorationSize(node, context);
413
+ if (!options.constrain) {
414
+ next = fitFrame(next, numeric(options.width ?? node.props.width, 0), numeric(options.height ?? node.props.height, 0));
415
+ }
416
+ else if (node.tag === "terminal-pane" && (typeof node.props.width !== "undefined" || typeof node.props.height !== "undefined")) {
417
+ const width = typeof node.props.width === "undefined" || typeof options.width === "undefined" ? undefined : options.width - decoration.horizontal;
418
+ const height = typeof node.props.height === "undefined" || typeof options.height === "undefined" ? undefined : options.height - decoration.vertical;
419
+ if ((typeof width === "number" && width > 0) || (typeof height === "number" && height > 0)) {
420
+ next = constrainFrame(next, {
421
+ width: typeof width === "number" && width > 0 ? width : undefined,
422
+ height: typeof height === "number" && height > 0 ? height : undefined,
423
+ expandFullFrameSpans: true
424
+ });
425
+ }
426
+ }
427
+ next = padFrameSides(next, padding);
428
+ next = addBorder(next, border);
429
+ if (options.constrain) {
430
+ next = constrainFrame(next, {
431
+ width: typeof options.width === "undefined" ? positiveDimension(node.props.width, "width") : options.width,
432
+ height: typeof options.height === "undefined" ? positiveDimension(node.props.height, "height") : options.height,
433
+ expandFullFrameSpans: true
434
+ });
435
+ }
436
+ next = addContainerStyleSpans(next, node, resolved);
437
+ if (node.props.id && isFocusable(node)) {
438
+ next = addFocusableHitbox(next, node);
439
+ }
440
+ return next;
441
+ }
442
+ function addContainerStyleSpans(frame, node, resolved = resolveNodeStyle(node)) {
443
+ const width = getFrameWidth(frame);
444
+ const height = getFrameHeight(frame);
445
+ if (width <= 0 || height <= 0) {
446
+ return frame;
447
+ }
448
+ const spans = frame.spans.slice();
449
+ const containerSpans = resolved.spanKinds.slice();
450
+ const border = normalizeBorder(resolved.style?.border);
451
+ if (!resolved.style?.color && border.color) {
452
+ containerSpans.push(styleSpan("#style", { color: border.color }));
453
+ }
454
+ for (const span of containerSpans) {
455
+ for (let y = 1; y <= height; y += 1) {
456
+ spans.push({ ...span, x1: 1, x2: width + 1, y });
457
+ }
458
+ }
459
+ if (node.props.__focused && isFocusable(node)) {
460
+ for (let y = 1; y <= height; y += 1) {
461
+ spans.push({ kind: "focus", x1: 1, x2: width + 1, y });
462
+ }
463
+ }
464
+ return createFrame(frame.lines, frame.hitboxes, frame.cursor, spans);
465
+ }
466
+ function decoratedContentDimension(node, prop, outer, context) {
467
+ positiveInteger(outer, `Pane ${prop}`);
468
+ const decoration = containerDecorationSize(node, context);
469
+ const inner = outer - (prop === "width" ? decoration.horizontal : decoration.vertical);
470
+ if (!Number.isFinite(inner) || !Number.isInteger(inner) || inner <= 0) {
471
+ throw new RangeError(`Pane ${prop} leaves no room for Fixed content`);
472
+ }
473
+ return inner;
474
+ }
475
+ function resolveContainerChildContext(node, dimensions, context) {
476
+ if (typeof dimensions.width !== "number" && typeof dimensions.height !== "number") {
477
+ return context;
478
+ }
479
+ return {
480
+ cols: typeof dimensions.width === "number" ? decoratedContentDimension(node, "width", dimensions.width, context) : context?.cols ?? 1,
481
+ rows: typeof dimensions.height === "number" ? decoratedContentDimension(node, "height", dimensions.height, context) : context?.rows ?? 1,
482
+ theme: context?.theme
483
+ };
484
+ }
485
+ function interactiveTextMetadata(value) {
486
+ const textLength = value.length;
487
+ const textCellToStringIndex = terminalCellToStringIndex(value);
488
+ const usesLinearIndexes = textCellToStringIndex.length === textLength + 1
489
+ && textCellToStringIndex.every((index, cellOffset) => index === cellOffset);
490
+ if (usesLinearIndexes) {
491
+ return { textLength };
492
+ }
493
+ return { textLength, textCellToStringIndex };
494
+ }
495
+ function renderInputLine(value, inputState, padding = { top: 0, right: 0, bottom: 0, left: 0 }) {
496
+ const state = normalizeInputState(inputState, value.length);
497
+ const { start, end } = getSelectionRange(state);
498
+ const line = `${value.slice(0, state.cursor)}|${value.slice(state.cursor)}`;
499
+ const paddedLine = `${" ".repeat(padding.left)}${line}${" ".repeat(padding.right)}`;
500
+ const textStart = padding.left + 1;
501
+ return {
502
+ line: paddedLine,
503
+ cursor: { x: textStart + cursorCellOffset(value, state.cursor), y: 1 },
504
+ spans: start === end ? [] : [{ kind: "input.selection", x1: textStart + cursorCellOffset(value, start), x2: textStart + cursorCellOffset(value, end), y: 1 }]
505
+ };
506
+ }
507
+ function scrollFocusedInputLine(rendered, visibleWidth) {
508
+ const width = Math.max(1, Math.trunc(visibleWidth));
509
+ const scrollOffset = Math.max(0, rendered.cursor.x - width);
510
+ if (scrollOffset === 0) {
511
+ return rendered;
512
+ }
513
+ return {
514
+ line: dropTerminalCells(rendered.line, scrollOffset),
515
+ cursor: { x: rendered.cursor.x - scrollOffset, y: rendered.cursor.y },
516
+ spans: rendered.spans.map((span) => ({
517
+ ...span,
518
+ x1: span.x1 - scrollOffset,
519
+ x2: span.x2 - scrollOffset
520
+ }))
521
+ };
522
+ }
523
+ function resolveEditorDimensions(node, context) {
524
+ const explicitWidth = positiveDimension(node.props.width, "width");
525
+ const explicitHeight = positiveDimension(node.props.height, "height");
526
+ return {
527
+ width: typeof explicitWidth !== "undefined"
528
+ ? explicitWidth
529
+ : node.props.fill === true
530
+ ? fillContextDimension(context, "width", "Editor")
531
+ : contextBackedDimension(context, "width", "Editor"),
532
+ height: typeof explicitHeight !== "undefined"
533
+ ? explicitHeight
534
+ : node.props.fill === true
535
+ ? fillContextDimension(context, "height", "Editor")
536
+ : undefined
537
+ };
538
+ }
539
+ function wrapEditorLineSegments(value, width, sourceLine) {
540
+ if (!Number.isFinite(width) || !Number.isInteger(width) || width <= 0) {
541
+ return [{ text: "", sourceLine, start: 0, end: 0 }];
542
+ }
543
+ if (value.length === 0) {
544
+ return [{ text: "", sourceLine, start: 0, end: 0 }];
545
+ }
546
+ const segments = [];
547
+ let remaining = value;
548
+ let offset = 0;
549
+ while (terminalCellWidth(remaining) > width) {
550
+ const slice = sliceTerminalCells(remaining, width);
551
+ if (slice.length === 0) {
552
+ const [firstGrapheme = ""] = terminalGraphemes(remaining);
553
+ segments.push({ text: firstGrapheme, sourceLine, start: offset, end: offset + firstGrapheme.length });
554
+ remaining = remaining.slice(firstGrapheme.length);
555
+ offset += firstGrapheme.length;
556
+ continue;
557
+ }
558
+ const breakAt = slice.lastIndexOf(" ");
559
+ const useWordBreak = breakAt > 0 && breakAt >= Math.floor(width * 0.6);
560
+ const segmentText = useWordBreak ? remaining.slice(0, breakAt) : slice;
561
+ segments.push({ text: segmentText, sourceLine, start: offset, end: offset + segmentText.length });
562
+ const consumed = useWordBreak ? breakAt + 1 : slice.length;
563
+ remaining = remaining.slice(consumed);
564
+ offset += consumed;
565
+ }
566
+ segments.push({ text: remaining, sourceLine, start: offset, end: offset + remaining.length });
567
+ return segments;
568
+ }
569
+ function wrapEditorLines(lines, width) {
570
+ return lines.flatMap((line, sourceLine) => wrapEditorLineSegments(line, width, sourceLine));
571
+ }
572
+ function insertTrailingEditorCursorSegment(segments, line, column, contentWidth) {
573
+ if (typeof contentWidth !== "number") {
574
+ return segments;
575
+ }
576
+ for (let index = 0; index < segments.length; index += 1) {
577
+ const segment = segments[index];
578
+ if (segment.sourceLine !== line || column !== segment.end || segment.start === segment.end) {
579
+ continue;
580
+ }
581
+ const nextSegment = segments[index + 1];
582
+ if (nextSegment && nextSegment.sourceLine === line && nextSegment.start === column) {
583
+ return segments;
584
+ }
585
+ if (terminalCellWidth(segment.text) < contentWidth) {
586
+ return segments;
587
+ }
588
+ const cursorSegment = { text: "", sourceLine: line, start: column, end: column };
589
+ return [...segments.slice(0, index + 1), cursorSegment, ...segments.slice(index + 1)];
590
+ }
591
+ return segments;
592
+ }
593
+ function visualEditorCursor(segments, line, column) {
594
+ for (let index = 0; index < segments.length; index += 1) {
595
+ const segment = segments[index];
596
+ if (segment.sourceLine !== line) {
597
+ continue;
598
+ }
599
+ const nextSegment = segments[index + 1];
600
+ if (column === segment.end && nextSegment && nextSegment.sourceLine === line && nextSegment.start === column) {
601
+ return { index: index + 1, column: 0 };
602
+ }
603
+ const containsCursor = column >= segment.start && column <= segment.end;
604
+ const isTrailingEmpty = segment.start === segment.end && column === segment.start;
605
+ if (containsCursor || isTrailingEmpty) {
606
+ return { index, column: Math.max(0, Math.min(column - segment.start, segment.text.length)) };
607
+ }
608
+ }
609
+ return { index: Math.max(0, segments.length - 1), column: 0 };
610
+ }
611
+ function renderEditorFrame(node, context) {
612
+ const value = typeof node.props.value !== "undefined" ? plainText(node.props.value) : "";
613
+ const placeholder = typeof node.props.placeholder !== "undefined" ? plainText(node.props.placeholder) : "";
614
+ const displayValue = value.length === 0 && !node.props.__focused && placeholder ? placeholder : value;
615
+ const state = createEditorState(displayValue, node.props.__editorState?.cursor);
616
+ const focusedState = createEditorState(value, node.props.__editorState?.cursor);
617
+ const focusedLine = focusedState.cursor.line;
618
+ const focusedColumn = focusedState.cursor.column;
619
+ const dimensions = resolveEditorDimensions(node, context);
620
+ const contentWidth = typeof dimensions.width === "number" ? Math.max(1, dimensions.width - 2) : undefined;
621
+ const baseSegments = typeof contentWidth === "number" ? wrapEditorLines(state.lines, contentWidth) : state.lines.map((line, sourceLine) => ({ text: line, sourceLine, start: 0, end: line.length }));
622
+ const segments = node.props.__focused ? insertTrailingEditorCursorSegment(baseSegments, focusedLine, focusedColumn, contentWidth) : baseSegments;
623
+ const focusedVisualCursor = visualEditorCursor(segments, focusedLine, focusedColumn);
624
+ const lines = segments.map((segment, index) => {
625
+ if (!node.props.__focused || index !== focusedVisualCursor.index) {
626
+ return ` ${segment.text}`;
627
+ }
628
+ return ` ${segment.text.slice(0, focusedVisualCursor.column)}|${segment.text.slice(focusedVisualCursor.column)}`;
629
+ });
630
+ const cursor = node.props.__focused ? { x: 3 + cursorCellOffset(segments[focusedVisualCursor.index]?.text ?? "", focusedVisualCursor.column), y: focusedVisualCursor.index + 1 } : null;
631
+ const spans = node.props.__focused ? [{ kind: "editor.focus", x1: 1, x2: Math.max(2, terminalCellWidth(lines[focusedVisualCursor.index] ?? "") + 1), y: focusedVisualCursor.index + 1 }] : [];
632
+ const frame = createFrame(lines, [], cursor, spans);
633
+ const scrollOffset = node.props.__focused && typeof dimensions.height !== "undefined"
634
+ ? Math.min(Math.max(0, focusedVisualCursor.index - dimensions.height + 1), Math.max(0, lines.length - dimensions.height))
635
+ : 0;
636
+ const croppedFrame = typeof dimensions.height === "undefined"
637
+ ? frame
638
+ : scrollOffset > 0
639
+ ? cropFrame(frame, scrollOffset, dimensions.height)
640
+ : frame;
641
+ const constrainedFrame = typeof dimensions.width === "undefined" && typeof dimensions.height === "undefined"
642
+ ? croppedFrame
643
+ : constrainFrame(croppedFrame, { ...dimensions, expandFullFrameSpans: true });
644
+ if (!node.props.id) {
645
+ return constrainedFrame;
646
+ }
647
+ 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);
648
+ }
649
+ function publicRenderContext(context) {
650
+ const publicContext = { cols: context.cols, rows: context.rows };
651
+ if (typeof context.theme !== "undefined") {
652
+ publicContext.theme = context.theme;
653
+ }
654
+ return publicContext;
655
+ }
656
+ function notifyLayoutContextProbe(node, context) {
657
+ if (context && typeof node.props.__layoutContextProbe === "function") {
658
+ node.props.__layoutContextProbe(publicRenderContext(context));
659
+ }
660
+ }
661
+ function renderTableFrame(node, context) {
662
+ const rowNodes = node.children.filter((child) => child.type === "element");
663
+ if (!rowNodes.length) {
664
+ return createFrame([""]);
665
+ }
666
+ const rowFrames = rowNodes.map((row) => row.children.map((child) => renderTerminalFrame(child, context)));
667
+ const columnCount = rowFrames.reduce((max, row) => Math.max(max, row.length), 0);
668
+ const columnWidths = new Array(columnCount).fill(0);
669
+ for (const row of rowFrames) {
670
+ for (let index = 0; index < columnCount; index += 1) {
671
+ const cell = row[index] || createFrame([""]);
672
+ columnWidths[index] = Math.max(columnWidths[index], getFrameWidth(cell));
673
+ }
674
+ }
675
+ const lines = [];
676
+ const hitboxes = [];
677
+ const spans = [];
678
+ let cursor = null;
679
+ let yOffset = 0;
680
+ for (const row of rowFrames) {
681
+ const cells = new Array(columnCount).fill(createFrame([""])).map((cell, index) => row[index] || cell);
682
+ const rowHeight = cells.reduce((max, cell) => Math.max(max, getFrameHeight(cell)), 0);
683
+ const normalized = cells.map((cell, index) => ({
684
+ frame: fitFrame(cell, columnWidths[index], rowHeight, { expandFullFrameSpans: true }),
685
+ width: columnWidths[index]
686
+ }));
687
+ for (let rowIndex = 0; rowIndex < rowHeight; rowIndex += 1) {
688
+ lines.push(normalized.map((cell) => cell.frame.lines[rowIndex]).join(" | "));
689
+ }
690
+ let xOffset = 0;
691
+ for (let index = 0; index < normalized.length; index += 1) {
692
+ const cell = normalized[index];
693
+ const shifted = shiftFrame(cell.frame, xOffset, yOffset);
694
+ hitboxes.push(...shifted.hitboxes);
695
+ spans.push(...shifted.spans);
696
+ if (!cursor && cell.frame.cursor) {
697
+ cursor = { x: cell.frame.cursor.x + xOffset, y: cell.frame.cursor.y + yOffset };
698
+ }
699
+ xOffset += cell.width + (index < normalized.length - 1 ? 3 : 0);
700
+ }
701
+ yOffset += rowHeight;
702
+ }
703
+ return createFrame(lines, hitboxes, cursor, spans);
704
+ }
705
+ function resolveSplitBreakpoint(node, width, height) {
706
+ const breakpoints = Array.isArray(node.props.breakpoints) ? node.props.breakpoints : [];
707
+ for (const breakpoint of breakpoints) {
708
+ if (typeof breakpoint.maxCols === "number" && width > breakpoint.maxCols)
709
+ continue;
710
+ if (typeof breakpoint.maxRows === "number" && height > breakpoint.maxRows)
711
+ continue;
712
+ return breakpoint;
713
+ }
714
+ return undefined;
715
+ }
716
+ function parseSplitSize(value, index) {
717
+ if (typeof value === "number") {
718
+ return { type: "absolute", value: positiveInteger(value, `Split sizes[${index}]`) };
719
+ }
720
+ if (typeof value !== "string") {
721
+ throw new RangeError(`Split sizes[${index}] must be a number, percent, or fr value`);
722
+ }
723
+ const percent = value.match(/^([+-]?\d+(?:\.\d+)?)%$/);
724
+ if (percent) {
725
+ const amount = Number(percent[1]);
726
+ if (!Number.isFinite(amount) || amount <= 0)
727
+ throw new RangeError(`Split sizes[${index}] percent must be greater than zero`);
728
+ return { type: "percent", value: amount };
729
+ }
730
+ const fraction = value.match(/^([+-]?\d+(?:\.\d+)?)fr$/);
731
+ if (fraction) {
732
+ const amount = Number(fraction[1]);
733
+ if (!Number.isFinite(amount) || amount <= 0)
734
+ throw new RangeError(`Split sizes[${index}] fr must be greater than zero`);
735
+ return { type: "fr", value: amount };
736
+ }
737
+ throw new RangeError(`Invalid Split sizes[${index}]: ${value}`);
738
+ }
739
+ function allocateDecimals(ideals, total) {
740
+ const sizes = ideals.map((value) => Math.floor(value));
741
+ let remainder = total - sizes.reduce((sum, value) => sum + value, 0);
742
+ const order = ideals.map((value, index) => ({ index, fraction: value - Math.floor(value) }))
743
+ .sort((a, b) => b.fraction - a.fraction || a.index - b.index);
744
+ for (let index = 0; index < order.length && remainder > 0; index += 1) {
745
+ sizes[order[index].index] += 1;
746
+ remainder -= 1;
747
+ }
748
+ return sizes;
749
+ }
750
+ function resolveSplitSizes(sizesInput, childCount, available) {
751
+ if (!Array.isArray(sizesInput)) {
752
+ const base = Math.floor(available / childCount);
753
+ const remainder = available % childCount;
754
+ const sizes = new Array(childCount);
755
+ for (let index = 0; index < childCount; index += 1)
756
+ sizes[index] = base + (index < remainder ? 1 : 0);
757
+ return sizes;
758
+ }
759
+ if (sizesInput.length !== childCount)
760
+ throw new RangeError("Split sizes length must match child count");
761
+ const parsed = sizesInput.map(parseSplitSize);
762
+ const percentTotal = parsed.reduce((sum, size) => sum + (size.type === "percent" ? size.value : 0), 0);
763
+ if (percentTotal > 100)
764
+ throw new RangeError("Split percentage sizes must not exceed 100%");
765
+ const ideals = new Array(childCount).fill(0);
766
+ let absoluteUsed = 0;
767
+ for (let index = 0; index < parsed.length; index += 1) {
768
+ const size = parsed[index];
769
+ if (size.type === "absolute") {
770
+ ideals[index] = size.value;
771
+ absoluteUsed += size.value;
772
+ }
773
+ else if (size.type === "percent") {
774
+ ideals[index] = available * (size.value / 100);
775
+ }
776
+ }
777
+ const percentUsed = ideals.reduce((sum, value, index) => sum + (parsed[index].type === "percent" ? value : 0), 0);
778
+ const remaining = available - absoluteUsed - percentUsed;
779
+ if (remaining < -0.000001)
780
+ throw new RangeError("Split sizes plus gaps must fit within the major axis");
781
+ const frTotal = parsed.reduce((sum, size) => sum + (size.type === "fr" ? size.value : 0), 0);
782
+ for (let index = 0; index < parsed.length; index += 1) {
783
+ const size = parsed[index];
784
+ if (size.type === "fr")
785
+ ideals[index] = frTotal > 0 ? remaining * (size.value / frTotal) : 0;
786
+ }
787
+ const allocationTotal = frTotal > 0 ? available : Math.round(ideals.reduce((sum, value) => sum + value, 0));
788
+ const allocated = allocateDecimals(ideals, allocationTotal);
789
+ if (allocated.reduce((sum, size) => sum + size, 0) > available)
790
+ throw new RangeError("Split sizes plus gaps must fit within the major axis");
791
+ return allocated;
792
+ }
793
+ function isDirectSplitLayoutContainer(node) {
794
+ return node.type === "element" && (node.tag === "terminal-pane" || node.tag === "terminal-box" || node.tag === "terminal-view" || node.tag === "terminal-scroll");
795
+ }
796
+ function renderSplitChildFrame(child, cellWidth, cellHeight, context) {
797
+ const childContext = { cols: cellWidth, rows: cellHeight, theme: context?.theme };
798
+ if (!isDirectSplitLayoutContainer(child)) {
799
+ return renderTerminalFrame(child, childContext);
800
+ }
801
+ const props = { ...child.props };
802
+ if (typeof props.width === "undefined" && props.fill !== true) {
803
+ props.width = cellWidth;
804
+ }
805
+ if (typeof props.height === "undefined" && props.fill !== true) {
806
+ props.height = cellHeight;
807
+ }
808
+ return renderTerminalFrame({ ...child, props }, childContext);
809
+ }
810
+ function renderSplitFrame(node, context) {
811
+ const width = resolveSplitDimension(node, "width", context);
812
+ const height = resolveSplitDimension(node, "height", context);
813
+ const breakpoint = resolveSplitBreakpoint(node, width, height);
814
+ const gap = nonNegativeInteger(breakpoint?.gap ?? node.props.gap, "Split gap");
815
+ const direction = (breakpoint?.direction ?? node.props.direction) === "column" ? "column" : "row";
816
+ const childCount = node.children.length;
817
+ if (!childCount) {
818
+ const frame = createFrame(new Array(height).fill(" ".repeat(width)));
819
+ return node.props.id && isFocusable(node) ? addFocusableHitbox(frame, node) : frame;
820
+ }
821
+ const majorSize = direction === "row" ? width : height;
822
+ const available = majorSize - gap * (childCount - 1);
823
+ if (available < 0) {
824
+ throw new RangeError("Split gap leaves insufficient space for children");
825
+ }
826
+ const sizes = resolveSplitSizes(breakpoint?.sizes ?? node.props.sizes, childCount, available);
827
+ const frames = [];
828
+ for (let index = 0; index < childCount; index += 1) {
829
+ const cellWidth = direction === "row" ? sizes[index] : width;
830
+ const cellHeight = direction === "row" ? height : sizes[index];
831
+ if (cellWidth <= 0 || cellHeight <= 0) {
832
+ continue;
833
+ }
834
+ frames.push(constrainFrame(renderSplitChildFrame(node.children[index], cellWidth, cellHeight, context), { width: cellWidth, height: cellHeight, expandFullFrameSpans: true }));
835
+ }
836
+ const frame = frames.length
837
+ ? direction === "row" ? mergeHorizontal(frames, { gap }) : mergeVertical(frames, { gap })
838
+ : createFrame(new Array(height).fill(" ".repeat(width)));
839
+ const constrained = constrainFrame(frame, { width, height });
840
+ return node.props.id && isFocusable(node) ? addFocusableHitbox(constrained, node) : constrained;
841
+ }
842
+ function hasDirectFixedChildren(node) {
843
+ return node.children.some((child) => child.type === "element" && child.tag === "terminal-fixed");
844
+ }
845
+ function hasDirectOverlayChildren(node) {
846
+ return node.children.some((child) => child.type === "element" && child.tag === "terminal-overlay");
847
+ }
848
+ function splitOverlayChildren(children) {
849
+ const baseChildren = [];
850
+ const overlays = [];
851
+ for (const child of children) {
852
+ if (child.type === "element" && child.tag === "terminal-overlay") {
853
+ overlays.push(child);
854
+ }
855
+ else {
856
+ baseChildren.push(child);
857
+ }
858
+ }
859
+ return { baseChildren, overlays };
860
+ }
861
+ function renderBodyFrame(children, props, context) {
862
+ const direction = props.direction === "row" ? "row" : "column";
863
+ const gap = numeric(props.gap, 0);
864
+ const frames = children.map((child) => renderTerminalFrame(child, context));
865
+ return direction === "row" ? mergeHorizontal(frames, { gap }) : mergeVertical(frames, { gap });
866
+ }
867
+ function renderFixedChildFrame(node, width, height) {
868
+ return constrainFrame(renderBodyFrame(node.children, {}, { cols: width, rows: height }), { width, height, expandFullFrameSpans: true });
869
+ }
870
+ function renderFixedCompositionFrame(node, width, height) {
871
+ if (!Number.isFinite(width) || !Number.isInteger(width) || width <= 0 || !Number.isFinite(height) || !Number.isInteger(height) || height <= 0) {
872
+ throw new RangeError("Fixed composition requires exact positive parent dimensions");
873
+ }
874
+ const fixedNodes = { top: [], bottom: [], left: [], right: [] };
875
+ const bodyChildren = [];
876
+ for (const child of node.children) {
877
+ if (child.type === "element" && child.tag === "terminal-fixed") {
878
+ fixedNodes[fixedPosition(child.props.position)].push(child);
879
+ }
880
+ else if (child.type === "element" && child.tag === "terminal-overlay") {
881
+ continue;
882
+ }
883
+ else {
884
+ bodyChildren.push(child);
885
+ }
886
+ }
887
+ const topSize = fixedNodes.top.reduce((total, fixed) => total + positiveInteger(fixed.props.size, "Fixed size"), 0);
888
+ const bottomSize = fixedNodes.bottom.reduce((total, fixed) => total + positiveInteger(fixed.props.size, "Fixed size"), 0);
889
+ const middleHeight = height - topSize - bottomSize;
890
+ if (middleHeight < 0) {
891
+ throw new RangeError("Fixed top and bottom regions exceed parent height");
892
+ }
893
+ const leftSize = fixedNodes.left.reduce((total, fixed) => total + positiveInteger(fixed.props.size, "Fixed size"), 0);
894
+ const rightSize = fixedNodes.right.reduce((total, fixed) => total + positiveInteger(fixed.props.size, "Fixed size"), 0);
895
+ const bodyWidth = width - leftSize - rightSize;
896
+ if (bodyWidth < 0) {
897
+ throw new RangeError("Fixed left and right regions exceed parent width");
898
+ }
899
+ const topFrames = fixedNodes.top.map((fixed) => renderFixedChildFrame(fixed, width, positiveInteger(fixed.props.size, "Fixed size")));
900
+ const bottomFrames = fixedNodes.bottom.map((fixed) => renderFixedChildFrame(fixed, width, positiveInteger(fixed.props.size, "Fixed size")));
901
+ const middleFrames = [];
902
+ if (middleHeight > 0) {
903
+ const leftFrames = fixedNodes.left.map((fixed) => renderFixedChildFrame(fixed, positiveInteger(fixed.props.size, "Fixed size"), middleHeight));
904
+ const rightFrames = fixedNodes.right.map((fixed) => renderFixedChildFrame(fixed, positiveInteger(fixed.props.size, "Fixed size"), middleHeight));
905
+ const bodyFrames = bodyWidth > 0
906
+ ? [constrainFrame(renderBodyFrame(bodyChildren, node.props, { cols: bodyWidth, rows: middleHeight }), { width: bodyWidth, height: middleHeight, expandFullRowSpans: true })]
907
+ : [];
908
+ middleFrames.push(constrainFrame(mergeHorizontal([...leftFrames, ...bodyFrames, ...rightFrames]), { width, height: middleHeight, expandFullRowSpans: true }));
909
+ }
910
+ const frame = mergeVertical([...topFrames, ...middleFrames, ...bottomFrames]);
911
+ return constrainFrame(frame, { width, height, expandFullFrameSpans: true });
912
+ }
913
+ function renderStandaloneFixedFrame(node, context) {
914
+ const position = fixedPosition(node.props.position);
915
+ const size = positiveInteger(node.props.size, "Fixed size");
916
+ const frame = renderBodyFrame(node.children, {}, context);
917
+ return position === "top" || position === "bottom"
918
+ ? constrainFrame(frame, { height: size })
919
+ : constrainFrame(frame, { width: size });
920
+ }
921
+ function overlayMarginValue(value, axisSize, label) {
922
+ if (typeof value === "number") {
923
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
924
+ throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
925
+ }
926
+ return value;
927
+ }
928
+ if (typeof value === "string") {
929
+ const match = value.match(/^(\d+(?:\.\d+)?)%$/);
930
+ if (!match) {
931
+ throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
932
+ }
933
+ const percent = Number(match[1]);
934
+ if (!Number.isFinite(percent) || percent < 0) {
935
+ throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
936
+ }
937
+ return Math.round(axisSize * percent / 100);
938
+ }
939
+ throw new RangeError(`${label} must be a non-negative finite integer or percentage string`);
940
+ }
941
+ function overlayMargins(margin, width, height) {
942
+ if (typeof margin === "number" || typeof margin === "string") {
943
+ const x = overlayMarginValue(margin, width, "Overlay margin");
944
+ const y = overlayMarginValue(margin, height, "Overlay margin");
945
+ return { x, y };
946
+ }
947
+ if (margin && typeof margin === "object" && !Array.isArray(margin)) {
948
+ const axes = margin;
949
+ return {
950
+ x: overlayMarginValue(axes.x, width, "Overlay margin x"),
951
+ y: overlayMarginValue(axes.y, height, "Overlay margin y")
952
+ };
953
+ }
954
+ throw new RangeError("Overlay margin is required");
955
+ }
956
+ function overlayGeometry(node, width, height) {
957
+ const margin = overlayMargins(node.props.margin, width, height);
958
+ const overlayWidth = width - margin.x * 2;
959
+ const overlayHeight = height - margin.y * 2;
960
+ if (overlayWidth < 1 || overlayHeight < 1) {
961
+ throw new RangeError("Overlay margin leaves no renderable area");
962
+ }
963
+ return {
964
+ x: margin.x + 1,
965
+ y: margin.y + 1,
966
+ width: overlayWidth,
967
+ height: overlayHeight
968
+ };
969
+ }
970
+ function resolveOverlayBackdropSpans(node, width, height, context) {
971
+ const backdrop = node.props.backdrop;
972
+ if (backdrop === false || typeof backdrop === "undefined") {
973
+ return [];
974
+ }
975
+ const styleValue = backdrop === true ? "overlay.backdrop" : backdrop;
976
+ const resolvedStyle = resolveTerminalStyle(styleValue, context?.theme);
977
+ const span = typeof styleValue === "string" ? styleSpan(styleValue) : styleSpan("#style", resolvedStyle);
978
+ return fullFrameSpans([span.kind], width, height).map((fullSpan) => (typeof span.style === "undefined" ? fullSpan : { ...fullSpan, style: span.style }));
979
+ }
980
+ function renderOverlayChildFrame(node, width, height, context) {
981
+ const geometry = overlayGeometry(node, width, height);
982
+ let frame = constrainFrame(renderBodyFrame(node.children, {}, { cols: geometry.width, rows: geometry.height, theme: context?.theme }), { width: geometry.width, height: geometry.height, expandFullRowSpans: true });
983
+ frame = addContainerStyleSpans(frame, node, resolveNodeStyle(node, context));
984
+ if (node.props.id && isFocusable(node)) {
985
+ frame = addFocusableHitbox(frame, node);
986
+ }
987
+ return { frame, geometry };
988
+ }
989
+ function orderedDirectOverlays(overlays) {
990
+ return overlays.map((overlay, sourceOrder) => ({ overlay, sourceOrder })).sort((a, b) => a.sourceOrder - b.sourceOrder).map(({ overlay }) => overlay);
991
+ }
992
+ function applyDirectOverlays(base, overlays, context) {
993
+ let frame = base;
994
+ const width = Math.max(1, getFrameWidth(base));
995
+ const height = Math.max(1, getFrameHeight(base));
996
+ for (const overlay of orderedDirectOverlays(overlays)) {
997
+ const rendered = renderOverlayChildFrame(overlay, width, height, context);
998
+ frame = overlayFrame(frame, rendered.frame, {
999
+ ...rendered.geometry,
1000
+ backdropSpans: resolveOverlayBackdropSpans(overlay, width, height, context)
1001
+ });
1002
+ }
1003
+ return frame;
1004
+ }
1005
+ function renderScreenFrame(node, context) {
1006
+ const { baseChildren, overlays } = splitOverlayChildren(node.children);
1007
+ let base;
1008
+ if (hasDirectFixedChildren(node)) {
1009
+ if (!context) {
1010
+ throw new RangeError("Screen with direct Fixed children requires exact render context dimensions");
1011
+ }
1012
+ if (node.props.title) {
1013
+ const title = constrainFrame(createFrame([plainText(node.props.title)]), { width: context.cols, height: 1 });
1014
+ const remainingRows = context.rows - 1;
1015
+ base = remainingRows > 0
1016
+ ? constrainFrame(mergeVertical([title, renderFixedCompositionFrame(node, context.cols, remainingRows)]), { width: context.cols, height: context.rows })
1017
+ : constrainFrame(title, { width: context.cols, height: context.rows });
1018
+ }
1019
+ else {
1020
+ base = renderFixedCompositionFrame(node, context.cols, context.rows);
1021
+ }
1022
+ }
1023
+ else {
1024
+ const parts = [];
1025
+ if (node.props.title) {
1026
+ parts.push(createFrame([plainText(node.props.title)]));
1027
+ }
1028
+ const childContext = context && node.props.title
1029
+ ? { cols: context.cols, rows: context.rows - 1, theme: context.theme }
1030
+ : context;
1031
+ if (!context || !node.props.title || context.rows > 1) {
1032
+ parts.push(...baseChildren.map((child) => renderTerminalFrame(child, childContext)));
1033
+ }
1034
+ base = mergeVertical(parts);
1035
+ if (context && overlays.length) {
1036
+ base = constrainFrame(base, { width: context.cols, height: context.rows, expandFullRowSpans: true });
1037
+ }
1038
+ }
1039
+ return overlays.length ? applyDirectOverlays(base, overlays, context) : base;
1040
+ }
1041
+ function renderPaneFrame(node, context) {
1042
+ const { baseChildren, overlays } = splitOverlayChildren(node.children);
1043
+ const dimensions = resolveBlockLayoutDimensions(node, "Pane", context, { exactHeightFromContext: hasDirectFixedChildren(node) });
1044
+ let frame;
1045
+ if (hasDirectFixedChildren(node)) {
1046
+ const width = decoratedContentDimension(node, "width", dimensions.width ?? positiveInteger(node.props.width, "Pane width"), context);
1047
+ const height = decoratedContentDimension(node, "height", dimensions.height ?? positiveInteger(node.props.height, "Pane height"), context);
1048
+ frame = renderFixedCompositionFrame(node, width, height);
1049
+ frame = decorateContainerFrame(frame, node, { constrain: true, ...dimensions }, context);
1050
+ }
1051
+ else {
1052
+ const childContext = resolveContainerChildContext(node, dimensions, context);
1053
+ frame = renderBodyFrame(baseChildren, node.props, childContext);
1054
+ frame = decorateContainerFrame(frame, node, { constrain: true, ...dimensions }, context);
1055
+ }
1056
+ return overlays.length ? applyDirectOverlays(frame, overlays, context) : frame;
1057
+ }
1058
+ function renderStandaloneOverlayFrame(node, context) {
1059
+ if (!context) {
1060
+ throw new RangeError("Standalone Overlay requires exact render context dimensions");
1061
+ }
1062
+ const rendered = renderOverlayChildFrame(node, context.cols, context.rows, context);
1063
+ return rendered.frame;
1064
+ }
1065
+ function renderLogViewFrame(node, context) {
1066
+ const dimensions = resolveLayoutDimensions(node, "LogView", context);
1067
+ const entries = Array.isArray(node.props.entries) ? node.props.entries : [];
1068
+ const lines = [];
1069
+ if (entries.length === 0) {
1070
+ lines.push(plainText(node.props.emptyText ?? ""));
1071
+ }
1072
+ else {
1073
+ for (let index = 0; index < entries.length; index += 1) {
1074
+ const entry = entries[index];
1075
+ const value = typeof node.props.renderEntry === "function"
1076
+ ? node.props.renderEntry(entry, index)
1077
+ : entry?.content ?? "";
1078
+ lines.push(...plainText(value).split("\n"));
1079
+ }
1080
+ }
1081
+ const resolved = resolveNodeStyle(node, context);
1082
+ const layoutStyle = resolveNodeLayoutStyle(node, context);
1083
+ const padding = normalizeSpacing(layoutStyle?.padding ?? node.props.padding, "LogView padding");
1084
+ const border = normalizeBorder(layoutStyle?.border);
1085
+ const horizontalDecoration = padding.left + padding.right + (border.left ? 1 : 0) + (border.right ? 1 : 0);
1086
+ const verticalDecoration = padding.top + padding.bottom + (border.top ? 1 : 0) + (border.bottom ? 1 : 0);
1087
+ const innerWidth = typeof dimensions.width === "undefined" ? undefined : dimensions.width - horizontalDecoration;
1088
+ const innerHeight = typeof dimensions.height === "undefined" ? undefined : dimensions.height - verticalDecoration;
1089
+ let frame = createFrame(lines);
1090
+ if ((typeof innerWidth === "number" && innerWidth <= 0) || (typeof innerHeight === "number" && innerHeight <= 0)) {
1091
+ frame = createFrame([""]);
1092
+ }
1093
+ else {
1094
+ if (typeof innerHeight !== "undefined") {
1095
+ const offset = node.props.followTail === true ? Math.max(0, getFrameHeight(frame) - innerHeight) : 0;
1096
+ frame = cropFrame(frame, offset, innerHeight);
1097
+ while (getFrameHeight(frame) < innerHeight) {
1098
+ frame = createFrame([...frame.lines, ""], frame.hitboxes, frame.cursor, frame.spans);
1099
+ }
1100
+ }
1101
+ if (typeof innerWidth !== "undefined") {
1102
+ frame = constrainFrame(frame, { width: innerWidth, height: innerHeight, expandFullFrameSpans: true });
1103
+ }
1104
+ }
1105
+ frame = padFrameSides(frame, padding);
1106
+ frame = addBorder(frame, border);
1107
+ if (typeof dimensions.width !== "undefined") {
1108
+ frame = constrainFrame(frame, { width: dimensions.width, height: dimensions.height, expandFullFrameSpans: true });
1109
+ }
1110
+ else if (typeof dimensions.height !== "undefined") {
1111
+ frame = cropFrame(frame, 0, dimensions.height);
1112
+ while (typeof dimensions.height !== "undefined" && getFrameHeight(frame) < dimensions.height) {
1113
+ frame = createFrame([...frame.lines, ""], frame.hitboxes, frame.cursor, frame.spans);
1114
+ }
1115
+ }
1116
+ frame = addContainerStyleSpans(frame, node, resolved);
1117
+ if (node.props.id && isFocusable(node)) {
1118
+ frame = addFocusableHitbox(frame, node);
1119
+ }
1120
+ return frame;
1121
+ }
1122
+ function renderSeparatedRowFrame(frames, separator = " | ") {
1123
+ if (!frames.length) {
1124
+ return createFrame([""]);
1125
+ }
1126
+ const height = frames.reduce((max, frame) => Math.max(max, getFrameHeight(frame)), 0);
1127
+ const widths = frames.map(getFrameWidth);
1128
+ const lines = new Array(height).fill("");
1129
+ const hitboxes = [];
1130
+ const spans = [];
1131
+ let cursor = null;
1132
+ let xOffset = 0;
1133
+ for (let index = 0; index < frames.length; index += 1) {
1134
+ const frame = frames[index];
1135
+ const width = widths[index];
1136
+ for (let row = 0; row < height; row += 1) {
1137
+ lines[row] += padEndTerminalCells(frame.lines[row] || "", width);
1138
+ if (index < frames.length - 1) {
1139
+ lines[row] += separator;
1140
+ }
1141
+ }
1142
+ const shifted = shiftFrame(fitFrame(frame, width, height, { expandFullFrameSpans: true }), xOffset, 0);
1143
+ hitboxes.push(...shifted.hitboxes);
1144
+ spans.push(...shifted.spans);
1145
+ if (!cursor && frame.cursor) {
1146
+ cursor = { x: frame.cursor.x + xOffset, y: frame.cursor.y };
1147
+ }
1148
+ xOffset += width + (index < frames.length - 1 ? terminalCellWidth(separator) : 0);
1149
+ }
1150
+ return createFrame(lines, hitboxes, cursor, spans);
1151
+ }
1152
+ function renderElementFrame(node, context) {
1153
+ notifyLayoutContextProbe(node, context);
1154
+ switch (node.tag) {
1155
+ case "terminal-screen": {
1156
+ return renderScreenFrame(node, context);
1157
+ }
1158
+ case "terminal-box":
1159
+ case "terminal-view":
1160
+ case "terminal-pane":
1161
+ case "terminal-scroll": {
1162
+ let frame;
1163
+ if (node.tag === "terminal-pane" && (hasDirectFixedChildren(node) || hasDirectOverlayChildren(node))) {
1164
+ return renderPaneFrame(node, context);
1165
+ }
1166
+ const label = node.tag === "terminal-box" ? "Box" : node.tag === "terminal-view" ? "View" : node.tag === "terminal-scroll" ? "ScrollView" : "Pane";
1167
+ const dimensions = node.tag === "terminal-scroll"
1168
+ ? resolveLayoutDimensions(node, label, context)
1169
+ : resolveBlockLayoutDimensions(node, label, context);
1170
+ const childContext = resolveContainerChildContext(node, dimensions, context);
1171
+ frame = renderBodyFrame(node.children, node.props, childContext);
1172
+ frame = decorateContainerFrame(frame, node, { constrain: node.tag === "terminal-box" || node.tag === "terminal-view" || node.tag === "terminal-pane", ...dimensions }, context);
1173
+ if (node.tag === "terminal-scroll") {
1174
+ const offset = numeric(node.props.__scrollOffset, 0);
1175
+ if (typeof dimensions.width !== "undefined") {
1176
+ frame = constrainFrame(frame, { width: dimensions.width, expandFullFrameSpans: true });
1177
+ }
1178
+ const height = numeric(dimensions.height ?? node.props.height, getFrameHeight(frame));
1179
+ frame = cropFrame(frame, offset, height || getFrameHeight(frame));
1180
+ const highlightRows = Array.isArray(node.props.highlightRows) ? node.props.highlightRows.map((value) => Number(value)) : [];
1181
+ const hoveredRow = typeof node.props.__hoveredRow === "number" ? Number(node.props.__hoveredRow) : -1;
1182
+ const spans = frame.spans.slice();
1183
+ for (let index = 0; index < frame.lines.length; index += 1) {
1184
+ const row = index + 1;
1185
+ const width = terminalCellWidth(frame.lines[index]) + 1;
1186
+ if (highlightRows.includes(row)) {
1187
+ spans.push({ kind: "highlight", x1: 1, x2: width, y: row });
1188
+ }
1189
+ if (hoveredRow === row) {
1190
+ spans.push({ kind: "hover", x1: 1, x2: width, y: row });
1191
+ }
1192
+ }
1193
+ frame = createFrame(frame.lines, frame.hitboxes, frame.cursor, spans);
1194
+ }
1195
+ return frame;
1196
+ }
1197
+ case "terminal-split":
1198
+ return renderSplitFrame(node, context);
1199
+ case "terminal-fixed":
1200
+ return renderStandaloneFixedFrame(node, context);
1201
+ case "terminal-overlay":
1202
+ return renderStandaloneOverlayFrame(node, context);
1203
+ case "terminal-log-view":
1204
+ return renderLogViewFrame(node, context);
1205
+ case "terminal-list": {
1206
+ const items = Array.isArray(node.props.items) ? node.props.items : [];
1207
+ const activeIndex = clampListIndex(numeric(node.props.__activeIndex ?? node.props.__selectedIndex, 0), items.length);
1208
+ const selectedIndex = typeof node.props.__selectedIndex === "number" ? clampListIndex(Number(node.props.__selectedIndex), items.length) : null;
1209
+ const hoveredIndex = typeof node.props.__hoveredIndex === "number" ? Number(node.props.__hoveredIndex) : -1;
1210
+ const range = listVirtualRange(node, items.length, context);
1211
+ const layoutStyle = resolveLayoutStyle("list.base", node, context);
1212
+ const padding = normalizeSpacing(layoutStyle.padding, "List padding");
1213
+ const border = normalizeBorder(layoutStyle.border);
1214
+ const horizontalDecoration = padding.left + padding.right + (border.left ? 1 : 0) + (border.right ? 1 : 0);
1215
+ const wrapWidth = typeof context?.cols === "number" ? Math.max(1, context.cols - horizontalDecoration) : 1;
1216
+ const visibleLines = [];
1217
+ const itemIndexes = [];
1218
+ const childHitboxes = [];
1219
+ for (let index = range.start; index < range.end; index += 1) {
1220
+ const item = items[index];
1221
+ const itemFrame = renderListItemFrame(node, item, index, index - range.visibleStart, activeIndex, selectedIndex, wrapWidth, context);
1222
+ const rowOffset = visibleLines.length;
1223
+ visibleLines.push(...itemFrame.lines);
1224
+ for (let row = 0; row < itemFrame.lines.length; row += 1) {
1225
+ itemIndexes.push(index);
1226
+ }
1227
+ childHitboxes.push(...shiftFrame(itemFrame, 0, rowOffset).hitboxes);
1228
+ }
1229
+ if (!visibleLines.length) {
1230
+ visibleLines.push("");
1231
+ itemIndexes.push(0);
1232
+ }
1233
+ const frameHeight = node.props.virtualized && node.props.wrap === true
1234
+ ? positiveDimension(node.props.height, "height") ?? (typeof context?.rows === "number" ? Math.max(1, context.rows) : undefined)
1235
+ : undefined;
1236
+ const contentHeight = typeof frameHeight === "number"
1237
+ ? Math.max(1, frameHeight - padding.top - padding.bottom - (border.top ? 1 : 0) - (border.bottom ? 1 : 0))
1238
+ : visibleLines.length;
1239
+ let visibleLineStart = 0;
1240
+ if (node.props.virtualized && node.props.wrap === true && visibleLines.length > contentHeight) {
1241
+ const activeLineIndex = itemIndexes.findIndex((sourceIndex) => sourceIndex === activeIndex);
1242
+ if (activeLineIndex >= contentHeight) {
1243
+ visibleLineStart = activeLineIndex - contentHeight + 1;
1244
+ }
1245
+ }
1246
+ const frameLines = visibleLines.slice(visibleLineStart, visibleLineStart + contentHeight);
1247
+ const frameItemIndexes = itemIndexes.slice(visibleLineStart, visibleLineStart + contentHeight);
1248
+ const frameChildHitboxes = childHitboxes
1249
+ .filter((box) => box.y2 > visibleLineStart && box.y1 <= visibleLineStart + contentHeight)
1250
+ .map((box) => ({
1251
+ ...box,
1252
+ y1: Math.max(1, box.y1 - visibleLineStart),
1253
+ y2: Math.min(contentHeight, box.y2 - visibleLineStart),
1254
+ contentY: typeof box.contentY === "number" ? Math.max(1, box.contentY - visibleLineStart) : undefined
1255
+ }));
1256
+ const decorated = addBorder(padFrameSides(createFrame(frameLines, frameChildHitboxes), padding), border);
1257
+ const width = Math.max(1, getFrameWidth(decorated));
1258
+ const height = Math.max(1, getFrameHeight(decorated));
1259
+ const itemY = 1 + (border.top ? 1 : 0) + padding.top;
1260
+ const spans = [];
1261
+ for (let index = 0; index < frameLines.length; index += 1) {
1262
+ const sourceIndex = frameItemIndexes[index];
1263
+ const y = itemY + index;
1264
+ spans.push(markFullRowSpan({ kind: "list.base", x1: 1, x2: width + 1, y }));
1265
+ if (selectedIndex !== null && selectedIndex !== activeIndex && sourceIndex === selectedIndex) {
1266
+ spans.push(markFullRowSpan({ kind: "list.selected", x1: 1, x2: width + 1, y }));
1267
+ }
1268
+ if (sourceIndex === activeIndex) {
1269
+ spans.push(markFullRowSpan({ kind: "list.current", x1: 1, x2: width + 1, y }));
1270
+ }
1271
+ if (sourceIndex === hoveredIndex) {
1272
+ spans.push(markFullRowSpan({ kind: "list.hover", x1: 1, x2: width + 1, y }));
1273
+ }
1274
+ }
1275
+ const listHitboxes = [];
1276
+ const itemHitboxes = [];
1277
+ if (node.props.id) {
1278
+ listHitboxes.push({
1279
+ id: node.props.id,
1280
+ tag: node.tag,
1281
+ x1: 1,
1282
+ x2: width,
1283
+ y1: 1,
1284
+ y2: Math.min(height, typeof frameHeight === "number" ? frameHeight : height),
1285
+ itemOffset: range.start,
1286
+ itemIndexes: frameItemIndexes,
1287
+ contentY: itemY
1288
+ });
1289
+ let itemStart = 0;
1290
+ while (itemStart < frameItemIndexes.length) {
1291
+ const sourceIndex = frameItemIndexes[itemStart];
1292
+ let itemEnd = itemStart;
1293
+ while (itemEnd + 1 < frameItemIndexes.length && frameItemIndexes[itemEnd + 1] === sourceIndex) {
1294
+ itemEnd += 1;
1295
+ }
1296
+ itemHitboxes.push({
1297
+ id: node.props.id,
1298
+ tag: node.tag,
1299
+ x1: 1,
1300
+ x2: width,
1301
+ y1: itemY + itemStart,
1302
+ y2: itemY + itemEnd,
1303
+ itemOffset: range.start,
1304
+ __listItemIndex: sourceIndex,
1305
+ itemIndexes: new Array(itemEnd - itemStart + 1).fill(sourceIndex)
1306
+ });
1307
+ itemStart = itemEnd + 1;
1308
+ }
1309
+ }
1310
+ const frame = createFrame(decorated.lines, [...listHitboxes, ...itemHitboxes, ...decorated.hitboxes], decorated.cursor, spans);
1311
+ const styled = addFullFrameSpans(frame, resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
1312
+ return typeof frameHeight === "number" ? constrainFrame(styled, { height: frameHeight, expandFullFrameSpans: true }) : styled;
1313
+ }
1314
+ case "terminal-table":
1315
+ return renderTableFrame(node, context);
1316
+ case "terminal-row":
1317
+ return renderSeparatedRowFrame(node.children.map((child) => renderTerminalFrame(child, context)), plainText(node.props.separator || " | "));
1318
+ case "terminal-td": {
1319
+ const frame = mergeVertical(node.children.map((child) => renderTerminalFrame(child, context)));
1320
+ const resolved = resolveNodeStyle(node, context);
1321
+ const layoutStyle = resolveNodeLayoutStyle(node, context);
1322
+ const padding = normalizeSpacing(layoutStyle?.padding ?? node.props.padding, "Td padding");
1323
+ const border = normalizeBorder(layoutStyle?.border);
1324
+ return addFullFrameSpans(addBorder(padFrameSides(frame, padding), border), resolved.spanKinds);
1325
+ }
1326
+ case "terminal-text": {
1327
+ const value = typeof node.props.value !== "undefined" ? plainText(node.props.value) : plainText(node.children.map(textContent).join(""));
1328
+ const frame = createFrame(value.split("\n"));
1329
+ return addFullFrameSpans(frame, resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
1330
+ }
1331
+ case "terminal-input": {
1332
+ const value = typeof node.props.value !== "undefined" ? node.props.value : node.props.placeholder || "";
1333
+ const stringValue = plainText(value);
1334
+ const displayValue = !node.props.__focused && stringValue.length === 0 && typeof node.props.placeholder !== "undefined"
1335
+ ? plainText(node.props.placeholder)
1336
+ : stringValue;
1337
+ const layoutStyle = resolveLayoutStyle("input.base", node, context);
1338
+ const inputPadding = normalizeSpacing(layoutStyle.padding, "Input padding");
1339
+ const inputBorder = normalizeBorder(layoutStyle.border);
1340
+ const dimensions = {
1341
+ width: positiveDimension(node.props.width, "width") ?? contextBackedDimension(context, "width", "Input")
1342
+ };
1343
+ const textStartX = (inputBorder.left ? 1 : 0) + inputPadding.left + 1;
1344
+ if (!node.props.__focused) {
1345
+ const line = `${" ".repeat(inputPadding.left)}${displayValue}${" ".repeat(inputPadding.right)}`;
1346
+ const decorated = addBorder(createFrame([line]), inputBorder);
1347
+ const constrained = typeof dimensions.width === "number" ? constrainFrame(decorated, { width: dimensions.width, expandFullFrameSpans: true }) : decorated;
1348
+ const width = Math.max(1, getFrameWidth(constrained));
1349
+ const height = Math.max(1, getFrameHeight(constrained));
1350
+ const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, ...interactiveTextMetadata(stringValue) }] : [];
1351
+ const spans = fullFrameSpans(["input.base"], width, height);
1352
+ return addFullFrameSpans(createFrame(constrained.lines, hitboxes, constrained.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
1353
+ }
1354
+ const rendered = renderInputLine(stringValue, node.props.__inputState || { cursor: stringValue.length, anchor: stringValue.length }, inputPadding);
1355
+ const visibleInputWidth = typeof dimensions.width === "number"
1356
+ ? Math.max(1, dimensions.width - (inputBorder.left ? 1 : 0) - (inputBorder.right ? 1 : 0))
1357
+ : getFrameWidth(createFrame([rendered.line]));
1358
+ const visibleRendered = scrollFocusedInputLine(rendered, visibleInputWidth);
1359
+ const decorated = addBorder(createFrame([visibleRendered.line], [], visibleRendered.cursor, visibleRendered.spans), inputBorder);
1360
+ const constrained = typeof dimensions.width === "number" ? constrainFrame(decorated, { width: dimensions.width, expandFullFrameSpans: true }) : decorated;
1361
+ const width = Math.max(1, getFrameWidth(constrained));
1362
+ const height = Math.max(1, getFrameHeight(constrained));
1363
+ const hitboxes = node.props.id ? [{ id: node.props.id, tag: node.tag, x1: 1, x2: width, y1: 1, y2: height, textStartX, ...interactiveTextMetadata(stringValue) }] : [];
1364
+ const spans = [...fullFrameSpans(["input.base", "input.focus"], width, height), ...constrained.spans];
1365
+ return addFullFrameSpans(createFrame(constrained.lines, hitboxes, constrained.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
1366
+ }
1367
+ case "terminal-editor":
1368
+ return addFullFrameSpans(renderEditorFrame(node, context), resolveNodeStyle(node, context).spanKinds);
1369
+ case "terminal-button": {
1370
+ const label = typeof node.props.label !== "undefined" ? plainText(node.props.label) : plainText(node.children.map(textContent).join(""));
1371
+ const layoutStyle = resolveLayoutStyle("button.base", node, context);
1372
+ const decorated = decoratedControlFrame([String(label)], layoutStyle);
1373
+ const width = Math.max(1, getFrameWidth(decorated));
1374
+ const height = Math.max(1, getFrameHeight(decorated));
1375
+ 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 }] : [];
1376
+ const kinds = ["button.base", ...nodeStates(node).map((state) => `button.${state}`)];
1377
+ const spans = fullFrameSpans(kinds, width, height);
1378
+ return addFullFrameSpans(createFrame(decorated.lines, hitboxes, decorated.cursor, spans), resolveNodeStyle(node, context, { includeBase: false }).spanKinds);
1379
+ }
1380
+ default:
1381
+ return mergeVertical(node.children.map((child) => renderTerminalFrame(child, context)));
1382
+ }
1383
+ }
1384
+ export function renderTerminalFrame(node, context) {
1385
+ if (node.type === "text") {
1386
+ return createFrame([plainText(node.value)]);
1387
+ }
1388
+ return renderElementFrame(node, context);
1389
+ }
1390
+ export function renderTerminalNode(node, context) {
1391
+ return renderTerminalFrame(node, context).lines.join("\n");
1392
+ }
1393
+ export function renderTerminal(input, context) {
1394
+ const renderContext = validateRenderContext(context);
1395
+ return renderValyrianTerminal(input)
1396
+ .map((node) => renderTerminalNode(node, renderContext))
1397
+ .filter(Boolean)
1398
+ .join("\n")
1399
+ .trimEnd();
1400
+ }
1401
+ //# sourceMappingURL=render-internal.js.map