@onekeyfe/react-native-pager-view 6.7.0-1 → 6.7.0-12
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/README.md +9 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +2 -2
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +2 -2
- package/docs/SPEC.md +108 -7
- package/ios/RNCCollapsiblePagerViewComponentView.mm +341 -23
- package/ios/RNCNativeScrollerComponentView.mm +30 -5
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +124 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +186 -1
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/ios/tests/RNCNativeScrollerTests.mm +39 -0
- package/lib/module/CollapsiblePagerView.js +16 -7
- package/lib/module/CollapsiblePagerView.web.js +428 -37
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/lib/module/NativeScroller.js +2 -2
- package/lib/module/NativeScroller.web.js +16 -3
- package/lib/module/NativeScrollerNativeComponent.ts +1 -1
- package/lib/module/ScrollEdgeEffect.js +8 -0
- package/lib/module/WebCollapsiblePagerPage.js +182 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.native.js +9 -0
- package/lib/module/index.web.js +3 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +8 -5
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +66 -4
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +1 -1
- package/lib/typescript/src/NativeScroller.d.ts +3 -2
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +1 -1
- package/lib/typescript/src/ScrollEdgeEffect.d.ts +5 -0
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +42 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.native.d.ts +13 -0
- package/lib/typescript/src/index.web.d.ts +5 -0
- package/package.json +6 -4
- package/src/CollapsiblePagerView.tsx +43 -11
- package/src/CollapsiblePagerView.web.tsx +640 -50
- package/src/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/src/NativeScroller.tsx +6 -4
- package/src/NativeScroller.web.tsx +24 -2
- package/src/NativeScrollerNativeComponent.ts +1 -1
- package/src/ScrollEdgeEffect.ts +4 -0
- package/src/WebCollapsiblePagerPage.tsx +258 -0
- package/src/index.native.tsx +30 -0
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +6 -0
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
|
|
9
9
|
using namespace facebook::react;
|
|
10
10
|
|
|
11
|
+
static void RNCNativeScrollerHomeVisualLog(NSString *phase, UIScrollView *scrollView)
|
|
12
|
+
{
|
|
13
|
+
#if DEBUG
|
|
14
|
+
Class logClass = NSClassFromString(@"ReactNativeNativeLogger.OneKeyLog");
|
|
15
|
+
if (logClass == nil) logClass = NSClassFromString(@"OneKeyLog");
|
|
16
|
+
SEL selector = NSSelectorFromString(@"debug::");
|
|
17
|
+
if (logClass == nil || ![logClass respondsToSelector:selector]) return;
|
|
18
|
+
UIRefreshControl *control = scrollView.refreshControl;
|
|
19
|
+
NSString *message = [NSString stringWithFormat:
|
|
20
|
+
@"[IOS-HOME-VISUAL] epochMs=%.0f monoMs=%.0f phase=scroller-%@ scrollTag=%ld refresh=%d refreshing=%d scrollY=%.1f insetTop=%.1f",
|
|
21
|
+
[NSDate.date timeIntervalSince1970] * 1000, CACurrentMediaTime() * 1000,
|
|
22
|
+
phase, (long)scrollView.tag, control != nil, control.isRefreshing,
|
|
23
|
+
scrollView.contentOffset.y, scrollView.contentInset.top];
|
|
24
|
+
typedef void (*LogFunction)(id, SEL, NSString *, NSString *);
|
|
25
|
+
LogFunction logFunction = (LogFunction)[logClass methodForSelector:selector];
|
|
26
|
+
logFunction(logClass, selector, @"NativeScroller", message);
|
|
27
|
+
#endif
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
@interface RNCNativeVerticalScrollView : UIScrollView
|
|
12
31
|
@end
|
|
13
32
|
@implementation RNCNativeVerticalScrollView
|
|
@@ -156,6 +175,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
156
175
|
[_refreshControl addTarget:self action:@selector(refreshTriggered:)
|
|
157
176
|
forControlEvents:UIControlEventValueChanged];
|
|
158
177
|
_scrollView.refreshControl = _refreshControl;
|
|
178
|
+
RNCNativeScrollerHomeVisualLog(@"refresh-created", _scrollView);
|
|
159
179
|
} else if (!next.refreshEnabled && _refreshControl != nil) {
|
|
160
180
|
[_refreshControl endRefreshing];
|
|
161
181
|
_scrollView.refreshControl = nil;
|
|
@@ -174,8 +194,10 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
174
194
|
}
|
|
175
195
|
if (next.refreshing && !_refreshControl.refreshing) {
|
|
176
196
|
[_refreshControl beginRefreshing];
|
|
197
|
+
RNCNativeScrollerHomeVisualLog(@"refresh-begin-prop", _scrollView);
|
|
177
198
|
} else if (!next.refreshing && (previous.refreshing || createdRefreshControl)) {
|
|
178
199
|
[_refreshControl endRefreshing];
|
|
200
|
+
if (previous.refreshing) RNCNativeScrollerHomeVisualLog(@"refresh-end-prop", _scrollView);
|
|
179
201
|
}
|
|
180
202
|
}
|
|
181
203
|
|
|
@@ -196,7 +218,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
196
218
|
BOOL refreshEnded = previous.refreshing && !next.refreshing;
|
|
197
219
|
[super updateProps:props oldProps:oldProps];
|
|
198
220
|
[self updateTopScrollEdge];
|
|
199
|
-
if (next.
|
|
221
|
+
if ((next.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && _topOverlay == nil) {
|
|
200
222
|
__weak __typeof__(self) weakSelf = self;
|
|
201
223
|
dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf updateTopScrollEdge]; });
|
|
202
224
|
}
|
|
@@ -210,7 +232,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
210
232
|
if (@available(iOS 26.0, *)) {
|
|
211
233
|
const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
|
|
212
234
|
UIView *overlay = nil;
|
|
213
|
-
if (props.
|
|
235
|
+
if ((props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled) {
|
|
214
236
|
NSUInteger fabricSiblings = 0;
|
|
215
237
|
for (UIView *candidate in self.superview.subviews) {
|
|
216
238
|
if (![candidate conformsToProtocol:@protocol(RCTComponentViewProtocol)]) continue;
|
|
@@ -220,7 +242,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
220
242
|
if (fabricSiblings != 2) overlay = nil;
|
|
221
243
|
}
|
|
222
244
|
_topOverlay = overlay;
|
|
223
|
-
CGFloat requestedOverlayInset = props.
|
|
245
|
+
CGFloat requestedOverlayInset = (props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled && overlay != nil &&
|
|
224
246
|
isfinite(props.topOverlayHeight) ? MAX(0, props.topOverlayHeight) : 0;
|
|
225
247
|
if (_appliedOverlayInset != requestedOverlayInset) {
|
|
226
248
|
// Keep UIKit's refresh rebound animation in control while it is overscrolled.
|
|
@@ -236,7 +258,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
236
258
|
}
|
|
237
259
|
[self emitContentViewportChangeIfNeeded];
|
|
238
260
|
}
|
|
239
|
-
BOOL enabled = props.
|
|
261
|
+
BOOL enabled = (props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled && overlay != nil && self.window != nil;
|
|
240
262
|
if (!enabled || _topEdgeInteractionHost != overlay) {
|
|
241
263
|
if (_topEdgeInteraction != nil) {
|
|
242
264
|
[_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
|
|
@@ -271,7 +293,8 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
271
293
|
[overlay addInteraction:_topEdgeInteraction];
|
|
272
294
|
_topEdgeInteractionHost = overlay;
|
|
273
295
|
}
|
|
274
|
-
_scrollView.topEdgeEffect.style =
|
|
296
|
+
_scrollView.topEdgeEffect.style = props.topScrollEdgeEffect == RNCNativeScrollerTopScrollEdgeEffect::Soft
|
|
297
|
+
? UIScrollEdgeEffectStyle.softStyle : UIScrollEdgeEffectStyle.hardStyle;
|
|
275
298
|
}
|
|
276
299
|
}
|
|
277
300
|
|
|
@@ -378,6 +401,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
378
401
|
const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
|
|
379
402
|
if (!props.refreshEnabled || props.refreshing) return;
|
|
380
403
|
_refreshEventSentForDrag = YES;
|
|
404
|
+
RNCNativeScrollerHomeVisualLog(@"refresh-native-trigger", _scrollView);
|
|
381
405
|
if (_eventEmitter) {
|
|
382
406
|
std::static_pointer_cast<const RNCNativeScrollerEventEmitter>(_eventEmitter)->onRefresh({});
|
|
383
407
|
}
|
|
@@ -522,6 +546,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
522
546
|
- (void)setRefreshing:(BOOL)refreshing
|
|
523
547
|
{
|
|
524
548
|
if (_recycled || _refreshControl == nil) return;
|
|
549
|
+
RNCNativeScrollerHomeVisualLog(refreshing ? @"refresh-command-start" : @"refresh-command-stop", _scrollView);
|
|
525
550
|
if (refreshing) {
|
|
526
551
|
if (!_refreshControl.refreshing) [_refreshControl beginRefreshing];
|
|
527
552
|
} else {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import <XCTest/XCTest.h>
|
|
2
2
|
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
3
3
|
#import "RNCNativeScrollerComponentView.h"
|
|
4
|
+
#import <react/renderer/components/pagerview/Props.h>
|
|
4
5
|
#import <react/renderer/mounting/MountingTransaction.h>
|
|
5
6
|
|
|
6
7
|
using namespace facebook::react;
|
|
@@ -22,6 +23,10 @@ using namespace facebook::react;
|
|
|
22
23
|
- (void)updateHeaderSettledStateForceBusy:(BOOL)forceBusy;
|
|
23
24
|
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)index;
|
|
24
25
|
- (void)applyInsetsToScrollView:(UIScrollView *)scrollView pageIndex:(NSInteger)index restore:(BOOL)restore;
|
|
26
|
+
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps;
|
|
27
|
+
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index;
|
|
28
|
+
- (CGFloat)effectiveTopOverlayHeight;
|
|
29
|
+
- (void)updateRefreshControlForScrollView:(UIScrollView *)scrollView;
|
|
25
30
|
@end
|
|
26
31
|
|
|
27
32
|
@interface RNCNativeScrollerComponentView (ScrollCommandTesting)
|
|
@@ -34,6 +39,125 @@ using namespace facebook::react;
|
|
|
34
39
|
|
|
35
40
|
@implementation RNCCollapsiblePagerFirstFrameTests
|
|
36
41
|
|
|
42
|
+
- (void)testPendingLocalOverlayReservesManagedScrollerInsetBeforeSlotRebuild
|
|
43
|
+
{
|
|
44
|
+
RNCCollapsiblePagerViewComponentView *pager =
|
|
45
|
+
[[RNCCollapsiblePagerViewComponentView alloc] initWithFrame:CGRectMake(0, 0, 320, 844)];
|
|
46
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:pager.frame];
|
|
47
|
+
UIViewController *root = [UIViewController new];
|
|
48
|
+
window.rootViewController = root;
|
|
49
|
+
[root.view addSubview:pager];
|
|
50
|
+
window.hidden = NO;
|
|
51
|
+
[self addTeardownBlock:^{
|
|
52
|
+
[pager prepareForRecycle];
|
|
53
|
+
window.hidden = YES;
|
|
54
|
+
}];
|
|
55
|
+
|
|
56
|
+
auto baseline = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
57
|
+
baseline->nativeSmoothHeaderScrollEnabled = true;
|
|
58
|
+
baseline->headerHeight = 120;
|
|
59
|
+
baseline->stickyHeaderHeight = 44;
|
|
60
|
+
baseline->pageKeys = "[\"page-0\"]";
|
|
61
|
+
[pager updateProps:baseline oldProps:nullptr];
|
|
62
|
+
|
|
63
|
+
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 120)];
|
|
64
|
+
UIView *sticky = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
|
|
65
|
+
UIView *page = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 844)];
|
|
66
|
+
RNCNativeScrollerComponentView *scroller = [RNCNativeScrollerComponentView new];
|
|
67
|
+
scroller.frame = page.bounds;
|
|
68
|
+
scroller.scrollView.frame = scroller.bounds;
|
|
69
|
+
scroller.scrollView.contentSize = CGSizeMake(320, 1500);
|
|
70
|
+
[page addSubview:scroller];
|
|
71
|
+
[pager mountChildComponentView:(id)header index:0];
|
|
72
|
+
[pager mountChildComponentView:(id)sticky index:1];
|
|
73
|
+
[pager mountChildComponentView:(id)page index:2];
|
|
74
|
+
[root.view layoutIfNeeded];
|
|
75
|
+
[pager layoutIfNeeded];
|
|
76
|
+
XCTestExpectation *settled = [self expectationWithDescription:@"Baseline slots and observer settled"];
|
|
77
|
+
dispatch_async(dispatch_get_main_queue(), ^{ [settled fulfill]; });
|
|
78
|
+
[self waitForExpectations:@[settled] timeout:1];
|
|
79
|
+
[pager attachScrollObserverForCurrentPage];
|
|
80
|
+
UIScrollView *ownedScroll = scroller.scrollView;
|
|
81
|
+
XCTAssertEqual([pager valueForKey:@"observedScrollView"], ownedScroll);
|
|
82
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentInset.top, 164, 0.01);
|
|
83
|
+
[scroller scrollTo:0 y:40 - ownedScroll.contentInset.top animated:NO];
|
|
84
|
+
[pager setValue:@40 forKey:@"headerOffset"];
|
|
85
|
+
CGFloat initialLogicalOffset = ownedScroll.contentOffset.y + ownedScroll.contentInset.top;
|
|
86
|
+
XCTAssertEqualWithAccuracy(initialLogicalOffset, 40, 0.01);
|
|
87
|
+
|
|
88
|
+
auto hard = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
89
|
+
hard->nativeSmoothHeaderScrollEnabled = true;
|
|
90
|
+
hard->headerHeight = 120;
|
|
91
|
+
hard->stickyHeaderHeight = 44;
|
|
92
|
+
hard->pageKeys = "[\"page-0\"]";
|
|
93
|
+
hard->topScrollEdgeEffect = RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard;
|
|
94
|
+
hard->topOverlayHeight = 162;
|
|
95
|
+
[pager updateProps:hard oldProps:baseline];
|
|
96
|
+
XCTAssertNil([pager valueForKey:@"topOverlayView"]);
|
|
97
|
+
XCTAssertEqual([pager valueForKey:@"observedScrollView"], ownedScroll);
|
|
98
|
+
XCTAssertEqualWithAccuracy([pager effectiveTopOverlayHeight], 162, 0.01);
|
|
99
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentInset.top, 326, 0.01);
|
|
100
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentOffset.y + ownedScroll.contentInset.top,
|
|
101
|
+
initialLogicalOffset, 0.01);
|
|
102
|
+
[self finishMount:pager];
|
|
103
|
+
XCTAssertNil([pager valueForKey:@"topOverlayView"]);
|
|
104
|
+
XCTAssertTrue([[pager valueForKey:@"deferTopEdgeSlotRebuild"] boolValue]);
|
|
105
|
+
|
|
106
|
+
UIView *overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 162)];
|
|
107
|
+
[pager mountChildComponentView:(id)overlay index:2];
|
|
108
|
+
XCTAssertNil([pager valueForKey:@"topOverlayView"]);
|
|
109
|
+
[self finishMount:pager];
|
|
110
|
+
UIView *overlayHost = [pager valueForKey:@"topOverlayHostView"];
|
|
111
|
+
XCTAssertEqual([pager valueForKey:@"topOverlayView"], overlay);
|
|
112
|
+
XCTAssertEqual(overlay.superview, overlayHost);
|
|
113
|
+
XCTAssertEqual(overlay.window, window);
|
|
114
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentInset.top, 326, 0.01);
|
|
115
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentOffset.y + ownedScroll.contentInset.top,
|
|
116
|
+
initialLogicalOffset, 0.01);
|
|
117
|
+
XCTestExpectation *rebuilt = [self expectationWithDescription:@"Post-mount observer restored"];
|
|
118
|
+
dispatch_async(dispatch_get_main_queue(), ^{ [rebuilt fulfill]; });
|
|
119
|
+
[self waitForExpectations:@[rebuilt] timeout:1];
|
|
120
|
+
[pager attachScrollObserverForCurrentPage];
|
|
121
|
+
XCTAssertEqual([pager valueForKey:@"topOverlayView"], overlay);
|
|
122
|
+
XCTAssertEqual([pager valueForKey:@"observedScrollView"], ownedScroll);
|
|
123
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentInset.top, 326, 0.01);
|
|
124
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentOffset.y + ownedScroll.contentInset.top,
|
|
125
|
+
initialLogicalOffset, 0.01);
|
|
126
|
+
|
|
127
|
+
UIRefreshControl *control = [UIRefreshControl new];
|
|
128
|
+
ownedScroll.refreshControl = control;
|
|
129
|
+
[pager updateRefreshControlForScrollView:ownedScroll];
|
|
130
|
+
UIEdgeInsets refreshingInset = ownedScroll.contentInset;
|
|
131
|
+
refreshingInset.top += 60;
|
|
132
|
+
ownedScroll.contentInset = refreshingInset;
|
|
133
|
+
auto disabled = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
134
|
+
disabled->nativeSmoothHeaderScrollEnabled = true;
|
|
135
|
+
disabled->headerHeight = 120;
|
|
136
|
+
disabled->stickyHeaderHeight = 44;
|
|
137
|
+
disabled->pageKeys = "[\"page-0\"]";
|
|
138
|
+
disabled->topScrollEdgeEffect = RNCCollapsiblePagerViewTopScrollEdgeEffect::None;
|
|
139
|
+
disabled->topOverlayHeight = 0;
|
|
140
|
+
[pager updateProps:disabled oldProps:hard];
|
|
141
|
+
XCTAssertEqual(ownedScroll.refreshControl, control);
|
|
142
|
+
XCTAssertEqualWithAccuracy([pager effectiveTopOverlayHeight], 0, 0.01);
|
|
143
|
+
XCTAssertEqualWithAccuracy(ownedScroll.contentInset.top, 224, 0.01);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)testPendingLocalOverlayHeightDoesNotOverrideExternalGeometry
|
|
147
|
+
{
|
|
148
|
+
RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
|
|
149
|
+
auto props = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
150
|
+
props->topScrollEdgeEffect = RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard;
|
|
151
|
+
props->topOverlayHeight = 162;
|
|
152
|
+
props->externalTopOverlayTag = 999;
|
|
153
|
+
[pager updateProps:props oldProps:nullptr];
|
|
154
|
+
XCTAssertNil([pager valueForKey:@"topOverlayView"]);
|
|
155
|
+
XCTAssertEqualWithAccuracy([pager effectiveTopOverlayHeight], 0, 0.01);
|
|
156
|
+
[pager setValue:@64 forKey:@"externalTopOverlayHeight"];
|
|
157
|
+
XCTAssertEqualWithAccuracy([pager effectiveTopOverlayHeight], 64, 0.01);
|
|
158
|
+
[pager prepareForRecycle];
|
|
159
|
+
}
|
|
160
|
+
|
|
37
161
|
- (void)testHeaderSettledAtTopRequiresCurrentQuietOwnerAndCancelsDetachedTimer
|
|
38
162
|
{
|
|
39
163
|
RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:1];
|
|
@@ -8,6 +8,7 @@ using namespace facebook::react;
|
|
|
8
8
|
|
|
9
9
|
@interface RNCCollapsiblePagerViewComponentView (ScrollEdgeTesting)
|
|
10
10
|
- (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView;
|
|
11
|
+
- (void)updateScrollEdgeContentMasks;
|
|
11
12
|
@end
|
|
12
13
|
|
|
13
14
|
@interface RNCCollapsiblePagerHitTestTests : XCTestCase
|
|
@@ -90,9 +91,17 @@ using namespace facebook::react;
|
|
|
90
91
|
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.pager.bounds];
|
|
91
92
|
[self.pager addSubview:scrollView];
|
|
92
93
|
UIScrollEdgeEffectStyle *originalStyle = scrollView.topEdgeEffect.style;
|
|
93
|
-
[self.pager setValue:@YES forKey:@"
|
|
94
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
95
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
96
|
+
forKey:@"topScrollEdgeEffect"];
|
|
94
97
|
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
95
98
|
[self.pager setValue:@64 forKey:@"externalTopOverlayHeight"];
|
|
99
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
100
|
+
container.frame = self.pager.bounds;
|
|
101
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
102
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 64);
|
|
103
|
+
overlayHost.hidden = NO;
|
|
104
|
+
self.pager.backgroundColor = UIColor.redColor;
|
|
96
105
|
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
97
106
|
XCTAssertEqual([self.pager valueForKey:@"topEdgeScrollView"], scrollView);
|
|
98
107
|
UIScrollEdgeElementContainerInteraction *interaction =
|
|
@@ -100,6 +109,23 @@ using namespace facebook::react;
|
|
|
100
109
|
XCTAssertEqual(interaction.scrollView, scrollView);
|
|
101
110
|
UIView *host = [self.pager valueForKey:@"topEdgeInteractionHost"];
|
|
102
111
|
XCTAssertTrue([host.interactions containsObject:interaction]);
|
|
112
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
113
|
+
CALayer *cover = [self.pager valueForKey:@"topEdgeSeparatorCover"];
|
|
114
|
+
XCTAssertEqual(cover.superlayer, container.layer);
|
|
115
|
+
XCTAssertEqualWithAccuracy(CGRectGetMaxY(cover.frame), 64, 0.001);
|
|
116
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(cover.frame), 1.0 / window.screen.scale, 0.001);
|
|
117
|
+
XCTAssertTrue(CGColorEqualToColor(cover.backgroundColor, UIColor.redColor.CGColor));
|
|
118
|
+
UIEdgeInsets inset = scrollView.contentInset;
|
|
119
|
+
CGPoint offset = scrollView.contentOffset;
|
|
120
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Soft))
|
|
121
|
+
forKey:@"topScrollEdgeEffect"];
|
|
122
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
123
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.softStyle);
|
|
124
|
+
XCTAssertNil([self.pager valueForKey:@"topEdgeSeparatorCover"]);
|
|
125
|
+
XCTAssertNil(cover.superlayer);
|
|
126
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], interaction);
|
|
127
|
+
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, inset));
|
|
128
|
+
XCTAssertTrue(CGPointEqualToPoint(scrollView.contentOffset, offset));
|
|
103
129
|
|
|
104
130
|
RNCPagerViewComponentView *outer = [RNCPagerViewComponentView new];
|
|
105
131
|
[self.pager setValue:outer forKey:@"externalOuterPager"];
|
|
@@ -126,4 +152,163 @@ using namespace facebook::react;
|
|
|
126
152
|
}
|
|
127
153
|
}
|
|
128
154
|
|
|
155
|
+
- (void)testPinnedHardEdgeHidesOnlyTopSeparatorCoverUntilUnpinned
|
|
156
|
+
{
|
|
157
|
+
if (@available(iOS 26.0, *)) {
|
|
158
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:self.root.bounds];
|
|
159
|
+
[window addSubview:self.root];
|
|
160
|
+
[self addTeardownBlock:^{ window.hidden = YES; }];
|
|
161
|
+
window.hidden = NO;
|
|
162
|
+
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.pager.bounds];
|
|
163
|
+
[self.pager addSubview:scrollView];
|
|
164
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
165
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
166
|
+
forKey:@"topScrollEdgeEffect"];
|
|
167
|
+
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
168
|
+
[self.pager setValue:@64 forKey:@"externalTopOverlayHeight"];
|
|
169
|
+
[self.pager setValue:self.sticky forKey:@"stickyHeaderView"];
|
|
170
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
171
|
+
container.frame = self.pager.bounds;
|
|
172
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
173
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 64);
|
|
174
|
+
overlayHost.hidden = NO;
|
|
175
|
+
UIView *pinnedHost = [self.pager valueForKey:@"pinnedStickyHostView"];
|
|
176
|
+
pinnedHost.frame = CGRectMake(0, 64, 320, 44);
|
|
177
|
+
pinnedHost.hidden = NO;
|
|
178
|
+
[scrollView addSubview:pinnedHost];
|
|
179
|
+
|
|
180
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
181
|
+
UIScrollEdgeElementContainerInteraction *topInteraction =
|
|
182
|
+
[self.pager valueForKey:@"topEdgeInteraction"];
|
|
183
|
+
CALayer *topCover = [self.pager valueForKey:@"topEdgeSeparatorCover"];
|
|
184
|
+
XCTAssertNotNil(topInteraction);
|
|
185
|
+
XCTAssertNotNil(topCover);
|
|
186
|
+
XCTAssertFalse(topCover.hidden);
|
|
187
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
188
|
+
|
|
189
|
+
[self.pager setValue:@YES forKey:@"stickyHeaderIsPinned"];
|
|
190
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
191
|
+
UIScrollEdgeElementContainerInteraction *stickyInteraction =
|
|
192
|
+
[self.pager valueForKey:@"stickyEdgeInteraction"];
|
|
193
|
+
CALayer *stickyCover = [self.pager valueForKey:@"stickyEdgeSeparatorCover"];
|
|
194
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], topInteraction);
|
|
195
|
+
XCTAssertEqual(topInteraction.scrollView, scrollView);
|
|
196
|
+
XCTAssertEqual(stickyInteraction.scrollView, scrollView);
|
|
197
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeSeparatorCover"], topCover);
|
|
198
|
+
XCTAssertTrue(topCover.hidden);
|
|
199
|
+
XCTAssertNotNil(stickyCover);
|
|
200
|
+
XCTAssertFalse(stickyCover.hidden);
|
|
201
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
202
|
+
|
|
203
|
+
[self.pager setValue:@NO forKey:@"stickyHeaderIsPinned"];
|
|
204
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
205
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], topInteraction);
|
|
206
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeSeparatorCover"], topCover);
|
|
207
|
+
XCTAssertFalse(topCover.hidden);
|
|
208
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
209
|
+
[self.root removeFromSuperview];
|
|
210
|
+
} else {
|
|
211
|
+
XCTSkip(@"Scroll-edge interactions require iOS 26 or newer");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (void)testIOS27LocalHardThemeBackgroundTracksMaskedHeaderGeometry
|
|
216
|
+
{
|
|
217
|
+
if (@available(iOS 27.0, *)) {
|
|
218
|
+
UIWindowScene *scene = nil;
|
|
219
|
+
for (UIScene *connectedScene in UIApplication.sharedApplication.connectedScenes) {
|
|
220
|
+
if ([connectedScene isKindOfClass:UIWindowScene.class]) {
|
|
221
|
+
scene = (UIWindowScene *)connectedScene;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
XCTAssertNotNil(scene);
|
|
226
|
+
UIWindow *window = [[UIWindow alloc] initWithWindowScene:scene];
|
|
227
|
+
window.frame = self.root.bounds;
|
|
228
|
+
[window addSubview:self.root];
|
|
229
|
+
window.hidden = NO;
|
|
230
|
+
[self addTeardownBlock:^{ window.hidden = YES; }];
|
|
231
|
+
[self.pager layoutIfNeeded];
|
|
232
|
+
self.pager.backgroundColor = [UIColor colorWithWhite:0.2 alpha:1];
|
|
233
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
234
|
+
container.frame = self.pager.bounds;
|
|
235
|
+
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:container.bounds];
|
|
236
|
+
scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
237
|
+
scroll.alwaysBounceVertical = YES;
|
|
238
|
+
scroll.contentSize = CGSizeMake(320, 1200);
|
|
239
|
+
scroll.contentInset = UIEdgeInsetsMake(50 + 100 + 20, 0, 0, 0);
|
|
240
|
+
[container insertSubview:scroll atIndex:0];
|
|
241
|
+
UIView *sharedHost = [self.pager valueForKey:@"sharedHeaderHostView"];
|
|
242
|
+
for (UIView *child in sharedHost.subviews.copy) [child removeFromSuperview];
|
|
243
|
+
[scroll addSubview:sharedHost];
|
|
244
|
+
sharedHost.frame = CGRectMake(0, -120, 320, 120);
|
|
245
|
+
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
|
|
246
|
+
[sharedHost addSubview:header];
|
|
247
|
+
[self.pager setValue:header forKey:@"headerView"];
|
|
248
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
249
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 50);
|
|
250
|
+
overlayHost.hidden = NO;
|
|
251
|
+
UIView *overlay = [[UIView alloc] initWithFrame:overlayHost.bounds];
|
|
252
|
+
[overlayHost addSubview:overlay];
|
|
253
|
+
[self.pager setValue:overlay forKey:@"topOverlayView"];
|
|
254
|
+
[self.pager setValue:@50 forKey:@"topOverlayHeight"];
|
|
255
|
+
[self.pager setValue:@100 forKey:@"headerHeight"];
|
|
256
|
+
[self.pager setValue:@20 forKey:@"stickyHeaderHeight"];
|
|
257
|
+
[self.pager setValue:@YES forKey:@"nativeSmoothHeaderScrollEnabled"];
|
|
258
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
259
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
260
|
+
forKey:@"topScrollEdgeEffect"];
|
|
261
|
+
[self.pager setValue:scroll forKey:@"observedScrollView"];
|
|
262
|
+
[self.pager setValue:scroll forKey:@"sharedHeaderScrollView"];
|
|
263
|
+
scroll.contentOffset = CGPointMake(0, -170);
|
|
264
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
265
|
+
CALayer *layer = [self.pager valueForKey:@"topOverlayThemeBackgroundLayer"];
|
|
266
|
+
XCTAssertNotNil(layer);
|
|
267
|
+
XCTAssertEqual(layer.superlayer, overlayHost.layer);
|
|
268
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
269
|
+
XCTAssertEqual(scroll.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
270
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeScrollView"], scroll);
|
|
271
|
+
|
|
272
|
+
scroll.contentOffset = CGPointMake(0, -120);
|
|
273
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
274
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 0.5, 0.01);
|
|
275
|
+
scroll.contentOffset = CGPointMake(0, -70);
|
|
276
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
277
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 0, 0.01);
|
|
278
|
+
scroll.contentOffset = CGPointMake(0, -210);
|
|
279
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
280
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
281
|
+
|
|
282
|
+
scroll.contentInset = UIEdgeInsetsMake(230, 0, 0, 0);
|
|
283
|
+
scroll.contentOffset = CGPointMake(0, -230);
|
|
284
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
285
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
286
|
+
self.pager.backgroundColor = UIColor.whiteColor;
|
|
287
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
288
|
+
XCTAssertTrue(CGColorEqualToColor(layer.backgroundColor, UIColor.whiteColor.CGColor));
|
|
289
|
+
|
|
290
|
+
[self.pager updateTopScrollEdgeForScrollView:nil];
|
|
291
|
+
XCTAssertEqual([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"], layer);
|
|
292
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
293
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
294
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
295
|
+
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
296
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
297
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
298
|
+
[self.pager setValue:@0 forKey:@"externalTopOverlayTag"];
|
|
299
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
300
|
+
XCTAssertEqual(scroll.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
301
|
+
[self.pager setValue:@NO forKey:@"topScrollEdgeEnabled"];
|
|
302
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
303
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
304
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
305
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
306
|
+
XCTAssertNotNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
307
|
+
[self.pager prepareForRecycle];
|
|
308
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
309
|
+
} else {
|
|
310
|
+
XCTSkip(@"The theme background applies only on iOS 27 or newer");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
129
314
|
@end
|
|
@@ -60,6 +60,56 @@
|
|
|
60
60
|
XCTAssertEqualWithAccuracy(control.bounds.origin.x, 3, 0.001);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
- (void)testRefreshLayerWindowGeometryStartsBelowFixedOverlay
|
|
64
|
+
{
|
|
65
|
+
for (NSNumber *overlayHeight in @[@0, @162]) {
|
|
66
|
+
CGFloat fixedHeight = overlayHeight.doubleValue;
|
|
67
|
+
CGFloat baseInset = fixedHeight + 180 + 44;
|
|
68
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 390, 844)];
|
|
69
|
+
UIViewController *root = [UIViewController new];
|
|
70
|
+
window.rootViewController = root;
|
|
71
|
+
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:window.bounds];
|
|
72
|
+
scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
73
|
+
scroll.contentSize = CGSizeMake(390, 1500);
|
|
74
|
+
scroll.contentInset = UIEdgeInsetsMake(baseInset + 60, 0, 0, 0);
|
|
75
|
+
scroll.contentOffset = CGPointMake(0, -baseInset - 102);
|
|
76
|
+
[root.view addSubview:scroll];
|
|
77
|
+
UIView *overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 390, fixedHeight)];
|
|
78
|
+
[root.view addSubview:overlay];
|
|
79
|
+
UIRefreshControl *control = [UIRefreshControl new];
|
|
80
|
+
scroll.refreshControl = control;
|
|
81
|
+
window.hidden = NO;
|
|
82
|
+
[root.view layoutIfNeeded];
|
|
83
|
+
[control layoutIfNeeded];
|
|
84
|
+
// Match the control's window position recorded during a real refresh drag.
|
|
85
|
+
control.frame = [scroll convertRect:CGRectMake(0, baseInset, 390, 60)
|
|
86
|
+
fromView:window];
|
|
87
|
+
|
|
88
|
+
RNCCollapsiblePagerViewComponentView *pager = [self pagerWithHeader:180];
|
|
89
|
+
[pager setValue:@(fixedHeight) forKey:@"topOverlayHeight"];
|
|
90
|
+
if (fixedHeight > 0) [pager setValue:overlay forKey:@"topOverlayView"];
|
|
91
|
+
[pager updateRefreshControlForScrollView:scroll];
|
|
92
|
+
[CATransaction flush];
|
|
93
|
+
CALayer *firstChild = control.layer.sublayers.firstObject;
|
|
94
|
+
XCTAssertNotNil(firstChild);
|
|
95
|
+
XCTAssertEqual(control.window, window);
|
|
96
|
+
CGRect overlayWindow = [overlay convertRect:overlay.bounds toView:window];
|
|
97
|
+
CGRect controlWindow = [control convertRect:control.bounds toView:window];
|
|
98
|
+
CGRect childWindow = [firstChild convertRect:firstChild.bounds toLayer:window.layer];
|
|
99
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinY(controlWindow), baseInset, 0.01);
|
|
100
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinY(childWindow),
|
|
101
|
+
CGRectGetMaxY(overlayWindow), 0.01);
|
|
102
|
+
XCTAssertEqualWithAccuracy(scroll.contentInset.top, baseInset + 60, 0.01);
|
|
103
|
+
[pager updateRefreshControlForScrollView:scroll];
|
|
104
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinY(
|
|
105
|
+
[firstChild convertRect:firstChild.bounds toLayer:window.layer]),
|
|
106
|
+
CGRectGetMaxY(overlayWindow), 0.01);
|
|
107
|
+
[pager releaseRefreshControlForScrollView:scroll];
|
|
108
|
+
XCTAssertTrue(CATransform3DIsIdentity(control.layer.sublayerTransform));
|
|
109
|
+
window.hidden = YES;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
63
113
|
- (void)testUIKitSizeChangesAndRefreshCycleDoNotAccumulateOffset
|
|
64
114
|
{
|
|
65
115
|
RNCCollapsiblePagerViewComponentView *pager = [self pagerWithHeader:64];
|
|
@@ -69,6 +69,45 @@ using namespace facebook::react;
|
|
|
69
69
|
XCTAssertFalse([self.scroller.scrollView isKindOfClass:NSClassFromString(@"RCTEnhancedScrollView")]);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
- (void)testScrollEdgeStyleSwitchPreservesOverlayInsetAndRestoresOriginalStyle
|
|
73
|
+
{
|
|
74
|
+
if (@available(iOS 26.0, *)) {
|
|
75
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 600)];
|
|
76
|
+
UIView *root = [[UIView alloc] initWithFrame:window.bounds];
|
|
77
|
+
[window addSubview:root];
|
|
78
|
+
[root addSubview:self.scroller];
|
|
79
|
+
RCTViewComponentView *overlay = [RCTViewComponentView new];
|
|
80
|
+
overlay.frame = CGRectMake(0, 0, 320, 64);
|
|
81
|
+
[root addSubview:overlay];
|
|
82
|
+
UIScrollEdgeEffectStyle *original = self.scroller.scrollView.topEdgeEffect.style;
|
|
83
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
84
|
+
props->contentHeight = 1500;
|
|
85
|
+
props->topOverlayHeight = 64;
|
|
86
|
+
props->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::Hard;
|
|
87
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
88
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
89
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 64, 0.01);
|
|
90
|
+
CGPoint offset = self.scroller.scrollView.contentOffset;
|
|
91
|
+
auto soft = std::make_shared<RNCNativeScrollerProps>();
|
|
92
|
+
soft->contentHeight = 1500;
|
|
93
|
+
soft->topOverlayHeight = 64;
|
|
94
|
+
soft->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::Soft;
|
|
95
|
+
[self.scroller updateProps:soft oldProps:props];
|
|
96
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.softStyle);
|
|
97
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 64, 0.01);
|
|
98
|
+
XCTAssertTrue(CGPointEqualToPoint(self.scroller.scrollView.contentOffset, offset));
|
|
99
|
+
auto disabled = std::make_shared<RNCNativeScrollerProps>();
|
|
100
|
+
disabled->contentHeight = 1500;
|
|
101
|
+
disabled->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::None;
|
|
102
|
+
[self.scroller updateProps:disabled oldProps:soft];
|
|
103
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, original);
|
|
104
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 0, 0.01);
|
|
105
|
+
[self.scroller removeFromSuperview];
|
|
106
|
+
} else {
|
|
107
|
+
XCTSkip(@"Scroll-edge styles require iOS 26 or newer");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
72
111
|
- (void)testShrinkClampsOnlyUnreachableUpperOffset
|
|
73
112
|
{
|
|
74
113
|
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
@@ -23,7 +23,7 @@ const pageKey = (child, index) => {
|
|
|
23
23
|
*/
|
|
24
24
|
export class CollapsiblePagerView extends React.PureComponent {
|
|
25
25
|
state = {
|
|
26
|
-
topOverlayHeight:
|
|
26
|
+
topOverlayHeight: null,
|
|
27
27
|
selectedPage: clampedPageIndex(this.props.initialPage ?? 0, React.Children.toArray(this.props.children).length),
|
|
28
28
|
transientRetainedPages: []
|
|
29
29
|
};
|
|
@@ -41,7 +41,14 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
41
41
|
componentDidMount() {
|
|
42
42
|
this.notifyMountedPagesChanged();
|
|
43
43
|
}
|
|
44
|
-
componentDidUpdate() {
|
|
44
|
+
componentDidUpdate(previousProps) {
|
|
45
|
+
const previousOverlayEnabled = previousProps.topOverlay != null && (previousProps.topScrollEdgeEffect === "hard" || previousProps.topScrollEdgeEffect === "soft");
|
|
46
|
+
const overlayEnabled = this.props.topOverlay != null && (this.props.topScrollEdgeEffect === "hard" || this.props.topScrollEdgeEffect === "soft");
|
|
47
|
+
if (previousOverlayEnabled && !overlayEnabled && this.state.topOverlayHeight !== null) {
|
|
48
|
+
this.setState({
|
|
49
|
+
topOverlayHeight: null
|
|
50
|
+
});
|
|
51
|
+
}
|
|
45
52
|
const pageCount = this.pages().length;
|
|
46
53
|
const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
|
|
47
54
|
if (selectedPage !== this.state.selectedPage) {
|
|
@@ -197,6 +204,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
197
204
|
headerHeight,
|
|
198
205
|
stickyHeaderHeight,
|
|
199
206
|
topOverlay,
|
|
207
|
+
topOverlayHeightHint,
|
|
200
208
|
externalTopOverlayTag,
|
|
201
209
|
topScrollEdgeEffect,
|
|
202
210
|
topOverlayColorScheme,
|
|
@@ -217,8 +225,9 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
217
225
|
} = this.props;
|
|
218
226
|
const pages = this.pages();
|
|
219
227
|
const supportsTopScrollEdge = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
|
|
220
|
-
const
|
|
221
|
-
const hasTopOverlay =
|
|
228
|
+
const scrollEdgeEnabled = supportsTopScrollEdge && (topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft");
|
|
229
|
+
const hasTopOverlay = scrollEdgeEnabled && topOverlay != null;
|
|
230
|
+
const initialTopOverlayHeight = typeof topOverlayHeightHint === "number" && Number.isFinite(topOverlayHeightHint) ? Math.max(0, Math.ceil(topOverlayHeightHint)) : 0;
|
|
222
231
|
const pageKeys = pages.map(pageKey);
|
|
223
232
|
const retained = new Set(this.mountedPages(pages.length));
|
|
224
233
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
@@ -243,9 +252,9 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
243
252
|
offscreenPageLimit: Math.max(1, offscreenPageLimit ?? 1),
|
|
244
253
|
headerHeight: Math.max(0, Math.round(headerHeight)),
|
|
245
254
|
stickyHeaderHeight: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
246
|
-
|
|
247
|
-
topOverlayColorScheme:
|
|
248
|
-
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight : 0,
|
|
255
|
+
topScrollEdgeEffect: scrollEdgeEnabled ? topScrollEdgeEffect : "none",
|
|
256
|
+
topOverlayColorScheme: scrollEdgeEnabled ? topOverlayColorScheme : undefined,
|
|
257
|
+
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight ?? initialTopOverlayHeight : 0,
|
|
249
258
|
externalTopOverlayTag: supportsTopScrollEdge && !hasTopOverlay ? externalTopOverlayTag ?? 0 : 0,
|
|
250
259
|
nativeSmoothHeaderScrollEnabled: nativeSmoothHeaderScrollEnabled,
|
|
251
260
|
pageKeys: JSON.stringify(pageKeys),
|