@onekeyfe/react-native-pager-view 6.7.0-alpha.344 → 6.7.0-alpha.354
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 +5 -0
- package/android/src/androidTest/AndroidManifest.xml +7 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +136 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +72 -21
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +13 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
- package/docs/SPEC.md +68 -2
- package/ios/RNCCollapsiblePagerViewComponentView.mm +68 -7
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +87 -0
- package/lib/module/CollapsiblePagerView.js +16 -4
- package/lib/module/CollapsiblePagerView.web.js +11 -2
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +11 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +4 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +2 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +3 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +50 -4
- package/src/CollapsiblePagerView.web.tsx +19 -2
- package/src/CollapsiblePagerViewNativeComponent.ts +11 -0
package/README.md
CHANGED
|
@@ -66,6 +66,11 @@ 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.
|
|
69
74
|
|
|
70
75
|
On Web, `WebCollapsiblePagerView` is an explicit root export of the existing Web
|
|
71
76
|
pager. It measures Header and sticky content when their heights are omitted.
|
package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
}
|
|
@@ -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 logicalOffset: Int, val token: Int) {
|
|
146
146
|
val view = WeakReference(view)
|
|
147
147
|
}
|
|
148
148
|
private var pagerTopRequest: PagerTopRequest? = null
|
|
@@ -293,6 +293,20 @@ 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
|
+
}
|
|
296
310
|
var nativeTabBarContentPaddingHorizontal = 20.0
|
|
297
311
|
var nativeTabBarItemSpacing = 8.0
|
|
298
312
|
var nativeTabBarFontSize = 16.0
|
|
@@ -442,6 +456,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
442
456
|
private fun bringNativeHeadersToFront() {
|
|
443
457
|
nativeTabBarView.bringToFront()
|
|
444
458
|
nativeSubHeaderView.bringToFront()
|
|
459
|
+
if (nativeTabBarTrailingReservedWidth > 0.0) stickyHeaderView?.bringToFront()
|
|
445
460
|
}
|
|
446
461
|
|
|
447
462
|
fun addReactChild(child: View, index: Int) {
|
|
@@ -689,7 +704,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
689
704
|
|
|
690
705
|
private fun layoutPagerIfRequested() {
|
|
691
706
|
val recycler = pager.getChildAt(0) as? RecyclerView ?: return
|
|
692
|
-
if (!recycler.isLayoutRequested || recycler.isComputingLayout || recycler.width <= 0 || recycler.height <= 0) return
|
|
707
|
+
if (!recycler.isLayoutRequested || recycler.isLayoutSuppressed || recycler.isComputingLayout || recycler.width <= 0 || recycler.height <= 0) return
|
|
693
708
|
// OneKey patch: a non-animated page command queues RecyclerView layout, but RN
|
|
694
709
|
// can stop that request after navigation. Drain it before showing the page.
|
|
695
710
|
recycler.measure(
|
|
@@ -712,6 +727,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
712
727
|
layout(left, top, right, bottom)
|
|
713
728
|
}
|
|
714
729
|
|
|
730
|
+
private fun isPagerRecyclerLayoutSuppressed(): Boolean =
|
|
731
|
+
(pager.getChildAt(0) as? RecyclerView)?.isLayoutSuppressed == true
|
|
732
|
+
|
|
715
733
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
716
734
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
717
735
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
@@ -722,10 +740,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
722
740
|
return
|
|
723
741
|
}
|
|
724
742
|
val pagerHeight = height + headerHeightPx
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
743
|
+
if (!isPagerRecyclerLayoutSuppressed()) {
|
|
744
|
+
pager.measure(
|
|
745
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
746
|
+
MeasureSpec.makeMeasureSpec(pagerHeight, MeasureSpec.EXACTLY),
|
|
747
|
+
)
|
|
748
|
+
}
|
|
729
749
|
headerView?.measure(
|
|
730
750
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
731
751
|
MeasureSpec.makeMeasureSpec(headerHeightPx, MeasureSpec.EXACTLY),
|
|
@@ -737,8 +757,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
737
757
|
val nativeTabHeight = if (nativeTabBarView.visibility == View.VISIBLE) {
|
|
738
758
|
nativeTabBarView.preferredHeightPx()
|
|
739
759
|
} else 0
|
|
760
|
+
val tabReservedWidth = nativeTabReservationPx(width)
|
|
740
761
|
nativeTabBarView.measure(
|
|
741
|
-
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
762
|
+
MeasureSpec.makeMeasureSpec(width - tabReservedWidth, MeasureSpec.EXACTLY),
|
|
742
763
|
MeasureSpec.makeMeasureSpec(nativeTabHeight, MeasureSpec.EXACTLY),
|
|
743
764
|
)
|
|
744
765
|
val nativeSubHeaderHeight = if (nativeSubHeaderView.visibility == View.VISIBLE) {
|
|
@@ -757,7 +778,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
757
778
|
// RN ancestors allow overflow; keep translated header content inside this viewport.
|
|
758
779
|
clipBounds = Rect(0, 0, width, height)
|
|
759
780
|
if (width <= 0 || height <= 0) return
|
|
760
|
-
|
|
781
|
+
if (!isPagerRecyclerLayoutSuppressed()) {
|
|
782
|
+
pager.layout(0, 0, width, height + headerHeightPx)
|
|
783
|
+
}
|
|
761
784
|
headerView?.layout(0, 0, width, headerHeightPx)
|
|
762
785
|
stickyHeaderView?.layout(
|
|
763
786
|
0,
|
|
@@ -766,7 +789,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
766
789
|
headerHeightPx + stickyHeaderHeightPx,
|
|
767
790
|
)
|
|
768
791
|
val nativeTabHeight = nativeTabBarView.measuredHeight
|
|
769
|
-
|
|
792
|
+
val tabReservedWidth = nativeTabReservationPx(width)
|
|
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)
|
|
770
798
|
nativeSubHeaderView.layout(
|
|
771
799
|
0,
|
|
772
800
|
headerHeightPx + nativeTabHeight,
|
|
@@ -828,7 +856,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
828
856
|
}
|
|
829
857
|
if (!reached()) {
|
|
830
858
|
if (!isAttachedToWindow || !view.isAttachedToWindow || width <= 0 || height <= 0 ||
|
|
831
|
-
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.isComputingLayout ||
|
|
859
|
+
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.isLayoutSuppressed || view.isComputingLayout ||
|
|
832
860
|
pageKey(index) != key || primaryScrollableForPage(index) !== view || !isCurrentPageRestore(key, request)) return false
|
|
833
861
|
val previousSynthetic = syntheticPageRestore
|
|
834
862
|
syntheticPageRestore = true
|
|
@@ -987,13 +1015,33 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
987
1015
|
if (!settled) return
|
|
988
1016
|
pageOffsets[request.key] = 0
|
|
989
1017
|
if (view is RecyclerView) saveRecyclerReadingAnchor(request.key, view)
|
|
990
|
-
setHeaderOffset(
|
|
991
|
-
offsetDiag("pager-top-complete-
|
|
1018
|
+
setHeaderOffset(request.logicalOffset.coerceIn(0, headerHeightPx))
|
|
1019
|
+
offsetDiag("pager-top-complete-offset=${request.logicalOffset}-token=${request.token}", view, request.key)
|
|
992
1020
|
cancelPagerTop()
|
|
993
1021
|
}
|
|
994
1022
|
|
|
995
1023
|
internal fun scrollToTop(target: Int, animated: Boolean) {
|
|
996
|
-
if (target !in 0..1
|
|
1024
|
+
if (target !in 0..1) return
|
|
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 ||
|
|
997
1045
|
syntheticPageRestore || selectedPage !in 0 until adapter.itemCount || !isAttachedToWindow) return
|
|
998
1046
|
val view = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller ?: return
|
|
999
1047
|
val key = currentPageKey()
|
|
@@ -1020,11 +1068,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1020
1068
|
if (view is RecyclerView) deferredRecyclerGeometry.remove(view)
|
|
1021
1069
|
if (view is ScrollView) deferredScrollerGeometry.remove(view)
|
|
1022
1070
|
pageOffsets[key] = 0
|
|
1023
|
-
val
|
|
1071
|
+
val targetOffset = logicalOffset.coerceIn(0, headerHeightPx)
|
|
1072
|
+
val request = PagerTopRequest(view, key, selectedPage, attachmentGeneration, targetOffset, ++pagerTopToken)
|
|
1024
1073
|
pagerTopRequest = request
|
|
1025
1074
|
commandBodyKeys.add(key)
|
|
1026
|
-
setHeaderOffset(
|
|
1027
|
-
offsetDiag("pager-top-begin-
|
|
1075
|
+
setHeaderOffset(targetOffset)
|
|
1076
|
+
offsetDiag("pager-top-begin-offset=$targetOffset-animated=$animated-token=${request.token}", view, key)
|
|
1028
1077
|
dispatchingPagerTop = true
|
|
1029
1078
|
try {
|
|
1030
1079
|
when (view) {
|
|
@@ -1033,7 +1082,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1033
1082
|
if (animated && !recyclerIsAtBodyTop(view)) view.smoothScrollToPosition(0)
|
|
1034
1083
|
else if (manager != null && manager.orientation == RecyclerView.VERTICAL) {
|
|
1035
1084
|
manager.scrollToPositionWithOffset(0, 0)
|
|
1036
|
-
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.isComputingLayout) {
|
|
1085
|
+
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.isLayoutSuppressed && !view.isComputingLayout) {
|
|
1037
1086
|
view.measure(MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(view.height, MeasureSpec.EXACTLY))
|
|
1038
1087
|
view.layout(view.left, view.top, view.right, view.bottom)
|
|
1039
1088
|
}
|
|
@@ -1638,7 +1687,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1638
1687
|
if (anchorOffset != null) manager?.scrollToPositionWithOffset(first, anchorOffset)
|
|
1639
1688
|
// React Native can stop this nested requestLayout at its React-owned parent.
|
|
1640
1689
|
// Commit the changed inset before drawing instead of waiting for a data update.
|
|
1641
|
-
if (recycler.width > 0 && recycler.height > 0 && !recycler.isComputingLayout) {
|
|
1690
|
+
if (recycler.width > 0 && recycler.height > 0 && !recycler.isLayoutSuppressed && !recycler.isComputingLayout) {
|
|
1642
1691
|
recycler.forceLayout()
|
|
1643
1692
|
recycler.measure(
|
|
1644
1693
|
MeasureSpec.makeMeasureSpec(recycler.width, MeasureSpec.EXACTLY),
|
|
@@ -2012,14 +2061,16 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2012
2061
|
}
|
|
2013
2062
|
}
|
|
2014
2063
|
|
|
2015
|
-
private fun headerRegionAt(y: Float): String? {
|
|
2064
|
+
private fun headerRegionAt(x: Float, y: Float): String? {
|
|
2016
2065
|
val headerBottom = headerHeightPx - headerOffsetPx
|
|
2017
2066
|
val stickyBottom = headerHeightPx + stickyHeaderHeightPx - headerOffsetPx
|
|
2018
2067
|
return when {
|
|
2019
2068
|
y < 0 || y >= stickyBottom -> null
|
|
2020
2069
|
y < headerBottom -> "header"
|
|
2021
2070
|
y < headerBottom + nativeTabBarView.measuredHeight &&
|
|
2022
|
-
nativeTabBarView.visibility == View.VISIBLE ->
|
|
2071
|
+
nativeTabBarView.visibility == View.VISIBLE ->
|
|
2072
|
+
if (x >= nativeTabBarView.left && x < nativeTabBarView.right) "primary-tab"
|
|
2073
|
+
else "sticky-controls"
|
|
2023
2074
|
y < headerBottom + nativeTabBarView.measuredHeight + nativeSubHeaderView.measuredHeight &&
|
|
2024
2075
|
nativeSubHeaderView.visibility == View.VISIBLE -> "secondary-header"
|
|
2025
2076
|
else -> "sticky-controls"
|
|
@@ -2128,7 +2179,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2128
2179
|
pressCancelled = false
|
|
2129
2180
|
nativeGestureStarted = false
|
|
2130
2181
|
nestedNativeGestureStarted = false
|
|
2131
|
-
headerTouchRegion = headerRegionAt(event.y) ?: "none"
|
|
2182
|
+
headerTouchRegion = headerRegionAt(event.x, event.y) ?: "none"
|
|
2132
2183
|
headerTouchActive = headerTouchRegion != "none"
|
|
2133
2184
|
if (!headerTouchActive) return super.dispatchTouchEvent(event)
|
|
2134
2185
|
headerDownX = event.x
|
|
@@ -239,6 +239,11 @@ 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
|
+
|
|
242
247
|
@ReactProp(name = "nativeTabBarContentPaddingHorizontal")
|
|
243
248
|
override fun setNativeTabBarContentPaddingHorizontal(
|
|
244
249
|
view: CollapsiblePagerHost?,
|
|
@@ -343,6 +348,14 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
343
348
|
view?.scrollToTop(target, animated)
|
|
344
349
|
}
|
|
345
350
|
|
|
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
|
+
|
|
346
359
|
override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
347
360
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
348
361
|
view.beginPageTransition(selectedPage)
|
|
@@ -37,26 +37,50 @@ 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
|
+
}
|
|
40
54
|
}
|
|
41
55
|
private val contentFrame = object : FrameLayout(context) {
|
|
42
56
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
|
43
57
|
setMeasuredDimension(
|
|
44
|
-
max(MeasureSpec.getSize(widthSpec),
|
|
45
|
-
|
|
58
|
+
max(MeasureSpec.getSize(widthSpec), effectiveContentWidthPx + insetLeft + insetRight),
|
|
59
|
+
effectiveContentHeightPx + contentTop + insetBottom,
|
|
46
60
|
)
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
50
64
|
// Only the native origin layer moves. Fabric owns its React child's layout/transform.
|
|
65
|
+
val contentWidth = effectiveContentWidthPx
|
|
66
|
+
val contentHeight = effectiveContentHeightPx
|
|
51
67
|
contentOriginFrame.measure(
|
|
52
|
-
MeasureSpec.makeMeasureSpec(
|
|
53
|
-
MeasureSpec.makeMeasureSpec(
|
|
68
|
+
MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY),
|
|
69
|
+
MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY),
|
|
54
70
|
)
|
|
55
|
-
contentOriginFrame.layout(insetLeft, contentTop, insetLeft +
|
|
71
|
+
contentOriginFrame.layout(insetLeft, contentTop, insetLeft + contentWidth, contentTop + contentHeight)
|
|
56
72
|
}
|
|
57
73
|
}
|
|
58
74
|
private val pagerInsets = WeakHashMap<View, Int>()
|
|
59
75
|
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
|
|
60
84
|
private var fabricState: StateWrapper? = null
|
|
61
85
|
private var lastOriginX: Int? = null
|
|
62
86
|
private var lastOriginY: Int? = null
|
|
@@ -172,6 +196,8 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
172
196
|
internal fun addReactChild(child: View, index: Int) {
|
|
173
197
|
require(index == 0 && reactChild == null) { "NativeScroller requires one React content view" }
|
|
174
198
|
viewport.stopMotion()
|
|
199
|
+
clearRetainedGeometry()
|
|
200
|
+
transitioningReactChild = null
|
|
175
201
|
reactChild = child
|
|
176
202
|
contentOriginFrame.addView(child)
|
|
177
203
|
scheduleLayout()
|
|
@@ -180,13 +206,29 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
180
206
|
internal fun removeReactChild() {
|
|
181
207
|
viewport.stopMotion()
|
|
182
208
|
pendingRestore.cancel()
|
|
183
|
-
reactChild?.let
|
|
209
|
+
reactChild?.let { child ->
|
|
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
|
+
}
|
|
184
220
|
reactChild = null
|
|
185
221
|
contentWidthPx = 0
|
|
186
222
|
contentHeightPx = 0
|
|
187
223
|
scheduleLayout()
|
|
188
224
|
}
|
|
189
225
|
|
|
226
|
+
private fun clearRetainedGeometry() {
|
|
227
|
+
retainedReactChild = null
|
|
228
|
+
retainedContentWidthPx = 0
|
|
229
|
+
retainedContentHeightPx = 0
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
internal fun reactChildCount() = if (reactChild == null) 0 else 1
|
|
191
233
|
internal fun reactChildAt(index: Int): View {
|
|
192
234
|
require(index == 0)
|
|
@@ -381,6 +423,8 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
381
423
|
lastContentViewportHeight = null
|
|
382
424
|
viewport.stopMotion()
|
|
383
425
|
viewport.setTag(R.id.onekey_native_scroll_coordinator_offset_listener, null)
|
|
426
|
+
transitioningReactChild = null
|
|
427
|
+
clearRetainedGeometry()
|
|
384
428
|
super.onDetachedFromWindow()
|
|
385
429
|
}
|
|
386
430
|
|
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. Native scroll/refresh
|
|
77
|
+
interaction and restore any prior scroll-edge style. The existing owner-scoped native sticky-height callback also reaches NativeList hosts, so their React container slots clip below the same fixed viewport region and release it when the owner detaches. 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,6 +171,24 @@ 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
|
+
|
|
174
192
|
## Independent NativeScroller migration
|
|
175
193
|
|
|
176
194
|
Status: implemented in source. Both native builds pass. Android focused emulator
|
|
@@ -333,6 +351,32 @@ warnings were observed. Keyboard, multi-touch, physical-device performance and
|
|
|
333
351
|
the full lifecycle/interaction matrix remain unverified. These are local source
|
|
334
352
|
builds; the currently installed npm version does not yet contain this migration.
|
|
335
353
|
|
|
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
|
+
|
|
336
380
|
## iOS vertical-scroll press cancellation
|
|
337
381
|
|
|
338
382
|
Status: implemented in source. A source-identical `node_modules` build was
|
|
@@ -378,7 +422,7 @@ content, the shared header, and touches beginning on an inner horizontal banner.
|
|
|
378
422
|
Normal taps MUST remain functional. Press-retention behavior in application
|
|
379
423
|
controls belongs to the consuming responder implementation; the Pager package
|
|
380
424
|
does not contain or publish Tamagui helpers. The native application tab bar has
|
|
381
|
-
a separate contract in react-native-
|
|
425
|
+
a separate contract in react-native-bottom-tabs/docs/SPEC.md.
|
|
382
426
|
|
|
383
427
|
The current native implementation confirms horizontal intent after at least
|
|
384
428
|
32 points on iOS or 32 dp on Android, with horizontal displacement exceeding
|
|
@@ -472,6 +516,28 @@ Status: implemented; focused native-wrapper tests cover both directions,
|
|
|
472
516
|
explicit overrides and in-flight/superseded command targets. Home applies this policy to its JS tab bar, with iOS and
|
|
473
517
|
Android simulator recordings verifying distant jumps and adjacent transitions.
|
|
474
518
|
|
|
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
|
+
|
|
475
541
|
## Android refresh foreground coordination
|
|
476
542
|
|
|
477
543
|
Status: implemented; Android Home simulator placement verified on 2026-09-28.
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
// OneKey patch: deduplicate structural mutation parents before scanning the page.
|
|
20
20
|
#include <unordered_set>
|
|
21
|
+
#include <cmath>
|
|
21
22
|
|
|
22
23
|
using namespace facebook::react;
|
|
23
24
|
|
|
@@ -1471,6 +1472,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1471
1472
|
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
|
|
1472
1473
|
- (void)restoreSharedHeadersToContainer;
|
|
1473
1474
|
- (void)layoutSharedHeaders;
|
|
1475
|
+
- (void)bringReservedStickyControlToFront;
|
|
1474
1476
|
- (void)restorePinnedStickyHeader;
|
|
1475
1477
|
- (void)updatePinnedStickyHeader;
|
|
1476
1478
|
- (UIScrollView *)currentVerticalScrollViewForParentPager;
|
|
@@ -1558,6 +1560,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1558
1560
|
NSUInteger _pendingTopInsetReboundGeneration;
|
|
1559
1561
|
NSTimer *_topInsetReboundTimer;
|
|
1560
1562
|
CGFloat _nativeTabBarHeight;
|
|
1563
|
+
CGFloat _nativeTabBarTrailingReservedWidth;
|
|
1561
1564
|
CGFloat _nativeSubHeaderHeight;
|
|
1562
1565
|
CGFloat _headerOffset;
|
|
1563
1566
|
BOOL _scrollEnabled;
|
|
@@ -2479,6 +2482,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2479
2482
|
}
|
|
2480
2483
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2481
2484
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2485
|
+
[self bringReservedStickyControlToFront];
|
|
2482
2486
|
}
|
|
2483
2487
|
|
|
2484
2488
|
- (void)restoreSharedHeadersToContainer
|
|
@@ -2584,10 +2588,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2584
2588
|
CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
|
|
2585
2589
|
? 0
|
|
2586
2590
|
: MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2591
|
+
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2587
2592
|
_nativeTabBarView.frame = CGRectMake(
|
|
2588
|
-
0,
|
|
2593
|
+
self.isLtrLayout ? 0 : tabReservedWidth,
|
|
2589
2594
|
stickyChildY,
|
|
2590
|
-
width,
|
|
2595
|
+
width - tabReservedWidth,
|
|
2591
2596
|
nativeTabBarVisibleHeight
|
|
2592
2597
|
);
|
|
2593
2598
|
_nativeSubHeaderView.frame = CGRectMake(
|
|
@@ -2601,9 +2606,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2601
2606
|
);
|
|
2602
2607
|
_topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
|
|
2603
2608
|
_topOverlayView.frame = _topOverlayHostView.bounds;
|
|
2609
|
+
[self bringReservedStickyControlToFront];
|
|
2604
2610
|
[self applyHeaderOffset];
|
|
2605
2611
|
}
|
|
2606
2612
|
|
|
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
|
+
|
|
2607
2629
|
- (void)updatePinnedStickyHeader
|
|
2608
2630
|
{
|
|
2609
2631
|
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
@@ -2650,8 +2672,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2650
2672
|
CGFloat childY = shouldPin ? 0 : _headerHeight;
|
|
2651
2673
|
CGFloat width = CGRectGetWidth(self.bounds);
|
|
2652
2674
|
CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2675
|
+
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2653
2676
|
_stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
|
|
2654
|
-
_nativeTabBarView.frame = CGRectMake(0
|
|
2677
|
+
_nativeTabBarView.frame = CGRectMake(self.isLtrLayout ? 0 : tabReservedWidth,
|
|
2678
|
+
childY, width - tabReservedWidth, tabHeight);
|
|
2655
2679
|
_nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
|
|
2656
2680
|
MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
|
|
2657
2681
|
_pinnedStickyHostView.hidden = !shouldPin;
|
|
@@ -2671,6 +2695,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2671
2695
|
}
|
|
2672
2696
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2673
2697
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2698
|
+
[self bringReservedStickyControlToFront];
|
|
2674
2699
|
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2675
2700
|
[self logHomeVisualGeometry:shouldPin ? @"pin" : @"unpin"
|
|
2676
2701
|
scrollView:_observedScrollView];
|
|
@@ -3050,6 +3075,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3050
3075
|
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
3051
3076
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
3052
3077
|
if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
3078
|
+
[self bringReservedStickyControlToFront];
|
|
3053
3079
|
[_containerView bringSubviewToFront:_sharedHeaderHostView];
|
|
3054
3080
|
if (_stickyHeaderIsPinned) {
|
|
3055
3081
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -3262,6 +3288,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3262
3288
|
_homeVisualLastLayoutLog = 0;
|
|
3263
3289
|
_homeVisualLastGeometrySignature = nil;
|
|
3264
3290
|
_nativeTabBarHeight = 44;
|
|
3291
|
+
_nativeTabBarTrailingReservedWidth = 0;
|
|
3265
3292
|
_nativeSubHeaderHeight = 74;
|
|
3266
3293
|
_headerOffset = 0;
|
|
3267
3294
|
_currentLogicalOffset = 0;
|
|
@@ -3343,6 +3370,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3343
3370
|
_layoutDirection = RCTNSStringFromString(toString(newViewProps.layoutDirection));
|
|
3344
3371
|
[_nativeTabBarView setLeftToRight:self.isLtrLayout];
|
|
3345
3372
|
[_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];
|
|
3346
3380
|
}
|
|
3347
3381
|
BOOL insetsChanged = NO;
|
|
3348
3382
|
if (_needsPropsReapply || oldViewProps.headerHeight != newViewProps.headerHeight) {
|
|
@@ -3474,6 +3508,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3474
3508
|
if (!_nativeSubHeaderView.hidden) {
|
|
3475
3509
|
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
3476
3510
|
}
|
|
3511
|
+
[self bringReservedStickyControlToFront];
|
|
3477
3512
|
[_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
|
|
3478
3513
|
if (_stickyHeaderIsPinned) {
|
|
3479
3514
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -4438,8 +4473,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4438
4473
|
|
|
4439
4474
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
4440
4475
|
{
|
|
4441
|
-
|
|
4442
|
-
|
|
4476
|
+
id host = scrollView.superview;
|
|
4477
|
+
if ([host respondsToSelector:@selector(setPagerStickyHeight:forOwner:)]) {
|
|
4478
|
+
[host setPagerStickyHeight:height forOwner:self];
|
|
4443
4479
|
}
|
|
4444
4480
|
}
|
|
4445
4481
|
|
|
@@ -5233,6 +5269,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5233
5269
|
{
|
|
5234
5270
|
UIScrollView *scrollView = _pagerTopScrollView;
|
|
5235
5271
|
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
|
+
}
|
|
5236
5279
|
CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
5237
5280
|
if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
|
|
5238
5281
|
_pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
|
|
@@ -5244,11 +5287,29 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5244
5287
|
}
|
|
5245
5288
|
|
|
5246
5289
|
- (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
|
|
5247
5308
|
{
|
|
5248
5309
|
[self cancelTopInsetReboundCheck];
|
|
5249
5310
|
UIScrollView *scrollView = _observedScrollView;
|
|
5250
5311
|
NSString *key = _observedPageKey;
|
|
5251
|
-
if (
|
|
5312
|
+
if (_isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
5252
5313
|
_transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
|
|
5253
5314
|
key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
|
|
5254
5315
|
_observedPageGeneration != _generation || self.window == nil) return;
|
|
@@ -5264,7 +5325,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5264
5325
|
[_alignedBodyKeys removeObject:key];
|
|
5265
5326
|
[_suspendedBodyKeys removeObject:key];
|
|
5266
5327
|
[_displayBodyOverrides removeObjectForKey:key];
|
|
5267
|
-
|
|
5328
|
+
logicalTarget = MIN(MAX(logicalTarget, 0), _headerHeight);
|
|
5268
5329
|
_pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
|
|
5269
5330
|
_pagerTopScrollView = scrollView;
|
|
5270
5331
|
_pagerTopPageKey = [key copy];
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
4
4
|
#import "RNCPagerViewComponentView.h"
|
|
5
|
+
#import <React/RCTViewComponentView.h>
|
|
5
6
|
#import <react/renderer/components/pagerview/Props.h>
|
|
7
|
+
#import <react/renderer/components/view/ViewProps.h>
|
|
6
8
|
|
|
7
9
|
using namespace facebook::react;
|
|
8
10
|
|
|
9
11
|
@interface RNCCollapsiblePagerViewComponentView (ScrollEdgeTesting)
|
|
10
12
|
- (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView;
|
|
11
13
|
- (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;
|
|
12
21
|
@end
|
|
13
22
|
|
|
14
23
|
@interface RNCCollapsiblePagerHitTestTests : XCTestCase
|
|
@@ -56,6 +65,84 @@ using namespace facebook::react;
|
|
|
56
65
|
XCTAssertEqual([self.root hitTest:CGPointMake(20, 80) withEvent:nil], self.toolbar);
|
|
57
66
|
}
|
|
58
67
|
|
|
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
|
+
|
|
59
146
|
- (void)testPinnedChildOutsideHeaderHostStillReceivesViewportTouches
|
|
60
147
|
{
|
|
61
148
|
XCTAssertEqual([self.pager hitTest:CGPointMake(20, 90) withEvent:nil], self.sticky);
|
|
@@ -82,6 +82,12 @@ 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
|
+
};
|
|
85
91
|
scrollToStickyTop = options => {
|
|
86
92
|
if (this.nativeRef) CollapsiblePagerViewNativeCommands.scrollToTop(this.nativeRef, 1, options?.animated ?? true);
|
|
87
93
|
};
|
|
@@ -96,7 +102,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
96
102
|
nativeTabBarEnabled() {
|
|
97
103
|
return (Platform.OS === "ios" || Platform.OS === "android") && !!this.props.nativeTabBar?.items.length;
|
|
98
104
|
}
|
|
99
|
-
dispatchNativeTabPageCommand(position, animated) {
|
|
105
|
+
dispatchNativeTabPageCommand(position, animated, expectedKey) {
|
|
100
106
|
const commandId = ++this.nativeTabCommandId;
|
|
101
107
|
this.nativeTabCommandQueuedDuringScroll = this.nativePagerScrollState !== "idle";
|
|
102
108
|
this.latestNativeTabTarget = position;
|
|
@@ -104,7 +110,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
104
110
|
transientRetainedPages: state.transientRetainedPages.includes(position) ? state.transientRetainedPages : [...state.transientRetainedPages, position]
|
|
105
111
|
}), () => {
|
|
106
112
|
requestAnimationFrame(() => {
|
|
107
|
-
if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
|
|
113
|
+
if (commandId !== this.nativeTabCommandId || !this.nativeRef || expectedKey !== undefined && (!this.nativeTabBarEnabled() || this.props.nativeTabBar?.items[position]?.key !== expectedKey)) {
|
|
108
114
|
return;
|
|
109
115
|
}
|
|
110
116
|
this.dispatchedNativeTabTarget = position;
|
|
@@ -186,10 +192,13 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
186
192
|
};
|
|
187
193
|
onNativeTabPress = event => {
|
|
188
194
|
const position = pageIndex(event.nativeEvent.position);
|
|
189
|
-
if (
|
|
195
|
+
if (!this.nativeTabBarEnabled() || position === null || this.props.nativeTabBar?.items[position]?.key !== event.nativeEvent.key) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
|
|
190
199
|
// Selection state lags an in-flight transition until native reports it.
|
|
191
200
|
const currentPage = this.dispatchedNativeTabTarget ?? this.state.selectedPage;
|
|
192
|
-
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1);
|
|
201
|
+
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - currentPage) <= 1, event.nativeEvent.key);
|
|
193
202
|
}
|
|
194
203
|
this.props.onNativeTabPress?.(event);
|
|
195
204
|
};
|
|
@@ -233,6 +242,8 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
233
242
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
234
243
|
const nativeTabBarEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeTabBar?.items.length;
|
|
235
244
|
const nativeTabBarStyle = nativeTabBar?.style;
|
|
245
|
+
const requestedTrailingReservedWidth = nativeTabBarStyle?.trailingReservedWidth;
|
|
246
|
+
const nativeTabBarTrailingReservedWidth = typeof requestedTrailingReservedWidth === "number" && Number.isFinite(requestedTrailingReservedWidth) && requestedTrailingReservedWidth >= 0 ? requestedTrailingReservedWidth : 0;
|
|
236
247
|
const nativeSubHeaderEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeSubHeader?.items.length;
|
|
237
248
|
const nativeSubHeaderStyle = nativeSubHeader?.style;
|
|
238
249
|
const nativeSubHeaderConfig = nativeSubHeaderEnabled ? JSON.stringify({
|
|
@@ -263,6 +274,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
263
274
|
onPageScrollStateChanged: this.onPageScrollStateChanged,
|
|
264
275
|
nativeTabBarItems: nativeTabBarEnabled ? JSON.stringify(nativeTabBar.items) : undefined,
|
|
265
276
|
nativeTabBarHeight: nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined,
|
|
277
|
+
nativeTabBarTrailingReservedWidth: nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0,
|
|
266
278
|
nativeTabBarContentPaddingHorizontal: nativeTabBarEnabled ? nativeTabBarStyle?.contentPaddingHorizontal ?? 20 : undefined,
|
|
267
279
|
nativeTabBarItemSpacing: nativeTabBarEnabled ? nativeTabBarStyle?.itemSpacing ?? 8 : undefined,
|
|
268
280
|
nativeTabBarFontSize: nativeTabBarEnabled ? nativeTabBarStyle?.fontSize ?? 16 : undefined,
|
|
@@ -335,6 +335,15 @@ 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
|
+
};
|
|
338
347
|
scrollToStickyTop = options => {
|
|
339
348
|
this.scrollCurrentPageToTop(this.headerHeight, options?.animated ?? true);
|
|
340
349
|
};
|
|
@@ -386,13 +395,13 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
386
395
|
this.pendingTopFrame = null;
|
|
387
396
|
if (!element.isConnected || this.horizontalTouch || this.transitionTimer !== null || this.pageScrollElement(this.state.selectedPage) !== element || this.pages().map(pageKey)[this.state.selectedPage] !== key) return;
|
|
388
397
|
this.pageOffsets.set(key, 0);
|
|
389
|
-
this.sharedHeaderOffset = offset;
|
|
398
|
+
this.sharedHeaderOffset = Math.min(this.headerHeight, Math.max(0, offset));
|
|
390
399
|
this.bodyAlignment = {
|
|
391
400
|
element,
|
|
392
401
|
key
|
|
393
402
|
};
|
|
394
403
|
scrollElementTo(element, {
|
|
395
|
-
top:
|
|
404
|
+
top: this.sharedHeaderOffset,
|
|
396
405
|
behavior: animated ? "smooth" : "instant"
|
|
397
406
|
});
|
|
398
407
|
this.syncFallbackHeader();
|
|
@@ -67,6 +67,7 @@ export interface NativeProps extends ViewProps {
|
|
|
67
67
|
retainedPages?: string;
|
|
68
68
|
nativeTabBarItems?: string;
|
|
69
69
|
nativeTabBarHeight?: Double;
|
|
70
|
+
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
70
71
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
71
72
|
nativeTabBarItemSpacing?: Double;
|
|
72
73
|
nativeTabBarFontSize?: Double;
|
|
@@ -91,6 +92,14 @@ export interface NativeProps extends ViewProps {
|
|
|
91
92
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
92
93
|
|
|
93
94
|
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;
|
|
94
103
|
scrollToTop: (
|
|
95
104
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
96
105
|
target: Int32,
|
|
@@ -112,6 +121,8 @@ export interface NativeCommands {
|
|
|
112
121
|
|
|
113
122
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
114
123
|
supportedCommands: [
|
|
124
|
+
"scrollHeaderToOffset",
|
|
125
|
+
"scrollHeaderBy",
|
|
115
126
|
"scrollToTop",
|
|
116
127
|
"setPage",
|
|
117
128
|
"setPageWithoutAnimation",
|
|
@@ -17,6 +17,7 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
|
|
|
17
17
|
}>;
|
|
18
18
|
export type CollapsiblePagerNativeTabBarStyle = Readonly<{
|
|
19
19
|
height?: number;
|
|
20
|
+
trailingReservedWidth?: number;
|
|
20
21
|
contentPaddingHorizontal?: number;
|
|
21
22
|
itemSpacing?: number;
|
|
22
23
|
fontSize?: number;
|
|
@@ -56,7 +57,7 @@ export type CollapsiblePagerNativeSubHeaderConfig = Readonly<{
|
|
|
56
57
|
}>;
|
|
57
58
|
export type CollapsiblePagerViewOnNativeTabPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
58
59
|
export type CollapsiblePagerViewOnNativeSubHeaderPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
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"> {
|
|
60
|
+
export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "topScrollEdgeEffect" | "topOverlayHeight" | "externalTopOverlayTag" | "pageKeys" | "retainedPages" | "nativeTabBarItems" | "nativeTabBarHeight" | "nativeTabBarTrailingReservedWidth" | "nativeTabBarContentPaddingHorizontal" | "nativeTabBarItemSpacing" | "nativeTabBarFontSize" | "nativeTabBarFontFamily" | "nativeTabBarBackgroundColor" | "nativeTabBarActiveTextColor" | "nativeTabBarInactiveTextColor" | "nativeTabBarIndicatorColor" | "nativeTabBarIndicatorHeight" | "nativeTabBarIndicatorBottom" | "nativeSubHeaderConfig" | "nativeSubHeaderSelectedBackgroundColor" | "onNativeTabPress" | "onNativeSubHeaderPress" | "layoutDirection"> {
|
|
60
61
|
/** Collapsible content rendered above the sticky bar. */
|
|
61
62
|
header: React.ReactNode;
|
|
62
63
|
/** Bar which remains pinned after the header has collapsed. */
|
|
@@ -128,6 +129,8 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
128
129
|
scrollToPageTop: (options?: Readonly<{
|
|
129
130
|
animated?: boolean;
|
|
130
131
|
}>) => void;
|
|
132
|
+
scrollHeaderBy: (delta: number) => void;
|
|
133
|
+
scrollHeaderToOffset: (offset: number) => void;
|
|
131
134
|
scrollToStickyTop: (options?: Readonly<{
|
|
132
135
|
animated?: boolean;
|
|
133
136
|
}>) => void;
|
|
@@ -132,6 +132,8 @@ 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;
|
|
135
137
|
scrollToStickyTop: (options?: Readonly<{
|
|
136
138
|
animated?: boolean;
|
|
137
139
|
}>) => void;
|
|
@@ -50,6 +50,7 @@ export interface NativeProps extends ViewProps {
|
|
|
50
50
|
retainedPages?: string;
|
|
51
51
|
nativeTabBarItems?: string;
|
|
52
52
|
nativeTabBarHeight?: Double;
|
|
53
|
+
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
53
54
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
54
55
|
nativeTabBarItemSpacing?: Double;
|
|
55
56
|
nativeTabBarFontSize?: Double;
|
|
@@ -72,6 +73,8 @@ export interface NativeProps extends ViewProps {
|
|
|
72
73
|
}
|
|
73
74
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
74
75
|
export interface NativeCommands {
|
|
76
|
+
scrollHeaderToOffset: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, offset: Double) => void;
|
|
77
|
+
scrollHeaderBy: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, delta: Double) => void;
|
|
75
78
|
scrollToTop: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, target: Int32, animated: boolean) => void;
|
|
76
79
|
setPage: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
|
|
77
80
|
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.354",
|
|
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.354",
|
|
77
77
|
"react": "*",
|
|
78
78
|
"react-native": "*"
|
|
79
79
|
},
|
|
@@ -33,6 +33,7 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
|
|
|
33
33
|
|
|
34
34
|
export type CollapsiblePagerNativeTabBarStyle = Readonly<{
|
|
35
35
|
height?: number;
|
|
36
|
+
trailingReservedWidth?: number;
|
|
36
37
|
contentPaddingHorizontal?: number;
|
|
37
38
|
itemSpacing?: number;
|
|
38
39
|
fontSize?: number;
|
|
@@ -94,6 +95,7 @@ export interface CollapsiblePagerViewProps
|
|
|
94
95
|
| "retainedPages"
|
|
95
96
|
| "nativeTabBarItems"
|
|
96
97
|
| "nativeTabBarHeight"
|
|
98
|
+
| "nativeTabBarTrailingReservedWidth"
|
|
97
99
|
| "nativeTabBarContentPaddingHorizontal"
|
|
98
100
|
| "nativeTabBarItemSpacing"
|
|
99
101
|
| "nativeTabBarFontSize"
|
|
@@ -290,6 +292,22 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
290
292
|
);
|
|
291
293
|
};
|
|
292
294
|
|
|
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
|
+
|
|
293
311
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
294
312
|
if (this.nativeRef)
|
|
295
313
|
CollapsiblePagerViewNativeCommands.scrollToTop(
|
|
@@ -319,7 +337,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
319
337
|
);
|
|
320
338
|
}
|
|
321
339
|
|
|
322
|
-
private dispatchNativeTabPageCommand(
|
|
340
|
+
private dispatchNativeTabPageCommand(
|
|
341
|
+
position: number,
|
|
342
|
+
animated: boolean,
|
|
343
|
+
expectedKey?: string
|
|
344
|
+
) {
|
|
323
345
|
const commandId = ++this.nativeTabCommandId;
|
|
324
346
|
this.nativeTabCommandQueuedDuringScroll =
|
|
325
347
|
this.nativePagerScrollState !== "idle";
|
|
@@ -332,7 +354,13 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
332
354
|
}),
|
|
333
355
|
() => {
|
|
334
356
|
requestAnimationFrame(() => {
|
|
335
|
-
if (
|
|
357
|
+
if (
|
|
358
|
+
commandId !== this.nativeTabCommandId ||
|
|
359
|
+
!this.nativeRef ||
|
|
360
|
+
(expectedKey !== undefined &&
|
|
361
|
+
(!this.nativeTabBarEnabled() ||
|
|
362
|
+
this.props.nativeTabBar?.items[position]?.key !== expectedKey))
|
|
363
|
+
) {
|
|
336
364
|
return;
|
|
337
365
|
}
|
|
338
366
|
this.dispatchedNativeTabTarget = position;
|
|
@@ -458,7 +486,13 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
458
486
|
) => {
|
|
459
487
|
const position = pageIndex(event.nativeEvent.position);
|
|
460
488
|
if (
|
|
461
|
-
|
|
489
|
+
!this.nativeTabBarEnabled() ||
|
|
490
|
+
position === null ||
|
|
491
|
+
this.props.nativeTabBar?.items[position]?.key !== event.nativeEvent.key
|
|
492
|
+
) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (
|
|
462
496
|
position >= 0 &&
|
|
463
497
|
position < this.pages().length &&
|
|
464
498
|
(position !== this.state.selectedPage ||
|
|
@@ -470,7 +504,8 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
470
504
|
this.dispatchNativeTabPageCommand(
|
|
471
505
|
position,
|
|
472
506
|
this.props.nativeTabPressAnimationEnabled ??
|
|
473
|
-
Math.abs(position - currentPage) <= 1
|
|
507
|
+
Math.abs(position - currentPage) <= 1,
|
|
508
|
+
event.nativeEvent.key
|
|
474
509
|
);
|
|
475
510
|
}
|
|
476
511
|
this.props.onNativeTabPress?.(event);
|
|
@@ -533,6 +568,14 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
533
568
|
(Platform.OS === "ios" || Platform.OS === "android") &&
|
|
534
569
|
!!nativeTabBar?.items.length;
|
|
535
570
|
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;
|
|
536
579
|
const nativeSubHeaderEnabled =
|
|
537
580
|
(Platform.OS === "ios" || Platform.OS === "android") &&
|
|
538
581
|
!!nativeSubHeader?.items.length;
|
|
@@ -586,6 +629,9 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
586
629
|
nativeTabBarHeight={
|
|
587
630
|
nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined
|
|
588
631
|
}
|
|
632
|
+
nativeTabBarTrailingReservedWidth={
|
|
633
|
+
nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0
|
|
634
|
+
}
|
|
589
635
|
nativeTabBarContentPaddingHorizontal={
|
|
590
636
|
nativeTabBarEnabled
|
|
591
637
|
? nativeTabBarStyle?.contentPaddingHorizontal ?? 20
|
|
@@ -580,6 +580,23 @@ 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
|
+
|
|
583
600
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
584
601
|
this.scrollCurrentPageToTop(
|
|
585
602
|
this.headerHeight,
|
|
@@ -674,10 +691,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
674
691
|
)
|
|
675
692
|
return;
|
|
676
693
|
this.pageOffsets.set(key, 0);
|
|
677
|
-
this.sharedHeaderOffset = offset;
|
|
694
|
+
this.sharedHeaderOffset = Math.min(this.headerHeight, Math.max(0, offset));
|
|
678
695
|
this.bodyAlignment = { element, key };
|
|
679
696
|
scrollElementTo(element, {
|
|
680
|
-
top:
|
|
697
|
+
top: this.sharedHeaderOffset,
|
|
681
698
|
behavior: animated ? "smooth" : "instant",
|
|
682
699
|
});
|
|
683
700
|
this.syncFallbackHeader();
|
|
@@ -67,6 +67,7 @@ export interface NativeProps extends ViewProps {
|
|
|
67
67
|
retainedPages?: string;
|
|
68
68
|
nativeTabBarItems?: string;
|
|
69
69
|
nativeTabBarHeight?: Double;
|
|
70
|
+
nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
|
|
70
71
|
nativeTabBarContentPaddingHorizontal?: Double;
|
|
71
72
|
nativeTabBarItemSpacing?: Double;
|
|
72
73
|
nativeTabBarFontSize?: Double;
|
|
@@ -91,6 +92,14 @@ export interface NativeProps extends ViewProps {
|
|
|
91
92
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
92
93
|
|
|
93
94
|
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;
|
|
94
103
|
scrollToTop: (
|
|
95
104
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
96
105
|
target: Int32,
|
|
@@ -112,6 +121,8 @@ export interface NativeCommands {
|
|
|
112
121
|
|
|
113
122
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
114
123
|
supportedCommands: [
|
|
124
|
+
"scrollHeaderToOffset",
|
|
125
|
+
"scrollHeaderBy",
|
|
115
126
|
"scrollToTop",
|
|
116
127
|
"setPage",
|
|
117
128
|
"setPageWithoutAnimation",
|