@onekeyfe/react-native-pager-view 6.7.0-alpha.327 → 6.7.0-alpha.336
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/docs/SPEC.md +82 -8
- package/ios/RNCCollapsiblePagerViewComponentView.mm +257 -2
- package/ios/RNCNativeScrollerComponentView.mm +24 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +124 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +160 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/lib/module/CollapsiblePagerView.js +12 -3
- package/lib/module/CollapsiblePagerView.web.js +428 -37
- package/lib/module/NativeScroller.web.js +16 -3
- package/lib/module/WebCollapsiblePagerPage.js +182 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.native.js +9 -0
- package/lib/module/index.web.js +2 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +4 -2
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +64 -3
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +42 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.native.d.ts +13 -0
- package/lib/typescript/src/index.web.d.ts +4 -0
- package/package.json +6 -4
- package/src/CollapsiblePagerView.tsx +31 -4
- package/src/CollapsiblePagerView.web.tsx +638 -49
- package/src/NativeScroller.web.tsx +24 -2
- package/src/WebCollapsiblePagerPage.tsx +258 -0
- package/src/index.native.tsx +30 -0
- package/src/index.tsx +4 -0
- package/src/index.web.tsx +4 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ScrollEdgeEffect } from "./ScrollEdgeEffect";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { I18nManager, StyleSheet, View } from "react-native";
|
|
3
|
+
import { I18nManager, Platform, StyleSheet, View } from "react-native";
|
|
4
4
|
import type * as ReactNative from "react-native";
|
|
5
5
|
|
|
6
|
+
import { WebCollapsiblePagerPage } from "./WebCollapsiblePagerPage";
|
|
7
|
+
import type { WebCollapsiblePagerPageBridge } from "./WebCollapsiblePagerPage";
|
|
8
|
+
|
|
6
9
|
export type OnPageScrollEventData = Readonly<{
|
|
7
10
|
position: number;
|
|
8
11
|
offset: number;
|
|
@@ -65,8 +68,9 @@ export interface CollapsiblePagerViewProps
|
|
|
65
68
|
> {
|
|
66
69
|
header: React.ReactNode;
|
|
67
70
|
stickyHeader: React.ReactNode;
|
|
68
|
-
headerHeight
|
|
69
|
-
stickyHeaderHeight
|
|
71
|
+
headerHeight?: number;
|
|
72
|
+
stickyHeaderHeight?: number;
|
|
73
|
+
isActive?: boolean;
|
|
70
74
|
topOverlay?: React.ReactNode;
|
|
71
75
|
topScrollEdgeEffect?: `${ScrollEdgeEffect}`;
|
|
72
76
|
topOverlayColorScheme?: "light" | "dark";
|
|
@@ -83,7 +87,9 @@ type State = {
|
|
|
83
87
|
};
|
|
84
88
|
|
|
85
89
|
type ConfiguredScrollElement = Readonly<{
|
|
90
|
+
key: string;
|
|
86
91
|
element: HTMLElement;
|
|
92
|
+
paddingTarget: HTMLElement;
|
|
87
93
|
rangeSpacer: HTMLElement;
|
|
88
94
|
position: string;
|
|
89
95
|
paddingTop: string;
|
|
@@ -95,6 +101,14 @@ type ConfiguredScrollElement = Readonly<{
|
|
|
95
101
|
pagerStickyInset: string;
|
|
96
102
|
}>;
|
|
97
103
|
|
|
104
|
+
type WebRouteTarget = Readonly<{
|
|
105
|
+
key: string;
|
|
106
|
+
identity: string;
|
|
107
|
+
owner: HTMLElement;
|
|
108
|
+
generation: number;
|
|
109
|
+
top: number;
|
|
110
|
+
}>;
|
|
111
|
+
|
|
98
112
|
const COLLAPSE_ANIMATION_NAME = "ok-collapsible-pager-collapse";
|
|
99
113
|
const COLLAPSE_STYLE_ID = "ok-collapsible-pager-styles";
|
|
100
114
|
const PAGER_HEADER_INSET = "--ok-collapsible-pager-header-inset";
|
|
@@ -124,6 +138,14 @@ const timelineStyle = (element: HTMLElement) =>
|
|
|
124
138
|
scrollTimelineAxis: string;
|
|
125
139
|
};
|
|
126
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
|
+
|
|
127
149
|
function ensureCollapseAnimation(document: Document) {
|
|
128
150
|
if (document.getElementById(COLLAPSE_STYLE_ID)) return;
|
|
129
151
|
const style = document.createElement("style");
|
|
@@ -132,6 +154,19 @@ function ensureCollapseAnimation(document: Document) {
|
|
|
132
154
|
document.head.appendChild(style);
|
|
133
155
|
}
|
|
134
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
|
+
|
|
135
170
|
const pageKey = (child: React.ReactNode, index: number) => {
|
|
136
171
|
if (React.isValidElement(child) && child.key != null)
|
|
137
172
|
return String(child.key);
|
|
@@ -153,11 +188,39 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
153
188
|
|
|
154
189
|
private root: HTMLElement | null = null;
|
|
155
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;
|
|
156
201
|
private touchStartX: number | null = null;
|
|
157
202
|
private touchStartY: number | null = null;
|
|
158
203
|
private horizontalTouch = false;
|
|
159
204
|
private pageOffsets = new Map<string, number>();
|
|
160
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;
|
|
161
224
|
private sharedHeaderOffset = 0;
|
|
162
225
|
private selectionRestoreToken = 0;
|
|
163
226
|
private bodyAlignment: Readonly<{
|
|
@@ -178,14 +241,79 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
178
241
|
private configuredScrollElement: ConfiguredScrollElement | null = null;
|
|
179
242
|
private readonly timelineName = `--ok-collapsible-pager-${++collapsiblePagerWebInstance}`;
|
|
180
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
|
+
|
|
181
274
|
componentDidMount() {
|
|
182
|
-
|
|
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
|
+
}
|
|
183
290
|
this.scheduleScrollConfiguration();
|
|
291
|
+
this.syncFallbackHeader();
|
|
184
292
|
this.notifyMountedPagesChanged();
|
|
185
293
|
this.emitDiagnostics("mounted");
|
|
186
294
|
}
|
|
187
295
|
|
|
188
|
-
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
|
+
}
|
|
189
317
|
if (previousProps.scrollEnabled !== this.props.scrollEnabled) {
|
|
190
318
|
this.scrollEnabled = this.props.scrollEnabled ?? true;
|
|
191
319
|
if (!this.scrollEnabled) {
|
|
@@ -207,11 +335,37 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
207
335
|
});
|
|
208
336
|
return;
|
|
209
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
|
+
}
|
|
210
359
|
this.scheduleScrollConfiguration();
|
|
360
|
+
this.syncFallbackHeader();
|
|
211
361
|
this.notifyMountedPagesChanged();
|
|
212
362
|
}
|
|
213
363
|
|
|
214
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;
|
|
215
369
|
this.selectionRestoreToken += 1;
|
|
216
370
|
this.cancelPendingTopFrame();
|
|
217
371
|
this.savePageOffset(this.state.selectedPage);
|
|
@@ -224,9 +378,196 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
224
378
|
this.transitionTimer = null;
|
|
225
379
|
}
|
|
226
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();
|
|
227
389
|
this.restoreConfiguredScrollElement();
|
|
228
390
|
}
|
|
229
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
|
+
|
|
230
571
|
public setPage = (selectedPage: number) => {
|
|
231
572
|
this.selectPage(selectedPage, true);
|
|
232
573
|
};
|
|
@@ -241,7 +582,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
241
582
|
|
|
242
583
|
public scrollToStickyTop = (options?: Readonly<{ animated?: boolean }>) => {
|
|
243
584
|
this.scrollCurrentPageToTop(
|
|
244
|
-
|
|
585
|
+
this.headerHeight,
|
|
245
586
|
options?.animated ?? true
|
|
246
587
|
);
|
|
247
588
|
};
|
|
@@ -308,6 +649,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
308
649
|
const element = this.pageScrollElement(this.state.selectedPage);
|
|
309
650
|
const key = this.pages().map(pageKey)[this.state.selectedPage];
|
|
310
651
|
if (!element?.isConnected || !key) return;
|
|
652
|
+
this.setRouteTarget(null);
|
|
311
653
|
this.selectionRestoreToken += 1;
|
|
312
654
|
this.cancelPendingTopFrame();
|
|
313
655
|
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
@@ -315,7 +657,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
315
657
|
element.dispatchEvent(
|
|
316
658
|
new EventConstructor("ok-collapsible-pager-cancel-scroll-command")
|
|
317
659
|
);
|
|
318
|
-
element
|
|
660
|
+
scrollElementTo(element, {
|
|
319
661
|
left: element.scrollLeft,
|
|
320
662
|
top: element.scrollTop,
|
|
321
663
|
behavior: "instant",
|
|
@@ -334,10 +676,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
334
676
|
this.pageOffsets.set(key, 0);
|
|
335
677
|
this.sharedHeaderOffset = offset;
|
|
336
678
|
this.bodyAlignment = { element, key };
|
|
337
|
-
element
|
|
679
|
+
scrollElementTo(element, {
|
|
338
680
|
top: offset,
|
|
339
681
|
behavior: animated ? "smooth" : "instant",
|
|
340
682
|
});
|
|
683
|
+
this.syncFallbackHeader();
|
|
341
684
|
});
|
|
342
685
|
this.pendingTopFrame = { frame, element, key };
|
|
343
686
|
}
|
|
@@ -411,12 +754,186 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
411
754
|
private pageScrollElement(index: number) {
|
|
412
755
|
const pageHost = this.pageHosts.get(index);
|
|
413
756
|
return (
|
|
414
|
-
pageHost?.querySelector<HTMLElement>(
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
417
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;
|
|
418
802
|
}
|
|
419
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
|
+
|
|
420
937
|
private notifyInsetsChanged(element: HTMLElement) {
|
|
421
938
|
const EventConstructor = element.ownerDocument.defaultView?.Event;
|
|
422
939
|
if (EventConstructor) {
|
|
@@ -444,10 +961,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
444
961
|
"scrollend",
|
|
445
962
|
this.onVerticalScrollSettled
|
|
446
963
|
);
|
|
964
|
+
configured.element.removeEventListener("scroll", this.syncFallbackHeader);
|
|
447
965
|
const style = timelineStyle(configured.element);
|
|
448
966
|
configured.rangeSpacer.remove();
|
|
449
967
|
style.position = configured.position;
|
|
450
|
-
style.paddingTop = configured.paddingTop;
|
|
968
|
+
configured.paddingTarget.style.paddingTop = configured.paddingTop;
|
|
451
969
|
style.boxSizing = configured.boxSizing;
|
|
452
970
|
style.scrollTimelineName = configured.scrollTimelineName;
|
|
453
971
|
style.scrollTimelineAxis = configured.scrollTimelineAxis;
|
|
@@ -456,6 +974,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
456
974
|
configured.element.removeAttribute("data-collapsible-pager-active");
|
|
457
975
|
this.notifyInsetsChanged(configured.element);
|
|
458
976
|
this.configuredScrollElement = null;
|
|
977
|
+
this.syncFallbackHeader();
|
|
459
978
|
if (this.bodyAlignment?.element === configured.element)
|
|
460
979
|
this.bodyAlignment = null;
|
|
461
980
|
}
|
|
@@ -464,13 +983,19 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
464
983
|
this.configureFrame = null;
|
|
465
984
|
const element = this.pageScrollElement(this.state.selectedPage);
|
|
466
985
|
if (!element) return;
|
|
986
|
+
this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
|
|
467
987
|
if (this.configuredScrollElement?.element !== element) {
|
|
468
988
|
this.restoreConfiguredScrollElement();
|
|
469
989
|
const style = timelineStyle(element);
|
|
990
|
+
const paddingTarget = element.hasAttribute(
|
|
991
|
+
"data-collapsible-pager-scroll"
|
|
992
|
+
)
|
|
993
|
+
? webElement(element.firstElementChild) ?? element
|
|
994
|
+
: element;
|
|
470
995
|
const computedPaddingTop =
|
|
471
|
-
element.ownerDocument.defaultView?.getComputedStyle(
|
|
996
|
+
element.ownerDocument.defaultView?.getComputedStyle(paddingTarget)
|
|
472
997
|
.paddingTop ||
|
|
473
|
-
style.paddingTop ||
|
|
998
|
+
paddingTarget.style.paddingTop ||
|
|
474
999
|
"0px";
|
|
475
1000
|
const rangeSpacer = element.ownerDocument.createElement("div");
|
|
476
1001
|
Object.assign(rangeSpacer.style, {
|
|
@@ -490,10 +1015,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
490
1015
|
style.position = "relative";
|
|
491
1016
|
element.appendChild(rangeSpacer);
|
|
492
1017
|
this.configuredScrollElement = {
|
|
1018
|
+
key: this.pages().map(pageKey)[this.state.selectedPage],
|
|
493
1019
|
element,
|
|
1020
|
+
paddingTarget,
|
|
494
1021
|
rangeSpacer,
|
|
495
1022
|
position,
|
|
496
|
-
paddingTop: style.paddingTop,
|
|
1023
|
+
paddingTop: paddingTarget.style.paddingTop,
|
|
497
1024
|
basePaddingTop: computedPaddingTop,
|
|
498
1025
|
boxSizing: style.boxSizing,
|
|
499
1026
|
scrollTimelineName: style.scrollTimelineName,
|
|
@@ -509,11 +1036,17 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
509
1036
|
element.addEventListener("wheel", this.onQueuedTopWheel);
|
|
510
1037
|
element.addEventListener("pointerdown", this.onScrollbarPointerDown);
|
|
511
1038
|
element.addEventListener("keydown", this.onQueuedTopKey);
|
|
1039
|
+
if (!this.cssTimelineSupported)
|
|
1040
|
+
element.addEventListener("scroll", this.syncFallbackHeader, {
|
|
1041
|
+
passive: true,
|
|
1042
|
+
});
|
|
512
1043
|
}
|
|
513
1044
|
|
|
514
1045
|
const style = timelineStyle(element);
|
|
515
|
-
const
|
|
516
|
-
|
|
1046
|
+
const paddingStyle =
|
|
1047
|
+
this.configuredScrollElement?.paddingTarget.style ?? style;
|
|
1048
|
+
const headerInset = this.headerHeight;
|
|
1049
|
+
const stickyInset = this.stickyHeaderHeight;
|
|
517
1050
|
if (this.configuredScrollElement)
|
|
518
1051
|
this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
|
|
519
1052
|
const basePaddingTop =
|
|
@@ -524,10 +1057,10 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
524
1057
|
const headerInsetValue = `${headerInset}px`;
|
|
525
1058
|
const stickyInsetValue = `${stickyInset}px`;
|
|
526
1059
|
const insetsChanged =
|
|
527
|
-
|
|
1060
|
+
paddingStyle.paddingTop !== paddingTop ||
|
|
528
1061
|
style.getPropertyValue(PAGER_HEADER_INSET) !== headerInsetValue ||
|
|
529
1062
|
style.getPropertyValue(PAGER_STICKY_INSET) !== stickyInsetValue;
|
|
530
|
-
|
|
1063
|
+
paddingStyle.paddingTop = paddingTop;
|
|
531
1064
|
style.boxSizing = "border-box";
|
|
532
1065
|
style.scrollTimelineName = this.timelineName;
|
|
533
1066
|
style.scrollTimelineAxis = "block";
|
|
@@ -535,10 +1068,25 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
535
1068
|
style.setProperty(PAGER_STICKY_INSET, stickyInsetValue);
|
|
536
1069
|
element.setAttribute("data-collapsible-pager-active", "true");
|
|
537
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;
|
|
538
1086
|
};
|
|
539
1087
|
|
|
540
1088
|
private scheduleScrollConfiguration() {
|
|
541
|
-
if (this.configureFrame !== null) return;
|
|
1089
|
+
if (this.disposed || this.configureFrame !== null) return;
|
|
542
1090
|
if (typeof requestAnimationFrame === "function") {
|
|
543
1091
|
this.configureFrame = requestAnimationFrame(
|
|
544
1092
|
this.configureActiveScrollElement
|
|
@@ -549,6 +1097,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
549
1097
|
}
|
|
550
1098
|
|
|
551
1099
|
private takeOverVerticalScroll = () => {
|
|
1100
|
+
this.setRouteTarget(null);
|
|
552
1101
|
this.selectionRestoreToken += 1;
|
|
553
1102
|
this.bodyAlignment = null;
|
|
554
1103
|
this.cancelPendingTopFrame();
|
|
@@ -573,15 +1122,18 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
573
1122
|
};
|
|
574
1123
|
|
|
575
1124
|
private savePageOffset(index: number) {
|
|
576
|
-
const
|
|
577
|
-
|
|
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;
|
|
578
1130
|
if (key && scrollElement) {
|
|
579
1131
|
if (
|
|
580
1132
|
this.bodyAlignment?.key === key &&
|
|
581
1133
|
this.bodyAlignment.element === scrollElement
|
|
582
1134
|
)
|
|
583
1135
|
return;
|
|
584
|
-
const D =
|
|
1136
|
+
const D = this.headerHeight;
|
|
585
1137
|
this.pageOffsets.set(key, Math.max(0, scrollElement.scrollTop - D));
|
|
586
1138
|
this.sharedHeaderOffset = Math.min(
|
|
587
1139
|
Math.max(scrollElement.scrollTop, 0),
|
|
@@ -594,11 +1146,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
594
1146
|
const key = this.pages().map(pageKey)[index];
|
|
595
1147
|
const scrollElement = this.pageScrollElement(index);
|
|
596
1148
|
if (!key || !scrollElement) return;
|
|
597
|
-
const D =
|
|
1149
|
+
const D = this.headerHeight;
|
|
598
1150
|
const C = Math.min(Math.max(0, this.sharedHeaderOffset), D);
|
|
599
1151
|
const expectedQ = C + (C === D ? this.pageOffsets.get(key) ?? 0 : 0);
|
|
600
1152
|
this.bodyAlignment = { element: scrollElement, key };
|
|
601
1153
|
scrollElement.scrollTop = expectedQ;
|
|
1154
|
+
this.syncFallbackHeader();
|
|
602
1155
|
}
|
|
603
1156
|
|
|
604
1157
|
private selectPage(selectedPage: number, animated: boolean) {
|
|
@@ -612,8 +1165,11 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
612
1165
|
) {
|
|
613
1166
|
return;
|
|
614
1167
|
}
|
|
1168
|
+
this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
|
|
615
1169
|
const selectionToken = ++this.selectionRestoreToken;
|
|
616
1170
|
const expectedKey = this.pages().map(pageKey)[position];
|
|
1171
|
+
if (this.routeTarget && this.routeTarget.key !== expectedKey)
|
|
1172
|
+
this.setRouteTarget(null);
|
|
617
1173
|
this.cancelPendingTopFrame();
|
|
618
1174
|
this.savePageOffset(this.state.selectedPage);
|
|
619
1175
|
|
|
@@ -632,6 +1188,12 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
632
1188
|
)
|
|
633
1189
|
return;
|
|
634
1190
|
this.configureActiveScrollElement();
|
|
1191
|
+
if (
|
|
1192
|
+
this.selectionRestoreToken !== selectionToken ||
|
|
1193
|
+
this.routeTarget?.key === expectedKey
|
|
1194
|
+
) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
635
1197
|
this.restorePageOffset(position);
|
|
636
1198
|
};
|
|
637
1199
|
if (typeof requestAnimationFrame === "function") {
|
|
@@ -679,7 +1241,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
679
1241
|
this.pageScrollElement(this.state.selectedPage)?.scrollTop ?? 0,
|
|
680
1242
|
0
|
|
681
1243
|
),
|
|
682
|
-
|
|
1244
|
+
this.headerHeight
|
|
683
1245
|
),
|
|
684
1246
|
nativePageCount: pages.length,
|
|
685
1247
|
attachedPageCount: retainedPages.length,
|
|
@@ -770,6 +1332,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
770
1332
|
externalTopOverlayTag: _externalTopOverlayTag,
|
|
771
1333
|
headerHeight: _headerHeight,
|
|
772
1334
|
stickyHeaderHeight: _stickyHeaderHeight,
|
|
1335
|
+
isActive: _isActive,
|
|
773
1336
|
pageRetentionDistance: _pageRetentionDistance,
|
|
774
1337
|
layoutDirection: _layoutDirection,
|
|
775
1338
|
scrollEnabled: _scrollEnabled,
|
|
@@ -787,18 +1350,20 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
787
1350
|
const keys = pages.map(pageKey);
|
|
788
1351
|
const retained = new Set(this.mountedPages(pages.length));
|
|
789
1352
|
const pageWidth = pages.length > 0 ? `${100 / pages.length}%` : "100%";
|
|
790
|
-
const collapseAnimation =
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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;
|
|
802
1367
|
const trackStyle = [
|
|
803
1368
|
styles.track,
|
|
804
1369
|
{
|
|
@@ -818,35 +1383,45 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
818
1383
|
return (
|
|
819
1384
|
<View
|
|
820
1385
|
{...viewProps}
|
|
821
|
-
ref={
|
|
822
|
-
this.root = webElement(node);
|
|
823
|
-
}}
|
|
1386
|
+
ref={this.setRoot}
|
|
824
1387
|
style={[
|
|
825
1388
|
styles.root,
|
|
826
|
-
|
|
1389
|
+
this.cssTimelineSupported
|
|
1390
|
+
? ({ timelineScope: this.timelineName } as never)
|
|
1391
|
+
: undefined,
|
|
827
1392
|
style,
|
|
828
1393
|
]}
|
|
829
1394
|
>
|
|
830
1395
|
<View
|
|
1396
|
+
ref={(node) => {
|
|
1397
|
+
this.headerSlot = webElement(node);
|
|
1398
|
+
}}
|
|
831
1399
|
style={[
|
|
832
1400
|
styles.header,
|
|
833
|
-
{ height:
|
|
1401
|
+
{ height: this.headerHeight },
|
|
834
1402
|
collapseAnimation,
|
|
835
1403
|
]}
|
|
836
1404
|
>
|
|
837
|
-
{header
|
|
1405
|
+
{this.hasExplicitHeaderHeight ? header : (
|
|
1406
|
+
<View ref={this.setHeaderContent}>{header}</View>
|
|
1407
|
+
)}
|
|
838
1408
|
</View>
|
|
839
1409
|
<View
|
|
1410
|
+
ref={(node) => {
|
|
1411
|
+
this.stickySlot = webElement(node);
|
|
1412
|
+
}}
|
|
840
1413
|
style={[
|
|
841
1414
|
styles.sticky,
|
|
842
1415
|
{
|
|
843
|
-
top:
|
|
844
|
-
height:
|
|
1416
|
+
top: this.headerHeight,
|
|
1417
|
+
height: this.stickyHeaderHeight,
|
|
845
1418
|
},
|
|
846
1419
|
collapseAnimation,
|
|
847
1420
|
]}
|
|
848
1421
|
>
|
|
849
|
-
{stickyHeader
|
|
1422
|
+
{this.hasExplicitStickyHeaderHeight ? stickyHeader : (
|
|
1423
|
+
<View ref={this.setStickyContent}>{stickyHeader}</View>
|
|
1424
|
+
)}
|
|
850
1425
|
</View>
|
|
851
1426
|
<View
|
|
852
1427
|
ref={this.setTrack}
|
|
@@ -871,7 +1446,21 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
871
1446
|
style={[styles.page, { width: pageWidth }]}
|
|
872
1447
|
aria-hidden={index !== this.state.selectedPage}
|
|
873
1448
|
>
|
|
874
|
-
{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}
|
|
875
1464
|
</View>
|
|
876
1465
|
))}
|
|
877
1466
|
</View>
|
|
@@ -883,7 +1472,7 @@ export class CollapsiblePagerView extends React.PureComponent<
|
|
|
883
1472
|
const styles = StyleSheet.create({
|
|
884
1473
|
root: {
|
|
885
1474
|
flex: 1,
|
|
886
|
-
overflow: "
|
|
1475
|
+
overflow: "clip",
|
|
887
1476
|
position: "relative",
|
|
888
1477
|
} as never,
|
|
889
1478
|
header: {
|
|
@@ -899,7 +1488,7 @@ const styles = StyleSheet.create({
|
|
|
899
1488
|
left: 0,
|
|
900
1489
|
right: 0,
|
|
901
1490
|
zIndex: 4,
|
|
902
|
-
overflow: "
|
|
1491
|
+
overflow: "visible",
|
|
903
1492
|
} as never,
|
|
904
1493
|
track: {
|
|
905
1494
|
position: "absolute",
|