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

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 (329) hide show
  1. package/README.md +67 -96
  2. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/BlankAreaEvent.kt +2 -2
  3. package/dist/AnimatedFlashList.d.ts +0 -1
  4. package/dist/AnimatedFlashList.d.ts.map +1 -1
  5. package/dist/AnimatedFlashList.js +3 -3
  6. package/dist/AnimatedFlashList.js.map +1 -1
  7. package/dist/FlashList.d.ts +9 -0
  8. package/dist/FlashList.d.ts.map +1 -1
  9. package/dist/FlashList.js +22 -3
  10. package/dist/FlashList.js.map +1 -1
  11. package/dist/FlashListProps.d.ts +33 -13
  12. package/dist/FlashListProps.d.ts.map +1 -1
  13. package/dist/FlashListProps.js.map +1 -1
  14. package/dist/FlashListRef.d.ts +305 -0
  15. package/dist/FlashListRef.d.ts.map +1 -0
  16. package/dist/FlashListRef.js +3 -0
  17. package/dist/FlashListRef.js.map +1 -0
  18. package/dist/GridLayoutProviderWithProps.js +1 -2
  19. package/dist/GridLayoutProviderWithProps.js.map +1 -1
  20. package/dist/MasonryFlashList.d.ts +2 -2
  21. package/dist/MasonryFlashList.d.ts.map +1 -1
  22. package/dist/MasonryFlashList.js.map +1 -1
  23. package/dist/PureComponentWrapper.js +1 -1
  24. package/dist/PureComponentWrapper.js.map +1 -1
  25. package/dist/__tests__/AverageWindow.test.js.map +1 -1
  26. package/dist/__tests__/ConsecutiveNumbers.test.d.ts +2 -0
  27. package/dist/__tests__/ConsecutiveNumbers.test.d.ts.map +1 -0
  28. package/dist/__tests__/ConsecutiveNumbers.test.js +224 -0
  29. package/dist/__tests__/ConsecutiveNumbers.test.js.map +1 -0
  30. package/dist/__tests__/FlashList.test.js.map +1 -1
  31. package/dist/__tests__/GridLayoutManager.test.d.ts +2 -0
  32. package/dist/__tests__/GridLayoutManager.test.d.ts.map +1 -0
  33. package/dist/__tests__/GridLayoutManager.test.js +69 -0
  34. package/dist/__tests__/GridLayoutManager.test.js.map +1 -0
  35. package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -1
  36. package/dist/__tests__/LayoutCommitObserver.test.d.ts +2 -0
  37. package/dist/__tests__/LayoutCommitObserver.test.d.ts.map +1 -0
  38. package/dist/__tests__/LayoutCommitObserver.test.js +35 -0
  39. package/dist/__tests__/LayoutCommitObserver.test.js.map +1 -0
  40. package/dist/__tests__/LinearLayoutManager.test.d.ts +2 -0
  41. package/dist/__tests__/LinearLayoutManager.test.d.ts.map +1 -0
  42. package/dist/__tests__/LinearLayoutManager.test.js +140 -0
  43. package/dist/__tests__/LinearLayoutManager.test.js.map +1 -0
  44. package/dist/__tests__/MasonryFlashList.test.js.map +1 -1
  45. package/dist/__tests__/MasonryLayoutManager.test.d.ts +2 -0
  46. package/dist/__tests__/MasonryLayoutManager.test.d.ts.map +1 -0
  47. package/dist/__tests__/MasonryLayoutManager.test.js +148 -0
  48. package/dist/__tests__/MasonryLayoutManager.test.js.map +1 -0
  49. package/dist/__tests__/RecyclerView.test.d.ts +2 -0
  50. package/dist/__tests__/RecyclerView.test.d.ts.map +1 -0
  51. package/dist/__tests__/RecyclerView.test.js +103 -0
  52. package/dist/__tests__/RecyclerView.test.js.map +1 -0
  53. package/dist/__tests__/RenderStackManager.test.d.ts +2 -0
  54. package/dist/__tests__/RenderStackManager.test.d.ts.map +1 -0
  55. package/dist/__tests__/RenderStackManager.test.js +485 -0
  56. package/dist/__tests__/RenderStackManager.test.js.map +1 -0
  57. package/dist/__tests__/ViewabilityHelper.test.js.map +1 -1
  58. package/dist/__tests__/findVisibleIndex.test.d.ts +2 -0
  59. package/dist/__tests__/findVisibleIndex.test.d.ts.map +1 -0
  60. package/dist/__tests__/findVisibleIndex.test.js +259 -0
  61. package/dist/__tests__/findVisibleIndex.test.js.map +1 -0
  62. package/dist/__tests__/helpers/createLayoutManager.d.ts +34 -0
  63. package/dist/__tests__/helpers/createLayoutManager.d.ts.map +1 -0
  64. package/dist/__tests__/helpers/createLayoutManager.js +110 -0
  65. package/dist/__tests__/helpers/createLayoutManager.js.map +1 -0
  66. package/dist/__tests__/helpers/mountFlashList.d.ts +2 -2
  67. package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -1
  68. package/dist/__tests__/helpers/mountFlashList.js +2 -2
  69. package/dist/__tests__/helpers/mountFlashList.js.map +1 -1
  70. package/dist/__tests__/helpers/mountMasonryFlashList.d.ts +2 -2
  71. package/dist/__tests__/helpers/mountMasonryFlashList.d.ts.map +1 -1
  72. package/dist/__tests__/helpers/mountMasonryFlashList.js +2 -2
  73. package/dist/__tests__/helpers/mountMasonryFlashList.js.map +1 -1
  74. package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -1
  75. package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts +2 -0
  76. package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts.map +1 -0
  77. package/dist/__tests__/useUnmountAwareCallbacks.test.js +185 -0
  78. package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +1 -0
  79. package/dist/benchmark/AutoScrollHelper.js +2 -2
  80. package/dist/benchmark/AutoScrollHelper.js.map +1 -1
  81. package/dist/benchmark/JSFPSMonitor.js.map +1 -1
  82. package/dist/benchmark/roundToDecimalPlaces.js +1 -2
  83. package/dist/benchmark/roundToDecimalPlaces.js.map +1 -1
  84. package/dist/benchmark/useBenchmark.js +2 -28
  85. package/dist/benchmark/useBenchmark.js.map +1 -1
  86. package/dist/benchmark/useBlankAreaTracker.js +1 -2
  87. package/dist/benchmark/useBlankAreaTracker.js.map +1 -1
  88. package/dist/benchmark/useDataMultiplier.js +1 -2
  89. package/dist/benchmark/useDataMultiplier.js.map +1 -1
  90. package/dist/benchmark/useFlatListBenchmark.d.ts +0 -1
  91. package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -1
  92. package/dist/benchmark/useFlatListBenchmark.js +9 -9
  93. package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
  94. package/dist/enableNewCore.d.ts.map +1 -1
  95. package/dist/enableNewCore.js +4 -4
  96. package/dist/enableNewCore.js.map +1 -1
  97. package/dist/errors/CustomError.js.map +1 -1
  98. package/dist/index.d.ts +4 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +11 -3
  101. package/dist/index.js.map +1 -1
  102. package/dist/native/auto-layout/AutoLayoutView.d.ts +1 -1
  103. package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -1
  104. package/dist/native/auto-layout/AutoLayoutView.js +1 -1
  105. package/dist/native/auto-layout/AutoLayoutView.js.map +1 -1
  106. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -1
  107. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +1 -1
  108. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -1
  109. package/dist/native/config/PlatformHelper.android.d.ts +2 -0
  110. package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
  111. package/dist/native/config/PlatformHelper.android.js +2 -0
  112. package/dist/native/config/PlatformHelper.android.js.map +1 -1
  113. package/dist/native/config/PlatformHelper.d.ts +2 -0
  114. package/dist/native/config/PlatformHelper.d.ts.map +1 -1
  115. package/dist/native/config/PlatformHelper.ios.d.ts +2 -0
  116. package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
  117. package/dist/native/config/PlatformHelper.ios.js +2 -0
  118. package/dist/native/config/PlatformHelper.ios.js.map +1 -1
  119. package/dist/native/config/PlatformHelper.js +2 -0
  120. package/dist/native/config/PlatformHelper.js.map +1 -1
  121. package/dist/native/config/PlatformHelper.web.d.ts +2 -0
  122. package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
  123. package/dist/native/config/PlatformHelper.web.js +3 -1
  124. package/dist/native/config/PlatformHelper.web.js.map +1 -1
  125. package/dist/recyclerview/LayoutCommitObserver.d.ts +12 -0
  126. package/dist/recyclerview/LayoutCommitObserver.d.ts.map +1 -0
  127. package/dist/recyclerview/LayoutCommitObserver.js +62 -0
  128. package/dist/recyclerview/LayoutCommitObserver.js.map +1 -0
  129. package/dist/recyclerview/RecyclerView.d.ts +3 -2
  130. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  131. package/dist/recyclerview/RecyclerView.js +133 -69
  132. package/dist/recyclerview/RecyclerView.js.map +1 -1
  133. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +41 -7
  134. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
  135. package/dist/recyclerview/RecyclerViewContextProvider.js +6 -2
  136. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
  137. package/dist/recyclerview/RecyclerViewManager.d.ts +31 -7
  138. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
  139. package/dist/recyclerview/RecyclerViewManager.js +154 -117
  140. package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
  141. package/dist/recyclerview/RecyclerViewProps.d.ts +1 -1
  142. package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -1
  143. package/dist/recyclerview/RenderStackManager.d.ts +86 -0
  144. package/dist/recyclerview/RenderStackManager.d.ts.map +1 -0
  145. package/dist/recyclerview/RenderStackManager.js +343 -0
  146. package/dist/recyclerview/RenderStackManager.js.map +1 -0
  147. package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
  148. package/dist/recyclerview/ViewHolder.js +8 -6
  149. package/dist/recyclerview/ViewHolder.js.map +1 -1
  150. package/dist/recyclerview/ViewHolderCollection.d.ts +10 -4
  151. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
  152. package/dist/recyclerview/ViewHolderCollection.js +26 -10
  153. package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
  154. package/dist/recyclerview/components/ScrollAnchor.d.ts +2 -1
  155. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
  156. package/dist/recyclerview/components/ScrollAnchor.js +12 -9
  157. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
  158. package/dist/recyclerview/components/StickyHeaders.d.ts +2 -2
  159. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
  160. package/dist/recyclerview/components/StickyHeaders.js +44 -45
  161. package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
  162. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +1 -1
  163. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -1
  164. package/dist/recyclerview/helpers/ConsecutiveNumbers.js +2 -2
  165. package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -1
  166. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +48 -2
  167. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -1
  168. package/dist/recyclerview/helpers/EngagedIndicesTracker.js +89 -19
  169. package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -1
  170. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts +11 -0
  171. package/dist/recyclerview/helpers/RenderTimeTracker.d.ts.map +1 -0
  172. package/dist/recyclerview/helpers/RenderTimeTracker.js +42 -0
  173. package/dist/recyclerview/helpers/RenderTimeTracker.js.map +1 -0
  174. package/dist/recyclerview/helpers/VelocityTracker.d.ts +29 -0
  175. package/dist/recyclerview/helpers/VelocityTracker.d.ts.map +1 -0
  176. package/dist/recyclerview/helpers/VelocityTracker.js +70 -0
  177. package/dist/recyclerview/helpers/VelocityTracker.js.map +1 -0
  178. package/dist/recyclerview/hooks/useBoundDetection.d.ts +1 -3
  179. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
  180. package/dist/recyclerview/hooks/useBoundDetection.js +60 -28
  181. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
  182. package/dist/recyclerview/hooks/useLayoutState.d.ts +3 -1
  183. package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -1
  184. package/dist/recyclerview/hooks/useLayoutState.js +6 -5
  185. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
  186. package/dist/recyclerview/hooks/useMappingHelper.d.ts +9 -0
  187. package/dist/recyclerview/hooks/useMappingHelper.d.ts.map +1 -0
  188. package/dist/recyclerview/hooks/useMappingHelper.js +19 -0
  189. package/dist/recyclerview/hooks/useMappingHelper.js.map +1 -0
  190. package/dist/recyclerview/hooks/useOnLoad.d.ts +2 -2
  191. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
  192. package/dist/recyclerview/hooks/useOnLoad.js +9 -10
  193. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
  194. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +5 -49
  195. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
  196. package/dist/recyclerview/hooks/useRecyclerViewController.js +343 -191
  197. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
  198. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +2 -0
  199. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
  200. package/dist/recyclerview/hooks/useRecyclerViewManager.js +11 -1
  201. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
  202. package/dist/recyclerview/hooks/useRecyclingState.d.ts +4 -2
  203. package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -1
  204. package/dist/recyclerview/hooks/useRecyclingState.js +3 -4
  205. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
  206. package/dist/recyclerview/hooks/useSecondaryProps.d.ts +1 -1
  207. package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -1
  208. package/dist/recyclerview/hooks/useSecondaryProps.js +15 -12
  209. package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -1
  210. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +15 -0
  211. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -0
  212. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +63 -0
  213. package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -0
  214. package/dist/recyclerview/hooks/useUnmountFlag.d.ts +0 -1
  215. package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -1
  216. package/dist/recyclerview/hooks/useUnmountFlag.js +1 -0
  217. package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -1
  218. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +18 -4
  219. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
  220. package/dist/recyclerview/layout-managers/GridLayoutManager.js +61 -25
  221. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
  222. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +36 -21
  223. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
  224. package/dist/recyclerview/layout-managers/LayoutManager.js +96 -28
  225. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
  226. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +1 -2
  227. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -1
  228. package/dist/recyclerview/layout-managers/LinearLayoutManager.js +3 -3
  229. package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -1
  230. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +9 -1
  231. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -1
  232. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +30 -16
  233. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
  234. package/dist/recyclerview/utils/adjustOffsetForRTL.js +1 -2
  235. package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -1
  236. package/dist/recyclerview/utils/componentUtils.d.ts +1 -1
  237. package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -1
  238. package/dist/recyclerview/utils/componentUtils.js.map +1 -1
  239. package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -1
  240. package/dist/recyclerview/utils/findVisibleIndex.js +3 -5
  241. package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -1
  242. package/dist/recyclerview/utils/measureLayout.d.ts +24 -28
  243. package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -1
  244. package/dist/recyclerview/utils/measureLayout.js +36 -6
  245. package/dist/recyclerview/utils/measureLayout.js.map +1 -1
  246. package/dist/recyclerview/utils/measureLayout.web.d.ts +29 -0
  247. package/dist/recyclerview/utils/measureLayout.web.d.ts.map +1 -0
  248. package/dist/recyclerview/utils/measureLayout.web.js +87 -0
  249. package/dist/recyclerview/utils/measureLayout.web.js.map +1 -0
  250. package/dist/specs/AutoLayoutNativeComponent.d.ts +1 -2
  251. package/dist/specs/AutoLayoutNativeComponent.d.ts.map +1 -1
  252. package/dist/specs/CellContainerNativeComponent.d.ts +0 -1
  253. package/dist/specs/CellContainerNativeComponent.d.ts.map +1 -1
  254. package/dist/tsconfig.tsbuildinfo +1 -1
  255. package/dist/utils/AverageWindow.js.map +1 -1
  256. package/dist/utils/ContentContainerUtils.d.ts.map +1 -1
  257. package/dist/utils/ContentContainerUtils.js.map +1 -1
  258. package/dist/viewability/ViewToken.d.ts +2 -2
  259. package/dist/viewability/ViewToken.d.ts.map +1 -1
  260. package/dist/viewability/ViewabilityHelper.js +1 -1
  261. package/dist/viewability/ViewabilityHelper.js.map +1 -1
  262. package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
  263. package/dist/viewability/ViewabilityManager.js +11 -5
  264. package/dist/viewability/ViewabilityManager.js.map +1 -1
  265. package/jestSetup.js +30 -11
  266. package/package.json +4 -3
  267. package/src/AnimatedFlashList.ts +3 -2
  268. package/src/FlashList.tsx +25 -1
  269. package/src/FlashListProps.ts +42 -11
  270. package/src/FlashListRef.ts +320 -0
  271. package/src/MasonryFlashList.tsx +2 -2
  272. package/src/__tests__/ConsecutiveNumbers.test.ts +232 -0
  273. package/src/__tests__/GridLayoutManager.test.ts +113 -0
  274. package/src/__tests__/LayoutCommitObserver.test.tsx +60 -0
  275. package/src/__tests__/LinearLayoutManager.test.ts +227 -0
  276. package/src/__tests__/MasonryLayoutManager.test.ts +202 -0
  277. package/src/__tests__/RecyclerView.test.tsx +144 -0
  278. package/src/__tests__/RenderStackManager.test.ts +574 -0
  279. package/src/__tests__/findVisibleIndex.test.ts +369 -0
  280. package/src/__tests__/helpers/createLayoutManager.ts +141 -0
  281. package/src/__tests__/useUnmountAwareCallbacks.test.tsx +285 -0
  282. package/src/benchmark/useBenchmark.ts +0 -37
  283. package/src/benchmark/useFlatListBenchmark.ts +2 -2
  284. package/src/enableNewCore.ts +3 -1
  285. package/src/index.ts +14 -3
  286. package/src/native/config/PlatformHelper.android.ts +2 -0
  287. package/src/native/config/PlatformHelper.ios.ts +2 -0
  288. package/src/native/config/PlatformHelper.ts +2 -0
  289. package/src/native/config/PlatformHelper.web.ts +3 -1
  290. package/src/recyclerview/LayoutCommitObserver.tsx +74 -0
  291. package/src/recyclerview/RecyclerView.tsx +178 -89
  292. package/src/recyclerview/RecyclerViewContextProvider.ts +53 -7
  293. package/src/recyclerview/RecyclerViewManager.ts +176 -97
  294. package/src/recyclerview/RecyclerViewProps.ts +2 -1
  295. package/src/recyclerview/RenderStackManager.ts +317 -0
  296. package/src/recyclerview/ViewHolder.tsx +13 -6
  297. package/src/recyclerview/ViewHolderCollection.tsx +45 -16
  298. package/src/recyclerview/components/ScrollAnchor.tsx +24 -11
  299. package/src/recyclerview/components/StickyHeaders.tsx +70 -58
  300. package/src/recyclerview/helpers/ConsecutiveNumbers.ts +2 -2
  301. package/src/recyclerview/helpers/EngagedIndicesTracker.ts +135 -25
  302. package/src/recyclerview/helpers/RenderTimeTracker.ts +42 -0
  303. package/src/recyclerview/helpers/VelocityTracker.ts +77 -0
  304. package/src/recyclerview/hooks/useBoundDetection.ts +74 -25
  305. package/src/recyclerview/hooks/useLayoutState.ts +15 -6
  306. package/src/recyclerview/hooks/useMappingHelper.ts +20 -0
  307. package/src/recyclerview/hooks/useOnLoad.ts +11 -10
  308. package/src/recyclerview/hooks/useRecyclerViewController.tsx +380 -241
  309. package/src/recyclerview/hooks/useRecyclerViewManager.ts +13 -1
  310. package/src/recyclerview/hooks/useRecyclingState.ts +11 -7
  311. package/src/recyclerview/hooks/useSecondaryProps.tsx +12 -7
  312. package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +73 -0
  313. package/src/recyclerview/hooks/useUnmountFlag.ts +1 -0
  314. package/src/recyclerview/layout-managers/GridLayoutManager.ts +68 -27
  315. package/src/recyclerview/layout-managers/LayoutManager.ts +116 -42
  316. package/src/recyclerview/layout-managers/LinearLayoutManager.ts +12 -8
  317. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +34 -13
  318. package/src/recyclerview/utils/componentUtils.ts +1 -1
  319. package/src/recyclerview/utils/findVisibleIndex.ts +1 -2
  320. package/src/recyclerview/utils/measureLayout.ts +41 -2
  321. package/src/recyclerview/utils/measureLayout.web.ts +102 -0
  322. package/src/viewability/ViewToken.ts +2 -2
  323. package/src/viewability/ViewabilityHelper.ts +1 -1
  324. package/src/viewability/ViewabilityManager.ts +16 -9
  325. package/dist/recyclerview/RecycleKeyManager.d.ts +0 -82
  326. package/dist/recyclerview/RecycleKeyManager.d.ts.map +0 -1
  327. package/dist/recyclerview/RecycleKeyManager.js +0 -135
  328. package/dist/recyclerview/RecycleKeyManager.js.map +0 -1
  329. package/src/recyclerview/RecycleKeyManager.ts +0 -185
