@rdlabo/ionic-theme-ios27 2.0.0-beta.pr304.sha56daf151ad80 → 2.0.0-beta.pr310.shab88b79526a19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ionic Theme iOS27
2
2
 
3
- Install with `npm install @rdlabo/ionic-theme-ios27`. For 1.x, use the [v1.2.1 guide](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.1).
3
+ Install with `npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable`. For 1.x, use the [v1.2.1 guide](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.1).
4
4
  A theme for Ionic apps that brings iOS 27 Liquid Glass and motion to the Web. Capacitor iOS apps can also opt into a preview Native UI for supported controls.
5
5
 
6
6
  **[Ionic 9 demo](https://ionic-theme-ios27.rdlabo.dev/) · [Ionic 8 demo](https://ionic8-theme-ios27.rdlabo.dev/) · [Setup guide](docs/setup.md)**
@@ -17,17 +17,19 @@ A theme for Ionic apps that brings iOS 27 Liquid Glass and motion to the Web. Ca
17
17
 
18
18
  ## What would you like to do?
19
19
 
20
- | Goal | Guide |
21
- | ----------------------------------------------------- | -------------------------------------------------------------------- |
22
- | Style an existing Ionic app | [Get started](docs/setup.md) |
23
- | Select iOS 26/27 styles and optionally use MD3 | [Adaptive themes](docs/adaptive-themes.md) |
24
- | Add native iOS controls after setting up the theme | [Add Native UI](docs/add-native-ui.md) |
25
- | Add a side rail to Ionic while keeping your own theme | [Vertical Bars step by step](docs/iphone-duo-with-original-theme.md) |
26
- | Upgrade an existing installation | [Migration](docs/migration.md) |
20
+ | Goal | Guide |
21
+ | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
22
+ | Style an existing Ionic app | [Get started](docs/setup.md) |
23
+ | Select iOS 26/27 styles and optionally use MD3 | [Adaptive themes](docs/adaptive-themes.md) |
24
+ | Add native iOS controls after setting up the theme | [Add Native UI](docs/add-native-ui.md) |
25
+ | Add a side rail to Ionic while keeping your own theme | [Vertical Bars step by step](../capacitor-native-ui/docs/ionic-vertical-bars.md) |
26
+ | Upgrade an existing installation | [Migration](docs/migration.md) |
27
27
 
28
28
  ## Get started
29
29
 
30
- Use the [four-step setup guide](docs/setup.md) to install the theme, load styles, configure animations, and preview your first screen. It supports Ionic 8.8.1 or later within Ionic 8 or 9, including Angular, React, and Vue. Capacitor is not required.
30
+ We use [Capacitor Foldable](https://github.com/erkamyaman/capacitor-foldable), an excellent community plugin by Erkam Yaman, as our standard device-layout integration. It exposes fold state, hinge geometry, bar placement and change events to your application, giving you control over how the layout responds. Foldable handles device reporting; the theme provides Web layout helpers and Native UI projects controls. Install it alongside the package you use, then connect its events in your app.
31
+
32
+ Use the [four-step setup guide](docs/setup.md) to install the theme, load styles, configure animations, and preview your first screen. It supports Ionic 8.8.1 or later within Ionic 8 or 9, including Angular, React, and Vue. A native Capacitor project is not required for browser use.
31
33
 
32
34
  The theme styles toolbars, tabs, lists, buttons, search, and overlays, with light and dark appearances. It does not activate native controls. Once the Web theme is working, you can optionally [add Native UI](docs/add-native-ui.md).
33
35
 
@@ -38,5 +40,4 @@ The theme styles toolbars, tabs, lists, buttons, search, and overlays, with ligh
38
40
  - [Using ion-item-group](docs/using-ion-item-group.md): inset list markup.
39
41
  - [Animation](docs/animation.md): tab, segment, and search effects.
40
42
  - [ESLint](docs/eslint.md): catch unsupported list structure.
41
- - [Vertical Bars layout](docs/vertical-bars-layout.md): spacing, RTL, and layout helpers.
42
- - [iPhone Duo device layout](docs/iphone-duo.md): connect device placement and hinge posture.
43
+ - [Vertical Bars layout](docs/vertical-bars-layout.md): spacing, RTL, device placement, posture, and split panes.
@@ -7,7 +7,7 @@ Use this optional setup when you want browser capabilities to select iOS styles,
7
7
  Install both themes in an existing Ionic 8 or 9 app (`@ionic/core` 8.8.1 or later):
8
8
 
9
9
  ```bash
10
- npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27
10
+ npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable
11
11
  ```
12
12
 
13
13
  ## 2. Load styles
@@ -42,7 +42,17 @@ If compilation reports `Can't find stylesheet to import.` for a `meta.load-css()
42
42
 
43
43
  Alternatively, use a relative path from the Sass file containing `meta.load-css()` to the installed package, such as `../node_modules/@rdlabo/ionic-theme-ios27/src/styles/default-variables` from `src/styles.scss`. Adjust the `../` prefix for your file's location, and apply the same change to each theme import.
44
44
 
45
- These checks select styles by browser features, not by iOS version. Browsers without either feature retain Ionic's default iOS appearance. The example uses class-based dark mode: also load [Ionic's matching dark palette](https://ionicframework.com/docs/theming/dark-mode). For system or always-dark mode, replace both `-dark-class` imports with the matching variant. The `md-remove-ios-class-effect` styles prevent iOS-specific classes from affecting Material Design mode.
45
+ ### Which styles are selected?
46
+
47
+ The checks select browser capabilities, not iOS versions:
48
+
49
+ | Browser capability in Ionic `ios` mode | Result |
50
+ | ---------------------------------------------------- | ------------------------------- |
51
+ | `overflow-anchor: auto` | iOS 27 theme. |
52
+ | `text-wrap: pretty`, without `overflow-anchor: auto` | iOS 26 theme. |
53
+ | Neither feature | Ionic's default iOS appearance. |
54
+
55
+ The example uses class-based dark mode: also load [Ionic's matching dark palette](https://ionicframework.com/docs/theming/dark-mode). For system or always-dark mode, replace both `-dark-class` imports with the matching variant; see the [dark-mode pairs](setup.md#choose-one-dark-mode-pair). The `md-remove-ios-class-effect` styles prevent iOS-specific classes from affecting Material Design mode.
46
56
 
47
57
  ## 3. Configure animations
48
58
 
@@ -96,7 +106,7 @@ Use this markup to preview the inset grouped list look. For the list structure t
96
106
  Install all three themes to use iOS 27 on supported Safari versions, fall back to iOS 26 on the preceding Safari generation, and use MD3 whenever Ionic runs in Material Design mode. All three themes require `@ionic/core` 8.8.1 or later:
97
107
 
98
108
  ```bash
99
- npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3
109
+ npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3 @erkamyaman/capacitor-foldable
100
110
  ```
101
111
 
102
112
  Keep the two iOS themes behind the same browser feature checks used in step 2 above, then load MD3 unconditionally. Using `meta.load-css()` throughout also keeps the MD3 styles after the conditional iOS styles in the generated CSS:
@@ -10,10 +10,10 @@ Read [how projection works](../../capacitor-native-ui/README.md#how-projection-w
10
10
 
11
11
  This guide assumes a Capacitor 8 application with an iOS project. Web-only theme users can continue without Capacitor; adding native iOS controls requires setting up that native project first. See [Native UI requirements](../../capacitor-native-ui/README.md#requirements) for supported Ionic, iOS, and Xcode versions.
12
12
 
13
- Install both packages together from npm so the theme and native plugin use the same version:
13
+ Update the theme and Native UI together to keep their versions aligned, and install Foldable as a separate application dependency:
14
14
 
15
15
  ```sh
16
- npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui
16
+ npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable
17
17
  ```
18
18
 
19
19
  Keep your existing theme stylesheets. Capacitor's default plugin discovery requires a direct dependency; the copy installed through the theme alone does not register the native plugin.
@@ -46,7 +46,7 @@ Run the app on a supported iOS device or simulator. Eligible fixed controls shou
46
46
 
47
47
  ## Optional: Vertical Bars
48
48
 
49
- For a side rail, load the [Vertical Bars layout stylesheet](vertical-bars-layout.md) and configure [device placement](iphone-duo.md). The Shell includes the rail runtime; no additional startup call is needed. To automatically select rail controls, use `enableNativeUI({ controls: 'verticalOnly' })` instead of the default startup call. See [configuration options](../../capacitor-native-ui/docs/configuration.md).
49
+ For a side rail, follow the [standard Vertical Bars setup](../../capacitor-native-ui/docs/ionic-vertical-bars.md): load the layout stylesheet and connect the installed Foldable plugin’s initial placement and events. The Shell includes the rail runtime; no additional startup call is needed. To automatically select rail controls, use `enableNativeUI({ controls: 'verticalOnly' })` instead of the default startup call. See [configuration options](../../capacitor-native-ui/docs/configuration.md).
50
50
 
51
51
  ## Upgrading an existing native integration
52
52
 
package/docs/animation.md CHANGED
@@ -61,9 +61,25 @@ const effect = supportSeachbarCancelButtonIcon(searchbar);
61
61
  // effect.destroy();
62
62
  ```
63
63
 
64
- Register after the Ionic element has initialized (for example, Angular's `ngAfterViewInit`). The helper preserves Ionic's button and event handlers, and `destroy()` restores its text content. It does not apply to MD mode, `searchbar-classic`, `ios-theme-disabled`, or `ios26-disabled` searchbars. The CSS animation follows `animated` and respects `prefers-reduced-motion`. Standard searchbar CSS variables such as `--background`, `--box-shadow`, `--border-radius`, and `--cancel-button-color` remain available.
64
+ ### Registration and exclusions
65
65
 
66
- The clear button shares the 44px glass design and appears beside the input. It uses Ionic's `clearIcon`, `showClearButton`, and `--clear-button-color`; clearing retains input focus, while cancel ends the search. Clear and cancel can be displayed together. Set `clearIcon` and `cancelButtonIcon` to the same icon to give them the same appearance.
66
+ Register after the Ionic element has initialized (for example, Angular's `ngAfterViewInit`).
67
+
68
+ | Condition | Behavior |
69
+ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
70
+ | Supported iOS-mode searchbar | Preserves Ionic's button and event handlers; `destroy()` restores text content. |
71
+ | MD mode, `searchbar-classic`, `ios-theme-disabled` or `ios26-disabled` | Helper does not apply. |
72
+ | `animated` / `prefers-reduced-motion` | Respected by the CSS animation. |
73
+ | CSS customization | `--background`, `--box-shadow`, `--border-radius` and `--cancel-button-color` remain available. |
74
+
75
+ ### Clear and cancel buttons
76
+
77
+ | Button | Ionic appearance settings | Action |
78
+ | ------ | ------------------------------------------------------ | ----------------------------------- |
79
+ | Clear | `clearIcon`, `showClearButton`, `--clear-button-color` | Clears while retaining input focus. |
80
+ | Cancel | `cancelButtonIcon`, `--cancel-button-color` | Ends the search. |
81
+
82
+ Both can appear together beside the input. Set `clearIcon` and `cancelButtonIcon` to the same icon for matching appearance.
67
83
 
68
84
  Both external glass buttons use a 44px resting size and expand while pressed. Reduced motion disables scaling. The clear button appears outside the text field; Ionic supplies the icons.
69
85
 
package/docs/eslint.md CHANGED
@@ -2,6 +2,8 @@
2
2
  title: Keep lists consistent with ESLint
3
3
  ---
4
4
 
5
+ # Keep lists consistent with ESLint
6
+
5
7
  Catch missing list groups before they reach the screen. In Ionic Angular apps, `@rdlabo/eslint-plugin-rules` checks the markup used by iOS 27.
6
8
 
7
9
  ## Enable the list check
package/docs/features.md CHANGED
@@ -11,9 +11,13 @@ Customize the theme with CSS variables and Sass mixins, or adopt it one componen
11
11
  To customize the library's default styles to match your design, several CSS variables are provided. See this file for details:
12
12
  [Default variables](../src/styles/default-variables.scss)
13
13
 
14
- For menus, `--ios-theme-menu-background-rgb` sets the surface RGB channels (light: `225, 230, 240`; dark: `26, 31, 34`). `--ios-theme-menu-background-opacity` controls opacity and defaults to `0.96`. Both are public customization variables and can be set on `ion-menu`.
14
+ | Variable | Default | Purpose |
15
+ | ------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------- |
16
+ | `--ios-theme-menu-background-rgb` | Light: `225, 230, 240`; dark: `26, 31, 34` | Menu surface RGB channels; set on `ion-menu`. |
17
+ | `--ios-theme-menu-background-opacity` | `0.96` | Menu surface opacity; set on `ion-menu`. |
18
+ | `--ios-theme-button-press-duration` | `380ms` | Duration of the button's `:active` scaling with slight overshoot. |
15
19
 
16
- Button press scaling follows `:active` with a slight overshoot. Set `--ios-theme-button-press-duration` (default: `380ms`) to customize its duration instead of the shared activated-transition duration variables.
20
+ Use `--ios-theme-button-press-duration` instead of the shared activated-transition duration variables to customize button press scaling.
17
21
 
18
22
  ## Liquid Glass mixin
19
23
 
package/docs/migration.md CHANGED
@@ -13,7 +13,27 @@ Theme styles and ordinary theme API imports remain supported. If you previously
13
13
  | `@rdlabo/ionic-theme-ios27/native` | `@rdlabo/capacitor-native-ui` |
14
14
  | `@rdlabo/ionic-theme-ios27/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
15
15
 
16
- Add the native package as a direct application dependency, update imports, rebuild Web assets, and run `npx cap sync ios`. Follow the [Native UI migration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/migration.md) for the intermediate package rename and old Swift package references. Theme-only apps do not need native startup or plugin setup.
16
+ Add the native package as a direct application dependency, update imports, rebuild Web assets, and run `npx cap sync ios`. Follow the [Native UI migration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/migration.md) for 1.2.1 startup settings and native project changes. Theme-only apps do not need native startup or plugin setup.
17
+
18
+ ## Device layout in 2.0
19
+
20
+ Install Foldable alongside the theme as part of the standard setup:
21
+
22
+ ```sh
23
+ npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable
24
+ ```
25
+
26
+ Replace the former device-layout APIs with Foldable subscriptions:
27
+
28
+ | Previous API | Replacement |
29
+ | ------------------------------------ | ----------------------------------------------------------------------------------- |
30
+ | `DeviceLayout`, `HingeStatus` | `BarPlacement`, `FoldState` and `HingeStatus` from `@erkamyaman/capacitor-foldable` |
31
+ | `getDeviceLayout()` | `Foldable.getBarPlacement()` and `Foldable.getFoldState()` |
32
+ | `deviceLayoutChange` | `barPlacementChange` and `foldStateChange` listeners |
33
+ | Start/stop device-layout monitoring | Subscribe with `addListener()`; remove each listener at teardown |
34
+ | One-shot WebView corner-radius reads | Native UI's `getWebViewMetrics()` |
35
+
36
+ Apply placement with `setVerticalBarsLayout` and posture with `applyFoldStateClasses`, both imported from the theme. Read the initial state as well as subscribing to changes. See [device layout and split panes](vertical-bars-layout.md#read-the-device-layout) and [rail projection](../../capacitor-native-ui/docs/vertical-bars.md#use-with-capacitor-foldable) for the current setup. Installing the packages alone does not subscribe to device events or activate projection.
17
37
 
18
38
  ## From the iOS 26 theme
19
39
 
@@ -24,7 +44,7 @@ For an app using `@rdlabo/ionic-theme-ios26`, the recommended migration keeps th
24
44
  Keep the iOS 26 package and add iOS 27. The new theme requires `@ionic/core` 8.8.1 or later (Ionic 8 or 9).
25
45
 
26
46
  ```bash
27
- npm install @rdlabo/ionic-theme-ios27
47
+ npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable
28
48
  ```
29
49
 
30
50
  ### 2. Make the styles adaptive
@@ -121,9 +141,12 @@ Change the animation import from `@rdlabo/ionic-theme-ios26` to `@rdlabo/ionic-t
121
141
 
122
142
  For the iOS 27 branch or an iOS 27-only app, use `@rdlabo/ionic-theme-ios27`. Its stylesheets are `ionic-theme-ios27.scss` or `ionic-theme-ios27.css`, including the `-dark-always`, `-dark-system`, and `-dark-class` variants. Keep the iOS 26 stylesheet names in the iOS 26 branch of an adaptive setup.
123
143
 
124
- Use the version-independent `--ios-theme-*` CSS variables. The corresponding `--ios26-*` variables remain supported as deprecated fallbacks. When both are set, the new name takes precedence. For example, use `--ios-theme-content-box-shadow-rgb` instead of `--ios26-content-box-shadow-rgb`.
144
+ | Customization | Current name | Legacy behavior |
145
+ | ------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
146
+ | CSS variables | `--ios-theme-*`, for example `--ios-theme-content-box-shadow-rgb` | Corresponding `--ios26-*` names remain deprecated fallbacks. Current names win when both are set. |
147
+ | Theme opt-out class | `ios-theme-disabled` | `ios26-disabled` remains a deprecated alias; migrate existing markup when convenient. |
125
148
 
126
- For opting out of the theme, use the version-independent `ios-theme-disabled` class. The `ios26-disabled` class remains supported as a deprecated alias; migrate existing markup when convenient.
149
+ Use the version-independent names for new customizations.
127
150
 
128
151
  See [Special markup and classes](special-markup.md) and [Default variables](../src/styles/default-variables.scss) for the current names.
129
152
 
package/docs/setup.md CHANGED
@@ -4,14 +4,16 @@ Give an existing Ionic app the iOS 27 appearance in four steps. This guide uses
4
4
 
5
5
  ## Before you start
6
6
 
7
- Use an existing Ionic Angular, React, or Vue application with Ionic `>=8.8.1 <10`. Keep Ionic's base CSS imports. Capacitor is not required, including for browser-only apps.
7
+ Use an existing Ionic Angular, React, or Vue application with Ionic `>=8.8.1 <10`. Keep Ionic's base CSS imports. Browser-only apps do not need a native Capacitor project.
8
8
 
9
- ## 1. Install the theme
9
+ ## 1. Install the theme and Foldable
10
10
 
11
11
  ```sh
12
- npm install @rdlabo/ionic-theme-ios27
12
+ npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable
13
13
  ```
14
14
 
15
+ [Capacitor Foldable](https://github.com/erkamyaman/capacitor-foldable) is our standard integration for device state. Its placement and posture APIs let your app control how the layout adapts; installing it is followed by connecting its initial values and change events. Connect it using the [layout guide](vertical-bars-layout.md) when configuring device layout.
16
+
15
17
  ## 2. Load the styles
16
18
 
17
19
  Add these imports to your global stylesheet after Ionic's base styles. Replace an existing Ionic dark palette import rather than loading multiple palettes:
@@ -24,7 +26,17 @@ Add these imports to your global stylesheet after Ionic's base styles. Replace a
24
26
  @import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-system.css';
25
27
  ```
26
28
 
27
- For a manual dark-mode toggle, use Ionic's `dark.class.css` and the theme's `ionic-theme-ios27-dark-class.css`, then toggle `ion-palette-dark` on the document root. For always-dark styling, use `dark.always.css` and `ionic-theme-ios27-dark-always.css`. Choose one matching pair.
29
+ ### Choose one dark-mode pair
30
+
31
+ Use one matching Ionic palette and theme variant. Paths below are relative to `@ionic/core/css/palettes/` and `@rdlabo/ionic-theme-ios27/dist/css/` respectively.
32
+
33
+ | Mode | Ionic palette | Theme stylesheet | Activation |
34
+ | ---------------------- | ----------------- | ----------------------------------- | ----------------------------------------------- |
35
+ | System (example above) | `dark.system.css` | `ionic-theme-ios27-dark-system.css` | System appearance. |
36
+ | Manual | `dark.class.css` | `ionic-theme-ios27-dark-class.css` | Toggle `ion-palette-dark` on the document root. |
37
+ | Always dark | `dark.always.css` | `ionic-theme-ios27-dark-always.css` | Always active. |
38
+
39
+ If you also enable Native UI, these styles provide its light/dark appearance marker. Keep the Ionic palette and theme variant paired so Web and native controls agree. See [native appearance inheritance](../../capacitor-native-ui/docs/appearance.md) for the resolution order and shared rail behavior.
28
40
 
29
41
  ## 3. Configure Ionic animations
30
42
 
@@ -78,5 +90,5 @@ You should see an inset group with rounded edges. Switch the system between ligh
78
90
 
79
91
  - [Customize styles and components](features.md).
80
92
  - [Add Native UI](add-native-ui.md) for supported controls in a Capacitor iOS app. Theme installation alone does not enable native controls.
81
- - [Add Vertical Bars with an existing theme](iphone-duo-with-original-theme.md).
93
+ - [Add Vertical Bars with an existing theme](../../capacitor-native-ui/docs/ionic-vertical-bars.md).
82
94
  - [Migrate an existing app](migration.md).
@@ -28,15 +28,38 @@ buttons: [
28
28
  ];
29
29
  ```
30
30
 
31
- Buttons with no role or `default` keep the normal text color. `cancel` retains Ionic's cancellation behavior, `selected` remains a selection state, and `destructive` uses `--ios-theme-destructive-color`. An existing `confirm` role is not treated as preferred. Use `preferred` for the recommended action, not simply any action that confirms a choice.
31
+ Choose the role by action semantics:
32
+
33
+ | Role | Appearance and behavior |
34
+ | -------------------- | ------------------------------------------------------------------------------------------------- |
35
+ | `preferred` | Filled primary action; dismissal reports `preferred`. Does not automatically select or invoke it. |
36
+ | No role or `default` | Normal text color. |
37
+ | `cancel` | Retains Ionic's cancellation behavior. |
38
+ | `selected` | Retains selection state. |
39
+ | `destructive` | Uses `--ios-theme-destructive-color`. |
40
+ | `confirm` | Not treated as preferred. Use `preferred` for a recommended action, not simply any confirmation. |
32
41
 
33
42
  ## Floating iPad sheets
34
43
 
35
- Set `expandToScroll: false` on a sheet modal to use floating lower corners and a 20px bottom gap on iPad. Ionic then sizes the visible page at each breakpoint, so the theme can style it with CSS alone. Content scrolls within the current breakpoint; dragging the handle still resizes the sheet. With the default `expandToScroll: true`, the sheet keeps Ionic's bottom-attached layout and scroll-to-expand behavior.
44
+ Choose the sheet's scrolling behavior with `expandToScroll`:
45
+
46
+ | Setting | iPad appearance | Scrolling and resizing |
47
+ | ---------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `false` | Floating lower corners and a 20px bottom gap. | Content scrolls within the current breakpoint; dragging the handle resizes the sheet. Ionic sizes the visible page at each breakpoint so CSS can style it. |
49
+ | `true` (default) | Ionic's bottom-attached layout. | Scroll-to-expand behavior. |
36
50
 
37
51
  ## Tab bar position
38
52
 
39
- Add one of `tab-bar-position-start`, `tab-bar-position-center`, or `tab-bar-position-end` to an iOS `ion-tab-bar` to position the whole bar within its safe area. These classes work with both `slot="top"` and `slot="bottom"` and preserve the bar's width and press animation. Start and end follow the text direction (reversed in RTL). Without a class, the existing placement is unchanged.
53
+ Choose a class on an iOS `ion-tab-bar` to position the whole bar within its safe area:
54
+
55
+ | Class | Placement |
56
+ | ------------------------- | ------------------------------- |
57
+ | `tab-bar-position-start` | Start of the reading direction. |
58
+ | `tab-bar-position-center` | Center. |
59
+ | `tab-bar-position-end` | End of the reading direction. |
60
+ | None | Existing placement unchanged. |
61
+
62
+ All three classes support `slot="top"` and `slot="bottom"`, preserve width and press animation, and resolve start/end in RTL.
40
63
 
41
64
  ```html
42
65
  <ion-tab-bar slot="bottom" class="tab-bar-position-center">
@@ -47,15 +70,15 @@ Add one of `tab-bar-position-start`, `tab-bar-position-center`, or `tab-bar-posi
47
70
 
48
71
  These classes do not reposition a separate `ion-fab`; leave room for it when choosing the bar's position.
49
72
 
50
- ## Support iPhone Duo (preview)
73
+ ## Support iPhone Duo
51
74
 
52
- iPhone Duo support, including standalone vertical bars, is available as a **preview** alongside Native UI. Its APIs and supported behavior may change.
75
+ iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.
53
76
 
54
- iPhone Duo support — the vertical system rail, hinge posture, and the posture-driven split-pane layout — is independent of the iOS 27 theme and the full Native UI. See [iPhone Duo support](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo.md) for the complete setup, including device-layout monitoring without a projection runtime.
77
+ iPhone Duo support — the vertical system rail, hinge posture, and the posture-driven split-pane layout — is independent of the iOS 27 theme and the full Native UI. See [Device layout and split panes](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md) for the complete setup, including device-layout monitoring without a projection runtime.
55
78
 
56
79
  See [Vertical Bars](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/vertical-bars.md) for rail layout classes, control eligibility and native button appearance.
57
80
 
58
- For standalone setup while keeping your existing theme, see [iPhone Duo with your existing theme](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md).
81
+ For standalone setup while keeping your existing theme, see [iPhone Duo with your existing theme](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/ionic-vertical-bars.md).
59
82
 
60
83
  ## Two-line inset list items
61
84
 
@@ -115,7 +138,15 @@ For colored segments, use Ionic's `color` property (for example, `color="primary
115
138
 
116
139
  Add `.segment-expand` when segment buttons should divide the available width evenly. The class also changes the Liquid Glass effect sizing when `registerSegmentEffect` is used.
117
140
 
118
- Segments use a 32px minimum height in content and a 48px minimum height inside `ion-toolbar`. `.segment-expand` keeps the compact 32px layout in a toolbar. Compact segments retain Ionic's flat background and indicator colors; only the regular toolbar variant has a glass container and scales its outer container while pressed. Content and expanded segments keep their outer bounds. The optional moving glass lens is independent of the container's background.
141
+ ### Segment size and press behavior
142
+
143
+ | Placement | Minimum height | Container behavior |
144
+ | ----------------------------------------- | -------------- | ---------------------------------------------------------------- |
145
+ | In content | 32px | Compact, flat background and indicator; outer bounds stay fixed. |
146
+ | Inside `ion-toolbar` | 48px | Glass container; outer container scales while pressed. |
147
+ | `.segment-expand`, including in a toolbar | 32px | Compact, flat background and indicator; outer bounds stay fixed. |
148
+
149
+ The optional moving glass lens is independent of the container's background.
119
150
 
120
151
  ```html preview
121
152
  <ion-segment class="segment-expand" value="new">
@@ -3,17 +3,19 @@
3
3
  This optional theme stylesheet owns Web spacing and positioning around a vertical rail. It can be used with your existing theme without loading the full iOS 27 appearance. Native rendering and state synchronization are provided separately by [Capacitor Native UI](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/vertical-bars.md).
4
4
 
5
5
  ```sh
6
- npm install @rdlabo/ionic-theme-ios27
6
+ npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable
7
7
  ```
8
8
 
9
9
  ```scss
10
10
  @use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';
11
11
  ```
12
12
 
13
- The layout behavior below assumes this stylesheet is loaded. For native runtime setup and device placement, follow the [integration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md).
13
+ The layout behavior below assumes this stylesheet is loaded. For native runtime setup and device placement, follow the [integration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/ionic-vertical-bars.md).
14
14
 
15
15
  ## Reserve the vertical rail
16
16
 
17
+ For device setup, use the installed Foldable plugin and apply its initial placement and events through `setVerticalBarsLayout`; follow the [standard Ionic rail setup](../../capacitor-native-ui/docs/ionic-vertical-bars.md). The classes below describe the stylesheet's layout inputs and optional browser simulation, not a manual device setup step.
18
+
17
19
  Add `.ios-theme-vertical-bars` to `ion-app` to reserve the rail region on the physical right, or add `.ios-theme-vertical-bars-left` as well to use the physical left:
18
20
 
19
21
  ```html
@@ -22,11 +24,31 @@ Add `.ios-theme-vertical-bars` to `ion-app` to reserve the rail region on the ph
22
24
 
23
25
  The classes are physical — `-left` always means the physical left edge — because CSS and the native renderer work in physical coordinates. `setVerticalBarsLayout` applies the logical `verticalBarEdge` reported by the device plugin and resolves it through the document's direction, so an RTL app does not need its own conversion.
24
26
 
25
- For Chrome development, no native plugin is needed — the class alone reserves `80px` to simulate iPhone Duo. When `setVerticalBarsLayout` receives `{ edge, inset }`, the 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.
27
+ ### Rail width
28
+
29
+ | Source | Reserved width |
30
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------------- |
31
+ | Classes alone, including Chrome previews | `80px`; no native plugin required. |
32
+ | `setVerticalBarsLayout({ edge, inset })` | The supplied inset, even below `80px`. |
33
+ | Custom CSS preview | Override `--ios-theme-vertical-bars-safe-area-left` or `--ios-theme-vertical-bars-safe-area-right`. |
34
+
35
+ ### Content and overlay layout
26
36
 
27
- 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.
37
+ | Surface | Layout with the rail stylesheet |
38
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | Routers and component backgrounds | Remain full-viewport. |
40
+ | `ion-content` | Insets its scroll foreground. |
41
+ | `ion-toolbar` | Insets its container foreground. |
42
+ | `ion-fab` | Adjusts only when placed beside the system UI. |
43
+ | Full-width `ion-modal`, including sheets | Applies the foreground inset. With the runtime enabled, the topmost modal projects eligible toolbar controls into its own rail; sheet rails follow visible bounds as the breakpoint changes. |
44
+ | Centered modal | Keeps toolbar buttons and receives no page-rail inset. |
45
+ | `ion-menu` / `ion-popover` | Internal foreground components retain Ionic's standard safe-area handling, without the main-page conversion. |
46
+ | Menu beside the system UI | Keeps its full-viewport animation host; offsets only the visible container by the inset. |
47
+ | Menu on the opposite side | Unchanged. |
28
48
 
29
- `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.
49
+ The corresponding Ionic safe-area variable is reset inside inset foreground components so descendants do not add the inset again.
50
+
51
+ Modal eligibility follows the visible dialog width, not hinge posture or modal type. Left and right remain physical coordinates in RTL; Ionic's `side="start"` and `side="end"` remain logical.
30
52
 
31
53
  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"`.
32
54
 
@@ -40,7 +62,75 @@ setVerticalBarsLayout({ edge: 'trailing', inset: 80 });
40
62
  setVerticalBarsLayout({ edge: null }); // restore ordinary Web layout
41
63
  ```
42
64
 
43
- The helper operates on Web CSS only and work without Capacitor or Native UI startup. The application supplies device state and owns subscriptions. Starting, disabling or destroying Native UI does not change these layout classes or the reserved inset. Unsupported native rails retain the chosen Web rail; choose `edge: null` explicitly if the application wants a horizontal layout instead.
65
+ The helper operates on Web CSS only and works without Capacitor or Native UI startup. The application supplies device state and owns subscriptions. Starting, disabling or destroying Native UI does not change these layout classes or the reserved inset. Unsupported native rails retain the chosen Web rail; choose `edge: null` explicitly if the application wants a horizontal layout instead.
66
+
67
+ ## Read the device layout
68
+
69
+ The standard theme and Native UI setup commands install Foldable as a separate application dependency. After building your Web assets, sync the native project:
70
+
71
+ ```bash
72
+ npx cap sync
73
+ ```
74
+
75
+ Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for iPhone Duo's iOS 27.1 APIs. Foldable supplies placement and posture in the standard setup. Browser simulation can temporarily replace its subscription with fixed layout values. Do not import the plugin's `ionic-tabs.css` alongside this package's rail projection; both would reposition the same tabs.
76
+
77
+ After `ion-app` is mounted, pass device state to `applyFoldStateClasses`. The application owns subscriptions and cleanup. No projection runtime is needed for posture-driven layout.
78
+
79
+ ```ts
80
+ import { Foldable } from '@erkamyaman/capacitor-foldable';
81
+ import { applyFoldStateClasses } from '@rdlabo/ionic-theme-ios27';
82
+
83
+ const root = document.querySelector('ion-app')!;
84
+ const listener = await Foldable.addListener('foldStateChange', (fold) => applyFoldStateClasses(root, fold));
85
+ applyFoldStateClasses(root, await Foldable.getFoldState());
86
+ ```
87
+
88
+ When the application owner is disposed:
89
+
90
+ ```ts
91
+ await listener.remove();
92
+ ```
93
+
94
+ Subscribe to changes, then read the current state. The same helper handles initial values and events.
95
+
96
+ ### Posture classes
97
+
98
+ `applyFoldStateClasses` updates the supplied root while preserving unrelated classes:
99
+
100
+ | Class | Applied when |
101
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
102
+ | `ios-theme-fold-flat` | Flat posture. |
103
+ | `ios-theme-fold-half-opened` | Half-opened posture. |
104
+ | `ios-theme-fold-closed` | Closed posture. |
105
+ | `ios-theme-fold-expanded` | Half-opened, or flat with hinge geometry. Cleared for flat without geometry (including Web fallback) and closed posture. |
106
+
107
+ Only one of the first three classes is retained. The helper does not subscribe to the plugin or change Ionic's split-pane `when` property.
108
+
109
+ ## Adapt the split pane
110
+
111
+ For a side-by-side menu on iPhone Duo, opt the `ion-split-pane` into the separately measured Settings layout. The sidebar is 320pt when fully unfolded and reaches the display midpoint when half-opened (50vw). The application supplies the posture; both states have the same viewport width, so a width media query cannot distinguish them:
112
+
113
+ ```html
114
+ <ion-split-pane class="split-pane-fold-layout" contentId="main-content" when="(min-width: 900px)">
115
+ <ion-menu contentId="main-content">...</ion-menu>
116
+ <div id="main-content">...</div>
117
+ </ion-split-pane>
118
+ ```
119
+
120
+ Set the ordinary split-pane width to 320pt in the application's stylesheet, and let the half-open class change only the width value:
121
+
122
+ ```css
123
+ ion-split-pane.split-pane-fold-layout {
124
+ --ios-theme-menu-width: var(--ios-theme-split-pane-width);
125
+ --side-width: var(--ios-theme-menu-width);
126
+ --side-max-width: var(--ios-theme-menu-width);
127
+ transition: --ios-theme-split-pane-width 300ms ease;
128
+ }
129
+ ```
130
+
131
+ The registered `--ios-theme-split-pane-width` defaults to `320px`. `applyFoldStateClasses` sets `ios-theme-fold-half-opened` on `ion-app`; the stylesheet then sets only descendant split panes with `split-pane-fold-layout` to `50vw`. Add this opt-in class once; no state-dependent class binding is needed. Other split panes retain their existing width.
132
+
133
+ Ionic's `when` still controls whether the menu is persistent. The example chooses a fixed 900px breakpoint. If your app needs different breakpoints for folded and ordinary displays, use the `ios-theme-fold-expanded` class applied by the helper to select that policy in your application's layout code. The helper only updates state classes, not `when`. This layout does not enable Vertical Bars or move an overlay menu.
44
134
 
45
135
  ## Migration
46
136
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rdlabo/ionic-theme-ios27",
3
3
  "private": false,
4
- "version": "2.0.0-beta.pr304.sha56daf151ad80",
4
+ "version": "2.0.0-beta.pr310.shab88b79526a19",
5
5
  "description": "iOS27 Theme for Ionic Framework",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -46,8 +46,8 @@
46
46
  },
47
47
  "homepage": "https://docs.rdlabo.dev/projects/ionic-theme-ios27",
48
48
  "dependencies": {
49
- "@rdlabo/capacitor-native-ui": "2.0.0-beta.pr304.sha56daf151ad80",
50
- "@rdlabo/ionic-theme-utils": "0.1.4",
49
+ "@rdlabo/capacitor-native-ui": "2.0.0-beta.pr310.shab88b79526a19",
50
+ "@rdlabo/ionic-theme-utils": "0.1.5",
51
51
  "@stencil/core": "^4.43.5",
52
52
  "ionicons": "^8.1.0"
53
53
  },
@@ -1,173 +0,0 @@
1
- ---
2
- title: Add Vertical Bars while keeping your theme
3
- ---
4
-
5
- # Add Vertical Bars while keeping your theme
6
-
7
- Keep your app's existing appearance and add Vertical Bars. Choose the setup for your app's components, whether you use Angular, React, Vue or another framework.
8
-
9
- Read [how projection works](../../capacitor-native-ui/README.md#how-projection-works) for the relationship between source DOM, projected controls and actions. Then choose your setup below.
10
-
11
- ## Choose your setup
12
-
13
- | Your app | Start here | What you add |
14
- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
15
- | Ionic components with your own theme, Ionic's default styles, or another theme | [Ionic setup below](#try-it-in-your-existing-ionic-app) | Standalone rail CSS and the Ionic Native UI adapter. Keep your existing theme. |
16
- | No Ionic components (plain HTML, React, Vue, Angular, etc.) | [HTML setup](../../capacitor-native-ui/docs/framework-independent.md), then its [Web rail configuration](../../capacitor-native-ui/docs/framework-independent.md#optional-web-rail) | Only `@rdlabo/capacitor-native-ui`, using its `/core` entry. Your app supplies the rail layout. |
17
- | Already using the iOS 27 theme and Native UI | [Connect device placement](iphone-duo.md#project-controls-into-the-rail) | Reuse your existing runtime. Do not start another one. |
18
-
19
- **Without Ionic, follow the HTML guide instead of the steps below.** You do not need this theme package, `vertical-bars.css`, `ion-app`, or Ionic navigation animations. Explicitly marked HTML buttons and back buttons are supported; arbitrary HTML tabs are not automatically converted. A browser preview requires an application-styled `webHost`; native rail presentation also requires supported iOS hardware and software.
20
-
21
- The rest of this page is for **Ionic apps**. Both Ionic `ios` and `md` modes are supported. Tabs and supported toolbar actions move to the side; content and horizontal controls keep their existing appearance.
22
-
23
- Available as a **preview** feature. APIs and supported behavior may change.
24
-
25
- ## Try it in your existing Ionic app
26
-
27
- ### 1. Install and load the standalone stylesheet
28
-
29
- Start with your existing Ionic app (Ionic `>=8.8.1 <10`, Capacitor 8). Add the two packages:
30
-
31
- ```bash
32
- npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui
33
- ```
34
-
35
- Keep your existing theme imports. Add this to your global Sass file:
36
-
37
- ```scss
38
- @use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';
39
- ```
40
-
41
- The full iOS 27 theme stylesheets are not required. Preview these steps in Chrome before connecting an iPhone Duo.
42
-
43
- ### 2. Opt your app into the side layout
44
-
45
- Add the class to your existing app root and keep the content inside it:
46
-
47
- ```html
48
- <ion-app class="ios-theme-vertical-bars">
49
- <!-- Keep your existing pages, tabs, and toolbar controls here. -->
50
- </ion-app>
51
- ```
52
-
53
- The preview reserves `80px` on the physical right. To preview the left side, also add `ios-theme-vertical-bars-left`.
54
-
55
- ### 3. Connect your navigation animation
56
-
57
- Configure `navAnimation` before Ionic initializes. Starting the rail runtime does not register this option. The adapter waits for native control retirement and coordinates swipe progress and cancellation while keeping your existing animation.
58
-
59
- #### Keep Ionic's default animation
60
-
61
- If you have not configured `navAnimation`, wrap Ionic's standard builders. Select the builder from Ionic's transition `mode` so both `ios` and `md` keep their usual animation:
62
-
63
- ```ts
64
- import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';
65
- import { withNativeUITransition } from '@rdlabo/ionic-theme-ios27';
66
-
67
- const defaultTransition: AnimationBuilder = (baseEl, opts) =>
68
- (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);
69
-
70
- const ionicConfig = {
71
- navAnimation: withNativeUITransition(defaultTransition),
72
- };
73
- ```
74
-
75
- 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.
76
-
77
- #### Use this package's iOS animation
78
-
79
- 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.
80
-
81
- ```ts
82
- import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';
83
-
84
- const ionicConfig = {
85
- navAnimation: iosTransitionAnimation,
86
- };
87
- ```
88
-
89
- Apply this option to your existing iOS-mode configuration and keep your MD configuration.
90
-
91
- #### Keep your custom animation
92
-
93
- If your app uses another builder for `navAnimation`, wrap it:
94
-
95
- ```ts
96
- import type { AnimationBuilder } from '@ionic/core';
97
- import { withNativeUITransition } from '@rdlabo/ionic-theme-ios27';
98
-
99
- // Pass the animation builder your app already uses.
100
- const configureNavigation = (existingTransition: AnimationBuilder) => ({
101
- navAnimation: withNativeUITransition(existingTransition),
102
- });
103
- ```
104
-
105
- The adapter returns the original `Animation`, preserving its effects, duration, and easing. Use it only for navigation, not modal or popover animations. The builder must return a fresh `Animation` for each navigation; Ionic destroys it after the transition. Keep lifecycle events for control registration and transitions without animation.
106
-
107
- 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.
108
-
109
- ### 4. Start the controls after the app root is mounted
110
-
111
- Call this once from your application startup after `ion-app` exists in the DOM:
112
-
113
- ```ts
114
- import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
115
-
116
- const rail = await enableNativeUI({ controls: 'verticalOnly' });
117
- ```
118
-
119
- **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.
120
-
121
- 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.
122
-
123
- When the application owner is disposed, call `await rail.destroy()` to restore the original controls and release the runtime. If you already use `enableNativeUI()`, keep that runtime and follow the [shared placement guide](iphone-duo.md#project-controls-into-the-rail).
124
-
125
- ### Optional: choose native button appearance
126
-
127
- See [Choose button appearance](../../capacitor-native-ui/docs/vertical-bars.md#choose-button-appearance) for appearance settings and local overrides.
128
-
129
- The default is `system`: SwiftUI styles vertical buttons and tints their icons. If your existing theme should supply their fill and colors, replace the startup call in step 4 with:
130
-
131
- ```ts
132
- const rail = await enableNativeUI({
133
- controls: 'verticalOnly',
134
- ionic: { verticalBars: { buttonProjection: 'source', buttonDefaultFill: 'solid' } },
135
- });
136
- ```
137
-
138
- The `solid` default suits ordinary Ionic buttons. Buttons inside `ion-buttons` still default to clear; set `fill="solid"` explicitly to project their background. For one-off exceptions, use the [local projection settings](../../capacitor-native-ui/docs/vertical-bars.md#override-individual-buttons-or-groups). These settings affect native vertical buttons only; Web clones keep their existing appearance.
139
-
140
- ### If the preview does not appear
141
-
142
- | What you see | What to check |
143
- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
144
- | No space at the side | Load `vertical-bars.css` and put the class on `ion-app`. |
145
- | Space appears, but controls stay horizontal | Start `enableNativeUI({ controls: 'verticalOnly' })` after mounting the app root. Use existing tabs or `slot="icon-only"` actions in a fixed header/footer toolbar. |
146
- | 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](../../capacitor-native-ui/docs/vertical-bars.md#toolbar-actions). |
147
-
148
- ## Connect an iPhone Duo
149
-
150
- Install [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) for device state:
151
-
152
- ```bash
153
- npm install @erkamyaman/capacitor-foldable
154
- npx cap sync ios
155
- ```
156
-
157
- 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. Keep your existing SPM or CocoaPods integration; see [Native UI setup](../../capacitor-native-ui/docs/setup.md#4-build-sync-and-run). Keep this package's `vertical-bars.css`; the device plugin's `ionic-tabs.css` is not needed with our rail projection.
158
-
159
- Replace the browser-only startup with the [device placement setup](iphone-duo.md#project-controls-into-the-rail). That setup sends initial values and `barPlacementChange` events to `setVerticalBarsLayout({ edge: verticalBarEdge, inset })`, with subscriptions and cleanup kept in your application.
160
-
161
- Pass Foldable's reported logical edge and measured inset to `setVerticalBarsLayout`. The helper resolves RTL. A null edge restores the ordinary Web layout; Native UI detects native rail availability independently.
162
-
163
- On supported iOS, controls in the rail use native SwiftUI rendering; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.
164
-
165
- ## Use hinge posture without projecting controls
166
-
167
- 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`. Pass `Foldable.getFoldState()` results and `foldStateChange` events to `applyFoldStateClasses(root, fold)`, removing the listener when finished. There is no separate start/stop monitoring call.
168
-
169
- See [Read the device layout](iphone-duo.md#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](iphone-duo.md#adapt-the-split-pane) for the opt-in width rules and half-open state.
170
-
171
- ## Shared layout rules and API
172
-
173
- Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in [Vertical Bars](../../capacitor-native-ui/docs/vertical-bars.md). Read that reference when you need to customize this setup; it is not an additional installation step.
@@ -1,147 +0,0 @@
1
- ---
2
- title: iPhone Duo support (preview)
3
- ---
4
-
5
- # iPhone Duo support (preview)
6
-
7
- Adapt your Ionic app to iPhone Duo: place navigation and actions in its vertical system rail, and adjust your split pane as the device opens and closes. Existing Ionic markup remains the source of labels, icons, routing, and click handlers.
8
-
9
- **New here?** Start with [iPhone Duo with your existing theme](iphone-duo-with-original-theme.md) to preview the side layout in Chrome. This page explains device events, placement and split panes. [Vertical Bars](../../capacitor-native-ui/docs/vertical-bars.md) documents control projection and the runtime API.
10
-
11
- Available as a **preview** feature alongside [Native UI](../../capacitor-native-ui/docs/native-ui.md). APIs and supported behavior may change before stable status. Stable status is planned after the official release of Xcode 27.1. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer.
12
-
13
- Use the theme layout and native plugin independently for that hardware. Neither requires the full iOS 27 appearance:
14
-
15
- - Theme: `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
- - Native UI: `enableNativeUI({ controls: 'verticalOnly' })` — selects eligible tabs and toolbar controls placed in 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
-
18
- Device state belongs to [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable). The **application** subscribes to its events and chooses its layout. The theme reserves space and adapts the split pane; Native UI projects eligible controls. The theme does not monitor hinge state or bar placement.
19
-
20
- ## Choose what to adopt
21
-
22
- To keep your existing theme and add only the standalone support, follow [iPhone Duo with your existing theme](iphone-duo-with-original-theme.md). This page covers the shared device-layout rules.
23
-
24
- | Goal | Stylesheet | Runtime |
25
- | ------------------------------------------ | ------------------- | ---------------------------------------------- |
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 |
28
- | Vertical rail for tabs and toolbar actions | `vertical-bars.css` | `enableNativeUI({ controls: 'verticalOnly' })` |
29
- | Native shell plus the rail | `vertical-bars.css` | `enableNativeUI()` — includes rail projection |
30
-
31
- ```scss
32
- @use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';
33
- ```
34
-
35
- The stylesheet never changes ordinary Ionic UI by itself; every rule requires an opt-in class. Load it unconditionally — these values are simulation and layout inputs, independent from Ionic's normal safe-area variables.
36
-
37
- The `/vertical-bars` entry point imports `@capacitor/core` at module load, so install it even for Web-only use (it is an optional peer dependency). Apps that want only the stylesheet and its opt-in classes need nothing else.
38
-
39
- ## Read the device layout
40
-
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
- After `ion-app` is mounted, pass device state to `applyFoldStateClasses`. The application owns subscriptions and cleanup. No projection runtime is needed for posture-driven layout.
51
-
52
- ```ts
53
- import { Foldable } from '@erkamyaman/capacitor-foldable';
54
- import { applyFoldStateClasses } from '@rdlabo/ionic-theme-ios27';
55
-
56
- const root = document.querySelector('ion-app')!;
57
- const listener = await Foldable.addListener('foldStateChange', (fold) => applyFoldStateClasses(root, fold));
58
- applyFoldStateClasses(root, await Foldable.getFoldState());
59
- ```
60
-
61
- When the application owner is disposed:
62
-
63
- ```ts
64
- await listener.remove();
65
- ```
66
-
67
- Subscribe to changes, then read the current state. The same helper handles initial values and events.
68
-
69
- `applyFoldStateClasses` keeps one of `ios-theme-fold-flat`, `ios-theme-fold-half-opened`, and `ios-theme-fold-closed` on the supplied root, preserving unrelated classes. It also sets `ios-theme-fold-expanded` for a half-opened state or a flat state with hinge geometry. A flat state without geometry (including the Web fallback) and a closed state clear that class. The helper does not subscribe to the plugin or change Ionic's split-pane `when` property.
70
-
71
- For rail placement, use `setVerticalBarsLayout` as shown below. Import this Web layout helper from the theme and pass the reported logical `edge` with the measured `inset`. It does not report native availability or activate projection. Leading is the physical left in LTR and the physical right in RTL. A null edge restores the ordinary layout; an inset of zero clears the explicit width. No start/stop monitoring calls are needed.
72
-
73
- WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`.
74
-
75
- **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.
76
-
77
- ## Reserve the vertical rail
78
-
79
- Load `vertical-bars.css` as shown above. For class-based browser simulation, safe-area handling, RTL and overlay layout, see [Reserve the vertical rail](vertical-bars-layout.md#reserve-the-vertical-rail). On device, the placement helper below applies the layout classes and measured inset.
80
-
81
- ## Project controls into the rail
82
-
83
- Enable rail projection once after `ion-app` is mounted, and apply Foldable's placement to the Web layout with `setVerticalBarsLayout`:
84
-
85
- ```ts
86
- import { Foldable } from '@erkamyaman/capacitor-foldable';
87
- import { setVerticalBarsLayout } from '@rdlabo/ionic-theme-ios27';
88
- import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
89
-
90
- const rail = await enableNativeUI({ controls: 'verticalOnly' });
91
- const listener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
92
- setVerticalBarsLayout({ edge: verticalBarEdge, inset }),
93
- );
94
- const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
95
- setVerticalBarsLayout({ edge: verticalBarEdge, inset });
96
- ```
97
-
98
- When the application owner is disposed:
99
-
100
- ```ts
101
- await listener.remove();
102
- await rail.destroy();
103
- ```
104
-
105
- Native UI detects native availability independently through its own device metrics. The theme helper controls Web layout only. The measured `inset` is passed through instead of assuming a fixed width. Devices without a reported rail, including Web and Android, return a null edge and retain the ordinary layout. For browser simulation, use the [class-based preview](vertical-bars-layout.md#reserve-the-vertical-rail) without connecting device placement.
106
-
107
- Start either `enableNativeUI({ controls: 'verticalOnly' })` or the full `enableNativeUI()` — not both. If the app already uses Native UI, keep that runtime and use the same `setVerticalBarsLayout` callback. The application owner removes its listeners and destroys its runtime on teardown.
108
-
109
- 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. See [Toolbar actions](../../capacitor-native-ui/docs/vertical-bars.md#toolbar-actions) for control eligibility.
110
-
111
- ### Toolbar actions
112
-
113
- See [Vertical Bars: Toolbar actions](../../capacitor-native-ui/docs/vertical-bars.md#toolbar-actions) for eligible markup, placement, button appearance and local overrides.
114
-
115
- ### Tab bar
116
-
117
- See [Vertical Bars: Tab bar](../../capacitor-native-ui/docs/vertical-bars.md#tab-bar) for navigation, labels and Web fallback behavior.
118
-
119
- ## Adapt the split pane
120
-
121
- For a side-by-side menu on iPhone Duo, opt the `ion-split-pane` into the separately measured Settings layout. The sidebar is 320pt when fully unfolded and reaches the display midpoint when half-opened (50vw). The application supplies the posture; both states have the same viewport width, so a width media query cannot distinguish them:
122
-
123
- ```html
124
- <ion-split-pane class="split-pane-fold-layout" contentId="main-content" when="(min-width: 900px)">
125
- <ion-menu contentId="main-content">...</ion-menu>
126
- <div id="main-content">...</div>
127
- </ion-split-pane>
128
- ```
129
-
130
- Set the ordinary split-pane width to 320pt in the application's stylesheet, and let the half-open class change only the width value:
131
-
132
- ```css
133
- ion-split-pane.split-pane-fold-layout {
134
- --ios-theme-menu-width: var(--ios-theme-split-pane-width);
135
- --side-width: var(--ios-theme-menu-width);
136
- --side-max-width: var(--ios-theme-menu-width);
137
- transition: --ios-theme-split-pane-width 300ms ease;
138
- }
139
- ```
140
-
141
- The registered `--ios-theme-split-pane-width` defaults to `320px`. `applyFoldStateClasses` sets `ios-theme-fold-half-opened` on `ion-app`; the stylesheet then sets only descendant split panes with `split-pane-fold-layout` to `50vw`. Add this opt-in class once; no state-dependent class binding is needed. Other split panes retain their existing width.
142
-
143
- Ionic's `when` still controls whether the menu is persistent. The example chooses a fixed 900px breakpoint. If your app needs different breakpoints for folded and ordinary displays, use the `ios-theme-fold-expanded` class applied by the helper to select that policy in your application's layout code. The helper only updates state classes, not `when`. This layout does not enable Vertical Bars or move an overlay menu.
144
-
145
- ## Vertical Control Area API
146
-
147
- The runtime handle reference is maintained in [Vertical Bars](../../capacitor-native-ui/docs/vertical-bars.md#vertical-control-area-api).