@symbiote-native/vue 0.3.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/build/bootstrap.js +1 -1
  3. package/build/components/flat-list/index.js +32 -39
  4. package/build/components/image-background.js +8 -19
  5. package/build/components/image.js +12 -21
  6. package/build/components/keyboard-avoiding-view.js +47 -31
  7. package/build/components/modal/index.d.ts +4 -4
  8. package/build/components/modal/index.js +5 -2
  9. package/build/components/pressable.d.ts +1 -1
  10. package/build/components/pressable.js +25 -32
  11. package/build/components/refresh-control.js +12 -18
  12. package/build/components/scroll-view/index.android.d.ts +1 -1
  13. package/build/components/scroll-view/index.android.js +24 -34
  14. package/build/components/scroll-view/index.ios.d.ts +1 -1
  15. package/build/components/scroll-view/index.ios.js +3 -1
  16. package/build/components/scroll-view/shared.js +93 -123
  17. package/build/components/scroll-view/sticky-header.js +59 -64
  18. package/build/components/section-list/index.js +16 -21
  19. package/build/components/switch/shared.js +29 -32
  20. package/build/components/text-input/index.js +36 -51
  21. package/build/components/touchable-native-feedback.js +3 -1
  22. package/build/components/touchable.d.ts +7 -1
  23. package/build/components/touchable.js +181 -33
  24. package/build/components/virtualized-list/index.js +57 -18
  25. package/build/components/virtualized-section-list/index.d.ts +5 -0
  26. package/build/components/virtualized-section-list/index.js +50 -29
  27. package/build/components.js +9 -6
  28. package/build/create-portal/index.d.ts +28 -0
  29. package/build/create-portal/index.js +42 -0
  30. package/build/host-instance/index.js +5 -5
  31. package/build/index.d.ts +9 -6
  32. package/build/index.js +18 -46
  33. package/build/modules/animated/create-animated-component.js +46 -101
  34. package/build/modules/animated/index.d.ts +2 -0
  35. package/build/modules/animated/index.js +26 -17
  36. package/build/modules/app-registry/index.js +11 -13
  37. package/build/modules/status-bar.js +12 -17
  38. package/build/render.d.ts +3 -1
  39. package/build/render.js +62 -1
  40. package/build/renderer/index.js +11 -6
  41. package/build/runtime-helpers/index.d.ts +44 -24
  42. package/build/runtime-helpers/index.js +116 -47
  43. package/build/utils/normalize-attrs.js +3 -1
  44. package/metro-css-parser.cjs +9 -14
  45. package/metro-vue-transformer.cjs +196 -178
  46. package/package.json +5 -5
@@ -9,18 +9,21 @@
9
9
  // stateful component's ref would resolve to a useless component proxy, which is why
10
10
  // createAnimatedComponent (which captures the host node via that ref) needs the fall-through.
11
11
  import { h } from '@vue/runtime-core';
12
+ import { resolveTextProps } from '@symbiote-native/components';
12
13
  import { normalizeVueAttrs } from './utils/normalize-attrs.js';
