@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,302 @@
1
+ import {
2
+ LayoutParams,
3
+ RVDimension,
4
+ RVLayout,
5
+ RVLayoutInfo,
6
+ RVLayoutManager,
7
+ } from "./LayoutManager";
8
+
9
+ /**
10
+ * MasonryLayoutManager implementation that arranges items in a masonry/pinterest-style layout.
11
+ * Items are placed in columns, with support for items spanning multiple columns.
12
+ * Can optimize item placement to minimize column height differences.
13
+ */
14
+ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
15
+ /** The width of the bounded area for the masonry layout */
16
+ private boundedSize: number;
17
+ /** Array tracking the current height of each column */
18
+ private columnHeights: number[];
19
+ /** Current column index for sequential placement */
20
+ private currentColumn: number = 0;
21
+
22
+ constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
23
+ super(params, previousLayoutManager);
24
+ this.boundedSize = params.windowSize.width;
25
+ this.optimizeItemArrangement = params.optimizeItemArrangement ?? false;
26
+ this.columnHeights = Array(this.maxColumns).fill(0);
27
+ }
28
+
29
+ /**
30
+ * Updates layout parameters and triggers recomputation if necessary.
31
+ * @param params New layout parameters
32
+ */
33
+ updateLayoutParams(params: LayoutParams) {
34
+ const prevMaxColumns = this.maxColumns;
35
+ const prevOptimizeItemArrangement = this.optimizeItemArrangement;
36
+ super.updateLayoutParams(params);
37
+ if (
38
+ this.boundedSize !== params.windowSize.width ||
39
+ prevMaxColumns !== params.maxColumns ||
40
+ prevOptimizeItemArrangement !== params.optimizeItemArrangement
41
+ ) {
42
+ this.boundedSize = params.windowSize.width;
43
+ if (this.layouts.length > 0) {
44
+ //console.log("-----> recomputeLayouts");
45
+
46
+ //update all widths
47
+ for (let i = 0; i < this.layouts.length; i++) {
48
+ this.layouts[i].width = this.getWidth(i);
49
+ this.layouts[i].minHeight = undefined;
50
+ }
51
+ //TODO: Optimize masonry in general
52
+ this.recomputeLayouts(0, this.layouts.length - 1);
53
+ this.requiresRepaint = true;
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Processes layout information for items, updating their dimensions.
60
+ * @param layoutInfo Array of layout information for items (real measurements)
61
+ * @param itemCount Total number of items in the list
62
+ */
63
+ processLayoutInfo(layoutInfo: RVLayoutInfo[], itemCount: number) {
64
+ // Update layout information
65
+ for (const info of layoutInfo) {
66
+ const { index, dimensions } = info;
67
+ const layout = this.layouts[index];
68
+ layout.height = dimensions.height;
69
+ layout.isHeightMeasured = true;
70
+ layout.isWidthMeasured = true;
71
+ this.layouts[index] = layout;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Estimates layout dimensions for an item at the given index.
77
+ * Can be called by base class if estimate is required.
78
+ * @param index Index of the item to estimate layout for
79
+ */
80
+ estimateLayout(index: number) {
81
+ const layout = this.layouts[index];
82
+
83
+ // Set width based on columns and span
84
+ layout.width = this.getWidth(index);
85
+ layout.height = this.getEstimatedHeight(index);
86
+
87
+ layout.isWidthMeasured = true;
88
+ layout.enforcedWidth = true;
89
+ }
90
+
91
+ /**
92
+ * Returns the total size of the layout area.
93
+ * @returns RVDimension containing width and height of the layout
94
+ */
95
+ getLayoutSize(): RVDimension {
96
+ if (this.layouts.length === 0) return { width: 0, height: 0 };
97
+
98
+ // Find the tallest column
99
+ const maxHeight = Math.max(...this.columnHeights);
100
+
101
+ return {
102
+ width: this.boundedSize,
103
+ height: maxHeight,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Recomputes layouts for items in the given range.
109
+ * Uses different placement strategies based on optimization settings.
110
+ * @param startIndex Starting index of items to recompute
111
+ * @param endIndex Ending index of items to recompute
112
+ */
113
+ recomputeLayouts(startIndex: number, endIndex: number): void {
114
+ // Reset column heights if starting from the beginning
115
+ if (startIndex === 0) {
116
+ this.columnHeights = Array(this.maxColumns).fill(0);
117
+ this.currentColumn = 0;
118
+ } else {
119
+ // Find the y-position of the first item to recompute
120
+ // and adjust column heights accordingly
121
+ this.updateColumnHeightsToIndex(startIndex);
122
+ }
123
+
124
+ const itemCount = this.layouts.length;
125
+
126
+ for (let i = startIndex; i < itemCount; i++) {
127
+ const layout = this.getLayout(i);
128
+ const span = this.getSpanSizeInfo(i).span ?? 1;
129
+
130
+ if (this.optimizeItemArrangement) {
131
+ if (span === 1) {
132
+ // For single column items, place in the shortest column
133
+ this.placeSingleColumnItem(layout);
134
+ } else {
135
+ // For multi-column items, find the best position
136
+ this.placeOptimizedMultiColumnItem(layout, span);
137
+ }
138
+ } else {
139
+ // No optimization - place items sequentially
140
+ this.placeItemSequentially(layout, span);
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Calculates the width of an item based on its span.
147
+ * @param index Index of the item
148
+ * @returns Width of the item
149
+ */
150
+ private getWidth(index: number): number {
151
+ const span = this.getSpanSizeInfo(index).span ?? 1;
152
+ return (this.boundedSize / this.maxColumns) * span;
153
+ }
154
+
155
+ /**
156
+ * Places an item sequentially in the next available position.
157
+ * @param layout Layout information for the item
158
+ * @param span Number of columns the item spans
159
+ */
160
+ private placeItemSequentially(layout: RVLayout, span: number): void {
161
+ // Check if the item can fit in the current row
162
+ if (this.currentColumn + span > this.maxColumns) {
163
+ // Move to the next row
164
+ this.currentColumn = 0;
165
+ }
166
+
167
+ // Find the maximum height of the columns this item will span
168
+ let maxHeight = this.columnHeights[this.currentColumn];
169
+ for (
170
+ let col = this.currentColumn + 1;
171
+ col < this.currentColumn + span;
172
+ col++
173
+ ) {
174
+ if (col < this.maxColumns) {
175
+ maxHeight = Math.max(maxHeight, this.columnHeights[col]);
176
+ }
177
+ }
178
+
179
+ // Place the item
180
+ layout.x = (this.boundedSize / this.maxColumns) * this.currentColumn;
181
+ layout.y = maxHeight;
182
+
183
+ // Update column heights
184
+ for (let col = this.currentColumn; col < this.currentColumn + span; col++) {
185
+ if (col < this.maxColumns) {
186
+ this.columnHeights[col] = maxHeight + layout.height;
187
+ }
188
+ }
189
+
190
+ // Move to the next column
191
+ this.currentColumn += span;
192
+ if (this.currentColumn >= this.maxColumns) {
193
+ this.currentColumn = 0;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Places a single-column item in the shortest available column.
199
+ * @param layout Layout information for the item
200
+ */
201
+ private placeSingleColumnItem(layout: RVLayout): void {
202
+ // Find the shortest column
203
+ let shortestColumnIndex = 0;
204
+ let minHeight = this.columnHeights[0];
205
+
206
+ for (let i = 1; i < this.maxColumns; i++) {
207
+ if (this.columnHeights[i] < minHeight) {
208
+ minHeight = this.columnHeights[i];
209
+ shortestColumnIndex = i;
210
+ }
211
+ }
212
+
213
+ // Place the item in the shortest column
214
+ layout.x = (this.boundedSize / this.maxColumns) * shortestColumnIndex;
215
+ layout.y = this.columnHeights[shortestColumnIndex];
216
+
217
+ // Update the column height
218
+ this.columnHeights[shortestColumnIndex] += layout.height;
219
+ }
220
+
221
+ /**
222
+ * Places a multi-column item in the position that minimizes total column heights.
223
+ * @param layout Layout information for the item
224
+ * @param span Number of columns the item spans
225
+ */
226
+ private placeOptimizedMultiColumnItem(layout: RVLayout, span: number): void {
227
+ let bestStartColumn = 0;
228
+ let minTotalHeight = Number.MAX_VALUE;
229
+
230
+ // Try all possible positions
231
+ for (let startCol = 0; startCol <= this.maxColumns - span; startCol++) {
232
+ // Find the maximum height among the columns this item would span
233
+ let maxHeight = this.columnHeights[startCol];
234
+ for (let col = startCol + 1; col < startCol + span; col++) {
235
+ maxHeight = Math.max(maxHeight, this.columnHeights[col]);
236
+ }
237
+
238
+ // Calculate the total height after placing the item
239
+ let totalHeight = 0;
240
+ for (let col = 0; col < this.maxColumns; col++) {
241
+ if (col >= startCol && col < startCol + span) {
242
+ totalHeight += maxHeight + layout.height;
243
+ } else {
244
+ totalHeight += this.columnHeights[col];
245
+ }
246
+ }
247
+
248
+ // Update best position if this is better
249
+ if (totalHeight < minTotalHeight) {
250
+ minTotalHeight = totalHeight;
251
+ bestStartColumn = startCol;
252
+ }
253
+ }
254
+
255
+ // Place the item at the best position
256
+ const maxHeight = Math.max(
257
+ ...this.columnHeights.slice(bestStartColumn, bestStartColumn + span)
258
+ );
259
+ layout.x = (this.boundedSize / this.maxColumns) * bestStartColumn;
260
+ layout.y = maxHeight;
261
+
262
+ // Update column heights
263
+ for (let col = bestStartColumn; col < bestStartColumn + span; col++) {
264
+ this.columnHeights[col] = maxHeight + layout.height;
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Updates column heights up to a given index by recalculating item positions.
270
+ * @param index Index to update column heights up to
271
+ */
272
+ private updateColumnHeightsToIndex(index: number): void {
273
+ // Reset column heights
274
+ this.columnHeights = Array(this.maxColumns).fill(0);
275
+ this.currentColumn = 0;
276
+
277
+ // Recalculate column heights up to the given index
278
+ for (let i = 0; i < index; i++) {
279
+ const layout = this.layouts[i];
280
+ const itemWidth = layout.width;
281
+ const columnWidth = this.boundedSize / this.maxColumns;
282
+ const span = Math.round(itemWidth / columnWidth);
283
+
284
+ // Find which columns this item spans
285
+ const startColumn = Math.round(layout.x / columnWidth);
286
+ const endColumn = Math.min(startColumn + span, this.maxColumns);
287
+
288
+ // Update column heights
289
+ for (let col = startColumn; col < endColumn; col++) {
290
+ this.columnHeights[col] = Math.max(
291
+ this.columnHeights[col],
292
+ layout.y + layout.height
293
+ );
294
+ }
295
+
296
+ // Update current column for non-optimized layout
297
+ if (!this.optimizeItemArrangement) {
298
+ this.currentColumn = (startColumn + span) % this.maxColumns;
299
+ }
300
+ }
301
+ }
302
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Adjusts the scroll offset for Right-to-Left (RTL) layouts.
3
+ * offset it flipped when RTL is enabled.
4
+ * This function converts a left-to-right offset to its RTL equivalent.
5
+ *
6
+ * @param offset - The original scroll offset in LTR layout
7
+ * @param contentSize - The total size of the scrollable content
8
+ * @param windowSize - The size of the visible window/viewport
9
+ * @returns The adjusted offset for RTL layout
10
+ */
11
+ export function adjustOffsetForRTL(
12
+ offset: number,
13
+ contentSize: number,
14
+ windowSize: number
15
+ ) {
16
+ return contentSize - offset - windowSize;
17
+ }
@@ -0,0 +1,28 @@
1
+ import React from "react";
2
+
3
+ /**
4
+ * Helper function to handle both React components and React elements.
5
+ * This utility ensures proper rendering of components whether they are passed as
6
+ * component types or pre-rendered elements.
7
+ *
8
+ * @param component - Can be a React component type, React element, null, or undefined
9
+ * @returns A valid React element if the input is valid, null otherwise
10
+ *
11
+ * @example
12
+ * // With a component type
13
+ * getValidComponent(MyComponent)
14
+ *
15
+ * @example
16
+ * // With a pre-rendered element
17
+ * getValidComponent(<MyComponent />)
18
+ */
19
+ export const getValidComponent = (
20
+ component: React.ComponentType<any> | React.ReactElement | null | undefined
21
+ ): React.ReactElement | null => {
22
+ if (React.isValidElement(component)) {
23
+ return component;
24
+ } else if (typeof component === "function") {
25
+ return React.createElement(component);
26
+ }
27
+ return null;
28
+ };
@@ -0,0 +1,94 @@
1
+ import { RVLayout } from "../layout-managers/LayoutManager";
2
+
3
+ /**
4
+ * A helper function to perform binary search for the first or last visible index.
5
+ * This function efficiently finds items that are visible within a viewport by using
6
+ * a binary search algorithm on sorted layouts.
7
+ *
8
+ * @param layouts - The sorted array of RVLayout objects, sorted by either x or y position
9
+ * @param threshold - The threshold value to determine visibility (viewport boundary)
10
+ * @param isSortedByX - A boolean indicating if the array is sorted by x (true) or y (false)
11
+ * @param findFirst - A boolean indicating whether to find the first (true) or last (false) visible index
12
+ * @returns The index of the visible layout or -1 if none are visible
13
+ *
14
+ * @remarks
15
+ * The binary search implementation ensures O(log n) time complexity for finding visible items.
16
+ * The function assumes the layouts array is pre-sorted by the relevant dimension (x or y).
17
+ */
18
+ function binarySearchVisibleIndex(
19
+ layouts: RVLayout[],
20
+ threshold: number,
21
+ isSortedByX: boolean,
22
+ findFirst: boolean
23
+ ): number {
24
+ let left = 0;
25
+ let right = layouts.length - 1;
26
+ let visibleIndex = -1;
27
+
28
+ while (left <= right) {
29
+ const mid = Math.floor((left + right) / 2);
30
+ const layout = layouts[mid];
31
+
32
+ // Check visibility based on the sorting criteria
33
+ const position = isSortedByX ? layout.x : layout.y;
34
+ const size = isSortedByX ? layout.width : layout.height;
35
+
36
+ //TODO: Will this find item bigger than viewport
37
+ if (findFirst) {
38
+ // Logic for finding the first visible index
39
+ if (position >= threshold || position + size >= threshold) {
40
+ // Potential visible index found, continue searching left for earlier visible items
41
+ visibleIndex = mid;
42
+ right = mid - 1;
43
+ } else {
44
+ // Search in the right half for visible items
45
+ left = mid + 1;
46
+ }
47
+ } else if (position <= threshold) {
48
+ // Potential visible index found, continue searching right for later visible items
49
+ visibleIndex = mid;
50
+ left = mid + 1;
51
+ } else {
52
+ // Search in the left half for visible items
53
+ right = mid - 1;
54
+ }
55
+ }
56
+
57
+ return visibleIndex;
58
+ }
59
+
60
+ /**
61
+ * Finds the first visible index in a sorted array of RVLayout objects.
62
+ * This is a wrapper around binarySearchVisibleIndex that specifically finds
63
+ * the first item that becomes visible in the viewport.
64
+ *
65
+ * @param layouts - The sorted array of RVLayout objects
66
+ * @param threshold - The threshold value to determine visibility
67
+ * @param isSortedByX - A boolean indicating if the array is sorted by x (true) or y (false)
68
+ * @returns The index of the first visible layout or -1 if none are visible
69
+ */
70
+ export function findFirstVisibleIndex(
71
+ layouts: RVLayout[],
72
+ threshold: number,
73
+ isSortedByX: boolean
74
+ ): number {
75
+ return binarySearchVisibleIndex(layouts, threshold, isSortedByX, true);
76
+ }
77
+
78
+ /**
79
+ * Finds the last visible index in a sorted array of RVLayout objects.
80
+ * This is a wrapper around binarySearchVisibleIndex that specifically finds
81
+ * the last item that remains visible in the viewport.
82
+ *
83
+ * @param layouts - The sorted array of RVLayout objects
84
+ * @param threshold - The threshold value to determine visibility
85
+ * @param isSortedByX - A boolean indicating if the array is sorted by x (true) or y (false)
86
+ * @returns The index of the last visible layout or -1 if none are visible
87
+ */
88
+ export function findLastVisibleIndex(
89
+ layouts: RVLayout[],
90
+ threshold: number,
91
+ isSortedByX: boolean
92
+ ): number {
93
+ return binarySearchVisibleIndex(layouts, threshold, isSortedByX, false);
94
+ }
@@ -0,0 +1,89 @@
1
+ import { PixelRatio, View } from "react-native";
2
+
3
+ interface Layout {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ /**
11
+ * Measures the layout of a view relative to itselft.
12
+ * Using measure wasn't returing accurate values but this workaround does.
13
+ * Returns the x, y coordinates and dimensions of the view.
14
+ *
15
+ * @param view - The React Native View component to measure
16
+ * @returns An object containing x, y, width, and height measurements
17
+ */
18
+ export function measureLayout(view: View, oldLayout: Layout | undefined) {
19
+ // const layout = view.unstable_getBoundingClientRect();
20
+ // layout.width = roundOffPixel(layout.width);
21
+ // layout.height = roundOffPixel(layout.height);
22
+ // return layout;
23
+ return measureLayoutRelative(view, view, oldLayout);
24
+ }
25
+
26
+ /**
27
+ * Measures the layout of a view relative to another view.
28
+ * Useful for measuring positions relative to a specific reference view.
29
+ *
30
+ * @param view - The React Native View component to measure
31
+ * @param relativeTo - The reference view to measure against
32
+ * @returns An object containing x, y, width, and height measurements
33
+ */
34
+ export function measureLayoutRelative(
35
+ view: View,
36
+ relativeTo: View,
37
+ oldLayout: Layout | undefined
38
+ ) {
39
+ const layout = { x: 0, y: 0, width: 0, height: 0 };
40
+ view.measureLayout(relativeTo, (x, y, width, height) => {
41
+ layout.x = x;
42
+ layout.y = y;
43
+ layout.width = roundOffPixel(width);
44
+ layout.height = roundOffPixel(height);
45
+ });
46
+
47
+ if (oldLayout) {
48
+ if (areDimensionsEqual(layout.width, oldLayout.width)) {
49
+ layout.width = oldLayout.width;
50
+ }
51
+ if (areDimensionsEqual(layout.height, oldLayout.height)) {
52
+ layout.height = oldLayout.height;
53
+ }
54
+ }
55
+ return layout;
56
+ }
57
+
58
+ /**
59
+ * Checks if two dimension values are not equal, with a small tolerance.
60
+ * Used to handle floating-point precision issues in layout measurements.
61
+ *
62
+ * @param value1 - First dimension value to compare
63
+ * @param value2 - Second dimension value to compare
64
+ * @returns true if the values are significantly different, false otherwise
65
+ */
66
+ export function areDimensionsNotEqual(value1: number, value2: number): boolean {
67
+ return !areDimensionsEqual(value1, value2);
68
+ }
69
+
70
+ /**
71
+ * Checks if two dimension values are equal, with a small tolerance.
72
+ * Used to handle floating-point precision issues in layout measurements.
73
+ *
74
+ * @param value1 - First dimension value to compare
75
+ * @param value2 - Second dimension value to compare
76
+ * @returns true if the values are approximately equal, false otherwise
77
+ */
78
+ export function areDimensionsEqual(value1: number, value2: number): boolean {
79
+ return (
80
+ Math.abs(
81
+ PixelRatio.getPixelSizeForLayoutSize(value1) -
82
+ PixelRatio.getPixelSizeForLayoutSize(value2)
83
+ ) <= 1
84
+ );
85
+ }
86
+
87
+ export function roundOffPixel(value: number): number {
88
+ return PixelRatio.roundToNearestPixel(value);
89
+ }
@@ -47,3 +47,36 @@ export class AverageWindow {
47
47
  return newTarget;
48
48
  }
49
49
  }
50
+
51
+ export class MultiTypeAverageWindow {
52
+ private averageWindows: Map<string | number, AverageWindow>;
53
+ private windowSize: number;
54
+ private defaultValue?: number;
55
+ /**
56
+ * @param windowSize Size of the average window
57
+ * @param defaultValue Default value to return if no value is available
58
+ */
59
+ constructor(windowSize: number, defaultValue?: number) {
60
+ this.averageWindows = new Map<string | number, AverageWindow>();
61
+ this.windowSize = windowSize;
62
+ this.defaultValue = defaultValue;
63
+ }
64
+
65
+ public addValue(value: number, type: string | number): void {
66
+ let averageWindow = this.averageWindows.get(type);
67
+ if (!averageWindow) {
68
+ averageWindow = new AverageWindow(this.windowSize);
69
+ this.averageWindows.set(type, averageWindow);
70
+ }
71
+ averageWindow.addValue(value);
72
+ }
73
+
74
+ public getCurrentValue(type: string | number): number {
75
+ const averageWindow = this.averageWindows.get(type);
76
+ return averageWindow?.currentValue ?? this.defaultValue ?? 0;
77
+ }
78
+
79
+ public reset(): void {
80
+ this.averageWindows.clear();
81
+ }
82
+ }
@@ -1,5 +1,5 @@
1
1
  export default interface ViewToken {
2
- item: any;
2
+ item: any; //TODO: fix this type
3
3
  key: string;
4
4
  index: number | null;
5
5
  isViewable: boolean;