@vizejs/fresco 0.715.0 → 0.717.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.
Files changed (37) hide show
  1. package/dist/accessibility-DY_alnTr.d.mts +123 -0
  2. package/dist/accessibility-DY_alnTr.d.mts.map +1 -0
  3. package/dist/{app-DoXcF_Dn.mjs → app-B9yJzz5S.mjs} +284 -88
  4. package/dist/app-B9yJzz5S.mjs.map +1 -0
  5. package/dist/{app-adHHi99c.d.mts → app-CKsTWQuP.d.mts} +38 -19
  6. package/dist/app-CKsTWQuP.d.mts.map +1 -0
  7. package/dist/components/index.d.mts +2 -2
  8. package/dist/components/index.mjs +2 -2
  9. package/dist/{components-DWIDBGTL.mjs → components-C57eT1BL.mjs} +646 -434
  10. package/dist/components-C57eT1BL.mjs.map +1 -0
  11. package/dist/composables/index.d.mts +1 -1
  12. package/dist/composables/index.mjs +3 -3
  13. package/dist/{composables-b_R5s4QQ.mjs → composables-uj7hkBfP.mjs} +3 -3
  14. package/dist/{composables-b_R5s4QQ.mjs.map → composables-uj7hkBfP.mjs.map} +1 -1
  15. package/dist/{index-8MIK02ET.d.mts → index-BjK3JSNo.d.mts} +338 -770
  16. package/dist/index-BjK3JSNo.d.mts.map +1 -0
  17. package/dist/{index-B-asCcvK.d.mts → index-Dikou9bu.d.mts} +4 -2
  18. package/dist/{index-B-asCcvK.d.mts.map → index-Dikou9bu.d.mts.map} +1 -1
  19. package/dist/index.d.mts +6 -6
  20. package/dist/index.mjs +8 -5
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{protocol--xy80nuC.d.mts → protocol-D8xt3DG7.d.mts} +31 -1
  23. package/dist/protocol-D8xt3DG7.d.mts.map +1 -0
  24. package/dist/testing/index.d.mts +8 -2
  25. package/dist/testing/index.d.mts.map +1 -1
  26. package/dist/testing/index.mjs +10 -7
  27. package/dist/testing/index.mjs.map +1 -1
  28. package/dist/{useVirtualViewport-CcJuIc7U.mjs → useVirtualViewport-DYuUlTxU.mjs} +2 -2
  29. package/dist/{useVirtualViewport-CcJuIc7U.mjs.map → useVirtualViewport-DYuUlTxU.mjs.map} +1 -1
  30. package/package.json +2 -2
  31. package/dist/accessibility-D1R9HiTE.d.mts +0 -40
  32. package/dist/accessibility-D1R9HiTE.d.mts.map +0 -1
  33. package/dist/app-DoXcF_Dn.mjs.map +0 -1
  34. package/dist/app-adHHi99c.d.mts.map +0 -1
  35. package/dist/components-DWIDBGTL.mjs.map +0 -1
  36. package/dist/index-8MIK02ET.d.mts.map +0 -1
  37. package/dist/protocol--xy80nuC.d.mts.map +0 -1
