diff --git a/docs/api/modal.md b/docs/api/modal.md index c0eb448318a..9bc25674960 100644 --- a/docs/api/modal.md +++ b/docs/api/modal.md @@ -87,6 +87,18 @@ import CanDismissPreventSwipeToCloseExample from '@site/static/usage/v7/modal/ca +### Modifying dismiss behavior in child components + +In certain scenarios, developers may need to customize the behavior of the `canDismiss` callback based on the state of a presented modal. This customization can be particularly useful, for instance, when developers want to prevent the modal from being dismissed if a form within it is invalid. + +To achieve this customization, child components can employ various techniques such as function callbacks, event emission, or other reactivity mechanisms to communicate with the parent component and update the conditions governing the `canDismiss` callback. + +Here's a simplified example illustrating how a child component can interact with a parent component to modify the `canDismiss` callback: + +import CanDismissChildStateExample from '@site/static/usage/v7/modal/can-dismiss/child-state/index.md'; + + + ## Types of modals ### Card Modal diff --git a/static/usage/v7/modal/can-dismiss/child-state/angular/app_module_ts.md b/static/usage/v7/modal/can-dismiss/child-state/angular/app_module_ts.md new file mode 100644 index 00000000000..b273d851f90 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/angular/app_module_ts.md @@ -0,0 +1,19 @@ +```ts +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { IonicModule } from '@ionic/angular'; + +import { AppComponent } from './app.component'; +import { ExampleComponent } from './example.component'; +import { ChildComponent } from './child.component'; + +@NgModule({ + imports: [BrowserModule, FormsModule, RouterModule.forRoot([]), IonicModule.forRoot({})], + declarations: [AppComponent, ExampleComponent, ChildComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_html.md b/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_html.md new file mode 100644 index 00000000000..3bef90bc0f9 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_html.md @@ -0,0 +1,24 @@ +```html +
+ + + Modal + + Close + + + + + + + + Override Dismiss
+ Toggle the checkbox to allow immediately dismissing the modal without a prompt. +
+
+
+
+
+``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_ts.md b/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_ts.md new file mode 100644 index 00000000000..420359602a2 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/angular/child_component_ts.md @@ -0,0 +1,22 @@ +```ts +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +import { CheckboxCustomEvent, IonModal } from '@ionic/angular'; + +@Component({ + selector: 'app-child', + templateUrl: 'child.component.html', +}) +export class ChildComponent { + @Input() modal!: IonModal; + + @Output() dismissChange = new EventEmitter(); + + checkboxChanged(event: any) { + const ev = event as CheckboxCustomEvent; + const checked = ev.detail.checked; + + this.dismissChange.emit(checked); + } +} +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_html.md b/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_html.md new file mode 100644 index 00000000000..aeed41b7408 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_html.md @@ -0,0 +1,23 @@ +```html +
+ + + App + + + + Open + + + + + + +
+``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_ts.md b/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_ts.md new file mode 100644 index 00000000000..19544ec574a --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/angular/example_component_ts.md @@ -0,0 +1,58 @@ +```ts +import { Component } from '@angular/core'; + +import { ActionSheetController } from '@ionic/angular'; + +@Component({ + selector: 'app-example', + templateUrl: 'example.component.html', +}) +export class ExampleComponent { + presentingElement = undefined; + + private canDismissOverride = false; + + constructor(private actionSheetCtrl: ActionSheetController) {} + + ngOnInit() { + this.presentingElement = document.querySelector('.ion-page'); + } + + onDismissChange(canDismiss: boolean) { + // Allows the modal to be dismissed based on the state of the checkbox + this.canDismissOverride = canDismiss; + } + + onWillPresent() { + // Resets the override when the modal is presented + this.canDismissOverride = false; + } + + canDismiss = async () => { + if (this.canDismissOverride) { + // Checks for the override flag to return early if we can dismiss the overlay immediately + return true; + } + + const actionSheet = await this.actionSheetCtrl.create({ + header: 'Are you sure?', + buttons: [ + { + text: 'Yes', + role: 'confirm', + }, + { + text: 'No', + role: 'cancel', + }, + ], + }); + + actionSheet.present(); + + const { role } = await actionSheet.onWillDismiss(); + + return role === 'confirm'; + }; +} +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/demo.html b/static/usage/v7/modal/can-dismiss/child-state/demo.html new file mode 100644 index 00000000000..3d8398d22ba --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/demo.html @@ -0,0 +1,92 @@ + + + + + + Modal | Can Dismiss + + + + + + + + +
+ + + App + + + + Open + + + + + Modal + + Close + + + + + + + + Override Dismiss
+ Toggle the checkbox to allow immediately dismissing the modal without a prompt. +
+
+
+
+
+
+
+
+ + + + diff --git a/static/usage/v7/modal/can-dismiss/child-state/index.md b/static/usage/v7/modal/can-dismiss/child-state/index.md new file mode 100644 index 00000000000..d213d283663 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/index.md @@ -0,0 +1,42 @@ +import Playground from '@site/src/components/global/Playground'; + +import vue_example_vue from './vue/example_vue.md'; +import vue_child_vue from './vue/child_vue.md'; + +import react_main_tsx from './react/main_tsx.md'; +import react_child_tsx from './react/child_tsx.md'; + +import angular_example_component_html from './angular/example_component_html.md'; +import angular_example_component_ts from './angular/example_component_ts.md'; +import angular_child_component_html from './angular/child_component_html.md'; +import angular_child_component_ts from './angular/child_component_ts.md'; +import angular_app_module_ts from './angular/app_module_ts.md'; + + diff --git a/static/usage/v7/modal/can-dismiss/child-state/react/child_tsx.md b/static/usage/v7/modal/can-dismiss/child-state/react/child_tsx.md new file mode 100644 index 00000000000..35035eb357e --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/react/child_tsx.md @@ -0,0 +1,55 @@ +```tsx +import { + IonHeader, + IonToolbar, + IonTitle, + IonButtons, + IonButton, + IonContent, + IonList, + IonItem, + IonCheckbox, + IonNote, +} from '@ionic/react'; + +import type { CheckboxCustomEvent } from '@ionic/core/components'; + +interface ChildProps { + dismissChange: (checked: boolean) => void; + dismiss: () => void; +} + +function Child({ dismissChange, dismiss }: ChildProps) { + const checkboxChanged = (ev: CheckboxCustomEvent) => { + const checked = ev.detail.checked; + dismissChange(checked); + }; + + return ( + <> + + + Modal + + dismiss()}>Close + + + + + + + + Override Dismiss +
+ + Toggle the checkbox to allow immediately dismissing the modal without a prompt. + +
+
+
+
+ + ); +} +export default Child; +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/react/main_tsx.md b/static/usage/v7/modal/can-dismiss/child-state/react/main_tsx.md new file mode 100644 index 00000000000..d8db547e7f9 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/react/main_tsx.md @@ -0,0 +1,97 @@ +```tsx +import React, { useState, useRef, useEffect } from 'react'; +import { + IonButton, + IonModal, + IonHeader, + IonContent, + IonToolbar, + IonTitle, + IonPage, + useIonActionSheet, +} from '@ionic/react'; + +import Child from './Child'; + +function Example() { + const modal = useRef(null); + const page = useRef(null); + const [canDismissOverride, setCanDismissOverride] = useState(false); + + const [presentingElement, setPresentingElement] = useState(null); + const [present] = useIonActionSheet(); + + useEffect(() => { + setPresentingElement(page.current); + }, []); + + function dismiss() { + modal.current?.dismiss(); + } + + function onWillPresent() { + // Resets the override when the modal is presented + setCanDismissOverride(false); + } + + async function canDismiss() { + if (canDismissOverride) { + // Checks for the override flag to return early if we can dismiss the overlay immediately + return true; + } + return new Promise((resolve, reject) => { + present({ + header: 'Are you sure?', + buttons: [ + { + text: 'Yes', + role: 'confirm', + }, + { + text: 'No', + role: 'cancel', + }, + ], + onWillDismiss: (ev) => { + if (ev.detail.role === 'confirm') { + resolve(true); + } else { + reject(); + } + }, + }); + }); + } + + return ( + + + + App + + + + + Open + + + { + setCanDismissOverride(checked); + }} + /> + + + + ); +} + +export default Example; +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/vue/child_vue.md b/static/usage/v7/modal/can-dismiss/child-state/vue/child_vue.md new file mode 100644 index 00000000000..6c402f88a98 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/vue/child_vue.md @@ -0,0 +1,51 @@ +```html + + + +``` diff --git a/static/usage/v7/modal/can-dismiss/child-state/vue/example_vue.md b/static/usage/v7/modal/can-dismiss/child-state/vue/example_vue.md new file mode 100644 index 00000000000..831c89cb435 --- /dev/null +++ b/static/usage/v7/modal/can-dismiss/child-state/vue/example_vue.md @@ -0,0 +1,85 @@ +```html + + + +```