@rdlabo/ionic-theme-ios27 1.2.0-3 → 1.2.0-4

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 (175) hide show
  1. package/README.md +15 -2
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/css/ionic-theme-ios27.css +1 -1
  4. package/dist/css/vertical-bars.css +1 -1
  5. package/dist/focus-controller/index.d.ts +4 -2
  6. package/dist/focus-controller/index.d.ts.map +1 -1
  7. package/dist/focus-controller/index.js +100 -57
  8. package/dist/focus-controller/index.js.map +1 -0
  9. package/dist/index.d.ts +10 -8
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +54 -60
  12. package/dist/index.js.map +1 -0
  13. package/dist/native/components/index.d.ts +13 -10
  14. package/dist/native/components/index.d.ts.map +1 -1
  15. package/dist/native/components/index.js +39 -41
  16. package/dist/native/components/index.js.map +1 -0
  17. package/dist/native/components/ion-back-button.d.ts +6 -1
  18. package/dist/native/components/ion-back-button.d.ts.map +1 -1
  19. package/dist/native/components/ion-back-button.js +19 -13
  20. package/dist/native/components/ion-back-button.js.map +1 -0
  21. package/dist/native/components/ion-button.d.ts +8 -2
  22. package/dist/native/components/ion-button.d.ts.map +1 -1
  23. package/dist/native/components/ion-button.js +22 -17
  24. package/dist/native/components/ion-button.js.map +1 -0
  25. package/dist/native/components/ion-buttons.d.ts +8 -2
  26. package/dist/native/components/ion-buttons.d.ts.map +1 -1
  27. package/dist/native/components/ion-buttons.js +29 -33
  28. package/dist/native/components/ion-buttons.js.map +1 -0
  29. package/dist/native/components/ion-fab.d.ts +6 -1
  30. package/dist/native/components/ion-fab.d.ts.map +1 -1
  31. package/dist/native/components/ion-fab.js +131 -151
  32. package/dist/native/components/ion-fab.js.map +1 -0
  33. package/dist/native/components/ion-menu-button.d.ts +8 -2
  34. package/dist/native/components/ion-menu-button.d.ts.map +1 -1
  35. package/dist/native/components/ion-menu-button.js +29 -19
  36. package/dist/native/components/ion-menu-button.js.map +1 -0
  37. package/dist/native/components/ion-segment.d.ts +6 -1
  38. package/dist/native/components/ion-segment.d.ts.map +1 -1
  39. package/dist/native/components/ion-segment.js +29 -23
  40. package/dist/native/components/ion-segment.js.map +1 -0
  41. package/dist/native/components/ion-tab-bar.d.ts +6 -1
  42. package/dist/native/components/ion-tab-bar.d.ts.map +1 -1
  43. package/dist/native/components/ion-tab-bar.js +49 -40
  44. package/dist/native/components/ion-tab-bar.js.map +1 -0
  45. package/dist/native/components/searchable-tabs.d.ts +17 -15
  46. package/dist/native/components/searchable-tabs.d.ts.map +1 -1
  47. package/dist/native/components/searchable-tabs.js +382 -434
  48. package/dist/native/components/searchable-tabs.js.map +1 -0
  49. package/dist/native/definitions.d.ts +118 -111
  50. package/dist/native/definitions.d.ts.map +1 -1
  51. package/dist/native/definitions.js +0 -1
  52. package/dist/native/foldable/fold-state.d.ts +5 -0
  53. package/dist/native/foldable/fold-state.d.ts.map +1 -0
  54. package/dist/native/foldable/fold-state.js +14 -0
  55. package/dist/native/foldable/fold-state.js.map +1 -0
  56. package/dist/native/foldable/types.d.ts +12 -0
  57. package/dist/native/foldable/types.d.ts.map +1 -0
  58. package/dist/native/foldable/types.js +0 -0
  59. package/dist/native/index.d.ts +6 -3
  60. package/dist/native/index.d.ts.map +1 -1
  61. package/dist/native/index.js +228 -204
  62. package/dist/native/index.js.map +1 -0
  63. package/dist/native/prehide.d.ts +4 -2
  64. package/dist/native/prehide.d.ts.map +1 -1
  65. package/dist/native/prehide.js +220 -242
  66. package/dist/native/prehide.js.map +1 -0
  67. package/dist/native/runtime.d.ts +3 -1
  68. package/dist/native/runtime.d.ts.map +1 -1
  69. package/dist/native/runtime.js +626 -730
  70. package/dist/native/runtime.js.map +1 -0
  71. package/dist/native/shared/candidate.d.ts +16 -11
  72. package/dist/native/shared/candidate.d.ts.map +1 -1
  73. package/dist/native/shared/candidate.js +96 -95
  74. package/dist/native/shared/candidate.js.map +1 -0
  75. package/dist/native/shared/crossfade.d.ts +6 -4
  76. package/dist/native/shared/crossfade.d.ts.map +1 -1
  77. package/dist/native/shared/crossfade.js +49 -45
  78. package/dist/native/shared/crossfade.js.map +1 -0
  79. package/dist/native/shared/dom.d.ts +7 -5
  80. package/dist/native/shared/dom.d.ts.map +1 -1
  81. package/dist/native/shared/dom.js +163 -211
  82. package/dist/native/shared/dom.js.map +1 -0
  83. package/dist/native/shared/events.d.ts +8 -6
  84. package/dist/native/shared/events.d.ts.map +1 -1
  85. package/dist/native/shared/events.js +12 -7
  86. package/dist/native/shared/events.js.map +1 -0
  87. package/dist/native/shared/icons.d.ts +4 -2
  88. package/dist/native/shared/icons.d.ts.map +1 -1
  89. package/dist/native/shared/icons.js +59 -53
  90. package/dist/native/shared/icons.js.map +1 -0
  91. package/dist/native/shared/modal.d.ts +3 -1
  92. package/dist/native/shared/modal.d.ts.map +1 -1
  93. package/dist/native/shared/modal.js +156 -139
  94. package/dist/native/shared/modal.js.map +1 -0
  95. package/dist/native/vertical-bars-web.d.ts +3 -1
  96. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  97. package/dist/native/vertical-bars-web.js +332 -346
  98. package/dist/native/vertical-bars-web.js.map +1 -0
  99. package/dist/native-integration/index.d.ts +10 -8
  100. package/dist/native-integration/index.d.ts.map +1 -1
  101. package/dist/native-integration/index.js +102 -100
  102. package/dist/native-integration/index.js.map +1 -0
  103. package/dist/native-integration/transition.d.ts +3 -1
  104. package/dist/native-integration/transition.d.ts.map +1 -1
  105. package/dist/native-integration/transition.js +15 -6
  106. package/dist/native-integration/transition.js.map +1 -0
  107. package/dist/searchbar/index.d.ts +4 -2
  108. package/dist/searchbar/index.d.ts.map +1 -1
  109. package/dist/searchbar/index.js +64 -56
  110. package/dist/searchbar/index.js.map +1 -0
  111. package/dist/segment/index.d.ts +3 -1
  112. package/dist/segment/index.d.ts.map +1 -1
  113. package/dist/segment/index.js +465 -324
  114. package/dist/segment/index.js.map +1 -0
  115. package/dist/sheets-of-glass/animations.d.ts +13 -12
  116. package/dist/sheets-of-glass/animations.d.ts.map +1 -1
  117. package/dist/sheets-of-glass/animations.js +496 -306
  118. package/dist/sheets-of-glass/animations.js.map +1 -0
  119. package/dist/sheets-of-glass/index.d.ts +3 -1
  120. package/dist/sheets-of-glass/index.d.ts.map +1 -1
  121. package/dist/sheets-of-glass/index.js +294 -357
  122. package/dist/sheets-of-glass/index.js.map +1 -0
  123. package/dist/sheets-of-glass/interfaces.d.ts +11 -9
  124. package/dist/sheets-of-glass/interfaces.d.ts.map +1 -1
  125. package/dist/sheets-of-glass/interfaces.js +0 -1
  126. package/dist/tab-bar-searchable/animations/enter.d.ts +4 -2
  127. package/dist/tab-bar-searchable/animations/enter.d.ts.map +1 -1
  128. package/dist/tab-bar-searchable/animations/enter.js +50 -69
  129. package/dist/tab-bar-searchable/animations/enter.js.map +1 -0
  130. package/dist/tab-bar-searchable/animations/leave.d.ts +4 -2
  131. package/dist/tab-bar-searchable/animations/leave.d.ts.map +1 -1
  132. package/dist/tab-bar-searchable/animations/leave.js +48 -62
  133. package/dist/tab-bar-searchable/animations/leave.js.map +1 -0
  134. package/dist/tab-bar-searchable/index.d.ts +5 -3
  135. package/dist/tab-bar-searchable/index.d.ts.map +1 -1
  136. package/dist/tab-bar-searchable/index.js +123 -135
  137. package/dist/tab-bar-searchable/index.js.map +1 -0
  138. package/dist/transition/index.d.ts +9 -8
  139. package/dist/transition/index.d.ts.map +1 -1
  140. package/dist/transition/index.js +203 -192
  141. package/dist/transition/index.js.map +1 -0
  142. package/dist/transition/ios.transition.d.ts +7 -5
  143. package/dist/transition/ios.transition.d.ts.map +1 -1
  144. package/dist/transition/ios.transition.js +15 -11
  145. package/dist/transition/ios.transition.js.map +1 -0
  146. package/dist/utils.d.ts +10 -8
  147. package/dist/utils.d.ts.map +1 -1
  148. package/dist/utils.js +58 -66
  149. package/dist/utils.js.map +1 -0
  150. package/dist/vertical-bars.d.ts +5 -4
  151. package/dist/vertical-bars.js +4 -2
  152. package/docs/iphone-duo-with-original-theme.md +18 -29
  153. package/docs/iphone-duo.md +44 -204
  154. package/docs/native-ui-shell.md +10 -8
  155. package/docs/special-markup.md +2 -0
  156. package/docs/vertical-bars.md +283 -0
  157. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +34 -14
  158. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +2 -0
  159. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +3 -0
  160. package/package.json +9 -6
  161. package/src/native/components/index.ts +3 -2
  162. package/src/native/components/ion-button.ts +3 -2
  163. package/src/native/components/ion-buttons.ts +4 -3
  164. package/src/native/components/ion-menu-button.ts +3 -2
  165. package/src/native/definitions.ts +22 -1
  166. package/src/native/foldable/fold-state.ts +9 -0
  167. package/src/native/foldable/types.ts +4 -0
  168. package/src/native/index.ts +17 -3
  169. package/src/native/prehide.ts +3 -1
  170. package/src/native/runtime.ts +2 -2
  171. package/src/native/shared/candidate.ts +39 -10
  172. package/src/native/shared/dom.ts +4 -4
  173. package/src/styles/vertical-bars.scss +4 -2
  174. package/src/vertical-bars.ts +9 -1
  175. package/dist/vertical-bars.d.ts.map +0 -1
