@vizejs/fresco 0.101.0 → 0.104.0

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,1463 @@
1
+ import { format } from "node:util";
2
+ import { computed, createRenderer, defineComponent, h, inject, isRef, onUnmounted, ref, shallowRef, watch } from "@vue/runtime-core";
3
+ //#region src/accessibility.ts
4
+ const SCREEN_READER_KEY = Symbol("fresco-screen-reader");
5
+ function isScreenReaderEnabledByDefault() {
6
+ return process.env.INK_SCREEN_READER === "true" || process.env.FRESCO_SCREEN_READER === "true";
7
+ }
8
+ function stringValue$1(value) {
9
+ if (typeof value === "string" || typeof value === "number") return String(value);
10
+ }
11
+ function nodeText$1(node) {
12
+ const ariaLabel = stringValue$1(node.props["aria-label"]);
13
+ if (ariaLabel !== void 0) return ariaLabel;
14
+ if (node.text !== void 0) return node.text;
15
+ const propText = node.props.text ?? node.props.content;
16
+ if (typeof propText === "string" || typeof propText === "number") return String(propText);
17
+ if (node.type === "input") {
18
+ const value = node.props.value;
19
+ const placeholder = node.props.placeholder;
20
+ if (typeof value === "string" || typeof value === "number") return String(value);
21
+ if (typeof placeholder === "string" || typeof placeholder === "number") return String(placeholder);
22
+ }
23
+ return "";
24
+ }
25
+ function flexDirection(node) {
26
+ const style = node.props.style ?? {};
27
+ return stringValue$1(style.flexDirection ?? style.flex_direction) ?? "column";
28
+ }
29
+ function ariaRole(node) {
30
+ return stringValue$1(node.props["aria-role"]);
31
+ }
32
+ function ariaState(node) {
33
+ const state = node.props["aria-state"];
34
+ if (!state || typeof state !== "object") return void 0;
35
+ return state;
36
+ }
37
+ function isAriaHidden(node) {
38
+ return node.props["aria-hidden"] === true;
39
+ }
40
+ function treeToScreenReaderString(node, options = {}) {
41
+ if (isAriaHidden(node)) return "";
42
+ let output = "";
43
+ const text = nodeText$1(node);
44
+ if (node.type === "text" || node.type === "input") output = `${text}${node.children.map((child) => treeToScreenReaderString(child, options)).join("")}`;
45
+ else if (text) output = text;
46
+ else {
47
+ const direction = flexDirection(node);
48
+ const separator = direction === "row" || direction === "row-reverse" ? " " : "\n";
49
+ const children = direction === "row-reverse" || direction === "column-reverse" ? [...node.children].reverse() : node.children;
50
+ const role = ariaRole(node);
51
+ output = children.map((child) => treeToScreenReaderString(child, { parentRole: role })).filter(Boolean).join(separator);
52
+ }
53
+ const state = ariaState(node);
54
+ if (state) {
55
+ const stateDescription = Object.keys(state).filter((key) => state[key]).join(", ");
56
+ if (stateDescription) output = `(${stateDescription}) ${output}`;
57
+ }
58
+ const role = ariaRole(node);
59
+ if (role && role !== options.parentRole) output = `${role}: ${output}`;
60
+ return output;
61
+ }
62
+ function treeToScreenReaderRenderNodes(root) {
63
+ const output = treeToScreenReaderString(root);
64
+ const rootStyle = root.props.style ?? {};
65
+ const nodes = [{
66
+ id: root.id,
67
+ nodeType: "root",
68
+ style: {
69
+ ...rootStyle,
70
+ flexDirection: "column"
71
+ }
72
+ }];
73
+ if (!output) return nodes;
74
+ const textId = root.id - 1;
75
+ nodes[0].children = [textId];
76
+ nodes.push({
77
+ id: textId,
78
+ nodeType: "text",
79
+ text: output,
80
+ wrap: true,
81
+ wrapMode: "wrap"
82
+ });
83
+ return nodes;
84
+ }
85
+ //#endregion
86
+ //#region src/composables/useApp.ts
87
+ /**
88
+ * useApp - App context composable
89
+ */
90
+ const APP_KEY = Symbol("fresco-app");
91
+ /**
92
+ * Create app context (use at app root)
93
+ */
94
+ function createAppContext(controls = {}) {
95
+ const width = ref(controls.width ?? 80);
96
+ const height = ref(controls.height ?? 24);
97
+ const isRunning = ref(true);
98
+ const exit = (errorOrResult) => {
99
+ isRunning.value = false;
100
+ controls.exit?.(errorOrResult);
101
+ };
102
+ const render = () => {
103
+ controls.render?.();
104
+ };
105
+ const clear = () => {
106
+ controls.clear?.();
107
+ };
108
+ const waitUntilRenderFlush = () => {
109
+ return controls.waitUntilRenderFlush?.() ?? Promise.resolve();
110
+ };
111
+ const stdout = controls.stdout ?? (typeof process !== "undefined" ? process.stdout : void 0);
112
+ if (stdout) {
113
+ width.value = controls.width ?? stdout.columns ?? 80;
114
+ height.value = controls.height ?? stdout.rows ?? 24;
115
+ stdout.on?.("resize", () => {
116
+ width.value = stdout.columns ?? 80;
117
+ height.value = stdout.rows ?? 24;
118
+ });
119
+ }
120
+ return {
121
+ width,
122
+ height,
123
+ isRunning,
124
+ exit,
125
+ render,
126
+ clear,
127
+ waitUntilRenderFlush
128
+ };
129
+ }
130
+ /**
131
+ * Use app context
132
+ */
133
+ function useApp() {
134
+ const context = inject(APP_KEY);
135
+ if (!context) return {
136
+ width: ref(80),
137
+ height: ref(24),
138
+ isRunning: ref(false),
139
+ exit: () => {},
140
+ render: () => {},
141
+ clear: () => {},
142
+ waitUntilRenderFlush: () => Promise.resolve()
143
+ };
144
+ return context;
145
+ }
146
+ //#endregion
147
+ //#region src/composables/useFocus.ts
148
+ /**
149
+ * useFocus - Focus management composable
150
+ */
151
+ const FOCUS_KEY = Symbol("fresco-focus");
152
+ function activeFocusables(focusables) {
153
+ return focusables.filter((focusable) => focusable.isActive);
154
+ }
155
+ /**
156
+ * Create a focus manager (use at app root)
157
+ */
158
+ function createFocusManager() {
159
+ const focusedId = ref(null);
160
+ const activeId = computed(() => focusedId.value ?? void 0);
161
+ const focusables = ref([]);
162
+ const focusableIds = computed(() => activeFocusables(focusables.value).map(({ id }) => id));
163
+ const isEnabled = ref(true);
164
+ const focus = (id) => {
165
+ const target = focusables.value.find((focusable) => focusable.id === id);
166
+ if (isEnabled.value && target?.isActive) focusedId.value = id;
167
+ };
168
+ const focusNext = () => {
169
+ const active = activeFocusables(focusables.value);
170
+ if (!isEnabled.value || active.length === 0) return;
171
+ const currentIndex = focusedId.value ? focusables.value.findIndex((focusable) => focusable.id === focusedId.value) : -1;
172
+ focusedId.value = focusables.value.slice(currentIndex + 1).find((focusable) => focusable.isActive)?.id ?? active[0]?.id ?? null;
173
+ };
174
+ const focusPrevious = () => {
175
+ const active = activeFocusables(focusables.value);
176
+ if (!isEnabled.value || active.length === 0) return;
177
+ const currentIndex = focusedId.value ? focusables.value.findIndex((focusable) => focusable.id === focusedId.value) : focusables.value.length;
178
+ focusedId.value = focusables.value.slice(0, currentIndex < 0 ? 0 : currentIndex).findLast((focusable) => focusable.isActive)?.id ?? active.at(-1)?.id ?? null;
179
+ };
180
+ const register = (id, options = {}) => {
181
+ const existing = focusables.value.find((focusable) => focusable.id === id);
182
+ if (existing) existing.isActive = options.isActive ?? existing.isActive;
183
+ else focusables.value.push({
184
+ id,
185
+ isActive: options.isActive ?? true
186
+ });
187
+ if (options.autoFocus && !focusedId.value && options.isActive !== false) focus(id);
188
+ };
189
+ const unregister = (id) => {
190
+ const index = focusables.value.findIndex((focusable) => focusable.id === id);
191
+ if (index !== -1) {
192
+ focusables.value.splice(index, 1);
193
+ if (focusedId.value === id) focusedId.value = null;
194
+ }
195
+ };
196
+ const setActive = (id, isActive) => {
197
+ const focusable = focusables.value.find((item) => item.id === id);
198
+ if (!focusable) return;
199
+ focusable.isActive = isActive;
200
+ if (!isActive && focusedId.value === id) focusedId.value = null;
201
+ };
202
+ const enableFocus = () => {
203
+ isEnabled.value = true;
204
+ };
205
+ const disableFocus = () => {
206
+ isEnabled.value = false;
207
+ focusedId.value = null;
208
+ };
209
+ return {
210
+ focusedId,
211
+ activeId,
212
+ focusableIds,
213
+ isEnabled,
214
+ enableFocus,
215
+ disableFocus,
216
+ focus,
217
+ focusNext,
218
+ focusPrevious,
219
+ register,
220
+ unregister,
221
+ setActive
222
+ };
223
+ }
224
+ /**
225
+ * Use focus management
226
+ */
227
+ function useFocus(options = {}) {
228
+ const { autoFocus = false, id = `focus-${Math.random().toString(36).slice(2)}`, isActive: isActiveOption = true } = options;
229
+ const manager = inject(FOCUS_KEY, null);
230
+ const localFocused = ref(autoFocus);
231
+ const active = isRef(isActiveOption) ? isActiveOption : ref(isActiveOption);
232
+ const isFocused = computed(() => {
233
+ if (manager) return manager.isEnabled.value && manager.focusedId.value === id;
234
+ return active.value && localFocused.value;
235
+ });
236
+ const focus = (targetId = id) => {
237
+ if (manager) manager.focus(targetId);
238
+ else if (targetId === id) localFocused.value = true;
239
+ };
240
+ const blur = () => {
241
+ if (manager) {
242
+ if (manager.focusedId.value === id) manager.focusedId.value = null;
243
+ } else localFocused.value = false;
244
+ };
245
+ if (manager) {
246
+ manager.register(id, {
247
+ isActive: active.value,
248
+ autoFocus
249
+ });
250
+ watch(active, (enabled) => {
251
+ manager.setActive(id, enabled);
252
+ if (enabled && autoFocus && !manager.focusedId.value) manager.focus(id);
253
+ }, { immediate: false });
254
+ onUnmounted(() => {
255
+ manager.unregister(id);
256
+ });
257
+ }
258
+ return {
259
+ id,
260
+ isFocused,
261
+ focus,
262
+ blur
263
+ };
264
+ }
265
+ /**
266
+ * Use global focus manager controls.
267
+ */
268
+ function useFocusManager() {
269
+ const manager = inject(FOCUS_KEY, null);
270
+ if (!manager) {
271
+ const empty = ref([]);
272
+ return {
273
+ enableFocus: () => {},
274
+ disableFocus: () => {},
275
+ focusNext: () => {},
276
+ focusPrevious: () => {},
277
+ focus: (_id) => {},
278
+ activeId: ref(void 0),
279
+ focusableIds: empty
280
+ };
281
+ }
282
+ return {
283
+ enableFocus: manager.enableFocus,
284
+ disableFocus: manager.disableFocus,
285
+ focusNext: manager.focusNext,
286
+ focusPrevious: manager.focusPrevious,
287
+ focus: manager.focus,
288
+ activeId: manager.activeId,
289
+ focusableIds: manager.focusableIds
290
+ };
291
+ }
292
+ //#endregion
293
+ //#region src/composables/useStreams.ts
294
+ /**
295
+ * Stream composables matching Ink's stdin/stdout/stderr helpers.
296
+ */
297
+ const BRACKETED_PASTE_ENABLE = "\x1B[?2004h";
298
+ const BRACKETED_PASTE_DISABLE = "\x1B[?2004l";
299
+ const STREAMS_KEY = Symbol("fresco-streams");
300
+ function createStreamsContext(options = {}) {
301
+ const stdin = options.stdin ?? process.stdin;
302
+ const stdout = options.stdout ?? process.stdout;
303
+ const stderr = options.stderr ?? process.stderr;
304
+ const isInteractive = options.interactive ?? true;
305
+ const writeToStdout = options.writeToStdout ?? ((data) => stdout.write(data));
306
+ const writeToStderr = options.writeToStderr ?? ((data) => stderr.write(data));
307
+ let rawModeDepth = 0;
308
+ let pendingRawModeDisable = false;
309
+ let bracketedPasteDepth = 0;
310
+ return {
311
+ stdin,
312
+ stdout,
313
+ stderr,
314
+ setRawMode: (isRawMode) => {
315
+ if (!isInteractive || typeof stdin.setRawMode !== "function") return;
316
+ stdin.setEncoding?.("utf8");
317
+ if (isRawMode) {
318
+ pendingRawModeDisable = false;
319
+ if (rawModeDepth === 0) {
320
+ stdin.ref?.();
321
+ stdin.setRawMode(true);
322
+ }
323
+ rawModeDepth += 1;
324
+ return;
325
+ }
326
+ if (rawModeDepth === 0) return;
327
+ rawModeDepth -= 1;
328
+ if (rawModeDepth > 0) return;
329
+ pendingRawModeDisable = true;
330
+ queueMicrotask(() => {
331
+ if (!pendingRawModeDisable || rawModeDepth > 0) return;
332
+ pendingRawModeDisable = false;
333
+ stdin.setRawMode?.(false);
334
+ stdin.unref?.();
335
+ });
336
+ },
337
+ isRawModeSupported: isInteractive && typeof stdin.setRawMode === "function",
338
+ setBracketedPasteMode: (isEnabled) => {
339
+ if (!isInteractive) return;
340
+ if (isEnabled) {
341
+ bracketedPasteDepth += 1;
342
+ if (bracketedPasteDepth === 1) stdout.write(BRACKETED_PASTE_ENABLE);
343
+ return;
344
+ }
345
+ if (bracketedPasteDepth === 0) return;
346
+ bracketedPasteDepth -= 1;
347
+ if (bracketedPasteDepth === 0) stdout.write(BRACKETED_PASTE_DISABLE);
348
+ },
349
+ writeToStdout,
350
+ writeToStderr,
351
+ internal_exitOnCtrlC: options.exitOnCtrlC ?? true
352
+ };
353
+ }
354
+ function useStreamsContext() {
355
+ return inject(STREAMS_KEY) ?? createStreamsContext();
356
+ }
357
+ function useStdin() {
358
+ const streams = useStreamsContext();
359
+ return {
360
+ stdin: streams.stdin,
361
+ setRawMode: streams.setRawMode,
362
+ isRawModeSupported: streams.isRawModeSupported,
363
+ internal_exitOnCtrlC: streams.internal_exitOnCtrlC
364
+ };
365
+ }
366
+ function useStdout() {
367
+ const { stdout, writeToStdout } = useStreamsContext();
368
+ return {
369
+ stdout,
370
+ write: writeToStdout
371
+ };
372
+ }
373
+ function useStderr() {
374
+ const { stderr, writeToStderr } = useStreamsContext();
375
+ return {
376
+ stderr,
377
+ write: writeToStderr
378
+ };
379
+ }
380
+ //#endregion
381
+ //#region src/composables/useCursor.ts
382
+ /**
383
+ * useCursor - terminal cursor positioning.
384
+ */
385
+ const CURSOR_KEY = Symbol("fresco-cursor");
386
+ async function loadNative$1() {
387
+ return import("@vizejs/fresco-native");
388
+ }
389
+ function createCursorContext(setCursorPosition) {
390
+ return { setCursorPosition };
391
+ }
392
+ function setNativeCursorPosition(position) {
393
+ loadNative$1().then((native) => {
394
+ if (position) {
395
+ native.setCursor(position.x, position.y);
396
+ native.showCursor();
397
+ } else native.hideCursor();
398
+ }).catch(() => {});
399
+ }
400
+ function useCursor() {
401
+ const context = inject(CURSOR_KEY, null);
402
+ let didSetCursor = false;
403
+ const setCursorPosition = (position) => {
404
+ didSetCursor = true;
405
+ if (context) {
406
+ context.setCursorPosition(position);
407
+ return;
408
+ }
409
+ setNativeCursorPosition(position);
410
+ };
411
+ onUnmounted(() => {
412
+ if (didSetCursor) context?.setCursorPosition(void 0);
413
+ });
414
+ return { setCursorPosition };
415
+ }
416
+ //#endregion
417
+ //#region src/layoutMetrics.ts
418
+ let lastRenderLayouts = [];
419
+ function updateLastRenderLayouts(layouts) {
420
+ lastRenderLayouts = layouts;
421
+ }
422
+ function getLastRenderLayout(node) {
423
+ const id = node?.id ?? node?.$el?.id;
424
+ if (id === void 0) return null;
425
+ return lastRenderLayouts.find((layout) => layout.id === id) ?? null;
426
+ }
427
+ function measureElement(node) {
428
+ const layout = getLastRenderLayout(node);
429
+ return {
430
+ width: layout?.width ?? 0,
431
+ height: layout?.height ?? 0
432
+ };
433
+ }
434
+ //#endregion
435
+ //#region src/renderer.ts
436
+ /**
437
+ * Fresco Vue Custom Renderer
438
+ */
439
+ let nextId = 0;
440
+ function createNode(type) {
441
+ return {
442
+ id: nextId++,
443
+ type,
444
+ props: {},
445
+ children: [],
446
+ parent: null
447
+ };
448
+ }
449
+ /**
450
+ * Renderer options for Fresco
451
+ */
452
+ const rendererOptions = {
453
+ patchProp(el, key, _prevValue, nextValue) {
454
+ if (nextValue == null) delete el.props[key];
455
+ else el.props[key] = nextValue;
456
+ },
457
+ insert(child, parent, anchor) {
458
+ child.parent = parent;
459
+ if (anchor) {
460
+ const index = parent.children.indexOf(anchor);
461
+ if (index !== -1) {
462
+ parent.children.splice(index, 0, child);
463
+ return;
464
+ }
465
+ }
466
+ parent.children.push(child);
467
+ },
468
+ remove(child) {
469
+ if (child.parent) {
470
+ const index = child.parent.children.indexOf(child);
471
+ if (index !== -1) child.parent.children.splice(index, 1);
472
+ child.parent = null;
473
+ }
474
+ },
475
+ createElement(type) {
476
+ return createNode(mapElementType(type));
477
+ },
478
+ createText(text) {
479
+ const node = createNode("text");
480
+ node.text = text;
481
+ return node;
482
+ },
483
+ createComment() {
484
+ return createNode("text");
485
+ },
486
+ setText(node, text) {
487
+ node.text = text;
488
+ },
489
+ setElementText(el, text) {
490
+ el.text = text;
491
+ el.children = [];
492
+ },
493
+ parentNode(node) {
494
+ return node.parent;
495
+ },
496
+ nextSibling(node) {
497
+ if (!node.parent) return null;
498
+ const index = node.parent.children.indexOf(node);
499
+ return node.parent.children[index + 1] || null;
500
+ }
501
+ };
502
+ /**
503
+ * Map Vue element types to Fresco node types
504
+ */
505
+ function mapElementType(type) {
506
+ switch (type.toLowerCase()) {
507
+ case "box":
508
+ case "div":
509
+ case "view": return "box";
510
+ case "text":
511
+ case "span": return "text";
512
+ case "input":
513
+ case "textinput": return "input";
514
+ default: return "box";
515
+ }
516
+ }
517
+ /**
518
+ * Create the Fresco renderer
519
+ */
520
+ function createRenderer$1() {
521
+ return createRenderer(rendererOptions);
522
+ }
523
+ function stringValue(value) {
524
+ if (typeof value === "string" || typeof value === "number") return String(value);
525
+ }
526
+ function styleValue(style, ...keys) {
527
+ for (const key of keys) if (style[key] !== void 0) return style[key];
528
+ }
529
+ function copyStringStyle(output, style, nativeKey, ...sourceKeys) {
530
+ const normalized = stringValue(styleValue(style, ...sourceKeys));
531
+ if (normalized !== void 0) output[nativeKey] = normalized;
532
+ }
533
+ function copyNumberStyle(output, style, nativeKey, ...sourceKeys) {
534
+ const value = styleValue(style, ...sourceKeys);
535
+ if (typeof value === "number") output[nativeKey] = value;
536
+ }
537
+ function copyRawStyle(output, style, nativeKey, ...sourceKeys) {
538
+ const value = styleValue(style, ...sourceKeys);
539
+ if (value !== void 0) output[nativeKey] = value;
540
+ }
541
+ function isWrappingEnabled(value) {
542
+ if (value === void 0) return false;
543
+ if (value === false) return false;
544
+ if (typeof value === "string" && value.startsWith("truncate")) return false;
545
+ return true;
546
+ }
547
+ function textWrapMode(value) {
548
+ if (value === void 0) return void 0;
549
+ if (value === false) return "none";
550
+ if (value === true) return "wrap";
551
+ if (value === "end") return "truncate-end";
552
+ if (value === "middle") return "truncate-middle";
553
+ if (typeof value === "string") return value;
554
+ }
555
+ /**
556
+ * Convert Fresco tree to render nodes for native
557
+ */
558
+ function treeToRenderNodes(root) {
559
+ const nodes = [];
560
+ function visit(node) {
561
+ const renderNode = {
562
+ id: node.id,
563
+ nodeType: node.type
564
+ };
565
+ const text = node.text ?? stringValue(node.props.text) ?? stringValue(node.props.content);
566
+ if (text !== void 0) renderNode.text = text;
567
+ if (node.props.wrap !== void 0) {
568
+ renderNode.wrap = isWrappingEnabled(node.props.wrap);
569
+ renderNode.wrapMode = textWrapMode(node.props.wrap);
570
+ }
571
+ if (node.props.value !== void 0) renderNode.value = stringValue(node.props.value) ?? "";
572
+ if (node.props.placeholder !== void 0) renderNode.placeholder = stringValue(node.props.placeholder) ?? "";
573
+ if (node.props.focused !== void 0 || node.props.focus !== void 0) renderNode.focused = Boolean(node.props.focused ?? node.props.focus);
574
+ if (node.props.cursor !== void 0) renderNode.cursor = Number(node.props.cursor);
575
+ if (node.props.mask !== void 0) renderNode.mask = Boolean(node.props.mask);
576
+ if (node.props.maskChar !== void 0 || node.props["mask-char"] !== void 0) renderNode.maskChar = stringValue(node.props.maskChar ?? node.props["mask-char"]);
577
+ if (node.props.border !== void 0) renderNode.border = stringValue(node.props.border) ?? "";
578
+ if (node.props.style) {
579
+ const s = node.props.style;
580
+ const style = {};
581
+ copyRawStyle(style, s, "display", "display");
582
+ copyRawStyle(style, s, "position", "position");
583
+ copyStringStyle(style, s, "top", "top");
584
+ copyStringStyle(style, s, "right", "right");
585
+ copyStringStyle(style, s, "bottom", "bottom");
586
+ copyStringStyle(style, s, "left", "left");
587
+ copyRawStyle(style, s, "flexDirection", "flexDirection", "flex_direction");
588
+ copyRawStyle(style, s, "flexWrap", "flexWrap", "flex_wrap");
589
+ copyRawStyle(style, s, "justifyContent", "justifyContent", "justify_content");
590
+ copyRawStyle(style, s, "alignItems", "alignItems", "align_items");
591
+ copyRawStyle(style, s, "alignSelf", "alignSelf", "align_self");
592
+ copyRawStyle(style, s, "alignContent", "alignContent", "align_content");
593
+ copyNumberStyle(style, s, "flexGrow", "flexGrow", "flex_grow");
594
+ copyNumberStyle(style, s, "flexShrink", "flexShrink", "flex_shrink");
595
+ copyStringStyle(style, s, "flexBasis", "flexBasis", "flex_basis");
596
+ copyStringStyle(style, s, "width", "width");
597
+ copyStringStyle(style, s, "height", "height");
598
+ copyStringStyle(style, s, "minWidth", "minWidth", "min_width");
599
+ copyStringStyle(style, s, "minHeight", "minHeight", "min_height");
600
+ copyStringStyle(style, s, "maxWidth", "maxWidth", "max_width");
601
+ copyStringStyle(style, s, "maxHeight", "maxHeight", "max_height");
602
+ copyNumberStyle(style, s, "aspectRatio", "aspectRatio", "aspect_ratio");
603
+ copyNumberStyle(style, s, "padding", "padding");
604
+ copyNumberStyle(style, s, "paddingTop", "paddingTop", "padding_top");
605
+ copyNumberStyle(style, s, "paddingRight", "paddingRight", "padding_right");
606
+ copyNumberStyle(style, s, "paddingBottom", "paddingBottom", "padding_bottom");
607
+ copyNumberStyle(style, s, "paddingLeft", "paddingLeft", "padding_left");
608
+ copyNumberStyle(style, s, "margin", "margin");
609
+ copyNumberStyle(style, s, "marginTop", "marginTop", "margin_top");
610
+ copyNumberStyle(style, s, "marginRight", "marginRight", "margin_right");
611
+ copyNumberStyle(style, s, "marginBottom", "marginBottom", "margin_bottom");
612
+ copyNumberStyle(style, s, "marginLeft", "marginLeft", "margin_left");
613
+ copyNumberStyle(style, s, "gap", "gap");
614
+ copyNumberStyle(style, s, "columnGap", "columnGap", "column_gap");
615
+ copyNumberStyle(style, s, "rowGap", "rowGap", "row_gap");
616
+ copyRawStyle(style, s, "overflow", "overflow");
617
+ copyRawStyle(style, s, "overflowX", "overflowX", "overflow_x");
618
+ copyRawStyle(style, s, "overflowY", "overflowY", "overflow_y");
619
+ if (Object.keys(style).length > 0) renderNode.style = style;
620
+ }
621
+ const appearance = {};
622
+ const fg = node.props.fg ?? node.props.color;
623
+ const bg = node.props.bg ?? node.props.backgroundColor;
624
+ if (fg) appearance.fg = fg;
625
+ if (bg) appearance.bg = bg;
626
+ if (node.props.bold) appearance.bold = node.props.bold;
627
+ if (node.props.dim || node.props.dimColor) appearance.dim = Boolean(node.props.dim || node.props.dimColor);
628
+ if (node.props.italic) appearance.italic = node.props.italic;
629
+ if (node.props.underline) appearance.underline = node.props.underline;
630
+ if (node.props.strikethrough) appearance.strikethrough = node.props.strikethrough;
631
+ if (node.props.inverse) appearance.inverse = node.props.inverse;
632
+ if (Object.keys(appearance).length > 0) renderNode.appearance = appearance;
633
+ if (node.children.length > 0) renderNode.children = node.children.map((c) => c.id);
634
+ nodes.push(renderNode);
635
+ for (const child of node.children) visit(child);
636
+ }
637
+ visit(root);
638
+ return nodes;
639
+ }
640
+ //#endregion
641
+ //#region src/app.ts
642
+ /**
643
+ * Fresco App - Application instance management
644
+ */
645
+ const lastKeyEvent = ref(null);
646
+ const lastPasteEvent = ref(null);
647
+ const lastResizeEvent = ref(null);
648
+ const lastMouseEvent = ref(null);
649
+ const lastFocusEvent = ref(null);
650
+ const lastCompositionEvent = ref(null);
651
+ let native = null;
652
+ const consoleMethods = [
653
+ "debug",
654
+ "error",
655
+ "info",
656
+ "log",
657
+ "warn"
658
+ ];
659
+ async function loadNative() {
660
+ if (!native) native = await import("@vizejs/fresco-native");
661
+ return native;
662
+ }
663
+ function createNoopWriteStream(columns, rows) {
664
+ const stream = {
665
+ isTTY: false,
666
+ columns,
667
+ rows,
668
+ write: () => true,
669
+ on: (_event, _listener) => stream,
670
+ off: (_event, _listener) => stream,
671
+ once: (_event, _listener) => stream,
672
+ removeListener: (_event, _listener) => stream
673
+ };
674
+ return stream;
675
+ }
676
+ function createNoopReadStream() {
677
+ const stream = {
678
+ isTTY: false,
679
+ isPaused: () => true,
680
+ pause: () => stream,
681
+ resume: () => stream,
682
+ ref: () => stream,
683
+ unref: () => stream,
684
+ setEncoding: () => stream,
685
+ setRawMode: () => stream,
686
+ on: (_event, _listener) => stream,
687
+ off: (_event, _listener) => stream,
688
+ once: (_event, _listener) => stream,
689
+ removeListener: (_event, _listener) => stream
690
+ };
691
+ return stream;
692
+ }
693
+ function isWritableStream(value) {
694
+ return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
695
+ }
696
+ function normalizeRenderOptions(options) {
697
+ return isWritableStream(options) ? { stdout: options } : options;
698
+ }
699
+ function isVNodeRoot(root) {
700
+ return typeof root === "object" && root !== null && "__v_isVNode" in root;
701
+ }
702
+ function componentFromRoot(root) {
703
+ if (!isVNodeRoot(root)) return root;
704
+ return defineComponent({
705
+ name: "FrescoVNodeRoot",
706
+ setup() {
707
+ return () => root;
708
+ }
709
+ });
710
+ }
711
+ function createRootElement(width = "100%", height = "100%") {
712
+ return {
713
+ id: -1,
714
+ type: "root",
715
+ props: { style: {
716
+ width,
717
+ height,
718
+ flexDirection: "column",
719
+ justifyContent: "flex-start",
720
+ alignItems: "flex-start",
721
+ alignContent: "flex-start"
722
+ } },
723
+ children: [],
724
+ parent: null
725
+ };
726
+ }
727
+ function nodeText(node) {
728
+ if (node.text !== void 0) return node.text;
729
+ const propText = node.props.text ?? node.props.content;
730
+ if (typeof propText === "string" || typeof propText === "number") return String(propText);
731
+ if (node.type === "input") {
732
+ const value = node.props.value;
733
+ const placeholder = node.props.placeholder;
734
+ if (typeof value === "string" || typeof value === "number") return String(value);
735
+ if (typeof placeholder === "string" || typeof placeholder === "number") return String(placeholder);
736
+ }
737
+ return "";
738
+ }
739
+ function isStaticNode(node) {
740
+ return node.props.internal_static === true || node.props.internalStatic === true;
741
+ }
742
+ function joinOutput(parts, separator = "\n") {
743
+ return parts.filter(Boolean).join(separator);
744
+ }
745
+ function treeToString(node, options = {}) {
746
+ if (options.skipStatic && isStaticNode(node)) return "";
747
+ const ownText = nodeText(node);
748
+ const childOutput = node.children.map((child) => treeToString(child, options));
749
+ if (node.type === "text" || node.type === "input") return `${ownText}${childOutput.join("")}`;
750
+ const style = node.props.style ?? {};
751
+ return joinOutput(childOutput, (style.flexDirection ?? style.flex_direction) === "column" ? "\n" : "");
752
+ }
753
+ function normalizeOutput(output) {
754
+ return output.endsWith("\n") ? output.slice(0, -1) : output;
755
+ }
756
+ function appendOutput(previous, next) {
757
+ const normalized = normalizeOutput(next);
758
+ if (!normalized) return previous;
759
+ return previous ? `${previous}\n${normalized}` : normalized;
760
+ }
761
+ function captureStaticOutput(node, renderedStaticItems) {
762
+ const output = [];
763
+ function visit(current) {
764
+ if (isStaticNode(current)) {
765
+ const renderedItems = renderedStaticItems.get(current.id) ?? 0;
766
+ const nextOutput = joinOutput(current.children.slice(renderedItems).map((child) => treeToString(child)), "\n");
767
+ if (nextOutput) output.push(nextOutput);
768
+ renderedStaticItems.set(current.id, current.children.length);
769
+ return;
770
+ }
771
+ for (const child of current.children) visit(child);
772
+ }
773
+ visit(node);
774
+ return joinOutput(output, "\n");
775
+ }
776
+ function createStaticOutputNode(output) {
777
+ const normalized = normalizeOutput(output);
778
+ if (!normalized) return null;
779
+ const staticRoot = {
780
+ id: -1,
781
+ type: "box",
782
+ props: { style: { flexDirection: "column" } },
783
+ children: [],
784
+ parent: null
785
+ };
786
+ staticRoot.children = normalized.split("\n").map((line, index) => ({
787
+ id: -2 - index,
788
+ type: "text",
789
+ props: { text: line },
790
+ children: [],
791
+ parent: staticRoot
792
+ }));
793
+ return staticRoot;
794
+ }
795
+ function cloneDynamicTree(node) {
796
+ if (isStaticNode(node)) return null;
797
+ const clone = {
798
+ id: node.id,
799
+ type: node.type,
800
+ props: node.props,
801
+ children: [],
802
+ parent: null,
803
+ text: node.text
804
+ };
805
+ clone.children = node.children.map((child) => cloneDynamicTree(child)).filter((child) => child !== null);
806
+ for (const child of clone.children) child.parent = clone;
807
+ return clone;
808
+ }
809
+ function createRenderTree(root, staticOutput) {
810
+ const dynamicRoot = cloneDynamicTree(root) ?? createRootElement();
811
+ const staticOutputNode = createStaticOutputNode(staticOutput);
812
+ if (staticOutputNode) {
813
+ staticOutputNode.parent = dynamicRoot;
814
+ dynamicRoot.children = [staticOutputNode, ...dynamicRoot.children];
815
+ }
816
+ return dynamicRoot;
817
+ }
818
+ function renderPlainOutput(root, staticOutput) {
819
+ return joinOutput([staticOutput, treeToString(root, { skipStatic: true })], "\n");
820
+ }
821
+ function updateAppSize(context, width, height) {
822
+ if (!context) return;
823
+ context.width.value = width;
824
+ context.height.value = height;
825
+ }
826
+ const CI_ENVIRONMENT_KEYS = [
827
+ "CI",
828
+ "CONTINUOUS_INTEGRATION",
829
+ "BUILD_NUMBER",
830
+ "RUN_ID"
831
+ ];
832
+ function isTruthyEnvironmentValue(value) {
833
+ if (!value) return false;
834
+ return ![
835
+ "0",
836
+ "false",
837
+ "no"
838
+ ].includes(value.toLowerCase());
839
+ }
840
+ function isCiEnvironment(env = process.env) {
841
+ return CI_ENVIRONMENT_KEYS.some((key) => isTruthyEnvironmentValue(env[key]));
842
+ }
843
+ function detectInteractiveMode(options) {
844
+ const stdout = options.stdout ?? process.stdout;
845
+ return options.interactive ?? (stdout.isTTY === true && !isCiEnvironment());
846
+ }
847
+ /**
848
+ * Create a Fresco TUI app
849
+ */
850
+ function createApp(rootComponent, options = {}) {
851
+ const { mouse = false, exitOnCtrlC = true, onError } = options;
852
+ const interactive = detectInteractiveMode(options);
853
+ const screenReaderEnabled = ref(options.isScreenReaderEnabled ?? isScreenReaderEnabledByDefault());
854
+ const frameDelay = Math.max(1, Math.floor(1e3 / Math.max(1, options.maxFps ?? 30)));
855
+ let streamsContext;
856
+ const writeToStdout = (data) => {
857
+ writeExternalOutput(streamsContext.stdout, data);
858
+ };
859
+ const writeToStderr = (data) => {
860
+ writeExternalOutput(streamsContext.stderr, data);
861
+ };
862
+ streamsContext = createStreamsContext({
863
+ stdin: options.stdin,
864
+ stdout: options.stdout,
865
+ stderr: options.stderr,
866
+ exitOnCtrlC,
867
+ interactive,
868
+ writeToStdout,
869
+ writeToStderr
870
+ });
871
+ let vueApp = null;
872
+ let rootElement = null;
873
+ let mounted = false;
874
+ let running = false;
875
+ let needsRender = true;
876
+ let appContext = null;
877
+ let focusManager = null;
878
+ let consoleRestore = null;
879
+ let isWritingExternalOutput = false;
880
+ let nonInteractiveOutput = "";
881
+ let staticOutput = "";
882
+ const renderedStaticItems = /* @__PURE__ */ new Map();
883
+ let cursorPosition;
884
+ let hasCursorOverride = false;
885
+ let exitSettled = false;
886
+ let resolveExit = null;
887
+ let rejectExit = null;
888
+ const exitPromise = new Promise((resolve, reject) => {
889
+ resolveExit = resolve;
890
+ rejectExit = reject;
891
+ });
892
+ const { createApp: createVueApp } = createRenderer$1();
893
+ async function mount() {
894
+ if (mounted) return;
895
+ const n = interactive ? await loadNative() : null;
896
+ if (n) {
897
+ if (typeof n.initTerminalWithOptions === "function") n.initTerminalWithOptions({
898
+ alternateScreen: options.alternateScreen === true,
899
+ bracketedPaste: true,
900
+ hideCursor: true,
901
+ mouse,
902
+ rawMode: true
903
+ });
904
+ else if (mouse) n.initTerminalWithMouse();
905
+ else n.initTerminal();
906
+ n.enableIme?.();
907
+ n.initLayout();
908
+ }
909
+ const app = createVueApp(componentFromRoot(rootComponent));
910
+ const info = n?.getTerminalInfo() ?? {
911
+ width: streamsContext.stdout.columns ?? 80,
912
+ height: streamsContext.stdout.rows ?? 24
913
+ };
914
+ appContext = createAppContext({
915
+ width: info.width,
916
+ height: info.height,
917
+ exit: (value) => {
918
+ unmount(value);
919
+ },
920
+ render,
921
+ clear,
922
+ waitUntilRenderFlush,
923
+ stdout: streamsContext.stdout
924
+ });
925
+ focusManager = createFocusManager();
926
+ app.provide(APP_KEY, appContext);
927
+ app.provide(FOCUS_KEY, focusManager);
928
+ app.provide(SCREEN_READER_KEY, screenReaderEnabled);
929
+ app.provide(STREAMS_KEY, streamsContext);
930
+ app.provide(CURSOR_KEY, createCursorContext((position) => {
931
+ hasCursorOverride = true;
932
+ cursorPosition = position;
933
+ needsRender = true;
934
+ }));
935
+ rootElement = createRootElement();
936
+ patchConsoleMethods();
937
+ try {
938
+ app.mount(rootElement);
939
+ } catch (error) {
940
+ restoreConsoleMethods();
941
+ throw error;
942
+ }
943
+ vueApp = app;
944
+ mounted = true;
945
+ running = true;
946
+ needsRender = true;
947
+ eventLoop();
948
+ }
949
+ async function unmount(errorOrResult) {
950
+ if (!mounted) return;
951
+ running = false;
952
+ if (!interactive && rootElement) {
953
+ staticOutput = appendOutput(staticOutput, captureStaticOutput(rootElement, renderedStaticItems));
954
+ nonInteractiveOutput = renderPlainOutput(rootElement, staticOutput);
955
+ }
956
+ if (native && interactive) {
957
+ native.disableIme?.();
958
+ native.restoreTerminal();
959
+ }
960
+ restoreConsoleMethods();
961
+ if (vueApp) {
962
+ vueApp.unmount();
963
+ vueApp = null;
964
+ }
965
+ if (!interactive && nonInteractiveOutput) {
966
+ streamsContext.stdout.write(nonInteractiveOutput.endsWith("\n") ? nonInteractiveOutput : `${nonInteractiveOutput}\n`);
967
+ nonInteractiveOutput = "";
968
+ }
969
+ rootElement = null;
970
+ mounted = false;
971
+ appContext = null;
972
+ focusManager = null;
973
+ cursorPosition = void 0;
974
+ hasCursorOverride = false;
975
+ if (!exitSettled) {
976
+ exitSettled = true;
977
+ if (errorOrResult instanceof Error) rejectExit?.(errorOrResult);
978
+ else resolveExit?.(errorOrResult);
979
+ }
980
+ }
981
+ async function waitUntilExit() {
982
+ return exitPromise;
983
+ }
984
+ async function waitUntilRenderFlush() {
985
+ return Promise.resolve();
986
+ }
987
+ function clear() {
988
+ if (!mounted) return;
989
+ staticOutput = "";
990
+ if (!interactive) {
991
+ nonInteractiveOutput = "";
992
+ return;
993
+ }
994
+ if (!native) return;
995
+ native.clearScreen();
996
+ native.flushTerminal();
997
+ }
998
+ function writeExternalOutput(stream, data) {
999
+ if (!mounted || !interactive || !native || isWritingExternalOutput) {
1000
+ stream.write(data);
1001
+ return;
1002
+ }
1003
+ try {
1004
+ isWritingExternalOutput = true;
1005
+ native.clearScreen();
1006
+ native.flushTerminal();
1007
+ stream.write(data);
1008
+ needsRender = true;
1009
+ render();
1010
+ } finally {
1011
+ isWritingExternalOutput = false;
1012
+ }
1013
+ }
1014
+ function patchConsoleMethods() {
1015
+ if (options.patchConsole === false || consoleRestore) return;
1016
+ const target = console;
1017
+ const original = {};
1018
+ const patch = (method, writer) => {
1019
+ original[method] = target[method];
1020
+ target[method] = (...args) => {
1021
+ writer(`${format(...args)}\n`);
1022
+ };
1023
+ };
1024
+ patch("debug", writeToStdout);
1025
+ patch("info", writeToStdout);
1026
+ patch("log", writeToStdout);
1027
+ patch("warn", writeToStderr);
1028
+ patch("error", writeToStderr);
1029
+ consoleRestore = () => {
1030
+ for (const method of consoleMethods) {
1031
+ const originalMethod = original[method];
1032
+ if (originalMethod) target[method] = originalMethod;
1033
+ }
1034
+ consoleRestore = null;
1035
+ };
1036
+ }
1037
+ function restoreConsoleMethods() {
1038
+ consoleRestore?.();
1039
+ }
1040
+ function applyCursorOverride() {
1041
+ if (!hasCursorOverride || !native) return;
1042
+ if (cursorPosition) {
1043
+ native.setCursor(cursorPosition.x, cursorPosition.y);
1044
+ native.showCursor();
1045
+ } else native.hideCursor();
1046
+ }
1047
+ function render() {
1048
+ if (!mounted || !rootElement) return;
1049
+ const start = performance.now();
1050
+ try {
1051
+ if (!interactive) {
1052
+ staticOutput = appendOutput(staticOutput, captureStaticOutput(rootElement, renderedStaticItems));
1053
+ nonInteractiveOutput = renderPlainOutput(rootElement, staticOutput);
1054
+ options.onRender?.({ renderTime: performance.now() - start });
1055
+ return;
1056
+ }
1057
+ if (!native) return;
1058
+ staticOutput = appendOutput(staticOutput, captureStaticOutput(rootElement, renderedStaticItems));
1059
+ const renderRoot = createRenderTree(rootElement, staticOutput);
1060
+ const renderNodes = screenReaderEnabled.value ? treeToScreenReaderRenderNodes(renderRoot) : treeToRenderNodes(renderRoot);
1061
+ if (renderNodes.length > 0) {
1062
+ if (options.debug) streamsContext.stderr.write(`${JSON.stringify(renderNodes, null, 2)}\n`);
1063
+ native.renderTree(renderNodes);
1064
+ if ("getLastRenderLayouts" in native) updateLastRenderLayouts(native.getLastRenderLayouts());
1065
+ applyCursorOverride();
1066
+ native.flushTerminal();
1067
+ options.onRender?.({ renderTime: performance.now() - start });
1068
+ }
1069
+ } catch (error) {
1070
+ if (onError) onError(error);
1071
+ else console.error("Render error:", error);
1072
+ }
1073
+ }
1074
+ async function getTerminalInfo() {
1075
+ const info = (await loadNative()).getTerminalInfo();
1076
+ return {
1077
+ width: info.width,
1078
+ height: info.height
1079
+ };
1080
+ }
1081
+ function dispatchEvent(event) {
1082
+ if (event.eventType === "resize") {
1083
+ const resizeEvent = {
1084
+ type: "resize",
1085
+ width: event.width ?? 0,
1086
+ height: event.height ?? 0
1087
+ };
1088
+ lastResizeEvent.value = resizeEvent;
1089
+ updateAppSize(appContext, resizeEvent.width, resizeEvent.height);
1090
+ return;
1091
+ }
1092
+ if (event.eventType === "paste") {
1093
+ lastPasteEvent.value = {
1094
+ type: "paste",
1095
+ text: event.text ?? ""
1096
+ };
1097
+ return;
1098
+ }
1099
+ if (event.eventType === "mouse") {
1100
+ lastMouseEvent.value = {
1101
+ type: "mouse",
1102
+ button: event.button ?? void 0,
1103
+ x: event.x ?? 0,
1104
+ y: event.y ?? 0
1105
+ };
1106
+ return;
1107
+ }
1108
+ if (event.eventType === "focus") {
1109
+ lastFocusEvent.value = {
1110
+ type: "focus",
1111
+ focused: event.key === "gained"
1112
+ };
1113
+ return;
1114
+ }
1115
+ if (event.eventType === "compositionstart" || event.eventType === "compositionupdate" || event.eventType === "compositionend") {
1116
+ lastCompositionEvent.value = {
1117
+ type: event.eventType,
1118
+ text: event.text ?? "",
1119
+ cursor: event.cursor ?? 0
1120
+ };
1121
+ return;
1122
+ }
1123
+ if (event.eventType === "key") {
1124
+ const modifiers = event.modifiers ?? {};
1125
+ lastKeyEvent.value = {
1126
+ type: "key",
1127
+ key: event.key ?? void 0,
1128
+ char: event.char ?? void 0,
1129
+ ctrl: modifiers.ctrl ?? false,
1130
+ alt: modifiers.alt ?? false,
1131
+ shift: modifiers.shift ?? false,
1132
+ meta: modifiers.meta ?? false,
1133
+ super: modifiers.super ?? false,
1134
+ hyper: modifiers.hyper ?? false,
1135
+ capsLock: modifiers.capsLock ?? false,
1136
+ numLock: modifiers.numLock ?? false,
1137
+ eventType: event.keyEventType ?? void 0
1138
+ };
1139
+ }
1140
+ }
1141
+ async function eventLoop() {
1142
+ const n = interactive ? await loadNative() : null;
1143
+ while (running) {
1144
+ try {
1145
+ const event = n?.pollEvent(16);
1146
+ if (event) {
1147
+ if (event.eventType === "resize") {
1148
+ n?.syncTerminalSize();
1149
+ n?.clearScreen();
1150
+ needsRender = true;
1151
+ }
1152
+ dispatchEvent(event);
1153
+ if (event.eventType === "key" && event.key === "tab") {
1154
+ focusManager?.focusNext();
1155
+ needsRender = true;
1156
+ } else if (event.eventType === "key" && event.key === "backtab") {
1157
+ focusManager?.focusPrevious();
1158
+ needsRender = true;
1159
+ }
1160
+ if (exitOnCtrlC && event.eventType === "key" && event.char === "c" && event.modifiers?.ctrl) {
1161
+ await unmount();
1162
+ break;
1163
+ }
1164
+ }
1165
+ if (needsRender) {
1166
+ render();
1167
+ needsRender = false;
1168
+ }
1169
+ needsRender = true;
1170
+ } catch (error) {
1171
+ if (onError) onError(error);
1172
+ }
1173
+ await new Promise((resolve) => setTimeout(resolve, frameDelay));
1174
+ }
1175
+ }
1176
+ return {
1177
+ mount,
1178
+ unmount,
1179
+ waitUntilExit,
1180
+ waitUntilRenderFlush,
1181
+ render,
1182
+ clear,
1183
+ getTerminalInfo
1184
+ };
1185
+ }
1186
+ /**
1187
+ * Ink-compatible helper that mounts immediately.
1188
+ */
1189
+ function render(root, options = {}) {
1190
+ const renderOptions = normalizeRenderOptions(options);
1191
+ const rootRef = shallowRef(root);
1192
+ const app = createApp(defineComponent({
1193
+ name: "FrescoRenderRoot",
1194
+ setup() {
1195
+ return () => {
1196
+ const current = rootRef.value;
1197
+ return isVNodeRoot(current) ? current : h(current);
1198
+ };
1199
+ }
1200
+ }), renderOptions);
1201
+ app.mount();
1202
+ return {
1203
+ rerender(nextRoot) {
1204
+ rootRef.value = nextRoot;
1205
+ app.render();
1206
+ },
1207
+ unmount: (value) => app.unmount(value),
1208
+ waitUntilExit: () => app.waitUntilExit(),
1209
+ waitUntilRenderFlush: () => app.waitUntilRenderFlush(),
1210
+ cleanup: () => app.unmount(),
1211
+ clear: () => app.clear()
1212
+ };
1213
+ }
1214
+ /**
1215
+ * Render a Fresco component to a plain string without starting a terminal app.
1216
+ */
1217
+ function renderToString(root, options = {}) {
1218
+ const { createApp: createVueApp } = createRenderer$1();
1219
+ const app = createVueApp(componentFromRoot(root));
1220
+ const columns = options.columns ?? 80;
1221
+ const stdout = createNoopWriteStream(columns, 24);
1222
+ const stderr = createNoopWriteStream(columns, 24);
1223
+ const stdin = createNoopReadStream();
1224
+ const rootElement = createRootElement(String(columns), "auto");
1225
+ app.provide(APP_KEY, createAppContext({
1226
+ width: columns,
1227
+ height: 24,
1228
+ stdout
1229
+ }));
1230
+ app.provide(FOCUS_KEY, createFocusManager());
1231
+ app.provide(SCREEN_READER_KEY, ref(false));
1232
+ app.provide(CURSOR_KEY, createCursorContext(() => {}));
1233
+ app.provide(STREAMS_KEY, createStreamsContext({
1234
+ stdin,
1235
+ stdout,
1236
+ stderr,
1237
+ interactive: false,
1238
+ writeToStdout: () => {},
1239
+ writeToStderr: () => {}
1240
+ }));
1241
+ app.mount(rootElement);
1242
+ const output = renderPlainOutput(rootElement, captureStaticOutput(rootElement, /* @__PURE__ */ new Map()));
1243
+ app.unmount();
1244
+ return output;
1245
+ }
1246
+ //#endregion
1247
+ //#region src/composables/useIsScreenReaderEnabled.ts
1248
+ /**
1249
+ * useIsScreenReaderEnabled - screen reader mode flag.
1250
+ */
1251
+ function useIsScreenReaderEnabled() {
1252
+ return inject(SCREEN_READER_KEY, null)?.value ?? isScreenReaderEnabledByDefault();
1253
+ }
1254
+ //#endregion
1255
+ //#region src/composables/usePaste.ts
1256
+ /**
1257
+ * usePaste - bracketed paste handling.
1258
+ */
1259
+ let activePasteHandlerCount = 0;
1260
+ function hasActivePasteHandlers() {
1261
+ return activePasteHandlerCount > 0;
1262
+ }
1263
+ function toRef$1(value) {
1264
+ return isRef(value) ? value : ref(value);
1265
+ }
1266
+ function usePaste(handler, options = {}) {
1267
+ const isActive = toRef$1(options.isActive ?? true);
1268
+ const streams = useStreamsContext();
1269
+ let rawModeEnabled = false;
1270
+ let bracketedPasteEnabled = false;
1271
+ let pasteHandlerRegistered = false;
1272
+ const syncRawMode = (isEnabled) => {
1273
+ if (rawModeEnabled === isEnabled) return;
1274
+ streams.setRawMode(isEnabled);
1275
+ rawModeEnabled = isEnabled;
1276
+ };
1277
+ const syncBracketedPasteMode = (isEnabled) => {
1278
+ if (bracketedPasteEnabled === isEnabled) return;
1279
+ streams.setBracketedPasteMode(isEnabled);
1280
+ bracketedPasteEnabled = isEnabled;
1281
+ };
1282
+ const syncPasteRegistration = (isEnabled) => {
1283
+ if (pasteHandlerRegistered === isEnabled) return;
1284
+ activePasteHandlerCount += isEnabled ? 1 : -1;
1285
+ pasteHandlerRegistered = isEnabled;
1286
+ };
1287
+ const syncActiveState = (isEnabled) => {
1288
+ syncRawMode(isEnabled);
1289
+ syncBracketedPasteMode(isEnabled);
1290
+ syncPasteRegistration(isEnabled);
1291
+ };
1292
+ watch(lastPasteEvent, (event) => {
1293
+ if (!event || !isActive.value) return;
1294
+ handler(event.text);
1295
+ });
1296
+ watch(isActive, syncActiveState, { immediate: true });
1297
+ onUnmounted(() => syncActiveState(false));
1298
+ return {
1299
+ isActive,
1300
+ enable: () => {
1301
+ isActive.value = true;
1302
+ },
1303
+ disable: () => {
1304
+ isActive.value = false;
1305
+ }
1306
+ };
1307
+ }
1308
+ //#endregion
1309
+ //#region src/composables/useInput.ts
1310
+ /**
1311
+ * useInput - Input handling composable
1312
+ */
1313
+ function toRef(value) {
1314
+ return isRef(value) ? value : ref(value);
1315
+ }
1316
+ function keyName(event) {
1317
+ return event.char ?? event.key ?? "";
1318
+ }
1319
+ function toInkKey(event) {
1320
+ const key = event.key;
1321
+ return {
1322
+ upArrow: key === "up",
1323
+ downArrow: key === "down",
1324
+ leftArrow: key === "left",
1325
+ rightArrow: key === "right",
1326
+ pageDown: key === "pagedown" || key === "pageDown",
1327
+ pageUp: key === "pageup" || key === "pageUp",
1328
+ home: key === "home",
1329
+ end: key === "end",
1330
+ return: key === "enter" || key === "return",
1331
+ escape: key === "escape" || key === "esc",
1332
+ ctrl: event.ctrl,
1333
+ shift: event.shift,
1334
+ tab: key === "tab" || key === "backtab",
1335
+ backspace: key === "backspace",
1336
+ delete: key === "delete",
1337
+ meta: event.meta,
1338
+ super: event.super,
1339
+ hyper: event.hyper,
1340
+ capsLock: event.capsLock,
1341
+ numLock: event.numLock,
1342
+ eventType: event.eventType
1343
+ };
1344
+ }
1345
+ function emptyKey() {
1346
+ return {
1347
+ upArrow: false,
1348
+ downArrow: false,
1349
+ leftArrow: false,
1350
+ rightArrow: false,
1351
+ pageDown: false,
1352
+ pageUp: false,
1353
+ home: false,
1354
+ end: false,
1355
+ return: false,
1356
+ escape: false,
1357
+ ctrl: false,
1358
+ shift: false,
1359
+ tab: false,
1360
+ backspace: false,
1361
+ delete: false,
1362
+ meta: false,
1363
+ super: false,
1364
+ hyper: false,
1365
+ capsLock: false,
1366
+ numLock: false
1367
+ };
1368
+ }
1369
+ function inputValue(event, key) {
1370
+ if (event.char) return event.char;
1371
+ if (key.return) return "\r";
1372
+ return "";
1373
+ }
1374
+ function handleStructuredOptions(event, options, lastKey) {
1375
+ const modifiers = {
1376
+ ctrl: event.ctrl,
1377
+ alt: event.alt,
1378
+ shift: event.shift,
1379
+ meta: event.meta
1380
+ };
1381
+ const pressedKey = keyName(event);
1382
+ const inkKey = toInkKey(event);
1383
+ if (event.char) {
1384
+ lastKey.value = event.char;
1385
+ options.onChar?.(event.char);
1386
+ options.onKey?.(event.char, modifiers);
1387
+ return;
1388
+ }
1389
+ if (pressedKey) {
1390
+ lastKey.value = pressedKey;
1391
+ options.onKey?.(pressedKey, modifiers);
1392
+ }
1393
+ if (inkKey.return) options.onSubmit?.();
1394
+ if (inkKey.escape) options.onEscape?.();
1395
+ if (inkKey.upArrow) options.onArrow?.("up");
1396
+ if (inkKey.downArrow) options.onArrow?.("down");
1397
+ if (inkKey.leftArrow) options.onArrow?.("left");
1398
+ if (inkKey.rightArrow) options.onArrow?.("right");
1399
+ }
1400
+ function useInput(handlerOrOptions = {}, handlerOptions = {}) {
1401
+ const options = typeof handlerOrOptions === "function" ? {
1402
+ handler: handlerOrOptions,
1403
+ isActive: handlerOptions.isActive
1404
+ } : handlerOrOptions;
1405
+ const isActive = toRef(options.isActive ?? options.active ?? true);
1406
+ const lastKey = ref(null);
1407
+ const streams = useStreamsContext();
1408
+ let rawModeEnabled = false;
1409
+ const syncRawMode = (isEnabled) => {
1410
+ if (rawModeEnabled === isEnabled) return;
1411
+ streams.setRawMode(isEnabled);
1412
+ rawModeEnabled = isEnabled;
1413
+ };
1414
+ watch(lastKeyEvent, (event) => {
1415
+ if (!event || !isActive.value) return;
1416
+ const inkKey = toInkKey(event);
1417
+ const input = inputValue(event, inkKey);
1418
+ const pressedKey = keyName(event);
1419
+ if (input === "c" && inkKey.ctrl && streams.internal_exitOnCtrlC) return;
1420
+ lastKey.value = pressedKey || null;
1421
+ options.handler?.(input, inkKey);
1422
+ handleStructuredOptions(event, options, lastKey);
1423
+ });
1424
+ watch(lastPasteEvent, (event) => {
1425
+ if (!event || !isActive.value || hasActivePasteHandlers()) return;
1426
+ lastKey.value = event.text;
1427
+ options.handler?.(event.text, emptyKey());
1428
+ options.onChar?.(event.text);
1429
+ });
1430
+ watch(lastCompositionEvent, (event) => {
1431
+ if (!event || !isActive.value) return;
1432
+ if (event.type === "compositionstart") options.onCompositionStart?.();
1433
+ else if (event.type === "compositionupdate") options.onCompositionUpdate?.(event.text, event.cursor);
1434
+ else options.onCompositionEnd?.(event.text);
1435
+ });
1436
+ watch(isActive, syncRawMode, { immediate: true });
1437
+ onUnmounted(() => syncRawMode(false));
1438
+ const enable = () => {
1439
+ isActive.value = true;
1440
+ };
1441
+ const disable = () => {
1442
+ isActive.value = false;
1443
+ };
1444
+ return {
1445
+ isActive,
1446
+ lastKey,
1447
+ enable,
1448
+ disable
1449
+ };
1450
+ }
1451
+ /**
1452
+ * Shorthand for handling specific key combinations
1453
+ */
1454
+ function useKeyPress(key, handler, options = {}) {
1455
+ const { ctrl = false, alt = false, shift = false, meta = false } = options;
1456
+ useInput({ onKey: (pressedKey, modifiers) => {
1457
+ if (pressedKey.toLowerCase() === key.toLowerCase() && modifiers.ctrl === ctrl && modifiers.alt === alt && modifiers.shift === shift && modifiers.meta === meta) handler();
1458
+ } });
1459
+ }
1460
+ //#endregion
1461
+ export { useFocusManager as C, useFocus as S, updateLastRenderLayouts as _, createApp as a, useStdin as b, lastKeyEvent as c, lastResizeEvent as d, render as f, measureElement as g, getLastRenderLayout as h, useIsScreenReaderEnabled as i, lastMouseEvent as l, createRenderer$1 as m, useKeyPress as n, lastCompositionEvent as o, renderToString as p, usePaste as r, lastFocusEvent as s, useInput as t, lastPasteEvent as u, useCursor as v, useApp as w, useStdout as x, useStderr as y };
1462
+
1463
+ //# sourceMappingURL=useInput-DrlvpGkS.mjs.map