@onekeyfe/react-native-pager-view 3.0.172 → 3.0.174

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.
@@ -345,6 +345,8 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
345
345
  {
346
346
  if (!_momentumActive) return;
347
347
  _momentumActive = NO;
348
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"OneKeyNativeListScrollCommandAccepted"
349
+ object:_scrollView userInfo:@{@"phase": @2}];
348
350
  [self emitScrollEvent:4 velocity:CGPointZero target:_scrollView.contentOffset];
349
351
  }
350
352
 
@@ -443,11 +445,15 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
443
445
  MAX(-inset.top, MIN(y,
444
446
  MAX(-inset.top, _scrollView.contentSize.height - _scrollView.bounds.size.height + inset.bottom))));
445
447
  if (CGPointEqualToPoint(target, _scrollView.contentOffset)) return;
448
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"OneKeyNativeListScrollCommandAccepted"
449
+ object:_scrollView userInfo:@{@"phase": @1}];
446
450
  if (animated) {
447
451
  _momentumActive = YES;
448
452
  [self emitScrollEvent:3 velocity:CGPointZero target:target];
449
453
  }
450
454
  [_scrollView setContentOffset:target animated:animated];
455
+ if (!animated) [[NSNotificationCenter defaultCenter] postNotificationName:@"OneKeyNativeListScrollCommandAccepted"
456
+ object:_scrollView userInfo:@{@"phase": @2}];
451
457
  }
452
458
 
453
459
  - (void)scrollToEnd:(BOOL)animated
@@ -18,6 +18,7 @@ using namespace facebook::react;
18
18
  - (void)beginPageStateTransition;
19
19
  - (void)updatePageStateTransitionProgress:(CGFloat)progress;
20
20
  - (void)finishPageStateTransition;
21
+ - (void)scrollToTop:(NSInteger)target animated:(BOOL)animated;
21
22
  - (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)index;
22
23
  - (void)applyInsetsToScrollView:(UIScrollView *)scrollView pageIndex:(NSInteger)index restore:(BOOL)restore;
23
24
  @end
@@ -39,6 +40,7 @@ using namespace facebook::react;
39
40
  RNCNativeScrollerComponentView *scroller = [self addScrollerToPager:pager atIndex:0];
40
41
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
41
42
  offsets[@"page-0"] = @660;
43
+ [pager setValue:@120 forKey:@"headerOffset"];
42
44
  scroller.scrollView.contentSize = CGSizeMake(0, 0);
43
45
  [pager attachScrollObserverForCurrentPage];
