diff --git a/.gitignore b/.gitignore index 71e2c8eba4d..a1ae05b6ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ yarn-debug.log* yarn-error.log* static/**/node_modules/ +.idea diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f589964046..d398360bf39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,7 @@ Thanks for your interest in contributing to Ionic's documentation! :tada: Check - [Reporting Issues](#reporting-issues) - [Pull Request Guidelines](#pull-request-guidelines) - [Deploying](#deploying) + - [Archiving a Version](#archiving-a-version) - [License](#license) --- @@ -175,6 +176,33 @@ When submitting pull requests, please keep the scope of your change contained to The Ionic documentation's `main` branch is deployed automatically and separately from the [Ionic site](https://github.com/ionic-team/ionic-site) itself. The Ionic site then uses a proxy for paths under `/docs` to request the deployed documentation. +### Archiving a Version + +Archived versions are served from a frozen Vercel deployment instead of being rebuilt on every `main` deploy, which keeps build times and memory usage low. Two files control this: + +- [`versions.json`](./versions.json): lists the versions Docusaurus rebuilds on every deploy. +- [`versionsArchived.json`](./versionsArchived.json): maps each archived version to the frozen deployment URL the version picker links to. + +The archived URL has to point at a build that _included_ the version, so you build it first, then move it to `versionsArchived.json`: + +1. **Build the version.** Make sure it is in `versions.json`. If you are refreshing an already-archived version, move it out of `versionsArchived.json` and back into `versions.json`. Commit, push and let Vercel deploy. +2. **Promote the deployment.** In the Vercel dashboard, open that deployment and **Promote to Production** so it does not get cleaned up. Wait for the build to finish before pushing again, or it may get canceled. +3. **Copy its URL.** Use the deployment's unique `ionic-docs--ionic1.vercel.app` URL, not the branch or production alias. +4. **Archive it.** Remove the version from `versions.json`, then add it to `versionsArchived.json` with `/docs/` appended and no trailing slash (a trailing slash causes a brief 404 flash): + + ```json + { + "v6": "https://ionic-docs--ionic1.vercel.app/docs/v6" + } + ``` + +5. **Open a PR.** Once merged, the version picker links to the archive and `main` stops building that version. + +Removed versions keep their `versioned_docs/` and `versioned_sidebars/` content, so they can be rebuilt anytime by adding them back to `versions.json`. + +> [!NOTE] +> Ionic v3 and v4 use other build tools and are not managed here. + --- ## License diff --git a/_templates/playground/new/vue.md.ejs.t b/_templates/playground/new/vue.md.ejs.t index 1a13f8b8dcc..5867eb38b95 100644 --- a/_templates/playground/new/vue.md.ejs.t +++ b/_templates/playground/new/vue.md.ejs.t @@ -2,7 +2,7 @@ arbitrary: <% pascalComponent = h.changeCase.pascal(component) %> to: "<%= `static/usage/v${version}/${name}/${path}/vue.md` %>" --- -```html +```vue diff --git a/cspell-wordlist.txt b/cspell-wordlist.txt index c7be2770bad..dacba1037f5 100644 --- a/cspell-wordlist.txt +++ b/cspell-wordlist.txt @@ -13,6 +13,7 @@ Udemy Vetur Wistia WCAG +CDK actionsheet fabs diff --git a/docs/angular/overlays.md b/docs/angular/overlays.md new file mode 100644 index 00000000000..2e4335effcd --- /dev/null +++ b/docs/angular/overlays.md @@ -0,0 +1,213 @@ +--- +title: Overlay Components +sidebar_label: Overlays +--- + + + Angular Overlay Components: Modals, Popovers with Custom Injectors + + + +Ionic provides overlay components such as modals and popovers that display content on top of your application. In Angular, these overlays can be created using controllers like `ModalController` and `PopoverController`. + +## Creating Overlays + +Overlays can be created programmatically using their respective controllers: + +```typescript +import { Component } from '@angular/core'; +import { ModalController } from '@ionic/angular/standalone'; +import { MyModalComponent } from './my-modal.component'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', +}) +export class HomeComponent { + constructor(private modalController: ModalController) {} + + async openModal() { + const modal = await this.modalController.create({ + component: MyModalComponent, + componentProps: { + title: 'My Modal', + }, + }); + await modal.present(); + } +} +``` + +## Custom Injectors + +By default, overlay components use the root injector for dependency injection. This means that services or tokens provided at the route level or within a specific component tree are not accessible inside the overlay. + +The `injector` option allows you to pass a custom Angular `Injector` when creating a modal or popover. This enables overlay components to access services and tokens that are not available in the root injector. + +### Use Cases + +Custom injectors are useful when you need to: + +- Access route-scoped services from within an overlay +- Use Angular CDK's `Dir` directive for bidirectional text support +- Access any providers that are not registered at the root level + +### Usage + +To use a custom injector, pass it to the `create()` method: + +```typescript +import { Component, Injector } from '@angular/core'; +import { ModalController } from '@ionic/angular/standalone'; +import { MyModalComponent } from './my-modal.component'; +import { MyRouteService } from './my-route.service'; + +@Component({ + selector: 'app-feature', + templateUrl: './feature.component.html', + providers: [MyRouteService], // Service provided at route level +}) +export class FeatureComponent { + constructor(private modalController: ModalController, private injector: Injector) {} + + async openModal() { + const modal = await this.modalController.create({ + component: MyModalComponent, + injector: this.injector, // Pass the component's injector + }); + await modal.present(); + } +} +``` + +The modal component can now inject `MyRouteService`: + +```typescript +import { Component, inject } from '@angular/core'; +import { MyRouteService } from '../my-route.service'; + +@Component({ + selector: 'app-my-modal', + templateUrl: './my-modal.component.html', +}) +export class MyModalComponent { + private myRouteService = inject(MyRouteService); +} +``` + +### Creating a Custom Injector + +You can also create a custom injector with specific providers: + +```typescript +import { Component, Injector } from '@angular/core'; +import { ModalController } from '@ionic/angular/standalone'; +import { MyModalComponent } from './my-modal.component'; +import { MyService } from './my.service'; + +@Component({ + selector: 'app-feature', + templateUrl: './feature.component.html', +}) +export class FeatureComponent { + constructor(private modalController: ModalController, private injector: Injector) {} + + async openModal() { + const myService = new MyService(); + myService.configure({ someOption: true }); + + const customInjector = Injector.create({ + providers: [{ provide: MyService, useValue: myService }], + parent: this.injector, + }); + + const modal = await this.modalController.create({ + component: MyModalComponent, + injector: customInjector, + }); + await modal.present(); + } +} +``` + +### Using with Angular CDK Directionality + +A common use case is providing the Angular CDK `Dir` directive to overlays for bidirectional text support: + +```typescript +import { Component, Injector } from '@angular/core'; +import { Dir } from '@angular/cdk/bidi'; +import { ModalController } from '@ionic/angular/standalone'; +import { MyModalComponent } from './my-modal.component'; + +@Component({ + selector: 'app-feature', + templateUrl: './feature.component.html', +}) +export class FeatureComponent { + constructor(private modalController: ModalController, private injector: Injector) {} + + async openModal() { + const modal = await this.modalController.create({ + component: MyModalComponent, + injector: this.injector, // Includes Dir from component tree + }); + await modal.present(); + } +} +``` + +### Popover Controller + +The `PopoverController` supports the same `injector` option: + +```typescript +import { Component, Injector } from '@angular/core'; +import { PopoverController } from '@ionic/angular/standalone'; +import { MyPopoverComponent } from './my-popover.component'; + +@Component({ + selector: 'app-feature', + templateUrl: './feature.component.html', +}) +export class FeatureComponent { + constructor(private popoverController: PopoverController, private injector: Injector) {} + + async openPopover(event: Event) { + const popover = await this.popoverController.create({ + component: MyPopoverComponent, + event: event, + injector: this.injector, + }); + await popover.present(); + } +} +``` + +## Angular Options Types + +Ionic Angular exports its own `ModalOptions` and `PopoverOptions` types that extend the core options with Angular-specific properties like `injector`: + +- `ModalOptions` - Extends core `ModalOptions` with the `injector` property +- `PopoverOptions` - Extends core `PopoverOptions` with the `injector` property + +These types are exported from `@ionic/angular` and `@ionic/angular/standalone`: + +```typescript +import type { ModalOptions, PopoverOptions } from '@ionic/angular/standalone'; +``` + +## Docs for Overlays in Ionic + +For full docs and to see usage examples, visit the docs page for each of the overlays in Ionic: + +- [Action Sheet](https://ionicframework.com/docs/api/action-sheet) +- [Alert](https://ionicframework.com/docs/api/alert) +- [Loading](https://ionicframework.com/docs/api/loading) +- [Modal](https://ionicframework.com/docs/api/modal) +- [Picker](https://ionicframework.com/docs/api/picker) +- [Popover](https://ionicframework.com/docs/api/popover) +- [Toast](https://ionicframework.com/docs/api/toast) diff --git a/docs/angular/quickstart.md b/docs/angular/quickstart.md index e2d7b1436f0..fa7bcf218bf 100644 --- a/docs/angular/quickstart.md +++ b/docs/angular/quickstart.md @@ -172,7 +172,7 @@ And the template, in the `home.page.html` file, uses those components: ``` -This creates a page with a header and scrollable content area. The second header shows a [collapsible large title](/docs/api/title.md#collapsible-large-titles) that displays when at the top of the content, then condenses to show the smaller title in the first header when scrolling down. +This creates a page with a header and scrollable content area. The second header shows a [collapsible large title](/docs/api/title.md#collapsible-large-titles) that displays on iOS devices when at the top of the content, then condenses to show the smaller title in the first header when scrolling down. :::tip Learn More For detailed information about Ionic layout components, see the [Header](/docs/api/header.md), [Toolbar](/docs/api/toolbar.md), [Title](/docs/api/title.md), and [Content](/docs/api/content.md) documentation. diff --git a/docs/api/datetime-button.md b/docs/api/datetime-button.md index 69514649056..846993f097b 100644 --- a/docs/api/datetime-button.md +++ b/docs/api/datetime-button.md @@ -23,7 +23,7 @@ Datetime Button links with a [Datetime](./datetime) component to display the for Datetime Button should be used when space is constrained. This component displays buttons which show the current date and time values. When the buttons are tapped, the date or time pickers open in the overlay. -When using Datetime Button with a JavaScript framework such as Angular, React, or Vue be sure to use the [keepContentsMounted property on ion-modal](./modal#keepcontentsmounted) or the [keepContentsMounted property on ion-popover](./popover#keepcontentsmounted). This allows the linked datetime instance to be mounted even if the overlay has not been presented yet. +When using Datetime Button with a JavaScript framework such as Angular, React, or Vue be sure to use the [keepContentsMounted property on ion-modal](./modal#prop-keep-contents-mounted) or the [keepContentsMounted property on ion-popover](./popover#prop-keep-contents-mounted). This allows the linked datetime instance to be mounted even if the overlay has not been presented yet. ## Basic Usage diff --git a/docs/api/datetime.md b/docs/api/datetime.md index fae0603a238..1c8de5c334d 100644 --- a/docs/api/datetime.md +++ b/docs/api/datetime.md @@ -41,7 +41,9 @@ import ShowAdjacentDays from '@site/static/usage/v9/datetime/show-adjacent-days/ import MultipleDateSelection from '@site/static/usage/v9/datetime/multiple/index.md'; import GlobalTheming from '@site/static/usage/v9/datetime/styling/global-theming/index.md'; +import CalendarHeaderStyling from '@site/static/usage/v9/datetime/styling/calendar-header/index.md'; import CalendarDaysStyling from '@site/static/usage/v9/datetime/styling/calendar-days/index.md'; +import DatetimeHeaderStyling from '@site/static/usage/v9/datetime/styling/datetime-header/index.md'; import WheelStyling from '@site/static/usage/v9/datetime/styling/wheel-styling/index.md'; @@ -300,7 +302,7 @@ By default, `ionChange` is emitted with the new datetime value whenever a new da ### Showing Confirmation Buttons -The default Done and Cancel buttons are already preconfigured to call the [`confirm`](#confirm) and [`cancel`](#cancel) methods, respectively. +The default Done and Cancel buttons are already preconfigured to call the [`confirm`](#method-confirm) and [`cancel`](#method-cancel) methods, respectively. @@ -352,6 +354,24 @@ The benefit of this approach is that every component, not just `ion-datetime`, c +### Datetime Header + +The datetime header manages the content for the `title` slot and the selected date. + +:::note +The selected date will not render if `preferWheel` is set to `true`. +::: + + + +### Calendar Header + +The calendar header manages the date navigation controls (month/year picker and prev/next buttons) and the days of the week when using a grid style layout. + +The header can be styled using CSS shadow parts. + + + ### Calendar Days The calendar days in a grid-style `ion-datetime` can be styled using CSS shadow parts. diff --git a/docs/api/loading.md b/docs/api/loading.md index eef9ea68416..acaa83b97a5 100644 --- a/docs/api/loading.md +++ b/docs/api/loading.md @@ -42,7 +42,7 @@ import Controller from '@site/static/usage/v9/loading/controller/index.md'; ### Spinners -The spinner that is used can be customized using the `spinner` property. See the [spinner property documentation](#spinner) for a full list of options. +The spinner that is used can be customized using the `spinner` property. See the [spinner property documentation](#prop-spinner) for a full list of options. import Spinners from '@site/static/usage/v9/loading/spinners/index.md'; diff --git a/docs/api/modal.md b/docs/api/modal.md index 5695cfbc580..a9153305e68 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -210,6 +210,26 @@ A few things to keep in mind when creating custom dialogs: * `ion-content` is intended to be used in full-page modals, cards, and sheets. If your custom dialog has a dynamic or unknown size, `ion-content` should not be used. * Creating custom dialogs provides a way of ejecting from the default modal experience. As a result, custom dialogs should not be used with card or sheet modals. +## Event Handling + +### Using `ionDragStart` and `ionDragEnd` + +The `ionDragStart` event is emitted as soon as the user begins a dragging gesture on the modal. This event fires at the moment the user initiates contact with the handle or modal surface, before any actual displacement occurs. It is particularly useful for preparing the interface for a transition, such as hiding certain interactive elements (like headers or buttons) to ensure a smooth dragging experience. + +The `ionDragEnd` event is emitted when the user completes the dragging gesture by releasing the modal. Like the move event, it includes the final [`ModalDragEventDetail`](#modaldrageventdetail) object. This event is commonly used to finalize state changes once the modal has come to a rest. + +import DragStartEndEvents from '@site/static/usage/v9/modal/drag-start-end-events/index.md'; + + + +### Using `ionDragMove` + +The `ionDragMove` event is emitted continuously while the user is actively dragging the modal. This event provides a [`ModalDragEventDetail`](#modaldrageventdetail) object containing real-time data, essential for creating highly responsive UI updates that react instantly to the user's touch. For example, the `progress` value can be used to dynamically darken a header's opacity as the modal is dragged upward. + +import DragMoveEvent from '@site/static/usage/v9/modal/drag-move-event/index.md'; + + + ## Interfaces ### ModalOptions @@ -251,6 +271,59 @@ interface ModalCustomEvent extends CustomEvent { } ``` +### ModalDragEventDetail + +When using the `ionDragMove` and `ionDragEnd` events, the event detail contains the following properties: + +```typescript +interface ModalDragEventDetail { + /** + * The current Y position of the modal. + * + * This can be used to determine how far the modal has been dragged. + */ + currentY: number; + /** + * The change in Y position since the gesture started. + * + * This can be used to determine the direction of the drag. + */ + deltaY: number; + /** + * The velocity of the drag in the Y direction. + * + * This can be used to determine how fast the modal is being dragged. + */ + velocityY: number; + /** + * A number between 0 and 1. + * + * In a sheet modal, progress represents the relative position between + * the lowest and highest defined breakpoints. + * + * In a card modal, it measures the relative position between the + * bottom of the screen and the top of the modal when it is fully + * open. + * + * This can be used to style content based on how far the modal has + * been dragged. + */ + progress: number; + /** + * If the modal is a sheet modal, this will be the breakpoint that + * the modal will snap to if the user lets go of the modal at the + * current moment. + * + * If it's a card modal, this property will not be included in the + * event payload. + * + * This can be used to style content based on where the modal will + * snap to upon release. + */ + snapBreakpoint?: number; +} +``` + ## Accessibility ### Keyboard Interactions diff --git a/docs/api/range.md b/docs/api/range.md index 8dfec042426..3be1f6e6ea5 100644 --- a/docs/api/range.md +++ b/docs/api/range.md @@ -124,6 +124,8 @@ import CSSProps from '@site/static/usage/v9/range/theming/css-properties/index.m Range includes [CSS Shadow Parts](#css-shadow-parts) to allow complete customization of specific element nodes within the Range component. CSS Shadow Parts offer the most customization capabilities and are the recommended approach when requiring advance styling with the Range component. +When `dualKnobs` is enabled, additional Shadow Parts are exposed to allow each knob to be styled independently. These are available in two forms: **static identity parts** (`A` and `B`) and **dynamic position parts** (`lower` and `upper`). The A and B parts always refer to the same physical knobs, even if the knobs cross. In contrast, the lower and upper parts reflect the current value position and automatically swap if the knobs cross. This allows styling by consistent identity or by relative value within the range. + import CSSParts from '@site/static/usage/v9/range/theming/css-shadow-parts/index.md'; diff --git a/docs/api/refresher.md b/docs/api/refresher.md index 15c2abe8e49..c6385a528ba 100644 --- a/docs/api/refresher.md +++ b/docs/api/refresher.md @@ -73,10 +73,10 @@ Developers should apply the following CSS to the scrollable container. This CSS .ion-content-scroll-host::before, .ion-content-scroll-host::after { position: absolute; - + width: 1px; height: 1px; - + content: ""; } @@ -102,6 +102,17 @@ import Advanced from '@site/static/usage/v9/refresher/advanced/index.md'; +## Event Handling + +### Using `ionPullStart` and `ionPullEnd` + +The `ionPullStart` event is emitted when the user begins a pull gesture. This event fires when the user starts to pull the refresher down. + +The `ionPullEnd` event is emitted when the refresher returns to an inactive state, with a reason property of `'complete'` or `'cancel'` indicating whether the refresh operation completed successfully or was canceled. + +import PullStartEndEvents from '@site/static/usage/v9/refresher/pull-start-end-events/index.md'; + + ## Interfaces @@ -113,6 +124,14 @@ interface RefresherEventDetail { } ``` +### RefresherPullEndEventDetail + +```typescript +interface RefresherPullEndEventDetail { + reason: 'complete' | 'cancel'; +} +``` + ### RefresherCustomEvent While not required, this interface can be used in place of the `CustomEvent` interface for stronger typing with Ionic events emitted from this component. @@ -124,6 +143,17 @@ interface RefresherCustomEvent extends CustomEvent { } ``` +### RefresherPullEndCustomEvent + +While not required, this interface can be used in place of the `CustomEvent` interface for stronger typing with the `ionPullEnd` event. + +```typescript +interface RefresherPullEndCustomEvent extends CustomEvent { + detail: RefresherPullEndEventDetail; + target: HTMLIonRefresherElement; +} +``` + ## Properties diff --git a/docs/deployment/play-store.mdx b/docs/deployment/play-store.mdx index ce2dd35317e..577cbfdf625 100644 --- a/docs/deployment/play-store.mdx +++ b/docs/deployment/play-store.mdx @@ -23,7 +23,7 @@ If you are using Capacitor, you can additionally refer to the Capacitor document To generate a release build for Android, build your web app and then run the following cli command: ```shell -npx cap copy && npx cap sync +npx cap sync ``` This will copy all web assets and sync any plugin changes. diff --git a/docs/layout/dynamic-font-scaling.md b/docs/layout/dynamic-font-scaling.md index 2ea1054851e..edc1af0ba92 100644 --- a/docs/layout/dynamic-font-scaling.md +++ b/docs/layout/dynamic-font-scaling.md @@ -67,7 +67,6 @@ However, the `em` unit has a compounding effect which can cause issues. In the f
Child element with 80px
-```
Parent element with 20px @@ -76,6 +75,7 @@ However, the `em` unit has a compounding effect which can cause issues. In the f
Child element with 80px
+``` Due to this compounding effect, we strongly recommend using `rem` units instead of `em` units when working with Dynamic Font Scaling. `rem` units set the font size of an element relative to the font size of the root element, which is typically ``. The default font size of the root element is typically `16px`. diff --git a/docs/react/navigation.md b/docs/react/navigation.md index 0941f148f5e..6b1d0515cd6 100644 --- a/docs/react/navigation.md +++ b/docs/react/navigation.md @@ -117,6 +117,8 @@ const DashboardPage: React.FC = () => ( The `IonPage` component wraps each view in an Ionic React app and allows page transitions and stack navigation to work properly. Each view that is navigated to using the router must include an `IonPage` component. +`IonPage` is also required for proper styling. It provides a flex container that ensures page content, such as `IonContent`, is properly sized and does not overlap other UI elements like `IonTabBar`. + ```tsx import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react'; import React from 'react'; diff --git a/docs/react/pwa.md b/docs/react/pwa.md index 16ea8df72f2..121b485c023 100644 --- a/docs/react/pwa.md +++ b/docs/react/pwa.md @@ -111,7 +111,7 @@ Create a new Firebase project or select an existing one. **"Select a default Firebase project for this directory:"** Choose the project you created on the Firebase website. -**"What do you want to use as your public directory?"** Enter "build". +**"What do you want to use as your public directory?"** Enter "dist". :::note Answering this next question will ensure that routing, hard reload, and deep linking work in the app: diff --git a/docs/react/quickstart.md b/docs/react/quickstart.md index 93d7bfdf006..6bb82daf98e 100644 --- a/docs/react/quickstart.md +++ b/docs/react/quickstart.md @@ -148,7 +148,7 @@ const Home: React.FC = () => { export default Home; ``` -This creates a page with a header and scrollable content area. The `IonPage` component provides the basic page structure and must be used on every page. The second header shows a [collapsible large title](/docs/api/title.md#collapsible-large-titles) that displays when at the top of the content, then condenses to show the smaller title in the first header when scrolling down. +This creates a page with a header and scrollable content area. The `IonPage` component provides the basic page structure and must be used on every page. The second header shows a [collapsible large title](/docs/api/title.md#collapsible-large-titles) that displays on iOS devices when at the top of the content, then condenses to show the smaller title in the first header when scrolling down. :::tip Learn More For detailed information about Ionic layout components, see the [Header](/docs/api/header.md), [Toolbar](/docs/api/toolbar.md), [Title](/docs/api/title.md), and [Content](/docs/api/content.md) documentation. diff --git a/docs/troubleshooting/runtime.md b/docs/troubleshooting/runtime.md index c0466b288f9..d25fe72687e 100644 --- a/docs/troubleshooting/runtime.md +++ b/docs/troubleshooting/runtime.md @@ -212,7 +212,7 @@ class MyApp { ## Accessing `this` in a function callback returns `undefined` {#accessing-this} -Certain components, such as [counterFormatter on ion-input](../api/input#counterformatter) and [pinFormatter on ion-range](../api/input#pinformatter), allow developers to pass callbacks. It's important that you bind the correct `this` value if you plan to access `this` from within the context of the callback. You may need to access `this` when using Angular components or when using class components in React. There are two ways to bind `this`: +Certain components, such as [counterFormatter on ion-input](../api/input#prop-counter-formatter) and [pinFormatter on ion-range](../api/input#pinformatter), allow developers to pass callbacks. It's important that you bind the correct `this` value if you plan to access `this` from within the context of the callback. You may need to access `this` when using Angular components or when using class components in React. There are two ways to bind `this`: The first way to bind `this` is to use the `bind()` method on a function instance. If you want to pass a callback called `counterFormatterFn`, then you would write `counterFormatterFn.bind(this)`. diff --git a/docs/vue/navigation.md b/docs/vue/navigation.md index b77a19ba7e1..745e3dda77c 100644 --- a/docs/vue/navigation.md +++ b/docs/vue/navigation.md @@ -494,6 +494,8 @@ Nothing should be provided inside of `IonRouterOutlet` when setting it up in you The `IonPage` component wraps each view in an Ionic Vue app and allows page transitions and stack navigation to work properly. Each view that is navigated to using the router must include an `IonPage` component. +`IonPage` is also required for proper styling. It provides a flex container that ensures page content, such as `IonContent`, is properly sized and does not overlap other UI elements like `IonTabBar`. + ```vue