@symbiote-native/vue 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +6 -6
  2. package/build/bootstrap.js +1 -1
  3. package/build/components/flat-list/index.js +14 -5
  4. package/build/components/image-background.js +4 -5
  5. package/build/components/image.js +3 -1
  6. package/build/components/keyboard-avoiding-view.js +39 -14
  7. package/build/components/modal/index.js +5 -2
  8. package/build/components/pressable.d.ts +1 -1
  9. package/build/components/pressable.js +1 -1
  10. package/build/components/scroll-view/index.android.d.ts +1 -1
  11. package/build/components/scroll-view/index.android.js +3 -1
  12. package/build/components/scroll-view/index.ios.d.ts +1 -1
  13. package/build/components/scroll-view/index.ios.js +3 -1
  14. package/build/components/scroll-view/shared.js +33 -13
  15. package/build/components/scroll-view/sticky-header.js +18 -5
  16. package/build/components/section-list/index.js +4 -2
  17. package/build/components/switch/shared.js +12 -3
  18. package/build/components/text-input/index.js +7 -2
  19. package/build/components/touchable-native-feedback.js +3 -1
  20. package/build/components/touchable.d.ts +7 -1
  21. package/build/components/touchable.js +181 -33
  22. package/build/components/virtualized-list/index.js +42 -20
  23. package/build/components/virtualized-section-list/index.d.ts +5 -0
  24. package/build/components/virtualized-section-list/index.js +33 -4
  25. package/build/components.js +8 -3
  26. package/build/create-portal/index.d.ts +28 -0
  27. package/build/create-portal/index.js +42 -0
  28. package/build/host-instance/index.js +3 -1
  29. package/build/index.d.ts +8 -6
  30. package/build/index.js +9 -3
  31. package/build/modules/animated/create-animated-component.js +12 -4
  32. package/build/modules/animated/index.d.ts +2 -0
  33. package/build/modules/animated/index.js +18 -5
  34. package/build/modules/app-registry/index.js +4 -2
  35. package/build/modules/status-bar.js +4 -2
  36. package/build/render.d.ts +3 -1
  37. package/build/render.js +62 -1
  38. package/build/runtime-helpers/index.d.ts +44 -24
  39. package/build/runtime-helpers/index.js +107 -28
  40. package/build/utils/normalize-attrs.js +3 -1
  41. package/metro-vue-transformer.cjs +138 -97
  42. package/package.json +5 -5
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @symbiote-native/vue
2
2
 
3
3
  The **Vue 3 adapter** for [SymbioteNative](../../README.md) — render real native iOS/Android views from
4
- Vue, on the *same* untouched core as React, with React Native's own renderer never in the path. It
4
+ Vue, on the _same_ untouched core as React, with React Native's own renderer never in the path. It
5
5
  is a `@vue/runtime-core` `createRenderer` whose nodeOps map each mutation onto the engine's
6
6
  four-call API; `@symbiote-native/engine` does the clone-on-write commit into Fabric.
7
7
 
@@ -34,7 +34,7 @@ additionally needs a Metro transformer for `.vue` files (see
34
34
 
35
35
  ## Use it
36
36
 
37
- The native entry reaches the *same* `registerRunnable` seam as React — only the adapter changes. It
37
+ The native entry reaches the _same_ `registerRunnable` seam as React — only the adapter changes. It
38
38
  hands the surface's `rootTag` to `mount` from `@symbiote-native/vue`, which drives the engine through Vue's
39
39
  `createRenderer`:
40
40
 
@@ -95,19 +95,19 @@ and each adapter supplies only its lifecycle (Vue's `ref`/`watch` + the descript
95
95
  The one deliberate gap is third-party **React component** packages such as
96
96
  `@react-native-community/slider` used directly. Their body calls React hooks off the React
97
97
  dispatcher, so they run only under the React adapter — under Vue the dispatcher is null and they
98
- throw. SymbioteNative makes the *native view* framework-agnostic, not the library's React *component*; such
98
+ throw. SymbioteNative makes the _native view_ framework-agnostic, not the library's React _component_; such
99
99
  a view becomes reachable from Vue only through a thin wrapper over the same `createNode`-by-ViewConfig
100
100
  path SymbioteNative uses for its own primitives. `@symbiote-native/slider` (this repo's own wrapper around
101
- `@react-native-community/slider`) *does* ship a real Vue build (`@symbiote-native/slider/vue`) through
101
+ `@react-native-community/slider`) _does_ ship a real Vue build (`@symbiote-native/slider/vue`) through
102
102
  exactly that path — it's what makes this one third-party native view usable from Vue at all; any
103
- *other* React-only component package stays React-adapter-only until it gets the same treatment.
103
+ _other_ React-only component package stays React-adapter-only until it gets the same treatment.
104
104
 
105
105
  ---
106
106
 
107
107
  ## A Vue-specific gotcha — async commit timing
108
108
 
109
109
  Vue batches commits on a microtask (every mutation schedules one `completeRoot`), so a node's
110
- Fabric tag is assigned *after* `onMounted` / `watch(flush:'post')` runs. A native bind that reads
110
+ Fabric tag is assigned _after_ `onMounted` / `watch(flush:'post')` runs. A native bind that reads
111
111
  the tag at lifecycle time (native-driver `Animated`, sticky-header scroll attach, `TextInput`
112
112
  autoFocus) would race the commit and silently no-op — while the JS-path headless test stays green.
113
113
  React doesn't hit this because `react-reconciler` commits synchronously.
@@ -3,7 +3,7 @@
3
3
  // AppRegistry actually get wired. Lives OUTSIDE the package's main barrel — see that file's
4
4
  // header for why anything importing react-native must stay there.
5
5
  import { AppRegistry as RNAppRegistry } from 'react-native';
6
- import { bootstrapHost } from '@symbiote-native/components/bootstrap';
6
+ import { bootstrapHost, } from '@symbiote-native/components/bootstrap';
7
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 = {}) {
@@ -56,7 +56,7 @@ function buildDelegateHandle(getInner) {
56
56
  recordInteraction: () => getInner()?.recordInteraction(),
57
57
  };
58
58
  }
