@shopify/flash-list 2.0.4-alpha.1 → 2.2.0
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/dist/AnimatedFlashList.js +4 -6
- package/dist/AnimatedFlashList.js.map +1 -1
- package/dist/FlashList.js +1 -5
- package/dist/FlashList.js.map +1 -1
- package/dist/FlashListProps.d.ts +41 -1
- package/dist/FlashListProps.d.ts.map +1 -1
- package/dist/FlashListProps.js +1 -4
- package/dist/FlashListProps.js.map +1 -1
- package/dist/FlashListRef.js +1 -2
- package/dist/benchmark/AutoScrollHelper.js +22 -30
- package/dist/benchmark/AutoScrollHelper.js.map +1 -1
- package/dist/benchmark/JSFPSMonitor.js +27 -33
- package/dist/benchmark/JSFPSMonitor.js.map +1 -1
- package/dist/benchmark/roundToDecimalPlaces.js +2 -5
- package/dist/benchmark/roundToDecimalPlaces.js.map +1 -1
- package/dist/benchmark/useBenchmark.d.ts +9 -1
- package/dist/benchmark/useBenchmark.d.ts.map +1 -1
- package/dist/benchmark/useBenchmark.js +86 -95
- package/dist/benchmark/useBenchmark.js.map +1 -1
- package/dist/benchmark/useDataMultiplier.js +6 -10
- package/dist/benchmark/useDataMultiplier.js.map +1 -1
- package/dist/benchmark/useFlatListBenchmark.d.ts +4 -1
- package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -1
- package/dist/benchmark/useFlatListBenchmark.js +73 -81
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
- package/dist/errors/ErrorMessages.js +1 -4
- package/dist/errors/ErrorMessages.js.map +1 -1
- package/dist/errors/WarningMessages.js +1 -4
- package/dist/errors/WarningMessages.js.map +1 -1
- package/dist/index.js +17 -35
- package/dist/index.js.map +1 -1
- package/dist/isNewArch.js +6 -9
- package/dist/isNewArch.js.map +1 -1
- package/dist/native/config/PlatformHelper.android.js +2 -5
- package/dist/native/config/PlatformHelper.android.js.map +1 -1
- package/dist/native/config/PlatformHelper.ios.js +2 -5
- package/dist/native/config/PlatformHelper.ios.js.map +1 -1
- package/dist/native/config/PlatformHelper.js +2 -5
- package/dist/native/config/PlatformHelper.js.map +1 -1
- package/dist/native/config/PlatformHelper.web.js +2 -5
- package/dist/native/config/PlatformHelper.web.js.map +1 -1
- package/dist/recyclerview/LayoutCommitObserver.js +20 -24
- package/dist/recyclerview/LayoutCommitObserver.js.map +1 -1
- package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
- package/dist/recyclerview/RecyclerView.js +134 -111
- package/dist/recyclerview/RecyclerView.js.map +1 -1
- package/dist/recyclerview/RecyclerViewContextProvider.js +7 -12
- package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
- package/dist/recyclerview/RecyclerViewManager.js +138 -167
- package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
- package/dist/recyclerview/RecyclerViewProps.js +1 -2
- package/dist/recyclerview/RenderStackManager.js +97 -188
- package/dist/recyclerview/RenderStackManager.js.map +1 -1
- package/dist/recyclerview/ViewHolder.d.ts +2 -0
- package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
- package/dist/recyclerview/ViewHolder.js +19 -21
- package/dist/recyclerview/ViewHolder.js.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.d.ts +4 -0
- package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
- package/dist/recyclerview/ViewHolderCollection.js +26 -30
- package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
- package/dist/recyclerview/components/CompatScroller.js +6 -7
- package/dist/recyclerview/components/CompatScroller.js.map +1 -1
- package/dist/recyclerview/components/CompatView.js +6 -7
- package/dist/recyclerview/components/CompatView.js.map +1 -1
- package/dist/recyclerview/components/ScrollAnchor.js +10 -15
- package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.d.ts +5 -1
- package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
- package/dist/recyclerview/components/StickyHeaders.js +77 -51
- package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js +39 -66
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -1
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js +57 -63
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -1
- package/dist/recyclerview/helpers/RenderTimeTracker.js +19 -24
- package/dist/recyclerview/helpers/RenderTimeTracker.js.map +1 -1
- package/dist/recyclerview/helpers/VelocityTracker.js +16 -22
- package/dist/recyclerview/helpers/VelocityTracker.js.map +1 -1
- package/dist/recyclerview/hooks/useBoundDetection.js +37 -40
- package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
- package/dist/recyclerview/hooks/useLayoutState.js +9 -15
- package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
- package/dist/recyclerview/hooks/useMappingHelper.js +6 -10
- package/dist/recyclerview/hooks/useMappingHelper.js.map +1 -1
- package/dist/recyclerview/hooks/useOnLoad.js +16 -22
- package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewController.js +169 -188
- package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclerViewManager.js +12 -17
- package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -1
- package/dist/recyclerview/hooks/useRecyclingState.js +10 -14
- package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts +2 -0
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -1
- package/dist/recyclerview/hooks/useSecondaryProps.js +39 -30
- package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -1
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js +17 -22
- package/dist/recyclerview/hooks/useUnmountAwareCallbacks.js.map +1 -1
- package/dist/recyclerview/hooks/useUnmountFlag.js +5 -9
- package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -1
- package/dist/recyclerview/layout-managers/GridLayoutManager.js +61 -80
- package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/LayoutManager.js +83 -123
- package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js +51 -91
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -1
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +77 -96
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
- package/dist/recyclerview/utils/adjustOffsetForRTL.js +1 -4
- package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -1
- package/dist/recyclerview/utils/componentUtils.js +4 -9
- package/dist/recyclerview/utils/componentUtils.js.map +1 -1
- package/dist/recyclerview/utils/findVisibleIndex.js +9 -13
- package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -1
- package/dist/recyclerview/utils/measureLayout.js +12 -20
- package/dist/recyclerview/utils/measureLayout.js.map +1 -1
- package/dist/recyclerview/utils/measureLayout.web.js +15 -23
- package/dist/recyclerview/utils/measureLayout.web.js.map +1 -1
- package/dist/recyclerview/viewability/ViewToken.js +1 -2
- package/dist/recyclerview/viewability/ViewabilityHelper.js +34 -41
- package/dist/recyclerview/viewability/ViewabilityHelper.js.map +1 -1
- package/dist/recyclerview/viewability/ViewabilityManager.js +48 -61
- package/dist/recyclerview/viewability/ViewabilityManager.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/AverageWindow.js +28 -39
- package/dist/utils/AverageWindow.js.map +1 -1
- package/package.json +4 -6
- package/src/FlashListProps.ts +51 -1
- package/src/benchmark/useBenchmark.ts +47 -4
- package/src/benchmark/useFlatListBenchmark.ts +38 -5
- package/src/recyclerview/RecyclerView.tsx +42 -8
- package/src/recyclerview/ViewHolder.tsx +6 -1
- package/src/recyclerview/ViewHolderCollection.tsx +10 -0
- package/src/recyclerview/components/StickyHeaders.tsx +54 -13
- package/src/recyclerview/hooks/useRecyclerViewController.tsx +7 -4
- package/src/recyclerview/hooks/useSecondaryProps.tsx +23 -0
- package/dist/__tests__/AverageWindow.test.d.ts +0 -2
- package/dist/__tests__/AverageWindow.test.d.ts.map +0 -1
- package/dist/__tests__/AverageWindow.test.js +0 -104
- package/dist/__tests__/AverageWindow.test.js.map +0 -1
- package/dist/__tests__/ConsecutiveNumbers.test.d.ts +0 -2
- package/dist/__tests__/ConsecutiveNumbers.test.d.ts.map +0 -1
- package/dist/__tests__/ConsecutiveNumbers.test.js +0 -224
- package/dist/__tests__/ConsecutiveNumbers.test.js.map +0 -1
- package/dist/__tests__/GridLayoutManager.test.d.ts +0 -2
- package/dist/__tests__/GridLayoutManager.test.d.ts.map +0 -1
- package/dist/__tests__/GridLayoutManager.test.js +0 -69
- package/dist/__tests__/GridLayoutManager.test.js.map +0 -1
- package/dist/__tests__/LayoutCommitObserver.test.d.ts +0 -2
- package/dist/__tests__/LayoutCommitObserver.test.d.ts.map +0 -1
- package/dist/__tests__/LayoutCommitObserver.test.js +0 -37
- package/dist/__tests__/LayoutCommitObserver.test.js.map +0 -1
- package/dist/__tests__/LinearLayoutManager.test.d.ts +0 -2
- package/dist/__tests__/LinearLayoutManager.test.d.ts.map +0 -1
- package/dist/__tests__/LinearLayoutManager.test.js +0 -140
- package/dist/__tests__/LinearLayoutManager.test.js.map +0 -1
- package/dist/__tests__/MasonryLayoutManager.test.d.ts +0 -2
- package/dist/__tests__/MasonryLayoutManager.test.d.ts.map +0 -1
- package/dist/__tests__/MasonryLayoutManager.test.js +0 -148
- package/dist/__tests__/MasonryLayoutManager.test.js.map +0 -1
- package/dist/__tests__/RecyclerView.test.d.ts +0 -2
- package/dist/__tests__/RecyclerView.test.d.ts.map +0 -1
- package/dist/__tests__/RecyclerView.test.js +0 -103
- package/dist/__tests__/RecyclerView.test.js.map +0 -1
- package/dist/__tests__/RecyclerViewManager.test.d.ts +0 -2
- package/dist/__tests__/RecyclerViewManager.test.d.ts.map +0 -1
- package/dist/__tests__/RecyclerViewManager.test.js +0 -56
- package/dist/__tests__/RecyclerViewManager.test.js.map +0 -1
- package/dist/__tests__/RenderStackManager.test.d.ts +0 -2
- package/dist/__tests__/RenderStackManager.test.d.ts.map +0 -1
- package/dist/__tests__/RenderStackManager.test.js +0 -485
- package/dist/__tests__/RenderStackManager.test.js.map +0 -1
- package/dist/__tests__/ViewabilityHelper.test.d.ts +0 -2
- package/dist/__tests__/ViewabilityHelper.test.d.ts.map +0 -1
- package/dist/__tests__/ViewabilityHelper.test.js +0 -186
- package/dist/__tests__/ViewabilityHelper.test.js.map +0 -1
- package/dist/__tests__/findVisibleIndex.test.d.ts +0 -2
- package/dist/__tests__/findVisibleIndex.test.d.ts.map +0 -1
- package/dist/__tests__/findVisibleIndex.test.js +0 -259
- package/dist/__tests__/findVisibleIndex.test.js.map +0 -1
- package/dist/__tests__/helpers/createLayoutManager.d.ts +0 -34
- package/dist/__tests__/helpers/createLayoutManager.d.ts.map +0 -1
- package/dist/__tests__/helpers/createLayoutManager.js +0 -110
- package/dist/__tests__/helpers/createLayoutManager.js.map +0 -1
- package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts +0 -2
- package/dist/__tests__/useUnmountAwareCallbacks.test.d.ts.map +0 -1
- package/dist/__tests__/useUnmountAwareCallbacks.test.js +0 -185
- package/dist/__tests__/useUnmountAwareCallbacks.test.js.map +0 -1
- package/src/__tests__/AverageWindow.test.ts +0 -128
- package/src/__tests__/ConsecutiveNumbers.test.ts +0 -232
- package/src/__tests__/GridLayoutManager.test.ts +0 -113
- package/src/__tests__/LayoutCommitObserver.test.tsx +0 -63
- package/src/__tests__/LinearLayoutManager.test.ts +0 -227
- package/src/__tests__/MasonryLayoutManager.test.ts +0 -202
- package/src/__tests__/RecyclerView.test.tsx +0 -144
- package/src/__tests__/RecyclerViewManager.test.ts +0 -74
- package/src/__tests__/RenderStackManager.test.ts +0 -574
- package/src/__tests__/ViewabilityHelper.test.ts +0 -282
- package/src/__tests__/findVisibleIndex.test.ts +0 -369
- package/src/__tests__/helpers/createLayoutManager.ts +0 -141
- package/src/__tests__/useUnmountAwareCallbacks.test.tsx +0 -285
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
1
|
+
import React, { useEffect, useState, useCallback, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
import { FlashListRef } from "../FlashListRef";
|
|
4
4
|
import { ErrorMessages } from "../errors/ErrorMessages";
|
|
@@ -24,6 +24,12 @@ export interface BenchmarkParams {
|
|
|
24
24
|
* Blank area is negative when list is able to draw faster than the scroll speed.
|
|
25
25
|
*/
|
|
26
26
|
sumNegativeBlankAreaValues?: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* When set to true, the benchmark will not start automatically.
|
|
30
|
+
* Use the returned startBenchmark function to trigger it manually.
|
|
31
|
+
*/
|
|
32
|
+
startManually?: boolean;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
export interface BenchmarkResult {
|
|
@@ -44,15 +50,27 @@ export function useBenchmark(
|
|
|
44
50
|
callback: (benchmarkResult: BenchmarkResult) => void,
|
|
45
51
|
params: BenchmarkParams = {}
|
|
46
52
|
) {
|
|
47
|
-
|
|
53
|
+
const [isBenchmarkRunning, setIsBenchmarkRunning] = useState(false);
|
|
54
|
+
const cancellableRef = useRef<Cancellable | null>(null);
|
|
55
|
+
|
|
56
|
+
const startBenchmark = useCallback(() => {
|
|
57
|
+
if (isBenchmarkRunning) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
const cancellable = new Cancellable();
|
|
62
|
+
cancellableRef.current = cancellable;
|
|
49
63
|
const suggestions: string[] = [];
|
|
64
|
+
|
|
50
65
|
if (flashListRef.current) {
|
|
51
66
|
if (!(Number(flashListRef.current.props.data?.length) > 0)) {
|
|
52
67
|
throw new Error(ErrorMessages.dataEmptyCannotRunBenchmark);
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
|
-
|
|
70
|
+
|
|
71
|
+
setIsBenchmarkRunning(true);
|
|
72
|
+
|
|
73
|
+
const runBenchmark = async () => {
|
|
56
74
|
const jsFPSMonitor = new JSFPSMonitor();
|
|
57
75
|
jsFPSMonitor.startTracking();
|
|
58
76
|
for (let i = 0; i < (params.repeatCount || 1); i++) {
|
|
@@ -78,13 +96,37 @@ export function useBenchmark(
|
|
|
78
96
|
result.formattedString = getFormattedString(result);
|
|
79
97
|
}
|
|
80
98
|
callback(result);
|
|
99
|
+
setIsBenchmarkRunning(false);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
runBenchmark();
|
|
103
|
+
}, [
|
|
104
|
+
callback,
|
|
105
|
+
flashListRef,
|
|
106
|
+
isBenchmarkRunning,
|
|
107
|
+
params.repeatCount,
|
|
108
|
+
params.speedMultiplier,
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (params.startManually) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cancelTimeout = setTimeout(() => {
|
|
117
|
+
startBenchmark();
|
|
81
118
|
}, params.startDelayInMs || 3000);
|
|
119
|
+
|
|
82
120
|
return () => {
|
|
83
121
|
clearTimeout(cancelTimeout);
|
|
84
|
-
|
|
122
|
+
if (cancellableRef.current) {
|
|
123
|
+
cancellableRef.current.cancel();
|
|
124
|
+
}
|
|
85
125
|
};
|
|
86
126
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
87
127
|
}, []);
|
|
128
|
+
|
|
129
|
+
return { startBenchmark, isBenchmarkRunning } as const;
|
|
88
130
|
}
|
|
89
131
|
|
|
90
132
|
export function getFormattedString(res: BenchmarkResult) {
|
|
@@ -161,6 +203,7 @@ async function runScrollBenchmark(
|
|
|
161
203
|
}
|
|
162
204
|
}
|
|
163
205
|
}
|
|
206
|
+
|
|
164
207
|
function computeSuggestions(
|
|
165
208
|
flashListRef: React.RefObject<FlashListRef<any> | null | undefined>,
|
|
166
209
|
suggestions: string[]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { FlatList } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { ErrorMessages } from "../errors/ErrorMessages";
|
|
@@ -25,14 +25,24 @@ export function useFlatListBenchmark(
|
|
|
25
25
|
callback: (benchmarkResult: BenchmarkResult) => void,
|
|
26
26
|
params: FlatListBenchmarkParams
|
|
27
27
|
) {
|
|
28
|
-
|
|
28
|
+
const [isBenchmarkRunning, setIsBenchmarkRunning] = useState(false);
|
|
29
|
+
const cancellableRef = useRef<Cancellable | null>(null);
|
|
30
|
+
|
|
31
|
+
const startBenchmark = useCallback(() => {
|
|
32
|
+
if (isBenchmarkRunning) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
29
35
|
const cancellable = new Cancellable();
|
|
36
|
+
cancellableRef.current = cancellable;
|
|
30
37
|
if (flatListRef.current && flatListRef.current.props) {
|
|
31
38
|
if (!(Number(flatListRef.current.props.data?.length) > 0)) {
|
|
32
39
|
throw new Error(ErrorMessages.dataEmptyCannotRunBenchmark);
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
|
-
|
|
42
|
+
|
|
43
|
+
setIsBenchmarkRunning(true);
|
|
44
|
+
|
|
45
|
+
const runBenchmark = async () => {
|
|
36
46
|
const jsFPSMonitor = new JSFPSMonitor();
|
|
37
47
|
jsFPSMonitor.startTracking();
|
|
38
48
|
for (let i = 0; i < (params.repeatCount || 1); i++) {
|
|
@@ -53,14 +63,37 @@ export function useFlatListBenchmark(
|
|
|
53
63
|
result.formattedString = getFormattedString(result);
|
|
54
64
|
}
|
|
55
65
|
callback(result);
|
|
66
|
+
setIsBenchmarkRunning(false);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
runBenchmark();
|
|
70
|
+
}, [
|
|
71
|
+
callback,
|
|
72
|
+
flatListRef,
|
|
73
|
+
isBenchmarkRunning,
|
|
74
|
+
params.repeatCount,
|
|
75
|
+
params.speedMultiplier,
|
|
76
|
+
params.targetOffset,
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (params.startManually) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cancelTimeout = setTimeout(() => {
|
|
85
|
+
startBenchmark();
|
|
56
86
|
}, params.startDelayInMs || 3000);
|
|
87
|
+
|
|
57
88
|
return () => {
|
|
58
89
|
clearTimeout(cancelTimeout);
|
|
59
|
-
|
|
90
|
+
if (cancellableRef.current) {
|
|
91
|
+
cancellableRef.current.cancel();
|
|
92
|
+
}
|
|
60
93
|
};
|
|
61
94
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
95
|
}, []);
|
|
63
|
-
return
|
|
96
|
+
return { startBenchmark, isBenchmarkRunning };
|
|
64
97
|
}
|
|
65
98
|
|
|
66
99
|
/**
|
|
@@ -84,6 +84,8 @@ const RecyclerViewComponent = <T,>(
|
|
|
84
84
|
stickyHeaderIndices,
|
|
85
85
|
maintainVisibleContentPosition,
|
|
86
86
|
onCommitLayoutEffect,
|
|
87
|
+
onChangeStickyIndex,
|
|
88
|
+
stickyHeaderConfig,
|
|
87
89
|
...rest
|
|
88
90
|
} = props;
|
|
89
91
|
|
|
@@ -91,6 +93,13 @@ const RecyclerViewComponent = <T,>(
|
|
|
91
93
|
|
|
92
94
|
renderTimeTracker.startTracking();
|
|
93
95
|
|
|
96
|
+
// Sticky header config
|
|
97
|
+
const stickyHeaderOffset = stickyHeaderConfig?.offset ?? 0;
|
|
98
|
+
const stickyHeaderUseNativeDriver =
|
|
99
|
+
stickyHeaderConfig?.useNativeDriver ?? true;
|
|
100
|
+
const stickyHeaderHideRelatedCell =
|
|
101
|
+
stickyHeaderConfig?.hideRelatedCell ?? false;
|
|
102
|
+
|
|
94
103
|
// Core refs for managing scroll view, internal view, and child container
|
|
95
104
|
const scrollViewRef = useRef<CompatScroller>(null);
|
|
96
105
|
const internalViewRef = useRef<CompatView>(null);
|
|
@@ -108,6 +117,7 @@ const RecyclerViewComponent = <T,>(
|
|
|
108
117
|
// State for managing layout and render updates
|
|
109
118
|
const [_, setLayoutTreeId] = useLayoutState(0);
|
|
110
119
|
const [__, setRenderId] = useState(0);
|
|
120
|
+
const [currentStickyIndex, setCurrentStickyIndex] = useState(-1);
|
|
111
121
|
|
|
112
122
|
// Map to store refs for each item in the list
|
|
113
123
|
const refHolder = useMemo(
|
|
@@ -388,6 +398,7 @@ const RecyclerViewComponent = <T,>(
|
|
|
388
398
|
renderFooter,
|
|
389
399
|
renderEmpty,
|
|
390
400
|
CompatScrollView,
|
|
401
|
+
renderStickyHeaderBackdrop,
|
|
391
402
|
} = useSecondaryProps(props);
|
|
392
403
|
|
|
393
404
|
if (
|
|
@@ -408,15 +419,23 @@ const RecyclerViewComponent = <T,>(
|
|
|
408
419
|
if (horizontal) {
|
|
409
420
|
throw new Error(ErrorMessages.stickyHeadersNotSupportedForHorizontal);
|
|
410
421
|
}
|
|
422
|
+
|
|
411
423
|
return (
|
|
412
424
|
<StickyHeaders
|
|
413
425
|
stickyHeaderIndices={stickyHeaderIndices}
|
|
426
|
+
stickyHeaderOffset={stickyHeaderOffset}
|
|
414
427
|
data={data}
|
|
415
428
|
renderItem={renderItem}
|
|
416
429
|
scrollY={scrollY}
|
|
417
430
|
stickyHeaderRef={stickyHeaderRef}
|
|
418
431
|
recyclerViewManager={recyclerViewManager}
|
|
419
432
|
extraData={extraData}
|
|
433
|
+
onChangeStickyIndex={(newStickyHeaderIndex) => {
|
|
434
|
+
if (stickyHeaderHideRelatedCell) {
|
|
435
|
+
setCurrentStickyIndex(newStickyHeaderIndex);
|
|
436
|
+
}
|
|
437
|
+
onChangeStickyIndex?.(newStickyHeaderIndex, currentStickyIndex);
|
|
438
|
+
}}
|
|
420
439
|
/>
|
|
421
440
|
);
|
|
422
441
|
}
|
|
@@ -424,11 +443,15 @@ const RecyclerViewComponent = <T,>(
|
|
|
424
443
|
}, [
|
|
425
444
|
data,
|
|
426
445
|
stickyHeaderIndices,
|
|
446
|
+
stickyHeaderOffset,
|
|
427
447
|
renderItem,
|
|
428
448
|
scrollY,
|
|
429
449
|
horizontal,
|
|
430
450
|
recyclerViewManager,
|
|
431
451
|
extraData,
|
|
452
|
+
currentStickyIndex,
|
|
453
|
+
onChangeStickyIndex,
|
|
454
|
+
stickyHeaderHideRelatedCell,
|
|
432
455
|
]);
|
|
433
456
|
|
|
434
457
|
// Set up scroll event handling with animation support for sticky headers
|
|
@@ -436,11 +459,14 @@ const RecyclerViewComponent = <T,>(
|
|
|
436
459
|
if (stickyHeaders) {
|
|
437
460
|
return Animated.event(
|
|
438
461
|
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
|
439
|
-
{
|
|
462
|
+
{
|
|
463
|
+
useNativeDriver: stickyHeaderUseNativeDriver,
|
|
464
|
+
listener: onScrollHandler,
|
|
465
|
+
}
|
|
440
466
|
);
|
|
441
467
|
}
|
|
442
468
|
return onScrollHandler;
|
|
443
|
-
}, [onScrollHandler, scrollY, stickyHeaders]);
|
|
469
|
+
}, [onScrollHandler, scrollY, stickyHeaders, stickyHeaderUseNativeDriver]);
|
|
444
470
|
|
|
445
471
|
const shouldMaintainVisibleContentPosition =
|
|
446
472
|
recyclerViewManager.shouldMaintainVisibleContentPosition();
|
|
@@ -464,13 +490,14 @@ const RecyclerViewComponent = <T,>(
|
|
|
464
490
|
return (
|
|
465
491
|
<CompatView
|
|
466
492
|
style={{
|
|
493
|
+
marginTop: horizontal ? undefined : stickyHeaderOffset,
|
|
467
494
|
height: horizontal ? undefined : 0,
|
|
468
495
|
width: horizontal ? 0 : undefined,
|
|
469
496
|
}}
|
|
470
497
|
ref={firstChildViewRef}
|
|
471
498
|
/>
|
|
472
499
|
);
|
|
473
|
-
}, [horizontal]);
|
|
500
|
+
}, [horizontal, stickyHeaderOffset]);
|
|
474
501
|
|
|
475
502
|
const scrollAnchor = useMemo(() => {
|
|
476
503
|
if (shouldMaintainVisibleContentPosition) {
|
|
@@ -490,11 +517,13 @@ const RecyclerViewComponent = <T,>(
|
|
|
490
517
|
return (
|
|
491
518
|
<RecyclerViewContextProvider value={recyclerViewContext}>
|
|
492
519
|
<CompatView
|
|
493
|
-
style={
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
520
|
+
style={[
|
|
521
|
+
{
|
|
522
|
+
flex: horizontal ? undefined : 1,
|
|
523
|
+
overflow: "hidden",
|
|
524
|
+
},
|
|
525
|
+
style,
|
|
526
|
+
]}
|
|
498
527
|
ref={internalViewRef}
|
|
499
528
|
collapsable={false}
|
|
500
529
|
onLayout={(event) => {
|
|
@@ -588,10 +617,15 @@ const RecyclerViewComponent = <T,>(
|
|
|
588
617
|
? recyclerViewManager.getChildContainerDimensions()
|
|
589
618
|
: undefined
|
|
590
619
|
}
|
|
620
|
+
currentStickyIndex={currentStickyIndex}
|
|
621
|
+
hideStickyHeaderRelatedCell={stickyHeaderHideRelatedCell}
|
|
591
622
|
/>
|
|
592
623
|
{renderEmpty}
|
|
593
624
|
{renderFooter}
|
|
594
625
|
</CompatScrollView>
|
|
626
|
+
{stickyHeaderIndices && stickyHeaderIndices.length > 0
|
|
627
|
+
? renderStickyHeaderBackdrop
|
|
628
|
+
: null}
|
|
595
629
|
{stickyHeaders}
|
|
596
630
|
</CompatView>
|
|
597
631
|
</RecyclerViewContextProvider>
|
|
@@ -47,6 +47,8 @@ export interface ViewHolderProps<TItem> {
|
|
|
47
47
|
horizontal?: FlashListProps<TItem>["horizontal"];
|
|
48
48
|
/** Callback when the item's size changes */
|
|
49
49
|
onSizeChanged?: (index: number, size: RVDimension) => void;
|
|
50
|
+
/** Whether this item should be hidden (likely because it is associated with the active sticky header) */
|
|
51
|
+
hidden: boolean;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -69,6 +71,7 @@ const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
|
|
|
69
71
|
ItemSeparatorComponent,
|
|
70
72
|
trailingItem,
|
|
71
73
|
horizontal,
|
|
74
|
+
hidden,
|
|
72
75
|
} = props;
|
|
73
76
|
|
|
74
77
|
useLayoutEffect(() => {
|
|
@@ -113,6 +116,7 @@ const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
|
|
|
113
116
|
maxWidth: layout.maxWidth,
|
|
114
117
|
left: layout.x,
|
|
115
118
|
top: layout.y,
|
|
119
|
+
opacity: hidden ? 0 : 1,
|
|
116
120
|
} as const;
|
|
117
121
|
|
|
118
122
|
// TODO: Fix this type issue
|
|
@@ -152,7 +156,8 @@ export const ViewHolder = React.memo(
|
|
|
152
156
|
prevProps.CellRendererComponent === nextProps.CellRendererComponent &&
|
|
153
157
|
prevProps.ItemSeparatorComponent === nextProps.ItemSeparatorComponent &&
|
|
154
158
|
prevProps.trailingItem === nextProps.trailingItem &&
|
|
155
|
-
prevProps.horizontal === nextProps.horizontal
|
|
159
|
+
prevProps.horizontal === nextProps.horizontal &&
|
|
160
|
+
prevProps.hidden === nextProps.hidden
|
|
156
161
|
);
|
|
157
162
|
}
|
|
158
163
|
);
|
|
@@ -50,6 +50,10 @@ export interface ViewHolderCollectionProps<TItem> {
|
|
|
50
50
|
* For startRenderingFromBottom, we need to adjust the height of the container
|
|
51
51
|
*/
|
|
52
52
|
getAdjustmentMargin: () => number;
|
|
53
|
+
/** Current sticky index */
|
|
54
|
+
currentStickyIndex: number;
|
|
55
|
+
/** Whether the cell associated with an active sticky header is hidden */
|
|
56
|
+
hideStickyHeaderRelatedCell: boolean;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/**
|
|
@@ -84,6 +88,8 @@ export const ViewHolderCollection = <TItem,>(
|
|
|
84
88
|
onCommitEffect,
|
|
85
89
|
horizontal,
|
|
86
90
|
getAdjustmentMargin,
|
|
91
|
+
currentStickyIndex,
|
|
92
|
+
hideStickyHeaderRelatedCell,
|
|
87
93
|
} = props;
|
|
88
94
|
|
|
89
95
|
const [renderId, setRenderId] = React.useState(0);
|
|
@@ -168,6 +174,7 @@ export const ViewHolderCollection = <TItem,>(
|
|
|
168
174
|
const trailingItem = ItemSeparatorComponent
|
|
169
175
|
? data[index + 1]
|
|
170
176
|
: undefined;
|
|
177
|
+
|
|
171
178
|
return (
|
|
172
179
|
<ViewHolder
|
|
173
180
|
key={reactKey}
|
|
@@ -185,6 +192,9 @@ export const ViewHolderCollection = <TItem,>(
|
|
|
185
192
|
CellRendererComponent={CellRendererComponent}
|
|
186
193
|
ItemSeparatorComponent={ItemSeparatorComponent}
|
|
187
194
|
horizontal={horizontal}
|
|
195
|
+
hidden={
|
|
196
|
+
hideStickyHeaderRelatedCell && currentStickyIndex === index
|
|
197
|
+
}
|
|
188
198
|
/>
|
|
189
199
|
);
|
|
190
200
|
})}
|
|
@@ -27,6 +27,10 @@ import { CompatAnimatedView } from "./CompatView";
|
|
|
27
27
|
export interface StickyHeaderProps<TItem> {
|
|
28
28
|
/** Array of indices that should have sticky headers */
|
|
29
29
|
stickyHeaderIndices: number[];
|
|
30
|
+
/** Offset from the top where sticky headers should stick (in pixels) */
|
|
31
|
+
stickyHeaderOffset: number;
|
|
32
|
+
/** Sticky header change handler */
|
|
33
|
+
onChangeStickyIndex: (index: number) => void;
|
|
30
34
|
/** The data array being rendered */
|
|
31
35
|
data: ReadonlyArray<TItem>;
|
|
32
36
|
/** Animated value tracking scroll position */
|
|
@@ -56,12 +60,14 @@ interface StickyHeaderState {
|
|
|
56
60
|
|
|
57
61
|
export const StickyHeaders = <TItem,>({
|
|
58
62
|
stickyHeaderIndices,
|
|
63
|
+
stickyHeaderOffset,
|
|
59
64
|
renderItem,
|
|
60
65
|
stickyHeaderRef,
|
|
61
66
|
recyclerViewManager,
|
|
62
67
|
scrollY,
|
|
63
68
|
data,
|
|
64
69
|
extraData,
|
|
70
|
+
onChangeStickyIndex,
|
|
65
71
|
}: StickyHeaderProps<TItem>) => {
|
|
66
72
|
const [stickyHeaderState, setStickyHeaderState] = useState<StickyHeaderState>(
|
|
67
73
|
{
|
|
@@ -91,7 +97,7 @@ export const StickyHeaders = <TItem,>({
|
|
|
91
97
|
// Binary search for current sticky index
|
|
92
98
|
const currentIndexInArray = findCurrentStickyIndex(
|
|
93
99
|
sortedIndices,
|
|
94
|
-
adjustedScrollOffset,
|
|
100
|
+
adjustedScrollOffset + stickyHeaderOffset,
|
|
95
101
|
(index) => recyclerViewManager.getLayout(index).y
|
|
96
102
|
);
|
|
97
103
|
|
|
@@ -102,7 +108,8 @@ export const StickyHeaders = <TItem,>({
|
|
|
102
108
|
newNextStickyIndex = -1;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
//
|
|
111
|
+
// Calculate when the next sticky header should start pushing the current one
|
|
112
|
+
// The next header starts pushing when it reaches the bottom of the current sticky header
|
|
106
113
|
const newNextStickyY =
|
|
107
114
|
newNextStickyIndex === -1
|
|
108
115
|
? Number.MAX_SAFE_INTEGER
|
|
@@ -111,6 +118,7 @@ export const StickyHeaders = <TItem,>({
|
|
|
111
118
|
const newCurrentStickyHeight =
|
|
112
119
|
recyclerViewManager.tryGetLayout(newStickyIndex)?.height ?? 0;
|
|
113
120
|
|
|
121
|
+
// Push should start when the next header reaches the bottom of the current sticky header
|
|
114
122
|
const newPushStartsAt = newNextStickyY - newCurrentStickyHeight;
|
|
115
123
|
|
|
116
124
|
if (
|
|
@@ -119,15 +127,21 @@ export const StickyHeaders = <TItem,>({
|
|
|
119
127
|
) {
|
|
120
128
|
setStickyHeaderState({
|
|
121
129
|
currentStickyIndex: newStickyIndex,
|
|
122
|
-
pushStartsAt: newPushStartsAt,
|
|
130
|
+
pushStartsAt: newPushStartsAt - stickyHeaderOffset,
|
|
123
131
|
});
|
|
124
132
|
}
|
|
133
|
+
|
|
134
|
+
if (newStickyIndex !== currentStickyIndex) {
|
|
135
|
+
onChangeStickyIndex?.(newStickyIndex);
|
|
136
|
+
}
|
|
125
137
|
}, [
|
|
126
138
|
legthInvalid,
|
|
127
139
|
recyclerViewManager,
|
|
128
140
|
sortedIndices,
|
|
129
141
|
currentStickyIndex,
|
|
130
142
|
pushStartsAt,
|
|
143
|
+
onChangeStickyIndex,
|
|
144
|
+
stickyHeaderOffset,
|
|
131
145
|
]);
|
|
132
146
|
|
|
133
147
|
useEffect(() => {
|
|
@@ -147,16 +161,32 @@ export const StickyHeaders = <TItem,>({
|
|
|
147
161
|
|
|
148
162
|
const refHolder = useRef(new Map()).current;
|
|
149
163
|
|
|
150
|
-
const translateY = useMemo(() => {
|
|
164
|
+
const { translateY, opacity } = useMemo(() => {
|
|
151
165
|
const currentStickyHeight =
|
|
152
166
|
recyclerViewManager.tryGetLayout(currentStickyIndex)?.height ?? 0;
|
|
153
167
|
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
168
|
+
return {
|
|
169
|
+
translateY: scrollY.interpolate({
|
|
170
|
+
inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
|
|
171
|
+
outputRange: [0, -currentStickyHeight],
|
|
172
|
+
extrapolate: "clamp",
|
|
173
|
+
}),
|
|
174
|
+
opacity:
|
|
175
|
+
stickyHeaderOffset > 0
|
|
176
|
+
? scrollY.interpolate({
|
|
177
|
+
inputRange: [pushStartsAt, pushStartsAt + currentStickyHeight],
|
|
178
|
+
outputRange: [1, 0],
|
|
179
|
+
extrapolate: "clamp",
|
|
180
|
+
})
|
|
181
|
+
: undefined,
|
|
182
|
+
};
|
|
183
|
+
}, [
|
|
184
|
+
recyclerViewManager,
|
|
185
|
+
currentStickyIndex,
|
|
186
|
+
scrollY,
|
|
187
|
+
pushStartsAt,
|
|
188
|
+
stickyHeaderOffset,
|
|
189
|
+
]);
|
|
160
190
|
|
|
161
191
|
// Memoize header content
|
|
162
192
|
const headerContent = useMemo(() => {
|
|
@@ -164,11 +194,12 @@ export const StickyHeaders = <TItem,>({
|
|
|
164
194
|
<CompatAnimatedView
|
|
165
195
|
style={{
|
|
166
196
|
position: "absolute",
|
|
167
|
-
top:
|
|
197
|
+
top: stickyHeaderOffset,
|
|
168
198
|
left: 0,
|
|
169
199
|
right: 0,
|
|
170
|
-
zIndex:
|
|
200
|
+
zIndex: 2,
|
|
171
201
|
transform: [{ translateY }],
|
|
202
|
+
opacity,
|
|
172
203
|
}}
|
|
173
204
|
>
|
|
174
205
|
{currentStickyIndex !== -1 && currentStickyIndex < data.length ? (
|
|
@@ -181,11 +212,21 @@ export const StickyHeaders = <TItem,>({
|
|
|
181
212
|
extraData={extraData}
|
|
182
213
|
trailingItem={null}
|
|
183
214
|
target="StickyHeader"
|
|
215
|
+
hidden={false}
|
|
184
216
|
/>
|
|
185
217
|
) : null}
|
|
186
218
|
</CompatAnimatedView>
|
|
187
219
|
);
|
|
188
|
-
}, [
|
|
220
|
+
}, [
|
|
221
|
+
translateY,
|
|
222
|
+
opacity,
|
|
223
|
+
currentStickyIndex,
|
|
224
|
+
data,
|
|
225
|
+
renderItem,
|
|
226
|
+
refHolder,
|
|
227
|
+
extraData,
|
|
228
|
+
stickyHeaderOffset,
|
|
229
|
+
]);
|
|
189
230
|
|
|
190
231
|
return headerContent;
|
|
191
232
|
};
|
|
@@ -289,7 +289,7 @@ export function useRecyclerViewController<T>(
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
setTimeout(() => {
|
|
292
|
-
scrollViewRef.current
|
|
292
|
+
scrollViewRef.current?.scrollToEnd({ animated });
|
|
293
293
|
}, 0);
|
|
294
294
|
},
|
|
295
295
|
|
|
@@ -564,7 +564,8 @@ export function useRecyclerViewController<T>(
|
|
|
564
564
|
]);
|
|
565
565
|
|
|
566
566
|
const applyInitialScrollIndex = useCallback(() => {
|
|
567
|
-
const { horizontal, data } =
|
|
567
|
+
const { horizontal, data, initialScrollIndexParams } =
|
|
568
|
+
recyclerViewManager.props;
|
|
568
569
|
|
|
569
570
|
const initialScrollIndex =
|
|
570
571
|
recyclerViewManager.getInitialScrollIndex() ?? -1;
|
|
@@ -583,9 +584,11 @@ export function useRecyclerViewController<T>(
|
|
|
583
584
|
|
|
584
585
|
pauseOffsetCorrection.current = true;
|
|
585
586
|
|
|
587
|
+
const additionalOffset = initialScrollIndexParams?.viewOffset ?? 0;
|
|
586
588
|
const offset = horizontal
|
|
587
|
-
? recyclerViewManager.getLayout(initialScrollIndex).x
|
|
588
|
-
: recyclerViewManager.getLayout(initialScrollIndex).y
|
|
589
|
+
? recyclerViewManager.getLayout(initialScrollIndex).x + additionalOffset
|
|
590
|
+
: recyclerViewManager.getLayout(initialScrollIndex).y +
|
|
591
|
+
additionalOffset;
|
|
589
592
|
handlerMethods.scrollToOffset({
|
|
590
593
|
offset,
|
|
591
594
|
animated: false,
|
|
@@ -20,6 +20,7 @@ import { CompatAnimatedScroller } from "../components/CompatScroller";
|
|
|
20
20
|
* - renderHeader: The header component renderer
|
|
21
21
|
* - renderFooter: The footer component renderer
|
|
22
22
|
* - renderEmpty: The empty state component renderer
|
|
23
|
+
* - renderStickyHeaderBackdrop: The sticky header backdrop component renderer
|
|
23
24
|
* - CompatScrollView: The animated scroll component
|
|
24
25
|
*/
|
|
25
26
|
export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
|
|
@@ -35,6 +36,7 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
|
|
|
35
36
|
onRefresh,
|
|
36
37
|
data,
|
|
37
38
|
refreshControl: customRefreshControl,
|
|
39
|
+
stickyHeaderConfig,
|
|
38
40
|
} = props;
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -94,6 +96,26 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
|
|
|
94
96
|
return getValidComponent(ListEmptyComponent);
|
|
95
97
|
}, [ListEmptyComponent, data]);
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Creates the sticky header backdrop component.
|
|
101
|
+
*/
|
|
102
|
+
const renderStickyHeaderBackdrop = useMemo(() => {
|
|
103
|
+
if (!stickyHeaderConfig?.backdropComponent) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return (
|
|
107
|
+
<CompatView
|
|
108
|
+
style={{
|
|
109
|
+
position: "absolute",
|
|
110
|
+
inset: 0,
|
|
111
|
+
pointerEvents: "none",
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{getValidComponent(stickyHeaderConfig?.backdropComponent)}
|
|
115
|
+
</CompatView>
|
|
116
|
+
);
|
|
117
|
+
}, [stickyHeaderConfig?.backdropComponent]);
|
|
118
|
+
|
|
97
119
|
/**
|
|
98
120
|
* Creates an animated scroll component based on the provided renderScrollComponent.
|
|
99
121
|
* If no custom component is provided, uses the default CompatAnimatedScroller.
|
|
@@ -120,5 +142,6 @@ export function useSecondaryProps<T>(props: RecyclerViewProps<T>) {
|
|
|
120
142
|
renderFooter,
|
|
121
143
|
renderEmpty,
|
|
122
144
|
CompatScrollView,
|
|
145
|
+
renderStickyHeaderBackdrop,
|
|
123
146
|
};
|
|
124
147
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AverageWindow.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/AverageWindow.test.ts"],"names":[],"mappings":""}
|