react-native-list 1.0.1 → 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.
Files changed (224) hide show
  1. package/README.md +186 -32
  2. package/ReactNativeList.podspec +39 -0
  3. package/android/CMakeLists.txt +48 -0
  4. package/android/build.gradle +151 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/JHybridUiListModule.cpp +192 -0
  9. package/android/src/main/cpp/JHybridUiListModule.h +50 -0
  10. package/android/src/main/cpp/cpp-adapter.cpp +12 -0
  11. package/android/src/main/java/com/hannojg/reactnativelist/ReactNativeListPackage.kt +27 -0
  12. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListDataSource.kt +146 -0
  13. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListLayout.kt +86 -0
  14. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListModule.kt +116 -0
  15. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListView.kt +410 -0
  16. package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridViewHolder.kt +9 -0
  17. package/android/src/main/java/com/margelo/nitro/reactnativelist/NativeListAdapter.kt +217 -0
  18. package/ios/DataSource/HybridNativeListDataSource.swift +213 -0
  19. package/ios/HybridObjects/HybridUiListModule.swift +49 -0
  20. package/ios/HybridObjects/HybridViewHolder.swift +16 -0
  21. package/ios/Layout/HybridNativeListLayout.swift +128 -0
  22. package/ios/Utils/ErrorUtils.h +26 -0
  23. package/ios/Utils/HybridIOSWorkletsModuleProxyHolder.swift +10 -0
  24. package/ios/Utils/SurfaceHelper.h +20 -0
  25. package/ios/Utils/SurfaceHelper.mm +144 -0
  26. package/ios/Utils/SurfacePresenterRegistry.h +17 -0
  27. package/ios/Utils/SurfacePresenterRegistry.m +31 -0
  28. package/ios/Utils/TurboModuleInstaller.h +18 -0
  29. package/ios/Utils/TurboModuleInstaller.mm +267 -0
  30. package/ios/Views/HostCell.swift +216 -0
  31. package/ios/Views/HybridUiListView.swift +695 -0
  32. package/lib/ReactFabricMirror.d.ts +4 -0
  33. package/lib/ReactFabricMirror.js +515 -0
  34. package/lib/UiListModule.d.ts +2 -0
  35. package/lib/UiListModule.js +2 -0
  36. package/lib/index.d.ts +8 -0
  37. package/lib/index.js +21 -0
  38. package/lib/privateGlobals.d.ts +14 -0
  39. package/lib/privateGlobals.js +2 -0
  40. package/lib/renderer/RenderHelper.d.ts +2 -0
  41. package/lib/renderer/RenderHelper.js +11 -0
  42. package/lib/renderer/UiManagerHelper.d.ts +2 -0
  43. package/lib/renderer/UiManagerHelper.js +2 -0
  44. package/lib/renderer/fabric/RenderHelper.d.ts +2 -0
  45. package/lib/renderer/fabric/RenderHelper.js +11 -0
  46. package/lib/renderer/fabric/UiManagerHelper.d.ts +2 -0
  47. package/lib/renderer/fabric/UiManagerHelper.js +2 -0
  48. package/lib/renderer/react/ReactFabricMirror.d.ts +4 -0
  49. package/lib/renderer/react/ReactFabricMirror.js +515 -0
  50. package/lib/renderer/react/ReactFabricRenderer.d.ts +3 -0
  51. package/lib/renderer/react/ReactFabricRenderer.js +9 -0
  52. package/lib/specs/IOSWorkletsModuleProxyHolder.nitro.d.ts +6 -0
  53. package/lib/specs/IOSWorkletsModuleProxyHolder.nitro.js +1 -0
  54. package/lib/specs/UIListModule.nitro.d.ts +9 -0
  55. package/lib/specs/UIListModule.nitro.js +1 -0
  56. package/lib/specs/UIManagerHelper.nitro.d.ts +13 -0
  57. package/lib/specs/UIManagerHelper.nitro.js +1 -0
  58. package/lib/specs/UiListView.nitro.d.ts +20 -0
  59. package/lib/specs/UiListView.nitro.js +1 -0
  60. package/lib/specs/ViewHolder.nitro.d.ts +6 -0
  61. package/lib/specs/ViewHolder.nitro.js +1 -0
  62. package/lib/views/List.d.ts +35 -0
  63. package/lib/views/List.js +225 -0
  64. package/lib/views/UiListHostComponent.d.ts +2 -0
  65. package/lib/views/UiListHostComponent.js +3 -0
  66. package/metro/RendererProxyThreadSwitch.js +66 -0
  67. package/metro-config.d.ts +1 -0
  68. package/metro-config.js +124 -0
  69. package/nitro.json +47 -0
  70. package/nitrogen/generated/.gitattributes +1 -0
  71. package/nitrogen/generated/android/ReactNativeList+autolinking.cmake +99 -0
  72. package/nitrogen/generated/android/ReactNativeList+autolinking.gradle +27 -0
  73. package/nitrogen/generated/android/ReactNativeListOnLoad.cpp +156 -0
  74. package/nitrogen/generated/android/ReactNativeListOnLoad.hpp +34 -0
  75. package/nitrogen/generated/android/c++/JFunc_bool_NativeListItem_NativeListItem.hpp +83 -0
  76. package/nitrogen/generated/android/c++/JFunc_bool_double_NativeListItem_double.hpp +83 -0
  77. package/nitrogen/generated/android/c++/JFunc_double_std__string.hpp +78 -0
  78. package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.cpp +49 -0
  79. package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.hpp +63 -0
  80. package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.cpp +63 -0
  81. package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.hpp +65 -0
  82. package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.cpp +101 -0
  83. package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.hpp +70 -0
  84. package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.cpp +49 -0
  85. package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.hpp +63 -0
  86. package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.cpp +65 -0
  87. package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.hpp +64 -0
  88. package/nitrogen/generated/android/c++/JHybridUiListViewSpec.cpp +92 -0
  89. package/nitrogen/generated/android/c++/JHybridUiListViewSpec.hpp +67 -0
  90. package/nitrogen/generated/android/c++/JHybridViewHolderSpec.cpp +49 -0
  91. package/nitrogen/generated/android/c++/JHybridViewHolderSpec.hpp +63 -0
  92. package/nitrogen/generated/android/c++/JNativeItemSizeEstimate.hpp +61 -0
  93. package/nitrogen/generated/android/c++/JNativeLinearListLayoutConfig.hpp +81 -0
  94. package/nitrogen/generated/android/c++/JNativeLinearListLayoutIOSConfig.hpp +59 -0
  95. package/nitrogen/generated/android/c++/JNativeListItem.hpp +76 -0
  96. package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.cpp +26 -0
  97. package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.hpp +72 -0
  98. package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.cpp +53 -0
  99. package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.hpp +49 -0
  100. package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.cpp +53 -0
  101. package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.hpp +49 -0
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_NativeListItem_NativeListItem.kt +80 -0
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_double_NativeListItem_double.kt +80 -0
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_double_std__string.kt +80 -0
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridIOSWorkletsModuleProxyHolderSpec.kt +52 -0
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeLinearListLayoutSpec.kt +54 -0
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListDataSourceSpec.kt +87 -0
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListLayoutSpec.kt +52 -0
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListModuleSpec.kt +59 -0
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListViewSpec.kt +76 -0
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridViewHolderSpec.kt +53 -0
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeItemSizeEstimate.kt +56 -0
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutConfig.kt +76 -0
  114. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutIOSConfig.kt +51 -0
  115. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeListItem.kt +71 -0
  116. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/ReactNativeListOnLoad.kt +35 -0
  117. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Variant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.kt +62 -0
  118. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewManager.kt +80 -0
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewStateUpdater.kt +23 -0
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderManager.kt +80 -0
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderStateUpdater.kt +23 -0
  122. package/nitrogen/generated/ios/ReactNativeList+autolinking.rb +62 -0
  123. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.cpp +162 -0
  124. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.hpp +368 -0
  125. package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Umbrella.hpp +92 -0
  126. package/nitrogen/generated/ios/ReactNativeListAutolinking.mm +83 -0
  127. package/nitrogen/generated/ios/ReactNativeListAutolinking.swift +86 -0
  128. package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.cpp +11 -0
  129. package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.hpp +75 -0
  130. package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.cpp +11 -0
  131. package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.hpp +92 -0
  132. package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.cpp +11 -0
  133. package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.hpp +132 -0
  134. package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.cpp +11 -0
  135. package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.hpp +75 -0
  136. package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.cpp +11 -0
  137. package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.hpp +93 -0
  138. package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.cpp +11 -0
  139. package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.hpp +121 -0
  140. package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.cpp +11 -0
  141. package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.hpp +75 -0
  142. package/nitrogen/generated/ios/c++/views/HybridUiListViewComponent.mm +118 -0
  143. package/nitrogen/generated/ios/c++/views/HybridViewHolderComponent.mm +118 -0
  144. package/nitrogen/generated/ios/swift/Func_bool_NativeListItem_NativeListItem.swift +47 -0
  145. package/nitrogen/generated/ios/swift/Func_bool_double_NativeListItem_double.swift +47 -0
  146. package/nitrogen/generated/ios/swift/Func_double_std__string.swift +47 -0
  147. package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec.swift +55 -0
  148. package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec_cxx.swift +128 -0
  149. package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec.swift +55 -0
  150. package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec_cxx.swift +140 -0
  151. package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec.swift +62 -0
  152. package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec_cxx.swift +222 -0
  153. package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec.swift +55 -0
  154. package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec_cxx.swift +128 -0
  155. package/nitrogen/generated/ios/swift/HybridUiListModuleSpec.swift +56 -0
  156. package/nitrogen/generated/ios/swift/HybridUiListModuleSpec_cxx.swift +175 -0
  157. package/nitrogen/generated/ios/swift/HybridUiListViewSpec.swift +59 -0
  158. package/nitrogen/generated/ios/swift/HybridUiListViewSpec_cxx.swift +227 -0
  159. package/nitrogen/generated/ios/swift/HybridViewHolderSpec.swift +55 -0
  160. package/nitrogen/generated/ios/swift/HybridViewHolderSpec_cxx.swift +147 -0
  161. package/nitrogen/generated/ios/swift/NativeItemSizeEstimate.swift +60 -0
  162. package/nitrogen/generated/ios/swift/NativeLinearListLayoutConfig.swift +60 -0
  163. package/nitrogen/generated/ios/swift/NativeLinearListLayoutIOSConfig.swift +35 -0
  164. package/nitrogen/generated/ios/swift/NativeListItem.swift +75 -0
  165. package/nitrogen/generated/ios/swift/Variant_NullType__any_HybridIOSWorkletsModuleProxyHolderSpec_.swift +30 -0
  166. package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.cpp +21 -0
  167. package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.hpp +62 -0
  168. package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.cpp +22 -0
  169. package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.hpp +67 -0
  170. package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.cpp +28 -0
  171. package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.hpp +72 -0
  172. package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.cpp +21 -0
  173. package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.hpp +62 -0
  174. package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.cpp +22 -0
  175. package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.hpp +68 -0
  176. package/nitrogen/generated/shared/c++/HybridUiListViewSpec.cpp +25 -0
  177. package/nitrogen/generated/shared/c++/HybridUiListViewSpec.hpp +79 -0
  178. package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.cpp +23 -0
  179. package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.hpp +65 -0
  180. package/nitrogen/generated/shared/c++/HybridViewHolderSpec.cpp +21 -0
  181. package/nitrogen/generated/shared/c++/HybridViewHolderSpec.hpp +62 -0
  182. package/nitrogen/generated/shared/c++/NativeItemSizeEstimate.hpp +87 -0
  183. package/nitrogen/generated/shared/c++/NativeLinearListLayoutConfig.hpp +105 -0
  184. package/nitrogen/generated/shared/c++/NativeLinearListLayoutIOSConfig.hpp +85 -0
  185. package/nitrogen/generated/shared/c++/NativeListItem.hpp +101 -0
  186. package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.cpp +72 -0
  187. package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.hpp +109 -0
  188. package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.cpp +72 -0
  189. package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.hpp +109 -0
  190. package/nitrogen/generated/shared/json/UiListViewConfig.json +9 -0
  191. package/nitrogen/generated/shared/json/ViewHolderConfig.json +9 -0
  192. package/package.json +152 -5
  193. package/react-native.config.js +16 -0
  194. package/src/ListDataSource.ts +232 -0
  195. package/src/ListLayout.ts +95 -0
  196. package/src/UiListModule.ts +5 -0
  197. package/src/hooks/useChangeEffect.ts +50 -0
  198. package/src/index.tsx +49 -0
  199. package/src/privateGlobals.ts +20 -0
  200. package/src/renderer/fabric/RenderHelper.ts +29 -0
  201. package/src/renderer/fabric/UiManagerHelper.ts +5 -0
  202. package/src/renderer/react/ReactFabricMirror.bundle.js +1984 -0
  203. package/src/renderer/react/ReactFabricMirror.ts +766 -0
  204. package/src/renderer/react/ReactFabricRenderer.ts +11 -0
  205. package/src/specs/IOSWorkletsModuleProxyHolder.nitro.ts +6 -0
  206. package/src/specs/NativeLinearListLayout.nitro.ts +23 -0
  207. package/src/specs/NativeListDataSource.nitro.ts +28 -0
  208. package/src/specs/NativeListLayout.nitro.ts +6 -0
  209. package/src/specs/UIListModule.nitro.ts +13 -0
  210. package/src/specs/UIManagerHelper.nitro.ts +34 -0
  211. package/src/specs/UiListView.nitro.ts +31 -0
  212. package/src/specs/ViewHolder.nitro.ts +11 -0
  213. package/src/views/List.tsx +525 -0
  214. package/src/views/UiListHostComponent.ts +8 -0
  215. package/FillRateHelper.js +0 -179
  216. package/FlatList.js +0 -494
  217. package/LICENSE.md +0 -31
  218. package/MetroListView.js +0 -166
  219. package/SectionList.js +0 -291
  220. package/ViewabilityHelper.js +0 -260
  221. package/VirtualizeUtils.js +0 -163
  222. package/VirtualizedList.js +0 -861
  223. package/VirtualizedSectionList.js +0 -397
  224. package/index.js +0 -5
