@onekeyfe/react-native-pager-view 3.0.157-alpha.256 → 3.0.157-alpha.258

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 (33) hide show
  1. package/README.md +51 -4
  2. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +254 -8
  3. package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +24 -0
  4. package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +573 -0
  5. package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +130 -0
  6. package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
  7. package/android/src/main/jni/CMakeLists.txt +19 -0
  8. package/android/src/main/jni/pagerview.h +11 -0
  9. package/android/src/main/res/values/native_scroll_coordinator_ids.xml +1 -0
  10. package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +38 -0
  11. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
  12. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
  13. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
  14. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
  15. package/docs/SPEC.md +268 -1
  16. package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
  17. package/ios/RNCNativeScrollerComponentView.h +17 -0
  18. package/ios/RNCNativeScrollerComponentView.mm +479 -0
  19. package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +346 -0
  20. package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
  21. package/ios/tests/RNCNativeScrollerTests.mm +252 -0
  22. package/lib/module/CollapsiblePagerView.js +1 -1
  23. package/lib/module/NativeScroller.js +171 -11
  24. package/lib/module/NativeScrollerNativeComponent.ts +101 -0
  25. package/lib/typescript/src/CollapsiblePagerView.d.ts +2 -3
  26. package/lib/typescript/src/NativeScroller.d.ts +15 -7
  27. package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
  28. package/package.json +6 -3
  29. package/react-native-pager-view.podspec +2 -1
  30. package/react-native.config.js +15 -0
  31. package/src/CollapsiblePagerView.tsx +4 -4
  32. package/src/NativeScroller.tsx +291 -14
  33. package/src/NativeScrollerNativeComponent.ts +101 -0
@@ -1,4 +1,5 @@
1
1
  #import "RNCCollapsiblePagerViewComponentView.h"
2
+ #import "RNCNativeScrollerComponentView.h"
2
3
  #import "RNCCollapsiblePagerReleasedStatePolicy.h"
3
4
 
4
5
  #import <react/renderer/components/pagerview/ComponentDescriptors.h>
@@ -12,6 +13,7 @@
12
13
  #import "React/RCTSurfaceTouchHandler.h"
13
14
  #import "React/RCTTouchHandler.h"
14
15
  #import <objc/runtime.h>
16
+ #import <UIKit/UIGestureRecognizerSubclass.h>
15
17
 
16
18
  // OneKey patch: deduplicate structural mutation parents before scanning the page.
17
19
  #include <unordered_set>
@@ -1019,6 +1021,114 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1019
1021
 
1020
1022
  @end
1021
1023
 
