@shopify/flash-list 1.8.0 → 2.0.0-alpha.2

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 (170) hide show
  1. package/README.md +147 -26
  2. package/dist/FlashListProps.d.ts +65 -2
  3. package/dist/FlashListProps.d.ts.map +1 -1
  4. package/dist/__tests__/AverageWindow.test.js +35 -0
  5. package/dist/__tests__/AverageWindow.test.js.map +1 -1
  6. package/dist/enableNewCore.d.ts +3 -0
  7. package/dist/enableNewCore.d.ts.map +1 -0
  8. package/dist/enableNewCore.js +25 -0
  9. package/dist/enableNewCore.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +28 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/recyclerview/RecycleKeyManager.d.ts +82 -0
  15. package/dist/recyclerview/RecycleKeyManager.d.ts.map +1 -0
  16. package/dist/recyclerview/RecycleKeyManager.js +135 -0
  17. package/dist/recyclerview/RecycleKeyManager.js.map +1 -0
  18. package/dist/recyclerview/RecyclerView.d.ts +12 -0
  19. package/dist/recyclerview/RecyclerView.d.ts.map +1 -0
  20. package/dist/recyclerview/RecyclerView.js +283 -0
  21. package/dist/recyclerview/RecyclerView.js.map +1 -0
  22. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +12 -0
  23. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -0
  24. package/dist/recyclerview/RecyclerViewContextProvider.js +11 -0
  25. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -0
  26. package/dist/recyclerview/RecyclerViewManager.d.ts +52 -0
  27. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -0
  28. package/dist/recyclerview/RecyclerViewManager.js +323 -0
  29. package/dist/recyclerview/RecyclerViewManager.js.map +1 -0
  30. package/dist/recyclerview/RecyclerViewProps.d.ts +9 -0
  31. package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -0
  32. package/dist/recyclerview/RecyclerViewProps.js +3 -0
  33. package/dist/recyclerview/RecyclerViewProps.js.map +1 -0
  34. package/dist/recyclerview/ViewHolder.d.ts +45 -0
  35. package/dist/recyclerview/ViewHolder.d.ts.map +1 -0
  36. package/dist/recyclerview/ViewHolder.js +96 -0
  37. package/dist/recyclerview/ViewHolder.js.map +1 -0
  38. package/dist/recyclerview/ViewHolderCollection.d.ts +57 -0
  39. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -0
  40. package/dist/recyclerview/ViewHolderCollection.js +75 -0
  41. package/dist/recyclerview/ViewHolderCollection.js.map +1 -0
  42. package/dist/recyclerview/components/CompatScroller.d.ts +7 -0
  43. package/dist/recyclerview/components/CompatScroller.d.ts.map +1 -0
  44. package/dist/recyclerview/components/CompatScroller.js +8 -0
  45. package/dist/recyclerview/components/CompatScroller.js.map +1 -0
  46. package/dist/recyclerview/components/CompatView.d.ts +7 -0
  47. package/dist/recyclerview/components/CompatView.d.ts.map +1 -0
  48. package/dist/recyclerview/components/CompatView.js +8 -0
  49. package/dist/recyclerview/components/CompatView.js.map +1 -0
  50. package/dist/recyclerview/components/ScrollAnchor.d.ts +28 -0
  51. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -0
  52. package/dist/recyclerview/components/ScrollAnchor.js +35 -0
  53. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -0
  54. package/dist/recyclerview/components/StickyHeaders.d.ts +38 -0
  55. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -0
  56. package/dist/recyclerview/components/StickyHeaders.js +119 -0
  57. package/dist/recyclerview/components/StickyHeaders.js.map +1 -0
  58. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +51 -0
  59. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -0
  60. package/dist/recyclerview/helpers/ConsecutiveNumbers.js +122 -0
  61. package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -0
  62. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +59 -0
  63. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -0
  64. package/dist/recyclerview/helpers/EngagedIndicesTracker.js +138 -0
  65. package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -0
  66. package/dist/recyclerview/hooks/useBoundDetection.d.ts +19 -0
  67. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -0
  68. package/dist/recyclerview/hooks/useBoundDetection.js +103 -0
  69. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -0
  70. package/dist/recyclerview/hooks/useLayoutState.d.ts +12 -0
  71. package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -0
  72. package/dist/recyclerview/hooks/useLayoutState.js +43 -0
  73. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -0
  74. package/dist/recyclerview/hooks/useOnLoad.d.ts +25 -0
  75. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -0
  76. package/dist/recyclerview/hooks/useOnLoad.js +73 -0
  77. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -0
  78. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +72 -0
  79. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -0
  80. package/dist/recyclerview/hooks/useRecyclerViewController.js +370 -0
  81. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -0
  82. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +6 -0
  83. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -0
  84. package/dist/recyclerview/hooks/useRecyclerViewManager.js +27 -0
  85. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -0
  86. package/dist/recyclerview/hooks/useRecyclingState.d.ts +16 -0
  87. package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -0
  88. package/dist/recyclerview/hooks/useRecyclingState.js +54 -0
  89. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -0
  90. package/dist/recyclerview/hooks/useSecondaryProps.d.ts +27 -0
  91. package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -0
  92. package/dist/recyclerview/hooks/useSecondaryProps.js +93 -0
  93. package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -0
  94. package/dist/recyclerview/hooks/useUnmountFlag.d.ts +11 -0
  95. package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -0
  96. package/dist/recyclerview/hooks/useUnmountFlag.js +28 -0
  97. package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -0
  98. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +65 -0
  99. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -0
  100. package/dist/recyclerview/layout-managers/GridLayoutManager.js +204 -0
  101. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -0
  102. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +281 -0
  103. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -0
  104. package/dist/recyclerview/layout-managers/LayoutManager.js +250 -0
  105. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -0
  106. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +52 -0
  107. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -0
  108. package/dist/recyclerview/layout-managers/LinearLayoutManager.js +191 -0
  109. package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -0
  110. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +73 -0
  111. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -0
  112. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +274 -0
  113. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -0
  114. package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts +12 -0
  115. package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts.map +1 -0
  116. package/dist/recyclerview/utils/adjustOffsetForRTL.js +18 -0
  117. package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -0
  118. package/dist/recyclerview/utils/componentUtils.d.ts +19 -0
  119. package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -0
  120. package/dist/recyclerview/utils/componentUtils.js +32 -0
  121. package/dist/recyclerview/utils/componentUtils.js.map +1 -0
  122. package/dist/recyclerview/utils/findVisibleIndex.d.ts +24 -0
  123. package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -0
  124. package/dist/recyclerview/utils/findVisibleIndex.js +82 -0
  125. package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -0
  126. package/dist/recyclerview/utils/measureLayout.d.ts +56 -0
  127. package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -0
  128. package/dist/recyclerview/utils/measureLayout.js +77 -0
  129. package/dist/recyclerview/utils/measureLayout.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -1
  131. package/dist/utils/AverageWindow.d.ts +13 -0
  132. package/dist/utils/AverageWindow.d.ts.map +1 -1
  133. package/dist/utils/AverageWindow.js +30 -1
  134. package/dist/utils/AverageWindow.js.map +1 -1
  135. package/package.json +1 -1
  136. package/src/FlashListProps.ts +73 -2
  137. package/src/__tests__/AverageWindow.test.ts +49 -1
  138. package/src/enableNewCore.ts +22 -0
  139. package/src/index.ts +21 -0
  140. package/src/recyclerview/RecycleKeyManager.ts +185 -0
  141. package/src/recyclerview/RecyclerView.tsx +500 -0
  142. package/src/recyclerview/RecyclerViewContextProvider.ts +19 -0
  143. package/src/recyclerview/RecyclerViewManager.ts +379 -0
  144. package/src/recyclerview/RecyclerViewProps.ts +10 -0
  145. package/src/recyclerview/ViewHolder.tsx +173 -0
  146. package/src/recyclerview/ViewHolderCollection.tsx +164 -0
  147. package/src/recyclerview/components/CompatScroller.ts +9 -0
  148. package/src/recyclerview/components/CompatView.ts +9 -0
  149. package/src/recyclerview/components/ScrollAnchor.tsx +53 -0
  150. package/src/recyclerview/components/StickyHeaders.tsx +210 -0
  151. package/src/recyclerview/helpers/ConsecutiveNumbers.ts +120 -0
  152. package/src/recyclerview/helpers/EngagedIndicesTracker.ts +191 -0
  153. package/src/recyclerview/hooks/useBoundDetection.ts +127 -0
  154. package/src/recyclerview/hooks/useLayoutState.ts +46 -0
  155. package/src/recyclerview/hooks/useOnLoad.ts +78 -0
  156. package/src/recyclerview/hooks/useRecyclerViewController.tsx +487 -0
  157. package/src/recyclerview/hooks/useRecyclerViewManager.ts +30 -0
  158. package/src/recyclerview/hooks/useRecyclingState.ts +63 -0
  159. package/src/recyclerview/hooks/useSecondaryProps.tsx +119 -0
  160. package/src/recyclerview/hooks/useUnmountFlag.ts +26 -0
  161. package/src/recyclerview/layout-managers/GridLayoutManager.ts +215 -0
  162. package/src/recyclerview/layout-managers/LayoutManager.ts +493 -0
  163. package/src/recyclerview/layout-managers/LinearLayoutManager.ts +167 -0
  164. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +302 -0
  165. package/src/recyclerview/utils/adjustOffsetForRTL.ts +17 -0
  166. package/src/recyclerview/utils/componentUtils.ts +28 -0
  167. package/src/recyclerview/utils/findVisibleIndex.ts +94 -0
  168. package/src/recyclerview/utils/measureLayout.ts +89 -0
  169. package/src/utils/AverageWindow.ts +33 -0
  170. package/src/viewability/ViewToken.ts +1 -1
