@vizejs/fresco 0.347.7 → 0.350.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/app-DJImCkSW.d.mts +135 -0
  2. package/dist/app-DJImCkSW.d.mts.map +1 -0
  3. package/dist/{useInput-iUuaOefz.mjs → app-D_Ke7C8w.mjs} +2 -216
  4. package/dist/{useInput-iUuaOefz.mjs.map → app-D_Ke7C8w.mjs.map} +1 -1
  5. package/dist/components/index.d.mts +2 -2
  6. package/dist/components/index.mjs +1 -1
  7. package/dist/{components-rzllq5gt.mjs → components-3-vKPu1z.mjs} +2 -2
  8. package/dist/{components-rzllq5gt.mjs.map → components-3-vKPu1z.mjs.map} +1 -1
  9. package/dist/composables/index.d.mts +3 -2
  10. package/dist/composables/index.mjs +3 -2
  11. package/dist/{composables-Dh8YqmVY.mjs → composables-Da3c5pxz.mjs} +3 -2
  12. package/dist/{composables-Dh8YqmVY.mjs.map → composables-Da3c5pxz.mjs.map} +1 -1
  13. package/dist/{index-CQHg8OjC.d.mts → index-D2kUje57.d.mts} +184 -378
  14. package/dist/index-D2kUje57.d.mts.map +1 -0
  15. package/dist/{index-Dwx6lKP0.d.mts → index-DqGcW9sz.d.mts} +2 -85
  16. package/dist/index-DqGcW9sz.d.mts.map +1 -0
  17. package/dist/index.d.mts +7 -136
  18. package/dist/index.mjs +4 -3
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/renderer-DWG0G3g8.d.mts +198 -0
  21. package/dist/renderer-DWG0G3g8.d.mts.map +1 -0
  22. package/dist/testing/index.d.mts +124 -0
  23. package/dist/testing/index.d.mts.map +1 -0
  24. package/dist/testing/index.mjs +282 -0
  25. package/dist/testing/index.mjs.map +1 -0
  26. package/dist/useApp-CNjer3hJ.d.mts +86 -0
  27. package/dist/useApp-CNjer3hJ.d.mts.map +1 -0
  28. package/dist/useInput-BpH-JIU3.mjs +219 -0
  29. package/dist/useInput-BpH-JIU3.mjs.map +1 -0
  30. package/package.json +8 -4
  31. package/dist/index-CQHg8OjC.d.mts.map +0 -1
  32. package/dist/index-Dwx6lKP0.d.mts.map +0 -1
  33. package/dist/index.d.mts.map +0 -1
