@symbiote-native/vue 0.3.8 → 0.4.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.
@@ -1,25 +1,18 @@
1
- // createAnimatedComponent: the Vue twin of the React wrapper. It wraps a base component
2
- // (View / Text / Image / ScrollView / any) so it accepts AnimatedNodes in its props.
3
- // Same JS-driven path, NO native driver in the hot loop: a frame is the scoped commit
4
- // setNativeProps drives from the AnimatedProps leaf. The framework-agnostic
5
- // pieces (reduceProps / readPassthroughStyle / resolveHostNode / isAnimatedNode + the
6
- // AnimatedProps leaf itself) live in @symbiote-native/engine, shared verbatim with React; here
7
- // Vue supplies only the lifecycle.
8
- //
9
- // React → hooks: useMemo(leaf) + three useEffects + a callback ref. Vue → reactivity:
10
- // a render that rebuilds the leaf each pass (like React's per-render useMemo(rest)) + a
11
- // post-commit reconcile (onMounted/onUpdated, the Vue twin of a post-commit useEffect) +
12
- // a function ref. The per-frame path is unchanged and NEVER goes through Vue render:
13
- // value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
1
+ // createAnimatedComponent: the Vue twin of the React wrapper. Wraps a base component
2
+ // (View/Text/Image/ScrollView/any) so it accepts AnimatedNodes in its props. Framework-agnostic
3
+ // pieces (reduceProps/readPassthroughStyle/resolveHostNode/isAnimatedNode + the AnimatedProps
4
+ // leaf) live in @symbiote-native/engine, shared verbatim with React; Vue supplies only the
5
+ // lifecycle: a render that rebuilds the leaf each pass + a post-commit reconcile (onMounted/
6
+ // onUpdated) + a function ref. The per-frame path is unchanged and NEVER goes through Vue
7
+ // render: value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
14
8
  import { defineComponent, h, onBeforeUnmount, onMounted, onUpdated, shallowRef, } from '@vue/runtime-core';
