@symbiote-native/svelte 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/build/bootstrap.d.ts +7 -0
  4. package/build/bootstrap.js +17 -0
  5. package/build/class-value.d.ts +6 -0
  6. package/build/class-value.js +81 -0
  7. package/build/components/RefreshControl.svelte +38 -0
  8. package/build/components/RefreshControl.svelte.d.ts +5 -0
  9. package/build/components/SafeAreaView.svelte +34 -0
  10. package/build/components/SafeAreaView.svelte.d.ts +5 -0
  11. package/build/components/Text.svelte +25 -0
  12. package/build/components/Text.svelte.d.ts +5 -0
  13. package/build/components/View.svelte +41 -0
  14. package/build/components/View.svelte.d.ts +5 -0
  15. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +3 -0
  16. package/build/components/activity-indicator/activity-indicator-platform.android.js +5 -0
  17. package/build/components/activity-indicator/activity-indicator-platform.d.ts +1 -0
  18. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +3 -0
  19. package/build/components/activity-indicator/activity-indicator-platform.ios.js +6 -0
  20. package/build/components/activity-indicator/activity-indicator-platform.js +5 -0
  21. package/build/components/activity-indicator/activity-indicator-props.d.ts +5 -0
  22. package/build/components/activity-indicator/activity-indicator-props.js +1 -0
  23. package/build/components/activity-indicator/index.svelte +57 -0
  24. package/build/components/activity-indicator/index.svelte.d.ts +5 -0
  25. package/build/components/button-props.d.ts +5 -0
  26. package/build/components/button-props.js +1 -0
  27. package/build/components/button.svelte +71 -0
  28. package/build/components/button.svelte.d.ts +5 -0
  29. package/build/components/flat-list/flat-list-props.d.ts +10 -0
  30. package/build/components/flat-list/flat-list-props.js +1 -0
  31. package/build/components/flat-list/index.svelte +254 -0
  32. package/build/components/flat-list/index.svelte.d.ts +79 -0
  33. package/build/components/image/image-logic.d.ts +2 -0
  34. package/build/components/image/image-logic.js +125 -0
  35. package/build/components/image/image-props.d.ts +5 -0
  36. package/build/components/image/image-props.js +1 -0
  37. package/build/components/image/index.d.ts +3 -0
  38. package/build/components/image/index.js +10 -0
  39. package/build/components/image/index.svelte +66 -0
  40. package/build/components/image/index.svelte.d.ts +5 -0
  41. package/build/components/image-background/image-background-props.d.ts +10 -0
  42. package/build/components/image-background/image-background-props.js +1 -0
  43. package/build/components/image-background/index.svelte +81 -0
  44. package/build/components/image-background/index.svelte.d.ts +5 -0
  45. package/build/components/index.d.ts +50 -0
  46. package/build/components/index.js +24 -0
  47. package/build/components/input-accessory-view/index.svelte +48 -0
  48. package/build/components/input-accessory-view/index.svelte.d.ts +5 -0
  49. package/build/components/input-accessory-view/input-accessory-view-props.d.ts +11 -0
  50. package/build/components/input-accessory-view/input-accessory-view-props.js +1 -0
  51. package/build/components/keyboard-avoiding-view/index.svelte +135 -0
  52. package/build/components/keyboard-avoiding-view/index.svelte.d.ts +5 -0
  53. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.d.ts +15 -0
  54. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.js +1 -0
  55. package/build/components/modal/index.svelte +126 -0
  56. package/build/components/modal/index.svelte.d.ts +5 -0
  57. package/build/components/modal/modal-props.d.ts +24 -0
  58. package/build/components/modal/modal-props.js +1 -0
  59. package/build/components/pressable/index.svelte +160 -0
  60. package/build/components/pressable/index.svelte.d.ts +5 -0
  61. package/build/components/pressable/pressable-props.d.ts +27 -0
  62. package/build/components/pressable/pressable-props.js +1 -0
  63. package/build/components/refresh-control-props.d.ts +19 -0
  64. package/build/components/refresh-control-props.js +1 -0
  65. package/build/components/safe-area-view-props.d.ts +10 -0
  66. package/build/components/safe-area-view-props.js +1 -0
  67. package/build/components/scroll-view/index.svelte +298 -0
  68. package/build/components/scroll-view/index.svelte.d.ts +18 -0
  69. package/build/components/scroll-view/scroll-view-platform-types.d.ts +3 -0
  70. package/build/components/scroll-view/scroll-view-platform-types.js +1 -0
  71. package/build/components/scroll-view/scroll-view-platform.android.d.ts +3 -0
  72. package/build/components/scroll-view/scroll-view-platform.android.js +3 -0
  73. package/build/components/scroll-view/scroll-view-platform.d.ts +1 -0
  74. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +3 -0
  75. package/build/components/scroll-view/scroll-view-platform.ios.js +3 -0
  76. package/build/components/scroll-view/scroll-view-platform.js +4 -0
  77. package/build/components/scroll-view/scroll-view-props.d.ts +78 -0
  78. package/build/components/scroll-view/scroll-view-props.js +1 -0
  79. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +7 -0
  80. package/build/components/scroll-view/scroll-view-sticky-context.js +1 -0
  81. package/build/components/scroll-view/sticky-header-props.d.ts +10 -0
  82. package/build/components/scroll-view/sticky-header-props.js +1 -0
  83. package/build/components/scroll-view/sticky-header.svelte +254 -0
  84. package/build/components/scroll-view/sticky-header.svelte.d.ts +5 -0
  85. package/build/components/section-list/index.svelte +107 -0
  86. package/build/components/section-list/index.svelte.d.ts +57 -0
  87. package/build/components/section-list/section-list-props.d.ts +1 -0
  88. package/build/components/section-list/section-list-props.js +1 -0
  89. package/build/components/switch/index.svelte +123 -0
  90. package/build/components/switch/index.svelte.d.ts +5 -0
  91. package/build/components/switch/switch-platform-types.d.ts +5 -0
  92. package/build/components/switch/switch-platform-types.js +1 -0
  93. package/build/components/switch/switch-platform.android.d.ts +3 -0
  94. package/build/components/switch/switch-platform.android.js +8 -0
  95. package/build/components/switch/switch-platform.d.ts +1 -0
  96. package/build/components/switch/switch-platform.ios.d.ts +3 -0
  97. package/build/components/switch/switch-platform.ios.js +7 -0
  98. package/build/components/switch/switch-platform.js +5 -0
  99. package/build/components/switch/switch-props.d.ts +5 -0
  100. package/build/components/switch/switch-props.js +1 -0
  101. package/build/components/text-input/index.svelte +300 -0
  102. package/build/components/text-input/index.svelte.d.ts +12 -0
  103. package/build/components/text-input/text-input-props.d.ts +5 -0
  104. package/build/components/text-input/text-input-props.js +1 -0
  105. package/build/components/text-props.d.ts +23 -0
  106. package/build/components/text-props.js +1 -0
  107. package/build/components/touchable-base-props.d.ts +10 -0
  108. package/build/components/touchable-base-props.js +1 -0
  109. package/build/components/touchable-highlight/index.svelte +39 -0
  110. package/build/components/touchable-highlight/index.svelte.d.ts +5 -0
  111. package/build/components/touchable-highlight/touchable-highlight-props.d.ts +5 -0
  112. package/build/components/touchable-highlight/touchable-highlight-props.js +1 -0
  113. package/build/components/touchable-native-feedback/index.d.ts +9 -0
  114. package/build/components/touchable-native-feedback/index.js +14 -0
  115. package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
  116. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  117. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +44 -0
  118. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +5 -0
  119. package/build/components/touchable-opacity/index.svelte +124 -0
  120. package/build/components/touchable-opacity/index.svelte.d.ts +5 -0
  121. package/build/components/touchable-opacity/touchable-opacity-props.d.ts +5 -0
  122. package/build/components/touchable-opacity/touchable-opacity-props.js +1 -0
  123. package/build/components/touchable-without-feedback/index.svelte +19 -0
  124. package/build/components/touchable-without-feedback/index.svelte.d.ts +5 -0
  125. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +2 -0
  126. package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
  127. package/build/components/view-props.d.ts +29 -0
  128. package/build/components/view-props.js +1 -0
  129. package/build/components/virtualized-list/index.svelte +702 -0
  130. package/build/components/virtualized-list/index.svelte.d.ts +79 -0
  131. package/build/components/virtualized-list/virtualized-list-props.d.ts +70 -0
  132. package/build/components/virtualized-list/virtualized-list-props.js +73 -0
  133. package/build/components/virtualized-section-list/index.svelte +206 -0
  134. package/build/components/virtualized-section-list/index.svelte.d.ts +57 -0
  135. package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +62 -0
  136. package/build/components/virtualized-section-list/virtualized-section-list-props.js +1 -0
  137. package/build/create-tunnel/index.d.ts +3 -0
  138. package/build/create-tunnel/index.js +3 -0
  139. package/build/create-tunnel/tunnel-in.svelte +24 -0
  140. package/build/create-tunnel/tunnel-in.svelte.d.ts +9 -0
  141. package/build/create-tunnel/tunnel-out.svelte +14 -0
  142. package/build/create-tunnel/tunnel-out.svelte.d.ts +7 -0
  143. package/build/create-tunnel/tunnel.d.ts +9 -0
  144. package/build/create-tunnel/tunnel.js +9 -0
  145. package/build/descriptor-to-svelte.d.ts +7 -0
  146. package/build/descriptor-to-svelte.js +104 -0
  147. package/build/dom-shim/comment.d.ts +9 -0
  148. package/build/dom-shim/comment.js +24 -0
  149. package/build/dom-shim/document-fragment.d.ts +8 -0
  150. package/build/dom-shim/document-fragment.js +23 -0
  151. package/build/dom-shim/document.d.ts +23 -0
  152. package/build/dom-shim/document.js +61 -0
  153. package/build/dom-shim/element.d.ts +22 -0
  154. package/build/dom-shim/element.js +130 -0
  155. package/build/dom-shim/index.d.ts +5 -0
  156. package/build/dom-shim/index.js +7 -0
  157. package/build/dom-shim/patch-globals.d.ts +2 -0
  158. package/build/dom-shim/patch-globals.js +94 -0
  159. package/build/dom-shim/shim-node.d.ts +30 -0
  160. package/build/dom-shim/shim-node.js +207 -0
  161. package/build/dom-shim/text.d.ts +14 -0
  162. package/build/dom-shim/text.js +97 -0
  163. package/build/host-instance.d.ts +5 -0
  164. package/build/host-instance.js +48 -0
  165. package/build/index.d.ts +28 -0
  166. package/build/index.js +77 -0
  167. package/build/modules/accessibility-info/index.android.d.ts +2 -0
  168. package/build/modules/accessibility-info/index.android.js +4 -0
  169. package/build/modules/accessibility-info/index.d.ts +1 -0
  170. package/build/modules/accessibility-info/index.ios.d.ts +2 -0
  171. package/build/modules/accessibility-info/index.ios.js +3 -0
  172. package/build/modules/accessibility-info/index.js +5 -0
  173. package/build/modules/animated/AnimatedImage.svelte +115 -0
  174. package/build/modules/animated/AnimatedImage.svelte.d.ts +5 -0
  175. package/build/modules/animated/AnimatedScrollView.svelte +92 -0
  176. package/build/modules/animated/AnimatedScrollView.svelte.d.ts +17 -0
  177. package/build/modules/animated/AnimatedText.svelte +58 -0
  178. package/build/modules/animated/AnimatedText.svelte.d.ts +5 -0
  179. package/build/modules/animated/AnimatedView.svelte +95 -0
  180. package/build/modules/animated/AnimatedView.svelte.d.ts +5 -0
  181. package/build/modules/animated/animated-component-props.d.ts +7 -0
  182. package/build/modules/animated/animated-component-props.js +1 -0
  183. package/build/modules/animated/index.d.ts +58 -0
  184. package/build/modules/animated/index.js +69 -0
  185. package/build/modules/app-registry/index.d.ts +7 -0
  186. package/build/modules/app-registry/index.js +28 -0
  187. package/build/modules/status-bar/index.d.ts +5 -0
  188. package/build/modules/status-bar/index.js +21 -0
  189. package/build/modules/status-bar/index.svelte +20 -0
  190. package/build/modules/status-bar/index.svelte.d.ts +4 -0
  191. package/build/native-view-bridge.d.ts +4 -0
  192. package/build/native-view-bridge.js +27 -0
  193. package/build/preprocessor/forbid-web-only-constructs.d.ts +8 -0
  194. package/build/preprocessor/forbid-web-only-constructs.js +204 -0
  195. package/build/preprocessor/scoped-styles.d.ts +10 -0
  196. package/build/preprocessor/scoped-styles.js +399 -0
  197. package/build/render.d.ts +7 -0
  198. package/build/render.js +46 -0
  199. package/build/root-element.d.ts +3 -0
  200. package/build/root-element.js +27 -0
  201. package/build/runes/attachments.d.ts +5 -0
  202. package/build/runes/attachments.js +73 -0
  203. package/build/runes/dimensions-value.d.ts +4 -0
  204. package/build/runes/dimensions-value.js +30 -0
  205. package/build/runes/media-query.d.ts +8 -0
  206. package/build/runes/media-query.js +38 -0
  207. package/build/runes/use-color-scheme.svelte.d.ts +4 -0
  208. package/build/runes/use-color-scheme.svelte.js +25 -0
  209. package/build/runes/use-window-dimensions.svelte.d.ts +4 -0
  210. package/build/runes/use-window-dimensions.svelte.js +43 -0
  211. package/build/runes/window.d.ts +6 -0
  212. package/build/runes/window.js +28 -0
  213. package/build/scope-token.d.ts +2 -0
  214. package/build/scope-token.js +33 -0
  215. package/build/style-scope.d.ts +1 -0
  216. package/build/style-scope.js +29 -0
  217. package/metro-css-parser.cjs +9 -0
  218. package/metro-svelte-transformer.cjs +156 -0
  219. package/package.json +83 -0
