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,
|
||||
presentingEl?: HTMLElement,
|
||||
): Animation => {
|
||||
// The top translate Y for the presenting element
|
||||
const backdropAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('ion-backdrop')!)
|
||||
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
||||
@ -19,7 +18,7 @@ export const iosEnterAnimation = (
|
||||
.afterClearStyles(['pointer-events']);
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('.modal-wrapper')!)
|
||||
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||
.beforeStyles({ 'opacity': 1 })
|
||||
.fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
|
||||
|
||||
@ -27,26 +26,33 @@ export const iosEnterAnimation = (
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(500)
|
||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
||||
.addAnimation(wrapperAnimation);
|
||||
|
||||
if (presentingEl) {
|
||||
/**
|
||||
* Fallback for browsers that does not support `max()` (ex: Firefox)
|
||||
* 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 isMobile = window.innerWidth < 768;
|
||||
const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined);
|
||||
|
||||
const presentingAnimation = createAnimation()
|
||||
.beforeStyles({
|
||||
'transform': 'translateY(0)',
|
||||
'transform-origin': 'top center',
|
||||
'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({
|
||||
'transform': finalTransform
|
||||
})
|
||||
@ -58,6 +64,40 @@ export const iosEnterAnimation = (
|
||||
]);
|
||||
|
||||
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;
|
||||
|
@ -10,13 +10,12 @@ export const iosLeaveAnimation = (
|
||||
presentingEl?: HTMLElement,
|
||||
duration = 500
|
||||
): Animation => {
|
||||
|
||||
const backdropAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('ion-backdrop')!)
|
||||
.fromTo('opacity', 'var(--backdrop-opacity)', 0.0);
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('.modal-wrapper')!)
|
||||
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow')!)
|
||||
.beforeStyles({ 'opacity': 1 })
|
||||
.fromTo('transform', 'translateY(0vh)', 'translateY(100vh)');
|
||||
|
||||
@ -24,15 +23,13 @@ export const iosLeaveAnimation = (
|
||||
.addElement(baseEl)
|
||||
.easing('cubic-bezier(0.32,0.72,0,1)')
|
||||
.duration(duration)
|
||||
.addAnimation([backdropAnimation, wrapperAnimation]);
|
||||
.addAnimation(wrapperAnimation);
|
||||
|
||||
if (presentingEl) {
|
||||
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 currentPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const hasCardModal = (presentingEl.tagName === 'ION-MODAL' && (presentingEl as HTMLIonModalElement).presentingElement !== undefined);
|
||||
|
||||
const presentingAnimation = createAnimation()
|
||||
.addElement(presentingEl)
|
||||
.beforeClearStyles(['transform'])
|
||||
.afterClearStyles(['transform'])
|
||||
.onFinish(currentStep => {
|
||||
@ -45,13 +42,58 @@ export const iosLeaveAnimation = (
|
||||
if (numModals <= 1) {
|
||||
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([
|
||||
{ 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' }
|
||||
]);
|
||||
|
||||
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;
|
||||
|
@ -19,6 +19,23 @@
|
||||
@include transform(translate3d(0, 100%, 0));
|
||||
}
|
||||
|
||||
@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;
|
||||
--width: 100%;
|
||||
@ -26,17 +43,34 @@
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
:host(.modal-card) .modal-shadow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host(.modal-card) ion-backdrop {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host(.modal-card) .modal-wrapper {
|
||||
@include border-radius($modal-ios-border-radius, $modal-ios-border-radius, 0, 0);
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
@supports (width: max(0px, 1px)) {
|
||||
:host(.modal-card) .modal-wrapper {
|
||||
height: calc(100% - max(30px, var(--ion-safe-area-top)) - 10px);
|
||||
@media screen and (min-width: 768px) {
|
||||
:host {
|
||||
--width: calc(100% - 120px);
|
||||
--height: calc(100% - (120px + var(--ion-safe-area-top) + var(--ion-safe-area-bottom)));
|
||||
--max-width: 720px;
|
||||
--max-height: 1000px;
|
||||
}
|
||||
|
||||
: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;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
.modal-wrapper,
|
||||
.modal-shadow {
|
||||
@include border-radius(var(--border-radius));
|
||||
|
||||
width: var(--width);
|
||||
@ -74,6 +75,12 @@
|
||||
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) {
|
||||
:host {
|
||||
--width: #{$modal-inset-width};
|
||||
|
@ -272,6 +272,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
onIonModalDidDismiss={this.onLifecycle}
|
||||
>
|
||||
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}/>
|
||||
|
||||
{mode === 'ios' && <div class="modal-shadow"></div>}
|
||||
<div
|
||||
role="dialog"
|
||||
class="modal-wrapper"
|
||||
|
@ -78,7 +78,7 @@
|
||||
.fromTo('opacity', '0.01', '0.4');
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('.modal-wrapper'))
|
||||
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
||||
.keyframes([
|
||||
{ offset: 0, opacity: '0', transform: 'scale(0)' },
|
||||
{ offset: 1, opacity: '0.99', transform: 'scale(1)' }
|
||||
@ -97,7 +97,7 @@
|
||||
.fromTo('opacity', '0.4', '0.01');
|
||||
|
||||
const wrapperAnimation = createAnimation()
|
||||
.addElement(baseEl.querySelector('.modal-wrapper'))
|
||||
.addElement(baseEl.querySelectorAll('.modal-wrapper, .modal-shadow'))
|
||||
.keyframes([
|
||||
{ offset: 0, opacity: '0.99', transform: 'scale(1)' },
|
||||
{ offset: 1, opacity: '0', transform: 'scale(0)' }
|
||||
|
Reference in New Issue
Block a user