@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.
@@ -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;
@@ -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",