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

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 +232 -187
  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 +232 -165
  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
@@ -116,13 +116,17 @@ const RecyclerViewComponent = <T,>(
116
116
  // Initialize core RecyclerView manager and content offset management
117
117
  const { recyclerViewManager, velocityTracker } =
118
118
  useRecyclerViewManager(props);
119
- const { applyContentOffset, applyInitialScrollIndex, handlerMethods } =
120
- useRecyclerViewController(
121
- recyclerViewManager,
122
- ref,
123
- scrollViewRef,
124
- scrollAnchorRef
125
- );
119
+ const {
120
+ applyOffsetCorrection,
121
+ computeFirstVisibleIndexForOffsetCorrection,
122
+ applyInitialScrollIndex,
123
+ handlerMethods,
124
+ } = useRecyclerViewController(
125
+ recyclerViewManager,
126
+ ref,
127
+ scrollViewRef,
128
+ scrollAnchorRef
129
+ );
126
130
 
127
131
  // Initialize view holder collection ref
128
132
  const viewHolderCollectionRef = useRef<ViewHolderCollectionRef>(null);
@@ -211,7 +215,7 @@ const RecyclerViewComponent = <T,>(
211
215
  setRenderId((prev) => prev + 1);
212
216
  } else {
213
217
  viewHolderCollectionRef.current?.commitLayout();
214
- applyContentOffset();
218
+ applyOffsetCorrection();
215
219
  }
216
220
  });
217
221
 
@@ -247,6 +251,7 @@ const RecyclerViewComponent = <T,>(
247
251
  }
248
252
 
249
253
  if (isMomentumEnd) {
254
+ computeFirstVisibleIndexForOffsetCorrection();
250
255
  if (!recyclerViewManager.isOffsetProjectionEnabled) {
251
256
  return;
252
257
  }
@@ -272,6 +277,7 @@ const RecyclerViewComponent = <T,>(
272
277
  },
273
278
  [
274
279
  checkBounds,
280
+ computeFirstVisibleIndexForOffsetCorrection,
275
281
  horizontal,
276
282
  isHorizontalRTL,
277
283
  recyclerViewManager,
@@ -439,6 +445,18 @@ const RecyclerViewComponent = <T,>(
439
445
  );
440
446
  }, [horizontal, shouldRenderFromBottom, adjustmentMinHeight]);
441
447
 
448
+ const scrollAnchor = useMemo(() => {
449
+ if (shouldMaintainVisibleContentPosition) {
450
+ return (
451
+ <ScrollAnchor
452
+ horizontal={Boolean(horizontal)}
453
+ scrollAnchorRef={scrollAnchorRef}
454
+ />
455
+ );
456
+ }
457
+ return null;
458
+ }, [horizontal, shouldMaintainVisibleContentPosition]);
459
+
442
460
  // console.log("render", recyclerViewManager.getRenderStack());
443
461
 
444
462
  // Render the main RecyclerView structure
