@shopify/flash-list 2.0.0-alpha.9 → 2.0.0-rc.10

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 (209) hide show
  1. package/README.md +37 -97
  2. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/BlankAreaEvent.kt +2 -2
  3. package/dist/AnimatedFlashList.d.ts.map +1 -1
  4. package/dist/AnimatedFlashList.js +3 -3
  5. package/dist/AnimatedFlashList.js.map +1 -1
  6. package/dist/FlashList.d.ts +9 -0
  7. package/dist/FlashList.d.ts.map +1 -1
  8. package/dist/FlashList.js +20 -0
  9. package/dist/FlashList.js.map +1 -1
  10. package/dist/FlashListProps.d.ts +30 -10
  11. package/dist/FlashListProps.d.ts.map +1 -1
  12. package/dist/FlashListProps.js.map +1 -1
  13. package/dist/FlashListRef.d.ts +305 -0
  14. package/dist/FlashListRef.d.ts.map +1 -0
  15. package/dist/FlashListRef.js +3 -0
  16. package/dist/FlashListRef.js.map +1 -0
  17. package/dist/MasonryFlashList.js.map +1 -1
  18. package/dist/__tests__/RecyclerView.test.js +72 -28
  19. package/dist/__tests__/RecyclerView.test.js.map +1 -1
  20. package/dist/__tests__/RenderStackManager.test.d.ts +2 -0
  21. package/dist/__tests__/RenderStackManager.test.d.ts.map +1 -0
  22. package/dist/__tests__/RenderStackManager.test.js +485 -0
  23. package/dist/__tests__/RenderStackManager.test.js.map +1 -0
  24. package/dist/__tests__/helpers/createLayoutManager.d.ts.map +1 -1
  25. package/dist/__tests__/helpers/createLayoutManager.js +3 -4
  26. package/dist/__tests__/helpers/createLayoutManager.js.map +1 -1
  27. package/dist/__tests__/useUnmountAwareCallbacks.test.js +1 -1
  28. package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +1 -1
  29. package/dist/benchmark/useBenchmark.js +0 -25
  30. package/dist/benchmark/useBenchmark.js.map +1 -1
  31. package/dist/benchmark/useFlatListBenchmark.js +8 -7
  32. package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/native/config/PlatformHelper.android.d.ts +1 -0
  38. package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
  39. package/dist/native/config/PlatformHelper.android.js +1 -0
  40. package/dist/native/config/PlatformHelper.android.js.map +1 -1
  41. package/dist/native/config/PlatformHelper.d.ts +1 -0
  42. package/dist/native/config/PlatformHelper.d.ts.map +1 -1
  43. package/dist/native/config/PlatformHelper.ios.d.ts +1 -0
  44. package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
  45. package/dist/native/config/PlatformHelper.ios.js +1 -0
  46. package/dist/native/config/PlatformHelper.ios.js.map +1 -1
  47. package/dist/native/config/PlatformHelper.js +1 -0
  48. package/dist/native/config/PlatformHelper.js.map +1 -1
  49. package/dist/native/config/PlatformHelper.web.d.ts +1 -0
  50. package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
  51. package/dist/native/config/PlatformHelper.web.js +1 -0
  52. package/dist/native/config/PlatformHelper.web.js.map +1 -1
  53. package/dist/recyclerview/RecyclerView.d.ts +2 -1
  54. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  55. package/dist/recyclerview/RecyclerView.js +104 -57
  56. package/dist/recyclerview/RecyclerView.js.map +1 -1
  57. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +41 -6
  58. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
  59. package/dist/recyclerview/RecyclerViewContextProvider.js +4 -0
  60. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
  61. package/dist/recyclerview/RecyclerViewManager.d.ts +24 -7
  62. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
  63. package/dist/recyclerview/RecyclerViewManager.js +119 -113
  64. package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
  65. package/dist/recyclerview/RenderStackManager.d.ts +86 -0
  66. package/dist/recyclerview/RenderStackManager.d.ts.map +1 -0
  67. package/dist/recyclerview/RenderStackManager.js +343 -0
  68. package/dist/recyclerview/RenderStackManager.js.map +1 -0
  69. package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
  70. package/dist/recyclerview/ViewHolder.js +5 -3
  71. package/dist/recyclerview/ViewHolder.js.map +1 -1
  72. package/dist/recyclerview/ViewHolderCollection.d.ts +9 -3
  73. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
  74. package/dist/recyclerview/ViewHolderCollection.js +26 -9
  75. package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
  76. package/dist/recyclerview/components/ScrollAnchor.d.ts +2 -2
  77. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
  78. package/dist/recyclerview/components/ScrollAnchor.js +9 -5
  79. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
  80. package/dist/recyclerview/components/StickyHeaders.d.ts +1 -1
  81. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
  82. package/dist/recyclerview/components/StickyHeaders.js +40 -33
  83. package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
  84. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +45 -1
  85. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -1
  86. package/dist/recyclerview/helpers/EngagedIndicesTracker.js +77 -20
  87. package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -1
  88. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts +11 -0
  89. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts.map +1 -0
  90. package/dist/recyclerview/helpers/RenderTimeTracker.js +42 -0
  91. package/dist/recyclerview/helpers/RenderTimeTracker.js.map +1 -0
  92. package/dist/recyclerview/helpers/VelocityTracker.d.ts +29 -0
  93. package/dist/recyclerview/helpers/VelocityTracker.d.ts.map +1 -0
  94. package/dist/recyclerview/helpers/VelocityTracker.js +70 -0
  95. package/dist/recyclerview/helpers/VelocityTracker.js.map +1 -0
  96. package/dist/recyclerview/hooks/useBoundDetection.d.ts +1 -2
  97. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
  98. package/dist/recyclerview/hooks/useBoundDetection.js +56 -22
  99. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
  100. package/dist/recyclerview/hooks/useLayoutState.d.ts +3 -1
  101. package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -1
  102. package/dist/recyclerview/hooks/useLayoutState.js +5 -3
  103. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
  104. package/dist/recyclerview/hooks/useMappingHelper.d.ts +1 -1
  105. package/dist/recyclerview/hooks/useMappingHelper.d.ts.map +1 -1
  106. package/dist/recyclerview/hooks/useMappingHelper.js +1 -1
  107. package/dist/recyclerview/hooks/useMappingHelper.js.map +1 -1
  108. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
  109. package/dist/recyclerview/hooks/useOnLoad.js +4 -6
  110. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
  111. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +5 -49
  112. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
  113. package/dist/recyclerview/hooks/useRecyclerViewController.js +315 -204
  114. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
  115. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +2 -0
  116. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
  117. package/dist/recyclerview/hooks/useRecyclerViewManager.js +11 -1
  118. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
  119. package/dist/recyclerview/hooks/useRecyclingState.d.ts +4 -2
  120. package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -1
  121. package/dist/recyclerview/hooks/useRecyclingState.js +2 -2
  122. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
  123. package/dist/recyclerview/hooks/useSecondaryProps.js +1 -1
  124. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +10 -3
  125. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -1
  126. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +33 -4
  127. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -1
  128. package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -1
  129. package/dist/recyclerview/hooks/useUnmountFlag.js +1 -0
  130. package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -1
  131. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +18 -4
  132. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
  133. package/dist/recyclerview/layout-managers/GridLayoutManager.js +60 -21
  134. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
  135. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +35 -21
  136. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
  137. package/dist/recyclerview/layout-managers/LayoutManager.js +92 -28
  138. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
  139. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +9 -1
  140. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -1
  141. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +28 -12
  142. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
  143. package/dist/recyclerview/utils/measureLayout.web.d.ts.map +1 -1
  144. package/dist/recyclerview/utils/measureLayout.web.js +1 -3
  145. package/dist/recyclerview/utils/measureLayout.web.js.map +1 -1
  146. package/dist/tsconfig.tsbuildinfo +1 -1
  147. package/dist/viewability/ViewToken.d.ts +2 -2
  148. package/dist/viewability/ViewToken.d.ts.map +1 -1
  149. package/dist/viewability/ViewabilityHelper.js +1 -1
  150. package/dist/viewability/ViewabilityHelper.js.map +1 -1
  151. package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
  152. package/dist/viewability/ViewabilityManager.js +11 -5
  153. package/dist/viewability/ViewabilityManager.js.map +1 -1
  154. package/jestSetup.js +30 -11
  155. package/package.json +2 -1
  156. package/src/AnimatedFlashList.ts +3 -2
  157. package/src/FlashList.tsx +24 -0
  158. package/src/FlashListProps.ts +41 -10
  159. package/src/FlashListRef.ts +320 -0
  160. package/src/MasonryFlashList.tsx +2 -2
  161. package/src/__tests__/RecyclerView.test.tsx +106 -31
  162. package/src/__tests__/RenderStackManager.test.ts +574 -0
  163. package/src/__tests__/helpers/createLayoutManager.ts +2 -3
  164. package/src/__tests__/useUnmountAwareCallbacks.test.tsx +12 -12
  165. package/src/benchmark/useBenchmark.ts +0 -37
  166. package/src/benchmark/useFlatListBenchmark.ts +2 -2
  167. package/src/index.ts +2 -1
  168. package/src/native/config/PlatformHelper.android.ts +1 -0
  169. package/src/native/config/PlatformHelper.ios.ts +1 -0
  170. package/src/native/config/PlatformHelper.ts +1 -0
  171. package/src/native/config/PlatformHelper.web.ts +1 -0
  172. package/src/recyclerview/RecyclerView.tsx +139 -75
  173. package/src/recyclerview/RecyclerViewContextProvider.ts +52 -7
  174. package/src/recyclerview/RecyclerViewManager.ts +135 -98
  175. package/src/recyclerview/RenderStackManager.ts +317 -0
  176. package/src/recyclerview/ViewHolder.tsx +5 -3
  177. package/src/recyclerview/ViewHolderCollection.tsx +42 -14
  178. package/src/recyclerview/components/ScrollAnchor.tsx +21 -9
  179. package/src/recyclerview/components/StickyHeaders.tsx +63 -45
  180. package/src/recyclerview/helpers/EngagedIndicesTracker.ts +118 -23
  181. package/src/recyclerview/helpers/RenderTimeTracker.ts +42 -0
  182. package/src/recyclerview/helpers/VelocityTracker.ts +77 -0
  183. package/src/recyclerview/hooks/useBoundDetection.ts +72 -23
  184. package/src/recyclerview/hooks/useLayoutState.ts +15 -6
  185. package/src/recyclerview/hooks/useMappingHelper.ts +1 -1
  186. package/src/recyclerview/hooks/useOnLoad.ts +4 -6
  187. package/src/recyclerview/hooks/useRecyclerViewController.tsx +364 -254
  188. package/src/recyclerview/hooks/useRecyclerViewManager.ts +13 -1
  189. package/src/recyclerview/hooks/useRecyclingState.ts +11 -7
  190. package/src/recyclerview/hooks/useSecondaryProps.tsx +1 -1
  191. package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +39 -3
  192. package/src/recyclerview/hooks/useUnmountFlag.ts +1 -0
  193. package/src/recyclerview/layout-managers/GridLayoutManager.ts +67 -23
  194. package/src/recyclerview/layout-managers/LayoutManager.ts +110 -41
  195. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +30 -8
  196. package/src/recyclerview/utils/measureLayout.web.ts +1 -3
  197. package/src/viewability/ViewToken.ts +2 -2
  198. package/src/viewability/ViewabilityHelper.ts +1 -1
  199. package/src/viewability/ViewabilityManager.ts +16 -9
  200. package/dist/__tests__/RecycleKeyManager.test.d.ts +0 -2
  201. package/dist/__tests__/RecycleKeyManager.test.d.ts.map +0 -1
  202. package/dist/__tests__/RecycleKeyManager.test.js +0 -210
  203. package/dist/__tests__/RecycleKeyManager.test.js.map +0 -1
  204. package/dist/recyclerview/RecycleKeyManager.d.ts +0 -82
  205. package/dist/recyclerview/RecycleKeyManager.d.ts.map +0 -1
  206. package/dist/recyclerview/RecycleKeyManager.js +0 -135
  207. package/dist/recyclerview/RecycleKeyManager.js.map +0 -1
  208. package/src/__tests__/RecycleKeyManager.test.ts +0 -254
  209. package/src/recyclerview/RecycleKeyManager.ts +0 -185
