@onekeyfe/react-native-pager-view 3.0.173 → 3.0.175
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/CollapsiblePagerHost.kt +860 -25
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +17 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +16 -0
- package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt +4 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +2 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerPendingRestoreTest.kt +11 -0
- package/docs/SPEC.md +65 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +616 -30
- package/ios/RNCNativeScrollerComponentView.h +1 -0
- package/ios/RNCNativeScrollerComponentView.mm +48 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +332 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +14 -5
- package/lib/module/CollapsiblePagerView.js +6 -0
- package/lib/module/CollapsiblePagerView.web.js +81 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +8 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -0
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +11 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +3 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +18 -0
- package/src/CollapsiblePagerView.web.tsx +170 -36
- package/src/CollapsiblePagerViewNativeComponent.ts +8 -0
|
@@ -47,6 +47,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
47
47
|
scrollEnabled = this.props.scrollEnabled ?? true;
|
|
48
48
|
lastMountSignature = null;
|
|
49
49
|
configureFrame = null;
|
|
50
|
+
pendingTopFrame = null;
|
|
50
51
|
transitionTimer = null;
|
|
51
52
|
pageScrollState = "idle";
|
|
52
53
|
configuredScrollElement = null;
|
|
@@ -70,6 +71,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
70
71
|
const pageCount = this.pages().length;
|
|
71
72
|
const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
|
|
72
73
|
if (selectedPage !== this.state.selectedPage) {
|
|
74
|
+
this.cancelPendingTopFrame();
|
|
73
75
|
this.finishPageTransition();
|
|
74
76
|
this.setState({
|
|
75
77
|
selectedPage,
|
|
@@ -81,6 +83,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
81
83
|
this.notifyMountedPagesChanged();
|
|
82
84
|
}
|
|
83
85
|
componentWillUnmount() {
|
|
86
|
+
this.cancelPendingTopFrame();
|
|
84
87
|
this.savePageOffset(this.state.selectedPage);
|
|
85
88
|
if (this.configureFrame !== null) {
|
|
86
89
|
cancelAnimationFrame(this.configureFrame);
|
|
@@ -99,6 +102,55 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
99
102
|
setPageWithoutAnimation = selectedPage => {
|
|
100
103
|
this.selectPage(selectedPage, false);
|
|
101
104
|
};
|
|
105
|
+
scrollToPageTop = options => {
|
|
106
|
+
this.scrollCurrentPageToTop(0, options?.animated ?? true);
|
|
107
|
+
};
|
|
108
|
+
scrollToStickyTop = options => {
|
|
109
|
+
this.scrollCurrentPageToTop(Math.max(0, this.props.headerHeight), options?.animated ?? true);
|
|
110
|
+
};
|
|
111
|
+
cancelPendingTopFrame = () => {
|
|
112
|
+
if (!this.pendingTopFrame) return;
|
|
113
|
+
const pending = this.pendingTopFrame;
|
|
114
|
+
cancelAnimationFrame(pending.frame);
|
|
115
|
+
this.pendingTopFrame = null;
|
|
116
|
+
if (this.pageScrollElement(this.state.selectedPage) === pending.element && this.pages().map(pageKey)[this.state.selectedPage] === pending.key) this.savePageOffset(this.state.selectedPage);
|
|
117
|
+
};
|
|
118
|
+
onQueuedTopWheel = event => {
|
|
119
|
+
if (event.deltaY !== 0) this.cancelPendingTopFrame();
|
|
120
|
+
};
|
|
121
|
+
onQueuedTopKey = event => {
|
|
122
|
+
if (["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key)) this.cancelPendingTopFrame();
|
|
123
|
+
};
|
|
124
|
+
scrollCurrentPageToTop(offset, animated) {
|
|
125
|
+
if (this.horizontalTouch || this.transitionTimer !== null) return;
|
|
126
|
+
const element = this.pageScrollElement(this.state.selectedPage);
|
|
127
|
+
const key = this.pages().map(pageKey)[this.state.selectedPage];
|
|
128
|
+
if (!element?.isConnected || !key) return;
|
|
129
|
+
this.cancelPendingTopFrame();
|
|
130
|
+
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
131
|
+
if (EventConstructor) element.dispatchEvent(new EventConstructor("ok-collapsible-pager-cancel-scroll-command"));
|
|
132
|
+
element.scrollTo({
|
|
133
|
+
left: element.scrollLeft,
|
|
134
|
+
top: element.scrollTop,
|
|
135
|
+
behavior: "instant"
|
|
136
|
+
});
|
|
137
|
+
const frame = requestAnimationFrame(() => {
|
|
138
|
+
if (this.pendingTopFrame?.frame !== frame) return;
|
|
139
|
+
this.pendingTopFrame = null;
|
|
140
|
+
if (!element.isConnected || this.horizontalTouch || this.transitionTimer !== null || this.pageScrollElement(this.state.selectedPage) !== element || this.pages().map(pageKey)[this.state.selectedPage] !== key) return;
|
|
141
|
+
this.pageOffsets.set(key, offset);
|
|
142
|
+
this.sharedHeaderOffset = offset;
|
|
143
|
+
element.scrollTo({
|
|
144
|
+
top: offset,
|
|
145
|
+
behavior: animated ? "smooth" : "instant"
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
this.pendingTopFrame = {
|
|
149
|
+
frame,
|
|
150
|
+
element,
|
|
151
|
+
key
|
|
152
|
+
};
|
|
153
|
+
}
|
|
102
154
|
setScrollEnabled = scrollEnabled => {
|
|
103
155
|
this.scrollEnabled = scrollEnabled;
|
|
104
156
|
};
|
|
@@ -165,8 +217,14 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
165
217
|
restoreConfiguredScrollElement() {
|
|
166
218
|
const configured = this.configuredScrollElement;
|
|
167
219
|
if (!configured) return;
|
|
220
|
+
this.cancelPendingTopFrame();
|
|
221
|
+
configured.element.removeEventListener("ok-native-list-scroll-command-accepted", this.cancelPendingTopFrame);
|
|
222
|
+
configured.element.removeEventListener("wheel", this.onQueuedTopWheel);
|
|
223
|
+
configured.element.removeEventListener("keydown", this.onQueuedTopKey);
|
|
168
224
|
configured.element.removeEventListener("scrollend", this.onVerticalScrollSettled);
|
|
169
225
|
const style = timelineStyle(configured.element);
|
|
226
|
+
configured.rangeSpacer.remove();
|
|
227
|
+
style.position = configured.position;
|
|
170
228
|
style.paddingTop = configured.paddingTop;
|
|
171
229
|
style.boxSizing = configured.boxSizing;
|
|
172
230
|
style.scrollTimelineName = configured.scrollTimelineName;
|
|
@@ -185,8 +243,23 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
185
243
|
this.restoreConfiguredScrollElement();
|
|
186
244
|
const style = timelineStyle(element);
|
|
187
245
|
const computedPaddingTop = element.ownerDocument.defaultView?.getComputedStyle(element).paddingTop || style.paddingTop || "0px";
|
|
246
|
+
const rangeSpacer = element.ownerDocument.createElement("div");
|
|
247
|
+
Object.assign(rangeSpacer.style, {
|
|
248
|
+
position: "absolute",
|
|
249
|
+
top: "0",
|
|
250
|
+
left: "0",
|
|
251
|
+
width: "1px",
|
|
252
|
+
pointerEvents: "none",
|
|
253
|
+
opacity: "0"
|
|
254
|
+
});
|
|
255
|
+
rangeSpacer.setAttribute("aria-hidden", "true");
|
|
256
|
+
const position = style.position;
|
|
257
|
+
if (element.ownerDocument.defaultView?.getComputedStyle(element).position === "static") style.position = "relative";
|
|
258
|
+
element.appendChild(rangeSpacer);
|
|
188
259
|
this.configuredScrollElement = {
|
|
189
260
|
element,
|
|
261
|
+
rangeSpacer,
|
|
262
|
+
position,
|
|
190
263
|
paddingTop: style.paddingTop,
|
|
191
264
|
basePaddingTop: computedPaddingTop,
|
|
192
265
|
boxSizing: style.boxSizing,
|
|
@@ -196,10 +269,14 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
196
269
|
pagerStickyInset: style.getPropertyValue(PAGER_STICKY_INSET)
|
|
197
270
|
};
|
|
198
271
|
element.addEventListener("scrollend", this.onVerticalScrollSettled);
|
|
272
|
+
element.addEventListener("ok-native-list-scroll-command-accepted", this.cancelPendingTopFrame);
|
|
273
|
+
element.addEventListener("wheel", this.onQueuedTopWheel);
|
|
274
|
+
element.addEventListener("keydown", this.onQueuedTopKey);
|
|
199
275
|
}
|
|
200
276
|
const style = timelineStyle(element);
|
|
201
277
|
const headerInset = Math.max(0, this.props.headerHeight);
|
|
202
278
|
const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
|
|
279
|
+
if (this.configuredScrollElement) this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
|
|
203
280
|
const basePaddingTop = this.configuredScrollElement?.basePaddingTop ?? "0px";
|
|
204
281
|
const paddingTop = `calc(${basePaddingTop} + ${headerInset + stickyInset}px)`;
|
|
205
282
|
const headerInsetValue = `${headerInset}px`;
|
|
@@ -223,6 +300,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
223
300
|
}
|
|
224
301
|
}
|
|
225
302
|
onVerticalScrollSettled = () => {
|
|
303
|
+
if (this.pendingTopFrame) return;
|
|
226
304
|
this.savePageOffset(this.state.selectedPage);
|
|
227
305
|
this.emitDiagnostics("scroll-settled");
|
|
228
306
|
};
|
|
@@ -246,6 +324,7 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
246
324
|
if (position === null || position < 0 || position >= pages.length || position === this.state.selectedPage) {
|
|
247
325
|
return;
|
|
248
326
|
}
|
|
327
|
+
this.cancelPendingTopFrame();
|
|
249
328
|
this.savePageOffset(this.state.selectedPage);
|
|
250
329
|
if (animated) this.beginPageTransition();else this.finishPageTransition();
|
|
251
330
|
this.setState({
|
|
@@ -313,7 +392,9 @@ export class CollapsiblePagerView extends React.PureComponent {
|
|
|
313
392
|
if (!touch) return;
|
|
314
393
|
const deltaX = touch.pageX - this.touchStartX;
|
|
315
394
|
const deltaY = touch.pageY - (this.touchStartY ?? touch.pageY);
|
|
395
|
+
if (Math.abs(deltaY) >= 8 && Math.abs(deltaY) > Math.abs(deltaX)) this.cancelPendingTopFrame();
|
|
316
396
|
if (Math.abs(deltaX) >= 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
397
|
+
this.cancelPendingTopFrame();
|
|
317
398
|
this.horizontalTouch = true;
|
|
318
399
|
this.emitPageScrollState("dragging");
|
|
319
400
|
}
|
|
@@ -35,6 +35,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
|
|
|
35
35
|
observedScrollableCount: Int32;
|
|
36
36
|
retainedPages: string;
|
|
37
37
|
reason: string;
|
|
38
|
+
pageKey: string;
|
|
39
|
+
headerSettledAtTop: boolean;
|
|
38
40
|
}>;
|
|
39
41
|
|
|
40
42
|
export type OnNativeTabPressEventData = Readonly<{
|
|
@@ -83,6 +85,11 @@ export interface NativeProps extends ViewProps {
|
|
|
83
85
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
84
86
|
|
|
85
87
|
export interface NativeCommands {
|
|
88
|
+
scrollToTop: (
|
|
89
|
+
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
90
|
+
target: Int32,
|
|
91
|
+
animated: boolean
|
|
92
|
+
) => void;
|
|
86
93
|
setPage: (
|
|
87
94
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
88
95
|
selectedPage: Int32
|
|
@@ -99,6 +106,7 @@ export interface NativeCommands {
|
|
|
99
106
|
|
|
100
107
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
101
108
|
supportedCommands: [
|
|
109
|
+
"scrollToTop",
|
|
102
110
|
"setPage",
|
|
103
111
|
"setPageWithoutAnimation",
|
|
104
112
|
"setScrollEnabledImperatively",
|
|
@@ -113,6 +113,12 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
113
113
|
componentDidUpdate(): void;
|
|
114
114
|
setPage: (selectedPage: number) => void;
|
|
115
115
|
setPageWithoutAnimation: (selectedPage: number) => void;
|
|
116
|
+
scrollToPageTop: (options?: Readonly<{
|
|
117
|
+
animated?: boolean;
|
|
118
|
+
}>) => void;
|
|
119
|
+
scrollToStickyTop: (options?: Readonly<{
|
|
120
|
+
animated?: boolean;
|
|
121
|
+
}>) => void;
|
|
116
122
|
setScrollEnabled: (scrollEnabled: boolean) => void;
|
|
117
123
|
private pages;
|
|
118
124
|
private nativeTabBarEnabled;
|
|
@@ -62,6 +62,7 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
62
62
|
private scrollEnabled;
|
|
63
63
|
private lastMountSignature;
|
|
64
64
|
private configureFrame;
|
|
65
|
+
private pendingTopFrame;
|
|
65
66
|
private transitionTimer;
|
|
66
67
|
private pageScrollState;
|
|
67
68
|
private configuredScrollElement;
|
|
@@ -71,6 +72,16 @@ export declare class CollapsiblePagerView extends React.PureComponent<Collapsibl
|
|
|
71
72
|
componentWillUnmount(): void;
|
|
72
73
|
setPage: (selectedPage: number) => void;
|
|
73
74
|
setPageWithoutAnimation: (selectedPage: number) => void;
|
|
75
|
+
scrollToPageTop: (options?: Readonly<{
|
|
76
|
+
animated?: boolean;
|
|
77
|
+
}>) => void;
|
|
78
|
+
scrollToStickyTop: (options?: Readonly<{
|
|
79
|
+
animated?: boolean;
|
|
80
|
+
}>) => void;
|
|
81
|
+
private cancelPendingTopFrame;
|
|
82
|
+
private onQueuedTopWheel;
|
|
83
|
+
private onQueuedTopKey;
|
|
84
|
+
private scrollCurrentPageToTop;
|
|
74
85
|
setScrollEnabled: (scrollEnabled: boolean) => void;
|
|
75
86
|
private pages;
|
|
76
87
|
private retentionDistance;
|
|
@@ -19,6 +19,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
|
|
|
19
19
|
observedScrollableCount: Int32;
|
|
20
20
|
retainedPages: string;
|
|
21
21
|
reason: string;
|
|
22
|
+
pageKey: string;
|
|
23
|
+
headerSettledAtTop: boolean;
|
|
22
24
|
}>;
|
|
23
25
|
export type OnNativeTabPressEventData = Readonly<{
|
|
24
26
|
position: Int32;
|
|
@@ -62,6 +64,7 @@ export interface NativeProps extends ViewProps {
|
|
|
62
64
|
}
|
|
63
65
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
64
66
|
export interface NativeCommands {
|
|
67
|
+
scrollToTop: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, target: Int32, animated: boolean) => void;
|
|
65
68
|
setPage: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
|
|
66
69
|
setPageWithoutAnimation: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, selectedPage: Int32) => void;
|
|
67
70
|
setScrollEnabledImperatively: (viewRef: React.ElementRef<CollapsiblePagerViewNativeType>, scrollEnabled: boolean) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-pager-view",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.175",
|
|
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": "3.0.
|
|
75
|
+
"@onekeyfe/react-native-native-logger": "3.0.175",
|
|
76
76
|
"react": "*",
|
|
77
77
|
"react-native": "*"
|
|
78
78
|
},
|
|
@@ -250,6 +250,24 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
+
public scrollToPageTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
254
|
+
if (this.nativeRef)
|
|
255
|
+
CollapsiblePagerViewNativeCommands.scrollToTop(
|
|
256
|
+
this.nativeRef,
|
|
257
|
+
0,
|
|
258
|
+
options?.animated ?? true
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
263
|
+
if (this.nativeRef)
|
|
264
|
+
CollapsiblePagerViewNativeCommands.scrollToTop(
|
|
265
|
+
this.nativeRef,
|
|
266
|
+
1,
|
|
267
|
+
options?.animated ?? true
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
253
271
|
public setScrollEnabled = (scrollEnabled: boolean) => {
|
|
254
272
|
if (this.nativeRef) {
|
|
255
273
|
CollapsiblePagerViewNativeCommands.setScrollEnabledImperatively(
|
|
@@ -79,6 +79,8 @@ type State = {
|
|
|
79
79
|
|
|
80
80
|
type ConfiguredScrollElement = Readonly<{
|
|
81
81
|
element: HTMLElement;
|
|
82
|
+
rangeSpacer: HTMLElement;
|
|
83
|
+
position: string;
|
|
82
84
|
paddingTop: string;
|
|
83
85
|
basePaddingTop: string;
|
|
84
86
|
boxSizing: string;
|
|
@@ -100,10 +102,7 @@ const pageIndex = (value: number) => {
|
|
|
100
102
|
};
|
|
101
103
|
|
|
102
104
|
const clampedPageIndex = (value: number, pageCount: number) =>
|
|
103
|
-
Math.min(
|
|
104
|
-
Math.max(0, pageIndex(value) ?? 0),
|
|
105
|
-
Math.max(0, pageCount - 1)
|
|
106
|
-
);
|
|
105
|
+
Math.min(Math.max(0, pageIndex(value) ?? 0), Math.max(0, pageCount - 1));
|
|
107
106
|
|
|
108
107
|
const webElement = (node: unknown) => {
|
|
109
108
|
const element = node as HTMLElement | null;
|
|
@@ -158,6 +157,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
158
157
|
private scrollEnabled = this.props.scrollEnabled ?? true;
|
|
159
158
|
private lastMountSignature: string | null = null;
|
|
160
159
|
private configureFrame: number | null = null;
|
|
160
|
+
private pendingTopFrame: Readonly<{
|
|
161
|
+
frame: number;
|
|
162
|
+
element: HTMLElement;
|
|
163
|
+
key: string;
|
|
164
|
+
}> | null = null;
|
|
161
165
|
private transitionTimer: ReturnType<typeof setTimeout> | null = null;
|
|
162
166
|
private pageScrollState: OnPageScrollStateChangedEventData["pageScrollState"] =
|
|
163
167
|
"idle";
|
|
@@ -184,6 +188,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
184
188
|
const pageCount = this.pages().length;
|
|
185
189
|
const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
|
|
186
190
|
if (selectedPage !== this.state.selectedPage) {
|
|
191
|
+
this.cancelPendingTopFrame();
|
|
187
192
|
this.finishPageTransition();
|
|
188
193
|
this.setState({
|
|
189
194
|
selectedPage,
|
|
@@ -196,6 +201,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
componentWillUnmount() {
|
|
204
|
+
this.cancelPendingTopFrame();
|
|
199
205
|
this.savePageOffset(this.state.selectedPage);
|
|
200
206
|
if (this.configureFrame !== null) {
|
|
201
207
|
cancelAnimationFrame(this.configureFrame);
|
|
@@ -217,6 +223,85 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
217
223
|
this.selectPage(selectedPage, false);
|
|
218
224
|
};
|
|
219
225
|
|
|
226
|
+
public scrollToPageTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
227
|
+
this.scrollCurrentPageToTop(0, options?.animated ?? true);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
231
|
+
this.scrollCurrentPageToTop(
|
|
232
|
+
Math.max(0, this.props.headerHeight),
|
|
233
|
+
options?.animated ?? true
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
private cancelPendingTopFrame = () => {
|
|
238
|
+
if (!this.pendingTopFrame) return;
|
|
239
|
+
const pending = this.pendingTopFrame;
|
|
240
|
+
cancelAnimationFrame(pending.frame);
|
|
241
|
+
this.pendingTopFrame = null;
|
|
242
|
+
if (
|
|
243
|
+
this.pageScrollElement(this.state.selectedPage) === pending.element &&
|
|
244
|
+
this.pages().map(pageKey)[this.state.selectedPage] === pending.key
|
|
245
|
+
)
|
|
246
|
+
this.savePageOffset(this.state.selectedPage);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
private onQueuedTopWheel = (event: WheelEvent) => {
|
|
250
|
+
if (event.deltaY !== 0) this.cancelPendingTopFrame();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
private onQueuedTopKey = (event: KeyboardEvent) => {
|
|
254
|
+
if (
|
|
255
|
+
[
|
|
256
|
+
"ArrowUp",
|
|
257
|
+
"ArrowDown",
|
|
258
|
+
"PageUp",
|
|
259
|
+
"PageDown",
|
|
260
|
+
"Home",
|
|
261
|
+
"End",
|
|
262
|
+
" ",
|
|
263
|
+
].includes(event.key)
|
|
264
|
+
)
|
|
265
|
+
this.cancelPendingTopFrame();
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
private scrollCurrentPageToTop(offset: number, animated: boolean) {
|
|
269
|
+
if (this.horizontalTouch || this.transitionTimer !== null) return;
|
|
270
|
+
const element = this.pageScrollElement(this.state.selectedPage);
|
|
271
|
+
const key = this.pages().map(pageKey)[this.state.selectedPage];
|
|
272
|
+
if (!element?.isConnected || !key) return;
|
|
273
|
+
this.cancelPendingTopFrame();
|
|
274
|
+
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
275
|
+
if (EventConstructor)
|
|
276
|
+
element.dispatchEvent(
|
|
277
|
+
new EventConstructor("ok-collapsible-pager-cancel-scroll-command")
|
|
278
|
+
);
|
|
279
|
+
element.scrollTo({
|
|
280
|
+
left: element.scrollLeft,
|
|
281
|
+
top: element.scrollTop,
|
|
282
|
+
behavior: "instant",
|
|
283
|
+
});
|
|
284
|
+
const frame = requestAnimationFrame(() => {
|
|
285
|
+
if (this.pendingTopFrame?.frame !== frame) return;
|
|
286
|
+
this.pendingTopFrame = null;
|
|
287
|
+
if (
|
|
288
|
+
!element.isConnected ||
|
|
289
|
+
this.horizontalTouch ||
|
|
290
|
+
this.transitionTimer !== null ||
|
|
291
|
+
this.pageScrollElement(this.state.selectedPage) !== element ||
|
|
292
|
+
this.pages().map(pageKey)[this.state.selectedPage] !== key
|
|
293
|
+
)
|
|
294
|
+
return;
|
|
295
|
+
this.pageOffsets.set(key, offset);
|
|
296
|
+
this.sharedHeaderOffset = offset;
|
|
297
|
+
element.scrollTo({
|
|
298
|
+
top: offset,
|
|
299
|
+
behavior: animated ? "smooth" : "instant",
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
this.pendingTopFrame = { frame, element, key };
|
|
303
|
+
}
|
|
304
|
+
|
|
220
305
|
public setScrollEnabled = (scrollEnabled: boolean) => {
|
|
221
306
|
this.scrollEnabled = scrollEnabled;
|
|
222
307
|
};
|
|
@@ -278,10 +363,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
278
363
|
private setTrack = (node: unknown) => {
|
|
279
364
|
const track = webElement(node);
|
|
280
365
|
if (track === this.track) return;
|
|
281
|
-
this.track?.removeEventListener(
|
|
282
|
-
"transitionend",
|
|
283
|
-
this.onTrackTransitionEnd
|
|
284
|
-
);
|
|
366
|
+
this.track?.removeEventListener("transitionend", this.onTrackTransitionEnd);
|
|
285
367
|
this.track = track;
|
|
286
368
|
this.track?.addEventListener("transitionend", this.onTrackTransitionEnd);
|
|
287
369
|
};
|
|
@@ -307,11 +389,20 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
307
389
|
private restoreConfiguredScrollElement() {
|
|
308
390
|
const configured = this.configuredScrollElement;
|
|
309
391
|
if (!configured) return;
|
|
392
|
+
this.cancelPendingTopFrame();
|
|
393
|
+
configured.element.removeEventListener(
|
|
394
|
+
"ok-native-list-scroll-command-accepted",
|
|
395
|
+
this.cancelPendingTopFrame
|
|
396
|
+
);
|
|
397
|
+
configured.element.removeEventListener("wheel", this.onQueuedTopWheel);
|
|
398
|
+
configured.element.removeEventListener("keydown", this.onQueuedTopKey);
|
|
310
399
|
configured.element.removeEventListener(
|
|
311
400
|
"scrollend",
|
|
312
401
|
this.onVerticalScrollSettled
|
|
313
402
|
);
|
|
314
403
|
const style = timelineStyle(configured.element);
|
|
404
|
+
configured.rangeSpacer.remove();
|
|
405
|
+
style.position = configured.position;
|
|
315
406
|
style.paddingTop = configured.paddingTop;
|
|
316
407
|
style.boxSizing = configured.boxSizing;
|
|
317
408
|
style.scrollTimelineName = configured.scrollTimelineName;
|
|
@@ -332,9 +423,30 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
332
423
|
const style = timelineStyle(element);
|
|
333
424
|
const computedPaddingTop =
|
|
334
425
|
element.ownerDocument.defaultView?.getComputedStyle(element)
|
|
335
|
-
.paddingTop ||
|
|
426
|
+
.paddingTop ||
|
|
427
|
+
style.paddingTop ||
|
|
428
|
+
"0px";
|
|
429
|
+
const rangeSpacer = element.ownerDocument.createElement("div");
|
|
430
|
+
Object.assign(rangeSpacer.style, {
|
|
431
|
+
position: "absolute",
|
|
432
|
+
top: "0",
|
|
433
|
+
left: "0",
|
|
434
|
+
width: "1px",
|
|
435
|
+
pointerEvents: "none",
|
|
436
|
+
opacity: "0",
|
|
437
|
+
});
|
|
438
|
+
rangeSpacer.setAttribute("aria-hidden", "true");
|
|
439
|
+
const position = style.position;
|
|
440
|
+
if (
|
|
441
|
+
element.ownerDocument.defaultView?.getComputedStyle(element)
|
|
442
|
+
.position === "static"
|
|
443
|
+
)
|
|
444
|
+
style.position = "relative";
|
|
445
|
+
element.appendChild(rangeSpacer);
|
|
336
446
|
this.configuredScrollElement = {
|
|
337
447
|
element,
|
|
448
|
+
rangeSpacer,
|
|
449
|
+
position,
|
|
338
450
|
paddingTop: style.paddingTop,
|
|
339
451
|
basePaddingTop: computedPaddingTop,
|
|
340
452
|
boxSizing: style.boxSizing,
|
|
@@ -344,13 +456,24 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
344
456
|
pagerStickyInset: style.getPropertyValue(PAGER_STICKY_INSET),
|
|
345
457
|
};
|
|
346
458
|
element.addEventListener("scrollend", this.onVerticalScrollSettled);
|
|
459
|
+
element.addEventListener(
|
|
460
|
+
"ok-native-list-scroll-command-accepted",
|
|
461
|
+
this.cancelPendingTopFrame
|
|
462
|
+
);
|
|
463
|
+
element.addEventListener("wheel", this.onQueuedTopWheel);
|
|
464
|
+
element.addEventListener("keydown", this.onQueuedTopKey);
|
|
347
465
|
}
|
|
348
466
|
|
|
349
467
|
const style = timelineStyle(element);
|
|
350
468
|
const headerInset = Math.max(0, this.props.headerHeight);
|
|
351
469
|
const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
|
|
352
|
-
|
|
353
|
-
|
|
470
|
+
if (this.configuredScrollElement)
|
|
471
|
+
this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
|
|
472
|
+
const basePaddingTop =
|
|
473
|
+
this.configuredScrollElement?.basePaddingTop ?? "0px";
|
|
474
|
+
const paddingTop = `calc(${basePaddingTop} + ${
|
|
475
|
+
headerInset + stickyInset
|
|
476
|
+
}px)`;
|
|
354
477
|
const headerInsetValue = `${headerInset}px`;
|
|
355
478
|
const stickyInsetValue = `${stickyInset}px`;
|
|
356
479
|
const insetsChanged =
|
|
@@ -379,6 +502,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
379
502
|
}
|
|
380
503
|
|
|
381
504
|
private onVerticalScrollSettled = () => {
|
|
505
|
+
if (this.pendingTopFrame) return;
|
|
382
506
|
this.savePageOffset(this.state.selectedPage);
|
|
383
507
|
this.emitDiagnostics("scroll-settled");
|
|
384
508
|
};
|
|
@@ -416,33 +540,37 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
416
540
|
) {
|
|
417
541
|
return;
|
|
418
542
|
}
|
|
543
|
+
this.cancelPendingTopFrame();
|
|
419
544
|
this.savePageOffset(this.state.selectedPage);
|
|
420
545
|
|
|
421
546
|
if (animated) this.beginPageTransition();
|
|
422
547
|
else this.finishPageTransition();
|
|
423
|
-
this.setState(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
548
|
+
this.setState(
|
|
549
|
+
{ selectedPage: position, animateTransition: animated },
|
|
550
|
+
() => {
|
|
551
|
+
this.scheduleScrollConfiguration();
|
|
552
|
+
const restore = () => {
|
|
553
|
+
this.configureActiveScrollElement();
|
|
554
|
+
this.restorePageOffset(position);
|
|
555
|
+
};
|
|
556
|
+
if (typeof requestAnimationFrame === "function") {
|
|
557
|
+
requestAnimationFrame(restore);
|
|
558
|
+
} else {
|
|
559
|
+
setTimeout(restore, 0);
|
|
560
|
+
}
|
|
561
|
+
this.props.onPageSelected?.(
|
|
562
|
+
this.nativeEvent<OnPageSelectedEventData>({ position })
|
|
563
|
+
);
|
|
564
|
+
this.props.onPageScroll?.(
|
|
565
|
+
this.nativeEvent<OnPageScrollEventData>({
|
|
566
|
+
position,
|
|
567
|
+
offset: 0,
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
this.notifyMountedPagesChanged();
|
|
571
|
+
this.emitDiagnostics("page-selected");
|
|
433
572
|
}
|
|
434
|
-
|
|
435
|
-
this.nativeEvent<OnPageSelectedEventData>({ position })
|
|
436
|
-
);
|
|
437
|
-
this.props.onPageScroll?.(
|
|
438
|
-
this.nativeEvent<OnPageScrollEventData>({
|
|
439
|
-
position,
|
|
440
|
-
offset: 0,
|
|
441
|
-
})
|
|
442
|
-
);
|
|
443
|
-
this.notifyMountedPagesChanged();
|
|
444
|
-
this.emitDiagnostics("page-selected");
|
|
445
|
-
});
|
|
573
|
+
);
|
|
446
574
|
}
|
|
447
575
|
|
|
448
576
|
private notifyMountedPagesChanged = () => {
|
|
@@ -500,7 +628,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
500
628
|
if (!touch) return;
|
|
501
629
|
const deltaX = touch.pageX - this.touchStartX;
|
|
502
630
|
const deltaY = touch.pageY - (this.touchStartY ?? touch.pageY);
|
|
631
|
+
if (Math.abs(deltaY) >= 8 && Math.abs(deltaY) > Math.abs(deltaX))
|
|
632
|
+
this.cancelPendingTopFrame();
|
|
503
633
|
if (Math.abs(deltaX) >= 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
634
|
+
this.cancelPendingTopFrame();
|
|
504
635
|
this.horizontalTouch = true;
|
|
505
636
|
this.emitPageScrollState("dragging");
|
|
506
637
|
}
|
|
@@ -522,9 +653,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
522
653
|
((!this.props.layoutDirection ||
|
|
523
654
|
this.props.layoutDirection === "locale") &&
|
|
524
655
|
I18nManager.isRTL);
|
|
525
|
-
const direction =
|
|
526
|
-
|
|
527
|
-
|
|
656
|
+
const direction =
|
|
657
|
+
Math.abs(delta) >= 40 && Math.abs(delta) > Math.abs(verticalDelta)
|
|
658
|
+
? delta < 0
|
|
659
|
+
? 1
|
|
660
|
+
: -1
|
|
661
|
+
: 0;
|
|
528
662
|
const next = this.state.selectedPage + (rtl ? -direction : direction);
|
|
529
663
|
if (direction === 0 || next < 0 || next >= this.pages().length) {
|
|
530
664
|
const wasHorizontal = this.horizontalTouch;
|
|
@@ -35,6 +35,8 @@ export type OnCollapsibleStateChangedEventData = Readonly<{
|
|
|
35
35
|
observedScrollableCount: Int32;
|
|
36
36
|
retainedPages: string;
|
|
37
37
|
reason: string;
|
|
38
|
+
pageKey: string;
|
|
39
|
+
headerSettledAtTop: boolean;
|
|
38
40
|
}>;
|
|
39
41
|
|
|
40
42
|
export type OnNativeTabPressEventData = Readonly<{
|
|
@@ -83,6 +85,11 @@ export interface NativeProps extends ViewProps {
|
|
|
83
85
|
type CollapsiblePagerViewNativeType = HostComponent<NativeProps>;
|
|
84
86
|
|
|
85
87
|
export interface NativeCommands {
|
|
88
|
+
scrollToTop: (
|
|
89
|
+
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
90
|
+
target: Int32,
|
|
91
|
+
animated: boolean
|
|
92
|
+
) => void;
|
|
86
93
|
setPage: (
|
|
87
94
|
viewRef: React.ElementRef<CollapsiblePagerViewNativeType>,
|
|
88
95
|
selectedPage: Int32
|
|
@@ -99,6 +106,7 @@ export interface NativeCommands {
|
|
|
99
106
|
|
|
100
107
|
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
101
108
|
supportedCommands: [
|
|
109
|
+
"scrollToTop",
|
|
102
110
|
"setPage",
|
|
103
111
|
"setPageWithoutAnimation",
|
|
104
112
|
"setScrollEnabledImperatively",
|