@rdlabo/capacitor-native-ui 0.0.0-stage → 1.2.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 (255) hide show
  1. package/LICENSE +21 -0
  2. package/Package.swift +23 -0
  3. package/README.md +53 -2
  4. package/RdlaboCapacitorNativeUi.podspec +17 -0
  5. package/android/build.gradle +69 -0
  6. package/android/proguard-rules.pro +0 -0
  7. package/android/src/main/AndroidManifest.xml +1 -0
  8. package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUI.kt +51 -0
  9. package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUIPlugin.kt +192 -0
  10. package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUISnapshot.kt +57 -0
  11. package/dist/_virtual/_rolldown/runtime.js +13 -0
  12. package/dist/core.d.ts +4 -0
  13. package/dist/core.js +3 -0
  14. package/dist/native/activation.d.ts +14 -0
  15. package/dist/native/activation.d.ts.map +1 -0
  16. package/dist/native/activation.js +305 -0
  17. package/dist/native/activation.js.map +1 -0
  18. package/dist/native/components/index.d.ts +19 -0
  19. package/dist/native/components/index.d.ts.map +1 -0
  20. package/dist/native/components/index.js +38 -0
  21. package/dist/native/components/index.js.map +1 -0
  22. package/dist/native/components/ion-back-button.d.ts +10 -0
  23. package/dist/native/components/ion-back-button.d.ts.map +1 -0
  24. package/dist/native/components/ion-back-button.js +21 -0
  25. package/dist/native/components/ion-back-button.js.map +1 -0
  26. package/dist/native/components/ion-button.d.ts +10 -0
  27. package/dist/native/components/ion-button.d.ts.map +1 -0
  28. package/dist/native/components/ion-button.js +25 -0
  29. package/dist/native/components/ion-button.js.map +1 -0
  30. package/dist/native/components/ion-buttons.d.ts +11 -0
  31. package/dist/native/components/ion-buttons.d.ts.map +1 -0
  32. package/dist/native/components/ion-buttons.js +30 -0
  33. package/dist/native/components/ion-buttons.js.map +1 -0
  34. package/dist/native/components/ion-fab.d.ts +10 -0
  35. package/dist/native/components/ion-fab.d.ts.map +1 -0
  36. package/dist/native/components/ion-fab.js +132 -0
  37. package/dist/native/components/ion-fab.js.map +1 -0
  38. package/dist/native/components/ion-menu-button.d.ts +13 -0
  39. package/dist/native/components/ion-menu-button.d.ts.map +1 -0
  40. package/dist/native/components/ion-menu-button.js +44 -0
  41. package/dist/native/components/ion-menu-button.js.map +1 -0
  42. package/dist/native/components/ion-segment.d.ts +11 -0
  43. package/dist/native/components/ion-segment.d.ts.map +1 -0
  44. package/dist/native/components/ion-segment.js +30 -0
  45. package/dist/native/components/ion-segment.js.map +1 -0
  46. package/dist/native/components/ion-tab-bar.d.ts +11 -0
  47. package/dist/native/components/ion-tab-bar.d.ts.map +1 -0
  48. package/dist/native/components/ion-tab-bar.js +50 -0
  49. package/dist/native/components/ion-tab-bar.js.map +1 -0
  50. package/dist/native/components/searchable-tabs.d.ts +20 -0
  51. package/dist/native/components/searchable-tabs.d.ts.map +1 -0
  52. package/dist/native/components/searchable-tabs.js +413 -0
  53. package/dist/native/components/searchable-tabs.js.map +1 -0
  54. package/dist/native/definitions.d.ts +255 -0
  55. package/dist/native/definitions.d.ts.map +1 -0
  56. package/dist/native/definitions.js +0 -0
  57. package/dist/native/extensions.d.ts +31 -0
  58. package/dist/native/extensions.d.ts.map +1 -0
  59. package/dist/native/extensions.js +122 -0
  60. package/dist/native/extensions.js.map +1 -0
  61. package/dist/native/foldable/fold-state.d.ts +5 -0
  62. package/dist/native/foldable/fold-state.d.ts.map +1 -0
  63. package/dist/native/foldable/fold-state.js +15 -0
  64. package/dist/native/foldable/fold-state.js.map +1 -0
  65. package/dist/native/foldable/types.d.ts +12 -0
  66. package/dist/native/foldable/types.d.ts.map +1 -0
  67. package/dist/native/foldable/types.js +0 -0
  68. package/dist/native/html.d.ts +9 -0
  69. package/dist/native/html.d.ts.map +1 -0
  70. package/dist/native/html.js +188 -0
  71. package/dist/native/html.js.map +1 -0
  72. package/dist/native/index.d.ts +10 -0
  73. package/dist/native/index.d.ts.map +1 -0
  74. package/dist/native/index.js +28 -0
  75. package/dist/native/index.js.map +1 -0
  76. package/dist/native/legacy-layout.d.ts +5 -0
  77. package/dist/native/legacy-layout.d.ts.map +1 -0
  78. package/dist/native/legacy-layout.js +27 -0
  79. package/dist/native/legacy-layout.js.map +1 -0
  80. package/dist/native/overlays/content.d.ts +4 -0
  81. package/dist/native/overlays/content.d.ts.map +1 -0
  82. package/dist/native/overlays/content.js +51 -0
  83. package/dist/native/overlays/content.js.map +1 -0
  84. package/dist/native/overlays/controller.d.ts +8 -0
  85. package/dist/native/overlays/controller.d.ts.map +1 -0
  86. package/dist/native/overlays/controller.js +285 -0
  87. package/dist/native/overlays/controller.js.map +1 -0
  88. package/dist/native/overlays/dialog-animation.d.ts +9 -0
  89. package/dist/native/overlays/dialog-animation.d.ts.map +1 -0
  90. package/dist/native/overlays/dialog-animation.js +53 -0
  91. package/dist/native/overlays/dialog-animation.js.map +1 -0
  92. package/dist/native/overlays/dismiss-animation.d.ts +4 -0
  93. package/dist/native/overlays/dismiss-animation.d.ts.map +1 -0
  94. package/dist/native/overlays/dismiss-animation.js +18 -0
  95. package/dist/native/overlays/dismiss-animation.js.map +1 -0
  96. package/dist/native/overlays/focus.d.ts +5 -0
  97. package/dist/native/overlays/focus.d.ts.map +1 -0
  98. package/dist/native/overlays/focus.js +59 -0
  99. package/dist/native/overlays/focus.js.map +1 -0
  100. package/dist/native/overlays/popover.d.ts +12 -0
  101. package/dist/native/overlays/popover.d.ts.map +1 -0
  102. package/dist/native/overlays/popover.js +110 -0
  103. package/dist/native/overlays/popover.js.map +1 -0
  104. package/dist/native/overlays/styles.d.ts +11 -0
  105. package/dist/native/overlays/styles.d.ts.map +1 -0
  106. package/dist/native/overlays/styles.js +167 -0
  107. package/dist/native/overlays/styles.js.map +1 -0
  108. package/dist/native/prehide.d.ts +8 -0
  109. package/dist/native/prehide.d.ts.map +1 -0
  110. package/dist/native/prehide.js +238 -0
  111. package/dist/native/prehide.js.map +1 -0
  112. package/dist/native/runtime.d.ts +7 -0
  113. package/dist/native/runtime.d.ts.map +1 -0
  114. package/dist/native/runtime.js +788 -0
  115. package/dist/native/runtime.js.map +1 -0
  116. package/dist/native/shared/bridge.d.ts +6 -0
  117. package/dist/native/shared/bridge.d.ts.map +1 -0
  118. package/dist/native/shared/bridge.js +18 -0
  119. package/dist/native/shared/bridge.js.map +1 -0
  120. package/dist/native/shared/candidate.d.ts +25 -0
  121. package/dist/native/shared/candidate.d.ts.map +1 -0
  122. package/dist/native/shared/candidate.js +106 -0
  123. package/dist/native/shared/candidate.js.map +1 -0
  124. package/dist/native/shared/clipping.d.ts +5 -0
  125. package/dist/native/shared/clipping.d.ts.map +1 -0
  126. package/dist/native/shared/clipping.js +26 -0
  127. package/dist/native/shared/clipping.js.map +1 -0
  128. package/dist/native/shared/crossfade.d.ts +12 -0
  129. package/dist/native/shared/crossfade.d.ts.map +1 -0
  130. package/dist/native/shared/crossfade.js +67 -0
  131. package/dist/native/shared/crossfade.js.map +1 -0
  132. package/dist/native/shared/diagnostics.d.ts +4 -0
  133. package/dist/native/shared/diagnostics.d.ts.map +1 -0
  134. package/dist/native/shared/diagnostics.js +18 -0
  135. package/dist/native/shared/diagnostics.js.map +1 -0
  136. package/dist/native/shared/dom.d.ts +46 -0
  137. package/dist/native/shared/dom.d.ts.map +1 -0
  138. package/dist/native/shared/dom.js +180 -0
  139. package/dist/native/shared/dom.js.map +1 -0
  140. package/dist/native/shared/events.d.ts +15 -0
  141. package/dist/native/shared/events.d.ts.map +1 -0
  142. package/dist/native/shared/events.js +17 -0
  143. package/dist/native/shared/events.js.map +1 -0
  144. package/dist/native/shared/icons.d.ts +8 -0
  145. package/dist/native/shared/icons.d.ts.map +1 -0
  146. package/dist/native/shared/icons.js +61 -0
  147. package/dist/native/shared/icons.js.map +1 -0
  148. package/dist/native/shared/modal.d.ts +10 -0
  149. package/dist/native/shared/modal.d.ts.map +1 -0
  150. package/dist/native/shared/modal.js +160 -0
  151. package/dist/native/shared/modal.js.map +1 -0
  152. package/dist/native/shared/projection-id.d.ts +4 -0
  153. package/dist/native/shared/projection-id.d.ts.map +1 -0
  154. package/dist/native/shared/projection-id.js +7 -0
  155. package/dist/native/shared/projection-id.js.map +1 -0
  156. package/dist/native/shared/toolbar-options.d.ts +9 -0
  157. package/dist/native/shared/toolbar-options.d.ts.map +1 -0
  158. package/dist/native/shared/toolbar-options.js +36 -0
  159. package/dist/native/shared/toolbar-options.js.map +1 -0
  160. package/dist/native/shared/vertical-bars-geometry.d.ts +10 -0
  161. package/dist/native/shared/vertical-bars-geometry.d.ts.map +1 -0
  162. package/dist/native/shared/vertical-bars-geometry.js +85 -0
  163. package/dist/native/shared/vertical-bars-geometry.js.map +1 -0
  164. package/dist/native/startup.d.ts +8 -0
  165. package/dist/native/startup.d.ts.map +1 -0
  166. package/dist/native/startup.js +86 -0
  167. package/dist/native/startup.js.map +1 -0
  168. package/dist/native/vertical-bars-web-search.d.ts +10 -0
  169. package/dist/native/vertical-bars-web-search.d.ts.map +1 -0
  170. package/dist/native/vertical-bars-web-search.js +90 -0
  171. package/dist/native/vertical-bars-web-search.js.map +1 -0
  172. package/dist/native/vertical-bars-web.d.ts +5 -0
  173. package/dist/native/vertical-bars-web.d.ts.map +1 -0
  174. package/dist/native/vertical-bars-web.js +421 -0
  175. package/dist/native/vertical-bars-web.js.map +1 -0
  176. package/dist/native-integration/index.d.ts +25 -0
  177. package/dist/native-integration/index.d.ts.map +1 -0
  178. package/dist/native-integration/index.js +33 -0
  179. package/dist/native-integration/index.js.map +1 -0
  180. package/dist/vertical-bars.d.ts +6 -0
  181. package/dist/vertical-bars.js +5 -0
  182. package/docs/configuration.md +187 -0
  183. package/docs/framework-independent.md +82 -0
  184. package/docs/migration.md +206 -0
  185. package/docs/native-mini-player.md +47 -0
  186. package/docs/native-ui-extensions.md +365 -0
  187. package/docs/native-ui.md +409 -0
  188. package/docs/setup.md +205 -0
  189. package/docs/vertical-bars.md +379 -0
  190. package/ios/Sources/NativeUIPlugin/Components/NativeUIButton.swift +71 -0
  191. package/ios/Sources/NativeUIPlugin/Components/NativeUIButtons.swift +24 -0
  192. package/ios/Sources/NativeUIPlugin/Components/NativeUIComponents.swift +19 -0
  193. package/ios/Sources/NativeUIPlugin/Components/NativeUIFab.swift +80 -0
  194. package/ios/Sources/NativeUIPlugin/Components/NativeUISearchController.swift +439 -0
  195. package/ios/Sources/NativeUIPlugin/Components/NativeUISegment.swift +91 -0
  196. package/ios/Sources/NativeUIPlugin/Components/NativeUITabBar.swift +218 -0
  197. package/ios/Sources/NativeUIPlugin/Components/NativeUIVerticalBars.swift +590 -0
  198. package/ios/Sources/NativeUIPlugin/Components/NativeUIVerticalSearch.swift +118 -0
  199. package/ios/Sources/NativeUIPlugin/Extensions/NativeUI.swift +178 -0
  200. package/ios/Sources/NativeUIPlugin/Extensions/NativeUIRendering.swift +38 -0
  201. package/ios/Sources/NativeUIPlugin/NativeUIPlugin.swift +615 -0
  202. package/ios/Sources/NativeUIPlugin/Overlays/NativeUIOverlayController.swift +119 -0
  203. package/ios/Sources/NativeUIPlugin/Overlays/NativeUIOverlayHost.swift +75 -0
  204. package/ios/Sources/NativeUIPlugin/Shared/NativeUIControlRendering.swift +21 -0
  205. package/ios/Sources/NativeUIPlugin/Shared/NativeUICrossfade.swift +35 -0
  206. package/ios/Sources/NativeUIPlugin/Shared/NativeUIHost.swift +8 -0
  207. package/ios/Sources/NativeUIPlugin/Shared/NativeUISnapshot.swift +161 -0
  208. package/ios/Sources/NativeUIPlugin/Shared/NativeUIVerticalBarsGeometry.swift +52 -0
  209. package/ios/Tests/NativeUIPluginTests/NativeUIExtensionTests.swift +165 -0
  210. package/ios/Tests/NativeUIPluginTests/NativeUISnapshotTests.swift +805 -0
  211. package/package.json +89 -4
  212. package/src/core.ts +15 -0
  213. package/src/native/activation.ts +394 -0
  214. package/src/native/components/index.ts +45 -0
  215. package/src/native/components/ion-back-button.ts +22 -0
  216. package/src/native/components/ion-button.ts +25 -0
  217. package/src/native/components/ion-buttons.ts +37 -0
  218. package/src/native/components/ion-fab.ts +151 -0
  219. package/src/native/components/ion-menu-button.ts +37 -0
  220. package/src/native/components/ion-segment.ts +27 -0
  221. package/src/native/components/ion-tab-bar.ts +50 -0
  222. package/src/native/components/searchable-tabs.ts +499 -0
  223. package/src/native/definitions.ts +324 -0
  224. package/src/native/extensions.ts +176 -0
  225. package/src/native/foldable/fold-state.ts +10 -0
  226. package/src/native/foldable/types.ts +4 -0
  227. package/src/native/html.ts +193 -0
  228. package/src/native/index.ts +41 -0
  229. package/src/native/legacy-layout.ts +21 -0
  230. package/src/native/overlays/content.ts +51 -0
  231. package/src/native/overlays/controller.ts +349 -0
  232. package/src/native/overlays/dialog-animation.ts +53 -0
  233. package/src/native/overlays/dismiss-animation.ts +15 -0
  234. package/src/native/overlays/focus.ts +64 -0
  235. package/src/native/overlays/popover.ts +120 -0
  236. package/src/native/overlays/styles.ts +181 -0
  237. package/src/native/prehide.ts +285 -0
  238. package/src/native/runtime.ts +988 -0
  239. package/src/native/shared/bridge.ts +18 -0
  240. package/src/native/shared/candidate.ts +162 -0
  241. package/src/native/shared/clipping.ts +24 -0
  242. package/src/native/shared/crossfade.ts +67 -0
  243. package/src/native/shared/diagnostics.ts +13 -0
  244. package/src/native/shared/dom.ts +304 -0
  245. package/src/native/shared/events.ts +14 -0
  246. package/src/native/shared/icons.ts +57 -0
  247. package/src/native/shared/modal.ts +157 -0
  248. package/src/native/shared/projection-id.ts +2 -0
  249. package/src/native/shared/toolbar-options.ts +53 -0
  250. package/src/native/shared/vertical-bars-geometry.ts +80 -0
  251. package/src/native/startup.ts +84 -0
  252. package/src/native/vertical-bars-web-search.ts +86 -0
  253. package/src/native/vertical-bars-web.ts +498 -0
  254. package/src/native-integration/index.ts +49 -0
  255. package/src/vertical-bars.ts +11 -0
