@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
@@ -13,11 +13,13 @@ import React, {
13
13
  useEffect,
14
14
  } from "react";
15
15
  import { Animated, NativeScrollEvent } from "react-native";
16
+
16
17
  import { FlashListProps } from "../..";
17
- import { CompatAnimatedView } from "./CompatView";
18
18
  import { RecyclerViewManager } from "../RecyclerViewManager";
19
19
  import { ViewHolder } from "../ViewHolder";
20
20
 
21
+ import { CompatAnimatedView } from "./CompatView";
22
+
21
23
  /**
22
24
  * Props for the StickyHeaders component
23
25
  * @template TItem - The type of items in the list
@@ -26,7 +28,7 @@ export interface StickyHeaderProps<TItem> {
26
28
  /** Array of indices that should have sticky headers */
27
29
  stickyHeaderIndices: number[];
28
30
  /** The data array being rendered */
29
- data: readonly TItem[];
31
+ data: ReadonlyArray<TItem>;
30
32
  /** Animated value tracking scroll position */
31
33
  scrollY: Animated.Value;
32
34
  /** Function to render each item */
@@ -47,6 +49,11 @@ export interface StickyHeaderRef {
47
49
  reportScrollEvent: (event: NativeScrollEvent) => void;
48
50
  }
49
51
 