44
46
  XCTAssertTrue([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
@@ -58,14 +60,14 @@ using namespace facebook::react;
58
60
  else [scroller scrollTo:0 y:-scroller.scrollView.adjustedContentInset.top animated:NO];
59
61
  XCTAssertEqualWithAccuracy(scroller.scrollView.contentOffset.y + scroller.scrollView.contentInset.top, command.integerValue == 2 ? 120 : 0, 0.01);
60
62
  XCTAssertFalse([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
61
- XCTAssertEqualWithAccuracy([offsets[@"page-0"] doubleValue], command.integerValue == 2 ? 120 : 0, 0.01);
63
+ XCTAssertEqualWithAccuracy([offsets[@"page-0"] doubleValue], 0, 0.01);
62
64
  XCTAssertEqual([pager valueForKey:@"observedScrollView"], scroller.scrollView);
63
65
  XCTAssertEqualObjects([pager valueForKey:@"observedPageKey"], @"page-0");
64
66
  }
65
67
  // The existing observed content-size path retries the pending restoration.
66
68
  scroller.scrollView.contentSize = CGSizeMake(320, 1500);
67
69
  XCTAssertFalse([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
68
- CGFloat expected = command.integerValue == 2 ? 120 : (command.boolValue ? 0 : 660);
70
+ CGFloat expected = command.integerValue == 2 ? 120 : (command.boolValue ? 0 : 780);
69
71
  XCTAssertEqualWithAccuracy(scroller.scrollView.contentOffset.y + scroller.scrollView.contentInset.top,
70
72
  expected, 0.01, @"command=%@ must not replay a cancelled reading position", command);
71
73
  XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], command.integerValue == 1 ? 0 : 120, 0.01);
@@ -80,6 +82,7 @@ using namespace facebook::react;
80
82
  RNCNativeScrollerComponentView *other = [self addScrollerToPager:pager atIndex:1];
81
83
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
82
84
  offsets[@"page-0"] = @660;
85
+ [pager setValue:@120 forKey:@"headerOffset"];
83
86
  offsets[@"page-1"] = @360;
84
87
  current.scrollView.contentSize = CGSizeMake(0, 0);
85
88
  [pager attachScrollObserverForCurrentPage];
@@ -103,21 +106,51 @@ using namespace facebook::react;
103
106
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
104
107
  offsets[@"page-0"] = @0;
105
108
  offsets[@"page-1"] = @360;
109
+ [pager setValue:@120 forKey:@"headerOffset"];
106
110
  [pager attachScrollObserverForCurrentPage];
107
111
  XCTAssertFalse([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
108
112
  [current scrollTo:0 y:200 - current.scrollView.contentInset.top animated:NO];
109
113
  XCTAssertEqualWithAccuracy(current.scrollView.contentOffset.y + current.scrollView.contentInset.top, 200, 0.01);
110
- XCTAssertEqualWithAccuracy([offsets[@"page-0"] doubleValue], 200, 0.01);
114
+ XCTAssertEqualWithAccuracy([offsets[@"page-0"] doubleValue], 80, 0.01);
111
115
  XCTAssertEqualObjects(offsets[@"page-1"], @360);
112
116
  }
113
117
 
118
+ - (void)testPagerTopCommandsClearOnlyCurrentReadingMemoryInBothModes
119
+ {
120
+ for (NSNumber *smooth in @[@NO, @YES]) {
121
+ for (NSNumber *target in @[@0, @1]) {
122
+ RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:2];
123
+ [pager setValue:smooth forKey:@"nativeSmoothHeaderScrollEnabled"];
124
+ RNCNativeScrollerComponentView *current = [self addScrollerToPager:pager atIndex:0];
125
+ [self addScrollerToPager:pager atIndex:1];
126
+ NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
127
+ offsets[@"page-0"] = smooth.boolValue ? @660 : @780;
128
+ offsets[@"page-1"] = @360;
129
+ [pager setValue:@120 forKey:@"headerOffset"];
130
+ [pager attachScrollObserverForCurrentPage];
131
+ [pager scrollToTop:target.integerValue animated:NO];
132
+ CGFloat expectedQ = target.boolValue ? 120 : 0;
133
+ XCTAssertEqualWithAccuracy(current.scrollView.contentOffset.y + current.scrollView.contentInset.top, expectedQ, 0.01);
134
+ XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], expectedQ, 0.01);
135
+ XCTAssertEqualWithAccuracy([offsets[@"page-0"] doubleValue], smooth.boolValue ? 0 : expectedQ, 0.01);
136
+ XCTAssertEqualObjects(offsets[@"page-1"], @360);
137
+ XCTAssertFalse([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
138
+ XCTAssertNil([pager valueForKey:@"pagerTopScrollView"]);
139
+ // A same-target call cannot resurrect the old reading position.
140
+ [pager scrollToTop:target.integerValue animated:NO];
141
+ XCTAssertEqualWithAccuracy(current.scrollView.contentOffset.y + current.scrollView.contentInset.top, expectedQ, 0.01);
142
+ XCTAssertEqualObjects(offsets[@"page-1"], @360);
143
+ }
144
+ }
145
+ }
146
+
114
147
  - (void)testTransitionLayoutCannotSaveSourceOffsetIntoDestinationCache
115
148
  {
116
149
  RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:2];
117
150
  RNCNativeScrollerComponentView *source = [self addScrollerToPager:pager atIndex:0];
118
151
  RNCNativeScrollerComponentView *target = [self addScrollerToPager:pager atIndex:1];
119
152
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
120
- offsets[@"page-0"] = @120;
153
+ offsets[@"page-0"] = @0;
121
154
  offsets[@"page-1"] = @660;
122
155
  [pager setValue:@120 forKey:@"headerOffset"];
123
156
  [pager attachScrollObserverForCurrentPage];
@@ -131,49 +164,50 @@ using namespace facebook::react;
131
164
  [pager setValue:@1 forKey:@"currentIndex"];
132
165
  [pager attachScrollObserverForCurrentPageAfterTransition:YES];
133
166
  XCTAssertEqualWithAccuracy([offsets[@"page-1"] doubleValue], 660, 0.01);
134
- XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, 660, 0.01);
167
+ XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, 780, 0.01);
135
168
  XCTAssertNotEqual([pager valueForKey:@"observedScrollView"], source.scrollView);
136
169
  }
137
170
 