13
- function hostComponent(intrinsic, name) {
14
- // A functional component's ctx is Omit<SetupContext, 'expose'> (no instance to expose); let the
15
- // FunctionalComponent target infer the param types rather than annotate SetupContext.
14
+ function hostComponent(intrinsic, name,
15
+ // An optional last fold over the normalized attrs. Text needs one (RN's ellipsizeMode /
16
+ // allowFontScaling defaults); View does not, so it stays a straight pass-through.
17
+ fold = attrs => attrs) {
16
18
  // normalizeVueAttrs folds kebab template props (:accessibility-label) to the RN camelCase contract.
17
- const component = (_props, { slots, attrs }) => h(intrinsic, normalizeVueAttrs(attrs), slots.default !== undefined ? slots.default() : undefined);
19
+ const component = (_props, { slots, attrs }) => h(intrinsic, fold(normalizeVueAttrs(attrs)), slots.default !== undefined ? slots.default() : undefined);
18
20
  component.displayName = name;
19
21
  component.inheritAttrs = false;
20
22
  return component;
21
23
  }
22
24
  export const View = hostComponent('symbiote-view', 'View');
23
- export const Text = hostComponent('symbiote-text', 'Text');
25
+ export const Text = hostComponent('symbiote-text', 'Text', resolveTextProps);
26
+ // Text is no longer bare either — it carries RN's Text.js defaults through resolveTextProps.
24
27
  // Image is NOT a bare host primitive: it needs the shared fold (source/src/srcSet resolution,
25
- // width/height → style, alt → accessibility) + the Image statics, so it lives in ./image as a
28
+ // width/height -> style, alt -> accessibility) + the Image statics, so it lives in ./image as a
26
29
  // functional component over renderImage. View/Text stay bare; they forward attrs verbatim.
@@ -0,0 +1,28 @@
1
+ import { SymbioteSurface, type ISymbioteNode } from '@symbiote-native/engine';
2
+ /** Anything already mounted in THIS surface: a host-node ref, or the surface root itself. */
3
+ export type ITeleportTarget = ISymbioteNode | SymbioteSurface;
4
+ export declare function isTeleportTarget(target: unknown): target is ITeleportTarget;
5
+ export declare const Teleport: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
6
+ to: {
7
+ type: null;
8
+ default: null;
9
+ };
10
+ disabled: {
11
+ type: BooleanConstructor;
12
+ default: boolean;
13
+ };
14
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
15
+ [key: string]: any;
16
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
17
+ to: {
18
+ type: null;
19
+ default: null;
20
+ };
21
+ disabled: {
22
+ type: BooleanConstructor;
23
+ default: boolean;
24
+ };
25
+ }>> & Readonly<{}>, {
26
+ to: any;
27
+ disabled: boolean;
28
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,42 @@
1
+ // The Vue adapter's portal — Vue's OWN `<Teleport>`, not a parallel API. Teleport lives in
2
+ // @vue/runtime-core, not @vue/runtime-dom, and resolves its target through the RendererOptions we
3
+ // already supply: `resolveTarget` only consults `querySelector` for the STRING form of `to`, and
4
+ // returns an object target untouched. So stock Teleport drives this renderer as-is — target
5
+ // change, unmount, disabled, call-site provide/inject and reactive updates all measured working
6
+ // over the engine (create-portal.test.ts). This module adds exactly one thing on top: a runtime
7
+ // guard on `to`.
8
+ //
9
+ // Why the guard: `to` is untyped at the template boundary, and a wrong value (a CSS selector
10
+ // copied from web code, a forgotten `.value` on a ref, a plain object) would otherwise sink into
11
+ // insert/remove deep in the engine and corrupt the retained tree silently. Vue's own dev warning
12
+ // for the string form only fires in a dev build and still returns null, which paints nothing.
13
+ //
14
+ // Scope — same-surface targets only, the same PERMANENT boundary as React's createPortal
15
+ // (adapters/react/src/create-portal/index.ts): a portal MOVES host nodes, and only the surface
16
+ // that owns them ever commits them. Content whose target lives in a second, independently
17
+ // mount()ed surface needs createTunnel instead, which copies rather than moves.
18
+ import { defineComponent, h, Teleport as VueTeleport } from '@vue/runtime-core';
19
+ import { isSymbioteNode, SymbioteSurface, } from '@symbiote-native/engine';
20
+ export function isTeleportTarget(target) {
21
+ return target instanceof SymbioteSurface || isSymbioteNode(target);
22
+ }
23
+ export const Teleport = defineComponent({
24
+ name: 'Teleport',
25
+ inheritAttrs: false,
26
+ props: {
27
+ to: { type: null, default: null },
28
+ disabled: { type: Boolean, default: false },
29
+ },
30
+ setup(props, { slots }) {
31
+ return () => {
32
+ const { to } = props;
33
+ if (typeof to === 'string') {
34
+ throw new Error(`Teleport target must be a host node ref, not a CSS-selector string ("${to}") — symbiote has no querySelector. Pass a ref to an already-rendered element instead (e.g. <View ref="overlayHost" />, then :to="overlayHost").`);
35
+ }
36
+ if (to != null && !isTeleportTarget(to)) {
37
+ throw new Error('Teleport target is not a real host node — did you forget `.value` on a ref, or pass something symbiote never rendered?');
38
+ }
39
+ return h(VueTeleport, { to, disabled: props.disabled }, slots);
40
+ };
41
+ },
42
+ });
@@ -8,14 +8,14 @@
8
8
  // engine so React and Vue share one definition.
9
9
  //
10
10
  // The node -> tag resolution itself belongs to the engine (getNativeTag, keyed on the raw
11
- // node in the commit mirror); this only adapts the Vue-shaped inputs onto it. Accepts, in
12
- // RN's idempotent spirit: a bare number (passes through), an engine host node (a ref's
13
- // .value), a Vue Ref carrying either (unwrapped), or null / undefined (-> null). An
14
- // uncommitted or unknown input has no tag yet and surfaces as null.
11
+ // node in the commit mirror); this only unwraps the Vue-shaped inputs (a Ref or a bare node)
12
+ // onto it. An uncommitted or unknown input has no tag yet and surfaces as null.
15
13
  import { isRef, toRaw } from '@vue/runtime-core';
16
14
  import { getNativeTag, isSymbioteNode, dlog } from '@symbiote-native/engine';
17
15
  export function findNodeHandle(componentOrHandle) {
18
- const candidate = isRef(componentOrHandle) ? componentOrHandle.value : componentOrHandle;
16
+ const candidate = isRef(componentOrHandle)
17
+ ? componentOrHandle.value
18
+ : componentOrHandle;
19
19
  if (candidate === null || candidate === undefined)
20
20
  return null;
21
21
  if (typeof candidate === 'number')
package/build/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export { mount, unmount } from './render';
1
+ export { mount, unmount, setAppConfigurator } from './render';
2
+ export type { IAppConfigurator } from './render';
3
+ export { Teleport, type ITeleportTarget } from './create-portal';
2
4
  export { createTunnel, type ITunnel } from './create-tunnel';
3
5
  export { View, Text } from './components';
4
6
  export type { IAccessibilityProps, IAriaProps, IAccessibilityRole, IRole, IAccessibilityStateValue, IAccessibilityValue, IAccessibilityActionInfo, IResponderProps, } from '@symbiote-native/components';
@@ -21,7 +23,7 @@ export type { INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground
21
23
  export { Button } from './components/button';
22
24
  export type { IButtonProps } from './components/button';
23
25
  export { TextInput } from './components/text-input';
24
- export type { ITextInputProps, ITextInputHandle } from './components/text-input';
26
+ export type { ITextInputProps, ITextInputHandle, } from './components/text-input';
25
27
  export { VirtualizedList } from './components/virtualized-list';
26
28
  export type { IVirtualizedListProps, IVirtualizedListSlots, IVirtualizedListEmits, IVirtualizedListHandle, ISeparators, ISeparatorProps, IViewToken, IViewableItemsChangedInfo, IViewabilityConfig, IViewabilityConfigCallbackPair, ICellLayout, } from './components/virtualized-list';
27
29
  export { FlatList } from './components/flat-list';
@@ -33,7 +35,7 @@ export type { ISection, ISectionListProps, ISectionListSlots, ISectionListEmits,
33
35
  export { SafeAreaView } from './components/safe-area-view';
34
36
  export type { ISafeAreaViewProps } from './components/safe-area-view';
35
37
  export { RefreshControl } from './components/refresh-control';
36
- export type { IRefreshControlProps, IRefreshControlEmits } from './components/refresh-control';
38
+ export type { IRefreshControlProps, IRefreshControlEmits, } from './components/refresh-control';
37
39
  export { descriptorToVue } from './descriptor-to-vue';
38
40
  export { normalizeVueAttrs } from './utils/normalize-attrs';
39
41
  export { resolveModelValue, emitModelUpdate } from './utils/model-binding';
@@ -56,10 +58,11 @@ export { useWindowDimensions } from './composables/use-window-dimensions';
56
58
  export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Settings, I18nManager, Dimensions, Appearance, AppState, Keyboard, KEYBOARD_EVENT, BackHandler, PermissionsAndroid, PERMISSIONS, RESULTS, AccessibilityInfo, LayoutAnimation, } from '@symbiote-native/engine';
57
59
  export type { IAlertType, IAlertButtonStyle, IAlertButton, IAlertButtons, IAlertOptions, IShareContent, IShareOptions, IShareAction, IActionSheetIOSOptions, IShareActionSheetIOSOptions, IShareActionSheetError, IUrlEvent, II18nManagerConstants, IDisplayMetrics, IDisplayMetricsAndroid, IDimensionsPayload, IDimensionsSet, IDimensionsKey, IDimensionsChangeListener, IDimensionsStatic, IColorSchemeName, IColorSchemePreference, IAppStateStatus, IAppStateEvent, IKeyboardEventName, IKeyboardEvent, IKeyboardMetrics, IBackPressEventName, IBackPressHandler, IPermission, IPermissionStatus, IRationale, IAccessibilityChangeEvent, IAccessibilityChangeEventName, IAccessibilityChangeEventHandler, IAccessibilityAnnouncementFinishedEvent, IAnnounceForAccessibilityOptions, IAccessibilityEventType, ILayoutAnimationType, ILayoutAnimationProperty, ILayoutAnimationConfig, ILayoutAnimationAnim, } from '@symbiote-native/engine';
58
60
  export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
59
- export type { ISymbioteEvent, ISymbioteNode, IRootTag } from '@symbiote-native/engine';
61
+ export type { ISymbioteEvent, ISymbioteNode, IRootTag, } from '@symbiote-native/engine';
60
62
  export type { IViewStyle, ITextStyle, IFlexAlign, IFlexJustify, IPlatformStatic, IPlatformOSType, IPlatformConstantsIOS, IPlatformConstantsAndroid, IPlatformSelectSpec, } from '@symbiote-native/engine';
61
- export { setNativeViewConfigSource } from '@symbiote-native/engine';
62
- export type { INativeViewConfig, INativeViewConfigSource } from '@symbiote-native/engine';
63
+ export { setNativeViewConfigSource, setDeviceEventSource, } from '@symbiote-native/engine';
64
+ export type { INativeViewConfig, INativeViewConfigSource, } from '@symbiote-native/engine';
65
+ export type { IEnterKeyHint, IInputMode, ISubmitBehavior, ITextInputSelection, IImageStatics, } from '@symbiote-native/components';
63
66
  export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';
64
67
  export type { IPixelRatioStatic } from '@symbiote-native/engine';
65
68
  export type { IPanResponderGestureState, IPanResponderCallbacks, IGestureResponderHandlers, IPanResponderInstance, } from '@symbiote-native/engine';
package/build/index.js CHANGED
@@ -1,79 +1,50 @@
1
1
  // @symbiote-native/vue: a thin Vue 3 reconciler over @symbiote-native/engine. createRenderer maps
2
2
  // each RendererOptions call onto the engine's mutation API; all Fabric clone-on-write
3
3
  // lives in the engine, shared with every other adapter. App code names only @symbiote-native/vue.
4
- export { mount, unmount } from './render.js';
5
- // createTunnel: cross-surface content sharing (Teleport stays same-surface-only by design —
6
- // see create-tunnel.ts for why).
4
+ export { mount, unmount, setAppConfigurator } from './render.js';
5
+ // The portal: Vue's own <Teleport>, guarded so `to` must be a node/surface this renderer actually
6
+ // mounted (there is no querySelector). It MOVES host nodes, so it reaches any already-mounted
7
+ // target in the SAME surface — including one you only hold a ref to — and keeps the content's
8
+ // reactive owner at the call site (provide/inject resolve from where it was written). Reaching a
9
+ // second, independently mount()ed surface is a different mechanism: createTunnel, which copies
10
+ // into an <Out/> the destination has to render. See create-portal/index.ts.
11
+ export { Teleport } from './create-portal/index.js';
7
12
  export { createTunnel } from './create-tunnel/index.js';
8
13
  export { View, Text } from './components.js';
9
- // findNodeHandle: RN's ref -> native reactTag lookup, the Vue twin of the React adapter's
10
- // host-instance resolution. IHostInstance is the raw engine node (Vue host refs fall
11
- // through to it; imperative methods live on each component's expose() handle).
14
+ // IHostInstance is the raw engine node (Vue host refs fall through to it; imperative methods
15
+ // live on each component's expose() handle).
12
16
  export { findNodeHandle } from './host-instance/index.js';
13
- // Image. Full parity with React: source/src/srcSet resolution, the width/height → style fold,
14
- // alt → accessibility, and the Image statics (getSize / prefetch / queryCache / …) are shared
15
- // verbatim from @symbiote-native/components via renderImage; Vue supplies only the functional bridge.
16
17
  export { Image, setImageSourceResolver } from './components/image.js';
17
- // First component off the shared @symbiote-native/components layer: render-only, drives the
18
- // agnostic renderActivityIndicator through descriptorToVue. Proof the layer is reusable:
19
- // the render fn is shared verbatim with React; Vue supplies only the bridge.
20
18
  export { ActivityIndicator } from './components/activity-indicator';
21
- // First component to bring the state half into Vue (the lastNativeReport reducer + the
22
- // snap-back watch); render shared verbatim with React, Vue supplies the reactive lifecycle.
23
19
  export { Switch } from './components/switch';
24
- // ScrollView. Full parity: vertical + horizontal, every pass-through prop, the
25
- // synthesized onContentSizeChange, the imperative handle via expose() + shallowRef, RefreshControl
26
- // (iOS sibling / Android wrap), and sticky headers (the scroll AnimatedValue,
27
- // the headerLayoutYs cross-talk, and the per-header Animated.View wrap). The pure math (intrinsics,
28
- // decelerationRate, content-size dedupe, the handle, the sticky interpolation) is shared verbatim
29
- // with React from @symbiote-native/components.
30
20
  export { ScrollView } from './components/scroll-view';
31
- // Pressable family. Full parity with React (the 3-layer split): the press state machine + render
32
- // decisions are shared in @symbiote-native/components; Vue supplies the reactivity + descriptor bridge.
33
21
  export { Pressable } from './components/pressable.js';
34
22
  export { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, } from './components/touchable.js';
35
23
  export { TouchableNativeFeedback } from './components/touchable-native-feedback.js';
36
24
  export { Button } from './components/button.js';
37
- // TextInput. Full parity with React (the 3-layer split): the value/selection folds + the
38
- // controlled-write predicate live in @symbiote-native/components; Vue supplies the reactivity
39
- // (shallowRef host node, post-flush watch) + the imperative handle via expose().
40
25
  export { TextInput } from './components/text-input/index.js';
41
- // VirtualizedList family. Full parity with React: the windowing math (visible range, cell
42
- // keys, viewability token diffing, edge-reached, the list plan) is shared in
43
- // @symbiote-native/components; Vue supplies the reactive lifecycle + the per-cell element.
44
26
  export { VirtualizedList } from './components/virtualized-list/index.js';
45
27
  export { FlatList } from './components/flat-list/index.js';
46
28
  export { VirtualizedSectionList } from './components/virtualized-section-list/index.js';
47
29
  export { SectionList } from './components/section-list/index.js';
48
- // SafeAreaView + RefreshControl, wired into ScrollView with the iOS-sibling /
49
- // Android-wrap platform split. RefreshControl hosts the wrapped scroll view via its default slot.
30
+ // RefreshControl hosts the wrapped scroll view via its default slot.
50
31
  export { SafeAreaView } from './components/safe-area-view.js';
51
32
  export { RefreshControl } from './components/refresh-control.js';
52
33
  export { descriptorToVue } from './descriptor-to-vue.js';
53
- // normalizeVueAttrs: the kebab→camel attr fold every component applies at entry. Exported so an
54
- // external wrapper package (e.g. @symbiote-native/slider/vue over a third-party native view) can fold
55
- // its incoming attrs through the SAME transform rather than reimplementing it.
34
+ // Exported so an external wrapper package (e.g. @symbiote-native/slider/vue over a third-party
35
+ // native view) can fold its incoming attrs/v-model through the SAME transform rather than
36
+ // reimplementing it.
56
37
  export { normalizeVueAttrs } from './utils/normalize-attrs.js';
57
- // resolveModelValue/emitModelUpdate: the v-model helper every
58
- // controlled-value component uses. Exported for the same external-package reason as
59
- // normalizeVueAttrs above (e.g. @symbiote-native/slider/vue).
60
38
  export { resolveModelValue, emitModelUpdate } from './utils/model-binding.js';
61
39
  export { createSymbioteRenderer } from './renderer/index.js';
62
- // Animated: Animated.View/Text/Image + the lazy Animated.ScrollView over the
63
- // Vue primitives, with the value graph / easing / drivers spread from @symbiote-native/engine. The wrap
64
- // mechanism (createAnimatedComponent) is the Vue twin of React's; the pure leaves live in the
65
- // engine.
66
40
  export { Animated, createAnimatedComponent } from './modules/animated/index.js';
67
- // Visual components off the shared @symbiote-native/components layer (wave: simple-visual).
68
41
  export { ImageBackground } from './components/image-background.js';
69
42
  export { InputAccessoryView } from './components/input-accessory-view.js';
70
43
  export { Modal } from './components/modal/index.js';
71
- // KeyboardAvoidingView: full parity via the shared inset math; Vue owns the Keyboard subscription.
72
44
  export { KeyboardAvoidingView } from './components/keyboard-avoiding-view.js';
73
- // StatusBar: declarative component + the shared imperative API.
74
45
  export { StatusBar } from './modules/status-bar.js';
75
- // AppRegistry: RN's app entry point over `mount`. setHostRegistrar wires RN's own
76
- // registrar so the native Fabric host finds our runnable by app key.
46
+ // RN's app entry point over `mount`. setHostRegistrar wires RN's own registrar so the native
47
+ // Fabric host finds our runnable by app key.
77
48
  export { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
78
49
  // Vue composables over the core device-state modules.
79
50
  export { useColorScheme } from './composables/use-color-scheme.js';
@@ -84,7 +55,8 @@ export { Alert, Share, ActionSheetIOS, Linking, Vibration, ToastAndroid, Setting
84
55
  export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, } from '@symbiote-native/engine';
85
56
  // Wired once by the app entry on a real host (like setColorProcessor): hands the engine
86
57
  // RN's ViewConfig registry so third-party Fabric views auto-derive their metadata.
87
- export { setNativeViewConfigSource } from '@symbiote-native/engine';
58
+ // setDeviceEventSource is the third seam of the same set and travels with them.
59
+ export { setNativeViewConfigSource, setDeviceEventSource, } from '@symbiote-native/engine';
88
60
  // Pure utilities that moved to the engine (single source, both adapters re-export):
89
61
  // PixelRatio + PanResponder, plus the color builders and the interaction scheduler.
90
62
  export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';
@@ -1,27 +1,21 @@
1
- // createAnimatedComponent: the Vue twin of the React wrapper. It wraps a base component
2
- // (View / Text / Image / ScrollView / any) so it accepts AnimatedNodes in its props.
3
- // Same JS-driven path, NO native driver in the hot loop: a frame is the scoped commit
4
- // setNativeProps drives from the AnimatedProps leaf. The framework-agnostic
5
- // pieces (reduceProps / readPassthroughStyle / resolveHostNode / isAnimatedNode + the
6
- // AnimatedProps leaf itself) live in @symbiote-native/engine, shared verbatim with React; here
7
- // Vue supplies only the lifecycle.
8
- //
9
- // React → hooks: useMemo(leaf) + three useEffects + a callback ref. Vue → reactivity:
10
- // a render that rebuilds the leaf each pass (like React's per-render useMemo(rest)) + a
11
- // post-commit reconcile (onMounted/onUpdated, the Vue twin of a post-commit useEffect) +
12
- // a function ref. The per-frame path is unchanged and NEVER goes through Vue render:
13
- // value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
1
+ // createAnimatedComponent: the Vue twin of the React wrapper. Wraps a base component
2
+ // (View/Text/Image/ScrollView/any) so it accepts AnimatedNodes in its props. Framework-agnostic
3
+ // pieces (reduceProps/readPassthroughStyle/resolveHostNode/isAnimatedNode + the AnimatedProps
4
+ // leaf) live in @symbiote-native/engine, shared verbatim with React; Vue supplies only the
5
+ // lifecycle: a render that rebuilds the leaf each pass + a post-commit reconcile (onMounted/
6
+ // onUpdated) + a function ref. The per-frame path is unchanged and NEVER goes through Vue
7
+ // render: value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
14
8
  import { defineComponent, h, onBeforeUnmount, onMounted, onUpdated, shallowRef, } from '@vue/runtime-core';
15
- import { AnimatedProps, attachNativeEventHandler, isNativeAnimatedAvailable, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
9
+ import { createAnimatedLeafLifecycle, isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
16
10
  import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
17
11
  // RN's prop carrying explicit (already-rasterized) values that override the animated prop in
18
12
  // the COMMITTED props (sticky-header passthrough). Named once here so render/reconcile agree.
19
13
  const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
20
- // A base component is either a functional component (View / Text / Image, a function) or a
21
- // stateful defineComponent (ScrollView, an object). Read its display name without a cast for
22
- // the wrapper's devtools name.
14
+ // Reads either a functional component's or a stateful defineComponent's display name, without a
15
+ // cast, for the wrapper's devtools name.
23
16
  function baseName(component) {
24
- if (component === null || (typeof component !== 'function' && typeof component !== 'object')) {
17
+ if (component === null ||
18
+ (typeof component !== 'function' && typeof component !== 'object')) {
25
19
  return 'Anonymous';
26
20
  }
27
21
  const display = Reflect.get(component, 'displayName');
@@ -37,86 +31,35 @@ export function createAnimatedComponent(Component) {
37
31
  name: `Animated(${baseName(Component)})`,
38
32
  inheritAttrs: false,
39
33
  setup(_props, { attrs: rawAttrs, slots, expose }) {
40
- // The committed host node, held by IDENTITY in a shallowRef (the reactivity rule: a deep
41
- // ref() would run it through toReactive() and hand back a Proxy, so AnimatedProps.setNativeView
42
- // / attachNativeEventHandler would miss the engine's WeakMap mirror, keyed on the raw node).
43
- // Same rule as Switch / ScrollView host nodes.
34
+ // shallowRef, not ref: a deep ref() would run it through toReactive() and hand back a
35
+ // Proxy, missing the engine's WeakMap mirror. Same rule as Switch / ScrollView host nodes.
44
36
  const nodeRef = shallowRef(null);
45
- // The base component's public instance (a ScrollView handle, or the host node for View).
46
- // Forwarded to a parent ref via expose(). shallowRef: it may itself BE an engine node.
37
+ // The base component's public instance (a ScrollView handle, or the host node for View),
38
+ // forwarded to a parent ref via expose(). shallowRef: it may itself BE an engine node.
47
39
  const instanceRef = shallowRef(null);
48
- // The AnimatedProps leaf the next reconcile should wire in, rebuilt by render each pass
49
- // (like React's per-render useMemo(rest)). Plain (non-reactive) so writing it in render
50
- // triggers no reactive effect. `attached` is the leaf currently in the value graph.
51
- let pendingLeaf = null;
52
- let attached = null;
53
- // The current non-passthrough props, kept so the post-commit event-attach can re-scan them.
40
+ // The leaf lifecycle - build/swap/bind/detach, and the rebuild-vs-skip decision - is the
41
+ // engine's, shared by every adapter (core/engine/src/animated/leaf-lifecycle.ts). Vue owns
42
+ // only WHEN to run it: after mount and after every re-render, which is when `currentRest`
43
+ // has just been refreshed by the render function below.
44
+ const lifecycle = createAnimatedLeafLifecycle('vue');
45
+ // The props the next reconcile should wire in, refreshed by render each pass.
54
46
  let currentRest = {};
55
47
  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
48
  function reconcile() {
85
- const newLeaf = pendingLeaf;
86
- if (newLeaf === null)
87
- return;
88
- newLeaf.__attach();
89
- if (attached !== null && attached !== newLeaf)
90
- attached.__detach();
91
- attached = newLeaf;
92
- const node = nodeRef.value;
93
- if (node !== null)
94
- newLeaf.setNativeView(node);
95
- if (wantsNative)
96
- newLeaf.__makeNative();
97
- attachEvents();
49
+ lifecycle.reconcile(currentRest, isSymbioteNode(nodeRef.value) ? nodeRef.value : null, wantsNative);
98
50
  }
99
51
  onMounted(reconcile);
100
52
  onUpdated(reconcile);
101
- // Final teardown: detach the last-attached leaf and any native events on unmount.
102
- onBeforeUnmount(() => {
103
- detachEvents();
104
- if (attached !== null) {
105
- attached.__detach();
106
- attached = null;
107
- }
108
- });
109
- // Function ref (held stably, so Vue doesn't re-invoke it per patch). On mount the base hands
110
- // back its public instance; resolve it to the underlying host node (unwrapping a scroll-container
111
- // handle via getScrollNode), record THAT for the leaf binding / event attach, and keep the
112
- // ORIGINAL instance for ref forwarding. Vue calls it with null on unmount, node clears.
53
+ onBeforeUnmount(() => lifecycle.teardown());
54
+ // On mount the base hands back its public instance; resolve it to the underlying host node
55
+ // (unwrapping a scroll-container handle via getScrollNode) for the leaf binding / event
56
+ // attach, and keep the ORIGINAL instance for ref forwarding.
113
57
  const captureRef = (instance) => {
114
58
  instanceRef.value = instance;
115
59
  nodeRef.value = resolveHostNode(instance);
116
60
  };
117
- // Forward the caller's ref to the base instance: a parent ref on Animated.ScrollView still gets
118
- // scrollTo (the exposed handle), on Animated.View the host node. A delegating proxy because the
119
- // instance is captured async (after mount), so a snapshot exposed at setup time would be null.
61
+ // A delegating proxy because the instance is captured async (after mount), so a snapshot
62
+ // exposed at setup time would be null.
120
63
  expose(new Proxy(Object.create(null), {
121
64
  get: (_target, key) => {
122
65
  const instance = instanceRef.value;
@@ -126,15 +69,15 @@ export function createAnimatedComponent(Component) {
126
69
  },
127
70
  has: (_target, key) => {
128
71
  const instance = instanceRef.value;
129
- return instance !== null && typeof instance === 'object' && Reflect.has(instance, key);
72
+ return (instance !== null &&
73
+ typeof instance === 'object' &&
74
+ Reflect.has(instance, key));
130
75
  },
131
76
  }));
132
77
  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
78
  const attrs = normalizeVueAttrs(rawAttrs);
136
- // Split the incoming attrs into the passthrough (consumed) and the rest (forwarded). The
137
- // passthrough never reaches the base as a prop; it only overrides the committed style below.
79
+ // Split into the passthrough (consumed) and the rest (forwarded). The passthrough never
80
+ // reaches the base as a prop; it only overrides the committed style below.
138
81
  const rest = {};
139
82
  let passthrough;
140
83
  for (const key of Object.keys(attrs)) {
@@ -146,23 +89,25 @@ export function createAnimatedComponent(Component) {
146
89
  }
147
90
  currentRest = rest;
148
91
  // Native driving is opt-in per the passthrough prop AND requires a real native module;
149
- // headless / unsupported hosts keep the JS flush path (and the JS smokes green).
92
+ // headless/unsupported hosts keep the JS flush path.
150
93
  wantsNative = passthrough != null && isNativeAnimatedAvailable();
151
- // One AnimatedProps leaf per render pass (rebuilt like React's useMemo(rest)); the post-commit
152
- // reconcile wires it into the graph and swaps the previous one out.
153
- pendingLeaf = new AnimatedProps(rest);
154
- // Reduced props are concrete (animated nodes replaced by their current values) so the first
155
- // paint carries real values, exactly like React's first render.
94
+ // Reduced props are concrete (animated nodes replaced by their current values) so the
95
+ // first paint carries real values.
156
96
  const reduced = reduceProps(rest);
157
- // Override the committed style with the explicit passthrough values (last wins via the style
158
- // array, which the commit layer flattens) so the ShadowTree carries the current transform.
97
+ // Override the committed style with the explicit passthrough values (last wins via the
98
+ // style array) so the ShadowTree carries the current transform.
159
99
  const passthroughStyle = readPassthroughStyle(passthrough);
160
100
  if (passthroughStyle !== undefined) {
161
101
  reduced.style =
162
- reduced.style === undefined ? passthroughStyle : [reduced.style, passthroughStyle];
102
+ reduced.style === undefined
103
+ ? passthroughStyle
104
+ : [reduced.style, passthroughStyle];
163
105
  }
164
106
  reduced.ref = captureRef;
165
- return h(Component, reduced, slots.default !== undefined ? { default: slots.default } : undefined);
107
+ // Forward the WHOLE slots object, not just `default`: the list containers take their
108
+ // content through named scoped slots (`item`, `sectionHeader`, ...), which a default-only
109
+ // forward drops silently - Animated.FlatList would commit empty cells.
110
+ return h(Component, reduced, slots);
166
111
  };
167
112
  },
168
113
  });
@@ -51,5 +51,7 @@ export declare const Animated: {
51
51
  [key: string]: any;
52
52
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
53
53
  ScrollView: ReturnType<typeof createAnimatedComponent>;
54
+ FlatList: ReturnType<typeof createAnimatedComponent>;
55
+ SectionList: ReturnType<typeof createAnimatedComponent>;
54
56
  createAnimatedComponent: typeof createAnimatedComponent;
55
57
  };
@@ -1,12 +1,14 @@
1
- // The Animated namespace for @symbiote-native/vue: the Vue twin of adapters/react/src/animated/index.ts.
2
- // createAnimatedComponent applied to the Vue primitives gives Animated.View / Text / Image; the
3
- // value graph, easing and imperative drivers come from @symbiote-native/engine (framework-agnostic,
4
- // JS-driven), spread in verbatim. Both halves meet here so the familiar surface,
5
- // `Animated.timing(new Animated.Value(0), …).start()`, works against the Vue-driven engine.
1
+ // The Animated namespace for @symbiote-native/vue. createAnimatedComponent applied to the Vue
2
+ // primitives gives Animated.View/Text/Image; the value graph, easing and imperative drivers come
3
+ // from @symbiote-native/engine (framework-agnostic, JS-driven), spread in verbatim. Both halves
4
+ // meet here so `Animated.timing(new Animated.Value(0), ...).start()` works against the
5
+ // Vue-driven engine.
6
6
  import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
7
7
  import { View, Text } from '../../components.js';
8
8
  import { Image } from '../../components/image.js';
9
9
  import { ScrollView } from '../../components/scroll-view';
10
+ import { FlatList } from '../../components/flat-list/index.js';
11
+ import { SectionList } from '../../components/section-list/index.js';
10
12
  import { createAnimatedComponent } from './create-animated-component.js';
11
13
  export { createAnimatedComponent } from './create-animated-component.js';
12
14
  // View/Text are pure host primitives; Image is the functional renderImage wrapper, and all expose
@@ -14,15 +16,15 @@ export { createAnimatedComponent } from './create-animated-component.js';
14
16
  const AnimatedView = createAnimatedComponent(View);
15
17
  const AnimatedText = createAnimatedComponent(Text);
16
18
  const AnimatedImage = createAnimatedComponent(Image);
17
- // ScrollView is wrapped behind a LAZY, memoized getter, mirroring RN's `get ScrollView()` and the
18
- // React adapter: ScrollView's module chain imports this Animated namespace back (sticky headers,
19
- // Phase 3), so a static createAnimatedComponent(ScrollView) at init could read ScrollView inside
20
- // its own TDZ. A memoized getter defers the wrap past module init, so the cycle never fires.
19
+ // LAZY, memoized getters, mirroring RN's `get ScrollView()`. Every scrolling container reaches
20
+ // scroll-view/sticky-header, which imports this Animated namespace back, so a wrap at module
21
+ // scope captures whatever the half-evaluated cycle holds at that instant. Under a bundler's ESM
22
+ // interop that is `undefined`, not a ReferenceError - the wrapper builds fine and then renders
23
+ // nothing. Deferring past module init is what keeps it from firing. FlatList and SectionList
24
+ // reach ScrollView through VirtualizedList, so they sit on the same cycle and take the same shape.
21
25
  let animatedScrollView;
22
- // FlatList / SectionList: DEFERRED. The Vue adapter has no FlatList/SectionList base component yet
23
- // (the React adapter does), so there is nothing to wrap; Animated.FlatList / Animated.SectionList
24
- // are intentionally OMITTED, not faked. They land once those base components exist (you cannot wrap
25
- // a component that does not exist). This is a named gap, not a silent drop.
26
+ let animatedFlatList;
27
+ let animatedSectionList;
26
28
  // The live, JS-driven driver namespace (real frames). RN's AnimatedImplementation.
27
29
  const liveDrivers = {
28
30
  Value: AnimatedValue,
@@ -48,10 +50,9 @@ const liveDrivers = {
48
50
  unforkEvent,
49
51
  };
50
52
  // RN swaps the WHOLE driver namespace for the mock when the host reports isDisableAnimations
51
- // (reduced motion / test env): the mock keeps the same surface but jumps each animation to its
52
- // final value synchronously, no frames. The animated COMPONENTS (View/Text/Image + the lazy
53
- // ScrollView getter) are live in both branches; only the drivers/value/operators/events half is
54
- // swapped, exactly like RN spreading `...Animated` (impl or mock) over the same component getters.
53
+ // (reduced motion/test env): the mock keeps the same surface but jumps each animation to its
54
+ // final value synchronously, no frames. The animated COMPONENTS stay live in both branches; only
55
+ // the drivers/value/operators/events half is swapped.
55
56
  const drivers = Platform.isDisableAnimations ? AnimatedMock : liveDrivers;
56
57
  export const Animated = {
57
58
  View: AnimatedView,
@@ -61,6 +62,14 @@ export const Animated = {
61
62
  animatedScrollView ??= createAnimatedComponent(ScrollView);
62
63
  return animatedScrollView;
63
64
  },
65
+ get FlatList() {
66
+ animatedFlatList ??= createAnimatedComponent(FlatList);
67
+ return animatedFlatList;
68
+ },
69
+ get SectionList() {
70
+ animatedSectionList ??= createAnimatedComponent(SectionList);
71
+ return animatedSectionList;
72
+ },
64
73
  createAnimatedComponent,
65
74
  ...drivers,
66
75
  };
@@ -2,12 +2,10 @@
2
2
  // AppRegistry.registerComponent(appKey, () => App)
3
3
  // The registry bookkeeping (sections, host-registrar bridge, headless tasks) is
4
4
  // framework-agnostic and lives in @symbiote-native/engine's createAppRegistry; this file supplies
5
- // only the one Vue-specific seam — building a runnable from a component provider via a
6
- // synthetic functional root (h() has no createElement-style prop/children spread of its
7
- // own targets, so the wrap step is a render function rather than an object merge) — so
8
- // examples/vue-sfc's own registerRunnable + mount call can move onto the same entry point
9
- // React already has.
10
- import { h } from '@vue/runtime-core';
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).
8
+ import { h, } from '@vue/runtime-core';
11
9
  import { createAppRegistry, dlog, } from '@symbiote-native/engine';
12
10
  import { mount } from '../../render.js';
13
11
  function runnableFor(componentProvider, getWrapperComponentProvider) {
@@ -15,16 +13,16 @@ function runnableFor(componentProvider, getWrapperComponentProvider) {
15
13
  dlog(`AppRegistry: mounting on rootTag ${String(appParameters.rootTag)}`);
16
14
  const rootComponent = componentProvider();
17
15
  const renderRoot = () => h(rootComponent, appParameters.initialProps);
18
- // Wrap the root when a provider is set, mirroring RN's `registerComponent` passing
19
- // `wrapperComponentProvider && wrapperComponentProvider(...)` into renderApplication
20
- // (AppRegistryImpl.js:80) — Vue's equivalent of React's createElement(Wrapper, null,
21
- // element) is a default-slot render, since Vue has no standalone element value to nest.
22
- // Read live (not at registration time) so setWrapperComponentProvider affects every
23
- // runnable's next run, not just future ones.
16
+ // Wrap the root when a provider is set, mirroring RN's `registerComponent`. Vue's equivalent
17
+ // of React's createElement(Wrapper, null, element) is a default-slot render, since Vue has
18
+ // no standalone element value to nest. Read live so setWrapperComponentProvider affects
19
+ // every runnable's next run, not just future ones.
24
20
  const wrapperComponentProvider = getWrapperComponentProvider();
25
21
  const mounted = wrapperComponentProvider === undefined
26
22
  ? renderRoot
27
- : () => h(wrapperComponentProvider(appParameters), null, { default: renderRoot });
23
+ : () => h(wrapperComponentProvider(appParameters), null, {
24
+ default: renderRoot,
25
+ });
28
26
  mount(appParameters.rootTag, mounted);
29
27
  };
30
28
  }