1024
+ // This observer never competes for scrolling. The owning pager explicitly
1025
+ // recognizes it only after its real pan begins, so UIKit terminates RN presses.
1026
+ @interface RNCCollapsiblePagerContentPressCancellationGestureRecognizer : UIGestureRecognizer
1027
+ @property (nonatomic, weak) UIGestureRecognizer *reactTouchHandler;
1028
+ - (void)cancelPressForPan:(UIPanGestureRecognizer *)pan;
1029
+ - (void)observeIncomingTouch:(UITouch *)touch;
1030
+ @end
1031
+
1032
+ @implementation RNCCollapsiblePagerContentPressCancellationGestureRecognizer {
1033
+ __weak UITouch *_trackedTouch;
1034
+ UIEvent *_touchEvent;
1035
+ BOOL _multipleSurfaceTouchesObserved;
1036
+ }
1037
+
1038
+ - (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)other
1039
+ {
1040
+ return other == self.reactTouchHandler;
1041
+ }
1042
+
1043
+ - (void)observeIncomingTouch:(UITouch *)touch
1044
+ {
1045
+ if (_trackedTouch != nil && touch != _trackedTouch &&
1046
+ [touch.view isDescendantOfView:self.reactTouchHandler.view]) {
1047
+ _multipleSurfaceTouchesObserved = YES;
1048
+ }
1049
+ }
1050
+
1051
+ - (void)recordEvent:(UIEvent *)event
1052
+ {
1053
+ _touchEvent = event;
1054
+ NSUInteger count = 0;
1055
+ for (UITouch *touch in event.allTouches) {
1056
+ if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled &&
1057
+ [touch.view isDescendantOfView:self.reactTouchHandler.view]) ++count;
1058
+ }
1059
+ if (count > 1) _multipleSurfaceTouchesObserved = YES;
1060
+ }
1061
+
1062
+ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
1063
+ {
1064
+ if (_trackedTouch != nil || touches.count != 1) {
1065
+ _multipleSurfaceTouchesObserved = YES;
1066
+ if (self.state == UIGestureRecognizerStatePossible) {
1067
+ self.state = UIGestureRecognizerStateFailed;
1068
+ }
1069
+ return;
1070
+ }
1071
+ _trackedTouch = touches.anyObject;
1072
+ [self recordEvent:event];
1073
+ }
1074
+
1075
+ - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
1076
+ {
1077
+ [self recordEvent:event];
1078
+ }
1079
+
1080
+ - (void)cancelPressForPan:(UIPanGestureRecognizer *)pan
1081
+ {
1082
+ UIGestureRecognizer *handler = self.reactTouchHandler;
1083
+ UITouch *touch = _trackedTouch;
1084
+ if (self.state != UIGestureRecognizerStatePossible || _multipleSurfaceTouchesObserved ||
1085
+ pan.state != UIGestureRecognizerStateBegan || touch == nil ||
1086
+ touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled ||
1087
+ handler.view == nil || self.view != handler.view.superview ||
1088
+ handler.state == UIGestureRecognizerStateFailed ||
1089
+ handler.state == UIGestureRecognizerStateCancelled ||
1090
+ handler.state == UIGestureRecognizerStateEnded) return;
1091
+
1092
+ // RN cancellation belongs to a Surface, not one finger. Do not terminate an
1093
+ // unrelated, already active touch on that Surface during a multi-touch sequence.
1094
+ NSUInteger activeTouches = 0;
1095
+ for (UITouch *activeTouch in _touchEvent.allTouches) {
1096
+ if (activeTouch.phase == UITouchPhaseEnded ||
1097
+ activeTouch.phase == UITouchPhaseCancelled) continue;
1098
+ if ([activeTouch.view isDescendantOfView:handler.view]) ++activeTouches;
1099
+ }
1100
+ if (activeTouches != 1) return;
1101
+ self.state = UIGestureRecognizerStateBegan;
1102
+ }
1103
+
1104
+ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
1105
+ {
1106
+ if (![touches containsObject:_trackedTouch]) return;
1107
+ self.state = self.state == UIGestureRecognizerStateBegan
1108
+ ? UIGestureRecognizerStateEnded : UIGestureRecognizerStateFailed;
1109
+ _trackedTouch = nil;
1110
+ _touchEvent = nil;
1111
+ }
1112
+
1113
+ - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
1114
+ {
1115
+ if (![touches containsObject:_trackedTouch]) return;
1116
+ self.state = self.state == UIGestureRecognizerStateBegan
1117
+ ? UIGestureRecognizerStateCancelled : UIGestureRecognizerStateFailed;
1118
+ _trackedTouch = nil;
1119
+ _touchEvent = nil;
1120
+ }
1121
+
1122
+ - (void)reset
1123
+ {
1124
+ [super reset];
1125
+ _trackedTouch = nil;
1126
+ _touchEvent = nil;
1127
+ _multipleSurfaceTouchesObserved = NO;
1128
+ }
1129
+
1130
+ @end
1131
+
1022
1132
  @interface RNCCollapsiblePagerOuterPagerPanGestureRecognizer : UIPanGestureRecognizer
1023
1133
  @property (nonatomic, strong) NSHashTable<UIGestureRecognizer *> *blockedPagerGestures;
1024
1134
  @end
@@ -1065,7 +1175,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1065
1175
  UIPageViewControllerDelegate,
1066
1176
  UIScrollViewDelegate,
1067
1177
  UIGestureRecognizerDelegate,
1068
- RCTMountingTransactionObserving
1178
+ RCTMountingTransactionObserving,
1179
+ RNCNativeScrollerInsetCoordinator
1069
1180
  >
