Skip to content

Commit b16031a

Browse files
crisbetojelbourn
authored andcommitted
fix(overlay): proper backdrop stacking with multiple overlays (#2276)
* fix(overlay): proper backdrop stacking with multiple overlays Currently backdrops get inserted after their corresponding overlays in the DOM. This can lead to situations where another overlay that is technically lower in the stacking order could go above a backdrop (e.g. opening a `select` inside a `dialog`). These changes switch to doing the stacking by having the overlay and backdrop have the same `z-index` and determining the stacking order by the order of the elements in the DOM. Fixes #2272. * Fix wrong selectors after merge with master.
1 parent 75cd73b commit b16031a

File tree

5 files changed

+50
-23
lines changed

5 files changed

+50
-23
lines changed

src/lib/core/overlay/overlay-ref.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ export class OverlayRef implements PortalHost {
101101
this._backdropElement.classList.add('cdk-overlay-backdrop');
102102
this._backdropElement.classList.add(this._state.backdropClass);
103103

104-
this._pane.parentElement.appendChild(this._backdropElement);
104+
// Insert the backdrop before the pane in the DOM order,
105+
// in order to handle stacked overlays properly.
106+
this._pane.parentElement.insertBefore(this._backdropElement, this._pane);
105107

106108
// Forward backdrop clicks such that the consumer of the overlay can perform whatever
107109
// action desired when such a click occurs (usually closing the overlay).
108-
this._backdropElement.addEventListener('click', () => {
109-
this._backdropClick.next(null);
110-
});
110+
this._backdropElement.addEventListener('click', () => this._backdropClick.next(null));
111111

112112
// Add class to fade-in the backdrop after one frame.
113113
requestAnimationFrame(() => {

src/lib/core/overlay/overlay.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,22 @@ describe('Overlay', () => {
235235
expect(backdrop.style.pointerEvents).toBe('none');
236236
});
237237

238+
it('should insert the backdrop before the overlay pane in the DOM order', () => {
239+
let overlayRef = overlay.create(config);
240+
overlayRef.attach(componentPortal);
241+
242+
viewContainerFixture.detectChanges();
243+
244+
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop');
245+
let pane = overlayContainerElement.querySelector('.cdk-overlay-pane');
246+
let children = Array.prototype.slice.call(overlayContainerElement.children);
247+
248+
expect(children.indexOf(backdrop)).toBeGreaterThan(-1);
249+
expect(children.indexOf(pane)).toBeGreaterThan(-1);
250+
expect(children.indexOf(backdrop))
251+
.toBeLessThan(children.indexOf(pane), 'Expected backdrop to be before the pane in the DOM');
252+
});
253+
238254
});
239255
});
240256

src/lib/core/style/_variables.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ $z-index-drawer: 100 !default;
2424
// stacking context for all overlays.
2525
$cdk-z-index-overlay-container: 1000;
2626
$cdk-z-index-overlay: 1000;
27-
$cdk-z-index-overlay-backdrop: 1;
27+
$cdk-z-index-overlay-backdrop: 1000;
2828

2929
// Background color for all of the backdrops
3030
$cdk-overlay-dark-backdrop-background: rgba(0, 0, 0, 0.6);
3131

32-
3332
// Global constants
3433
$pi: 3.14159265;
3534

src/lib/menu/menu.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('MdMenu', () => {
101101
fixture.componentInstance.trigger.openMenu();
102102
fixture.detectChanges();
103103

104-
const overlayPane = overlayContainerElement.children[0];
104+
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
105105
expect(overlayPane.getAttribute('dir')).toEqual('rtl');
106106
});
107107

@@ -248,7 +248,7 @@ describe('MdMenu', () => {
248248
});
249249

250250
function getOverlayPane(): HTMLElement {
251-
let pane = overlayContainerElement.children[0] as HTMLElement;
251+
let pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
252252
pane.style.position = 'absolute';
253253
return pane;
254254
}

