From f028c88e14e00a32cb2fa204c424e039b890fae6 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 24 Feb 2026 13:41:52 -0800 Subject: [PATCH 01/12] docs(modal): add playgrounds for sheet and card model drag events --- docs/api/modal.md | 7 + .../angular/example_component_html.md | 28 ++++ .../angular/example_component_ts.md | 107 ++++++++++++++ .../v8/modal/sheet/drag-events/demo.html | 127 +++++++++++++++++ .../usage/v8/modal/sheet/drag-events/index.md | 28 ++++ .../v8/modal/sheet/drag-events/javascript.md | 113 +++++++++++++++ .../usage/v8/modal/sheet/drag-events/react.md | 131 ++++++++++++++++++ .../usage/v8/modal/sheet/drag-events/vue.md | 127 +++++++++++++++++ 8 files changed, 668 insertions(+) create mode 100644 static/usage/v8/modal/sheet/drag-events/angular/example_component_html.md create mode 100644 static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md create mode 100644 static/usage/v8/modal/sheet/drag-events/demo.html create mode 100644 static/usage/v8/modal/sheet/drag-events/index.md create mode 100644 static/usage/v8/modal/sheet/drag-events/javascript.md create mode 100644 static/usage/v8/modal/sheet/drag-events/react.md create mode 100644 static/usage/v8/modal/sheet/drag-events/vue.md diff --git a/docs/api/modal.md b/docs/api/modal.md index 1249e2a813b..0b71546002b 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -7,6 +7,7 @@ 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'; ion-modal: Ionic Mobile App Custom Modal API Component @@ -165,6 +166,12 @@ import SheetScrollingContentExample from '@site/static/usage/v8/modal/sheet/expa +### Drag Events + +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`. + + + ## 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. diff --git a/static/usage/v8/modal/sheet/drag-events/angular/example_component_html.md b/static/usage/v8/modal/sheet/drag-events/angular/example_component_html.md new file mode 100644 index 00000000000..564b8e95f3b --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/angular/example_component_html.md @@ -0,0 +1,28 @@ +```html + + + App + + + + Open Sheet Modal + + + + +
+ Drag the handle to adjust the modal's opacity based on a custom max opacity. +
+
+
+
+
+``` diff --git a/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md new file mode 100644 index 00000000000..69263a8b813 --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md @@ -0,0 +1,107 @@ +```ts +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { IonButton, IonContent, IonHeader, IonLabel, IonModal, IonTitle, IonToolbar } from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-example', + templateUrl: 'example.component.html', + standalone: true, + imports: [IonButton, IonContent, IonHeader, IonLabel, IonModal, IonTitle, IonToolbar], +}) +export class ExampleComponent { + @ViewChild('modal', { read: ElementRef }) + modal!: ElementRef; + + private baseOpacity!: number; + private readonly MAX_OPACITY = 0.8; + + onDragStart() { + const modalEl = this.modal.nativeElement; + const style = getComputedStyle(modalEl); + + // Fetch the current variable value + this.baseOpacity = parseFloat(style.getPropertyValue('--backdrop-opacity')); + + // Ensure transitions are off during the active drag + modalEl.style.transition = 'none'; + } + + onDragMove(event: any) { + // `progress` is a value from 1 (top) to 0 (bottom) + const { progress } = event.detail; + const modalEl = this.modal.nativeElement; + const initialBreakpoint = modalEl.initialBreakpoint!; + let dynamicOpacity: number; + + /** + * Dragging Down: Progress is between 0 and the initial breakpoint + */ + if (progress <= initialBreakpoint) { + /** + * Calculate how far down the user has dragged from the initial + * breakpoint as a ratio + */ + const downwardProgressRatio = progress / initialBreakpoint; + /** + * Multiplying this by `baseOpacity` ensures that as progress hits 0, + * the opacity also hits 0 without a sudden jump + */ + dynamicOpacity = downwardProgressRatio * this.baseOpacity; + } else { + /** + * Dragging Up: Progress is between the initial breakpoint and 1.0 + */ + + /** + * Calculate how far up the user has dragged from the initial + * breakpoint as a ratio + */ + const distanceFromStart = progress - initialBreakpoint; + /** + * Calculate the total available space to drag up from the initial + * breakpoint to the top (1.0) + */ + const range = 1 - initialBreakpoint; + /** + * Normalizes that distance into a 0.0 to 1.0 value relative to + * the available upward space + */ + const currentGain = distanceFromStart / range; + + // Scale from `baseOpacity` up to `MAX_OPACITY` + dynamicOpacity = this.baseOpacity + currentGain * (this.MAX_OPACITY - this.baseOpacity); + } + + modalEl.style.setProperty('--backdrop-opacity', dynamicOpacity.toString()); + } + + onDragEnd(event: any) { + // `currentBreakpoint` tells us which snap point the modal will animate to after the drag ends + const { currentBreakpoint } = event.detail; + const modalEl = this.modal.nativeElement; + + /** + * If the modal is snapping to the closed state (0), reset the + * styles. + */ + if (currentBreakpoint === 0) { + modalEl.style.removeProperty('--backdrop-opacity'); + return; + } + + // Determine the target opacity for the snap-back animation + let targetOpacity = this.baseOpacity; + /** + * If snapping to the top (1), set opacity to MAX_OPACITY for a nice + * visual effect. + */ + if (currentBreakpoint === 1) { + targetOpacity = this.MAX_OPACITY; + } + + // Re-enable transition for a smooth snap-back + modalEl.style.transition = '--backdrop-opacity 0.3s ease'; + modalEl.style.setProperty('--backdrop-opacity', targetOpacity.toString()); + } +} +``` diff --git a/static/usage/v8/modal/sheet/drag-events/demo.html b/static/usage/v8/modal/sheet/drag-events/demo.html new file mode 100644 index 00000000000..2835d936bc4 --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/demo.html @@ -0,0 +1,127 @@ + + + + + + Modal + + + + + + + + + + + App + + + + Open Sheet Modal + + + +
+ Drag the handle to adjust the modal's opacity based on a custom max opacity. +
+
+
+
+
+ + + + diff --git a/static/usage/v8/modal/sheet/drag-events/index.md b/static/usage/v8/modal/sheet/drag-events/index.md new file mode 100644 index 00000000000..d947355b9d7 --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/index.md @@ -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'; + + diff --git a/static/usage/v8/modal/sheet/drag-events/javascript.md b/static/usage/v8/modal/sheet/drag-events/javascript.md new file mode 100644 index 00000000000..00270f7654e --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/javascript.md @@ -0,0 +1,113 @@ +```html + + + App + + + + Open Sheet Modal + + + +
+ Drag the handle to adjust the modal's opacity based on a custom max opacity. +
+
+
+
+ + +``` diff --git a/static/usage/v8/modal/sheet/drag-events/react.md b/static/usage/v8/modal/sheet/drag-events/react.md new file mode 100644 index 00000000000..5ff917c9017 --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/react.md @@ -0,0 +1,131 @@ +```tsx +import React, { useRef } from 'react'; +import { IonButton, IonModal, IonHeader, IonContent, IonToolbar, IonTitle, IonPage, IonLabel } from '@ionic/react'; + +function Example() { + const modal = useRef(null); + const baseOpacity = useRef(undefined); + const MAX_OPACITY = 0.8; + + const onDragStart = () => { + const modalEl = modal.current!; + const style = getComputedStyle(modalEl); + + // Fetch the current variable value + baseOpacity.current = parseFloat(style.getPropertyValue('--backdrop-opacity')); + + // Ensure transitions are off during the active drag + modalEl.style.transition = 'none'; + }; + + const onDragMove = (event: CustomEvent) => { + // `progress` is a value from 1 (top) to 0 (bottom) + const { progress } = event.detail; + const modalEl = modal.current!; + const initialBreakpoint = modalEl.initialBreakpoint!; + let dynamicOpacity: number; + + /** + * Dragging Down: Progress is between 0 and the initial breakpoint + */ + if (progress <= initialBreakpoint) { + /** + * Calculate how far down the user has dragged from the initial + * breakpoint as a ratio + */ + const downwardProgressRatio = progress / initialBreakpoint; + /** + * Multiplying this by `baseOpacity` ensures that as progress hits 0, + * the opacity also hits 0 without a sudden jump + */ + dynamicOpacity = downwardProgressRatio * baseOpacity.current!; + } else { + /** + * Dragging Up: Progress is between the initial breakpoint and 1.0 + */ + + /** + * Calculate how far up the user has dragged from the initial + * breakpoint as a ratio + */ + const distanceFromStart = progress - initialBreakpoint; + /** + * Calculate the total available space to drag up from the initial + * breakpoint to the top (1.0) + */ + const range = 1 - initialBreakpoint; + /** + * Normalizes that distance into a 0.0 to 1.0 value relative to + * the available upward space + */ + const currentGain = distanceFromStart / range; + + // Scale from `baseOpacity` up to `MAX_OPACITY` + dynamicOpacity = baseOpacity.current! + currentGain * (MAX_OPACITY - baseOpacity.current!); + } + + modalEl.style.setProperty('--backdrop-opacity', dynamicOpacity.toString()); + }; + + const onDragEnd = (event: CustomEvent) => { + // `currentBreakpoint` tells us which snap point the modal will animate to after the drag ends + const { currentBreakpoint } = event.detail; + const modalEl = modal.current!; + + /** + * If the modal is snapping to the closed state (0), reset the + * styles. + */ + if (currentBreakpoint === 0) { + modalEl.style.removeProperty('--backdrop-opacity'); + return; + } + + // Determine the target opacity for the snap-back animation + let targetOpacity = baseOpacity.current; + /** + * If snapping to the top (1), set opacity to MAX_OPACITY for a nice + * visual effect. + */ + if (currentBreakpoint === 1) { + targetOpacity = MAX_OPACITY; + } + + // Re-enable transition for a smooth snap-back + modalEl.style.transition = '--backdrop-opacity 0.3s ease'; + modalEl.style.setProperty('--backdrop-opacity', targetOpacity!.toString()); + }; + + return ( + + + + App + + + + + Open Sheet Modal + + + +
+ Drag the handle to adjust the modal's opacity based on a custom max opacity. +
+
+
+
+
+ ); +} + +export default Example; +``` diff --git a/static/usage/v8/modal/sheet/drag-events/vue.md b/static/usage/v8/modal/sheet/drag-events/vue.md new file mode 100644 index 00000000000..a60981a3cf1 --- /dev/null +++ b/static/usage/v8/modal/sheet/drag-events/vue.md @@ -0,0 +1,127 @@ +```vue + + + +``` From 6950b0e382967f2ef5ef7abd9c717c497e75a0e9 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 24 Feb 2026 17:25:23 -0800 Subject: [PATCH 02/12] docs(modal): add drag events playground for card --- docs/api/modal.md | 7 ++ .../angular/example_component_html.md | 37 ++++++ .../angular/example_component_ts.md | 84 +++++++++++++ .../usage/v8/modal/card/drag-events/demo.html | 102 ++++++++++++++++ .../usage/v8/modal/card/drag-events/index.md | 28 +++++ .../v8/modal/card/drag-events/javascript.md | 87 +++++++++++++ .../usage/v8/modal/card/drag-events/react.md | 109 +++++++++++++++++ static/usage/v8/modal/card/drag-events/vue.md | 115 ++++++++++++++++++ 8 files changed, 569 insertions(+) create mode 100644 static/usage/v8/modal/card/drag-events/angular/example_component_html.md create mode 100644 static/usage/v8/modal/card/drag-events/angular/example_component_ts.md create mode 100644 static/usage/v8/modal/card/drag-events/demo.html create mode 100644 static/usage/v8/modal/card/drag-events/index.md create mode 100644 static/usage/v8/modal/card/drag-events/javascript.md create mode 100644 static/usage/v8/modal/card/drag-events/react.md create mode 100644 static/usage/v8/modal/card/drag-events/vue.md diff --git a/docs/api/modal.md b/docs/api/modal.md index 0b71546002b..522d84b6d4c 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -8,6 +8,7 @@ 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'; ion-modal: Ionic Mobile App Custom Modal API Component @@ -116,6 +117,12 @@ import CardExample from '@site/static/usage/v8/modal/card/basic/index.md'; +### Drag Events + +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`. + + + ## Sheet Modal :::info diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_html.md b/static/usage/v8/modal/card/drag-events/angular/example_component_html.md new file mode 100644 index 00000000000..0327526a1c0 --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/angular/example_component_html.md @@ -0,0 +1,37 @@ +```html +
+ + + App + + + + + Open Card Modal + + + + + + Modal + + + +
+ Drag the handle to adjust the background brightness based on a custom brightness. +
+
+
+
+
+
+``` diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md new file mode 100644 index 00000000000..1318c2d8f52 --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md @@ -0,0 +1,84 @@ +```ts +import { Component, ElementRef, ViewChild, OnInit } from '@angular/core'; +import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonModal, IonLabel } 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; + + 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: any) { + // `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: any) { + // `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'); + } +} +``` diff --git a/static/usage/v8/modal/card/drag-events/demo.html b/static/usage/v8/modal/card/drag-events/demo.html new file mode 100644 index 00000000000..6e8b20ba92f --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/demo.html @@ -0,0 +1,102 @@ + + + + + + Modal + + + + + + + + + +
+ + + App + + + + Open Sheet Modal + + + + + Modal + + + +
+ Drag the handle to adjust the background brightness based on a custom brightness. +
+
+
+
+
+
+ + + + diff --git a/static/usage/v8/modal/card/drag-events/index.md b/static/usage/v8/modal/card/drag-events/index.md new file mode 100644 index 00000000000..045bfe9f151 --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/index.md @@ -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'; + + diff --git a/static/usage/v8/modal/card/drag-events/javascript.md b/static/usage/v8/modal/card/drag-events/javascript.md new file mode 100644 index 00000000000..977d776b1c8 --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/javascript.md @@ -0,0 +1,87 @@ +```html +
+ + + App + + + + Open Sheet Modal + + + + + Modal + + + +
+ Drag the handle to adjust the background brightness based on a custom brightness. +
+
+
+
+
+ + +``` diff --git a/static/usage/v8/modal/card/drag-events/react.md b/static/usage/v8/modal/card/drag-events/react.md new file mode 100644 index 00000000000..fd9f0345c48 --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/react.md @@ -0,0 +1,109 @@ +```tsx +import React, { useRef, useState, useEffect } from 'react'; +import { IonButton, IonModal, IonHeader, IonContent, IonToolbar, IonTitle, IonPage, IonLabel } from '@ionic/react'; + +function Example() { + const modal = useRef(null); + const page = useRef(null); + + const [presentingElement, setPresentingElement] = useState(null); + + const DARKEST_PERCENT = 50; + const BRIGHTNESS_RANGE = 100 - DARKEST_PERCENT; + + useEffect(() => { + setPresentingElement(page.current); + }, []); + + const onModalWillPresent = () => { + page.current!.style.transition = 'filter 0.4s ease'; + // Set to max darkness immediately + page.current!.style.setProperty('filter', `brightness(${DARKEST_PERCENT}%)`, 'important'); + }; + + const onDragStart = () => { + // Ensure transitions are off during the active drag + page.current!.style.transition = 'none'; + }; + + const onDragMove = (event: CustomEvent) => { + // `progress` is a value from 1 (top) to 0 (bottom) + const { progress } = event.detail; + + /** + * 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 * BRIGHTNESS_RANGE; + + // Update the brightness in real-time as the user drags + page.current!.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); + }; + + const onDragEnd = (event: CustomEvent) => { + // `progress` is a value from 1 (top) to 0 (bottom) + const { progress } = event.detail; + + /** + * 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 ? DARKEST_PERCENT : 100; + + // Reset to max darkness on snap-back for a nice visual effect + page.current!.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); + + // Re-enable transition for a smooth snap-back + page.current!.style.transition = 'filter 0.4s ease'; + }; + + const onModalWillDismiss = () => { + // Clean up styles when the modal is dismissed + page.current!.style.removeProperty('filter'); + page.current!.style.removeProperty('transition'); + }; + + return ( + + + + App + + + + + Open Card Modal + + + + + + Modal + + + +
+ Drag the handle to adjust the background brightness based on a custom brightness. +
+
+
+
+
+ ); +} + +export default Example; +``` diff --git a/static/usage/v8/modal/card/drag-events/vue.md b/static/usage/v8/modal/card/drag-events/vue.md new file mode 100644 index 00000000000..4ce0db76f9f --- /dev/null +++ b/static/usage/v8/modal/card/drag-events/vue.md @@ -0,0 +1,115 @@ +```vue + + + +``` From d38e734da18a85d67a5e404cf2db726f522ede21 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 25 Feb 2026 06:53:50 -0800 Subject: [PATCH 03/12] docs(modal): add type import and cleanup --- static/code/stackblitz/v8/angular/package.json | 4 ++-- static/code/stackblitz/v8/html/package.json | 2 +- static/code/stackblitz/v8/react/package.json | 4 ++-- static/code/stackblitz/v8/vue/package.json | 4 ++-- .../modal/card/drag-events/angular/example_component_ts.md | 5 +++-- static/usage/v8/modal/card/drag-events/demo.html | 2 +- static/usage/v8/modal/card/drag-events/javascript.md | 2 +- static/usage/v8/modal/card/drag-events/react.md | 5 +++-- static/usage/v8/modal/card/drag-events/vue.md | 7 ++++--- .../sheet/drag-events/angular/example_component_html.md | 2 +- .../sheet/drag-events/angular/example_component_ts.md | 5 +++-- static/usage/v8/modal/sheet/drag-events/react.md | 7 ++++--- static/usage/v8/modal/sheet/drag-events/vue.md | 5 +++-- 13 files changed, 30 insertions(+), 24 deletions(-) diff --git a/static/code/stackblitz/v8/angular/package.json b/static/code/stackblitz/v8/angular/package.json index 1b5bd920983..e10f9b2e1f9 100644 --- a/static/code/stackblitz/v8/angular/package.json +++ b/static/code/stackblitz/v8/angular/package.json @@ -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.11772030258.1ee4f499", + "@ionic/core": "8.7.17-dev.11772030258.1ee4f499", "ionicons": "8.0.13", "rxjs": "^7.8.1", "tslib": "^2.5.0", diff --git a/static/code/stackblitz/v8/html/package.json b/static/code/stackblitz/v8/html/package.json index e0c5262152f..4ced4d66b34 100644 --- a/static/code/stackblitz/v8/html/package.json +++ b/static/code/stackblitz/v8/html/package.json @@ -9,7 +9,7 @@ "start": "vite preview" }, "dependencies": { - "@ionic/core": "8.7.14", + "@ionic/core": "8.7.17-dev.11772030258.1ee4f499", "ionicons": "8.0.13" }, "devDependencies": { diff --git a/static/code/stackblitz/v8/react/package.json b/static/code/stackblitz/v8/react/package.json index bbbf6308c01..5bc578845d8 100644 --- a/static/code/stackblitz/v8/react/package.json +++ b/static/code/stackblitz/v8/react/package.json @@ -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.11772030258.1ee4f499", + "@ionic/react-router": "8.7.17-dev.11772030258.1ee4f499", "@types/node": "^24.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", diff --git a/static/code/stackblitz/v8/vue/package.json b/static/code/stackblitz/v8/vue/package.json index dfe2750877e..d646b2b6302 100644 --- a/static/code/stackblitz/v8/vue/package.json +++ b/static/code/stackblitz/v8/vue/package.json @@ -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.11772030258.1ee4f499", + "@ionic/vue-router": "8.7.17-dev.11772030258.1ee4f499", "vue": "^3.2.25", "vue-router": "5.0.1" }, diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md index 1318c2d8f52..9afe599f857 100644 --- a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md +++ b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md @@ -1,6 +1,7 @@ ```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/core'; @Component({ selector: 'app-example', @@ -36,7 +37,7 @@ export class ExampleComponent implements OnInit { appEl.style.transition = 'none'; } - onDragMove(event: any) { + onDragMove(event: CustomEvent) { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; @@ -54,7 +55,7 @@ export class ExampleComponent implements OnInit { appEl.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); } - onDragEnd(event: any) { + onDragEnd(event: CustomEvent) { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; diff --git a/static/usage/v8/modal/card/drag-events/demo.html b/static/usage/v8/modal/card/drag-events/demo.html index 6e8b20ba92f..f2acec3843b 100644 --- a/static/usage/v8/modal/card/drag-events/demo.html +++ b/static/usage/v8/modal/card/drag-events/demo.html @@ -20,7 +20,7 @@ - Open Sheet Modal + Open Card Modal diff --git a/static/usage/v8/modal/card/drag-events/javascript.md b/static/usage/v8/modal/card/drag-events/javascript.md index 977d776b1c8..cbb63f7684d 100644 --- a/static/usage/v8/modal/card/drag-events/javascript.md +++ b/static/usage/v8/modal/card/drag-events/javascript.md @@ -6,7 +6,7 @@ - Open Sheet Modal + Open Card Modal diff --git a/static/usage/v8/modal/card/drag-events/react.md b/static/usage/v8/modal/card/drag-events/react.md index fd9f0345c48..77ce43c134e 100644 --- a/static/usage/v8/modal/card/drag-events/react.md +++ b/static/usage/v8/modal/card/drag-events/react.md @@ -1,6 +1,7 @@ ```tsx import React, { useRef, useState, useEffect } from 'react'; import { IonButton, IonModal, IonHeader, IonContent, IonToolbar, IonTitle, IonPage, IonLabel } from '@ionic/react'; +import type { ModalDragEventDetail } from '@ionic/react'; function Example() { const modal = useRef(null); @@ -26,7 +27,7 @@ function Example() { page.current!.style.transition = 'none'; }; - const onDragMove = (event: CustomEvent) => { + const onDragMove = (event: CustomEvent) => { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; @@ -43,7 +44,7 @@ function Example() { page.current!.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); }; - const onDragEnd = (event: CustomEvent) => { + const onDragEnd = (event: CustomEvent) => { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; diff --git a/static/usage/v8/modal/card/drag-events/vue.md b/static/usage/v8/modal/card/drag-events/vue.md index 4ce0db76f9f..8a5ea7a0f50 100644 --- a/static/usage/v8/modal/card/drag-events/vue.md +++ b/static/usage/v8/modal/card/drag-events/vue.md @@ -17,7 +17,7 @@ @ionDragStart="onDragStart()" @ionDragMove="onDragMove($event)" @ionDragEnd="onDragEnd($event)" - @willDismiss="onWillDismiss" + @willDismiss="onWillDismiss()" > @@ -37,6 +37,7 @@ - From 1befa48324cf809933c5389157268981f46ce23e Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 25 Feb 2026 08:16:55 -0800 Subject: [PATCH 05/12] chore(modal): update format --- .../usage/v8/modal/card/drag-events/demo.html | 121 +++++++----------- 1 file changed, 49 insertions(+), 72 deletions(-) diff --git a/static/usage/v8/modal/card/drag-events/demo.html b/static/usage/v8/modal/card/drag-events/demo.html index 383a8d10672..5b41b41f23d 100644 --- a/static/usage/v8/modal/card/drag-events/demo.html +++ b/static/usage/v8/modal/card/drag-events/demo.html @@ -13,89 +13,66 @@
- - - App - - - - Open Card Modal + + + App + + + + Open Card Modal - - - - Modal - - - -
- Drag the handle to adjust the background brightness based on a custom brightness. -
-
-
-
-
+ + + + Modal + + + +
+ Drag the handle to adjust the background brightness based on a custom brightness. +
+
+
+
+ + modal.addEventListener('ionModalWillDismiss', () => { + presentingElement.style.removeProperty('filter'); + presentingElement.style.removeProperty('transition'); + }); + From 8f9d11f0eed46971b99e5f1d66104a4ae80f1902 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 25 Feb 2026 14:09:06 -0800 Subject: [PATCH 06/12] docs(modal): update playgrounds and add interface --- docs/api/modal.md | 53 +++++++++++++++++++ .../angular/example_component_ts.md | 2 +- static/usage/v8/modal/card/drag-events/vue.md | 18 +++---- .../angular/example_component_ts.md | 2 +- .../usage/v8/modal/sheet/drag-events/vue.md | 10 ++-- 5 files changed, 68 insertions(+), 17 deletions(-) diff --git a/docs/api/modal.md b/docs/api/modal.md index 522d84b6d4c..46f3dab77d3 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -265,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 diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md index 9afe599f857..4c999605489 100644 --- a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md +++ b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md @@ -1,7 +1,7 @@ ```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/core'; +import type { ModalDragEventDetail } from '@ionic/angular/standalone'; @Component({ selector: 'app-example', diff --git a/static/usage/v8/modal/card/drag-events/vue.md b/static/usage/v8/modal/card/drag-events/vue.md index 8a5ea7a0f50..a23a27cc8cf 100644 --- a/static/usage/v8/modal/card/drag-events/vue.md +++ b/static/usage/v8/modal/card/drag-events/vue.md @@ -10,7 +10,6 @@ Open Card Modal (null); -const presentingElement = ref(null); +const page = ref>(); +const presentingElement = ref(); const DARKEST_PERCENT = 50; const BRIGHTNESS_RANGE = 100 - DARKEST_PERCENT; onMounted(() => { - presentingElement.value = page.value.$el; + presentingElement.value = page.value!.$el; }); /** * Sync the background dimming with the modal's entry animation. */ const onWillPresent = () => { - const appEl = page.value.$el; + const appEl = page.value!.$el; appEl.style.transition = 'filter 0.4s ease'; // Set to max darkness immediately @@ -62,7 +60,7 @@ const onWillPresent = () => { }; const onDragStart = () => { - const appEl = page.value.$el; + const appEl = page.value!.$el; // Ensure transitions are off during the active drag appEl.style.transition = 'none'; @@ -72,7 +70,7 @@ const onDragMove = (event: CustomEvent) => { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; - const appEl = page.value.$el; + const appEl = page.value!.$el; /** * Calculate the current brightness based on how far the user has * dragged. @@ -90,7 +88,7 @@ const onDragEnd = (event: CustomEvent) => { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; - const appEl = page.value.$el; + const appEl = page.value!.$el; /** * Snap the background brightness based on the user's drag intent. * Progress > 0.4 implies an intent to open (snap dark), @@ -106,7 +104,7 @@ const onDragEnd = (event: CustomEvent) => { }; const onWillDismiss = () => { - const appEl = page.value.$el; + const appEl = page.value!.$el; // Clean up styles when the modal is dismissed appEl.style.removeProperty('filter'); diff --git a/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md index b25594ad355..98fa60ff5cd 100644 --- a/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md +++ b/static/usage/v8/modal/sheet/drag-events/angular/example_component_ts.md @@ -1,7 +1,7 @@ ```ts import { Component, ElementRef, ViewChild } from '@angular/core'; import { IonButton, IonContent, IonHeader, IonLabel, IonModal, IonTitle, IonToolbar } from '@ionic/angular/standalone'; -import type { ModalDragEventDetail } from '@ionic/core'; +import type { ModalDragEventDetail } from '@ionic/angular/standalone'; @Component({ selector: 'app-example', diff --git a/static/usage/v8/modal/sheet/drag-events/vue.md b/static/usage/v8/modal/sheet/drag-events/vue.md index 98c96baf171..ee686e76383 100644 --- a/static/usage/v8/modal/sheet/drag-events/vue.md +++ b/static/usage/v8/modal/sheet/drag-events/vue.md @@ -31,13 +31,13 @@ import { ref } from 'vue'; import { IonButton, IonModal, IonHeader, IonContent, IonToolbar, IonTitle, IonLabel } from '@ionic/vue'; import type { ModalDragEventDetail } from '@ionic/vue'; -const modal = ref(); +const modal = ref>(); let baseOpacity: number; const MAX_OPACITY = 0.8; const onDragStart = () => { - const modalEl = modal.value.$el; + const modalEl = modal.value!.$el; const style = window.getComputedStyle(modalEl); // Fetch the current variable value @@ -50,7 +50,7 @@ const onDragStart = () => { const onDragMove = (event: CustomEvent) => { // `progress` is a value from 1 (top) to 0 (bottom) const { progress } = event.detail; - const modalEl = modal.value.$el; + const modalEl = modal.value!.$el; const initialBreakpoint = modalEl.initialBreakpoint || 0.25; let dynamicOpacity: number; @@ -99,7 +99,7 @@ const onDragMove = (event: CustomEvent) => { const onDragEnd = (event: CustomEvent) => { // `currentBreakpoint` tells us which snap point the modal will animate to after the drag ends const { currentBreakpoint } = event.detail; - const modalEl = modal.value.$el; + const modalEl = modal.value!.$el; /** * If the modal is snapping to the closed state (0), reset the @@ -117,7 +117,7 @@ const onDragEnd = (event: CustomEvent) => { * visual effect. */ if (currentBreakpoint === 1) { - targetOpacity = this.MAX_OPACITY; + targetOpacity = MAX_OPACITY; } // Re-enable transition for a smooth snap-back From 36e1b781eb11f3ddd8c34e83b23a0d02506d9a85 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 25 Feb 2026 14:11:02 -0800 Subject: [PATCH 07/12] chore(stackblitz): add dev build --- static/code/stackblitz/v8/angular/package.json | 4 ++-- static/code/stackblitz/v8/html/package.json | 2 +- static/code/stackblitz/v8/react/package.json | 4 ++-- static/code/stackblitz/v8/vue/package.json | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/static/code/stackblitz/v8/angular/package.json b/static/code/stackblitz/v8/angular/package.json index e10f9b2e1f9..17c37b07e2b 100644 --- a/static/code/stackblitz/v8/angular/package.json +++ b/static/code/stackblitz/v8/angular/package.json @@ -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.17-dev.11772030258.1ee4f499", - "@ionic/core": "8.7.17-dev.11772030258.1ee4f499", + "@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", diff --git a/static/code/stackblitz/v8/html/package.json b/static/code/stackblitz/v8/html/package.json index 4ced4d66b34..44393ee2a7e 100644 --- a/static/code/stackblitz/v8/html/package.json +++ b/static/code/stackblitz/v8/html/package.json @@ -9,7 +9,7 @@ "start": "vite preview" }, "dependencies": { - "@ionic/core": "8.7.17-dev.11772030258.1ee4f499", + "@ionic/core": "8.7.17-dev.11772055571.14fddd38", "ionicons": "8.0.13" }, "devDependencies": { diff --git a/static/code/stackblitz/v8/react/package.json b/static/code/stackblitz/v8/react/package.json index 5bc578845d8..2bfaf414804 100644 --- a/static/code/stackblitz/v8/react/package.json +++ b/static/code/stackblitz/v8/react/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { - "@ionic/react": "8.7.17-dev.11772030258.1ee4f499", - "@ionic/react-router": "8.7.17-dev.11772030258.1ee4f499", + "@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", diff --git a/static/code/stackblitz/v8/vue/package.json b/static/code/stackblitz/v8/vue/package.json index d646b2b6302..480c6960243 100644 --- a/static/code/stackblitz/v8/vue/package.json +++ b/static/code/stackblitz/v8/vue/package.json @@ -8,8 +8,8 @@ "preview": "vite preview" }, "dependencies": { - "@ionic/vue": "8.7.17-dev.11772030258.1ee4f499", - "@ionic/vue-router": "8.7.17-dev.11772030258.1ee4f499", + "@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" }, From 2247d3f38a547b03c716ce1b538df14f214a5d94 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 25 Feb 2026 16:01:43 -0800 Subject: [PATCH 08/12] docs(modal): update section titles --- docs/api/modal.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/modal.md b/docs/api/modal.md index 46f3dab77d3..f8c4ce532e4 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -117,7 +117,7 @@ import CardExample from '@site/static/usage/v8/modal/card/basic/index.md'; -### Drag Events +### 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`. @@ -173,7 +173,7 @@ import SheetScrollingContentExample from '@site/static/usage/v8/modal/sheet/expa -### Drag Events +### 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`. From bd55ca5eae72274cfa21fec664bc0cba0755edd6 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 26 Feb 2026 08:10:09 -0800 Subject: [PATCH 09/12] chore(stackblitz): update dev build --- static/code/stackblitz/v8/angular/package.json | 4 ++-- static/code/stackblitz/v8/html/package.json | 2 +- static/code/stackblitz/v8/react/package.json | 4 ++-- static/code/stackblitz/v8/vue/package.json | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/static/code/stackblitz/v8/angular/package.json b/static/code/stackblitz/v8/angular/package.json index 17c37b07e2b..82814bfdfeb 100644 --- a/static/code/stackblitz/v8/angular/package.json +++ b/static/code/stackblitz/v8/angular/package.json @@ -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.17-dev.11772055571.14fddd38", - "@ionic/core": "8.7.17-dev.11772055571.14fddd38", + "@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", diff --git a/static/code/stackblitz/v8/html/package.json b/static/code/stackblitz/v8/html/package.json index 44393ee2a7e..1122bd961da 100644 --- a/static/code/stackblitz/v8/html/package.json +++ b/static/code/stackblitz/v8/html/package.json @@ -9,7 +9,7 @@ "start": "vite preview" }, "dependencies": { - "@ionic/core": "8.7.17-dev.11772055571.14fddd38", + "@ionic/core": "8.7.17-dev.11772118942.181221d4", "ionicons": "8.0.13" }, "devDependencies": { diff --git a/static/code/stackblitz/v8/react/package.json b/static/code/stackblitz/v8/react/package.json index 2bfaf414804..41436604bc0 100644 --- a/static/code/stackblitz/v8/react/package.json +++ b/static/code/stackblitz/v8/react/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { - "@ionic/react": "8.7.17-dev.11772055571.14fddd38", - "@ionic/react-router": "8.7.17-dev.11772055571.14fddd38", + "@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", diff --git a/static/code/stackblitz/v8/vue/package.json b/static/code/stackblitz/v8/vue/package.json index 480c6960243..421a8a70e32 100644 --- a/static/code/stackblitz/v8/vue/package.json +++ b/static/code/stackblitz/v8/vue/package.json @@ -8,8 +8,8 @@ "preview": "vite preview" }, "dependencies": { - "@ionic/vue": "8.7.17-dev.11772055571.14fddd38", - "@ionic/vue-router": "8.7.17-dev.11772055571.14fddd38", + "@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" }, From 8a5d2cbc5d884ed996ae97373abd8259e8aebc92 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 26 Feb 2026 16:56:10 -0800 Subject: [PATCH 10/12] docs(modal): create event handling section --- docs/api/modal.md | 40 ++++-- .../angular/example_component_html.md | 37 ------ .../angular/example_component_ts.md | 85 ------------- .../usage/v8/modal/card/drag-events/demo.html | 78 ------------ .../usage/v8/modal/card/drag-events/index.md | 28 ----- .../v8/modal/card/drag-events/javascript.md | 87 ------------- .../usage/v8/modal/card/drag-events/react.md | 110 ----------------- static/usage/v8/modal/card/drag-events/vue.md | 114 ------------------ 8 files changed, 27 insertions(+), 552 deletions(-) delete mode 100644 static/usage/v8/modal/card/drag-events/angular/example_component_html.md delete mode 100644 static/usage/v8/modal/card/drag-events/angular/example_component_ts.md delete mode 100644 static/usage/v8/modal/card/drag-events/demo.html delete mode 100644 static/usage/v8/modal/card/drag-events/index.md delete mode 100644 static/usage/v8/modal/card/drag-events/javascript.md delete mode 100644 static/usage/v8/modal/card/drag-events/react.md delete mode 100644 static/usage/v8/modal/card/drag-events/vue.md diff --git a/docs/api/modal.md b/docs/api/modal.md index f8c4ce532e4..fa78fcd5299 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -8,7 +8,6 @@ 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'; ion-modal: Ionic Mobile App Custom Modal API Component @@ -117,12 +116,6 @@ import CardExample from '@site/static/usage/v8/modal/card/basic/index.md'; -### 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`. - - - ## Sheet Modal :::info @@ -173,12 +166,6 @@ import SheetScrollingContentExample from '@site/static/usage/v8/modal/sheet/expa -### 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`. - - - ## 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. @@ -224,6 +211,33 @@ 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 Drag Events + +When using card or sheet modals, Ionic emits several events related to the dragging gesture. These events allow developers to perform specific actions or UI updates based on the movement of the modal. + +#### Using `ionDragStart` + +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 blurring background content or disabling certain interactive elements to ensure a smooth dragging experience. + +#### Using `ionDragMove` + +The `ionDragMove` event is emitted continuously while the user is actively dragging the modal. This event provides a `ModalDragEventDetail` [object](#modaldrageventdetail) containing real-time data: + +- `currentY` and `deltaY`: Track the absolute position and the change in distance since the last frame, useful for calculating drag direction. +- `velocityY`: Measures the speed of the drag, which can be used to trigger specific animations if a user "flicks" the modal. +- `progress`: A normalized value between 0 and 1 representing how far the modal is open. This is ideal for dynamically adjusting the opacity of an overlay or scaling background content as the modal moves. +- `currentBreakpoint`: For sheet modals, this identifies which breakpoint the modal will snap to if released at that moment. + +This event is 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 the backdrop's opacity as the modal is dragged upward. + +#### Using `ionDragEnd` + +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` [object](#modaldrageventdetail). This event is commonly used to finalize state changes once the modal has come to a rest. For example, you might use the `currentBreakpoint` property to determine which content to load or to update the application's routing state once the user has finished swiping the sheet to a specific height. + + + ## Interfaces ### ModalOptions diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_html.md b/static/usage/v8/modal/card/drag-events/angular/example_component_html.md deleted file mode 100644 index 0327526a1c0..00000000000 --- a/static/usage/v8/modal/card/drag-events/angular/example_component_html.md +++ /dev/null @@ -1,37 +0,0 @@ -```html -
- - - App - - - - - Open Card Modal - - - - - - Modal - - - -
- Drag the handle to adjust the background brightness based on a custom brightness. -
-
-
-
-
-
-``` diff --git a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md b/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md deleted file mode 100644 index 4c999605489..00000000000 --- a/static/usage/v8/modal/card/drag-events/angular/example_component_ts.md +++ /dev/null @@ -1,85 +0,0 @@ -```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; - - 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) { - // `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) { - // `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'); - } -} -``` diff --git a/static/usage/v8/modal/card/drag-events/demo.html b/static/usage/v8/modal/card/drag-events/demo.html deleted file mode 100644 index 5b41b41f23d..00000000000 --- a/static/usage/v8/modal/card/drag-events/demo.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - Modal - - - - - - - - -
- - - App - - - - Open Card Modal - - - - - Modal - - - -
- Drag the handle to adjust the background brightness based on a custom brightness. -
-
-
-
-
-
- - - - diff --git a/static/usage/v8/modal/card/drag-events/index.md b/static/usage/v8/modal/card/drag-events/index.md deleted file mode 100644 index 045bfe9f151..00000000000 --- a/static/usage/v8/modal/card/drag-events/index.md +++ /dev/null @@ -1,28 +0,0 @@ -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'; - - diff --git a/static/usage/v8/modal/card/drag-events/javascript.md b/static/usage/v8/modal/card/drag-events/javascript.md deleted file mode 100644 index cbb63f7684d..00000000000 --- a/static/usage/v8/modal/card/drag-events/javascript.md +++ /dev/null @@ -1,87 +0,0 @@ -```html -
- - - App - - - - Open Card Modal - - - - - Modal - - - -
- Drag the handle to adjust the background brightness based on a custom brightness. -
-
-
-
-
- - -``` diff --git a/static/usage/v8/modal/card/drag-events/react.md b/static/usage/v8/modal/card/drag-events/react.md deleted file mode 100644 index 77ce43c134e..00000000000 --- a/static/usage/v8/modal/card/drag-events/react.md +++ /dev/null @@ -1,110 +0,0 @@ -```tsx -import React, { useRef, useState, useEffect } from 'react'; -import { IonButton, IonModal, IonHeader, IonContent, IonToolbar, IonTitle, IonPage, IonLabel } from '@ionic/react'; -import type { ModalDragEventDetail } from '@ionic/react'; - -function Example() { - const modal = useRef(null); - const page = useRef(null); - - const [presentingElement, setPresentingElement] = useState(null); - - const DARKEST_PERCENT = 50; - const BRIGHTNESS_RANGE = 100 - DARKEST_PERCENT; - - useEffect(() => { - setPresentingElement(page.current); - }, []); - - const onModalWillPresent = () => { - page.current!.style.transition = 'filter 0.4s ease'; - // Set to max darkness immediately - page.current!.style.setProperty('filter', `brightness(${DARKEST_PERCENT}%)`, 'important'); - }; - - const onDragStart = () => { - // Ensure transitions are off during the active drag - page.current!.style.transition = 'none'; - }; - - const onDragMove = (event: CustomEvent) => { - // `progress` is a value from 1 (top) to 0 (bottom) - const { progress } = event.detail; - - /** - * 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 * BRIGHTNESS_RANGE; - - // Update the brightness in real-time as the user drags - page.current!.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); - }; - - const onDragEnd = (event: CustomEvent) => { - // `progress` is a value from 1 (top) to 0 (bottom) - const { progress } = event.detail; - - /** - * 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 ? DARKEST_PERCENT : 100; - - // Reset to max darkness on snap-back for a nice visual effect - page.current!.style.setProperty('filter', `brightness(${brightnessValue}%)`, 'important'); - - // Re-enable transition for a smooth snap-back - page.current!.style.transition = 'filter 0.4s ease'; - }; - - const onModalWillDismiss = () => { - // Clean up styles when the modal is dismissed - page.current!.style.removeProperty('filter'); - page.current!.style.removeProperty('transition'); - }; - - return ( - - - - App - - - - - Open Card Modal - - - - - - Modal - - - -
- Drag the handle to adjust the background brightness based on a custom brightness. -
-
-
-
-
- ); -} - -export default Example; -``` diff --git a/static/usage/v8/modal/card/drag-events/vue.md b/static/usage/v8/modal/card/drag-events/vue.md deleted file mode 100644 index a23a27cc8cf..00000000000 --- a/static/usage/v8/modal/card/drag-events/vue.md +++ /dev/null @@ -1,114 +0,0 @@ -```vue - - - -``` From 3fc476a9c13795b310cb32eaab5eb09d0fe597df Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 27 Feb 2026 13:42:11 -0800 Subject: [PATCH 11/12] docs(modal): clarify deltaY --- docs/api/modal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/modal.md b/docs/api/modal.md index fa78fcd5299..9853196211b 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -292,7 +292,7 @@ interface ModalDragEventDetail { */ currentY: number; /** - * The change in Y position since the last event. + * The change in Y position since the gesture started. * * This can be used to determine the direction of the drag. */ From 351119afe449b7918c250dea5e563c44ab6fe54c Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 27 Feb 2026 13:43:38 -0800 Subject: [PATCH 12/12] docs(modal): change to predictedBreakpoint --- docs/api/modal.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/modal.md b/docs/api/modal.md index 9853196211b..2dc192bb1d3 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -228,13 +228,13 @@ The `ionDragMove` event is emitted continuously while the user is actively dragg - `currentY` and `deltaY`: Track the absolute position and the change in distance since the last frame, useful for calculating drag direction. - `velocityY`: Measures the speed of the drag, which can be used to trigger specific animations if a user "flicks" the modal. - `progress`: A normalized value between 0 and 1 representing how far the modal is open. This is ideal for dynamically adjusting the opacity of an overlay or scaling background content as the modal moves. -- `currentBreakpoint`: For sheet modals, this identifies which breakpoint the modal will snap to if released at that moment. +- `predictedBreakpoint`: For sheet modals, this identifies which breakpoint the modal will snap to if released at that moment. This event is 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 the backdrop's opacity as the modal is dragged upward. #### Using `ionDragEnd` -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` [object](#modaldrageventdetail). This event is commonly used to finalize state changes once the modal has come to a rest. For example, you might use the `currentBreakpoint` property to determine which content to load or to update the application's routing state once the user has finished swiping the sheet to a specific height. +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` [object](#modaldrageventdetail). This event is commonly used to finalize state changes once the modal has come to a rest. For example, you might use the `predictedBreakpoint` property to determine which content to load or to update the application's routing state once the user has finished swiping the sheet to a specific height. @@ -328,7 +328,7 @@ interface ModalDragEventDetail { * This can be used to style content based on where the modal will * snap to upon release. */ - currentBreakpoint?: number; + predictedBreakpoint?: number; } ```