@onekeyfe/react-native-pager-view 6.7.0-15 → 6.7.0-17

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.
Files changed (24) hide show
  1. package/README.md +5 -0
  2. package/android/src/androidTest/AndroidManifest.xml +7 -0
  3. package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +247 -0
  4. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +76 -21
  5. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +72 -7
  6. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +27 -0
  7. package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
  8. package/docs/SPEC.md +146 -1
  9. package/ios/RNCCollapsiblePagerViewComponentView.mm +355 -40
  10. package/ios/RNCNativeScrollerComponentView.h +2 -0
  11. package/ios/RNCNativeScrollerComponentView.mm +18 -0
  12. package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +186 -0
  13. package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +178 -0
  14. package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +74 -9
  15. package/lib/module/CollapsiblePagerView.js +20 -4
  16. package/lib/module/CollapsiblePagerView.web.js +11 -2
  17. package/lib/module/CollapsiblePagerViewNativeComponent.ts +13 -0
  18. package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -1
  19. package/lib/typescript/src/CollapsiblePagerView.web.d.ts +2 -0
  20. package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +5 -0
  21. package/package.json +2 -2
  22. package/src/CollapsiblePagerView.tsx +68 -4
  23. package/src/CollapsiblePagerView.web.tsx +19 -2
  24. package/src/CollapsiblePagerViewNativeComponent.ts +13 -0
@@ -18,6 +18,7 @@
18
18
 
19
19
  // OneKey patch: deduplicate structural mutation parents before scanning the page.
20
20
  #include <unordered_set>
21
+ #include <cmath>
21
22
 
22
23
  using namespace facebook::react;
23
24
 
@@ -157,6 +158,20 @@ static NSString *const RNCCollapsiblePagerNativeScrollerIDPrefix =
157
158
 
158
159
  static const CFTimeInterval RNCCollapsiblePagerFallbackDebounceInterval = 0.5;
159
160
 
161
+ typedef NS_ENUM(NSInteger, RNCScrollCandidateTier) {
162
+ RNCScrollCandidateTierNone = 0,
163
+ RNCScrollCandidateTierGenericFallback = 1,
164
+ RNCScrollCandidateTierVerifiedNativeList = 2,
165
+ RNCScrollCandidateTierFabricScrollView = 3,
166
+ RNCScrollCandidateTierExplicitScroller = 4,
167
+ };
168
+
169
+ struct RNCScrollCandidateResolution {
170
+ UIScrollView *scrollView = nil;
171
+ RNCScrollCandidateTier tier = RNCScrollCandidateTierNone;
172
+ BOOL hasUnreadyScrollHost = NO;
173
+ };
174
+
160
175
  // OneKey patch: native scroll views can survive a move or be reused for a new
161
176
  // Fabric component. Keep weak identity until their page is attached again.
162
177
  @interface RNCCollapsiblePagerReleasedScrollState : NSObject
@@ -368,7 +383,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
368
383
  @property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
369
384
  - (void)updateItemsJSON:(NSString *)itemsJSON;
370
385
  - (void)updateStyleWithHeight:(CGFloat)height
386
+ variant:(NSString *)variant
371
387
  contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
388
+ contentPaddingVertical:(CGFloat)contentPaddingVertical
372
389
  itemSpacing:(CGFloat)itemSpacing
373
390
  fontSize:(CGFloat)fontSize
374
391
  fontFamily:(NSString *)fontFamily
@@ -385,12 +402,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
385
402
 
386
403
  @implementation RNCCollapsiblePagerNativeTabBarView {
387
404
  RNCCollapsiblePagerHorizontalScrollView *_scrollView;
405
+ UIView *_trackView;
388
406
  UIView *_indicatorView;
389
407
  NSArray<NSDictionary *> *_items;
390
408
  NSMutableArray<UIButton *> *_buttons;
391
409
  NSMutableArray<NSValue *> *_indicatorFrames;
392
410
  CGFloat _barHeight;
393
411
  CGFloat _contentPaddingHorizontal;
412
+ CGFloat _contentPaddingVertical;
413
+ BOOL _segmented;
394
414
  CGFloat _itemSpacing;
395
415
  CGFloat _fontSize;
396
416
  NSString *_fontFamily;
@@ -413,6 +433,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
413
433
  _indicatorFrames = [NSMutableArray new];
414
434
  _barHeight = 44;
415
435
  _contentPaddingHorizontal = 20;
436
+ _contentPaddingVertical = 0;
437
+ _segmented = NO;
416
438
  _itemSpacing = 8;
417
439
  _fontSize = 16;
418
440
  _activeTextColor = UIColor.labelColor;
@@ -434,6 +456,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
434
456
  _scrollView.delegate = self;
435
457
  [self addSubview:_scrollView];
436
458
 
459
+ _trackView = [UIView new];
460
+ _trackView.userInteractionEnabled = NO;
461
+ [_scrollView addSubview:_trackView];
462
+
437
463
  _indicatorView = [UIView new];
438
464
  _indicatorView.userInteractionEnabled = NO;
439
465
  [_scrollView addSubview:_indicatorView];
@@ -488,7 +514,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
488
514
  }
489
515
 
490
516
  - (void)updateStyleWithHeight:(CGFloat)height
517
+ variant:(NSString *)variant
491
518
  contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
519
+ contentPaddingVertical:(CGFloat)contentPaddingVertical
492
520
  itemSpacing:(CGFloat)itemSpacing
493
521
  fontSize:(CGFloat)fontSize
494
522
  fontFamily:(NSString *)fontFamily
