@onekeyfe/react-native-pager-view 6.7.0-alpha.435 → 6.7.0-alpha.445

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 (25) hide show
  1. package/README.md +16 -0
  2. package/android/build.gradle +1 -0
  3. package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +135 -0
  4. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +88 -12
  5. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +442 -15
  6. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +11 -1
  7. package/android/src/main/java/com/reactnativepagerview/event/NativeHeaderPressEvent.kt +8 -0
  8. package/docs/SPEC.md +298 -3
  9. package/ios/RNCCollapsiblePagerViewComponentView.mm +834 -20
  10. package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +61 -0
  11. package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +22 -0
  12. package/lib/module/CollapsiblePagerView.js +37 -5
  13. package/lib/module/CollapsiblePagerView.web.js +519 -6
  14. package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
  15. package/lib/module/NativeScrollerNativeComponent.ts +0 -1
  16. package/lib/typescript/src/CollapsiblePagerView.d.ts +31 -2
  17. package/lib/typescript/src/CollapsiblePagerView.web.d.ts +44 -1
  18. package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +11 -0
  19. package/lib/typescript/src/NativeScroller.d.ts +2 -2
  20. package/package.json +3 -2
  21. package/src/CollapsiblePagerView.tsx +68 -5
  22. package/src/CollapsiblePagerView.web.tsx +464 -5
  23. package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
  24. package/src/NativeScroller.tsx +2 -3
  25. package/src/NativeScrollerNativeComponent.ts +0 -1
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  import React from "react";
4
- import { I18nManager, Platform, StyleSheet, View } from "react-native";
4
+ import { createNamedIcon } from "@onekeyfe/react-native-image/icon/dom";
5
+ import { I18nManager, Platform, processColor, StyleSheet, View } from "react-native";
5
6
  import { WebCollapsiblePagerPage } from "./WebCollapsiblePagerPage.js";
6
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
7
8
  const COLLAPSE_ANIMATION_NAME = "ok-collapsible-pager-collapse";
8
9
  const COLLAPSE_STYLE_ID = "ok-collapsible-pager-styles";
9
10
  const PAGER_HEADER_INSET = "--ok-collapsible-pager-header-inset";
@@ -48,6 +49,10 @@ export class CollapsiblePagerView extends React.PureComponent {
48
49
  };
49
50
  root = null;
50
51
  track = null;
52
+ tabProgress = null;
53
+ tabProgressFrame = null;
54
+ tabProgressGeneration = 0;
55
+ tabButtons = new Map();
51
56
  headerSlot = null;
52
57
  stickySlot = null;
53
58
  cssTimelineSupported = false;