@@ -7,64 +7,23 @@ import {
7
7
  useState,
8
8
  } from "react";
9
9
  import { I18nManager } from "react-native";
10
- import { RecyclerViewProps } from "../RecyclerViewProps";
10
+
11
+ import {
12
+ ScrollToOffsetParams,
13
+ ScrollToIndexParams,
14
+ ScrollToItemParams,
15
+ ScrollToEdgeParams,
16
+ FlashListRef,
17
+ } from "../../FlashListRef";
11
18
  import { CompatScroller } from "../components/CompatScroller";
12
19
  import { RecyclerViewManager } from "../RecyclerViewManager";
13
20
  import { adjustOffsetForRTL } from "../utils/adjustOffsetForRTL";
14
- import { useUnmountFlag } from "./useUnmountFlag";
15
21
  import { RVLayout } from "../layout-managers/LayoutManager";
16
22
  import { ScrollAnchorRef } from "../components/ScrollAnchor";
23
+ import { PlatformConfig } from "../../native/config/PlatformHelper";
17
24
 
18
- /**
19
- * Parameters for scrolling to a specific position in the list.
20
- * Extends ScrollToEdgeParams to include view positioning options.
21
- */
22
- export interface ScrollToParams extends ScrollToEdgeParams {
23
- /** Position of the target item relative to the viewport (0 = top, 0.5 = center, 1 = bottom) */
24
- viewPosition?: number;
25
- /** Additional offset to apply after viewPosition calculation */
26
- viewOffset?: number;
27
- }
28
-
29
- /**
30
- * Parameters for scrolling to a specific offset in the list.
31
- * Used when you want to scroll to an exact pixel position.
32
- */
33
- export interface ScrollToOffsetParams extends ScrollToParams {
34
- /** The pixel offset to scroll to */
35
- offset: number;
36
- /**
37
- * If true, the first item offset will not be added to the offset calculation.
38
- * First offset represents header size or top padding.
39
- */
40
- skipFirstItemOffset?: boolean;
41
- }
42
-
43
- /**
44
- * Parameters for scrolling to a specific index in the list.
45
- * Used when you want to scroll to a specific item by its position in the data array.
46
- */
47
- export interface ScrollToIndexParams extends ScrollToParams {
48
- /** The index of the item to scroll to */
49
- index: number;
50
- }
51
-
52
- /**
53
- * Parameters for scrolling to a specific item in the list.
54
- * Used when you want to scroll to a specific item by its data value.
55
- */
56
- export interface ScrollToItemParams<T> extends ScrollToParams {
57
- /** The item to scroll to */
58
- item: T;
59
- }
60
-
61
- /**
62
- * Base parameters for scrolling to the edges of the list.
63
- */
64
- export interface ScrollToEdgeParams {
65
- /** Whether the scroll should be animated */
66
- animated?: boolean;
67
- }
25
+ import { useUnmountFlag } from "./useUnmountFlag";
26
+ import { useUnmountAwareTimeout } from "./useUnmountAwareCallbacks";
68
27
 
