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

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