@shopify/flash-list 1.0.1
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/CHANGELOG.md +159 -0
- package/LICENSE.md +7 -0
- package/README.md +65 -0
- package/RNFlashList.podspec +26 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutShadow.kt +94 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt +79 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutViewManager.kt +69 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainer.java +16 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerImpl.kt +16 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerManager.kt +27 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/FlashListPackage.kt +19 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/AutoLayoutShadowTest.kt +146 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/Rect.kt +59 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestCollection.kt +6 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestDataModel.kt +8 -0
- package/android/src/test/resources/LayoutTestData.json +708 -0
- package/dist/AnimatedFlashList.d.ts +6 -0
- package/dist/AnimatedFlashList.d.ts.map +1 -0
- package/dist/AnimatedFlashList.js +8 -0
- package/dist/AnimatedFlashList.js.map +1 -0
- package/dist/FlashList.d.ts +121 -0
- package/dist/FlashList.d.ts.map +1 -0
- package/dist/FlashList.js +502 -0
- package/dist/FlashList.js.map +1 -0
- package/dist/FlashListProps.d.ts +251 -0
- package/dist/FlashListProps.d.ts.map +1 -0
- package/dist/FlashListProps.js +3 -0
- package/dist/FlashListProps.js.map +1 -0
- package/dist/GridLayoutProviderWithProps.d.ts +30 -0
- package/dist/GridLayoutProviderWithProps.d.ts.map +1 -0
- package/dist/GridLayoutProviderWithProps.js +80 -0
- package/dist/GridLayoutProviderWithProps.js.map +1 -0
- package/dist/PureComponentWrapper.d.ts +22 -0
- package/dist/PureComponentWrapper.d.ts.map +1 -0
- package/dist/PureComponentWrapper.js +37 -0
- package/dist/PureComponentWrapper.js.map +1 -0
- package/dist/__tests__/AverageWindow.test.d.ts +2 -0
- package/dist/__tests__/AverageWindow.test.d.ts.map +1 -0
- package/dist/__tests__/AverageWindow.test.js +69 -0
- package/dist/__tests__/AverageWindow.test.js.map +1 -0
- package/dist/__tests__/FlashList.test.d.ts +2 -0
- package/dist/__tests__/FlashList.test.d.ts.map +1 -0
- package/dist/__tests__/FlashList.test.js +656 -0
- package/dist/__tests__/FlashList.test.js.map +1 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts +2 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts.map +1 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.js +133 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -0
- package/dist/__tests__/PlatformHelper.web.test.d.ts +2 -0
- package/dist/__tests__/PlatformHelper.web.test.d.ts.map +1 -0
- package/dist/__tests__/PlatformHelper.web.test.js +25 -0
- package/dist/__tests__/PlatformHelper.web.test.js.map +1 -0
- package/dist/__tests__/ViewabilityHelper.test.d.ts +2 -0
- package/dist/__tests__/ViewabilityHelper.test.d.ts.map +1 -0
- package/dist/__tests__/ViewabilityHelper.test.js +187 -0
- package/dist/__tests__/ViewabilityHelper.test.js.map +1 -0
- package/dist/__tests__/helpers/mountFlashList.d.ts +20 -0
- package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -0
- package/dist/__tests__/helpers/mountFlashList.js +44 -0
- package/dist/__tests__/helpers/mountFlashList.js.map +1 -0
- package/dist/__tests__/useBlankAreaTracker.test.d.ts +2 -0
- package/dist/__tests__/useBlankAreaTracker.test.d.ts.map +1 -0
- package/dist/__tests__/useBlankAreaTracker.test.js +179 -0
- package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -0
- package/dist/benchmark/AutoScrollHelper.d.ts +18 -0
- package/dist/benchmark/AutoScrollHelper.d.ts.map +1 -0
- package/dist/benchmark/AutoScrollHelper.js +68 -0
- package/dist/benchmark/AutoScrollHelper.js.map +1 -0
- package/dist/benchmark/JSFPSMonitor.d.ts +23 -0
- package/dist/benchmark/JSFPSMonitor.d.ts.map +1 -0
- package/dist/benchmark/JSFPSMonitor.js +65 -0
- package/dist/benchmark/JSFPSMonitor.js.map +1 -0
- package/dist/benchmark/roundToDecimalPlaces.d.ts +2 -0
- package/dist/benchmark/roundToDecimalPlaces.d.ts.map +1 -0
- package/dist/benchmark/roundToDecimalPlaces.js +9 -0
- package/dist/benchmark/roundToDecimalPlaces.js.map +1 -0
- package/dist/benchmark/useBenchmark.d.ts +35 -0
- package/dist/benchmark/useBenchmark.d.ts.map +1 -0
- package/dist/benchmark/useBenchmark.js +167 -0
- package/dist/benchmark/useBenchmark.js.map +1 -0
- package/dist/benchmark/useBlankAreaTracker.d.ts +34 -0
- package/dist/benchmark/useBlankAreaTracker.d.ts.map +1 -0
- package/dist/benchmark/useBlankAreaTracker.js +67 -0
- package/dist/benchmark/useBlankAreaTracker.js.map +1 -0
- package/dist/benchmark/useDataMultiplier.d.ts +9 -0
- package/dist/benchmark/useDataMultiplier.d.ts.map +1 -0
- package/dist/benchmark/useDataMultiplier.js +25 -0
- package/dist/benchmark/useDataMultiplier.js.map +1 -0
- package/dist/benchmark/useFlatListBenchmark.d.ts +13 -0
- package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -0
- package/dist/benchmark/useFlatListBenchmark.js +100 -0
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -0
- package/dist/errors/CustomError.d.ts +8 -0
- package/dist/errors/CustomError.d.ts.map +1 -0
- package/dist/errors/CustomError.js +14 -0
- package/dist/errors/CustomError.js.map +1 -0
- package/dist/errors/ExceptionList.d.ts +20 -0
- package/dist/errors/ExceptionList.d.ts.map +1 -0
- package/dist/errors/ExceptionList.js +22 -0
- package/dist/errors/ExceptionList.js.map +1 -0
- package/dist/errors/Warnings.d.ts +10 -0
- package/dist/errors/Warnings.d.ts.map +1 -0
- package/dist/errors/Warnings.js +15 -0
- package/dist/errors/Warnings.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutView.d.ts +21 -0
- package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutView.js +48 -0
- package/dist/native/auto-layout/AutoLayoutView.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts +4 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js +6 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts +5 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js +6 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +14 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js +3 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js.map +1 -0
- package/dist/native/cell-container/CellContainer.d.ts +6 -0
- package/dist/native/cell-container/CellContainer.d.ts.map +1 -0
- package/dist/native/cell-container/CellContainer.js +9 -0
- package/dist/native/cell-container/CellContainer.js.map +1 -0
- package/dist/native/cell-container/CellContainer.web.d.ts +7 -0
- package/dist/native/cell-container/CellContainer.web.d.ts.map +1 -0
- package/dist/native/cell-container/CellContainer.web.js +13 -0
- package/dist/native/cell-container/CellContainer.web.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/AverageWindow.d.ts +21 -0
- package/dist/utils/AverageWindow.d.ts.map +1 -0
- package/dist/utils/AverageWindow.js +49 -0
- package/dist/utils/AverageWindow.js.map +1 -0
- package/dist/utils/PlatformHelper.d.ts +14 -0
- package/dist/utils/PlatformHelper.d.ts.map +1 -0
- package/dist/utils/PlatformHelper.js +16 -0
- package/dist/utils/PlatformHelper.js.map +1 -0
- package/dist/utils/PlatformHelper.web.d.ts +14 -0
- package/dist/utils/PlatformHelper.web.d.ts.map +1 -0
- package/dist/utils/PlatformHelper.web.js +18 -0
- package/dist/utils/PlatformHelper.web.js.map +1 -0
- package/dist/viewability/ViewToken.d.ts +8 -0
- package/dist/viewability/ViewToken.d.ts.map +1 -0
- package/dist/viewability/ViewToken.js +3 -0
- package/dist/viewability/ViewToken.js.map +1 -0
- package/dist/viewability/ViewabilityHelper.d.ts +25 -0
- package/dist/viewability/ViewabilityHelper.d.ts.map +1 -0
- package/dist/viewability/ViewabilityHelper.js +104 -0
- package/dist/viewability/ViewabilityHelper.js.map +1 -0
- package/dist/viewability/ViewabilityManager.d.ts +24 -0
- package/dist/viewability/ViewabilityManager.d.ts.map +1 -0
- package/dist/viewability/ViewabilityManager.js +94 -0
- package/dist/viewability/ViewabilityManager.js.map +1 -0
- package/ios/RNFlashList.xcodeproj/project.pbxproj +3 -0
- package/ios/RNFlashList.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/ios/Sources/AutoLayoutView.swift +218 -0
- package/ios/Sources/AutoLayoutViewManager.m +14 -0
- package/ios/Sources/AutoLayoutViewManager.swift +12 -0
- package/ios/Sources/CellContainer.swift +9 -0
- package/ios/Sources/CellContainerManager.m +8 -0
- package/ios/Sources/CellContainerManager.swift +12 -0
- package/ios/Sources/FlatListPro-Bridging-Header.h +8 -0
- package/ios/Tests/AutoLayoutViewTests.swift +113 -0
- package/jestSetup.js +15 -0
- package/package.json +75 -0
- package/src/AnimatedFlashList.ts +11 -0
- package/src/FlashList.tsx +801 -0
- package/src/FlashListProps.ts +312 -0
- package/src/GridLayoutProviderWithProps.ts +137 -0
- package/src/PureComponentWrapper.tsx +42 -0
- package/src/__tests__/AverageWindow.test.ts +80 -0
- package/src/__tests__/FlashList.test.tsx +738 -0
- package/src/__tests__/GridLayoutProviderWithProps.test.ts +150 -0
- package/src/__tests__/PlatformHelper.web.test.ts +29 -0
- package/src/__tests__/ViewabilityHelper.test.ts +283 -0
- package/src/__tests__/helpers/mountFlashList.tsx +62 -0
- package/src/__tests__/useBlankAreaTracker.test.tsx +206 -0
- package/src/benchmark/AutoScrollHelper.ts +70 -0
- package/src/benchmark/JSFPSMonitor.ts +74 -0
- package/src/benchmark/roundToDecimalPlaces.ts +4 -0
- package/src/benchmark/useBenchmark.ts +240 -0
- package/src/benchmark/useBlankAreaTracker.ts +117 -0
- package/src/benchmark/useDataMultiplier.ts +19 -0
- package/src/benchmark/useFlatListBenchmark.ts +107 -0
- package/src/errors/CustomError.ts +10 -0
- package/src/errors/ExceptionList.ts +23 -0
- package/src/errors/Warnings.ts +18 -0
- package/src/index.ts +32 -0
- package/src/native/auto-layout/AutoLayoutView.tsx +72 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponent.ts +7 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponent.web.ts +8 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponentProps.ts +14 -0
- package/src/native/cell-container/CellContainer.ts +7 -0
- package/src/native/cell-container/CellContainer.web.tsx +9 -0
- package/src/utils/AverageWindow.ts +49 -0
- package/src/utils/PlatformHelper.ts +16 -0
- package/src/utils/PlatformHelper.web.ts +20 -0
- package/src/viewability/ViewToken.ts +7 -0
- package/src/viewability/ViewabilityHelper.ts +162 -0
- package/src/viewability/ViewabilityManager.ts +133 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useCallback, useRef } from "react";
|
|
2
|
+
import { RecyclerListView, RecyclerListViewProps } from "recyclerlistview";
|
|
3
|
+
|
|
4
|
+
import { BlankAreaEvent } from "../native/auto-layout/AutoLayoutView";
|
|
5
|
+
import FlashList from "../FlashList";
|
|
6
|
+
|
|
7
|
+
export interface BlankAreaTrackerResult {
|
|
8
|
+
/**
|
|
9
|
+
* Max blank area displayed
|
|
10
|
+
*/
|
|
11
|
+
maxBlankArea: number;
|
|
12
|
+
/**
|
|
13
|
+
* Sum of all blank area values across all frames
|
|
14
|
+
*/
|
|
15
|
+
cumulativeBlankArea: number;
|
|
16
|
+
}
|
|
17
|
+
export interface BlankAreaTrackerConfig {
|
|
18
|
+
/**
|
|
19
|
+
* When set to true the hook will also sum all negative blank area values.
|
|
20
|
+
* Blank area is negative when list is able to draw faster than the scroll speed.
|
|
21
|
+
*/
|
|
22
|
+
sumNegativeValues?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* By default, the hook ignores blank events for 1s after the list load. This value can be changed using this parameter.
|
|
25
|
+
* Please note that this duration kicks in after the list has loaded and not after the first scroll.
|
|
26
|
+
*/
|
|
27
|
+
startDelayInMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Can be used to track visible blank area in production
|
|
32
|
+
* @param flashListRef Ref to the FlashList component
|
|
33
|
+
* @param onBlankAreaChange This event handler will be called when the blank area changes
|
|
34
|
+
* @param config additional configuration for the blank area tracker
|
|
35
|
+
* @returns blankAreaTrackerResult - maxBlankArea, cumulativeBlankArea this object is mutated and kept up to date. Also returns a callback that needs to be forwarded to FlashList.
|
|
36
|
+
*/
|
|
37
|
+
export function useBlankAreaTracker(
|
|
38
|
+
flashListRef: React.RefObject<FlashList<any>>,
|
|
39
|
+
onBlankAreaChange?: (value: BlankAreaTrackerResult) => void,
|
|
40
|
+
config?: BlankAreaTrackerConfig
|
|
41
|
+
): [BlankAreaTrackerResult, (event: BlankAreaEvent) => void] {
|
|
42
|
+
const startDelay = config?.startDelayInMs ?? 1000;
|
|
43
|
+
const blankAreaResult = useRef({
|
|
44
|
+
maxBlankArea: 0,
|
|
45
|
+
cumulativeBlankArea: 0,
|
|
46
|
+
}).current;
|
|
47
|
+
const waitOperations = useRef({ inProgress: false, complete: false }).current;
|
|
48
|
+
const onBlankAreaChangeRef = useRef(onBlankAreaChange);
|
|
49
|
+
onBlankAreaChangeRef.current = onBlankAreaChange;
|
|
50
|
+
|
|
51
|
+
const blankAreaTracker = useCallback(
|
|
52
|
+
(event: BlankAreaEvent) => {
|
|
53
|
+
// we're ignoring some of the events that will be fired on list load
|
|
54
|
+
// initial events are fired on mount and thus, this won't lead to missing events during scroll
|
|
55
|
+
if (!waitOperations.complete && startDelay > 0) {
|
|
56
|
+
if (!waitOperations.inProgress) {
|
|
57
|
+
waitOperations.inProgress = true;
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
waitOperations.complete = true;
|
|
60
|
+
}, startDelay);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const rlv = flashListRef.current?.recyclerlistview_unsafe;
|
|
65
|
+
const horizontal = Boolean(flashListRef.current?.props.horizontal);
|
|
66
|
+
if (rlv) {
|
|
67
|
+
processBlankAreaChange(
|
|
68
|
+
rlv,
|
|
69
|
+
horizontal,
|
|
70
|
+
blankAreaResult,
|
|
71
|
+
event,
|
|
72
|
+
onBlankAreaChangeRef.current,
|
|
73
|
+
config
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
+
[flashListRef]
|
|
79
|
+
);
|
|
80
|
+
return [blankAreaResult, blankAreaTracker];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function processBlankAreaChange(
|
|
84
|
+
rlv: RecyclerListView<RecyclerListViewProps, any>,
|
|
85
|
+
horizontal: boolean,
|
|
86
|
+
blankAreaResult: BlankAreaTrackerResult,
|
|
87
|
+
event: BlankAreaEvent,
|
|
88
|
+
onBlankAreaChange?: (value: BlankAreaTrackerResult) => void,
|
|
89
|
+
config?: BlankAreaTrackerConfig
|
|
90
|
+
) {
|
|
91
|
+
const listSize = horizontal
|
|
92
|
+
? rlv.getRenderedSize().width
|
|
93
|
+
: rlv.getRenderedSize().height;
|
|
94
|
+
const contentSize = horizontal
|
|
95
|
+
? rlv.getContentDimension().width
|
|
96
|
+
: rlv.getContentDimension().height;
|
|
97
|
+
|
|
98
|
+
// ignores blank events when there isn't enough content to fill the list
|
|
99
|
+
if (contentSize > listSize) {
|
|
100
|
+
const lastMaxBlankArea = blankAreaResult.maxBlankArea;
|
|
101
|
+
const lastCumulativeBlankArea = blankAreaResult.cumulativeBlankArea;
|
|
102
|
+
blankAreaResult.maxBlankArea = Math.max(
|
|
103
|
+
blankAreaResult.maxBlankArea,
|
|
104
|
+
event.blankArea,
|
|
105
|
+
0
|
|
106
|
+
);
|
|
107
|
+
blankAreaResult.cumulativeBlankArea += config?.sumNegativeValues
|
|
108
|
+
? event.blankArea
|
|
109
|
+
: Math.max(event.blankArea, 0);
|
|
110
|
+
if (
|
|
111
|
+
lastCumulativeBlankArea !== blankAreaResult.cumulativeBlankArea ||
|
|
112
|
+
lastMaxBlankArea !== blankAreaResult.maxBlankArea
|
|
113
|
+
) {
|
|
114
|
+
onBlankAreaChange?.(blankAreaResult);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Increases the data size by duplicating it, it's kept in hook format so that in future we can add auto pagination support.
|
|
3
|
+
* If you're using this with FlatList then make sure you remove `keyExtractor` because this method might duplicate ids that might be in the data.
|
|
4
|
+
* @param data The data to duplicate
|
|
5
|
+
* @param count Final count of data to be returned from this hook
|
|
6
|
+
* @returns Multiplied data.
|
|
7
|
+
*/
|
|
8
|
+
export function useDataMultiplier<T>(data: T[], count: number): [T[]] {
|
|
9
|
+
const len = data.length;
|
|
10
|
+
const arr = new Array<T>(count);
|
|
11
|
+
let isObject = false;
|
|
12
|
+
if (typeof data[0] === "object") {
|
|
13
|
+
isObject = true;
|
|
14
|
+
}
|
|
15
|
+
for (let i = 0; i < count; i++) {
|
|
16
|
+
arr[i] = isObject ? { ...data[i % len] } : data[i % len];
|
|
17
|
+
}
|
|
18
|
+
return [arr];
|
|
19
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { FlatList } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { autoScroll, Cancellable } from "./AutoScrollHelper";
|
|
5
|
+
import { JSFPSMonitor } from "./JSFPSMonitor";
|
|
6
|
+
import {
|
|
7
|
+
BenchmarkParams,
|
|
8
|
+
BenchmarkResult,
|
|
9
|
+
getFormattedString,
|
|
10
|
+
} from "./useBenchmark";
|
|
11
|
+
|
|
12
|
+
export interface FlatListBenchmarkParams extends BenchmarkParams {
|
|
13
|
+
targetOffset: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs the benchmark on FlatList and calls the callback method with the result.
|
|
18
|
+
* Target offset is mandatory in params.
|
|
19
|
+
* It's recommended to remove pagination while running the benchmark. Removing the onEndReached callback is the easiest way to do that.
|
|
20
|
+
*/
|
|
21
|
+
export function useFlatListBenchmark(
|
|
22
|
+
flatListRef: React.RefObject<FlatList<any>>,
|
|
23
|
+
callback: (benchmarkResult: BenchmarkResult) => void,
|
|
24
|
+
params: FlatListBenchmarkParams
|
|
25
|
+
) {
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const cancellable = new Cancellable();
|
|
28
|
+
if (flatListRef.current) {
|
|
29
|
+
if (!(Number(flatListRef.current.props.data?.length) > 0)) {
|
|
30
|
+
throw new Error("Data is empty, cannot run benchmark");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const cancelTimeout = setTimeout(async () => {
|
|
34
|
+
const jsFPSMonitor = new JSFPSMonitor();
|
|
35
|
+
jsFPSMonitor.startTracking();
|
|
36
|
+
for (let i = 0; i < (params.repeatCount || 1); i++) {
|
|
37
|
+
await runScrollBenchmark(
|
|
38
|
+
flatListRef,
|
|
39
|
+
params.targetOffset,
|
|
40
|
+
cancellable,
|
|
41
|
+
params.speedMultiplier || 1
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const jsProfilerResponse = jsFPSMonitor.stopAndGetData();
|
|
45
|
+
const result: BenchmarkResult = {
|
|
46
|
+
js: jsProfilerResponse,
|
|
47
|
+
suggestions: [],
|
|
48
|
+
interrupted: cancellable.isCancelled(),
|
|
49
|
+
};
|
|
50
|
+
if (!cancellable.isCancelled()) {
|
|
51
|
+
result.formattedString = getFormattedString(result);
|
|
52
|
+
}
|
|
53
|
+
callback(result);
|
|
54
|
+
}, params.startDelayInMs || 3000);
|
|
55
|
+
return () => {
|
|
56
|
+
clearTimeout(cancelTimeout);
|
|
57
|
+
cancellable.cancel();
|
|
58
|
+
};
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, []);
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Scrolls to the target offset and then back to 0
|
|
66
|
+
*/
|
|
67
|
+
async function runScrollBenchmark(
|
|
68
|
+
flatListRef: React.RefObject<FlatList<any> | null | undefined>,
|
|
69
|
+
targetOffset: number,
|
|
70
|
+
cancellable: Cancellable,
|
|
71
|
+
scrollSpeedMultiplier: number
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
if (flatListRef.current) {
|
|
74
|
+
const horizontal = flatListRef.current.props.horizontal;
|
|
75
|
+
|
|
76
|
+
const fromX = 0;
|
|
77
|
+
const fromY = 0;
|
|
78
|
+
const toX = horizontal ? targetOffset : 0;
|
|
79
|
+
const toY = horizontal ? 0 : targetOffset;
|
|
80
|
+
|
|
81
|
+
const scrollNow = (x: number, y: number) => {
|
|
82
|
+
flatListRef.current?.scrollToOffset({
|
|
83
|
+
offset: horizontal ? x : y,
|
|
84
|
+
animated: false,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await autoScroll(
|
|
89
|
+
scrollNow,
|
|
90
|
+
fromX,
|
|
91
|
+
fromY,
|
|
92
|
+
toX,
|
|
93
|
+
toY,
|
|
94
|
+
scrollSpeedMultiplier,
|
|
95
|
+
cancellable
|
|
96
|
+
);
|
|
97
|
+
await autoScroll(
|
|
98
|
+
scrollNow,
|
|
99
|
+
toX,
|
|
100
|
+
toY,
|
|
101
|
+
fromX,
|
|
102
|
+
fromY,
|
|
103
|
+
scrollSpeedMultiplier,
|
|
104
|
+
cancellable
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const ExceptionList = {
|
|
2
|
+
refreshBooleanMissing: {
|
|
3
|
+
message:
|
|
4
|
+
"`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `undefined`.",
|
|
5
|
+
type: "InvariantViolation",
|
|
6
|
+
},
|
|
7
|
+
stickyWhileHorizontalNotSupported: {
|
|
8
|
+
message:
|
|
9
|
+
"sticky headers are not supported when list is in horizontal mode. Remove `stickyHeaderIndices` prop.",
|
|
10
|
+
type: "NotSupportedException",
|
|
11
|
+
},
|
|
12
|
+
columnsWhileHorizontalNotSupported: {
|
|
13
|
+
message:
|
|
14
|
+
"numColumns is not supported when list is in horizontal mode. Please remove or set numColumns to 1.",
|
|
15
|
+
type: "NotSupportedException",
|
|
16
|
+
},
|
|
17
|
+
multipleViewabilityThresholdTypesNotSupported: {
|
|
18
|
+
message:
|
|
19
|
+
"You can set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold. Specifying both is not supported.",
|
|
20
|
+
type: "MultipleViewabilityThresholdTypesException",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export default ExceptionList;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const WarningList = {
|
|
2
|
+
styleUnsupported:
|
|
3
|
+
"You have passed a style to FlashList. This list doesn't support styling, use contentContainerStyle or wrap the list in a parent and apply style to it instead.",
|
|
4
|
+
styleContentContainerUnsupported:
|
|
5
|
+
"FlashList only supports padding related props and backgroundColor in contentContainerStyle." +
|
|
6
|
+
" Please remove other values as they're not used. In case of vertical lists horizontal padding is ignored and vice versa, if you need it apply padding to your items instead.",
|
|
7
|
+
styleUnsupportedPaddingType:
|
|
8
|
+
"FlashList will ignore horizontal padding in case of vertical lists and vertical padding if the list is horizontal. If you need to have it apply relevant padding to your items instead.",
|
|
9
|
+
unusableRenderedSize:
|
|
10
|
+
"FlashList's rendered size is not usable. Either the height or width is too small (<2px). " +
|
|
11
|
+
"Please make sure that the parent view of the list has a valid size. FlashList will match the size of the parent.",
|
|
12
|
+
missingKeyExtractor:
|
|
13
|
+
"FlashList requires a keyExtractor prop to be defined when animating elements. Without it, the animations will not run as expected.",
|
|
14
|
+
estimatedItemSizeMissingWarning:
|
|
15
|
+
"estimatedItemSize FlashList prop is not defined - based on current configuration you can set it to @size to optimize list performance. " +
|
|
16
|
+
"Refer to FlashList documentation for more details.",
|
|
17
|
+
};
|
|
18
|
+
export default WarningList;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { default as FlashList } from "./FlashList";
|
|
2
|
+
export {
|
|
3
|
+
FlashListProps,
|
|
4
|
+
ContentStyle,
|
|
5
|
+
ListRenderItem,
|
|
6
|
+
ListRenderItemInfo,
|
|
7
|
+
} from "./FlashListProps";
|
|
8
|
+
export { default as AnimatedFlashList } from "./AnimatedFlashList";
|
|
9
|
+
export {
|
|
10
|
+
useOnNativeBlankAreaEvents,
|
|
11
|
+
BlankAreaEventHandler,
|
|
12
|
+
BlankAreaEvent,
|
|
13
|
+
} from "./native/auto-layout/AutoLayoutView";
|
|
14
|
+
export {
|
|
15
|
+
useBenchmark,
|
|
16
|
+
BenchmarkParams,
|
|
17
|
+
BenchmarkResult,
|
|
18
|
+
} from "./benchmark/useBenchmark";
|
|
19
|
+
export { useDataMultiplier } from "./benchmark/useDataMultiplier";
|
|
20
|
+
export {
|
|
21
|
+
useFlatListBenchmark,
|
|
22
|
+
FlatListBenchmarkParams,
|
|
23
|
+
} from "./benchmark/useFlatListBenchmark";
|
|
24
|
+
export {
|
|
25
|
+
useBlankAreaTracker,
|
|
26
|
+
BlankAreaTrackerResult,
|
|
27
|
+
BlankAreaTrackerConfig,
|
|
28
|
+
} from "./benchmark/useBlankAreaTracker";
|
|
29
|
+
export { JSFPSMonitor, JSFPSResult } from "./benchmark/JSFPSMonitor";
|
|
30
|
+
export { autoScroll, Cancellable } from "./benchmark/AutoScrollHelper";
|
|
31
|
+
export { default as ViewToken } from "./viewability/ViewToken";
|
|
32
|
+
export { default as CellContainer } from "./native/cell-container/CellContainer";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { LayoutChangeEvent } from "react-native";
|
|
3
|
+
|
|
4
|
+
import AutoLayoutViewNativeComponent from "./AutoLayoutViewNativeComponent";
|
|
5
|
+
import { OnBlankAreaEvent } from "./AutoLayoutViewNativeComponentProps";
|
|
6
|
+
|
|
7
|
+
export type BlankAreaEventHandler = (blankAreaEvent: BlankAreaEvent) => void;
|
|
8
|
+
const listeners: BlankAreaEventHandler[] = [];
|
|
9
|
+
|
|
10
|
+
export const useOnNativeBlankAreaEvents = (
|
|
11
|
+
onBlankAreaEvent: (blankAreaEvent: BlankAreaEvent) => void
|
|
12
|
+
) => {
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
listeners.push(onBlankAreaEvent);
|
|
15
|
+
return () => {
|
|
16
|
+
listeners.filter((callback) => callback !== onBlankAreaEvent);
|
|
17
|
+
};
|
|
18
|
+
}, [onBlankAreaEvent]);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface BlankAreaEvent {
|
|
22
|
+
offsetStart: number;
|
|
23
|
+
offsetEnd: number;
|
|
24
|
+
blankArea: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AutoLayoutViewProps {
|
|
28
|
+
onBlankAreaEvent?: BlankAreaEventHandler;
|
|
29
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
30
|
+
disableAutoLayout?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class AutoLayoutView extends React.Component<AutoLayoutViewProps> {
|
|
34
|
+
private onBlankAreaEventCallback = ({
|
|
35
|
+
nativeEvent,
|
|
36
|
+
}: OnBlankAreaEvent): void => {
|
|
37
|
+
const blankArea = Math.max(nativeEvent.offsetStart, nativeEvent.offsetEnd);
|
|
38
|
+
const blankEventValue = {
|
|
39
|
+
blankArea,
|
|
40
|
+
offsetStart: nativeEvent.offsetStart,
|
|
41
|
+
offsetEnd: nativeEvent.offsetEnd,
|
|
42
|
+
};
|
|
43
|
+
this.broadcastBlankEvent(blankEventValue);
|
|
44
|
+
if (this.props.onBlankAreaEvent) {
|
|
45
|
+
this.props.onBlankAreaEvent(blankEventValue);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
private broadcastBlankEvent(value: BlankAreaEvent) {
|
|
50
|
+
const len = listeners.length;
|
|
51
|
+
for (let i = 0; i < len; i++) {
|
|
52
|
+
listeners[i](value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render() {
|
|
57
|
+
return (
|
|
58
|
+
<AutoLayoutViewNativeComponent
|
|
59
|
+
{...this.props}
|
|
60
|
+
onBlankAreaEvent={this.onBlankAreaEventCallback}
|
|
61
|
+
enableInstrumentation={
|
|
62
|
+
listeners.length !== 0 || Boolean(this.props.onBlankAreaEvent)
|
|
63
|
+
}
|
|
64
|
+
disableAutoLayout={this.props.disableAutoLayout}
|
|
65
|
+
>
|
|
66
|
+
{this.props.children}
|
|
67
|
+
</AutoLayoutViewNativeComponent>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default AutoLayoutView;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { requireNativeComponent } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { AutoLayoutViewNativeComponentProps } from "./AutoLayoutViewNativeComponentProps";
|
|
4
|
+
|
|
5
|
+
const AutoLayoutViewNativeComponent =
|
|
6
|
+
requireNativeComponent<AutoLayoutViewNativeComponentProps>("AutoLayoutView");
|
|
7
|
+
export default AutoLayoutViewNativeComponent;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { AutoLayoutViewNativeComponentProps } from "./AutoLayoutViewNativeComponentProps";
|
|
5
|
+
|
|
6
|
+
const AutoLayoutViewNativeComponent =
|
|
7
|
+
View as any as React.Component<AutoLayoutViewNativeComponentProps>;
|
|
8
|
+
export default AutoLayoutViewNativeComponent;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface OnBlankAreaEvent {
|
|
2
|
+
nativeEvent: {
|
|
3
|
+
offsetStart: number;
|
|
4
|
+
offsetEnd: number;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type OnBlankAreaEventHandler = (event: OnBlankAreaEvent) => void;
|
|
9
|
+
|
|
10
|
+
export interface AutoLayoutViewNativeComponentProps {
|
|
11
|
+
onBlankAreaEvent: OnBlankAreaEventHandler;
|
|
12
|
+
enableInstrumentation: boolean;
|
|
13
|
+
disableAutoLayout?: boolean;
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* On web we use a view instead of cell container till we build native web implementations
|
|
4
|
+
*/
|
|
5
|
+
const CellContainer = React.forwardRef((props: any, ref) => {
|
|
6
|
+
return <div ref={ref} {...props} />;
|
|
7
|
+
});
|
|
8
|
+
CellContainer.displayName = "CellContainer";
|
|
9
|
+
export default CellContainer;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper class to calculate running average of the most recent n values
|
|
3
|
+
*/
|
|
4
|
+
export class AverageWindow {
|
|
5
|
+
private currentAverage: number;
|
|
6
|
+
private currentCount: number;
|
|
7
|
+
private inputValues: (number | undefined)[];
|
|
8
|
+
private nextIndex: number = 0;
|
|
9
|
+
constructor(size: number, startValue?: number) {
|
|
10
|
+
this.inputValues = new Array<number>(Math.max(1, size));
|
|
11
|
+
this.currentAverage = startValue ?? 0;
|
|
12
|
+
this.currentCount = startValue === undefined ? 0 : 1;
|
|
13
|
+
this.nextIndex = this.currentCount;
|
|
14
|
+
this.inputValues[0] = startValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Can be used to get the current average value
|
|
19
|
+
*/
|
|
20
|
+
public get currentValue(): number {
|
|
21
|
+
return this.currentAverage;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param value Add new value to the average window and updated current average
|
|
27
|
+
*/
|
|
28
|
+
public addValue(value: number): void {
|
|
29
|
+
const target = this.getNextIndex();
|
|
30
|
+
const oldValue = this.inputValues[target];
|
|
31
|
+
const newCount =
|
|
32
|
+
oldValue === undefined ? this.currentCount + 1 : this.currentCount;
|
|
33
|
+
|
|
34
|
+
this.inputValues[target] = value;
|
|
35
|
+
|
|
36
|
+
this.currentAverage =
|
|
37
|
+
this.currentAverage * (this.currentCount / newCount) +
|
|
38
|
+
(value - (oldValue ?? 0)) / newCount;
|
|
39
|
+
|
|
40
|
+
this.currentCount = newCount;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private getNextIndex(): number {
|
|
44
|
+
// starts from 0 once we reach end of the array
|
|
45
|
+
const newTarget = this.nextIndex;
|
|
46
|
+
this.nextIndex = (this.nextIndex + 1) % this.inputValues.length;
|
|
47
|
+
return newTarget;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseItemAnimator } from "recyclerlistview";
|
|
2
|
+
|
|
3
|
+
const PlatformConfig = {
|
|
4
|
+
defaultDrawDistance: 250,
|
|
5
|
+
};
|
|
6
|
+
const getCellContainerPlatformStyles = (
|
|
7
|
+
inverted: boolean,
|
|
8
|
+
parentProps: { x: number; y: number }
|
|
9
|
+
): { transform: string; WebkitTransform: string } | undefined => {
|
|
10
|
+
return undefined;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getItemAnimator = (): BaseItemAnimator | undefined => {
|
|
14
|
+
return undefined;
|
|
15
|
+
};
|
|
16
|
+
export { PlatformConfig, getCellContainerPlatformStyles, getItemAnimator };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseItemAnimator } from "recyclerlistview";
|
|
2
|
+
import { DefaultJSItemAnimator } from "recyclerlistview/dist/reactnative/platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator";
|
|
3
|
+
|
|
4
|
+
const PlatformConfig = {
|
|
5
|
+
defaultDrawDistance: 2000,
|
|
6
|
+
};
|
|
7
|
+
const getCellContainerPlatformStyles = (
|
|
8
|
+
inverted: boolean,
|
|
9
|
+
parentProps: { x: number; y: number }
|
|
10
|
+
): { transform: string; WebkitTransform: string } | undefined => {
|
|
11
|
+
const transformValue = `translate(${parentProps.x}px,${parentProps.y}px)${
|
|
12
|
+
inverted ? ` scaleY(-1)` : ``
|
|
13
|
+
}`;
|
|
14
|
+
return { transform: transformValue, WebkitTransform: transformValue };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getItemAnimator = (): BaseItemAnimator | undefined => {
|
|
18
|
+
return new DefaultJSItemAnimator();
|
|
19
|
+
};
|
|
20
|
+
export { PlatformConfig, getCellContainerPlatformStyles, getItemAnimator };
|