mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
fix(modal): respect card-style modal spec for iPadOS (#20750)
fixes #20700
This commit is contained in:
@ -9,7 +9,6 @@ export const iosEnterAnimation = (
|
|||||||
baseEl: HTMLElement,
|
baseEl: HTMLElement,
|
||||||
presentingEl?: HTMLElement,
|
presentingEl?: HTMLElement,
|
||||||
): Animation => {
|
): Animation => {
|
||||||
// The top translate Y for the presenting element
|
|
||||||
const backdropAnimation = createAnimation()
|
const backdropAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('ion-backdrop')!)
|
.addElement(baseEl.querySelector('ion-backdrop')!)
|
||||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
||||||
@ -19,7 +18,7 @@ export const iosEnterAnimation = (
|
|||||||
.afterClearStyles(['pointer-events']);
|
.afterClearStyles(['pointer-events']);
|
||||||
|
|
||||||
const wrapperAnimation = createAnimation()
|
const wrapperAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('.modal-wrapper')!)
|
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||||
.beforeStyles({ 'opacity': 1 })
|
.beforeStyles({ 'opacity': 1 })
|
||||||
.fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
|
.fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
|
||||||
|
|
||||||
@ -27,26 +26,33 @@ export const iosEnterAnimation = (
|
|||||||
.addElement(baseEl)
|
.addElement(baseEl)
|
||||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||||
.duration(500)
|
.duration(500)
|
||||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
.addAnimation(wrapperAnimation);
|
||||||
|
|
||||||
if (presentingEl) {
|
if (presentingEl) {
|
||||||
/**
|
const isMobile = window.innerWidth < 768;
|
||||||
* Fallback for browsers that does not support `max()` (ex: Firefox)
|
const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined);
|
||||||
* No need to wrry about statusbar padding since engines like Gecko
|
|
||||||
* are not used as the engine for standlone Cordova/Capacitor apps
|
|
||||||
*/
|
|
||||||
const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
|
||||||
const modalTransform = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined) ? '-10px' : transformOffset;
|
|
||||||
const bodyEl = document.body;
|
|
||||||
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
||||||
const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
|
|
||||||
|
|
||||||
const presentingAnimation = createAnimation()
|
const presentingAnimation = createAnimation()
|
||||||
.beforeStyles({
|
.beforeStyles({
|
||||||
'transform': 'translateY(0)',
|
'transform': 'translateY(0)',
|
||||||
'transform-origin': 'top center',
|
'transform-origin': 'top center',
|
||||||
'overflow': 'hidden'
|
'overflow': 'hidden'
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const bodyEl = document.body;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
/**
|
||||||
|
* Fallback for browsers that does not support `max()` (ex: Firefox)
|
||||||
|
* No need to worry about statusbar padding since engines like Gecko
|
||||||
|
* are not used as the engine for standlone Cordova/Capacitor apps
|
||||||
|
*/
|
||||||
|
const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
||||||
|
const modalTransform = hasCardModal ? '-10px' : transformOffset;
|
||||||
|
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||||
|
const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
|
||||||
|
|
||||||
|
presentingAnimation
|
||||||
.afterStyles({
|
.afterStyles({
|
||||||
'transform': finalTransform
|
'transform': finalTransform
|
||||||
})
|
})
|
||||||
@ -58,6 +64,40 @@ export const iosEnterAnimation = (
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
baseAnimation.addAnimation(presentingAnimation);
|
baseAnimation.addAnimation(presentingAnimation);
|
||||||
|
} else {
|
||||||
|
baseAnimation.addAnimation(backdropAnimation);
|
||||||
|
|
||||||
|
if (!hasCardModal) {
|
||||||
|
wrapperAnimation.fromTo('opacity', '0', '1');
|
||||||
|
} else {
|
||||||
|
const toPresentingScale = (hasCardModal) ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
|
||||||
|
const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
||||||
|
|
||||||
|
presentingAnimation
|
||||||
|
.afterStyles({
|
||||||
|
'transform': finalTransform
|
||||||
|
})
|
||||||
|
.addElement(presentingEl.querySelector('.modal-wrapper')!)
|
||||||
|
.keyframes([
|
||||||
|
{ offset: 0, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' },
|
||||||
|
{ offset: 1, filter: 'contrast(0.85)', transform: finalTransform }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const shadowAnimation = createAnimation()
|
||||||
|
.afterStyles({
|
||||||
|
'transform': finalTransform
|
||||||
|
})
|
||||||
|
.addElement(presentingEl.querySelector('.modal-shadow')!)
|
||||||
|
.keyframes([
|
||||||
|
{ offset: 0, opacity: '1', transform: 'translateY(0) scale(1)' },
|
||||||
|
{ offset: 1, opacity: '0', transform: finalTransform }
|
||||||
|
]);
|
||||||
|
|
||||||
|
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseAnimation.addAnimation(backdropAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseAnimation;
|
return baseAnimation;
|
||||||
|
@ -10,13 +10,12 @@ export const iosLeaveAnimation = (
|
|||||||
presentingEl?: HTMLElement,
|
presentingEl?: HTMLElement,
|
||||||
duration = 500
|
duration = 500
|
||||||
): Animation => {
|
): Animation => {
|
||||||
|
|
||||||
const backdropAnimation = createAnimation()
|
const backdropAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('ion-backdrop')!)
|
.addElement(baseEl.querySelector('ion-backdrop')!)
|
||||||
.fromTo('opacity', 'var(--backdrop-opacity)', 0.0);
|
.fromTo('opacity', 'var(--backdrop-opacity)', 0.0);
|
||||||
|
|
||||||
const wrapperAnimation = createAnimation()
|
const wrapperAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('.modal-wrapper')!)
|
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||||
.beforeStyles({ 'opacity': 1 })
|
.beforeStyles({ 'opacity': 1 })
|
||||||
.fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
|
.fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
|
||||||
|
|
||||||
@ -24,15 +23,13 @@ export const iosLeaveAnimation = (
|
|||||||
.addElement(baseEl)
|
.addElement(baseEl)
|
||||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||||
.duration(duration)
|
.duration(duration)
|
||||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
.addAnimation(wrapperAnimation);
|
||||||
|
|
||||||
if (presentingEl) {
|
if (presentingEl) {
|
||||||
const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
const isMobile = window.innerWidth < 768;
|
||||||
const modalTransform = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined) ? '-10px' : transformOffset;
|
const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined);
|
||||||
const bodyEl = document.body;
|
|
||||||
const currentPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
|
||||||
const presentingAnimation = createAnimation()
|
const presentingAnimation = createAnimation()
|
||||||
.addElement(presentingEl)
|
|
||||||
.beforeClearStyles(['transform'])
|
.beforeClearStyles(['transform'])
|
||||||
.afterClearStyles(['transform'])
|
.afterClearStyles(['transform'])
|
||||||
.onFinish(currentStep => {
|
.onFinish(currentStep => {
|
||||||
@ -45,13 +42,58 @@ export const iosLeaveAnimation = (
|
|||||||
if (numModals <= 1) {
|
if (numModals <= 1) {
|
||||||
bodyEl.style.setProperty('background-color', '');
|
bodyEl.style.setProperty('background-color', '');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const bodyEl = document.body;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
const transformOffset = (!CSS.supports('width', 'max(0px, 1px)')) ? '30px' : 'max(30px, var(--ion-safe-area-top))';
|
||||||
|
const modalTransform = hasCardModal ? '-10px' : transformOffset;
|
||||||
|
const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||||
|
const finalTransform = `translateY(${modalTransform}) scale(${toPresentingScale})`;
|
||||||
|
|
||||||
|
presentingAnimation
|
||||||
|
.addElement(presentingEl)
|
||||||
.keyframes([
|
.keyframes([
|
||||||
{ offset: 0, filter: 'contrast(0.85)', transform: `translateY(${modalTransform}) scale(${currentPresentingScale})`, borderRadius: '10px 10px 0 0' },
|
{ offset: 0, filter: 'contrast(0.85)', transform: finalTransform, borderRadius: '10px 10px 0 0' },
|
||||||
{ offset: 1, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' }
|
{ offset: 1, filter: 'contrast(1)', transform: 'translateY(0px) scale(1)', borderRadius: '0px' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
baseAnimation.addAnimation(presentingAnimation);
|
baseAnimation.addAnimation(presentingAnimation);
|
||||||
|
} else {
|
||||||
|
baseAnimation.addAnimation(backdropAnimation);
|
||||||
|
|
||||||
|
if (!hasCardModal) {
|
||||||
|
wrapperAnimation.fromTo('opacity', '1', '0');
|
||||||
|
} else {
|
||||||
|
const toPresentingScale = (hasCardModal) ? SwipeToCloseDefaults.MIN_PRESENTING_SCALE : 1;
|
||||||
|
const finalTransform = `translateY(-10px) scale(${toPresentingScale})`;
|
||||||
|
|
||||||
|
presentingAnimation
|
||||||
|
.addElement(presentingEl.querySelector('.modal-wrapper')!)
|
||||||
|
.afterStyles({
|
||||||
|
'transform': 'translate3d(0, 0, 0)'
|
||||||
|
})
|
||||||
|
.keyframes([
|
||||||
|
{ offset: 0, filter: 'contrast(0.85)', transform: finalTransform },
|
||||||
|
{ offset: 1, filter: 'contrast(1)', transform: 'translateY(0) scale(1)' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const shadowAnimation = createAnimation()
|
||||||
|
.addElement(presentingEl.querySelector('.modal-shadow')!)
|
||||||
|
.afterStyles({
|
||||||
|
'transform': 'translateY(0) scale(1)'
|
||||||
|
})
|
||||||
|
.keyframes([
|
||||||
|
{ offset: 0, opacity: '0', transform: finalTransform },
|
||||||
|
{ offset: 1, opacity: '1', transform: 'translateY(0) scale(1)' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseAnimation.addAnimation(backdropAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseAnimation;
|
return baseAnimation;
|
||||||
|
@ -19,24 +19,58 @@
|
|||||||
@include transform(translate3d(0, 100%, 0));
|
@include transform(translate3d(0, 100%, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.modal-card) {
|
@media screen and (max-width: 767px) {
|
||||||
|
@supports (width: max(0px, 1px)) {
|
||||||
|
:host(.modal-card) .modal-wrapper {
|
||||||
|
height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (width: max(0px, 1px)) {
|
||||||
|
:host(.modal-card) .modal-wrapper {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.modal-card) .modal-wrapper {
|
||||||
|
@include border-radius($modal-ios-border-radius, $modal-ios-border-radius, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.modal-card) {
|
||||||
--backdrop-opacity: 0;
|
--backdrop-opacity: 0;
|
||||||
--width: 100%;
|
--width: 100%;
|
||||||
|
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.modal-card) ion-backdrop {
|
:host(.modal-card) .modal-shadow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.modal-card) ion-backdrop {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
:host(.modal-card) .modal-wrapper {
|
|
||||||
@include border-radius($modal-ios-border-radius, $modal-ios-border-radius, 0, 0);
|
@media screen and (min-width: 768px) {
|
||||||
height: calc(100% - 40px);
|
:host {
|
||||||
}
|
--width: calc(100% - 120px);
|
||||||
|
--height: calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));
|
||||||
@supports (width: max(0px, 1px)) {
|
--max-width: 720px;
|
||||||
:host(.modal-card) .modal-wrapper {
|
--max-height: 1000px;
|
||||||
height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
|
}
|
||||||
|
|
||||||
|
:host(.modal-card) {
|
||||||
|
--backdrop-opacity: 0;
|
||||||
|
|
||||||
|
transition: all 0.5s ease-in-out;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
--backdrop-opacity: 0.18;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.modal-card) .modal-shadow {
|
||||||
|
box-shadow: 0px 0px 30px 10px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -52,7 +52,8 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-wrapper {
|
.modal-wrapper,
|
||||||
|
.modal-shadow {
|
||||||
@include border-radius(var(--border-radius));
|
@include border-radius(var(--border-radius));
|
||||||
|
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
@ -74,6 +75,12 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-shadow {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: $modal-inset-min-width) and (min-height: $modal-inset-min-height-small) {
|
@media only screen and (min-width: $modal-inset-min-width) and (min-height: $modal-inset-min-height-small) {
|
||||||
:host {
|
:host {
|
||||||
--width: #{$modal-inset-width};
|
--width: #{$modal-inset-width};
|
||||||
|
@ -272,6 +272,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
|||||||
onIonModalDidDismiss={this.onLifecycle}
|
onIonModalDidDismiss={this.onLifecycle}
|
||||||
>
|
>
|
||||||
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}/>
|
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}/>
|
||||||
|
|
||||||
|
{mode === 'ios' && <div class="modal-shadow"></div>}
|
||||||
<div
|
<div
|
||||||
role="dialog"
|
role="dialog"
|
||||||
class="modal-wrapper"
|
class="modal-wrapper"
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
.fromTo('opacity', '0.01', '0.4');
|
.fromTo('opacity', '0.01', '0.4');
|
||||||
|
|
||||||
const wrapperAnimation = createAnimation()
|
const wrapperAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('.modal-wrapper'))
|
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
||||||
.keyframes([
|
.keyframes([
|
||||||
{ offset: 0, opacity: '0', transform: 'scale(0)' },
|
{ offset: 0, opacity: '0', transform: 'scale(0)' },
|
||||||
{ offset: 1, opacity: '0.99', transform: 'scale(1)' }
|
{ offset: 1, opacity: '0.99', transform: 'scale(1)' }
|
||||||
@ -97,7 +97,7 @@
|
|||||||
.fromTo('opacity', '0.4', '0.01');
|
.fromTo('opacity', '0.4', '0.01');
|
||||||
|
|
||||||
const wrapperAnimation = createAnimation()
|
const wrapperAnimation = createAnimation()
|
||||||
.addElement(baseEl.querySelector('.modal-wrapper'))
|
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
||||||
.keyframes([
|
.keyframes([
|
||||||
{ offset: 0, opacity: '0.99', transform: 'scale(1)' },
|
{ offset: 0, opacity: '0.99', transform: 'scale(1)' },
|
||||||
{ offset: 1, opacity: '0', transform: 'scale(0)' }
|
{ offset: 1, opacity: '0', transform: 'scale(0)' }
|
||||||
|
Reference in New Issue
Block a user