src/lib/select/select.spec.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('MdSelect', () => {
104104
fixture.whenStable().then(() => {
105105
trigger.click();
106106
fixture.detectChanges();
107-
const pane = overlayContainerElement.children[0] as HTMLElement;
107+
const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
108108
expect(pane.style.minWidth).toBe('200px');
109109
});
110110
}));
@@ -561,7 +561,7 @@ describe('MdSelect', () => {
561561
* @param index The index of the option.
562562
*/
563563
function checkTriggerAlignedWithOption(index: number): void {
564-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
564+
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
565565

566566
// We need to set the position to absolute, because the top/left positioning won't work
567567
// since the component CSS isn't included in the tests.
@@ -599,7 +599,8 @@ describe('MdSelect', () => {
599599
trigger.click();
600600
fixture.detectChanges();
601601

602-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
602+
const overlayPane =
603+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
603604
const scrollContainer = overlayPane.querySelector('.md-select-panel');
604605

605606
// The panel should be scrolled to 0 because centering the option is not possible.
@@ -616,7 +617,8 @@ describe('MdSelect', () => {
616617
trigger.click();
617618
fixture.detectChanges();
618619

619-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
620+
const overlayPane =
621+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
620622
const scrollContainer = overlayPane.querySelector('.md-select-panel');
621623

622624
// The panel should be scrolled to 0 because centering the option is not possible.
@@ -633,7 +635,8 @@ describe('MdSelect', () => {
633635
trigger.click();
634636
fixture.detectChanges();
635637

636-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
638+
const overlayPane =
639+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
637640
const scrollContainer = overlayPane.querySelector('.md-select-panel');
638641

639642
// The selected option should be scrolled to the center of the panel.
@@ -654,7 +657,8 @@ describe('MdSelect', () => {
654657
trigger.click();
655658
fixture.detectChanges();
656659

657-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
660+
const overlayPane =
661+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
658662
const scrollContainer = overlayPane.querySelector('.md-select-panel');
659663

660664
// The selected option should be scrolled to the max scroll position.
@@ -687,7 +691,8 @@ describe('MdSelect', () => {
687691
trigger.click();
688692
fixture.detectChanges();
689693

690-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
694+
const overlayPane =
695+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
691696
const scrollContainer = overlayPane.querySelector('.md-select-panel');
692697

693698
// Scroll should adjust by the difference between the top space available (85px + 8px
@@ -711,7 +716,8 @@ describe('MdSelect', () => {
711716
trigger.click();
712717
fixture.detectChanges();
713718

714-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
719+
const overlayPane =
720+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
715721
const scrollContainer = overlayPane.querySelector('.md-select-panel');
716722

717723
// Scroll should adjust by the difference between the bottom space available
@@ -736,7 +742,8 @@ describe('MdSelect', () => {
736742
trigger.click();
737743
fixture.detectChanges();
738744

739-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
745+
const overlayPane =
746+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
740747

741748
// We need to set the position to absolute, because the top/left positioning won't work
742749
// since the component CSS isn't included in the tests.
@@ -768,7 +775,8 @@ describe('MdSelect', () => {
768775
trigger.click();
769776
fixture.detectChanges();
770777

771-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
778+
const overlayPane =
779+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
772780

773781
// We need to set the position to absolute, because the top/left positioning won't work
774782
// since the component CSS isn't included in the tests.
@@ -857,7 +865,8 @@ describe('MdSelect', () => {
857865
fixture.detectChanges();
858866

859867
// CSS styles aren't in the tests, so position must be absolute to reflect top/left
860-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
868+
const overlayPane =
869+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
861870
overlayPane.style.position = 'absolute';
862871

863872
const triggerBottom = trigger.getBoundingClientRect().bottom;
@@ -884,7 +893,8 @@ describe('MdSelect', () => {
884893
fixture.detectChanges();
885894

886895
// CSS styles aren't in the tests, so position must be absolute to reflect top/left
887-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
896+
const overlayPane =
897+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
888898
overlayPane.style.position = 'absolute';
889899

890900
const triggerTop = trigger.getBoundingClientRect().top;
@@ -906,7 +916,8 @@ describe('MdSelect', () => {
906916
trigger.click();
907917
fixture.detectChanges();
908918

909-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
919+
const overlayPane =
920+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
910921

911922
// We need to set the position to absolute, because the top/left positioning won't work
912923
// since the component CSS isn't included in the tests.
@@ -929,7 +940,8 @@ describe('MdSelect', () => {
929940
trigger.click();
930941
fixture.detectChanges();
931942

932-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
943+
const overlayPane =
944+
overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
933945

934946
// We need to set the position to absolute, because the top/left positioning won't work
935947
// since the component CSS isn't included in the tests.
@@ -1170,7 +1182,7 @@ describe('MdSelect', () => {
11701182
trigger.click();
11711183
fixture.detectChanges();
11721184

1173-
const pane = overlayContainerElement.children[0] as HTMLElement;
1185+
const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
11741186
expect(pane.style.minWidth).toEqual('300px');
11751187

11761188
expect(fixture.componentInstance.select.panelOpen).toBe(true);

0 commit comments

Comments
 (0)