diff --git a/src/cdk/overlay/_overlay.scss b/src/cdk/overlay/_overlay.scss index 8582fd822471..07bafcee449e 100644 --- a/src/cdk/overlay/_overlay.scss +++ b/src/cdk/overlay/_overlay.scss @@ -90,6 +90,13 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; } } + .cdk-overlay-backdrop-transition-disabled { + // Reset the transition by overriding, rather than adding + // the `transition` conditionally, so we can cover the case + // where consumers specified their own transition. + transition: none; + } + .cdk-overlay-dark-backdrop { background: $cdk-overlay-dark-backdrop-background; } diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 4abf91a8c702..b04fda3c2ee4 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -55,6 +55,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference { }; }); + /** Whether animations are disabled for this overlay. */ + private readonly _animationsDisabled: boolean; + /** Stream of keydown events dispatched to this overlay. */ _keydownEvents = new Subject(); @@ -70,7 +73,12 @@ export class OverlayRef implements PortalOutlet, OverlayReference { private _keyboardDispatcher: OverlayKeyboardDispatcher, private _document: Document, // @breaking-change 8.0.0 `_location` parameter to be made required. - private _location?: Location) { + private _location?: Location, + /** + * @deprecated `animationMode` parameter to be made required. + * @breaking-change 8.0.0 + */ + animationMode?: string) { if (_config.scrollStrategy) { this._scrollStrategy = _config.scrollStrategy; @@ -78,6 +86,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference { } this._positionStrategy = _config.positionStrategy; + this._animationsDisabled = animationMode === 'NoopAnimations'; } /** The overlay's HTML element */ @@ -380,6 +389,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference { this._backdropElement = this._document.createElement('div'); this._backdropElement.classList.add('cdk-overlay-backdrop'); + if (this._animationsDisabled) { + this._backdropElement.classList.add('cdk-overlay-backdrop-transition-disabled'); + } + if (this._config.backdropClass) { this._toggleClasses(this._backdropElement, this._config.backdropClass, true); } @@ -449,20 +462,26 @@ export class OverlayRef implements PortalOutlet, OverlayReference { clearTimeout(timeoutId); }; - backdropToDetach.classList.remove('cdk-overlay-backdrop-showing'); + if (this._animationsDisabled) { + // When the animations are disabled via the `NoopAnimationsModule`, we can be + // fairly certain that they won't happen so we can remove the element immediately. + finishDetach(); + } else { + backdropToDetach.classList.remove('cdk-overlay-backdrop-showing'); - this._ngZone.runOutsideAngular(() => { - backdropToDetach!.addEventListener('transitionend', finishDetach); - }); + this._ngZone.runOutsideAngular(() => { + backdropToDetach!.addEventListener('transitionend', finishDetach); + }); - // If the backdrop doesn't have a transition, the `transitionend` event won't fire. - // In this case we make it unclickable and we try to remove it after a delay. - backdropToDetach.style.pointerEvents = 'none'; + // If the backdrop doesn't have a transition, the `transitionend` event won't fire. + // In this case we make it unclickable and we try to remove it after a delay. + backdropToDetach.style.pointerEvents = 'none'; - // Run this outside the Angular zone because there's nothing that Angular cares about. - // If it were to run inside the Angular zone, every test that used Overlay would have to be - // either async or fakeAsync. - timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500)); + // Run this outside the Angular zone because there's nothing that Angular cares about. + // If it were to run inside the Angular zone, every test that used Overlay would have to be + // either async or fakeAsync. + timeoutId = this._ngZone.runOutsideAngular(() => setTimeout(finishDetach, 500)); + } } /** Toggles a single CSS class or an array of classes on an element. */ diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index 0a3a54ce88a6..d92267fd45ba 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -29,6 +29,7 @@ import { ScrollStrategy, } from './index'; import {OverlayReference} from './overlay-reference'; +import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations'; describe('Overlay', () => { @@ -703,6 +704,64 @@ describe('Overlay', () => { expect(backdrop.classList).toContain('cdk-overlay-transparent-backdrop'); }); + it('should not disable the backdrop transition if animations are enabled', () => { + overlayContainer.ngOnDestroy(); + + TestBed + .resetTestingModule() + .configureTestingModule({ + imports: [OverlayModule, PortalModule, OverlayTestModule, BrowserAnimationsModule], + }) + .compileComponents(); + + inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => { + overlay = o; + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })(); + + let fixture = TestBed.createComponent(TestComponentWithTemplatePortals); + fixture.detectChanges(); + componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef); + viewContainerFixture = fixture; + + let overlayRef = overlay.create(config); + overlayRef.attach(componentPortal); + viewContainerFixture.detectChanges(); + + let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; + expect(backdrop.classList).not.toContain('cdk-overlay-backdrop-transition-disabled'); + }); + + it('should disable the backdrop transition when animations are disabled', () => { + overlayContainer.ngOnDestroy(); + + TestBed + .resetTestingModule() + .configureTestingModule({ + imports: [OverlayModule, PortalModule, OverlayTestModule, NoopAnimationsModule], + }) + .compileComponents(); + + inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => { + overlay = o; + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })(); + + let fixture = TestBed.createComponent(TestComponentWithTemplatePortals); + fixture.detectChanges(); + componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.viewContainerRef); + viewContainerFixture = fixture; + + let overlayRef = overlay.create(config); + overlayRef.attach(componentPortal); + viewContainerFixture.detectChanges(); + + let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; + expect(backdrop.classList).toContain('cdk-overlay-backdrop-transition-disabled'); + }); + it('should apply multiple custom classes to the overlay', () => { config.backdropClass = ['custom-class-1', 'custom-class-2']; diff --git a/src/cdk/overlay/overlay.ts b/src/cdk/overlay/overlay.ts index 04a6c26eaf0b..a8b62139d5cd 100644 --- a/src/cdk/overlay/overlay.ts +++ b/src/cdk/overlay/overlay.ts @@ -24,6 +24,7 @@ import {OverlayContainer} from './overlay-container'; import {OverlayRef} from './overlay-ref'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; import {ScrollStrategyOptions} from './scroll/index'; +import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; /** Next overlay unique ID. */ @@ -56,7 +57,12 @@ export class Overlay { @Inject(DOCUMENT) private _document: any, private _directionality: Directionality, // @breaking-change 8.0.0 `_location` parameter to be made required. - @Optional() private _location?: Location) { } + @Optional() private _location?: Location, + /** + * @deprecated `animationMode` parameter to be made required. + * @breaking-change 8.0.0 + */ + @Optional() @Inject(ANIMATION_MODULE_TYPE) private _animationMode?: string) { } /** * Creates an overlay. @@ -72,7 +78,7 @@ export class Overlay { overlayConfig.direction = overlayConfig.direction || this._directionality.value; return new OverlayRef(portalOutlet, host, pane, overlayConfig, this._ngZone, - this._keyboardDispatcher, this._document, this._location); + this._keyboardDispatcher, this._document, this._location, this._animationMode); } /** diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index ff7a0195697d..be5dc42fa38b 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -372,13 +372,16 @@ describe('MatDatepicker', () => { for (let i = 0; i < 3; i++) { testComponent.datepicker.open(); fixture.detectChanges(); + flush(); testComponent.datepicker.close(); fixture.detectChanges(); + flush(); } testComponent.datepicker.open(); fixture.detectChanges(); + flush(); const spy = jasmine.createSpy('close event spy'); const subscription = testComponent.datepicker.closedStream.subscribe(spy);