@onekeyfe/react-native-native-list 3.0.157-alpha.254 → 3.0.157-alpha.258
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/android/build.gradle +7 -0
- package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +20 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityDetailsView.kt +179 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +23 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListContainerSlotAdapter.kt +53 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListEndAlignment.kt +5 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaPreviewSlot.kt +215 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +15 -6
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +3 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListScrollPositionTracker.kt +29 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +16 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +165 -26
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +5 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListEndAlignmentTest.kt +34 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListScrollPositionTrackerTest.kt +28 -0
- package/docs/DESIGN.md +4 -2
- package/docs/ROW_TEMPLATES.md +45 -0
- package/docs/SPEC.md +241 -1
- package/docs/STYLE_SPEC.md +8 -0
- package/ios/HybridNativeList.swift +30 -0
- package/ios/NativeListActivityDetailsView.swift +229 -0
- package/ios/NativeListActivityRenderer.swift +24 -2
- package/ios/NativeListMediaPreviewSlot.swift +199 -0
- package/ios/NativeListMediaTileRenderer.swift +10 -4
- package/ios/NativeListModels.swift +4 -1
- package/ios/NativeListScrollPositionTracker.swift +30 -0
- package/ios/NativeListSectionHeaderRenderer.swift +27 -1
- package/ios/RNCNativeListView.swift +159 -4
- package/ios/tests/NativeListActivityPresentationTests.swift +43 -0
- package/ios/tests/NativeListLifecycleTests.swift +123 -0
- package/ios/tests/NativeListMediaVisibilityTests.swift +42 -0
- package/ios/tests/NativeListScrollPositionTrackerTests.swift +25 -0
- package/lib/module/NativeList.js +56 -3
- package/lib/module/NativeList.web.js +5 -0
- package/lib/module/validation.js +64 -4
- package/lib/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
- package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
- package/lib/typescript/src/NativeList.nitro.d.ts +3 -0
- package/lib/typescript/src/NativeList.types.d.ts +14 -1
- package/lib/typescript/src/models.d.ts +33 -5
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +39 -3
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +6 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_bool.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +24 -9
- package/nitrogen/generated/android/nativelistOnLoad.cpp +2 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +37 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +23 -2
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +32 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +3 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +54 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +6 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +8 -2
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +6 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +11 -2
- package/nitrogen/generated/shared/json/NativeListConfig.json +3 -0
- package/package.json +6 -5
- package/scripts/enable-container-slots.mjs +58 -0
- package/scripts/enable-container-slots.test.mjs +58 -0
- package/src/NativeList.nitro.ts +3 -0
- package/src/NativeList.tsx +84 -2
- package/src/NativeList.types.ts +14 -1
- package/src/NativeList.web.tsx +5 -0
- package/src/models.ts +38 -1
- package/src/validation.ts +146 -4
|
@@ -4,6 +4,54 @@ import UIKit
|
|
|
4
4
|
import UniformTypeIdentifiers
|
|
5
5
|
|
|
6
6
|
final class NativeListView: UIView {
|
|
7
|
+
private var refreshEmittedForDrag = false
|
|
8
|
+
private var minimumDragOffset: CGFloat = 0
|
|
9
|
+
private var reactContainerSlots: [UIView] = []
|
|
10
|
+
private var containerSlotHeights: [CGFloat] = [0, 0, 0]
|
|
11
|
+
private var lastLaidOutContentHeight: CGFloat?
|
|
12
|
+
private var needsContentShrinkCorrection = false
|
|
13
|
+
private var contentShrinkCorrectionScheduled = false
|
|
14
|
+
|
|
15
|
+
@objc func mountContainerSlot(_ view: UIView, atIndex index: Int) {
|
|
16
|
+
reactContainerSlots.insert(view, at: min(index, reactContainerSlots.count))
|
|
17
|
+
collectionView.addSubview(view)
|
|
18
|
+
setNeedsLayout()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@objc func unmountContainerSlot(_ view: UIView) {
|
|
22
|
+
reactContainerSlots.removeAll { $0 === view }
|
|
23
|
+
view.removeFromSuperview()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func setContainerSlotHeightsJson(_ json: String) {
|
|
27
|
+
guard let data = json.data(using: .utf8),
|
|
28
|
+
let heights = try? JSONSerialization.jsonObject(with: data) as? [Double],
|
|
29
|
+
heights.count == 3, heights.allSatisfy({ $0.isFinite && $0 >= 0 }) else { return }
|
|
30
|
+
containerSlotHeights = heights.map { CGFloat($0) }
|
|
31
|
+
if let config { configureLayout(config) }
|
|
32
|
+
setNeedsLayout()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private func layoutContainerSlots() {
|
|
36
|
+
guard reactContainerSlots.count == 3 else { return }
|
|
37
|
+
let empty = config?.items.isEmpty ?? true
|
|
38
|
+
let header = containerSlotHeights[0]
|
|
39
|
+
let emptyHeight = empty ? containerSlotHeights[1] : 0
|
|
40
|
+
let footer = containerSlotHeights[2]
|
|
41
|
+
let paddingTop = config?.contentPaddingTop ?? 0
|
|
42
|
+
let paddingBottom = config?.contentPaddingBottom ?? 0
|
|
43
|
+
let footerY = max(paddingTop + header + emptyHeight,
|
|
44
|
+
collectionView.contentSize.height - paddingBottom - footer)
|
|
45
|
+
let positions = [paddingTop, paddingTop + header, footerY]
|
|
46
|
+
for (index, view) in reactContainerSlots.enumerated() {
|
|
47
|
+
view.isHidden = index == 1 && !empty
|
|
48
|
+
let horizontalPadding = config?.contentPaddingHorizontal ?? 0
|
|
49
|
+
view.frame = CGRect(x: horizontalPadding, y: positions[index],
|
|
50
|
+
width: max(0, collectionView.bounds.width - horizontalPadding * 2),
|
|
51
|
+
height: containerSlotHeights[index])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
private final class ActionAnchorRecord {
|
|
8
56
|
let token: String
|
|
9
57
|
weak var sourceView: UIView?
|
|
@@ -47,6 +95,26 @@ final class NativeListView: UIView {
|
|
|
47
95
|
case end(Bool)
|
|
48
96
|
}
|
|
49
97
|
|
|
98
|
+
private var scrollPositionTracker = NativeListScrollPositionTracker()
|
|
99
|
+
var onScrollPositionThresholdChange: ((Bool) -> Void)? {
|
|
100
|
+
didSet {
|
|
101
|
+
scrollPositionTracker.resetDelivery()
|
|
102
|
+
emitScrollPositionThresholdIfNeeded()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func setScrollPositionThresholdsJson(_ json: String) {
|
|
107
|
+
scrollPositionTracker.configure(json: json)
|
|
108
|
+
emitScrollPositionThresholdIfNeeded()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private func emitScrollPositionThresholdIfNeeded() {
|
|
112
|
+
guard let callback = onScrollPositionThresholdChange,
|
|
113
|
+
let next = scrollPositionTracker.update(offset: Double(max(0,
|
|
114
|
+
collectionView.contentOffset.y + collectionView.contentInset.top))) else { return }
|
|
115
|
+
callback(next)
|
|
116
|
+
}
|
|
117
|
+
|
|
50
118
|
var onRowAction: ((String) -> Void)?
|
|
51
119
|
var onActionAnchorInvalidated: ((String) -> Void)?
|
|
52
120
|
var onSelectionDelta: ((String) -> Void)?
|
|
@@ -60,7 +128,14 @@ final class NativeListView: UIView {
|
|
|
60
128
|
}
|
|
61
129
|
|
|
62
130
|
private let flowLayout = NativeListFlowLayout()
|
|
63
|
-
private lazy var collectionView
|
|
131
|
+
private lazy var collectionView: NativeListCollectionView = {
|
|
132
|
+
let view = NativeListCollectionView(frame: .zero, collectionViewLayout: flowLayout)
|
|
133
|
+
view.onContentLayout = { [weak self] in
|
|
134
|
+
self?.layoutContainerSlots()
|
|
135
|
+
self?.contentLayoutDidChange()
|
|
136
|
+
}
|
|
137
|
+
return view
|
|
138
|
+
}()
|
|
64
139
|
private let footerContainer = UIView()
|
|
65
140
|
private var footerCell: NativeListRowHost = NativeListRendererRegistry.create(.action)
|
|
66
141
|
private var footerRendererKey = NativeListRendererKey.action
|
|
@@ -333,6 +408,7 @@ final class NativeListView: UIView {
|
|
|
333
408
|
rebindRowsForLayoutDirection(config)
|
|
334
409
|
}
|
|
335
410
|
performPendingScrollIfNeeded()
|
|
411
|
+
layoutContainerSlots()
|
|
336
412
|
}
|
|
337
413
|
|
|
338
414
|
// A layout-direction change may arrive without a layout pass (semantic attribute or trait).
|
|
@@ -673,6 +749,39 @@ final class NativeListView: UIView {
|
|
|
673
749
|
collectionView.setContentOffset(point, animated: animated)
|
|
674
750
|
}
|
|
675
751
|
|
|
752
|
+
private func contentLayoutDidChange() {
|
|
753
|
+
let height = collectionView.contentSize.height
|
|
754
|
+
if config?.orientation != "horizontal",
|
|
755
|
+
let previous = lastLaidOutContentHeight, height < previous {
|
|
756
|
+
needsContentShrinkCorrection = true
|
|
757
|
+
}
|
|
758
|
+
lastLaidOutContentHeight = height
|
|
759
|
+
scheduleContentShrinkCorrection()
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private func scheduleContentShrinkCorrection() {
|
|
763
|
+
guard needsContentShrinkCorrection, !contentShrinkCorrectionScheduled, !disposed else { return }
|
|
764
|
+
contentShrinkCorrectionScheduled = true
|
|
765
|
+
// Wait for the collection layout and ancestor pager inset observers to settle.
|
|
766
|
+
DispatchQueue.main.async { [weak self] in
|
|
767
|
+
guard let self else { return }
|
|
768
|
+
self.contentShrinkCorrectionScheduled = false
|
|
769
|
+
guard !self.disposed, self.needsContentShrinkCorrection,
|
|
770
|
+
!self.collectionView.isTracking, !self.collectionView.isDragging,
|
|
771
|
+
!self.collectionView.isDecelerating,
|
|
772
|
+
self.collectionView.refreshControl?.isRefreshing != true,
|
|
773
|
+
self.config?.refreshing != true else { return }
|
|
774
|
+
self.needsContentShrinkCorrection = false
|
|
775
|
+
guard self.config?.orientation != "horizontal", self.collectionView.bounds.height > 0 else { return }
|
|
776
|
+
let insets = self.collectionView.adjustedContentInset
|
|
777
|
+
let maximum = max(-insets.top,
|
|
778
|
+
self.collectionView.contentSize.height - self.collectionView.bounds.height + insets.bottom)
|
|
779
|
+
if self.collectionView.contentOffset.y > maximum {
|
|
780
|
+
self.setScrollOffset(self.collectionView.contentOffset.y, animated: false)
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
676
785
|
func setRefreshing(_ refreshing: Bool) {
|
|
677
786
|
guard var current = config else { return }
|
|
678
787
|
current.refreshing = refreshing
|
|
@@ -681,6 +790,7 @@ final class NativeListView: UIView {
|
|
|
681
790
|
collectionView.refreshControl?.beginRefreshing()
|
|
682
791
|
} else {
|
|
683
792
|
collectionView.refreshControl?.endRefreshing()
|
|
793
|
+
scheduleContentShrinkCorrection()
|
|
684
794
|
}
|
|
685
795
|
}
|
|
686
796
|
|
|
@@ -693,11 +803,14 @@ final class NativeListView: UIView {
|
|
|
693
803
|
flowLayout.minimumLineSpacing = config.itemSpacing
|
|
694
804
|
flowLayout.minimumInteritemSpacing = config.itemSpacing
|
|
695
805
|
flowLayout.sectionInset = UIEdgeInsets(
|
|
696
|
-
top: config.contentPaddingTop,
|
|
806
|
+
top: config.contentPaddingTop + containerSlotHeights[0],
|
|
697
807
|
left: config.contentPaddingHorizontal + (isRightToLeft ? indexGutter : 0),
|
|
698
|
-
bottom: config.contentPaddingBottom,
|
|
808
|
+
bottom: config.contentPaddingBottom + containerSlotHeights[2],
|
|
699
809
|
right: config.contentPaddingHorizontal + (isRightToLeft ? 0 : indexGutter)
|
|
700
810
|
)
|
|
811
|
+
flowLayout.minimumContainerContentHeight = config.items.isEmpty
|
|
812
|
+
? config.contentPaddingTop + config.contentPaddingBottom + containerSlotHeights.reduce(0, +)
|
|
813
|
+
: 0
|
|
701
814
|
flowLayout.stickyItemIndexes = config.stickyHeaders
|
|
702
815
|
? Set(config.items.enumerated().compactMap {
|
|
703
816
|
$0.element.type == "sectionHeader" && $0.element.data.bool("sticky", default: true) && $0.element.data.string("variant") != "summary"
|
|
@@ -1347,6 +1460,7 @@ final class NativeListView: UIView {
|
|
|
1347
1460
|
} else {
|
|
1348
1461
|
collectionView.refreshControl = nil
|
|
1349
1462
|
}
|
|
1463
|
+
if !config.refreshing { scheduleContentShrinkCorrection() }
|
|
1350
1464
|
}
|
|
1351
1465
|
|
|
1352
1466
|
private func configureFooter(_ config: NativeListConfig) {
|
|
@@ -1539,6 +1653,7 @@ final class NativeListView: UIView {
|
|
|
1539
1653
|
current.rowPressToggles == next.rowPressToggles,
|
|
1540
1654
|
current.reorderable == next.reorderable,
|
|
1541
1655
|
current.pullToRefresh == next.pullToRefresh,
|
|
1656
|
+
current.refreshTriggerDistance == next.refreshTriggerDistance,
|
|
1542
1657
|
current.refreshing == next.refreshing,
|
|
1543
1658
|
current.loadMore == next.loadMore,
|
|
1544
1659
|
current.endReachedThreshold == next.endReachedThreshold,
|
|
@@ -1849,6 +1964,8 @@ final class NativeListView: UIView {
|
|
|
1849
1964
|
itemsByKey.removeAll()
|
|
1850
1965
|
sectionIndexEntries.removeAll()
|
|
1851
1966
|
pendingScrollRequest = nil
|
|
1967
|
+
needsContentShrinkCorrection = false
|
|
1968
|
+
lastLaidOutContentHeight = nil
|
|
1852
1969
|
lastVisibleRange = nil
|
|
1853
1970
|
onRowAction = nil
|
|
1854
1971
|
onActionAnchorInvalidated = nil
|
|
@@ -1856,6 +1973,7 @@ final class NativeListView: UIView {
|
|
|
1856
1973
|
onReorder = nil
|
|
1857
1974
|
onEndReached = nil
|
|
1858
1975
|
onVisibleRangeChanged = nil
|
|
1976
|
+
onScrollPositionThresholdChange = nil
|
|
1859
1977
|
}
|
|
1860
1978
|
|
|
1861
1979
|
private func createActionAnchor(origin: NativeListActionOrigin) -> [String: Any]? {
|
|
@@ -1921,6 +2039,9 @@ final class NativeListView: UIView {
|
|
|
1921
2039
|
}
|
|
1922
2040
|
|
|
1923
2041
|
@objc private func refreshTriggered() {
|
|
2042
|
+
guard !disposed, config?.pullToRefresh == true,
|
|
2043
|
+
config?.refreshing != true, !refreshEmittedForDrag else { return }
|
|
2044
|
+
refreshEmittedForDrag = true
|
|
1924
2045
|
emit(onRowAction, ["actionKey": "nativeList.refresh"])
|
|
1925
2046
|
}
|
|
1926
2047
|
|
|
@@ -2142,7 +2263,31 @@ extension NativeListView: UICollectionViewDelegateFlowLayout {
|
|
|
2142
2263
|
return item.isRowPressEnabled
|
|
2143
2264
|
}
|
|
2144
2265
|
|
|
2266
|
+
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
2267
|
+
minimumDragOffset = 0
|
|
2268
|
+
refreshEmittedForDrag = false
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
|
2272
|
+
defer { if !decelerate { scheduleContentShrinkCorrection() } }
|
|
2273
|
+
minimumDragOffset = min(minimumDragOffset, scrollView.contentOffset.y + scrollView.contentInset.top)
|
|
2274
|
+
guard let threshold = config?.refreshTriggerDistance,
|
|
2275
|
+
config?.pullToRefresh == true, config?.refreshing != true,
|
|
2276
|
+
minimumDragOffset <= -threshold, !refreshEmittedForDrag else { return }
|
|
2277
|
+
collectionView.refreshControl?.beginRefreshing()
|
|
2278
|
+
refreshTriggered()
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
2282
|
+
scheduleContentShrinkCorrection()
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2145
2285
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
2286
|
+
if scrollView.isDragging {
|
|
2287
|
+
minimumDragOffset = min(minimumDragOffset, scrollView.contentOffset.y + scrollView.contentInset.top)
|
|
2288
|
+
}
|
|
2289
|
+
layoutContainerSlots()
|
|
2290
|
+
emitScrollPositionThresholdIfNeeded()
|
|
2146
2291
|
invalidateActionAnchor(reason: "scroll")
|
|
2147
2292
|
syncSectionIndexToVisibleRows()
|
|
2148
2293
|
emitVisibleRangeIfNeeded()
|
|
@@ -2205,6 +2350,13 @@ extension NativeListView: UICollectionViewDragDelegate, UICollectionViewDropDele
|
|
|
2205
2350
|
// quick tap highlighted and unhighlighted a row within one frame and the pressed background never
|
|
2206
2351
|
// rendered. Deliver touches immediately, as Android rows do.
|
|
2207
2352
|
private final class NativeListCollectionView: UICollectionView {
|
|
2353
|
+
var onContentLayout: (() -> Void)?
|
|
2354
|
+
|
|
2355
|
+
override func layoutSubviews() {
|
|
2356
|
+
super.layoutSubviews()
|
|
2357
|
+
onContentLayout?()
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2208
2360
|
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
|
|
2209
2361
|
super.init(frame: frame, collectionViewLayout: layout)
|
|
2210
2362
|
delaysContentTouches = false
|
|
@@ -2223,6 +2375,7 @@ private final class NativeListCollectionView: UICollectionView {
|
|
|
2223
2375
|
|
|
2224
2376
|
final class NativeListFlowLayout: UICollectionViewFlowLayout {
|
|
2225
2377
|
var stickyItemIndexes: Set<Int> = []
|
|
2378
|
+
var minimumContainerContentHeight: CGFloat = 0
|
|
2226
2379
|
private var horizontalAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
|
|
2227
2380
|
private var horizontalContentSize: CGSize = .zero
|
|
2228
2381
|
|
|
@@ -2251,7 +2404,9 @@ final class NativeListFlowLayout: UICollectionViewFlowLayout {
|
|
|
2251
2404
|
}
|
|
2252
2405
|
|
|
2253
2406
|
override var collectionViewContentSize: CGSize {
|
|
2254
|
-
scrollDirection == .horizontal
|
|
2407
|
+
if scrollDirection == .horizontal { return horizontalContentSize }
|
|
2408
|
+
let size = super.collectionViewContentSize
|
|
2409
|
+
return CGSize(width: size.width, height: max(size.height, minimumContainerContentHeight))
|
|
2255
2410
|
}
|
|
2256
2411
|
|
|
2257
2412
|
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import NativeListModule
|
|
4
|
+
|
|
5
|
+
@MainActor
|
|
6
|
+
final class NativeListActivityPresentationTests: XCTestCase {
|
|
7
|
+
func testSmallNumberRunsPreserveBaselineAcrossAmountsFiatAndFee() throws {
|
|
8
|
+
func segments(_ prefix: String, _ suffix: String) -> [[String: Any]] {
|
|
9
|
+
[["text": prefix], ["text": "5", "style": "subscript"], ["text": suffix]]
|
|
10
|
+
}
|
|
11
|
+
let item = try NativeListItem(data: [
|
|
12
|
+
"type": "activity", "key": "transfer", "title": "Transfer",
|
|
13
|
+
"leading": ["kind": "icon", "name": "ArrowTopOutline"],
|
|
14
|
+
"amounts": [[
|
|
15
|
+
"key": "receive", "text": "0.051 ETH", "textSegments": segments("0.0", "1 ETH"),
|
|
16
|
+
"secondaryText": "$0.051", "secondaryTextSegments": segments("$0.0", "1"),
|
|
17
|
+
]],
|
|
18
|
+
"fee": [
|
|
19
|
+
"primary": "0.051 GAS", "primaryTextSegments": segments("0.0", "1 GAS"),
|
|
20
|
+
"secondary": "€0.051", "secondaryTextSegments": segments("€0.0", "1"),
|
|
21
|
+
],
|
|
22
|
+
])
|
|
23
|
+
let view = NativeListActivityDetailsView()
|
|
24
|
+
view.bind(item, theme: nil)
|
|
25
|
+
func labels(in view: UIView) -> [UILabel] {
|
|
26
|
+
(view as? UILabel).map { [$0] } ?? view.subviews.flatMap { labels(in: $0) }
|
|
27
|
+
}
|
|
28
|
+
let cases: [(String, Int, CGFloat)] = [
|
|
29
|
+
("0.051 ETH", 3, 16), ("$0.051", 4, 14),
|
|
30
|
+
("0.051 GAS", 3, 14), ("€0.051", 4, 12),
|
|
31
|
+
]
|
|
32
|
+
let rendered = labels(in: view)
|
|
33
|
+
for (text, countIndex, parentSize) in cases {
|
|
34
|
+
let label = try XCTUnwrap(rendered.first { $0.attributedText?.string == text })
|
|
35
|
+
let value = try XCTUnwrap(label.attributedText)
|
|
36
|
+
let font = try XCTUnwrap(value.attribute(.font, at: countIndex, effectiveRange: nil) as? UIFont)
|
|
37
|
+
XCTAssertEqual(font.pointSize, ceil(parentSize * 0.6))
|
|
38
|
+
let baseline = value.attribute(.baselineOffset, at: 0, effectiveRange: nil) as? NSNumber
|
|
39
|
+
let countBaseline = value.attribute(.baselineOffset, at: countIndex, effectiveRange: nil) as? NSNumber
|
|
40
|
+
XCTAssertEqual(countBaseline?.doubleValue ?? 0, baseline?.doubleValue ?? 0)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -4,6 +4,42 @@ import XCTest
|
|
|
4
4
|
|
|
5
5
|
@MainActor
|
|
6
6
|
final class NativeListLifecycleTests: XCTestCase {
|
|
7
|
+
func testSectionLoadingStopsOnRebindReuseAndDetach() throws {
|
|
8
|
+
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
9
|
+
let controller = UIViewController()
|
|
10
|
+
window.rootViewController = controller
|
|
11
|
+
window.isHidden = false
|
|
12
|
+
defer { window.isHidden = true }
|
|
13
|
+
let cell = NativeListSectionHeaderCell(frame: CGRect(x: 0, y: 0, width: 320, height: 40))
|
|
14
|
+
controller.view.addSubview(cell)
|
|
15
|
+
func bind(loading: Bool) throws {
|
|
16
|
+
let item = try NativeListItem(data: [
|
|
17
|
+
"key": "section", "type": "sectionHeader", "sectionKey": "pending",
|
|
18
|
+
"title": "Confirming", "variant": "history", "titleLoading": loading,
|
|
19
|
+
"style": ["title": ["color": "#FFAA00"]],
|
|
20
|
+
])
|
|
21
|
+
cell.bind(item: item, theme: nil, layout: "sectioned", selected: false,
|
|
22
|
+
checkboxState: { _, _, fallback in fallback })
|
|
23
|
+
}
|
|
24
|
+
try bind(loading: true)
|
|
25
|
+
let spinner = try XCTUnwrap(cell.root.arrangedSubviews.first as? UIActivityIndicatorView)
|
|
26
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
27
|
+
XCTAssertFalse(spinner.isAccessibilityElement)
|
|
28
|
+
XCTAssertEqual(spinner.color, UIColor(nativeListHex: "#FFAA00", fallback: .clear))
|
|
29
|
+
|
|
30
|
+
try bind(loading: false)
|
|
31
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
32
|
+
XCTAssertNil(spinner.superview)
|
|
33
|
+
try bind(loading: true)
|
|
34
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
35
|
+
cell.removeFromSuperview()
|
|
36
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
37
|
+
controller.view.addSubview(cell)
|
|
38
|
+
XCTAssertTrue(spinner.isAnimating)
|
|
39
|
+
cell.prepareForReuse()
|
|
40
|
+
XCTAssertFalse(spinner.isAnimating)
|
|
41
|
+
}
|
|
42
|
+
|
|
7
43
|
func testRebindingAndReuseInvalidateThePreviousActionEpoch() throws {
|
|
8
44
|
let cell = NativeListActionCell(frame: CGRect(x: 0, y: 0, width: 320, height: 60))
|
|
9
45
|
let first = try NativeListItem(data: ["key": "row", "type": "action", "title": "First"])
|
|
@@ -82,6 +118,93 @@ final class NativeListLifecycleTests: XCTestCase {
|
|
|
82
118
|
return String(decoding: data, as: UTF8.self)
|
|
83
119
|
}
|
|
84
120
|
|
|
121
|
+
func testContentShrinkCorrectsRestoredOffsetAfterPagerInsetsAndUpdatesObserver() throws {
|
|
122
|
+
let view = try makeScrollingList()
|
|
123
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
124
|
+
list.contentInset = UIEdgeInsets(top: 108, left: 0, bottom: 0, right: 0)
|
|
125
|
+
list.contentOffset.y = 600
|
|
126
|
+
var savedLogicalOffset = list.contentOffset.y + list.contentInset.top
|
|
127
|
+
let offsetObserver = list.observe(\.contentOffset, options: [.new]) { list, _ in
|
|
128
|
+
savedLogicalOffset = list.contentOffset.y + list.contentInset.top
|
|
129
|
+
}
|
|
130
|
+
// Model the pager's synchronous content-size observer and cached-offset restoration.
|
|
131
|
+
let sizeObserver = list.observe(\.contentSize, options: [.old, .new]) { list, change in
|
|
132
|
+
if change.oldValue != change.newValue, list.contentSize.height < 200 {
|
|
133
|
+
list.contentInset.bottom = max(0, list.bounds.height - 44 - list.contentSize.height)
|
|
134
|
+
list.contentOffset.y = 600
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 0))
|
|
138
|
+
view.layoutIfNeeded()
|
|
139
|
+
list.layoutIfNeeded()
|
|
140
|
+
drainMainQueue()
|
|
141
|
+
XCTAssertEqual(list.contentSize.height, 132, accuracy: 0.5)
|
|
142
|
+
XCTAssertEqual(list.contentOffset.y, -44, accuracy: 0.5)
|
|
143
|
+
XCTAssertEqual(savedLogicalOffset, 64, accuracy: 0.5)
|
|
144
|
+
withExtendedLifetime((offsetObserver, sizeObserver)) {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func testContentShrinkPreservesInRangeAndNegativePullOffsets() throws {
|
|
148
|
+
for offset in [CGFloat(120), CGFloat(-160)] {
|
|
149
|
+
let view = try makeScrollingList()
|
|
150
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
151
|
+
list.contentInset.top = 108
|
|
152
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 20))
|
|
153
|
+
view.layoutIfNeeded()
|
|
154
|
+
list.layoutIfNeeded()
|
|
155
|
+
// Preserve a valid offset or a refresh pull while shrink correction is queued.
|
|
156
|
+
list.contentOffset.y = offset
|
|
157
|
+
drainMainQueue()
|
|
158
|
+
XCTAssertEqual(list.contentOffset.y, offset, accuracy: 0.5)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func testContentShrinkWaitsForRefreshCompletion() throws {
|
|
163
|
+
let view = try makeScrollingList()
|
|
164
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
165
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 0, refreshing: true))
|
|
166
|
+
view.layoutIfNeeded()
|
|
167
|
+
list.layoutIfNeeded()
|
|
168
|
+
list.contentOffset.y = 600
|
|
169
|
+
drainMainQueue()
|
|
170
|
+
XCTAssertEqual(list.contentOffset.y, 600, accuracy: 0.5)
|
|
171
|
+
view.setRefreshing(false)
|
|
172
|
+
drainMainQueue()
|
|
173
|
+
let inset = list.adjustedContentInset
|
|
174
|
+
let maximum = max(-inset.top, list.contentSize.height - list.bounds.height + inset.bottom)
|
|
175
|
+
XCTAssertEqual(list.contentOffset.y, maximum, accuracy: 0.5)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func makeScrollingList() throws -> NativeListView {
|
|
179
|
+
let view = NativeListView(frame: CGRect(x: 0, y: 0, width: 320, height: 708))
|
|
180
|
+
for index in 0..<3 { view.mountContainerSlot(UIView(), atIndex: index) }
|
|
181
|
+
view.setContainerSlotHeightsJson("[44,44,44]")
|
|
182
|
+
view.applySnapshotJson(try scrollingSnapshot(count: 40))
|
|
183
|
+
view.layoutIfNeeded()
|
|
184
|
+
let list = try XCTUnwrap(view.subviews.compactMap { $0 as? UICollectionView }.first)
|
|
185
|
+
list.layoutIfNeeded()
|
|
186
|
+
drainMainQueue()
|
|
187
|
+
return view
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func scrollingSnapshot(count: Int, refreshing: Bool = false) throws -> String {
|
|
191
|
+
let rows: [[String: Any]] = (0..<count).map {
|
|
192
|
+
["key": "row-\($0)", "type": "action", "title": "Row", "height": 80]
|
|
193
|
+
}
|
|
194
|
+
let value: [String: Any] = [
|
|
195
|
+
"schemaVersion": 1, "generation": count + 1,
|
|
196
|
+
"layout": ["kind": "linear"], "rows": rows,
|
|
197
|
+
"capabilities": ["pullToRefresh": true, "refreshing": refreshing],
|
|
198
|
+
]
|
|
199
|
+
return String(decoding: try JSONSerialization.data(withJSONObject: value), as: UTF8.self)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private func drainMainQueue() {
|
|
203
|
+
let settled = expectation(description: "Deferred content shrink correction")
|
|
204
|
+
DispatchQueue.main.async { settled.fulfill() }
|
|
205
|
+
wait(for: [settled], timeout: 1)
|
|
206
|
+
}
|
|
207
|
+
|
|
85
208
|
private func footerCell(in view: NativeListView) -> NativeListRowHost? {
|
|
86
209
|
view.subviews.lazy.compactMap { container in
|
|
87
210
|
container.subviews.compactMap { $0 as? NativeListRowHost }.first
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import NativeListModule
|
|
4
|
+
|
|
5
|
+
@MainActor
|
|
6
|
+
final class NativeListMediaVisibilityTests: XCTestCase {
|
|
7
|
+
func testRetainedPagerAndListCellsRequireViewportIntersection() {
|
|
8
|
+
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
9
|
+
let controller = UIViewController()
|
|
10
|
+
window.rootViewController = controller
|
|
11
|
+
window.isHidden = false
|
|
12
|
+
defer { window.isHidden = true }
|
|
13
|
+
let pager = UIScrollView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
|
|
14
|
+
pager.contentSize = CGSize(width: 640, height: 480)
|
|
15
|
+
controller.view.addSubview(pager)
|
|
16
|
+
let list = UIScrollView(frame: CGRect(x: 320, y: 0, width: 320, height: 480))
|
|
17
|
+
list.contentSize = CGSize(width: 320, height: 960)
|
|
18
|
+
pager.addSubview(list)
|
|
19
|
+
let tile = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
|
|
20
|
+
list.addSubview(tile)
|
|
21
|
+
|
|
22
|
+
XCTAssertNotNil(tile.window, "Pager retains the offscreen cell in the window")
|
|
23
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
24
|
+
pager.contentOffset.x = 320
|
|
25
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
26
|
+
list.contentOffset.y = 110
|
|
27
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
28
|
+
list.contentOffset.y = 100
|
|
29
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile), "Partially visible previews remain eligible")
|
|
30
|
+
list.transform = CGAffineTransform(translationX: 320, y: 0)
|
|
31
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
32
|
+
list.transform = .identity
|
|
33
|
+
XCTAssertTrue(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
34
|
+
list.isHidden = true
|
|
35
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
36
|
+
list.isHidden = false
|
|
37
|
+
list.alpha = 0
|
|
38
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
39
|
+
tile.removeFromSuperview()
|
|
40
|
+
XCTAssertFalse(NativeListMediaPreviewSlot.intersectsViewport(tile))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import NativeListModule
|
|
3
|
+
|
|
4
|
+
final class NativeListScrollPositionTrackerTests: XCTestCase {
|
|
5
|
+
func testHysteresisAndDisabledLifecycle() {
|
|
6
|
+
var tracker = NativeListScrollPositionTracker()
|
|
7
|
+
XCTAssertNil(tracker.update(offset: 200))
|
|
8
|
+
tracker.configure(json: "{\"start\":48,\"end\":160}")
|
|
9
|
+
XCTAssertEqual(tracker.update(offset: 0), false)
|
|
10
|
+
XCTAssertNil(tracker.update(offset: 159))
|
|
11
|
+
XCTAssertEqual(tracker.update(offset: 160), true)
|
|
12
|
+
XCTAssertNil(tracker.update(offset: 100))
|
|
13
|
+
XCTAssertEqual(tracker.update(offset: 48), false)
|
|
14
|
+
XCTAssertNil(tracker.update(offset: 49))
|
|
15
|
+
tracker.configure(json: "")
|
|
16
|
+
XCTAssertNil(tracker.update(offset: 200))
|
|
17
|
+
tracker.configure(json: "{\"start\":48,\"end\":160}")
|
|
18
|
+
XCTAssertEqual(tracker.update(offset: 200), true)
|
|
19
|
+
tracker.resetDelivery()
|
|
20
|
+
XCTAssertEqual(tracker.update(offset: 200), true)
|
|
21
|
+
XCTAssertNil(tracker.update(offset: .nan))
|
|
22
|
+
tracker.configure(json: "{\"start\":48,\"end\":48}")
|
|
23
|
+
XCTAssertNil(tracker.update(offset: 48))
|
|
24
|
+
}
|
|
25
|
+
}
|
package/lib/module/NativeList.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
|
|
3
|
+
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { OneKeyImageCache, OneKeyImageCachePolicy } from '@onekeyfe/react-native-image';
|
|
5
5
|
import { NativeAvatarPrefetchModel, NativeAvatarPrefetchQueue } from "./avatarPrefetch.js";
|
|
6
|
+
import { View } from 'react-native';
|
|
6
7
|
import { callback, getHostComponent } from 'react-native-nitro-modules';
|
|
7
8
|
import { isSelectableRow } from "./selection.js";
|
|
8
9
|
import { normalizeIndexScroll, normalizeKeyScroll, normalizePositionScroll, resolveLocationIndex, scrollFailure, validateOffset } from "./scrolling.js";
|
|
@@ -26,6 +27,10 @@ function parsePayload(payloadJson) {
|
|
|
26
27
|
}
|
|
27
28
|
export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
28
29
|
snapshot,
|
|
30
|
+
listHeader,
|
|
31
|
+
listFooter,
|
|
32
|
+
listEmpty,
|
|
33
|
+
scrollPositionThresholds,
|
|
29
34
|
webVirtualizationEnabled: _webVirtualizationEnabled,
|
|
30
35
|
webSectionIndexContainerRef: _webSectionIndexContainerRef,
|
|
31
36
|
keyboardDismissMode = 'none',
|
|
@@ -36,6 +41,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
36
41
|
onReorder,
|
|
37
42
|
onEndReached,
|
|
38
43
|
onVisibleRangeChanged,
|
|
44
|
+
onScrollPositionThresholdChange,
|
|
39
45
|
onRefresh,
|
|
40
46
|
onScrollToIndexFailed,
|
|
41
47
|
initialScrollIndex,
|
|
@@ -44,6 +50,13 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
44
50
|
initialScrollViewOffset,
|
|
45
51
|
...viewProps
|
|
46
52
|
}, forwardedRef) {
|
|
53
|
+
const [slotHeights, setSlotHeights] = useState([0, 0, 0]);
|
|
54
|
+
const containerSlotHeightsJson = JSON.stringify(slotHeights);
|
|
55
|
+
const slotInset = snapshot.layout.contentPaddingHorizontal ?? snapshot.layout.contentPadding ?? 0;
|
|
56
|
+
if (snapshot.layout.orientation === 'horizontal' && (listHeader || listEmpty || listFooter)) {
|
|
57
|
+
throw new Error('NativeList container slots require vertical orientation');
|
|
58
|
+
}
|
|
59
|
+
const slotContents = [listHeader, listEmpty, listFooter];
|
|
47
60
|
const normalizedKeyboardShouldPersistTaps = keyboardShouldPersistTaps === true ? 'always' : keyboardShouldPersistTaps === false ? 'never' : keyboardShouldPersistTaps;
|
|
48
61
|
const nativeRef = useRef(null);
|
|
49
62
|
const callbacksRef = useRef({
|
|
@@ -53,6 +66,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
53
66
|
onReorder,
|
|
54
67
|
onEndReached,
|
|
55
68
|
onVisibleRangeChanged,
|
|
69
|
+
onScrollPositionThresholdChange,
|
|
56
70
|
onRefresh,
|
|
57
71
|
onScrollToIndexFailed
|
|
58
72
|
});
|
|
@@ -63,9 +77,24 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
63
77
|
onReorder,
|
|
64
78
|
onEndReached,
|
|
65
79
|
onVisibleRangeChanged,
|
|
80
|
+
onScrollPositionThresholdChange,
|
|
66
81
|
onRefresh,
|
|
67
82
|
onScrollToIndexFailed
|
|
68
83
|
};
|
|
84
|
+
const scrollPositionThresholdsJson = useMemo(() => {
|
|
85
|
+
if (!scrollPositionThresholds || scrollPositionThresholds.enabled === false) return '';
|
|
86
|
+
const {
|
|
87
|
+
start,
|
|
88
|
+
end
|
|
89
|
+
} = scrollPositionThresholds;
|
|
90
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end <= start) {
|
|
91
|
+
throw new Error('scrollPositionThresholds requires 0 <= start < end and finite values');
|
|
92
|
+
}
|
|
93
|
+
return JSON.stringify({
|
|
94
|
+
start,
|
|
95
|
+
end
|
|
96
|
+
});
|
|
97
|
+
}, [scrollPositionThresholds]);
|
|
69
98
|
const snapshotJson = useMemo(() => serializeSnapshot(snapshot), [snapshot]);
|
|
70
99
|
const snapshotRef = useRef(snapshot);
|
|
71
100
|
const previousSnapshotJsonRef = useRef(snapshotJson);
|
|
@@ -235,7 +264,10 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
235
264
|
}),
|
|
236
265
|
onRowAction: callback(payloadJson => {
|
|
237
266
|
const payload = parsePayload(payloadJson);
|
|
238
|
-
if (payload.actionKey === 'nativeList.refresh'
|
|
267
|
+
if (payload.actionKey === 'nativeList.refresh' && payload.rowKey === undefined) {
|
|
268
|
+
callbacksRef.current.onRefresh?.();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
239
271
|
callbacksRef.current.onRowAction?.(payload);
|
|
240
272
|
}),
|
|
241
273
|
onActionAnchorInvalidated: callback(payloadJson => {
|
|
@@ -264,6 +296,11 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
264
296
|
onEndReached: callback(payloadJson => {
|
|
265
297
|
callbacksRef.current.onEndReached?.(parsePayload(payloadJson));
|
|
266
298
|
}),
|
|
299
|
+
onScrollPositionThresholdChange: callback(isBeyondThreshold => {
|
|
300
|
+
callbacksRef.current.onScrollPositionThresholdChange?.({
|
|
301
|
+
isBeyondThreshold
|
|
302
|
+
});
|
|
303
|
+
}),
|
|
267
304
|
onVisibleRangeChanged: callback(payloadJson => {
|
|
268
305
|
const payload = parsePayload(payloadJson);
|
|
269
306
|
const previous = avatarRangeRef.current;
|
|
@@ -280,8 +317,24 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
|
280
317
|
...viewProps,
|
|
281
318
|
...nativeCallbacks,
|
|
282
319
|
snapshotJson: snapshotJson,
|
|
320
|
+
containerSlotHeightsJson: containerSlotHeightsJson,
|
|
321
|
+
scrollPositionThresholdsJson: scrollPositionThresholdsJson,
|
|
283
322
|
keyboardDismissMode: keyboardDismissMode,
|
|
284
|
-
keyboardShouldPersistTaps: normalizedKeyboardShouldPersistTaps
|
|
323
|
+
keyboardShouldPersistTaps: normalizedKeyboardShouldPersistTaps,
|
|
324
|
+
children: slotContents.map((content, index) => /*#__PURE__*/_jsx(View, {
|
|
325
|
+
collapsable: false,
|
|
326
|
+
style: {
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
left: slotInset,
|
|
329
|
+
right: slotInset,
|
|
330
|
+
top: 0
|
|
331
|
+
},
|
|
332
|
+
onLayout: event => {
|
|
333
|
+
const height = Math.ceil(event.nativeEvent.layout.height);
|
|
334
|
+
setSlotHeights(previous => previous[index] === height ? previous : previous.map((value, slot) => slot === index ? height : value));
|
|
335
|
+
},
|
|
336
|
+
children: content
|
|
337
|
+
}, index))
|
|
285
338
|
});
|
|
286
339
|
});
|
|
287
340
|
//# sourceMappingURL=NativeList.js.map
|
|
@@ -22,6 +22,11 @@ export function preloadNativeListAvatarImages(sources) {
|
|
|
22
22
|
}
|
|
23
23
|
export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
|
|
24
24
|
snapshot,
|
|
25
|
+
listHeader: _listHeader,
|
|
26
|
+
listEmpty: _listEmpty,
|
|
27
|
+
listFooter: _listFooter,
|
|
28
|
+
scrollPositionThresholds: _scrollPositionThresholds,
|
|
29
|
+
onScrollPositionThresholdChange: _onScrollPositionThresholdChange,
|
|
25
30
|
webVirtualizationEnabled = true,
|
|
26
31
|
webSectionIndexContainerRef,
|
|
27
32
|
keyboardDismissMode: _keyboardDismissMode,
|