@tamagui/react-native-web-lite 1.116.2 → 1.116.4
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/cjs/Batchinator/index.cjs +68 -0
- package/dist/cjs/Batchinator/index.js +57 -0
- package/dist/cjs/Batchinator/index.js.map +6 -0
- package/dist/cjs/Batchinator/index.native.js +92 -0
- package/dist/cjs/Batchinator/index.native.js.map +6 -0
- package/dist/cjs/FlatList.cjs +196 -0
- package/dist/cjs/FlatList.js +193 -0
- package/dist/cjs/FlatList.js.map +6 -0
- package/dist/cjs/FlatList.native.js +256 -0
- package/dist/cjs/FlatList.native.js.map +6 -0
- package/dist/cjs/InteractionManager/TaskQueue.cjs +88 -0
- package/dist/cjs/InteractionManager/TaskQueue.js +76 -0
- package/dist/cjs/InteractionManager/TaskQueue.js.map +6 -0
- package/dist/cjs/InteractionManager/TaskQueue.native.js +134 -0
- package/dist/cjs/InteractionManager/TaskQueue.native.js.map +6 -0
- package/dist/cjs/InteractionManager/index.cjs +114 -0
- package/dist/cjs/InteractionManager/index.js +95 -0
- package/dist/cjs/InteractionManager/index.js.map +6 -0
- package/dist/cjs/InteractionManager/index.native.js +99 -0
- package/dist/cjs/InteractionManager/index.native.js.map +6 -0
- package/dist/cjs/index.cjs +7 -7
- package/dist/cjs/index.js +6 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +6 -6
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/vendor/react-native/FillRateHelper/index.cjs +138 -0
- package/dist/cjs/vendor/react-native/FillRateHelper/index.js +124 -0
- package/dist/cjs/vendor/react-native/FillRateHelper/index.js.map +6 -0
- package/dist/cjs/vendor/react-native/FillRateHelper/index.native.js +158 -0
- package/dist/cjs/vendor/react-native/FillRateHelper/index.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/Utilities/clamp.cjs +29 -0
- package/dist/cjs/vendor/react-native/Utilities/clamp.js +24 -0
- package/dist/cjs/vendor/react-native/Utilities/clamp.js.map +6 -0
- package/dist/cjs/vendor/react-native/Utilities/clamp.native.js +25 -0
- package/dist/cjs/vendor/react-native/Utilities/clamp.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/ViewabilityHelper.cjs +133 -0
- package/dist/cjs/vendor/react-native/ViewabilityHelper.js +144 -0
- package/dist/cjs/vendor/react-native/ViewabilityHelper.js.map +6 -0
- package/dist/cjs/vendor/react-native/ViewabilityHelper.native.js +199 -0
- package/dist/cjs/vendor/react-native/ViewabilityHelper.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizeUtils/index.cjs +106 -0
- package/dist/cjs/vendor/react-native/VirtualizeUtils/index.js +86 -0
- package/dist/cjs/vendor/react-native/VirtualizeUtils/index.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizeUtils/index.native.js +95 -0
- package/dist/cjs/vendor/react-native/VirtualizeUtils/index.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.cjs +81 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.js +84 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.native.js +125 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.cjs +56 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.js +59 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.native.js +153 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.cjs +80 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.js +74 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js +163 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.cjs +164 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js +149 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js +245 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.cjs +84 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.js +72 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js +85 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.cjs +16 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.js +14 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js +15 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/index.cjs +1003 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/index.js +1021 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/index.js.map +6 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/index.native.js +1075 -0
- package/dist/cjs/vendor/react-native/VirtualizedList/index.native.js.map +6 -0
- package/dist/cjs/vendor/react-native/deepDiffer/index.cjs +41 -0
- package/dist/cjs/vendor/react-native/deepDiffer/index.js +47 -0
- package/dist/cjs/vendor/react-native/deepDiffer/index.js.map +6 -0
- package/dist/cjs/vendor/react-native/deepDiffer/index.native.js +48 -0
- package/dist/cjs/vendor/react-native/deepDiffer/index.native.js.map +6 -0
- package/dist/esm/Batchinator/index.js +33 -0
- package/dist/esm/Batchinator/index.js.map +6 -0
- package/dist/esm/Batchinator/index.mjs +34 -0
- package/dist/esm/Batchinator/index.mjs.map +1 -0
- package/dist/esm/Batchinator/index.native.js +67 -0
- package/dist/esm/Batchinator/index.native.js.map +6 -0
- package/dist/esm/FlatList.js +176 -0
- package/dist/esm/FlatList.js.map +6 -0
- package/dist/esm/FlatList.mjs +162 -0
- package/dist/esm/FlatList.mjs.map +1 -0
- package/dist/esm/FlatList.native.js +238 -0
- package/dist/esm/FlatList.native.js.map +6 -0
- package/dist/esm/InteractionManager/TaskQueue.js +60 -0
- package/dist/esm/InteractionManager/TaskQueue.js.map +6 -0
- package/dist/esm/InteractionManager/TaskQueue.mjs +65 -0
- package/dist/esm/InteractionManager/TaskQueue.mjs.map +1 -0
- package/dist/esm/InteractionManager/TaskQueue.native.js +117 -0
- package/dist/esm/InteractionManager/TaskQueue.native.js.map +6 -0
- package/dist/esm/InteractionManager/index.js +73 -0
- package/dist/esm/InteractionManager/index.js.map +6 -0
- package/dist/esm/InteractionManager/index.mjs +80 -0
- package/dist/esm/InteractionManager/index.mjs.map +1 -0
- package/dist/esm/InteractionManager/index.native.js +77 -0
- package/dist/esm/InteractionManager/index.native.js.map +6 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.native.js +2 -2
- package/dist/esm/vendor/react-native/FillRateHelper/index.js +108 -0
- package/dist/esm/vendor/react-native/FillRateHelper/index.js.map +6 -0
- package/dist/esm/vendor/react-native/FillRateHelper/index.mjs +115 -0
- package/dist/esm/vendor/react-native/FillRateHelper/index.mjs.map +1 -0
- package/dist/esm/vendor/react-native/FillRateHelper/index.native.js +141 -0
- package/dist/esm/vendor/react-native/FillRateHelper/index.native.js.map +6 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.js +8 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.js.map +6 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.mjs +6 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.mjs.map +1 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.native.js +8 -0
- package/dist/esm/vendor/react-native/Utilities/clamp.native.js.map +6 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.js +128 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.js.map +6 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.mjs +110 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.mjs.map +1 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.native.js +182 -0
- package/dist/esm/vendor/react-native/ViewabilityHelper.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.js +70 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.mjs +80 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.native.js +71 -0
- package/dist/esm/vendor/react-native/VirtualizeUtils/index.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.js +68 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.mjs +58 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.native.js +104 -0
- package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.js +43 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.mjs +33 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.native.js +136 -0
- package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.js +51 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.mjs +46 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js +139 -0
- package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js +132 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.mjs +130 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js +224 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.js +50 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.mjs +47 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js +56 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.js +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.mjs +2 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.js +1016 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.js.map +6 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.mjs +969 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.mjs.map +1 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.native.js +1065 -0
- package/dist/esm/vendor/react-native/VirtualizedList/index.native.js.map +6 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.js +31 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.js.map +6 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.mjs +18 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.mjs.map +1 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.native.js +31 -0
- package/dist/esm/vendor/react-native/deepDiffer/index.native.js.map +6 -0
- package/package.json +6 -6
- package/src/Batchinator/index.tsx +78 -0
- package/src/FlatList.tsx +330 -0
- package/src/InteractionManager/TaskQueue.tsx +114 -0
- package/src/InteractionManager/index.tsx +139 -0
- package/src/index.tsx +2 -2
- package/src/vendor/react-native/FillRateHelper/index.tsx +218 -0
- package/src/vendor/react-native/Utilities/clamp.ts +21 -0
- package/src/vendor/react-native/ViewabilityHelper.ts +291 -0
- package/src/vendor/react-native/VirtualizeUtils/index.tsx +212 -0
- package/src/vendor/react-native/VirtualizedList/CellRenderMask.ts +147 -0
- package/src/vendor/react-native/VirtualizedList/ChildListCollection.tsx +73 -0
- package/src/vendor/react-native/VirtualizedList/StateSafePureComponent.tsx +79 -0
- package/src/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.tsx +210 -0
- package/src/vendor/react-native/VirtualizedList/VirtualizedListContext.tsx +116 -0
- package/src/vendor/react-native/VirtualizedList/VirtualizedListProps.ts +130 -0
- package/src/vendor/react-native/VirtualizedList/index.tsx +1797 -0
- package/src/vendor/react-native/deepDiffer/index.tsx +56 -0
|
@@ -0,0 +1,1797 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @format
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { invariant, StyleSheet } from '@tamagui/react-native-web-internals'
|
|
13
|
+
import type { ViewProps } from '../../../View'
|
|
14
|
+
import ViewabilityHelper, { type ViewToken } from '../ViewabilityHelper'
|
|
15
|
+
import { CellRenderMask } from './CellRenderMask'
|
|
16
|
+
import StateSafePureComponent from './StateSafePureComponent'
|
|
17
|
+
import {
|
|
18
|
+
VirtualizedListCellContextProvider,
|
|
19
|
+
VirtualizedListContext,
|
|
20
|
+
VirtualizedListContextProvider,
|
|
21
|
+
} from './VirtualizedListContext'
|
|
22
|
+
import type { FrameMetricProps, Item, Props } from './VirtualizedListProps'
|
|
23
|
+
import ScrollView from '../../../ScrollView/ScrollViewBase'
|
|
24
|
+
import View from '../../../View'
|
|
25
|
+
import type { ScrollEvent } from '../Types/CoreEventTypes'
|
|
26
|
+
import CellRenderer from './VirtualizedListCellRenderer'
|
|
27
|
+
import FillRateHelper from '../FillRateHelper'
|
|
28
|
+
import ChildListCollection from './ChildListCollection'
|
|
29
|
+
import React from 'react'
|
|
30
|
+
import clamp from '../Utilities/clamp'
|
|
31
|
+
import { keyExtractor as defaultKeyExtractor } from '../VirtualizeUtils'
|
|
32
|
+
import Batchinator from '../../../Batchinator'
|
|
33
|
+
import RefreshControl from '../../../RefreshControl'
|
|
34
|
+
|
|
35
|
+
export type { RenderItemProps, RenderItemType, Separators } from './VirtualizedListProps'
|
|
36
|
+
|
|
37
|
+
const __DEV__ = process.env.NODE_ENV !== 'production'
|
|
38
|
+
|
|
39
|
+
const ON_EDGE_REACHED_EPSILON = 0.001
|
|
40
|
+
|
|
41
|
+
let _usedIndexForKey = false
|
|
42
|
+
let _keylessItemComponentName = ''
|
|
43
|
+
|
|
44
|
+
type ScrollResponderType = any
|
|
45
|
+
type ViewStyleProp = ViewProps['style']
|
|
46
|
+
|
|
47
|
+
interface ViewabilityHelperCallbackTuple {
|
|
48
|
+
viewabilityHelper: ViewabilityHelper
|
|
49
|
+
onViewableItemsChanged: (info: {
|
|
50
|
+
viewableItems: Array<ViewToken>
|
|
51
|
+
changed: Array<ViewToken>
|
|
52
|
+
}) => void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface State {
|
|
56
|
+
renderMask: CellRenderMask
|
|
57
|
+
cellsAroundViewport: { first: number; last: number }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default Props Helper Functions
|
|
62
|
+
* Use the following helper functions for default values
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
function horizontalOrDefault(horizontal?: boolean) {
|
|
66
|
+
return horizontal ?? false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function initialNumToRenderOrDefault(initialNumToRender?: number) {
|
|
70
|
+
return initialNumToRender ?? 10
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function maxToRenderPerBatchOrDefault(maxToRenderPerBatch?: number) {
|
|
74
|
+
return maxToRenderPerBatch ?? 10
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function onStartReachedThresholdOrDefault(onStartReachedThreshold?: number) {
|
|
78
|
+
return onStartReachedThreshold ?? 2
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function onEndReachedThresholdOrDefault(onEndReachedThreshold?: number) {
|
|
82
|
+
return onEndReachedThreshold ?? 2
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getScrollingThreshold(threshold: number, visibleLength: number) {
|
|
86
|
+
return (threshold * visibleLength) / 2
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function scrollEventThrottleOrDefault(scrollEventThrottle?: number) {
|
|
90
|
+
return scrollEventThrottle ?? 50
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function windowSizeOrDefault(windowSize?: number) {
|
|
94
|
+
return windowSize ?? 21
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findLastWhere<T>(
|
|
98
|
+
arr: ReadonlyArray<T>,
|
|
99
|
+
predicate: (element: T) => boolean
|
|
100
|
+
): T | null {
|
|
101
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
102
|
+
if (predicate(arr[i])) {
|
|
103
|
+
return arr[i]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Base implementation for the more convenient [`<FlatList>`](https://reactnative.dev/docs/flatlist)
|
|
111
|
+
* and [`<SectionList>`](https://reactnative.dev/docs/sectionlist) components, which are also better
|
|
112
|
+
* documented. In general, this should only really be used if you need more flexibility than
|
|
113
|
+
* `FlatList` provides, e.g., for use with immutable data instead of plain arrays.
|
|
114
|
+
*
|
|
115
|
+
* Virtualization massively improves memory consumption and performance of large lists by
|
|
116
|
+
* maintaining a finite render window of active items and replacing all items outside of the render
|
|
117
|
+
* window with appropriately sized blank space. The window adapts to scrolling behavior, and items
|
|
118
|
+
* are rendered incrementally with low-pri (after any running interactions) if they are far from the
|
|
119
|
+
* visible area, or with hi-pri otherwise to minimize the potential of seeing blank space.
|
|
120
|
+
*
|
|
121
|
+
* Some caveats:
|
|
122
|
+
*
|
|
123
|
+
* - Internal state is not preserved when content scrolls out of the render window. Make sure all
|
|
124
|
+
* your data is captured in the item data or external stores like Flux, Redux, or Relay.
|
|
125
|
+
* - This is a `PureComponent` which means that it will not re-render if `props` remain shallow-
|
|
126
|
+
* equal. Make sure that everything your `renderItem` function depends on is passed as a prop
|
|
127
|
+
* (e.g., `extraData`) that is not `===` after updates, otherwise your UI may not update on
|
|
128
|
+
* changes. This includes the `data` prop and parent component state.
|
|
129
|
+
* - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously
|
|
130
|
+
* offscreen. This means it's possible to scroll faster than the fill rate and momentarily see
|
|
131
|
+
* blank content. This is a tradeoff that can be adjusted to suit the needs of each application,
|
|
132
|
+
* and we are working on improving it behind the scenes.
|
|
133
|
+
* - By default, the list looks for a `key` or `id` prop on each item and uses that for the React key.
|
|
134
|
+
* Alternatively, you can provide a custom `keyExtractor` prop.
|
|
135
|
+
* - As an effort to remove defaultProps, use helper functions when referencing certain props
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
class VirtualizedList extends StateSafePureComponent<any, State> {
|
|
139
|
+
static contextType: typeof VirtualizedListContext = VirtualizedListContext
|
|
140
|
+
|
|
141
|
+
// scrollToEnd may be janky without getItemLayout prop
|
|
142
|
+
scrollToEnd(params?: { animated?: boolean }) {
|
|
143
|
+
const animated = params ? params.animated : true
|
|
144
|
+
const veryLast = this.props.getItemCount(this.props.data) - 1
|
|
145
|
+
if (veryLast < 0) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
const frame = this.__getFrameMetricsApprox(veryLast, this.props)
|
|
150
|
+
const offset = Math.max(
|
|
151
|
+
0,
|
|
152
|
+
frame.offset + frame.length + this._footerLength - this._scrollMetrics.visibleLength
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if (this._scrollRef == null) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
if (this._scrollRef.scrollTo == null) {
|
|
161
|
+
console.warn(
|
|
162
|
+
'No scrollTo method provided. This may be because you have two nested ' +
|
|
163
|
+
'VirtualizedLists with the same orientation, or because you are ' +
|
|
164
|
+
'using a custom component that does not implement scrollTo.'
|
|
165
|
+
)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
this._scrollRef.scrollTo(
|
|
171
|
+
horizontalOrDefault(this.props.horizontal)
|
|
172
|
+
? { x: offset, animated }
|
|
173
|
+
: { y: offset, animated }
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// scrollToIndex may be janky without getItemLayout prop
|
|
178
|
+
scrollToIndex(params: {
|
|
179
|
+
animated?: boolean
|
|
180
|
+
index: number
|
|
181
|
+
viewOffset?: number
|
|
182
|
+
viewPosition?: number
|
|
183
|
+
}) {
|
|
184
|
+
const { data, horizontal, getItemCount, getItemLayout, onScrollToIndexFailed } =
|
|
185
|
+
this.props
|
|
186
|
+
const { animated, index, viewOffset, viewPosition } = params
|
|
187
|
+
invariant(
|
|
188
|
+
index >= 0,
|
|
189
|
+
`scrollToIndex out of range: requested index ${index} but minimum is 0`
|
|
190
|
+
)
|
|
191
|
+
invariant(
|
|
192
|
+
getItemCount(data) >= 1,
|
|
193
|
+
`scrollToIndex out of range: item length ${getItemCount(data)} but minimum is 1`
|
|
194
|
+
)
|
|
195
|
+
invariant(
|
|
196
|
+
index < getItemCount(data),
|
|
197
|
+
`scrollToIndex out of range: requested index ${index} is out of 0 to ${
|
|
198
|
+
getItemCount(data) - 1
|
|
199
|
+
}`
|
|
200
|
+
)
|
|
201
|
+
if (!getItemLayout && index > this._highestMeasuredFrameIndex) {
|
|
202
|
+
invariant(
|
|
203
|
+
!!onScrollToIndexFailed,
|
|
204
|
+
'scrollToIndex should be used in conjunction with getItemLayout or onScrollToIndexFailed, ' +
|
|
205
|
+
'otherwise there is no way to know the location of offscreen indices or handle failures.'
|
|
206
|
+
)
|
|
207
|
+
onScrollToIndexFailed({
|
|
208
|
+
averageItemLength: this._averageCellLength,
|
|
209
|
+
highestMeasuredFrameIndex: this._highestMeasuredFrameIndex,
|
|
210
|
+
index,
|
|
211
|
+
})
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
// @ts-ignore
|
|
215
|
+
const frame = this.__getFrameMetricsApprox(Math.floor(index), this.props)
|
|
216
|
+
const offset =
|
|
217
|
+
Math.max(
|
|
218
|
+
0,
|
|
219
|
+
// @ts-ignore
|
|
220
|
+
this._getOffsetApprox(index, this.props) -
|
|
221
|
+
(viewPosition || 0) * (this._scrollMetrics.visibleLength - frame.length)
|
|
222
|
+
) - (viewOffset || 0)
|
|
223
|
+
|
|
224
|
+
if (this._scrollRef == null) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// @ts-ignore
|
|
229
|
+
if (this._scrollRef.scrollTo == null) {
|
|
230
|
+
console.warn(
|
|
231
|
+
'No scrollTo method provided. This may be because you have two nested ' +
|
|
232
|
+
'VirtualizedLists with the same orientation, or because you are ' +
|
|
233
|
+
'using a custom component that does not implement scrollTo.'
|
|
234
|
+
)
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
this._scrollRef.scrollTo(
|
|
240
|
+
horizontal ? { x: offset, animated } : { y: offset, animated }
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// scrollToItem may be janky without getItemLayout prop. Required linear scan through items -
|
|
245
|
+
// use scrollToIndex instead if possible.
|
|
246
|
+
scrollToItem(params: {
|
|
247
|
+
animated?: boolean
|
|
248
|
+
item: Item
|
|
249
|
+
viewOffset?: number
|
|
250
|
+
viewPosition?: number
|
|
251
|
+
}) {
|
|
252
|
+
const { item } = params
|
|
253
|
+
const { data, getItem, getItemCount } = this.props
|
|
254
|
+
const itemCount = getItemCount(data)
|
|
255
|
+
for (let index = 0; index < itemCount; index++) {
|
|
256
|
+
if (getItem(data, index) === item) {
|
|
257
|
+
this.scrollToIndex({ ...params, index })
|
|
258
|
+
break
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Scroll to a specific content pixel offset in the list.
|
|
265
|
+
*
|
|
266
|
+
* Param `offset` expects the offset to scroll to.
|
|
267
|
+
* In case of `horizontal` is true, the offset is the x-value,
|
|
268
|
+
* in any other case the offset is the y-value.
|
|
269
|
+
*
|
|
270
|
+
* Param `animated` (`true` by default) defines whether the list
|
|
271
|
+
* should do an animation while scrolling.
|
|
272
|
+
*/
|
|
273
|
+
scrollToOffset(params: { animated?: boolean; offset: number }) {
|
|
274
|
+
const { animated, offset } = params
|
|
275
|
+
|
|
276
|
+
if (this._scrollRef == null) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// @ts-ignore
|
|
281
|
+
if (this._scrollRef.scrollTo == null) {
|
|
282
|
+
console.warn(
|
|
283
|
+
'No scrollTo method provided. This may be because you have two nested ' +
|
|
284
|
+
'VirtualizedLists with the same orientation, or because you are ' +
|
|
285
|
+
'using a custom component that does not implement scrollTo.'
|
|
286
|
+
)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// @ts-ignore
|
|
291
|
+
this._scrollRef.scrollTo(
|
|
292
|
+
horizontalOrDefault(this.props.horizontal)
|
|
293
|
+
? { x: offset, animated }
|
|
294
|
+
: { y: offset, animated }
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
recordInteraction() {
|
|
299
|
+
this._nestedChildLists.forEach((childList) => {
|
|
300
|
+
childList.recordInteraction()
|
|
301
|
+
})
|
|
302
|
+
this._viewabilityTuples.forEach((t) => {
|
|
303
|
+
t.viewabilityHelper.recordInteraction()
|
|
304
|
+
})
|
|
305
|
+
// @ts-ignore
|
|
306
|
+
this._updateViewableItems(this.props, this.state.cellsAroundViewport)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
flashScrollIndicators() {
|
|
310
|
+
if (this._scrollRef == null) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// @ts-ignore
|
|
315
|
+
this._scrollRef.flashScrollIndicators()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Provides a handle to the underlying scroll responder.
|
|
320
|
+
* Note that `this._scrollRef` might not be a `ScrollView`, so we
|
|
321
|
+
* need to check that it responds to `getScrollResponder` before calling it.
|
|
322
|
+
*/
|
|
323
|
+
getScrollResponder(): ScrollResponderType | null {
|
|
324
|
+
// @ts-ignore
|
|
325
|
+
if (this._scrollRef && this._scrollRef.getScrollResponder) {
|
|
326
|
+
// @ts-ignore
|
|
327
|
+
return this._scrollRef.getScrollResponder()
|
|
328
|
+
}
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
getScrollableNode(): number | null {
|
|
333
|
+
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
|
334
|
+
return this._scrollRef.getScrollableNode()
|
|
335
|
+
} else {
|
|
336
|
+
return this._scrollRef
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
getScrollRef():
|
|
341
|
+
| React.ElementRef<typeof ScrollView>
|
|
342
|
+
| React.ElementRef<typeof View>
|
|
343
|
+
| null {
|
|
344
|
+
if (this._scrollRef && this._scrollRef.getScrollRef) {
|
|
345
|
+
return this._scrollRef.getScrollRef()
|
|
346
|
+
} else {
|
|
347
|
+
return this._scrollRef
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_getCellKey(): string {
|
|
352
|
+
return this.context?.cellKey || 'rootList'
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_getScrollMetrics = () => {
|
|
356
|
+
return this._scrollMetrics
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
hasMore(): boolean {
|
|
360
|
+
return this._hasMore
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_getOutermostParentListRef = () => {
|
|
364
|
+
if (this._isNestedWithSameOrientation()) {
|
|
365
|
+
return this.context.getOutermostParentListRef()
|
|
366
|
+
} else {
|
|
367
|
+
return this
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_registerAsNestedChild = (childList: {
|
|
372
|
+
cellKey: string
|
|
373
|
+
ref: React.ElementRef<typeof VirtualizedList>
|
|
374
|
+
}): void => {
|
|
375
|
+
this._nestedChildLists.add(childList.ref, childList.cellKey)
|
|
376
|
+
if (this._hasInteracted) {
|
|
377
|
+
childList.ref.recordInteraction()
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
_unregisterAsNestedChild = (childList: {
|
|
382
|
+
ref: React.ElementRef<typeof VirtualizedList>
|
|
383
|
+
}): void => {
|
|
384
|
+
this._nestedChildLists.remove(childList.ref)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
invertedWheelEventHandler?: (ev: any) => void
|
|
388
|
+
|
|
389
|
+
constructor(props: Props) {
|
|
390
|
+
super(props)
|
|
391
|
+
this._checkProps(props)
|
|
392
|
+
|
|
393
|
+
this._fillRateHelper = new FillRateHelper(this._getFrameMetrics)
|
|
394
|
+
this._updateCellsToRenderBatcher = new Batchinator(
|
|
395
|
+
this._updateCellsToRender,
|
|
396
|
+
this.props.updateCellsBatchingPeriod ?? 50
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if (this.props.viewabilityConfigCallbackPairs) {
|
|
400
|
+
this._viewabilityTuples = this.props.viewabilityConfigCallbackPairs.map((pair) => ({
|
|
401
|
+
viewabilityHelper: new ViewabilityHelper(pair.viewabilityConfig),
|
|
402
|
+
onViewableItemsChanged: pair.onViewableItemsChanged,
|
|
403
|
+
}))
|
|
404
|
+
} else {
|
|
405
|
+
const { onViewableItemsChanged, viewabilityConfig } = this.props
|
|
406
|
+
if (onViewableItemsChanged) {
|
|
407
|
+
this._viewabilityTuples.push({
|
|
408
|
+
viewabilityHelper: new ViewabilityHelper(viewabilityConfig),
|
|
409
|
+
onViewableItemsChanged: onViewableItemsChanged,
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const initialRenderRegion = VirtualizedList._initialRenderRegion(props)
|
|
415
|
+
|
|
416
|
+
this.state = {
|
|
417
|
+
cellsAroundViewport: initialRenderRegion,
|
|
418
|
+
renderMask: VirtualizedList._createRenderMask(props, initialRenderRegion),
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.invertedWheelEventHandler = (ev: any) => {
|
|
422
|
+
const scrollOffset = this.props.horizontal
|
|
423
|
+
? ev.target.scrollLeft
|
|
424
|
+
: ev.target.scrollTop
|
|
425
|
+
const scrollLength = this.props.horizontal
|
|
426
|
+
? ev.target.scrollWidth
|
|
427
|
+
: ev.target.scrollHeight
|
|
428
|
+
const clientLength = this.props.horizontal
|
|
429
|
+
? ev.target.clientWidth
|
|
430
|
+
: ev.target.clientHeight
|
|
431
|
+
const isEventTargetScrollable = scrollLength > clientLength
|
|
432
|
+
const delta = this.props.horizontal
|
|
433
|
+
? ev.deltaX || ev.wheelDeltaX
|
|
434
|
+
: ev.deltaY || ev.wheelDeltaY
|
|
435
|
+
let leftoverDelta = delta
|
|
436
|
+
if (isEventTargetScrollable) {
|
|
437
|
+
leftoverDelta =
|
|
438
|
+
delta < 0
|
|
439
|
+
? Math.min(delta + scrollOffset, 0)
|
|
440
|
+
: Math.max(delta - (scrollLength - clientLength - scrollOffset), 0)
|
|
441
|
+
}
|
|
442
|
+
const targetDelta = delta - leftoverDelta
|
|
443
|
+
|
|
444
|
+
if (this.props.inverted && this._scrollRef && this._scrollRef.getScrollableNode) {
|
|
445
|
+
const node = this._scrollRef.getScrollableNode()
|
|
446
|
+
if (this.props.horizontal) {
|
|
447
|
+
ev.target.scrollLeft += targetDelta
|
|
448
|
+
const nextScrollLeft = node.scrollLeft - leftoverDelta
|
|
449
|
+
node.scrollLeft = !this.props.getItemLayout
|
|
450
|
+
? Math.min(nextScrollLeft, this._totalCellLength)
|
|
451
|
+
: nextScrollLeft
|
|
452
|
+
} else {
|
|
453
|
+
ev.target.scrollTop += targetDelta
|
|
454
|
+
const nextScrollTop = node.scrollTop - leftoverDelta
|
|
455
|
+
node.scrollTop = !this.props.getItemLayout
|
|
456
|
+
? Math.min(nextScrollTop, this._totalCellLength)
|
|
457
|
+
: nextScrollTop
|
|
458
|
+
}
|
|
459
|
+
ev.preventDefault()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
_checkProps(props: Props) {
|
|
465
|
+
const { onScroll, windowSize, getItemCount, data, initialScrollIndex } = props
|
|
466
|
+
|
|
467
|
+
invariant(
|
|
468
|
+
!(onScroll && (onScroll as any).__isNative),
|
|
469
|
+
'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' +
|
|
470
|
+
'to support native onScroll events with useNativeDriver'
|
|
471
|
+
)
|
|
472
|
+
invariant(
|
|
473
|
+
windowSizeOrDefault(windowSize) > 0,
|
|
474
|
+
'VirtualizedList: The windowSize prop must be present and set to a value greater than 0.'
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
invariant(getItemCount, 'VirtualizedList: The "getItemCount" prop must be provided')
|
|
478
|
+
|
|
479
|
+
const itemCount = getItemCount(data)
|
|
480
|
+
|
|
481
|
+
if (
|
|
482
|
+
initialScrollIndex != null &&
|
|
483
|
+
!this._hasTriggeredInitialScrollToIndex &&
|
|
484
|
+
(initialScrollIndex < 0 || (itemCount > 0 && initialScrollIndex >= itemCount)) &&
|
|
485
|
+
!this._hasWarned.initialScrollIndex
|
|
486
|
+
) {
|
|
487
|
+
console.warn(
|
|
488
|
+
`initialScrollIndex "${initialScrollIndex}" is not valid (list has ${itemCount} items)`
|
|
489
|
+
)
|
|
490
|
+
this._hasWarned.initialScrollIndex = true
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (__DEV__ && !this._hasWarned.flexWrap) {
|
|
494
|
+
const flatStyles = StyleSheet.flatten(this.props.contentContainerStyle)
|
|
495
|
+
if (flatStyles != null && flatStyles.flexWrap === 'wrap') {
|
|
496
|
+
console.warn(
|
|
497
|
+
'`flexWrap: `wrap`` is not supported with the `VirtualizedList` components.' +
|
|
498
|
+
'Consider using `numColumns` with `FlatList` instead.'
|
|
499
|
+
)
|
|
500
|
+
this._hasWarned.flexWrap = true
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static _createRenderMask(
|
|
506
|
+
props: Props,
|
|
507
|
+
cellsAroundViewport: { first: number; last: number },
|
|
508
|
+
additionalRegions?: Array<{ first: number; last: number }>
|
|
509
|
+
): CellRenderMask {
|
|
510
|
+
const itemCount = props.getItemCount(props.data)
|
|
511
|
+
|
|
512
|
+
invariant(
|
|
513
|
+
cellsAroundViewport.first >= 0 &&
|
|
514
|
+
cellsAroundViewport.last >= cellsAroundViewport.first - 1 &&
|
|
515
|
+
cellsAroundViewport.last < itemCount,
|
|
516
|
+
`Invalid cells around viewport "[${cellsAroundViewport.first}, ${cellsAroundViewport.last}]" was passed to VirtualizedList._createRenderMask`
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
const renderMask = new CellRenderMask(itemCount)
|
|
520
|
+
|
|
521
|
+
if (itemCount > 0) {
|
|
522
|
+
const allRegions = [cellsAroundViewport, ...(additionalRegions ?? [])]
|
|
523
|
+
for (const region of allRegions) {
|
|
524
|
+
renderMask.addCells(region)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// The initially rendered cells are retained as part of the
|
|
528
|
+
// "scroll-to-top" optimization
|
|
529
|
+
if (props.initialScrollIndex == null || props.initialScrollIndex <= 0) {
|
|
530
|
+
const initialRegion = VirtualizedList._initialRenderRegion(props)
|
|
531
|
+
renderMask.addCells(initialRegion)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// The layout coordinates of sticker headers may be off-screen while the
|
|
535
|
+
// actual header is on-screen. Keep the most recent before the viewport
|
|
536
|
+
// rendered, even if its layout coordinates are not in viewport.
|
|
537
|
+
const stickyIndicesSet = new Set(props.stickyHeaderIndices)
|
|
538
|
+
VirtualizedList._ensureClosestStickyHeader(
|
|
539
|
+
props,
|
|
540
|
+
stickyIndicesSet,
|
|
541
|
+
renderMask,
|
|
542
|
+
cellsAroundViewport.first
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return renderMask
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
static _initialRenderRegion(props: Props): { first: number; last: number } {
|
|
550
|
+
const itemCount = props.getItemCount(props.data)
|
|
551
|
+
|
|
552
|
+
const firstCellIndex = Math.max(
|
|
553
|
+
0,
|
|
554
|
+
Math.min(itemCount - 1, Math.floor(props.initialScrollIndex ?? 0))
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
const lastCellIndex =
|
|
558
|
+
Math.min(
|
|
559
|
+
itemCount,
|
|
560
|
+
firstCellIndex + initialNumToRenderOrDefault(props.initialNumToRender)
|
|
561
|
+
) - 1
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
first: firstCellIndex,
|
|
565
|
+
last: lastCellIndex,
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
static _ensureClosestStickyHeader(
|
|
570
|
+
props: Props,
|
|
571
|
+
stickyIndicesSet: Set<number>,
|
|
572
|
+
renderMask: CellRenderMask,
|
|
573
|
+
cellIdx: number
|
|
574
|
+
) {
|
|
575
|
+
const stickyOffset = props.ListHeaderComponent ? 1 : 0
|
|
576
|
+
|
|
577
|
+
for (let itemIdx = cellIdx - 1; itemIdx >= 0; itemIdx--) {
|
|
578
|
+
if (stickyIndicesSet.has(itemIdx + stickyOffset)) {
|
|
579
|
+
renderMask.addCells({ first: itemIdx, last: itemIdx })
|
|
580
|
+
break
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
_adjustCellsAroundViewport(
|
|
586
|
+
props: Props,
|
|
587
|
+
cellsAroundViewport: { first: number; last: number }
|
|
588
|
+
): { first: number; last: number } {
|
|
589
|
+
const { data, getItemCount } = props
|
|
590
|
+
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
|
|
591
|
+
props.onEndReachedThreshold
|
|
592
|
+
)
|
|
593
|
+
const { contentLength, offset, visibleLength } = this._scrollMetrics
|
|
594
|
+
const distanceFromEnd = contentLength - visibleLength - offset
|
|
595
|
+
|
|
596
|
+
// Wait until the scroll view metrics have been set up. And until then,
|
|
597
|
+
// we will trust the initialNumToRender suggestion
|
|
598
|
+
if (visibleLength <= 0 || contentLength <= 0) {
|
|
599
|
+
return cellsAroundViewport.last >= getItemCount(data)
|
|
600
|
+
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
|
|
601
|
+
: cellsAroundViewport
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let newCellsAroundViewport: { first: number; last: number }
|
|
605
|
+
if (props.disableVirtualization) {
|
|
606
|
+
const renderAhead =
|
|
607
|
+
distanceFromEnd < onEndReachedThreshold * visibleLength
|
|
608
|
+
? maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch)
|
|
609
|
+
: 0
|
|
610
|
+
|
|
611
|
+
newCellsAroundViewport = {
|
|
612
|
+
first: 0,
|
|
613
|
+
last: Math.min(cellsAroundViewport.last + renderAhead, getItemCount(data) - 1),
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
// If we have a non-zero initialScrollIndex and run this before we've scrolled,
|
|
617
|
+
// we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
|
|
618
|
+
// So let's wait until we've scrolled the view to the right place. And until then,
|
|
619
|
+
// we will trust the initialScrollIndex suggestion.
|
|
620
|
+
|
|
621
|
+
// Thus, we want to recalculate the windowed render limits if any of the following hold:
|
|
622
|
+
// - initialScrollIndex is undefined or is 0
|
|
623
|
+
// - initialScrollIndex > 0 AND scrolling is complete
|
|
624
|
+
// - initialScrollIndex > 0 AND the end of the list is visible (this handles the case
|
|
625
|
+
// where the list is shorter than the visible area)
|
|
626
|
+
if (
|
|
627
|
+
props.initialScrollIndex &&
|
|
628
|
+
!this._scrollMetrics.offset &&
|
|
629
|
+
Math.abs(distanceFromEnd) >= Number.EPSILON
|
|
630
|
+
) {
|
|
631
|
+
return cellsAroundViewport.last >= getItemCount(data)
|
|
632
|
+
? VirtualizedList._constrainToItemCount(cellsAroundViewport, props)
|
|
633
|
+
: cellsAroundViewport
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
newCellsAroundViewport = computeWindowedRenderLimits(
|
|
637
|
+
props,
|
|
638
|
+
maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch),
|
|
639
|
+
windowSizeOrDefault(props.windowSize),
|
|
640
|
+
cellsAroundViewport,
|
|
641
|
+
this.__getFrameMetricsApprox,
|
|
642
|
+
this._scrollMetrics
|
|
643
|
+
)
|
|
644
|
+
invariant(
|
|
645
|
+
newCellsAroundViewport.last < getItemCount(data),
|
|
646
|
+
'computeWindowedRenderLimits() should return range in-bounds'
|
|
647
|
+
)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (this._nestedChildLists.size() > 0) {
|
|
651
|
+
// If some cell in the new state has a child list in it, we should only render
|
|
652
|
+
// up through that item, so that we give that list a chance to render.
|
|
653
|
+
// Otherwise there's churn from multiple child lists mounting and un-mounting
|
|
654
|
+
// their items.
|
|
655
|
+
|
|
656
|
+
// Will this prevent rendering if the nested list doesn't realize the end?
|
|
657
|
+
const childIdx = this._findFirstChildWithMore(
|
|
658
|
+
newCellsAroundViewport.first,
|
|
659
|
+
newCellsAroundViewport.last
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
newCellsAroundViewport.last = childIdx ?? newCellsAroundViewport.last
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return newCellsAroundViewport
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
_findFirstChildWithMore(first: number, last: number): number | null {
|
|
669
|
+
for (let ii = first; ii <= last; ii++) {
|
|
670
|
+
const cellKeyForIndex = this._indicesToKeys.get(ii)
|
|
671
|
+
if (
|
|
672
|
+
cellKeyForIndex != null &&
|
|
673
|
+
this._nestedChildLists.anyInCell(cellKeyForIndex, (childList) =>
|
|
674
|
+
childList.hasMore()
|
|
675
|
+
)
|
|
676
|
+
) {
|
|
677
|
+
return ii
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return null
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
componentDidMount() {
|
|
685
|
+
if (this._isNestedWithSameOrientation()) {
|
|
686
|
+
this.context.registerAsNestedChild({
|
|
687
|
+
ref: this,
|
|
688
|
+
cellKey: this.context.cellKey,
|
|
689
|
+
})
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller.
|
|
693
|
+
this.setupWebWheelHandler()
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
componentWillUnmount() {
|
|
697
|
+
if (this._isNestedWithSameOrientation()) {
|
|
698
|
+
this.context.unregisterAsNestedChild({ ref: this })
|
|
699
|
+
}
|
|
700
|
+
this._updateCellsToRenderBatcher.dispose({ abort: true })
|
|
701
|
+
this._viewabilityTuples.forEach((tuple) => {
|
|
702
|
+
tuple.viewabilityHelper.dispose()
|
|
703
|
+
})
|
|
704
|
+
this._fillRateHelper.deactivateAndFlush()
|
|
705
|
+
|
|
706
|
+
// REACT-NATIVE-WEB patch to preserve during future RN merges: Support inverted wheel scroller.
|
|
707
|
+
this.teardownWebWheelHandler()
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
setupWebWheelHandler() {
|
|
711
|
+
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
|
712
|
+
this._scrollRef
|
|
713
|
+
.getScrollableNode()
|
|
714
|
+
.addEventListener('wheel', this.invertedWheelEventHandler)
|
|
715
|
+
} else {
|
|
716
|
+
setTimeout(() => this.setupWebWheelHandler(), 50)
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
teardownWebWheelHandler() {
|
|
722
|
+
if (this._scrollRef && this._scrollRef.getScrollableNode) {
|
|
723
|
+
this._scrollRef
|
|
724
|
+
.getScrollableNode()
|
|
725
|
+
.removeEventListener('wheel', this.invertedWheelEventHandler)
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
static getDerivedStateFromProps(newProps: Props, prevState: State): State {
|
|
730
|
+
const itemCount = newProps.getItemCount(newProps.data)
|
|
731
|
+
if (itemCount === prevState.renderMask.numCells()) {
|
|
732
|
+
return prevState
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const constrainedCells = VirtualizedList._constrainToItemCount(
|
|
736
|
+
prevState.cellsAroundViewport,
|
|
737
|
+
newProps
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
cellsAroundViewport: constrainedCells,
|
|
742
|
+
renderMask: VirtualizedList._createRenderMask(newProps, constrainedCells),
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
_pushCells(
|
|
747
|
+
cells: Array<Object>,
|
|
748
|
+
stickyHeaderIndices: Array<number>,
|
|
749
|
+
stickyIndicesFromProps: Set<number>,
|
|
750
|
+
first: number,
|
|
751
|
+
last: number,
|
|
752
|
+
inversionStyle: ViewStyleProp
|
|
753
|
+
) {
|
|
754
|
+
const {
|
|
755
|
+
CellRendererComponent,
|
|
756
|
+
ItemSeparatorComponent,
|
|
757
|
+
ListHeaderComponent,
|
|
758
|
+
ListItemComponent,
|
|
759
|
+
data,
|
|
760
|
+
debug,
|
|
761
|
+
getItem,
|
|
762
|
+
getItemCount,
|
|
763
|
+
getItemLayout,
|
|
764
|
+
horizontal,
|
|
765
|
+
renderItem,
|
|
766
|
+
} = this.props
|
|
767
|
+
const stickyOffset = ListHeaderComponent ? 1 : 0
|
|
768
|
+
const end = getItemCount(data) - 1
|
|
769
|
+
let prevCellKey
|
|
770
|
+
last = Math.min(end, last)
|
|
771
|
+
|
|
772
|
+
for (let ii = first; ii <= last; ii++) {
|
|
773
|
+
const item = getItem(data, ii)
|
|
774
|
+
const key = this._keyExtractor(item, ii, this.props)
|
|
775
|
+
|
|
776
|
+
this._indicesToKeys.set(ii, key)
|
|
777
|
+
if (stickyIndicesFromProps.has(ii + stickyOffset)) {
|
|
778
|
+
stickyHeaderIndices.push(cells.length)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const shouldListenForLayout =
|
|
782
|
+
getItemLayout == null || debug || this._fillRateHelper.enabled()
|
|
783
|
+
|
|
784
|
+
cells.push(
|
|
785
|
+
<CellRenderer
|
|
786
|
+
CellRendererComponent={CellRendererComponent}
|
|
787
|
+
ItemSeparatorComponent={ii < end ? ItemSeparatorComponent : undefined}
|
|
788
|
+
ListItemComponent={ListItemComponent}
|
|
789
|
+
cellKey={key}
|
|
790
|
+
horizontal={horizontal}
|
|
791
|
+
index={ii}
|
|
792
|
+
inversionStyle={inversionStyle}
|
|
793
|
+
item={item}
|
|
794
|
+
key={key}
|
|
795
|
+
prevCellKey={prevCellKey}
|
|
796
|
+
onUpdateSeparators={this._onUpdateSeparators}
|
|
797
|
+
onCellFocusCapture={(e) => this._onCellFocusCapture(key)}
|
|
798
|
+
onUnmount={this._onCellUnmount}
|
|
799
|
+
ref={(ref) => {
|
|
800
|
+
this._cellRefs[key] = ref
|
|
801
|
+
}}
|
|
802
|
+
renderItem={renderItem}
|
|
803
|
+
{...(shouldListenForLayout && {
|
|
804
|
+
onCellLayout: this._onCellLayout,
|
|
805
|
+
})}
|
|
806
|
+
/>
|
|
807
|
+
)
|
|
808
|
+
prevCellKey = key
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
static _constrainToItemCount(
|
|
813
|
+
cells: { first: number; last: number },
|
|
814
|
+
props: Props
|
|
815
|
+
): { first: number; last: number } {
|
|
816
|
+
const itemCount = props.getItemCount(props.data)
|
|
817
|
+
const last = Math.min(itemCount - 1, cells.last)
|
|
818
|
+
|
|
819
|
+
const maxToRenderPerBatch = maxToRenderPerBatchOrDefault(props.maxToRenderPerBatch)
|
|
820
|
+
|
|
821
|
+
return {
|
|
822
|
+
first: clamp(0, itemCount - 1 - maxToRenderPerBatch, cells.first),
|
|
823
|
+
last,
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
_onUpdateSeparators = (keys: Array<string | null>, newProps: Object) => {
|
|
828
|
+
keys.forEach((key) => {
|
|
829
|
+
const ref = key != null && this._cellRefs[key]
|
|
830
|
+
ref && ref.updateSeparatorProps(newProps)
|
|
831
|
+
})
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
_isNestedWithSameOrientation(): boolean {
|
|
835
|
+
const nestedContext = this.context
|
|
836
|
+
return !!(
|
|
837
|
+
nestedContext &&
|
|
838
|
+
!!nestedContext.horizontal === horizontalOrDefault(this.props.horizontal)
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
_getSpacerKey = (isVertical: boolean): string => (isVertical ? 'height' : 'width')
|
|
843
|
+
|
|
844
|
+
_keyExtractor(
|
|
845
|
+
item: Item,
|
|
846
|
+
index: number,
|
|
847
|
+
props: {
|
|
848
|
+
keyExtractor?: (item: Item, index: number) => string
|
|
849
|
+
}
|
|
850
|
+
): string {
|
|
851
|
+
if (props.keyExtractor != null) {
|
|
852
|
+
return props.keyExtractor(item, index)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const key = defaultKeyExtractor(item, index)
|
|
856
|
+
if (key === String(index)) {
|
|
857
|
+
_usedIndexForKey = true
|
|
858
|
+
if (item.type && item.type.displayName) {
|
|
859
|
+
_keylessItemComponentName = item.type.displayName
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return key
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
render(): React.ReactNode {
|
|
866
|
+
this._checkProps(this.props)
|
|
867
|
+
const { ListEmptyComponent, ListFooterComponent, ListHeaderComponent } = this.props
|
|
868
|
+
const { data, horizontal } = this.props
|
|
869
|
+
const inversionStyle = this.props.inverted
|
|
870
|
+
? horizontalOrDefault(this.props.horizontal)
|
|
871
|
+
? styles.horizontallyInverted
|
|
872
|
+
: styles.verticallyInverted
|
|
873
|
+
: null
|
|
874
|
+
const cells: Array<any | React.ReactNode> = []
|
|
875
|
+
const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices)
|
|
876
|
+
const stickyHeaderIndices = []
|
|
877
|
+
|
|
878
|
+
// 1. Add cell for ListHeaderComponent
|
|
879
|
+
if (ListHeaderComponent) {
|
|
880
|
+
if (stickyIndicesFromProps.has(0)) {
|
|
881
|
+
stickyHeaderIndices.push(0)
|
|
882
|
+
}
|
|
883
|
+
const element = React.isValidElement(ListHeaderComponent) ? (
|
|
884
|
+
ListHeaderComponent
|
|
885
|
+
) : (
|
|
886
|
+
<ListHeaderComponent />
|
|
887
|
+
)
|
|
888
|
+
cells.push(
|
|
889
|
+
<VirtualizedListCellContextProvider
|
|
890
|
+
cellKey={this._getCellKey() + '-header'}
|
|
891
|
+
key="$header"
|
|
892
|
+
>
|
|
893
|
+
<View
|
|
894
|
+
onLayout={this._onLayoutHeader}
|
|
895
|
+
style={[inversionStyle, this.props.ListHeaderComponentStyle]}
|
|
896
|
+
>
|
|
897
|
+
{element}
|
|
898
|
+
</View>
|
|
899
|
+
</VirtualizedListCellContextProvider>
|
|
900
|
+
)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// 2a. Add a cell for ListEmptyComponent if applicable
|
|
904
|
+
const itemCount = this.props.getItemCount(data)
|
|
905
|
+
if (itemCount === 0 && ListEmptyComponent) {
|
|
906
|
+
const element: React.ReactElement<any> = React.isValidElement(
|
|
907
|
+
ListEmptyComponent
|
|
908
|
+
) ? (
|
|
909
|
+
ListEmptyComponent
|
|
910
|
+
) : (
|
|
911
|
+
<ListEmptyComponent />
|
|
912
|
+
)
|
|
913
|
+
cells.push(
|
|
914
|
+
<VirtualizedListCellContextProvider
|
|
915
|
+
cellKey={this._getCellKey() + '-empty'}
|
|
916
|
+
key="$empty"
|
|
917
|
+
>
|
|
918
|
+
{React.cloneElement(element, {
|
|
919
|
+
onLayout: (event: LayoutChangeEvent) => {
|
|
920
|
+
this._onLayoutEmpty(event)
|
|
921
|
+
if (element.props.onLayout) {
|
|
922
|
+
element.props.onLayout(event)
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
style: [inversionStyle, element.props.style],
|
|
926
|
+
})}
|
|
927
|
+
</VirtualizedListCellContextProvider>
|
|
928
|
+
)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// 2b. Add cells and spacers for each item
|
|
932
|
+
if (itemCount > 0) {
|
|
933
|
+
_usedIndexForKey = false
|
|
934
|
+
_keylessItemComponentName = ''
|
|
935
|
+
const spacerKey = this._getSpacerKey(!horizontal)
|
|
936
|
+
|
|
937
|
+
const renderRegions = this.state.renderMask.enumerateRegions()
|
|
938
|
+
const lastSpacer = findLastWhere(renderRegions, (r) => r.isSpacer)
|
|
939
|
+
|
|
940
|
+
for (const section of renderRegions) {
|
|
941
|
+
if (section.isSpacer) {
|
|
942
|
+
// Legacy behavior is to avoid spacers when virtualization is
|
|
943
|
+
// disabled (including head spacers on initial render).
|
|
944
|
+
if (this.props.disableVirtualization) {
|
|
945
|
+
continue
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Without getItemLayout, we limit our tail spacer to the _highestMeasuredFrameIndex to
|
|
949
|
+
// prevent the user for hyperscrolling into un-measured area because otherwise content will
|
|
950
|
+
// likely jump around as it renders in above the viewport.
|
|
951
|
+
const isLastSpacer = section === lastSpacer
|
|
952
|
+
const constrainToMeasured = isLastSpacer && !this.props.getItemLayout
|
|
953
|
+
const last = constrainToMeasured
|
|
954
|
+
? clamp(section.first - 1, section.last, this._highestMeasuredFrameIndex)
|
|
955
|
+
: section.last
|
|
956
|
+
|
|
957
|
+
const firstMetrics = this.__getFrameMetricsApprox(section.first, this.props)
|
|
958
|
+
const lastMetrics = this.__getFrameMetricsApprox(last, this.props)
|
|
959
|
+
const spacerSize = lastMetrics.offset + lastMetrics.length - firstMetrics.offset
|
|
960
|
+
cells.push(
|
|
961
|
+
<View key={`$spacer-${section.first}`} style={{ [spacerKey]: spacerSize }} />
|
|
962
|
+
)
|
|
963
|
+
} else {
|
|
964
|
+
this._pushCells(
|
|
965
|
+
cells,
|
|
966
|
+
stickyHeaderIndices,
|
|
967
|
+
stickyIndicesFromProps,
|
|
968
|
+
section.first,
|
|
969
|
+
section.last,
|
|
970
|
+
inversionStyle
|
|
971
|
+
)
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (!this._hasWarned.keys && _usedIndexForKey) {
|
|
976
|
+
console.warn(
|
|
977
|
+
'VirtualizedList: missing keys for items, make sure to specify a key or id property on each ' +
|
|
978
|
+
'item or provide a custom keyExtractor.',
|
|
979
|
+
_keylessItemComponentName
|
|
980
|
+
)
|
|
981
|
+
this._hasWarned.keys = true
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// 3. Add cell for ListFooterComponent
|
|
986
|
+
if (ListFooterComponent) {
|
|
987
|
+
const element = React.isValidElement(ListFooterComponent) ? (
|
|
988
|
+
ListFooterComponent
|
|
989
|
+
) : (
|
|
990
|
+
<ListFooterComponent />
|
|
991
|
+
)
|
|
992
|
+
cells.push(
|
|
993
|
+
<VirtualizedListCellContextProvider
|
|
994
|
+
cellKey={this._getFooterCellKey()}
|
|
995
|
+
key="$footer"
|
|
996
|
+
>
|
|
997
|
+
<View
|
|
998
|
+
onLayout={this._onLayoutFooter}
|
|
999
|
+
style={[inversionStyle, this.props.ListFooterComponentStyle]}
|
|
1000
|
+
>
|
|
1001
|
+
{element}
|
|
1002
|
+
</View>
|
|
1003
|
+
</VirtualizedListCellContextProvider>
|
|
1004
|
+
)
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// 4. Render the ScrollView
|
|
1008
|
+
const scrollProps = {
|
|
1009
|
+
...this.props,
|
|
1010
|
+
onContentSizeChange: this._onContentSizeChange,
|
|
1011
|
+
onLayout: this._onLayout,
|
|
1012
|
+
onScroll: this._onScroll,
|
|
1013
|
+
onScrollBeginDrag: this._onScrollBeginDrag,
|
|
1014
|
+
onScrollEndDrag: this._onScrollEndDrag,
|
|
1015
|
+
onMomentumScrollBegin: this._onMomentumScrollBegin,
|
|
1016
|
+
onMomentumScrollEnd: this._onMomentumScrollEnd,
|
|
1017
|
+
scrollEventThrottle: scrollEventThrottleOrDefault(this.props.scrollEventThrottle),
|
|
1018
|
+
invertStickyHeaders:
|
|
1019
|
+
this.props.invertStickyHeaders !== undefined
|
|
1020
|
+
? this.props.invertStickyHeaders
|
|
1021
|
+
: this.props.inverted,
|
|
1022
|
+
stickyHeaderIndices,
|
|
1023
|
+
style: inversionStyle ? [inversionStyle, this.props.style] : this.props.style,
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
this._hasMore = this.state.cellsAroundViewport.last < itemCount - 1
|
|
1027
|
+
|
|
1028
|
+
const innerRet = (
|
|
1029
|
+
<VirtualizedListContextProvider
|
|
1030
|
+
value={{
|
|
1031
|
+
cellKey: null,
|
|
1032
|
+
getScrollMetrics: this._getScrollMetrics,
|
|
1033
|
+
horizontal: horizontalOrDefault(this.props.horizontal),
|
|
1034
|
+
getOutermostParentListRef: this._getOutermostParentListRef,
|
|
1035
|
+
registerAsNestedChild: this._registerAsNestedChild,
|
|
1036
|
+
unregisterAsNestedChild: this._unregisterAsNestedChild,
|
|
1037
|
+
}}
|
|
1038
|
+
>
|
|
1039
|
+
{React.cloneElement(
|
|
1040
|
+
(this.props.renderScrollComponent || this._defaultRenderScrollComponent)(
|
|
1041
|
+
scrollProps
|
|
1042
|
+
),
|
|
1043
|
+
{
|
|
1044
|
+
ref: this._captureScrollRef,
|
|
1045
|
+
},
|
|
1046
|
+
cells
|
|
1047
|
+
)}
|
|
1048
|
+
</VirtualizedListContextProvider>
|
|
1049
|
+
)
|
|
1050
|
+
let ret: React.ReactNode = innerRet
|
|
1051
|
+
return ret
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
componentDidUpdate(prevProps: Props) {
|
|
1055
|
+
const { data, extraData } = this.props
|
|
1056
|
+
if (data !== prevProps.data || extraData !== prevProps.extraData) {
|
|
1057
|
+
this._viewabilityTuples.forEach((tuple) => {
|
|
1058
|
+
tuple.viewabilityHelper.resetViewableIndices()
|
|
1059
|
+
})
|
|
1060
|
+
}
|
|
1061
|
+
const hiPriInProgress = this._hiPriInProgress
|
|
1062
|
+
this._scheduleCellsToRenderUpdate()
|
|
1063
|
+
if (hiPriInProgress) {
|
|
1064
|
+
this._hiPriInProgress = false
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
_averageCellLength = 0
|
|
1069
|
+
_cellRefs: { [key: string]: CellRenderer<any> | null } = {}
|
|
1070
|
+
_fillRateHelper: FillRateHelper
|
|
1071
|
+
_frames: {
|
|
1072
|
+
[key: string]: {
|
|
1073
|
+
inLayout?: boolean
|
|
1074
|
+
index: number
|
|
1075
|
+
length: number
|
|
1076
|
+
offset: number
|
|
1077
|
+
}
|
|
1078
|
+
} = {}
|
|
1079
|
+
_footerLength = 0
|
|
1080
|
+
_hasTriggeredInitialScrollToIndex = false
|
|
1081
|
+
_hasInteracted = false
|
|
1082
|
+
_hasMore = false
|
|
1083
|
+
_hasWarned: { [key: string]: boolean } = {}
|
|
1084
|
+
_headerLength = 0
|
|
1085
|
+
_hiPriInProgress = false
|
|
1086
|
+
_highestMeasuredFrameIndex = 0
|
|
1087
|
+
_indicesToKeys: Map<number, string> = new Map()
|
|
1088
|
+
_lastFocusedCellKey: string | null = null
|
|
1089
|
+
_nestedChildLists: ChildListCollection<VirtualizedList> = new ChildListCollection()
|
|
1090
|
+
_offsetFromParentVirtualizedList = 0
|
|
1091
|
+
_prevParentOffset = 0
|
|
1092
|
+
_scrollMetrics = {
|
|
1093
|
+
contentLength: 0,
|
|
1094
|
+
dOffset: 0,
|
|
1095
|
+
dt: 10,
|
|
1096
|
+
offset: 0,
|
|
1097
|
+
timestamp: 0,
|
|
1098
|
+
velocity: 0,
|
|
1099
|
+
visibleLength: 0,
|
|
1100
|
+
zoomScale: 1,
|
|
1101
|
+
}
|
|
1102
|
+
_scrollRef: any = null
|
|
1103
|
+
_sentStartForContentLength = 0
|
|
1104
|
+
_sentEndForContentLength = 0
|
|
1105
|
+
_totalCellLength = 0
|
|
1106
|
+
_totalCellsMeasured = 0
|
|
1107
|
+
_updateCellsToRenderBatcher: Batchinator
|
|
1108
|
+
_viewabilityTuples: Array<ViewabilityHelperCallbackTuple> = []
|
|
1109
|
+
|
|
1110
|
+
_captureScrollRef = (ref: any) => {
|
|
1111
|
+
this._scrollRef = ref
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
_computeBlankness() {
|
|
1115
|
+
this._fillRateHelper.computeBlankness(
|
|
1116
|
+
// @ts-ignore
|
|
1117
|
+
this.props,
|
|
1118
|
+
this.state.cellsAroundViewport,
|
|
1119
|
+
this._scrollMetrics
|
|
1120
|
+
)
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
_defaultRenderScrollComponent = (props: any) => {
|
|
1124
|
+
const onRefresh = props.onRefresh
|
|
1125
|
+
if (this._isNestedWithSameOrientation()) {
|
|
1126
|
+
return <View {...props} />
|
|
1127
|
+
} else if (onRefresh) {
|
|
1128
|
+
invariant(
|
|
1129
|
+
typeof props.refreshing === 'boolean',
|
|
1130
|
+
'`refreshing` prop must be set as a boolean in order to use `onRefresh`, but got `' +
|
|
1131
|
+
JSON.stringify(props.refreshing ?? 'undefined') +
|
|
1132
|
+
'`'
|
|
1133
|
+
)
|
|
1134
|
+
return (
|
|
1135
|
+
<ScrollView
|
|
1136
|
+
{...props}
|
|
1137
|
+
refreshControl={
|
|
1138
|
+
props.refreshControl == null ? (
|
|
1139
|
+
<RefreshControl
|
|
1140
|
+
refreshing={props.refreshing}
|
|
1141
|
+
onRefresh={onRefresh}
|
|
1142
|
+
progressViewOffset={props.progressViewOffset}
|
|
1143
|
+
/>
|
|
1144
|
+
) : (
|
|
1145
|
+
props.refreshControl
|
|
1146
|
+
)
|
|
1147
|
+
}
|
|
1148
|
+
/>
|
|
1149
|
+
)
|
|
1150
|
+
} else {
|
|
1151
|
+
return <ScrollView {...props} />
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
_onCellLayout = (e: any, cellKey: string, index: number): void => {
|
|
1156
|
+
const layout = e.nativeEvent.layout
|
|
1157
|
+
const next = {
|
|
1158
|
+
offset: this._selectOffset(layout),
|
|
1159
|
+
length: this._selectLength(layout),
|
|
1160
|
+
index,
|
|
1161
|
+
inLayout: true,
|
|
1162
|
+
}
|
|
1163
|
+
const curr = this._frames[cellKey]
|
|
1164
|
+
if (
|
|
1165
|
+
!curr ||
|
|
1166
|
+
next.offset !== curr.offset ||
|
|
1167
|
+
next.length !== curr.length ||
|
|
1168
|
+
index !== curr.index
|
|
1169
|
+
) {
|
|
1170
|
+
this._totalCellLength += next.length - (curr ? curr.length : 0)
|
|
1171
|
+
this._totalCellsMeasured += curr ? 0 : 1
|
|
1172
|
+
this._averageCellLength = this._totalCellLength / this._totalCellsMeasured
|
|
1173
|
+
this._frames[cellKey] = next
|
|
1174
|
+
this._highestMeasuredFrameIndex = Math.max(this._highestMeasuredFrameIndex, index)
|
|
1175
|
+
this._scheduleCellsToRenderUpdate()
|
|
1176
|
+
} else {
|
|
1177
|
+
this._frames[cellKey].inLayout = true
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
this._triggerRemeasureForChildListsInCell(cellKey)
|
|
1181
|
+
|
|
1182
|
+
this._computeBlankness()
|
|
1183
|
+
this._updateViewableItems(
|
|
1184
|
+
// @ts-ignore
|
|
1185
|
+
this.props,
|
|
1186
|
+
this.state.cellsAroundViewport
|
|
1187
|
+
)
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
_onCellFocusCapture(cellKey: string) {
|
|
1191
|
+
this._lastFocusedCellKey = cellKey
|
|
1192
|
+
this._updateCellsToRender()
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
_onCellUnmount = (cellKey: string) => {
|
|
1196
|
+
delete this._cellRefs[cellKey]
|
|
1197
|
+
const curr = this._frames[cellKey]
|
|
1198
|
+
if (curr) {
|
|
1199
|
+
this._frames[cellKey] = { ...curr, inLayout: false }
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
_triggerRemeasureForChildListsInCell(cellKey: string): void {
|
|
1204
|
+
this._nestedChildLists.forEachInCell(cellKey, (childList) => {
|
|
1205
|
+
childList.measureLayoutRelativeToContainingList()
|
|
1206
|
+
})
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
measureLayoutRelativeToContainingList(): void {
|
|
1210
|
+
try {
|
|
1211
|
+
if (!this._scrollRef) {
|
|
1212
|
+
return
|
|
1213
|
+
}
|
|
1214
|
+
this._scrollRef.measureLayout(
|
|
1215
|
+
this.context.getOutermostParentListRef().getScrollRef(),
|
|
1216
|
+
(x, y, width, height) => {
|
|
1217
|
+
this._offsetFromParentVirtualizedList = this._selectOffset({ x, y })
|
|
1218
|
+
this._scrollMetrics.contentLength = this._selectLength({
|
|
1219
|
+
width,
|
|
1220
|
+
height,
|
|
1221
|
+
})
|
|
1222
|
+
const scrollMetrics = this._convertParentScrollMetrics(
|
|
1223
|
+
this.context.getScrollMetrics()
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
const metricsChanged =
|
|
1227
|
+
this._scrollMetrics.visibleLength !== scrollMetrics.visibleLength ||
|
|
1228
|
+
this._scrollMetrics.offset !== scrollMetrics.offset
|
|
1229
|
+
|
|
1230
|
+
if (metricsChanged) {
|
|
1231
|
+
this._scrollMetrics.visibleLength = scrollMetrics.visibleLength
|
|
1232
|
+
this._scrollMetrics.offset = scrollMetrics.offset
|
|
1233
|
+
|
|
1234
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1235
|
+
childList.measureLayoutRelativeToContainingList()
|
|
1236
|
+
})
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
(error) => {
|
|
1240
|
+
console.warn(
|
|
1241
|
+
"VirtualizedList: Encountered an error while measuring a list's" +
|
|
1242
|
+
' offset from its containing VirtualizedList.'
|
|
1243
|
+
)
|
|
1244
|
+
}
|
|
1245
|
+
)
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
console.warn('measureLayoutRelativeToContainingList threw an error', error.stack)
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
_onLayout = (e: LayoutChangeEvent) => {
|
|
1252
|
+
if (this._isNestedWithSameOrientation()) {
|
|
1253
|
+
this.measureLayoutRelativeToContainingList()
|
|
1254
|
+
} else {
|
|
1255
|
+
this._scrollMetrics.visibleLength = this._selectLength(e.nativeEvent.layout)
|
|
1256
|
+
}
|
|
1257
|
+
this.props.onLayout && this.props.onLayout(e)
|
|
1258
|
+
this._scheduleCellsToRenderUpdate()
|
|
1259
|
+
this._maybeCallOnEdgeReached()
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
_onLayoutEmpty = (e: LayoutChangeEvent) => {
|
|
1263
|
+
this.props.onLayout && this.props.onLayout(e)
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
_getFooterCellKey(): string {
|
|
1267
|
+
return this._getCellKey() + '-footer'
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
_onLayoutFooter = (e: LayoutChangeEvent) => {
|
|
1271
|
+
this._triggerRemeasureForChildListsInCell(this._getFooterCellKey())
|
|
1272
|
+
this._footerLength = this._selectLength(e.nativeEvent.layout)
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
_onLayoutHeader = (e: LayoutChangeEvent) => {
|
|
1276
|
+
this._headerLength = this._selectLength(e.nativeEvent.layout)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
_renderDebugOverlay() {
|
|
1280
|
+
const normalize =
|
|
1281
|
+
this._scrollMetrics.visibleLength / (this._scrollMetrics.contentLength || 1)
|
|
1282
|
+
const framesInLayout = []
|
|
1283
|
+
const itemCount = this.props.getItemCount(this.props.data)
|
|
1284
|
+
for (let ii = 0; ii < itemCount; ii++) {
|
|
1285
|
+
const frame = this.__getFrameMetricsApprox(ii, this.props)
|
|
1286
|
+
if (frame.inLayout) {
|
|
1287
|
+
framesInLayout.push(frame)
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const windowTop = this.__getFrameMetricsApprox(
|
|
1291
|
+
this.state.cellsAroundViewport.first,
|
|
1292
|
+
this.props
|
|
1293
|
+
).offset
|
|
1294
|
+
const frameLast = this.__getFrameMetricsApprox(
|
|
1295
|
+
this.state.cellsAroundViewport.last,
|
|
1296
|
+
this.props
|
|
1297
|
+
)
|
|
1298
|
+
const windowLen = frameLast.offset + frameLast.length - windowTop
|
|
1299
|
+
const visTop = this._scrollMetrics.offset
|
|
1300
|
+
const visLen = this._scrollMetrics.visibleLength
|
|
1301
|
+
|
|
1302
|
+
return (
|
|
1303
|
+
<View style={[styles.debugOverlayBase, styles.debugOverlay]}>
|
|
1304
|
+
{framesInLayout.map((f, ii) => (
|
|
1305
|
+
<View
|
|
1306
|
+
key={'f' + ii}
|
|
1307
|
+
style={[
|
|
1308
|
+
styles.debugOverlayBase,
|
|
1309
|
+
styles.debugOverlayFrame,
|
|
1310
|
+
{
|
|
1311
|
+
top: f.offset * normalize,
|
|
1312
|
+
height: f.length * normalize,
|
|
1313
|
+
},
|
|
1314
|
+
]}
|
|
1315
|
+
/>
|
|
1316
|
+
))}
|
|
1317
|
+
<View
|
|
1318
|
+
style={[
|
|
1319
|
+
styles.debugOverlayBase,
|
|
1320
|
+
styles.debugOverlayFrameLast,
|
|
1321
|
+
{
|
|
1322
|
+
top: windowTop * normalize,
|
|
1323
|
+
height: windowLen * normalize,
|
|
1324
|
+
},
|
|
1325
|
+
]}
|
|
1326
|
+
/>
|
|
1327
|
+
<View
|
|
1328
|
+
style={[
|
|
1329
|
+
styles.debugOverlayBase,
|
|
1330
|
+
styles.debugOverlayFrameVis,
|
|
1331
|
+
{
|
|
1332
|
+
top: visTop * normalize,
|
|
1333
|
+
height: visLen * normalize,
|
|
1334
|
+
},
|
|
1335
|
+
]}
|
|
1336
|
+
/>
|
|
1337
|
+
</View>
|
|
1338
|
+
)
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
_selectLength(metrics: { height: number; width: number }): number {
|
|
1342
|
+
return !horizontalOrDefault(this.props.horizontal) ? metrics.height : metrics.width
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
_selectOffset(metrics: { x: number; y: number }): number {
|
|
1346
|
+
return !horizontalOrDefault(this.props.horizontal) ? metrics.y : metrics.x
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
_maybeCallOnEdgeReached() {
|
|
1350
|
+
const {
|
|
1351
|
+
data,
|
|
1352
|
+
getItemCount,
|
|
1353
|
+
onStartReached,
|
|
1354
|
+
onStartReachedThreshold,
|
|
1355
|
+
onEndReached,
|
|
1356
|
+
onEndReachedThreshold,
|
|
1357
|
+
initialScrollIndex,
|
|
1358
|
+
} = this.props
|
|
1359
|
+
const { contentLength, visibleLength, offset } = this._scrollMetrics
|
|
1360
|
+
let distanceFromStart = offset
|
|
1361
|
+
let distanceFromEnd = contentLength - visibleLength - offset
|
|
1362
|
+
|
|
1363
|
+
if (distanceFromStart < ON_EDGE_REACHED_EPSILON) {
|
|
1364
|
+
distanceFromStart = 0
|
|
1365
|
+
}
|
|
1366
|
+
if (distanceFromEnd < ON_EDGE_REACHED_EPSILON) {
|
|
1367
|
+
distanceFromEnd = 0
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const DEFAULT_THRESHOLD_PX = 2
|
|
1371
|
+
|
|
1372
|
+
const startThreshold =
|
|
1373
|
+
onStartReachedThreshold != null
|
|
1374
|
+
? onStartReachedThreshold * visibleLength
|
|
1375
|
+
: DEFAULT_THRESHOLD_PX
|
|
1376
|
+
const endThreshold =
|
|
1377
|
+
onEndReachedThreshold != null
|
|
1378
|
+
? onEndReachedThreshold * visibleLength
|
|
1379
|
+
: DEFAULT_THRESHOLD_PX
|
|
1380
|
+
const isWithinStartThreshold = distanceFromStart <= startThreshold
|
|
1381
|
+
const isWithinEndThreshold = distanceFromEnd <= endThreshold
|
|
1382
|
+
|
|
1383
|
+
if (
|
|
1384
|
+
onEndReached &&
|
|
1385
|
+
this.state.cellsAroundViewport.last === getItemCount(data) - 1 &&
|
|
1386
|
+
isWithinEndThreshold &&
|
|
1387
|
+
this._scrollMetrics.contentLength !== this._sentEndForContentLength
|
|
1388
|
+
) {
|
|
1389
|
+
this._sentEndForContentLength = this._scrollMetrics.contentLength
|
|
1390
|
+
onEndReached({ distanceFromEnd })
|
|
1391
|
+
} else if (
|
|
1392
|
+
onStartReached != null &&
|
|
1393
|
+
this.state.cellsAroundViewport.first === 0 &&
|
|
1394
|
+
isWithinStartThreshold &&
|
|
1395
|
+
this._scrollMetrics.contentLength !== this._sentStartForContentLength
|
|
1396
|
+
) {
|
|
1397
|
+
if (!initialScrollIndex || this._scrollMetrics.timestamp !== 0) {
|
|
1398
|
+
this._sentStartForContentLength = this._scrollMetrics.contentLength
|
|
1399
|
+
onStartReached({ distanceFromStart })
|
|
1400
|
+
}
|
|
1401
|
+
} else {
|
|
1402
|
+
this._sentStartForContentLength = isWithinStartThreshold
|
|
1403
|
+
? this._sentStartForContentLength
|
|
1404
|
+
: 0
|
|
1405
|
+
this._sentEndForContentLength = isWithinEndThreshold
|
|
1406
|
+
? this._sentEndForContentLength
|
|
1407
|
+
: 0
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
_onContentSizeChange = (width: number, height: number) => {
|
|
1412
|
+
if (
|
|
1413
|
+
width > 0 &&
|
|
1414
|
+
height > 0 &&
|
|
1415
|
+
this.props.initialScrollIndex != null &&
|
|
1416
|
+
this.props.initialScrollIndex > 0 &&
|
|
1417
|
+
!this._hasTriggeredInitialScrollToIndex
|
|
1418
|
+
) {
|
|
1419
|
+
if (this.props.contentOffset == null) {
|
|
1420
|
+
if (this.props.initialScrollIndex < this.props.getItemCount(this.props.data)) {
|
|
1421
|
+
this.scrollToIndex({
|
|
1422
|
+
animated: false,
|
|
1423
|
+
index: nullthrows(this.props.initialScrollIndex),
|
|
1424
|
+
})
|
|
1425
|
+
} else {
|
|
1426
|
+
this.scrollToEnd({ animated: false })
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
this._hasTriggeredInitialScrollToIndex = true
|
|
1430
|
+
}
|
|
1431
|
+
if (this.props.onContentSizeChange) {
|
|
1432
|
+
this.props.onContentSizeChange(width, height)
|
|
1433
|
+
}
|
|
1434
|
+
this._scrollMetrics.contentLength = this._selectLength({ height, width })
|
|
1435
|
+
this._scheduleCellsToRenderUpdate()
|
|
1436
|
+
this._maybeCallOnEdgeReached()
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
_convertParentScrollMetrics = (metrics: {
|
|
1440
|
+
visibleLength: number
|
|
1441
|
+
offset: number
|
|
1442
|
+
}) => {
|
|
1443
|
+
const offset = metrics.offset - this._offsetFromParentVirtualizedList
|
|
1444
|
+
const visibleLength = metrics.visibleLength
|
|
1445
|
+
const dOffset = offset - this._scrollMetrics.offset
|
|
1446
|
+
const contentLength = this._scrollMetrics.contentLength
|
|
1447
|
+
|
|
1448
|
+
return {
|
|
1449
|
+
visibleLength,
|
|
1450
|
+
contentLength,
|
|
1451
|
+
offset,
|
|
1452
|
+
dOffset,
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
_onScroll = (e: ScrollEvent) => {
|
|
1457
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1458
|
+
childList._onScroll(e)
|
|
1459
|
+
})
|
|
1460
|
+
if (this.props.onScroll) {
|
|
1461
|
+
this.props.onScroll(e)
|
|
1462
|
+
}
|
|
1463
|
+
const timestamp = e.timeStamp
|
|
1464
|
+
let visibleLength = this._selectLength(e.nativeEvent.layoutMeasurement)
|
|
1465
|
+
let contentLength = this._selectLength(e.nativeEvent.contentSize)
|
|
1466
|
+
let offset = this._selectOffset(e.nativeEvent.contentOffset)
|
|
1467
|
+
let dOffset = offset - this._scrollMetrics.offset
|
|
1468
|
+
|
|
1469
|
+
if (this._isNestedWithSameOrientation()) {
|
|
1470
|
+
if (this._scrollMetrics.contentLength === 0) {
|
|
1471
|
+
return
|
|
1472
|
+
}
|
|
1473
|
+
;({ visibleLength, contentLength, offset, dOffset } =
|
|
1474
|
+
this._convertParentScrollMetrics({
|
|
1475
|
+
visibleLength,
|
|
1476
|
+
offset,
|
|
1477
|
+
}))
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const dt = this._scrollMetrics.timestamp
|
|
1481
|
+
? Math.max(1, timestamp - this._scrollMetrics.timestamp)
|
|
1482
|
+
: 1
|
|
1483
|
+
const velocity = dOffset / dt
|
|
1484
|
+
|
|
1485
|
+
if (
|
|
1486
|
+
dt > 500 &&
|
|
1487
|
+
this._scrollMetrics.dt > 500 &&
|
|
1488
|
+
contentLength > 5 * visibleLength &&
|
|
1489
|
+
!this._hasWarned.perf
|
|
1490
|
+
) {
|
|
1491
|
+
infoLog(
|
|
1492
|
+
'VirtualizedList: You have a large list that is slow to update - make sure your ' +
|
|
1493
|
+
'renderItem function renders components that follow React performance best practices ' +
|
|
1494
|
+
'like PureComponent, shouldComponentUpdate, etc.',
|
|
1495
|
+
{ dt, prevDt: this._scrollMetrics.dt, contentLength }
|
|
1496
|
+
)
|
|
1497
|
+
this._hasWarned.perf = true
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const zoomScale = e.nativeEvent.zoomScale < 0 ? 1 : e.nativeEvent.zoomScale
|
|
1501
|
+
this._scrollMetrics = {
|
|
1502
|
+
contentLength,
|
|
1503
|
+
dt,
|
|
1504
|
+
dOffset,
|
|
1505
|
+
offset,
|
|
1506
|
+
timestamp,
|
|
1507
|
+
velocity,
|
|
1508
|
+
visibleLength,
|
|
1509
|
+
zoomScale,
|
|
1510
|
+
}
|
|
1511
|
+
this._updateViewableItems(this.props, this.state.cellsAroundViewport)
|
|
1512
|
+
if (!this.props) {
|
|
1513
|
+
return
|
|
1514
|
+
}
|
|
1515
|
+
this._maybeCallOnEdgeReached()
|
|
1516
|
+
if (velocity !== 0) {
|
|
1517
|
+
this._fillRateHelper.activate()
|
|
1518
|
+
}
|
|
1519
|
+
this._computeBlankness()
|
|
1520
|
+
this._scheduleCellsToRenderUpdate()
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
_scheduleCellsToRenderUpdate() {
|
|
1524
|
+
const { first, last } = this.state.cellsAroundViewport
|
|
1525
|
+
const { offset, visibleLength, velocity } = this._scrollMetrics
|
|
1526
|
+
const itemCount = this.props.getItemCount(this.props.data)
|
|
1527
|
+
let hiPri = false
|
|
1528
|
+
const onStartReachedThreshold = onStartReachedThresholdOrDefault(
|
|
1529
|
+
this.props.onStartReachedThreshold
|
|
1530
|
+
)
|
|
1531
|
+
const onEndReachedThreshold = onEndReachedThresholdOrDefault(
|
|
1532
|
+
this.props.onEndReachedThreshold
|
|
1533
|
+
)
|
|
1534
|
+
if (first > 0) {
|
|
1535
|
+
const distTop = offset - this.__getFrameMetricsApprox(first, this.props).offset
|
|
1536
|
+
hiPri =
|
|
1537
|
+
distTop < 0 ||
|
|
1538
|
+
(velocity < -2 &&
|
|
1539
|
+
distTop < getScrollingThreshold(onStartReachedThreshold, visibleLength))
|
|
1540
|
+
}
|
|
1541
|
+
if (!hiPri && last >= 0 && last < itemCount - 1) {
|
|
1542
|
+
const distBottom =
|
|
1543
|
+
this.__getFrameMetricsApprox(last, this.props).offset - (offset + visibleLength)
|
|
1544
|
+
hiPri =
|
|
1545
|
+
distBottom < 0 ||
|
|
1546
|
+
(velocity > 2 &&
|
|
1547
|
+
distBottom < getScrollingThreshold(onEndReachedThreshold, visibleLength))
|
|
1548
|
+
}
|
|
1549
|
+
if (
|
|
1550
|
+
hiPri &&
|
|
1551
|
+
(this._averageCellLength || this.props.getItemLayout) &&
|
|
1552
|
+
!this._hiPriInProgress
|
|
1553
|
+
) {
|
|
1554
|
+
this._hiPriInProgress = true
|
|
1555
|
+
this._updateCellsToRenderBatcher.dispose({ abort: true })
|
|
1556
|
+
this._updateCellsToRender()
|
|
1557
|
+
return
|
|
1558
|
+
} else {
|
|
1559
|
+
this._updateCellsToRenderBatcher.schedule()
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
_onScrollBeginDrag = (e: ScrollEvent): void => {
|
|
1564
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1565
|
+
childList._onScrollBeginDrag(e)
|
|
1566
|
+
})
|
|
1567
|
+
this._viewabilityTuples.forEach((tuple) => {
|
|
1568
|
+
tuple.viewabilityHelper.recordInteraction()
|
|
1569
|
+
})
|
|
1570
|
+
this._hasInteracted = true
|
|
1571
|
+
this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e)
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
_onScrollEndDrag = (e: ScrollEvent): void => {
|
|
1575
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1576
|
+
childList._onScrollEndDrag(e)
|
|
1577
|
+
})
|
|
1578
|
+
// @ts-ignore
|
|
1579
|
+
const { velocity } = e.nativeEvent
|
|
1580
|
+
if (velocity) {
|
|
1581
|
+
this._scrollMetrics.velocity = this._selectOffset(velocity)
|
|
1582
|
+
}
|
|
1583
|
+
this._computeBlankness()
|
|
1584
|
+
this.props.onScrollEndDrag && this.props.onScrollEndDrag(e)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
_onMomentumScrollBegin = (e: ScrollEvent): void => {
|
|
1588
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1589
|
+
childList._onMomentumScrollBegin(e)
|
|
1590
|
+
})
|
|
1591
|
+
this.props.onMomentumScrollBegin && this.props.onMomentumScrollBegin(e)
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
_onMomentumScrollEnd = (e: ScrollEvent): void => {
|
|
1595
|
+
this._nestedChildLists.forEach((childList) => {
|
|
1596
|
+
childList._onMomentumScrollEnd(e)
|
|
1597
|
+
})
|
|
1598
|
+
this._scrollMetrics.velocity = 0
|
|
1599
|
+
this._computeBlankness()
|
|
1600
|
+
this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(e)
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
_updateCellsToRender = () => {
|
|
1604
|
+
this._updateViewableItems(this.props, this.state.cellsAroundViewport)
|
|
1605
|
+
|
|
1606
|
+
this.setState((state, props) => {
|
|
1607
|
+
const cellsAroundViewport = this._adjustCellsAroundViewport(
|
|
1608
|
+
props,
|
|
1609
|
+
state.cellsAroundViewport
|
|
1610
|
+
)
|
|
1611
|
+
const renderMask = VirtualizedList._createRenderMask(
|
|
1612
|
+
props,
|
|
1613
|
+
cellsAroundViewport,
|
|
1614
|
+
this._getNonViewportRenderRegions(props)
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
if (
|
|
1618
|
+
cellsAroundViewport.first === state.cellsAroundViewport.first &&
|
|
1619
|
+
cellsAroundViewport.last === state.cellsAroundViewport.last &&
|
|
1620
|
+
renderMask.equals(state.renderMask)
|
|
1621
|
+
) {
|
|
1622
|
+
return null
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
return { cellsAroundViewport, renderMask }
|
|
1626
|
+
})
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
_createViewToken = (index: number, isViewable: boolean, props: FrameMetricProps) => {
|
|
1630
|
+
const { data, getItem } = props
|
|
1631
|
+
const item = getItem(data, index)
|
|
1632
|
+
return {
|
|
1633
|
+
index,
|
|
1634
|
+
item,
|
|
1635
|
+
key: this._keyExtractor(item, index, props),
|
|
1636
|
+
isViewable,
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
_getOffsetApprox = (index: number, props: FrameMetricProps): number => {
|
|
1641
|
+
if (Number.isInteger(index)) {
|
|
1642
|
+
return this.__getFrameMetricsApprox(index, props).offset
|
|
1643
|
+
} else {
|
|
1644
|
+
const frameMetrics = this.__getFrameMetricsApprox(Math.floor(index), props)
|
|
1645
|
+
const remainder = index - Math.floor(index)
|
|
1646
|
+
return frameMetrics.offset + remainder * frameMetrics.length
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
__getFrameMetricsApprox = (index: number, props: FrameMetricProps) => {
|
|
1651
|
+
const frame = this._getFrameMetrics(index, props)
|
|
1652
|
+
if (frame && frame.index === index) {
|
|
1653
|
+
return frame
|
|
1654
|
+
} else {
|
|
1655
|
+
const { data, getItemCount, getItemLayout } = props
|
|
1656
|
+
invariant(
|
|
1657
|
+
index >= 0 && index < getItemCount(data),
|
|
1658
|
+
'Tried to get frame for out of range index ' + index
|
|
1659
|
+
)
|
|
1660
|
+
invariant(
|
|
1661
|
+
!getItemLayout,
|
|
1662
|
+
'Should not have to estimate frames when a measurement metrics function is provided'
|
|
1663
|
+
)
|
|
1664
|
+
return {
|
|
1665
|
+
length: this._averageCellLength,
|
|
1666
|
+
offset: this._averageCellLength * index,
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
_getFrameMetrics = (
|
|
1672
|
+
index: number,
|
|
1673
|
+
props: FrameMetricProps
|
|
1674
|
+
): {
|
|
1675
|
+
length: number
|
|
1676
|
+
offset: number
|
|
1677
|
+
index: number
|
|
1678
|
+
inLayout?: boolean
|
|
1679
|
+
} | null => {
|
|
1680
|
+
const { data, getItem, getItemCount, getItemLayout } = props
|
|
1681
|
+
invariant(
|
|
1682
|
+
index >= 0 && index < getItemCount(data),
|
|
1683
|
+
'Tried to get frame for out of range index ' + index
|
|
1684
|
+
)
|
|
1685
|
+
const item = getItem(data, index)
|
|
1686
|
+
const frame = this._frames[this._keyExtractor(item, index, props)]
|
|
1687
|
+
if (!frame || frame.index !== index) {
|
|
1688
|
+
if (getItemLayout) {
|
|
1689
|
+
return getItemLayout(data, index)
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return frame
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
_getNonViewportRenderRegions = (
|
|
1696
|
+
props: FrameMetricProps
|
|
1697
|
+
): Array<{ first: number; last: number }> => {
|
|
1698
|
+
if (!(this._lastFocusedCellKey && this._cellRefs[this._lastFocusedCellKey])) {
|
|
1699
|
+
return []
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const lastFocusedCellRenderer = this._cellRefs[this._lastFocusedCellKey]
|
|
1703
|
+
const focusedCellIndex = lastFocusedCellRenderer!.props.index
|
|
1704
|
+
const itemCount = props.getItemCount(props.data)
|
|
1705
|
+
|
|
1706
|
+
if (
|
|
1707
|
+
focusedCellIndex >= itemCount ||
|
|
1708
|
+
this._keyExtractor(
|
|
1709
|
+
props.getItem(props.data, focusedCellIndex),
|
|
1710
|
+
focusedCellIndex,
|
|
1711
|
+
props
|
|
1712
|
+
) !== this._lastFocusedCellKey
|
|
1713
|
+
) {
|
|
1714
|
+
return []
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
let first = focusedCellIndex
|
|
1718
|
+
let heightOfCellsBeforeFocused = 0
|
|
1719
|
+
for (
|
|
1720
|
+
let i = first - 1;
|
|
1721
|
+
i >= 0 && heightOfCellsBeforeFocused < this._scrollMetrics.visibleLength;
|
|
1722
|
+
i--
|
|
1723
|
+
) {
|
|
1724
|
+
first--
|
|
1725
|
+
heightOfCellsBeforeFocused += this.__getFrameMetricsApprox(i, props).length
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
let last = focusedCellIndex
|
|
1729
|
+
let heightOfCellsAfterFocused = 0
|
|
1730
|
+
for (
|
|
1731
|
+
let i = last + 1;
|
|
1732
|
+
i < itemCount && heightOfCellsAfterFocused < this._scrollMetrics.visibleLength;
|
|
1733
|
+
i++
|
|
1734
|
+
) {
|
|
1735
|
+
last++
|
|
1736
|
+
heightOfCellsAfterFocused += this.__getFrameMetricsApprox(i, props).length
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return [{ first, last }]
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
_updateViewableItems(
|
|
1743
|
+
props: FrameMetricProps,
|
|
1744
|
+
cellsAroundViewport: { first: number; last: number }
|
|
1745
|
+
) {
|
|
1746
|
+
this._viewabilityTuples.forEach((tuple) => {
|
|
1747
|
+
tuple.viewabilityHelper.onUpdate(
|
|
1748
|
+
props,
|
|
1749
|
+
this._scrollMetrics.offset,
|
|
1750
|
+
this._scrollMetrics.visibleLength,
|
|
1751
|
+
this._getFrameMetrics,
|
|
1752
|
+
this._createViewToken,
|
|
1753
|
+
tuple.onViewableItemsChanged,
|
|
1754
|
+
cellsAroundViewport
|
|
1755
|
+
)
|
|
1756
|
+
})
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
const styles = StyleSheet.create({
|
|
1761
|
+
verticallyInverted: {
|
|
1762
|
+
transform: 'scaleY(-1)',
|
|
1763
|
+
},
|
|
1764
|
+
horizontallyInverted: {
|
|
1765
|
+
transform: 'scaleX(-1)',
|
|
1766
|
+
},
|
|
1767
|
+
debug: {
|
|
1768
|
+
flex: 1,
|
|
1769
|
+
},
|
|
1770
|
+
debugOverlayBase: {
|
|
1771
|
+
position: 'absolute',
|
|
1772
|
+
top: 0,
|
|
1773
|
+
right: 0,
|
|
1774
|
+
},
|
|
1775
|
+
debugOverlay: {
|
|
1776
|
+
bottom: 0,
|
|
1777
|
+
width: 20,
|
|
1778
|
+
borderColor: 'blue',
|
|
1779
|
+
borderWidth: 1,
|
|
1780
|
+
},
|
|
1781
|
+
debugOverlayFrame: {
|
|
1782
|
+
left: 0,
|
|
1783
|
+
backgroundColor: 'orange',
|
|
1784
|
+
},
|
|
1785
|
+
debugOverlayFrameLast: {
|
|
1786
|
+
left: 0,
|
|
1787
|
+
borderColor: 'green',
|
|
1788
|
+
borderWidth: 2,
|
|
1789
|
+
},
|
|
1790
|
+
debugOverlayFrameVis: {
|
|
1791
|
+
left: 0,
|
|
1792
|
+
borderColor: 'red',
|
|
1793
|
+
borderWidth: 2,
|
|
1794
|
+
},
|
|
1795
|
+
})
|
|
1796
|
+
|
|
1797
|
+
export default VirtualizedList
|