@onekeyfe/react-native-pager-view 3.0.112 → 3.0.113
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 +40 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +503 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +215 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
- package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt +39 -0
- package/ios/RNCCollapsiblePagerViewComponentView.h +9 -0
- package/ios/RNCCollapsiblePagerViewComponentView.mm +738 -0
- package/lib/module/CollapsiblePagerView.js +152 -0
- package/lib/module/CollapsiblePagerView.web.js +364 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +85 -0
- package/lib/module/PagerView.web.js +177 -0
- package/lib/module/index.js +2 -1
- package/lib/module/index.web.js +6 -0
- package/lib/module/usePagerView.js +1 -1
- package/lib/typescript/src/CollapsiblePagerView.d.ts +58 -0
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +93 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +50 -0
- package/lib/typescript/src/PagerView.web.d.ts +55 -0
- package/lib/typescript/src/index.d.ts +5 -4
- package/lib/typescript/src/index.web.d.ts +5 -0
- package/package.json +5 -2
- package/src/CollapsiblePagerView.tsx +258 -0
- package/src/CollapsiblePagerView.web.tsx +559 -0
- package/src/CollapsiblePagerViewNativeComponent.ts +85 -0
- package/src/PagerView.web.tsx +280 -0
- package/src/index.tsx +5 -4
- package/src/index.web.tsx +12 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { I18nManager, Platform, StyleSheet, View } from "react-native";
|
|
3
|
+
import type * as ReactNative from "react-native";
|
|
4
|
+
|
|
5
|
+
import CollapsiblePagerViewNativeComponent, {
|
|
6
|
+
Commands as CollapsiblePagerViewNativeCommands,
|
|
7
|
+
type NativeProps,
|
|
8
|
+
type OnCollapsibleStateChangedEventData,
|
|
9
|
+
} from "./CollapsiblePagerViewNativeComponent";
|
|
10
|
+
import type {
|
|
11
|
+
OnPageScrollEventData,
|
|
12
|
+
OnPageScrollStateChangedEventData,
|
|
13
|
+
OnPageSelectedEventData,
|
|
14
|
+
} from "./PagerViewNativeComponent";
|
|
15
|
+
|
|
16
|
+
export type CollapsiblePagerDiagnostics =
|
|
17
|
+
ReactNative.NativeSyntheticEvent<OnCollapsibleStateChangedEventData>;
|
|
18
|
+
|
|
19
|
+
export type CollapsiblePagerMountState = Readonly<{
|
|
20
|
+
position: number;
|
|
21
|
+
mountedPages: readonly number[];
|
|
22
|
+
pageKeys: readonly string[];
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
export interface CollapsiblePagerViewProps
|
|
26
|
+
extends Omit<
|
|
27
|
+
NativeProps,
|
|
28
|
+
| "children"
|
|
29
|
+
| "headerHeight"
|
|
30
|
+
| "stickyHeaderHeight"
|
|
31
|
+
| "pageKeys"
|
|
32
|
+
| "retainedPages"
|
|
33
|
+
| "layoutDirection"
|
|
34
|
+
> {
|
|
35
|
+
/** Collapsible content rendered above the sticky bar. */
|
|
36
|
+
header: React.ReactNode;
|
|
37
|
+
/** Bar which remains pinned after the header has collapsed. */
|
|
38
|
+
stickyHeader: React.ReactNode;
|
|
39
|
+
/** Measured header height. It may be updated after an onLayout measurement. */
|
|
40
|
+
headerHeight: number;
|
|
41
|
+
/** Measured sticky bar height. */
|
|
42
|
+
stickyHeaderHeight: number;
|
|
43
|
+
/**
|
|
44
|
+
* Number of pages retained on either side of the selected page. Page slots
|
|
45
|
+
* remain stable, while distant React/native list subtrees are really
|
|
46
|
+
* unmounted. Defaults to one adjacent page.
|
|
47
|
+
*/
|
|
48
|
+
pageRetentionDistance?: number;
|
|
49
|
+
layoutDirection?: "ltr" | "rtl" | "locale";
|
|
50
|
+
children?: React.ReactNode;
|
|
51
|
+
onMountedPagesChanged?: (state: CollapsiblePagerMountState) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type State = {
|
|
55
|
+
selectedPage: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const pageKey = (child: React.ReactNode, index: number) => {
|
|
59
|
+
if (React.isValidElement(child) && child.key != null) {
|
|
60
|
+
return String(child.key);
|
|
61
|
+
}
|
|
62
|
+
return `page-${index}`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pager variant whose vertical header coordination is performed by the native
|
|
67
|
+
* scrolling containers. React participates only at settled page boundaries to
|
|
68
|
+
* retain the selected/adjacent heavy page subtrees.
|
|
69
|
+
*/
|
|
70
|
+
export class CollapsiblePagerView extends React.PureComponent<
|
|
71
|
+
CollapsiblePagerViewProps,
|
|
72
|
+
State
|
|
73
|
+
> {
|
|
74
|
+
state: State = {
|
|
75
|
+
selectedPage: Math.max(0, this.props.initialPage ?? 0),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
private nativeRef: React.ElementRef<
|
|
79
|
+
typeof CollapsiblePagerViewNativeComponent
|
|
80
|
+
> | null = null;
|
|
81
|
+
|
|
82
|
+
private lastMountSignature: string | null = null;
|
|
83
|
+
|
|
84
|
+
componentDidMount() {
|
|
85
|
+
this.notifyMountedPagesChanged();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
componentDidUpdate() {
|
|
89
|
+
this.notifyMountedPagesChanged();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public setPage = (selectedPage: number) => {
|
|
93
|
+
if (this.nativeRef) {
|
|
94
|
+
CollapsiblePagerViewNativeCommands.setPage(this.nativeRef, selectedPage);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
public setPageWithoutAnimation = (selectedPage: number) => {
|
|
99
|
+
if (this.nativeRef) {
|
|
100
|
+
CollapsiblePagerViewNativeCommands.setPageWithoutAnimation(
|
|
101
|
+
this.nativeRef,
|
|
102
|
+
selectedPage
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
public setScrollEnabled = (scrollEnabled: boolean) => {
|
|
108
|
+
if (this.nativeRef) {
|
|
109
|
+
CollapsiblePagerViewNativeCommands.setScrollEnabledImperatively(
|
|
110
|
+
this.nativeRef,
|
|
111
|
+
scrollEnabled
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
private pages() {
|
|
117
|
+
return React.Children.toArray(this.props.children);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private retentionDistance() {
|
|
121
|
+
return Math.max(0, Math.floor(this.props.pageRetentionDistance ?? 1));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private mountedPages(pageCount = this.pages().length) {
|
|
125
|
+
const distance = this.retentionDistance();
|
|
126
|
+
const selected = Math.min(
|
|
127
|
+
Math.max(0, this.state.selectedPage),
|
|
128
|
+
Math.max(0, pageCount - 1)
|
|
129
|
+
);
|
|
130
|
+
const mounted: number[] = [];
|
|
131
|
+
for (let index = 0; index < pageCount; index += 1) {
|
|
132
|
+
if (Math.abs(index - selected) <= distance) {
|
|
133
|
+
mounted.push(index);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return mounted;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private notifyMountedPagesChanged = () => {
|
|
140
|
+
if (!this.props.onMountedPagesChanged) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const pages = this.pages();
|
|
144
|
+
const state = {
|
|
145
|
+
position: this.state.selectedPage,
|
|
146
|
+
mountedPages: this.mountedPages(pages.length),
|
|
147
|
+
pageKeys: pages.map(pageKey),
|
|
148
|
+
};
|
|
149
|
+
const signature = JSON.stringify(state);
|
|
150
|
+
if (signature === this.lastMountSignature) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.lastMountSignature = signature;
|
|
154
|
+
this.props.onMountedPagesChanged(state);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
private onPageSelected = (
|
|
158
|
+
event: ReactNative.NativeSyntheticEvent<OnPageSelectedEventData>
|
|
159
|
+
) => {
|
|
160
|
+
const position = Math.max(0, Math.trunc(event.nativeEvent.position));
|
|
161
|
+
if (position !== this.state.selectedPage) {
|
|
162
|
+
this.setState({ selectedPage: position }, this.notifyMountedPagesChanged);
|
|
163
|
+
}
|
|
164
|
+
this.props.onPageSelected?.(event);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
render() {
|
|
168
|
+
const {
|
|
169
|
+
children: _children,
|
|
170
|
+
header,
|
|
171
|
+
stickyHeader,
|
|
172
|
+
headerHeight,
|
|
173
|
+
stickyHeaderHeight,
|
|
174
|
+
pageRetentionDistance: _pageRetentionDistance,
|
|
175
|
+
onMountedPagesChanged: _onMountedPagesChanged,
|
|
176
|
+
onPageSelected: _onPageSelected,
|
|
177
|
+
layoutDirection,
|
|
178
|
+
offscreenPageLimit,
|
|
179
|
+
...nativeProps
|
|
180
|
+
} = this.props;
|
|
181
|
+
const pages = this.pages();
|
|
182
|
+
const pageKeys = pages.map(pageKey);
|
|
183
|
+
const retained = new Set(this.mountedPages(pages.length));
|
|
184
|
+
const deducedLayoutDirection =
|
|
185
|
+
!layoutDirection || layoutDirection === "locale"
|
|
186
|
+
? I18nManager.isRTL
|
|
187
|
+
? "rtl"
|
|
188
|
+
: "ltr"
|
|
189
|
+
: layoutDirection;
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<CollapsiblePagerViewNativeComponent
|
|
193
|
+
{...nativeProps}
|
|
194
|
+
ref={(ref) => {
|
|
195
|
+
this.nativeRef = ref;
|
|
196
|
+
}}
|
|
197
|
+
layoutDirection={deducedLayoutDirection}
|
|
198
|
+
offscreenPageLimit={Math.max(1, offscreenPageLimit ?? 1)}
|
|
199
|
+
headerHeight={Math.max(0, Math.round(headerHeight))}
|
|
200
|
+
stickyHeaderHeight={Math.max(0, Math.round(stickyHeaderHeight))}
|
|
201
|
+
pageKeys={JSON.stringify(pageKeys)}
|
|
202
|
+
retainedPages={JSON.stringify([...retained])}
|
|
203
|
+
onPageSelected={this.onPageSelected}
|
|
204
|
+
>
|
|
205
|
+
<View
|
|
206
|
+
key="collapsible-header"
|
|
207
|
+
collapsable={false}
|
|
208
|
+
pointerEvents="box-none"
|
|
209
|
+
style={[styles.slot, { bottom: undefined, height: Math.max(0, Math.round(headerHeight)) }]}
|
|
210
|
+
>
|
|
211
|
+
{header}
|
|
212
|
+
</View>
|
|
213
|
+
<View
|
|
214
|
+
key="sticky-header"
|
|
215
|
+
collapsable={false}
|
|
216
|
+
pointerEvents="box-none"
|
|
217
|
+
style={[
|
|
218
|
+
styles.slot,
|
|
219
|
+
{
|
|
220
|
+
top: Math.max(0, Math.round(headerHeight)),
|
|
221
|
+
bottom: undefined,
|
|
222
|
+
height: Math.max(0, Math.round(stickyHeaderHeight)),
|
|
223
|
+
},
|
|
224
|
+
]}
|
|
225
|
+
>
|
|
226
|
+
{stickyHeader}
|
|
227
|
+
</View>
|
|
228
|
+
{pages.map((page, index) => (
|
|
229
|
+
<View
|
|
230
|
+
key={pageKeys[index]}
|
|
231
|
+
collapsable={false}
|
|
232
|
+
style={[
|
|
233
|
+
styles.slot,
|
|
234
|
+
// Android translates the expanded pager while consuming header scroll.
|
|
235
|
+
// Keep Yoga's page bounds aligned with that native viewport.
|
|
236
|
+
Platform.OS === "android"
|
|
237
|
+
? { bottom: -Math.max(0, Math.round(headerHeight)) }
|
|
238
|
+
: null,
|
|
239
|
+
]}
|
|
240
|
+
>
|
|
241
|
+
{retained.has(index) ? page : null}
|
|
242
|
+
</View>
|
|
243
|
+
))}
|
|
244
|
+
</CollapsiblePagerViewNativeComponent>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const styles = {
|
|
250
|
+
slot: StyleSheet.absoluteFill,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export type CollapsiblePagerViewOnPageScrollEvent =
|
|
254
|
+
ReactNative.NativeSyntheticEvent<OnPageScrollEventData>;
|
|
255
|
+
export type CollapsiblePagerViewOnPageSelectedEvent =
|
|
256
|
+
ReactNative.NativeSyntheticEvent<OnPageSelectedEventData>;
|
|
257
|
+
export type CollapsiblePagerViewOnPageScrollStateChangedEvent =
|
|
258
|
+
ReactNative.NativeSyntheticEvent<OnPageScrollStateChangedEventData>;
|