@symbiote-native/vue 1.0.0 → 2.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 (110) hide show
  1. package/README.md +9 -9
  2. package/babel-jsx.cjs +7 -8
  3. package/build/components/activity-indicator-props.d.ts +5 -0
  4. package/build/components/activity-indicator-props.js +1 -0
  5. package/build/components/button-props.d.ts +5 -0
  6. package/build/components/button-props.js +1 -0
  7. package/build/components/flat-list/index.d.ts +3 -3
  8. package/build/components/flat-list/index.js +2 -2
  9. package/build/components/image-background-props.d.ts +7 -0
  10. package/build/components/image-background-props.js +1 -0
  11. package/build/components/image-props.d.ts +5 -0
  12. package/build/components/image-props.js +5 -0
  13. package/build/components/{input-accessory-view.d.ts → input-accessory-view-props.d.ts} +1 -3
  14. package/build/components/input-accessory-view-props.js +5 -0
  15. package/build/components/keyboard-avoiding-view.js +3 -3
  16. package/build/components/modal/index.js +1 -1
  17. package/build/components/pressable-props.d.ts +29 -0
  18. package/build/components/pressable-props.js +12 -0
  19. package/build/components/refresh-control-props.d.ts +16 -0
  20. package/build/components/refresh-control-props.js +9 -0
  21. package/build/components/safe-area-view-props.d.ts +6 -0
  22. package/build/components/safe-area-view-props.js +5 -0
  23. package/build/components/scroll-view/{shared.d.ts → scroll-view-props.d.ts} +4 -27
  24. package/build/components/scroll-view/scroll-view-props.js +1 -0
  25. package/build/components/section-list/index.d.ts +3 -3
  26. package/build/components/switch/switch-props.d.ts +6 -0
  27. package/build/components/switch/switch-props.js +1 -0
  28. package/build/components/text-input/text-input-props.d.ts +5 -0
  29. package/build/components/text-input/text-input-props.js +1 -0
  30. package/build/components/text-props.d.ts +24 -0
  31. package/build/components/text-props.js +6 -0
  32. package/build/components/touchable-native-feedback-props.d.ts +6 -0
  33. package/build/components/touchable-native-feedback-props.js +1 -0
  34. package/build/components/touchable-props.d.ts +15 -0
  35. package/build/components/touchable-props.js +1 -0
  36. package/build/components/touchable-without-feedback-props.d.ts +3 -0
  37. package/build/components/touchable-without-feedback-props.js +1 -0
  38. package/build/components/view-props.d.ts +29 -0
  39. package/build/components/view-props.js +5 -0
  40. package/build/components/virtualized-list/index.d.ts +3 -3
  41. package/build/components/virtualized-list/index.js +42 -37
  42. package/build/components/virtualized-section-list/index.d.ts +3 -3
  43. package/build/descriptor-to-vue.js +2 -2
  44. package/build/index.d.ts +24 -27
  45. package/build/index.js +31 -16
  46. package/build/intrinsic-elements.d.ts +19 -0
  47. package/build/intrinsic-elements.js +1 -0
  48. package/build/jsx-runtime.d.ts +67 -0
  49. package/build/jsx-runtime.js +1 -0
  50. package/build/modules/animated/index.d.ts +3 -1
  51. package/build/modules/animated/index.js +20 -19
  52. package/build/modules/image/index.d.ts +3 -0
  53. package/build/modules/image/index.js +13 -0
  54. package/build/register.d.ts +1 -1
  55. package/build/register.js +13 -33
  56. package/build/renderer/index.js +58 -7
  57. package/build/runtime-helpers/index.d.ts +10 -1
  58. package/build/runtime-helpers/index.js +47 -11
  59. package/intrinsic-tags.cjs +52 -0
  60. package/metro-vue-transformer.cjs +55 -345
  61. package/package.json +12 -8
  62. package/babel-lower-host-primitives.cjs +0 -225
  63. package/build/components/activity-indicator/index.android.d.ts +0 -4
  64. package/build/components/activity-indicator/index.android.js +0 -10
  65. package/build/components/activity-indicator/index.d.ts +0 -1
  66. package/build/components/activity-indicator/index.ios.d.ts +0 -4
  67. package/build/components/activity-indicator/index.ios.js +0 -10
  68. package/build/components/activity-indicator/index.js +0 -4
  69. package/build/components/activity-indicator/shared.d.ts +0 -8
  70. package/build/components/activity-indicator/shared.js +0 -48
  71. package/build/components/button.d.ts +0 -14
  72. package/build/components/button.js +0 -61
  73. package/build/components/image-background.d.ts +0 -9
  74. package/build/components/image-background.js +0 -103
  75. package/build/components/image.d.ts +0 -9
  76. package/build/components/image.js +0 -90
  77. package/build/components/input-accessory-view.js +0 -41
  78. package/build/components/pressable.d.ts +0 -58
  79. package/build/components/pressable.js +0 -233
  80. package/build/components/refresh-control.d.ts +0 -20
  81. package/build/components/refresh-control.js +0 -47
  82. package/build/components/safe-area-view.d.ts +0 -9
  83. package/build/components/safe-area-view.js +0 -33
  84. package/build/components/scroll-view/index.android.d.ts +0 -4
  85. package/build/components/scroll-view/index.android.js +0 -64
  86. package/build/components/scroll-view/index.d.ts +0 -1
  87. package/build/components/scroll-view/index.ios.d.ts +0 -4
  88. package/build/components/scroll-view/index.ios.js +0 -17
  89. package/build/components/scroll-view/index.js +0 -4
  90. package/build/components/scroll-view/shared.js +0 -271
  91. package/build/components/scroll-view/sticky-header.d.ts +0 -7
  92. package/build/components/scroll-view/sticky-header.js +0 -208
  93. package/build/components/switch/index.android.d.ts +0 -12
  94. package/build/components/switch/index.android.js +0 -13
  95. package/build/components/switch/index.d.ts +0 -2
  96. package/build/components/switch/index.ios.d.ts +0 -12
  97. package/build/components/switch/index.ios.js +0 -11
  98. package/build/components/switch/index.js +0 -5
  99. package/build/components/switch/shared.d.ts +0 -23
  100. package/build/components/switch/shared.js +0 -126
  101. package/build/components/text-input/index.d.ts +0 -24
  102. package/build/components/text-input/index.js +0 -260
  103. package/build/components/touchable-native-feedback.d.ts +0 -24
  104. package/build/components/touchable-native-feedback.js +0 -82
  105. package/build/components/touchable.d.ts +0 -50
  106. package/build/components/touchable.js +0 -281
  107. package/build/components.d.ts +0 -53
  108. package/build/components.js +0 -45
  109. package/build/state-style.d.ts +0 -1
  110. package/build/state-style.js +0 -13
