@shopify/flash-list 2.0.0-alpha.9 → 2.0.0-rc.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 +37 -97
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/BlankAreaEvent.kt +2 -2
- 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 +20 -0
- package/dist/FlashList.js.map +1 -1
- package/dist/FlashListProps.d.ts +30 -10
- 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/MasonryFlashList.js.map +1 -1
- package/dist/__tests__/RecyclerView.test.js +72 -28
- package/dist/__tests__/RecyclerView.test.js.map +1 -1
- 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__/helpers/createLayoutManager.d.ts.map +1 -1
- package/dist/__tests__/helpers/createLayoutManager.js +3 -4
- package/dist/__tests__/helpers/createLayoutManager.js.map +1 -1
- package/dist/__tests__/useUnmountAwareCallbacks.test.js +1 -1
- package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +1 -1
- package/dist/benchmark/useBenchmark.js +0 -25
- package/dist/benchmark/useBenchmark.js.map +1 -1
- package/dist/benchmark/useFlatListBenchmark.js +8 -7
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/native/config/PlatformHelper.android.d.ts +1 -0
- package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.android.js +1 -0
- package/dist/native/config/PlatformHelper.android.js.map +1 -1
- package/dist/native/config/PlatformHelper.d.ts +1 -0
- package/dist/native/config/PlatformHelper.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.ios.d.ts +1 -0
- package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.ios.js +1 -0
- package/dist/native/config/PlatformHelper.ios.js.map +1 -1
- package/dist/native/config/PlatformHelper.js +1 -0
- package/dist/native/config/PlatformHelper.js.map +1 -1
- package/dist/native/config/PlatformHelper.web.d.ts +1 -0
- package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
- package/dist/native/config/PlatformHelper.web.js +1 -0
- package/dist/native/config/PlatformHelper.web.js.map +1 -1
- package/dist/recyclerview/RecyclerView.d.ts +2 -1
- package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerView.js +104 -57
- package/dist/recyclerview/RecyclerView.js.map +1 -1
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts +41 -6
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerViewContextProvider.js +4 -0
- package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
- package/dist/recyclerview/RecyclerViewManager.d.ts +24 -7
- package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerViewManager.js +119 -113
- package/dist/recyclerview/RecyclerViewManager.js.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 +5 -3
- package/dist/recyclerview/ViewHolder.js.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.d.ts +9 -3
- package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.js +26 -9
- package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
- package/dist/recyclerview/components/ScrollAnchor.d.ts +2 -2
- package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
- package/dist/recyclerview/components/ScrollAnchor.js +9 -5
- package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.d.ts +1 -1
- package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.js +40 -33
- package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +45 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js +77 -20
- 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 -2
- package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useBoundDetection.js +56 -22
- 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 +5 -3
- package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.d.ts +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.js +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.js.map +1 -1
- package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useOnLoad.js +4 -6
- 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 +315 -204
- 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 +2 -2
- package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.js +1 -1
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts +10 -3
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +33 -4
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -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 +60 -21
- package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts +35 -21
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
- package/dist/recyclerview/layout-managers/LayoutManager.js +92 -28
- package/dist/recyclerview/layout-managers/LayoutManager.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 +28 -12
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
- package/dist/recyclerview/utils/measureLayout.web.d.ts.map +1 -1
- package/dist/recyclerview/utils/measureLayout.web.js +1 -3
- package/dist/recyclerview/utils/measureLayout.web.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +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 +2 -1
- package/src/AnimatedFlashList.ts +3 -2
- package/src/FlashList.tsx +24 -0
- package/src/FlashListProps.ts +41 -10
- package/src/FlashListRef.ts +320 -0
- package/src/MasonryFlashList.tsx +2 -2
- package/src/__tests__/RecyclerView.test.tsx +106 -31
- package/src/__tests__/RenderStackManager.test.ts +574 -0
- package/src/__tests__/helpers/createLayoutManager.ts +2 -3
- package/src/__tests__/useUnmountAwareCallbacks.test.tsx +12 -12
- package/src/benchmark/useBenchmark.ts +0 -37
- package/src/benchmark/useFlatListBenchmark.ts +2 -2
- package/src/index.ts +2 -1
- package/src/native/config/PlatformHelper.android.ts +1 -0
- package/src/native/config/PlatformHelper.ios.ts +1 -0
- package/src/native/config/PlatformHelper.ts +1 -0
- package/src/native/config/PlatformHelper.web.ts +1 -0
- package/src/recyclerview/RecyclerView.tsx +139 -75
- package/src/recyclerview/RecyclerViewContextProvider.ts +52 -7
- package/src/recyclerview/RecyclerViewManager.ts +135 -98
- package/src/recyclerview/RenderStackManager.ts +317 -0
- package/src/recyclerview/ViewHolder.tsx +5 -3
- package/src/recyclerview/ViewHolderCollection.tsx +42 -14
- package/src/recyclerview/components/ScrollAnchor.tsx +21 -9
- package/src/recyclerview/components/StickyHeaders.tsx +63 -45
- package/src/recyclerview/helpers/EngagedIndicesTracker.ts +118 -23
- package/src/recyclerview/helpers/RenderTimeTracker.ts +42 -0
- package/src/recyclerview/helpers/VelocityTracker.ts +77 -0
- package/src/recyclerview/hooks/useBoundDetection.ts +72 -23
- package/src/recyclerview/hooks/useLayoutState.ts +15 -6
- package/src/recyclerview/hooks/useMappingHelper.ts +1 -1
- package/src/recyclerview/hooks/useOnLoad.ts +4 -6
- package/src/recyclerview/hooks/useRecyclerViewController.tsx +364 -254
- package/src/recyclerview/hooks/useRecyclerViewManager.ts +13 -1
- package/src/recyclerview/hooks/useRecyclingState.ts +11 -7
- package/src/recyclerview/hooks/useSecondaryProps.tsx +1 -1
- package/src/recyclerview/hooks/useUnmountAwareCallbacks.ts +39 -3
- package/src/recyclerview/hooks/useUnmountFlag.ts +1 -0
- package/src/recyclerview/layout-managers/GridLayoutManager.ts +67 -23
- package/src/recyclerview/layout-managers/LayoutManager.ts +110 -41
- package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +30 -8
- package/src/recyclerview/utils/measureLayout.web.ts +1 -3
- package/src/viewability/ViewToken.ts +2 -2
- package/src/viewability/ViewabilityHelper.ts +1 -1
- package/src/viewability/ViewabilityManager.ts +16 -9
- package/dist/__tests__/RecycleKeyManager.test.d.ts +0 -2
- package/dist/__tests__/RecycleKeyManager.test.d.ts.map +0 -1
- package/dist/__tests__/RecycleKeyManager.test.js +0 -210
- package/dist/__tests__/RecycleKeyManager.test.js.map +0 -1
- 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/__tests__/RecycleKeyManager.test.ts +0 -254
- package/src/recyclerview/RecycleKeyManager.ts +0 -185
|
@@ -8,7 +8,13 @@ import {
|
|
|
8
8
|
} from "react";
|
|
9
9
|
import { I18nManager } from "react-native";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ScrollToOffsetParams,
|
|
13
|
+
ScrollToIndexParams,
|
|
14
|
+
ScrollToItemParams,
|
|
15
|
+
ScrollToEdgeParams,
|
|
16
|
+
FlashListRef,
|
|
17
|
+
} from "../../FlashListRef";
|
|
12
18
|
import { CompatScroller } from "../components/CompatScroller";
|
|
13
19
|
import { RecyclerViewManager } from "../RecyclerViewManager";
|
|
14
20
|
import { adjustOffsetForRTL } from "../utils/adjustOffsetForRTL";
|
|
@@ -17,58 +23,7 @@ import { ScrollAnchorRef } from "../components/ScrollAnchor";
|
|
|
17
23
|
import { PlatformConfig } from "../../native/config/PlatformHelper";
|
|
18
24
|
|
|
19
25
|
import { useUnmountFlag } from "./useUnmountFlag";
|
|
20
|
-
import {
|
|
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
|
-
}
|
|
26
|
+
import { useUnmountAwareTimeout } from "./useUnmountAwareCallbacks";
|
|
72
27
|
|
|
73
28
|
/**
|
|
74
29
|
* Comprehensive hook that manages RecyclerView scrolling behavior and provides
|
|
@@ -88,104 +43,98 @@ export interface ScrollToEdgeParams {
|
|
|
88
43
|
*/
|
|
89
44
|
export function useRecyclerViewController<T>(
|
|
90
45
|
recyclerViewManager: RecyclerViewManager<T>,
|
|
91
|
-
ref: React.Ref<
|
|
46
|
+
ref: React.Ref<FlashListRef<T>>,
|
|
92
47
|
scrollViewRef: RefObject<CompatScroller>,
|
|
93
|
-
scrollAnchorRef: React.RefObject<ScrollAnchorRef
|
|
94
|
-
props: RecyclerViewProps<T>
|
|
48
|
+
scrollAnchorRef: React.RefObject<ScrollAnchorRef>
|
|
95
49
|
) {
|
|
96
|
-
const { horizontal, data } = props;
|
|
97
50
|
const isUnmounted = useUnmountFlag();
|
|
98
51
|
const [_, setRenderId] = useState(0);
|
|
99
|
-
const
|
|
52
|
+
const pauseOffsetCorrection = useRef(false);
|
|
100
53
|
const initialScrollCompletedRef = useRef(false);
|
|
101
|
-
const lastDataLengthRef = useRef(
|
|
102
|
-
const { setTimeout } =
|
|
54
|
+
const lastDataLengthRef = useRef(recyclerViewManager.getDataLength());
|
|
55
|
+
const { setTimeout } = useUnmountAwareTimeout();
|
|
103
56
|
|
|
104
57
|
// Track the first visible item for maintaining scroll position
|
|
105
58
|
const firstVisibleItemKey = useRef<string | undefined>(undefined);
|
|
106
59
|
const firstVisibleItemLayout = useRef<RVLayout | undefined>(undefined);
|
|
107
|
-
const pendingScrollResolves = useRef<(() => void)[]>([]);
|
|
108
60
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
pauseAdjustRef.current = false;
|
|
123
|
-
}, 100);
|
|
124
|
-
|
|
125
|
-
pauseAdjustRef.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]);
|
|
61
|
+
// Queue to store callbacks that should be executed after scroll offset updates
|
|
62
|
+
const pendingScrollCallbacks = useRef<(() => void)[]>([]);
|
|
145
63
|
|
|
146
64
|
// Handle initial scroll position when the list first loads
|
|
147
65
|
// useOnLoad(recyclerViewManager, () => {
|
|
148
66
|
|
|
149
67
|
// });
|
|
150
68
|
/**
|
|
151
|
-
* Updates the scroll offset and
|
|
152
|
-
*
|
|
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
|
|
153
74
|
*/
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
83
|
+
setRenderId((prev) => prev + 1);
|
|
84
|
+
} else {
|
|
85
|
+
// No update needed, execute callback immediately
|
|
86
|
+
callback();
|
|
87
|
+
}
|
|
166
88
|
},
|
|
167
89
|
[recyclerViewManager]
|
|
168
90
|
);
|
|
169
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
|
+
|
|
170
117
|
/**
|
|
171
118
|
* Maintains the visible content position when the list updates.
|
|
172
119
|
* This is particularly useful for chat applications where we want to keep
|
|
173
120
|
* the user's current view position when new messages are added.
|
|
174
121
|
*/
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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());
|
|
180
130
|
|
|
181
|
-
const currentDataLength =
|
|
131
|
+
const currentDataLength = recyclerViewManager.getDataLength();
|
|
182
132
|
|
|
183
133
|
if (
|
|
184
|
-
!props.horizontal &&
|
|
185
134
|
recyclerViewManager.getIsFirstLayoutComplete() &&
|
|
186
|
-
|
|
135
|
+
keyExtractor &&
|
|
187
136
|
currentDataLength > 0 &&
|
|
188
|
-
|
|
137
|
+
recyclerViewManager.shouldMaintainVisibleContentPosition()
|
|
189
138
|
) {
|
|
190
139
|
const hasDataChanged = currentDataLength !== lastDataLengthRef.current;
|
|
191
140
|
// If we have a tracked first visible item, maintain its position
|
|
@@ -195,38 +144,54 @@ export function useRecyclerViewController<T>(
|
|
|
195
144
|
.getEngagedIndices()
|
|
196
145
|
.findValue(
|
|
197
146
|
(index) =>
|
|
198
|
-
|
|
147
|
+
keyExtractor?.(data![index], index) ===
|
|
199
148
|
firstVisibleItemKey.current
|
|
200
149
|
) ??
|
|
201
150
|
(hasDataChanged
|
|
202
|
-
?
|
|
151
|
+
? data?.findIndex(
|
|
203
152
|
(item, index) =>
|
|
204
|
-
|
|
205
|
-
firstVisibleItemKey.current
|
|
153
|
+
keyExtractor?.(item, index) === firstVisibleItemKey.current
|
|
206
154
|
)
|
|
207
155
|
: undefined);
|
|
208
156
|
|
|
209
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
currentIndexOfFirstVisibleItem !== undefined &&
|
|
159
|
+
currentIndexOfFirstVisibleItem >= 0
|
|
160
|
+
) {
|
|
210
161
|
// Calculate the difference in position and apply the offset
|
|
211
|
-
const diff =
|
|
212
|
-
recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).
|
|
213
|
-
|
|
162
|
+
const diff = horizontal
|
|
163
|
+
? recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).x -
|
|
164
|
+
firstVisibleItemLayout.current!.x
|
|
165
|
+
: recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem).y -
|
|
166
|
+
firstVisibleItemLayout.current!.y;
|
|
214
167
|
firstVisibleItemLayout.current = {
|
|
215
168
|
...recyclerViewManager.getLayout(currentIndexOfFirstVisibleItem),
|
|
216
169
|
};
|
|
217
|
-
if (
|
|
170
|
+
if (
|
|
171
|
+
diff !== 0 &&
|
|
172
|
+
!pauseOffsetCorrection.current &&
|
|
173
|
+
!recyclerViewManager.animationOptimizationsEnabled
|
|
174
|
+
) {
|
|
218
175
|
// console.log("diff", diff, firstVisibleItemKey.current);
|
|
219
176
|
if (PlatformConfig.supportsOffsetCorrection) {
|
|
177
|
+
// console.log("scrollBy", diff);
|
|
220
178
|
scrollAnchorRef.current?.scrollBy(diff);
|
|
221
179
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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);
|
|
226
190
|
}
|
|
227
191
|
if (hasDataChanged) {
|
|
228
|
-
|
|
229
|
-
recyclerViewManager.getAbsoluteLastScrollOffset() + diff
|
|
192
|
+
updateScrollOffsetWithCallback(
|
|
193
|
+
recyclerViewManager.getAbsoluteLastScrollOffset() + diff,
|
|
194
|
+
() => {}
|
|
230
195
|
);
|
|
231
196
|
recyclerViewManager.ignoreScrollEvents = true;
|
|
232
197
|
setTimeout(() => {
|
|
@@ -237,27 +202,23 @@ export function useRecyclerViewController<T>(
|
|
|
237
202
|
}
|
|
238
203
|
}
|
|
239
204
|
|
|
240
|
-
|
|
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
|
-
}
|
|
205
|
+
computeFirstVisibleIndexForOffsetCorrection();
|
|
254
206
|
}
|
|
255
|
-
lastDataLengthRef.current =
|
|
256
|
-
}, [
|
|
257
|
-
|
|
258
|
-
|
|
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(() => {
|
|
259
218
|
return {
|
|
260
|
-
props
|
|
219
|
+
get props() {
|
|
220
|
+
return recyclerViewManager.props;
|
|
221
|
+
},
|
|
261
222
|
/**
|
|
262
223
|
* Scrolls the list to a specific offset position.
|
|
263
224
|
* Handles RTL layouts and first item offset adjustments.
|
|
@@ -267,6 +228,7 @@ export function useRecyclerViewController<T>(
|
|
|
267
228
|
animated,
|
|
268
229
|
skipFirstItemOffset = true,
|
|
269
230
|
}: ScrollToOffsetParams) => {
|
|
231
|
+
const { horizontal } = recyclerViewManager.props;
|
|
270
232
|
if (scrollViewRef.current) {
|
|
271
233
|
// Adjust offset for RTL layouts in horizontal mode
|
|
272
234
|
if (I18nManager.isRTL && horizontal) {
|
|
@@ -295,6 +257,9 @@ export function useRecyclerViewController<T>(
|
|
|
295
257
|
});
|
|
296
258
|
}
|
|
297
259
|
},
|
|
260
|
+
clearLayoutCacheOnUpdate: () => {
|
|
261
|
+
recyclerViewManager.markLayoutManagerDirty();
|
|
262
|
+
},
|
|
298
263
|
|
|
299
264
|
// Expose native scroll view methods
|
|
300
265
|
flashScrollIndicators: () => {
|
|
@@ -314,13 +279,16 @@ export function useRecyclerViewController<T>(
|
|
|
314
279
|
* Scrolls to the end of the list.
|
|
315
280
|
*/
|
|
316
281
|
scrollToEnd: async ({ animated }: ScrollToEdgeParams = {}) => {
|
|
282
|
+
const { data } = recyclerViewManager.props;
|
|
317
283
|
if (data && data.length > 0) {
|
|
318
284
|
await handlerMethods.scrollToIndex({
|
|
319
285
|
index: data.length - 1,
|
|
320
286
|
animated,
|
|
321
287
|
});
|
|
322
288
|
}
|
|
323
|
-
|
|
289
|
+
setTimeout(() => {
|
|
290
|
+
scrollViewRef.current!.scrollToEnd({ animated });
|
|
291
|
+
}, 0);
|
|
324
292
|
},
|
|
325
293
|
|
|
326
294
|
/**
|
|
@@ -336,107 +304,190 @@ export function useRecyclerViewController<T>(
|
|
|
336
304
|
/**
|
|
337
305
|
* Scrolls to a specific index in the list.
|
|
338
306
|
* Supports viewPosition and viewOffset for precise positioning.
|
|
307
|
+
* Returns a Promise that resolves when the scroll is complete.
|
|
339
308
|
*/
|
|
340
|
-
scrollToIndex:
|
|
309
|
+
scrollToIndex: ({
|
|
341
310
|
index,
|
|
342
311
|
animated,
|
|
343
312
|
viewPosition,
|
|
344
313
|
viewOffset,
|
|
345
|
-
}: ScrollToIndexParams) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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 = () => {
|
|
327
|
+
const layout = recyclerViewManager.getLayout(index);
|
|
328
|
+
const offset = horizontal ? layout.x : layout.y;
|
|
329
|
+
let finalOffset = offset;
|
|
330
|
+
// take viewPosition etc into account
|
|
331
|
+
if (viewPosition !== undefined || viewOffset !== undefined) {
|
|
332
|
+
const containerSize = horizontal
|
|
333
|
+
? recyclerViewManager.getWindowSize().width
|
|
334
|
+
: recyclerViewManager.getWindowSize().height;
|
|
335
|
+
|
|
336
|
+
const itemSize = horizontal ? layout.width : layout.height;
|
|
337
|
+
|
|
338
|
+
if (viewPosition !== undefined) {
|
|
339
|
+
// viewPosition: 0 = top, 0.5 = center, 1 = bottom
|
|
340
|
+
finalOffset =
|
|
341
|
+
offset - (containerSize - itemSize) * viewPosition;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (viewOffset !== undefined) {
|
|
345
|
+
finalOffset += viewOffset;
|
|
346
|
+
}
|
|
365
347
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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();
|
|
361
|
+
|
|
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");
|
|
369
374
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
} else {
|
|
388
|
-
lastScrollOffset = Math.min(
|
|
389
|
-
finalOffset + bufferForCompute,
|
|
390
|
-
lastScrollOffset
|
|
391
|
-
);
|
|
392
|
-
recyclerViewManager.setScrollDirection("backward");
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (animated) {
|
|
396
|
-
// go from finalOffset to lastScrollOffset in 5 steps
|
|
397
|
-
for (let i = 0; i < 5; i++) {
|
|
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
|
|
398
392
|
if (isUnmounted.current) {
|
|
393
|
+
resolve();
|
|
399
394
|
return;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
// go from lastScrollOffset to finalOffset in 5 steps
|
|
407
|
-
for (let i = 0; i < 5; i++) {
|
|
408
|
-
if (isUnmounted.current) {
|
|
395
|
+
} else if (currentStep >= steps) {
|
|
396
|
+
// All steps completed, perform final scroll
|
|
397
|
+
finishScrollToIndex();
|
|
409
398
|
return;
|
|
410
399
|
}
|
|
411
|
-
|
|
412
|
-
|
|
400
|
+
|
|
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
|
+
}
|
|
421
|
+
|
|
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
|
|
436
|
+
} else {
|
|
437
|
+
// Continue to next step
|
|
438
|
+
performScrollStep(currentStep + 1);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
|
|
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();
|
|
450
|
+
|
|
451
|
+
if (finalOffset > maxOffset) {
|
|
452
|
+
finalOffset = maxOffset;
|
|
453
|
+
}
|
|
454
|
+
|
|
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
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Perform the final scroll to the target position
|
|
466
|
+
handlerMethods.scrollToOffset({
|
|
467
|
+
offset: finalOffset,
|
|
468
|
+
animated,
|
|
469
|
+
skipFirstItemOffset: true,
|
|
470
|
+
});
|
|
471
|
+
|
|
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
|
|
413
481
|
);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
finalOffset = getFinalOffset();
|
|
417
|
-
const maxOffset = recyclerViewManager.getMaxScrollOffset();
|
|
482
|
+
};
|
|
418
483
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
handlerMethods.scrollToOffset({
|
|
425
|
-
offset: lastScrollOffset,
|
|
426
|
-
animated: false,
|
|
427
|
-
skipFirstItemOffset: false,
|
|
428
|
-
});
|
|
484
|
+
// Start the scroll animation process
|
|
485
|
+
performScrollStep(0);
|
|
486
|
+
} else {
|
|
487
|
+
// Invalid parameters, resolve immediately
|
|
488
|
+
resolve();
|
|
429
489
|
}
|
|
430
|
-
|
|
431
|
-
offset: finalOffset,
|
|
432
|
-
animated,
|
|
433
|
-
skipFirstItemOffset: false,
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
setTimeout(() => {
|
|
437
|
-
pauseAdjustRef.current = false;
|
|
438
|
-
}, 200);
|
|
439
|
-
}
|
|
490
|
+
});
|
|
440
491
|
},
|
|
441
492
|
|
|
442
493
|
/**
|
|
@@ -449,11 +500,10 @@ export function useRecyclerViewController<T>(
|
|
|
449
500
|
viewPosition,
|
|
450
501
|
viewOffset,
|
|
451
502
|
}: ScrollToItemParams<T>) => {
|
|
503
|
+
const { data } = recyclerViewManager.props;
|
|
452
504
|
if (scrollViewRef.current && data) {
|
|
453
505
|
// Find the index of the item in the data array
|
|
454
|
-
const index =
|
|
455
|
-
(dataItem) => dataItem === item
|
|
456
|
-
);
|
|
506
|
+
const index = data.findIndex((dataItem) => dataItem === item);
|
|
457
507
|
if (index >= 0) {
|
|
458
508
|
handlerMethods.scrollToIndex({
|
|
459
509
|
index,
|
|
@@ -473,7 +523,7 @@ export function useRecyclerViewController<T>(
|
|
|
473
523
|
return recyclerViewManager.getWindowSize();
|
|
474
524
|
},
|
|
475
525
|
getLayout: (index: number) => {
|
|
476
|
-
return recyclerViewManager.
|
|
526
|
+
return recyclerViewManager.tryGetLayout(index);
|
|
477
527
|
},
|
|
478
528
|
getAbsoluteLastScrollOffset: () => {
|
|
479
529
|
return recyclerViewManager.getAbsoluteLastScrollOffset();
|
|
@@ -484,11 +534,11 @@ export function useRecyclerViewController<T>(
|
|
|
484
534
|
recordInteraction: () => {
|
|
485
535
|
recyclerViewManager.recordInteraction();
|
|
486
536
|
},
|
|
487
|
-
|
|
488
|
-
return recyclerViewManager.
|
|
537
|
+
computeVisibleIndices: () => {
|
|
538
|
+
return recyclerViewManager.computeVisibleIndices();
|
|
489
539
|
},
|
|
490
540
|
getFirstVisibleIndex: () => {
|
|
491
|
-
return recyclerViewManager.
|
|
541
|
+
return recyclerViewManager.computeVisibleIndices().startIndex;
|
|
492
542
|
},
|
|
493
543
|
recomputeViewableItems: () => {
|
|
494
544
|
recyclerViewManager.recomputeViewableItems();
|
|
@@ -497,19 +547,79 @@ export function useRecyclerViewController<T>(
|
|
|
497
547
|
* Disables item recycling in preparation for layout animations.
|
|
498
548
|
*/
|
|
499
549
|
prepareForLayoutAnimationRender: () => {
|
|
500
|
-
recyclerViewManager.
|
|
550
|
+
recyclerViewManager.animationOptimizationsEnabled = true;
|
|
501
551
|
},
|
|
502
552
|
};
|
|
503
|
-
}, [
|
|
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]);
|
|
504
599
|
|
|
505
600
|
// Expose imperative methods through the ref
|
|
506
601
|
useImperativeHandle(
|
|
507
602
|
ref,
|
|
508
603
|
() => {
|
|
509
|
-
|
|
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;
|
|
510
615
|
},
|
|
511
|
-
[handlerMethods]
|
|
616
|
+
[handlerMethods, scrollViewRef, recyclerViewManager]
|
|
512
617
|
);
|
|
513
618
|
|
|
514
|
-
return {
|
|
619
|
+
return {
|
|
620
|
+
applyOffsetCorrection,
|
|
621
|
+
computeFirstVisibleIndexForOffsetCorrection,
|
|
622
|
+
applyInitialScrollIndex,
|
|
623
|
+
handlerMethods,
|
|
624
|
+
};
|
|
515
625
|
}
|