Skip to content

Commit a3a1d35

Browse files
committed
feat: block scrolling as part of the overlay implementation
This commit removes the blocking behavior as part of the backdrop. Developers with custom implementations using ion-backdrop that want scroll blocking will either need to use Ionic's gesture controller API or manually add the backdrop-no-scroll class to the body when their overlay is presented and remove it when dismissed.
1 parent 55bb326 commit a3a1d35

File tree

6 files changed

+53
-33
lines changed

6 files changed

+53
-33
lines changed

core/src/components.d.ts

-2
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ export namespace Components {
345345
"type": 'submit' | 'reset' | 'button';
346346
}
347347
interface IonBackdrop {
348-
"block": () => Promise<void>;
349348
/**
350349
* If `true`, the backdrop will stop propagation on tap.
351350
*/
@@ -354,7 +353,6 @@ export namespace Components {
354353
* If `true`, the backdrop will can be clicked and will emit the `ionBackdropTap` event.
355354
*/
356355
"tappable": boolean;
357-
"unblock": () => Promise<void>;
358356
/**
359357
* If `true`, the backdrop will be visible.
360358
*/

core/src/components/backdrop/backdrop.tsx

+1-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ComponentInterface, EventEmitter } from '@stencil/core';
2-
import { Component, Element, Event, Host, Listen, Method, Prop, h } from '@stencil/core';
3-
import { GESTURE_CONTROLLER } from '@utils/gesture';
2+
import { Component, Element, Event, Host, Listen, Prop, h } from '@stencil/core';
43

54
import { getIonMode } from '../../global/ionic-global';
65

@@ -13,10 +12,6 @@ import { getIonMode } from '../../global/ionic-global';
1312
shadow: true,
1413
})
1514
export class Backdrop implements ComponentInterface {
16-
private blocker = GESTURE_CONTROLLER.createBlocker({
17-
disableScroll: true,
18-
});
19-
2015
/**
2116
* If `true`, the backdrop will be visible.
2217
*/
@@ -39,22 +34,6 @@ export class Backdrop implements ComponentInterface {
3934

4035
@Element() el!: HTMLElement;
4136

42-
/** @internal */
43-
@Method()
44-
async block() {
45-
if (this.stopPropagation) {
46-
this.blocker.block();
47-
}
48-
}
49-
50-
/** @internal */
51-
@Method()
52-
async unblock() {
53-
if (this.stopPropagation) {
54-
this.blocker.unblock();
55-
}
56-
}
57-
5837
@Listen('click', { passive: false, capture: true })
5938
protected onMouseDown(ev: TouchEvent) {
6039
this.emitTap(ev);

core/src/utils/gesture/gesture-controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ class GestureDelegate {
185185
}
186186
}
187187

188-
class BlockerDelegate {
188+
export class BlockerDelegate {
189189
private ctrl?: GestureController;
190190

191191
constructor(

core/src/utils/overlays-interface.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,30 @@ import type { EventEmitter } from '@stencil/core';
22

33
import type { AnimationBuilder, HTMLStencilElement } from '../interface';
44

5+
import type { BlockerDelegate } from './gesture/gesture-controller';
6+
57
export interface OverlayEventDetail<T = any> {
68
data?: T;
79
role?: string;
810
}
911

1012
export interface OverlayInterface {
1113
el: HTMLIonOverlayElement;
12-
backdropEl?: HTMLIonBackdropElement;
1314
animated: boolean;
1415
keyboardClose: boolean;
1516
overlayIndex: number;
1617
presented: boolean;
1718

19+
/**
20+
* Reference to the ion-backdrop element in the overlay shadow DOM.
21+
*/
22+
backdropEl?: HTMLIonBackdropElement;
23+
/**
24+
* Instance of the gesture controller, used to enable and disable scrolling
25+
* on the main content when the overlay is presented and dismissed.
26+
*/
27+
blocker?: BlockerDelegate;
28+
1829
enterAnimation?: AnimationBuilder;
1930
leaveAnimation?: AnimationBuilder;
2031

core/src/utils/overlays.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
} from '../interface';
2121

2222
import { CoreDelegate } from './framework-delegate';
23+
import { GESTURE_CONTROLLER } from './gesture/gesture-controller';
2324
import { OVERLAY_BACK_BUTTON_PRIORITY } from './hardware-back-button';
2425
import { addEventListener, componentOnReady, focusElement, getElementRoot, removeEventListener } from './helpers';
2526
import { printIonWarning } from './logging';
@@ -472,7 +473,12 @@ export const present = async <OverlayPresentOptions>(
472473
setRootAriaHidden(true);
473474

474475
if (overlay.backdropEl) {
475-
overlay.backdropEl.block();
476+
// Scrolling should only be disabled if the overlay has a backdrop
477+
overlay.blocker = GESTURE_CONTROLLER.createBlocker({
478+
disableScroll: true,
479+
});
480+
481+
overlay.blocker.block();
476482
}
477483

478484
overlay.presented = true;
@@ -553,14 +559,11 @@ export const dismiss = async <OverlayDismissOptions>(
553559
return false;
554560
}
555561

556-
let isLastVisibleOverlay = false;
557-
558562
/**
559563
* If this is the last visible overlay then
560564
* we want to re-add the root to the accessibility tree.
561565
*/
562566
if (doc !== undefined && getPresentedOverlays(doc).length === 1) {
563-
isLastVisibleOverlay = true;
564567
setRootAriaHidden(false);
565568
}
566569

@@ -582,9 +585,8 @@ export const dismiss = async <OverlayDismissOptions>(
582585
await overlayAnimation(overlay, animationBuilder, overlay.el, opts);
583586
}
584587

585-
if (overlay.backdropEl && isLastVisibleOverlay) {
586-
// If the dismissed overlay is the last visible one, re-enable scrolling
587-
overlay.backdropEl.unblock();
588+
if (overlay.blocker) {
589+
overlay.blocker.destroy();
588590
}
589591

590592
overlay.didDismiss.emit({ data, role });

core/src/utils/test/overlays/overlays.spec.ts

+30
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,34 @@ describe('overlays: scroll blocking', () => {
184184

185185
expect(body.classList.contains('backdrop-no-scroll')).toEqual(false);
186186
});
187+
188+
it('should not enable scroll until last overlay is dismissed', async () => {
189+
const page = await newSpecPage({
190+
components: [Modal, Backdrop],
191+
html: `
192+
<ion-modal id="one"></ion-modal>
193+
<ion-modal id="two"></ion-modal>
194+
`,
195+
});
196+
197+
const modalOne = page.body.querySelector('#one')!;
198+
const modalTwo = page.body.querySelector('#two')!;
199+
const body = page.doc.querySelector('body')!;
200+
201+
await modalOne.present();
202+
203+
expect(body.classList.contains('backdrop-no-scroll')).toEqual(true);
204+
205+
await modalTwo.present();
206+
207+
expect(body.classList.contains('backdrop-no-scroll')).toEqual(true);
208+
209+
await modalOne.dismiss();
210+
211+
expect(body.classList.contains('backdrop-no-scroll')).toEqual(true);
212+
213+
await modalTwo.dismiss();
214+
215+
expect(body.classList.contains('backdrop-no-scroll')).toEqual(false);
216+
});
187217
});

0 commit comments

Comments
 (0)