@rdlabo/ionic-theme-ios27 1.2.0-beta.pr243.shae972a0e49191 → 1.2.0-beta.pr246.sha237333ceac21

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.
@@ -1,10 +1,10 @@
1
1
  ---
2
- title: Native UI Shell (Experimental)
2
+ title: Native UI Shell (Preview)
3
3
  ---
4
4
 
5
- # Native UI Shell (Experimental)
5
+ # Native UI Shell (Preview)
6
6
 
7
- Native UI Shell is experimental. Its API and supported controls may change.
7
+ Native UI Shell is available as a **preview** in `1.2.0`. Its API and supported controls may change before stable status. Stable status is planned after the official release of Xcode 27.1.
8
8
 
9
9
  Native UI Shell gives an Ionic app native navigation and action controls around its Web content. The optional Capacitor iOS plugin renders supported fixed Ionic controls with UIKit or SwiftUI and the system's Liquid Glass material. Page content, scrolling, application state and routing remain in Ionic's WebView.
10
10
 
@@ -52,12 +52,14 @@ The table below describes the ordinary Native UI Shell. Vertical Bars uses the s
52
52
  | `ion-segment` | Fixed toolbar, non-scrollable, text **or** one icon per item | `UISegmentedControl` |
53
53
  | `ion-fab` / `ion-fab-button` / `ion-fab-list` | Glass FAB in an `ion-content` fixed slot; one main button and optional directional lists | Persistent glass `UIButton` per button; one FAB synchronization group |
54
54
 
55
- For the ordinary Native UI Shell, only iOS-mode components with the theme variables installed are eligible. Explicitly enabled Vertical Bars is mode-independent as described below. `ionic-theme-disabled`, `ios-theme-disabled`, and the legacy `ios26-disabled` on an element or ancestor always exclude it. A disabled theme on one tab/segment item keeps its whole group on the Web.
55
+ For the ordinary Native UI Shell, only iOS-mode components with the theme variables installed are eligible. Explicitly enabled Vertical Bars is mode-independent as described below. `ios-theme-disabled` and the legacy `ios26-disabled` on an element or ancestor always exclude it. A disabled theme on one tab/segment item keeps its whole group on the Web.
56
56
 
57
- Use `ios-theme-shell-disabled` to disable only the iOS Native UI Shell while keeping the Web theme. It excludes the element and all its descendants. Adding or removing the class at runtime automatically restores Web rendering or re-evaluates native eligibility.
57
+ Use `data-shell="disabled"` (or the equivalent `ios-theme-shell-disabled` class) to disable only the iOS Native UI Shell while keeping the Web theme. It excludes the element and all its descendants. Adding or removing the attribute or class at runtime automatically restores Web rendering or re-evaluates native eligibility.
58
+
59
+ Only the exact value `disabled` opts out; an empty or unknown value is ignored. Remove the attribute to re-enable projection. If the class is also present, remove both to re-enable projection. This does not disable clicks or change the Web theme. The data attribute is available in `1.2.0`.
58
60
 
59
61
  ```html
60
- <ion-toolbar class="ios-theme-shell-disabled">
62
+ <ion-toolbar data-shell="disabled">
61
63
  <ion-button>Web glass button</ion-button>
62
64
  </ion-toolbar>
63
65
  ```
@@ -171,9 +173,9 @@ Suspensions are nestable and `resume()` is idempotent. Native projection resumes
171
173
 
172
174
  The native material and control appearance follow the running iOS version; an iOS 26 device does not acquire iOS 27's appearance merely by installing this theme.
173
175
 
174
- ## Support iPhone Duo (experimental)
176
+ ## Support iPhone Duo (preview)
175
177
 
176
- iPhone Duo support is experimental, including standalone use without Native UI Shell. It is available in the `1.2.0-0` prerelease alongside Native UI Shell. Its APIs and supported behavior may change.
178
+ iPhone Duo support, including standalone vertical bars without Native UI Shell, is available as a **preview** in `1.2.0` alongside Native UI Shell. Its APIs and supported behavior may change.
177
179
 
