@shopify/flash-list 1.8.0 → 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -27
- package/dist/AnimatedFlashList.d.ts +0 -1
- package/dist/AnimatedFlashList.d.ts.map +1 -1
- package/dist/FlashList.js +2 -3
- package/dist/FlashList.js.map +1 -1
- package/dist/FlashListProps.d.ts +68 -5
- package/dist/FlashListProps.d.ts.map +1 -1
- 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 +35 -0
- 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__/RecycleKeyManager.test.d.ts +2 -0
- package/dist/__tests__/RecycleKeyManager.test.d.ts.map +1 -0
- package/dist/__tests__/RecycleKeyManager.test.js +210 -0
- package/dist/__tests__/RecycleKeyManager.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 +59 -0
- package/dist/__tests__/RecyclerView.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 +111 -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 +1 -2
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
- package/dist/enableNewCore.d.ts +3 -0
- package/dist/enableNewCore.d.ts.map +1 -0
- package/dist/enableNewCore.js +25 -0
- package/dist/enableNewCore.js.map +1 -0
- package/dist/errors/CustomError.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -8
- 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/RecycleKeyManager.d.ts +82 -0
- package/dist/recyclerview/RecycleKeyManager.d.ts.map +1 -0
- package/dist/recyclerview/RecycleKeyManager.js +135 -0
- package/dist/recyclerview/RecycleKeyManager.js.map +1 -0
- package/dist/recyclerview/RecyclerView.d.ts +12 -0
- package/dist/recyclerview/RecyclerView.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerView.js +300 -0
- package/dist/recyclerview/RecyclerView.js.map +1 -0
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts +11 -0
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewContextProvider.js +11 -0
- package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -0
- package/dist/recyclerview/RecyclerViewManager.d.ts +62 -0
- package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewManager.js +371 -0
- package/dist/recyclerview/RecyclerViewManager.js.map +1 -0
- package/dist/recyclerview/RecyclerViewProps.d.ts +9 -0
- package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewProps.js +3 -0
- package/dist/recyclerview/RecyclerViewProps.js.map +1 -0
- package/dist/recyclerview/ViewHolder.d.ts +45 -0
- package/dist/recyclerview/ViewHolder.d.ts.map +1 -0
- package/dist/recyclerview/ViewHolder.js +96 -0
- package/dist/recyclerview/ViewHolder.js.map +1 -0
- package/dist/recyclerview/ViewHolderCollection.d.ts +57 -0
- package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -0
- package/dist/recyclerview/ViewHolderCollection.js +74 -0
- package/dist/recyclerview/ViewHolderCollection.js.map +1 -0
- package/dist/recyclerview/components/CompatScroller.d.ts +7 -0
- package/dist/recyclerview/components/CompatScroller.d.ts.map +1 -0
- package/dist/recyclerview/components/CompatScroller.js +8 -0
- package/dist/recyclerview/components/CompatScroller.js.map +1 -0
- package/dist/recyclerview/components/CompatView.d.ts +7 -0
- package/dist/recyclerview/components/CompatView.d.ts.map +1 -0
- package/dist/recyclerview/components/CompatView.js +8 -0
- package/dist/recyclerview/components/CompatView.js.map +1 -0
- package/dist/recyclerview/components/ScrollAnchor.d.ts +29 -0
- package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -0
- package/dist/recyclerview/components/ScrollAnchor.js +34 -0
- package/dist/recyclerview/components/ScrollAnchor.js.map +1 -0
- package/dist/recyclerview/components/StickyHeaders.d.ts +38 -0
- package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -0
- package/dist/recyclerview/components/StickyHeaders.js +111 -0
- package/dist/recyclerview/components/StickyHeaders.js.map +1 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +51 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js +122 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +105 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js +209 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.d.ts +10 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.d.ts.map +1 -0
- package/dist/recyclerview/helpers/RenderTimeTracker.js +39 -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 +18 -0
- package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useBoundDetection.js +101 -0
- package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -0
- package/dist/recyclerview/hooks/useLayoutState.d.ts +12 -0
- package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useLayoutState.js +42 -0
- package/dist/recyclerview/hooks/useLayoutState.js.map +1 -0
- 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 +25 -0
- package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useOnLoad.js +74 -0
- package/dist/recyclerview/hooks/useOnLoad.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +72 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.js +424 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +8 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.js +30 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclingState.d.ts +16 -0
- package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclingState.js +53 -0
- package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -0
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts +27 -0
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useSecondaryProps.js +96 -0
- package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +8 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +34 -0
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts +10 -0
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.js +28 -0
- package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +65 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.js +201 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts +282 -0
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/LayoutManager.js +254 -0
- package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +51 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js +191 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +73 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +272 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts +12 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts.map +1 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.js +17 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -0
- package/dist/recyclerview/utils/componentUtils.d.ts +19 -0
- package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -0
- package/dist/recyclerview/utils/componentUtils.js +32 -0
- package/dist/recyclerview/utils/componentUtils.js.map +1 -0
- package/dist/recyclerview/utils/findVisibleIndex.d.ts +24 -0
- package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -0
- package/dist/recyclerview/utils/findVisibleIndex.js +80 -0
- package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -0
- package/dist/recyclerview/utils/measureLayout.d.ts +52 -0
- package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -0
- package/dist/recyclerview/utils/measureLayout.js +107 -0
- package/dist/recyclerview/utils/measureLayout.js.map +1 -0
- 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 +89 -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.d.ts +13 -0
- package/dist/utils/AverageWindow.d.ts.map +1 -1
- package/dist/utils/AverageWindow.js +30 -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/ViewabilityHelper.js.map +1 -1
- package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
- package/dist/viewability/ViewabilityManager.js.map +1 -1
- package/package.json +4 -3
- package/src/FlashList.tsx +1 -1
- package/src/FlashListProps.ts +73 -2
- package/src/__tests__/AverageWindow.test.ts +49 -1
- 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__/RecycleKeyManager.test.ts +254 -0
- package/src/__tests__/RecyclerView.test.tsx +69 -0
- package/src/__tests__/findVisibleIndex.test.ts +369 -0
- package/src/__tests__/helpers/createLayoutManager.ts +142 -0
- package/src/__tests__/useUnmountAwareCallbacks.test.tsx +285 -0
- package/src/enableNewCore.ts +24 -0
- package/src/index.ts +27 -0
- 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/RecycleKeyManager.ts +185 -0
- package/src/recyclerview/RecyclerView.tsx +535 -0
- package/src/recyclerview/RecyclerViewContextProvider.ts +20 -0
- package/src/recyclerview/RecyclerViewManager.ts +434 -0
- package/src/recyclerview/RecyclerViewProps.ts +11 -0
- package/src/recyclerview/ViewHolder.tsx +178 -0
- package/src/recyclerview/ViewHolderCollection.tsx +165 -0
- package/src/recyclerview/components/CompatScroller.ts +9 -0
- package/src/recyclerview/components/CompatView.ts +9 -0
- package/src/recyclerview/components/ScrollAnchor.tsx +54 -0
- package/src/recyclerview/components/StickyHeaders.tsx +204 -0
- package/src/recyclerview/helpers/ConsecutiveNumbers.ts +120 -0
- package/src/recyclerview/helpers/EngagedIndicesTracker.ts +302 -0
- package/src/recyclerview/helpers/RenderTimeTracker.ts +38 -0
- package/src/recyclerview/helpers/VelocityTracker.ts +77 -0
- package/src/recyclerview/hooks/useBoundDetection.ts +127 -0
- package/src/recyclerview/hooks/useLayoutState.ts +46 -0
- package/src/recyclerview/hooks/useMappingHelper.ts +20 -0
- package/src/recyclerview/hooks/useOnLoad.ts +81 -0
- package/src/recyclerview/hooks/useRecyclerViewController.tsx +546 -0
- package/src/recyclerview/hooks/useRecyclerViewManager.ts +34 -0
- package/src/recyclerview/hooks/useRecyclingState.ts +63 -0
- package/src/recyclerview/hooks/useSecondaryProps.tsx +124 -0
- package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +37 -0
- package/src/recyclerview/hooks/useUnmountFlag.ts +26 -0
- package/src/recyclerview/layout-managers/GridLayoutManager.ts +212 -0
- package/src/recyclerview/layout-managers/LayoutManager.ts +498 -0
- package/src/recyclerview/layout-managers/LinearLayoutManager.ts +171 -0
- package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +301 -0
- package/src/recyclerview/utils/adjustOffsetForRTL.ts +17 -0
- package/src/recyclerview/utils/componentUtils.ts +28 -0
- package/src/recyclerview/utils/findVisibleIndex.ts +93 -0
- package/src/recyclerview/utils/measureLayout.ts +128 -0
- package/src/recyclerview/utils/measureLayout.web.ts +104 -0
- package/src/utils/AverageWindow.ts +33 -0
- package/src/viewability/ViewToken.ts +1 -1
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RefObject,
|
|
3
|
+
useCallback,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { I18nManager } from "react-native";
|
|
10
|
+
|
|
11
|
+
import { RecyclerViewProps } from "../RecyclerViewProps";
|
|
12
|
+
import { CompatScroller } from "../components/CompatScroller";
|
|
13
|
+
import { RecyclerViewManager } from "../RecyclerViewManager";
|
|
14
|
+
import { adjustOffsetForRTL } from "../utils/adjustOffsetForRTL";
|
|
15
|
+
import { RVLayout } from "../layout-managers/LayoutManager";
|
|
16
|
+
import { ScrollAnchorRef } from "../components/ScrollAnchor";
|
|
17
|
+
import { PlatformConfig } from "../../native/config/PlatformHelper";
|
|
18
|
+
|
|
19
|
+
import { useUnmountFlag } from "./useUnmountFlag";
|
|
20
|
+
import { useUnmountAwareCallbacks } from "./useUnmountAwareCallbacks";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parameters for scrolling to a specific position in the list.
|
|
24
|
+
* Extends ScrollToEdgeParams to include view positioning options.
|
|
25
|
+
*/
|
|
26
|
+
export interface ScrollToParams extends ScrollToEdgeParams {
|
|
27
|
+
/** Position of the target item relative to the viewport (0 = top, 0.5 = center, 1 = bottom) */
|
|
28
|
+
viewPosition?: number;
|
|
29
|
+
/** Additional offset to apply after viewPosition calculation */
|
|
30
|
+
viewOffset?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parameters for scrolling to a specific offset in the list.
|
|
35
|
+
* Used when you want to scroll to an exact pixel position.
|
|
36
|
+
*/
|
|
37
|
+
export interface ScrollToOffsetParams extends ScrollToParams {
|
|
38
|
+
/** The pixel offset to scroll to */
|
|
39
|
+
offset: number;
|
|
40
|
+
/**
|
|
41
|
+
* If true, the first item offset will not be added to the offset calculation.
|
|
42
|
+
* First offset represents header size or top padding.
|
|
43
|
+
*/
|
|
44
|
+
skipFirstItemOffset?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parameters for scrolling to a specific index in the list.
|
|
49
|
+
* Used when you want to scroll to a specific item by its position in the data array.
|
|
50
|
+
*/
|
|
51
|
+
export interface ScrollToIndexParams extends ScrollToParams {
|
|
52
|
+
/** The index of the item to scroll to */
|
|
53
|
+
index: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parameters for scrolling to a specific item in the list.
|
|
58
|
+
* Used when you want to scroll to a specific item by its data value.
|
|
59
|
+
*/
|
|
60
|
+
export interface ScrollToItemParams<T> extends ScrollToParams {
|
|
61
|
+
/** The item to scroll to */
|
|
62
|
+
item: T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Base parameters for scrolling to the edges of the list.
|
|
67
|
+
*/
|
|
68
|
+
export interface ScrollToEdgeParams {
|
|
69
|
+
/** Whether the scroll should be animated */
|
|
70
|
+
animated?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Comprehensive hook that manages RecyclerView scrolling behavior and provides
|
|
75
|
+
* imperative methods for controlling the RecyclerView.
|
|
76
|
+
*
|
|
77
|
+
* This hook combines content offset management and scroll handling functionality:
|
|
78
|
+
* 1. Provides imperative methods for scrolling and measurement
|
|
79
|
+
* 2. Handles initial scroll position when the list first loads
|
|
80
|
+
* 3. Maintains visible content position during updates
|
|
81
|
+
* 4. Manages scroll anchors for chat-like applications
|
|
82
|
+
*
|
|
83
|
+
* @param recyclerViewManager - The RecyclerViewManager instance that handles core functionality
|
|
84
|
+
* @param ref - The ref to expose the imperative methods
|
|
85
|
+
* @param scrollViewRef - Reference to the scrollable container component
|
|
86
|
+
* @param scrollAnchorRef - Reference to the scroll anchor component
|
|
87
|
+
* @param props - The RecyclerViewProps containing configuration
|
|
88
|
+
*/
|
|
89
|
+
export function useRecyclerViewController<T>(
|
|
90
|
+
recyclerViewManager: RecyclerViewManager<T>,
|
|
91
|
+
ref: React.Ref<any>,
|
|
92
|
+
scrollViewRef: RefObject<CompatScroller>,
|
|
93
|
+
scrollAnchorRef: React.RefObject<ScrollAnchorRef>,
|
|
94
|
+
props: RecyclerViewProps<T>
|
|
95
|
+
) {
|
|
96
|
+
const { horizontal, data } = props;
|
|
97
|
+
const isUnmounted = useUnmountFlag();
|
|
98
|
+
const [_, setRenderId] = useState(0);
|
|
99
|
+
const pauseOffsetCorrection = useRef(false);
|
|
100
|
+
const initialScrollCompletedRef = useRef(false);
|
|
101
|
+
const lastDataLengthRef = useRef(data?.length ?? 0);
|
|
102
|
+
const { setTimeout } = useUnmountAwareCallbacks();
|
|
103
|
+
|
|
104
|
+
// Track the first visible item for maintaining scroll position
|
|
105
|
+
const firstVisibleItemKey = useRef<string | undefined>(undefined);
|
|
106
|
+
const firstVisibleItemLayout = useRef<RVLayout | undefined>(undefined);
|
|
107
|
+
const pendingScrollResolves = useRef<(() => void)[]>([]);
|
|
108
|
+
|
|
109
|
+
const applyInitialScrollIndex = useCallback(() => {
|
|
110
|
+
const initialScrollIndex =
|
|
111
|
+
recyclerViewManager.getInitialScrollIndex() ?? -1;
|
|
112
|
+
const dataLength = props.data?.length ?? 0;
|
|
113
|
+
if (
|
|
114
|
+
initialScrollIndex >= 0 &&
|
|
115
|
+
initialScrollIndex < dataLength &&
|
|
116
|
+
!initialScrollCompletedRef.current &&
|
|
117
|
+
recyclerViewManager.getIsFirstLayoutComplete()
|
|
118
|
+
) {
|
|
119
|
+
// Use setTimeout to ensure that we keep trying to scroll on first few renders
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
initialScrollCompletedRef.current = true;
|
|
122
|
+
pauseOffsetCorrection.current = false;
|
|
123
|
+
}, 100);
|
|
124
|
+
|
|
125
|
+
pauseOffsetCorrection.current = true;
|
|
126
|
+
|
|
127
|
+
const offset = horizontal
|
|
128
|
+
? recyclerViewManager.getLayout(initialScrollIndex).x
|
|
129
|
+
: recyclerViewManager.getLayout(initialScrollIndex).y;
|
|
130
|
+
handlerMethods.scrollToOffset({
|
|
131
|
+
offset,
|
|
132
|
+
animated: false,
|
|
133
|
+
skipFirstItemOffset: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
handlerMethods.scrollToOffset({
|
|
138
|
+
offset,
|
|
139
|
+
animated: false,
|
|
140
|
+
skipFirstItemOffset: false,
|
|
141
|
+
});
|
|
142
|
+
}, 0);
|
|
143
|
+
}
|
|
144
|
+
}, [recyclerViewManager, props.data]);
|
|
145
|
+
|
|
146
|
+
// Handle initial scroll position when the list first loads
|
|
147
|
+
// useOnLoad(recyclerViewManager, () => {
|
|
148
|
+
|
|
149
|
+
// });
|
|
150
|
+
/**
|
|
151
|
+
* Updates the scroll offset and returns a Promise that resolves
|
|
152
|
+
* when the update has been applied.
|
|
153
|
+
*/
|
|
154
|
+
const updateScrollOffsetAsync = useCallback(
|
|
155
|
+
async (offset: number): Promise<void> => {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
// TODO: Make sure we don't scroll beyond content size
|
|
158
|
+
if (recyclerViewManager.updateScrollOffset(offset) !== undefined) {
|
|
159
|
+
// Add the resolve function to the queue
|
|
160
|
+
pendingScrollResolves.current.push(resolve);
|
|
161
|
+
setRenderId((prev) => prev + 1);
|
|
162
|
+
} else {
|
|
163
|
+
resolve();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
[recyclerViewManager]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Maintains the visible content position when the list updates.
|
|
172
|
+
* This is particularly useful for chat applications where we want to keep
|
|
173
|
+
* the user's current view position when new messages are added.
|
|
174
|
+
*/
|
|
175
|
+
const applyContentOffset = useCallback(async () => {
|
|
176
|
+
// Resolve all pending scroll updates from previous calls
|
|
177
|
+
const resolves = pendingScrollResolves.current;
|
|
178
|
+
pendingScrollResolves.current = [];
|
|
179
|
+
resolves.forEach((resolve) => resolve());
|
|
180
|
+
|
|
181
|
+
const currentDataLength = props.data?.length ?? 0;
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
!props.horizontal &&
|
|
185
|
+
recyclerViewManager.getIsFirstLayoutComplete() &&
|
|
186
|
+
props.keyExtractor &&
|
|
187
|
+
currentDataLength > 0 &&
|
|
188
|
+
props.maintainVisibleContentPosition?.disabled !== true
|
|
189
|
+
) {
|
|
190
|
+
const hasDataChanged = currentDataLength !== lastDataLengthRef.current;
|
|
191
|
+
// If we have a tracked first visible item, maintain its position
|
|
192
|
+
if (firstVisibleItemKey.current) {
|
|
193
|
+
const currentIndexOfFirstVisibleItem =
|
|
194
|
+
recyclerViewManager
|
|
195
|
+
.getEngagedIndices()
|
|
196
|
+
.findValue(
|
|
197
|
+
(index) =>
|
|
198
|
+
props.keyExtractor?.(props.data![index], index) ===
|
|
199
|
+
firstVisibleItemKey.current
|
|
200
|
+
) ??
|
|
201
|
+
(hasDataChanged
|
|
202
|
+
? props.data?.findIndex(
|
|
203
|
+
(item, index) =>
|
|
204
|
+
props.keyExtractor?.(item, index) ===
|
|
205
|
+
firstVisibleItemKey.current
|
|
206
|
+
)
|
|
207
|
+
: undefined);
|
|
208
|
+
|
|
209
|
+
if (currentIndexOfFirstVisibleItem !== undefined) {
|
|
210
|
+
// Calculate the difference in position and apply the offset
|
|
211
|
+
const diff =
|
|
212
|
+
recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).y -
|
|
213
|
+
firstVisibleItemLayout.current!.y;
|
|
214
|
+
firstVisibleItemLayout.current = {
|
|
215
|
+
...recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem),
|
|
216
|
+
};
|
|
217
|
+
if (diff !== 0 && !pauseOffsetCorrection.current) {
|
|
218
|
+
// console.log("diff", diff, firstVisibleItemKey.current);
|
|
219
|
+
if (PlatformConfig.supportsOffsetCorrection) {
|
|
220
|
+
scrollAnchorRef.current?.scrollBy(diff);
|
|
221
|
+
} else {
|
|
222
|
+
scrollViewRef.current?.scrollTo({
|
|
223
|
+
y: recyclerViewManager.getAbsoluteLastScrollOffset() + diff,
|
|
224
|
+
animated: false,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (hasDataChanged) {
|
|
228
|
+
updateScrollOffsetAsync(
|
|
229
|
+
recyclerViewManager.getAbsoluteLastScrollOffset() + diff
|
|
230
|
+
);
|
|
231
|
+
recyclerViewManager.ignoreScrollEvents = true;
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
recyclerViewManager.ignoreScrollEvents = false;
|
|
234
|
+
}, 100);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Update the tracked first visible item
|
|
241
|
+
const firstVisibleIndex = Math.max(
|
|
242
|
+
0,
|
|
243
|
+
recyclerViewManager.getVisibleIndices().startIndex
|
|
244
|
+
);
|
|
245
|
+
if (firstVisibleIndex !== undefined && firstVisibleIndex >= 0) {
|
|
246
|
+
firstVisibleItemKey.current = props.keyExtractor(
|
|
247
|
+
props.data![firstVisibleIndex],
|
|
248
|
+
firstVisibleIndex
|
|
249
|
+
);
|
|
250
|
+
firstVisibleItemLayout.current = {
|
|
251
|
+
...recyclerViewManager.getLayout(firstVisibleIndex),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
lastDataLengthRef.current = props.data?.length ?? 0;
|
|
256
|
+
}, [props.data, props.keyExtractor, recyclerViewManager, setTimeout]);
|
|
257
|
+
|
|
258
|
+
const handlerMethods = useMemo(() => {
|
|
259
|
+
return {
|
|
260
|
+
props,
|
|
261
|
+
/**
|
|
262
|
+
* Scrolls the list to a specific offset position.
|
|
263
|
+
* Handles RTL layouts and first item offset adjustments.
|
|
264
|
+
*/
|
|
265
|
+
scrollToOffset: ({
|
|
266
|
+
offset,
|
|
267
|
+
animated,
|
|
268
|
+
skipFirstItemOffset = true,
|
|
269
|
+
}: ScrollToOffsetParams) => {
|
|
270
|
+
if (scrollViewRef.current) {
|
|
271
|
+
// Adjust offset for RTL layouts in horizontal mode
|
|
272
|
+
if (I18nManager.isRTL && horizontal) {
|
|
273
|
+
// eslint-disable-next-line no-param-reassign
|
|
274
|
+
offset =
|
|
275
|
+
adjustOffsetForRTL(
|
|
276
|
+
offset,
|
|
277
|
+
recyclerViewManager.getChildContainerDimensions().width,
|
|
278
|
+
recyclerViewManager.getWindowSize().width
|
|
279
|
+
) +
|
|
280
|
+
(skipFirstItemOffset
|
|
281
|
+
? recyclerViewManager.firstItemOffset
|
|
282
|
+
: -recyclerViewManager.firstItemOffset);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Calculate the final offset including first item offset if needed
|
|
286
|
+
const adjustedOffset =
|
|
287
|
+
offset +
|
|
288
|
+
(skipFirstItemOffset ? 0 : recyclerViewManager.firstItemOffset);
|
|
289
|
+
const scrollTo = horizontal
|
|
290
|
+
? { x: adjustedOffset, y: 0 }
|
|
291
|
+
: { x: 0, y: adjustedOffset };
|
|
292
|
+
scrollViewRef.current.scrollTo({
|
|
293
|
+
...scrollTo,
|
|
294
|
+
animated,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Expose native scroll view methods
|
|
300
|
+
flashScrollIndicators: () => {
|
|
301
|
+
scrollViewRef.current!.flashScrollIndicators();
|
|
302
|
+
},
|
|
303
|
+
getNativeScrollRef: () => {
|
|
304
|
+
return scrollViewRef.current;
|
|
305
|
+
},
|
|
306
|
+
getScrollResponder: () => {
|
|
307
|
+
return scrollViewRef.current!.getScrollResponder();
|
|
308
|
+
},
|
|
309
|
+
getScrollableNode: () => {
|
|
310
|
+
return scrollViewRef.current!.getScrollableNode();
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Scrolls to the end of the list.
|
|
315
|
+
*/
|
|
316
|
+
scrollToEnd: async ({ animated }: ScrollToEdgeParams = {}) => {
|
|
317
|
+
if (data && data.length > 0) {
|
|
318
|
+
await handlerMethods.scrollToIndex({
|
|
319
|
+
index: data.length - 1,
|
|
320
|
+
animated,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
scrollViewRef.current!.scrollToEnd({ animated });
|
|
325
|
+
}, 0);
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Scrolls to the beginning of the list.
|
|
330
|
+
*/
|
|
331
|
+
scrollToTop: ({ animated }: ScrollToEdgeParams = {}) => {
|
|
332
|
+
handlerMethods.scrollToOffset({
|
|
333
|
+
offset: 0,
|
|
334
|
+
animated,
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Scrolls to a specific index in the list.
|
|
340
|
+
* Supports viewPosition and viewOffset for precise positioning.
|
|
341
|
+
*/
|
|
342
|
+
scrollToIndex: async ({
|
|
343
|
+
index,
|
|
344
|
+
animated,
|
|
345
|
+
viewPosition,
|
|
346
|
+
viewOffset,
|
|
347
|
+
}: ScrollToIndexParams) => {
|
|
348
|
+
if (
|
|
349
|
+
scrollViewRef.current &&
|
|
350
|
+
data &&
|
|
351
|
+
index >= 0 &&
|
|
352
|
+
index < data.length
|
|
353
|
+
) {
|
|
354
|
+
// Pause the scroll offset adjustments
|
|
355
|
+
pauseOffsetCorrection.current = true;
|
|
356
|
+
recyclerViewManager.setOffsetProjectionEnabled(false);
|
|
357
|
+
|
|
358
|
+
const getFinalOffset = () => {
|
|
359
|
+
const layout = recyclerViewManager.getLayout(index);
|
|
360
|
+
const offset = horizontal ? layout.x : layout.y;
|
|
361
|
+
let finalOffset = offset;
|
|
362
|
+
// take viewPosition etc into account
|
|
363
|
+
if (viewPosition !== undefined || viewOffset !== undefined) {
|
|
364
|
+
const containerSize = horizontal
|
|
365
|
+
? recyclerViewManager.getWindowSize().width
|
|
366
|
+
: recyclerViewManager.getWindowSize().height;
|
|
367
|
+
|
|
368
|
+
const itemSize = horizontal ? layout.width : layout.height;
|
|
369
|
+
|
|
370
|
+
if (viewPosition !== undefined) {
|
|
371
|
+
// viewPosition: 0 = top, 0.5 = center, 1 = bottom
|
|
372
|
+
finalOffset =
|
|
373
|
+
offset - (containerSize - itemSize) * viewPosition;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (viewOffset !== undefined) {
|
|
377
|
+
finalOffset += viewOffset;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return finalOffset + recyclerViewManager.firstItemOffset;
|
|
381
|
+
};
|
|
382
|
+
const lastAbsoluteScrollOffset =
|
|
383
|
+
recyclerViewManager.getAbsoluteLastScrollOffset();
|
|
384
|
+
const bufferForScroll = horizontal
|
|
385
|
+
? recyclerViewManager.getWindowSize().width
|
|
386
|
+
: recyclerViewManager.getWindowSize().height;
|
|
387
|
+
|
|
388
|
+
const bufferForCompute = bufferForScroll * 2;
|
|
389
|
+
|
|
390
|
+
const getStartScrollOffset = () => {
|
|
391
|
+
let lastScrollOffset = lastAbsoluteScrollOffset;
|
|
392
|
+
const finalOffset = getFinalOffset();
|
|
393
|
+
|
|
394
|
+
if (finalOffset > lastScrollOffset) {
|
|
395
|
+
lastScrollOffset = Math.max(
|
|
396
|
+
finalOffset - bufferForCompute,
|
|
397
|
+
lastScrollOffset
|
|
398
|
+
);
|
|
399
|
+
recyclerViewManager.setScrollDirection("forward");
|
|
400
|
+
} else {
|
|
401
|
+
lastScrollOffset = Math.min(
|
|
402
|
+
finalOffset + bufferForCompute,
|
|
403
|
+
lastScrollOffset
|
|
404
|
+
);
|
|
405
|
+
recyclerViewManager.setScrollDirection("backward");
|
|
406
|
+
}
|
|
407
|
+
return lastScrollOffset;
|
|
408
|
+
};
|
|
409
|
+
let initialTargetOffset = getFinalOffset();
|
|
410
|
+
let initialStartScrollOffset = getStartScrollOffset();
|
|
411
|
+
let finalOffset = initialTargetOffset;
|
|
412
|
+
let startScrollOffset = initialStartScrollOffset;
|
|
413
|
+
|
|
414
|
+
const steps = 5;
|
|
415
|
+
// go from finalOffset to startScrollOffset in 5 steps for animated
|
|
416
|
+
// otherwise go from startScrollOffset to finalOffset in 5 steps
|
|
417
|
+
for (let i = 0; i < steps; i++) {
|
|
418
|
+
if (isUnmounted.current) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const nextOffset = animated
|
|
422
|
+
? finalOffset +
|
|
423
|
+
(startScrollOffset - finalOffset) * (i / (steps - 1))
|
|
424
|
+
: startScrollOffset +
|
|
425
|
+
(finalOffset - startScrollOffset) * (i / (steps - 1));
|
|
426
|
+
await updateScrollOffsetAsync(nextOffset);
|
|
427
|
+
const newFinalOffset = getFinalOffset();
|
|
428
|
+
if (
|
|
429
|
+
(newFinalOffset < initialTargetOffset &&
|
|
430
|
+
newFinalOffset < initialStartScrollOffset) ||
|
|
431
|
+
(newFinalOffset > initialTargetOffset &&
|
|
432
|
+
newFinalOffset > initialStartScrollOffset)
|
|
433
|
+
) {
|
|
434
|
+
finalOffset = newFinalOffset;
|
|
435
|
+
startScrollOffset = getStartScrollOffset();
|
|
436
|
+
initialTargetOffset = newFinalOffset;
|
|
437
|
+
initialStartScrollOffset = startScrollOffset;
|
|
438
|
+
i = -1; // Restart compute loop
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
finalOffset = getFinalOffset();
|
|
443
|
+
const maxOffset = recyclerViewManager.getMaxScrollOffset();
|
|
444
|
+
|
|
445
|
+
if (finalOffset > maxOffset) {
|
|
446
|
+
finalOffset = maxOffset;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (animated) {
|
|
450
|
+
// We don't need to add firstItemOffset here as it's already added
|
|
451
|
+
handlerMethods.scrollToOffset({
|
|
452
|
+
offset: startScrollOffset,
|
|
453
|
+
animated: false,
|
|
454
|
+
skipFirstItemOffset: true,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
handlerMethods.scrollToOffset({
|
|
458
|
+
offset: finalOffset,
|
|
459
|
+
animated,
|
|
460
|
+
skipFirstItemOffset: true,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
setTimeout(
|
|
464
|
+
() => {
|
|
465
|
+
pauseOffsetCorrection.current = false;
|
|
466
|
+
recyclerViewManager.setOffsetProjectionEnabled(true);
|
|
467
|
+
},
|
|
468
|
+
animated ? 300 : 200
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Scrolls to a specific item in the list.
|
|
475
|
+
* Finds the item's index and uses scrollToIndex internally.
|
|
476
|
+
*/
|
|
477
|
+
scrollToItem: ({
|
|
478
|
+
item,
|
|
479
|
+
animated,
|
|
480
|
+
viewPosition,
|
|
481
|
+
viewOffset,
|
|
482
|
+
}: ScrollToItemParams<T>) => {
|
|
483
|
+
if (scrollViewRef.current && data) {
|
|
484
|
+
// Find the index of the item in the data array
|
|
485
|
+
const index = Array.from(data).findIndex(
|
|
486
|
+
(dataItem) => dataItem === item
|
|
487
|
+
);
|
|
488
|
+
if (index >= 0) {
|
|
489
|
+
handlerMethods.scrollToIndex({
|
|
490
|
+
index,
|
|
491
|
+
animated,
|
|
492
|
+
viewPosition,
|
|
493
|
+
viewOffset,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
// Utility methods for measuring header height / top padding
|
|
500
|
+
getFirstItemOffset: () => {
|
|
501
|
+
return recyclerViewManager.firstItemOffset;
|
|
502
|
+
},
|
|
503
|
+
getWindowSize: () => {
|
|
504
|
+
return recyclerViewManager.getWindowSize();
|
|
505
|
+
},
|
|
506
|
+
getLayout: (index: number) => {
|
|
507
|
+
return recyclerViewManager.getLayout(index);
|
|
508
|
+
},
|
|
509
|
+
getAbsoluteLastScrollOffset: () => {
|
|
510
|
+
return recyclerViewManager.getAbsoluteLastScrollOffset();
|
|
511
|
+
},
|
|
512
|
+
getChildContainerDimensions: () => {
|
|
513
|
+
return recyclerViewManager.getChildContainerDimensions();
|
|
514
|
+
},
|
|
515
|
+
recordInteraction: () => {
|
|
516
|
+
recyclerViewManager.recordInteraction();
|
|
517
|
+
},
|
|
518
|
+
getVisibleIndices: () => {
|
|
519
|
+
return recyclerViewManager.getVisibleIndices();
|
|
520
|
+
},
|
|
521
|
+
getFirstVisibleIndex: () => {
|
|
522
|
+
return recyclerViewManager.getVisibleIndices().startIndex;
|
|
523
|
+
},
|
|
524
|
+
recomputeViewableItems: () => {
|
|
525
|
+
recyclerViewManager.recomputeViewableItems();
|
|
526
|
+
},
|
|
527
|
+
/**
|
|
528
|
+
* Disables item recycling in preparation for layout animations.
|
|
529
|
+
*/
|
|
530
|
+
prepareForLayoutAnimationRender: () => {
|
|
531
|
+
recyclerViewManager.disableRecycling = true;
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}, [horizontal, data, recyclerViewManager]);
|
|
535
|
+
|
|
536
|
+
// Expose imperative methods through the ref
|
|
537
|
+
useImperativeHandle(
|
|
538
|
+
ref,
|
|
539
|
+
() => {
|
|
540
|
+
return { ...scrollViewRef.current, ...handlerMethods };
|
|
541
|
+
},
|
|
542
|
+
[handlerMethods]
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
return { applyContentOffset, applyInitialScrollIndex };
|
|
546
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { RecyclerViewProps } from "../RecyclerViewProps";
|
|
4
|
+
import { RecyclerViewManager } from "../RecyclerViewManager";
|
|
5
|
+
import { VelocityTracker } from "../helpers/VelocityTracker";
|
|
6
|
+
|
|
7
|
+
export const useRecyclerViewManager = <T>(props: RecyclerViewProps<T>) => {
|
|
8
|
+
const [recyclerViewManager] = useState<RecyclerViewManager<T>>(
|
|
9
|
+
() => new RecyclerViewManager(props)
|
|
10
|
+
);
|
|
11
|
+
const [velocityTracker] = useState(() => new VelocityTracker());
|
|
12
|
+
|
|
13
|
+
const { data } = props;
|
|
14
|
+
|
|
15
|
+
useMemo(() => {
|
|
16
|
+
recyclerViewManager.updateProps(props);
|
|
17
|
+
}, [props]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* When data changes, we need to process the data update before the render happens
|
|
21
|
+
*/
|
|
22
|
+
useMemo(() => {
|
|
23
|
+
recyclerViewManager.processDataUpdate();
|
|
24
|
+
}, [data]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
return () => {
|
|
28
|
+
recyclerViewManager.dispose();
|
|
29
|
+
velocityTracker.cleanUp();
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return { recyclerViewManager, velocityTracker };
|
|
34
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction, useCallback, useMemo, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { useLayoutState } from "./useLayoutState";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A custom hook that provides state management with automatic reset functionality.
|
|
7
|
+
* Similar to useState, but automatically resets the state when specified dependencies change.
|
|
8
|
+
* This is particularly useful for managing state that needs to be reset when certain props or values change when items are recycled.
|
|
9
|
+
* This also avoids another setState call on recycling and helps with performance.
|
|
10
|
+
*
|
|
11
|
+
* @param initialState - The initial state value or a function that returns the initial state
|
|
12
|
+
* @param deps - Array of dependencies that trigger a state reset when changed
|
|
13
|
+
* @param onReset - Optional callback function that is called when the state is reset
|
|
14
|
+
* @returns A tuple containing:
|
|
15
|
+
* - The current state value
|
|
16
|
+
* - A setState function that works like useState's setState
|
|
17
|
+
*/
|
|
18
|
+
export function useRecyclingState<T>(
|
|
19
|
+
initialState: T | (() => T),
|
|
20
|
+
deps: React.DependencyList,
|
|
21
|
+
onReset?: () => void
|
|
22
|
+
): [T, Dispatch<SetStateAction<T>>] {
|
|
23
|
+
// Store the current state value in a ref to persist between renders
|
|
24
|
+
const valueStore = useRef<T>();
|
|
25
|
+
// Use layoutState to trigger re-renders when state changes
|
|
26
|
+
const [_, setCounter] = useLayoutState(0);
|
|
27
|
+
|
|
28
|
+
// Reset state when dependencies change
|
|
29
|
+
useMemo(() => {
|
|
30
|
+
// Calculate initial value from function or direct value
|
|
31
|
+
const initialValue =
|
|
32
|
+
typeof initialState === "function"
|
|
33
|
+
? (initialState as () => T)()
|
|
34
|
+
: initialState;
|
|
35
|
+
valueStore.current = initialValue;
|
|
36
|
+
// Call onReset callback if provided
|
|
37
|
+
onReset?.();
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
+
}, deps);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Proxy setState function that updates the stored value and triggers a re-render.
|
|
43
|
+
* Only triggers a re-render if the new value is different from the current value.
|
|
44
|
+
*/
|
|
45
|
+
const setStateProxy = useCallback(
|
|
46
|
+
(newValue: T | ((prevValue: T) => T)) => {
|
|
47
|
+
// Calculate next state value from function or direct value
|
|
48
|
+
const nextState =
|
|
49
|
+
typeof newValue === "function"
|
|
50
|
+
? (newValue as (prevValue: T) => T)(valueStore.current!)
|
|
51
|
+
: newValue;
|
|
52
|
+
|
|
53
|
+
// Only update and trigger re-render if value has changed
|
|
54
|
+
if (nextState !== valueStore.current) {
|
|
55
|
+
valueStore.current = nextState;
|
|
56
|
+
setCounter((prev) => prev + 1);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
[setCounter]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return [valueStore.current!, setStateProxy];
|
|
63
|
+
}
|