138
- - (void)testSmoothTransitionRestoresWholePagePositionsAndCancellation
171
+ - (void)testSmoothTransitionKeepsGlobalHeaderAndIndependentBodyMemory
139
172
  {
140
- for (NSNumber *targetQ in @[@0, @60, @660]) {
141
- RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:2];
142
- RNCNativeScrollerComponentView *source = [self addScrollerToPager:pager atIndex:0];
143
- RNCNativeScrollerComponentView *target = [self addScrollerToPager:pager atIndex:1];
144
- NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
145
- offsets[@"page-0"] = @0;
146
- offsets[@"page-1"] = targetQ;
147
- [pager attachScrollObserverForCurrentPage];
148
- [pager beginPageStateTransition];
149
- [pager setValue:@YES forKey:@"isPagerDragging"];
150
- [pager setValue:@YES forKey:@"transitioning"];
151
- [pager updatePageStateTransitionProgress:0.5];
152
- CGFloat displayH = MIN(targetQ.doubleValue, 120) / 2;
153
- XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], displayH, 0.01);
154
- XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top, displayH, 0.01);
155
- XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top,
156
- MAX(targetQ.doubleValue - 120, 0) + displayH, 0.01);
157
- XCTAssertEqualObjects(offsets[@"page-0"], @0);
158
- XCTAssertEqualObjects(offsets[@"page-1"], targetQ);
159
- // Cancelling the swipe restores both pages, not just the selected source.
160
- [pager finishPageStateTransition];
161
- XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top, 0, 0.01);
162
- XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, targetQ.doubleValue, 0.01);
163
- XCTAssertEqualObjects(offsets[@"page-1"], targetQ);
164
- [pager setValue:@NO forKey:@"isPagerDragging"];
165
- [pager setValue:@NO forKey:@"transitioning"];
166
- [pager attachScrollObserverForCurrentPage];
167
- [pager beginPageStateTransition];
168
- [pager setValue:@1 forKey:@"destinationIndex"];
169
- [pager preparePageForHorizontalTransitionAtIndex:1];
170
- [pager updatePageStateTransitionProgress:1];
171
- XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top,
172
- MIN(targetQ.doubleValue, 120), 0.01);
173
- [pager setValue:@1 forKey:@"currentIndex"];
174
- [pager attachScrollObserverForCurrentPageAfterTransition:YES];
175
- XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, targetQ.doubleValue, 0.01);
176
- XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], MIN(targetQ.doubleValue, 120), 0.01);
173
+ for (NSNumber *collapse in @[@0, @60, @120]) {
174
+ for (NSNumber *body in @[@0, @60, @660]) {
175
+ RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:2];
176
+ RNCNativeScrollerComponentView *source = [self addScrollerToPager:pager atIndex:0];
177
+ RNCNativeScrollerComponentView *target = [self addScrollerToPager:pager atIndex:1];
178
+ NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
179
+ offsets[@"page-0"] = @0;
180
+ offsets[@"page-1"] = body;
181
+ [pager setValue:collapse forKey:@"headerOffset"];
182
+ [pager attachScrollObserverForCurrentPage];
183
+ [pager beginPageStateTransition];
184
+ [pager setValue:@YES forKey:@"isPagerDragging"];
185
+ [pager setValue:@YES forKey:@"transitioning"];
186
+ [pager updatePageStateTransitionProgress:0.5];
187
+ CGFloat targetQ = collapse.doubleValue + (collapse.doubleValue == 120 ? body.doubleValue : 0);
188
+ XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], collapse.doubleValue, 0.01);
189
+ XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top, collapse.doubleValue, 0.01);
190
+ XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, targetQ, 0.01);
191
+ XCTAssertEqualObjects(offsets[@"page-0"], @0);
192
+ XCTAssertEqualObjects(offsets[@"page-1"], body);
193
+ // Cancellation restores the source display without consuming either reading memory.
194
+ [pager finishPageStateTransition];
195
+ XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top, collapse.doubleValue, 0.01);
196
+ XCTAssertEqualObjects(offsets[@"page-1"], body);
197
+ [pager setValue:@NO forKey:@"isPagerDragging"];
198
+ [pager setValue:@NO forKey:@"transitioning"];
199
+ [pager attachScrollObserverForCurrentPage];
200
+ [pager beginPageStateTransition];
201
+ [pager setValue:@1 forKey:@"destinationIndex"];
202
+ [pager preparePageForHorizontalTransitionAtIndex:1];
203
+ [pager updatePageStateTransitionProgress:1];
204
+ XCTAssertEqualWithAccuracy(source.scrollView.contentOffset.y + source.scrollView.contentInset.top, collapse.doubleValue, 0.01);
205
+ [pager setValue:@1 forKey:@"currentIndex"];
206
+ [pager attachScrollObserverForCurrentPageAfterTransition:YES];
207
+ XCTAssertEqualWithAccuracy(target.scrollView.contentOffset.y + target.scrollView.contentInset.top, targetQ, 0.01);
208
+ XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], collapse.doubleValue, 0.01);
209
+ XCTAssertEqualObjects(offsets[@"page-1"], body);
210
+ }
177
211
  }