@@ -0,0 +1,695 @@
1
+ //
2
+ // HybridUiListView.swift
3
+ // ReactNativeList
4
+ //
5
+ // Created by Hanno Gödecke on 14.02.26.
6
+ //
7
+
8
+ import DifferenceKit
9
+ import Foundation
10
+ import NitroModules
11
+ import UIKit
12
+
13
+ final class CollectionViewDataSourceProxy: NSObject, UICollectionViewDataSource {
14
+ weak var owner: HybridUiListView?
15
+
16
+ init(owner: HybridUiListView) {
17
+ self.owner = owner
18
+ super.init()
19
+ }
20
+
21
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
22
+ return owner?.numberOfSections(in: collectionView) ?? 0
23
+ }
24
+
25
+ func collectionView(
26
+ _ collectionView: UICollectionView,
27
+ numberOfItemsInSection section: Int
28
+ ) -> Int {
29
+ return owner?.collectionView(collectionView, numberOfItemsInSection: section) ?? 0
30
+ }
31
+
32
+ func collectionView(
33
+ _ collectionView: UICollectionView,
34
+ cellForItemAt indexPath: IndexPath
35
+ ) -> UICollectionViewCell {
36
+ guard let owner else {
37
+ return UICollectionViewCell()
38
+ }
39
+ return owner.collectionView(collectionView, cellForItemAt: indexPath)
40
+ }
41
+ }
42
+
43
+ final class CollectionViewDelegateProxy: NSObject, UICollectionViewDelegateFlowLayout {
44
+ weak var owner: HybridUiListView?
45
+
46
+ init(owner: HybridUiListView) {
47
+ self.owner = owner
48
+ super.init()
49
+ }
50
+
51
+ func collectionView(
52
+ _ collectionView: UICollectionView,
53
+ layout collectionViewLayout: UICollectionViewLayout,
54
+ sizeForItemAt indexPath: IndexPath
55
+ ) -> CGSize {
56
+ guard let owner else {
57
+ return .zero
58
+ }
59
+ return owner.collectionView(
60
+ collectionView,
61
+ layout: collectionViewLayout,
62
+ sizeForItemAt: indexPath
63
+ )
64
+ }
65
+ }
66
+
67
+ private struct HostedFabricViewRestorePoint {
68
+ let view: UIView
69
+ let originalSuperview: UIView
70
+ let originalIndex: Int
71
+ }
72
+
73
+ class HybridUiListView : HybridUiListViewSpec {
74
+ let view: UIView
75
+
76
+ private var collectionView: UICollectionView?
77
+ private var collectionDataSourceProxy: CollectionViewDataSourceProxy?
78
+ private var collectionDelegateProxy: CollectionViewDelegateProxy?
79
+ private var dataSource: HybridNativeListDataSource?
80
+ private var layoutProvider: NativeListLayoutProviding = HybridNativeLinearListLayout()
81
+ private var registeredReuseIdentifiers = Set<String>()
82
+ private var measuredContentSizeByItemKey: [String: CGSize] = [:]
83
+ private var hasScheduledLayoutInvalidation = false
84
+ private let measuredSizeTolerance: CGFloat = 0.5
85
+ private var rendererSurfaceId: ReactTag?
86
+ // Fabric unmount asserts that a child is still mounted under its original parent.
87
+ // Cells temporarily host those views, so teardown must restore the parent first.
88
+ private var fabricRestorePointsByReactTag: [ReactTag: HostedFabricViewRestorePoint] = [:]
89
+ private let hostedCells = NSHashTable<HostCell>.weakObjects()
90
+
91
+ private var createViewCallback: ((_ type: String) -> Double)?
92
+ private var updateViewCallback: ((_ reactTag: Double, _ item: NativeListItem, _ index: Double) -> Bool)?
93
+
94
+ override init() {
95
+ view = UIView(frame: .zero)
96
+ super.init()
97
+ }
98
+
99
+ func getSurfaceId() throws -> Double {
100
+ if let rendererSurfaceId {
101
+ return Double(rendererSurfaceId)
102
+ }
103
+
104
+ var createdSurfaceId: ReactTag?
105
+ var capturedError: Error?
106
+
107
+ let createSurface = {
108
+ do {
109
+ let surfaceId = try SurfaceHelper.createExternalSurface()
110
+ createdSurfaceId = ReactTag(truncating: surfaceId)
111
+ } catch {
112
+ capturedError = error
113
+ }
114
+ }
115
+
116
+ if Thread.isMainThread {
117
+ createSurface()
118
+ } else {
119
+ DispatchQueue.main.sync(execute: createSurface)
120
+ }
121
+
122
+ if let capturedError {
123
+ let message = String(describing: capturedError)
124
+ throw RuntimeError.error(withMessage: message)
125
+ }
126
+
127
+ guard let createdSurfaceId else {
128
+ throw RuntimeError.error(withMessage: "Could not create renderer surface.")
129
+ }
130
+
131
+ rendererSurfaceId = createdSurfaceId
132
+ return Double(createdSurfaceId)
133
+ }
134
+
135
+ func disposeRendererSurface() throws {
136
+ let surfaceId = rendererSurfaceId
137
+ rendererSurfaceId = nil
138
+ createViewCallback = nil
139
+ updateViewCallback = nil
140
+
141
+ var capturedError: Error?
142
+
143
+ let disposeSurface = {
144
+ self.dataSource?.observer = nil
145
+ self.dataSource = nil
146
+ self.collectionView?.dataSource = nil
147
+ self.collectionView?.delegate = nil
148
+ // RCTFabricSurface.stop() commits an empty tree. Let Fabric perform that cleanup
149
+ // after the UIKit hierarchy matches Fabric's expected parent-child structure again (otherwise we crash).
150
+ self.restoreFabricViewHierarchy()
151
+ self.collectionView?.removeFromSuperview()
152
+ self.collectionView = nil
153
+ self.collectionDataSourceProxy = nil
154
+ self.collectionDelegateProxy = nil
155
+ self.registeredReuseIdentifiers.removeAll()
156
+ self.measuredContentSizeByItemKey.removeAll()
157
+ self.hasScheduledLayoutInvalidation = false
158
+
159
+ guard let surfaceId else {
160
+ return
161
+ }
162
+
163
+ do {
164
+ _ = try SurfaceHelper.releaseExternalSurface(surfaceId)
165
+ } catch {
166
+ capturedError = error
167
+ }
168
+ }
169
+
170
+ if Thread.isMainThread {
171
+ disposeSurface()
172
+ } else {
173
+ DispatchQueue.main.sync(execute: disposeSurface)
174
+ }
175
+
176
+ if let capturedError {
177
+ let message = String(describing: capturedError)
178
+ throw RuntimeError.error(withMessage: message)
179
+ }
180
+ }
181
+
182
+ func onDropView() {
183
+ do {
184
+ try disposeRendererSurface()
185
+ } catch {
186
+ print("Failed to dispose list renderer surface: \(error)")
187
+ }
188
+ }
189
+
190
+ func setListCallbacks(
191
+ uiListModule: any HybridUiListModuleSpec,
192
+ createView: @escaping (String) -> Double,
193
+ updateView: @escaping (Double, NativeListItem, Double) -> Bool
194
+ ) throws {
195
+ createViewCallback = createView
196
+ updateViewCallback = updateView
197
+ runOnMain { [weak self] in
198
+ self?.configureCollectionViewIfNeeded()
199
+ }
200
+ }
201
+
202
+ func setDataSource(dataSource nextDataSource: any HybridNativeListDataSourceSpec) throws {
203
+ guard let concreteDataSource = nextDataSource as? HybridNativeListDataSource else {
204
+ throw RuntimeError.error(withMessage: "NativeListDataSource must be created by react-native-list.")
205
+ }
206
+
207
+ runOnMain { [weak self] in
208
+ guard let self else { return }
209
+ self.dataSource?.observer = nil
210
+ self.dataSource = concreteDataSource
211
+ concreteDataSource.observer = self
212
+ self.configureCollectionViewIfNeeded()
213
+ self.retainMeasuredContent(in: concreteDataSource)
214
+ self.collectionView?.collectionViewLayout.invalidateLayout()
215
+ self.collectionView?.reloadData()
216
+ }
217
+ }
218
+
219
+ func setLayout(layout: any HybridNativeListLayoutSpec) throws {
220
+ guard let nextLayout = layout as? NativeListLayoutProviding else {
221
+ throw RuntimeError.error(withMessage: "NativeListLayout must provide a platform layout.")
222
+ }
223
+
224
+ runOnMain { [weak self] in
225
+ guard let self else { return }
226
+ layoutProvider = nextLayout
227
+ configureCollectionViewIfNeeded()
228
+ let collectionViewLayout = nextLayout.makeCollectionViewLayout(owner: self)
229
+ collectionView?.setCollectionViewLayout(collectionViewLayout, animated: false)
230
+ updateVisibleCellContentLayouts()
231
+ collectionView?.collectionViewLayout.invalidateLayout()
232
+ }
233
+ }
234
+
235
+ private func configureCollectionViewIfNeeded() {
236
+ guard collectionView == nil else { return }
237
+
238
+ let layout = layoutProvider.makeCollectionViewLayout(owner: self)
239
+ let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
240
+ collectionView.backgroundColor = .clear
241
+ collectionView.isOpaque = false
242
+ collectionView.translatesAutoresizingMaskIntoConstraints = false
243
+
244
+ view.isOpaque = false
245
+ view.addSubview(collectionView)
246
+ NSLayoutConstraint.activate([
247
+ collectionView.topAnchor.constraint(equalTo: view.topAnchor),
248
+ collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
249
+ collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
250
+ collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
251
+ ])
252
+
253
+ let dataSourceProxy = CollectionViewDataSourceProxy(owner: self)
254
+ let delegateProxy = CollectionViewDelegateProxy(owner: self)
255
+ collectionDataSourceProxy = dataSourceProxy
256
+ collectionDelegateProxy = delegateProxy
257
+ collectionView.dataSource = dataSourceProxy
258
+ collectionView.delegate = delegateProxy
259
+ self.collectionView = collectionView
260
+ }
261
+
262
+ private func makeView(type: String) throws -> (UIView, ReactTag) {
263
+ guard let createViewCallback else {
264
+ throw RuntimeError.error(withMessage: "Can only call makeView after setListCallbacks.")
265
+ }
266
+
267
+ let rawViewTag = createViewCallback(type)
268
+ let viewTag = ReactTag(rawViewTag)
269
+ let resolvedView = try SurfaceHelper.getViewByTag(viewTag)
270
+ try recordFabricRestorePoint(for: resolvedView, viewTag: viewTag)
271
+ resolvedView.removeFromSuperview()
272
+ return (resolvedView, viewTag)
273
+ }
274
+
275
+ private func recordFabricRestorePoint(for resolvedView: UIView, viewTag: ReactTag) throws {
276
+ guard let originalSuperview = resolvedView.superview else {
277
+ throw RuntimeError.error(
278
+ withMessage: "Fabric view \(viewTag) must have a superview before being hosted by the list."
279
+ )
280
+ }
281
+
282
+ guard let originalIndex = originalSuperview.subviews.firstIndex(of: resolvedView) else {
283
+ throw RuntimeError.error(
284
+ withMessage: "Fabric view \(viewTag) must be present in its Fabric parent before being hosted by the list."
285
+ )
286
+ }
287
+
288
+ let restorePoint = HostedFabricViewRestorePoint(
289
+ view: resolvedView,
290
+ originalSuperview: originalSuperview,
291
+ originalIndex: originalIndex
292
+ )
293
+ fabricRestorePointsByReactTag[viewTag] = restorePoint
294
+ }
295
+
296
+ private func restoreFabricViewHierarchy() {
297
+ detachHostedCells()
298
+
299
+ let restorePoints = fabricRestorePointsByReactTag.values.sorted { firstPoint, secondPoint in
300
+ return firstPoint.originalIndex < secondPoint.originalIndex
301
+ }
302
+
303
+ for restorePoint in restorePoints {
304
+ restoreFabricView(restorePoint)
305
+ }
306
+
307
+ fabricRestorePointsByReactTag.removeAll()
308
+ }
309
+
310
+ private func detachHostedCells() {
311
+ let cells = hostedCells.allObjects
312
+ for cell in cells {
313
+ cell.detachHostedView()
314
+ }
315
+ hostedCells.removeAllObjects()
316
+ }
317
+
318
+ private func restoreFabricView(_ restorePoint: HostedFabricViewRestorePoint) {
319
+ let hostedView = restorePoint.view
320
+ let originalSuperview = restorePoint.originalSuperview
321
+
322
+ if hostedView.superview === originalSuperview {
323
+ let currentIndex = originalSuperview.subviews.firstIndex(of: hostedView)
324
+ if currentIndex == restorePoint.originalIndex {
325
+ return
326
+ }
327
+ hostedView.removeFromSuperview()
328
+ }
329
+
330
+ precondition(hostedView.superview == nil, "Fabric view must be detached before restoring it.")
331
+
332
+ let subviewCount = originalSuperview.subviews.count
333
+ let insertionIndex: Int
334
+ if restorePoint.originalIndex <= subviewCount {
335
+ insertionIndex = restorePoint.originalIndex
336
+ } else {
337
+ insertionIndex = subviewCount
338
+ }
339
+
340
+ originalSuperview.insertSubview(hostedView, at: insertionIndex)
341
+ }
342
+
343
+ private func measure(view: UIView) -> CGSize? {
344
+ // Fabric has already applied layout metrics after updateView returns.
345
+ // Asking UIKit Auto Layout to resolve this root can collapse it back to zero.
346
+ let measuredWidth = [view.bounds.width, view.frame.width]
347
+ .filter { $0.isFinite && $0 > 0 }
348
+ .max()
349
+ let measuredHeight = [view.bounds.height, view.frame.height]
350
+ .filter { $0.isFinite && $0 > 0 }
351
+ .max()
352
+
353
+ guard let measuredWidth, let measuredHeight else {
354
+ return nil
355
+ }
356
+ return CGSize(width: measuredWidth, height: measuredHeight)
357
+ }
358
+
359
+ private func resolvedContentSize(for item: NativeListItem) -> CGSize {
360
+ let measuredSize = measuredContentSizeByItemKey[item.key]
361
+
362
+ // UICollectionViewFlowLayout asks for item sizes before cells are created.
363
+ // If JS did not provide a dimension and this item has not been measured yet,
364
+ // use a viewport-based estimate for the first pass. Once cellForItemAt binds
365
+ // real content, we measure the Fabric-applied frame and future queries use it.
366
+ let width: CGFloat
367
+ if let itemWidth = item.width {
368
+ width = CGFloat(itemWidth)
369
+ } else if let measuredWidth = measuredSize?.width {
370
+ width = measuredWidth
371
+ } else {
372
+ let collectionViewWidth = collectionView?.bounds.width ?? 0
373
+ width = layoutProvider.estimatedContentWidth(
374
+ collectionViewWidth: collectionViewWidth,
375
+ viewWidth: view.bounds.width
376
+ )
377
+ }
378
+
379
+ let height: CGFloat
380
+ if let itemHeight = item.height {
381
+ height = CGFloat(itemHeight)
382
+ } else if let measuredHeight = measuredSize?.height {
383
+ height = measuredHeight
384
+ } else {
385
+ let collectionViewHeight = collectionView?.bounds.height ?? 0
386
+ height = layoutProvider.estimatedContentHeight(
387
+ collectionViewHeight: collectionViewHeight,
388
+ viewHeight: view.bounds.height
389
+ )
390
+ }
391
+
392
+ return CGSize(width: width, height: height)
393
+ }
394
+
395
+ private func resolvedLayoutSize(for item: NativeListItem) -> CGSize {
396
+ let contentSize = resolvedContentSize(for: item)
397
+ return layoutProvider.layoutSize(contentSize: contentSize)
398
+ }
399
+
400
+ private func resolvedHostedContentSize(for item: NativeListItem) -> CGSize {
401
+ let layoutSize = resolvedLayoutSize(for: item)
402
+ let contentInset = layoutProvider.itemContentInset()
403
+ let width = layoutSize.width - contentInset.left - contentInset.right
404
+ let height = layoutSize.height - contentInset.top - contentInset.bottom
405
+ return CGSize(width: width, height: height)
406
+ }
407
+
408
+ private func captureMeasuredContentSize(for item: NativeListItem, view: UIView) -> Bool {
409
+ guard let measuredSize = measure(view: view) else {
410
+ return false
411
+ }
412
+
413
+ let width = item.width.map { CGFloat($0) } ?? measuredSize.width
414
+ let height = item.height.map { CGFloat($0) } ?? measuredSize.height
415
+ let nextSize = CGSize(width: width, height: height)
416
+ let previousSize = measuredContentSizeByItemKey[item.key]
417
+
418
+ if let previousSize {
419
+ let widthDelta = abs(previousSize.width - nextSize.width)
420
+ let heightDelta = abs(previousSize.height - nextSize.height)
421
+ // Fabric can report tiny fractional differences for the same rendered row.
422
+ // Ignoring sub-point churn avoids full layout invalidations for visual no-ops.
423
+ if widthDelta < measuredSizeTolerance && heightDelta < measuredSizeTolerance {
424
+ return false
425
+ }
426
+ }
427
+
428
+ measuredContentSizeByItemKey[item.key] = nextSize
429
+ return true
430
+ }
431
+
432
+ private func needsMeasuredContentSize(for item: NativeListItem) -> Bool {
433
+ return item.width == nil || item.height == nil
434
+ }
435
+
436
+ private func ensureReuseRegistered(for type: String) {
437
+ guard !registeredReuseIdentifiers.contains(type) else { return }
438
+
439
+ collectionView?.register(HostCell.self, forCellWithReuseIdentifier: type)
440
+ registeredReuseIdentifiers.insert(type)
441
+ }
442
+
443
+ private func reuseIdentifier(for item: NativeListItem) -> String {
444
+ return item.type
445
+ }
446
+
447
+ private func installHostedContent(
448
+ in cell: HostCell,
449
+ item: NativeListItem,
450
+ contentSize: CGSize
451
+ ) throws {
452
+ if cell.hostedContentView != nil {
453
+ guard cell.itemType == item.type else {
454
+ throw RuntimeError.error(
455
+ withMessage:
456
+ "CollectionView supplied cell for type '\(cell.itemType ?? "<nil>")' " +
457
+ "to item type '\(item.type)'."
458
+ )
459
+ }
460
+ cell.bind(itemKey: item.key)
461
+ cell.updateContentLayout(
462
+ contentSize: contentSize,
463
+ contentInset: layoutProvider.itemContentInset()
464
+ )
465
+ return
466
+ }
467
+
468
+ let result = try makeView(type: item.type)
469
+ cell.install(
470
+ view: result.0,
471
+ contentSize: contentSize,
472
+ contentInset: layoutProvider.itemContentInset(),
473
+ itemKey: item.key,
474
+ itemType: item.type
475
+ )
476
+ cell.reactTag = result.1
477
+ hostedCells.add(cell)
478
+ }
479
+
480
+ private func retainMeasuredContent(in dataSource: HybridNativeListDataSource) {
481
+ let activeKeys = dataSource.itemsForPremeasurement().map { item in
482
+ item.key
483
+ }
484
+ let activeKeySet = Set(activeKeys)
485
+ measuredContentSizeByItemKey = measuredContentSizeByItemKey.filter { entry in
486
+ return activeKeySet.contains(entry.key)
487
+ }
488
+ }
489
+
490
+ private func updateVisibleCellContentLayouts() {
491
+ guard let collectionView, let dataSource else { return }
492
+
493
+ let visibleCells = collectionView.visibleCells
494
+ for visibleCell in visibleCells {
495
+ guard let cell = visibleCell as? HostCell else { continue }
496
+ guard let indexPath = collectionView.indexPath(for: cell) else { continue }
497
+
498
+ let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
499
+ let contentSize = resolvedHostedContentSize(for: item)
500
+ cell.updateContentLayout(
501
+ contentSize: contentSize,
502
+ contentInset: layoutProvider.itemContentInset()
503
+ )
504
+ }
505
+ }
506
+
507
+ private func runOnMain(_ block: @escaping () -> Void) {
508
+ if Thread.isMainThread {
509
+ block()
510
+ } else {
511
+ DispatchQueue.main.async(execute: block)
512
+ }
513
+ }
514
+
515
+ private func scheduleLayoutInvalidation() {
516
+ if hasScheduledLayoutInvalidation {
517
+ return
518
+ }
519
+
520
+ hasScheduledLayoutInvalidation = true
521
+
522
+ // Perf optimization: Several visible cells can discover their real size in the same UI tick.
523
+ // Coalesce those updates so FlowLayout only recalculates once.
524
+ DispatchQueue.main.async { [weak self] in
525
+ guard let self else { return }
526
+ self.hasScheduledLayoutInvalidation = false
527
+ self.collectionView?.collectionViewLayout.invalidateLayout()
528
+ }
529
+ }
530
+
531
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
532
+ return 1
533
+ }
534
+
535
+ func collectionView(
536
+ _ collectionView: UICollectionView,
537
+ numberOfItemsInSection section: Int
538
+ ) -> Int {
539
+ guard let dataSource else { return 0 }
540
+ return Int((try? dataSource.getCount()) ?? 0)
541
+ }
542
+
543
+ func layoutSizeForItem(at index: Int) -> CGSize {
544
+ guard let dataSource else { return .zero }
545
+ let item = dataSource.itemForCollectionViewQuery(at: index)
546
+ return resolvedLayoutSize(for: item)
547
+ }
548
+
549
+ func collectionView(
550
+ _ collectionView: UICollectionView,
551
+ layout collectionViewLayout: UICollectionViewLayout,
552
+ sizeForItemAt indexPath: IndexPath
553
+ ) -> CGSize {
554
+ guard let dataSource else {
555
+ return .zero
556
+ }
557
+
558
+ let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
559
+ return resolvedLayoutSize(for: item)
560
+ }
561
+
562
+ func collectionView(
563
+ _ collectionView: UICollectionView,
564
+ cellForItemAt indexPath: IndexPath
565
+ ) -> UICollectionViewCell {
566
+ guard let dataSource else {
567
+ return UICollectionViewCell()
568
+ }
569
+
570
+ let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
571
+ let reuseIdentifier = reuseIdentifier(for: item)
572
+ ensureReuseRegistered(for: reuseIdentifier)
573
+
574
+ let cell = collectionView.dequeueReusableCell(
575
+ withReuseIdentifier: reuseIdentifier,
576
+ for: indexPath
577
+ ) as! HostCell
578
+
579
+ let contentSize = resolvedHostedContentSize(for: item)
580
+
581
+ do {
582
+ try installHostedContent(in: cell, item: item, contentSize: contentSize)
583
+ } catch {
584
+ print("Failed to create list item view: \(error)")
585
+ }
586
+
587
+ if let reactTag = cell.reactTag {
588
+ let width = contentSize.width
589
+ let height: CGFloat?
590
+ if item.height != nil {
591
+ height = contentSize.height
592
+ } else {
593
+ height = nil
594
+ }
595
+
596
+ cell.prepareForMeasurement(width: width, height: height)
597
+
598
+ _ = updateViewCallback?(Double(reactTag), item, Double(indexPath.item))
599
+ }
600
+
601
+ if let hostedView = cell.hostedContentView, needsMeasuredContentSize(for: item) {
602
+ let didMeasureNewSize = captureMeasuredContentSize(for: item, view: hostedView)
603
+ let measuredContentSize = resolvedHostedContentSize(for: item)
604
+ cell.updateContentLayout(
605
+ contentSize: measuredContentSize,
606
+ contentInset: layoutProvider.itemContentInset()
607
+ )
608
+
609
+ if didMeasureNewSize {
610
+ scheduleLayoutInvalidation()
611
+ }
612
+ }
613
+
614
+ return cell
615
+ }
616
+ }
617
+
618
+ extension HybridUiListView: NativeListDataSourceObserver {
619
+ func dataSourceDidReload(
620
+ _ dataSource: HybridNativeListDataSource,
621
+ animated: Bool,
622
+ changeset: StagedChangeset<[DiffableListItem]>?
623
+ ) {
624
+ runOnMain { [weak self] in
625
+ guard let self else { return }
626
+ configureCollectionViewIfNeeded()
627
+ retainMeasuredContent(in: dataSource)
628
+
629
+ guard animated, let collectionView, let changeset else {
630
+ collectionView?.reloadData()
631
+ return
632
+ }
633
+
634
+ collectionView.reload(using: changeset) { nextItems in
635
+ dataSource.replaceWrappedItemsFromCollectionView(nextItems)
636
+ collectionView.collectionViewLayout.invalidateLayout()
637
+ }
638
+ }
639
+ }
640
+
641
+ func dataSourceDidInsert(_ dataSource: HybridNativeListDataSource, index: Int) {
642
+ runOnMain { [weak self] in
643
+ guard let self else { return }
644
+ let item = dataSource.item(at: index)
645
+ let reuseIdentifier = reuseIdentifier(for: item)
646
+ ensureReuseRegistered(for: reuseIdentifier)
647
+ let indexPath = IndexPath(item: index, section: 0)
648
+ let indexPaths = [indexPath]
649
+ collectionView?.insertItems(at: indexPaths)
650
+ }
651
+ }
652
+
653
+ func dataSourceDidUpdate(
654
+ _ dataSource: HybridNativeListDataSource,
655
+ index: Int,
656
+ previousItem: NativeListItem
657
+ ) {
658
+ runOnMain { [weak self] in
659
+ guard let self else { return }
660
+ let item = dataSource.item(at: index)
661
+ if previousItem.key != item.key {
662
+ measuredContentSizeByItemKey[previousItem.key] = nil
663
+ }
664
+
665
+ let reuseIdentifier = reuseIdentifier(for: item)
666
+ ensureReuseRegistered(for: reuseIdentifier)
667
+ let indexPath = IndexPath(item: index, section: 0)
668
+ let indexPaths = [indexPath]
669
+ collectionView?.reloadItems(at: indexPaths)
670
+ }
671
+ }
672
+
673
+ func dataSourceDidRemove(
674
+ _ dataSource: HybridNativeListDataSource,
675
+ index: Int,
676
+ removedItem: NativeListItem
677
+ ) {
678
+ runOnMain { [weak self] in
679
+ guard let self else { return }
680
+ measuredContentSizeByItemKey[removedItem.key] = nil
681
+ let indexPath = IndexPath(item: index, section: 0)
682
+ let indexPaths = [indexPath]
683
+ collectionView?.deleteItems(at: indexPaths)
684
+ }
685
+ }
686
+
687
+ func dataSourceDidMove(_ dataSource: HybridNativeListDataSource, fromIndex: Int, toIndex: Int) {
688
+ runOnMain { [weak self] in
689
+ guard let self else { return }
690
+ let sourceIndexPath = IndexPath(item: fromIndex, section: 0)
691
+ let targetIndexPath = IndexPath(item: toIndex, section: 0)
692
+ collectionView?.moveItem(at: sourceIndexPath, to: targetIndexPath)
693
+ }
694
+ }
695
+ }
@@ -0,0 +1,4 @@
1
+ import type * as ReactModule from 'react';
2
+ declare function nativeLog(...args: unknown[]): void;
3
+ declare function reactRender(element: ReactModule.ReactElement, callback?: () => void): void;
4
+ export { nativeLog, reactRender };