59
- export const FlatList = defineComponent((props, { attrs, expose, emit, slots }) => {
59
+ export const FlatList = defineComponent((props, { attrs, expose, emit, slots, }) => {
60
60
  const inner = shallowRef(null);
61
61
  const setInner = (instance) => {
62
62
  inner.value = isVirtualizedListHandle(instance) ? instance : null;
@@ -70,11 +70,17 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots })
70
70
  return vnodeProps != null && typeof vnodeProps[onName] === 'function';
71
71
  };
72
72
  return () => {
73
- const data = Array.isArray(props.data) ? props.data : [];
73
+ const data = Array.isArray(props.data)
74
+ ? props.data
75
+ : [];
74
76
  const keyExtractor = props.keyExtractor;
75
77
  const numColumns = typeof props.numColumns === 'number' ? props.numColumns : SINGLE_COLUMN;
76
78
  const viewabilityPairs = props.viewabilityConfigCallbackPairs;
77
- const chromeSlots = { header: slots.header, footer: slots.footer, empty: slots.empty };
79
+ const chromeSlots = {
80
+ header: slots.header,
81
+ footer: slots.footer,
82
+ empty: slots.empty,
83
+ };
78
84
  const forwarded = normalizeVueAttrs(attrs);
79
85
  dlog(`Vue FlatList over ${data.length} items, ${numColumns} column(s)`);
80
86
  const endReached = listens('onEndReached')
@@ -83,7 +89,9 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots })
83
89
  const startReached = listens('onStartReached')
84
90
  ? (info) => emit('startReached', info)
85
91
  : undefined;
86
- const refresh = listens('onRefresh') ? () => emit('refresh') : undefined;
92
+ const refresh = listens('onRefresh')
93
+ ? () => emit('refresh')
94
+ : undefined;
87
95
  const scrollToIndexFailed = listens('onScrollToIndexFailed')
88
96
  ? (info) => emit('scrollToIndexFailed', info)
89
97
  : undefined;
@@ -114,7 +122,8 @@ export const FlatList = defineComponent((props, { attrs, expose, emit, slots })
114
122
  { flexDirection: 'row' },
115
123
  typeof props.columnWrapperStyle === 'string'
116
124
  ? resolveClassName(props.columnWrapperStyle)
117
- : isRecord(props.columnWrapperStyle) || Array.isArray(props.columnWrapperStyle)
125
+ : isRecord(props.columnWrapperStyle) ||
126
+ Array.isArray(props.columnWrapperStyle)
118
127
  ? props.columnWrapperStyle
119
128
  : undefined,
120
129
  ];
@@ -34,7 +34,9 @@ function asResizeMode(value) {
34
34
  return isResizeMode(value) ? value : undefined;
35
35
  }
36
36
  function asCrossOrigin(value) {
37
- return value === 'anonymous' || value === 'use-credentials' ? value : undefined;
37
+ return value === 'anonymous' || value === 'use-credentials'
38
+ ? value
39
+ : undefined;
38
40
  }
39
41
  function isStyleProp(value) {
40
42
  return typeof value === 'object' && value !== null;
@@ -94,10 +96,7 @@ const ImageBackgroundComponent = (_props, { attrs: rawAttrs, slots }) => {
94
96
  });
95
97
  // wrapper = symbiote-view > [imageDescriptor]; the slot children paint AFTER the image (on top).
96
98
  const slotChildren = slots.default !== undefined ? slots.default() : [];
97
- return h(wrapper.type, { ...wrapper.props, key: wrapper.key, class: attrs.class }, [
98
- ...wrapper.children.map(toChildVNode),
99
- ...slotChildren,
100
- ]);
99
+ return h(wrapper.type, { ...wrapper.props, key: wrapper.key, class: attrs.class }, [...wrapper.children.map(toChildVNode), ...slotChildren]);
101
100
  };
