@shopify/flash-list 1.0.1

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 (206) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +65 -0
  4. package/RNFlashList.podspec +26 -0
  5. package/android/build.gradle +59 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutShadow.kt +94 -0
  8. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt +79 -0
  9. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutViewManager.kt +69 -0
  10. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainer.java +16 -0
  11. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerImpl.kt +16 -0
  12. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerManager.kt +27 -0
  13. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/FlashListPackage.kt +19 -0
  14. package/android/src/test/java/com/shopify/reactnative/flash_list/AutoLayoutShadowTest.kt +146 -0
  15. package/android/src/test/java/com/shopify/reactnative/flash_list/models/Rect.kt +59 -0
  16. package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestCollection.kt +6 -0
  17. package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestDataModel.kt +8 -0
  18. package/android/src/test/resources/LayoutTestData.json +708 -0
  19. package/dist/AnimatedFlashList.d.ts +6 -0
  20. package/dist/AnimatedFlashList.d.ts.map +1 -0
  21. package/dist/AnimatedFlashList.js +8 -0
  22. package/dist/AnimatedFlashList.js.map +1 -0
  23. package/dist/FlashList.d.ts +121 -0
  24. package/dist/FlashList.d.ts.map +1 -0
  25. package/dist/FlashList.js +502 -0
  26. package/dist/FlashList.js.map +1 -0
  27. package/dist/FlashListProps.d.ts +251 -0
  28. package/dist/FlashListProps.d.ts.map +1 -0
  29. package/dist/FlashListProps.js +3 -0
  30. package/dist/FlashListProps.js.map +1 -0
  31. package/dist/GridLayoutProviderWithProps.d.ts +30 -0
  32. package/dist/GridLayoutProviderWithProps.d.ts.map +1 -0
  33. package/dist/GridLayoutProviderWithProps.js +80 -0
  34. package/dist/GridLayoutProviderWithProps.js.map +1 -0
  35. package/dist/PureComponentWrapper.d.ts +22 -0
  36. package/dist/PureComponentWrapper.d.ts.map +1 -0
  37. package/dist/PureComponentWrapper.js +37 -0
  38. package/dist/PureComponentWrapper.js.map +1 -0
  39. package/dist/__tests__/AverageWindow.test.d.ts +2 -0
  40. package/dist/__tests__/AverageWindow.test.d.ts.map +1 -0
  41. package/dist/__tests__/AverageWindow.test.js +69 -0
  42. package/dist/__tests__/AverageWindow.test.js.map +1 -0
  43. package/dist/__tests__/FlashList.test.d.ts +2 -0
  44. package/dist/__tests__/FlashList.test.d.ts.map +1 -0
  45. package/dist/__tests__/FlashList.test.js +656 -0
  46. package/dist/__tests__/FlashList.test.js.map +1 -0
  47. package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts +2 -0
  48. package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts.map +1 -0
  49. package/dist/__tests__/GridLayoutProviderWithProps.test.js +133 -0
  50. package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -0
  51. package/dist/__tests__/PlatformHelper.web.test.d.ts +2 -0
  52. package/dist/__tests__/PlatformHelper.web.test.d.ts.map +1 -0
  53. package/dist/__tests__/PlatformHelper.web.test.js +25 -0
  54. package/dist/__tests__/PlatformHelper.web.test.js.map +1 -0
  55. package/dist/__tests__/ViewabilityHelper.test.d.ts +2 -0
  56. package/dist/__tests__/ViewabilityHelper.test.d.ts.map +1 -0
  57. package/dist/__tests__/ViewabilityHelper.test.js +187 -0
  58. package/dist/__tests__/ViewabilityHelper.test.js.map +1 -0
  59. package/dist/__tests__/helpers/mountFlashList.d.ts +20 -0
  60. package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -0
  61. package/dist/__tests__/helpers/mountFlashList.js +44 -0
  62. package/dist/__tests__/helpers/mountFlashList.js.map +1 -0
  63. package/dist/__tests__/useBlankAreaTracker.test.d.ts +2 -0
  64. package/dist/__tests__/useBlankAreaTracker.test.d.ts.map +1 -0
  65. package/dist/__tests__/useBlankAreaTracker.test.js +179 -0
  66. package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -0
  67. package/dist/benchmark/AutoScrollHelper.d.ts +18 -0
  68. package/dist/benchmark/AutoScrollHelper.d.ts.map +1 -0
  69. package/dist/benchmark/AutoScrollHelper.js +68 -0
  70. package/dist/benchmark/AutoScrollHelper.js.map +1 -0
  71. package/dist/benchmark/JSFPSMonitor.d.ts +23 -0
  72. package/dist/benchmark/JSFPSMonitor.d.ts.map +1 -0
  73. package/dist/benchmark/JSFPSMonitor.js +65 -0
  74. package/dist/benchmark/JSFPSMonitor.js.map +1 -0
  75. package/dist/benchmark/roundToDecimalPlaces.d.ts +2 -0
  76. package/dist/benchmark/roundToDecimalPlaces.d.ts.map +1 -0
  77. package/dist/benchmark/roundToDecimalPlaces.js +9 -0
  78. package/dist/benchmark/roundToDecimalPlaces.js.map +1 -0
  79. package/dist/benchmark/useBenchmark.d.ts +35 -0
  80. package/dist/benchmark/useBenchmark.d.ts.map +1 -0
  81. package/dist/benchmark/useBenchmark.js +167 -0
  82. package/dist/benchmark/useBenchmark.js.map +1 -0
  83. package/dist/benchmark/useBlankAreaTracker.d.ts +34 -0
  84. package/dist/benchmark/useBlankAreaTracker.d.ts.map +1 -0
  85. package/dist/benchmark/useBlankAreaTracker.js +67 -0
  86. package/dist/benchmark/useBlankAreaTracker.js.map +1 -0
  87. package/dist/benchmark/useDataMultiplier.d.ts +9 -0
  88. package/dist/benchmark/useDataMultiplier.d.ts.map +1 -0
  89. package/dist/benchmark/useDataMultiplier.js +25 -0
  90. package/dist/benchmark/useDataMultiplier.js.map +1 -0
  91. package/dist/benchmark/useFlatListBenchmark.d.ts +13 -0
  92. package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -0
  93. package/dist/benchmark/useFlatListBenchmark.js +100 -0
  94. package/dist/benchmark/useFlatListBenchmark.js.map +1 -0
  95. package/dist/errors/CustomError.d.ts +8 -0
  96. package/dist/errors/CustomError.d.ts.map +1 -0
  97. package/dist/errors/CustomError.js +14 -0
  98. package/dist/errors/CustomError.js.map +1 -0
  99. package/dist/errors/ExceptionList.d.ts +20 -0
  100. package/dist/errors/ExceptionList.d.ts.map +1 -0
  101. package/dist/errors/ExceptionList.js +22 -0
  102. package/dist/errors/ExceptionList.js.map +1 -0
  103. package/dist/errors/Warnings.d.ts +10 -0
  104. package/dist/errors/Warnings.d.ts.map +1 -0
  105. package/dist/errors/Warnings.js +15 -0
  106. package/dist/errors/Warnings.js.map +1 -0
  107. package/dist/index.d.ts +13 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +28 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/native/auto-layout/AutoLayoutView.d.ts +21 -0
  112. package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -0
  113. package/dist/native/auto-layout/AutoLayoutView.js +48 -0
  114. package/dist/native/auto-layout/AutoLayoutView.js.map +1 -0
  115. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts +4 -0
  116. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -0
  117. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js +6 -0
  118. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js.map +1 -0
  119. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts +5 -0
  120. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts.map +1 -0
  121. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js +6 -0
  122. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js.map +1 -0
  123. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +14 -0
  124. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -0
  125. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js +3 -0
  126. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js.map +1 -0
  127. package/dist/native/cell-container/CellContainer.d.ts +6 -0
  128. package/dist/native/cell-container/CellContainer.d.ts.map +1 -0
  129. package/dist/native/cell-container/CellContainer.js +9 -0
  130. package/dist/native/cell-container/CellContainer.js.map +1 -0
  131. package/dist/native/cell-container/CellContainer.web.d.ts +7 -0
  132. package/dist/native/cell-container/CellContainer.web.d.ts.map +1 -0
  133. package/dist/native/cell-container/CellContainer.web.js +13 -0
  134. package/dist/native/cell-container/CellContainer.web.js.map +1 -0
  135. package/dist/tsconfig.tsbuildinfo +1 -0
  136. package/dist/utils/AverageWindow.d.ts +21 -0
  137. package/dist/utils/AverageWindow.d.ts.map +1 -0
  138. package/dist/utils/AverageWindow.js +49 -0
  139. package/dist/utils/AverageWindow.js.map +1 -0
  140. package/dist/utils/PlatformHelper.d.ts +14 -0
  141. package/dist/utils/PlatformHelper.d.ts.map +1 -0
  142. package/dist/utils/PlatformHelper.js +16 -0
  143. package/dist/utils/PlatformHelper.js.map +1 -0
  144. package/dist/utils/PlatformHelper.web.d.ts +14 -0
  145. package/dist/utils/PlatformHelper.web.d.ts.map +1 -0
  146. package/dist/utils/PlatformHelper.web.js +18 -0
  147. package/dist/utils/PlatformHelper.web.js.map +1 -0
  148. package/dist/viewability/ViewToken.d.ts +8 -0
  149. package/dist/viewability/ViewToken.d.ts.map +1 -0
  150. package/dist/viewability/ViewToken.js +3 -0
  151. package/dist/viewability/ViewToken.js.map +1 -0
  152. package/dist/viewability/ViewabilityHelper.d.ts +25 -0
  153. package/dist/viewability/ViewabilityHelper.d.ts.map +1 -0
  154. package/dist/viewability/ViewabilityHelper.js +104 -0
  155. package/dist/viewability/ViewabilityHelper.js.map +1 -0
  156. package/dist/viewability/ViewabilityManager.d.ts +24 -0
  157. package/dist/viewability/ViewabilityManager.d.ts.map +1 -0
  158. package/dist/viewability/ViewabilityManager.js +94 -0
  159. package/dist/viewability/ViewabilityManager.js.map +1 -0
  160. package/ios/RNFlashList.xcodeproj/project.pbxproj +3 -0
  161. package/ios/RNFlashList.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  162. package/ios/Sources/AutoLayoutView.swift +218 -0
  163. package/ios/Sources/AutoLayoutViewManager.m +14 -0
  164. package/ios/Sources/AutoLayoutViewManager.swift +12 -0
  165. package/ios/Sources/CellContainer.swift +9 -0
  166. package/ios/Sources/CellContainerManager.m +8 -0
  167. package/ios/Sources/CellContainerManager.swift +12 -0
  168. package/ios/Sources/FlatListPro-Bridging-Header.h +8 -0
  169. package/ios/Tests/AutoLayoutViewTests.swift +113 -0
  170. package/jestSetup.js +15 -0
  171. package/package.json +75 -0
  172. package/src/AnimatedFlashList.ts +11 -0
  173. package/src/FlashList.tsx +801 -0
  174. package/src/FlashListProps.ts +312 -0
  175. package/src/GridLayoutProviderWithProps.ts +137 -0
  176. package/src/PureComponentWrapper.tsx +42 -0
  177. package/src/__tests__/AverageWindow.test.ts +80 -0
  178. package/src/__tests__/FlashList.test.tsx +738 -0
  179. package/src/__tests__/GridLayoutProviderWithProps.test.ts +150 -0
  180. package/src/__tests__/PlatformHelper.web.test.ts +29 -0
  181. package/src/__tests__/ViewabilityHelper.test.ts +283 -0
  182. package/src/__tests__/helpers/mountFlashList.tsx +62 -0
  183. package/src/__tests__/useBlankAreaTracker.test.tsx +206 -0
  184. package/src/benchmark/AutoScrollHelper.ts +70 -0
  185. package/src/benchmark/JSFPSMonitor.ts +74 -0
  186. package/src/benchmark/roundToDecimalPlaces.ts +4 -0
  187. package/src/benchmark/useBenchmark.ts +240 -0
  188. package/src/benchmark/useBlankAreaTracker.ts +117 -0
  189. package/src/benchmark/useDataMultiplier.ts +19 -0
  190. package/src/benchmark/useFlatListBenchmark.ts +107 -0
  191. package/src/errors/CustomError.ts +10 -0
  192. package/src/errors/ExceptionList.ts +23 -0
  193. package/src/errors/Warnings.ts +18 -0
  194. package/src/index.ts +32 -0
  195. package/src/native/auto-layout/AutoLayoutView.tsx +72 -0
  196. package/src/native/auto-layout/AutoLayoutViewNativeComponent.ts +7 -0
  197. package/src/native/auto-layout/AutoLayoutViewNativeComponent.web.ts +8 -0
  198. package/src/native/auto-layout/AutoLayoutViewNativeComponentProps.ts +14 -0
  199. package/src/native/cell-container/CellContainer.ts +7 -0
  200. package/src/native/cell-container/CellContainer.web.tsx +9 -0
  201. package/src/utils/AverageWindow.ts +49 -0
  202. package/src/utils/PlatformHelper.ts +16 -0
  203. package/src/utils/PlatformHelper.web.ts +20 -0
  204. package/src/viewability/ViewToken.ts +7 -0
  205. package/src/viewability/ViewabilityHelper.ts +162 -0
  206. package/src/viewability/ViewabilityManager.ts +133 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ViewabilityManager.js","sourceRoot":"","sources":["../../src/viewability/ViewabilityManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ // This file is necessary because of the `react-native` CLI autolinking feature that adds a module based on whether this file exists
