fix(modal): backdropBreakpoint allows interactivity behind sheet (#24798)

resolves #24797
This commit is contained in:
Liam DeBeasi
2022-02-16 11:25:08 -05:00
committed by GitHub
parent 866f261f07
commit fca3f56ca4
8 changed files with 89 additions and 14 deletions

View File

@ -7,7 +7,11 @@ import { createSheetEnterAnimation } from './sheet';
const createEnterAnimation = () => {
const backdropAnimation = createAnimation()
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)');
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
.beforeStyles({
'pointer-events': 'none'
})
.afterClearStyles(['pointer-events']);
const wrapperAnimation = createAnimation()
.fromTo('transform', 'translateY(100vh)', 'translateY(0vh)');
@ -27,11 +31,7 @@ export const iosEnterAnimation = (
const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
backdropAnimation
.addElement(root.querySelector('ion-backdrop')!)
.beforeStyles({
'pointer-events': 'none'
})
.afterClearStyles(['pointer-events']);
.addElement(root.querySelector('ion-backdrop')!);
wrapperAnimation
.addElement(root.querySelectorAll('.modal-wrapper, .modal-shadow')!)

View File

@ -6,7 +6,11 @@ import { createSheetEnterAnimation } from './sheet';
const createEnterAnimation = () => {
const backdropAnimation = createAnimation()
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)');
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
.beforeStyles({
'pointer-events': 'none'
})
.afterClearStyles(['pointer-events']);
const wrapperAnimation = createAnimation()
.keyframes([
@ -29,11 +33,7 @@ export const mdEnterAnimation = (
const { wrapperAnimation, backdropAnimation } = currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation();
backdropAnimation
.addElement(root.querySelector('ion-backdrop')!)
.beforeStyles({
'pointer-events': 'none'
})
.afterClearStyles(['pointer-events']);
.addElement(root.querySelector('ion-backdrop')!);
wrapperAnimation
.addElement(root.querySelector('.modal-wrapper')!);

View File

@ -16,6 +16,14 @@ export const createSheetEnterAnimation = (opts: ModalAnimationOptions) => {
const backdropAnimation = createAnimation('backdropAnimation')
.fromTo('opacity', 0, initialBackdrop);
if (shouldShowBackdrop) {
backdropAnimation
.beforeStyles({
'pointer-events': 'none'
})
.afterClearStyles(['pointer-events']);
}
const wrapperAnimation = createAnimation('wrapperAnimation')
.keyframes([
{ offset: 0, opacity: 1, transform: 'translateY(100%)' },

View File

@ -56,10 +56,14 @@ export const createSheetGesture = (
animation.progressStart(true, 1 - currentBreakpoint);
/**
* Backdrop should become enabled
* after the backdropBreakpoint value
* If backdrop is not enabled, then content
* behind modal should be clickable. To do this, we need
* to remove pointer-events from ion-modal as a whole.
* ion-backdrop and .modal-wrapper always have pointer-events: auto
* applied, so the modal content can still be interacted with.
*/
const backdropEnabled = currentBreakpoint > backdropBreakpoint
baseEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
backdropEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
}
@ -187,6 +191,7 @@ export const createSheetGesture = (
* after the backdropBreakpoint value
*/
const backdropEnabled = currentBreakpoint > backdropBreakpoint;
baseEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
backdropEl.style.setProperty('pointer-events', backdropEnabled ? 'auto' : 'none');
gesture.enable(true);

View File

@ -50,6 +50,10 @@
contain: strict;
}
.modal-wrapper, ion-backdrop {
pointer-events: auto;
}
:host(.overlay-hidden) {
display: none;
}

View File

@ -84,3 +84,15 @@ test('modal: htmlAttributes', async () => {
expect(attribute).toEqual('basic-modal');
});
test('it should dismiss the modal when clicking the backdrop', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' });
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
await page.click('#basic-modal');
await ionModalDidPresent.next();
await page.mouse.click(20, 20);
await ionModalDidDismiss.next();
})

View File

@ -43,3 +43,47 @@ test('modal - open', async () => {
expect(screenshotCompare).toMatchScreenshot();
}
});
test('should click to dismiss sheet modal', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
await page.click('#sheet-modal');
const modal = await page.find('ion-modal');
await modal.waitForVisible();
await page.mouse.click(50, 50);
await ionModalDidDismiss.next();
});
test('should click to dismiss sheet modal when backdrop is active', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
await page.click('#backdrop-active');
const modal = await page.find('ion-modal');
await modal.waitForVisible();
await page.mouse.click(50, 50);
await ionModalDidDismiss.next();
});
test('should click to present another modal when backdrop is inactive', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/sheet?ionic:_testing=true' });
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#backdrop-inactive');
await ionModalDidPresent.next();
await page.click('#custom-height-modal');
await ionModalDidPresent.next();
const customModal = await page.find('.custom-height');
expect(customModal).not.toBe(null);
});

View File

@ -93,6 +93,8 @@
<ion-button id="custom-height-modal" onclick="presentModal({ cssClass: 'custom-height' })">Present Sheet Modal (Custom Height)</ion-button>
<ion-button id="custom-handle-modal" onclick="presentModal({ cssClass: 'custom-handle' })">Present Sheet Modal (Custom Handle)</ion-button>
<ion-button id="no-handle-modal" onclick="presentModal({ handle: false })">Present Sheet Modal (No Handle)</ion-button>
<ion-button id="backdrop-active" onclick="presentModal({ backdropBreakpoint: 0.3, initialBreakpoint: 0.5, breakpoints: [0.3, 0.5, 0.7, 1] })">Backdrop is active</ion-button>
<ion-button id="backdrop-inactive" onclick="presentModal({ cssClass: 'backdrop-inactive', backdropBreakpoint: 0.5, initialBreakpoint: 0.3, breakpoints: [0.3, 0.5, 0.7, 1] })">Backdrop is inactive</ion-button>
<div class="grid">
<div class="grid-item red"></div>