@symbiote-native/vue 0.4.0 → 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 +14 -5
- package/build/components/image-background.js +4 -5
- package/build/components/image.js +3 -1
- package/build/components/keyboard-avoiding-view.js +39 -14
- package/build/components/modal/index.js +5 -2
- package/build/components/pressable.d.ts +1 -1
- package/build/components/pressable.js +1 -1
- package/build/components/scroll-view/index.android.d.ts +1 -1
- package/build/components/scroll-view/index.android.js +3 -1
- 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 +33 -13
- package/build/components/scroll-view/sticky-header.js +18 -5
- package/build/components/section-list/index.js +4 -2
- package/build/components/switch/shared.js +12 -3
- package/build/components/text-input/index.js +7 -2
- 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 +42 -20
- package/build/components/virtualized-section-list/index.d.ts +5 -0
- package/build/components/virtualized-section-list/index.js +33 -4
- package/build/components.js +8 -3
- package/build/create-portal/index.d.ts +28 -0
- package/build/create-portal/index.js +42 -0
- package/build/host-instance/index.js +3 -1
- package/build/index.d.ts +8 -6
- package/build/index.js +9 -3
- package/build/modules/animated/create-animated-component.js +12 -4
- package/build/modules/animated/index.d.ts +2 -0
- package/build/modules/animated/index.js +18 -5
- package/build/modules/app-registry/index.js +4 -2
- package/build/modules/status-bar.js +4 -2
- package/build/render.d.ts +3 -1
- package/build/render.js +62 -1
- package/build/runtime-helpers/index.d.ts +44 -24
- package/build/runtime-helpers/index.js +107 -28
- package/build/utils/normalize-attrs.js +3 -1
- package/metro-vue-transformer.cjs +138 -97
- package/package.json +5 -5
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,10 @@ 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, setDeviceEventSource } 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';
|
|
63
65
|
export type { IEnterKeyHint, IInputMode, ISubmitBehavior, ITextInputSelection, IImageStatics, } from '@symbiote-native/components';
|
|
64
66
|
export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';
|
|
65
67
|
export type { IPixelRatioStatic } from '@symbiote-native/engine';
|
package/build/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
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
|
-
// Teleport
|
|
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';
|
|
6
12
|
export { createTunnel } from './create-tunnel/index.js';
|
|
7
13
|
export { View, Text } from './components.js';
|
|
8
14
|
// IHostInstance is the raw engine node (Vue host refs fall through to it; imperative methods
|
|
@@ -50,7 +56,7 @@ export { Platform, StyleSheet, processColor, setColorProcessor, dlog, isDebug, }
|
|
|
50
56
|
// Wired once by the app entry on a real host (like setColorProcessor): hands the engine
|
|
51
57
|
// RN's ViewConfig registry so third-party Fabric views auto-derive their metadata.
|
|
52
58
|
// setDeviceEventSource is the third seam of the same set and travels with them.
|
|
53
|
-
export { setNativeViewConfigSource, setDeviceEventSource } from '@symbiote-native/engine';
|
|
59
|
+
export { setNativeViewConfigSource, setDeviceEventSource, } from '@symbiote-native/engine';
|
|
54
60
|
// Pure utilities that moved to the engine (single source, both adapters re-export):
|
|
55
61
|
// PixelRatio + PanResponder, plus the color builders and the interaction scheduler.
|
|
56
62
|
export { PixelRatio, PanResponder, PlatformColor, DynamicColorIOS, InteractionManager, } from '@symbiote-native/engine';
|
|
@@ -14,7 +14,8 @@ const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
|
|
|
14
14
|
// Reads either a functional component's or a stateful defineComponent's display name, without a
|
|
15
15
|
// cast, for the wrapper's devtools name.
|
|
16
16
|
function baseName(component) {
|
|
17
|
-
if (component === null ||
|
|
17
|
+
if (component === null ||
|
|
18
|
+
(typeof component !== 'function' && typeof component !== 'object')) {
|
|
18
19
|
return 'Anonymous';
|
|
19
20
|
}
|
|
20
21
|
const display = Reflect.get(component, 'displayName');
|
|
@@ -68,7 +69,9 @@ export function createAnimatedComponent(Component) {
|
|
|
68
69
|
},
|
|
69
70
|
has: (_target, key) => {
|
|
70
71
|
const instance = instanceRef.value;
|
|
71
|
-
return instance !== null &&
|
|
72
|
+
return (instance !== null &&
|
|
73
|
+
typeof instance === 'object' &&
|
|
74
|
+
Reflect.has(instance, key));
|
|
72
75
|
},
|
|
73
76
|
}));
|
|
74
77
|
return () => {
|
|
@@ -96,10 +99,15 @@ export function createAnimatedComponent(Component) {
|
|
|
96
99
|
const passthroughStyle = readPassthroughStyle(passthrough);
|
|
97
100
|
if (passthroughStyle !== undefined) {
|
|
98
101
|
reduced.style =
|
|
99
|
-
reduced.style === undefined
|
|
102
|
+
reduced.style === undefined
|
|
103
|
+
? passthroughStyle
|
|
104
|
+
: [reduced.style, passthroughStyle];
|
|
100
105
|
}
|
|
101
106
|
reduced.ref = captureRef;
|
|
102
|
-
|
|
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);
|
|
103
111
|
};
|
|
104
112
|
},
|
|
105
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
|
};
|
|
@@ -7,6 +7,8 @@ import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Pl
|
|
|
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,12 +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
|
-
// LAZY, memoized
|
|
18
|
-
// this Animated namespace back
|
|
19
|
-
//
|
|
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.
|
|
20
25
|
let animatedScrollView;
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
let animatedFlatList;
|
|
27
|
+
let animatedSectionList;
|
|
23
28
|
// The live, JS-driven driver namespace (real frames). RN's AnimatedImplementation.
|
|
24
29
|
const liveDrivers = {
|
|
25
30
|
Value: AnimatedValue,
|
|
@@ -57,6 +62,14 @@ export const Animated = {
|
|
|
57
62
|
animatedScrollView ??= createAnimatedComponent(ScrollView);
|
|
58
63
|
return animatedScrollView;
|
|
59
64
|
},
|
|
65
|
+
get FlatList() {
|
|
66
|
+
animatedFlatList ??= createAnimatedComponent(FlatList);
|
|
67
|
+
return animatedFlatList;
|
|
68
|
+
},
|
|
69
|
+
get SectionList() {
|
|
70
|
+
animatedSectionList ??= createAnimatedComponent(SectionList);
|
|
71
|
+
return animatedSectionList;
|
|
72
|
+
},
|
|
60
73
|
createAnimatedComponent,
|
|
61
74
|
...drivers,
|
|
62
75
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// only the one Vue-specific seam - building a runnable from a component provider via a synthetic
|
|
6
6
|
// functional root (h() has no createElement-style prop/children spread of its own targets, so
|
|
7
7
|
// the wrap step is a render function rather than an object merge).
|
|
8
|
-
import { h } from '@vue/runtime-core';
|
|
8
|
+
import { h, } from '@vue/runtime-core';
|
|
9
9
|
import { createAppRegistry, dlog, } from '@symbiote-native/engine';
|
|
10
10
|
import { mount } from '../../render.js';
|
|
11
11
|
function runnableFor(componentProvider, getWrapperComponentProvider) {
|
|
@@ -20,7 +20,9 @@ function runnableFor(componentProvider, getWrapperComponentProvider) {
|
|
|
20
20
|
const wrapperComponentProvider = getWrapperComponentProvider();
|
|
21
21
|
const mounted = wrapperComponentProvider === undefined
|
|
22
22
|
? renderRoot
|
|
23
|
-
: () => h(wrapperComponentProvider(appParameters), null, {
|
|
23
|
+
: () => h(wrapperComponentProvider(appParameters), null, {
|
|
24
|
+
default: renderRoot,
|
|
25
|
+
});
|
|
24
26
|
mount(appParameters.rootTag, mounted);
|
|
25
27
|
};
|
|
26
28
|
}
|
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
// prop change, with the imperative statics attached to the component object.
|
|
7
7
|
//
|
|
8
8
|
// Inputs arrive as attrs (untyped) and run through normalizeVueAttrs before each is narrowed by a guard.
|
|
9
|
-
import { defineComponent, watchEffect } from '@vue/runtime-core';
|
|
9
|
+
import { defineComponent, watchEffect, } from '@vue/runtime-core';
|
|
10
10
|
import { applyStatusBarProps, statusBarImperative, statusBarCurrentHeight, isOpaqueColorValue, } from '@symbiote-native/engine';
|
|
11
11
|
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
12
12
|
function asBoolean(value) {
|
|
13
13
|
return typeof value === 'boolean' ? value : undefined;
|
|
14
14
|
}
|
|
15
15
|
function asBarStyle(value) {
|
|
16
|
-
return value === 'default' ||
|
|
16
|
+
return value === 'default' ||
|
|
17
|
+
value === 'light-content' ||
|
|
18
|
+
value === 'dark-content'
|
|
17
19
|
? value
|
|
18
20
|
: undefined;
|
|
19
21
|
}
|
package/build/render.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type IRootTag, type SymbioteSurface } from '@symbiote-native/engine';
|
|
2
|
-
import type { Component } from '@vue/runtime-core';
|
|
2
|
+
import type { App, Component } from '@vue/runtime-core';
|
|
3
|
+
export type IAppConfigurator = (app: App) => void;
|
|
4
|
+
export declare function setAppConfigurator(configure: IAppConfigurator | undefined): void;
|
|
3
5
|
export declare function mount(rootTag: IRootTag, rootComponent: Component): SymbioteSurface;
|
|
4
6
|
export declare function unmount(rootTag: IRootTag): void;
|
|
5
7
|
declare global {
|
package/build/render.js
CHANGED
|
@@ -1,12 +1,71 @@
|
|
|
1
1
|
// Mount a Vue app onto a Fabric surface. The native host hands us a rootTag; we create
|
|
2
2
|
// a surface for it and let the Vue renderer drive the engine, which commits into
|
|
3
3
|
// nativeFabricUIManager; RN's own renderer never in the path.
|
|
4
|
-
import { createSurface, disposeRoot, dlog, } from '@symbiote-native/engine';
|
|
4
|
+
import { createSurface, disposeRoot, dlog, reportUncaughtError, } from '@symbiote-native/engine';
|
|
5
5
|
import { createSymbioteRenderer } from './renderer/index.js';
|
|
6
6
|
// One Vue app per surface, so a surface can be torn down (unmount) or cleanly
|
|
7
7
|
// re-mounted on the same rootTag: the bridgeless host stops and restarts a surface on
|
|
8
8
|
// Fast Refresh and on lifecycle/focus changes, reusing the rootTag.
|
|
9
9
|
const apps = new Map();
|
|
10
|
+
// A stack this deep already names the screen; the rest is noise in a redbox.
|
|
11
|
+
const MAX_STACK_FRAMES = 20;
|
|
12
|
+
// SFC components carry their name on `__name` (the compiler's), plain ones on `name`; neither is
|
|
13
|
+
// on Vue's public `ConcreteComponent` type, hence the guarded read rather than a cast.
|
|
14
|
+
function displayName(type) {
|
|
15
|
+
for (const key of ['name', '__name']) {
|
|
16
|
+
const value = Reflect.get(type, key);
|
|
17
|
+
if (typeof value === 'string' && value.length > 0)
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return 'Anonymous';
|
|
21
|
+
}
|
|
22
|
+
// Vue's own component trace (getComponentTrace, used by `warn`) is internal, so the frames are
|
|
23
|
+
// walked off the public instance's parent chain. LogBox renders whatever string it is handed;
|
|
24
|
+
// React's shape is `\n in <Name>`, and matching it keeps the redbox reading the same in an
|
|
25
|
+
// app that mixes adapters' errors in one console.
|
|
26
|
+
function componentStack(instance) {
|
|
27
|
+
if (instance === null)
|
|
28
|
+
return null;
|
|
29
|
+
const frames = [];
|
|
30
|
+
let current = instance.$;
|
|
31
|
+
while (current !== null && frames.length < MAX_STACK_FRAMES) {
|
|
32
|
+
frames.push(`\n in ${displayName(current.type)}`);
|
|
33
|
+
current = current.parent;
|
|
34
|
+
}
|
|
35
|
+
return frames.length === 0 ? null : frames.join('');
|
|
36
|
+
}
|
|
37
|
+
// The app-level error seam. Without it, an unclaimed error fell through to Vue's own `logError`
|
|
38
|
+
// (runtime-core errorHandling.ts), which does neither useful thing: in a dev bundle it RE-THROWS
|
|
39
|
+
// out of `app.mount()`, so it escapes this function, aborts the AppRegistry runnable and leaves
|
|
40
|
+
// the surface half-brought-up; in a release bundle it console.errors a bare error with no origin
|
|
41
|
+
// and no component context, never touching `global.ErrorUtils`, so nothing reaches the host.
|
|
42
|
+
// Setting a handler suppresses both branches and routes to the engine's shared channel instead —
|
|
43
|
+
// the native redbox on a device, console.error off one.
|
|
44
|
+
//
|
|
45
|
+
// CAUGHT vs UNCAUGHT — the asymmetry is Vue's own, and it costs us no code. `handleError` walks
|
|
46
|
+
// the `onErrorCaptured` chain BEFORE consulting `config.errorHandler`, and a hook returning
|
|
47
|
+
// `false` stops propagation and returns early (runtime-core 3.5 errorHandling.ts), so a boundary
|
|
48
|
+
// that claims the error never reaches this function at all. That is the behaviour we want and
|
|
49
|
+
// the same split the React adapter makes by hand: writing a boundary IS the developer saying
|
|
50
|
+
// "I am handling this", and answering it with a full-screen redbox over the fallback the app
|
|
51
|
+
// just rendered contradicts what the app asked for.
|
|
52
|
+
//
|
|
53
|
+
// So do NOT "fix" this to report from inside an `onErrorCaptured` of our own — that would
|
|
54
|
+
// re-report exactly the errors a boundary claimed. A hook that returns undefined instead of
|
|
55
|
+
// `false` still lands here, and correctly: Vue treats that error as still propagating.
|
|
56
|
+
function reportToHost(error, instance, info) {
|
|
57
|
+
// `info` is Vue's own phase label ('render function', 'setup function', 'native event
|
|
58
|
+
// handler'), which is the closest thing it has to React's "no error boundary" / "recovered"
|
|
59
|
+
// qualifier. Off a native host it prefixes the log line, where there is no redbox to carry it.
|
|
60
|
+
reportUncaughtError(error, {
|
|
61
|
+
origin: `vue render (${String(info)})`,
|
|
62
|
+
componentStack: componentStack(instance),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
let configurator;
|
|
66
|
+
export function setAppConfigurator(configure) {
|
|
67
|
+
configurator = configure;
|
|
68
|
+
}
|
|
10
69
|
function teardown(rootTag) {
|
|
11
70
|
const app = apps.get(rootTag);
|
|
12
71
|
if (app === undefined)
|
|
@@ -22,6 +81,8 @@ export function mount(rootTag, rootComponent) {
|
|
|
22
81
|
const surface = createSurface(rootTag);
|
|
23
82
|
const renderer = createSymbioteRenderer(surface);
|
|
24
83
|
const app = renderer.createApp(rootComponent);
|
|
84
|
+
app.config.errorHandler = reportToHost;
|
|
85
|
+
configurator?.(app);
|
|
25
86
|
apps.set(rootTag, app);
|
|
26
87
|
// The surface IS the Vue container: a top-level mutation routes to surface.appendChild
|
|
27
88
|
// (renderer.ts), and the engine wraps surface.children in its synthetic flex root.
|
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
export * from '@vue/runtime-core';
|
|
2
2
|
import { type ObjectDirective } from '@vue/runtime-core';
|
|
3
3
|
import { type ISymbioteNode } from '@symbiote-native/engine';
|
|
4
|
+
export { Teleport, type ITeleportTarget } from '../create-portal';
|
|
4
5
|
export declare const vShow: ObjectDirective<ISymbioteNode, boolean>;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
6
|
+
type ISystemModifier = 'ctrl' | 'shift' | 'alt' | 'meta';
|
|
7
|
+
type IEventModifier = ISystemModifier | 'stop' | 'prevent' | 'self' | 'left' | 'middle' | 'right' | 'exact';
|
|
8
|
+
type IModifierGuardableEvent = {
|
|
9
|
+
stopPropagation?: () => void;
|
|
10
|
+
preventDefault?: () => void;
|
|
11
|
+
target?: unknown;
|
|
12
|
+
currentTarget?: unknown;
|
|
13
|
+
button?: number;
|
|
14
|
+
ctrlKey?: boolean;
|
|
15
|
+
shiftKey?: boolean;
|
|
16
|
+
altKey?: boolean;
|
|
17
|
+
metaKey?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type IEventHandler<TEvent> = ((event: TEvent, ...args: never[]) => unknown) & {
|
|
20
|
+
_withMods?: Record<string, IEventHandler<TEvent>>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Render-function/compiled-template equivalent of `v-on.stop`/`.prevent`/`.self`/etc. `fn` is
|
|
24
|
+
* typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
|
|
25
|
+
* (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
|
|
26
|
+
* type there either.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withModifiers<TEvent extends IModifierGuardableEvent>(fn: IEventHandler<TEvent>, modifiers: readonly IEventModifier[]): IEventHandler<TEvent>;
|
|
29
|
+
type IKeyedEvent = {
|
|
30
|
+
key?: string;
|
|
31
|
+
};
|
|
32
|
+
type IKeyedHandler<TEvent extends IKeyedEvent> = ((event: TEvent) => unknown) & {
|
|
33
|
+
_withKeys?: Record<string, (event: TEvent) => unknown>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Render-function/compiled-template equivalent of `v-on.enter`/`.esc`/etc. No-ops on any event
|
|
37
|
+
* without a `.key` field (e.g. Symbiote's Pressable `onPress`) — same as upstream Vue's own
|
|
38
|
+
* behavior on a non-keyboard DOM event, not a Symbiote-specific gap. `TextInput`'s `onKeyPress`
|
|
39
|
+
* carries `nativeEvent.key`, so that is the applicable Symbiote event shape.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withKeys<TEvent extends IKeyedEvent>(fn: IKeyedHandler<TEvent>, modifiers: readonly string[]): (event: TEvent) => unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Reads the class map a `<style module>` SFC block compiles onto the component's own options
|
|
44
|
+
* (`__cssModules`, set by @symbiote-native/css-parser's SFC compiler — see the
|
|
45
|
+
* `symbiote-sfc-style-compiler` skill). Warns and returns an empty map exactly like upstream Vue
|
|
46
|
+
* does when the current component has no such block, rather than throwing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function useCssModule(name?: string): Record<string, string>;
|
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
// Scope: this covers only Vue's own template directives (v-show, Teleport); native-element
|
|
10
10
|
// v-model is a separate, out-of-scope case.
|
|
11
11
|
export * from '@vue/runtime-core';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { getCurrentInstance, warn, } from '@vue/runtime-core';
|
|
13
|
+
import { setNativeProps, whenCommitted, } from '@symbiote-native/engine';
|
|
14
|
+
// Shadows runtime-core's own Teleport for every compiled `from 'vue'` import (see the header):
|
|
15
|
+
// same component, plus a runtime guard on `to`. Lives in ../create-portal so the adapter's portal
|
|
16
|
+
// sits where every other adapter's does; re-exported here because THIS is the module Metro
|
|
17
|
+
// rewrites `vue` to, and an `export *` alone would resolve `Teleport` to the unguarded original.
|
|
18
|
+
export { Teleport } from '../create-portal/index.js';
|
|
14
19
|
// whenCommitted, not a direct call: Vue's `mounted` hook fires synchronously during the patch
|
|
15
20
|
// pass, but this renderer coalesces the actual Fabric commit onto a microtask
|
|
16
21
|
// (surface.requestCommit()), so the node may have no committed tag yet - the same async-commit
|
|
@@ -28,31 +33,105 @@ export const vShow = {
|
|
|
28
33
|
// Drop a still-pending first-commit wait if the element unmounts before it ever lands.
|
|
29
34
|
unmounted: el => pendingShowCommits.get(el)?.(),
|
|
30
35
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
inheritAttrs: false,
|
|
42
|
-
props: {
|
|
43
|
-
to: { type: null, default: null },
|
|
44
|
-
disabled: { type: Boolean, default: false },
|
|
36
|
+
const SYSTEM_MODIFIERS = [
|
|
37
|
+
'ctrl',
|
|
38
|
+
'shift',
|
|
39
|
+
'alt',
|
|
40
|
+
'meta',
|
|
41
|
+
];
|
|
42
|
+
const MODIFIER_GUARDS = {
|
|
43
|
+
stop: event => {
|
|
44
|
+
event.stopPropagation?.();
|
|
45
|
+
return false;
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (typeof to === 'string') {
|
|
50
|
-
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").`);
|
|
51
|
-
}
|
|
52
|
-
if (to != null && !isSymbioteNode(to)) {
|
|
53
|
-
throw new Error('Teleport target is not a real host node — did you forget `.value` on a ref, or pass something symbiote never rendered?');
|
|
54
|
-
}
|
|
55
|
-
return h(VueTeleport, { to, disabled: props.disabled }, slots);
|
|
56
|
-
};
|
|
47
|
+
prevent: event => {
|
|
48
|
+
event.preventDefault?.();
|
|
49
|
+
return false;
|
|
57
50
|
},
|
|
58
|
-
|
|
51
|
+
self: event => event.target !== event.currentTarget,
|
|
52
|
+
ctrl: event => !event.ctrlKey,
|
|
53
|
+
shift: event => !event.shiftKey,
|
|
54
|
+
alt: event => !event.altKey,
|
|
55
|
+
meta: event => !event.metaKey,
|
|
56
|
+
left: event => event.button !== 0,
|
|
57
|
+
middle: event => event.button !== 1,
|
|
58
|
+
right: event => event.button !== 2,
|
|
59
|
+
exact: (event, modifiers) => SYSTEM_MODIFIERS.some(modifier => event[`${modifier}Key`] && !modifiers.includes(modifier)),
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Render-function/compiled-template equivalent of `v-on.stop`/`.prevent`/`.self`/etc. `fn` is
|
|
63
|
+
* typed as required, matching upstream Vue's own declaration — its runtime-only `!fn` guard below
|
|
64
|
+
* (for a compiler-generated call site that could pass a falsy handler) isn't reflected in the
|
|
65
|
+
* type there either.
|
|
66
|
+
*/
|
|
67
|
+
export function withModifiers(fn, modifiers) {
|
|
68
|
+
if (!fn)
|
|
69
|
+
return fn;
|
|
70
|
+
const cache = fn._withMods ?? (fn._withMods = {});
|
|
71
|
+
const cacheKey = modifiers.join('.');
|
|
72
|
+
return (cache[cacheKey] ??= ((event, ...args) => {
|
|
73
|
+
for (const modifier of modifiers) {
|
|
74
|
+
if (MODIFIER_GUARDS[modifier](event, modifiers))
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return fn(event, ...args);
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
const KEY_ALIASES = {
|
|
81
|
+
esc: 'escape',
|
|
82
|
+
space: ' ',
|
|
83
|
+
up: 'arrow-up',
|
|
84
|
+
left: 'arrow-left',
|
|
85
|
+
right: 'arrow-right',
|
|
86
|
+
down: 'arrow-down',
|
|
87
|
+
delete: 'backspace',
|
|
88
|
+
};
|
|
89
|
+
// \B (non-word-boundary), not a bare capture: without it, "Enter" hyphenates to "-enter" (a
|
|
90
|
+
// leading dash) instead of "enter", since the very first character is otherwise still a match.
|
|
91
|
+
function hyphenate(camelCase) {
|
|
92
|
+
return camelCase.replace(/\B([A-Z])/g, '-$1').toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Render-function/compiled-template equivalent of `v-on.enter`/`.esc`/etc. No-ops on any event
|
|
96
|
+
* without a `.key` field (e.g. Symbiote's Pressable `onPress`) — same as upstream Vue's own
|
|
97
|
+
* behavior on a non-keyboard DOM event, not a Symbiote-specific gap. `TextInput`'s `onKeyPress`
|
|
98
|
+
* carries `nativeEvent.key`, so that is the applicable Symbiote event shape.
|
|
99
|
+
*/
|
|
100
|
+
export function withKeys(fn, modifiers) {
|
|
101
|
+
const cache = fn._withKeys ?? (fn._withKeys = {});
|
|
102
|
+
const cacheKey = modifiers.join('.');
|
|
103
|
+
return (cache[cacheKey] ??= (event) => {
|
|
104
|
+
if (event.key == null)
|
|
105
|
+
return undefined;
|
|
106
|
+
const eventKey = hyphenate(event.key);
|
|
107
|
+
if (modifiers.some(modifier => modifier === eventKey || KEY_ALIASES[modifier] === eventKey)) {
|
|
108
|
+
return fn(event);
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reads the class map a `<style module>` SFC block compiles onto the component's own options
|
|
115
|
+
* (`__cssModules`, set by @symbiote-native/css-parser's SFC compiler — see the
|
|
116
|
+
* `symbiote-sfc-style-compiler` skill). Warns and returns an empty map exactly like upstream Vue
|
|
117
|
+
* does when the current component has no such block, rather than throwing.
|
|
118
|
+
*/
|
|
119
|
+
export function useCssModule(name = '$style') {
|
|
120
|
+
const instance = getCurrentInstance();
|
|
121
|
+
if (!instance) {
|
|
122
|
+
warn('useCssModule must be called inside setup()');
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
const modules = instance.type
|
|
126
|
+
.__cssModules;
|
|
127
|
+
if (!modules) {
|
|
128
|
+
warn('Current instance does not have CSS modules injected.');
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
const cssModule = modules[name];
|
|
132
|
+
if (!cssModule) {
|
|
133
|
+
warn(`Current instance does not have CSS module named "${name}".`);
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
return cssModule;
|
|
137
|
+
}
|
|
@@ -16,7 +16,9 @@ export function normalizeVueAttrs(attrs) {
|
|
|
16
16
|
let changed = false;
|
|
17
17
|
const out = {};
|
|
18
18
|
for (const key of Object.keys(attrs)) {
|
|
19
|
-
if (key.includes('-') &&
|
|
19
|
+
if (key.includes('-') &&
|
|
20
|
+
!key.startsWith('aria-') &&
|
|
21
|
+
!key.startsWith('data-')) {
|
|
20
22
|
out[toCamel(key)] = attrs[key];
|
|
21
23
|
changed = true;
|
|
22
24
|
}
|