@@ -0,0 +1,702 @@
1
+ <script lang="ts" module>
2
+ // VirtualizedList: real windowing over a hand-authored minimal scroll host. Only cells whose
3
+ // computed offset falls inside the visible window (plus a leading/trailing buffer) render;
4
+ // the rest collapses into two spacer symbiote-view nodes.
5
+ //
6
+ // The orchestration - window recompute, edge-reached, viewability, batch fill, MVCP, imperative
7
+ // scrolls - is the framework-agnostic `reduceList` state machine in @symbiote-native/components
8
+ // (state/virtualized-list-reducer), shared verbatim with React/Vue. This file supplies only
9
+ // Svelte's lifecycle: turns native events into ACTIONS, holds one plain (non-reactive) state cell
10
+ // (`listState`), runs the returned EFFECTS with Svelte primitives, and renders the windowed slice
11
+ // with `{#each plan.cells}` (Lists have no Descriptor render fn, per svelte-adapter-dom-shim §15).
12
+ //
13
+ // This file hand-authors the raw `symbiote-scroll-view`/`symbiote-scroll-content` intrinsics
14
+ // directly rather than rendering <ScrollView>: unlike ScrollView.svelte, it walks an indexable
15
+ // `plan.cells` list instead of an opaque children Snippet, so it can auto-wrap sticky cells
16
+ // itself. `onContentSizeChange` remains the one gap left from that; nestedScrollEnabled, the
17
+ // Android RefreshControl wrap style-split, and JS sticky-header wrapping are all wired directly
18
+ // here instead.
19
+ //
20
+ // RefreshControl attaches directly to these raw scroll intrinsics: a sibling before the content
21
+ // on iOS, wrapping the whole scroll view on Android (PLATFORM.refreshControlMode) - the same
22
+ // shape ScrollView's own index.svelte uses.
23
+ //
24
+ // `<svelte:element>` is forbidden (svelte-adapter-dom-shim skill §4/§7 - its shim surface isn't
25
+ // implemented), so the horizontal/vertical choice is two static branches sharing one
26
+ // `{#snippet listBody()}`, not a dynamic tag.
27
+ import type { IVirtualizedListProps, IVirtualizedListHandle } from './virtualized-list-props';
28
+
29
+ export type { IVirtualizedListProps, IVirtualizedListHandle };
30
+ </script>
31
+
32
+ <script lang="ts" generics="ItemT">
33
+ import {
34
+ DEFAULT_END_REACHED_THRESHOLD,
35
+ DEFAULT_INITIAL_NUM_TO_RENDER,
36
+ DEFAULT_MAX_TO_RENDER_PER_BATCH,
37
+ DEFAULT_START_REACHED_THRESHOLD,
38
+ DEFAULT_UPDATE_CELLS_BATCHING_PERIOD,
39
+ DEFAULT_WINDOW_SIZE,
40
+ EMPTY_OFFSET,
41
+ FIRST_INDEX,
42
+ INVERTED_X_STYLE,
43
+ INVERTED_Y_STYLE,
44
+ attachStickyScroll,
45
+ buildListPlan,
46
+ buildScrollViewHandle,
47
+ buildViewabilityPairs,
48
+ createInitialListState,
49
+ forwardScrollEvent,
50
+ isSeparatorGapInRange,
51
+ listEffectSignature,
52
+ nextStickyHeaderY,
53
+ readLayoutLength,
54
+ readLayoutNumber,
55
+ readScrollOffset,
56
+ reduceList,
57
+ resolveItemKey,
58
+ resolveScrollForwarding,
59
+ selectScrollIntrinsics,
60
+ splitLayoutProps,
61
+ type IListAction,
62
+ type IListEffect,
63
+ type IListReducerInputs,
64
+ type IListState,
65
+ type IScrollViewHandle,
66
+ type ISeparatorProps,
67
+ type ISeparators,
68
+ } from '@symbiote-native/components';
69
+ import {
70
+ AnimatedValue,
71
+ dlog,
72
+ event as animatedEvent,
73
+ isNativeAnimatedAvailable,
74
+ resolveClassName,
75
+ type ISymbioteEvent,
76
+ type ISymbioteNode,
77
+ } from '@symbiote-native/engine';
78
+ import { resolveSvelteClass } from '../../class-value';
79
+ import { setContext } from 'svelte';
80
+ import type { ShimElement } from '../../dom-shim';
81
+ import RefreshControl from '../RefreshControl.svelte';
82
+ import { PLATFORM } from '../scroll-view/scroll-view-platform';
83
+ import ScrollViewStickyHeader from '../scroll-view/sticky-header.svelte';
84
+ import {
85
+ SCROLL_VIEW_STICKY_CONTEXT_KEY,
86
+ type IScrollViewStickyContext,
87
+ } from '../scroll-view/scroll-view-sticky-context';
88
+ import { pickAccessibilityProps, type IVirtualizedListProps as IProps } from './virtualized-list-props';
89
+ import { createAttachmentsSync } from '../../runes/attachments';
90
+
91
+ let props: IProps<ItemT> = $props();
92
+
93
+ // $state.raw, NOT $state: holds the shim element by IDENTITY (same concern as Switch's
94
+ // hostShim / Vue's shallowRef scrollHandle). dispatchViewCommand reads `.engineNode` off the RAW
95
+ // ShimElement the engine's WeakMap mirror actually knows about.
96
+ let hostShim = $state.raw<ShimElement | null>(null);
97
+
98
+ // See View.svelte's note on `{@attach}` - bound to the scroll host, the node a caller
99
+ // means by "the list" (the same node getScrollNode()/scrollTo drive).
100
+ const syncAttachments = createAttachmentsSync();
101
+ $effect(() => {
102
+ syncAttachments(hostShim, props);
103
+ });
104
+
105
+ // Offset we're imperatively driving native to before hostShim's engine node is live. Fresh
106
+ // object identity each push so the commit path re-applies it even when the value repeats.
107
+ let commandedOffset = $state.raw<{ x: number; y: number } | undefined>(undefined);
108
+ // Bumped on a render-relevant change so `metrics` re-runs - listState is a PLAIN object
109
+ // (not $state), mutating it triggers nothing on its own.
110
+ let version = $state(0);
111
+ let separatorVersion = $state(0);
112
+
113
+ const scrollHandle: IScrollViewHandle = buildScrollViewHandle(() => hostShim?.engineNode ?? null);
114
+
115
+ // The one folded state cell — the Svelte twin of Vue's plain listState / React's stateRef.
116
+ const listState: IListState<ItemT> = createInitialListState<ItemT>();
117
+ const separatorOverrides = new Map<number, Partial<ISeparatorProps<ItemT>>>();
118
+ let viewableTimer: ReturnType<typeof setTimeout> | null = null;
119
+ let batchTimer: ReturnType<typeof setTimeout> | null = null;
120
+
121
+ // Sticky headers: this file hand-rolls the raw scroll intrinsic (see the header comment), so it
122
+ // can't lean on ScrollView.svelte's sticky wiring directly - it mirrors the same
123
+ // scrollAnimatedValue/attachStickyScroll/context wiring here, applied per windowed cell.
124
+ const scrollAnimatedValue = new AnimatedValue(0);
125
+ let viewportHeight = $state<number | undefined>(undefined);
126
+ // y of each measured sticky header, keyed by ORIGINAL list index (RN's _headerLayoutYs) - plain,
127
+ // not $state; stickyVersion (below) drives re-derivation of the collision math that reads it.
128
+ const headerLayoutYs = new Map<number, number>();
129
+ let stickyVersion = $state(0);
130
+
131
+ setContext<IScrollViewStickyContext>(SCROLL_VIEW_STICKY_CONTEXT_KEY, {
132
+ scrollAnimatedValue,
133
+ getInverted: () => narrowed.inverted,
134
+ getViewportHeight: () => viewportHeight,
135
+ });
136
+
137
+ const narrowed = $derived.by(() => {
138
+ // extraData has no field of its own; reading it tracks it so a change forces this derived to
139
+ // re-run (RN's extraData contract).
140
+ void props.extraData;
141
+ return {
142
+ data: props.data,
143
+ getItem: props.getItem,
144
+ getItemCount: props.getItemCount,
145
+ keyExtractor: props.keyExtractor,
146
+ getItemLayout: props.getItemLayout,
147
+ horizontal: props.horizontal === true,
148
+ inverted: props.inverted === true,
149
+ onEndReached: props.onEndReached,
150
+ onEndReachedThreshold: props.onEndReachedThreshold ?? DEFAULT_END_REACHED_THRESHOLD,
151
+ onStartReached: props.onStartReached,
152
+ onStartReachedThreshold: props.onStartReachedThreshold ?? DEFAULT_START_REACHED_THRESHOLD,
153
+ onRefresh: props.onRefresh,
154
+ refreshing: props.refreshing,
155
+ progressViewOffset: props.progressViewOffset,
156
+ onViewableItemsChanged: props.onViewableItemsChanged,
157
+ viewabilityConfig: props.viewabilityConfig,
158
+ viewabilityConfigCallbackPairs: props.viewabilityConfigCallbackPairs,
159
+ onScrollToIndexFailed: props.onScrollToIndexFailed,
160
+ initialNumToRender: props.initialNumToRender ?? DEFAULT_INITIAL_NUM_TO_RENDER,
161
+ initialScrollIndex: props.initialScrollIndex,
162
+ maxToRenderPerBatch: props.maxToRenderPerBatch ?? DEFAULT_MAX_TO_RENDER_PER_BATCH,
163
+ updateCellsBatchingPeriod:
164
+ props.updateCellsBatchingPeriod ?? DEFAULT_UPDATE_CELLS_BATCHING_PERIOD,
165
+ windowSize: props.windowSize ?? DEFAULT_WINDOW_SIZE,
166
+ stickyHeaderIndices: props.stickyHeaderIndices,
167
+ maintainVisibleContentPosition: props.maintainVisibleContentPosition,
168
+ userOnScroll: props.onScroll,
169
+ onScrollBeginDrag: props.onScrollBeginDrag,
170
+ onScrollEndDrag: props.onScrollEndDrag,
171
+ onMomentumScrollBegin: props.onMomentumScrollBegin,
172
+ onMomentumScrollEnd: props.onMomentumScrollEnd,
173
+ scrollEventThrottle: props.scrollEventThrottle,
174
+ keyboardShouldPersistTaps: props.keyboardShouldPersistTaps,
175
+ keyboardDismissMode: props.keyboardDismissMode,
176
+ style: props.style,
177
+ contentContainerStyle: props.contentContainerStyle,
178
+ class: props.class,
179
+ };
180
+ });
181
+
182
+ function buildInputs(): IListReducerInputs<ItemT> {
183
+ const p = narrowed;
184
+ return {
185
+ data: p.data,
186
+ getItem: p.getItem,
187
+ getItemCount: p.getItemCount,
188
+ keyExtractor: p.keyExtractor,
189
+ getItemLayout: p.getItemLayout,
190
+ horizontal: p.horizontal,
191
+ windowSize: p.windowSize,
192
+ initialNumToRender: p.initialNumToRender,
193
+ maxToRenderPerBatch: p.maxToRenderPerBatch,
194
+ updateCellsBatchingPeriod: p.updateCellsBatchingPeriod,
195
+ onEndReachedThreshold: p.onEndReachedThreshold,
196
+ onStartReachedThreshold: p.onStartReachedThreshold,
197
+ onEndReachedActive: p.onEndReached !== undefined,
198
+ onStartReachedActive: p.onStartReached !== undefined,
199
+ viewabilityPairs: buildViewabilityPairs(
200
+ p.onViewableItemsChanged,
201
+ p.viewabilityConfig,
202
+ p.viewabilityConfigCallbackPairs,
203
+ ),
204
+ maintainVisibleContentPosition: p.maintainVisibleContentPosition,
205
+ initialScrollIndex: p.initialScrollIndex,
206
+ };
207
+ }
208
+
209
+ function keyFor(index: number): string {
210
+ return resolveItemKey(narrowed.getItem(narrowed.data, index), index, narrowed.keyExtractor);
211
+ }
212
+
213
+ function scrollToPixel(offset: number, animated: boolean): void {
214
+ const clamped = Math.max(EMPTY_OFFSET, offset);
215
+ const target = narrowed.horizontal
216
+ ? { x: clamped, y: EMPTY_OFFSET }
217
+ : { x: EMPTY_OFFSET, y: clamped };
218
+ if (hostShim?.engineNode === undefined) {
219
+ dlog(`VirtualizedList scrollTo offset=${clamped} pending-ref`);
220
+ commandedOffset = target;
221
+ return;
222
+ }
223
+ commandedOffset = undefined;
224
+ dlog(`VirtualizedList scrollTo offset=${clamped} animated=${animated} (horizontal=${narrowed.horizontal})`);
225
+ scrollHandle.scrollTo({ x: target.x, y: target.y, animated });
226
+ }
227
+
228
+ function runEffects(effects: IListEffect<ItemT>[], inputs: IListReducerInputs<ItemT>): void {
229
+ const p = narrowed;
230
+ for (const effect of effects) {
231
+ switch (effect.kind) {
232
+ case 'scroll-to':
233
+ scrollToPixel(effect.offset, effect.animated);
234
+ break;
235
+ case 'fire-end-reached':
236
+ p.onEndReached?.({ distanceFromEnd: effect.distanceFromEnd });
237
+ break;
238
+ case 'fire-start-reached':
239
+ p.onStartReached?.({ distanceFromStart: effect.distanceFromStart });
240
+ break;
241
+ case 'fire-scroll-to-index-failed':
242
+ p.onScrollToIndexFailed?.({
243
+ index: effect.index,
244
+ highestMeasuredFrameIndex: effect.highestMeasuredFrameIndex,
245
+ averageItemLength: effect.averageItemLength,
246
+ });
247
+ break;
248
+ case 'schedule-refill': {
249
+ if (batchTimer !== null) clearTimeout(batchTimer);
250
+ batchTimer = setTimeout(() => {
251
+ batchTimer = null;
252
+ dispatch({ kind: 'batch-tick' });
253
+ }, effect.delay);
254
+ break;
255
+ }
256
+ case 'fire-viewable': {
257
+ const pairs = inputs.viewabilityPairs;
258
+ const info = effect.info;
259
+ const map = effect.map;
260
+ const fire = (): void => {
261
+ for (const pair of pairs) pair.onViewableItemsChanged(info);
262
+ dispatch({ kind: 'viewable-fired', map });
263
+ };
264
+ if (viewableTimer !== null) {
265
+ clearTimeout(viewableTimer);
266
+ viewableTimer = null;
267
+ }
268
+ if (effect.delay > EMPTY_OFFSET) {
269
+ viewableTimer = setTimeout(() => {
270
+ viewableTimer = null;
271
+ fire();
272
+ }, effect.delay);
273
+ } else {
274
+ fire();
275
+ }
276
+ break;
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ function dispatch(action: IListAction<ItemT>): void {
283
+ const inputs = buildInputs();
284
+ const result = reduceList(listState, action, inputs);
285
+ runEffects(result.effects, inputs);
286
+ if (result.changed) version += 1;
287
+ }
288
+
289
+ // The window is recomputed exactly once here (refresh-metrics), cached until `version` or
290
+ // `narrowed` change - the Svelte twin of Vue's `metrics` computed.
291
+ const metrics = $derived.by(() => {
292
+ void version;
293
+ reduceList(listState, { kind: 'refresh-metrics' }, buildInputs());
294
+ return listState.metrics;
295
+ });
296
+
297
+ const commitSignature = $derived.by(() => {
298
+ void metrics;
299
+ return listEffectSignature(listState);
300
+ });
301
+
302
+ const hasStickyHeaders = $derived(
303
+ narrowed.stickyHeaderIndices !== undefined && narrowed.stickyHeaderIndices.length > 0,
304
+ );
305
+ // Resolved dynamically, exactly like React (adapters/react/.../scroll-view/shared.ts:267). Do
306
+ // not hardcode this false to keep the JS listener alive - RN gates native the same way
307
+ // (AnimatedWithChildren.js:74 `if (!this.__isNative)`) and the pin is the native transform, not
308
+ // the listener, so forcing JS mode only trades away the native driver: header lag on iOS,
309
+ // outright failure on Android (commit debounce 15ms vs iOS's 64ms - render-scroll-sticky.ts).
310
+ const nativeStickyAvailable = $derived(hasStickyHeaders && isNativeAnimatedAvailable());
311
+ // RN's FlatList/SectionList expose no invertStickyHeaders of their own (only ScrollView does) -
312
+ // sticky headers always pin to the top here, matching RN.
313
+ const forwarding = $derived(
314
+ resolveScrollForwarding({
315
+ hasStickyHeaders,
316
+ nativeStickyAvailable,
317
+ invertStickyHeaders: undefined,
318
+ scrollEventThrottle: narrowed.scrollEventThrottle,
319
+ maintainVisibleContentPosition: narrowed.maintainVisibleContentPosition,
320
+ snapToAlignment: undefined,
321
+ }),
322
+ );
323
+
324
+ $effect(() => {
325
+ dlog(
326
+ `VirtualizedList sticky hasStickyHeaders=${hasStickyHeaders} nativeStickyAvailable=${nativeStickyAvailable} mode=${forwarding.mode} scrollEventThrottle=${String(forwarding.scrollEventThrottle)}`,
327
+ );
328
+ });
329
+
330
+ // Drive the sticky scroll value on the native UI thread once the scroll host commits (mirrors
331
+ // ScrollView.svelte). No-op without the native animated module - the JS fallback below still
332
+ // keeps headers pinned via onScroll.
333
+ $effect(() => {
334
+ if (!nativeStickyAvailable) {
335
+ dlog('VirtualizedList sticky attachStickyScroll skipped: nativeStickyAvailable=false');
336
+ return;
337
+ }
338
+ const node = hostShim?.engineNode;
339
+ if (node === undefined) {
340
+ dlog('VirtualizedList sticky attachStickyScroll skipped: engineNode not ready yet');
341
+ return;
342
+ }
343
+ dlog('VirtualizedList sticky attachStickyScroll attached');
344
+ return attachStickyScroll(node, scrollAnimatedValue);
345
+ });
346
+
347
+ function handleScroll(event: ISymbioteEvent): void {
348
+ const offset = readScrollOffset(event, narrowed.horizontal);
349
+ if (offset === undefined) return;
350
+ dlog(`VirtualizedList onScroll offset=${offset}`);
351
+ commandedOffset = undefined;
352
+ dispatch({ kind: 'scroll', offset });
353
+ narrowed.userOnScroll?.(event);
354
+ }
355
+
356
+ // sticky-js: no native Animated module, so the scroll value is driven off the JS thread via the
357
+ // same wrapped-listener shape ScrollView.svelte uses. sticky-native/plain: forward untouched.
358
+ const onScroll = $derived.by(() => {
359
+ if (forwarding.mode !== 'sticky-js') return handleScroll;
360
+ return animatedEvent(
361
+ [{ nativeEvent: { contentOffset: { y: scrollAnimatedValue } } }],
362
+ { listener: (...args: unknown[]) => forwardScrollEvent(handleScroll, args) },
363
+ );
364
+ });
365
+
366
+ function onViewportLayout(event: ISymbioteEvent): void {
367
+ const length = readLayoutLength(event, narrowed.horizontal);
368
+ if (length === undefined) return;
369
+ dlog(`VirtualizedList onLayout viewport=${length}`);
370
+ dispatch({ kind: 'layout', length });
371
+ if (forwarding.capturesViewportHeight) {
372
+ const height = readLayoutNumber(event, 'height');
373
+ if (height !== undefined) viewportHeight = height;
374
+ }
375
+ }
376
+
377
+ // Records a sticky header's measured y (RN's _headerLayoutYs) so the NEXT sticky header down the
378
+ // list can compute its collision point - without this every header would stick indefinitely
379
+ // instead of being pushed off by the one behind it.
380
+ function recordHeaderY(index: number, event: ISymbioteEvent): void {
381
+ const y = readLayoutNumber(event, 'y');
382
+ if (y === undefined) return;
383
+ // This map is the ONLY source of nextHeaderLayoutY for every header ahead of this one; if a
384
+ // later header's onLayout never fires (windowing drops its cell) the collision math upstream
385
+ // falls back to `?? 0` (collisionPoint = -layoutHeight), which is almost certainly wrong.
386
+ dlog(`VirtualizedList recordHeaderY index=${index} y=${y}`);
387
+ headerLayoutYs.set(index, y);
388
+ stickyVersion += 1;
389
+ }
390
+
391
+ function stickyLayoutFor(index: number): (event: ISymbioteEvent) => void {
392
+ const measure = makeCellMeasure(index);
393
+ return (event: ISymbioteEvent): void => {
394
+ measure(event);
395
+ recordHeaderY(index, event);
396
+ };
397
+ }
398
+
399
+ // Only a header MOUNTED RIGHT NOW can collide with this one, matching React (its ScrollView
400
+ // receives `renderedStickyIndices`, not the full section list -
401
+ // adapters/react/.../virtualized-list/index.ts:732). Passing the full index list instead let a
402
+ // header collide against a stale y from one that had already scrolled out and unmounted,
403
+ // freezing it in place - proven by sticky-collision-parity.test.ts against the React reference.
404
+ // The map itself is deliberately NOT pruned (React doesn't either): a measured y stays valid for
405
+ // when that header scrolls back in.
406
+ function nextStickyHeaderYFor(index: number): number | undefined {
407
+ void stickyVersion;
408
+ const indices = narrowed.stickyHeaderIndices;
409
+ if (indices === undefined) return undefined;
410
+ const mounted = new Set(allCells.map(cell => cell.index));
411
+ const rendered = indices.filter(stickyIndex => mounted.has(stickyIndex));
412
+ return nextStickyHeaderY(rendered, rendered.indexOf(index), headerLayoutYs);
413
+ }
414
+
415
+ function makeCellMeasure(index: number): (event: ISymbioteEvent) => void {
416
+ return (event: ISymbioteEvent): void => {
417
+ const length = readLayoutLength(event, narrowed.horizontal);
418
+ if (length === undefined) return;
419
+ dlog(`VirtualizedList cell ${index} measured length=${length}`);
420
+ dispatch({ kind: 'measure', index, length });
421
+ };
422
+ }
423
+
424
+ function mergeSeparator(gapIndex: number, patch: Partial<ISeparatorProps<ItemT>>): void {
425
+ if (!isSeparatorGapInRange(gapIndex, listState.metrics.count)) return;
426
+ separatorOverrides.set(gapIndex, { ...separatorOverrides.get(gapIndex), ...patch });
427
+ separatorVersion += 1;
428
+ }
429
+
430
+ function makeSeparators(index: number): ISeparators {
431
+ return {
432
+ highlight: (): void => {
433
+ dlog(`VirtualizedList separator highlight cell=${index}`);
434
+ mergeSeparator(index - 1, { highlighted: true });
435
+ mergeSeparator(index, { highlighted: true });
436
+ },
437
+ unhighlight: (): void => {
438
+ dlog(`VirtualizedList separator unhighlight cell=${index}`);
439
+ mergeSeparator(index - 1, { highlighted: false });
440
+ mergeSeparator(index, { highlighted: false });
441
+ },
442
+ updateProps: (select: 'leading' | 'trailing', newProps: Record<string, unknown>): void => {
443
+ mergeSeparator(select === 'leading' ? index - 1 : index, newProps);
444
+ },
445
+ };
446
+ }
447
+
448
+ // ---- imperative handle: component instance exports, the Svelte twin of Vue's expose() /
449
+ // React's useImperativeHandle. A parent does `<VirtualizedList bind:this={ref} .../>` and calls
450
+ // `ref.scrollToIndex(...)`. ----
451
+ export function scrollToOffset(params: { offset: number; animated?: boolean }): void {
452
+ dispatch({ kind: 'scroll-to-offset', offset: params.offset, animated: params.animated ?? true });
453
+ }
454
+ export function scrollToIndex(params: {
455
+ index: number;
456
+ animated?: boolean;
457
+ viewOffset?: number;
458
+ viewPosition?: number;
459
+ }): void {
460
+ dispatch({
461
+ kind: 'scroll-to-index',
462
+ index: params.index,
463
+ animated: params.animated ?? true,
464
+ viewPosition: params.viewPosition ?? FIRST_INDEX,
465
+ viewOffset: params.viewOffset ?? EMPTY_OFFSET,
466
+ });
467
+ }
468
+ export function scrollToItem(params: {
469
+ item: unknown;
470
+ animated?: boolean;
471
+ viewPosition?: number;
472
+ }): void {
473
+ dispatch({
474
+ kind: 'scroll-to-item',
475
+ item: params.item,
476
+ animated: params.animated ?? true,
477
+ viewPosition: params.viewPosition ?? FIRST_INDEX,
478
+ });
479
+ }
480
+ export function scrollToEnd(params?: { animated?: boolean }): void {
481
+ dispatch({ kind: 'scroll-to-end', animated: params?.animated ?? true });
482
+ }
483
+ export function flashScrollIndicators(): void {
484
+ scrollHandle.flashScrollIndicators();
485
+ }
486
+ export function getNativeScrollRef(): IScrollViewHandle | null {
487
+ return scrollHandle;
488
+ }
489
+ export function getScrollableNode(): IScrollViewHandle | null {
490
+ return scrollHandle;
491
+ }
492
+ export function getScrollResponder(): IScrollViewHandle | null {
493
+ return scrollHandle;
494
+ }
495
+ export function getScrollNode(): ISymbioteNode | null {
496
+ return scrollHandle.getScrollNode();
497
+ }
498
+ export function recordInteraction(): void {
499
+ dispatch({ kind: 'record-interaction' });
500
+ }
501
+
502
+ // ---- after-commit pass: runs the deferred effects (batch fill, edge-reached, viewability,
503
+ // initial-scroll, MVCP) whenever the windowing signature changes. $effect runs after the DOM
504
+ // update lands, the same after-commit timing Vue's `flush: 'post'` watcher and React's layout
505
+ // effect give the reducer. ----
506
+ $effect(() => {
507
+ void commitSignature;
508
+ const inputs = buildInputs();
509
+ const result = reduceList(listState, { kind: 'commit' }, inputs);
510
+ runEffects(result.effects, inputs);
511
+ });
512
+
513
+ // Clear pending timers on unmount (RN ViewabilityHelper.dispose + the fill timer). A bare
514
+ // `$effect` with no reactive reads runs once on mount; its returned cleanup runs on destroy -
515
+ // the Svelte twin of Vue's onBeforeUnmount.
516
+ $effect(() => {
517
+ return () => {
518
+ if (viewableTimer !== null) clearTimeout(viewableTimer);
519
+ if (batchTimer !== null) clearTimeout(batchTimer);
520
+ };
521
+ });
522
+
523
+ const scrollViewIntrinsics = $derived(selectScrollIntrinsics(narrowed.horizontal, narrowed.contentContainerStyle));
524
+
525
+ const resolvedStyle = $derived(
526
+ narrowed.inverted
527
+ ? [narrowed.style, narrowed.horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE]
528
+ : narrowed.style,
529
+ );
530
+ const resolvedContentContainerStyle = $derived(
531
+ narrowed.horizontal
532
+ ? [scrollViewIntrinsics.contentStyle, { width: metrics.total }]
533
+ : scrollViewIntrinsics.contentStyle,
534
+ );
535
+
536
+ // Android wrap mode only: mirrors ScrollView's own index.svelte - RN's splitLayoutProps routes
537
+ // LAYOUT props (margin/flex/size/position/...) onto the wrapping AndroidSwipeRefreshLayout,
538
+ // VISUAL props (background/padding/border/...) onto the inner scroll view, instead of leaving
539
+ // the wrapper unstyled (which collapses it to zero height).
540
+ const layoutSplit = $derived(
541
+ shouldWrapRefreshControl
542
+ ? splitLayoutProps([resolveSvelteClass(narrowed.class), resolvedStyle])
543
+ : undefined,
544
+ );
545
+
546
+ const outerBag = $derived.by(() => {
547
+ const bag: Record<string, unknown> = {
548
+ style: [scrollViewIntrinsics.scrollViewBaseStyle, layoutSplit !== undefined ? layoutSplit.inner : resolvedStyle],
549
+ horizontal: narrowed.horizontal,
550
+ // RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`). This file
551
+ // hand-rolls the raw scroll intrinsic instead of rendering <ScrollView>, so it never
552
+ // inherited that default - on Android, a list nested inside a page ScrollView never got
553
+ // nested-scroll gesture arbitration and only the outer page scrolled.
554
+ nestedScrollEnabled: true,
555
+ onScroll,
556
+ onLayout: onViewportLayout,
557
+ };
558
+ if (layoutSplit === undefined) bag.class = narrowed.class;
559
+ if (commandedOffset !== undefined) bag.contentOffset = commandedOffset;
560
+ // forwarding.scrollEventThrottle (not the raw prop): folds in the sticky-mode default
561
+ // (1 native / 16 JS-fallback) when unset - without it a sticky header rebuilt off too-sparse
562
+ // scroll events pins/collides late.
563
+ if (forwarding.scrollEventThrottle !== undefined) bag.scrollEventThrottle = forwarding.scrollEventThrottle;
564
+ if (narrowed.onScrollBeginDrag !== undefined) bag.onScrollBeginDrag = narrowed.onScrollBeginDrag;
565
+ if (narrowed.onScrollEndDrag !== undefined) bag.onScrollEndDrag = narrowed.onScrollEndDrag;
566
+ if (narrowed.onMomentumScrollBegin !== undefined) bag.onMomentumScrollBegin = narrowed.onMomentumScrollBegin;
567
+ if (narrowed.onMomentumScrollEnd !== undefined) bag.onMomentumScrollEnd = narrowed.onMomentumScrollEnd;
568
+ if (narrowed.keyboardShouldPersistTaps !== undefined) {
569
+ bag.keyboardShouldPersistTaps = narrowed.keyboardShouldPersistTaps;
570
+ }
571
+ if (narrowed.keyboardDismissMode !== undefined) bag.keyboardDismissMode = narrowed.keyboardDismissMode;
572
+ if (narrowed.stickyHeaderIndices !== undefined) bag.stickyHeaderIndices = narrowed.stickyHeaderIndices;
573
+ if (narrowed.maintainVisibleContentPosition !== undefined) {
574
+ bag.maintainVisibleContentPosition = narrowed.maintainVisibleContentPosition;
575
+ }
576
+ // Object.assign merges a bag already built field-by-field (pickAccessibilityProps), not a raw
577
+ // spread of `props`, so this stays inside the object-bag convention (svelte-adapter-dom-shim
578
+ // skill §3g(c)) - only ONE prop (`p={bag}`) ever lands on the symbiote-* host tag.
579
+ Object.assign(bag, pickAccessibilityProps(props));
580
+ return bag;
581
+ });
582
+
583
+ // Build the real RefreshControl's own prop bag when onRefresh is set; refreshing defaults to
584
+ // false when nullish, same as RN/React.
585
+ const refreshControlProps = $derived.by(() => {
586
+ if (narrowed.onRefresh === undefined) return undefined;
587
+ return {
588
+ refreshing: narrowed.refreshing ?? false,
589
+ onRefresh: narrowed.onRefresh,
590
+ progressViewOffset: narrowed.progressViewOffset,
591
+ };
592
+ });
593
+ // iOS: RefreshControl is a childless sibling before the content container. Android: it WRAPS the
594
+ // whole scroll view (an Android ScrollView accepts only one child) - same PLATFORM.
595
+ // refreshControlMode ScrollView's own index.svelte reads.
596
+ const shouldWrapRefreshControl = $derived(
597
+ PLATFORM.refreshControlMode === 'wrap' && refreshControlProps !== undefined,
598
+ );
599
+
600
+ const contentBag = $derived({ style: resolvedContentContainerStyle, collapsable: false });
601
+
602
+ const stickySet = $derived(
603
+ narrowed.stickyHeaderIndices !== undefined ? new Set(narrowed.stickyHeaderIndices) : undefined,
604
+ );
605
+ $effect(() => {
606
+ dlog(
607
+ `VirtualizedList sticky stickyHeaderIndices=${JSON.stringify(narrowed.stickyHeaderIndices)} ` +
608
+ `first=${metrics.first} last=${metrics.last} windowedCells=${JSON.stringify(plan?.cells.map(c => c.index))} ` +
609
+ `forcedStickyCell=${plan?.forcedStickyCell?.index ?? 'none'} gapExtent=${plan?.gapExtent ?? 0} ` +
610
+ `allCells=${JSON.stringify(allCells.map(c => c.index))}`,
611
+ );
612
+ });
613
+ const hasHeader = $derived(props.header !== undefined);
614
+ const plan = $derived.by(() => {
615
+ void separatorVersion;
616
+ if (metrics.count === FIRST_INDEX) return null;
617
+ return buildListPlan({
618
+ count: metrics.count,
619
+ first: metrics.first,
620
+ last: metrics.last,
621
+ offsets: metrics.offsets,
622
+ lengths: metrics.lengths,
623
+ total: metrics.total,
624
+ keyFor,
625
+ stickyIndices: stickySet,
626
+ hasHeader,
627
+ hasSeparators: props.separator !== undefined,
628
+ });
629
+ });
630
+ // forcedStickyCell prepended to the window cells so BOTH walk through the SAME keyed {#each}.
631
+ // A Svelte {#if}/{#each} split is two different template positions with no shared component
632
+ // identity, so a header toggling between "forced ahead of the window" and "inside the window"
633
+ // would get destroyed and recreated (losing its measured layout, back to its default
634
+ // translateY) - one keyed list keeps the SAME instance across that transition, by cell.key.
635
+ const allCells = $derived(
636
+ plan?.forcedStickyCell !== undefined && plan !== null
637
+ ? [plan.forcedStickyCell, ...plan.cells]
638
+ : (plan?.cells ?? []),
639
+ );
640
+ const cellInvertedStyle = $derived(
641
+ narrowed.inverted ? (narrowed.horizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE) : undefined,
642
+ );
643
+
644
+ // Reads separatorVersion so a .highlight()/.unhighlight()/.updateProps() call (ISeparators, above)
645
+ // reactively refreshes this cell's separator props - a plain object literal inlined in the
646
+ // template would NOT re-track separatorOverrides (a non-reactive Map) on its own.
647
+ function separatorPropsFor(index: number): ISeparatorProps<ItemT> {
648
+ void separatorVersion;
649
+ const overrides = separatorOverrides.get(index);
650
+ return {
651
+ highlighted: overrides?.highlighted === true,
652
+ leadingItem: narrowed.getItem(narrowed.data, index),
653
+ trailingItem: narrowed.getItem(narrowed.data, index + 1),
654
+ ...overrides,
655
+ };
656
+ }
657
+ </script>
658
+
659
+ {#snippet listBody()}<!--
660
+ No whitespace between sibling {#if} blocks below, or between each-block siblings, or around the
661
+ spacers. Svelte only trims LEADING/TRAILING whitespace of a fragment; whitespace BETWEEN two
662
+ sibling non-text nodes collapses to a text node and is kept (Svelte compiler utils.js
663
+ clean_nodes, verified against 5.56.8), which would land as a stray RCTRawText child of this
664
+ scroll content - invalid on real Fabric for a non-Text view (dom-shim/text.ts). Caught by
665
+ virtualized-list.smoke.test.ts asserting an exact windowed child count; keep it exact this way
666
+ if you touch this block.
667
+ -->{#if hasHeader}<symbiote-view p={{}}>{@render props.header?.()}</symbiote-view>{/if}{#if metrics.count === FIRST_INDEX}{#if props.empty}<symbiote-view p={{}}>{@render props.empty()}</symbiote-view>{/if}{:else if plan}{#if plan.leadingExtent > EMPTY_OFFSET}<symbiote-view
668
+ p={{ style: narrowed.horizontal ? { width: plan.leadingExtent } : { height: plan.leadingExtent } }}
669
+ ></symbiote-view>{/if}{#each allCells as cell (cell.key)}{#if stickySet?.has(cell.index)}<ScrollViewStickyHeader onLayout={stickyLayoutFor(cell.index)} nextHeaderLayoutY={nextStickyHeaderYFor(cell.index)}>{@render props.item({
670
+ item: narrowed.getItem(narrowed.data, cell.index),
671
+ index: cell.index,
672
+ separators: makeSeparators(cell.index),
673
+ })}</ScrollViewStickyHeader>{:else}<symbiote-view p={{ onLayout: makeCellMeasure(cell.index), style: cellInvertedStyle }}>{@render props.item({
674
+ item: narrowed.getItem(narrowed.data, cell.index),
675
+ index: cell.index,
676
+ separators: makeSeparators(cell.index),
677
+ })}</symbiote-view>{/if}{#if plan.forcedStickyCell && cell.index === plan.forcedStickyCell.index && plan.gapExtent > EMPTY_OFFSET}<symbiote-view
678
+ p={{ style: narrowed.horizontal ? { width: plan.gapExtent } : { height: plan.gapExtent } }}
679
+ ></symbiote-view>{/if}{#if props.separator && cell.index < metrics.last && cell.index !== plan.forcedStickyCell?.index}<symbiote-view p={{}}>{@render props.separator(separatorPropsFor(cell.index))}</symbiote-view>{/if}{/each}{#if plan.trailingExtent > EMPTY_OFFSET}<symbiote-view
680
+ p={{ style: narrowed.horizontal ? { width: plan.trailingExtent } : { height: plan.trailingExtent } }}
681
+ ></symbiote-view>{/if}{/if}{#if props.footer}<symbiote-view p={{}}>{@render props.footer()}</symbiote-view>{/if}
682
+ {/snippet}
683
+
684
+ {#snippet scrollBody()}<!--
685
+ Same no-whitespace-between-siblings rule as listBody() above: the optional sibling
686
+ RefreshControl and the content container are two siblings of one parent when RefreshControl is
687
+ NOT wrapping (iOS), so they must sit edge-to-edge with zero characters between them.
688
+ -->{#if !shouldWrapRefreshControl && refreshControlProps !== undefined}<RefreshControl {...refreshControlProps} />{/if}{#if narrowed.horizontal}<symbiote-horizontal-scroll-content p={contentBag}>{@render listBody()}</symbiote-horizontal-scroll-content>{:else}<symbiote-scroll-content p={contentBag}>{@render listBody()}</symbiote-scroll-content>{/if}{/snippet}
689
+
690
+ {#if shouldWrapRefreshControl && refreshControlProps !== undefined}
691
+ <RefreshControl {...refreshControlProps} style={layoutSplit?.outer}>
692
+ {#if narrowed.horizontal}
693
+ <symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>{@render scrollBody()}</symbiote-horizontal-scroll-view>
694
+ {:else}
695
+ <symbiote-scroll-view p={outerBag} bind:this={hostShim}>{@render scrollBody()}</symbiote-scroll-view>
696
+ {/if}
697
+ </RefreshControl>
698
+ {:else if narrowed.horizontal}
699
+ <symbiote-horizontal-scroll-view p={outerBag} bind:this={hostShim}>{@render scrollBody()}</symbiote-horizontal-scroll-view>
700
+ {:else}
701
+ <symbiote-scroll-view p={outerBag} bind:this={hostShim}>{@render scrollBody()}</symbiote-scroll-view>
702
+ {/if}