@onekeyfe/react-native-pager-view 3.0.114 → 3.0.116

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.
Files changed (29) hide show
  1. package/README.md +40 -0
  2. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +630 -0
  3. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +222 -0
  4. package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +3 -1
  5. package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
  6. package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt +39 -0
  7. package/ios/RNCCollapsiblePagerViewComponentView.h +9 -0
  8. package/ios/RNCCollapsiblePagerViewComponentView.mm +991 -0
  9. package/ios/RNCPagerViewComponentView.mm +4 -2
  10. package/lib/module/CollapsiblePagerView.js +167 -0
  11. package/lib/module/CollapsiblePagerView.web.js +478 -0
  12. package/lib/module/CollapsiblePagerViewNativeComponent.ts +87 -0
  13. package/lib/module/PagerView.web.js +177 -0
  14. package/lib/module/index.js +2 -1
  15. package/lib/module/index.web.js +6 -0
  16. package/lib/module/usePagerView.js +1 -1
  17. package/lib/typescript/src/CollapsiblePagerView.d.ts +58 -0
  18. package/lib/typescript/src/CollapsiblePagerView.web.d.ts +105 -0
  19. package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +51 -0
  20. package/lib/typescript/src/PagerView.web.d.ts +55 -0
  21. package/lib/typescript/src/index.d.ts +1 -0
  22. package/lib/typescript/src/index.web.d.ts +5 -0
  23. package/package.json +5 -2
  24. package/src/CollapsiblePagerView.tsx +293 -0
  25. package/src/CollapsiblePagerView.web.tsx +710 -0
  26. package/src/CollapsiblePagerViewNativeComponent.ts +87 -0
  27. package/src/PagerView.web.tsx +280 -0
  28. package/src/index.tsx +1 -0
  29. package/src/index.web.tsx +12 -0