@@ -485,12 +503,7 @@ const RecyclerViewComponent = <T,>(
485
503
  {...overrideProps}
486
504
  >
487
505
  {/* Scroll anchor for maintaining content position */}
488
- {maintainVisibleContentPositionInternal && (
489
- <ScrollAnchor
490
- horizontal={Boolean(horizontal)}
491
- scrollAnchorRef={scrollAnchorRef}
492
- />
493
- )}
506
+ {scrollAnchor}
494
507
  {isHorizontalRTL && viewToMeasureBoundedSize}
495
508
  {renderHeader}
496
509
  {!isHorizontalRTL && viewToMeasureBoundedSize}
@@ -304,6 +304,12 @@ export class RecyclerViewManager<T> {
304
304
  return this.updateScrollOffset(this.getAbsoluteLastScrollOffset());
305
305
  }
306
306
 
307
+ restoreIfNeeded() {
308
+ if (this._isDisposed) {
309
+ this._isDisposed = false;
310
+ }
311
+ }
312
+
307
313
  dispose() {
308
314
  this._isDisposed = true;
309
315
  this.itemViewabilityManager.dispose();
@@ -26,6 +26,8 @@ export class RenderStackManager {
26
26
  // Counter for generating unique sequential keys
27
27
  private keyCounter: number;
28
28
 
29
+ private unProcessedIndices: Set<number>;
30
+
29
31
  /**
30
32
  * @param maxItemsInRecyclePool - Maximum number of items that can be in the recycle pool
31
33
  */
@@ -35,6 +37,7 @@ export class RenderStackManager {
35
37
  this.keyMap = new Map();
36
38
  this.stableIdMap = new Map();
37
39
  this.keyCounter = 0;
40
+ this.unProcessedIndices = new Set();
38
41
  }
39
42
 
40
43
  /**
@@ -57,6 +60,7 @@ export class RenderStackManager {
57
60
  dataLength: number
58
61
  ) {
59
62
  this.clearRecyclePool();
63
+ this.unProcessedIndices.clear();
60
64
 
61
65
  // Recycle keys for items that are no longer valid or visible
62
66
  this.keyMap.forEach((keyInfo, key) => {
@@ -65,6 +69,9 @@ export class RenderStackManager {
65
69
  this.recycleKey(key);
66
70
  return;
67
71
  }
72
+ if (!this.disableRecycling) {
73
+ this.unProcessedIndices.add(index);
74
+ }
68
75
  if (!engagedIndices.includes(index)) {
69
76
  this.recycleKey(key);
70
77
  return;
@@ -114,7 +121,7 @@ export class RenderStackManager {
114
121
  }
115
122
 
116
123
  // Clean up stale items and manage the recycle pool size
117
- this.cleanup(getStableId, engagedIndices, dataLength);
124
+ this.cleanup(getStableId, getItemType, engagedIndices, dataLength);
118
125
  }
119
126
 
120
127
  /**
@@ -131,6 +138,7 @@ export class RenderStackManager {
131
138
  */
132
139
  private cleanup(
133
140
  getStableId: (index: number) => string,
141
+ getItemType: (index: number) => string,
134
142
  engagedIndices: ConsecutiveNumbers,
135
143
  dataLength: number
136
144
  ) {
@@ -139,11 +147,27 @@ export class RenderStackManager {
139
147
  // Remove items that are no longer in the dataset
140
148
  for (const [key, keyInfo] of this.keyMap.entries()) {
141
149
  const { index, itemType, stableId } = keyInfo;
142
- if (index >= dataLength || getStableId(index) !== stableId) {
143
- // TODO: Find a way to reusue the key, instead of deleting it
144
- this.deleteKeyFromRecyclePool(itemType, key);
145
- this.stableIdMap.delete(stableId);
146
- itemsToDelete.push(key);
150
+ const indexOutOfBounds = index >= dataLength;
151
+ const hasStableIdChanged =
152
+ !indexOutOfBounds && getStableId(index) !== stableId;
153
+
154
+ if (indexOutOfBounds || hasStableIdChanged) {
155
+ const nextIndex = this.unProcessedIndices.values().next().value;
156
+ let shouldDeleteKey = true;
157
+
158
+ if (nextIndex !== undefined) {
159
+ const nextItemType = getItemType(nextIndex);
160
+ const nextStableId = getStableId(nextIndex);
161
+ if (itemType === nextItemType) {
162
+ this.syncItem(nextIndex, nextItemType, nextStableId);
163
+ shouldDeleteKey = false;
164
+ }
165
+ }
166
+ if (shouldDeleteKey) {
167
+ this.deleteKeyFromRecyclePool(itemType, key);
168
+ this.stableIdMap.delete(stableId);
169
+ itemsToDelete.push(key);
170
+ }
147
171
  }
148
172
  }
149
173
 
@@ -215,6 +239,8 @@ export class RenderStackManager {
215
239
  this.getKeyFromRecyclePool(itemType) ||
216
240
  this.generateKey();
217
241
 
242
+ this.unProcessedIndices.delete(index);
243
+
218
244
  const keyInfo = this.keyMap.get(newKey);
219
245
  if (keyInfo) {
220
246
  // Update an existing key's metadata
@@ -26,6 +26,10 @@ export class RenderTimeTracker {
26
26
  }
27
27
  }
28
28
 
29
+ getRawValue() {
30
+ return this.renderTimeAvgWindow.currentValue;
31
+ }
32
+
29
33
  getAverageRenderTime() {
30
34
  if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
31
35
  return this.defaultRenderTime;
@@ -2,6 +2,13 @@ import { useState, useCallback } from "react";
2
2
 
3
3
  import { useRecyclerViewContext } from "../RecyclerViewContextProvider";
4
4
 
5
+ export type LayoutStateSetter<T> = (
6
+ newValue: T | ((prevValue: T) => T),
7
+ skipParentLayout?: boolean
8
+ ) => void;
9
+
10
+ export type LayoutStateInitialValue<T> = T | (() => T);
11
+
5
12
  /**
6
13
  * Custom hook that combines state management with RecyclerView layout updates.
7
14
  * This hook provides a way to manage state that affects the layout of the RecyclerView,
@@ -13,8 +20,8 @@ import { useRecyclerViewContext } from "../RecyclerViewContextProvider";
13
20
  * - A setter function that updates the state and triggers a layout recalculation
14
21
  */
15
22
  export function useLayoutState<T>(
16
- initialState: T | (() => T)
17
- ): [T, (newValue: T | ((prevValue: T) => T)) => void] {
23
+ initialState: LayoutStateInitialValue<T>
24
+ ): [T, LayoutStateSetter<T>] {
18
25
  // Initialize state with the provided initial value
19
26
  const [state, setState] = useState<T>(initialState);
20
27
  // Get the RecyclerView context for layout management
@@ -28,16 +35,18 @@ export function useLayoutState<T>(
28
35
  * @param newValue - Either a new state value or a function that receives the previous state
29
36
  * and returns the new state
30
37
  */
31
- const setLayoutState = useCallback(
32
- (newValue: T | ((prevValue: T) => T)) => {
38
+ const setLayoutState: LayoutStateSetter<T> = useCallback(
39
+ (newValue, skipParentLayout) => {
33
40
  // Update the state using either the new value or the result of the updater function
34
41
  setState((prevValue) =>
35
42
  typeof newValue === "function"
36
43
  ? (newValue as (prevValue: T) => T)(prevValue)
37
44
  : newValue
38
45
  );
39
- // Trigger a layout recalculation in the RecyclerView
40
- recyclerViewContext?.layout();
46
+ if (!skipParentLayout) {
47
+ // Trigger a layout recalculation in the RecyclerView
48
+ recyclerViewContext?.layout();
49
+ }
41
50
  },
42
51
  [recyclerViewContext]
43
52
  );