feat(modal): add card-style presentation with swipe to close gesture (#19428)

resolves #18660
This commit is contained in:
Manu MA
2019-12-10 22:02:41 +01:00
committed by Liam DeBeasi
parent 56f67bd9a5
commit b3b3312711
29 changed files with 994 additions and 159 deletions

View File

@ -1,9 +1,9 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, h } from '@stencil/core';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, OverlayEventDetail, OverlayInterface } from '../../interface';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, OverlayEventDetail, OverlayInterface } from '../../interface';
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
import { BACKDROP, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
import { BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
import { getClassMap } from '../../utils/theme';
import { deepReady } from '../../utils/transition';
@ -11,6 +11,7 @@ import { iosEnterAnimation } from './animations/ios.enter';
import { iosLeaveAnimation } from './animations/ios.leave';
import { mdEnterAnimation } from './animations/md.enter';
import { mdLeaveAnimation } from './animations/md.leave';
import { createSwipeToCloseGesture } from './gestures/swipe-to-close';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
@ -24,11 +25,13 @@ import { mdLeaveAnimation } from './animations/md.leave';
scoped: true
})
export class Modal implements ComponentInterface, OverlayInterface {
private gesture?: Gesture;
// Reference to the user's provided modal content
private usersElement?: HTMLElement;
presented = false;
animation: Animation | undefined;
animation?: Animation;
mode = getIonMode(this);
@Element() el!: HTMLIonModalElement;
@ -85,6 +88,17 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/
@Prop() animated = true;
/**
* If `true`, the modal can be swiped to dismiss. Only applies in iOS mode.
*/
@Prop() swipeToClose = false;
/**
* 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.
*/
@Prop() presentingElement?: HTMLElement;
/**
* Emitted after the modal has presented.
*/
@ -127,7 +141,21 @@ export class Modal implements ComponentInterface, OverlayInterface {
};
this.usersElement = await attachComponent(this.delegate, container, this.component, ['ion-page'], componentProps);
await deepReady(this.usersElement);
return present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation);
await present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, this.presentingElement);
const mode = getIonMode(this);
if (this.swipeToClose && mode === 'ios') {
// All of the elements needed for the swipe gesture
// should be in the DOM and referenced by now, except
// for the presenting el
const ani = this.animation = iosLeaveAnimation(this.el, this.presentingElement);
this.gesture = createSwipeToCloseGesture(
this.el,
ani,
() => this.dismiss(undefined, 'gesture')
);
this.gesture.enable(true);
}
}
/**
@ -138,10 +166,21 @@ export class Modal implements ComponentInterface, OverlayInterface {
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {
const dismissed = await dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation);
const iosAni = (this.animation === undefined || (role === BACKDROP || role === undefined)) ? iosLeaveAnimation : undefined;
const enteringAnimation = activeAnimations.get(this) || [];
const dismissed = await dismiss(this, data, role, 'modalLeave', iosAni, mdLeaveAnimation, this.presentingElement);
if (dismissed) {
await detachComponent(this.delegate, this.usersElement);
if (this.animation) {
this.animation.destroy();
}
enteringAnimation.forEach(ani => ani.destroy());
}
this.animation = undefined;
return dismissed;
}
@ -194,6 +233,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
aria-modal="true"
class={{
[mode]: true,
[`modal-card`]: this.presentingElement !== undefined,
...getClassMap(this.cssClass)
}}
style={{
@ -209,10 +249,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}/>
<div
role="dialog"
class={{
[`modal-wrapper`]: true,
[mode]: true,
}}
class="modal-wrapper"
>
</div>
</Host>