@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.
- package/CHANGELOG.md +159 -0
- package/LICENSE.md +7 -0
- package/README.md +65 -0
- package/RNFlashList.podspec +26 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutShadow.kt +94 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt +79 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutViewManager.kt +69 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainer.java +16 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerImpl.kt +16 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerManager.kt +27 -0
- package/android/src/main/kotlin/com/shopify/reactnative/flash_list/FlashListPackage.kt +19 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/AutoLayoutShadowTest.kt +146 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/Rect.kt +59 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestCollection.kt +6 -0
- package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestDataModel.kt +8 -0
- package/android/src/test/resources/LayoutTestData.json +708 -0
- package/dist/AnimatedFlashList.d.ts +6 -0
- package/dist/AnimatedFlashList.d.ts.map +1 -0
- package/dist/AnimatedFlashList.js +8 -0
- package/dist/AnimatedFlashList.js.map +1 -0
- package/dist/FlashList.d.ts +121 -0
- package/dist/FlashList.d.ts.map +1 -0
- package/dist/FlashList.js +502 -0
- package/dist/FlashList.js.map +1 -0
- package/dist/FlashListProps.d.ts +251 -0
- package/dist/FlashListProps.d.ts.map +1 -0
- package/dist/FlashListProps.js +3 -0
- package/dist/FlashListProps.js.map +1 -0
- package/dist/GridLayoutProviderWithProps.d.ts +30 -0
- package/dist/GridLayoutProviderWithProps.d.ts.map +1 -0
- package/dist/GridLayoutProviderWithProps.js +80 -0
- package/dist/GridLayoutProviderWithProps.js.map +1 -0
- package/dist/PureComponentWrapper.d.ts +22 -0
- package/dist/PureComponentWrapper.d.ts.map +1 -0
- package/dist/PureComponentWrapper.js +37 -0
- package/dist/PureComponentWrapper.js.map +1 -0
- package/dist/__tests__/AverageWindow.test.d.ts +2 -0
- package/dist/__tests__/AverageWindow.test.d.ts.map +1 -0
- package/dist/__tests__/AverageWindow.test.js +69 -0
- package/dist/__tests__/AverageWindow.test.js.map +1 -0
- package/dist/__tests__/FlashList.test.d.ts +2 -0
- package/dist/__tests__/FlashList.test.d.ts.map +1 -0
- package/dist/__tests__/FlashList.test.js +656 -0
- package/dist/__tests__/FlashList.test.js.map +1 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts +2 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts.map +1 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.js +133 -0
- package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -0
- package/dist/__tests__/PlatformHelper.web.test.d.ts +2 -0
- package/dist/__tests__/PlatformHelper.web.test.d.ts.map +1 -0
- package/dist/__tests__/PlatformHelper.web.test.js +25 -0
- package/dist/__tests__/PlatformHelper.web.test.js.map +1 -0
- package/dist/__tests__/ViewabilityHelper.test.d.ts +2 -0
- package/dist/__tests__/ViewabilityHelper.test.d.ts.map +1 -0
- package/dist/__tests__/ViewabilityHelper.test.js +187 -0
- package/dist/__tests__/ViewabilityHelper.test.js.map +1 -0
- package/dist/__tests__/helpers/mountFlashList.d.ts +20 -0
- package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -0
- package/dist/__tests__/helpers/mountFlashList.js +44 -0
- package/dist/__tests__/helpers/mountFlashList.js.map +1 -0
- package/dist/__tests__/useBlankAreaTracker.test.d.ts +2 -0
- package/dist/__tests__/useBlankAreaTracker.test.d.ts.map +1 -0
- package/dist/__tests__/useBlankAreaTracker.test.js +179 -0
- package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -0
- package/dist/benchmark/AutoScrollHelper.d.ts +18 -0
- package/dist/benchmark/AutoScrollHelper.d.ts.map +1 -0
- package/dist/benchmark/AutoScrollHelper.js +68 -0
- package/dist/benchmark/AutoScrollHelper.js.map +1 -0
- package/dist/benchmark/JSFPSMonitor.d.ts +23 -0
- package/dist/benchmark/JSFPSMonitor.d.ts.map +1 -0
- package/dist/benchmark/JSFPSMonitor.js +65 -0
- package/dist/benchmark/JSFPSMonitor.js.map +1 -0
- package/dist/benchmark/roundToDecimalPlaces.d.ts +2 -0
- package/dist/benchmark/roundToDecimalPlaces.d.ts.map +1 -0
- package/dist/benchmark/roundToDecimalPlaces.js +9 -0
- package/dist/benchmark/roundToDecimalPlaces.js.map +1 -0
- package/dist/benchmark/useBenchmark.d.ts +35 -0
- package/dist/benchmark/useBenchmark.d.ts.map +1 -0
- package/dist/benchmark/useBenchmark.js +167 -0
- package/dist/benchmark/useBenchmark.js.map +1 -0
- package/dist/benchmark/useBlankAreaTracker.d.ts +34 -0
- package/dist/benchmark/useBlankAreaTracker.d.ts.map +1 -0
- package/dist/benchmark/useBlankAreaTracker.js +67 -0
- package/dist/benchmark/useBlankAreaTracker.js.map +1 -0
- package/dist/benchmark/useDataMultiplier.d.ts +9 -0
- package/dist/benchmark/useDataMultiplier.d.ts.map +1 -0
- package/dist/benchmark/useDataMultiplier.js +25 -0
- package/dist/benchmark/useDataMultiplier.js.map +1 -0
- package/dist/benchmark/useFlatListBenchmark.d.ts +13 -0
- package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -0
- package/dist/benchmark/useFlatListBenchmark.js +100 -0
- package/dist/benchmark/useFlatListBenchmark.js.map +1 -0
- package/dist/errors/CustomError.d.ts +8 -0
- package/dist/errors/CustomError.d.ts.map +1 -0
- package/dist/errors/CustomError.js +14 -0
- package/dist/errors/CustomError.js.map +1 -0
- package/dist/errors/ExceptionList.d.ts +20 -0
- package/dist/errors/ExceptionList.d.ts.map +1 -0
- package/dist/errors/ExceptionList.js +22 -0
- package/dist/errors/ExceptionList.js.map +1 -0
- package/dist/errors/Warnings.d.ts +10 -0
- package/dist/errors/Warnings.d.ts.map +1 -0
- package/dist/errors/Warnings.js +15 -0
- package/dist/errors/Warnings.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutView.d.ts +21 -0
- package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutView.js +48 -0
- package/dist/native/auto-layout/AutoLayoutView.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts +4 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js +6 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts +5 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js +6 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +14 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js +3 -0
- package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js.map +1 -0
- package/dist/native/cell-container/CellContainer.d.ts +6 -0
- package/dist/native/cell-container/CellContainer.d.ts.map +1 -0
- package/dist/native/cell-container/CellContainer.js +9 -0
- package/dist/native/cell-container/CellContainer.js.map +1 -0
- package/dist/native/cell-container/CellContainer.web.d.ts +7 -0
- package/dist/native/cell-container/CellContainer.web.d.ts.map +1 -0
- package/dist/native/cell-container/CellContainer.web.js +13 -0
- package/dist/native/cell-container/CellContainer.web.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/AverageWindow.d.ts +21 -0
- package/dist/utils/AverageWindow.d.ts.map +1 -0
- package/dist/utils/AverageWindow.js +49 -0
- package/dist/utils/AverageWindow.js.map +1 -0
- package/dist/utils/PlatformHelper.d.ts +14 -0
- package/dist/utils/PlatformHelper.d.ts.map +1 -0
- package/dist/utils/PlatformHelper.js +16 -0
- package/dist/utils/PlatformHelper.js.map +1 -0
- package/dist/utils/PlatformHelper.web.d.ts +14 -0
- package/dist/utils/PlatformHelper.web.d.ts.map +1 -0
- package/dist/utils/PlatformHelper.web.js +18 -0
- package/dist/utils/PlatformHelper.web.js.map +1 -0
- package/dist/viewability/ViewToken.d.ts +8 -0
- package/dist/viewability/ViewToken.d.ts.map +1 -0
- package/dist/viewability/ViewToken.js +3 -0
- package/dist/viewability/ViewToken.js.map +1 -0
- package/dist/viewability/ViewabilityHelper.d.ts +25 -0
- package/dist/viewability/ViewabilityHelper.d.ts.map +1 -0
- package/dist/viewability/ViewabilityHelper.js +104 -0
- package/dist/viewability/ViewabilityHelper.js.map +1 -0
- package/dist/viewability/ViewabilityManager.d.ts +24 -0
- package/dist/viewability/ViewabilityManager.d.ts.map +1 -0
- package/dist/viewability/ViewabilityManager.js +94 -0
- package/dist/viewability/ViewabilityManager.js.map +1 -0
- package/ios/RNFlashList.xcodeproj/project.pbxproj +3 -0
- package/ios/RNFlashList.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/ios/Sources/AutoLayoutView.swift +218 -0
- package/ios/Sources/AutoLayoutViewManager.m +14 -0
- package/ios/Sources/AutoLayoutViewManager.swift +12 -0
- package/ios/Sources/CellContainer.swift +9 -0
- package/ios/Sources/CellContainerManager.m +8 -0
- package/ios/Sources/CellContainerManager.swift +12 -0
- package/ios/Sources/FlatListPro-Bridging-Header.h +8 -0
- package/ios/Tests/AutoLayoutViewTests.swift +113 -0
- package/jestSetup.js +15 -0
- package/package.json +75 -0
- package/src/AnimatedFlashList.ts +11 -0
- package/src/FlashList.tsx +801 -0
- package/src/FlashListProps.ts +312 -0
- package/src/GridLayoutProviderWithProps.ts +137 -0
- package/src/PureComponentWrapper.tsx +42 -0
- package/src/__tests__/AverageWindow.test.ts +80 -0
- package/src/__tests__/FlashList.test.tsx +738 -0
- package/src/__tests__/GridLayoutProviderWithProps.test.ts +150 -0
- package/src/__tests__/PlatformHelper.web.test.ts +29 -0
- package/src/__tests__/ViewabilityHelper.test.ts +283 -0
- package/src/__tests__/helpers/mountFlashList.tsx +62 -0
- package/src/__tests__/useBlankAreaTracker.test.tsx +206 -0
- package/src/benchmark/AutoScrollHelper.ts +70 -0
- package/src/benchmark/JSFPSMonitor.ts +74 -0
- package/src/benchmark/roundToDecimalPlaces.ts +4 -0
- package/src/benchmark/useBenchmark.ts +240 -0
- package/src/benchmark/useBlankAreaTracker.ts +117 -0
- package/src/benchmark/useDataMultiplier.ts +19 -0
- package/src/benchmark/useFlatListBenchmark.ts +107 -0
- package/src/errors/CustomError.ts +10 -0
- package/src/errors/ExceptionList.ts +23 -0
- package/src/errors/Warnings.ts +18 -0
- package/src/index.ts +32 -0
- package/src/native/auto-layout/AutoLayoutView.tsx +72 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponent.ts +7 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponent.web.ts +8 -0
- package/src/native/auto-layout/AutoLayoutViewNativeComponentProps.ts +14 -0
- package/src/native/cell-container/CellContainer.ts +7 -0
- package/src/native/cell-container/CellContainer.web.tsx +9 -0
- package/src/utils/AverageWindow.ts +49 -0
- package/src/utils/PlatformHelper.ts +16 -0
- package/src/utils/PlatformHelper.web.ts +20 -0
- package/src/viewability/ViewToken.ts +7 -0
- package/src/viewability/ViewabilityHelper.ts +162 -0
- 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,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,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;
|