2
+ // as per [this](https://github.com/react-native-community/cli/issues/1054) issue.
3
+ // To edit a native iOS module, you should use the Xcode project in the `fixture` folder.
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Workspace
3
+ version = "1.0">
4
+ </Workspace>
@@ -0,0 +1,218 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+
5
+ /// Container for all RecyclerListView children. This will automatically remove all gaps and overlaps for GridLayouts with flexible spans.
6
+ /// Note: This cannot work for masonry layouts i.e, pinterest like layout
7
+ @objc class AutoLayoutView: UIView {
8
+ @objc(onBlankAreaEvent)
9
+ var onBlankAreaEvent: RCTDirectEventBlock?
10
+
11
+ @objc func setHorizontal(_ horizontal: Bool) {
12
+ self.horizontal = horizontal
13
+ }
14
+
15
+ @objc func setScrollOffset(_ scrollOffset: Int) {
16
+ self.scrollOffset = CGFloat(scrollOffset)
17
+ }
18
+
19
+ @objc func setWindowSize(_ windowSize: Int) {
20
+ self.windowSize = CGFloat(windowSize)
21
+ }
22
+
23
+ @objc func setRenderAheadOffset(_ renderAheadOffset: Int) {
24
+ self.renderAheadOffset = CGFloat(renderAheadOffset)
25
+ }
26
+
27
+ @objc func setEnableInstrumentation(_ enableInstrumentation: Bool) {
28
+ self.enableInstrumentation = enableInstrumentation
29
+ }
30
+
31
+ @objc func setDisableAutoLayout(_ disableAutoLayout: Bool) {
32
+ self.disableAutoLayout = disableAutoLayout
33
+ }
34
+
35
+ private var horizontal = false
36
+ private var scrollOffset: CGFloat = 0
37
+ private var windowSize: CGFloat = 0
38
+ private var renderAheadOffset: CGFloat = 0
39
+ private var enableInstrumentation = false
40
+ private var disableAutoLayout = false
41
+
42
+ /// Tracks where the last pixel is drawn in the visible window
43
+ private var lastMaxBound: CGFloat = 0
44
+ /// Tracks where first pixel is drawn in the visible window
45
+ private var lastMinBound: CGFloat = 0
46
+
47
+ override func layoutSubviews() {
48
+ fixLayout()
49
+ super.layoutSubviews()
50
+
51
+ let scrollView = sequence(first: self, next: { $0.superview }).first(where: { $0 is UIScrollView })
52
+ guard enableInstrumentation, let scrollView = scrollView as? UIScrollView else { return }
53
+
54
+ let scrollContainerSize = horizontal ? scrollView.frame.width : scrollView.frame.height
55
+ let currentScrollOffset = horizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y
56
+ let startOffset = horizontal ? frame.minX : frame.minY
57
+ let endOffset = horizontal ? frame.maxX : frame.maxY
58
+ let distanceFromWindowStart = max(startOffset - currentScrollOffset, 0)
59
+ let distanceFromWindowEnd = max(currentScrollOffset + scrollContainerSize - endOffset, 0)
60
+
61
+ let (blankOffsetStart, blankOffsetEnd) = computeBlankFromGivenOffset(
62
+ currentScrollOffset - startOffset,
63
+ filledBoundMin: lastMinBound,
64
+ filledBoundMax: lastMaxBound,
65
+ renderAheadOffset: renderAheadOffset,
66
+ windowSize: windowSize,
67
+ distanceFromWindowStart: distanceFromWindowStart,
68
+ distanceFromWindowEnd: distanceFromWindowEnd
69
+ )
70
+
71
+ onBlankAreaEvent?(
72
+ [
73
+ "offsetStart": blankOffsetStart,
74
+ "offsetEnd": blankOffsetEnd,
75
+ ]
76
+ )
77
+ }
78
+
79
+ /// Sorts views by index and then invokes clearGaps which does the correction.
80
+ /// Performance: Sort is needed. Given relatively low number of views in RecyclerListView render tree this should be a non issue.
81
+ private func fixLayout() {
82
+ guard
83
+ subviews.count > 1,
84
+ // Fixing layout during animation can interfere with it.
85
+ layer.animationKeys()?.isEmpty ?? true,
86
+ !disableAutoLayout
87
+ else { return }
88
+ let cellContainers = subviews
89
+ .compactMap { subview -> CellContainer? in
90
+ if let cellContainer = subview as? CellContainer {
91
+ return cellContainer
92
+ } else {
93
+ assertionFailure("CellRendererComponent outer view should always be CellContainer. Learn more here: https://shopify.github.io/flash-list/docs/usage#cellrenderercomponent.")
94
+ return nil
95
+ }
96
+ }
97
+ .sorted(by: { $0.index < $1.index })
98
+ clearGaps(for: cellContainers)
99
+ }
100
+
101
+ /// Checks for overlaps or gaps between adjacent items and then applies a correction.
102
+ /// Performance: RecyclerListView renders very small number of views and this is not going to trigger multiple layouts on the iOS side.
103
+ private func clearGaps(for cellContainers: [CellContainer]) {
104
+ var maxBound: CGFloat = 0
105
+ var minBound: CGFloat = CGFloat(Int.max)
106
+ var maxBoundNextCell: CGFloat = 0
107
+ let correctedScrollOffset = scrollOffset - (horizontal ? frame.minX : frame.minY)
108
+
109
+ cellContainers.indices.dropLast().forEach { index in
110
+ let cellContainer = cellContainers[index]
111
+ let cellTop = cellContainer.frame.minY
112
+ let cellBottom = cellContainer.frame.maxY
113
+ let cellLeft = cellContainer.frame.minX
114
+ let cellRight = cellContainer.frame.maxX
115
+
116
+ let nextCell = cellContainers[index + 1]
117
+ let nextCellTop = nextCell.frame.minY
118
+ let nextCellBottom = nextCell.frame.maxY
119
+ let nextCellLeft = nextCell.frame.minX
120
+ let nextCellRight = nextCell.frame.maxX
121
+
122
+
123
+ guard
124
+ isWithinBounds(
125
+ cellContainer,
126
+ scrollOffset: correctedScrollOffset,
127
+ renderAheadOffset: renderAheadOffset,
128
+ windowSize: windowSize,
129
+ isHorizontal: horizontal
130
+ )
131
+ else { return }
132
+ let isNextCellVisible = isWithinBounds(
133
+ nextCell,
134
+ scrollOffset: correctedScrollOffset,
135
+ renderAheadOffset: renderAheadOffset,
136
+ windowSize: windowSize,
137
+ isHorizontal: horizontal
138
+ )
139
+
140
+ if horizontal {
141
+ maxBound = max(maxBound, cellRight)
142
+ minBound = min(minBound, cellLeft)
143
+ maxBoundNextCell = maxBound
144
+ if cellTop < nextCellTop {
145
+ if cellBottom != nextCellTop {
146
+ nextCell.frame.origin.y = cellBottom
147
+ }
148
+ if cellLeft != nextCellLeft {
149
+ nextCell.frame.origin.x = cellLeft
150
+ }
151
+ } else {
152
+ nextCell.frame.origin.x = maxBound
153
+ }
154
+ if isNextCellVisible {
155
+ maxBoundNextCell = max(maxBound, nextCellRight)
156
+ }
157
+ } else {
158
+ maxBound = max(maxBound, cellBottom)
159
+ minBound = min(minBound, cellTop)
160
+ maxBoundNextCell = maxBound
161
+ if cellLeft < nextCellLeft {
162
+ if cellRight != nextCellLeft {
163
+ nextCell.frame.origin.x = cellRight
164
+ }
165
+ if cellTop != nextCellTop {
166
+ nextCell.frame.origin.y = cellTop
167
+ }
168
+ } else {
169
+ nextCell.frame.origin.y = maxBound
170
+ }
171
+ if isNextCellVisible {
172
+ maxBoundNextCell = max(maxBound, nextCellBottom)
173
+ }
174
+ }
175
+ }
176
+
177
+ lastMaxBound = maxBoundNextCell
178
+ lastMinBound = minBound
179
+ }
180
+
181
+ func computeBlankFromGivenOffset(
182
+ _ actualScrollOffset: CGFloat,
183
+ filledBoundMin: CGFloat,
184
+ filledBoundMax: CGFloat,
185
+ renderAheadOffset: CGFloat,
186
+ windowSize: CGFloat,
187
+ distanceFromWindowStart: CGFloat,
188
+ distanceFromWindowEnd: CGFloat
189
+ ) -> (
190
+ offsetStart: CGFloat,
191
+ offsetEnd: CGFloat
192
+ ) {
193
+ let blankOffsetStart = filledBoundMin - actualScrollOffset - distanceFromWindowStart
194
+
195
+ let blankOffsetEnd = actualScrollOffset + windowSize - renderAheadOffset - filledBoundMax - distanceFromWindowEnd
196
+
197
+ return (blankOffsetStart, blankOffsetEnd)
198
+ }
199
+
200
+ /// It's important to avoid correcting views outside the render window. An item that isn't being recycled might still remain in the view tree. If views outside get considered then gaps between unused items will cause algorithm to fail.
201
+ func isWithinBounds(
202
+ _ cellContainer: CellContainer,
203
+ scrollOffset: CGFloat,
204
+ renderAheadOffset: CGFloat,
205
+ windowSize: CGFloat,
206
+ isHorizontal: Bool
207
+ ) -> Bool {
208
+ let boundsStart = scrollOffset - renderAheadOffset
209
+ let boundsEnd = scrollOffset + windowSize
210
+ let cellFrame = cellContainer.frame
211
+
212
+ if isHorizontal {
213
+ return (cellFrame.minX >= boundsStart || cellFrame.maxX >= boundsStart) && (cellFrame.minX <= boundsEnd || cellFrame.maxX <= boundsEnd)
214
+ } else {
215
+ return (cellFrame.minY >= boundsStart || cellFrame.maxY >= boundsStart) && (cellFrame.minY <= boundsEnd || cellFrame.maxY <= boundsEnd)
216
+ }
217
+ }
218
+ }
@@ -0,0 +1,14 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTViewManager.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(AutoLayoutViewManager, RCTViewManager)
5
+
6
+ RCT_EXPORT_VIEW_PROPERTY(horizontal, BOOL)
7
+ RCT_EXPORT_VIEW_PROPERTY(scrollOffset, NSInteger)
8
+ RCT_EXPORT_VIEW_PROPERTY(windowSize, NSInteger)
9
+ RCT_EXPORT_VIEW_PROPERTY(renderAheadOffset, NSInteger)
10
+ RCT_EXPORT_VIEW_PROPERTY(enableInstrumentation, BOOL)
11
+ RCT_EXPORT_VIEW_PROPERTY(disableAutoLayout, BOOL)
12
+ RCT_EXPORT_VIEW_PROPERTY(onBlankAreaEvent, RCTDirectEventBlock)
13
+
14
+ @end
@@ -0,0 +1,12 @@
1
+ import Foundation
2
+
3
+ @objc(AutoLayoutViewManager)
4
+ class AutoLayoutViewManager: RCTViewManager {
5
+ override func view() -> UIView! {
6
+ return AutoLayoutView()
7
+ }
8
+
9
+ override static func requiresMainQueueSetup() -> Bool {
10
+ return true
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ import Foundation
2
+
3
+ @objc class CellContainer: UIView {
4
+ var index: Int = -1
5
+
6
+ @objc func setIndex(_ index: Int) {
7
+ self.index = index
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTViewManager.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(CellContainerManager, RCTViewManager)
5
+
6
+ RCT_EXPORT_VIEW_PROPERTY(index, NSInteger)
7
+
8
+ @end
@@ -0,0 +1,12 @@
1
+ import Foundation
2
+
3
+ @objc(CellContainerManager)
4
+ class CellContainerManager: RCTViewManager {
5
+ override func view() -> UIView! {
6
+ return CellContainer()
7
+ }
8
+
9
+ override static func requiresMainQueueSetup() -> Bool {
10
+ return true
11
+ }
12
+ }
@@ -0,0 +1,8 @@
1
+ #ifndef FlatListPro_Bridging_Header_h
2
+ #define FlatListPro_Bridging_Header_h
3
+
4
+ #import <React/RCTBridgeModule.h>
5
+ #import <React/RCTEventEmitter.h>
6
+ #import <React/RCTViewManager.h>
7
+
8
+ #endif /* FlatListPro_Bridging_Header_h */
@@ -0,0 +1,113 @@
1
+ import XCTest
2
+ @testable import RNFlashList
3
+
4
+ class AutoLayoutViewTests: XCTestCase {
5
+ var autoLayoutView: AutoLayoutView!
6
+ var testCell: CellContainer!
7
+ let cellHeight: CGFloat = 50
8
+ let windowSize: CGFloat = 500
9
+
10
+ override func setUp() {
11
+ super.setUp()
12
+ autoLayoutView = AutoLayoutView()
13
+ testCell = CellContainer()
14
+ }
15
+
16
+ func testIsWithinBoundsVerticalListScrollToBottom() {
17
+ testCell.frame = CGRect(x: 0, y: 0, width: cellHeight, height: cellHeight)
18
+ // Top left corner of the screen
19
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
20
+ scrollOffset: 0,
21
+ renderAheadOffset: 0,
22
+ windowSize: windowSize,
23
+ isHorizontal: false))
24
+
25
+ // Partly visible
26
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
27
+ scrollOffset: 25,
28
+ renderAheadOffset: 0,
29
+ windowSize: windowSize,
30
+ isHorizontal: false))
31
+
32
+ // Above visible
33
+ XCTAssertFalse(autoLayoutView.isWithinBounds(testCell,
34
+ scrollOffset: 100,
35
+ renderAheadOffset: 0,
36
+ windowSize: windowSize,
37
+ isHorizontal: false))
38
+ }
39
+
40
+ func testIsWithinBoundsVerticalListScrollToTop() {
41
+ testCell.frame = CGRect(x: 0, y: windowSize - cellHeight, width: cellHeight, height: cellHeight)
42
+ // Bottom left corner of the screen
43
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
44
+ scrollOffset: 0,
45
+ renderAheadOffset: 0,
46
+ windowSize: windowSize,
47
+ isHorizontal: false))
48
+
49
+ // Partly visible
50
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
51
+ scrollOffset: -25,
52
+ renderAheadOffset: 0,
53
+ windowSize: windowSize,
54
+ isHorizontal: false))
55
+
56
+ // Below visible
57
+ XCTAssertFalse(autoLayoutView.isWithinBounds(testCell,
58
+ scrollOffset: -100,
59
+ renderAheadOffset: 0,
60
+ windowSize: windowSize,
61
+ isHorizontal: false))
62
+ }
63
+
64
+ func testIsWithinBoundsHorizontalListScrollToLeft() {
65
+ testCell.frame = CGRect(x: 0, y: 0, width: cellHeight, height: cellHeight)
66
+ // Top left corner of the screen
67
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
68
+ scrollOffset: 0,
69
+ renderAheadOffset: 0,
70
+ windowSize: windowSize,
71
+ isHorizontal: true))
72
+
73
+ // Partly visible
74
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
75
+ scrollOffset: 25,
76
+ renderAheadOffset: 0,
77
+ windowSize: windowSize,
78
+ isHorizontal: true))
79
+
80
+ // Hidden
81
+ XCTAssertFalse(autoLayoutView.isWithinBounds(testCell,
82
+ scrollOffset: 100,
83
+ renderAheadOffset: 0,
84
+ windowSize: windowSize,
85
+ isHorizontal: true))
86
+ }
87
+
88
+ func testIsWithinBoundsHorizontalListScrollToRight() {
89
+ testCell.frame = CGRect(x: windowSize - cellHeight, y: 0, width: cellHeight, height: cellHeight)
90
+ // Bottom left corner of the screen
91
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
92
+ scrollOffset: 0,
93
+ renderAheadOffset: 0,
94
+ windowSize: windowSize,
95
+ isHorizontal: true))
96
+
97
+ // Partly visible
98
+ XCTAssertTrue(autoLayoutView.isWithinBounds(testCell,
99
+ scrollOffset: -25,
100
+ renderAheadOffset: 0,
101
+ windowSize: windowSize,
102
+ isHorizontal: true))
103
+
104
+ testCell.frame = CGRect(x: windowSize + cellHeight, y: 0, width: cellHeight, height: cellHeight)
105
+ // Hidden
106
+ XCTAssertFalse(autoLayoutView.isWithinBounds(testCell,
107
+ scrollOffset: 0,
108
+ renderAheadOffset: 0,
109
+ windowSize: windowSize,
110
+ isHorizontal: true))
111
+ }
112
+
113
+ }
package/jestSetup.js ADDED
@@ -0,0 +1,15 @@
1
+ jest.mock("@shopify/flash-list", () => {
2
+ const ActualFlashList = jest.requireActual("@shopify/flash-list").FlashList;
3
+ class MockFlashList extends ActualFlashList {
4
+ componentDidMount() {
5
+ super.componentDidMount();
6
+ this.rlvRef?._scrollComponent?._scrollViewRef?.props.onLayout({
7
+ nativeEvent: { layout: { height: 900, width: 400 } },
8
+ });
9
+ }
10
+ }
11
+ return {
12
+ ...jest.requireActual("@shopify/flash-list"),
13
+ FlashList: MockFlashList,
14
+ };
15
+ });
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@shopify/flash-list",
3
+ "version": "1.0.1",
4
+ "keywords": [
5
+ "react-native"
6
+ ],
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "@shopify:registry": "https://registry.npmjs.org/"
10
+ },
11
+ "description": "FlashList is a more performant FlatList replacement",
12
+ "author": "shopify",
13
+ "license": "MIT",
14
+ "homepage": "https://reimagined-fiesta-030d04f7.pages.github.io",
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "scripts": {
18
+ "up": "/opt/dev/bin/dev up && bundle install && yarn fixture-up && yarn e2e-up && yarn build && yarn fixture-web-up",
19
+ "fixture-web-up": "cd fixture/web-app && yarn && cd ../../",
20
+ "fixture-up": "cd fixture && yarn && cd ios && bundle exec pod install && cd ../../",
21
+ "e2e-up": "cd fixture/ios && brew tap wix/brew && brew install applesimutils && cd ../../",
22
+ "start": "cd fixture && react-native start",
23
+ "test": "jest",
24
+ "lint": "yarn eslint . --ext .ts,.tsx",
25
+ "lint:fix": "yarn eslint . --ext .ts,.tsx --fix",
26
+ "build": "tsc -b",
27
+ "e2e:build:android": "detox build -c android.emu.release",
28
+ "run-ios": "cd fixture && yarn react-native run-ios && yarn build --watch",
29
+ "ri": "yarn run-ios",
30
+ "run-android": "cd fixture && yarn react-native run-android && yarn build --watch",
31
+ "ra": "yarn run-android",
32
+ "run-e2e-ios": "cd fixture && yarn e2e:build:ios && yarn e2e:test:ios",
33
+ "rei": "yarn run-e2e-ios",
34
+ "run-e2e-android": "cd fixture && yarn e2e:build:android && yarn e2e:test:android",
35
+ "rea": "yarn run-e2e-android",
36
+ "run-web": "cd fixture/web-app && yarn web",
37
+ "rw": "yarn run-web",
38
+ "predeploy:website": "sh ./build_website.sh",
39
+ "deploy:website": "gh-pages -d website/_site"
40
+ },
41
+ "peerDependencies": {
42
+ "@babel/runtime": "*",
43
+ "react": "*",
44
+ "react-native": "*"
45
+ },
46
+ "devDependencies": {
47
+ "@babel/core": "^7.18.5",
48
+ "@babel/runtime": "^7.18.3",
49
+ "@quilted/react-testing": "^0.5.14",
50
+ "@react-native-community/eslint-config": "^3.0.0",
51
+ "@shopify/eslint-plugin": "^41.3.1",
52
+ "@types/jest": "^28.1.3",
53
+ "@types/react-native": "0.67.6",
54
+ "babel-jest": "^28.1.1",
55
+ "enhanced-resolve": "^5.9.3",
56
+ "eslint": "8.18.0",
57
+ "gh-pages": "^3.2.3",
58
+ "jest": "^28.1.1",
59
+ "metro-react-native-babel-preset": "^0.71.1",
60
+ "react": "17.0.2",
61
+ "react-native": "0.68.1",
62
+ "typescript": "^4.7.4"
63
+ },
64
+ "files": [
65
+ "android",
66
+ "ios",
67
+ "dist",
68
+ "RNFlashList.podspec",
69
+ "src",
70
+ "jestSetup.js"
71
+ ],
72
+ "dependencies": {
73
+ "recyclerlistview": "3.3.0-beta.2"
74
+ }
75
+ }
@@ -0,0 +1,11 @@
1
+ import { Animated } from "react-native";
2
+
3
+ import FlashList from "./FlashList";
4
+ import { FlashListProps } from "./FlashListProps";
5
+
6
+ const AnimatedFlashList =
7
+ Animated.createAnimatedComponent<React.ComponentType<FlashListProps<any>>>(
8
+ FlashList
9
+ );
10
+
11
+ export default AnimatedFlashList;