@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,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecyclerView is a high-performance list component that efficiently renders and recycles list items.
|
|
3
|
+
* It's designed to handle large lists with optimal memory usage and smooth scrolling.
|
|
4
|
+
*/
|
|
5
|
+
import React, {
|
|
6
|
+
RefObject,
|
|
7
|
+
useCallback,
|
|
8
|
+
useLayoutEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useState,
|
|
13
|
+
useId,
|
|
14
|
+
} from "react";
|
|
15
|
+
import {
|
|
16
|
+
Animated,
|
|
17
|
+
I18nManager,
|
|
18
|
+
NativeScrollEvent,
|
|
19
|
+
NativeSyntheticEvent,
|
|
20
|
+
} from "react-native";
|
|
21
|
+
|
|
22
|
+
import { RVDimension } from "./layout-managers/LayoutManager";
|
|
23
|
+
import {
|
|
24
|
+
areDimensionsNotEqual,
|
|
25
|
+
measureLayout,
|
|
26
|
+
measureLayoutRelative,
|
|
27
|
+
} from "./utils/measureLayout";
|
|
28
|
+
import {
|
|
29
|
+
RecyclerViewContext,
|
|
30
|
+
RecyclerViewContextProvider,
|
|
31
|
+
useRecyclerViewContext,
|
|
32
|
+
} from "./RecyclerViewContextProvider";
|
|
33
|
+
import { useLayoutState } from "./hooks/useLayoutState";
|
|
34
|
+
import { useRecyclerViewManager } from "./hooks/useRecyclerViewManager";
|
|
35
|
+
import { RecyclerViewProps } from "./RecyclerViewProps";
|
|
36
|
+
import { useOnListLoad } from "./hooks/useOnLoad";
|
|
37
|
+
import {
|
|
38
|
+
ViewHolderCollection,
|
|
39
|
+
ViewHolderCollectionRef,
|
|
40
|
+
} from "./ViewHolderCollection";
|
|
41
|
+
import { CompatView } from "./components/CompatView";
|
|
42
|
+
import { CompatScroller } from "./components/CompatScroller";
|
|
43
|
+
import { useBoundDetection } from "./hooks/useBoundDetection";
|
|
44
|
+
import { adjustOffsetForRTL } from "./utils/adjustOffsetForRTL";
|
|
45
|
+
import { useSecondaryProps } from "./hooks/useSecondaryProps";
|
|
46
|
+
import { StickyHeaders, StickyHeaderRef } from "./components/StickyHeaders";
|
|
47
|
+
import { ScrollAnchor, ScrollAnchorRef } from "./components/ScrollAnchor";
|
|
48
|
+
import { useRecyclerViewController } from "./hooks/useRecyclerViewController";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Main RecyclerView component that handles list rendering, scrolling, and item recycling.
|
|
52
|
+
* @template T - The type of items in the list
|
|
53
|
+
*/
|
|
54
|
+
const RecyclerViewComponent = <T,>(
|
|
55
|
+
props: RecyclerViewProps<T>,
|
|
56
|
+
ref: React.Ref<any>
|
|
57
|
+
) => {
|
|
58
|
+
// Destructure props and initialize refs
|
|
59
|
+
const {
|
|
60
|
+
horizontal,
|
|
61
|
+
renderItem,
|
|
62
|
+
data,
|
|
63
|
+
extraData,
|
|
64
|
+
onLoad,
|
|
65
|
+
CellRendererComponent,
|
|
66
|
+
overrideProps,
|
|
67
|
+
refreshing,
|
|
68
|
+
onRefresh,
|
|
69
|
+
progressViewOffset,
|
|
70
|
+
ListEmptyComponent,
|
|
71
|
+
ListHeaderComponent,
|
|
72
|
+
ListHeaderComponentStyle,
|
|
73
|
+
ListFooterComponent,
|
|
74
|
+
ListFooterComponentStyle,
|
|
75
|
+
ItemSeparatorComponent,
|
|
76
|
+
renderScrollComponent,
|
|
77
|
+
onScroll,
|
|
78
|
+
disableRecycling,
|
|
79
|
+
style,
|
|
80
|
+
stickyHeaderIndices,
|
|
81
|
+
maintainVisibleContentPosition,
|
|
82
|
+
onCommitLayoutEffect,
|
|
83
|
+
...rest
|
|
84
|
+
} = props;
|
|
85
|
+
|
|
86
|
+
// Core refs for managing scroll view, internal view, and child container
|
|
87
|
+
const scrollViewRef = useRef<CompatScroller>(null);
|
|
88
|
+
const internalViewRef = useRef<CompatView>(null);
|
|
89
|
+
const childContainerViewRef = useRef<CompatView>(null);
|
|
90
|
+
const containerViewSizeRef = useRef<RVDimension | undefined>(undefined);
|
|
91
|
+
const pendingChildIds = useRef<Set<string>>(new Set()).current;
|
|
92
|
+
|
|
93
|
+
// Track scroll position
|
|
94
|
+
const scrollY = useRef(new Animated.Value(0)).current;
|
|
95
|
+
|
|
96
|
+
// Refs for sticky headers and scroll anchoring
|
|
97
|
+
const stickyHeaderRef = useRef<StickyHeaderRef>(null);
|
|
98
|
+
const scrollAnchorRef = useRef<ScrollAnchorRef>(null);
|
|
99
|
+
|
|
100
|
+
// State for managing layout and render updates
|
|
101
|
+
const [_, setLayoutTreeId] = useLayoutState(0);
|
|
102
|
+
const [__, setRenderId] = useState(0);
|
|
103
|
+
|
|
104
|
+
// Map to store refs for each item in the list
|
|
105
|
+
const refHolder = useMemo(
|
|
106
|
+
() => new Map<number, RefObject<CompatView | null>>(),
|
|
107
|
+
[]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Initialize core RecyclerView manager and content offset management
|
|
111
|
+
const { recyclerViewManager } = useRecyclerViewManager(props);
|
|
112
|
+
const { applyContentOffset, applyInitialScrollIndex } =
|
|
113
|
+
useRecyclerViewController(
|
|
114
|
+
recyclerViewManager,
|
|
115
|
+
ref,
|
|
116
|
+
scrollViewRef,
|
|
117
|
+
scrollAnchorRef,
|
|
118
|
+
props
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Initialize view holder collection ref
|
|
122
|
+
const viewHolderCollectionRef = useRef<ViewHolderCollectionRef>(null);
|
|
123
|
+
|
|
124
|
+
// Hook to handle list loading
|
|
125
|
+
useOnListLoad(recyclerViewManager, onLoad);
|
|
126
|
+
|
|
127
|
+
// Hook to detect when scrolling reaches list bounds
|
|
128
|
+
const { checkBounds } = useBoundDetection(
|
|
129
|
+
recyclerViewManager,
|
|
130
|
+
props,
|
|
131
|
+
scrollViewRef
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Initialize the RecyclerView by measuring and setting up the window size
|
|
136
|
+
* This effect runs when the component mounts or when layout changes
|
|
137
|
+
*/
|
|
138
|
+
useLayoutEffect(() => {
|
|
139
|
+
if (internalViewRef.current && childContainerViewRef.current) {
|
|
140
|
+
// Measure the outer and inner container layouts
|
|
141
|
+
const outerViewLayout = measureLayout(internalViewRef.current, undefined);
|
|
142
|
+
const childViewLayout = measureLayoutRelative(
|
|
143
|
+
childContainerViewRef.current,
|
|
144
|
+
internalViewRef.current,
|
|
145
|
+
undefined
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
containerViewSizeRef.current = outerViewLayout;
|
|
149
|
+
|
|
150
|
+
// Calculate offset of first item
|
|
151
|
+
const firstItemOffset = horizontal
|
|
152
|
+
? childViewLayout.x - outerViewLayout.x
|
|
153
|
+
: childViewLayout.y - outerViewLayout.y;
|
|
154
|
+
|
|
155
|
+
// Update the RecyclerView manager with window dimensions
|
|
156
|
+
recyclerViewManager.updateLayoutParams(
|
|
157
|
+
{
|
|
158
|
+
width: horizontal ? outerViewLayout.width : childViewLayout.width,
|
|
159
|
+
height: horizontal ? childViewLayout.height : outerViewLayout.height,
|
|
160
|
+
},
|
|
161
|
+
firstItemOffset
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Effect to handle layout updates for list items
|
|
168
|
+
* This ensures proper positioning and recycling of items
|
|
169
|
+
*/
|
|
170
|
+
useLayoutEffect(() => {
|
|
171
|
+
if (pendingChildIds.size > 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const layoutInfo = Array.from(refHolder, ([index, viewHolderRef]) => {
|
|
175
|
+
const layout = measureLayout(
|
|
176
|
+
viewHolderRef.current!,
|
|
177
|
+
recyclerViewManager.getLayout(index)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
//comapre height with stored layout
|
|
181
|
+
//const storedLayout = recyclerViewManager.getLayout(index);
|
|
182
|
+
// if (
|
|
183
|
+
// storedLayout.height !== layout.height &&
|
|
184
|
+
// storedLayout.isHeightMeasured
|
|
185
|
+
// ) {
|
|
186
|
+
// console.log(
|
|
187
|
+
// "height changed",
|
|
188
|
+
// index,
|
|
189
|
+
// layout.height,
|
|
190
|
+
// storedLayout.height
|
|
191
|
+
// );
|
|
192
|
+
// }
|
|
193
|
+
return { index, dimensions: layout };
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
recyclerViewManager.modifyChildrenLayout(layoutInfo, data?.length ?? 0)
|
|
198
|
+
) {
|
|
199
|
+
// Trigger re-render if layout modifications were made
|
|
200
|
+
setRenderId((prev) => prev + 1);
|
|
201
|
+
} else {
|
|
202
|
+
//console.log("commitLayout");
|
|
203
|
+
// TODO: reduce perf impact of commitLayout
|
|
204
|
+
viewHolderCollectionRef.current?.commitLayout();
|
|
205
|
+
applyContentOffset();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Scroll event handler that manages scroll position, velocity, and RTL support
|
|
211
|
+
*/
|
|
212
|
+
const onScrollHandler = useCallback(
|
|
213
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
214
|
+
let velocity = event.nativeEvent.velocity;
|
|
215
|
+
let scrollOffset = horizontal
|
|
216
|
+
? event.nativeEvent.contentOffset.x
|
|
217
|
+
: event.nativeEvent.contentOffset.y;
|
|
218
|
+
|
|
219
|
+
// Handle RTL (Right-to-Left) layout adjustments
|
|
220
|
+
if (I18nManager.isRTL && horizontal) {
|
|
221
|
+
scrollOffset = adjustOffsetForRTL(
|
|
222
|
+
scrollOffset,
|
|
223
|
+
event.nativeEvent.contentSize.width,
|
|
224
|
+
event.nativeEvent.layoutMeasurement.width
|
|
225
|
+
);
|
|
226
|
+
if (velocity) {
|
|
227
|
+
velocity = {
|
|
228
|
+
x: -velocity.x,
|
|
229
|
+
y: velocity.y,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Update scroll position and trigger re-render if needed
|
|
235
|
+
if (recyclerViewManager.updateScrollOffset(scrollOffset, velocity)) {
|
|
236
|
+
setRenderId((prev) => prev + 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Update sticky headers and check bounds
|
|
240
|
+
stickyHeaderRef.current?.reportScrollEvent(event.nativeEvent);
|
|
241
|
+
checkBounds();
|
|
242
|
+
|
|
243
|
+
// Record interaction and compute item visibility
|
|
244
|
+
recyclerViewManager.recordInteraction();
|
|
245
|
+
recyclerViewManager.computeItemViewability();
|
|
246
|
+
|
|
247
|
+
// Call user-provided onScroll handler
|
|
248
|
+
onScroll?.(event);
|
|
249
|
+
},
|
|
250
|
+
[horizontal, recyclerViewManager]
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Create context for child components
|
|
254
|
+
const recyclerViewContext: RecyclerViewContext = useMemo(() => {
|
|
255
|
+
return {
|
|
256
|
+
layout: () => {
|
|
257
|
+
setLayoutTreeId((prev) => prev + 1);
|
|
258
|
+
},
|
|
259
|
+
getRef: () => {
|
|
260
|
+
return ref;
|
|
261
|
+
},
|
|
262
|
+
getScrollViewRef: () => {
|
|
263
|
+
return scrollViewRef;
|
|
264
|
+
},
|
|
265
|
+
markChildLayoutAsPending: (id: string) => {
|
|
266
|
+
pendingChildIds.add(id);
|
|
267
|
+
},
|
|
268
|
+
unmarkChildLayoutAsPending: (id: string) => {
|
|
269
|
+
if (pendingChildIds.has(id)) {
|
|
270
|
+
pendingChildIds.delete(id);
|
|
271
|
+
recyclerViewContext.layout();
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}, [setLayoutTreeId]);
|
|
276
|
+
|
|
277
|
+
const parentRecyclerViewContext = useRecyclerViewContext();
|
|
278
|
+
const recyclerViewId = useId();
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Validates that item dimensions match the expected layout
|
|
282
|
+
*/
|
|
283
|
+
const validateItemSize = useCallback(
|
|
284
|
+
(index: number, size: RVDimension) => {
|
|
285
|
+
const layout = recyclerViewManager.getLayout(index);
|
|
286
|
+
const width = Math.max(
|
|
287
|
+
Math.min(layout.width, layout.maxWidth ?? Infinity),
|
|
288
|
+
layout.minWidth ?? 0
|
|
289
|
+
);
|
|
290
|
+
const height = Math.max(
|
|
291
|
+
Math.min(layout.height, layout.maxHeight ?? Infinity),
|
|
292
|
+
layout.minHeight ?? 0
|
|
293
|
+
);
|
|
294
|
+
if (
|
|
295
|
+
areDimensionsNotEqual(width, size.width) ||
|
|
296
|
+
areDimensionsNotEqual(height, size.height)
|
|
297
|
+
) {
|
|
298
|
+
// console.log(
|
|
299
|
+
// "invalid size",
|
|
300
|
+
// index,
|
|
301
|
+
// width,
|
|
302
|
+
// size.width,
|
|
303
|
+
// height,
|
|
304
|
+
// size.height
|
|
305
|
+
// );
|
|
306
|
+
// TODO: Add a warning for missing useLayoutState
|
|
307
|
+
recyclerViewContext.layout();
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
[recyclerViewManager]
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Get secondary props and components
|
|
314
|
+
const {
|
|
315
|
+
refreshControl,
|
|
316
|
+
renderHeader,
|
|
317
|
+
renderFooter,
|
|
318
|
+
renderEmpty,
|
|
319
|
+
CompatScrollView,
|
|
320
|
+
} = useSecondaryProps(props);
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
!recyclerViewManager.getIsFirstLayoutComplete() &&
|
|
324
|
+
recyclerViewManager.getDataLength() > 0
|
|
325
|
+
) {
|
|
326
|
+
parentRecyclerViewContext?.markChildLayoutAsPending(recyclerViewId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Render sticky headers if configured
|
|
330
|
+
const stickyHeaders = useMemo(() => {
|
|
331
|
+
if (
|
|
332
|
+
data &&
|
|
333
|
+
data.length > 0 &&
|
|
334
|
+
stickyHeaderIndices &&
|
|
335
|
+
stickyHeaderIndices.length > 0
|
|
336
|
+
) {
|
|
337
|
+
return (
|
|
338
|
+
<StickyHeaders
|
|
339
|
+
stickyHeaderIndices={stickyHeaderIndices}
|
|
340
|
+
data={data}
|
|
341
|
+
renderItem={renderItem}
|
|
342
|
+
scrollY={scrollY}
|
|
343
|
+
stickyHeaderRef={stickyHeaderRef}
|
|
344
|
+
recyclerViewManager={recyclerViewManager}
|
|
345
|
+
extraData={extraData}
|
|
346
|
+
/>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}, [data, stickyHeaderIndices, renderItem, extraData]);
|
|
351
|
+
|
|
352
|
+
// Set up scroll event handling with animation support for sticky headers
|
|
353
|
+
const animatedEvent = useMemo(() => {
|
|
354
|
+
if (stickyHeaders) {
|
|
355
|
+
return Animated.event(
|
|
356
|
+
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
|
|
357
|
+
{ useNativeDriver: true, listener: onScrollHandler }
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return onScrollHandler;
|
|
361
|
+
}, [onScrollHandler, stickyHeaders]);
|
|
362
|
+
|
|
363
|
+
const maintainVisibleContentPositionInternal = useMemo(() => {
|
|
364
|
+
if (maintainVisibleContentPosition?.disabled || horizontal) {
|
|
365
|
+
return undefined;
|
|
366
|
+
} else {
|
|
367
|
+
return {
|
|
368
|
+
...maintainVisibleContentPosition,
|
|
369
|
+
minIndexForVisible: 0,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}, [maintainVisibleContentPosition]);
|
|
373
|
+
|
|
374
|
+
const shouldRenderFromBottom =
|
|
375
|
+
maintainVisibleContentPositionInternal?.startRenderingFromBottom ?? false;
|
|
376
|
+
|
|
377
|
+
// Calculate minimum height adjustment for bottom rendering
|
|
378
|
+
const adjustmentMinHeight = recyclerViewManager.hasLayout()
|
|
379
|
+
? Math.max(
|
|
380
|
+
0,
|
|
381
|
+
recyclerViewManager.getWindowSize().height -
|
|
382
|
+
recyclerViewManager.getChildContainerDimensions().height -
|
|
383
|
+
recyclerViewManager.firstItemOffset
|
|
384
|
+
)
|
|
385
|
+
: 0;
|
|
386
|
+
|
|
387
|
+
// Create view for measuring bounded size
|
|
388
|
+
const viewToMeasureBoundedSize = useMemo(() => {
|
|
389
|
+
return (
|
|
390
|
+
<CompatView
|
|
391
|
+
style={{
|
|
392
|
+
height: horizontal ? undefined : 0,
|
|
393
|
+
width: horizontal ? 0 : undefined,
|
|
394
|
+
minHeight: shouldRenderFromBottom ? adjustmentMinHeight : undefined,
|
|
395
|
+
}}
|
|
396
|
+
ref={childContainerViewRef}
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
}, [horizontal, shouldRenderFromBottom, adjustmentMinHeight]);
|
|
400
|
+
|
|
401
|
+
//console.log("render");
|
|
402
|
+
|
|
403
|
+
// Render the main RecyclerView structure
|
|
404
|
+
return (
|
|
405
|
+
<RecyclerViewContextProvider value={recyclerViewContext}>
|
|
406
|
+
<CompatView
|
|
407
|
+
style={{ flex: horizontal ? undefined : 1, ...style }}
|
|
408
|
+
ref={internalViewRef}
|
|
409
|
+
collapsable={false}
|
|
410
|
+
onLayout={(event) => {
|
|
411
|
+
if (
|
|
412
|
+
areDimensionsNotEqual(
|
|
413
|
+
event.nativeEvent.layout.width,
|
|
414
|
+
containerViewSizeRef.current?.width ?? 0
|
|
415
|
+
) ||
|
|
416
|
+
areDimensionsNotEqual(
|
|
417
|
+
event.nativeEvent.layout.height,
|
|
418
|
+
containerViewSizeRef.current?.height ?? 0
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
// console.log(
|
|
422
|
+
// "onLayout",
|
|
423
|
+
|
|
424
|
+
// recyclerViewManager.getWindowSize(),
|
|
425
|
+
// event.nativeEvent.layout
|
|
426
|
+
// );
|
|
427
|
+
recyclerViewContext.layout();
|
|
428
|
+
}
|
|
429
|
+
}}
|
|
430
|
+
>
|
|
431
|
+
<CompatScrollView
|
|
432
|
+
{...rest}
|
|
433
|
+
horizontal={horizontal}
|
|
434
|
+
ref={scrollViewRef}
|
|
435
|
+
onScroll={animatedEvent}
|
|
436
|
+
// TODO: evaluate perf
|
|
437
|
+
maintainVisibleContentPosition={
|
|
438
|
+
maintainVisibleContentPositionInternal
|
|
439
|
+
}
|
|
440
|
+
refreshControl={refreshControl}
|
|
441
|
+
{...overrideProps}
|
|
442
|
+
>
|
|
443
|
+
{/* Scroll anchor for maintaining content position */}
|
|
444
|
+
{maintainVisibleContentPositionInternal && (
|
|
445
|
+
<ScrollAnchor scrollAnchorRef={scrollAnchorRef} />
|
|
446
|
+
)}
|
|
447
|
+
{renderHeader}
|
|
448
|
+
{viewToMeasureBoundedSize}
|
|
449
|
+
{/* Main list content */}
|
|
450
|
+
<ViewHolderCollection
|
|
451
|
+
viewHolderCollectionRef={viewHolderCollectionRef}
|
|
452
|
+
data={data}
|
|
453
|
+
horizontal={horizontal}
|
|
454
|
+
renderStack={recyclerViewManager.getRenderStack()}
|
|
455
|
+
getLayout={(index) => recyclerViewManager.getLayout(index)}
|
|
456
|
+
refHolder={refHolder}
|
|
457
|
+
onSizeChanged={validateItemSize}
|
|
458
|
+
renderItem={renderItem}
|
|
459
|
+
extraData={extraData}
|
|
460
|
+
onCommitLayoutEffect={() => {
|
|
461
|
+
applyInitialScrollIndex();
|
|
462
|
+
parentRecyclerViewContext?.unmarkChildLayoutAsPending(
|
|
463
|
+
recyclerViewId
|
|
464
|
+
);
|
|
465
|
+
onCommitLayoutEffect?.();
|
|
466
|
+
}}
|
|
467
|
+
onCommitEffect={() => {
|
|
468
|
+
applyInitialScrollIndex();
|
|
469
|
+
checkBounds();
|
|
470
|
+
recyclerViewManager.computeItemViewability();
|
|
471
|
+
recyclerViewManager.disableRecycling = Boolean(disableRecycling);
|
|
472
|
+
}}
|
|
473
|
+
CellRendererComponent={CellRendererComponent}
|
|
474
|
+
ItemSeparatorComponent={ItemSeparatorComponent}
|
|
475
|
+
getChildContainerLayout={() =>
|
|
476
|
+
recyclerViewManager.hasLayout()
|
|
477
|
+
? recyclerViewManager.getChildContainerDimensions()
|
|
478
|
+
: undefined
|
|
479
|
+
}
|
|
480
|
+
/>
|
|
481
|
+
{renderEmpty}
|
|
482
|
+
{renderFooter}
|
|
483
|
+
</CompatScrollView>
|
|
484
|
+
{stickyHeaders}
|
|
485
|
+
</CompatView>
|
|
486
|
+
</RecyclerViewContextProvider>
|
|
487
|
+
);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// Type definition for the RecyclerView component
|
|
491
|
+
type RecyclerViewType = <T>(
|
|
492
|
+
props: RecyclerViewProps<T> & { ref?: React.Ref<any> }
|
|
493
|
+
) => React.JSX.Element;
|
|
494
|
+
|
|
495
|
+
// Create and export the memoized, forwarded ref component
|
|
496
|
+
const RecyclerView = React.memo(
|
|
497
|
+
forwardRef(RecyclerViewComponent)
|
|
498
|
+
) as RecyclerViewType;
|
|
499
|
+
|
|
500
|
+
export { RecyclerView };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { CompatScroller } from "./components/CompatScroller";
|
|
3
|
+
|
|
4
|
+
export interface RecyclerViewContext {
|
|
5
|
+
layout: () => void;
|
|
6
|
+
getRef: () => React.Ref<any>;
|
|
7
|
+
getScrollViewRef: () => React.RefObject<CompatScroller | null>;
|
|
8
|
+
markChildLayoutAsPending: (id: string) => void;
|
|
9
|
+
unmarkChildLayoutAsPending: (id: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const RecyclerViewContextInstance = createContext<
|
|
13
|
+
RecyclerViewContext | undefined
|
|
14
|
+
>(undefined);
|
|
15
|
+
|
|
16
|
+
export const RecyclerViewContextProvider = RecyclerViewContextInstance.Provider;
|
|
17
|
+
export function useRecyclerViewContext() {
|
|
18
|
+
return useContext(RecyclerViewContextInstance);
|
|
19
|
+
}
|