@rdlabo/ionic-theme-ios27 1.2.1-beta.pr275.shad339e7688db7 → 2.0.0-1

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 (248) hide show
  1. package/README.md +24 -280
  2. package/dist/css/components/ion-toolbar.css +1 -1
  3. package/dist/css/ionic-theme-ios27.css +1 -1
  4. package/dist/css/vertical-bars.css +1 -1
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/layout/index.d.ts +18 -0
  10. package/dist/layout/index.d.ts.map +1 -0
  11. package/dist/layout/index.js +26 -0
  12. package/dist/layout/index.js.map +1 -0
  13. package/dist/segment/index.js +3 -3
  14. package/dist/segment/index.js.map +1 -1
  15. package/dist/sheets-of-glass/index.js +7 -7
  16. package/dist/sheets-of-glass/index.js.map +1 -1
  17. package/dist/tab-bar-searchable/dismiss-button.js +53 -3
  18. package/dist/tab-bar-searchable/dismiss-button.js.map +1 -1
  19. package/dist/tab-bar-searchable/index.js +18 -3
  20. package/dist/tab-bar-searchable/index.js.map +1 -1
  21. package/dist/transition/ios.transition.d.ts.map +1 -1
  22. package/dist/transition/ios.transition.js +7 -4
  23. package/dist/transition/ios.transition.js.map +1 -1
  24. package/dist/transition/native-ui.d.ts +6 -0
  25. package/dist/transition/native-ui.d.ts.map +1 -0
  26. package/dist/{native-integration/index.js → transition/native-ui.js} +16 -30
  27. package/dist/transition/native-ui.js.map +1 -0
  28. package/docs/adaptive-themes.md +161 -0
  29. package/docs/add-native-ui.md +53 -0
  30. package/docs/animation.md +19 -3
  31. package/docs/eslint.md +3 -1
  32. package/docs/features.md +9 -5
  33. package/docs/iphone-duo.md +62 -35
  34. package/docs/migration.md +17 -26
  35. package/docs/setup.md +92 -0
  36. package/docs/special-markup.md +41 -10
  37. package/docs/using-ion-item-group.md +2 -2
  38. package/docs/vertical-bars-layout.md +67 -0
  39. package/package.json +12 -42
  40. package/src/index.ts +3 -1
  41. package/src/layout/index.ts +29 -0
  42. package/src/segment/index.ts +3 -3
  43. package/src/sheets-of-glass/index.ts +7 -7
  44. package/src/styles/components/ion-toolbar.scss +1 -1
  45. package/src/styles/vertical-bars.scss +15 -18
  46. package/src/tab-bar-searchable/dismiss-button.ts +73 -4
  47. package/src/tab-bar-searchable/index.ts +20 -3
  48. package/src/transition/ios.transition.ts +7 -4
  49. package/src/{native-integration/index.ts → transition/native-ui.ts} +17 -46
  50. package/Package.swift +0 -21
  51. package/dist/_virtual/_rolldown/runtime.js +0 -13
  52. package/dist/native/components/index.d.ts +0 -19
  53. package/dist/native/components/index.d.ts.map +0 -1
  54. package/dist/native/components/index.js +0 -40
  55. package/dist/native/components/index.js.map +0 -1
  56. package/dist/native/components/ion-back-button.d.ts +0 -9
  57. package/dist/native/components/ion-back-button.d.ts.map +0 -1
  58. package/dist/native/components/ion-back-button.js +0 -20
  59. package/dist/native/components/ion-back-button.js.map +0 -1
  60. package/dist/native/components/ion-button.d.ts +0 -10
  61. package/dist/native/components/ion-button.d.ts.map +0 -1
  62. package/dist/native/components/ion-button.js +0 -23
  63. package/dist/native/components/ion-button.js.map +0 -1
  64. package/dist/native/components/ion-buttons.d.ts +0 -11
  65. package/dist/native/components/ion-buttons.d.ts.map +0 -1
  66. package/dist/native/components/ion-buttons.js +0 -30
  67. package/dist/native/components/ion-buttons.js.map +0 -1
  68. package/dist/native/components/ion-fab.d.ts +0 -10
  69. package/dist/native/components/ion-fab.d.ts.map +0 -1
  70. package/dist/native/components/ion-fab.js +0 -132
  71. package/dist/native/components/ion-fab.js.map +0 -1
  72. package/dist/native/components/ion-menu-button.d.ts +0 -13
  73. package/dist/native/components/ion-menu-button.d.ts.map +0 -1
  74. package/dist/native/components/ion-menu-button.js +0 -31
  75. package/dist/native/components/ion-menu-button.js.map +0 -1
  76. package/dist/native/components/ion-segment.d.ts +0 -11
  77. package/dist/native/components/ion-segment.d.ts.map +0 -1
  78. package/dist/native/components/ion-segment.js +0 -30
  79. package/dist/native/components/ion-segment.js.map +0 -1
  80. package/dist/native/components/ion-tab-bar.d.ts +0 -11
  81. package/dist/native/components/ion-tab-bar.d.ts.map +0 -1
  82. package/dist/native/components/ion-tab-bar.js +0 -50
  83. package/dist/native/components/ion-tab-bar.js.map +0 -1
  84. package/dist/native/components/searchable-tabs.d.ts +0 -20
  85. package/dist/native/components/searchable-tabs.d.ts.map +0 -1
  86. package/dist/native/components/searchable-tabs.js +0 -383
  87. package/dist/native/components/searchable-tabs.js.map +0 -1
  88. package/dist/native/definitions.d.ts +0 -191
  89. package/dist/native/definitions.d.ts.map +0 -1
  90. package/dist/native/definitions.js +0 -0
  91. package/dist/native/foldable/fold-state.d.ts +0 -5
  92. package/dist/native/foldable/fold-state.d.ts.map +0 -1
  93. package/dist/native/foldable/fold-state.js +0 -14
  94. package/dist/native/foldable/fold-state.js.map +0 -1
  95. package/dist/native/foldable/types.d.ts +0 -12
  96. package/dist/native/foldable/types.d.ts.map +0 -1
  97. package/dist/native/foldable/types.js +0 -0
  98. package/dist/native/index.d.ts +0 -11
  99. package/dist/native/index.d.ts.map +0 -1
  100. package/dist/native/index.js +0 -267
  101. package/dist/native/index.js.map +0 -1
  102. package/dist/native/overlays/content.d.ts +0 -4
  103. package/dist/native/overlays/content.d.ts.map +0 -1
  104. package/dist/native/overlays/content.js +0 -51
  105. package/dist/native/overlays/content.js.map +0 -1
  106. package/dist/native/overlays/controller.d.ts +0 -8
  107. package/dist/native/overlays/controller.d.ts.map +0 -1
  108. package/dist/native/overlays/controller.js +0 -285
  109. package/dist/native/overlays/controller.js.map +0 -1
  110. package/dist/native/overlays/dialog-animation.d.ts +0 -9
  111. package/dist/native/overlays/dialog-animation.d.ts.map +0 -1
  112. package/dist/native/overlays/dialog-animation.js +0 -53
  113. package/dist/native/overlays/dialog-animation.js.map +0 -1
  114. package/dist/native/overlays/dismiss-animation.d.ts +0 -4
  115. package/dist/native/overlays/dismiss-animation.d.ts.map +0 -1
  116. package/dist/native/overlays/dismiss-animation.js +0 -18
  117. package/dist/native/overlays/dismiss-animation.js.map +0 -1
  118. package/dist/native/overlays/focus.d.ts +0 -5
  119. package/dist/native/overlays/focus.d.ts.map +0 -1
  120. package/dist/native/overlays/focus.js +0 -59
  121. package/dist/native/overlays/focus.js.map +0 -1
  122. package/dist/native/overlays/popover.d.ts +0 -12
  123. package/dist/native/overlays/popover.d.ts.map +0 -1
  124. package/dist/native/overlays/popover.js +0 -110
  125. package/dist/native/overlays/popover.js.map +0 -1
  126. package/dist/native/overlays/styles.d.ts +0 -11
  127. package/dist/native/overlays/styles.d.ts.map +0 -1
  128. package/dist/native/overlays/styles.js +0 -167
  129. package/dist/native/overlays/styles.js.map +0 -1
  130. package/dist/native/prehide.d.ts +0 -7
  131. package/dist/native/prehide.d.ts.map +0 -1
  132. package/dist/native/prehide.js +0 -230
  133. package/dist/native/prehide.js.map +0 -1
  134. package/dist/native/runtime.d.ts +0 -7
  135. package/dist/native/runtime.d.ts.map +0 -1
  136. package/dist/native/runtime.js +0 -710
  137. package/dist/native/runtime.js.map +0 -1
  138. package/dist/native/shared/bridge.d.ts +0 -6
  139. package/dist/native/shared/bridge.d.ts.map +0 -1
  140. package/dist/native/shared/bridge.js +0 -18
  141. package/dist/native/shared/bridge.js.map +0 -1
  142. package/dist/native/shared/candidate.d.ts +0 -21
  143. package/dist/native/shared/candidate.d.ts.map +0 -1
  144. package/dist/native/shared/candidate.js +0 -98
  145. package/dist/native/shared/candidate.js.map +0 -1
  146. package/dist/native/shared/crossfade.d.ts +0 -12
  147. package/dist/native/shared/crossfade.d.ts.map +0 -1
  148. package/dist/native/shared/crossfade.js +0 -67
  149. package/dist/native/shared/crossfade.js.map +0 -1
  150. package/dist/native/shared/dom.d.ts +0 -46
  151. package/dist/native/shared/dom.d.ts.map +0 -1
  152. package/dist/native/shared/dom.js +0 -172
  153. package/dist/native/shared/dom.js.map +0 -1
  154. package/dist/native/shared/events.d.ts +0 -11
  155. package/dist/native/shared/events.d.ts.map +0 -1
  156. package/dist/native/shared/events.js +0 -13
  157. package/dist/native/shared/events.js.map +0 -1
  158. package/dist/native/shared/icons.d.ts +0 -8
  159. package/dist/native/shared/icons.d.ts.map +0 -1
  160. package/dist/native/shared/icons.js +0 -61
  161. package/dist/native/shared/icons.js.map +0 -1
  162. package/dist/native/shared/modal.d.ts +0 -10
  163. package/dist/native/shared/modal.d.ts.map +0 -1
  164. package/dist/native/shared/modal.js +0 -160
  165. package/dist/native/shared/modal.js.map +0 -1
  166. package/dist/native/shared/projection-id.d.ts +0 -4
  167. package/dist/native/shared/projection-id.d.ts.map +0 -1
  168. package/dist/native/shared/projection-id.js +0 -7
  169. package/dist/native/shared/projection-id.js.map +0 -1
  170. package/dist/native/shared/vertical-bars-geometry.d.ts +0 -10
  171. package/dist/native/shared/vertical-bars-geometry.d.ts.map +0 -1
  172. package/dist/native/shared/vertical-bars-geometry.js +0 -85
  173. package/dist/native/shared/vertical-bars-geometry.js.map +0 -1
  174. package/dist/native/vertical-bars-web-search.d.ts +0 -10
  175. package/dist/native/vertical-bars-web-search.d.ts.map +0 -1
  176. package/dist/native/vertical-bars-web-search.js +0 -90
  177. package/dist/native/vertical-bars-web-search.js.map +0 -1
  178. package/dist/native/vertical-bars-web.d.ts +0 -5
  179. package/dist/native/vertical-bars-web.d.ts.map +0 -1
  180. package/dist/native/vertical-bars-web.js +0 -411
  181. package/dist/native/vertical-bars-web.js.map +0 -1
  182. package/dist/native-integration/index.d.ts +0 -23
  183. package/dist/native-integration/index.d.ts.map +0 -1
  184. package/dist/native-integration/index.js.map +0 -1
  185. package/dist/native-integration/transition.d.ts +0 -5
  186. package/dist/native-integration/transition.d.ts.map +0 -1
  187. package/dist/native-integration/transition.js +0 -16
  188. package/dist/native-integration/transition.js.map +0 -1
  189. package/dist/vertical-bars.d.ts +0 -5
  190. package/dist/vertical-bars.js +0 -4
  191. package/docs/e2e-testing.md +0 -51
  192. package/docs/feedback.md +0 -65
  193. package/docs/iphone-duo-with-original-theme.md +0 -158
  194. package/docs/native-ui-shell.md +0 -325
  195. package/docs/vertical-bars.md +0 -300
  196. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButton.swift +0 -71
  197. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButtons.swift +0 -24
  198. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellComponents.swift +0 -18
  199. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellFab.swift +0 -80
  200. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +0 -432
  201. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSegment.swift +0 -91
  202. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +0 -218
  203. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +0 -565
  204. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +0 -118
  205. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +0 -502
  206. package/ios/Sources/IonicNativeUIShellPlugin/Overlays/ShellOverlayController.swift +0 -119
  207. package/ios/Sources/IonicNativeUIShellPlugin/Overlays/ShellOverlayHost.swift +0 -75
  208. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellCrossfade.swift +0 -35
  209. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellHost.swift +0 -8
  210. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellRendering.swift +0 -27
  211. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +0 -135
  212. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellVerticalBarsGeometry.swift +0 -52
  213. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +0 -788
  214. package/src/native/components/index.ts +0 -53
  215. package/src/native/components/ion-back-button.ts +0 -19
  216. package/src/native/components/ion-button.ts +0 -24
  217. package/src/native/components/ion-buttons.ts +0 -38
  218. package/src/native/components/ion-fab.ts +0 -151
  219. package/src/native/components/ion-menu-button.ts +0 -24
  220. package/src/native/components/ion-segment.ts +0 -27
  221. package/src/native/components/ion-tab-bar.ts +0 -50
  222. package/src/native/components/searchable-tabs.ts +0 -459
  223. package/src/native/definitions.ts +0 -218
  224. package/src/native/foldable/fold-state.ts +0 -9
  225. package/src/native/foldable/types.ts +0 -4
  226. package/src/native/index.ts +0 -315
  227. package/src/native/overlays/content.ts +0 -51
  228. package/src/native/overlays/controller.ts +0 -349
  229. package/src/native/overlays/dialog-animation.ts +0 -53
  230. package/src/native/overlays/dismiss-animation.ts +0 -15
  231. package/src/native/overlays/focus.ts +0 -64
  232. package/src/native/overlays/popover.ts +0 -120
  233. package/src/native/overlays/styles.ts +0 -181
  234. package/src/native/prehide.ts +0 -265
  235. package/src/native/runtime.ts +0 -885
  236. package/src/native/shared/bridge.ts +0 -18
  237. package/src/native/shared/candidate.ts +0 -150
  238. package/src/native/shared/crossfade.ts +0 -67
  239. package/src/native/shared/dom.ts +0 -286
  240. package/src/native/shared/events.ts +0 -8
  241. package/src/native/shared/icons.ts +0 -57
  242. package/src/native/shared/modal.ts +0 -157
  243. package/src/native/shared/projection-id.ts +0 -2
  244. package/src/native/shared/vertical-bars-geometry.ts +0 -80
  245. package/src/native/vertical-bars-web-search.ts +0 -86
  246. package/src/native/vertical-bars-web.ts +0 -485
  247. package/src/native-integration/transition.ts +0 -15
  248. package/src/vertical-bars.ts +0 -11
