@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Package.swift ADDED
@@ -0,0 +1,23 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "RdlaboCapacitorNativeUi",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(name: "RdlaboCapacitorNativeUi", targets: ["NativeUIPlugin"])
9
+ ],
10
+ dependencies: [.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")],
11
+ targets: [.target(
12
+ name: "NativeUIPlugin",
13
+ dependencies: [
14
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
15
+ .product(name: "Cordova", package: "capacitor-swift-pm")
16
+ ],
17
+ path: "ios/Sources/NativeUIPlugin"
18
+ ), .testTarget(
19
+ name: "NativeUIPluginTests",
20
+ dependencies: ["NativeUIPlugin"],
21
+ path: "ios/Tests/NativeUIPluginTests"
22
+ )]
23
+ )
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
- # Temporary Holding Version
1
+ # Capacitor Native UI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Unreleased package:** the first npm release of `@rdlabo/capacitor-native-ui` is pending. The registry install commands in these guides apply after that release. To try the current code now, install a candidate archive as described below.
4
+
5
+ Render supported Web controls with UIKit and SwiftUI on iOS while keeping your application state, routing, and handlers in the WebView. Use existing Ionic markup or explicit HTML buttons; custom SwiftUI or Android Compose content can be added through experimental app-local extensions.
6
+
7
+ Native UI is opt-in and currently a preview. Standard controls on Android, browsers, older iOS, and unsupported controls retain Web presentation. Android supports experimental app-local Compose extensions. Keep your application's styles and layout; the iOS theme is optional.
8
+
9
+ ## Choose what to build
10
+
11
+ | Goal | Start here |
12
+ | --- | --- |
13
+ | Render existing Ionic controls natively | [Ionic setup](docs/setup.md) |
14
+ | Use native buttons without Ionic | [HTML setup](docs/framework-independent.md) |
15
+ | Project only tabs and toolbar actions into a side rail | [Vertical Bars step by step](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md) |
16
+ | Add native controls to an existing iOS 27 theme app | [Add Native UI to your theme](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/add-native-ui.md) |
17
+ | Render custom SwiftUI or Android Compose content | [App-local extensions (experimental)](docs/native-ui-extensions.md) |
18
+
19
+ ## Try the unreleased packages
20
+
21
+ 1. Open a successful [Package Candidate run](https://github.com/rdlabo-dev/ionic-theme-ios27/actions/workflows/package-candidate.yml) for the commit you want to try. Use the documentation from that same commit. Download and extract its `npm-candidate-…` artifact; GitHub requires sign-in to download artifacts.
22
+ 2. Choose the packages for your goal. Replace `VERSION` and the paths with the downloaded filenames:
23
+
24
+ **Native UI only** — in your Capacitor 8 app:
25
+
26
+ ```sh
27
+ npm install /path/to/rdlabo-capacitor-native-ui-VERSION.tgz
28
+ ```
29
+
30
+ **Theme only, or theme with Native UI** — install both archives from the same artifact so their versions match:
31
+
32
+ ```sh
33
+ npm install /path/to/rdlabo-capacitor-native-ui-VERSION.tgz /path/to/rdlabo-ionic-theme-ios27-VERSION.tgz
34
+ ```
35
+
36
+ The theme depends on the Native UI package, so its unpublished dependency must also be supplied as an archive. **Theme-only browser apps do not need Capacitor, shell activation, or native sync.** Continue with [theme setup](../theme-ios27/docs/setup.md).
37
+
38
+ 3. For Native UI, continue with the guide for your goal above. Build your Web assets, sync Capacitor and rebuild the native app as described there. The archive install replaces each guide's `npm install` step.
39
+
40
+ Already using published theme v1.2.1? Its bundled native APIs belong to the old setup. Follow [migration](docs/migration.md) when adopting these candidate packages. The [published documentation](https://docs.rdlabo.dev/projects/ionic-theme-ios27) describes that released version; this directory describes the upcoming split.
41
+
42
+ ## Requirements
43
+
44
+ Native startup requires Capacitor 8 and an iOS or Android project. Android extensions require API 24+ and Kotlin/Compose. Ordinary native rendering requires iOS 26+ and Xcode 26+. The Ionic adapter supports Ionic `>=8.8.1 <10`; the `/core` HTML adapter requires no Ionic or theme package. [Vertical Bars](docs/vertical-bars.md) and [extension placement](docs/native-ui-extensions.md#content-and-placement-are-independent) have additional device requirements.
45
+
46
+ For standard controls, follow the setup guide for your markup; the Ionic and HTML adapters use different imports. For custom native content, start directly with the [extensions guide](docs/native-ui-extensions.md).
47
+
48
+ ## Reference and examples
49
+
50
+ - [Supported Ionic controls](docs/native-ui.md#supported-markup) and fallback behavior.
51
+ - [Configuration](docs/configuration.md): defaults, selected controls, rail-only mode, and live changes.
52
+ - [Vertical Bars](docs/vertical-bars.md): eligibility, appearance, and runtime API.
53
+ - [Now Playing extension](docs/native-mini-player.md): a Web and SwiftUI example to adapt.
54
+ - [Migration](docs/migration.md): package names, imports, and native project updates.
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'RdlaboCapacitorNativeUi'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '15.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.9'
17
+ end
@@ -0,0 +1,69 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ }
7
+
8
+ buildscript {
9
+ ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
10
+ repositories {
11
+ google()
12
+ mavenCentral()
13
+ }
14
+ dependencies {
15
+ classpath 'com.android.tools.build:gradle:8.13.0'
16
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
17
+ classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlin_version"
18
+ }
19
+ }
20
+
21
+ apply plugin: 'com.android.library'
22
+ apply plugin: 'kotlin-android'
23
+ apply plugin: 'org.jetbrains.kotlin.plugin.compose'
24
+
25
+ android {
26
+ namespace = "dev.rdlabo.capacitor.nativeui"
27
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
28
+ defaultConfig {
29
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
30
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
31
+ versionCode 1
32
+ versionName "1.0"
33
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
34
+ }
35
+ buildTypes {
36
+ release {
37
+ minifyEnabled false
38
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
39
+ }
40
+ }
41
+ lintOptions {
42
+ abortOnError = false
43
+ }
44
+ buildFeatures { compose true }
45
+ kotlinOptions { jvmTarget = '21' }
46
+ compileOptions {
47
+ sourceCompatibility JavaVersion.VERSION_21
48
+ targetCompatibility JavaVersion.VERSION_21
49
+ }
50
+ }
51
+
52
+ repositories {
53
+ google()
54
+ mavenCentral()
55
+ }
56
+
57
+ dependencies {
58
+ api platform("androidx.compose:compose-bom:2025.10.00")
59
+ api "androidx.compose.ui:ui"
60
+ api "androidx.compose.foundation:foundation"
61
+ api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
62
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
63
+ implementation project(':capacitor-android')
64
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
65
+ testImplementation "junit:junit:$junitVersion"
66
+ testImplementation "org.json:json:20250517"
67
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
68
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
69
+ }
File without changes
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"></manifest>
@@ -0,0 +1,51 @@
1
+ package dev.rdlabo.capacitor.nativeui
2
+
3
+ import android.os.Looper
4
+ import androidx.compose.runtime.Composable
5
+ import org.json.JSONObject
6
+ import java.util.Collections
7
+
8
+ /**
9
+ * App-local Compose renderers. Register on the main thread before starting the shell.
10
+ * Experimental API: registration interfaces and behavior may change in future releases.
11
+ */
12
+ object NativeUI {
13
+ internal val renderers = linkedMapOf<String, (JSONObject, NativeUIActions, NativeUIContext) -> (@Composable () -> Unit)>()
14
+
15
+ fun <Props> register(
16
+ name: String,
17
+ decode: (JSONObject) -> Props,
18
+ content: @Composable (Props, NativeUIActions, NativeUIContext) -> Unit,
19
+ ) {
20
+ check(Looper.myLooper() == Looper.getMainLooper()) { "Register Native UI on the main thread" }
21
+ require(name.isNotBlank() && !renderers.containsKey(name)) { "Native UI names must be nonempty and unique" }
22
+ renderers[name] = { json, actions, context ->
23
+ val props = decode(json)
24
+ val view: @Composable () -> Unit = { content(props, actions, context) }
25
+ view
26
+ }
27
+ }
28
+ }
29
+
30
+ data class NativeUIFrame(val x: Double, val y: Double, val width: Double, val height: Double)
31
+
32
+ /** Source geometry, not screen coordinates. Android currently supports element placement only. */
33
+ data class NativeUIContext(
34
+ val sourceID: String,
35
+ val sourceFrame: NativeUIFrame,
36
+ val sourceFrameInDp: NativeUIFrame,
37
+ val viewportWidth: Double,
38
+ val dpPerCSSPixel: Double,
39
+ val dark: Boolean,
40
+ val rtl: Boolean,
41
+ )
42
+
43
+ /** Completion acknowledges the DOM click dispatch, not the application's asynchronous work. */
44
+ class NativeUIActions internal constructor(names: Set<String>, private val dispatch: suspend (String) -> Unit) {
45
+ val availableNames: Set<String> = Collections.unmodifiableSet(names.toSet())
46
+ fun isAvailable(name: String): Boolean = name in availableNames
47
+ suspend fun activate(name: String) {
48
+ check(isAvailable(name)) { "Native UI action is unavailable" }
49
+ dispatch(name)
50
+ }
51
+ }
@@ -0,0 +1,192 @@
1
+ package dev.rdlabo.capacitor.nativeui
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import android.widget.FrameLayout
8
+ import androidx.compose.runtime.Composable
9
+ import androidx.compose.runtime.CompositionLocalProvider
10
+ import androidx.compose.runtime.mutableStateOf
11
+ import androidx.compose.ui.platform.ComposeView
12
+ import androidx.compose.ui.platform.LocalLayoutDirection
13
+ import androidx.compose.ui.platform.ViewCompositionStrategy
14
+ import androidx.compose.ui.unit.LayoutDirection
15
+ import com.getcapacitor.JSArray
16
+ import com.getcapacitor.JSObject
17
+ import com.getcapacitor.Plugin
18
+ import com.getcapacitor.PluginCall
19
+ import com.getcapacitor.PluginMethod
20
+ import com.getcapacitor.annotation.CapacitorPlugin
21
+ import kotlinx.coroutines.Dispatchers
22
+ import kotlinx.coroutines.suspendCancellableCoroutine
23
+ import kotlinx.coroutines.withContext
24
+ import org.json.JSONObject
25
+ import java.util.UUID
26
+ import kotlin.coroutines.resume
27
+ import kotlin.coroutines.resumeWithException
28
+ import kotlin.math.roundToInt
29
+
30
+ @CapacitorPlugin(name = "NativeUI")
31
+ class NativeUIPlugin : Plugin() {
32
+ private val handler = Handler(Looper.getMainLooper())
33
+ private var host: FrameLayout? = null
34
+ private var revision = 0L
35
+ private var sequence = 0L
36
+ private var paused = false
37
+ private val entries = linkedMapOf<String, Entry>()
38
+ private val pending = mutableMapOf<String, (Boolean) -> Unit>()
39
+ private val layoutListener = View.OnLayoutChangeListener { _, l, t, r, b, oldL, oldT, oldR, oldB ->
40
+ if (l != oldL || t != oldT || r != oldR || b != oldB) {
41
+ host?.visibility = View.INVISIBLE
42
+ failActions()
43
+ notifyListeners("webViewMetricsChange", JSObject().put("radius", 0))
44
+ }
45
+ }
46
+
47
+ private class Entry(val view: ComposeView, var node: ExtensionNode) {
48
+ val content = mutableStateOf<(@Composable () -> Unit)?>(null)
49
+ }
50
+
51
+ override fun load() {
52
+ activity.runOnUiThread { bridge.webView.addOnLayoutChangeListener(layoutListener) }
53
+ }
54
+
55
+ @PluginMethod
56
+ fun configure(call: PluginCall) = onMain(call) {
57
+ clearViews()
58
+ revision = 0
59
+ sequence = 0
60
+ call.resolve(JSObject().put("supported", true).put("extensions", JSArray(NativeUI.renderers.keys.toList())))
61
+ }
62
+
63
+ @PluginMethod
64
+ fun getWebViewMetrics(call: PluginCall) = call.resolve(JSObject().put("radius", 0))
65
+
66
+ @PluginMethod
67
+ fun update(call: PluginCall) = onMain(call) {
68
+ val snapshot = NativeUISnapshot.parse(call.data)
69
+ require(snapshot.revision > revision) { "Stale Native UI snapshot" }
70
+ val web = bridge.webView
71
+ val parent = web.parent as? ViewGroup ?: error("WebView has no host")
72
+ require(web.width > 0 && web.height > 0 && web.scaleX == 1f && web.scaleY == 1f) { "WebView geometry unavailable" }
73
+ val scale = web.width / snapshot.viewportWidth
74
+ val dpScale = scale / web.resources.displayMetrics.density
75
+ val accepted = linkedMapOf<String, Pair<ExtensionNode, @Composable () -> Unit>>()
76
+ val rejected = snapshot.rejected.toMutableList()
77
+ for (node in snapshot.nodes) {
78
+ val renderer = NativeUI.renderers[node.name]
79
+ if (renderer == null || paused) {
80
+ rejected.add(node.id)
81
+ continue
82
+ }
83
+ try {
84
+ val f = node.frame
85
+ require(listOf(f.x * scale, f.y * scale, f.width * scale, f.height * scale).all { it in -1_000_000.0..1_000_000.0 })
86
+ val context = NativeUIContext(node.id, f, NativeUIFrame(f.x * dpScale, f.y * dpScale, f.width * dpScale, f.height * dpScale), snapshot.viewportWidth, dpScale, node.dark, node.rtl)
87
+ val actions = NativeUIActions(node.actions.keys) { action -> activate(node, action) }
88
+ accepted[node.id] = node to renderer(JSONObject(node.props), actions, context)
89
+ } catch (_: Exception) {
90
+ rejected.add(node.id)
91
+ }
92
+ }
93
+ revision = snapshot.revision
94
+ val container = host ?: FrameLayout(activity).also {
95
+ it.clipChildren = true
96
+ it.clipToPadding = true
97
+ parent.addView(it, ViewGroup.LayoutParams(web.width, web.height))
98
+ host = it
99
+ }
100
+ container.layoutParams = container.layoutParams.apply { width = web.width; height = web.height }
101
+ container.x = web.x
102
+ container.y = web.y
103
+ for (id in entries.keys.toList()) if (id !in accepted) removeEntry(id)
104
+ for ((id, pair) in accepted) {
105
+ val (node, content) = pair
106
+ val entry = entries[id] ?: Entry(ComposeView(activity), node).also { entry ->
107
+ entry.view.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
108
+ container.addView(entry.view)
109
+ entry.view.setContent { entry.content.value?.invoke() }
110
+ entries[id] = entry
111
+ }
112
+ entry.node = node
113
+ entry.content.value = {
114
+ CompositionLocalProvider(LocalLayoutDirection provides if (node.rtl) LayoutDirection.Rtl else LayoutDirection.Ltr) { content() }
115
+ }
116
+ entry.view.layoutParams = FrameLayout.LayoutParams((node.frame.width * scale).roundToInt().coerceAtLeast(1), (node.frame.height * scale).roundToInt().coerceAtLeast(1)).apply {
117
+ leftMargin = (node.frame.x * scale).roundToInt()
118
+ topMargin = (node.frame.y * scale).roundToInt()
119
+ }
120
+ }
121
+ container.visibility = if (paused) View.INVISIBLE else View.VISIBLE
122
+ call.resolve(JSObject().put("revision", revision).put("rejectedControls", JSArray(rejected)))
123
+ }
124
+
125
+ private suspend fun activate(node: ExtensionNode, name: String): Unit = withContext(Dispatchers.Main.immediate) {
126
+ check(!paused && host?.visibility == View.VISIBLE && entries[node.id]?.node == node) { "Native UI action is stale" }
127
+ val target = node.actions[name] ?: error("Native UI action is unavailable")
128
+ suspendCancellableCoroutine { continuation ->
129
+ val request = UUID.randomUUID().toString()
130
+ val timeout = Runnable { pending.remove(request)?.invoke(false) }
131
+ pending[request] = { accepted ->
132
+ handler.removeCallbacks(timeout)
133
+ if (continuation.isActive) {
134
+ if (accepted) continuation.resume(Unit)
135
+ else continuation.resumeWithException(IllegalStateException("Native UI action was not accepted"))
136
+ }
137
+ }
138
+ continuation.invokeOnCancellation { handler.post { pending.remove(request); handler.removeCallbacks(timeout) } }
139
+ handler.postDelayed(timeout, 5000)
140
+ notifyListeners("activate", JSObject().put("revision", revision).put("id", target).put("sequence", ++sequence).put("requestId", request))
141
+ }
142
+ }
143
+
144
+ @PluginMethod
145
+ fun completeExtensionAction(call: PluginCall) = onMain(call) {
146
+ val request = call.getString("requestId") ?: error("Missing requestId")
147
+ pending.remove(request)?.invoke(call.getBoolean("accepted", false) == true)
148
+ call.resolve()
149
+ }
150
+
151
+ @PluginMethod
152
+ fun clear(call: PluginCall) = onMain(call) {
153
+ val next = call.data.getLong("revision")
154
+ require(next > revision) { "Stale Native UI clear" }
155
+ revision = next
156
+ clearViews()
157
+ call.resolve()
158
+ }
159
+
160
+ private fun onMain(call: PluginCall, block: () -> Unit) {
161
+ activity.runOnUiThread {
162
+ try { block() } catch (error: Exception) { call.reject(error.message ?: "Native UI failed", error) }
163
+ }
164
+ }
165
+ private fun failActions() {
166
+ val callbacks = pending.values.toList()
167
+ pending.clear()
168
+ callbacks.forEach { it(false) }
169
+ }
170
+ private fun removeEntry(id: String) {
171
+ entries.remove(id)?.let { it.view.disposeComposition(); host?.removeView(it.view) }
172
+ }
173
+ private fun clearViews() {
174
+ failActions()
175
+ entries.keys.toList().forEach(::removeEntry)
176
+ (host?.parent as? ViewGroup)?.removeView(host)
177
+ host = null
178
+ }
179
+ override fun handleOnPause() {
180
+ paused = true
181
+ host?.visibility = View.INVISIBLE
182
+ failActions()
183
+ }
184
+ override fun handleOnResume() {
185
+ paused = false
186
+ notifyListeners("webViewMetricsChange", JSObject().put("radius", 0))
187
+ }
188
+ override fun handleOnDestroy() {
189
+ bridge.webView.removeOnLayoutChangeListener(layoutListener)
190
+ clearViews()
191
+ }
192
+ }
@@ -0,0 +1,57 @@
1
+ package dev.rdlabo.capacitor.nativeui
2
+
3
+ import org.json.JSONObject
4
+
5
+ internal data class ExtensionNode(
6
+ val id: String,
7
+ val frame: NativeUIFrame,
8
+ val dark: Boolean,
9
+ val rtl: Boolean,
10
+ val name: String,
11
+ val props: String,
12
+ val actions: Map<String, String>,
13
+ )
14
+
15
+ internal data class NativeUISnapshot(val revision: Long, val viewportWidth: Double, val nodes: List<ExtensionNode>, val rejected: List<String>) {
16
+ companion object {
17
+ fun parse(json: JSONObject): NativeUISnapshot {
18
+ val revisionValue = json.getDouble("revision")
19
+ require(revisionValue.isFinite() && revisionValue >= 0 && revisionValue % 1.0 == 0.0 && revisionValue <= 9007199254740991.0)
20
+ val width = json.getDouble("viewportWidth")
21
+ require(width.isFinite() && width > 0)
22
+ val controls = json.getJSONArray("controls")
23
+ require(controls.length() <= 1000)
24
+ val ids = mutableSetOf<String>()
25
+ val nodes = mutableListOf<ExtensionNode>()
26
+ val rejected = mutableListOf<String>()
27
+ for (index in 0 until controls.length()) {
28
+ val item = controls.getJSONObject(index)
29
+ val id = item.getString("id")
30
+ require(id.isNotBlank() && ids.add(id))
31
+ if (item.getString("kind") != "extension") {
32
+ rejected.add(id)
33
+ continue
34
+ }
35
+ val extension = item.getJSONObject("extension")
36
+ if (!extension.isNull("tabBarId") || item.optString("placement") == "vertical-bars") {
37
+ rejected.add(id)
38
+ continue
39
+ }
40
+ val frame = NativeUIFrame(item.getDouble("x"), item.getDouble("y"), item.getDouble("width"), item.getDouble("height"))
41
+ require(listOf(frame.x, frame.y, frame.width, frame.height).all { it.isFinite() })
42
+ require(frame.width > 0 && frame.height > 0)
43
+ val props = extension.getString("props")
44
+ require(props.length <= 65536)
45
+ JSONObject(props)
46
+ val rawActions = extension.getJSONObject("actions")
47
+ require(rawActions.length() <= 30)
48
+ val actions = rawActions.keys().asSequence().associateWith { key ->
49
+ require(key.isNotBlank())
50
+ rawActions.getString(key).also { require(it.isNotBlank()) }
51
+ }
52
+ nodes.add(ExtensionNode(id, frame, item.optBoolean("dark"), item.optBoolean("rtl"), extension.getString("name"), props, actions))
53
+ }
54
+ return NativeUISnapshot(revisionValue.toLong(), width, nodes, rejected)
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll };
package/dist/core.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { NativeUIExtension, NativeUIImageReference, NativeUIReadContext, NativeUIValue, defineNativeUI } from "./native/extensions.js";
2
+ import { AutoIonicControls, IonicProjectionOptions, NativeUIControls, NativeUIHandle, NativeUIOptions, NativeUIStatus, NativeUISuspension, NativeUIVerticalBarsOptions, ToolbarProjectionOptions } from "./native/definitions.js";
3
+ import { enableNativeUI } from "./native/startup.js";
4
+ export { type AutoIonicControls, type IonicProjectionOptions, type NativeUIControls, type NativeUIExtension, type NativeUIHandle, type NativeUIImageReference, type NativeUIOptions, type NativeUIReadContext, type NativeUIStatus, type NativeUISuspension, type NativeUIValue, type NativeUIVerticalBarsOptions, type ToolbarProjectionOptions, defineNativeUI, enableNativeUI };
package/dist/core.js ADDED
@@ -0,0 +1,3 @@
1
+ import { defineNativeUI } from "./native/extensions.js";
2
+ import { enableNativeUI } from "./native/startup.js";
3
+ export { defineNativeUI, enableNativeUI };
@@ -0,0 +1,14 @@
1
+ import { NativeUIExtension, NativeUIValue, defineNativeUI } from "./extensions.js";
2
+ import { NativeUIComponent, NativeUIControls, NativeUIPlugin, NativeUIRuntimeHandle, NativeUIRuntimeOptions, NativeUIStatus, NativeUISuspension, VerticalBarEdge, WebViewMetrics } from "./definitions.js";
3
+ //#region src/native/activation.d.ts
4
+ interface OverlayController {
5
+ suspend(): Promise<() => void>;
6
+ destroy(): Promise<void>;
7
+ }
8
+ type OverlayFactory = (doc: Document, plugin: NativeUIPlugin, options: NativeUIRuntimeOptions, retain: (deferActivation?: (id: string) => boolean) => () => void, nativeRail: () => boolean) => Promise<OverlayController>;
9
+ export declare const NativeUI: NativeUIPlugin;
10
+ export declare const configureNativeTransition: () => Promise<WebViewMetrics>;
11
+ export declare const enableNativeUI: (options?: NativeUIRuntimeOptions, overlayFactory?: OverlayFactory) => Promise<NativeUIRuntimeHandle>;
12
+ //#endregion
13
+ export { type NativeUIComponent, type NativeUIControls, type NativeUIExtension, type NativeUIRuntimeHandle, type NativeUIRuntimeOptions, type NativeUIStatus, type NativeUISuspension, type NativeUIValue, type VerticalBarEdge, type WebViewMetrics, defineNativeUI };
14
+ //# sourceMappingURL=activation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activation.d.ts","names":[],"sources":["../../src/native/activation.ts"],"mappings":";;;UASU;EACR,WAAW;EACX,WAAW;;KAER,kBACH,KAAK,UACL,QAAQ,gBACR,SAAS,wBACT,SAAS,mBAAmB,uCAC5B,8BACG,QAAQ;qBAkBA,UAAQ;qBAqGR,iCAAsC,QAAQ;qBAuB9C,iBAAkB,UAAS,wBAA6B,iBAAiB,mBAAiB,QAAQ"}