@@ -0,0 +1,123 @@
1
+ import { S as FrescoRenderNodeKind, m as FrescoInputEvent } from "./protocol-D8xt3DG7.mjs";
2
+ import { Ref, RendererElement, RendererNode } from "vue";
3
+
4
+ //#region src/renderer.d.ts
5
+ /** Read-only view of a node owned by one Fresco renderer instance. */
6
+ interface FrescoHostNode {
7
+ /** Stable, process-unique identifier retained across keyed moves and updates. */
8
+ readonly id: number;
9
+ /** Native render-node kind represented by this host node. */
10
+ readonly type: FrescoRenderNodeKind;
11
+ /** Current Vue host properties. */
12
+ readonly props: Readonly<Record<string, unknown>>;
13
+ /** Ordered child host nodes. */
14
+ readonly children: readonly FrescoHostNode[];
15
+ /** Parent host node, or null while detached. */
16
+ readonly parent: FrescoHostNode | null;
17
+ /** Current text-node content when present. */
18
+ readonly text?: string;
19
+ }
20
+ /** Declared host dimensions observed before native layout. */
21
+ interface FrescoDeclaredSize {
22
+ /** Declared width, when the host props provide one. */
23
+ readonly width?: string | number;
24
+ /** Declared height, when the host props provide one. */
25
+ readonly height?: string | number;
26
+ }
27
+ /** Host-tree update category passed to lifecycle observers. */
28
+ type FrescoNodeUpdateKind = "children" | "props" | "text";
29
+ /** Position of a routed input event within its stable host-node path. */
30
+ type FrescoInputEventPhase = "capture" | "target" | "bubble";
31
+ /** Input event plus the stable host-node routing context. */
32
+ interface FrescoRoutedInputEvent<Event extends FrescoInputEvent = FrescoInputEvent> {
33
+ /** Normalized terminal input supplied by the caller. */
34
+ readonly input: Event;
35
+ /** Original live host node selected for this dispatch. */
36
+ readonly target: FrescoHostNode;
37
+ /** Node whose handlers are currently running. */
38
+ readonly currentTarget: FrescoHostNode;
39
+ /** Capture, target, or bubble portion of the route. */
40
+ readonly phase: FrescoInputEventPhase;
41
+ /** Whether a handler has prevented the associated default action. */
42
+ readonly defaultPrevented: boolean;
43
+ /** Whether traversal beyond the current node has been stopped. */
44
+ readonly propagationStopped: boolean;
45
+ /** Prevent the caller from performing the input event default action. */
46
+ preventDefault(): void;
47
+ /** Stop traversal after every handler on the current node has run. */
48
+ stopPropagation(): void;
49
+ }
50
+ /** Public host prop callback accepted by routed input dispatch. */
51
+ type FrescoInputEventHandler<Event extends FrescoInputEvent = FrescoInputEvent> = (event: FrescoRoutedInputEvent<Event>) => void;
52
+ /** Immutable receipt returned after one routed input dispatch. */
53
+ interface FrescoInputDispatchResult {
54
+ /** Stable target that owned the dispatch route. */
55
+ readonly target: FrescoHostNode;
56
+ /** Number of generic and event-specific callbacks invoked. */
57
+ readonly handled: number;
58
+ /** Whether any callback prevented the default action. */
59
+ readonly defaultPrevented: boolean;
60
+ /** Whether any callback stopped route traversal. */
61
+ readonly propagationStopped: boolean;
62
+ }
63
+ /** Optional lifecycle observers for nodes owned by a Fresco renderer. */
64
+ interface FrescoNodeLifecycleHooks {
65
+ /** Observe allocation of a stable host node. @default No-op. */
66
+ onCreate?(node: FrescoHostNode): void;
67
+ /** Observe a committed host-tree update. @default No-op. */
68
+ onUpdate?(node: FrescoHostNode, kind: FrescoNodeUpdateKind): void;
69
+ /** Observe a change to declared width or height. @default No-op. */
70
+ onResize?(node: FrescoHostNode, previous: FrescoDeclaredSize, current: FrescoDeclaredSize): void;
71
+ /** Observe recursive disposal after descendants have been removed. @default No-op. */
72
+ onRemove?(node: FrescoHostNode): void;
73
+ }
74
+ /** Configuration for {@link createRenderer}. */
75
+ interface FrescoRendererOptions {
76
+ /** Observe a host-tree mutation that requires native frame work. @default No-op. */
77
+ onMutation?: () => void;
78
+ /** Observe stable-node lifecycle transitions. @default No lifecycle observers. */
79
+ lifecycle?: FrescoNodeLifecycleHooks;
80
+ }
81
+ /** @internal Mutable Vue host node; not part of the public render protocol. */
82
+ interface FrescoNode extends RendererNode, FrescoHostNode {
83
+ id: number;
84
+ type: FrescoRenderNodeKind;
85
+ props: Record<string, unknown>;
86
+ children: FrescoNode[];
87
+ parent: FrescoNode | null;
88
+ text?: string;
89
+ }
90
+ /** @internal Mutable Vue host element; not part of the public render protocol. */
91
+ interface FrescoElement extends FrescoNode, RendererElement {}
92
+ /**
93
+ * Create a Fresco renderer with stable host-node lookup and lifecycle control.
94
+ */
95
+ declare function createRenderer$1(options?: FrescoRendererOptions | (() => void)): import("vue").Renderer<FrescoElement> & {
96
+ /** Return one live host node by its stable identifier. */getNode(id: number): FrescoHostNode | undefined; /** Find one node under the supplied subtree without crossing its boundary. */
97
+ findDescendantById(root: FrescoHostNode, id: number): FrescoHostNode | undefined; /** Detach and dispose a registered node and every descendant exactly once. */
98
+ destroyRecursively(id: number): boolean; /** Route normalized input through capture, target, and bubble handlers. */
99
+ dispatchInput<Event extends FrescoInputEvent>(targetId: number, input: Event): FrescoInputDispatchResult | undefined;
100
+ };
101
+ //#endregion
102
+ //#region src/accessibility.d.ts
103
+ /** Semantic roles supported by Fresco screen-reader serialization. */
104
+ type AriaRole = "button" | "checkbox" | "combobox" | "list" | "listbox" | "listitem" | "menu" | "menuitem" | "option" | "progressbar" | "radio" | "radiogroup" | "scrollbar" | "tab" | "tablist" | "table" | "textbox" | "timer" | "toolbar";
105
+ /** Accessible state rendered alongside the semantic node description. */
106
+ interface AriaState {
107
+ busy?: boolean;
108
+ checked?: boolean;
109
+ disabled?: boolean;
110
+ expanded?: boolean;
111
+ multiline?: boolean;
112
+ multiselectable?: boolean;
113
+ readonly?: boolean;
114
+ required?: boolean;
115
+ selected?: boolean;
116
+ orientation?: "horizontal" | "vertical";
117
+ valueMin?: number;
118
+ valueMax?: number;
119
+ valueNow?: number;
120
+ }
121
+ //#endregion
122
+ export { FrescoHostNode as a, FrescoInputEventPhase as c, FrescoNodeUpdateKind as d, FrescoRendererOptions as f, FrescoElement as i, FrescoNode as l, createRenderer$1 as m, AriaState as n, FrescoInputDispatchResult as o, FrescoRoutedInputEvent as p, FrescoDeclaredSize as r, FrescoInputEventHandler as s, AriaRole as t, FrescoNodeLifecycleHooks as u };
123
+ //# sourceMappingURL=accessibility-DY_alnTr.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility-DY_alnTr.d.mts","names":[],"sources":["../src/renderer.ts","../src/accessibility.ts"],"mappings":";;;;;UAgBiB,cAAA;EAMU;EAAA,SAJhB,EAAA;EAMmB;EAAA,SAJnB,IAAA,EAAM,oBAAA;EAMgB;EAAA,SAJtB,KAAA,EAAO,QAAA,CAAS,MAAA;EAJhB;EAAA,SAMA,QAAA,WAAmB,cAAA;EAJb;EAAA,SAMN,MAAA,EAAQ,cAAA;EAJD;EAAA,SAMP,IAAA;AAAA;;UAIM,kBAAA;EANE;EAAA,SAQR,KAAA;EANI;EAAA,SAQJ,MAAM;AAAA;;KAIL,oBAAA;;KAGA,qBAAA;AAHZ;AAAA,UAMiB,sBAAA,eAAqC,gBAAA,GAAmB,gBAAA;;WAE9D,KAAA,EAAO,KAAA;EARc;EAAA,SAUrB,MAAA,EAAQ,cAAA;EAPc;EAAA,SAStB,aAAA,EAAe,cAAA;EATO;EAAA,SAWtB,KAAA,EAAO,qBAAA;EARD;EAAA,SAUN,gBAAA;EAV4B;EAAA,SAY5B,kBAAA;EAZ8D;EAcvE,cAAA;EAViB;EAYjB,eAAA;AAAA;;KAIU,uBAAA,eAAsC,gBAAA,GAAmB,gBAAA,KACnE,KAAA,EAAO,sBAAA,CAAuB,KAAA;;UAIf,yBAAA;EAzBwD;EAAA,SA2B9D,MAAA,EAAQ,cAAc;EAzBf;EAAA,SA2BP,OAAA;EAzBQ;EAAA,SA2BR,gBAAA;EAzBe;EAAA,SA2Bf,kBAAA;AAAA;;UAGM,wBAAA;EAtBf;EAwBA,QAAA,EAAU,IAAA,EAAM,cAAA;EAtBD;EAwBf,QAAA,EAAU,IAAA,EAAM,cAAA,EAAgB,IAAA,EAAM,oBAAA;EApB5B;EAsBV,QAAA,EAAU,IAAA,EAAM,cAAA,EAAgB,QAAA,EAAU,kBAAA,EAAoB,OAAA,EAAS,kBAAA;EAtBtC;EAwBjC,QAAA,EAAU,IAAA,EAAM,cAAA;AAAA;;UAID,qBAAA;EA3Bc;EA6B7B,UAAA;EA9BkC;EAgClC,SAAA,GAAY,wBAAwB;AAAA;;UAIrB,UAAA,SAAmB,YAAA,EAAc,cAAA;EAChD,EAAA;EACA,IAAA,EAAM,oBAAA;EACN,KAAA,EAAO,MAAA;EACP,QAAA,EAAU,UAAA;EACV,MAAA,EAAQ,UAAA;EACR,IAAA;AAAA;;UAIe,aAAA,SAAsB,UAAA,EAAY,eAAe;;;;iBA8OlD,gBAAA,CAAe,OAAA,GAAS,qBAAA,gCAAyC,QAAA,CAAA,aAAA;EA5QhE,+EAuRQ,cAAA;2BAII,cAAA,EAAc,EAAA,WAAe,cAAA,cAvRxC;2CAEA;8BAwSc,gBAAA,EAAgB,QAAA,UAC1B,KAAA,EACT,KAAA,GACN,yBAAA;AAAA;;;;KCtXK,QAAA;;UAsBK,SAAA;EACf,IAAA;EACA,OAAA;EACA,QAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,QAAA;EACA,QAAA;EACA,QAAA;EACA,WAAA;EACA,QAAA;EACA,QAAA;EACA,QAAA;AAAA"}
@@ -96,6 +96,7 @@ function nonEmptyStringValue(value) {
96
96
  const string = stringValue$1(value);
97
97
  return string ? string : void 0;
98
98
  }
