@onekeyfe/react-native-pager-view 3.0.157-alpha.257 → 3.0.157-alpha.262

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 (35) hide show
  1. package/README.md +55 -4
  2. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +295 -59
  3. package/android/src/main/java/com/reactnativepagerview/NativeScrollerPendingRestore.kt +26 -0
  4. package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +15 -11
  5. package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +600 -0
  6. package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +145 -0
  7. package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
  8. package/android/src/main/jni/CMakeLists.txt +19 -0
  9. package/android/src/main/jni/pagerview.h +11 -0
  10. package/android/src/main/res/values/native_scroll_coordinator_ids.xml +1 -0
  11. package/android/src/test/java/com/reactnativepagerview/NativeScrollerPendingRestoreTest.kt +31 -0
  12. package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +32 -52
  13. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
  14. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
  15. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
  16. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
  17. package/docs/SPEC.md +282 -34
  18. package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
  19. package/ios/RNCNativeScrollerComponentView.h +17 -0
  20. package/ios/RNCNativeScrollerComponentView.mm +479 -0
  21. package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +759 -0
  22. package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
  23. package/ios/tests/RNCNativeScrollerTests.mm +252 -0
  24. package/lib/module/CollapsiblePagerView.js +10 -1
  25. package/lib/module/NativeScroller.js +171 -11
  26. package/lib/module/NativeScrollerNativeComponent.ts +101 -0
  27. package/lib/typescript/src/CollapsiblePagerView.d.ts +3 -3
  28. package/lib/typescript/src/NativeScroller.d.ts +15 -7
  29. package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
  30. package/package.json +7 -4
  31. package/react-native-pager-view.podspec +2 -1
  32. package/react-native.config.js +15 -0
  33. package/src/CollapsiblePagerView.tsx +14 -4
  34. package/src/NativeScroller.tsx +291 -14
  35. package/src/NativeScrollerNativeComponent.ts +101 -0