@@ -0,0 +1,379 @@
1
+ import { ConsecutiveNumbers } from "./helpers/ConsecutiveNumbers";
2
+ import { RVGridLayoutManagerImpl } from "./layout-managers/GridLayoutManager";
3
+ import {
4
+ RVDimension,
5
+ RVLayoutInfo,
6
+ RVLayoutManager,
7
+ } from "./layout-managers/LayoutManager";
8
+ import { RVLinearLayoutManagerImpl } from "./layout-managers/LinearLayoutManager";
9
+ import { RVMasonryLayoutManagerImpl } from "./layout-managers/MasonryLayoutManager";
10
+ import { RecycleKeyManagerImpl, RecycleKeyManager } from "./RecycleKeyManager";
11
+ import { RecyclerViewProps } from "./RecyclerViewProps";
12
+ import {
13
+ RVEngagedIndicesTracker,
14
+ RVEngagedIndicesTrackerImpl,
15
+ Velocity,
16
+ } from "./helpers/EngagedIndicesTracker";
17
+ import ViewabilityManager from "../viewability/ViewabilityManager";
18
+
19
+ // Abstracts layout manager, key manager and viewability manager and generates render stack (progressively on load)
20
+ export class RecyclerViewManager<T> {
21
+ private initialDrawBatchSize = 1;
22
+ private engagedIndicesTracker: RVEngagedIndicesTracker;
23
+ private recycleKeyManager: RecycleKeyManager;
24
+ private layoutManager?: RVLayoutManager;
25
+ // Map of index to key
26
+ private renderStack: Map<number, string> = new Map();
27
+ private isFirstLayoutComplete = false;
28
+ private hasRenderedProgressively = false;
29
+ private props: RecyclerViewProps<T>;
30
+ private itemViewabilityManager: ViewabilityManager<T>;
31
+
32
+ public disableRecycling = false;
33
+ public firstItemOffset = 0;
34
+
35
+ constructor(props: RecyclerViewProps<T>) {
36
+ this.props = props;
37
+ this.engagedIndicesTracker = new RVEngagedIndicesTrackerImpl();
38
+ this.recycleKeyManager = new RecycleKeyManagerImpl();
39
+ this.itemViewabilityManager = new ViewabilityManager<T>(this as any);
40
+ }
41
+
42
+ // updates render stack based on the engaged indices which are sorted. Recycles unused keys.
43
+ // TODO: Call getKey anyway if stableIds are present
44
+ private updateRenderStack = (engagedIndices: ConsecutiveNumbers): void => {
45
+ //console.log("updateRenderStack", engagedIndices);
46
+ const newRenderStack = new Map<number, string>();
47
+ for (const [index, key] of this.renderStack) {
48
+ if (!engagedIndices.includes(index)) {
49
+ this.recycleKeyManager.recycleKey(key);
50
+ }
51
+ }
52
+ if (this.disableRecycling) {
53
+ this.recycleKeyManager.clearPool();
54
+ }
55
+ for (const index of engagedIndices) {
56
+ const newKey = this.recycleKeyManager.getKey(
57
+ this.getItemType(index),
58
+ this.getStableId(index),
59
+ this.renderStack.get(index)
60
+ );
61
+ newRenderStack.set(index, newKey);
62
+ }
63
+
64
+ // DANGER
65
+ for (const [index, key] of this.renderStack) {
66
+ if (
67
+ this.recycleKeyManager.hasKeyInPool(key) &&
68
+ !newRenderStack.has(index) &&
69
+ index < (this.props.data?.length ?? 0)
70
+ ) {
71
+ newRenderStack.set(index, key);
72
+ }
73
+ }
74
+
75
+ this.renderStack = newRenderStack;
76
+ };
77
+
78
+ updateProps(props: RecyclerViewProps<T>) {
79
+ this.props = props;
80
+ this.engagedIndicesTracker.drawDistance =
81
+ props.drawDistance ?? this.engagedIndicesTracker.drawDistance;
82
+ if (this.props.drawDistance === 0) {
83
+ this.initialDrawBatchSize = 1;
84
+ } else {
85
+ this.initialDrawBatchSize = (props.numColumns ?? 1) * 2;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Updates the scroll offset and returns the engaged indices if any
91
+ * @param offset
92
+ * @param velocity
93
+ */
94
+ updateScrollOffset(
95
+ offset: number,
96
+ velocity?: Velocity
97
+ ): ConsecutiveNumbers | undefined {
98
+ if (this.layoutManager) {
99
+ const engagedIndices = this.engagedIndicesTracker.updateScrollOffset(
100
+ offset - this.firstItemOffset,
101
+ velocity,
102
+ this.layoutManager
103
+ );
104
+
105
+ if (engagedIndices) {
106
+ this.updateRenderStack(engagedIndices);
107
+ return engagedIndices;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ getIsFirstLayoutComplete() {
114
+ return this.isFirstLayoutComplete;
115
+ }
116
+
117
+ getLayout(index: number) {
118
+ if (!this.layoutManager) {
119
+ throw new Error(
120
+ "LayoutManager is not initialized, layout info is unavailable"
121
+ );
122
+ }
123
+ return this.layoutManager.getLayout(index);
124
+ }
125
+
126
+ // Doesn't include header / foot etc
127
+ getChildContainerDimensions() {
128
+ if (!this.layoutManager) {
129
+ throw new Error(
130
+ "LayoutManager is not initialized, child container layout is unavailable"
131
+ );
132
+ }
133
+ return this.layoutManager.getLayoutSize();
134
+ }
135
+
136
+ getRenderStack() {
137
+ return this.renderStack;
138
+ }
139
+
140
+ getWindowSize() {
141
+ if (!this.layoutManager) {
142
+ throw new Error(
143
+ "LayoutManager is not initialized, window size is unavailable"
144
+ );
145
+ }
146
+ return this.layoutManager.getWindowsSize();
147
+ }
148
+
149
+ // Includes first item offset correction
150
+ getLastScrollOffset() {
151
+ return this.engagedIndicesTracker.scrollOffset;
152
+ }
153
+
154
+ // Doesn't include first item offset correction
155
+ getAbsoluteLastScrollOffset() {
156
+ return this.engagedIndicesTracker.scrollOffset + this.firstItemOffset;
157
+ }
158
+
159
+ updateLayoutParams(windowSize: RVDimension, firstItemOffset: number) {
160
+ this.firstItemOffset = firstItemOffset;
161
+ const LayoutManagerClass = this.getLayoutManagerClass();
162
+ if (
163
+ this.layoutManager &&
164
+ Boolean(this.layoutManager?.isHorizontal()) !==
165
+ Boolean(this.props.horizontal)
166
+ ) {
167
+ throw new Error(
168
+ "Horizontal prop cannot be toggled, you can use a key on FlashList to recreate it."
169
+ );
170
+ }
171
+ if (!(this.layoutManager instanceof LayoutManagerClass)) {
172
+ //console.log("-----> new LayoutManagerClass");
173
+
174
+ this.layoutManager = new LayoutManagerClass(
175
+ {
176
+ windowSize,
177
+ maxColumns: this.props.numColumns ?? 1,
178
+ horizontal: !!this.props.horizontal,
179
+ optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
180
+ overrideItemLayout: (index, layout) => {
181
+ this.props?.overrideItemLayout?.(
182
+ layout,
183
+ this.props.data![index],
184
+ index,
185
+ this.props.numColumns ?? 1,
186
+ this.props.extraData
187
+ );
188
+ },
189
+ },
190
+ this.layoutManager
191
+ );
192
+ } else {
193
+ this.layoutManager.updateLayoutParams({
194
+ windowSize,
195
+ maxColumns: this.props.numColumns ?? 1,
196
+ horizontal: !!this.props.horizontal,
197
+ optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
198
+ });
199
+ }
200
+ }
201
+
202
+ hasLayout() {
203
+ return this.layoutManager !== undefined;
204
+ }
205
+
206
+ getVisibleIndices() {
207
+ if (!this.layoutManager) {
208
+ throw new Error(
209
+ "LayoutManager is not initialized, visible indices are not unavailable"
210
+ );
211
+ }
212
+ return this.engagedIndicesTracker.computeVisibleIndices(this.layoutManager);
213
+ }
214
+
215
+ getEngagedIndices() {
216
+ return this.engagedIndicesTracker.getEngagedIndices();
217
+ }
218
+
219
+ modifyChildrenLayout(
220
+ layoutInfo: RVLayoutInfo[],
221
+ dataLength: number
222
+ ): boolean {
223
+ this.layoutManager?.modifyLayout(layoutInfo, dataLength);
224
+ if (dataLength === 0) {
225
+ return false;
226
+ }
227
+ if (this.layoutManager?.requiresRepaint) {
228
+ // console.log("requiresRepaint triggered");
229
+ this.layoutManager.requiresRepaint = false;
230
+ return true;
231
+ }
232
+ if (this.hasRenderedProgressively) {
233
+ return this.recomputeEngagedIndices() !== undefined; //TODO: Move to an effect as this can block paint for more than necessary
234
+ } else {
235
+ this.renderProgressively();
236
+ }
237
+ return !this.hasRenderedProgressively;
238
+ }
239
+
240
+ computeItemViewability() {
241
+ // Using higher buffer for masonry to avoid missing items
242
+ this.itemViewabilityManager.shouldListenToVisibleIndices &&
243
+ this.itemViewabilityManager.updateViewableItems(
244
+ this.props.masonry
245
+ ? this.engagedIndicesTracker.getEngagedIndices().toArray()
246
+ : this.getVisibleIndices().toArray()
247
+ );
248
+ }
249
+
250
+ recordInteraction() {
251
+ this.itemViewabilityManager.recordInteraction();
252
+ }
253
+
254
+ recomputeViewableItems() {
255
+ this.itemViewabilityManager.recomputeViewableItems();
256
+ }
257
+
258
+ processDataUpdate() {
259
+ if (this.hasLayout()) {
260
+ this.modifyChildrenLayout([], this.props.data?.length ?? 0);
261
+ if (!this.recomputeEngagedIndices()) {
262
+ // recomputeEngagedIndices will update the render stack if there are any changes in the engaged indices.
263
+ // It's important to update render stack so that elements are assgined right keys incase items were deleted.
264
+ this.updateRenderStack(this.engagedIndicesTracker.getEngagedIndices());
265
+ }
266
+ }
267
+ }
268
+
269
+ recomputeEngagedIndices(): ConsecutiveNumbers | undefined {
270
+ return this.updateScrollOffset(this.getAbsoluteLastScrollOffset());
271
+ }
272
+
273
+ dispose() {
274
+ this.itemViewabilityManager.dispose();
275
+ }
276
+
277
+ getInitialScrollIndex() {
278
+ return (
279
+ this.props.initialScrollIndex ??
280
+ (this.props.maintainVisibleContentPosition?.startRenderingFromBottom
281
+ ? this.getDataLength() - 1
282
+ : undefined)
283
+ );
284
+ }
285
+
286
+ getDataLength() {
287
+ return this.props.data?.length ?? 0;
288
+ }
289
+
290
+ private getLayoutManagerClass() {
291
+ // throw errors for incompatible props
292
+ if (this.props.masonry && this.props.horizontal) {
293
+ throw new Error("Masonry and horizontal props are incompatible");
294
+ }
295
+ if ((this.props.numColumns ?? 1) > 1 && this.props.horizontal) {
296
+ throw new Error("numColumns and horizontal props are incompatible");
297
+ }
298
+ return this.props.masonry
299
+ ? RVMasonryLayoutManagerImpl
300
+ : (this.props.numColumns ?? 1) > 1 && !this.props.horizontal
301
+ ? RVGridLayoutManagerImpl
302
+ : RVLinearLayoutManagerImpl;
303
+ }
304
+
305
+ private applyInitialScrollAdjustment() {
306
+ if (!this.layoutManager || this.getDataLength() === 0) {
307
+ return;
308
+ }
309
+
310
+ const initialScrollIndex = this.getInitialScrollIndex();
311
+ const initialItemLayout = this.layoutManager?.getLayout(
312
+ initialScrollIndex ?? 0
313
+ );
314
+ const initialItemOffset = this.props.horizontal
315
+ ? initialItemLayout?.x
316
+ : initialItemLayout?.y;
317
+
318
+ if (initialScrollIndex !== undefined) {
319
+ // console.log(
320
+ // "initialItemOffset",
321
+ // initialScrollIndex,
322
+ // initialItemOffset,
323
+ // this.firstItemOffset
324
+ // );
325
+ this.layoutManager.recomputeLayouts(0, initialScrollIndex);
326
+ this.engagedIndicesTracker.scrollOffset =
327
+ initialItemOffset ?? 0 + this.firstItemOffset;
328
+ } else {
329
+ //console.log("initialItemOffset", initialItemOffset, this.firstItemOffset);
330
+ this.engagedIndicesTracker.scrollOffset =
331
+ (initialItemOffset ?? 0) - this.firstItemOffset;
332
+ }
333
+ }
334
+
335
+ private renderProgressively() {
336
+ const layoutManager = this.layoutManager;
337
+ if (layoutManager) {
338
+ this.applyInitialScrollAdjustment();
339
+ const visibleIndices = this.getVisibleIndices();
340
+ //console.log("---------> visibleIndices", visibleIndices);
341
+ this.hasRenderedProgressively = visibleIndices.every(
342
+ (index) =>
343
+ layoutManager.getLayout(index).isHeightMeasured &&
344
+ layoutManager.getLayout(index).isWidthMeasured
345
+ );
346
+
347
+ if (this.hasRenderedProgressively) {
348
+ this.isFirstLayoutComplete = true;
349
+ }
350
+
351
+ // If everything is measured then render stack will be in sync. The buffer items will get rendered in the next update
352
+ // triggered by the useOnLoad hook.
353
+ !this.hasRenderedProgressively &&
354
+ this.updateRenderStack(
355
+ // pick first n indices from visible ones and n is size of renderStack
356
+ visibleIndices.slice(
357
+ 0,
358
+ Math.min(
359
+ visibleIndices.length,
360
+ this.renderStack.size + this.initialDrawBatchSize
361
+ )
362
+ )
363
+ );
364
+ }
365
+ }
366
+
367
+ private getItemType(index: number): string {
368
+ return (
369
+ this.props.getItemType?.(this.props.data![index], index) ?? "default"
370
+ ).toString();
371
+ }
372
+
373
+ private getStableId(index: number): string {
374
+ return (
375
+ this.props.keyExtractor?.(this.props.data![index], index) ??
376
+ index.toString()
377
+ );
378
+ }
379
+ }
@@ -0,0 +1,10 @@
1
+ import { FlashListProps } from "../FlashListProps";
2
+ import { ScrollViewProps } from "react-native";
3
+
4
+ export interface RecyclerViewProps<TItem>
5
+ extends Omit<FlashListProps<TItem>, "contentContainerStyle"> {
6
+ /**
7
+ * Style for the RecyclerView's parent container.
8
+ */
9
+ contentContainerStyle?: ScrollViewProps["contentContainerStyle"];
10
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * ViewHolder is a core component in FlashList that manages individual item rendering and layout.
3
+ * It handles the rendering of list items, separators, and manages layout updates for each item.
4
+ * The component is memoized to prevent unnecessary re-renders and includes layout comparison logic.
5
+ */
6
+
7
+ import { LayoutChangeEvent } from "react-native";
8
+ import React, {
9
+ RefObject,
10
+ useCallback,
11
+ useLayoutEffect,
12
+ useMemo,
13
+ useRef,
14
+ } from "react";
15
+
16
+ import { FlashListProps, RenderTarget } from "../FlashListProps";
17
+
18
+ import { RVDimension, RVLayout } from "./layout-managers/LayoutManager";
19
+ import { CompatView } from "./components/CompatView";
20
+
21
+ /**
22
+ * Props interface for the ViewHolder component
23
+ * @template TItem - The type of item being rendered in the list
24
+ */
25
+ export interface ViewHolderProps<TItem> {
26
+ /** Index of the item in the data array */
27
+ index: number;
28
+ /** Layout information for positioning and sizing the item */
29
+ layout: RVLayout;
30
+ /** Map to store refs for each ViewHolder instance, keyed by index */
31
+ refHolder: Map<number, RefObject<CompatView | null>>;
32
+ /** Additional data passed to renderItem that can trigger re-renders */
33
+ extraData: any;
34
+ /** Specifies the rendering target (e.g., "Cell", "StickyHeader") */
35
+ target: RenderTarget;
36
+ /** The actual item data to be rendered */
37
+ item: TItem;
38
+ /** The next item in the list, used for rendering separators */
39
+ trailingItem: TItem | undefined;
40
+ /** Function to render the item content */
41
+ renderItem: FlashListProps<TItem>["renderItem"];
42
+ /** Optional custom component to wrap each item */
43
+ CellRendererComponent?: FlashListProps<TItem>["CellRendererComponent"];
44
+ /** Optional component to render between items */
45
+ ItemSeparatorComponent?: FlashListProps<TItem>["ItemSeparatorComponent"];
46
+ /** Whether the list is horizontal or vertical */
47
+ horizontal?: FlashListProps<TItem>["horizontal"];
48
+ /** Callback when the item's size changes */
49
+ onSizeChanged?: (index: number, size: RVDimension) => void;
50
+ }
51
+
52
+ /**
53
+ * Internal ViewHolder component that handles the actual rendering of list items
54
+ * @template TItem - The type of item being rendered in the list
55
+ */
56
+ const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
57
+ // create ref for View
58
+ const viewRef = useRef<CompatView>(null);
59
+ const {
60
+ index,
61
+ refHolder,
62
+ layout,
63
+ onSizeChanged,
64
+ renderItem,
65
+ extraData,
66
+ item,
67
+ target,
68
+ CellRendererComponent,
69
+ ItemSeparatorComponent,
70
+ trailingItem,
71
+ horizontal,
72
+ } = props;
73
+
74
+ useLayoutEffect(() => {
75
+ refHolder.set(index, viewRef);
76
+ return () => {
77
+ if (refHolder.get(index) === viewRef) {
78
+ refHolder.delete(index);
79
+ }
80
+ };
81
+ }, [index, refHolder]);
82
+
83
+ const onLayout = useCallback(
84
+ (event: LayoutChangeEvent) => {
85
+ onSizeChanged?.(index, event.nativeEvent.layout);
86
+ },
87
+ [index, onSizeChanged]
88
+ );
89
+
90
+ const separator = useMemo(() => {
91
+ return ItemSeparatorComponent ? (
92
+ <ItemSeparatorComponent leadingItem={item} trailingItem={trailingItem} />
93
+ ) : null;
94
+ }, [ItemSeparatorComponent, item, trailingItem]);
95
+
96
+ //console.log("ViewHolder re-render", index);
97
+
98
+ const children = useMemo(() => {
99
+ return renderItem?.({ item, index, extraData, target }) ?? null;
100
+ }, [item, index, extraData, target, renderItem]);
101
+
102
+ const style = {
103
+ flexDirection: horizontal ? "row" : "column",
104
+ position: target === "StickyHeader" ? "relative" : "absolute",
105
+ width: layout.enforcedWidth ? layout.width : undefined,
106
+ height: layout.enforcedHeight ? layout.height : undefined,
107
+ minHeight: layout.minHeight,
108
+ minWidth: layout.minWidth,
109
+ maxHeight: layout.maxHeight,
110
+ maxWidth: layout.maxWidth,
111
+ left: layout.x,
112
+ top: layout.y,
113
+ zIndex: 0,
114
+ } as const;
115
+
116
+ //TODO: Fix this type issue
117
+ const CompatContainer = (CellRendererComponent ??
118
+ CompatView) as unknown as any;
119
+
120
+ return (
121
+ <CompatContainer ref={viewRef} onLayout={onLayout} style={style}>
122
+ {children}
123
+ {separator}
124
+ </CompatContainer>
125
+ );
126
+ };
127
+
128
+ /**
129
+ * Memoized ViewHolder component that prevents unnecessary re-renders by comparing props
130
+ * @template TItem - The type of item being rendered in the list
131
+ */
132
+ export const ViewHolder = React.memo(
133
+ ViewHolderInternal,
134
+ (prevProps, nextProps) => {
135
+ // compare all props and spread layout
136
+ return (
137
+ prevProps.index === nextProps.index &&
138
+ areLayoutsEqual(prevProps.layout, nextProps.layout) &&
139
+ prevProps.refHolder === nextProps.refHolder &&
140
+ prevProps.onSizeChanged === nextProps.onSizeChanged &&
141
+ prevProps.extraData === nextProps.extraData &&
142
+ prevProps.target === nextProps.target &&
143
+ prevProps.item === nextProps.item &&
144
+ prevProps.renderItem === nextProps.renderItem &&
145
+ prevProps.CellRendererComponent === nextProps.CellRendererComponent &&
146
+ prevProps.ItemSeparatorComponent === nextProps.ItemSeparatorComponent &&
147
+ prevProps.trailingItem === nextProps.trailingItem &&
148
+ prevProps.horizontal === nextProps.horizontal
149
+ );
150
+ }
151
+ );
152
+
153
+ /**
154
+ * Compares two RVLayout objects to determine if they are equal
155
+ * Used in the memo comparison function to prevent unnecessary re-renders
156
+ * @param prevLayout - Previous layout object
157
+ * @param nextLayout - Next layout object
158
+ * @returns boolean indicating if layouts are equal
159
+ */
160
+ function areLayoutsEqual(prevLayout: RVLayout, nextLayout: RVLayout): boolean {
161
+ return (
162
+ prevLayout.x === nextLayout.x &&
163
+ prevLayout.y === nextLayout.y &&
164
+ prevLayout.width === nextLayout.width &&
165
+ prevLayout.height === nextLayout.height &&
166
+ prevLayout.enforcedWidth === nextLayout.enforcedWidth &&
167
+ prevLayout.enforcedHeight === nextLayout.enforcedHeight &&
168
+ prevLayout.minWidth === nextLayout.minWidth &&
169
+ prevLayout.minHeight === nextLayout.minHeight &&
170
+ prevLayout.maxWidth === nextLayout.maxWidth &&
171
+ prevLayout.maxHeight === nextLayout.maxHeight
172
+ );
173
+ }