@onekeyfe/react-native-pager-view 3.0.176 → 3.0.178

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.
@@ -474,6 +474,14 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
474
474
  releaseRefreshForeground()
475
475
  val index = logicalChildren.indexOf(child)
476
476
  if (index < 0) return
477
+ if (index >= PAGE_SLOT_OFFSET) {
478
+ val observed = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller
479
+ val removingObservedPage = observed != null && isDescendant(observed, child)
480
+ cancelPageTransitionForPageMutation()
481
+ cancelPagerTop()
482
+ detachPrimaryScrollObserver(recordOffset = false)
483
+ if (removingObservedPage) observedPageRemoved = true
484
+ }
477
485
  logicalChildren.removeAt(index)
478
486
  when (index) {
479
487
  0 -> {
@@ -485,12 +493,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
485
493
  stickyHeaderView = null
486
494
  }
487
495
  else -> {
488
- val observed = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller
489
- if (observed != null && isDescendant(observed, child)) {
490
- // The page detaches only later; its scroller must not keep writing this key.
491
- detachPrimaryScrollObserver(recordOffset = false)
492
- observedPageRemoved = true
493
- }
494
496
  when (val scrollable =
495
497
  findExplicitNativeScrollerInView(child) ?: findVerticalScrollableView(child)) {
496
498
  is RecyclerView -> restoreRecyclerInsets(scrollable)
@@ -498,14 +500,17 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
498
500
  is NativeScrollerViewport -> restoreOwnedScrollerInsets(scrollable)
499
501
  }
500
502
  adapter.removePage(child)
503
+ selectedPage = if (adapter.itemCount == 0) 0 else selectedPage.coerceIn(0, adapter.itemCount - 1)
501
504
  log("page-detach slot=${index - PAGE_SLOT_OFFSET} nativePages=${adapter.itemCount}")
502
505
  }
503
506
  }
504
507
  }
505
508
 
506
509
  fun removeAllReactChildren() {
510
+ cancelPageTransitionForPageMutation()
511
+ cancelPagerTop()
507
512
  releaseRefreshForeground()
508
- detachPrimaryScrollObserver()
513
+ detachPrimaryScrollObserver(recordOffset = false)
509
514
  pageRestoreRequests.clear()
510
515
  pendingPageRestores.clear()
511
516
  commandBodyKeys.clear()
@@ -534,6 +539,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
534
539
  stickyHeaderView = null
535
540
  logicalChildren.clear()
536
541
  adapter.clear()
542
+ selectedPage = 0
537
543
  hasAppliedInitialPage = false
538
544
  log("pages-clear")
539
545
  }
@@ -542,9 +548,28 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
542
548
 
543
549
  fun reactChildAt(index: Int) = logicalChildren[index]
544
550
 
551
+ private fun cancelPageTransitionForPageMutation() {
552
+ if (transitionHeaderOffsets != null) {
553
+ val keys = transitionAlignedViews.values.toTypedArray().toMutableSet()
554
+ keys.add(pageKey(transitionSourcePage))
555
+ transitionTargetPage?.let { keys.add(pageKey(it)) }
556
+ for (key in keys) {
557
+ displayBodyOverrides.remove(key)
558
+ displayRecyclerAnchors.remove(key)
559
+ }
560
+ }
561
+ transitionHeaderOffsets = null
562
+ transitionPageOffsets = null
563
+ transitionTargetPage = null
564
+ transitionSourceRecyclerAnchor = null
565
+ transitionSourceDisplayBody = 0
566
+ transitionAlignedViews.clear()
567
+ resumeTransitionSource = null
568
+ }
569
+
545
570
  internal fun beginPageTransition(target: Int? = null) {
546
571
  cancelPagerTop()
547
- if (!nativeSmoothHeaderScrollEnabled || headerHeightPx <= 0) return
572
+ if (adapter.itemCount == 0 || !nativeSmoothHeaderScrollEnabled || headerHeightPx <= 0) return
548
573
  if (transitionHeaderOffsets == null) {
549
574
  transitionSourceDisplayBody = primaryScrollableForPage(selectedPage)?.let(::verticalScrollOffset) ?: 0
550
575
  transitionSourceRecyclerAnchor = (primaryScrollableForPage(selectedPage) as? RecyclerView)?.let(::captureRecyclerAnchor)
@@ -578,6 +603,11 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
578
603
  }
579
604
 
580
605
  private fun finishPageTransition(position: Int) {
606
+ if (adapter.itemCount == 0) {
607
+ cancelPageTransitionForPageMutation()
608
+ selectedPage = 0
609
+ return
610
+ }
581
611
  val headers = transitionHeaderOffsets ?: return
582
612
  detachPrimaryScrollObserver(recordOffset = false)
583
613
  releaseRefreshForeground()
package/docs/SPEC.md CHANGED
@@ -732,3 +732,9 @@ activation remain unverified.
732
732
  The Header is settled only when C is at its expanded top, the current attached view/key/index/generation still matches, and there is no actual drag, deceleration, scrolling command or horizontal transition. Offset/inset/content/header geometry MUST remain stable for 150ms before true is emitted. Motion or geometry changes revoke true promptly. Deferred reading memory alone MUST NOT be treated as active motion. Pull-to-refresh may settle at a normalized negative offset after drag, bounce and inset geometry stop; network refreshing alone is not a motion gate. Checks stop for collapsed, detached and recycled owners; delayed callbacks revalidate their captured identity. State changes are deduplicated; identity changes still publish an unknown/false snapshot.
733
733
 
734
734
  The native Home Spotlight adapter consumes this snapshot in the main UI runtime and keeps unknown states paused. It MUST use the mounted page-key mapping and reject old page/source callbacks. Native snapshots already include the 150ms stabilization delay: consumers MUST NOT add another such delay. Legacy Tabs keep their existing normalized-offset and debounce behavior. Anchor measurement, guide qualification and the existing display delay remain independent gates.
735
+
736
+ ### Android page mutation during horizontal transitions
737
+
738
+ Removing a page or clearing the page collection MUST cancel the active horizontal transition and its temporary display snapshots before notifying the adapter. Cancellation MUST NOT rewrite the shared Header collapse or surviving pages' saved body reading memory. The previous observed owner MUST be detached without committing an offset under a shifted page key.
739
+
740
+ An empty adapter MUST reject transition starts and transition completion without coercing an index into an empty range. The selected index uses zero as an empty-state sentinel; nonempty mutations clamp it to the remaining valid range. Readding pages MUST allow a fresh selection and MUST NOT resume a removed transition source. Conformance requires actual Android active-transition → zero-pages → readd/selection regression and remaining-page mutation checks; structural checks and compilation alone do not establish runtime behavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-pager-view",
3
- "version": "3.0.176",
3
+ "version": "3.0.178",
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.176",
75
+ "@onekeyfe/react-native-native-logger": "3.0.178",
76
76
  "react": "*",
77
77
  "react-native": "*"
78
78
  },