@@ -1,271 +0,0 @@
1
- // ScrollView, the Vue lifecycle half. The Fabric tree is nested: a scroll view wraps a
2
- // content view holding the children (RN's ScrollView.js shape). Platform-invariant math
3
- // (decelerationRate, per-axis intrinsics/base style, content-size dedupe, imperative handle,
4
- // aria/role fold) lives in @symbiote-native/components, shared with React; Vue supplies only
5
- // the reactivity - shallowRef host node, setup-scope lastContentSize dedupe, expose() handle -
6
- // the twin of React's useRef + buildScrollViewHandle.
7
- //
8
- // Attrs are untyped, so every field is narrowed with a runtime guard, never a cast. The legacy
9
- // onContentSizeChange callback key MUST be consumed if present: it is NOT a ViewConfig event,
10
- // and forwarding a function prop reaches Fabric and crashes Android's folly::dynamic.
11
- //
12
- // Sticky headers are real: the scroll AnimatedValue, headerLayoutYs cross-talk map, viewport-
13
- // height capture, and onScroll composition (native attach vs Animated.event) live here; the
14
- // per-header component and children wrap live in sticky-header.ts.
15
- import { defineComponent, h, isVNode, markRaw, onBeforeUnmount, ref, shallowRef, watch, } from '@vue/runtime-core';
16
- import { attachStickyScroll, buildScrollViewHandle, didContentSizeChange, forwardScrollEvent, readLayoutDimension, resolveAccessibilityProps, resolveDecelerationRate, resolveScrollForwarding, selectScrollIntrinsics, } from '@symbiote-native/components';
17
- import { AnimatedValue, dlog, event as animatedEvent, isClassNameValue, isNativeAnimatedAvailable, isSymbioteNode, resolveClassName, } from '@symbiote-native/engine';
18
- import { wrapStickyHeaders, } from './sticky-header.js';
19
- import { normalizeVueAttrs } from '../../utils/normalize-attrs.js';
20
- function isHandler(value) {
21
- return typeof value === 'function';
22
- }
23
- // Objects and arrays are valid StyleProp<ViewStyle>; primitives/null degrade to undefined.
24
- function isStyleProp(value) {
25
- return typeof value === 'object' && value !== null;
26
- }
27
- // `class` forwards raw via forwardAttrs, resolved later by the renderer's patchProp. But the
28
- // Android RefreshControl wrap (index.android.ts) reads userStyle alone to splitLayoutProps()
29
- // the outer wrapper BEFORE that later resolution runs, so a class-only layout prop (flex,
30
- // height, gap, …) would otherwise never reach the wrapper.
31
- const isClassNameProp = isClassNameValue;
32
- function asDecelerationRate(value) {
33
- if (typeof value === 'number')
34
- return value;
35
- if (value === 'normal' || value === 'fast')
36
- return value;
37
- return undefined;
38
- }
39
- function isNumberArray(value) {
40
- return (Array.isArray(value) && value.every(entry => typeof entry === 'number'));
41
- }
42
- function isComponent(value) {
43
- return (typeof value === 'function' || (typeof value === 'object' && value !== null));
44
- }
45
- // Prop/handler keys the lifecycle consumes itself; everything else forwards onto the scroll-view
46
- // node. onContentSizeChange is consumed (synthesized from the content onLayout); refreshControl
47
- // and the sticky-header props are consumed by the platform assemble / children wrap and must
48
- // NEVER reach Fabric.
49
- const HANDLED_ATTRS = [
50
- 'style',
51
- 'contentContainerStyle',
52
- 'horizontal',
53
- 'decelerationRate',
54
- 'onContentSizeChange',
55
- 'refreshControl',
56
- 'stickyHeaderIndices',
57
- 'invertStickyHeaders',
58
- 'StickyHeaderComponent',
59
- ];
60
- function forwardAttrs(attrs) {
61
- const result = {};
62
- for (const key of Object.keys(attrs)) {
63
- if (!HANDLED_ATTRS.includes(key))
64
- result[key] = attrs[key];
65
- }
66
- return result;
67
- }
68
- export function createScrollView(platform) {
69
- return defineComponent((_props, { slots, attrs: rawAttrs, expose, emit }) => {
70
- // shallowRef, NOT ref: a plain ref() would run the node through Vue's toReactive(),
71
- // handing back a Proxy the engine's WeakMap mirror doesn't recognize, so
72
- // scrollTo/scrollToEnd/flashScrollIndicators would silently no-op. Same rule as Switch.
73
- const nodeRef = shallowRef(null);
74
- const setNodeRef = (el) => {
75
- nodeRef.value = isSymbioteNode(el) ? el : null;
76
- };
77
- // Lazy getter, not the node captured once: it is null until the element commits, so an
78
- // eager capture would freeze null. Vue twin of useImperativeHandle(ref, buildScrollViewHandle).
79
- expose(buildScrollViewHandle(() => nodeRef.value));
80
- // RN fires the content onLayout on every layout pass; only a real size change emits.
81
- let lastContentSize = null;
82
- // Drives every sticky header's translateY (RN's _scrollAnimatedValue). markRaw: an engine
83
- // object held by identity, never run through toReactive. Allocated unconditionally
84
- // (unused when no sticky headers are flagged), like React's unconditional hook.
85
- const scrollAnimatedValue = markRaw(new AnimatedValue(0));
86
- // Inverted sticky headers stick to the BOTTOM, so they need the viewport height (RN's
87
- // _handleLayout). Fed back into the wrapped headers on the next render.
88
- const viewportHeight = ref(undefined);
89
- // Sticky-header cross-talk (RN ScrollView.js _headerLayoutYs): a child-index -> measured-y
90
- // map so each header learns where the NEXT sticky header starts (its push-off collision
91
- // point). Mutated imperatively from each header's onLayout; the bump ref forces the
92
- // re-render that feeds the freshly-recorded y into the previous header's nextStickyHeaderY.
93
- const headerLayoutYs = new Map();
94
- const bumpHeaderLayout = ref(0);
95
- const onHeaderLayoutY = (index, y) => {
96
- if (headerLayoutYs.get(index) === y)
97
- return;
98
- headerLayoutYs.set(index, y);
99
- dlog(`Vue ScrollView sticky-header layoutY index=${index} y=${y}`);
100
- bumpHeaderLayout.value += 1;
101
- };
102
- // Native sticky-scroll attach (RN attachNativeEvent): when the native module is available,
103
- // the scroll value is driven on the UI thread so interpolations ride scroll natively (no
104
- // JS jitter). A plain non-reactive flag set in render; the post-commit watch reads it once
105
- // the node commits. flush:'post' so the node has a Fabric handle before attachStickyScroll
106
- // reads it. Detached on unmount and re-detached if the node identity changes.
107
- let nativeStickyWanted = false;
108
- let detachStickyScroll;
109
- watch(() => nodeRef.value, node => {
110
- if (detachStickyScroll !== undefined) {
111
- detachStickyScroll();
112
- detachStickyScroll = undefined;
113
- }
114
- if (!nativeStickyWanted || node === null)
115
- return;
116
- detachStickyScroll = attachStickyScroll(node, scrollAnimatedValue);
117
- }, { flush: 'post' });
118
- onBeforeUnmount(() => {
119
- if (detachStickyScroll !== undefined)
120
- detachStickyScroll();
121
- });
122
- return () => {
123
- // Read the bump so a recorded header y re-runs render and feeds nextStickyHeaderY forward.
124
- void bumpHeaderLayout.value;
125
- // Fold kebab template props (:content-container-style) to the camelCase prop surface.
126
- const attrs = normalizeVueAttrs(rawAttrs);
127
- const isHorizontal = attrs.horizontal === true;
128
- const userStyle = isStyleProp(attrs.style) ? attrs.style : undefined;
129
- const classProp = isClassNameProp(attrs.class)
130
- ? attrs.class
131
- : undefined;
132
- const layoutSplitStyle = [
133
- resolveClassName(classProp),
134
- userStyle,
135
- ];
136
- const contentContainerStyle = typeof attrs.contentContainerStyle === 'string'
137
- ? resolveClassName(attrs.contentContainerStyle)
138
- : isStyleProp(attrs.contentContainerStyle)
139
- ? attrs.contentContainerStyle
140
- : undefined;
141
- const stickyHeaderIndices = isNumberArray(attrs.stickyHeaderIndices)
142
- ? attrs.stickyHeaderIndices
143
- : undefined;
144
- const hasStickyHeaders = stickyHeaderIndices !== undefined && stickyHeaderIndices.length > 0;
145
- const invertStickyHeaders = attrs.invertStickyHeaders === true ? true : undefined;
146
- const stickyHeaderComponent = isComponent(attrs.StickyHeaderComponent)
147
- ? attrs.StickyHeaderComponent
148
- : undefined;
149
- const { scrollViewIntrinsic, contentIntrinsic, scrollViewBaseStyle, contentStyle, } = selectScrollIntrinsics(isHorizontal, contentContainerStyle);
150
- // RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`);
151
- // horizontal forwards only when defined (load-bearing on iOS's RCTScrollView axis).
152
- const outerProps = {
153
- ...resolveAccessibilityProps(forwardAttrs(attrs)),
154
- };
155
- outerProps.nestedScrollEnabled =
156
- typeof attrs.nestedScrollEnabled === 'boolean'
157
- ? attrs.nestedScrollEnabled
158
- : true;
159
- if (attrs.horizontal !== undefined)
160
- outerProps.horizontal = attrs.horizontal;
161
- const decel = asDecelerationRate(attrs.decelerationRate);
162
- if (decel !== undefined)
163
- outerProps.decelerationRate = resolveDecelerationRate(decel);
164
- // When sticky headers are active, the offset must reach the AnimatedValue (RN's
165
- // _scrollAnimatedValueAttachment). forwardAttrs already put the user's onScroll/onLayout/
166
- // scrollEventThrottle on outerProps; here we override per resolveScrollForwarding's
167
- // decisions (which path, throttle default, inverted capture).
168
- const nativeStickyAvailable = hasStickyHeaders && isNativeAnimatedAvailable();
169
- nativeStickyWanted = nativeStickyAvailable;
170
- const userThrottle = typeof attrs.scrollEventThrottle === 'number'
171
- ? attrs.scrollEventThrottle
172
- : undefined;
173
- const forwarding = resolveScrollForwarding({
174
- hasStickyHeaders,
175
- nativeStickyAvailable,
176
- invertStickyHeaders,
177
- scrollEventThrottle: userThrottle,
178
- maintainVisibleContentPosition: attrs.maintainVisibleContentPosition,
179
- snapToAlignment: attrs.snapToAlignment,
180
- });
181
- if (hasStickyHeaders) {
182
- const userOnScroll = isHandler(attrs.onScroll)
183
- ? attrs.onScroll
184
- : undefined;
185
- if (forwarding.mode === 'sticky-js') {
186
- // JS fallback (no native module): correct, but lags a frame under fast scroll, which
187
- // the native path removes on a real host.
188
- outerProps.onScroll = animatedEvent([{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }], userOnScroll === undefined
189
- ? undefined
190
- : {
191
- listener: (...args) => forwardScrollEvent(userOnScroll, args),
192
- });
193
- }
194
- // Native path: the value is driven on the UI thread by the post-commit watch above, so
195
- // onScroll forwards untouched.
196
- if (forwarding.scrollEventThrottle !== undefined) {
197
- outerProps.scrollEventThrottle = forwarding.scrollEventThrottle;
198
- }
199
- // Capture the viewport height for inverted sticky headers (RN _handleLayout), then call
200
- // the user's handler.
201
- if (forwarding.capturesViewportHeight) {
202
- const userOnLayout = isHandler(attrs.onLayout)
203
- ? attrs.onLayout
204
- : undefined;
205
- outerProps.onLayout = (event) => {
206
- const height = readLayoutDimension(event, 'height');
207
- if (height !== undefined)
208
- viewportHeight.value = height;
209
- if (userOnLayout !== undefined)
210
- userOnLayout(event);
211
- };
212
- }
213
- }
214
- dlog(`Vue ScrollView -> ${scrollViewIntrinsic} (horizontal=${isHorizontal} sticky=${hasStickyHeaders})`);
215
- // `collapsable: false` keeps the layout-only content view as a real native view: Android
216
- // Fabric view-flattens it away otherwise, hoisting the cells as direct children of the
217
- // scroll view (which hosts exactly one) - an addViewAt crash. iOS never flattens.
218
- const contentProps = {
219
- style: contentStyle,
220
- collapsable: false,
221
- };
222
- if (forwarding.collapsableChildren) {
223
- contentProps.collapsableChildren = false;
224
- }
225
- // Synthesized from the content view's own onLayout (RN _handleContentOnLayout); emit only
226
- // on a real size change.
227
- contentProps.onLayout = (event) => {
228
- const width = readLayoutDimension(event, 'width');
229
- const height = readLayoutDimension(event, 'height');
230
- if (width === undefined || height === undefined)
231
- return;
232
- if (!didContentSizeChange(lastContentSize, { width, height }))
233
- return;
234
- lastContentSize = { width, height };
235
- dlog(`Vue ScrollView contentSizeChange ${width}x${height}`);
236
- emit('contentSizeChange', width, height);
237
- };
238
- const slotChildren = slots.default !== undefined ? slots.default() : [];
239
- const contentChildren = hasStickyHeaders
240
- ? wrapStickyHeaders(slotChildren, stickyHeaderIndices, scrollAnimatedValue, invertStickyHeaders, viewportHeight.value, stickyHeaderComponent, headerLayoutYs, onHeaderLayoutY)
241
- : slotChildren;
242
- const content = h(contentIntrinsic, contentProps, contentChildren);
243
- // Base style UNDER user style so an explicit user value (height, flexDirection) still wins.
244
- const scrollProps = {
245
- ...outerProps,
246
- style: [scrollViewBaseStyle, userStyle],
247
- ref: setNodeRef,
248
- };
249
- const refreshControl = isVNode(attrs.refreshControl)
250
- ? attrs.refreshControl
251
- : undefined;
252
- return platform.assemble({
253
- scrollViewIntrinsic,
254
- scrollProps,
255
- content,
256
- refreshControl,
257
- scrollViewBaseStyle,
258
- userStyle,
259
- layoutSplitStyle,
260
- scrollOuterProps: outerProps,
261
- setNodeRef,
262
- });
263
- };
264
- }, {
265
- name: 'ScrollView',
266
- inheritAttrs: false,
267
- emits: {
268
- contentSizeChange: (_width, _height) => true,
269
- },
270
- });
271
- }
@@ -1,7 +0,0 @@
1
- import { type Component, type VNode } from '@vue/runtime-core';
2
- import { AnimatedValue } from '@symbiote-native/engine';
3
- export type IStickyHeaderComponentType = Component;
4
- export declare const ScrollViewStickyHeader: import("vue").DefineComponent<{}, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
5
- [key: string]: any;
6
- }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
7
- export declare function wrapStickyHeaders(children: VNode[], stickyHeaderIndices: number[] | undefined, scrollAnimatedValue: AnimatedValue, invertStickyHeaders: boolean | undefined, scrollViewHeight: number | undefined, StickyHeaderComponent: IStickyHeaderComponentType | undefined, headerLayoutYs: ReadonlyMap<number, number>, onHeaderLayoutY: (index: number, y: number) => void): VNode[];
@@ -1,208 +0,0 @@
1
- // Sticky headers: the Vue twin of adapters/react/src/scroll-view-sticky-header.tsx, the JS layer
2
- // RN implements in ScrollView.js / ScrollViewStickyHeader.js.
3
- //
4
- // RN does stickiness PURELY IN JS: ScrollView.js wraps each flagged child in a
5
- // ScrollViewStickyHeader fed by a single _scrollAnimatedValue that Animated.event drives from
6
- // onScroll. The native scroll view does NOT honor the index array itself, so forwarding
7
- // stickyHeaderIndices to native is a silent no-op - we replicate the JS layer instead. The
8
- // interpolation math (non-inverted + inverted) lives framework-agnostic in
9
- // @symbiote-native/components (computeStickyInterpolation); this file holds the Vue component
10
- // shell, layout state, and child-wrapping. Vue supplies only the reactive lifecycle
11
- // (refs/watch instead of useState/useEffect).
12
- import { defineComponent, h, isVNode, onBeforeUnmount, ref, shallowRef, watchEffect, markRaw, } from '@vue/runtime-core';
13
- import { AnimatedValue, AnimatedInterpolation, Platform, dlog, } from '@symbiote-native/engine';
14
- import { createInitialStickyState, nextStickyHeaderY, readLayoutNumber, reduceSticky, STICKY_HEADER_Z_INDEX, } from '@symbiote-native/components';
15
- import { Animated } from '../../modules/animated/index.js';
16
- function isHandler(value) {
17
- return typeof value === 'function';
18
- }
19
- function isRecord(value) {
20
- return typeof value === 'object' && value !== null;
21
- }
22
- function isAnimatedValue(value) {
23
- return value instanceof AnimatedValue;
24
- }
25
- // So the sticky wrapper can forward layout to the child (RN calls the child's onLayout after its own).
26
- function readChildOnLayout(child) {
27
- if (!isRecord(child.props))
28
- return undefined;
29
- const handler = child.props.onLayout;
30
- return isHandler(handler) ? handler : undefined;
31
- }
32
- // One sticky header. Measures its own y/height via onLayout, interpolates the shared scroll
33
- // offset into a translateY pinning it to the top (or bottom, inverted) until the next header
34
- // collides with it, driven through the native driver when available (no JS jitter). Ported from
35
- // ScrollViewStickyHeader.js, including the Fabric ShadowTree debounce path. inheritAttrs:false
36
- // so the IStickyHeaderProps inputs never fall through onto Animated.View and reach Fabric as
37
- // props (scrollAnimatedValue on a host node would crash Android's folly::dynamic).
38
- export const ScrollViewStickyHeader = defineComponent({
39
- name: 'ScrollViewStickyHeader',
40
- inheritAttrs: false,
41
- setup(_props, { attrs, slots }) {
42
- // Read once - stable across renders (the same markRaw'd AnimatedValue). Held by IDENTITY, never
43
- // run through toReactive. A fresh fallback keeps working if wrapStickyHeaders ever fails to supply it.
44
- const scrollAnimatedValue = isAnimatedValue(attrs.scrollAnimatedValue)
45
- ? attrs.scrollAnimatedValue
46
- : markRaw(new AnimatedValue(0));
47
- // Mutated in place by reduceSticky. A plain object, not a ref: render reads it gated by the
48
- // reactive `version` bump + `animatedTranslateY` below. DECISIONS (zero-swallow gate,
49
- // debounce delay, rebuild ranges) all live in reduceSticky.
50
- const state = createInitialStickyState();
51
- // Bumped when the reducer commits a new translateY, forcing render to re-read state.translateY.
52
- const version = ref(0);
53
- // Engine node -> shallowRef (identity rule); the un-measured identity stub until the
54
- // rebuild-interpolation effect below replaces it.
55
- const animatedTranslateY = shallowRef(scrollAnimatedValue.interpolate({
56
- inputRange: [-1, 0],
57
- outputRange: [0, 0],
58
- }));
59
- let debounceTimer;
60
- // Held so the next rebuild detaches the old listener and onBeforeUnmount cleans up.
61
- let interpolation;
62
- let listenerId;
63
- const inputs = () => ({
64
- os: Platform.OS,
65
- inverted: typeof attrs.inverted === 'boolean' ? attrs.inverted : undefined,
66
- scrollViewHeight: typeof attrs.scrollViewHeight === 'number'
67
- ? attrs.scrollViewHeight
68
- : undefined,
69
- nextHeaderLayoutY: typeof attrs.nextHeaderLayoutY === 'number'
70
- ? attrs.nextHeaderLayoutY
71
- : undefined,
72
- });
73
- const runEffects = (effects) => {
74
- for (const effect of effects) {
75
- switch (effect.kind) {
76
- case 'rebuild-interpolation': {
77
- // Detach the old listener, build a fresh interpolation, and wire the settled-value listener.
78
- if (interpolation !== undefined && listenerId !== undefined) {
79
- interpolation.removeListener(listenerId);
80
- listenerId = undefined;
81
- }
82
- const next = scrollAnimatedValue.interpolate({
83
- inputRange: effect.inputRange,
84
- outputRange: effect.outputRange,
85
- });
86
- listenerId = next.addListener(({ value }) => {
87
- if (typeof value === 'number')
88
- dispatch({ kind: 'animated-tick', value });
89
- });
90
- interpolation = next;
91
- animatedTranslateY.value = next;
92
- break;
93
- }
94
- case 'schedule-debounce':
95
- // The animated value updates several times per frame; debounce the settled value so
96
- // hit detection stays current (a Fabric issue, worse on Android).
97
- if (debounceTimer !== undefined)
98
- clearTimeout(debounceTimer);
99
- debounceTimer = setTimeout(() => {
100
- debounceTimer = undefined;
101
- dispatch({ kind: 'debounce-fired', value: effect.value });
102
- }, effect.delay);
103
- break;
104
- case 'apply-passthrough':
105
- version.value += 1;
106
- break;
107
- case 'record-header-y':
108
- // Vue records through attrs.onLayout (the wrapper closure); the reducer emits no index for it.
109
- break;
110
- }
111
- }
112
- };
113
- const dispatch = (action) => {
114
- runEffects(reduceSticky(state, action, inputs()).effects);
115
- };
116
- // Rebuild whenever the collision/viewport inputs change ([inverted, scrollViewHeight,
117
- // nextHeaderLayoutY]); also runs once on mount.
118
- watchEffect(() => {
119
- dispatch({ kind: 'inputs-changed' });
120
- });
121
- onBeforeUnmount(() => {
122
- if (interpolation !== undefined && listenerId !== undefined) {
123
- interpolation.removeListener(listenerId);
124
- }
125
- if (debounceTimer !== undefined)
126
- clearTimeout(debounceTimer);
127
- });
128
- // Dispatch the layout, fire the wrapper's recorder (onHeaderLayoutY via attrs.onLayout), then
129
- // the child's own onLayout. Matches RN ScrollViewStickyHeader.js._onLayout.
130
- const onLayout = (event) => {
131
- const y = readLayoutNumber(event, 'y');
132
- const height = readLayoutNumber(event, 'height');
133
- // Keep the previous value when a field is absent (RN sets state only on a defined read).
134
- dispatch({
135
- kind: 'layout',
136
- y: y ?? state.layoutY,
137
- height: height ?? state.layoutHeight,
138
- });
139
- const recorder = attrs.onLayout;
140
- if (isHandler(recorder))
141
- recorder(event);
142
- const children = slots.default !== undefined ? slots.default() : [];
143
- const child = children[0];
144
- const childOnLayout = isVNode(child)
145
- ? readChildOnLayout(child)
146
- : undefined;
147
- if (childOnLayout !== undefined)
148
- childOnLayout(event);
149
- };
150
- return () => {
151
- // Read the version bump so a committed translateY re-runs render.
152
- void version.value;
153
- // The EXPLICIT debounced translateY overrides the committed transform for hit-testing, while
154
- // animatedTranslateY does the smooth (native-driven) pin.
155
- const passthroughAnimatedPropExplicitValues = state.translateY !== null
156
- ? { style: { transform: [{ translateY: state.translateY }] } }
157
- : null;
158
- // collapsable:false keeps the wrapper a real Yoga node; zIndex makes the pinned header paint
159
- // OVER the rows scrolling under it.
160
- return h(Animated.View, {
161
- style: {
162
- transform: [{ translateY: animatedTranslateY.value }],
163
- zIndex: STICKY_HEADER_Z_INDEX,
164
- },
165
- onLayout,
166
- collapsable: false,
167
- passthroughAnimatedPropExplicitValues,
168
- }, { default: () => (slots.default !== undefined ? slots.default() : []) });
169
- };
170
- },
171
- });
172
- // Wrap each child flagged by `stickyHeaderIndices` in the sticky header component, fed by the
173
- // shared scroll AnimatedValue. Returns the children unchanged when no indices are flagged.
174
- //
175
- // Cross-talk plumbing (RN's _headerLayoutYs + _onStickyHeaderLayout): headerLayoutYs is a
176
- // child-index -> measured-y map; each header reports its own y through onHeaderLayoutY, and we
177
- // feed every header the y of the NEXT flagged header (the collision point past which it scrolls
178
- // off). The LAST flagged header has no successor, so its nextHeaderLayoutY stays undefined and
179
- // it sticks indefinitely (correct).
180
- export function wrapStickyHeaders(children, stickyHeaderIndices, scrollAnimatedValue, invertStickyHeaders, scrollViewHeight, StickyHeaderComponent, headerLayoutYs, onHeaderLayoutY) {
181
- if (stickyHeaderIndices === undefined || stickyHeaderIndices.length === 0)
182
- return children;
183
- const Wrapper = StickyHeaderComponent ?? ScrollViewStickyHeader;
184
- return children.map((child, index) => {
185
- const indexOfIndex = stickyHeaderIndices.indexOf(index);
186
- if (indexOfIndex === -1 || !isVNode(child))
187
- return child;
188
- // The next flagged header's measured y. undefined until that header has measured (or for the last).
189
- const nextIndex = stickyHeaderIndices[indexOfIndex + 1];
190
- const nextHeaderLayoutY = nextStickyHeaderY(stickyHeaderIndices, indexOfIndex, headerLayoutYs);
191
- dlog(`Vue ScrollView sticky-header wrap index=${index} next=${nextIndex} nextY=${nextHeaderLayoutY}`);
192
- const props = {
193
- key: child.key ?? `sticky-${index}`,
194
- nextHeaderLayoutY,
195
- // Record this header's own y into the parent map; the lookup above feeds it forward to the
196
- // previous header on the resulting re-render (the headerLayoutYs bump in scroll-view-shared).
197
- onLayout: (event) => {
198
- const y = readLayoutNumber(event, 'y');
199
- if (y !== undefined)
200
- onHeaderLayoutY(index, y);
201
- },
202
- scrollAnimatedValue,
203
- inverted: invertStickyHeaders,
204
- scrollViewHeight,
205
- };
206
- return h(Wrapper, props, { default: () => [child] });
207
- });
208
- }
@@ -1,12 +0,0 @@
1
- export declare const Switch: import("vue").DefineSetupFnComponent<import("./shared").ISwitchProps, {
2
- valueChange: (value: boolean, event: import("@symbiote-native/engine").ISymbioteEvent) => boolean;
3
- 'update:modelValue': (value: boolean) => boolean;
4
- 'update:value': (value: boolean) => boolean;
5
- }, {}, Omit<import("@symbiote-native/components").ISwitchProps, "onValueChange"> & {
6
- modelValue?: boolean;
7
- class?: import("@symbiote-native/engine").IClassNameValue;
8
- } & {
9
- "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
10
- "onUpdate:value"?: ((value: boolean) => any) | undefined;
11
- onValueChange?: ((value: boolean, event: import("@symbiote-native/engine").ISymbioteEvent) => any) | undefined;
12
- }, import("vue").PublicProps>;
@@ -1,13 +0,0 @@
1
- // Switch on Android: AndroidSwitch takes trackColorForTrue / trackColorForFalse plus
2
- // trackTintColor (the color for the CURRENT value), and snaps native back via the
3
- // `setNativeValue` command. Metro picks this on an Android host; no Platform.OS read.
4
- // Mirrors the React adapter's Android binding.
5
- import { createSwitch } from './shared.js';
6
- export const Switch = createSwitch({
7
- snapBackCommand: 'setNativeValue',
8
- trackColorProps: (value, trackColor) => ({
9
- trackColorForFalse: trackColor?.false,
10
- trackColorForTrue: trackColor?.true,
11
- trackTintColor: value ? trackColor?.true : trackColor?.false,
12
- }),
13
- });
@@ -1,2 +0,0 @@
1
- export * from './index.ios';
2
- export type { ISwitchProps, ISwitchTrackColor } from './shared';
@@ -1,12 +0,0 @@
1
- export declare const Switch: import("vue").DefineSetupFnComponent<import("./shared").ISwitchProps, {
2
- valueChange: (value: boolean, event: import("@symbiote-native/engine").ISymbioteEvent) => boolean;
3
- 'update:modelValue': (value: boolean) => boolean;
4
- 'update:value': (value: boolean) => boolean;
5
- }, {}, Omit<import("@symbiote-native/components").ISwitchProps, "onValueChange"> & {
6
- modelValue?: boolean;
7
- class?: import("@symbiote-native/engine").IClassNameValue;
8
- } & {
9
- "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
10
- "onUpdate:value"?: ((value: boolean) => any) | undefined;
11
- onValueChange?: ((value: boolean, event: import("@symbiote-native/engine").ISymbioteEvent) => any) | undefined;
12
- }, import("vue").PublicProps>;
@@ -1,11 +0,0 @@
1
- // Switch on iOS: the native Switch takes onTintColor (ON-track) / tintColor (OFF-track) and
2
- // snaps native back via the `setValue` command. Also the base (switch.ts re-exports it) for
3
- // headless / web. Mirrors the React adapter's iOS binding.
4
- import { createSwitch } from './shared.js';
5
- export const Switch = createSwitch({
6
- snapBackCommand: 'setValue',
7
- trackColorProps: (_value, trackColor) => ({
8
- onTintColor: trackColor?.true,
9
- tintColor: trackColor?.false,
10
- }),
11
- });
@@ -1,5 +0,0 @@
1
- // Base / default Switch. Re-exports the iOS build. Metro overrides this with switch.ios.ts /
2
- // switch.android.ts on a real host; under tsx / tsc / web the host resolves here. Filename is
3
- // the selector, no Platform.OS read. Switch is the first component to bring the
4
- // state half (the reducer) into the Vue adapter: render-and-state, not render-only.
5
- export * from './index.ios.js';
@@ -1,23 +0,0 @@
1
- import { type ISwitchPlatform, type ISwitchProps as ISwitchBaseProps, type ISwitchTrackColor } from '@symbiote-native/components';
2
- import { type IClassNameValue, type ISymbioteEvent } from '@symbiote-native/engine';
3
- type ISwitchHostPlatform = ISwitchPlatform & {
4
- snapBackCommand: string;
5
- };
6
- export type ISwitchProps = Omit<ISwitchBaseProps, 'onValueChange'> & {
7
- modelValue?: boolean;
8
- class?: IClassNameValue;
9
- };
10
- export type { ISwitchTrackColor };
11
- type ISwitchEmits = {
12
- valueChange: (value: boolean, event: ISymbioteEvent) => boolean;
13
- 'update:modelValue': (value: boolean) => boolean;
14
- 'update:value': (value: boolean) => boolean;
15
- };
16
- export declare function createSwitch(platform: ISwitchHostPlatform): import("vue").DefineSetupFnComponent<ISwitchProps, ISwitchEmits, {}, Omit<ISwitchBaseProps, "onValueChange"> & {
17
- modelValue?: boolean;
18
- class?: IClassNameValue;
19
- } & {
20
- "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
21
- "onUpdate:value"?: ((value: boolean) => any) | undefined;
22
- onValueChange?: ((value: boolean, event: ISymbioteEvent) => any) | undefined;
23
- }, import("vue").PublicProps>;