@symbiote-native/vue 0.4.0 → 1.0.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 +24 -9
- package/babel-jsx.cjs +53 -0
- package/babel-lower-host-primitives.cjs +225 -0
- package/build/bootstrap.js +1 -1
- package/build/components/button.js +4 -2
- 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 +39 -20
- package/build/components/safe-area-view.d.ts +1 -0
- 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 +25 -2
- package/build/components/touchable-native-feedback.js +11 -4
- package/build/components/touchable.d.ts +7 -1
- package/build/components/touchable.js +184 -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.d.ts +2 -0
- package/build/components.js +25 -4
- package/build/composables/use-raw-attrs.d.ts +1 -0
- package/build/composables/use-raw-attrs.js +26 -0
- package/build/create-portal/index.d.ts +28 -0
- package/build/create-portal/index.js +42 -0
- package/build/host-instance/index.js +7 -6
- package/build/index.d.ts +9 -6
- package/build/index.js +14 -3
- package/build/modules/animated/create-animated-component.d.ts +1 -1
- package/build/modules/animated/create-animated-component.js +27 -5
- 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/register.d.ts +1 -0
- package/build/register.js +35 -0
- package/build/render.d.ts +3 -1
- package/build/render.js +62 -1
- package/build/renderer/index.js +58 -9
- package/build/runtime-helpers/index.d.ts +48 -24
- package/build/runtime-helpers/index.js +314 -30
- package/build/state-style.d.ts +1 -0
- package/build/state-style.js +13 -0
- package/build/utils/normalize-attrs.d.ts +1 -0
- package/build/utils/normalize-attrs.js +21 -13
- package/metro-css-parser.cjs +2 -1
- package/metro-vue-transformer.cjs +502 -101
- package/package.json +29 -8
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
// forwarded attrs (pure-JS callbacks); the machine's SYNTHESIZED handlers go on the View instead.
|
|
12
12
|
// Children arrive as a (scoped) default slot so `v-slot="{ pressed }"` mirrors React's
|
|
13
13
|
// children-as-function.
|
|
14
|
-
import { defineComponent, h, ref, shallowRef } from '@vue/runtime-core';
|
|
15
|
-
import { createPressHandlers, createPressRuntime, rippleProps, buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, resolveAccessibilityProps, DEFAULT_DELAY_LONG_PRESS_MS, } from '@symbiote-native/components';
|
|
14
|
+
import { defineComponent, h, onUnmounted, ref, shallowRef, } from '@vue/runtime-core';
|
|
15
|
+
import { createPressHandlers, createPressRuntime, disposePressRuntime, rippleProps, buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, resolveAccessibilityProps, DEFAULT_DELAY_LONG_PRESS_MS, DEFAULT_MIN_PRESS_DURATION_MS, } from '@symbiote-native/components';
|
|
16
16
|
import { measure, isSymbioteNode, } from '@symbiote-native/engine';
|
|
17
|
-
import {
|
|
17
|
+
import { HOST_VIEW } from '../components.js';
|
|
18
|
+
import { useRawAttrs } from '../composables/use-raw-attrs.js';
|
|
18
19
|
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
19
20
|
// Reused verbatim as the `emits` of every Touchable* wrapper, so the runtime list lives once.
|
|
20
21
|
export const PRESSABLE_EMITS = {
|
|
@@ -106,7 +107,10 @@ function resolveStyle(value, state) {
|
|
|
106
107
|
}
|
|
107
108
|
// Everything else forwards onto the View. User press callbacks are pure JS and must never reach
|
|
108
109
|
// the host; the machine's synthesized handlers go on via buildPressableListeners.
|
|
109
|
-
|
|
110
|
+
// A Set, not an array: forwardAttrs asks this question once per attr per Pressable instance, and
|
|
111
|
+
// `Array.prototype.includes` walks all eighteen entries for every key that is NOT handled — which
|
|
112
|
+
// is most of them. Membership is the whole use; the order never mattered.
|
|
113
|
+
const HANDLED_ATTRS = new Set([
|
|
110
114
|
'onPress',
|
|
111
115
|
'onPressIn',
|
|
112
116
|
'onPressOut',
|
|
@@ -117,6 +121,7 @@ const HANDLED_ATTRS = [
|
|
|
117
121
|
'cancelable',
|
|
118
122
|
'pressRetentionOffset',
|
|
119
123
|
'unstable_pressDelay',
|
|
124
|
+
'__minPressDuration',
|
|
120
125
|
'android_ripple',
|
|
121
126
|
'android_disableSound',
|
|
122
127
|
'onHoverIn',
|
|
@@ -125,16 +130,19 @@ const HANDLED_ATTRS = [
|
|
|
125
130
|
'delayHoverOut',
|
|
126
131
|
'style',
|
|
127
132
|
'accessibilityState',
|
|
128
|
-
];
|
|
133
|
+
]);
|
|
129
134
|
function forwardAttrs(attrs) {
|
|
130
135
|
const result = {};
|
|
131
136
|
for (const key of Object.keys(attrs)) {
|
|
132
|
-
if (!HANDLED_ATTRS.
|
|
137
|
+
if (!HANDLED_ATTRS.has(key))
|
|
133
138
|
result[key] = attrs[key];
|
|
134
139
|
}
|
|
135
140
|
return result;
|
|
136
141
|
}
|
|
137
|
-
export const Pressable = defineComponent((_props, { slots, attrs:
|
|
142
|
+
export const Pressable = defineComponent((_props, { slots, attrs: contextAttrs, emit, }) => {
|
|
143
|
+
// The unproxied bag, captured once. See use-raw-attrs.ts for why the context's own attrs cost
|
|
144
|
+
// a track() per read and why capturing here (a setup body) is the only safe place to do it.
|
|
145
|
+
const rawAttrs = useRawAttrs(contextAttrs);
|
|
138
146
|
const pressed = ref(false);
|
|
139
147
|
// The mutable press runtime (timers, suppression flags, measured region). A plain setup-scope
|
|
140
148
|
// object, never a ref: mutated by the machine, never reactively read.
|
|
@@ -161,7 +169,11 @@ export const Pressable = defineComponent((_props, { slots, attrs: rawAttrs, emit
|
|
|
161
169
|
const id = setTimeout(callback, ms);
|
|
162
170
|
return () => clearTimeout(id);
|
|
163
171
|
},
|
|
172
|
+
now: Date.now,
|
|
164
173
|
};
|
|
174
|
+
onUnmounted(() => {
|
|
175
|
+
disposePressRuntime(runtime);
|
|
176
|
+
});
|
|
165
177
|
return () => {
|
|
166
178
|
const attrs = normalizeVueAttrs(rawAttrs);
|
|
167
179
|
const disabled = attrs.disabled === true ? true : undefined;
|
|
@@ -174,6 +186,7 @@ export const Pressable = defineComponent((_props, { slots, attrs: rawAttrs, emit
|
|
|
174
186
|
onLongPress: event => emit('longPress', event),
|
|
175
187
|
delayLongPress: numberOr(attrs.delayLongPress, DEFAULT_DELAY_LONG_PRESS_MS),
|
|
176
188
|
unstable_pressDelay: numberOr(attrs.unstable_pressDelay, 0),
|
|
189
|
+
minPressDuration: numberOr(attrs.__minPressDuration, DEFAULT_MIN_PRESS_DURATION_MS),
|
|
177
190
|
hitSlop: asRectOffset(attrs.hitSlop),
|
|
178
191
|
pressRetentionOffset: asRectOffset(attrs.pressRetentionOffset),
|
|
179
192
|
};
|
|
@@ -182,14 +195,15 @@ export const Pressable = defineComponent((_props, { slots, attrs: rawAttrs, emit
|
|
|
182
195
|
const state = { pressed: pressed.value };
|
|
183
196
|
// Vue's View is a bare host primitive, so Pressable folds disabled into accessibilityState
|
|
184
197
|
// and aria/role itself, rather than the View folding it (as React's does).
|
|
198
|
+
// One bag, built once and then written into. The spread this replaces rebuilt the whole
|
|
199
|
+
// forwarded set a second time just to add three keys; resolveAccessibilityProps hands back
|
|
200
|
+
// the SAME object whenever no aria key is present, which is the common case, so on that path
|
|
201
|
+
// the render now allocates one bag instead of two.
|
|
185
202
|
const forwarded = forwardAttrs(attrs);
|
|
186
203
|
forwarded.accessibilityState = resolveDisabledAccessibilityState(asAccessibilityState(attrs.accessibilityState), disabled);
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
ref: setNodeRef,
|
|
191
|
-
style: resolveStyle(attrs.style, state),
|
|
192
|
-
};
|
|
204
|
+
const viewProps = resolveAccessibilityProps(forwarded);
|
|
205
|
+
viewProps.ref = setNodeRef;
|
|
206
|
+
viewProps.style = resolveStyle(attrs.style, state);
|
|
193
207
|
if (typeof attrs.android_disableSound === 'boolean')
|
|
194
208
|
viewProps.android_disableSound = attrs.android_disableSound;
|
|
195
209
|
Object.assign(viewProps, buildPressableListeners(handlers, { disabled, cancelable }));
|
|
@@ -199,13 +213,18 @@ export const Pressable = defineComponent((_props, { slots, attrs: rawAttrs, emit
|
|
|
199
213
|
const ripple = isRecord(attrs.android_ripple)
|
|
200
214
|
? rippleProps(asRippleConfig(attrs.android_ripple) ?? {})
|
|
201
215
|
: undefined;
|
|
202
|
-
const inner = ripple !== undefined ? [h(
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
216
|
+
const inner = ripple !== undefined ? [h(HOST_VIEW, ripple, content)] : content;
|
|
217
|
+
// The host node is the intrinsic TAG, not our <View> component: a Vue component instance
|
|
218
|
+
// costs createComponentInstance + initProps + initSlots + setupRenderEffect even when the
|
|
219
|
+
// component is functional, and this one only forwards attrs to the same tag. Pressable is
|
|
220
|
+
// 2 of the 6 primitives in a benchmark row, so this is one instance per row, twice.
|
|
221
|
+
//
|
|
222
|
+
// Children are therefore an ARRAY, and that also RETIRES a hazard: passing an array to the
|
|
223
|
+
// functional <View> made Vue normalize it to a default slot with a "Prefer function slots"
|
|
224
|
+
// dev warn, whose trace formats the __self/__source dev props (native HostObjects) — a read
|
|
225
|
+
// that throws under JSX and unwinds the whole mount into a blank screen. An element takes
|
|
226
|
+
// array children natively and never reaches that path.
|
|
227
|
+
return h(HOST_VIEW, viewProps, inner);
|
|
209
228
|
};
|
|
210
229
|
}, {
|
|
211
230
|
name: 'Pressable',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type IClassNameValue } from '@symbiote-native/engine';
|
|
2
2
|
import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
|
|
3
3
|
export type ISafeAreaViewProps = IAccessibilityProps & IAriaProps & {
|
|
4
|
+
id?: string;
|
|
4
5
|
class?: IClassNameValue;
|
|
5
6
|
};
|
|
6
7
|
export declare const SafeAreaView: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { IScrollViewProps, IScrollViewEmits, IScrollViewHandle } from './shared';
|
|
1
|
+
export type { IScrollViewProps, IScrollViewEmits, IScrollViewHandle, } from './shared';
|
|
2
2
|
export declare const ScrollView: import("vue").DefineSetupFnComponent<import("./shared").IScrollViewProps, import("./shared").IScrollViewEmits, {}, import("./shared").IScrollViewProps & {
|
|
3
3
|
onContentSizeChange?: ((width: number, height: number) => any) | undefined;
|
|
4
4
|
}, import("vue").PublicProps>;
|
|
@@ -23,7 +23,9 @@ function isRecord(value) {
|
|
|
23
23
|
function isHostType(type) {
|
|
24
24
|
if (typeof type === 'string')
|
|
25
25
|
return true;
|
|
26
|
-
return ((typeof type === 'object' || typeof type === 'function') &&
|
|
26
|
+
return ((typeof type === 'object' || typeof type === 'function') &&
|
|
27
|
+
type !== null &&
|
|
28
|
+
!isVNode(type));
|
|
27
29
|
}
|
|
28
30
|
export const ScrollView = createScrollView({
|
|
29
31
|
assemble: input => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { IScrollViewProps, IScrollViewEmits, IScrollViewHandle } from './shared';
|
|
1
|
+
export type { IScrollViewProps, IScrollViewEmits, IScrollViewHandle, } from './shared';
|
|
2
2
|
export declare const ScrollView: import("vue").DefineSetupFnComponent<import("./shared").IScrollViewProps, import("./shared").IScrollViewEmits, {}, import("./shared").IScrollViewProps & {
|
|
3
3
|
onContentSizeChange?: ((width: number, height: number) => any) | undefined;
|
|
4
4
|
}, import("vue").PublicProps>;
|
|
@@ -9,7 +9,9 @@ export const ScrollView = createScrollView({
|
|
|
9
9
|
assemble: input => {
|
|
10
10
|
// Sibling placement: RefreshControl before content (RN iOS). The node ref stays on the scroll
|
|
11
11
|
// view via input.scrollProps, so the imperative handle targets it whether or not refresh is on.
|
|
12
|
-
const children = input.refreshControl === undefined
|
|
12
|
+
const children = input.refreshControl === undefined
|
|
13
|
+
? [input.content]
|
|
14
|
+
: [input.refreshControl, input.content];
|
|
13
15
|
return h(input.scrollViewIntrinsic, input.scrollProps, children);
|
|
14
16
|
},
|
|
15
17
|
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { defineComponent, h, isVNode, markRaw, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
|
|
16
16
|
import { attachStickyScroll, buildScrollViewHandle, didContentSizeChange, forwardScrollEvent, readLayoutDimension, resolveAccessibilityProps, resolveDecelerationRate, resolveScrollForwarding, selectScrollIntrinsics, } from '@symbiote-native/components';
|
|
17
17
|
import { AnimatedValue, dlog, event as animatedEvent, isClassNameValue, isNativeAnimatedAvailable, isSymbioteNode, resolveClassName, } from '@symbiote-native/engine';
|
|
18
|
-
import { wrapStickyHeaders } from './sticky-header.js';
|
|
18
|
+
import { wrapStickyHeaders, } from './sticky-header.js';
|
|
19
19
|
import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
|
|
20
20
|
function isHandler(value) {
|
|
21
21
|
return typeof value === 'function';
|
|
@@ -37,10 +37,10 @@ function asDecelerationRate(value) {
|
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
39
|
function isNumberArray(value) {
|
|
40
|
-
return Array.isArray(value) && value.every(entry => typeof entry === 'number');
|
|
40
|
+
return (Array.isArray(value) && value.every(entry => typeof entry === 'number'));
|
|
41
41
|
}
|
|
42
42
|
function isComponent(value) {
|
|
43
|
-
return typeof value === 'function' || (typeof value === 'object' && value !== null);
|
|
43
|
+
return (typeof value === 'function' || (typeof value === 'object' && value !== null));
|
|
44
44
|
}
|
|
45
45
|
// Prop/handler keys the lifecycle consumes itself; everything else forwards onto the scroll-view
|
|
46
46
|
// node. onContentSizeChange is consumed (synthesized from the content onLayout); refreshControl
|
|
@@ -126,8 +126,13 @@ export function createScrollView(platform) {
|
|
|
126
126
|
const attrs = normalizeVueAttrs(rawAttrs);
|
|
127
127
|
const isHorizontal = attrs.horizontal === true;
|
|
128
128
|
const userStyle = isStyleProp(attrs.style) ? attrs.style : undefined;
|
|
129
|
-
const classProp = isClassNameProp(attrs.class)
|
|
130
|
-
|
|
129
|
+
const classProp = isClassNameProp(attrs.class)
|
|
130
|
+
? attrs.class
|
|
131
|
+
: undefined;
|
|
132
|
+
const layoutSplitStyle = [
|
|
133
|
+
resolveClassName(classProp),
|
|
134
|
+
userStyle,
|
|
135
|
+
];
|
|
131
136
|
const contentContainerStyle = typeof attrs.contentContainerStyle === 'string'
|
|
132
137
|
? resolveClassName(attrs.contentContainerStyle)
|
|
133
138
|
: isStyleProp(attrs.contentContainerStyle)
|
|
@@ -141,14 +146,16 @@ export function createScrollView(platform) {
|
|
|
141
146
|
const stickyHeaderComponent = isComponent(attrs.StickyHeaderComponent)
|
|
142
147
|
? attrs.StickyHeaderComponent
|
|
143
148
|
: undefined;
|
|
144
|
-
const { scrollViewIntrinsic, contentIntrinsic, scrollViewBaseStyle, contentStyle } = selectScrollIntrinsics(isHorizontal, contentContainerStyle);
|
|
149
|
+
const { scrollViewIntrinsic, contentIntrinsic, scrollViewBaseStyle, contentStyle, } = selectScrollIntrinsics(isHorizontal, contentContainerStyle);
|
|
145
150
|
// RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`);
|
|
146
151
|
// horizontal forwards only when defined (load-bearing on iOS's RCTScrollView axis).
|
|
147
152
|
const outerProps = {
|
|
148
153
|
...resolveAccessibilityProps(forwardAttrs(attrs)),
|
|
149
154
|
};
|
|
150
155
|
outerProps.nestedScrollEnabled =
|
|
151
|
-
typeof attrs.nestedScrollEnabled === 'boolean'
|
|
156
|
+
typeof attrs.nestedScrollEnabled === 'boolean'
|
|
157
|
+
? attrs.nestedScrollEnabled
|
|
158
|
+
: true;
|
|
152
159
|
if (attrs.horizontal !== undefined)
|
|
153
160
|
outerProps.horizontal = attrs.horizontal;
|
|
154
161
|
const decel = asDecelerationRate(attrs.decelerationRate);
|
|
@@ -160,7 +167,9 @@ export function createScrollView(platform) {
|
|
|
160
167
|
// decisions (which path, throttle default, inverted capture).
|
|
161
168
|
const nativeStickyAvailable = hasStickyHeaders && isNativeAnimatedAvailable();
|
|
162
169
|
nativeStickyWanted = nativeStickyAvailable;
|
|
163
|
-
const userThrottle = typeof attrs.scrollEventThrottle === 'number'
|
|
170
|
+
const userThrottle = typeof attrs.scrollEventThrottle === 'number'
|
|
171
|
+
? attrs.scrollEventThrottle
|
|
172
|
+
: undefined;
|
|
164
173
|
const forwarding = resolveScrollForwarding({
|
|
165
174
|
hasStickyHeaders,
|
|
166
175
|
nativeStickyAvailable,
|
|
@@ -170,13 +179,17 @@ export function createScrollView(platform) {
|
|
|
170
179
|
snapToAlignment: attrs.snapToAlignment,
|
|
171
180
|
});
|
|
172
181
|
if (hasStickyHeaders) {
|
|
173
|
-
const userOnScroll = isHandler(attrs.onScroll)
|
|
182
|
+
const userOnScroll = isHandler(attrs.onScroll)
|
|
183
|
+
? attrs.onScroll
|
|
184
|
+
: undefined;
|
|
174
185
|
if (forwarding.mode === 'sticky-js') {
|
|
175
186
|
// JS fallback (no native module): correct, but lags a frame under fast scroll, which
|
|
176
187
|
// the native path removes on a real host.
|
|
177
188
|
outerProps.onScroll = animatedEvent([{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }], userOnScroll === undefined
|
|
178
189
|
? undefined
|
|
179
|
-
: {
|
|
190
|
+
: {
|
|
191
|
+
listener: (...args) => forwardScrollEvent(userOnScroll, args),
|
|
192
|
+
});
|
|
180
193
|
}
|
|
181
194
|
// Native path: the value is driven on the UI thread by the post-commit watch above, so
|
|
182
195
|
// onScroll forwards untouched.
|
|
@@ -186,7 +199,9 @@ export function createScrollView(platform) {
|
|
|
186
199
|
// Capture the viewport height for inverted sticky headers (RN _handleLayout), then call
|
|
187
200
|
// the user's handler.
|
|
188
201
|
if (forwarding.capturesViewportHeight) {
|
|
189
|
-
const userOnLayout = isHandler(attrs.onLayout)
|
|
202
|
+
const userOnLayout = isHandler(attrs.onLayout)
|
|
203
|
+
? attrs.onLayout
|
|
204
|
+
: undefined;
|
|
190
205
|
outerProps.onLayout = (event) => {
|
|
191
206
|
const height = readLayoutDimension(event, 'height');
|
|
192
207
|
if (height !== undefined)
|
|
@@ -200,7 +215,10 @@ export function createScrollView(platform) {
|
|
|
200
215
|
// `collapsable: false` keeps the layout-only content view as a real native view: Android
|
|
201
216
|
// Fabric view-flattens it away otherwise, hoisting the cells as direct children of the
|
|
202
217
|
// scroll view (which hosts exactly one) - an addViewAt crash. iOS never flattens.
|
|
203
|
-
const contentProps = {
|
|
218
|
+
const contentProps = {
|
|
219
|
+
style: contentStyle,
|
|
220
|
+
collapsable: false,
|
|
221
|
+
};
|
|
204
222
|
if (forwarding.collapsableChildren) {
|
|
205
223
|
contentProps.collapsableChildren = false;
|
|
206
224
|
}
|
|
@@ -228,7 +246,9 @@ export function createScrollView(platform) {
|
|
|
228
246
|
style: [scrollViewBaseStyle, userStyle],
|
|
229
247
|
ref: setNodeRef,
|
|
230
248
|
};
|
|
231
|
-
const refreshControl = isVNode(attrs.refreshControl)
|
|
249
|
+
const refreshControl = isVNode(attrs.refreshControl)
|
|
250
|
+
? attrs.refreshControl
|
|
251
|
+
: undefined;
|
|
232
252
|
return platform.assemble({
|
|
233
253
|
scrollViewIntrinsic,
|
|
234
254
|
scrollProps,
|
|
@@ -52,7 +52,10 @@ export const ScrollViewStickyHeader = defineComponent({
|
|
|
52
52
|
const version = ref(0);
|
|
53
53
|
// Engine node -> shallowRef (identity rule); the un-measured identity stub until the
|
|
54
54
|
// rebuild-interpolation effect below replaces it.
|
|
55
|
-
const animatedTranslateY = shallowRef(scrollAnimatedValue.interpolate({
|
|
55
|
+
const animatedTranslateY = shallowRef(scrollAnimatedValue.interpolate({
|
|
56
|
+
inputRange: [-1, 0],
|
|
57
|
+
outputRange: [0, 0],
|
|
58
|
+
}));
|
|
56
59
|
let debounceTimer;
|
|
57
60
|
// Held so the next rebuild detaches the old listener and onBeforeUnmount cleans up.
|
|
58
61
|
let interpolation;
|
|
@@ -60,8 +63,12 @@ export const ScrollViewStickyHeader = defineComponent({
|
|
|
60
63
|
const inputs = () => ({
|
|
61
64
|
os: Platform.OS,
|
|
62
65
|
inverted: typeof attrs.inverted === 'boolean' ? attrs.inverted : undefined,
|
|
63
|
-
scrollViewHeight: typeof attrs.scrollViewHeight === 'number'
|
|
64
|
-
|
|
66
|
+
scrollViewHeight: typeof attrs.scrollViewHeight === 'number'
|
|
67
|
+
? attrs.scrollViewHeight
|
|
68
|
+
: undefined,
|
|
69
|
+
nextHeaderLayoutY: typeof attrs.nextHeaderLayoutY === 'number'
|
|
70
|
+
? attrs.nextHeaderLayoutY
|
|
71
|
+
: undefined,
|
|
65
72
|
});
|
|
66
73
|
const runEffects = (effects) => {
|
|
67
74
|
for (const effect of effects) {
|
|
@@ -124,13 +131,19 @@ export const ScrollViewStickyHeader = defineComponent({
|
|
|
124
131
|
const y = readLayoutNumber(event, 'y');
|
|
125
132
|
const height = readLayoutNumber(event, 'height');
|
|
126
133
|
// Keep the previous value when a field is absent (RN sets state only on a defined read).
|
|
127
|
-
dispatch({
|
|
134
|
+
dispatch({
|
|
135
|
+
kind: 'layout',
|
|
136
|
+
y: y ?? state.layoutY,
|
|
137
|
+
height: height ?? state.layoutHeight,
|
|
138
|
+
});
|
|
128
139
|
const recorder = attrs.onLayout;
|
|
129
140
|
if (isHandler(recorder))
|
|
130
141
|
recorder(event);
|
|
131
142
|
const children = slots.default !== undefined ? slots.default() : [];
|
|
132
143
|
const child = children[0];
|
|
133
|
-
const childOnLayout = isVNode(child)
|
|
144
|
+
const childOnLayout = isVNode(child)
|
|
145
|
+
? readChildOnLayout(child)
|
|
146
|
+
: undefined;
|
|
134
147
|
if (childOnLayout !== undefined)
|
|
135
148
|
childOnLayout(event);
|
|
136
149
|
};
|
|
@@ -34,7 +34,7 @@ function buildDelegate(getInner) {
|
|
|
34
34
|
}
|
|
35
35
|
export const SectionList = defineComponent(
|
|
36
36
|
// _props is read only by the type system, to let ItemT infer at the call site.
|
|
37
|
-
(_props, { attrs, expose, emit, slots }) => {
|
|
37
|
+
(_props, { attrs, expose, emit, slots, }) => {
|
|
38
38
|
const inner = shallowRef(null);
|
|
39
39
|
const setInner = (instance) => {
|
|
40
40
|
inner.value = isSectionHandle(instance) ? instance : null;
|
|
@@ -54,7 +54,9 @@ export const SectionList = defineComponent(
|
|
|
54
54
|
const startReached = listens('onStartReached')
|
|
55
55
|
? (info) => emit('startReached', info)
|
|
56
56
|
: undefined;
|
|
57
|
-
const refresh = listens('onRefresh')
|
|
57
|
+
const refresh = listens('onRefresh')
|
|
58
|
+
? () => emit('refresh')
|
|
59
|
+
: undefined;
|
|
58
60
|
return h(VirtualizedSectionListHost, {
|
|
59
61
|
...normalizeVueAttrs(attrs),
|
|
60
62
|
ref: setInner,
|
|
@@ -73,7 +73,10 @@ export function createSwitch(platform) {
|
|
|
73
73
|
return;
|
|
74
74
|
emit('valueChange', next, event);
|
|
75
75
|
emitModelUpdate(emit, next);
|
|
76
|
-
state.value = switchReducer(state.value, {
|
|
76
|
+
state.value = switchReducer(state.value, {
|
|
77
|
+
type: 'native-reported',
|
|
78
|
+
value: next,
|
|
79
|
+
});
|
|
77
80
|
};
|
|
78
81
|
// Snap-back: when native reported a value the parent rejected, command the JS value back
|
|
79
82
|
// down (the controlled-Switch correction RN does via SwitchCommands.setValue). The
|
|
@@ -97,12 +100,18 @@ export function createSwitch(platform) {
|
|
|
97
100
|
const attrs = normalizeVueAttrs(rawAttrs);
|
|
98
101
|
return descriptorToVue(renderSwitch({
|
|
99
102
|
value: resolveModelValue(attrs, isBoolean) === true,
|
|
100
|
-
disabled: typeof attrs.disabled === 'boolean'
|
|
103
|
+
disabled: typeof attrs.disabled === 'boolean'
|
|
104
|
+
? attrs.disabled
|
|
105
|
+
: undefined,
|
|
101
106
|
trackColor: normalizeTrackColor(attrs.trackColor),
|
|
102
107
|
thumbColor: asString(attrs.thumbColor),
|
|
103
108
|
ios_backgroundColor: asString(attrs.ios_backgroundColor),
|
|
104
109
|
style: isViewStyleObject(attrs.style) ? attrs.style : undefined,
|
|
105
|
-
passthrough: {
|
|
110
|
+
passthrough: {
|
|
111
|
+
...forwardAttrs(attrs),
|
|
112
|
+
ref: setNodeRef,
|
|
113
|
+
onChange: handleChange,
|
|
114
|
+
},
|
|
106
115
|
}, platform));
|
|
107
116
|
};
|
|
108
117
|
}, {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard, never a cast.
|
|
9
9
|
// onValueChange MUST be stripped from the forwarded attrs: it is not a ViewConfig event, so
|
|
10
10
|
// leaking it would reach Fabric as a function prop and crash Android's folly::dynamic.
|
|
11
|
-
import { defineComponent, onBeforeUnmount, ref, shallowRef, watch } from '@vue/runtime-core';
|
|
11
|
+
import { defineComponent, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
|
|
12
12
|
import { resolveAccessibilityProps, resolveTextInputProps, renderTextInput, foldText, textFromChange, eventCountFromChange, shouldCommandText, INITIAL_EVENT_COUNT, SELECTION_NONE, } from '@symbiote-native/components';
|
|
13
13
|
import { dispatchViewCommand, isSymbioteNode, dlog, blurTextInput, setInputFocused, setInputBlurred, whenCommitted, } from '@symbiote-native/engine';
|
|
14
14
|
import { descriptorToVue } from '../../descriptor-to-vue.js';
|
|
@@ -170,7 +170,12 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
|
|
|
170
170
|
const node = nodeRef.value;
|
|
171
171
|
if (node === null)
|
|
172
172
|
return;
|
|
173
|
-
dispatchViewCommand(node, 'setTextAndSelection', [
|
|
173
|
+
dispatchViewCommand(node, 'setTextAndSelection', [
|
|
174
|
+
mostRecentEventCount.value,
|
|
175
|
+
'',
|
|
176
|
+
0,
|
|
177
|
+
0,
|
|
178
|
+
]);
|
|
174
179
|
lastNativeText = '';
|
|
175
180
|
},
|
|
176
181
|
isFocused: () => focused,
|
|
@@ -186,6 +191,24 @@ export const TextInput = defineComponent((_props, { attrs: rawAttrs, emit, expos
|
|
|
186
191
|
end,
|
|
187
192
|
]);
|
|
188
193
|
},
|
|
194
|
+
// Forwarded from the node, because `expose()` REPLACES the public instance rather than
|
|
195
|
+
// extending it: without these four, a `<TextInput>` ref could not measure while a `<View>`
|
|
196
|
+
// ref could, and an app moved between the component and lowered paths — which it does by
|
|
197
|
+
// writing `:multiline="isLong"` instead of `multiline` — silently swapped which four methods
|
|
198
|
+
// it had. The lowered path answers all nine through `buildTextInputHandle`; this is the same
|
|
199
|
+
// set on the component path, so the two surfaces are one.
|
|
200
|
+
measure: (callback) => {
|
|
201
|
+
nodeRef.value?.measure(callback);
|
|
202
|
+
},
|
|
203
|
+
measureInWindow: (callback) => {
|
|
204
|
+
nodeRef.value?.measureInWindow(callback);
|
|
205
|
+
},
|
|
206
|
+
measureLayout: (relativeToNativeNode, onSuccess, onFail) => {
|
|
207
|
+
nodeRef.value?.measureLayout(relativeToNativeNode, onSuccess, onFail);
|
|
208
|
+
},
|
|
209
|
+
setNativeProps: (nativeProps) => {
|
|
210
|
+
nodeRef.value?.setNativeProps(nativeProps);
|
|
211
|
+
},
|
|
189
212
|
});
|
|
190
213
|
return () => {
|
|
191
214
|
const attrs = normalizeVueAttrs(rawAttrs);
|
|
@@ -6,7 +6,7 @@
|
|
|
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 {
|
|
9
|
+
import { HOST_VIEW } from '../components.js';
|
|
10
10
|
import { Pressable, emitPressableEvents, PRESSABLE_EMITS, } from './pressable.js';
|
|
11
11
|
import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
|
|
12
12
|
const HANDLED = ['background', 'useForeground'];
|
|
@@ -32,7 +32,9 @@ function asBackground(value) {
|
|
|
32
32
|
return {
|
|
33
33
|
type: 'ThemeAttrAndroid',
|
|
34
34
|
attribute: value.attribute,
|
|
35
|
-
rippleRadius: typeof value.rippleRadius === 'number'
|
|
35
|
+
rippleRadius: typeof value.rippleRadius === 'number'
|
|
36
|
+
? value.rippleRadius
|
|
37
|
+
: undefined,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
return undefined;
|
|
@@ -57,8 +59,13 @@ const TouchableNativeFeedbackImpl = defineComponent((_props, { slots, attrs: raw
|
|
|
57
59
|
dlog(`TouchableNativeFeedback render ${resolved.type} useForeground ${useForeground}`);
|
|
58
60
|
const nativeProps = backgroundProps(resolved, useForeground);
|
|
59
61
|
const children = slots.default !== undefined ? slots.default() : [];
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// The intrinsic tag, not our <View> component — see the note at HOST_VIEW.
|
|
63
|
+
const feedback = h(HOST_VIEW, nativeProps, children);
|
|
64
|
+
return h(Pressable, {
|
|
65
|
+
...forwardAttrs(attrs),
|
|
66
|
+
...emitPressableEvents(emit),
|
|
67
|
+
__minPressDuration: 0,
|
|
68
|
+
}, { default: () => [feedback] });
|
|
62
69
|
};
|
|
63
70
|
}, {
|
|
64
71
|
name: 'TouchableNativeFeedback',
|
|
@@ -12,6 +12,10 @@ export interface ITouchableHighlightProps extends ITouchableBaseProps {
|
|
|
12
12
|
underlayColor?: string;
|
|
13
13
|
}
|
|
14
14
|
export type ITouchableWithoutFeedbackProps = ITouchableBaseProps;
|
|
15
|
+
export type ITouchableHighlightEmits = IPressableEmits & {
|
|
16
|
+
showUnderlay: () => boolean;
|
|
17
|
+
hideUnderlay: () => boolean;
|
|
18
|
+
};
|
|
15
19
|
export declare const TouchableOpacity: import("vue").DefineSetupFnComponent<ITouchableOpacityProps, IPressableEmits, {}, ITouchableOpacityProps & {
|
|
16
20
|
onPress?: ((event: ISymbioteEvent) => any) | undefined;
|
|
17
21
|
onPressIn?: ((event: ISymbioteEvent) => any) | undefined;
|
|
@@ -21,7 +25,7 @@ export declare const TouchableOpacity: import("vue").DefineSetupFnComponent<ITou
|
|
|
21
25
|
onHoverIn?: ((event: ISymbioteEvent) => any) | undefined;
|
|
22
26
|
onHoverOut?: ((event: ISymbioteEvent) => any) | undefined;
|
|
23
27
|
}, import("vue").PublicProps>;
|
|
24
|
-
export declare const TouchableHighlight: import("vue").DefineSetupFnComponent<ITouchableHighlightProps,
|
|
28
|
+
export declare const TouchableHighlight: import("vue").DefineSetupFnComponent<ITouchableHighlightProps, ITouchableHighlightEmits, {}, ITouchableHighlightProps & {
|
|
25
29
|
onPress?: ((event: ISymbioteEvent) => any) | undefined;
|
|
26
30
|
onPressIn?: ((event: ISymbioteEvent) => any) | undefined;
|
|
27
31
|
onPressOut?: ((event: ISymbioteEvent) => any) | undefined;
|
|
@@ -29,6 +33,8 @@ export declare const TouchableHighlight: import("vue").DefineSetupFnComponent<IT
|
|
|
29
33
|
onLongPress?: ((event: ISymbioteEvent) => any) | undefined;
|
|
30
34
|
onHoverIn?: ((event: ISymbioteEvent) => any) | undefined;
|
|
31
35
|
onHoverOut?: ((event: ISymbioteEvent) => any) | undefined;
|
|
36
|
+
onShowUnderlay?: (() => any) | undefined;
|
|
37
|
+
onHideUnderlay?: (() => any) | undefined;
|
|
32
38
|
}, import("vue").PublicProps>;
|
|
33
39
|
export declare const TouchableWithoutFeedback: import("vue").DefineSetupFnComponent<ITouchableBaseProps, IPressableEmits, {}, Omit<IPressableProps, "style"> & IPressTimingProps & {
|
|
34
40
|
style?: IStyleProp<IViewStyle>;
|