52
+ interface StickyHeaderState {
53
+ currentStickyIndex: number;
54
+ pushStartsAt: number;
55
+ }
56
+
50
57
  export const StickyHeaders = <TItem,>({
51
58
  stickyHeaderIndices,
52
59
  renderItem,
@@ -56,29 +63,35 @@ export const StickyHeaders = <TItem,>({
56
63
  data,
57
64
  extraData,
58
65
  }: StickyHeaderProps<TItem>) => {
59
- const [stickyIndices, setStickyIndices] = useState<{
60
- currentStickyIndex: number;
61
- nextStickyIndex: number;
62
- }>({ currentStickyIndex: -1, nextStickyIndex: -1 });
66
+ const [stickyHeaderState, setStickyHeaderState] = useState<StickyHeaderState>(
67
+ {
68
+ currentStickyIndex: -1,
69
+ pushStartsAt: Number.MAX_SAFE_INTEGER,
70
+ }
71
+ );
63
72
 
64
- const { currentStickyIndex, nextStickyIndex } = stickyIndices;
65
- const hasLayout = recyclerViewManager.hasLayout();
73
+ const { currentStickyIndex, pushStartsAt } = stickyHeaderState;
66
74
 
67
- // Memoize sorted indices based on their Y positions
75
+ // sort indices and memoize compute
68
76
  const sortedIndices = useMemo(() => {
69
- if (!hasLayout) {
70
- return [];
71
- }
72
- return stickyHeaderIndices.sort((a, b) => a - b);
73
- }, [stickyHeaderIndices, recyclerViewManager, hasLayout]);
77
+ return [...stickyHeaderIndices].sort((first, second) => first - second);
78
+ }, [stickyHeaderIndices]);
79
+
80
+ const legthInvalid =
81
+ sortedIndices.length === 0 ||
82
+ recyclerViewManager.getDataLength() <=
83
+ sortedIndices[sortedIndices.length - 1];
74
84
 
75
85
  const compute = useCallback(() => {
76
- const adjustedValue = recyclerViewManager.getLastScrollOffset();
86
+ if (legthInvalid) {
87
+ return;
88
+ }
89
+ const adjustedScrollOffset = recyclerViewManager.getLastScrollOffset();
77
90
 
78
91
  // Binary search for current sticky index
79
92
  const currentIndexInArray = findCurrentStickyIndex(
80
93
  sortedIndices,
81
- adjustedValue,
94
+ adjustedScrollOffset,
82
95
  (index) => recyclerViewManager.getLayout(index).y
83
96
  );
84
97
 
@@ -89,16 +102,33 @@ export const StickyHeaders = <TItem,>({
89
102
  newNextStickyIndex = -1;
90
103
  }
91
104
 
105
+ // To make sure header offset is 0 in the interpolate compute
106
+ const newNextStickyY =
107
+ newNextStickyIndex === -1
108
+ ? Number.MAX_SAFE_INTEGER
109
+ : (recyclerViewManager.tryGetLayout(newNextStickyIndex)?.y ?? 0) +
110
+ recyclerViewManager.firstItemOffset;
111
+ const newCurrentStickyHeight =
112
+ recyclerViewManager.tryGetLayout(newStickyIndex)?.height ?? 0;
113
+
114
+ const newPushStartsAt = newNextStickyY - newCurrentStickyHeight;
115
+
92
116
  if (
93
117
  newStickyIndex !== currentStickyIndex ||
94
- newNextStickyIndex !== nextStickyIndex
118
+ newPushStartsAt !== pushStartsAt
95
119
  ) {
96
- setStickyIndices({
120
+ setStickyHeaderState({
97
121
  currentStickyIndex: newStickyIndex,
98
- nextStickyIndex: newNextStickyIndex,
122
+ pushStartsAt: newPushStartsAt,
99
123
  });
100
124
  }
101
- }, [currentStickyIndex, nextStickyIndex, recyclerViewManager, sortedIndices]);
125
+ }, [
126
+ legthInvalid,
127
+ recyclerViewManager,
128
+ sortedIndices,
129
+ currentStickyIndex,
130
+ pushStartsAt,
131
+ ]);
102
132
 
103
133
  useEffect(() => {
104
134
  compute();
@@ -112,45 +142,24 @@ export const StickyHeaders = <TItem,>({
112
142
  compute();
113
143
  },
114
144
  }),
115
- [
116
- stickyHeaderIndices,
117
- recyclerViewManager,
118
- currentStickyIndex,
119
- nextStickyIndex,
120
- ]
145
+ [compute]
121
146
  );
122
147
 
123
148
  const refHolder = useRef(new Map()).current;
124
149
 
125
- // Memoize translateY calculation
126
150
  const translateY = useMemo(() => {
127
- if (currentStickyIndex === -1 || nextStickyIndex === -1) {
128
- return scrollY.interpolate({
129
- inputRange: [0, Infinity],
130
- outputRange: [0, 0],
131
- extrapolate: "clamp",
132
- });
133
- }
134
-
135
- const currentLayout = recyclerViewManager.getLayout(currentStickyIndex);
136
- const nextLayout = recyclerViewManager.getLayout(nextStickyIndex);
137
-
138
- const pushStartsAt = nextLayout.y - currentLayout.height;
151
+ const currentStickyHeight =
152
+ recyclerViewManager.tryGetLayout(currentStickyIndex)?.height ?? 0;
139
153
 
140
154
  return scrollY.interpolate({
141
- inputRange: [
142
- pushStartsAt + recyclerViewManager.firstItemOffset,
143
- nextLayout.y + recyclerViewManager.firstItemOffset,
144
- ],
145
- outputRange: [0, -currentLayout.height],
155
+ inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
156
+ outputRange: [0, -currentStickyHeight],
146
157
  extrapolate: "clamp",
147
158
  });
148
- }, [currentStickyIndex, nextStickyIndex, recyclerViewManager, scrollY]);
159
+ }, [recyclerViewManager, currentStickyIndex, scrollY, pushStartsAt]);
149
160
 
150
161
  // Memoize header content
151
162
  const headerContent = useMemo(() => {
152
- if (currentStickyIndex === -1) return null;
153
-
154
163
  return (
155
164
  <CompatAnimatedView
156
165
  style={{
@@ -158,22 +167,25 @@ export const StickyHeaders = <TItem,>({
158
167
  top: 0,
159
168
  left: 0,
160
169
  right: 0,
170
+ zIndex: 1,
161
171
  transform: [{ translateY }],
162
172
  }}
163
173
  >
164
- <ViewHolder
165
- index={currentStickyIndex}
166
- item={data[currentStickyIndex]}
167
- renderItem={renderItem}
168
- layout={{ x: 0, y: 0, width: 0, height: 0 }}
169
- refHolder={refHolder}
170
- extraData={extraData}
171
- trailingItem={null}
172
- target="StickyHeader"
173
- />
174
+ {currentStickyIndex !== -1 && currentStickyIndex < data.length ? (
175
+ <ViewHolder
176
+ index={currentStickyIndex}
177
+ item={data[currentStickyIndex]}
178
+ renderItem={renderItem}
179
+ layout={{ x: 0, y: 0, width: 0, height: 0 }}
180
+ refHolder={refHolder}
181
+ extraData={extraData}
182
+ trailingItem={null}
183
+ target="StickyHeader"
184
+ />
185
+ ) : null}
174
186
  </CompatAnimatedView>
175
187
  );
176
- }, [currentStickyIndex, data, renderItem, extraData, refHolder, translateY]);
188
+ }, [translateY, currentStickyIndex, data, renderItem, refHolder, extraData]);
177
189
 
