@onekeyfe/react-native-pager-view 6.7.0-20 → 6.7.0-22
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.
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +40 -9
- package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +8 -2
- package/docs/SPEC.md +44 -0
- package/ios/RNCCollapsiblePagerViewComponentView.mm +410 -19
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +36 -5
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +756 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +41 -46
- package/lib/module/NativeScroller.web.js +3 -0
- package/lib/module/NativeScrollerNativeComponent.ts +43 -46
- package/lib/module/PagerViewNativeComponent.ts +20 -25
- package/lib/module/WebCollapsiblePagerPage.js +7 -2
- package/lib/typescript/src/CollapsiblePagerView.d.ts +1 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +1 -1
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +41 -42
- package/lib/typescript/src/NativeScroller.d.ts +5 -3
- package/lib/typescript/src/NativeScroller.web.d.ts +1 -1
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +47 -48
- package/lib/typescript/src/PagerView.d.ts +1 -1
- package/lib/typescript/src/PagerView.web.d.ts +1 -1
- package/lib/typescript/src/PagerViewNativeComponent.d.ts +20 -21
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +1 -1
- package/lib/typescript/src/usePagerView.d.ts +2 -2
- package/lib/typescript/src/utils.d.ts +2 -2
- package/package.json +19 -7
- package/src/CollapsiblePagerViewNativeComponent.ts +41 -46
- package/src/NativeScroller.tsx +14 -3
- package/src/NativeScroller.web.tsx +15 -1
- package/src/NativeScrollerNativeComponent.ts +43 -46
- package/src/PagerViewNativeComponent.ts +20 -25
- package/src/WebCollapsiblePagerPage.tsx +7 -2
|
@@ -591,9 +591,29 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
591
591
|
cancelPagerTop()
|
|
592
592
|
if (adapter.itemCount == 0 || !nativeSmoothHeaderScrollEnabled || headerHeightPx <= 0) return
|
|
593
593
|
if (transitionHeaderOffsets == null) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
594
|
+
val sourceKey = currentPageKey()
|
|
595
|
+
val sourceView = primaryScrollableForPage(selectedPage)
|
|
596
|
+
val topInset = headerHeightPx + stickyHeaderHeightPx
|
|
597
|
+
val sourceRestored = sourceKey !in pendingPageRestores && when (sourceView) {
|
|
598
|
+
is RecyclerView -> restoredRecyclerKeys[sourceView] == sourceKey &&
|
|
599
|
+
originalRecyclerPadding[sourceView]?.appliedTopInset == topInset
|
|
600
|
+
is ScrollView -> restoredScrollerKeys[sourceView] == sourceKey &&
|
|
601
|
+
originalScrollerPadding[sourceView]?.appliedTopInset == topInset
|
|
602
|
+
is NativeScrollerViewport -> ownedScrollerKeys[sourceView] == sourceKey
|
|
603
|
+
else -> false
|
|
604
|
+
}
|
|
605
|
+
transitionSourceDisplayBody = if (sourceRestored && sourceView != null) {
|
|
606
|
+
verticalScrollOffset(sourceView)
|
|
607
|
+
} else {
|
|
608
|
+
displayBodyOverrides[sourceKey] ?: pageOffsets[sourceKey] ?: 0
|
|
609
|
+
}
|
|
610
|
+
transitionSourceRecyclerAnchor = if (sourceRestored && sourceView is RecyclerView) {
|
|
611
|
+
captureRecyclerAnchor(sourceView)
|
|
612
|
+
} else {
|
|
613
|
+
(displayRecyclerAnchors[sourceKey] ?: recyclerReadingAnchors[sourceKey])
|
|
614
|
+
?.takeIf { sourceView !is RecyclerView || (it.view.get() === sourceView && it.adapter === sourceView.adapter) }
|
|
615
|
+
}
|
|
616
|
+
pageHeaderOffsets[sourceKey] = headerOffsetPx
|
|
597
617
|
detachPrimaryScrollObserver()
|
|
598
618
|
transitionSourcePage = selectedPage
|
|
599
619
|
transitionHeaderOffsets = pageHeaderOffsets.toMap()
|
|
@@ -1684,6 +1704,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1684
1704
|
)
|
|
1685
1705
|
}
|
|
1686
1706
|
updateRecyclerPaddingOwnership(recycler, original)
|
|
1707
|
+
val key = pageKey(pageIndex)
|
|
1687
1708
|
val topInset = headerHeightPx + stickyHeaderHeightPx
|
|
1688
1709
|
recycler.clipToPadding = false
|
|
1689
1710
|
val top = original.top + topInset
|
|
@@ -1695,12 +1716,23 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1695
1716
|
// OneKey patch: LinearLayoutManager otherwise anchors children at their old
|
|
1696
1717
|
// screen coordinates when a page changes the sticky header height.
|
|
1697
1718
|
val manager = recycler.layoutManager as? LinearLayoutManager
|
|
1698
|
-
val
|
|
1699
|
-
|
|
1700
|
-
|
|
1719
|
+
val savedAnchor = if (nativeSmoothHeaderScrollEnabled && original.appliedTopInset == 0) {
|
|
1720
|
+
if (headerOffsetPx < headerHeightPx) {
|
|
1721
|
+
recycler.adapter?.let { RecyclerReadingAnchor(recycler, it, 0, 0) }
|
|
1722
|
+
} else {
|
|
1723
|
+
(if (transitionPageOffsets != null && pageIndex == transitionSourcePage) transitionSourceRecyclerAnchor
|
|
1724
|
+
else displayRecyclerAnchors[key] ?: recyclerReadingAnchors[key])
|
|
1725
|
+
?.takeIf { it.view.get() === recycler && it.adapter === recycler.adapter }
|
|
1726
|
+
}
|
|
1727
|
+
} else null
|
|
1728
|
+
val first = savedAnchor?.position
|
|
1729
|
+
?: manager?.takeIf { it.orientation == RecyclerView.VERTICAL }
|
|
1730
|
+
?.findFirstVisibleItemPosition()
|
|
1731
|
+
?: RecyclerView.NO_POSITION
|
|
1732
|
+
val anchorOffset = savedAnchor?.top ?: if (first != RecyclerView.NO_POSITION) {
|
|
1701
1733
|
manager?.findViewByPosition(first)?.let { manager.getDecoratedTop(it) - recycler.paddingTop }
|
|
1702
1734
|
} else null
|
|
1703
|
-
offsetDiag("recycler-inset-before-nextTop=$top-anchorPosition=$first-anchorOffset=$anchorOffset", recycler,
|
|
1735
|
+
offsetDiag("recycler-inset-before-nextTop=$top-anchorPosition=$first-anchorOffset=$anchorOffset", recycler, key, pageIndex)
|
|
1704
1736
|
recycler.setPadding(original.left, top, original.right, bottom)
|
|
1705
1737
|
if (anchorOffset != null) manager?.scrollToPositionWithOffset(first, anchorOffset)
|
|
1706
1738
|
// React Native can stop this nested requestLayout at its React-owned parent.
|
|
@@ -1714,9 +1746,8 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1714
1746
|
recycler.layout(recycler.left, recycler.top, recycler.right, recycler.bottom)
|
|
1715
1747
|
}
|
|
1716
1748
|
}
|
|
1717
|
-
if (original.appliedTopInset != topInset) offsetDiag("recycler-inset-after-old=${original.appliedTopInset}-new=$topInset", recycler,
|
|
1749
|
+
if (original.appliedTopInset != topInset) offsetDiag("recycler-inset-after-old=${original.appliedTopInset}-new=$topInset", recycler, key, pageIndex)
|
|
1718
1750
|
original.appliedTopInset = topInset
|
|
1719
|
-
val key = pageKey(pageIndex)
|
|
1720
1751
|
val geometry = Triple(recycler.computeVerticalScrollRange(), recycler.computeVerticalScrollExtent(), recycler.childCount)
|
|
1721
1752
|
deferredRecyclerGeometry[recycler]?.let { deferred ->
|
|
1722
1753
|
if (geometry != deferred) {
|
|
@@ -112,6 +112,10 @@ open class NestedScrollableHost : FrameLayout {
|
|
|
112
112
|
else -> 0
|
|
113
113
|
}
|
|
114
114
|
if (smoothTouchAxis != 0) {
|
|
115
|
+
if (!nestedNativeGestureStarted) {
|
|
116
|
+
NativeGestureUtil.notifyNativeGestureStarted(this, e)
|
|
117
|
+
nestedNativeGestureStarted = true
|
|
118
|
+
}
|
|
115
119
|
OneKeyLog.debug("CollapsiblePager", "content-direction owner=${if (smoothTouchAxis == 1) "list" else "horizontal"} dx=$dx dy=$dy nested=${nestedHorizontalOwner != null}")
|
|
116
120
|
}
|
|
117
121
|
}
|
|
@@ -184,8 +188,10 @@ open class NestedScrollableHost : FrameLayout {
|
|
|
184
188
|
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
|
185
189
|
|
|
186
190
|
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
if (!nestedNativeGestureStarted) {
|
|
192
|
+
NativeGestureUtil.notifyNativeGestureStarted(this, e)
|
|
193
|
+
nestedNativeGestureStarted = true
|
|
194
|
+
}
|
|
189
195
|
|
|
190
196
|
if (orientation == null) return
|
|
191
197
|
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
package/docs/SPEC.md
CHANGED
|
@@ -41,6 +41,31 @@ UIKit's Hard style, owner or interactions. During temporary horizontal owner
|
|
|
41
41
|
transitions the layer holds its last opacity until geometry is valid again;
|
|
42
42
|
disabling the local overlay, changing style or recycling removes it.
|
|
43
43
|
|
|
44
|
+
For this iOS 27 local smooth Hard direct-pager case, an animated page command
|
|
45
|
+
uses `UIScrollView`'s native content-offset animation; UIKit determines its
|
|
46
|
+
duration. Both page commands and horizontal user drags transfer the source's
|
|
47
|
+
existing top and pinned sticky scroll-edge interactions, markers, hosts and
|
|
48
|
+
saved original style to the transition owner before the source page detaches.
|
|
49
|
+
The interactions remain bound to the source list on their
|
|
50
|
+
original hosts; the borrowed pinned sticky host stays inside the source list
|
|
51
|
+
while the shared controls move. Incoming pages use temporary interactions; a
|
|
52
|
+
drag prepares both adjacent pages so either direction can remain visible.
|
|
53
|
+
Participating pages retain their scroll-edge sampling area during the animation;
|
|
54
|
+
fixed controls still reserve their existing hit-test area. Completion or
|
|
55
|
+
cancellation releases temporary ownership, restores each list's prior style
|
|
56
|
+
and pinned host, and reattaches the settled page's normal owner before a queued
|
|
57
|
+
page command starts. A drag that returns to its source page follows the same
|
|
58
|
+
cleanup path. A new drag invalidates an older pending settlement before it can
|
|
59
|
+
release the new transition's owners.
|
|
60
|
+
If the direct pager viewport width changes while a native animated page command is pending, stop the old-coordinate animation before resizing its viewport. After the normal layout completes, settle the captured logical destination at its new physical offset through the original generation/transition-guarded completion path. This restores scrolling, releases temporary edge ownership and drains queued commands. Any width change, including a fractional-point change, is significant. Zero width completes the logical destination without retaining a disabled pending animation; a later positive-width idle layout realigns the current page. Height-only layout and horizontal user drags retain their existing paths. Late animation-end callbacks must not complete a newer command before its own current target has been reached.
|
|
61
|
+
|
|
62
|
+
Other pager modes retain their prior command and observer ordering. Recycling,
|
|
63
|
+
window detachment, slot replacement and disabling the style also release the
|
|
64
|
+
temporary resources. Conformance requires preserving the source interaction
|
|
65
|
+
and marker identities and prior style until settlement, releasing borrowed
|
|
66
|
+
resources on completion and cancellation, and rendered blur continuity in both
|
|
67
|
+
directions over long scrollable pages.
|
|
68
|
+
|
|
44
69
|
For an iOS 26 pager whose fixed header is rendered by a sibling in the same
|
|
45
70
|
React surface, `externalTopOverlayTag` may identify that header's native view.
|
|
46
71
|
The pager weakly observes its geometry in the same window, retaining its own
|
|
@@ -111,6 +136,21 @@ style forwarding, overlay retention and disabling; UIKit tests cover native
|
|
|
111
136
|
style switching and restoring the previous style. Rendered iOS acceptance must
|
|
112
137
|
be reported separately from type, codegen and build checks.
|
|
113
138
|
|
|
139
|
+
### iOS 27 external-overlay interactive ownership
|
|
140
|
+
|
|
141
|
+
Status: Implemented in iOS source; runtime acceptance is reported separately.
|
|
142
|
+
|
|
143
|
+
For a direct native pager using smooth headers and a Hard top edge, an interactive horizontal drag may preserve scroll-edge ownership when the external overlay is resolved in the same window with a positive effective height. A registered outer pager must own the active page and must not be transitioning; an external overlay without an outer pager remains eligible.
|
|
144
|
+
|
|
145
|
+
Before detaching the source observer, transfer the source's existing top/sticky interactions, markers, original edge style and borrowed pinned host into the transition owner. Do not move or take ownership of the actual external React overlay. Interactive preparation is bounded to the source and its valid immediate neighbours.
|
|
146
|
+
|
|
147
|
+
Each newly prepared external incoming page owns separate transparent, noninteractive top/sticky interaction hosts inside that page. Their horizontal origin follows the page's scroll viewport rather than the fixed external overlay; their vertical positions and heights match the existing top/sticky geometry. Hosts and markers follow page-width changes. They add no replacement material or background. The source retains its existing host identities. Local-overlay ownership and external programmatic page commands keep their existing paths; this extension is selected only by interactive drag preparation.
|
|
148
|
+
|
|
149
|
+
Completion or release back to the source releases temporary interactions and markers, restores the captured original styles, removes only the owned temporary hosts, and reattaches normal ownership to the settled page. Existing cancellation, window removal, slot/recycle and external invalidation paths must release these resources as well. External identity/tag/effective-height changes and outer-owner inactivity or transition invalidate temporary ownership before references are updated. Never remove a borrowed pinned host, shared proxy, shared sticky view or actual external overlay as though it were an owned temporary host.
|
|
150
|
+
|
|
151
|
+
Rendering conformance requires real underlying content on both pages to remain blurred throughout the drag and release, with no premature whiteout, sharp penetration or frozen texture. Object registration and lifecycle tests do not establish visual conformance.
|
|
152
|
+
|
|
153
|
+
|
|
114
154
|
## Smooth shared-header and per-page body memory
|
|
115
155
|
|
|
116
156
|
With nativeSmoothHeaderScrollEnabled, the header collapse C is shared across all
|
|
@@ -435,6 +475,10 @@ controls belongs to the consuming responder implementation; the Pager package
|
|
|
435
475
|
does not contain or publish Tamagui helpers. The native application tab bar has
|
|
436
476
|
a separate contract in react-native-bottom-tabs/docs/SPEC.md.
|
|
437
477
|
|
|
478
|
+
On Android, smooth-touch coordination and nested interception share the same
|
|
479
|
+
native-gesture state. Once either path starts the gesture, later MOVE events
|
|
480
|
+
MUST NOT start it again before its end or cancellation clears that state.
|
|
481
|
+
|
|
438
482
|
The current native implementation confirms horizontal intent after at least
|
|
439
483
|
32 points on iOS or 32 dp on Android, with horizontal displacement exceeding
|
|
440
484
|
vertical displacement. Vertical intent uses the native vertical scroll threshold
|