@onekeyfe/react-native-pager-view 6.7.0-2 → 6.7.0-21
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 +14 -0
- package/android/src/androidTest/AndroidManifest.xml +7 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +306 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +130 -30
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +72 -7
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +27 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +19 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
- package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +8 -2
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +40 -0
- package/docs/SPEC.md +257 -10
- package/ios/RNCCollapsiblePagerViewComponentView.mm +614 -42
- package/ios/RNCNativeScrollerComponentView.h +2 -0
- package/ios/RNCNativeScrollerComponentView.mm +42 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +403 -1
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +338 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +74 -9
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/lib/module/CollapsiblePagerView.js +32 -7
- package/lib/module/CollapsiblePagerView.web.js +439 -39
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +13 -0
- 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 +10 -3
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +66 -3
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +5 -0
- 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/react-native-pager-view.podspec +1 -0
- package/src/CollapsiblePagerView.tsx +99 -8
- package/src/CollapsiblePagerView.web.tsx +657 -51
- package/src/CollapsiblePagerViewNativeComponent.ts +13 -0
- 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
|
@@ -2,12 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
4
4
|
#import "RNCPagerViewComponentView.h"
|
|
5
|
+
#import <React/RCTViewComponentView.h>
|
|
5
6
|
#import <react/renderer/components/pagerview/Props.h>
|
|
7
|
+
#import <react/renderer/components/view/ViewProps.h>
|
|
6
8
|
|
|
7
9
|
using namespace facebook::react;
|
|
8
10
|
|
|
9
11
|
@interface RNCCollapsiblePagerViewComponentView (ScrollEdgeTesting)
|
|
10
12
|
- (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView;
|
|
13
|
+
- (void)updateScrollEdgeContentMasks;
|
|
14
|
+
- (void)layoutSharedHeaders;
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@interface UIView (NativeTabReservationTesting)
|
|
18
|
+
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
19
|
+
- (void)updateStyleWithHeight:(CGFloat)height
|
|
20
|
+
variant:(NSString *)variant
|
|
21
|
+
contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
|
|
22
|
+
contentPaddingVertical:(CGFloat)contentPaddingVertical
|
|
23
|
+
itemSpacing:(CGFloat)itemSpacing
|
|
24
|
+
fontSize:(CGFloat)fontSize
|
|
25
|
+
fontFamily:(NSString *)fontFamily
|
|
26
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
27
|
+
activeTextColor:(UIColor *)activeTextColor
|
|
28
|
+
inactiveTextColor:(UIColor *)inactiveTextColor
|
|
29
|
+
indicatorColor:(UIColor *)indicatorColor
|
|
30
|
+
indicatorHeight:(CGFloat)indicatorHeight
|
|
31
|
+
indicatorBottom:(CGFloat)indicatorBottom;
|
|
32
|
+
- (void)setProgress:(CGFloat)progress;
|
|
33
|
+
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
11
34
|
@end
|
|
12
35
|
|
|
13
36
|
@interface RNCCollapsiblePagerHitTestTests : XCTestCase
|
|
@@ -55,6 +78,162 @@ using namespace facebook::react;
|
|
|
55
78
|
XCTAssertEqual([self.root hitTest:CGPointMake(20, 80) withEvent:nil], self.toolbar);
|
|
56
79
|
}
|
|
57
80
|
|
|
81
|
+
- (void)testNativeTabReservationKeepsReactToolbarAboveTabsAndResetsInRTL
|
|
82
|
+
{
|
|
83
|
+
UIView *sharedHost = [self.pager valueForKey:@"sharedHeaderHostView"];
|
|
84
|
+
UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
|
|
85
|
+
[tabBar updateItemsJSON:@"[{\"key\":\"a\",\"title\":\"Overview\"},"
|
|
86
|
+
@"{\"key\":\"b\",\"title\":\"Ethereum\"},"
|
|
87
|
+
@"{\"key\":\"c\",\"title\":\"BNB Chain\"},"
|
|
88
|
+
@"{\"key\":\"d\",\"title\":\"Polygon\"}]"];
|
|
89
|
+
RCTViewComponentView *sticky = [RCTViewComponentView new];
|
|
90
|
+
auto stickyProps = std::make_shared<ViewProps>();
|
|
91
|
+
stickyProps->pointerEvents = PointerEventsMode::BoxNone;
|
|
92
|
+
[sticky updateProps:stickyProps oldProps:nullptr];
|
|
93
|
+
UIButton *trailingControl = [[UIButton alloc] initWithFrame:CGRectMake(250, 0, 70, 44)];
|
|
94
|
+
[sticky addSubview:trailingControl];
|
|
95
|
+
[sharedHost addSubview:sticky];
|
|
96
|
+
[sharedHost addSubview:tabBar];
|
|
97
|
+
[self.pager setValue:sticky forKey:@"stickyHeaderView"];
|
|
98
|
+
[self.pager setValue:@0 forKey:@"headerHeight"];
|
|
99
|
+
[self.pager setValue:@44 forKey:@"stickyHeaderHeight"];
|
|
100
|
+
tabBar.hidden = NO;
|
|
101
|
+
|
|
102
|
+
[self.pager setValue:@64 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
103
|
+
[self.pager layoutSharedHeaders];
|
|
104
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(sticky.frame), 320, 0.001);
|
|
105
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 256, 0.001);
|
|
106
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 0, 0.001);
|
|
107
|
+
XCTAssertEqual(sharedHost.subviews.lastObject, sticky);
|
|
108
|
+
XCTAssertEqual([sharedHost hitTest:CGPointMake(300, 22) withEvent:nil], trailingControl);
|
|
109
|
+
XCTAssertTrue([tabBar pointInside:CGPointMake(250, 22) withEvent:nil]);
|
|
110
|
+
[tabBar layoutIfNeeded];
|
|
111
|
+
[tabBar setProgress:3];
|
|
112
|
+
UIScrollView *tabScroll = (UIScrollView *)tabBar.subviews.firstObject;
|
|
113
|
+
NSArray<UIButton *> *buttons = [tabScroll.subviews filteredArrayUsingPredicate:
|
|
114
|
+
[NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
|
|
115
|
+
return [view isKindOfClass:UIButton.class];
|
|
116
|
+
}]];
|
|
117
|
+
UIButton *lastTab = buttons.lastObject;
|
|
118
|
+
XCTAssertNotNil(lastTab);
|
|
119
|
+
XCTAssertGreaterThanOrEqual(CGRectGetMinX(lastTab.frame), tabScroll.contentOffset.x - 1);
|
|
120
|
+
XCTAssertLessThanOrEqual(CGRectGetMaxX(lastTab.frame),
|
|
121
|
+
tabScroll.contentOffset.x + CGRectGetWidth(tabScroll.bounds) + 1);
|
|
122
|
+
|
|
123
|
+
[self.pager setValue:@"rtl" forKey:@"layoutDirection"];
|
|
124
|
+
[tabBar setLeftToRight:NO];
|
|
125
|
+
trailingControl.frame = CGRectMake(0, 0, 70, 44);
|
|
126
|
+
[self.pager layoutSharedHeaders];
|
|
127
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 64, 0.001);
|
|
128
|
+
XCTAssertEqualWithAccuracy(CGRectGetMaxX(tabBar.frame), 320, 0.001);
|
|
129
|
+
XCTAssertEqual([sharedHost hitTest:CGPointMake(20, 22) withEvent:nil], trailingControl);
|
|
130
|
+
|
|
131
|
+
[self.pager setValue:@500 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
132
|
+
[self.pager layoutSharedHeaders];
|
|
133
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 320, 0.001);
|
|
134
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 0, 0.001);
|
|
135
|
+
|
|
136
|
+
[self.pager setValue:@0 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
137
|
+
[self.pager layoutSharedHeaders];
|
|
138
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 0, 0.001);
|
|
139
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 320, 0.001);
|
|
140
|
+
XCTAssertEqual(sharedHost.subviews.lastObject, tabBar);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (void)testNativeTabReservationPropsRestoreDefaultOnRemoval
|
|
144
|
+
{
|
|
145
|
+
auto reservedProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
146
|
+
reservedProps->nativeSmoothHeaderScrollEnabled = true;
|
|
147
|
+
reservedProps->nativeTabBarTrailingReservedWidth = 64;
|
|
148
|
+
[self.pager updateProps:reservedProps oldProps:nullptr];
|
|
149
|
+
XCTAssertEqualWithAccuracy(
|
|
150
|
+
[[self.pager valueForKey:@"nativeTabBarTrailingReservedWidth"] doubleValue], 64, 0.001);
|
|
151
|
+
|
|
152
|
+
auto defaultProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
153
|
+
defaultProps->nativeSmoothHeaderScrollEnabled = true;
|
|
154
|
+
[self.pager updateProps:defaultProps oldProps:reservedProps];
|
|
155
|
+
XCTAssertEqualWithAccuracy(
|
|
156
|
+
[[self.pager valueForKey:@"nativeTabBarTrailingReservedWidth"] doubleValue], 0, 0.001);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
- (void)testSegmentedNativeTabsFollowFractionalProgressAndResetToUnderline
|
|
160
|
+
{
|
|
161
|
+
UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
|
|
162
|
+
[tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents\"},"
|
|
163
|
+
@"{\"key\":\"account\",\"title\":\"Accounts\"},"
|
|
164
|
+
@"{\"key\":\"book\",\"title\":\"Address Book\"}]"];
|
|
165
|
+
[tabBar updateStyleWithHeight:56 variant:@"segmented"
|
|
166
|
+
contentPaddingHorizontal:20 contentPaddingVertical:12 itemSpacing:0
|
|
167
|
+
fontSize:14 fontFamily:nil backgroundColor:UIColor.lightGrayColor
|
|
168
|
+
activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
|
|
169
|
+
indicatorColor:UIColor.whiteColor indicatorHeight:2 indicatorBottom:0];
|
|
170
|
+
tabBar.frame = CGRectMake(0, 0, 320, 56);
|
|
171
|
+
[tabBar layoutIfNeeded];
|
|
172
|
+
UIScrollView *scroll = (UIScrollView *)tabBar.subviews.firstObject;
|
|
173
|
+
UIView *track = scroll.subviews.firstObject;
|
|
174
|
+
UIView *thumb = [tabBar valueForKey:@"indicatorView"];
|
|
175
|
+
NSArray<UIButton *> *buttons = [scroll.subviews filteredArrayUsingPredicate:
|
|
176
|
+
[NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
|
|
177
|
+
return [view isKindOfClass:UIButton.class];
|
|
178
|
+
}]];
|
|
179
|
+
XCTAssertEqual(buttons.count, 3);
|
|
180
|
+
XCTAssertFalse(scroll.scrollEnabled);
|
|
181
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(track.frame), 20, 0.01);
|
|
182
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(track.frame), 32, 0.01);
|
|
183
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(buttons[0].frame),
|
|
184
|
+
CGRectGetWidth(buttons[1].frame), 0.01);
|
|
185
|
+
[tabBar setProgress:0];
|
|
186
|
+
CGFloat firstX = CGRectGetMinX(thumb.frame);
|
|
187
|
+
[tabBar setProgress:1];
|
|
188
|
+
CGFloat secondX = CGRectGetMinX(thumb.frame);
|
|
189
|
+
[tabBar setProgress:0.25];
|
|
190
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstX + (secondX - firstX) * 0.25, 0.01);
|
|
191
|
+
[tabBar setProgress:0.5];
|
|
192
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), (firstX + secondX) / 2, 0.01);
|
|
193
|
+
[tabBar setProgress:0];
|
|
194
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstX, 0.01);
|
|
195
|
+
|
|
196
|
+
[tabBar setLeftToRight:NO];
|
|
197
|
+
[tabBar layoutIfNeeded];
|
|
198
|
+
XCTAssertGreaterThan(CGRectGetMinX(buttons[0].frame), CGRectGetMinX(buttons[1].frame));
|
|
199
|
+
[tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents (8)\"},"
|
|
200
|
+
@"{\"key\":\"account\",\"title\":\"Accounts\"}]"];
|
|
201
|
+
[tabBar layoutIfNeeded];
|
|
202
|
+
NSArray<UIButton *> *twoButtons = [scroll.subviews filteredArrayUsingPredicate:
|
|
203
|
+
[NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
|
|
204
|
+
return [view isKindOfClass:UIButton.class];
|
|
205
|
+
}]];
|
|
206
|
+
XCTAssertEqual(twoButtons.count, 2);
|
|
207
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(twoButtons[0].frame),
|
|
208
|
+
CGRectGetWidth(track.frame) / 2, 0.01);
|
|
209
|
+
[tabBar updateStyleWithHeight:56 variant:@"segmented"
|
|
210
|
+
contentPaddingHorizontal:20 contentPaddingVertical:40 itemSpacing:0
|
|
211
|
+
fontSize:14 fontFamily:nil backgroundColor:UIColor.lightGrayColor
|
|
212
|
+
activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
|
|
213
|
+
indicatorColor:UIColor.whiteColor indicatorHeight:2 indicatorBottom:0];
|
|
214
|
+
tabBar.frame = CGRectMake(0, 0, 30, 56);
|
|
215
|
+
[tabBar layoutIfNeeded];
|
|
216
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(track.frame), 15, 0.01);
|
|
217
|
+
XCTAssertEqualWithAccuracy(CGRectGetWidth(track.frame), 0, 0.01);
|
|
218
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinY(track.frame), 28, 0.01);
|
|
219
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(track.frame), 0, 0.01);
|
|
220
|
+
XCTAssertTrue(thumb.hidden);
|
|
221
|
+
[tabBar updateItemsJSON:@"[]"];
|
|
222
|
+
XCTAssertTrue(tabBar.hidden);
|
|
223
|
+
|
|
224
|
+
[tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents\"}]"];
|
|
225
|
+
[tabBar updateStyleWithHeight:44 variant:@"underline"
|
|
226
|
+
contentPaddingHorizontal:20 contentPaddingVertical:0 itemSpacing:8
|
|
227
|
+
fontSize:16 fontFamily:nil backgroundColor:UIColor.clearColor
|
|
228
|
+
activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
|
|
229
|
+
indicatorColor:UIColor.blackColor indicatorHeight:2 indicatorBottom:0];
|
|
230
|
+
tabBar.frame = CGRectMake(0, 0, 320, 44);
|
|
231
|
+
[tabBar layoutIfNeeded];
|
|
232
|
+
XCTAssertTrue(scroll.scrollEnabled);
|
|
233
|
+
XCTAssertTrue(track.hidden);
|
|
234
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(thumb.frame), 2, 0.01);
|
|
235
|
+
}
|
|
236
|
+
|
|
58
237
|
- (void)testPinnedChildOutsideHeaderHostStillReceivesViewportTouches
|
|
59
238
|
{
|
|
60
239
|
XCTAssertEqual([self.pager hitTest:CGPointMake(20, 90) withEvent:nil], self.sticky);
|
|
@@ -151,4 +330,163 @@ using namespace facebook::react;
|
|
|
151
330
|
}
|
|
152
331
|
}
|
|
153
332
|
|
|
333
|
+
- (void)testPinnedHardEdgeHidesOnlyTopSeparatorCoverUntilUnpinned
|
|
334
|
+
{
|
|
335
|
+
if (@available(iOS 26.0, *)) {
|
|
336
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:self.root.bounds];
|
|
337
|
+
[window addSubview:self.root];
|
|
338
|
+
[self addTeardownBlock:^{ window.hidden = YES; }];
|
|
339
|
+
window.hidden = NO;
|
|
340
|
+
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.pager.bounds];
|
|
341
|
+
[self.pager addSubview:scrollView];
|
|
342
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
343
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
344
|
+
forKey:@"topScrollEdgeEffect"];
|
|
345
|
+
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
346
|
+
[self.pager setValue:@64 forKey:@"externalTopOverlayHeight"];
|
|
347
|
+
[self.pager setValue:self.sticky forKey:@"stickyHeaderView"];
|
|
348
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
349
|
+
container.frame = self.pager.bounds;
|
|
350
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
351
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 64);
|
|
352
|
+
overlayHost.hidden = NO;
|
|
353
|
+
UIView *pinnedHost = [self.pager valueForKey:@"pinnedStickyHostView"];
|
|
354
|
+
pinnedHost.frame = CGRectMake(0, 64, 320, 44);
|
|
355
|
+
pinnedHost.hidden = NO;
|
|
356
|
+
[scrollView addSubview:pinnedHost];
|
|
357
|
+
|
|
358
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
359
|
+
UIScrollEdgeElementContainerInteraction *topInteraction =
|
|
360
|
+
[self.pager valueForKey:@"topEdgeInteraction"];
|
|
361
|
+
CALayer *topCover = [self.pager valueForKey:@"topEdgeSeparatorCover"];
|
|
362
|
+
XCTAssertNotNil(topInteraction);
|
|
363
|
+
XCTAssertNotNil(topCover);
|
|
364
|
+
XCTAssertFalse(topCover.hidden);
|
|
365
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
366
|
+
|
|
367
|
+
[self.pager setValue:@YES forKey:@"stickyHeaderIsPinned"];
|
|
368
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
369
|
+
UIScrollEdgeElementContainerInteraction *stickyInteraction =
|
|
370
|
+
[self.pager valueForKey:@"stickyEdgeInteraction"];
|
|
371
|
+
CALayer *stickyCover = [self.pager valueForKey:@"stickyEdgeSeparatorCover"];
|
|
372
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], topInteraction);
|
|
373
|
+
XCTAssertEqual(topInteraction.scrollView, scrollView);
|
|
374
|
+
XCTAssertEqual(stickyInteraction.scrollView, scrollView);
|
|
375
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeSeparatorCover"], topCover);
|
|
376
|
+
XCTAssertTrue(topCover.hidden);
|
|
377
|
+
XCTAssertNotNil(stickyCover);
|
|
378
|
+
XCTAssertFalse(stickyCover.hidden);
|
|
379
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
380
|
+
|
|
381
|
+
[self.pager setValue:@NO forKey:@"stickyHeaderIsPinned"];
|
|
382
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
383
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], topInteraction);
|
|
384
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeSeparatorCover"], topCover);
|
|
385
|
+
XCTAssertFalse(topCover.hidden);
|
|
386
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
387
|
+
[self.root removeFromSuperview];
|
|
388
|
+
} else {
|
|
389
|
+
XCTSkip(@"Scroll-edge interactions require iOS 26 or newer");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
- (void)testIOS27LocalHardThemeBackgroundTracksMaskedHeaderGeometry
|
|
394
|
+
{
|
|
395
|
+
if (@available(iOS 27.0, *)) {
|
|
396
|
+
UIWindowScene *scene = nil;
|
|
397
|
+
for (UIScene *connectedScene in UIApplication.sharedApplication.connectedScenes) {
|
|
398
|
+
if ([connectedScene isKindOfClass:UIWindowScene.class]) {
|
|
399
|
+
scene = (UIWindowScene *)connectedScene;
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
XCTAssertNotNil(scene);
|
|
404
|
+
UIWindow *window = [[UIWindow alloc] initWithWindowScene:scene];
|
|
405
|
+
window.frame = self.root.bounds;
|
|
406
|
+
[window addSubview:self.root];
|
|
407
|
+
window.hidden = NO;
|
|
408
|
+
[self addTeardownBlock:^{ window.hidden = YES; }];
|
|
409
|
+
[self.pager layoutIfNeeded];
|
|
410
|
+
self.pager.backgroundColor = [UIColor colorWithWhite:0.2 alpha:1];
|
|
411
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
412
|
+
container.frame = self.pager.bounds;
|
|
413
|
+
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:container.bounds];
|
|
414
|
+
scroll.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
415
|
+
scroll.alwaysBounceVertical = YES;
|
|
416
|
+
scroll.contentSize = CGSizeMake(320, 1200);
|
|
417
|
+
scroll.contentInset = UIEdgeInsetsMake(50 + 100 + 20, 0, 0, 0);
|
|
418
|
+
[container insertSubview:scroll atIndex:0];
|
|
419
|
+
UIView *sharedHost = [self.pager valueForKey:@"sharedHeaderHostView"];
|
|
420
|
+
for (UIView *child in sharedHost.subviews.copy) [child removeFromSuperview];
|
|
421
|
+
[scroll addSubview:sharedHost];
|
|
422
|
+
sharedHost.frame = CGRectMake(0, -120, 320, 120);
|
|
423
|
+
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
|
|
424
|
+
[sharedHost addSubview:header];
|
|
425
|
+
[self.pager setValue:header forKey:@"headerView"];
|
|
426
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
427
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 50);
|
|
428
|
+
overlayHost.hidden = NO;
|
|
429
|
+
UIView *overlay = [[UIView alloc] initWithFrame:overlayHost.bounds];
|
|
430
|
+
[overlayHost addSubview:overlay];
|
|
431
|
+
[self.pager setValue:overlay forKey:@"topOverlayView"];
|
|
432
|
+
[self.pager setValue:@50 forKey:@"topOverlayHeight"];
|
|
433
|
+
[self.pager setValue:@100 forKey:@"headerHeight"];
|
|
434
|
+
[self.pager setValue:@20 forKey:@"stickyHeaderHeight"];
|
|
435
|
+
[self.pager setValue:@YES forKey:@"nativeSmoothHeaderScrollEnabled"];
|
|
436
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
437
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
438
|
+
forKey:@"topScrollEdgeEffect"];
|
|
439
|
+
[self.pager setValue:scroll forKey:@"observedScrollView"];
|
|
440
|
+
[self.pager setValue:scroll forKey:@"sharedHeaderScrollView"];
|
|
441
|
+
scroll.contentOffset = CGPointMake(0, -170);
|
|
442
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
443
|
+
CALayer *layer = [self.pager valueForKey:@"topOverlayThemeBackgroundLayer"];
|
|
444
|
+
XCTAssertNotNil(layer);
|
|
445
|
+
XCTAssertEqual(layer.superlayer, overlayHost.layer);
|
|
446
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
447
|
+
XCTAssertEqual(scroll.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
448
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeScrollView"], scroll);
|
|
449
|
+
|
|
450
|
+
scroll.contentOffset = CGPointMake(0, -120);
|
|
451
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
452
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 0.5, 0.01);
|
|
453
|
+
scroll.contentOffset = CGPointMake(0, -70);
|
|
454
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
455
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 0, 0.01);
|
|
456
|
+
scroll.contentOffset = CGPointMake(0, -210);
|
|
457
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
458
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
459
|
+
|
|
460
|
+
scroll.contentInset = UIEdgeInsetsMake(230, 0, 0, 0);
|
|
461
|
+
scroll.contentOffset = CGPointMake(0, -230);
|
|
462
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
463
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
464
|
+
self.pager.backgroundColor = UIColor.whiteColor;
|
|
465
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
466
|
+
XCTAssertTrue(CGColorEqualToColor(layer.backgroundColor, UIColor.whiteColor.CGColor));
|
|
467
|
+
|
|
468
|
+
[self.pager updateTopScrollEdgeForScrollView:nil];
|
|
469
|
+
XCTAssertEqual([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"], layer);
|
|
470
|
+
XCTAssertEqualWithAccuracy(layer.opacity, 1, 0.01);
|
|
471
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
472
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
473
|
+
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
474
|
+
[self.pager updateScrollEdgeContentMasks];
|
|
475
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
476
|
+
[self.pager setValue:@0 forKey:@"externalTopOverlayTag"];
|
|
477
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
478
|
+
XCTAssertEqual(scroll.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
479
|
+
[self.pager setValue:@NO forKey:@"topScrollEdgeEnabled"];
|
|
480
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
481
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
482
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
483
|
+
[self.pager updateTopScrollEdgeForScrollView:scroll];
|
|
484
|
+
XCTAssertNotNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
485
|
+
[self.pager prepareForRecycle];
|
|
486
|
+
XCTAssertNil([self.pager valueForKey:@"topOverlayThemeBackgroundLayer"]);
|
|
487
|
+
} else {
|
|
488
|
+
XCTSkip(@"The theme background applies only on iOS 27 or newer");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
154
492
|
@end
|
|
@@ -668,6 +668,7 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
668
668
|
@property (nonatomic, strong) RNCSynthesizedTouchDriver *driver;
|
|
669
669
|
@property (nonatomic, strong) UIView *firstPage;
|
|
670
670
|
@property (nonatomic) NSUInteger longPressBegins;
|
|
671
|
+
@property (nonatomic, strong) XCTestExpectation *longPressExpectation;
|
|
671
672
|
@property (nonatomic) NSUInteger pagerPanBegins;
|
|
672
673
|
@end
|
|
673
674
|
|
|
@@ -763,6 +764,26 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
763
764
|
XCTAssertTrue([pager isKindOfClass:NSClassFromString(@"RNCCollapsiblePagerDirectScrollView")]);
|
|
764
765
|
}
|
|
765
766
|
|
|
767
|
+
- (void)configureSegmentedTabsForTwoPages
|
|
768
|
+
{
|
|
769
|
+
auto baseline = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
770
|
+
baseline->nativeSmoothHeaderScrollEnabled = true;
|
|
771
|
+
baseline->headerHeight = 120;
|
|
772
|
+
baseline->stickyHeaderHeight = 44;
|
|
773
|
+
auto segmented = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
774
|
+
segmented->nativeSmoothHeaderScrollEnabled = true;
|
|
775
|
+
segmented->headerHeight = 120;
|
|
776
|
+
segmented->stickyHeaderHeight = 56;
|
|
777
|
+
segmented->nativeTabBarItems =
|
|
778
|
+
R"([{"key":"recent","title":"Recents"},{"key":"account","title":"Accounts"}])";
|
|
779
|
+
segmented->nativeTabBarVariant = "segmented";
|
|
780
|
+
segmented->nativeTabBarHeight = 56;
|
|
781
|
+
segmented->nativeTabBarContentPaddingHorizontal = 20;
|
|
782
|
+
segmented->nativeTabBarContentPaddingVertical = 12;
|
|
783
|
+
[self.pager updateProps:segmented oldProps:baseline];
|
|
784
|
+
[self.window layoutIfNeeded];
|
|
785
|
+
}
|
|
786
|
+
|
|
766
787
|
- (void)recordPagerPan:(UIPanGestureRecognizer *)pan
|
|
767
788
|
{
|
|
768
789
|
if (pan.state == UIGestureRecognizerStateBegan) self.pagerPanBegins += 1;
|
|
@@ -904,6 +925,51 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
904
925
|
XCTAssertEqual(self.handler.events.count, eventsBeforeLift);
|
|
905
926
|
}
|
|
906
927
|
|
|
928
|
+
- (void)testSegmentedThumbTracksDeliveredPagerDragAndReverseBeforeLift
|
|
929
|
+
{
|
|
930
|
+
[self assertFixtureIsReadyForDelivery];
|
|
931
|
+
[self assertDirectPagerForRegression];
|
|
932
|
+
[self configureSegmentedTabsForTwoPages];
|
|
933
|
+
|
|
934
|
+
UIScrollView *pagerScrollView = [self.pager valueForKey:@"pagerScrollView"];
|
|
935
|
+
UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
|
|
936
|
+
UIView *thumb = [tabBar valueForKey:@"indicatorView"];
|
|
937
|
+
NSArray<UIButton *> *buttons = [tabBar valueForKey:@"buttons"];
|
|
938
|
+
XCTAssertEqual(buttons.count, 2);
|
|
939
|
+
XCTAssertFalse(tabBar.hidden);
|
|
940
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(tabBar.bounds), 56, 0.01);
|
|
941
|
+
CGFloat firstThumbX = CGRectGetMinX(buttons[0].frame) + 2;
|
|
942
|
+
CGFloat secondThumbX = CGRectGetMinX(buttons[1].frame) + 2;
|
|
943
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstThumbX, 1);
|
|
944
|
+
|
|
945
|
+
CGPoint point = CGPointMake(350, 600);
|
|
946
|
+
UITouch *touch = [self.driver beginAt:point];
|
|
947
|
+
CGFloat largestOffset = 0;
|
|
948
|
+
NSUInteger intermediateFrames = 0;
|
|
949
|
+
for (NSInteger step = 1; step <= 18; ++step) {
|
|
950
|
+
[self.driver move:touch to:CGPointMake(point.x - 14 * step, point.y)];
|
|
951
|
+
CGFloat offset = pagerScrollView.contentOffset.x;
|
|
952
|
+
largestOffset = MAX(largestOffset, offset);
|
|
953
|
+
if (offset > 1 && offset < CGRectGetWidth(pagerScrollView.bounds) - 1) {
|
|
954
|
+
CGFloat progress = offset / CGRectGetWidth(pagerScrollView.bounds);
|
|
955
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame),
|
|
956
|
+
firstThumbX + (secondThumbX - firstThumbX) * progress, 1);
|
|
957
|
+
intermediateFrames += 1;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
XCTAssertTrue(pagerScrollView.isDragging);
|
|
961
|
+
XCTAssertGreaterThan(largestOffset, 80);
|
|
962
|
+
XCTAssertGreaterThan(intermediateFrames, 2);
|
|
963
|
+
|
|
964
|
+
for (NSInteger step = 17; step >= 0; --step) {
|
|
965
|
+
[self.driver move:touch to:CGPointMake(point.x - 14 * step, point.y)];
|
|
966
|
+
}
|
|
967
|
+
// UIKit can briefly bounce past the leading edge before the finger lifts.
|
|
968
|
+
XCTAssertLessThan(fabs(pagerScrollView.contentOffset.x), 10);
|
|
969
|
+
XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstThumbX, 1);
|
|
970
|
+
[self.driver end:touch];
|
|
971
|
+
}
|
|
972
|
+
|
|
907
973
|
- (void)testStationaryTapKeepsReactPress
|
|
908
974
|
{
|
|
909
975
|
[self assertFixtureIsReadyForDelivery];
|
|
@@ -1001,7 +1067,10 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
1001
1067
|
|
|
1002
1068
|
- (void)nestedLongPress:(UILongPressGestureRecognizer *)recognizer
|
|
1003
1069
|
{
|
|
1004
|
-
if (recognizer.state == UIGestureRecognizerStateBegan)
|
|
1070
|
+
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
|
1071
|
+
self.longPressBegins += 1;
|
|
1072
|
+
[self.longPressExpectation fulfill];
|
|
1073
|
+
}
|
|
1005
1074
|
}
|
|
1006
1075
|
|
|
1007
1076
|
- (CGPoint)pressPointInNestedScrollView:(UIScrollView *)scroll
|
|
@@ -1022,14 +1091,12 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
1022
1091
|
[pager addGestureRecognizer:longPress];
|
|
1023
1092
|
// Exercise production wiring after a root recognizer is attached.
|
|
1024
1093
|
((void (*)(id, SEL))objc_msgSend)(self.pager, NSSelectorFromString(@"connectVerticalPagerGuard"));
|
|
1094
|
+
self.longPressExpectation = [self expectationWithDescription:@"root long press recognized before finger-up"];
|
|
1025
1095
|
UITouch *touch = [self.driver beginAt:[self pressPoint]];
|
|
1026
1096
|
UIGestureRecognizer *guard = [self.pager valueForKey:@"verticalPagerGesture"];
|
|
1027
1097
|
XCTAssertEqual(guard.state, UIGestureRecognizerStatePossible);
|
|
1028
1098
|
XCTAssertEqual(pager.panGestureRecognizer.minimumNumberOfTouches, 2);
|
|
1029
|
-
|
|
1030
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)),
|
|
1031
|
-
dispatch_get_main_queue(), ^{ [hold fulfill]; });
|
|
1032
|
-
[self waitForExpectations:@[hold] timeout:2];
|
|
1099
|
+
[self waitForExpectations:@[self.longPressExpectation] timeout:2];
|
|
1033
1100
|
NSLog(@"[PagerPressDelivery] rootLongPress begins=%lu state=%ld touchPhase=%ld guardState=%ld pagerPanMinTouches=%lu",
|
|
1034
1101
|
(unsigned long)self.longPressBegins, (long)longPress.state, (long)touch.phase,
|
|
1035
1102
|
(long)guard.state, (unsigned long)pager.panGestureRecognizer.minimumNumberOfTouches);
|
|
@@ -1045,14 +1112,12 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
|
|
|
1045
1112
|
- (void)testNestedStationaryNativeLongPressRecognizesBeforeFingerUp
|
|
1046
1113
|
{
|
|
1047
1114
|
UIScrollView *scroll = [self mountNestedHorizontalScrollView];
|
|
1115
|
+
self.longPressExpectation = [self expectationWithDescription:@"nested long press recognized before finger-up"];
|
|
1048
1116
|
UITouch *touch = [self.driver beginAt:[self pressPointInNestedScrollView:scroll]];
|
|
1049
1117
|
UIGestureRecognizer *guard = [self.pager valueForKey:@"verticalPagerGesture"];
|
|
1050
1118
|
XCTAssertEqual(guard.state, UIGestureRecognizerStatePossible);
|
|
1051
1119
|
XCTAssertEqual(scroll.panGestureRecognizer.minimumNumberOfTouches, 2);
|
|
1052
|
-
|
|
1053
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)),
|
|
1054
|
-
dispatch_get_main_queue(), ^{ [hold fulfill]; });
|
|
1055
|
-
[self waitForExpectations:@[hold] timeout:2];
|
|
1120
|
+
[self waitForExpectations:@[self.longPressExpectation] timeout:2];
|
|
1056
1121
|
// The real UIKit target action must arrive while the finger is still down.
|
|
1057
1122
|
XCTAssertEqual(self.longPressBegins, 1);
|
|
1058
1123
|
XCTAssertEqual(touch.phase, UITouchPhaseBegan);
|
|
@@ -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) {
|
|
@@ -75,6 +82,12 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
75
82
|
scrollToPageTop = options => {
|
|
76
83
|
if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 0, options?.animated ?? true);
|
|
77
84
|
};
|
|
85
|
+
scrollHeaderBy = delta => {
|
|
86
|
+
if (this.nativeRef && Number.isFinite(delta)) CollapsiblePagerViewNativeCommands.scrollHeaderBy(this.nativeRef, delta);
|
|
87
|
+
};
|
|
88
|
+
scrollHeaderToOffset = offset => {
|
|
89
|
+
if (this.nativeRef && Number.isFinite(offset)) CollapsiblePagerViewNativeCommands.scrollHeaderToOffset(this.nativeRef, offset);
|
|
90
|
+
};
|
|
78
91
|
scrollToStickyTop = options => {
|
|
79
92
|
if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 1, options?.animated ?? true);
|
|
80
93
|
};
|
|
@@ -89,7 +102,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
89
102
|
nativeTabBarEnabled() {
|
|
90
103
|
return (Platform.OS === "ios" || Platform.OS === "android") && !!this.props.nativeTabBar?.items.length;
|
|
91
104
|
}
|
|
92
|
-
dispatchNativeTabPageCommand(position, animated) {
|
|
105
|
+
dispatchNativeTabPageCommand(position, animated, expectedKey) {
|
|
93
106
|
const commandId = ++this.nativeTabCommandId;
|
|
94
107
|
this.nativeTabCommandQueuedDuringScroll = this.nativePagerScrollState !== "idle";
|
|
95
108
|
this.latestNativeTabTarget = position;
|
|
@@ -97,7 +110,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
97
110
|
transientRetainedPages: state.transientRetainedPages.includes(position) ? state.transientRetainedPages : [...state.transientRetainedPages, position]
|
|
98
111
|
}), () => {
|
|
99
112
|
requestAnimationFrame(() => {
|
|
100
|
-
if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
|
|
113
|
+
if (commandId !== this.nativeTabCommandId || !this.nativeRef || expectedKey !== undefined && (!this.nativeTabBarEnabled() || this.props.nativeTabBar?.items[position]?.key !== expectedKey)) {
|
|
101
114
|
return;
|
|
102
115
|
}
|
|
103
116
|
this.dispatchedNativeTabTarget = position;
|
|
@@ -179,10 +192,13 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
179
192
|
};
|
|
180
193
|
onNativeTabPress = event => {
|
|
181
194
|
const position = pageIndex(event.nativeEvent.position);
|
|
182
|
-
if (
|
|
195
|
+
if (!this.nativeTabBarEnabled() || position === null || this.props.nativeTabBar?.items[position]?.key !== event.nativeEvent.key) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
|
|
183
199
|
// Selection state lags an in-flight transition until native reports it.
|
|
184
200
|
const currentPage = this.dispatchedNativeTabTarget ?? this.state.selectedPage;
|
|
185
|
-
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1);
|
|
201
|
+
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1, event.nativeEvent.key);
|
|
186
202
|
}
|
|
187
203
|
this.props.onNativeTabPress?.(event);
|
|
188
204
|
};
|
|
@@ -197,6 +213,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
197
213
|
headerHeight,
|
|
198
214
|
stickyHeaderHeight,
|
|
199
215
|
topOverlay,
|
|
216
|
+
topOverlayHeightHint,
|
|
200
217
|
externalTopOverlayTag,
|
|
201
218
|
topScrollEdgeEffect,
|
|
202
219
|
topOverlayColorScheme,
|
|
@@ -219,11 +236,16 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
219
236
|
const supportsTopScrollEdge = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
|
|
220
237
|
const scrollEdgeEnabled = supportsTopScrollEdge && (topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft");
|
|
221
238
|
const hasTopOverlay = scrollEdgeEnabled && topOverlay != null;
|
|
239
|
+
const initialTopOverlayHeight = typeof topOverlayHeightHint === "number" && Number.isFinite(topOverlayHeightHint) ? Math.max(0, Math.ceil(topOverlayHeightHint)) : 0;
|
|
222
240
|
const pageKeys = pages.map(pageKey);
|
|
223
241
|
const retained = new Set(this.mountedPages(pages.length));
|
|
224
242
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
225
243
|
const nativeTabBarEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeTabBar?.items.length;
|
|
226
244
|
const nativeTabBarStyle = nativeTabBar?.style;
|
|
245
|
+
const requestedContentPaddingVertical = nativeTabBarStyle?.contentPaddingVertical;
|
|
246
|
+
const nativeTabBarContentPaddingVertical = typeof requestedContentPaddingVertical === "number" && Number.isFinite(requestedContentPaddingVertical) && requestedContentPaddingVertical >= 0 ? requestedContentPaddingVertical : 0;
|
|
247
|
+
const requestedTrailingReservedWidth = nativeTabBarStyle?.trailingReservedWidth;
|
|
248
|
+
const nativeTabBarTrailingReservedWidth = typeof requestedTrailingReservedWidth === "number" && Number.isFinite(requestedTrailingReservedWidth) && requestedTrailingReservedWidth >= 0 ? requestedTrailingReservedWidth : 0;
|
|
227
249
|
const nativeSubHeaderEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeSubHeader?.items.length;
|
|
228
250
|
const nativeSubHeaderStyle = nativeSubHeader?.style;
|
|
229
251
|
const nativeSubHeaderConfig = nativeSubHeaderEnabled ? JSON.stringify({
|
|
@@ -245,7 +267,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
245
267
|
stickyHeaderHeight: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
246
268
|
topScrollEdgeEffect: scrollEdgeEnabled ? topScrollEdgeEffect : "none",
|
|
247
269
|
topOverlayColorScheme: scrollEdgeEnabled ? topOverlayColorScheme : undefined,
|
|
248
|
-
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight : 0,
|
|
270
|
+
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight ?? initialTopOverlayHeight : 0,
|
|
249
271
|
externalTopOverlayTag: supportsTopScrollEdge && !hasTopOverlay ? externalTopOverlayTag ?? 0 : 0,
|
|
250
272
|
nativeSmoothHeaderScrollEnabled: nativeSmoothHeaderScrollEnabled,
|
|
251
273
|
pageKeys: JSON.stringify(pageKeys),
|
|
@@ -254,7 +276,10 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
254
276
|
onPageScrollStateChanged: this.onPageScrollStateChanged,
|
|
255
277
|
nativeTabBarItems: nativeTabBarEnabled ? JSON.stringify(nativeTabBar.items) : undefined,
|
|
256
278
|
nativeTabBarHeight: nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined,
|
|
279
|
+
nativeTabBarVariant: nativeTabBarEnabled ? nativeTabBarStyle?.variant ?? "underline" : undefined,
|
|
280
|
+
nativeTabBarTrailingReservedWidth: nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0,
|
|
257
281
|
nativeTabBarContentPaddingHorizontal: nativeTabBarEnabled ? nativeTabBarStyle?.contentPaddingHorizontal ?? 20 : undefined,
|
|
282
|
+
nativeTabBarContentPaddingVertical: nativeTabBarEnabled ? nativeTabBarContentPaddingVertical : undefined,
|
|
258
283
|
nativeTabBarItemSpacing: nativeTabBarEnabled ? nativeTabBarStyle?.itemSpacing ?? 8 : undefined,
|
|
259
284
|
nativeTabBarFontSize: nativeTabBarEnabled ? nativeTabBarStyle?.fontSize ?? 16 : undefined,
|
|
260
285
|
nativeTabBarFontFamily: nativeTabBarEnabled ? nativeTabBarStyle?.fontFamily : undefined,
|