178
212
  }
179
213
 
@@ -186,12 +220,13 @@ using namespace facebook::react;
186
220
  offsets[@"page-1"] = @60;
187
221
  offsets[@"page-2"] = @360;
188
222
  offsets[@"page-3"] = @660;
223
+ [pager setValue:@60 forKey:@"headerOffset"];
189
224
  [pager attachScrollObserverForCurrentPage];
190
225
  [pager beginPageStateTransition];
191
226
  [pager setValue:@3 forKey:@"destinationIndex"];
192
227
  [pager preparePageForHorizontalTransitionAtIndex:3];
193
228
  [pager updatePageStateTransitionProgress:1];
194
- XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], 40, 0.01);
229
+ XCTAssertEqualWithAccuracy([[pager valueForKey:@"headerOffset"] doubleValue], 60, 0.01);
195
230
  [pager setValue:@3 forKey:@"currentIndex"];
196
231
  [pager attachScrollObserverForCurrentPageAfterTransition:YES];
197
232
  XCTAssertEqualObjects(offsets[@"page-1"], @60);
@@ -200,23 +235,23 @@ using namespace facebook::react;
200
235
  for (NSInteger index = 1; index < 4; index++) {
201
236
  UIViewController *page = [pager valueForKey:@"pageControllers"][index];
202
237
  RNCNativeScrollerComponentView *scroller = page.view.subviews.firstObject;
203
- NSString *key = [NSString stringWithFormat:@"page-%ld", (long)index];
204
238
  XCTAssertEqualWithAccuracy(scroller.scrollView.contentOffset.y + scroller.scrollView.contentInset.top,
205
- [offsets[key] doubleValue], 0.01);
239
+ 60, 0.01);
206
240
  }
207
241
  }
208
242
 
209
- - (void)testZeroLayoutPreservesPendingWholePageRestore
243
+ - (void)testZeroLayoutPreservesPendingBodyRestore
210
244
  {
211
245
  RNCCollapsiblePagerViewComponentView *pager = [self pagerWithPageCount:1];
212
246
  RNCNativeScrollerComponentView *scroller = [self addScrollerToPager:pager atIndex:0];
213
247
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
214
248
  offsets[@"page-0"] = @660;
249
+ [pager setValue:@120 forKey:@"headerOffset"];
215
250
  scroller.scrollView.contentSize = CGSizeMake(0, 0);
216
251
  [pager attachScrollObserverForCurrentPage];
217
252
  XCTAssertEqualObjects(offsets[@"page-0"], @660);
218
253
  XCTAssertTrue([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
219
- // Releasing an observed but unmeasured identity must retain its pending Q.
254
+ // Releasing an observed but unmeasured identity must retain its pending B.
220
255
  scroller.tag = 91001;
221
256
  ShadowView removedView;
222
257
  removedView.tag = (Tag)scroller.tag;
@@ -229,7 +264,7 @@ using namespace facebook::react;
229
264
  scroller.scrollView.contentSize = CGSizeMake(320, 1500);
230
265
  XCTAssertEqualObjects(offsets[@"page-0"], @660);
231
266
  XCTAssertFalse([[pager valueForKey:@"pendingPageRestoreKeys"] containsObject:@"page-0"]);
232
- XCTAssertEqualWithAccuracy(scroller.scrollView.contentOffset.y + scroller.scrollView.contentInset.top, 660, 0.01);
267
+ XCTAssertEqualWithAccuracy(scroller.scrollView.contentOffset.y + scroller.scrollView.contentInset.top, 780, 0.01);
233
268
  }
234
269
 
235
270
  - (void)testUnresolvedReplacementDoesNotCommitTemporaryClamp
@@ -239,6 +274,7 @@ using namespace facebook::react;
239
274
  NSMutableDictionary *offsets = [pager valueForKey:@"pageOffsets"];
240
275
  offsets[@"page-0"] = @0;
241
276
  offsets[@"page-1"] = @660;
277
+ [pager setValue:@120 forKey:@"headerOffset"];
242
278
  [pager attachScrollObserverForCurrentPage];
243
279
  [pager beginPageStateTransition];
244
280
  [pager setValue:@1 forKey:@"destinationIndex"];
@@ -1,6 +1,7 @@
1
1
  #import <XCTest/XCTest.h>
2
2
  #import <React/RCTViewComponentView.h>
3
3
  #import "RNCCollapsiblePagerViewComponentView.h"
4
+ #import "RNCNativeScrollerComponentView.h"
4
5
 
5
6
  @interface RNCCollapsiblePagerViewComponentView (RefreshControlTesting)
6
7
  - (void)updateRefreshControlForScrollView:(UIScrollView *)scrollView;
@@ -8,6 +9,10 @@
8
9
  - (void)attachScrollObserverForCurrentPage;
9
10
  @end
10
11
 
12
+ @interface RNCNativeScrollerComponentView (RefreshControlScrollCommandTesting)
13
+ - (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated;
14
+ @end
15
+
11
16
  @interface RNCCollapsiblePagerRefreshControlTests : XCTestCase
12
17
  @end
13
18
 
@@ -184,14 +189,18 @@
184
189
  RNCCollapsiblePagerViewComponentView *pager = [self pagerWithHeader:64];
185
190
  [pager mountChildComponentView:[RCTViewComponentView new] index:0];
186
191
  [pager mountChildComponentView:[RCTViewComponentView new] index:1];
187
- UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 600)];
192
+ RNCNativeScrollerComponentView *scroller = [RNCNativeScrollerComponentView new];
193
+ scroller.frame = CGRectMake(0, 0, 320, 600);
194
+ scroller.scrollView.frame = scroller.bounds;
195
+ UIScrollView *scroll = scroller.scrollView;
188
196
  scroll.refreshControl = [UIRefreshControl new];
189
197
  scroll.contentSize = CGSizeMake(320, 2000);
190
198
  scroll.contentInset = UIEdgeInsetsMake(232, 0, 0, 0);
191
199
  UIView *content = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 2000)];
