@onekeyfe/react-native-pager-view 6.7.0-alpha.354 → 6.7.0-alpha.362
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/README.md +0 -5
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +21 -72
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +0 -13
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +6 -50
- package/docs/SPEC.md +1 -67
- package/ios/RNCCollapsiblePagerViewComponentView.mm +7 -68
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +0 -87
- package/lib/module/CollapsiblePagerView.js +4 -16
- package/lib/module/CollapsiblePagerView.web.js +2 -11
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +0 -11
- package/lib/typescript/src/CollapsiblePagerView.d.ts +1 -4
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +0 -2
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +0 -3
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +4 -50
- package/src/CollapsiblePagerView.web.tsx +2 -19
- package/src/CollapsiblePagerViewNativeComponent.ts +0 -11
- package/android/src/androidTest/AndroidManifest.xml +0 -7
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +0 -136
package/README.md
CHANGED
|
@@ -66,11 +66,6 @@ replacing its delegate. Android coordinates nested scroll with the active
|
|
|
66
66
|
Web scroll child can opt in by exposing a DOM element with
|
|
67
67
|
`data-collapsible-pager-scroll`. CSS scroll timelines drive header/sticky
|
|
68
68
|
movement, so React is not updated on each vertical scroll frame.
|
|
69
|
-
`pagerRef.current?.scrollHeaderBy(delta)` immediately moves the active page's
|
|
70
|
-
collapsible header by a finite logical distance, clamped to its measured range.
|
|
71
|
-
`scrollHeaderToOffset(offset)` targets an absolute collapse position, so a
|
|
72
|
-
keyboard's own scroll adjustment cannot add a second relative movement.
|
|
73
|
-
It is useful for revealing a focused field above a keyboard.
|
|
74
69
|
|
|
75
70
|
On Web, `WebCollapsiblePagerView` is an explicit root export of the existing Web
|
|
76
71
|
pager. It measures Header and sticky content when their heights are omitted.
|
|
@@ -142,7 +142,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
142
142
|
private var transitionSourceRecyclerAnchor: RecyclerReadingAnchor? = null
|
|
143
143
|
private val commandBodyKeys = HashSet<String>()
|
|
144
144
|
private var explicitTopCommand: Pair<String, PageRestoreRequest>? = null
|
|
145
|
-
private class PagerTopRequest(view: View, val key: String, val pageIndex: Int, val generation: Int, val
|
|
145
|
+
private class PagerTopRequest(view: View, val key: String, val pageIndex: Int, val generation: Int, val target: Int, val token: Int) {
|
|
146
146
|
val view = WeakReference(view)
|
|
147
147
|
}
|
|
148
148
|
private var pagerTopRequest: PagerTopRequest? = null
|
|
@@ -293,20 +293,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
293
293
|
var onNativeSubHeaderPress: ((Int, String) -> Unit)? = null
|
|
294
294
|
|
|
295
295
|
var nativeTabBarHeight = 44.0
|
|
296
|
-
var nativeTabBarTrailingReservedWidth = 0.0
|
|
297
|
-
set(value) {
|
|
298
|
-
field = if (value.isFinite() && value >= 0.0) value else 0.0
|
|
299
|
-
bringNativeHeadersToFront()
|
|
300
|
-
requestLayout()
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private fun nativeTabReservationPx(width: Int): Int {
|
|
304
|
-
val reserved = nativeTabBarTrailingReservedWidth
|
|
305
|
-
val density = resources.displayMetrics.density
|
|
306
|
-
return if (reserved.isFinite() && reserved > 0.0 && width > 0 && density > 0f) {
|
|
307
|
-
(reserved.coerceAtMost(width / density.toDouble()) * density).roundToInt()
|
|
308
|
-
} else 0
|
|
309
|
-
}
|
|
310
296
|
var nativeTabBarContentPaddingHorizontal = 20.0
|
|
311
297
|
var nativeTabBarItemSpacing = 8.0
|
|
312
298
|
var nativeTabBarFontSize = 16.0
|
|
@@ -456,7 +442,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
456
442
|
private fun bringNativeHeadersToFront() {
|
|
457
443
|
nativeTabBarView.bringToFront()
|
|
458
444
|
nativeSubHeaderView.bringToFront()
|
|
459
|
-
if (nativeTabBarTrailingReservedWidth > 0.0) stickyHeaderView?.bringToFront()
|
|
460
445
|
}
|
|
461
446
|
|
|
462
447
|
fun addReactChild(child: View, index: Int) {
|
|
@@ -704,7 +689,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
704
689
|
|
|
705
690
|
private fun layoutPagerIfRequested() {
|
|
706
691
|
val recycler = pager.getChildAt(0) as? RecyclerView ?: return
|
|
707
|
-
if (!recycler.isLayoutRequested || recycler.
|
|
692
|
+
if (!recycler.isLayoutRequested || recycler.isComputingLayout || recycler.width <= 0 || recycler.height <= 0) return
|
|
708
693
|
// OneKey patch: a non-animated page command queues RecyclerView layout, but RN
|
|
709
694
|
// can stop that request after navigation. Drain it before showing the page.
|
|
710
695
|
recycler.measure(
|
|
@@ -727,9 +712,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
727
712
|
layout(left, top, right, bottom)
|
|
728
713
|
}
|
|
729
714
|
|
|
730
|
-
private fun isPagerRecyclerLayoutSuppressed(): Boolean =
|
|
731
|
-
(pager.getChildAt(0) as? RecyclerView)?.isLayoutSuppressed == true
|
|
732
|
-
|
|
733
715
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
734
716
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
735
717
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
@@ -740,12 +722,10 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
740
722
|
return
|
|
741
723
|
}
|
|
742
724
|
val pagerHeight = height + headerHeightPx
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
)
|
|
748
|
-
}
|
|
725
|
+
pager.measure(
|
|
726
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
727
|
+
MeasureSpec.makeMeasureSpec(pagerHeight, MeasureSpec.EXACTLY),
|
|
728
|
+
)
|
|
749
729
|
headerView?.measure(
|
|
750
730
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
751
731
|
MeasureSpec.makeMeasureSpec(headerHeightPx, MeasureSpec.EXACTLY),
|
|
@@ -757,9 +737,8 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
757
737
|
val nativeTabHeight = if (nativeTabBarView.visibility == View.VISIBLE) {
|
|
758
738
|
nativeTabBarView.preferredHeightPx()
|
|
759
739
|
} else 0
|
|
760
|
-
val tabReservedWidth = nativeTabReservationPx(width)
|
|
761
740
|
nativeTabBarView.measure(
|
|
762
|
-
MeasureSpec.makeMeasureSpec(width
|
|
741
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
763
742
|
MeasureSpec.makeMeasureSpec(nativeTabHeight, MeasureSpec.EXACTLY),
|
|
764
743
|
)
|
|
765
744
|
val nativeSubHeaderHeight = if (nativeSubHeaderView.visibility == View.VISIBLE) {
|
|
@@ -778,9 +757,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
778
757
|
// RN ancestors allow overflow; keep translated header content inside this viewport.
|
|
779
758
|
clipBounds = Rect(0, 0, width, height)
|
|
780
759
|
if (width <= 0 || height <= 0) return
|
|
781
|
-
|
|
782
|
-
pager.layout(0, 0, width, height + headerHeightPx)
|
|
783
|
-
}
|
|
760
|
+
pager.layout(0, 0, width, height + headerHeightPx)
|
|
784
761
|
headerView?.layout(0, 0, width, headerHeightPx)
|
|
785
762
|
stickyHeaderView?.layout(
|
|
786
763
|
0,
|
|
@@ -789,12 +766,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
789
766
|
headerHeightPx + stickyHeaderHeightPx,
|
|
790
767
|
)
|
|
791
768
|
val nativeTabHeight = nativeTabBarView.measuredHeight
|
|
792
|
-
|
|
793
|
-
val tabLeft = if (nativeTabBarView.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
|
794
|
-
tabReservedWidth
|
|
795
|
-
} else 0
|
|
796
|
-
nativeTabBarView.layout(tabLeft, headerHeightPx, tabLeft + width - tabReservedWidth,
|
|
797
|
-
headerHeightPx + nativeTabHeight)
|
|
769
|
+
nativeTabBarView.layout(0, headerHeightPx, width, headerHeightPx + nativeTabHeight)
|
|
798
770
|
nativeSubHeaderView.layout(
|
|
799
771
|
0,
|
|
800
772
|
headerHeightPx + nativeTabHeight,
|
|
@@ -856,7 +828,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
856
828
|
}
|
|
857
829
|
if (!reached()) {
|
|
858
830
|
if (!isAttachedToWindow || !view.isAttachedToWindow || width <= 0 || height <= 0 ||
|
|
859
|
-
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.
|
|
831
|
+
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.isComputingLayout ||
|
|
860
832
|
pageKey(index) != key || primaryScrollableForPage(index) !== view || !isCurrentPageRestore(key, request)) return false
|
|
861
833
|
val previousSynthetic = syntheticPageRestore
|
|
862
834
|
syntheticPageRestore = true
|
|
@@ -1015,33 +987,13 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1015
987
|
if (!settled) return
|
|
1016
988
|
pageOffsets[request.key] = 0
|
|
1017
989
|
if (view is RecyclerView) saveRecyclerReadingAnchor(request.key, view)
|
|
1018
|
-
setHeaderOffset(request.
|
|
1019
|
-
offsetDiag("pager-top-complete-
|
|
990
|
+
setHeaderOffset(if (request.target == 1) headerHeightPx else 0)
|
|
991
|
+
offsetDiag("pager-top-complete-target=${request.target}-token=${request.token}", view, request.key)
|
|
1020
992
|
cancelPagerTop()
|
|
1021
993
|
}
|
|
1022
994
|
|
|
1023
995
|
internal fun scrollToTop(target: Int, animated: Boolean) {
|
|
1024
|
-
if (target !in 0..1
|
|
1025
|
-
scrollHeaderToOffset(if (target == 1) headerHeightPx else 0, animated)
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
internal fun scrollHeaderBy(delta: Double) {
|
|
1029
|
-
if (!delta.isFinite()) return
|
|
1030
|
-
val density = resources.displayMetrics.density.toDouble()
|
|
1031
|
-
val maxDip = headerHeightPx / density
|
|
1032
|
-
val deltaPx = (delta.coerceIn(-maxDip, maxDip) * density).roundToInt()
|
|
1033
|
-
scrollHeaderToOffset((headerOffsetPx.toLong() + deltaPx).coerceIn(0, headerHeightPx.toLong()).toInt(), false)
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
internal fun scrollHeaderToOffset(offset: Double) {
|
|
1037
|
-
if (!offset.isFinite()) return
|
|
1038
|
-
val density = resources.displayMetrics.density.toDouble()
|
|
1039
|
-
val targetPx = (offset.coerceIn(0.0, headerHeightPx / density) * density).roundToInt()
|
|
1040
|
-
scrollHeaderToOffset(targetPx, false)
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
private fun scrollHeaderToOffset(logicalOffset: Int, animated: Boolean) {
|
|
1044
|
-
if (pager.scrollState != ViewPager2.SCROLL_STATE_IDLE || transitionHeaderOffsets != null ||
|
|
996
|
+
if (target !in 0..1 || pager.scrollState != ViewPager2.SCROLL_STATE_IDLE || transitionHeaderOffsets != null ||
|
|
1045
997
|
syntheticPageRestore || selectedPage !in 0 until adapter.itemCount || !isAttachedToWindow) return
|
|
1046
998
|
val view = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller ?: return
|
|
1047
999
|
val key = currentPageKey()
|
|
@@ -1068,12 +1020,11 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1068
1020
|
if (view is RecyclerView) deferredRecyclerGeometry.remove(view)
|
|
1069
1021
|
if (view is ScrollView) deferredScrollerGeometry.remove(view)
|
|
1070
1022
|
pageOffsets[key] = 0
|
|
1071
|
-
val
|
|
1072
|
-
val request = PagerTopRequest(view, key, selectedPage, attachmentGeneration, targetOffset, ++pagerTopToken)
|
|
1023
|
+
val request = PagerTopRequest(view, key, selectedPage, attachmentGeneration, target, ++pagerTopToken)
|
|
1073
1024
|
pagerTopRequest = request
|
|
1074
1025
|
commandBodyKeys.add(key)
|
|
1075
|
-
setHeaderOffset(
|
|
1076
|
-
offsetDiag("pager-top-begin-
|
|
1026
|
+
setHeaderOffset(if (target == 1) headerHeightPx else 0)
|
|
1027
|
+
offsetDiag("pager-top-begin-target=$target-animated=$animated-token=${request.token}", view, key)
|
|
1077
1028
|
dispatchingPagerTop = true
|
|
1078
1029
|
try {
|
|
1079
1030
|
when (view) {
|
|
@@ -1082,7 +1033,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1082
1033
|
if (animated && !recyclerIsAtBodyTop(view)) view.smoothScrollToPosition(0)
|
|
1083
1034
|
else if (manager != null && manager.orientation == RecyclerView.VERTICAL) {
|
|
1084
1035
|
manager.scrollToPositionWithOffset(0, 0)
|
|
1085
|
-
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.
|
|
1036
|
+
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.isComputingLayout) {
|
|
1086
1037
|
view.measure(MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(view.height, MeasureSpec.EXACTLY))
|
|
1087
1038
|
view.layout(view.left, view.top, view.right, view.bottom)
|
|
1088
1039
|
}
|
|
@@ -1687,7 +1638,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1687
1638
|
if (anchorOffset != null) manager?.scrollToPositionWithOffset(first, anchorOffset)
|
|
1688
1639
|
// React Native can stop this nested requestLayout at its React-owned parent.
|
|
1689
1640
|
// Commit the changed inset before drawing instead of waiting for a data update.
|
|
1690
|
-
if (recycler.width > 0 && recycler.height > 0 && !recycler.
|
|
1641
|
+
if (recycler.width > 0 && recycler.height > 0 && !recycler.isComputingLayout) {
|
|
1691
1642
|
recycler.forceLayout()
|
|
1692
1643
|
recycler.measure(
|
|
1693
1644
|
MeasureSpec.makeMeasureSpec(recycler.width, MeasureSpec.EXACTLY),
|
|
@@ -2061,16 +2012,14 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2061
2012
|
}
|
|
2062
2013
|
}
|
|
2063
2014
|
|
|
2064
|
-
private fun headerRegionAt(
|
|
2015
|
+
private fun headerRegionAt(y: Float): String? {
|
|
2065
2016
|
val headerBottom = headerHeightPx - headerOffsetPx
|
|
2066
2017
|
val stickyBottom = headerHeightPx + stickyHeaderHeightPx - headerOffsetPx
|
|
2067
2018
|
return when {
|
|
2068
2019
|
y < 0 || y >= stickyBottom -> null
|
|
2069
2020
|
y < headerBottom -> "header"
|
|
2070
2021
|
y < headerBottom + nativeTabBarView.measuredHeight &&
|
|
2071
|
-
nativeTabBarView.visibility == View.VISIBLE ->
|
|
2072
|
-
if (x >= nativeTabBarView.left && x < nativeTabBarView.right) "primary-tab"
|
|
2073
|
-
else "sticky-controls"
|
|
2022
|
+
nativeTabBarView.visibility == View.VISIBLE -> "primary-tab"
|
|
2074
2023
|
y < headerBottom + nativeTabBarView.measuredHeight + nativeSubHeaderView.measuredHeight &&
|
|
2075
2024
|
nativeSubHeaderView.visibility == View.VISIBLE -> "secondary-header"
|
|
2076
2025
|
else -> "sticky-controls"
|
|
@@ -2179,7 +2128,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2179
2128
|
pressCancelled = false
|
|
2180
2129
|
nativeGestureStarted = false
|
|
2181
2130
|
nestedNativeGestureStarted = false
|
|
2182
|
-
headerTouchRegion = headerRegionAt(event.
|
|
2131
|
+
headerTouchRegion = headerRegionAt(event.y) ?: "none"
|
|
2183
2132
|
headerTouchActive = headerTouchRegion != "none"
|
|
2184
2133
|
if (!headerTouchActive) return super.dispatchTouchEvent(event)
|
|
2185
2134
|
headerDownX = event.x
|
|
@@ -239,11 +239,6 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
239
239
|
view.applyNativeHeaderStyle()
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
@ReactProp(name = "nativeTabBarTrailingReservedWidth", defaultDouble = 0.0)
|
|
243
|
-
override fun setNativeTabBarTrailingReservedWidth(view: CollapsiblePagerHost?, value: Double) {
|
|
244
|
-
view?.nativeTabBarTrailingReservedWidth = value
|
|
245
|
-
}
|
|
246
|
-
|
|
247
242
|
@ReactProp(name = "nativeTabBarContentPaddingHorizontal")
|
|
248
243
|
override fun setNativeTabBarContentPaddingHorizontal(
|
|
249
244
|
view: CollapsiblePagerHost?,
|
|
@@ -348,14 +343,6 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
348
343
|
view?.scrollToTop(target, animated)
|
|
349
344
|
}
|
|
350
345
|
|
|
351
|
-
override fun scrollHeaderBy(view: CollapsiblePagerHost?, delta: Double) {
|
|
352
|
-
view?.scrollHeaderBy(delta)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
override fun scrollHeaderToOffset(view: CollapsiblePagerHost?, offset: Double) {
|
|
356
|
-
view?.scrollHeaderToOffset(offset)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
346
|
override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
360
347
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
361
348
|
view.beginPageTransition(selectedPage)
|
|
@@ -37,50 +37,26 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
37
37
|
setMeasuredDimension(MeasureSpec.getSize(widthSpec), MeasureSpec.getSize(heightSpec))
|
|
38
38
|
}
|
|
39
39
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) = Unit
|
|
40
|
-
|
|
41
|
-
override fun startViewTransition(view: View) {
|
|
42
|
-
super.startViewTransition(view)
|
|
43
|
-
if (view === reactChild) transitioningReactChild = view
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
override fun endViewTransition(view: View) {
|
|
47
|
-
super.endViewTransition(view)
|
|
48
|
-
if (transitioningReactChild === view) transitioningReactChild = null
|
|
49
|
-
if (retainedReactChild === view) {
|
|
50
|
-
clearRetainedGeometry()
|
|
51
|
-
if (isAttachedToWindow) scheduleLayout()
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
40
|
}
|
|
55
41
|
private val contentFrame = object : FrameLayout(context) {
|
|
56
42
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
|
57
43
|
setMeasuredDimension(
|
|
58
|
-
max(MeasureSpec.getSize(widthSpec),
|
|
59
|
-
|
|
44
|
+
max(MeasureSpec.getSize(widthSpec), contentWidthPx + insetLeft + insetRight),
|
|
45
|
+
contentHeightPx + contentTop + insetBottom,
|
|
60
46
|
)
|
|
61
47
|
}
|
|
62
48
|
|
|
63
49
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
64
50
|
// Only the native origin layer moves. Fabric owns its React child's layout/transform.
|
|
65
|
-
val contentWidth = effectiveContentWidthPx
|
|
66
|
-
val contentHeight = effectiveContentHeightPx
|
|
67
51
|
contentOriginFrame.measure(
|
|
68
|
-
MeasureSpec.makeMeasureSpec(
|
|
69
|
-
MeasureSpec.makeMeasureSpec(
|
|
52
|
+
MeasureSpec.makeMeasureSpec(contentWidthPx, MeasureSpec.EXACTLY),
|
|
53
|
+
MeasureSpec.makeMeasureSpec(contentHeightPx, MeasureSpec.EXACTLY),
|
|
70
54
|
)
|
|
71
|
-
contentOriginFrame.layout(insetLeft, contentTop, insetLeft +
|
|
55
|
+
contentOriginFrame.layout(insetLeft, contentTop, insetLeft + contentWidthPx, contentTop + contentHeightPx)
|
|
72
56
|
}
|
|
73
57
|
}
|
|
74
58
|
private val pagerInsets = WeakHashMap<View, Int>()
|
|
75
59
|
private var reactChild: View? = null
|
|
76
|
-
private var transitioningReactChild: View? = null
|
|
77
|
-
private var retainedReactChild: View? = null
|
|
78
|
-
private var retainedContentWidthPx = 0
|
|
79
|
-
private var retainedContentHeightPx = 0
|
|
80
|
-
private val effectiveContentWidthPx: Int
|
|
81
|
-
get() = if (reactChild == null && retainedReactChild != null) retainedContentWidthPx else contentWidthPx
|
|
82
|
-
private val effectiveContentHeightPx: Int
|
|
83
|
-
get() = if (reactChild == null && retainedReactChild != null) retainedContentHeightPx else contentHeightPx
|
|
84
60
|
private var fabricState: StateWrapper? = null
|
|
85
61
|
private var lastOriginX: Int? = null
|
|
86
62
|
private var lastOriginY: Int? = null
|
|
@@ -196,8 +172,6 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
196
172
|
internal fun addReactChild(child: View, index: Int) {
|
|
197
173
|
require(index == 0 && reactChild == null) { "NativeScroller requires one React content view" }
|
|
198
174
|
viewport.stopMotion()
|
|
199
|
-
clearRetainedGeometry()
|
|
200
|
-
transitioningReactChild = null
|
|
201
175
|
reactChild = child
|
|
202
176
|
contentOriginFrame.addView(child)
|
|
203
177
|
scheduleLayout()
|
|
@@ -206,29 +180,13 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
206
180
|
internal fun removeReactChild() {
|
|
207
181
|
viewport.stopMotion()
|
|
208
182
|
pendingRestore.cancel()
|
|
209
|
-
reactChild?.let
|
|
210
|
-
clearRetainedGeometry()
|
|
211
|
-
// Fabric removes the child before its disappearing view finishes drawing.
|
|
212
|
-
if (transitioningReactChild === child &&
|
|
213
|
-
contentOriginFrame.width > 0 && contentOriginFrame.height > 0) {
|
|
214
|
-
retainedReactChild = child
|
|
215
|
-
retainedContentWidthPx = contentOriginFrame.width
|
|
216
|
-
retainedContentHeightPx = contentOriginFrame.height
|
|
217
|
-
}
|
|
218
|
-
contentOriginFrame.removeView(child)
|
|
219
|
-
}
|
|
183
|
+
reactChild?.let(contentOriginFrame::removeView)
|
|
220
184
|
reactChild = null
|
|
221
185
|
contentWidthPx = 0
|
|
222
186
|
contentHeightPx = 0
|
|
223
187
|
scheduleLayout()
|
|
224
188
|
}
|
|
225
189
|
|
|
226
|
-
private fun clearRetainedGeometry() {
|
|
227
|
-
retainedReactChild = null
|
|
228
|
-
retainedContentWidthPx = 0
|
|
229
|
-
retainedContentHeightPx = 0
|
|
230
|
-
}
|
|
231
|
-
|
|
232
190
|
internal fun reactChildCount() = if (reactChild == null) 0 else 1
|
|
233
191
|
internal fun reactChildAt(index: Int): View {
|
|
234
192
|
require(index == 0)
|
|
@@ -423,8 +381,6 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
423
381
|
lastContentViewportHeight = null
|
|
424
382
|
viewport.stopMotion()
|
|
425
383
|
viewport.setTag(R.id.onekey_native_scroll_coordinator_offset_listener, null)
|
|
426
|
-
transitioningReactChild = null
|
|
427
|
-
clearRetainedGeometry()
|
|
428
384
|
super.onDetachedFromWindow()
|
|
429
385
|
}
|
|
430
386
|
|
package/docs/SPEC.md
CHANGED
|
@@ -74,7 +74,7 @@ the shared host before their old scroll-edge interaction is released.
|
|
|
74
74
|
The measured overlay height is excluded from the collapsible header distance:
|
|
75
75
|
`header` remains collapsible and `stickyHeader` pins immediately below the
|
|
76
76
|
overlay. Selection, cancellation, reuse and disposal rebind or release the
|
|
77
|
-
interaction and restore any prior scroll-edge style.
|
|
77
|
+
interaction and restore any prior scroll-edge style. Native scroll/refresh
|
|
78
78
|
coordination continues on the main thread without per-frame JavaScript work.
|
|
79
79
|
Refresh retains the pager-owned `overlay + header + sticky` top inset but offsets
|
|
80
80
|
the indicator by `header + sticky`, so its spinner begins below the fixed overlay.
|
|
@@ -171,24 +171,6 @@ intent. Real vertical consumption, a newer valid command, or owner detachment
|
|
|
171
171
|
releases the target. Existing NativeList and NativeScroller public coordinates
|
|
172
172
|
are unchanged. The same page-top/sticky-top destinations are available without smooth-header mode.
|
|
173
173
|
|
|
174
|
-
## Current-page partial header command
|
|
175
|
-
|
|
176
|
-
`scrollHeaderBy(delta: number)` moves the current page's shared collapsible
|
|
177
|
-
header from its displayed offset C to `clamp(C + delta, 0, D)`.
|
|
178
|
-
`scrollHeaderToOffset(offset: number)` moves it to `clamp(offset, 0, D)`
|
|
179
|
-
regardless of intervening native keyboard or user scrolling. D is the latest
|
|
180
|
-
measured collapsible height. Inputs use logical points on iOS and Android and
|
|
181
|
-
CSS pixels on Web. Non-finite values are ignored. The header and current body move immediately
|
|
182
|
-
so a focused input becomes visible before the next keyboard frame. A valid command
|
|
183
|
-
clears only the current page's body reading position B; it never changes
|
|
184
|
-
another page's memory or adds permanent content/insets. It requires the same
|
|
185
|
-
stable attached current owner as the page-top commands and is a safe no-op
|
|
186
|
-
during horizontal transitions, before mount, or after disposal. A newer
|
|
187
|
-
command or real user vertical consumption takes ownership from an older one.
|
|
188
|
-
Dynamic header measurements clamp any in-flight target to the latest D.
|
|
189
|
-
This command allows a focused input inside the shared header to move above a
|
|
190
|
-
keyboard without replacing the pager's sole vertical scroll owner.
|
|
191
|
-
|
|
192
174
|
## Independent NativeScroller migration
|
|
193
175
|
|
|
194
176
|
Status: implemented in source. Both native builds pass. Android focused emulator
|
|
@@ -351,32 +333,6 @@ warnings were observed. Keyboard, multi-touch, physical-device performance and
|
|
|
351
333
|
the full lifecycle/interaction matrix remain unverified. These are local source
|
|
352
334
|
builds; the currently installed npm version does not yet contain this migration.
|
|
353
335
|
|
|
354
|
-
## Android removal-transition geometry retention
|
|
355
|
-
|
|
356
|
-
Status: implemented locally; unreleased. On the Android UI thread, during a
|
|
357
|
-
screen removal transition,
|
|
358
|
-
NativeList and CollapsiblePager layout entry points respect the RecyclerView
|
|
359
|
-
suppression state established by the screen-transition owner. The owner restores
|
|
360
|
-
the original state when its last transition ownership ends. This adds no public
|
|
361
|
-
API and does not change Web or iOS behavior.
|
|
362
|
-
|
|
363
|
-
If Fabric removes the current NativeScroller React child while its origin-frame
|
|
364
|
-
transition remains active, Android retains the already displayed native extent
|
|
365
|
-
for drawing. The current child and raw content dimensions still clear normally;
|
|
366
|
-
only native content measurement and layout use the retained effective extent.
|
|
367
|
-
The matching transition end or detach clears the private child and extent state
|
|
368
|
-
idempotently. Adding a new child cancels prior retention, and a late end for the
|
|
369
|
-
old child cannot alter the new content. Ordinary removal, size events and scroll
|
|
370
|
-
commands keep their existing semantics.
|
|
371
|
-
|
|
372
|
-
Local arm64 DevShell acceptance covered Base, CXO and Overview exit content,
|
|
373
|
-
normal scrolling, RecyclerView ownership and suppression cleanup for the
|
|
374
|
-
observed CXO exit, and NativeScroller raw-zero content dimensions with a
|
|
375
|
-
nonzero displayed origin followed by detach cleanup. Nested-owner and cancel
|
|
376
|
-
paths, Overview rebound/cancel, natural resize and new-child runtime cases,
|
|
377
|
-
and the full performance matrix remain unverified. SDK publication and
|
|
378
|
-
consumer version alignment are separate steps.
|
|
379
|
-
|
|
380
336
|
## iOS vertical-scroll press cancellation
|
|
381
337
|
|
|
382
338
|
Status: implemented in source. A source-identical `node_modules` build was
|
|
@@ -516,28 +472,6 @@ Status: implemented; focused native-wrapper tests cover both directions,
|
|
|
516
472
|
explicit overrides and in-flight/superseded command targets. Home applies this policy to its JS tab bar, with iOS and
|
|
517
473
|
Android simulator recordings verifying distant jumps and adjacent transitions.
|
|
518
474
|
|
|
519
|
-
## Native tab trailing control reservation
|
|
520
|
-
|
|
521
|
-
Status: implemented locally, unreleased. The focused Android host test passed
|
|
522
|
-
(1/1); iOS hit tests had 8 passes and 1 existing skip. iOS companion-app UI
|
|
523
|
-
acceptance remains unverified.
|
|
524
|
-
`nativeTabBar.style.trailingReservedWidth` is an optional logical trailing
|
|
525
|
-
reservation for a sibling React control in the full-width sticky header. It
|
|
526
|
-
defaults to zero. JavaScript normalizes non-finite or negative input to zero;
|
|
527
|
-
native also clamps the reservation to the actual tab viewport width. On Android
|
|
528
|
-
and iOS, only the native tab viewport shrinks from `W` to `W - R`: its frame
|
|
529
|
-
begins at zero in LTR and at `R` in RTL. The sticky React header, its height,
|
|
530
|
-
and page content keep their original full-width geometry. Touches in the
|
|
531
|
-
reserved region reach the sibling control, while native tab hit testing and
|
|
532
|
-
last-tab visibility use the reduced viewport. Changing the reservation updates
|
|
533
|
-
that viewport; removing it or recycling the pager restores the default zero.
|
|
534
|
-
Web does not render the native tab and ignores this style field. For a positive
|
|
535
|
-
reservation, the box-none sticky React control draws above the native tab so
|
|
536
|
-
its inward fade and toolbar hit area remain visible; zero preserves the old
|
|
537
|
-
native-tab foreground order. Acceptance requires focused native layout and hit
|
|
538
|
-
testing for zero, nonzero, reset, and RTL reservations; type forwarding alone
|
|
539
|
-
is insufficient.
|
|
540
|
-
|
|
541
475
|
## Android refresh foreground coordination
|
|
542
476
|
|
|
543
477
|
Status: implemented; Android Home simulator placement verified on 2026-09-28.
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
|
|
19
19
|
// OneKey patch: deduplicate structural mutation parents before scanning the page.
|
|
20
20
|
#include <unordered_set>
|
|
21
|
-
#include <cmath>
|
|
22
21
|
|
|
23
22
|
using namespace facebook::react;
|
|
24
23
|
|
|
@@ -1472,7 +1471,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1472
1471
|
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
|
|
1473
1472
|
- (void)restoreSharedHeadersToContainer;
|
|
1474
1473
|
- (void)layoutSharedHeaders;
|
|
1475
|
-
- (void)bringReservedStickyControlToFront;
|
|
1476
1474
|
- (void)restorePinnedStickyHeader;
|
|
1477
1475
|
- (void)updatePinnedStickyHeader;
|
|
1478
1476
|
- (UIScrollView *)currentVerticalScrollViewForParentPager;
|
|
@@ -1560,7 +1558,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1560
1558
|
NSUInteger _pendingTopInsetReboundGeneration;
|
|
1561
1559
|
NSTimer *_topInsetReboundTimer;
|
|
1562
1560
|
CGFloat _nativeTabBarHeight;
|
|
1563
|
-
CGFloat _nativeTabBarTrailingReservedWidth;
|
|
1564
1561
|
CGFloat _nativeSubHeaderHeight;
|
|
1565
1562
|
CGFloat _headerOffset;
|
|
1566
1563
|
BOOL _scrollEnabled;
|
|
@@ -2482,7 +2479,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2482
2479
|
}
|
|
2483
2480
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2484
2481
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2485
|
-
[self bringReservedStickyControlToFront];
|
|
2486
2482
|
}
|
|
2487
2483
|
|
|
2488
2484
|
- (void)restoreSharedHeadersToContainer
|
|
@@ -2588,11 +2584,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2588
2584
|
CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
|
|
2589
2585
|
? 0
|
|
2590
2586
|
: MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2591
|
-
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2592
2587
|
_nativeTabBarView.frame = CGRectMake(
|
|
2593
|
-
|
|
2588
|
+
0,
|
|
2594
2589
|
stickyChildY,
|
|
2595
|
-
width
|
|
2590
|
+
width,
|
|
2596
2591
|
nativeTabBarVisibleHeight
|
|
2597
2592
|
);
|
|
2598
2593
|
_nativeSubHeaderView.frame = CGRectMake(
|
|
@@ -2606,26 +2601,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2606
2601
|
);
|
|
2607
2602
|
_topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
|
|
2608
2603
|
_topOverlayView.frame = _topOverlayHostView.bounds;
|
|
2609
|
-
[self bringReservedStickyControlToFront];
|
|
2610
2604
|
[self applyHeaderOffset];
|
|
2611
2605
|
}
|
|
2612
2606
|
|
|
2613
|
-
- (void)bringReservedStickyControlToFront
|
|
2614
|
-
{
|
|
2615
|
-
if (_stickyHeaderView != nil &&
|
|
2616
|
-
_stickyHeaderView.superview == _nativeTabBarView.superview) {
|
|
2617
|
-
if (_nativeTabBarTrailingReservedWidth > 0) {
|
|
2618
|
-
[_stickyHeaderView.superview bringSubviewToFront:_stickyHeaderView];
|
|
2619
|
-
} else {
|
|
2620
|
-
[_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
|
|
2621
|
-
if (!_nativeSubHeaderView.hidden &&
|
|
2622
|
-
_nativeSubHeaderView.superview == _nativeTabBarView.superview) {
|
|
2623
|
-
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
2607
|
- (void)updatePinnedStickyHeader
|
|
2630
2608
|
{
|
|
2631
2609
|
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
@@ -2672,10 +2650,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2672
2650
|
CGFloat childY = shouldPin ? 0 : _headerHeight;
|
|
2673
2651
|
CGFloat width = CGRectGetWidth(self.bounds);
|
|
2674
2652
|
CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2675
|
-
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2676
2653
|
_stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
|
|
2677
|
-
_nativeTabBarView.frame = CGRectMake(
|
|
2678
|
-
childY, width - tabReservedWidth, tabHeight);
|
|
2654
|
+
_nativeTabBarView.frame = CGRectMake(0, childY, width, tabHeight);
|
|
2679
2655
|
_nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
|
|
2680
2656
|
MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
|
|
2681
2657
|
_pinnedStickyHostView.hidden = !shouldPin;
|
|
@@ -2695,7 +2671,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2695
2671
|
}
|
|
2696
2672
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2697
2673
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2698
|
-
[self bringReservedStickyControlToFront];
|
|
2699
2674
|
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2700
2675
|
[self logHomeVisualGeometry:shouldPin ? @"pin" : @"unpin"
|
|
2701
2676
|
scrollView:_observedScrollView];
|
|
@@ -3075,7 +3050,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3075
3050
|
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
3076
3051
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
3077
3052
|
if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
3078
|
-
[self bringReservedStickyControlToFront];
|
|
3079
3053
|
[_containerView bringSubviewToFront:_sharedHeaderHostView];
|
|
3080
3054
|
if (_stickyHeaderIsPinned) {
|
|
3081
3055
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -3288,7 +3262,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3288
3262
|
_homeVisualLastLayoutLog = 0;
|
|
3289
3263
|
_homeVisualLastGeometrySignature = nil;
|
|
3290
3264
|
_nativeTabBarHeight = 44;
|
|
3291
|
-
_nativeTabBarTrailingReservedWidth = 0;
|
|
3292
3265
|
_nativeSubHeaderHeight = 74;
|
|
3293
3266
|
_headerOffset = 0;
|
|
3294
3267
|
_currentLogicalOffset = 0;
|
|
@@ -3370,13 +3343,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3370
3343
|
_layoutDirection = RCTNSStringFromString(toString(newViewProps.layoutDirection));
|
|
3371
3344
|
[_nativeTabBarView setLeftToRight:self.isLtrLayout];
|
|
3372
3345
|
[_nativeSubHeaderView setLeftToRight:self.isLtrLayout];
|
|
3373
|
-
[self setNeedsLayout];
|
|
3374
|
-
}
|
|
3375
|
-
if (_needsPropsReapply ||
|
|
3376
|
-
oldViewProps.nativeTabBarTrailingReservedWidth != newViewProps.nativeTabBarTrailingReservedWidth) {
|
|
3377
|
-
double reserved = newViewProps.nativeTabBarTrailingReservedWidth;
|
|
3378
|
-
_nativeTabBarTrailingReservedWidth = std::isfinite(reserved) && reserved >= 0 ? reserved : 0;
|
|
3379
|
-
[self setNeedsLayout];
|
|
3380
3346
|
}
|
|
3381
3347
|
BOOL insetsChanged = NO;
|
|
3382
3348
|
if (_needsPropsReapply || oldViewProps.headerHeight != newViewProps.headerHeight) {
|
|
@@ -3508,7 +3474,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3508
3474
|
if (!_nativeSubHeaderView.hidden) {
|
|
3509
3475
|
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
3510
3476
|
}
|
|
3511
|
-
[self bringReservedStickyControlToFront];
|
|
3512
3477
|
[_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
|
|
3513
3478
|
if (_stickyHeaderIsPinned) {
|
|
3514
3479
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -4473,9 +4438,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4473
4438
|
|
|
4474
4439
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
4475
4440
|
{
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
[host setPagerStickyHeight:height forOwner:self];
|
|
4441
|
+
if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
|
|
4442
|
+
[(RNCNativeScrollerComponentView *)scrollView.superview setPagerStickyHeight:height forOwner:self];
|
|
4479
4443
|
}
|
|
4480
4444
|
}
|
|
4481
4445
|
|
|
@@ -5269,13 +5233,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5269
5233
|
{
|
|
5270
5234
|
UIScrollView *scrollView = _pagerTopScrollView;
|
|
5271
5235
|
if (_dispatchingPagerTop || ![self matchesPagerTop:scrollView]) return;
|
|
5272
|
-
CGFloat clampedTarget = MIN(MAX(_pagerTopLogicalTarget, 0), _headerHeight);
|
|
5273
|
-
if (clampedTarget != _pagerTopLogicalTarget) {
|
|
5274
|
-
_pagerTopLogicalTarget = clampedTarget;
|
|
5275
|
-
CGFloat physicalTarget = RNCLimitRestoredScrollOffset(clampedTarget - scrollView.contentInset.top,
|
|
5276
|
-
scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
|
|
5277
|
-
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:NO];
|
|
5278
|
-
}
|
|
5279
5236
|
CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
5280
5237
|
if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
|
|
5281
5238
|
_pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
|
|
@@ -5287,29 +5244,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5287
5244
|
}
|
|
5288
5245
|
|
|
5289
5246
|
- (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
|
|
5290
|
-
{
|
|
5291
|
-
if (target != 0 && target != 1) return;
|
|
5292
|
-
[self scrollHeaderToOffset:(target == 1 ? _headerHeight : 0) animated:animated];
|
|
5293
|
-
}
|
|
5294
|
-
|
|
5295
|
-
- (void)scrollHeaderBy:(double)delta
|
|
5296
|
-
{
|
|
5297
|
-
if (!std::isfinite(delta)) return;
|
|
5298
|
-
[self scrollHeaderToOffset:MIN(MAX(_headerOffset + delta, 0), _headerHeight) animated:NO];
|
|
5299
|
-
}
|
|
5300
|
-
|
|
5301
|
-
- (void)scrollHeaderToOffset:(double)offset
|
|
5302
|
-
{
|
|
5303
|
-
if (!std::isfinite(offset)) return;
|
|
5304
|
-
[self scrollHeaderToOffset:MIN(MAX(offset, 0), _headerHeight) animated:NO];
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
- (void)scrollHeaderToOffset:(CGFloat)logicalTarget animated:(BOOL)animated
|
|
5308
5247
|
{
|
|
5309
5248
|
[self cancelTopInsetReboundCheck];
|
|
5310
5249
|
UIScrollView *scrollView = _observedScrollView;
|
|
5311
5250
|
NSString *key = _observedPageKey;
|
|
5312
|
-
if (_isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
5251
|
+
if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
5313
5252
|
_transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
|
|
5314
5253
|
key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
|
|
5315
5254
|
_observedPageGeneration != _generation || self.window == nil) return;
|
|
@@ -5325,7 +5264,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5325
5264
|
[_alignedBodyKeys removeObject:key];
|
|
5326
5265
|
[_suspendedBodyKeys removeObject:key];
|
|
5327
5266
|
[_displayBodyOverrides removeObjectForKey:key];
|
|
5328
|
-
logicalTarget =
|
|
5267
|
+
CGFloat logicalTarget = target == 1 ? _headerHeight : 0;
|
|
5329
5268
|
_pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
|
|
5330
5269
|
_pagerTopScrollView = scrollView;
|
|
5331
5270
|
_pagerTopPageKey = [key copy];
|
|
@@ -2,22 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
4
4
|
#import "RNCPagerViewComponentView.h"
|
|
5
|
-
#import <React/RCTViewComponentView.h>
|
|
6
5
|
#import <react/renderer/components/pagerview/Props.h>
|
|
7
|
-
#import <react/renderer/components/view/ViewProps.h>
|
|
8
6
|
|
|
9
7
|
using namespace facebook::react;
|
|
10
8
|
|
|
11
9
|
@interface RNCCollapsiblePagerViewComponentView (ScrollEdgeTesting)
|
|
12
10
|
- (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView;
|
|
13
11
|
- (void)updateScrollEdgeContentMasks;
|
|
14
|
-
- (void)layoutSharedHeaders;
|
|
15
|
-
@end
|
|
16
|
-
|
|
17
|
-
@interface UIView (NativeTabReservationTesting)
|
|
18
|
-
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
19
|
-
- (void)setProgress:(CGFloat)progress;
|
|
20
|
-
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
21
12
|
@end
|
|
22
13
|
|
|
23
14
|
@interface RNCCollapsiblePagerHitTestTests : XCTestCase
|
|
@@ -65,84 +56,6 @@ using namespace facebook::react;
|
|
|
65
56
|
XCTAssertEqual([self.root hitTest:CGPointMake(20, 80) withEvent:nil], self.toolbar);
|
|
66
57
|
}
|
|
67
58
|
|
|
68
|
-
- (void)testNativeTabReservationKeepsReactToolbarAboveTabsAndResetsInRTL
|
|
69
|
-
{
|
|
70
|
-
UIView *sharedHost = [self.pager valueForKey:@"sharedHeaderHostView"];
|
|
71
|
-
UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
|
|
72
|
-
[tabBar updateItemsJSON:@"[{\"key\":\"a\",\"title\":\"Overview\"},"
|
|
73
|
-
@"{\"key\":\"b\",\"title\":\"Ethereum\"},"
|
|
74
|
-
@"{\"key\":\"c\",\"title\":\"BNB Chain\"},"
|
|
75
|
-
@"{\"key\":\"d\",\"title\":\"Polygon\"}]"];
|
|
76
|
-
RCTViewComponentView *sticky = [RCTViewComponentView new];
|
|
77
|
-
auto stickyProps = std::make_shared<ViewProps>();
|
|
78
|
-
stickyProps->pointerEvents = PointerEventsMode::BoxNone;
|
|
79
|
-
[sticky updateProps:stickyProps oldProps:nullptr];
|
|
80
|
-
UIButton *trailingControl = [[UIButton alloc] initWithFrame:CGRectMake(250, 0, 70, 44)];
|
|
81
|
-
[sticky addSubview:trailingControl];
|
|
82
|
-
[sharedHost addSubview:sticky];
|
|
83
|
-
[sharedHost addSubview:tabBar];
|
|
84
|
-
[self.pager setValue:sticky forKey:@"stickyHeaderView"];
|
|
85
|
-
[self.pager setValue:@0 forKey:@"headerHeight"];
|
|
86
|
-
[self.pager setValue:@44 forKey:@"stickyHeaderHeight"];
|
|
87
|
-
tabBar.hidden = NO;
|
|
88
|
-
|
|
89
|
-
[self.pager setValue:@64 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
90
|
-
[self.pager layoutSharedHeaders];
|
|
91
|
-
XCTAssertEqualWithAccuracy(CGRectGetWidth(sticky.frame), 320, 0.001);
|
|
92
|
-
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 256, 0.001);
|
|
93
|
-
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 0, 0.001);
|
|
94
|
-
XCTAssertEqual(sharedHost.subviews.lastObject, sticky);
|
|
95
|
-
XCTAssertEqual([sharedHost hitTest:CGPointMake(300, 22) withEvent:nil], trailingControl);
|
|
96
|
-
XCTAssertTrue([tabBar pointInside:CGPointMake(250, 22) withEvent:nil]);
|
|
97
|
-
[tabBar layoutIfNeeded];
|
|
98
|
-
[tabBar setProgress:3];
|
|
99
|
-
UIScrollView *tabScroll = (UIScrollView *)tabBar.subviews.firstObject;
|
|
100
|
-
NSArray<UIButton *> *buttons = [tabScroll.subviews filteredArrayUsingPredicate:
|
|
101
|
-
[NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
|
|
102
|
-
return [view isKindOfClass:UIButton.class];
|
|
103
|
-
}]];
|
|
104
|
-
UIButton *lastTab = buttons.lastObject;
|
|
105
|
-
XCTAssertNotNil(lastTab);
|
|
106
|
-
XCTAssertGreaterThanOrEqual(CGRectGetMinX(lastTab.frame), tabScroll.contentOffset.x - 1);
|
|
107
|
-
XCTAssertLessThanOrEqual(CGRectGetMaxX(lastTab.frame),
|
|
108
|
-
tabScroll.contentOffset.x + CGRectGetWidth(tabScroll.bounds) + 1);
|
|
109
|
-
|
|
110
|
-
[self.pager setValue:@"rtl" forKey:@"layoutDirection"];
|
|
111
|
-
[tabBar setLeftToRight:NO];
|
|
112
|
-
trailingControl.frame = CGRectMake(0, 0, 70, 44);
|
|
113
|
-
[self.pager layoutSharedHeaders];
|
|
114
|
-
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 64, 0.001);
|
|
115
|
-
XCTAssertEqualWithAccuracy(CGRectGetMaxX(tabBar.frame), 320, 0.001);
|
|
116
|
-
XCTAssertEqual([sharedHost hitTest:CGPointMake(20, 22) withEvent:nil], trailingControl);
|
|
117
|
-
|
|
118
|
-
[self.pager setValue:@500 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
119
|
-
[self.pager layoutSharedHeaders];
|
|
120
|
-
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 320, 0.001);
|
|
121
|
-
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 0, 0.001);
|
|
122
|
-
|
|
123
|
-
[self.pager setValue:@0 forKey:@"nativeTabBarTrailingReservedWidth"];
|
|
124
|
-
[self.pager layoutSharedHeaders];
|
|
125
|
-
XCTAssertEqualWithAccuracy(CGRectGetMinX(tabBar.frame), 0, 0.001);
|
|
126
|
-
XCTAssertEqualWithAccuracy(CGRectGetWidth(tabBar.frame), 320, 0.001);
|
|
127
|
-
XCTAssertEqual(sharedHost.subviews.lastObject, tabBar);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
- (void)testNativeTabReservationPropsRestoreDefaultOnRemoval
|
|
131
|
-
{
|
|
132
|
-
auto reservedProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
133
|
-
reservedProps->nativeSmoothHeaderScrollEnabled = true;
|
|
134
|
-
reservedProps->nativeTabBarTrailingReservedWidth = 64;
|
|
135
|
-
[self.pager updateProps:reservedProps oldProps:nullptr];
|
|
136
|
-
XCTAssertEqualWithAccuracy(
|
|
137
|
-
[[self.pager valueForKey:@"nativeTabBarTrailingReservedWidth"] doubleValue], 64, 0.001);
|
|
138
|
-
|
|
139
|
-
auto defaultProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
140
|
-
defaultProps->nativeSmoothHeaderScrollEnabled = true;
|
|
141
|
-
[self.pager updateProps:defaultProps oldProps:reservedProps];
|
|
142
|
-
XCTAssertEqualWithAccuracy(
|
|
143
|
-
[[self.pager valueForKey:@"nativeTabBarTrailingReservedWidth"] doubleValue], 0, 0.001);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
59
|
- (void)testPinnedChildOutsideHeaderHostStillReceivesViewportTouches
|
|
147
60
|
{
|
|
148
61
|
XCTAssertEqual([self.pager hitTest:CGPointMake(20, 90) withEvent:nil], self.sticky);
|
|
@@ -82,12 +82,6 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
82
82
|
scrollToPageTop = options => {
|
|
83
83
|
if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 0, options?.animated ?? true);
|
|
84
84
|
};
|
|
85
|
-
scrollHeaderBy = delta => {
|
|
86
|
-
if (this.nativeRef && Number.isFinite(delta)) CollapsiblePagerViewNativeCommands.scrollHeaderBy(this.nativeRef, delta);
|
|
87
|
-
};
|
|
88
|
-
scrollHeaderToOffset = offset => {
|
|
89
|
-
if (this.nativeRef && Number.isFinite(offset)) CollapsiblePagerViewNativeCommands.scrollHeaderToOffset(this.nativeRef, offset);
|
|
90
|
-
};
|
|
91
85
|
scrollToStickyTop = options => {
|
|
92
86
|
if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 1, options?.animated ?? true);
|
|
93
87
|
};
|
|
@@ -102,7 +96,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
102
96
|
nativeTabBarEnabled() {
|
|
103
97
|
return (Platform.OS === "ios" || Platform.OS === "android") && !!this.props.nativeTabBar?.items.length;
|
|
104
98
|
}
|
|
105
|
-
dispatchNativeTabPageCommand(position, animated
|
|
99
|
+
dispatchNativeTabPageCommand(position, animated) {
|
|
106
100
|
const commandId = ++this.nativeTabCommandId;
|
|
107
101
|
this.nativeTabCommandQueuedDuringScroll = this.nativePagerScrollState !== "idle";
|
|
108
102
|
this.latestNativeTabTarget = position;
|
|
@@ -110,7 +104,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
110
104
|
transientRetainedPages: state.transientRetainedPages.includes(position) ? state.transientRetainedPages : [...state.transientRetainedPages, position]
|
|
111
105
|
}), () => {
|
|
112
106
|
requestAnimationFrame(() => {
|
|
113
|
-
if (commandId !== this.nativeTabCommandId || !this.nativeRef
|
|
107
|
+
if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
|
|
114
108
|
return;
|
|
115
109
|
}
|
|
116
110
|
this.dispatchedNativeTabTarget = position;
|
|
@@ -192,13 +186,10 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
192
186
|
};
|
|
193
187
|
onNativeTabPress = event => {
|
|
194
188
|
const position = pageIndex(event.nativeEvent.position);
|
|
195
|
-
if (
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
|
|
189
|
+
if (position !== null && position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
|
|
199
190
|
// Selection state lags an in-flight transition until native reports it.
|
|
200
191
|
const currentPage = this.dispatchedNativeTabTarget ?? this.state.selectedPage;
|
|
201
|
-
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1
|
|
192
|
+
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1);
|
|
202
193
|
}
|
|
203
194
|
this.props.onNativeTabPress?.(event);
|
|
204
195
|
};
|
|
@@ -242,8 +233,6 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
242
233
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
243
234
|
const nativeTabBarEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeTabBar?.items.length;
|
|
244
235
|
const nativeTabBarStyle = nativeTabBar?.style;
|
|
245
|
-
const requestedTrailingReservedWidth = nativeTabBarStyle?.trailingReservedWidth;
|
|
246
|
-
const nativeTabBarTrailingReservedWidth = typeof requestedTrailingReservedWidth === "number" && Number.isFinite(requestedTrailingReservedWidth) && requestedTrailingReservedWidth >= 0 ? requestedTrailingReservedWidth : 0;
|
|
247
236
|
const nativeSubHeaderEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeSubHeader?.items.length;
|
|
248
237
|
const nativeSubHeaderStyle = nativeSubHeader?.style;
|
|
249
238
|
const nativeSubHeaderConfig = nativeSubHeaderEnabled ? JSON.stringify({
|
|
@@ -274,7 +263,6 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
274
263
|
onPageScrollStateChanged: this.onPageScrollStateChanged,
|
|
275
264
|
nativeTabBarItems: nativeTabBarEnabled ? JSON.stringify(nativeTabBar.items) : undefined,
|
|
276
265
|
nativeTabBarHeight: nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined,
|
|
277
|
-
nativeTabBarTrailingReservedWidth: nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0,
|
|
278
266
|
nativeTabBarContentPaddingHorizontal: nativeTabBarEnabled ? nativeTabBarStyle?.contentPaddingHorizontal ?? 20 : undefined,
|
|
279
267
|
nativeTabBarItemSpacing: nativeTabBarEnabled ? nativeTabBarStyle?.itemSpacing ?? 8 : undefined,
|
|
280
268
|
nativeTabBarFontSize: nativeTabBarEnabled ? nativeTabBarStyle?.fontSize ?? 16 : undefined,
|
|
@@ -335,15 +335,6 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
335
335
|
scrollToPageTop = options => {
|
|
336
336
|
this.scrollCurrentPageToTop(0, options?.animated ?? true);
|
|
337
337
|
};
|
|
338
|
-
scrollHeaderBy = delta => {
|
|
339
|
-
if (!Number.isFinite(delta)) return;
|
|
340
|
-
const offset = Math.min(this.headerHeight, Math.max(0, this.sharedHeaderOffset + delta));
|
|
341
|
-
this.scrollCurrentPageToTop(offset, false);
|
|
342
|
-
};
|
|
343
|
-
scrollHeaderToOffset = offset => {
|
|
344
|
-
if (!Number.isFinite(offset)) return;
|
|
345
|
-
this.scrollCurrentPageToTop(Math.min(this.headerHeight, Math.max(0, offset)), false);
|
|
346
|
-
};
|
|
347
338
|
scrollToStickyTop = options => {
|
|
348
339
|
this.scrollCurrentPageToTop(this.headerHeight, options?.animated ?? true);
|
|
349
340
|
};
|
|
@@ -395,13 +386,13 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
395
386
|
this.pendingTopFrame = null;
|
|
396
387
|
if (!element.isConnected || this.horizontalTouch || this.transitionTimer !== null || this.pageScrollElement(this.state.selectedPage) !== element || this.pages().map(pageKey)[this.state.selectedPage] !== key) return;
|
|
397
388
|
this.pageOffsets.set(key, 0);
|
|
398
|
-
this.sharedHeaderOffset =
|
|
389
|
+
this.sharedHeaderOffset = offset;
|
|
399
390
|
this.bodyAlignment = {
|
|
400
391
|
element,
|
|
401
392
|
key
|
|
402
393
|
};
|
|
403
394
|
scrollElementTo(element, {
|
|
404
|
-
top:
|
|
395
|
+
top: offset,
|
|
405
396
|
behavior: animated ? "smooth" : "instant"
|
|
406
397
|
});
|
|
407
398
|
this.syncFallbackHeader();
|
|
@@ -67,7 +67,6 @@ export interface NativeProps extends ViewProps {
|
|
|
67
67
|
retainedPages?: string;
|
|
68
68
|
nativeTabBarItems?: string;
|
|
69
69
|
nativeTabBarHeight?: Double;
|
|
70
|
-
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
71
70
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
72
71
|
nativeTabBarItemSpacing?: Double;
|
|
73
72
|
nativeTabBarFontSize?: Double;
|
|
@@ -92,14 +91,6 @@ export interface NativeProps extends ViewProps {
|
|
|
92
91
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
93
92
|
|
|
94
93
|
export interface NativeCommands {
|
|
95
|
-
scrollHeaderToOffset: (
|
|
96
|
-
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
97
|
-
offset: Double
|
|
98
|
-
) => void;
|
|
99
|
-
scrollHeaderBy: (
|
|
100
|
-
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
101
|
-
delta: Double
|
|
102
|
-
) => void;
|
|
103
94
|
scrollToTop: (
|
|
104
95
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
105
96
|
target: Int32,
|
|
@@ -121,8 +112,6 @@ export interface NativeCommands {
|
|
|
121
112
|
|
|
122
113
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
123
114
|
supportedCommands: [
|
|
124
|
-
"scrollHeaderToOffset",
|
|
125
|
-
"scrollHeaderBy",
|
|
126
115
|
"scrollToTop",
|
|
127
116
|
"setPage",
|
|
128
117
|
"setPageWithoutAnimation",
|
|
@@ -17,7 +17,6 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
|
|
|
17
17
|
}>;
|
|
18
18
|
export type CollapsiblePagerNativeTabBarStyle = Readonly<{
|
|
19
19
|
height?: number;
|
|
20
|
-
trailingReservedWidth?: number;
|
|
21
20
|
contentPaddingHorizontal?: number;
|
|
22
21
|
itemSpacing?: number;
|
|
23
22
|
fontSize?: number;
|
|
@@ -57,7 +56,7 @@ export type CollapsiblePagerNativeSubHeaderConfig = Readonly<{
|
|
|
57
56
|
}>;
|
|
58
57
|
export type CollapsiblePagerViewOnNativeTabPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
59
58
|
export type CollapsiblePagerViewOnNativeSubHeaderPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
60
|
-
export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "topScrollEdgeEffect" | "topOverlayHeight" | "externalTopOverlayTag" | "pageKeys" | "retainedPages" | "nativeTabBarItems" | "nativeTabBarHeight" | "
|
|
59
|
+
export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "topScrollEdgeEffect" | "topOverlayHeight" | "externalTopOverlayTag" | "pageKeys" | "retainedPages" | "nativeTabBarItems" | "nativeTabBarHeight" | "nativeTabBarContentPaddingHorizontal" | "nativeTabBarItemSpacing" | "nativeTabBarFontSize" | "nativeTabBarFontFamily" | "nativeTabBarBackgroundColor" | "nativeTabBarActiveTextColor" | "nativeTabBarInactiveTextColor" | "nativeTabBarIndicatorColor" | "nativeTabBarIndicatorHeight" | "nativeTabBarIndicatorBottom" | "nativeSubHeaderConfig" | "nativeSubHeaderSelectedBackgroundColor" | "onNativeTabPress" | "onNativeSubHeaderPress" | "layoutDirection"> {
|
|
61
60
|
/** Collapsible content rendered above the sticky bar. */
|
|
62
61
|
header: React.ReactNode;
|
|
63
62
|
/** Bar which remains pinned after the header has collapsed. */
|
|
@@ -129,8 +128,6 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
129
128
|
scrollToPageTop: (options?: Readonly<{
|
|
130
129
|
animated?: boolean;
|
|
131
130
|
}>) => void;
|
|
132
|
-
scrollHeaderBy: (delta: number) => void;
|
|
133
|
-
scrollHeaderToOffset: (offset: number) => void;
|
|
134
131
|
scrollToStickyTop: (options?: Readonly<{
|
|
135
132
|
animated?: boolean;
|
|
136
133
|
}>) => void;
|
|
@@ -132,8 +132,6 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
132
132
|
scrollToPageTop: (options?: Readonly<{
|
|
133
133
|
animated?: boolean;
|
|
134
134
|
}>) => void;
|
|
135
|
-
scrollHeaderBy: (delta: number) => void;
|
|
136
|
-
scrollHeaderToOffset: (offset: number) => void;
|
|
137
135
|
scrollToStickyTop: (options?: Readonly<{
|
|
138
136
|
animated?: boolean;
|
|
139
137
|
}>) => void;
|
|
@@ -50,7 +50,6 @@ export interface NativeProps extends ViewProps {
|
|
|
50
50
|
retainedPages?: string;
|
|
51
51
|
nativeTabBarItems?: string;
|
|
52
52
|
nativeTabBarHeight?: Double;
|
|
53
|
-
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
54
53
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
55
54
|
nativeTabBarItemSpacing?: Double;
|
|
56
55
|
nativeTabBarFontSize?: Double;
|
|
@@ -73,8 +72,6 @@ export interface NativeProps extends ViewProps {
|
|
|
73
72
|
}
|
|
74
73
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
75
74
|
export interface NativeCommands {
|
|
76
|
-
scrollHeaderToOffset: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, offset: Double) => void;
|
|
77
|
-
scrollHeaderBy: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, delta: Double) => void;
|
|
78
75
|
scrollToTop: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, target: Int32, animated: boolean) => void;
|
|
79
76
|
setPage: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
|
|
80
77
|
setPageWithoutAnimation: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-pager-view",
|
|
3
|
-
"version": "6.7.0-alpha.
|
|
3
|
+
"version": "6.7.0-alpha.362",
|
|
4
4
|
"description": "React Native wrapper for Android and iOS ViewPager",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"typescript": "^5.9.2"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.
|
|
76
|
+
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.362",
|
|
77
77
|
"react": "*",
|
|
78
78
|
"react-native": "*"
|
|
79
79
|
},
|
|
@@ -33,7 +33,6 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
|
|
|
33
33
|
|
|
34
34
|
export type CollapsiblePagerNativeTabBarStyle = Readonly<{
|
|
35
35
|
height?: number;
|
|
36
|
-
trailingReservedWidth?: number;
|
|
37
36
|
contentPaddingHorizontal?: number;
|
|
38
37
|
itemSpacing?: number;
|
|
39
38
|
fontSize?: number;
|
|
@@ -95,7 +94,6 @@ export interface CollapsiblePagerViewProps
|
|
|
95
94
|
| "retainedPages"
|
|
96
95
|
| "nativeTabBarItems"
|
|
97
96
|
| "nativeTabBarHeight"
|
|
98
|
-
| "nativeTabBarTrailingReservedWidth"
|
|
99
97
|
| "nativeTabBarContentPaddingHorizontal"
|
|
100
98
|
| "nativeTabBarItemSpacing"
|
|
101
99
|
| "nativeTabBarFontSize"
|
|
@@ -292,22 +290,6 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
292
290
|
);
|
|
293
291
|
};
|
|
294
292
|
|
|
295
|
-
public scrollHeaderBy = (delta: number) => {
|
|
296
|
-
if (this.nativeRef && Number.isFinite(delta))
|
|
297
|
-
CollapsiblePagerViewNativeCommands.scrollHeaderBy(
|
|
298
|
-
this.nativeRef,
|
|
299
|
-
delta
|
|
300
|
-
);
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
public scrollHeaderToOffset = (offset: number) => {
|
|
304
|
-
if (this.nativeRef && Number.isFinite(offset))
|
|
305
|
-
CollapsiblePagerViewNativeCommands.scrollHeaderToOffset(
|
|
306
|
-
this.nativeRef,
|
|
307
|
-
offset
|
|
308
|
-
);
|
|
309
|
-
};
|
|
310
|
-
|
|
311
293
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
312
294
|
if (this.nativeRef)
|
|
313
295
|
CollapsiblePagerViewNativeCommands.scrollToTop(
|
|
@@ -337,11 +319,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
337
319
|
);
|
|
338
320
|
}
|
|
339
321
|
|
|
340
|
-
private dispatchNativeTabPageCommand(
|
|
341
|
-
position: number,
|
|
342
|
-
animated: boolean,
|
|
343
|
-
expectedKey?: string
|
|
344
|
-
) {
|
|
322
|
+
private dispatchNativeTabPageCommand(position: number, animated: boolean) {
|
|
345
323
|
const commandId = ++this.nativeTabCommandId;
|
|
346
324
|
this.nativeTabCommandQueuedDuringScroll =
|
|
347
325
|
this.nativePagerScrollState !== "idle";
|
|
@@ -354,13 +332,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
354
332
|
}),
|
|
355
333
|
() => {
|
|
356
334
|
requestAnimationFrame(() => {
|
|
357
|
-
if (
|
|
358
|
-
commandId !== this.nativeTabCommandId ||
|
|
359
|
-
!this.nativeRef ||
|
|
360
|
-
(expectedKey !== undefined &&
|
|
361
|
-
(!this.nativeTabBarEnabled() ||
|
|
362
|
-
this.props.nativeTabBar?.items[position]?.key !== expectedKey))
|
|
363
|
-
) {
|
|
335
|
+
if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
|
|
364
336
|
return;
|
|
365
337
|
}
|
|
366
338
|
this.dispatchedNativeTabTarget = position;
|
|
@@ -486,13 +458,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
486
458
|
) => {
|
|
487
459
|
const position = pageIndex(event.nativeEvent.position);
|
|
488
460
|
if (
|
|
489
|
-
|
|
490
|
-
position === null ||
|
|
491
|
-
this.props.nativeTabBar?.items[position]?.key !== event.nativeEvent.key
|
|
492
|
-
) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (
|
|
461
|
+
position !== null &&
|
|
496
462
|
position >= 0 &&
|
|
497
463
|
position < this.pages().length &&
|
|
498
464
|
(position !== this.state.selectedPage ||
|
|
@@ -504,8 +470,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
504
470
|
this.dispatchNativeTabPageCommand(
|
|
505
471
|
position,
|
|
506
472
|
this.props.nativeTabPressAnimationEnabled ??
|
|
507
|
-
Math.abs(position - currentPage) <= 1
|
|
508
|
-
event.nativeEvent.key
|
|
473
|
+
Math.abs(position - currentPage) <= 1
|
|
509
474
|
);
|
|
510
475
|
}
|
|
511
476
|
this.props.onNativeTabPress?.(event);
|
|
@@ -568,14 +533,6 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
568
533
|
(Platform.OS === "ios" || Platform.OS === "android") &&
|
|
569
534
|
!!nativeTabBar?.items.length;
|
|
570
535
|
const nativeTabBarStyle = nativeTabBar?.style;
|
|
571
|
-
const requestedTrailingReservedWidth =
|
|
572
|
-
nativeTabBarStyle?.trailingReservedWidth;
|
|
573
|
-
const nativeTabBarTrailingReservedWidth =
|
|
574
|
-
typeof requestedTrailingReservedWidth === "number" &&
|
|
575
|
-
Number.isFinite(requestedTrailingReservedWidth) &&
|
|
576
|
-
requestedTrailingReservedWidth >= 0
|
|
577
|
-
? requestedTrailingReservedWidth
|
|
578
|
-
: 0;
|
|
579
536
|
const nativeSubHeaderEnabled =
|
|
580
537
|
(Platform.OS === "ios" || Platform.OS === "android") &&
|
|
581
538
|
!!nativeSubHeader?.items.length;
|
|
@@ -629,9 +586,6 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
629
586
|
nativeTabBarHeight={
|
|
630
587
|
nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined
|
|
631
588
|
}
|
|
632
|
-
nativeTabBarTrailingReservedWidth={
|
|
633
|
-
nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0
|
|
634
|
-
}
|
|
635
589
|
nativeTabBarContentPaddingHorizontal={
|
|
636
590
|
nativeTabBarEnabled
|
|
637
591
|
? nativeTabBarStyle?.contentPaddingHorizontal ?? 20
|
|
@@ -580,23 +580,6 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
580
580
|
this.scrollCurrentPageToTop(0, options?.animated ?? true);
|
|
581
581
|
};
|
|
582
582
|
|
|
583
|
-
public scrollHeaderBy = (delta: number) => {
|
|
584
|
-
if (!Number.isFinite(delta)) return;
|
|
585
|
-
const offset = Math.min(
|
|
586
|
-
this.headerHeight,
|
|
587
|
-
Math.max(0, this.sharedHeaderOffset + delta)
|
|
588
|
-
);
|
|
589
|
-
this.scrollCurrentPageToTop(offset, false);
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
public scrollHeaderToOffset = (offset: number) => {
|
|
593
|
-
if (!Number.isFinite(offset)) return;
|
|
594
|
-
this.scrollCurrentPageToTop(
|
|
595
|
-
Math.min(this.headerHeight, Math.max(0, offset)),
|
|
596
|
-
false
|
|
597
|
-
);
|
|
598
|
-
};
|
|
599
|
-
|
|
600
583
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
601
584
|
this.scrollCurrentPageToTop(
|
|
602
585
|
this.headerHeight,
|
|
@@ -691,10 +674,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
691
674
|
)
|
|
692
675
|
return;
|
|
693
676
|
this.pageOffsets.set(key, 0);
|
|
694
|
-
this.sharedHeaderOffset =
|
|
677
|
+
this.sharedHeaderOffset = offset;
|
|
695
678
|
this.bodyAlignment = { element, key };
|
|
696
679
|
scrollElementTo(element, {
|
|
697
|
-
top:
|
|
680
|
+
top: offset,
|
|
698
681
|
behavior: animated ? "smooth" : "instant",
|
|
699
682
|
});
|
|
700
683
|
this.syncFallbackHeader();
|
|
@@ -67,7 +67,6 @@ export interface NativeProps extends ViewProps {
|
|
|
67
67
|
retainedPages?: string;
|
|
68
68
|
nativeTabBarItems?: string;
|
|
69
69
|
nativeTabBarHeight?: Double;
|
|
70
|
-
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
71
70
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
72
71
|
nativeTabBarItemSpacing?: Double;
|
|
73
72
|
nativeTabBarFontSize?: Double;
|
|
@@ -92,14 +91,6 @@ export interface NativeProps extends ViewProps {
|
|
|
92
91
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
93
92
|
|
|
94
93
|
export interface NativeCommands {
|
|
95
|
-
scrollHeaderToOffset: (
|
|
96
|
-
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
97
|
-
offset: Double
|
|
98
|
-
) => void;
|
|
99
|
-
scrollHeaderBy: (
|
|
100
|
-
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
101
|
-
delta: Double
|
|
102
|
-
) => void;
|
|
103
94
|
scrollToTop: (
|
|
104
95
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
105
96
|
target: Int32,
|
|
@@ -121,8 +112,6 @@ export interface NativeCommands {
|
|
|
121
112
|
|
|
122
113
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
123
114
|
supportedCommands: [
|
|
124
|
-
"scrollHeaderToOffset",
|
|
125
|
-
"scrollHeaderBy",
|
|
126
115
|
"scrollToTop",
|
|
127
116
|
"setPage",
|
|
128
117
|
"setPageWithoutAnimation",
|
package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
package com.reactnativepagerview
|
|
2
|
-
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.content.Intent
|
|
5
|
-
import android.content.pm.ApplicationInfo
|
|
6
|
-
import android.graphics.Bitmap
|
|
7
|
-
import android.graphics.Canvas
|
|
8
|
-
import android.graphics.Color
|
|
9
|
-
import android.test.InstrumentationTestCase
|
|
10
|
-
import android.view.MotionEvent
|
|
11
|
-
import android.view.View
|
|
12
|
-
import android.view.ViewGroup
|
|
13
|
-
import android.widget.HorizontalScrollView
|
|
14
|
-
import android.widget.TextView
|
|
15
|
-
import com.facebook.react.uimanager.PointerEvents
|
|
16
|
-
import com.facebook.react.views.view.ReactViewGroup
|
|
17
|
-
import java.util.concurrent.atomic.AtomicInteger
|
|
18
|
-
import kotlin.math.roundToInt
|
|
19
|
-
|
|
20
|
-
class CollapsiblePagerHitTestActivity : Activity()
|
|
21
|
-
|
|
22
|
-
class CollapsiblePagerTabReservationTest : InstrumentationTestCase() {
|
|
23
|
-
fun testTabViewportToolbarHitAndResetInBothDirections() {
|
|
24
|
-
val context = instrumentation.targetContext
|
|
25
|
-
assertTrue(context.applicationInfo.flags and ApplicationInfo.FLAG_SUPPORTS_RTL != 0)
|
|
26
|
-
val activity = instrumentation.startActivitySync(
|
|
27
|
-
Intent(context, CollapsiblePagerHitTestActivity::class.java)
|
|
28
|
-
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
29
|
-
) as CollapsiblePagerHitTestActivity
|
|
30
|
-
val toolbarPresses = AtomicInteger()
|
|
31
|
-
val tabPresses = AtomicInteger()
|
|
32
|
-
try {
|
|
33
|
-
instrumentation.runOnMainSync {
|
|
34
|
-
val density = context.resources.displayMetrics.density
|
|
35
|
-
fun dp(value: Int) = (value * density).roundToInt()
|
|
36
|
-
val width = dp(320)
|
|
37
|
-
val height = dp(500)
|
|
38
|
-
val reserved = dp(64)
|
|
39
|
-
val tabHeight = dp(44)
|
|
40
|
-
val host = CollapsiblePagerHost(context)
|
|
41
|
-
activity.setContentView(host)
|
|
42
|
-
assertTrue(host.isAttachedToWindow)
|
|
43
|
-
val sticky = ReactViewGroup(context).apply { pointerEvents = PointerEvents.BOX_NONE }
|
|
44
|
-
val fade = View(context).apply { setBackgroundColor(Color.RED) }
|
|
45
|
-
val toolbar = View(context).apply {
|
|
46
|
-
setBackgroundColor(Color.BLUE)
|
|
47
|
-
isClickable = true
|
|
48
|
-
}
|
|
49
|
-
sticky.addView(fade)
|
|
50
|
-
sticky.addView(toolbar)
|
|
51
|
-
host.addReactChild(View(context), 0)
|
|
52
|
-
host.addReactChild(sticky, 1)
|
|
53
|
-
host.headerHeightPx = 0
|
|
54
|
-
host.stickyHeaderHeightPx = tabHeight
|
|
55
|
-
host.nativeTabBarBackgroundColor = Color.GREEN
|
|
56
|
-
host.applyNativeHeaderStyle()
|
|
57
|
-
host.updateNativeTabBarItems(
|
|
58
|
-
"""[{"key":"a","title":"Overview"},{"key":"b","title":"Ethereum"},""" +
|
|
59
|
-
"""{"key":"c","title":"BNB Chain"},{"key":"d","title":"Polygon"}]""",
|
|
60
|
-
)
|
|
61
|
-
val tab = (0 until host.childCount).map(host::getChildAt)
|
|
62
|
-
.first { it.javaClass.simpleName == "CollapsiblePagerNativeTabBarView" } as ViewGroup
|
|
63
|
-
val row = tab.getChildAt(0) as HorizontalScrollView
|
|
64
|
-
toolbar.setOnClickListener { toolbarPresses.incrementAndGet() }
|
|
65
|
-
host.onNativeTabPress = { _, _ -> tabPresses.incrementAndGet() }
|
|
66
|
-
|
|
67
|
-
fun layout() {
|
|
68
|
-
host.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
69
|
-
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
|
|
70
|
-
host.layout(0, 0, width, height)
|
|
71
|
-
}
|
|
72
|
-
fun tap(x: Int) {
|
|
73
|
-
val time = android.os.SystemClock.uptimeMillis()
|
|
74
|
-
val down = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
|
|
75
|
-
x.toFloat(), (tabHeight / 2).toFloat(), 0)
|
|
76
|
-
val up = MotionEvent.obtain(time, time + 20, MotionEvent.ACTION_UP,
|
|
77
|
-
x.toFloat(), (tabHeight / 2).toFloat(), 0)
|
|
78
|
-
host.dispatchTouchEvent(down)
|
|
79
|
-
host.dispatchTouchEvent(up)
|
|
80
|
-
down.recycle()
|
|
81
|
-
up.recycle()
|
|
82
|
-
}
|
|
83
|
-
fun colorAt(x: Int): Int {
|
|
84
|
-
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
85
|
-
host.draw(Canvas(bitmap))
|
|
86
|
-
val color = bitmap.getPixel(x, tabHeight / 2)
|
|
87
|
-
bitmap.recycle()
|
|
88
|
-
return color
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
host.nativeTabBarTrailingReservedWidth = 64.0
|
|
92
|
-
layout()
|
|
93
|
-
fade.layout(width - reserved - dp(20), 0, width - reserved, tabHeight)
|
|
94
|
-
toolbar.layout(width - reserved, 0, width, tabHeight)
|
|
95
|
-
assertEquals(width - reserved, tab.width)
|
|
96
|
-
assertEquals(0, tab.left)
|
|
97
|
-
assertEquals(width, sticky.width)
|
|
98
|
-
assertEquals(Color.RED, colorAt(width - reserved - dp(10)))
|
|
99
|
-
tap(width - dp(10))
|
|
100
|
-
tap(dp(28))
|
|
101
|
-
host.updateNativeTabProgress(3, 0f)
|
|
102
|
-
val content = row.getChildAt(0) as ViewGroup
|
|
103
|
-
val lastTab = (0 until content.childCount).map(content::getChildAt)
|
|
104
|
-
.filterIsInstance<TextView>().last()
|
|
105
|
-
assertTrue(lastTab.left >= row.scrollX - dp(1))
|
|
106
|
-
assertTrue(lastTab.right <= row.scrollX + row.width + dp(1))
|
|
107
|
-
|
|
108
|
-
host.setPagerLayoutDirection(View.LAYOUT_DIRECTION_RTL)
|
|
109
|
-
host.nativeTabBarTrailingReservedWidth = 64.0
|
|
110
|
-
layout()
|
|
111
|
-
assertEquals(View.LAYOUT_DIRECTION_RTL, tab.layoutDirection)
|
|
112
|
-
toolbar.layout(0, 0, reserved, tabHeight)
|
|
113
|
-
fade.layout(reserved, 0, reserved + dp(20), tabHeight)
|
|
114
|
-
assertEquals(reserved, tab.left)
|
|
115
|
-
assertEquals(width, tab.right)
|
|
116
|
-
assertEquals(Color.RED, colorAt(reserved + dp(10)))
|
|
117
|
-
tap(dp(10))
|
|
118
|
-
|
|
119
|
-
host.nativeTabBarTrailingReservedWidth = 500.0
|
|
120
|
-
layout()
|
|
121
|
-
assertEquals(0, tab.width)
|
|
122
|
-
host.nativeTabBarTrailingReservedWidth = 0.0
|
|
123
|
-
layout()
|
|
124
|
-
assertEquals(0, tab.left)
|
|
125
|
-
assertEquals(width, tab.width)
|
|
126
|
-
assertTrue(host.indexOfChild(sticky) < host.indexOfChild(tab))
|
|
127
|
-
}
|
|
128
|
-
instrumentation.waitForIdleSync()
|
|
129
|
-
assertEquals(2, toolbarPresses.get())
|
|
130
|
-
assertEquals(1, tabPresses.get())
|
|
131
|
-
} finally {
|
|
132
|
-
instrumentation.runOnMainSync { activity.finish() }
|
|
133
|
-
instrumentation.waitForIdleSync()
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|