@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 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** (milestone R4): a second, non-React,
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 — see
118
- the `vue-adapter-reactivity` notes and `core/engine/src/post-commit.ts`.
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. See ADR 0020.
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. See ADR 0020.
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. See ADR 0020.
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 (ADR 0024 Phase 2). On iOS this is the PullToRefreshView
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 (ADR 0024 Phase 2). A plain view whose native side
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 (ADR 0024 Phase 2). An Android ScrollView accepts only ONE child, so a
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 (ADR 0024 Phase 2). The RefreshControl is a CHILD of the scroll view, rendered
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. See ADR 0020 / 0024.
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 (Phase 1: base, ADR 0024). The Fabric tree is nested:
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
- // Phase 2 (ADR 0024 §4): RefreshControl is wired through the platform assemble (iOS sibling /
17
- // Android wrap). Phase 3 (§5): sticky headers are real. The scroll AnimatedValue (markRaw, held by
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. See the
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. See
94
- // .claude/skills/vue-adapter-reactivity. This is the same rule as the Switch host node.
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 (ADR 0024 §3). expose() makes it the value a parent ref
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 (Phase 3, ADR 0024): a pure-JS layer; the native scroll view ignores
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, ADR 0024); this file holds the Vue component
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. See .claude/skills/vue-adapter-reactivity.
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. See ADR 0020.
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. See ADR 0020.
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. See ADR 0020. Switch is the first component to bring the
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
- // See .claude/skills/vue-adapter-reactivity. Same rule as the Switch / ScrollView host node.
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); see
22
- // core/components/.docs-note-lists.md. Cells/spacers are built with h() directly off the plan.
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 (.claude/skills/vue-adapter-reactivity): the ScrollView's exposed handle
25
- // is held in a shallowRef so the engine node it closes over is reached by identity; a deep
26
- // ref would proxy it and the imperative scroll commands would silently no-op.
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/index.js';
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. See
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;
@@ -1,6 +1,7 @@
1
- // createTunnel — the Vue twin of the React adapter's create-tunnel.ts (see its header, plus
2
- // the react-adapter-portal / vue-adapter-directives skills, for the full "why not just
3
- // extend Teleport across surfaces" rationale). `Out` lives in whichever component should
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. See
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 and the vue-adapter-directives skill for why).
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/index.js';
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/index.js';
24
- // ScrollView. Full parity (ADR 0024): vertical + horizontal, every pass-through prop, the
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, Phase 2), and sticky headers (Phase 3: the scroll AnimatedValue,
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/index.js';
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
- // Phase 2 (ADR 0024): SafeAreaView + RefreshControl, wired into ScrollView with the iOS-sibling /
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 (Rule 6, vue-adapter-events skill) helper every
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 (ADR 0024 Phase 3a): Animated.View/Text/Image + the lazy Animated.ScrollView over the
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 (ADR 0016). The framework-agnostic
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
- // See .claude/skills/vue-adapter-reactivity. Same rule as Switch / ScrollView host nodes.
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, ADR 0016), spread in verbatim. Both halves meet here so the familiar surface,
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/index.js';
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 (M3 / R4).
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. See the
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. (wolf-tui fakes a comment with an empty text node;
44
- // here an empty RCTRawText would actually paint, so an anchor is the right call.)
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
  },
@@ -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
- // See the vue-adapter-directives skill for the full rationale and scope (what this does and does
13
- // NOT cover — native-element v-model is a separate, out-of-scope case).
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 (see vue-adapter-reactivity). A bare setNativeProps
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 (the symbiote port of pd-web-kit's pattern): a
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 — it is the only way to express a generic component in plain TS; see CLAUDE.md no-`as` rule).
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. See the
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 glue,
7
- // so it belongs in the adapter (adapters_stay_thin): the shared windowing math is untouched.
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
@@ -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. See the symbiote-sfc-style-compiler skill.
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 wolf-tui's
6
- // pattern (a custom, non-DOM renderer needs the compiler helpers from @vue/runtime-core,
7
- // not from vue/runtime-dom).
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 — see the
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) — see the symbiote-sfc-style-compiler skill for
39
- // the full design (why AST-level, not a raw-text regex: Vue itself merges a static `class=`
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 the way wolf-tui's Vite plugin does.
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, see the
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 — see metro-transformer.ts for the fuller writeup),
284
- // so returning a Promise here is a supported, exercised shape, not a hack. A sync fast-path for
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) — see the
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.1",
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.1",
48
- "@symbiote-native/css-parser": "0.2.0",
49
- "@symbiote-native/engine": "0.1.2"
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.1"
56
+ "@symbiote-native/test-utils": "0.1.5"
57
57
  },
58
58
  "scripts": {
59
59
  "typecheck": "tsc --build",