@shopify/flash-list 2.0.0-rc.2 → 2.0.0-rc.4

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 (66) hide show
  1. package/dist/FlashListProps.d.ts +10 -2
  2. package/dist/FlashListProps.d.ts.map +1 -1
  3. package/dist/FlashListProps.js.map +1 -1
  4. package/dist/__tests__/RenderStackManager.test.js +1 -2
  5. package/dist/__tests__/RenderStackManager.test.js.map +1 -1
  6. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  7. package/dist/recyclerview/RecyclerView.js +11 -3
  8. package/dist/recyclerview/RecyclerView.js.map +1 -1
  9. package/dist/recyclerview/RecyclerViewManager.d.ts +1 -0
  10. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
  11. package/dist/recyclerview/RecyclerViewManager.js +5 -0
  12. package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
  13. package/dist/recyclerview/RenderStackManager.d.ts +1 -0
  14. package/dist/recyclerview/RenderStackManager.d.ts.map +1 -1
  15. package/dist/recyclerview/RenderStackManager.js +26 -7
  16. package/dist/recyclerview/RenderStackManager.js.map +1 -1
  17. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts +1 -0
  18. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts.map +1 -1
  19. package/dist/recyclerview/helpers/RenderTimeTracker.js +3 -0
  20. package/dist/recyclerview/helpers/RenderTimeTracker.js.map +1 -1
  21. package/dist/recyclerview/hooks/useLayoutState.d.ts +3 -1
  22. package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -1
  23. package/dist/recyclerview/hooks/useLayoutState.js +5 -3
  24. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
  25. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +2 -1
  26. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
  27. package/dist/recyclerview/hooks/useRecyclerViewController.js +237 -190
  28. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
  29. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
  30. package/dist/recyclerview/hooks/useRecyclerViewManager.js +2 -1
  31. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
  32. package/dist/recyclerview/hooks/useRecyclingState.d.ts +4 -2
  33. package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -1
  34. package/dist/recyclerview/hooks/useRecyclingState.js +2 -2
  35. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
  36. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +14 -6
  37. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
  38. package/dist/recyclerview/layout-managers/GridLayoutManager.js +40 -23
  39. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
  40. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +26 -6
  41. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
  42. package/dist/recyclerview/layout-managers/LayoutManager.js +89 -15
  43. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
  44. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +9 -1
  45. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -1
  46. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +28 -12
  47. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
  50. package/dist/viewability/ViewabilityManager.js +10 -3
  51. package/dist/viewability/ViewabilityManager.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/FlashListProps.ts +16 -2
  54. package/src/__tests__/RenderStackManager.test.ts +1 -2
  55. package/src/recyclerview/RecyclerView.tsx +27 -14
  56. package/src/recyclerview/RecyclerViewManager.ts +6 -0
  57. package/src/recyclerview/RenderStackManager.ts +32 -6
  58. package/src/recyclerview/helpers/RenderTimeTracker.ts +4 -0
  59. package/src/recyclerview/hooks/useLayoutState.ts +15 -6
  60. package/src/recyclerview/hooks/useRecyclerViewController.tsx +240 -168
  61. package/src/recyclerview/hooks/useRecyclerViewManager.ts +3 -1
  62. package/src/recyclerview/hooks/useRecyclingState.ts +11 -7
  63. package/src/recyclerview/layout-managers/GridLayoutManager.ts +44 -23
  64. package/src/recyclerview/layout-managers/LayoutManager.ts +98 -20
  65. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +30 -8
  66. package/src/viewability/ViewabilityManager.ts +10 -6
@@ -14,6 +14,9 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
14
14
  /** The width of the bounded area for the grid */
15
15
  private boundedSize: number;
16
16
 
17
+ /** If there's a span change for grid layout, we need to recompute all the widths */
18
+ private fullRelayoutRequired = false;
19
+
17
20
  constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
18
21
  super(params, previousLayoutManager);
19
22
  this.boundedSize = params.windowSize.width;
@@ -33,10 +36,7 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
33
36
  this.boundedSize = params.windowSize.width;
