mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
feat(modal): add card-style presentation with swipe to close gesture (#19428)
resolves #18660
This commit is contained in:
@ -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>
|
||||
|
Reference in New Issue
Block a user