192
200
  [scroll addSubview:content];
193
201
  UIViewController *page = [UIViewController new];
194
- [page.view addSubview:scroll];
202
+ [page.view addSubview:scroller];
203
+ [pager addSubview:page.view];
195
204
  NSMutableArray<UIViewController *> *pages = [pager valueForKey:@"pageControllers"];
196
205
  [pages addObject:page];
197
206
  [pager attachScrollObserverForCurrentPage];
@@ -204,7 +213,7 @@
204
213
  XCTAssertEqualWithAccuracy(scroll.contentInset.top, 340, 0.001);
205
214
  XCTAssertEqualWithAccuracy(headerHost.transform.ty, 0, 0.001);
206
215
 
207
- scroll.contentOffset = CGPointMake(0, 500);
216
+ [scroller scrollTo:0 y:500 animated:NO];
208
217
  CGFloat baselineStickyTranslation = stickyHeader.transform.ty;
209
218
  CGFloat baselineTabBarTranslation = tabBar.transform.ty;
210
219
  XCTAssertEqualWithAccuracy(baselineStickyTranslation, 776, 0.001);
@@ -217,11 +226,11 @@
217
226
  XCTAssertEqualWithAccuracy(tabBar.transform.ty, baselineTabBarTranslation, 0.001);
218
227
  XCTAssertEqualWithAccuracy(CGRectGetMaxY(headerHost.frame), CGRectGetMinY(content.frame), 0.001);
219
228
 
220
- // Exercise actual scroll notifications while the temporary refresh inset is
229
+ // Exercise accepted native commands while the temporary refresh inset is
221
230
  // present, including direction reversals and the sticky/unpinned boundary.
222
231
  // The pre-fix formula adds the extra 60 points on these scroll updates.
223
232
  for (NSNumber *nextOffset in @[@620, @450, @-300, @-340, @500]) {
224
- scroll.contentOffset = CGPointMake(0, nextOffset.doubleValue);
233
+ [scroller scrollTo:0 y:nextOffset.doubleValue animated:NO];
225
234
  CGFloat expectedTranslation = MAX(0, nextOffset.doubleValue + 340 - 64);
226
235
  XCTAssertEqualWithAccuracy(headerHost.transform.ty, 0, 0.001);
227
236
  XCTAssertEqualWithAccuracy(stickyHeader.transform.ty, expectedTranslation, 0.001);
@@ -71,6 +71,12 @@ export class CollapsiblePagerView extends React.PureComponent {
71
71
  }
72
72
  }
73
73
  };
