@onekeyfe/react-native-pager-view 3.0.173 → 3.0.175

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.
@@ -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
  }
@@ -35,6 +35,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
35
35
  observedScrollableCount: Int32;
36
36
  retainedPages: string;
37
37
  reason: string;
38
+ pageKey: string;
39
+ headerSettledAtTop: boolean;
38
40
  }>;
39
41
 
40
42
  export type OnNativeTabPressEventData = Readonly<{
@@ -83,6 +85,11 @@ export interface NativeProps extends ViewProps {
83
85
  type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
84
86
 
85
87
  export interface NativeCommands {
88
+ scrollToTop: (
89
+ viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
90
+ target: Int32,
91
+ animated: boolean
92
+ ) => void;
86
93
  setPage: (
87
94
  viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
88
95
  selectedPage: Int32
@@ -99,6 +106,7 @@ export interface NativeCommands {
99
106
 
100
107
  export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
101
108
  supportedCommands: [
109
+ "scrollToTop",
102
110
  "setPage",
103
111
  "setPageWithoutAnimation",
104
112
  "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;
@@ -19,6 +19,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
19
19
  observedScrollableCount: Int32;
20
20
  retainedPages: string;
21
21
  reason: string;
22
+ pageKey: string;
23
+ headerSettledAtTop: boolean;
22
24
  }>;
23
25
  export type OnNativeTabPressEventData = Readonly<{
24
26
  position: Int32;
@@ -62,6 +64,7 @@ export interface NativeProps extends ViewProps {
62
64
  }
63
65
  type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
64
66
  export interface NativeCommands {
67
+ scrollToTop: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, target: Int32, animated: boolean) => void;
65
68
  setPage: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
66
69
  setPageWithoutAnimation: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
67
70
  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.173",
3
+ "version": "3.0.175",
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.173",
75
+ "@onekeyfe/react-native-native-logger": "3.0.175",
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(
@@ -79,6 +79,8 @@ type State = {
79
79
 
80
80
  type ConfiguredScrollElement = Readonly<{
81
81
  element: HTMLElement;
82
+ rangeSpacer: HTMLElement;
83
+ position: string;
82
84
  paddingTop: string;
83
85
  basePaddingTop: string;
84
86
  boxSizing: string;
@@ -100,10 +102,7 @@ const pageIndex = (value: number) => {
100
102
  };
101
103
 
102
104
  const clampedPageIndex = (value: number, pageCount: number) =>
103
- Math.min(
104
- Math.max(0, pageIndex(value) ?? 0),
105
- Math.max(0, pageCount - 1)
106
- );
105
+ Math.min(Math.max(0, pageIndex(value) ?? 0), Math.max(0, pageCount - 1));
107
106
 
108
107
  const webElement = (node: unknown) => {
109
108
  const element = node as HTMLElement | null;
@@ -158,6 +157,11 @@ export class CollapsiblePagerView extends React.PureComponent<
158
157
  private scrollEnabled = this.props.scrollEnabled ?? true;
159
158
  private lastMountSignature: string | null = null;
160
159
  private configureFrame: number | null = null;
160
+ private pendingTopFrame: Readonly<{
161
+ frame: number;
162
+ element: HTMLElement;
163
+ key: string;
164
+ }> | null = null;
161
165
  private transitionTimer: ReturnType<typeof setTimeout> | null = null;
162
166
  private pageScrollState: OnPageScrollStateChangedEventData["pageScrollState"] =
163
167
  "idle";
@@ -184,6 +188,7 @@ export class CollapsiblePagerView extends React.PureComponent<
184
188
  const pageCount = this.pages().length;
185
189
  const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
186
190
  if (selectedPage !== this.state.selectedPage) {
191
+ this.cancelPendingTopFrame();
187
192
  this.finishPageTransition();
188
193
  this.setState({
189
194
  selectedPage,
@@ -196,6 +201,7 @@ export class CollapsiblePagerView extends React.PureComponent<
196
201
  }
197
202
 
198
203
  componentWillUnmount() {
204
+ this.cancelPendingTopFrame();
199
205
  this.savePageOffset(this.state.selectedPage);
200
206
  if (this.configureFrame !== null) {
201
207
  cancelAnimationFrame(this.configureFrame);
@@ -217,6 +223,85 @@ export class CollapsiblePagerView extends React.PureComponent<
217
223
  this.selectPage(selectedPage, false);
218
224
  };
219
225
 
226
+ public scrollToPageTop = (options?: Readonly<{ animated?: boolean }>) => {
227
+ this.scrollCurrentPageToTop(0, options?.animated ?? true);
228
+ };
229
+
230
+ public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
231
+ this.scrollCurrentPageToTop(
232
+ Math.max(0, this.props.headerHeight),
233
+ options?.animated ?? true
234
+ );
235
+ };
236
+
237
+ private cancelPendingTopFrame = () => {
238
+ if (!this.pendingTopFrame) return;
239
+ const pending = this.pendingTopFrame;
240
+ cancelAnimationFrame(pending.frame);
241
+ this.pendingTopFrame = null;
242
+ if (
243
+ this.pageScrollElement(this.state.selectedPage) === pending.element &&
244
+ this.pages().map(pageKey)[this.state.selectedPage] === pending.key
245
+ )
246
+ this.savePageOffset(this.state.selectedPage);
247
+ };
248
+
249
+ private onQueuedTopWheel = (event: WheelEvent) => {
250
+ if (event.deltaY !== 0) this.cancelPendingTopFrame();
251
+ };
252
+
253
+ private onQueuedTopKey = (event: KeyboardEvent) => {
254
+ if (
255
+ [
256
+ "ArrowUp",
257
+ "ArrowDown",
258
+ "PageUp",
259
+ "PageDown",
260
+ "Home",
261
+ "End",
262
+ " ",
263
+ ].includes(event.key)
264
+ )
265
+ this.cancelPendingTopFrame();
266
+ };
267
+
268
+ private scrollCurrentPageToTop(offset: number, animated: boolean) {
269
+ if (this.horizontalTouch || this.transitionTimer !== null) return;
270
+ const element = this.pageScrollElement(this.state.selectedPage);
271
+ const key = this.pages().map(pageKey)[this.state.selectedPage];
272
+ if (!element?.isConnected || !key) return;
273
+ this.cancelPendingTopFrame();
274
+ const EventConstructor = element.ownerDocument.defaultView?.Event;
275
+ if (EventConstructor)
276
+ element.dispatchEvent(
277
+ new EventConstructor("ok-collapsible-pager-cancel-scroll-command")
278
+ );
279
+ element.scrollTo({
280
+ left: element.scrollLeft,
281
+ top: element.scrollTop,
282
+ behavior: "instant",
283
+ });
284
+ const frame = requestAnimationFrame(() => {
285
+ if (this.pendingTopFrame?.frame !== frame) return;
286
+ this.pendingTopFrame = null;
287
+ if (
288
+ !element.isConnected ||
289
+ this.horizontalTouch ||
290
+ this.transitionTimer !== null ||
291
+ this.pageScrollElement(this.state.selectedPage) !== element ||
292
+ this.pages().map(pageKey)[this.state.selectedPage] !== key
293
+ )
294
+ return;
295
+ this.pageOffsets.set(key, offset);
296
+ this.sharedHeaderOffset = offset;
297
+ element.scrollTo({
298
+ top: offset,
299
+ behavior: animated ? "smooth" : "instant",
300
+ });
301
+ });
302
+ this.pendingTopFrame = { frame, element, key };
303
+ }
304
+
220
305
  public setScrollEnabled = (scrollEnabled: boolean) => {
221
306
  this.scrollEnabled = scrollEnabled;
222
307
  };
@@ -278,10 +363,7 @@ export class CollapsiblePagerView extends React.PureComponent<
278
363
  private setTrack = (node: unknown) => {
279
364
  const track = webElement(node);
280
365
  if (track === this.track) return;
281
- this.track?.removeEventListener(
282
- "transitionend",
283
- this.onTrackTransitionEnd
284
- );
366
+ this.track?.removeEventListener("transitionend", this.onTrackTransitionEnd);
285
367
  this.track = track;
286
368
  this.track?.addEventListener("transitionend", this.onTrackTransitionEnd);
287
369
  };
@@ -307,11 +389,20 @@ export class CollapsiblePagerView extends React.PureComponent<
307
389
  private restoreConfiguredScrollElement() {
308
390
  const configured = this.configuredScrollElement;
309
391
  if (!configured) return;
392
+ this.cancelPendingTopFrame();
393
+ configured.element.removeEventListener(
394
+ "ok-native-list-scroll-command-accepted",
395
+ this.cancelPendingTopFrame
396
+ );
397
+ configured.element.removeEventListener("wheel", this.onQueuedTopWheel);
398
+ configured.element.removeEventListener("keydown", this.onQueuedTopKey);
310
399
  configured.element.removeEventListener(
311
400
  "scrollend",
312
401
  this.onVerticalScrollSettled
313
402
  );
314
403
  const style = timelineStyle(configured.element);
404
+ configured.rangeSpacer.remove();
405
+ style.position = configured.position;
315
406
  style.paddingTop = configured.paddingTop;
316
407
  style.boxSizing = configured.boxSizing;
317
408
  style.scrollTimelineName = configured.scrollTimelineName;
@@ -332,9 +423,30 @@ export class CollapsiblePagerView extends React.PureComponent<
332
423
  const style = timelineStyle(element);
333
424
  const computedPaddingTop =
334
425
  element.ownerDocument.defaultView?.getComputedStyle(element)
335
- .paddingTop || style.paddingTop || "0px";
426
+ .paddingTop ||
427
+ style.paddingTop ||
428
+ "0px";
429
+ const rangeSpacer = element.ownerDocument.createElement("div");
430
+ Object.assign(rangeSpacer.style, {
431
+ position: "absolute",
432
+ top: "0",
433
+ left: "0",
434
+ width: "1px",
435
+ pointerEvents: "none",
436
+ opacity: "0",
437
+ });
438
+ rangeSpacer.setAttribute("aria-hidden", "true");
439
+ const position = style.position;
440
+ if (
441
+ element.ownerDocument.defaultView?.getComputedStyle(element)
442
+ .position === "static"
443
+ )
444
+ style.position = "relative";
445
+ element.appendChild(rangeSpacer);
336
446
  this.configuredScrollElement = {
337
447
  element,
448
+ rangeSpacer,
449
+ position,
338
450
  paddingTop: style.paddingTop,
339
451
  basePaddingTop: computedPaddingTop,
340
452
  boxSizing: style.boxSizing,
@@ -344,13 +456,24 @@ export class CollapsiblePagerView extends React.PureComponent<
344
456
  pagerStickyInset: style.getPropertyValue(PAGER_STICKY_INSET),
345
457
  };
346
458
  element.addEventListener("scrollend", this.onVerticalScrollSettled);
459
+ element.addEventListener(
460
+ "ok-native-list-scroll-command-accepted",
461
+ this.cancelPendingTopFrame
462
+ );
463
+ element.addEventListener("wheel", this.onQueuedTopWheel);
464
+ element.addEventListener("keydown", this.onQueuedTopKey);
347
465
  }
348
466
 
349
467
  const style = timelineStyle(element);
350
468
  const headerInset = Math.max(0, this.props.headerHeight);
351
469
  const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
352
- const basePaddingTop = this.configuredScrollElement?.basePaddingTop ?? "0px";
353
- const paddingTop = `calc(${basePaddingTop} + ${headerInset + stickyInset}px)`;
470
+ if (this.configuredScrollElement)
471
+ this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
472
+ const basePaddingTop =
473
+ this.configuredScrollElement?.basePaddingTop ?? "0px";
474
+ const paddingTop = `calc(${basePaddingTop} + ${
475
+ headerInset + stickyInset
476
+ }px)`;
354
477
  const headerInsetValue = `${headerInset}px`;
355
478
  const stickyInsetValue = `${stickyInset}px`;
356
479
  const insetsChanged =
@@ -379,6 +502,7 @@ export class CollapsiblePagerView extends React.PureComponent<
379
502
  }
380
503
 
381
504
  private onVerticalScrollSettled = () => {
505
+ if (this.pendingTopFrame) return;
382
506
  this.savePageOffset(this.state.selectedPage);
383
507
  this.emitDiagnostics("scroll-settled");
384
508
  };
@@ -416,33 +540,37 @@ export class CollapsiblePagerView extends React.PureComponent<
416
540
  ) {
417
541
  return;
418
542
  }
543
+ this.cancelPendingTopFrame();
419
544
  this.savePageOffset(this.state.selectedPage);
420
545
 
421
546
  if (animated) this.beginPageTransition();
422
547
  else this.finishPageTransition();
423
- this.setState({ selectedPage: position, animateTransition: animated }, () => {
424
- this.scheduleScrollConfiguration();
425
- const restore = () => {
426
- this.configureActiveScrollElement();
427
- this.restorePageOffset(position);
428
- };
429
- if (typeof requestAnimationFrame === "function") {
430
- requestAnimationFrame(restore);
431
- } else {
432
- setTimeout(restore, 0);
548
+ this.setState(
549
+ { selectedPage: position, animateTransition: animated },
550
+ () => {
551
+ this.scheduleScrollConfiguration();
552
+ const restore = () => {
553
+ this.configureActiveScrollElement();
554
+ this.restorePageOffset(position);
555
+ };
556
+ if (typeof requestAnimationFrame === "function") {
557
+ requestAnimationFrame(restore);
558
+ } else {
559
+ setTimeout(restore, 0);
560
+ }
561
+ this.props.onPageSelected?.(
562
+ this.nativeEvent<OnPageSelectedEventData>({ position })
563
+ );
564
+ this.props.onPageScroll?.(
565
+ this.nativeEvent<OnPageScrollEventData>({
566
+ position,
567
+ offset: 0,
568
+ })
569
+ );
570
+ this.notifyMountedPagesChanged();
571
+ this.emitDiagnostics("page-selected");
433
572
  }
434
- this.props.onPageSelected?.(
435
- this.nativeEvent<OnPageSelectedEventData>({ position })
436
- );
437
- this.props.onPageScroll?.(
438
- this.nativeEvent<OnPageScrollEventData>({
439
- position,
440
- offset: 0,
441
- })
442
- );
443
- this.notifyMountedPagesChanged();
444
- this.emitDiagnostics("page-selected");
445
- });
573
+ );
446
574
  }
447
575
 
448
576
  private notifyMountedPagesChanged = () => {
@@ -500,7 +628,10 @@ export class CollapsiblePagerView extends React.PureComponent<
500
628
  if (!touch) return;
501
629
  const deltaX = touch.pageX - this.touchStartX;
502
630
  const deltaY = touch.pageY - (this.touchStartY ?? touch.pageY);
631
+ if (Math.abs(deltaY) >= 8 && Math.abs(deltaY) > Math.abs(deltaX))
632
+ this.cancelPendingTopFrame();
503
633
  if (Math.abs(deltaX) >= 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
634
+ this.cancelPendingTopFrame();
504
635
  this.horizontalTouch = true;
505
636
  this.emitPageScrollState("dragging");
506
637
  }
@@ -522,9 +653,12 @@ export class CollapsiblePagerView extends React.PureComponent<
522
653
  ((!this.props.layoutDirection ||
523
654
  this.props.layoutDirection === "locale") &&
524
655
  I18nManager.isRTL);
525
- const direction = Math.abs(delta) >= 40 && Math.abs(delta) > Math.abs(verticalDelta)
526
- ? (delta < 0 ? 1 : -1)
527
- : 0;
656
+ const direction =
657
+ Math.abs(delta) >= 40 && Math.abs(delta) > Math.abs(verticalDelta)
658
+ ? delta < 0
659
+ ? 1
660
+ : -1
661
+ : 0;
528
662
  const next = this.state.selectedPage + (rtl ? -direction : direction);
529
663
  if (direction === 0 || next < 0 || next >= this.pages().length) {
530
664
  const wasHorizontal = this.horizontalTouch;
@@ -35,6 +35,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
35
35
  observedScrollableCount: Int32;
36
36
  retainedPages: string;
37
37
  reason: string;
38
+ pageKey: string;
39
+ headerSettledAtTop: boolean;
38
40
  }>;
39
41
 
40
42
  export type OnNativeTabPressEventData = Readonly<{
@@ -83,6 +85,11 @@ export interface NativeProps extends ViewProps {
83
85
  type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
84
86
 
85
87
  export interface NativeCommands {
88
+ scrollToTop: (
89
+ viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
90
+ target: Int32,
91
+ animated: boolean
92
+ ) => void;
86
93
  setPage: (
87
94
  viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
88
95
  selectedPage: Int32
@@ -99,6 +106,7 @@ export interface NativeCommands {
99
106
 
100
107
  export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
101
108
  supportedCommands: [
109
+ "scrollToTop",
102
110
  "setPage",
103
111
  "setPageWithoutAnimation",
104
112
  "setScrollEnabledImperatively",