@@ -199,42 +199,5 @@ function computeSuggestions(
199
199
  `Data count is low. Try to increase it to a large number (e.g 200) using the 'useDataMultiplier' hook.`
200
200
  );
201
201
  }
202
- const distanceFromWindow = roundToDecimalPlaces(
203
- flashListRef.current.firstItemOffset,
204
- 0
205
- );
206
- if (
207
- (flashListRef.current.props.estimatedFirstItemOffset || 0) !==
208
- distanceFromWindow
209
- ) {
210
- suggestions.push(
211
- `estimatedFirstItemOffset can be set to ${distanceFromWindow}`
212
- );
213
- }
214
- const rlv = flashListRef.current.recyclerlistview_unsafe;
215
- const horizontal = flashListRef.current.props.horizontal;
216
- if (rlv) {
217
- const sizeArray = rlv.props.dataProvider
218
- .getAllData()
219
- .map((_, index) =>
220
- horizontal
221
- ? rlv.getLayout?.(index)?.width || 0
222
- : rlv.getLayout?.(index)?.height || 0
223
- );
224
- const averageSize = Math.round(
225
- sizeArray.reduce((prev, current) => prev + current, 0) /
226
- sizeArray.length
227
- );
228
- if (
229
- Math.abs(
230
- averageSize -
231
- (flashListRef.current.props.estimatedItemSize ??
232
- flashListRef.current.state.layoutProvider
233
- .defaultEstimatedItemSize)
234
- ) > 5
235
- ) {
236
- suggestions.push(`estimatedItemSize can be set to ${averageSize}`);
237
- }
238
- }
239
202
  }
