Skip to content

Commit b52ee9a

Browse files
committed
fix(overlay): backdrop blocking element scroll
Fixes a long-standing where the backdrop doesn't allow the user to scroll, if the main scrollable container is not the body. Fixes #6927.
1 parent 24f0471 commit b52ee9a

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

src/cdk/overlay/overlay-ref.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import {OverlayConfig} from './overlay-config';
1212
import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher';
1313
import {Observable} from 'rxjs/Observable';
1414
import {Subject} from 'rxjs/Subject';
15+
import {fromEvent} from 'rxjs/observable/fromEvent';
1516
import {first} from 'rxjs/operators/first';
17+
import {debounceTime} from 'rxjs/operators/debounceTime';
18+
import {tap} from 'rxjs/operators/tap';
19+
import {BlockScrollStrategy} from './scroll/index';
1620

1721

1822
/**
@@ -256,6 +260,19 @@ export class OverlayRef implements PortalOutlet {
256260
// action desired when such a click occurs (usually closing the overlay).
257261
this._backdropElement.addEventListener('click', () => this._backdropClick.next(null));
258262

263+
if (!(this._config.scrollStrategy instanceof BlockScrollStrategy)) {
264+
// When the user starts scrolling by mouse, disable pointer events on the backdrop. This
265+
// allows for non-body scroll containers (e.g. a sidenav container), which would normally
266+
// be blocked due to the backdrop, to scroll. When the user has stopped scrolling for 100ms
267+
// restore the pointer events in order for the click handler to work.
268+
this._ngZone.runOutsideAngular(() => {
269+
fromEvent(this._backdropElement!, 'wheel').pipe(
270+
tap(() => this._backdropElement!.style.pointerEvents = 'none'),
271+
debounceTime(100)
272+
).subscribe(() => this._backdropElement!.style.pointerEvents = '');
273+
});
274+
}
275+
259276
// Add class to fade-in the backdrop after one frame.
260277
requestAnimationFrame(() => {
261278
if (this._backdropElement) {

src/cdk/overlay/overlay.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {async, fakeAsync, tick, ComponentFixture, inject, TestBed} from '@angular/core/testing';
22
import {Component, NgModule, ViewChild, ViewContainerRef} from '@angular/core';
3+
import {dispatchFakeEvent} from '@angular/cdk/testing';
34
import {
45
ComponentPortal,
56
PortalModule,
@@ -429,6 +430,40 @@ describe('Overlay', () => {
429430
.toBeLessThan(children.indexOf(pane), 'Expected backdrop to be before the pane in the DOM');
430431
});
431432

433+
it('should disable pointer events on the backdrop when scrolling', fakeAsync(() => {
434+
let overlayRef = overlay.create(config);
435+
overlayRef.attach(componentPortal);
436+
437+
viewContainerFixture.detectChanges();
438+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
439+
440+
expect(backdrop.style.pointerEvents).toBeFalsy();
441+
442+
dispatchFakeEvent(backdrop, 'wheel');
443+
444+
expect(backdrop.style.pointerEvents).toBe('none');
445+
446+
tick(100);
447+
448+
expect(backdrop.style.pointerEvents).toBeFalsy();
449+
}));
450+
451+
it('should not disable pointer events on the backdrop when scrolling is blocked', () => {
452+
config.scrollStrategy = overlay.scrollStrategies.block();
453+
454+
let overlayRef = overlay.create(config);
455+
overlayRef.attach(componentPortal);
456+
457+
viewContainerFixture.detectChanges();
458+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
459+
460+
expect(backdrop.style.pointerEvents).toBeFalsy();
461+
462+
dispatchFakeEvent(backdrop, 'wheel');
463+
464+
expect(backdrop.style.pointerEvents).toBeFalsy();
465+
});
466+
432467
});
433468

434469
describe('panelClass', () => {

0 commit comments

Comments
 (0)