@@ -500,19 +528,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
500
528
  indicatorBottom:(CGFloat)indicatorBottom
501
529
  {
502
530
  _barHeight = MAX(1, height);
531
+ BOOL wasSegmented = _segmented;
532
+ _segmented = [variant isEqualToString:@"segmented"];
503
533
  _contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
534
+ _contentPaddingVertical = MAX(0, contentPaddingVertical);
504
535
  _itemSpacing = MAX(0, itemSpacing);
505
536
  _fontSize = MAX(1, fontSize);
506
537
  _fontFamily = [fontFamily copy];
507
- self.backgroundColor = backgroundColor ?: UIColor.clearColor;
538
+ self.backgroundColor = _segmented ? UIColor.clearColor : (backgroundColor ?: UIColor.clearColor);
508
539
  _scrollView.backgroundColor = self.backgroundColor;
540
+ _scrollView.scrollEnabled = !_segmented;
541
+ if (_segmented && !wasSegmented) [_scrollView setContentOffset:CGPointZero animated:NO];
542
+ _trackView.hidden = !_segmented;
543
+ _trackView.backgroundColor = backgroundColor ?: UIColor.clearColor;
509
544
  _activeTextColor = activeTextColor ?: UIColor.labelColor;
510
545
  _inactiveTextColor = inactiveTextColor ?: UIColor.secondaryLabelColor;
511
546
  _indicatorColor = indicatorColor ?: _activeTextColor;
512
547
  _indicatorHeight = MAX(0, indicatorHeight);
513
548
  _indicatorBottom = MAX(0, indicatorBottom);
514
549
  _indicatorView.backgroundColor = _indicatorColor;
515
- _indicatorView.layer.cornerRadius = _indicatorHeight / 2;
550
+ _indicatorView.layer.cornerRadius = _segmented ? 0 : _indicatorHeight / 2;
516
551
  for (UIButton *button in _buttons) button.titleLabel.font = self.tabFont;
517
552
  [self setNeedsLayout];
518
553
  }
@@ -571,6 +606,33 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
571
606
  {
572
607
  [super layoutSubviews];
573
608
  _scrollView.frame = self.bounds;
609
+ if (_segmented) {
610
+ CGFloat width = CGRectGetWidth(self.bounds);
611
+ CGFloat height = CGRectGetHeight(self.bounds);
612
+ CGFloat insetX = MIN(_contentPaddingHorizontal, width / 2);
613
+ CGFloat insetY = MIN(_contentPaddingVertical, height / 2);
614
+ CGRect trackFrame = CGRectMake(insetX, insetY,
615
+ MAX(0, width - 2 * insetX), MAX(0, height - 2 * insetY));
616
+ _trackView.frame = trackFrame;
617
+ _trackView.layer.cornerRadius = CGRectGetHeight(trackFrame) / 2;
618
+ _scrollView.contentSize = self.bounds.size;
619
+ [_indicatorFrames removeAllObjects];
620
+ NSUInteger count = _buttons.count;
621
+ [_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
622
+ CGFloat first = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * index / count;
623
+ CGFloat last = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * (index + 1) / count;
624
+ CGFloat x = self->_leftToRight ? first : CGRectGetMaxX(trackFrame) - (last - CGRectGetMinX(trackFrame));
625
+ CGFloat end = self->_leftToRight ? last : CGRectGetMaxX(trackFrame) - (first - CGRectGetMinX(trackFrame));
626
+ button.frame = CGRectMake(x, CGRectGetMinY(trackFrame), MAX(0, end - x), CGRectGetHeight(trackFrame));
627
+ CGRect thumbFrame = CGRectMake(x + 2, CGRectGetMinY(trackFrame) + 2,
628
+ MAX(0, CGRectGetWidth(button.frame) - 4), MAX(0, CGRectGetHeight(trackFrame) - 4));
629
+ [self->_indicatorFrames addObject:[NSValue valueWithCGRect:thumbFrame]];
630
+ }];
631
+ _indicatorView.layer.cornerRadius = MAX(0, CGRectGetHeight(trackFrame) - 4) / 2;
632
+ [self updatePresentation];
633
+ return;
634
+ }
635
+ _trackView.frame = CGRectZero;
574
636
  UIFont *font = self.tabFont;
575
637
  NSMutableArray<NSNumber *> *widths = [NSMutableArray arrayWithCapacity:_buttons.count];
576
638
  CGFloat itemsWidth = 0;
@@ -634,10 +696,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
634
696
  CGRectGetMinX(fromFrame) + (CGRectGetMinX(toFrame) - CGRectGetMinX(fromFrame)) * fraction,
635
697
  CGRectGetMinY(fromFrame),
636
698
  CGRectGetWidth(fromFrame) + (CGRectGetWidth(toFrame) - CGRectGetWidth(fromFrame)) * fraction,
637
- _indicatorHeight
699
+ _segmented ? CGRectGetHeight(fromFrame) : _indicatorHeight
638
700
  );
639
701
  _indicatorView.frame = indicatorFrame;
640
- _indicatorView.hidden = _indicatorHeight <= 0;
702
+ _indicatorView.hidden = CGRectGetHeight(indicatorFrame) <= 0;
641
703
 
642
704
  NSInteger selectedIndex = (NSInteger)round(progress);
643
705
  [_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
@@ -654,7 +716,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
654
716
  : UIAccessibilityTraitButton;
655
717
  }];
656
718
 