102
101
  ImageBackgroundComponent.displayName = 'ImageBackground';
103
102
  ImageBackgroundComponent.inheritAttrs = false;
@@ -35,7 +35,9 @@ function asResizeMode(value) {
35
35
  return isResizeMode(value) ? value : undefined;
36
36
  }
37
37
  function asCrossOrigin(value) {
38
- return value === 'anonymous' || value === 'use-credentials' ? value : undefined;
38
+ return value === 'anonymous' || value === 'use-credentials'
39
+ ? value
40
+ : undefined;
39
41
  }
40
42
  function isStyleProp(value) {
41
43
  return typeof value === 'object' && value !== null;
@@ -1,20 +1,23 @@
1
1
  // KeyboardAvoidingView: the Vue lifecycle half. The inset math + the behavior->style/structure
2
2
  // decision live in @symbiote-native/components (render-keyboard-avoiding-view), shared verbatim
3
3
  // with React; Vue supplies only the reactivity: a ref holds the inset, onMounted subscribes to
4
- // the Keyboard module (show/changeFrame/hide), onUnmounted tears the subscriptions down, and
5
- // onLayout measures the wrapper frame that feeds the next event's inset.
4
+ // this host's two keyboard notifications (keyboardAvoidingEventNamesFor — will* on iOS, did* on
5
+ // Android, and never changeFrame), onUnmounted tears the subscriptions down, and onLayout
6
+ // measures the wrapper frame that feeds the next event's inset.
6
7
  //
7
8
  // Inputs arrive as attrs (untyped), so each is narrowed with a runtime guard, never a cast.
8
9
  // rawAttrs runs through normalizeVueAttrs so a template `:keyboard-vertical-offset` resolves.
9
- import { defineComponent, h, ref, onMounted, onUnmounted } from '@vue/runtime-core';
10
- import { Keyboard, KEYBOARD_EVENT, dlog, } from '@symbiote-native/engine';
11
- import { computeInset, readKeyboardFrame, readLayoutFrame, resolveKeyboardAvoidingLayout, resolveAccessibilityProps, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
10
+ import { defineComponent, h, ref, onMounted, onUnmounted, } from '@vue/runtime-core';
11
+ import { Keyboard, Platform, dlog, } from '@symbiote-native/engine';
12
+ import { computeInset, keyboardAvoidingEventNamesFor, readKeyboardFrame, readPrefersCrossFadeTransitions, readLayoutFrame, resolveKeyboardAvoidingLayout, resolveAccessibilityProps, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
12
13
  import { normalizeVueAttrs } from '../utils/normalize-attrs.js';
13
14
  function isStyleProp(value) {
14
15
  return typeof value === 'object' && value !== null;
15
16
  }
16
17
  function asBehavior(value) {
17
- return value === 'height' || value === 'position' || value === 'padding' ? value : undefined;
18
+ return value === 'height' || value === 'position' || value === 'padding'
19
+ ? value
20
+ : undefined;
18
21
  }
19
22
  const HANDLED_ATTRS = [
20
23
  'behavior',
@@ -38,12 +41,26 @@ export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs,
38
41
  // next keyboard event's inset math.
39
42
  let frame;
40
43
  let initialHeight;
41
- const verticalOffset = () => typeof rawAttrs.keyboardVerticalOffset === 'number'
42
- ? rawAttrs.keyboardVerticalOffset
43
- : DEFAULT_VERTICAL_OFFSET;
44
+ // A device accessibility setting, read once per mount. Deliberately a plain variable, not a
45
+ // ref: it cannot change during a session, so nothing should re-render when it resolves.
46
+ let prefersCrossFadeTransitions = false;
47
+ // Read at EVENT time rather than captured once: attrs is live, and both the offset and the
48
+ // behavior feed every keyboard notification's inset math.
49
+ const currentAttrs = () => normalizeVueAttrs(rawAttrs);
50
+ const verticalOffset = () => {
51
+ const offset = currentAttrs().keyboardVerticalOffset;
52
+ return typeof offset === 'number' ? offset : DEFAULT_VERTICAL_OFFSET;
53
+ };
44
54
  const onShow = (payload) => {
45
55
  const keyboard = readKeyboardFrame(payload);
46
- const next = computeInset(frame, keyboard, verticalOffset());
56
+ const next = computeInset(frame, keyboard, verticalOffset(), {
57
+ behavior: asBehavior(currentAttrs().behavior),
58
+ // The inset CURRENTLY applied (RN's this.state.bottom), read live off the reactive cell:
59
+ // in 'height' mode the wrapper is shrunk by it, so the next onLayout under-reports the
60
+ // frame by exactly that much and core adds it back.
61
+ previousInset: inset.value,
62
+ prefersCrossFadeTransitions,
63
+ });
47
64
  dlog(`KeyboardAvoidingView show -> inset ${next}`);
48
65
  inset.value = next;
49
66
  };
@@ -53,11 +70,19 @@ export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs,
53
70
  };
54
71
  let subscriptions = [];
55
72
  onMounted(() => {
73
+ // TWO notifications, per host: iOS takes the will* pair so the view rides up with the
74
+ // keyboard animation, Android the did* pair. changeFrame is deliberately absent — with an
75
+ // undocked/split/floating iOS keyboard it fires BEFORE the hide, mid-dismissal.
76
+ const events = keyboardAvoidingEventNamesFor(Platform.OS);
56
77
  subscriptions = [
57
- Keyboard.addListener(KEYBOARD_EVENT.didShow, onShow),
58
- Keyboard.addListener(KEYBOARD_EVENT.didChangeFrame, onShow),
59
- Keyboard.addListener(KEYBOARD_EVENT.didHide, onHide),
78
+ Keyboard.addListener(events.show, onShow),
79
+ Keyboard.addListener(events.hide, onHide),
60
80
  ];
81
+ // Nobody awaits this, so it goes through the core wrapper, which answers false on a failed
82
+ // native read instead of leaving an unhandled rejection.
83
+ void readPrefersCrossFadeTransitions().then(enabled => {
84
+ prefersCrossFadeTransitions = enabled;
85
+ });
61
86
  });
62
87
  onUnmounted(() => {
63
88
  for (const subscription of subscriptions)
@@ -74,7 +99,7 @@ export const KeyboardAvoidingView = defineComponent((_props, { attrs: rawAttrs,
74
99
  emit('layout', event);
75
100
  };
76
101
  return () => {
77
- const attrs = normalizeVueAttrs(rawAttrs);
102
+ const attrs = currentAttrs();
78
103
  const behavior = asBehavior(attrs.behavior);
79
104
  // RN gates every inset on `enabled ?? true`; only an explicit `false` disables.
80
105
  const isEnabled = attrs.enabled !== false;
@@ -21,7 +21,9 @@ function asString(value) {
21
21
  return typeof value === 'string' ? value : undefined;
22
22
  }
23
23
  function asAnimationType(value) {
24
- return value === 'none' || value === 'slide' || value === 'fade' ? value : undefined;
24
+ return value === 'none' || value === 'slide' || value === 'fade'
25
+ ? value
26
+ : undefined;
25
27
  }
26
28
  function asPresentationStyle(value) {
27
29
  return value === 'fullScreen' ||
@@ -41,7 +43,8 @@ const ORIENTATIONS = [
41
43
  function isOrientation(value) {
42
44
  // .includes() types its arg as IModalOrientation, so a bare string won't pass; .some keeps the
43
45
  // ORIENTATIONS list as the single source of truth without an `as` cast.
44
- return typeof value === 'string' && ORIENTATIONS.some(orientation => orientation === value);
46
+ return (typeof value === 'string' &&
47
+ ORIENTATIONS.some(orientation => orientation === value));
45
48
  }
46
49
  function asSupportedOrientations(value) {
47
50
  if (!Array.isArray(value))
@@ -1,7 +1,7 @@
1
1
  import { type EmitFn, type VNode } from '@vue/runtime-core';
2
2
  import { type IPressState, type IRectOffset, type IPressableAndroidRippleConfig, type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
3
3
  import { type IClassNameValue, type ISymbioteEvent, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
4
- export type { IPressState, IPressableAndroidRippleConfig } from '@symbiote-native/components';
4
+ export type { IPressState, IPressableAndroidRippleConfig, } from '@symbiote-native/components';
5
5
  export type IPressableEmits = {
6
6
  press: (event: ISymbioteEvent) => boolean;
7
7
  pressIn: (event: ISymbioteEvent) => boolean;
@@ -11,7 +11,7 @@
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';
14
+ import { defineComponent, h, ref, shallowRef, } from '@vue/runtime-core';
15
15
  import { createPressHandlers, createPressRuntime, rippleProps, buildPressableListeners, resolveDisabledAccessibilityState, noteHoverNoop, resolveAccessibilityProps, DEFAULT_DELAY_LONG_PRESS_MS, } from '@symbiote-native/components';
16
16
  import { measure, isSymbioteNode, } from '@symbiote-native/engine';
17
17
  import { View } from '../components.js';
@@ -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,
@@ -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;
@@ -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>;