@symbiote-native/vue 0.2.0 → 0.3.1
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/build/bootstrap.js +1 -1
- package/build/components/activity-indicator/index.android.js +1 -1
- package/build/components/activity-indicator/index.ios.js +1 -1
- package/build/components/activity-indicator/index.js +1 -1
- package/build/components/activity-indicator/shared.js +2 -2
- package/build/components/button.js +3 -3
- package/build/components/flat-list/index.js +2 -2
- package/build/components/image-background.js +2 -2
- package/build/components/image.js +2 -2
- package/build/components/input-accessory-view.js +1 -1
- package/build/components/keyboard-avoiding-view.js +1 -1
- package/build/components/modal.js +1 -1
- package/build/components/pressable.js +2 -2
- package/build/components/refresh-control.js +1 -1
- package/build/components/safe-area-view.js +1 -1
- package/build/components/scroll-view/index.android.js +1 -1
- package/build/components/scroll-view/index.ios.js +1 -1
- package/build/components/scroll-view/index.js +1 -1
- package/build/components/scroll-view/shared.js +2 -2
- package/build/components/scroll-view/sticky-header.js +1 -1
- package/build/components/section-list/index.js +2 -2
- package/build/components/switch/index.android.js +1 -1
- package/build/components/switch/index.ios.js +1 -1
- package/build/components/switch/index.js +1 -1
- package/build/components/switch/shared.js +3 -3
- package/build/components/text-input/index.js +3 -3
- package/build/components/touchable-native-feedback.js +3 -3
- package/build/components/touchable.js +3 -3
- package/build/components/virtualized-list/index.js +4 -4
- package/build/components/virtualized-section-list/index.js +2 -2
- package/build/components.js +1 -1
- package/build/index.js +32 -32
- package/build/modules/animated/create-animated-component.js +1 -1
- package/build/modules/animated/index.js +5 -5
- package/build/modules/app-registry/index.js +1 -1
- package/build/modules/status-bar.js +1 -1
- package/build/render.js +1 -1
- package/metro-css-parser.cjs +9 -4
- package/metro-vue-transformer.cjs +313 -0
- package/package.json +10 -6
package/build/bootstrap.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// header for why anything importing react-native must stay there.
|
|
5
5
|
import { AppRegistry as RNAppRegistry } from 'react-native';
|
|
6
6
|
import { bootstrapHost } from '@symbiote-native/components/bootstrap';
|
|
7
|
-
import { AppRegistry, setHostRegistrar } from './modules/app-registry';
|
|
7
|
+
import { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
|
|
8
8
|
// Mirrors real Vue's createApp(App).mount(selector) two-step idiom.
|
|
9
9
|
export function createApp(App, options = {}) {
|
|
10
10
|
return {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ProgressBar's setStyle(); without it the view throws "setStyle() not called") and
|
|
3
3
|
// `indeterminate: true`, and its default color is the theme (null). Metro picks this on an
|
|
4
4
|
// Android host; no Platform.OS read. Mirrors the React adapter's Android binding. See ADR 0020.
|
|
5
|
-
import { createActivityIndicator } from './shared';
|
|
5
|
+
import { createActivityIndicator } from './shared.js';
|
|
6
6
|
export const ActivityIndicator = createActivityIndicator({
|
|
7
7
|
// RN: `color = Platform.OS === 'ios' ? GRAY : null`. Android lets the theme color it.
|
|
8
8
|
defaultColor: null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ActivityIndicator on iOS: RCTActivityIndicatorView takes the size enum + a GRAY default
|
|
2
2
|
// color and no extra native props. Also the base (activity-indicator.ts re-exports it) for
|
|
3
3
|
// headless / web. Mirrors the React adapter's iOS binding. See ADR 0020.
|
|
4
|
-
import { createActivityIndicator } from './shared';
|
|
4
|
+
import { createActivityIndicator } from './shared.js';
|
|
5
5
|
// RN's iOS default spinner color (Libraries/.../ActivityIndicator.js GRAY).
|
|
6
6
|
const IOS_DEFAULT_COLOR = '#999999';
|
|
7
7
|
export const ActivityIndicator = createActivityIndicator({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Base / default ActivityIndicator. Re-exports the iOS build. Metro overrides this with
|
|
2
2
|
// activity-indicator.ios.ts / .android.ts on a real host; under tsx / tsc / web the host
|
|
3
3
|
// resolves here. Filename is the selector, no Platform.OS read. See ADR 0020.
|
|
4
|
-
export * from './index.ios';
|
|
4
|
+
export * from './index.ios.js';
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// indeterminate) are supplied by the .ios/.android files via `IActivityIndicatorPlatform`.
|
|
10
10
|
import { defineComponent } from '@vue/runtime-core';
|
|
11
11
|
import { renderActivityIndicator } from '@symbiote-native/components';
|
|
12
|
-
import { descriptorToVue } from '../../descriptor-to-vue';
|
|
13
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
12
|
+
import { descriptorToVue } from '../../descriptor-to-vue.js';
|
|
13
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
14
14
|
function normalizeSize(size) {
|
|
15
15
|
if (size === 'large')
|
|
16
16
|
return 'large';
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// forwards the native-only props. The Vue twin of the React adapter's Button.
|
|
5
5
|
import { defineComponent, h } from '@vue/runtime-core';
|
|
6
6
|
import { BUTTON_ACCESSIBILITY_ROLE, resolveButtonTextStyle, } from '@symbiote-native/components';
|
|
7
|
-
import { Text } from '../components';
|
|
8
|
-
import { TouchableOpacity } from './touchable';
|
|
9
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
7
|
+
import { Text } from '../components.js';
|
|
8
|
+
import { TouchableOpacity } from './touchable.js';
|
|
9
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
10
10
|
// The props Button consumes / re-maps itself; everything else (accessibility, aria, TV-focus,
|
|
11
11
|
// testID, nativeID) forwards to the TouchableOpacity. `title` becomes the Text child; onPress and
|
|
12
12
|
// disabled re-map; touchSoundDisabled maps to the pressable's android_disableSound.
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
|
|
17
17
|
import { SINGLE_COLUMN, chunkIntoRows, expandRowViewability, firstItemOfRow, lastItemOfRow, rowKeyExtractor, } from '@symbiote-native/components';
|
|
18
18
|
import { dlog, resolveClassName, } from '@symbiote-native/engine';
|
|
19
|
-
import { VirtualizedList } from '../virtualized-list';
|
|
20
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
19
|
+
import { VirtualizedList } from '../virtualized-list/index.js';
|
|
20
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
21
21
|
// VirtualizedList is itself a generic component (generic construct signature), which h()'s overloads
|
|
22
22
|
// can't resolve. Drive it through a loose functional-component handle (generic-component h()
|
|
23
23
|
// limitation — the ItemT surface is proven at the typed FlatList boundary above).
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { h } from '@vue/runtime-core';
|
|
11
11
|
import { renderImageBackground, resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
12
12
|
import { resolveClassName, } from '@symbiote-native/engine';
|
|
13
|
-
import { descriptorToVue } from '../descriptor-to-vue';
|
|
14
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
13
|
+
import { descriptorToVue } from '../descriptor-to-vue.js';
|
|
14
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
15
15
|
function asString(value) {
|
|
16
16
|
return typeof value === 'string' ? value : undefined;
|
|
17
17
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// the forward-only rest (events, blurRadius, capInsets, testID, accessibility*) is typed as the
|
|
14
14
|
// a11y intersection so resolveAccessibilityProps folds aria-* into accessibility* over it.
|
|
15
15
|
import { imageStatics, renderImage, resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
16
|
-
import { descriptorToVue } from '../descriptor-to-vue';
|
|
17
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
16
|
+
import { descriptorToVue } from '../descriptor-to-vue.js';
|
|
17
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
18
18
|
export { setImageSourceResolver } from '@symbiote-native/components';
|
|
19
19
|
function asString(value) {
|
|
20
20
|
return typeof value === 'string' ? value : undefined;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// so resolveAccessibilityProps lands aria-* onto the host node.
|
|
10
10
|
import { h } from '@vue/runtime-core';
|
|
11
11
|
import { renderInputAccessoryView, resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
12
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
12
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
13
13
|
function asString(value) {
|
|
14
14
|
return typeof value === 'string' ? value : undefined;
|
|
15
15
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { defineComponent, h, ref, onMounted, onUnmounted } from '@vue/runtime-core';
|
|
13
13
|
import { Keyboard, KEYBOARD_EVENT, dlog, } from '@symbiote-native/engine';
|
|
14
14
|
import { computeInset, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, resolveAccessibilityProps, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
|
|
15
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
15
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
16
16
|
// A style prop is an object (a style record) or an array of them; numbers/strings/null degrade
|
|
17
17
|
// to undefined (the engine flattens what it gets). A runtime guard, not a cast.
|
|
18
18
|
function isStyleProp(value) {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { defineComponent, h, ref, watch } from '@vue/runtime-core';
|
|
14
14
|
import { createInitialModalState, modalReducer, renderModal, resolveAccessibilityProps, shouldRenderModal, } from '@symbiote-native/components';
|
|
15
15
|
import { dlog, } from '@symbiote-native/engine';
|
|
16
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
16
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
17
17
|
function asBoolean(value) {
|
|
18
18
|
return typeof value === 'boolean' ? value : undefined;
|
|
19
19
|
}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
import { defineComponent, h, ref, shallowRef } from '@vue/runtime-core';
|
|
16
16
|
import { createPressHandlers, createPressRuntime, rippleProps, buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, resolveAccessibilityProps, DEFAULT_DELAY_LONG_PRESS_MS, } from '@symbiote-native/components';
|
|
17
17
|
import { measure, isSymbioteNode, } from '@symbiote-native/engine';
|
|
18
|
-
import { View } from '../components';
|
|
19
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
18
|
+
import { View } from '../components.js';
|
|
19
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
20
20
|
// The emits-options validator for the full press surface. Reused verbatim as the `emits` of every
|
|
21
21
|
// Touchable* wrapper (they expose the same events), so the runtime list lives in exactly one place.
|
|
22
22
|
export const PRESSABLE_EMITS = {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { defineComponent, h } from '@vue/runtime-core';
|
|
15
15
|
import { dlog } from '@symbiote-native/engine';
|
|
16
16
|
import { resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
17
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
17
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
18
18
|
// Copy every non-consumed attr into a bag typed as the a11y intersection (the accumulator is BUILT
|
|
19
19
|
// at that type, a real narrowing, not a cast), then fold aria-*/role into the canonical
|
|
20
20
|
// accessibility* props. Children cross via slots, never attrs. When the Android wrap re-invokes
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { defineComponent, h } from '@vue/runtime-core';
|
|
11
11
|
import { dlog } from '@symbiote-native/engine';
|
|
12
12
|
import { resolveAccessibilityProps, } from '@symbiote-native/components';
|
|
13
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
13
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
14
14
|
// Copy every attr into a bag typed as the a11y intersection (the accumulator is BUILT at that
|
|
15
15
|
// type, a real narrowing, not a cast), then fold aria-*/role into the canonical accessibility*
|
|
16
16
|
// props before they reach the host. style / onLayout / testID forward unchanged.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { h, isVNode } from '@vue/runtime-core';
|
|
16
16
|
import { dlog } from '@symbiote-native/engine';
|
|
17
17
|
import { splitLayoutProps } from '@symbiote-native/components';
|
|
18
|
-
import { createScrollView } from './shared';
|
|
18
|
+
import { createScrollView } from './shared.js';
|
|
19
19
|
function isRecord(value) {
|
|
20
20
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
21
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// childless sibling. Also the base (scroll-view.ts re-exports it) for headless / web. Metro picks
|
|
5
5
|
// this on an iOS host; no Platform.OS read. Mirrors the React adapter's iOS binding.
|
|
6
6
|
import { h } from '@vue/runtime-core';
|
|
7
|
-
import { createScrollView } from './shared';
|
|
7
|
+
import { createScrollView } from './shared.js';
|
|
8
8
|
export const ScrollView = createScrollView({
|
|
9
9
|
assemble: input => {
|
|
10
10
|
// Sibling placement: RefreshControl before content (RN iOS). The node ref stays on the scroll
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Base / default ScrollView. Re-exports the iOS build. Metro overrides this with
|
|
2
2
|
// scroll-view.ios.ts / scroll-view.android.ts on a real host; under tsx / tsc / web the host
|
|
3
3
|
// resolves here. Filename is the selector, no Platform.OS read. See ADR 0020 / 0024.
|
|
4
|
-
export * from './index.ios';
|
|
4
|
+
export * from './index.ios.js';
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
import { defineComponent, h, isVNode, markRaw, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
|
|
22
22
|
import { attachStickyScroll, buildScrollViewHandle, didContentSizeChange, forwardScrollEvent, readLayoutDimension, resolveAccessibilityProps, resolveDecelerationRate, selectScrollIntrinsics, } from '@symbiote-native/components';
|
|
23
23
|
import { AnimatedValue, dlog, event as animatedEvent, isClassNameValue, isNativeAnimatedAvailable, isSymbioteNode, resolveClassName, } from '@symbiote-native/engine';
|
|
24
|
-
import { wrapStickyHeaders } from './sticky-header';
|
|
25
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
24
|
+
import { wrapStickyHeaders } from './sticky-header.js';
|
|
25
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
26
26
|
function isHandler(value) {
|
|
27
27
|
return typeof value === 'function';
|
|
28
28
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { defineComponent, h, isVNode, ref, shallowRef, watch, watchEffect, markRaw, } from '@vue/runtime-core';
|
|
15
15
|
import { AnimatedValue, AnimatedInterpolation, Platform, dlog, } from '@symbiote-native/engine';
|
|
16
16
|
import { computeStickyInterpolation, nextStickyHeaderY, readLayoutNumber, stickyDebounceMs, STICKY_HEADER_Z_INDEX, } from '@symbiote-native/components';
|
|
17
|
-
import { Animated } from '../../modules/animated';
|
|
17
|
+
import { Animated } from '../../modules/animated/index.js';
|
|
18
18
|
function isHandler(value) {
|
|
19
19
|
return typeof value === 'function';
|
|
20
20
|
}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// synthesized events (endReached/startReached/refresh) are bridged, gated on listener presence so
|
|
13
13
|
// the inner list keeps its RefreshControl / edge-reached gating.
|
|
14
14
|
import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
|
|
15
|
-
import { VirtualizedSectionList, } from '../virtualized-section-list';
|
|
16
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
15
|
+
import { VirtualizedSectionList, } from '../virtualized-section-list/index.js';
|
|
16
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
17
17
|
// VirtualizedSectionList is a generic component (generic construct signature), which h()'s overloads
|
|
18
18
|
// can't resolve. Drive it through a loose functional-component handle (generic-component h() limit).
|
|
19
19
|
const VirtualizedSectionListHost = VirtualizedSectionList;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// trackTintColor (the color for the CURRENT value), and snaps native back via the
|
|
3
3
|
// `setNativeValue` command. Metro picks this on an Android host; no Platform.OS read.
|
|
4
4
|
// Mirrors the React adapter's Android binding. See ADR 0020.
|
|
5
|
-
import { createSwitch } from './shared';
|
|
5
|
+
import { createSwitch } from './shared.js';
|
|
6
6
|
export const Switch = createSwitch({
|
|
7
7
|
snapBackCommand: 'setNativeValue',
|
|
8
8
|
trackColorProps: (value, trackColor) => ({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Switch on iOS: the native Switch takes onTintColor (ON-track) / tintColor (OFF-track) and
|
|
2
2
|
// snaps native back via the `setValue` command. Also the base (switch.ts re-exports it) for
|
|
3
3
|
// headless / web. Mirrors the React adapter's iOS binding. See ADR 0020.
|
|
4
|
-
import { createSwitch } from './shared';
|
|
4
|
+
import { createSwitch } from './shared.js';
|
|
5
5
|
export const Switch = createSwitch({
|
|
6
6
|
snapBackCommand: 'setValue',
|
|
7
7
|
trackColorProps: (_value, trackColor) => ({
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// switch.android.ts on a real host; under tsx / tsc / web the host resolves here. Filename is
|
|
3
3
|
// the selector, no Platform.OS read. See ADR 0020. Switch is the first component to bring the
|
|
4
4
|
// state half (the reducer) into the Vue adapter: render-and-state, not render-only.
|
|
5
|
-
export * from './index.ios';
|
|
5
|
+
export * from './index.ios.js';
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
import { defineComponent, ref, shallowRef, watch } from '@vue/runtime-core';
|
|
13
13
|
import { renderSwitch, switchReducer, createInitialSwitchState, shouldSnapBack, valueFromChange, } from '@symbiote-native/components';
|
|
14
14
|
import { dispatchViewCommand, isSymbioteNode, dlog, } from '@symbiote-native/engine';
|
|
15
|
-
import { descriptorToVue } from '../../descriptor-to-vue';
|
|
16
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
17
|
-
import { resolveModelValue, emitModelUpdate } from '../../utils/model-binding';
|
|
15
|
+
import { descriptorToVue } from '../../descriptor-to-vue.js';
|
|
16
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
17
|
+
import { resolveModelValue, emitModelUpdate } from '../../utils/model-binding.js';
|
|
18
18
|
function isRecord(value) {
|
|
19
19
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
20
20
|
}
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
import { defineComponent, onBeforeUnmount, ref, shallowRef, watch } from '@vue/runtime-core';
|
|
16
16
|
import { resolveAccessibilityProps, resolveTextInputProps, renderTextInput, foldText, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from '@symbiote-native/components';
|
|
17
17
|
import { dispatchViewCommand, isSymbioteNode, dlog, blurTextInput, setInputFocused, setInputBlurred, whenCommitted, } from '@symbiote-native/engine';
|
|
18
|
-
import { descriptorToVue } from '../../descriptor-to-vue';
|
|
19
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
20
|
-
import { resolveModelValue, emitModelUpdate } from '../../utils/model-binding';
|
|
18
|
+
import { descriptorToVue } from '../../descriptor-to-vue.js';
|
|
19
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
20
|
+
import { resolveModelValue, emitModelUpdate } from '../../utils/model-binding.js';
|
|
21
21
|
function isRecord(value) {
|
|
22
22
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
23
|
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { defineComponent, h } from '@vue/runtime-core';
|
|
7
7
|
import { backgroundProps, canUseNativeForeground, rippleBackground, selectableBackground, selectableBackgroundBorderless, } from '@symbiote-native/components';
|
|
8
8
|
import { dlog } from '@symbiote-native/engine';
|
|
9
|
-
import { View } from '../components';
|
|
10
|
-
import { Pressable, emitPressableEvents, PRESSABLE_EMITS, } from './pressable';
|
|
11
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
9
|
+
import { View } from '../components.js';
|
|
10
|
+
import { Pressable, emitPressableEvents, PRESSABLE_EMITS, } from './pressable.js';
|
|
11
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
12
12
|
const HANDLED = ['background', 'useForeground'];
|
|
13
13
|
function forwardAttrs(attrs) {
|
|
14
14
|
const result = {};
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
import { defineComponent, h } from '@vue/runtime-core';
|
|
14
14
|
import { computePressOutWait, DEFAULT_ACTIVE_OPACITY, DEFAULT_HIGHLIGHT_CHILD_OPACITY, DEFAULT_MIN_PRESS_DURATION_MS, DEFAULT_UNDERLAY_COLOR, OPACITY_ACTIVE_DURATION_MS, OPACITY_INACTIVE_DURATION_MS, RESTING_OPACITY, } from '@symbiote-native/components';
|
|
15
15
|
import { dlog, } from '@symbiote-native/engine';
|
|
16
|
-
import { Pressable, emitPressableEvents, PRESSABLE_EMITS, } from './pressable';
|
|
17
|
-
import { Animated } from '../modules/animated';
|
|
18
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
16
|
+
import { Pressable, emitPressableEvents, PRESSABLE_EMITS, } from './pressable.js';
|
|
17
|
+
import { Animated } from '../modules/animated/index.js';
|
|
18
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
19
19
|
function numberOr(value, fallback) {
|
|
20
20
|
return typeof value === 'number' ? value : fallback;
|
|
21
21
|
}
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
import { computed, defineComponent, getCurrentInstance, h, isVNode, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
|
|
28
28
|
import { DEFAULT_END_REACHED_THRESHOLD, DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_START_REACHED_THRESHOLD, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, NO_CONTENT_LENGTH_SENT, averageMeasuredLength, buildListPlan, buildOffsets, buildViewabilityPairs, computeEndReached, computeStartReached, computeViewableSet, computeWindow, diffViewable, highestMeasuredIndex, maxMinimumViewTime, offsetForIndex, readLayoutLength, readScrollOffset, throttleWindow, } from '@symbiote-native/components';
|
|
29
29
|
import { dlog, } from '@symbiote-native/engine';
|
|
30
|
-
import { ScrollView } from '../scroll-view';
|
|
31
|
-
import { RefreshControl } from '../refresh-control';
|
|
32
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
33
|
-
import { componentFromSlot } from '../../utils/slots-to-render-props';
|
|
30
|
+
import { ScrollView } from '../scroll-view/index.js';
|
|
31
|
+
import { RefreshControl } from '../refresh-control.js';
|
|
32
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
33
|
+
import { componentFromSlot } from '../../utils/slots-to-render-props.js';
|
|
34
34
|
// The typed inputs VirtualizedList reads off `props`; everything else (raw scroll, keyboard*,
|
|
35
35
|
// accessibility, testID, …) falls through $attrs onto the inner ScrollView. Listed for the runtime
|
|
36
36
|
// `props` declaration (keyof can't derive it: the index signature widens keyof to `string`). The
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
import { defineComponent, getCurrentInstance, h, shallowRef, } from '@vue/runtime-core';
|
|
19
19
|
import { flattenSections, scrollLocationToFlatIndex, sectionEntryKey, unwrapEntryItem, } from '@symbiote-native/components';
|
|
20
20
|
import { Platform, dlog, } from '@symbiote-native/engine';
|
|
21
|
-
import { VirtualizedList } from '../virtualized-list';
|
|
22
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
21
|
+
import { VirtualizedList } from '../virtualized-list/index.js';
|
|
22
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
23
23
|
// VirtualizedList is a generic component (generic construct signature), which h()'s overloads can't
|
|
24
24
|
// resolve. Drive it through a loose functional-component handle (generic-component h() limitation).
|
|
25
25
|
const VirtualizedListHost = VirtualizedList;
|
package/build/components.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
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 { normalizeVueAttrs } from './utils/normalize-attrs';
|
|
12
|
+
import { normalizeVueAttrs } from './utils/normalize-attrs.js';
|
|
13
13
|
function hostComponent(intrinsic, name) {
|
|
14
14
|
// A functional component's ctx is Omit<SetupContext, 'expose'> (no instance to expose); let the
|
|
15
15
|
// FunctionalComponent target infer the param types rather than annotate SetupContext.
|
package/build/index.js
CHANGED
|
@@ -1,83 +1,83 @@
|
|
|
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';
|
|
4
|
+
export { mount, unmount } from './render.js';
|
|
5
5
|
// createTunnel: cross-surface content sharing (Teleport stays same-surface-only by design —
|
|
6
6
|
// see create-tunnel.ts and the vue-adapter-directives skill for why).
|
|
7
|
-
export { createTunnel } from './create-tunnel';
|
|
8
|
-
export { View, Text } from './components';
|
|
7
|
+
export { createTunnel } from './create-tunnel.js';
|
|
8
|
+
export { View, Text } from './components.js';
|
|
9
9
|
// findNodeHandle: RN's ref -> native reactTag lookup, the Vue twin of the React adapter's
|
|
10
10
|
// host-instance resolution. IHostInstance is the raw engine node (Vue host refs fall
|
|
11
11
|
// through to it; imperative methods live on each component's expose() handle).
|
|
12
|
-
export { findNodeHandle } from './host-instance';
|
|
12
|
+
export { findNodeHandle } from './host-instance/index.js';
|
|
13
13
|
// Image. Full parity with React: source/src/srcSet resolution, the width/height → style fold,
|
|
14
14
|
// alt → accessibility, and the Image statics (getSize / prefetch / queryCache / …) are shared
|
|
15
15
|
// verbatim from @symbiote-native/components via renderImage; Vue supplies only the functional bridge.
|
|
16
|
-
export { Image, setImageSourceResolver } from './components/image';
|
|
16
|
+
export { Image, setImageSourceResolver } from './components/image.js';
|
|
17
17
|
// First component off the shared @symbiote-native/components layer: render-only, drives the
|
|
18
18
|
// agnostic renderActivityIndicator through descriptorToVue. Proof the layer is reusable:
|
|
19
19
|
// the render fn is shared verbatim with React; Vue supplies only the bridge.
|
|
20
|
-
export { ActivityIndicator } from './components/activity-indicator';
|
|
20
|
+
export { ActivityIndicator } from './components/activity-indicator/index.js';
|
|
21
21
|
// First component to bring the state half into Vue (the lastNativeReport reducer + the
|
|
22
22
|
// snap-back watch); render shared verbatim with React, Vue supplies the reactive lifecycle.
|
|
23
|
-
export { Switch } from './components/switch';
|
|
23
|
+
export { Switch } from './components/switch/index.js';
|
|
24
24
|
// ScrollView. Full parity (ADR 0024): vertical + horizontal, every pass-through prop, the
|
|
25
25
|
// synthesized onContentSizeChange, the imperative handle via expose() + shallowRef, RefreshControl
|
|
26
26
|
// (iOS sibling / Android wrap, Phase 2), and sticky headers (Phase 3: the scroll AnimatedValue,
|
|
27
27
|
// the headerLayoutYs cross-talk, and the per-header Animated.View wrap). The pure math (intrinsics,
|
|
28
28
|
// decelerationRate, content-size dedupe, the handle, the sticky interpolation) is shared verbatim
|
|
29
29
|
// with React from @symbiote-native/components.
|
|
30
|
-
export { ScrollView } from './components/scroll-view';
|
|
30
|
+
export { ScrollView } from './components/scroll-view/index.js';
|
|
31
31
|
// Pressable family. Full parity with React (the 3-layer split): the press state machine + render
|
|
32
32
|
// decisions are shared in @symbiote-native/components; Vue supplies the reactivity + descriptor bridge.
|
|
33
|
-
export { Pressable } from './components/pressable';
|
|
34
|
-
export { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, } from './components/touchable';
|
|
35
|
-
export { TouchableNativeFeedback } from './components/touchable-native-feedback';
|
|
36
|
-
export { Button } from './components/button';
|
|
33
|
+
export { Pressable } from './components/pressable.js';
|
|
34
|
+
export { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, } from './components/touchable.js';
|
|
35
|
+
export { TouchableNativeFeedback } from './components/touchable-native-feedback.js';
|
|
36
|
+
export { Button } from './components/button.js';
|
|
37
37
|
// TextInput. Full parity with React (the 3-layer split): the value/selection folds + the
|
|
38
38
|
// controlled-write predicate live in @symbiote-native/components; Vue supplies the reactivity
|
|
39
39
|
// (shallowRef host node, post-flush watch) + the imperative handle via expose().
|
|
40
|
-
export { TextInput } from './components/text-input';
|
|
40
|
+
export { TextInput } from './components/text-input/index.js';
|
|
41
41
|
// VirtualizedList family. Full parity with React: the windowing math (visible range, cell
|
|
42
42
|
// keys, viewability token diffing, edge-reached, the list plan) is shared in
|
|
43
43
|
// @symbiote-native/components; Vue supplies the reactive lifecycle + the per-cell element.
|
|
44
|
-
export { VirtualizedList } from './components/virtualized-list';
|
|
45
|
-
export { FlatList } from './components/flat-list';
|
|
46
|
-
export { VirtualizedSectionList } from './components/virtualized-section-list';
|
|
47
|
-
export { SectionList } from './components/section-list';
|
|
44
|
+
export { VirtualizedList } from './components/virtualized-list/index.js';
|
|
45
|
+
export { FlatList } from './components/flat-list/index.js';
|
|
46
|
+
export { VirtualizedSectionList } from './components/virtualized-section-list/index.js';
|
|
47
|
+
export { SectionList } from './components/section-list/index.js';
|
|
48
48
|
// Phase 2 (ADR 0024): SafeAreaView + RefreshControl, wired into ScrollView with the iOS-sibling /
|
|
49
49
|
// Android-wrap platform split. RefreshControl hosts the wrapped scroll view via its default slot.
|
|
50
|
-
export { SafeAreaView } from './components/safe-area-view';
|
|
51
|
-
export { RefreshControl } from './components/refresh-control';
|
|
52
|
-
export { descriptorToVue } from './descriptor-to-vue';
|
|
50
|
+
export { SafeAreaView } from './components/safe-area-view.js';
|
|
51
|
+
export { RefreshControl } from './components/refresh-control.js';
|
|
52
|
+
export { descriptorToVue } from './descriptor-to-vue.js';
|
|
53
53
|
// normalizeVueAttrs: the kebab→camel attr fold every component applies at entry. Exported so an
|
|
54
54
|
// external wrapper package (e.g. @symbiote-native/slider/vue over a third-party native view) can fold
|
|
55
55
|
// its incoming attrs through the SAME transform rather than reimplementing it.
|
|
56
|
-
export { normalizeVueAttrs } from './utils/normalize-attrs';
|
|
56
|
+
export { normalizeVueAttrs } from './utils/normalize-attrs.js';
|
|
57
57
|
// resolveModelValue/emitModelUpdate: the v-model (Rule 6, vue-adapter-events skill) helper every
|
|
58
58
|
// controlled-value component uses. Exported for the same external-package reason as
|
|
59
59
|
// normalizeVueAttrs above (e.g. @symbiote-native/slider/vue).
|
|
60
|
-
export { resolveModelValue, emitModelUpdate } from './utils/model-binding';
|
|
61
|
-
export { createSymbioteRenderer } from './renderer';
|
|
60
|
+
export { resolveModelValue, emitModelUpdate } from './utils/model-binding.js';
|
|
61
|
+
export { createSymbioteRenderer } from './renderer.js';
|
|
62
62
|
// Animated (ADR 0024 Phase 3a): Animated.View/Text/Image + the lazy Animated.ScrollView over the
|
|
63
63
|
// Vue primitives, with the value graph / easing / drivers spread from @symbiote-native/engine. The wrap
|
|
64
64
|
// mechanism (createAnimatedComponent) is the Vue twin of React's; the pure leaves live in the
|
|
65
65
|
// engine.
|
|
66
|
-
export { Animated, createAnimatedComponent } from './modules/animated';
|
|
66
|
+
export { Animated, createAnimatedComponent } from './modules/animated/index.js';
|
|
67
67
|
// Visual components off the shared @symbiote-native/components layer (wave: simple-visual).
|
|
68
|
-
export { ImageBackground } from './components/image-background';
|
|
69
|
-
export { InputAccessoryView } from './components/input-accessory-view';
|
|
70
|
-
export { Modal } from './components/modal';
|
|
68
|
+
export { ImageBackground } from './components/image-background.js';
|
|
69
|
+
export { InputAccessoryView } from './components/input-accessory-view.js';
|
|
70
|
+
export { Modal } from './components/modal.js';
|
|
71
71
|
// KeyboardAvoidingView: full parity via the shared inset math; Vue owns the Keyboard subscription.
|
|
72
|
-
export { KeyboardAvoidingView } from './components/keyboard-avoiding-view';
|
|
72
|
+
export { KeyboardAvoidingView } from './components/keyboard-avoiding-view.js';
|
|
73
73
|
// StatusBar: declarative component + the shared imperative API.
|
|
74
|
-
export { StatusBar } from './modules/status-bar';
|
|
74
|
+
export { StatusBar } from './modules/status-bar.js';
|
|
75
75
|
// AppRegistry: RN's app entry point over `mount`. setHostRegistrar wires RN's own
|
|
76
76
|
// registrar so the native Fabric host finds our runnable by app key.
|
|
77
|
-
export { AppRegistry, setHostRegistrar } from './modules/app-registry';
|
|
77
|
+
export { AppRegistry, setHostRegistrar } from './modules/app-registry/index.js';
|
|
78
78
|
// Vue composables over the core device-state modules.
|
|
79
|
-
export { useColorScheme } from './composables/use-color-scheme';
|
|
80
|
-
export { useWindowDimensions } from './composables/use-window-dimensions';
|
|
79
|
+
export { useColorScheme } from './composables/use-color-scheme.js';
|
|
80
|
+
export { useWindowDimensions } from './composables/use-window-dimensions.js';
|
|
81
81
|
// Imperative runtime modules: the SAME module both adapters share, re-exported from @symbiote-native/engine.
|
|
82
82
|
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';
|
|
83
83
|
// Re-export the framework-agnostic engine surface (pure utilities + diagnostics).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// value.setValue / animation -> flushValue -> AnimatedProps.update() -> setNativeProps(node).
|
|
14
14
|
import { defineComponent, h, onBeforeUnmount, onMounted, onUpdated, shallowRef, } from '@vue/runtime-core';
|
|
15
15
|
import { AnimatedProps, attachNativeEventHandler, isNativeAnimatedAvailable, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
|
|
16
|
-
import { normalizeVueAttrs } from '../../utils/normalize-attrs';
|
|
16
|
+
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
17
17
|
// RN's prop carrying explicit (already-rasterized) values that override the animated prop in
|
|
18
18
|
// the COMMITTED props (sticky-header passthrough). Named once here so render/reconcile agree.
|
|
19
19
|
const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// JS-driven, ADR 0016), spread in verbatim. Both halves meet here so the familiar surface,
|
|
5
5
|
// `Animated.timing(new Animated.Value(0), …).start()`, works against the Vue-driven engine.
|
|
6
6
|
import { AnimatedValue, AnimatedValueXY, AnimatedColor, AnimatedMock, Easing, Platform, timing, spring, decay, parallel, sequence, stagger, loop, delay, add, subtract, multiply, divide, modulo, diffClamp, event, forkEvent, unforkEvent, } from '@symbiote-native/engine';
|
|
7
|
-
import { View, Text } from '../../components';
|
|
8
|
-
import { Image } from '../../components/image';
|
|
9
|
-
import { ScrollView } from '../../components/scroll-view';
|
|
10
|
-
import { createAnimatedComponent } from './create-animated-component';
|
|
11
|
-
export { createAnimatedComponent } from './create-animated-component';
|
|
7
|
+
import { View, Text } from '../../components.js';
|
|
8
|
+
import { Image } from '../../components/image.js';
|
|
9
|
+
import { ScrollView } from '../../components/scroll-view/index.js';
|
|
10
|
+
import { createAnimatedComponent } from './create-animated-component.js';
|
|
11
|
+
export { createAnimatedComponent } from './create-animated-component.js';
|
|
12
12
|
// View/Text are pure host primitives; Image is the functional renderImage wrapper, and all expose
|
|
13
13
|
// their host node via ref fall-through, so wrap them eagerly.
|
|
14
14
|
const AnimatedView = createAnimatedComponent(View);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// React already has.
|
|
10
10
|
import { h } from '@vue/runtime-core';
|
|
11
11
|
import { createAppRegistry, dlog, } from '@symbiote-native/engine';
|
|
12
|
-
import { mount } from '../../render';
|
|
12
|
+
import { mount } from '../../render.js';
|
|
13
13
|
function runnableFor(componentProvider, getWrapperComponentProvider) {
|
|
14
14
|
return appParameters => {
|
|
15
15
|
dlog(`AppRegistry: mounting on rootTag ${String(appParameters.rootTag)}`);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// (untyped) and run through normalizeVueAttrs (kebab→camel) before each is narrowed by a guard.
|
|
12
12
|
import { defineComponent, watchEffect } from '@vue/runtime-core';
|
|
13
13
|
import { applyStatusBarProps, statusBarImperative, statusBarCurrentHeight, isOpaqueColorValue, } from '@symbiote-native/engine';
|
|
14
|
-
import { normalizeVueAttrs } from '../utils/normalize-attrs';
|
|
14
|
+
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
15
15
|
function asBoolean(value) {
|
|
16
16
|
return typeof value === 'boolean' ? value : undefined;
|
|
17
17
|
}
|
package/build/render.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
4
|
import { createSurface, disposeRoot, dlog, } from '@symbiote-native/engine';
|
|
5
|
-
import { createSymbioteRenderer } from './renderer';
|
|
5
|
+
import { createSymbioteRenderer } from './renderer.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.
|
package/metro-css-parser.cjs
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
// requiring FILE's own location, so this indirection is what actually removes the extra install
|
|
7
7
|
// step, not pnpm hoisting (which does not propagate this transitively across workspace packages
|
|
8
8
|
// the way a flat classic node_modules would). .cjs, not .js: this package is "type": "module",
|
|
9
|
-
// and Metro's babelTransformerPath loading expects a require()-able module. Used
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
9
|
+
// and Metro's babelTransformerPath loading expects a require()-able module. Used as
|
|
10
|
+
// vue-tsx's babelTransformerPath for standalone .css/.module.css file imports —
|
|
11
|
+
// metro-vue-transformer.cjs (the .vue SFC transformer) requires @symbiote-native/css-parser
|
|
12
|
+
// directly instead, since it needs the individual parse/compile functions, not a Metro
|
|
13
|
+
// transformer object. See the symbiote-sfc-style-compiler skill.
|
|
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.
|
|
17
|
+
module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Metro babel transformer that teaches the bundler to read Vue SFCs (.vue). Metro has no
|
|
2
|
+
// Vue plugin (unplugin-vue ships vite/webpack/esbuild/rollup adapters, not Metro), so we do
|
|
3
|
+
// the single-pass compile here: parse the SFC, compile <script setup> + <template> into one
|
|
4
|
+
// component module, then hand the JS to RN's own babel transformer. This is the Metro twin
|
|
5
|
+
// of what @vitejs/plugin-vue does for Vite; the 'vue'→runtime-core rewrite is wolf-tui's
|
|
6
|
+
// pattern (a custom, non-DOM renderer needs the compiler helpers from @vue/runtime-core,
|
|
7
|
+
// not from vue/runtime-dom).
|
|
8
|
+
//
|
|
9
|
+
// Retargeted at @symbiote-native/vue/runtime-helpers rather than bare @vue/runtime-core: that shim
|
|
10
|
+
// re-exports runtime-core verbatim PLUS supplies our own `vShow` (compiled v-show imports it by
|
|
11
|
+
// name, and only @vue/runtime-dom's DOM-based version exists otherwise — see the
|
|
12
|
+
// vue-adapter-directives skill).
|
|
13
|
+
//
|
|
14
|
+
// Ships as a package-level export (`@symbiote-native/vue/metro-vue-transformer`) rather than living
|
|
15
|
+
// in each consuming app: a consumer's own metro.config.js just points babelTransformerPath at it —
|
|
16
|
+
// see the symbiote-sfc-style-compiler skill.
|
|
17
|
+
|
|
18
|
+
const { parse, compileScript } = require('@vue/compiler-sfc');
|
|
19
|
+
const { createCompoundExpression } = require('@vue/compiler-core');
|
|
20
|
+
// @symbiote-native/css-parser is a real dependency of THIS package, so requiring it directly here
|
|
21
|
+
// (rather than through the ./metro-css-parser public subpath, which exists for CONSUMERS) resolves
|
|
22
|
+
// straight from this package's own node_modules under pnpm.
|
|
23
|
+
const {
|
|
24
|
+
compile: compilePreprocessor,
|
|
25
|
+
compileCssFile,
|
|
26
|
+
globalClassNamesIn,
|
|
27
|
+
hashFilePath,
|
|
28
|
+
isStyleFile,
|
|
29
|
+
kebabToCamel,
|
|
30
|
+
parseCSS,
|
|
31
|
+
resolveUpstreamTransformer,
|
|
32
|
+
} = require('@symbiote-native/css-parser');
|
|
33
|
+
|
|
34
|
+
const upstreamTransformer = resolveUpstreamTransformer();
|
|
35
|
+
|
|
36
|
+
// Rewrites a Vue template AST so every `class`/`:class` binding on an element resolves
|
|
37
|
+
// against this file's scoped class names at the compiled call site, via @symbiote-native/engine's
|
|
38
|
+
// scopeClassName(value, localNames, scopeId) — see the symbiote-sfc-style-compiler skill for
|
|
39
|
+
// the full design (why AST-level, not a raw-text regex: Vue itself merges a static `class=`
|
|
40
|
+
// and a dynamic `:class=` on the same element into ONE codegen entry, and text substitution
|
|
41
|
+
// can't reproduce that merge safely; letting Vue's own transformElement do the merge on our
|
|
42
|
+
// already-rewritten nodes reuses that logic instead of reimplementing it).
|
|
43
|
+
//
|
|
44
|
+
// A static class="foo bar" attribute is a plain AttributeNode (prop.type === 6): its value is
|
|
45
|
+
// resolved to the final string directly here, at compile time — no runtime call needed for the
|
|
46
|
+
// purely-static case, since every token is already known. Each token is normalized kebab->camel
|
|
47
|
+
// FIRST (kebabToCamel) — a template may write class="section-label" (idiomatic CSS) instead of
|
|
48
|
+
// class="sectionLabel"; the css-parser always registers the CAMEL form, and localNames (built
|
|
49
|
+
// from that same registration) is camelCase-keyed, so the scoping check below only recognizes a
|
|
50
|
+
// token once it is in that form. The emitted string is always camelCase, suffixed or not.
|
|
51
|
+
//
|
|
52
|
+
// A dynamic :class="expr" is a `bind` DirectiveNode (prop.type === 7) targeting the `class`
|
|
53
|
+
// arg. Vue's own transformExpression has ALREADY run by the time our transform sees it (node
|
|
54
|
+
// transforms are appended after the built-in preset, per @vue/compiler-core's TransformOptions
|
|
55
|
+
// merge order), so prop.exp is usually a COMPOUND_EXPRESSION (type 8, a `children` array mixing
|
|
56
|
+
// literal source chunks with resolved identifier nodes), not a plain string — a bare identifier
|
|
57
|
+
// binding (`:class="dynamicClass"`) is the one case that stays a SIMPLE_EXPRESSION (type 4).
|
|
58
|
+
// Either way, `createCompoundExpression` wraps the ORIGINAL exp node (whichever shape it is) as
|
|
59
|
+
// a single child inside `scopeClassName(<original>, __localScopedClassNames, __scopeId)`, so
|
|
60
|
+
// codegen emits the call with the original expression source reproduced unchanged inside it —
|
|
61
|
+
// this needs no per-shape branching, and correctly defers even a fully opaque runtime value
|
|
62
|
+
// (`:class="dynamicClass"`) to scopeClassName's own runtime token-matching, so there is no
|
|
63
|
+
// unresolved gap for dynamic scoped classes.
|
|
64
|
+
function createScopeClassNodeTransform(localNames, scopeId) {
|
|
65
|
+
return function scopeClassNodeTransform(node) {
|
|
66
|
+
if (node.type !== 1 /* NodeTypes.ELEMENT */) return;
|
|
67
|
+
|
|
68
|
+
for (const prop of node.props) {
|
|
69
|
+
if (prop.type === 6 /* NodeTypes.ATTRIBUTE */ && prop.name === 'class' && prop.value) {
|
|
70
|
+
prop.value.content = prop.value.content
|
|
71
|
+
.split(/\s+/)
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.map((token) => {
|
|
74
|
+
const camelToken = kebabToCamel(token);
|
|
75
|
+
return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
|
|
76
|
+
})
|
|
77
|
+
.join(' ');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
prop.type === 7 /* NodeTypes.DIRECTIVE */ &&
|
|
83
|
+
prop.name === 'bind' &&
|
|
84
|
+
prop.arg &&
|
|
85
|
+
prop.arg.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
|
|
86
|
+
prop.arg.content === 'class' &&
|
|
87
|
+
prop.exp
|
|
88
|
+
) {
|
|
89
|
+
prop.exp = createCompoundExpression(
|
|
90
|
+
['__scopeClass(', prop.exp, ', __localScopedClassNames, __scopeId)'],
|
|
91
|
+
prop.exp.loc,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A short, stable id per file, used as the SFC scope id (compileScript wants one regardless of
|
|
99
|
+
// whether the file has scoped styles) — Vue's own `data-v-hash` naming convention, built on
|
|
100
|
+
// @symbiote-native/css-parser's shared hashFilePath so the algorithm isn't duplicated against the
|
|
101
|
+
// standalone .module.css compiler's identical need (metro-css-module.ts).
|
|
102
|
+
function scopeIdFor(filename) {
|
|
103
|
+
return 'data-v-' + hashFilePath(filename);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// An SFC style block's `lang` attribute names a preprocessor language directly (`lang="scss"`),
|
|
107
|
+
// unlike a standalone file, which is identified by its extension — so this is its own small
|
|
108
|
+
// lookup rather than reusing detectLanguage(), which is extension-keyed.
|
|
109
|
+
const SFC_STYLE_LANG_TO_PREPROCESSOR = new Map([
|
|
110
|
+
['scss', 'scss'],
|
|
111
|
+
['sass', 'scss'],
|
|
112
|
+
['less', 'less'],
|
|
113
|
+
['stylus', 'stylus'],
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
// Reduces one <style> block down to plain CSS text. A lang-less or lang="css" block passes
|
|
117
|
+
// through unchanged, exactly as before. Anything outside the four recognized preprocessor langs
|
|
118
|
+
// (a typo, or a genuinely unsupported lang) still throws, same message as before this feature
|
|
119
|
+
// existed.
|
|
120
|
+
async function compileStyleBlockContent(style, filename) {
|
|
121
|
+
if (style.lang == null || style.lang === 'css') return style.content;
|
|
122
|
+
|
|
123
|
+
const preprocessorLang = SFC_STYLE_LANG_TO_PREPROCESSOR.get(style.lang);
|
|
124
|
+
if (!preprocessorLang) {
|
|
125
|
+
throw new Error(`SFC style lang="${style.lang}" not supported yet — plain CSS only`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sass' `.sass` indented syntax and `.scss` syntax share one compiler entry point that picks
|
|
129
|
+
// between them off the file extension (see preprocessors.ts's compileScss) — an inline SFC
|
|
130
|
+
// style block has no file of its own, so a synthetic `.sass`-suffixed path is the only way to
|
|
131
|
+
// select the indented syntax. Every other preprocessor only uses the path for relative-import
|
|
132
|
+
// resolution (dirname), where the real .vue file's own path is correct as-is.
|
|
133
|
+
const syntheticPath = style.lang === 'sass' ? `${filename}.sass` : filename;
|
|
134
|
+
return compilePreprocessor(style.content, preprocessorLang, syntheticPath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function compileSfc(src, filename) {
|
|
138
|
+
const { descriptor, errors } = parse(src, { filename });
|
|
139
|
+
if (errors && errors.length > 0) {
|
|
140
|
+
throw new Error(`Vue SFC parse error in ${filename}:\n${errors.map(String).join('\n')}`);
|
|
141
|
+
}
|
|
142
|
+
if (descriptor.scriptSetup == null && descriptor.script == null) {
|
|
143
|
+
throw new Error(`Vue SFC ${filename} has no <script> / <script setup> block`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const scopeId = scopeIdFor(filename);
|
|
147
|
+
|
|
148
|
+
// `descriptor.styles` is already parsed by @vue/compiler-sfc itself (one entry per <style>
|
|
149
|
+
// block, `.content` pre-trimmed, `.scoped` already a plain boolean flag), so there's no need
|
|
150
|
+
// to re-extract style blocks with a regex the way wolf-tui's Vite plugin does.
|
|
151
|
+
//
|
|
152
|
+
// A scoped block's classes get their key SUFFIXED with this file's scopeId before
|
|
153
|
+
// registration (`card` -> `card__data-v-xxxxxxxx`), so two components can each define their
|
|
154
|
+
// own `.card` without colliding in the shared global registry — mirroring what Vue's own
|
|
155
|
+
// `data-v-hash` attribute does for DOM targets, just as a name suffix instead of an attribute
|
|
156
|
+
// selector (we have neither DOM nor attribute-selector matching). An unscoped block's classes
|
|
157
|
+
// register exactly as before: no suffix, globally shared. `:global(...)` selectors inside a
|
|
158
|
+
// scoped block are the one exception — @symbiote-native/css-parser already unwraps them to their
|
|
159
|
+
// plain class name (`:global(.reset)` parses like `.reset`), so `globalClassNamesIn` re-scans
|
|
160
|
+
// the block's own raw text to find which specific keys should be exempted from suffixing.
|
|
161
|
+
//
|
|
162
|
+
// Cascade multiple blocks last-block-wins, same as CSS — this holds across scoped/unscoped
|
|
163
|
+
// blocks too: only each block's OWN css-parser output is scoped independently, then merged.
|
|
164
|
+
//
|
|
165
|
+
// <style module> (CSS Modules) reuses this exact suffixing machinery instead of a separate
|
|
166
|
+
// pipeline: `.card` still goes through parseCSS unchanged and still registers via the same
|
|
167
|
+
// registerStyles() call, just under a suffixed key — the only new output is a plain name->
|
|
168
|
+
// scopedName object (`$style` by default, or the block's `module="name"` value) emitted as a
|
|
169
|
+
// preamble const, so `:class="$style.card"` passes the already-scoped string straight to
|
|
170
|
+
// resolveClassName's existing exact-match path (no registry changes needed, see the
|
|
171
|
+
// symbiote-sfc-style-compiler skill). Unlike `scoped`, a module block's classes are NEVER
|
|
172
|
+
// auto-applied to a literal class="..." attribute — CSS Modules is opt-in per usage via
|
|
173
|
+
// `$style.x`, so module classes are kept out of `localScopedNames` (the nodeTransform only
|
|
174
|
+
// rewrites literal class strings for `scoped` blocks). The registry key gets an extra
|
|
175
|
+
// `module` tag (`card__module__<scopeId>`, vs scoped's plain `card__<scopeId>`) so a file
|
|
176
|
+
// that happens to mix `<style scoped>` and `<style module>` with the same class name can't
|
|
177
|
+
// collide in the shared registry.
|
|
178
|
+
const styles = {};
|
|
179
|
+
const localScopedNames = new Set();
|
|
180
|
+
const cssModuleBindings = new Map();
|
|
181
|
+
|
|
182
|
+
for (const style of descriptor.styles) {
|
|
183
|
+
// Reduces a preprocessor block down to plain CSS text BEFORE any of the scoping logic
|
|
184
|
+
// below runs — that logic is entirely language-agnostic, it only ever sees parseCSS's
|
|
185
|
+
// plain-CSS output, same as it always did for a lang-less/lang="css" block. Anything other
|
|
186
|
+
// than the four recognized preprocessor langs (a typo, or a genuinely unsupported lang)
|
|
187
|
+
// still throws, unchanged from before.
|
|
188
|
+
const content = await compileStyleBlockContent(style, filename);
|
|
189
|
+
const parsed = parseCSS(content, { filename });
|
|
190
|
+
|
|
191
|
+
if (style.module) {
|
|
192
|
+
const bindingName = typeof style.module === 'string' ? style.module : '$style';
|
|
193
|
+
// Scanned against the COMPILED content, not style.content: :global(...) isn't native
|
|
194
|
+
// SCSS/Less/Stylus syntax (each preprocessor passes an unrecognized selector through
|
|
195
|
+
// unchanged), but scanning the compiler's actual output can't drift under nesting/
|
|
196
|
+
// interpolation the way assuming source-and-output stay textually identical could.
|
|
197
|
+
const exemptFromScope = globalClassNamesIn(content);
|
|
198
|
+
const classMap = cssModuleBindings.get(bindingName) ?? {};
|
|
199
|
+
for (const [className, props] of Object.entries(parsed)) {
|
|
200
|
+
const isExempt = exemptFromScope.has(className);
|
|
201
|
+
const registeredName = isExempt ? className : `${className}__module__${scopeId}`;
|
|
202
|
+
classMap[className] = registeredName;
|
|
203
|
+
styles[registeredName] = { ...styles[registeredName], ...props };
|
|
204
|
+
}
|
|
205
|
+
cssModuleBindings.set(bindingName, classMap);
|
|
206
|
+
} else if (style.scoped) {
|
|
207
|
+
const exemptFromScope = globalClassNamesIn(content);
|
|
208
|
+
for (const [className, props] of Object.entries(parsed)) {
|
|
209
|
+
const isExempt = exemptFromScope.has(className);
|
|
210
|
+
const registeredName = isExempt ? className : `${className}__${scopeId}`;
|
|
211
|
+
if (!isExempt) localScopedNames.add(className);
|
|
212
|
+
styles[registeredName] = { ...styles[registeredName], ...props };
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
for (const [className, props] of Object.entries(parsed)) {
|
|
216
|
+
styles[className] = { ...styles[className], ...props };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The nodeTransform rewrites every class/:class binding's compiled output to route through
|
|
222
|
+
// scopeClassName() at the scoped names this file actually defines — skipped entirely (not
|
|
223
|
+
// even passed to the compiler) when nothing in this file is scoped, so a .vue with only
|
|
224
|
+
// unscoped/no styles compiles exactly as before, zero added runtime cost or behavior change.
|
|
225
|
+
const templateOptions =
|
|
226
|
+
localScopedNames.size > 0
|
|
227
|
+
? {
|
|
228
|
+
compilerOptions: {
|
|
229
|
+
nodeTransforms: [createScopeClassNodeTransform(localScopedNames, scopeId)],
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
234
|
+
// inlineTemplate folds the <template> render fn into setup(): one module, one `export
|
|
235
|
+
// default`. Only valid with <script setup>, which the canary uses.
|
|
236
|
+
const compiled = compileScript(descriptor, {
|
|
237
|
+
id: scopeId,
|
|
238
|
+
inlineTemplate: true,
|
|
239
|
+
templateOptions,
|
|
240
|
+
});
|
|
241
|
+
// Point every Vue import (the compiler's injected helpers AND the user's own
|
|
242
|
+
// `import { ref } from 'vue'`) at the runtime-helpers shim, which re-exports the same
|
|
243
|
+
// @vue/runtime-core singleton the @symbiote-native/vue adapter builds its custom renderer on, plus
|
|
244
|
+
// our own directive implementations. No vue/runtime-dom in a native bundle.
|
|
245
|
+
const code = compiled.content.replace(/from\s*(['"])vue\1/g, 'from "@symbiote-native/vue/runtime-helpers"');
|
|
246
|
+
|
|
247
|
+
if (Object.keys(styles).length === 0) return code;
|
|
248
|
+
|
|
249
|
+
// Only a scoped file needs scopeClassName + its two per-file constants — the nodeTransform
|
|
250
|
+
// above only ever emits calls to `__scopeClass`/`__localScopedClassNames`/`__scopeId` when
|
|
251
|
+
// localScopedNames is non-empty, so these stay unimported/undeclared (and absent from the
|
|
252
|
+
// bundle) for every non-scoped .vue file.
|
|
253
|
+
const engineImports =
|
|
254
|
+
localScopedNames.size > 0 ? 'registerStyles, scopeClassName as __scopeClass' : 'registerStyles';
|
|
255
|
+
|
|
256
|
+
const preamble = [`registerStyles(${JSON.stringify(styles)});`];
|
|
257
|
+
if (localScopedNames.size > 0) {
|
|
258
|
+
preamble.push(
|
|
259
|
+
`const __localScopedClassNames = new Set(${JSON.stringify([...localScopedNames])});`,
|
|
260
|
+
`const __scopeId = ${JSON.stringify(scopeId)};`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
// Each <style module> binding becomes a plain top-level const holding its name->scopedName
|
|
264
|
+
// map. Placed before the compiled `export default {...}`, so it's just a closed-over module
|
|
265
|
+
// scope variable inside setup() — same trick registerStyles/__scopeId already rely on — and
|
|
266
|
+
// usable both from the inlined template (`:class="$style.card"`) and from <script setup> code
|
|
267
|
+
// itself (`$style.card`), no extra wiring needed on either side.
|
|
268
|
+
for (const [bindingName, classMap] of cssModuleBindings) {
|
|
269
|
+
preamble.push(`const ${bindingName} = ${JSON.stringify(classMap)};`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return [`import { ${engineImports} } from '@symbiote-native/engine';`, ...preamble, code].join('\n') + '\n';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Exported separately from `transform` so tests can assert on the compiled SFC output
|
|
276
|
+
// (imports, injected `registerStyles` call) without driving the full upstream RN Babel preset.
|
|
277
|
+
module.exports.compileSfc = compileSfc;
|
|
278
|
+
|
|
279
|
+
// Async uniformly, including the `.vue` and plain-passthrough branches that never touch a
|
|
280
|
+
// preprocessor: compileSfc() itself is async now (a scss/sass/less/stylus <style> block awaits
|
|
281
|
+
// preprocessors.ts's compile()), and Metro's own metro-transform-worker already
|
|
282
|
+
// `await transformer.transform(...)` before touching the result (confirmed by reading the
|
|
283
|
+
// installed metro-transform-worker source — see metro-transformer.ts for the fuller writeup),
|
|
284
|
+
// so returning a Promise here is a supported, exercised shape, not a hack. A sync fast-path for
|
|
285
|
+
// the no-preprocessor branches would fork this function into two shapes to save a single
|
|
286
|
+
// microtask on a call that only ever runs at Metro build time, content-hash-cached — not worth
|
|
287
|
+
// the duplication.
|
|
288
|
+
module.exports.transform = async function transform(params) {
|
|
289
|
+
if (params.filename.endsWith('.vue')) {
|
|
290
|
+
const code = await compileSfc(params.src, params.filename);
|
|
291
|
+
// Re-label as .tsx so RN's transformer strips any TS from <script setup lang="ts"> and
|
|
292
|
+
// processes the module exactly like app source. Metro tracks the real path separately.
|
|
293
|
+
return upstreamTransformer.transform({
|
|
294
|
+
...params,
|
|
295
|
+
src: code,
|
|
296
|
+
filename: params.filename + '.tsx',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// A standalone style file (as opposed to a `.vue` file's own inline <style> block above) —
|
|
300
|
+
// the framework-agnostic path (core/css-parser's compileCssFile), usable from this example's
|
|
301
|
+
// .vue files exactly like from any other adapter's example. isStyleFile recognizes
|
|
302
|
+
// .css/.scss/.sass/.less/.styl/.stylus (+ each .module.* twin) — see the
|
|
303
|
+
// symbiote-sfc-style-compiler skill.
|
|
304
|
+
if (isStyleFile(params.filename)) {
|
|
305
|
+
const { code } = await compileCssFile(params.src, params.filename);
|
|
306
|
+
return upstreamTransformer.transform({ ...params, src: code, filename: params.filename + '.js' });
|
|
307
|
+
}
|
|
308
|
+
return upstreamTransformer.transform(params);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Surface the upstream cache key so RN preset changes still bust Metro's cache. The SFC
|
|
312
|
+
// step itself is invalidated by `--reset-cache` (mirrors the babel DEBUG-inline note).
|
|
313
|
+
module.exports.getCacheKey = upstreamTransformer.getCacheKey;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/vue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "SymbioteNative's Vue adapter — a @vue/runtime-core createRenderer driving real native iOS/Android views through the same engine as the React adapter.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,20 +29,24 @@
|
|
|
29
29
|
"types": "./build/runtime-helpers.d.ts",
|
|
30
30
|
"default": "./build/runtime-helpers.js"
|
|
31
31
|
},
|
|
32
|
-
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
32
|
+
"./metro-css-parser": "./metro-css-parser.cjs",
|
|
33
|
+
"./metro-vue-transformer": "./metro-vue-transformer.cjs"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"build",
|
|
36
|
-
"metro-css-parser.cjs"
|
|
37
|
+
"metro-css-parser.cjs",
|
|
38
|
+
"metro-vue-transformer.cjs"
|
|
37
39
|
],
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
44
|
+
"@vue/compiler-core": "^3.5.13",
|
|
45
|
+
"@vue/compiler-sfc": "^3.5.13",
|
|
42
46
|
"@vue/runtime-core": "^3.5.13",
|
|
43
|
-
"@symbiote-native/
|
|
44
|
-
"@symbiote-native/
|
|
45
|
-
"@symbiote-native/engine": "0.1.
|
|
47
|
+
"@symbiote-native/components": "0.2.1",
|
|
48
|
+
"@symbiote-native/css-parser": "0.2.0",
|
|
49
|
+
"@symbiote-native/engine": "0.1.2"
|
|
46
50
|
},
|
|
47
51
|
"peerDependencies": {
|
|
48
52
|
"vue": ">=3.5.0",
|