657
- if (_shouldCenterSelectedItem && !_scrollView.tracking && !_scrollView.dragging &&
719
+ if (_shouldCenterSelectedItem && !_segmented && !_scrollView.tracking && !_scrollView.dragging &&
658
720
  !_scrollView.decelerating && CGRectGetWidth(_scrollView.bounds) > 0) {
659
721
  CGFloat maximumOffset = MAX(0, _scrollView.contentSize.width - CGRectGetWidth(_scrollView.bounds));
660
722
  CGFloat desiredOffset = RNCClamp(
@@ -1471,13 +1533,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1471
1533
  - (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
1472
1534
  - (void)restoreSharedHeadersToContainer;
1473
1535
  - (void)layoutSharedHeaders;
1536
+ - (void)bringReservedStickyControlToFront;
1474
1537
  - (void)restorePinnedStickyHeader;
1475
1538
  - (void)updatePinnedStickyHeader;
1476
1539
  - (UIScrollView *)currentVerticalScrollViewForParentPager;
1477
1540
  - (UIScrollView *)verticalScrollViewForPageAtIndex:(NSInteger)pageIndex;
1541
+ - (RNCScrollCandidateResolution)resolveScrollCandidateForPageAtIndex:(NSInteger)pageIndex;
1542
+ - (RNCScrollCandidateResolution)resolveScrollCandidateInView:(UIView *)view;
1478
1543
  - (UIScrollView *)findExplicitNativeScrollerInView:(UIView *)view;
1479
1544
  - (UIScrollView *)findFabricScrollViewInView:(UIView *)view;
1480
1545
  - (UIScrollView *)findVerticalScrollViewInView:(UIView *)view;
1546
+ - (void)scheduleCoalescedScrollReconcile;
1481
1547
  - (void)scheduleScrollObserverRetryForCurrentPage;
1482
1548
  - (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex;
1483
1549
  - (void)updateSharedHeaderPressCancellationGesture;
@@ -1558,6 +1624,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1558
1624
  NSUInteger _pendingTopInsetReboundGeneration;
1559
1625
  NSTimer *_topInsetReboundTimer;
1560
1626
  CGFloat _nativeTabBarHeight;
1627
+ CGFloat _nativeTabBarTrailingReservedWidth;
1561
1628
  CGFloat _nativeSubHeaderHeight;
1562
1629
  CGFloat _headerOffset;
1563
1630
  BOOL _scrollEnabled;
@@ -1644,6 +1711,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1644
1711
  RNCCollapsiblePagerOuterPagerPanGestureRecognizer *_sharedHeaderOuterPagerGesture;
1645
1712
  RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer *_verticalPagerGesture;
1646
1713
  BOOL _observingContentOffset;
1714
+ BOOL _awaitingScrollOwnerForCurrentPage;
1715
+ BOOL _scrollReconcileScheduled;
1647
1716
  BOOL _scrollResolveRetryScheduled;
1648
1717
  CFTimeInterval _pendingFallbackScrollViewDetectedAt;
1649
1718
  NSInteger _scrollResolveRetryPageIndex;
@@ -1675,6 +1744,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1675
1744
  _displayBodyOverrides = [NSMutableDictionary new];
1676
1745
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nativeListScrollCommandAccepted:)
1677
1746
  name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
1747
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nativeScrollViewDidBecomeReadyNotification:)
1748
+ name:@"OneKeyNativeScrollViewDidBecomeReady" object:nil];
1678
1749
  _pendingPageRestoreKeys = [NSMutableSet new];
1679
1750
  _releasedPageScrollStates = [NSMutableDictionary new];
1680
1751
  _mountingRemovedScrollViews = [NSHashTable weakObjectsHashTable];
@@ -2332,6 +2403,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2332
2403
  [self updateTopScrollEdgeForScrollView:nil];
2333
2404
  [self cancelHeaderSettledCheck];
2334
2405
  [[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
2406
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeScrollViewDidBecomeReady" object:nil];
2335
2407
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
2336
2408
  [self updateNativeScrollerViewport:scrollView stickyHeight:0];
2337
2409
  }
@@ -2479,6 +2551,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2479
2551
  }
2480
2552
  [_nativeTabBarView updateAncestorGesturePrecedence];
2481
2553
  [_nativeSubHeaderView updateAncestorGesturePrecedence];
2554
+ [self bringReservedStickyControlToFront];
2482
2555
  }
2483
2556
 
2484
2557
  - (void)restoreSharedHeadersToContainer
@@ -2584,10 +2657,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2584
2657
  CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
2585
2658
  ? 0
2586
2659
  : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2660
+ CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
2587
2661
  _nativeTabBarView.frame = CGRectMake(
2588
- 0,
2662
+ self.isLtrLayout ? 0 : tabReservedWidth,
2589
2663
  stickyChildY,
2590
- width,
2664
+ width - tabReservedWidth,
2591
2665
  nativeTabBarVisibleHeight
2592
2666
  );
2593
2667
  _nativeSubHeaderView.frame = CGRectMake(
@@ -2601,9 +2675,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2601
2675
  );
2602
2676
  _topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
2603
2677
  _topOverlayView.frame = _topOverlayHostView.bounds;
2678
+ [self bringReservedStickyControlToFront];
2604
2679
  [self applyHeaderOffset];
2605
2680
  }
2606
2681
 