@@ -0,0 +1,187 @@
1
+ # Configure Native UI
2
+
3
+ Call `enableNativeUI()` once after your application's DOM is mounted. Ionic and explicitly marked HTML controls share the standard Swift renderers. The theme is optional; Web layout, safe areas and spacing belong to your app or theme.
4
+
5
+ ```ts
6
+ import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
7
+
8
+ const shell = await enableNativeUI();
9
+ ```
10
+
11
+ Without Ionic, import the same API from `@rdlabo/capacitor-native-ui/core`. That entry does not load Ionic's optional popover integration. See [plain HTML](framework-independent.md).
12
+
13
+ ## Platform behavior
14
+
15
+ | Feature | Supported iOS | Android | Browser / older iOS |
16
+ | --- | --- | --- | --- |
17
+ | Automatic Ionic controls (`ionic.auto`) | Native | Original Web controls | Original Web controls |
18
+ | Explicit element buttons | Native | Original Web controls | Original Web controls |
19
+ | Element extensions | SwiftUI | Kotlin / Compose | Original Web content |
20
+ | Vertical Bars | Native when available; otherwise Web rail | Web rail | Web rail |
21
+ | `tab-accessory` extensions | Supported iOS tab host only | Original Web content | Original Web content |
22
+
23
+ Web rails require the Ionic rail layout or an application-owned HTML `webHost`. Settings never create a device capability. `verticalBars.only: true` excludes element extensions on every platform; leave it false when combining a Web rail with Android extensions.
24
+
25
+ ## All settings
26
+
27
+ All nested objects merge with their defaults. For example, `ionic: { auto: { tabs: false } }` disables only tabs.
28
+
29
+ ```ts
30
+ const shell = await enableNativeUI({
31
+ root: document.body,
32
+ debug: false,
33
+ ionic: {
34
+ auto: {
35
+ buttons: true,
36
+ tabs: true,
37
+ segment: true,
38
+ fab: true,
39
+ },
40
+ },
41
+ verticalBars: {
42
+ enabled: true,
43
+ only: false,
44
+ buttonProjection: 'system',
45
+ buttonDefaultFill: 'glass',
46
+ popover: false,
47
+ // Optional: placement: { edge: 'trailing', webHost: appOwnedHost },
48
+ },
49
+ horizontal: true,
50
+ vertical: true,
51
+ extensions: [],
52
+ });
53
+ ```
54
+
55
+ | Setting | Default | Meaning |
56
+ | --- | --- | --- |
57
+ | `ionic.auto` | `true` | `true` or omitted enables automatic Ionic projection; `false` disables it; an object selects kinds. Explicit `data-native-ui` sources and extensions are unaffected. |
58
+ | `horizontal` / `vertical` | `true` | Automatic Ionic toolbar selection and grouping per layout: boolean or `{ buttons, button, backButton }`. Omitted fields are `true`; local attributes may override. See [toolbar settings](native-ui.md#toolbar-selection-and-grouping). |
59
+ | `debug` | `false` | Log source elements and Web fallback reasons to the developer console; no props or application exception messages. |
60
+ | `root` | `document.body` | Connected source subtree. Controls outside it are not projected. |
61
+ | `ionic.auto.buttons` | `true` | Automatic Ionic toolbar/back/menu controls. Explicitly marked buttons are independent. |
62
+ | `ionic.auto.tabs` | `true` | Ionic tabs and their registered native search. |
63
+ | `ionic.auto.segment` | `true` | Supported Ionic segments. |
64
+ | `ionic.auto.fab` | `true` | Supported Ionic floating action buttons. |
65
+ | `verticalBars.enabled` | `true` | Allow rail projection when available. Device capability is always checked; enabling cannot create a system rail. |
66
+ | `verticalBars.only` | `false` | Restrict native projection to rail controls. Other controls stay Web. |
67
+ | `verticalBars.buttonProjection` | `'system'` | `'system'` uses native styling; `'source'` uses supported source styling. Local overrides take precedence. |
68
+ | `verticalBars.buttonDefaultFill` | `'glass'` | `'glass'` or `'solid'` for source-style standalone Ionic buttons with no explicit fill. Explicit fills and the clear default inside `ion-buttons` take precedence. |
69
+ | `verticalBars.popover` | `false` | Opt into Ionic popovers anchored to native rail buttons. Requires the Ionic entry and its optional peers; no effect on plain HTML. |
70
+ | `verticalBars.placement` | omitted | Optional `{ edge: 'leading' \| 'trailing', webHost?: HTMLElement }`. Edge is a preference, not a device capability declaration. |
71
+ | `extensions` | `[]` | **Experimental.** Application-specific SwiftUI / Compose extensions, paired with native registrations. API and behavior may change. |
72
+
73
+ `webHost` is an optional, application-styled HTML fallback rail. It must be connected in the same document and outside `root`, with neither element containing the other. Use a limited `root`, such as your `<main>`, when supplying a sibling host; the default `document.body` cannot have such a host. Without a host, unavailable HTML rails leave their original controls visible. Ionic's existing rail layout uses its own fallback adapter.
74
+
75
+ HTML currently supports buttons and back buttons. Tab, segment and FAB readers remain Ionic adapters; enabling those settings does not infer equivalent structures from arbitrary HTML.
76
+
77
+ ## Common configurations
78
+
79
+ ```ts
80
+ // Ionic or HTML: all supported controls, rail support enabled by default.
81
+ const shell = await enableNativeUI();
82
+
83
+ // Alternative startup: Ionic with only Vertical Bars native presentation.
84
+ const railOnly = await enableNativeUI({ verticalBars: { only: true } });
85
+
86
+ // Alternative startup: automatically project only Ionic tabs; explicit sources remain independent.
87
+ const tabsOnly = await enableNativeUI({
88
+ ionic: { auto: { buttons: false, segment: false, fab: false } },
89
+ });
90
+ ```
91
+
92
+ These are alternatives: start only one shell. Repeating the same startup returns its handle; different startup settings require destroying the previous shell.
93
+
94
+ | Rail setting | Rail available | Rail unavailable |
95
+ | --- | --- | --- |
96
+ | `enabled: true, only: false` | Eligible rail and ordinary native controls | Ordinary native controls; unavailable rail controls retain Web fallback |
97
+ | `enabled: true, only: true` | Eligible rail controls only | Web fallback |
98
+ | `enabled: false, only: false` | Ordinary native controls; rail controls remain Web | Same |
99
+ | `enabled: false, only: true` | All controls remain Web | Same |
100
+
101
+ `data-native-ui-placement="element"` preserves a control's original position. It is never moved into a rail; with `only: true` it stays Web. For Ionic groups, put the placement on the whole `ion-buttons` group so its shared native surface has a single placement.
102
+
103
+ ## Select controls explicitly in a customized Ionic app
104
+
105
+ Disable automatic Ionic projection while retaining the native rail and your custom extensions:
106
+
107
+ ```ts
108
+ const shell = await enableNativeUI({
109
+ root: document.querySelector<HTMLElement>('#content')!,
110
+ ionic: { auto: false },
111
+ verticalBars: { enabled: true, placement: {
112
+ edge: 'trailing',
113
+ webHost: document.querySelector<HTMLElement>('#rail')!,
114
+ } },
115
+ extensions: [myExtension],
116
+ });
117
+ ```
118
+
119
+ ```html
120
+ <main id="content">
121
+ <ion-button>Keep this on the Web</ion-button>
122
+ <ion-button data-native-ui="button" data-native-ui-placement="rail">Project this action</ion-button>
123
+ <button data-native-ui="button">Project at its original position</button>
124
+ </main>
125
+ <aside id="rail" aria-label="Actions"></aside>
126
+ ```
127
+
128
+ Keep your original handlers on each source. Explicit `ion-button` uses the standard button reader, so supported content and styling rules still apply; the attribute does not make arbitrary custom markup supported. `data-native-ui-placement="rail"` requests the system rail when available. Style the optional `webHost` and reserve space in your app; without it, unavailable explicit rails keep their source controls visible. Unmarked Ionic controls remain Web-rendered. An automatically projected group containing an explicitly marked source stays on the Web to avoid covering that source twice.
129
+
130
+ Explicit built-in sources support individual `button`, `ion-button`, and `[role="button"]` elements with `data-native-ui="button"` or `"back-button"`. This does not opt existing `ion-back-button`, `ion-menu-button`, button groups, tabs, segments, or FABs into their Ionic-specific readers. Keep automatic projection enabled for the corresponding kind, use a standard explicit button with your own click handler, or register an extension for custom UI.
131
+
132
+ `verticalBars.enabled` and `only` still govern rail availability and projection scope. For example, `only: true` leaves explicitly marked element-position buttons and ordinary element extensions on the Web too.
133
+
134
+ ## Change rail use after startup
135
+
136
+ ```ts
137
+ await shell.disableVerticalControlArea();
138
+ await shell.enableVerticalControlArea();
139
+ ```
140
+
141
+ These methods change `verticalBars.enabled` on this shell and preserve `only`, appearance, controls and placement. They retire existing projection before transferring ownership, without creating another shell. Calls are serialized. Calling either method after `destroy()` rejects.
142
+
143
+ ```ts
144
+ const lease = await shell.suspend(); // temporarily restore all Web controls
145
+ await lease.resume();
146
+ await shell.destroy(); // stop the shell and restore sources
147
+ ```
148
+
149
+ Disabling rail projection does not remove application CSS or change application-owned spacing. Update your own layout alongside this setting if needed.
150
+
151
+ ## Request a check after an unobserved change
152
+
153
+ ```ts
154
+ shell.markForCheck();
155
+ ```
156
+
157
+ Use this after changing a property or reader state that does not cause a DOM mutation. It schedules a check, batches repeated requests, and sends only changed native snapshots. It returns `void`; it does not wait for native completion or render your framework's DOM. Calls after destruction are ignored. See [source checks and observation](native-ui-extensions.md#schedule-a-source-check).
158
+
159
+ ## Understand why a source stays on the Web
160
+
161
+ Temporarily add `debug: true` to your existing startup options, then rebuild the Web app. Do not start a second shell to enable diagnostics:
162
+
163
+ ```ts
164
+ const shell = await enableNativeUI({ debug: true, extensions: [myExtension] });
165
+ console.info(shell.getStatus());
166
+ ```
167
+
168
+ Open the WebView developer console and filter for `[Native UI]`. Diagnostics include the DOM source when available. Each reason is logged once per source during that activation, so scrolling or repeated `markForCheck()` calls do not flood the console. Props, action payloads and application exception messages are not logged.
169
+
170
+ | Diagnostic | What to check |
171
+ | --- | --- |
172
+ | `web-fallback` | Platform support, native plugin registration, sync and native rebuild. |
173
+ | `unregistered-extension` | Native registration name matches the DOM definition and runs before startup. Newly inserted sources are checked too. |
174
+ | `reader-error` / `invalid-props` | Reader implementation, JSON-only values and the 64K size limit. |
175
+ | `unsupported-placement` / `missing-tab-owner` | Platform and owning native tab host for a tab accessory. |
176
+ | `source-hidden` / `source-clipped` / `source-focused` | Visibility, clipping/transformed ancestors, or a focused Web descendant. |
177
+ | `reader-opt-out` / `rail-only` | Your reader returned null, or `verticalBars.only` excludes an element extension. |
178
+ | `unsupported-control` | Supported markup, accessible name, fixed placement and styling. |
179
+ | `native-rejected` | Native props decoding, placement or device support. The bridge reports rejection, not the native exception. |
180
+
181
+ `getStatus()` summarizes the shell; it is not a per-element diagnostic report. Normal navigation, suspension and unsupported content can intentionally retain Web presentation. Turn debug off after investigating; it does not change projection eligibility.
182
+
183
+ ## Compatibility
184
+
185
+ `controls` is deprecated. If `ionic.auto` is omitted, existing `controls` settings retain their previous behavior, including `buttons: false` disabling explicitly marked buttons. When `ionic.auto` is supplied, it takes precedence for automatic controls and explicit sources are independent. Move `controls.popover` to `verticalBars.popover`. Partial objects retain enabled defaults for omitted kinds; use `false` to disable all automatic kinds.
186
+
187
+ The existing global `enableVerticalControlArea()` remains a startup wrapper for Ionic rail-only usage, while `setVerticalControlAreaPlacement()` is a deprecated Web CSS compatibility adapter. Its `nativeEdge` input is ignored; native availability is obtained from the plugin’s own device metrics. Use the theme’s `setVerticalBarsLayout({ edge, inset })` for Web layout. New code should use the shared startup and handle methods above. See [migration](migration.md) for legacy settings and the changed partial-control defaults.
@@ -0,0 +1,82 @@
1
+ # Native UI without Ionic
2
+
3
+ > This guide targets the upcoming package split. Until the first native package release, use the [candidate archive installation](../README.md#try-the-unreleased-packages) in place of registry install commands.
4
+
5
+ Add native buttons to an existing Web app without Ionic or theme CSS. Keep labels, state, and click handlers on ordinary HTML elements. Standard buttons require no custom Swift registration.
6
+
7
+ Use a Capacitor 8 app with an iOS project, Xcode 26+, and an iOS 26+ device or simulator. Browsers and Android retain Web controls. For custom Android native content, use [Kotlin/Compose extensions](native-ui-extensions.md#2b-register-compose-content-on-android).
8
+
9
+ ## 1. Install the plugin
10
+
11
+ Keep your existing Capacitor 8 installation and add the plugin directly:
12
+
13
+ ```sh
14
+ npm install @rdlabo/capacitor-native-ui
15
+ ```
16
+
17
+ The `/core` entry requires `@capacitor/core` but no Ionic dependencies. If this is currently a browser-only application, configure its Capacitor 8 iOS project before running native controls.
18
+
19
+ ## 2. Mark source controls
20
+
21
+ ```html
22
+ <main id="app">
23
+ <button type="button" data-native-ui="button" data-native-ui-placement="element">Add</button>
24
+ <button type="button" data-native-ui="back-button" data-native-ui-placement="rail">Back</button>
25
+ </main>
26
+ <aside id="rail" aria-label="Navigation"></aside>
27
+ ```
28
+
29
+ - `data-native-ui="button"` opts into a standard action button.
30
+ - `data-native-ui="back-button"` opts into a standard back button. Its original click handler owns navigation; the plugin never calls `history.back()`.
31
+ - `data-native-ui-placement="rail"` requests Vertical Bars. Without it, the source rectangle is used. `element` explicitly preserves that rectangle.
32
+ - Sources must be `<button>`, explicitly marked `ion-button`, or custom elements exposing `role="button"` and click behavior. Unmarked HTML is never inferred to be a control.
33
+
34
+ Keep the accessible name, supported static SVG, disabled state and application handlers on the source. Names resolve from `aria-labelledby`, `aria-label`, then text. Native `disabled`, a disabled fieldset and `aria-disabled="true"` are respected. Hidden, removed, excluded or out-of-scope sources are retired and stale actions are rejected. Use `data-native-ui="disabled"` to exclude a subtree.
35
+
36
+ Native and Web-proxy actions click the original element. An ordinary `<button type="submit">` therefore keeps its original form behavior. Use `type="button"` for actions that must not submit. The native bridge does not preserve browser user-activation privileges for APIs such as popup opening.
37
+
38
+ ## 3. Start after mounting the controls
39
+
40
+ ```ts
41
+ import { enableNativeUI } from '@rdlabo/capacitor-native-ui/core';
42
+
43
+ const shell = await enableNativeUI({
44
+ root: document.querySelector<HTMLElement>('#app')!,
45
+ });
46
+ ```
47
+
48
+ Call once after the source root exists. Keep the handle and call `await shell.destroy()` when that root is disposed. The plugin does not add page padding or safe-area rules; retain your application's CSS. For Angular, React, or Vue, use the [app-root lifecycle examples](setup.md#place-startup-in-your-frameworks-app-root) with the `/core` import. For animated navigation, use [suspension through completion and cancellation](setup.md#navigation-without-the-theme).
49
+
50
+ ## 4. Build and run on iOS
51
+
52
+ Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. The marked Add button should render natively and invoke its original click handler. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged.
53
+
54
+ If a button stays on the Web on supported iOS, check that it is visible within the configured root, has an accessible name, and that the direct plugin dependency was synced and the iOS app rebuilt.
55
+
56
+ In an app that also uses Ionic, set `ionic: { auto: false }` to project only explicit sources and registered extensions. See [selective Ionic projection](configuration.md#select-controls-explicitly-in-a-customized-ionic-app).
57
+
58
+ ## Optional Web rail
59
+
60
+ Replace the startup call in step 3 with the following configuration; do not start a second shell:
61
+
62
+ ```ts
63
+ const shell = await enableNativeUI({
64
+ root: document.querySelector<HTMLElement>('#app')!,
65
+ verticalBars: {
66
+ placement: {
67
+ edge: 'trailing',
68
+ webHost: document.querySelector<HTMLElement>('#rail')!,
69
+ },
70
+ },
71
+ });
72
+ ```
73
+
74
+ The plugin uses device-reported rail availability and resolves leading/trailing through the document direction. A placement preference never forces an unsupported system rail. Native rejection restores Web presentation. With `webHost`, explicitly rail-marked HTML controls have Web proxies when native projection is unavailable. Without it, their originals remain visible. The application styles and positions the host and reserves any content spacing. The host must be outside the source root.
75
+
76
+ To use only rail projection, add `verticalBars.only: true`. To change rail use later, call `await shell.disableVerticalControlArea()` or `await shell.enableVerticalControlArea()`. Disabling restores original controls and removes Web rail proxies without destroying ordinary native projection. `only: true` keeps ordinary controls on the Web throughout.
77
+
78
+ Currently the HTML adapter supports buttons and back buttons. Arbitrary HTML tabs, segments, searchbars and FAB structures are not translated. Ionic adapters continue to support their existing controls. Custom element extensions work with the same scoped root; the existing `tab-accessory` extension placement still requires an Ionic tab-bar owner.
79
+
80
+ ## Example
81
+
82
+ The [plain HTML example](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/main/examples/native-ui-html) demonstrates Add, routerless Home/Details/Back navigation, optional Web rail projection and cleanup. It uses no Ionic or theme CSS.
@@ -0,0 +1,206 @@
1
+ ---
2
+ title: Migration
3
+ ---
4
+
5
+ # Migration
6
+
7
+ > This guide targets the upcoming package split. Until the first native package release, use the [candidate archive installation](../README.md#try-the-unreleased-packages) in place of registry install commands.
8
+
9
+ ## Migrating from Native UI Shell Preview
10
+
11
+ Native UI Shell was already published as **Preview** in `@rdlabo/ionic-theme-ios27` 1.2.0 and 1.2.1. Preview users need the migration below when adopting the split packages; changing the import alone is not sufficient for every configuration. Candidate/beta users should also compare their configuration with these mappings and use JavaScript and native code from the same candidate.
12
+
13
+ 1. Install the matching packages and replace removed imports using [package separation](#native-ui-package-separation).
14
+ 2. Apply the [API and native naming changes](#native-ui-naming-changes), then translate startup settings using [shared shell configuration](#shared-shell-configuration-and-html-adapters), especially partial `controls` objects.
15
+ 3. Update [Web layout ownership](#web-layout-ownership) and the [transition adapter](#native-ui-transition-adapter) if used.
16
+ 4. Rebuild Web assets, sync Capacitor, and rebuild the native app. A Web-only update cannot replace the native plugin.
17
+
18
+ Native UI remains Preview. App-local SwiftUI / Compose **extensions are experimental**, including their TypeScript definitions, native registration APIs and placement behavior. The `defineNativeUI` and `extensions` names stay unchanged; native registration names change as listed below. Applications using extensions should keep their Web fallback and verify their reader, native renderer and action forwarding together after updates. See the [extension guide](native-ui-extensions.md).
19
+
20
+ ## Native UI package separation
21
+
22
+ For a new native integration, follow [Native UI setup](setup.md) or, if you already use the iOS theme, [Add Native UI to your theme](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/add-native-ui.md). The steps below cover existing applications migrating package names and native projects.
23
+
24
+ The theme and native controls now live in separate packages. Choose dependencies by the behavior your app uses:
25
+
26
+ | App behavior | Package to use directly |
27
+ | --- | --- |
28
+ | Theme styles, Ionic Web behavior, and theme animations | `@rdlabo/ionic-theme-ios27` |
29
+ | Native UI or standalone Vertical Control Area | `@rdlabo/capacitor-native-ui` |
30
+ | Theme styling together with native controls | Both packages |
31
+
32
+ The theme owns the appearance and behavior of Ionic's Web elements. Native UI owns projecting supported controls into native iOS views and coordinating their lifecycle with Web navigation. Native UI can be used without the iOS 27 theme; installing it does not apply theme styles. Standard controls on Android and browsers retain Web presentation; Android can render [app-local Compose extensions](native-ui-extensions.md#2b-register-compose-content-on-android).
33
+
34
+ The theme uses the native package's shared transition/search integration internally. This dependency does not enable native controls automatically. Apps using native controls must declare the native package directly so Capacitor can discover the plugin.
35
+
36
+ ### 1. Update application dependencies
37
+
38
+ These commands apply once the packages containing this migration are published.
39
+
40
+ For theme-only apps, update the theme package. Keep existing theme imports and CSS/Sass paths; no native startup or plugin setup is needed for theme-only use.
41
+
42
+ ```sh
43
+ npm install @rdlabo/ionic-theme-ios27
44
+ ```
45
+
46
+ For apps using both the theme and native controls, install both as direct application dependencies:
47
+
48
+ ```sh
49
+ npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui
50
+ ```
51
+
52
+ For native controls with your existing Web appearance, follow [Native UI only](setup.md). Install the native package directly and keep your existing Web styles. The plugin no longer requires theme variables to select supported controls; Web layout remains the responsibility of your app or theme. Native startup requires Capacitor 8. The Ionic adapter requires Ionic 8.8.1 or later within Ionic 8 or 9; the `/core` entry does not require Ionic.
53
+
54
+ If you used the intermediate `@ionic-theme/capacitor-native-ui` package name during development, remove it and install `@rdlabo/capacitor-native-ui`. Keep the theme dependency if your app uses its styles or APIs.
55
+
56
+ ```sh
57
+ npm uninstall @ionic-theme/capacitor-native-ui
58
+ npm install @rdlabo/capacitor-native-ui
59
+ ```
60
+
61
+ ### 2. Replace native imports
62
+
63
+ | Previous import | Replacement |
64
+ | --- | --- |
65
+ | `@rdlabo/ionic-theme-ios27/native` | `@rdlabo/capacitor-native-ui` |
66
+ | `@rdlabo/ionic-theme-ios27/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
67
+ | `@ionic-theme/capacitor-native-ui` | `@rdlabo/capacitor-native-ui` |
68
+ | `@ionic-theme/capacitor-native-ui/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
69
+ | `@ionic-theme/capacitor-native-ui/integration` | `@rdlabo/capacitor-native-ui/integration` |
70
+
71
+ Update both value and type imports, including lazy imports, test aliases, and bundler configuration. The old theme `/native` and `/vertical-bars` entries have been removed; there are no compatibility aliases.
72
+
73
+ ```diff
74
+ - import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';
75
+ + import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
76
+ ```
77
+
78
+ Keep existing control markup; review the [startup option changes](#shared-shell-configuration-and-html-adapters), including partial `controls` defaults. The ordinary `@rdlabo/ionic-theme-ios27` entry, theme CSS/Sass paths, and theme animation imports stay unchanged. For custom navigation animations, see the [transition adapter instructions](#native-ui-transition-adapter) below.
79
+
80
+ ### 3. Sync the native project
81
+
82
+ After updating dependencies and imports, rebuild your app's Web assets with its usual build command, then run:
83
+
84
+ ```sh
85
+ npx cap sync ios
86
+ ```
87
+
88
+ The Capacitor plugin is now supplied by the native package. Remove manually linked copies of the old theme Swift package so only the new package supplies the plugin. The Swift package/product and CocoaPods pod are now `RdlaboCapacitorNativeUi`; the Swift module is `NativeUIPlugin`. The Capacitor bridge name is now `NativeUI` on JavaScript, iOS and Android. Run Capacitor sync for each native platform you use and rebuild the native app. An OTA/Web-only update cannot migrate an installed binary containing only the old plugin. Use matching JavaScript and native package versions.
89
+
90
+ For local Swift package references, the package now lives at `packages/capacitor-native-ui` within this repository; there is no root Swift package. Prefer Capacitor's generated integration for application dependencies.
91
+
92
+ ### 4. Verify the migration
93
+
94
+ - Check `package.json` and the lockfile: native-enabled apps have a direct `@rdlabo/capacitor-native-ui` dependency and no intermediate `@ionic-theme/capacitor-native-ui` dependency.
95
+ - Build the application and confirm there are no imports of the removed theme entries or the intermediate package name.
96
+ - Build and launch the iOS app after sync. Check native controls, navigation/back gestures, and any app-local Swift extensions you use.
97
+ - Check browser and Android Web presentation where supported by your app, and confirm the theme still looks correct.
98
+ - Check that previously disabled control kinds remain disabled after expanding partial Preview settings. Test toolbar grouping, individually selected actions and back navigation in both horizontal and vertical layouts.
99
+ - For experimental extensions, check native registration, props, click forwarding and restoration of the original Web UI when projection is unavailable. Rebuild and sync Android as well if the app uses Compose extensions.
100
+
101
+ ## Native UI naming changes
102
+
103
+ The split package uses Native UI names throughout. These are breaking renames; the old names are not aliases. The historical names below refer to the published theme Preview or pre-release package candidates.
104
+
105
+ | Previous name | New name |
106
+ | --- | --- |
107
+ | `enableNativeUIShell()` | `enableNativeUI()` |
108
+ | `NativeUIShellOptions`, `NativeUIShellHandle`, `NativeUIShellStatus`, `NativeUIShellSuspension`, `NativeUIShellControls`, `NativeUIShellComponent` | `NativeUIOptions`, `NativeUIHandle`, `NativeUIStatus`, `NativeUISuspension`, `NativeUIControls`, `NativeUIComponent` |
109
+ | `IonicNativeUIShell` (plugin export and bridge registration) | `NativeUI` |
110
+ | `withNativeUIShellTransition()` | `withNativeUITransition()` (still exported by the theme package) |
111
+ | `IonicNativeUIShellPlugin` (Swift module/native class) | `NativeUIPlugin` |
112
+ | `IonicThemeCapacitorNativeUi` (SPM package/product and pod) | `RdlaboCapacitorNativeUi` |
113
+ | `NativeUIShell.register()` (Swift / Kotlin extensions) | `NativeUI.register()` |
114
+ | `data-shell="disabled"` | `data-native-ui="disabled"` |
115
+ | `.ios-theme-shell-disabled` | `.native-ui-disabled` |
116
+
117
+ Replace the `NativeUIShell` part with `NativeUI` in imports from `/integration` as well (for example `suspendNativeUI` and `setNativeUIIntegration`). App-local Swift extensions now use `import NativeUIPlugin`; Kotlin extensions use `import dev.rdlabo.capacitor.nativeui.NativeUI`.
118
+
119
+ Runtime-owned attributes/events are internal, not configuration: `data-native-ui-shell` is now `data-native-ui-projected`, other `data-native-ui-shell-*` attributes use `data-native-ui-*`, `nativeUIShell*` events use `nativeUI*`, and `.ios-theme-native-ui-shell-*` classes use `.native-ui-*`. Update application tests or diagnostics that inspect them. **Keep explicit source declarations such as `data-native-ui="button"` unchanged**; the projected marker is separate and is managed by the runtime.
120
+
121
+ ## Native UI transition adapter
122
+
123
+ The Ionic transition adapter now belongs to `@rdlabo/ionic-theme-ios27`. Replace preview imports from the plugin’s `/integration` or `/vertical-bars` entry with the theme root. Native-only applications can use `shell.suspend()` and the returned `resume()` directly.
124
+
125
+ Use `withNativeUITransition()` to keep your existing Ionic navigation animation while coordinating Native UI controls.
126
+
127
+ - If you already import `iosTransitionAnimation` from `@rdlabo/ionic-theme-ios27`, keep the [package animation setup](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md#use-this-packages-ios-animation); no configuration change is needed. It now uses the shared adapter internally; do not add another wrapper.
128
+ - If you use Ionic's default animation without a `navAnimation` option, follow [Keep Ionic's default animation](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md#keep-ionics-default-animation). The example selects Ionic's standard iOS or MD builder from the transition mode.
129
+ - If you use a custom navigation animation with Native UI or the standalone Vertical Control Area, wrap your existing builder when configuring Ionic:
130
+
131
+ ```diff
132
+ + import { withNativeUITransition } from '@rdlabo/ionic-theme-ios27';
133
+
134
+ const ionicConfig = {
135
+ - navAnimation: existingTransition,
136
+ + navAnimation: withNativeUITransition(existingTransition),
137
+ };
138
+ ```
139
+
140
+ Merge this option into your existing Ionic configuration before initialization. The adapter preserves the animation's effects, duration, and easing while coordinating native retirement, swipe progress, and cancellation. Keep your existing theme stylesheet imports and Native UI or Vertical Control Area startup.
141
+
142
+ Use the adapter only for navigation; leave modal and popover animations unchanged. Your builder must create a fresh `Animation` for each navigation because Ionic destroys it afterward. Keep lifecycle events for control registration and transitions without animation. If the custom builder animates a horizontal back button separately, exclude that effect while `.ios-theme-vertical-bars` is active.
143
+
144
+ See [Connect your navigation animation](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md#3-connect-your-navigation-animation) for the setup and supported scope.
145
+
146
+ ## Shared shell configuration and HTML adapters
147
+
148
+ Run `npx cap sync ios` after updating: JavaScript now sends framework-independent control kinds to Swift. New Swift code also accepts the previous `ion-*` kinds, but new JavaScript requires the matching native plugin.
149
+
150
+ - `controls.toolbar` becomes `ionic.auto.buttons`.
151
+ - `controls.popover` becomes `verticalBars.popover`.
152
+ - Top-level `buttonProjection` and `buttonDefaultFill` move into `verticalBars`.
153
+ - The new `verticalBars.buttonDefaultFill` uses `'glass'` instead of `null`.
154
+ - `verticalBarsOnly: true` becomes `verticalBars: { only: true }`.
155
+ - Omitted automatic kinds now default to `true`, including partial legacy `controls` objects. Published 1.2.x enabled only fields explicitly set to `true` when `controls` was supplied. Expand partial objects when migrating, even if you keep the legacy option name.
156
+ - The global `enableVerticalControlArea()` remains a legacy startup wrapper. New code starts with `enableNativeUI({ verticalBars: { only: true } })`, then uses the returned handle's `enableVerticalControlArea()` / `disableVerticalControlArea()` methods.
157
+ - Legacy option names and global `enabled: false` remain accepted; use `shell.destroy()` to stop new code. If both old and new settings are supplied, the new nested settings take precedence.
158
+
159
+ For example, preserve a Preview configuration that projected only tabs:
160
+
161
+ ```diff
162
+ - import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';
163
+ + import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
164
+
165
+ - const shell = await enableNativeUIShell({
166
+ + const shell = await enableNativeUI({
167
+ - controls: { tabs: true },
168
+ - buttonProjection: 'source',
169
+ - buttonDefaultFill: null,
170
+ + ionic: { auto: { tabs: true, buttons: false, segment: false, fab: false } },
171
+ + verticalBars: { buttonProjection: 'source', buttonDefaultFill: 'glass' },
172
+ });
173
+ ```
174
+
175
+ Likewise, replace `controls: {}` with `ionic: { auto: false }` to keep automatic Ionic projection off. Explicit `data-native-ui` sources and extensions remain independent. Calling `enableNativeUI()` with no options still enables all supported automatic kinds.
176
+
177
+ ### Toolbar selection and grouping by layout
178
+
179
+ `horizontal` and `vertical` are new toolbar settings. Omit them to retain automatic selection and grouping, or configure each layout independently:
180
+
181
+ ```ts
182
+ const shell = await enableNativeUI({
183
+ horizontal: false,
184
+ vertical: { buttons: false, button: false, backButton: true },
185
+ });
186
+ ```
187
+
188
+ This selects only eligible back buttons among vertical toolbar actions and disables automatic horizontal toolbar selection. Tabs, segments and FABs retain their own settings. To enable all eligible toolbar actions only in vertical layout, use `{ horizontal: false, vertical: true }`; this follows layout, not the Duo model.
189
+
190
+ Do not translate `controls.toolbar` or `ionic.auto.buttons` into `vertical.buttons`: those options govern the entire automatic toolbar category, while `horizontal.buttons` / `vertical.buttons` govern **grouping only**. Within each layout, `button` selects ordinary actions and `backButton` selects back navigation independently. All three fields default to `true`; a layout value of `false` sets all three to `false`.
191
+
192
+ If you used the short-lived development settings at the top level, move `{ buttons, button, backButton }` into `vertical: { buttons, button, backButton }`. Those fields were not part of the published 1.2.0/1.2.1 API and are not compatibility aliases. Existing `buttonProjection` / `buttonDefaultFill` are separate appearance settings, mapped above.
193
+
194
+ Local `data-horizontal-bars="true|false"` / `data-vertical-bars="true|false"` can override these layout defaults. On `ion-buttons` they affect grouping only and never select children. A back button ignores its group's grouping setting. `ionic.auto` and structural exclusions still apply. See [toolbar selection and grouping](native-ui.md#toolbar-selection-and-grouping) for eligibility and when changes take effect.
195
+
196
+ See [all settings and defaults](configuration.md) and [plain HTML setup](framework-independent.md). Standard HTML buttons use the same Swift controls as Ionic; they do not require an application-specific Swift extension.
197
+
198
+ ## Web layout ownership
199
+
200
+ Import `setVerticalBarsLayout` and `applyFoldStateClasses` from `@rdlabo/ionic-theme-ios27` for theme CSS. Replace `setVerticalControlAreaPlacement({ edge, nativeEdge, inset })` with `setVerticalBarsLayout({ edge, inset })`. Native UI now reads availability from its own device metrics; `nativeEdge` is ignored by the deprecated compatibility helper. Normal startup, disabled rails and teardown do not remove theme classes or alter reserved Web spacing. An unavailable native rail keeps the application’s Web rail layout; applications wanting a horizontal layout must select it themselves. Native-only applications can continue to use their own layout CSS.
201
+
202
+ ## Automatic Ionic controls
203
+
204
+ Use `ionic.auto` for new configuration. It accepts `true` (default), `false`, or `{ buttons, tabs, segment, fab }`. Unlike legacy `controls`, it affects only automatic Ionic projection; explicit `data-native-ui` sources and registered extensions remain enabled. `verticalBars` independently controls rail projection. See [selective projection](configuration.md#select-controls-explicitly-in-a-customized-ionic-app) for an existing Ionic app that opts in individual buttons.
205
+
206
+ The legacy `controls` option is marked `@deprecated` and remains available. When `ionic.auto` is omitted, legacy kind settings are used, but omitted kinds now default to `true`; expand partial Preview configurations as shown above. If both are supplied, `ionic.auto` takes precedence for automatic kinds. Move the legacy `popover` option to `verticalBars.popover`.
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: Native UI Extension: Now Playing
3
+ ---
4
+
5
+ # Native UI Extension: Now Playing
6
+
7
+ This example uses the experimental extension API, which may change in future releases.
8
+
9
+ Use the [app-local extension API](native-ui-extensions.md) to add a SwiftUI mini-player with a Web fallback. The demo's Index → Native UI Extension page provides a sample you can copy into your application.
10
+
11
+ - [Web component](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/app/mini-player/mini-player.component.ts): owns playback state and the Now Playing modal. Play/pause is separate from opening the player. Tap or swipe up on the track opens the same modal.
12
+ - [Web renderer + DOM reader](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/native-ui/mini-player.ts): renders the Web UI from attributes, sends title, artist and playback state to SwiftUI, and maps `open` / `toggle` to the original Web buttons.
13
+ - [SwiftUI View](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/ios/App/App/NativeMiniPlayer.swift): uses `Button`, SF Symbols, `DragGesture`, and a capsule glass background. It awaits `NativeUIActions.activate()` rather than duplicating playback state or presenting its own modal.
14
+
15
+ The SwiftUI sample uses `NativeUIContext.sourceFrame.width` for compact spacing and `actions.isAvailable(...)` to disable actions whose Web targets are unavailable. See [projection context](native-ui-extensions.md#read-source-geometry-and-presentation-state) for coordinate units and system placement.
16
+
17
+ Register the View once in the app delegate and pass `miniPlayer` in `enableNativeUI({ extensions: [miniPlayer] })`. Copy these app-local files to adapt the sample to your own playback state and artwork. The original sunset artwork is drawn with SwiftUI shapes, with a matching local SVG for the Web. The compact player shows only artwork, track metadata and playback; elapsed time stays in Now Playing.
18
+
19
+ The sample uses the default **element projection**: SwiftUI follows the Web element’s position and size on iOS 26+, with ordinary tabs or Vertical Bars. Browsers and unsupported environments retain the Web player. Use the **Native extension** toggle to switch native projection on or off. The player starts in Web mode; disabling the extension restores its Web UI. It is shown only on this demo page; leaving the page hides it without resetting playback state.
20
+
21
+ The Web fallback reserves its own bottom-slot space and keeps play state across tab changes. Opening Now Playing retires the native player through the shell's existing overlay policy; dismissing it restores the current Web state.
22
+
23
+ This is a UI example: elapsed time is fixed and no audio engine is started. Applications own playback and reflect current values into the Web DOM. Connect your application's audio engine to the sample's playback events before using it as a player.
24
+
25
+ ## Use the Web + SwiftUI set in your application
26
+
27
+ The local TypeScript file includes both a renderer for an ordinary DOM element and its native reader. Copy it together with `NativeMiniPlayer.swift`; in your app, replace the demo’s relative `defineNativeUI` import with `@rdlabo/capacitor-native-ui`. The Web renderer works without Angular, Capacitor or an enabled Native UI; SwiftUI replaces it only after native accepts the projection.
28
+
29
+ ```ts
30
+ import { miniPlayer, mountNowPlaying } from './native-ui/mini-player';
31
+ import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
32
+
33
+ const player = document.querySelector<HTMLElement>('[data-mini-player]')!;
34
+ const destroyWeb = mountNowPlaying(player); // Run on every platform, including the Web.
35
+ player.addEventListener('togglePlayback', () => {
36
+ player.setAttribute('data-playing', String(player.getAttribute('data-playing') !== 'true'));
37
+ });
38
+ player.addEventListener('openNowPlaying', () => openNowPlaying());
39
+ await enableNativeUI({ extensions: [miniPlayer] });
40
+ // Call destroyWeb() when the application's source element is destroyed.
41
+ ```
42
+
43
+ ```html
44
+ <section data-mini-player data-title="Midnight Drive" data-artist="The Coast" data-playing="true"></section>
45
+ ```
46
+
47
+ The sample does not specify `placement`; the default is `'element'`. Applications can opt into `placement: 'tab-accessory'` when they want a system tab accessory; see the [placement options and host requirements](native-ui-extensions.md#content-and-placement-are-independent). Its surrounding Web layout belongs to the application. The same attributes feed the Web view and SwiftUI; both dispatch the same events. The application updates attributes after its playback state changes. Mount into an empty element. The Web renderer owns only its generated children and releases its observer and handlers on teardown. Frameworks own the host's attributes, not those children. Text is rendered as text, never interpreted as HTML.