@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,6 +8,10 @@ export interface RVEngagedIndicesTracker {
|
|
|
8
8
|
scrollOffset: number;
|
|
9
9
|
// Total distance (in pixels) to pre-render items before and after the visible viewport
|
|
10
10
|
drawDistance: number;
|
|
11
|
+
// Whether to use offset projection to predict the next scroll offset
|
|
12
|
+
enableOffsetProjection: boolean;
|
|
13
|
+
// Average render time of the list
|
|
14
|
+
averageRenderTime: number;
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Updates the scroll offset and calculates which items should be rendered (engaged indices).
|
|
@@ -21,9 +25,31 @@ export interface RVEngagedIndicesTracker {
|
|
|
21
25
|
velocity: Velocity | null | undefined,
|
|
22
26
|
layoutManager: RVLayoutManager
|
|
23
27
|
) => ConsecutiveNumbers | undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the currently engaged (rendered) indices.
|
|
31
|
+
* This includes both visible items and buffer items.
|
|
32
|
+
* @returns The last computed set of engaged indices
|
|
33
|
+
*/
|
|
24
34
|
getEngagedIndices: () => ConsecutiveNumbers;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Computes the visible indices in the viewport.
|
|
38
|
+
* @param layoutManager - Layout manager to fetch item positions and dimensions
|
|
39
|
+
* @returns Indices of items currently visible in the viewport
|
|
40
|
+
*/
|
|
25
41
|
computeVisibleIndices: (layoutManager: RVLayoutManager) => ConsecutiveNumbers;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the scroll direction for velocity history tracking.
|
|
45
|
+
* @param scrollDirection - The direction of scrolling ("forward" or "backward")
|
|
46
|
+
*/
|
|
26
47
|
setScrollDirection: (scrollDirection: "forward" | "backward") => void;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resets the velocity history based on the current scroll direction.
|
|
51
|
+
*/
|
|
52
|
+
resetVelocityHistory: () => void;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
export interface Velocity {
|
|
@@ -35,17 +61,26 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
35
61
|
// Current scroll position of the list
|
|
36
62
|
public scrollOffset = 0;
|
|
37
63
|
// Distance to pre-render items before and after the visible viewport (in pixels)
|
|
38
|
-
// TODO: Increase this value for web
|
|
39
64
|
public drawDistance = PlatformConfig.defaultDrawDistance;
|
|
65
|
+
|
|
66
|
+
// Whether to use offset projection to predict the next scroll offset
|
|
67
|
+
public enableOffsetProjection = true;
|
|
68
|
+
|
|
69
|
+
// Average render time of the list
|
|
70
|
+
public averageRenderTime = 16;
|
|
71
|
+
|
|
72
|
+
// Internal override to disable offset projection
|
|
73
|
+
private forceDisableOffsetProjection = false;
|
|
74
|
+
|
|
40
75
|
// Currently rendered item indices (including buffer items)
|
|
41
76
|
private engagedIndices = ConsecutiveNumbers.EMPTY;
|
|
42
77
|
|
|
43
78
|
// Buffer distribution multipliers for scroll direction optimization
|
|
44
|
-
private smallMultiplier = 0.
|
|
45
|
-
private largeMultiplier = 0.
|
|
79
|
+
private smallMultiplier = 0.3; // Used for buffer in the opposite direction of scroll
|
|
80
|
+
private largeMultiplier = 0.7; // Used for buffer in the direction of scroll
|
|
46
81
|
|
|
47
82
|
// Circular buffer to track recent scroll velocities for direction detection
|
|
48
|
-
private velocityHistory = [
|
|
83
|
+
private velocityHistory = [0, 0, 0, -0.1, -0.1];
|
|
49
84
|
private velocityIndex = 0;
|
|
50
85
|
|
|
51
86
|
/**
|
|
@@ -67,7 +102,21 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
67
102
|
// STEP 1: Determine the currently visible viewport
|
|
68
103
|
const windowSize = layoutManager.getWindowsSize();
|
|
69
104
|
const isHorizontal = layoutManager.isHorizontal();
|
|
70
|
-
|
|
105
|
+
|
|
106
|
+
// Update velocity history
|
|
107
|
+
if (velocity) {
|
|
108
|
+
this.updateVelocityHistory(isHorizontal ? velocity.x : velocity.y);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Determine scroll direction to optimize buffer distribution
|
|
112
|
+
const isScrollingBackward = this.isScrollingBackward();
|
|
113
|
+
const viewportStart =
|
|
114
|
+
this.enableOffsetProjection && !this.forceDisableOffsetProjection
|
|
115
|
+
? this.getProjectedScrollOffset(offset, this.averageRenderTime)
|
|
116
|
+
: offset;
|
|
117
|
+
|
|
118
|
+
// console.log("timeMs", this.averageRenderTime, offset, viewportStart);
|
|
119
|
+
|
|
71
120
|
const viewportSize = isHorizontal ? windowSize.width : windowSize.height;
|
|
72
121
|
const viewportEnd = viewportStart + viewportSize;
|
|
73
122
|
|
|
@@ -75,11 +124,6 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
75
124
|
// The total extra space where items will be pre-rendered
|
|
76
125
|
const totalBuffer = this.drawDistance * 2;
|
|
77
126
|
|
|
78
|
-
// Determine scroll direction to optimize buffer distribution
|
|
79
|
-
const isScrollingBackward = this.isScrollingBackward(
|
|
80
|
-
isHorizontal ? velocity?.x : velocity?.y
|
|
81
|
-
);
|
|
82
|
-
|
|
83
127
|
// Distribute more buffer in the direction of scrolling
|
|
84
128
|
// When scrolling forward: more buffer after viewport
|
|
85
129
|
// When scrolling backward: more buffer before viewport
|
|
@@ -123,9 +167,12 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
123
167
|
extendedStart,
|
|
124
168
|
extendedEnd
|
|
125
169
|
);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
170
|
+
// console.log(
|
|
171
|
+
// "newEngagedIndices",
|
|
172
|
+
// newEngagedIndices,
|
|
173
|
+
// this.scrollOffset,
|
|
174
|
+
// viewportStart
|
|
175
|
+
// );
|
|
129
176
|
// Only return new indices if they've changed
|
|
130
177
|
const oldEngagedIndices = this.engagedIndices;
|
|
131
178
|
this.engagedIndices = newEngagedIndices;
|
|
@@ -135,19 +182,21 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
135
182
|
: newEngagedIndices;
|
|
136
183
|
}
|
|
137
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Updates the velocity history with a new velocity value.
|
|
187
|
+
* @param velocity - Current scroll velocity component (x or y)
|
|
188
|
+
*/
|
|
189
|
+
private updateVelocityHistory(velocity: number) {
|
|
190
|
+
this.velocityHistory[this.velocityIndex] = velocity;
|
|
191
|
+
this.velocityIndex = (this.velocityIndex + 1) % this.velocityHistory.length;
|
|
192
|
+
}
|
|
193
|
+
|
|
138
194
|
/**
|
|
139
195
|
* Determines scroll direction by analyzing recent velocity history.
|
|
140
196
|
* Uses a majority voting system on the last 5 velocity values.
|
|
141
|
-
* @param velocity - Current scroll velocity component (x or y)
|
|
142
197
|
* @returns true if scrolling backward (negative direction), false otherwise
|
|
143
198
|
*/
|
|
144
|
-
private isScrollingBackward(
|
|
145
|
-
// update velocity history
|
|
146
|
-
if (velocity) {
|
|
147
|
-
this.velocityHistory[this.velocityIndex] = velocity;
|
|
148
|
-
this.velocityIndex =
|
|
149
|
-
(this.velocityIndex + 1) % this.velocityHistory.length;
|
|
150
|
-
}
|
|
199
|
+
private isScrollingBackward(): boolean {
|
|
151
200
|
// should decide based on whether we have more positive or negative values, use for loop
|
|
152
201
|
let positiveCount = 0;
|
|
153
202
|
let negativeCount = 0;
|
|
@@ -162,6 +211,40 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
162
211
|
return positiveCount < negativeCount;
|
|
163
212
|
}
|
|
164
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Calculates the median velocity based on velocity history
|
|
216
|
+
* Medina works better agains outliers
|
|
217
|
+
* @returns Median velocity over the recent history
|
|
218
|
+
*/
|
|
219
|
+
private getMedianVelocity(): number {
|
|
220
|
+
// Make a copy of velocity history and sort it
|
|
221
|
+
const sortedVelocities = [...this.velocityHistory].sort(
|
|
222
|
+
(valueA, valueB) => valueA - valueB
|
|
223
|
+
);
|
|
224
|
+
const length = sortedVelocities.length;
|
|
225
|
+
|
|
226
|
+
// If length is odd, return the middle element
|
|
227
|
+
if (length % 2 === 1) {
|
|
228
|
+
return sortedVelocities[Math.floor(length / 2)];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// If length is even, return the average of the two middle elements
|
|
232
|
+
const midIndex = length / 2;
|
|
233
|
+
return (sortedVelocities[midIndex - 1] + sortedVelocities[midIndex]) / 2;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Projects the next scroll offset based on median velocity
|
|
238
|
+
* @param timeMs Time in milliseconds to predict ahead
|
|
239
|
+
* @returns Projected scroll offset
|
|
240
|
+
*/
|
|
241
|
+
private getProjectedScrollOffset(offset: number, timeMs: number): number {
|
|
242
|
+
const medianVelocity = this.getMedianVelocity();
|
|
243
|
+
// Convert time from ms to seconds for velocity calculation
|
|
244
|
+
// Predict next position: current position + (velocity * time)
|
|
245
|
+
return offset + medianVelocity * timeMs;
|
|
246
|
+
}
|
|
247
|
+
|
|
165
248
|
/**
|
|
166
249
|
* Calculates which items are currently visible in the viewport.
|
|
167
250
|
* Unlike getEngagedIndices, this doesn't include buffer items.
|
|
@@ -196,11 +279,23 @@ export class RVEngagedIndicesTrackerImpl implements RVEngagedIndicesTracker {
|
|
|
196
279
|
|
|
197
280
|
setScrollDirection(scrollDirection: "forward" | "backward") {
|
|
198
281
|
if (scrollDirection === "forward") {
|
|
199
|
-
this.velocityHistory = [
|
|
282
|
+
this.velocityHistory = [0, 0, 0, 0.1, 0.1];
|
|
200
283
|
this.velocityIndex = 0;
|
|
201
284
|
} else {
|
|
202
|
-
this.velocityHistory = [
|
|
285
|
+
this.velocityHistory = [0, 0, 0, -0.1, -0.1];
|
|
203
286
|
this.velocityIndex = 0;
|
|
204
287
|
}
|
|
205
288
|
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Resets the velocity history based on the current scroll direction.
|
|
292
|
+
* This ensures that the velocity history is always in sync with the current scroll direction.
|
|
293
|
+
*/
|
|
294
|
+
resetVelocityHistory() {
|
|
295
|
+
if (this.isScrollingBackward()) {
|
|
296
|
+
this.setScrollDirection("backward");
|
|
297
|
+
} else {
|
|
298
|
+
this.setScrollDirection("forward");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
206
301
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PlatformConfig } from "../../native/config/PlatformHelper";
|
|
2
|
+
import { AverageWindow } from "../../utils/AverageWindow";
|
|
3
|
+
|
|
4
|
+
export class RenderTimeTracker {
|
|
5
|
+
private renderTimeAvgWindow = new AverageWindow(5);
|
|
6
|
+
private lastTimerStartedAt = -1;
|
|
7
|
+
private maxRenderTime = 32; // TODO: Improve this even more
|
|
8
|
+
private defaultRenderTime = 16;
|
|
9
|
+
|
|
10
|
+
startTracking() {
|
|
11
|
+
if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (this.lastTimerStartedAt === -1) {
|
|
15
|
+
this.lastTimerStartedAt = Date.now();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
markRenderComplete() {
|
|
20
|
+
if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (this.lastTimerStartedAt !== -1) {
|
|
24
|
+
this.renderTimeAvgWindow.addValue(Date.now() - this.lastTimerStartedAt);
|
|
25
|
+
this.lastTimerStartedAt = -1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getRawValue() {
|
|
30
|
+
return this.renderTimeAvgWindow.currentValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getAverageRenderTime() {
|
|
34
|
+
if (!PlatformConfig.trackAverageRenderTimeForOffsetProjection) {
|
|
35
|
+
return this.defaultRenderTime;
|
|
36
|
+
}
|
|
37
|
+
return Math.min(
|
|
38
|
+
this.maxRenderTime,
|
|
39
|
+
Math.max(Math.round(this.renderTimeAvgWindow.currentValue), 16)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks and calculates velocity for scroll/drag movements
|
|
3
|
+
* Used to determine momentum scrolling behavior
|
|
4
|
+
*/
|
|
5
|
+
export class VelocityTracker<T> {
|
|
6
|
+
/** Timestamp of the last velocity update */
|
|
7
|
+
private lastUpdateTime = Date.now();
|
|
8
|
+
/** Current velocity vector with x and y components */
|
|
9
|
+
private velocity = { x: 0, y: 0 };
|
|
10
|
+
|
|
11
|
+
/** Reference to the momentum end timeout */
|
|
12
|
+
private timeoutId: NodeJS.Timeout | null = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calculates velocity based on position change over time
|
|
16
|
+
* @param newOffset Current position value
|
|
17
|
+
* @param oldOffset Previous position value
|
|
18
|
+
* @param isHorizontal Whether movement is horizontal (true) or vertical (false)
|
|
19
|
+
* @param isRTL Whether layout direction is right-to-left
|
|
20
|
+
* @param callback Function to call with velocity updates and momentum end signal
|
|
21
|
+
*/
|
|
22
|
+
computeVelocity(
|
|
23
|
+
newOffset: number,
|
|
24
|
+
oldOffset: number,
|
|
25
|
+
isHorizontal: boolean,
|
|
26
|
+
callback: (
|
|
27
|
+
velocity: { x: number; y: number },
|
|
28
|
+
isMomentumEnd: boolean
|
|
29
|
+
) => void
|
|
30
|
+
) {
|
|
31
|
+
// Clear any pending momentum end timeout
|
|
32
|
+
this.cleanUp();
|
|
33
|
+
// Calculate time since last update
|
|
34
|
+
const currentTime = Date.now();
|
|
35
|
+
const timeSinceLastUpdate = Math.max(1, currentTime - this.lastUpdateTime);
|
|
36
|
+
|
|
37
|
+
// Calculate velocity as distance/time
|
|
38
|
+
const newVelocity = (newOffset - oldOffset) / timeSinceLastUpdate;
|
|
39
|
+
|
|
40
|
+
// console.log(
|
|
41
|
+
// "newVelocity",
|
|
42
|
+
// newOffset,
|
|
43
|
+
// oldOffset,
|
|
44
|
+
// currentTime,
|
|
45
|
+
// this.lastUpdateTime,
|
|
46
|
+
// timeSinceLastUpdate,
|
|
47
|
+
// newVelocity
|
|
48
|
+
// );
|
|
49
|
+
this.lastUpdateTime = currentTime;
|
|
50
|
+
|
|
51
|
+
// Apply velocity to the correct axis
|
|
52
|
+
this.velocity.x = isHorizontal ? newVelocity : 0;
|
|
53
|
+
this.velocity.y = isHorizontal ? 0 : newVelocity;
|
|
54
|
+
|
|
55
|
+
// Trigger callback with current velocity
|
|
56
|
+
callback(this.velocity, false);
|
|
57
|
+
|
|
58
|
+
// Set timeout to signal momentum end after 100ms of no updates
|
|
59
|
+
this.timeoutId = setTimeout(() => {
|
|
60
|
+
this.cleanUp();
|
|
61
|
+
this.lastUpdateTime = Date.now();
|
|
62
|
+
this.velocity.x = 0;
|
|
63
|
+
this.velocity.y = 0;
|
|
64
|
+
callback(this.velocity, true);
|
|
65
|
+
}, 100);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Cleans up resources by clearing any pending timeout
|
|
70
|
+
*/
|
|
71
|
+
cleanUp() {
|
|
72
|
+
if (this.timeoutId !== null) {
|
|
73
|
+
clearTimeout(this.timeoutId);
|
|
74
|
+
this.timeoutId = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
import { RecyclerViewManager } from "../RecyclerViewManager";
|
|
4
|
-
import { RecyclerViewProps } from "../RecyclerViewProps";
|
|
5
4
|
import { CompatScroller } from "../components/CompatScroller";
|
|
6
5
|
|
|
6
|
+
import { useUnmountAwareAnimationFrame } from "./useUnmountAwareCallbacks";
|
|
7
|
+
|
|
7
8
|
/**
|
|
8
9
|
* Hook to detect when the scroll position reaches near the start or end of the list
|
|
9
10
|
* and trigger the appropriate callbacks. This hook is responsible for:
|
|
@@ -17,7 +18,6 @@ import { CompatScroller } from "../components/CompatScroller";
|
|
|
17
18
|
*/
|
|
18
19
|
export function useBoundDetection<T>(
|
|
19
20
|
recyclerViewManager: RecyclerViewManager<T>,
|
|
20
|
-
props: RecyclerViewProps<T>,
|
|
21
21
|
scrollViewRef: React.RefObject<CompatScroller>
|
|
22
22
|
) {
|
|
23
23
|
// Track whether we've already triggered the end reached callback to prevent duplicate calls
|
|
@@ -26,22 +26,48 @@ export function useBoundDetection<T>(
|
|
|
26
26
|
const pendingStartReached = useRef(false);
|
|
27
27
|
// Track whether we should auto-scroll to bottom when new content is added
|
|
28
28
|
const pendingAutoscrollToBottom = useRef(false);
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
const lastCheckBoundsTime = useRef(Date.now());
|
|
31
|
+
|
|
32
|
+
const { data } = recyclerViewManager.props;
|
|
33
|
+
const { requestAnimationFrame } = useUnmountAwareAnimationFrame();
|
|
34
|
+
|
|
35
|
+
const windowHeight = recyclerViewManager.hasLayout()
|
|
36
|
+
? recyclerViewManager.getWindowSize().height
|
|
37
|
+
: 0;
|
|
38
|
+
|
|
39
|
+
const contentHeight = recyclerViewManager.hasLayout()
|
|
40
|
+
? recyclerViewManager.getChildContainerDimensions().height
|
|
41
|
+
: 0;
|
|
42
|
+
|
|
43
|
+
const windowWidth = recyclerViewManager.hasLayout()
|
|
44
|
+
? recyclerViewManager.getWindowSize().width
|
|
45
|
+
: 0;
|
|
46
|
+
|
|
47
|
+
const contentWidth = recyclerViewManager.hasLayout()
|
|
48
|
+
? recyclerViewManager.getChildContainerDimensions().width
|
|
49
|
+
: 0;
|
|
30
50
|
|
|
31
51
|
/**
|
|
32
52
|
* Checks if the scroll position is near the start or end of the list
|
|
33
53
|
* and triggers appropriate callbacks if configured.
|
|
34
54
|
*/
|
|
35
55
|
const checkBounds = useCallback(() => {
|
|
56
|
+
lastCheckBoundsTime.current = Date.now();
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
onEndReached,
|
|
60
|
+
onStartReached,
|
|
61
|
+
maintainVisibleContentPosition,
|
|
62
|
+
horizontal,
|
|
63
|
+
onEndReachedThreshold: onEndReachedThresholdProp,
|
|
64
|
+
onStartReachedThreshold: onStartReachedThresholdProp,
|
|
65
|
+
} = recyclerViewManager.props;
|
|
36
66
|
// Skip all calculations if neither callback is provided and autoscroll is disabled
|
|
37
67
|
const autoscrollToBottomThreshold =
|
|
38
68
|
maintainVisibleContentPosition?.autoscrollToBottomThreshold ?? -1;
|
|
39
69
|
|
|
40
|
-
if (
|
|
41
|
-
!props.onEndReached &&
|
|
42
|
-
!props.onStartReached &&
|
|
43
|
-
autoscrollToBottomThreshold < 0
|
|
44
|
-
) {
|
|
70
|
+
if (!onEndReached && !onStartReached && autoscrollToBottomThreshold < 0) {
|
|
45
71
|
return;
|
|
46
72
|
}
|
|
47
73
|
|
|
@@ -50,7 +76,7 @@ export function useBoundDetection<T>(
|
|
|
50
76
|
recyclerViewManager.getAbsoluteLastScrollOffset();
|
|
51
77
|
const contentSize = recyclerViewManager.getChildContainerDimensions();
|
|
52
78
|
const windowSize = recyclerViewManager.getWindowSize();
|
|
53
|
-
const isHorizontal =
|
|
79
|
+
const isHorizontal = horizontal === true;
|
|
54
80
|
|
|
55
81
|
// Calculate dimensions based on scroll direction
|
|
56
82
|
const visibleLength = isHorizontal ? windowSize.width : windowSize.height;
|
|
@@ -59,8 +85,8 @@ export function useBoundDetection<T>(
|
|
|
59
85
|
recyclerViewManager.firstItemOffset;
|
|
60
86
|
|
|
61
87
|
// Check if we're near the end of the list
|
|
62
|
-
if (
|
|
63
|
-
const onEndReachedThreshold =
|
|
88
|
+
if (onEndReached) {
|
|
89
|
+
const onEndReachedThreshold = onEndReachedThresholdProp ?? 0.5;
|
|
64
90
|
const endThresholdDistance = onEndReachedThreshold * visibleLength;
|
|
65
91
|
|
|
66
92
|
const isNearEnd =
|
|
@@ -69,27 +95,27 @@ export function useBoundDetection<T>(
|
|
|
69
95
|
|
|
70
96
|
if (isNearEnd && !pendingEndReached.current) {
|
|
71
97
|
pendingEndReached.current = true;
|
|
72
|
-
|
|
98
|
+
onEndReached();
|
|
73
99
|
}
|
|
74
100
|
pendingEndReached.current = isNearEnd;
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
// Check if we're near the start of the list
|
|
78
|
-
if (
|
|
79
|
-
const onStartReachedThreshold =
|
|
104
|
+
if (onStartReached) {
|
|
105
|
+
const onStartReachedThreshold = onStartReachedThresholdProp ?? 0.2;
|
|
80
106
|
const startThresholdDistance = onStartReachedThreshold * visibleLength;
|
|
81
107
|
|
|
82
108
|
const isNearStart = lastScrollOffset <= startThresholdDistance;
|
|
83
109
|
|
|
84
110
|
if (isNearStart && !pendingStartReached.current) {
|
|
85
111
|
pendingStartReached.current = true;
|
|
86
|
-
|
|
112
|
+
onStartReached();
|
|
87
113
|
}
|
|
88
114
|
pendingStartReached.current = isNearStart;
|
|
89
115
|
}
|
|
90
116
|
|
|
91
117
|
// Handle auto-scrolling to bottom for vertical lists
|
|
92
|
-
if (!
|
|
118
|
+
if (!isHorizontal && autoscrollToBottomThreshold >= 0) {
|
|
93
119
|
const autoscrollToBottomThresholdDistance =
|
|
94
120
|
autoscrollToBottomThreshold * visibleLength;
|
|
95
121
|
|
|
@@ -104,22 +130,45 @@ export function useBoundDetection<T>(
|
|
|
104
130
|
}
|
|
105
131
|
}
|
|
106
132
|
}
|
|
107
|
-
}, [recyclerViewManager
|
|
133
|
+
}, [recyclerViewManager]);
|
|
134
|
+
|
|
135
|
+
const runAutoScrollToBottomCheck = useCallback(() => {
|
|
136
|
+
if (pendingAutoscrollToBottom.current) {
|
|
137
|
+
pendingAutoscrollToBottom.current = false;
|
|
138
|
+
requestAnimationFrame(() => {
|
|
139
|
+
const shouldAnimate =
|
|
140
|
+
recyclerViewManager.props.maintainVisibleContentPosition
|
|
141
|
+
?.animateAutoScrollToBottom ?? true;
|
|
142
|
+
scrollViewRef.current?.scrollToEnd({
|
|
143
|
+
animated: shouldAnimate,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}, [requestAnimationFrame, scrollViewRef, recyclerViewManager]);
|
|
108
148
|
|
|
109
149
|
// Reset end reached state when data changes
|
|
110
150
|
useMemo(() => {
|
|
111
151
|
pendingEndReached.current = false;
|
|
152
|
+
// needs to run only when data changes
|
|
153
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
112
154
|
}, [data]);
|
|
113
155
|
|
|
114
156
|
// Auto-scroll to bottom when new content is added and we're near the bottom
|
|
115
157
|
useEffect(() => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
158
|
+
runAutoScrollToBottomCheck();
|
|
159
|
+
}, [data, runAutoScrollToBottomCheck, windowHeight, windowWidth]);
|
|
160
|
+
|
|
161
|
+
// Since content changes frequently, we try and avoid doing the auto scroll during active scrolls
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (Date.now() - lastCheckBoundsTime.current >= 100) {
|
|
164
|
+
runAutoScrollToBottomCheck();
|
|
121
165
|
}
|
|
122
|
-
}, [
|
|
166
|
+
}, [
|
|
167
|
+
contentHeight,
|
|
168
|
+
contentWidth,
|
|
169
|
+
recyclerViewManager.firstItemOffset,
|
|
170
|
+
runAutoScrollToBottomCheck,
|
|
171
|
+
]);
|
|
123
172
|
|
|
124
173
|
return {
|
|
125
174
|
checkBounds,
|
|
@@ -2,6 +2,13 @@ import { useState, useCallback } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { useRecyclerViewContext } from "../RecyclerViewContextProvider";
|
|
4
4
|
|
|
5
|
+
export type LayoutStateSetter<T> = (
|
|
6
|
+
newValue: T | ((prevValue: T) => T),
|
|
7
|
+
skipParentLayout?: boolean
|
|
8
|
+
) => void;
|
|
9
|
+
|
|
10
|
+
export type LayoutStateInitialValue<T> = T | (() => T);
|
|
11
|
+
|
|
5
12
|
/**
|
|
6
13
|
* Custom hook that combines state management with RecyclerView layout updates.
|
|
7
14
|
* This hook provides a way to manage state that affects the layout of the RecyclerView,
|
|
@@ -13,8 +20,8 @@ import { useRecyclerViewContext } from "../RecyclerViewContextProvider";
|
|
|
13
20
|
* - A setter function that updates the state and triggers a layout recalculation
|
|
14
21
|
*/
|
|
15
22
|
export function useLayoutState<T>(
|
|
16
|
-
initialState: T
|
|
17
|
-
): [T,
|
|
23
|
+
initialState: LayoutStateInitialValue<T>
|
|
24
|
+
): [T, LayoutStateSetter<T>] {
|
|
18
25
|
// Initialize state with the provided initial value
|
|
19
26
|
const [state, setState] = useState<T>(initialState);
|
|
20
27
|
// Get the RecyclerView context for layout management
|
|
@@ -28,16 +35,18 @@ export function useLayoutState<T>(
|
|
|
28
35
|
* @param newValue - Either a new state value or a function that receives the previous state
|
|
29
36
|
* and returns the new state
|
|
30
37
|
*/
|
|
31
|
-
const setLayoutState = useCallback(
|
|
32
|
-
(newValue
|
|
38
|
+
const setLayoutState: LayoutStateSetter<T> = useCallback(
|
|
39
|
+
(newValue, skipParentLayout) => {
|
|
33
40
|
// Update the state using either the new value or the result of the updater function
|
|
34
41
|
setState((prevValue) =>
|
|
35
42
|
typeof newValue === "function"
|
|
36
43
|
? (newValue as (prevValue: T) => T)(prevValue)
|
|
37
44
|
: newValue
|
|
38
45
|
);
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
if (!skipParentLayout) {
|
|
47
|
+
// Trigger a layout recalculation in the RecyclerView
|
|
48
|
+
recyclerViewContext?.layout();
|
|
49
|
+
}
|
|
41
50
|
},
|
|
42
51
|
[recyclerViewContext]
|
|
43
52
|
);
|
|
@@ -10,7 +10,7 @@ import { useRecyclerViewContext } from "../RecyclerViewContextProvider";
|
|
|
10
10
|
export const useMappingHelper = () => {
|
|
11
11
|
const recyclerViewContext = useRecyclerViewContext();
|
|
12
12
|
const getMappingKey = useCallback(
|
|
13
|
-
(
|
|
13
|
+
(itemKey: string | number | bigint, index: number) => {
|
|
14
14
|
return recyclerViewContext ? index : itemKey;
|
|
15
15
|
},
|
|
16
16
|
[recyclerViewContext]
|
|
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { RecyclerViewManager } from "../RecyclerViewManager";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useUnmountAwareAnimationFrame } from "./useUnmountAwareCallbacks";
|
|
6
6
|
// import { ToastAndroid } from "react-native";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -22,7 +22,7 @@ export const useOnListLoad = <T>(
|
|
|
22
22
|
const [isLoaded, setIsLoaded] = useState<boolean>(false);
|
|
23
23
|
const dataLength = recyclerViewManager.getDataLength();
|
|
24
24
|
// const dataCollector = useRef<number[]>([]);
|
|
25
|
-
const
|
|
25
|
+
const { requestAnimationFrame } = useUnmountAwareAnimationFrame();
|
|
26
26
|
// Track render cycles by collecting elapsed time on each render
|
|
27
27
|
// useEffect(() => {
|
|
28
28
|
// const elapsedTimeInMs = Date.now() - loadStartTimeRef.current;
|
|
@@ -48,10 +48,8 @@ export const useOnListLoad = <T>(
|
|
|
48
48
|
// console.log("----------> dataCollector", dataCollectorString);
|
|
49
49
|
// console.log("----------> FlashList v2 load in", `${elapsedTimeInMs} ms`);
|
|
50
50
|
requestAnimationFrame(() => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
setIsLoaded(true);
|
|
54
|
-
}
|
|
51
|
+
onLoad?.({ elapsedTimeInMs });
|
|
52
|
+
setIsLoaded(true);
|
|
55
53
|
});
|
|
56
54
|
});
|
|
57
55
|
|