1070
1181
  - (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
1071
1182
  - (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
@@ -1151,6 +1262,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1151
1262
  __weak UIView *_sharedHeaderPressCancellationGestureHost;
1152
1263
  __weak UIGestureRecognizer *_reactTouchHandler;
1153
1264
  UIPanGestureRecognizer *_sharedHeaderPressCancellationGesture;
1265
+ RNCCollapsiblePagerContentPressCancellationGestureRecognizer *_contentPressCancellationGesture;
1154
1266
  RNCCollapsiblePagerOuterPagerPanGestureRecognizer *_sharedHeaderOuterPagerGesture;
1155
1267
  UIPanGestureRecognizer *_verticalPagerGesture;
1156
1268
  BOOL _observingContentOffset;
@@ -1295,6 +1407,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1295
1407
  {
1296
1408
  [super didMoveToWindow];
1297
1409
  [self updateSharedHeaderPressCancellationGesture];
1410
+ for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
1411
+ [self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight];
1412
+ }
1298
1413
  // OneKey patch: removing a decelerating page from its window can suppress
1299
1414
  // UIKit's final scroll callback. Restore the last acknowledged page on reattach.
1300
1415
  if (self.window != nil && _isPagerDragging &&
@@ -1318,6 +1433,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1318
1433
 
1319
1434
  - (void)dealloc
1320
1435
  {
1436
+ for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
1437
+ [self updateNativeScrollerViewport:scrollView stickyHeight:0];
1438
+ }
1321
1439
  for (UIScrollView *scrollView in _refreshControlObservedScrollViews.allObjects) {
1322
1440
  [self releaseRefreshControlForScrollView:scrollView];
1323
1441
  }
@@ -1863,6 +1981,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1863
1981
  [self releaseRefreshControlForScrollView:scrollView];
1864
1982
  }
1865
1983
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
1984
+ [self updateNativeScrollerViewport:scrollView stickyHeight:0];
1866
1985
  NSValue *value = [_originalInsets objectForKey:scrollView];
1867
1986
  if (value != nil) {
1868
1987
  UIEdgeInsets original = value.UIEdgeInsetsValue;
@@ -2130,6 +2249,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2130
2249
  return [(RNCCollapsiblePagerViewComponentView *)view
2131
2250
  currentVerticalScrollViewForParentPager];
2132
2251
  }
2252
+ if ([view isKindOfClass:RNCNativeScrollerComponentView.class]) {
2253
+ return ((RNCNativeScrollerComponentView *)view).scrollView;
2254
+ }
2133
2255
  if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
2134
2256
  RCTScrollViewComponentView *componentView =
2135
2257
  (RCTScrollViewComponentView *)view;
@@ -2344,10 +2466,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2344
2466
  }
2345
2467
  _observingContentOffset = NO;
2346
2468
  _observedScrollView = nil;
2469
+ if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
2470
+ [(RNCNativeScrollerComponentView *)scrollView.superview stopScrolling];
2471
+ }
2347
2472
  }
2348
2473
 
2349
2474
  - (void)detachSharedHeaderPressCancellationGesture
