Skip to content

Commit df1d455

Browse files
committed
fix(menu): update to use overlay backdrop
1 parent 8f0aaea commit df1d455

File tree

6 files changed

+42
-30
lines changed

6 files changed

+42
-30
lines changed

e2e/components/menu/menu.e2e.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ describe('menu', () => {
1515
expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour");
1616
});
1717

18-
it('should close menu when area outside menu is clicked', () => {
19-
page.trigger().click();
20-
page.body().click();
21-
page.expectMenuPresent(false);
22-
});
23-
2418
it('should close menu when menu item is clicked', () => {
2519
page.trigger().click();
2620
page.items(0).click();

src/lib/menu/menu-directive.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {UP_ARROW, DOWN_ARROW, TAB} from '../core';
2727
exportAs: 'mdMenu'
2828
})
2929
export class MdMenu {
30-
_showClickCatcher: boolean = false;
3130
private _focusedItemIndex: number = 0;
3231

3332
// config object to be passed into the menu's ngClass
@@ -61,15 +60,6 @@ export class MdMenu {
6160

6261
@Output() close = new EventEmitter;
6362

64-
/**
65-
* This function toggles the display of the menu's click catcher element.
66-
* This element covers the viewport when the menu is open to detect clicks outside the menu.
67-
* TODO: internal
68-
*/
69-
_setClickCatcher(bool: boolean): void {
70-
this._showClickCatcher = bool;
71-
}
72-
7363
/**
7464
* Focus the first item in the menu. This method is used by the menu trigger
7565
* to focus the first item when the menu is opened by the ENTER key.

src/lib/menu/menu-trigger.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
HorizontalConnectionPos,
2424
VerticalConnectionPos
2525
} from '../core';
26+
import { Subscription } from 'rxjs/Subscription';
2627

2728
/**
2829
* This directive is intended to be used in conjunction with an md-menu tag. It is
@@ -40,6 +41,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
4041
private _portal: TemplatePortal;
4142
private _overlayRef: OverlayRef;
4243
private _menuOpen: boolean = false;
44+
private _backdropSubscription: Subscription;
4345

4446
// tracking input type is necessary so it's possible to only auto-focus
4547
// the first item of the list when the menu is opened via the keyboard
@@ -70,13 +72,15 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
7072
if (!this._menuOpen) {
7173
this._createOverlay();
7274
this._overlayRef.attach(this._portal);
75+
this._subscribeToBackdrop();
7376
this._initMenu();
7477
}
7578
}
7679

7780
closeMenu(): void {
7881
if (this._overlayRef) {
7982
this._overlayRef.detach();
83+
this._backdropSubscription.unsubscribe();
8084
this._resetMenu();
8185
}
8286
}
@@ -92,6 +96,15 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
9296
this._renderer.invokeElementMethod(this._element.nativeElement, 'focus');
9397
}
9498

99+
/**
100+
* This method ensures that the menu closes when the overlay backdrop is clicked.
101+
*/
102+
private _subscribeToBackdrop(): void {
103+
this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => {
104+
this.closeMenu();
105+
});
106+
}
107+
95108
/**
96109
* This method sets the menu state to open and focuses the first item if
97110
* the menu was opened via the keyboard.
@@ -120,7 +133,6 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
120133
// set state rather than toggle to support triggers sharing a menu
121134
private _setIsMenuOpen(isOpen: boolean): void {
122135
this._menuOpen = isOpen;
123-
this.menu._setClickCatcher(isOpen);
124136
this._menuOpen ? this.onMenuOpen.emit(null) : this.onMenuClose.emit(null);
125137
}
126138

@@ -152,6 +164,8 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
152164
private _getOverlayConfig(): OverlayState {
153165
const overlayState = new OverlayState();
154166
overlayState.positionStrategy = this._getPosition();
167+
overlayState.hasBackdrop = true;
168+
overlayState.backdropClass = 'md-overlay-transparent-backdrop';
155169
return overlayState;
156170
}
157171

src/lib/menu/menu.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
<ng-content></ng-content>
55
</div>
66
</template>
7-
<div class="md-menu-click-catcher" *ngIf="_showClickCatcher" (click)="_emitCloseEvent()"></div>
7+

src/lib/menu/menu.scss

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,4 @@ $md-menu-vertical-padding: 8px !default;
5252

5353
button[md-menu-item] {
5454
width: 100%;
55-
}
56-
57-
.md-menu-click-catcher {
58-
@include md-fullscreen();
59-
}
55+
}

src/lib/menu/menu.spec.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,58 @@
11
import {TestBed, async} from '@angular/core/testing';
22
import {Component, ViewChild} from '@angular/core';
3-
import {By} from '@angular/platform-browser';
43
import {MdMenuModule, MdMenuTrigger} from './menu';
4+
import {OverlayContainer} from '../core/overlay/overlay-container';
55

66

77
describe('MdMenu', () => {
8+
let overlayContainerElement: HTMLElement;
89

910
beforeEach(async(() => {
1011
TestBed.configureTestingModule({
1112
imports: [MdMenuModule.forRoot()],
1213
declarations: [SimpleMenu],
14+
providers: [
15+
{provide: OverlayContainer, useFactory: () => {
16+
overlayContainerElement = document.createElement('div');
17+
return {getContainerElement: () => overlayContainerElement};
18+
}}
19+
]
1320
});
1421

1522
TestBed.compileComponents();
1623
}));
1724

1825
it('should open the menu as an idempotent operation', () => {
19-
let fixture = TestBed.createComponent(SimpleMenu);
26+
const fixture = TestBed.createComponent(SimpleMenu);
2027
fixture.detectChanges();
21-
let menu = fixture.debugElement.query(By.css('.md-menu-panel'));
22-
expect(menu).toBe(null);
28+
expect(overlayContainerElement.textContent).toBe('');
2329
expect(() => {
2430
fixture.componentInstance.trigger.openMenu();
2531
fixture.componentInstance.trigger.openMenu();
2632

27-
menu = fixture.debugElement.query(By.css('.md-menu-panel'));
28-
expect(menu.nativeElement.innerHTML.trim()).toEqual('Content');
33+
expect(overlayContainerElement.textContent.trim()).toBe('Content');
2934
}).not.toThrowError();
3035
});
36+
37+
it('should close the menu when a click occurs outside the menu', () => {
38+
const fixture = TestBed.createComponent(SimpleMenu);
39+
fixture.detectChanges();
40+
fixture.componentInstance.trigger.openMenu();
41+
42+
const backdrop = <HTMLElement>overlayContainerElement.querySelector('.md-overlay-backdrop');
43+
backdrop.click();
44+
fixture.detectChanges();
45+
46+
expect(overlayContainerElement.textContent).toBe('');
47+
});
48+
3149
});
3250

3351
@Component({
3452
template: `
3553
<button [md-menu-trigger-for]="menu">Toggle menu</button>
3654
<md-menu #menu="mdMenu">
37-
Content
55+
<button md-menu-item> Content </button>
3856
</md-menu>
3957
`
4058
})

0 commit comments

Comments
 (0)