178
190
  return headerContent;
179
191
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A simple wrapper for consecutive integer arrays
2
+ * A simple wrapper for consecutive postive integer arrays
3
3
  * Only stores start and end indices for faster computation as numbers are consecutive.
4
4
  */
5
5
  export class ConsecutiveNumbers {
@@ -8,7 +8,7 @@ export class ConsecutiveNumbers {
8
8
  public readonly endIndex: number
9
9
  ) {}
10
10
 
11
- static readonly EMPTY = new ConsecutiveNumbers(0, -1);
11
+ static readonly EMPTY = new ConsecutiveNumbers(-1, -2);
12
12
 
13
13
  /**
14
14
  * Get the length of the array
@@ -1,11 +1,17 @@
1
- import { ConsecutiveNumbers } from "./ConsecutiveNumbers";
1
+ import { PlatformConfig } from "../../native/config/PlatformHelper";
2
2
  import { RVLayoutManager } from "../layout-managers/LayoutManager";
3
3
 
4
+ import { ConsecutiveNumbers } from "./ConsecutiveNumbers";
5
+
4
6
  export interface RVEngagedIndicesTracker {
5
7
  // Current scroll offset of the list. Directly setting this won't trigger visible indices updates
6
8
  scrollOffset: number;
7
9
  // Total distance (in pixels) to pre-render items before and after the visible viewport
8
10
  drawDistance: number;
11
+ // Whether to use offset projection to predict the next scroll offset
12
+ enableOffsetProjection: boolean;
13
+ // Average render time of the list
14
+ averageRenderTime: number;
9
15
 
10
16
  /**
11
17
  * Updates the scroll offset and calculates which items should be rendered (engaged indices).
@@ -19,8 +25,31 @@ export interface RVEngagedIndicesTracker {
19
25
  velocity: Velocity | null | undefined,
20
26
  layoutManager: RVLayoutManager
21
27
  ) => ConsecutiveNumbers | undefined;
28
+
29
+ /**
30
+ * Returns the currently engaged (rendered) indices.
31
+ * This includes both visible items and buffer items.
32
+ * @returns The last computed set of engaged indices
33
+ */
22
34
  getEngagedIndices: () => ConsecutiveNumbers;
35
+
36
+ /**
37
+ * Computes the visible indices in the viewport.
38
+ * @param layoutManager - Layout manager to fetch item positions and dimensions
39
+ * @returns Indices of items currently visible in the viewport
40
+ */
23
41
  computeVisibleIndices: (layoutManager: RVLayoutManager) => ConsecutiveNumbers;
42
+
43
+ /**
44
+ * Sets the scroll direction for velocity history tracking.
45
+ * @param scrollDirection - The direction of scrolling ("forward" or "backward")
46
+ */
47
+ setScrollDirection: (scrollDirection: "forward" | "backward") => void;
48
+
49
+ /**
50
+ * Resets the velocity history based on the current scroll direction.
51
+ */
52
+ resetVelocityHistory: () => void;
24
53
  }
25
54
 
