diff --git a/README.md b/README.md index add2e728..07785b6e 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Both packages support Ionic 8.8.1 or later within Ionic 8 or 9. Native UI requir ## Explore further -- [iPhone Duo layout](packages/theme-ios27/docs/iphone-duo.md) · [Vertical Bars with an existing theme](packages/capacitor-native-ui/docs/ionic-vertical-bars.md). +- [Device layout and posture](packages/theme-ios27/docs/vertical-bars-layout.md) · [Vertical Bars with an existing theme](packages/capacitor-native-ui/docs/ionic-vertical-bars.md). - [Theme migration](packages/theme-ios27/docs/migration.md) · [Native UI migration](packages/capacitor-native-ui/docs/migration.md). Use standard native buttons without Ionic through the [plain HTML adapter](packages/capacitor-native-ui/docs/framework-independent.md), or keep Ionic markup and enable only [Vertical Bars](packages/capacitor-native-ui/docs/configuration.md). Both use the same native renderers. diff --git a/demo/src/app/docs/docs-content.generated.ts b/demo/src/app/docs/docs-content.generated.ts index f5c0b98d..b65e8cde 100644 --- a/demo/src/app/docs/docs-content.generated.ts +++ b/demo/src/app/docs/docs-content.generated.ts @@ -1,3 +1,3 @@ // Generated from packages/theme-ios27/docs/special-markup.md. Do not edit directly. export const docsContentHtml = - '

Special markup and classes

\n

Most Ionic markup works without changes. The combinations below are explicit opt-ins provided by the theme.

\n

Primary submit buttons

\n

Solid submit buttons use the Ionic color's contrast value for their foreground. Their directional edge treatment follows the iOS 27 prominent-button appearance and does not require an additional brightness color.

\n
<ion-button type="submit" color="primary">Submit</ion-button>\n<ion-button class="button-submit" fill="solid" color="primary">Continue</ion-button>
\n\n
\n
Preview
\nSubmit\nContinue\n
\n\n

Use .button-submit when the button needs the same treatment but cannot use type="submit".

\n

Preferred overlay actions

\n

For iOS alerts and action sheets, set role: 'preferred' on a button to give it a filled --ion-color-primary background and --ion-color-primary-contrast text and icons. While pressed, the background uses --ion-color-primary-shade. This is a theme convention using Ionic's custom button roles; it does not automatically select or invoke the action. Dismissal reports the role as preferred.

