Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs/api/modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Methods from '@ionic-internal/component-api/v8/modal/methods.md';
import Parts from '@ionic-internal/component-api/v8/modal/parts.md';
import CustomProps from '@ionic-internal/component-api/v8/modal/custom-props.mdx';
import Slots from '@ionic-internal/component-api/v8/modal/slots.md';
import SheetDragEvents from '@site/static/usage/v8/modal/sheet/drag-events/index.md';
import CardDragEvents from '@site/static/usage/v8/modal/card/drag-events/index.md';

<head>
<title>ion-modal: Ionic Mobile App Custom Modal API Component</title>
Expand Down Expand Up @@ -115,6 +117,12 @@ import CardExample from '@site/static/usage/v8/modal/card/basic/index.md';

<CardExample />

### Drag Events for Card Modals

When using a card modal, you may want to perform certain actions based on the dragging of the card. Ionic emits several events related to dragging that can be used for this purpose, such as `ionDragStart`, `ionDragMove`, and `ionDragEnd`.

<CardDragEvents />

## Sheet Modal

:::info
Expand Down Expand Up @@ -165,6 +173,12 @@ import SheetScrollingContentExample from '@site/static/usage/v8/modal/sheet/expa

<SheetScrollingContentExample />

### Drag Events for Sheet Modals

When using a sheet modal, you may want to perform certain actions based on the dragging of the sheet. Ionic emits several events related to dragging that can be used for this purpose, such as `ionDragStart`, `ionDragMove`, and `ionDragEnd`.

<SheetDragEvents />

## Styling

Modals are presented at the root of your application so they overlay your entire app. This behavior applies to both inline modals and modals presented from a controller. As a result, custom modal styles can not be scoped to a particular component as they will not apply to the modal. Instead, styles must be applied globally. For most developers, placing the custom styles in `global.css` is sufficient.
Expand Down Expand Up @@ -251,6 +265,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 last event.
*
* 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.
*/
currentBreakpoint?: number;
}
```

## Accessibility

### Keyboard Interactions
Expand Down
4 changes: 2 additions & 2 deletions static/code/stackblitz/v8/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"@angular/platform-browser": "^20.0.0",
"@angular/platform-browser-dynamic": "^20.0.0",
"@angular/router": "^20.0.0",
"@ionic/angular": "8.7.14",
"@ionic/core": "8.7.14",
"@ionic/angular": "8.7.17-dev.11772055571.14fddd38",
"@ionic/core": "8.7.17-dev.11772055571.14fddd38",
"ionicons": "8.0.13",
"rxjs": "^7.8.1",
"tslib": "^2.5.0",
Expand Down
2 changes: 1 addition & 1 deletion static/code/stackblitz/v8/html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"start": "vite preview"
},
"dependencies": {
"@ionic/core": "8.7.14",
"@ionic/core": "8.7.17-dev.11772055571.14fddd38",
"ionicons": "8.0.13"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions static/code/stackblitz/v8/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ionic/react": "8.7.14",
"@ionic/react-router": "8.7.14",
"@ionic/react": "8.7.17-dev.11772055571.14fddd38",
"@ionic/react-router": "8.7.17-dev.11772055571.14fddd38",
"@types/node": "^24.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
Expand Down
4 changes: 2 additions & 2 deletions static/code/stackblitz/v8/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"preview": "vite preview"
},
"dependencies": {
"@ionic/vue": "8.7.14",
"@ionic/vue-router": "8.7.14",
"@ionic/vue": "8.7.17-dev.11772055571.14fddd38",
"@ionic/vue-router": "8.7.17-dev.11772055571.14fddd38",
"vue": "^3.2.25",
"vue-router": "5.0.1"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
```html
<div class="ion-page" #appPage>
<ion-header>
<ion-toolbar>
<ion-title>App</ion-title>
</ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
<ion-button id="open-modal" expand="block">Open Card Modal</ion-button>

