@legendapp/list 2.0.0-next.1 → 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 (260) 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} +16 -10
  17. package/{index.d.ts → dist/index.d.ts} +16 -10
  18. package/{index.js → dist/index.js} +52 -32
  19. package/{index.mjs → dist/index.mjs} +52 -32
  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/{animated.js → dist/animated.js} +0 -0
  254. /package/{animated.mjs → dist/animated.mjs} +0 -0
  255. /package/{keyboard-controller.js → dist/keyboard-controller.js} +0 -0
  256. /package/{keyboard-controller.mjs → dist/keyboard-controller.mjs} +0 -0
  257. /package/{reanimated.d.mts → dist/reanimated.d.mts} +0 -0
  258. /package/{reanimated.d.ts → dist/reanimated.d.ts} +0 -0
  259. /package/{reanimated.js → dist/reanimated.js} +0 -0
  260. /package/{reanimated.mjs → dist/reanimated.mjs} +0 -0
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "list-test",
3
+ "license": "MIT",
4
+ "main": "expo-router/entry",
5
+ "version": "1.0.0",
6
+ "scripts": {
7
+ "start": "expo start",
8
+ "android": "expo run:android",
9
+ "android:release": "RELEASE=TRUE expo run:android --variant release",
10
+ "android-o": "OLD_ARCH=TRUE expo run:android",
11
+ "android-o:release": "RELEASE=TRUE OLD_ARCH=TRUE expo run:android --variant release",
12
+ "ios": "expo run:ios",
13
+ "ios-o": "OLD_ARCH=TRUE expo run:ios",
14
+ "web": "expo start --web",
15
+ "test": "jest --watchAll",
16
+ "lint": "expo lint",
17
+ "prebuild:clean": "rm -rf ios android && npx expo prebuild --clean && npx expo prebuild",
18
+ "prebuild-o:clean": "rm -rf ios android && OLD_ARCH=TRUE npx expo prebuild --clean && OLD_ARCH=TRUE npx expo prebuild"
19
+ },
20
+ "jest": {
21
+ "preset": "jest-expo"
22
+ },
23
+ "dependencies": {
24
+ "@expo/vector-icons": "^14.1.0",
25
+ "@legendapp/motion": "^2.4.0",
26
+ "@legendapp/state": "^3.0.0-beta.19",
27
+ "@react-navigation/bottom-tabs": "^7.3.10",
28
+ "@react-navigation/native": "^7.1.6",
29
+ "@shopify/flash-list": "1.7.6",
30
+ "babel-plugin-react-compiler": "^19.1.0-rc.2",
31
+ "countries-list": "^3.1.1",
32
+ "expo": "^53.0.11",
33
+ "expo-atlas": "^0.4.0",
34
+ "expo-blur": "~14.1.5",
35
+ "expo-constants": "~17.1.6",
36
+ "expo-dev-client": "~5.2.0",
37
+ "expo-font": "~13.3.1",
38
+ "expo-haptics": "~14.1.4",
39
+ "expo-linear-gradient": "~14.1.5",
40
+ "expo-linking": "~7.1.5",
41
+ "expo-router": "~5.1.0",
42
+ "expo-splash-screen": "~0.30.9",
43
+ "expo-status-bar": "~2.2.3",
44
+ "expo-symbols": "~0.4.5",
45
+ "expo-system-ui": "~5.0.8",
46
+ "expo-web-browser": "~14.1.6",
47
+ "react": "19.0.0",
48
+ "react-compiler-runtime": "^19.1.0-rc.2",
49
+ "react-dom": "19.0.0",
50
+ "react-native": "0.79.3",
51
+ "react-native-gesture-handler": "~2.24.0",
52
+ "react-native-keyboard-controller": "^1.17.4",
53
+ "react-native-reanimated": "~3.17.4",
54
+ "react-native-redash": "^18.1.3",
55
+ "react-native-safe-area-context": "5.4.0",
56
+ "react-native-screens": "~4.11.1",
57
+ "react-native-web": "^0.20.0",
58
+ "react-native-webview": "13.13.5"
59
+ },
60
+ "devDependencies": {
61
+ "@babel/core": "^7.27.4",
62
+ "@react-native-community/cli": "latest",
63
+ "@types/expo__vector-icons": "^10.0.2",
64
+ "@types/jest": "^29.5.14",
65
+ "@types/react": "~19.0.10",
66
+ "@types/react-test-renderer": "^19.0.0",
67
+ "jest": "~29.7.0",
68
+ "jest-expo": "~53.0.7",
69
+ "react-test-renderer": "19.0.0",
70
+ "typescript": "~5.8.3"
71
+ },
72
+ "private": true
73
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * This script is used to reset the project to a blank state.
5
+ * It moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.
6
+ * You can remove the `reset-project` script from package.json and safely delete this file after running it.
7
+ */
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+
12
+ const root = process.cwd();
13
+ const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
14
+ const newDir = "app-example";
15
+ const newAppDir = "app";
16
+ const newDirPath = path.join(root, newDir);
17
+
18
+ const indexContent = `import { Text, View } from "react-native";
19
+
20
+ export default function Index() {
21
+ return (
22
+ <View
23
+ style={{
24
+ flex: 1,
25
+ justifyContent: "center",
26
+ alignItems: "center",
27
+ }}
28
+ >
29
+ <Text>Edit app/index.tsx to edit this screen.</Text>
30
+ </View>
31
+ );
32
+ }
33
+ `;
34
+
35
+ const layoutContent = `import { Stack } from "expo-router";
36
+
37
+ export default function RootLayout() {
38
+ return <Stack />;
39
+ }
40
+ `;
41
+
42
+ const moveDirectories = async () => {
43
+ try {
44
+ // Create the app-example directory
45
+ await fs.promises.mkdir(newDirPath, { recursive: true });
46
+ console.log(`📁 /${newDir} directory created.`);
47
+
48
+ // Move old directories to new app-example directory
49
+ for (const dir of oldDirs) {
50
+ const oldDirPath = path.join(root, dir);
51
+ const newDirPath = path.join(root, newDir, dir);
52
+ if (fs.existsSync(oldDirPath)) {
53
+ await fs.promises.rename(oldDirPath, newDirPath);
54
+ console.log(`➡️ /${dir} moved to /${newDir}/${dir}.`);
55
+ } else {
56
+ console.log(`➡️ /${dir} does not exist, skipping.`);
57
+ }
58
+ }
59
+
60
+ // Create new /app directory
61
+ const newAppDirPath = path.join(root, newAppDir);
62
+ await fs.promises.mkdir(newAppDirPath, { recursive: true });
63
+ console.log("\n📁 New /app directory created.");
64
+
65
+ // Create index.tsx
66
+ const indexPath = path.join(newAppDirPath, "index.tsx");
67
+ await fs.promises.writeFile(indexPath, indexContent);
68
+ console.log("📄 app/index.tsx created.");
69
+
70
+ // Create _layout.tsx
71
+ const layoutPath = path.join(newAppDirPath, "_layout.tsx");
72
+ await fs.promises.writeFile(layoutPath, layoutContent);
73
+ console.log("📄 app/_layout.tsx created.");
74
+
75
+ console.log("\n✅ Project reset complete. Next steps:");
76
+ console.log(
77
+ "1. Run `npx expo start` to start a development server.\n2. Edit app/index.tsx to edit the main screen.\n3. Delete the /app-example directory when you're done referencing it."
78
+ );
79
+ } catch (error) {
80
+ console.error(`Error during script execution: ${error}`);
81
+ }
82
+ };
83
+
84
+ moveDirectories();
@@ -0,0 +1,26 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "paths": {
6
+ "@/*": [
7
+ "./*"
8
+ ],
9
+ "@legendapp/list": [
10
+ "../src/index"
11
+ ],
12
+ "@legendapp/list/keyboard-controller": [
13
+ "../src/keyboard-controller"
14
+ ],
15
+ "@legendapp/list/reanimated": [
16
+ "../src/reanimated"
17
+ ]
18
+ }
19
+ },
20
+ "include": [
21
+ "**/*.ts",
22
+ "**/*.tsx",
23
+ ".expo/types/**/*.ts",
24
+ "expo-env.d.ts"
25
+ ],
26
+ }
package/package.json CHANGED
@@ -1,35 +1,89 @@
1
1
  {
2
- "name": "@legendapp/list",
3
- "version": "2.0.0-next.1",
4
- "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
- "sideEffects": false,
6
- "private": false,
7
- "main": "./index.js",
8
- "module": "./index.mjs",
9
- "types": "./index.d.ts",
10
- "files": [
11
- "**"
12
- ],
13
- "peerDependencies": {
14
- "react": "*",
15
- "react-native": "*"
16
- },
17
- "author": "Legend <contact@legendapp.com> (https://github.com/LegendApp)",
18
- "keywords": [
19
- "react",
20
- "react-native",
21
- "list"
22
- ],
23
- "repository": "github:LegendApp/legend-list",
24
- "license": "MIT",
25
- "bugs": {
26
- "url": "https://github.com/LegendApp/legend-list/issues"
27
- },
28
- "homepage": "https://github.com/LegendApp/legend-list#readme",
29
- "publishConfig": {
30
- "registry": "https://registry.npmjs.org/"
31
- },
32
- "dependencies": {
33
- "use-sync-external-store": "^1.5.0"
34
- }
35
- }
2
+ "name": "@legendapp/list",
3
+ "version": "2.0.0-next.2",
4
+ "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
+ "sideEffects": false,
6
+ "private": false,
7
+ "main": "./index.js",
8
+ "module": "./index.mjs",
9
+ "types": "./index.d.ts",
10
+ "files": [
11
+ "**"
12
+ ],
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ ".": {
16
+ "types": "./index.d.ts",
17
+ "import": "./index.mjs",
18
+ "require": "./index.js"
19
+ },
20
+ "./animated": {
21
+ "types": "./animated.d.ts",
22
+ "import": "./animated.mjs",
23
+ "require": "./animated.js"
24
+ },
25
+ "./reanimated": {
26
+ "types": "./reanimated.d.ts",
27
+ "import": "./reanimated.mjs",
28
+ "require": "./reanimated.js"
29
+ },
30
+ "./keyboard-controller": {
31
+ "types": "./keyboard-controller.d.ts",
32
+ "import": "./keyboard-controller.mjs",
33
+ "require": "./keyboard-controller.js"
34
+ }
35
+ },
36
+ "engines": {
37
+ "node": ">=16.6.0",
38
+ "npm": ">=8.11.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup && bun run posttsup.ts",
42
+ "publish": "bun run build && cd dist && npm publish",
43
+ "publish:next": "bun run build && cd dist && npm publish --tag next",
44
+ "publish:beta": "bun run build && cd dist && npm publish --tag beta",
45
+ "lint": "bunx biome check ./src && bunx biome format ./src && bun run lint:example",
46
+ "lint:example": "bunx biome check ./example/app && bunx biome format ./example/app",
47
+ "lint:fix": "bunx biome lint --write ./src && bunx biome format --write ./src && bunx biome check --write ./src && bun run lint:fix:example",
48
+ "lint:fix:example": "bunx biome lint --write ./example/app && bunx biome format --write ./example/app && bunx biome check --write ./example/app"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "*",
52
+ "react-native": "*"
53
+ },
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^1.9.4",
56
+ "@types/bun": "^1.1.13",
57
+ "@types/react": "^18.3.12",
58
+ "@types/use-sync-external-store": "^1.5.0",
59
+ "react": "^18.3.1",
60
+ "react-native": "^0.76.2",
61
+ "react-native-keyboard-controller": "^1.17.0",
62
+ "react-native-reanimated": "^3.16.6",
63
+ "tsup": "^8.3.5",
64
+ "typescript": "^5.8.3"
65
+ },
66
+ "author": "Legend <contact@legendapp.com> (https://github.com/LegendApp)",
67
+ "keywords": [
68
+ "react",
69
+ "react-native",
70
+ "list"
71
+ ],
72
+ "repository": "github:LegendApp/legend-list",
73
+ "license": "MIT",
74
+ "bugs": {
75
+ "url": "https://github.com/LegendApp/legend-list/issues"
76
+ },
77
+ "homepage": "https://github.com/LegendApp/legend-list#readme",
78
+ "publishConfig": {
79
+ "registry": "https://registry.npmjs.org/"
80
+ },
81
+ "commitlint": {
82
+ "extends": [
83
+ "@commitlint/config-conventional"
84
+ ]
85
+ },
86
+ "dependencies": {
87
+ "use-sync-external-store": "^1.5.0"
88
+ }
89
+ }
package/posttsup.ts ADDED
@@ -0,0 +1,24 @@
1
+ import pkg from './package.json';
2
+
3
+ async function copy(...files: string[]) {
4
+ return files.map((file) => Bun.write('dist/' + file.replace('src/', ''), Bun.file(file), { createPath: true }));
5
+ }
6
+
7
+ copy('LICENSE', 'CHANGELOG.md', 'README.md');
8
+
9
+ const exports: Record<string, string | { import?: string; require?: string; types: string }> = {
10
+ './package.json': './package.json',
11
+ };
12
+
13
+ const pkgOut = pkg as Record<string, any>;
14
+
15
+ pkg.private = false;
16
+ pkgOut.exports = exports;
17
+ delete pkgOut.devDependencies;
18
+ delete pkgOut.overrides;
19
+ delete pkgOut.scripts;
20
+ delete pkgOut.engines;
21
+ delete pkgOut.exports;
22
+ delete pkgOut.commitlint;
23
+
24
+ Bun.write('dist/package.json', JSON.stringify(pkg, undefined, 2));
@@ -0,0 +1,176 @@
1
+ // biome-ignore lint/style/useImportType: Leaving this out makes it crash in some environments
2
+ import * as React from "react";
3
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import type { DimensionValue, LayoutChangeEvent, StyleProp, View, ViewStyle } from "react-native";
5
+ import { ContextContainer, type ContextContainerType } from "./ContextContainer";
6
+ import { LeanView } from "./LeanView";
7
+ import { Separator } from "./Separator";
8
+ import { IsNewArchitecture, POSITION_OUT_OF_VIEW } from "./constants";
9
+ import { isNullOrUndefined } from "./helpers";
10
+ import { useArr$, useStateContext } from "./state";
11
+ import type { GetRenderedItem } from "./types";
12
+
13
+ export const Container = <ItemT,>({
14
+ id,
15
+ recycleItems,
16
+ horizontal,
17
+ getRenderedItem,
18
+ updateItemSize,
19
+ ItemSeparatorComponent,
20
+ }: {
21
+ id: number;
22
+ recycleItems?: boolean;
23
+ horizontal: boolean;
24
+ getRenderedItem: GetRenderedItem;
25
+ updateItemSize: (itemKey: string, size: { width: number; height: number }) => void;
26
+ ItemSeparatorComponent?: React.ComponentType<{ leadingItem: ItemT }>;
27
+ }) => {
28
+ const ctx = useStateContext();
29
+ const columnWrapperStyle = ctx.columnWrapperStyle;
30
+
31
+ const [column = 0, data, itemKey, position = POSITION_OUT_OF_VIEW, numColumns, extraData] = useArr$([
32
+ `containerColumn${id}`,
33
+ `containerItemData${id}`,
34
+ `containerItemKey${id}`,
35
+ `containerPosition${id}`,
36
+ "numColumns",
37
+ "extraData",
38
+ ]);
39
+
40
+ const refLastSize = useRef<{ width: number; height: number }>();
41
+ const ref = useRef<View>(null);
42
+ const [layoutRenderCount, forceLayoutRender] = useState(0);
43
+
44
+ const otherAxisPos: DimensionValue | undefined = numColumns > 1 ? `${((column - 1) / numColumns) * 100}%` : 0;
45
+ const otherAxisSize: DimensionValue | undefined = numColumns > 1 ? `${(1 / numColumns) * 100}%` : undefined;
46
+ let didLayout = false;
47
+
48
+ let paddingStyles: ViewStyle | undefined;
49
+ if (columnWrapperStyle) {
50
+ // Extract gap properties from columnWrapperStyle if available
51
+ const { columnGap, rowGap, gap } = columnWrapperStyle;
52
+
53
+ // Create padding styles for both horizontal and vertical layouts with multiple columns
54
+ if (horizontal) {
55
+ paddingStyles = {
56
+ paddingRight: columnGap || gap || undefined,
57
+ paddingVertical: numColumns > 1 ? (rowGap || gap || 0) / 2 : undefined,
58
+ };
59
+ } else {
60
+ paddingStyles = {
61
+ paddingBottom: rowGap || gap || undefined,
62
+ paddingHorizontal: numColumns > 1 ? (columnGap || gap || 0) / 2 : undefined,
63
+ };
64
+ }
65
+ }
66
+
67
+ const style: StyleProp<ViewStyle> = horizontal
68
+ ? {
69
+ flexDirection: ItemSeparatorComponent ? "row" : undefined,
70
+ position: "absolute",
71
+ top: otherAxisPos,
72
+ height: otherAxisSize,
73
+ left: position,
74
+ ...(paddingStyles || {}),
75
+ }
76
+ : {
77
+ position: "absolute",
78
+ left: otherAxisPos,
79
+ right: numColumns > 1 ? null : 0,
80
+ width: otherAxisSize,
81
+ top: position,
82
+ ...(paddingStyles || {}),
83
+ };
84
+
85
+ const renderedItemInfo = useMemo(
86
+ () => (itemKey !== undefined ? getRenderedItem(itemKey) : null),
87
+ [itemKey, data, extraData],
88
+ );
89
+ const { index, renderedItem } = renderedItemInfo || {};
90
+
91
+ const triggerLayout = useCallback(() => {
92
+ forceLayoutRender((v) => v + 1);
93
+ }, []);
94
+
95
+ const contextValue = useMemo<ContextContainerType>(() => {
96
+ ctx.viewRefs.set(id, ref);
97
+ return { containerId: id, itemKey, index: index!, value: data, triggerLayout };
98
+ }, [id, itemKey, index, data]);
99
+
100
+ const onLayout = (event: LayoutChangeEvent) => {
101
+ if (!isNullOrUndefined(itemKey)) {
102
+ didLayout = true;
103
+ let layout: { width: number; height: number } = event.nativeEvent.layout;
104
+ const size = layout[horizontal ? "width" : "height"];
105
+
106
+ const doUpdate = () => {
107
+ refLastSize.current = { width: layout.width, height: layout.height };
108
+ updateItemSize(itemKey, layout);
109
+ };
110
+
111
+ if (IsNewArchitecture || size > 0) {
112
+ doUpdate();
113
+ } else {
114
+ // On old architecture, the size can be 0 sometimes, maybe when not fully rendered?
115
+ // So we need to make sure it's actually rendered and measure it to make sure it's actually 0.
116
+ ref.current?.measure?.((x, y, width, height) => {
117
+ layout = { width, height };
118
+ doUpdate();
119
+ });
120
+ }
121
+ }
122
+ };
123
+
124
+ if (IsNewArchitecture) {
125
+ // New architecture supports unstable_getBoundingClientRect for getting layout synchronously
126
+ useLayoutEffect(() => {
127
+ if (!isNullOrUndefined(itemKey)) {
128
+ // @ts-expect-error unstable_getBoundingClientRect is unstable and only on Fabric
129
+ const measured = ref.current?.unstable_getBoundingClientRect?.();
130
+ if (measured) {
131
+ const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
132
+
133
+ if (size) {
134
+ updateItemSize(itemKey, measured);
135
+ }
136
+ }
137
+ }
138
+ }, [itemKey, layoutRenderCount]);
139
+ } else {
140
+ // Since old architecture cannot use unstable_getBoundingClientRect it needs to ensure that
141
+ // all containers updateItemSize even if the container did not resize.
142
+ useEffect(() => {
143
+ // Catch a bug where a container is reused and is the exact same size as the previous item
144
+ // so it does not fire an onLayout, so we need to trigger it manually.
145
+ // TODO: There must be a better way to do this?
146
+ if (!isNullOrUndefined(itemKey)) {
147
+ const timeout = setTimeout(() => {
148
+ if (!didLayout && refLastSize.current) {
149
+ updateItemSize(itemKey, refLastSize.current);
150
+ }
151
+ }, 16);
152
+ return () => {
153
+ clearTimeout(timeout);
154
+ };
155
+ }
156
+ }, [itemKey]);
157
+ }
158
+
159
+ // Use a reactive View to ensure the container element itself
160
+ // is not rendered when style changes, only the style prop.
161
+ // This is a big perf boost to do less work rendering.
162
+ return (
163
+ <LeanView style={style} onLayout={onLayout} ref={ref} key={recycleItems ? undefined : itemKey}>
164
+ <ContextContainer.Provider value={contextValue}>
165
+ {renderedItem}
166
+ {renderedItemInfo && ItemSeparatorComponent && (
167
+ <Separator
168
+ itemKey={itemKey}
169
+ ItemSeparatorComponent={ItemSeparatorComponent}
170
+ leadingItem={renderedItemInfo.item}
171
+ />
172
+ )}
173
+ </ContextContainer.Provider>
174
+ </LeanView>
175
+ );
176
+ };
@@ -0,0 +1,85 @@
1
+ // biome-ignore lint/style/useImportType: Leaving this out makes it crash in some environments
2
+ import * as React from "react";
3
+ import { Animated, type StyleProp, type ViewStyle } from "react-native";
4
+ import { Container } from "./Container";
5
+ import { IsNewArchitecture } from "./constants";
6
+ import { useArr$, useStateContext } from "./state";
7
+ import { type GetRenderedItem, typedMemo } from "./types";
8
+ import { useValue$ } from "./useValue$";
9
+
10
+ interface ContainersProps<ItemT> {
11
+ horizontal: boolean;
12
+ recycleItems: boolean;
13
+ ItemSeparatorComponent?: React.ComponentType<{ leadingItem: ItemT }>;
14
+ waitForInitialLayout: boolean | undefined;
15
+ updateItemSize: (itemKey: string, size: { width: number; height: number }) => void;
16
+ getRenderedItem: GetRenderedItem;
17
+ }
18
+
19
+ export const Containers = typedMemo(function Containers<ItemT>({
20
+ horizontal,
21
+ recycleItems,
22
+ ItemSeparatorComponent,
23
+ waitForInitialLayout,
24
+ updateItemSize,
25
+ getRenderedItem,
26
+ }: ContainersProps<ItemT>) {
27
+ const ctx = useStateContext();
28
+ const columnWrapperStyle = ctx.columnWrapperStyle;
29
+ const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
30
+ const animSize = useValue$("totalSize", {
31
+ // Use a microtask if increasing the size significantly, otherwise use a timeout
32
+ delay: (value, prevValue) => (!prevValue || value - prevValue > 20 ? 0 : 200),
33
+ });
34
+ const animOpacity =
35
+ waitForInitialLayout && !IsNewArchitecture
36
+ ? useValue$("containersDidLayout", { getValue: (value) => (value ? 1 : 0) })
37
+ : undefined;
38
+ const otherAxisSize = useValue$("otherAxisSize", { delay: 0 });
39
+
40
+ const containers: React.ReactNode[] = [];
41
+ for (let i = 0; i < numContainers; i++) {
42
+ containers.push(
43
+ <Container
44
+ id={i}
45
+ key={i}
46
+ recycleItems={recycleItems}
47
+ horizontal={horizontal}
48
+ getRenderedItem={getRenderedItem}
49
+ updateItemSize={updateItemSize}
50
+ // specifying inline separator makes Containers rerender on each data change
51
+ // should we do memo of ItemSeparatorComponent?
52
+ ItemSeparatorComponent={ItemSeparatorComponent}
53
+ />,
54
+ );
55
+ }
56
+
57
+ const style: StyleProp<ViewStyle> = horizontal
58
+ ? { width: animSize, opacity: animOpacity, minHeight: otherAxisSize }
59
+ : { height: animSize, opacity: animOpacity, minWidth: otherAxisSize };
60
+
61
+ if (columnWrapperStyle && numColumns > 1) {
62
+ // Extract gap properties from columnWrapperStyle if available
63
+ const { columnGap, rowGap, gap } = columnWrapperStyle;
64
+
65
+ const gapX = columnGap || gap || 0;
66
+ const gapY = rowGap || gap || 0;
67
+ if (horizontal) {
68
+ if (gapY) {
69
+ style.marginVertical = -gapY / 2;
70
+ }
71
+ if (gapX) {
72
+ style.marginRight = -gapX;
73
+ }
74
+ } else {
75
+ if (gapX) {
76
+ style.marginHorizontal = -gapX;
77
+ }
78
+ if (gapY) {
79
+ style.marginBottom = -gapY;
80
+ }
81
+ }
82
+ }
83
+
84
+ return <Animated.View style={style}>{containers}</Animated.View>;
85
+ });