@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.
- package/build/components/flat-list/index.js +18 -34
- package/build/components/image-background.js +4 -14
- package/build/components/image.js +9 -20
- package/build/components/keyboard-avoiding-view.js +10 -19
- package/build/components/modal/index.d.ts +4 -4
- package/build/components/pressable.js +24 -31
- package/build/components/refresh-control.js +12 -18
- package/build/components/scroll-view/index.android.js +21 -33
- package/build/components/scroll-view/shared.js +60 -110
- package/build/components/scroll-view/sticky-header.js +41 -59
- package/build/components/section-list/index.js +12 -19
- package/build/components/switch/shared.js +17 -29
- package/build/components/text-input/index.js +29 -49
- package/build/components/virtualized-list/index.js +20 -3
- package/build/components/virtualized-section-list/index.js +17 -25
- package/build/components.js +1 -3
- package/build/host-instance/index.js +2 -4
- package/build/index.d.ts +2 -1
- package/build/index.js +11 -45
- package/build/modules/animated/create-animated-component.js +34 -97
- package/build/modules/animated/index.js +13 -17
- package/build/modules/app-registry/index.js +7 -11
- package/build/modules/status-bar.js +8 -15
- package/build/renderer/index.js +11 -6
- package/build/runtime-helpers/index.js +16 -26
- package/metro-css-parser.cjs +9 -14
- package/metro-vue-transformer.cjs +112 -135
- package/package.json +4 -4
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
// createAnimatedComponent: the Vue twin of the React wrapper.
|
|
2
|
-
// (View
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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 {
|
|
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
|
-
//
|
|
21
|
-
//
|
|
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
|
-
//
|
|
41
|
-
//
|
|
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
|
-
//
|
|
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
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// The
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
//
|
|
118
|
-
//
|
|
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
|
|
137
|
-
//
|
|
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
|
|
89
|
+
// headless/unsupported hosts keep the JS flush path.
|
|
150
90
|
wantsNative = passthrough != null && isNativeAnimatedAvailable();
|
|
151
|
-
//
|
|
152
|
-
//
|
|
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
|
|
158
|
-
// array
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
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
|
|
23
|
-
//
|
|
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
|
|
52
|
-
// final value synchronously, no frames. The animated COMPONENTS
|
|
53
|
-
//
|
|
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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
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
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
});
|
package/build/renderer/index.js
CHANGED
|
@@ -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
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
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
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
24
|
-
// race TextInput's autoFocus guards against. A bare setNativeProps
|
|
25
|
-
//
|
|
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
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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,
|
package/metro-css-parser.cjs
CHANGED
|
@@ -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
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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();
|