@rdlabo/capacitor-native-ui 1.2.1 → 2.0.0-2

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 (67) hide show
  1. package/README.md +39 -24
  2. package/dist/core.d.ts +2 -2
  3. package/dist/native/activation.d.ts.map +1 -1
  4. package/dist/native/activation.js +19 -19
  5. package/dist/native/activation.js.map +1 -1
  6. package/dist/native/definitions.d.ts +22 -40
  7. package/dist/native/definitions.d.ts.map +1 -1
  8. package/dist/native/html.d.ts.map +1 -1
  9. package/dist/native/html.js +10 -1
  10. package/dist/native/html.js.map +1 -1
  11. package/dist/native/index.d.ts +2 -2
  12. package/dist/native/index.d.ts.map +1 -1
  13. package/dist/native/index.js +2 -11
  14. package/dist/native/index.js.map +1 -1
  15. package/dist/native/overlays/controller.js +1 -1
  16. package/dist/native/overlays/controller.js.map +1 -1
  17. package/dist/native/prehide.js +1 -1
  18. package/dist/native/prehide.js.map +1 -1
  19. package/dist/native/runtime.d.ts +1 -1
  20. package/dist/native/runtime.d.ts.map +1 -1
  21. package/dist/native/runtime.js +8 -20
  22. package/dist/native/runtime.js.map +1 -1
  23. package/dist/native/shared/candidate.d.ts +2 -2
  24. package/dist/native/shared/candidate.d.ts.map +1 -1
  25. package/dist/native/shared/candidate.js +2 -2
  26. package/dist/native/shared/candidate.js.map +1 -1
  27. package/dist/native/shared/control-options.d.ts +6 -0
  28. package/dist/native/shared/control-options.d.ts.map +1 -0
  29. package/dist/native/shared/control-options.js +17 -0
  30. package/dist/native/shared/control-options.js.map +1 -0
  31. package/dist/native/shared/toolbar-options.d.ts +6 -2
  32. package/dist/native/shared/toolbar-options.d.ts.map +1 -1
  33. package/dist/native/shared/toolbar-options.js +7 -15
  34. package/dist/native/shared/toolbar-options.js.map +1 -1
  35. package/dist/native/startup.d.ts.map +1 -1
  36. package/dist/native/startup.js +0 -16
  37. package/dist/native/startup.js.map +1 -1
  38. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  39. package/dist/native/vertical-bars-web.js +3 -2
  40. package/dist/native/vertical-bars-web.js.map +1 -1
  41. package/dist/vertical-bars.d.ts +2 -2
  42. package/docs/appearance.md +45 -0
  43. package/docs/configuration.md +106 -74
  44. package/docs/framework-independent.md +52 -13
  45. package/docs/ionic-vertical-bars.md +186 -0
  46. package/docs/migration.md +67 -89
  47. package/docs/native-ui-extensions.md +61 -32
  48. package/docs/native-ui.md +187 -68
  49. package/docs/setup.md +46 -25
  50. package/docs/vertical-bars.md +118 -79
  51. package/ios/Sources/NativeUIPlugin/NativeUIPlugin.swift +1 -1
  52. package/package.json +1 -1
  53. package/src/core.ts +1 -2
  54. package/src/native/activation.ts +13 -22
  55. package/src/native/definitions.ts +40 -90
  56. package/src/native/html.ts +11 -5
  57. package/src/native/index.ts +2 -11
  58. package/src/native/overlays/controller.ts +1 -1
  59. package/src/native/prehide.ts +1 -1
  60. package/src/native/runtime.ts +7 -16
  61. package/src/native/shared/candidate.ts +4 -4
  62. package/src/native/shared/control-options.ts +20 -0
  63. package/src/native/shared/toolbar-options.ts +8 -12
  64. package/src/native/startup.ts +0 -16
  65. package/src/native/vertical-bars-web.ts +4 -2
  66. package/src/vertical-bars.ts +2 -1
  67. package/docs/native-mini-player.md +0 -47
@@ -6,16 +6,16 @@ title: App-local Native UI extensions (experimental)
6
6
 
