@onekeyfe/react-native-pager-view 3.0.157-alpha.257 → 3.0.157-alpha.262
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 +55 -4
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +295 -59
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerPendingRestore.kt +26 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +15 -11
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +600 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +145 -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/NativeScrollerPendingRestoreTest.kt +31 -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 +282 -34
- package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
- package/ios/RNCNativeScrollerComponentView.h +17 -0
- package/ios/RNCNativeScrollerComponentView.mm +479 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +759 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
- package/ios/tests/RNCNativeScrollerTests.mm +252 -0
- package/lib/module/CollapsiblePagerView.js +10 -1
- package/lib/module/NativeScroller.js +171 -11
- package/lib/module/NativeScrollerNativeComponent.ts +101 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +3 -3
- package/lib/typescript/src/NativeScroller.d.ts +15 -7
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
- package/package.json +7 -4
- package/react-native-pager-view.podspec +2 -1
- package/react-native.config.js +15 -0
- package/src/CollapsiblePagerView.tsx +14 -4
- package/src/NativeScroller.tsx +291 -14
- package/src/NativeScrollerNativeComponent.ts +101 -0
|
@@ -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. */
|
|
@@ -106,6 +105,7 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
106
105
|
private lastMountSignature;
|
|
107
106
|
private nativeTabCommandId;
|
|
108
107
|
private latestNativeTabTarget;
|
|
108
|
+
private dispatchedNativeTabTarget;
|
|
109
109
|
private latestNativeSelectedPage;
|
|
110
110
|
private nativePagerScrollState;
|
|
111
111
|
private nativeTabCommandQueuedDuringScroll;
|
|
@@ -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
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { type ColorValue, type HostComponent, type ViewProps } from "react-native";
|
|
3
|
+
import type { DirectEventHandler, Double, WithDefault } from "react-native/Libraries/Types/CodegenTypes";
|
|
4
|
+
export type NativeScrollerScrollEvent = Readonly<{
|
|
5
|
+
contentInset: Readonly<{
|
|
6
|
+
top: Double;
|
|
7
|
+
right: Double;
|
|
8
|
+
bottom: Double;
|
|
9
|
+
left: Double;
|
|
10
|
+
}>;
|
|
11
|
+
contentOffset: Readonly<{
|
|
12
|
+
x: Double;
|
|
13
|
+
y: Double;
|
|
14
|
+
}>;
|
|
15
|
+
contentSize: Readonly<{
|
|
16
|
+
width: Double;
|
|
17
|
+
height: Double;
|
|
18
|
+
}>;
|
|
19
|
+
layoutMeasurement: Readonly<{
|
|
20
|
+
width: Double;
|
|
21
|
+
height: Double;
|
|
22
|
+
}>;
|
|
23
|
+
velocity: Readonly<{
|
|
24
|
+
x: Double;
|
|
25
|
+
y: Double;
|
|
26
|
+
}>;
|
|
27
|
+
targetContentOffset: Readonly<{
|
|
28
|
+
x: Double;
|
|
29
|
+
y: Double;
|
|
30
|
+
}>;
|
|
31
|
+
zoomScale: Double;
|
|
32
|
+
}>;
|
|
33
|
+
export interface NativeProps extends ViewProps {
|
|
34
|
+
contentWidth?: WithDefault<Double, 0>;
|
|
35
|
+
contentHeight?: WithDefault<Double, 0>;
|
|
36
|
+
scrollEnabled?: WithDefault<boolean, true>;
|
|
37
|
+
nestedScrollEnabled?: WithDefault<boolean, true>;
|
|
38
|
+
showsVerticalScrollIndicator?: WithDefault<boolean, true>;
|
|
39
|
+
showsHorizontalScrollIndicator?: WithDefault<boolean, false>;
|
|
40
|
+
bounces?: WithDefault<boolean, true>;
|
|
41
|
+
alwaysBounceVertical?: WithDefault<boolean, true>;
|
|
42
|
+
keyboardDismissMode?: WithDefault<"none" | "on-drag" | "interactive", "none">;
|
|
43
|
+
keyboardShouldPersistTaps?: WithDefault<"never" | "always" | "handled", "never">;
|
|
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<{
|
|
60
|
+
height: Double;
|
|
61
|
+
}>>;
|
|
62
|
+
onRefresh?: DirectEventHandler<null>;
|
|
63
|
+
onScroll?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
64
|
+
onScrollBeginDrag?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
65
|
+
onScrollEndDrag?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
66
|
+
onMomentumScrollBegin?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
67
|
+
onMomentumScrollEnd?: DirectEventHandler<NativeScrollerScrollEvent>;
|
|
68
|
+
}
|
|
69
|
+
type NativeScrollerHost = HostComponent<NativeProps>;
|
|
70
|
+
export interface NativeCommands {
|
|
71
|
+
scrollTo: (viewRef: React.ElementRef<NativeScrollerHost>, x: Double, y: Double, animated: boolean) => void;
|
|
72
|
+
scrollToEnd: (viewRef: React.ElementRef<NativeScrollerHost>, animated: boolean) => void;
|
|
73
|
+
flashScrollIndicators: (viewRef: React.ElementRef<NativeScrollerHost>) => void;
|
|
74
|
+
setRefreshState: (viewRef: React.ElementRef<NativeScrollerHost>, refreshing: boolean) => void;
|
|
75
|
+
}
|
|
76
|
+
export declare const Commands: NativeCommands;
|
|
77
|
+
declare const _default: NativeScrollerHost;
|
|
78
|
+
export default _default;
|
|
79
|
+
//# sourceMappingURL=NativeScrollerNativeComponent.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-pager-view",
|
|
3
|
-
"version": "3.0.157-alpha.
|
|
3
|
+
"version": "3.0.157-alpha.262",
|
|
4
4
|
"description": "React Native wrapper for Android and iOS ViewPager",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"!**/__fixtures__",
|
|
34
34
|
"!**/__mocks__",
|
|
35
35
|
"!**/.*",
|
|
36
|
-
"!**/*.map"
|
|
36
|
+
"!**/*.map",
|
|
37
|
+
"common",
|
|
38
|
+
"react-native.config.js"
|
|
37
39
|
],
|
|
38
40
|
"scripts": {
|
|
39
41
|
"clean": "del-cli lib",
|
|
@@ -67,7 +69,7 @@
|
|
|
67
69
|
"typescript": "^5.9.2"
|
|
68
70
|
},
|
|
69
71
|
"peerDependencies": {
|
|
70
|
-
"@onekeyfe/react-native-native-logger": "3.0.157-alpha.
|
|
72
|
+
"@onekeyfe/react-native-native-logger": "3.0.157-alpha.262",
|
|
71
73
|
"react": "*",
|
|
72
74
|
"react-native": "*"
|
|
73
75
|
},
|
|
@@ -99,7 +101,8 @@
|
|
|
99
101
|
"ios": {
|
|
100
102
|
"componentProvider": {
|
|
101
103
|
"RNCViewPager": "RNCPagerViewComponentView",
|
|
102
|
-
"RNCCollapsiblePagerView": "RNCCollapsiblePagerViewComponentView"
|
|
104
|
+
"RNCCollapsiblePagerView": "RNCCollapsiblePagerViewComponentView",
|
|
105
|
+
"RNCNativeScroller": "RNCNativeScrollerComponentView"
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
@@ -13,7 +13,8 @@ Pod::Spec.new do |s|
|
|
|
13
13
|
s.ios.deployment_target = "15.1"
|
|
14
14
|
s.source = { :git => "https://github.com/nicholasxuu/app-modules.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
|
-
s.source_files = "ios/**/*.{h,m,mm}"
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm}", "common/cpp/**/*.{h,cpp}"
|
|
17
|
+
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/common/cpp"' }
|
|
17
18
|
s.exclude_files = "ios/tests/**/*"
|
|
18
19
|
|
|
19
20
|
s.test_spec "Tests" do |test_spec|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
android: {
|
|
5
|
+
libraryName: 'pagerview',
|
|
6
|
+
componentDescriptors: [
|
|
7
|
+
'RNCViewPagerComponentDescriptor',
|
|
8
|
+
'RNCCollapsiblePagerViewComponentDescriptor',
|
|
9
|
+
'RNCNativeScrollerComponentDescriptor',
|
|
10
|
+
],
|
|
11
|
+
cmakeListsPath: 'src/main/jni/CMakeLists.txt',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -130,9 +130,8 @@ export interface CollapsiblePagerViewProps
|
|
|
130
130
|
nativeTabBar?: CollapsiblePagerNativeTabBarConfig;
|
|
131
131
|
onNativeTabPress?: (event: CollapsiblePagerViewOnNativeTabPressEvent) => void;
|
|
132
132
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* scroll through every page in between, including unmounted ones.
|
|
133
|
+
* Defaults to animating adjacent native tab presses and jumping directly to
|
|
134
|
+
* non-adjacent pages. Set true to always animate, or false to always jump.
|
|
136
135
|
*/
|
|
137
136
|
nativeTabPressAnimationEnabled?: boolean;
|
|
138
137
|
/** Optional native secondary sticky header for iOS and Android. */
|
|
@@ -191,6 +190,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
191
190
|
|
|
192
191
|
private latestNativeTabTarget: number | null = null;
|
|
193
192
|
|
|
193
|
+
// Target of the last tab command native has received and not yet reported
|
|
194
|
+
// as selected. Commands superseded before their frame never reach native.
|
|
195
|
+
private dispatchedNativeTabTarget: number | null = null;
|
|
196
|
+
|
|
194
197
|
private latestNativeSelectedPage = this.state.selectedPage;
|
|
195
198
|
|
|
196
199
|
private nativePagerScrollState: OnPageScrollStateChangedEventData["pageScrollState"] =
|
|
@@ -283,6 +286,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
283
286
|
if (commandId !== this.nativeTabCommandId || !this.nativeRef) {
|
|
284
287
|
return;
|
|
285
288
|
}
|
|
289
|
+
this.dispatchedNativeTabTarget = position;
|
|
286
290
|
if (animated) {
|
|
287
291
|
CollapsiblePagerViewNativeCommands.setPage(
|
|
288
292
|
this.nativeRef,
|
|
@@ -355,6 +359,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
355
359
|
const transitionComplete = pendingTarget === position;
|
|
356
360
|
if (transitionComplete) {
|
|
357
361
|
this.latestNativeTabTarget = null;
|
|
362
|
+
this.dispatchedNativeTabTarget = null;
|
|
358
363
|
this.nativeTabCommandQueuedDuringScroll = false;
|
|
359
364
|
}
|
|
360
365
|
this.setState(
|
|
@@ -387,6 +392,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
387
392
|
) {
|
|
388
393
|
++this.nativeTabCommandId;
|
|
389
394
|
this.latestNativeTabTarget = null;
|
|
395
|
+
this.dispatchedNativeTabTarget = null;
|
|
390
396
|
this.setState(
|
|
391
397
|
{ transientRetainedPages: [] },
|
|
392
398
|
this.notifyMountedPagesChanged
|
|
@@ -409,9 +415,13 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
409
415
|
(position !== this.state.selectedPage ||
|
|
410
416
|
this.latestNativeTabTarget !== null)
|
|
411
417
|
) {
|
|
418
|
+
// Selection state lags an in-flight transition until native reports it.
|
|
419
|
+
const currentPage =
|
|
420
|
+
this.dispatchedNativeTabTarget ?? this.state.selectedPage;
|
|
412
421
|
this.dispatchNativeTabPageCommand(
|
|
413
422
|
position,
|
|
414
|
-
this.props.nativeTabPressAnimationEnabled ??
|
|
423
|
+
this.props.nativeTabPressAnimationEnabled ??
|
|
424
|
+
Math.abs(position - currentPage) <= 1
|
|
415
425
|
);
|
|
416
426
|
}
|
|
417
427
|
this.props.onNativeTabPress?.(event);
|
package/src/NativeScroller.tsx
CHANGED
|
@@ -1,34 +1,311 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findNodeHandle,
|
|
4
|
+
Keyboard,
|
|
5
|
+
Platform,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
TextInput,
|
|
8
|
+
View,
|
|
9
|
+
type GestureResponderEvent,
|
|
10
|
+
type NativeMethods,
|
|
11
|
+
type RefreshControlProps,
|
|
12
|
+
type ScrollView,
|
|
13
|
+
type ScrollViewProps,
|
|
14
|
+
type ViewProps,
|
|
15
|
+
} from "react-native";
|
|
16
|
+
|
|
17
|
+
import NativeScrollerHost, { Commands } from "./NativeScrollerNativeComponent";
|
|
3
18
|
|
|
4
19
|
export const NATIVE_SCROLLER_NATIVE_ID_PREFIX =
|
|
5
20
|
"rnc-collapsible-pager-native-scroller";
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
|
|
22
|
+
/** Supported vertical subset; horizontal, snapping, zoom and sticky rows are not supported. */
|
|
23
|
+
export interface NativeScrollerProps
|
|
24
|
+
extends ViewProps,
|
|
25
|
+
Pick<
|
|
26
|
+
ScrollViewProps,
|
|
27
|
+
| "contentContainerStyle"
|
|
28
|
+
| "onContentSizeChange"
|
|
29
|
+
| "scrollEnabled"
|
|
30
|
+
| "nestedScrollEnabled"
|
|
31
|
+
| "showsVerticalScrollIndicator"
|
|
32
|
+
| "showsHorizontalScrollIndicator"
|
|
33
|
+
| "bounces"
|
|
34
|
+
| "alwaysBounceVertical"
|
|
35
|
+
| "contentInset"
|
|
36
|
+
| "keyboardDismissMode"
|
|
37
|
+
| "keyboardShouldPersistTaps"
|
|
38
|
+
| "scrollEventThrottle"
|
|
39
|
+
| "decelerationRate"
|
|
40
|
+
| "onScroll"
|
|
41
|
+
| "onScrollBeginDrag"
|
|
42
|
+
| "onScrollEndDrag"
|
|
43
|
+
| "onMomentumScrollBegin"
|
|
44
|
+
| "onMomentumScrollEnd"
|
|
45
|
+
> {
|
|
9
46
|
pagerScrollKey?: string;
|
|
47
|
+
/** iOS only. Android retains AndroidX's native fling physics. */
|
|
48
|
+
decelerationRate?: ScrollViewProps["decelerationRate"];
|
|
49
|
+
/** Standard RefreshControl props are mapped to the independently owned native controller. */
|
|
50
|
+
refreshControl?: React.ReactElement<RefreshControlProps>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type HostRef = React.ElementRef<typeof NativeScrollerHost>;
|
|
54
|
+
|
|
55
|
+
export interface NativeScrollerRef
|
|
56
|
+
extends NativeMethods,
|
|
57
|
+
Pick<
|
|
58
|
+
React.ElementRef<typeof ScrollView>,
|
|
59
|
+
"scrollTo" | "scrollToEnd" | "flashScrollIndicators"
|
|
60
|
+
> {
|
|
61
|
+
getNativeScrollRef(): HostRef | null;
|
|
62
|
+
getScrollableNode(): number | null;
|
|
10
63
|
}
|
|
11
64
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
65
|
+
// The RN 0.86 JS registry distinguishes another input from an unhandled tap.
|
|
66
|
+
// TextInput.State's public facade does not export this query.
|
|
67
|
+
const textInputState =
|
|
68
|
+
require("react-native/Libraries/Components/TextInput/TextInputState")
|
|
69
|
+
.default as {
|
|
70
|
+
isTextInput(target: GestureResponderEvent["target"]): boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const styles = StyleSheet.create({
|
|
74
|
+
viewport: { flexGrow: 1, flexShrink: 1, overflow: "hidden" },
|
|
75
|
+
content: { position: "absolute", top: 0, left: 0, right: 0, flexShrink: 0 },
|
|
76
|
+
});
|
|
77
|
+
|
|
15
78
|
export const NativeScroller = React.forwardRef<
|
|
16
|
-
|
|
79
|
+
NativeScrollerRef,
|
|
17
80
|
NativeScrollerProps
|
|
18
81
|
>(function NativeScroller(
|
|
19
|
-
{
|
|
20
|
-
|
|
82
|
+
{
|
|
83
|
+
children,
|
|
84
|
+
nativeID,
|
|
85
|
+
pagerScrollKey,
|
|
86
|
+
style,
|
|
87
|
+
contentContainerStyle,
|
|
88
|
+
onLayout,
|
|
89
|
+
onContentSizeChange,
|
|
90
|
+
refreshControl,
|
|
91
|
+
nestedScrollEnabled = true,
|
|
92
|
+
contentInset,
|
|
93
|
+
decelerationRate = "normal",
|
|
94
|
+
keyboardShouldPersistTaps = "never",
|
|
95
|
+
onScroll,
|
|
96
|
+
onScrollBeginDrag,
|
|
97
|
+
onMomentumScrollBegin,
|
|
98
|
+
onMomentumScrollEnd,
|
|
99
|
+
onStartShouldSetResponder,
|
|
100
|
+
onStartShouldSetResponderCapture,
|
|
101
|
+
onResponderGrant,
|
|
102
|
+
onResponderRelease,
|
|
103
|
+
onResponderTerminationRequest,
|
|
104
|
+
...props
|
|
105
|
+
},
|
|
106
|
+
forwardedRef
|
|
21
107
|
) {
|
|
108
|
+
const hostRef = React.useRef<HostRef>(null);
|
|
22
109
|
const generatedId = React.useId();
|
|
23
|
-
const
|
|
110
|
+
const [contentViewportHeight, setContentViewportHeight] = React.useState(0);
|
|
111
|
+
const [contentSize, setContentSize] = React.useState({ width: 0, height: 0 });
|
|
112
|
+
const [refreshEpoch, requestRefreshSync] = React.useReducer(
|
|
113
|
+
(value: number) => value + 1,
|
|
114
|
+
0
|
|
115
|
+
);
|
|
116
|
+
const contentSizeRef = React.useRef(contentSize);
|
|
117
|
+
const scrolledSinceGrant = React.useRef(false);
|
|
118
|
+
const momentum = React.useRef(false);
|
|
119
|
+
const grantedDuringMomentum = React.useRef(false);
|
|
120
|
+
const persistTaps =
|
|
121
|
+
keyboardShouldPersistTaps === true
|
|
122
|
+
? "always"
|
|
123
|
+
: keyboardShouldPersistTaps === false
|
|
124
|
+
? "never"
|
|
125
|
+
: keyboardShouldPersistTaps;
|
|
126
|
+
const contentStyle = StyleSheet.flatten(contentContainerStyle);
|
|
127
|
+
const refresh = refreshControl?.props;
|
|
128
|
+
const rate =
|
|
129
|
+
typeof decelerationRate === "number"
|
|
130
|
+
? decelerationRate
|
|
131
|
+
: decelerationRate === "fast"
|
|
132
|
+
? Platform.OS === "ios"
|
|
133
|
+
? 0.99
|
|
134
|
+
: 0.9
|
|
135
|
+
: Platform.OS === "ios"
|
|
136
|
+
? 0.998
|
|
137
|
+
: 0.985;
|
|
138
|
+
|
|
139
|
+
React.useEffect(() => {
|
|
140
|
+
if (hostRef.current)
|
|
141
|
+
Commands.setRefreshState(hostRef.current, refresh?.refreshing ?? false);
|
|
142
|
+
}, [refresh?.refreshing, refreshEpoch]);
|
|
143
|
+
|
|
144
|
+
React.useImperativeHandle(
|
|
145
|
+
forwardedRef,
|
|
146
|
+
() => ({
|
|
147
|
+
scrollTo(options, x, animated) {
|
|
148
|
+
const host = hostRef.current;
|
|
149
|
+
if (!host) return;
|
|
150
|
+
if (typeof options === "number") {
|
|
151
|
+
Commands.scrollTo(host, x ?? 0, options, animated !== false);
|
|
152
|
+
} else {
|
|
153
|
+
Commands.scrollTo(
|
|
154
|
+
host,
|
|
155
|
+
options?.x ?? 0,
|
|
156
|
+
options?.y ?? 0,
|
|
157
|
+
options?.animated !== false
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
scrollToEnd(options) {
|
|
162
|
+
if (hostRef.current)
|
|
163
|
+
Commands.scrollToEnd(hostRef.current, options?.animated !== false);
|
|
164
|
+
},
|
|
165
|
+
flashScrollIndicators() {
|
|
166
|
+
if (hostRef.current) Commands.flashScrollIndicators(hostRef.current);
|
|
167
|
+
},
|
|
168
|
+
getNativeScrollRef: () => hostRef.current,
|
|
169
|
+
getScrollableNode: () => findNodeHandle(hostRef.current) ?? null,
|
|
170
|
+
measure: (...args) => hostRef.current?.measure(...args),
|
|
171
|
+
measureInWindow: (...args) => hostRef.current?.measureInWindow(...args),
|
|
172
|
+
measureLayout: (...args) => hostRef.current?.measureLayout(...args),
|
|
173
|
+
setNativeProps: (nativeProps) =>
|
|
174
|
+
hostRef.current?.setNativeProps(nativeProps),
|
|
175
|
+
focus: () => hostRef.current?.focus(),
|
|
176
|
+
blur: () => hostRef.current?.blur(),
|
|
177
|
+
}),
|
|
178
|
+
[]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const keyboardIsDismissible = () =>
|
|
182
|
+
Boolean(
|
|
183
|
+
TextInput.State.currentlyFocusedInput() &&
|
|
184
|
+
(Keyboard.isVisible() ||
|
|
185
|
+
(Platform.OS === "android" && Number(Platform.Version) < 30)) &&
|
|
186
|
+
Keyboard.metrics()?.height !== 0
|
|
187
|
+
);
|
|
24
188
|
|
|
25
189
|
return (
|
|
26
|
-
<
|
|
190
|
+
<NativeScrollerHost
|
|
27
191
|
{...props}
|
|
28
|
-
ref={
|
|
29
|
-
nativeID={`${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${
|
|
192
|
+
ref={hostRef}
|
|
193
|
+
nativeID={`${NATIVE_SCROLLER_NATIVE_ID_PREFIX}:${
|
|
194
|
+
pagerScrollKey ?? nativeID ?? generatedId
|
|
195
|
+
}`}
|
|
196
|
+
style={[styles.viewport, style]}
|
|
30
197
|
nestedScrollEnabled={nestedScrollEnabled}
|
|
31
|
-
|
|
198
|
+
contentWidth={contentSize.width}
|
|
199
|
+
contentHeight={contentSize.height}
|
|
200
|
+
contentInsetTop={contentInset?.top ?? 0}
|
|
201
|
+
contentInsetRight={contentInset?.right ?? 0}
|
|
202
|
+
contentInsetBottom={contentInset?.bottom ?? 0}
|
|
203
|
+
contentInsetLeft={contentInset?.left ?? 0}
|
|
204
|
+
decelerationRate={rate}
|
|
205
|
+
keyboardShouldPersistTaps={persistTaps}
|
|
206
|
+
refreshEnabled={Boolean(refresh) && refresh?.enabled !== false}
|
|
207
|
+
refreshing={refresh?.refreshing ?? false}
|
|
208
|
+
refreshTintColor={refresh?.tintColor}
|
|
209
|
+
refreshColors={refresh?.colors}
|
|
210
|
+
refreshProgressBackgroundColor={refresh?.progressBackgroundColor}
|
|
211
|
+
refreshProgressViewOffset={refresh?.progressViewOffset ?? 0}
|
|
212
|
+
refreshTitle={refresh?.title}
|
|
213
|
+
refreshTitleColor={refresh?.titleColor}
|
|
214
|
+
refreshSize={refresh?.size === "large" ? "large" : "default"}
|
|
215
|
+
onRefresh={() => {
|
|
216
|
+
try {
|
|
217
|
+
refresh?.onRefresh?.();
|
|
218
|
+
} finally {
|
|
219
|
+
requestRefreshSync();
|
|
220
|
+
}
|
|
221
|
+
}}
|
|
222
|
+
onLayout={onLayout}
|
|
223
|
+
onContentViewportChange={(event) => {
|
|
224
|
+
const height = event.nativeEvent.height;
|
|
225
|
+
if (!Number.isFinite(height) || height < 0) return;
|
|
226
|
+
setContentViewportHeight((previous) =>
|
|
227
|
+
previous === height ? previous : height
|
|
228
|
+
);
|
|
229
|
+
}}
|
|
230
|
+
onScroll={(event) => {
|
|
231
|
+
scrolledSinceGrant.current = true;
|
|
232
|
+
onScroll?.(event);
|
|
233
|
+
}}
|
|
234
|
+
onScrollBeginDrag={(event) => {
|
|
235
|
+
scrolledSinceGrant.current = true;
|
|
236
|
+
onScrollBeginDrag?.(event);
|
|
237
|
+
}}
|
|
238
|
+
onMomentumScrollBegin={(event) => {
|
|
239
|
+
momentum.current = true;
|
|
240
|
+
onMomentumScrollBegin?.(event);
|
|
241
|
+
}}
|
|
242
|
+
onMomentumScrollEnd={(event) => {
|
|
243
|
+
momentum.current = false;
|
|
244
|
+
onMomentumScrollEnd?.(event);
|
|
245
|
+
}}
|
|
246
|
+
onStartShouldSetResponder={(event) =>
|
|
247
|
+
onStartShouldSetResponder?.(event) ??
|
|
248
|
+
(persistTaps === "handled" &&
|
|
249
|
+
keyboardIsDismissible() &&
|
|
250
|
+
event.target !== TextInput.State.currentlyFocusedInput())
|
|
251
|
+
}
|
|
252
|
+
onStartShouldSetResponderCapture={(event) =>
|
|
253
|
+
onStartShouldSetResponderCapture?.(event) ??
|
|
254
|
+
(momentum.current ||
|
|
255
|
+
(persistTaps === "never" &&
|
|
256
|
+
keyboardIsDismissible() &&
|
|
257
|
+
typeof event.target !== "number" &&
|
|
258
|
+
!textInputState.isTextInput(event.target)))
|
|
259
|
+
}
|
|
260
|
+
onResponderGrant={(event) => {
|
|
261
|
+
scrolledSinceGrant.current = false;
|
|
262
|
+
grantedDuringMomentum.current = momentum.current;
|
|
263
|
+
onResponderGrant?.(event);
|
|
264
|
+
}}
|
|
265
|
+
onResponderRelease={(event) => {
|
|
266
|
+
onResponderRelease?.(event);
|
|
267
|
+
if (
|
|
268
|
+
persistTaps !== "always" &&
|
|
269
|
+
keyboardIsDismissible() &&
|
|
270
|
+
event.target !== TextInput.State.currentlyFocusedInput() &&
|
|
271
|
+
!scrolledSinceGrant.current &&
|
|
272
|
+
!grantedDuringMomentum.current
|
|
273
|
+
)
|
|
274
|
+
Keyboard.dismiss();
|
|
275
|
+
}}
|
|
276
|
+
onResponderTerminationRequest={(event) =>
|
|
277
|
+
onResponderTerminationRequest?.(event) ?? true
|
|
278
|
+
}
|
|
279
|
+
>
|
|
280
|
+
<View
|
|
281
|
+
collapsable={false}
|
|
282
|
+
style={[
|
|
283
|
+
contentContainerStyle,
|
|
284
|
+
(contentStyle?.flexGrow ?? 0) > 0 &&
|
|
285
|
+
(contentStyle?.minHeight === undefined ||
|
|
286
|
+
typeof contentStyle.minHeight === "number")
|
|
287
|
+
? {
|
|
288
|
+
minHeight: Math.max(
|
|
289
|
+
contentViewportHeight,
|
|
290
|
+
contentStyle?.minHeight ?? 0
|
|
291
|
+
),
|
|
292
|
+
}
|
|
293
|
+
: undefined,
|
|
294
|
+
styles.content,
|
|
295
|
+
]}
|
|
296
|
+
onLayout={(event) => {
|
|
297
|
+
const { width, height } = event.nativeEvent.layout;
|
|
298
|
+
const previous = contentSizeRef.current;
|
|
299
|
+
if (previous.width === width && previous.height === height) return;
|
|
300
|
+
const next = { width, height };
|
|
301
|
+
contentSizeRef.current = next;
|
|
302
|
+
setContentSize(next);
|
|
303
|
+
onContentSizeChange?.(width, height);
|
|
304
|
+
}}
|
|
305
|
+
>
|
|
306
|
+
{children}
|
|
307
|
+
</View>
|
|
308
|
+
</NativeScrollerHost>
|
|
32
309
|
);
|
|
33
310
|
});
|
|
34
311
|
|