@onekeyfe/react-native-pager-view 6.7.0-alpha.327 → 6.7.0-alpha.336
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/docs/SPEC.md +82 -8
- package/ios/RNCCollapsiblePagerViewComponentView.mm +257 -2
- package/ios/RNCNativeScrollerComponentView.mm +24 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +124 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +160 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/lib/module/CollapsiblePagerView.js +12 -3
- package/lib/module/CollapsiblePagerView.web.js +428 -37
- package/lib/module/NativeScroller.web.js +16 -3
- package/lib/module/WebCollapsiblePagerPage.js +182 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.native.js +9 -0
- package/lib/module/index.web.js +2 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +4 -2
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +64 -3
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +42 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.native.d.ts +13 -0
- package/lib/typescript/src/index.web.d.ts +4 -0
- package/package.json +6 -4
- package/src/CollapsiblePagerView.tsx +31 -4
- package/src/CollapsiblePagerView.web.tsx +638 -49
- package/src/NativeScroller.web.tsx +24 -2
- package/src/WebCollapsiblePagerPage.tsx +258 -0
- package/src/index.native.tsx +30 -0
- package/src/index.tsx +4 -0
- package/src/index.web.tsx +4 -0
|
@@ -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
|
|
@@ -151,4 +152,163 @@ using namespace facebook::react;
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
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
|
+
|
|
154
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];
|
|
@@ -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,
|
|
@@ -219,6 +227,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
219
227
|
const supportsTopScrollEdge = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
|
|
220
228
|
const scrollEdgeEnabled = supportsTopScrollEdge && (topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft");
|
|
221
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;
|
|
@@ -245,7 +254,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
245
254
|
stickyHeaderHeight: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
246
255
|
topScrollEdgeEffect: scrollEdgeEnabled ? topScrollEdgeEffect : "none",
|
|
247
256
|
topOverlayColorScheme: scrollEdgeEnabled ? topOverlayColorScheme : undefined,
|
|
248
|
-
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight : 0,
|
|
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),
|