@onekeyfe/react-native-pager-view 3.0.180 → 6.7.0-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +2 -2
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +2 -2
- package/docs/SPEC.md +31 -4
- package/ios/RNCCollapsiblePagerViewComponentView.mm +84 -21
- package/ios/RNCNativeScrollerComponentView.mm +6 -5
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +26 -1
- package/ios/tests/RNCNativeScrollerTests.mm +39 -0
- package/lib/module/CollapsiblePagerView.js +4 -4
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/lib/module/NativeScroller.js +2 -2
- package/lib/module/NativeScrollerNativeComponent.ts +1 -1
- package/lib/module/ScrollEdgeEffect.js +8 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.web.js +1 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +4 -3
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +2 -1
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +1 -1
- package/lib/typescript/src/NativeScroller.d.ts +3 -2
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +1 -1
- package/lib/typescript/src/ScrollEdgeEffect.d.ts +5 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.web.d.ts +1 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +12 -7
- package/src/CollapsiblePagerView.web.tsx +2 -1
- package/src/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/src/NativeScroller.tsx +6 -4
- package/src/NativeScrollerNativeComponent.ts +1 -1
- package/src/ScrollEdgeEffect.ts +4 -0
- package/src/index.tsx +2 -0
- package/src/index.web.tsx +2 -0
|
@@ -207,8 +207,8 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// The scroll-edge effect and external overlay are UIKit-only.
|
|
210
|
-
@ReactProp(name = "
|
|
211
|
-
override fun
|
|
210
|
+
@ReactProp(name = "topScrollEdgeEffect")
|
|
211
|
+
override fun setTopScrollEdgeEffect(view: CollapsiblePagerHost?, value: String?) = Unit
|
|
212
212
|
@ReactProp(name = "topOverlayColorScheme")
|
|
213
213
|
override fun setTopOverlayColorScheme(view: CollapsiblePagerHost?, value: String?) = Unit
|
|
214
214
|
@ReactProp(name = "topOverlayHeight", defaultDouble = 0.0)
|
|
@@ -49,8 +49,8 @@ class NativeScrollerViewManager : ViewGroupManager<NativeScrollerView>(),
|
|
|
49
49
|
override fun setContentWidth(view: NativeScrollerView, value: Double) = view.setContentSize(width = view.dip(value))
|
|
50
50
|
@ReactProp(name = "contentHeight")
|
|
51
51
|
override fun setContentHeight(view: NativeScrollerView, value: Double) = view.setContentSize(height = view.dip(value))
|
|
52
|
-
@ReactProp(name = "
|
|
53
|
-
override fun
|
|
52
|
+
@ReactProp(name = "topScrollEdgeEffect")
|
|
53
|
+
override fun setTopScrollEdgeEffect(view: NativeScrollerView, value: String?) = Unit
|
|
54
54
|
@ReactProp(name = "topOverlayHeight", defaultDouble = 0.0)
|
|
55
55
|
override fun setTopOverlayHeight(view: NativeScrollerView, value: Double) = Unit
|
|
56
56
|
@ReactProp(name = "scrollEnabled", defaultBoolean = true)
|
package/docs/SPEC.md
CHANGED
|
@@ -7,13 +7,17 @@ scroll-edge props.
|
|
|
7
7
|
## iOS 26 top scroll edge overlay
|
|
8
8
|
|
|
9
9
|
`CollapsiblePagerView` and standalone `NativeScroller` accept the optional
|
|
10
|
-
`topScrollEdgeEffect=
|
|
10
|
+
`topScrollEdgeEffect={ScrollEdgeEffect.Hard}` or `ScrollEdgeEffect.Soft` with a
|
|
11
|
+
fixed `topOverlay` React child. `ScrollEdgeEffect` is an exported string enum
|
|
12
|
+
(`Hard = "hard"`, `Soft = "soft"`); the corresponding string literals remain
|
|
13
|
+
valid prop values. Both the style and overlay are
|
|
11
14
|
disabled by default. On iOS 26 or newer, a local overlay remains fixed at the
|
|
12
15
|
top of the viewport while the owned vertical `UIScrollView` can draw behind it.
|
|
13
16
|
The component measures the overlay's Fabric layout height and owns the
|
|
14
17
|
corresponding top content inset. It binds UIKit's
|
|
15
18
|
`UIScrollEdgeElementContainerInteraction` to that exact scroll view and selects
|
|
16
|
-
the
|
|
19
|
+
the requested top edge style. Hard has UIKit's dividing line; Soft has its
|
|
20
|
+
gradual blurred transition. A pager owns the effect and inset for its current page;
|
|
17
21
|
its nested NativeList or NativeScroller must not opt into another top overlay.
|
|
18
22
|
Invisible, noninteractive native labels let UIKit recognize the React-drawn
|
|
19
23
|
overlay and sticky header as scroll-edge elements. The pager does not add an
|
|
@@ -28,7 +32,7 @@ The pager weakly observes its geometry in the same window, retaining its own
|
|
|
28
32
|
noninteractive native proxy for UIKit recognition without reparenting or
|
|
29
33
|
modifying the React header. The measured external header bottom supplies the
|
|
30
34
|
top inset and sticky anchor. A local `topOverlay` takes precedence. The tag
|
|
31
|
-
alone never enables the effect: `topScrollEdgeEffect
|
|
35
|
+
alone never enables the effect: either `topScrollEdgeEffect` enum value controls the
|
|
32
36
|
interaction, while valid external geometry can reserve the top inset before
|
|
33
37
|
the effect is enabled. Before the tag and height are ready, the caller keeps
|
|
34
38
|
its existing layout; an unresolved native tag has zero external height. An
|
|
@@ -47,7 +51,7 @@ When a smooth shared header reaches the overlay boundary, the existing sticky
|
|
|
47
51
|
React and native tab views move together into a view pinned to the active
|
|
48
52
|
scroll view. Their identities, scroll-view gesture ancestry and hit targets
|
|
49
53
|
remain intact; the collapsible header stays in the scrolling header host.
|
|
50
|
-
Without a local or external overlay,
|
|
54
|
+
Without a local or external overlay, either scroll-edge style may protect
|
|
51
55
|
only the genuinely pinned sticky header; it must not consume a page as a
|
|
52
56
|
synthetic overlay slot or change the collapsible banner distance.
|
|
53
57
|
Changing pages, replacing the active list or recycling returns those views to
|
|
@@ -69,6 +73,29 @@ Acceptance requires real iOS 26 scrolling, partial collapse, short/empty page,
|
|
|
69
73
|
refresh, page transition/cancellation, dynamic tabs and light/dark checks;
|
|
70
74
|
source and build checks alone do not establish visual correctness.
|
|
71
75
|
|
|
76
|
+
Experimental trial: `CollapsiblePagerView` paints a cover at each owned
|
|
77
|
+
fixed-overlay or pinned-sticky boundary with one physical pixel of the nearest
|
|
78
|
+
opaque ancestor background color (system background if none is available).
|
|
79
|
+
These native layers do not receive touches, change insets, or clip glass shadows.
|
|
80
|
+
They are removed for Soft, disabled/detached effects, and recycling. This is a
|
|
81
|
+
visual workaround using public layer APIs, not a UIKit separator setting; rendered
|
|
82
|
+
light/dark, scrolling, and transition acceptance is pending. Standalone
|
|
83
|
+
NativeScroller and NativeList retain UIKit's unmodified Hard appearance.
|
|
84
|
+
On the iOS 26.5 simulator, a runtime probe confirmed Hard and the cover layers,
|
|
85
|
+
including pinned-header ownership. The original hairline was not reliably
|
|
86
|
+
reproduced with the covers hidden, so separator suppression is not yet verified.
|
|
87
|
+
|
|
88
|
+
Style selection crosses Fabric as the enum `none | hard | soft`, defaulting to
|
|
89
|
+
`none`, rather than a hard-effect boolean. Switching Hard ↔ Soft on an attached
|
|
90
|
+
owner updates UIKit without replacing the overlay or changing its inset,
|
|
91
|
+
logical scroll offset, or saved original style. Clearing the prop, detaching,
|
|
92
|
+
or recycling releases the interaction and restores that original style.
|
|
93
|
+
This bridge contract requires a matching native rebuild. Soft selection is
|
|
94
|
+
implemented in the public wrappers and native hosts. Focused JS tests cover
|
|
95
|
+
style forwarding, overlay retention and disabling; UIKit tests cover native
|
|
96
|
+
style switching and restoring the previous style. Rendered iOS acceptance must
|
|
97
|
+
be reported separately from type, codegen and build checks.
|
|
98
|
+
|
|
72
99
|
## Smooth shared-header and per-page body memory
|
|
73
100
|
|
|
74
101
|
With nativeSmoothHeaderScrollEnabled, the header collapse C is shared across all
|
|
@@ -1467,6 +1467,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1467
1467
|
- (void)cancelTopInsetReboundCheck;
|
|
1468
1468
|
- (void)scheduleTopInsetReboundCheck;
|
|
1469
1469
|
- (void)syncScrollEdgeAppearance;
|
|
1470
|
+
- (void)updateHardEdgeSeparatorCovers;
|
|
1470
1471
|
- (void)updateScrollEdgeContentMasks;
|
|
1471
1472
|
- (void)restoreScrollEdgeContentMasks;
|
|
1472
1473
|
- (void)sharedHeaderPressCancellationGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
@@ -1503,6 +1504,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1503
1504
|
UIScrollEdgeElementContainerInteraction *_stickyEdgeInteraction;
|
|
1504
1505
|
UILabel *_topEdgeMarker;
|
|
1505
1506
|
UILabel *_stickyEdgeMarker;
|
|
1507
|
+
CALayer *_topEdgeSeparatorCover;
|
|
1508
|
+
CALayer *_stickyEdgeSeparatorCover;
|
|
1506
1509
|
__weak UIView *_topEdgeInteractionHost;
|
|
1507
1510
|
__weak UIView *_stickyEdgeInteractionHost;
|
|
1508
1511
|
__weak UIScrollView *_topEdgeScrollView;
|
|
@@ -1521,7 +1524,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1521
1524
|
__weak RNCPagerViewComponentView *_externalOuterPager;
|
|
1522
1525
|
BOOL _externalOuterPageActive;
|
|
1523
1526
|
BOOL _externalOuterTransitioning;
|
|
1524
|
-
BOOL
|
|
1527
|
+
BOOL _topScrollEdgeEnabled;
|
|
1528
|
+
RNCCollapsiblePagerViewTopScrollEdgeEffect _topScrollEdgeEffect;
|
|
1525
1529
|
NSString *_topOverlayColorScheme;
|
|
1526
1530
|
BOOL _pendingTopOverlayAppearanceCommit;
|
|
1527
1531
|
BOOL _applyingTopOverlayInsetChange;
|
|
@@ -1914,7 +1918,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1914
1918
|
|
|
1915
1919
|
- (void)syncScrollEdgeAppearance
|
|
1916
1920
|
{
|
|
1917
|
-
BOOL active =
|
|
1921
|
+
BOOL active = _topScrollEdgeEnabled &&
|
|
1918
1922
|
(self.hasFixedTopOverlay || _stickyHeaderView != nil) && self.window != nil &&
|
|
1919
1923
|
!_isBeingRecycled;
|
|
1920
1924
|
UIUserInterfaceStyle style = UIUserInterfaceStyleUnspecified;
|
|
@@ -1952,7 +1956,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1952
1956
|
|
|
1953
1957
|
- (void)updateScrollEdgeContentMasks
|
|
1954
1958
|
{
|
|
1955
|
-
|
|
1959
|
+
[self updateHardEdgeSeparatorCovers];
|
|
1960
|
+
BOOL enabled = _topScrollEdgeEnabled &&
|
|
1956
1961
|
(self.hasFixedTopOverlay || _stickyHeaderView != nil) &&
|
|
1957
1962
|
self.window != nil && !_isBeingRecycled;
|
|
1958
1963
|
if (!enabled) {
|
|
@@ -2052,9 +2057,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2052
2057
|
BOOL externalOwnerReady = _externalTopOverlayTag <= 0 ||
|
|
2053
2058
|
_externalOuterPager == nil ||
|
|
2054
2059
|
(_externalOuterPageActive && !_externalOuterTransitioning);
|
|
2055
|
-
BOOL hasOverlay =
|
|
2060
|
+
BOOL hasOverlay = _topScrollEdgeEnabled && self.hasFixedTopOverlay &&
|
|
2056
2061
|
externalOwnerReady && self.window != nil;
|
|
2057
|
-
BOOL hasSticky =
|
|
2062
|
+
BOOL hasSticky = _topScrollEdgeEnabled && _stickyHeaderView != nil &&
|
|
2058
2063
|
externalOwnerReady && self.window != nil;
|
|
2059
2064
|
BOOL enabled = (hasOverlay || hasSticky) && scrollView != nil;
|
|
2060
2065
|
BOOL topHostChanged = (_topEdgeInteraction != nil) != hasOverlay ||
|
|
@@ -2080,7 +2085,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2080
2085
|
if (enabled && _topEdgeScrollView == nil) {
|
|
2081
2086
|
_originalTopEdgeStyle = scrollView.topEdgeEffect.style;
|
|
2082
2087
|
_topEdgeScrollView = scrollView;
|
|
2083
|
-
|
|
2088
|
+
}
|
|
2089
|
+
if (enabled) {
|
|
2090
|
+
UIScrollEdgeEffectStyle *style =
|
|
2091
|
+
_topScrollEdgeEffect == RNCCollapsiblePagerViewTopScrollEdgeEffect::Soft
|
|
2092
|
+
? UIScrollEdgeEffectStyle.softStyle : UIScrollEdgeEffectStyle.hardStyle;
|
|
2093
|
+
if (scrollView.topEdgeEffect.style != style) scrollView.topEdgeEffect.style = style;
|
|
2084
2094
|
}
|
|
2085
2095
|
if (hasOverlay && enabled && _topEdgeInteraction == nil) {
|
|
2086
2096
|
// UIKit needs a native element to shape the edge effect around React-drawn content.
|
|
@@ -2135,6 +2145,54 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2135
2145
|
}
|
|
2136
2146
|
}
|
|
2137
2147
|
|
|
2148
|
+
- (void)updateHardEdgeSeparatorCovers
|
|
2149
|
+
{
|
|
2150
|
+
BOOL enabled = _topScrollEdgeEnabled &&
|
|
2151
|
+
_topScrollEdgeEffect == RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard &&
|
|
2152
|
+
_topEdgeScrollView != nil && self.window != nil && !_isBeingRecycled;
|
|
2153
|
+
if (!enabled && _topEdgeSeparatorCover == nil && _stickyEdgeSeparatorCover == nil) return;
|
|
2154
|
+
UIColor *color = nil;
|
|
2155
|
+
for (UIView *view = self; view != nil; view = view.superview) {
|
|
2156
|
+
UIColor *candidate = [view.backgroundColor resolvedColorWithTraitCollection:
|
|
2157
|
+
_topOverlayHostView.traitCollection];
|
|
2158
|
+
if (candidate != nil && CGColorGetAlpha(candidate.CGColor) >= 1) {
|
|
2159
|
+
color = candidate;
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
color = color ?: [UIColor.systemBackgroundColor resolvedColorWithTraitCollection:
|
|
2164
|
+
_topOverlayHostView.traitCollection];
|
|
2165
|
+
CGFloat scale = self.window.screen.scale ?: self.traitCollection.displayScale;
|
|
2166
|
+
CGFloat pixel = 1.0 / MAX(1, scale);
|
|
2167
|
+
UIView *hosts[] = {_topEdgeInteractionHost, _stickyEdgeInteractionHost};
|
|
2168
|
+
CALayer *covers[] = {_topEdgeSeparatorCover, _stickyEdgeSeparatorCover};
|
|
2169
|
+
for (NSInteger index = 0; index < 2; index++) {
|
|
2170
|
+
UIView *host = hosts[index];
|
|
2171
|
+
CALayer *cover = covers[index];
|
|
2172
|
+
if (!enabled || host == nil || host.hidden || CGRectIsEmpty(host.bounds)) {
|
|
2173
|
+
[cover removeFromSuperlayer];
|
|
2174
|
+
cover = nil;
|
|
2175
|
+
} else {
|
|
2176
|
+
if (cover == nil) {
|
|
2177
|
+
cover = [CALayer layer];
|
|
2178
|
+
cover.name = @"onekey-hard-edge-separator-cover";
|
|
2179
|
+
cover.zPosition = 1;
|
|
2180
|
+
cover.actions = @{@"position": NSNull.null, @"bounds": NSNull.null,
|
|
2181
|
+
@"backgroundColor": NSNull.null};
|
|
2182
|
+
[_containerView.layer addSublayer:cover];
|
|
2183
|
+
}
|
|
2184
|
+
// Cover only the hairline; keep the native Hard material and glass shadows.
|
|
2185
|
+
CGFloat boundary = CGRectGetMaxY([host convertRect:host.bounds toView:_containerView]);
|
|
2186
|
+
cover.frame = CGRectMake(0, round(boundary * scale) / scale - pixel,
|
|
2187
|
+
CGRectGetWidth(_containerView.bounds), pixel);
|
|
2188
|
+
cover.contentsScale = scale;
|
|
2189
|
+
cover.backgroundColor = color.CGColor;
|
|
2190
|
+
}
|
|
2191
|
+
if (index == 0) _topEdgeSeparatorCover = cover;
|
|
2192
|
+
else _stickyEdgeSeparatorCover = cover;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2138
2196
|
- (void)dealloc
|
|
2139
2197
|
{
|
|
2140
2198
|
[self updateTopScrollEdgeForScrollView:nil];
|
|
@@ -2321,7 +2379,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2321
2379
|
if (_sharedHeaderScrollView == scrollView &&
|
|
2322
2380
|
_sharedHeaderHostView.superview == scrollView) {
|
|
2323
2381
|
_sharedHeaderHostView.layer.zPosition =
|
|
2324
|
-
|
|
2382
|
+
_topScrollEdgeEnabled && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
|
|
2325
2383
|
[self layoutSharedHeaders];
|
|
2326
2384
|
return;
|
|
2327
2385
|
}
|
|
@@ -2329,7 +2387,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2329
2387
|
_sharedHeaderScrollView = scrollView;
|
|
2330
2388
|
_sharedHeadersLiftedForPagerTransition = NO;
|
|
2331
2389
|
_sharedHeaderHostView.layer.zPosition =
|
|
2332
|
-
|
|
2390
|
+
_topScrollEdgeEnabled && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
|
|
2333
2391
|
if (_sharedHeaderHostView.superview != scrollView) {
|
|
2334
2392
|
[scrollView addSubview:_sharedHeaderHostView];
|
|
2335
2393
|
}
|
|
@@ -2424,7 +2482,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2424
2482
|
CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
|
|
2425
2483
|
CGFloat stickyTop = [_sharedHeaderHostView convertPoint:
|
|
2426
2484
|
CGPointMake(0, _headerHeight + pinnedTranslation) toView:self].y;
|
|
2427
|
-
BOOL shouldPin =
|
|
2485
|
+
BOOL shouldPin = _topScrollEdgeEnabled &&
|
|
2428
2486
|
_stickyHeaderView != nil && attachedToList && !_transitioning &&
|
|
2429
2487
|
!_externalOuterTransitioning &&
|
|
2430
2488
|
!_isPagerDragging && _headerOffset >= _headerHeight - 0.5 &&
|
|
@@ -2646,7 +2704,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2646
2704
|
_topOverlayView.window == self.window;
|
|
2647
2705
|
BOOL externalOverlayReady = _externalTopOverlayTag > 0 &&
|
|
2648
2706
|
_externalTopOverlayView.window == self.window;
|
|
2649
|
-
if (!_isBeingRecycled &&
|
|
2707
|
+
if (!_isBeingRecycled && _topScrollEdgeEnabled && self.window != nil &&
|
|
2650
2708
|
(localOverlayReady || externalOverlayReady)) {
|
|
2651
2709
|
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2652
2710
|
}
|
|
@@ -2729,7 +2787,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2729
2787
|
if (_isBeingRecycled) return;
|
|
2730
2788
|
NSInteger safeIndex = MIN(MAX(index, 0), _logicalChildren.count);
|
|
2731
2789
|
[_logicalChildren insertObject:childComponentView atIndex:safeIndex];
|
|
2732
|
-
if (
|
|
2790
|
+
if (_topScrollEdgeEnabled || _topOverlayView != nil) {
|
|
2733
2791
|
_needsSlotRebuild = YES;
|
|
2734
2792
|
} else {
|
|
2735
2793
|
[self rebuildSlots];
|
|
@@ -2744,7 +2802,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2744
2802
|
if (existingIndex == NSNotFound) return;
|
|
2745
2803
|
[childComponentView removeFromSuperview];
|
|
2746
2804
|
[_logicalChildren removeObjectAtIndex:existingIndex];
|
|
2747
|
-
if (
|
|
2805
|
+
if (_topScrollEdgeEnabled || _topOverlayView != nil) {
|
|
2748
2806
|
_needsSlotRebuild = YES;
|
|
2749
2807
|
} else {
|
|
2750
2808
|
[self rebuildSlots];
|
|
@@ -2761,7 +2819,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2761
2819
|
_needsSlotRebuild = YES;
|
|
2762
2820
|
return;
|
|
2763
2821
|
}
|
|
2764
|
-
if (
|
|
2822
|
+
if (_topScrollEdgeEnabled &&
|
|
2765
2823
|
_logicalChildren.count != _pageKeys.count + 2 &&
|
|
2766
2824
|
_logicalChildren.count != _pageKeys.count + 3) {
|
|
2767
2825
|
_needsSlotRebuild = YES;
|
|
@@ -2800,7 +2858,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2800
2858
|
_stickyHeaderView = _logicalChildren[1];
|
|
2801
2859
|
[_sharedHeaderHostView addSubview:_stickyHeaderView];
|
|
2802
2860
|
}
|
|
2803
|
-
BOOL hasOverlaySlot =
|
|
2861
|
+
BOOL hasOverlaySlot = _topScrollEdgeEnabled &&
|
|
2804
2862
|
_logicalChildren.count == _pageKeys.count + 3;
|
|
2805
2863
|
NSInteger pageSlotStart = hasOverlaySlot ? 3 : 2;
|
|
2806
2864
|
if (hasOverlaySlot) {
|
|
@@ -3050,7 +3108,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3050
3108
|
_externalTopOverlayHeight = 0;
|
|
3051
3109
|
_externalOuterPageActive = NO;
|
|
3052
3110
|
_externalOuterTransitioning = NO;
|
|
3053
|
-
|
|
3111
|
+
_topScrollEdgeEnabled = NO;
|
|
3112
|
+
_topScrollEdgeEffect = RNCCollapsiblePagerViewTopScrollEdgeEffect::None;
|
|
3054
3113
|
_nativeTabBarHeight = 44;
|
|
3055
3114
|
_nativeSubHeaderHeight = 74;
|
|
3056
3115
|
_headerOffset = 0;
|
|
@@ -3092,7 +3151,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3092
3151
|
const auto &oldViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(_props);
|
|
3093
3152
|
const auto &newViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(props);
|
|
3094
3153
|
_applyingTopOverlayInsetChange =
|
|
3095
|
-
oldViewProps.
|
|
3154
|
+
(oldViewProps.topScrollEdgeEffect == RNCCollapsiblePagerViewTopScrollEdgeEffect::None) !=
|
|
3155
|
+
(newViewProps.topScrollEdgeEffect == RNCCollapsiblePagerViewTopScrollEdgeEffect::None) ||
|
|
3096
3156
|
oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight;
|
|
3097
3157
|
|
|
3098
3158
|
if (_needsPropsReapply || oldViewProps.initialPage != newViewProps.initialPage) {
|
|
@@ -3143,9 +3203,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3143
3203
|
_stickyHeaderHeight = MAX(0, newViewProps.stickyHeaderHeight);
|
|
3144
3204
|
insetsChanged = YES;
|
|
3145
3205
|
}
|
|
3146
|
-
if (_needsPropsReapply || oldViewProps.
|
|
3147
|
-
|
|
3148
|
-
|
|
3206
|
+
if (_needsPropsReapply || oldViewProps.topScrollEdgeEffect != newViewProps.topScrollEdgeEffect) {
|
|
3207
|
+
BOOL enabled = newViewProps.topScrollEdgeEffect != RNCCollapsiblePagerViewTopScrollEdgeEffect::None;
|
|
3208
|
+
BOOL enabledChanged = _topScrollEdgeEnabled != enabled;
|
|
3209
|
+
_topScrollEdgeEnabled = enabled;
|
|
3210
|
+
_topScrollEdgeEffect = newViewProps.topScrollEdgeEffect;
|
|
3211
|
+
if (enabledChanged) {
|
|
3149
3212
|
_deferTopEdgeSlotRebuild = YES;
|
|
3150
3213
|
[self rebuildSlots];
|
|
3151
3214
|
NSUInteger generation = _generation;
|
|
@@ -3156,12 +3219,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3156
3219
|
[self rebuildSlots];
|
|
3157
3220
|
});
|
|
3158
3221
|
}
|
|
3159
|
-
insetsChanged
|
|
3222
|
+
insetsChanged |= enabledChanged;
|
|
3160
3223
|
}
|
|
3161
3224
|
if (_needsPropsReapply ||
|
|
3162
3225
|
oldViewProps.topOverlayColorScheme != newViewProps.topOverlayColorScheme) {
|
|
3163
3226
|
NSString *nextScheme = RCTNSStringFromString(newViewProps.topOverlayColorScheme);
|
|
3164
|
-
if (self.hasFixedTopOverlay &&
|
|
3227
|
+
if (self.hasFixedTopOverlay && _topScrollEdgeEnabled &&
|
|
3165
3228
|
_topOverlayColorScheme != nil &&
|
|
3166
3229
|
![_topOverlayColorScheme isEqualToString:nextScheme]) {
|
|
3167
3230
|
_pendingTopOverlayAppearanceCommit = YES;
|
|
@@ -196,7 +196,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
196
196
|
BOOL refreshEnded = previous.refreshing && !next.refreshing;
|
|
197
197
|
[super updateProps:props oldProps:oldProps];
|
|
198
198
|
[self updateTopScrollEdge];
|
|
199
|
-
if (next.
|
|
199
|
+
if ((next.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && _topOverlay == nil) {
|
|
200
200
|
__weak __typeof__(self) weakSelf = self;
|
|
201
201
|
dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf updateTopScrollEdge]; });
|
|
202
202
|
}
|
|
@@ -210,7 +210,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
210
210
|
if (@available(iOS 26.0, *)) {
|
|
211
211
|
const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
|
|
212
212
|
UIView *overlay = nil;
|
|
213
|
-
if (props.
|
|
213
|
+
if ((props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled) {
|
|
214
214
|
NSUInteger fabricSiblings = 0;
|
|
215
215
|
for (UIView *candidate in self.superview.subviews) {
|
|
216
216
|
if (![candidate conformsToProtocol:@protocol(RCTComponentViewProtocol)]) continue;
|
|
@@ -220,7 +220,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
220
220
|
if (fabricSiblings != 2) overlay = nil;
|
|
221
221
|
}
|
|
222
222
|
_topOverlay = overlay;
|
|
223
|
-
CGFloat requestedOverlayInset = props.
|
|
223
|
+
CGFloat requestedOverlayInset = (props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled && overlay != nil &&
|
|
224
224
|
isfinite(props.topOverlayHeight) ? MAX(0, props.topOverlayHeight) : 0;
|
|
225
225
|
if (_appliedOverlayInset != requestedOverlayInset) {
|
|
226
226
|
// Keep UIKit's refresh rebound animation in control while it is overscrolled.
|
|
@@ -236,7 +236,7 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
236
236
|
}
|
|
237
237
|
[self emitContentViewportChangeIfNeeded];
|
|
238
238
|
}
|
|
239
|
-
BOOL enabled = props.
|
|
239
|
+
BOOL enabled = (props.topScrollEdgeEffect != RNCNativeScrollerTopScrollEdgeEffect::None) && !_recycled && overlay != nil && self.window != nil;
|
|
240
240
|
if (!enabled || _topEdgeInteractionHost != overlay) {
|
|
241
241
|
if (_topEdgeInteraction != nil) {
|
|
242
242
|
[_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
|
|
@@ -271,7 +271,8 @@ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint
|
|
|
271
271
|
[overlay addInteraction:_topEdgeInteraction];
|
|
272
272
|
_topEdgeInteractionHost = overlay;
|
|
273
273
|
}
|
|
274
|
-
_scrollView.topEdgeEffect.style =
|
|
274
|
+
_scrollView.topEdgeEffect.style = props.topScrollEdgeEffect == RNCNativeScrollerTopScrollEdgeEffect::Soft
|
|
275
|
+
? UIScrollEdgeEffectStyle.softStyle : UIScrollEdgeEffectStyle.hardStyle;
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
|
|
@@ -90,9 +90,17 @@ using namespace facebook::react;
|
|
|
90
90
|
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.pager.bounds];
|
|
91
91
|
[self.pager addSubview:scrollView];
|
|
92
92
|
UIScrollEdgeEffectStyle *originalStyle = scrollView.topEdgeEffect.style;
|
|
93
|
-
[self.pager setValue:@YES forKey:@"
|
|
93
|
+
[self.pager setValue:@YES forKey:@"topScrollEdgeEnabled"];
|
|
94
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Hard))
|
|
95
|
+
forKey:@"topScrollEdgeEffect"];
|
|
94
96
|
[self.pager setValue:@123 forKey:@"externalTopOverlayTag"];
|
|
95
97
|
[self.pager setValue:@64 forKey:@"externalTopOverlayHeight"];
|
|
98
|
+
UIView *container = [self.pager valueForKey:@"containerView"];
|
|
99
|
+
container.frame = self.pager.bounds;
|
|
100
|
+
UIView *overlayHost = [self.pager valueForKey:@"topOverlayHostView"];
|
|
101
|
+
overlayHost.frame = CGRectMake(0, 0, 320, 64);
|
|
102
|
+
overlayHost.hidden = NO;
|
|
103
|
+
self.pager.backgroundColor = UIColor.redColor;
|
|
96
104
|
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
97
105
|
XCTAssertEqual([self.pager valueForKey:@"topEdgeScrollView"], scrollView);
|
|
98
106
|
UIScrollEdgeElementContainerInteraction *interaction =
|
|
@@ -100,6 +108,23 @@ using namespace facebook::react;
|
|
|
100
108
|
XCTAssertEqual(interaction.scrollView, scrollView);
|
|
101
109
|
UIView *host = [self.pager valueForKey:@"topEdgeInteractionHost"];
|
|
102
110
|
XCTAssertTrue([host.interactions containsObject:interaction]);
|
|
111
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
112
|
+
CALayer *cover = [self.pager valueForKey:@"topEdgeSeparatorCover"];
|
|
113
|
+
XCTAssertEqual(cover.superlayer, container.layer);
|
|
114
|
+
XCTAssertEqualWithAccuracy(CGRectGetMaxY(cover.frame), 64, 0.001);
|
|
115
|
+
XCTAssertEqualWithAccuracy(CGRectGetHeight(cover.frame), 1.0 / window.screen.scale, 0.001);
|
|
116
|
+
XCTAssertTrue(CGColorEqualToColor(cover.backgroundColor, UIColor.redColor.CGColor));
|
|
117
|
+
UIEdgeInsets inset = scrollView.contentInset;
|
|
118
|
+
CGPoint offset = scrollView.contentOffset;
|
|
119
|
+
[self.pager setValue:@(static_cast<NSInteger>(RNCCollapsiblePagerViewTopScrollEdgeEffect::Soft))
|
|
120
|
+
forKey:@"topScrollEdgeEffect"];
|
|
121
|
+
[self.pager updateTopScrollEdgeForScrollView:scrollView];
|
|
122
|
+
XCTAssertEqual(scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.softStyle);
|
|
123
|
+
XCTAssertNil([self.pager valueForKey:@"topEdgeSeparatorCover"]);
|
|
124
|
+
XCTAssertNil(cover.superlayer);
|
|
125
|
+
XCTAssertEqual([self.pager valueForKey:@"topEdgeInteraction"], interaction);
|
|
126
|
+
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, inset));
|
|
127
|
+
XCTAssertTrue(CGPointEqualToPoint(scrollView.contentOffset, offset));
|
|
103
128
|
|
|
104
129
|
RNCPagerViewComponentView *outer = [RNCPagerViewComponentView new];
|
|
105
130
|
[self.pager setValue:outer forKey:@"externalOuterPager"];
|
|
@@ -69,6 +69,45 @@ using namespace facebook::react;
|
|
|
69
69
|
XCTAssertFalse([self.scroller.scrollView isKindOfClass:NSClassFromString(@"RCTEnhancedScrollView")]);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
- (void)testScrollEdgeStyleSwitchPreservesOverlayInsetAndRestoresOriginalStyle
|
|
73
|
+
{
|
|
74
|
+
if (@available(iOS 26.0, *)) {
|
|
75
|
+
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 600)];
|
|
76
|
+
UIView *root = [[UIView alloc] initWithFrame:window.bounds];
|
|
77
|
+
[window addSubview:root];
|
|
78
|
+
[root addSubview:self.scroller];
|
|
79
|
+
RCTViewComponentView *overlay = [RCTViewComponentView new];
|
|
80
|
+
overlay.frame = CGRectMake(0, 0, 320, 64);
|
|
81
|
+
[root addSubview:overlay];
|
|
82
|
+
UIScrollEdgeEffectStyle *original = self.scroller.scrollView.topEdgeEffect.style;
|
|
83
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
84
|
+
props->contentHeight = 1500;
|
|
85
|
+
props->topOverlayHeight = 64;
|
|
86
|
+
props->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::Hard;
|
|
87
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
88
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.hardStyle);
|
|
89
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 64, 0.01);
|
|
90
|
+
CGPoint offset = self.scroller.scrollView.contentOffset;
|
|
91
|
+
auto soft = std::make_shared<RNCNativeScrollerProps>();
|
|
92
|
+
soft->contentHeight = 1500;
|
|
93
|
+
soft->topOverlayHeight = 64;
|
|
94
|
+
soft->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::Soft;
|
|
95
|
+
[self.scroller updateProps:soft oldProps:props];
|
|
96
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, UIScrollEdgeEffectStyle.softStyle);
|
|
97
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 64, 0.01);
|
|
98
|
+
XCTAssertTrue(CGPointEqualToPoint(self.scroller.scrollView.contentOffset, offset));
|
|
99
|
+
auto disabled = std::make_shared<RNCNativeScrollerProps>();
|
|
100
|
+
disabled->contentHeight = 1500;
|
|
101
|
+
disabled->topScrollEdgeEffect = RNCNativeScrollerTopScrollEdgeEffect::None;
|
|
102
|
+
[self.scroller updateProps:disabled oldProps:soft];
|
|
103
|
+
XCTAssertEqual(self.scroller.scrollView.topEdgeEffect.style, original);
|
|
104
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 0, 0.01);
|
|
105
|
+
[self.scroller removeFromSuperview];
|
|
106
|
+
} else {
|
|
107
|
+
XCTSkip(@"Scroll-edge styles require iOS 26 or newer");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
72
111
|
- (void)testShrinkClampsOnlyUnreachableUpperOffset
|
|
73
112
|
{
|
|
74
113
|
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
@@ -217,8 +217,8 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
217
217
|
} = this.props;
|
|
218
218
|
const pages = this.pages();
|
|
219
219
|
const supportsTopScrollEdge = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
|
|
220
|
-
const
|
|
221
|
-
const hasTopOverlay =
|
|
220
|
+
const scrollEdgeEnabled = supportsTopScrollEdge && (topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft");
|
|
221
|
+
const hasTopOverlay = scrollEdgeEnabled && topOverlay != null;
|
|
222
222
|
const pageKeys = pages.map(pageKey);
|
|
223
223
|
const retained = new Set(this.mountedPages(pages.length));
|
|
224
224
|
const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
|
|
@@ -243,8 +243,8 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
243
243
|
offscreenPageLimit: Math.max(1, offscreenPageLimit ?? 1),
|
|
244
244
|
headerHeight: Math.max(0, Math.round(headerHeight)),
|
|
245
245
|
stickyHeaderHeight: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
246
|
-
|
|
247
|
-
topOverlayColorScheme:
|
|
246
|
+
topScrollEdgeEffect: scrollEdgeEnabled ? topScrollEdgeEffect : "none",
|
|
247
|
+
topOverlayColorScheme: scrollEdgeEnabled ? topOverlayColorScheme : undefined,
|
|
248
248
|
topOverlayHeight: hasTopOverlay ? this.state.topOverlayHeight : 0,
|
|
249
249
|
externalTopOverlayTag: supportsTopScrollEdge && !hasTopOverlay ? externalTopOverlayTag ?? 0 : 0,
|
|
250
250
|
nativeSmoothHeaderScrollEnabled: nativeSmoothHeaderScrollEnabled,
|
|
@@ -59,7 +59,7 @@ export interface NativeProps extends ViewProps {
|
|
|
59
59
|
nativeSmoothHeaderScrollEnabled?: WithDefault<boolean, false>;
|
|
60
60
|
headerHeight?: Int32;
|
|
61
61
|
stickyHeaderHeight?: Int32;
|
|
62
|
-
|
|
62
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
63
63
|
topOverlayColorScheme?: string;
|
|
64
64
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
65
65
|
externalTopOverlayTag?: WithDefault<Int32, 0>;
|
|
@@ -59,7 +59,7 @@ export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScrol
|
|
|
59
59
|
height: 0
|
|
60
60
|
});
|
|
61
61
|
const [topOverlayHeight, setTopOverlayHeight] = React.useState(0);
|
|
62
|
-
const hasTopOverlay = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26 && topScrollEdgeEffect === "hard" && topOverlay != null;
|
|
62
|
+
const hasTopOverlay = Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26 && (topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft") && topOverlay != null;
|
|
63
63
|
const [refreshEpoch, requestRefreshSync] = React.useReducer(value => value + 1, 0);
|
|
64
64
|
const contentSizeRef = React.useRef(contentSize);
|
|
65
65
|
const scrolledSinceGrant = React.useRef(false);
|
|
@@ -114,7 +114,7 @@ export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScrol
|
|
|
114
114
|
contentInsetRight: contentInset?.right ?? 0,
|
|
115
115
|
contentInsetBottom: contentInset?.bottom ?? 0,
|
|
116
116
|
contentInsetLeft: contentInset?.left ?? 0,
|
|
117
|
-
|
|
117
|
+
topScrollEdgeEffect: hasTopOverlay ? topScrollEdgeEffect : "none",
|
|
118
118
|
topOverlayHeight: hasTopOverlay ? topOverlayHeight : 0,
|
|
119
119
|
decelerationRate: rate,
|
|
120
120
|
keyboardShouldPersistTaps: persistTaps,
|
|
@@ -47,7 +47,7 @@ export interface NativeProps extends ViewProps {
|
|
|
47
47
|
contentInsetRight?: WithDefault<Double, 0>;
|
|
48
48
|
contentInsetBottom?: WithDefault<Double, 0>;
|
|
49
49
|
contentInsetLeft?: WithDefault<Double, 0>;
|
|
50
|
-
|
|
50
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
51
51
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
52
52
|
refreshEnabled?: WithDefault<boolean, false>;
|
|
53
53
|
refreshing?: WithDefault<boolean, false>;
|
package/lib/module/index.js
CHANGED
package/lib/module/index.web.js
CHANGED
|
@@ -4,4 +4,5 @@ export { PagerView as default, PagerView } from "./PagerView.web.js";
|
|
|
4
4
|
export * from "./usePagerView.js";
|
|
5
5
|
export * from "./CollapsiblePagerView.web.js";
|
|
6
6
|
export * from "./NativeScroller.web.js";
|
|
7
|
+
export { ScrollEdgeEffect } from "./ScrollEdgeEffect.js";
|
|
7
8
|
//# sourceMappingURL=index.web.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import type * as ReactNative from "react-native";
|
|
3
4
|
import { type NativeProps, type OnCollapsibleStateChangedEventData, type OnNativeTabPressEventData } from "./CollapsiblePagerViewNativeComponent";
|
|
@@ -55,7 +56,7 @@ export type CollapsiblePagerNativeSubHeaderConfig = Readonly<{
|
|
|
55
56
|
}>;
|
|
56
57
|
export type CollapsiblePagerViewOnNativeTabPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
57
58
|
export type CollapsiblePagerViewOnNativeSubHeaderPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
|
|
58
|
-
export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "
|
|
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"> {
|
|
59
60
|
/** Collapsible content rendered above the sticky bar. */
|
|
60
61
|
header: React.ReactNode;
|
|
61
62
|
/** Bar which remains pinned after the header has collapsed. */
|
|
@@ -68,8 +69,8 @@ export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children"
|
|
|
68
69
|
topOverlay?: React.ReactNode;
|
|
69
70
|
/** Existing fixed header shared with a parent pager on iOS 26 and newer. */
|
|
70
71
|
externalTopOverlayTag?: number;
|
|
71
|
-
/**
|
|
72
|
-
topScrollEdgeEffect?:
|
|
72
|
+
/** Selects UIKit's top scroll-edge effect on iOS 26 and newer. */
|
|
73
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
73
74
|
/** Effective appearance of the fixed overlay or pinned sticky bar. */
|
|
74
75
|
topOverlayColorScheme?: "light" | "dark";
|
|
75
76
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import type * as ReactNative from "react-native";
|
|
3
4
|
export type OnPageScrollEventData = Readonly<{
|
|
@@ -40,7 +41,7 @@ export interface CollapsiblePagerViewProps extends Omit<WebCollapsiblePagerNativ
|
|
|
40
41
|
headerHeight: number;
|
|
41
42
|
stickyHeaderHeight: number;
|
|
42
43
|
topOverlay?: React.ReactNode;
|
|
43
|
-
topScrollEdgeEffect?:
|
|
44
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
44
45
|
topOverlayColorScheme?: "light" | "dark";
|
|
45
46
|
externalTopOverlayTag?: number;
|
|
46
47
|
pageRetentionDistance?: number;
|
|
@@ -42,7 +42,7 @@ export interface NativeProps extends ViewProps {
|
|
|
42
42
|
nativeSmoothHeaderScrollEnabled?: WithDefault<boolean, false>;
|
|
43
43
|
headerHeight?: Int32;
|
|
44
44
|
stickyHeaderHeight?: Int32;
|
|
45
|
-
|
|
45
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
46
46
|
topOverlayColorScheme?: string;
|
|
47
47
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
48
48
|
externalTopOverlayTag?: WithDefault<Int32, 0>;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type NativeMethods, type RefreshControlProps, type ScrollView, type ScrollViewProps, type ViewProps } from "react-native";
|
|
3
3
|
import NativeScrollerHost from "./NativeScrollerNativeComponent";
|
|
4
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
4
5
|
export declare const NATIVE_SCROLLER_NATIVE_ID_PREFIX = "rnc-collapsible-pager-native-scroller";
|
|
5
6
|
/** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
|
|
6
7
|
export interface NativeScrollerProps extends ViewProps, Pick<ScrollViewProps, "contentContainerStyle" | "onContentSizeChange" | "scrollEnabled" | "nestedScrollEnabled" | "showsVerticalScrollIndicator" | "showsHorizontalScrollIndicator" | "bounces" | "alwaysBounceVertical" | "contentInset" | "keyboardDismissMode" | "keyboardShouldPersistTaps" | "scrollEventThrottle" | "decelerationRate" | "onScroll" | "onScrollBeginDrag" | "onScrollEndDrag" | "onMomentumScrollBegin" | "onMomentumScrollEnd"> {
|
|
7
8
|
pagerScrollKey?: string;
|
|
8
9
|
/** Fixed overlay above scrolling content on iOS 26 and newer. */
|
|
9
10
|
topOverlay?: React.ReactNode;
|
|
10
|
-
/**
|
|
11
|
-
topScrollEdgeEffect?:
|
|
11
|
+
/** Selects UIKit's top scroll-edge effect on iOS 26 and newer. */
|
|
12
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
12
13
|
/** iOS only. Android retains AndroidX's native fling physics. */
|
|
13
14
|
decelerationRate?: ScrollViewProps["decelerationRate"];
|
|
14
15
|
/** Standard RefreshControl props are mapped to the independently owned native controller. */
|
|
@@ -47,7 +47,7 @@ export interface NativeProps extends ViewProps {
|
|
|
47
47
|
contentInsetRight?: WithDefault<Double, 0>;
|
|
48
48
|
contentInsetBottom?: WithDefault<Double, 0>;
|
|
49
49
|
contentInsetLeft?: WithDefault<Double, 0>;
|
|
50
|
-
|
|
50
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
51
51
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
52
52
|
refreshEnabled?: WithDefault<boolean, false>;
|
|
53
53
|
refreshing?: WithDefault<boolean, false>;
|
|
@@ -9,4 +9,5 @@ export type { PagerViewOnPageScrollEventData, PagerViewOnPageSelectedEventData,
|
|
|
9
9
|
export type PagerViewOnPageScrollEvent = ReactNative.NativeSyntheticEvent<PagerViewOnPageScrollEventData>;
|
|
10
10
|
export type PagerViewOnPageSelectedEvent = ReactNative.NativeSyntheticEvent<PagerViewOnPageSelectedEventData>;
|
|
11
11
|
export type PageScrollStateChangedNativeEvent = ReactNative.NativeSyntheticEvent<PageScrollStateChangedNativeEventData>;
|
|
12
|
+
export { ScrollEdgeEffect } from './ScrollEdgeEffect';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,4 +3,5 @@ export type { PagerViewProps, PagerViewOnPageScrollEvent, PagerViewOnPageScrollE
|
|
|
3
3
|
export * from "./usePagerView";
|
|
4
4
|
export * from "./CollapsiblePagerView.web";
|
|
5
5
|
export * from "./NativeScroller.web";
|
|
6
|
+
export { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
6
7
|
//# sourceMappingURL=index.web.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-pager-view",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.7.0-2",
|
|
4
4
|
"description": "React Native wrapper for Android and iOS ViewPager",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"typescript": "^5.9.2"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@onekeyfe/react-native-native-logger": "
|
|
75
|
+
"@onekeyfe/react-native-native-logger": "6.7.0-2",
|
|
76
76
|
"react": "*",
|
|
77
77
|
"react-native": "*"
|
|
78
78
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { I18nManager, Platform, StyleSheet, View } from "react-native";
|
|
3
4
|
import type * as ReactNative from "react-native";
|
|
@@ -86,7 +87,7 @@ export interface CollapsiblePagerViewProps
|
|
|
86
87
|
| "children"
|
|
87
88
|
| "headerHeight"
|
|
88
89
|
| "stickyHeaderHeight"
|
|
89
|
-
| "
|
|
90
|
+
| "topScrollEdgeEffect"
|
|
90
91
|
| "topOverlayHeight"
|
|
91
92
|
| "externalTopOverlayTag"
|
|
92
93
|
| "pageKeys"
|
|
@@ -121,8 +122,8 @@ export interface CollapsiblePagerViewProps
|
|
|
121
122
|
topOverlay?: React.ReactNode;
|
|
122
123
|
/** Existing fixed header shared with a parent pager on iOS 26 and newer. */
|
|
123
124
|
externalTopOverlayTag?: number;
|
|
124
|
-
/**
|
|
125
|
-
topScrollEdgeEffect?:
|
|
125
|
+
/** Selects UIKit's top scroll-edge effect on iOS 26 and newer. */
|
|
126
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
126
127
|
/** Effective appearance of the fixed overlay or pinned sticky bar. */
|
|
127
128
|
topOverlayColorScheme?: "light" | "dark";
|
|
128
129
|
/**
|
|
@@ -493,8 +494,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
493
494
|
const pages = this.pages();
|
|
494
495
|
const supportsTopScrollEdge =
|
|
495
496
|
Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
|
|
496
|
-
const
|
|
497
|
-
|
|
497
|
+
const scrollEdgeEnabled =
|
|
498
|
+
supportsTopScrollEdge &&
|
|
499
|
+
(topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft");
|
|
500
|
+
const hasTopOverlay = scrollEdgeEnabled && topOverlay != null;
|
|
498
501
|
const pageKeys = pages.map(pageKey);
|
|
499
502
|
const retained = new Set(this.mountedPages(pages.length));
|
|
500
503
|
const deducedLayoutDirection =
|
|
@@ -535,8 +538,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
535
538
|
offscreenPageLimit={Math.max(1, offscreenPageLimit ?? 1)}
|
|
536
539
|
headerHeight={Math.max(0, Math.round(headerHeight))}
|
|
537
540
|
stickyHeaderHeight={Math.max(0, Math.round(stickyHeaderHeight))}
|
|
538
|
-
|
|
539
|
-
topOverlayColorScheme={
|
|
541
|
+
topScrollEdgeEffect={scrollEdgeEnabled ? topScrollEdgeEffect : "none"}
|
|
542
|
+
topOverlayColorScheme={
|
|
543
|
+
scrollEdgeEnabled ? topOverlayColorScheme : undefined
|
|
544
|
+
}
|
|
540
545
|
topOverlayHeight={hasTopOverlay ? this.state.topOverlayHeight : 0}
|
|
541
546
|
externalTopOverlayTag={
|
|
542
547
|
supportsTopScrollEdge && !hasTopOverlay
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { I18nManager, StyleSheet, View } from "react-native";
|
|
3
4
|
import type * as ReactNative from "react-native";
|
|
@@ -67,7 +68,7 @@ export interface CollapsiblePagerViewProps
|
|
|
67
68
|
headerHeight: number;
|
|
68
69
|
stickyHeaderHeight: number;
|
|
69
70
|
topOverlay?: React.ReactNode;
|
|
70
|
-
topScrollEdgeEffect?:
|
|
71
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
71
72
|
topOverlayColorScheme?: "light" | "dark";
|
|
72
73
|
externalTopOverlayTag?: number;
|
|
73
74
|
pageRetentionDistance?: number;
|
|
@@ -59,7 +59,7 @@ export interface NativeProps extends ViewProps {
|
|
|
59
59
|
nativeSmoothHeaderScrollEnabled?: WithDefault<boolean, false>;
|
|
60
60
|
headerHeight?: Int32;
|
|
61
61
|
stickyHeaderHeight?: Int32;
|
|
62
|
-
|
|
62
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
63
63
|
topOverlayColorScheme?: string;
|
|
64
64
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
65
65
|
externalTopOverlayTag?: WithDefault<Int32, 0>;
|
package/src/NativeScroller.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "react-native";
|
|
16
16
|
|
|
17
17
|
import NativeScrollerHost, { Commands } from "./NativeScrollerNativeComponent";
|
|
18
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
18
19
|
|
|
19
20
|
export const NATIVE_SCROLLER_NATIVE_ID_PREFIX =
|
|
20
21
|
"rnc-collapsible-pager-native-scroller";
|
|
@@ -46,8 +47,8 @@ export interface NativeScrollerProps
|
|
|
46
47
|
pagerScrollKey?: string;
|
|
47
48
|
/** Fixed overlay above scrolling content on iOS 26 and newer. */
|
|
48
49
|
topOverlay?: React.ReactNode;
|
|
49
|
-
/**
|
|
50
|
-
topScrollEdgeEffect?:
|
|
50
|
+
/** Selects UIKit's top scroll-edge effect on iOS 26 and newer. */
|
|
51
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
51
52
|
/** iOS only. Android retains AndroidX's native fling physics. */
|
|
52
53
|
decelerationRate?: ScrollViewProps["decelerationRate"];
|
|
53
54
|
/** Standard RefreshControl props are mapped to the independently owned native controller. */
|
|
@@ -118,7 +119,8 @@ export const NativeScroller = React.forwardRef<
|
|
|
118
119
|
const [topOverlayHeight, setTopOverlayHeight] = React.useState(0);
|
|
119
120
|
const hasTopOverlay =
|
|
120
121
|
Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26 &&
|
|
121
|
-
topScrollEdgeEffect === "hard"
|
|
122
|
+
(topScrollEdgeEffect === "hard" || topScrollEdgeEffect === "soft") &&
|
|
123
|
+
topOverlay != null;
|
|
122
124
|
const [refreshEpoch, requestRefreshSync] = React.useReducer(
|
|
123
125
|
(value: number) => value + 1,
|
|
124
126
|
0
|
|
@@ -212,7 +214,7 @@ export const NativeScroller = React.forwardRef<
|
|
|
212
214
|
contentInsetRight={contentInset?.right ?? 0}
|
|
213
215
|
contentInsetBottom={contentInset?.bottom ?? 0}
|
|
214
216
|
contentInsetLeft={contentInset?.left ?? 0}
|
|
215
|
-
|
|
217
|
+
topScrollEdgeEffect={hasTopOverlay ? topScrollEdgeEffect : "none"}
|
|
216
218
|
topOverlayHeight={hasTopOverlay ? topOverlayHeight : 0}
|
|
217
219
|
decelerationRate={rate}
|
|
218
220
|
keyboardShouldPersistTaps={persistTaps}
|
|
@@ -47,7 +47,7 @@ export interface NativeProps extends ViewProps {
|
|
|
47
47
|
contentInsetRight?: WithDefault<Double, 0>;
|
|
48
48
|
contentInsetBottom?: WithDefault<Double, 0>;
|
|
49
49
|
contentInsetLeft?: WithDefault<Double, 0>;
|
|
50
|
-
|
|
50
|
+
topScrollEdgeEffect?: WithDefault<"none" | "hard" | "soft", "none">;
|
|
51
51
|
topOverlayHeight?: WithDefault<Double, 0>;
|
|
52
52
|
refreshEnabled?: WithDefault<boolean, false>;
|
|
53
53
|
refreshing?: WithDefault<boolean, false>;
|
package/src/index.tsx
CHANGED