@shopify/flash-list 1.8.0 → 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -26
- package/dist/FlashListProps.d.ts +65 -2
- package/dist/FlashListProps.d.ts.map +1 -1
- package/dist/__tests__/AverageWindow.test.js +35 -0
- package/dist/__tests__/AverageWindow.test.js.map +1 -1
- package/dist/enableNewCore.d.ts +3 -0
- package/dist/enableNewCore.d.ts.map +1 -0
- package/dist/enableNewCore.js +25 -0
- package/dist/enableNewCore.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -8
- package/dist/index.js.map +1 -1
- package/dist/recyclerview/RecycleKeyManager.d.ts +82 -0
- package/dist/recyclerview/RecycleKeyManager.d.ts.map +1 -0
- package/dist/recyclerview/RecycleKeyManager.js +135 -0
- package/dist/recyclerview/RecycleKeyManager.js.map +1 -0
- package/dist/recyclerview/RecyclerView.d.ts +12 -0
- package/dist/recyclerview/RecyclerView.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerView.js +283 -0
- package/dist/recyclerview/RecyclerView.js.map +1 -0
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts +12 -0
- package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewContextProvider.js +11 -0
- package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -0
- package/dist/recyclerview/RecyclerViewManager.d.ts +52 -0
- package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewManager.js +323 -0
- package/dist/recyclerview/RecyclerViewManager.js.map +1 -0
- package/dist/recyclerview/RecyclerViewProps.d.ts +9 -0
- package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -0
- package/dist/recyclerview/RecyclerViewProps.js +3 -0
- package/dist/recyclerview/RecyclerViewProps.js.map +1 -0
- package/dist/recyclerview/ViewHolder.d.ts +45 -0
- package/dist/recyclerview/ViewHolder.d.ts.map +1 -0
- package/dist/recyclerview/ViewHolder.js +96 -0
- package/dist/recyclerview/ViewHolder.js.map +1 -0
- package/dist/recyclerview/ViewHolderCollection.d.ts +57 -0
- package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -0
- package/dist/recyclerview/ViewHolderCollection.js +75 -0
- package/dist/recyclerview/ViewHolderCollection.js.map +1 -0
- package/dist/recyclerview/components/CompatScroller.d.ts +7 -0
- package/dist/recyclerview/components/CompatScroller.d.ts.map +1 -0
- package/dist/recyclerview/components/CompatScroller.js +8 -0
- package/dist/recyclerview/components/CompatScroller.js.map +1 -0
- package/dist/recyclerview/components/CompatView.d.ts +7 -0
- package/dist/recyclerview/components/CompatView.d.ts.map +1 -0
- package/dist/recyclerview/components/CompatView.js +8 -0
- package/dist/recyclerview/components/CompatView.js.map +1 -0
- package/dist/recyclerview/components/ScrollAnchor.d.ts +28 -0
- package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -0
- package/dist/recyclerview/components/ScrollAnchor.js +35 -0
- package/dist/recyclerview/components/ScrollAnchor.js.map +1 -0
- package/dist/recyclerview/components/StickyHeaders.d.ts +38 -0
- package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -0
- package/dist/recyclerview/components/StickyHeaders.js +119 -0
- package/dist/recyclerview/components/StickyHeaders.js.map +1 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +51 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js +122 -0
- package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +59 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js +138 -0
- package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -0
- package/dist/recyclerview/hooks/useBoundDetection.d.ts +19 -0
- package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useBoundDetection.js +103 -0
- package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -0
- package/dist/recyclerview/hooks/useLayoutState.d.ts +12 -0
- package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useLayoutState.js +43 -0
- package/dist/recyclerview/hooks/useLayoutState.js.map +1 -0
- package/dist/recyclerview/hooks/useOnLoad.d.ts +25 -0
- package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useOnLoad.js +73 -0
- package/dist/recyclerview/hooks/useOnLoad.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +72 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.js +370 -0
- package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +6 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.js +27 -0
- package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -0
- package/dist/recyclerview/hooks/useRecyclingState.d.ts +16 -0
- package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useRecyclingState.js +54 -0
- package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -0
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts +27 -0
- package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useSecondaryProps.js +93 -0
- package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts +11 -0
- package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -0
- package/dist/recyclerview/hooks/useUnmountFlag.js +28 -0
- package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +65 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.js +204 -0
- package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts +281 -0
- package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/LayoutManager.js +250 -0
- package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +52 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js +191 -0
- package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +73 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +274 -0
- package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts +12 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts.map +1 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.js +18 -0
- package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -0
- package/dist/recyclerview/utils/componentUtils.d.ts +19 -0
- package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -0
- package/dist/recyclerview/utils/componentUtils.js +32 -0
- package/dist/recyclerview/utils/componentUtils.js.map +1 -0
- package/dist/recyclerview/utils/findVisibleIndex.d.ts +24 -0
- package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -0
- package/dist/recyclerview/utils/findVisibleIndex.js +82 -0
- package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -0
- package/dist/recyclerview/utils/measureLayout.d.ts +56 -0
- package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -0
- package/dist/recyclerview/utils/measureLayout.js +77 -0
- package/dist/recyclerview/utils/measureLayout.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/AverageWindow.d.ts +13 -0
- package/dist/utils/AverageWindow.d.ts.map +1 -1
- package/dist/utils/AverageWindow.js +30 -1
- package/dist/utils/AverageWindow.js.map +1 -1
- package/package.json +1 -1
- package/src/FlashListProps.ts +73 -2
- package/src/__tests__/AverageWindow.test.ts +49 -1
- package/src/enableNewCore.ts +22 -0
- package/src/index.ts +21 -0
- package/src/recyclerview/RecycleKeyManager.ts +185 -0
- package/src/recyclerview/RecyclerView.tsx +500 -0
- package/src/recyclerview/RecyclerViewContextProvider.ts +19 -0
- package/src/recyclerview/RecyclerViewManager.ts +379 -0
- package/src/recyclerview/RecyclerViewProps.ts +10 -0
- package/src/recyclerview/ViewHolder.tsx +173 -0
- package/src/recyclerview/ViewHolderCollection.tsx +164 -0
- package/src/recyclerview/components/CompatScroller.ts +9 -0
- package/src/recyclerview/components/CompatView.ts +9 -0
- package/src/recyclerview/components/ScrollAnchor.tsx +53 -0
- package/src/recyclerview/components/StickyHeaders.tsx +210 -0
- package/src/recyclerview/helpers/ConsecutiveNumbers.ts +120 -0
- package/src/recyclerview/helpers/EngagedIndicesTracker.ts +191 -0
- package/src/recyclerview/hooks/useBoundDetection.ts +127 -0
- package/src/recyclerview/hooks/useLayoutState.ts +46 -0
- package/src/recyclerview/hooks/useOnLoad.ts +78 -0
- package/src/recyclerview/hooks/useRecyclerViewController.tsx +487 -0
- package/src/recyclerview/hooks/useRecyclerViewManager.ts +30 -0
- package/src/recyclerview/hooks/useRecyclingState.ts +63 -0
- package/src/recyclerview/hooks/useSecondaryProps.tsx +119 -0
- package/src/recyclerview/hooks/useUnmountFlag.ts +26 -0
- package/src/recyclerview/layout-managers/GridLayoutManager.ts +215 -0
- package/src/recyclerview/layout-managers/LayoutManager.ts +493 -0
- package/src/recyclerview/layout-managers/LinearLayoutManager.ts +167 -0
- package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +302 -0
- package/src/recyclerview/utils/adjustOffsetForRTL.ts +17 -0
- package/src/recyclerview/utils/componentUtils.ts +28 -0
- package/src/recyclerview/utils/findVisibleIndex.ts +94 -0
- package/src/recyclerview/utils/measureLayout.ts +89 -0
- package/src/utils/AverageWindow.ts +33 -0
- package/src/viewability/ViewToken.ts +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { ConsecutiveNumbers } from "./helpers/ConsecutiveNumbers";
|
|
2
|
+
import { RVGridLayoutManagerImpl } from "./layout-managers/GridLayoutManager";
|
|
3
|
+
import {
|
|
4
|
+
RVDimension,
|
|
5
|
+
RVLayoutInfo,
|
|
6
|
+
RVLayoutManager,
|
|
7
|
+
} from "./layout-managers/LayoutManager";
|
|
8
|
+
import { RVLinearLayoutManagerImpl } from "./layout-managers/LinearLayoutManager";
|
|
9
|
+
import { RVMasonryLayoutManagerImpl } from "./layout-managers/MasonryLayoutManager";
|
|
10
|
+
import { RecycleKeyManagerImpl, RecycleKeyManager } from "./RecycleKeyManager";
|
|
11
|
+
import { RecyclerViewProps } from "./RecyclerViewProps";
|
|
12
|
+
import {
|
|
13
|
+
RVEngagedIndicesTracker,
|
|
14
|
+
RVEngagedIndicesTrackerImpl,
|
|
15
|
+
Velocity,
|
|
16
|
+
} from "./helpers/EngagedIndicesTracker";
|
|
17
|
+
import ViewabilityManager from "../viewability/ViewabilityManager";
|
|
18
|
+
|
|
19
|
+
// Abstracts layout manager, key manager and viewability manager and generates render stack (progressively on load)
|
|
20
|
+
export class RecyclerViewManager<T> {
|
|
21
|
+
private initialDrawBatchSize = 1;
|
|
22
|
+
private engagedIndicesTracker: RVEngagedIndicesTracker;
|
|
23
|
+
private recycleKeyManager: RecycleKeyManager;
|
|
24
|
+
private layoutManager?: RVLayoutManager;
|
|
25
|
+
// Map of index to key
|
|
26
|
+
private renderStack: Map<number, string> = new Map();
|
|
27
|
+
private isFirstLayoutComplete = false;
|
|
28
|
+
private hasRenderedProgressively = false;
|
|
29
|
+
private props: RecyclerViewProps<T>;
|
|
30
|
+
private itemViewabilityManager: ViewabilityManager<T>;
|
|
31
|
+
|
|
32
|
+
public disableRecycling = false;
|
|
33
|
+
public firstItemOffset = 0;
|
|
34
|
+
|
|
35
|
+
constructor(props: RecyclerViewProps<T>) {
|
|
36
|
+
this.props = props;
|
|
37
|
+
this.engagedIndicesTracker = new RVEngagedIndicesTrackerImpl();
|
|
38
|
+
this.recycleKeyManager = new RecycleKeyManagerImpl();
|
|
39
|
+
this.itemViewabilityManager = new ViewabilityManager<T>(this as any);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// updates render stack based on the engaged indices which are sorted. Recycles unused keys.
|
|
43
|
+
// TODO: Call getKey anyway if stableIds are present
|
|
44
|
+
private updateRenderStack = (engagedIndices: ConsecutiveNumbers): void => {
|
|
45
|
+
//console.log("updateRenderStack", engagedIndices);
|
|
46
|
+
const newRenderStack = new Map<number, string>();
|
|
47
|
+
for (const [index, key] of this.renderStack) {
|
|
48
|
+
if (!engagedIndices.includes(index)) {
|
|
49
|
+
this.recycleKeyManager.recycleKey(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (this.disableRecycling) {
|
|
53
|
+
this.recycleKeyManager.clearPool();
|
|
54
|
+
}
|
|
55
|
+
for (const index of engagedIndices) {
|
|
56
|
+
const newKey = this.recycleKeyManager.getKey(
|
|
57
|
+
this.getItemType(index),
|
|
58
|
+
this.getStableId(index),
|
|
59
|
+
this.renderStack.get(index)
|
|
60
|
+
);
|
|
61
|
+
newRenderStack.set(index, newKey);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// DANGER
|
|
65
|
+
for (const [index, key] of this.renderStack) {
|
|
66
|
+
if (
|
|
67
|
+
this.recycleKeyManager.hasKeyInPool(key) &&
|
|
68
|
+
!newRenderStack.has(index) &&
|
|
69
|
+
index < (this.props.data?.length ?? 0)
|
|
70
|
+
) {
|
|
71
|
+
newRenderStack.set(index, key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.renderStack = newRenderStack;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
updateProps(props: RecyclerViewProps<T>) {
|
|
79
|
+
this.props = props;
|
|
80
|
+
this.engagedIndicesTracker.drawDistance =
|
|
81
|
+
props.drawDistance ?? this.engagedIndicesTracker.drawDistance;
|
|
82
|
+
if (this.props.drawDistance === 0) {
|
|
83
|
+
this.initialDrawBatchSize = 1;
|
|
84
|
+
} else {
|
|
85
|
+
this.initialDrawBatchSize = (props.numColumns ?? 1) * 2;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Updates the scroll offset and returns the engaged indices if any
|
|
91
|
+
* @param offset
|
|
92
|
+
* @param velocity
|
|
93
|
+
*/
|
|
94
|
+
updateScrollOffset(
|
|
95
|
+
offset: number,
|
|
96
|
+
velocity?: Velocity
|
|
97
|
+
): ConsecutiveNumbers | undefined {
|
|
98
|
+
if (this.layoutManager) {
|
|
99
|
+
const engagedIndices = this.engagedIndicesTracker.updateScrollOffset(
|
|
100
|
+
offset - this.firstItemOffset,
|
|
101
|
+
velocity,
|
|
102
|
+
this.layoutManager
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (engagedIndices) {
|
|
106
|
+
this.updateRenderStack(engagedIndices);
|
|
107
|
+
return engagedIndices;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getIsFirstLayoutComplete() {
|
|
114
|
+
return this.isFirstLayoutComplete;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getLayout(index: number) {
|
|
118
|
+
if (!this.layoutManager) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"LayoutManager is not initialized, layout info is unavailable"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return this.layoutManager.getLayout(index);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Doesn't include header / foot etc
|
|
127
|
+
getChildContainerDimensions() {
|
|
128
|
+
if (!this.layoutManager) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"LayoutManager is not initialized, child container layout is unavailable"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return this.layoutManager.getLayoutSize();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getRenderStack() {
|
|
137
|
+
return this.renderStack;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getWindowSize() {
|
|
141
|
+
if (!this.layoutManager) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"LayoutManager is not initialized, window size is unavailable"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return this.layoutManager.getWindowsSize();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Includes first item offset correction
|
|
150
|
+
getLastScrollOffset() {
|
|
151
|
+
return this.engagedIndicesTracker.scrollOffset;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Doesn't include first item offset correction
|
|
155
|
+
getAbsoluteLastScrollOffset() {
|
|
156
|
+
return this.engagedIndicesTracker.scrollOffset + this.firstItemOffset;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
updateLayoutParams(windowSize: RVDimension, firstItemOffset: number) {
|
|
160
|
+
this.firstItemOffset = firstItemOffset;
|
|
161
|
+
const LayoutManagerClass = this.getLayoutManagerClass();
|
|
162
|
+
if (
|
|
163
|
+
this.layoutManager &&
|
|
164
|
+
Boolean(this.layoutManager?.isHorizontal()) !==
|
|
165
|
+
Boolean(this.props.horizontal)
|
|
166
|
+
) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
"Horizontal prop cannot be toggled, you can use a key on FlashList to recreate it."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!(this.layoutManager instanceof LayoutManagerClass)) {
|
|
172
|
+
//console.log("-----> new LayoutManagerClass");
|
|
173
|
+
|
|
174
|
+
this.layoutManager = new LayoutManagerClass(
|
|
175
|
+
{
|
|
176
|
+
windowSize,
|
|
177
|
+
maxColumns: this.props.numColumns ?? 1,
|
|
178
|
+
horizontal: !!this.props.horizontal,
|
|
179
|
+
optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
|
|
180
|
+
overrideItemLayout: (index, layout) => {
|
|
181
|
+
this.props?.overrideItemLayout?.(
|
|
182
|
+
layout,
|
|
183
|
+
this.props.data![index],
|
|
184
|
+
index,
|
|
185
|
+
this.props.numColumns ?? 1,
|
|
186
|
+
this.props.extraData
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
this.layoutManager
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
this.layoutManager.updateLayoutParams({
|
|
194
|
+
windowSize,
|
|
195
|
+
maxColumns: this.props.numColumns ?? 1,
|
|
196
|
+
horizontal: !!this.props.horizontal,
|
|
197
|
+
optimizeItemArrangement: this.props.optimizeItemArrangement ?? true,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
hasLayout() {
|
|
203
|
+
return this.layoutManager !== undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getVisibleIndices() {
|
|
207
|
+
if (!this.layoutManager) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"LayoutManager is not initialized, visible indices are not unavailable"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return this.engagedIndicesTracker.computeVisibleIndices(this.layoutManager);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getEngagedIndices() {
|
|
216
|
+
return this.engagedIndicesTracker.getEngagedIndices();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
modifyChildrenLayout(
|
|
220
|
+
layoutInfo: RVLayoutInfo[],
|
|
221
|
+
dataLength: number
|
|
222
|
+
): boolean {
|
|
223
|
+
this.layoutManager?.modifyLayout(layoutInfo, dataLength);
|
|
224
|
+
if (dataLength === 0) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (this.layoutManager?.requiresRepaint) {
|
|
228
|
+
// console.log("requiresRepaint triggered");
|
|
229
|
+
this.layoutManager.requiresRepaint = false;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (this.hasRenderedProgressively) {
|
|
233
|
+
return this.recomputeEngagedIndices() !== undefined; //TODO: Move to an effect as this can block paint for more than necessary
|
|
234
|
+
} else {
|
|
235
|
+
this.renderProgressively();
|
|
236
|
+
}
|
|
237
|
+
return !this.hasRenderedProgressively;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
computeItemViewability() {
|
|
241
|
+
// Using higher buffer for masonry to avoid missing items
|
|
242
|
+
this.itemViewabilityManager.shouldListenToVisibleIndices &&
|
|
243
|
+
this.itemViewabilityManager.updateViewableItems(
|
|
244
|
+
this.props.masonry
|
|
245
|
+
? this.engagedIndicesTracker.getEngagedIndices().toArray()
|
|
246
|
+
: this.getVisibleIndices().toArray()
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
recordInteraction() {
|
|
251
|
+
this.itemViewabilityManager.recordInteraction();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
recomputeViewableItems() {
|
|
255
|
+
this.itemViewabilityManager.recomputeViewableItems();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
processDataUpdate() {
|
|
259
|
+
if (this.hasLayout()) {
|
|
260
|
+
this.modifyChildrenLayout([], this.props.data?.length ?? 0);
|
|
261
|
+
if (!this.recomputeEngagedIndices()) {
|
|
262
|
+
// recomputeEngagedIndices will update the render stack if there are any changes in the engaged indices.
|
|
263
|
+
// It's important to update render stack so that elements are assgined right keys incase items were deleted.
|
|
264
|
+
this.updateRenderStack(this.engagedIndicesTracker.getEngagedIndices());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
recomputeEngagedIndices(): ConsecutiveNumbers | undefined {
|
|
270
|
+
return this.updateScrollOffset(this.getAbsoluteLastScrollOffset());
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
dispose() {
|
|
274
|
+
this.itemViewabilityManager.dispose();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getInitialScrollIndex() {
|
|
278
|
+
return (
|
|
279
|
+
this.props.initialScrollIndex ??
|
|
280
|
+
(this.props.maintainVisibleContentPosition?.startRenderingFromBottom
|
|
281
|
+
? this.getDataLength() - 1
|
|
282
|
+
: undefined)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
getDataLength() {
|
|
287
|
+
return this.props.data?.length ?? 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private getLayoutManagerClass() {
|
|
291
|
+
// throw errors for incompatible props
|
|
292
|
+
if (this.props.masonry && this.props.horizontal) {
|
|
293
|
+
throw new Error("Masonry and horizontal props are incompatible");
|
|
294
|
+
}
|
|
295
|
+
if ((this.props.numColumns ?? 1) > 1 && this.props.horizontal) {
|
|
296
|
+
throw new Error("numColumns and horizontal props are incompatible");
|
|
297
|
+
}
|
|
298
|
+
return this.props.masonry
|
|
299
|
+
? RVMasonryLayoutManagerImpl
|
|
300
|
+
: (this.props.numColumns ?? 1) > 1 && !this.props.horizontal
|
|
301
|
+
? RVGridLayoutManagerImpl
|
|
302
|
+
: RVLinearLayoutManagerImpl;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private applyInitialScrollAdjustment() {
|
|
306
|
+
if (!this.layoutManager || this.getDataLength() === 0) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const initialScrollIndex = this.getInitialScrollIndex();
|
|
311
|
+
const initialItemLayout = this.layoutManager?.getLayout(
|
|
312
|
+
initialScrollIndex ?? 0
|
|
313
|
+
);
|
|
314
|
+
const initialItemOffset = this.props.horizontal
|
|
315
|
+
? initialItemLayout?.x
|
|
316
|
+
: initialItemLayout?.y;
|
|
317
|
+
|
|
318
|
+
if (initialScrollIndex !== undefined) {
|
|
319
|
+
// console.log(
|
|
320
|
+
// "initialItemOffset",
|
|
321
|
+
// initialScrollIndex,
|
|
322
|
+
// initialItemOffset,
|
|
323
|
+
// this.firstItemOffset
|
|
324
|
+
// );
|
|
325
|
+
this.layoutManager.recomputeLayouts(0, initialScrollIndex);
|
|
326
|
+
this.engagedIndicesTracker.scrollOffset =
|
|
327
|
+
initialItemOffset ?? 0 + this.firstItemOffset;
|
|
328
|
+
} else {
|
|
329
|
+
//console.log("initialItemOffset", initialItemOffset, this.firstItemOffset);
|
|
330
|
+
this.engagedIndicesTracker.scrollOffset =
|
|
331
|
+
(initialItemOffset ?? 0) - this.firstItemOffset;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private renderProgressively() {
|
|
336
|
+
const layoutManager = this.layoutManager;
|
|
337
|
+
if (layoutManager) {
|
|
338
|
+
this.applyInitialScrollAdjustment();
|
|
339
|
+
const visibleIndices = this.getVisibleIndices();
|
|
340
|
+
//console.log("---------> visibleIndices", visibleIndices);
|
|
341
|
+
this.hasRenderedProgressively = visibleIndices.every(
|
|
342
|
+
(index) =>
|
|
343
|
+
layoutManager.getLayout(index).isHeightMeasured &&
|
|
344
|
+
layoutManager.getLayout(index).isWidthMeasured
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if (this.hasRenderedProgressively) {
|
|
348
|
+
this.isFirstLayoutComplete = true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// If everything is measured then render stack will be in sync. The buffer items will get rendered in the next update
|
|
352
|
+
// triggered by the useOnLoad hook.
|
|
353
|
+
!this.hasRenderedProgressively &&
|
|
354
|
+
this.updateRenderStack(
|
|
355
|
+
// pick first n indices from visible ones and n is size of renderStack
|
|
356
|
+
visibleIndices.slice(
|
|
357
|
+
0,
|
|
358
|
+
Math.min(
|
|
359
|
+
visibleIndices.length,
|
|
360
|
+
this.renderStack.size + this.initialDrawBatchSize
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private getItemType(index: number): string {
|
|
368
|
+
return (
|
|
369
|
+
this.props.getItemType?.(this.props.data![index], index) ?? "default"
|
|
370
|
+
).toString();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private getStableId(index: number): string {
|
|
374
|
+
return (
|
|
375
|
+
this.props.keyExtractor?.(this.props.data![index], index) ??
|
|
376
|
+
index.toString()
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FlashListProps } from "../FlashListProps";
|
|
2
|
+
import { ScrollViewProps } from "react-native";
|
|
3
|
+
|
|
4
|
+
export interface RecyclerViewProps<TItem>
|
|
5
|
+
extends Omit<FlashListProps<TItem>, "contentContainerStyle"> {
|
|
6
|
+
/**
|
|
7
|
+
* Style for the RecyclerView's parent container.
|
|
8
|
+
*/
|
|
9
|
+
contentContainerStyle?: ScrollViewProps["contentContainerStyle"];
|
|
10
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewHolder is a core component in FlashList that manages individual item rendering and layout.
|
|
3
|
+
* It handles the rendering of list items, separators, and manages layout updates for each item.
|
|
4
|
+
* The component is memoized to prevent unnecessary re-renders and includes layout comparison logic.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LayoutChangeEvent } from "react-native";
|
|
8
|
+
import React, {
|
|
9
|
+
RefObject,
|
|
10
|
+
useCallback,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
useRef,
|
|
14
|
+
} from "react";
|
|
15
|
+
|
|
16
|
+
import { FlashListProps, RenderTarget } from "../FlashListProps";
|
|
17
|
+
|
|
18
|
+
import { RVDimension, RVLayout } from "./layout-managers/LayoutManager";
|
|
19
|
+
import { CompatView } from "./components/CompatView";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Props interface for the ViewHolder component
|
|
23
|
+
* @template TItem - The type of item being rendered in the list
|
|
24
|
+
*/
|
|
25
|
+
export interface ViewHolderProps<TItem> {
|
|
26
|
+
/** Index of the item in the data array */
|
|
27
|
+
index: number;
|
|
28
|
+
/** Layout information for positioning and sizing the item */
|
|
29
|
+
layout: RVLayout;
|
|
30
|
+
/** Map to store refs for each ViewHolder instance, keyed by index */
|
|
31
|
+
refHolder: Map<number, RefObject<CompatView | null>>;
|
|
32
|
+
/** Additional data passed to renderItem that can trigger re-renders */
|
|
33
|
+
extraData: any;
|
|
34
|
+
/** Specifies the rendering target (e.g., "Cell", "StickyHeader") */
|
|
35
|
+
target: RenderTarget;
|
|
36
|
+
/** The actual item data to be rendered */
|
|
37
|
+
item: TItem;
|
|
38
|
+
/** The next item in the list, used for rendering separators */
|
|
39
|
+
trailingItem: TItem | undefined;
|
|
40
|
+
/** Function to render the item content */
|
|
41
|
+
renderItem: FlashListProps<TItem>["renderItem"];
|
|
42
|
+
/** Optional custom component to wrap each item */
|
|
43
|
+
CellRendererComponent?: FlashListProps<TItem>["CellRendererComponent"];
|
|
44
|
+
/** Optional component to render between items */
|
|
45
|
+
ItemSeparatorComponent?: FlashListProps<TItem>["ItemSeparatorComponent"];
|
|
46
|
+
/** Whether the list is horizontal or vertical */
|
|
47
|
+
horizontal?: FlashListProps<TItem>["horizontal"];
|
|
48
|
+
/** Callback when the item's size changes */
|
|
49
|
+
onSizeChanged?: (index: number, size: RVDimension) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Internal ViewHolder component that handles the actual rendering of list items
|
|
54
|
+
* @template TItem - The type of item being rendered in the list
|
|
55
|
+
*/
|
|
56
|
+
const ViewHolderInternal = <TItem,>(props: ViewHolderProps<TItem>) => {
|
|
57
|
+
// create ref for View
|
|
58
|
+
const viewRef = useRef<CompatView>(null);
|
|
59
|
+
const {
|
|
60
|
+
index,
|
|
61
|
+
refHolder,
|
|
62
|
+
layout,
|
|
63
|
+
onSizeChanged,
|
|
64
|
+
renderItem,
|
|
65
|
+
extraData,
|
|
66
|
+
item,
|
|
67
|
+
target,
|
|
68
|
+
CellRendererComponent,
|
|
69
|
+
ItemSeparatorComponent,
|
|
70
|
+
trailingItem,
|
|
71
|
+
horizontal,
|
|
72
|
+
} = props;
|
|
73
|
+
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
refHolder.set(index, viewRef);
|
|
76
|
+
return () => {
|
|
77
|
+
if (refHolder.get(index) === viewRef) {
|
|
78
|
+
refHolder.delete(index);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}, [index, refHolder]);
|
|
82
|
+
|
|
83
|
+
const onLayout = useCallback(
|
|
84
|
+
(event: LayoutChangeEvent) => {
|
|
85
|
+
onSizeChanged?.(index, event.nativeEvent.layout);
|
|
86
|
+
},
|
|
87
|
+
[index, onSizeChanged]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const separator = useMemo(() => {
|
|
91
|
+
return ItemSeparatorComponent ? (
|
|
92
|
+
<ItemSeparatorComponent leadingItem={item} trailingItem={trailingItem} />
|
|
93
|
+
) : null;
|
|
94
|
+
}, [ItemSeparatorComponent, item, trailingItem]);
|
|
95
|
+
|
|
96
|
+
//console.log("ViewHolder re-render", index);
|
|
97
|
+
|
|
98
|
+
const children = useMemo(() => {
|
|
99
|
+
return renderItem?.({ item, index, extraData, target }) ?? null;
|
|
100
|
+
}, [item, index, extraData, target, renderItem]);
|
|
101
|
+
|
|
102
|
+
const style = {
|
|
103
|
+
flexDirection: horizontal ? "row" : "column",
|
|
104
|
+
position: target === "StickyHeader" ? "relative" : "absolute",
|
|
105
|
+
width: layout.enforcedWidth ? layout.width : undefined,
|
|
106
|
+
height: layout.enforcedHeight ? layout.height : undefined,
|
|
107
|
+
minHeight: layout.minHeight,
|
|
108
|
+
minWidth: layout.minWidth,
|
|
109
|
+
maxHeight: layout.maxHeight,
|
|
110
|
+
maxWidth: layout.maxWidth,
|
|
111
|
+
left: layout.x,
|
|
112
|
+
top: layout.y,
|
|
113
|
+
zIndex: 0,
|
|
114
|
+
} as const;
|
|
115
|
+
|
|
116
|
+
//TODO: Fix this type issue
|
|
117
|
+
const CompatContainer = (CellRendererComponent ??
|
|
118
|
+
CompatView) as unknown as any;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<CompatContainer ref={viewRef} onLayout={onLayout} style={style}>
|
|
122
|
+
{children}
|
|
123
|
+
{separator}
|
|
124
|
+
</CompatContainer>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Memoized ViewHolder component that prevents unnecessary re-renders by comparing props
|
|
130
|
+
* @template TItem - The type of item being rendered in the list
|
|
131
|
+
*/
|
|
132
|
+
export const ViewHolder = React.memo(
|
|
133
|
+
ViewHolderInternal,
|
|
134
|
+
(prevProps, nextProps) => {
|
|
135
|
+
// compare all props and spread layout
|
|
136
|
+
return (
|
|
137
|
+
prevProps.index === nextProps.index &&
|
|
138
|
+
areLayoutsEqual(prevProps.layout, nextProps.layout) &&
|
|
139
|
+
prevProps.refHolder === nextProps.refHolder &&
|
|
140
|
+
prevProps.onSizeChanged === nextProps.onSizeChanged &&
|
|
141
|
+
prevProps.extraData === nextProps.extraData &&
|
|
142
|
+
prevProps.target === nextProps.target &&
|
|
143
|
+
prevProps.item === nextProps.item &&
|
|
144
|
+
prevProps.renderItem === nextProps.renderItem &&
|
|
145
|
+
prevProps.CellRendererComponent === nextProps.CellRendererComponent &&
|
|
146
|
+
prevProps.ItemSeparatorComponent === nextProps.ItemSeparatorComponent &&
|
|
147
|
+
prevProps.trailingItem === nextProps.trailingItem &&
|
|
148
|
+
prevProps.horizontal === nextProps.horizontal
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Compares two RVLayout objects to determine if they are equal
|
|
155
|
+
* Used in the memo comparison function to prevent unnecessary re-renders
|
|
156
|
+
* @param prevLayout - Previous layout object
|
|
157
|
+
* @param nextLayout - Next layout object
|
|
158
|
+
* @returns boolean indicating if layouts are equal
|
|
159
|
+
*/
|
|
160
|
+
function areLayoutsEqual(prevLayout: RVLayout, nextLayout: RVLayout): boolean {
|
|
161
|
+
return (
|
|
162
|
+
prevLayout.x === nextLayout.x &&
|
|
163
|
+
prevLayout.y === nextLayout.y &&
|
|
164
|
+
prevLayout.width === nextLayout.width &&
|
|
165
|
+
prevLayout.height === nextLayout.height &&
|
|
166
|
+
prevLayout.enforcedWidth === nextLayout.enforcedWidth &&
|
|
167
|
+
prevLayout.enforcedHeight === nextLayout.enforcedHeight &&
|
|
168
|
+
prevLayout.minWidth === nextLayout.minWidth &&
|
|
169
|
+
prevLayout.minHeight === nextLayout.minHeight &&
|
|
170
|
+
prevLayout.maxWidth === nextLayout.maxWidth &&
|
|
171
|
+
prevLayout.maxHeight === nextLayout.maxHeight
|
|
172
|
+
);
|
|
173
|
+
}
|