69
28
  /**
70
29
  * Comprehensive hook that manages RecyclerView scrolling behavior and provides
@@ -84,138 +43,180 @@ export interface ScrollToEdgeParams {
84
43
  */
85
44
  export function useRecyclerViewController<T>(
86
45
  recyclerViewManager: RecyclerViewManager<T>,
87
- ref: React.Ref<any>,
46
+ ref: React.Ref<FlashListRef<T>>,
88
47
  scrollViewRef: RefObject<CompatScroller>,
89
- scrollAnchorRef: React.RefObject<ScrollAnchorRef>,
90
- props: RecyclerViewProps<T>
48
+ scrollAnchorRef: React.RefObject<ScrollAnchorRef>
91
49
  ) {
92
- const { horizontal, data } = props;
93
50
  const isUnmounted = useUnmountFlag();
94
51
  const [_, setRenderId] = useState(0);
95
- const pauseAdjustRef = useRef(false);
52
+ const pauseOffsetCorrection = useRef(false);
96
53
  const initialScrollCompletedRef = useRef(false);
54
+ const lastDataLengthRef = useRef(recyclerViewManager.getDataLength());
55
+ const { setTimeout } = useUnmountAwareTimeout();
97
56
 
98
57
  // Track the first visible item for maintaining scroll position
99
58
  const firstVisibleItemKey = useRef<string | undefined>(undefined);
100
59
  const firstVisibleItemLayout = useRef<RVLayout | undefined>(undefined);
101
- const pendingScrollResolves = useRef<(() => void)[]>([]);
102
-
103
- const applyInitialScrollIndex = useCallback(() => {
104
- const initialScrollIndex =
105
- recyclerViewManager.getInitialScrollIndex() ?? -1;
106
- const dataLength = props.data?.length ?? 0;
107
- if (
108
- initialScrollIndex >= 0 &&
109
- initialScrollIndex < dataLength &&
110
- !initialScrollCompletedRef.current &&
111
- recyclerViewManager.getIsFirstLayoutComplete()
112
- ) {
113
- // Use setTimeout to ensure that we keep trying to scroll on first few renders
114
- setTimeout(() => {
115
- initialScrollCompletedRef.current = true;
116
- pauseAdjustRef.current = false;
117
- }, 100);
118
60
 
119
- pauseAdjustRef.current = true;
120
-
121
- const offset = horizontal
122
- ? recyclerViewManager.getLayout(initialScrollIndex).x
123
- : recyclerViewManager.getLayout(initialScrollIndex).y;
124
- handlerMethods.scrollToOffset({
125
- offset,
126
- animated: false,
127
- });
128
-
129
- setTimeout(() => {
130
- handlerMethods.scrollToOffset({
131
- offset,
132
- animated: false,
133
- });
134
- }, 0);
135
- }
136
- }, [recyclerViewManager, props.data]);
61
+ // Queue to store callbacks that should be executed after scroll offset updates
62
+ const pendingScrollCallbacks = useRef<(() => void)[]>([]);
137
63
 
138
64
  // Handle initial scroll position when the list first loads
139
65
  // useOnLoad(recyclerViewManager, () => {
140
66
 
141
67
  // });
142
68
  /**
143
- * Updates the scroll offset and returns a Promise that resolves
144
- * when the update has been applied.
69
+ * Updates the scroll offset and calls the provided callback
70
+ * after the update has been applied and the component has re-rendered.
71
+ *
72
+ * @param offset - The new scroll offset to apply
73
+ * @param callback - Optional callback to execute after the update is applied
145
74
  */
146
- const updateScrollOffsetAsync = useCallback(
147
- async (offset: number): Promise<void> => {
148
- return new Promise((resolve) => {
149
- recyclerViewManager.updateScrollOffset(offset);
150
- // Add the resolve function to the queue
151
- pendingScrollResolves.current.push(resolve);
75
+ const updateScrollOffsetWithCallback = useCallback(
76
+ (offset: number, callback: () => void): void => {
77
+ // Attempt to update the scroll offset in the RecyclerViewManager
78
+ // This returns undefined if no update is needed
79
+ if (recyclerViewManager.updateScrollOffset(offset) !== undefined) {
80
+ // It will be executed after the next render
81
+ pendingScrollCallbacks.current.push(callback);
82
+ // Trigger a re-render to apply the scroll offset update
152
83
  setRenderId((prev) => prev + 1);
153
- });
84
+ } else {
85
+ // No update needed, execute callback immediately
86
+ callback();
87
+ }
154
88
  },
155
89
  [recyclerViewManager]
156
90
  );
157
91
 
92
+ const computeFirstVisibleIndexForOffsetCorrection = useCallback(() => {
93
+ if (
94
+ recyclerViewManager.getIsFirstLayoutComplete() &&
95
+ recyclerViewManager.hasStableDataKeys() &&
96
+ recyclerViewManager.getDataLength() > 0 &&
97
+ recyclerViewManager.shouldMaintainVisibleContentPosition()
98
+ ) {
99
+ // Update the tracked first visible item
100
+ const firstVisibleIndex = Math.max(
101
+ 0,
102
+ recyclerViewManager.computeVisibleIndices().startIndex
103
+ );
104
+ if (firstVisibleIndex !== undefined && firstVisibleIndex >= 0) {
105
+ firstVisibleItemKey.current =
106
+ recyclerViewManager.getDataKey(firstVisibleIndex);
107
+ firstVisibleItemLayout.current = {
108
+ ...recyclerViewManager.getLayout(firstVisibleIndex),
109
+ };
110
+ }
111
+ }
112
+ }, [recyclerViewManager]);
113
+
158
114
  /**
159
115
  * Maintains the visible content position when the list updates.
160
116
  * This is particularly useful for chat applications where we want to keep
161
117
  * the user's current view position when new messages are added.
162
118
  */
163
- const applyContentOffset = useCallback(async () => {
164
- // Resolve all pending scroll updates from previous calls
165
- const resolves = pendingScrollResolves.current;
166
- pendingScrollResolves.current = [];
167
- resolves.forEach((resolve) => resolve());
119
+ const applyOffsetCorrection = useCallback(() => {
120
+ const { horizontal, data } = recyclerViewManager.props;
121
+
122
+ // Execute all pending callbacks from previous scroll offset updates
123
+ // This ensures any scroll operations that were waiting for render are completed
124
+ const callbacks = pendingScrollCallbacks.current;
125
+ pendingScrollCallbacks.current = [];
126
+ callbacks.forEach((callback) => callback());
127
+
128
+ const currentDataLength = recyclerViewManager.getDataLength();
168
129
 
169
130
  if (
170
- !props.horizontal &&
171
131
  recyclerViewManager.getIsFirstLayoutComplete() &&
172
- props.keyExtractor &&
173
- props.maintainVisibleContentPosition?.disabled !== true
132
+ recyclerViewManager.hasStableDataKeys() &&
133
+ currentDataLength > 0 &&
134
+ recyclerViewManager.shouldMaintainVisibleContentPosition()
174
135
  ) {
136
+ const hasDataChanged = currentDataLength !== lastDataLengthRef.current;
175
137
  // If we have a tracked first visible item, maintain its position
176
138
  if (firstVisibleItemKey.current) {
177
- const currentIndexOfFirstVisibleItem = recyclerViewManager
178
- .getEngagedIndices()
179
- .findValue(
180
- (index) =>
181
- props.keyExtractor?.(props.data![index], index) ===
182
- firstVisibleItemKey.current
183
- );
184
-
185
- if (currentIndexOfFirstVisibleItem !== undefined) {
139
+ const currentIndexOfFirstVisibleItem =
140
+ recyclerViewManager
141
+ .getEngagedIndices()
142
+ .findValue(
143
+ (index) =>
144
+ recyclerViewManager.getDataKey(index) ===
145
+ firstVisibleItemKey.current
146
+ ) ??
147
+ (hasDataChanged
148
+ ? data?.findIndex(
149
+ (item, index) =>
150
+ recyclerViewManager.getDataKey(index) ===
151
+ firstVisibleItemKey.current
152
+ )
153
+ : undefined);
154
+
155
+ if (
156
+ currentIndexOfFirstVisibleItem !== undefined &&
157
+ currentIndexOfFirstVisibleItem >= 0
158
+ ) {
186
159
  // Calculate the difference in position and apply the offset
187
- const diff =
188
- recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).y -
189
- firstVisibleItemLayout.current!.y;
160
+ const diff = horizontal
161
+ ? recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).x -
162
+ firstVisibleItemLayout.current!.x
163
+ : recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).y -
164
+ firstVisibleItemLayout.current!.y;
190
165
  firstVisibleItemLayout.current = {
191
166
  ...recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem),
192
167
  };
193
- if (diff !== 0 && !pauseAdjustRef.current) {
194
- //console.log("diff", diff, firstVisibleItemKey.current);
195
- scrollAnchorRef.current?.scrollBy(diff);
168
+ if (
169
+ diff !== 0 &&
170
+ !pauseOffsetCorrection.current &&
171
+ !recyclerViewManager.animationOptimizationsEnabled
172
+ ) {
173
+ // console.log("diff", diff, firstVisibleItemKey.current);
174
+ if (PlatformConfig.supportsOffsetCorrection) {
175
+ // console.log("scrollBy", diff);
176
+ scrollAnchorRef.current?.scrollBy(diff);
177
+ } else {
178
+ const scrollToParams = horizontal
179
+ ? {
180
+ x: recyclerViewManager.getAbsoluteLastScrollOffset() + diff,
181
+ animated: false,
182
+ }
183
+ : {
184
+ y: recyclerViewManager.getAbsoluteLastScrollOffset() + diff,
185
+ animated: false,
186
+ };
187
+ scrollViewRef.current?.scrollTo(scrollToParams);
188
+ }
189
+ if (hasDataChanged) {
190
+ updateScrollOffsetWithCallback(
191
+ recyclerViewManager.getAbsoluteLastScrollOffset() + diff,
192
+ () => {}
193
+ );
194
+ recyclerViewManager.ignoreScrollEvents = true;
195
+ setTimeout(() => {
196
+ recyclerViewManager.ignoreScrollEvents = false;
197
+ }, 100);
198
+ }
196
199
  }
197
200
  }
198
201
  }
199
202
 
200
- // Update the tracked first visible item
201
- const firstVisibleIndex =
202
- recyclerViewManager.getVisibleIndices().startIndex;
203
- if (firstVisibleIndex !== undefined) {
204
- firstVisibleItemKey.current =
205
- props.keyExtractor?.(
206
- props.data![firstVisibleIndex],
207
- firstVisibleIndex
208
- ) ?? "0";
209
- firstVisibleItemLayout.current = {
210
- ...recyclerViewManager.getLayout(firstVisibleIndex),
211
- };
212
- }
203
+ computeFirstVisibleIndexForOffsetCorrection();
213
204
  }
214
- }, [props.data, props.keyExtractor, recyclerViewManager]);
215
-
216
- const handlerMethods = useMemo(() => {
205
+ lastDataLengthRef.current = recyclerViewManager.getDataLength();
206
+ }, [
207
+ recyclerViewManager,
208
+ scrollAnchorRef,
209
+ scrollViewRef,
210
+ setTimeout,
211
+ updateScrollOffsetWithCallback,
212
+ computeFirstVisibleIndexForOffsetCorrection,
213
+ ]);
214
+
215
+ const handlerMethods: FlashListRef<T> = useMemo(() => {
217
216
  return {
218
- props,
217
+ get props() {
218
+ return recyclerViewManager.props;
219
+ },
219
220
  /**
220
221
  * Scrolls the list to a specific offset position.
221
222
  * Handles RTL layouts and first item offset adjustments.
@@ -225,14 +226,20 @@ export function useRecyclerViewController<T>(
225
226
  animated,
226
227
  skipFirstItemOffset = true,
227
228
  }: ScrollToOffsetParams) => {
229
+ const { horizontal } = recyclerViewManager.props;
228
230
  if (scrollViewRef.current) {
229
231
  // Adjust offset for RTL layouts in horizontal mode
230
232
  if (I18nManager.isRTL && horizontal) {
231
- offset = adjustOffsetForRTL(
232
- offset,
233
- recyclerViewManager.getChildContainerDimensions().width,
234
- recyclerViewManager.getWindowSize().width
235
- );
233
+ // eslint-disable-next-line no-param-reassign
234
+ offset =
235
+ adjustOffsetForRTL(
236
+ offset,
237
+ recyclerViewManager.getChildContainerDimensions().width,
238
+ recyclerViewManager.getWindowSize().width
239
+ ) +
240
+ (skipFirstItemOffset
241
+ ? recyclerViewManager.firstItemOffset
242
+ : -recyclerViewManager.firstItemOffset);
236
243
  }
237
244
 
238
245
  // Calculate the final offset including first item offset if needed
@@ -248,6 +255,9 @@ export function useRecyclerViewController<T>(
248
255
  });
249
256
  }
250
257
  },
258
+ clearLayoutCacheOnUpdate: () => {
259
+ recyclerViewManager.markLayoutManagerDirty();
260
+ },
251
261
 
252
262
  // Expose native scroll view methods
253
263
  flashScrollIndicators: () => {
@@ -267,13 +277,19 @@ export function useRecyclerViewController<T>(
267
277
  * Scrolls to the end of the list.
268
278
  */
269
279
  scrollToEnd: async ({ animated }: ScrollToEdgeParams = {}) => {
280
+ const { data } = recyclerViewManager.props;
270
281
  if (data && data.length > 0) {
271
- await handlerMethods.scrollToIndex({
272
- index: data.length - 1,
273
- animated,
274
- });
282
+ const lastIndex = data.length - 1;
283
+ if (!recyclerViewManager.getEngagedIndices().includes(lastIndex)) {
284
+ await handlerMethods.scrollToIndex({
285
+ index: lastIndex,
286
+ animated,
287
+ });
288
+ }
275
289
  }
276
- scrollViewRef.current!.scrollToEnd({ animated });
290
+ setTimeout(() => {
291
+ scrollViewRef.current!.scrollToEnd({ animated });
292
+ }, 0);
277
293
  },
278
294
 
279
295
  /**
@@ -289,38 +305,30 @@ export function useRecyclerViewController<T>(
289
305
  /**
290
306
  * Scrolls to a specific index in the list.
291
307
  * Supports viewPosition and viewOffset for precise positioning.
308
+ * Returns a Promise that resolves when the scroll is complete.
292
309
  */
293
- scrollToIndex: async ({
310
+ scrollToIndex: ({
294
311
  index,
295
312
  animated,
296
313
  viewPosition,
297
314
  viewOffset,
298
- }: ScrollToIndexParams) => {
299
- if (scrollViewRef.current && data && data.length > index) {
300
- pauseAdjustRef.current = true;
301
- const layout = recyclerViewManager.getLayout(index);
302
- let lastScrollOffset = recyclerViewManager.getLastScrollOffset();
303
- const bufferForScroll = horizontal
304
- ? recyclerViewManager.getWindowSize().width
305
- : recyclerViewManager.getWindowSize().height;
306
-
307
- const bufferForCompute = bufferForScroll * 2;
308
-
309
- if (layout) {
310
- let prevFinalOffset = Number.POSITIVE_INFINITY;
311
- let finalOffset = 0;
312
- let attempts = 0;
313
- const MAX_ATTEMPTS = 5;
314
- const OFFSET_TOLERANCE = 1; // 1px tolerance
315
-
316
- do {
315
+ }: ScrollToIndexParams): Promise<void> => {
316
+ return new Promise((resolve) => {
317
+ const { horizontal } = recyclerViewManager.props;
318
+ if (
319
+ scrollViewRef.current &&
320
+ index >= 0 &&
321
+ index < recyclerViewManager.getDataLength()
322
+ ) {
323
+ // Pause the scroll offset adjustments
324
+ pauseOffsetCorrection.current = true;
325
+ recyclerViewManager.setOffsetProjectionEnabled(false);
326
+
327
+ const getFinalOffset = () => {
317
328
  const layout = recyclerViewManager.getLayout(index);
318
- if (!layout || isUnmounted.current) break;
319
-
320
329
  const offset = horizontal ? layout.x : layout.y;
321
- finalOffset = offset;
322
-
323
- // Apply viewPosition and viewOffset adjustments if provided
330
+ let finalOffset = offset;
331
+ // take viewPosition etc into account
324
332
  if (viewPosition !== undefined || viewOffset !== undefined) {
325
333
  const containerSize = horizontal
326
334
  ? recyclerViewManager.getWindowSize().width
@@ -338,77 +346,149 @@ export function useRecyclerViewController<T>(
338
346
  finalOffset += viewOffset;
339
347
  }
340
348
  }
349
+ return finalOffset + recyclerViewManager.firstItemOffset;
350
+ };
351
+ const lastAbsoluteScrollOffset =
352
+ recyclerViewManager.getAbsoluteLastScrollOffset();
353
+ const bufferForScroll = horizontal
354
+ ? recyclerViewManager.getWindowSize().width
355
+ : recyclerViewManager.getWindowSize().height;
356
+
357
+ const bufferForCompute = bufferForScroll * 2;
358
+
359
+ const getStartScrollOffset = () => {
360
+ let lastScrollOffset = lastAbsoluteScrollOffset;
361
+ const finalOffset = getFinalOffset();
341
362
 
342
- // Check if offset has stabilized
343
- if (Math.abs(prevFinalOffset - finalOffset) <= OFFSET_TOLERANCE) {
344
- break;
363
+ if (finalOffset > lastScrollOffset) {
364
+ lastScrollOffset = Math.max(
365
+ finalOffset - bufferForCompute,
366
+ lastScrollOffset
367
+ );
368
+ recyclerViewManager.setScrollDirection("forward");
369
+ } else {
370
+ lastScrollOffset = Math.min(
371
+ finalOffset + bufferForCompute,
372
+ lastScrollOffset
373
+ );
374
+ recyclerViewManager.setScrollDirection("backward");
375
+ }
376
+ return lastScrollOffset;
377
+ };
378
+ let initialTargetOffset = getFinalOffset();
379
+ let initialStartScrollOffset = getStartScrollOffset();
380
+ let finalOffset = initialTargetOffset;
381
+ let startScrollOffset = initialStartScrollOffset;
382
+
383
+ const steps = 5;
384
+
385
+ /**
386
+ * Recursively performs the scroll animation steps.
387
+ * This function replaces the async/await loop with callback-based execution.
388
+ *
389
+ * @param currentStep - The current step in the animation (0 to steps-1)
390
+ */
391
+ const performScrollStep = (currentStep: number) => {
392
+ // Check if component is unmounted or we've completed all steps
393
+ if (isUnmounted.current) {
394
+ resolve();
395
+ return;
396
+ } else if (currentStep >= steps) {
397
+ // All steps completed, perform final scroll
398
+ finishScrollToIndex();
399
+ return;
345
400
  }
346
401
 
347
- prevFinalOffset = finalOffset;
402
+ // Calculate the offset for this step
403
+ // For animated scrolls: interpolate from finalOffset to startScrollOffset
404
+ // For non-animated: interpolate from startScrollOffset to finalOffset
405
+ const nextOffset = animated
406
+ ? finalOffset +
407
+ (startScrollOffset - finalOffset) *
408
+ (currentStep / (steps - 1))
409
+ : startScrollOffset +
410
+ (finalOffset - startScrollOffset) *
411
+ (currentStep / (steps - 1));
412
+
413
+ // Update scroll offset with a callback to continue to the next step
414
+ updateScrollOffsetWithCallback(nextOffset, () => {
415
+ // Check if the index is still valid after the update
416
+ if (index >= recyclerViewManager.getDataLength()) {
417
+ // Index out of bounds, scroll to end instead
418
+ handlerMethods.scrollToEnd({ animated });
419
+ resolve(); // Resolve the promise as we're done
420
+ return;
421
+ }
348
422
 
349
- if (animated) {
350
- if (finalOffset > lastScrollOffset) {
351
- lastScrollOffset = Math.max(
352
- finalOffset - bufferForCompute,
353
- lastScrollOffset
354
- );
423
+ // Check if the target position has changed significantly
424
+ const newFinalOffset = getFinalOffset();
425
+ if (
426
+ (newFinalOffset < initialTargetOffset &&
427
+ newFinalOffset < initialStartScrollOffset) ||
428
+ (newFinalOffset > initialTargetOffset &&
429
+ newFinalOffset > initialStartScrollOffset)
430
+ ) {
431
+ // Target has moved, recalculate and restart from beginning
432
+ finalOffset = newFinalOffset;
433
+ startScrollOffset = getStartScrollOffset();
434
+ initialTargetOffset = newFinalOffset;
435
+ initialStartScrollOffset = startScrollOffset;
436
+ performScrollStep(0); // Restart from step 0
355
437
  } else {
356
- lastScrollOffset = Math.min(
357
- finalOffset + bufferForCompute,
358
- lastScrollOffset
359
- );
438
+ // Continue to next step
439
+ performScrollStep(currentStep + 1);
360
440
  }
441
+ });
442
+ };
361
443
 
362
- await updateScrollOffsetAsync(lastScrollOffset);
363
- }
364
- await updateScrollOffsetAsync(finalOffset);
365
-
366
- attempts++;
367
- } while (attempts < MAX_ATTEMPTS);
368
-
369
- if (animated) {
370
- const maxOffset =
371
- (horizontal
372
- ? recyclerViewManager.getChildContainerDimensions().width
373
- : recyclerViewManager.getChildContainerDimensions().height) -
374
- (horizontal
375
- ? recyclerViewManager.getWindowSize().width
376
- : recyclerViewManager.getWindowSize().height);
444
+ /**
445
+ * Completes the scroll to index operation by performing the final scroll
446
+ * and re-enabling offset correction after a delay.
447
+ */
448
+ const finishScrollToIndex = () => {
449
+ finalOffset = getFinalOffset();
450
+ const maxOffset = recyclerViewManager.getMaxScrollOffset();
377
451
 
378
452
  if (finalOffset > maxOffset) {
379
453
  finalOffset = maxOffset;
380
454
  }
381
455
 
382
- if (finalOffset > lastScrollOffset) {
383
- lastScrollOffset = Math.max(
384
- finalOffset - bufferForScroll,
385
- lastScrollOffset
386
- );
387
- } else {
388
- lastScrollOffset = Math.min(
389
- finalOffset + bufferForScroll,
390
- lastScrollOffset
391
- );
456
+ if (animated) {
457
+ // For animated scrolls, first jump to the start position
458
+ // We don't need to add firstItemOffset here as it's already added
459
+ handlerMethods.scrollToOffset({
460
+ offset: startScrollOffset,
461
+ animated: false,
462
+ skipFirstItemOffset: true,
463
+ });
392
464
  }
393
465
 
394
- //We don't need to add firstItemOffset here as it will be added in scrollToOffset
466
+ // Perform the final scroll to the target position
395
467
  handlerMethods.scrollToOffset({
396
- offset: lastScrollOffset,
397
- animated: false,
398
- skipFirstItemOffset: false,
468
+ offset: finalOffset,
469
+ animated,
470
+ skipFirstItemOffset: true,
399
471
  });
400
- }
401
472
 
402
- handlerMethods.scrollToOffset({
403
- offset: finalOffset,
404
- animated,
405
- skipFirstItemOffset: false,
406
- });
473
+ // Re-enable offset correction after a delay
474
+ // Longer delay for animated scrolls to allow animation to complete
475
+ setTimeout(
476
+ () => {
477
+ pauseOffsetCorrection.current = false;
478
+ recyclerViewManager.setOffsetProjectionEnabled(true);
479
+ resolve(); // Resolve the promise after re-enabling corrections
480
+ },
481
+ animated ? 300 : 200
482
+ );
483
+ };
484
+
485
+ // Start the scroll animation process
486
+ performScrollStep(0);
487
+ } else {
488
+ // Invalid parameters, resolve immediately
489
+ resolve();
407
490
  }
408
- setTimeout(() => {
409
- pauseAdjustRef.current = false;
410
- }, 200);
411
- }
491
+ });
412
492
  },
413
493
 
414
494
  /**
@@ -421,11 +501,10 @@ export function useRecyclerViewController<T>(
421
501
  viewPosition,
422
502
  viewOffset,
423
503
  }: ScrollToItemParams<T>) => {
504
+ const { data } = recyclerViewManager.props;
424
505
  if (scrollViewRef.current && data) {
425
506
  // Find the index of the item in the data array
426
- const index = Array.from(data).findIndex(
427
- (dataItem) => dataItem === item
428
- );
507
+ const index = data.findIndex((dataItem) => dataItem === item);
429
508
  if (index >= 0) {
430
509
  handlerMethods.scrollToIndex({
431
510
  index,
@@ -445,7 +524,7 @@ export function useRecyclerViewController<T>(
445
524
  return recyclerViewManager.getWindowSize();
446
525
  },
447
526
  getLayout: (index: number) => {
448
- return recyclerViewManager.getLayout(index);
527
+ return recyclerViewManager.tryGetLayout(index);
449
528
  },
450
529
  getAbsoluteLastScrollOffset: () => {
451
530
  return recyclerViewManager.getAbsoluteLastScrollOffset();
@@ -456,11 +535,11 @@ export function useRecyclerViewController<T>(
456
535
  recordInteraction: () => {
457
536
  recyclerViewManager.recordInteraction();
458
537
  },
459
- getVisibleIndices: () => {
460
- return recyclerViewManager.getVisibleIndices();
538
+ computeVisibleIndices: () => {
539
+ return recyclerViewManager.computeVisibleIndices();
461
540
  },
462
541
  getFirstVisibleIndex: () => {
463
- return recyclerViewManager.getVisibleIndices().startIndex;
542
+ return recyclerViewManager.computeVisibleIndices().startIndex;
464
543
  },
465
544
  recomputeViewableItems: () => {
466
545
  recyclerViewManager.recomputeViewableItems();
@@ -469,19 +548,79 @@ export function useRecyclerViewController<T>(
469
548
  * Disables item recycling in preparation for layout animations.
470
549
  */
471
550
  prepareForLayoutAnimationRender: () => {
472
- recyclerViewManager.disableRecycling = true;
551
+ recyclerViewManager.animationOptimizationsEnabled = true;
473
552
  },
474
553
  };
475
- }, [horizontal, data, recyclerViewManager]);
554
+ }, [
555
+ recyclerViewManager,
556
+ scrollViewRef,
557
+ setTimeout,
558
+ isUnmounted,
559
+ updateScrollOffsetWithCallback,
560
+ ]);
561
+
562
+ const applyInitialScrollIndex = useCallback(() => {
563
+ const { horizontal, data } = recyclerViewManager.props;
564
+
565
+ const initialScrollIndex =
566
+ recyclerViewManager.getInitialScrollIndex() ?? -1;
567
+ const dataLength = data?.length ?? 0;
568
+ if (
569
+ initialScrollIndex >= 0 &&
570
+ initialScrollIndex < dataLength &&
571
+ !initialScrollCompletedRef.current &&
572
+ recyclerViewManager.getIsFirstLayoutComplete()
573
+ ) {
574
+ // Use setTimeout to ensure that we keep trying to scroll on first few renders
575
+ setTimeout(() => {
576
+ initialScrollCompletedRef.current = true;
577
+ pauseOffsetCorrection.current = false;
578
+ }, 100);
579
+
580
+ pauseOffsetCorrection.current = true;
581
+
582
+ const offset = horizontal
583
+ ? recyclerViewManager.getLayout(initialScrollIndex).x
584
+ : recyclerViewManager.getLayout(initialScrollIndex).y;
585
+ handlerMethods.scrollToOffset({
586
+ offset,
587
+ animated: false,
588
+ skipFirstItemOffset: false,
589
+ });
590
+
591
+ setTimeout(() => {
592
+ handlerMethods.scrollToOffset({
593
+ offset,
594
+ animated: false,
595
+ skipFirstItemOffset: false,
596
+ });
597
+ }, 0);
598
+ }
599
+ }, [handlerMethods, recyclerViewManager, setTimeout]);
476
600
 
477
601
  // Expose imperative methods through the ref
478
602
  useImperativeHandle(
479
603
  ref,
480
604
  () => {
481
- return { ...scrollViewRef.current, ...handlerMethods };
605
+ const imperativeApi = { ...scrollViewRef.current, ...handlerMethods };
606
+ // Without this the props getter from handlerMethods is evaluated during spread and
607
+ // future updates to props are not reflected in the ref
608
+ Object.defineProperty(imperativeApi, "props", {
609
+ get() {
610
+ return recyclerViewManager.props;
611
+ },
612
+ enumerable: true,
613
+ configurable: true,
614
+ });
615
+ return imperativeApi;
482
616
  },
483
- [handlerMethods]
617
+ [handlerMethods, scrollViewRef, recyclerViewManager]
484
618
  );
485
619
 
486
- return { applyContentOffset, applyInitialScrollIndex };
620
+ return {
621
+ applyOffsetCorrection,
622
+ computeFirstVisibleIndexForOffsetCorrection,
623
+ applyInitialScrollIndex,
624
+ handlerMethods,
625
+ };
487
626
  }