2350
2475
  {
2476
+ if (_contentPressCancellationGesture.view != nil) {
2477
+ [_contentPressCancellationGesture.view removeGestureRecognizer:_contentPressCancellationGesture];
2478
+ }
2479
+ _contentPressCancellationGesture.reactTouchHandler = nil;
2480
+ _contentPressCancellationGesture = nil;
2351
2481
  if (_sharedHeaderPressCancellationGesture.view != nil) {
2352
2482
  [_sharedHeaderPressCancellationGesture.view
2353
2483
  removeGestureRecognizer:_sharedHeaderPressCancellationGesture];
@@ -2432,6 +2562,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2432
2562
  _sharedHeaderOuterPagerGesture.maximumNumberOfTouches = 1;
2433
2563
  }
2434
2564
  _sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
2565
+ if (_contentPressCancellationGesture == nil) {
2566
+ _contentPressCancellationGesture =
2567
+ [RNCCollapsiblePagerContentPressCancellationGestureRecognizer new];
2568
+ _contentPressCancellationGesture.delegate = self;
2569
+ // RN's external-recognizer path cancels its responder immediately only
2570
+ // when this flag is set; otherwise reset can be deferred until finger-up.
2571
+ _contentPressCancellationGesture.cancelsTouchesInView = YES;
2572
+ _contentPressCancellationGesture.delaysTouchesBegan = NO;
2573
+ _contentPressCancellationGesture.delaysTouchesEnded = NO;
2574
+ }
2575
+ _contentPressCancellationGesture.reactTouchHandler = reactTouchHandler;
2576
+ [gestureHost addGestureRecognizer:_contentPressCancellationGesture];
2435
2577
  _reactTouchHandler = reactTouchHandler;
2436
2578
  _sharedHeaderPressCancellationGestureHost = gestureHost;
2437
2579
  [gestureHost addGestureRecognizer:_sharedHeaderPressCancellationGesture];
@@ -2588,6 +2730,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2588
2730
  // still represents the old list, before Fabric applies its next owner's props.
2589
2731
  - (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)resetSavedOffset
2590
2732
  {
2733
+ [self updateNativeScrollerViewport:scrollView stickyHeight:0];
2591
2734
  [self releaseRefreshControlForScrollView:scrollView];
2592
2735
  if (scrollView == _observedScrollView) {
2593
2736
  [self detachScrollObserver];
@@ -2705,6 +2848,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2705
2848
  // This host already includes the fixed header and safe area in its frame.
2706
2849
  // UIKit automatic adjustment would add them a second time to short pages.
2707
2850
  scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
2851
+ [self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight];
2708
2852
  [self updateRefreshControlForScrollView:scrollView];
2709
2853
  CGFloat previousTopInset = scrollView.contentInset.top;
2710
2854
  CGFloat previousLogicalOffset = scrollView.contentOffset.y + previousTopInset;
@@ -2765,6 +2909,31 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2765
2909
  if (pageIndex == _currentIndex) [self updateHeaderForScrollView:scrollView];
2766
2910
  }
2767
2911
 
2912
+ - (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
2913
+ {
2914
+ if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
2915
+ [(RNCNativeScrollerComponentView *)scrollView.superview setPagerStickyHeight:height forOwner:self];
2916
+ }
2917
+ }
2918
+
2919
+ - (void)nativeScroller:(RNCNativeScrollerComponentView *)scroller
2920
+ callerInsetChangedFrom:(UIEdgeInsets)previous to:(UIEdgeInsets)next
2921
+ {
2922
+ UIScrollView *scrollView = scroller.scrollView;
2923
+ NSValue *stored = [_originalInsets objectForKey:scrollView];
2924
+ if (stored == nil) return;
2925
+ UIEdgeInsets original = stored.UIEdgeInsetsValue;
2926
+ original.top += next.top - previous.top;
2927
+ original.left += next.left - previous.left;
2928
+ original.bottom += next.bottom - previous.bottom;
2929
+ original.right += next.right - previous.right;
2930
+ [_originalInsets setObject:[NSValue valueWithUIEdgeInsets:original] forKey:scrollView];
2931
+ NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
2932
+ if (appliedTop != nil) {
2933
+ [_appliedTopInsets setObject:@(appliedTop.doubleValue + next.top - previous.top) forKey:scrollView];
2934
+ }
2935
+ }
2936
+
2768
2937
  - (void)reapplyInsetsToObservedScrollView
2769
2938
  {
2770
2939
  [self restoreDetachedScrollInsets];
@@ -3030,6 +3199,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3030
3199
 
3031
3200
  - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
3032
3201
  {
3202
+ if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
3203
+ [_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
3204
+ }
3033
3205
  if (_directNativePagerEnabled) {
3034
3206
  [self preparePageForHorizontalTransitionAtIndex:_currentIndex - 1];
3035
3207
  [self preparePageForHorizontalTransitionAtIndex:_currentIndex + 1];
@@ -3375,6 +3547,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3375
3547
  (unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count]);
3376
3548
  return YES;
3377
3549
  }
3550
+ if (gestureRecognizer == _contentPressCancellationGesture) {
3551
+ [_contentPressCancellationGesture observeIncomingTouch:touch];
3552
+ UIView *touchView = touch.view;
3553
+ return _nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
3554
+ _contentPressCancellationGesture.view != nil &&
3555
+ [touchView isDescendantOfView:_pagerScrollView] &&
3556
+ ![touchView isDescendantOfView:_sharedHeaderHostView];
3557
+ }
3378
3558
  if (gestureRecognizer == _sharedHeaderPressCancellationGesture) {
3379
3559
  if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
3380
3560
  _sharedHeaderPressCancellationGestureHost == nil) return NO;
@@ -3415,6 +3595,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3415
3595
  ![other isKindOfClass:RCTTouchHandler.class] &&
3416
3596
  ![_sharedHeaderOuterPagerGesture.blockedPagerGestures containsObject:other];
3417
3597
  }
3598
+ if (gestureRecognizer == _contentPressCancellationGesture ||
3599
+ otherGestureRecognizer == _contentPressCancellationGesture) {
3600
+ UIGestureRecognizer *other = gestureRecognizer == _contentPressCancellationGesture
3601
+ ? otherGestureRecognizer : gestureRecognizer;
3602
+ return other != _contentPressCancellationGesture.reactTouchHandler;
3603
+ }
3418
3604
  if (gestureRecognizer == _sharedHeaderPressCancellationGesture ||
3419
3605
  otherGestureRecognizer == _sharedHeaderPressCancellationGesture) {
3420
3606
  UIGestureRecognizer *other = gestureRecognizer == _sharedHeaderPressCancellationGesture
@@ -0,0 +1,17 @@
1
+ #import <React/RCTViewComponentView.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ @interface RNCNativeScrollerComponentView : RCTViewComponentView
6
+ @property (nonatomic, readonly) UIScrollView *scrollView;
7
+ - (void)stopScrolling;
8
+ - (void)setPagerStickyHeight:(CGFloat)height forOwner:(id)owner;
9
+ @end
10
+
11
+ // Native inset ownership stays separate from Fabric/Yoga content layout.
12
+ @protocol RNCNativeScrollerInsetCoordinator <NSObject>
13
+ - (void)nativeScroller:(RNCNativeScrollerComponentView *)scroller
14
+ callerInsetChangedFrom:(UIEdgeInsets)previous to:(UIEdgeInsets)next;
15
+ @end
16
+
17
+ NS_ASSUME_NONNULL_END