diff --git a/core/api.txt b/core/api.txt index ec0527ac0c..1ef287856a 100644 --- a/core/api.txt +++ b/core/api.txt @@ -780,7 +780,7 @@ ion-modal,prop,animated,boolean,true,false,false ion-modal,prop,backdropBreakpoint,number,0,false,false ion-modal,prop,backdropDismiss,boolean,true,false,false ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false -ion-modal,prop,canDismiss,(() => Promise) | boolean | undefined,undefined,false,false +ion-modal,prop,canDismiss,((data?: any, role?: string | undefined) => Promise) | boolean | undefined,undefined,false,false ion-modal,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-modal,prop,handle,boolean | undefined,undefined,false,false ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 5cd95a8b89..107ef42924 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1560,7 +1560,7 @@ export namespace Components { /** * Determines whether or not a modal can dismiss when calling the `dismiss` method. If the value is `true` or the value's function returns `true`, the modal will close when trying to dismiss. If the value is `false` or the value's function returns `false`, the modal will not close when trying to dismiss. */ - "canDismiss"?: undefined | boolean | (() => Promise); + "canDismiss"?: undefined | boolean | ((data?: any, role?: string) => Promise); /** * The component to display inside of the modal. */ @@ -5548,7 +5548,7 @@ declare namespace LocalJSX { /** * Determines whether or not a modal can dismiss when calling the `dismiss` method. If the value is `true` or the value's function returns `true`, the modal will close when trying to dismiss. If the value is `false` or the value's function returns `false`, the modal will not close when trying to dismiss. */ - "canDismiss"?: undefined | boolean | (() => Promise); + "canDismiss"?: undefined | boolean | ((data?: any, role?: string) => Promise); /** * The component to display inside of the modal. */ diff --git a/core/src/components/modal/gestures/utils.ts b/core/src/components/modal/gestures/utils.ts index 36268e8412..694227d630 100644 --- a/core/src/components/modal/gestures/utils.ts +++ b/core/src/components/modal/gestures/utils.ts @@ -1,3 +1,5 @@ +import { GESTURE } from '@utils/overlays'; + import type { Animation } from '../../../interface'; export const handleCanDismiss = async (el: HTMLIonModalElement, animation: Animation) => { @@ -18,7 +20,7 @@ export const handleCanDismiss = async (el: HTMLIonModalElement, animation: Anima * If the function returns `true`, * then we can proceed with dismiss. */ - const shouldDismiss = await el.canDismiss(); + const shouldDismiss = await el.canDismiss(undefined, GESTURE); if (!shouldDismiss) { return; } diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 7d8aa91503..6269637ab2 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -23,7 +23,15 @@ import type { Attributes } from '../../utils/helpers'; import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard'; import { printIonWarning } from '../../utils/logging'; import { Style as StatusBarStyle, StatusBar } from '../../utils/native/status-bar'; -import { BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays'; +import { + GESTURE, + BACKDROP, + activeAnimations, + dismiss, + eventMethod, + prepareOverlay, + present, +} from '../../utils/overlays'; import { getClassMap } from '../../utils/theme'; import { deepReady } from '../../utils/transition'; @@ -267,7 +275,7 @@ export class Modal implements ComponentInterface, OverlayInterface { * If the value is `true` or the value's function returns `true`, the modal will close when trying to dismiss. * If the value is `false` or the value's function returns `false`, the modal will not close when trying to dismiss. */ - @Prop() canDismiss?: undefined | boolean | (() => Promise); + @Prop() canDismiss?: undefined | boolean | ((data?: any, role?: string) => Promise); /** * Emitted after the modal has presented. @@ -449,7 +457,7 @@ export class Modal implements ComponentInterface, OverlayInterface { * modal is allowed to dismiss based * on the state of the canDismiss prop. */ - private async checkCanDismiss() { + private async checkCanDismiss(data?: any, role?: string) { const { canDismiss } = this; /** @@ -461,7 +469,7 @@ export class Modal implements ComponentInterface, OverlayInterface { } if (typeof canDismiss === 'function') { - return canDismiss(); + return canDismiss(data, role); } return canDismiss; @@ -603,7 +611,7 @@ export class Modal implements ComponentInterface, OverlayInterface { */ this.gestureAnimationDismissing = true; this.animation!.onFinish(async () => { - await this.dismiss(undefined, 'gesture'); + await this.dismiss(undefined, GESTURE); this.gestureAnimationDismissing = false; }); }); @@ -665,7 +673,7 @@ export class Modal implements ComponentInterface, OverlayInterface { this.animation!.onFinish(async () => { this.currentBreakpoint = 0; this.ionBreakpointDidChange.emit({ breakpoint: this.currentBreakpoint }); - await this.dismiss(undefined, 'gesture'); + await this.dismiss(undefined, GESTURE); this.gestureAnimationDismissing = false; }); } @@ -678,7 +686,7 @@ export class Modal implements ComponentInterface, OverlayInterface { */ @Method() async dismiss(data?: any, role?: string): Promise { - if (this.gestureAnimationDismissing && role !== 'gesture') { + if (this.gestureAnimationDismissing && role !== GESTURE) { return false; } @@ -687,7 +695,7 @@ export class Modal implements ComponentInterface, OverlayInterface { * for calling the dismiss method, we should * not run the canDismiss check again. */ - if (role !== 'handler' && !(await this.checkCanDismiss())) { + if (role !== 'handler' && !(await this.checkCanDismiss(data, role))) { return false; } diff --git a/core/src/components/modal/test/canDismiss/index.html b/core/src/components/modal/test/canDismiss/index.html index c51c09bff8..d0ed369978 100644 --- a/core/src/components/modal/test/canDismiss/index.html +++ b/core/src/components/modal/test/canDismiss/index.html @@ -116,13 +116,13 @@ handler = false; break; case 'promise-true': - handler = () => { + handler = (data, role) => { return new Promise((resolve) => { setTimeout(() => { resolve(true); setTimeout(() => { - const event = new CustomEvent('ionHandlerDone'); + const event = new CustomEvent('ionHandlerDone', { detail: { data, role } }); window.dispatchEvent(event); }, 1000); }, 250); @@ -132,11 +132,11 @@ case 'promise-false': handler = () => { return new Promise((resolve) => { - setTimeout(() => { + setTimeout((data, role) => { resolve(false); setTimeout(() => { - const event = new CustomEvent('ionHandlerDone'); + const event = new CustomEvent('ionHandlerDone', { detail: { data, role } }); window.dispatchEvent(event); }, 1000); }, 250); diff --git a/core/src/components/modal/test/canDismiss/modal.e2e.ts b/core/src/components/modal/test/canDismiss/modal.e2e.ts index 81d9519f94..637a913e29 100644 --- a/core/src/components/modal/test/canDismiss/modal.e2e.ts +++ b/core/src/components/modal/test/canDismiss/modal.e2e.ts @@ -378,4 +378,42 @@ test.describe('modal: canDismiss', () => { await ionModalDidDismiss.next(); }); }); + + test.describe('function params', () => { + test.beforeEach(({ skip }) => { + skip.rtl(); + skip.mode('md'); + }); + test('should pass data and role when calling dismiss', async ({ page }) => { + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionHandlerDone = await page.spyOnEvent('ionHandlerDone'); + + await page.click('#radio-promise-true'); + await page.click('#show-modal'); + + await ionModalDidPresent.next(); + + const modal = await page.locator('ion-modal'); + await modal.evaluate((el: HTMLIonModalElement) => el.dismiss('my data', 'my role')); + + await ionHandlerDone.next(); + await expect(ionHandlerDone).toHaveReceivedEventDetail({ data: 'my data', role: 'my role' }); + }); + test('should pass data and role when swiping', async ({ page }) => { + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionHandlerDone = await page.spyOnEvent('ionHandlerDone'); + + await page.click('#radio-card'); + await page.click('#radio-promise-true'); + await page.click('#show-modal'); + + await ionModalDidPresent.next(); + + const modalHeader = await page.locator('#modal-header'); + await dragElementBy(modalHeader, page, 0, 500); + + await ionHandlerDone.next(); + await expect(ionHandlerDone).toHaveReceivedEventDetail({ data: undefined, role: 'gesture' }); + }); + }); }); diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 4c1f267cc4..5f570c83dd 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -507,7 +507,7 @@ export const dismiss = async ( : config.get(name, mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); // If dismissed via gesture, no need to play leaving animation again - if (role !== 'gesture') { + if (role !== GESTURE) { await overlayAnimation(overlay, animationBuilder, overlay.el, opts); } overlay.didDismiss.emit({ data, role }); @@ -612,3 +612,4 @@ export const safeCall = (handler: any, arg?: any) => { }; export const BACKDROP = 'backdrop'; +export const GESTURE = 'gesture';