@rdlabo/capacitor-native-ui 1.2.1 → 2.0.0-beta.pr304.sha56daf151ad80
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/core.d.ts +2 -2
- package/dist/native/activation.d.ts.map +1 -1
- package/dist/native/activation.js +19 -19
- package/dist/native/activation.js.map +1 -1
- package/dist/native/definitions.d.ts +22 -40
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/html.d.ts.map +1 -1
- package/dist/native/html.js +0 -1
- package/dist/native/html.js.map +1 -1
- package/dist/native/index.d.ts +2 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +2 -11
- package/dist/native/index.js.map +1 -1
- package/dist/native/overlays/controller.js +1 -1
- package/dist/native/overlays/controller.js.map +1 -1
- package/dist/native/prehide.js +1 -1
- package/dist/native/prehide.js.map +1 -1
- package/dist/native/runtime.d.ts +1 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +8 -20
- package/dist/native/runtime.js.map +1 -1
- package/dist/native/shared/candidate.d.ts +2 -2
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +2 -2
- package/dist/native/shared/candidate.js.map +1 -1
- package/dist/native/shared/control-options.d.ts +6 -0
- package/dist/native/shared/control-options.d.ts.map +1 -0
- package/dist/native/shared/control-options.js +17 -0
- package/dist/native/shared/control-options.js.map +1 -0
- package/dist/native/shared/toolbar-options.d.ts +6 -2
- package/dist/native/shared/toolbar-options.d.ts.map +1 -1
- package/dist/native/shared/toolbar-options.js +7 -15
- package/dist/native/shared/toolbar-options.js.map +1 -1
- package/dist/native/startup.d.ts.map +1 -1
- package/dist/native/startup.js +0 -16
- package/dist/native/startup.js.map +1 -1
- package/dist/native/vertical-bars-web.d.ts.map +1 -1
- package/dist/native/vertical-bars-web.js +3 -2
- package/dist/native/vertical-bars-web.js.map +1 -1
- package/dist/vertical-bars.d.ts +2 -2
- package/docs/configuration.md +94 -72
- package/docs/framework-independent.md +11 -5
- package/docs/migration.md +62 -84
- package/docs/native-ui-extensions.md +31 -28
- package/docs/native-ui.md +34 -50
- package/docs/setup.md +34 -22
- package/docs/vertical-bars.md +59 -65
- package/ios/Sources/NativeUIPlugin/NativeUIPlugin.swift +1 -1
- package/package.json +1 -1
- package/src/core.ts +1 -2
- package/src/native/activation.ts +13 -22
- package/src/native/definitions.ts +40 -90
- package/src/native/html.ts +0 -5
- package/src/native/index.ts +2 -11
- package/src/native/overlays/controller.ts +1 -1
- package/src/native/prehide.ts +1 -1
- package/src/native/runtime.ts +7 -16
- package/src/native/shared/candidate.ts +4 -4
- package/src/native/shared/control-options.ts +20 -0
- package/src/native/shared/toolbar-options.ts +8 -12
- package/src/native/startup.ts +0 -16
- package/src/native/vertical-bars-web.ts +4 -2
- package/src/vertical-bars.ts +2 -1
- package/docs/native-mini-player.md +0 -47
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,11 +4,17 @@ 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
10
|
|
|
11
|
-
|
|
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).
|
|
16
|
+
|
|
17
|
+
Available as a **preview** feature. APIs and supported behavior may change.
|
|
12
18
|
|
|
13
19
|
See [shared configuration](configuration.md) for all defaults and live `rail.enableVerticalControlArea()` / `rail.disableVerticalControlArea()` methods. [Plain HTML controls](framework-independent.md) use the same native rail renderers.
|
|
14
20
|
|
|
@@ -22,12 +28,12 @@ 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';
|
|
29
35
|
|
|
30
|
-
const rail = await enableNativeUI({
|
|
36
|
+
const rail = await enableNativeUI({ controls: 'verticalOnly' });
|
|
31
37
|
```
|
|
32
38
|
|
|
33
39
|
Configure your Web rail layout and [apply device placement](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) for an iPhone Duo. The application owns placement and cleanup: call `await rail.destroy()` when its owner is disposed. If you already use `enableNativeUI()`, keep that runtime; it includes Vertical Bars. Do not start both.
|
|
@@ -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`):
|
|
@@ -57,24 +62,24 @@ Native popover presentation is limited to popovers anchored to accepted native V
|
|
|
57
62
|
import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
|
|
58
63
|
|
|
59
64
|
const rail = await enableNativeUI({
|
|
60
|
-
|
|
65
|
+
controls: 'verticalOnly',
|
|
66
|
+
ionic: { verticalBars: { popover: true } },
|
|
61
67
|
});
|
|
62
68
|
```
|
|
63
69
|
|
|
64
70
|
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
71
|
|
|
66
|
-
|
|
67
72
|
## Toolbar actions
|
|
68
73
|
|
|
69
74
|
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
75
|
|
|
71
76
|
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
77
|
|
|
73
|
-
| Icon markup
|
|
74
|
-
|
|
|
75
|
-
| `slot="icon-only"`
|
|
78
|
+
| Icon markup | Placement |
|
|
79
|
+
| ---------------------------------------- | --------------------------- |
|
|
80
|
+
| `slot="icon-only"` | Vertical rail |
|
|
76
81
|
| `slot="start"`, `slot="end"`, or no slot | Original horizontal toolbar |
|
|
77
|
-
| No icon
|
|
82
|
+
| No icon | Original horizontal toolbar |
|
|
78
83
|
|
|
79
84
|
```html
|
|
80
85
|
<ion-header>
|
|
@@ -96,29 +101,22 @@ A toolbar whose content has all moved into the rail collapses while projection i
|
|
|
96
101
|
|
|
97
102
|
### Select actions and grouping independently
|
|
98
103
|
|
|
99
|
-
Use `
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for horizontal settings, boolean shorthands and defaults.
|
|
107
|
-
|
|
108
|
-
| Option | Responsibility | What it does not do |
|
|
109
|
-
| --- | --- | --- |
|
|
110
|
-
| `button` | Automatically select eligible `ion-button` and `ion-menu-button` actions, whether inside or outside `ion-buttons`. | Does not select back buttons. |
|
|
111
|
-
| `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. |
|
|
112
|
-
| `backButton` | Automatically select eligible `ion-back-button` elements, independently of their parent group. | Does not group back buttons with other actions. |
|
|
104
|
+
Use `controls` to configure toolbar selection and grouping. Each field accepts `true`, `false`, `'horizontalOnly'`, or `'verticalOnly'`, with omitted fields defaulting to `true`. Use `controls: 'verticalOnly'` for all default selection in the rail. Local attributes can override selection; explicit sources and extensions remain independent. See [all selection defaults](configuration.md#selection-defaults).
|
|
105
|
+
|
|
106
|
+
| Option | Responsibility | What it does not do |
|
|
107
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
|
|
108
|
+
| `button` | Automatically select eligible `ion-button` and `ion-menu-button` actions, whether inside or outside `ion-buttons`. | Does not select back buttons. |
|
|
109
|
+
| `buttonGroup` | 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. |
|
|
110
|
+
| `backButton` | Automatically select eligible `ion-back-button` elements, independently of their parent group. | Does not group back buttons with other actions. |
|
|
113
111
|
|
|
114
112
|
For ordinary icon-only buttons in a group, with no local attributes:
|
|
115
113
|
|
|
116
|
-
| `
|
|
117
|
-
|
|
|
118
|
-
| `true`
|
|
119
|
-
| `false`
|
|
120
|
-
| `true`
|
|
121
|
-
| `false`
|
|
114
|
+
| `buttonGroup` | `button` | Result |
|
|
115
|
+
| ------------- | -------- | ------------------------------------------------------------ |
|
|
116
|
+
| `true` | `true` | Eligible buttons project together when the group qualifies. |
|
|
117
|
+
| `false` | `true` | Eligible buttons project individually. |
|
|
118
|
+
| `true` | `false` | No ordinary buttons project; grouping alone selects nothing. |
|
|
119
|
+
| `false` | `false` | No ordinary buttons project. |
|
|
122
120
|
|
|
123
121
|
These results concern ordinary actions only. Back navigation, tabs and native
|
|
124
122
|
search are independent. For example, to select only back navigation among toolbar
|
|
@@ -126,8 +124,7 @@ actions:
|
|
|
126
124
|
|
|
127
125
|
```ts
|
|
128
126
|
const rail = await enableNativeUI({
|
|
129
|
-
|
|
130
|
-
vertical: { buttons: false, button: false, backButton: true },
|
|
127
|
+
controls: { buttonGroup: false, button: false, backButton: 'verticalOnly', tabs: 'verticalOnly', segment: false, fab: false },
|
|
131
128
|
});
|
|
132
129
|
```
|
|
133
130
|
|
|
@@ -138,19 +135,18 @@ This does not disable tab projection or tab rail CSS.
|
|
|
138
135
|
Use `data-vertical-bars="true|false"` on the element whose behavior you want to
|
|
139
136
|
override. The attribute is **not inherited**.
|
|
140
137
|
|
|
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`
|
|
138
|
+
| Element | Meaning of its attribute | Fallback when absent or invalid |
|
|
139
|
+
| -------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
140
|
+
| `ion-button` / `ion-menu-button` | Select or exclude this action. Explicit `true` bypasses the icon-only selection rule. | `button` and the automatic shape rules |
|
|
141
|
+
| `ion-buttons` | Enable or disable grouping of already-selected actions. Never selects children. | `buttonGroup` and the existing grouping rules |
|
|
142
|
+
| `ion-back-button` | Select or exclude this back button. | `backButton` and the supported back-button rules |
|
|
146
143
|
|
|
147
144
|
Empty or unknown attribute values are ignored. To disable automatic action
|
|
148
145
|
selection and explicitly select a text button:
|
|
149
146
|
|
|
150
147
|
```ts
|
|
151
148
|
const rail = await enableNativeUI({
|
|
152
|
-
|
|
153
|
-
vertical: { buttons: true, button: false, backButton: false },
|
|
149
|
+
controls: { buttonGroup: 'verticalOnly', button: false, backButton: false, tabs: 'verticalOnly', segment: false, fab: false },
|
|
154
150
|
});
|
|
155
151
|
```
|
|
156
152
|
|
|
@@ -164,7 +160,7 @@ const rail = await enableNativeUI({
|
|
|
164
160
|
Only Save moves; Cancel stays in the horizontal toolbar. Adding
|
|
165
161
|
`data-vertical-bars="true"` to the group would not select Cancel.
|
|
166
162
|
|
|
167
|
-
To group two explicitly selected actions even when `
|
|
163
|
+
To group two explicitly selected actions even when `buttonGroup: false`:
|
|
168
164
|
|
|
169
165
|
```html
|
|
170
166
|
<ion-buttons data-vertical-bars="true">
|
|
@@ -188,7 +184,7 @@ startup options.
|
|
|
188
184
|
### Back buttons inside groups
|
|
189
185
|
|
|
190
186
|
An `ion-back-button` remains a navigation action even inside `ion-buttons`.
|
|
191
|
-
It ignores `
|
|
187
|
+
It ignores `buttonGroup`, `button`, and the group's `data-vertical-bars` attribute.
|
|
192
188
|
Its own attribute overrides `backButton`. It uses the dedicated back position,
|
|
193
189
|
never an ordinary action group, and is not duplicated as a toolbar action.
|
|
194
190
|
|
|
@@ -216,29 +212,27 @@ button-then-group inheritance rules. Neither appearance value selects an action.
|
|
|
216
212
|
|
|
217
213
|
### Choose button appearance
|
|
218
214
|
|
|
219
|
-
`buttonProjection`
|
|
215
|
+
Use `ionic.verticalBars.buttonProjection` to choose native button appearance.
|
|
220
216
|
|
|
221
217
|
For native vertical `ion-button` and `ion-menu-button` actions, choose who controls appearance:
|
|
222
218
|
|
|
223
|
-
| `buttonProjection`
|
|
224
|
-
|
|
|
225
|
-
| `'system'` (default) | SwiftUI styles the buttons and tints their icons. Ionic fill, colors and borders are not applied.
|
|
226
|
-
| `'source'`
|
|
219
|
+
| `buttonProjection` | Appearance |
|
|
220
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `'system'` (default) | SwiftUI styles the buttons and tints their icons. Ionic fill, colors and borders are not applied. |
|
|
222
|
+
| `'source'` | Projects the supported Ionic fill and computed colors described in [Source fill rules](#source-fill-rules). |
|
|
227
223
|
|
|
228
224
|
```ts
|
|
229
|
-
const rail = await enableNativeUI({
|
|
225
|
+
const rail = await enableNativeUI({ controls: 'verticalOnly', ionic: { verticalBars: { buttonProjection: 'source' } } });
|
|
230
226
|
```
|
|
231
227
|
|
|
232
|
-
`enableNativeUI()`
|
|
233
|
-
|
|
234
|
-
**Migration from the experimental releases:** the default changes from source styling to `system`. Set `buttonProjection: 'source'` to retain the previous projection behavior.
|
|
228
|
+
`enableNativeUI()` and the standalone `enableVerticalControlArea()` accept these settings inside `ionic.verticalBars`. Use one runtime, and destroy it before restarting with different options. Either mode preserves actions, disabled state and grouping. Disabled icons use the native disabled appearance. These appearance settings do not affect horizontal controls, source elements or Web fallback clones, so compare the native appearance on supported iOS.
|
|
235
229
|
|
|
236
230
|
### Override individual buttons or groups
|
|
237
231
|
|
|
238
232
|
Use `data-projection` for local exceptions. Existing classes remain supported:
|
|
239
233
|
|
|
240
|
-
| Attribute
|
|
241
|
-
|
|
|
234
|
+
| Attribute | Equivalent class |
|
|
235
|
+
| -------------------------- | ----------------------------- |
|
|
242
236
|
| `data-projection="source"` | `ios-theme-projection-source` |
|
|
243
237
|
| `data-projection="system"` | `ios-theme-projection-system` |
|
|
244
238
|
|
|
@@ -267,17 +261,17 @@ Only native vertical `ion-button` and `ion-menu-button` actions interpret these
|
|
|
267
261
|
|
|
268
262
|
For an `ion-button` resolved to `source`, fill is chosen separately from the projection mode:
|
|
269
263
|
|
|
270
|
-
| Button
|
|
271
|
-
|
|
|
272
|
-
| Explicit `fill="clear"`, `"solid"` or `"outline"`
|
|
273
|
-
| Omitted fill or `fill="default"` inside `ion-buttons`
|
|
264
|
+
| Button | Effective fill |
|
|
265
|
+
| ------------------------------------------------------ | ------------------- |
|
|
266
|
+
| Explicit `fill="clear"`, `"solid"` or `"outline"` | The explicit value |
|
|
267
|
+
| Omitted fill or `fill="default"` inside `ion-buttons` | `clear` |
|
|
274
268
|
| Omitted fill or `fill="default"` outside `ion-buttons` | `buttonDefaultFill` |
|
|
275
269
|
|
|
276
270
|
`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.
|
|
277
271
|
|
|
278
272
|
```ts
|
|
279
273
|
// Keep system styling globally; local source buttons use Ionic's default fill.
|
|
280
|
-
const rail = await enableNativeUI({
|
|
274
|
+
const rail = await enableNativeUI({ controls: 'verticalOnly', ionic: { verticalBars: { buttonDefaultFill: 'solid' } } });
|
|
281
275
|
```
|
|
282
276
|
|
|
283
277
|
```html
|
|
@@ -287,12 +281,12 @@ const rail = await enableNativeUI({ verticalBars: { only: true, buttonDefaultFil
|
|
|
287
281
|
</ion-button>
|
|
288
282
|
```
|
|
289
283
|
|
|
290
|
-
| Effective fill | Native appearance in `source` mode
|
|
291
|
-
|
|
|
292
|
-
| `clear`
|
|
293
|
-
| `solid`
|
|
294
|
-
| `outline`
|
|
295
|
-
| `null`
|
|
284
|
+
| Effective fill | Native appearance in `source` mode |
|
|
285
|
+
| -------------- | ------------------------------------------------------------------- |
|
|
286
|
+
| `clear` | Icon color, without a glass background; CSS backgrounds are ignored |
|
|
287
|
+
| `solid` | Computed background color tints a prominent glass button |
|
|
288
|
+
| `outline` | Computed border color and width, with native glass |
|
|
289
|
+
| `null` | Native glass with source icon colors |
|
|
296
290
|
|
|
297
291
|
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
292
|
|
|
@@ -210,7 +210,7 @@ public class NativeUIPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarDelegate {
|
|
|
210
210
|
self.bridge?.webView?.layoutIfNeeded()
|
|
211
211
|
// Ionic already paints the header edge; a second native effect can
|
|
212
212
|
// add a dark scrim when the OS and Web themes differ.
|
|
213
|
-
if call.getBool("
|
|
213
|
+
if call.getBool("suppressScrollEdgeEffect") == true, let effect = self.bridge?.webView?.scrollView.topEdgeEffect {
|
|
214
214
|
let hidden = effect.isHidden
|
|
215
215
|
effect.isHidden = true
|
|
216
216
|
self.restoreTopEdge = { [weak effect] in effect?.isHidden = hidden }
|
package/package.json
CHANGED
package/src/core.ts
CHANGED
package/src/native/activation.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { controlDefault } from './shared/control-options';
|
|
1
2
|
import { toolbarOptions } from './shared/toolbar-options';
|
|
2
3
|
import { createDiagnostics } from './shared/diagnostics';
|
|
3
4
|
import { createHTMLWebProjection } from './html';
|
|
@@ -160,15 +161,6 @@ const webProjection = (doc: Document, options: NativeUIRuntimeOptions, enabled:
|
|
|
160
161
|
|
|
161
162
|
/** Call once at application startup. Ionic markup remains the source of truth. */
|
|
162
163
|
export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFactory?: OverlayFactory): Promise<NativeUIRuntimeHandle> => {
|
|
163
|
-
if (options.enabled === false) {
|
|
164
|
-
const current = active;
|
|
165
|
-
if (current)
|
|
166
|
-
stopActivation(
|
|
167
|
-
current,
|
|
168
|
-
current.then((handle) => handle.destroy()),
|
|
169
|
-
);
|
|
170
|
-
return (stopping ?? Promise.resolve()).then(() => web('Disabled'));
|
|
171
|
-
}
|
|
172
164
|
if (typeof document === 'undefined') return Promise.resolve(web('Requires a document'));
|
|
173
165
|
const extensions = [...(options.extensions ?? [])];
|
|
174
166
|
const nativeState: NonNullable<NativeUIRuntimeOptions['nativeState']> = (options.nativeState = {});
|
|
@@ -177,22 +169,20 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
177
169
|
} catch (error) {
|
|
178
170
|
return Promise.reject(error);
|
|
179
171
|
}
|
|
180
|
-
const controls = options.controls;
|
|
181
172
|
const configuration = JSON.stringify([
|
|
182
173
|
options.source ?? 'ionic',
|
|
183
174
|
options.debug === true,
|
|
184
|
-
options.
|
|
175
|
+
options.ionic?.auto !== false,
|
|
185
176
|
options.verticalBars?.enabled !== false,
|
|
186
177
|
options.verticalBars?.placement?.edge,
|
|
187
178
|
options.rail?.edge,
|
|
188
|
-
options.verticalBarsOnly === true,
|
|
189
179
|
toolbarOptions(options, false),
|
|
190
180
|
toolbarOptions(options, true),
|
|
191
|
-
options.buttonProjection ?? 'system',
|
|
181
|
+
options.ionic?.verticalBars?.buttonProjection ?? 'system',
|
|
192
182
|
// Local source overrides can use this even when the startup projection is system.
|
|
193
|
-
options.buttonDefaultFill ??
|
|
194
|
-
...(['tabs', '
|
|
195
|
-
|
|
183
|
+
options.ionic?.verticalBars?.buttonDefaultFill ?? 'glass',
|
|
184
|
+
...(['tabs', 'segment', 'fab'] as const).flatMap((kind) => [controlDefault(options, kind, false), controlDefault(options, kind, true)]),
|
|
185
|
+
options.ionic?.verticalBars?.popover === true,
|
|
196
186
|
]);
|
|
197
187
|
if (active && activeFailed) {
|
|
198
188
|
const previous = active;
|
|
@@ -248,7 +238,7 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
248
238
|
const diagnose = createDiagnostics(options.debug);
|
|
249
239
|
const stopModals = options.source === 'html' ? () => {} : observeVerticalBarsModals(document);
|
|
250
240
|
const prehide =
|
|
251
|
-
platform !== 'android' && options.source !== 'html' &&
|
|
241
|
+
platform !== 'android' && options.source !== 'html' && options.ionic?.auto !== false
|
|
252
242
|
? prehideVerticalBarsToolbarSources(document, () => options.verticalBars?.enabled !== false, options.root, options)
|
|
253
243
|
: undefined;
|
|
254
244
|
const fallback = (reason: string) => {
|
|
@@ -290,7 +280,10 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
290
280
|
}
|
|
291
281
|
};
|
|
292
282
|
try {
|
|
293
|
-
const
|
|
283
|
+
const ordinaryControls = (['button', 'backButton', 'tabs', 'segment', 'fab'] as const).some((kind) =>
|
|
284
|
+
controlDefault(options, kind, false),
|
|
285
|
+
);
|
|
286
|
+
const capabilities = await bounded(plugin.configure({ suppressScrollEdgeEffect: ordinaryControls }));
|
|
294
287
|
if (!capabilities.supported) return fallback('Native UI is unavailable on this device');
|
|
295
288
|
const unavailableExtensions = new Map<string, string>();
|
|
296
289
|
for (const extension of extensions) {
|
|
@@ -313,7 +306,6 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
313
306
|
unavailableExtensions,
|
|
314
307
|
},
|
|
315
308
|
() => false,
|
|
316
|
-
options.verticalBarsOnly === true,
|
|
317
309
|
runtimeStopped,
|
|
318
310
|
);
|
|
319
311
|
runtime = combine(runtime, webProjection(document, options));
|
|
@@ -349,7 +341,7 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
349
341
|
return physicalEdge === nativeState.edge;
|
|
350
342
|
};
|
|
351
343
|
const applyMetrics = (metrics: WebViewMetrics) => {
|
|
352
|
-
if (
|
|
344
|
+
if (ordinaryControls) nativeWebViewMetrics.radius = metrics.radius;
|
|
353
345
|
deviceEdge = metrics.verticalBarEdge ?? null;
|
|
354
346
|
updateNativeEdge();
|
|
355
347
|
document.defaultView?.dispatchEvent(new Event('nativeUIRefresh'));
|
|
@@ -363,14 +355,13 @@ export const enableNativeUI = (options: NativeUIRuntimeOptions = {}, overlayFact
|
|
|
363
355
|
plugin,
|
|
364
356
|
{ ...options, extensions, unavailableExtensions },
|
|
365
357
|
nativeVerticalBars,
|
|
366
|
-
options.verticalBarsOnly === true,
|
|
367
358
|
runtimeStopped,
|
|
368
359
|
);
|
|
369
360
|
runtime = combine(
|
|
370
361
|
native,
|
|
371
362
|
webProjection(document, options, () => !nativeVerticalBars() || native.getStatus().projected === 0),
|
|
372
363
|
);
|
|
373
|
-
if (
|
|
364
|
+
if (options.ionic?.verticalBars?.popover === true && overlayFactory)
|
|
374
365
|
overlays = await overlayFactory(document, plugin, options, native.retain, nativeVerticalBars);
|
|
375
366
|
return manage(runtime, {
|
|
376
367
|
suspend: async () => {
|