@onekeyfe/react-native-pager-view 6.7.0-alpha.344 → 6.7.0-alpha.359
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 +247 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +76 -21
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +72 -7
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +27 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
- package/docs/SPEC.md +102 -2
- package/ios/RNCCollapsiblePagerViewComponentView.mm +124 -12
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +178 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +65 -0
- package/lib/module/CollapsiblePagerView.js +20 -4
- package/lib/module/CollapsiblePagerView.web.js +11 -2
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +13 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +2 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +5 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +68 -4
- package/src/CollapsiblePagerView.web.tsx +19 -2
- package/src/CollapsiblePagerViewNativeComponent.ts +13 -0
|
@@ -239,6 +239,18 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
239
239
|
view.applyNativeHeaderStyle()
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
@ReactProp(name = "nativeTabBarVariant")
|
|
243
|
+
override fun setNativeTabBarVariant(view: CollapsiblePagerHost?, value: String?) {
|
|
244
|
+
view ?: return
|
|
245
|
+
view.nativeTabBarVariant = if (value == "segmented") "segmented" else "underline"
|
|
246
|
+
view.applyNativeHeaderStyle()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@ReactProp(name = "nativeTabBarTrailingReservedWidth", defaultDouble = 0.0)
|
|
250
|
+
override fun setNativeTabBarTrailingReservedWidth(view: CollapsiblePagerHost?, value: Double) {
|
|
251
|
+
view?.nativeTabBarTrailingReservedWidth = value
|
|
252
|
+
}
|
|
253
|
+
|
|
242
254
|
@ReactProp(name = "nativeTabBarContentPaddingHorizontal")
|
|
243
255
|
override fun setNativeTabBarContentPaddingHorizontal(
|
|
244
256
|
view: CollapsiblePagerHost?,
|
|
@@ -249,6 +261,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
249
261
|
view.applyNativeHeaderStyle()
|
|
250
262
|
}
|
|
251
263
|
|
|
264
|
+
@ReactProp(name = "nativeTabBarContentPaddingVertical")
|
|
265
|
+
override fun setNativeTabBarContentPaddingVertical(view: CollapsiblePagerHost?, value: Double) {
|
|
266
|
+
view ?: return
|
|
267
|
+
view.nativeTabBarContentPaddingVertical = if (value.isFinite()) value.coerceAtLeast(0.0) else 0.0
|
|
268
|
+
view.applyNativeHeaderStyle()
|
|
269
|
+
}
|
|
270
|
+
|
|
252
271
|
@ReactProp(name = "nativeTabBarItemSpacing")
|
|
253
272
|
override fun setNativeTabBarItemSpacing(view: CollapsiblePagerHost?, value: Double) {
|
|
254
273
|
view ?: return
|
|
@@ -343,6 +362,14 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
343
362
|
view?.scrollToTop(target, animated)
|
|
344
363
|
}
|
|
345
364
|
|
|
365
|
+
override fun scrollHeaderBy(view: CollapsiblePagerHost?, delta: Double) {
|
|
366
|
+
view?.scrollHeaderBy(delta)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
override fun scrollHeaderToOffset(view: CollapsiblePagerHost?, offset: Double) {
|
|
370
|
+
view?.scrollHeaderToOffset(offset)
|
|
371
|
+
}
|
|
372
|
+
|
|
346
373
|
override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
347
374
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
348
375
|
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
|
|
@@ -452,6 +496,40 @@ The attached UIKit regression intentionally constrains the real pager pan to two
|
|
|
452
496
|
required touches to prove the no-pan path; it is distinct from natural Home OS
|
|
453
497
|
input, coalesced-event delivery, and physical-device acceptance.
|
|
454
498
|
|
|
499
|
+
## Native segmented tab bar
|
|
500
|
+
|
|
501
|
+
Status: implemented locally. Focused production-class iOS XCTest and Android
|
|
502
|
+
instrumentation checks pass; consuming App interaction acceptance is separate.
|
|
503
|
+
`nativeTabBar.style.variant` accepts `"underline"` (the default) or
|
|
504
|
+
`"segmented"` on iOS and Android. The optional
|
|
505
|
+
`nativeTabBar.style.contentPaddingVertical` is a nonnegative logical inset,
|
|
506
|
+
defaulting to zero. Omitting both fields preserves the existing content-width,
|
|
507
|
+
horizontally scrollable underline bar. Web ignores `nativeTabBar` and retains
|
|
508
|
+
its caller-rendered sticky header.
|
|
509
|
+
|
|
510
|
+
In segmented mode, the existing `height` is the entire tab slot. The track
|
|
511
|
+
fills the space inside `contentPaddingHorizontal` and
|
|
512
|
+
`contentPaddingVertical`, uses `backgroundColor`, and has a fully rounded
|
|
513
|
+
outline. Its visible items divide the track into equal-width segments in their
|
|
514
|
+
current order, including after item insertion, removal, relabeling, or resize.
|
|
515
|
+
The thumb uses `indicatorColor`, is inset two logical points from its segment,
|
|
516
|
+
and is fully rounded. Segment titles use the existing font and active/inactive
|
|
517
|
+
text colors, remain single-line with truncation, and retain their existing
|
|
518
|
+
accessibility labels, test IDs, and native press event keys. The segmented bar
|
|
519
|
+
does not scroll horizontally; `itemSpacing`, `indicatorHeight`, and
|
|
520
|
+
`indicatorBottom` apply only to underline mode. The pager's native fractional
|
|
521
|
+
page progress moves the thumb continuously between equal-width segments and
|
|
522
|
+
interpolates text emphasis without per-frame JavaScript updates. It settles at
|
|
523
|
+
the actual selected page after a completed or cancelled gesture. RTL reverses
|
|
524
|
+
physical segment order without changing logical page keys or indices.
|
|
525
|
+
|
|
526
|
+
Switching variants or removing the native tab bar clears the previous visual
|
|
527
|
+
state. The existing native press command, page callbacks, retention, vertical
|
|
528
|
+
header geometry, and Web behavior remain unchanged. Acceptance requires
|
|
529
|
+
focused native geometry/progress/press checks on iOS and Android, including
|
|
530
|
+
two or three segments, dynamic labels/items, RTL, narrow widths, and variant
|
|
531
|
+
switching. Type forwarding alone does not prove rendered behavior.
|
|
532
|
+
|
|
455
533
|
## Native tab-press animation default
|
|
456
534
|
|
|
457
535
|
Behavior change: previously an omitted `nativeTabPressAnimationEnabled` animated
|
|
@@ -472,6 +550,28 @@ Status: implemented; focused native-wrapper tests cover both directions,
|
|
|
472
550
|
explicit overrides and in-flight/superseded command targets. Home applies this policy to its JS tab bar, with iOS and
|
|
473
551
|
Android simulator recordings verifying distant jumps and adjacent transitions.
|
|
474
552
|
|
|
553
|
+
## Native tab trailing control reservation
|
|
554
|
+
|
|
555
|
+
Status: implemented locally, unreleased. The focused Android host test passed
|
|
556
|
+
(1/1); iOS hit tests had 8 passes and 1 existing skip. iOS companion-app UI
|
|
557
|
+
acceptance remains unverified.
|
|
558
|
+
`nativeTabBar.style.trailingReservedWidth` is an optional logical trailing
|
|
559
|
+
reservation for a sibling React control in the full-width sticky header. It
|
|
560
|
+
defaults to zero. JavaScript normalizes non-finite or negative input to zero;
|
|
561
|
+
native also clamps the reservation to the actual tab viewport width. On Android
|
|
562
|
+
and iOS, only the native tab viewport shrinks from `W` to `W - R`: its frame
|
|
563
|
+
begins at zero in LTR and at `R` in RTL. The sticky React header, its height,
|
|
564
|
+
and page content keep their original full-width geometry. Touches in the
|
|
565
|
+
reserved region reach the sibling control, while native tab hit testing and
|
|
566
|
+
last-tab visibility use the reduced viewport. Changing the reservation updates
|
|
567
|
+
that viewport; removing it or recycling the pager restores the default zero.
|
|
568
|
+
Web does not render the native tab and ignores this style field. For a positive
|
|
569
|
+
reservation, the box-none sticky React control draws above the native tab so
|
|
570
|
+
its inward fade and toolbar hit area remain visible; zero preserves the old
|
|
571
|
+
native-tab foreground order. Acceptance requires focused native layout and hit
|
|
572
|
+
testing for zero, nonzero, reset, and RTL reservations; type forwarding alone
|
|
573
|
+
is insufficient.
|
|
574
|
+
|
|
475
575
|
## Android refresh foreground coordination
|
|
476
576
|
|
|
477
577
|
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
|
|
|
@@ -368,7 +369,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
368
369
|
@property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
|
|
369
370
|
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
370
371
|
- (void)updateStyleWithHeight:(CGFloat)height
|
|
372
|
+
variant:(NSString *)variant
|
|
371
373
|
contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
|
|
374
|
+
contentPaddingVertical:(CGFloat)contentPaddingVertical
|
|
372
375
|
itemSpacing:(CGFloat)itemSpacing
|
|
373
376
|
fontSize:(CGFloat)fontSize
|
|
374
377
|
fontFamily:(NSString *)fontFamily
|
|
@@ -385,12 +388,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
385
388
|
|
|
386
389
|
@implementation RNCCollapsiblePagerNativeTabBarView {
|
|
387
390
|
RNCCollapsiblePagerHorizontalScrollView *_scrollView;
|
|
391
|
+
UIView *_trackView;
|
|
388
392
|
UIView *_indicatorView;
|
|
389
393
|
NSArray<NSDictionary *> *_items;
|
|
390
394
|
NSMutableArray<UIButton *> *_buttons;
|
|
391
395
|
NSMutableArray<NSValue *> *_indicatorFrames;
|
|
392
396
|
CGFloat _barHeight;
|
|
393
397
|
CGFloat _contentPaddingHorizontal;
|
|
398
|
+
CGFloat _contentPaddingVertical;
|
|
399
|
+
BOOL _segmented;
|
|
394
400
|
CGFloat _itemSpacing;
|
|
395
401
|
CGFloat _fontSize;
|
|
396
402
|
NSString *_fontFamily;
|
|
@@ -413,6 +419,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
413
419
|
_indicatorFrames = [NSMutableArray new];
|
|
414
420
|
_barHeight = 44;
|
|
415
421
|
_contentPaddingHorizontal = 20;
|
|
422
|
+
_contentPaddingVertical = 0;
|
|
423
|
+
_segmented = NO;
|
|
416
424
|
_itemSpacing = 8;
|
|
417
425
|
_fontSize = 16;
|
|
418
426
|
_activeTextColor = UIColor.labelColor;
|
|
@@ -434,6 +442,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
434
442
|
_scrollView.delegate = self;
|
|
435
443
|
[self addSubview:_scrollView];
|
|
436
444
|
|
|
445
|
+
_trackView = [UIView new];
|
|
446
|
+
_trackView.userInteractionEnabled = NO;
|
|
447
|
+
[_scrollView addSubview:_trackView];
|
|
448
|
+
|
|
437
449
|
_indicatorView = [UIView new];
|
|
438
450
|
_indicatorView.userInteractionEnabled = NO;
|
|
439
451
|
[_scrollView addSubview:_indicatorView];
|
|
@@ -488,7 +500,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
488
500
|
}
|
|
489
501
|
|
|
490
502
|
- (void)updateStyleWithHeight:(CGFloat)height
|
|
503
|
+
variant:(NSString *)variant
|
|
491
504
|
contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
|
|
505
|
+
contentPaddingVertical:(CGFloat)contentPaddingVertical
|
|
492
506
|
itemSpacing:(CGFloat)itemSpacing
|
|
493
507
|
fontSize:(CGFloat)fontSize
|
|
494
508
|
fontFamily:(NSString *)fontFamily
|
|
@@ -500,19 +514,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
500
514
|
indicatorBottom:(CGFloat)indicatorBottom
|
|
501
515
|
{
|
|
502
516
|
_barHeight = MAX(1, height);
|
|
517
|
+
BOOL wasSegmented = _segmented;
|
|
518
|
+
_segmented = [variant isEqualToString:@"segmented"];
|
|
503
519
|
_contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
|
|
520
|
+
_contentPaddingVertical = MAX(0, contentPaddingVertical);
|
|
504
521
|
_itemSpacing = MAX(0, itemSpacing);
|
|
505
522
|
_fontSize = MAX(1, fontSize);
|
|
506
523
|
_fontFamily = [fontFamily copy];
|
|
507
|
-
self.backgroundColor = backgroundColor ?: UIColor.clearColor;
|
|
524
|
+
self.backgroundColor = _segmented ? UIColor.clearColor : (backgroundColor ?: UIColor.clearColor);
|
|
508
525
|
_scrollView.backgroundColor = self.backgroundColor;
|
|
526
|
+
_scrollView.scrollEnabled = !_segmented;
|
|
527
|
+
if (_segmented && !wasSegmented) [_scrollView setContentOffset:CGPointZero animated:NO];
|
|
528
|
+
_trackView.hidden = !_segmented;
|
|
529
|
+
_trackView.backgroundColor = backgroundColor ?: UIColor.clearColor;
|
|
509
530
|
_activeTextColor = activeTextColor ?: UIColor.labelColor;
|
|
510
531
|
_inactiveTextColor = inactiveTextColor ?: UIColor.secondaryLabelColor;
|
|
511
532
|
_indicatorColor = indicatorColor ?: _activeTextColor;
|
|
512
533
|
_indicatorHeight = MAX(0, indicatorHeight);
|
|
513
534
|
_indicatorBottom = MAX(0, indicatorBottom);
|
|
514
535
|
_indicatorView.backgroundColor = _indicatorColor;
|
|
515
|
-
_indicatorView.layer.cornerRadius = _indicatorHeight / 2;
|
|
536
|
+
_indicatorView.layer.cornerRadius = _segmented ? 0 : _indicatorHeight / 2;
|
|
516
537
|
for (UIButton *button in _buttons) button.titleLabel.font = self.tabFont;
|
|
517
538
|
[self setNeedsLayout];
|
|
518
539
|
}
|
|
@@ -571,6 +592,33 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
571
592
|
{
|
|
572
593
|
[super layoutSubviews];
|
|
573
594
|
_scrollView.frame = self.bounds;
|
|
595
|
+
if (_segmented) {
|
|
596
|
+
CGFloat width = CGRectGetWidth(self.bounds);
|
|
597
|
+
CGFloat height = CGRectGetHeight(self.bounds);
|
|
598
|
+
CGFloat insetX = MIN(_contentPaddingHorizontal, width / 2);
|
|
599
|
+
CGFloat insetY = MIN(_contentPaddingVertical, height / 2);
|
|
600
|
+
CGRect trackFrame = CGRectMake(insetX, insetY,
|
|
601
|
+
MAX(0, width - 2 * insetX), MAX(0, height - 2 * insetY));
|
|
602
|
+
_trackView.frame = trackFrame;
|
|
603
|
+
_trackView.layer.cornerRadius = CGRectGetHeight(trackFrame) / 2;
|
|
604
|
+
_scrollView.contentSize = self.bounds.size;
|
|
605
|
+
[_indicatorFrames removeAllObjects];
|
|
606
|
+
NSUInteger count = _buttons.count;
|
|
607
|
+
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
608
|
+
CGFloat first = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * index / count;
|
|
609
|
+
CGFloat last = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * (index + 1) / count;
|
|
610
|
+
CGFloat x = self->_leftToRight ? first : CGRectGetMaxX(trackFrame) - (last - CGRectGetMinX(trackFrame));
|
|
611
|
+
CGFloat end = self->_leftToRight ? last : CGRectGetMaxX(trackFrame) - (first - CGRectGetMinX(trackFrame));
|
|
612
|
+
button.frame = CGRectMake(x, CGRectGetMinY(trackFrame), MAX(0, end - x), CGRectGetHeight(trackFrame));
|
|
613
|
+
CGRect thumbFrame = CGRectMake(x + 2, CGRectGetMinY(trackFrame) + 2,
|
|
614
|
+
MAX(0, CGRectGetWidth(button.frame) - 4), MAX(0, CGRectGetHeight(trackFrame) - 4));
|
|
615
|
+
[self->_indicatorFrames addObject:[NSValue valueWithCGRect:thumbFrame]];
|
|
616
|
+
}];
|
|
617
|
+
_indicatorView.layer.cornerRadius = MAX(0, CGRectGetHeight(trackFrame) - 4) / 2;
|
|
618
|
+
[self updatePresentation];
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
_trackView.frame = CGRectZero;
|
|
574
622
|
UIFont *font = self.tabFont;
|
|
575
623
|
NSMutableArray<NSNumber *> *widths = [NSMutableArray arrayWithCapacity:_buttons.count];
|
|
576
624
|
CGFloat itemsWidth = 0;
|
|
@@ -634,10 +682,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
634
682
|
CGRectGetMinX(fromFrame) + (CGRectGetMinX(toFrame) - CGRectGetMinX(fromFrame)) * fraction,
|
|
635
683
|
CGRectGetMinY(fromFrame),
|
|
636
684
|
CGRectGetWidth(fromFrame) + (CGRectGetWidth(toFrame) - CGRectGetWidth(fromFrame)) * fraction,
|
|
637
|
-
_indicatorHeight
|
|
685
|
+
_segmented ? CGRectGetHeight(fromFrame) : _indicatorHeight
|
|
638
686
|
);
|
|
639
687
|
_indicatorView.frame = indicatorFrame;
|
|
640
|
-
_indicatorView.hidden =
|
|
688
|
+
_indicatorView.hidden = CGRectGetHeight(indicatorFrame) <= 0;
|
|
641
689
|
|
|
642
690
|
NSInteger selectedIndex = (NSInteger)round(progress);
|
|
643
691
|
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
@@ -654,7 +702,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
654
702
|
: UIAccessibilityTraitButton;
|
|
655
703
|
}];
|
|
656
704
|
|
|
657
|
-
if (_shouldCenterSelectedItem && !_scrollView.tracking && !_scrollView.dragging &&
|
|
705
|
+
if (_shouldCenterSelectedItem && !_segmented && !_scrollView.tracking && !_scrollView.dragging &&
|
|
658
706
|
!_scrollView.decelerating && CGRectGetWidth(_scrollView.bounds) > 0) {
|
|
659
707
|
CGFloat maximumOffset = MAX(0, _scrollView.contentSize.width - CGRectGetWidth(_scrollView.bounds));
|
|
660
708
|
CGFloat desiredOffset = RNCClamp(
|
|
@@ -1471,6 +1519,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1471
1519
|
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
|
|
1472
1520
|
- (void)restoreSharedHeadersToContainer;
|
|
1473
1521
|
- (void)layoutSharedHeaders;
|
|
1522
|
+
- (void)bringReservedStickyControlToFront;
|
|
1474
1523
|
- (void)restorePinnedStickyHeader;
|
|
1475
1524
|
- (void)updatePinnedStickyHeader;
|
|
1476
1525
|
- (UIScrollView *)currentVerticalScrollViewForParentPager;
|
|
@@ -1558,6 +1607,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1558
1607
|
NSUInteger _pendingTopInsetReboundGeneration;
|
|
1559
1608
|
NSTimer *_topInsetReboundTimer;
|
|
1560
1609
|
CGFloat _nativeTabBarHeight;
|
|
1610
|
+
CGFloat _nativeTabBarTrailingReservedWidth;
|
|
1561
1611
|
CGFloat _nativeSubHeaderHeight;
|
|
1562
1612
|
CGFloat _headerOffset;
|
|
1563
1613
|
BOOL _scrollEnabled;
|
|
@@ -2479,6 +2529,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2479
2529
|
}
|
|
2480
2530
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2481
2531
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2532
|
+
[self bringReservedStickyControlToFront];
|
|
2482
2533
|
}
|
|
2483
2534
|
|
|
2484
2535
|
- (void)restoreSharedHeadersToContainer
|
|
@@ -2584,10 +2635,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2584
2635
|
CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
|
|
2585
2636
|
? 0
|
|
2586
2637
|
: MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2638
|
+
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2587
2639
|
_nativeTabBarView.frame = CGRectMake(
|
|
2588
|
-
0,
|
|
2640
|
+
self.isLtrLayout ? 0 : tabReservedWidth,
|
|
2589
2641
|
stickyChildY,
|
|
2590
|
-
width,
|
|
2642
|
+
width - tabReservedWidth,
|
|
2591
2643
|
nativeTabBarVisibleHeight
|
|
2592
2644
|
);
|
|
2593
2645
|
_nativeSubHeaderView.frame = CGRectMake(
|
|
@@ -2601,9 +2653,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2601
2653
|
);
|
|
2602
2654
|
_topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
|
|
2603
2655
|
_topOverlayView.frame = _topOverlayHostView.bounds;
|
|
2656
|
+
[self bringReservedStickyControlToFront];
|
|
2604
2657
|
[self applyHeaderOffset];
|
|
2605
2658
|
}
|
|
2606
2659
|
|
|
2660
|
+
- (void)bringReservedStickyControlToFront
|
|
2661
|
+
{
|
|
2662
|
+
if (_stickyHeaderView != nil &&
|
|
2663
|
+
_stickyHeaderView.superview == _nativeTabBarView.superview) {
|
|
2664
|
+
if (_nativeTabBarTrailingReservedWidth > 0) {
|
|
2665
|
+
[_stickyHeaderView.superview bringSubviewToFront:_stickyHeaderView];
|
|
2666
|
+
} else {
|
|
2667
|
+
[_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
|
|
2668
|
+
if (!_nativeSubHeaderView.hidden &&
|
|
2669
|
+
_nativeSubHeaderView.superview == _nativeTabBarView.superview) {
|
|
2670
|
+
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2607
2676
|
- (void)updatePinnedStickyHeader
|
|
2608
2677
|
{
|
|
2609
2678
|
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
@@ -2650,8 +2719,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2650
2719
|
CGFloat childY = shouldPin ? 0 : _headerHeight;
|
|
2651
2720
|
CGFloat width = CGRectGetWidth(self.bounds);
|
|
2652
2721
|
CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2722
|
+
CGFloat tabReservedWidth = MIN(width, MAX(0, _nativeTabBarTrailingReservedWidth));
|
|
2653
2723
|
_stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
|
|
2654
|
-
_nativeTabBarView.frame = CGRectMake(0
|
|
2724
|
+
_nativeTabBarView.frame = CGRectMake(self.isLtrLayout ? 0 : tabReservedWidth,
|
|
2725
|
+
childY, width - tabReservedWidth, tabHeight);
|
|
2655
2726
|
_nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
|
|
2656
2727
|
MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
|
|
2657
2728
|
_pinnedStickyHostView.hidden = !shouldPin;
|
|
@@ -2671,6 +2742,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2671
2742
|
}
|
|
2672
2743
|
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2673
2744
|
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2745
|
+
[self bringReservedStickyControlToFront];
|
|
2674
2746
|
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2675
2747
|
[self logHomeVisualGeometry:shouldPin ? @"pin" : @"unpin"
|
|
2676
2748
|
scrollView:_observedScrollView];
|
|
@@ -3050,6 +3122,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3050
3122
|
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
3051
3123
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
3052
3124
|
if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
3125
|
+
[self bringReservedStickyControlToFront];
|
|
3053
3126
|
[_containerView bringSubviewToFront:_sharedHeaderHostView];
|
|
3054
3127
|
if (_stickyHeaderIsPinned) {
|
|
3055
3128
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -3262,6 +3335,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3262
3335
|
_homeVisualLastLayoutLog = 0;
|
|
3263
3336
|
_homeVisualLastGeometrySignature = nil;
|
|
3264
3337
|
_nativeTabBarHeight = 44;
|
|
3338
|
+
_nativeTabBarTrailingReservedWidth = 0;
|
|
3265
3339
|
_nativeSubHeaderHeight = 74;
|
|
3266
3340
|
_headerOffset = 0;
|
|
3267
3341
|
_currentLogicalOffset = 0;
|
|
@@ -3343,6 +3417,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3343
3417
|
_layoutDirection = RCTNSStringFromString(toString(newViewProps.layoutDirection));
|
|
3344
3418
|
[_nativeTabBarView setLeftToRight:self.isLtrLayout];
|
|
3345
3419
|
[_nativeSubHeaderView setLeftToRight:self.isLtrLayout];
|
|
3420
|
+
[self setNeedsLayout];
|
|
3421
|
+
}
|
|
3422
|
+
if (_needsPropsReapply ||
|
|
3423
|
+
oldViewProps.nativeTabBarTrailingReservedWidth != newViewProps.nativeTabBarTrailingReservedWidth) {
|
|
3424
|
+
double reserved = newViewProps.nativeTabBarTrailingReservedWidth;
|
|
3425
|
+
_nativeTabBarTrailingReservedWidth = std::isfinite(reserved) && reserved >= 0 ? reserved : 0;
|
|
3426
|
+
[self setNeedsLayout];
|
|
3346
3427
|
}
|
|
3347
3428
|
BOOL insetsChanged = NO;
|
|
3348
3429
|
if (_needsPropsReapply || oldViewProps.headerHeight != newViewProps.headerHeight) {
|
|
@@ -3423,7 +3504,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3423
3504
|
}
|
|
3424
3505
|
if (_needsPropsReapply ||
|
|
3425
3506
|
oldViewProps.nativeTabBarHeight != newViewProps.nativeTabBarHeight ||
|
|
3507
|
+
oldViewProps.nativeTabBarVariant != newViewProps.nativeTabBarVariant ||
|
|
3426
3508
|
oldViewProps.nativeTabBarContentPaddingHorizontal != newViewProps.nativeTabBarContentPaddingHorizontal ||
|
|
3509
|
+
oldViewProps.nativeTabBarContentPaddingVertical != newViewProps.nativeTabBarContentPaddingVertical ||
|
|
3427
3510
|
oldViewProps.nativeTabBarItemSpacing != newViewProps.nativeTabBarItemSpacing ||
|
|
3428
3511
|
oldViewProps.nativeTabBarFontSize != newViewProps.nativeTabBarFontSize ||
|
|
3429
3512
|
oldViewProps.nativeTabBarFontFamily != newViewProps.nativeTabBarFontFamily ||
|
|
@@ -3440,7 +3523,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3440
3523
|
NSString *fontFamily = RCTNSStringFromString(newViewProps.nativeTabBarFontFamily);
|
|
3441
3524
|
[_nativeTabBarView
|
|
3442
3525
|
updateStyleWithHeight:_nativeTabBarHeight
|
|
3526
|
+
variant:RCTNSStringFromString(newViewProps.nativeTabBarVariant)
|
|
3443
3527
|
contentPaddingHorizontal:MAX(0, newViewProps.nativeTabBarContentPaddingHorizontal)
|
|
3528
|
+
contentPaddingVertical:MAX(0, newViewProps.nativeTabBarContentPaddingVertical)
|
|
3444
3529
|
itemSpacing:MAX(0, newViewProps.nativeTabBarItemSpacing)
|
|
3445
3530
|
fontSize:newViewProps.nativeTabBarFontSize > 0 ? newViewProps.nativeTabBarFontSize : 16
|
|
3446
3531
|
fontFamily:fontFamily
|
|
@@ -3474,6 +3559,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3474
3559
|
if (!_nativeSubHeaderView.hidden) {
|
|
3475
3560
|
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
3476
3561
|
}
|
|
3562
|
+
[self bringReservedStickyControlToFront];
|
|
3477
3563
|
[_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
|
|
3478
3564
|
if (_stickyHeaderIsPinned) {
|
|
3479
3565
|
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
@@ -4438,8 +4524,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4438
4524
|
|
|
4439
4525
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
4440
4526
|
{
|
|
4441
|
-
|
|
4442
|
-
|
|
4527
|
+
id host = scrollView.superview;
|
|
4528
|
+
if ([host respondsToSelector:@selector(setPagerStickyHeight:forOwner:)]) {
|
|
4529
|
+
[host setPagerStickyHeight:height forOwner:self];
|
|
4443
4530
|
}
|
|
4444
4531
|
}
|
|
4445
4532
|
|
|
@@ -5233,6 +5320,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5233
5320
|
{
|
|
5234
5321
|
UIScrollView *scrollView = _pagerTopScrollView;
|
|
5235
5322
|
if (_dispatchingPagerTop || ![self matchesPagerTop:scrollView]) return;
|
|
5323
|
+
CGFloat clampedTarget = MIN(MAX(_pagerTopLogicalTarget, 0), _headerHeight);
|
|
5324
|
+
if (clampedTarget != _pagerTopLogicalTarget) {
|
|
5325
|
+
_pagerTopLogicalTarget = clampedTarget;
|
|
5326
|
+
CGFloat physicalTarget = RNCLimitRestoredScrollOffset(clampedTarget - scrollView.contentInset.top,
|
|
5327
|
+
scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
|
|
5328
|
+
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:NO];
|
|
5329
|
+
}
|
|
5236
5330
|
CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
5237
5331
|
if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
|
|
5238
5332
|
_pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
|
|
@@ -5244,11 +5338,29 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5244
5338
|
}
|
|
5245
5339
|
|
|
5246
5340
|
- (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
|
|
5341
|
+
{
|
|
5342
|
+
if (target != 0 && target != 1) return;
|
|
5343
|
+
[self scrollHeaderToOffset:(target == 1 ? _headerHeight : 0) animated:animated];
|
|
5344
|
+
}
|
|
5345
|
+
|
|
5346
|
+
- (void)scrollHeaderBy:(double)delta
|
|
5347
|
+
{
|
|
5348
|
+
if (!std::isfinite(delta)) return;
|
|
5349
|
+
[self scrollHeaderToOffset:MIN(MAX(_headerOffset + delta, 0), _headerHeight) animated:NO];
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
- (void)scrollHeaderToOffset:(double)offset
|
|
5353
|
+
{
|
|
5354
|
+
if (!std::isfinite(offset)) return;
|
|
5355
|
+
[self scrollHeaderToOffset:MIN(MAX(offset, 0), _headerHeight) animated:NO];
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5358
|
+
- (void)scrollHeaderToOffset:(CGFloat)logicalTarget animated:(BOOL)animated
|
|
5247
5359
|
{
|
|
5248
5360
|
[self cancelTopInsetReboundCheck];
|
|
5249
5361
|
UIScrollView *scrollView = _observedScrollView;
|
|
5250
5362
|
NSString *key = _observedPageKey;
|
|
5251
|
-
if (
|
|
5363
|
+
if (_isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
5252
5364
|
_transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
|
|
5253
5365
|
key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
|
|
5254
5366
|
_observedPageGeneration != _generation || self.window == nil) return;
|
|
@@ -5264,7 +5376,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5264
5376
|
[_alignedBodyKeys removeObject:key];
|
|
5265
5377
|
[_suspendedBodyKeys removeObject:key];
|
|
5266
5378
|
[_displayBodyOverrides removeObjectForKey:key];
|
|
5267
|
-
|
|
5379
|
+
logicalTarget = MIN(MAX(logicalTarget, 0), _headerHeight);
|
|
5268
5380
|
_pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
|
|
5269
5381
|
_pagerTopScrollView = scrollView;
|
|
5270
5382
|
_pagerTopPageKey = [key copy];
|