@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.
- package/README.md +6 -6
- package/build/bootstrap.js +1 -1
- package/build/components/flat-list/index.js +32 -39
- package/build/components/image-background.js +8 -19
- package/build/components/image.js +12 -21
- package/build/components/keyboard-avoiding-view.js +47 -31
- package/build/components/modal/index.d.ts +4 -4
- package/build/components/modal/index.js +5 -2
- package/build/components/pressable.d.ts +1 -1
- package/build/components/pressable.js +25 -32
- package/build/components/refresh-control.js +12 -18
- package/build/components/scroll-view/index.android.d.ts +1 -1
- package/build/components/scroll-view/index.android.js +24 -34
- package/build/components/scroll-view/index.ios.d.ts +1 -1
- package/build/components/scroll-view/index.ios.js +3 -1
- package/build/components/scroll-view/shared.js +93 -123
- package/build/components/scroll-view/sticky-header.js +59 -64
- package/build/components/section-list/index.js +16 -21
- package/build/components/switch/shared.js +29 -32
- package/build/components/text-input/index.js +36 -51
- package/build/components/touchable-native-feedback.js +3 -1
- package/build/components/touchable.d.ts +7 -1
- package/build/components/touchable.js +181 -33
- package/build/components/virtualized-list/index.js +57 -18
- package/build/components/virtualized-section-list/index.d.ts +5 -0
- package/build/components/virtualized-section-list/index.js +50 -29
- package/build/components.js +9 -6
- package/build/create-portal/index.d.ts +28 -0
- package/build/create-portal/index.js +42 -0
- package/build/host-instance/index.js +5 -5
- package/build/index.d.ts +9 -6
- package/build/index.js +18 -46
- package/build/modules/animated/create-animated-component.js +46 -101
- package/build/modules/animated/index.d.ts +2 -0
- package/build/modules/animated/index.js +26 -17
- package/build/modules/app-registry/index.js +11 -13
- package/build/modules/status-bar.js +12 -17
- package/build/render.d.ts +3 -1
- package/build/render.js +62 -1
- package/build/renderer/index.js +11 -6
- package/build/runtime-helpers/index.d.ts +44 -24
- package/build/runtime-helpers/index.js +116 -47
- package/build/utils/normalize-attrs.js +3 -1
- package/metro-css-parser.cjs +9 -14
- package/metro-vue-transformer.cjs +196 -178
- package/package.json +5 -5
package/build/components.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
|
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
|
|
12
|
-
//
|
|
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)
|
|
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
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
10
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
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
|
-
//
|
|
76
|
-
//
|
|
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
|
-
|
|
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.
|
|
2
|
-
// (View
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// React → hooks: useMemo(leaf) + three useEffects + a callback ref. Vue → reactivity:
|
|
10
|
-
// a render that rebuilds the leaf each pass (like React's per-render useMemo(rest)) + a
|
|
11
|
-
// post-commit reconcile (onMounted/onUpdated, the Vue twin of a post-commit useEffect) +
|
|
12
|
-
// a function ref. The per-frame path is unchanged and NEVER goes through Vue render:
|
|
13
|
-
// value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
|
|
1
|
+
// createAnimatedComponent: the Vue twin of the React wrapper. Wraps a base component
|
|
2
|
+
// (View/Text/Image/ScrollView/any) so it accepts AnimatedNodes in its props. Framework-agnostic
|
|
3
|
+
// pieces (reduceProps/readPassthroughStyle/resolveHostNode/isAnimatedNode + the AnimatedProps
|
|
4
|
+
// leaf) live in @symbiote-native/engine, shared verbatim with React; Vue supplies only the
|
|
5
|
+
// lifecycle: a render that rebuilds the leaf each pass + a post-commit reconcile (onMounted/
|
|
6
|
+
// onUpdated) + a function ref. The per-frame path is unchanged and NEVER goes through Vue
|
|
7
|
+
// render: value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
|
|
14
8
|
import { defineComponent, h, onBeforeUnmount, onMounted, onUpdated, shallowRef, } from '@vue/runtime-core';
|
|
15
|
-
import {
|
|
9
|
+
import { createAnimatedLeafLifecycle, isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
|
|
16
10
|
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
17
11
|
// RN's prop carrying explicit (already-rasterized) values that override the animated prop in
|
|
18
12
|
// the COMMITTED props (sticky-header passthrough). Named once here so render/reconcile agree.
|
|
19
13
|
const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// the wrapper's devtools name.
|
|
14
|
+
// Reads either a functional component's or a stateful defineComponent's display name, without a
|
|
15
|
+
// cast, for the wrapper's devtools name.
|
|
23
16
|
function baseName(component) {
|
|
24
|
-
if (component === null ||
|
|
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
|
-
//
|
|
41
|
-
//
|
|
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
|
-
//
|
|
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
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// The
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
attached.__detach();
|
|
106
|
-
attached = null;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
// Function ref (held stably, so Vue doesn't re-invoke it per patch). On mount the base hands
|
|
110
|
-
// back its public instance; resolve it to the underlying host node (unwrapping a scroll-container
|
|
111
|
-
// handle via getScrollNode), record THAT for the leaf binding / event attach, and keep the
|
|
112
|
-
// ORIGINAL instance for ref forwarding. Vue calls it with null on unmount, node clears.
|
|
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
|
-
//
|
|
118
|
-
//
|
|
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 &&
|
|
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
|
|
137
|
-
//
|
|
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
|
|
92
|
+
// headless/unsupported hosts keep the JS flush path.
|
|
150
93
|
wantsNative = passthrough != null && isNativeAnimatedAvailable();
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
pendingLeaf = new AnimatedProps(rest);
|
|
154
|
-
// Reduced props are concrete (animated nodes replaced by their current values) so the first
|
|
155
|
-
// paint carries real values, exactly like React's first render.
|
|
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
|
|
158
|
-
// array
|
|
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
|
|
102
|
+
reduced.style === undefined
|
|
103
|
+
? passthroughStyle
|
|
104
|
+
: [reduced.style, passthroughStyle];
|
|
163
105
|
}
|
|
164
106
|
reduced.ref = captureRef;
|
|
165
|
-
|
|
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
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// The Animated namespace for @symbiote-native/vue. createAnimatedComponent applied to the Vue
|
|
2
|
+
// primitives gives Animated.View/Text/Image; the value graph, easing and imperative drivers come
|
|
3
|
+
// from @symbiote-native/engine (framework-agnostic, JS-driven), spread in verbatim. Both halves
|
|
4
|
+
// meet here so `Animated.timing(new Animated.Value(0), ...).start()` works against the
|
|
5
|
+
// Vue-driven engine.
|
|
6
6
|
import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
|
|
7
7
|
import { View, Text } from '../../components.js';
|
|
8
8
|
import { Image } from '../../components/image.js';
|
|
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
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
// are intentionally OMITTED, not faked. They land once those base components exist (you cannot wrap
|
|
25
|
-
// a component that does not exist). This is a named gap, not a silent drop.
|
|
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
|
|
52
|
-
// final value synchronously, no frames. The animated COMPONENTS
|
|
53
|
-
//
|
|
54
|
-
// swapped, exactly like RN spreading `...Animated` (impl or mock) over the same component getters.
|
|
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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
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
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// Read live (not at registration time) so setWrapperComponentProvider affects every
|
|
23
|
-
// runnable's next run, not just future ones.
|
|
16
|
+
// Wrap the root when a provider is set, mirroring RN's `registerComponent`. Vue's equivalent
|
|
17
|
+
// of React's createElement(Wrapper, null, element) is a default-slot render, since Vue has
|
|
18
|
+
// no standalone element value to nest. Read live so setWrapperComponentProvider affects
|
|
19
|
+
// every runnable's next run, not just future ones.
|
|
24
20
|
const wrapperComponentProvider = getWrapperComponentProvider();
|
|
25
21
|
const mounted = wrapperComponentProvider === undefined
|
|
26
22
|
? renderRoot
|
|
27
|
-
: () => h(wrapperComponentProvider(appParameters), null, {
|
|
23
|
+
: () => h(wrapperComponentProvider(appParameters), null, {
|
|
24
|
+
default: renderRoot,
|
|
25
|
+
});
|
|
28
26
|
mount(appParameters.rootTag, mounted);
|
|
29
27
|
};
|
|
30
28
|
}
|