\n
buttons: [\n  { text: \'Cancel\', role: \'cancel\' },\n  { text: \'Continue\', role: \'preferred\' },\n];
\n\n

Choose the role by action semantics:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
RoleAppearance and behavior
preferredFilled primary action; dismissal reports preferred. Does not automatically select or invoke it.
No role or defaultNormal text color.
cancelRetains Ionic's cancellation behavior.
selectedRetains selection state.
destructiveUses --ios-theme-destructive-color.
confirmNot treated as preferred. Use preferred for a recommended action, not simply any confirmation.
\n

Floating iPad sheets

\n

Choose the sheet's scrolling behavior with expandToScroll:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
SettingiPad appearanceScrolling and resizing
falseFloating 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.
true (default)Ionic's bottom-attached layout.Scroll-to-expand behavior.
\n

Tab bar position

\n

Choose a class on an iOS ion-tab-bar to position the whole bar within its safe area:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ClassPlacement
tab-bar-position-startStart of the reading direction.
tab-bar-position-centerCenter.
tab-bar-position-endEnd of the reading direction.
NoneExisting placement unchanged.
\n

All three classes support slot="top" and slot="bottom", preserve width and press animation, and resolve start/end in RTL.

\n
<ion-tab-bar slot="bottom" class="tab-bar-position-center">\n  <ion-tab-button tab="home">Home</ion-tab-button>\n  <ion-tab-button tab="settings">Settings</ion-tab-button>\n</ion-tab-bar>
\n\n

These classes do not reposition a separate ion-fab; leave room for it when choosing the bar's position.

\n

Support iPhone Duo

\n

iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.

\n

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 for the complete setup, including device-layout monitoring without a projection runtime.

\n

See Vertical Bars for rail layout classes, control eligibility and native button appearance.

\n

For standalone setup while keeping your existing theme, see iPhone Duo with your existing theme.

\n

Two-line inset list items

\n

Place an unslotted ion-label immediately alongside an unslotted ion-note to render a two-line item. When using the iOS-style inset-list background, wrap the items in ion-item-group; keep ion-list-header outside the group.

\n
<ion-list inset="true">\n  <ion-list-header>\n    <ion-label>Connections</ion-label>\n  </ion-list-header>\n  <ion-item-group>\n    <ion-item>\n      <ion-label>Network &amp; internet</ion-label>\n      <ion-note>Mobile, Wi-Fi, hotspot</ion-note>\n    </ion-item>\n  </ion-item-group>\n</ion-list>
\n\n
\n
Preview
\n\n \n Connections\n \n \n \n Network & internet\n Mobile, Wi-Fi, hotspot\n \n \n\n
\n\n

Use slot="end" on ion-note when you want the standard trailing-note layout instead.

\n

Inset-list section headers

\n

Add .item-group-header to an ion-item-group to create the centered icon, title, and description used at the top of the component demo pages.

\n

This is an introductory group. Place regular list items in a separate ion-item-group that follows it.

\n
<ion-list inset="true">\n  <ion-item-group class="item-group-header">\n    <ion-item>\n      <ion-label>\n        <ion-icon name="list" style="background: var(--ion-color-primary)"></ion-icon>\n        <h2>Lists</h2>\n        <ion-text>Inset-list examples</ion-text>\n      </ion-label>\n    </ion-item>\n  </ion-item-group>\n  <ion-item-group>\n    <ion-item><ion-label>First item</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>
\n\n
\n
Preview
\n\n \n \n \n \n

Lists

\n Inset-list examples\n
\n
\n
\n \n First item\n \n
\n
\n\n

Full-width segments

\n

Add .segment-style-glass to give a segment the same glass surface and selected indicator treatment as the tab bar. The class preserves the segment's existing dimensions and text colors, supports scrollable segments, and respects Ionic's public --background property.

\n
<ion-segment class="segment-style-glass" value="available">\n  <ion-segment-button value="available">Available</ion-segment-button>\n  <ion-segment-button value="away">Away</ion-segment-button>\n</ion-segment>
\n\n

For colored segments, use Ionic's color property (for example, color="primary" or color="secondary"). Ionic uses the palette's base color for the softly tinted track while keeping the selected surface and labels neutral. A surrounding colored toolbar only supplies colors when the segment has no color of its own. The optional moving glass inherits the selected surface color, and custom Ionic palettes work without additional registration.

\n

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.

\n

Segment size and press behavior

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PlacementMinimum heightContainer behavior
In content32pxCompact, flat background and indicator; outer bounds stay fixed.
Inside ion-toolbar48pxGlass container; outer container scales while pressed.
.segment-expand, including in a toolbar32pxCompact, flat background and indicator; outer bounds stay fixed.
\n

The optional moving glass lens is independent of the container's background.

\n
<ion-segment class="segment-expand" value="new">\n  <ion-segment-button value="new"><ion-label>New</ion-label></ion-segment-button>\n  <ion-segment-button value="replied"><ion-label>Replied</ion-label></ion-segment-button>\n</ion-segment>
\n\n
\n
Preview
\n\n New\n Replied\n\n
\n\n

Classic search bar in a condense header

\n

The theme gives iOS search bars the iOS 27 appearance by default. Add .searchbar-classic to the search field shown beneath a large title in an ion-header with collapse="condense". It uses the conventional filled iOS appearance and collapses with the large title instead of remaining in the fixed header.

\n

Place it in a toolbar with a color, such as color="light"; the classic background is derived from that color's contrast value.

\n

The example uses Ionic's standard collapsible large-title structure. Scroll the preview to collapse the large title and reveal the fixed header.

\n
<div class="ion-page">\n  <ion-header translucent="true">\n    <ion-toolbar color="light">\n      <ion-title>Search</ion-title>\n    </ion-toolbar>\n  </ion-header>\n  <ion-content color="light" fullscreen="true">\n    <ion-header collapse="condense">\n      <ion-toolbar color="light">\n        <ion-title size="large">Search</ion-title>\n      </ion-toolbar>\n      <ion-toolbar color="light">\n        <ion-searchbar class="searchbar-classic" placeholder="Filter results"></ion-searchbar>\n      </ion-toolbar>\n    </ion-header>\n    <ion-list inset="true">\n      <ion-item-group>\n        <ion-item><ion-label>Recent item 1</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 2</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 3</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 4</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 5</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 6</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 7</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 8</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 9</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 10</ion-label></ion-item>\n      </ion-item-group>\n    </ion-list>\n  </ion-content>\n</div>
\n\n
\n
Preview
\n
\n \n \n Search\n \n \n \n \n \n Search\n \n \n \n \n \n \n \n Recent item 1\n Recent item 2\n Recent item 3\n Recent item 4\n Recent item 5\n Recent item 6\n Recent item 7\n Recent item 8\n Recent item 9\n Recent item 10\n \n \n \n
\n
\n\n

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.

\n

Opting out

\n

Add .ios-theme-disabled to an individual Ionic component when it must retain Ionic's standard iOS styling.

\n

.ios26-disabled is deprecated but remains supported as an alias with the same behavior. Use .ios-theme-disabled for new code.

\n
<ion-button>iOS 27 theme</ion-button> <ion-button class="ios-theme-disabled">Standard Ionic button</ion-button>
\n\n
\n
Preview
\niOS 27 theme Standard Ionic button\n
\n\n

For the background model behind inset lists, see Using ion-item-group.

\n'; + '

Special markup and classes

\n

Most Ionic markup works without changes. The combinations below are explicit opt-ins provided by the theme.

\n

Primary submit buttons

\n

Solid submit buttons use the Ionic color's contrast value for their foreground. Their directional edge treatment follows the iOS 27 prominent-button appearance and does not require an additional brightness color.

\n
<ion-button type="submit" color="primary">Submit</ion-button>\n<ion-button class="button-submit" fill="solid" color="primary">Continue</ion-button>
\n\n
\n
Preview
\nSubmit\nContinue\n
\n\n

Use .button-submit when the button needs the same treatment but cannot use type="submit".

\n

Preferred overlay actions

\n

For iOS alerts and action sheets, set role: 'preferred' on a button to give it a filled --ion-color-primary background and --ion-color-primary-contrast text and icons. While pressed, the background uses --ion-color-primary-shade. This is a theme convention using Ionic's custom button roles; it does not automatically select or invoke the action. Dismissal reports the role as preferred.

\n
buttons: [\n  { text: \'Cancel\', role: \'cancel\' },\n  { text: \'Continue\', role: \'preferred\' },\n];
\n\n

Choose the role by action semantics:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
RoleAppearance and behavior
preferredFilled primary action; dismissal reports preferred. Does not automatically select or invoke it.
No role or defaultNormal text color.
cancelRetains Ionic's cancellation behavior.
selectedRetains selection state.
destructiveUses --ios-theme-destructive-color.
confirmNot treated as preferred. Use preferred for a recommended action, not simply any confirmation.
\n

Floating iPad sheets

\n

Choose the sheet's scrolling behavior with expandToScroll:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
SettingiPad appearanceScrolling and resizing
falseFloating 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.
true (default)Ionic's bottom-attached layout.Scroll-to-expand behavior.
\n

Tab bar position

\n

Choose a class on an iOS ion-tab-bar to position the whole bar within its safe area:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ClassPlacement
tab-bar-position-startStart of the reading direction.
tab-bar-position-centerCenter.
tab-bar-position-endEnd of the reading direction.
NoneExisting placement unchanged.
\n

All three classes support slot="top" and slot="bottom", preserve width and press animation, and resolve start/end in RTL.

\n
<ion-tab-bar slot="bottom" class="tab-bar-position-center">\n  <ion-tab-button tab="home">Home</ion-tab-button>\n  <ion-tab-button tab="settings">Settings</ion-tab-button>\n</ion-tab-bar>
\n\n

These classes do not reposition a separate ion-fab; leave room for it when choosing the bar's position.

\n

Support iPhone Duo

\n

iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.

\n

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 for the complete setup, including device-layout monitoring without a projection runtime.

\n

See Vertical Bars for rail layout classes, control eligibility and native button appearance.

\n

For standalone setup while keeping your existing theme, see iPhone Duo with your existing theme.

\n

Two-line inset list items

\n

Place an unslotted ion-label immediately alongside an unslotted ion-note to render a two-line item. When using the iOS-style inset-list background, wrap the items in ion-item-group; keep ion-list-header outside the group.

\n
<ion-list inset="true">\n  <ion-list-header>\n    <ion-label>Connections</ion-label>\n  </ion-list-header>\n  <ion-item-group>\n    <ion-item>\n      <ion-label>Network &amp; internet</ion-label>\n      <ion-note>Mobile, Wi-Fi, hotspot</ion-note>\n    </ion-item>\n  </ion-item-group>\n</ion-list>
\n\n
\n
Preview
\n\n \n Connections\n \n \n \n Network & internet\n Mobile, Wi-Fi, hotspot\n \n \n\n
\n\n

Use slot="end" on ion-note when you want the standard trailing-note layout instead.

\n

Inset-list section headers

\n

Add .item-group-header to an ion-item-group to create the centered icon, title, and description used at the top of the component demo pages.

\n

This is an introductory group. Place regular list items in a separate ion-item-group that follows it.

\n
<ion-list inset="true">\n  <ion-item-group class="item-group-header">\n    <ion-item>\n      <ion-label>\n        <ion-icon name="list" style="background: var(--ion-color-primary)"></ion-icon>\n        <h2>Lists</h2>\n        <ion-text>Inset-list examples</ion-text>\n      </ion-label>\n    </ion-item>\n  </ion-item-group>\n  <ion-item-group>\n    <ion-item><ion-label>First item</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>
\n\n
\n
Preview
\n\n \n \n \n \n

Lists

\n Inset-list examples\n
\n
\n
\n \n First item\n \n
\n
\n\n

Full-width segments

\n

Add .segment-style-glass to give a segment the same glass surface and selected indicator treatment as the tab bar. The class preserves the segment's existing dimensions and text colors, supports scrollable segments, and respects Ionic's public --background property.

\n
<ion-segment class="segment-style-glass" value="available">\n  <ion-segment-button value="available">Available</ion-segment-button>\n  <ion-segment-button value="away">Away</ion-segment-button>\n</ion-segment>
\n\n

For colored segments, use Ionic's color property (for example, color="primary" or color="secondary"). Ionic uses the palette's base color for the softly tinted track while keeping the selected surface and labels neutral. A surrounding colored toolbar only supplies colors when the segment has no color of its own. The optional moving glass inherits the selected surface color, and custom Ionic palettes work without additional registration.

\n

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.

\n

Segment size and press behavior

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PlacementMinimum heightContainer behavior
In content32pxCompact, flat background and indicator; outer bounds stay fixed.
Inside ion-toolbar48pxGlass container; outer container scales while pressed.
.segment-expand, including in a toolbar32pxCompact, flat background and indicator; outer bounds stay fixed.
\n

The optional moving glass lens is independent of the container's background.

\n
<ion-segment class="segment-expand" value="new">\n  <ion-segment-button value="new"><ion-label>New</ion-label></ion-segment-button>\n  <ion-segment-button value="replied"><ion-label>Replied</ion-label></ion-segment-button>\n</ion-segment>
\n\n
\n
Preview
\n\n New\n Replied\n\n
\n\n

Classic search bar in a condense header

\n

The theme gives iOS search bars the iOS 27 appearance by default. Add .searchbar-classic to the search field shown beneath a large title in an ion-header with collapse="condense". It uses the conventional filled iOS appearance and collapses with the large title instead of remaining in the fixed header.

\n

Place it in a toolbar with a color, such as color="light"; the classic background is derived from that color's contrast value.

\n

The example uses Ionic's standard collapsible large-title structure. Scroll the preview to collapse the large title and reveal the fixed header.

\n
<div class="ion-page">\n  <ion-header translucent="true">\n    <ion-toolbar color="light">\n      <ion-title>Search</ion-title>\n    </ion-toolbar>\n  </ion-header>\n  <ion-content color="light" fullscreen="true">\n    <ion-header collapse="condense">\n      <ion-toolbar color="light">\n        <ion-title size="large">Search</ion-title>\n      </ion-toolbar>\n      <ion-toolbar color="light">\n        <ion-searchbar class="searchbar-classic" placeholder="Filter results"></ion-searchbar>\n      </ion-toolbar>\n    </ion-header>\n    <ion-list inset="true">\n      <ion-item-group>\n        <ion-item><ion-label>Recent item 1</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 2</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 3</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 4</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 5</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 6</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 7</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 8</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 9</ion-label></ion-item>\n        <ion-item><ion-label>Recent item 10</ion-label></ion-item>\n      </ion-item-group>\n    </ion-list>\n  </ion-content>\n</div>
\n\n
\n
Preview
\n
\n \n \n Search\n \n \n \n \n \n Search\n \n \n \n \n \n \n \n Recent item 1\n Recent item 2\n Recent item 3\n Recent item 4\n Recent item 5\n Recent item 6\n Recent item 7\n Recent item 8\n Recent item 9\n Recent item 10\n \n \n \n
\n
\n\n

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.

\n

Opting out

\n

Add .ios-theme-disabled to an individual Ionic component when it must retain Ionic's standard iOS styling.

\n

.ios26-disabled is deprecated but remains supported as an alias with the same behavior. Use .ios-theme-disabled for new code.

\n
<ion-button>iOS 27 theme</ion-button> <ion-button class="ios-theme-disabled">Standard Ionic button</ion-button>
\n\n
\n
Preview
\niOS 27 theme Standard Ionic button\n
\n\n

For the background model behind inset lists, see Using ion-item-group.

\n'; diff --git a/packages/capacitor-native-ui/README.md b/packages/capacitor-native-ui/README.md index 23938b53..47fff948 100644 --- a/packages/capacitor-native-ui/README.md +++ b/packages/capacitor-native-ui/README.md @@ -34,19 +34,21 @@ Projection changes presentation; it does not translate arbitrary HTML or CSS int ## Install from npm -Install in your Capacitor 8 application: +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. + +Install Native UI and Foldable together in your Capacitor application: ```sh -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` If you also use the iOS 27 theme, install or update both packages together: ```sh -npm install @rdlabo/capacitor-native-ui @rdlabo/ionic-theme-ios27 +npm install @rdlabo/capacitor-native-ui @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` -Both packages share the same release version. Declare Native UI as a direct dependency so Capacitor can discover the plugin. Theme-only apps can install just the theme; its dependency is resolved by npm and does not activate native controls. +The theme and Native UI share the same release version; Foldable is versioned independently. Declare Native UI as a direct dependency so Capacitor can discover the plugin. The setup commands install Foldable as a separate application dependency. Installing these packages does not activate native controls; application startup chooses projection and connects device layout. Continue with [Ionic setup](docs/setup.md), [HTML setup](docs/framework-independent.md), or [extensions](docs/native-ui-extensions.md). After configuring startup, build your application's Web assets, run `npx cap sync` and rebuild the native app. No repository checkout or archive download is needed. diff --git a/packages/capacitor-native-ui/docs/framework-independent.md b/packages/capacitor-native-ui/docs/framework-independent.md index 92421c8f..db2530d1 100644 --- a/packages/capacitor-native-ui/docs/framework-independent.md +++ b/packages/capacitor-native-ui/docs/framework-independent.md @@ -6,16 +6,18 @@ Use a Capacitor 8 app with an iOS project, Xcode 26+, and an iOS 26+ device or s Start with [how projection works](../README.md#how-projection-works): the HTML element remains the source while native controls or Web proxies provide its presentation. -**Want only Vertical Bars?** Follow steps 1–2 to install and mark your HTML buttons, then use the [Web rail configuration](#optional-web-rail) in place of step 3. Remove explicit markers from buttons you want to leave on the Web. Style the rail host in your application and build with step 4 for iOS. You do not need Ionic, `@rdlabo/ionic-theme-ios27`, or `vertical-bars.css`. +**Want only Vertical Bars?** Follow the [device placement setup](#device-placement-for-vertical-bars), alongside steps 1–4 below. Reuse the step 3 runtime without a fixed placement preference. Remove explicit markers from buttons you want to leave on the Web. You do not need Ionic, `@rdlabo/ionic-theme-ios27`, or `vertical-bars.css`. For Vertical Bars in an app using `ion-tabs`, `ion-toolbar` or other Ionic components with a custom theme, use the [Ionic Vertical Bars setup](ionic-vertical-bars.md#try-it-in-your-existing-ionic-app) instead. The framework (Angular, React or Vue) alone does not determine which adapter to use. ## 1. Install the plugin -Keep your existing Capacitor 8 installation and add the plugin directly: +[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. + +Keep your existing Capacitor installation and add Native UI and Foldable together: ```sh -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` The `/core` entry requires `@capacitor/core` but no Ionic dependencies. If this is currently a browser-only application, configure its Capacitor 8 iOS project before running native controls. @@ -70,15 +72,29 @@ For a custom HTML theme, declare `color-scheme: light` or `color-scheme: dark` i ## 4. Build and run on iOS -Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. With the step 3 configuration, Add renders natively and invokes its original click handler. With the rail configuration below, Add retains native element placement and Back uses the native rail when available. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged. +Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. With the step 3 configuration, Add renders at its source position and invokes its original click handler. Back requests the device-reported native rail when available; connect the device placement below to keep Web spacing in sync. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged. If a button stays on the Web on supported iOS, check that it is visible within the configured root, has an accessible name, and that the direct plugin dependency was synced and the iOS app rebuilt. In an app that also uses Ionic, set `ionic: { auto: false }` to project only explicit sources and registered extensions. See [selective Ionic projection](configuration.md#select-controls-explicitly-in-a-customized-ionic-app). -## Optional Web rail +## Device placement for Vertical Bars + +Foldable is already included in the standard installation in step 1. Connect it to your application layout as follows. + +Reuse the runtime from step 3 **without `verticalBars.placement`**. Native UI then follows the device-reported edge; a fixed `edge: 'trailing'` would constrain it and can reject a device's leading rail. + +Follow [Foldable integration](vertical-bars.md#with-application-owned-css-or-without-ionic): -For a Web fallback rail, use this instead of the startup call in step 3. Call once after both the source root and rail host are mounted; call `await shell.destroy()` when their owner is disposed. Do not start a second shell: +1. Subscribe to `barPlacementChange`, then read `getBarPlacement()` for the initial edge and inset. +2. Apply the edge and inset to your application's CSS. On a null edge, restore ordinary spacing and call `shell.disableVerticalControlArea()`; when an edge returns, call `shell.enableVerticalControlArea()`. Serialize these changes with layout updates. +3. At teardown, remove the listener and destroy the runtime owned by this application. + +Keep the explicit source markers from step 2; they declare the controls and their requested placement. To project only Back, remove `data-native-ui="button"` from Add. `controls` does not disable explicit declarations. Without a Web rail host, unavailable native rail controls stay at their original Web positions. The theme package is not required. + +## Optional browser rail preview + +For a browser-only layout preview, temporarily use this fixed Web rail instead of the startup call in step 3 and the device subscription. Do not copy the fixed placement into the standard device setup. Call once after the source root and host are mounted, and call `await shell.destroy()` when their owner is disposed: ```ts import { enableNativeUI } from '@rdlabo/capacitor-native-ui/core'; @@ -96,12 +112,10 @@ const shell = await enableNativeUI({ The plugin uses device-reported rail availability and resolves leading/trailing through the document direction. A placement preference never forces an unsupported system rail. Native rejection restores Web presentation. With `webHost`, explicitly rail-marked HTML controls have Web proxies when native projection is unavailable. Without it, their originals remain visible. The application styles and positions the host and reserves any content spacing. The host must be outside the source root. -Explicit markup chooses placement: Back goes to the rail and Add keeps native element placement. To project only Back, remove `data-native-ui="button"` from Add. `controls` does not disable explicit source declarations. To change rail use later, call `await shell.disableVerticalControlArea()` or `await shell.enableVerticalControlArea()`. Disabling restores original rail controls and removes Web rail proxies without destroying ordinary native projection. +Explicit markup chooses placement: Back goes to the preview rail and Add stays at its original position. Disabling rail projection restores original rail controls and removes Web proxies without destroying ordinary native projection. Before building for a device, restore the step 3 runtime and the Foldable subscription above. Currently the HTML adapter supports buttons and back buttons. Arbitrary HTML tabs, segments, searchbars and FAB structures are not translated. Ionic adapters continue to support their existing controls. Custom element extensions work with the same scoped root; the existing `tab-accessory` extension placement still requires an Ionic tab-bar owner. -For device-driven spacing and placement, see [Use with Capacitor Foldable](vertical-bars.md#use-with-capacitor-foldable). Read its initial placement and subscribe to changes, applying the edge and inset to your own CSS; the theme package is not required. - ## Example The [plain HTML example](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/main/examples/native-ui-html) demonstrates Add, routerless Home/Details/Back navigation, optional Web rail projection and cleanup. It uses no Ionic or theme CSS. diff --git a/packages/capacitor-native-ui/docs/ionic-vertical-bars.md b/packages/capacitor-native-ui/docs/ionic-vertical-bars.md index 3975d7ae..8c78b413 100644 --- a/packages/capacitor-native-ui/docs/ionic-vertical-bars.md +++ b/packages/capacitor-native-ui/docs/ionic-vertical-bars.md @@ -10,11 +10,11 @@ Read [how projection works](../README.md#how-projection-works) for the relations ## Choose your setup -| Your app | Start here | What you add | -| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| 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. | -| No Ionic components (plain HTML, React, Vue, Angular, etc.) | [HTML setup](framework-independent.md), then its [Web rail configuration](framework-independent.md#optional-web-rail) | Only `@rdlabo/capacitor-native-ui`, using its `/core` entry. Your app supplies the rail layout. | -| Already using the iOS 27 theme and Native UI | [Connect device placement](../../theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) | Reuse your existing runtime. Do not start another one. | +| Your app | Start here | What you add | +| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Ionic components with your own theme, Ionic's default styles, or another theme | [Ionic setup below](#try-it-in-your-existing-ionic-app) | Foldable device placement, standalone rail CSS and the Ionic Native UI adapter. Keep your existing theme. | +| No Ionic components (plain HTML, React, Vue, Angular, etc.) | [HTML setup](framework-independent.md), then its [device rail setup](framework-independent.md#device-placement-for-vertical-bars) | `@rdlabo/capacitor-native-ui/core` and Foldable. Your app supplies the rail layout; no theme package. | +| Already using the iOS 27 theme and Native UI | [Connect device placement](../../capacitor-native-ui/docs/vertical-bars.md#use-with-capacitor-foldable) | Reuse your existing runtime. Do not start another one. | **Without Ionic, follow the HTML guide instead of the steps below.** You do not need `@rdlabo/ionic-theme-ios27`, `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. @@ -26,10 +26,10 @@ iPhone Duo support and Vertical Bars are stable starting with version 2.0.0. ### 1. Install and load the standalone stylesheet -Start with your existing Ionic app (Ionic `>=8.8.1 <10`, Capacitor 8). Add the two packages: +Start with your existing Ionic app (Ionic `>=8.8.1 <10`, Capacitor 8.5 or later). Install Native UI, the layout stylesheet package and the required device-state plugin together: ```bash -npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui +npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` Keep your existing theme imports. Add this to your global Sass file: @@ -38,21 +38,11 @@ Keep your existing theme imports. Add this to your global Sass file: @use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css'; ``` -The full iOS 27 theme stylesheets are not required. Preview these steps in Chrome before connecting an iPhone Duo. +The full iOS 27 theme stylesheets are not required. Keep this stylesheet instead of Foldable's `ionic-tabs.css`; loading both would reposition the same tabs. -### 2. Opt your app into the side layout +We adopt [Capacitor Foldable](https://github.com/erkamyaman/capacitor-foldable) as the standard device integration because it exposes placement, posture and their changes while leaving layout decisions to your app. Pass its logical rail edge and measured inset to the theme's layout helper. Native UI checks native rail availability independently. -Add the class to your existing app root and keep the content inside it: - -```html - - - -``` - -The preview reserves `80px` on the physical right. To preview the left side, also add `ios-theme-vertical-bars-left`. - -### 3. Connect your navigation animation +### 2. Connect your navigation animation 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. @@ -106,27 +96,54 @@ The adapter returns the original `Animation`, preserving its effects, duration, 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. -### 4. Start the controls after the app root is mounted +### 3. Connect device placement and start the controls -Call this once from your application startup after `ion-app` exists in the DOM: +After `ion-app` is mounted, subscribe to placement changes, read the initial placement, and start one runtime. Keep your existing `ion-app` markup; the helper applies or removes the layout classes from device state: ```ts +import { Foldable, type BarPlacement } from '@erkamyaman/capacitor-foldable'; +import { setVerticalBarsLayout } from '@rdlabo/ionic-theme-ios27'; import { enableNativeUI } from '@rdlabo/capacitor-native-ui'; +const applyPlacement = ({ verticalBarEdge, inset }: BarPlacement) => setVerticalBarsLayout({ edge: verticalBarEdge, inset }); +const listener = await Foldable.addListener('barPlacementChange', applyPlacement); +applyPlacement(await Foldable.getBarPlacement()); const rail = await enableNativeUI({ controls: 'verticalOnly' }); ``` -**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. +| Device placement | Web layout | +| -------------------------------------- | -------------------------------------------------------------------------- | +| `leading` or `trailing` | Reserves the reported inset at that logical edge. The helper resolves RTL. | +| `null`, including the browser fallback | Restores the ordinary layout; no side rail is forced. | + +When the application owner is disposed, remove its listener and destroy the runtime it owns: + +```ts +await listener.remove(); +await rail.destroy(); +``` + +If Native UI is already active, keep that runtime and add only the placement subscription. Do not start a second runtime. For shared configuration and application-owned CSS, see [Foldable integration](vertical-bars.md#use-with-capacitor-foldable). + +### 4. Build, sync and run on iPhone Duo + +Build your application's Web assets, then sync the native project: + +```sh +npx cap sync ios +``` + +Build with Xcode 27.1 or newer and run on iPhone Duo with iOS 27.1 or later. Keep your existing SPM or CocoaPods integration; see [Native UI setup](setup.md#4-build-sync-and-run). -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. +**What you should see when the device reports a rail:** your existing tab bar and eligible fixed-toolbar actions move to the side. Content keeps its theme and reserves the measured inset. Supported iOS rails use SwiftUI; unavailable native presentation retains the Web rail. Devices reporting a null edge keep their ordinary layout. Use the [optional browser preview](#optional-browser-preview) to simulate a rail without a device report. -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](../../theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail). +Keep your existing click handlers, routing and form associations. By default, actions need an `ion-icon` or SVG with `slot="icon-only"` in a fixed toolbar. All fills (`default`, `clear`, `solid`, `outline`) follow this rule, including submit buttons. Other actions remain horizontal. Use `.ios-theme-horizontal-only` on an `ion-buttons` group or individual `ion-button` to keep an action horizontal. ### Optional: choose native button appearance See [Choose button appearance](vertical-bars.md#choose-button-appearance) for appearance settings and local overrides. -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: +The default is `system`: SwiftUI styles vertical buttons and tints their icons. If your existing theme should supply their fill and colors, replace only the `enableNativeUI` call in step 3 with the following; keep the Foldable subscription: ```ts const rail = await enableNativeUI({ @@ -137,36 +154,32 @@ const rail = await enableNativeUI({ 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](vertical-bars.md#override-individual-buttons-or-groups). These settings affect native vertical buttons only; Web clones keep their existing appearance. -### If the preview does not appear +### If the rail does not appear -| What you see | What to check | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| No space at the side | Load `vertical-bars.css` and put the class on `ion-app`. | -| 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. | -| 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](vertical-bars.md#toolbar-actions). | +| What you see | What to check | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| Ordinary layout | Check Foldable's reported edge. A null edge intentionally keeps ordinary layout. | +| Reported edge but no side spacing | Load `vertical-bars.css` and apply the initial placement and `barPlacementChange` events after `ion-app` mounts. | +| Space appears, but controls stay horizontal | Start the runtime once. Use existing tabs or supported `slot="icon-only"` actions in a fixed header/footer toolbar. | +| One action stays horizontal | Check its icon slot, fixed placement and exclusions. See [control requirements](vertical-bars.md#toolbar-actions). | -## Connect an iPhone Duo +## Optional browser preview -Install [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) for device state: +For local layout testing without a device report, temporarily omit the Foldable subscription from step 3 and apply a simulated placement after `ion-app` mounts: -```bash -npm install @erkamyaman/capacitor-foldable -npx cap sync ios +```ts +setVerticalBarsLayout({ edge: 'trailing', inset: 80 }); ``` -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](setup.md#4-build-sync-and-run). Keep the theme's `vertical-bars.css`; the device plugin's `ionic-tabs.css` is not needed with our rail projection. - -Replace the browser-only startup with the [device placement setup](../../theme-ios27/docs/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. - -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. +Keep the same stylesheet and single Native UI runtime. Use `leading` to preview the other logical edge, or `null` to restore ordinary layout. The helper manages `ios-theme-vertical-bars` and its physical-left variant; manual classes are not part of device setup. See [layout classes](../../theme-ios27/docs/vertical-bars-layout.md#reserve-the-vertical-rail) for CSS-only previews. -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. +Web rail tabs display icons and reveal labels while pressing and dragging. Browser previews verify Web layout and actions, not native SwiftUI appearance. Restore the Foldable subscription for the device build. ## Use hinge posture without projecting controls 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. -See [Read the device layout](../../theme-ios27/docs/iphone-duo.md#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](../../theme-ios27/docs/iphone-duo.md#adapt-the-split-pane) for the opt-in width rules and half-open state. +See [Read the device layout](../../theme-ios27/docs/vertical-bars-layout.md#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](../../theme-ios27/docs/vertical-bars-layout.md#adapt-the-split-pane) for the opt-in width rules and half-open state. ## Shared layout rules and API diff --git a/packages/capacitor-native-ui/docs/migration.md b/packages/capacitor-native-ui/docs/migration.md index 19f9397b..ae1cccc6 100644 --- a/packages/capacitor-native-ui/docs/migration.md +++ b/packages/capacitor-native-ui/docs/migration.md @@ -31,18 +31,18 @@ The theme uses the native package's shared transition/search integration interna ### 1. Update application dependencies -When using both packages, update them together to keep their versions aligned. +When using the theme and Native UI together, update them to the same version. The commands below also install [Capacitor Foldable](https://github.com/erkamyaman/capacitor-foldable) as a separate application dependency for placement and posture reporting. Foldable has its own release versions. For theme-only apps, update the theme package. Keep existing theme imports and CSS/Sass paths; no native startup or plugin setup is needed for theme-only use. ```sh -npm install @rdlabo/ionic-theme-ios27 +npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` For apps using both the theme and native controls, install both as direct application dependencies: ```sh -npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui +npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` For native controls with your existing Web appearance, follow [Native UI only](setup.md). Install the native package directly and keep your existing Web styles. The plugin no longer requires theme variables to select supported controls; Web layout remains the responsibility of your app or theme. Native startup requires Capacitor 8. The Ionic adapter requires Ionic 8.8.1 or later within Ionic 8 or 9; the `/core` entry does not require Ionic. @@ -122,7 +122,7 @@ Merge this option into your existing Ionic configuration before initialization. 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. -See [Connect your navigation animation](ionic-vertical-bars.md#3-connect-your-navigation-animation) for the setup and supported scope. +See [Connect your navigation animation](ionic-vertical-bars.md#2-connect-your-navigation-animation) for the setup and supported scope. ## Startup options diff --git a/packages/capacitor-native-ui/docs/native-ui-extensions.md b/packages/capacitor-native-ui/docs/native-ui-extensions.md index f9c02916..bd8e0906 100644 --- a/packages/capacitor-native-ui/docs/native-ui-extensions.md +++ b/packages/capacitor-native-ui/docs/native-ui-extensions.md @@ -15,7 +15,7 @@ Extensions use the same [projection model](../README.md#how-projection-works) as Start with an existing Capacitor 8 app and install the plugin if you have not already: ```sh -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` Choose the native platform you want to support: diff --git a/packages/capacitor-native-ui/docs/native-ui.md b/packages/capacitor-native-ui/docs/native-ui.md index 9d1bc642..a88d87e4 100644 --- a/packages/capacitor-native-ui/docs/native-ui.md +++ b/packages/capacitor-native-ui/docs/native-ui.md @@ -17,7 +17,7 @@ Already using the iOS theme? Follow [Add Native UI to your theme](https://github Install the native package as a direct application dependency so Capacitor can discover it: ```bash -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable npx cap sync ios ``` @@ -301,7 +301,7 @@ Custom host animation or transition declarations on the FAB, list or button keep ## Transitions and recovery -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 `withNativeUITransition()`, exported by `@rdlabo/ionic-theme-ios27`. See [Connect your navigation animation](ionic-vertical-bars.md#3-connect-your-navigation-animation) for the setup. +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 `withNativeUITransition()`, exported by `@rdlabo/ionic-theme-ios27`. See [Connect your navigation animation](ionic-vertical-bars.md#2-connect-your-navigation-animation) for the setup. Ordinary native/Web handoffs share a 180ms opacity crossfade, including Vertical Bars in both directions. Outgoing visuals cannot receive input or accessibility focus. Reduce Motion and modal presentation use immediate handoffs; search controllers keep their UIKit-managed transition. @@ -405,7 +405,7 @@ The native material and control appearance follow the running iOS version; an iO iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0. -The standalone Vertical Control Area entry point (`@rdlabo/capacitor-native-ui/vertical-bars`) works with application-supplied Web layout. The theme package offers an [optional rail stylesheet](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md), `@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css`, which can be used without loading the full iOS 27 appearance. Call `enableNativeUI({ controls: 'verticalOnly' })` for this use case; its default selection targets controls placed in the vertical area. Apps already calling `enableNativeUI()` should keep that single runtime rather than starting both. 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 hinge posture and the split-pane layout for apps that do not use this shell at all. +The standalone Vertical Control Area entry point (`@rdlabo/capacitor-native-ui/vertical-bars`) works with application-supplied Web layout. The theme package offers an [optional rail stylesheet](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md), `@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css`, which can be used without loading the full iOS 27 appearance. Call `enableNativeUI({ controls: 'verticalOnly' })` for this use case; its default selection targets controls placed in the vertical area. Apps already calling `enableNativeUI()` should keep that single runtime rather than starting both. 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 hinge posture and the split-pane layout for apps that do not use this shell at all. Vertical Bars uses a SwiftUI `TabView` and toolbar in the system rail. Ionic remains the source of labels, icons, selected/disabled state and actions. See [Vertical Bars](vertical-bars.md) for rail layout, native fallback and supported overlays. diff --git a/packages/capacitor-native-ui/docs/setup.md b/packages/capacitor-native-ui/docs/setup.md index f544eefc..b289b228 100644 --- a/packages/capacitor-native-ui/docs/setup.md +++ b/packages/capacitor-native-ui/docs/setup.md @@ -21,10 +21,12 @@ If the app does not yet use Capacitor, create and configure its Capacitor iOS pr ## 1. Install the plugin +[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. + Keep your Capacitor packages on version 8. Add these direct application dependencies: ```sh -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` The Ionic adapter uses `@ionic/core`, which your Ionic app already supplies. Do not remove your existing Web styles: they style page content and fallback controls. @@ -140,7 +142,7 @@ These hooks manage the app's lifetime, not individual page transitions. Configur For optional Ionic animation coordination, install `@rdlabo/ionic-theme-ios27` and update Native UI together so their versions match. Configure navigation before Ionic initializes. Its adapter can retain Ionic's default transitions without loading theme CSS: ```sh -npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui +npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` ```ts diff --git a/packages/capacitor-native-ui/docs/vertical-bars.md b/packages/capacitor-native-ui/docs/vertical-bars.md index 8045da2d..c4a7803f 100644 --- a/packages/capacitor-native-ui/docs/vertical-bars.md +++ b/packages/capacitor-native-ui/docs/vertical-bars.md @@ -9,10 +9,10 @@ Vertical Bars moves eligible Ionic controls or explicitly marked HTML buttons in Choose your setup before using this reference: - **Ionic with a custom or default theme:** [Ionic rail setup](ionic-vertical-bars.md#try-it-in-your-existing-ionic-app). -- **No Ionic:** [HTML setup](framework-independent.md) and its [Web rail configuration](framework-independent.md#optional-web-rail). Use `/core` and your own layout; the Ionic setup below does not apply. -- **Existing iOS 27 theme and Native UI:** [Connect device placement](../../theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) using your existing runtime. +- **No Ionic:** [HTML setup](framework-independent.md) and its [device rail setup](framework-independent.md#device-placement-for-vertical-bars). Use `/core` and your own layout; the Ionic setup below does not apply. +- **Existing iOS 27 theme and Native UI:** [Connect device placement](../../capacitor-native-ui/docs/vertical-bars.md#use-with-capacitor-foldable) using your existing runtime. -This page covers device-plugin integration, control eligibility, native appearance and runtime options. For posture-driven Ionic split panes, see [iPhone Duo support](../../theme-ios27/docs/iphone-duo.md). +This page covers device-plugin integration, control eligibility, native appearance and runtime options. For posture-driven Ionic split panes, see [Device layout and split panes](../../theme-ios27/docs/vertical-bars-layout.md). Vertical Bars and iPhone Duo support are stable starting with version 2.0.0. @@ -20,23 +20,15 @@ See [shared configuration](configuration.md) for all defaults and live `rail.ena ## Enable Vertical Bars -Install the native package as a direct dependency: +Install Native UI and the required Foldable device-state plugin as direct application dependencies: ```sh -npm install @rdlabo/capacitor-native-ui +npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` -For Capacitor iOS, build your Web assets and run `npx cap sync ios`. Supply the Web layout through your app or theme as described below. +For Ionic, follow the [standard setup](ionic-vertical-bars.md): load your rail CSS, configure navigation, then connect Foldable's initial placement and `barPlacementChange` events before starting one runtime. The [integration below](#use-with-capacitor-foldable) also covers existing runtimes and application-owned CSS. Build your Web assets, run `npx cap sync ios`, and rebuild the native app. -For Ionic apps, start one runtime after `ion-app` is mounted: - -```ts -import { enableNativeUI } from '@rdlabo/capacitor-native-ui'; - -const rail = await enableNativeUI({ controls: 'verticalOnly' }); -``` - -Configure your Web rail layout and [apply device placement](#use-with-capacitor-foldable) for an iPhone Duo. The application owns placement and cleanup: call `await rail.destroy()` when its owner is disposed. If you already use `enableNativeUI()`, keep that runtime; it includes Vertical Bars. Do not start both. +A runtime alone does not reserve Web space. Keep the device subscription for the standard setup; manual placement belongs to the [optional browser preview](ionic-vertical-bars.md#optional-browser-preview). If Native UI is already active, reuse it rather than starting another runtime. The `/vertical-bars` entry point requires `@capacitor/core`, including in browser builds. Using only the theme’s rail CSS needs no native runtime. For native setup and navigation transitions, follow the [existing-theme guide](ionic-vertical-bars.md). @@ -50,7 +42,7 @@ The [theme's optional rail stylesheet](https://github.com/rdlabo-dev/ionic-theme ## Use with Capacitor Foldable -Use [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) when your Web layout needs to follow the device's bar placement or hinge posture. Native UI does not subscribe to Foldable automatically. +[`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) is required in the standard Vertical Bars setup. Install it with Native UI as shown above, and connect its device placement to your Web layout. Native UI does not subscribe to Foldable automatically. | Owner | Responsibility | | -------------------- | ------------------------------------------------------------------------------------------- | @@ -58,24 +50,19 @@ Use [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-f | Application or theme | Reserve Web space and choose the layout using the reported logical edge and measured inset. | | Native UI | Check native rail availability independently and project eligible source controls. | -```sh -npm install @erkamyaman/capacitor-foldable -npx cap sync ios -``` - ### With the theme's Ionic rail layout -After loading [the rail stylesheet](../../theme-ios27/docs/vertical-bars-layout.md) and mounting `ion-app`, start the runtime once and connect device placement: +After loading [the rail stylesheet](../../theme-ios27/docs/vertical-bars-layout.md) and mounting `ion-app`, connect device placement and start the runtime once: ```ts import { Foldable, type BarPlacement } from '@erkamyaman/capacitor-foldable'; import { setVerticalBarsLayout } from '@rdlabo/ionic-theme-ios27'; import { enableNativeUI } from '@rdlabo/capacitor-native-ui'; -const rail = await enableNativeUI({ controls: 'verticalOnly' }); const applyPlacement = ({ verticalBarEdge, inset }: BarPlacement) => setVerticalBarsLayout({ edge: verticalBarEdge, inset }); const listener = await Foldable.addListener('barPlacementChange', applyPlacement); applyPlacement(await Foldable.getBarPlacement()); +const rail = await enableNativeUI({ controls: 'verticalOnly' }); ``` If a runtime is already active, keep it and add only the placement subscription. `setVerticalBarsLayout` resolves logical edges for RTL; a null edge restores the ordinary Web layout. Use the measured inset instead of a fixed width. Keep the theme's rail stylesheet; Foldable's `ionic-tabs.css` is not needed for this integration. @@ -89,11 +76,11 @@ await rail.destroy(); ### With application-owned CSS or without Ionic -Use the same `getBarPlacement()` / `barPlacementChange` subscription, but apply the edge and inset to your own CSS instead of calling `setVerticalBarsLayout`. A null edge means your layout should restore its ordinary spacing. For Ionic, retain the rail layout classes described above; for plain HTML, use the `/core` entry and [source placement and an optional Web rail host](framework-independent.md#optional-web-rail). Neither Foldable nor Native UI supplies the application's CSS. +Use the same `getBarPlacement()` / `barPlacementChange` subscription, but apply the edge and inset to your own CSS instead of calling `setVerticalBarsLayout`. A null edge means your layout should restore its ordinary spacing. For Ionic, retain the rail layout classes described above; for plain HTML, use the `/core` entry and [device rail setup](framework-independent.md#device-placement-for-vertical-bars). Neither Foldable nor Native UI supplies the application's CSS. Explicit HTML rail markers remain active independently of CSS; to return those sources to their original placement on a null edge, call `disableVerticalControlArea()`, then `enableVerticalControlArea()` when a rail edge returns. Serialize these changes with your layout updates. -Keep Web layout updates separate from native availability: do not pass Foldable's edge through the deprecated `nativeEdge` option. Native UI reads that capability itself. A browser layout preview does not require Foldable. +Keep Web layout updates separate from native availability: do not pass Foldable's edge through the deprecated `nativeEdge` option. Native UI reads that capability itself. Only an optional browser layout preview substitutes simulated placement for this subscription. -For hinge-driven content layout, read `getFoldState()` and subscribe to `foldStateChange` separately. Hinge posture does not select controls or enable the rail; see the theme's [posture subscription and split-pane example](../../theme-ios27/docs/iphone-duo.md#read-the-device-layout). +For hinge-driven content layout, read `getFoldState()` and subscribe to `foldStateChange` separately. Hinge posture does not select controls or enable the rail; see the theme's [posture subscription and split-pane example](../../theme-ios27/docs/vertical-bars-layout.md#read-the-device-layout). ## Native rendering diff --git a/packages/theme-ios27/README.md b/packages/theme-ios27/README.md index c1a78969..b8c58e2a 100644 --- a/packages/theme-ios27/README.md +++ b/packages/theme-ios27/README.md @@ -1,6 +1,6 @@ # Ionic Theme iOS27 -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). +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). 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. **[Ionic 9 demo](https://ionic-theme-ios27.rdlabo.dev/) · [Ionic 8 demo](https://ionic8-theme-ios27.rdlabo.dev/) · [Setup guide](docs/setup.md)** @@ -27,7 +27,9 @@ A theme for Ionic apps that brings iOS 27 Liquid Glass and motion to the Web. Ca ## Get started -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. +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. + +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. 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). @@ -38,5 +40,4 @@ The theme styles toolbars, tabs, lists, buttons, search, and overlays, with ligh - [Using ion-item-group](docs/using-ion-item-group.md): inset list markup. - [Animation](docs/animation.md): tab, segment, and search effects. - [ESLint](docs/eslint.md): catch unsupported list structure. -- [Vertical Bars layout](docs/vertical-bars-layout.md): spacing, RTL, and layout helpers. -- [iPhone Duo device layout](docs/iphone-duo.md): connect device placement and hinge posture. +- [Vertical Bars layout](docs/vertical-bars-layout.md): spacing, RTL, device placement, posture, and split panes. diff --git a/packages/theme-ios27/docs/adaptive-themes.md b/packages/theme-ios27/docs/adaptive-themes.md index efc074f7..a73b5c58 100644 --- a/packages/theme-ios27/docs/adaptive-themes.md +++ b/packages/theme-ios27/docs/adaptive-themes.md @@ -7,7 +7,7 @@ Use this optional setup when you want browser capabilities to select iOS styles, Install both themes in an existing Ionic 8 or 9 app (`@ionic/core` 8.8.1 or later): ```bash -npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 +npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` ## 2. Load styles @@ -106,7 +106,7 @@ Use this markup to preview the inset grouped list look. For the list structure t 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: ```bash -npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3 +npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3 @erkamyaman/capacitor-foldable ``` 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: diff --git a/packages/theme-ios27/docs/add-native-ui.md b/packages/theme-ios27/docs/add-native-ui.md index 5da8adab..07b7897b 100644 --- a/packages/theme-ios27/docs/add-native-ui.md +++ b/packages/theme-ios27/docs/add-native-ui.md @@ -10,10 +10,10 @@ Read [how projection works](../../capacitor-native-ui/README.md#how-projection-w 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. -Install both packages together from npm so the theme and native plugin use the same version: +Update the theme and Native UI together to keep their versions aligned, and install Foldable as a separate application dependency: ```sh -npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui +npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable ``` 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 ## Optional: Vertical Bars -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). +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). ## Upgrading an existing native integration diff --git a/packages/theme-ios27/docs/iphone-duo.md b/packages/theme-ios27/docs/iphone-duo.md deleted file mode 100644 index b5280808..00000000 --- a/packages/theme-ios27/docs/iphone-duo.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: iPhone Duo support ---- - -# iPhone Duo support - -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. - -**New here?** Start with [iPhone Duo with your existing theme](../../capacitor-native-ui/docs/ionic-vertical-bars.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. - -iPhone Duo support is stable starting with version 2.0.0. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer. - -Use the theme layout and native plugin independently for that hardware. Neither requires the full iOS 27 appearance: - -- 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. -- 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. - -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. - -| Owner | Responsibility | -| ------------------- | ----------------------------------------------------------------------------------------------------------- | -| Foldable | Report bar placement and hinge posture, initially and when they change. | -| Application / theme | Subscribe to device events and apply Web layout through `setVerticalBarsLayout` or `applyFoldStateClasses`. | -| Native UI | Project eligible controls and determine native rail availability independently. | - -For the native package's integration, including application-owned CSS and plain HTML, see [Use with Capacitor Foldable](../../capacitor-native-ui/docs/vertical-bars.md#use-with-capacitor-foldable). - -## Choose what to adopt - -To keep your existing theme and add only the standalone support, follow [iPhone Duo with your existing theme](../../capacitor-native-ui/docs/ionic-vertical-bars.md). This page covers the shared device-layout rules. - -| Goal | Stylesheet | Runtime | -| ------------------------------------------ | ------------------- | ---------------------------------------------- | -| Hinge posture only (layout switches) | none | none — subscribe to `Foldable` directly | -| Posture-driven split-pane width | `vertical-bars.css` | none — subscribe to `Foldable` directly | -| Vertical rail for tabs and toolbar actions | `vertical-bars.css` | `enableNativeUI({ controls: 'verticalOnly' })` | -| Native shell plus the rail | `vertical-bars.css` | `enableNativeUI()` — includes rail projection | - -```scss -@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css'; -``` - -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. - -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. - -## Read the device layout - -Install the device-state plugin in the application, then sync the native project: - -```bash -npm install @erkamyaman/capacitor-foldable -npx cap sync -``` - -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. - -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. - -```ts -import { Foldable } from '@erkamyaman/capacitor-foldable'; -import { applyFoldStateClasses } from '@rdlabo/ionic-theme-ios27'; - -const root = document.querySelector('ion-app')!; -const listener = await Foldable.addListener('foldStateChange', (fold) => applyFoldStateClasses(root, fold)); -applyFoldStateClasses(root, await Foldable.getFoldState()); -``` - -When the application owner is disposed: - -```ts -await listener.remove(); -``` - -Subscribe to changes, then read the current state. The same helper handles initial values and events. - -### Posture classes - -`applyFoldStateClasses` updates the supplied root while preserving unrelated classes: - -| Class | Applied when | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `ios-theme-fold-flat` | Flat posture. | -| `ios-theme-fold-half-opened` | Half-opened posture. | -| `ios-theme-fold-closed` | Closed posture. | -| `ios-theme-fold-expanded` | Half-opened, or flat with hinge geometry. Cleared for flat without geometry (including Web fallback) and closed posture. | - -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. - -### Placement values - -For rail placement, import `setVerticalBarsLayout` from the theme and pass the reported logical `edge` with the measured `inset`: - -| Value | Web layout effect | -| ------------------ | ---------------------------------------------------- | -| `edge: 'leading'` | Physical left in LTR; physical right in RTL. | -| `edge: 'trailing'` | Physical right in LTR; physical left in RTL. | -| `edge: null` | Restores ordinary layout. | -| Measured `inset` | Reserves that width; zero clears the explicit width. | - -The helper does not report native availability or activate projection. No start/stop monitoring calls are needed. See [Project controls into the rail](#project-controls-into-the-rail) for the subscription. - -WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`. - -**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. - -## Reserve the vertical rail - -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. - -## Project controls into the rail - -Enable rail projection once after `ion-app` is mounted, and apply Foldable's placement to the Web layout with `setVerticalBarsLayout`: - -```ts -import { Foldable } from '@erkamyaman/capacitor-foldable'; -import { setVerticalBarsLayout } from '@rdlabo/ionic-theme-ios27'; -import { enableNativeUI } from '@rdlabo/capacitor-native-ui'; - -const rail = await enableNativeUI({ controls: 'verticalOnly' }); -const listener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) => - setVerticalBarsLayout({ edge: verticalBarEdge, inset }), -); -const { verticalBarEdge, inset } = await Foldable.getBarPlacement(); -setVerticalBarsLayout({ edge: verticalBarEdge, inset }); -``` - -When the application owner is disposed: - -```ts -await listener.remove(); -await rail.destroy(); -``` - -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. - -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. - -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. - -### Toolbar actions - -See [Vertical Bars: Toolbar actions](../../capacitor-native-ui/docs/vertical-bars.md#toolbar-actions) for eligible markup, placement, button appearance and local overrides. - -### Tab bar - -See [Vertical Bars: Tab bar](../../capacitor-native-ui/docs/vertical-bars.md#tab-bar) for navigation, labels and Web fallback behavior. - -## Adapt the split pane - -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: - -```html - - ... -
...
-
-``` - -Set the ordinary split-pane width to 320pt in the application's stylesheet, and let the half-open class change only the width value: - -```css -ion-split-pane.split-pane-fold-layout { - --ios-theme-menu-width: var(--ios-theme-split-pane-width); - --side-width: var(--ios-theme-menu-width); - --side-max-width: var(--ios-theme-menu-width); - transition: --ios-theme-split-pane-width 300ms ease; -} -``` - -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. - -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. - -## Vertical Control Area API - -The runtime handle reference is maintained in [Vertical Bars](../../capacitor-native-ui/docs/vertical-bars.md#vertical-control-area-api). diff --git a/packages/theme-ios27/docs/migration.md b/packages/theme-ios27/docs/migration.md index b75dc932..23f6d2b2 100644 --- a/packages/theme-ios27/docs/migration.md +++ b/packages/theme-ios27/docs/migration.md @@ -15,6 +15,26 @@ Theme styles and ordinary theme API imports remain supported. If you previously 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. +## Device layout in 2.0 + +Install Foldable alongside the theme as part of the standard setup: + +```sh +npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable +``` + +Replace the former device-layout APIs with Foldable subscriptions: + +| Previous API | Replacement | +| ------------------------------------ | ----------------------------------------------------------------------------------- | +| `DeviceLayout`, `HingeStatus` | `BarPlacement`, `FoldState` and `HingeStatus` from `@erkamyaman/capacitor-foldable` | +| `getDeviceLayout()` | `Foldable.getBarPlacement()` and `Foldable.getFoldState()` | +| `deviceLayoutChange` | `barPlacementChange` and `foldStateChange` listeners | +| Start/stop device-layout monitoring | Subscribe with `addListener()`; remove each listener at teardown | +| One-shot WebView corner-radius reads | Native UI's `getWebViewMetrics()` | + +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. + ## From the iOS 26 theme For an app using `@rdlabo/ionic-theme-ios26`, the recommended migration keeps that package and adds `@rdlabo/ionic-theme-ios27`. The [adaptive theme setup](adaptive-themes.md) selects iOS 27 or iOS 26 styles by browser capability and leaves Ionic's default iOS appearance on older browsers. @@ -24,7 +44,7 @@ For an app using `@rdlabo/ionic-theme-ios26`, the recommended migration keeps th Keep the iOS 26 package and add iOS 27. The new theme requires `@ionic/core` 8.8.1 or later (Ionic 8 or 9). ```bash -npm install @rdlabo/ionic-theme-ios27 +npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` ### 2. Make the styles adaptive diff --git a/packages/theme-ios27/docs/setup.md b/packages/theme-ios27/docs/setup.md index 85720a93..63772a1e 100644 --- a/packages/theme-ios27/docs/setup.md +++ b/packages/theme-ios27/docs/setup.md @@ -4,14 +4,16 @@ Give an existing Ionic app the iOS 27 appearance in four steps. This guide uses ## Before you start -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. +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. -## 1. Install the theme +## 1. Install the theme and Foldable ```sh -npm install @rdlabo/ionic-theme-ios27 +npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` +[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. + ## 2. Load the styles Add these imports to your global stylesheet after Ionic's base styles. Replace an existing Ionic dark palette import rather than loading multiple palettes: diff --git a/packages/theme-ios27/docs/special-markup.md b/packages/theme-ios27/docs/special-markup.md index d545046e..eceff66a 100644 --- a/packages/theme-ios27/docs/special-markup.md +++ b/packages/theme-ios27/docs/special-markup.md @@ -74,7 +74,7 @@ These classes do not reposition a separate `ion-fab`; leave room for it when cho iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0. -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. +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. 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. diff --git a/packages/theme-ios27/docs/vertical-bars-layout.md b/packages/theme-ios27/docs/vertical-bars-layout.md index 9f62eb51..b5a46517 100644 --- a/packages/theme-ios27/docs/vertical-bars-layout.md +++ b/packages/theme-ios27/docs/vertical-bars-layout.md @@ -3,7 +3,7 @@ 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). ```sh -npm install @rdlabo/ionic-theme-ios27 +npm install @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable ``` ```scss @@ -14,6 +14,8 @@ The layout behavior below assumes this stylesheet is loaded. For native runtime ## Reserve the vertical rail +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. + 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: ```html @@ -62,6 +64,74 @@ setVerticalBarsLayout({ edge: null }); // restore ordinary Web layout 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. +## Read the device layout + +The standard theme and Native UI setup commands install Foldable as a separate application dependency. After building your Web assets, sync the native project: + +```bash +npx cap sync +``` + +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. + +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. + +```ts +import { Foldable } from '@erkamyaman/capacitor-foldable'; +import { applyFoldStateClasses } from '@rdlabo/ionic-theme-ios27'; + +const root = document.querySelector('ion-app')!; +const listener = await Foldable.addListener('foldStateChange', (fold) => applyFoldStateClasses(root, fold)); +applyFoldStateClasses(root, await Foldable.getFoldState()); +``` + +When the application owner is disposed: + +```ts +await listener.remove(); +``` + +Subscribe to changes, then read the current state. The same helper handles initial values and events. + +### Posture classes + +`applyFoldStateClasses` updates the supplied root while preserving unrelated classes: + +| Class | Applied when | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `ios-theme-fold-flat` | Flat posture. | +| `ios-theme-fold-half-opened` | Half-opened posture. | +| `ios-theme-fold-closed` | Closed posture. | +| `ios-theme-fold-expanded` | Half-opened, or flat with hinge geometry. Cleared for flat without geometry (including Web fallback) and closed posture. | + +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. + +## Adapt the split pane + +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: + +```html + + ... +
...
+
+``` + +Set the ordinary split-pane width to 320pt in the application's stylesheet, and let the half-open class change only the width value: + +```css +ion-split-pane.split-pane-fold-layout { + --ios-theme-menu-width: var(--ios-theme-split-pane-width); + --side-width: var(--ios-theme-menu-width); + --side-max-width: var(--ios-theme-menu-width); + transition: --ios-theme-split-pane-width 300ms ease; +} +``` + +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. + +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. + ## Migration Move `setVerticalControlAreaPlacement` calls from the plugin to `setVerticalBarsLayout({ edge, inset })` from the theme. Move `applyFoldStateClasses` imports to the theme too. Remove `nativeEdge`: it no longer reports availability. The old plugin helpers are deprecated; update existing imports using the [migration guide](../../capacitor-native-ui/docs/migration.md#web-layout-ownership).