@@ -0,0 +1,124 @@
1
+ import { M as KeyEvent, P as MouseEvent, n as FrescoNode, t as FrescoElement } from "../renderer-DWG0G3g8.mjs";
2
+ import { r as FocusManager, t as UseAppReturn } from "../useApp-CNjer3hJ.mjs";
3
+ import { App, Component, Ref, VNodeChild } from "@vue/runtime-core";
4
+
5
+ //#region src/testing/mount.d.ts
6
+ /** Options for {@link mountFresco}. */
7
+ interface MountFrescoOptions {
8
+ /** Terminal width provided through the app context. Defaults to 80. */
9
+ width?: number;
10
+ /** Terminal height provided through the app context. Defaults to 24. */
11
+ height?: number;
12
+ /** Initial screen reader mode. Defaults to false. */
13
+ screenReader?: boolean;
14
+ }
15
+ /** A mounted Fresco component tree under test. */
16
+ interface MountedFresco {
17
+ /** Root element the Vue app mounted into; children form the output tree. */
18
+ root: FrescoElement;
19
+ /** The Vue app instance (for `provide`-level introspection if needed). */
20
+ app: App<FrescoElement>;
21
+ /** App context provided to composables such as `useApp` and `useWindowSize`. */
22
+ appContext: UseAppReturn;
23
+ /** Focus manager provided to the tree, as the real app would. */
24
+ focusManager: FocusManager;
25
+ /** Screen reader flag provided to the tree; writable to flip modes. */
26
+ screenReaderEnabled: Ref<boolean>;
27
+ /** Unmount the tree and dispose all component watchers. */
28
+ unmount(): void;
29
+ }
30
+ /**
31
+ * Mount a component (or a plain render function) through the Fresco renderer.
32
+ *
33
+ * The returned {@link MountedFresco.root} is live: re-read it after reactive
34
+ * updates plus `await nextTick()` to observe the patched output tree.
35
+ */
36
+ declare function mountFresco(root: Component | (() => VNodeChild), options?: MountFrescoOptions): MountedFresco;
37
+ /**
38
+ * Dispatch a key event through the same `lastKeyEvent` ref the interactive
39
+ * event loop writes, then wait for watchers to flush.
40
+ *
41
+ * Pass `char` for printable input or `key` for named keys ("left", "enter",
42
+ * "backspace", ...). Modifiers default to false.
43
+ */
44
+ declare function dispatchKey(event: Partial<Omit<KeyEvent, "type">>): Promise<void>;
45
+ /** Dispatch a sequence of printable characters via {@link dispatchKey}. */
46
+ declare function typeChars(text: string): Promise<void>;
47
+ /**
48
+ * Serializable snapshot of a mounted output tree.
49
+ *
50
+ * Node ids come from a module-global counter and depend on mount order, and
51
+ * `parent` back-references make trees cyclic, so both are omitted. Props are
52
+ * kept only when defined, so snapshots stay inline-sized and deterministic.
53
+ */
54
+ interface FrescoNodeSnapshot {
55
+ type: FrescoNode["type"];
56
+ text?: string;
57
+ props?: Record<string, unknown>;
58
+ children?: FrescoNodeSnapshot[];
59
+ }
60
+ /**
61
+ * Convert a mounted node (or the harness root) into a plain snapshot object
62
+ * suitable for `assert.deepEqual` against an inline expected structure.
63
+ */
64
+ declare function toTreeSnapshot(node: FrescoNode): FrescoNodeSnapshot;
65
+ /** Depth-first list of nodes matching a predicate, starting at `node`. */
66
+ declare function findNodes(node: FrescoNode, predicate: (candidate: FrescoNode) => boolean): FrescoNode[];
67
+ /** First mounted child under the harness root, asserting it exists. */
68
+ declare function firstChild(mounted: MountedFresco): FrescoNode;
69
+ /** Convenience wrapper that renders `component` with `h` and mounts it. */
70
+ declare function mountComponent(component: Component, props?: Record<string, unknown>, slot?: () => VNodeChild, options?: MountFrescoOptions): MountedFresco;
71
+ //#endregion
72
+ //#region src/testing/index.d.ts
73
+ type RenderTuiOptions = MountFrescoOptions;
74
+ type RenderTuiRoot = Component | (() => VNodeChild);
75
+ type KeyInput = Partial<Omit<KeyEvent, "type">>;
76
+ type MouseInput = Omit<MouseEvent, "type">;
77
+ /** A single recorded harness frame. */
78
+ interface FrescoFrameSnapshot {
79
+ /** Plain terminal output projected from the current mounted tree. */
80
+ output: string;
81
+ /** Serializable renderer tree for structural assertions. */
82
+ tree: FrescoNodeSnapshot;
83
+ }
84
+ /** Input driver that injects events through Fresco's public event refs. */
85
+ interface FrescoInputDriver {
86
+ /** Dispatch a key event and record the resulting frame. */
87
+ key(event: KeyInput): Promise<FrescoFrameSnapshot>;
88
+ /** Dispatch each printable character and record one frame after the text. */
89
+ text(text: string): Promise<FrescoFrameSnapshot>;
90
+ /** Dispatch a bracketed paste payload and record the resulting frame. */
91
+ paste(text: string): Promise<FrescoFrameSnapshot>;
92
+ /** Update the provided app size, dispatch a resize event, and record a frame. */
93
+ resize(width: number, height: number): Promise<FrescoFrameSnapshot>;
94
+ /** Dispatch a mouse event and record the resulting frame. */
95
+ mouse(event: MouseInput): Promise<FrescoFrameSnapshot>;
96
+ /** Dispatch terminal focus state and record the resulting frame. */
97
+ focus(focused: boolean): Promise<FrescoFrameSnapshot>;
98
+ /** Dispatch a composition start event and record the resulting frame. */
99
+ compositionStart(text?: string, cursor?: number): Promise<FrescoFrameSnapshot>;
100
+ /** Dispatch a composition update event and record the resulting frame. */
101
+ compositionUpdate(text: string, cursor: number): Promise<FrescoFrameSnapshot>;
102
+ /** Dispatch a composition end event and record the resulting frame. */
103
+ compositionEnd(text: string, cursor?: number): Promise<FrescoFrameSnapshot>;
104
+ }
105
+ /** Mounted TUI harness with frame history and input helpers. */
106
+ interface RenderTuiResult extends MountedFresco {
107
+ /** Recorded plain frames, including the initial mount frame. */
108
+ readonly frames: readonly string[];
109
+ /** Recorded frame snapshots, including the initial mount frame. */
110
+ readonly frameSnapshots: readonly FrescoFrameSnapshot[];
111
+ /** Event injection helpers. */
112
+ readonly input: FrescoInputDriver;
113
+ /** Record and return a fresh snapshot of the current mounted tree. */
114
+ captureFrame(): FrescoFrameSnapshot;
115
+ /** Return the latest recorded plain frame. */
116
+ lastFrame(): string;
117
+ /** Return the latest recorded frame snapshot. */
118
+ frameSnapshot(): FrescoFrameSnapshot;
119
+ }
120
+ /** Mount a Fresco app for tests with frame snapshots and input injection. */
121
+ declare function renderTui(root: RenderTuiRoot, options?: RenderTuiOptions): RenderTuiResult;
122
+ //#endregion
123
+ export { FrescoFrameSnapshot, FrescoInputDriver, type FrescoNodeSnapshot, KeyInput, type MountFrescoOptions, type MountedFresco, MouseInput, RenderTuiOptions, RenderTuiResult, RenderTuiRoot, dispatchKey, findNodes, firstChild, mountComponent, mountFresco, renderTui, toTreeSnapshot, typeChars };
124
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/testing/mount.ts","../../src/testing/index.ts"],"mappings":";;;;;;UA+BiB,kBAAA;EAcH;EAZZ,KAAA;EAcY;EAZZ,MAAA;EAgBqB;EAdrB,YAAA;AAAA;;UAIe,aAAA;EAIf;EAFA,IAAA,EAAM,aAAA;EAEM;EAAZ,GAAA,EAAK,GAAA,CAAO,aAAA;EAEA;EAAZ,UAAA,EAAY,YAAA;EAEE;EAAd,YAAA,EAAc,YAAA;EAEO;EAArB,mBAAA,EAAqB,GAAA;EAEd;EAAP,OAAA;AAAA;;;;;;;iBASc,WAAA,CACd,IAAA,EAAM,SAAA,UAAmB,UAAA,GACzB,OAAA,GAAS,kBAAA,GACR,aAAA;;;;;;;;iBA2Da,WAAA,CAAY,KAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,QAAA,aAAqB,OAAA;AA3DrD;AAAA,iBA4EM,SAAA,CAAU,IAAA,WAAe,OAAO;;;;;;;;UAarC,kBAAA;EACf,IAAA,EAAM,UAAA;EACN,IAAA;EACA,KAAA,GAAQ,MAAA;EACR,QAAA,GAAW,kBAAA;AAAA;;AAlC+D;AAiB5E;;iBA8BgB,cAAA,CAAe,IAAA,EAAM,UAAA,GAAa,kBAAkB;;iBAYpD,SAAA,CACd,IAAA,EAAM,UAAA,EACN,SAAA,GAAY,SAAA,EAAW,UAAA,eACtB,UAAA;AAhCH;AAAA,iBA2CgB,UAAA,CAAW,OAAA,EAAS,aAAA,GAAgB,UAAU;;iBAO9C,cAAA,CACd,SAAA,EAAW,SAAA,EACX,KAAA,GAAO,MAAA,mBACP,IAAA,SAAa,UAAA,EACb,OAAA,GAAS,kBAAA,GACR,aAAA;;;KCpKS,gBAAA,GAAmB,kBAAkB;AAAA,KACrC,aAAA,GAAgB,SAAA,UAAmB,UAAU;AAAA,KAC7C,QAAA,GAAW,OAAA,CAAQ,IAAA,CAAK,QAAA;AAAA,KACxB,UAAA,GAAa,IAAI,CAAC,UAAA;ADP9B;AAAA,UCUiB,mBAAA;;EAEf,MAAA;EDRY;ECUZ,IAAA,EAAM,kBAAkB;AAAA;;UAIT,iBAAA;EDRS;ECUxB,GAAA,CAAI,KAAA,EAAO,QAAA,GAAW,OAAA,CAAQ,mBAAA;EDlB9B;ECoBA,IAAA,CAAK,IAAA,WAAe,OAAA,CAAQ,mBAAA;EDlB5B;ECoBA,KAAA,CAAM,IAAA,WAAe,OAAA,CAAQ,mBAAA;EDpBjB;ECsBZ,MAAA,CAAO,KAAA,UAAe,MAAA,WAAiB,OAAA,CAAQ,mBAAA;EDpBnC;ECsBZ,KAAA,CAAM,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,mBAAA;EDpBpB;ECsBd,KAAA,CAAM,OAAA,YAAmB,OAAA,CAAQ,mBAAA;EDpBZ;ECsBrB,gBAAA,CAAiB,IAAA,WAAe,MAAA,YAAkB,OAAA,CAAQ,mBAAA;EDpBnD;ECsBP,iBAAA,CAAkB,IAAA,UAAc,MAAA,WAAiB,OAAA,CAAQ,mBAAA;EDb3C;ECed,cAAA,CAAe,IAAA,UAAc,MAAA,YAAkB,OAAA,CAAQ,mBAAA;AAAA;;UAIxC,eAAA,SAAwB,aAAA;EDjB9B;EAAA,SCmBA,MAAA;EDlBK;EAAA,SCoBL,cAAA,WAAyB,mBAAA;EDtB5B;EAAA,SCwBG,KAAA,EAAO,iBAAA;EDxBhB;EC0BA,YAAA,IAAgB,mBAAA;EDzBhB;EC2BA,SAAA;ED1Bc;EC4Bd,aAAA,IAAiB,mBAAA;AAAA;;iBA2HH,SAAA,CAAU,IAAA,EAAM,aAAA,EAAe,OAAA,GAAS,gBAAA,GAAwB,eAAA"}
@@ -0,0 +1,282 @@
1
+ import { A as SCREEN_READER_KEY, C as FOCUS_KEY, D as APP_KEY, O as createAppContext, _ as STREAMS_KEY, a as lastMouseEvent, h as createCursorContext, i as lastKeyEvent, m as CURSOR_KEY, n as lastCompositionEvent, o as lastPasteEvent, r as lastFocusEvent, s as lastResizeEvent, u as createRenderer$1, v as createStreamsContext, w as createFocusManager } from "../app-D_Ke7C8w.mjs";
2
+ import { h, nextTick, ref } from "@vue/runtime-core";
3
+ //#region src/testing/mount.ts
4
+ /**
5
+ * Test-only mount harness for the Fresco JS layer.
6
+ *
7
+ * Fresco's renderer is pure JS up to the byte boundary that crosses into the
8
+ * native module: `renderer.ts` imports only types from `@vizejs/fresco-native`,
9
+ * and `app.ts` loads the native binding lazily and only in interactive mode.
10
+ * This harness mounts components through the real custom renderer with the
11
+ * same provide set `createApp`/`renderToString` install, so tests assert the
12
+ * mounted `FrescoNode` output tree without a native build and without mocks.
13
+ *
14
+ * Not exported from the package entry points; `vp pack` does not ship it.
15
+ */
16
+ /**
17
+ * Mount a component (or a plain render function) through the Fresco renderer.
18
+ *
19
+ * The returned {@link MountedFresco.root} is live: re-read it after reactive
20
+ * updates plus `await nextTick()` to observe the patched output tree.
21
+ */
22
+ function mountFresco(root, options = {}) {
23
+ const { createApp } = createRenderer$1();
24
+ const app = createApp(typeof root === "function" ? { setup: () => root } : root);
25
+ const focusManager = createFocusManager();
26
+ const screenReaderEnabled = ref(options.screenReader ?? false);
27
+ const noopWrite = () => {};
28
+ const width = options.width ?? 80;
29
+ const height = options.height ?? 24;
30
+ const appContext = createAppContext({
31
+ width,
32
+ height,
33
+ stdout: {
34
+ isTTY: false,
35
+ columns: width,
36
+ rows: height,
37
+ write: () => true
38
+ }
39
+ });
40
+ app.provide(APP_KEY, appContext);
41
+ app.provide(FOCUS_KEY, focusManager);
42
+ app.provide(SCREEN_READER_KEY, screenReaderEnabled);
43
+ app.provide(CURSOR_KEY, createCursorContext(noopWrite));
44
+ app.provide(STREAMS_KEY, createStreamsContext({
45
+ interactive: false,
46
+ writeToStdout: noopWrite,
47
+ writeToStderr: noopWrite
48
+ }));
49
+ const rootElement = {
50
+ id: -1,
51
+ type: "root",
52
+ props: {},
53
+ children: [],
54
+ parent: null
55
+ };
56
+ app.mount(rootElement);
57
+ return {
58
+ root: rootElement,
59
+ app,
60
+ appContext,
61
+ focusManager,
62
+ screenReaderEnabled,
63
+ unmount: () => app.unmount()
64
+ };
65
+ }
66
+ /**
67
+ * Dispatch a key event through the same `lastKeyEvent` ref the interactive
68
+ * event loop writes, then wait for watchers to flush.
69
+ *
70
+ * Pass `char` for printable input or `key` for named keys ("left", "enter",
71
+ * "backspace", ...). Modifiers default to false.
72
+ */
73
+ function dispatchKey(event) {
74
+ lastKeyEvent.value = {
75
+ type: "key",
76
+ ctrl: false,
77
+ alt: false,
78
+ shift: false,
79
+ meta: false,
80
+ super: false,
81
+ hyper: false,
82
+ capsLock: false,
83
+ numLock: false,
84
+ ...event
85
+ };
86
+ return nextTick();
87
+ }
88
+ /** Dispatch a sequence of printable characters via {@link dispatchKey}. */
89
+ async function typeChars(text) {
90
+ for (const char of text) await dispatchKey({ char });
91
+ }
92
+ function definedProps(props) {
93
+ const entries = Object.entries(props).filter(([, value]) => value !== void 0);
94
+ if (entries.length === 0) return void 0;
95
+ return Object.fromEntries(entries);
96
+ }
97
+ /**
98
+ * Convert a mounted node (or the harness root) into a plain snapshot object
99
+ * suitable for `assert.deepEqual` against an inline expected structure.
100
+ */
101
+ function toTreeSnapshot(node) {
102
+ const snapshot = { type: node.type };
103
+ if (node.text !== void 0) snapshot.text = node.text;
104
+ const props = definedProps(node.props);
105
+ if (props) snapshot.props = props;
106
+ if (node.children.length > 0) snapshot.children = node.children.map((child) => toTreeSnapshot(child));
107
+ return snapshot;
108
+ }
109
+ /** Depth-first list of nodes matching a predicate, starting at `node`. */
110
+ function findNodes(node, predicate) {
111
+ const matches = [];
112
+ const visit = (current) => {
113
+ if (predicate(current)) matches.push(current);
114
+ for (const child of current.children) visit(child);
115
+ };
116
+ visit(node);
117
+ return matches;
118
+ }
119
+ /** First mounted child under the harness root, asserting it exists. */
120
+ function firstChild(mounted) {
121
+ const child = mounted.root.children[0];
122
+ if (!child) throw new Error("expected the mounted tree to have a root child");
123
+ return child;
124
+ }
125
+ /** Convenience wrapper that renders `component` with `h` and mounts it. */
126
+ function mountComponent(component, props = {}, slot, options = {}) {
127
+ return mountFresco(() => h(component, props, slot), options);
128
+ }
129
+ //#endregion
130
+ //#region src/testing/index.ts
131
+ /**
132
+ * Test utilities for Fresco component and app behavior.
133
+ *
134
+ * The harness mounts through the real Vue custom renderer and records frames
135
+ * without loading the native terminal binding, so it is safe in plain Node,
136
+ * Vitest, and `node:test` suites.
137
+ */
138
+ function nodeText(node) {
139
+ if (node.text !== void 0) return node.text;
140
+ const propText = node.props.text ?? node.props.content;
141
+ if (typeof propText === "string" || typeof propText === "number") return String(propText);
142
+ if (node.type !== "input") return "";
143
+ const value = node.props.value;
144
+ const placeholder = node.props.placeholder;
145
+ if (typeof value === "string" || typeof value === "number") return String(value);
146
+ if (typeof placeholder === "string" || typeof placeholder === "number") return String(placeholder);
147
+ return "";
148
+ }
149
+ function isStaticNode(node) {
150
+ return node.props.internal_static === true || node.props.internalStatic === true;
151
+ }
152
+ function joinOutput(parts, separator = "\n") {
153
+ return parts.filter(Boolean).join(separator);
154
+ }
155
+ function normalizeOutput(output) {
156
+ return output.endsWith("\n") ? output.slice(0, -1) : output;
157
+ }
158
+ function appendOutput(previous, next) {
159
+ const normalized = normalizeOutput(next);
160
+ if (!normalized) return previous;
161
+ return previous ? `${previous}\n${normalized}` : normalized;
162
+ }
163
+ function treeToFrameOutput(node, options = {}) {
164
+ if (options.skipStatic && isStaticNode(node)) return "";
165
+ const ownText = nodeText(node);
166
+ const childOutput = node.children.map((child) => treeToFrameOutput(child, options));
167
+ if (node.type === "text" || node.type === "input") return `${ownText}${childOutput.join("")}`;
168
+ const style = node.props.style ?? {};
169
+ return joinOutput(childOutput, (style.flexDirection ?? style.flex_direction) === "column" ? "\n" : "");
170
+ }
171
+ function captureStaticOutput(node, renderedStaticItems) {
172
+ const output = [];
173
+ const visit = (current) => {
174
+ if (isStaticNode(current)) {
175
+ const renderedItems = renderedStaticItems.get(current.id) ?? 0;
176
+ const nextOutput = joinOutput(current.children.slice(renderedItems).map((child) => treeToFrameOutput(child)), "\n");
177
+ if (nextOutput) output.push(nextOutput);
178
+ renderedStaticItems.set(current.id, current.children.length);
179
+ return;
180
+ }
181
+ for (const child of current.children) visit(child);
182
+ };
183
+ visit(node);
184
+ return joinOutput(output, "\n");
185
+ }
186
+ function renderFrame(root, staticOutput) {
187
+ return joinOutput([staticOutput, treeToFrameOutput(root, { skipStatic: true })], "\n");
188
+ }
189
+ function latestSnapshot(snapshots) {
190
+ const snapshot = snapshots[snapshots.length - 1];
191
+ if (!snapshot) throw new Error("expected renderTui to record an initial frame");
192
+ return snapshot;
193
+ }
194
+ async function dispatchPaste(text) {
195
+ lastPasteEvent.value = {
196
+ type: "paste",
197
+ text
198
+ };
199
+ await nextTick();
200
+ }
201
+ async function dispatchMouse(event) {
202
+ lastMouseEvent.value = {
203
+ type: "mouse",
204
+ ...event
205
+ };
206
+ await nextTick();
207
+ }
208
+ async function dispatchFocus(focused) {
209
+ lastFocusEvent.value = {
210
+ type: "focus",
211
+ focused
212
+ };
213
+ await nextTick();
214
+ }
215
+ async function dispatchComposition(type, text, cursor) {
216
+ lastCompositionEvent.value = {
217
+ type,
218
+ text,
219
+ cursor
220
+ };
221
+ await nextTick();
222
+ }
223
+ async function dispatchResize(mounted, width, height) {
224
+ lastResizeEvent.value = {
225
+ type: "resize",
226
+ width,
227
+ height
228
+ };
229
+ mounted.appContext.width.value = width;
230
+ mounted.appContext.height.value = height;
231
+ await nextTick();
232
+ }
233
+ /** Mount a Fresco app for tests with frame snapshots and input injection. */
234
+ function renderTui(root, options = {}) {
235
+ const mounted = mountFresco(root, options);
236
+ const frames = [];
237
+ const frameSnapshots = [];
238
+ const renderedStaticItems = /* @__PURE__ */ new Map();
239
+ let staticOutput = "";
240
+ const captureFrame = () => {
241
+ staticOutput = appendOutput(staticOutput, captureStaticOutput(mounted.root, renderedStaticItems));
242
+ const snapshot = {
243
+ output: renderFrame(mounted.root, staticOutput),
244
+ tree: toTreeSnapshot(mounted.root)
245
+ };
246
+ frames.push(snapshot.output);
247
+ frameSnapshots.push(snapshot);
248
+ return snapshot;
249
+ };
250
+ const recordAfter = async (dispatch) => {
251
+ await dispatch;
252
+ return captureFrame();
253
+ };
254
+ const input = {
255
+ key: (event) => recordAfter(dispatchKey(event)),
256
+ async text(text) {
257
+ for (const char of text) await dispatchKey({ char });
258
+ return captureFrame();
259
+ },
260
+ paste: (text) => recordAfter(dispatchPaste(text)),
261
+ resize: (width, height) => recordAfter(dispatchResize(mounted, width, height)),
262
+ mouse: (event) => recordAfter(dispatchMouse(event)),
263
+ focus: (focused) => recordAfter(dispatchFocus(focused)),
264
+ compositionStart: (text = "", cursor = 0) => recordAfter(dispatchComposition("compositionstart", text, cursor)),
265
+ compositionUpdate: (text, cursor) => recordAfter(dispatchComposition("compositionupdate", text, cursor)),
266
+ compositionEnd: (text, cursor = text.length) => recordAfter(dispatchComposition("compositionend", text, cursor))
267
+ };
268
+ captureFrame();
269
+ return {
270
+ ...mounted,
271
+ frames,
272
+ frameSnapshots,
273
+ input,
274
+ captureFrame,
275
+ lastFrame: () => latestSnapshot(frameSnapshots).output,
276
+ frameSnapshot: () => latestSnapshot(frameSnapshots)
277
+ };
278
+ }
279
+ //#endregion
280
+ export { dispatchKey, findNodes, firstChild, mountComponent, mountFresco, renderTui, toTreeSnapshot, typeChars };
281
+
282
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["createRenderer"],"sources":["../../src/testing/mount.ts","../../src/testing/index.ts"],"sourcesContent":["/**\n * Test-only mount harness for the Fresco JS layer.\n *\n * Fresco's renderer is pure JS up to the byte boundary that crosses into the\n * native module: `renderer.ts` imports only types from `@vizejs/fresco-native`,\n * and `app.ts` loads the native binding lazily and only in interactive mode.\n * This harness mounts components through the real custom renderer with the\n * same provide set `createApp`/`renderToString` install, so tests assert the\n * mounted `FrescoNode` output tree without a native build and without mocks.\n *\n * Not exported from the package entry points; `vp pack` does not ship it.\n */\n\nimport {\n h,\n ref,\n nextTick,\n type App as VueApp,\n type Component,\n type Ref,\n type VNodeChild,\n} from \"@vue/runtime-core\";\nimport { SCREEN_READER_KEY } from \"../accessibility.js\";\nimport { lastKeyEvent, type KeyEvent } from \"../app.js\";\nimport { APP_KEY, createAppContext, type UseAppReturn } from \"../composables/useApp.js\";\nimport { createCursorContext, CURSOR_KEY } from \"../composables/useCursor.js\";\nimport { createFocusManager, FOCUS_KEY, type FocusManager } from \"../composables/useFocus.js\";\nimport { createStreamsContext, STREAMS_KEY } from \"../composables/useStreams.js\";\nimport { createRenderer, type FrescoElement, type FrescoNode } from \"../renderer.js\";\n\n/** Options for {@link mountFresco}. */\nexport interface MountFrescoOptions {\n /** Terminal width provided through the app context. Defaults to 80. */\n width?: number;\n /** Terminal height provided through the app context. Defaults to 24. */\n height?: number;\n /** Initial screen reader mode. Defaults to false. */\n screenReader?: boolean;\n}\n\n/** A mounted Fresco component tree under test. */\nexport interface MountedFresco {\n /** Root element the Vue app mounted into; children form the output tree. */\n root: FrescoElement;\n /** The Vue app instance (for `provide`-level introspection if needed). */\n app: VueApp<FrescoElement>;\n /** App context provided to composables such as `useApp` and `useWindowSize`. */\n appContext: UseAppReturn;\n /** Focus manager provided to the tree, as the real app would. */\n focusManager: FocusManager;\n /** Screen reader flag provided to the tree; writable to flip modes. */\n screenReaderEnabled: Ref<boolean>;\n /** Unmount the tree and dispose all component watchers. */\n unmount(): void;\n}\n\n/**\n * Mount a component (or a plain render function) through the Fresco renderer.\n *\n * The returned {@link MountedFresco.root} is live: re-read it after reactive\n * updates plus `await nextTick()` to observe the patched output tree.\n */\nexport function mountFresco(\n root: Component | (() => VNodeChild),\n options: MountFrescoOptions = {},\n): MountedFresco {\n const { createApp } = createRenderer();\n const component: Component = typeof root === \"function\" ? { setup: () => root } : root;\n const app = createApp(component);\n\n const focusManager = createFocusManager();\n const screenReaderEnabled = ref(options.screenReader ?? false);\n const noopWrite = () => {};\n const width = options.width ?? 80;\n const height = options.height ?? 24;\n // A detached stdout stand-in keeps createAppContext from attaching resize\n // listeners to the real process.stdout on every mounted test tree.\n const stdout = {\n isTTY: false,\n columns: width,\n rows: height,\n write: () => true,\n } as unknown as NodeJS.WriteStream;\n\n const appContext = createAppContext({ width, height, stdout });\n app.provide(APP_KEY, appContext);\n app.provide(FOCUS_KEY, focusManager);\n app.provide(SCREEN_READER_KEY, screenReaderEnabled);\n app.provide(CURSOR_KEY, createCursorContext(noopWrite));\n app.provide(\n STREAMS_KEY,\n createStreamsContext({\n interactive: false,\n writeToStdout: noopWrite,\n writeToStderr: noopWrite,\n }),\n );\n\n const rootElement: FrescoElement = {\n id: -1,\n type: \"root\",\n props: {},\n children: [],\n parent: null,\n };\n app.mount(rootElement);\n\n return {\n root: rootElement,\n app,\n appContext,\n focusManager,\n screenReaderEnabled,\n unmount: () => app.unmount(),\n };\n}\n\n/**\n * Dispatch a key event through the same `lastKeyEvent` ref the interactive\n * event loop writes, then wait for watchers to flush.\n *\n * Pass `char` for printable input or `key` for named keys (\"left\", \"enter\",\n * \"backspace\", ...). Modifiers default to false.\n */\nexport function dispatchKey(event: Partial<Omit<KeyEvent, \"type\">>): Promise<void> {\n lastKeyEvent.value = {\n type: \"key\",\n ctrl: false,\n alt: false,\n shift: false,\n meta: false,\n super: false,\n hyper: false,\n capsLock: false,\n numLock: false,\n ...event,\n };\n return nextTick();\n}\n\n/** Dispatch a sequence of printable characters via {@link dispatchKey}. */\nexport async function typeChars(text: string): Promise<void> {\n for (const char of text) {\n await dispatchKey({ char });\n }\n}\n\n/**\n * Serializable snapshot of a mounted output tree.\n *\n * Node ids come from a module-global counter and depend on mount order, and\n * `parent` back-references make trees cyclic, so both are omitted. Props are\n * kept only when defined, so snapshots stay inline-sized and deterministic.\n */\nexport interface FrescoNodeSnapshot {\n type: FrescoNode[\"type\"];\n text?: string;\n props?: Record<string, unknown>;\n children?: FrescoNodeSnapshot[];\n}\n\nfunction definedProps(props: Record<string, unknown>): Record<string, unknown> | undefined {\n const entries = Object.entries(props).filter(([, value]) => value !== undefined);\n if (entries.length === 0) return undefined;\n return Object.fromEntries(entries);\n}\n\n/**\n * Convert a mounted node (or the harness root) into a plain snapshot object\n * suitable for `assert.deepEqual` against an inline expected structure.\n */\nexport function toTreeSnapshot(node: FrescoNode): FrescoNodeSnapshot {\n const snapshot: FrescoNodeSnapshot = { type: node.type };\n if (node.text !== undefined) snapshot.text = node.text;\n const props = definedProps(node.props);\n if (props) snapshot.props = props;\n if (node.children.length > 0) {\n snapshot.children = node.children.map((child) => toTreeSnapshot(child));\n }\n return snapshot;\n}\n\n/** Depth-first list of nodes matching a predicate, starting at `node`. */\nexport function findNodes(\n node: FrescoNode,\n predicate: (candidate: FrescoNode) => boolean,\n): FrescoNode[] {\n const matches: FrescoNode[] = [];\n const visit = (current: FrescoNode) => {\n if (predicate(current)) matches.push(current);\n for (const child of current.children) visit(child);\n };\n visit(node);\n return matches;\n}\n\n/** First mounted child under the harness root, asserting it exists. */\nexport function firstChild(mounted: MountedFresco): FrescoNode {\n const child = mounted.root.children[0];\n if (!child) throw new Error(\"expected the mounted tree to have a root child\");\n return child;\n}\n\n/** Convenience wrapper that renders `component` with `h` and mounts it. */\nexport function mountComponent(\n component: Component,\n props: Record<string, unknown> = {},\n slot?: () => VNodeChild,\n options: MountFrescoOptions = {},\n): MountedFresco {\n return mountFresco(() => h(component, props, slot), options);\n}\n","/**\n * Test utilities for Fresco component and app behavior.\n *\n * The harness mounts through the real Vue custom renderer and records frames\n * without loading the native terminal binding, so it is safe in plain Node,\n * Vitest, and `node:test` suites.\n */\n\nimport { nextTick, type Component, type VNodeChild } from \"@vue/runtime-core\";\nimport {\n lastCompositionEvent,\n lastFocusEvent,\n lastMouseEvent,\n lastPasteEvent,\n lastResizeEvent,\n type CompositionEvent,\n type FocusEvent,\n type KeyEvent,\n type MouseEvent,\n type PasteEvent,\n type ResizeEvent,\n} from \"../app.js\";\nimport type { FrescoNode } from \"../renderer.js\";\nimport {\n dispatchKey,\n mountFresco,\n toTreeSnapshot,\n type FrescoNodeSnapshot,\n type MountedFresco,\n type MountFrescoOptions,\n} from \"./mount.js\";\n\nexport {\n dispatchKey,\n findNodes,\n firstChild,\n mountComponent,\n mountFresco,\n toTreeSnapshot,\n typeChars,\n type FrescoNodeSnapshot,\n type MountedFresco,\n type MountFrescoOptions,\n} from \"./mount.js\";\n\nexport type RenderTuiOptions = MountFrescoOptions;\nexport type RenderTuiRoot = Component | (() => VNodeChild);\nexport type KeyInput = Partial<Omit<KeyEvent, \"type\">>;\nexport type MouseInput = Omit<MouseEvent, \"type\">;\n\n/** A single recorded harness frame. */\nexport interface FrescoFrameSnapshot {\n /** Plain terminal output projected from the current mounted tree. */\n output: string;\n /** Serializable renderer tree for structural assertions. */\n tree: FrescoNodeSnapshot;\n}\n\n/** Input driver that injects events through Fresco's public event refs. */\nexport interface FrescoInputDriver {\n /** Dispatch a key event and record the resulting frame. */\n key(event: KeyInput): Promise<FrescoFrameSnapshot>;\n /** Dispatch each printable character and record one frame after the text. */\n text(text: string): Promise<FrescoFrameSnapshot>;\n /** Dispatch a bracketed paste payload and record the resulting frame. */\n paste(text: string): Promise<FrescoFrameSnapshot>;\n /** Update the provided app size, dispatch a resize event, and record a frame. */\n resize(width: number, height: number): Promise<FrescoFrameSnapshot>;\n /** Dispatch a mouse event and record the resulting frame. */\n mouse(event: MouseInput): Promise<FrescoFrameSnapshot>;\n /** Dispatch terminal focus state and record the resulting frame. */\n focus(focused: boolean): Promise<FrescoFrameSnapshot>;\n /** Dispatch a composition start event and record the resulting frame. */\n compositionStart(text?: string, cursor?: number): Promise<FrescoFrameSnapshot>;\n /** Dispatch a composition update event and record the resulting frame. */\n compositionUpdate(text: string, cursor: number): Promise<FrescoFrameSnapshot>;\n /** Dispatch a composition end event and record the resulting frame. */\n compositionEnd(text: string, cursor?: number): Promise<FrescoFrameSnapshot>;\n}\n\n/** Mounted TUI harness with frame history and input helpers. */\nexport interface RenderTuiResult extends MountedFresco {\n /** Recorded plain frames, including the initial mount frame. */\n readonly frames: readonly string[];\n /** Recorded frame snapshots, including the initial mount frame. */\n readonly frameSnapshots: readonly FrescoFrameSnapshot[];\n /** Event injection helpers. */\n readonly input: FrescoInputDriver;\n /** Record and return a fresh snapshot of the current mounted tree. */\n captureFrame(): FrescoFrameSnapshot;\n /** Return the latest recorded plain frame. */\n lastFrame(): string;\n /** Return the latest recorded frame snapshot. */\n frameSnapshot(): FrescoFrameSnapshot;\n}\n\nfunction nodeText(node: FrescoNode): string {\n if (node.text !== undefined) return node.text;\n const propText = node.props.text ?? node.props.content;\n if (typeof propText === \"string\" || typeof propText === \"number\") return String(propText);\n if (node.type !== \"input\") return \"\";\n\n const value = node.props.value;\n const placeholder = node.props.placeholder;\n if (typeof value === \"string\" || typeof value === \"number\") return String(value);\n if (typeof placeholder === \"string\" || typeof placeholder === \"number\")\n return String(placeholder);\n return \"\";\n}\n\nfunction isStaticNode(node: FrescoNode): boolean {\n return node.props.internal_static === true || node.props.internalStatic === true;\n}\n\nfunction joinOutput(parts: readonly string[], separator = \"\\n\"): string {\n return parts.filter(Boolean).join(separator);\n}\n\nfunction normalizeOutput(output: string): string {\n return output.endsWith(\"\\n\") ? output.slice(0, -1) : output;\n}\n\nfunction appendOutput(previous: string, next: string): string {\n const normalized = normalizeOutput(next);\n if (!normalized) return previous;\n return previous ? `${previous}\\n${normalized}` : normalized;\n}\n\nfunction treeToFrameOutput(node: FrescoNode, options: { skipStatic?: boolean } = {}): string {\n if (options.skipStatic && isStaticNode(node)) return \"\";\n\n const ownText = nodeText(node);\n const childOutput = node.children.map((child) => treeToFrameOutput(child, options));\n\n if (node.type === \"text\" || node.type === \"input\") {\n return `${ownText}${childOutput.join(\"\")}`;\n }\n\n const style = (node.props.style ?? {}) as Record<string, unknown>;\n const flexDirection = style.flexDirection ?? style.flex_direction;\n const separator = flexDirection === \"column\" ? \"\\n\" : \"\";\n return joinOutput(childOutput, separator);\n}\n\nfunction captureStaticOutput(node: FrescoNode, renderedStaticItems: Map<number, number>): string {\n const output: string[] = [];\n\n const visit = (current: FrescoNode) => {\n if (isStaticNode(current)) {\n const renderedItems = renderedStaticItems.get(current.id) ?? 0;\n const nextItems = current.children.slice(renderedItems);\n const nextOutput = joinOutput(\n nextItems.map((child) => treeToFrameOutput(child)),\n \"\\n\",\n );\n if (nextOutput) output.push(nextOutput);\n renderedStaticItems.set(current.id, current.children.length);\n return;\n }\n\n for (const child of current.children) visit(child);\n };\n\n visit(node);\n return joinOutput(output, \"\\n\");\n}\n\nfunction renderFrame(root: FrescoNode, staticOutput: string): string {\n return joinOutput([staticOutput, treeToFrameOutput(root, { skipStatic: true })], \"\\n\");\n}\n\nfunction latestSnapshot(snapshots: readonly FrescoFrameSnapshot[]): FrescoFrameSnapshot {\n const snapshot = snapshots[snapshots.length - 1];\n if (!snapshot) throw new Error(\"expected renderTui to record an initial frame\");\n return snapshot;\n}\n\nasync function dispatchPaste(text: string): Promise<void> {\n const event: PasteEvent = { type: \"paste\", text };\n lastPasteEvent.value = event;\n await nextTick();\n}\n\nasync function dispatchMouse(event: MouseInput): Promise<void> {\n lastMouseEvent.value = { type: \"mouse\", ...event };\n await nextTick();\n}\n\nasync function dispatchFocus(focused: boolean): Promise<void> {\n const event: FocusEvent = { type: \"focus\", focused };\n lastFocusEvent.value = event;\n await nextTick();\n}\n\nasync function dispatchComposition(\n type: CompositionEvent[\"type\"],\n text: string,\n cursor: number,\n): Promise<void> {\n lastCompositionEvent.value = { type, text, cursor };\n await nextTick();\n}\n\nasync function dispatchResize(\n mounted: MountedFresco,\n width: number,\n height: number,\n): Promise<void> {\n const event: ResizeEvent = { type: \"resize\", width, height };\n lastResizeEvent.value = event;\n mounted.appContext.width.value = width;\n mounted.appContext.height.value = height;\n await nextTick();\n}\n\n/** Mount a Fresco app for tests with frame snapshots and input injection. */\nexport function renderTui(root: RenderTuiRoot, options: RenderTuiOptions = {}): RenderTuiResult {\n const mounted = mountFresco(root, options);\n const frames: string[] = [];\n const frameSnapshots: FrescoFrameSnapshot[] = [];\n const renderedStaticItems = new Map<number, number>();\n let staticOutput = \"\";\n\n const captureFrame = () => {\n staticOutput = appendOutput(\n staticOutput,\n captureStaticOutput(mounted.root, renderedStaticItems),\n );\n const snapshot: FrescoFrameSnapshot = {\n output: renderFrame(mounted.root, staticOutput),\n tree: toTreeSnapshot(mounted.root),\n };\n frames.push(snapshot.output);\n frameSnapshots.push(snapshot);\n return snapshot;\n };\n\n const recordAfter = async (dispatch: Promise<void>) => {\n await dispatch;\n return captureFrame();\n };\n\n const input: FrescoInputDriver = {\n key: (event) => recordAfter(dispatchKey(event)),\n async text(text) {\n for (const char of text) {\n await dispatchKey({ char });\n }\n return captureFrame();\n },\n paste: (text) => recordAfter(dispatchPaste(text)),\n resize: (width, height) => recordAfter(dispatchResize(mounted, width, height)),\n mouse: (event) => recordAfter(dispatchMouse(event)),\n focus: (focused) => recordAfter(dispatchFocus(focused)),\n compositionStart: (text = \"\", cursor = 0) =>\n recordAfter(dispatchComposition(\"compositionstart\", text, cursor)),\n compositionUpdate: (text, cursor) =>\n recordAfter(dispatchComposition(\"compositionupdate\", text, cursor)),\n compositionEnd: (text, cursor = text.length) =>\n recordAfter(dispatchComposition(\"compositionend\", text, cursor)),\n };\n\n captureFrame();\n\n return {\n ...mounted,\n frames,\n frameSnapshots,\n input,\n captureFrame,\n lastFrame: () => latestSnapshot(frameSnapshots).output,\n frameSnapshot: () => latestSnapshot(frameSnapshots),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { Ref } from "@vue/runtime-core";
2
+
3
+ //#region src/composables/useFocus.d.ts
4
+ interface UseFocusOptions {
5
+ /** Enable or disable this focus target while keeping its logical identity */
6
+ isActive?: boolean | Ref<boolean>;
7
+ /** Auto-focus this component when nothing else is focused */
8
+ autoFocus?: boolean;
9
+ /** Focus ID for this element */
10
+ id?: string;
11
+ }
12
+ interface FocusManager {
13
+ /** Currently focused element ID */
14
+ focusedId: Ref<string | null>;
15
+ /** Ink-compatible currently focused ID alias */
16
+ activeId: Readonly<Ref<string | undefined>>;
17
+ /** All active focusable element IDs */
18
+ focusableIds: Readonly<Ref<string[]>>;
19
+ /** Whether focus management is enabled */
20
+ isEnabled: Ref<boolean>;
21
+ /** Enable focus management */
22
+ enableFocus: () => void;
23
+ /** Disable focus management */
24
+ disableFocus: () => void;
25
+ /** Focus a specific element */
26
+ focus: (id: string) => void;
27
+ /** Focus next element */
28
+ focusNext: () => void;
29
+ /** Focus previous element */
30
+ focusPrevious: () => void;
31
+ /** Register a focusable element */
32
+ register: (id: string, options?: {
33
+ isActive?: boolean;
34
+ autoFocus?: boolean;
35
+ }) => void;
36
+ /** Unregister a focusable element */
37
+ unregister: (id: string) => void;
38
+ /** Activate or deactivate a focusable element while keeping its order */
39
+ setActive: (id: string, isActive: boolean) => void;
40
+ }
41
+ /**
42
+ * Use focus management
43
+ */
44
+ declare function useFocus(options?: UseFocusOptions): {
45
+ id: string;
46
+ isFocused: import("@vue/reactivity").ComputedRef<boolean>;
47
+ focus: (targetId?: string) => void;
48
+ blur: () => void;
49
+ };
50
+ /**
51
+ * Use global focus manager controls.
52
+ */
53
+ declare function useFocusManager(): {
54
+ enableFocus: () => void;
55
+ disableFocus: () => void;
56
+ focusNext: () => void;
57
+ focusPrevious: () => void;
58
+ focus: (id: string) => void;
59
+ activeId: Readonly<Ref<string | undefined, string | undefined>>;
60
+ focusableIds: Readonly<Ref<string[], string[]>>;
61
+ };
62
+ //#endregion
63
+ //#region src/composables/useApp.d.ts
64
+ interface UseAppReturn {
65
+ /** Terminal width */
66
+ width: Ref<number>;
67
+ /** Terminal height */
68
+ height: Ref<number>;
69
+ /** Whether app is running */
70
+ isRunning: Ref<boolean>;
71
+ /** Exit the app */
72
+ exit: (errorOrResult?: unknown) => void;
73
+ /** Force re-render */
74
+ render: () => void;
75
+ /** Clear the screen */
76
+ clear: () => void;
77
+ /** Wait until pending render output has flushed */
78
+ waitUntilRenderFlush: () => Promise<void>;
79
+ }
80
+ /**
81
+ * Use app context
82
+ */
83
+ declare function useApp(): UseAppReturn;
84
+ //#endregion
85
+ export { useFocus as a, UseFocusOptions as i, useApp as n, useFocusManager as o, FocusManager as r, UseAppReturn as t };
86
+ //# sourceMappingURL=useApp-CNjer3hJ.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useApp-CNjer3hJ.d.mts","names":[],"sources":["../src/composables/useFocus.ts","../src/composables/useApp.ts"],"mappings":";;;UAkBiB,eAAA;EAES;EAAxB,QAAA,aAAqB,GAAG;EAAH;EAErB,SAAA;EAEA;EAAA,EAAA;AAAA;AAAA,UAGe,YAAA;EAAY;EAE3B,SAAA,EAAW,GAAA;EAAA;EAEX,QAAA,EAAU,QAAA,CAAS,GAAA;EAAT;EAEV,YAAA,EAAc,QAAA,CAAS,GAAA;EAAT;EAEd,SAAA,EAAW,GAAA;EAAG;EAEd,WAAA;EARA;EAUA,YAAA;EARA;EAUA,KAAA,GAAQ,EAAA;EAVW;EAYnB,SAAA;EAVc;EAYd,aAAA;EAVA;EAYA,QAAA,GAAW,EAAA,UAAY,OAAA;IAAY,QAAA;IAAoB,SAAA;EAAA;EAN/C;EAQR,UAAA,GAAa,EAAA;EAJb;EAMA,SAAA,GAAY,EAAA,UAAY,QAAA;AAAA;;AAAiB;AA4H3C;iBAAgB,QAAA,CAAS,OAAA,GAAS,eAAoB;;;;;;;;;iBAgEtC,eAAA;;;;;;;;;;;UCvOC,YAAA;EDYS;ECVxB,KAAA,EAAO,GAAA;EDUc;ECRrB,MAAA,EAAQ,GAAA;EDYR;ECVA,SAAA,EAAW,GAAA;EDUT;ECRF,IAAA,GAAO,aAAA;EDWoB;ECT3B,MAAA;EDWW;ECTX,KAAA;EDWU;ECTV,oBAAA,QAA4B,OAAA;AAAA;;;;iBAwEd,MAAA,IAAU,YAAY"}