@@ -1,85 +1,96 @@
1
- import { inVerticalBarsSurface, topModal, modalUsesVerticalBars, modalVerticalBarFrame } from './shared/modal';
2
- import { LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE } from '@ionic/core';
3
- import { VERTICAL_BARS_TRANSITION_CANCELED, getNativeSearchBindings, setNativeUIShellIntegration } from '../native-integration';
4
- import { createSearchSupport } from './components/searchable-tabs';
5
- import { readCandidate, selector, shadowSelector, motionSelector, isVerticalBarsCandidate } from './components';
6
- import { activateProjectedElement, createVerticalBarsPageState, isVerticalBarsSource, preferredVerticalBarsBack, marker, prehideOnlyMutation, rejectedClass, unprojected, } from './shared/dom';
7
- import { createIconRenderer } from './shared/icons';
8
- import { CSS_MOTION_EVENTS } from './shared/events';
9
- import { createCrossfade, fadeMarker } from './shared/crossfade';
10
- const overlays = 'ion-modal, ion-popover, ion-alert, ion-action-sheet, ion-loading, ion-picker, ion-toast, ion-menu';
11
- const overlayNames = ['Modal', 'Popover', 'Alert', 'ActionSheet', 'Loading', 'Picker', 'Toast'];
12
- const verticalBarsMarker = 'data-native-ui-shell-vertical-bars';
13
- const verticalBarsMemberMarker = 'data-native-ui-shell-vertical-bars-member';
1
+ import { VERTICAL_BARS_TRANSITION_CANCELED, getNativeSearchBindings, setNativeUIShellIntegration } from "../native-integration/index.js";
2
+ import { inVerticalBarsSurface, modalUsesVerticalBars, modalVerticalBarFrame, topModal } from "./shared/modal.js";
3
+ import { createCrossfade, fadeMarker } from "./shared/crossfade.js";
4
+ import { activateProjectedElement, createVerticalBarsPageState, isVerticalBarsSource, marker, preferredVerticalBarsBack, prehideOnlyMutation, rejectedClass, unprojected } from "./shared/dom.js";
5
+ import { createIconRenderer } from "./shared/icons.js";
6
+ import { createSearchSupport } from "./components/searchable-tabs.js";
7
+ import { isVerticalBarsCandidate, motionSelector, readCandidate, selector, shadowSelector } from "./components/index.js";
8
+ import { CSS_MOTION_EVENTS } from "./shared/events.js";
9
+ import { LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from "@ionic/core";
10
+ //#region src/native/runtime.ts
11
+ const overlays = "ion-modal, ion-popover, ion-alert, ion-action-sheet, ion-loading, ion-picker, ion-toast, ion-menu";
12
+ const overlayNames = [
13
+ "Modal",
14
+ "Popover",
15
+ "Alert",
16
+ "ActionSheet",
17
+ "Loading",
18
+ "Picker",
19
+ "Toast"
20
+ ];
21
+ const verticalBarsMarker = "data-native-ui-shell-vertical-bars";
22
+ const verticalBarsMemberMarker = "data-native-ui-shell-vertical-bars-member";
14
23
  const bounded = (promise) => new Promise((resolve, reject) => {
15
- const timer = setTimeout(() => reject(new Error('Native UI Shell bridge timed out')), 5000);
16
- promise.then(resolve, reject).finally(() => clearTimeout(timer));
24
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Native UI Shell bridge timed out")), 5e3);
25
+ promise.then(resolve, reject).finally(() => clearTimeout(timer));
17
26
  });
18
- export const createRuntime = async (doc, plugin, options = {}, nativeVerticalBars = () => true, verticalBarsOnly = false) => {
19
- const win = doc.defaultView;
20
- const icons = createIconRenderer();
21
- const crossfade = createCrossfade(win);
22
- const ids = new WeakMap();
23
- let rejected = new WeakMap();
24
- const sources = new Map();
25
- const verticalBarsOwners = new Set();
26
- const verticalBarsMembers = new Set();
27
- const suspended = new Set();
28
- const pages = new Set();
29
- const verticalBarsPages = createVerticalBarsPageState();
30
- const presented = new Set();
31
- const manualSuspensions = new Set();
32
- const moving = new Map();
33
- const observed = new Set();
34
- const listeners = new AbortController();
35
- let actions = new Map();
36
- let nextId = 0;
37
- let revision = 0;
38
- let acceptedRevision = 0;
39
- let updatingRevision;
40
- let pendingActivations = [];
41
- let lastSequence = 0;
42
- let lastSnapshot = '';
43
- let viewport = `${win.innerWidth}:${win.innerHeight}`;
44
- let forceRefresh = false;
45
- let dirty = false;
46
- let pending = false;
47
- let stopped = false;
48
- let tabSwitchHandoff = false;
49
- let handoffUntil = 0;
50
- let handoffAcrossPending = false;
51
- let handoffInstant = false;
52
- let modalPresentHandoff = false;
53
- let frame = 0;
54
- let updates = 0;
55
- let reason;
56
- let listener;
57
- let searchListener;
58
- let waiters = [];
59
- const control = (kind) => {
60
- if (kind === 'ion-tab-bar')
61
- return 'tabs';
62
- if (kind === 'ion-segment')
63
- return 'segment';
64
- if (kind === 'ion-fab')
65
- return 'fab';
66
- return 'toolbar';
67
- };
68
- const controlEnabled = (candidate) => {
69
- const controls = options.controls;
70
- return controls === undefined || controls[control(candidate.control.kind)] === true;
71
- };
72
- const id = (element) => {
73
- let value = ids.get(element);
74
- if (!value) {
75
- value = `shell-${++nextId}`;
76
- ids.set(element, value);
77
- }
78
- return value;
79
- };
80
- const style = doc.createElement('style');
81
- const hidden = `[${marker}]:not([${fadeMarker}])`;
82
- style.textContent = `${hidden}, ${hidden} *, ${hidden}::before, ${hidden}::after, ${hidden}::part(native) { visibility: hidden !important; }
27
+ const createRuntime = async (doc, plugin, options = {}, nativeVerticalBars = () => true, verticalBarsOnly = false) => {
28
+ const win = doc.defaultView;
29
+ const icons = createIconRenderer();
30
+ const crossfade = createCrossfade(win);
31
+ const ids = /* @__PURE__ */ new WeakMap();
32
+ let rejected = /* @__PURE__ */ new WeakMap();
33
+ const sources = /* @__PURE__ */ new Map();
34
+ const verticalBarsOwners = /* @__PURE__ */ new Set();
35
+ const verticalBarsMembers = /* @__PURE__ */ new Set();
36
+ const suspended = /* @__PURE__ */ new Set();
37
+ const pages = /* @__PURE__ */ new Set();
38
+ const verticalBarsPages = createVerticalBarsPageState();
39
+ const presented = /* @__PURE__ */ new Set();
40
+ const manualSuspensions = /* @__PURE__ */ new Set();
41
+ const moving = /* @__PURE__ */ new Map();
42
+ const observed = /* @__PURE__ */ new Set();
43
+ const listeners = new AbortController();
44
+ let actions = /* @__PURE__ */ new Map();
45
+ let nextId = 0;
46
+ let revision = 0;
47
+ let acceptedRevision = 0;
48
+ let updatingRevision;
49
+ let pendingActivations = [];
50
+ let lastSequence = 0;
51
+ let lastSnapshot = "";
52
+ let viewport = `${win.innerWidth}:${win.innerHeight}`;
53
+ let forceRefresh = false;
54
+ let dirty = false;
55
+ let pending = false;
56
+ let stopped = false;
57
+ /** True while a tab-switch handoff should skip crossfade. */
58
+ let tabSwitchHandoff = false;
59
+ /** Keep instant updates until this time — DidLeave often precedes the retire sync. */
60
+ let handoffUntil = 0;
61
+ /** Tab switch armed while a sync was already in flight; extend instant past that sync. */
62
+ let handoffAcrossPending = false;
63
+ /** Captured at the start of each sync so an in-flight update keeps a stable duration. */
64
+ let handoffInstant = false;
65
+ /** Retire native controls immediately as a modal starts covering the page. */
66
+ let modalPresentHandoff = false;
67
+ let frame = 0;
68
+ let updates = 0;
69
+ let reason;
70
+ let listener;
71
+ let searchListener;
72
+ let waiters = [];
73
+ const control = (kind) => {
74
+ if (kind === "ion-tab-bar") return "tabs";
75
+ if (kind === "ion-segment") return "segment";
76
+ if (kind === "ion-fab") return "fab";
77
+ return "toolbar";
78
+ };
79
+ const controlEnabled = (candidate) => {
80
+ const controls = options.controls;
81
+ return controls === void 0 || controls[control(candidate.control.kind)] === true;
82
+ };
83
+ const id = (element) => {
84
+ let value = ids.get(element);
85
+ if (!value) {
86
+ value = `shell-${++nextId}`;
87
+ ids.set(element, value);
88
+ }
89
+ return value;
90
+ };
91
+ const style = doc.createElement("style");
92
+ const hidden = `[${marker}]:not([${fadeMarker}])`;
93
+ style.textContent = `${hidden}, ${hidden} *, ${hidden}::before, ${hidden}::after, ${hidden}::part(native) { visibility: hidden !important; }
83
94
  [${marker}], [${marker}] * { pointer-events: none !important; }
84
95
  /* Ionic disables the covered page while a menu is open. VerticalBars rail
85
96
  controls remain outside that page; zero specificity preserves any
@@ -90,654 +101,539 @@ export const createRuntime = async (doc, plugin, options = {}, nativeVerticalBar
90
101
  :where(.menu-content-open) :where([${verticalBarsMarker}]) > :where(:not([${verticalBarsMemberMarker}])) {
91
102
  pointer-events: none;
92
103
  }`;
93
- const restore = (element) => {
94
- lastSnapshot = '';
95
- search.release(element);
96
- element.removeAttribute(marker);
97
- if (!stopped)
98
- crossfade.play(element, false, isVerticalBarsSource(element) ? !element.matches('ion-tab-bar') : handoffInstant);
99
- if (element.getAttribute('aria-hidden') === 'true') {
100
- const previous = sources.get(element);
101
- if (previous == null)
102
- element.removeAttribute('aria-hidden');
103
- else
104
- element.setAttribute('aria-hidden', previous);
105
- }
106
- sources.delete(element);
107
- element.dispatchEvent(new CustomEvent('nativeUIShellChange'));
108
- };
109
- const restoreAll = () => {
110
- Array.from(sources.keys()).forEach(restore);
111
- verticalBarsOwners.forEach((element) => element.removeAttribute(verticalBarsMarker));
112
- verticalBarsOwners.clear();
113
- verticalBarsMembers.forEach((element) => element.removeAttribute(verticalBarsMemberMarker));
114
- verticalBarsMembers.clear();
115
- };
116
- const finishWaiters = () => {
117
- const current = waiters;
118
- waiters = [];
119
- current.forEach((resolve) => resolve());
120
- };
121
- const schedule = () => {
122
- if (stopped)
123
- return;
124
- dirty = true;
125
- if (!pending && !frame)
126
- frame = win.requestAnimationFrame(() => void sync());
127
- };
128
- const search = createSearchSupport(doc, id, schedule);
129
- const candidateSources = (candidate) => candidate.sources ?? [candidate.element];
130
- const setRejected = (element, value) => {
131
- element.classList.toggle(rejectedClass, value);
132
- };
133
- const measuringPointerPages = new WeakSet();
134
- const readEnabledCandidate = (element) => {
135
- if (element.matches('ion-back-button') && element.closest('ion-app.ios-theme-vertical-bars') && !isVerticalBarsCandidate(element))
136
- return;
137
- if (verticalBarsOnly && !isVerticalBarsCandidate(element))
138
- return;
139
- const pointerPage = isVerticalBarsCandidate(element) ? element.closest('.ion-page') : undefined;
140
- let candidate;
141
- if (pointerPage && getComputedStyle(pointerPage).pointerEvents === 'none') {
142
- const previous = pointerPage.style.getPropertyValue('pointer-events');
143
- const priority = pointerPage.style.getPropertyPriority('pointer-events');
144
- const hadStyle = pointerPage.hasAttribute('style');
145
- measuringPointerPages.add(pointerPage);
146
- pointerPage.style.setProperty('pointer-events', 'auto', 'important');
147
- try {
148
- candidate = readCandidate(element, id);
149
- }
150
- finally {
151
- if (previous)
152
- pointerPage.style.setProperty('pointer-events', previous, priority);
153
- else
154
- pointerPage.style.removeProperty('pointer-events');
155
- if (!hadStyle && !pointerPage.style.length)
156
- pointerPage.removeAttribute('style');
157
- win.setTimeout(() => measuringPointerPages.delete(pointerPage), 0);
158
- }
159
- }
160
- else
161
- candidate = readCandidate(element, id);
162
- if (candidate && isVerticalBarsCandidate(element)) {
163
- if (!nativeVerticalBars())
164
- return undefined;
165
- candidate.control.placement = 'vertical-bars';
166
- if (['ion-button', 'ion-buttons', 'ion-menu-button'].includes(candidate.control.kind)) {
167
- const slot = (element.matches('ion-buttons') ? element : (element.closest('ion-buttons') ?? element)).getAttribute('slot');
168
- if (slot === 'start' || slot === 'end')
169
- candidate.control.toolbarSlot = slot;
170
- }
171
- }
172
- return candidate && controlEnabled(candidate) ? candidate : undefined;
173
- };
174
- const flush = async () => {
175
- if (stopped)
176
- return;
177
- schedule();
178
- try {
179
- await bounded(new Promise((resolve) => waiters.push(resolve)));
180
- }
181
- catch (error) {
182
- await fail(error);
183
- }
184
- };
185
- const blocked = (element) => verticalBarsPages.isDeparted(element) ||
186
- (!isVerticalBarsSource(element) &&
187
- (Array.from(suspended).some((scopes) => scopes.some((scope) => scope.contains(element))) ||
188
- Array.from(pages).some((scope) => scope.contains(element)) ||
189
- Array.from(moving.keys()).some((surface) => surface.contains(element))));
190
- const painted = () => new Promise((resolve) => win.requestAnimationFrame(() => win.requestAnimationFrame(() => resolve())));
191
- const overlayOpen = (includeMenu = true, allowModal = false) => {
192
- const modal = topModal(doc);
193
- const ignored = (element) => allowModal && !!modal && modalUsesVerticalBars(modal) && element.matches('ion-modal');
194
- for (const element of presented)
195
- if (!element.isConnected)
196
- presented.delete(element);
197
- const presentedOverlayOpen = Array.from(presented).some((element) => !ignored(element) && (includeMenu || !element.matches('ion-menu')));
198
- return (manualSuspensions.size > 0 ||
199
- presentedOverlayOpen ||
200
- Array.from(doc.querySelectorAll(overlays)).some((element) => !ignored(element) &&
201
- (includeMenu || !element.matches('ion-menu')) &&
202
- (element.presented || element.classList.contains('show-menu'))));
203
- };
204
- const signature = (candidate) => JSON.stringify({ control: candidate.control, icons: candidate.icons.map(({ source }) => source) });
205
- const read = () => {
206
- search.keepSearchTabsVisible();
207
- for (const page of pages)
208
- if (!page.isConnected)
209
- pages.delete(page);
210
- for (const surface of moving.keys())
211
- if (!surface.isConnected)
212
- moving.delete(surface);
213
- if (doc.hidden || overlayOpen(false, true))
214
- return [];
215
- if (win.visualViewport && (win.visualViewport.scale !== 1 || win.visualViewport.offsetTop !== 0) && !search.hasActive())
216
- return [];
217
- const menuOpen = overlayOpen();
218
- const candidates = unprojected(sources.keys(), () => search
219
- .decorate(Array.from(doc.querySelectorAll(selector))
220
- .filter((element) => inVerticalBarsSurface(element) &&
221
- !verticalBarsPages.isDeparted(element) &&
222
- (!blocked(element) || (menuOpen && isVerticalBarsSource(element))))
223
- .map(readEnabledCandidate)
224
- .filter((candidate) => !!candidate), blocked)
225
- .filter((candidate) => !rejected.has(candidate.element) || rejected.get(candidate.element) !== signature(candidate)));
226
- const back = preferredVerticalBarsBack(candidates
227
- .filter((candidate) => candidate.control.kind === 'ion-back-button' && isVerticalBarsCandidate(candidate.element))
228
- .map((candidate) => candidate.element), doc);
229
- const selected = candidates.filter((candidate) => candidate.control.kind !== 'ion-back-button' || !isVerticalBarsCandidate(candidate.element) || candidate.element === back);
230
- return menuOpen ? selected.filter((candidate) => isVerticalBarsCandidate(candidate.element)) : selected;
231
- };
232
- const observe = () => {
233
- const wanted = new Set();
234
- for (const element of Array.from(doc.querySelectorAll(selector))) {
235
- wanted.add(element);
236
- if (element.parentElement)
237
- wanted.add(element.parentElement);
238
- const inspectShadow = (node) => {
239
- if (node.shadowRoot) {
240
- wanted.add(node.shadowRoot);
241
- node.shadowRoot.querySelectorAll('ion-icon').forEach(inspectShadow);
242
- }
243
- };
244
- inspectShadow(element);
245
- element.querySelectorAll(shadowSelector).forEach(inspectShadow);
246
- }
247
- if (wanted.size === observed.size && Array.from(wanted).every((element) => observed.has(element)))
248
- return;
249
- observer.disconnect();
250
- resize.disconnect();
251
- observed.clear();
252
- observer.observe(doc.documentElement, observation);
253
- for (const element of wanted) {
254
- observed.add(element);
255
- if (element instanceof ShadowRoot)
256
- observer.observe(element, observation);
257
- else
258
- resize.observe(element);
259
- }
260
- };
261
- const fail = async (error) => {
262
- reason = error instanceof Error ? error.message : String(error);
263
- console.warn('[Native UI Shell] Returning to Web:', reason);
264
- await handle.destroy();
265
- win.dispatchEvent(new Event('nativeUIShellRefresh'));
266
- };
267
- const sync = async () => {
268
- frame = 0;
269
- dirty = false;
270
- pending = true;
271
- handoffInstant = tabSwitchHandoff || handoffAcrossPending || modalPresentHandoff || win.performance.now() < handoffUntil;
272
- modalPresentHandoff = false;
273
- try {
274
- const size = `${win.innerWidth}:${win.innerHeight}`;
275
- if (viewport !== size) {
276
- viewport = size;
277
- dirty = true;
278
- return;
279
- }
280
- observe();
281
- const candidates = read();
282
- const signatures = new Map(candidates.map((candidate) => [candidate.element, signature(candidate)]));
283
- for (const candidate of candidates) {
284
- for (const icon of candidate.icons) {
285
- const field = icon.field ?? 'icon';
286
- icon.item[field] = await icons.render(icon.source, field === 'icon' ? icon.item.iconWidth : icon.item.closeIconWidth, field === 'icon' ? icon.item.iconHeight : icon.item.closeIconHeight);
287
- }
288
- }
289
- if (stopped || dirty)
290
- return;
291
- const retained = new Set(candidates.flatMap(candidateSources));
292
- const removed = Array.from(sources.keys()).filter((element) => !retained.has(element));
293
- if (removed.length) {
294
- removed.forEach(restore);
295
- if (!handoffInstant && removed.some((element) => !isVerticalBarsSource(element))) {
296
- await painted();
297
- if (stopped || dirty)
298
- return;
299
- }
300
- }
301
- const root = doc.querySelector('ion-app.ios-theme-vertical-bars');
302
- const verticalBarEdge = root
303
- ? root.classList.contains('ios-theme-vertical-bars-left')
304
- ? 'left'
305
- : 'right'
306
- : undefined;
307
- const modal = topModal(doc);
308
- const verticalBarFrame = modal && modalUsesVerticalBars(modal) ? modalVerticalBarFrame(modal) : undefined;
309
- const data = {
310
- verticalBarFrame,
311
- viewportWidth: win.innerWidth,
312
- verticalBarEdge,
313
- controls: candidates.map((candidate) => candidate.control),
314
- };
315
- const serialized = JSON.stringify(data);
316
- if (serialized === lastSnapshot && !forceRefresh)
317
- return;
318
- const snapshot = { ...data, revision: ++revision, transitionDuration: crossfade.duration(handoffInstant) };
319
- forceRefresh = false;
320
- updates++;
321
- updatingRevision = snapshot.revision;
322
- const result = await bounded(plugin.update(snapshot));
323
- if (stopped)
324
- return;
325
- if (result.revision !== snapshot.revision)
326
- throw new Error('Native UI Shell revision mismatch');
327
- if (`${win.innerWidth}:${win.innerHeight}` !== size) {
328
- lastSnapshot = '';
329
- dirty = true;
330
- return;
331
- }
332
- if (result.rejectedSearches?.length) {
333
- search.reject(result.rejectedSearches);
334
- lastSnapshot = '';
335
- dirty = true;
336
- }
337
- for (const candidate of candidates) {
338
- if (result.rejectedControls?.includes(candidate.control.id)) {
339
- rejected.set(candidate.element, signatures.get(candidate.element));
340
- setRejected(candidate.element, true);
341
- dirty = true;
342
- }
343
- }
344
- const currentCandidates = dirty ? read() : candidates;
345
- const current = dirty ? new Map(currentCandidates.map((candidate) => [candidate.element, signature(candidate)])) : signatures;
346
- const currentSources = new Set(currentCandidates.flatMap(candidateSources));
347
- const accepted = candidates.filter((candidate) => current.get(candidate.element) === signatures.get(candidate.element));
348
- accepted.forEach((candidate) => setRejected(candidate.element, false));
349
- const acceptedVerticalBarsOwners = new Set(accepted.filter((candidate) => candidate.control.placement === 'vertical-bars').map((candidate) => candidate.element));
350
- const acceptedVerticalBarsMembers = new Set(accepted
351
- .filter((candidate) => candidate.control.placement === 'vertical-bars')
352
- .flatMap((candidate) => Array.from(candidate.actions.values())));
353
- const invalidated = candidates.length !== accepted.length;
354
- acceptedRevision = result.revision;
355
- lastSnapshot = invalidated ? '' : serialized;
356
- actions = new Map(candidates.filter((candidate) => current.has(candidate.element)).flatMap((candidate) => Array.from(candidate.actions)));
357
- for (const element of sources.keys())
358
- if (!currentSources.has(element))
359
- restore(element);
360
- for (const element of verticalBarsOwners) {
361
- if (acceptedVerticalBarsOwners.has(element))
362
- continue;
363
- element.removeAttribute(verticalBarsMarker);
364
- verticalBarsOwners.delete(element);
365
- }
366
- for (const element of acceptedVerticalBarsOwners) {
367
- element.setAttribute(verticalBarsMarker, '');
368
- verticalBarsOwners.add(element);
369
- }
370
- for (const element of verticalBarsMembers) {
371
- if (acceptedVerticalBarsMembers.has(element))
372
- continue;
373
- element.removeAttribute(verticalBarsMemberMarker);
374
- verticalBarsMembers.delete(element);
375
- }
376
- for (const element of acceptedVerticalBarsMembers) {
377
- element.setAttribute(verticalBarsMemberMarker, '');
378
- verticalBarsMembers.add(element);
379
- }
380
- for (const element of accepted.flatMap(candidateSources)) {
381
- const newlyProjected = !sources.has(element) || !element.hasAttribute(marker);
382
- if (!sources.has(element)) {
383
- sources.set(element, element.getAttribute('aria-hidden'));
384
- crossfade.play(element, true, handoffInstant || isVerticalBarsSource(element));
385
- }
386
- element.setAttribute(marker, '');
387
- element.setAttribute('aria-hidden', 'true');
388
- if (newlyProjected)
389
- element.dispatchEvent(new CustomEvent('nativeUIShellChange'));
390
- }
391
- const received = pendingActivations;
392
- pendingActivations = [];
393
- received.forEach(activate);
394
- await crossfade.settled();
395
- if (invalidated) {
396
- dirty = true;
397
- await painted();
398
- }
399
- }
400
- catch (error) {
401
- await fail(error);
402
- }
403
- finally {
404
- updatingRevision = undefined;
405
- pendingActivations = [];
406
- if (forceRefresh)
407
- rejected = new WeakMap();
408
- if (!stopped && (handoffAcrossPending || (handoffInstant && dirty))) {
409
- handoffUntil = Math.max(handoffUntil, win.performance.now() + 400);
410
- }
411
- handoffAcrossPending = false;
412
- pending = false;
413
- if (dirty && !stopped)
414
- schedule();
415
- else
416
- finishWaiters();
417
- }
418
- };
419
- const observation = {
420
- subtree: true,
421
- childList: true,
422
- characterData: true,
423
- attributes: true,
424
- attributeOldValue: true,
425
- };
426
- const observer = new MutationObserver((records) => {
427
- if (records.some((record) => record.attributeName !== marker &&
428
- record.attributeName !== fadeMarker &&
429
- !prehideOnlyMutation(record) &&
430
- !(record.attributeName === 'style' && measuringPointerPages.has(record.target)) &&
431
- !(record.attributeName === 'aria-hidden' &&
432
- sources.has(record.target) &&
433
- record.target.getAttribute('aria-hidden') === 'true')))
434
- schedule();
435
- });
436
- const resize = new ResizeObserver(schedule);
437
- const on = (target, name, callback = schedule) => target.addEventListener(name, callback, { capture: true, signal: listeners.signal });
438
- const pageWill = (event) => {
439
- const page = event.target;
440
- verticalBarsPages.lifecycle(event);
441
- getNativeSearchBindings(doc)
442
- .filter((binding) => page.contains(binding.footer))
443
- .forEach((binding) => search.retire(binding));
444
- if (event.type === LIFECYCLE_WILL_LEAVE)
445
- beginTabSwitchHandoffIfNeeded(page);
446
- pages.add(page);
447
- schedule();
448
- };
449
- const pageDid = (event) => {
450
- const page = event.target;
451
- verticalBarsPages.lifecycle(event);
452
- pages.delete(page);
453
- schedule();
454
- if (tabSwitchHandoff && pages.size === 0)
455
- endTabSwitchHandoff();
456
- };
457
- const tabOfPath = (tabs, pathname) => {
458
- let best;
459
- for (const button of Array.from(tabs.querySelectorAll('ion-tab-button'))) {
460
- const tab = button.getAttribute('tab');
461
- const href = button.getAttribute('href')?.split(/[?#]/)[0];
462
- if (!tab || !href)
463
- continue;
464
- if (pathname === href || pathname.startsWith(`${href}/`)) {
465
- if (!best || href.length > best.length)
466
- best = { tab, length: href.length };
467
- }
468
- }
469
- return best?.tab;
470
- };
471
- const armTabSwitchHandoff = () => {
472
- tabSwitchHandoff = true;
473
- handoffUntil = Math.max(handoffUntil, win.performance.now() + 400);
474
- if (pending)
475
- handoffAcrossPending = true;
476
- };
477
- const beginTabSwitchHandoffIfNeeded = (page) => {
478
- const tabs = page.closest('ion-tabs');
479
- if (!tabs)
480
- return;
481
- const bar = tabs.querySelector('ion-tab-bar');
482
- const selected = (bar && 'selectedTab' in bar ? String(bar.selectedTab ?? '') : '') ||
483
- tabs.querySelector('ion-tab-button.tab-selected')?.getAttribute('tab') ||
484
- undefined;
485
- const destination = tabOfPath(tabs, win.location.pathname);
486
- if (selected && destination && selected !== destination)
487
- armTabSwitchHandoff();
488
- };
489
- const endTabSwitchHandoff = () => {
490
- win.requestAnimationFrame(() => {
491
- if (stopped || !tabSwitchHandoff)
492
- return;
493
- tabSwitchHandoff = false;
494
- schedule();
495
- });
496
- };
497
- const motion = (event) => {
498
- const target = event.target;
499
- if (!target.matches?.(motionSelector))
500
- return;
501
- const key = event.propertyName ?? event.animationName;
502
- if (event.type === CSS_MOTION_EVENTS.transitionRun || event.type === CSS_MOTION_EVENTS.animationStart) {
503
- const keys = moving.get(target) ?? new Set();
504
- keys.add(key);
505
- moving.set(target, keys);
506
- }
507
- else {
508
- moving.get(target)?.delete(key);
509
- if (!moving.get(target)?.size)
510
- moving.delete(target);
511
- }
512
- schedule();
513
- };
514
- for (const name of Object.values(CSS_MOTION_EVENTS))
515
- on(doc, name, motion);
516
- for (const name of [LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE])
517
- on(doc, name, pageWill);
518
- for (const name of [LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE])
519
- on(doc, name, pageDid);
520
- on(doc, VERTICAL_BARS_TRANSITION_CANCELED, (event) => {
521
- const leaving = event.target;
522
- const entering = event.detail?.entering;
523
- verticalBarsPages.cancel(entering, leaving);
524
- pages.delete(leaving);
525
- if (entering)
526
- pages.delete(entering);
527
- schedule();
528
- });
529
- on(doc, 'ionTabsWillChange', () => {
530
- armTabSwitchHandoff();
531
- schedule();
532
- });
533
- on(doc, 'ionTabsDidChange', endTabSwitchHandoff);
534
- for (const name of overlayNames) {
535
- on(doc, `ion${name}WillPresent`, (event) => {
536
- presented.add(event.target);
537
- if (name === 'Modal') {
538
- modalPresentHandoff = true;
539
- if (pending)
540
- handoffInstant = true;
541
- }
542
- schedule();
543
- });
544
- on(doc, `ion${name}DidDismiss`, (event) => {
545
- presented.delete(event.target);
546
- schedule();
547
- });
548
- }
549
- const eventMenu = (event) => event.composedPath().find((target) => target instanceof HTMLElement && target.matches('ion-menu'));
550
- on(doc, 'ionWillOpen', (event) => {
551
- const menu = eventMenu(event);
552
- if (menu)
553
- presented.add(menu);
554
- schedule();
555
- });
556
- on(doc, 'ionDidClose', (event) => {
557
- const menu = eventMenu(event);
558
- if (menu)
559
- presented.delete(menu);
560
- schedule();
561
- });
562
- for (const name of [
563
- 'ionChange',
564
- 'ionSelect',
565
- 'ionImgDidLoad',
566
- CSS_MOTION_EVENTS.transitionEnd,
567
- CSS_MOTION_EVENTS.animationEnd,
568
- 'focusin',
569
- 'focusout',
570
- ])
571
- on(doc, name);
572
- const refreshLayout = () => {
573
- rejected = new WeakMap();
574
- forceRefresh = true;
575
- schedule();
576
- };
577
- on(doc, 'visibilitychange', () => {
578
- if (doc.hidden) {
579
- getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
580
- schedule();
581
- }
582
- else
583
- refreshLayout();
584
- });
585
- on(win, 'resize', refreshLayout);
586
- on(win, 'keyboardWillShow', () => search.keyboard(true));
587
- on(win, 'keyboardWillHide', () => search.keyboard(false));
588
- on(win, 'scroll');
589
- on(win.matchMedia('(prefers-color-scheme: dark)'), 'change');
590
- on(win, 'nativeUIShellRefresh', (event) => {
591
- if (event.retireSearch)
592
- getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
593
- refreshLayout();
594
- });
595
- if (win.visualViewport) {
596
- on(win.visualViewport, 'resize', refreshLayout);
597
- on(win.visualViewport, 'scroll');
598
- }
599
- const handle = {
600
- getStatus: () => ({
601
- state: stopped ? 'stopped' : sources.size ? 'native' : 'web',
602
- projected: sources.size,
603
- updates,
604
- reason,
605
- }),
606
- async suspend() {
607
- const token = Symbol();
608
- let resumed = false;
609
- if (!stopped) {
610
- manualSuspensions.add(token);
611
- getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
612
- await flush();
613
- }
614
- return {
615
- async resume() {
616
- if (resumed)
617
- return;
618
- resumed = true;
619
- if (stopped || !manualSuspensions.delete(token))
620
- return;
621
- await flush();
622
- },
623
- };
624
- },
625
- async destroy() {
626
- if (stopped)
627
- return;
628
- stopped = true;
629
- setNativeUIShellIntegration(doc);
630
- win.cancelAnimationFrame(frame);
631
- listeners.abort();
632
- observer.disconnect();
633
- resize.disconnect();
634
- observed.clear();
635
- actions.clear();
636
- pendingActivations = [];
637
- search.destroy();
638
- crossfade.destroy();
639
- restoreAll();
640
- if (!doc.hidden)
641
- await painted();
642
- try {
643
- await bounded(plugin.clear({ revision: ++revision }));
644
- }
645
- catch {
646
- }
647
- style.remove();
648
- doc.querySelectorAll(`.${rejectedClass}`).forEach((element) => setRejected(element, false));
649
- icons.clear();
650
- pages.clear();
651
- presented.clear();
652
- manualSuspensions.clear();
653
- moving.clear();
654
- suspended.clear();
655
- await listener?.remove().catch(() => { });
656
- await searchListener?.remove().catch(() => { });
657
- finishWaiters();
658
- },
659
- };
660
- const activate = (event) => {
661
- if (stopped ||
662
- doc.hidden ||
663
- event.revision < acceptedRevision ||
664
- event.revision > revision ||
665
- event.sequence <= lastSequence ||
666
- overlayOpen(false, true))
667
- return;
668
- if (event.revision > acceptedRevision && event.revision === updatingRevision) {
669
- pendingActivations.push(event);
670
- return;
671
- }
672
- lastSequence = event.sequence;
673
- const element = actions.get(event.id);
674
- if (!element)
675
- return;
676
- const owner = Array.from(sources.keys()).find((source) => source === element || source.contains(element));
677
- if (!owner || !inVerticalBarsSurface(owner))
678
- return;
679
- const direct = !blocked(owner) && unprojected(sources.keys(), () => readEnabledCandidate(owner));
680
- const candidate = direct || read().find((candidate) => candidate.actions.has(event.id));
681
- const item = candidate?.control.items.find((item) => item.id === event.id);
682
- const searchAction = candidate?.control.search && [candidate.control.search.trigger.id, candidate.control.search.closeId].includes(event.id);
683
- if (!searchAction && (!item || item.disabled || item.visible === false))
684
- return;
685
- activateProjectedElement(element);
686
- lastSnapshot = '';
687
- schedule();
688
- };
689
- try {
690
- listener = await bounded(plugin.addListener('activate', activate));
691
- searchListener = await bounded(plugin.addListener('search', (event) => {
692
- if (stopped || doc.hidden || event.revision < acceptedRevision || event.revision > revision || overlayOpen())
693
- return;
694
- search.event(event);
695
- }));
696
- doc.head.append(style);
697
- observer.observe(doc.documentElement, observation);
698
- setNativeUIShellIntegration(doc, {
699
- async search(binding, active, focus) {
700
- if (!getNativeSearchBindings(doc).includes(binding) || !search.projected(binding))
701
- return false;
702
- if (!active) {
703
- search.setStatus(binding, false);
704
- }
705
- else {
706
- const entering = !binding.active;
707
- if (entering)
708
- search.begin(binding, revision + 1);
709
- const nextFocused = focus === true ? true : focus === false ? false : entering ? false : binding.focused;
710
- search.setStatus(binding, true, nextFocused);
711
- }
712
- await flush();
713
- return !stopped && search.projected(binding);
714
- },
715
- async suspend(scopes) {
716
- getNativeSearchBindings(doc)
717
- .filter((binding) => scopes.some((scope) => scope.contains(binding.footer) || scope.contains(binding.tabBar)))
718
- .forEach((binding) => search.retire(binding));
719
- suspended.add(scopes);
720
- await flush();
721
- await new Promise((resolve) => win.requestAnimationFrame(() => resolve()));
722
- return (canceled = false) => {
723
- suspended.delete(scopes);
724
- if (canceled || !scopes.some((scope) => scope.closest('ion-app.ios-theme-vertical-bars'))) {
725
- scopes.forEach((scope) => pages.delete(scope));
726
- if (canceled) {
727
- verticalBarsPages.cancel(scopes[0], scopes[1]);
728
- }
729
- }
730
- schedule();
731
- };
732
- },
733
- });
734
- on(win, 'pagehide', () => {
735
- void handle.destroy();
736
- });
737
- schedule();
738
- }
739
- catch (error) {
740
- await fail(error);
741
- }
742
- return handle;
104
+ const restore = (element) => {
105
+ lastSnapshot = "";
106
+ search.release(element);
107
+ element.removeAttribute(marker);
108
+ if (!stopped) crossfade.play(element, false, isVerticalBarsSource(element) ? !element.matches("ion-tab-bar") : handoffInstant);
109
+ if (element.getAttribute("aria-hidden") === "true") {
110
+ const previous = sources.get(element);
111
+ if (previous == null) element.removeAttribute("aria-hidden");
112
+ else element.setAttribute("aria-hidden", previous);
113
+ }
114
+ sources.delete(element);
115
+ element.dispatchEvent(new CustomEvent("nativeUIShellChange"));
116
+ };
117
+ const restoreAll = () => {
118
+ Array.from(sources.keys()).forEach(restore);
119
+ verticalBarsOwners.forEach((element) => element.removeAttribute(verticalBarsMarker));
120
+ verticalBarsOwners.clear();
121
+ verticalBarsMembers.forEach((element) => element.removeAttribute(verticalBarsMemberMarker));
122
+ verticalBarsMembers.clear();
123
+ };
124
+ const finishWaiters = () => {
125
+ const current = waiters;
126
+ waiters = [];
127
+ current.forEach((resolve) => resolve());
128
+ };
129
+ const schedule = () => {
130
+ if (stopped) return;
131
+ dirty = true;
132
+ if (!pending && !frame) frame = win.requestAnimationFrame(() => void sync());
133
+ };
134
+ const search = createSearchSupport(doc, id, schedule);
135
+ const candidateSources = (candidate) => candidate.sources ?? [candidate.element];
136
+ const setRejected = (element, value) => {
137
+ element.classList.toggle(rejectedClass, value);
138
+ };
139
+ const measuringPointerPages = /* @__PURE__ */ new WeakSet();
140
+ const readEnabledCandidate = (element) => {
141
+ if (element.matches("ion-back-button") && element.closest("ion-app.ios-theme-vertical-bars") && !isVerticalBarsCandidate(element)) return;
142
+ if (verticalBarsOnly && !isVerticalBarsCandidate(element)) return;
143
+ const pointerPage = isVerticalBarsCandidate(element) ? element.closest(".ion-page") : void 0;
144
+ let candidate;
145
+ if (pointerPage && getComputedStyle(pointerPage).pointerEvents === "none") {
146
+ const previous = pointerPage.style.getPropertyValue("pointer-events");
147
+ const priority = pointerPage.style.getPropertyPriority("pointer-events");
148
+ const hadStyle = pointerPage.hasAttribute("style");
149
+ measuringPointerPages.add(pointerPage);
150
+ pointerPage.style.setProperty("pointer-events", "auto", "important");
151
+ try {
152
+ candidate = readCandidate(element, id, options);
153
+ } finally {
154
+ if (previous) pointerPage.style.setProperty("pointer-events", previous, priority);
155
+ else pointerPage.style.removeProperty("pointer-events");
156
+ if (!hadStyle && !pointerPage.style.length) pointerPage.removeAttribute("style");
157
+ win.setTimeout(() => measuringPointerPages.delete(pointerPage), 0);
158
+ }
159
+ } else candidate = readCandidate(element, id, options);
160
+ if (candidate && isVerticalBarsCandidate(element)) {
161
+ if (!nativeVerticalBars()) return void 0;
162
+ candidate.control.placement = "vertical-bars";
163
+ if ([
164
+ "ion-button",
165
+ "ion-buttons",
166
+ "ion-menu-button"
167
+ ].includes(candidate.control.kind)) {
168
+ const slot = (element.matches("ion-buttons") ? element : element.closest("ion-buttons") ?? element).getAttribute("slot");
169
+ if (slot === "start" || slot === "end") candidate.control.toolbarSlot = slot;
170
+ }
171
+ }
172
+ return candidate && controlEnabled(candidate) ? candidate : void 0;
173
+ };
174
+ const flush = async () => {
175
+ if (stopped) return;
176
+ schedule();
177
+ try {
178
+ await bounded(new Promise((resolve) => waiters.push(resolve)));
179
+ } catch (error) {
180
+ await fail(error);
181
+ }
182
+ };
183
+ const blocked = (element) => verticalBarsPages.isDeparted(element) || !isVerticalBarsSource(element) && (Array.from(suspended).some((scopes) => scopes.some((scope) => scope.contains(element))) || Array.from(pages).some((scope) => scope.contains(element)) || Array.from(moving.keys()).some((surface) => surface.contains(element)));
184
+ const painted = () => new Promise((resolve) => win.requestAnimationFrame(() => win.requestAnimationFrame(() => resolve())));
185
+ const overlayOpen = (includeMenu = true, allowModal = false) => {
186
+ const modal = topModal(doc);
187
+ const ignored = (element) => allowModal && !!modal && modalUsesVerticalBars(modal) && element.matches("ion-modal");
188
+ for (const element of presented) if (!element.isConnected) presented.delete(element);
189
+ const presentedOverlayOpen = Array.from(presented).some((element) => !ignored(element) && (includeMenu || !element.matches("ion-menu")));
190
+ return manualSuspensions.size > 0 || presentedOverlayOpen || Array.from(doc.querySelectorAll(overlays)).some((element) => !ignored(element) && (includeMenu || !element.matches("ion-menu")) && (element.presented || element.classList.contains("show-menu")));
191
+ };
192
+ const signature = (candidate) => JSON.stringify({
193
+ control: candidate.control,
194
+ icons: candidate.icons.map(({ source }) => source)
195
+ });
196
+ const read = () => {
197
+ search.keepSearchTabsVisible();
198
+ for (const page of pages) if (!page.isConnected) pages.delete(page);
199
+ for (const surface of moving.keys()) if (!surface.isConnected) moving.delete(surface);
200
+ if (doc.hidden || overlayOpen(false, true)) return [];
201
+ if (win.visualViewport && (win.visualViewport.scale !== 1 || win.visualViewport.offsetTop !== 0) && !search.hasActive()) return [];
202
+ const menuOpen = overlayOpen();
203
+ const candidates = unprojected(sources.keys(), () => search.decorate(Array.from(doc.querySelectorAll(selector)).filter((element) => inVerticalBarsSurface(element) && !verticalBarsPages.isDeparted(element) && (!blocked(element) || menuOpen && isVerticalBarsSource(element))).map(readEnabledCandidate).filter((candidate) => !!candidate), blocked).filter((candidate) => !rejected.has(candidate.element) || rejected.get(candidate.element) !== signature(candidate)));
204
+ const back = preferredVerticalBarsBack(candidates.filter((candidate) => candidate.control.kind === "ion-back-button" && isVerticalBarsCandidate(candidate.element)).map((candidate) => candidate.element), doc);
205
+ const selected = candidates.filter((candidate) => candidate.control.kind !== "ion-back-button" || !isVerticalBarsCandidate(candidate.element) || candidate.element === back);
206
+ return menuOpen ? selected.filter((candidate) => isVerticalBarsCandidate(candidate.element)) : selected;
207
+ };
208
+ const observe = () => {
209
+ const wanted = /* @__PURE__ */ new Set();
210
+ for (const element of Array.from(doc.querySelectorAll(selector))) {
211
+ wanted.add(element);
212
+ if (element.parentElement) wanted.add(element.parentElement);
213
+ const inspectShadow = (node) => {
214
+ if (node.shadowRoot) {
215
+ wanted.add(node.shadowRoot);
216
+ node.shadowRoot.querySelectorAll("ion-icon").forEach(inspectShadow);
217
+ }
218
+ };
219
+ inspectShadow(element);
220
+ element.querySelectorAll(shadowSelector).forEach(inspectShadow);
221
+ }
222
+ if (wanted.size === observed.size && Array.from(wanted).every((element) => observed.has(element))) return;
223
+ observer.disconnect();
224
+ resize.disconnect();
225
+ observed.clear();
226
+ observer.observe(doc.documentElement, observation);
227
+ for (const element of wanted) {
228
+ observed.add(element);
229
+ if (element instanceof ShadowRoot) observer.observe(element, observation);
230
+ else resize.observe(element);
231
+ }
232
+ };
233
+ const fail = async (error) => {
234
+ reason = error instanceof Error ? error.message : String(error);
235
+ console.warn("[Native UI Shell] Returning to Web:", reason);
236
+ await handle.destroy();
237
+ win.dispatchEvent(new Event("nativeUIShellRefresh"));
238
+ };
239
+ const sync = async () => {
240
+ frame = 0;
241
+ dirty = false;
242
+ pending = true;
243
+ handoffInstant = tabSwitchHandoff || handoffAcrossPending || modalPresentHandoff || win.performance.now() < handoffUntil;
244
+ modalPresentHandoff = false;
245
+ try {
246
+ const size = `${win.innerWidth}:${win.innerHeight}`;
247
+ if (viewport !== size) {
248
+ viewport = size;
249
+ dirty = true;
250
+ return;
251
+ }
252
+ observe();
253
+ const candidates = read();
254
+ const signatures = new Map(candidates.map((candidate) => [candidate.element, signature(candidate)]));
255
+ for (const candidate of candidates) for (const icon of candidate.icons) {
256
+ const field = icon.field ?? "icon";
257
+ icon.item[field] = await icons.render(icon.source, field === "icon" ? icon.item.iconWidth : icon.item.closeIconWidth, field === "icon" ? icon.item.iconHeight : icon.item.closeIconHeight);
258
+ }
259
+ if (stopped || dirty) return;
260
+ const retained = new Set(candidates.flatMap(candidateSources));
261
+ const removed = Array.from(sources.keys()).filter((element) => !retained.has(element));
262
+ if (removed.length) {
263
+ removed.forEach(restore);
264
+ if (!handoffInstant && removed.some((element) => !isVerticalBarsSource(element))) {
265
+ await painted();
266
+ if (stopped || dirty) return;
267
+ }
268
+ }
269
+ const root = doc.querySelector("ion-app.ios-theme-vertical-bars");
270
+ const verticalBarEdge = root ? root.classList.contains("ios-theme-vertical-bars-left") ? "left" : "right" : void 0;
271
+ const modal = topModal(doc);
272
+ const data = {
273
+ verticalBarFrame: modal && modalUsesVerticalBars(modal) ? modalVerticalBarFrame(modal) : void 0,
274
+ viewportWidth: win.innerWidth,
275
+ verticalBarEdge,
276
+ controls: candidates.map((candidate) => candidate.control)
277
+ };
278
+ const serialized = JSON.stringify(data);
279
+ if (serialized === lastSnapshot && !forceRefresh) return;
280
+ const snapshot = {
281
+ ...data,
282
+ revision: ++revision,
283
+ transitionDuration: crossfade.duration(handoffInstant)
284
+ };
285
+ forceRefresh = false;
286
+ updates++;
287
+ updatingRevision = snapshot.revision;
288
+ const result = await bounded(plugin.update(snapshot));
289
+ if (stopped) return;
290
+ if (result.revision !== snapshot.revision) throw new Error("Native UI Shell revision mismatch");
291
+ if (`${win.innerWidth}:${win.innerHeight}` !== size) {
292
+ lastSnapshot = "";
293
+ dirty = true;
294
+ return;
295
+ }
296
+ if (result.rejectedSearches?.length) {
297
+ search.reject(result.rejectedSearches);
298
+ lastSnapshot = "";
299
+ dirty = true;
300
+ }
301
+ for (const candidate of candidates) if (result.rejectedControls?.includes(candidate.control.id)) {
302
+ rejected.set(candidate.element, signatures.get(candidate.element));
303
+ setRejected(candidate.element, true);
304
+ dirty = true;
305
+ }
306
+ const currentCandidates = dirty ? read() : candidates;
307
+ const current = dirty ? new Map(currentCandidates.map((candidate) => [candidate.element, signature(candidate)])) : signatures;
308
+ const currentSources = new Set(currentCandidates.flatMap(candidateSources));
309
+ const accepted = candidates.filter((candidate) => current.get(candidate.element) === signatures.get(candidate.element));
310
+ accepted.forEach((candidate) => setRejected(candidate.element, false));
311
+ const acceptedVerticalBarsOwners = new Set(accepted.filter((candidate) => candidate.control.placement === "vertical-bars").map((candidate) => candidate.element));
312
+ const acceptedVerticalBarsMembers = new Set(accepted.filter((candidate) => candidate.control.placement === "vertical-bars").flatMap((candidate) => Array.from(candidate.actions.values())));
313
+ const invalidated = candidates.length !== accepted.length;
314
+ acceptedRevision = result.revision;
315
+ lastSnapshot = invalidated ? "" : serialized;
316
+ actions = new Map(candidates.filter((candidate) => current.has(candidate.element)).flatMap((candidate) => Array.from(candidate.actions)));
317
+ for (const element of sources.keys()) if (!currentSources.has(element)) restore(element);
318
+ for (const element of verticalBarsOwners) {
319
+ if (acceptedVerticalBarsOwners.has(element)) continue;
320
+ element.removeAttribute(verticalBarsMarker);
321
+ verticalBarsOwners.delete(element);
322
+ }
323
+ for (const element of acceptedVerticalBarsOwners) {
324
+ element.setAttribute(verticalBarsMarker, "");
325
+ verticalBarsOwners.add(element);
326
+ }
327
+ for (const element of verticalBarsMembers) {
328
+ if (acceptedVerticalBarsMembers.has(element)) continue;
329
+ element.removeAttribute(verticalBarsMemberMarker);
330
+ verticalBarsMembers.delete(element);
331
+ }
332
+ for (const element of acceptedVerticalBarsMembers) {
333
+ element.setAttribute(verticalBarsMemberMarker, "");
334
+ verticalBarsMembers.add(element);
335
+ }
336
+ for (const element of accepted.flatMap(candidateSources)) {
337
+ const newlyProjected = !sources.has(element) || !element.hasAttribute("data-native-ui-shell");
338
+ if (!sources.has(element)) {
339
+ sources.set(element, element.getAttribute("aria-hidden"));
340
+ crossfade.play(element, true, handoffInstant || isVerticalBarsSource(element));
341
+ }
342
+ element.setAttribute(marker, "");
343
+ element.setAttribute("aria-hidden", "true");
344
+ if (newlyProjected) element.dispatchEvent(new CustomEvent("nativeUIShellChange"));
345
+ }
346
+ const received = pendingActivations;
347
+ pendingActivations = [];
348
+ received.forEach(activate);
349
+ await crossfade.settled();
350
+ if (invalidated) {
351
+ dirty = true;
352
+ await painted();
353
+ }
354
+ } catch (error) {
355
+ await fail(error);
356
+ } finally {
357
+ updatingRevision = void 0;
358
+ pendingActivations = [];
359
+ if (forceRefresh) rejected = /* @__PURE__ */ new WeakMap();
360
+ if (!stopped && (handoffAcrossPending || handoffInstant && dirty)) handoffUntil = Math.max(handoffUntil, win.performance.now() + 400);
361
+ handoffAcrossPending = false;
362
+ pending = false;
363
+ if (dirty && !stopped) schedule();
364
+ else finishWaiters();
365
+ }
366
+ };
367
+ const observation = {
368
+ subtree: true,
369
+ childList: true,
370
+ characterData: true,
371
+ attributes: true,
372
+ attributeOldValue: true
373
+ };
374
+ const observer = new MutationObserver((records) => {
375
+ if (records.some((record) => record.attributeName !== "data-native-ui-shell" && record.attributeName !== "data-native-ui-shell-fading" && !prehideOnlyMutation(record) && !(record.attributeName === "style" && measuringPointerPages.has(record.target)) && !(record.attributeName === "aria-hidden" && sources.has(record.target) && record.target.getAttribute("aria-hidden") === "true"))) schedule();
376
+ });
377
+ const resize = new ResizeObserver(schedule);
378
+ const on = (target, name, callback = schedule) => target.addEventListener(name, callback, {
379
+ capture: true,
380
+ signal: listeners.signal
381
+ });
382
+ const pageWill = (event) => {
383
+ const page = event.target;
384
+ verticalBarsPages.lifecycle(event);
385
+ getNativeSearchBindings(doc).filter((binding) => page.contains(binding.footer)).forEach((binding) => search.retire(binding));
386
+ if (event.type === LIFECYCLE_WILL_LEAVE) beginTabSwitchHandoffIfNeeded(page);
387
+ pages.add(page);
388
+ schedule();
389
+ };
390
+ const pageDid = (event) => {
391
+ const page = event.target;
392
+ verticalBarsPages.lifecycle(event);
393
+ pages.delete(page);
394
+ schedule();
395
+ if (tabSwitchHandoff && pages.size === 0) endTabSwitchHandoff();
396
+ };
397
+ const tabOfPath = (tabs, pathname) => {
398
+ let best;
399
+ for (const button of Array.from(tabs.querySelectorAll("ion-tab-button"))) {
400
+ const tab = button.getAttribute("tab");
401
+ const href = button.getAttribute("href")?.split(/[?#]/)[0];
402
+ if (!tab || !href) continue;
403
+ if (pathname === href || pathname.startsWith(`${href}/`)) {
404
+ if (!best || href.length > best.length) best = {
405
+ tab,
406
+ length: href.length
407
+ };
408
+ }
409
+ }
410
+ return best?.tab;
411
+ };
412
+ const armTabSwitchHandoff = () => {
413
+ tabSwitchHandoff = true;
414
+ handoffUntil = Math.max(handoffUntil, win.performance.now() + 400);
415
+ if (pending) handoffAcrossPending = true;
416
+ };
417
+ const beginTabSwitchHandoffIfNeeded = (page) => {
418
+ const tabs = page.closest("ion-tabs");
419
+ if (!tabs) return;
420
+ const bar = tabs.querySelector("ion-tab-bar");
421
+ const selected = (bar && "selectedTab" in bar ? String(bar.selectedTab ?? "") : "") || tabs.querySelector("ion-tab-button.tab-selected")?.getAttribute("tab") || void 0;
422
+ const destination = tabOfPath(tabs, win.location.pathname);
423
+ if (selected && destination && selected !== destination) armTabSwitchHandoff();
424
+ };
425
+ const endTabSwitchHandoff = () => {
426
+ win.requestAnimationFrame(() => {
427
+ if (stopped || !tabSwitchHandoff) return;
428
+ tabSwitchHandoff = false;
429
+ schedule();
430
+ });
431
+ };
432
+ const motion = (event) => {
433
+ const target = event.target;
434
+ if (!target.matches?.(motionSelector)) return;
435
+ const key = event.propertyName ?? event.animationName;
436
+ if (event.type === CSS_MOTION_EVENTS.transitionRun || event.type === CSS_MOTION_EVENTS.animationStart) {
437
+ const keys = moving.get(target) ?? /* @__PURE__ */ new Set();
438
+ keys.add(key);
439
+ moving.set(target, keys);
440
+ } else {
441
+ moving.get(target)?.delete(key);
442
+ if (!moving.get(target)?.size) moving.delete(target);
443
+ }
444
+ schedule();
445
+ };
446
+ for (const name of Object.values(CSS_MOTION_EVENTS)) on(doc, name, motion);
447
+ for (const name of [LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE]) on(doc, name, pageWill);
448
+ for (const name of [LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE]) on(doc, name, pageDid);
449
+ on(doc, VERTICAL_BARS_TRANSITION_CANCELED, (event) => {
450
+ const leaving = event.target;
451
+ const entering = event.detail?.entering;
452
+ verticalBarsPages.cancel(entering, leaving);
453
+ pages.delete(leaving);
454
+ if (entering) pages.delete(entering);
455
+ schedule();
456
+ });
457
+ on(doc, "ionTabsWillChange", () => {
458
+ armTabSwitchHandoff();
459
+ schedule();
460
+ });
461
+ on(doc, "ionTabsDidChange", endTabSwitchHandoff);
462
+ for (const name of overlayNames) {
463
+ on(doc, `ion${name}WillPresent`, (event) => {
464
+ presented.add(event.target);
465
+ if (name === "Modal") {
466
+ modalPresentHandoff = true;
467
+ if (pending) handoffInstant = true;
468
+ }
469
+ schedule();
470
+ });
471
+ on(doc, `ion${name}DidDismiss`, (event) => {
472
+ presented.delete(event.target);
473
+ schedule();
474
+ });
475
+ }
476
+ const eventMenu = (event) => event.composedPath().find((target) => target instanceof HTMLElement && target.matches("ion-menu"));
477
+ on(doc, "ionWillOpen", (event) => {
478
+ const menu = eventMenu(event);
479
+ if (menu) presented.add(menu);
480
+ schedule();
481
+ });
482
+ on(doc, "ionDidClose", (event) => {
483
+ const menu = eventMenu(event);
484
+ if (menu) presented.delete(menu);
485
+ schedule();
486
+ });
487
+ for (const name of [
488
+ "ionChange",
489
+ "ionSelect",
490
+ "ionImgDidLoad",
491
+ CSS_MOTION_EVENTS.transitionEnd,
492
+ CSS_MOTION_EVENTS.animationEnd,
493
+ "focusin",
494
+ "focusout"
495
+ ]) on(doc, name);
496
+ const refreshLayout = () => {
497
+ rejected = /* @__PURE__ */ new WeakMap();
498
+ forceRefresh = true;
499
+ schedule();
500
+ };
501
+ on(doc, "visibilitychange", () => {
502
+ if (doc.hidden) {
503
+ getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
504
+ schedule();
505
+ } else refreshLayout();
506
+ });
507
+ on(win, "resize", refreshLayout);
508
+ on(win, "keyboardWillShow", () => search.keyboard(true));
509
+ on(win, "keyboardWillHide", () => search.keyboard(false));
510
+ on(win, "scroll");
511
+ on(win.matchMedia("(prefers-color-scheme: dark)"), "change");
512
+ on(win, "nativeUIShellRefresh", (event) => {
513
+ if (event.retireSearch) getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
514
+ refreshLayout();
515
+ });
516
+ if (win.visualViewport) {
517
+ on(win.visualViewport, "resize", refreshLayout);
518
+ on(win.visualViewport, "scroll");
519
+ }
520
+ const handle = {
521
+ getStatus: () => ({
522
+ state: stopped ? "stopped" : sources.size ? "native" : "web",
523
+ projected: sources.size,
524
+ updates,
525
+ reason
526
+ }),
527
+ async suspend() {
528
+ const token = Symbol();
529
+ let resumed = false;
530
+ if (!stopped) {
531
+ manualSuspensions.add(token);
532
+ getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
533
+ await flush();
534
+ }
535
+ return { async resume() {
536
+ if (resumed) return;
537
+ resumed = true;
538
+ if (stopped || !manualSuspensions.delete(token)) return;
539
+ await flush();
540
+ } };
541
+ },
542
+ async destroy() {
543
+ if (stopped) return;
544
+ stopped = true;
545
+ setNativeUIShellIntegration(doc);
546
+ win.cancelAnimationFrame(frame);
547
+ listeners.abort();
548
+ observer.disconnect();
549
+ resize.disconnect();
550
+ observed.clear();
551
+ actions.clear();
552
+ pendingActivations = [];
553
+ search.destroy();
554
+ crossfade.destroy();
555
+ restoreAll();
556
+ if (!doc.hidden) await painted();
557
+ try {
558
+ await bounded(plugin.clear({ revision: ++revision }));
559
+ } catch {}
560
+ style.remove();
561
+ doc.querySelectorAll(`.${rejectedClass}`).forEach((element) => setRejected(element, false));
562
+ icons.clear();
563
+ pages.clear();
564
+ presented.clear();
565
+ manualSuspensions.clear();
566
+ moving.clear();
567
+ suspended.clear();
568
+ await listener?.remove().catch(() => {});
569
+ await searchListener?.remove().catch(() => {});
570
+ finishWaiters();
571
+ }
572
+ };
573
+ const activate = (event) => {
574
+ if (stopped || doc.hidden || event.revision < acceptedRevision || event.revision > revision || event.sequence <= lastSequence || overlayOpen(false, true)) return;
575
+ if (event.revision > acceptedRevision && event.revision === updatingRevision) {
576
+ pendingActivations.push(event);
577
+ return;
578
+ }
579
+ lastSequence = event.sequence;
580
+ const element = actions.get(event.id);
581
+ if (!element) return;
582
+ const owner = Array.from(sources.keys()).find((source) => source === element || source.contains(element));
583
+ if (!owner || !inVerticalBarsSurface(owner)) return;
584
+ const candidate = !blocked(owner) && unprojected(sources.keys(), () => readEnabledCandidate(owner)) || read().find((candidate) => candidate.actions.has(event.id));
585
+ const item = candidate?.control.items.find((item) => item.id === event.id);
586
+ if (!(candidate?.control.search && [candidate.control.search.trigger.id, candidate.control.search.closeId].includes(event.id)) && (!item || item.disabled || item.visible === false)) return;
587
+ activateProjectedElement(element);
588
+ lastSnapshot = "";
589
+ schedule();
590
+ };
591
+ try {
592
+ listener = await bounded(plugin.addListener("activate", activate));
593
+ searchListener = await bounded(plugin.addListener("search", (event) => {
594
+ if (stopped || doc.hidden || event.revision < acceptedRevision || event.revision > revision || overlayOpen()) return;
595
+ search.event(event);
596
+ }));
597
+ doc.head.append(style);
598
+ observer.observe(doc.documentElement, observation);
599
+ setNativeUIShellIntegration(doc, {
600
+ async search(binding, active, focus) {
601
+ if (!getNativeSearchBindings(doc).includes(binding) || !search.projected(binding)) return false;
602
+ if (!active) search.setStatus(binding, false);
603
+ else {
604
+ const entering = !binding.active;
605
+ if (entering) search.begin(binding, revision + 1);
606
+ const nextFocused = focus === true ? true : focus === false ? false : entering ? false : binding.focused;
607
+ search.setStatus(binding, true, nextFocused);
608
+ }
609
+ await flush();
610
+ return !stopped && search.projected(binding);
611
+ },
612
+ async suspend(scopes) {
613
+ getNativeSearchBindings(doc).filter((binding) => scopes.some((scope) => scope.contains(binding.footer) || scope.contains(binding.tabBar))).forEach((binding) => search.retire(binding));
614
+ suspended.add(scopes);
615
+ await flush();
616
+ await new Promise((resolve) => win.requestAnimationFrame(() => resolve()));
617
+ return (canceled = false) => {
618
+ suspended.delete(scopes);
619
+ if (canceled || !scopes.some((scope) => scope.closest("ion-app.ios-theme-vertical-bars"))) {
620
+ scopes.forEach((scope) => pages.delete(scope));
621
+ if (canceled) verticalBarsPages.cancel(scopes[0], scopes[1]);
622
+ }
623
+ schedule();
624
+ };
625
+ }
626
+ });
627
+ on(win, "pagehide", () => {
628
+ handle.destroy();
629
+ });
630
+ schedule();
631
+ } catch (error) {
632
+ await fail(error);
633
+ }
634
+ return handle;
743
635
  };
636
+ //#endregion
637
+ export { createRuntime };
638
+
639
+ //# sourceMappingURL=runtime.js.map