@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,349 +1,335 @@
1
- import { inVerticalBarsSurface, topModal } from './shared/modal';
2
- import { VERTICAL_BARS_TRANSITION_CANCELED } from '../native-integration';
3
- import { activateProjectedElement, createVerticalBarsPageState, verticalBarsActionCandidate, verticalBarsBackCandidate, verticalBarsEnteringPage, verticalBarsToolbarActions, isExcluded, isVerticalBarsToolbarGroup, preferredVerticalBarsBack, verticalBarsOwned, marker, prehideOnlyMutation, prehiddenClass, unprojected, withoutPrehide, } from './shared/dom';
4
- const backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';
5
- const toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';
6
- const readyClass = 'ios-theme-vertical-bars-toolbar-ready';
1
+ import { VERTICAL_BARS_TRANSITION_CANCELED } from "../native-integration/index.js";
2
+ import { inVerticalBarsSurface, topModal } from "./shared/modal.js";
3
+ import { activateProjectedElement, createVerticalBarsPageState, isExcluded, isVerticalBarsToolbarGroup, marker, preferredVerticalBarsBack, prehiddenClass, prehideOnlyMutation, unprojected, verticalBarsActionCandidate, verticalBarsBackCandidate, verticalBarsEnteringPage, verticalBarsOwned, verticalBarsToolbarActions, withoutPrehide } from "./shared/dom.js";
4
+ //#region src/native/vertical-bars-web.ts
5
+ const backProjectionClass = "ios-theme-vertical-bars-back-button-projection";
6
+ const toolbarProjectionClass = "ios-theme-vertical-bars-toolbar-projection";
7
+ const readyClass = "ios-theme-vertical-bars-toolbar-ready";
7
8
  const toolbarControlSize = 46;
8
9
  const toolbarControlGap = 10;
