Skip to content

Commit 6040d03

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 9673f63 commit 6040d03

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

src/cdk/overlay/overlay-ref.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {PortalHost, Portal} from '@angular/cdk/portal';
1111
import {OverlayConfig} from './overlay-config';
1212
import {Observable} from 'rxjs/Observable';
1313
import {Subject} from 'rxjs/Subject';
14-
import {first} from 'rxjs/operator/first';
14+
import {fromEvent} from 'rxjs/observable/fromEvent';
15+
import {RxChain, debounceTime, first, doOperator} from '@angular/cdk/rxjs';
16+
import {BlockScrollStrategy} from './scroll/index';
1517

1618

1719
/**
@@ -230,6 +232,19 @@ export class OverlayRef implements PortalHost {
230232
// action desired when such a click occurs (usually closing the overlay).
231233
this._backdropElement.addEventListener('click', () => this._backdropClick.next(null));
232234

235+
if (!(this._config.scrollStrategy instanceof BlockScrollStrategy)) {
236+
// When the user starts scrolling by mouse, disable pointer events on the backdrop. This
237+
// allows for non-body scroll containers (e.g. a sidenav container), which would normally
238+
// be blocked due to the backdrop, to scroll. When the user has stopped scrolling for 100ms
239+
// restore the pointer events in order for the click handler to work.
240+
this._ngZone.runOutsideAngular(() => {
241+
RxChain.from(fromEvent(this._backdropElement!, 'wheel'))
242+
.call(doOperator, () => this._backdropElement!.style.pointerEvents = 'none')
243+
.call(debounceTime, 100)
244+
.subscribe(() => this._backdropElement!.style.pointerEvents = '');
245+
});
246+
}
247+
233248
// Add class to fade-in the backdrop after one frame.
234249
requestAnimationFrame(() => {
235250
if (this._backdropElement) {

src/cdk/overlay/overlay.spec.ts

+35
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,
@@ -409,6 +410,40 @@ describe('Overlay', () => {
409410
.toBeLessThan(children.indexOf(pane), 'Expected backdrop to be before the pane in the DOM');
410411
});
411412

413+
it('should disable pointer events on the backdrop when scrolling', fakeAsync(() => {
414+
let overlayRef = overlay.create(config);
415+
overlayRef.attach(componentPortal);
416+
417+
viewContainerFixture.detectChanges();
418+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
419+
420+
expect(backdrop.style.pointerEvents).toBeFalsy();
421+
422+
dispatchFakeEvent(backdrop, 'wheel');
423+
424+
expect(backdrop.style.pointerEvents).toBe('none');
425+
426+
tick(100);
427+
428+
expect(backdrop.style.pointerEvents).toBeFalsy();
429+
}));
430+
431+
it('should not disable pointer events on the backdrop when scrolling is blocked', () => {
432+
config.scrollStrategy = overlay.scrollStrategies.block();
433+
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).toBeFalsy();
445+
});
446+
412447
});
413448

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

0 commit comments

Comments
 (0)