@legendapp/list 2.0.0-next.0 → 2.0.0-next.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.
Files changed (262) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.cursor/rules/changelog.mdc +60 -0
  3. package/.github/FUNDING.yml +15 -0
  4. package/.gitignore +5 -0
  5. package/.prettierrc.json +5 -0
  6. package/.vscode/settings.json +14 -0
  7. package/CLAUDE.md +126 -0
  8. package/biome.json +46 -0
  9. package/bun.lock +1289 -0
  10. package/bunfig.toml +2 -0
  11. package/dist/CHANGELOG.md +119 -0
  12. package/dist/LICENSE +21 -0
  13. package/dist/README.md +139 -0
  14. package/{animated.d.mts → dist/animated.d.mts} +1 -1
  15. package/{animated.d.ts → dist/animated.d.ts} +1 -1
  16. package/{index.d.mts → dist/index.d.mts} +63 -15
  17. package/{index.d.ts → dist/index.d.ts} +63 -15
  18. package/dist/index.js +2525 -0
  19. package/dist/index.mjs +2497 -0
  20. package/{keyboard-controller.d.mts → dist/keyboard-controller.d.mts} +4 -4
  21. package/{keyboard-controller.d.ts → dist/keyboard-controller.d.ts} +4 -4
  22. package/dist/package.json +35 -0
  23. package/example/README.md +40 -0
  24. package/example/api/data/genres.json +23 -0
  25. package/example/api/data/playlist/10402-10749.json +1 -0
  26. package/example/api/data/playlist/10402-10770.json +1 -0
  27. package/example/api/data/playlist/10402-37.json +1 -0
  28. package/example/api/data/playlist/10749-10752.json +1 -0
  29. package/example/api/data/playlist/10749-10770.json +1 -0
  30. package/example/api/data/playlist/10749-37.json +1 -0
  31. package/example/api/data/playlist/10749-878.json +1 -0
  32. package/example/api/data/playlist/10751-10402.json +1 -0
  33. package/example/api/data/playlist/10751-10752.json +1 -0
  34. package/example/api/data/playlist/10751-37.json +1 -0
  35. package/example/api/data/playlist/10751-53.json +1 -0
  36. package/example/api/data/playlist/10751-878.json +1 -0
  37. package/example/api/data/playlist/10751-9648.json +1 -0
  38. package/example/api/data/playlist/10752-37.json +1 -0
  39. package/example/api/data/playlist/12-10402.json +1 -0
  40. package/example/api/data/playlist/12-10749.json +1 -0
  41. package/example/api/data/playlist/12-18.json +1 -0
  42. package/example/api/data/playlist/12-27.json +1 -0
  43. package/example/api/data/playlist/12-35.json +1 -0
  44. package/example/api/data/playlist/14-36.json +1 -0
  45. package/example/api/data/playlist/14-878.json +1 -0
  46. package/example/api/data/playlist/16-10751.json +1 -0
  47. package/example/api/data/playlist/16-10770.json +1 -0
  48. package/example/api/data/playlist/16-35.json +1 -0
  49. package/example/api/data/playlist/16-36.json +1 -0
  50. package/example/api/data/playlist/16-53.json +1 -0
  51. package/example/api/data/playlist/18-10751.json +1 -0
  52. package/example/api/data/playlist/18-10752.json +1 -0
  53. package/example/api/data/playlist/18-37.json +1 -0
  54. package/example/api/data/playlist/18-53.json +1 -0
  55. package/example/api/data/playlist/18-878.json +1 -0
  56. package/example/api/data/playlist/27-10749.json +1 -0
  57. package/example/api/data/playlist/27-10770.json +1 -0
  58. package/example/api/data/playlist/28-10749.json +1 -0
  59. package/example/api/data/playlist/28-10751.json +1 -0
  60. package/example/api/data/playlist/28-10770.json +1 -0
  61. package/example/api/data/playlist/28-16.json +1 -0
  62. package/example/api/data/playlist/28-18.json +1 -0
  63. package/example/api/data/playlist/28-36.json +1 -0
  64. package/example/api/data/playlist/28-37.json +1 -0
  65. package/example/api/data/playlist/28-53.json +1 -0
  66. package/example/api/data/playlist/28-80.json +1 -0
  67. package/example/api/data/playlist/28-99.json +1 -0
  68. package/example/api/data/playlist/35-10749.json +1 -0
  69. package/example/api/data/playlist/35-10751.json +1 -0
  70. package/example/api/data/playlist/35-10752.json +1 -0
  71. package/example/api/data/playlist/35-27.json +1 -0
  72. package/example/api/data/playlist/35-36.json +1 -0
  73. package/example/api/data/playlist/35-53.json +1 -0
  74. package/example/api/data/playlist/35-80.json +1 -0
  75. package/example/api/data/playlist/36-37.json +1 -0
  76. package/example/api/data/playlist/36-878.json +1 -0
  77. package/example/api/data/playlist/36-9648.json +1 -0
  78. package/example/api/data/playlist/53-10752.json +1 -0
  79. package/example/api/data/playlist/80-10770.json +1 -0
  80. package/example/api/data/playlist/80-14.json +1 -0
  81. package/example/api/data/playlist/80-18.json +1 -0
  82. package/example/api/data/playlist/80-37.json +1 -0
  83. package/example/api/data/playlist/878-37.json +1 -0
  84. package/example/api/data/playlist/9648-10770.json +1 -0
  85. package/example/api/data/playlist/9648-37.json +1 -0
  86. package/example/api/data/playlist/9648-53.json +1 -0
  87. package/example/api/data/playlist/9648-878.json +1 -0
  88. package/example/api/data/playlist/99-10749.json +1 -0
  89. package/example/api/data/playlist/99-14.json +1 -0
  90. package/example/api/data/playlist/99-18.json +1 -0
  91. package/example/api/data/playlist/99-27.json +1 -0
  92. package/example/api/data/playlist/99-53.json +1 -0
  93. package/example/api/data/playlist/99-9648.json +1 -0
  94. package/example/api/data/playlist/index.ts +73 -0
  95. package/example/api/data/rows.json +1 -0
  96. package/example/api/index.ts +36 -0
  97. package/example/app/(tabs)/_layout.tsx +60 -0
  98. package/example/app/(tabs)/cards.tsx +81 -0
  99. package/example/app/(tabs)/index.tsx +205 -0
  100. package/example/app/(tabs)/moviesL.tsx +7 -0
  101. package/example/app/(tabs)/moviesLR.tsx +7 -0
  102. package/example/app/+not-found.tsx +32 -0
  103. package/example/app/_layout.tsx +34 -0
  104. package/example/app/accurate-scrollto/index.tsx +125 -0
  105. package/example/app/accurate-scrollto-2/index.tsx +52 -0
  106. package/example/app/accurate-scrollto-huge/index.tsx +128 -0
  107. package/example/app/add-to-end/index.tsx +82 -0
  108. package/example/app/ai-chat/index.tsx +236 -0
  109. package/example/app/bidirectional-infinite-list/index.tsx +133 -0
  110. package/example/app/cards-columns/index.tsx +37 -0
  111. package/example/app/cards-flashlist/index.tsx +122 -0
  112. package/example/app/cards-flatlist/index.tsx +94 -0
  113. package/example/app/cards-no-recycle/index.tsx +110 -0
  114. package/example/app/cards-renderItem.tsx +354 -0
  115. package/example/app/chat-example/index.tsx +167 -0
  116. package/example/app/chat-infinite/index.tsx +239 -0
  117. package/example/app/chat-keyboard/index.tsx +248 -0
  118. package/example/app/chat-resize-outer/index.tsx +247 -0
  119. package/example/app/columns/index.tsx +78 -0
  120. package/example/app/countries/index.tsx +182 -0
  121. package/example/app/countries-flashlist/index.tsx +163 -0
  122. package/example/app/countries-reorder/index.tsx +187 -0
  123. package/example/app/extra-data/index.tsx +86 -0
  124. package/example/app/filter-elements/filter-data-provider.tsx +55 -0
  125. package/example/app/filter-elements/index.tsx +118 -0
  126. package/example/app/initial-scroll-index/index.tsx +106 -0
  127. package/example/app/initial-scroll-index/renderFixedItem.tsx +215 -0
  128. package/example/app/initial-scroll-index-free-height/index.tsx +70 -0
  129. package/example/app/initial-scroll-index-keyed/index.tsx +62 -0
  130. package/example/app/lazy-list/index.tsx +123 -0
  131. package/example/app/movies-flashlist/index.tsx +7 -0
  132. package/example/app/mutable-cells/index.tsx +104 -0
  133. package/example/app/video-feed/index.tsx +119 -0
  134. package/example/app.config.js +22 -0
  135. package/example/app.json +45 -0
  136. package/example/assets/fonts/SpaceMono-Regular.ttf +0 -0
  137. package/example/assets/images/adaptive-icon.png +0 -0
  138. package/example/assets/images/favicon.png +0 -0
  139. package/example/assets/images/icon.png +0 -0
  140. package/example/assets/images/partial-react-logo.png +0 -0
  141. package/example/assets/images/react-logo.png +0 -0
  142. package/example/assets/images/react-logo@2x.png +0 -0
  143. package/example/assets/images/react-logo@3x.png +0 -0
  144. package/example/assets/images/splash-icon.png +0 -0
  145. package/example/autoscroll.sh +101 -0
  146. package/example/bun.lock +2266 -0
  147. package/example/bunfig.toml +2 -0
  148. package/example/components/Breathe.tsx +54 -0
  149. package/example/components/Circle.tsx +69 -0
  150. package/example/components/Collapsible.tsx +44 -0
  151. package/example/components/ExternalLink.tsx +24 -0
  152. package/example/components/HapticTab.tsx +18 -0
  153. package/example/components/HelloWave.tsx +37 -0
  154. package/example/components/Movies.tsx +179 -0
  155. package/example/components/ParallaxScrollView.tsx +81 -0
  156. package/example/components/ThemedText.tsx +60 -0
  157. package/example/components/ThemedView.tsx +14 -0
  158. package/example/components/__tests__/ThemedText-test.tsx +10 -0
  159. package/example/components/__tests__/__snapshots__/ThemedText-test.tsx.snap +24 -0
  160. package/example/components/ui/IconSymbol.ios.tsx +32 -0
  161. package/example/components/ui/IconSymbol.tsx +43 -0
  162. package/example/components/ui/TabBarBackground.ios.tsx +22 -0
  163. package/example/components/ui/TabBarBackground.tsx +6 -0
  164. package/example/constants/Colors.ts +26 -0
  165. package/example/constants/constants.ts +5 -0
  166. package/example/constants/useScrollTest.ts +19 -0
  167. package/example/hooks/useColorScheme.ts +1 -0
  168. package/example/hooks/useColorScheme.web.ts +8 -0
  169. package/example/hooks/useThemeColor.ts +22 -0
  170. package/example/ios/.xcode.env +11 -0
  171. package/example/ios/Podfile +64 -0
  172. package/example/ios/Podfile.lock +2767 -0
  173. package/example/ios/Podfile.properties.json +5 -0
  174. package/example/ios/listtest/AppDelegate.swift +70 -0
  175. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  176. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  177. package/example/ios/listtest/Images.xcassets/Contents.json +6 -0
  178. package/example/ios/listtest/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  179. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/Contents.json +23 -0
  180. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image.png +0 -0
  181. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@2x.png +0 -0
  182. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@3x.png +0 -0
  183. package/example/ios/listtest/Info.plist +85 -0
  184. package/example/ios/listtest/PrivacyInfo.xcprivacy +48 -0
  185. package/example/ios/listtest/SplashScreen.storyboard +42 -0
  186. package/example/ios/listtest/Supporting/Expo.plist +12 -0
  187. package/example/ios/listtest/listtest-Bridging-Header.h +3 -0
  188. package/example/ios/listtest/listtest.entitlements +5 -0
  189. package/example/ios/listtest.xcodeproj/project.pbxproj +547 -0
  190. package/example/ios/listtest.xcodeproj/xcshareddata/xcschemes/listtest.xcscheme +88 -0
  191. package/example/ios/listtest.xcworkspace/contents.xcworkspacedata +10 -0
  192. package/example/metro.config.js +16 -0
  193. package/example/package.json +73 -0
  194. package/example/scripts/reset-project.js +84 -0
  195. package/example/tsconfig.json +26 -0
  196. package/package.json +88 -34
  197. package/posttsup.ts +24 -0
  198. package/src/Container.tsx +176 -0
  199. package/src/Containers.tsx +85 -0
  200. package/src/ContextContainer.ts +145 -0
  201. package/src/DebugView.tsx +83 -0
  202. package/src/LazyLegendList.tsx +41 -0
  203. package/src/LeanView.tsx +18 -0
  204. package/src/LegendList.tsx +558 -0
  205. package/src/ListComponent.tsx +191 -0
  206. package/src/ScrollAdjust.tsx +24 -0
  207. package/src/ScrollAdjustHandler.ts +26 -0
  208. package/src/Separator.tsx +14 -0
  209. package/src/animated.tsx +6 -0
  210. package/src/calculateItemsInView.ts +363 -0
  211. package/src/calculateOffsetForIndex.ts +23 -0
  212. package/src/calculateOffsetWithOffsetPosition.ts +26 -0
  213. package/src/checkAllSizesKnown.ts +17 -0
  214. package/src/checkAtBottom.ts +36 -0
  215. package/src/checkAtTop.ts +27 -0
  216. package/src/checkThreshold.ts +30 -0
  217. package/src/constants.ts +11 -0
  218. package/src/createColumnWrapperStyle.ts +16 -0
  219. package/src/doInitialAllocateContainers.ts +40 -0
  220. package/src/doMaintainScrollAtEnd.ts +34 -0
  221. package/src/findAvailableContainers.ts +98 -0
  222. package/src/finishScrollTo.ts +8 -0
  223. package/src/getId.ts +21 -0
  224. package/src/getItemSize.ts +52 -0
  225. package/src/getRenderedItem.ts +34 -0
  226. package/src/getScrollVelocity.ts +47 -0
  227. package/src/handleLayout.ts +70 -0
  228. package/src/helpers.ts +39 -0
  229. package/src/index.ts +11 -0
  230. package/src/keyboard-controller.tsx +63 -0
  231. package/src/onScroll.ts +66 -0
  232. package/src/prepareMVCP.ts +50 -0
  233. package/src/reanimated.tsx +63 -0
  234. package/src/requestAdjust.ts +41 -0
  235. package/src/scrollTo.ts +40 -0
  236. package/src/scrollToIndex.ts +34 -0
  237. package/src/setDidLayout.ts +25 -0
  238. package/src/setPaddingTop.ts +28 -0
  239. package/src/state.tsx +304 -0
  240. package/src/types.ts +610 -0
  241. package/src/updateAlignItemsPaddingTop.ts +18 -0
  242. package/src/updateAllPositions.ts +130 -0
  243. package/src/updateItemSize.ts +203 -0
  244. package/src/updateTotalSize.ts +44 -0
  245. package/src/useAnimatedValue.ts +6 -0
  246. package/src/useCombinedRef.ts +22 -0
  247. package/src/useInit.ts +17 -0
  248. package/src/useSyncLayout.tsx +68 -0
  249. package/src/useValue$.ts +53 -0
  250. package/src/viewability.ts +279 -0
  251. package/tsconfig.json +59 -0
  252. package/tsup.config.ts +21 -0
  253. package/index.js +0 -2348
  254. package/index.mjs +0 -2320
  255. /package/{animated.js → dist/animated.js} +0 -0
  256. /package/{animated.mjs → dist/animated.mjs} +0 -0
  257. /package/{keyboard-controller.js → dist/keyboard-controller.js} +0 -0
  258. /package/{keyboard-controller.mjs → dist/keyboard-controller.mjs} +0 -0
  259. /package/{reanimated.d.mts → dist/reanimated.d.mts} +0 -0
  260. /package/{reanimated.d.ts → dist/reanimated.d.ts} +0 -0
  261. /package/{reanimated.js → dist/reanimated.js} +0 -0
  262. /package/{reanimated.mjs → dist/reanimated.mjs} +0 -0
