@rdlabo/capacitor-native-ui 1.2.1 → 2.0.0-beta.pr303.shaf4f5ca16816f
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.
- package/README.md +38 -26
- package/dist/native/shared/crossfade.d.ts +1 -1
- package/docs/configuration.md +50 -45
- package/docs/framework-independent.md +12 -5
- package/docs/migration.md +24 -26
- package/docs/native-ui-extensions.md +30 -27
- package/docs/native-ui.md +16 -18
- package/docs/setup.md +34 -22
- package/docs/vertical-bars.md +45 -41
- package/package.json +1 -1
- package/docs/native-mini-player.md +0 -47
package/README.md
CHANGED
|
@@ -1,43 +1,56 @@
|
|
|
1
1
|
# Capacitor Native UI
|
|
2
2
|
|
|
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
3
|
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
4
|
|
|
7
5
|
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
6
|
|
|
9
|
-
##
|
|
7
|
+
## How projection works
|
|
8
|
+
|
|
9
|
+
A **projection** is another presentation of a Web control. Your application keeps the original DOM element, state and event handlers. The plugin reads supported labels, icons, selected/disabled state and geometry, then associates the rendered control with that source element. It does not convert the page into a native screen or run a second copy of your application.
|
|
10
|
+
|
|
11
|
+
| Web source | Projected presentation |
|
|
12
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
13
|
+
| Supported Ionic buttons, tabs and segments | Native UIKit/SwiftUI controls on supported iOS |
|
|
14
|
+
| Explicitly marked HTML buttons and back buttons | Native buttons on supported iOS |
|
|
15
|
+
| Eligible controls placed in Vertical Bars | SwiftUI controls in the available system rail; Web rail presentation otherwise |
|
|
16
|
+
| An app-local extension's DOM element | The SwiftUI or Compose content registered by your app, using props read from that element |
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
The original element **stays in the DOM** while projected. Its bindings and handlers remain attached. Once native presentation is accepted, the runtime hides the source visually, prevents direct pointer interaction and hides it from Web accessibility so there are not two interactive copies. Element projection retains the source's layout box; rail spacing is supplied by your app or theme. A Web rail similarly uses its adapter's Web presentation: Ionic layout/clones or HTML proxy buttons in your `webHost`.
|
|
19
|
+
|
|
20
|
+
For example, tapping a projected Save button activates the original Web button. Its existing handler performs the save and updates application state; supported DOM changes then update the projection. Tabs and other controls likewise route actions through their source adapters. Continue updating the source, rather than managing separate native state. For reader values that change without a DOM mutation, use `markForCheck()`.
|
|
21
|
+
|
|
22
|
+
Projection changes presentation; it does not translate arbitrary HTML or CSS into native views. Native controls use supported data and styling; a custom Web appearance is not guaranteed to be reproduced. Removing or disabling a source prevents further valid actions. If projection is unavailable or stopped, the runtime releases its hiding state and the source returns to Web presentation, or to an available Web rail. Your application's own hidden/disabled state still applies.
|
|
23
|
+
|
|
24
|
+
## Choose what to build
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
| Goal | Start here |
|
|
27
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| Render existing Ionic controls natively | [Ionic setup](docs/setup.md) |
|
|
29
|
+
| Use native buttons without Ionic | [HTML setup](docs/framework-independent.md) |
|
|
30
|
+
| Add a side rail to Ionic while keeping your theme | [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) |
|
|
31
|
+
| Add a side rail without Ionic | [HTML rail setup](docs/framework-independent.md) |
|
|
32
|
+
| 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) |
|
|
33
|
+
| Render custom SwiftUI or Android Compose content | [App-local extensions (experimental)](docs/native-ui-extensions.md) |
|
|
20
34
|
|
|
21
|
-
|
|
22
|
-
2. Choose the packages for your goal. Replace `VERSION` and the paths with the downloaded filenames:
|
|
35
|
+
## Install from npm
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
Install in your Capacitor 8 application:
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
```sh
|
|
40
|
+
npm install @rdlabo/capacitor-native-ui
|
|
41
|
+
```
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
If you also use the iOS 27 theme, install or update both packages together:
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
```sh
|
|
46
|
+
npm install @rdlabo/capacitor-native-ui @rdlabo/ionic-theme-ios27
|
|
47
|
+
```
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
Both packages share the same release version. Declare Native UI as a direct dependency so Capacitor can discover the plugin. Theme-only apps can install just the theme; its dependency is resolved by npm and does not activate native controls.
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
Continue with [Ionic setup](docs/setup.md), [HTML setup](docs/framework-independent.md), or [extensions](docs/native-ui-extensions.md). After configuring startup, build your application's Web assets, run `npx cap sync` and rebuild the native app. No repository checkout or archive download is needed.
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
Upgrading from the native APIs bundled in theme 1.2.0 or 1.2.1? Follow the [migration guide](docs/migration.md) for import, configuration and native project changes.
|
|
41
54
|
|
|
42
55
|
## Requirements
|
|
43
56
|
|
|
@@ -45,10 +58,9 @@ Native startup requires Capacitor 8 and an iOS or Android project. Android exten
|
|
|
45
58
|
|
|
46
59
|
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
60
|
|
|
48
|
-
## Reference
|
|
61
|
+
## Reference
|
|
49
62
|
|
|
50
63
|
- [Supported Ionic controls](docs/native-ui.md#supported-markup) and fallback behavior.
|
|
51
64
|
- [Configuration](docs/configuration.md): defaults, selected controls, rail-only mode, and live changes.
|
|
52
65
|
- [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
66
|
- [Migration](docs/migration.md): package names, imports, and native project updates.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export declare const handoffDuration = 180;
|
|
3
3
|
export declare const fadeMarker = "data-native-ui-fading";
|
|
4
4
|
export declare const createCrossfade: (win: Window) => {
|
|
5
|
-
duration: (instant?: boolean) =>
|
|
5
|
+
duration: (instant?: boolean) => 180 | 0;
|
|
6
6
|
play(element: HTMLElement, toNative: boolean, instant?: boolean): void;
|
|
7
7
|
retire(element: HTMLElement, instant?: boolean): void;
|
|
8
8
|
settled(): Promise<void>;
|
package/docs/configuration.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Configure Native UI
|
|
2
2
|
|
|
3
|
+
This is the settings reference. Start with [how projection works](../README.md#how-projection-works), then [Ionic setup](setup.md) or [HTML setup](framework-independent.md) for installation.
|
|
4
|
+
|
|
3
5
|
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
6
|
|
|
5
7
|
```ts
|
|
@@ -12,13 +14,13 @@ Without Ionic, import the same API from `@rdlabo/capacitor-native-ui/core`. That
|
|
|
12
14
|
|
|
13
15
|
## Platform behavior
|
|
14
16
|
|
|
15
|
-
| Feature
|
|
16
|
-
|
|
|
17
|
-
| Automatic Ionic controls (`ionic.auto`) | Native
|
|
18
|
-
| Explicit element buttons
|
|
19
|
-
| Element extensions
|
|
20
|
-
| Vertical Bars
|
|
21
|
-
| `tab-accessory` extensions
|
|
17
|
+
| Feature | Supported iOS | Android | Browser / older iOS |
|
|
18
|
+
| --------------------------------------- | ----------------------------------------- | --------------------- | --------------------- |
|
|
19
|
+
| Automatic Ionic controls (`ionic.auto`) | Native | Original Web controls | Original Web controls |
|
|
20
|
+
| Explicit element buttons | Native | Original Web controls | Original Web controls |
|
|
21
|
+
| Element extensions | SwiftUI | Kotlin / Compose | Original Web content |
|
|
22
|
+
| Vertical Bars | Native when available; otherwise Web rail | Web rail | Web rail |
|
|
23
|
+
| `tab-accessory` extensions | Supported iOS tab host only | Original Web content | Original Web content |
|
|
22
24
|
|
|
23
25
|
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
26
|
|
|
@@ -52,23 +54,23 @@ const shell = await enableNativeUI({
|
|
|
52
54
|
});
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
| Setting
|
|
56
|
-
|
|
|
57
|
-
| `ionic.auto`
|
|
58
|
-
| `horizontal` / `vertical`
|
|
59
|
-
| `debug`
|
|
60
|
-
| `root`
|
|
61
|
-
| `ionic.auto.buttons`
|
|
62
|
-
| `ionic.auto.tabs`
|
|
63
|
-
| `ionic.auto.segment`
|
|
64
|
-
| `ionic.auto.fab`
|
|
65
|
-
| `verticalBars.enabled`
|
|
66
|
-
| `verticalBars.only`
|
|
67
|
-
| `verticalBars.buttonProjection`
|
|
68
|
-
| `verticalBars.buttonDefaultFill` | `'glass'`
|
|
69
|
-
| `verticalBars.popover`
|
|
70
|
-
| `verticalBars.placement`
|
|
71
|
-
| `extensions`
|
|
57
|
+
| Setting | Default | Meaning |
|
|
58
|
+
| -------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `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. |
|
|
60
|
+
| `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). |
|
|
61
|
+
| `debug` | `false` | Log source elements and Web fallback reasons to the developer console; no props or application exception messages. |
|
|
62
|
+
| `root` | `document.body` | Connected source subtree. Controls outside it are not projected. |
|
|
63
|
+
| `ionic.auto.buttons` | `true` | Automatic Ionic toolbar/back/menu controls. Explicitly marked buttons are independent. |
|
|
64
|
+
| `ionic.auto.tabs` | `true` | Ionic tabs and their registered native search. |
|
|
65
|
+
| `ionic.auto.segment` | `true` | Supported Ionic segments. |
|
|
66
|
+
| `ionic.auto.fab` | `true` | Supported Ionic floating action buttons. |
|
|
67
|
+
| `verticalBars.enabled` | `true` | Allow rail projection when available. Device capability is always checked; enabling cannot create a system rail. |
|
|
68
|
+
| `verticalBars.only` | `false` | Restrict native projection to rail controls. Other controls stay Web. |
|
|
69
|
+
| `verticalBars.buttonProjection` | `'system'` | `'system'` uses native styling; `'source'` uses supported source styling. Local overrides take precedence. |
|
|
70
|
+
| `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. |
|
|
71
|
+
| `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. |
|
|
72
|
+
| `verticalBars.placement` | omitted | Optional `{ edge: 'leading' \| 'trailing', webHost?: HTMLElement }`. Edge is a preference, not a device capability declaration. |
|
|
73
|
+
| `extensions` | `[]` | **Experimental.** Application-specific SwiftUI / Compose extensions, paired with native registrations. API and behavior may change. |
|
|
72
74
|
|
|
73
75
|
`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
76
|
|
|
@@ -91,12 +93,12 @@ const tabsOnly = await enableNativeUI({
|
|
|
91
93
|
|
|
92
94
|
These are alternatives: start only one shell. Repeating the same startup returns its handle; different startup settings require destroying the previous shell.
|
|
93
95
|
|
|
94
|
-
| Rail setting
|
|
95
|
-
|
|
|
96
|
-
| `enabled: true, only: false`
|
|
97
|
-
| `enabled: true, only: true`
|
|
98
|
-
| `enabled: false, only: false` | Ordinary native controls; rail controls remain Web | Same
|
|
99
|
-
| `enabled: false, only: true`
|
|
96
|
+
| Rail setting | Rail available | Rail unavailable |
|
|
97
|
+
| ----------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
98
|
+
| `enabled: true, only: false` | Eligible rail and ordinary native controls | Ordinary native controls; unavailable rail controls retain Web fallback |
|
|
99
|
+
| `enabled: true, only: true` | Eligible rail controls only | Web fallback |
|
|
100
|
+
| `enabled: false, only: false` | Ordinary native controls; rail controls remain Web | Same |
|
|
101
|
+
| `enabled: false, only: true` | All controls remain Web | Same |
|
|
100
102
|
|
|
101
103
|
`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
104
|
|
|
@@ -108,10 +110,13 @@ Disable automatic Ionic projection while retaining the native rail and your cust
|
|
|
108
110
|
const shell = await enableNativeUI({
|
|
109
111
|
root: document.querySelector<HTMLElement>('#content')!,
|
|
110
112
|
ionic: { auto: false },
|
|
111
|
-
verticalBars: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
verticalBars: {
|
|
114
|
+
enabled: true,
|
|
115
|
+
placement: {
|
|
116
|
+
edge: 'trailing',
|
|
117
|
+
webHost: document.querySelector<HTMLElement>('#rail')!,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
115
120
|
extensions: [myExtension],
|
|
116
121
|
});
|
|
117
122
|
```
|
|
@@ -143,7 +148,7 @@ These methods change `verticalBars.enabled` on this shell and preserve `only`, a
|
|
|
143
148
|
```ts
|
|
144
149
|
const lease = await shell.suspend(); // temporarily restore all Web controls
|
|
145
150
|
await lease.resume();
|
|
146
|
-
await shell.destroy();
|
|
151
|
+
await shell.destroy(); // stop the shell and restore sources
|
|
147
152
|
```
|
|
148
153
|
|
|
149
154
|
Disabling rail projection does not remove application CSS or change application-owned spacing. Update your own layout alongside this setting if needed.
|
|
@@ -167,16 +172,16 @@ console.info(shell.getStatus());
|
|
|
167
172
|
|
|
168
173
|
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
174
|
|
|
170
|
-
| Diagnostic
|
|
171
|
-
|
|
|
172
|
-
| `web-fallback`
|
|
173
|
-
| `unregistered-extension`
|
|
174
|
-
| `reader-error` / `invalid-props`
|
|
175
|
-
| `unsupported-placement` / `missing-tab-owner`
|
|
176
|
-
| `source-hidden` / `source-clipped` / `source-focused` | Visibility, clipping/transformed ancestors, or a focused Web descendant.
|
|
177
|
-
| `reader-opt-out` / `rail-only`
|
|
178
|
-
| `unsupported-control`
|
|
179
|
-
| `native-rejected`
|
|
175
|
+
| Diagnostic | What to check |
|
|
176
|
+
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| `web-fallback` | Platform support, native plugin registration, sync and native rebuild. |
|
|
178
|
+
| `unregistered-extension` | Native registration name matches the DOM definition and runs before startup. Newly inserted sources are checked too. |
|
|
179
|
+
| `reader-error` / `invalid-props` | Reader implementation, JSON-only values and the 64K size limit. |
|
|
180
|
+
| `unsupported-placement` / `missing-tab-owner` | Platform and owning native tab host for a tab accessory. |
|
|
181
|
+
| `source-hidden` / `source-clipped` / `source-focused` | Visibility, clipping/transformed ancestors, or a focused Web descendant. |
|
|
182
|
+
| `reader-opt-out` / `rail-only` | Your reader returned null, or `verticalBars.only` excludes an element extension. |
|
|
183
|
+
| `unsupported-control` | Supported markup, accessible name, fixed placement and styling. |
|
|
184
|
+
| `native-rejected` | Native props decoding, placement or device support. The bridge reports rejection, not the native exception. |
|
|
180
185
|
|
|
181
186
|
`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
187
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Native UI without Ionic
|
|
2
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
3
|
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
4
|
|
|
7
5
|
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
6
|
|
|
7
|
+
Start with [how projection works](../README.md#how-projection-works): the HTML element remains the source while native controls or Web proxies provide its presentation.
|
|
8
|
+
|
|
9
|
+
**Want only Vertical Bars?** Follow steps 1–2 to install and mark your HTML buttons, then use the [Web rail configuration](#optional-web-rail) in place of step 3. Style the rail host in your application and build with step 4 for iOS. You do not need Ionic, `@rdlabo/ionic-theme-ios27`, or `vertical-bars.css`.
|
|
10
|
+
|
|
11
|
+
For Vertical Bars in an app using `ion-tabs`, `ion-toolbar` or other Ionic components with a custom theme, use the [Ionic Vertical Bars setup](../../theme-ios27/docs/iphone-duo-with-original-theme.md#try-it-in-your-existing-ionic-app) instead. The framework (Angular, React or Vue) alone does not determine which adapter to use.
|
|
12
|
+
|
|
9
13
|
## 1. Install the plugin
|
|
10
14
|
|
|
11
15
|
Keep your existing Capacitor 8 installation and add the plugin directly:
|
|
@@ -49,7 +53,7 @@ Call once after the source root exists. Keep the handle and call `await shell.de
|
|
|
49
53
|
|
|
50
54
|
## 4. Build and run on iOS
|
|
51
55
|
|
|
52
|
-
Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode.
|
|
56
|
+
Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. With the step 3 configuration, Add renders natively and invokes its original click handler. With the rail-only configuration below, Add stays on the Web and Back uses the native rail when available. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged.
|
|
53
57
|
|
|
54
58
|
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
59
|
|
|
@@ -57,12 +61,15 @@ In an app that also uses Ionic, set `ionic: { auto: false }` to project only exp
|
|
|
57
61
|
|
|
58
62
|
## Optional Web rail
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
For Vertical Bars only, use this instead of the startup call in step 3. Call once after both the source root and rail host are mounted; call `await shell.destroy()` when their owner is disposed. Do not start a second shell:
|
|
61
65
|
|
|
62
66
|
```ts
|
|
67
|
+
import { enableNativeUI } from '@rdlabo/capacitor-native-ui/core';
|
|
68
|
+
|
|
63
69
|
const shell = await enableNativeUI({
|
|
64
70
|
root: document.querySelector<HTMLElement>('#app')!,
|
|
65
71
|
verticalBars: {
|
|
72
|
+
only: true,
|
|
66
73
|
placement: {
|
|
67
74
|
edge: 'trailing',
|
|
68
75
|
webHost: document.querySelector<HTMLElement>('#rail')!,
|
|
@@ -73,7 +80,7 @@ const shell = await enableNativeUI({
|
|
|
73
80
|
|
|
74
81
|
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
82
|
|
|
76
|
-
|
|
83
|
+
This configuration projects only rail controls: in the example above, Back goes to the rail and Add remains a Web button. Set `verticalBars.only: false` if you also want ordinary element buttons rendered natively. 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
84
|
|
|
78
85
|
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
86
|
|
package/docs/migration.md
CHANGED
|
@@ -4,11 +4,9 @@ title: Migration
|
|
|
4
4
|
|
|
5
5
|
# Migration
|
|
6
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
7
|
## Migrating from Native UI Shell Preview
|
|
10
8
|
|
|
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.
|
|
9
|
+
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. Users upgrading from an earlier prerelease should also compare their configuration with these mappings and rebuild native code with the installed package version.
|
|
12
10
|
|
|
13
11
|
1. Install the matching packages and replace removed imports using [package separation](#native-ui-package-separation).
|
|
14
12
|
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.
|
|
@@ -23,11 +21,11 @@ For a new native integration, follow [Native UI setup](setup.md) or, if you alre
|
|
|
23
21
|
|
|
24
22
|
The theme and native controls now live in separate packages. Choose dependencies by the behavior your app uses:
|
|
25
23
|
|
|
26
|
-
| App behavior
|
|
27
|
-
|
|
|
28
|
-
| Theme styles, Ionic Web behavior, and theme animations | `@rdlabo/ionic-theme-ios27`
|
|
29
|
-
| Native UI or standalone Vertical Control Area
|
|
30
|
-
| Theme styling together with native controls
|
|
24
|
+
| App behavior | Package to use directly |
|
|
25
|
+
| ------------------------------------------------------ | ----------------------------- |
|
|
26
|
+
| Theme styles, Ionic Web behavior, and theme animations | `@rdlabo/ionic-theme-ios27` |
|
|
27
|
+
| Native UI or standalone Vertical Control Area | `@rdlabo/capacitor-native-ui` |
|
|
28
|
+
| Theme styling together with native controls | Both packages |
|
|
31
29
|
|
|
32
30
|
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
31
|
|
|
@@ -35,7 +33,7 @@ The theme uses the native package's shared transition/search integration interna
|
|
|
35
33
|
|
|
36
34
|
### 1. Update application dependencies
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
When using both packages, update them together to keep their versions aligned.
|
|
39
37
|
|
|
40
38
|
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
39
|
|
|
@@ -60,13 +58,13 @@ npm install @rdlabo/capacitor-native-ui
|
|
|
60
58
|
|
|
61
59
|
### 2. Replace native imports
|
|
62
60
|
|
|
63
|
-
| Previous import
|
|
64
|
-
|
|
|
65
|
-
| `@rdlabo/ionic-theme-ios27/native`
|
|
66
|
-
| `@rdlabo/ionic-theme-ios27/vertical-bars`
|
|
67
|
-
| `@ionic-theme/capacitor-native-ui`
|
|
61
|
+
| Previous import | Replacement |
|
|
62
|
+
| ------------------------------------------------ | ------------------------------------------- |
|
|
63
|
+
| `@rdlabo/ionic-theme-ios27/native` | `@rdlabo/capacitor-native-ui` |
|
|
64
|
+
| `@rdlabo/ionic-theme-ios27/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
|
|
65
|
+
| `@ionic-theme/capacitor-native-ui` | `@rdlabo/capacitor-native-ui` |
|
|
68
66
|
| `@ionic-theme/capacitor-native-ui/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
|
|
69
|
-
| `@ionic-theme/capacitor-native-ui/integration`
|
|
67
|
+
| `@ionic-theme/capacitor-native-ui/integration` | `@rdlabo/capacitor-native-ui/integration` |
|
|
70
68
|
|
|
71
69
|
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
70
|
|
|
@@ -87,7 +85,7 @@ npx cap sync ios
|
|
|
87
85
|
|
|
88
86
|
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
87
|
|
|
90
|
-
|
|
88
|
+
Use Capacitor's generated integration for the npm dependency; applications do not need a checkout of this repository or a manually linked Swift package.
|
|
91
89
|
|
|
92
90
|
### 4. Verify the migration
|
|
93
91
|
|
|
@@ -102,17 +100,17 @@ For local Swift package references, the package now lives at `packages/capacitor
|
|
|
102
100
|
|
|
103
101
|
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
102
|
|
|
105
|
-
| Previous name
|
|
106
|
-
|
|
|
107
|
-
| `enableNativeUIShell()`
|
|
103
|
+
| Previous name | New name |
|
|
104
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `enableNativeUIShell()` | `enableNativeUI()` |
|
|
108
106
|
| `NativeUIShellOptions`, `NativeUIShellHandle`, `NativeUIShellStatus`, `NativeUIShellSuspension`, `NativeUIShellControls`, `NativeUIShellComponent` | `NativeUIOptions`, `NativeUIHandle`, `NativeUIStatus`, `NativeUISuspension`, `NativeUIControls`, `NativeUIComponent` |
|
|
109
|
-
| `IonicNativeUIShell` (plugin export and bridge registration)
|
|
110
|
-
| `withNativeUIShellTransition()`
|
|
111
|
-
| `IonicNativeUIShellPlugin` (Swift module/native class)
|
|
112
|
-
| `IonicThemeCapacitorNativeUi` (SPM package/product and pod)
|
|
113
|
-
| `NativeUIShell.register()` (Swift / Kotlin extensions)
|
|
114
|
-
| `data-shell="disabled"`
|
|
115
|
-
| `.ios-theme-shell-disabled`
|
|
107
|
+
| `IonicNativeUIShell` (plugin export and bridge registration) | `NativeUI` |
|
|
108
|
+
| `withNativeUIShellTransition()` | `withNativeUITransition()` (still exported by the theme package) |
|
|
109
|
+
| `IonicNativeUIShellPlugin` (Swift module/native class) | `NativeUIPlugin` |
|
|
110
|
+
| `IonicThemeCapacitorNativeUi` (SPM package/product and pod) | `RdlaboCapacitorNativeUi` |
|
|
111
|
+
| `NativeUIShell.register()` (Swift / Kotlin extensions) | `NativeUI.register()` |
|
|
112
|
+
| `data-shell="disabled"` | `data-native-ui="disabled"` |
|
|
113
|
+
| `.ios-theme-shell-disabled` | `.native-ui-disabled` |
|
|
116
114
|
|
|
117
115
|
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
116
|
|
|
@@ -6,10 +6,10 @@ 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:
|
|
@@ -63,7 +63,8 @@ source.querySelector('button')!.addEventListener('click', () => {
|
|
|
63
63
|
source.querySelector('[data-count-label]')!.textContent = `Count: ${count}`;
|
|
64
64
|
});
|
|
65
65
|
```
|
|
66
|
-
|
|
66
|
+
|
|
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.
|
|
67
68
|
|
|
68
69
|
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.
|
|
69
70
|
|
|
@@ -221,10 +222,10 @@ For source-specific fallback reasons, temporarily enable [`debug: true`](configu
|
|
|
221
222
|
|
|
222
223
|
## Content and placement are independent
|
|
223
224
|
|
|
224
|
-
| `placement`
|
|
225
|
-
|
|
|
225
|
+
| `placement` | Behavior |
|
|
226
|
+
| ---------------------- | ------------------------------------------------------------------------------------- |
|
|
226
227
|
| Omitted or `'element'` | Hosts native content in the source element's Web frame on iOS 26+ or Android API 24+. |
|
|
227
|
-
| `'tab-accessory'`
|
|
228
|
+
| `'tab-accessory'` | Attaches the View to a projected tab bar's system SwiftUI tab accessory on iOS 26.1+. |
|
|
228
229
|
|
|
229
230
|
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.
|
|
230
231
|
|
|
@@ -246,16 +247,16 @@ try NativeUI.register("app.counter") {
|
|
|
246
247
|
|
|
247
248
|
The existing `(props, actions)` closure remains supported. Both forms use the same registration names and duplicate-name checks.
|
|
248
249
|
|
|
249
|
-
| Context property
|
|
250
|
-
|
|
|
251
|
-
| `sourceID`
|
|
252
|
-
| `sourceFrame`
|
|
253
|
-
| `sourceFrameInWebView` | The source rectangle converted to UIKit points in the WebView's local coordinate system; not screen coordinates.
|
|
254
|
-
| `viewportWidth`
|
|
255
|
-
| `pointsPerCSSPixel`
|
|
256
|
-
| `placement`
|
|
257
|
-
| `colorScheme`
|
|
258
|
-
| `layoutDirection`
|
|
250
|
+
| Context property | Meaning |
|
|
251
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
252
|
+
| `sourceID` | Opaque source identity for its current lifetime; not the HTML `id`. |
|
|
253
|
+
| `sourceFrame` | DOM `getBoundingClientRect()` in CSS pixels relative to the Web viewport, including `x`, `y`, `width`, and `height`. |
|
|
254
|
+
| `sourceFrameInWebView` | The source rectangle converted to UIKit points in the WebView's local coordinate system; not screen coordinates. |
|
|
255
|
+
| `viewportWidth` | CSS viewport width used for that snapshot's coordinate conversion. |
|
|
256
|
+
| `pointsPerCSSPixel` | UIKit points per CSS pixel; not device pixel density. |
|
|
257
|
+
| `placement` | `.element` or `.tabAccessory`. |
|
|
258
|
+
| `colorScheme` | `.light` or `.dark`, also applied to the SwiftUI environment. |
|
|
259
|
+
| `layoutDirection` | `.leftToRight` or `.rightToLeft`, also applied to the SwiftUI environment. |
|
|
259
260
|
|
|
260
261
|
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
262
|
|
|
@@ -272,16 +273,16 @@ Do not apply `sourceFrame.x` or `y` as another SwiftUI offset: the shell already
|
|
|
272
273
|
|
|
273
274
|
## Use the shared projection capabilities
|
|
274
275
|
|
|
275
|
-
| Capability
|
|
276
|
-
|
|
|
277
|
-
| Position and size
|
|
278
|
-
| Dark mode and RTL
|
|
279
|
-
| Reduce Motion and accessibility
|
|
280
|
-
| DOM-backed application state
|
|
281
|
-
| Enabled/visible action targets
|
|
282
|
-
| Invoke the original Web action
|
|
283
|
-
| Temporary Web fallback and teardown | Managed automatically for unsupported content, overlays, navigation suspension, and source removal.
|
|
284
|
-
| Application-controlled suspension
|
|
276
|
+
| Capability | How an extension uses it |
|
|
277
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
278
|
+
| Position and size | Automatic host placement plus `NativeUIContext`; actual local bounds through `GeometryReader`. |
|
|
279
|
+
| Dark mode and RTL | `context` or SwiftUI `@Environment(\.colorScheme)` and `@Environment(\.layoutDirection)`. |
|
|
280
|
+
| Reduce Motion and accessibility | SwiftUI environment values such as `@Environment(\.accessibilityReduceMotion)` and native accessibility modifiers. Give custom controls accessible names. |
|
|
281
|
+
| DOM-backed application state | Return JSON `props` from `read(element)`; DOM changes trigger synchronization. For changes without a DOM mutation, call `shell.markForCheck()`. |
|
|
282
|
+
| Enabled/visible action targets | `actions.availableNames` or `actions.isAvailable("increment")`; unavailable DOM targets are omitted. |
|
|
283
|
+
| Invoke the original Web action | `try await actions.activate("increment")`; completion and failure follow the contract below. |
|
|
284
|
+
| Temporary Web fallback and teardown | Managed automatically for unsupported content, overlays, navigation suspension, and source removal. |
|
|
285
|
+
| Application-controlled suspension | Use the shell handle's `suspend()`, the returned token's `resume()`, and `destroy()` from JavaScript. |
|
|
285
286
|
|
|
286
287
|
For example, disable a native button when its original action is unavailable:
|
|
287
288
|
|
|
@@ -362,4 +363,6 @@ Element placement requires the source to fit fully inside the viewport and its a
|
|
|
362
363
|
|
|
363
364
|
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
365
|
|
|
365
|
-
|
|
366
|
+
## Example: mini-player
|
|
367
|
+
|
|
368
|
+
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.
|
package/docs/native-ui.md
CHANGED
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
title: Native UI (Preview)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
For application-specific SwiftUI or Compose content, see [experimental App-local Native UI extensions](native-ui-extensions.md).
|
|
6
|
-
|
|
7
5
|
# Native UI (Preview)
|
|
8
6
|
|
|
9
7
|
Native UI is a **preview**. The earlier Native UI Shell was bundled with theme versions 1.2.0 and 1.2.1; this guide describes the split package and its renamed API. See [migration](migration.md#native-ui-naming-changes). Its API and supported controls may change before stable status. Stable status is planned after the official release of Xcode 27.1.
|
|
10
8
|
|
|
11
9
|
Native UI gives an Ionic app native navigation and action controls around its Web content. The optional Capacitor iOS plugin renders supported fixed Ionic controls with UIKit or SwiftUI and the system's Liquid Glass material. Page content, scrolling, application state and routing remain in Ionic's WebView.
|
|
12
10
|
|
|
13
|
-
Use this page as the supported-control and API reference. For first-time installation and a working example, start with [Ionic setup](setup.md) or [HTML setup](framework-independent.md).
|
|
11
|
+
Read [how projection works](../README.md#how-projection-works) for the shared DOM and native presentation model. Use this page as the supported-control and API reference. For first-time installation and a working example, start with [Ionic setup](setup.md) or [HTML setup](framework-independent.md).
|
|
14
12
|
|
|
15
13
|
## Enable Native UI
|
|
16
14
|
|
|
@@ -58,11 +56,11 @@ const shell = await enableNativeUI({
|
|
|
58
56
|
|
|
59
57
|
Each layout accepts `boolean | ToolbarProjectionOptions`:
|
|
60
58
|
|
|
61
|
-
| Value
|
|
62
|
-
|
|
|
63
|
-
| `true` or omitted | All three options default to `true`.
|
|
64
|
-
| `false`
|
|
65
|
-
| Object
|
|
59
|
+
| Value | Meaning |
|
|
60
|
+
| ----------------- | ----------------------------------------------------------------- |
|
|
61
|
+
| `true` or omitted | All three options default to `true`. |
|
|
62
|
+
| `false` | All three options are `false`; local attributes may still opt in. |
|
|
63
|
+
| Object | Set individual options; omitted fields default to `true`. |
|
|
66
64
|
|
|
67
65
|
For toolbar actions that automatically project only in vertical layout:
|
|
68
66
|
|
|
@@ -118,7 +116,7 @@ The table below describes the ordinary Native UI. Vertical Bars uses the separat
|
|
|
118
116
|
|
|
119
117
|
| Ionic component | Supported appearance and placement | Native rendering |
|
|
120
118
|
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
121
|
-
| `ion-button` | `fill="default"`, or clear inside an ungrouped `ion-buttons`, in a fixed header/footer toolbar
|
|
119
|
+
| `ion-button` | `fill="default"`, or clear inside an ungrouped `ion-buttons`, in a fixed header/footer toolbar | Glass `UIButton` |
|
|
122
120
|
| `ion-buttons` | Fixed toolbar, two or more direct clear `ion-button` / `ion-menu-button` children sharing the theme glass | One `UIGlassEffect` surface with independent native buttons |
|
|
123
121
|
| `ion-back-button` | Standard icon and color in a fixed header/footer toolbar | Glass `UIButton`, using the resolved Ionic label/icon |
|
|
124
122
|
| `ion-menu-button` | Fixed toolbar, inside the theme glass `ion-buttons` | Glass `UIButton`; original Ionic menu toggle |
|
|
@@ -204,15 +202,15 @@ Tab switches skip the Web/native crossfade so a retiring UIKit snapshot cannot l
|
|
|
204
202
|
|
|
205
203
|
Standard Ionic overlays suspend ordinary native projection until dismissal. Vertical Bars use a separate policy:
|
|
206
204
|
|
|
207
|
-
| Surface or condition
|
|
208
|
-
|
|
|
209
|
-
| Web toast, ordinary popover or legacy picker overlay
|
|
210
|
-
| Popover opened from a Vertical Bars button, rendered on Web
|
|
211
|
-
| Alert, action sheet or loading
|
|
212
|
-
| Menu
|
|
213
|
-
| Full-width foreground modal
|
|
214
|
-
| Modal that cannot use Vertical Bars
|
|
215
|
-
| Explicit suspension, unavailable native layout, hidden document or unsupported keyboard/viewport geometry | Preserve the existing fallback behavior.
|
|
205
|
+
| Surface or condition | Vertical Bars behavior |
|
|
206
|
+
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
207
|
+
| Web toast, ordinary popover or legacy picker overlay | Keep the native rail visible, updated and interactive. |
|
|
208
|
+
| Popover opened from a Vertical Bars button, rendered on Web | Return the rail to Web projection until dismissal, including native rejection or preparation failure. |
|
|
209
|
+
| Alert, action sheet or loading | Restore Web rendering so the overlay and its dimming backdrop cover the controls correctly. |
|
|
210
|
+
| Menu | Keep the existing native rail behavior. |
|
|
211
|
+
| Full-width foreground modal | Project the modal's controls instead of the covered page's controls. |
|
|
212
|
+
| Modal that cannot use Vertical Bars | Restore Web rendering. |
|
|
213
|
+
| Explicit suspension, unavailable native layout, hidden document or unsupported keyboard/viewport geometry | Preserve the existing fallback behavior. |
|
|
216
214
|
|
|
217
215
|
Native projection resumes only after every overlay requiring Web rendering closes. The opt-in native Vertical Bars popover described below keeps its own SwiftUI presentation and input handling. Unsupported searchable-tab configurations use the existing Web animation and share this ownership with Web glass gestures. CSS motion of supported containing surfaces also causes temporary Web rendering.
|
|
218
216
|
|
package/docs/setup.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Set up Native UI in an Ionic app
|
|
2
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
3
|
Keep your existing Web appearance and render supported Ionic controls natively on iOS. This guide does not require iOS theme stylesheets.
|
|
6
4
|
|
|
7
5
|
Already using the iOS 27 theme? Follow [Add Native UI to your theme](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/add-native-ui.md). Without Ionic, follow the [HTML setup](framework-independent.md) instead.
|
|
8
6
|
|
|
7
|
+
Read [how projection works](../README.md#how-projection-works) first if you are new to Native UI. This guide connects your existing Ionic controls to that presentation.
|
|
8
|
+
|
|
9
9
|
## Before you start
|
|
10
10
|
|
|
11
11
|
Use an existing Ionic `>=8.8.1 <10` application with Capacitor 8 and an iOS project. Build with Xcode 26 or later; ordinary native controls require iOS 26 or later. Vertical Bars have separate [device requirements](vertical-bars.md). Keep your current SPM or CocoaPods integration.
|
|
@@ -66,17 +66,21 @@ const report = (error: unknown) => console.error('Native UI lifecycle failed', e
|
|
|
66
66
|
export function mountNativeUI(): () => void {
|
|
67
67
|
let disposed = false;
|
|
68
68
|
let shell: NativeUIHandle | undefined;
|
|
69
|
-
lifecycle = lifecycle
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
lifecycle = lifecycle
|
|
70
|
+
.then(async () => {
|
|
71
|
+
if (disposed) return;
|
|
72
|
+
shell = await enableNativeUI(); // Put your options here.
|
|
73
|
+
if (disposed) await shell.destroy();
|
|
74
|
+
})
|
|
75
|
+
.catch(report);
|
|
74
76
|
return () => {
|
|
75
77
|
disposed = true;
|
|
76
|
-
lifecycle = lifecycle
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
lifecycle = lifecycle
|
|
79
|
+
.then(async () => {
|
|
80
|
+
await shell?.destroy();
|
|
81
|
+
shell = undefined;
|
|
82
|
+
})
|
|
83
|
+
.catch(report);
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
```
|
|
@@ -114,18 +118,22 @@ import { onMounted, onBeforeUnmount } from 'vue';
|
|
|
114
118
|
import { mountNativeUI } from './native-ui';
|
|
115
119
|
|
|
116
120
|
let stop: (() => void) | undefined;
|
|
117
|
-
onMounted(() => {
|
|
118
|
-
|
|
121
|
+
onMounted(() => {
|
|
122
|
+
stop = mountNativeUI();
|
|
123
|
+
});
|
|
124
|
+
onBeforeUnmount(() => {
|
|
125
|
+
stop?.();
|
|
126
|
+
});
|
|
119
127
|
```
|
|
120
128
|
|
|
121
129
|
These hooks manage the app's lifetime, not individual page transitions. Configure navigation below as well.
|
|
122
130
|
|
|
123
131
|
## 3. Coordinate navigation
|
|
124
132
|
|
|
125
|
-
For optional Ionic animation coordination, install `@rdlabo/ionic-theme-ios27` and
|
|
133
|
+
For optional Ionic animation coordination, install `@rdlabo/ionic-theme-ios27` and update Native UI together so their versions match. Configure navigation before Ionic initializes. Its adapter can retain Ionic's default transitions without loading theme CSS:
|
|
126
134
|
|
|
127
135
|
```sh
|
|
128
|
-
npm install @rdlabo/ionic-theme-ios27
|
|
136
|
+
npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui
|
|
129
137
|
```
|
|
130
138
|
|
|
131
139
|
```ts
|
|
@@ -154,12 +162,12 @@ npx cap sync ios
|
|
|
154
162
|
|
|
155
163
|
Run the iOS app from Xcode on an iOS 26+ device or simulator. Supported fixed controls should render natively and still invoke their original handlers. Navigate forward and back, including a canceled swipe-back gesture. A browser or Android build should retain working Web controls.
|
|
156
164
|
|
|
157
|
-
| If you see…
|
|
158
|
-
|
|
|
165
|
+
| If you see… | Check… |
|
|
166
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
159
167
|
| All controls remain on the Web on iOS | The native package is a direct dependency, the app was synced and rebuilt, and startup ran on a supported OS. |
|
|
160
|
-
| Only one control remains on the Web
|
|
161
|
-
| Controls overlap during navigation
|
|
162
|
-
| Missing styles or spacing
|
|
168
|
+
| Only one control remains on the Web | Compare its markup and styles with [supported controls](native-ui.md#supported-markup). |
|
|
169
|
+
| Controls overlap during navigation | Configure the transition adapter or suspend/resume lifecycle before navigating. |
|
|
170
|
+
| Missing styles or spacing | Retain the app's Web CSS; the plugin does not supply page layout. |
|
|
163
171
|
|
|
164
172
|
## Next steps
|
|
165
173
|
|
|
@@ -193,8 +201,12 @@ async function showPage(current: HTMLElement, next: HTMLElement) {
|
|
|
193
201
|
next.hidden = false;
|
|
194
202
|
if (!matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
195
203
|
const animation = next.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150 });
|
|
196
|
-
try {
|
|
197
|
-
|
|
204
|
+
try {
|
|
205
|
+
await animation.finished;
|
|
206
|
+
} catch {
|
|
207
|
+
next.hidden = true;
|
|
208
|
+
current.hidden = false;
|
|
209
|
+
}
|
|
198
210
|
}
|
|
199
211
|
} finally {
|
|
200
212
|
await lease.resume();
|
package/docs/vertical-bars.md
CHANGED
|
@@ -4,9 +4,15 @@ title: Vertical Bars (preview)
|
|
|
4
4
|
|
|
5
5
|
# Vertical Bars (preview)
|
|
6
6
|
|
|
7
|
-
Vertical Bars moves eligible Ionic
|
|
7
|
+
Vertical Bars moves eligible Ionic controls or explicitly marked HTML buttons into a side rail while keeping the original elements as the source of labels, icons and behavior. It works with the iOS theme or an existing Ionic theme, independently of hinge posture and the full Native UI.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Choose your setup before using this reference:
|
|
10
|
+
|
|
11
|
+
- **Ionic with a custom or default theme:** [Ionic rail setup](../../theme-ios27/docs/iphone-duo-with-original-theme.md#try-it-in-your-existing-ionic-app).
|
|
12
|
+
- **No Ionic:** [HTML setup](framework-independent.md) and its [Web rail configuration](framework-independent.md#optional-web-rail). Use `/core` and your own layout; the Ionic setup below does not apply.
|
|
13
|
+
- **Existing iOS 27 theme and Native UI:** [Connect device placement](../../theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) using your existing runtime.
|
|
14
|
+
|
|
15
|
+
Return to this page for control eligibility, native button appearance and runtime options. Device events and Ionic split panes are covered in [iPhone Duo support](../../theme-ios27/docs/iphone-duo.md).
|
|
10
16
|
|
|
11
17
|
Available in `1.2.0` as a **preview** feature. APIs and supported behavior may change.
|
|
12
18
|
|
|
@@ -22,7 +28,7 @@ npm install @rdlabo/capacitor-native-ui
|
|
|
22
28
|
|
|
23
29
|
For Capacitor iOS, build your Web assets and run `npx cap sync ios`. Supply the Web layout through your app or theme as described below.
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
For Ionic apps, start one runtime after `ion-app` is mounted:
|
|
26
32
|
|
|
27
33
|
```ts
|
|
28
34
|
import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
|
|
@@ -48,7 +54,6 @@ On supported iOS versions, adding `.ios-theme-vertical-bars` changes only contro
|
|
|
48
54
|
|
|
49
55
|
The SwiftUI surface is clipped and hit-tested to the system rail. Web content remains visible and interactive outside that physical region. The runtime optimistically updates tab selection before forwarding the action to the original `ion-tab-button`, using the same event and stale-revision protection as the other native controls. Overlay layout follows the [optional theme rail layout](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md#reserve-the-vertical-rail) when that stylesheet is used; custom layouts remain the application’s responsibility.
|
|
50
56
|
|
|
51
|
-
|
|
52
57
|
### Native popovers (Vertical Bars only)
|
|
53
58
|
|
|
54
59
|
Native popover presentation is limited to popovers anchored to accepted native Vertical Bars `ion-button` controls. It is available with the standalone Vertical Bars runtime; the full Native UI is not required. Enable it explicitly when starting the runtime (`popover` defaults to `false`):
|
|
@@ -63,18 +68,17 @@ const rail = await enableNativeUI({
|
|
|
63
68
|
|
|
64
69
|
Use an `ion-popover` whose `trigger` identifies the projected button, or pass that button's click event to `PopoverController.create({ event, ... })`. Ordinary toolbar and body popovers remain on the Web. If a rail-anchored popover falls back to Web rendering, the rail also returns to Web projection until dismissal. See [native popover behavior and limitations](native-ui.md#popovers-anchored-to-vertical-bars).
|
|
65
70
|
|
|
66
|
-
|
|
67
71
|
## Toolbar actions
|
|
68
72
|
|
|
69
73
|
A standard `ion-back-button` can project from outside a fixed toolbar, including routed content or a persistent app shell. `ion-menu-button` and other toolbar actions require a fixed toolbar.
|
|
70
74
|
|
|
71
75
|
By default, an `ion-button` moves into the rail when it contains an `ion-icon` or SVG with `slot="icon-only"`. The button must be in a fixed `ion-toolbar` directly inside `ion-header` or `ion-footer`, outside scrolling `ion-content`.
|
|
72
76
|
|
|
73
|
-
| Icon markup
|
|
74
|
-
|
|
|
75
|
-
| `slot="icon-only"`
|
|
77
|
+
| Icon markup | Placement |
|
|
78
|
+
| ---------------------------------------- | --------------------------- |
|
|
79
|
+
| `slot="icon-only"` | Vertical rail |
|
|
76
80
|
| `slot="start"`, `slot="end"`, or no slot | Original horizontal toolbar |
|
|
77
|
-
| No icon
|
|
81
|
+
| No icon | Original horizontal toolbar |
|
|
78
82
|
|
|
79
83
|
```html
|
|
80
84
|
<ion-header>
|
|
@@ -105,20 +109,20 @@ This selects by layout, not device model, and leaves tabs, segments and FABs unc
|
|
|
105
109
|
See [toolbar configuration](native-ui.md#toolbar-selection-and-grouping)
|
|
106
110
|
for horizontal settings, boolean shorthands and defaults.
|
|
107
111
|
|
|
108
|
-
| Option
|
|
109
|
-
|
|
|
110
|
-
| `button`
|
|
111
|
-
| `buttons`
|
|
112
|
-
| `backButton` | Automatically select eligible `ion-back-button` elements, independently of their parent group.
|
|
112
|
+
| Option | Responsibility | What it does not do |
|
|
113
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
|
|
114
|
+
| `button` | Automatically select eligible `ion-button` and `ion-menu-button` actions, whether inside or outside `ion-buttons`. | Does not select back buttons. |
|
|
115
|
+
| `buttons` | Allow selected actions in `ion-buttons` to project as a group when the existing grouping rules allow it. | Does not select, enable or disable any child action. |
|
|
116
|
+
| `backButton` | Automatically select eligible `ion-back-button` elements, independently of their parent group. | Does not group back buttons with other actions. |
|
|
113
117
|
|
|
114
118
|
For ordinary icon-only buttons in a group, with no local attributes:
|
|
115
119
|
|
|
116
|
-
| `buttons` | `button` | Result
|
|
117
|
-
|
|
|
118
|
-
| `true`
|
|
119
|
-
| `false`
|
|
120
|
-
| `true`
|
|
121
|
-
| `false`
|
|
120
|
+
| `buttons` | `button` | Result |
|
|
121
|
+
| --------- | -------- | ------------------------------------------------------------ |
|
|
122
|
+
| `true` | `true` | Eligible buttons project together when the group qualifies. |
|
|
123
|
+
| `false` | `true` | Eligible buttons project individually. |
|
|
124
|
+
| `true` | `false` | No ordinary buttons project; grouping alone selects nothing. |
|
|
125
|
+
| `false` | `false` | No ordinary buttons project. |
|
|
122
126
|
|
|
123
127
|
These results concern ordinary actions only. Back navigation, tabs and native
|
|
124
128
|
search are independent. For example, to select only back navigation among toolbar
|
|
@@ -138,11 +142,11 @@ This does not disable tab projection or tab rail CSS.
|
|
|
138
142
|
Use `data-vertical-bars="true|false"` on the element whose behavior you want to
|
|
139
143
|
override. The attribute is **not inherited**.
|
|
140
144
|
|
|
141
|
-
| Element
|
|
142
|
-
|
|
|
143
|
-
| `ion-button` / `ion-menu-button` | Select or exclude this action. Explicit `true` bypasses the icon-only selection rule. | `button` and the automatic shape rules
|
|
144
|
-
| `ion-buttons`
|
|
145
|
-
| `ion-back-button`
|
|
145
|
+
| Element | Meaning of its attribute | Fallback when absent or invalid |
|
|
146
|
+
| -------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
147
|
+
| `ion-button` / `ion-menu-button` | Select or exclude this action. Explicit `true` bypasses the icon-only selection rule. | `button` and the automatic shape rules |
|
|
148
|
+
| `ion-buttons` | Enable or disable grouping of already-selected actions. Never selects children. | `buttons` and the existing grouping rules |
|
|
149
|
+
| `ion-back-button` | Select or exclude this back button. | `backButton` and the supported back-button rules |
|
|
146
150
|
|
|
147
151
|
Empty or unknown attribute values are ignored. To disable automatic action
|
|
148
152
|
selection and explicitly select a text button:
|
|
@@ -220,10 +224,10 @@ button-then-group inheritance rules. Neither appearance value selects an action.
|
|
|
220
224
|
|
|
221
225
|
For native vertical `ion-button` and `ion-menu-button` actions, choose who controls appearance:
|
|
222
226
|
|
|
223
|
-
| `buttonProjection`
|
|
224
|
-
|
|
|
225
|
-
| `'system'` (default) | SwiftUI styles the buttons and tints their icons. Ionic fill, colors and borders are not applied.
|
|
226
|
-
| `'source'`
|
|
227
|
+
| `buttonProjection` | Appearance |
|
|
228
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
229
|
+
| `'system'` (default) | SwiftUI styles the buttons and tints their icons. Ionic fill, colors and borders are not applied. |
|
|
230
|
+
| `'source'` | Projects the supported Ionic fill and computed colors described in [Source fill rules](#source-fill-rules). |
|
|
227
231
|
|
|
228
232
|
```ts
|
|
229
233
|
const rail = await enableNativeUI({ verticalBars: { only: true, buttonProjection: 'source' } });
|
|
@@ -237,8 +241,8 @@ const rail = await enableNativeUI({ verticalBars: { only: true, buttonProjection
|
|
|
237
241
|
|
|
238
242
|
Use `data-projection` for local exceptions. Existing classes remain supported:
|
|
239
243
|
|
|
240
|
-
| Attribute
|
|
241
|
-
|
|
|
244
|
+
| Attribute | Equivalent class |
|
|
245
|
+
| -------------------------- | ----------------------------- |
|
|
242
246
|
| `data-projection="source"` | `ios-theme-projection-source` |
|
|
243
247
|
| `data-projection="system"` | `ios-theme-projection-system` |
|
|
244
248
|
|
|
@@ -267,10 +271,10 @@ Only native vertical `ion-button` and `ion-menu-button` actions interpret these
|
|
|
267
271
|
|
|
268
272
|
For an `ion-button` resolved to `source`, fill is chosen separately from the projection mode:
|
|
269
273
|
|
|
270
|
-
| Button
|
|
271
|
-
|
|
|
272
|
-
| Explicit `fill="clear"`, `"solid"` or `"outline"`
|
|
273
|
-
| Omitted fill or `fill="default"` inside `ion-buttons`
|
|
274
|
+
| Button | Effective fill |
|
|
275
|
+
| ------------------------------------------------------ | ------------------- |
|
|
276
|
+
| Explicit `fill="clear"`, `"solid"` or `"outline"` | The explicit value |
|
|
277
|
+
| Omitted fill or `fill="default"` inside `ion-buttons` | `clear` |
|
|
274
278
|
| Omitted fill or `fill="default"` outside `ion-buttons` | `buttonDefaultFill` |
|
|
275
279
|
|
|
276
280
|
`verticalBars.buttonDefaultFill` accepts `'solid'` or `'glass'`; omission is equivalent to `'glass'`. Use `'solid'` for Ionic's default button design, or `'glass'` for a glass design. It applies to every button resolved to `source`, including a local exception under a global `system` setting.
|
|
@@ -287,12 +291,12 @@ const rail = await enableNativeUI({ verticalBars: { only: true, buttonDefaultFil
|
|
|
287
291
|
</ion-button>
|
|
288
292
|
```
|
|
289
293
|
|
|
290
|
-
| Effective fill | Native appearance in `source` mode
|
|
291
|
-
|
|
|
292
|
-
| `clear`
|
|
293
|
-
| `solid`
|
|
294
|
-
| `outline`
|
|
295
|
-
| `null`
|
|
294
|
+
| Effective fill | Native appearance in `source` mode |
|
|
295
|
+
| -------------- | ------------------------------------------------------------------- |
|
|
296
|
+
| `clear` | Icon color, without a glass background; CSS backgrounds are ignored |
|
|
297
|
+
| `solid` | Computed background color tints a prominent glass button |
|
|
298
|
+
| `outline` | Computed border color and width, with native glass |
|
|
299
|
+
| `null` | Native glass with source icon colors |
|
|
296
300
|
|
|
297
301
|
Inside `ion-buttons`, explicitly set `fill="solid"` to project a background; `buttonDefaultFill: 'solid'` does not override the group's clear default. Native Liquid Glass tinting can differ from the CSS color, especially for translucent backgrounds. With the iOS theme, ordinary `ion-buttons` retain group projection; `ion-buttons.ios-theme-disabled` projects eligible buttons individually. Local projection settings do not change that grouping rule.
|
|
298
302
|
|
package/package.json
CHANGED
|
@@ -1,47 +0,0 @@
|
|
|
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.
|