<ion-modal
#modal
trigger="open-modal"
[presentingElement]="presentingElement"
(ionModalWillPresent)="onModalWillPresent()"
(ionDragStart)="onDragStart()"
(ionDragMove)="onDragMove($event)"
(ionDragEnd)="onDragEnd($event)"
(ionModalWillDismiss)="onModalWillDismiss()"
>
<ng-template>
<ion-header>
<ion-toolbar>
<ion-title>Modal</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="ion-margin-top">
<ion-label> Drag the handle to adjust the background brightness based on a custom brightness. </ion-label>
</div>
</ion-content>
</ng-template>
</ion-modal>
</ion-content>
</div>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
```ts
import { Component, ElementRef, ViewChild, OnInit } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonModal, IonLabel } from '@ionic/angular/standalone';
import type { ModalDragEventDetail } from '@ionic/angular/standalone';

@Component({
selector: 'app-example',
templateUrl: 'example.component.html',
standalone: true,
imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonModal, IonLabel],
})
export class ExampleComponent implements OnInit {
@ViewChild('modal', { static: true }) modal!: IonModal;
@ViewChild('appPage', { static: true }) appPage!: ElementRef<HTMLDivElement>;

presentingElement: HTMLElement | undefined;

private readonly DARKEST_PERCENT = 50;
private readonly BRIGHTNESS_RANGE = 100 - this.DARKEST_PERCENT;

ngOnInit() {
this.presentingElement = this.appPage.nativeElement;
}

onModalWillPresent() {
const appEl = this.appPage.nativeElement;

appEl.style.transition = 'filter 0.4s ease';
// Set to max darkness immediately
appEl.style.setProperty('filter', `brightness(${this.DARKEST_PERCENT}%)`, 'important');
}

onDragStart() {
const appEl = this.appPage.nativeElement;

// Ensure transitions are off during the active drag
appEl.style.transition = 'none';
}

onDragMove(event: CustomEvent<ModalDragEventDetail>) {
// `progress` is a value from 1 (top) to 0 (bottom)
const { progress } = event.detail;

const appEl = this.appPage.nativeElement;
/**
* Calculate the current brightness based on how far the user has
* dragged.
*
* When dragging up, the background should become darker,
* and when dragging down, it should become lighter.
*/
const brightnessValue = 100 - progress * this.BRIGHTNESS_RANGE;

// Update the brightness in real-time as the user drags
appEl.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important');
}

onDragEnd(event: CustomEvent<ModalDragEventDetail>) {
// `progress` is a value from 1 (top) to 0 (bottom)
const { progress } = event.detail;

const appEl = this.appPage.nativeElement;
/**
* Snap the background brightness based on the user's drag intent.
* Progress > 0.4 implies an intent to open (snap dark),
* while < 0.4 implies a dismissal (snap bright).
*/
const brightnessValue = progress > 0.4 ? this.DARKEST_PERCENT : 100;

// Reset to max darkness on snap-back for a nice visual effect
appEl.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important');

// Re-enable transition for a smooth snap-back
appEl.style.transition = 'filter 0.4s ease';
}

onModalWillDismiss() {
const appEl = this.appPage.nativeElement;

// Clean up styles when the modal is dismissed
appEl.style.removeProperty('filter');
appEl.style.removeProperty('transition');
}
}
```
78 changes: 78 additions & 0 deletions static/usage/v8/modal/card/drag-events/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Modal</title>
<link rel="stylesheet" href="../../../common.css" />
<script src="../../../common.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@8/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@8/css/ionic.bundle.css" />
</head>

<body>
<ion-app>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>App</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="open-modal" expand="block">Open Card Modal</ion-button>

<ion-modal trigger="open-modal">
<ion-header>
<ion-toolbar>
<ion-title>Modal</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<div class="ion-margin-top">
<ion-label>Drag the handle to adjust the background brightness based on a custom brightness.</ion-label>
</div>
</ion-content>
</ion-modal>
</ion-content>
</div>
</ion-app>

<script>
const modal = document.querySelector('ion-modal');
const presentingElement = document.querySelector('.ion-page');

modal.presentingElement = presentingElement;

const DARKEST_PERCENT = 50;
const BRIGHTNESS_RANGE = 100 - DARKEST_PERCENT;

modal.addEventListener('ionModalWillPresent', () => {
presentingElement.style.transition = 'filter 0.4s ease';
presentingElement.style.setProperty('filter', `brightness(${DARKEST_PERCENT}%)`, 'important');
});

modal.addEventListener('ionDragStart', () => {
presentingElement.style.transition = 'none';
});

modal.addEventListener('ionDragMove', (event) => {
const { progress } = event.detail;
const brightnessValue = 100 - progress * BRIGHTNESS_RANGE;
presentingElement.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important');
});

modal.addEventListener('ionDragEnd', (event) => {
const { progress } = event.detail;
const brightnessValue = progress > 0.4 ? DARKEST_PERCENT : 100;

presentingElement.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important');
presentingElement.style.transition = 'filter 0.4s ease';
});

modal.addEventListener('ionModalWillDismiss', () => {
presentingElement.style.removeProperty('filter');
presentingElement.style.removeProperty('transition');
});
</script>
</body>
</html>
28 changes: 28 additions & 0 deletions static/usage/v8/modal/card/drag-events/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Playground from '@site/src/components/global/Playground';

import javascript from './javascript.md';

import react from './react.md';

import vue from './vue.md';

import angular_example_component_html from './angular/example_component_html.md';
import angular_example_component_ts from './angular/example_component_ts.md';

<Playground
version="8"
code={{
javascript,
react,
vue,
angular: {
files: {
'src/app/example.component.html': angular_example_component_html,
'src/app/example.component.ts': angular_example_component_ts,
},
},
}}
src="usage/v8/modal/card/drag-events/demo.html"
devicePreview
mode="ios"
/>
Loading