diff --git a/src/lib/core/overlay/overlay-ref.ts b/src/lib/core/overlay/overlay-ref.ts index 62b36cdf5389..b574a77da058 100644 --- a/src/lib/core/overlay/overlay-ref.ts +++ b/src/lib/core/overlay/overlay-ref.ts @@ -97,22 +97,23 @@ export class OverlayRef implements PortalHost { /** Attaches a backdrop for this overlay. */ private _attachBackdrop() { - this._backdropElement = document.createElement('div'); - this._backdropElement.classList.add('md-overlay-backdrop'); - this._backdropElement.classList.add(this._state.backdropClass); + let backdrop = this._backdropElement = document.createElement('div'); - this._pane.parentElement.appendChild(this._backdropElement); + backdrop.classList.add('md-overlay-backdrop'); + backdrop.classList.add(this._state.backdropClass); + + // Insert the backdrop before the pane in the DOM order, + // in order to handle stacked overlays properly. + this._pane.parentElement.insertBefore(backdrop, this._pane); // Forward backdrop clicks such that the consumer of the overlay can perform whatever // action desired when such a click occurs (usually closing the overlay). - this._backdropElement.addEventListener('click', () => { - this._backdropClick.next(null); - }); + backdrop.addEventListener('click', () => this._backdropClick.next(null)); // Add class to fade-in the backdrop after one frame. requestAnimationFrame(() => { - if (this._backdropElement) { - this._backdropElement.classList.add('md-overlay-backdrop-showing'); + if (backdrop) { + backdrop.classList.add('md-overlay-backdrop-showing'); } }); } diff --git a/src/lib/core/overlay/overlay.spec.ts b/src/lib/core/overlay/overlay.spec.ts index 119b4a75d67a..7ccda5811323 100644 --- a/src/lib/core/overlay/overlay.spec.ts +++ b/src/lib/core/overlay/overlay.spec.ts @@ -235,6 +235,22 @@ describe('Overlay', () => { expect(backdrop.style.pointerEvents).toBe('none'); }); + it('should insert the backdrop before the overlay pane in the DOM order', () => { + let overlayRef = overlay.create(config); + overlayRef.attach(componentPortal); + + viewContainerFixture.detectChanges(); + + let backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop'); + let pane = overlayContainerElement.querySelector('.md-overlay-pane'); + let children = Array.prototype.slice.call(overlayContainerElement.children); + + expect(children.indexOf(backdrop)).toBeGreaterThan(-1); + expect(children.indexOf(pane)).toBeGreaterThan(-1); + expect(children.indexOf(backdrop)) + .toBeLessThan(children.indexOf(pane), 'Expected backdrop to be before the pane in the DOM'); + }); + }); }); diff --git a/src/lib/core/style/_variables.scss b/src/lib/core/style/_variables.scss index 22567e9c34d1..6b8689c95eac 100644 --- a/src/lib/core/style/_variables.scss +++ b/src/lib/core/style/_variables.scss @@ -24,7 +24,7 @@ $z-index-drawer: 100 !default; // stacking context for all overlays. $md-z-index-overlay-container: 1000; $md-z-index-overlay: 1000; -$md-z-index-overlay-backdrop: 1; +$md-z-index-overlay-backdrop: 1000; // Global constants diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index a4e82efacc74..04fc232e615f 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -101,7 +101,7 @@ describe('MdMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0]; + const overlayPane = overlayContainerElement.querySelector('.md-overlay-pane'); expect(overlayPane.getAttribute('dir')).toEqual('rtl'); }); @@ -248,7 +248,7 @@ describe('MdMenu', () => { }); function getOverlayPane(): HTMLElement { - let pane = overlayContainerElement.children[0] as HTMLElement; + let pane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; pane.style.position = 'absolute'; return pane; } diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 856524937535..abbe3bfaa275 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -102,7 +102,7 @@ describe('MdSelect', () => { fixture.whenStable().then(() => { trigger.click(); fixture.detectChanges(); - const pane = overlayContainerElement.children[0] as HTMLElement; + const pane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; expect(pane.style.minWidth).toBe('200px'); }); })); @@ -559,7 +559,7 @@ describe('MdSelect', () => { * @param index The index of the option. */ function checkTriggerAlignedWithOption(index: number): void { - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; // We need to set the position to absolute, because the top/left positioning won't work // since the component CSS isn't included in the tests. @@ -597,7 +597,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // The panel should be scrolled to 0 because centering the option is not possible. @@ -614,7 +615,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // The panel should be scrolled to 0 because centering the option is not possible. @@ -631,7 +633,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // The selected option should be scrolled to the center of the panel. @@ -652,7 +655,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // The selected option should be scrolled to the max scroll position. @@ -685,7 +689,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // Scroll should adjust by the difference between the top space available (85px + 8px @@ -709,7 +714,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; const scrollContainer = overlayPane.querySelector('.md-select-panel'); // Scroll should adjust by the difference between the bottom space available @@ -734,7 +740,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; // We need to set the position to absolute, because the top/left positioning won't work // since the component CSS isn't included in the tests. @@ -766,7 +773,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; // We need to set the position to absolute, because the top/left positioning won't work // since the component CSS isn't included in the tests. @@ -855,7 +863,8 @@ describe('MdSelect', () => { fixture.detectChanges(); // CSS styles aren't in the tests, so position must be absolute to reflect top/left - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; overlayPane.style.position = 'absolute'; const triggerBottom = trigger.getBoundingClientRect().bottom; @@ -882,7 +891,8 @@ describe('MdSelect', () => { fixture.detectChanges(); // CSS styles aren't in the tests, so position must be absolute to reflect top/left - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; overlayPane.style.position = 'absolute'; const triggerTop = trigger.getBoundingClientRect().top; @@ -904,7 +914,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; // We need to set the position to absolute, because the top/left positioning won't work // since the component CSS isn't included in the tests. @@ -927,7 +938,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.children[0] as HTMLElement; + const overlayPane = + overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; // We need to set the position to absolute, because the top/left positioning won't work // since the component CSS isn't included in the tests. @@ -1168,7 +1180,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const pane = overlayContainerElement.children[0] as HTMLElement; + const pane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement; expect(pane.style.minWidth).toEqual('300px'); expect(fixture.componentInstance.select.panelOpen).toBe(true);