@vizejs/fresco 0.100.0 → 0.103.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.
- package/dist/components/index.d.mts +2 -2
- package/dist/components/index.mjs +2 -2
- package/dist/{components-B5VXjX9s.mjs → components-BFV7PBp6.mjs} +399 -66
- package/dist/components-BFV7PBp6.mjs.map +1 -0
- package/dist/composables/index.d.mts +2 -2
- package/dist/composables/index.mjs +3 -3
- package/dist/composables-BKj30tnc.mjs +318 -0
- package/dist/composables-BKj30tnc.mjs.map +1 -0
- package/dist/index-43FxHkwF.d.mts +316 -0
- package/dist/index-43FxHkwF.d.mts.map +1 -0
- package/dist/{index-D0wpImTF.d.mts → index-BPjzljOc.d.mts} +361 -63
- package/dist/index-BPjzljOc.d.mts.map +1 -0
- package/dist/index.d.mts +129 -26
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +31 -4
- package/dist/index.mjs.map +1 -0
- package/dist/useInput-DrlvpGkS.mjs +1463 -0
- package/dist/useInput-DrlvpGkS.mjs.map +1 -0
- package/package.json +5 -2
- package/dist/components-B5VXjX9s.mjs.map +0 -1
- package/dist/composables-ThPaZc16.mjs +0 -194
- package/dist/composables-ThPaZc16.mjs.map +0 -1
- package/dist/index-BeImxraZ.d.mts +0 -142
- package/dist/index-BeImxraZ.d.mts.map +0 -1
- package/dist/index-D0wpImTF.d.mts.map +0 -1
- package/dist/useInput-CbggNZUF.mjs +0 -351
- package/dist/useInput-CbggNZUF.mjs.map +0 -1
|
@@ -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
|