178
180
  The standalone Vertical Control Area entry point (`@rdlabo/ionic-theme-ios27/vertical-bars`) and `dist/css/vertical-bars.css` work without loading the iOS 27 theme. Call `enableVerticalControlArea()` for this use case; it projects only controls placed in the vertical area. Apps already calling `enableNativeUIShell()` should keep that single runtime rather than starting both. See [iPhone Duo support](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo) for the complete setup, including hinge posture and the split-pane layout for apps that do not use this shell at all.
179
181
 
@@ -181,7 +183,7 @@ Vertical Bars uses a SwiftUI `TabView` and toolbar in the system rail. Ionic rem
181
183
 
182
184
  In Vertical Bars, fixed-toolbar `ion-button` actions need an `ion-icon` or SVG with `slot="icon-only"`. All fills and Ionic colors are eligible; submit buttons follow the same placement rule. Add `.ios-theme-horizontal-only` to a button or its `ion-buttons` group to keep it horizontal. See [Toolbar actions](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#toolbar-actions) for placement and exclusion rules.
183
185
 
184
- **Unreleased button appearance options:** the next release defaults native vertical buttons to `buttonProjection: 'system'`. Use `source` to project Ionic fill and colors, with `ios-theme-projection-source` / `ios-theme-projection-system` classes for local exceptions. These settings affect native vertical `ion-button` and `ion-menu-button` actions only. Horizontal controls and Web clones keep their existing behavior. See [Choose button appearance](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#choose-button-appearance) for migration, priority and fill rules.
186
+ **Native button appearance:** `1.2.0` defaults native vertical buttons to `buttonProjection: 'system'`. Use `source` to project Ionic fill and colors, with `data-projection="source|system"` or the equivalent `ios-theme-projection-source` / `ios-theme-projection-system` classes for local exceptions. These settings affect native vertical `ion-button` and `ion-menu-button` actions only. Horizontal controls and Web clones keep their existing behavior. See [Choose button appearance](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/vertical-bars#choose-button-appearance) for migration, priority and fill rules.
185
187
 
186
188
  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).
187
189
 
@@ -47,9 +47,9 @@ Add one of `tab-bar-position-start`, `tab-bar-position-center`, or `tab-bar-posi
47
47
 
48
48
  These classes do not reposition a separate `ion-fab`; leave room for it when choosing the bar's position.
49
49
 
50
- ## Support iPhone Duo (experimental)
50
+ ## Support iPhone Duo (preview)
51
51
 
52
- iPhone Duo support is experimental. It is available in the `1.2.0-0` prerelease alongside Native UI Shell. Its APIs and supported behavior may change.
52
+ iPhone Duo support, including standalone vertical bars, is available as a **preview** in `1.2.0` alongside Native UI Shell. Its APIs and supported behavior may change.
53
53
 
54
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 Shell. See [iPhone Duo support](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo) for the complete setup, including device-layout monitoring without a projection runtime.
55
55
 
@@ -1,14 +1,14 @@
1
1
  ---
2
- title: Vertical Bars (experimental)
2
+ title: Vertical Bars (preview)
3
3
  ---
4
4
 
5
- # Vertical Bars (experimental)
5
+ # Vertical Bars (preview)
6
6
 
7
7
  Vertical Bars moves eligible Ionic navigation and actions into a side rail while keeping the original components as the source of labels, icons and behavior. It works with this theme or an existing Ionic theme, independently of hinge posture and the full Native UI Shell.
8
8
 
9
9
  Use this page for layout, control eligibility, native button appearance and the runtime API. For device events and split panes, see [iPhone Duo support](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo). For a step-by-step browser preview and native setup, start with [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme).
10
10
 
11
- Vertical Bars is experimental; APIs and supported behavior may change. The button appearance options below are unreleased.
11
+ Available in `1.2.0` as a **preview** feature. APIs and supported behavior may change.
12
12
 
13
13
  ## Enable Vertical Bars
14
14
 
@@ -87,7 +87,7 @@ The rule applies to individual buttons and buttons inside `ion-buttons`, on ordi
87
87
 
88
88
  ### Choose button appearance
89
89
 
90
- **Unreleased:** `buttonProjection` and the local projection classes below are part of the next release. They are not available in the `1.2.0-1` package used by the introductory guide. The following rules describe this branch.
90
+ `buttonProjection` and the local projection settings below are available in `1.2.0`.
91
91
 
92
92
  For native vertical `ion-button` and `ion-menu-button` actions, choose who controls appearance:
93
93
 
@@ -102,18 +102,23 @@ const rail = await enableVerticalControlArea({ buttonProjection: 'source' });
102
102
 
103
103
  Both `enableVerticalControlArea()` and `enableNativeUIShell()` accept the option. Use one runtime, and destroy it before restarting with different options. Either mode preserves actions, disabled state and grouping. These appearance settings do not affect horizontal controls, source elements or Web fallback clones, so compare the native appearance on supported iOS.
104
104
 
105
- **Migration:** the default changes from source styling to `system`. Set `buttonProjection: 'source'` to retain the previous projection behavior. This does not change the feature's experimental status.
105
+ **Migration from the experimental releases:** the default changes from source styling to `system`. Set `buttonProjection: 'source'` to retain the previous projection behavior.
106
106
 
107
107
  ### Override individual buttons or groups
108
108
 
109
- Use `ios-theme-projection-source` or `ios-theme-projection-system` for local exceptions:
109
+ Use `data-projection` for local exceptions. Existing classes remain supported:
110
+
111
+ | Attribute | Equivalent class |
112
+ | --- | --- |
113
+ | `data-projection="source"` | `ios-theme-projection-source` |
114
+ | `data-projection="system"` | `ios-theme-projection-system` |
110
115
 
111
116
  ```html
112
- <ion-buttons class="ios-theme-projection-source">
117
+ <ion-buttons data-projection="source">
113
118
  <ion-button fill="solid" aria-label="Add">
114
119
  <ion-icon name="add-outline" slot="icon-only"></ion-icon>
115
120
  </ion-button>
116
- <ion-button class="ios-theme-projection-system" aria-label="Search">
121
+ <ion-button data-projection="system" aria-label="Search">
117
122
  <ion-icon name="search-outline" slot="icon-only"></ion-icon>
118
123
  </ion-button>
119
124
  </ion-buttons>
@@ -121,11 +126,13 @@ Use `ios-theme-projection-source` or `ios-theme-projection-system` for local exc
121
126
 
122
127
  The first matching setting wins:
123
128
 
124
- 1. The button's projection class.
125
- 2. Its nearest `ion-buttons` projection class.
129
+ 1. The button's local setting.
130
+ 2. Its nearest `ion-buttons` local setting.
126
131
  3. The startup `buttonProjection` option, or `system` if omitted.
127
132
 
128
- If both classes are present on the same element, `system` wins. Removing the classes restores the inherited setting. Class changes apply without restarting the runtime; grouping and placement stay unchanged. Only native vertical `ion-button` and `ion-menu-button` actions interpret these classes. Other ancestors, back buttons, tabs and FABs do not.
133
+ On the same element, a valid `data-projection` value takes precedence over the classes. Empty or unknown values are ignored. Without a valid attribute, `system` wins if both classes are present. Removing an attribute falls back to the element's classes, then the next level above. Attribute and class changes apply without restarting the runtime; grouping and placement stay unchanged.
134
+
135
+ Only native vertical `ion-button` and `ion-menu-button` actions interpret these settings. Other ancestors, back buttons, tabs and FABs do not. They do not change Web styling. To keep a control or subtree on the Web entirely, use [`data-shell="disabled"`](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell#supported-markup).
129
136
 
130
137
  ### Source fill rules
131
138
 
@@ -146,7 +153,7 @@ const rail = await enableVerticalControlArea({ buttonDefaultFill: 'solid' });
146
153
 
147
154
  ```html
148
155
  <!-- In a fixed toolbar, outside ion-buttons: omitted fill resolves to solid. -->
149
- <ion-button class="ios-theme-projection-source" aria-label="Add">
156
+ <ion-button data-projection="source" aria-label="Add">
150
157
  <ion-icon name="add-outline" slot="icon-only"></ion-icon>
151
158
  </ion-button>
152
159
  ```
@@ -158,7 +165,7 @@ const rail = await enableVerticalControlArea({ buttonDefaultFill: 'solid' });
158
165
  | `outline` | Computed border color and width, with native glass |
159
166
  | `null` | Native glass with source icon colors |
160
167
 
161
- Inside `ion-buttons`, explicitly set `fill="solid"` to project a background; `buttonDefaultFill: 'solid'` does not override the group's clear default. Native Liquid Glass tinting can differ from the CSS color, especially for translucent backgrounds. With the iOS theme, ordinary `ion-buttons` retain group projection; `ion-buttons.ios-theme-disabled` projects eligible buttons individually. Projection classes do not change that grouping rule.
168
+ Inside `ion-buttons`, explicitly set `fill="solid"` to project a background; `buttonDefaultFill: 'solid'` does not override the group's clear default. Native Liquid Glass tinting can differ from the CSS color, especially for translucent backgrounds. With the iOS theme, ordinary `ion-buttons` retain group projection; `ion-buttons.ios-theme-disabled` projects eligible buttons individually. Local projection settings do not change that grouping rule.
162
169
 
163
170
  ## Tab bar
164
171
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rdlabo/ionic-theme-ios27",
3
3
  "private": false,
4
- "version": "1.2.0-beta.pr243.shae972a0e49191",
4
+ "version": "1.2.0-beta.pr246.sha237333ceac21",
5
5
  "description": "iOS27 Theme for Ionic Framework",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -13,9 +13,10 @@ export interface NativeUIShellStatus {
13
13
  export interface VerticalControlAreaOptions {
14
14
  /** Appearance of native vertical ion-button and ion-menu-button actions.
15
15
  * `system` (default) uses SwiftUI styling and template icons; `source` projects supported Ionic fill and colors.
16
- * Local ios-theme-projection-source / ios-theme-projection-system classes take precedence:
17
- * the button itself, then its nearest ion-buttons, then this option. On the same element, system wins.
18
- * Classes update live; removing them restores inheritance. Actions, disabled state and grouping are preserved.
16
+ * Local data-projection="source|system" or ios-theme-projection-source / ios-theme-projection-system take precedence:
17
+ * the button itself, then its nearest ion-buttons, then this option. On the same element, a valid attribute wins;
18
+ * otherwise system wins if both classes are present. Invalid attribute values are ignored.
19
+ * Local settings update live; removing them restores inheritance. Actions, disabled state and grouping are preserved.
19
20
  * Does not affect back buttons, tabs, FABs, horizontal controls, source elements or Web clones.
20
21
  */
21
22
  buttonProjection?: 'source' | 'system';
@@ -74,6 +74,8 @@ export const prehideVerticalBarsToolbarSources = (doc: Document): { suspend: ()
74
74
  };
75
75
  const sources = scope.matches('ion-back-button') ? [scope] : Array.from(scope.querySelectorAll<HTMLElement>(sourceSelector));
76
76
  sources.forEach((element) => {
77
+ // Shell opt-out is temporary; decide initial placement once it is lifted.
78
+ if (isShellDisabled(element)) return;
77
79
  if (!modalUsesVerticalBars(element) || element.closest(overlays) || (scope.matches('.ion-page') && routedPage(element) !== scope))
78
80
  return;
79
81
  if (element.matches('ion-buttons')) {
@@ -228,7 +230,7 @@ export const prehideVerticalBarsToolbarSources = (doc: Document): { suspend: ()
228
230
  childList: true,
229
231
  attributes: true,
230
232
  attributeOldValue: true,
231
- attributeFilter: ['class', 'hidden', 'inert', 'icon', 'color'],
233
+ attributeFilter: ['class', 'data-shell', 'hidden', 'inert', 'icon', 'color'],
232
234
  });
233
235
  return {
234
236
  suspend: () => {
@@ -65,13 +65,17 @@ export const appendItem = (
65
65
  const badgeStyle = badge && visible(badge) ? getComputedStyle(badge) : undefined;
66
66
  const sourceFill = (child as HTMLIonButtonElement).fill;
67
67
  const projectionOwner = [child, child.closest('ion-buttons')].find((element) =>
68
- element?.matches('.ios-theme-projection-source, .ios-theme-projection-system'),
68
+ element?.matches('[data-projection="source"], [data-projection="system"], .ios-theme-projection-source, .ios-theme-projection-system'),
69
69
  );
70
- const projection = projectionOwner
71
- ? projectionOwner.classList.contains('ios-theme-projection-system')
72
- ? 'system'
73
- : 'source'
74
- : candidate.buttonProjection;
70
+ const attributeProjection = projectionOwner?.getAttribute('data-projection');
71
+ const projection =
72
+ attributeProjection === 'source' || attributeProjection === 'system'
73
+ ? attributeProjection
74
+ : projectionOwner
75
+ ? projectionOwner.classList.contains('ios-theme-projection-system')
76
+ ? 'system'
77
+ : 'source'
78
+ : candidate.buttonProjection;
75
79
  const systemButton = projection !== 'source' && isVerticalBarsSource(child) && child.matches('ion-button, ion-menu-button');
76
80
  // Ionic defaults buttons inside ion-buttons to clear, even when the app defaults to solid.
77
81
  const defaultFill = child.closest('ion-buttons') ? 'clear' : candidate.buttonDefaultFill;
@@ -52,7 +52,7 @@ export const isDark = (style: CSSStyleDeclaration): boolean => {
52
52
  const background = style.getPropertyValue('--ion-background-color-rgb').match(/\d+/g)?.slice(0, 3).map(Number);
53
53
  return !!background && background.length === 3 && background[0] * 0.2126 + background[1] * 0.7152 + background[2] * 0.0722 < 128;
54
54
  };
55
- const permanentlyExcluded = '.ionic-theme-disabled, .ios-theme-disabled, .ios26-disabled, .ion-cloned-element, [hidden], [inert]';
55
+ const permanentlyExcluded = '.ios-theme-disabled, .ios26-disabled, .ion-cloned-element, [hidden], [inert]';
56
56
  export const excluded = `${permanentlyExcluded}, .ion-page-hidden, .ion-page-invisible`;
57
57
  const enteringPages = new WeakSet<HTMLElement>();
58
58
  export const setVerticalBarsEnteringPage = (page: HTMLElement, entering: boolean): void => {
@@ -88,8 +88,8 @@ export const createVerticalBarsPageState = () => {
88
88
  },
89
89
  };
90
90
  };
91
- const disabledButtonGroup = 'ion-buttons:is(.ionic-theme-disabled, .ios-theme-disabled, .ios26-disabled)';
92
- const shellDisabledSelector = '.ios-theme-shell-disabled';
91
+ const disabledButtonGroup = 'ion-buttons:is(.ios-theme-disabled, .ios26-disabled)';
92
+ const shellDisabledSelector = '.ios-theme-shell-disabled, [data-shell="disabled"]';
93
93
 
94
94
  export const isDisabledButtonGroupChild = (element: HTMLElement): boolean =>
95
95
  element.matches('ion-button') && element.parentElement?.matches(disabledButtonGroup) === true;
@@ -188,7 +188,7 @@ export const isVerticalBarsToolbarGroup = (element: HTMLElement): boolean => {
188
188
  return (
189
189
  element.matches('ion-buttons') &&
190
190
  verticalBarsOwned(element) &&
191
- !element.matches('.ionic-theme-disabled, .ios-theme-disabled, .ios26-disabled') &&
191
+ !element.matches('.ios-theme-disabled, .ios26-disabled') &&
192
192
  !isShellDisabled(element)
193
193
  );
194
194
  };
@@ -283,7 +283,8 @@ html.ios-theme-native-ui-shell-prehide
283
283
  [icon],
284
284
  [color],
285
285
  .ios-theme-vertical-bars-back-web-owned,
286
- .ionic-theme-disabled,
286
+ .ios-theme-shell-disabled,
287
+ [data-shell='disabled'],
287
288
  .ios-theme-disabled,
288
289
  .ios26-disabled,
289
290
  .ios-theme-vertical-bars-back-button-projection
@@ -296,7 +297,7 @@ html.ios-theme-native-ui-shell-prehide
296
297
  ion-footer[collapse] *,
297
298
  ion-buttons.ios-theme-horizontal-only *,
298
299
  .ios-theme-shell-disabled *,
299
- .ionic-theme-disabled *,
300
+ [data-shell='disabled'] *,
300
301
  .ios-theme-disabled *,
301
302
  .ios26-disabled *
302
303
  )