99
+ /** Normalize component style input into the canonical native render style. */
99
100
  function normalizeFrescoStyle(value) {
100
101
  const source = recordValue(value);
101
102
  const style = {};
@@ -177,6 +178,7 @@ function wrappingEnabled(value, mode) {
177
178
  if (value === void 0 || value === false) return false;
178
179
  return !(mode?.startsWith("truncate") ?? false);
179
180
  }
181
+ /** Convert one internal Fresco node into its strict public render protocol node. */
180
182
  function frescoNodeToRenderNode(node) {
181
183
  const common = commonFields(node);
182
184
  if (node.type === "root" || node.type === "box") return {
@@ -217,7 +219,9 @@ function frescoNodeToRenderNode(node) {
217
219
  }
218
220
  //#endregion
219
221
  //#region src/accessibility.ts
222
+ /** Injection key exposing whether screen-reader output is active. */
220
223
  const SCREEN_READER_KEY = Symbol("fresco-screen-reader");
224
+ /** Read the Ink- and Fresco-compatible screen-reader environment flags. */
221
225
  function isScreenReaderEnabledByDefault() {
222
226
  return process.env.INK_SCREEN_READER === "true" || process.env.FRESCO_SCREEN_READER === "true";
223
227
  }
@@ -253,6 +257,7 @@ function ariaState(node) {
253
257
  function isAriaHidden(node) {
254
258
  return node.props["aria-hidden"] === true;
255
259
  }
260
+ /** Serialize a Fresco tree into deterministic semantic screen-reader text. */
256
261
  function treeToScreenReaderString(node, options = {}) {
257
262
  if (isAriaHidden(node)) return "";
258
263
  let output = "";
@@ -275,6 +280,7 @@ function treeToScreenReaderString(node, options = {}) {
275
280
  if (role && role !== options.parentRole) output = `${role}: ${output}`;
276
281
  return output;
277
282
  }
283
+ /** Convert a Fresco tree into the minimal native nodes used for screen-reader output. */
278
284
  function treeToScreenReaderRenderNodes(root) {
279
285
  const output = treeToScreenReaderString(root);
280
286
  const rootStyle = normalizeFrescoStyle(root.props.style) ?? {};
@@ -664,14 +670,17 @@ function useCursor() {
664
670
  //#endregion
665
671
  //#region src/layoutMetrics.ts
666
672
  let lastRenderLayouts = [];
673
+ /** Replace the layout snapshot exposed by the most recent native render. */
667
674
  function updateLastRenderLayouts(layouts) {
668
675
  lastRenderLayouts = layouts;
669
676
  }
677
+ /** Look up the latest native layout for an element or component public instance. */
670
678
  function getLastRenderLayout(node) {
671
679
  const id = node?.id ?? node?.$el?.id;
672
680
  if (id === void 0) return null;
673
681
  return lastRenderLayouts.find((layout) => layout.id === id) ?? null;
674
682
  }
683
+ /** Return the latest element dimensions, falling back to zero before layout. */
675
684
  function measureElement(node) {
676
685
  const layout = getLastRenderLayout(node);
677
686
  return {
@@ -681,6 +690,7 @@ function measureElement(node) {
681
690
  }
682
691
  //#endregion
683
692
  //#region src/nativeInput.ts
693
+ /** Accept only key event phases supported by the public Fresco protocol. */
684
694
  function normalizeKeyEventType(value) {
685
695
  switch (value) {
686
696
  case "press":
@@ -715,72 +725,160 @@ function normalizeMouseEvent(event) {
715
725
  * Fresco Vue Custom Renderer
716
726
  */
717
727
  let nextId = 0;
718
- function createNode(type) {
728
+ /** Keep only dimension values accepted by the public style contract. */
729
+ function dimension(value) {
730
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
731
+ }
732
+ /** Read the dimensions declared directly by a host node before native layout. */
733
+ function declaredSize(node) {
734
+ const style = typeof node.props.style === "object" && node.props.style !== null ? node.props.style : {};
719
735
  return {
736
+ width: dimension(style.width ?? node.props.width),
737
+ height: dimension(style.height ?? node.props.height)
738
+ };
739
+ }
740
+ /** Compare declared dimensions without coercing numeric and string values. */
741
+ function sameSize(left, right) {
742
+ return Object.is(left.width, right.width) && Object.is(left.height, right.height);
743
+ }
744
+ /** Allocate and register one stable host node before exposing it to Vue. */
745
+ function createNode(tree, type, text) {
746
+ const node = {
720
747
  id: nextId++,
721
748
  type,
722
749
  props: {},
723
750
  children: [],
724
- parent: null
751
+ parent: null,
752
+ ...text === void 0 ? {} : { text }
725
753
  };
754
+ tree.nodes.set(node.id, node);
755
+ tree.lifecycle.onCreate?.(node);
756
+ return node;
757
+ }
758
+ /** Publish a committed host-tree update to the optional lifecycle observer. */
759
+ function notifyUpdate(tree, node, kind) {
760
+ tree.lifecycle.onUpdate?.(node, kind);
761
+ }
762
+ /** Dispose a registered subtree depth-first, reporting each node once. */
763
+ function disposeNode(tree, node) {
764
+ if (!tree.nodes.has(node.id)) return false;
765
+ for (const child of node.children) disposeNode(tree, child);
766
+ node.children = [];
767
+ node.parent = null;
768
+ tree.nodes.delete(node.id);
769
+ tree.lifecycle.onRemove?.(node);
770
+ return true;
771
+ }
772
+ /** Detach a node only after its parent collection reflects the removal. */
773
+ function detachNode(tree, node) {
774
+ const parent = node.parent;
775
+ if (!parent) return false;
776
+ const index = parent.children.indexOf(node);
777
+ node.parent = null;
778
+ if (index === -1) return false;
779
+ parent.children.splice(index, 1);
780
+ notifyUpdate(tree, parent, "children");
781
+ return true;
782
+ }
783
+ function routedInputEventName(event) {
784
+ switch (event.type) {
785
+ case "compositionstart": return "CompositionStart";
786
+ case "compositionupdate": return "CompositionUpdate";
787
+ case "compositionend": return "CompositionEnd";
788
+ default: return `${event.type[0]?.toUpperCase() ?? ""}${event.type.slice(1)}`;
789
+ }
790
+ }
791
+ function callInputHandlers(value, event) {
792
+ if (typeof value === "function") {
793
+ value(event);
794
+ return 1;
795
+ }
796
+ if (!Array.isArray(value)) return 0;
797
+ let handled = 0;
798
+ for (const handler of value) handled += callInputHandlers(handler, event);
799
+ return handled;
800
+ }
801
+ function dispatchAtNode(node, event, capture, eventName) {
802
+ const suffix = capture ? "Capture" : "";
803
+ let handled = callInputHandlers(node.props[`onFrescoInput${suffix}`], event);
804
+ handled += callInputHandlers(node.props[`on${eventName}${suffix}`], event);
805
+ return handled;
726
806
  }
727
807
  /**
728
808
  * Renderer options for Fresco
729
809
  */
730
- const rendererOptions = {
731
- patchProp(el, key, _prevValue, nextValue) {
732
- if (nextValue == null) delete el.props[key];
733
- else el.props[key] = nextValue;
734
- },
735
- insert(child, parent, anchor) {
736
- if (child.parent) {
737
- const existing = child.parent.children.indexOf(child);
738
- if (existing !== -1) child.parent.children.splice(existing, 1);
739
- }
740
- child.parent = parent;
741
- if (anchor) {
742
- const index = parent.children.indexOf(anchor);
743
- if (index !== -1) {
744
- parent.children.splice(index, 0, child);
745
- return;
810
+ function rendererOptions(tree) {
811
+ return {
812
+ patchProp(el, key, _prevValue, nextValue) {
813
+ const previousSize = declaredSize(el);
814
+ if (nextValue == null) {
815
+ if (!(key in el.props)) return;
816
+ delete el.props[key];
817
+ } else {
818
+ if (Object.is(el.props[key], nextValue)) return;
819
+ el.props[key] = nextValue;
746
820
  }
821
+ notifyUpdate(tree, el, "props");
822
+ const currentSize = declaredSize(el);
823
+ if (!sameSize(previousSize, currentSize)) tree.lifecycle.onResize?.(el, previousSize, currentSize);
824
+ tree.onMutation();
825
+ },
826
+ insert(child, parent, anchor) {
827
+ const previousParent = child.parent;
828
+ if (previousParent) {
829
+ const existing = previousParent.children.indexOf(child);
830
+ if (existing !== -1) previousParent.children.splice(existing, 1);
831
+ child.parent = null;
832
+ }
833
+ child.parent = parent;
834
+ const index = anchor ? parent.children.indexOf(anchor) : -1;
835
+ if (index === -1) parent.children.push(child);
836
+ else parent.children.splice(index, 0, child);
837
+ try {
838
+ if (previousParent && previousParent !== parent) notifyUpdate(tree, previousParent, "children");
839
+ notifyUpdate(tree, parent, "children");
840
+ } finally {
841
+ tree.onMutation();
842
+ }
843
+ },
844
+ remove(child) {
845
+ const detached = detachNode(tree, child);
846
+ const disposed = disposeNode(tree, child);
847
+ if (detached || disposed) tree.onMutation();
848
+ },
849
+ createElement(type) {
850
+ return createNode(tree, mapElementType(type));
851
+ },
852
+ createText(text) {
853
+ return createNode(tree, "text", text);
854
+ },
855
+ createComment() {
856
+ return createNode(tree, "text");
857
+ },
858
+ setText(node, text) {
859
+ if (node.text === text) return;
860
+ node.text = text;
861
+ notifyUpdate(tree, node, "text");
862
+ tree.onMutation();
863
+ },
864
+ setElementText(el, text) {
865
+ if (el.text === text && el.children.length === 0) return;
866
+ for (const child of el.children) disposeNode(tree, child);
867
+ el.text = text;
868
+ el.children = [];
869
+ notifyUpdate(tree, el, "text");
870
+ tree.onMutation();
871
+ },
872
+ parentNode(node) {
873
+ return node.parent;
874
+ },
875
+ nextSibling(node) {
876
+ if (!node.parent) return null;
877
+ const index = node.parent.children.indexOf(node);
878
+ return node.parent.children[index + 1] || null;
747
879
  }
748
- parent.children.push(child);
749
- },
750
- remove(child) {
751
- if (child.parent) {
752
- const index = child.parent.children.indexOf(child);
753
- if (index !== -1) child.parent.children.splice(index, 1);
754
- child.parent = null;
755
- }
756
- },
757
- createElement(type) {
758
- return createNode(mapElementType(type));
759
- },
760
- createText(text) {
761
- const node = createNode("text");
762
- node.text = text;
763
- return node;
764
- },
765
- createComment() {
766
- return createNode("text");
767
- },
768
- setText(node, text) {
769
- node.text = text;
770
- },
771
- setElementText(el, text) {
772
- el.text = text;
773
- el.children = [];
774
- },
775
- parentNode(node) {
776
- return node.parent;
777
- },
778
- nextSibling(node) {
779
- if (!node.parent) return null;
780
- const index = node.parent.children.indexOf(node);
781
- return node.parent.children[index + 1] || null;
782
- }
783
- };
880
+ };
881
+ }
784
882
  /**
785
883
  * Map Vue element types to Fresco node types
786
884
  */
@@ -797,10 +895,89 @@ function mapElementType(type) {
797
895
  }
798
896
  }
799
897
  /**
800
- * Create the Fresco renderer
898
+ * Create a Fresco renderer with stable host-node lookup and lifecycle control.
801
899
  */
802
- function createRenderer$1() {
803
- return createRenderer(rendererOptions);
900
+ function createRenderer$1(options = {}) {
901
+ const normalized = typeof options === "function" ? { onMutation: options } : options;
902
+ const tree = {
903
+ nodes: /* @__PURE__ */ new Map(),
904
+ lifecycle: normalized.lifecycle ?? {},
905
+ onMutation: normalized.onMutation ?? (() => {})
906
+ };
907
+ const renderer = createRenderer(rendererOptions(tree));
908
+ return Object.assign(renderer, {
909
+ /** Return one live host node by its stable identifier. */
910
+ getNode(id) {
911
+ return tree.nodes.get(id);
912
+ },
913
+ /** Find one node under the supplied subtree without crossing its boundary. */
914
+ findDescendantById(root, id) {
915
+ if (root.id === id) return root;
916
+ const registered = tree.nodes.get(id);
917
+ if (!registered) return void 0;
918
+ for (let parent = registered.parent; parent; parent = parent.parent) if (parent === root) return registered;
919
+ },
920
+ /** Detach and dispose a registered node and every descendant exactly once. */
921
+ destroyRecursively(id) {
922
+ const node = tree.nodes.get(id);
923
+ if (!node) return false;
924
+ detachNode(tree, node);
925
+ disposeNode(tree, node);
926
+ tree.onMutation();
927
+ return true;
928
+ },
929
+ /** Route normalized input through capture, target, and bubble handlers. */
930
+ dispatchInput(targetId, input) {
931
+ const target = tree.nodes.get(targetId);
932
+ if (!target) return void 0;
933
+ const path = [];
934
+ for (let node = target; node; node = node.parent) path.push(node);
935
+ const routed = {
936
+ input,
937
+ target,
938
+ currentTarget: target,
939
+ phase: "target",
940
+ defaultPrevented: false,
941
+ propagationStopped: false,
942
+ preventDefault() {
943
+ this.defaultPrevented = true;
944
+ },
945
+ stopPropagation() {
946
+ this.propagationStopped = true;
947
+ }
948
+ };
949
+ const eventName = routedInputEventName(input);
950
+ let handled = 0;
951
+ for (let index = path.length - 1; index > 0; index -= 1) {
952
+ const node = path[index];
953
+ if (!node) continue;
954
+ routed.currentTarget = node;
955
+ routed.phase = "capture";
956
+ handled += dispatchAtNode(node, routed, true, eventName);
957
+ if (routed.propagationStopped) break;
958
+ }
959
+ if (!routed.propagationStopped) {
960
+ routed.currentTarget = target;
961
+ routed.phase = "target";
962
+ handled += dispatchAtNode(target, routed, true, eventName);
963
+ handled += dispatchAtNode(target, routed, false, eventName);
964
+ }
965
+ if (!routed.propagationStopped) for (let index = 1; index < path.length; index += 1) {
966
+ const node = path[index];
967
+ if (!node) continue;
968
+ routed.currentTarget = node;
969
+ routed.phase = "bubble";
970
+ handled += dispatchAtNode(node, routed, false, eventName);
971
+ if (routed.propagationStopped) break;
972
+ }
973
+ return {
974
+ target,
975
+ handled,
976
+ defaultPrevented: routed.defaultPrevented,
977
+ propagationStopped: routed.propagationStopped
978
+ };
979
+ }
980
+ });
804
981
  }
805
982
  /**
806
983
  * Convert Fresco tree to render nodes for native
@@ -816,28 +993,8 @@ function treeToRenderNodes(root) {
816
993
  return nodes;
817
994
  }
818
995
  //#endregion
819
- //#region src/app.ts
820
- /**
821
- * Fresco App - Application instance management
822
- */
823
- const lastKeyEvent = ref(null);
824
- const lastPasteEvent = ref(null);
825
- const lastResizeEvent = ref(null);
826
- const lastMouseEvent = ref(null);
827
- const lastFocusEvent = ref(null);
828
- const lastCompositionEvent = ref(null);
829
- let native = null;
830
- const consoleMethods = [
831
- "debug",
832
- "error",
833
- "info",
834
- "log",
835
- "warn"
836
- ];
837
- async function loadNative() {
838
- if (!native) native = await import("@vizejs/fresco-native");
839
- return native;
840
- }
996
+ //#region src/renderToStringStreams.ts
997
+ /** Create a detached writable stream for non-interactive Fresco rendering. */
841
998
  function createNoopWriteStream(columns, rows) {
842
999
  const stream = {
843
1000
  isTTY: false,
@@ -851,6 +1008,7 @@ function createNoopWriteStream(columns, rows) {
851
1008
  };
852
1009
  return stream;
853
1010
  }
1011
+ /** Create a detached readable stream for non-interactive Fresco rendering. */
854
1012
  function createNoopReadStream() {
855
1013
  const stream = {
856
1014
  isTTY: false,
@@ -868,6 +1026,45 @@ function createNoopReadStream() {
868
1026
  };
869
1027
  return stream;
870
1028
  }
1029
+ //#endregion
1030
+ //#region src/staticNodes.ts
1031
+ /** Whether a renderer node owns append-only static output. */
1032
+ function isStaticNode(node) {
1033
+ return node.props.internal_static === true || node.props.internalStatic === true;
1034
+ }
1035
+ /** Whether a renderer node is one append-only item inside a static owner. */
1036
+ function isStaticItemNode(node) {
1037
+ return node.props.internal_static_item === true || node.props.internalStaticItem === true;
1038
+ }
1039
+ //#endregion
1040
+ //#region src/app.ts
1041
+ /**
1042
+ * Fresco App - Application instance management
1043
+ */
1044
+ /** Latest normalized key event received by the active Fresco application. */
1045
+ const lastKeyEvent = ref(null);
1046
+ /** Latest bracketed-paste event received by the active Fresco application. */
1047
+ const lastPasteEvent = ref(null);
1048
+ /** Latest terminal resize event received by the active Fresco application. */
1049
+ const lastResizeEvent = ref(null);
1050
+ /** Latest normalized mouse event received by the active Fresco application. */
1051
+ const lastMouseEvent = ref(null);
1052
+ /** Latest terminal focus event received by the active Fresco application. */
1053
+ const lastFocusEvent = ref(null);
1054
+ /** Latest IME composition event received by the active Fresco application. */
1055
+ const lastCompositionEvent = ref(null);
1056
+ let native = null;
1057
+ const consoleMethods = [
1058
+ "debug",
1059
+ "error",
1060
+ "info",
1061
+ "log",
1062
+ "warn"
1063
+ ];
1064
+ async function loadNative() {
1065
+ if (!native) native = await import("@vizejs/fresco-native");
1066
+ return native;
1067
+ }
871
1068
  function isWritableStream(value) {
872
1069
  return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
873
1070
  }
@@ -914,9 +1111,6 @@ function nodeText(node) {
914
1111
  }
915
1112
  return "";
916
1113
  }
917
- function isStaticNode(node) {
918
- return node.props.internal_static === true || node.props.internalStatic === true;
919
- }
920
1114
  function joinOutput(parts, separator = "\n") {
921
1115
  return parts.filter(Boolean).join(separator);
922
1116
  }
@@ -941,9 +1135,10 @@ function captureStaticOutput(node, renderedStaticItems) {
941
1135
  function visit(current) {
942
1136
  if (isStaticNode(current)) {
943
1137
  const renderedItems = renderedStaticItems.get(current.id) ?? 0;
944
- const nextOutput = joinOutput(current.children.slice(renderedItems).map((child) => treeToString(child)), "\n");
1138
+ const staticItems = current.children.filter(isStaticItemNode);
1139
+ const nextOutput = joinOutput(staticItems.slice(renderedItems).map((child) => treeToString(child)), "\n");
945
1140
  if (nextOutput) output.push(nextOutput);
946
- renderedStaticItems.set(current.id, current.children.length);
1141
+ renderedStaticItems.set(current.id, staticItems.length);
947
1142
  return;
948
1143
  }
949
1144
  for (const child of current.children) visit(child);
@@ -1067,7 +1262,9 @@ function createApp(rootComponent, options = {}) {
1067
1262
  resolveExit = resolve;
1068
1263
  rejectExit = reject;
1069
1264
  });
1070
- const { createApp: createVueApp } = createRenderer$1();
1265
+ const { createApp: createVueApp } = createRenderer$1(() => {
1266
+ needsRender = true;
1267
+ });
1071
1268
  async function mount() {
1072
1269
  if (mounted) return;
1073
1270
  const n = interactive ? await loadNative() : null;
@@ -1335,7 +1532,6 @@ function createApp(rootComponent, options = {}) {
1335
1532
  render();
1336
1533
  needsRender = false;
1337
1534
  }
1338
- needsRender = true;
1339
1535
  } catch (error) {
1340
1536
  if (onError) onError(error);
1341
1537
  }
@@ -1413,6 +1609,6 @@ function renderToString(root, options = {}) {
1413
1609
  return output;
1414
1610
  }
1415
1611
  //#endregion
1416
- export { useApp as A, useStreamsContext as C, useFocusManager as D, useFocus as E, isScreenReaderEnabledByDefault as M, APP_KEY as O, useStdout as S, createFocusManager as T, useCursor as _, lastMouseEvent as a, useStderr as b, render as c, treeToRenderNodes as d, getLastRenderLayout as f, createCursorContext as g, CURSOR_KEY as h, lastKeyEvent as i, SCREEN_READER_KEY as j, createAppContext as k, renderToString as l, updateLastRenderLayouts as m, lastCompositionEvent as n, lastPasteEvent as o, measureElement as p, lastFocusEvent as r, lastResizeEvent as s, createApp as t, createRenderer$1 as u, STREAMS_KEY as v, FOCUS_KEY as w, useStdin as x, createStreamsContext as y };
1612
+ export { APP_KEY as A, useStdin as C, createFocusManager as D, FOCUS_KEY as E, useApp as M, SCREEN_READER_KEY as N, useFocus as O, isScreenReaderEnabledByDefault as P, useStderr as S, useStreamsContext as T, CURSOR_KEY as _, lastMouseEvent as a, STREAMS_KEY as b, render as c, isStaticNode as d, createRenderer$1 as f, updateLastRenderLayouts as g, measureElement as h, lastKeyEvent as i, createAppContext as j, useFocusManager as k, renderToString as l, getLastRenderLayout as m, lastCompositionEvent as n, lastPasteEvent as o, treeToRenderNodes as p, lastFocusEvent as r, lastResizeEvent as s, createApp as t, isStaticItemNode as u, createCursorContext as v, useStdout as w, createStreamsContext as x, useCursor as y };
1417
1613
 
1418
- //# sourceMappingURL=app-DoXcF_Dn.mjs.map
1614
+ //# sourceMappingURL=app-B9yJzz5S.mjs.map