@onekeyfe/react-native-pager-view 3.0.178 → 3.0.179-alpha.20261015
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +10 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +4 -0
- package/docs/SPEC.md +75 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +835 -138
- package/ios/RNCNativeScrollerComponentView.mm +93 -38
- package/ios/RNCPagerViewComponentView.h +7 -0
- package/ios/RNCPagerViewComponentView.mm +34 -0
- package/lib/module/CollapsiblePagerView.js +27 -1
- package/lib/module/CollapsiblePagerView.web.js +4 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/lib/module/NativeScroller.js +105 -82
- package/lib/module/NativeScroller.web.js +2 -0
- package/lib/module/NativeScrollerNativeComponent.ts +2 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +10 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +4 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/NativeScroller.d.ts +4 -0
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +2 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +44 -0
- package/src/CollapsiblePagerView.web.tsx +8 -0
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/src/NativeScroller.tsx +27 -2
- package/src/NativeScroller.web.tsx +2 -0
- package/src/NativeScrollerNativeComponent.ts +2 -0
|
@@ -33,12 +33,15 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
33
33
|
@end
|
|
34
34
|
|
|
35
35
|
@implementation RNCNativeScrollerComponentView {
|
|
36
|
-
#if DEBUG
|
|
37
|
-
NSUInteger _offsetDiagSequence;
|
|
38
|
-
#endif
|
|
39
36
|
RNCNativeScrollerShadowNode::ConcreteState::Shared _state;
|
|
40
37
|
RNCNativeVerticalScrollView *_scrollView;
|
|
41
38
|
UIView<RCTComponentViewProtocol> *_reactContent;
|
|
39
|
+
__weak UIView *_topOverlay;
|
|
40
|
+
CGFloat _appliedOverlayInset;
|
|
41
|
+
UIScrollEdgeElementContainerInteraction *_topEdgeInteraction;
|
|
42
|
+
__weak UIView *_topEdgeInteractionHost;
|
|
43
|
+
UILabel *_topEdgeMarker;
|
|
44
|
+
UIScrollEdgeEffectStyle *_originalTopEdgeStyle;
|
|
42
45
|
UIRefreshControl *_refreshControl;
|
|
43
46
|
UIEdgeInsets _callerInset;
|
|
44
47
|
BOOL _dragActive;
|
|
@@ -54,24 +57,6 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
54
57
|
NSNumber *_lastContentViewportHeight;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
- (void)offsetDiag:(NSString *)reason
|
|
58
|
-
{
|
|
59
|
-
#if DEBUG
|
|
60
|
-
Class logClass = NSClassFromString(@"ReactNativeNativeLogger.OneKeyLog") ?: NSClassFromString(@"OneKeyLog");
|
|
61
|
-
SEL selector = NSSelectorFromString(@"debug::");
|
|
62
|
-
if (logClass == nil || ![logClass respondsToSelector:selector]) return;
|
|
63
|
-
NSString *message = [NSString stringWithFormat:
|
|
64
|
-
@"[HOME-OFFSET-DIAG] scroller phase=%@ seq=%lu wallMs=%.0f mono=%.6f host=%p tag=%ld scroll=%p generation=%lu physicalY=%.4f insetTop=%.4f logicalY=%.4f contentHeight=%.4f boundsHeight=%.4f adjustedBottom=%.4f needsClamp=%d drag=%d decel=%d refresh=%d",
|
|
65
|
-
reason, (unsigned long)++_offsetDiagSequence, NSDate.date.timeIntervalSince1970 * 1000, CACurrentMediaTime(),
|
|
66
|
-
(__bridge void *)self, (long)self.tag, (__bridge void *)_scrollView, (unsigned long)_generation,
|
|
67
|
-
_scrollView.contentOffset.y, _scrollView.contentInset.top, _scrollView.contentOffset.y + _scrollView.contentInset.top,
|
|
68
|
-
_scrollView.contentSize.height, _scrollView.bounds.size.height, _scrollView.adjustedContentInset.bottom,
|
|
69
|
-
_needsContentClamp, _scrollView.dragging, _scrollView.decelerating, _refreshControl.refreshing];
|
|
70
|
-
typedef void (*LogFunction)(id, SEL, NSString *, NSString *);
|
|
71
|
-
((LogFunction)[logClass methodForSelector:selector])(logClass, selector, @"NativeScroller", message);
|
|
72
|
-
#endif
|
|
73
|
-
}
|
|
74
|
-
|
|
75
60
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
76
61
|
{
|
|
77
62
|
return concreteComponentDescriptorProvider<RNCNativeScrollerComponentDescriptor>();
|
|
@@ -200,9 +185,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
200
185
|
_needsContentClamp |= size.height < _scrollView.contentSize.height;
|
|
201
186
|
CGPoint offset = _scrollView.contentOffset;
|
|
202
187
|
BOOL wasPulling = offset.y < -_scrollView.adjustedContentInset.top;
|
|
203
|
-
[self offsetDiag:@"content-size-before"];
|
|
204
188
|
_scrollView.contentSize = size;
|
|
205
|
-
[self offsetDiag:@"content-size-after"];
|
|
206
189
|
// UIKit can synchronously discard a pull when content becomes empty.
|
|
207
190
|
// Upper-range shrink correction remains owned by the deferred clamp.
|
|
208
191
|
if (wasPulling && _scrollView.contentOffset.y != offset.y) {
|
|
@@ -212,20 +195,99 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
212
195
|
}
|
|
213
196
|
BOOL refreshEnded = previous.refreshing && !next.refreshing;
|
|
214
197
|
[super updateProps:props oldProps:oldProps];
|
|
198
|
+
[self updateTopScrollEdge];
|
|
199
|
+
if (next.topScrollEdgeHard && _topOverlay == nil) {
|
|
200
|
+
__weak __typeof__(self) weakSelf = self;
|
|
201
|
+
dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf updateTopScrollEdge]; });
|
|
202
|
+
}
|
|
215
203
|
if (refreshEnded) [self scheduleContentClamp];
|
|
216
204
|
[self updateFabricContentOrigin];
|
|
217
205
|
[self emitContentViewportChangeIfNeeded];
|
|
218
206
|
}
|
|
219
207
|
|
|
208
|
+
- (void)updateTopScrollEdge
|
|
209
|
+
{
|
|
210
|
+
if (@available(iOS 26.0, *)) {
|
|
211
|
+
const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
|
|
212
|
+
UIView *overlay = nil;
|
|
213
|
+
if (props.topScrollEdgeHard && !_recycled) {
|
|
214
|
+
NSUInteger fabricSiblings = 0;
|
|
215
|
+
for (UIView *candidate in self.superview.subviews) {
|
|
216
|
+
if (![candidate conformsToProtocol:@protocol(RCTComponentViewProtocol)]) continue;
|
|
217
|
+
fabricSiblings++;
|
|
218
|
+
if (candidate != self) overlay = candidate;
|
|
219
|
+
}
|
|
220
|
+
if (fabricSiblings != 2) overlay = nil;
|
|
221
|
+
}
|
|
222
|
+
_topOverlay = overlay;
|
|
223
|
+
CGFloat requestedOverlayInset = props.topScrollEdgeHard && !_recycled && overlay != nil &&
|
|
224
|
+
isfinite(props.topOverlayHeight) ? MAX(0, props.topOverlayHeight) : 0;
|
|
225
|
+
if (_appliedOverlayInset != requestedOverlayInset) {
|
|
226
|
+
// Keep UIKit's refresh rebound animation in control while it is overscrolled.
|
|
227
|
+
BOOL wasReturningFromPull = _scrollView.contentOffset.y < -_scrollView.adjustedContentInset.top - 0.5;
|
|
228
|
+
CGFloat logicalOffset = _scrollView.contentOffset.y + _scrollView.contentInset.top;
|
|
229
|
+
UIEdgeInsets current = _scrollView.contentInset;
|
|
230
|
+
current.top += requestedOverlayInset - _appliedOverlayInset;
|
|
231
|
+
_scrollView.contentInset = current;
|
|
232
|
+
_appliedOverlayInset = requestedOverlayInset;
|
|
233
|
+
if (!wasReturningFromPull) {
|
|
234
|
+
[_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x,
|
|
235
|
+
logicalOffset - current.top) animated:NO];
|
|
236
|
+
}
|
|
237
|
+
[self emitContentViewportChangeIfNeeded];
|
|
238
|
+
}
|
|
239
|
+
BOOL enabled = props.topScrollEdgeHard && !_recycled && overlay != nil && self.window != nil;
|
|
240
|
+
if (!enabled || _topEdgeInteractionHost != overlay) {
|
|
241
|
+
if (_topEdgeInteraction != nil) {
|
|
242
|
+
[_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
|
|
243
|
+
_topEdgeInteraction.scrollView = nil;
|
|
244
|
+
_topEdgeInteraction = nil;
|
|
245
|
+
_topEdgeInteractionHost = nil;
|
|
246
|
+
}
|
|
247
|
+
[_topEdgeMarker removeFromSuperview];
|
|
248
|
+
_topEdgeMarker = nil;
|
|
249
|
+
if (_originalTopEdgeStyle != nil) {
|
|
250
|
+
_scrollView.topEdgeEffect.style = _originalTopEdgeStyle;
|
|
251
|
+
_originalTopEdgeStyle = nil;
|
|
252
|
+
}
|
|
253
|
+
if (!enabled) return;
|
|
254
|
+
}
|
|
255
|
+
if (_topEdgeInteraction == nil) {
|
|
256
|
+
_originalTopEdgeStyle = _scrollView.topEdgeEffect.style;
|
|
257
|
+
// UIKit needs a native element to shape the edge effect around React-drawn content.
|
|
258
|
+
_topEdgeMarker = [UILabel new];
|
|
259
|
+
_topEdgeMarker.frame = overlay.bounds;
|
|
260
|
+
_topEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
261
|
+
_topEdgeMarker.text = @" ";
|
|
262
|
+
_topEdgeMarker.textColor = UIColor.clearColor;
|
|
263
|
+
_topEdgeMarker.backgroundColor = UIColor.clearColor;
|
|
264
|
+
_topEdgeMarker.userInteractionEnabled = NO;
|
|
265
|
+
_topEdgeMarker.isAccessibilityElement = NO;
|
|
266
|
+
_topEdgeMarker.accessibilityElementsHidden = YES;
|
|
267
|
+
[overlay addSubview:_topEdgeMarker];
|
|
268
|
+
_topEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
|
|
269
|
+
_topEdgeInteraction.edge = UIRectEdgeTop;
|
|
270
|
+
_topEdgeInteraction.scrollView = _scrollView;
|
|
271
|
+
[overlay addInteraction:_topEdgeInteraction];
|
|
272
|
+
_topEdgeInteractionHost = overlay;
|
|
273
|
+
}
|
|
274
|
+
_scrollView.topEdgeEffect.style = UIScrollEdgeEffectStyle.hardStyle;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
- (void)didMoveToSuperview
|
|
279
|
+
{
|
|
280
|
+
[super didMoveToSuperview];
|
|
281
|
+
[self updateTopScrollEdge];
|
|
282
|
+
}
|
|
283
|
+
|
|
220
284
|
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
|
221
285
|
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
|
|
222
286
|
{
|
|
223
|
-
[self offsetDiag:@"layout-before"];
|
|
224
287
|
CGSize previous = _scrollView.bounds.size;
|
|
225
288
|
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
226
289
|
[self updateFabricContentOrigin];
|
|
227
290
|
[self emitContentViewportChangeIfNeeded];
|
|
228
|
-
[self offsetDiag:@"layout-after"];
|
|
229
291
|
if (!CGSizeEqualToSize(previous, _scrollView.bounds.size)) {
|
|
230
292
|
_needsContentClamp = YES;
|
|
231
293
|
[self scheduleContentClamp];
|
|
@@ -236,7 +298,8 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
236
298
|
{
|
|
237
299
|
CGFloat stickyHeight = 0;
|
|
238
300
|
for (NSNumber *height in _pagerStickyHeights.objectEnumerator) stickyHeight += height.doubleValue;
|
|
239
|
-
return MAX(0, _scrollView.bounds.size.height - _callerInset.top - _callerInset.bottom
|
|
301
|
+
return MAX(0, _scrollView.bounds.size.height - _callerInset.top - _callerInset.bottom
|
|
302
|
+
- _appliedOverlayInset - stickyHeight);
|
|
240
303
|
}
|
|
241
304
|
|
|
242
305
|
- (void)setPagerStickyHeight:(CGFloat)height forOwner:(id)owner
|
|
@@ -288,7 +351,6 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
288
351
|
- (void)scheduleContentClamp
|
|
289
352
|
{
|
|
290
353
|
if (!_needsContentClamp) return;
|
|
291
|
-
[self offsetDiag:@"clamp-scheduled"];
|
|
292
354
|
NSUInteger generation = _generation;
|
|
293
355
|
__weak __typeof__(self) weakSelf = self;
|
|
294
356
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -300,19 +362,13 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
300
362
|
|
|
301
363
|
- (void)clampContentOffsetIfNeeded
|
|
302
364
|
{
|
|
303
|
-
[self offsetDiag:@"clamp-enter-or-deferred"];
|
|
304
365
|
if (!_needsContentClamp || _scrollView.dragging || _scrollView.decelerating ||
|
|
305
366
|
_refreshControl.refreshing || _scrollView.bounds.size.height <= 0) return;
|
|
306
367
|
_needsContentClamp = NO;
|
|
307
368
|
CGFloat maximum = MAX(-_scrollView.adjustedContentInset.top,
|
|
308
369
|
_scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.adjustedContentInset.bottom);
|
|
309
|
-
#if DEBUG
|
|
310
|
-
[self offsetDiag:[NSString stringWithFormat:@"clamp-candidate-max=%.4f", maximum]];
|
|
311
|
-
#endif
|
|
312
370
|
if (_scrollView.contentOffset.y > maximum) {
|
|
313
|
-
[self offsetDiag:@"clamp-applied-before"];
|
|
314
371
|
[_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, maximum) animated:NO];
|
|
315
|
-
[self offsetDiag:@"clamp-applied-after"];
|
|
316
372
|
}
|
|
317
373
|
}
|
|
318
374
|
|
|
@@ -353,7 +409,6 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
353
409
|
- (void)stopScrolling
|
|
354
410
|
{
|
|
355
411
|
if (_stopping) return;
|
|
356
|
-
[self offsetDiag:@"stop-before"];
|
|
357
412
|
_stopping = YES;
|
|
358
413
|
[_scrollView setContentOffset:_scrollView.contentOffset animated:NO];
|
|
359
414
|
if (_dragActive) {
|
|
@@ -365,7 +420,6 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
365
420
|
}
|
|
366
421
|
[self finishMomentum];
|
|
367
422
|
_stopping = NO;
|
|
368
|
-
[self offsetDiag:@"stop-after"];
|
|
369
423
|
}
|
|
370
424
|
|
|
371
425
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
@@ -435,9 +489,6 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
435
489
|
[(id<RNCNativeScrollerInsetCoordinator>)owner nativeScrollerWillReceiveScrollCommand:self];
|
|
436
490
|
}
|
|
437
491
|
}
|
|
438
|
-
#if DEBUG
|
|
439
|
-
[self offsetDiag:[NSString stringWithFormat:@"command-scrollTo-enter-x=%.4f-y=%.4f-animated=%d", x, y, animated]];
|
|
440
|
-
#endif
|
|
441
492
|
[self stopScrolling];
|
|
442
493
|
UIEdgeInsets inset = _scrollView.adjustedContentInset;
|
|
443
494
|
CGPoint target = CGPointMake(MAX(-inset.left, MIN(x,
|
|
@@ -489,6 +540,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
489
540
|
- (void)didMoveToWindow
|
|
490
541
|
{
|
|
491
542
|
[super didMoveToWindow];
|
|
543
|
+
[self updateTopScrollEdge];
|
|
492
544
|
if (self.window == nil) {
|
|
493
545
|
[self stopScrolling];
|
|
494
546
|
[_pagerStickyHeights removeAllObjects];
|
|
@@ -504,6 +556,9 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
504
556
|
{
|
|
505
557
|
[self stopScrolling];
|
|
506
558
|
_recycled = YES;
|
|
559
|
+
[self updateTopScrollEdge];
|
|
560
|
+
_topOverlay = nil;
|
|
561
|
+
_appliedOverlayInset = 0;
|
|
507
562
|
_scrollView.delegate = nil;
|
|
508
563
|
++_generation;
|
|
509
564
|
_state.reset();
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
NS_ASSUME_NONNULL_BEGIN
|
|
6
6
|
|
|
7
|
+
@protocol RNCPagerViewNestedOverlayObserver <NSObject>
|
|
8
|
+
- (void)outerPagerDidUpdateOverlayOwner:(BOOL)active transitioning:(BOOL)transitioning;
|
|
9
|
+
@end
|
|
10
|
+
|
|
7
11
|
@interface RNCPagerViewComponentView : RCTViewComponentView
|
|
8
12
|
|
|
13
|
+
- (void)addNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer;
|
|
14
|
+
- (void)removeNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer;
|
|
15
|
+
|
|
9
16
|
@end
|
|
10
17
|
|
|
11
18
|
NS_ASSUME_NONNULL_END
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
using namespace facebook::react;
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
@interface RNCPagerViewComponentView () <RCTRNCViewPagerViewProtocol, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate>
|
|
16
17
|
|
|
17
18
|
@property(nonatomic, strong) UIPageViewController *nativePageViewController;
|
|
@@ -39,10 +40,32 @@ using namespace facebook::react;
|
|
|
39
40
|
NSInteger _pendingGoToIndex;
|
|
40
41
|
// _nestedScrollEnabled: when YES, this inner PagerView coordinates gestures with an outer PagerView
|
|
41
42
|
BOOL _nestedScrollEnabled;
|
|
43
|
+
NSHashTable<id<RNCPagerViewNestedOverlayObserver>> *_nestedOverlayObservers;
|
|
42
44
|
// _blockerGesture: gate gesture that blocks inner scroll at page boundaries when nested
|
|
43
45
|
UIPanGestureRecognizer *_blockerGesture;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
- (void)notifyNestedOverlayObservers {
|
|
49
|
+
UIView *activePage = _currentIndex >= 0 &&
|
|
50
|
+
_currentIndex < _nativeChildrenViewControllers.count
|
|
51
|
+
? _nativeChildrenViewControllers[_currentIndex].view : nil;
|
|
52
|
+
for (id<RNCPagerViewNestedOverlayObserver> observer in _nestedOverlayObservers) {
|
|
53
|
+
BOOL active = activePage != nil &&
|
|
54
|
+
[(UIView *)observer isDescendantOfView:activePage];
|
|
55
|
+
[observer outerPagerDidUpdateOverlayOwner:active transitioning:transitioning || scrollView.dragging || scrollView.decelerating];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
- (void)addNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer {
|
|
60
|
+
if (_nestedOverlayObservers == nil) _nestedOverlayObservers = [NSHashTable weakObjectsHashTable];
|
|
61
|
+
[_nestedOverlayObservers addObject:observer];
|
|
62
|
+
[self notifyNestedOverlayObservers];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
- (void)removeNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer {
|
|
66
|
+
[_nestedOverlayObservers removeObject:observer];
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
// Needed because of this: https://github.com/facebook/react-native/pull/37274
|
|
47
70
|
+ (void)load
|
|
48
71
|
{
|
|
@@ -158,6 +181,7 @@ using namespace facebook::react;
|
|
|
158
181
|
-(void)prepareForRecycle {
|
|
159
182
|
_isBeingRecycled = YES;
|
|
160
183
|
_generation++;
|
|
184
|
+
[_nestedOverlayObservers removeAllObjects];
|
|
161
185
|
|
|
162
186
|
// Cancel any ongoing transitions
|
|
163
187
|
if (_nativePageViewController != nil && transitioning) {
|
|
@@ -339,6 +363,7 @@ using namespace facebook::react;
|
|
|
339
363
|
_pendingGoToIndex = -1;
|
|
340
364
|
|
|
341
365
|
transitioning = YES;
|
|
366
|
+
[self notifyNestedOverlayObservers];
|
|
342
367
|
|
|
343
368
|
__weak RNCPagerViewComponentView *weakSelf = self;
|
|
344
369
|
NSUInteger capturedGeneration = _generation;
|
|
@@ -370,6 +395,7 @@ using namespace facebook::react;
|
|
|
370
395
|
|
|
371
396
|
[strongSelf enableSwipe];
|
|
372
397
|
strongSelf->_currentIndex = index;
|
|
398
|
+
[strongSelf notifyNestedOverlayObservers];
|
|
373
399
|
if (strongSelf->_eventEmitter != nullptr ) {
|
|
374
400
|
const auto eventEmitter = [strongSelf pagerEventEmitter];
|
|
375
401
|
if (eventEmitter) {
|
|
@@ -422,6 +448,7 @@ using namespace facebook::react;
|
|
|
422
448
|
if (_isBeingRecycled) {
|
|
423
449
|
return;
|
|
424
450
|
}
|
|
451
|
+
[self notifyNestedOverlayObservers];
|
|
425
452
|
const auto eventEmitter = [self pagerEventEmitter];
|
|
426
453
|
if (!eventEmitter) {
|
|
427
454
|
return;
|
|
@@ -455,7 +482,12 @@ using namespace facebook::react;
|
|
|
455
482
|
}
|
|
456
483
|
}
|
|
457
484
|
|
|
485
|
+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
|
|
486
|
+
if (!decelerate) [self notifyNestedOverlayObservers];
|
|
487
|
+
}
|
|
488
|
+
|
|
458
489
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
|
490
|
+
[self notifyNestedOverlayObservers];
|
|
459
491
|
if (_isBeingRecycled) {
|
|
460
492
|
return;
|
|
461
493
|
}
|
|
@@ -529,12 +561,14 @@ using namespace facebook::react;
|
|
|
529
561
|
UIViewController* currentVC = [self currentlyDisplayed];
|
|
530
562
|
NSUInteger currentIndex = [_nativeChildrenViewControllers indexOfObject:currentVC];
|
|
531
563
|
_currentIndex = currentIndex;
|
|
564
|
+
[self notifyNestedOverlayObservers];
|
|
532
565
|
int position = (int) currentIndex;
|
|
533
566
|
const auto eventEmitter = [self pagerEventEmitter];
|
|
534
567
|
if (eventEmitter) {
|
|
535
568
|
eventEmitter->onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast<double>(position)});
|
|
536
569
|
}
|
|
537
570
|
}
|
|
571
|
+
if (!completed) [self notifyNestedOverlayObservers];
|
|
538
572
|
}
|
|
539
573
|
|
|
540
574
|
#pragma mark - UIPageViewControllerDataSource
|
|
@@ -23,6 +23,7 @@ const pageKey = (child, index) => {
|
|
|
23
23
|
*/
|
|
24
24
|
export class CollapsiblePagerView extends React.PureComponent {
|
|
25
25
|
state = {
|
|
26
|
+
topOverlayHeight: 0,
|
|
26
27
|
selectedPage: clampedPageIndex(this.props.initialPage ?? 0, React.Children.toArray(this.props.children).length),
|
|
27
28
|
transientRetainedPages: []
|
|
28
29
|
};
|
|
@@ -195,6 +196,10 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
195
196
|
stickyHeader,
|
|
196
197
|
headerHeight,
|
|
197
198
|
stickyHeaderHeight,
|
|
199
|
+
topOverlay,
|
|
200
|
+
externalTopOverlayTag,
|
|
201
|
+
topScrollEdgeEffect,
|
|
202
|
+
topOverlayColorScheme,
|
|
198
203
|
nativeSmoothHeaderScrollEnabled,
|
|
199
204
|
pageRetentionDistance: _pageRetentionDistance,
|
|
200
205
|
onMountedPagesChanged: _onMountedPagesChanged,
|
|
@@ -211,6 +216,9 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
211
216
|
...nativeProps
|
|
212
217
|
} = this.props;
|
|
213
218
|
const pages = this.pages();
|
|
219
|
+
const supportsTopScrollEdge = Platform.OS === "ios" && Number(Platform.Version) >= 26;
|
|
220
|
+
const hardScrollEdge = supportsTopScrollEdge && topScrollEdgeEffect === "hard";
|
|
221
|
+
const hasTopOverlay = hardScrollEdge && topOverlay != null;
|
|
214
222
|
const pageKeys = pages.map(pageKey);
|
|
215
223
|
const retained = new Set(this.mountedPages(pages.length));
|
|
216
224
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
@@ -235,6 +243,10 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
235
243
|
offscreenPageLimit: Math.max(1, offscreenPageLimit ?? 1),
|
|
236
244
|
headerHeight: Math.max(0, Math.round(headerHeight)),
|
|
237
245
|
stickyHeaderHeight: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
246
|
+
topScrollEdgeHard: hardScrollEdge,
|
|
247
|
+
topOverlayColorScheme: hardScrollEdge ? topOverlayColorScheme : undefined,
|
|
248
|
+
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight : 0,
|
|
249
|
+
externalTopOverlayTag: supportsTopScrollEdge && !hasTopOverlay ? externalTopOverlayTag ?? 0 : 0,
|
|
238
250
|
nativeSmoothHeaderScrollEnabled: nativeSmoothHeaderScrollEnabled,
|
|
239
251
|
pageKeys: JSON.stringify(pageKeys),
|
|
240
252
|
retainedPages: JSON.stringify([...retained]),
|
|
@@ -273,7 +285,21 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
273
285
|
height: Math.max(0, Math.round(stickyHeaderHeight))
|
|
274
286
|
}],
|
|
275
287
|
children: stickyHeader
|
|
276
|
-
}, "sticky-header"),
|
|
288
|
+
}, "sticky-header"), hasTopOverlay ? /*#__PURE__*/_jsx(View, {
|
|
289
|
+
collapsable: false,
|
|
290
|
+
style: [styles.slot, {
|
|
291
|
+
bottom: undefined
|
|
292
|
+
}],
|
|
293
|
+
onLayout: event => {
|
|
294
|
+
const height = Math.ceil(event.nativeEvent.layout.height);
|
|
295
|
+
if (this.state.topOverlayHeight !== height) {
|
|
296
|
+
this.setState({
|
|
297
|
+
topOverlayHeight: height
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
children: topOverlay
|
|
302
|
+
}, "top-overlay") : null, pages.map((page, index) => /*#__PURE__*/_jsx(View, {
|
|
277
303
|
collapsable: false,
|
|
278
304
|
style: [styles.slot,
|
|
279
305
|
// Android translates the expanded pager while consuming header scroll.
|
|
@@ -483,6 +483,10 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
483
483
|
children: _children,
|
|
484
484
|
header,
|
|
485
485
|
stickyHeader,
|
|
486
|
+
topOverlay: _topOverlay,
|
|
487
|
+
topScrollEdgeEffect: _topScrollEdgeEffect,
|
|
488
|
+
topOverlayColorScheme: _topOverlayColorScheme,
|
|
489
|
+
externalTopOverlayTag: _externalTopOverlayTag,
|
|
486
490
|
headerHeight: _headerHeight,
|
|
487
491
|
stickyHeaderHeight: _stickyHeaderHeight,
|
|
488
492
|
pageRetentionDistance: _pageRetentionDistance,
|
|
@@ -43,6 +43,7 @@ export type OnNativeTabPressEventData = Readonly<{
|
|
|
43
43
|
position: Int32;
|
|
44
44
|
key: string;
|
|
45
45
|
}>;
|
|
46
|
+
export type OnScrollEdgeCapabilityEventData = Readonly<{ available: boolean }>;
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Internal native props. Consumers should use CollapsiblePagerViewProps from
|
|
@@ -58,6 +59,10 @@ export interface NativeProps extends ViewProps {
|
|
|
58
59
|
nativeSmoothHeaderScrollEnabled?: WithDefault<boolean, false>;
|
|
59
60
|
headerHeight?: Int32;
|
|
60
61
|
stickyHeaderHeight?: Int32;
|
|
62
|
+
topScrollEdgeHard?: WithDefault<boolean, false>;
|
|
63
|
+
topOverlayColorScheme?: string;
|
|
64
|
+
topOverlayHeight?: WithDefault<Double, 0>;
|
|
65
|
+
externalTopOverlayTag?: WithDefault<Int32, 0>;
|
|
61
66
|
pageKeys?: string;
|
|
62
67
|
retainedPages?: string;
|
|
63
68
|
nativeTabBarItems?: string;
|
|
@@ -80,6 +85,7 @@ export interface NativeProps extends ViewProps {
|
|
|
80
85
|
onCollapsibleStateChanged?: DirectEventHandler<OnCollapsibleStateChangedEventData>;
|
|
81
86
|
onNativeTabPress?: DirectEventHandler<OnNativeTabPressEventData>;
|
|
82
87
|
onNativeSubHeaderPress?: DirectEventHandler<OnNativeTabPressEventData>;
|
|
88
|
+
onScrollEdgeCapability?: DirectEventHandler<OnScrollEdgeCapabilityEventData>;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { findNodeHandle, Keyboard, Platform, StyleSheet, TextInput, View } from "react-native";
|
|
5
5
|
import NativeScrollerHost, { Commands } from "./NativeScrollerNativeComponent";
|
|
6
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
7
|
export const NATIVE_SCROLLER_NATIVE_ID_PREFIX = "rnc-collapsible-pager-native-scroller";
|
|
8
8
|
|
|
9
9
|
/** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
|
|
@@ -27,6 +27,8 @@ const styles = StyleSheet.create({
|
|
|
27
27
|
});
|
|
28
28
|
export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScroller({
|
|
29
29
|
children,
|
|
30
|
+
topOverlay,
|
|
31
|
+
topScrollEdgeEffect,
|
|
30
32
|
nativeID,
|
|
31
33
|
pagerScrollKey,
|
|
32
34
|
style,
|
|
@@ -56,6 +58,8 @@ export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScrol
|
|
|
56
58
|
width: 0,
|
|
57
59
|
height: 0
|
|
58
60
|
});
|
|
61
|
+
const [topOverlayHeight, setTopOverlayHeight] = React.useState(0);
|
|
62
|
+
const hasTopOverlay = Platform.OS === "ios" && Number(Platform.Version) >= 26 && topScrollEdgeEffect === "hard" && topOverlay != null;
|
|
59
63
|
const [refreshEpoch, requestRefreshSync] = React.useReducer(value => value + 1, 0);
|
|
60
64
|
const contentSizeRef = React.useRef(contentSize);
|
|
61
65
|
const scrolledSinceGrant = React.useRef(false);
|
|
@@ -94,92 +98,111 @@ export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScrol
|
|
|
94
98
|
blur: () => hostRef.current?.blur()
|
|
95
99
|
}), []);
|
|
96
100
|
const keyboardIsDismissible = () => Boolean(TextInput.State.currentlyFocusedInput() && (Keyboard.isVisible() || Platform.OS === "android" && Number(Platform.Version) < 30) && Keyboard.metrics()?.height !== 0);
|
|
97
|
-
return /*#__PURE__*/
|
|
98
|
-
|
|
99
|
-
ref: hostRef,
|
|
100
|
-
nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${pagerScrollKey ?? nativeID ?? generatedId}`,
|
|
101
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
102
|
+
collapsable: false,
|
|
101
103
|
style: [styles.viewport, style],
|
|
102
|
-
nestedScrollEnabled: nestedScrollEnabled,
|
|
103
|
-
contentWidth: contentSize.width,
|
|
104
|
-
contentHeight: contentSize.height,
|
|
105
|
-
contentInsetTop: contentInset?.top ?? 0,
|
|
106
|
-
contentInsetRight: contentInset?.right ?? 0,
|
|
107
|
-
contentInsetBottom: contentInset?.bottom ?? 0,
|
|
108
|
-
contentInsetLeft: contentInset?.left ?? 0,
|
|
109
|
-
decelerationRate: rate,
|
|
110
|
-
keyboardShouldPersistTaps: persistTaps,
|
|
111
|
-
refreshEnabled: Boolean(refresh) && refresh?.enabled !== false,
|
|
112
|
-
refreshing: refresh?.refreshing ?? false,
|
|
113
|
-
refreshTintColor: refresh?.tintColor,
|
|
114
|
-
refreshColors: refresh?.colors,
|
|
115
|
-
refreshProgressBackgroundColor: refresh?.progressBackgroundColor,
|
|
116
|
-
refreshProgressViewOffset: refresh?.progressViewOffset ?? 0,
|
|
117
|
-
refreshTitle: refresh?.title,
|
|
118
|
-
refreshTitleColor: refresh?.titleColor,
|
|
119
|
-
refreshSize: refresh?.size === "large" ? "large" : "default",
|
|
120
|
-
onRefresh: () => {
|
|
121
|
-
try {
|
|
122
|
-
refresh?.onRefresh?.();
|
|
123
|
-
} finally {
|
|
124
|
-
requestRefreshSync();
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
104
|
onLayout: onLayout,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
105
|
+
children: [/*#__PURE__*/_jsx(NativeScrollerHost, {
|
|
106
|
+
...props,
|
|
107
|
+
ref: hostRef,
|
|
108
|
+
nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${pagerScrollKey ?? nativeID ?? generatedId}`,
|
|
109
|
+
style: StyleSheet.absoluteFill,
|
|
110
|
+
nestedScrollEnabled: nestedScrollEnabled,
|
|
111
|
+
contentWidth: contentSize.width,
|
|
112
|
+
contentHeight: contentSize.height,
|
|
113
|
+
contentInsetTop: contentInset?.top ?? 0,
|
|
114
|
+
contentInsetRight: contentInset?.right ?? 0,
|
|
115
|
+
contentInsetBottom: contentInset?.bottom ?? 0,
|
|
116
|
+
contentInsetLeft: contentInset?.left ?? 0,
|
|
117
|
+
topScrollEdgeHard: hasTopOverlay,
|
|
118
|
+
topOverlayHeight: hasTopOverlay ? topOverlayHeight : 0,
|
|
119
|
+
decelerationRate: rate,
|
|
120
|
+
keyboardShouldPersistTaps: persistTaps,
|
|
121
|
+
refreshEnabled: Boolean(refresh) && refresh?.enabled !== false,
|
|
122
|
+
refreshing: refresh?.refreshing ?? false,
|
|
123
|
+
refreshTintColor: refresh?.tintColor,
|
|
124
|
+
refreshColors: refresh?.colors,
|
|
125
|
+
refreshProgressBackgroundColor: refresh?.progressBackgroundColor,
|
|
126
|
+
refreshProgressViewOffset: refresh?.progressViewOffset ?? 0,
|
|
127
|
+
refreshTitle: refresh?.title,
|
|
128
|
+
refreshTitleColor: refresh?.titleColor,
|
|
129
|
+
refreshSize: refresh?.size === "large" ? "large" : "default",
|
|
130
|
+
onRefresh: () => {
|
|
131
|
+
try {
|
|
132
|
+
refresh?.onRefresh?.();
|
|
133
|
+
} finally {
|
|
134
|
+
requestRefreshSync();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
onContentViewportChange: event => {
|
|
138
|
+
const height = event.nativeEvent.height;
|
|
139
|
+
if (!Number.isFinite(height) || height < 0) return;
|
|
140
|
+
setContentViewportHeight(previous => previous === height ? previous : height);
|
|
141
|
+
},
|
|
142
|
+
onScroll: event => {
|
|
143
|
+
scrolledSinceGrant.current = true;
|
|
144
|
+
onScroll?.(event);
|
|
145
|
+
},
|
|
146
|
+
onScrollBeginDrag: event => {
|
|
147
|
+
scrolledSinceGrant.current = true;
|
|
148
|
+
onScrollBeginDrag?.(event);
|
|
149
|
+
},
|
|
150
|
+
onMomentumScrollBegin: event => {
|
|
151
|
+
momentum.current = true;
|
|
152
|
+
onMomentumScrollBegin?.(event);
|
|
153
|
+
},
|
|
154
|
+
onMomentumScrollEnd: event => {
|
|
155
|
+
momentum.current = false;
|
|
156
|
+
onMomentumScrollEnd?.(event);
|
|
157
|
+
},
|
|
158
|
+
onStartShouldSetResponder: event => onStartShouldSetResponder?.(event) ?? (persistTaps === "handled" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput()),
|
|
159
|
+
onStartShouldSetResponderCapture: event => onStartShouldSetResponderCapture?.(event) ?? (momentum.current || persistTaps === "never" && keyboardIsDismissible() && typeof event.target !== "number" && !textInputState.isTextInput(event.target)),
|
|
160
|
+
onResponderGrant: event => {
|
|
161
|
+
scrolledSinceGrant.current = false;
|
|
162
|
+
grantedDuringMomentum.current = momentum.current;
|
|
163
|
+
onResponderGrant?.(event);
|
|
164
|
+
},
|
|
165
|
+
onResponderRelease: event => {
|
|
166
|
+
onResponderRelease?.(event);
|
|
167
|
+
if (persistTaps !== "always" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput() && !scrolledSinceGrant.current && !grantedDuringMomentum.current) Keyboard.dismiss();
|
|
168
|
+
},
|
|
169
|
+
onResponderTerminationRequest: event => onResponderTerminationRequest?.(event) ?? true,
|
|
170
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
171
|
+
collapsable: false,
|
|
172
|
+
style: [contentContainerStyle, (contentStyle?.flexGrow ?? 0) > 0 && (contentStyle?.minHeight === undefined || typeof contentStyle.minHeight === "number") ? {
|
|
173
|
+
minHeight: Math.max(contentViewportHeight, contentStyle?.minHeight ?? 0)
|
|
174
|
+
} : undefined, styles.content],
|
|
175
|
+
onLayout: event => {
|
|
176
|
+
const {
|
|
177
|
+
width,
|
|
178
|
+
height
|
|
179
|
+
} = event.nativeEvent.layout;
|
|
180
|
+
const previous = contentSizeRef.current;
|
|
181
|
+
if (previous.width === width && previous.height === height) return;
|
|
182
|
+
const next = {
|
|
183
|
+
width,
|
|
184
|
+
height
|
|
185
|
+
};
|
|
186
|
+
contentSizeRef.current = next;
|
|
187
|
+
setContentSize(next);
|
|
188
|
+
onContentSizeChange?.(width, height);
|
|
189
|
+
},
|
|
190
|
+
children: children
|
|
191
|
+
})
|
|
192
|
+
}), hasTopOverlay ? /*#__PURE__*/_jsx(View, {
|
|
162
193
|
collapsable: false,
|
|
163
|
-
style:
|
|
164
|
-
|
|
165
|
-
|
|
194
|
+
style: {
|
|
195
|
+
position: "absolute",
|
|
196
|
+
top: 0,
|
|
197
|
+
left: 0,
|
|
198
|
+
right: 0
|
|
199
|
+
},
|
|
166
200
|
onLayout: event => {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
height
|
|
170
|
-
} = event.nativeEvent.layout;
|
|
171
|
-
const previous = contentSizeRef.current;
|
|
172
|
-
if (previous.width === width && previous.height === height) return;
|
|
173
|
-
const next = {
|
|
174
|
-
width,
|
|
175
|
-
height
|
|
176
|
-
};
|
|
177
|
-
contentSizeRef.current = next;
|
|
178
|
-
setContentSize(next);
|
|
179
|
-
onContentSizeChange?.(width, height);
|
|
201
|
+
const height = Math.ceil(event.nativeEvent.layout.height);
|
|
202
|
+
setTopOverlayHeight(previous => previous === height ? previous : height);
|
|
180
203
|
},
|
|
181
|
-
children:
|
|
182
|
-
})
|
|
204
|
+
children: topOverlay
|
|
205
|
+
}) : null]
|
|
183
206
|
});
|
|
184
207
|
});
|
|
185
208
|
NativeScroller.displayName = "NativeScroller";
|