@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.
- package/README.md +67 -96
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/BlankAreaEvent.kt +2 -2
- package/dist/AnimatedFlashList.d.ts +0 -1
- package/dist/AnimatedFlashList.d.ts.map +1 -1
- package/dist/AnimatedFlashList.js +3 -3
- package/dist/AnimatedFlashList.js.map +1 -1
- package/dist/FlashList.d.ts +9 -0
- package/dist/FlashList.d.ts.map +1 -1
- package/dist/FlashList.js +22 -3
- package/dist/FlashList.js.map +1 -1
- package/dist/FlashListProps.d.ts +33 -13
- package/dist/FlashListProps.d.ts.map +1 -1
- package/dist/FlashListProps.js.map +1 -1
- package/dist/FlashListRef.d.ts +305 -0
- package/dist/FlashListRef.d.ts.map +1 -0
- package/dist/FlashListRef.js +3 -0
- package/dist/FlashListRef.js.map +1 -0
- package/dist/GridLayoutProviderWithProps.js +1 -2
- package/dist/GridLayoutProviderWithProps.js.map +1 -1
- package/dist/MasonryFlashList.d.ts +2 -2
- package/dist/MasonryFlashList.d.ts.map +1 -1
- package/dist/MasonryFlashList.js.map +1 -1
- package/dist/PureComponentWrapper.js +1 -1
- package/dist/PureComponentWrapper.js.map +1 -1
- package/dist/__tests__/AverageWindow.test.js.map +1 -1
- package/dist/__tests__/ConsecutiveNumbers.test.d.ts +2 -0
- package/dist/__tests__/ConsecutiveNumbers.test.d.ts.map +1 -0
- package/dist/__tests__/ConsecutiveNumbers.test.js +224 -0
- package/dist/__tests__/ConsecutiveNumbers.test.js.map +1 -0
- package/dist/__tests__/FlashList.test.js.map +1 -1
- package/dist/__tests__/GridLayoutManager.test.d.ts +2 -0
- package/dist/__tests__/GridLayoutManager.test.d.ts.map +1 -0
- package/dist/__tests__/GridLayoutManager.test.js +69 -0
- package/dist/__tests__/GridLayoutManager.test.js.map +1 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -1
- package/dist/__tests__/LinearLayoutManager.test.d.ts +2 -0
- package/dist/__tests__/LinearLayoutManager.test.d.ts.map +1 -0
- package/dist/__tests__/LinearLayoutManager.test.js +140 -0
- package/dist/__tests__/LinearLayoutManager.test.js.map +1 -0
- package/dist/__tests__/MasonryFlashList.test.js.map +1 -1
- package/dist/__tests__/MasonryLayoutManager.test.d.ts +2 -0
- package/dist/__tests__/MasonryLayoutManager.test.d.ts.map +1 -0
- package/dist/__tests__/MasonryLayoutManager.test.js +148 -0
- package/dist/__tests__/MasonryLayoutManager.test.js.map +1 -0
- package/dist/__tests__/RecyclerView.test.d.ts +2 -0
- package/dist/__tests__/RecyclerView.test.d.ts.map +1 -0
- package/dist/__tests__/RecyclerView.test.js +103 -0
- package/dist/__tests__/RecyclerView.test.js.map +1 -0
- package/dist/__tests__/RenderStackManager.test.d.ts +2 -0
- package/dist/__tests__/RenderStackManager.test.d.ts.map +1 -0
- package/dist/__tests__/RenderStackManager.test.js +485 -0
- package/dist/__tests__/RenderStackManager.test.js.map +1 -0
- package/dist/__tests__/ViewabilityHelper.test.js.map +1 -1
- package/dist/__tests__/findVisibleIndex.test.d.ts +2 -0
- package/dist/__tests__/findVisibleIndex.test.d.ts.map +1 -0
- package/dist/__tests__/findVisibleIndex.test.js +259 -0
- package/dist/__tests__/findVisibleIndex.test.js.map +1 -0
- package/dist/__tests__/helpers/createLayoutManager.d.ts +34 -0
- package/dist/__tests__/helpers/createLayoutManager.d.ts.map +1 -0
- package/dist/__tests__/helpers/createLayoutManager.js +110 -0
- package/dist/__tests__/helpers/createLayoutManager.js.map +1 -0
- package/dist/__tests__/helpers/mountFlashList.d.ts +2 -2
- package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -1
- package/dist/__tests__/helpers/mountFlashList.js +2 -2
- package/dist/__tests__/helpers/mountFlashList.js.map +1 -1
- package/dist/__tests__/helpers/mountMasonryFlashList.d.ts +2 -2
- package/dist/__tests__/helpers/mountMasonryFlashList.d.ts.map +1 -1
- package/dist/__tests__/helpers/mountMasonryFlashList.js +2 -2
- package/dist/__tests__/helpers/mountMasonryFlashList.js.map +1 -1
- package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -1
- package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts +2 -0
- package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts.map +1 -0
- package/dist/__tests__/useUnmountAwareCallbacks.test.js +185 -0
- package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +1 -0
- package/dist/benchmark/AutoScrollHelper.js +2 -2
- package/dist/benchmark/AutoScrollHelper.js.map +1 -1
- package/dist/benchmark/JSFPSMonitor.js.map +1 -1
- package/dist/benchmark/roundToDecimalPlaces.js +1 -2
- package/dist/benchmark/roundToDecimalPlaces.js.map +1 -1
- package/dist/benchmark/useBenchmark.js +2 -3
- package/dist/benchmark/useBenchmark.js.map +1 -1
- package/dist/benchmark/useBlankAreaTracker.js +1 -2
- package/dist/benchmark/useBlankAreaTracker.js.map +1 -1
- package/dist/benchmark/useDataMultiplier.js +1 -2
- package/dist/benchmark/useDataMultiplier.js.map +1 -1
- package/dist/benchmark/useFlatListBenchmark.d.ts +0 -1
- package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -1
- package/dist/benchmark/useFlatListBenchmark.js +9 -9
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
- package/dist/enableNewCore.d.ts.map +1 -1
- package/dist/enableNewCore.js +4 -4
- package/dist/enableNewCore.js.map +1 -1
- package/dist/errors/CustomError.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/native/auto-layout/AutoLayoutView.d.ts +1 -1
- package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -1
- package/dist/native/auto-layout/AutoLayoutView.js +1 -1
- package/dist/native/auto-layout/AutoLayoutView.js.map +1 -1
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -1
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +1 -1
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.android.d.ts +2 -0
- package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.android.js +2 -0
- package/dist/native/config/PlatformHelper.android.js.map +1 -1
- package/dist/native/config/PlatformHelper.d.ts +2 -0
- package/dist/native/config/PlatformHelper.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.ios.d.ts +2 -0
- package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.ios.js +2 -0
- package/dist/native/config/PlatformHelper.ios.js.map +1 -1
- package/dist/native/config/PlatformHelper.js +2 -0
- package/dist/native/config/PlatformHelper.js.map +1 -1
- package/dist/native/config/PlatformHelper.web.d.ts +2 -0
- package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.web.js +3 -1
- package/dist/native/config/PlatformHelper.web.js.map +1 -1
- package/dist/recyclerview/RecyclerView.d.ts +3 -2
- package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerView.js +133 -69
- package/dist/recyclerview/RecyclerView.js.map +1 -1
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts +41 -7
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerViewContextProvider.js +6 -2
- package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
- package/dist/recyclerview/RecyclerViewManager.d.ts +27 -6
- package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerViewManager.js +135 -106
- package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
- package/dist/recyclerview/RecyclerViewProps.d.ts +1 -1
- package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -1
- package/dist/recyclerview/RenderStackManager.d.ts +86 -0
- package/dist/recyclerview/RenderStackManager.d.ts.map +1 -0
- package/dist/recyclerview/RenderStackManager.js +343 -0
- package/dist/recyclerview/RenderStackManager.js.map +1 -0
- package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
- package/dist/recyclerview/ViewHolder.js +8 -6
- package/dist/recyclerview/ViewHolder.js.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.d.ts +10 -4
- package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.js +26 -10
- package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
- package/dist/recyclerview/components/ScrollAnchor.d.ts +2 -1
- package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
- package/dist/recyclerview/components/ScrollAnchor.js +12 -9
- package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.d.ts +2 -2
- package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.js +44 -45
- package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +1 -1
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -1
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js +2 -2
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +48 -2
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js +89 -19
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -1
- package/dist/recyclerview/helpers/RenderTimeTracker.d.ts +11 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.d.ts.map +1 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.js +42 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.js.map +1 -0
- package/dist/recyclerview/helpers/VelocityTracker.d.ts +29 -0
- package/dist/recyclerview/helpers/VelocityTracker.d.ts.map +1 -0
- package/dist/recyclerview/helpers/VelocityTracker.js +70 -0
- package/dist/recyclerview/helpers/VelocityTracker.js.map +1 -0
- package/dist/recyclerview/hooks/useBoundDetection.d.ts +1 -3
- package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useBoundDetection.js +60 -28
- package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
- package/dist/recyclerview/hooks/useLayoutState.d.ts +3 -1
- package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useLayoutState.js +6 -5
- package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.d.ts +9 -0
- package/dist/recyclerview/hooks/useMappingHelper.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useMappingHelper.js +19 -0
- package/dist/recyclerview/hooks/useMappingHelper.js.map +1 -0
- package/dist/recyclerview/hooks/useOnLoad.d.ts +2 -2
- package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useOnLoad.js +9 -10
- package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +5 -49
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewController.js +340 -191
- package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +2 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewManager.js +11 -1
- package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclingState.d.ts +4 -2
- package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useRecyclingState.js +3 -4
- package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.js +15 -12
- package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -1
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +15 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +63 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts +0 -1
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useUnmountFlag.js +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -1
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +18 -4
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
- package/dist/recyclerview/layout-managers/GridLayoutManager.js +61 -25
- package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts +36 -21
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
- package/dist/recyclerview/layout-managers/LayoutManager.js +96 -28
- package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +1 -2
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -1
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js +3 -3
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +9 -1
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -1
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +30 -16
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
- package/dist/recyclerview/utils/adjustOffsetForRTL.js +1 -2
- package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -1
- package/dist/recyclerview/utils/componentUtils.d.ts +1 -1
- package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -1
- package/dist/recyclerview/utils/componentUtils.js.map +1 -1
- package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -1
- package/dist/recyclerview/utils/findVisibleIndex.js +3 -5
- package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -1
- package/dist/recyclerview/utils/measureLayout.d.ts +24 -28
- package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -1
- package/dist/recyclerview/utils/measureLayout.js +36 -6
- package/dist/recyclerview/utils/measureLayout.js.map +1 -1
- package/dist/recyclerview/utils/measureLayout.web.d.ts +29 -0
- package/dist/recyclerview/utils/measureLayout.web.d.ts.map +1 -0
- package/dist/recyclerview/utils/measureLayout.web.js +87 -0
- package/dist/recyclerview/utils/measureLayout.web.js.map +1 -0
- package/dist/specs/AutoLayoutNativeComponent.d.ts +1 -2
- package/dist/specs/AutoLayoutNativeComponent.d.ts.map +1 -1
- package/dist/specs/CellContainerNativeComponent.d.ts +0 -1
- package/dist/specs/CellContainerNativeComponent.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/AverageWindow.js.map +1 -1
- package/dist/utils/ContentContainerUtils.d.ts.map +1 -1
- package/dist/utils/ContentContainerUtils.js.map +1 -1
- package/dist/viewability/ViewToken.d.ts +2 -2
- package/dist/viewability/ViewToken.d.ts.map +1 -1
- package/dist/viewability/ViewabilityHelper.js +1 -1
- package/dist/viewability/ViewabilityHelper.js.map +1 -1
- package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
- package/dist/viewability/ViewabilityManager.js +11 -5
- package/dist/viewability/ViewabilityManager.js.map +1 -1
- package/jestSetup.js +30 -11
- package/package.json +4 -3
- package/src/AnimatedFlashList.ts +3 -2
- package/src/FlashList.tsx +25 -1
- package/src/FlashListProps.ts +42 -11
- package/src/FlashListRef.ts +320 -0
- package/src/MasonryFlashList.tsx +2 -2
- package/src/__tests__/ConsecutiveNumbers.test.ts +232 -0
- package/src/__tests__/GridLayoutManager.test.ts +113 -0
- package/src/__tests__/LinearLayoutManager.test.ts +227 -0
- package/src/__tests__/MasonryLayoutManager.test.ts +202 -0
- package/src/__tests__/RecyclerView.test.tsx +144 -0
- package/src/__tests__/RenderStackManager.test.ts +574 -0
- package/src/__tests__/findVisibleIndex.test.ts +369 -0
- package/src/__tests__/helpers/createLayoutManager.ts +141 -0
- package/src/__tests__/useUnmountAwareCallbacks.test.tsx +285 -0
- package/src/benchmark/useFlatListBenchmark.ts +2 -2
- package/src/enableNewCore.ts +3 -1
- package/src/index.ts +10 -3
- package/src/native/config/PlatformHelper.android.ts +2 -0
- package/src/native/config/PlatformHelper.ios.ts +2 -0
- package/src/native/config/PlatformHelper.ts +2 -0
- package/src/native/config/PlatformHelper.web.ts +3 -1
- package/src/recyclerview/RecyclerView.tsx +178 -89
- package/src/recyclerview/RecyclerViewContextProvider.ts +53 -7
- package/src/recyclerview/RecyclerViewManager.ts +157 -87
- package/src/recyclerview/RecyclerViewProps.ts +2 -1
- package/src/recyclerview/RenderStackManager.ts +317 -0
- package/src/recyclerview/ViewHolder.tsx +13 -6
- package/src/recyclerview/ViewHolderCollection.tsx +45 -16
- package/src/recyclerview/components/ScrollAnchor.tsx +24 -11
- package/src/recyclerview/components/StickyHeaders.tsx +70 -58
- package/src/recyclerview/helpers/ConsecutiveNumbers.ts +2 -2
- package/src/recyclerview/helpers/EngagedIndicesTracker.ts +135 -25
- package/src/recyclerview/helpers/RenderTimeTracker.ts +42 -0
- package/src/recyclerview/helpers/VelocityTracker.ts +77 -0
- package/src/recyclerview/hooks/useBoundDetection.ts +74 -25
- package/src/recyclerview/hooks/useLayoutState.ts +15 -6
- package/src/recyclerview/hooks/useMappingHelper.ts +20 -0
- package/src/recyclerview/hooks/useOnLoad.ts +11 -10
- package/src/recyclerview/hooks/useRecyclerViewController.tsx +375 -237
- package/src/recyclerview/hooks/useRecyclerViewManager.ts +13 -1
- package/src/recyclerview/hooks/useRecyclingState.ts +11 -7
- package/src/recyclerview/hooks/useSecondaryProps.tsx +12 -7
- package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +73 -0
- package/src/recyclerview/hooks/useUnmountFlag.ts +1 -0
- package/src/recyclerview/layout-managers/GridLayoutManager.ts +68 -27
- package/src/recyclerview/layout-managers/LayoutManager.ts +116 -42
- package/src/recyclerview/layout-managers/LinearLayoutManager.ts +12 -8
- package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +34 -13
- package/src/recyclerview/utils/componentUtils.ts +1 -1
- package/src/recyclerview/utils/findVisibleIndex.ts +1 -2
- package/src/recyclerview/utils/measureLayout.ts +41 -2
- package/src/recyclerview/utils/measureLayout.web.ts +102 -0
- package/src/viewability/ViewToken.ts +2 -2
- package/src/viewability/ViewabilityHelper.ts +1 -1
- package/src/viewability/ViewabilityManager.ts +16 -9
- package/dist/recyclerview/RecycleKeyManager.d.ts +0 -82
- package/dist/recyclerview/RecycleKeyManager.d.ts.map +0 -1
- package/dist/recyclerview/RecycleKeyManager.js +0 -135
- package/dist/recyclerview/RecycleKeyManager.js.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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<
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
144
|
-
*
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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 =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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).
|
|
189
|
-
|
|
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 (
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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:
|
|
309
|
+
scrollToIndex: ({
|
|
294
310
|
index,
|
|
295
311
|
animated,
|
|
296
312
|
viewPosition,
|
|
297
313
|
viewOffset,
|
|
298
|
-
}: ScrollToIndexParams) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
lastScrollOffset
|
|
359
|
-
);
|
|
437
|
+
// Continue to next step
|
|
438
|
+
performScrollStep(currentStep + 1);
|
|
360
439
|
}
|
|
440
|
+
});
|
|
441
|
+
};
|
|
361
442
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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 (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
//
|
|
465
|
+
// Perform the final scroll to the target position
|
|
395
466
|
handlerMethods.scrollToOffset({
|
|
396
|
-
offset:
|
|
397
|
-
animated
|
|
398
|
-
skipFirstItemOffset:
|
|
467
|
+
offset: finalOffset,
|
|
468
|
+
animated,
|
|
469
|
+
skipFirstItemOffset: true,
|
|
399
470
|
});
|
|
400
|
-
}
|
|
401
471
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
460
|
-
return recyclerViewManager.
|
|
537
|
+
computeVisibleIndices: () => {
|
|
538
|
+
return recyclerViewManager.computeVisibleIndices();
|
|
461
539
|
},
|
|
462
540
|
getFirstVisibleIndex: () => {
|
|
463
|
-
return recyclerViewManager.
|
|
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.
|
|
550
|
+
recyclerViewManager.animationOptimizationsEnabled = true;
|
|
473
551
|
},
|
|
474
552
|
};
|
|
475
|
-
}, [
|
|
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
|
-
|
|
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 {
|
|
619
|
+
return {
|
|
620
|
+
applyOffsetCorrection,
|
|
621
|
+
computeFirstVisibleIndexForOffsetCorrection,
|
|
622
|
+
applyInitialScrollIndex,
|
|
623
|
+
handlerMethods,
|
|
624
|
+
};
|
|
487
625
|
}
|