9
- export const createVerticalBarsWebProjection = (doc, options, enabled = () => true) => {
10
- const win = doc.defaultView;
11
- if (options.controls !== undefined && options.controls.toolbar !== true)
12
- return {
13
- getStatus: () => ({ state: 'web', projected: 0, updates: 0 }),
14
- suspend: async () => ({ resume: async () => { } }),
15
- destroy: async () => { },
16
- };
17
- let root;
18
- let backSource;
19
- let backProjection;
20
- let previousBackAriaHidden = null;
21
- let toolbarProjections = [];
22
- let suspended = 0;
23
- let stopped = false;
24
- let frame = 0;
25
- let updates = 0;
26
- let observingVerticalBars = false;
27
- const verticalBarsPages = createVerticalBarsPageState();
28
- let sourceObserver;
29
- let waiters = [];
30
- const listeners = new AbortController();
31
- const verticalBarsRoot = () => doc.querySelector('ion-app.ios-theme-vertical-bars');
32
- const projectedSources = () => [backSource, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter((source) => !!source);
33
- const isRendered = (element) => withoutPrehide(element, () => {
34
- const style = getComputedStyle(element);
35
- const rect = element.getBoundingClientRect();
36
- return element.isConnected && style.display !== 'none' && style.visibility === 'visible' && rect.width > 0 && rect.height > 0;
37
- });
38
- const inEligibleToolbar = (element) => !!verticalBarsRoot()?.contains(element) &&
39
- inVerticalBarsSurface(element) &&
40
- verticalBarsActionCandidate(element) &&
41
- !isExcluded(element, verticalBarsEnteringPage(element)) &&
42
- !verticalBarsPages.isDeparted(element);
43
- const isEligibleBack = (element) => !!verticalBarsRoot()?.contains(element) &&
44
- inVerticalBarsSurface(element) &&
45
- verticalBarsOwned(element) &&
46
- verticalBarsBackCandidate(element) &&
47
- !isExcluded(element, verticalBarsEnteringPage(element)) &&
48
- !verticalBarsPages.isDeparted(element) &&
49
- unprojected(projectedSources(), () => isRendered(element));
50
- const pageOrder = (element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page'));
51
- const findBack = () => {
52
- const candidates = Array.from(doc.querySelectorAll(`ion-back-button:not(.${backProjectionClass})`)).filter(isEligibleBack);
53
- return preferredVerticalBarsBack(candidates, doc);
54
- };
55
- const findToolbarGroups = () => {
56
- const candidates = Array.from(doc.querySelectorAll(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap((group) => {
57
- const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));
58
- if (!actions.length)
59
- return [];
60
- if (!isVerticalBarsToolbarGroup(group))
61
- return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));
62
- return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];
63
- });
64
- const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));
65
- return candidates.filter(({ group }) => pageOrder(group) === highestPage);
66
- };
67
- const isCurrentToolbarAction = (source) => findToolbarGroups().some(({ actions }) => actions.includes(source));
68
- const restore = () => {
69
- backProjection?.remove();
70
- backProjection = undefined;
71
- if (backSource) {
72
- backSource.removeAttribute(marker);
73
- if (previousBackAriaHidden === null)
74
- backSource.removeAttribute('aria-hidden');
75
- else
76
- backSource.setAttribute('aria-hidden', previousBackAriaHidden);
77
- backSource.dispatchEvent(new CustomEvent('nativeUIShellChange'));
78
- }
79
- backSource = undefined;
80
- previousBackAriaHidden = null;
81
- for (const { projection, actions } of toolbarProjections) {
82
- projection.remove();
83
- for (const { source, previousAriaHidden } of actions) {
84
- source.removeAttribute(marker);
85
- if (previousAriaHidden === null)
86
- source.removeAttribute('aria-hidden');
87
- else
88
- source.setAttribute('aria-hidden', previousAriaHidden);
89
- source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
90
- }
91
- }
92
- toolbarProjections = [];
93
- sourceObserver?.disconnect();
94
- sourceObserver = undefined;
95
- root?.classList.remove(readyClass);
96
- root = undefined;
97
- };
98
- const copyAttributes = (target, original) => {
99
- for (const name of ['id', 'slot', marker, 'aria-hidden'])
100
- target.removeAttribute(name);
101
- for (const attribute of Array.from(target.attributes))
102
- if (!['class', marker].includes(attribute.name) && !original.hasAttribute(attribute.name))
103
- target.removeAttribute(attribute.name);
104
- for (const attribute of Array.from(original.attributes))
105
- if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name))
106
- target.setAttribute(attribute.name, attribute.value);
107
- target.classList.remove(prehiddenClass);
108
- };
109
- const syncBack = (target, original) => {
110
- copyAttributes(target, original);
111
- target.classList.add(backProjectionClass, 'ion-cloned-element');
112
- target.disabled = original.disabled;
113
- target.defaultHref = original.defaultHref;
114
- target.icon = original.icon;
115
- target.color = original.color;
116
- target.mode = original.mode;
117
- target.text = '';
118
- };
119
- const syncAction = (target, original) => {
120
- copyAttributes(target, original);
121
- target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');
122
- const label = original.getAttribute('aria-label') ??
123
- original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??
124
- original.textContent?.trim();
125
- if (label)
126
- target.setAttribute('aria-label', label);
127
- if ('disabled' in original)
128
- target.disabled = original.disabled;
129
- if (original.matches('ion-button')) {
130
- const icon = original.querySelector('ion-icon, svg');
131
- const clonedIcon = icon?.cloneNode(true);
132
- clonedIcon?.setAttribute('slot', 'icon-only');
133
- target.replaceChildren(...(clonedIcon ? [clonedIcon] : []));
134
- }
135
- };
136
- const sameSources = (back, groups) => back === backSource &&
137
- groups.length === toolbarProjections.length &&
138
- groups.every(({ group, actions }, index) => {
139
- const current = toolbarProjections[index];
140
- return (group === current.source &&
141
- actions.length === current.actions.length &&
142
- actions.every((action, actionIndex) => action === current.actions[actionIndex].source));
143
- });
144
- const syncExisting = () => {
145
- if (backSource && backProjection)
146
- syncBack(backProjection, backSource);
147
- let topOffset = backSource ? toolbarControlSize + toolbarControlGap : 0;
148
- for (const { projection, source, actions } of toolbarProjections) {
149
- if (actions.length === 1 && !projection.matches('ion-buttons'))
150
- syncAction(projection, actions[0].source);
151
- else
152
- copyAttributes(projection, source);
153
- projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
154
- projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
155
- if (projection.matches('ion-buttons'))
156
- actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));
157
- topOffset += actions.length * toolbarControlSize + toolbarControlGap;
158
- }
159
- };
160
- const project = (nextBack, groups) => {
161
- root = topModal(doc) ?? verticalBarsRoot();
162
- let topOffset = 0;
163
- if (nextBack) {
164
- backSource = nextBack;
165
- previousBackAriaHidden = nextBack.getAttribute('aria-hidden');
166
- backProjection = nextBack.cloneNode(false);
167
- syncBack(backProjection, nextBack);
168
- backProjection.addEventListener('click', (event) => {
169
- event.preventDefault();
170
- event.stopImmediatePropagation();
171
- if (backSource && backSource === findBack())
172
- activateProjectedElement(backSource);
173
- }, { capture: true });
174
- root.append(backProjection);
175
- nextBack.setAttribute(marker, '');
176
- nextBack.setAttribute('aria-hidden', 'true');
177
- nextBack.dispatchEvent(new CustomEvent('nativeUIShellChange'));
178
- topOffset = toolbarControlSize + toolbarControlGap;
179
- }
180
- for (const { group, actions: sources } of groups) {
181
- const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false);
182
- if (sources.length === 1)
183
- syncAction(projection, sources[0]);
184
- else
185
- copyAttributes(projection, group);
186
- projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
187
- projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
188
- const actions = sources.map((source) => {
189
- const clone = sources.length === 1 ? projection : source.cloneNode(false);
190
- if (sources.length > 1)
191
- syncAction(clone, source);
192
- clone.addEventListener('click', (event) => {
193
- event.preventDefault();
194
- event.stopImmediatePropagation();
195
- if (isCurrentToolbarAction(source))
196
- activateProjectedElement(source);
197
- }, { capture: true });
198
- if (sources.length > 1)
199
- projection.append(clone);
200
- const previousAriaHidden = source.getAttribute('aria-hidden');
201
- source.setAttribute(marker, '');
202
- source.setAttribute('aria-hidden', 'true');
203
- source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
204
- return { source, projection: clone, previousAriaHidden };
205
- });
206
- root.append(projection);
207
- toolbarProjections.push({ source: group, projection, actions });
208
- topOffset += actions.length * toolbarControlSize + toolbarControlGap;
209
- }
210
- if (nextBack || toolbarProjections.length)
211
- root.classList.add(readyClass);
212
- sourceObserver = new MutationObserver((records) => {
213
- if (records.some((record) => record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record))))
214
- schedule();
215
- });
216
- if (nextBack?.shadowRoot)
217
- sourceObserver.observe(nextBack.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
218
- for (const { group } of groups)
219
- sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
220
- };
221
- const performUpdate = () => {
222
- frame = 0;
223
- const currentRoot = verticalBarsRoot();
224
- if (observingVerticalBars !== !!currentRoot) {
225
- observingVerticalBars = !!currentRoot;
226
- observer.disconnect();
227
- observer.observe(doc.documentElement, {
228
- subtree: true,
229
- childList: true,
230
- attributes: true,
231
- attributeOldValue: true,
232
- attributeFilter: observingVerticalBars ? undefined : ['class'],
233
- });
234
- }
235
- if (stopped || suspended || !currentRoot || !enabled())
236
- return restore();
237
- const nextBack = findBack();
238
- const groups = findToolbarGroups();
239
- if (!nextBack && !groups.length)
240
- return restore();
241
- if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups))
242
- return syncExisting();
243
- restore();
244
- project(nextBack, groups);
245
- updates++;
246
- };
247
- const update = () => {
248
- try {
249
- performUpdate();
250
- }
251
- catch {
252
- restore();
253
- }
254
- finally {
255
- const pending = waiters;
256
- waiters = [];
257
- pending.forEach((resolve) => resolve());
258
- }
259
- };
260
- const schedule = () => {
261
- if (stopped)
262
- return Promise.resolve();
263
- if (!enabled())
264
- restore();
265
- const done = new Promise((resolve) => waiters.push(resolve));
266
- if (!frame)
267
- frame = win.requestAnimationFrame(update);
268
- return done;
269
- };
270
- const observer = new MutationObserver((records) => {
271
- const insideProjection = (target) => {
272
- const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;
273
- const rootNode = target.getRootNode();
274
- const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;
275
- return !![element, shadowHost].some((candidate) => candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}`));
276
- };
277
- const verticalBarsChanged = records.some((record) => (record.type === 'attributes' && record.target.matches('ion-app')) ||
278
- (record.type === 'childList' &&
279
- Array.from(record.addedNodes).some((node) => node instanceof Element &&
280
- (node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')))));
281
- if ((observingVerticalBars &&
282
- records.some((record) => record.attributeName !== marker && !prehideOnlyMutation(record) && !insideProjection(record.target))) ||
283
- (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record))))
284
- schedule();
285
- });
286
- observer.observe(doc.documentElement, {
287
- subtree: true,
288
- childList: true,
289
- attributes: true,
290
- attributeOldValue: true,
291
- attributeFilter: ['class'],
292
- });
293
- const pageLifecycle = (event) => {
294
- const page = event.target;
295
- if (!(page instanceof HTMLElement) || !page.matches('.ion-page'))
296
- return;
297
- if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {
298
- const entering = event.detail?.entering;
299
- verticalBarsPages.cancel(entering, page);
300
- }
301
- else
302
- verticalBarsPages.lifecycle(event);
303
- if (verticalBarsRoot())
304
- schedule();
305
- };
306
- for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])
307
- doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });
308
- doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });
309
- for (const name of ['ionModalWillPresent', 'ionModalDidDismiss'])
310
- doc.addEventListener(name, schedule, { capture: true, signal: listeners.signal });
311
- win.addEventListener('resize', schedule, { signal: listeners.signal });
312
- win.addEventListener('nativeUIShellRefresh', schedule, { signal: listeners.signal });
313
- schedule();
314
- return {
315
- getStatus: () => ({
316
- state: stopped ? 'stopped' : 'web',
317
- projected: Number(!!backSource) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),
318
- updates,
319
- }),
320
- async suspend() {
321
- suspended++;
322
- restore();
323
- let resumed = false;
324
- return {
325
- async resume() {
326
- if (resumed)
327
- return;
328
- resumed = true;
329
- suspended = Math.max(0, suspended - 1);
330
- if (!suspended)
331
- await schedule();
332
- },
333
- };
334
- },
335
- async destroy() {
336
- if (stopped)
337
- return;
338
- stopped = true;
339
- win.cancelAnimationFrame(frame);
340
- frame = 0;
341
- waiters.forEach((resolve) => resolve());
342
- waiters = [];
343
- observer.disconnect();
344
- sourceObserver?.disconnect();
345
- listeners.abort();
346
- restore();
347
- },
348
- };
10
+ const createVerticalBarsWebProjection = (doc, options, enabled = () => true) => {
11
+ const win = doc.defaultView;
12
+ if (options.controls !== void 0 && options.controls.toolbar !== true) return {
13
+ getStatus: () => ({
14
+ state: "web",
15
+ projected: 0,
16
+ updates: 0
17
+ }),
18
+ suspend: async () => ({ resume: async () => {} }),
19
+ destroy: async () => {}
20
+ };
21
+ let root;
22
+ let backSource;
23
+ let backProjection;
24
+ let previousBackAriaHidden = null;
25
+ let toolbarProjections = [];
26
+ let suspended = 0;
27
+ let stopped = false;
28
+ let frame = 0;
29
+ let updates = 0;
30
+ let observingVerticalBars = false;
31
+ const verticalBarsPages = createVerticalBarsPageState();
32
+ let sourceObserver;
33
+ let waiters = [];
34
+ const listeners = new AbortController();
35
+ const verticalBarsRoot = () => doc.querySelector("ion-app.ios-theme-vertical-bars");
36
+ const projectedSources = () => [backSource, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter((source) => !!source);
37
+ const isRendered = (element) => withoutPrehide(element, () => {
38
+ const style = getComputedStyle(element);
39
+ const rect = element.getBoundingClientRect();
40
+ return element.isConnected && style.display !== "none" && style.visibility === "visible" && rect.width > 0 && rect.height > 0;
41
+ });
42
+ const inEligibleToolbar = (element) => !!verticalBarsRoot()?.contains(element) && inVerticalBarsSurface(element) && verticalBarsActionCandidate(element) && !isExcluded(element, verticalBarsEnteringPage(element)) && !verticalBarsPages.isDeparted(element);
43
+ const isEligibleBack = (element) => !!verticalBarsRoot()?.contains(element) && inVerticalBarsSurface(element) && verticalBarsOwned(element) && verticalBarsBackCandidate(element) && !isExcluded(element, verticalBarsEnteringPage(element)) && !verticalBarsPages.isDeparted(element) && unprojected(projectedSources(), () => isRendered(element));
44
+ const pageOrder = (element) => Array.from(doc.querySelectorAll(".ion-page")).indexOf(element.closest(".ion-page"));
45
+ const findBack = () => {
46
+ const candidates = Array.from(doc.querySelectorAll(`ion-back-button:not(.${backProjectionClass})`)).filter(isEligibleBack);
47
+ return preferredVerticalBarsBack(candidates, doc);
48
+ };
49
+ const findToolbarGroups = () => {
50
+ const candidates = Array.from(doc.querySelectorAll(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap((group) => {
51
+ const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));
52
+ if (!actions.length) return [];
53
+ if (!isVerticalBarsToolbarGroup(group)) return actions.filter(inEligibleToolbar).map((action) => ({
54
+ group,
55
+ actions: [action]
56
+ }));
57
+ return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{
58
+ group,
59
+ actions
60
+ }] : [];
61
+ });
62
+ const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));
63
+ return candidates.filter(({ group }) => pageOrder(group) === highestPage);
64
+ };
65
+ const isCurrentToolbarAction = (source) => findToolbarGroups().some(({ actions }) => actions.includes(source));
66
+ const restore = () => {
67
+ backProjection?.remove();
68
+ backProjection = void 0;
69
+ if (backSource) {
70
+ backSource.removeAttribute(marker);
71
+ if (previousBackAriaHidden === null) backSource.removeAttribute("aria-hidden");
72
+ else backSource.setAttribute("aria-hidden", previousBackAriaHidden);
73
+ backSource.dispatchEvent(new CustomEvent("nativeUIShellChange"));
74
+ }
75
+ backSource = void 0;
76
+ previousBackAriaHidden = null;
77
+ for (const { projection, actions } of toolbarProjections) {
78
+ projection.remove();
79
+ for (const { source, previousAriaHidden } of actions) {
80
+ source.removeAttribute(marker);
81
+ if (previousAriaHidden === null) source.removeAttribute("aria-hidden");
82
+ else source.setAttribute("aria-hidden", previousAriaHidden);
83
+ source.dispatchEvent(new CustomEvent("nativeUIShellChange"));
84
+ }
85
+ }
86
+ toolbarProjections = [];
87
+ sourceObserver?.disconnect();
88
+ sourceObserver = void 0;
89
+ root?.classList.remove(readyClass);
90
+ root = void 0;
91
+ };
92
+ const copyAttributes = (target, original) => {
93
+ for (const name of [
94
+ "id",
95
+ "slot",
96
+ marker,
97
+ "aria-hidden"
98
+ ]) target.removeAttribute(name);
99
+ for (const attribute of Array.from(target.attributes)) if (!["class", "data-native-ui-shell"].includes(attribute.name) && !original.hasAttribute(attribute.name)) target.removeAttribute(attribute.name);
100
+ for (const attribute of Array.from(original.attributes)) if (![
101
+ "id",
102
+ "slot",
103
+ "data-native-ui-shell",
104
+ "aria-hidden"
105
+ ].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);
106
+ target.classList.remove(prehiddenClass);
107
+ };
108
+ const syncBack = (target, original) => {
109
+ copyAttributes(target, original);
110
+ target.classList.add(backProjectionClass, "ion-cloned-element");
111
+ target.disabled = original.disabled;
112
+ target.defaultHref = original.defaultHref;
113
+ target.icon = original.icon;
114
+ target.color = original.color;
115
+ target.mode = original.mode;
116
+ target.text = "";
117
+ };
118
+ const syncAction = (target, original) => {
119
+ copyAttributes(target, original);
120
+ target.classList.add("ios-theme-vertical-bars-toolbar-action", "ion-cloned-element");
121
+ const label = original.getAttribute("aria-label") ?? original.shadowRoot?.querySelector("[part~=native]")?.getAttribute("aria-label") ?? original.textContent?.trim();
122
+ if (label) target.setAttribute("aria-label", label);
123
+ if ("disabled" in original) target.disabled = original.disabled;
124
+ if (original.matches("ion-button")) {
125
+ const clonedIcon = original.querySelector("ion-icon, svg")?.cloneNode(true);
126
+ clonedIcon?.setAttribute("slot", "icon-only");
127
+ target.replaceChildren(...clonedIcon ? [clonedIcon] : []);
128
+ }
129
+ };
130
+ const sameSources = (back, groups) => back === backSource && groups.length === toolbarProjections.length && groups.every(({ group, actions }, index) => {
131
+ const current = toolbarProjections[index];
132
+ return group === current.source && actions.length === current.actions.length && actions.every((action, actionIndex) => action === current.actions[actionIndex].source);
133
+ });
134
+ const syncExisting = () => {
135
+ if (backSource && backProjection) syncBack(backProjection, backSource);
136
+ let topOffset = backSource ? 56 : 0;
137
+ for (const { projection, source, actions } of toolbarProjections) {
138
+ if (actions.length === 1 && !projection.matches("ion-buttons")) syncAction(projection, actions[0].source);
139
+ else copyAttributes(projection, source);
140
+ projection.classList.add(toolbarProjectionClass, "ion-cloned-element");
141
+ projection.style.setProperty("--ios-theme-vertical-bars-toolbar-offset", `${topOffset}px`);
142
+ if (projection.matches("ion-buttons")) actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));
143
+ topOffset += actions.length * toolbarControlSize + toolbarControlGap;
144
+ }
145
+ };
146
+ const project = (nextBack, groups) => {
147
+ root = topModal(doc) ?? verticalBarsRoot();
148
+ let topOffset = 0;
149
+ if (nextBack) {
150
+ backSource = nextBack;
151
+ previousBackAriaHidden = nextBack.getAttribute("aria-hidden");
152
+ backProjection = nextBack.cloneNode(false);
153
+ syncBack(backProjection, nextBack);
154
+ backProjection.addEventListener("click", (event) => {
155
+ event.preventDefault();
156
+ event.stopImmediatePropagation();
157
+ if (backSource && backSource === findBack()) activateProjectedElement(backSource);
158
+ }, { capture: true });
159
+ root.append(backProjection);
160
+ nextBack.setAttribute(marker, "");
161
+ nextBack.setAttribute("aria-hidden", "true");
162
+ nextBack.dispatchEvent(new CustomEvent("nativeUIShellChange"));
163
+ topOffset = 56;
164
+ }
165
+ for (const { group, actions: sources } of groups) {
166
+ const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false);
167
+ if (sources.length === 1) syncAction(projection, sources[0]);
168
+ else copyAttributes(projection, group);
169
+ projection.classList.add(toolbarProjectionClass, "ion-cloned-element");
170
+ projection.style.setProperty("--ios-theme-vertical-bars-toolbar-offset", `${topOffset}px`);
171
+ const actions = sources.map((source) => {
172
+ const clone = sources.length === 1 ? projection : source.cloneNode(false);
173
+ if (sources.length > 1) syncAction(clone, source);
174
+ clone.addEventListener("click", (event) => {
175
+ event.preventDefault();
176
+ event.stopImmediatePropagation();
177
+ if (isCurrentToolbarAction(source)) activateProjectedElement(source);
178
+ }, { capture: true });
179
+ if (sources.length > 1) projection.append(clone);
180
+ const previousAriaHidden = source.getAttribute("aria-hidden");
181
+ source.setAttribute(marker, "");
182
+ source.setAttribute("aria-hidden", "true");
183
+ source.dispatchEvent(new CustomEvent("nativeUIShellChange"));
184
+ return {
185
+ source,
186
+ projection: clone,
187
+ previousAriaHidden
188
+ };
189
+ });
190
+ root.append(projection);
191
+ toolbarProjections.push({
192
+ source: group,
193
+ projection,
194
+ actions
195
+ });
196
+ topOffset += actions.length * toolbarControlSize + toolbarControlGap;
197
+ }
198
+ if (nextBack || toolbarProjections.length) root.classList.add(readyClass);
199
+ sourceObserver = new MutationObserver((records) => {
200
+ if (records.some((record) => record.type !== "attributes" || !["data-native-ui-shell", "aria-hidden"].includes(record.attributeName ?? "") && !prehideOnlyMutation(record))) schedule();
201
+ });
202
+ if (nextBack?.shadowRoot) sourceObserver.observe(nextBack.shadowRoot, {
203
+ subtree: true,
204
+ childList: true,
205
+ attributes: true,
206
+ attributeOldValue: true
207
+ });
208
+ for (const { group } of groups) sourceObserver.observe(group, {
209
+ subtree: true,
210
+ childList: true,
211
+ attributes: true,
212
+ attributeOldValue: true
213
+ });
214
+ };
215
+ const performUpdate = () => {
216
+ frame = 0;
217
+ const currentRoot = verticalBarsRoot();
218
+ if (observingVerticalBars !== !!currentRoot) {
219
+ observingVerticalBars = !!currentRoot;
220
+ observer.disconnect();
221
+ observer.observe(doc.documentElement, {
222
+ subtree: true,
223
+ childList: true,
224
+ attributes: true,
225
+ attributeOldValue: true,
226
+ attributeFilter: observingVerticalBars ? void 0 : ["class"]
227
+ });
228
+ }
229
+ if (stopped || suspended || !currentRoot || !enabled()) return restore();
230
+ const nextBack = findBack();
231
+ const groups = findToolbarGroups();
232
+ if (!nextBack && !groups.length) return restore();
233
+ if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups)) return syncExisting();
234
+ restore();
235
+ project(nextBack, groups);
236
+ updates++;
237
+ };
238
+ const update = () => {
239
+ try {
240
+ performUpdate();
241
+ } catch {
242
+ restore();
243
+ } finally {
244
+ const pending = waiters;
245
+ waiters = [];
246
+ pending.forEach((resolve) => resolve());
247
+ }
248
+ };
249
+ const schedule = () => {
250
+ if (stopped) return Promise.resolve();
251
+ if (!enabled()) restore();
252
+ const done = new Promise((resolve) => waiters.push(resolve));
253
+ if (!frame) frame = win.requestAnimationFrame(update);
254
+ return done;
255
+ };
256
+ const observer = new MutationObserver((records) => {
257
+ const insideProjection = (target) => {
258
+ const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : void 0;
259
+ const rootNode = target.getRootNode();
260
+ return !![element, rootNode instanceof ShadowRoot ? rootNode.host : void 0].some((candidate) => candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}`));
261
+ };
262
+ const verticalBarsChanged = records.some((record) => record.type === "attributes" && record.target.matches("ion-app") || record.type === "childList" && Array.from(record.addedNodes).some((node) => node instanceof Element && (node.matches("ion-app.ios-theme-vertical-bars") || !!node.querySelector("ion-app.ios-theme-vertical-bars"))));
263
+ if (observingVerticalBars && records.some((record) => record.attributeName !== "data-native-ui-shell" && !prehideOnlyMutation(record) && !insideProjection(record.target)) || !observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record))) schedule();
264
+ });
265
+ observer.observe(doc.documentElement, {
266
+ subtree: true,
267
+ childList: true,
268
+ attributes: true,
269
+ attributeOldValue: true,
270
+ attributeFilter: ["class"]
271
+ });
272
+ const pageLifecycle = (event) => {
273
+ const page = event.target;
274
+ if (!(page instanceof HTMLElement) || !page.matches(".ion-page")) return;
275
+ if (event.type === "iosThemeVerticalBarsTransitionCanceled") {
276
+ const entering = event.detail?.entering;
277
+ verticalBarsPages.cancel(entering, page);
278
+ } else verticalBarsPages.lifecycle(event);
279
+ if (verticalBarsRoot()) schedule();
280
+ };
281
+ for (const name of [
282
+ "ionViewWillEnter",
283
+ "ionViewWillLeave",
284
+ "ionViewDidEnter",
285
+ "ionViewDidLeave"
286
+ ]) doc.addEventListener(name, pageLifecycle, {
287
+ capture: true,
288
+ signal: listeners.signal
289
+ });
290
+ doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, {
291
+ capture: true,
292
+ signal: listeners.signal
293
+ });
294
+ for (const name of ["ionModalWillPresent", "ionModalDidDismiss"]) doc.addEventListener(name, schedule, {
295
+ capture: true,
296
+ signal: listeners.signal
297
+ });
298
+ win.addEventListener("resize", schedule, { signal: listeners.signal });
299
+ win.addEventListener("nativeUIShellRefresh", schedule, { signal: listeners.signal });
300
+ schedule();
301
+ return {
302
+ getStatus: () => ({
303
+ state: stopped ? "stopped" : "web",
304
+ projected: Number(!!backSource) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),
305
+ updates
306
+ }),
307
+ async suspend() {
308
+ suspended++;
309
+ restore();
310
+ let resumed = false;
311
+ return { async resume() {
312
+ if (resumed) return;
313
+ resumed = true;
314
+ suspended = Math.max(0, suspended - 1);
315
+ if (!suspended) await schedule();
316
+ } };
317
+ },
318
+ async destroy() {
319
+ if (stopped) return;
320
+ stopped = true;
321
+ win.cancelAnimationFrame(frame);
322
+ frame = 0;
323
+ waiters.forEach((resolve) => resolve());
324
+ waiters = [];
325
+ observer.disconnect();
326
+ sourceObserver?.disconnect();
327
+ listeners.abort();
328
+ restore();
329
+ }
330
+ };
349
331
  };
332
+ //#endregion
333
+ export { createVerticalBarsWebProjection };
334
+
335
+ //# sourceMappingURL=vertical-bars-web.js.map