@onekeyfe/react-native-pager-view 6.7.0-1 → 6.7.0-12
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 +9 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +2 -2
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +2 -2
- package/docs/SPEC.md +108 -7
- package/ios/RNCCollapsiblePagerViewComponentView.mm +341 -23
- package/ios/RNCNativeScrollerComponentView.mm +30 -5
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +124 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +186 -1
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/ios/tests/RNCNativeScrollerTests.mm +39 -0
- package/lib/module/CollapsiblePagerView.js +16 -7
- package/lib/module/CollapsiblePagerView.web.js +428 -37
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/lib/module/NativeScroller.js +2 -2
- package/lib/module/NativeScroller.web.js +16 -3
- package/lib/module/NativeScrollerNativeComponent.ts +1 -1
- package/lib/module/ScrollEdgeEffect.js +8 -0
- package/lib/module/WebCollapsiblePagerPage.js +182 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.native.js +9 -0
- package/lib/module/index.web.js +3 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +8 -5
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +66 -4
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +1 -1
- package/lib/typescript/src/NativeScroller.d.ts +3 -2
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +1 -1
- package/lib/typescript/src/ScrollEdgeEffect.d.ts +5 -0
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +42 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.native.d.ts +13 -0
- package/lib/typescript/src/index.web.d.ts +5 -0
- package/package.json +6 -4
- package/src/CollapsiblePagerView.tsx +43 -11
- package/src/CollapsiblePagerView.web.tsx +640 -50
- package/src/CollapsiblePagerViewNativeComponent.ts +1 -1
- package/src/NativeScroller.tsx +6 -4
- package/src/NativeScroller.web.tsx +24 -2
- package/src/NativeScrollerNativeComponent.ts +1 -1
- package/src/ScrollEdgeEffect.ts +4 -0
- package/src/WebCollapsiblePagerPage.tsx +258 -0
- package/src/index.native.tsx +30 -0
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +6 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
1
2
|
import React from "react";
|
|
2
|
-
import { I18nManager, StyleSheet, View } from "react-native";
|
|
3
|
+
import { I18nManager, Platform, StyleSheet, View } from "react-native";
|
|
3
4
|
import type * as ReactNative from "react-native";
|
|
4
5
|
|
|
6
|
+
import { WebCollapsiblePagerPage } from "./WebCollapsiblePagerPage";
|
|
7
|
+
import type { WebCollapsiblePagerPageBridge } from "./WebCollapsiblePagerPage";
|
|
8
|
+
|
|
5
9
|
export type OnPageScrollEventData = Readonly<{
|
|
6
10
|
position: number;
|
|
7
11
|
offset: number;
|
|
@@ -64,10 +68,11 @@ export interface CollapsiblePagerViewProps
|
|
|
64
68
|
> {
|
|
65
69
|
header: React.ReactNode;
|
|
66
70
|
stickyHeader: React.ReactNode;
|
|
67
|
-
headerHeight
|
|
68
|
-
stickyHeaderHeight
|
|
71
|
+
headerHeight?: number;
|
|
72
|
+
stickyHeaderHeight?: number;
|
|
73
|
+
isActive?: boolean;
|
|
69
74
|
topOverlay?: React.ReactNode;
|
|
70
|
-
topScrollEdgeEffect?:
|
|
75
|
+
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
71
76
|
topOverlayColorScheme?: "light" | "dark";
|
|
72
77
|
externalTopOverlayTag?: number;
|
|
73
78
|
pageRetentionDistance?: number;
|
|
@@ -82,7 +87,9 @@ type State = {
|
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
type ConfiguredScrollElement = Readonly<{
|
|
90
|
+
key: string;
|
|
85
91
|
element: HTMLElement;
|
|
92
|
+
paddingTarget: HTMLElement;
|
|
86
93
|
rangeSpacer: HTMLElement;
|
|
87
94
|
position: string;
|
|
88
95
|
paddingTop: string;
|
|
@@ -94,6 +101,14 @@ type ConfiguredScrollElement = Readonly<{
|
|
|
94
101
|
pagerStickyInset: string;
|
|
95
102
|
}>;
|
|
96
103
|
|
|
104
|
+
type WebRouteTarget = Readonly<{
|
|
105
|
+
key: string;
|
|
106
|
+
identity: string;
|
|
107
|
+
owner: HTMLElement;
|
|
108
|
+
generation: number;
|
|
109
|
+
top: number;
|
|
110
|
+
}>;
|
|
111
|
+
|
|
97
112
|
const COLLAPSE_ANIMATION_NAME = "ok-collapsible-pager-collapse";
|
|
98
113
|
const COLLAPSE_STYLE_ID = "ok-collapsible-pager-styles";
|
|
99
114
|
const PAGER_HEADER_INSET = "--ok-collapsible-pager-header-inset";
|
|
@@ -123,6 +138,14 @@ const timelineStyle = (element: HTMLElement) =>
|
|
|
123
138
|
scrollTimelineAxis: string;
|
|
124
139
|
};
|
|
125
140
|
|
|
141
|
+
function scrollElementTo(element: HTMLElement, options: ScrollToOptions) {
|
|
142
|
+
// RN Web decorates ScrollView nodes with a React Native scrollTo signature.
|
|
143
|
+
const scrollTo: (options: ScrollToOptions) => void =
|
|
144
|
+
element.ownerDocument.defaultView?.Element.prototype.scrollTo ??
|
|
145
|
+
element.scrollTo;
|
|
146
|
+
scrollTo.call(element, options);
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
function ensureCollapseAnimation(document: Document) {
|
|
127
150
|
if (document.getElementById(COLLAPSE_STYLE_ID)) return;
|
|
128
151
|
const style = document.createElement("style");
|
|
@@ -131,6 +154,19 @@ function ensureCollapseAnimation(document: Document) {
|
|
|
131
154
|
document.head.appendChild(style);
|
|
132
155
|
}
|
|
133
156
|
|
|
157
|
+
function supportsScrollTimeline(document: Document) {
|
|
158
|
+
const css = document.defaultView?.CSS;
|
|
159
|
+
return !!(
|
|
160
|
+
typeof css?.supports === "function" &&
|
|
161
|
+
css.supports("animation-timeline", "--ok-test") &&
|
|
162
|
+
css.supports("timeline-scope", "--ok-test") &&
|
|
163
|
+
css.supports("scroll-timeline-name", "--ok-test") &&
|
|
164
|
+
css.supports("scroll-timeline-axis", "block") &&
|
|
165
|
+
css.supports("animation-range", "0px 1px") &&
|
|
166
|
+
css.supports("animation-duration", "auto")
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
134
170
|
const pageKey = (child: React.ReactNode, index: number) => {
|
|
135
171
|
if (React.isValidElement(child) && child.key != null)
|
|
136
172
|
return String(child.key);
|
|
@@ -152,11 +188,39 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
152
188
|
|
|
153
189
|
private root: HTMLElement | null = null;
|
|
154
190
|
private track: HTMLElement | null = null;
|
|
191
|
+
private headerSlot: HTMLElement | null = null;
|
|
192
|
+
private stickySlot: HTMLElement | null = null;
|
|
193
|
+
private cssTimelineSupported = false;
|
|
194
|
+
private overlayTouch: {
|
|
195
|
+
owner: HTMLElement;
|
|
196
|
+
target: HTMLElement;
|
|
197
|
+
x: number;
|
|
198
|
+
y: number;
|
|
199
|
+
lastY: number;
|
|
200
|
+
} | null = null;
|
|
155
201
|
private touchStartX: number | null = null;
|
|
156
202
|
private touchStartY: number | null = null;
|
|
157
203
|
private horizontalTouch = false;
|
|
158
204
|
private pageOffsets = new Map<string, number>();
|
|
159
205
|
private pageHosts = new Map<number, HTMLElement>();
|
|
206
|
+
private pageScrollIdentities = new Map<string, string>();
|
|
207
|
+
private pageBridges = new Map<string, WebCollapsiblePagerPageBridge>();
|
|
208
|
+
private ownerRecords = new Map<
|
|
209
|
+
string,
|
|
210
|
+
{ element: HTMLElement; identity: string; generation: number }
|
|
211
|
+
>();
|
|
212
|
+
private ownerGeneration = 0;
|
|
213
|
+
private lastRawTops = new Map<string, number>();
|
|
214
|
+
private routeTarget: WebRouteTarget | null = null;
|
|
215
|
+
private measuredHeaderHeight = 0;
|
|
216
|
+
private measuredStickyHeight = 0;
|
|
217
|
+
private headerMeasured = false;
|
|
218
|
+
private stickyMeasured = false;
|
|
219
|
+
private headerContent: HTMLElement | null = null;
|
|
220
|
+
private stickyContent: HTMLElement | null = null;
|
|
221
|
+
private chromeObserver: ResizeObserver | null = null;
|
|
222
|
+
private chromeMeasureFrame: number | null = null;
|
|
223
|
+
private disposed = false;
|
|
160
224
|
private sharedHeaderOffset = 0;
|
|
161
225
|
private selectionRestoreToken = 0;
|
|
162
226
|
private bodyAlignment: Readonly<{
|
|
@@ -177,14 +241,79 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
177
241
|
private configuredScrollElement: ConfiguredScrollElement | null = null;
|
|
178
242
|
private readonly timelineName = `--ok-collapsible-pager-${++collapsiblePagerWebInstance}`;
|
|
179
243
|
|
|
244
|
+
private get headerHeight() {
|
|
245
|
+
return this.hasExplicitHeaderHeight
|
|
246
|
+
? (this.props.headerHeight ?? this.measuredHeaderHeight)
|
|
247
|
+
: this.measuredHeaderHeight;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private get hasExplicitHeaderHeight() {
|
|
251
|
+
return this.props.headerHeight !== undefined &&
|
|
252
|
+
Number.isFinite(this.props.headerHeight) &&
|
|
253
|
+
this.props.headerHeight >= 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private get stickyHeaderHeight() {
|
|
257
|
+
return this.hasExplicitStickyHeaderHeight
|
|
258
|
+
? (this.props.stickyHeaderHeight ?? this.measuredStickyHeight)
|
|
259
|
+
: this.measuredStickyHeight;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private get hasExplicitStickyHeaderHeight() {
|
|
263
|
+
return this.props.stickyHeaderHeight !== undefined &&
|
|
264
|
+
Number.isFinite(this.props.stickyHeaderHeight) &&
|
|
265
|
+
this.props.stickyHeaderHeight >= 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getSnapshotBeforeUpdate(previousProps: CollapsiblePagerViewProps) {
|
|
269
|
+
if (previousProps.isActive !== false && this.props.isActive === false)
|
|
270
|
+
return this.captureRouteTarget(previousProps);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
180
274
|
componentDidMount() {
|
|
181
|
-
|
|
275
|
+
this.disposed = false;
|
|
276
|
+
if (Platform.OS !== "web" || typeof document === "undefined")
|
|
277
|
+
throw new Error("WebCollapsiblePagerView requires a Web DOM runtime");
|
|
278
|
+
if (!this.hasExplicitHeaderHeight) this.headerMeasured = false;
|
|
279
|
+
if (!this.hasExplicitStickyHeaderHeight) this.stickyMeasured = false;
|
|
280
|
+
this.observeChrome();
|
|
281
|
+
if (this.root) {
|
|
282
|
+
this.cssTimelineSupported = supportsScrollTimeline(
|
|
283
|
+
this.root.ownerDocument
|
|
284
|
+
);
|
|
285
|
+
if (this.cssTimelineSupported) {
|
|
286
|
+
ensureCollapseAnimation(this.root.ownerDocument);
|
|
287
|
+
this.forceUpdate();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
182
290
|
this.scheduleScrollConfiguration();
|
|
291
|
+
this.syncFallbackHeader();
|
|
183
292
|
this.notifyMountedPagesChanged();
|
|
184
293
|
this.emitDiagnostics("mounted");
|
|
185
294
|
}
|
|
186
295
|
|
|
187
|
-
componentDidUpdate(
|
|
296
|
+
componentDidUpdate(
|
|
297
|
+
previousProps: CollapsiblePagerViewProps,
|
|
298
|
+
_previousState: State,
|
|
299
|
+
routeSnapshot: WebRouteTarget | null,
|
|
300
|
+
) {
|
|
301
|
+
if (routeSnapshot) {
|
|
302
|
+
const record = this.ownerRecords.get(routeSnapshot.key);
|
|
303
|
+
if (
|
|
304
|
+
this.pages().map(pageKey).includes(routeSnapshot.key) &&
|
|
305
|
+
record?.element === routeSnapshot.owner &&
|
|
306
|
+
record.identity === routeSnapshot.identity &&
|
|
307
|
+
record.generation === routeSnapshot.generation
|
|
308
|
+
)
|
|
309
|
+
this.setRouteTarget(routeSnapshot);
|
|
310
|
+
}
|
|
311
|
+
if (previousProps.isActive === false && this.props.isActive !== false) {
|
|
312
|
+
if (!this.hasExplicitHeaderHeight) this.headerMeasured = false;
|
|
313
|
+
if (!this.hasExplicitStickyHeaderHeight) this.stickyMeasured = false;
|
|
314
|
+
this.scheduleChromeMeasurement();
|
|
315
|
+
this.restoreScrollPosition();
|
|
316
|
+
}
|
|
188
317
|
if (previousProps.scrollEnabled !== this.props.scrollEnabled) {
|
|
189
318
|
this.scrollEnabled = this.props.scrollEnabled ?? true;
|
|
190
319
|
if (!this.scrollEnabled) {
|
|
@@ -206,11 +335,37 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
206
335
|
});
|
|
207
336
|
return;
|
|
208
337
|
}
|
|
338
|
+
// Child layout effects can run before page-host refs are reattached.
|
|
339
|
+
// Recheck identities in the same commit, before a new user command runs.
|
|
340
|
+
this.mountedPages().forEach((index) =>
|
|
341
|
+
this.synchronizeScrollOwnerIdentity(index)
|
|
342
|
+
);
|
|
343
|
+
const currentKeys = new Set(this.pages().map(pageKey));
|
|
344
|
+
const ownedKeys = new Set([
|
|
345
|
+
...this.pageScrollIdentities.keys(),
|
|
346
|
+
...this.pageOffsets.keys(),
|
|
347
|
+
...this.lastRawTops.keys(),
|
|
348
|
+
...this.ownerRecords.keys(),
|
|
349
|
+
]);
|
|
350
|
+
for (const key of ownedKeys) {
|
|
351
|
+
if (!currentKeys.has(key)) {
|
|
352
|
+
this.pageScrollIdentities.delete(key);
|
|
353
|
+
this.pageOffsets.delete(key);
|
|
354
|
+
this.lastRawTops.delete(key);
|
|
355
|
+
this.ownerRecords.delete(key);
|
|
356
|
+
if (this.routeTarget?.key === key) this.setRouteTarget(null);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
209
359
|
this.scheduleScrollConfiguration();
|
|
360
|
+
this.syncFallbackHeader();
|
|
210
361
|
this.notifyMountedPagesChanged();
|
|
211
362
|
}
|
|
212
363
|
|
|
213
364
|
componentWillUnmount() {
|
|
365
|
+
// Activity can disconnect effects before the inactive update commits.
|
|
366
|
+
const routeTarget = this.captureRouteTarget(this.props);
|
|
367
|
+
if (routeTarget) this.setRouteTarget(routeTarget);
|
|
368
|
+
this.disposed = true;
|
|
214
369
|
this.selectionRestoreToken += 1;
|
|
215
370
|
this.cancelPendingTopFrame();
|
|
216
371
|
this.savePageOffset(this.state.selectedPage);
|
|
@@ -223,9 +378,196 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
223
378
|
this.transitionTimer = null;
|
|
224
379
|
}
|
|
225
380
|
this.setTrack(null);
|
|
381
|
+
this.setRoot(null);
|
|
382
|
+
this.chromeObserver?.disconnect();
|
|
383
|
+
this.chromeObserver = null;
|
|
384
|
+
if (this.chromeMeasureFrame !== null) {
|
|
385
|
+
cancelAnimationFrame(this.chromeMeasureFrame);
|
|
386
|
+
this.chromeMeasureFrame = null;
|
|
387
|
+
}
|
|
388
|
+
this.pageBridges.clear();
|
|
226
389
|
this.restoreConfiguredScrollElement();
|
|
227
390
|
}
|
|
228
391
|
|
|
392
|
+
private setHeaderContent = (node: unknown) => {
|
|
393
|
+
const element = webElement(node);
|
|
394
|
+
if (element === this.headerContent) return;
|
|
395
|
+
if (this.headerContent) this.chromeObserver?.unobserve(this.headerContent);
|
|
396
|
+
this.headerContent = element;
|
|
397
|
+
this.headerMeasured = false;
|
|
398
|
+
if (element) this.chromeObserver?.observe(element);
|
|
399
|
+
this.scheduleChromeMeasurement();
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
private setStickyContent = (node: unknown) => {
|
|
403
|
+
const element = webElement(node);
|
|
404
|
+
if (element === this.stickyContent) return;
|
|
405
|
+
if (this.stickyContent) this.chromeObserver?.unobserve(this.stickyContent);
|
|
406
|
+
this.stickyContent = element;
|
|
407
|
+
this.stickyMeasured = false;
|
|
408
|
+
if (element) this.chromeObserver?.observe(element);
|
|
409
|
+
this.scheduleChromeMeasurement();
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
private observeChrome() {
|
|
413
|
+
this.chromeObserver = new ResizeObserver(this.scheduleChromeMeasurement);
|
|
414
|
+
if (this.headerContent) this.chromeObserver.observe(this.headerContent);
|
|
415
|
+
if (this.stickyContent) this.chromeObserver.observe(this.stickyContent);
|
|
416
|
+
this.scheduleChromeMeasurement();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private scheduleChromeMeasurement = () => {
|
|
420
|
+
if (this.disposed || this.chromeMeasureFrame !== null || typeof requestAnimationFrame !== "function")
|
|
421
|
+
return;
|
|
422
|
+
this.chromeMeasureFrame = requestAnimationFrame(() => {
|
|
423
|
+
this.chromeMeasureFrame = null;
|
|
424
|
+
const header = this.headerContent;
|
|
425
|
+
const sticky = this.stickyContent;
|
|
426
|
+
const nextHeader = header?.isConnected
|
|
427
|
+
? Math.max(0, header.getBoundingClientRect().height)
|
|
428
|
+
: 0;
|
|
429
|
+
const nextSticky = sticky?.isConnected
|
|
430
|
+
? Math.max(0, sticky.getBoundingClientRect().height)
|
|
431
|
+
: 0;
|
|
432
|
+
const headerChanged =
|
|
433
|
+
this.measuredHeaderHeight !== nextHeader ||
|
|
434
|
+
this.headerMeasured !== Boolean(header?.isConnected);
|
|
435
|
+
const stickyChanged =
|
|
436
|
+
this.measuredStickyHeight !== nextSticky ||
|
|
437
|
+
this.stickyMeasured !== Boolean(sticky?.isConnected);
|
|
438
|
+
this.measuredHeaderHeight = nextHeader;
|
|
439
|
+
this.measuredStickyHeight = nextSticky;
|
|
440
|
+
this.headerMeasured = Boolean(header?.isConnected);
|
|
441
|
+
this.stickyMeasured = Boolean(sticky?.isConnected);
|
|
442
|
+
if (headerChanged || stickyChanged) {
|
|
443
|
+
this.forceUpdate();
|
|
444
|
+
this.scheduleScrollConfiguration();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
private onPageBridge = (key: string, bridge: WebCollapsiblePagerPageBridge | null) => {
|
|
450
|
+
if (bridge) {
|
|
451
|
+
this.pageBridges.set(key, bridge);
|
|
452
|
+
bridge.setPendingRouteScrollTop(
|
|
453
|
+
this.routeTarget?.key === key ? this.routeTarget.top : null,
|
|
454
|
+
);
|
|
455
|
+
const index = this.pages().map(pageKey).indexOf(key);
|
|
456
|
+
if (index >= 0) this.syncPageOwner(index);
|
|
457
|
+
} else {
|
|
458
|
+
this.pageBridges.delete(key);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
private setRouteTarget(target: WebRouteTarget | null) {
|
|
463
|
+
const oldKey = this.routeTarget?.key;
|
|
464
|
+
this.routeTarget = target;
|
|
465
|
+
if (oldKey) this.pageBridges.get(oldKey)?.setPendingRouteScrollTop(null);
|
|
466
|
+
if (target)
|
|
467
|
+
this.pageBridges.get(target.key)?.setPendingRouteScrollTop(target.top);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private syncPageOwner(index: number) {
|
|
471
|
+
const key = this.pages().map(pageKey)[index];
|
|
472
|
+
const element = this.pageScrollElement(index);
|
|
473
|
+
if (!key || !element) return;
|
|
474
|
+
const identity = element.getAttribute("data-collapsible-pager-scroll-key") ?? key;
|
|
475
|
+
const previous = this.ownerRecords.get(key);
|
|
476
|
+
if (previous?.element !== element || previous.identity !== identity) {
|
|
477
|
+
if (this.routeTarget?.key === key) this.setRouteTarget(null);
|
|
478
|
+
this.ownerRecords.set(key, {
|
|
479
|
+
element,
|
|
480
|
+
identity,
|
|
481
|
+
generation: ++this.ownerGeneration,
|
|
482
|
+
});
|
|
483
|
+
this.lastRawTops.delete(key);
|
|
484
|
+
}
|
|
485
|
+
this.pageBridges.get(key)?.setOwner(element);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private captureRouteTarget(previousProps: CollapsiblePagerViewProps) {
|
|
489
|
+
const index = this.state.selectedPage;
|
|
490
|
+
const oldKey = React.Children.toArray(previousProps.children).map(pageKey)[index];
|
|
491
|
+
const key = this.configuredScrollElement?.key ?? oldKey;
|
|
492
|
+
const record = key ? this.ownerRecords.get(key) : null;
|
|
493
|
+
if (!record?.element.isConnected) return null;
|
|
494
|
+
if (
|
|
495
|
+
this.routeTarget?.key === key &&
|
|
496
|
+
this.routeTarget.identity === record.identity &&
|
|
497
|
+
this.routeTarget.owner === record.element &&
|
|
498
|
+
this.routeTarget.generation === record.generation
|
|
499
|
+
)
|
|
500
|
+
return this.routeTarget;
|
|
501
|
+
const top =
|
|
502
|
+
record.element.clientHeight > 0
|
|
503
|
+
? record.element.scrollTop
|
|
504
|
+
: this.lastRawTops.get(key) ?? record.element.scrollTop;
|
|
505
|
+
return {
|
|
506
|
+
key,
|
|
507
|
+
identity: record.identity,
|
|
508
|
+
owner: record.element,
|
|
509
|
+
generation: record.generation,
|
|
510
|
+
top: Math.max(0, top),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
public restoreScrollPosition = () => {
|
|
515
|
+
if (this.disposed || this.props.isActive === false) return false;
|
|
516
|
+
const pending = this.routeTarget;
|
|
517
|
+
if (!pending) return true;
|
|
518
|
+
const key = this.pages().map(pageKey)[this.state.selectedPage];
|
|
519
|
+
if (key !== pending.key) return false;
|
|
520
|
+
this.syncPageOwner(this.state.selectedPage);
|
|
521
|
+
const record = this.ownerRecords.get(key);
|
|
522
|
+
if (
|
|
523
|
+
!record ||
|
|
524
|
+
record.element !== pending.owner ||
|
|
525
|
+
record.identity !== pending.identity ||
|
|
526
|
+
record.generation !== pending.generation
|
|
527
|
+
)
|
|
528
|
+
return false;
|
|
529
|
+
const element = record.element;
|
|
530
|
+
const configured = this.configuredScrollElement;
|
|
531
|
+
if (
|
|
532
|
+
!element.isConnected ||
|
|
533
|
+
element.clientHeight <= 0 ||
|
|
534
|
+
configured?.element !== element ||
|
|
535
|
+
configured.key !== key ||
|
|
536
|
+
(!this.hasExplicitHeaderHeight && !this.headerMeasured) ||
|
|
537
|
+
(!this.hasExplicitStickyHeaderHeight && !this.stickyMeasured) ||
|
|
538
|
+
element.getAttribute("data-collapsible-pager-active") !== "true" ||
|
|
539
|
+
element.style.getPropertyValue(PAGER_HEADER_INSET).trim() !==
|
|
540
|
+
`${this.headerHeight}px` ||
|
|
541
|
+
element.style.getPropertyValue(PAGER_STICKY_INSET).trim() !==
|
|
542
|
+
`${this.stickyHeaderHeight}px` ||
|
|
543
|
+
element.scrollHeight - element.clientHeight < pending.top
|
|
544
|
+
)
|
|
545
|
+
return false;
|
|
546
|
+
this.selectionRestoreToken += 1;
|
|
547
|
+
this.cancelPendingTopFrame();
|
|
548
|
+
this.setRouteTarget(null);
|
|
549
|
+
this.lastRawTops.set(key, pending.top);
|
|
550
|
+
const D = this.headerHeight;
|
|
551
|
+
this.sharedHeaderOffset = Math.min(pending.top, D);
|
|
552
|
+
this.pageOffsets.set(key, Math.max(0, pending.top - D));
|
|
553
|
+
this.bodyAlignment = { element, key };
|
|
554
|
+
scrollElementTo(element, { top: pending.top, behavior: "instant" });
|
|
555
|
+
this.syncFallbackHeader();
|
|
556
|
+
return true;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
private onPageScroll = (key: string, top: number) => {
|
|
560
|
+
if (this.props.isActive !== false) this.lastRawTops.set(key, top);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
private onPageGeometryChange = (key: string) => {
|
|
564
|
+
if (this.routeTarget?.key === key) this.restoreScrollPosition();
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
private onPageUserInput = (key: string) => {
|
|
568
|
+
if (this.routeTarget?.key === key) this.setRouteTarget(null);
|
|
569
|
+
};
|
|
570
|
+
|
|
229
571
|
public setPage = (selectedPage: number) => {
|
|
230
572
|
this.selectPage(selectedPage, true);
|
|
231
573
|
};
|
|
@@ -240,7 +582,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
240
582
|
|
|
241
583
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
242
584
|
this.scrollCurrentPageToTop(
|
|
243
|
-
|
|
585
|
+
this.headerHeight,
|
|
244
586
|
options?.animated ?? true
|
|
245
587
|
);
|
|
246
588
|
};
|
|
@@ -307,6 +649,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
307
649
|
const element = this.pageScrollElement(this.state.selectedPage);
|
|
308
650
|
const key = this.pages().map(pageKey)[this.state.selectedPage];
|
|
309
651
|
if (!element?.isConnected || !key) return;
|
|
652
|
+
this.setRouteTarget(null);
|
|
310
653
|
this.selectionRestoreToken += 1;
|
|
311
654
|
this.cancelPendingTopFrame();
|
|
312
655
|
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
@@ -314,7 +657,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
314
657
|
element.dispatchEvent(
|
|
315
658
|
new EventConstructor("ok-collapsible-pager-cancel-scroll-command")
|
|
316
659
|
);
|
|
317
|
-
element
|
|
660
|
+
scrollElementTo(element, {
|
|
318
661
|
left: element.scrollLeft,
|
|
319
662
|
top: element.scrollTop,
|
|
320
663
|
behavior: "instant",
|
|
@@ -333,10 +676,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
333
676
|
this.pageOffsets.set(key, 0);
|
|
334
677
|
this.sharedHeaderOffset = offset;
|
|
335
678
|
this.bodyAlignment = { element, key };
|
|
336
|
-
element
|
|
679
|
+
scrollElementTo(element, {
|
|
337
680
|
top: offset,
|
|
338
681
|
behavior: animated ? "smooth" : "instant",
|
|
339
682
|
});
|
|
683
|
+
this.syncFallbackHeader();
|
|
340
684
|
});
|
|
341
685
|
this.pendingTopFrame = { frame, element, key };
|
|
342
686
|
}
|
|
@@ -410,12 +754,186 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
410
754
|
private pageScrollElement(index: number) {
|
|
411
755
|
const pageHost = this.pageHosts.get(index);
|
|
412
756
|
return (
|
|
413
|
-
pageHost?.querySelector<HTMLElement>(
|
|
414
|
-
|
|
415
|
-
|
|
757
|
+
pageHost?.querySelector<HTMLElement>("[data-collapsible-pager-scroll]") ??
|
|
758
|
+
pageHost?.querySelector<HTMLElement>(".ok-native-list-viewport") ??
|
|
759
|
+
pageHost
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private setRoot = (node: unknown) => {
|
|
764
|
+
const root = webElement(node);
|
|
765
|
+
if (root === this.root) return;
|
|
766
|
+
this.root?.removeEventListener(
|
|
767
|
+
"ok-collapsible-pager-scroll-key-changed",
|
|
768
|
+
this.onScrollOwnerIdentityChanged
|
|
769
|
+
);
|
|
770
|
+
this.root?.removeEventListener("wheel", this.onOverlayWheel);
|
|
771
|
+
this.root?.removeEventListener("touchstart", this.onOverlayTouchStart);
|
|
772
|
+
this.root?.removeEventListener("touchmove", this.onOverlayTouchMove);
|
|
773
|
+
this.root?.removeEventListener("touchend", this.onOverlayTouchEnd);
|
|
774
|
+
this.root?.removeEventListener("touchcancel", this.onOverlayTouchEnd);
|
|
775
|
+
this.overlayTouch = null;
|
|
776
|
+
this.root = root;
|
|
777
|
+
root?.addEventListener(
|
|
778
|
+
"ok-collapsible-pager-scroll-key-changed",
|
|
779
|
+
this.onScrollOwnerIdentityChanged
|
|
416
780
|
);
|
|
781
|
+
root?.addEventListener("wheel", this.onOverlayWheel, { passive: false });
|
|
782
|
+
root?.addEventListener("touchstart", this.onOverlayTouchStart, {
|
|
783
|
+
passive: true,
|
|
784
|
+
});
|
|
785
|
+
root?.addEventListener("touchmove", this.onOverlayTouchMove, {
|
|
786
|
+
passive: false,
|
|
787
|
+
});
|
|
788
|
+
root?.addEventListener("touchend", this.onOverlayTouchEnd);
|
|
789
|
+
root?.addEventListener("touchcancel", this.onOverlayTouchEnd);
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
private overlayTarget(event: Event) {
|
|
793
|
+
const target = event.target as HTMLElement | null;
|
|
794
|
+
if (
|
|
795
|
+
!target ||
|
|
796
|
+
(!this.headerSlot?.contains(target) &&
|
|
797
|
+
!this.stickySlot?.contains(target)) ||
|
|
798
|
+
target.closest("input,textarea,select,[contenteditable=true]")
|
|
799
|
+
)
|
|
800
|
+
return null;
|
|
801
|
+
return target;
|
|
417
802
|
}
|
|
418
803
|
|
|
804
|
+
private nestedScrollConsumes(target: HTMLElement, delta: number) {
|
|
805
|
+
for (
|
|
806
|
+
let node: HTMLElement | null = target;
|
|
807
|
+
node && node !== this.root;
|
|
808
|
+
node = node.parentElement
|
|
809
|
+
) {
|
|
810
|
+
const style = node.ownerDocument.defaultView?.getComputedStyle(node);
|
|
811
|
+
if (
|
|
812
|
+
/auto|scroll/.test(style?.overflowY ?? "") &&
|
|
813
|
+
node.scrollHeight > node.clientHeight &&
|
|
814
|
+
(delta < 0
|
|
815
|
+
? node.scrollTop > 0
|
|
816
|
+
: node.scrollTop < node.scrollHeight - node.clientHeight)
|
|
817
|
+
)
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private scrollOverlayOwner(owner: HTMLElement, delta: number, event: Event) {
|
|
824
|
+
if (!event.cancelable) return;
|
|
825
|
+
const EventConstructor = owner.ownerDocument.defaultView?.Event;
|
|
826
|
+
if (EventConstructor)
|
|
827
|
+
owner.dispatchEvent(
|
|
828
|
+
new EventConstructor("ok-collapsible-pager-user-scroll")
|
|
829
|
+
);
|
|
830
|
+
const top = Math.min(
|
|
831
|
+
Math.max(0, owner.scrollTop + delta),
|
|
832
|
+
owner.scrollHeight - owner.clientHeight
|
|
833
|
+
);
|
|
834
|
+
if (top === owner.scrollTop) return;
|
|
835
|
+
this.takeOverVerticalScroll();
|
|
836
|
+
scrollElementTo(owner, { top, behavior: "instant" });
|
|
837
|
+
event.preventDefault();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private onOverlayWheel = (event: WheelEvent) => {
|
|
841
|
+
const target = this.overlayTarget(event);
|
|
842
|
+
const owner = this.pageScrollElement(this.state.selectedPage);
|
|
843
|
+
if (
|
|
844
|
+
!target ||
|
|
845
|
+
!owner ||
|
|
846
|
+
event.defaultPrevented ||
|
|
847
|
+
event.ctrlKey ||
|
|
848
|
+
Math.abs(event.deltaX) > Math.abs(event.deltaY)
|
|
849
|
+
)
|
|
850
|
+
return;
|
|
851
|
+
const lineHeight =
|
|
852
|
+
Number.parseFloat(
|
|
853
|
+
target.ownerDocument.defaultView?.getComputedStyle(target).lineHeight ??
|
|
854
|
+
""
|
|
855
|
+
) || 16;
|
|
856
|
+
const delta =
|
|
857
|
+
event.deltaY *
|
|
858
|
+
(event.deltaMode === 1
|
|
859
|
+
? lineHeight
|
|
860
|
+
: event.deltaMode === 2
|
|
861
|
+
? owner.clientHeight
|
|
862
|
+
: 1);
|
|
863
|
+
if (!this.nestedScrollConsumes(target, delta))
|
|
864
|
+
this.scrollOverlayOwner(owner, delta, event);
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
private onOverlayTouchStart = (event: TouchEvent) => {
|
|
868
|
+
const target = this.overlayTarget(event);
|
|
869
|
+
const owner = this.pageScrollElement(this.state.selectedPage);
|
|
870
|
+
const touch = event.touches.length === 1 ? event.touches[0] : null;
|
|
871
|
+
this.overlayTouch =
|
|
872
|
+
target && owner && touch
|
|
873
|
+
? {
|
|
874
|
+
owner,
|
|
875
|
+
target,
|
|
876
|
+
x: touch.clientX,
|
|
877
|
+
y: touch.clientY,
|
|
878
|
+
lastY: touch.clientY,
|
|
879
|
+
}
|
|
880
|
+
: null;
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
private onOverlayTouchMove = (event: TouchEvent) => {
|
|
884
|
+
if (event.defaultPrevented) return;
|
|
885
|
+
const pending = this.overlayTouch;
|
|
886
|
+
const touch = event.touches.length === 1 ? event.touches[0] : null;
|
|
887
|
+
if (
|
|
888
|
+
!pending ||
|
|
889
|
+
!touch ||
|
|
890
|
+
pending.owner !== this.pageScrollElement(this.state.selectedPage)
|
|
891
|
+
) {
|
|
892
|
+
this.overlayTouch = null;
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const delta = pending.lastY - touch.clientY;
|
|
896
|
+
pending.lastY = touch.clientY;
|
|
897
|
+
if (
|
|
898
|
+
Math.abs(touch.clientY - pending.y) < 8 ||
|
|
899
|
+
Math.abs(touch.clientX - pending.x) >= Math.abs(touch.clientY - pending.y)
|
|
900
|
+
)
|
|
901
|
+
return;
|
|
902
|
+
if (!this.nestedScrollConsumes(pending.target, delta))
|
|
903
|
+
this.scrollOverlayOwner(pending.owner, delta, event);
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
private onOverlayTouchEnd = () => {
|
|
907
|
+
this.overlayTouch = null;
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
private synchronizeScrollOwnerIdentity(index: number) {
|
|
911
|
+
const key = this.pages().map(pageKey)[index];
|
|
912
|
+
const element = this.pageScrollElement(index);
|
|
913
|
+
if (!key || !element) return;
|
|
914
|
+
const identity =
|
|
915
|
+
element.getAttribute("data-collapsible-pager-scroll-key") ?? key;
|
|
916
|
+
const previous = this.pageScrollIdentities.get(key);
|
|
917
|
+
this.pageScrollIdentities.set(key, identity);
|
|
918
|
+
this.syncPageOwner(index);
|
|
919
|
+
if (previous === undefined || previous === identity) return;
|
|
920
|
+
if (index === this.state.selectedPage) {
|
|
921
|
+
this.selectionRestoreToken += 1;
|
|
922
|
+
this.cancelPendingTopFrame();
|
|
923
|
+
this.sharedHeaderOffset = 0;
|
|
924
|
+
this.bodyAlignment = null;
|
|
925
|
+
scrollElementTo(element, { top: 0, behavior: "instant" });
|
|
926
|
+
}
|
|
927
|
+
this.pageOffsets.delete(key);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private onScrollOwnerIdentityChanged = (event: Event) => {
|
|
931
|
+
this.pages().forEach((_, index) => {
|
|
932
|
+
if (event.target === this.pageScrollElement(index))
|
|
933
|
+
this.synchronizeScrollOwnerIdentity(index);
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
|
|
419
937
|
private notifyInsetsChanged(element: HTMLElement) {
|
|
420
938
|
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
421
939
|
if (EventConstructor) {
|
|
@@ -443,10 +961,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
443
961
|
"scrollend",
|
|
444
962
|
this.onVerticalScrollSettled
|
|
445
963
|
);
|
|
964
|
+
configured.element.removeEventListener("scroll", this.syncFallbackHeader);
|
|
446
965
|
const style = timelineStyle(configured.element);
|
|
447
966
|
configured.rangeSpacer.remove();
|
|
448
967
|
style.position = configured.position;
|
|
449
|
-
style.paddingTop = configured.paddingTop;
|
|
968
|
+
configured.paddingTarget.style.paddingTop = configured.paddingTop;
|
|
450
969
|
style.boxSizing = configured.boxSizing;
|
|
451
970
|
style.scrollTimelineName = configured.scrollTimelineName;
|
|
452
971
|
style.scrollTimelineAxis = configured.scrollTimelineAxis;
|
|
@@ -455,6 +974,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
455
974
|
configured.element.removeAttribute("data-collapsible-pager-active");
|
|
456
975
|
this.notifyInsetsChanged(configured.element);
|
|
457
976
|
this.configuredScrollElement = null;
|
|
977
|
+
this.syncFallbackHeader();
|
|
458
978
|
if (this.bodyAlignment?.element === configured.element)
|
|
459
979
|
this.bodyAlignment = null;
|
|
460
980
|
}
|
|
@@ -463,13 +983,19 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
463
983
|
this.configureFrame = null;
|
|
464
984
|
const element = this.pageScrollElement(this.state.selectedPage);
|
|
465
985
|
if (!element) return;
|
|
986
|
+
this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
|
|
466
987
|
if (this.configuredScrollElement?.element !== element) {
|
|
467
988
|
this.restoreConfiguredScrollElement();
|
|
468
989
|
const style = timelineStyle(element);
|
|
990
|
+
const paddingTarget = element.hasAttribute(
|
|
991
|
+
"data-collapsible-pager-scroll"
|
|
992
|
+
)
|
|
993
|
+
? webElement(element.firstElementChild) ?? element
|
|
994
|
+
: element;
|
|
469
995
|
const computedPaddingTop =
|
|
470
|
-
element.ownerDocument.defaultView?.getComputedStyle(
|
|
996
|
+
element.ownerDocument.defaultView?.getComputedStyle(paddingTarget)
|
|
471
997
|
.paddingTop ||
|
|
472
|
-
style.paddingTop ||
|
|
998
|
+
paddingTarget.style.paddingTop ||
|
|
473
999
|
"0px";
|
|
474
1000
|
const rangeSpacer = element.ownerDocument.createElement("div");
|
|
475
1001
|
Object.assign(rangeSpacer.style, {
|
|
@@ -489,10 +1015,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
489
1015
|
style.position = "relative";
|
|
490
1016
|
element.appendChild(rangeSpacer);
|
|
491
1017
|
this.configuredScrollElement = {
|
|
1018
|
+
key: this.pages().map(pageKey)[this.state.selectedPage],
|
|
492
1019
|
element,
|
|
1020
|
+
paddingTarget,
|
|
493
1021
|
rangeSpacer,
|
|
494
1022
|
position,
|
|
495
|
-
paddingTop: style.paddingTop,
|
|
1023
|
+
paddingTop: paddingTarget.style.paddingTop,
|
|
496
1024
|
basePaddingTop: computedPaddingTop,
|
|
497
1025
|
boxSizing: style.boxSizing,
|
|
498
1026
|
scrollTimelineName: style.scrollTimelineName,
|
|
@@ -508,11 +1036,17 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
508
1036
|
element.addEventListener("wheel", this.onQueuedTopWheel);
|
|
509
1037
|
element.addEventListener("pointerdown", this.onScrollbarPointerDown);
|
|
510
1038
|
element.addEventListener("keydown", this.onQueuedTopKey);
|
|
1039
|
+
if (!this.cssTimelineSupported)
|
|
1040
|
+
element.addEventListener("scroll", this.syncFallbackHeader, {
|
|
1041
|
+
passive: true,
|
|
1042
|
+
});
|
|
511
1043
|
}
|
|
512
1044
|
|
|
513
1045
|
const style = timelineStyle(element);
|
|
514
|
-
const
|
|
515
|
-
|
|
1046
|
+
const paddingStyle =
|
|
1047
|
+
this.configuredScrollElement?.paddingTarget.style ?? style;
|
|
1048
|
+
const headerInset = this.headerHeight;
|
|
1049
|
+
const stickyInset = this.stickyHeaderHeight;
|
|
516
1050
|
if (this.configuredScrollElement)
|
|
517
1051
|
this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
|
|
518
1052
|
const basePaddingTop =
|
|
@@ -523,10 +1057,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
523
1057
|
const headerInsetValue = `${headerInset}px`;
|
|
524
1058
|
const stickyInsetValue = `${stickyInset}px`;
|
|
525
1059
|
const insetsChanged =
|
|
526
|
-
|
|
1060
|
+
paddingStyle.paddingTop !== paddingTop ||
|
|
527
1061
|
style.getPropertyValue(PAGER_HEADER_INSET) !== headerInsetValue ||
|
|
528
1062
|
style.getPropertyValue(PAGER_STICKY_INSET) !== stickyInsetValue;
|
|
529
|
-
|
|
1063
|
+
paddingStyle.paddingTop = paddingTop;
|
|
530
1064
|
style.boxSizing = "border-box";
|
|
531
1065
|
style.scrollTimelineName = this.timelineName;
|
|
532
1066
|
style.scrollTimelineAxis = "block";
|
|
@@ -534,10 +1068,25 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
534
1068
|
style.setProperty(PAGER_STICKY_INSET, stickyInsetValue);
|
|
535
1069
|
element.setAttribute("data-collapsible-pager-active", "true");
|
|
536
1070
|
if (insetsChanged) this.notifyInsetsChanged(element);
|
|
1071
|
+
this.syncFallbackHeader();
|
|
1072
|
+
this.pageBridges.get(this.pages().map(pageKey)[this.state.selectedPage])?.requestMeasure();
|
|
1073
|
+
this.restoreScrollPosition();
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
private syncFallbackHeader = () => {
|
|
1077
|
+
if (this.cssTimelineSupported) return;
|
|
1078
|
+
const top = this.configuredScrollElement?.element.scrollTop ?? 0;
|
|
1079
|
+
const collapse = Math.min(
|
|
1080
|
+
Math.max(top, 0),
|
|
1081
|
+
this.headerHeight
|
|
1082
|
+
);
|
|
1083
|
+
const transform = `translateY(${-collapse}px)`;
|
|
1084
|
+
if (this.headerSlot) this.headerSlot.style.transform = transform;
|
|
1085
|
+
if (this.stickySlot) this.stickySlot.style.transform = transform;
|
|
537
1086
|
};
|
|
538
1087
|
|
|
539
1088
|
private scheduleScrollConfiguration() {
|
|
540
|
-
if (this.configureFrame !== null) return;
|
|
1089
|
+
if (this.disposed || this.configureFrame !== null) return;
|
|
541
1090
|
if (typeof requestAnimationFrame === "function") {
|
|
542
1091
|
this.configureFrame = requestAnimationFrame(
|
|
543
1092
|
this.configureActiveScrollElement
|
|
@@ -548,6 +1097,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
548
1097
|
}
|
|
549
1098
|
|
|
550
1099
|
private takeOverVerticalScroll = () => {
|
|
1100
|
+
this.setRouteTarget(null);
|
|
551
1101
|
this.selectionRestoreToken += 1;
|
|
552
1102
|
this.bodyAlignment = null;
|
|
553
1103
|
this.cancelPendingTopFrame();
|
|
@@ -572,15 +1122,18 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
572
1122
|
};
|
|
573
1123
|
|
|
574
1124
|
private savePageOffset(index: number) {
|
|
575
|
-
const
|
|
576
|
-
|
|
1125
|
+
const configured =
|
|
1126
|
+
index === this.state.selectedPage ? this.configuredScrollElement : null;
|
|
1127
|
+
const key = configured?.key ?? this.pages().map(pageKey)[index];
|
|
1128
|
+
const scrollElement = configured?.element ?? this.pageScrollElement(index);
|
|
1129
|
+
if (configured && !scrollElement?.isConnected) return;
|
|
577
1130
|
if (key && scrollElement) {
|
|
578
1131
|
if (
|
|
579
1132
|
this.bodyAlignment?.key === key &&
|
|
580
1133
|
this.bodyAlignment.element === scrollElement
|
|
581
1134
|
)
|
|
582
1135
|
return;
|
|
583
|
-
const D =
|
|
1136
|
+
const D = this.headerHeight;
|
|
584
1137
|
this.pageOffsets.set(key, Math.max(0, scrollElement.scrollTop - D));
|
|
585
1138
|
this.sharedHeaderOffset = Math.min(
|
|
586
1139
|
Math.max(scrollElement.scrollTop, 0),
|
|
@@ -593,11 +1146,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
593
1146
|
const key = this.pages().map(pageKey)[index];
|
|
594
1147
|
const scrollElement = this.pageScrollElement(index);
|
|
595
1148
|
if (!key || !scrollElement) return;
|
|
596
|
-
const D =
|
|
1149
|
+
const D = this.headerHeight;
|
|
597
1150
|
const C = Math.min(Math.max(0, this.sharedHeaderOffset), D);
|
|
598
1151
|
const expectedQ = C + (C === D ? this.pageOffsets.get(key) ?? 0 : 0);
|
|
599
1152
|
this.bodyAlignment = { element: scrollElement, key };
|
|
600
1153
|
scrollElement.scrollTop = expectedQ;
|
|
1154
|
+
this.syncFallbackHeader();
|
|
601
1155
|
}
|
|
602
1156
|
|
|
603
1157
|
private selectPage(selectedPage: number, animated: boolean) {
|
|
@@ -611,8 +1165,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
611
1165
|
) {
|
|
612
1166
|
return;
|
|
613
1167
|
}
|
|
1168
|
+
this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
|
|
614
1169
|
const selectionToken = ++this.selectionRestoreToken;
|
|
615
1170
|
const expectedKey = this.pages().map(pageKey)[position];
|
|
1171
|
+
if (this.routeTarget && this.routeTarget.key !== expectedKey)
|
|
1172
|
+
this.setRouteTarget(null);
|
|
616
1173
|
this.cancelPendingTopFrame();
|
|
617
1174
|
this.savePageOffset(this.state.selectedPage);
|
|
618
1175
|
|
|
@@ -631,6 +1188,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
631
1188
|
)
|
|
632
1189
|
return;
|
|
633
1190
|
this.configureActiveScrollElement();
|
|
1191
|
+
if (
|
|
1192
|
+
this.selectionRestoreToken !== selectionToken ||
|
|
1193
|
+
this.routeTarget?.key === expectedKey
|
|
1194
|
+
) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
634
1197
|
this.restorePageOffset(position);
|
|
635
1198
|
};
|
|
636
1199
|
if (typeof requestAnimationFrame === "function") {
|
|
@@ -678,7 +1241,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
678
1241
|
this.pageScrollElement(this.state.selectedPage)?.scrollTop ?? 0,
|
|
679
1242
|
0
|
|
680
1243
|
),
|
|
681
|
-
|
|
1244
|
+
this.headerHeight
|
|
682
1245
|
),
|
|
683
1246
|
nativePageCount: pages.length,
|
|
684
1247
|
attachedPageCount: retainedPages.length,
|
|
@@ -769,6 +1332,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
769
1332
|
externalTopOverlayTag: _externalTopOverlayTag,
|
|
770
1333
|
headerHeight: _headerHeight,
|
|
771
1334
|
stickyHeaderHeight: _stickyHeaderHeight,
|
|
1335
|
+
isActive: _isActive,
|
|
772
1336
|
pageRetentionDistance: _pageRetentionDistance,
|
|
773
1337
|
layoutDirection: _layoutDirection,
|
|
774
1338
|
scrollEnabled: _scrollEnabled,
|
|
@@ -786,18 +1350,20 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
786
1350
|
const keys = pages.map(pageKey);
|
|
787
1351
|
const retained = new Set(this.mountedPages(pages.length));
|
|
788
1352
|
const pageWidth = pages.length > 0 ? `${100 / pages.length}%` : "100%";
|
|
789
|
-
const collapseAnimation =
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1353
|
+
const collapseAnimation = this.cssTimelineSupported
|
|
1354
|
+
? ({
|
|
1355
|
+
"--ok-collapsible-header-height": `${Math.max(
|
|
1356
|
+
0,
|
|
1357
|
+
this.headerHeight
|
|
1358
|
+
)}px`,
|
|
1359
|
+
animationName: COLLAPSE_ANIMATION_NAME,
|
|
1360
|
+
animationDuration: "auto",
|
|
1361
|
+
animationFillMode: "both",
|
|
1362
|
+
animationTimingFunction: "linear",
|
|
1363
|
+
animationTimeline: this.timelineName,
|
|
1364
|
+
animationRange: `0px ${Math.max(1, this.headerHeight)}px`,
|
|
1365
|
+
} as never)
|
|
1366
|
+
: undefined;
|
|
801
1367
|
const trackStyle = [
|
|
802
1368
|
styles.track,
|
|
803
1369
|
{
|
|
@@ -817,35 +1383,45 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
817
1383
|
return (
|
|
818
1384
|
<View
|
|
819
1385
|
{...viewProps}
|
|
820
|
-
ref={
|
|
821
|
-
this.root = webElement(node);
|
|
822
|
-
}}
|
|
1386
|
+
ref={this.setRoot}
|
|
823
1387
|
style={[
|
|
824
1388
|
styles.root,
|
|
825
|
-
|
|
1389
|
+
this.cssTimelineSupported
|
|
1390
|
+
? ({ timelineScope: this.timelineName } as never)
|
|
1391
|
+
: undefined,
|
|
826
1392
|
style,
|
|
827
1393
|
]}
|
|
828
1394
|
>
|
|
829
1395
|
<View
|
|
1396
|
+
ref={(node) => {
|
|
1397
|
+
this.headerSlot = webElement(node);
|
|
1398
|
+
}}
|
|
830
1399
|
style={[
|
|
831
1400
|
styles.header,
|
|
832
|
-
{ height:
|
|
1401
|
+
{ height: this.headerHeight },
|
|
833
1402
|
collapseAnimation,
|
|
834
1403
|
]}
|
|
835
1404
|
>
|
|
836
|
-
{header
|
|
1405
|
+
{this.hasExplicitHeaderHeight ? header : (
|
|
1406
|
+
<View ref={this.setHeaderContent}>{header}</View>
|
|
1407
|
+
)}
|
|
837
1408
|
</View>
|
|
838
1409
|
<View
|
|
1410
|
+
ref={(node) => {
|
|
1411
|
+
this.stickySlot = webElement(node);
|
|
1412
|
+
}}
|
|
839
1413
|
style={[
|
|
840
1414
|
styles.sticky,
|
|
841
1415
|
{
|
|
842
|
-
top:
|
|
843
|
-
height:
|
|
1416
|
+
top: this.headerHeight,
|
|
1417
|
+
height: this.stickyHeaderHeight,
|
|
844
1418
|
},
|
|
845
1419
|
collapseAnimation,
|
|
846
1420
|
]}
|
|
847
1421
|
>
|
|
848
|
-
{stickyHeader
|
|
1422
|
+
{this.hasExplicitStickyHeaderHeight ? stickyHeader : (
|
|
1423
|
+
<View ref={this.setStickyContent}>{stickyHeader}</View>
|
|
1424
|
+
)}
|
|
849
1425
|
</View>
|
|
850
1426
|
<View
|
|
851
1427
|
ref={this.setTrack}
|
|
@@ -870,7 +1446,21 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
870
1446
|
style={[styles.page, { width: pageWidth }]}
|
|
871
1447
|
aria-hidden={index !== this.state.selectedPage}
|
|
872
1448
|
>
|
|
873
|
-
{retained.has(index) ?
|
|
1449
|
+
{retained.has(index) ? (
|
|
1450
|
+
<WebCollapsiblePagerPage
|
|
1451
|
+
pageKey={keys[index]}
|
|
1452
|
+
isFocused={index === this.state.selectedPage}
|
|
1453
|
+
isRouteActive={this.props.isActive !== false}
|
|
1454
|
+
headerHeight={this.headerHeight}
|
|
1455
|
+
stickyHeaderHeight={this.stickyHeaderHeight}
|
|
1456
|
+
onBridge={this.onPageBridge}
|
|
1457
|
+
onGeometryChange={this.onPageGeometryChange}
|
|
1458
|
+
onScroll={this.onPageScroll}
|
|
1459
|
+
onUserInput={this.onPageUserInput}
|
|
1460
|
+
>
|
|
1461
|
+
{page}
|
|
1462
|
+
</WebCollapsiblePagerPage>
|
|
1463
|
+
) : null}
|
|
874
1464
|
</View>
|
|
875
1465
|
))}
|
|
876
1466
|
</View>
|
|
@@ -882,7 +1472,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
882
1472
|
const styles = StyleSheet.create({
|
|
883
1473
|
root: {
|
|
884
1474
|
flex: 1,
|
|
885
|
-
overflow: "
|
|
1475
|
+
overflow: "clip",
|
|
886
1476
|
position: "relative",
|
|
887
1477
|
} as never,
|
|
888
1478
|
header: {
|
|
@@ -898,7 +1488,7 @@ const styles = StyleSheet.create({
|
|
|
898
1488
|
left: 0,
|
|
899
1489
|
right: 0,
|
|
900
1490
|
zIndex: 4,
|
|
901
|
-
overflow: "
|
|
1491
|
+
overflow: "visible",
|
|
902
1492
|
} as never,
|
|
903
1493
|
track: {
|
|
904
1494
|
position: "absolute",
|