2682
+ - (void)bringReservedStickyControlToFront
2683
+ {
2684
+ if (_stickyHeaderView != nil &&
2685
+ _stickyHeaderView.superview == _nativeTabBarView.superview) {
2686
+ if (_nativeTabBarTrailingReservedWidth > 0) {
2687
+ [_stickyHeaderView.superview bringSubviewToFront:_stickyHeaderView];
2688
+ } else {
2689
+ [_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
2690
+ if (!_nativeSubHeaderView.hidden &&
2691
+ _nativeSubHeaderView.superview == _nativeTabBarView.superview) {
2692
+ [_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+
2607
2698
  - (void)updatePinnedStickyHeader
2608
2699
  {
2609
2700
  BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
@@ -2650,8 +2741,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2650
2741
  CGFloat childY = shouldPin ? 0 : _headerHeight;
2651
2742
  CGFloat width = CGRectGetWidth(self.bounds);
2652
2743
  CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2744
+ CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
2653
2745
  _stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
2654
- _nativeTabBarView.frame = CGRectMake(0, childY, width, tabHeight);
2746
+ _nativeTabBarView.frame = CGRectMake(self.isLtrLayout ? 0 : tabReservedWidth,
2747
+ childY, width - tabReservedWidth, tabHeight);
2655
2748
  _nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
2656
2749
  MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
2657
2750
  _pinnedStickyHostView.hidden = !shouldPin;
@@ -2671,6 +2764,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2671
2764
  }
2672
2765
  [_nativeTabBarView updateAncestorGesturePrecedence];
2673
2766
  [_nativeSubHeaderView updateAncestorGesturePrecedence];
2767
+ [self bringReservedStickyControlToFront];
2674
2768
  [self updateTopScrollEdgeForScrollView:_observedScrollView];
2675
2769
  [self logHomeVisualGeometry:shouldPin ? @"pin" : @"unpin"
2676
2770
  scrollView:_observedScrollView];
@@ -2874,6 +2968,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2874
2968
  [self applyInsetsToScrollView:scrollView pageIndex:index restore:YES];
2875
2969
  }
2876
2970
  }
2971
+ UIView *page = _pageControllers[_currentIndex].view;
2972
+ if (_scrollResolveRetryAttempt >= 12 &&
2973
+ _releasedPageScrollStates[self.currentPageKey] != nil) {
2974
+ for (const auto &mutation : transaction.getMutations()) {
2975
+ if (mutation.type == ShadowViewMutation::Insert && mutation.parentTag > 0 &&
2976
+ [page viewWithTag:mutation.parentTag] != nil) {
2977
+ // A new current-page insertion starts a bounded recovery round after
2978
+ // the previous replacement search has scheduled its final retry.
2979
+ _scrollResolveRetryToken += 1;
2980
+ _scrollResolveRetryScheduled = NO;
2981
+ _scrollResolveRetryAttempt = 0;
2982
+ break;
2983
+ }
2984
+ }
2985
+ }
2986
+
2987
+ // Record recovery during transitions; ownership still waits for settlement.
2877
2988
  if (_transitioning || _isPagerDragging) return;
2878
2989
 
2879
2990
  // OneKey patch: a release must recover even when the changed parent is above
@@ -2882,7 +2993,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2882
2993
  [self attachScrollObserverForCurrentPage];
2883
2994
  return;
2884
2995
  }
2885
- UIView *page = _pageControllers[_currentIndex].view;
2886
2996
  BOOL headerIsAttached = [_sharedHeaderHostView isDescendantOfView:self];
2887
2997
  BOOL currentListIsAttached = _observedScrollView != nil &&
2888
2998
  [_observedScrollView isDescendantOfView:page] && headerIsAttached;
@@ -3050,18 +3160,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3050
3160
  if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
3051
3161
  if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
3052
3162
  if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
3163
+ [self bringReservedStickyControlToFront];
3053
3164
  [_containerView bringSubviewToFront:_sharedHeaderHostView];
3054
3165
  if (_stickyHeaderIsPinned) {
3055
3166
  [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
3056
3167
  }
3057
3168
  if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
3058
3169
  [self setNeedsLayout];
3059
- dispatch_async(dispatch_get_main_queue(), ^{
3060
- if (!self->_isBeingRecycled) {
3061
- [self prepareAdjacentPageInsets];
3062
- [self attachScrollObserverForCurrentPage];
3063
- }
3064
- });
3170
+ [self scheduleCoalescedScrollReconcile];
3065
3171
  }
3066
3172
 
3067
3173
  - (void)layoutSubviews
@@ -3138,12 +3244,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3138
3244
  [self updateScrollEdgeContentMasks];
3139
3245
  [self updateHeaderSettledStateForceBusy:NO];
3140
3246
  [self logHomeVisualGeometry:@"layout" scrollView:_observedScrollView];
3141
- dispatch_async(dispatch_get_main_queue(), ^{
3142
- if (!self->_isBeingRecycled) {
3143
- [self prepareAdjacentPageInsets];
3144
- [self attachScrollObserverForCurrentPage];
3145
- }
3146
- });
3247
+ [self scheduleCoalescedScrollReconcile];
3147
3248
  }
3148
3249
 
3149
3250
  - (void)prepareForRecycle
@@ -3161,6 +3262,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3161
3262
  _generation++;
3162
3263
  [_mountingRemovedScrollViews removeAllObjects];
3163
3264
  _needsScrollObserverReattachAfterMount = NO;
3265
+ _awaitingScrollOwnerForCurrentPage = NO;
3266
+ _scrollReconcileScheduled = NO;
3164
3267
  [_releasedPageScrollStates removeAllObjects];
3165
3268
  [self detachSharedHeaderPressCancellationGesture];
3166
3269
  [self detachScrollObserver];
@@ -3262,6 +3365,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3262
3365
  _homeVisualLastLayoutLog = 0;
3263
3366
  _homeVisualLastGeometrySignature = nil;
3264
3367
  _nativeTabBarHeight = 44;
3368
+ _nativeTabBarTrailingReservedWidth = 0;
3265
3369
  _nativeSubHeaderHeight = 74;
3266
3370
  _headerOffset = 0;
3267
3371
  _currentLogicalOffset = 0;
@@ -3343,6 +3447,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3343
3447
  _layoutDirection = RCTNSStringFromString(toString(newViewProps.layoutDirection));
3344
3448
  [_nativeTabBarView setLeftToRight:self.isLtrLayout];
3345
3449
  [_nativeSubHeaderView setLeftToRight:self.isLtrLayout];
3450
+ [self setNeedsLayout];
3451
+ }
3452
+ if (_needsPropsReapply ||
3453
+ oldViewProps.nativeTabBarTrailingReservedWidth != newViewProps.nativeTabBarTrailingReservedWidth) {
3454
+ double reserved = newViewProps.nativeTabBarTrailingReservedWidth;
3455
+ _nativeTabBarTrailingReservedWidth = std::isfinite(reserved) && reserved >= 0 ? reserved : 0;
3456
+ [self setNeedsLayout];
3346
3457
  }
3347
3458
  BOOL insetsChanged = NO;
3348
3459
  if (_needsPropsReapply || oldViewProps.headerHeight != newViewProps.headerHeight) {
@@ -3423,7 +3534,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3423
3534
  }
3424
3535
  if (_needsPropsReapply ||
3425
3536
  oldViewProps.nativeTabBarHeight != newViewProps.nativeTabBarHeight ||
3537
+ oldViewProps.nativeTabBarVariant != newViewProps.nativeTabBarVariant ||
3426
3538
  oldViewProps.nativeTabBarContentPaddingHorizontal != newViewProps.nativeTabBarContentPaddingHorizontal ||
3539
+ oldViewProps.nativeTabBarContentPaddingVertical != newViewProps.nativeTabBarContentPaddingVertical ||
3427
3540
  oldViewProps.nativeTabBarItemSpacing != newViewProps.nativeTabBarItemSpacing ||
3428
3541
  oldViewProps.nativeTabBarFontSize != newViewProps.nativeTabBarFontSize ||
3429
3542
  oldViewProps.nativeTabBarFontFamily != newViewProps.nativeTabBarFontFamily ||
@@ -3440,7 +3553,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3440
3553
  NSString *fontFamily = RCTNSStringFromString(newViewProps.nativeTabBarFontFamily);
3441
3554
  [_nativeTabBarView
3442
3555
  updateStyleWithHeight:_nativeTabBarHeight
3556
+ variant:RCTNSStringFromString(newViewProps.nativeTabBarVariant)
3443
3557
  contentPaddingHorizontal:MAX(0, newViewProps.nativeTabBarContentPaddingHorizontal)
3558
+ contentPaddingVertical:MAX(0, newViewProps.nativeTabBarContentPaddingVertical)
3444
3559
  itemSpacing:MAX(0, newViewProps.nativeTabBarItemSpacing)
3445
3560
  fontSize:newViewProps.nativeTabBarFontSize > 0 ? newViewProps.nativeTabBarFontSize : 16
3446
3561
  fontFamily:fontFamily
@@ -3474,6 +3589,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3474
3589
  if (!_nativeSubHeaderView.hidden) {
3475
3590
  [_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
3476
3591
  }
3592
+ [self bringReservedStickyControlToFront];
3477
3593
  [_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
3478
3594
  if (_stickyHeaderIsPinned) {
3479
3595
  [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
@@ -3501,12 +3617,105 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3501
3617
 
3502
3618
  - (UIScrollView *)verticalScrollViewForPageAtIndex:(NSInteger)pageIndex
3503
3619
  {
3504
- if (pageIndex < 0 || pageIndex >= _pageControllers.count) return nil;
3505
- UIView *pageView = _pageControllers[pageIndex].view;
3506
- UIScrollView *explicitScroller = [self findExplicitNativeScrollerInView:pageView];
3507
- if (explicitScroller != nil) return explicitScroller;
3508
- UIScrollView *fabricScrollView = [self findFabricScrollViewInView:pageView];
3509
- return fabricScrollView ?: [self findVerticalScrollViewInView:pageView];
3620
+ return [self resolveScrollCandidateForPageAtIndex:pageIndex].scrollView;
3621
+ }
3622
+
3623
+ - (RNCScrollCandidateResolution)resolveScrollCandidateForPageAtIndex:(NSInteger)pageIndex
3624
+ {
3625
+ if (pageIndex < 0 || pageIndex >= _pageControllers.count) return {};
3626
+ return [self resolveScrollCandidateInView:_pageControllers[pageIndex].view];
3627
+ }
3628
+
3629
+ // Single-pass tiered resolution replaces three separate subtree walks while
3630
+ // distinguishing genuinely static pages from pages with unready scroll hosts.
3631
+ - (RNCScrollCandidateResolution)resolveScrollCandidateInView:(UIView *)view
3632
+ {
3633
+ RNCScrollCandidateResolution resolution;
3634
+ if (view == nil || view == _sharedHeaderHostView) return resolution;
3635
+ if (view != self &&
3636
+ [view isKindOfClass:RNCCollapsiblePagerViewComponentView.class]) {
3637
+ UIScrollView *nested = [(RNCCollapsiblePagerViewComponentView *)view
3638
+ currentVerticalScrollViewForParentPager];
3639
+ if (nested != nil) {
3640
+ resolution.scrollView = nested;
3641
+ resolution.tier = RNCScrollCandidateTierExplicitScroller;
3642
+ } else {
3643
+ resolution.hasUnreadyScrollHost = YES;
3644
+ }
3645
+ return resolution;
3646
+ }
3647
+
3648
+ Class nativeListClass = NSClassFromString(@"HybridNativeListComponent");
3649
+ if (nativeListClass != Nil && [view isKindOfClass:nativeListClass]) {
3650
+ resolution.hasUnreadyScrollHost = YES;
3651
+ }
3652
+
3653
+ if ([view isKindOfClass:RNCNativeScrollerComponentView.class]) {
3654
+ UIScrollView *scrollView = ((RNCNativeScrollerComponentView *)view).scrollView;
3655
+ if (![_mountingRemovedScrollViews containsObject:scrollView]) {
3656
+ resolution.scrollView = scrollView;
3657
+ resolution.tier = RNCScrollCandidateTierExplicitScroller;
3658
+ return resolution;
3659
+ }
3660
+ } else if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
3661
+ RCTScrollViewComponentView *componentView = (RCTScrollViewComponentView *)view;
3662
+ UIScrollView *scrollView = componentView.scrollView;
3663
+ if (![_mountingRemovedScrollViews containsObject:scrollView]) {
3664
+ if ([componentView.nativeId hasPrefix:RNCCollapsiblePagerNativeScrollerIDPrefix]) {
3665
+ resolution.scrollView = scrollView;
3666
+ resolution.tier = RNCScrollCandidateTierExplicitScroller;
3667
+ return resolution;
3668
+ }
3669
+ if (!scrollView.alwaysBounceHorizontal) {
3670
+ resolution.scrollView = scrollView;
3671
+ resolution.tier = RNCScrollCandidateTierFabricScrollView;
3672
+ }
3673
+ }
3674
+ } else if ([view isKindOfClass:UICollectionView.class]) {
3675
+ UICollectionView *collectionView = (UICollectionView *)view;
3676
+ if (![_mountingRemovedScrollViews containsObject:collectionView]) {
3677
+ UICollectionViewLayout *layout = collectionView.collectionViewLayout;
3678
+ BOOL isVerticalLayout = ![layout isKindOfClass:UICollectionViewFlowLayout.class] ||
3679
+ ((UICollectionViewFlowLayout *)layout).scrollDirection == UICollectionViewScrollDirectionVertical;
3680
+ if (isVerticalLayout) {
3681
+ BOOL hasNativeListHost = RNCNativeListComponentForScrollView(collectionView) != nil;
3682
+ BOOL isMountedNativeList = hasNativeListHost && RNCIsMountedNativeListScrollView(collectionView);
3683
+ BOOL hasUsableBounds = CGRectGetWidth(collectionView.bounds) > 0 &&
3684
+ CGRectGetHeight(collectionView.bounds) > 0;
3685
+ if (hasNativeListHost && !isMountedNativeList) {
3686
+ resolution.hasUnreadyScrollHost = YES;
3687
+ } else if (!hasUsableBounds) {
3688
+ resolution.hasUnreadyScrollHost = YES;
3689
+ } else {
3690
+ resolution.scrollView = collectionView;
3691
+ resolution.tier = isMountedNativeList
3692
+ ? RNCScrollCandidateTierVerifiedNativeList
3693
+ : RNCScrollCandidateTierGenericFallback;
3694
+ }
3695
+ }
3696
+ }
3697
+ } else if ([view isKindOfClass:UIScrollView.class]) {
3698
+ UIScrollView *scrollView = (UIScrollView *)view;
3699
+ if (![_mountingRemovedScrollViews containsObject:scrollView] &&
3700
+ !scrollView.alwaysBounceHorizontal &&
3701
+ scrollView.contentSize.width <= scrollView.bounds.size.width + 1) {
3702
+ resolution.scrollView = scrollView;
3703
+ resolution.tier = RNCScrollCandidateTierGenericFallback;
3704
+ }
3705
+ }
3706
+
3707
+ for (UIView *subview in view.subviews) {
3708
+ RNCScrollCandidateResolution child = [self resolveScrollCandidateInView:subview];
3709
+ resolution.hasUnreadyScrollHost |= child.hasUnreadyScrollHost;
3710
+ if (child.tier > resolution.tier) {
3711
+ resolution.scrollView = child.scrollView;
3712
+ resolution.tier = child.tier;
3713
+ if (resolution.tier == RNCScrollCandidateTierExplicitScroller) {
3714
+ return resolution;
3715
+ }
3716
+ }
3717
+ }
3718
+ return resolution;
3510
3719
  }
3511
3720
 
3512
3721
  - (UIScrollView *)findExplicitNativeScrollerInView:(UIView *)view
@@ -3602,6 +3811,19 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3602
3811
  return nil;
3603
3812
  }
3604
3813
 
3814
+ - (void)scheduleCoalescedScrollReconcile
3815
+ {
3816
+ if (_isBeingRecycled || _scrollReconcileScheduled) return;
3817
+ _scrollReconcileScheduled = YES;
3818
+ NSUInteger generation = _generation;
3819
+ dispatch_async(dispatch_get_main_queue(), ^{
3820
+ self->_scrollReconcileScheduled = NO;
3821
+ if (self->_isBeingRecycled || self->_generation != generation) return;
3822
+ [self prepareAdjacentPageInsets];
3823
+ [self attachScrollObserverForCurrentPage];
3824
+ });
3825
+ }
3826
+
3605
3827
  - (void)scheduleScrollObserverRetryForCurrentPage
3606
3828
  {
3607
3829
  if (_scrollResolveRetryScheduled || _scrollResolveRetryAttempt >= 12 ||
@@ -3654,29 +3876,35 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3654
3876
  _pendingFallbackScrollView = nil;
3655
3877
  _pendingFallbackScrollViewDetectedAt = 0;
3656
3878
  }
3657
- UIScrollView *candidate = [self verticalScrollViewForPageAtIndex:_currentIndex];
3879
+ RNCScrollCandidateResolution resolution = [self resolveScrollCandidateForPageAtIndex:_currentIndex];
3880
+ UIScrollView *candidate = resolution.scrollView;
3658
3881
  if (_nativeSmoothHeaderScrollEnabled && _currentIndex >= 0 && _currentIndex < _pageControllers.count) {
3659
3882
  RNCGuardNestedHorizontalPans(_pageControllers[_currentIndex].view, _verticalPagerGesture);
3660
3883
  }
3661
3884
  if (candidate == nil) {
3662
3885
  [self detachScrollObserver];
3663
3886
  [self restoreSharedHeadersToContainer];
3664
- // Stop retrying released lists on unrelated commits without discarding a
3665
- // surviving identity or offset. A relevant insertion resumes recovery.
3666
- if (_needsScrollObserverReattachAfterMount) {
3667
- _needsScrollObserverReattachAfterMount = NO;
3887
+ _needsScrollObserverReattachAfterMount = NO;
3888
+ _pendingFallbackScrollView = nil;
3889
+ _pendingFallbackScrollViewDetectedAt = 0;
3890
+ _awaitingScrollOwnerForCurrentPage = YES;
3891
+ // Only schedule bounded retry when the page has an unready scroll host in
3892
+ // its subtree or a released scroll owner awaiting replacement. Static pages
3893
+ // without a scroll host do not spin retry timers, and late replacements on
3894
+ // any page also attach deterministically via readiness notifications.
3895
+ BOOL hasReleasedOwnerAwaitingReplacement = _releasedPageScrollStates[self.currentPageKey] != nil;
3896
+ if (resolution.hasUnreadyScrollHost || hasReleasedOwnerAwaitingReplacement) {
3897
+ [self scheduleScrollObserverRetryForCurrentPage];
3898
+ } else {
3668
3899
  _scrollResolveRetryToken += 1;
3669
3900
  _scrollResolveRetryScheduled = NO;
3670
3901
  _scrollResolveRetryAttempt = 0;
3671
- _pendingFallbackScrollView = nil;
3672
- _pendingFallbackScrollViewDetectedAt = 0;
3673
- } else {
3674
- [self scheduleScrollObserverRetryForCurrentPage];
3675
3902
  }
3676
3903
  return;
3677
3904
  }
3678
3905
  if ([candidate isKindOfClass:UICollectionView.class] &&
3679
3906
  candidate != _observedScrollView && !RNCIsMountedNativeListScrollView(candidate)) {
3907
+ _awaitingScrollOwnerForCurrentPage = YES;
3680
3908
  if (_pendingFallbackScrollView != candidate) {
3681
3909
  _pendingFallbackScrollView = candidate;
3682
3910
  _pendingFallbackScrollViewDetectedAt = CACurrentMediaTime();
@@ -3688,6 +3916,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3688
3916
  return;
3689
3917
  }
3690
3918
  }
3919
+ _awaitingScrollOwnerForCurrentPage = NO;
3691
3920
  _scrollResolveRetryAttempt = 0;
3692
3921
  _scrollResolveRetryPageIndex = _currentIndex;
3693
3922
  _scrollResolveRetryToken += 1;
@@ -4438,8 +4667,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4438
4667
 
4439
4668
  - (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
4440
4669
  {
4441
- if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
4442
- [(RNCNativeScrollerComponentView *)scrollView.superview setPagerStickyHeight:height forOwner:self];
4670
+ id host = scrollView.superview;
4671
+ if ([host respondsToSelector:@selector(setPagerStickyHeight:forOwner:)]) {
4672
+ [host setPagerStickyHeight:height forOwner:self];
4443
4673
  }
4444
4674
  }
4445
4675
 
@@ -4511,6 +4741,66 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4511
4741
  }
4512
4742
 
4513
4743
 
4744
+ - (void)nativeScrollViewDidBecomeReadyNotification:(NSNotification *)notification
4745
+ {
4746
+ if ([notification.object isKindOfClass:UIScrollView.class]) {
4747
+ [self nativeScrollViewDidBecomeReady:(UIScrollView *)notification.object];
4748
+ }
4749
+ }
4750
+
4751
+ - (void)nativeScrollViewDidBecomeReady:(UIScrollView *)scrollView
4752
+ {
4753
+ if (_isBeingRecycled || self.window == nil || scrollView == nil ||
4754
+ [_mountingRemovedScrollViews containsObject:scrollView]) return;
4755
+ for (UIView *ancestor = scrollView.superview;
4756
+ ancestor != nil && ancestor != self;
4757
+ ancestor = ancestor.superview) {
4758
+ if ([ancestor isKindOfClass:RNCCollapsiblePagerViewComponentView.class]) {
4759
+ RNCCollapsiblePagerViewComponentView *nestedPager =
4760
+ (RNCCollapsiblePagerViewComponentView *)ancestor;
4761
+ if ([nestedPager currentVerticalScrollViewForParentPager] != scrollView) return;
4762
+ break;
4763
+ }
4764
+ }
4765
+ NSInteger pageIndex = NSNotFound;
4766
+ for (NSInteger index = 0; index < _pageControllers.count; index++) {
4767
+ if ([scrollView isDescendantOfView:_pageControllers[index].view]) {
4768
+ pageIndex = index;
4769
+ break;
4770
+ }
4771
+ }
4772
+ if (pageIndex == NSNotFound) return;
4773
+
4774
+ NSString *pageKey = [self pageKeyForIndex:pageIndex];
4775
+ BOOL hasReleasedStateForPage = _releasedPageScrollStates[pageKey] != nil;
4776
+ BOOL currentPageNeedsOwner = pageIndex == _currentIndex &&
4777
+ (_awaitingScrollOwnerForCurrentPage || _needsScrollObserverReattachAfterMount ||
4778
+ hasReleasedStateForPage ||
4779
+ (_observedScrollView != nil &&
4780
+ [_observedScrollView isKindOfClass:UICollectionView.class] &&
4781
+ !RNCIsMountedNativeListScrollView(_observedScrollView) &&
4782
+ _observedScrollView != scrollView));
4783
+
4784
+ if (!hasReleasedStateForPage && !currentPageNeedsOwner &&
4785
+ !_needsScrollObserverReattachAfterMount) return;
4786
+
4787
+ if (_nativeSmoothHeaderScrollEnabled) {
4788
+ NSInteger first = MAX(0, _currentIndex - 1);
4789
+ NSInteger last = MIN((NSInteger)_pageControllers.count - 1, _currentIndex + 1);
4790
+ BOOL isOwnedScroller = [scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class] &&
4791
+ ((RNCNativeScrollerComponentView *)scrollView.superview).scrollView == scrollView;
4792
+ if ((pageIndex >= first && pageIndex <= last || pageIndex == _destinationIndex) &&
4793
+ (RNCIsMountedNativeListScrollView(scrollView) || isOwnedScroller) &&
4794
+ [_originalInsets objectForKey:scrollView] == nil) {
4795
+ [self applyInsetsToScrollView:scrollView pageIndex:pageIndex restore:YES];
4796
+ }
4797
+ }
4798
+
4799
+ if (currentPageNeedsOwner && !_transitioning && !_isPagerDragging) {
4800
+ [self attachScrollObserverForCurrentPage];
4801
+ }
4802
+ }
4803
+
4514
4804
  - (void)nativeScroller:(RNCNativeScrollerComponentView *)scroller
4515
4805
  callerInsetChangedFrom:(UIEdgeInsets)previous to:(UIEdgeInsets)next
4516
4806
  {
@@ -5233,6 +5523,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5233
5523
  {
5234
5524
  UIScrollView *scrollView = _pagerTopScrollView;
5235
5525
  if (_dispatchingPagerTop || ![self matchesPagerTop:scrollView]) return;
5526
+ CGFloat clampedTarget = MIN(MAX(_pagerTopLogicalTarget, 0), _headerHeight);
5527
+ if (clampedTarget != _pagerTopLogicalTarget) {
5528
+ _pagerTopLogicalTarget = clampedTarget;
5529
+ CGFloat physicalTarget = RNCLimitRestoredScrollOffset(clampedTarget - scrollView.contentInset.top,
5530
+ scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
5531
+ [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:NO];
5532
+ }
5236
5533
  CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
5237
5534
  if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
5238
5535
  _pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
@@ -5244,11 +5541,29 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5244
5541
  }
5245
5542
 
5246
5543
  - (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
5544
+ {
5545
+ if (target != 0 && target != 1) return;
5546
+ [self scrollHeaderToOffset:(target == 1 ? _headerHeight : 0) animated:animated];
5547
+ }
5548
+
5549
+ - (void)scrollHeaderBy:(double)delta
5550
+ {
5551
+ if (!std::isfinite(delta)) return;
5552
+ [self scrollHeaderToOffset:MIN(MAX(_headerOffset + delta, 0), _headerHeight) animated:NO];
5553
+ }
5554
+
5555
+ - (void)scrollHeaderToOffset:(double)offset
5556
+ {
5557
+ if (!std::isfinite(offset)) return;
5558
+ [self scrollHeaderToOffset:MIN(MAX(offset, 0), _headerHeight) animated:NO];
5559
+ }
5560
+
5561
+ - (void)scrollHeaderToOffset:(CGFloat)logicalTarget animated:(BOOL)animated
5247
5562
  {
5248
5563
  [self cancelTopInsetReboundCheck];
5249
5564
  UIScrollView *scrollView = _observedScrollView;
5250
5565
  NSString *key = _observedPageKey;
5251
- if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
5566
+ if (_isBeingRecycled || _transitioning || _isPagerDragging ||
5252
5567
  _transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
5253
5568
  key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
5254
5569
  _observedPageGeneration != _generation || self.window == nil) return;
@@ -5264,7 +5579,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5264
5579
  [_alignedBodyKeys removeObject:key];
5265
5580
  [_suspendedBodyKeys removeObject:key];
5266
5581
  [_displayBodyOverrides removeObjectForKey:key];
5267
- CGFloat logicalTarget = target == 1 ? _headerHeight : 0;
5582
+ logicalTarget = MIN(MAX(logicalTarget, 0), _headerHeight);
5268
5583
  _pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
5269
5584
  _pagerTopScrollView = scrollView;
5270
5585
  _pagerTopPageKey = [key copy];