@@ -0,0 +1,558 @@
1
+ import * as React from "react";
2
+ import {
3
+ type ForwardedRef,
4
+ useCallback,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useLayoutEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import {
13
+ Dimensions,
14
+ type LayoutChangeEvent,
15
+ type LayoutRectangle,
16
+ type NativeScrollEvent,
17
+ Platform,
18
+ RefreshControl,
19
+ type ScrollView,
20
+ StyleSheet,
21
+ type View,
22
+ } from "react-native";
23
+ import { DebugView } from "./DebugView";
24
+ import { ListComponent } from "./ListComponent";
25
+ import { ScrollAdjustHandler } from "./ScrollAdjustHandler";
26
+ import { calculateItemsInView } from "./calculateItemsInView";
27
+ import { calculateOffsetForIndex } from "./calculateOffsetForIndex";
28
+ import { checkAtBottom } from "./checkAtBottom";
29
+ import { checkAtTop } from "./checkAtTop";
30
+ import { ENABLE_DEBUG_VIEW, IsNewArchitecture } from "./constants";
31
+ import { createColumnWrapperStyle } from "./createColumnWrapperStyle";
32
+ import { doInitialAllocateContainers } from "./doInitialAllocateContainers";
33
+ import { doMaintainScrollAtEnd } from "./doMaintainScrollAtEnd";
34
+ import { finishScrollTo } from "./finishScrollTo";
35
+ import { getId } from "./getId";
36
+ import { getRenderedItem } from "./getRenderedItem";
37
+ import { handleLayout } from "./handleLayout";
38
+ import { extractPadding, warnDevOnce } from "./helpers";
39
+ import { onScroll } from "./onScroll";
40
+ import { requestAdjust } from "./requestAdjust";
41
+ import { scrollTo } from "./scrollTo";
42
+ import { scrollToIndex } from "./scrollToIndex";
43
+ import { setPaddingTop } from "./setPaddingTop";
44
+ import { StateProvider, peek$, set$, useStateContext } from "./state";
45
+ import type {
46
+ InternalState,
47
+ LegendListProps,
48
+ LegendListRef,
49
+ MaintainScrollAtEndOptions,
50
+ ScrollIndexWithOffsetPosition,
51
+ ScrollState,
52
+ } from "./types";
53
+ import { typedForwardRef } from "./types";
54
+ import { updateAllPositions } from "./updateAllPositions";
55
+ import { updateItemSize } from "./updateItemSize";
56
+ import { useCombinedRef } from "./useCombinedRef";
57
+ import { useInit } from "./useInit";
58
+ import { setupViewability } from "./viewability";
59
+
60
+ const DEFAULT_DRAW_DISTANCE = 250;
61
+ const DEFAULT_ITEM_SIZE = 100;
62
+
63
+ export const LegendList = typedForwardRef(function LegendList<T>(
64
+ props: LegendListProps<T>,
65
+ forwardedRef: ForwardedRef<LegendListRef>,
66
+ ) {
67
+ return (
68
+ <StateProvider>
69
+ <LegendListInner {...props} ref={forwardedRef} />
70
+ </StateProvider>
71
+ );
72
+ });
73
+
74
+ const LegendListInner = typedForwardRef(function LegendListInner<T>(
75
+ props: LegendListProps<T>,
76
+ forwardedRef: ForwardedRef<LegendListRef>,
77
+ ) {
78
+ const {
79
+ data: dataProp = [],
80
+ initialScrollIndex: initialScrollIndexProp,
81
+ initialScrollOffset,
82
+ horizontal,
83
+ drawDistance = 250,
84
+ recycleItems = false,
85
+ onEndReachedThreshold = 0.5,
86
+ onStartReachedThreshold = 0.5,
87
+ maintainScrollAtEnd = false,
88
+ maintainScrollAtEndThreshold = 0.1,
89
+ alignItemsAtEnd = false,
90
+ maintainVisibleContentPosition = false,
91
+ onScroll: onScrollProp,
92
+ onMomentumScrollEnd,
93
+ numColumns: numColumnsProp = 1,
94
+ columnWrapperStyle,
95
+ keyExtractor: keyExtractorProp,
96
+ renderItem,
97
+ estimatedListSize,
98
+ estimatedItemSize: estimatedItemSizeProp,
99
+ getEstimatedItemSize,
100
+ suggestEstimatedItemSize,
101
+ ListHeaderComponent,
102
+ ListEmptyComponent,
103
+ onItemSizeChanged,
104
+ refScrollView,
105
+ waitForInitialLayout = true,
106
+ extraData,
107
+ contentContainerStyle: contentContainerStyleProp,
108
+ style: styleProp,
109
+ onLayout: onLayoutProp,
110
+ onRefresh,
111
+ refreshing,
112
+ progressViewOffset,
113
+ refreshControl,
114
+ initialContainerPoolRatio = 2,
115
+ viewabilityConfig,
116
+ viewabilityConfigCallbackPairs,
117
+ onViewableItemsChanged,
118
+ onStartReached,
119
+ onEndReached,
120
+ onLoad,
121
+ ...rest
122
+ } = props;
123
+
124
+ const [renderNum, setRenderNum] = useState(0);
125
+ const initialScroll: ScrollIndexWithOffsetPosition | undefined =
126
+ typeof initialScrollIndexProp === "number" ? { index: initialScrollIndexProp } : initialScrollIndexProp;
127
+ const initialScrollIndex = initialScroll?.index;
128
+
129
+ const [canRender, setCanRender] = React.useState(!IsNewArchitecture);
130
+
131
+ const contentContainerStyle = { ...StyleSheet.flatten(contentContainerStyleProp) };
132
+ const style = { ...StyleSheet.flatten(styleProp) };
133
+ const stylePaddingTopState = extractPadding(style, contentContainerStyle, "Top");
134
+ const stylePaddingBottomState = extractPadding(style, contentContainerStyle, "Bottom");
135
+
136
+ const ctx = useStateContext();
137
+ ctx.columnWrapperStyle =
138
+ columnWrapperStyle || (contentContainerStyle ? createColumnWrapperStyle(contentContainerStyle) : undefined);
139
+
140
+ const refScroller = useRef<ScrollView>(null) as React.MutableRefObject<ScrollView>;
141
+ const combinedRef = useCombinedRef(refScroller, refScrollView);
142
+ const estimatedItemSize = estimatedItemSizeProp ?? DEFAULT_ITEM_SIZE;
143
+ const scrollBuffer = (drawDistance ?? DEFAULT_DRAW_DISTANCE) || 1;
144
+ const keyExtractor = keyExtractorProp ?? ((item, index) => index.toString());
145
+
146
+ const refState = useRef<InternalState>();
147
+
148
+ if (!refState.current) {
149
+ const initialScrollLength = (estimatedListSize ??
150
+ (IsNewArchitecture ? { width: 0, height: 0 } : Dimensions.get("window")))[horizontal ? "width" : "height"];
151
+
152
+ refState.current = {
153
+ sizes: new Map(),
154
+ positions: new Map(),
155
+ columns: new Map(),
156
+ pendingAdjust: 0,
157
+ isStartReached: false,
158
+ isEndReached: false,
159
+ isAtEnd: false,
160
+ isAtStart: false,
161
+ scrollLength: initialScrollLength,
162
+ startBuffered: -1,
163
+ startNoBuffer: -1,
164
+ endBuffered: -1,
165
+ endNoBuffer: -1,
166
+ firstFullyOnScreenIndex: -1,
167
+ scroll: 0,
168
+ totalSize: 0,
169
+ timeouts: new Set(),
170
+ viewabilityConfigCallbackPairs: undefined as never,
171
+ scrollAdjustHandler: new ScrollAdjustHandler(ctx),
172
+ nativeMarginTop: 0,
173
+ scrollPrev: 0,
174
+ scrollPrevTime: 0,
175
+ scrollTime: 0,
176
+ scrollPending: 0,
177
+ indexByKey: new Map(),
178
+ scrollHistory: [],
179
+ sizesKnown: new Map(),
180
+ timeoutSizeMessage: 0,
181
+ startReachedBlockedByTimer: false,
182
+ endReachedBlockedByTimer: false,
183
+ scrollForNextCalculateItemsInView: undefined,
184
+ enableScrollForNextCalculateItemsInView: true,
185
+ minIndexSizeChanged: 0,
186
+ queuedCalculateItemsInView: 0,
187
+ lastBatchingAction: Date.now(),
188
+ averageSizes: {},
189
+ idsInView: [],
190
+ containerItemKeys: new Set(),
191
+ idCache: new Map(),
192
+ props: {} as any,
193
+ refScroller: undefined as any,
194
+ loadStartTime: Date.now(),
195
+ initialScroll,
196
+ lastLayout: undefined,
197
+ };
198
+
199
+ set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
200
+ set$(ctx, "extraData", extraData);
201
+ }
202
+
203
+ const state = refState.current!;
204
+
205
+ const isFirst = !state.props.renderItem;
206
+
207
+ const didDataChange = state.props.data !== dataProp;
208
+ state.props = {
209
+ alignItemsAtEnd,
210
+ data: dataProp,
211
+ estimatedItemSize,
212
+ maintainScrollAtEnd,
213
+ maintainScrollAtEndThreshold,
214
+ onEndReachedThreshold,
215
+ onStartReachedThreshold,
216
+ stylePaddingBottom: stylePaddingBottomState,
217
+ horizontal: !!horizontal,
218
+ maintainVisibleContentPosition,
219
+ onItemSizeChanged,
220
+ suggestEstimatedItemSize: !!suggestEstimatedItemSize,
221
+ keyExtractor,
222
+ onScroll: onScrollProp,
223
+ getEstimatedItemSize,
224
+ onStartReached,
225
+ onEndReached,
226
+ onLoad,
227
+ renderItem: renderItem!,
228
+ initialScroll,
229
+ scrollBuffer,
230
+ viewabilityConfigCallbackPairs: undefined,
231
+ numColumns: numColumnsProp,
232
+ initialContainerPoolRatio,
233
+ stylePaddingTop: stylePaddingTopState,
234
+ };
235
+
236
+ state.refScroller = refScroller;
237
+
238
+ const checkResetContainers = (isFirst: boolean) => {
239
+ const state = refState.current;
240
+ if (state) {
241
+ state.props.data = dataProp;
242
+
243
+ if (!isFirst) {
244
+ calculateItemsInView(ctx, state, { dataChanged: true, doMVCP: true });
245
+
246
+ const shouldMaintainScrollAtEnd =
247
+ maintainScrollAtEnd === true || (maintainScrollAtEnd as MaintainScrollAtEndOptions).onDataChange;
248
+
249
+ const didMaintainScrollAtEnd = shouldMaintainScrollAtEnd && doMaintainScrollAtEnd(ctx, state, false);
250
+
251
+ // Reset the endReached flag if new data has been added and we didn't
252
+ // just maintain the scroll at end
253
+ if (!didMaintainScrollAtEnd && dataProp.length > state.props.data.length) {
254
+ state.isEndReached = false;
255
+ }
256
+
257
+ if (!didMaintainScrollAtEnd) {
258
+ checkAtTop(state);
259
+ checkAtBottom(ctx, state);
260
+ }
261
+ }
262
+ }
263
+ };
264
+
265
+ const memoizedLastItemKeys = useMemo(() => {
266
+ if (!dataProp.length) return [];
267
+ return Array.from({ length: Math.min(numColumnsProp, dataProp.length) }, (_, i) =>
268
+ getId(state, dataProp.length - 1 - i),
269
+ );
270
+ }, [dataProp, numColumnsProp]);
271
+
272
+ // Run first time and whenever data changes
273
+ const initalizeStateVars = () => {
274
+ set$(ctx, "lastItemKeys", memoizedLastItemKeys);
275
+ set$(ctx, "numColumns", numColumnsProp);
276
+
277
+ // If the stylePaddingTop has changed, scroll to an adjusted offset to
278
+ // keep the same content in view
279
+ const prevPaddingTop = peek$(ctx, "stylePaddingTop");
280
+ setPaddingTop(ctx, { stylePaddingTop: stylePaddingTopState });
281
+ refState.current!.props.stylePaddingBottom = stylePaddingBottomState;
282
+
283
+ const paddingDiff = stylePaddingTopState - prevPaddingTop;
284
+ // If the style padding has changed then adjust the paddingTop and update scroll to compensate
285
+ // Only iOS seems to need the scroll compensation
286
+ if (paddingDiff && prevPaddingTop !== undefined && Platform.OS === "ios") {
287
+ calculateItemsInView(ctx, state, { doMVCP: true });
288
+ requestAdjust(ctx, state, paddingDiff);
289
+ }
290
+ };
291
+ if (isFirst) {
292
+ initalizeStateVars();
293
+ updateAllPositions(ctx, state);
294
+ }
295
+ const initialContentOffset = useMemo(() => {
296
+ const initialContentOffset = initialScrollOffset || calculateOffsetForIndex(ctx, state, initialScrollIndex);
297
+ refState.current!.isStartReached =
298
+ initialContentOffset < refState.current!.scrollLength * onStartReachedThreshold!;
299
+
300
+ if (initialContentOffset > 0) {
301
+ scrollTo(state, { offset: initialContentOffset, animated: false, index: initialScrollIndex });
302
+ }
303
+
304
+ return initialContentOffset;
305
+ }, [renderNum]);
306
+
307
+ if (isFirst || didDataChange || numColumnsProp !== peek$(ctx, "numColumns")) {
308
+ refState.current.lastBatchingAction = Date.now();
309
+ if (!keyExtractorProp && !isFirst && didDataChange) {
310
+ __DEV__ &&
311
+ warnDevOnce(
312
+ "keyExtractor",
313
+ "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior.",
314
+ );
315
+ // If we have no keyExtractor then we have no guarantees about previous item sizes so we have to reset
316
+ refState.current.sizes.clear();
317
+ refState.current.positions.clear();
318
+ }
319
+ }
320
+
321
+ useLayoutEffect(() => {
322
+ if (IsNewArchitecture) {
323
+ let measured: LayoutRectangle;
324
+ (refScroller.current as unknown as View).measure((x, y, width, height) => {
325
+ measured = { x, y, width, height };
326
+ });
327
+ if (measured!) {
328
+ const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
329
+
330
+ if (size) {
331
+ handleLayout(ctx, state, measured, setCanRender);
332
+ }
333
+ }
334
+ }
335
+ if (!isFirst) {
336
+ calculateItemsInView(ctx, state, { doMVCP: true });
337
+ }
338
+ }, [dataProp]);
339
+
340
+ const onLayoutHeader = useCallback((rect: LayoutRectangle, fromLayoutEffect: boolean) => {
341
+ const size = rect[horizontal ? "width" : "height"];
342
+ set$(ctx, "headerSize", size);
343
+
344
+ if (initialScroll) {
345
+ if (IsNewArchitecture && Platform.OS !== "android") {
346
+ if (fromLayoutEffect) {
347
+ setRenderNum((v) => v + 1);
348
+ }
349
+ } else {
350
+ setTimeout(() => {
351
+ scrollToIndex(ctx, state, { ...initialScroll, animated: false });
352
+ }, 17);
353
+ }
354
+ }
355
+ }, []);
356
+
357
+ useLayoutEffect(() => {
358
+ const didAllocateContainers = doInitialAllocateContainersCallback();
359
+ if (!didAllocateContainers) {
360
+ checkResetContainers(/*isFirst*/ isFirst);
361
+ }
362
+ }, [dataProp, numColumnsProp]);
363
+
364
+ useLayoutEffect(() => {
365
+ set$(ctx, "extraData", extraData);
366
+ }, [extraData]);
367
+
368
+ useLayoutEffect(initalizeStateVars, [
369
+ memoizedLastItemKeys.join(","),
370
+ numColumnsProp,
371
+ stylePaddingTopState,
372
+ stylePaddingBottomState,
373
+ ]);
374
+
375
+ const doInitialAllocateContainersCallback = () => {
376
+ return doInitialAllocateContainers(ctx, state);
377
+ };
378
+
379
+ useEffect(() => {
380
+ const viewability = setupViewability({
381
+ viewabilityConfig,
382
+ viewabilityConfigCallbackPairs,
383
+ onViewableItemsChanged,
384
+ });
385
+ state.viewabilityConfigCallbackPairs = viewability;
386
+ state.props.viewabilityConfigCallbackPairs = viewability;
387
+ state.enableScrollForNextCalculateItemsInView = !viewability;
388
+ }, [viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged]);
389
+
390
+ if (!IsNewArchitecture) {
391
+ // Needs to use the initial estimated size on old arch, new arch will come within the useLayoutEffect
392
+ useInit(() => {
393
+ doInitialAllocateContainersCallback();
394
+ });
395
+ }
396
+
397
+ const onLayout = useCallback((event: LayoutChangeEvent) => {
398
+ const layout = event.nativeEvent.layout;
399
+ handleLayout(ctx, state, layout, setCanRender);
400
+
401
+ if (onLayoutProp) {
402
+ onLayoutProp(event);
403
+ }
404
+ }, []);
405
+
406
+ useImperativeHandle(
407
+ forwardedRef,
408
+ () => {
409
+ const scrollIndexIntoView = (options: Parameters<LegendListRef["scrollIndexIntoView"]>[0]) => {
410
+ const state = refState.current;
411
+ if (state) {
412
+ const { index, ...rest } = options;
413
+ const { startNoBuffer, endNoBuffer } = state;
414
+ if (index < startNoBuffer || index > endNoBuffer) {
415
+ const viewPosition = index < startNoBuffer ? 0 : 1;
416
+ scrollToIndex(ctx, state, {
417
+ ...rest,
418
+ viewPosition,
419
+ index,
420
+ });
421
+ }
422
+ }
423
+ };
424
+ return {
425
+ flashScrollIndicators: () => refScroller.current!.flashScrollIndicators(),
426
+ getNativeScrollRef: () => refScroller.current!,
427
+ getScrollableNode: () => refScroller.current!.getScrollableNode(),
428
+ getScrollResponder: () => refScroller.current!.getScrollResponder(),
429
+ getState: () => {
430
+ const state = refState.current;
431
+ return state
432
+ ? {
433
+ contentLength: state.totalSize,
434
+ end: state.endNoBuffer,
435
+ endBuffered: state.endBuffered,
436
+ isAtEnd: state.isAtEnd,
437
+ isAtStart: state.isAtStart,
438
+ scroll: state.scroll,
439
+ scrollLength: state.scrollLength,
440
+ start: state.startNoBuffer,
441
+ startBuffered: state.startBuffered,
442
+ sizes: state.sizesKnown,
443
+ sizeAtIndex: (index: number) => state.sizesKnown.get(getId(state, index))!,
444
+ }
445
+ : ({} as ScrollState);
446
+ },
447
+ scrollIndexIntoView,
448
+ scrollItemIntoView: ({ item, ...props }) => {
449
+ const data = refState.current!.props.data;
450
+ const index = data.indexOf(item);
451
+ if (index !== -1) {
452
+ scrollIndexIntoView({ index, ...props });
453
+ }
454
+ },
455
+ scrollToIndex: (params) => scrollToIndex(ctx, state, params),
456
+ scrollToItem: ({ item, ...props }) => {
457
+ const data = refState.current!.props.data;
458
+ const index = data.indexOf(item);
459
+ if (index !== -1) {
460
+ scrollToIndex(ctx, state, { index, ...props });
461
+ }
462
+ },
463
+ scrollToOffset: (params) => scrollTo(state, params),
464
+ scrollToEnd: (options) => {
465
+ const data = refState.current!.props.data;
466
+ const stylePaddingBottom = refState.current!.props.stylePaddingBottom;
467
+ const index = data.length - 1;
468
+ if (index !== -1) {
469
+ const paddingBottom = stylePaddingBottom || 0;
470
+ const footerSize = peek$(ctx, "footerSize") || 0;
471
+ scrollToIndex(ctx, state, {
472
+ index,
473
+ viewPosition: 1,
474
+ viewOffset: -paddingBottom - footerSize,
475
+ ...options,
476
+ });
477
+ }
478
+ },
479
+ setVisibleContentAnchorOffset: (value: number | ((value: number) => number)) => {
480
+ const val = typeof value === "function" ? value(peek$(ctx, "scrollAdjustUserOffset") || 0) : value;
481
+ set$(ctx, "scrollAdjustUserOffset", val);
482
+ },
483
+ };
484
+ },
485
+ [],
486
+ );
487
+
488
+ if (Platform.OS === "web") {
489
+ useEffect(() => {
490
+ if (initialContentOffset) {
491
+ scrollTo(state, { offset: initialContentOffset, animated: false });
492
+ }
493
+ }, []);
494
+ }
495
+
496
+ const fns = useMemo(
497
+ () => ({
498
+ updateItemSize: (itemKey: string, sizeObj: { width: number; height: number }) =>
499
+ updateItemSize(ctx, state, itemKey, sizeObj),
500
+ getRenderedItem: (key: string) => getRenderedItem(ctx, state, key),
501
+ onScroll: (event: { nativeEvent: NativeScrollEvent }) => onScroll(ctx, state, event),
502
+ }),
503
+ [],
504
+ );
505
+
506
+ return (
507
+ <>
508
+ <ListComponent
509
+ {...rest}
510
+ canRender={canRender}
511
+ horizontal={horizontal!}
512
+ refScrollView={combinedRef}
513
+ initialContentOffset={initialContentOffset}
514
+ getRenderedItem={fns.getRenderedItem}
515
+ updateItemSize={fns.updateItemSize}
516
+ onScroll={fns.onScroll}
517
+ onMomentumScrollEnd={(event) => {
518
+ requestAnimationFrame(() => {
519
+ finishScrollTo(refState.current);
520
+ });
521
+
522
+ if (onMomentumScrollEnd) {
523
+ onMomentumScrollEnd(event);
524
+ }
525
+ }}
526
+ onLayout={onLayout}
527
+ recycleItems={recycleItems}
528
+ alignItemsAtEnd={alignItemsAtEnd}
529
+ ListEmptyComponent={dataProp.length === 0 ? ListEmptyComponent : undefined}
530
+ ListHeaderComponent={ListHeaderComponent}
531
+ maintainVisibleContentPosition={maintainVisibleContentPosition}
532
+ scrollEventThrottle={Platform.OS === "web" ? 16 : undefined}
533
+ waitForInitialLayout={waitForInitialLayout}
534
+ refreshControl={
535
+ refreshControl
536
+ ? stylePaddingTopState > 0
537
+ ? React.cloneElement(refreshControl, {
538
+ progressViewOffset:
539
+ (refreshControl.props.progressViewOffset || 0) + stylePaddingTopState,
540
+ })
541
+ : refreshControl
542
+ : onRefresh && (
543
+ <RefreshControl
544
+ refreshing={!!refreshing}
545
+ onRefresh={onRefresh}
546
+ progressViewOffset={(progressViewOffset || 0) + stylePaddingTopState}
547
+ />
548
+ )
549
+ }
550
+ style={style}
551
+ contentContainerStyle={contentContainerStyle}
552
+ scrollAdjustHandler={refState.current?.scrollAdjustHandler}
553
+ onLayoutHeader={onLayoutHeader}
554
+ />
555
+ {__DEV__ && ENABLE_DEBUG_VIEW && <DebugView state={refState.current!} />}
556
+ </>
557
+ );
558
+ });