@rdlabo/ionic-theme-ios27 1.2.0-1 → 1.2.0-3
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 +9 -0
- package/dist/css/components/ion-tabs.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -12
- package/dist/native/components/index.d.ts.map +1 -1
- package/dist/native/components/index.js +4 -1
- package/dist/native/components/ion-button.d.ts.map +1 -1
- package/dist/native/components/ion-button.js +3 -3
- package/dist/native/components/ion-buttons.d.ts.map +1 -1
- package/dist/native/components/ion-buttons.js +3 -2
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +6 -7
- package/dist/native/definitions.d.ts +8 -15
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/definitions.js +1 -6
- package/dist/native/index.d.ts +1 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +64 -28
- package/dist/native/prehide.d.ts.map +1 -1
- package/dist/native/prehide.js +6 -4
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +40 -19
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +8 -0
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +11 -10
- package/dist/native/shared/modal.d.ts +8 -0
- package/dist/native/shared/modal.d.ts.map +1 -0
- package/dist/native/shared/modal.js +143 -0
- package/dist/native/vertical-bars-web.d.ts.map +1 -1
- package/dist/native/vertical-bars-web.js +11 -3
- package/dist/sheets-of-glass/animations.d.ts +13 -4
- package/dist/sheets-of-glass/animations.d.ts.map +1 -1
- package/dist/sheets-of-glass/animations.js +56 -40
- package/dist/sheets-of-glass/index.d.ts +1 -1
- package/dist/sheets-of-glass/index.d.ts.map +1 -1
- package/dist/sheets-of-glass/index.js +64 -20
- package/dist/tab-bar-searchable/index.d.ts.map +1 -1
- package/dist/tab-bar-searchable/index.js +39 -11
- package/dist/vertical-bars.d.ts +1 -2
- package/dist/vertical-bars.d.ts.map +1 -1
- package/dist/vertical-bars.js +0 -1
- package/docs/iphone-duo-with-original-theme.md +37 -15
- package/docs/iphone-duo.md +90 -47
- package/docs/migration.md +3 -3
- package/docs/native-ui-shell.md +12 -8
- package/docs/special-markup.md +0 -13
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +1 -1
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +50 -6
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +118 -0
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +32 -179
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +5 -0
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +67 -0
- package/package.json +1 -1
- package/src/index.ts +40 -9
- package/src/native/components/index.ts +6 -1
- package/src/native/components/ion-button.ts +4 -3
- package/src/native/components/ion-buttons.ts +4 -2
- package/src/native/components/searchable-tabs.ts +8 -11
- package/src/native/definitions.ts +11 -19
- package/src/native/index.ts +67 -30
- package/src/native/prehide.ts +7 -4
- package/src/native/runtime.ts +40 -18
- package/src/native/shared/candidate.ts +10 -0
- package/src/native/shared/dom.ts +11 -10
- package/src/native/shared/modal.ts +157 -0
- package/src/native/vertical-bars-web.ts +12 -3
- package/src/sheets-of-glass/animations.ts +66 -41
- package/src/sheets-of-glass/index.ts +82 -16
- package/src/styles/components/ion-tabs.scss +7 -4
- package/src/styles/vertical-bars.scss +110 -35
- package/src/tab-bar-searchable/index.ts +40 -9
- package/src/vertical-bars.ts +1 -2
|
@@ -17,7 +17,7 @@ Available in `1.2.0-0` as an **experimental** feature. APIs and supported behavi
|
|
|
17
17
|
This guide assumes an existing Ionic app with Ionic `>=8.8.1 <10` and Capacitor Core `>=8 <9`. Keep your existing Capacitor 8 installation. If your app uses another Capacitor major, migrate its Core, CLI, and platform packages together before following this guide. For a Web-only app without Capacitor, also install `@capacitor/core@^8`; the JavaScript entry point needs it even in Chrome.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @rdlabo/ionic-theme-ios27@1.2.0-
|
|
20
|
+
npm install @rdlabo/ionic-theme-ios27@1.2.0-1
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Keep your existing theme imports. Add this to your global Sass file:
|
|
@@ -62,9 +62,23 @@ const ionicConfig = {
|
|
|
62
62
|
|
|
63
63
|
Merge this option into your existing Ionic configuration before initialization: pass it to Angular's `provideIonicAngular()`, React's `setupIonicReact()`, or Vue's `IonicVue` plugin options. Keep your existing theme stylesheet imports. No iOS 27 theme stylesheet is required.
|
|
64
64
|
|
|
65
|
+
#### Use this package's iOS animation
|
|
66
|
+
|
|
67
|
+
If you already use the iOS 27 transition, keep this configuration. It includes the native adapter and excludes the horizontal back-button effect in vertical layouts; no additional wrapper is needed. Importing this JavaScript entry point does not load the theme stylesheets.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';
|
|
71
|
+
|
|
72
|
+
const ionicConfig = {
|
|
73
|
+
navAnimation: iosTransitionAnimation,
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Apply this option to your existing iOS-mode configuration and keep your MD configuration.
|
|
78
|
+
|
|
65
79
|
#### Keep your custom animation
|
|
66
80
|
|
|
67
|
-
If your app
|
|
81
|
+
If your app uses another builder for `navAnimation`, wrap it:
|
|
68
82
|
|
|
69
83
|
```ts
|
|
70
84
|
import type { AnimationBuilder } from '@ionic/core';
|
|
@@ -80,7 +94,7 @@ The adapter returns the original `Animation`, preserving its effects, duration,
|
|
|
80
94
|
|
|
81
95
|
The adapter keeps the builder's animation targets, including any horizontal back-button effect. If you need the iOS 27 transition with that effect excluded in vertical layouts, use `iosTransitionAnimation` from `@rdlabo/ionic-theme-ios27` as your `navAnimation` instead. It already includes the adapter, so no wrapper is needed.
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
`withNativeUIShellTransition()` is available in `1.2.0-1` and later.
|
|
84
98
|
|
|
85
99
|
### 4. Start the controls after the app root is mounted
|
|
86
100
|
|
|
@@ -92,9 +106,9 @@ import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-ba
|
|
|
92
106
|
const rail = await enableVerticalControlArea();
|
|
93
107
|
```
|
|
94
108
|
|
|
95
|
-
**What you should see:** your existing tab bar moves to the side, and
|
|
109
|
+
**What you should see:** your existing tab bar moves to the side, and fixed-toolbar buttons with an `ion-icon` or SVG using `slot="icon-only"` appear there too. Content keeps its existing theme and leaves room for the controls. The Web tab rail displays icons; pressing and dragging reveals tab labels.
|
|
96
110
|
|
|
97
|
-
Use your existing Ionic click handlers and
|
|
111
|
+
Use your existing Ionic click handlers, routing, and form associations. All button fills (`default`, `clear`, `solid`, and `outline`) use the same `icon-only` rule, including submit buttons. Actions without that slot remain horizontal. Add `.ios-theme-horizontal-only` to an `ion-buttons` group or individual `ion-button` to keep an action in the horizontal toolbar.
|
|
98
112
|
|
|
99
113
|
When the application owner is disposed, call `await rail.destroy()` to restore the original controls and release the runtime. If you already use `enableNativeUIShell()`, keep that runtime and follow the [shared placement guide](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#project-controls-into-the-rail).
|
|
100
114
|
|
|
@@ -103,28 +117,36 @@ When the application owner is disposed, call `await rail.destroy()` to restore t
|
|
|
103
117
|
| What you see | What to check |
|
|
104
118
|
| --- | --- |
|
|
105
119
|
| No space at the side | Load `vertical-bars.css` and put the class on `ion-app`. |
|
|
106
|
-
| Space appears, but controls stay horizontal | Start `enableVerticalControlArea()` after mounting the app root. Use existing tabs or
|
|
107
|
-
| One action stays horizontal |
|
|
120
|
+
| Space appears, but controls stay horizontal | Start `enableVerticalControlArea()` after mounting the app root. Use existing tabs or `slot="icon-only"` actions in a fixed header/footer toolbar. |
|
|
121
|
+
| One action stays horizontal | Check for `slot="icon-only"` on the icon and a fixed toolbar outside scrolling content. Explicitly excluded controls and controls in centered modals stay horizontal; `fill` and `type="submit"` do not prevent movement. See [control requirements](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#toolbar-actions). |
|
|
108
122
|
|
|
109
123
|
## Connect an iPhone Duo
|
|
110
124
|
|
|
111
|
-
|
|
125
|
+
Install [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) for device state:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm install @erkamyaman/capacitor-foldable
|
|
129
|
+
npx cap sync ios
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for actual rail placement and hinge posture on iOS 27.1. Native UI Shell uses Swift Package Manager; existing CocoaPods apps can follow [Native UI Shell setup](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell#enable-the-shell). Keep this package's `vertical-bars.css`; the device plugin's `ionic-tabs.css` is not needed with our rail projection.
|
|
112
133
|
|
|
113
134
|
Replace the browser-only startup above with this after `ion-app` is mounted:
|
|
114
135
|
|
|
115
136
|
```ts
|
|
116
137
|
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';
|
|
117
|
-
import { enableVerticalControlArea
|
|
138
|
+
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
|
|
139
|
+
import { Foldable } from '@erkamyaman/capacitor-foldable';
|
|
118
140
|
|
|
119
141
|
const rail = await enableVerticalControlArea();
|
|
120
142
|
let layoutListener: PluginListenerHandle | undefined;
|
|
121
143
|
|
|
122
144
|
if (Capacitor.getPlatform() === 'ios') {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
rail.setPlacement(placement),
|
|
145
|
+
layoutListener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
|
|
146
|
+
rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
|
|
126
147
|
);
|
|
127
|
-
|
|
148
|
+
const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
|
|
149
|
+
rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
|
|
128
150
|
}
|
|
129
151
|
|
|
130
152
|
// Call when the application owner is disposed.
|
|
@@ -134,13 +156,13 @@ const stopVerticalArea = async () => {
|
|
|
134
156
|
};
|
|
135
157
|
```
|
|
136
158
|
|
|
137
|
-
`setPlacement()`
|
|
159
|
+
`setPlacement()` resolves the logical edge through the document direction. Pass Foldable’s measured `inset` with the reported edge so the theme reserves the actual bar width and follows width changes. An inset of `0` clears the explicit width; manually requested rails then use the theme’s CSS safe-area rules. A `null` edge restores the ordinary layout. Devices without a reported rail return `null`, so this example restores the ordinary layout there. On iOS 27.1 or later, Foldable can infer Duo bar placement from safe-area insets when the app is built with an older SDK. To deliberately request a rail when the plugin reports no edge, have your application choose a fixed edge with `rail.setPlacement('trailing')` instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.
|
|
138
160
|
|
|
139
161
|
On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.
|
|
140
162
|
|
|
141
163
|
## Use hinge posture without projecting controls
|
|
142
164
|
|
|
143
|
-
If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Use `
|
|
165
|
+
If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Use `Foldable.getFoldState()` and `foldStateChange` directly, removing the listener when finished. There is no separate start/stop monitoring call.
|
|
144
166
|
|
|
145
167
|
See [Read the device layout](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#adapt-the-split-pane) for the opt-in width rules and half-open state.
|
|
146
168
|
|
package/docs/iphone-duo.md
CHANGED
|
@@ -10,13 +10,12 @@ Adapt your Ionic app to iPhone Duo: place navigation and actions in its vertical
|
|
|
10
10
|
|
|
11
11
|
Available in `1.2.0-0` as an **experimental** feature alongside [Native UI Shell](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell). APIs and supported behavior may change. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer.
|
|
12
12
|
|
|
13
|
-
This package provides
|
|
13
|
+
This package provides two independent pieces for that hardware. Each works **without the iOS 27 theme stylesheets** and **without the full Native UI Shell**:
|
|
14
14
|
|
|
15
15
|
- `dist/css/vertical-bars.css` — opt-in classes that reserve the rail's safe area, plus a registered custom property for a posture-driven split-pane width.
|
|
16
16
|
- `enableVerticalControlArea()` — moves eligible tabs and toolbar controls into the reserved area. On Capacitor iOS they are rendered by a native SwiftUI `TabView` and toolbar; everywhere else the same controls appear as Web clones.
|
|
17
|
-
- Device-layout reporting — the bundled Capacitor plugin reports rail placement, hinge status and the WebView corner radius through `getDeviceLayout()` and the `deviceLayoutChange` event.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Device state belongs to [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable). The **application** subscribes to its events and chooses its layout. This package's **stylesheet and runtime** apply that decision by reserving space, projecting controls and adapting the split pane. The theme does not monitor hinge state or bar placement.
|
|
20
19
|
|
|
21
20
|
## Choose what to adopt
|
|
22
21
|
|
|
@@ -24,10 +23,10 @@ To keep your existing theme and add only the standalone support, follow [iPhone
|
|
|
24
23
|
|
|
25
24
|
| Goal | Stylesheet | Runtime |
|
|
26
25
|
| ------------------------------------------------ | ------------------- | ------------------------------------------------------------------ |
|
|
27
|
-
| Hinge posture only (layout switches) | none | none — subscribe to
|
|
28
|
-
| Posture-driven split-pane width | `vertical-bars.css` | none — subscribe to
|
|
26
|
+
| Hinge posture only (layout switches) | none | none — subscribe to `Foldable` directly |
|
|
27
|
+
| Posture-driven split-pane width | `vertical-bars.css` | none — subscribe to `Foldable` directly |
|
|
29
28
|
| Vertical rail for tabs and toolbar actions | `vertical-bars.css` | `enableVerticalControlArea()` |
|
|
30
|
-
| Native shell plus the rail | `vertical-bars.css` | `enableNativeUIShell()` —
|
|
29
|
+
| Native shell plus the rail | `vertical-bars.css` | `enableNativeUIShell()` — includes rail projection |
|
|
31
30
|
|
|
32
31
|
```scss
|
|
33
32
|
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';
|
|
@@ -39,39 +38,49 @@ The `/vertical-bars` entry point imports `@capacitor/core` at module load, so in
|
|
|
39
38
|
|
|
40
39
|
## Read the device layout
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
Install the device-state plugin in the application, then sync the native project:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @erkamyaman/capacitor-foldable
|
|
45
|
+
npx cap sync
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for iPhone Duo's iOS 27.1 APIs. The dependency is needed for device-driven layout, not for the theme's CSS, browser simulation, or native control projection alone. Do not import the plugin's `ionic-tabs.css` alongside this package's rail projection; both would reposition the same tabs.
|
|
49
|
+
|
|
50
|
+
For a posture-driven split pane, subscribe directly without starting a projection runtime:
|
|
43
51
|
|
|
44
52
|
```ts
|
|
45
|
-
import {
|
|
46
|
-
import { HingeStatus, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';
|
|
53
|
+
import { Foldable, type FoldState } from '@erkamyaman/capacitor-foldable';
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
55
|
+
const applyFold = (fold: FoldState) => {
|
|
56
|
+
const pane = document.querySelector('ion-split-pane');
|
|
57
|
+
pane?.classList.toggle('ios-theme-split-pane-half-open', fold.state === 'half-opened');
|
|
58
|
+
const expanded = fold.state === 'half-opened' || (fold.state === 'flat' && !!fold.hingeBounds);
|
|
59
|
+
pane?.setAttribute('when', expanded ? '(min-width: 900px)' : '(min-width: 992px)');
|
|
60
|
+
};
|
|
61
|
+
let receivedEvent = false;
|
|
62
|
+
let disposed = false;
|
|
63
|
+
const listener = await Foldable.addListener('foldStateChange', (fold) => {
|
|
64
|
+
receivedEvent = true;
|
|
65
|
+
if (!disposed) applyFold(fold);
|
|
66
|
+
});
|
|
67
|
+
const initialFold = await Foldable.getFoldState();
|
|
68
|
+
if (!disposed && !receivedEvent) applyFold(initialFold);
|
|
69
|
+
|
|
70
|
+
// When the consumer goes away:
|
|
71
|
+
// disposed = true;
|
|
72
|
+
// await listener.remove();
|
|
60
73
|
```
|
|
61
74
|
|
|
62
|
-
`
|
|
75
|
+
`getFoldState()` and `foldStateChange` report `state` (`'flat'`, `'half-opened'`, or `'closed'`), `posture`, and optional hinge geometry. Without fold information, the plugin returns a flat state without `hingeBounds`; restore the ordinary split-pane breakpoint in that case. The Web implementation also returns a flat state. A half-opened state uses the 900px breakpoint even without hinge geometry; a flat state with hinge geometry also uses 900px. A closed state restores the ordinary 992px breakpoint. Events received during initialization take precedence over the initial read.
|
|
63
76
|
|
|
64
|
-
|
|
|
65
|
-
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
66
|
-
| `placement` | `{ edge: 'leading' \| 'trailing' \| null, inset }` — the rail's logical edge in the reading direction and its UIKit safe-area inset in points; `edge` is `null` on devices without a rail |
|
|
67
|
-
| `hingeStatus` | `HingeStatus.Closed`, `PartiallyOpen`, or `FullyOpen`; `null` when the device reports no hinge |
|
|
68
|
-
| `webViewMetrics.radius` | the WebView's effective top-left corner radius in points |
|
|
77
|
+
`getBarPlacement()` and `barPlacementChange` report `{ verticalBarEdge: 'leading' | 'trailing' | null, inset: number }`. The edge is **logical**: leading is the physical left in LTR and the physical right in RTL. Pass `{ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }` to `setPlacement()`. No start/stop monitoring calls are needed; remove each listener when its owner is disposed.
|
|
69
78
|
|
|
70
|
-
|
|
79
|
+
The theme never reads UIKit bar-placement traits. The application supplies `nativeEdge` on the initial read and each event, even when choosing a fixed `edge`. Omit `nativeEdge` to keep the last supplied value; pass `null` when the plugin reports no edge. An explicit `nativeEdge: null` or an initial unregistered edge prevents native vertical projection: `enableVerticalControlArea()` keeps the requested Web rail, while the full Native UI Shell temporarily restores its ordinary horizontal layout. The requested rail is retained so a later reported edge can restore vertical layout. Native vertical projection starts only after a matching non-null edge is supplied. Omitting `nativeEdge` after supplying it preserves that value, including `null`. Passing `{ edge: null, nativeEdge }` updates the reported edge while keeping the rail disabled.
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
Foldable reports the measured width reserved by the native bar as `inset` and notifies changes through `barPlacementChange`. Pass this value to `setPlacement()` when following the reported edge, rather than assuming a fixed width. When no bar is reported, `inset` is `0`; the theme clears the explicit width and uses its CSS safe-area rules if the application still requests a Web rail. Applications choosing a different edge can supply their own width or use the CSS fallback. WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`.
|
|
73
82
|
|
|
74
|
-
**
|
|
83
|
+
**Migration:** the theme's former `DeviceLayout`, `HingeStatus`, `getDeviceLayout()`, `deviceLayoutChange`, and start/stop device-layout monitoring APIs have been removed. Replace device subscriptions with the `Foldable` APIs above; use `getWebViewMetrics()` for one-shot radius reads. Foldable can infer Duo bar placement from safe-area insets when the app is built without the iOS 27.1 SDK. Hinge data still requires the newer SDK. Apps can also request a fixed rail placement independently of the reported edge.
|
|
75
84
|
|
|
76
85
|
## Reserve the vertical rail
|
|
77
86
|
|
|
@@ -81,13 +90,13 @@ Add `.ios-theme-vertical-bars` to `ion-app` to reserve the rail region on the ph
|
|
|
81
90
|
<ion-app class="ios-theme-vertical-bars">...</ion-app>
|
|
82
91
|
```
|
|
83
92
|
|
|
84
|
-
The classes are physical — `-left` always means the physical left edge — because CSS and the native renderer work in physical coordinates. `setPlacement` (below) is the usual way to apply them: it accepts the logical `
|
|
93
|
+
The classes are physical — `-left` always means the physical left edge — because CSS and the native renderer work in physical coordinates. `setPlacement` (below) is the usual way to apply them: it accepts the logical `verticalBarEdge` reported by `Foldable` and resolves it through the document's direction, so an RTL app does not need its own conversion.
|
|
85
94
|
|
|
86
|
-
For Chrome development, no native plugin is needed — the class alone reserves `80px` to simulate iPhone Duo. When `setPlacement` receives
|
|
95
|
+
For Chrome development, no native plugin is needed — the class alone reserves `80px` to simulate iPhone Duo. When `setPlacement` receives an explicit `{ edge, inset }`, that inset replaces the fallback width, even when it is less than `80px`. Override `--ios-theme-vertical-bars-safe-area-left` or `--ios-theme-vertical-bars-safe-area-right` when simulating a different layout.
|
|
87
96
|
|
|
88
97
|
This keeps routers and component backgrounds full-viewport. `ion-content` moves its scroll foreground, `ion-toolbar` moves its container foreground, and `ion-fab` adjusts only when placed beside the system UI. The corresponding Ionic safe-area variable is reset inside those foreground components so descendants do not add the inset again.
|
|
89
98
|
|
|
90
|
-
`ion-
|
|
99
|
+
`ion-modal` applies the same foreground correction when its visible dialog spans the viewport width. With the Vertical Control Area runtime enabled, the topmost full-width modal also projects eligible toolbar buttons into its own rail; centered dialogs keep their toolbar buttons and receive no page-rail inset. This includes full-width sheet modals: their rail follows the visible sheet bounds as the breakpoint changes. Eligibility follows the visible dialog width, not the hinge posture or modal type. `ion-menu` and `ion-popover` are handled as separate surfaces: their internal foreground components do not receive the main-page conversion and retain Ionic's standard safe-area handling. A menu presented beside the system UI keeps Ionic's full-viewport animation host and offsets only its visible container by the corresponding inset; a menu from the opposite side is unchanged. Left and right remain physical coordinates in RTL, while Ionic's `side="start"` and `side="end"` values remain logical.
|
|
91
100
|
|
|
92
101
|
The mode is component-mode independent: an app can keep Ionic `mode: 'md'` on iOS and still enable Vertical Bars. No component needs `mode="ios"`.
|
|
93
102
|
|
|
@@ -97,16 +106,19 @@ Start the standalone runtime once at application startup:
|
|
|
97
106
|
|
|
98
107
|
```ts
|
|
99
108
|
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';
|
|
100
|
-
import { enableVerticalControlArea
|
|
109
|
+
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
|
|
110
|
+
import { Foldable } from '@erkamyaman/capacitor-foldable';
|
|
101
111
|
|
|
102
112
|
// Start on Chrome too; the Web projection stays idle until the class is present.
|
|
103
113
|
const rail = await enableVerticalControlArea();
|
|
104
114
|
let layoutListener: PluginListenerHandle | undefined;
|
|
105
115
|
|
|
106
116
|
if (Capacitor.getPlatform() === 'ios') {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
layoutListener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
|
|
118
|
+
rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
|
|
119
|
+
);
|
|
120
|
+
const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
|
|
121
|
+
rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
// Call when the application owner is disposed.
|
|
@@ -118,15 +130,45 @@ const stopVerticalArea = async () => {
|
|
|
118
130
|
|
|
119
131
|
`setPlacement` on the handle and the exported `setVerticalControlAreaPlacement` are the same function; either applies the application's chosen placement to the CSS layout and both projections. It requires a mounted `ion-app` — call it after the app root exists.
|
|
120
132
|
|
|
121
|
-
- Pass
|
|
133
|
+
- Pass `{ edge, nativeEdge }`: `edge` is the application's chosen logical edge; `nativeEdge` is `verticalBarEdge` from `Foldable.getBarPlacement()`/`barPlacementChange`. They resolve through the nearest `dir` attribute, or the explicit `rtl` argument.
|
|
122
134
|
- Pass `null` to restore the ordinary layout.
|
|
123
|
-
- The device-layout listener reports what iOS chose; the application decides whether to apply it.
|
|
135
|
+
- The device-layout listener reports what iOS chose; the application decides whether to apply it. The theme compares the application's chosen edge with its supplied `nativeEdge`; a mismatch uses the Web rail until the edges match again. For a fixed right-in-LTR rail, pass `{ edge: 'trailing', nativeEdge: verticalBarEdge }` on each `Foldable` update.
|
|
136
|
+
|
|
137
|
+
Start either `enableVerticalControlArea()` or the full `enableNativeUIShell()` — not both. Repeating the same configuration returns the shared runtime; starting a different configuration while it is active throws an error. The application should have one owner responsible for destroying that runtime. If the app already uses `enableNativeUIShell()`, keep that single runtime and call `setVerticalControlAreaPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset })` from its listener.
|
|
138
|
+
|
|
139
|
+
On supported iOS versions the runtime hands eligible tabs, back navigation, menu buttons, and fixed-toolbar actions to a native SwiftUI `TabView` and toolbar; on Web, Android, or when native projection is unavailable, Web clones remain the fallback. Back navigation can come from outside a fixed toolbar; menu buttons and other toolbar actions still require one.
|
|
140
|
+
|
|
141
|
+
### Toolbar actions
|
|
142
|
+
|
|
143
|
+
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`.
|
|
144
|
+
|
|
145
|
+
| Icon markup | Placement |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `slot="icon-only"` | Vertical rail |
|
|
148
|
+
| `slot="start"`, `slot="end"`, or no slot | Original horizontal toolbar |
|
|
149
|
+
| No icon | Original horizontal toolbar |
|
|
150
|
+
|
|
151
|
+
This rule applies to `fill="default"`, `clear`, `solid`, and `outline`, including buttons with an Ionic `color`. Solid actions retain their background color through a prominent native button; outline actions retain their border color and width. The runtime preserves the original click or form-submit behavior. `type="submit"` and `.button-submit` do not select a different placement.
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<ion-header>
|
|
155
|
+
<ion-toolbar>
|
|
156
|
+
<ion-buttons slot="end">
|
|
157
|
+
<ion-button type="button" fill="outline" color="primary" aria-label="Done">
|
|
158
|
+
<ion-icon name="checkmark-outline" slot="icon-only"></ion-icon>
|
|
159
|
+
</ion-button>
|
|
160
|
+
</ion-buttons>
|
|
161
|
+
</ion-toolbar>
|
|
162
|
+
</ion-header>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Give icon-only actions an accessible name, such as `aria-label="Done"`. Keep the original Ionic event handler or form association on the source button.
|
|
124
166
|
|
|
125
|
-
|
|
167
|
+
The same rule applies to individual buttons and buttons inside `ion-buttons`, on ordinary pages and in the topmost full-width modal. Centered modals, menus, and popovers keep their own toolbar layout. Add `.ios-theme-horizontal-only` to a group or individual button to keep it horizontal. Placement is chosen when a routed page enters; changing an existing button's content or icon slot does not move it between the toolbar and rail until the page leaves and re-enters.
|
|
126
168
|
|
|
127
|
-
|
|
169
|
+
### Tab bar
|
|
128
170
|
|
|
129
|
-
When the app contains `ion-tabs`, its tab bar moves into the reserved region and
|
|
171
|
+
When the app contains `ion-tabs`, its tab bar moves into the reserved region and uses the native Duo edge spacing; the Ionic `slot` value does not select a different position. Without native projection, the stable Web rail is icon-only, matching the native resting presentation. While the user presses and drags across that rail, every icon-and-label tab reveals its label so the pending destination stays identifiable. The Web tab bar receives pointer input in the simulated system region. Native tabs and a restored Web tab bar fade in over 180ms; disappearance remains immediate. Reduced motion disables this fade. Use `ion-menu` when navigation should become a sidebar; this mode does not convert tabs into a menu. Web clones also work when no `ion-tabs` exists. Disabling the mode or leaving the page removes the native ownership or Web clones and restores their sources. Override `--ios-theme-vertical-bars-toolbar-top` when the simulated system controls use a different vertical layout.
|
|
130
172
|
|
|
131
173
|
## Adapt the split pane
|
|
132
174
|
|
|
@@ -154,7 +196,7 @@ ion-split-pane {
|
|
|
154
196
|
}
|
|
155
197
|
```
|
|
156
198
|
|
|
157
|
-
The registered `--ios-theme-split-pane-width` defaults to `320px`; `.ios-theme-split-pane-half-open` sets it to `50vw`. Set `halfOpened` when `
|
|
199
|
+
The registered `--ios-theme-split-pane-width` defaults to `320px`; `.ios-theme-split-pane-half-open` sets it to `50vw`. Set `halfOpened` when `foldStateChange` reports `state === 'half-opened'` (and read the initial value with `getFoldState()`). Ionic's `when` decides whether the menu is a persistent side pane; use the 900px breakpoint for a half-opened state or a flat state with hinge geometry, and the ordinary 992px breakpoint when closed or flat without geometry. Missing `hingeBounds` alone does not mean the device is flat. The application chooses where to apply this width rule; an ordinary split pane elsewhere is unchanged. This layout does not enable Vertical Bars or move an overlay menu.
|
|
158
200
|
|
|
159
201
|
## Vertical Control Area API
|
|
160
202
|
|
|
@@ -232,10 +274,11 @@ Stops synchronization, restores Web controls and releases native resources.
|
|
|
232
274
|
|
|
233
275
|
#### VerticalBarPlacement
|
|
234
276
|
|
|
235
|
-
| Prop
|
|
236
|
-
|
|
|
237
|
-
| **`edge`**
|
|
238
|
-
| **`inset`**
|
|
277
|
+
| Prop | Type | Description |
|
|
278
|
+
| ---------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
279
|
+
| **`edge`** | <code><a href="#verticalbaredge">VerticalBarEdge</a></code> | |
|
|
280
|
+
| **`inset`** | <code>number</code> | Explicit rail width in CSS pixels; omitted to use the stylesheet's safe-area rules. |
|
|
281
|
+
| **`nativeEdge`** | <code><a href="#verticalbaredge">VerticalBarEdge</a></code> | Native logical edge reported by the application's device plugin. Null or an unregistered edge uses a Web rail in verticalBarsOnly mode, or the ordinary Native UI Shell layout otherwise. Omission keeps the last supplied value. |
|
|
239
282
|
|
|
240
283
|
|
|
241
284
|
#### NativeUIShellStatus
|
package/docs/migration.md
CHANGED
|
@@ -8,8 +8,8 @@ title: Migration
|
|
|
8
8
|
|
|
9
9
|
Use `withNativeUIShellTransition()` to keep your existing Ionic navigation animation while coordinating Native UI Shell controls.
|
|
10
10
|
|
|
11
|
-
- If you already
|
|
12
|
-
- If you use Ionic's default animation without a `navAnimation` option, follow [Keep Ionic's default animation](./iphone-duo-with-original-theme.md#keep-
|
|
11
|
+
- If you already import `iosTransitionAnimation` from `@rdlabo/ionic-theme-ios27`, keep the [package animation setup](./iphone-duo-with-original-theme.md#use-this-package%27s-ios-animation); no configuration change is needed. It now uses the shared adapter internally; do not add another wrapper.
|
|
12
|
+
- If you use Ionic's default animation without a `navAnimation` option, follow [Keep Ionic's default animation](./iphone-duo-with-original-theme.md#keep-ionic%27s-default-animation). The example selects Ionic's standard iOS or MD builder from the transition mode.
|
|
13
13
|
- If you use a custom navigation animation with Native UI Shell or the standalone Vertical Control Area, wrap your existing builder when configuring Ionic:
|
|
14
14
|
|
|
15
15
|
```diff
|
|
@@ -25,7 +25,7 @@ Merge this option into your existing Ionic configuration before initialization.
|
|
|
25
25
|
|
|
26
26
|
Use the adapter only for navigation; leave modal and popover animations unchanged. Your builder must create a fresh `Animation` for each navigation because Ionic destroys it afterward. Keep lifecycle events for control registration and transitions without animation. If the custom builder animates a horizontal back button separately, exclude that effect while `.ios-theme-vertical-bars` is active.
|
|
27
27
|
|
|
28
|
-
See [Connect your navigation animation](./iphone-duo-with-original-theme.md#3
|
|
28
|
+
See [Connect your navigation animation](./iphone-duo-with-original-theme.md#3.-connect-your-navigation-animation) for the setup and supported scope.
|
|
29
29
|
|
|
30
30
|
## From the iOS 26 theme
|
|
31
31
|
|
package/docs/native-ui-shell.md
CHANGED
|
@@ -40,6 +40,8 @@ Native appearance follows the applied class, system or always-dark theme CSS. Sy
|
|
|
40
40
|
|
|
41
41
|
## Supported markup
|
|
42
42
|
|
|
43
|
+
The table below describes the ordinary Native UI Shell. Vertical Bars uses the separate [toolbar action rules](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#toolbar-actions).
|
|
44
|
+
|
|
43
45
|
| Ionic component | Supported appearance and placement | Native rendering |
|
|
44
46
|
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
45
47
|
| `ion-button` | `fill="default"`, standard glass, in a fixed header/footer toolbar | Glass `UIButton` |
|
|
@@ -62,11 +64,11 @@ Use `ios-theme-shell-disabled` to disable only the iOS Native UI Shell while kee
|
|
|
62
64
|
|
|
63
65
|
If a child inside a shared native surface opts out, the entire surface stays on the Web: this includes button groups, tab bars, segments and FAB lists. Opting out of the search FAB or any part of the search footer disables native search integration; the tab bar can still render natively if it remains eligible.
|
|
64
66
|
|
|
65
|
-
Placement is required even when the appearance is glass. In the ordinary Native UI Shell, buttons, back buttons, menu-button groups and segments need a toolbar directly inside `ion-header` or `ion-footer`, with no `ion-content` ancestor around the control. Buttons directly inside a header/footer, standalone toolbars, and toolbars or headers nested in scrolling content stay on the Web. FABs without `slot="fixed"` also stay on the Web. Moving a projected control to an excluded location restores its Web rendering; moving it back re-evaluates eligibility. When `.ios-theme-vertical-bars` is enabled, a standard `ion-back-button` can instead be projected to the Vertical Control Area from outside a fixed toolbar, including routed content or a persistent app shell. The application chooses where to enable this mode and which Ionic component mode to use; Vertical Bars projection does not require `ios` mode classes.
|
|
67
|
+
Placement is required even when the appearance is glass. In the ordinary Native UI Shell, buttons, back buttons, menu-button groups and segments need a toolbar directly inside `ion-header` or `ion-footer`, with no `ion-content` ancestor around the control. Buttons directly inside a header/footer, standalone toolbars, and toolbars or headers nested in scrolling content stay on the Web. FABs without `slot="fixed"` also stay on the Web. Moving a projected control to an excluded location restores its Web rendering; moving it back re-evaluates eligibility. When `.ios-theme-vertical-bars` is enabled, a standard `ion-back-button` can instead be projected to the Vertical Control Area from outside a fixed toolbar, including routed content or a persistent app shell. The application chooses where to enable this mode and which Ionic component mode to use; Vertical Bars projection does not require `ios` mode classes. Collapsed headers, opted-out controls and departed pages are excluded. Full-width foreground modals can participate in Vertical Bars as described below; other overlay surfaces keep their own layout.
|
|
66
68
|
|
|
67
69
|
Native tabs accept equal-width items with Ionic's default `layout="icon-top"`. The native bar uses a local compact horizontal and regular vertical size class to preserve the Web's stacked icon/label arrangement on iPad and in landscape. This does not change the app's size class. Label size and weight follow the Web snapshot. Other explicit Ionic layouts (`icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `label-hide`) and unequal item widths keep the entire tab bar on the Web. Start, center and end placement follow the original `ion-tab-bar`, including RTL. Directional `ion-icon` artwork preserves its rendered RTL flip.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
Outside Vertical Bars, standalone clear, solid and outline buttons are excluded. A glass `ion-buttons` group of two or more clear buttons is projected as one surface; its children retain separate actions. Menu buttons can also share that group. A single menu button uses its parent `ion-buttons` as the glass surface, so no Web glass is left underneath the native button. A menu button outside this theme glass remains on the Web. Mixed fills, unsupported children, or a theme-disabled child keep the group on the Web. Single clear buttons remain on the Web. Custom button colors, custom back icons/colors, collapsing headers, toolbars inside scrolling content, modal content, scrollable/expanded segments and segment-view integration remain on the Web. Complex slots and unsupported SVG features also fall back to Web. The plugin does not translate arbitrary application CSS into UIKit styles.
|
|
70
72
|
|
|
71
73
|
Native glass samples the Web content actually drawn behind it. Existing toolbar backgrounds and header blur still affect that content. For content to scroll beneath a header, use Ionic's normal translucent-header/fullscreen-content layout; the plugin does not move page content or override an application-owned opaque toolbar background.
|
|
72
74
|
|
|
@@ -88,13 +90,15 @@ The native host accepts input only within native controls. Tab interaction and a
|
|
|
88
90
|
|
|
89
91
|
## Searchable tabs
|
|
90
92
|
|
|
91
|
-
Existing `attachTabBarSearchable(tabBar, fabButton, footer)` registrations automatically use native search when their bottom tab bar and glass search controls are supported. No new component option, route, native setup or page listener is required. Ordinary tab bars continue using `UITabBar`; searchable groups use a persistent `UITabBarController`, `UITab` / `UISearchTab`, and `UISearchController`. The original Capacitor WebView continues to render results and handle navigation.
|
|
93
|
+
Existing `attachTabBarSearchable(tabBar, fabButton, footer)` registrations automatically use native search when their bottom tab bar and glass search controls are supported. No new component option, route, native setup or page listener is required. Ordinary tab bars continue using `UITabBar`; horizontal searchable groups use a persistent `UITabBarController`, `UITab` / `UISearchTab`, and `UISearchController`. The original Capacitor WebView continues to render results and handle navigation.
|
|
94
|
+
|
|
95
|
+
With vertical bars, the native rail keeps every tab and its selection. SwiftUI’s `searchable` and `searchToolbarBehavior(.minimize)` provide the system search button, field, dismissal control and transitions within the same native navigation surface. Apple determines their placement for the current device layout and focuses the native field when system search opens. Input, focus, clear and submit events use the same bridge as horizontal search. When native search is unavailable, the Web search presentation also preserves vertical tabs.
|
|
92
96
|
|
|
93
97
|
Search registration does not bypass placement restrictions. Its searchbar and close button must be in fixed footer toolbars. Its trigger must belong to an `ion-fab[slot="fixed"]` directly inside `ion-content`, or directly on the existing non-scrolling `.ion-page` layout. A wrapper inside scrolling content is not a fixed slot.
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
For horizontal tabs, while a registration is alive, Native UI Shell keeps the searchable controller even when the page is transitioning or temporarily unavailable (`available: false`), instead of demoting back to a separate ordinary control identity. Resting chrome (including Album before search opens) uses the same `UITabBar` + `ShellTabBar.fit` path as ordinary tabs, with the search trigger pinned to the FAB when available; `UISearchTab` is shown only while the search session is active. Flipping availability or entering/leaving search crossfades between those layers. Register before the destination page finishes entering (for example in `ionViewWillEnter`) so the first visit does not paint ordinary tabs and then swap.
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
With horizontal tabs, opening search preserves the selected Ionic tab and does not automatically show the keyboard (`automaticallyActivatesSearch` stays off). Tap the field or call `ion-searchbar.setFocus()` for the keyboard. While search is active, Native UI Shell freezes Web layout projection and holds Capacitor Keyboard resize at `none`; UIKit owns tab/search chrome (no Ionic `fit` mid-session). Leave re-fits ordinary tabs to `ion-tab-bar` and search to the FAB. Ordinary native tabs keep an optimistic selection until the Web `selected` state catches up. Input events and application `value` updates continue across the bridge until search closes. The trigger's resolved SVG and the search icon are projected from Ionic, including `ion-icon name`.
|
|
98
102
|
|
|
99
103
|
Native edits pass through Ionic's input handlers, preserving `ionInput` debounce, `ionChange`, `ionFocus`, `ionBlur`, and `ionClear`. Programmatic `value` changes do not emit `ionInput`; synchronous application corrections and stale native input are distinguished. Native editing owns marked text and the caret. Returning through the footer's close action retains the value and does not emit `ionCancel` or `ionClear`.
|
|
100
104
|
|
|
@@ -116,7 +120,7 @@ FAB support covers the standard circular glass appearance, text and resolved sta
|
|
|
116
120
|
|
|
117
121
|
Custom host animation or transition declarations on the FAB, list or button keep the group on the Web until removed. Button transforms support the standard identity and hidden-child scale(0), not custom scaling. For children inside a `display:none` list, browsers can report a computed transform of `none` even when a custom transform is declared. Such transforms are checked when layout becomes available; the whole FAB then returns to Web if needed. The plugin does not parse application stylesheets or temporarily open lists to predict hidden layout.
|
|
118
122
|
|
|
119
|
-
The built-in `iosTransitionAnimation` waits for native retirement before starting the Web animation. Interactive progress and completion/cancellation are queued while that retirement is in progress. Stationary shared tabs are retained. First render and transitions without an animation builder are covered by the startup runtime and Ionic lifecycle events. Default Ionic and custom navigation builders can use the same integration through `withNativeUIShellTransition()`, exported from both the package root and `/vertical-bars`. See [Connect your navigation animation](./iphone-duo-with-original-theme.md#3
|
|
123
|
+
The built-in `iosTransitionAnimation` waits for native retirement before starting the Web animation. Interactive progress and completion/cancellation are queued while that retirement is in progress. Stationary shared tabs are retained. First render and transitions without an animation builder are covered by the startup runtime and Ionic lifecycle events. Default Ionic and custom navigation builders can use the same integration through `withNativeUIShellTransition()`, exported from both the package root and `/vertical-bars`. See [Connect your navigation animation](./iphone-duo-with-original-theme.md#3.-connect-your-navigation-animation) for the setup.
|
|
120
124
|
|
|
121
125
|
Tab switches skip the Web/native crossfade so a retiring UIKit snapshot cannot linger over the next tab. Detection uses the router URL versus the still-selected tab on `ionViewWillLeave` (and vanilla `ionTabsWillChange` / `ionTabsDidChange` DOM events). Stack pushes and pops keep the normal 180ms handoff.
|
|
122
126
|
|
|
@@ -175,9 +179,9 @@ The standalone Vertical Control Area entry point (`@rdlabo/ionic-theme-ios27/ver
|
|
|
175
179
|
|
|
176
180
|
On supported iOS versions, adding `.ios-theme-vertical-bars` changes only controls that the system relocates into the physical side rail. Native UI Shell presents eligible tabs, back navigation, menu buttons, and toolbar actions through a SwiftUI `TabView` and toolbar once the class is applied. When the OS reports a rail edge — on iPhone Duo linked against iOS 27.1 or later — it must agree with the applied placement; a disagreeing report keeps the rail on the Web. Older toolchains that cannot report an edge trust the DOM placement directly. SwiftUI owns their adaptive placement and Liquid Glass appearance; Ionic remains the source of labels, icons, selected/disabled state, routing, form submission, and click handlers.
|
|
177
181
|
|
|
178
|
-
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.
|
|
182
|
+
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. A topmost full-width modal supplies its own eligible toolbar actions and rail bounds, including when it is a sheet. Centered modals, menus, and popovers keep their own toolbar layout. Modal placement follows the visible dialog width rather than its type or hinge posture.
|
|
179
183
|
|
|
180
|
-
|
|
184
|
+
In Vertical Bars, fixed-toolbar `ion-button` actions use an `ion-icon` or SVG with `slot="icon-only"`. All fills (`default`, `clear`, `solid`, and `outline`) and Ionic colors are eligible; submit buttons follow the same placement rule. Solid actions retain their background color and outline actions retain their border color and width. Actions without `icon-only` stay horizontal. Scrolling and explicitly opted-out controls retain their Web presentation. Add `.ios-theme-horizontal-only` to an `ion-buttons` group or individual `ion-button` to keep it in the horizontal Web toolbar. On Web, Android, older iOS, or when native projection is unavailable during setup, the Web projection remains the fallback.
|
|
181
185
|
|
|
182
186
|
For standalone setup while keeping your existing theme, see [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme).
|
|
183
187
|
|
package/docs/special-markup.md
CHANGED
|
@@ -166,19 +166,6 @@ The example uses Ionic's standard collapsible large-title structure. Scroll the
|
|
|
166
166
|
|
|
167
167
|
The `.ion-page` wrapper makes this embedded preview behave like a complete routed page. An application using `ion-router-outlet` normally receives that page container automatically. The inset list and its items only provide enough content to demonstrate scrolling; they are not required by `.searchbar-classic`.
|
|
168
168
|
|
|
169
|
-
## Search-bar toolbars
|
|
170
|
-
|
|
171
|
-
Add `.toolbar-searchbar` when an `ion-toolbar` combines a search bar with start or end buttons. The class centers the slotted controls and adjusts the spacing around the search field.
|
|
172
|
-
|
|
173
|
-
```html preview
|
|
174
|
-
<ion-toolbar class="toolbar-searchbar">
|
|
175
|
-
<ion-buttons slot="start">
|
|
176
|
-
<ion-button>Cancel</ion-button>
|
|
177
|
-
</ion-buttons>
|
|
178
|
-
<ion-searchbar></ion-searchbar>
|
|
179
|
-
</ion-toolbar>
|
|
180
|
-
```
|
|
181
|
-
|
|
182
169
|
## Opting out
|
|
183
170
|
|
|
184
171
|
Add `.ios-theme-disabled` to an individual Ionic component when it must retain Ionic's standard iOS styling.
|
|
@@ -35,7 +35,7 @@ final class ShellSearchHost: UIView {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Preserve UISearchBar's delegate while returning its clear action to Ionic.
|
|
38
|
-
|
|
38
|
+
final class ShellSearchInputDelegate: NSObject, UITextFieldDelegate {
|
|
39
39
|
weak var original: UITextFieldDelegate?
|
|
40
40
|
var clear: (() -> Void)?
|
|
41
41
|
override func responds(to selector: Selector!) -> Bool {
|