Skip to content

Commit 737df91

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 4bf3ceb commit 737df91

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

src/cdk/overlay/overlay-ref.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
import {Direction} from '@angular/cdk/bidi';
1010
import {ComponentPortal, Portal, PortalOutlet, TemplatePortal} from '@angular/cdk/portal';
1111
import {ComponentRef, EmbeddedViewRef, NgZone} from '@angular/core';
12-
import {Observable, Subject} from 'rxjs';
13-
import {take} from 'rxjs/operators';
12+
import {Observable, Subject, fromEvent} from 'rxjs';
13+
import {take, tap, debounceTime} from 'rxjs/operators';
1414
import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher';
1515
import {OverlayConfig} from './overlay-config';
1616
import {coerceCssPixelValue, coerceArray} from '@angular/cdk/coercion';
17+
import {BlockScrollStrategy} from './scroll/index';
1718

1819

1920
/** An object where all of its properties cannot be written. */
@@ -301,6 +302,19 @@ export class OverlayRef implements PortalOutlet {
301302
this._backdropElement.addEventListener('click',
302303
(event: MouseEvent) => this._backdropClick.next(event));
303304

305+
if (!(this._config.scrollStrategy instanceof BlockScrollStrategy)) {
306+
// When the user starts scrolling by mouse, disable pointer events on the backdrop. This
307+
// allows for non-body scroll containers (e.g. a sidenav container), which would normally
308+
// be blocked due to the backdrop, to scroll. When the user has stopped scrolling for 100ms
309+
// restore the pointer events in order for the click handler to work.
310+
this._ngZone.runOutsideAngular(() => {
311+
fromEvent(this._backdropElement!, 'wheel').pipe(
312+
tap(() => this._backdropElement!.style.pointerEvents = 'none'),
313+
debounceTime(100)
314+
).subscribe(() => this._backdropElement!.style.pointerEvents = '');
315+
});
316+
}
317+
304318
// Add class to fade-in the backdrop after one frame.
305319
if (typeof requestAnimationFrame !== 'undefined') {
306320
this._ngZone.runOutsideAngular(() => {

src/cdk/overlay/overlay.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
PositionStrategy,
2424
ScrollStrategy,
2525
} from './index';
26+
import {dispatchFakeEvent} from '@angular/cdk/testing';
2627

2728

2829
describe('Overlay', () => {
@@ -545,6 +546,40 @@ describe('Overlay', () => {
545546
.toBeLessThan(children.indexOf(host), 'Expected backdrop to be before the host in the DOM');
546547
});
547548

549+
it('should disable pointer events on the backdrop when scrolling', fakeAsync(() => {
550+
let overlayRef = overlay.create(config);
551+
overlayRef.attach(componentPortal);
552+
553+
viewContainerFixture.detectChanges();
554+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
555+
556+
expect(backdrop.style.pointerEvents).toBeFalsy();
557+
558+
dispatchFakeEvent(backdrop, 'wheel');
559+
560+
expect(backdrop.style.pointerEvents).toBe('none');
561+
562+
tick(100);
563+
564+
expect(backdrop.style.pointerEvents).toBeFalsy();
565+
}));
566+
567+
it('should not disable pointer events on the backdrop when scrolling is blocked', () => {
568+
config.scrollStrategy = overlay.scrollStrategies.block();
569+
570+
let overlayRef = overlay.create(config);
571+
overlayRef.attach(componentPortal);
572+
573+
viewContainerFixture.detectChanges();
574+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
575+
576+
expect(backdrop.style.pointerEvents).toBeFalsy();
577+
578+
dispatchFakeEvent(backdrop, 'wheel');
579+
580+
expect(backdrop.style.pointerEvents).toBeFalsy();
581+
});
582+
548583
});
549584

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

0 commit comments

Comments
 (0)