@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
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
// StatusBar, the Vue lifecycle half. The native StatusBarManager driving (applyStatusBarProps),
|
|
2
|
-
// the imperative statics
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// object (RN's StatusBar: the value doubles as the imperative namespace, like the Vue Image).
|
|
2
|
+
// the imperative statics, and the Android bar-height constant all live in @symbiote-native/engine,
|
|
3
|
+
// shared verbatim with React; Metro selects the engine's status-bar.ios.ts/.android.ts per host,
|
|
4
|
+
// so platform divergence never reaches this file. Vue supplies only the declarative shape: a
|
|
5
|
+
// component that renders NOTHING and re-applies the props through a watchEffect on mount + every
|
|
6
|
+
// prop change, with the imperative statics attached to the component object.
|
|
8
7
|
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// (untyped) and run through normalizeVueAttrs (kebab→camel) before each is narrowed by a guard.
|
|
12
|
-
import { defineComponent, watchEffect } from '@vue/runtime-core';
|
|
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';
|
|
13
10
|
import { applyStatusBarProps, statusBarImperative, statusBarCurrentHeight, isOpaqueColorValue, } from '@symbiote-native/engine';
|
|
14
11
|
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
15
12
|
function asBoolean(value) {
|
|
16
13
|
return typeof value === 'boolean' ? value : undefined;
|
|
17
14
|
}
|
|
18
15
|
function asBarStyle(value) {
|
|
19
|
-
return value === 'default' ||
|
|
16
|
+
return value === 'default' ||
|
|
17
|
+
value === 'light-content' ||
|
|
18
|
+
value === 'dark-content'
|
|
20
19
|
? value
|
|
21
20
|
: undefined;
|
|
22
21
|
}
|
|
23
|
-
// backgroundColor is a string / int / opaque PlatformColor; any other shape is dropped. A runtime
|
|
24
|
-
// guard at the untyped-attrs boundary, not a cast.
|
|
25
22
|
function asColorValue(value) {
|
|
26
23
|
if (typeof value === 'string')
|
|
27
24
|
return value;
|
|
@@ -43,13 +40,11 @@ const StatusBarComponent = defineComponent({
|
|
|
43
40
|
name: 'StatusBar',
|
|
44
41
|
inheritAttrs: false,
|
|
45
42
|
setup(_props, { attrs: rawAttrs }) {
|
|
46
|
-
// watchEffect tracks rawAttrs (reactive), so reading it here re-applies on every prop change
|
|
47
|
-
//
|
|
48
|
-
// (a missing StatusBarManager is a no-op), so this never crashes a render.
|
|
43
|
+
// watchEffect tracks rawAttrs (reactive), so reading it here re-applies on every prop change.
|
|
44
|
+
// Resolution is lazy inside the engine (a missing StatusBarManager is a no-op).
|
|
49
45
|
watchEffect(() => {
|
|
50
46
|
applyStatusBarProps(buildProps(normalizeVueAttrs(rawAttrs)));
|
|
51
47
|
});
|
|
52
|
-
// Renders nothing: StatusBar has no Fabric view, exactly like React's `return null`.
|
|
53
48
|
return () => null;
|
|
54
49
|
},
|
|
55
50
|
});
|
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.
|
package/build/renderer/index.js
CHANGED
|
@@ -51,6 +51,13 @@ export function createSymbioteRenderer(surface) {
|
|
|
51
51
|
setElementText(el, text) {
|
|
52
52
|
if (isSurface(el))
|
|
53
53
|
return;
|
|
54
|
+
// Same invariant insert() enforces, on the other route text can reach a node: Vue calls
|
|
55
|
+
// this instead of insert() when an element's children collapse to a single string, so
|
|
56
|
+
// without the check a raw text lands under a non-Text parent - an invalid Fabric tree
|
|
57
|
+
// built silently, which is worse than the throw insert() would have given.
|
|
58
|
+
if (!el.isText) {
|
|
59
|
+
throw new Error(`Text string "${text}" must be rendered inside a <Text>`);
|
|
60
|
+
}
|
|
54
61
|
// An RCTText carries its string as a single raw-text child. Reuse a lone existing
|
|
55
62
|
// one to avoid churn; otherwise replace all children with a fresh raw-text node.
|
|
56
63
|
const [first] = el.children;
|
|
@@ -103,12 +110,10 @@ export function createSymbioteRenderer(surface) {
|
|
|
103
110
|
patchProp(el, key, _prev, next) {
|
|
104
111
|
if (isSurface(el))
|
|
105
112
|
return;
|
|
106
|
-
// routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// Vue's two independent patchProp calls lands last) is centralized in routeProp too
|
|
111
|
-
// (core/engine/src/node.ts) — every adapter gets it, not just this one.
|
|
113
|
+
// routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a
|
|
114
|
+
// View becomes a listener; onTintColor on a Switch stays a prop), shared with React. The
|
|
115
|
+
// class/style merge (explicit :style always winning, regardless of which of Vue's two
|
|
116
|
+
// independent patchProp calls lands last) is centralized there too (core/engine/src/node.ts).
|
|
112
117
|
routeProp(el, key, next);
|
|
113
118
|
surface.requestCommit();
|
|
114
119
|
},
|
|
@@ -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>;
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
// Metro rewrites every compiled `from 'vue'` import to point HERE instead of at bare
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// failing at import time or silently resolving to `undefined` (a no-op directive). This module is
|
|
9
|
-
// the interception point: it supplies our OWN implementation under the same export name, so the
|
|
10
|
-
// compiler's import keeps resolving and the directive actually does something.
|
|
2
|
+
// @vue/runtime-core (see examples/*/metro-vue-transformer.js). Most compiler-injected helpers
|
|
3
|
+
// (ref, computed, withDirectives, openBlock, ...) live in runtime-core untouched, re-exported
|
|
4
|
+
// below. But two Vue template directives compile to a runtime-helper import that ONLY exists in
|
|
5
|
+
// @vue/runtime-dom (vShow, vModelText, vModelCheckbox, vModelSelect), written directly against a
|
|
6
|
+
// real HTMLElement - we have no DOM, so this module supplies our OWN implementation under the
|
|
7
|
+
// same export name instead of leaving `v-show` a silent no-op.
|
|
11
8
|
//
|
|
12
9
|
// Scope: this covers only Vue's own template directives (v-show, Teleport); native-element
|
|
13
10
|
// v-model is a separate, out-of-scope case.
|
|
14
11
|
export * from '@vue/runtime-core';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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';
|
|
21
19
|
// whenCommitted, not a direct call: Vue's `mounted` hook fires synchronously during the patch
|
|
22
20
|
// pass, but this renderer coalesces the actual Fabric commit onto a microtask
|
|
23
|
-
// (surface.requestCommit()), so the node may have no committed tag yet
|
|
24
|
-
// race TextInput's autoFocus guards against. A bare setNativeProps
|
|
25
|
-
//
|
|
21
|
+
// (surface.requestCommit()), so the node may have no committed tag yet - the same async-commit
|
|
22
|
+
// race TextInput's autoFocus guards against. A bare setNativeProps here would silently no-op
|
|
23
|
+
// with no retry on the very first mount.
|
|
26
24
|
const pendingShowCommits = new WeakMap();
|
|
27
25
|
function applyShow(el, value) {
|
|
28
26
|
pendingShowCommits.get(el)?.();
|
|
@@ -35,34 +33,105 @@ export const vShow = {
|
|
|
35
33
|
// Drop a still-pending first-commit wait if the element unmounts before it ever lands.
|
|
36
34
|
unmounted: el => pendingShowCommits.get(el)?.(),
|
|
37
35
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// real ISymbioteNode, since insert/remove/parentNode/nextSibling are already fully generic.
|
|
49
|
-
export const Teleport = defineComponent({
|
|
50
|
-
name: 'Teleport',
|
|
51
|
-
inheritAttrs: false,
|
|
52
|
-
props: {
|
|
53
|
-
to: { type: null, default: null },
|
|
54
|
-
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;
|
|
55
46
|
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (typeof to === 'string') {
|
|
60
|
-
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").`);
|
|
61
|
-
}
|
|
62
|
-
if (to != null && !isSymbioteNode(to)) {
|
|
63
|
-
throw new Error('Teleport target is not a real host node — did you forget `.value` on a ref, or pass something symbiote never rendered?');
|
|
64
|
-
}
|
|
65
|
-
return h(VueTeleport, { to, disabled: props.disabled }, slots);
|
|
66
|
-
};
|
|
47
|
+
prevent: event => {
|
|
48
|
+
event.preventDefault?.();
|
|
49
|
+
return false;
|
|
67
50
|
},
|
|
68
|
-
|
|
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
|
}
|
package/metro-css-parser.cjs
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
// Re-exports @symbiote-native/css-parser verbatim from INSIDE this package, so a consuming app's own
|
|
2
2
|
// require('@symbiote-native/vue/metro-css-parser') resolves css-parser relative to THIS file's
|
|
3
|
-
// location (adapters/vue/node_modules, where css-parser IS a real dependency
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// directly instead, since it needs the individual parse/compile functions, not a Metro
|
|
13
|
-
// transformer object.
|
|
14
|
-
// createCssMetroTransformer is a factory, not a ready transformer — Metro's
|
|
15
|
-
// babelTransformerPath needs the actual {transform, getCacheKey} object it returns,
|
|
16
|
-
// not the css-parser package barrel.
|
|
3
|
+
// location (adapters/vue/node_modules, where css-parser IS a real dependency), not relative to
|
|
4
|
+
// the app's own node_modules — Node resolves each require() relative to the requiring FILE, so
|
|
5
|
+
// this indirection is what removes the app's extra install step, not pnpm hoisting. .cjs, not
|
|
6
|
+
// .js: this package is "type": "module", and Metro's babelTransformerPath loading expects a
|
|
7
|
+
// require()-able module. Used as vue-tsx's babelTransformerPath for standalone .css/.module.css
|
|
8
|
+
// imports; metro-vue-transformer.cjs (the .vue SFC transformer) requires @symbiote-native/css-parser
|
|
9
|
+
// directly instead, since it needs the individual parse/compile functions, not a Metro transformer
|
|
10
|
+
// object. createCssMetroTransformer is a factory — babelTransformerPath needs the actual
|
|
11
|
+
// {transform, getCacheKey} object it returns, not the css-parser package barrel.
|
|
17
12
|
module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();
|