@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.
Files changed (57) hide show
  1. package/README.md +24 -9
  2. package/babel-jsx.cjs +53 -0
  3. package/babel-lower-host-primitives.cjs +225 -0
  4. package/build/bootstrap.js +1 -1
  5. package/build/components/button.js +4 -2
  6. package/build/components/flat-list/index.js +14 -5
  7. package/build/components/image-background.js +4 -5
  8. package/build/components/image.js +3 -1
  9. package/build/components/keyboard-avoiding-view.js +39 -14
  10. package/build/components/modal/index.js +5 -2
  11. package/build/components/pressable.d.ts +1 -1
  12. package/build/components/pressable.js +39 -20
  13. package/build/components/safe-area-view.d.ts +1 -0
  14. package/build/components/scroll-view/index.android.d.ts +1 -1
  15. package/build/components/scroll-view/index.android.js +3 -1
  16. package/build/components/scroll-view/index.ios.d.ts +1 -1
  17. package/build/components/scroll-view/index.ios.js +3 -1
  18. package/build/components/scroll-view/shared.js +33 -13
  19. package/build/components/scroll-view/sticky-header.js +18 -5
  20. package/build/components/section-list/index.js +4 -2
  21. package/build/components/switch/shared.js +12 -3
  22. package/build/components/text-input/index.js +25 -2
  23. package/build/components/touchable-native-feedback.js +11 -4
  24. package/build/components/touchable.d.ts +7 -1
  25. package/build/components/touchable.js +184 -33
  26. package/build/components/virtualized-list/index.js +42 -20
  27. package/build/components/virtualized-section-list/index.d.ts +5 -0
  28. package/build/components/virtualized-section-list/index.js +33 -4
  29. package/build/components.d.ts +2 -0
  30. package/build/components.js +25 -4
  31. package/build/composables/use-raw-attrs.d.ts +1 -0
  32. package/build/composables/use-raw-attrs.js +26 -0
  33. package/build/create-portal/index.d.ts +28 -0
  34. package/build/create-portal/index.js +42 -0
  35. package/build/host-instance/index.js +7 -6
  36. package/build/index.d.ts +9 -6
  37. package/build/index.js +14 -3
  38. package/build/modules/animated/create-animated-component.d.ts +1 -1
  39. package/build/modules/animated/create-animated-component.js +27 -5
  40. package/build/modules/animated/index.d.ts +2 -0
  41. package/build/modules/animated/index.js +18 -5
  42. package/build/modules/app-registry/index.js +4 -2
  43. package/build/modules/status-bar.js +4 -2
  44. package/build/register.d.ts +1 -0
  45. package/build/register.js +35 -0
  46. package/build/render.d.ts +3 -1
  47. package/build/render.js +62 -1
  48. package/build/renderer/index.js +58 -9
  49. package/build/runtime-helpers/index.d.ts +48 -24
  50. package/build/runtime-helpers/index.js +314 -30
  51. package/build/state-style.d.ts +1 -0
  52. package/build/state-style.js +13 -0
  53. package/build/utils/normalize-attrs.d.ts +1 -0
  54. package/build/utils/normalize-attrs.js +21 -13
  55. package/metro-css-parser.cjs +2 -1
  56. package/metro-vue-transformer.cjs +502 -101
  57. 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 { View } from '../components.js';
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
- const HANDLED_ATTRS = [
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.includes(key))
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: rawAttrs, emit }) => {
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 folded = resolveAccessibilityProps(forwarded);
188
- const viewProps = {
189
- ...folded,
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(View, ripple, () => content)] : content;
203
- // Children go to the host View as a FUNCTION slot, never a raw array: View is a
204
- // functional component, and an array child makes Vue normalize it to a default
205
- // slot with a dev warn ("Prefer function slots"). Benign under SFC, but in JSX the
206
- // warn's trace formats the __self/__source dev props (native HostObjects) and that
207
- // read throws, unwinding the whole mount → blank screen. A function slot skips it.
208
- return h(View, viewProps, () => inner);
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') && type !== null && !isVNode(type));
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 ? [input.content] : [input.refreshControl, input.content];
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) ? attrs.class : undefined;
130
- const layoutSplitStyle = [resolveClassName(classProp), userStyle];
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' ? attrs.nestedScrollEnabled : true;
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' ? attrs.scrollEventThrottle : undefined;
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) ? attrs.onScroll : undefined;
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
- : { listener: (...args) => forwardScrollEvent(userOnScroll, args) });
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) ? attrs.onLayout : undefined;
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 = { style: contentStyle, collapsable: false };
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) ? attrs.refreshControl : undefined;
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({ inputRange: [-1, 0], outputRange: [0, 0] }));
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' ? attrs.scrollViewHeight : undefined,
64
- nextHeaderLayoutY: typeof attrs.nextHeaderLayoutY === 'number' ? attrs.nextHeaderLayoutY : undefined,
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({ kind: 'layout', y: y ?? state.layoutY, height: height ?? state.layoutHeight });
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) ? readChildOnLayout(child) : undefined;
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') ? () => emit('refresh') : undefined;
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, { type: 'native-reported', value: next });
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' ? attrs.disabled : undefined,
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: { ...forwardAttrs(attrs), ref: setNodeRef, onChange: handleChange },
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', [mostRecentEventCount.value, '', 0, 0]);
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 { View } from '../components.js';
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' ? value.rippleRadius : undefined,
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
- const feedback = h(View, nativeProps, () => children);
61
- return h(Pressable, { ...forwardAttrs(attrs), ...emitPressableEvents(emit) }, { default: () => [feedback] });
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, IPressableEmits, {}, 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>;