@@ -0,0 +1,710 @@
1
+ import React from "react";
2
+ import { I18nManager, StyleSheet, View } from "react-native";
3
+ import type * as ReactNative from "react-native";
4
+
5
+ export type OnPageScrollEventData = Readonly<{
6
+ position: number;
7
+ offset: number;
8
+ }>;
9
+
10
+ export type OnPageSelectedEventData = Readonly<{
11
+ position: number;
12
+ }>;
13
+
14
+ export type OnPageScrollStateChangedEventData = Readonly<{
15
+ pageScrollState: "idle" | "dragging" | "settling";
16
+ }>;
17
+
18
+ export type OnCollapsibleStateChangedEventData = Readonly<{
19
+ position: number;
20
+ headerOffset: number;
21
+ nativePageCount: number;
22
+ attachedPageCount: number;
23
+ observedScrollableCount: number;
24
+ retainedPages: string;
25
+ reason: string;
26
+ }>;
27
+
28
+ interface WebCollapsiblePagerNativeProps extends ReactNative.ViewProps {
29
+ scrollEnabled?: boolean;
30
+ initialPage?: number;
31
+ offscreenPageLimit?: number;
32
+ onPageScroll?: (
33
+ event: ReactNative.NativeSyntheticEvent<OnPageScrollEventData>
34
+ ) => void;
35
+ onPageSelected?: (
36
+ event: ReactNative.NativeSyntheticEvent<OnPageSelectedEventData>
37
+ ) => void;
38
+ onPageScrollStateChanged?: (
39
+ event: ReactNative.NativeSyntheticEvent<OnPageScrollStateChangedEventData>
40
+ ) => void;
41
+ onCollapsibleStateChanged?: (
42
+ event: ReactNative.NativeSyntheticEvent<OnCollapsibleStateChangedEventData>
43
+ ) => void;
44
+ }
45
+
46
+ export type CollapsiblePagerDiagnostics =
47
+ ReactNative.NativeSyntheticEvent<OnCollapsibleStateChangedEventData>;
48
+
49
+ export type CollapsiblePagerMountState = Readonly<{
50
+ position: number;
51
+ mountedPages: readonly number[];
52
+ pageKeys: readonly string[];
53
+ }>;
54
+
55
+ export interface CollapsiblePagerViewProps
56
+ extends Omit<
57
+ WebCollapsiblePagerNativeProps,
58
+ | "children"
59
+ | "headerHeight"
60
+ | "stickyHeaderHeight"
61
+ | "pageKeys"
62
+ | "retainedPages"
63
+ | "layoutDirection"
64
+ > {
65
+ header: React.ReactNode;
66
+ stickyHeader: React.ReactNode;
67
+ headerHeight: number;
68
+ stickyHeaderHeight: number;
69
+ pageRetentionDistance?: number;
70
+ layoutDirection?: "ltr" | "rtl" | "locale";
71
+ children?: React.ReactNode;
72
+ onMountedPagesChanged?: (state: CollapsiblePagerMountState) => void;
73
+ }
74
+
75
+ type State = {
76
+ selectedPage: number;
77
+ animateTransition: boolean;
78
+ };
79
+
80
+ type ConfiguredScrollElement = Readonly<{
81
+ element: HTMLElement;
82
+ paddingTop: string;
83
+ basePaddingTop: string;
84
+ boxSizing: string;
85
+ scrollTimelineName: string;
86
+ scrollTimelineAxis: string;
87
+ pagerHeaderInset: string;
88
+ pagerStickyInset: string;
89
+ }>;
90
+
91
+ const COLLAPSE_ANIMATION_NAME = "ok-collapsible-pager-collapse";
92
+ const COLLAPSE_STYLE_ID = "ok-collapsible-pager-styles";
93
+ const PAGER_HEADER_INSET = "--ok-collapsible-pager-header-inset";
94
+ const PAGER_STICKY_INSET = "--ok-collapsible-pager-sticky-inset";
95
+ let collapsiblePagerWebInstance = 0;
96
+
97
+ const pageIndex = (value: number) => {
98
+ const index = Math.trunc(value);
99
+ return Number.isFinite(index) ? index : null;
100
+ };
101
+
102
+ const clampedPageIndex = (value: number, pageCount: number) =>
103
+ Math.min(
104
+ Math.max(0, pageIndex(value) ?? 0),
105
+ Math.max(0, pageCount - 1)
106
+ );
107
+
108
+ const webElement = (node: unknown) => {
109
+ const element = node as HTMLElement | null;
110
+ return element &&
111
+ typeof element.querySelector === "function" &&
112
+ typeof element.addEventListener === "function"
113
+ ? element
114
+ : null;
115
+ };
116
+
117
+ const timelineStyle = (element: HTMLElement) =>
118
+ element.style as CSSStyleDeclaration & {
119
+ scrollTimelineName: string;
120
+ scrollTimelineAxis: string;
121
+ };
122
+
123
+ function ensureCollapseAnimation(document: Document) {
124
+ if (document.getElementById(COLLAPSE_STYLE_ID)) return;
125
+ const style = document.createElement("style");
126
+ style.id = COLLAPSE_STYLE_ID;
127
+ style.textContent = `@keyframes ${COLLAPSE_ANIMATION_NAME}{from{transform:translateY(0)}to{transform:translateY(calc(-1 * var(--ok-collapsible-header-height)))}}`;
128
+ document.head.appendChild(style);
129
+ }
130
+
131
+ const pageKey = (child: React.ReactNode, index: number) => {
132
+ if (React.isValidElement(child) && child.key != null)
133
+ return String(child.key);
134
+ return `page-${index}`;
135
+ };
136
+
137
+ /** Web counterpart of the native collapsible pager contract. */
138
+ export class CollapsiblePagerView extends React.PureComponent<
139
+ CollapsiblePagerViewProps,
140
+ State
141
+ > {
142
+ state: State = {
143
+ selectedPage: clampedPageIndex(
144
+ this.props.initialPage ?? 0,
145
+ React.Children.toArray(this.props.children).length
146
+ ),
147
+ animateTransition: true,
148
+ };
149
+
150
+ private root: HTMLElement | null = null;
151
+ private track: HTMLElement | null = null;
152
+ private touchStartX: number | null = null;
153
+ private touchStartY: number | null = null;
154
+ private horizontalTouch = false;
155
+ private pageOffsets = new Map<string, number>();
156
+ private pageHosts = new Map<number, HTMLElement>();
157
+ private sharedHeaderOffset = 0;
158
+ private scrollEnabled = this.props.scrollEnabled ?? true;
159
+ private lastMountSignature: string | null = null;
160
+ private configureFrame: number | null = null;
161
+ private transitionTimer: ReturnType<typeof setTimeout> | null = null;
162
+ private pageScrollState: OnPageScrollStateChangedEventData["pageScrollState"] =
163
+ "idle";
164
+ private configuredScrollElement: ConfiguredScrollElement | null = null;
165
+ private readonly timelineName = `--ok-collapsible-pager-${++collapsiblePagerWebInstance}`;
166
+
167
+ componentDidMount() {
168
+ if (this.root) ensureCollapseAnimation(this.root.ownerDocument);
169
+ this.scheduleScrollConfiguration();
170
+ this.notifyMountedPagesChanged();
171
+ this.emitDiagnostics("mounted");
172
+ }
173
+
174
+ componentDidUpdate(previousProps: CollapsiblePagerViewProps) {
175
+ if (previousProps.scrollEnabled !== this.props.scrollEnabled) {
176
+ this.scrollEnabled = this.props.scrollEnabled ?? true;
177
+ if (!this.scrollEnabled) {
178
+ this.touchStartX = null;
179
+ this.touchStartY = null;
180
+ this.horizontalTouch = false;
181
+ this.finishPageTransition();
182
+ }
183
+ }
184
+ const pageCount = this.pages().length;
185
+ const selectedPage = clampedPageIndex(this.state.selectedPage, pageCount);
186
+ if (selectedPage !== this.state.selectedPage) {
187
+ this.finishPageTransition();
188
+ this.setState({
189
+ selectedPage,
190
+ animateTransition: false,
191
+ });
192
+ return;
193
+ }
194
+ this.scheduleScrollConfiguration();
195
+ this.notifyMountedPagesChanged();
196
+ }
197
+
198
+ componentWillUnmount() {
199
+ this.savePageOffset(this.state.selectedPage);
200
+ if (this.configureFrame !== null) {
201
+ cancelAnimationFrame(this.configureFrame);
202
+ this.configureFrame = null;
203
+ }
204
+ if (this.transitionTimer !== null) {
205
+ clearTimeout(this.transitionTimer);
206
+ this.transitionTimer = null;
207
+ }
208
+ this.setTrack(null);
209
+ this.restoreConfiguredScrollElement();
210
+ }
211
+
212
+ public setPage = (selectedPage: number) => {
213
+ this.selectPage(selectedPage, true);
214
+ };
215
+
216
+ public setPageWithoutAnimation = (selectedPage: number) => {
217
+ this.selectPage(selectedPage, false);
218
+ };
219
+
220
+ public setScrollEnabled = (scrollEnabled: boolean) => {
221
+ this.scrollEnabled = scrollEnabled;
222
+ };
223
+
224
+ private pages() {
225
+ return React.Children.toArray(this.props.children);
226
+ }
227
+
228
+ private retentionDistance() {
229
+ return Math.max(0, Math.floor(this.props.pageRetentionDistance ?? 1));
230
+ }
231
+
232
+ private mountedPages(
233
+ pageCount = this.pages().length,
234
+ selected = this.state.selectedPage
235
+ ) {
236
+ const mounted: number[] = [];
237
+ const distance = this.retentionDistance();
238
+ for (let index = 0; index < pageCount; index += 1) {
239
+ if (Math.abs(index - selected) <= distance) mounted.push(index);
240
+ }
241
+ return mounted;
242
+ }
243
+
244
+ private nativeEvent<T>(nativeEvent: T) {
245
+ return { nativeEvent } as ReactNative.NativeSyntheticEvent<T>;
246
+ }
247
+
248
+ private emitPageScrollState(
249
+ pageScrollState: OnPageScrollStateChangedEventData["pageScrollState"]
250
+ ) {
251
+ if (pageScrollState === this.pageScrollState) return;
252
+ this.pageScrollState = pageScrollState;
253
+ this.props.onPageScrollStateChanged?.(
254
+ this.nativeEvent<OnPageScrollStateChangedEventData>({ pageScrollState })
255
+ );
256
+ }
257
+
258
+ private finishPageTransition = () => {
259
+ if (this.transitionTimer !== null) {
260
+ clearTimeout(this.transitionTimer);
261
+ this.transitionTimer = null;
262
+ }
263
+ this.emitPageScrollState("idle");
264
+ };
265
+
266
+ private beginPageTransition() {
267
+ if (this.transitionTimer !== null) clearTimeout(this.transitionTimer);
268
+ this.emitPageScrollState("settling");
269
+ this.transitionTimer = setTimeout(this.finishPageTransition, 320);
270
+ }
271
+
272
+ private onTrackTransitionEnd = (event: TransitionEvent) => {
273
+ if (event.target === this.track && event.propertyName === "transform") {
274
+ this.finishPageTransition();
275
+ }
276
+ };
277
+
278
+ private setTrack = (node: unknown) => {
279
+ const track = webElement(node);
280
+ if (track === this.track) return;
281
+ this.track?.removeEventListener(
282
+ "transitionend",
283
+ this.onTrackTransitionEnd
284
+ );
285
+ this.track = track;
286
+ this.track?.addEventListener("transitionend", this.onTrackTransitionEnd);
287
+ };
288
+
289
+ private pageScrollElement(index: number) {
290
+ const pageHost = this.pageHosts.get(index);
291
+ return (
292
+ pageHost?.querySelector<HTMLElement>(
293
+ ".ok-native-list-viewport, [data-collapsible-pager-scroll]"
294
+ ) ?? pageHost
295
+ );
296
+ }
297
+
298
+ private notifyInsetsChanged(element: HTMLElement) {
299
+ const EventConstructor = element.ownerDocument.defaultView?.Event;
300
+ if (EventConstructor) {
301
+ element.dispatchEvent(
302
+ new EventConstructor("ok-collapsible-pager-insets-changed")
303
+ );
304
+ }
305
+ }
306
+
307
+ private restoreConfiguredScrollElement() {
308
+ const configured = this.configuredScrollElement;
309
+ if (!configured) return;
310
+ configured.element.removeEventListener(
311
+ "scrollend",
312
+ this.onVerticalScrollSettled
313
+ );
314
+ const style = timelineStyle(configured.element);
315
+ style.paddingTop = configured.paddingTop;
316
+ style.boxSizing = configured.boxSizing;
317
+ style.scrollTimelineName = configured.scrollTimelineName;
318
+ style.scrollTimelineAxis = configured.scrollTimelineAxis;
319
+ style.setProperty(PAGER_HEADER_INSET, configured.pagerHeaderInset);
320
+ style.setProperty(PAGER_STICKY_INSET, configured.pagerStickyInset);
321
+ configured.element.removeAttribute("data-collapsible-pager-active");
322
+ this.notifyInsetsChanged(configured.element);
323
+ this.configuredScrollElement = null;
324
+ }
325
+
326
+ private configureActiveScrollElement = () => {
327
+ this.configureFrame = null;
328
+ const element = this.pageScrollElement(this.state.selectedPage);
329
+ if (!element) return;
330
+ if (this.configuredScrollElement?.element !== element) {
331
+ this.restoreConfiguredScrollElement();
332
+ const style = timelineStyle(element);
333
+ const computedPaddingTop =
334
+ element.ownerDocument.defaultView?.getComputedStyle(element)
335
+ .paddingTop || style.paddingTop || "0px";
336
+ this.configuredScrollElement = {
337
+ element,
338
+ paddingTop: style.paddingTop,
339
+ basePaddingTop: computedPaddingTop,
340
+ boxSizing: style.boxSizing,
341
+ scrollTimelineName: style.scrollTimelineName,
342
+ scrollTimelineAxis: style.scrollTimelineAxis,
343
+ pagerHeaderInset: style.getPropertyValue(PAGER_HEADER_INSET),
344
+ pagerStickyInset: style.getPropertyValue(PAGER_STICKY_INSET),
345
+ };
346
+ element.addEventListener("scrollend", this.onVerticalScrollSettled);
347
+ }
348
+
349
+ const style = timelineStyle(element);
350
+ const headerInset = Math.max(0, this.props.headerHeight);
351
+ const stickyInset = Math.max(0, this.props.stickyHeaderHeight);
352
+ const basePaddingTop = this.configuredScrollElement?.basePaddingTop ?? "0px";
353
+ const paddingTop = `calc(${basePaddingTop} + ${headerInset + stickyInset}px)`;
354
+ const headerInsetValue = `${headerInset}px`;
355
+ const stickyInsetValue = `${stickyInset}px`;
356
+ const insetsChanged =
357
+ style.paddingTop !== paddingTop ||
358
+ style.getPropertyValue(PAGER_HEADER_INSET) !== headerInsetValue ||
359
+ style.getPropertyValue(PAGER_STICKY_INSET) !== stickyInsetValue;
360
+ style.paddingTop = paddingTop;
361
+ style.boxSizing = "border-box";
362
+ style.scrollTimelineName = this.timelineName;
363
+ style.scrollTimelineAxis = "block";
364
+ style.setProperty(PAGER_HEADER_INSET, headerInsetValue);
365
+ style.setProperty(PAGER_STICKY_INSET, stickyInsetValue);
366
+ element.setAttribute("data-collapsible-pager-active", "true");
367
+ if (insetsChanged) this.notifyInsetsChanged(element);
368
+ };
369
+
370
+ private scheduleScrollConfiguration() {
371
+ if (this.configureFrame !== null) return;
372
+ if (typeof requestAnimationFrame === "function") {
373
+ this.configureFrame = requestAnimationFrame(
374
+ this.configureActiveScrollElement
375
+ );
376
+ } else {
377
+ this.configureActiveScrollElement();
378
+ }
379
+ }
380
+
381
+ private onVerticalScrollSettled = () => {
382
+ this.savePageOffset(this.state.selectedPage);
383
+ this.emitDiagnostics("scroll-settled");
384
+ };
385
+
386
+ private savePageOffset(index: number) {
387
+ const key = this.pages().map(pageKey)[index];
388
+ const scrollElement = this.pageScrollElement(index);
389
+ if (key && scrollElement) {
390
+ this.pageOffsets.set(key, scrollElement.scrollTop);
391
+ this.sharedHeaderOffset = Math.min(
392
+ Math.max(scrollElement.scrollTop, 0),
393
+ Math.max(0, this.props.headerHeight)
394
+ );
395
+ }
396
+ }
397
+
398
+ private restorePageOffset(index: number) {
399
+ const key = this.pages().map(pageKey)[index];
400
+ const scrollElement = this.pageScrollElement(index);
401
+ if (!key || !scrollElement) return;
402
+ scrollElement.scrollTop = Math.max(
403
+ this.pageOffsets.get(key) ?? this.sharedHeaderOffset,
404
+ this.sharedHeaderOffset
405
+ );
406
+ }
407
+
408
+ private selectPage(selectedPage: number, animated: boolean) {
409
+ const pages = this.pages();
410
+ const position = pageIndex(selectedPage);
411
+ if (
412
+ position === null ||
413
+ position < 0 ||
414
+ position >= pages.length ||
415
+ position === this.state.selectedPage
416
+ ) {
417
+ return;
418
+ }
419
+ this.savePageOffset(this.state.selectedPage);
420
+
421
+ if (animated) this.beginPageTransition();
422
+ else this.finishPageTransition();
423
+ this.setState({ selectedPage: position, animateTransition: animated }, () => {
424
+ this.scheduleScrollConfiguration();
425
+ const restore = () => {
426
+ this.configureActiveScrollElement();
427
+ this.restorePageOffset(position);
428
+ };
429
+ if (typeof requestAnimationFrame === "function") {
430
+ requestAnimationFrame(restore);
431
+ } else {
432
+ setTimeout(restore, 0);
433
+ }
434
+ this.props.onPageSelected?.(
435
+ this.nativeEvent<OnPageSelectedEventData>({ position })
436
+ );
437
+ this.props.onPageScroll?.(
438
+ this.nativeEvent<OnPageScrollEventData>({
439
+ position,
440
+ offset: 0,
441
+ })
442
+ );
443
+ this.notifyMountedPagesChanged();
444
+ this.emitDiagnostics("page-selected");
445
+ });
446
+ }
447
+
448
+ private notifyMountedPagesChanged = () => {
449
+ if (!this.props.onMountedPagesChanged) return;
450
+ const pages = this.pages();
451
+ const state = {
452
+ position: this.state.selectedPage,
453
+ mountedPages: this.mountedPages(pages.length),
454
+ pageKeys: pages.map(pageKey),
455
+ };
456
+ const signature = JSON.stringify(state);
457
+ if (signature === this.lastMountSignature) return;
458
+ this.lastMountSignature = signature;
459
+ this.props.onMountedPagesChanged(state);
460
+ };
461
+
462
+ private emitDiagnostics(reason: string) {
463
+ const pages = this.pages();
464
+ const retainedPages = this.mountedPages(pages.length);
465
+ this.props.onCollapsibleStateChanged?.(
466
+ this.nativeEvent<OnCollapsibleStateChangedEventData>({
467
+ position: this.state.selectedPage,
468
+ headerOffset: Math.min(
469
+ Math.max(
470
+ this.pageScrollElement(this.state.selectedPage)?.scrollTop ?? 0,
471
+ 0
472
+ ),
473
+ Math.max(0, this.props.headerHeight)
474
+ ),
475
+ nativePageCount: pages.length,
476
+ attachedPageCount: retainedPages.length,
477
+ observedScrollableCount: retainedPages.reduce(
478
+ (count, index) => count + (this.pageScrollElement(index) ? 1 : 0),
479
+ 0
480
+ ),
481
+ retainedPages: JSON.stringify(retainedPages),
482
+ reason,
483
+ })
484
+ );
485
+ }
486
+
487
+ private onTouchStart = (event: ReactNative.GestureResponderEvent) => {
488
+ if (!this.scrollEnabled) return;
489
+ const touch = event.nativeEvent.touches[0];
490
+ if (!touch) return;
491
+ this.touchStartX = touch.pageX;
492
+ this.touchStartY = touch.pageY;
493
+ this.horizontalTouch = false;
494
+ };
495
+
496
+ private onTouchMove = (event: ReactNative.GestureResponderEvent) => {
497
+ if (this.touchStartX == null || !this.scrollEnabled || this.horizontalTouch)
498
+ return;
499
+ const touch = event.nativeEvent.touches[0];
500
+ if (!touch) return;
501
+ const deltaX = touch.pageX - this.touchStartX;
502
+ const deltaY = touch.pageY - (this.touchStartY ?? touch.pageY);
503
+ if (Math.abs(deltaX) >= 8 && Math.abs(deltaX) > Math.abs(deltaY)) {
504
+ this.horizontalTouch = true;
505
+ this.emitPageScrollState("dragging");
506
+ }
507
+ };
508
+
509
+ private onTouchEnd = (event: ReactNative.GestureResponderEvent) => {
510
+ if (this.touchStartX == null || !this.scrollEnabled) return;
511
+ const touch = event.nativeEvent.changedTouches[0];
512
+ if (!touch) {
513
+ this.onTouchCancel();
514
+ return;
515
+ }
516
+ const delta = touch.pageX - this.touchStartX;
517
+ const verticalDelta = touch.pageY - (this.touchStartY ?? touch.pageY);
518
+ this.touchStartX = null;
519
+ this.touchStartY = null;
520
+ const rtl =
521
+ this.props.layoutDirection === "rtl" ||
522
+ ((!this.props.layoutDirection ||
523
+ this.props.layoutDirection === "locale") &&
524
+ I18nManager.isRTL);
525
+ const direction = Math.abs(delta) >= 40 && Math.abs(delta) > Math.abs(verticalDelta)
526
+ ? (delta < 0 ? 1 : -1)
527
+ : 0;
528
+ const next = this.state.selectedPage + (rtl ? -direction : direction);
529
+ if (direction === 0 || next < 0 || next >= this.pages().length) {
530
+ const wasHorizontal = this.horizontalTouch;
531
+ this.horizontalTouch = false;
532
+ if (wasHorizontal) this.finishPageTransition();
533
+ return;
534
+ }
535
+ this.horizontalTouch = false;
536
+ this.selectPage(next, true);
537
+ };
538
+
539
+ private onTouchCancel = () => {
540
+ const wasHorizontal = this.horizontalTouch;
541
+ this.touchStartX = null;
542
+ this.touchStartY = null;
543
+ this.horizontalTouch = false;
544
+ if (wasHorizontal) this.finishPageTransition();
545
+ };
546
+
547
+ render() {
548
+ const {
549
+ children: _children,
550
+ header,
551
+ stickyHeader,
552
+ headerHeight: _headerHeight,
553
+ stickyHeaderHeight: _stickyHeaderHeight,
554
+ pageRetentionDistance: _pageRetentionDistance,
555
+ layoutDirection: _layoutDirection,
556
+ scrollEnabled: _scrollEnabled,
557
+ initialPage: _initialPage,
558
+ offscreenPageLimit: _offscreenPageLimit,
559
+ onPageScroll: _onPageScroll,
560
+ onPageSelected: _onPageSelected,
561
+ onPageScrollStateChanged: _onPageScrollStateChanged,
562
+ onCollapsibleStateChanged: _onCollapsibleStateChanged,
563
+ onMountedPagesChanged: _onMountedPagesChanged,
564
+ style,
565
+ ...viewProps
566
+ } = this.props;
567
+ const pages = this.pages();
568
+ const keys = pages.map(pageKey);
569
+ const retained = new Set(this.mountedPages(pages.length));
570
+ const pageWidth = pages.length > 0 ? `${100 / pages.length}%` : "100%";
571
+ const collapseAnimation = {
572
+ "--ok-collapsible-header-height": `${Math.max(
573
+ 0,
574
+ this.props.headerHeight
575
+ )}px`,
576
+ animationName: COLLAPSE_ANIMATION_NAME,
577
+ animationDuration: "auto",
578
+ animationFillMode: "both",
579
+ animationTimingFunction: "linear",
580
+ animationTimeline: this.timelineName,
581
+ animationRange: `0px ${Math.max(1, this.props.headerHeight)}px`,
582
+ } as never;
583
+ const trackStyle = [
584
+ styles.track,
585
+ {
586
+ width: `${Math.max(1, pages.length) * 100}%`,
587
+ transform: [
588
+ {
589
+ translateX: `${
590
+ (-this.state.selectedPage * 100) / Math.max(1, pages.length)
591
+ }%`,
592
+ },
593
+ ],
594
+ transitionProperty: "transform",
595
+ transitionDuration: this.state.animateTransition ? "240ms" : "0ms",
596
+ },
597
+ ] as never;
598
+
599
+ return (
600
+ <View
601
+ {...viewProps}
602
+ ref={(node) => {
603
+ this.root = webElement(node);
604
+ }}
605
+ style={[
606
+ styles.root,
607
+ { timelineScope: this.timelineName } as never,
608
+ style,
609
+ ]}
610
+ >
611
+ <View
612
+ style={[
613
+ styles.header,
614
+ { height: Math.max(0, this.props.headerHeight) },
615
+ collapseAnimation,
616
+ ]}
617
+ >
618
+ {header}
619
+ </View>
620
+ <View
621
+ style={[
622
+ styles.sticky,
623
+ {
624
+ top: Math.max(0, this.props.headerHeight),
625
+ height: Math.max(0, this.props.stickyHeaderHeight),
626
+ },
627
+ collapseAnimation,
628
+ ]}
629
+ >
630
+ {stickyHeader}
631
+ </View>
632
+ <View
633
+ ref={this.setTrack}
634
+ style={trackStyle}
635
+ onTouchStart={this.onTouchStart}
636
+ onTouchMove={this.onTouchMove}
637
+ onTouchEnd={this.onTouchEnd}
638
+ onTouchCancel={this.onTouchCancel}
639
+ >
640
+ {pages.map((page, index) => (
641
+ <View
642
+ key={keys[index]}
643
+ ref={(node) => {
644
+ const host = webElement(node);
645
+ if (host) {
646
+ host.inert = index !== this.state.selectedPage;
647
+ this.pageHosts.set(index, host);
648
+ } else {
649
+ this.pageHosts.delete(index);
650
+ }
651
+ }}
652
+ style={[styles.page, { width: pageWidth }]}
653
+ aria-hidden={index !== this.state.selectedPage}
654
+ >
655
+ {retained.has(index) ? page : null}
656
+ </View>
657
+ ))}
658
+ </View>
659
+ </View>
660
+ );
661
+ }
662
+ }
663
+
664
+ const styles = StyleSheet.create({
665
+ root: {
666
+ flex: 1,
667
+ overflow: "hidden",
668
+ position: "relative",
669
+ } as never,
670
+ header: {
671
+ position: "absolute",
672
+ left: 0,
673
+ right: 0,
674
+ top: 0,
675
+ zIndex: 3,
676
+ overflow: "hidden",
677
+ } as never,
678
+ sticky: {
679
+ position: "absolute",
680
+ left: 0,
681
+ right: 0,
682
+ zIndex: 4,
683
+ overflow: "hidden",
684
+ } as never,
685
+ track: {
686
+ position: "absolute",
687
+ inset: 0,
688
+ display: "flex",
689
+ flexDirection: "row",
690
+ alignItems: "stretch",
691
+ touchAction: "pan-y",
692
+ } as never,
693
+ page: {
694
+ flexShrink: 0,
695
+ minWidth: 0,
696
+ minHeight: 0,
697
+ height: "100%",
698
+ display: "flex",
699
+ position: "relative",
700
+ overflowY: "auto",
701
+ overflowX: "hidden",
702
+ } as never,
703
+ });
704
+
705
+ export type CollapsiblePagerViewOnPageScrollEvent =
706
+ ReactNative.NativeSyntheticEvent<OnPageScrollEventData>;
707
+ export type CollapsiblePagerViewOnPageSelectedEvent =
708
+ ReactNative.NativeSyntheticEvent<OnPageSelectedEventData>;
709
+ export type CollapsiblePagerViewOnPageScrollStateChangedEvent =
710
+ ReactNative.NativeSyntheticEvent<OnPageScrollStateChangedEventData>;