@@ -0,0 +1,109 @@
1
+ #import <XCTest/XCTest.h>
2
+ #import <React/RCTSurfaceTouchHandler.h>
3
+
4
+ #import "RNCCollapsiblePagerViewComponentView.h"
5
+ #import <react/renderer/components/pagerview/Props.h>
6
+
7
+ using namespace facebook::react;
8
+
9
+ @interface RNCPressWiringTestTouch : UITouch
10
+ @property (nonatomic, strong) UIView *testView;
11
+ @end
12
+ @implementation RNCPressWiringTestTouch
13
+ - (UIView *)view { return self.testView; }
14
+ @end
15
+
16
+ @interface RNCCollapsiblePagerPressCancellationWiringTests : XCTestCase
17
+ @property (nonatomic, strong) UIWindow *window;
18
+ @property (nonatomic, strong) UIView *host;
19
+ @property (nonatomic, strong) UIView *surface;
20
+ @property (nonatomic, strong) RCTSurfaceTouchHandler *handler;
21
+ @property (nonatomic, strong) RNCCollapsiblePagerViewComponentView *pager;
22
+ @end
23
+
24
+ @implementation RNCCollapsiblePagerPressCancellationWiringTests
25
+
26
+ - (void)setUp
27
+ {
28
+ [super setUp];
29
+ // Window membership installs the bridge without presenting or taking focus.
30
+ self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 600)];
31
+ self.host = [[UIView alloc] initWithFrame:self.window.bounds];
32
+ self.surface = [[UIView alloc] initWithFrame:self.host.bounds];
33
+ [self.window addSubview:self.host];
34
+ [self.host addSubview:self.surface];
35
+ self.handler = [RCTSurfaceTouchHandler new];
36
+ [self.handler attachToView:self.surface];
37
+ self.pager = [[RNCCollapsiblePagerViewComponentView alloc] init];
38
+ auto props = std::make_shared<RNCCollapsiblePagerViewProps>();
39
+ props->nativeSmoothHeaderScrollEnabled = true;
40
+ [self.pager updateProps:props oldProps:nullptr];
41
+ self.pager.frame = self.surface.bounds;
42
+ [self.surface addSubview:self.pager];
43
+ }
44
+
45
+ - (void)tearDown
46
+ {
47
+ [self.pager removeFromSuperview];
48
+ [self.handler detachFromView:self.surface];
49
+ self.pager = nil;
50
+ self.handler = nil;
51
+ self.surface = nil;
52
+ self.host = nil;
53
+ self.window = nil;
54
+ [super tearDown];
55
+ }
56
+
57
+ - (void)testInstalledBridgeCancelsTouchesAndFiltersItsContentScope
58
+ {
59
+ UIGestureRecognizer *bridge = [self.pager valueForKey:@"contentPressCancellationGesture"];
60
+ XCTAssertNotNil(bridge);
61
+ XCTAssertTrue([bridge isKindOfClass:NSClassFromString(@"RNCCollapsiblePagerContentPressCancellationGestureRecognizer")]);
62
+ XCTAssertEqual(bridge.view, self.host);
63
+ XCTAssertEqual([bridge valueForKey:@"reactTouchHandler"], self.handler);
64
+ XCTAssertTrue(bridge.cancelsTouchesInView);
65
+ XCTAssertFalse(bridge.delaysTouchesBegan);
66
+ XCTAssertFalse(bridge.delaysTouchesEnded);
67
+ XCTAssertTrue([self.handler canBePreventedByGestureRecognizer:bridge]);
68
+
69
+ UIScrollView *scrollView = [self.pager valueForKey:@"pagerScrollView"];
70
+ XCTAssertNotNil(scrollView);
71
+ UIView *content = [UIView new];
72
+ [scrollView addSubview:content];
73
+ UIView *header = [self.pager valueForKey:@"sharedHeaderHostView"];
74
+ // Smooth mode reparents the shared header into the same content subtree.
75
+ [scrollView addSubview:header];
76
+ UIView *headerButton = [UIView new];
77
+ [header addSubview:headerButton];
78
+ RNCPressWiringTestTouch *touch = [RNCPressWiringTestTouch new];
79
+ id<UIGestureRecognizerDelegate> delegate = bridge.delegate;
80
+ XCTAssertEqual((id)delegate, self.pager);
81
+ touch.testView = content;
82
+ XCTAssertTrue([delegate gestureRecognizer:bridge shouldReceiveTouch:touch]);
83
+ touch.testView = headerButton;
84
+ XCTAssertFalse([delegate gestureRecognizer:bridge shouldReceiveTouch:touch]);
85
+ touch.testView = self.surface;
86
+ XCTAssertFalse([delegate gestureRecognizer:bridge shouldReceiveTouch:touch]);
87
+ }
88
+
89
+ - (void)testWindowDetachUnbindsInstalledBridge
90
+ {
91
+ UIGestureRecognizer *bridge = [self.pager valueForKey:@"contentPressCancellationGesture"];
92
+ XCTAssertNotNil(bridge);
93
+ [self.pager removeFromSuperview];
94
+ XCTAssertNil(bridge.view);
95
+ XCTAssertNil([bridge valueForKey:@"reactTouchHandler"]);
96
+ XCTAssertNil([self.pager valueForKey:@"contentPressCancellationGesture"]);
97
+ }
98
+
99
+ - (void)testRecycleUnbindsInstalledBridge
100
+ {
101
+ UIGestureRecognizer *bridge = [self.pager valueForKey:@"contentPressCancellationGesture"];
102
+ XCTAssertNotNil(bridge);
103
+ [self.pager prepareForRecycle];
104
+ XCTAssertNil(bridge.view);
105
+ XCTAssertNil([bridge valueForKey:@"reactTouchHandler"]);
106
+ XCTAssertNil([self.pager valueForKey:@"contentPressCancellationGesture"]);
107
+ }
108
+
109
+ @end
@@ -0,0 +1,252 @@
1
+ #import <XCTest/XCTest.h>
2
+ #import "RNCNativeScrollerComponentView.h"
3
+ #import "RNCCollapsiblePagerViewComponentView.h"
4
+ #import <react/renderer/components/pagerview/Props.h>
5
+
6
+ using namespace facebook::react;
7
+
8
+ @interface RNCNativeScrollerComponentView (Tests)
9
+ - (void)emitScrollEvent:(NSInteger)kind velocity:(CGPoint)velocity target:(CGPoint)target;
10
+ - (void)clampContentOffsetIfNeeded;
11
+ - (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated;
12
+ - (void)setRefreshing:(BOOL)refreshing;
13
+ - (CGFloat)contentViewportHeight;
14
+ @end
15
+
16
+ @interface RNCCollapsiblePagerViewComponentView (ScrollerTests)
17
+ - (void)applyInsetsToScrollView:(UIScrollView *)scrollView pageIndex:(NSInteger)index restore:(BOOL)restore;
18
+ - (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)reset;
19
+ @end
20
+
21
+ @interface RNCObservedScroller : RNCNativeScrollerComponentView
22
+ @property (nonatomic, strong) NSMutableArray<NSNumber *> *events;
23
+ @end
24
+ @implementation RNCObservedScroller
25
+ - (void)emitScrollEvent:(NSInteger)kind velocity:(CGPoint)velocity target:(CGPoint)target
26
+ {
27
+ if (self.events == nil) self.events = [NSMutableArray new];
28
+ [self.events addObject:@(kind)];
29
+ [super emitScrollEvent:kind velocity:velocity target:target];
30
+ }
31
+ @end
32
+
33
+ @interface RNCNativeScrollerTests : XCTestCase
34
+ @property (nonatomic, strong) RNCObservedScroller *scroller;
35
+ @end
36
+ @implementation RNCNativeScrollerTests
37
+
38
+ - (void)setUp
39
+ {
40
+ [super setUp];
41
+ self.scroller = [RNCObservedScroller new];
42
+ LayoutMetrics metrics;
43
+ metrics.frame.size = {320, 600};
44
+ [self.scroller updateLayoutMetrics:metrics oldLayoutMetrics:{}];
45
+ }
46
+
47
+ - (void)tearDown
48
+ {
49
+ [self.scroller prepareForRecycle];
50
+ self.scroller = nil;
51
+ [super tearDown];
52
+ }
53
+
54
+ - (void)testContentSizeDoesNotRelayoutReactContentOrSharedHeader
55
+ {
56
+ RCTViewComponentView *content = [RCTViewComponentView new];
57
+ content.frame = CGRectMake(0, 0, 320, 900);
58
+ [self.scroller mountChildComponentView:content index:0];
59
+ UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, -200, 320, 200)];
60
+ [self.scroller.scrollView addSubview:header];
61
+ auto props = std::make_shared<RNCNativeScrollerProps>();
62
+ props->contentWidth = 320;
63
+ props->contentHeight = 900;
64
+ [self.scroller updateProps:props oldProps:nullptr];
65
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentSize.height, 900, 0.01);
66
+ XCTAssertTrue(CGRectEqualToRect(content.frame, CGRectMake(0, 0, 320, 900)));
67
+ XCTAssertTrue(CGRectEqualToRect(header.frame, CGRectMake(0, -200, 320, 200)));
68
+ XCTAssertEqual(header.superview, self.scroller.scrollView);
69
+ XCTAssertFalse([self.scroller.scrollView isKindOfClass:NSClassFromString(@"RCTEnhancedScrollView")]);
70
+ }
71
+
72
+ - (void)testShrinkClampsOnlyUnreachableUpperOffset
73
+ {
74
+ auto props = std::make_shared<RNCNativeScrollerProps>();
75
+ props->contentWidth = 320;
76
+ props->contentHeight = 1500;
77
+ [self.scroller updateProps:props oldProps:nullptr];
78
+ self.scroller.scrollView.contentInset = UIEdgeInsetsMake(200, 0, 468, 0);
79
+ self.scroller.scrollView.contentOffset = CGPointMake(0, 800);
80
+ auto shorter = std::make_shared<RNCNativeScrollerProps>();
81
+ shorter->contentWidth = 320;
82
+ shorter->contentHeight = 132;
83
+ [self.scroller updateProps:shorter oldProps:props];
84
+ [self.scroller clampContentOffsetIfNeeded];
85
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentOffset.y, 0, 0.01);
86
+ self.scroller.scrollView.contentOffset = CGPointMake(0, -240);
87
+ auto empty = std::make_shared<RNCNativeScrollerProps>();
88
+ empty->contentWidth = 320;
89
+ empty->contentHeight = 0;
90
+ [self.scroller updateProps:empty oldProps:shorter];
91
+ [self.scroller clampContentOffsetIfNeeded];
92
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentOffset.y, -240, 0.01);
93
+ }
94
+
95
+ - (void)testNativeInterruptionEndsEachDragAndMomentumOnlyOnce
96
+ {
97
+ UIScrollView *scroll = self.scroller.scrollView;
98
+ id<UIScrollViewDelegate> delegate = scroll.delegate;
99
+ [delegate scrollViewWillBeginDragging:scroll];
100
+ [self.scroller stopScrolling];
101
+ [delegate scrollViewDidEndDragging:scroll willDecelerate:NO];
102
+ XCTAssertEqualObjects(self.scroller.events, (@[@1, @2]));
103
+ [self.scroller.events removeAllObjects];
104
+ [delegate scrollViewWillBeginDecelerating:scroll];
105
+ [self.scroller stopScrolling];
106
+ [delegate scrollViewDidEndDecelerating:scroll];
107
+ NSPredicate *terminal = [NSPredicate predicateWithFormat:@"SELF == 4"];
108
+ XCTAssertEqual([self.scroller.events filteredArrayUsingPredicate:terminal].count, 1u);
109
+ XCTAssertEqualObjects(self.scroller.events.firstObject, @3);
110
+ }
111
+
112
+ - (void)testRefreshReplacementAndRecycleDoNotKeepOldControl
113
+ {
114
+ auto props = std::make_shared<RNCNativeScrollerProps>();
115
+ props->refreshEnabled = true;
116
+ props->refreshProgressViewOffset = 24;
117
+ [self.scroller updateProps:props oldProps:nullptr];
118
+ UIRefreshControl *original = self.scroller.scrollView.refreshControl;
119
+ XCTAssertNotNil(original);
120
+ XCTAssertEqualWithAccuracy(original.bounds.origin.y, -24, 0.01);
121
+ [self.scroller prepareForRecycle];
122
+ XCTAssertNil(self.scroller.scrollView.refreshControl);
123
+ [self.scroller updateProps:props oldProps:props];
124
+ XCTAssertNotNil(self.scroller.scrollView.refreshControl);
125
+ XCTAssertNotEqual(self.scroller.scrollView.refreshControl, original);
126
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.refreshControl.bounds.origin.y, -24, 0.01);
127
+ [self.scroller setRefreshing:NO];
128
+ XCTAssertFalse(self.scroller.scrollView.refreshControl.refreshing);
129
+ }
130
+
131
+ - (void)testCallerInsetUpdatesSurvivePagerReapplyAndRelease
132
+ {
133
+ RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
134
+ auto pagerProps = std::make_shared<RNCCollapsiblePagerViewProps>();
135
+ pagerProps->headerHeight = 120;
136
+ pagerProps->stickyHeaderHeight = 40;
137
+ [pager updateProps:pagerProps oldProps:nullptr];
138
+ [pager addSubview:self.scroller];
139
+ auto props = std::make_shared<RNCNativeScrollerProps>();
140
+ props->contentWidth = 320;
141
+ props->contentHeight = 1500;
142
+ props->contentInsetTop = 10;
143
+ [self.scroller updateProps:props oldProps:nullptr];
144
+ [pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
145
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 170, 0.01);
146
+ auto updated = std::make_shared<RNCNativeScrollerProps>();
147
+ updated->contentWidth = 320;
148
+ updated->contentHeight = 1500;
149
+ updated->contentInsetTop = 30;
150
+ updated->contentInsetBottom = 20;
151
+ [self.scroller updateProps:updated oldProps:props];
152
+ [pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
153
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 190, 0.01);
154
+ [pager restoreScrollViewState:self.scroller.scrollView resetSavedOffset:YES];
155
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 30, 0.01);
156
+ XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.bottom, 20, 0.01);
157
+ [self.scroller removeFromSuperview];
158
+ }
159
+
160
+ - (void)testRecycledDelegateCannotStartASequenceForTheNextOwner
161
+ {
162
+ UIScrollView *scroll = self.scroller.scrollView;
163
+ id<UIScrollViewDelegate> oldDelegate = scroll.delegate;
164
+ [self.scroller prepareForRecycle];
165
+ XCTAssertNil(scroll.delegate);
166
+ [oldDelegate scrollViewWillBeginDragging:scroll];
167
+ [oldDelegate scrollViewWillBeginDecelerating:scroll];
168
+ auto props = std::make_shared<RNCNativeScrollerProps>();
169
+ [self.scroller updateProps:props oldProps:nullptr];
170
+ XCTAssertEqual(scroll.delegate, self.scroller);
171
+ [self.scroller stopScrolling];
172
+ NSPredicate *terminal = [NSPredicate predicateWithFormat:@"SELF == 2 OR SELF == 4"];
173
+ XCTAssertEqual([self.scroller.events filteredArrayUsingPredicate:terminal].count, 0u);
174
+ }
175
+
176
+ - (void)testPagerExplicitlyFindsIndependentScrollView
177
+ {
178
+ RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
179
+ UIView *page = [UIView new];
180
+ [page addSubview:self.scroller];
181
+ SEL finder = NSSelectorFromString(@"findExplicitNativeScrollerInView:");
182
+ UIScrollView *(*find)(id, SEL, UIView *) =
183
+ (UIScrollView *(*)(id, SEL, UIView *))[pager methodForSelector:finder];
184
+ XCTAssertEqual(find(pager, finder, page), self.scroller.scrollView);
185
+ }
186
+
187
+ - (void)testContentViewportUsesOnlyCallerInsetsAndStaticStickyHeight
188
+ {
189
+ RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
190
+ auto pagerProps = std::make_shared<RNCCollapsiblePagerViewProps>();
191
+ pagerProps->headerHeight = 120;
192
+ pagerProps->stickyHeaderHeight = 40;
193
+ [pager updateProps:pagerProps oldProps:nullptr];
194
+ [pager addSubview:self.scroller];
195
+ auto props = std::make_shared<RNCNativeScrollerProps>();
196
+ props->contentWidth = 320;
197
+ props->contentHeight = 20;
198
+ props->contentInsetTop = 10;
199
+ props->contentInsetBottom = 20;
200
+ [self.scroller updateProps:props oldProps:nullptr];
201
+ [pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
202
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
203
+ // Synthetic bottom fill and temporary refresh insets are not caller geometry.
204
+ self.scroller.scrollView.contentInset = UIEdgeInsetsMake(230, 0, 510, 0);
205
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
206
+ auto filled = std::make_shared<RNCNativeScrollerProps>();
207
+ filled->contentWidth = 320;
208
+ filled->contentHeight = 530;
209
+ filled->contentInsetTop = 10;
210
+ filled->contentInsetBottom = 20;
211
+ [self.scroller updateProps:filled oldProps:props];
212
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
213
+ LayoutMetrics metrics;
214
+ metrics.frame.size = {320, 700};
215
+ [self.scroller updateLayoutMetrics:metrics oldLayoutMetrics:{}];
216
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 630, 0.01);
217
+ auto updated = std::make_shared<RNCNativeScrollerProps>();
218
+ updated->contentWidth = 320;
219
+ updated->contentHeight = 530;
220
+ updated->contentInsetTop = 10;
221
+ updated->contentInsetBottom = 30;
222
+ [self.scroller updateProps:updated oldProps:filled];
223
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 620, 0.01);
224
+ auto tallerSticky = std::make_shared<RNCCollapsiblePagerViewProps>();
225
+ tallerSticky->headerHeight = 250;
226
+ tallerSticky->stickyHeaderHeight = 60;
227
+ [pager updateProps:tallerSticky oldProps:pagerProps];
228
+ [pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
229
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 600, 0.01);
230
+ [pager restoreScrollViewState:self.scroller.scrollView resetSavedOffset:YES];
231
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 660, 0.01);
232
+ [self.scroller removeFromSuperview];
233
+ }
234
+
235
+ - (void)testContentViewportReleasesIndependentPagerContributions
236
+ {
237
+ NSObject *outer = [NSObject new];
238
+ NSObject *inner = [NSObject new];
239
+ [self.scroller setPagerStickyHeight:40 forOwner:outer];
240
+ [self.scroller setPagerStickyHeight:20 forOwner:inner];
241
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 540, 0.01);
242
+ [self.scroller setPagerStickyHeight:40 forOwner:outer];
243
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 540, 0.01);
244
+ [self.scroller setPagerStickyHeight:0 forOwner:outer];
245
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 580, 0.01);
246
+ [self.scroller prepareForRecycle];
247
+ auto props = std::make_shared<RNCNativeScrollerProps>();
248
+ [self.scroller updateProps:props oldProps:nullptr];
249
+ XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 600, 0.01);
250
+ }
251
+
252
+ @end
@@ -30,6 +30,10 @@ export class CollapsiblePagerView extends React.PureComponent {
30
30
  lastMountSignature = null;
31
31
  nativeTabCommandId = 0;
32
32
  latestNativeTabTarget = null;
33
+
34
+ // Target of the last tab command native has received and not yet reported
35
+ // as selected. Commands superseded before their frame never reach native.
36
+ dispatchedNativeTabTarget = null;
33
37
  latestNativeSelectedPage = this.state.selectedPage;
34
38
  nativePagerScrollState = "idle";
35
39
  nativeTabCommandQueuedDuringScroll = false;
@@ -89,6 +93,7 @@ export class CollapsiblePagerView extends React.PureComponent {
89
93
  if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
90
94
  return;
91
95
  }
96
+ this.dispatchedNativeTabTarget = position;
92
97
  if (animated) {
93
98
  CollapsiblePagerViewNativeCommands.setPage(this.nativeRef, position);
94
99
  } else {
@@ -136,6 +141,7 @@ export class CollapsiblePagerView extends React.PureComponent {
136
141
  const transitionComplete = pendingTarget === position;
137
142
  if (transitionComplete) {
138
143
  this.latestNativeTabTarget = null;
144
+ this.dispatchedNativeTabTarget = null;
139
145
  this.nativeTabCommandQueuedDuringScroll = false;
140
146
  }
141
147
  this.setState({
@@ -154,6 +160,7 @@ export class CollapsiblePagerView extends React.PureComponent {
154
160
  if (scrollState === "idle" && this.latestNativeTabTarget !== null && this.latestNativeTabTarget !== this.latestNativeSelectedPage && !queuedDuringPreviousScroll) {
155
161
  ++this.nativeTabCommandId;
156
162
  this.latestNativeTabTarget = null;
163
+ this.dispatchedNativeTabTarget = null;
157
164
  this.setState({
158
165
  transientRetainedPages: []
159
166
  }, this.notifyMountedPagesChanged);
@@ -166,7 +173,9 @@ export class CollapsiblePagerView extends React.PureComponent {
166
173
  onNativeTabPress = event => {
167
174
  const position = pageIndex(event.nativeEvent.position);
168
175
  if (position !== null && position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
169
- this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? true);
176
+ // Selection state lags an in-flight transition until native reports it.
177
+ const currentPage = this.dispatchedNativeTabTarget ?? this.state.selectedPage;
178
+ this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1);
170
179
  }
171
180
  this.props.onNativeTabPress?.(event);
172
181
  };
@@ -1,25 +1,185 @@
1
1
  "use strict";
2
2
 
3
3
  import * as React from "react";
4
- import { ScrollView } from "react-native";
4
+ import { findNodeHandle, Keyboard, Platform, StyleSheet, TextInput, View } from "react-native";
5
+ import NativeScrollerHost, { Commands } from "./NativeScrollerNativeComponent";
5
6
  import { jsx as _jsx } from "react/jsx-runtime";
6
7
  export const NATIVE_SCROLLER_NATIVE_ID_PREFIX = "rnc-collapsible-pager-native-scroller";
7
- /**
8
- * Primary non-virtualized vertical scroller for a CollapsiblePagerView page.
9
- */
8
+
9
+ /** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
10
+
11
+ // The RN 0.86 JS registry distinguishes another input from an unhandled tap.
12
+ // TextInput.State's public facade does not export this query.
13
+ const textInputState = require("react-native/Libraries/Components/TextInput/TextInputState").default;
14
+ const styles = StyleSheet.create({
15
+ viewport: {
16
+ flexGrow: 1,
17
+ flexShrink: 1,
18
+ overflow: "hidden"
19
+ },
20
+ content: {
21
+ position: "absolute",
22
+ top: 0,
23
+ left: 0,
24
+ right: 0,
25
+ flexShrink: 0
26
+ }
27
+ });
10
28
  export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScroller({
29
+ children,
11
30
  nativeID,
12
- nestedScrollEnabled = true,
13
31
  pagerScrollKey,
32
+ style,
33
+ contentContainerStyle,
34
+ onLayout,
35
+ onContentSizeChange,
36
+ refreshControl,
37
+ nestedScrollEnabled = true,
38
+ contentInset,
39
+ decelerationRate = "normal",
40
+ keyboardShouldPersistTaps = "never",
41
+ onScroll,
42
+ onScrollBeginDrag,
43
+ onMomentumScrollBegin,
44
+ onMomentumScrollEnd,
45
+ onStartShouldSetResponder,
46
+ onStartShouldSetResponderCapture,
47
+ onResponderGrant,
48
+ onResponderRelease,
49
+ onResponderTerminationRequest,
14
50
  ...props
15
- }, ref) {
51
+ }, forwardedRef) {
52
+ const hostRef = React.useRef(null);
16
53
  const generatedId = React.useId();
17
- const scrollKey = pagerScrollKey ?? nativeID ?? generatedId;
18
- return /*#__PURE__*/_jsx(ScrollView, {
54
+ const [contentViewportHeight, setContentViewportHeight] = React.useState(0);
55
+ const [contentSize, setContentSize] = React.useState({
56
+ width: 0,
57
+ height: 0
58
+ });
59
+ const [refreshEpoch, requestRefreshSync] = React.useReducer(value => value + 1, 0);
60
+ const contentSizeRef = React.useRef(contentSize);
61
+ const scrolledSinceGrant = React.useRef(false);
62
+ const momentum = React.useRef(false);
63
+ const grantedDuringMomentum = React.useRef(false);
64
+ const persistTaps = keyboardShouldPersistTaps === true ? "always" : keyboardShouldPersistTaps === false ? "never" : keyboardShouldPersistTaps;
65
+ const contentStyle = StyleSheet.flatten(contentContainerStyle);
66
+ const refresh = refreshControl?.props;
67
+ const rate = typeof decelerationRate === "number" ? decelerationRate : decelerationRate === "fast" ? Platform.OS === "ios" ? 0.99 : 0.9 : Platform.OS === "ios" ? 0.998 : 0.985;
68
+ React.useEffect(() => {
69
+ if (hostRef.current) Commands.setRefreshState(hostRef.current, refresh?.refreshing ?? false);
70
+ }, [refresh?.refreshing, refreshEpoch]);
71
+ React.useImperativeHandle(forwardedRef, () => ({
72
+ scrollTo(options, x, animated) {
73
+ const host = hostRef.current;
74
+ if (!host) return;
75
+ if (typeof options === "number") {
76
+ Commands.scrollTo(host, x ?? 0, options, animated !== false);
77
+ } else {
78
+ Commands.scrollTo(host, options?.x ?? 0, options?.y ?? 0, options?.animated !== false);
79
+ }
80
+ },
81
+ scrollToEnd(options) {
82
+ if (hostRef.current) Commands.scrollToEnd(hostRef.current, options?.animated !== false);
83
+ },
84
+ flashScrollIndicators() {
85
+ if (hostRef.current) Commands.flashScrollIndicators(hostRef.current);
86
+ },
87
+ getNativeScrollRef: () => hostRef.current,
88
+ getScrollableNode: () => findNodeHandle(hostRef.current) ?? null,
89
+ measure: (...args) => hostRef.current?.measure(...args),
90
+ measureInWindow: (...args) => hostRef.current?.measureInWindow(...args),
91
+ measureLayout: (...args) => hostRef.current?.measureLayout(...args),
92
+ setNativeProps: nativeProps => hostRef.current?.setNativeProps(nativeProps),
93
+ focus: () => hostRef.current?.focus(),
94
+ blur: () => hostRef.current?.blur()
95
+ }), []);
96
+ const keyboardIsDismissible = () => Boolean(TextInput.State.currentlyFocusedInput() && (Keyboard.isVisible() || Platform.OS === "android" && Number(Platform.Version) < 30) && Keyboard.metrics()?.height !== 0);
97
+ return /*#__PURE__*/_jsx(NativeScrollerHost, {
19
98
  ...props,
20
- ref: ref,
21
- nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${scrollKey}`,
22
- nestedScrollEnabled: nestedScrollEnabled
99
+ ref: hostRef,
100
+ nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${pagerScrollKey ?? nativeID ?? generatedId}`,
101
+ 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
+ onLayout: onLayout,
128
+ onContentViewportChange: event => {
129
+ const height = event.nativeEvent.height;
130
+ if (!Number.isFinite(height) || height < 0) return;
131
+ setContentViewportHeight(previous => previous === height ? previous : height);
132
+ },
133
+ onScroll: event => {
134
+ scrolledSinceGrant.current = true;
135
+ onScroll?.(event);
136
+ },
137
+ onScrollBeginDrag: event => {
138
+ scrolledSinceGrant.current = true;
139
+ onScrollBeginDrag?.(event);
140
+ },
141
+ onMomentumScrollBegin: event => {
142
+ momentum.current = true;
143
+ onMomentumScrollBegin?.(event);
144
+ },
145
+ onMomentumScrollEnd: event => {
146
+ momentum.current = false;
147
+ onMomentumScrollEnd?.(event);
148
+ },
149
+ onStartShouldSetResponder: event => onStartShouldSetResponder?.(event) ?? (persistTaps === "handled" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput()),
150
+ onStartShouldSetResponderCapture: event => onStartShouldSetResponderCapture?.(event) ?? (momentum.current || persistTaps === "never" && keyboardIsDismissible() && typeof event.target !== "number" && !textInputState.isTextInput(event.target)),
151
+ onResponderGrant: event => {
152
+ scrolledSinceGrant.current = false;
153
+ grantedDuringMomentum.current = momentum.current;
154
+ onResponderGrant?.(event);
155
+ },
156
+ onResponderRelease: event => {
157
+ onResponderRelease?.(event);
158
+ if (persistTaps !== "always" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput() && !scrolledSinceGrant.current && !grantedDuringMomentum.current) Keyboard.dismiss();
159
+ },
160
+ onResponderTerminationRequest: event => onResponderTerminationRequest?.(event) ?? true,
161
+ children: /*#__PURE__*/_jsx(View, {
162
+ collapsable: false,
163
+ style: [contentContainerStyle, (contentStyle?.flexGrow ?? 0) > 0 && (contentStyle?.minHeight === undefined || typeof contentStyle.minHeight === "number") ? {
164
+ minHeight: Math.max(contentViewportHeight, contentStyle?.minHeight ?? 0)
165
+ } : undefined, styles.content],
166
+ onLayout: event => {
167
+ const {
168
+ width,
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);
180
+ },
181
+ children: children
182
+ })
23
183
  });
24
184
  });
25
185
  NativeScroller.displayName = "NativeScroller";