26
55
  export interface Velocity {
@@ -32,16 +61,26 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
32
61
  // Current scroll position of the list
33
62
  public scrollOffset = 0;
34
63
  // Distance to pre-render items before and after the visible viewport (in pixels)
35
- public drawDistance: number = 250;
64
+ public drawDistance = PlatformConfig.defaultDrawDistance;
65
+
66
+ // Whether to use offset projection to predict the next scroll offset
67
+ public enableOffsetProjection = true;
68
+
69
+ // Average render time of the list
70
+ public averageRenderTime = 16;
71
+
72
+ // Internal override to disable offset projection
73
+ private forceDisableOffsetProjection = false;
74
+
36
75
  // Currently rendered item indices (including buffer items)
37
76
  private engagedIndices = ConsecutiveNumbers.EMPTY;
38
77
 
39
78
  // Buffer distribution multipliers for scroll direction optimization
40
- private smallMultiplier = 0.1; // Used for buffer in the opposite direction of scroll
41
- private largeMultiplier = 0.9; // Used for buffer in the direction of scroll
79
+ private smallMultiplier = 0.3; // Used for buffer in the opposite direction of scroll
80
+ private largeMultiplier = 0.7; // Used for buffer in the direction of scroll
42
81
 
43
82
  // Circular buffer to track recent scroll velocities for direction detection
44
- private velocityHistory = [-1, -1, -1, -1, -1];
83
+ private velocityHistory = [0, 0, 0, -0.1, -0.1];
45
84
  private velocityIndex = 0;
46
85
 
47
86
  /**
@@ -63,7 +102,21 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
63
102
  // STEP 1: Determine the currently visible viewport
64
103
  const windowSize = layoutManager.getWindowsSize();
65
104
  const isHorizontal = layoutManager.isHorizontal();
66
- const viewportStart = offset;
105
+
106
+ // Update velocity history
107
+ if (velocity) {
108
+ this.updateVelocityHistory(isHorizontal ? velocity.x : velocity.y);
109
+ }
110
+
111
+ // Determine scroll direction to optimize buffer distribution
112
+ const isScrollingBackward = this.isScrollingBackward();
113
+ const viewportStart =
114
+ this.enableOffsetProjection && !this.forceDisableOffsetProjection
115
+ ? this.getProjectedScrollOffset(offset, this.averageRenderTime)
116
+ : offset;
117
+
118
+ // console.log("timeMs", this.averageRenderTime, offset, viewportStart);
119
+
67
120
  const viewportSize = isHorizontal ? windowSize.width : windowSize.height;
68
121
  const viewportEnd = viewportStart + viewportSize;
69
122
 
@@ -71,11 +124,6 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
71
124
  // The total extra space where items will be pre-rendered
72
125
  const totalBuffer = this.drawDistance * 2;
73
126
 
74
- // Determine scroll direction to optimize buffer distribution
75
- const isScrollingBackward = this.isScrollingBackward(
76
- isHorizontal ? velocity?.x : velocity?.y
77
- );
78
-
79
127
  // Distribute more buffer in the direction of scrolling
80
128
  // When scrolling forward: more buffer after viewport
81
129
  // When scrolling backward: more buffer before viewport
@@ -86,8 +134,8 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
86
134
  ? this.smallMultiplier
87
135
  : this.largeMultiplier;
88
136
 
89
- let bufferBefore = Math.ceil(totalBuffer * beforeRatio);
90
- let bufferAfter = Math.ceil(totalBuffer * afterRatio);
137
+ const bufferBefore = Math.ceil(totalBuffer * beforeRatio);
138
+ const bufferAfter = Math.ceil(totalBuffer * afterRatio);
91
139
 
92
140
  // STEP 3: Calculate the extended viewport (visible area + buffers)
93
141
  // The start position with buffer (never less than 0)
@@ -119,9 +167,12 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
119
167
  extendedStart,
120
168
  extendedEnd
121
169
  );
122
- if (!isHorizontal) {
123
- //console.log("newEngagedIndices", newEngagedIndices, this.scrollOffset);
124
- }
170
+ // console.log(
171
+ // "newEngagedIndices",
172
+ // newEngagedIndices,
173
+ // this.scrollOffset,
174
+ // viewportStart
175
+ // );
125
176
  // Only return new indices if they've changed
126
177
  const oldEngagedIndices = this.engagedIndices;
127
178
  this.engagedIndices = newEngagedIndices;
@@ -131,22 +182,25 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
131
182
  : newEngagedIndices;
132
183
  }
133
184
 
185
+ /**
186
+ * Updates the velocity history with a new velocity value.
187
+ * @param velocity - Current scroll velocity component (x or y)
188
+ */
189
+ private updateVelocityHistory(velocity: number) {
190
+ this.velocityHistory[this.velocityIndex] = velocity;
191
+ this.velocityIndex = (this.velocityIndex + 1) % this.velocityHistory.length;
192
+ }
193
+
134
194
  /**
135
195
  * Determines scroll direction by analyzing recent velocity history.
136
196
  * Uses a majority voting system on the last 5 velocity values.
137
- * @param velocity - Current scroll velocity component (x or y)
138
197
  * @returns true if scrolling backward (negative direction), false otherwise
139
198
  */
140
- private isScrollingBackward(velocity?: number): boolean {
141
- //update velocity history
142
- if (velocity) {
143
- this.velocityHistory[this.velocityIndex] = velocity;
144
- this.velocityIndex =
145
- (this.velocityIndex + 1) % this.velocityHistory.length;
146
- }
147
- //should decide based on whether we have more positive or negative values, use for loop
199
+ private isScrollingBackward(): boolean {
200
+ // should decide based on whether we have more positive or negative values, use for loop
148
201
  let positiveCount = 0;
149
202
  let negativeCount = 0;
203
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
150
204
  for (let i = 0; i < this.velocityHistory.length; i++) {
151
205
  if (this.velocityHistory[i] > 0) {
152
206
  positiveCount++;
@@ -157,6 +211,40 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
157
211
  return positiveCount < negativeCount;
158
212
  }
159
213
 
214
+ /**
215
+ * Calculates the median velocity based on velocity history
216
+ * Medina works better agains outliers
217
+ * @returns Median velocity over the recent history
218
+ */
219
+ private getMedianVelocity(): number {
220
+ // Make a copy of velocity history and sort it
221
+ const sortedVelocities = [...this.velocityHistory].sort(
222
+ (valueA, valueB) => valueA - valueB
223
+ );
224
+ const length = sortedVelocities.length;
225
+
226
+ // If length is odd, return the middle element
227
+ if (length % 2 === 1) {
228
+ return sortedVelocities[Math.floor(length / 2)];
229
+ }
230
+
231
+ // If length is even, return the average of the two middle elements
232
+ const midIndex = length / 2;
233
+ return (sortedVelocities[midIndex - 1] + sortedVelocities[midIndex]) / 2;
234
+ }
235
+
236
+ /**
237
+ * Projects the next scroll offset based on median velocity
238
+ * @param timeMs Time in milliseconds to predict ahead
239
+ * @returns Projected scroll offset
240
+ */
241
+ private getProjectedScrollOffset(offset: number, timeMs: number): number {
242
+ const medianVelocity = this.getMedianVelocity();
243
+ // Convert time from ms to seconds for velocity calculation
244
+ // Predict next position: current position + (velocity * time)
245
+ return offset + medianVelocity * timeMs;
246
+ }
247
+
160
248
  /**
161
249
  * Calculates which items are currently visible in the viewport.
162
250
  * Unlike getEngagedIndices, this doesn't include buffer items.
@@ -188,4 +276,26 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
188
276
  getEngagedIndices(): ConsecutiveNumbers {
189
277
  return this.engagedIndices;
190
278
  }
279
+
280
+ setScrollDirection(scrollDirection: "forward" | "backward") {
281
+ if (scrollDirection === "forward") {
282
+ this.velocityHistory = [0, 0, 0, 0.1, 0.1];
283
+ this.velocityIndex = 0;
284
+ } else {
285
+ this.velocityHistory = [0, 0, 0, -0.1, -0.1];
286
+ this.velocityIndex = 0;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Resets the velocity history based on the current scroll direction.
292
+ * This ensures that the velocity history is always in sync with the current scroll direction.
293
+ */
294
+ resetVelocityHistory() {
295
+ if (this.isScrollingBackward()) {
296
+ this.setScrollDirection("backward");
297
+ } else {
298
+ this.setScrollDirection("forward");
299
+ }
300
+ }
191
301
  }
@@ -0,0 +1,42 @@
1
+ import { PlatformConfig } from "../../native/config/PlatformHelper";
2
+ import { AverageWindow } from "../../utils/AverageWindow";
3
+
4
+ export class RenderTimeTracker {
5
+ private renderTimeAvgWindow = new AverageWindow(5);
6
+ private lastTimerStartedAt = -1;
7
+ private maxRenderTime = 32; // TODO: Improve this even more
8
+ private defaultRenderTime = 16;
9
+
10
+ startTracking() {
11
+ if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
12
+ return;
13
+ }
14
+ if (this.lastTimerStartedAt === -1) {
15
+ this.lastTimerStartedAt = Date.now();
16
+ }
17
+ }
18
+
19
+ markRenderComplete() {
20
+ if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
21
+ return;
22
+ }
23
+ if (this.lastTimerStartedAt !== -1) {
24
+ this.renderTimeAvgWindow.addValue(Date.now() - this.lastTimerStartedAt);
25
+ this.lastTimerStartedAt = -1;
26
+ }
27
+ }
28
+
29
+ getRawValue() {
30
+ return this.renderTimeAvgWindow.currentValue;
31
+ }
32
+
33
+ getAverageRenderTime() {
34
+ if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
35
+ return this.defaultRenderTime;
36
+ }
37
+ return Math.min(
38
+ this.maxRenderTime,
39
+ Math.max(Math.round(this.renderTimeAvgWindow.currentValue), 16)
40
+ );
41
+ }
42
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Tracks and calculates velocity for scroll/drag movements
3
+ * Used to determine momentum scrolling behavior
4
+ */
5
+ export class VelocityTracker<T> {
6
+ /** Timestamp of the last velocity update */
7
+ private lastUpdateTime = Date.now();
8
+ /** Current velocity vector with x and y components */
9
+ private velocity = { x: 0, y: 0 };
10
+
11
+ /** Reference to the momentum end timeout */
12
+ private timeoutId: NodeJS.Timeout | null = null;
13
+
14
+ /**
15
+ * Calculates velocity based on position change over time
16
+ * @param newOffset Current position value
17
+ * @param oldOffset Previous position value
18
+ * @param isHorizontal Whether movement is horizontal (true) or vertical (false)
19
+ * @param isRTL Whether layout direction is right-to-left
20
+ * @param callback Function to call with velocity updates and momentum end signal
21
+ */
22
+ computeVelocity(
23
+ newOffset: number,
24
+ oldOffset: number,
25
+ isHorizontal: boolean,
26
+ callback: (
27
+ velocity: { x: number; y: number },
28
+ isMomentumEnd: boolean
29
+ ) => void
30
+ ) {
31
+ // Clear any pending momentum end timeout
32
+ this.cleanUp();
33
+ // Calculate time since last update
34
+ const currentTime = Date.now();
35
+ const timeSinceLastUpdate = Math.max(1, currentTime - this.lastUpdateTime);
36
+
37
+ // Calculate velocity as distance/time
38
+ const newVelocity = (newOffset - oldOffset) / timeSinceLastUpdate;
39
+
40
+ // console.log(
41
+ // "newVelocity",
42
+ // newOffset,
43
+ // oldOffset,
44
+ // currentTime,
45
+ // this.lastUpdateTime,
46
+ // timeSinceLastUpdate,
47
+ // newVelocity
48
+ // );
49
+ this.lastUpdateTime = currentTime;
50
+
51
+ // Apply velocity to the correct axis
52
+ this.velocity.x = isHorizontal ? newVelocity : 0;
53
+ this.velocity.y = isHorizontal ? 0 : newVelocity;
54
+
55
+ // Trigger callback with current velocity
56
+ callback(this.velocity, false);
57
+
58
+ // Set timeout to signal momentum end after 100ms of no updates
59
+ this.timeoutId = setTimeout(() => {
60
+ this.cleanUp();
61
+ this.lastUpdateTime = Date.now();
62
+ this.velocity.x = 0;
63
+ this.velocity.y = 0;
64
+ callback(this.velocity, true);
65
+ }, 100);
66
+ }
67
+
68
+ /**
69
+ * Cleans up resources by clearing any pending timeout
70
+ */
71
+ cleanUp() {
72
+ if (this.timeoutId !== null) {
73
+ clearTimeout(this.timeoutId);
74
+ this.timeoutId = null;
75
+ }
76
+ }
77
+ }