@onekeyfe/react-native-pager-view 3.0.157-alpha.257 → 3.0.157-alpha.258
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 +51 -4
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +237 -43
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +15 -11
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +573 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +130 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
- package/android/src/main/jni/CMakeLists.txt +19 -0
- package/android/src/main/jni/pagerview.h +11 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +1 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +32 -52
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
- package/docs/SPEC.md +267 -34
- package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
- package/ios/RNCNativeScrollerComponentView.h +17 -0
- package/ios/RNCNativeScrollerComponentView.mm +479 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +346 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
- package/ios/tests/RNCNativeScrollerTests.mm +252 -0
- package/lib/module/CollapsiblePagerView.js +1 -1
- package/lib/module/NativeScroller.js +171 -11
- package/lib/module/NativeScrollerNativeComponent.ts +101 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +2 -3
- package/lib/typescript/src/NativeScroller.d.ts +15 -7
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
- package/package.json +6 -3
- package/react-native-pager-view.podspec +2 -1
- package/react-native.config.js +15 -0
- package/src/CollapsiblePagerView.tsx +4 -4
- package/src/NativeScroller.tsx +291 -14
- package/src/NativeScrollerNativeComponent.ts +101 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#import <XCTest/XCTest.h>
|
|
2
|
+
#import "RNCNativeScrollerComponentView.h"
|
|
3
|
+
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
4
|
+
#import <react/renderer/components/pagerview/Props.h>
|
|
5
|
+
|
|
6
|
+
using namespace facebook::react;
|
|
7
|
+
|
|
8
|
+
@interface RNCNativeScrollerComponentView (Tests)
|
|
9
|
+
- (void)emitScrollEvent:(NSInteger)kind velocity:(CGPoint)velocity target:(CGPoint)target;
|
|
10
|
+
- (void)clampContentOffsetIfNeeded;
|
|
11
|
+
- (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated;
|
|
12
|
+
- (void)setRefreshing:(BOOL)refreshing;
|
|
13
|
+
- (CGFloat)contentViewportHeight;
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
@interface RNCCollapsiblePagerViewComponentView (ScrollerTests)
|
|
17
|
+
- (void)applyInsetsToScrollView:(UIScrollView *)scrollView pageIndex:(NSInteger)index restore:(BOOL)restore;
|
|
18
|
+
- (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)reset;
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@interface RNCObservedScroller : RNCNativeScrollerComponentView
|
|
22
|
+
@property (nonatomic, strong) NSMutableArray<NSNumber *> *events;
|
|
23
|
+
@end
|
|
24
|
+
@implementation RNCObservedScroller
|
|
25
|
+
- (void)emitScrollEvent:(NSInteger)kind velocity:(CGPoint)velocity target:(CGPoint)target
|
|
26
|
+
{
|
|
27
|
+
if (self.events == nil) self.events = [NSMutableArray new];
|
|
28
|
+
[self.events addObject:@(kind)];
|
|
29
|
+
[super emitScrollEvent:kind velocity:velocity target:target];
|
|
30
|
+
}
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
@interface RNCNativeScrollerTests : XCTestCase
|
|
34
|
+
@property (nonatomic, strong) RNCObservedScroller *scroller;
|
|
35
|
+
@end
|
|
36
|
+
@implementation RNCNativeScrollerTests
|
|
37
|
+
|
|
38
|
+
- (void)setUp
|
|
39
|
+
{
|
|
40
|
+
[super setUp];
|
|
41
|
+
self.scroller = [RNCObservedScroller new];
|
|
42
|
+
LayoutMetrics metrics;
|
|
43
|
+
metrics.frame.size = {320, 600};
|
|
44
|
+
[self.scroller updateLayoutMetrics:metrics oldLayoutMetrics:{}];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
- (void)tearDown
|
|
48
|
+
{
|
|
49
|
+
[self.scroller prepareForRecycle];
|
|
50
|
+
self.scroller = nil;
|
|
51
|
+
[super tearDown];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)testContentSizeDoesNotRelayoutReactContentOrSharedHeader
|
|
55
|
+
{
|
|
56
|
+
RCTViewComponentView *content = [RCTViewComponentView new];
|
|
57
|
+
content.frame = CGRectMake(0, 0, 320, 900);
|
|
58
|
+
[self.scroller mountChildComponentView:content index:0];
|
|
59
|
+
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, -200, 320, 200)];
|
|
60
|
+
[self.scroller.scrollView addSubview:header];
|
|
61
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
62
|
+
props->contentWidth = 320;
|
|
63
|
+
props->contentHeight = 900;
|
|
64
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
65
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentSize.height, 900, 0.01);
|
|
66
|
+
XCTAssertTrue(CGRectEqualToRect(content.frame, CGRectMake(0, 0, 320, 900)));
|
|
67
|
+
XCTAssertTrue(CGRectEqualToRect(header.frame, CGRectMake(0, -200, 320, 200)));
|
|
68
|
+
XCTAssertEqual(header.superview, self.scroller.scrollView);
|
|
69
|
+
XCTAssertFalse([self.scroller.scrollView isKindOfClass:NSClassFromString(@"RCTEnhancedScrollView")]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)testShrinkClampsOnlyUnreachableUpperOffset
|
|
73
|
+
{
|
|
74
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
75
|
+
props->contentWidth = 320;
|
|
76
|
+
props->contentHeight = 1500;
|
|
77
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
78
|
+
self.scroller.scrollView.contentInset = UIEdgeInsetsMake(200, 0, 468, 0);
|
|
79
|
+
self.scroller.scrollView.contentOffset = CGPointMake(0, 800);
|
|
80
|
+
auto shorter = std::make_shared<RNCNativeScrollerProps>();
|
|
81
|
+
shorter->contentWidth = 320;
|
|
82
|
+
shorter->contentHeight = 132;
|
|
83
|
+
[self.scroller updateProps:shorter oldProps:props];
|
|
84
|
+
[self.scroller clampContentOffsetIfNeeded];
|
|
85
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentOffset.y, 0, 0.01);
|
|
86
|
+
self.scroller.scrollView.contentOffset = CGPointMake(0, -240);
|
|
87
|
+
auto empty = std::make_shared<RNCNativeScrollerProps>();
|
|
88
|
+
empty->contentWidth = 320;
|
|
89
|
+
empty->contentHeight = 0;
|
|
90
|
+
[self.scroller updateProps:empty oldProps:shorter];
|
|
91
|
+
[self.scroller clampContentOffsetIfNeeded];
|
|
92
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentOffset.y, -240, 0.01);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)testNativeInterruptionEndsEachDragAndMomentumOnlyOnce
|
|
96
|
+
{
|
|
97
|
+
UIScrollView *scroll = self.scroller.scrollView;
|
|
98
|
+
id<UIScrollViewDelegate> delegate = scroll.delegate;
|
|
99
|
+
[delegate scrollViewWillBeginDragging:scroll];
|
|
100
|
+
[self.scroller stopScrolling];
|
|
101
|
+
[delegate scrollViewDidEndDragging:scroll willDecelerate:NO];
|
|
102
|
+
XCTAssertEqualObjects(self.scroller.events, (@[@1, @2]));
|
|
103
|
+
[self.scroller.events removeAllObjects];
|
|
104
|
+
[delegate scrollViewWillBeginDecelerating:scroll];
|
|
105
|
+
[self.scroller stopScrolling];
|
|
106
|
+
[delegate scrollViewDidEndDecelerating:scroll];
|
|
107
|
+
NSPredicate *terminal = [NSPredicate predicateWithFormat:@"SELF == 4"];
|
|
108
|
+
XCTAssertEqual([self.scroller.events filteredArrayUsingPredicate:terminal].count, 1u);
|
|
109
|
+
XCTAssertEqualObjects(self.scroller.events.firstObject, @3);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
- (void)testRefreshReplacementAndRecycleDoNotKeepOldControl
|
|
113
|
+
{
|
|
114
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
115
|
+
props->refreshEnabled = true;
|
|
116
|
+
props->refreshProgressViewOffset = 24;
|
|
117
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
118
|
+
UIRefreshControl *original = self.scroller.scrollView.refreshControl;
|
|
119
|
+
XCTAssertNotNil(original);
|
|
120
|
+
XCTAssertEqualWithAccuracy(original.bounds.origin.y, -24, 0.01);
|
|
121
|
+
[self.scroller prepareForRecycle];
|
|
122
|
+
XCTAssertNil(self.scroller.scrollView.refreshControl);
|
|
123
|
+
[self.scroller updateProps:props oldProps:props];
|
|
124
|
+
XCTAssertNotNil(self.scroller.scrollView.refreshControl);
|
|
125
|
+
XCTAssertNotEqual(self.scroller.scrollView.refreshControl, original);
|
|
126
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.refreshControl.bounds.origin.y, -24, 0.01);
|
|
127
|
+
[self.scroller setRefreshing:NO];
|
|
128
|
+
XCTAssertFalse(self.scroller.scrollView.refreshControl.refreshing);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (void)testCallerInsetUpdatesSurvivePagerReapplyAndRelease
|
|
132
|
+
{
|
|
133
|
+
RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
|
|
134
|
+
auto pagerProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
135
|
+
pagerProps->headerHeight = 120;
|
|
136
|
+
pagerProps->stickyHeaderHeight = 40;
|
|
137
|
+
[pager updateProps:pagerProps oldProps:nullptr];
|
|
138
|
+
[pager addSubview:self.scroller];
|
|
139
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
140
|
+
props->contentWidth = 320;
|
|
141
|
+
props->contentHeight = 1500;
|
|
142
|
+
props->contentInsetTop = 10;
|
|
143
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
144
|
+
[pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
|
|
145
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 170, 0.01);
|
|
146
|
+
auto updated = std::make_shared<RNCNativeScrollerProps>();
|
|
147
|
+
updated->contentWidth = 320;
|
|
148
|
+
updated->contentHeight = 1500;
|
|
149
|
+
updated->contentInsetTop = 30;
|
|
150
|
+
updated->contentInsetBottom = 20;
|
|
151
|
+
[self.scroller updateProps:updated oldProps:props];
|
|
152
|
+
[pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
|
|
153
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 190, 0.01);
|
|
154
|
+
[pager restoreScrollViewState:self.scroller.scrollView resetSavedOffset:YES];
|
|
155
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.top, 30, 0.01);
|
|
156
|
+
XCTAssertEqualWithAccuracy(self.scroller.scrollView.contentInset.bottom, 20, 0.01);
|
|
157
|
+
[self.scroller removeFromSuperview];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
- (void)testRecycledDelegateCannotStartASequenceForTheNextOwner
|
|
161
|
+
{
|
|
162
|
+
UIScrollView *scroll = self.scroller.scrollView;
|
|
163
|
+
id<UIScrollViewDelegate> oldDelegate = scroll.delegate;
|
|
164
|
+
[self.scroller prepareForRecycle];
|
|
165
|
+
XCTAssertNil(scroll.delegate);
|
|
166
|
+
[oldDelegate scrollViewWillBeginDragging:scroll];
|
|
167
|
+
[oldDelegate scrollViewWillBeginDecelerating:scroll];
|
|
168
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
169
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
170
|
+
XCTAssertEqual(scroll.delegate, self.scroller);
|
|
171
|
+
[self.scroller stopScrolling];
|
|
172
|
+
NSPredicate *terminal = [NSPredicate predicateWithFormat:@"SELF == 2 OR SELF == 4"];
|
|
173
|
+
XCTAssertEqual([self.scroller.events filteredArrayUsingPredicate:terminal].count, 0u);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
- (void)testPagerExplicitlyFindsIndependentScrollView
|
|
177
|
+
{
|
|
178
|
+
RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
|
|
179
|
+
UIView *page = [UIView new];
|
|
180
|
+
[page addSubview:self.scroller];
|
|
181
|
+
SEL finder = NSSelectorFromString(@"findExplicitNativeScrollerInView:");
|
|
182
|
+
UIScrollView *(*find)(id, SEL, UIView *) =
|
|
183
|
+
(UIScrollView *(*)(id, SEL, UIView *))[pager methodForSelector:finder];
|
|
184
|
+
XCTAssertEqual(find(pager, finder, page), self.scroller.scrollView);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
- (void)testContentViewportUsesOnlyCallerInsetsAndStaticStickyHeight
|
|
188
|
+
{
|
|
189
|
+
RNCCollapsiblePagerViewComponentView *pager = [RNCCollapsiblePagerViewComponentView new];
|
|
190
|
+
auto pagerProps = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
191
|
+
pagerProps->headerHeight = 120;
|
|
192
|
+
pagerProps->stickyHeaderHeight = 40;
|
|
193
|
+
[pager updateProps:pagerProps oldProps:nullptr];
|
|
194
|
+
[pager addSubview:self.scroller];
|
|
195
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
196
|
+
props->contentWidth = 320;
|
|
197
|
+
props->contentHeight = 20;
|
|
198
|
+
props->contentInsetTop = 10;
|
|
199
|
+
props->contentInsetBottom = 20;
|
|
200
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
201
|
+
[pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
|
|
202
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
|
|
203
|
+
// Synthetic bottom fill and temporary refresh insets are not caller geometry.
|
|
204
|
+
self.scroller.scrollView.contentInset = UIEdgeInsetsMake(230, 0, 510, 0);
|
|
205
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
|
|
206
|
+
auto filled = std::make_shared<RNCNativeScrollerProps>();
|
|
207
|
+
filled->contentWidth = 320;
|
|
208
|
+
filled->contentHeight = 530;
|
|
209
|
+
filled->contentInsetTop = 10;
|
|
210
|
+
filled->contentInsetBottom = 20;
|
|
211
|
+
[self.scroller updateProps:filled oldProps:props];
|
|
212
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 530, 0.01);
|
|
213
|
+
LayoutMetrics metrics;
|
|
214
|
+
metrics.frame.size = {320, 700};
|
|
215
|
+
[self.scroller updateLayoutMetrics:metrics oldLayoutMetrics:{}];
|
|
216
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 630, 0.01);
|
|
217
|
+
auto updated = std::make_shared<RNCNativeScrollerProps>();
|
|
218
|
+
updated->contentWidth = 320;
|
|
219
|
+
updated->contentHeight = 530;
|
|
220
|
+
updated->contentInsetTop = 10;
|
|
221
|
+
updated->contentInsetBottom = 30;
|
|
222
|
+
[self.scroller updateProps:updated oldProps:filled];
|
|
223
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 620, 0.01);
|
|
224
|
+
auto tallerSticky = std::make_shared<RNCCollapsiblePagerViewProps>();
|
|
225
|
+
tallerSticky->headerHeight = 250;
|
|
226
|
+
tallerSticky->stickyHeaderHeight = 60;
|
|
227
|
+
[pager updateProps:tallerSticky oldProps:pagerProps];
|
|
228
|
+
[pager applyInsetsToScrollView:self.scroller.scrollView pageIndex:0 restore:NO];
|
|
229
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 600, 0.01);
|
|
230
|
+
[pager restoreScrollViewState:self.scroller.scrollView resetSavedOffset:YES];
|
|
231
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 660, 0.01);
|
|
232
|
+
[self.scroller removeFromSuperview];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
- (void)testContentViewportReleasesIndependentPagerContributions
|
|
236
|
+
{
|
|
237
|
+
NSObject *outer = [NSObject new];
|
|
238
|
+
NSObject *inner = [NSObject new];
|
|
239
|
+
[self.scroller setPagerStickyHeight:40 forOwner:outer];
|
|
240
|
+
[self.scroller setPagerStickyHeight:20 forOwner:inner];
|
|
241
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 540, 0.01);
|
|
242
|
+
[self.scroller setPagerStickyHeight:40 forOwner:outer];
|
|
243
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 540, 0.01);
|
|
244
|
+
[self.scroller setPagerStickyHeight:0 forOwner:outer];
|
|
245
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 580, 0.01);
|
|
246
|
+
[self.scroller prepareForRecycle];
|
|
247
|
+
auto props = std::make_shared<RNCNativeScrollerProps>();
|
|
248
|
+
[self.scroller updateProps:props oldProps:nullptr];
|
|
249
|
+
XCTAssertEqualWithAccuracy([self.scroller contentViewportHeight], 600, 0.01);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@end
|
|
@@ -166,7 +166,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
166
166
|
onNativeTabPress = event => {
|
|
167
167
|
const position = pageIndex(event.nativeEvent.position);
|
|
168
168
|
if (position !== null && position >= 0 && position < this.pages().length && (position !== this.state.selectedPage || this.latestNativeTabTarget !== null)) {
|
|
169
|
-
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ??
|
|
169
|
+
this.dispatchNativeTabPageCommand(position, this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - this.state.selectedPage) <= 1);
|
|
170
170
|
}
|
|
171
171
|
this.props.onNativeTabPress?.(event);
|
|
172
172
|
};
|
|
@@ -1,25 +1,185 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { findNodeHandle, Keyboard, Platform, StyleSheet, TextInput, View } from "react-native";
|
|
5
|
+
import NativeScrollerHost, { Commands } from "./NativeScrollerNativeComponent";
|
|
5
6
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
7
|
export const NATIVE_SCROLLER_NATIVE_ID_PREFIX = "rnc-collapsible-pager-native-scroller";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
/** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
|
|
10
|
+
|
|
11
|
+
// The RN 0.86 JS registry distinguishes another input from an unhandled tap.
|
|
12
|
+
// TextInput.State's public facade does not export this query.
|
|
13
|
+
const textInputState = require("react-native/Libraries/Components/TextInput/TextInputState").default;
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
viewport: {
|
|
16
|
+
flexGrow: 1,
|
|
17
|
+
flexShrink: 1,
|
|
18
|
+
overflow: "hidden"
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
position: "absolute",
|
|
22
|
+
top: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
right: 0,
|
|
25
|
+
flexShrink: 0
|
|
26
|
+
}
|
|
27
|
+
});
|
|
10
28
|
export const NativeScroller = /*#__PURE__*/React.forwardRef(function NativeScroller({
|
|
29
|
+
children,
|
|
11
30
|
nativeID,
|
|
12
|
-
nestedScrollEnabled = true,
|
|
13
31
|
pagerScrollKey,
|
|
32
|
+
style,
|
|
33
|
+
contentContainerStyle,
|
|
34
|
+
onLayout,
|
|
35
|
+
onContentSizeChange,
|
|
36
|
+
refreshControl,
|
|
37
|
+
nestedScrollEnabled = true,
|
|
38
|
+
contentInset,
|
|
39
|
+
decelerationRate = "normal",
|
|
40
|
+
keyboardShouldPersistTaps = "never",
|
|
41
|
+
onScroll,
|
|
42
|
+
onScrollBeginDrag,
|
|
43
|
+
onMomentumScrollBegin,
|
|
44
|
+
onMomentumScrollEnd,
|
|
45
|
+
onStartShouldSetResponder,
|
|
46
|
+
onStartShouldSetResponderCapture,
|
|
47
|
+
onResponderGrant,
|
|
48
|
+
onResponderRelease,
|
|
49
|
+
onResponderTerminationRequest,
|
|
14
50
|
...props
|
|
15
|
-
},
|
|
51
|
+
}, forwardedRef) {
|
|
52
|
+
const hostRef = React.useRef(null);
|
|
16
53
|
const generatedId = React.useId();
|
|
17
|
-
const
|
|
18
|
-
|
|
54
|
+
const [contentViewportHeight, setContentViewportHeight] = React.useState(0);
|
|
55
|
+
const [contentSize, setContentSize] = React.useState({
|
|
56
|
+
width: 0,
|
|
57
|
+
height: 0
|
|
58
|
+
});
|
|
59
|
+
const [refreshEpoch, requestRefreshSync] = React.useReducer(value => value + 1, 0);
|
|
60
|
+
const contentSizeRef = React.useRef(contentSize);
|
|
61
|
+
const scrolledSinceGrant = React.useRef(false);
|
|
62
|
+
const momentum = React.useRef(false);
|
|
63
|
+
const grantedDuringMomentum = React.useRef(false);
|
|
64
|
+
const persistTaps = keyboardShouldPersistTaps === true ? "always" : keyboardShouldPersistTaps === false ? "never" : keyboardShouldPersistTaps;
|
|
65
|
+
const contentStyle = StyleSheet.flatten(contentContainerStyle);
|
|
66
|
+
const refresh = refreshControl?.props;
|
|
67
|
+
const rate = typeof decelerationRate === "number" ? decelerationRate : decelerationRate === "fast" ? Platform.OS === "ios" ? 0.99 : 0.9 : Platform.OS === "ios" ? 0.998 : 0.985;
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
if (hostRef.current) Commands.setRefreshState(hostRef.current, refresh?.refreshing ?? false);
|
|
70
|
+
}, [refresh?.refreshing, refreshEpoch]);
|
|
71
|
+
React.useImperativeHandle(forwardedRef, () => ({
|
|
72
|
+
scrollTo(options, x, animated) {
|
|
73
|
+
const host = hostRef.current;
|
|
74
|
+
if (!host) return;
|
|
75
|
+
if (typeof options === "number") {
|
|
76
|
+
Commands.scrollTo(host, x ?? 0, options, animated !== false);
|
|
77
|
+
} else {
|
|
78
|
+
Commands.scrollTo(host, options?.x ?? 0, options?.y ?? 0, options?.animated !== false);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
scrollToEnd(options) {
|
|
82
|
+
if (hostRef.current) Commands.scrollToEnd(hostRef.current, options?.animated !== false);
|
|
83
|
+
},
|
|
84
|
+
flashScrollIndicators() {
|
|
85
|
+
if (hostRef.current) Commands.flashScrollIndicators(hostRef.current);
|
|
86
|
+
},
|
|
87
|
+
getNativeScrollRef: () => hostRef.current,
|
|
88
|
+
getScrollableNode: () => findNodeHandle(hostRef.current) ?? null,
|
|
89
|
+
measure: (...args) => hostRef.current?.measure(...args),
|
|
90
|
+
measureInWindow: (...args) => hostRef.current?.measureInWindow(...args),
|
|
91
|
+
measureLayout: (...args) => hostRef.current?.measureLayout(...args),
|
|
92
|
+
setNativeProps: nativeProps => hostRef.current?.setNativeProps(nativeProps),
|
|
93
|
+
focus: () => hostRef.current?.focus(),
|
|
94
|
+
blur: () => hostRef.current?.blur()
|
|
95
|
+
}), []);
|
|
96
|
+
const keyboardIsDismissible = () => Boolean(TextInput.State.currentlyFocusedInput() && (Keyboard.isVisible() || Platform.OS === "android" && Number(Platform.Version) < 30) && Keyboard.metrics()?.height !== 0);
|
|
97
|
+
return /*#__PURE__*/_jsx(NativeScrollerHost, {
|
|
19
98
|
...props,
|
|
20
|
-
ref:
|
|
21
|
-
nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${
|
|
22
|
-
|
|
99
|
+
ref: hostRef,
|
|
100
|
+
nativeID: `${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${pagerScrollKey ?? nativeID ?? generatedId}`,
|
|
101
|
+
style: [styles.viewport, style],
|
|
102
|
+
nestedScrollEnabled: nestedScrollEnabled,
|
|
103
|
+
contentWidth: contentSize.width,
|
|
104
|
+
contentHeight: contentSize.height,
|
|
105
|
+
contentInsetTop: contentInset?.top ?? 0,
|
|
106
|
+
contentInsetRight: contentInset?.right ?? 0,
|
|
107
|
+
contentInsetBottom: contentInset?.bottom ?? 0,
|
|
108
|
+
contentInsetLeft: contentInset?.left ?? 0,
|
|
109
|
+
decelerationRate: rate,
|
|
110
|
+
keyboardShouldPersistTaps: persistTaps,
|
|
111
|
+
refreshEnabled: Boolean(refresh) && refresh?.enabled !== false,
|
|
112
|
+
refreshing: refresh?.refreshing ?? false,
|
|
113
|
+
refreshTintColor: refresh?.tintColor,
|
|
114
|
+
refreshColors: refresh?.colors,
|
|
115
|
+
refreshProgressBackgroundColor: refresh?.progressBackgroundColor,
|
|
116
|
+
refreshProgressViewOffset: refresh?.progressViewOffset ?? 0,
|
|
117
|
+
refreshTitle: refresh?.title,
|
|
118
|
+
refreshTitleColor: refresh?.titleColor,
|
|
119
|
+
refreshSize: refresh?.size === "large" ? "large" : "default",
|
|
120
|
+
onRefresh: () => {
|
|
121
|
+
try {
|
|
122
|
+
refresh?.onRefresh?.();
|
|
123
|
+
} finally {
|
|
124
|
+
requestRefreshSync();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
onLayout: onLayout,
|
|
128
|
+
onContentViewportChange: event => {
|
|
129
|
+
const height = event.nativeEvent.height;
|
|
130
|
+
if (!Number.isFinite(height) || height < 0) return;
|
|
131
|
+
setContentViewportHeight(previous => previous === height ? previous : height);
|
|
132
|
+
},
|
|
133
|
+
onScroll: event => {
|
|
134
|
+
scrolledSinceGrant.current = true;
|
|
135
|
+
onScroll?.(event);
|
|
136
|
+
},
|
|
137
|
+
onScrollBeginDrag: event => {
|
|
138
|
+
scrolledSinceGrant.current = true;
|
|
139
|
+
onScrollBeginDrag?.(event);
|
|
140
|
+
},
|
|
141
|
+
onMomentumScrollBegin: event => {
|
|
142
|
+
momentum.current = true;
|
|
143
|
+
onMomentumScrollBegin?.(event);
|
|
144
|
+
},
|
|
145
|
+
onMomentumScrollEnd: event => {
|
|
146
|
+
momentum.current = false;
|
|
147
|
+
onMomentumScrollEnd?.(event);
|
|
148
|
+
},
|
|
149
|
+
onStartShouldSetResponder: event => onStartShouldSetResponder?.(event) ?? (persistTaps === "handled" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput()),
|
|
150
|
+
onStartShouldSetResponderCapture: event => onStartShouldSetResponderCapture?.(event) ?? (momentum.current || persistTaps === "never" && keyboardIsDismissible() && typeof event.target !== "number" && !textInputState.isTextInput(event.target)),
|
|
151
|
+
onResponderGrant: event => {
|
|
152
|
+
scrolledSinceGrant.current = false;
|
|
153
|
+
grantedDuringMomentum.current = momentum.current;
|
|
154
|
+
onResponderGrant?.(event);
|
|
155
|
+
},
|
|
156
|
+
onResponderRelease: event => {
|
|
157
|
+
onResponderRelease?.(event);
|
|
158
|
+
if (persistTaps !== "always" && keyboardIsDismissible() && event.target !== TextInput.State.currentlyFocusedInput() && !scrolledSinceGrant.current && !grantedDuringMomentum.current) Keyboard.dismiss();
|
|
159
|
+
},
|
|
160
|
+
onResponderTerminationRequest: event => onResponderTerminationRequest?.(event) ?? true,
|
|
161
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
162
|
+
collapsable: false,
|
|
163
|
+
style: [contentContainerStyle, (contentStyle?.flexGrow ?? 0) > 0 && (contentStyle?.minHeight === undefined || typeof contentStyle.minHeight === "number") ? {
|
|
164
|
+
minHeight: Math.max(contentViewportHeight, contentStyle?.minHeight ?? 0)
|
|
165
|
+
} : undefined, styles.content],
|
|
166
|
+
onLayout: event => {
|
|
167
|
+
const {
|
|
168
|
+
width,
|
|
169
|
+
height
|
|
170
|
+
} = event.nativeEvent.layout;
|
|
171
|
+
const previous = contentSizeRef.current;
|
|
172
|
+
if (previous.width === width && previous.height === height) return;
|
|
173
|
+
const next = {
|
|
174
|
+
width,
|
|
175
|
+
height
|
|
176
|
+
};
|
|
177
|
+
contentSizeRef.current = next;
|
|
178
|
+
setContentSize(next);
|
|
179
|
+
onContentSizeChange?.(width, height);
|
|
180
|
+
},
|
|
181
|
+
children: children
|
|
182
|
+
})
|
|
23
183
|
});
|
|
24
184
|
});
|
|
25
185
|
NativeScroller.displayName = "NativeScroller";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
codegenNativeCommands,
|
|
4
|
+
codegenNativeComponent,
|
|
5
|
+
type ColorValue,
|
|
6
|
+
type HostComponent,
|
|
7
|
+
type ViewProps,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import type {
|
|
10
|
+
DirectEventHandler,
|
|
11
|
+
Double,
|
|
12
|
+
WithDefault,
|
|
13
|
+
} from "react-native/Libraries/Types/CodegenTypes";
|
|
14
|
+
|
|
15
|
+
export type NativeScrollerScrollEvent = Readonly<{
|
|
16
|
+
contentInset: Readonly<{
|
|
17
|
+
top: Double;
|
|
18
|
+
right: Double;
|
|
19
|
+
bottom: Double;
|
|
20
|
+
left: Double;
|
|
21
|
+
}>;
|
|
22
|
+
contentOffset: Readonly<{ x: Double; y: Double }>;
|
|
23
|
+
contentSize: Readonly<{ width: Double; height: Double }>;
|
|
24
|
+
layoutMeasurement: Readonly<{ width: Double; height: Double }>;
|
|
25
|
+
velocity: Readonly<{ x: Double; y: Double }>;
|
|
26
|
+
targetContentOffset: Readonly<{ x: Double; y: Double }>;
|
|
27
|
+
zoomScale: Double;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export interface NativeProps extends ViewProps {
|
|
31
|
+
contentWidth?: WithDefault<Double, 0>;
|
|
32
|
+
contentHeight?: WithDefault<Double, 0>;
|
|
33
|
+
scrollEnabled?: WithDefault<boolean, true>;
|
|
34
|
+
nestedScrollEnabled?: WithDefault<boolean, true>;
|
|
35
|
+
showsVerticalScrollIndicator?: WithDefault<boolean, true>;
|
|
36
|
+
showsHorizontalScrollIndicator?: WithDefault<boolean, false>;
|
|
37
|
+
bounces?: WithDefault<boolean, true>;
|
|
38
|
+
alwaysBounceVertical?: WithDefault<boolean, true>;
|
|
39
|
+
keyboardDismissMode?: WithDefault<"none" | "on-drag" | "interactive", "none">;
|
|
40
|
+
keyboardShouldPersistTaps?: WithDefault<
|
|
41
|
+
"never" | "always" | "handled",
|
|
42
|
+
"never"
|
|
43
|
+
>;
|
|
44
|
+
scrollEventThrottle?: WithDefault<Double, 0>;
|
|
45
|
+
decelerationRate?: WithDefault<Double, 0.998>;
|
|
46
|
+
contentInsetTop?: WithDefault<Double, 0>;
|
|
47
|
+
contentInsetRight?: WithDefault<Double, 0>;
|
|
48
|
+
contentInsetBottom?: WithDefault<Double, 0>;
|
|
49
|
+
contentInsetLeft?: WithDefault<Double, 0>;
|
|
50
|
+
refreshEnabled?: WithDefault<boolean, false>;
|
|
51
|
+
refreshing?: WithDefault<boolean, false>;
|
|
52
|
+
refreshTintColor?: ColorValue;
|
|
53
|
+
refreshColors?: ReadonlyArray<ColorValue>;
|
|
54
|
+
refreshProgressBackgroundColor?: ColorValue;
|
|
55
|
+
refreshProgressViewOffset?: WithDefault<Double, 0>;
|
|
56
|
+
refreshTitle?: string;
|
|
57
|
+
refreshTitleColor?: ColorValue;
|
|
58
|
+
refreshSize?: WithDefault<"default" | "large", "default">;
|
|
59
|
+
onContentViewportChange?: DirectEventHandler<Readonly<{ height: Double }>>;
|
|
60
|
+
onRefresh?: DirectEventHandler<null>;
|
|
61
|
+
onScroll?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
62
|
+
onScrollBeginDrag?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
63
|
+
onScrollEndDrag?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
64
|
+
onMomentumScrollBegin?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
65
|
+
onMomentumScrollEnd?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type NativeScrollerHost = HostComponent<NativeProps>;
|
|
69
|
+
|
|
70
|
+
export interface NativeCommands {
|
|
71
|
+
scrollTo: (
|
|
72
|
+
viewRef: React.ElementRef<NativeScrollerHost>,
|
|
73
|
+
x: Double,
|
|
74
|
+
y: Double,
|
|
75
|
+
animated: boolean
|
|
76
|
+
) => void;
|
|
77
|
+
scrollToEnd: (
|
|
78
|
+
viewRef: React.ElementRef<NativeScrollerHost>,
|
|
79
|
+
animated: boolean
|
|
80
|
+
) => void;
|
|
81
|
+
flashScrollIndicators: (
|
|
82
|
+
viewRef: React.ElementRef<NativeScrollerHost>
|
|
83
|
+
) => void;
|
|
84
|
+
setRefreshState: (
|
|
85
|
+
viewRef: React.ElementRef<NativeScrollerHost>,
|
|
86
|
+
refreshing: boolean
|
|
87
|
+
) => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
91
|
+
supportedCommands: [
|
|
92
|
+
"scrollTo",
|
|
93
|
+
"scrollToEnd",
|
|
94
|
+
"flashScrollIndicators",
|
|
95
|
+
"setRefreshState",
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export default codegenNativeComponent<NativeProps>("RNCNativeScroller", {
|
|
100
|
+
interfaceOnly: true,
|
|
101
|
+
}) as NativeScrollerHost;
|
|
@@ -80,9 +80,8 @@ export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children"
|
|
|
80
80
|
nativeTabBar?: CollapsiblePagerNativeTabBarConfig;
|
|
81
81
|
onNativeTabPress?: (event: CollapsiblePagerViewOnNativeTabPressEvent) => void;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* scroll through every page in between, including unmounted ones.
|
|
83
|
+
* Defaults to animating adjacent native tab presses and jumping directly to
|
|
84
|
+
* non-adjacent pages. Set true to always animate, or false to always jump.
|
|
86
85
|
*/
|
|
87
86
|
nativeTabPressAnimationEnabled?: boolean;
|
|
88
87
|
/** Optional native secondary sticky header for iOS and Android. */
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { type ScrollViewProps } from "react-native";
|
|
2
|
+
import { type NativeMethods, type RefreshControlProps, type ScrollView, type ScrollViewProps, type ViewProps } from "react-native";
|
|
3
|
+
import NativeScrollerHost from "./NativeScrollerNativeComponent";
|
|
3
4
|
export declare const NATIVE_SCROLLER_NATIVE_ID_PREFIX = "rnc-collapsible-pager-native-scroller";
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
/** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
|
|
6
|
+
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"> {
|
|
6
7
|
pagerScrollKey?: string;
|
|
8
|
+
/** iOS only. Android retains AndroidX's native fling physics. */
|
|
9
|
+
decelerationRate?: ScrollViewProps["decelerationRate"];
|
|
10
|
+
/** Standard RefreshControl props are mapped to the independently owned native controller. */
|
|
11
|
+
refreshControl?: React.ReactElement<RefreshControlProps>;
|
|
7
12
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
type HostRef = React.ElementRef<typeof NativeScrollerHost>;
|
|
14
|
+
export interface NativeScrollerRef extends NativeMethods, Pick<React.ElementRef<typeof ScrollView>, "scrollTo" | "scrollToEnd" | "flashScrollIndicators"> {
|
|
15
|
+
getNativeScrollRef(): HostRef | null;
|
|
16
|
+
getScrollableNode(): number | null;
|
|
17
|
+
}
|
|
18
|
+
export declare const NativeScroller: React.ForwardRefExoticComponent<NativeScrollerProps & React.RefAttributes<NativeScrollerRef>>;
|
|
19
|
+
export {};
|
|
12
20
|
//# sourceMappingURL=NativeScroller.d.ts.map
|