@@ -80,6 +85,7 @@ export class CollapsiblePagerView extends React.PureComponent {
80
85
  configureFrame = null;
81
86
  pendingTopFrame = null;
82
87
  transitionTimer = null;
88
+ transitionGeneration = 0;
83
89
  pageScrollState = "idle";
84
90
  configuredScrollElement = null;
85
91
  timelineName = `--ok-collapsible-pager-${++collapsiblePagerWebInstance}`;
@@ -115,6 +121,9 @@ export class CollapsiblePagerView extends React.PureComponent {
115
121
  this.scheduleScrollConfiguration();
116
122
  this.syncFallbackHeader();
117
123
  this.notifyMountedPagesChanged();
124
+ this.syncToolbar();
125
+ this.tabProgress = this.state.selectedPage;
126
+ this.syncTabColors();
118
127
  this.emitDiagnostics("mounted");
119
128
  }
120
129
  componentDidUpdate(previousProps, _previousState, routeSnapshot) {
@@ -166,12 +175,17 @@ export class CollapsiblePagerView extends React.PureComponent {
166
175
  this.scheduleScrollConfiguration();
167
176
  this.syncFallbackHeader();
168
177
  this.notifyMountedPagesChanged();
178
+ this.syncToolbar();
179
+ if (previousProps.isActive === false && this.props.isActive !== false) this.readTabProgress();
180
+ if (this.props.isActive === false || !this.tabColorWeights()) this.cancelTabProgressFrame();
181
+ this.syncTabColors();
169
182
  }
170
183
  componentWillUnmount() {
171
184
  // Activity can disconnect effects before the inactive update commits.
172
185
  const routeTarget = this.captureRouteTarget(this.props);
173
186
  if (routeTarget) this.setRouteTarget(routeTarget);
174
187
  this.disposed = true;
188
+ this.setToolbarSlot(null);
175
189
  this.selectionRestoreToken += 1;
176
190
  this.cancelPendingTopFrame();
177
191
  this.savePageOffset(this.state.selectedPage);
@@ -214,6 +228,7 @@ export class CollapsiblePagerView extends React.PureComponent {
214
228
  };
215
229
  observeChrome() {
216
230
  this.chromeObserver = new ResizeObserver(this.scheduleChromeMeasurement);
231
+ if (this.root) this.chromeObserver.observe(this.root);
217
232
  if (this.headerContent) this.chromeObserver.observe(this.headerContent);
218
233
  if (this.stickyContent) this.chromeObserver.observe(this.stickyContent);
219
234
  this.scheduleChromeMeasurement();
@@ -232,6 +247,8 @@ export class CollapsiblePagerView extends React.PureComponent {
232
247
  this.measuredStickyHeight = nextSticky;
233
248
  this.headerMeasured = Boolean(header?.isConnected);
234
249
  this.stickyMeasured = Boolean(sticky?.isConnected);
250
+ this.syncToolbar();
251
+ this.syncTabColors();
235
252
  if (headerChanged || stickyChanged) {
236
253
  this.forceUpdate();
237
254
  this.scheduleScrollConfiguration();
@@ -442,29 +459,138 @@ export class CollapsiblePagerView extends React.PureComponent {
442
459
  }));
443
460
  }
444
461
  finishPageTransition = () => {
462
+ this.transitionGeneration += 1;
445
463
  if (this.transitionTimer !== null) {
446
464
  clearTimeout(this.transitionTimer);
447
465
  this.transitionTimer = null;
448
466
  }
467
+ this.readTabProgress();
468
+ this.cancelTabProgressFrame();
469
+ this.syncTabColors();
449
470
  this.emitPageScrollState("idle");
471
+ this.syncToolbar();
450
472
  };
451
473
  beginPageTransition() {
474
+ this.suspendToolbar();
452
475
  if (this.transitionTimer !== null) clearTimeout(this.transitionTimer);
476
+ const generation = ++this.transitionGeneration;
453
477
  this.emitPageScrollState("settling");
454
- this.transitionTimer = setTimeout(this.finishPageTransition, 320);
478
+ this.transitionTimer = setTimeout(() => {
479
+ if (generation === this.transitionGeneration) this.finishPageTransition();
480
+ }, 320);
455
481
  }
456
482
  onTrackTransitionEnd = event => {
457
483
  if (event.target === this.track && event.propertyName === "transform") {
484
+ this.readTabProgress();
485
+ if (this.tabColorWeights() && this.tabProgress !== null && Math.abs(this.tabProgress - this.state.selectedPage) > 0.001) return;
458
486
  this.finishPageTransition();
459
487
  }
460
488
  };
461
489
  setTrack = node => {
462
490
  const track = webElement(node);
463
491
  if (track === this.track) return;
492
+ this.cancelTabProgressFrame();
464
493
  this.track?.removeEventListener("transitionend", this.onTrackTransitionEnd);
465
494
  this.track = track;
466
495
  this.track?.addEventListener("transitionend", this.onTrackTransitionEnd);
467
496
  };
497
+ cancelTabProgressFrame() {
498
+ this.tabProgressGeneration += 1;
499
+ if (this.tabProgressFrame !== null) cancelAnimationFrame(this.tabProgressFrame);
500
+ this.tabProgressFrame = null;
501
+ }
502
+ readTabProgress() {
503
+ const track = this.track;
504
+ const width = this.root?.getBoundingClientRect().width ?? 0;
505
+ if (!track?.isConnected || width <= 0) return null;
506
+ const transform = track.ownerDocument.defaultView?.getComputedStyle(track).transform;
507
+ const matrix = transform?.match(/^matrix(3d)?\(([^)]+)\)$/);
508
+ const offset = matrix ? Number(matrix[2].split(",")[matrix[1] ? 12 : 4]) : transform === "none" ? 0 : NaN;
509
+ if (!Number.isFinite(offset)) return null;
510
+ this.tabProgress = Math.min(Math.max(0, -offset / width), Math.max(0, this.pages().length - 1));
511
+ return this.tabProgress;
512
+ }
513
+ startTabProgressFrames() {
514
+ this.cancelTabProgressFrame();
515
+ if (!this.tabColorWeights() || typeof requestAnimationFrame !== "function") return;
516
+ const track = this.track;
517
+ const root = this.root;
518
+ const width = root?.getBoundingClientRect().width ?? 0;
519
+ if (!track?.isConnected || !root?.isConnected || width <= 0 || this.props.isActive === false) return;
520
+ const generation = this.tabProgressGeneration;
521
+ const selection = this.selectionRestoreToken;
522
+ const keys = JSON.stringify(this.pages().map(pageKey));
523
+ const owner = this.pageScrollElement(this.state.selectedPage);
524
+ const sample = () => {
525
+ if (generation !== this.tabProgressGeneration || this.disposed) return;
526
+ this.tabProgressFrame = null;
527
+ if (this.track !== track || this.root !== root || !track.isConnected || !root.isConnected || this.props.isActive === false || selection !== this.selectionRestoreToken || keys !== JSON.stringify(this.pages().map(pageKey)) || owner !== this.pageScrollElement(this.state.selectedPage) || width !== root.getBoundingClientRect().width || !this.tabColorWeights()) {
528
+ this.cancelTabProgressFrame();
529
+ this.readTabProgress();
530
+ this.syncTabColors();
531
+ if (this.transitionTimer !== null) this.startTabProgressFrames();
532
+ return;
533
+ }
534
+ this.readTabProgress();
535
+ this.syncTabColors();
536
+ if (this.transitionTimer !== null) this.tabProgressFrame = requestAnimationFrame(sample);
537
+ };
538
+ sample();
539
+ }
540
+ tabColorWeights() {
541
+ const config = this.props.nativeTabBar;
542
+ if (!config?.controlled || config.style?.variant !== "text" || this.tabProgress === null) return null;
543
+ const keys = [];
544
+ React.Children.forEach(this.props.children, child => {
545
+ if (child !== null && child !== undefined && typeof child !== "boolean") keys.push(pageKey(child, keys.length));
546
+ });
547
+ const itemKeys = config.items.map(item => item.key);
548
+ if (!keys.length || !keys.includes(config.selectedKey ?? "") || new Set(keys).size !== keys.length || new Set(itemKeys).size !== itemKeys.length || keys.some(key => !itemKeys.includes(key))) return null;
549
+ const progress = Math.min(Math.max(0, this.tabProgress), keys.length - 1);
550
+ const source = Math.floor(progress);
551
+ const destination = Math.min(source + 1, keys.length - 1);
552
+ const fraction = progress - source;
553
+ return new Map(keys.map((key, index) => [key, index === source ? 1 - fraction : index === destination ? fraction : 0]));
554
+ }
555
+ tabFontOwnerKey(weights) {
556
+ const config = this.props.nativeTabBar;
557
+ const semantic = config?.controlled ? config.selectedKey : config?.items[this.state.selectedPage]?.key;
558
+ if (!weights || this.tabProgress === null) return semantic;
559
+ const keys = Array.from(weights.keys());
560
+ const progress = Math.min(Math.max(0, this.tabProgress), keys.length - 1);
561
+ return keys[Math.floor(progress + 0.5)];
562
+ }
563
+ tabColor(selected, weight) {
564
+ const style = this.props.nativeTabBar?.style;
565
+ const active = style?.activeTextColor ?? "#000000";
566
+ const inactive = style?.inactiveTextColor ?? "#666666";
567
+ if (weight === undefined) return String(selected ? active : inactive);
568
+ if (weight <= 0) return String(inactive);
569
+ if (weight >= 1) return String(active);
570
+ const first = processColor(inactive);
571
+ const last = processColor(active);
572
+ if (typeof first !== "number" || typeof last !== "number") return String(selected ? active : inactive);
573
+ const channel = shift => (first >>> shift & 255) * (1 - weight) + (last >>> shift & 255) * weight;
574
+ return `rgba(${Math.round(channel(16))}, ${Math.round(channel(8))}, ${Math.round(channel(0))}, ${(channel(24) / 255).toFixed(4)})`;
575
+ }
576
+ syncTabColors() {
577
+ const weights = this.tabColorWeights();
578
+ const config = this.props.nativeTabBar;
579
+ if (!config) return;
580
+ const selectedKey = config.controlled ? config.selectedKey : config.items[this.state.selectedPage]?.key;
581
+ const fontOwnerKey = this.tabFontOwnerKey(weights);
582
+ for (const [key, button] of this.tabButtons) {
583
+ const fontSelected = key === fontOwnerKey;
584
+ button.style.color = this.tabColor(key === selectedKey, weights ? weights.get(key) ?? 0 : undefined);
585
+ button.style.fontWeight = config.style?.variant === "text" && fontSelected ? "600" : "500";
586
+ const fontFamily = (fontSelected ? config.style?.activeFontFamily ?? config.style?.fontFamily : config.style?.fontFamily) ?? "";
587
+ if (config.controlled && config.style?.variant === "text") {
588
+ button.style.fontFamily = config.style?.fontFamily ?? "";
589
+ const label = button.querySelector(".ok-pager-tab-label");
590
+ if (label) label.style.fontFamily = fontFamily;
591
+ } else button.style.fontFamily = fontFamily;
592
+ }
593
+ }
468
594
  pageScrollElement(index) {
469
595
  const pageHost = this.pageHosts.get(index);
470
596
  return pageHost?.querySelector("[data-collapsible-pager-scroll]") ?? pageHost?.querySelector(".ok-native-list-viewport") ?? pageHost;
@@ -479,7 +605,9 @@ export class CollapsiblePagerView extends React.PureComponent {
479
605
  this.root?.removeEventListener("touchend", this.onOverlayTouchEnd);
480
606
  this.root?.removeEventListener("touchcancel", this.onOverlayTouchEnd);
481
607
  this.overlayTouch = null;
608
+ if (this.root) this.chromeObserver?.unobserve(this.root);
482
609
  this.root = root;
610
+ if (root) this.chromeObserver?.observe(root);else this.suspendToolbar();
483
611
  root?.addEventListener("ok-collapsible-pager-scroll-key-changed", this.onScrollOwnerIdentityChanged);
484
612
  root?.addEventListener("wheel", this.onOverlayWheel, {
485
613
  passive: false
@@ -749,11 +877,15 @@ export class CollapsiblePagerView extends React.PureComponent {
749
877
  if (this.routeTarget && this.routeTarget.key !== expectedKey) this.setRouteTarget(null);
750
878
  this.cancelPendingTopFrame();
751
879
  this.savePageOffset(this.state.selectedPage);
752
- if (animated) this.beginPageTransition();else this.finishPageTransition();
880
+ if (animated) this.beginPageTransition();else {
881
+ this.finishPageTransition();
882
+ this.tabProgress = position;
883
+ }
753
884
  this.setState({
754
885
  selectedPage: position,
755
886
  animateTransition: animated
756
887
  }, () => {
888
+ if (animated) this.startTabProgressFrames();
757
889
  this.scheduleScrollConfiguration();
758
890
  const restore = () => {
759
891
  if (this.selectionRestoreToken !== selectionToken || this.state.selectedPage !== position || this.pages().map(pageKey)[position] !== expectedKey || !this.pageScrollElement(position)?.isConnected) return;
@@ -824,6 +956,7 @@ export class CollapsiblePagerView extends React.PureComponent {
824
956
  this.cancelPendingTopFrame();
825
957
  this.horizontalTouch = true;
826
958
  this.emitPageScrollState("dragging");
959
+ this.suspendToolbar();
827
960
  }
828
961
  };
829
962
  onTouchEnd = event => {
@@ -856,9 +989,387 @@ export class CollapsiblePagerView extends React.PureComponent {
856
989
  this.horizontalTouch = false;
857
990
  if (wasHorizontal) this.finishPageTransition();
858
991
  };
992
+ toolbarSlot = null;
993
+ toolbarButton = null;
994
+ toolbarIconIdentity = "";
995
+ toolbarLoadedIcon = null;
996
+ toolbarPendingIdentity = "";
997
+ toolbarReadyIcon = null;
998
+ toolbarFailedIdentity = "";
999
+ toolbarAnimation = null;
1000
+ toolbarPresence = false;
1001
+ toolbarPresenceInterrupted = false;
1002
+ toolbarImage = null;
1003
+ toolbarGeneration = 0;
1004
+ ownsSettledToolbar() {
1005
+ return this.props.nativeTabBar?.controlled === true && this.props.nativeTabBar.toolbar !== undefined;
1006
+ }
1007
+ toolbarCanCommit() {
1008
+ if (!this.ownsSettledToolbar()) return true;
1009
+ if (this.disposed || this.props.isActive === false || this.horizontalTouch || this.transitionTimer !== null || this.pageScrollState !== "idle" || !this.root?.isConnected) return false;
1010
+ const actual = this.readTabProgress();
1011
+ if (actual === null || Math.abs(actual - this.state.selectedPage) > 0.001) return false;
1012
+ const config = this.props.nativeTabBar;
1013
+ const keys = [];
1014
+ React.Children.forEach(this.props.children, child => {
1015
+ if (child !== null && child !== undefined && typeof child !== "boolean") keys.push(pageKey(child, keys.length));
1016
+ });
1017
+ const itemKeys = config.items.map(item => item.key);
1018
+ const valid = keys.length > 0 && new Set(keys).size === keys.length && new Set(itemKeys).size === itemKeys.length && keys.every(key => itemKeys.includes(key));
1019
+ return !valid || !keys.includes(config.selectedKey ?? "") || keys[this.state.selectedPage] === config.selectedKey;
1020
+ }
1021
+ cancelToolbarPending() {
1022
+ if (this.toolbarPendingIdentity || this.toolbarImage || this.toolbarReadyIcon) this.toolbarGeneration += 1;
1023
+ if (this.toolbarImage) this.toolbarImage.onload = this.toolbarImage.onerror = null;
1024
+ this.toolbarImage = null;
1025
+ this.toolbarPendingIdentity = "";
1026
+ this.toolbarReadyIcon = null;
1027
+ }
1028
+ setToolbarInteractive(enabled) {
1029
+ const button = this.toolbarButton;
1030
+ if (!button) return;
1031
+ if (!enabled) {
1032
+ if (button.ownerDocument.activeElement === button) button.blur();
1033
+ if (this.toolbarTouchKey !== undefined) this.toolbarTouchKey = null;
1034
+ }
1035
+ button.disabled = !enabled;
1036
+ button.style.pointerEvents = enabled ? "auto" : "none";
1037
+ button.tabIndex = enabled ? 0 : -1;
1038
+ button.setAttribute("aria-hidden", String(!enabled));
1039
+ }
1040
+ suspendToolbar() {
1041
+ if (!this.ownsSettledToolbar()) return;
1042
+ this.setToolbarInteractive(false);
1043
+ this.cancelToolbarPending();
1044
+ if (this.toolbarAnimation && this.toolbarButton) {
1045
+ const opacity = this.toolbarButton.ownerDocument.defaultView?.getComputedStyle(this.toolbarButton).opacity;
1046
+ this.toolbarAnimation.cancel();
1047
+ this.toolbarAnimation = null;
1048
+ this.toolbarButton.style.opacity = opacity || this.toolbarButton.style.opacity;
1049
+ this.toolbarPresenceInterrupted = true;
1050
+ }
1051
+ }
1052
+ setToolbarSlot = slot => {
1053
+ if (this.toolbarSlot === slot) return;
1054
+ this.toolbarAnimation?.cancel();
1055
+ this.toolbarAnimation = null;
1056
+ this.cancelToolbarPending();
1057
+ this.toolbarGeneration += 1;
1058
+ this.toolbarButton?.remove();
1059
+ this.toolbarButton = null;
1060
+ this.toolbarOwnerKey = undefined;
1061
+ this.toolbarOwnerEntry = undefined;
1062
+ this.toolbarIconIdentity = "";
1063
+ this.toolbarLoadedIcon = null;
1064
+ this.toolbarFailedIdentity = "";
1065
+ this.toolbarPresence = false;
1066
+ this.toolbarPresenceInterrupted = false;
1067
+ this.toolbarTouchKey = null;
1068
+ this.toolbarSlot = slot;
1069
+ if (slot) this.syncToolbar();
1070
+ };
1071
+ animateToolbar(visible) {
1072
+ const button = this.toolbarButton;
1073
+ if (!button || this.toolbarPresence === visible && !this.toolbarPresenceInterrupted) return;
1074
+ this.toolbarPresenceInterrupted = false;
1075
+ this.toolbarPresence = visible;
1076
+ const current = button.ownerDocument.defaultView?.getComputedStyle(button).opacity ?? button.style.opacity;
1077
+ this.toolbarAnimation?.cancel();
1078
+ this.toolbarAnimation = null;
1079
+ button.style.opacity = visible ? "1" : "0";
1080
+ const finish = () => {
1081
+ button.hidden = !visible;
1082
+ if (!visible && !this.toolbarOwnerKey && this.toolbarButton === button) {
1083
+ button.remove();
1084
+ this.toolbarButton = null;
1085
+ }
1086
+ };
1087
+ if (typeof button.animate !== "function") {
1088
+ finish();
1089
+ return;
1090
+ }
1091
+ button.hidden = false;
1092
+ const animation = button.animate([{
1093
+ opacity: current || "0"
1094
+ }, {
1095
+ opacity: visible ? "1" : "0"
1096
+ }], {
1097
+ duration: 100,
1098
+ fill: "forwards"
1099
+ });
1100
+ this.toolbarAnimation = animation;
1101
+ animation.onfinish = () => {
1102
+ if (this.toolbarAnimation !== animation || this.toolbarButton !== button || this.toolbarPresence !== visible) return;
1103
+ animation.cancel();
1104
+ this.toolbarAnimation = null;
1105
+ finish();
1106
+ };
1107
+ }
1108
+ hideToolbar(failed) {
1109
+ this.setToolbarInteractive(false);
1110
+ this.toolbarOwnerKey = undefined;
1111
+ this.toolbarOwnerEntry = undefined;
1112
+ this.toolbarButton?.removeAttribute("data-testid");
1113
+ this.toolbarButton?.removeAttribute("aria-label");
1114
+ if (failed) this.toolbarButton?.replaceChildren();
1115
+ this.animateToolbar(false);
1116
+ }
1117
+ commitToolbar(key, entry, icon, identity) {
1118
+ const button = this.toolbarButton;
1119
+ if (!button || !this.toolbarCanCommit()) return;
1120
+ if (this.toolbarOwnerKey !== key && this.toolbarTouchKey !== undefined) this.toolbarTouchKey = null;
1121
+ const size = Math.min(128, Math.max(1, entry.size ?? 38));
1122
+ const iconSize = Math.min(size, Math.max(1, entry.iconSize ?? 24));
1123
+ const contentWidth = Math.min(size, Math.max(0, entry.contentWidth ?? 24));
1124
+ const trailingInset = Math.min(128, Math.max(0, entry.trailingInset ?? 20));
1125
+ if (button.firstChild !== icon) button.replaceChildren(icon);
1126
+ Object.assign(button.style, {
1127
+ position: "absolute",
1128
+ width: `${size}px`,
1129
+ height: `${size}px`,
1130
+ borderRadius: "50%",
1131
+ top: "50%",
1132
+ transform: "translateY(-50%)",
1133
+ insetInlineEnd: `${trailingInset - (size - contentWidth) / 2}px`,
1134
+ padding: `${(size - iconSize) / 2}px`,
1135
+ color: entry.iconColor ?? "#000000"
1136
+ });
1137
+ button.style.setProperty("--pager-toolbar-base", entry.selected ? entry.selectedBackgroundColor ?? "transparent" : "transparent");
1138
+ button.style.setProperty("--pager-toolbar-hover", entry.hoverBackgroundColor ?? "transparent");
1139
+ button.style.setProperty("--pager-toolbar-pressed", entry.pressedBackgroundColor ?? "transparent");
1140
+ button.setAttribute("aria-label", entry.accessibilityLabel ?? "");
1141
+ if (entry.testID) button.setAttribute("data-testid", entry.testID);else button.removeAttribute("data-testid");
1142
+ this.toolbarOwnerKey = key;
1143
+ this.toolbarOwnerEntry = {
1144
+ ...entry
1145
+ };
1146
+ this.toolbarIconIdentity = identity;
1147
+ this.toolbarLoadedIcon = icon;
1148
+ this.toolbarPendingIdentity = "";
1149
+ this.toolbarReadyIcon = null;
1150
+ this.toolbarImage = null;
1151
+ this.animateToolbar(true);
1152
+ this.setToolbarInteractive(true);
1153
+ }
1154
+ syncToolbar() {
1155
+ const slot = this.toolbarSlot;
1156
+ if (!slot) return;
1157
+ if (!this.toolbarCanCommit()) {
1158
+ this.suspendToolbar();
1159
+ return;
1160
+ }
1161
+ const config = this.props.nativeTabBar;
1162
+ const key = config?.controlled ? config.selectedKey : config?.items[this.state.selectedPage]?.key;
1163
+ const entry = key ? config?.toolbar?.[key] : undefined;
1164
+ if (!entry?.iconName || !key) {
1165
+ this.cancelToolbarPending();
1166
+ this.hideToolbar(false);
1167
+ return;
1168
+ }
1169
+ let button = this.toolbarButton;
1170
+ if (!button) {
1171
+ button = slot.ownerDocument.createElement("button");
1172
+ button.type = "button";
1173
+ button.className = "ok-pager-toolbar";
1174
+ button.style.opacity = "0";
1175
+ button.hidden = true;
1176
+ button.disabled = true;
1177
+ button.addEventListener("pointerdown", () => {
1178
+ this.toolbarTouchKey = button.disabled ? null : this.toolbarOwnerKey ?? null;
1179
+ });
1180
+ button.addEventListener("pointercancel", () => {
1181
+ this.toolbarTouchKey = null;
1182
+ });
1183
+ button.onclick = event => {
1184
+ if (this.toolbarButton !== event.currentTarget) return;
1185
+ const pointer = event.detail !== 0;
1186
+ const touched = this.toolbarTouchKey;
1187
+ if (pointer) this.toolbarTouchKey = undefined;
1188
+ const current = this.props.nativeTabBar;
1189
+ const semantic = current?.controlled ? current.selectedKey : current?.items[this.state.selectedPage]?.key;
1190
+ const owner = this.toolbarOwnerKey;
1191
+ if (button.disabled || !this.toolbarCanCommit() || !owner || owner !== semantic || !this.toolbarOwnerEntry || pointer && touched !== owner) return;
1192
+ const rect = button.getBoundingClientRect();
1193
+ this.props.onNativeToolbarPress?.({
1194
+ nativeEvent: {
1195
+ key: owner,
1196
+ anchorRect: {
1197
+ x: rect.x,
1198
+ y: rect.y,
1199
+ width: rect.width,
1200
+ height: rect.height
1201
+ }
1202
+ }
1203
+ });
1204
+ };
1205
+ slot.appendChild(button);
1206
+ this.toolbarButton = button;
1207
+ this.toolbarPresence = false;
1208
+ }
1209
+ const size = Math.min(128, Math.max(1, entry.size ?? 38));
1210
+ const iconSize = Math.min(size, Math.max(1, entry.iconSize ?? 24));
1211
+ const identity = `${entry.iconName}|${entry.iconColor ?? "#000000"}|${iconSize}`;
1212
+ if (identity === this.toolbarIconIdentity && this.toolbarLoadedIcon) {
1213
+ this.cancelToolbarPending();
1214
+ this.commitToolbar(key, entry, this.toolbarLoadedIcon, identity);
1215
+ return;
1216
+ }
1217
+ if (identity === this.toolbarPendingIdentity) {
1218
+ if (this.toolbarReadyIcon) this.commitToolbar(key, entry, this.toolbarReadyIcon, identity);
1219
+ return;
1220
+ }
1221
+ this.cancelToolbarPending();
1222
+ this.setToolbarInteractive(false);
1223
+ this.toolbarPendingIdentity = identity;
1224
+ if (identity === this.toolbarFailedIdentity) {
1225
+ this.hideToolbar(true);
1226
+ return;
1227
+ }
1228
+ const generation = ++this.toolbarGeneration;
1229
+ const icon = createNamedIcon(button.ownerDocument, entry.iconName);
1230
+ icon.setAttribute("width", String(iconSize));
1231
+ icon.setAttribute("height", String(iconSize));
1232
+ const use = icon.querySelector("use");
1233
+ const fail = () => {
1234
+ if (this.toolbarGeneration !== generation || this.toolbarPendingIdentity !== identity) return;
1235
+ this.toolbarImage = null;
1236
+ this.toolbarFailedIdentity = identity;
1237
+ this.hideToolbar(true);
1238
+ };
1239
+ if (!use) {
1240
+ fail();
1241
+ return;
1242
+ }
1243
+ const ready = () => {
1244
+ if (this.disposed || this.toolbarGeneration !== generation || this.toolbarPendingIdentity !== identity) return;
1245
+ this.toolbarImage = null;
1246
+ this.toolbarReadyIcon = icon;
1247
+ this.syncToolbar();
1248
+ };
1249
+ const href = use.getAttribute("href") ?? use.getAttribute("xlink:href") ?? "";
1250
+ const view = button.ownerDocument.defaultView;
1251
+ if (view?.Image && href.split("#")[0]) {
1252
+ const image = new view.Image();
1253
+ this.toolbarImage = image;
1254
+ image.onload = ready;
1255
+ image.onerror = fail;
1256
+ image.src = href.split("#")[0];
1257
+ } else ready();
1258
+ }
1259
+ renderNativeTabBar() {
1260
+ const config = this.props.nativeTabBar;
1261
+ if (!config?.items.length || config.style?.variant !== "text" && config.controlled !== true && config.toolbar === undefined) return null;
1262
+ const style = config.style;
1263
+ const variant = style?.variant ?? "underline";
1264
+ const selectedKey = config.controlled ? config.selectedKey : config.items[this.state.selectedPage]?.key;
1265
+ const toolbarEntries = config.toolbar === undefined ? undefined : Object.values(config.toolbar);
1266
+ const reservedWidth = toolbarEntries === undefined ? 0 : toolbarEntries.length === 0 ? 44 : Math.max(...toolbarEntries.map(entry => {
1267
+ const size = Math.min(128, Math.max(1, entry.size ?? 38));
1268
+ return Math.min(size, Math.max(0, entry.contentWidth ?? 24)) + Math.min(128, Math.max(0, entry.trailingInset ?? 20));
1269
+ }));
1270
+ const segmented = variant === "segmented";
1271
+ const reserveTextFaces = config.controlled && variant === "text";
1272
+ const colorWeights = this.tabColorWeights();
1273
+ const fontOwnerKey = this.tabFontOwnerKey(colorWeights);
1274
+ return /*#__PURE__*/React.createElement("div", {
1275
+ className: "ok-pager-tab-bar",
1276
+ dir: this.props.layoutDirection === "rtl" || (this.props.layoutDirection ?? "locale") === "locale" && I18nManager.isRTL ? "rtl" : "ltr",
1277
+ style: {
1278
+ display: "flex",
1279
+ alignItems: "center",
1280
+ position: "relative",
1281
+ height: style?.height ?? 44,
1282
+ backgroundColor: String(style?.backgroundColor ?? "transparent"),
1283
+ minWidth: 0
1284
+ }
1285
+ }, /*#__PURE__*/React.createElement("style", null, `.ok-pager-tab{box-sizing:border-box;appearance:none;border:0;background:transparent;cursor:default;white-space:nowrap;flex-shrink:0;line-height:normal}.ok-pager-toolbar{box-sizing:border-box;appearance:none;border:1px solid transparent;cursor:default;display:flex;align-items:center;justify-content:center;background:var(--pager-toolbar-base)}.ok-pager-toolbar:hover:enabled{background:var(--pager-toolbar-hover)}.ok-pager-toolbar:active:enabled{background:var(--pager-toolbar-pressed)}`), /*#__PURE__*/React.createElement("div", {
1286
+ role: "tablist",
1287
+ style: {
1288
+ display: "flex",
1289
+ overflowX: segmented ? "hidden" : "auto",
1290
+ flex: 1,
1291
+ padding: `${style?.contentPaddingVertical ?? 0}px ${style?.contentPaddingHorizontal ?? 20}px`,
1292
+ gap: style?.itemSpacing ?? 8
1293
+ }
1294
+ }, ...config.items.map((item, position) => {
1295
+ const selected = item.key === selectedKey;
1296
+ const fontSelected = item.key === fontOwnerKey;
1297
+ return /*#__PURE__*/React.createElement("button", {
1298
+ key: item.key,
1299
+ role: "tab",
1300
+ type: "button",
1301
+ className: "ok-pager-tab",
1302
+ "aria-selected": selected,
1303
+ ref: button => {
1304
+ if (button) this.tabButtons.set(item.key, button);else this.tabButtons.delete(item.key);
1305
+ },
1306
+ "aria-label": item.accessibilityLabel ?? item.title,
1307
+ "data-testid": item.testID,
1308
+ style: {
1309
+ padding: variant === "text" ? "6px 10px" : "6px 8px",
1310
+ minWidth: 44,
1311
+ height: (style?.height ?? 44) - 2 * (style?.contentPaddingVertical ?? 0),
1312
+ fontSize: style?.fontSize ?? (variant === "text" ? 18 : 16),
1313
+ fontWeight: variant === "text" && fontSelected ? 600 : 500,
1314
+ fontFamily: reserveTextFaces ? style?.fontFamily : fontSelected ? style?.activeFontFamily ?? style?.fontFamily : style?.fontFamily,
1315
+ display: reserveTextFaces ? "inline-grid" : undefined,
1316
+ gridTemplateAreas: reserveTextFaces ? '"label"' : undefined,
1317
+ alignItems: reserveTextFaces ? "center" : undefined,
1318
+ color: this.tabColor(selected, colorWeights ? colorWeights.get(item.key) ?? 0 : undefined),
1319
+ flex: segmented ? 1 : undefined,
1320
+ borderRadius: segmented ? 999 : 0,
1321
+ backgroundColor: segmented && selected ? String(style?.indicatorColor ?? "transparent") : "transparent",
1322
+ borderBottom: variant === "underline" && selected ? `${style?.indicatorHeight ?? 2}px solid ${String(style?.indicatorColor ?? "#000000")}` : undefined
1323
+ },
1324
+ onClick: () => {
1325
+ if (!config.controlled) {
1326
+ const animated = this.props.nativeTabPressAnimationEnabled ?? Math.abs(position - this.state.selectedPage) <= 1;
1327
+ if (animated) this.setPage(position);else this.setPageWithoutAnimation(position);
1328
+ }
1329
+ this.props.onNativeTabPress?.({
1330
+ nativeEvent: {
1331
+ position,
1332
+ key: item.key
1333
+ }
1334
+ });
1335
+ }
1336
+ }, reserveTextFaces ? [/*#__PURE__*/React.createElement("span", {
1337
+ key: "label",
1338
+ className: "ok-pager-tab-label",
1339
+ style: {
1340
+ gridArea: "label",
1341
+ fontFamily: fontSelected ? style?.activeFontFamily ?? style?.fontFamily : style?.fontFamily
1342
+ }
1343
+ }, item.title), ...[false, true].map(active => /*#__PURE__*/React.createElement("span", {
1344
+ key: active ? "active-width" : "normal-width",
1345
+ "aria-hidden": true,
1346
+ style: {
1347
+ gridArea: "label",
1348
+ visibility: "hidden",
1349
+ pointerEvents: "none",
1350
+ userSelect: "none",
1351
+ fontWeight: active ? 600 : 500,
1352
+ fontFamily: active ? style?.activeFontFamily ?? style?.fontFamily : style?.fontFamily
1353
+ }
1354
+ }, item.title))] : item.title);
1355
+ })), config.toolbar !== undefined ? /*#__PURE__*/React.createElement("div", {
1356
+ ref: this.setToolbarSlot,
1357
+ className: "ok-pager-toolbar-slot",
1358
+ style: {
1359
+ flexShrink: 0,
1360
+ width: reservedWidth,
1361
+ height: "100%",
1362
+ position: "relative"
1363
+ }
1364
+ }) : null);
1365
+ }
859
1366
  render() {
860
1367
  const {
861
1368
  children: _children,
1369
+ nativeTabBar: _nativeTabBar,
1370
+ nativeTabPressAnimationEnabled: _nativeTabPressAnimationEnabled,
1371
+ onNativeTabPress: _onNativeTabPress,
1372
+ onNativeToolbarPress: _onNativeToolbarPress,
862
1373
  header,
863
1374
  stickyHeader,
864
1375
  topOverlay: _topOverlay,
@@ -927,9 +1438,11 @@ export class CollapsiblePagerView extends React.PureComponent {
927
1438
  top: this.headerHeight,
928
1439
  height: this.stickyHeaderHeight
929
1440
  }, collapseAnimation],
930
- children: this.hasExplicitStickyHeaderHeight ? stickyHeader : /*#__PURE__*/_jsx(View, {
1441
+ children: this.hasExplicitStickyHeaderHeight ? /*#__PURE__*/_jsxs(_Fragment, {
1442
+ children: [this.renderNativeTabBar(), stickyHeader]
1443
+ }) : /*#__PURE__*/_jsxs(View, {
931
1444
  ref: this.setStickyContent,
932
- children: stickyHeader
1445
+ children: [this.renderNativeTabBar(), stickyHeader]
933
1446
  })
934
1447
  }), /*#__PURE__*/_jsx(View, {
935
1448
  ref: this.setTrack,
@@ -38,6 +38,10 @@ export type OnNativeTabPressEventData = Readonly<{
38
38
  position: CodegenTypes.Int32;
39
39
  key: string;
40
40
  }>;
41
+ export type OnNativeToolbarPressEventData = Readonly<{
42
+ key: string;
43
+ anchorRect: Readonly<{ x: CodegenTypes.Double; y: CodegenTypes.Double; width: CodegenTypes.Double; height: CodegenTypes.Double }>;
44
+ }>;
41
45
  export type OnScrollEdgeCapabilityEventData = Readonly<{ available: boolean }>;
42
46
 
43
47
  /**
@@ -60,6 +64,7 @@ export interface NativeProps extends ViewProps {
60
64
  externalTopOverlayTag?: CodegenTypes.WithDefault<CodegenTypes.Int32, 0>;
61
65
  pageKeys?: string;
62
66
  retainedPages?: string;
67
+ nativeTabBarConfig?: string;
63
68
  nativeTabBarItems?: string;
64
69
  nativeTabBarHeight?: CodegenTypes.Double;
65
70
  nativeTabBarVariant?: string;
@@ -81,6 +86,7 @@ export interface NativeProps extends ViewProps {
81
86
  onPageSelected?: CodegenTypes.DirectEventHandler<OnPageSelectedEventData>;
82
87
  onPageScrollStateChanged?: CodegenTypes.DirectEventHandler<OnPageScrollStateChangedEventData>;
83
88
  onCollapsibleStateChanged?: CodegenTypes.DirectEventHandler<OnCollapsibleStateChangedEventData>;
89
+ onNativeToolbarPress?: CodegenTypes.DirectEventHandler<OnNativeToolbarPressEventData>;
84
90
  onNativeTabPress?: CodegenTypes.DirectEventHandler<OnNativeTabPressEventData>;
85
91
  onNativeSubHeaderPress?: CodegenTypes.DirectEventHandler<OnNativeTabPressEventData>;
86
92
  onScrollEdgeCapability?: CodegenTypes.DirectEventHandler<OnScrollEdgeCapabilityEventData>;
@@ -8,7 +8,6 @@ import {
8
8
  type ViewProps,
9
9
  } from "react-native";
10
10
 
11
-
12
11
  export type NativeScrollerScrollEvent = Readonly<{
13
12
  contentInset: Readonly<{
14
13
  top: CodegenTypes.Double;