240
203
  }
@@ -25,7 +25,7 @@ export function useFlatListBenchmark(
25
25
  ) {
26
26
  useEffect(() => {
27
27
  const cancellable = new Cancellable();
28
- if (flatListRef.current) {
28
+ if (flatListRef.current && flatListRef.current.props) {
29
29
  if (!(Number(flatListRef.current.props.data?.length) > 0)) {
30
30
  throw new Error("Data is empty, cannot run benchmark");
31
31
  }
@@ -71,7 +71,7 @@ async function runScrollBenchmark(
71
71
  scrollSpeedMultiplier: number
72
72
  ): Promise<void> {
73
73
  if (flatListRef.current) {
74
- const horizontal = flatListRef.current.props.horizontal;
74
+ const horizontal = Boolean(flatListRef.current.props?.horizontal);
75
75
 
76
76
  const fromX = 0;
77
77
  const fromY = 0;
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { RecyclerView } from "./recyclerview/RecyclerView";
5
5
 
6
6
  // Keep this unmodified for TS type checking
7
7
  export { default as FlashList } from "./FlashList";
8
+ export { FlashListRef } from "./FlashListRef";
8
9
  export {
9
10
  FlashListProps,
10
11
  ContentStyle,
@@ -52,7 +53,7 @@ export { default as ViewToken } from "./viewability/ViewToken";
52
53
  export { default as CellContainer } from "./native/cell-container/CellContainer";
53
54
  export { RecyclerView } from "./recyclerview/RecyclerView";
54
55
  export { RecyclerViewProps } from "./recyclerview/RecyclerViewProps";
55
- export { useRecyclerViewContext } from "./recyclerview/RecyclerViewContextProvider";
56
+ export { useFlashListContext } from "./recyclerview/RecyclerViewContextProvider";
56
57
 
57
58
  // @ts-ignore - This is ignored by TypeScript but will be present in the compiled JS
58
59
  // In the compiled JS, this will override the previous FlashList export with a conditional one
@@ -3,6 +3,7 @@ import { BaseItemAnimator } from "recyclerlistview";
3
3
  const PlatformConfig = {
4
4
  defaultDrawDistance: 250,
5
5
  supportsOffsetCorrection: true,
6
+ trackAverageRenderTimeForOffsetProjection: true,
6
7
  // Using rotate instead of scaleY on Android to avoid performance issues. Issue: https://github.com/Shopify/flash-list/issues/751
7
8
  invertedTransformStyle: { transform: [{ rotate: "180deg" }] },
8
9
  invertedTransformStyleHorizontal: { transform: [{ rotate: "180deg" }] },
@@ -3,6 +3,7 @@ import { BaseItemAnimator } from "recyclerlistview";
3
3
  const PlatformConfig = {
4
4
  defaultDrawDistance: 250,
5
5
  supportsOffsetCorrection: true,
6
+ trackAverageRenderTimeForOffsetProjection: false,
6
7
  invertedTransformStyle: { transform: [{ scaleY: -1 }] },
7
8
  invertedTransformStyleHorizontal: { transform: [{ scaleX: -1 }] },
8
9
  };
@@ -4,6 +4,7 @@ import { DefaultJSItemAnimator } from "recyclerlistview/dist/reactnative/platfor
4
4
  const PlatformConfig = {
5
5
  defaultDrawDistance: 250,
6
6
  supportsOffsetCorrection: false,
7
+ trackAverageRenderTimeForOffsetProjection: false,
7
8
  invertedTransformStyle: { transform: [{ scaleY: -1 }] },
8
9
  invertedTransformStyleHorizontal: { transform: [{ scaleX: -1 }] },
9
10
  };
@@ -6,6 +6,7 @@ import { DefaultJSItemAnimator } from "recyclerlistview/dist/reactnative/platfor
6
6
  const PlatformConfig = {
7
7
  defaultDrawDistance: 500,
8
8
  supportsOffsetCorrection: false,
9
+ trackAverageRenderTimeForOffsetProjection: false,
9
10
  invertedTransformStyle: { transform: [{ scaleY: -1 }] },
10
11
  invertedTransformStyleHorizontal: { transform: [{ scaleX: -1 }] },
11
12
  };
@@ -19,6 +19,8 @@ import {
19
19
  NativeSyntheticEvent,
20
20
  } from "react-native";
21
21
 
22
+ import { FlashListRef } from "../FlashListRef";
23
+
22
24
  import { RVDimension } from "./layout-managers/LayoutManager";
23
25
  import {
24
26
  areDimensionsNotEqual,
@@ -47,6 +49,7 @@ import { useSecondaryProps } from "./hooks/useSecondaryProps";
47
49
  import { StickyHeaders, StickyHeaderRef } from "./components/StickyHeaders";
48
50
  import { ScrollAnchor, ScrollAnchorRef } from "./components/ScrollAnchor";
49
51
  import { useRecyclerViewController } from "./hooks/useRecyclerViewController";
52
+ import { RenderTimeTracker } from "./helpers/RenderTimeTracker";
50
53
 
51
54
  /**
52
55
  * Main RecyclerView component that handles list rendering, scrolling, and item recycling.
@@ -54,7 +57,7 @@ import { useRecyclerViewController } from "./hooks/useRecyclerViewController";
54
57
  */
55
58
  const RecyclerViewComponent = <T,>(
56
59
  props: RecyclerViewProps<T>,
57
- ref: React.Ref<any>
60
+ ref: React.Ref<FlashListRef<T>>
58
61
  ) => {
59
62
  // Destructure props and initialize refs
60
63
  const {
@@ -75,8 +78,6 @@ const RecyclerViewComponent = <T,>(
75
78
  ListFooterComponentStyle,
76
79
  ItemSeparatorComponent,
77
80
  renderScrollComponent,
78
- onScroll,
79
- disableRecycling,
80
81
  style,
81
82
  stickyHeaderIndices,
82
83
  maintainVisibleContentPosition,
@@ -84,6 +85,10 @@ const RecyclerViewComponent = <T,>(
84
85
  ...rest
85
86
  } = props;
86
87
 
88
+ const [renderTimeTracker] = useState(() => new RenderTimeTracker());
89
+
90
+ renderTimeTracker.startTracking();
91
+
87
92
  // Core refs for managing scroll view, internal view, and child container
88
93
  const scrollViewRef = useRef<CompatScroller>(null);
89
94
  const internalViewRef = useRef<CompatView>(null);
@@ -109,15 +114,19 @@ const RecyclerViewComponent = <T,>(
109
114
  );
110
115
 
111
116
  // Initialize core RecyclerView manager and content offset management
112
- const { recyclerViewManager } = useRecyclerViewManager(props);
113
- const { applyContentOffset, applyInitialScrollIndex } =
114
- useRecyclerViewController(
115
- recyclerViewManager,
116
- ref,
117
- scrollViewRef,
118
- scrollAnchorRef,
119
- props
120
- );
117
+ const { recyclerViewManager, velocityTracker } =
118
+ useRecyclerViewManager(props);
119
+ const {
120
+ applyOffsetCorrection,
121
+ computeFirstVisibleIndexForOffsetCorrection,
122
+ applyInitialScrollIndex,
123
+ handlerMethods,
124
+ } = useRecyclerViewController(
125
+ recyclerViewManager,
126
+ ref,
127
+ scrollViewRef,
128
+ scrollAnchorRef
129
+ );
121
130
 
122
131
  // Initialize view holder collection ref
123
132
  const viewHolderCollectionRef = useRef<ViewHolderCollectionRef>(null);
@@ -126,11 +135,7 @@ const RecyclerViewComponent = <T,>(
126
135
  useOnListLoad(recyclerViewManager, onLoad);
127
136
 
128
137
  // Hook to detect when scrolling reaches list bounds
129
- const { checkBounds } = useBoundDetection(
130
- recyclerViewManager,
131
- props,
132
- scrollViewRef
133
- );
138
+ const { checkBounds } = useBoundDetection(recyclerViewManager, scrollViewRef);
134
139
 
135
140
  const isHorizontalRTL = I18nManager.isRTL && horizontal;
136
141
 
@@ -176,6 +181,7 @@ const RecyclerViewComponent = <T,>(
176
181
  * Effect to handle layout updates for list items
177
182
  * This ensures proper positioning and recycling of items
178
183
  */
184
+ // eslint-disable-next-line react-hooks/exhaustive-deps
179
185
  useLayoutEffect(() => {
180
186
  if (pendingChildIds.size > 0) {
181
187
  return;
@@ -183,7 +189,7 @@ const RecyclerViewComponent = <T,>(
183
189
  const layoutInfo = Array.from(refHolder, ([index, viewHolderRef]) => {
184
190
  const layout = measureItemLayout(
185
191
  viewHolderRef.current!,
186
- recyclerViewManager.getLayout(index)
192
+ recyclerViewManager.tryGetLayout(index)
187
193
  );
188
194
 
189
195
  // comapre height with stored layout
@@ -209,7 +215,7 @@ const RecyclerViewComponent = <T,>(
209
215
  setRenderId((prev) => prev + 1);
210
216
  } else {
211
217
  viewHolderCollectionRef.current?.commitLayout();
212
- applyContentOffset();
218
+ applyOffsetCorrection();
213
219
  }
214
220
  });
215
221
 
@@ -221,23 +227,11 @@ const RecyclerViewComponent = <T,>(
221
227
  if (recyclerViewManager.ignoreScrollEvents) {
222
228
  return;
223
229
  }
224
- let velocity = event.nativeEvent.velocity;
225
230
 
226
231
  let scrollOffset = horizontal
227
232
  ? event.nativeEvent.contentOffset.x
228
233
  : event.nativeEvent.contentOffset.y;
229
234
 
230
- if (!velocity) {
231
- const velocityValue =
232
- recyclerViewManager.getAbsoluteLastScrollOffset() < scrollOffset
233
- ? 1
234
- : -1;
235
- velocity = {
236
- x: horizontal ? velocityValue : 0,
237
- y: horizontal ? 0 : velocityValue,
238
- };
239
- }
240
-
241
235
  // Handle RTL (Right-to-Left) layout adjustments
242
236
  if (isHorizontalRTL) {
243
237
  scrollOffset = adjustOffsetForRTL(
@@ -245,18 +239,31 @@ const RecyclerViewComponent = <T,>(
245
239
  event.nativeEvent.contentSize.width,
246
240
  event.nativeEvent.layoutMeasurement.width
247
241
  );
248
- if (velocity) {
249
- velocity = {
250
- x: -velocity.x,
251
- y: velocity.y,
252
- };
253
- }
254
- }
255
- // Update scroll position and trigger re-render if needed
256
- if (recyclerViewManager.updateScrollOffset(scrollOffset, velocity)) {
257
- setRenderId((prev) => prev + 1);
258
242
  }
259
243
 
244
+ velocityTracker.computeVelocity(
245
+ scrollOffset,
246
+ recyclerViewManager.getAbsoluteLastScrollOffset(),
247
+ Boolean(horizontal),
248
+ (velocity, isMomentumEnd) => {
249
+ if (recyclerViewManager.ignoreScrollEvents) {
250
+ return;
251
+ }
252
+
253
+ if (isMomentumEnd) {
254
+ computeFirstVisibleIndexForOffsetCorrection();
255
+ if (!recyclerViewManager.isOffsetProjectionEnabled) {
256
+ return;
257
+ }
258
+ recyclerViewManager.resetVelocityCompute();
259
+ }
260
+ // Update scroll position and trigger re-render if needed
261
+ if (recyclerViewManager.updateScrollOffset(scrollOffset, velocity)) {
262
+ setRenderId((prev) => prev + 1);
263
+ }
264
+ }
265
+ );
266
+
260
267
  // Update sticky headers and check bounds
261
268
  stickyHeaderRef.current?.reportScrollEvent(event.nativeEvent);
262
269
  checkBounds();
@@ -266,22 +273,41 @@ const RecyclerViewComponent = <T,>(
266
273
  recyclerViewManager.computeItemViewability();
267
274
 
268
275
  // Call user-provided onScroll handler
269
- onScroll?.(event);
276
+ recyclerViewManager.props.onScroll?.(event);
270
277
  },
271
- [horizontal, isHorizontalRTL, recyclerViewManager]
278
+ [
279
+ checkBounds,
280
+ computeFirstVisibleIndexForOffsetCorrection,
281
+ horizontal,
282
+ isHorizontalRTL,
283
+ recyclerViewManager,
284
+ velocityTracker,
285
+ ]
272
286
  );
273
287
 
288
+ const parentRecyclerViewContext = useRecyclerViewContext();
289
+ const recyclerViewId = useId();
290
+
274
291
  // Create context for child components
275
- const recyclerViewContext: RecyclerViewContext = useMemo(() => {
292
+ const recyclerViewContext: RecyclerViewContext<T> = useMemo(() => {
276
293
  return {
277
294
  layout: () => {
278
295
  setLayoutTreeId((prev) => prev + 1);
279
296
  },
280
297
  getRef: () => {
281
- return ref;
298
+ if (recyclerViewManager.isDisposed) {
299
+ return null;
300
+ }
301
+ return handlerMethods;
302
+ },
303
+ getParentRef: () => {
304
+ return parentRecyclerViewContext?.getRef() ?? null;
305
+ },
306
+ getParentScrollViewRef: () => {
307
+ return parentRecyclerViewContext?.getScrollViewRef() ?? null;
282
308
  },
283
309
  getScrollViewRef: () => {
284
- return scrollViewRef;
310
+ return scrollViewRef.current;
285
311
  },
286
312
  markChildLayoutAsPending: (id: string) => {
287
313
  pendingChildIds.add(id);
@@ -293,10 +319,13 @@ const RecyclerViewComponent = <T,>(
293
319
  }
294
320
  },
295
321
  };
296
- }, [setLayoutTreeId]);
297
-
298
- const parentRecyclerViewContext = useRecyclerViewContext();
299
- const recyclerViewId = useId();
322
+ }, [
323
+ handlerMethods,
324
+ parentRecyclerViewContext,
325
+ pendingChildIds,
326
+ recyclerViewManager.isDisposed,
327
+ setLayoutTreeId,
328
+ ]);
300
329
 
301
330
  /**
302
331
  * Validates that item dimensions match the expected layout
@@ -328,7 +357,7 @@ const RecyclerViewComponent = <T,>(
328
357
  recyclerViewContext.layout();
329
358
  }
330
359
  },
331
- [recyclerViewManager]
360
+ [recyclerViewContext, recyclerViewManager]
332
361
  );
333
362
 
334
363
  // Get secondary props and components
@@ -368,7 +397,14 @@ const RecyclerViewComponent = <T,>(
368
397
  );
369
398
  }
370
399
  return null;
371
- }, [data, stickyHeaderIndices, renderItem, extraData]);
400
+ }, [
401
+ data,
402
+ stickyHeaderIndices,
403
+ renderItem,
404
+ scrollY,
405
+ recyclerViewManager,
406
+ extraData,
407
+ ]);
372
408
 
373
409
  // Set up scroll event handling with animation support for sticky headers
374
410
  const animatedEvent = useMemo(() => {
@@ -379,31 +415,24 @@ const RecyclerViewComponent = <T,>(
379
415
  );
380
416
  }
381
417
  return onScrollHandler;
382
- }, [onScrollHandler, stickyHeaders]);
418
+ }, [onScrollHandler, scrollY, stickyHeaders]);
419
+
420
+ const shouldMaintainVisibleContentPosition =
421
+ recyclerViewManager.shouldMaintainVisibleContentPosition();
383
422
 
384
423
  const maintainVisibleContentPositionInternal = useMemo(() => {
385
- if (maintainVisibleContentPosition?.disabled || horizontal) {
386
- return undefined;
387
- } else {
424
+ if (shouldMaintainVisibleContentPosition) {
388
425
  return {
389
426
  ...maintainVisibleContentPosition,
390
427
  minIndexForVisible: 0,
391
428
  };
392
429
  }
393
- }, [maintainVisibleContentPosition]);
430
+ return undefined;
431
+ }, [maintainVisibleContentPosition, shouldMaintainVisibleContentPosition]);
394
432
 
395
433
  const shouldRenderFromBottom =
396
- maintainVisibleContentPositionInternal?.startRenderingFromBottom ?? false;
397
-
398
- // Calculate minimum height adjustment for bottom rendering
399
- const adjustmentMinHeight = recyclerViewManager.hasLayout()
400
- ? Math.max(
401
- 0,
402
- recyclerViewManager.getWindowSize().height -
403
- recyclerViewManager.getChildContainerDimensions().height -
404
- recyclerViewManager.firstItemOffset
405
- )
406
- : 0;
434
+ recyclerViewManager.getDataLength() > 0 &&
435
+ (maintainVisibleContentPosition?.startRenderingFromBottom ?? false);
407
436
 
408
437
  // Create view for measuring bounded size
409
438
  const viewToMeasureBoundedSize = useMemo(() => {
@@ -412,12 +441,23 @@ const RecyclerViewComponent = <T,>(
412
441
  style={{
413
442
  height: horizontal ? undefined : 0,
414
443
  width: horizontal ? 0 : undefined,
415
- minHeight: shouldRenderFromBottom ? adjustmentMinHeight : undefined,
416
444
  }}
417
445
  ref={firstChildViewRef}
418
446
  />
419
447
  );
420
- }, [horizontal, shouldRenderFromBottom, adjustmentMinHeight]);
448
+ }, [horizontal]);
449
+
450
+ const scrollAnchor = useMemo(() => {
451
+ if (shouldMaintainVisibleContentPosition) {
452
+ return (
453
+ <ScrollAnchor
454
+ horizontal={Boolean(horizontal)}
455
+ scrollAnchorRef={scrollAnchorRef}
456
+ />
457
+ );
458
+ }
459
+ return null;
460
+ }, [horizontal, shouldMaintainVisibleContentPosition]);
421
461
 
422
462
  // console.log("render", recyclerViewManager.getRenderStack());
423
463
 
@@ -465,9 +505,7 @@ const RecyclerViewComponent = <T,>(
465
505
  {...overrideProps}
466
506
  >
467
507
  {/* Scroll anchor for maintaining content position */}
468
- {maintainVisibleContentPositionInternal && (
469
- <ScrollAnchor scrollAnchorRef={scrollAnchorRef} />
470
- )}
508
+ {scrollAnchor}
471
509
  {isHorizontalRTL && viewToMeasureBoundedSize}
472
510
  {renderHeader}
473
511
  {!isHorizontalRTL && viewToMeasureBoundedSize}
@@ -478,6 +516,25 @@ const RecyclerViewComponent = <T,>(
478
516
  horizontal={horizontal}
479
517
  renderStack={recyclerViewManager.getRenderStack()}
480
518
  getLayout={(index) => recyclerViewManager.getLayout(index)}
519
+ getAdjustmentMargin={() => {
520
+ if (!shouldRenderFromBottom || !recyclerViewManager.hasLayout()) {
521
+ return 0;
522
+ }
523
+
524
+ const windowSize = horizontal
525
+ ? recyclerViewManager.getWindowSize().width
526
+ : recyclerViewManager.getWindowSize().height;
527
+ const childContainerSize = horizontal
528
+ ? recyclerViewManager.getChildContainerDimensions().width
529
+ : recyclerViewManager.getChildContainerDimensions().height;
530
+
531
+ return Math.max(
532
+ 0,
533
+ windowSize -
534
+ childContainerSize -
535
+ recyclerViewManager.firstItemOffset
536
+ );
537
+ }}
481
538
  refHolder={refHolder}
482
539
  onSizeChanged={validateItemSize}
483
540
  renderItem={renderItem}
@@ -490,10 +547,14 @@ const RecyclerViewComponent = <T,>(
490
547
  onCommitLayoutEffect?.();
491
548
  }}
492
549
  onCommitEffect={() => {
550
+ renderTimeTracker.markRenderComplete();
551
+ recyclerViewManager.updateAverageRenderTime(
552
+ renderTimeTracker.getAverageRenderTime()
553
+ );
493
554
  applyInitialScrollIndex();
494
555
  checkBounds();
495
556
  recyclerViewManager.computeItemViewability();
496
- recyclerViewManager.disableRecycling = Boolean(disableRecycling);
557
+ recyclerViewManager.animationOptimizationsEnabled = false;
497
558
  }}
498
559
  CellRendererComponent={CellRendererComponent}
499
560
  ItemSeparatorComponent={ItemSeparatorComponent}
@@ -512,9 +573,12 @@ const RecyclerViewComponent = <T,>(
512
573
  );
513
574
  };
514
575
 
576
+ // Set displayName for the inner component
577
+ RecyclerViewComponent.displayName = "FlashList";
578
+
515
579
  // Type definition for the RecyclerView component
516
580
  type RecyclerViewType = <T>(
517
- props: RecyclerViewProps<T> & { ref?: React.Ref<any> }
581
+ props: RecyclerViewProps<T> & { ref?: React.Ref<FlashListRef<T>> }
518
582
  ) => React.JSX.Element;
519
583
 
520
584
  // Create and export the memoized, forwarded ref component
@@ -1,20 +1,65 @@
1
1
  import { createContext, useContext } from "react";
2
2
 
3
+ import { FlashListRef } from "../FlashListRef";
4
+
3
5
  import { CompatScroller } from "./components/CompatScroller";
4
6
 
5
- export interface RecyclerViewContext {
6
- layout: () => void;
7
- getRef: () => React.Ref<any>;
8
- getScrollViewRef: () => React.RefObject<CompatScroller | null>;
7
+ export interface RecyclerViewContext<T> extends FlashListContext<T> {
8
+ /**
9
+ * Mark the children FlashLists as pending for layout
10
+ * This will force parent FlashList to wait for children to be laid out before
11
+ * rendering the next batch of items.
12
+ * @param id - The id of the child FlashList
13
+ * @returns void
14
+ */
9
15
  markChildLayoutAsPending: (id: string) => void;
16
+ /**
17
+ * Unmark the child FlashList as pending for layout
18
+ * This will allow parent FlashList to render the next batch of items.
19
+ * @param id - The id of the child FlashList
20
+ * @returns void
21
+ */
10
22
  unmarkChildLayoutAsPending: (id: string) => void;
11
23
  }
12
24
 
25
+ export interface FlashListContext<T> {
26
+ /**
27
+ * Layout the current FlashList
28
+ * @returns void
29
+ */
30
+ layout: () => void;
31
+ /**
32
+ * Get the ref for the current FlashList
33
+ */
34
+ getRef: () => FlashListRef<T> | null;
35
+ /**
36
+ * Get the ref for the parent FlashList if present
37
+ */
38
+ getParentRef: () => FlashListRef<unknown> | null;
39
+ /**
40
+ * Get the scrollView ref for the current FlashList if present
41
+ */
42
+ getScrollViewRef: () => CompatScroller | null;
43
+ /**
44
+ * Get the scrollView ref for the parent FlashList if present
45
+ */
46
+ getParentScrollViewRef: () => CompatScroller | null;
47
+ }
48
+
13
49
  const RecyclerViewContextInstance = createContext<
14
- RecyclerViewContext | undefined
50
+ RecyclerViewContext<unknown> | undefined
15
51
  >(undefined);
16
52
 
17
53
  export const RecyclerViewContextProvider = RecyclerViewContextInstance.Provider;
18
- export function useRecyclerViewContext() {
19
- return useContext(RecyclerViewContextInstance);
54
+ export function useRecyclerViewContext<T>():
55
+ | RecyclerViewContext<T>
56
+ | undefined {
57
+ return useContext(RecyclerViewContextInstance) as
58
+ | RecyclerViewContext<T>
59
+ | undefined;
60
+ }
61
+ export function useFlashListContext<T>(): FlashListContext<T> | undefined {
62
+ return useContext(RecyclerViewContextInstance) as
63
+ | FlashListContext<T>
64
+ | undefined;
20
65
  }