74
+ scrollToPageTop = options => {
75
+ if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 0, options?.animated ?? true);
76
+ };
77
+ scrollToStickyTop = options => {
78
+ if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 1, options?.animated ?? true);
79
+ };
74
80
  setScrollEnabled = scrollEnabled => {
75
81
  if (this.nativeRef) {
76
82
  CollapsiblePagerViewNativeCommands.setScrollEnabledImperatively(this.nativeRef, scrollEnabled);
@@ -47,6 +47,7 @@ export class CollapsiblePagerView extends React.PureComponent {
47
47
  scrollEnabled = this.props.scrollEnabled ?? true;
48
48
  lastMountSignature = null;
49
49
  configureFrame = null;
50
+ pendingTopFrame = null;
50
51
  transitionTimer = null;
51
52
  pageScrollState = "idle";
52
53
  configuredScrollElement = null;
@@ -70,6 +71,7 @@ export class CollapsiblePagerView extends React.PureComponent {
70
71
  const pageCount = this.pages().length;
71
72
  const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
72
73
  if (selectedPage !== this.state.selectedPage) {
74
+ this.cancelPendingTopFrame();
73
75
  this.finishPageTransition();
74
76
  this.setState({
75
77
  selectedPage,
@@ -81,6 +83,7 @@ export class CollapsiblePagerView extends React.PureComponent {
81
83
  this.notifyMountedPagesChanged();
82
84
  }
83
85
  componentWillUnmount() {
86
+ this.cancelPendingTopFrame();
84
87
  this.savePageOffset(this.state.selectedPage);
85
88
  if (this.configureFrame !== null) {
86
89
  cancelAnimationFrame(this.configureFrame);
@@ -99,6 +102,55 @@ export class CollapsiblePagerView extends React.PureComponent {
99
102
  setPageWithoutAnimation = selectedPage => {
100
103
  this.selectPage(selectedPage, false);
101
104
  };
105
+ scrollToPageTop = options => {
106
+ this.scrollCurrentPageToTop(0, options?.animated ?? true);
107
+ };
108
+ scrollToStickyTop = options => {
109
+ this.scrollCurrentPageToTop(Math.max(0, this.props.headerHeight), options?.animated ?? true);
110
+ };
111
+ cancelPendingTopFrame = () => {
112
+ if (!this.pendingTopFrame) return;
113
+ const pending = this.pendingTopFrame;
114
+ cancelAnimationFrame(pending.frame);
115
+ this.pendingTopFrame = null;
116
+ if (this.pageScrollElement(this.state.selectedPage) === pending.element && this.pages().map(pageKey)[this.state.selectedPage] === pending.key) this.savePageOffset(this.state.selectedPage);
117
+ };
118
+ onQueuedTopWheel = event => {
119
+ if (event.deltaY !== 0) this.cancelPendingTopFrame();
120
+ };
121
+ onQueuedTopKey = event => {
122
+ if (["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key)) this.cancelPendingTopFrame();
123
+ };
124
+ scrollCurrentPageToTop(offset, animated) {
125
+ if (this.horizontalTouch || this.transitionTimer !== null) return;
126
+ const element = this.pageScrollElement(this.state.selectedPage);
127
+ const key = this.pages().map(pageKey)[this.state.selectedPage];
128
+ if (!element?.isConnected || !key) return;
129
+ this.cancelPendingTopFrame();
130
+ const EventConstructor = element.ownerDocument.defaultView?.Event;
131
+ if (EventConstructor) element.dispatchEvent(new EventConstructor("ok-collapsible-pager-cancel-scroll-command"));
132
+ element.scrollTo({
133
+ left: element.scrollLeft,
134
+ top: element.scrollTop,
135
+ behavior: "instant"
136
+ });
137
+ const frame = requestAnimationFrame(() => {
138
+ if (this.pendingTopFrame?.frame !== frame) return;
139
+ this.pendingTopFrame = null;
140
+ if (!element.isConnected || this.horizontalTouch || this.transitionTimer !== null || this.pageScrollElement(this.state.selectedPage) !== element || this.pages().map(pageKey)[this.state.selectedPage] !== key) return;
141
+ this.pageOffsets.set(key, offset);
142
+ this.sharedHeaderOffset = offset;
143
+ element.scrollTo({
144
+ top: offset,
145
+ behavior: animated ? "smooth" : "instant"
146
+ });
147
+ });
148
+ this.pendingTopFrame = {
149
+ frame,
150
+ element,
151
+ key
152
+ };
153
+ }
102
154
  setScrollEnabled = scrollEnabled => {
103
155
  this.scrollEnabled = scrollEnabled;
104
156
  };
@@ -165,8 +217,14 @@ export class CollapsiblePagerView extends React.PureComponent {
165
217
  restoreConfiguredScrollElement() {
166
218
  const configured = this.configuredScrollElement;
167
219
  if (!configured) return;
220
+ this.cancelPendingTopFrame();
221
+ configured.element.removeEventListener("ok-native-list-scroll-command-accepted", this.cancelPendingTopFrame);
222
+ configured.element.removeEventListener("wheel", this.onQueuedTopWheel);
223
+ configured.element.removeEventListener("keydown", this.onQueuedTopKey);
168
224
  configured.element.removeEventListener("scrollend", this.onVerticalScrollSettled);
169
225
  const style = timelineStyle(configured.element);
226
+ configured.rangeSpacer.remove();
227
+ style.position = configured.position;
170
228
  style.paddingTop = configured.paddingTop;
171
229
  style.boxSizing = configured.boxSizing;
172
230
  style.scrollTimelineName = configured.scrollTimelineName;
@@ -185,8 +243,23 @@ export class CollapsiblePagerView extends React.PureComponent {
185
243
  this.restoreConfiguredScrollElement();
186
244
  const style = timelineStyle(element);
187
245
  const computedPaddingTop = element.ownerDocument.defaultView?.getComputedStyle(element).paddingTop || style.paddingTop || "0px";
246
+ const rangeSpacer = element.ownerDocument.createElement("div");
247
+ Object.assign(rangeSpacer.style, {
248
+ position: "absolute",
249
+ top: "0",
250
+ left: "0",
251
+ width: "1px",
252
+ pointerEvents: "none",
253
+ opacity: "0"
254
+ });
255
+ rangeSpacer.setAttribute("aria-hidden", "true");
256
+ const position = style.position;
257
+ if (element.ownerDocument.defaultView?.getComputedStyle(element).position === "static") style.position = "relative";
258
+ element.appendChild(rangeSpacer);
188
259
  this.configuredScrollElement = {
189
260
  element,
261
+ rangeSpacer,
262
+ position,
190
263
  paddingTop: style.paddingTop,
191
264
  basePaddingTop: computedPaddingTop,
192
265
  boxSizing: style.boxSizing,
@@ -196,10 +269,14 @@ export class CollapsiblePagerView extends React.PureComponent {
196
269
  pagerStickyInset: style.getPropertyValue(PAGER_STICKY_INSET)
197
270
  };
198
271
  element.addEventListener("scrollend", this.onVerticalScrollSettled);
272
+ element.addEventListener("ok-native-list-scroll-command-accepted", this.cancelPendingTopFrame);
273
+ element.addEventListener("wheel", this.onQueuedTopWheel);
274
+ element.addEventListener("keydown", this.onQueuedTopKey);
199
275
  }
200
276
  const style = timelineStyle(element);
201
277
  const headerInset = Math.max(0, this.props.headerHeight);
202
278
  const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
279
+ if (this.configuredScrollElement) this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
203
280
  const basePaddingTop = this.configuredScrollElement?.basePaddingTop ?? "0px";
204
281
  const paddingTop = `calc(${basePaddingTop} + ${headerInset + stickyInset}px)`;
205
282
  const headerInsetValue = `${headerInset}px`;
@@ -223,6 +300,7 @@ export class CollapsiblePagerView extends React.PureComponent {
223
300
  }
224
301
  }
225
302
  onVerticalScrollSettled = () => {
303
+ if (this.pendingTopFrame) return;
226
304
  this.savePageOffset(this.state.selectedPage);
227
305
  this.emitDiagnostics("scroll-settled");
228
306
  };
@@ -246,6 +324,7 @@ export class CollapsiblePagerView extends React.PureComponent {
246
324
  if (position === null || position < 0 || position >= pages.length || position === this.state.selectedPage) {
247
325
  return;
248
326
  }
327
+ this.cancelPendingTopFrame();
249
328
  this.savePageOffset(this.state.selectedPage);
250
329
  if (animated) this.beginPageTransition();else this.finishPageTransition();
251
330
  this.setState({
@@ -313,7 +392,9 @@ export class CollapsiblePagerView extends React.PureComponent {
313
392
  if (!touch) return;
314
393
  const deltaX = touch.pageX - this.touchStartX;
315
394
  const deltaY = touch.pageY - (this.touchStartY ?? touch.pageY);
395
+ if (Math.abs(deltaY) >= 8 && Math.abs(deltaY) > Math.abs(deltaX)) this.cancelPendingTopFrame();
316
396
  if (Math.abs(deltaX) >= 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
397
+ this.cancelPendingTopFrame();
317
398
  this.horizontalTouch = true;
318
399
  this.emitPageScrollState("dragging");
319
400
  }
@@ -83,6 +83,11 @@ export interface NativeProps extends ViewProps {
83
83
  type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
84
84
 
85
85
  export interface NativeCommands {
86
+ scrollToTop: (
87
+ viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
88
+ target: Int32,
89
+ animated: boolean
90
+ ) => void;
86
91
  setPage: (
87
92
  viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
88
93
  selectedPage: Int32
@@ -99,6 +104,7 @@ export interface NativeCommands {
99
104
 
100
105
  export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
101
106
  supportedCommands: [
107
+ "scrollToTop",
102
108
  "setPage",
103
109
  "setPageWithoutAnimation",
104
110
  "setScrollEnabledImperatively",
@@ -113,6 +113,12 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
113
113
  componentDidUpdate(): void;
114
114
  setPage: (selectedPage: number) => void;
115
115
  setPageWithoutAnimation: (selectedPage: number) => void;
116
+ scrollToPageTop: (options?: Readonly<{
117
+ animated?: boolean;
118
+ }>) => void;
119
+ scrollToStickyTop: (options?: Readonly<{
120
+ animated?: boolean;
121
+ }>) => void;
116
122
  setScrollEnabled: (scrollEnabled: boolean) => void;
117
123
  private pages;
118
124
  private nativeTabBarEnabled;
@@ -62,6 +62,7 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
62
62
  private scrollEnabled;
63
63
  private lastMountSignature;
64
64
  private configureFrame;
65
+ private pendingTopFrame;
65
66
  private transitionTimer;
66
67
  private pageScrollState;
67
68
  private configuredScrollElement;
@@ -71,6 +72,16 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
71
72
  componentWillUnmount(): void;
72
73
  setPage: (selectedPage: number) => void;
73
74
  setPageWithoutAnimation: (selectedPage: number) => void;
75
+ scrollToPageTop: (options?: Readonly<{
76
+ animated?: boolean;
77
+ }>) => void;
78
+ scrollToStickyTop: (options?: Readonly<{
79
+ animated?: boolean;
80
+ }>) => void;
81
+ private cancelPendingTopFrame;
82
+ private onQueuedTopWheel;
83
+ private onQueuedTopKey;
84
+ private scrollCurrentPageToTop;
74
85
  setScrollEnabled: (scrollEnabled: boolean) => void;
75
86
  private pages;
76
87
  private retentionDistance;
@@ -62,6 +62,7 @@ export interface NativeProps extends ViewProps {
62
62
  }
63
63
  type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
64
64
  export interface NativeCommands {
65
+ scrollToTop: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, target: Int32, animated: boolean) => void;
65
66
  setPage: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
66
67
  setPageWithoutAnimation: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
67
68
  setScrollEnabledImperatively: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, scrollEnabled: boolean) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-pager-view",
3
- "version": "3.0.172",
3
+ "version": "3.0.174",
4
4
  "description": "React Native wrapper for Android and iOS ViewPager",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -72,7 +72,7 @@
72
72
  "typescript": "^5.9.2"
73
73
  },
74
74
  "peerDependencies": {
75
- "@onekeyfe/react-native-native-logger": "3.0.172",
75
+ "@onekeyfe/react-native-native-logger": "3.0.174",
76
76
  "react": "*",
77
77
  "react-native": "*"
78
78
  },
@@ -250,6 +250,24 @@ export class CollapsiblePagerView extends React.PureComponent<
250
250
  }
251
251
  };
252
252
 
253
+ public scrollToPageTop = (options?: Readonly<{ animated?: boolean }>) => {
254
+ if (this.nativeRef)
255
+ CollapsiblePagerViewNativeCommands.scrollToTop(
256
+ this.nativeRef,
257
+ 0,
258
+ options?.animated ?? true
259
+ );
260
+ };
261
+
262
+ public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
263
+ if (this.nativeRef)
264
+ CollapsiblePagerViewNativeCommands.scrollToTop(
265
+ this.nativeRef,
266
+ 1,
267
+ options?.animated ?? true
268
+ );
269
+ };
270
+
253
271
  public setScrollEnabled = (scrollEnabled: boolean) => {
254
272
  if (this.nativeRef) {
255
273
  CollapsiblePagerViewNativeCommands.setScrollEnabledImperatively(