7
7
  > **Experimental:** The extension API, native registration interfaces and behavior may change in future releases. This applies to both SwiftUI and Kotlin/Compose extensions, including `element` and `tab-accessory` placement.
8
8
 
9
- > 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.
10
-
11
9
  An extension projects your existing Web UI into a SwiftUI View on iOS or Jetpack Compose content on Android. Keep both definitions in your application: no npm package, separate library, or Capacitor plugin is required. The Web element remains the source of truth and the fallback UI.
12
10
 
11
+ Extensions use the same [projection model](../README.md#how-projection-works) as built-in controls. Define the DOM data and actions to expose, then provide their native presentation.
12
+
13
13
  ## Before you start
14
14
 
15
15
  Start with an existing Capacitor 8 app and install the plugin if you have not already:
16
16
 
17
17
  ```sh
18
- npm install @rdlabo/capacitor-native-ui
18
+ npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable
19
19
  ```
20
20
 
21
21
  Choose the native platform you want to support:
@@ -63,9 +63,18 @@ source.querySelector('button')!.addEventListener('click', () => {
63
63
  source.querySelector('[data-count-label]')!.textContent = `Count: ${count}`;
64
64
  });
65
65
  ```
66
- The shell observes DOM changes and sends the new props to the native content. State rendered into DOM attributes or text is observed automatically. For properties or external state read by the reader that do not mutate the DOM, call `shell.markForCheck()` after changing them.
67
66
 
68
- The reader is synchronous and read-only. Return `null` when the source should stay on the Web. Props are a JSON object (finite numbers, strings, booleans, null, arrays and objects), limited to 64K characters. Action names map to the source itself or its descendants; missing, hidden and disabled targets cannot be activated. Up to 30 actions are supported per source.
67
+ The shell observes DOM changes and sends the new props to the native content. State rendered into DOM attributes or text is observed automatically. For properties or external state read by the reader that do not mutate the DOM, call `shell.markForCheck()` after changing them.
68
+
69
+ ### Reader contract
70
+
71
+ | Part | Requirement |
72
+ | -------------- | -------------------------------------------------------------------------------------------------------------- |
73
+ | Execution | Synchronous and read-only. |
74
+ | Web fallback | Return `null` when the source should stay on the Web. |
75
+ | `props` | JSON object containing finite numbers, strings, booleans, null, arrays and objects; limited to 64K characters. |
76
+ | Action targets | The source itself or its descendants. Missing, hidden and disabled targets cannot be activated. |
77
+ | Action count | Up to 30 per source. |
69
78
 
70
79
  ## 2a. Register a SwiftUI View on iOS
71
80
 
@@ -221,14 +230,23 @@ For source-specific fallback reasons, temporarily enable [`debug: true`](configu
221
230
 
222
231
  ## Content and placement are independent
223
232
 
224
- | `placement` | Behavior |
225
- | --- | --- |
233
+ | `placement` | Behavior |
234
+ | ---------------------- | ------------------------------------------------------------------------------------- |
226
235
  | Omitted or `'element'` | Hosts native content in the source element's Web frame on iOS 26+ or Android API 24+. |
227
- | `'tab-accessory'` | Attaches the View to a projected tab bar's system SwiftUI tab accessory on iOS 26.1+. |
236
+ | `'tab-accessory'` | Attaches the View to a projected tab bar's system SwiftUI tab accessory on iOS 26.1+. |
237
+
238
+ Extensions are independent of `controls` and `ionic.auto`, including with `enableVerticalControlArea()`. The application controls their presentation: return `null` from the reader when a source should stay on the Web, then call `markForCheck()` when that condition changes. Accessories still require their supported native tab host.
228
239
 
229
- For `tab-accessory`, place the source inside its owning `ion-tabs`. The shell resolves that tabs element's `ion-tab-bar`; applications do not pass native controller references. Currently only the **Vertical Bars SwiftUI tab host** supports this placement. Ordinary UIKit tab bars, missing native tabs, iOS 26.0, or multiple competing accessories keep the source on the Web. The shell does not approximate a system accessory with a floating overlay. Keep an equivalent Web layout (for example, a fixed footer); the source retains its layout space while hidden.
240
+ ### Tab accessory requirements
230
241
 
231
- `enableVerticalControlArea({ extensions: [...] })` also accepts extensions. In this standalone mode only accessories belonging to native Vertical Bars are eligible; ordinary element projections require `enableNativeUI`.
242
+ | Requirement | Supported configuration |
243
+ | ------------------- | ------------------------------------------------------------------------------------------------- |
244
+ | Source location | Inside its owning `ion-tabs`; the shell resolves that element's `ion-tab-bar`. |
245
+ | Native host | Vertical Bars SwiftUI tab host on iOS 26.1+. |
246
+ | Accessory count | A single accessory for the host. |
247
+ | Web fallback layout | Keep an equivalent Web layout, such as a fixed footer; the source retains its space while hidden. |
248
+
249
+ Ordinary UIKit tab bars, missing native tabs, iOS 26.0 or competing accessories keep the source on the Web. Applications do not pass native controller references, and the shell does not approximate a system accessory with a floating overlay.
232
250
 
233
251
  ## Read source geometry and presentation state
234
252
 
@@ -246,16 +264,16 @@ try NativeUI.register("app.counter") {
246
264
 
247
265
  The existing `(props, actions)` closure remains supported. Both forms use the same registration names and duplicate-name checks.
248
266
 
249
- | Context property | Meaning |
250
- | --- | --- |
251
- | `sourceID` | Opaque source identity for its current lifetime; not the HTML `id`. |
252
- | `sourceFrame` | DOM `getBoundingClientRect()` in CSS pixels relative to the Web viewport, including `x`, `y`, `width`, and `height`. |
253
- | `sourceFrameInWebView` | The source rectangle converted to UIKit points in the WebView's local coordinate system; not screen coordinates. |
254
- | `viewportWidth` | CSS viewport width used for that snapshot's coordinate conversion. |
255
- | `pointsPerCSSPixel` | UIKit points per CSS pixel; not device pixel density. |
256
- | `placement` | `.element` or `.tabAccessory`. |
257
- | `colorScheme` | `.light` or `.dark`, also applied to the SwiftUI environment. |
258
- | `layoutDirection` | `.leftToRight` or `.rightToLeft`, also applied to the SwiftUI environment. |
267
+ | Context property | Meaning |
268
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
269
+ | `sourceID` | Opaque source identity for its current lifetime; not the HTML `id`. |
270
+ | `sourceFrame` | DOM `getBoundingClientRect()` in CSS pixels relative to the Web viewport, including `x`, `y`, `width`, and `height`. |
271
+ | `sourceFrameInWebView` | The source rectangle converted to UIKit points in the WebView's local coordinate system; not screen coordinates. |
272
+ | `viewportWidth` | CSS viewport width used for that snapshot's coordinate conversion. |
273
+ | `pointsPerCSSPixel` | UIKit points per CSS pixel; not device pixel density. |
274
+ | `placement` | `.element` or `.tabAccessory`. |
275
+ | `colorScheme` | `.light` or `.dark`, also applied to the SwiftUI environment. |
276
+ | `layoutDirection` | `.leftToRight` or `.rightToLeft`, also applied to the SwiftUI environment. |
259
277
 
260
278
  The closure receives a fresh context when the shell applies an updated snapshot, including source movement, resizing, and appearance changes. Use the current context rather than caching its geometry in `@State`. Updates cross the Web/native bridge; they are not a synchronous DOM measurement from Swift.
261
279
 
@@ -272,16 +290,16 @@ Do not apply `sourceFrame.x` or `y` as another SwiftUI offset: the shell already
272
290
 
273
291
  ## Use the shared projection capabilities
274
292
 
275
- | Capability | How an extension uses it |
276
- | --- | --- |
277
- | Position and size | Automatic host placement plus `NativeUIContext`; actual local bounds through `GeometryReader`. |
278
- | Dark mode and RTL | `context` or SwiftUI `@Environment(\.colorScheme)` and `@Environment(\.layoutDirection)`. |
279
- | Reduce Motion and accessibility | SwiftUI environment values such as `@Environment(\.accessibilityReduceMotion)` and native accessibility modifiers. Give custom controls accessible names. |
280
- | DOM-backed application state | Return JSON `props` from `read(element)`; DOM changes trigger synchronization. For changes without a DOM mutation, call `shell.markForCheck()`. |
281
- | Enabled/visible action targets | `actions.availableNames` or `actions.isAvailable("increment")`; unavailable DOM targets are omitted. |
282
- | Invoke the original Web action | `try await actions.activate("increment")`; completion and failure follow the contract below. |
283
- | Temporary Web fallback and teardown | Managed automatically for unsupported content, overlays, navigation suspension, and source removal. |
284
- | Application-controlled suspension | Use the shell handle's `suspend()`, the returned token's `resume()`, and `destroy()` from JavaScript. |
293
+ | Capability | How an extension uses it |
294
+ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
295
+ | Position and size | Automatic host placement plus `NativeUIContext`; actual local bounds through `GeometryReader`. |
296
+ | Dark mode and RTL | `context` or SwiftUI `@Environment(\.colorScheme)` and `@Environment(\.layoutDirection)`. |
297
+ | Reduce Motion and accessibility | SwiftUI environment values such as `@Environment(\.accessibilityReduceMotion)` and native accessibility modifiers. Give custom controls accessible names. |
298
+ | DOM-backed application state | Return JSON `props` from `read(element)`; DOM changes trigger synchronization. For changes without a DOM mutation, call `shell.markForCheck()`. |
299
+ | Enabled/visible action targets | `actions.availableNames` or `actions.isAvailable("increment")`; unavailable DOM targets are omitted. |
300
+ | Invoke the original Web action | `try await actions.activate("increment")`; completion and failure follow the contract below. |
301
+ | Temporary Web fallback and teardown | Managed automatically for unsupported content, overlays, navigation suspension, and source removal. |
302
+ | Application-controlled suspension | Use the shell handle's `suspend()`, the returned token's `resume()`, and `destroy()` from JavaScript. |
285
303
 
286
304
  For example, disable a native button when its original action is unavailable:
287
305
 
@@ -358,8 +376,19 @@ The shell owns discovery, geometry, state synchronization, native controller con
358
376
 
359
377
  Element placement requires the source to fit fully inside the viewport and its ancestors' overflow clipping bounds, including Ionic's shadow scroll containers. Partially clipped sources stay on the Web and become eligible again when scrolled fully into view. Tab accessories use the system host's placement instead.
360
378
 
361
- `try await actions.activate(name)` completes after the original Web element receives its click. It does **not** wait for application work launched by the click handler (network requests, navigation or modal presentation). Disabled, removed or stale actions reject; teardown, native task cancellation and bridge timeouts settle pending calls. The bridge acknowledgement is a Capacitor Promise method.
379
+ ### Action completion
380
+
381
+ | Event | Result of `try await actions.activate(name)` |
382
+ | ------------------------------------------------------------------ | -------------------------------------------- |
383
+ | Original Web element receives its click | Completes. |
384
+ | Handler starts a network request, navigation or modal presentation | Does not wait for that application work. |
385
+ | Action is disabled, removed or stale | Rejects. |
386
+ | Teardown, native task cancellation or bridge timeout | Settles pending calls. |
387
+
388
+ The bridge acknowledgement is a Capacitor Promise method.
362
389
 
363
390
  The [demo reader](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/native-ui/counter.ts) and [demo SwiftUI View](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/ios/App/App/NativeUICounter.swift) show both placements using app-local files. Open the Native UI demo page to compare native projection with its Web fallback.
364
391
 
365
- For a complete application feature, see the [SwiftUI mini-player example](native-mini-player.md): artwork, track metadata, playback controls and swipe-to-open, all defined in the demo application.
392
+ ## Example: mini-player
393
+
394
+ The demo mini-player shows the same pattern with track metadata and playback actions: a [DOM reader and Web fallback](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/native-ui/mini-player.ts) paired with an [app-local SwiftUI View](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/ios/App/App/NativeMiniPlayer.swift). The Web app owns playback state and modal presentation; native `open` and `toggle` actions activate its original buttons. This is a UI example, not an audio engine. Adapt the files after completing the extension setup above.