@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
package/dist/utils.js CHANGED
@@ -1,71 +1,63 @@
1
- export const getElementRoot = (el, fallback = el) => {
2
- return el.shadowRoot || fallback;
1
+ //#region src/utils.ts
2
+ const getElementRoot = (el, fallback = el) => {
3
+ return el.shadowRoot || fallback;
3
4
  };
4
- export const raf = (h) => {
5
- if (typeof __zone_symbol__requestAnimationFrame === 'function') {
6
- return __zone_symbol__requestAnimationFrame(h);
7
- }
8
- if (typeof requestAnimationFrame === 'function') {
9
- return requestAnimationFrame(h);
10
- }
11
- return setTimeout(h);
5
+ const raf = (h) => {
6
+ if (typeof __zone_symbol__requestAnimationFrame === "function") return __zone_symbol__requestAnimationFrame(h);
7
+ if (typeof requestAnimationFrame === "function") return requestAnimationFrame(h);
8
+ return setTimeout(h);
12
9
  };
13
- export const cloneElement = (tagName, useCache = true) => {
14
- if (useCache) {
15
- const cachedElement = document.querySelector(`${tagName}.ion-cloned-element`);
16
- if (cachedElement !== null) {
17
- return cachedElement;
18
- }
19
- }
20
- const clonedEl = document.createElement(tagName);
21
- clonedEl.classList.add('ion-cloned-element');
22
- clonedEl.style.setProperty('display', 'none');
23
- document.body.appendChild(clonedEl);
24
- return clonedEl;
10
+ const cloneElement = (tagName, useCache = true) => {
11
+ if (useCache) {
12
+ const cachedElement = document.querySelector(`${tagName}.ion-cloned-element`);
13
+ if (cachedElement !== null) return cachedElement;
14
+ }
15
+ const clonedEl = document.createElement(tagName);
16
+ clonedEl.classList.add("ion-cloned-element");
17
+ clonedEl.style.setProperty("display", "none");
18
+ document.body.appendChild(clonedEl);
19
+ return clonedEl;
25
20
  };
26
- export const getStep = (targetX, animationPosition) => {
27
- if (animationPosition === undefined) {
28
- return 0;
29
- }
30
- const currentX = targetX - animationPosition.width / 2;
31
- let progress = (currentX - animationPosition.minPositionX) / (animationPosition.maxPositionX - animationPosition.minPositionX);
32
- progress = Math.max(0, Math.min(1, progress));
33
- return progress;
21
+ const getStep = (targetX, animationPosition) => {
22
+ if (animationPosition === void 0) return 0;
23
+ let progress = (targetX - animationPosition.width / 2 - animationPosition.minPositionX) / (animationPosition.maxPositionX - animationPosition.minPositionX);
24
+ progress = Math.max(0, Math.min(1, progress));
25
+ return progress;
34
26
  };
35
- export const changeSelectedElement = (targetElement, selectedElement, effectTagName, selectedClassName) => {
36
- targetElement.querySelectorAll(effectTagName).forEach((element) => {
37
- element.classList.remove(selectedClassName);
38
- element.classList.remove('ion-activated');
39
- });
40
- selectedElement.classList.add('ion-activated');
27
+ const changeSelectedElement = (targetElement, selectedElement, effectTagName, selectedClassName) => {
28
+ targetElement.querySelectorAll(effectTagName).forEach((element) => {
29
+ element.classList.remove(selectedClassName);
30
+ element.classList.remove("ion-activated");
31
+ });
32
+ selectedElement.classList.add("ion-activated");
41
33
  };
42
- export class Config {
43
- constructor() {
44
- this.m = new Map();
45
- }
46
- reset(configObj) {
47
- this.m = new Map(Object.entries(configObj));
48
- }
49
- get(key, fallback) {
50
- const value = this.m.get(key);
51
- return value !== undefined ? value : fallback;
52
- }
53
- getBoolean(key, fallback = false) {
54
- const val = this.m.get(key);
55
- if (val === undefined) {
56
- return fallback;
57
- }
58
- if (typeof val === 'string') {
59
- return val === 'true';
60
- }
61
- return !!val;
62
- }
63
- getNumber(key, fallback) {
64
- const val = parseFloat(String(this.m.get(key)));
65
- return isNaN(val) ? (fallback !== undefined ? fallback : NaN) : val;
66
- }
67
- set(key, value) {
68
- this.m.set(key, value);
69
- }
70
- }
71
- export const config = new Config();
34
+ var Config = class {
35
+ constructor() {
36
+ this.m = /* @__PURE__ */ new Map();
37
+ }
38
+ reset(configObj) {
39
+ this.m = new Map(Object.entries(configObj));
40
+ }
41
+ get(key, fallback) {
42
+ const value = this.m.get(key);
43
+ return value !== void 0 ? value : fallback;
44
+ }
45
+ getBoolean(key, fallback = false) {
46
+ const val = this.m.get(key);
47
+ if (val === void 0) return fallback;
48
+ if (typeof val === "string") return val === "true";
49
+ return !!val;
50
+ }
51
+ getNumber(key, fallback) {
52
+ const val = parseFloat(String(this.m.get(key)));
53
+ return isNaN(val) ? fallback !== void 0 ? fallback : NaN : val;
54
+ }
55
+ set(key, value) {
56
+ this.m.set(key, value);
57
+ }
58
+ };
59
+ const config = /*@__PURE__*/ new Config();
60
+ //#endregion
61
+ export { Config, changeSelectedElement, cloneElement, config, getElementRoot, getStep, raf };
62
+
63
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { AnimationPosition } from './sheets-of-glass/interfaces';\nimport { IonicConfig } from '@ionic/core';\n\ndeclare const __zone_symbol__requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;\ndeclare const requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;\n\nexport const getElementRoot = (el: HTMLElement, fallback: HTMLElement = el) => {\n return el.shadowRoot || fallback;\n};\n\nexport const raf = (h: FrameRequestCallback) => {\n if (typeof __zone_symbol__requestAnimationFrame === 'function') {\n return __zone_symbol__requestAnimationFrame(h);\n }\n if (typeof requestAnimationFrame === 'function') {\n return requestAnimationFrame(h);\n }\n return setTimeout(h);\n};\n\nexport const cloneElement = (tagName: string, useCache: boolean = true): HTMLElement => {\n if (useCache) {\n const cachedElement = document.querySelector(`${tagName}.ion-cloned-element`);\n if (cachedElement !== null) {\n return cachedElement as HTMLElement;\n }\n }\n\n const clonedEl = document.createElement(tagName) as HTMLElement;\n clonedEl.classList.add('ion-cloned-element');\n clonedEl.style.setProperty('display', 'none');\n document.body.appendChild(clonedEl);\n\n return clonedEl;\n};\n\nexport const getStep = (targetX: number, animationPosition: AnimationPosition) => {\n if (animationPosition === undefined) {\n return 0;\n }\n const currentX = targetX - animationPosition.width / 2;\n let progress = (currentX - animationPosition.minPositionX) / (animationPosition.maxPositionX - animationPosition.minPositionX);\n progress = Math.max(0, Math.min(1, progress)); // clamp 0〜1\n return progress;\n};\n\nexport const changeSelectedElement = (\n targetElement: HTMLElement,\n selectedElement: HTMLElement,\n effectTagName: string,\n selectedClassName: string,\n): void => {\n targetElement.querySelectorAll(effectTagName).forEach((element) => {\n element.classList.remove(selectedClassName);\n element.classList.remove('ion-activated');\n });\n selectedElement.classList.add('ion-activated');\n};\n\nexport class Config {\n private m = new Map<keyof IonicConfig, unknown>();\n\n reset(configObj: IonicConfig) {\n this.m = new Map<keyof IonicConfig, unknown>(Object.entries(configObj) as [keyof IonicConfig, unknown][]);\n }\n\n get<T>(key: keyof IonicConfig, fallback?: T): T {\n const value = this.m.get(key);\n return value !== undefined ? (value as T) : (fallback as T);\n }\n\n getBoolean(key: keyof IonicConfig, fallback = false): boolean {\n const val = this.m.get(key);\n if (val === undefined) {\n return fallback;\n }\n if (typeof val === 'string') {\n return val === 'true';\n }\n return !!val;\n }\n\n getNumber(key: keyof IonicConfig, fallback?: number): number {\n const val = parseFloat(String(this.m.get(key)));\n return isNaN(val) ? (fallback !== undefined ? fallback : NaN) : val;\n }\n\n set(key: keyof IonicConfig, value: unknown) {\n this.m.set(key, value);\n }\n}\n\nexport const config = /*@__PURE__*/ new Config();\n"],"mappings":";AAMA,MAAa,kBAAkB,IAAiB,WAAwB,OAAO;CAC7E,OAAO,GAAG,cAAc;AAC1B;AAEA,MAAa,OAAO,MAA4B;CAC9C,IAAI,OAAO,yCAAyC,YAClD,OAAO,qCAAqC,CAAC;CAE/C,IAAI,OAAO,0BAA0B,YACnC,OAAO,sBAAsB,CAAC;CAEhC,OAAO,WAAW,CAAC;AACrB;AAEA,MAAa,gBAAgB,SAAiB,WAAoB,SAAsB;CACtF,IAAI,UAAU;EACZ,MAAM,gBAAgB,SAAS,cAAc,GAAG,QAAQ,oBAAoB;EAC5E,IAAI,kBAAkB,MACpB,OAAO;CAEX;CAEA,MAAM,WAAW,SAAS,cAAc,OAAO;CAC/C,SAAS,UAAU,IAAI,oBAAoB;CAC3C,SAAS,MAAM,YAAY,WAAW,MAAM;CAC5C,SAAS,KAAK,YAAY,QAAQ;CAElC,OAAO;AACT;AAEA,MAAa,WAAW,SAAiB,sBAAyC;CAChF,IAAI,sBAAsB,KAAA,GACxB,OAAO;CAGT,IAAI,YADa,UAAU,kBAAkB,QAAQ,IAC1B,kBAAkB,iBAAiB,kBAAkB,eAAe,kBAAkB;CACjH,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;CAC5C,OAAO;AACT;AAEA,MAAa,yBACX,eACA,iBACA,eACA,sBACS;CACT,cAAc,iBAAiB,aAAa,CAAC,CAAC,SAAS,YAAY;EACjE,QAAQ,UAAU,OAAO,iBAAiB;EAC1C,QAAQ,UAAU,OAAO,eAAe;CAC1C,CAAC;CACD,gBAAgB,UAAU,IAAI,eAAe;AAC/C;AAEA,IAAa,SAAb,MAAoB;;EAClB,KAAQ,oBAAI,IAAI,IAAgC;;CAEhD,MAAM,WAAwB;EAC5B,KAAK,IAAI,IAAI,IAAgC,OAAO,QAAQ,SAAS,CAAmC;CAC1G;CAEA,IAAO,KAAwB,UAAiB;EAC9C,MAAM,QAAQ,KAAK,EAAE,IAAI,GAAG;EAC5B,OAAO,UAAU,KAAA,IAAa,QAAe;CAC/C;CAEA,WAAW,KAAwB,WAAW,OAAgB;EAC5D,MAAM,MAAM,KAAK,EAAE,IAAI,GAAG;EAC1B,IAAI,QAAQ,KAAA,GACV,OAAO;EAET,IAAI,OAAO,QAAQ,UACjB,OAAO,QAAQ;EAEjB,OAAO,CAAC,CAAC;CACX;CAEA,UAAU,KAAwB,UAA2B;EAC3D,MAAM,MAAM,WAAW,OAAO,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC;EAC9C,OAAO,MAAM,GAAG,IAAK,aAAa,KAAA,IAAY,WAAW,MAAO;CAClE;CAEA,IAAI,KAAwB,OAAgB;EAC1C,KAAK,EAAE,IAAI,KAAK,KAAK;CACvB;AACF;AAEA,MAAa,uBAAuB,IAAI,OAAO"}
@@ -1,4 +1,5 @@
1
- export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { withNativeUIShellTransition } from './native-integration/transition';
3
- export type { NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
4
- //# sourceMappingURL=vertical-bars.d.ts.map
1
+ import { withNativeUIShellTransition } from "./native-integration/transition.js";
2
+ import { NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle, VerticalControlAreaOptions } from "./native/definitions.js";
3
+ import { applyFoldStateClasses } from "./native/foldable/fold-state.js";
4
+ import { IonicNativeUIShell, enableVerticalControlArea, setVerticalControlAreaPlacement } from "./native/index.js";
5
+ export { IonicNativeUIShell, type NativeUIShellStatus, type NativeUIShellSuspension, type VerticalBarEdge, type VerticalControlAreaHandle, type VerticalControlAreaOptions, applyFoldStateClasses, enableVerticalControlArea, setVerticalControlAreaPlacement, withNativeUIShellTransition };
@@ -1,2 +1,4 @@
1
- export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { withNativeUIShellTransition } from './native-integration/transition';
1
+ import { withNativeUIShellTransition } from "./native-integration/transition.js";
2
+ import { IonicNativeUIShell, enableVerticalControlArea, setVerticalControlAreaPlacement } from "./native/index.js";
3
+ import { applyFoldStateClasses } from "./native/foldable/fold-state.js";
4
+ export { IonicNativeUIShell, applyFoldStateClasses, enableVerticalControlArea, setVerticalControlAreaPlacement, withNativeUIShellTransition };
@@ -112,13 +112,25 @@ Use your existing Ionic click handlers, routing, and form associations. All butt
112
112
 
113
113
  When the application owner is disposed, call `await rail.destroy()` to restore the original controls and release the runtime. If you already use `enableNativeUIShell()`, keep that runtime and follow the [shared placement guide](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#project-controls-into-the-rail).
114
114
 
115
+ ### Optional: choose native button appearance (unreleased)
116
+
117
+ The next release adds `buttonProjection` and local projection settings. These options are not available in the `1.2.0-1` installation above. See [Choose button appearance](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#choose-button-appearance) for availability and migration details.
118
+
119
+ The new default is `system`: SwiftUI styles vertical buttons and tints their icons. If your existing theme should supply their fill and colors, use:
120
+
121
+ ```ts
122
+ const rail = await enableVerticalControlArea({ buttonProjection: 'source', buttonDefaultFill: 'solid' });
123
+ ```
124
+
125
+ The `solid` default suits ordinary Ionic buttons. Buttons inside `ion-buttons` still default to clear; set `fill="solid"` explicitly to project their background. For one-off exceptions, use the [local projection settings](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#override-individual-buttons-or-groups). These settings affect native vertical buttons only; Web clones keep their existing appearance.
126
+
115
127
  ### If the preview does not appear
116
128
 
117
129
  | What you see | What to check |
118
130
  | --- | --- |
119
131
  | No space at the side | Load `vertical-bars.css` and put the class on `ion-app`. |
120
132
  | Space appears, but controls stay horizontal | Start `enableVerticalControlArea()` after mounting the app root. Use existing tabs or `slot="icon-only"` actions in a fixed header/footer toolbar. |
121
- | One action stays horizontal | Check for `slot="icon-only"` on the icon and a fixed toolbar outside scrolling content. Explicitly excluded controls and controls in centered modals stay horizontal; `fill` and `type="submit"` do not prevent movement. See [control requirements](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#toolbar-actions). |
133
+ | One action stays horizontal | Check for `slot="icon-only"` on the icon and a fixed toolbar outside scrolling content. Explicitly excluded controls and controls in centered modals stay horizontal; `fill` and `type="submit"` do not prevent movement. See [control requirements](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#toolbar-actions). |
122
134
 
123
135
  ## Connect an iPhone Duo
124
136
 
@@ -131,41 +143,18 @@ npx cap sync ios
131
143
 
132
144
  Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for actual rail placement and hinge posture on iOS 27.1. Native UI Shell uses Swift Package Manager; existing CocoaPods apps can follow [Native UI Shell setup](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell#enable-the-shell). Keep this package's `vertical-bars.css`; the device plugin's `ionic-tabs.css` is not needed with our rail projection.
133
145
 
134
- Replace the browser-only startup above with this after `ion-app` is mounted:
135
-
136
- ```ts
137
- import { Capacitor, type PluginListenerHandle } from '@capacitor/core';
138
- import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
139
- import { Foldable } from '@erkamyaman/capacitor-foldable';
140
-
141
- const rail = await enableVerticalControlArea();
142
- let layoutListener: PluginListenerHandle | undefined;
143
-
144
- if (Capacitor.getPlatform() === 'ios') {
145
- layoutListener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
146
- rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
147
- );
148
- const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
149
- rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
150
- }
151
-
152
- // Call when the application owner is disposed.
153
- const stopVerticalArea = async () => {
154
- await layoutListener?.remove();
155
- await rail.destroy();
156
- };
157
- ```
146
+ Replace the browser-only startup with the [device placement setup](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#project-controls-into-the-rail). That setup sends initial values and `barPlacementChange` events to `setVerticalControlAreaPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset })`, with subscriptions and cleanup kept in your application.
158
147
 
159
- `setPlacement()` resolves the logical edge through the document direction. Pass Foldable’s measured `inset` with the reported edge so the theme reserves the actual bar width and follows width changes. An inset of `0` clears the explicit width; manually requested rails then use the theme’s CSS safe-area rules. A `null` edge restores the ordinary layout. Devices without a reported rail return `null`, so this example restores the ordinary layout there. On iOS 27.1 or later, Foldable can infer Duo bar placement from safe-area insets when the app is built with an older SDK. To deliberately request a rail when the plugin reports no edge, have your application choose a fixed edge with `rail.setPlacement('trailing')` instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.
148
+ Pass both the requested and native edge with Foldable's measured inset. The placement API resolves RTL. A null edge restores the ordinary layout.
160
149
 
161
- On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.
150
+ On supported iOS, controls in the rail use native SwiftUI rendering; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.
162
151
 
163
152
  ## Use hinge posture without projecting controls
164
153
 
165
- If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Use `Foldable.getFoldState()` and `foldStateChange` directly, removing the listener when finished. There is no separate start/stop monitoring call.
154
+ If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Pass `Foldable.getFoldState()` results and `foldStateChange` events to `applyFoldStateClasses(root, fold)`, removing the listener when finished. There is no separate start/stop monitoring call.
166
155
 
167
156
  See [Read the device layout](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#adapt-the-split-pane) for the opt-in width rules and half-open state.
168
157
 
169
158
  ## Shared layout rules and API
170
159
 
171
- Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in [iPhone Duo support](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo). Those rules apply to this standalone setup too.
160
+ Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in [Vertical Bars](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars). Those rules apply to this standalone setup too.
@@ -6,7 +6,7 @@ title: iPhone Duo support (experimental)
6
6
 
7
7
  Adapt your Ionic app to iPhone Duo: place navigation and actions in its vertical system rail, and adjust your split pane as the device opens and closes. Existing Ionic markup remains the source of labels, icons, routing, and click handlers.
8
8
 
9
- **New here?** Start with [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme) to preview the side layout in Chrome. This page explains device events, placement, split panes, and the API.
9
+ **New here?** Start with [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme) to preview the side layout in Chrome. This page explains device events, placement and split panes. [Vertical Bars](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars) documents control projection and the runtime API.
10
10
 
11
11
  Available in `1.2.0-0` as an **experimental** feature alongside [Native UI Shell](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell). APIs and supported behavior may change. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer.
12
12
 
@@ -19,7 +19,7 @@ Device state belongs to [`@erkamyaman/capacitor-foldable`](https://github.com/er
19
19
 
20
20
  ## Choose what to adopt
21
21
 
22
- To keep your existing theme and add only the standalone support, follow [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme). This page covers the shared device-layout rules and API.
22
+ To keep your existing theme and add only the standalone support, follow [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme). This page covers the shared device-layout rules.
23
23
 
24
24
  | Goal | Stylesheet | Runtime |
25
25
  | ------------------------------------------------ | ------------------- | ------------------------------------------------------------------ |
@@ -47,128 +47,73 @@ npx cap sync
47
47
 
48
48
  Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for iPhone Duo's iOS 27.1 APIs. The dependency is needed for device-driven layout, not for the theme's CSS, browser simulation, or native control projection alone. Do not import the plugin's `ionic-tabs.css` alongside this package's rail projection; both would reposition the same tabs.
49
49
 
50
- For a posture-driven split pane, subscribe directly without starting a projection runtime:
50
+ After `ion-app` is mounted, pass device state to `applyFoldStateClasses`. The application owns subscriptions and cleanup. No projection runtime is needed for posture-driven layout.
51
51
 
52
52
  ```ts
53
- import { Foldable, type FoldState } from '@erkamyaman/capacitor-foldable';
54
-
55
- const applyFold = (fold: FoldState) => {
56
- const pane = document.querySelector('ion-split-pane');
57
- pane?.classList.toggle('ios-theme-split-pane-half-open', fold.state === 'half-opened');
58
- const expanded = fold.state === 'half-opened' || (fold.state === 'flat' && !!fold.hingeBounds);
59
- pane?.setAttribute('when', expanded ? '(min-width: 900px)' : '(min-width: 992px)');
60
- };
61
- let receivedEvent = false;
62
- let disposed = false;
63
- const listener = await Foldable.addListener('foldStateChange', (fold) => {
64
- receivedEvent = true;
65
- if (!disposed) applyFold(fold);
66
- });
67
- const initialFold = await Foldable.getFoldState();
68
- if (!disposed && !receivedEvent) applyFold(initialFold);
69
-
70
- // When the consumer goes away:
71
- // disposed = true;
72
- // await listener.remove();
73
- ```
74
-
75
- `getFoldState()` and `foldStateChange` report `state` (`'flat'`, `'half-opened'`, or `'closed'`), `posture`, and optional hinge geometry. Without fold information, the plugin returns a flat state without `hingeBounds`; restore the ordinary split-pane breakpoint in that case. The Web implementation also returns a flat state. A half-opened state uses the 900px breakpoint even without hinge geometry; a flat state with hinge geometry also uses 900px. A closed state restores the ordinary 992px breakpoint. Events received during initialization take precedence over the initial read.
53
+ import { Foldable } from '@erkamyaman/capacitor-foldable';
54
+ import { applyFoldStateClasses } from '@rdlabo/ionic-theme-ios27/vertical-bars';
76
55
 
77
- `getBarPlacement()` and `barPlacementChange` report `{ verticalBarEdge: 'leading' | 'trailing' | null, inset: number }`. The edge is **logical**: leading is the physical left in LTR and the physical right in RTL. Pass `{ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }` to `setPlacement()`. No start/stop monitoring calls are needed; remove each listener when its owner is disposed.
56
+ const root = document.querySelector('ion-app')!;
57
+ const listener = await Foldable.addListener('foldStateChange', (fold) => applyFoldStateClasses(root, fold));
58
+ applyFoldStateClasses(root, await Foldable.getFoldState());
59
+ ```
78
60
 
79
- The theme never reads UIKit bar-placement traits. The application supplies `nativeEdge` on the initial read and each event, even when choosing a fixed `edge`. Omit `nativeEdge` to keep the last supplied value; pass `null` when the plugin reports no edge. An explicit `nativeEdge: null` or an initial unregistered edge prevents native vertical projection: `enableVerticalControlArea()` keeps the requested Web rail, while the full Native UI Shell temporarily restores its ordinary horizontal layout. The requested rail is retained so a later reported edge can restore vertical layout. Native vertical projection starts only after a matching non-null edge is supplied. Omitting `nativeEdge` after supplying it preserves that value, including `null`. Passing `{ edge: null, nativeEdge }` updates the reported edge while keeping the rail disabled.
61
+ When the application owner is disposed:
80
62
 
81
- Foldable reports the measured width reserved by the native bar as `inset` and notifies changes through `barPlacementChange`. Pass this value to `setPlacement()` when following the reported edge, rather than assuming a fixed width. When no bar is reported, `inset` is `0`; the theme clears the explicit width and uses its CSS safe-area rules if the application still requests a Web rail. Applications choosing a different edge can supply their own width or use the CSS fallback. WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`.
63
+ ```ts
64
+ await listener.remove();
65
+ ```
82
66
 
83
- **Migration:** the theme's former `DeviceLayout`, `HingeStatus`, `getDeviceLayout()`, `deviceLayoutChange`, and start/stop device-layout monitoring APIs have been removed. Replace device subscriptions with the `Foldable` APIs above; use `getWebViewMetrics()` for one-shot radius reads. Foldable can infer Duo bar placement from safe-area insets when the app is built without the iOS 27.1 SDK. Hinge data still requires the newer SDK. Apps can also request a fixed rail placement independently of the reported edge.
67
+ Subscribe to changes, then read the current state. The same helper handles initial values and events.
84
68
 
85
- ## Reserve the vertical rail
69
+ `applyFoldStateClasses` keeps one of `ios-theme-fold-flat`, `ios-theme-fold-half-opened`, and `ios-theme-fold-closed` on the supplied root, preserving unrelated classes. It also sets `ios-theme-fold-expanded` for a half-opened state or a flat state with hinge geometry. A flat state without geometry (including the Web fallback) and a closed state clear that class. The helper does not subscribe to the plugin or change Ionic's split-pane `when` property.
86
70
 
87
- Add `.ios-theme-vertical-bars` to `ion-app` to reserve the rail region on the physical right, or add `.ios-theme-vertical-bars-left` as well to use the physical left:
71
+ For rail placement, use `setVerticalControlAreaPlacement` as shown below. Pass the reported logical edge as both `edge` and `nativeEdge`, together with the measured `inset`. Leading is the physical left in LTR and the physical right in RTL. A null edge restores the ordinary layout; an inset of zero clears the explicit width. No start/stop monitoring calls are needed.
88
72
 
89
- ```html
90
- <ion-app class="ios-theme-vertical-bars">...</ion-app>
91
- ```
73
+ WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`.
92
74
 
93
- The classes are physical — `-left` always means the physical left edge — because CSS and the native renderer work in physical coordinates. `setPlacement` (below) is the usual way to apply them: it accepts the logical `verticalBarEdge` reported by `Foldable` and resolves it through the document's direction, so an RTL app does not need its own conversion.
94
-
95
- For Chrome development, no native plugin is needed — the class alone reserves `80px` to simulate iPhone Duo. When `setPlacement` receives an explicit `{ edge, inset }`, that inset replaces the fallback width, even when it is less than `80px`. Override `--ios-theme-vertical-bars-safe-area-left` or `--ios-theme-vertical-bars-safe-area-right` when simulating a different layout.
96
-
97
- This keeps routers and component backgrounds full-viewport. `ion-content` moves its scroll foreground, `ion-toolbar` moves its container foreground, and `ion-fab` adjusts only when placed beside the system UI. The corresponding Ionic safe-area variable is reset inside those foreground components so descendants do not add the inset again.
75
+ **Migration:** the theme's former `DeviceLayout`, `HingeStatus`, `getDeviceLayout()`, `deviceLayoutChange`, and start/stop device-layout monitoring APIs have been removed. Replace device subscriptions with the `Foldable` APIs above; use `getWebViewMetrics()` for one-shot radius reads. Foldable can infer Duo bar placement from safe-area insets when the app is built without the iOS 27.1 SDK. Hinge data still requires the newer SDK. Apps can also request a fixed rail placement independently of the reported edge.
98
76
 
99
- `ion-modal` applies the same foreground correction when its visible dialog spans the viewport width. With the Vertical Control Area runtime enabled, the topmost full-width modal also projects eligible toolbar buttons into its own rail; centered dialogs keep their toolbar buttons and receive no page-rail inset. This includes full-width sheet modals: their rail follows the visible sheet bounds as the breakpoint changes. Eligibility follows the visible dialog width, not the hinge posture or modal type. `ion-menu` and `ion-popover` are handled as separate surfaces: their internal foreground components do not receive the main-page conversion and retain Ionic's standard safe-area handling. A menu presented beside the system UI keeps Ionic's full-viewport animation host and offsets only its visible container by the corresponding inset; a menu from the opposite side is unchanged. Left and right remain physical coordinates in RTL, while Ionic's `side="start"` and `side="end"` values remain logical.
77
+ ## Reserve the vertical rail
100
78
 
101
- The mode is component-mode independent: an app can keep Ionic `mode: 'md'` on iOS and still enable Vertical Bars. No component needs `mode="ios"`.
79
+ Load `vertical-bars.css` as shown above. For class-based browser simulation, safe-area handling, RTL and overlay layout, see [Reserve the vertical rail](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#reserve-the-vertical-rail). On device, the placement helper below applies the layout classes and measured inset.
102
80
 
103
81
  ## Project controls into the rail
104
82
 
105
- Start the standalone runtime once at application startup:
83
+ Start the standalone runtime once after `ion-app` is mounted, and apply the plugin's placement with `setVerticalControlAreaPlacement`:
106
84
 
107
85
  ```ts
108
- import { Capacitor, type PluginListenerHandle } from '@capacitor/core';
109
- import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
110
86
  import { Foldable } from '@erkamyaman/capacitor-foldable';
87
+ import { setVerticalControlAreaPlacement, enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
111
88
 
112
- // Start on Chrome too; the Web projection stays idle until the class is present.
113
89
  const rail = await enableVerticalControlArea();
114
- let layoutListener: PluginListenerHandle | undefined;
115
-
116
- if (Capacitor.getPlatform() === 'ios') {
117
- layoutListener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
118
- rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
119
- );
120
- const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
121
- rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
122
- }
123
-
124
- // Call when the application owner is disposed.
125
- const stopVerticalArea = async () => {
126
- await layoutListener?.remove();
127
- await rail.destroy();
128
- };
90
+ const listener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
91
+ setVerticalControlAreaPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
92
+ );
93
+ const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
94
+ setVerticalControlAreaPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
129
95
  ```
130
96
 
131
- `setPlacement` on the handle and the exported `setVerticalControlAreaPlacement` are the same function; either applies the application's chosen placement to the CSS layout and both projections. It requires a mounted `ion-app` — call it after the app root exists.
132
-
133
- - Pass `{ edge, nativeEdge }`: `edge` is the application's chosen logical edge; `nativeEdge` is `verticalBarEdge` from `Foldable.getBarPlacement()`/`barPlacementChange`. They resolve through the nearest `dir` attribute, or the explicit `rtl` argument.
134
- - Pass `null` to restore the ordinary layout.
135
- - The device-layout listener reports what iOS chose; the application decides whether to apply it. The theme compares the application's chosen edge with its supplied `nativeEdge`; a mismatch uses the Web rail until the edges match again. For a fixed right-in-LTR rail, pass `{ edge: 'trailing', nativeEdge: verticalBarEdge }` on each `Foldable` update.
136
-
137
- Start either `enableVerticalControlArea()` or the full `enableNativeUIShell()` — not both. Repeating the same configuration returns the shared runtime; starting a different configuration while it is active throws an error. The application should have one owner responsible for destroying that runtime. If the app already uses `enableNativeUIShell()`, keep that single runtime and call `setVerticalControlAreaPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset })` from its listener.
138
-
139
- On supported iOS versions the runtime hands eligible tabs, back navigation, menu buttons, and fixed-toolbar actions to a native SwiftUI `TabView` and toolbar; on Web, Android, or when native projection is unavailable, Web clones remain the fallback. Back navigation can come from outside a fixed toolbar; menu buttons and other toolbar actions still require one.
97
+ When the application owner is disposed:
140
98
 
141
- ### Toolbar actions
142
-
143
- An `ion-button` moves into the rail when it contains an `ion-icon` or SVG with `slot="icon-only"`. The button must be in a fixed `ion-toolbar` directly inside `ion-header` or `ion-footer`, outside scrolling `ion-content`.
99
+ ```ts
100
+ await listener.remove();
101
+ await rail.destroy();
102
+ ```
144
103
 
145
- | Icon markup | Placement |
146
- | --- | --- |
147
- | `slot="icon-only"` | Vertical rail |
148
- | `slot="start"`, `slot="end"`, or no slot | Original horizontal toolbar |
149
- | No icon | Original horizontal toolbar |
104
+ Supply `nativeEdge` on both the initial read and each event so the renderer knows which rail the system actually provides. The measured `inset` is passed through instead of assuming a fixed width. Devices without a reported rail, including Web and Android, return a null edge and retain the ordinary layout. For browser simulation, use the [class-based preview](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#reserve-the-vertical-rail) without connecting device placement.
150
105
 
151
- This rule applies to `fill="default"`, `clear`, `solid`, and `outline`, including buttons with an Ionic `color`. Solid actions retain their background color through a prominent native button; outline actions retain their border color and width. The runtime preserves the original click or form-submit behavior. `type="submit"` and `.button-submit` do not select a different placement.
106
+ Start either `enableVerticalControlArea()` or the full `enableNativeUIShell()` — not both. If the app already uses Native UI Shell, keep that runtime and use the same `setVerticalControlAreaPlacement` callback. The application owner removes its listeners and destroys its runtime on teardown.
152
107
 
153
- ```html
154
- <ion-header>
155
- <ion-toolbar>
156
- <ion-buttons slot="end">
157
- <ion-button type="button" fill="outline" color="primary" aria-label="Done">
158
- <ion-icon name="checkmark-outline" slot="icon-only"></ion-icon>
159
- </ion-button>
160
- </ion-buttons>
161
- </ion-toolbar>
162
- </ion-header>
163
- ```
108
+ On supported iOS versions the runtime hands eligible tabs, back navigation, menu buttons, and fixed-toolbar actions to a native SwiftUI `TabView` and toolbar; on Web, Android, or when native projection is unavailable, Web clones remain the fallback. See [Toolbar actions](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#toolbar-actions) for control eligibility.
164
109
 
165
- Give icon-only actions an accessible name, such as `aria-label="Done"`. Keep the original Ionic event handler or form association on the source button.
110
+ ### Toolbar actions
166
111
 
167
- The same rule applies to individual buttons and buttons inside `ion-buttons`, on ordinary pages and in the topmost full-width modal. Centered modals, menus, and popovers keep their own toolbar layout. Add `.ios-theme-horizontal-only` to a group or individual button to keep it horizontal. Placement is chosen when a routed page enters; changing an existing button's content or icon slot does not move it between the toolbar and rail until the page leaves and re-enters.
112
+ See [Vertical Bars: Toolbar actions](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#toolbar-actions) for eligible markup, placement, button appearance and local overrides.
168
113
 
169
114
  ### Tab bar
170
115
 
171
- When the app contains `ion-tabs`, its tab bar moves into the reserved region and uses the native Duo edge spacing; the Ionic `slot` value does not select a different position. Without native projection, the stable Web rail is icon-only, matching the native resting presentation. While the user presses and drags across that rail, every icon-and-label tab reveals its label so the pending destination stays identifiable. The Web tab bar receives pointer input in the simulated system region. Native tabs and a restored Web tab bar fade in over 180ms; disappearance remains immediate. Reduced motion disables this fade. Use `ion-menu` when navigation should become a sidebar; this mode does not convert tabs into a menu. Web clones also work when no `ion-tabs` exists. Disabling the mode or leaving the page removes the native ownership or Web clones and restores their sources. Override `--ios-theme-vertical-bars-toolbar-top` when the simulated system controls use a different vertical layout.
116
+ See [Vertical Bars: Tab bar](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#tab-bar) for navigation, labels and Web fallback behavior.
172
117
 
173
118
  ## Adapt the split pane
174
119
 
@@ -176,7 +121,7 @@ For a side-by-side menu on iPhone Duo, opt the `ion-split-pane` into the separat
176
121
 
177
122
  ```html
178
123
  <ion-split-pane
179
- [class.ios-theme-split-pane-half-open]="halfOpened"
124
+ class="split-pane-fold-layout"
180
125
  contentId="main-content"
181
126
  when="(min-width: 900px)"
182
127
  >
@@ -188,7 +133,7 @@ For a side-by-side menu on iPhone Duo, opt the `ion-split-pane` into the separat
188
133
  Set the ordinary split-pane width to 320pt in the application's stylesheet, and let the half-open class change only the width value:
189
134
 
190
135
  ```css
191
- ion-split-pane {
136
+ ion-split-pane.split-pane-fold-layout {
192
137
  --ios-theme-menu-width: var(--ios-theme-split-pane-width);
193
138
  --side-width: var(--ios-theme-menu-width);
194
139
  --side-max-width: var(--ios-theme-menu-width);
@@ -196,115 +141,10 @@ ion-split-pane {
196
141
  }
197
142
  ```
198
143
 
199
- The registered `--ios-theme-split-pane-width` defaults to `320px`; `.ios-theme-split-pane-half-open` sets it to `50vw`. Set `halfOpened` when `foldStateChange` reports `state === 'half-opened'` (and read the initial value with `getFoldState()`). Ionic's `when` decides whether the menu is a persistent side pane; use the 900px breakpoint for a half-opened state or a flat state with hinge geometry, and the ordinary 992px breakpoint when closed or flat without geometry. Missing `hingeBounds` alone does not mean the device is flat. The application chooses where to apply this width rule; an ordinary split pane elsewhere is unchanged. This layout does not enable Vertical Bars or move an overlay menu.
200
-
201
- ## Vertical Control Area API
202
-
203
- The generated reference below documents the handle returned by `enableVerticalControlArea()`.
204
-
205
- <docgen-index>
206
-
207
- * [`setPlacement(...)`](#setplacement)
208
- * [`getStatus()`](#getstatus)
209
- * [`suspend()`](#suspend)
210
- * [`destroy()`](#destroy)
211
- * [Interfaces](#interfaces)
212
- * [Type Aliases](#type-aliases)
213
-
214
- </docgen-index>
215
-
216
- <docgen-api>
217
- <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
218
-
219
- ### setPlacement(...)
220
-
221
- ```typescript
222
- setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean | undefined) => void
223
- ```
224
-
225
- Applies the application's chosen placement to both Web and native controls.
226
-
227
- | Param | Type |
228
- | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
229
- | **`placement`** | <code><a href="#verticalbaredge">VerticalBarEdge</a> \| <a href="#verticalbarplacement">VerticalBarPlacement</a></code> |
230
- | **`rtl`** | <code>boolean</code> |
144
+ The registered `--ios-theme-split-pane-width` defaults to `320px`. `applyFoldStateClasses` sets `ios-theme-fold-half-opened` on `ion-app`; the stylesheet then sets only descendant split panes with `split-pane-fold-layout` to `50vw`. Add this opt-in class once; no state-dependent class binding is needed. Other split panes retain their existing width.
231
145
 
232
- --------------------
146
+ Ionic's `when` still controls whether the menu is persistent. The example chooses a fixed 900px breakpoint. If your app needs different breakpoints for folded and ordinary displays, use the `ios-theme-fold-expanded` class applied by the helper to select that policy in your application's layout code. The helper only updates state classes, not `when`. This layout does not enable Vertical Bars or move an overlay menu.
233
147
 
148
+ ## Vertical Control Area API
234
149
 
235
- ### getStatus()
236
-
237
- ```typescript
238
- getStatus() => NativeUIShellStatus
239
- ```
240
-
241
- Returns the current Web/native projection state.
242
-
243
- **Returns:** <code><a href="#nativeuishellstatus">NativeUIShellStatus</a></code>
244
-
245
- --------------------
246
-
247
-
248
- ### suspend()
249
-
250
- ```typescript
251
- suspend() => Promise<NativeUIShellSuspension>
252
- ```
253
-
254
- Restores projected controls to the Web until the returned lease is resumed.
255
-
256
- **Returns:** <code>Promise&lt;<a href="#nativeuishellsuspension">NativeUIShellSuspension</a>&gt;</code>
257
-
258
- --------------------
259
-
260
-
261
- ### destroy()
262
-
263
- ```typescript
264
- destroy() => Promise<void>
265
- ```
266
-
267
- Stops synchronization, restores Web controls and releases native resources.
268
-
269
- --------------------
270
-
271
-
272
- ### Interfaces
273
-
274
-
275
- #### VerticalBarPlacement
276
-
277
- | Prop | Type | Description |
278
- | ---------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
279
- | **`edge`** | <code><a href="#verticalbaredge">VerticalBarEdge</a></code> | |
280
- | **`inset`** | <code>number</code> | Explicit rail width in CSS pixels; omitted to use the stylesheet's safe-area rules. |
281
- | **`nativeEdge`** | <code><a href="#verticalbaredge">VerticalBarEdge</a></code> | Native logical edge reported by the application's device plugin. Null or an unregistered edge uses a Web rail in verticalBarsOnly mode, or the ordinary Native UI Shell layout otherwise. Omission keeps the last supplied value. |
282
-
283
-
284
- #### NativeUIShellStatus
285
-
286
- | Prop | Type |
287
- | --------------- | ------------------------------------------- |
288
- | **`state`** | <code>'native' \| 'stopped' \| 'web'</code> |
289
- | **`projected`** | <code>number</code> |
290
- | **`updates`** | <code>number</code> |
291
- | **`reason`** | <code>string</code> |
292
-
293
-
294
- #### NativeUIShellSuspension
295
-
296
- | Method | Signature | Description |
297
- | ---------- | ---------------------------- | ---------------------------------------------------------------------------------------------- |
298
- | **resume** | () =&gt; Promise&lt;void&gt; | Releases this suspension. Native projection resumes after all active suspensions are released. |
299
-
300
-
301
- ### Type Aliases
302
-
303
-
304
- #### VerticalBarEdge
305
-
306
- Logical edge in the reading direction, matching UIVerticalBarEdge and capacitor-foldable.
307
-
308
- <code>'leading' | 'trailing' | null</code>
309
-
310
- </docgen-api>
150
+ The runtime handle reference is maintained in [Vertical Bars](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#vertical-control-area-api).