@onekeyfe/react-native-pager-view 6.7.0-alpha.327 → 6.7.0-alpha.335

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.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  import React from "react";
4
- import { I18nManager, StyleSheet, View } from "react-native";
4
+ import { I18nManager, Platform, StyleSheet, View } from "react-native";
5
+ import { WebCollapsiblePagerPage } from "./WebCollapsiblePagerPage.js";
5
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
7
  const COLLAPSE_ANIMATION_NAME = "ok-collapsible-pager-collapse";
7
8
  const COLLAPSE_STYLE_ID = "ok-collapsible-pager-styles";
@@ -18,6 +19,11 @@ const webElement = node => {
18
19
  return element && typeof element.querySelector === "function" && typeof element.addEventListener === "function" ? element : null;
19
20
  };
20
21
  const timelineStyle = element => element.style;
22
+ function scrollElementTo(element, options) {
23
+ // RN Web decorates ScrollView nodes with a React Native scrollTo signature.
24
+ const scrollTo = element.ownerDocument.defaultView?.Element.prototype.scrollTo ?? element.scrollTo;
25
+ scrollTo.call(element, options);
26
+ }
21
27
  function ensureCollapseAnimation(document) {
22
28
  if (document.getElementById(COLLAPSE_STYLE_ID)) return;
23
29
  const style = document.createElement("style");
@@ -25,6 +31,10 @@ function ensureCollapseAnimation(document) {
25
31
  style.textContent = `@keyframes ${COLLAPSE_ANIMATION_NAME}{from{transform:translateY(0)}to{transform:translateY(calc(-1 * var(--ok-collapsible-header-height)))}}`;
26
32
  document.head.appendChild(style);
27
33
  }
34
+ function supportsScrollTimeline(document) {
35
+ const css = document.defaultView?.CSS;
36
+ return !!(typeof css?.supports === "function" && css.supports("animation-timeline", "--ok-test") && css.supports("timeline-scope", "--ok-test") && css.supports("scroll-timeline-name", "--ok-test") && css.supports("scroll-timeline-axis", "block") && css.supports("animation-range", "0px 1px") && css.supports("animation-duration", "auto"));
37
+ }
28
38
  const pageKey = (child, index) => {
29
39
  if (/*#__PURE__*/React.isValidElement(child) && child.key != null) return String(child.key);
30
40
  return `page-${index}`;
@@ -38,11 +48,30 @@ export class CollapsiblePagerView extends React.PureComponent {
38
48
  };
39
49
  root = null;
40
50
  track = null;
51
+ headerSlot = null;
52
+ stickySlot = null;
53
+ cssTimelineSupported = false;
54
+ overlayTouch = null;
41
55
  touchStartX = null;
42
56
  touchStartY = null;
43
57
  horizontalTouch = false;
44
58
  pageOffsets = new Map();
45
59
  pageHosts = new Map();
60
+ pageScrollIdentities = new Map();
61
+ pageBridges = new Map();
62
+ ownerRecords = new Map();
63
+ ownerGeneration = 0;
64
+ lastRawTops = new Map();
65
+ routeTarget = null;
66
+ measuredHeaderHeight = 0;
67
+ measuredStickyHeight = 0;
68
+ headerMeasured = false;
69
+ stickyMeasured = false;
70
+ headerContent = null;
71
+ stickyContent = null;
72
+ chromeObserver = null;
73
+ chromeMeasureFrame = null;
74
+ disposed = false;
46
75
  sharedHeaderOffset = 0;
47
76
  selectionRestoreToken = 0;
48
77
  bodyAlignment = null;
@@ -54,13 +83,51 @@ export class CollapsiblePagerView extends React.PureComponent {
54
83
  pageScrollState = "idle";
55
84
  configuredScrollElement = null;
56
85
  timelineName = `--ok-collapsible-pager-${++collapsiblePagerWebInstance}`;
86
+ get headerHeight() {
87
+ return this.hasExplicitHeaderHeight ? this.props.headerHeight ?? this.measuredHeaderHeight : this.measuredHeaderHeight;
88
+ }
89
+ get hasExplicitHeaderHeight() {
90
+ return this.props.headerHeight !== undefined && Number.isFinite(this.props.headerHeight) && this.props.headerHeight >= 0;
91
+ }
92
+ get stickyHeaderHeight() {
93
+ return this.hasExplicitStickyHeaderHeight ? this.props.stickyHeaderHeight ?? this.measuredStickyHeight : this.measuredStickyHeight;
94
+ }
95
+ get hasExplicitStickyHeaderHeight() {
96
+ return this.props.stickyHeaderHeight !== undefined && Number.isFinite(this.props.stickyHeaderHeight) && this.props.stickyHeaderHeight >= 0;
97
+ }
98
+ getSnapshotBeforeUpdate(previousProps) {
99
+ if (previousProps.isActive !== false && this.props.isActive === false) return this.captureRouteTarget(previousProps);
100
+ return null;
101
+ }
57
102
  componentDidMount() {
58
- if (this.root) ensureCollapseAnimation(this.root.ownerDocument);
103
+ this.disposed = false;
104
+ if (Platform.OS !== "web" || typeof document === "undefined") throw new Error("WebCollapsiblePagerView requires a Web DOM runtime");
105
+ if (!this.hasExplicitHeaderHeight) this.headerMeasured = false;
106
+ if (!this.hasExplicitStickyHeaderHeight) this.stickyMeasured = false;
107
+ this.observeChrome();
108
+ if (this.root) {
109
+ this.cssTimelineSupported = supportsScrollTimeline(this.root.ownerDocument);
110
+ if (this.cssTimelineSupported) {
111
+ ensureCollapseAnimation(this.root.ownerDocument);
112
+ this.forceUpdate();
113
+ }
114
+ }
59
115
  this.scheduleScrollConfiguration();
116
+ this.syncFallbackHeader();
60
117
  this.notifyMountedPagesChanged();
61
118
  this.emitDiagnostics("mounted");
62
119
  }
63
- componentDidUpdate(previousProps) {
120
+ componentDidUpdate(previousProps, _previousState, routeSnapshot) {
121
+ if (routeSnapshot) {
122
+ const record = this.ownerRecords.get(routeSnapshot.key);
123
+ if (this.pages().map(pageKey).includes(routeSnapshot.key) && record?.element === routeSnapshot.owner && record.identity === routeSnapshot.identity && record.generation === routeSnapshot.generation) this.setRouteTarget(routeSnapshot);
124
+ }
125
+ if (previousProps.isActive === false && this.props.isActive !== false) {
126
+ if (!this.hasExplicitHeaderHeight) this.headerMeasured = false;
127
+ if (!this.hasExplicitStickyHeaderHeight) this.stickyMeasured = false;
128
+ this.scheduleChromeMeasurement();
129
+ this.restoreScrollPosition();
130
+ }
64
131
  if (previousProps.scrollEnabled !== this.props.scrollEnabled) {
65
132
  this.scrollEnabled = this.props.scrollEnabled ?? true;
66
133
  if (!this.scrollEnabled) {
@@ -82,10 +149,29 @@ export class CollapsiblePagerView extends React.PureComponent {
82
149
  });
83
150
  return;
84
151
  }
152
+ // Child layout effects can run before page-host refs are reattached.
153
+ // Recheck identities in the same commit, before a new user command runs.
154
+ this.mountedPages().forEach(index => this.synchronizeScrollOwnerIdentity(index));
155
+ const currentKeys = new Set(this.pages().map(pageKey));
156
+ const ownedKeys = new Set([...this.pageScrollIdentities.keys(), ...this.pageOffsets.keys(), ...this.lastRawTops.keys(), ...this.ownerRecords.keys()]);
157
+ for (const key of ownedKeys) {
158
+ if (!currentKeys.has(key)) {
159
+ this.pageScrollIdentities.delete(key);
160
+ this.pageOffsets.delete(key);
161
+ this.lastRawTops.delete(key);
162
+ this.ownerRecords.delete(key);
163
+ if (this.routeTarget?.key === key) this.setRouteTarget(null);
164
+ }
165
+ }
85
166
  this.scheduleScrollConfiguration();
167
+ this.syncFallbackHeader();
86
168
  this.notifyMountedPagesChanged();
87
169
  }
88
170
  componentWillUnmount() {
171
+ // Activity can disconnect effects before the inactive update commits.
172
+ const routeTarget = this.captureRouteTarget(this.props);
173
+ if (routeTarget) this.setRouteTarget(routeTarget);
174
+ this.disposed = true;
89
175
  this.selectionRestoreToken += 1;
90
176
  this.cancelPendingTopFrame();
91
177
  this.savePageOffset(this.state.selectedPage);
@@ -98,8 +184,148 @@ export class CollapsiblePagerView extends React.PureComponent {
98
184
  this.transitionTimer = null;
99
185
  }
100
186
  this.setTrack(null);
187
+ this.setRoot(null);
188
+ this.chromeObserver?.disconnect();
189
+ this.chromeObserver = null;
190
+ if (this.chromeMeasureFrame !== null) {
191
+ cancelAnimationFrame(this.chromeMeasureFrame);
192
+ this.chromeMeasureFrame = null;
193
+ }
194
+ this.pageBridges.clear();
101
195
  this.restoreConfiguredScrollElement();
102
196
  }
197
+ setHeaderContent = node => {
198
+ const element = webElement(node);
199
+ if (element === this.headerContent) return;
200
+ if (this.headerContent) this.chromeObserver?.unobserve(this.headerContent);
201
+ this.headerContent = element;
202
+ this.headerMeasured = false;
203
+ if (element) this.chromeObserver?.observe(element);
204
+ this.scheduleChromeMeasurement();
205
+ };
206
+ setStickyContent = node => {
207
+ const element = webElement(node);
208
+ if (element === this.stickyContent) return;
209
+ if (this.stickyContent) this.chromeObserver?.unobserve(this.stickyContent);
210
+ this.stickyContent = element;
211
+ this.stickyMeasured = false;
212
+ if (element) this.chromeObserver?.observe(element);
213
+ this.scheduleChromeMeasurement();
214
+ };
215
+ observeChrome() {
216
+ this.chromeObserver = new ResizeObserver(this.scheduleChromeMeasurement);
217
+ if (this.headerContent) this.chromeObserver.observe(this.headerContent);
218
+ if (this.stickyContent) this.chromeObserver.observe(this.stickyContent);
219
+ this.scheduleChromeMeasurement();
220
+ }
221
+ scheduleChromeMeasurement = () => {
222
+ if (this.disposed || this.chromeMeasureFrame !== null || typeof requestAnimationFrame !== "function") return;
223
+ this.chromeMeasureFrame = requestAnimationFrame(() => {
224
+ this.chromeMeasureFrame = null;
225
+ const header = this.headerContent;
226
+ const sticky = this.stickyContent;
227
+ const nextHeader = header?.isConnected ? Math.max(0, header.getBoundingClientRect().height) : 0;
228
+ const nextSticky = sticky?.isConnected ? Math.max(0, sticky.getBoundingClientRect().height) : 0;
229
+ const headerChanged = this.measuredHeaderHeight !== nextHeader || this.headerMeasured !== Boolean(header?.isConnected);
230
+ const stickyChanged = this.measuredStickyHeight !== nextSticky || this.stickyMeasured !== Boolean(sticky?.isConnected);
231
+ this.measuredHeaderHeight = nextHeader;
232
+ this.measuredStickyHeight = nextSticky;
233
+ this.headerMeasured = Boolean(header?.isConnected);
234
+ this.stickyMeasured = Boolean(sticky?.isConnected);
235
+ if (headerChanged || stickyChanged) {
236
+ this.forceUpdate();
237
+ this.scheduleScrollConfiguration();
238
+ }
239
+ });
240
+ };
241
+ onPageBridge = (key, bridge) => {
242
+ if (bridge) {
243
+ this.pageBridges.set(key, bridge);
244
+ bridge.setPendingRouteScrollTop(this.routeTarget?.key === key ? this.routeTarget.top : null);
245
+ const index = this.pages().map(pageKey).indexOf(key);
246
+ if (index >= 0) this.syncPageOwner(index);
247
+ } else {
248
+ this.pageBridges.delete(key);
249
+ }
250
+ };
251
+ setRouteTarget(target) {
252
+ const oldKey = this.routeTarget?.key;
253
+ this.routeTarget = target;
254
+ if (oldKey) this.pageBridges.get(oldKey)?.setPendingRouteScrollTop(null);
255
+ if (target) this.pageBridges.get(target.key)?.setPendingRouteScrollTop(target.top);
256
+ }
257
+ syncPageOwner(index) {
258
+ const key = this.pages().map(pageKey)[index];
259
+ const element = this.pageScrollElement(index);
260
+ if (!key || !element) return;
261
+ const identity = element.getAttribute("data-collapsible-pager-scroll-key") ?? key;
262
+ const previous = this.ownerRecords.get(key);
263
+ if (previous?.element !== element || previous.identity !== identity) {
264
+ if (this.routeTarget?.key === key) this.setRouteTarget(null);
265
+ this.ownerRecords.set(key, {
266
+ element,
267
+ identity,
268
+ generation: ++this.ownerGeneration
269
+ });
270
+ this.lastRawTops.delete(key);
271
+ }
272
+ this.pageBridges.get(key)?.setOwner(element);
273
+ }
274
+ captureRouteTarget(previousProps) {
275
+ const index = this.state.selectedPage;
276
+ const oldKey = React.Children.toArray(previousProps.children).map(pageKey)[index];
277
+ const key = this.configuredScrollElement?.key ?? oldKey;
278
+ const record = key ? this.ownerRecords.get(key) : null;
279
+ if (!record?.element.isConnected) return null;
280
+ if (this.routeTarget?.key === key && this.routeTarget.identity === record.identity && this.routeTarget.owner === record.element && this.routeTarget.generation === record.generation) return this.routeTarget;
281
+ const top = record.element.clientHeight > 0 ? record.element.scrollTop : this.lastRawTops.get(key) ?? record.element.scrollTop;
282
+ return {
283
+ key,
284
+ identity: record.identity,
285
+ owner: record.element,
286
+ generation: record.generation,
287
+ top: Math.max(0, top)
288
+ };
289
+ }
290
+ restoreScrollPosition = () => {
291
+ if (this.disposed || this.props.isActive === false) return false;
292
+ const pending = this.routeTarget;
293
+ if (!pending) return true;
294
+ const key = this.pages().map(pageKey)[this.state.selectedPage];
295
+ if (key !== pending.key) return false;
296
+ this.syncPageOwner(this.state.selectedPage);
297
+ const record = this.ownerRecords.get(key);
298
+ if (!record || record.element !== pending.owner || record.identity !== pending.identity || record.generation !== pending.generation) return false;
299
+ const element = record.element;
300
+ const configured = this.configuredScrollElement;
301
+ if (!element.isConnected || element.clientHeight <= 0 || configured?.element !== element || configured.key !== key || !this.hasExplicitHeaderHeight && !this.headerMeasured || !this.hasExplicitStickyHeaderHeight && !this.stickyMeasured || element.getAttribute("data-collapsible-pager-active") !== "true" || element.style.getPropertyValue(PAGER_HEADER_INSET).trim() !== `${this.headerHeight}px` || element.style.getPropertyValue(PAGER_STICKY_INSET).trim() !== `${this.stickyHeaderHeight}px` || element.scrollHeight - element.clientHeight < pending.top) return false;
302
+ this.selectionRestoreToken += 1;
303
+ this.cancelPendingTopFrame();
304
+ this.setRouteTarget(null);
305
+ this.lastRawTops.set(key, pending.top);
306
+ const D = this.headerHeight;
307
+ this.sharedHeaderOffset = Math.min(pending.top, D);
308
+ this.pageOffsets.set(key, Math.max(0, pending.top - D));
309
+ this.bodyAlignment = {
310
+ element,
311
+ key
312
+ };
313
+ scrollElementTo(element, {
314
+ top: pending.top,
315
+ behavior: "instant"
316
+ });
317
+ this.syncFallbackHeader();
318
+ return true;
319
+ };
320
+ onPageScroll = (key, top) => {
321
+ if (this.props.isActive !== false) this.lastRawTops.set(key, top);
322
+ };
323
+ onPageGeometryChange = key => {
324
+ if (this.routeTarget?.key === key) this.restoreScrollPosition();
325
+ };
326
+ onPageUserInput = key => {
327
+ if (this.routeTarget?.key === key) this.setRouteTarget(null);
328
+ };
103
329
  setPage = selectedPage => {
104
330
  this.selectPage(selectedPage, true);
105
331
  };
@@ -110,7 +336,7 @@ export class CollapsiblePagerView extends React.PureComponent {
110
336
  this.scrollCurrentPageToTop(0, options?.animated ?? true);
111
337
  };
112
338
  scrollToStickyTop = options => {
113
- this.scrollCurrentPageToTop(Math.max(0, this.props.headerHeight), options?.animated ?? true);
339
+ this.scrollCurrentPageToTop(this.headerHeight, options?.animated ?? true);
114
340
  };
115
341
  cancelPendingTopFrame = () => {
116
342
  if (!this.pendingTopFrame) return;
@@ -145,11 +371,12 @@ export class CollapsiblePagerView extends React.PureComponent {
145
371
  const element = this.pageScrollElement(this.state.selectedPage);
146
372
  const key = this.pages().map(pageKey)[this.state.selectedPage];
147
373
  if (!element?.isConnected || !key) return;
374
+ this.setRouteTarget(null);
148
375
  this.selectionRestoreToken += 1;
149
376
  this.cancelPendingTopFrame();
150
377
  const EventConstructor = element.ownerDocument.defaultView?.Event;
151
378
  if (EventConstructor) element.dispatchEvent(new EventConstructor("ok-collapsible-pager-cancel-scroll-command"));
152
- element.scrollTo({
379
+ scrollElementTo(element, {
153
380
  left: element.scrollLeft,
154
381
  top: element.scrollTop,
155
382
  behavior: "instant"
@@ -164,10 +391,11 @@ export class CollapsiblePagerView extends React.PureComponent {
164
391
  element,
165
392
  key
166
393
  };
167
- element.scrollTo({
394
+ scrollElementTo(element, {
168
395
  top: offset,
169
396
  behavior: animated ? "smooth" : "instant"
170
397
  });
398
+ this.syncFallbackHeader();
171
399
  });
172
400
  this.pendingTopFrame = {
173
401
  frame,
@@ -230,8 +458,119 @@ export class CollapsiblePagerView extends React.PureComponent {
230
458
  };
231
459
  pageScrollElement(index) {
232
460
  const pageHost = this.pageHosts.get(index);
233
- return pageHost?.querySelector(".ok-native-list-viewport, [data-collapsible-pager-scroll]") ?? pageHost;
461
+ return pageHost?.querySelector("[data-collapsible-pager-scroll]") ?? pageHost?.querySelector(".ok-native-list-viewport") ?? pageHost;
462
+ }
463
+ setRoot = node => {
464
+ const root = webElement(node);
465
+ if (root === this.root) return;
466
+ this.root?.removeEventListener("ok-collapsible-pager-scroll-key-changed", this.onScrollOwnerIdentityChanged);
467
+ this.root?.removeEventListener("wheel", this.onOverlayWheel);
468
+ this.root?.removeEventListener("touchstart", this.onOverlayTouchStart);
469
+ this.root?.removeEventListener("touchmove", this.onOverlayTouchMove);
470
+ this.root?.removeEventListener("touchend", this.onOverlayTouchEnd);
471
+ this.root?.removeEventListener("touchcancel", this.onOverlayTouchEnd);
472
+ this.overlayTouch = null;
473
+ this.root = root;
474
+ root?.addEventListener("ok-collapsible-pager-scroll-key-changed", this.onScrollOwnerIdentityChanged);
475
+ root?.addEventListener("wheel", this.onOverlayWheel, {
476
+ passive: false
477
+ });
478
+ root?.addEventListener("touchstart", this.onOverlayTouchStart, {
479
+ passive: true
480
+ });
481
+ root?.addEventListener("touchmove", this.onOverlayTouchMove, {
482
+ passive: false
483
+ });
484
+ root?.addEventListener("touchend", this.onOverlayTouchEnd);
485
+ root?.addEventListener("touchcancel", this.onOverlayTouchEnd);
486
+ };
487
+ overlayTarget(event) {
488
+ const target = event.target;
489
+ if (!target || !this.headerSlot?.contains(target) && !this.stickySlot?.contains(target) || target.closest("input,textarea,select,[contenteditable=true]")) return null;
490
+ return target;
491
+ }
492
+ nestedScrollConsumes(target, delta) {
493
+ for (let node = target; node && node !== this.root; node = node.parentElement) {
494
+ const style = node.ownerDocument.defaultView?.getComputedStyle(node);
495
+ if (/auto|scroll/.test(style?.overflowY ?? "") && node.scrollHeight > node.clientHeight && (delta < 0 ? node.scrollTop > 0 : node.scrollTop < node.scrollHeight - node.clientHeight)) return true;
496
+ }
497
+ return false;
498
+ }
499
+ scrollOverlayOwner(owner, delta, event) {
500
+ if (!event.cancelable) return;
501
+ const EventConstructor = owner.ownerDocument.defaultView?.Event;
502
+ if (EventConstructor) owner.dispatchEvent(new EventConstructor("ok-collapsible-pager-user-scroll"));
503
+ const top = Math.min(Math.max(0, owner.scrollTop + delta), owner.scrollHeight - owner.clientHeight);
504
+ if (top === owner.scrollTop) return;
505
+ this.takeOverVerticalScroll();
506
+ scrollElementTo(owner, {
507
+ top,
508
+ behavior: "instant"
509
+ });
510
+ event.preventDefault();
511
+ }
512
+ onOverlayWheel = event => {
513
+ const target = this.overlayTarget(event);
514
+ const owner = this.pageScrollElement(this.state.selectedPage);
515
+ if (!target || !owner || event.defaultPrevented || event.ctrlKey || Math.abs(event.deltaX) > Math.abs(event.deltaY)) return;
516
+ const lineHeight = Number.parseFloat(target.ownerDocument.defaultView?.getComputedStyle(target).lineHeight ?? "") || 16;
517
+ const delta = event.deltaY * (event.deltaMode === 1 ? lineHeight : event.deltaMode === 2 ? owner.clientHeight : 1);
518
+ if (!this.nestedScrollConsumes(target, delta)) this.scrollOverlayOwner(owner, delta, event);
519
+ };
520
+ onOverlayTouchStart = event => {
521
+ const target = this.overlayTarget(event);
522
+ const owner = this.pageScrollElement(this.state.selectedPage);
523
+ const touch = event.touches.length === 1 ? event.touches[0] : null;
524
+ this.overlayTouch = target && owner && touch ? {
525
+ owner,
526
+ target,
527
+ x: touch.clientX,
528
+ y: touch.clientY,
529
+ lastY: touch.clientY
530
+ } : null;
531
+ };
532
+ onOverlayTouchMove = event => {
533
+ if (event.defaultPrevented) return;
534
+ const pending = this.overlayTouch;
535
+ const touch = event.touches.length === 1 ? event.touches[0] : null;
536
+ if (!pending || !touch || pending.owner !== this.pageScrollElement(this.state.selectedPage)) {
537
+ this.overlayTouch = null;
538
+ return;
539
+ }
540
+ const delta = pending.lastY - touch.clientY;
541
+ pending.lastY = touch.clientY;
542
+ if (Math.abs(touch.clientY - pending.y) < 8 || Math.abs(touch.clientX - pending.x) >= Math.abs(touch.clientY - pending.y)) return;
543
+ if (!this.nestedScrollConsumes(pending.target, delta)) this.scrollOverlayOwner(pending.owner, delta, event);
544
+ };
545
+ onOverlayTouchEnd = () => {
546
+ this.overlayTouch = null;
547
+ };
548
+ synchronizeScrollOwnerIdentity(index) {
549
+ const key = this.pages().map(pageKey)[index];
550
+ const element = this.pageScrollElement(index);
551
+ if (!key || !element) return;
552
+ const identity = element.getAttribute("data-collapsible-pager-scroll-key") ?? key;
553
+ const previous = this.pageScrollIdentities.get(key);
554
+ this.pageScrollIdentities.set(key, identity);
555
+ this.syncPageOwner(index);
556
+ if (previous === undefined || previous === identity) return;
557
+ if (index === this.state.selectedPage) {
558
+ this.selectionRestoreToken += 1;
559
+ this.cancelPendingTopFrame();
560
+ this.sharedHeaderOffset = 0;
561
+ this.bodyAlignment = null;
562
+ scrollElementTo(element, {
563
+ top: 0,
564
+ behavior: "instant"
565
+ });
566
+ }
567
+ this.pageOffsets.delete(key);
234
568
  }
569
+ onScrollOwnerIdentityChanged = event => {
570
+ this.pages().forEach((_, index) => {
571
+ if (event.target === this.pageScrollElement(index)) this.synchronizeScrollOwnerIdentity(index);
572
+ });
573
+ };
235
574
  notifyInsetsChanged(element) {
236
575
  const EventConstructor = element.ownerDocument.defaultView?.Event;
237
576
  if (EventConstructor) {
@@ -247,10 +586,11 @@ export class CollapsiblePagerView extends React.PureComponent {
247
586
  configured.element.removeEventListener("pointerdown", this.onScrollbarPointerDown);
248
587
  configured.element.removeEventListener("keydown", this.onQueuedTopKey);
249
588
  configured.element.removeEventListener("scrollend", this.onVerticalScrollSettled);
589
+ configured.element.removeEventListener("scroll", this.syncFallbackHeader);
250
590
  const style = timelineStyle(configured.element);
251
591
  configured.rangeSpacer.remove();
252
592
  style.position = configured.position;
253
- style.paddingTop = configured.paddingTop;
593
+ configured.paddingTarget.style.paddingTop = configured.paddingTop;
254
594
  style.boxSizing = configured.boxSizing;
255
595
  style.scrollTimelineName = configured.scrollTimelineName;
256
596
  style.scrollTimelineAxis = configured.scrollTimelineAxis;
@@ -259,16 +599,19 @@ export class CollapsiblePagerView extends React.PureComponent {
259
599
  configured.element.removeAttribute("data-collapsible-pager-active");
260
600
  this.notifyInsetsChanged(configured.element);
261
601
  this.configuredScrollElement = null;
602
+ this.syncFallbackHeader();
262
603
  if (this.bodyAlignment?.element === configured.element) this.bodyAlignment = null;
263
604
  }
264
605
  configureActiveScrollElement = () => {
265
606
  this.configureFrame = null;
266
607
  const element = this.pageScrollElement(this.state.selectedPage);
267
608
  if (!element) return;
609
+ this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
268
610
  if (this.configuredScrollElement?.element !== element) {
269
611
  this.restoreConfiguredScrollElement();
270
612
  const style = timelineStyle(element);
271
- const computedPaddingTop = element.ownerDocument.defaultView?.getComputedStyle(element).paddingTop || style.paddingTop || "0px";
613
+ const paddingTarget = element.hasAttribute("data-collapsible-pager-scroll") ? webElement(element.firstElementChild) ?? element : element;
614
+ const computedPaddingTop = element.ownerDocument.defaultView?.getComputedStyle(paddingTarget).paddingTop || paddingTarget.style.paddingTop || "0px";
272
615
  const rangeSpacer = element.ownerDocument.createElement("div");
273
616
  Object.assign(rangeSpacer.style, {
274
617
  position: "absolute",
@@ -283,10 +626,12 @@ export class CollapsiblePagerView extends React.PureComponent {
283
626
  if (element.ownerDocument.defaultView?.getComputedStyle(element).position === "static") style.position = "relative";
284
627
  element.appendChild(rangeSpacer);
285
628
  this.configuredScrollElement = {
629
+ key: this.pages().map(pageKey)[this.state.selectedPage],
286
630
  element,
631
+ paddingTarget,
287
632
  rangeSpacer,
288
633
  position,
289
- paddingTop: style.paddingTop,
634
+ paddingTop: paddingTarget.style.paddingTop,
290
635
  basePaddingTop: computedPaddingTop,
291
636
  boxSizing: style.boxSizing,
292
637
  scrollTimelineName: style.scrollTimelineName,
@@ -299,17 +644,21 @@ export class CollapsiblePagerView extends React.PureComponent {
299
644
  element.addEventListener("wheel", this.onQueuedTopWheel);
300
645
  element.addEventListener("pointerdown", this.onScrollbarPointerDown);
301
646
  element.addEventListener("keydown", this.onQueuedTopKey);
647
+ if (!this.cssTimelineSupported) element.addEventListener("scroll", this.syncFallbackHeader, {
648
+ passive: true
649
+ });
302
650
  }
303
651
  const style = timelineStyle(element);
304
- const headerInset = Math.max(0, this.props.headerHeight);
305
- const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
652
+ const paddingStyle = this.configuredScrollElement?.paddingTarget.style ?? style;
653
+ const headerInset = this.headerHeight;
654
+ const stickyInset = this.stickyHeaderHeight;
306
655
  if (this.configuredScrollElement) this.configuredScrollElement.rangeSpacer.style.height = `calc(100% + ${headerInset}px)`;
307
656
  const basePaddingTop = this.configuredScrollElement?.basePaddingTop ?? "0px";
308
657
  const paddingTop = `calc(${basePaddingTop} + ${headerInset + stickyInset}px)`;
309
658
  const headerInsetValue = `${headerInset}px`;
310
659
  const stickyInsetValue = `${stickyInset}px`;
311
- const insetsChanged = style.paddingTop !== paddingTop || style.getPropertyValue(PAGER_HEADER_INSET) !== headerInsetValue || style.getPropertyValue(PAGER_STICKY_INSET) !== stickyInsetValue;
312
- style.paddingTop = paddingTop;
660
+ const insetsChanged = paddingStyle.paddingTop !== paddingTop || style.getPropertyValue(PAGER_HEADER_INSET) !== headerInsetValue || style.getPropertyValue(PAGER_STICKY_INSET) !== stickyInsetValue;
661
+ paddingStyle.paddingTop = paddingTop;
313
662
  style.boxSizing = "border-box";
314
663
  style.scrollTimelineName = this.timelineName;
315
664
  style.scrollTimelineAxis = "block";
@@ -317,9 +666,20 @@ export class CollapsiblePagerView extends React.PureComponent {
317
666
  style.setProperty(PAGER_STICKY_INSET, stickyInsetValue);
318
667
  element.setAttribute("data-collapsible-pager-active", "true");
319
668
  if (insetsChanged) this.notifyInsetsChanged(element);
669
+ this.syncFallbackHeader();
670
+ this.pageBridges.get(this.pages().map(pageKey)[this.state.selectedPage])?.requestMeasure();
671
+ this.restoreScrollPosition();
672
+ };
673
+ syncFallbackHeader = () => {
674
+ if (this.cssTimelineSupported) return;
675
+ const top = this.configuredScrollElement?.element.scrollTop ?? 0;
676
+ const collapse = Math.min(Math.max(top, 0), this.headerHeight);
677
+ const transform = `translateY(${-collapse}px)`;
678
+ if (this.headerSlot) this.headerSlot.style.transform = transform;
679
+ if (this.stickySlot) this.stickySlot.style.transform = transform;
320
680
  };
321
681
  scheduleScrollConfiguration() {
322
- if (this.configureFrame !== null) return;
682
+ if (this.disposed || this.configureFrame !== null) return;
323
683
  if (typeof requestAnimationFrame === "function") {
324
684
  this.configureFrame = requestAnimationFrame(this.configureActiveScrollElement);
325
685
  } else {
@@ -327,6 +687,7 @@ export class CollapsiblePagerView extends React.PureComponent {
327
687
  }
328
688
  }
329
689
  takeOverVerticalScroll = () => {
690
+ this.setRouteTarget(null);
330
691
  this.selectionRestoreToken += 1;
331
692
  this.bodyAlignment = null;
332
693
  this.cancelPendingTopFrame();
@@ -342,11 +703,13 @@ export class CollapsiblePagerView extends React.PureComponent {
342
703
  this.emitDiagnostics("scroll-settled");
343
704
  };
344
705
  savePageOffset(index) {
345
- const key = this.pages().map(pageKey)[index];
346
- const scrollElement = this.pageScrollElement(index);
706
+ const configured = index === this.state.selectedPage ? this.configuredScrollElement : null;
707
+ const key = configured?.key ?? this.pages().map(pageKey)[index];
708
+ const scrollElement = configured?.element ?? this.pageScrollElement(index);
709
+ if (configured && !scrollElement?.isConnected) return;
347
710
  if (key && scrollElement) {
348
711
  if (this.bodyAlignment?.key === key && this.bodyAlignment.element === scrollElement) return;
349
- const D = Math.max(0, this.props.headerHeight);
712
+ const D = this.headerHeight;
350
713
  this.pageOffsets.set(key, Math.max(0, scrollElement.scrollTop - D));
351
714
  this.sharedHeaderOffset = Math.min(Math.max(scrollElement.scrollTop, 0), D);
352
715
  }
@@ -355,7 +718,7 @@ export class CollapsiblePagerView extends React.PureComponent {
355
718
  const key = this.pages().map(pageKey)[index];
356
719
  const scrollElement = this.pageScrollElement(index);
357
720
  if (!key || !scrollElement) return;
358
- const D = Math.max(0, this.props.headerHeight);
721
+ const D = this.headerHeight;
359
722
  const C = Math.min(Math.max(0, this.sharedHeaderOffset), D);
360
723
  const expectedQ = C + (C === D ? this.pageOffsets.get(key) ?? 0 : 0);
361
724
  this.bodyAlignment = {
@@ -363,6 +726,7 @@ export class CollapsiblePagerView extends React.PureComponent {
363
726
  key
364
727
  };
365
728
  scrollElement.scrollTop = expectedQ;
729
+ this.syncFallbackHeader();
366
730
  }
367
731
  selectPage(selectedPage, animated) {
368
732
  const pages = this.pages();
@@ -370,8 +734,10 @@ export class CollapsiblePagerView extends React.PureComponent {
370
734
  if (position === null || position < 0 || position >= pages.length || position === this.state.selectedPage) {
371
735
  return;
372
736
  }
737
+ this.synchronizeScrollOwnerIdentity(this.state.selectedPage);
373
738
  const selectionToken = ++this.selectionRestoreToken;
374
739
  const expectedKey = this.pages().map(pageKey)[position];
740
+ if (this.routeTarget && this.routeTarget.key !== expectedKey) this.setRouteTarget(null);
375
741
  this.cancelPendingTopFrame();
376
742
  this.savePageOffset(this.state.selectedPage);
377
743
  if (animated) this.beginPageTransition();else this.finishPageTransition();
@@ -383,6 +749,9 @@ export class CollapsiblePagerView extends React.PureComponent {
383
749
  const restore = () => {
384
750
  if (this.selectionRestoreToken !== selectionToken || this.state.selectedPage !== position || this.pages().map(pageKey)[position] !== expectedKey || !this.pageScrollElement(position)?.isConnected) return;
385
751
  this.configureActiveScrollElement();
752
+ if (this.selectionRestoreToken !== selectionToken || this.routeTarget?.key === expectedKey) {
753
+ return;
754
+ }
386
755
  this.restorePageOffset(position);
387
756
  };
388
757
  if (typeof requestAnimationFrame === "function") {
@@ -419,7 +788,7 @@ export class CollapsiblePagerView extends React.PureComponent {
419
788
  const retainedPages = this.mountedPages(pages.length);
420
789
  this.props.onCollapsibleStateChanged?.(this.nativeEvent({
421
790
  position: this.state.selectedPage,
422
- headerOffset: Math.min(Math.max(this.pageScrollElement(this.state.selectedPage)?.scrollTop ?? 0, 0), Math.max(0, this.props.headerHeight)),
791
+ headerOffset: Math.min(Math.max(this.pageScrollElement(this.state.selectedPage)?.scrollTop ?? 0, 0), this.headerHeight),
423
792
  nativePageCount: pages.length,
424
793
  attachedPageCount: retainedPages.length,
425
794
  observedScrollableCount: retainedPages.reduce((count, index) => count + (this.pageScrollElement(index) ? 1 : 0), 0),
@@ -489,6 +858,7 @@ export class CollapsiblePagerView extends React.PureComponent {
489
858
  externalTopOverlayTag: _externalTopOverlayTag,
490
859
  headerHeight: _headerHeight,
491
860
  stickyHeaderHeight: _stickyHeaderHeight,
861
+ isActive: _isActive,
492
862
  pageRetentionDistance: _pageRetentionDistance,
493
863
  layoutDirection: _layoutDirection,
494
864
  scrollEnabled: _scrollEnabled,
@@ -506,15 +876,15 @@ export class CollapsiblePagerView extends React.PureComponent {
506
876
  const keys = pages.map(pageKey);
507
877
  const retained = new Set(this.mountedPages(pages.length));
508
878
  const pageWidth = pages.length > 0 ? `${100 / pages.length}%` : "100%";
509
- const collapseAnimation = {
510
- "--ok-collapsible-header-height": `${Math.max(0, this.props.headerHeight)}px`,
879
+ const collapseAnimation = this.cssTimelineSupported ? {
880
+ "--ok-collapsible-header-height": `${Math.max(0, this.headerHeight)}px`,
511
881
  animationName: COLLAPSE_ANIMATION_NAME,
512
882
  animationDuration: "auto",
513
883
  animationFillMode: "both",
514
884
  animationTimingFunction: "linear",
515
885
  animationTimeline: this.timelineName,
516
- animationRange: `0px ${Math.max(1, this.props.headerHeight)}px`
517
- };
886
+ animationRange: `0px ${Math.max(1, this.headerHeight)}px`
887
+ } : undefined;
518
888
  const trackStyle = [styles.track, {
519
889
  width: `${Math.max(1, pages.length) * 100}%`,
520
890
  transform: [{
@@ -525,23 +895,33 @@ export class CollapsiblePagerView extends React.PureComponent {
525
895
  }];
526
896
  return /*#__PURE__*/_jsxs(View, {
527
897
  ...viewProps,
528
- ref: node => {
529
- this.root = webElement(node);
530
- },
531
- style: [styles.root, {
898
+ ref: this.setRoot,
899
+ style: [styles.root, this.cssTimelineSupported ? {
532
900
  timelineScope: this.timelineName
533
- }, style],
901
+ } : undefined, style],
534
902
  children: [/*#__PURE__*/_jsx(View, {
903
+ ref: node => {
904
+ this.headerSlot = webElement(node);
905
+ },
535
906
  style: [styles.header, {
536
- height: Math.max(0, this.props.headerHeight)
907
+ height: this.headerHeight
537
908
  }, collapseAnimation],
538
- children: header
909
+ children: this.hasExplicitHeaderHeight ? header : /*#__PURE__*/_jsx(View, {
910
+ ref: this.setHeaderContent,
911
+ children: header
912
+ })
539
913
  }), /*#__PURE__*/_jsx(View, {
914
+ ref: node => {
915
+ this.stickySlot = webElement(node);
916
+ },
540
917
  style: [styles.sticky, {
541
- top: Math.max(0, this.props.headerHeight),
542
- height: Math.max(0, this.props.stickyHeaderHeight)
918
+ top: this.headerHeight,
919
+ height: this.stickyHeaderHeight
543
920
  }, collapseAnimation],
544
- children: stickyHeader
921
+ children: this.hasExplicitStickyHeaderHeight ? stickyHeader : /*#__PURE__*/_jsx(View, {
922
+ ref: this.setStickyContent,
923
+ children: stickyHeader
924
+ })
545
925
  }), /*#__PURE__*/_jsx(View, {
546
926
  ref: this.setTrack,
547
927
  style: trackStyle,
@@ -563,7 +943,18 @@ export class CollapsiblePagerView extends React.PureComponent {
563
943
  width: pageWidth
564
944
  }],
565
945
  "aria-hidden": index !== this.state.selectedPage,
566
- children: retained.has(index) ? page : null
946
+ children: retained.has(index) ? /*#__PURE__*/_jsx(WebCollapsiblePagerPage, {
947
+ pageKey: keys[index],
948
+ isFocused: index === this.state.selectedPage,
949
+ isRouteActive: this.props.isActive !== false,
950
+ headerHeight: this.headerHeight,
951
+ stickyHeaderHeight: this.stickyHeaderHeight,
952
+ onBridge: this.onPageBridge,
953
+ onGeometryChange: this.onPageGeometryChange,
954
+ onScroll: this.onPageScroll,
955
+ onUserInput: this.onPageUserInput,
956
+ children: page
957
+ }) : null
567
958
  }, keys[index]))
568
959
  })]
569
960
  });
@@ -572,7 +963,7 @@ export class CollapsiblePagerView extends React.PureComponent {
572
963
  const styles = StyleSheet.create({
573
964
  root: {
574
965
  flex: 1,
575
- overflow: "hidden",
966
+ overflow: "clip",
576
967
  position: "relative"
577
968
  },
578
969
  header: {
@@ -588,7 +979,7 @@ const styles = StyleSheet.create({
588
979
  left: 0,
589
980
  right: 0,
590
981
  zIndex: 4,
591
- overflow: "hidden"
982
+ overflow: "visible"
592
983
  },
593
984
  track: {
594
985
  position: "absolute",