34
37
  if (this.layouts.length > 0) {
35
38
  // update all widths
36
- for (let i = 0; i < this.layouts.length; i++) {
37
- this.layouts[i].width = this.getWidth(i);
38
- }
39
- // console.log("-----> recomputeLayouts");
39
+ this.updateAllWidths();
40
40
 
41
41
  this.recomputeLayouts(0, this.layouts.length - 1);
42
42
  this.requiresRepaint = true;
@@ -57,6 +57,13 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
57
57
  layout.isHeightMeasured = true;
58
58
  layout.isWidthMeasured = true;
59
59
  }
60
+
61
+ // TODO: Can be optimized
62
+ if (this.fullRelayoutRequired) {
63
+ this.updateAllWidths();
64
+ this.fullRelayoutRequired = false;
65
+ return 0;
66
+ }
60
67
  }
61
68
 
62
69
  /**
@@ -72,13 +79,21 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
72
79
  layout.enforcedWidth = true;
73
80
  }
74
81
 
82
+ /**
83
+ * Handles span change for an item.
84
+ * @param index Index of the item
85
+ */
86
+ handleSpanChange(index: number) {
87
+ this.fullRelayoutRequired = true;
88
+ }
89
+
75
90
  /**
76
91
  * Returns the total size of the layout area.
77
92
  * @returns RVDimension containing width and height of the layout
78
93
  */