15
- import { AnimatedProps, attachNativeEventHandler, isNativeAnimatedAvailable, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
9
+ import { createAnimatedLeafLifecycle, isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
16
10
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
11
  // RN's prop carrying explicit (already-rasterized) values that override the animated prop in
18
12
  // the COMMITTED props (sticky-header passthrough). Named once here so render/reconcile agree.
19
13
  const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
20
- // A base component is either a functional component (View / Text / Image, a function) or a
21
- // stateful defineComponent (ScrollView, an object). Read its display name without a cast for
22
- // the wrapper's devtools name.
14
+ // Reads either a functional component's or a stateful defineComponent's display name, without a
15
+ // cast, for the wrapper's devtools name.
23
16
  function baseName(component) {
24
17
  if (component === null || (typeof component !== 'function' && typeof component !== 'object')) {
25
18
  return 'Anonymous';
@@ -37,86 +30,35 @@ export function createAnimatedComponent(Component) {
37
30
  name: `Animated(${baseName(Component)})`,
38
31
  inheritAttrs: false,
39
32
  setup(_props, { attrs: rawAttrs, slots, expose }) {
40
- // The committed host node, held by IDENTITY in a shallowRef (the reactivity rule: a deep
41
- // ref() would run it through toReactive() and hand back a Proxy, so AnimatedProps.setNativeView
42
- // / attachNativeEventHandler would miss the engine's WeakMap mirror, keyed on the raw node).
43
- // Same rule as Switch / ScrollView host nodes.
33
+ // shallowRef, not ref: a deep ref() would run it through toReactive() and hand back a
34
+ // Proxy, missing the engine's WeakMap mirror. Same rule as Switch / ScrollView host nodes.
44
35
  const nodeRef = shallowRef(null);
45
- // The base component's public instance (a ScrollView handle, or the host node for View).
46
- // Forwarded to a parent ref via expose(). shallowRef: it may itself BE an engine node.
36
+ // The base component's public instance (a ScrollView handle, or the host node for View),
37
+ // forwarded to a parent ref via expose(). shallowRef: it may itself BE an engine node.
47
38
  const instanceRef = shallowRef(null);
48
- // The AnimatedProps leaf the next reconcile should wire in, rebuilt by render each pass
49
- // (like React's per-render useMemo(rest)). Plain (non-reactive) so writing it in render
50
- // triggers no reactive effect. `attached` is the leaf currently in the value graph.
51
- let pendingLeaf = null;
52
- let attached = null;
53
- // The current non-passthrough props, kept so the post-commit event-attach can re-scan them.
39
+ // The leaf lifecycle - build/swap/bind/detach, and the rebuild-vs-skip decision - is the
40
+ // engine's, shared by every adapter (core/engine/src/animated/leaf-lifecycle.ts). Vue owns
41
+ // only WHEN to run it: after mount and after every re-render, which is when `currentRest`
42
+ // has just been refreshed by the render function below.
43
+ const lifecycle = createAnimatedLeafLifecycle('vue');
44
+ // The props the next reconcile should wire in, refreshed by render each pass.
54
45
  let currentRest = {};
55
46
  let wantsNative = false;
56
- // Detachers for any Animated.event prop bound natively to the committed node (JS path: none).
57
- let eventDetachers = [];
58
- function detachEvents() {
59
- for (const detach of eventDetachers)
60
- detach();
61
- eventDetachers = [];
62
- }
63
- // Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
64
- // to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
65
- // native event handler with a committed tag, so the JS path stays the fallback. Rebound on every
66
- // reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
67
- function attachEvents() {
68
- detachEvents();
69
- const node = nodeRef.value;
70
- if (node === null)
71
- return;
72
- for (const key of Object.keys(currentRest)) {
73
- const attachment = attachNativeEventHandler(node, key, currentRest[key]);
74
- if (attachment !== undefined)
75
- eventDetachers.push(attachment.detach);
76
- }
77
- }
78
- // Post-commit reconcile (onMounted first paint, onUpdated every re-render, the Vue twin of a
79
- // post-commit useEffect). Attach the NEW leaf BEFORE detaching the OLD one: order is load-bearing.
80
- // A shared Value self-detaches (and drops its native node) the instant its child count hits
81
- // zero, so detaching first would kill a running native animation on any unrelated re-render
82
- // (mirrors RN's AnimatedComponent._attachProps). Then bind the captured node, go native if wanted,
83
- // and rebind native events.
84
47
  function reconcile() {
85
- const newLeaf = pendingLeaf;
86
- if (newLeaf === null)
87
- return;
88
- newLeaf.__attach();
89
- if (attached !== null && attached !== newLeaf)
90
- attached.__detach();
91
- attached = newLeaf;
92
- const node = nodeRef.value;
93
- if (node !== null)
94
- newLeaf.setNativeView(node);
95
- if (wantsNative)
96
- newLeaf.__makeNative();
97
- attachEvents();
48
+ lifecycle.reconcile(currentRest, isSymbioteNode(nodeRef.value) ? nodeRef.value : null, wantsNative);
98
49
  }
99
50
  onMounted(reconcile);
100
51
  onUpdated(reconcile);
101
- // Final teardown: detach the last-attached leaf and any native events on unmount.
102
- onBeforeUnmount(() => {
103
- detachEvents();
104
- if (attached !== null) {
105
- attached.__detach();
106
- attached = null;
107
- }
108
- });
109
- // Function ref (held stably, so Vue doesn't re-invoke it per patch). On mount the base hands
110
- // back its public instance; resolve it to the underlying host node (unwrapping a scroll-container
111
- // handle via getScrollNode), record THAT for the leaf binding / event attach, and keep the
112
- // ORIGINAL instance for ref forwarding. Vue calls it with null on unmount, node clears.
52
+ onBeforeUnmount(() => lifecycle.teardown());
53
+ // On mount the base hands back its public instance; resolve it to the underlying host node
54
+ // (unwrapping a scroll-container handle via getScrollNode) for the leaf binding / event
55
+ // attach, and keep the ORIGINAL instance for ref forwarding.
113
56
  const captureRef = (instance) => {
114
57
  instanceRef.value = instance;
115
58
  nodeRef.value = resolveHostNode(instance);
116
59
  };
117
- // Forward the caller's ref to the base instance: a parent ref on Animated.ScrollView still gets
118
- // scrollTo (the exposed handle), on Animated.View the host node. A delegating proxy because the
119
- // instance is captured async (after mount), so a snapshot exposed at setup time would be null.
60
+ // A delegating proxy because the instance is captured async (after mount), so a snapshot
61
+ // exposed at setup time would be null.
120
62
  expose(new Proxy(Object.create(null), {
121
63
  get: (_target, key) => {
122
64
  const instance = instanceRef.value;
@@ -130,11 +72,9 @@ export function createAnimatedComponent(Component) {
130
72
  },
131
73
  }));
132
74
  return () => {
133
- // Fold kebab template props to camelCase before splitting (idempotent: the base component
134
- // normalizes too, but keep `rest`/reduceProps on the RN contract here as well).
135
75
  const attrs = normalizeVueAttrs(rawAttrs);
136
- // Split the incoming attrs into the passthrough (consumed) and the rest (forwarded). The
137
- // passthrough never reaches the base as a prop; it only overrides the committed style below.
76
+ // Split into the passthrough (consumed) and the rest (forwarded). The passthrough never
77
+ // reaches the base as a prop; it only overrides the committed style below.
138
78
  const rest = {};
139
79
  let passthrough;
140
80
  for (const key of Object.keys(attrs)) {
@@ -146,16 +86,13 @@ export function createAnimatedComponent(Component) {
146
86
  }
147
87
  currentRest = rest;
148
88
  // Native driving is opt-in per the passthrough prop AND requires a real native module;
149
- // headless / unsupported hosts keep the JS flush path (and the JS smokes green).
89
+ // headless/unsupported hosts keep the JS flush path.
150
90
  wantsNative = passthrough != null && isNativeAnimatedAvailable();
151
- // One AnimatedProps leaf per render pass (rebuilt like React's useMemo(rest)); the post-commit
152
- // reconcile wires it into the graph and swaps the previous one out.
153
- pendingLeaf = new AnimatedProps(rest);
154
- // Reduced props are concrete (animated nodes replaced by their current values) so the first
155
- // paint carries real values, exactly like React's first render.
91
+ // Reduced props are concrete (animated nodes replaced by their current values) so the
92
+ // first paint carries real values.
156
93
  const reduced = reduceProps(rest);
157
- // Override the committed style with the explicit passthrough values (last wins via the style
158
- // array, which the commit layer flattens) so the ShadowTree carries the current transform.
94
+ // Override the committed style with the explicit passthrough values (last wins via the
95
+ // style array) so the ShadowTree carries the current transform.
159
96
  const passthroughStyle = readPassthroughStyle(passthrough);
160
97
  if (passthroughStyle !== undefined) {
161
98
  reduced.style =
@@ -1,8 +1,8 @@
1
- // The Animated namespace for @symbiote-native/vue: the Vue twin of adapters/react/src/animated/index.ts.
2
- // createAnimatedComponent applied to the Vue primitives gives Animated.View / Text / Image; the
3
- // value graph, easing and imperative drivers come from @symbiote-native/engine (framework-agnostic,
4
- // JS-driven), spread in verbatim. Both halves meet here so the familiar surface,
5
- // `Animated.timing(new Animated.Value(0), …).start()`, works against the Vue-driven engine.
1
+ // The Animated namespace for @symbiote-native/vue. createAnimatedComponent applied to the Vue
2
+ // primitives gives Animated.View/Text/Image; the value graph, easing and imperative drivers come
3
+ // from @symbiote-native/engine (framework-agnostic, JS-driven), spread in verbatim. Both halves
4
+ // meet here so `Animated.timing(new Animated.Value(0), ...).start()` works against the
5
+ // Vue-driven engine.
6
6
  import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
7
7
  import { View, Text } from '../../components.js';
8
8
  import { Image } from '../../components/image.js';
@@ -14,15 +14,12 @@ export { createAnimatedComponent } from './create-animated-component.js';
14
14
  const AnimatedView = createAnimatedComponent(View);
15
15
  const AnimatedText = createAnimatedComponent(Text);
16
16
  const AnimatedImage = createAnimatedComponent(Image);
17
- // ScrollView is wrapped behind a LAZY, memoized getter, mirroring RN's `get ScrollView()` and the
18
- // React adapter: ScrollView's module chain imports this Animated namespace back (sticky headers,
19
- // Phase 3), so a static createAnimatedComponent(ScrollView) at init could read ScrollView inside
20
- // its own TDZ. A memoized getter defers the wrap past module init, so the cycle never fires.
17
+ // LAZY, memoized getter, mirroring RN's `get ScrollView()`: ScrollView's module chain imports
18
+ // this Animated namespace back (sticky headers), so a static createAnimatedComponent(ScrollView)
19
+ // at init could read ScrollView inside its own TDZ. Deferring the wrap past module init avoids the cycle.
21
20
  let animatedScrollView;
22
- // FlatList / SectionList: DEFERRED. The Vue adapter has no FlatList/SectionList base component yet
23
- // (the React adapter does), so there is nothing to wrap; Animated.FlatList / Animated.SectionList
24
- // are intentionally OMITTED, not faked. They land once those base components exist (you cannot wrap
25
- // a component that does not exist). This is a named gap, not a silent drop.
21
+ // Animated.FlatList / Animated.SectionList are intentionally OMITTED, not faked - a named gap,
22
+ // not a silent drop.
26
23
  // The live, JS-driven driver namespace (real frames). RN's AnimatedImplementation.
27
24
  const liveDrivers = {
28
25
  Value: AnimatedValue,
@@ -48,10 +45,9 @@ const liveDrivers = {
48
45
  unforkEvent,
49
46
  };
50
47
  // RN swaps the WHOLE driver namespace for the mock when the host reports isDisableAnimations
51
- // (reduced motion / test env): the mock keeps the same surface but jumps each animation to its
52
- // final value synchronously, no frames. The animated COMPONENTS (View/Text/Image + the lazy
53
- // ScrollView getter) are live in both branches; only the drivers/value/operators/events half is
54
- // swapped, exactly like RN spreading `...Animated` (impl or mock) over the same component getters.
48
+ // (reduced motion/test env): the mock keeps the same surface but jumps each animation to its
49
+ // final value synchronously, no frames. The animated COMPONENTS stay live in both branches; only
50
+ // the drivers/value/operators/events half is swapped.
55
51
  const drivers = Platform.isDisableAnimations ? AnimatedMock : liveDrivers;
56
52
  export const Animated = {
57
53
  View: AnimatedView,
@@ -2,11 +2,9 @@
2
2
  // AppRegistry.registerComponent(appKey, () => App)
3
3
  // The registry bookkeeping (sections, host-registrar bridge, headless tasks) is
4
4
  // framework-agnostic and lives in @symbiote-native/engine's createAppRegistry; this file supplies
5
- // only the one Vue-specific seam — building a runnable from a component provider via a
6
- // synthetic functional root (h() has no createElement-style prop/children spread of its
7
- // own targets, so the wrap step is a render function rather than an object merge) — so
8
- // examples/vue-sfc's own registerRunnable + mount call can move onto the same entry point
9
- // React already has.
5
+ // only the one Vue-specific seam - building a runnable from a component provider via a synthetic
6
+ // functional root (h() has no createElement-style prop/children spread of its own targets, so
7
+ // the wrap step is a render function rather than an object merge).
10
8
  import { h } from '@vue/runtime-core';
11
9
  import { createAppRegistry, dlog, } from '@symbiote-native/engine';
12
10
  import { mount } from '../../render.js';
@@ -15,12 +13,10 @@ function runnableFor(componentProvider, getWrapperComponentProvider) {
15
13
  dlog(`AppRegistry: mounting on rootTag ${String(appParameters.rootTag)}`);
16
14
  const rootComponent = componentProvider();
17
15
  const renderRoot = () => h(rootComponent, appParameters.initialProps);
18
- // Wrap the root when a provider is set, mirroring RN's `registerComponent` passing
19
- // `wrapperComponentProvider && wrapperComponentProvider(...)` into renderApplication
20
- // (AppRegistryImpl.js:80) — Vue's equivalent of React's createElement(Wrapper, null,
21
- // element) is a default-slot render, since Vue has no standalone element value to nest.
22
- // Read live (not at registration time) so setWrapperComponentProvider affects every
23
- // runnable's next run, not just future ones.
16
+ // Wrap the root when a provider is set, mirroring RN's `registerComponent`. Vue's equivalent
17
+ // of React's createElement(Wrapper, null, element) is a default-slot render, since Vue has
18
+ // no standalone element value to nest. Read live so setWrapperComponentProvider affects
19
+ // every runnable's next run, not just future ones.
24
20
  const wrapperComponentProvider = getWrapperComponentProvider();
25
21
  const mounted = wrapperComponentProvider === undefined
26
22
  ? renderRoot
@@ -1,14 +1,11 @@
1
1
  // StatusBar, the Vue lifecycle half. The native StatusBarManager driving (applyStatusBarProps),
2
- // the imperative statics (statusBarImperative), and the Android bar-height constant all live in
3
- // @symbiote-native/engine, shared verbatim with React; Metro selects the engine's status-bar.ios.ts /
4
- // status-bar.android.ts per host, so the platform divergence never reaches this file. Vue supplies
5
- // only the declarative shape: a component that renders NOTHING and re-applies the props through a
6
- // watchEffect on mount + every prop change, with the imperative statics attached to the component
7
- // object (RN's StatusBar: the value doubles as the imperative namespace, like the Vue Image).
2
+ // the imperative statics, and the Android bar-height constant all live in @symbiote-native/engine,
3
+ // shared verbatim with React; Metro selects the engine's status-bar.ios.ts/.android.ts per host,
4
+ // so platform divergence never reaches this file. Vue supplies only the declarative shape: a
5
+ // component that renders NOTHING and re-applies the props through a watchEffect on mount + every
6
+ // prop change, with the imperative statics attached to the component object.
8
7
  //
9
- // Renders null (no Fabric view), so it follows neither the descriptor split nor a host-node ref:
10
- // the "else keep its current shape" path of the StatusBar component. Inputs arrive as attrs
11
- // (untyped) and run through normalizeVueAttrs (kebab→camel) before each is narrowed by a guard.
8
+ // Inputs arrive as attrs (untyped) and run through normalizeVueAttrs before each is narrowed by a guard.
12
9
  import { defineComponent, watchEffect } from '@vue/runtime-core';
13
10
  import { applyStatusBarProps, statusBarImperative, statusBarCurrentHeight, isOpaqueColorValue, } from '@symbiote-native/engine';
14
11
  import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
@@ -20,8 +17,6 @@ function asBarStyle(value) {
20
17
  ? value
21
18
  : undefined;
22
19
  }
23
- // backgroundColor is a string / int / opaque PlatformColor; any other shape is dropped. A runtime
24
- // guard at the untyped-attrs boundary, not a cast.
25
20
  function asColorValue(value) {
26
21
  if (typeof value === 'string')
27
22
  return value;
@@ -43,13 +38,11 @@ const StatusBarComponent = defineComponent({
43
38
  name: 'StatusBar',
44
39
  inheritAttrs: false,
45
40
  setup(_props, { attrs: rawAttrs }) {
46
- // watchEffect tracks rawAttrs (reactive), so reading it here re-applies on every prop change:
47
- // the Vue twin of React's useEffect over the prop deps. Resolution is lazy inside the engine
48
- // (a missing StatusBarManager is a no-op), so this never crashes a render.
41
+ // watchEffect tracks rawAttrs (reactive), so reading it here re-applies on every prop change.
42
+ // Resolution is lazy inside the engine (a missing StatusBarManager is a no-op).
49
43
  watchEffect(() => {
50
44
  applyStatusBarProps(buildProps(normalizeVueAttrs(rawAttrs)));
51
45
  });
52
- // Renders nothing: StatusBar has no Fabric view, exactly like React's `return null`.
53
46
  return () => null;
54
47
  },
55
48
  });
@@ -51,6 +51,13 @@ export function createSymbioteRenderer(surface) {
51
51
  setElementText(el, text) {
52
52
  if (isSurface(el))
53
53
  return;
54
+ // Same invariant insert() enforces, on the other route text can reach a node: Vue calls
55
+ // this instead of insert() when an element's children collapse to a single string, so
56
+ // without the check a raw text lands under a non-Text parent - an invalid Fabric tree
57
+ // built silently, which is worse than the throw insert() would have given.
58
+ if (!el.isText) {
59
+ throw new Error(`Text string "${text}" must be rendered inside a <Text>`);
60
+ }
54
61
  // An RCTText carries its string as a single raw-text child. Reuse a lone existing
55
62
  // one to avoid churn; otherwise replace all children with a fresh raw-text node.
56
63
  const [first] = el.children;
@@ -103,12 +110,10 @@ export function createSymbioteRenderer(surface) {
103
110
  patchProp(el, key, _prev, next) {
104
111
  if (isSurface(el))
105
112
  return;
106
- // routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on
107
- // a View becomes a listener; onTintColor on a Switch stays a prop): identical to
108
- // the React host config, so the whole event layer is shared. `class`/`style` merge
109
- // (explicit :style always winning over class-derived style, regardless of which of
110
- // Vue's two independent patchProp calls lands last) is centralized in routeProp too
111
- // (core/engine/src/node.ts) — every adapter gets it, not just this one.
113
+ // routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a
114
+ // View becomes a listener; onTintColor on a Switch stays a prop), shared with React. The
115
+ // class/style merge (explicit :style always winning, regardless of which of Vue's two
116
+ // independent patchProp calls lands last) is centralized there too (core/engine/src/node.ts).
112
117
  routeProp(el, key, next);
113
118
  surface.requestCommit();
114
119
  },
@@ -1,28 +1,21 @@
1
1
  // Metro rewrites every compiled `from 'vue'` import to point HERE instead of at bare
2
- // `@vue/runtime-core` (see examples/*/metro-vue-transformer.js). Most of the compiler's injected
3
- // helpers (ref, computed, withDirectives, openBlock, …) already live in runtime-core untouched —
4
- // re-exported below. But two Vue template directives compile to a runtime-helper import that
5
- // ONLY exists in `@vue/runtime-dom` (vShow, vModelText, vModelCheckbox, vModelSelect), written
6
- // directly against a real HTMLElement (el.style.display, el.value, addEventListener). We have no
7
- // DOM, so those don't exist for us — rewriting straight to runtime-core leaves `v-show` either
8
- // failing at import time or silently resolving to `undefined` (a no-op directive). This module is
9
- // the interception point: it supplies our OWN implementation under the same export name, so the
10
- // compiler's import keeps resolving and the directive actually does something.
2
+ // @vue/runtime-core (see examples/*/metro-vue-transformer.js). Most compiler-injected helpers
3
+ // (ref, computed, withDirectives, openBlock, ...) live in runtime-core untouched, re-exported
4
+ // below. But two Vue template directives compile to a runtime-helper import that ONLY exists in
5
+ // @vue/runtime-dom (vShow, vModelText, vModelCheckbox, vModelSelect), written directly against a
6
+ // real HTMLElement - we have no DOM, so this module supplies our OWN implementation under the
7
+ // same export name instead of leaving `v-show` a silent no-op.
11
8
  //
12
9
  // Scope: this covers only Vue's own template directives (v-show, Teleport); native-element
13
10
  // v-model is a separate, out-of-scope case.
14
11
  export * from '@vue/runtime-core';
15
12
  import { defineComponent, h, Teleport as VueTeleport, } from '@vue/runtime-core';
16
13
  import { isSymbioteNode, setNativeProps, whenCommitted, } from '@symbiote-native/engine';
17
- // setNativeProps merges a partial `style` object onto the node's current one (never clobbering
18
- // other declarative style fields) and re-commits via the node's OWN tracked rootTag — no
19
- // SymbioteSurface reference needed here, which a directive hook has no access to anyway.
20
- //
21
14
  // whenCommitted, not a direct call: Vue's `mounted` hook fires synchronously during the patch
22
15
  // pass, but this renderer coalesces the actual Fabric commit onto a microtask
23
- // (surface.requestCommit()), so the node may have no committed tag yet — the same async-commit
24
- // race TextInput's autoFocus guards against. A bare setNativeProps
25
- // here would silently no-op with no retry on the very first mount.
16
+ // (surface.requestCommit()), so the node may have no committed tag yet - the same async-commit
17
+ // race TextInput's autoFocus guards against. A bare setNativeProps here would silently no-op
18
+ // with no retry on the very first mount.
26
19
  const pendingShowCommits = new WeakMap();
27
20
  function applyShow(el, value) {
28
21
  pendingShowCommits.get(el)?.();
@@ -36,16 +29,13 @@ export const vShow = {
36
29
  unmounted: el => pendingShowCommits.get(el)?.(),
37
30
  };
38
31
  // Teleport's `to` in the DOM world is usually a CSS-selector string ('body', '#modal-root'); we
39
- // have no querySelector (renderer.ts stubs it to null, same as insertStaticContent), so `to` here
40
- // must be an already-mounted host node — e.g. a ref to a persistent "overlay host" View rendered
41
- // once near the app root — not a selector string. This wrapper shadows runtime-core's own
42
- // `Teleport` (which the SFC compiler imports from 'vue' like vShow) purely to validate `to` BEFORE
43
- // handing it to the real Teleport: a wrong value (string, plain object, an unrendered/reactive-
44
- // wrapped node) throws immediately here instead of silently corrupting the retained tree deep
45
- // inside insert/remove — the "don't let a dev accidentally break everything" case this exists for.
46
- // Scope: same-surface targets only (see the React createPortal twin, create-portal.ts) — Vue's own
47
- // Teleport internals (disabled toggling, move-on-update) work unmodified once `to` resolves to a
48
- // real ISymbioteNode, since insert/remove/parentNode/nextSibling are already fully generic.
32
+ // have no querySelector, so `to` here must be an already-mounted host node - e.g. a ref to a
33
+ // persistent "overlay host" View rendered once near the app root. This wrapper shadows
34
+ // runtime-core's own Teleport purely to validate `to` BEFORE handing it to the real Teleport: a
35
+ // wrong value throws immediately here instead of silently corrupting the retained tree deep
36
+ // inside insert/remove. Scope: same-surface targets only (see the React createPortal twin,
37
+ // create-portal.ts) - Vue's own Teleport internals work unmodified once `to` resolves to a real
38
+ // ISymbioteNode.
49
39
  export const Teleport = defineComponent({
50
40
  name: 'Teleport',
51
41
  inheritAttrs: false,
@@ -1,17 +1,12 @@
1
1
  // Re-exports @symbiote-native/css-parser verbatim from INSIDE this package, so a consuming app's own
2
2
  // require('@symbiote-native/vue/metro-css-parser') resolves css-parser relative to THIS file's
3
- // location (adapters/vue/node_modules, where css-parser IS a real dependency — see
4
- // package.json), not relative to the app's own node_modules, which does not and should not
5
- // need to declare @symbiote-native/css-parser itself. Node resolves each require() relative to the
6
- // requiring FILE's own location, so this indirection is what actually removes the extra install
7
- // step, not pnpm hoisting (which does not propagate this transitively across workspace packages
8
- // the way a flat classic node_modules would). .cjs, not .js: this package is "type": "module",
9
- // and Metro's babelTransformerPath loading expects a require()-able module. Used as
10
- // vue-tsx's babelTransformerPath for standalone .css/.module.css file imports —
11
- // metro-vue-transformer.cjs (the .vue SFC transformer) requires @symbiote-native/css-parser
12
- // directly instead, since it needs the individual parse/compile functions, not a Metro
13
- // transformer object.
14
- // createCssMetroTransformer is a factory, not a ready transformer — Metro's
15
- // babelTransformerPath needs the actual {transform, getCacheKey} object it returns,
16
- // not the css-parser package barrel.
3
+ // location (adapters/vue/node_modules, where css-parser IS a real dependency), not relative to
4
+ // the app's own node_modules — Node resolves each require() relative to the requiring FILE, so
5
+ // this indirection is what removes the app's extra install step, not pnpm hoisting. .cjs, not
6
+ // .js: this package is "type": "module", and Metro's babelTransformerPath loading expects a
7
+ // require()-able module. Used as vue-tsx's babelTransformerPath for standalone .css/.module.css
8
+ // imports; metro-vue-transformer.cjs (the .vue SFC transformer) requires @symbiote-native/css-parser
9
+ // directly instead, since it needs the individual parse/compile functions, not a Metro transformer
10
+ // object. createCssMetroTransformer is a factory — babelTransformerPath needs the actual
11
+ // {transform, getCacheKey} object it returns, not the css-parser package barrel.
17
12
  module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();