Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f028c88
docs(modal): add playgrounds for sheet and card model drag events
thetaPC Feb 24, 2026
6950b0e
docs(modal): add drag events playground for card
thetaPC Feb 25, 2026
d38e734
docs(modal): add type import and cleanup
thetaPC Feb 25, 2026
867a38b
chore(modal): remove extra tag
thetaPC Feb 25, 2026
1befa48
chore(modal): update format
thetaPC Feb 25, 2026
8f9d11f
docs(modal): update playgrounds and add interface
thetaPC Feb 25, 2026
36e1b78
chore(stackblitz): add dev build
thetaPC Feb 25, 2026
2247d3f
docs(modal): update section titles
thetaPC Feb 26, 2026
bd55ca5
chore(stackblitz): update dev build
thetaPC Feb 26, 2026
8a5d2cb
docs(modal): create event handling section
thetaPC Feb 27, 2026
3fc476a
docs(modal): clarify deltaY
thetaPC Feb 27, 2026
351119a
docs(modal): change to predictedBreakpoint
thetaPC Feb 27, 2026
12620da
docs(modal): remove intro
thetaPC Mar 2, 2026
b8f8724
docs(modal): update heading levels
thetaPC Mar 2, 2026
9d5d012
docs(modal): update heading level
thetaPC Mar 2, 2026
db599b3
docs(modal): remove event list from dragMove
thetaPC Mar 2, 2026
ebcd579
docs(modal): separate playgrounds
thetaPC Mar 2, 2026
03828ff
docs(modal): add console logs
thetaPC Mar 2, 2026
656603c
feat(modal): cleanup
thetaPC Mar 3, 2026
dc144e0
docs(modal): update import name
thetaPC Mar 4, 2026
91069fd
docs(modal): move link
thetaPC Mar 4, 2026
7bb82f2
docs(modal): move link again
thetaPC Mar 4, 2026
f031a2c
docs(modal): use latest variable name
thetaPC Mar 4, 2026
db92545
docs(modal): update import path
thetaPC Mar 4, 2026
308d9b1
docs(modal): remove event from console
thetaPC Mar 4, 2026
0e2099e
docs(modal): remove event from the console end
thetaPC Mar 4, 2026
51a2ce8
docs(modal): add console for snap changes
thetaPC Mar 4, 2026
bfed59d
docs(modal): move events to top level modal usage directory
brandyscarney Mar 4, 2026
1b904f5
chore: revert dev build
brandyscarney Mar 4, 2026
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
Comment thread
brandyscarney marked this conversation as resolved.
Outdated

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.
Comment thread
ShaneK marked this conversation as resolved.
Outdated
*
* 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.11772118942.181221d4",
"@ionic/core": "8.7.17-dev.11772118942.181221d4",
"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.11772118942.181221d4",
"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.11772118942.181221d4",
"@ionic/react-router": "8.7.17-dev.11772118942.181221d4",
"@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.11772118942.181221d4",
"@ionic/vue-router": "8.7.17-dev.11772118942.181221d4",
"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