refactor(modal): remove swipeToClose in favor of canDismiss (#26050)

BREAKING CHANGE:

- The `swipeToClose` property has been removed in favor of `canDismiss`.
- The `canDismiss` property now defaults to `true` and can no longer be set to `undefined`.
This commit is contained in:
Liam DeBeasi
2022-09-29 16:19:53 -05:00
committed by GitHub
parent 219a2058a4
commit 1f3ddf2370
12 changed files with 20 additions and 80 deletions

View File

@ -17,6 +17,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Accordion Group](#version-7x-accordion-group) - [Accordion Group](#version-7x-accordion-group)
- [Checkbox](#version-7x-checkbox) - [Checkbox](#version-7x-checkbox)
- [Input](#version-7x-input) - [Input](#version-7x-input)
- [Modal](#version-7x-modal)
- [Overlays](#version-7x-overlays) - [Overlays](#version-7x-overlays)
- [Range](#version-7x-range) - [Range](#version-7x-range)
- [Segment](#version-7x-segment) - [Segment](#version-7x-segment)
@ -71,6 +72,11 @@ This section details the desktop browser, JavaScript framework, and mobile platf
- The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`. - The `debounce` property has been updated to control the timing in milliseconds to delay the event emission of the `ionInput` event after each keystroke. Previously it would delay the event emission of `ionChange`.
<h4 id="version-7x-modal">Modal</h4>
- The `swipeToClose` property has been removed in favor of `canDismiss`.
- The `canDismiss` property now defaults to `true` and can no longer be set to `undefined`.
<h4 id="version-7x-overlays">Overlays</h4> <h4 id="version-7x-overlays">Overlays</h4>
Ionic now listens on the `keydown` event instead of the `keyup` event when determining when to dismiss overlays via the "Escape" key. Any applications that were listening on `keyup` to suppress this behavior should listen on `keydown` instead. Ionic now listens on the `keydown` event instead of the `keyup` event when determining when to dismiss overlays via the "Escape" key. Any applications that were listening on `keyup` to suppress this behavior should listen on `keydown` instead.

View File

@ -71,7 +71,6 @@ export declare interface IonModal extends Components.IonModal {
'mode', 'mode',
'presentingElement', 'presentingElement',
'showBackdrop', 'showBackdrop',
'swipeToClose',
'translucent', 'translucent',
'trigger', 'trigger',
], ],
@ -102,7 +101,6 @@ export declare interface IonModal extends Components.IonModal {
'mode', 'mode',
'presentingElement', 'presentingElement',
'showBackdrop', 'showBackdrop',
'swipeToClose',
'translucent', 'translucent',
'trigger', 'trigger',
], ],

View File

@ -778,7 +778,7 @@ ion-modal,prop,animated,boolean,true,false,false
ion-modal,prop,backdropBreakpoint,number,0,false,false ion-modal,prop,backdropBreakpoint,number,0,false,false
ion-modal,prop,backdropDismiss,boolean,true,false,false ion-modal,prop,backdropDismiss,boolean,true,false,false
ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false
ion-modal,prop,canDismiss,(() => Promise<boolean>) | boolean | undefined,undefined,false,false ion-modal,prop,canDismiss,(() => Promise<boolean>) | boolean,true,false,false
ion-modal,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | 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,handle,boolean | undefined,undefined,false,false
ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false
@ -791,7 +791,6 @@ ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefin
ion-modal,prop,mode,"ios" | "md",undefined,false,false ion-modal,prop,mode,"ios" | "md",undefined,false,false
ion-modal,prop,presentingElement,HTMLElement | undefined,undefined,false,false ion-modal,prop,presentingElement,HTMLElement | undefined,undefined,false,false
ion-modal,prop,showBackdrop,boolean,true,false,false ion-modal,prop,showBackdrop,boolean,true,false,false
ion-modal,prop,swipeToClose,boolean,false,false,false
ion-modal,prop,trigger,string | undefined,undefined,false,false ion-modal,prop,trigger,string | undefined,undefined,false,false
ion-modal,method,dismiss,dismiss(data?: any, role?: string | undefined) => Promise<boolean> ion-modal,method,dismiss,dismiss(data?: any, role?: string | undefined) => Promise<boolean>
ion-modal,method,getCurrentBreakpoint,getCurrentBreakpoint() => Promise<number | undefined> ion-modal,method,getCurrentBreakpoint,getCurrentBreakpoint() => Promise<number | undefined>

View File

@ -1547,7 +1547,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. * 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<boolean>); "canDismiss": boolean | (() => Promise<boolean>);
/** /**
* The component to display inside of the modal. * The component to display inside of the modal.
*/ */
@ -1637,11 +1637,6 @@ export namespace Components {
* If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM. * If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM.
*/ */
"showBackdrop": boolean; "showBackdrop": boolean;
/**
* If `true`, the modal can be swiped to dismiss. Only applies in iOS mode.
* @deprecated - To prevent modals from dismissing, use canDismiss instead.
*/
"swipeToClose": boolean;
/** /**
* An ID corresponding to the trigger element that causes the modal to open when clicked. * An ID corresponding to the trigger element that causes the modal to open when clicked.
*/ */
@ -5332,7 +5327,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. * 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<boolean>); "canDismiss"?: boolean | (() => Promise<boolean>);
/** /**
* The component to display inside of the modal. * The component to display inside of the modal.
*/ */
@ -5432,11 +5427,6 @@ declare namespace LocalJSX {
* If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM. * If `true`, a backdrop will be displayed behind the modal. This property controls whether or not the backdrop darkens the screen when the modal is presented. It does not control whether or not the backdrop is active or present in the DOM.
*/ */
"showBackdrop"?: boolean; "showBackdrop"?: boolean;
/**
* If `true`, the modal can be swiped to dismiss. Only applies in iOS mode.
* @deprecated - To prevent modals from dismissing, use canDismiss instead.
*/
"swipeToClose"?: boolean;
/** /**
* An ID corresponding to the trigger element that causes the modal to open when clicked. * An ID corresponding to the trigger element that causes the modal to open when clicked.
*/ */

View File

@ -9,11 +9,6 @@ export interface ModalOptions<T extends ComponentRef = ComponentRef> {
cssClass?: string | string[]; cssClass?: string | string[];
delegate?: FrameworkDelegate; delegate?: FrameworkDelegate;
animated?: boolean; animated?: boolean;
/**
* If `true`, the modal can be swiped to dismiss. Only applies in iOS mode.
* @deprecated - To prevent modals from dismissing, use canDismiss instead.
*/
swipeToClose?: boolean;
canDismiss?: boolean | (() => Promise<boolean>); canDismiss?: boolean | (() => Promise<boolean>);
mode?: Mode; mode?: Mode;

View File

@ -193,12 +193,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/ */
@Prop() animated = true; @Prop() animated = true;
/**
* If `true`, the modal can be swiped to dismiss. Only applies in iOS mode.
* @deprecated - To prevent modals from dismissing, use canDismiss instead.
*/
@Prop() swipeToClose = false;
/** /**
* The element that presented the modal. This is used for card presentation effects * The element that presented the modal. This is used for card presentation effects
* and for stacking multiple modals on top of each other. Only applies in iOS mode. * and for stacking multiple modals on top of each other. Only applies in iOS mode.
@ -250,14 +244,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/ */
@Prop() keepContentsMounted = false; @Prop() keepContentsMounted = false;
/**
* TODO (FW-937)
* This needs to default to true in the next
* major release. We default it to undefined
* so we can force the card modal to be swipeable
* when using canDismiss.
*/
/** /**
* Determines whether or not a modal can dismiss * Determines whether or not a modal can dismiss
* when calling the `dismiss` method. * when calling the `dismiss` method.
@ -265,7 +251,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 `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. * 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<boolean>); @Prop() canDismiss: boolean | (() => Promise<boolean>) = true;
/** /**
* Emitted after the modal has presented. * Emitted after the modal has presented.
@ -316,15 +302,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/ */
@Event({ eventName: 'didDismiss' }) didDismissShorthand!: EventEmitter<OverlayEventDetail>; @Event({ eventName: 'didDismiss' }) didDismissShorthand!: EventEmitter<OverlayEventDetail>;
@Watch('swipeToClose')
async swipeToCloseChanged(enable: boolean) {
if (this.gesture) {
this.gesture.enable(enable);
} else if (enable) {
await this.initSwipeToClose();
}
}
breakpointsChanged(breakpoints: number[] | undefined) { breakpointsChanged(breakpoints: number[] | undefined) {
if (breakpoints !== undefined) { if (breakpoints !== undefined) {
this.sortedBreakpoints = breakpoints.sort((a, b) => a - b); this.sortedBreakpoints = breakpoints.sort((a, b) => a - b);
@ -336,7 +313,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
} }
componentWillLoad() { componentWillLoad() {
const { breakpoints, initialBreakpoint, swipeToClose, el } = this; const { breakpoints, initialBreakpoint, el } = this;
this.inheritedAttributes = inheritAttributes(el, ['role']); this.inheritedAttributes = inheritAttributes(el, ['role']);
@ -354,12 +331,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
if (breakpoints !== undefined && initialBreakpoint !== undefined && !breakpoints.includes(initialBreakpoint)) { if (breakpoints !== undefined && initialBreakpoint !== undefined && !breakpoints.includes(initialBreakpoint)) {
printIonWarning('Your breakpoints array must include the initialBreakpoint value.'); printIonWarning('Your breakpoints array must include the initialBreakpoint value.');
} }
if (swipeToClose) {
printIonWarning(
'swipeToClose has been deprecated in favor of canDismiss.\n\nIf you want a card modal to be swipeable, set canDismiss to `true`. In the next major release of Ionic, swipeToClose will be removed, and all card modals will be swipeable by default.'
);
}
} }
componentDidLoad() { componentDidLoad() {
@ -441,14 +412,6 @@ export class Modal implements ComponentInterface, OverlayInterface {
private async checkCanDismiss() { private async checkCanDismiss() {
const { canDismiss } = this; const { canDismiss } = this;
/**
* TODO (FW-937) - Remove the following check in
* the next major release of Ionic.
*/
if (canDismiss === undefined) {
return true;
}
if (typeof canDismiss === 'function') { if (typeof canDismiss === 'function') {
return canDismiss(); return canDismiss();
} }
@ -465,6 +428,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
return; return;
} }
const { presentingElement } = this;
/** /**
* When using an inline modal * When using an inline modal
* and dismissing a modal it is possible to * and dismissing a modal it is possible to
@ -496,21 +461,12 @@ export class Modal implements ComponentInterface, OverlayInterface {
writeTask(() => this.el.classList.add('show-modal')); writeTask(() => this.el.classList.add('show-modal'));
this.currentTransition = present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, { this.currentTransition = present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, {
presentingEl: this.presentingElement, presentingEl: presentingElement,
currentBreakpoint: this.initialBreakpoint, currentBreakpoint: this.initialBreakpoint,
backdropBreakpoint: this.backdropBreakpoint, backdropBreakpoint: this.backdropBreakpoint,
}); });
/** const hasCardModal = presentingElement !== undefined;
* TODO (FW-937) - In the next major release of Ionic, all card modals
* will be swipeable by default. canDismiss will be used to determine if the
* modal can be dismissed. This check should change to check the presence of
* presentingElement instead.
*
* If we did not do this check, then not using swipeToClose would mean you could
* not run canDismiss on swipe as there would be no swipe gesture created.
*/
const hasCardModal = this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined);
/** /**
* We need to change the status bar at the * We need to change the status bar at the
@ -676,13 +632,14 @@ export class Modal implements ComponentInterface, OverlayInterface {
return false; return false;
} }
const { presentingElement } = this;
/** /**
* We need to start the status bar change * We need to start the status bar change
* before the animation so that the change * before the animation so that the change
* finishes when the dismiss animation does. * finishes when the dismiss animation does.
* TODO (FW-937)
*/ */
const hasCardModal = this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined); const hasCardModal = presentingElement !== undefined;
if (hasCardModal && getIonMode(this) === 'ios') { if (hasCardModal && getIonMode(this) === 'ios') {
setCardStatusBarDefault(); setCardStatusBarDefault();
} }
@ -707,7 +664,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
const enteringAnimation = activeAnimations.get(this) || []; const enteringAnimation = activeAnimations.get(this) || [];
this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, { this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, {
presentingEl: this.presentingElement, presentingEl: presentingElement,
currentBreakpoint: this.currentBreakpoint || this.initialBreakpoint, currentBreakpoint: this.currentBreakpoint || this.initialBreakpoint,
backdropBreakpoint: this.backdropBreakpoint, backdropBreakpoint: this.backdropBreakpoint,
}); });

View File

@ -96,7 +96,6 @@
const modalElement = await modalController.create({ const modalElement = await modalController.create({
presentingElement: presentingEl, presentingElement: presentingEl,
component: element, component: element,
swipeToClose: true,
...opts, ...opts,
}); });
return modalElement; return modalElement;

View File

@ -119,7 +119,6 @@
const modalElement = await modalController.create({ const modalElement = await modalController.create({
presentingElement: presentingEl, presentingElement: presentingEl,
component: element, component: element,
swipeToClose: true,
...opts, ...opts,
}); });
return modalElement; return modalElement;

View File

@ -151,7 +151,6 @@
const modalElement = await modalController.create({ const modalElement = await modalController.create({
presentingElement: presentingEl, presentingElement: presentingEl,
component: element, component: element,
swipeToClose: true,
...opts, ...opts,
}); });
return modalElement; return modalElement;

View File

@ -515,7 +515,6 @@
const modalElement = await modalController.create({ const modalElement = await modalController.create({
presentingElement: presentingEl, presentingElement: presentingEl,
component: element, component: element,
swipeToClose: true,
enterAnimation: toggle.checked ? enterAnimation : undefined, enterAnimation: toggle.checked ? enterAnimation : undefined,
leaveAnimation: toggle.checked ? leaveAnimation : undefined, leaveAnimation: toggle.checked ? leaveAnimation : undefined,
}); });

View File

@ -1059,7 +1059,6 @@
// present the modal // present the modal
const modalElement = Object.assign(document.createElement('ion-modal'), { const modalElement = Object.assign(document.createElement('ion-modal'), {
component: element, component: element,
swipeToClose: true,
presentingElement: document.querySelector('ion-tabs'), presentingElement: document.querySelector('ion-tabs'),
}); });

View File

@ -29,7 +29,7 @@ export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('io
export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController); export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']); export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'trigger']);
export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']); export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']);