79
94
  getLayoutSize(): RVDimension {
80
95
  if (this.layouts.length === 0) return { width: 0, height: 0 };
81
- const totalHeight = this.computeTotalHeight(this.layouts.length - 1);
96
+ const totalHeight = this.computeTotalHeightTillRow(this.layouts.length - 1);
82
97
  return {
83
98
  width: this.boundedSize,
84
99
  height: totalHeight,
@@ -91,7 +106,7 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
91
106
  * @param endIndex Ending index of items to recompute
92
107
  */
93
108
  recomputeLayouts(startIndex: number, endIndex: number): void {
94
- const newStartIndex = this.locateFirstNeighbourIndex(
109
+ const newStartIndex = this.locateFirstIndexInRow(
95
110
  Math.max(0, startIndex - 1)
96
111
  );
97
112
  const startVal = this.getLayout(newStartIndex);
@@ -122,25 +137,23 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
122
137
  * @returns Width of the item
123
138
  */
124
139
  private getWidth(index: number): number {
125
- const span = this.getSpanSizeInfo(index).span ?? 1;
126
- return (this.boundedSize / this.maxColumns) * span;
140
+ return (this.boundedSize / this.maxColumns) * this.getSpan(index);
127
141
  }
128
142
 
129
143
  /**
130
144
  * Processes items in a row and returns the tallest item.
131
145
  * Also handles height normalization for items in the same row.
132
146
  * Tallest item per row helps in forcing tallest items height on neighbouring items.
133
- * @param index Index of the last item in the row
147
+ * @param endIndex Index of the last item in the row
134
148
  * @returns The tallest item in the row
135
149
  */
136
- private processAndReturnTallestItemInRow(index: number): RVLayout {
137
- const startIndex = this.locateFirstNeighbourIndex(index);
138
- const y = this.layouts[startIndex].y;
150
+ private processAndReturnTallestItemInRow(endIndex: number): RVLayout {
151
+ const startIndex = this.locateFirstIndexInRow(endIndex);
139
152
  let tallestItem: RVLayout | undefined;
140
153
  let maxHeight = 0;
141
154
  let i = startIndex;
142
155
  let isMeasured = false;
143
- while (Math.ceil(this.layouts[i].y) === Math.ceil(y)) {
156
+ while (i <= endIndex) {
144
157
  const layout = this.layouts[i];
145
158
  isMeasured = isMeasured || Boolean(layout.isHeightMeasured);
146
159
  maxHeight = Math.max(maxHeight, layout.height);
@@ -156,7 +169,9 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
156
169
  break;
157
170
  }
158
171
  }
159
-
172
+ if (!tallestItem && maxHeight > 0) {
173
+ maxHeight = Number.MAX_SAFE_INTEGER;
174
+ }
160
175
  tallestItem = tallestItem ?? this.layouts[startIndex];
161
176
 
162
177
  if (!isMeasured) {
@@ -170,7 +185,7 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
170
185
  this.requiresRepaint = true;
171
186
  }
172
187
  i = startIndex;
173
- while (Math.ceil(this.layouts[i].y) === Math.ceil(y)) {
188
+ while (i <= endIndex) {
174
189
  this.layouts[i].minHeight = targetHeight;
175
190
  if (targetHeight > 0) {
176
191
  this.layouts[i].height = targetHeight;
@@ -187,15 +202,15 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
187
202
 
188
203
  /**
189
204
  * Computes the total height of the layout.
190
- * @param index Index of the last item in the layout
205
+ * @param endIndex Index of the last item in the row
191
206
  * @returns Total height of the layout
192
207
  */
193
- private computeTotalHeight(index: number): number {
194
- const startIndex = this.locateFirstNeighbourIndex(index);
208
+ private computeTotalHeightTillRow(endIndex: number): number {
209
+ const startIndex = this.locateFirstIndexInRow(endIndex);
195
210
  const y = this.layouts[startIndex].y;
196
211
  let maxHeight = 0;
197
212
  let i = startIndex;
198
- while (Math.ceil(this.layouts[i].y) === Math.ceil(y)) {
213
+ while (i <= endIndex) {
199
214
  maxHeight = Math.max(maxHeight, this.layouts[i].height);
200
215
  i++;
201
216
  if (i >= this.layouts.length) {
@@ -205,6 +220,12 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
205
220
  return y + maxHeight;
206
221
  }
207
222
 
223
+ private updateAllWidths() {
224
+ for (let i = 0; i < this.layouts.length; i++) {
225
+ this.layouts[i].width = this.getWidth(i);
226
+ }
227
+ }
228
+
208
229
  /**
209
230
  * Checks if an item can fit within the bounded width.
210
231
  * @param itemX Starting X position of the item
@@ -217,14 +238,14 @@ export class RVGridLayoutManagerImpl extends RVLayoutManager {
217
238
 
218
239
  /**
219
240
  * Locates the index of the first item in the current row.
220
- * @param startIndex Index to start searching from
241
+ * @param itemIndex Index to start searching from
221
242
  * @returns Index of the first item in the row
222
243
  */
223
- private locateFirstNeighbourIndex(startIndex: number): number {
224
- if (startIndex === 0) {
244
+ private locateFirstIndexInRow(itemIndex: number): number {
245
+ if (itemIndex === 0) {
225
246
  return 0;
226
247
  }
227
- let i = startIndex;
248
+ let i = itemIndex;
228
249
  for (; i >= 0; i--) {
229
250
  if (this.layouts[i].x === 0) {
230
251
  break;
@@ -20,8 +20,6 @@ export abstract class RVLayoutManager {
20
20
  protected layouts: RVLayout[];
21
21
  /** Dimensions of the visible window/viewport */
22
22
  protected windowSize: RVDimension;
23
- /** Information about item spans and sizes */
24
- protected spanSizeInfo: SpanSizeInfo = {};
25
23
  /** Maximum number of columns in the layout */
26
24
  protected maxColumns: number;
27
25
 
@@ -40,7 +38,20 @@ export abstract class RVLayoutManager {
40
38
  /** Window for tracking average widths by item type */
41
39
  private widthAverageWindow: MultiTypeAverageWindow;
42
40
  /** Maximum number of items to process in a single layout pass */
43
- private maxItemsToProcess = 250; // TODO: make this dynamic
41
+ private maxItemsToProcess = 250;
42
+ /** Information about item spans and sizes */
43
+ private spanSizeInfo: SpanSizeInfo = {};
44
+ /** Span tracker for each item */
45
+ private spanTracker: (number | undefined)[] = [];
46
+
47
+ /** Current max index with changed layout */
48
+ private currentMaxIndexWithChangedLayout = -1;
49
+
50
+ /**
51
+ * Last index that was skipped during layout computation.
52
+ * Used to determine if a layout needs to be recomputed.
53
+ */
54
+ private lastSkippedLayoutIndex = Number.MAX_VALUE;
44
55
 
45
56
  constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
46
57
  this.heightAverageWindow = new MultiTypeAverageWindow(5, 200);
@@ -148,7 +159,7 @@ export abstract class RVLayoutManager {
148
159
  }
149
160
  const startIndex = Math.min(...indices);
150
161
  // Recompute layouts starting from the smallest index in the original indices array
151
- this.recomputeLayouts(
162
+ this._recomputeLayouts(
152
163
  this.getMinRecomputeIndex(startIndex),
153
164
  this.getMaxRecomputeIndex(startIndex)
154
165
  );
@@ -160,41 +171,49 @@ export abstract class RVLayoutManager {
160
171
  * @param totalItemCount Total number of items in the list
161
172
  */
162
173
  modifyLayout(layoutInfo: RVLayoutInfo[], totalItemCount: number): void {
174
+ this.maxItemsToProcess = Math.max(
175
+ this.maxItemsToProcess,
176
+ layoutInfo.length * 10
177
+ );
163
178
  let minRecomputeIndex = Number.MAX_VALUE;
164
179
 
165
180
  if (this.layouts.length > totalItemCount) {
166
181
  this.layouts.length = totalItemCount;
182
+ this.spanTracker.length = totalItemCount;
167
183
  minRecomputeIndex = totalItemCount - 1; // <0 gets skipped so it's safe to set to totalItemCount - 1
168
184
  }
169
185
  // update average windows
170
186
  minRecomputeIndex = Math.min(
171
187
  minRecomputeIndex,
172
- this.computeEstimatesAndMinRecomputeIndex(layoutInfo)
188
+ this.computeEstimatesAndMinMaxChangedLayout(layoutInfo)
173
189
  );
174
190
 
175
191
  if (this.layouts.length < totalItemCount && totalItemCount > 0) {
176
192
  const startIndex = this.layouts.length;
177
193
  this.layouts.length = totalItemCount;
194
+ this.spanTracker.length = totalItemCount;
178
195
  for (let i = startIndex; i < totalItemCount; i++) {
179
196
  this.getLayout(i);
197
+ this.getSpan(i);
180
198
  }
181
199
  this.recomputeLayouts(startIndex, totalItemCount - 1);
182
200
  }
183
- minRecomputeIndex = Math.min(
184
- minRecomputeIndex,
185
- this.processLayoutInfo(layoutInfo, totalItemCount) ?? minRecomputeIndex
186
- );
201
+
187
202
  // compute minRecomputeIndex
203
+
188
204
  minRecomputeIndex = Math.min(
189
205
  minRecomputeIndex,
190
- this.computeEstimatesAndMinRecomputeIndex(layoutInfo)
206
+ this.lastSkippedLayoutIndex,
207
+ this.computeMinIndexWithChangedSpan(layoutInfo),
208
+ this.processLayoutInfo(layoutInfo, totalItemCount) ?? minRecomputeIndex,
209
+ this.computeEstimatesAndMinMaxChangedLayout(layoutInfo)
191
210
  );
211
+
192
212
  if (minRecomputeIndex >= 0 && minRecomputeIndex < totalItemCount) {
193
- this.recomputeLayouts(
194
- this.getMinRecomputeIndex(minRecomputeIndex),
195
- this.getMaxRecomputeIndex(minRecomputeIndex)
196
- );
213
+ const maxRecomputeIndex = this.getMaxRecomputeIndex(minRecomputeIndex);
214
+ this._recomputeLayouts(minRecomputeIndex, maxRecomputeIndex);
197
215
  }
216
+ this.currentMaxIndexWithChangedLayout = -1;
198
217
  }
199
218
 
200
219
  /**
@@ -260,16 +279,30 @@ export abstract class RVLayoutManager {
260
279
  protected abstract estimateLayout(index: number): void;
261
280
 
262
281
  /**
263
- * Gets span size information for an item, applying any overrides.
282
+ * Gets span for an item, applying any overrides.
283
+ * This is intended to be called during a relayout call. The value is tracked and used to determine if a span change has occurred.
284
+ * If skipTracking is true, the operation is not tracked. Can be useful if span is required outside of a relayout call.
285
+ * The tracker is used to call handleSpanChange if a span change has occurred before relayout call.
286
+ * // TODO: improve this contract.
264
287
  * @param index Index of the item
265
- * @returns SpanSizeInfo for the item
288
+ * @returns Span for the item
266
289
  */
267
- protected getSpanSizeInfo(index: number): SpanSizeInfo {
290
+ protected getSpan(index: number, skipTracking = false): number {
268
291
  this.spanSizeInfo.span = undefined;
269
292
  this.overrideItemLayout(index, this.spanSizeInfo);
270
- return this.spanSizeInfo;
293
+ const span = Math.min(this.spanSizeInfo.span ?? 1, this.maxColumns);
294
+ if (!skipTracking) {
295
+ this.spanTracker[index] = span;
296
+ }
297
+ return span;
271
298
  }
272
299
 
300
+ /**
301
+ * Method to handle span change for an item. Can be overridden by subclasses.
302
+ * @param index Index of the item
303
+ */
304
+ protected handleSpanChange(index: number) {}
305
+
273
306
  /**
274
307
  * Gets the maximum index to process in a single layout pass.
275
308
  * @param startIndex Starting index
@@ -277,7 +310,8 @@ export abstract class RVLayoutManager {
277
310
  */
278
311
  private getMaxRecomputeIndex(startIndex: number): number {
279
312
  return Math.min(
280
- startIndex + this.maxItemsToProcess,
313
+ Math.max(startIndex, this.currentMaxIndexWithChangedLayout) +
314
+ this.maxItemsToProcess,
281
315
  this.layouts.length - 1
282
316
  );
283
317
  }
@@ -291,12 +325,36 @@ export abstract class RVLayoutManager {
291
325
  return startIndex;
292
326
  }
293
327
 
328
+ private _recomputeLayouts(startIndex: number, endIndex: number): void {
329
+ this.recomputeLayouts(startIndex, endIndex);
330
+ if (
331
+ this.lastSkippedLayoutIndex >= startIndex &&
332
+ this.lastSkippedLayoutIndex <= endIndex
333
+ ) {
334
+ this.lastSkippedLayoutIndex = Number.MAX_VALUE;
335
+ }
336
+
337
+ if (endIndex + 1 < this.layouts.length) {
338
+ this.lastSkippedLayoutIndex = Math.min(
339
+ endIndex + 1,
340
+ this.lastSkippedLayoutIndex
341
+ );
342
+ const lastIndex = this.layouts.length - 1;
343
+ // Since layout managers derive height from last indices we need to make
344
+ // sure they're not too much out of sync.
345
+ if (this.layouts[lastIndex].y < this.layouts[endIndex].y) {
346
+ this.recomputeLayouts(this.lastSkippedLayoutIndex, lastIndex);
347
+ this.lastSkippedLayoutIndex = Number.MAX_VALUE;
348
+ }
349
+ }
350
+ }
351
+
294
352
  /**
295
353
  * Computes size estimates and finds the minimum recompute index.
296
354
  * @param layoutInfo Array of layout information for items
297
355
  * @returns Minimum index that needs recomputation
298
356
  */
299
- private computeEstimatesAndMinRecomputeIndex(
357
+ private computeEstimatesAndMinMaxChangedLayout(
300
358
  layoutInfo: RVLayoutInfo[]
301
359
  ): number {
302
360
  let minRecomputeIndex = Number.MAX_VALUE;
@@ -304,6 +362,7 @@ export abstract class RVLayoutManager {
304
362
  const { index, dimensions } = info;
305
363
  const storedLayout = this.layouts[index];
306
364
  if (
365
+ index >= this.lastSkippedLayoutIndex ||
307
366
  !storedLayout ||
308
367
  !storedLayout.isHeightMeasured ||
309
368
  !storedLayout.isWidthMeasured ||
@@ -311,6 +370,10 @@ export abstract class RVLayoutManager {
311
370
  areDimensionsNotEqual(storedLayout.width, dimensions.width)
312
371
  ) {
313
372
  minRecomputeIndex = Math.min(minRecomputeIndex, index);
373
+ this.currentMaxIndexWithChangedLayout = Math.max(
374
+ this.currentMaxIndexWithChangedLayout,
375
+ index
376
+ );
314
377
  }
315
378
  this.heightAverageWindow.addValue(
316
379
  dimensions.height,
@@ -323,6 +386,21 @@ export abstract class RVLayoutManager {
323
386
  }
324
387
  return minRecomputeIndex;
325
388
  }
389
+
390
+ private computeMinIndexWithChangedSpan(layoutInfo: RVLayoutInfo[]): number {
391
+ let minIndexWithChangedSpan = Number.MAX_VALUE;
392
+ for (const info of layoutInfo) {
393
+ const { index } = info;
394
+ const span = this.getSpan(index, true);
395
+ const storedSpan = this.spanTracker[index];
396
+ if (span !== storedSpan) {
397
+ this.spanTracker[index] = span;
398
+ this.handleSpanChange(index);
399
+ minIndexWithChangedSpan = Math.min(minIndexWithChangedSpan, index);
400
+ }
401
+ }
402
+ return minIndexWithChangedSpan;
403
+ }
326
404
  }
327
405
 
328
406
  /**
@@ -19,10 +19,13 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
19
19
  /** Current column index for sequential placement */
20
20
  private currentColumn = 0;
21
21
 
22
+ /** If there's a span change for masonry layout, we need to recompute all the widths */
23
+ private fullRelayoutRequired = false;
24
+
22
25
  constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
23
26
  super(params, previousLayoutManager);
24
27
  this.boundedSize = params.windowSize.width;
25
- this.optimizeItemArrangement = params.optimizeItemArrangement ?? false;
28
+ this.optimizeItemArrangement = params.optimizeItemArrangement;
26
29
  this.columnHeights = this.columnHeights ?? Array(this.maxColumns).fill(0);
27
30
  }
28
31
 
@@ -44,10 +47,7 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
44
47
  // console.log("-----> recomputeLayouts");
45
48
 
46
49
  // 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
- }
50
+ this.updateAllWidths();
51
51
  this.recomputeLayouts(0, this.layouts.length - 1);
52
52
  this.requiresRepaint = true;
53
53
  }
@@ -69,6 +69,13 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
69
69
  layout.isWidthMeasured = true;
70
70
  this.layouts[index] = layout;
71
71
  }
72
+
73
+ // TODO: Can be optimized
74
+ if (this.fullRelayoutRequired) {
75
+ this.updateAllWidths();
76
+ this.fullRelayoutRequired = false;
77
+ return 0;
78
+ }
72
79
  }
73
80
 
74
81
  /**
@@ -87,6 +94,14 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
87
94
  layout.enforcedWidth = true;
88
95
  }
89
96
 
97
+ /**
98
+ * Handles span change for an item.
99
+ * @param index Index of the item
100
+ */
101
+ handleSpanChange(index: number) {
102
+ this.fullRelayoutRequired = true;
103
+ }
104
+
90
105
  /**
91
106
  * Returns the total size of the layout area.
92
107
  * @returns RVDimension containing width and height of the layout
@@ -124,7 +139,8 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
124
139
 
125
140
  for (let i = startIndex; i < itemCount; i++) {
126
141
  const layout = this.getLayout(i);
127
- const span = this.getSpanSizeInfo(i).span ?? 1;
142
+ // Skip tracking span because we're not changing widths
143
+ const span = this.getSpan(i, true);
128
144
 
129
145
  if (this.optimizeItemArrangement) {
130
146
  if (span === 1) {
@@ -147,8 +163,14 @@ export class RVMasonryLayoutManagerImpl extends RVLayoutManager {
147
163
  * @returns Width of the item
148
164
  */
149
165
  private getWidth(index: number): number {
150
- const span = this.getSpanSizeInfo(index).span ?? 1;
151
- return (this.boundedSize / this.maxColumns) * span;
166
+ return (this.boundedSize / this.maxColumns) * this.getSpan(index);
167
+ }
168
+
169
+ private updateAllWidths() {
170
+ for (let i = 0; i < this.layouts.length; i++) {
171
+ this.layouts[i].width = this.getWidth(i);
172
+ this.layouts[i].minHeight = undefined;
173
+ }
152
174
  }
153
175
 
154
176
  /**
@@ -22,17 +22,21 @@ export default class ViewabilityManager<T> {
22
22
  this.viewabilityHelpers.push(
23
23
  this.createViewabilityHelper(
24
24
  flashListRef.props.viewabilityConfig,
25
- flashListRef.props.onViewableItemsChanged
25
+ (info) => {
26
+ flashListRef.props.onViewableItemsChanged?.(info);
27
+ }
26
28
  )
27
29
  );
28
30
  }
29
31
  (flashListRef.props.viewabilityConfigCallbackPairs ?? []).forEach(
30
- (pair) => {
32
+ (pair, index) => {
31
33
  this.viewabilityHelpers.push(
32
- this.createViewabilityHelper(
33
- pair.viewabilityConfig,
34
- pair.onViewableItemsChanged
35
- )
34
+ this.createViewabilityHelper(pair.viewabilityConfig, (info) => {
35
+ const callback =
36
+ flashListRef.props.viewabilityConfigCallbackPairs?.[index]
37
+ ?.onViewableItemsChanged;
38
+ callback?.(info);
39
+ })
36
40
  );
37
41
  }
38
42
  );