@symbiote-native/vue 0.3.1 → 0.3.3
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/README.md +3 -3
- package/build/components/activity-indicator/index.android.js +1 -1
- package/build/components/activity-indicator/index.ios.js +1 -1
- package/build/components/activity-indicator/index.js +1 -1
- package/build/components/refresh-control.js +1 -1
- package/build/components/safe-area-view.js +1 -1
- package/build/components/scroll-view/index.android.js +1 -1
- package/build/components/scroll-view/index.ios.js +1 -1
- package/build/components/scroll-view/index.js +1 -1
- package/build/components/scroll-view/shared.js +8 -9
- package/build/components/scroll-view/sticky-header.js +2 -2
- package/build/components/switch/index.android.js +1 -1
- package/build/components/switch/index.ios.js +1 -1
- package/build/components/switch/index.js +1 -1
- package/build/components/text-input/index.js +1 -1
- package/build/components/virtualized-list/index.js +7 -8
- package/build/create-tunnel.js +4 -3
- package/build/host-instance/index.js +1 -2
- package/build/index.js +9 -9
- package/build/modules/animated/create-animated-component.js +2 -2
- package/build/modules/animated/index.js +2 -2
- package/build/renderer.js +4 -5
- package/build/runtime-helpers.js +3 -3
- package/build/utils/component-helpers.js +3 -2
- package/build/utils/model-binding.js +1 -2
- package/build/utils/slots-to-render-props.js +2 -2
- package/metro-css-parser.cjs +1 -1
- package/metro-vue-transformer.cjs +12 -16
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Vue, on the *same* untouched core as React, with React Native's own renderer nev
|
|
|
5
5
|
is a `@vue/runtime-core` `createRenderer` whose nodeOps map each mutation onto the engine's
|
|
6
6
|
four-call API; `@symbiote-native/engine` does the clone-on-write commit into Fabric.
|
|
7
7
|
|
|
8
|
-
Vue is the **proof the core is genuinely framework-agnostic
|
|
8
|
+
Vue is the **proof the core is genuinely framework-agnostic**: a second, non-React,
|
|
9
9
|
mutation-oriented framework driving the already-validated engine.
|
|
10
10
|
|
|
11
11
|
<div align="center">
|
|
@@ -114,8 +114,8 @@ React doesn't hit this because `react-reconciler` commits synchronously.
|
|
|
114
114
|
|
|
115
115
|
The fix lives in the engine: `whenCommitted(node, action)` runs `action` now if the node already
|
|
116
116
|
has a tag, else after the commit that assigns it. Any native/imperative call wired at Vue lifecycle
|
|
117
|
-
time must go through it. This is the only place the Vue adapter's timing differs from React's
|
|
118
|
-
|
|
117
|
+
time must go through it. This is the only place the Vue adapter's timing differs from React's; the
|
|
118
|
+
implementation is in `core/engine/src/post-commit.ts`.
|
|
119
119
|
|
|
120
120
|
---
|
|
121
121
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ActivityIndicator on Android: AndroidProgressBar needs `styleAttr` (it drives the
|
|
2
2
|
// ProgressBar's setStyle(); without it the view throws "setStyle() not called") and
|
|
3
3
|
// `indeterminate: true`, and its default color is the theme (null). Metro picks this on an
|
|
4
|
-
// Android host; no Platform.OS read. Mirrors the React adapter's Android binding.
|
|
4
|
+
// Android host; no Platform.OS read. Mirrors the React adapter's Android binding.
|
|
5
5
|
import { createActivityIndicator } from './shared.js';
|
|
6
6
|
export const ActivityIndicator = createActivityIndicator({
|
|
7
7
|
// RN: `color = Platform.OS === 'ios' ? GRAY : null`. Android lets the theme color it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ActivityIndicator on iOS: RCTActivityIndicatorView takes the size enum + a GRAY default
|
|
2
2
|
// color and no extra native props. Also the base (activity-indicator.ts re-exports it) for
|
|
3
|
-
// headless / web. Mirrors the React adapter's iOS binding.
|
|
3
|
+
// headless / web. Mirrors the React adapter's iOS binding.
|
|
4
4
|
import { createActivityIndicator } from './shared.js';
|
|
5
5
|
// RN's iOS default spinner color (Libraries/.../ActivityIndicator.js GRAY).
|
|
6
6
|
const IOS_DEFAULT_COLOR = '#999999';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Base / default ActivityIndicator. Re-exports the iOS build. Metro overrides this with
|
|
2
2
|
// activity-indicator.ios.ts / .android.ts on a real host; under tsx / tsc / web the host
|
|
3
|
-
// resolves here. Filename is the selector, no Platform.OS read.
|
|
3
|
+
// resolves here. Filename is the selector, no Platform.OS read.
|
|
4
4
|
export * from './index.ios.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// RefreshControl, the Vue lifecycle half
|
|
1
|
+
// RefreshControl, the Vue lifecycle half. On iOS this is the PullToRefreshView
|
|
2
2
|
// Fabric node that lives INSIDE a ScrollView (a sibling before the content container); on Android it
|
|
3
3
|
// is AndroidSwipeRefreshLayout and WRAPS the scroll view (a ScrollView there hosts one child). The
|
|
4
4
|
// Vue twin of the React adapter's RefreshControl. Vue takes the wrapped child via its DEFAULT SLOT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SafeAreaView, the Vue lifecycle half
|
|
1
|
+
// SafeAreaView, the Vue lifecycle half. A plain view whose native side
|
|
2
2
|
// insets its children to the safe area (notch, rounded corners, system bars); there is no
|
|
3
3
|
// JS-side translation, so this maps style + children straight onto the intrinsic. The Vue twin
|
|
4
4
|
// of the React adapter's SafeAreaView. Vue takes children via slots, folds aria/role through
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ScrollView on Android
|
|
1
|
+
// ScrollView on Android. An Android ScrollView accepts only ONE child, so a
|
|
2
2
|
// RefreshControl can't be a sibling of the content the way iOS allows ("addViewAt: failed to insert
|
|
3
3
|
// view ... at index 1"). Instead the RefreshControl (AndroidSwipeRefreshLayout) WRAPS the scroll
|
|
4
4
|
// view, with the scroll view nested inside and nestedScrollEnabled so the inner scroll handles the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ScrollView on iOS
|
|
1
|
+
// ScrollView on iOS. The RefreshControl is a CHILD of the scroll view, rendered
|
|
2
2
|
// as a SIBLING BEFORE the content container (RN ScrollView.js: {refreshControl}{contentContainer}).
|
|
3
3
|
// The user's RefreshControl VNode renders as-is; its default slot is empty on iOS, so it is a
|
|
4
4
|
// childless sibling. Also the base (scroll-view.ts re-exports it) for headless / web. Metro picks
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Base / default ScrollView. Re-exports the iOS build. Metro overrides this with
|
|
2
2
|
// scroll-view.ios.ts / scroll-view.android.ts on a real host; under tsx / tsc / web the host
|
|
3
|
-
// resolves here. Filename is the selector, no Platform.OS read.
|
|
3
|
+
// resolves here. Filename is the selector, no Platform.OS read.
|
|
4
4
|
export * from './index.ios.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ScrollView, the Vue lifecycle half
|
|
1
|
+
// ScrollView, the Vue lifecycle half. The Fabric tree is nested:
|
|
2
2
|
// a scroll view wraps a content view that holds the children (RN's ScrollView.js shape). The
|
|
3
3
|
// platform-invariant math (decelerationRate, the per-axis intrinsics/base style, the
|
|
4
4
|
// content-size dedupe, the imperative handle, the aria/role fold) lives in @symbiote-native/components,
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// (onScroll/onLayout/…) ARE ViewConfig events, so they forward raw and routeProp turns them into
|
|
14
14
|
// listeners.
|
|
15
15
|
//
|
|
16
|
-
//
|
|
17
|
-
// Android wrap).
|
|
16
|
+
// RefreshControl is wired through the platform assemble (iOS sibling /
|
|
17
|
+
// Android wrap). Sticky headers are real. The scroll AnimatedValue (markRaw, held by
|
|
18
18
|
// identity), the headerLayoutYs cross-talk map + bump, the viewport-height capture, and the
|
|
19
19
|
// onScroll composition (native attach vs Animated.event) all live here; the per-header component and
|
|
20
20
|
// the children wrap live in scroll-view-sticky-header.ts (the Vue twin of the React file).
|
|
@@ -37,8 +37,7 @@ function isStyleProp(value) {
|
|
|
37
37
|
// the renderer's own patchProp — fine for the single-node Phase 1 path. But the Android
|
|
38
38
|
// RefreshControl wrap (index.android.ts) reads userStyle alone to splitLayoutProps() the outer
|
|
39
39
|
// wrapper's layout style, BEFORE that later resolution ever runs, so a class-only layout prop
|
|
40
|
-
// (flex, height, gap, …) never reaches the wrapper and it collapses to nothing.
|
|
41
|
-
// symbiote-sfc-style-compiler skill for the failure this caused on a real Android device.
|
|
40
|
+
// (flex, height, gap, …) never reaches the wrapper and it collapses to nothing.
|
|
42
41
|
// isClassNameProp is @symbiote-native/engine's own isClassNameValue guard (shared, not redeclared —
|
|
43
42
|
// routeProp's centralized class+style merge needs the identical narrowing).
|
|
44
43
|
const isClassNameProp = isClassNameValue;
|
|
@@ -90,15 +89,15 @@ export function createScrollView(platform) {
|
|
|
90
89
|
// shallowRef, NOT ref: the engine node must be held by IDENTITY. A plain ref() runs the
|
|
91
90
|
// node through Vue's toReactive(), handing back a reactive Proxy, a different object than
|
|
92
91
|
// the raw node the engine's mirror (a WeakMap) is keyed on, so dispatchViewCommand would
|
|
93
|
-
// miss and every scrollTo/scrollToEnd/flashScrollIndicators silently no-op.
|
|
94
|
-
//
|
|
92
|
+
// miss and every scrollTo/scrollToEnd/flashScrollIndicators silently no-op. This is the
|
|
93
|
+
// same rule as the Switch host node.
|
|
95
94
|
const nodeRef = shallowRef(null);
|
|
96
95
|
const setNodeRef = (el) => {
|
|
97
96
|
nodeRef.value = isSymbioteNode(el) ? el : null;
|
|
98
97
|
};
|
|
99
98
|
// The imperative handle reads the node through a LAZY getter (() => nodeRef.value), not the
|
|
100
99
|
// node captured once: it is null until the element commits, so an eager capture would freeze
|
|
101
|
-
// null and every command would no-op
|
|
100
|
+
// null and every command would no-op. expose() makes it the value a parent ref
|
|
102
101
|
// sees: the Vue twin of React's useImperativeHandle(forwardedRef, buildScrollViewHandle(…)).
|
|
103
102
|
expose(buildScrollViewHandle(() => nodeRef.value));
|
|
104
103
|
// The last-seen content size, kept here (setup scope, persists across renders) to dedupe
|
|
@@ -169,7 +168,7 @@ export function createScrollView(platform) {
|
|
|
169
168
|
: isStyleProp(attrs.contentContainerStyle)
|
|
170
169
|
? attrs.contentContainerStyle
|
|
171
170
|
: undefined;
|
|
172
|
-
// Sticky headers
|
|
171
|
+
// Sticky headers are a pure-JS layer; the native scroll view ignores
|
|
173
172
|
// stickyHeaderIndices, so we wrap the flagged children below and drive their translateY off
|
|
174
173
|
// the scroll offset. invertStickyHeaders narrows to the inverted (stick-to-bottom) branch.
|
|
175
174
|
const stickyHeaderIndices = isNumberArray(attrs.stickyHeaderIndices)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// `stickyHeaderIndices` to native is a silent no-op. So we replicate the JS layer: subscribe each
|
|
9
9
|
// flagged child to the scroll offset and translate it to stay pinned. The interpolation mirrors
|
|
10
10
|
// ScrollViewStickyHeader.js (non-inverted + inverted branches) and lives, framework-agnostic, in
|
|
11
|
-
// @symbiote-native/components (computeStickyInterpolation
|
|
11
|
+
// @symbiote-native/components (computeStickyInterpolation); this file holds the Vue component
|
|
12
12
|
// shell, the layout state, and the child-wrapping. Render shared verbatim with React via the math.
|
|
13
13
|
// Vue supplies only the reactive lifecycle (refs/watch instead of useState/useEffect).
|
|
14
14
|
import { defineComponent, h, isVNode, ref, shallowRef, watch, watchEffect, markRaw, } from '@vue/runtime-core';
|
|
@@ -55,7 +55,7 @@ export const ScrollViewStickyHeader = defineComponent({
|
|
|
55
55
|
// The animated node that drives the transform (RN's animatedTranslateY). When the scroll value
|
|
56
56
|
// is native (attachStickyScroll), this interpolation runs on the UI thread: the smooth pin.
|
|
57
57
|
// Engine node → shallowRef (held by identity, the reactivity rule); the un-measured identity
|
|
58
|
-
// stub until the effect below rebuilds it.
|
|
58
|
+
// stub until the effect below rebuilds it.
|
|
59
59
|
const animatedTranslateY = shallowRef(scrollAnimatedValue.interpolate({ inputRange: [-1, 0], outputRange: [0, 0] }));
|
|
60
60
|
// The debounced EXPLICIT translateY pushed to the committed transform via
|
|
61
61
|
// passthroughAnimatedPropExplicitValues, so the Fabric ShadowTree (hit-testing) knows the pinned
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Switch on Android: AndroidSwitch takes trackColorForTrue / trackColorForFalse plus
|
|
2
2
|
// trackTintColor (the color for the CURRENT value), and snaps native back via the
|
|
3
3
|
// `setNativeValue` command. Metro picks this on an Android host; no Platform.OS read.
|
|
4
|
-
// Mirrors the React adapter's Android binding.
|
|
4
|
+
// Mirrors the React adapter's Android binding.
|
|
5
5
|
import { createSwitch } from './shared.js';
|
|
6
6
|
export const Switch = createSwitch({
|
|
7
7
|
snapBackCommand: 'setNativeValue',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Switch on iOS: the native Switch takes onTintColor (ON-track) / tintColor (OFF-track) and
|
|
2
2
|
// snaps native back via the `setValue` command. Also the base (switch.ts re-exports it) for
|
|
3
|
-
// headless / web. Mirrors the React adapter's iOS binding.
|
|
3
|
+
// headless / web. Mirrors the React adapter's iOS binding.
|
|
4
4
|
import { createSwitch } from './shared.js';
|
|
5
5
|
export const Switch = createSwitch({
|
|
6
6
|
snapBackCommand: 'setValue',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Base / default Switch. Re-exports the iOS build. Metro overrides this with switch.ios.ts /
|
|
2
2
|
// switch.android.ts on a real host; under tsx / tsc / web the host resolves here. Filename is
|
|
3
|
-
// the selector, no Platform.OS read.
|
|
3
|
+
// the selector, no Platform.OS read. Switch is the first component to bring the
|
|
4
4
|
// state half (the reducer) into the Vue adapter: render-and-state, not render-only.
|
|
5
5
|
export * from './index.ios.js';
|
|
@@ -84,7 +84,7 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
|
|
|
84
84
|
// through Vue's toReactive(), handing back a reactive Proxy, a different object than the raw
|
|
85
85
|
// node the engine's WeakMap mirror is keyed on, so every imperative command
|
|
86
86
|
// (dispatchViewCommand / focus / blur / setTextAndSelection) would miss and silently no-op.
|
|
87
|
-
//
|
|
87
|
+
// Same rule applies to the Switch / ScrollView host node.
|
|
88
88
|
const nodeRef = shallowRef(null);
|
|
89
89
|
const setNodeRef = (el) => {
|
|
90
90
|
nodeRef.value = isSymbioteNode(el) ? el : null;
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
// bridge is wired ONLY when the consumer actually listens (read off the instance vnode props),
|
|
19
19
|
// keeping behavior identical to the prop-callback era.
|
|
20
20
|
//
|
|
21
|
-
// Lists have no Descriptor render fn (the cell content is the framework's own children)
|
|
22
|
-
//
|
|
21
|
+
// Lists have no Descriptor render fn (the cell content is the framework's own children).
|
|
22
|
+
// Cells/spacers are built with h() directly off the plan.
|
|
23
23
|
//
|
|
24
|
-
// Reactivity gotcha
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// Reactivity gotcha: the ScrollView's exposed handle is held in a shallowRef so the engine
|
|
25
|
+
// node it closes over is reached by identity; a deep ref would proxy it and the imperative
|
|
26
|
+
// scroll commands would silently no-op.
|
|
27
27
|
import { computed, defineComponent, getCurrentInstance, h, isVNode, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
|
|
28
28
|
import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, NO_CONTENT_LENGTH_SENT, averageMeasuredLength, buildListPlan, buildOffsets, buildViewabilityPairs, computeEndReached, computeStartReached, computeViewableSet, computeWindow, diffViewable, highestMeasuredIndex, maxMinimumViewTime, offsetForIndex, readLayoutLength, readScrollOffset, throttleWindow, } from '@symbiote-native/components';
|
|
29
29
|
import { dlog, } from '@symbiote-native/engine';
|
|
30
|
-
import { ScrollView } from '../scroll-view
|
|
30
|
+
import { ScrollView } from '../scroll-view';
|
|
31
31
|
import { RefreshControl } from '../refresh-control.js';
|
|
32
32
|
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
33
33
|
import { componentFromSlot } from '../../utils/slots-to-render-props.js';
|
|
@@ -133,8 +133,7 @@ export const VirtualizedList = defineComponent((props, { attrs, expose, emit, sl
|
|
|
133
133
|
const separatorVersion = ref(EMPTY_OFFSET);
|
|
134
134
|
// shallowRef, NOT ref: the ScrollView handle closes over the engine scroll node, reached by
|
|
135
135
|
// identity through the engine's WeakMap mirror. A deep ref would proxy the object and every
|
|
136
|
-
// imperative scroll (scrollToOffset/Index/…) would miss the node and silently no-op.
|
|
137
|
-
// .claude/skills/vue-adapter-reactivity.
|
|
136
|
+
// imperative scroll (scrollToOffset/Index/…) would miss the node and silently no-op.
|
|
138
137
|
const scrollHandle = shallowRef(null);
|
|
139
138
|
const setScrollHandle = (instance) => {
|
|
140
139
|
scrollHandle.value = isScrollViewHandle(instance) ? instance : null;
|
package/build/create-tunnel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// createTunnel — the Vue twin of the React adapter's create-tunnel.ts
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// createTunnel — the Vue twin of the React adapter's create-tunnel.ts. Teleport only moves
|
|
2
|
+
// content within a single surface's own tree, so sharing content ACROSS two independently
|
|
3
|
+
// mounted surfaces needs a different mechanism: a plain shared reactive registry instead of
|
|
4
|
+
// a Fabric-tree relocation. `Out` lives in whichever component should
|
|
4
5
|
// PAINT the content; its own reactive read makes THAT surface's normal render/commit pick it
|
|
5
6
|
// up — no cross-surface reach-in, no rootTag lookup, works whether In and Out share a
|
|
6
7
|
// surface or not.
|
|
@@ -21,8 +21,7 @@ export function findNodeHandle(componentOrHandle) {
|
|
|
21
21
|
if (typeof candidate === 'number')
|
|
22
22
|
return candidate;
|
|
23
23
|
// A plain ref() would have wrapped the node in a reactive Proxy; the engine mirror is
|
|
24
|
-
// keyed on the RAW node, so recover it before the lookup or getNativeTag misses.
|
|
25
|
-
// the vue-adapter-reactivity skill.
|
|
24
|
+
// keyed on the RAW node, so recover it before the lookup or getNativeTag misses.
|
|
26
25
|
const node = toRaw(candidate);
|
|
27
26
|
if (isSymbioteNode(node)) {
|
|
28
27
|
const tag = getNativeTag(node) ?? null;
|
package/build/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// lives in the engine, shared with every other adapter. App code names only @symbiote-native/vue.
|
|
4
4
|
export { mount, unmount } from './render.js';
|
|
5
5
|
// createTunnel: cross-surface content sharing (Teleport stays same-surface-only by design —
|
|
6
|
-
// see create-tunnel.ts
|
|
6
|
+
// see create-tunnel.ts for why).
|
|
7
7
|
export { createTunnel } from './create-tunnel.js';
|
|
8
8
|
export { View, Text } from './components.js';
|
|
9
9
|
// findNodeHandle: RN's ref -> native reactTag lookup, the Vue twin of the React adapter's
|
|
@@ -17,17 +17,17 @@ export { Image, setImageSourceResolver } from './components/image.js';
|
|
|
17
17
|
// First component off the shared @symbiote-native/components layer: render-only, drives the
|
|
18
18
|
// agnostic renderActivityIndicator through descriptorToVue. Proof the layer is reusable:
|
|
19
19
|
// the render fn is shared verbatim with React; Vue supplies only the bridge.
|
|
20
|
-
export { ActivityIndicator } from './components/activity-indicator
|
|
20
|
+
export { ActivityIndicator } from './components/activity-indicator';
|
|
21
21
|
// First component to bring the state half into Vue (the lastNativeReport reducer + the
|
|
22
22
|
// snap-back watch); render shared verbatim with React, Vue supplies the reactive lifecycle.
|
|
23
|
-
export { Switch } from './components/switch
|
|
24
|
-
// ScrollView. Full parity
|
|
23
|
+
export { Switch } from './components/switch';
|
|
24
|
+
// ScrollView. Full parity: vertical + horizontal, every pass-through prop, the
|
|
25
25
|
// synthesized onContentSizeChange, the imperative handle via expose() + shallowRef, RefreshControl
|
|
26
|
-
// (iOS sibling / Android wrap
|
|
26
|
+
// (iOS sibling / Android wrap), and sticky headers (the scroll AnimatedValue,
|
|
27
27
|
// the headerLayoutYs cross-talk, and the per-header Animated.View wrap). The pure math (intrinsics,
|
|
28
28
|
// decelerationRate, content-size dedupe, the handle, the sticky interpolation) is shared verbatim
|
|
29
29
|
// with React from @symbiote-native/components.
|
|
30
|
-
export { ScrollView } from './components/scroll-view
|
|
30
|
+
export { ScrollView } from './components/scroll-view';
|
|
31
31
|
// Pressable family. Full parity with React (the 3-layer split): the press state machine + render
|
|
32
32
|
// decisions are shared in @symbiote-native/components; Vue supplies the reactivity + descriptor bridge.
|
|
33
33
|
export { Pressable } from './components/pressable.js';
|
|
@@ -45,7 +45,7 @@ export { VirtualizedList } from './components/virtualized-list/index.js';
|
|
|
45
45
|
export { FlatList } from './components/flat-list/index.js';
|
|
46
46
|
export { VirtualizedSectionList } from './components/virtualized-section-list/index.js';
|
|
47
47
|
export { SectionList } from './components/section-list/index.js';
|
|
48
|
-
//
|
|
48
|
+
// SafeAreaView + RefreshControl, wired into ScrollView with the iOS-sibling /
|
|
49
49
|
// Android-wrap platform split. RefreshControl hosts the wrapped scroll view via its default slot.
|
|
50
50
|
export { SafeAreaView } from './components/safe-area-view.js';
|
|
51
51
|
export { RefreshControl } from './components/refresh-control.js';
|
|
@@ -54,12 +54,12 @@ export { descriptorToVue } from './descriptor-to-vue.js';
|
|
|
54
54
|
// external wrapper package (e.g. @symbiote-native/slider/vue over a third-party native view) can fold
|
|
55
55
|
// its incoming attrs through the SAME transform rather than reimplementing it.
|
|
56
56
|
export { normalizeVueAttrs } from './utils/normalize-attrs.js';
|
|
57
|
-
// resolveModelValue/emitModelUpdate: the v-model
|
|
57
|
+
// resolveModelValue/emitModelUpdate: the v-model helper every
|
|
58
58
|
// controlled-value component uses. Exported for the same external-package reason as
|
|
59
59
|
// normalizeVueAttrs above (e.g. @symbiote-native/slider/vue).
|
|
60
60
|
export { resolveModelValue, emitModelUpdate } from './utils/model-binding.js';
|
|
61
61
|
export { createSymbioteRenderer } from './renderer.js';
|
|
62
|
-
// Animated
|
|
62
|
+
// Animated: Animated.View/Text/Image + the lazy Animated.ScrollView over the
|
|
63
63
|
// Vue primitives, with the value graph / easing / drivers spread from @symbiote-native/engine. The wrap
|
|
64
64
|
// mechanism (createAnimatedComponent) is the Vue twin of React's; the pure leaves live in the
|
|
65
65
|
// engine.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// createAnimatedComponent: the Vue twin of the React wrapper. It wraps a base component
|
|
2
2
|
// (View / Text / Image / ScrollView / any) so it accepts AnimatedNodes in its props.
|
|
3
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
|
|
4
|
+
// setNativeProps drives from the AnimatedProps leaf. The framework-agnostic
|
|
5
5
|
// pieces (reduceProps / readPassthroughStyle / resolveHostNode / isAnimatedNode + the
|
|
6
6
|
// AnimatedProps leaf itself) live in @symbiote-native/engine, shared verbatim with React; here
|
|
7
7
|
// Vue supplies only the lifecycle.
|
|
@@ -40,7 +40,7 @@ export function createAnimatedComponent(Component) {
|
|
|
40
40
|
// The committed host node, held by IDENTITY in a shallowRef (the reactivity rule: a deep
|
|
41
41
|
// ref() would run it through toReactive() and hand back a Proxy, so AnimatedProps.setNativeView
|
|
42
42
|
// / attachNativeEventHandler would miss the engine's WeakMap mirror, keyed on the raw node).
|
|
43
|
-
//
|
|
43
|
+
// Same rule as Switch / ScrollView host nodes.
|
|
44
44
|
const nodeRef = shallowRef(null);
|
|
45
45
|
// The base component's public instance (a ScrollView handle, or the host node for View).
|
|
46
46
|
// Forwarded to a parent ref via expose(). shallowRef: it may itself BE an engine node.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// The Animated namespace for @symbiote-native/vue: the Vue twin of adapters/react/src/animated/index.ts.
|
|
2
2
|
// createAnimatedComponent applied to the Vue primitives gives Animated.View / Text / Image; the
|
|
3
3
|
// value graph, easing and imperative drivers come from @symbiote-native/engine (framework-agnostic,
|
|
4
|
-
// JS-driven
|
|
4
|
+
// JS-driven), spread in verbatim. Both halves meet here so the familiar surface,
|
|
5
5
|
// `Animated.timing(new Animated.Value(0), …).start()`, works against the 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';
|
|
9
|
-
import { ScrollView } from '../../components/scroll-view
|
|
9
|
+
import { ScrollView } from '../../components/scroll-view';
|
|
10
10
|
import { createAnimatedComponent } from './create-animated-component.js';
|
|
11
11
|
export { createAnimatedComponent } from './create-animated-component.js';
|
|
12
12
|
// View/Text are pure host primitives; Image is the functional renderImage wrapper, and all expose
|
package/build/renderer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A Vue 3 custom renderer over @symbiote-native/engine. Each RendererOptions method maps onto
|
|
2
2
|
// the engine's tiny mutation API; the engine owns all Fabric clone-on-write, so Vue
|
|
3
3
|
// drives the exact same retained tree React does: the proof the core is framework-
|
|
4
|
-
// agnostic
|
|
4
|
+
// agnostic.
|
|
5
5
|
import { createRenderer } from '@vue/runtime-core';
|
|
6
6
|
import { appendChild, createAnchor, createElement, createRawText, dlog, insertBefore, removeChild, routeProp, setText, toPublicInstance, RAW_TEXT_COMPONENT, SymbioteSurface, } from '@symbiote-native/engine';
|
|
7
7
|
import { descriptorFor } from '@symbiote-native/components';
|
|
@@ -24,8 +24,7 @@ export function createSymbioteRenderer(surface) {
|
|
|
24
24
|
// the raw node so a template/function ref to a host element exposes it exactly like
|
|
25
25
|
// React's getPublicInstance. toPublicInstance mutates in place and returns the SAME node
|
|
26
26
|
// identity, so the engine commit mirror (keyed on the raw node) still resolves it — the
|
|
27
|
-
// ref must keep holding this raw node by identity (shallowRef), never a deep ref.
|
|
28
|
-
// vue-adapter-reactivity skill.
|
|
27
|
+
// ref must keep holding this raw node by identity (shallowRef), never a deep ref.
|
|
29
28
|
dlog(`vue createElement ${descriptor.component} -> public instance`);
|
|
30
29
|
return toPublicInstance(node);
|
|
31
30
|
},
|
|
@@ -40,8 +39,8 @@ export function createSymbioteRenderer(surface) {
|
|
|
40
39
|
},
|
|
41
40
|
// Fragment / v-if / v-for placeholder. A real retained node so insert/nextSibling/
|
|
42
41
|
// parentNode ordering stays correct, but the engine's commit walk skips it: no
|
|
43
|
-
// native view is ever created. (
|
|
44
|
-
//
|
|
42
|
+
// native view is ever created. (A comment can't just be an empty text node here —
|
|
43
|
+
// an empty RCTRawText would actually paint, so an anchor is the right call.)
|
|
45
44
|
createComment() {
|
|
46
45
|
return createAnchor();
|
|
47
46
|
},
|
package/build/runtime-helpers.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// the interception point: it supplies our OWN implementation under the same export name, so the
|
|
10
10
|
// compiler's import keeps resolving and the directive actually does something.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
12
|
+
// Scope: this covers only Vue's own template directives (v-show, Teleport); native-element
|
|
13
|
+
// v-model is a separate, out-of-scope case.
|
|
14
14
|
export * from '@vue/runtime-core';
|
|
15
15
|
import { defineComponent, h, Teleport as VueTeleport, } from '@vue/runtime-core';
|
|
16
16
|
import { isSymbioteNode, setNativeProps, whenCommitted, } from '@symbiote-native/engine';
|
|
@@ -21,7 +21,7 @@ import { isSymbioteNode, setNativeProps, whenCommitted, } from '@symbiote-native
|
|
|
21
21
|
// whenCommitted, not a direct call: Vue's `mounted` hook fires synchronously during the patch
|
|
22
22
|
// pass, but this renderer coalesces the actual Fabric commit onto a microtask
|
|
23
23
|
// (surface.requestCommit()), so the node may have no committed tag yet — the same async-commit
|
|
24
|
-
// race TextInput's autoFocus guards against
|
|
24
|
+
// race TextInput's autoFocus guards against. A bare setNativeProps
|
|
25
25
|
// here would silently no-op with no retry on the very first mount.
|
|
26
26
|
const pendingShowCommits = new WeakMap();
|
|
27
27
|
function applyShow(el, value) {
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Vue's `defineComponent` has no overload for a GENERIC setup function with separate runtime
|
|
4
4
|
// options, so a component generic over an item type (the list family) cannot type its emit payloads
|
|
5
|
-
// off that generic the normal way. The escape hatch
|
|
5
|
+
// off that generic the normal way. The escape hatch: a
|
|
6
6
|
// generic setup FUNCTION `<T,>(props: IProps<T>, ctx: ICtx<IEmits<T>>) => render`, plus the runtime
|
|
7
7
|
// options passed as a second arg cast through `as unknown as undefined` (the cast is sanctioned ONLY
|
|
8
|
-
// here
|
|
8
|
+
// here, as an explicit exception to the project's no-`as`-cast rule — it is the only way to express
|
|
9
|
+
// a generic component in plain TS).
|
|
9
10
|
//
|
|
10
11
|
// `ICtx` is a thin wrapper over Vue's SetupContext that lets the generic emits type E flow through,
|
|
11
12
|
// so `emit('viewableItemsChanged', info)` types `info` as `IViewableItemsChangedInfo<T>` and the
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// `v-model="x"` compiles to prop `modelValue` + emit `update:modelValue`; named `v-model:value="x"`
|
|
3
3
|
// compiles to prop `value` + emit `update:value`. These are independent compiler targets, not
|
|
4
4
|
// alternatives, so a component accepts either input and fires both update events rather than
|
|
5
|
-
// picking one. The RN-parity `value` prop/emit pair is untouched either way.
|
|
6
|
-
// vue-adapter-events skill (Rule 6) for the read-every-site gotcha this exists to solve.
|
|
5
|
+
// picking one. The RN-parity `value` prop/emit pair is untouched either way.
|
|
7
6
|
export function resolveModelValue(attrs, isValid) {
|
|
8
7
|
if (isValid(attrs.modelValue))
|
|
9
8
|
return attrs.modelValue;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// (#item / #separator / #header / #footer / #empty / #sectionHeader / #sectionFooter /
|
|
4
4
|
// #sectionSeparator) — the idiomatic Vue surface; React's twin is the renderItem /
|
|
5
5
|
// ItemSeparatorComponent prop family. This is the ONE place that translation lives, so a list's
|
|
6
|
-
// own body reads a single source (its slots), never a renderItem prop. Vue-only lifecycle
|
|
7
|
-
// so it belongs in the adapter
|
|
6
|
+
// own body reads a single source (its slots), never a renderItem prop. This is Vue-only lifecycle
|
|
7
|
+
// glue, so it belongs in the adapter, not the shared layer: the windowing math itself is untouched.
|
|
8
8
|
import {} from '@vue/runtime-core';
|
|
9
9
|
// A component-style renderer (ItemSeparatorComponent / SectionSeparatorComponent) is invoked by the
|
|
10
10
|
// list as h(component, props). Expose the slot as a functional component so its scope props reach
|
package/metro-css-parser.cjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// vue-tsx's babelTransformerPath for standalone .css/.module.css file imports —
|
|
11
11
|
// metro-vue-transformer.cjs (the .vue SFC transformer) requires @symbiote-native/css-parser
|
|
12
12
|
// directly instead, since it needs the individual parse/compile functions, not a Metro
|
|
13
|
-
// transformer object.
|
|
13
|
+
// transformer object.
|
|
14
14
|
// createCssMetroTransformer is a factory, not a ready transformer — Metro's
|
|
15
15
|
// babelTransformerPath needs the actual {transform, getCacheKey} object it returns,
|
|
16
16
|
// not the css-parser package barrel.
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
// Vue plugin (unplugin-vue ships vite/webpack/esbuild/rollup adapters, not Metro), so we do
|
|
3
3
|
// the single-pass compile here: parse the SFC, compile <script setup> + <template> into one
|
|
4
4
|
// component module, then hand the JS to RN's own babel transformer. This is the Metro twin
|
|
5
|
-
// of what @vitejs/plugin-vue does for Vite; the 'vue'→runtime-core rewrite is
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// of what @vitejs/plugin-vue does for Vite; the 'vue'→runtime-core rewrite is needed because
|
|
6
|
+
// a custom, non-DOM renderer needs the compiler helpers from @vue/runtime-core, not from
|
|
7
|
+
// vue/runtime-dom.
|
|
8
8
|
//
|
|
9
9
|
// Retargeted at @symbiote-native/vue/runtime-helpers rather than bare @vue/runtime-core: that shim
|
|
10
10
|
// re-exports runtime-core verbatim PLUS supplies our own `vShow` (compiled v-show imports it by
|
|
11
|
-
// name, and only @vue/runtime-dom's DOM-based version exists otherwise
|
|
12
|
-
// vue-adapter-directives skill).
|
|
11
|
+
// name, and only @vue/runtime-dom's DOM-based version exists otherwise).
|
|
13
12
|
//
|
|
14
13
|
// Ships as a package-level export (`@symbiote-native/vue/metro-vue-transformer`) rather than living
|
|
15
|
-
// in each consuming app: a consumer's own metro.config.js just points babelTransformerPath at it
|
|
16
|
-
// see the symbiote-sfc-style-compiler skill.
|
|
14
|
+
// in each consuming app: a consumer's own metro.config.js just points babelTransformerPath at it.
|
|
17
15
|
|
|
18
16
|
const { parse, compileScript } = require('@vue/compiler-sfc');
|
|
19
17
|
const { createCompoundExpression } = require('@vue/compiler-core');
|
|
@@ -35,8 +33,8 @@ const upstreamTransformer = resolveUpstreamTransformer();
|
|
|
35
33
|
|
|
36
34
|
// Rewrites a Vue template AST so every `class`/`:class` binding on an element resolves
|
|
37
35
|
// against this file's scoped class names at the compiled call site, via @symbiote-native/engine's
|
|
38
|
-
// scopeClassName(value, localNames, scopeId)
|
|
39
|
-
//
|
|
36
|
+
// scopeClassName(value, localNames, scopeId). This runs at the AST level rather than as a
|
|
37
|
+
// raw-text regex: Vue itself merges a static `class=`
|
|
40
38
|
// and a dynamic `:class=` on the same element into ONE codegen entry, and text substitution
|
|
41
39
|
// can't reproduce that merge safely; letting Vue's own transformElement do the merge on our
|
|
42
40
|
// already-rewritten nodes reuses that logic instead of reimplementing it).
|
|
@@ -147,7 +145,7 @@ async function compileSfc(src, filename) {
|
|
|
147
145
|
|
|
148
146
|
// `descriptor.styles` is already parsed by @vue/compiler-sfc itself (one entry per <style>
|
|
149
147
|
// block, `.content` pre-trimmed, `.scoped` already a plain boolean flag), so there's no need
|
|
150
|
-
// to re-extract style blocks with a regex
|
|
148
|
+
// to re-extract style blocks with a regex.
|
|
151
149
|
//
|
|
152
150
|
// A scoped block's classes get their key SUFFIXED with this file's scopeId before
|
|
153
151
|
// registration (`card` -> `card__data-v-xxxxxxxx`), so two components can each define their
|
|
@@ -167,8 +165,7 @@ async function compileSfc(src, filename) {
|
|
|
167
165
|
// registerStyles() call, just under a suffixed key — the only new output is a plain name->
|
|
168
166
|
// scopedName object (`$style` by default, or the block's `module="name"` value) emitted as a
|
|
169
167
|
// preamble const, so `:class="$style.card"` passes the already-scoped string straight to
|
|
170
|
-
// resolveClassName's existing exact-match path (no registry changes needed
|
|
171
|
-
// symbiote-sfc-style-compiler skill). Unlike `scoped`, a module block's classes are NEVER
|
|
168
|
+
// resolveClassName's existing exact-match path (no registry changes needed). Unlike `scoped`, a module block's classes are NEVER
|
|
172
169
|
// auto-applied to a literal class="..." attribute — CSS Modules is opt-in per usage via
|
|
173
170
|
// `$style.x`, so module classes are kept out of `localScopedNames` (the nodeTransform only
|
|
174
171
|
// rewrites literal class strings for `scoped` blocks). The registry key gets an extra
|
|
@@ -280,8 +277,8 @@ module.exports.compileSfc = compileSfc;
|
|
|
280
277
|
// preprocessor: compileSfc() itself is async now (a scss/sass/less/stylus <style> block awaits
|
|
281
278
|
// preprocessors.ts's compile()), and Metro's own metro-transform-worker already
|
|
282
279
|
// `await transformer.transform(...)` before touching the result (confirmed by reading the
|
|
283
|
-
// installed metro-transform-worker source
|
|
284
|
-
//
|
|
280
|
+
// installed metro-transform-worker source), so returning a Promise here is a supported,
|
|
281
|
+
// exercised shape, not a hack. A sync fast-path for
|
|
285
282
|
// the no-preprocessor branches would fork this function into two shapes to save a single
|
|
286
283
|
// microtask on a call that only ever runs at Metro build time, content-hash-cached — not worth
|
|
287
284
|
// the duplication.
|
|
@@ -299,8 +296,7 @@ module.exports.transform = async function transform(params) {
|
|
|
299
296
|
// A standalone style file (as opposed to a `.vue` file's own inline <style> block above) —
|
|
300
297
|
// the framework-agnostic path (core/css-parser's compileCssFile), usable from this example's
|
|
301
298
|
// .vue files exactly like from any other adapter's example. isStyleFile recognizes
|
|
302
|
-
// .css/.scss/.sass/.less/.styl/.stylus (+ each .module.* twin)
|
|
303
|
-
// symbiote-sfc-style-compiler skill.
|
|
299
|
+
// .css/.scss/.sass/.less/.styl/.stylus (+ each .module.* twin).
|
|
304
300
|
if (isStyleFile(params.filename)) {
|
|
305
301
|
const { code } = await compileCssFile(params.src, params.filename);
|
|
306
302
|
return upstreamTransformer.transform({ ...params, src: code, filename: params.filename + '.js' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/vue",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "SymbioteNative's Vue adapter — a @vue/runtime-core createRenderer driving real native iOS/Android views through the same engine as the React adapter.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,16 +44,16 @@
|
|
|
44
44
|
"@vue/compiler-core": "^3.5.13",
|
|
45
45
|
"@vue/compiler-sfc": "^3.5.13",
|
|
46
46
|
"@vue/runtime-core": "^3.5.13",
|
|
47
|
-
"@symbiote-native/components": "0.2.
|
|
48
|
-
"@symbiote-native/css-parser": "0.2.
|
|
49
|
-
"@symbiote-native/engine": "0.1.
|
|
47
|
+
"@symbiote-native/components": "0.2.3",
|
|
48
|
+
"@symbiote-native/css-parser": "0.2.1",
|
|
49
|
+
"@symbiote-native/engine": "0.1.4"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"vue": ">=3.5.0",
|
|
53
53
|
"react-native": ">=0.86"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@symbiote-native/test-utils": "0.1.
|
|
56
|
+
"@symbiote-native/test-utils": "0.1.5"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"typecheck": "tsc --build",
|