@@ -1,485 +0,0 @@
1
- import { createCrossfade, fadeMarker } from './shared/crossfade';
2
- import { alignVerticalBarClone, verticalBarFrame } from './shared/vertical-bars-geometry';
3
- import { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search';
4
- import { inVerticalBarsSurface, topModal } from './shared/modal';
5
- import type { NativeUIShellHandle, NativeUIShellOptions, NativeUIShellStatus } from './definitions';
6
- import { VERTICAL_BARS_TRANSITION_CANCELED } from '../native-integration';
7
- import {
8
- activateProjectedElement,
9
- createVerticalBarsPageState,
10
- verticalBarsActionCandidate,
11
- verticalBarsBackCandidate,
12
- verticalBarsEnteringPage,
13
- verticalBarsToolbarActions,
14
- isExcluded,
15
- isShellDisabled,
16
- isVerticalBarsToolbarGroup,
17
- preferredVerticalBarsBack,
18
- verticalBarsOwned,
19
- marker,
20
- syncToolbarText,
21
- toolbarTextMarker,
22
- prehideOnlyMutation,
23
- prehiddenClass,
24
- unprojected,
25
- withoutPrehide,
26
- } from './shared/dom';
27
-
28
- const backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';
29
- const toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';
30
- const readyClass = 'ios-theme-vertical-bars-toolbar-ready';
31
- const toolbarControlSize = 46;
32
- const toolbarControlGap = 10;
33
-
34
- interface ToolbarProjection {
35
- source: HTMLIonButtonsElement;
36
- projection: HTMLElement;
37
- actions: { source: HTMLElement; projection: HTMLElement; previousAriaHidden: string | null }[];
38
- }
39
-
40
- interface ToolbarSource {
41
- group: HTMLIonButtonsElement;
42
- actions: HTMLElement[];
43
- }
44
-
45
- export const createVerticalBarsWebProjection = (
46
- doc: Document,
47
- options: NativeUIShellOptions,
48
- enabled: () => boolean = () => true,
49
- ): NativeUIShellHandle => {
50
- const win = doc.defaultView!;
51
- const crossfade = createCrossfade(win);
52
- let incomingInstant: boolean | undefined;
53
- const toolbarEnabled = options.controls === undefined || options.controls.toolbar === true;
54
- let root: HTMLElement | undefined;
55
- let backSource: HTMLIonBackButtonElement | undefined;
56
- let backProjection: HTMLIonBackButtonElement | undefined;
57
- let previousBackAriaHidden: string | null = null;
58
- let toolbarProjections: ToolbarProjection[] = [];
59
- let suspended = 0;
60
- let stopped = false;
61
- let frame = 0;
62
- let updates = 0;
63
- let observingVerticalBars = false;
64
- const verticalBarsPages = createVerticalBarsPageState();
65
- let sourceObserver: MutationObserver | undefined;
66
- let waiters: (() => void)[] = [];
67
- const listeners = new AbortController();
68
- const verticalBarsRoot = () => doc.querySelector<HTMLElement>('ion-app.ios-theme-vertical-bars');
69
- const projectedSources = () =>
70
- [backSource, search.source, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter(
71
- (source): source is HTMLElement => !!source,
72
- );
73
- const isRendered = (element: HTMLElement) =>
74
- withoutPrehide(element, () => {
75
- const style = getComputedStyle(element);
76
- const rect = element.getBoundingClientRect();
77
- return element.isConnected && style.display !== 'none' && style.visibility === 'visible' && rect.width > 0 && rect.height > 0;
78
- });
79
- const search = createVerticalBarsWebSearch(
80
- doc,
81
- (element) =>
82
- (options.controls === undefined || options.controls.tabs === true) &&
83
- !!verticalBarsRoot()?.contains(element) &&
84
- inVerticalBarsSurface(element) &&
85
- !isExcluded(element, verticalBarsEnteringPage(element)) &&
86
- !isShellDisabled(element) &&
87
- !verticalBarsPages.isDeparted(element) &&
88
- isRendered(element),
89
- () => {
90
- void schedule();
91
- },
92
- );
93
- const inEligibleToolbar = (element: HTMLElement) =>
94
- !!verticalBarsRoot()?.contains(element) &&
95
- inVerticalBarsSurface(element) &&
96
- verticalBarsActionCandidate(element) &&
97
- !isExcluded(element, verticalBarsEnteringPage(element)) &&
98
- !verticalBarsPages.isDeparted(element);
99
- const isEligibleBack = (element: HTMLIonBackButtonElement) =>
100
- !!verticalBarsRoot()?.contains(element) &&
101
- inVerticalBarsSurface(element) &&
102
- verticalBarsOwned(element) &&
103
- verticalBarsBackCandidate(element) &&
104
- !isExcluded(element, verticalBarsEnteringPage(element)) &&
105
- !verticalBarsPages.isDeparted(element) &&
106
- unprojected(projectedSources(), () => isRendered(element));
107
- const pageOrder = (element: Element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page')!);
108
- const findBack = () => {
109
- const candidates = Array.from(doc.querySelectorAll<HTMLIonBackButtonElement>(`ion-back-button:not(.${backProjectionClass})`)).filter(
110
- isEligibleBack,
111
- );
112
- return preferredVerticalBarsBack(candidates, doc);
113
- };
114
- const findToolbarGroups = () => {
115
- const candidates = Array.from(doc.querySelectorAll<HTMLIonButtonsElement>(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap(
116
- (group): ToolbarSource[] => {
117
- const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));
118
- if (!actions.length) return [];
119
- if (!isVerticalBarsToolbarGroup(group)) return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));
120
- return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];
121
- },
122
- );
123
- const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));
124
- return candidates.filter(({ group }) => pageOrder(group) === highestPage);
125
- };
126
- const isCurrentToolbarAction = (source: HTMLElement) => findToolbarGroups().some(({ actions }) => actions.includes(source));
127
- const restore = (retire?: (surface: HTMLElement) => void) => {
128
- search.restore(retire);
129
- if (backProjection) {
130
- if (retire) retire(backProjection);
131
- else backProjection.remove();
132
- }
133
- backProjection = undefined;
134
- if (backSource) {
135
- backSource.removeAttribute(marker);
136
- if (previousBackAriaHidden === null) backSource.removeAttribute('aria-hidden');
137
- else backSource.setAttribute('aria-hidden', previousBackAriaHidden);
138
- backSource.dispatchEvent(new CustomEvent('nativeUIShellChange'));
139
- }
140
- backSource = undefined;
141
- previousBackAriaHidden = null;
142
- for (const { projection, actions } of toolbarProjections) {
143
- if (retire) retire(projection);
144
- else projection.remove();
145
- for (const { source, previousAriaHidden } of actions) {
146
- source.removeAttribute(marker);
147
- if (previousAriaHidden === null) source.removeAttribute('aria-hidden');
148
- else source.setAttribute('aria-hidden', previousAriaHidden);
149
- source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
150
- }
151
- }
152
- toolbarProjections = [];
153
- sourceObserver?.disconnect();
154
- sourceObserver = undefined;
155
- root?.classList.remove(readyClass);
156
- root = undefined;
157
- };
158
- const copyAttributes = (target: HTMLElement, original: HTMLElement) => {
159
- for (const name of ['id', 'slot', marker, 'aria-hidden']) target.removeAttribute(name);
160
- for (const attribute of Array.from(target.attributes))
161
- if (!['class', marker, fadeMarker].includes(attribute.name) && !original.hasAttribute(attribute.name))
162
- target.removeAttribute(attribute.name);
163
- for (const attribute of Array.from(original.attributes))
164
- if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);
165
- target.classList.remove(prehiddenClass);
166
- };
167
- const syncBack = (target: HTMLIonBackButtonElement, original: HTMLIonBackButtonElement) => {
168
- copyAttributes(target, original);
169
- target.classList.add(backProjectionClass, 'ion-cloned-element');
170
- target.disabled = original.disabled;
171
- target.defaultHref = original.defaultHref;
172
- target.icon = original.icon;
173
- target.color = original.color;
174
- target.mode = original.mode;
175
- target.text = '';
176
- if (root) alignVerticalBarClone(target, original, root);
177
- };
178
- const syncAction = (target: HTMLElement, original: HTMLElement) => {
179
- copyAttributes(target, original);
180
- target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');
181
- const label =
182
- original.getAttribute('aria-label') ??
183
- original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??
184
- original.textContent?.trim();
185
- if (label) target.setAttribute('aria-label', label);
186
- if ('disabled' in original) (target as HTMLIonButtonElement).disabled = (original as HTMLIonButtonElement).disabled;
187
- if (original.matches('ion-button')) {
188
- const icon = original.querySelector<HTMLElement>('ion-icon, svg');
189
- const clonedIcon = icon?.cloneNode(true) as HTMLElement | undefined;
190
- clonedIcon?.setAttribute('slot', 'icon-only');
191
- target.replaceChildren(...(clonedIcon ? [clonedIcon] : []));
192
- }
193
- };
194
- const sameSources = (back: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) =>
195
- back === backSource &&
196
- groups.length === toolbarProjections.length &&
197
- groups.every(({ group, actions }, index) => {
198
- const current = toolbarProjections[index];
199
- return (
200
- group === current.source &&
201
- actions.length === current.actions.length &&
202
- actions.every((action, actionIndex) => action === current.actions[actionIndex].source)
203
- );
204
- });
205
- const syncExisting = () => {
206
- if (backSource && backProjection) syncBack(backProjection, backSource);
207
- let topOffset = backSource ? toolbarControlSize + toolbarControlGap : 0;
208
- for (const { projection, source, actions } of toolbarProjections) {
209
- if (actions.length === 1 && !projection.matches('ion-buttons')) syncAction(projection, actions[0].source);
210
- else copyAttributes(projection, source);
211
- projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
212
- projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
213
- if (projection.matches('ion-buttons')) actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));
214
- const frames = actions.map(({ source }) => verticalBarFrame(source));
215
- if (root && frames.every((frame) => !!frame)) {
216
- if (actions.length === 1) alignVerticalBarClone(projection, actions[0].source, root);
217
- else {
218
- const left = Math.min(...frames.map((frame) => frame!.x));
219
- const top = Math.min(...frames.map((frame) => frame!.y));
220
- const right = Math.max(...frames.map((frame) => frame!.x + frame!.width));
221
- const bottom = Math.max(...frames.map((frame) => frame!.y + frame!.height));
222
- const origin = root.getBoundingClientRect();
223
- Object.assign(projection.style, {
224
- left: `${left - origin.left}px`,
225
- top: `${top - origin.top}px`,
226
- right: 'auto',
227
- bottom: 'auto',
228
- width: `${right - left}px`,
229
- height: `${bottom - top}px`,
230
- padding: '0',
231
- });
232
- actions.forEach(({ source, projection: clone }) => alignVerticalBarClone(clone, source, projection));
233
- }
234
- }
235
- topOffset += actions.length * toolbarControlSize + toolbarControlGap;
236
- }
237
- };
238
- const project = (nextBack: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) => {
239
- root = topModal(doc) ?? verticalBarsRoot()!;
240
- let topOffset = 0;
241
- if (nextBack) {
242
- backSource = nextBack;
243
- previousBackAriaHidden = nextBack.getAttribute('aria-hidden');
244
- backProjection = nextBack.cloneNode(false) as HTMLIonBackButtonElement;
245
- syncBack(backProjection, nextBack);
246
- backProjection.addEventListener(
247
- 'click',
248
- (event) => {
249
- event.preventDefault();
250
- event.stopImmediatePropagation();
251
- if (backSource && backSource === findBack()) activateProjectedElement(backSource);
252
- },
253
- { capture: true },
254
- );
255
- root.append(backProjection);
256
- nextBack.setAttribute(marker, '');
257
- nextBack.setAttribute('aria-hidden', 'true');
258
- nextBack.dispatchEvent(new CustomEvent('nativeUIShellChange'));
259
- topOffset = toolbarControlSize + toolbarControlGap;
260
- }
261
- for (const { group, actions: sources } of groups) {
262
- const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false) as HTMLElement;
263
- if (sources.length === 1) syncAction(projection, sources[0]);
264
- else copyAttributes(projection, group);
265
- projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
266
- projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
267
- const actions = sources.map((source) => {
268
- const clone = sources.length === 1 ? projection : (source.cloneNode(false) as HTMLElement);
269
- if (sources.length > 1) syncAction(clone, source);
270
- clone.addEventListener(
271
- 'click',
272
- (event) => {
273
- event.preventDefault();
274
- event.stopImmediatePropagation();
275
- if (isCurrentToolbarAction(source)) activateProjectedElement(source);
276
- },
277
- { capture: true },
278
- );
279
- if (sources.length > 1) projection.append(clone);
280
- const previousAriaHidden = source.getAttribute('aria-hidden');
281
- source.setAttribute(marker, '');
282
- source.setAttribute('aria-hidden', 'true');
283
- source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
284
- return { source, projection: clone, previousAriaHidden };
285
- });
286
- root.append(projection);
287
- toolbarProjections.push({ source: group, projection, actions });
288
- topOffset += actions.length * toolbarControlSize + toolbarControlGap;
289
- }
290
- syncExisting();
291
- if (nextBack || toolbarProjections.length) root.classList.add(readyClass);
292
- sourceObserver = new MutationObserver((records) => {
293
- if (
294
- records.some(
295
- (record) =>
296
- record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record)),
297
- )
298
- )
299
- schedule();
300
- });
301
- if (nextBack?.shadowRoot)
302
- sourceObserver.observe(nextBack.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
303
- for (const { group } of groups)
304
- sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
305
- };
306
- const reveal = () => {
307
- if (incomingInstant === undefined) return;
308
- for (const surface of [backProjection, ...toolbarProjections.map(({ projection }) => projection), search.surface])
309
- if (surface) crossfade.play(surface, false, incomingInstant);
310
- incomingInstant = undefined;
311
- };
312
- const performUpdate = () => {
313
- frame = 0;
314
- syncToolbarText(doc);
315
- const currentRoot = verticalBarsRoot();
316
- if (observingVerticalBars !== !!currentRoot) {
317
- observingVerticalBars = !!currentRoot;
318
- observer.disconnect();
319
- observer.observe(doc.documentElement, {
320
- subtree: true,
321
- childList: true,
322
- characterData: observingVerticalBars,
323
- attributes: true,
324
- attributeOldValue: true,
325
- attributeFilter: observingVerticalBars ? undefined : ['class'],
326
- });
327
- }
328
- if (stopped || suspended || !currentRoot || !enabled()) return restore();
329
- const nextBack = toolbarEnabled ? findBack() : undefined;
330
- const groups = toolbarEnabled ? findToolbarGroups() : [];
331
- const projectionRoot = topModal(doc) ?? currentRoot;
332
- search.update(projectionRoot);
333
- if (!nextBack && !groups.length && !search.source) return restore();
334
- if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups)) {
335
- syncExisting();
336
- reveal();
337
- return;
338
- }
339
- restore();
340
- project(nextBack, groups);
341
- search.update(projectionRoot);
342
- reveal();
343
- updates++;
344
- };
345
- const update = () => {
346
- try {
347
- performUpdate();
348
- } catch {
349
- restore();
350
- } finally {
351
- const pending = waiters;
352
- waiters = [];
353
- pending.forEach((resolve) => resolve());
354
- }
355
- };
356
- const schedule = (): Promise<void> => {
357
- if (stopped) return Promise.resolve();
358
- // Release Web ownership before the native runtime can acknowledge its next frame.
359
- if (!enabled()) restore();
360
- const done = new Promise<void>((resolve) => waiters.push(resolve));
361
- if (!frame) frame = win.requestAnimationFrame(update);
362
- return done;
363
- };
364
- const observer = new MutationObserver((records) => {
365
- const insideProjection = (target: Node) => {
366
- const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;
367
- const rootNode = target.getRootNode();
368
- const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;
369
- return !![element, shadowHost].some((candidate) =>
370
- candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}, .${searchProjectionClass}`),
371
- );
372
- };
373
- const verticalBarsChanged = records.some(
374
- (record) =>
375
- (record.type === 'attributes' && (record.target as Element).matches('ion-app')) ||
376
- (record.type === 'childList' &&
377
- Array.from(record.addedNodes).some(
378
- (node) =>
379
- node instanceof Element &&
380
- (node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')),
381
- )),
382
- );
383
- if (
384
- (observingVerticalBars &&
385
- records.some(
386
- (record) =>
387
- record.attributeName !== marker &&
388
- record.attributeName !== toolbarTextMarker &&
389
- !prehideOnlyMutation(record) &&
390
- !insideProjection(record.target),
391
- )) ||
392
- (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record)))
393
- )
394
- schedule();
395
- });
396
- observer.observe(doc.documentElement, {
397
- subtree: true,
398
- childList: true,
399
- attributes: true,
400
- attributeOldValue: true,
401
- attributeFilter: ['class'],
402
- });
403
- const pageLifecycle = (event: Event) => {
404
- const page = event.target;
405
- if (!(page instanceof HTMLElement) || !page.matches('.ion-page')) return;
406
- if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {
407
- const entering = (event as CustomEvent<{ entering?: HTMLElement }>).detail?.entering;
408
- verticalBarsPages.cancel(entering, page);
409
- } else verticalBarsPages.lifecycle(event);
410
- if (verticalBarsRoot()) schedule();
411
- };
412
- for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])
413
- doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });
414
- doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });
415
- for (const name of ['ionModalWillPresent', 'ionModalDidDismiss'])
416
- doc.addEventListener(name, schedule, { capture: true, signal: listeners.signal });
417
- win.addEventListener(
418
- 'nativeUIShellVerticalBarsAcquire',
419
- (event) => {
420
- const instant = (event as CustomEvent<{ instant: boolean }>).detail?.instant ?? true;
421
- incomingInstant = undefined;
422
- restore((surface) => {
423
- // Keep only the outgoing visual at its last rectangle after Web ownership
424
- // is released. The native acknowledgement transfers input exactly once.
425
- const box = surface.getBoundingClientRect();
426
- surface.style.setProperty('position', 'fixed', 'important');
427
- surface.style.setProperty('display', 'block', 'important');
428
- surface.style.setProperty('left', `${box.left}px`, 'important');
429
- surface.style.setProperty('top', `${box.top}px`, 'important');
430
- surface.style.setProperty('right', 'auto', 'important');
431
- surface.style.setProperty('bottom', 'auto', 'important');
432
- surface.style.setProperty('width', `${box.width}px`, 'important');
433
- surface.style.setProperty('height', `${box.height}px`, 'important');
434
- crossfade.retire(surface, instant);
435
- });
436
- },
437
- { signal: listeners.signal },
438
- );
439
- win.addEventListener(
440
- 'nativeUIShellVerticalBarsRelease',
441
- (event) => {
442
- incomingInstant = (event as CustomEvent<{ instant: boolean }>).detail.instant;
443
- void schedule();
444
- },
445
- { signal: listeners.signal },
446
- );
447
- win.addEventListener('resize', schedule, { signal: listeners.signal });
448
- win.addEventListener('nativeUIShellRefresh', schedule, { signal: listeners.signal });
449
- schedule();
450
-
451
- return {
452
- getStatus: (): NativeUIShellStatus => ({
453
- state: stopped ? 'stopped' : 'web',
454
- projected:
455
- Number(!!backSource) + Number(!!search.source) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),
456
- updates,
457
- }),
458
- async suspend() {
459
- suspended++;
460
- restore();
461
- let resumed = false;
462
- return {
463
- async resume() {
464
- if (resumed) return;
465
- resumed = true;
466
- suspended = Math.max(0, suspended - 1);
467
- if (!suspended) await schedule();
468
- },
469
- };
470
- },
471
- async destroy() {
472
- if (stopped) return;
473
- stopped = true;
474
- win.cancelAnimationFrame(frame);
475
- frame = 0;
476
- waiters.forEach((resolve) => resolve());
477
- waiters = [];
478
- observer.disconnect();
479
- sourceObserver?.disconnect();
480
- listeners.abort();
481
- crossfade.destroy();
482
- restore();
483
- },
484
- };
485
- };
@@ -1,15 +0,0 @@
1
- import type { AnimationBuilder } from '@ionic/core/components';
2
- import { connectNativeUIShellTransition } from './index';
3
-
4
- /**
5
- * Keeps an existing Ionic navigation animation and coordinates native control handoff.
6
- * Register the returned builder as navAnimation. Do not use it for overlay animations.
7
- * The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.
8
- */
9
- export const withNativeUIShellTransition =
10
- (builder: AnimationBuilder): AnimationBuilder =>
11
- (baseEl, opts) => {
12
- const animation = builder(baseEl, opts);
13
- if (opts?.enteringEl) connectNativeUIShellTransition(animation, opts.enteringEl, opts.leavingEl);
14
- return animation;
15
- };
@@ -1,11 +0,0 @@
1
- export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { withNativeUIShellTransition } from './native-integration/transition';
3
- export type {
4
- NativeUIShellStatus,
5
- NativeUIShellSuspension,
6
- VerticalBarEdge,
7
- VerticalControlAreaHandle,
8
- VerticalControlAreaOptions,
9
- } from './native';
10
-
11
- export { applyFoldStateClasses } from './native/foldable/fold-state';