diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index 05cf4f68a717..600e793bc3d3 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -16,6 +16,7 @@ import {MenuPositionX, MenuPositionY} from './menu-positions'; import {MdMenuInvalidPositionX, MdMenuInvalidPositionY} from './menu-errors'; import {MdMenuItem} from './menu-item'; import {ListKeyManager} from '../core/a11y/list-key-manager'; +import {MdMenuPanel} from './menu-panel'; @Component({ moduleId: module.id, @@ -26,7 +27,7 @@ import {ListKeyManager} from '../core/a11y/list-key-manager'; encapsulation: ViewEncapsulation.None, exportAs: 'mdMenu' }) -export class MdMenu { +export class MdMenu implements MdMenuPanel { private _keyManager: ListKeyManager; // config object to be passed into the menu's ngClass @@ -70,7 +71,7 @@ export class MdMenu { * to focus the first item when the menu is opened by the ENTER key. * TODO: internal */ - _focusFirstItem() { + focusFirstItem() { // The menu always opens with the first item focused. this.items.first.focus(); this._keyManager.focusedItemIndex = 0; diff --git a/src/lib/menu/menu-panel.ts b/src/lib/menu/menu-panel.ts new file mode 100644 index 000000000000..0d439cfefe5f --- /dev/null +++ b/src/lib/menu/menu-panel.ts @@ -0,0 +1,10 @@ +import {EventEmitter, TemplateRef} from '@angular/core'; +import {MenuPositionX, MenuPositionY} from './menu-positions'; + +export interface MdMenuPanel { + positionX: MenuPositionX; + positionY: MenuPositionY; + templateRef: TemplateRef; + close: EventEmitter; + focusFirstItem: () => void; +} diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index d84c12b32435..7239c74dcb8a 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -10,7 +10,7 @@ import { OnDestroy, Renderer } from '@angular/core'; -import {MdMenu} from './menu-directive'; +import {MdMenuPanel} from './menu-panel'; import {MdMenuMissingError} from './menu-errors'; import { ENTER, @@ -47,7 +47,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { // the first item of the list when the menu is opened via the keyboard private _openedFromKeyboard: boolean = false; - @Input('md-menu-trigger-for') menu: MdMenu; + @Input('md-menu-trigger-for') menu: MdMenuPanel; @Output() onMenuOpen = new EventEmitter(); @Output() onMenuClose = new EventEmitter(); @@ -120,7 +120,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { this._setIsMenuOpen(true); if (this._openedFromKeyboard) { - this.menu._focusFirstItem(); + this.menu.focusFirstItem(); } }; @@ -148,7 +148,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { * md-menu-trigger-for. If not, an exception is thrown. */ private _checkMenu() { - if (!this.menu || !(this.menu instanceof MdMenu)) { + if (!this.menu) { throw new MdMenuMissingError(); } } diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index f702bd81b75a..737531ae0063 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -1,6 +1,18 @@ import {TestBed, async} from '@angular/core/testing'; -import {Component, ViewChild} from '@angular/core'; -import {MdMenuModule, MdMenuTrigger} from './menu'; +import { + Component, + EventEmitter, + Output, + TemplateRef, + ViewChild +} from '@angular/core'; +import { + MdMenuModule, + MdMenuTrigger, + MdMenuPanel, + MenuPositionX, + MenuPositionY +} from './menu'; import {OverlayContainer} from '../core/overlay/overlay-container'; @@ -10,7 +22,7 @@ describe('MdMenu', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MdMenuModule.forRoot()], - declarations: [SimpleMenu], + declarations: [CustomMenuPanel, CustomMenu, SimpleMenu], providers: [ {provide: OverlayContainer, useFactory: () => { overlayContainerElement = document.createElement('div'); @@ -30,7 +42,7 @@ describe('MdMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.componentInstance.trigger.openMenu(); - expect(overlayContainerElement.textContent.trim()).toBe('Content'); + expect(overlayContainerElement.textContent.trim()).toBe('Simple Content'); }).not.toThrowError(); }); @@ -46,16 +58,59 @@ describe('MdMenu', () => { expect(overlayContainerElement.textContent).toBe(''); }); + it('should open a custom menu', () => { + const fixture = TestBed.createComponent(CustomMenu); + fixture.detectChanges(); + expect(overlayContainerElement.textContent).toBe(''); + expect(() => { + fixture.componentInstance.trigger.openMenu(); + fixture.componentInstance.trigger.openMenu(); + + expect(overlayContainerElement.textContent).toContain('Custom Menu header'); + expect(overlayContainerElement.textContent).toContain('Custom Content'); + }).not.toThrowError(); + }); + }); @Component({ template: ` - + ` }) class SimpleMenu { @ViewChild(MdMenuTrigger) trigger: MdMenuTrigger; } + +@Component({ + selector: 'custom-menu', + template: ` + + `, + exportAs: 'mdCustomMenu' +}) +class CustomMenuPanel implements MdMenuPanel { + positionX: MenuPositionX = 'after'; + positionY: MenuPositionY = 'below'; + @ViewChild(TemplateRef) templateRef: TemplateRef; + @Output() close = new EventEmitter(); + focusFirstItem: () => void; +} + +@Component({ + template: ` + + + + + ` +}) +class CustomMenu { + @ViewChild(MdMenuTrigger) trigger: MdMenuTrigger; +} diff --git a/src/lib/menu/menu.ts b/src/lib/menu/menu.ts index 8f54ae91cb95..2ebd65dc9774 100644 --- a/src/lib/menu/menu.ts +++ b/src/lib/menu/menu.ts @@ -7,6 +7,8 @@ import {MdMenuTrigger} from './menu-trigger'; export {MdMenu} from './menu-directive'; export {MdMenuItem} from './menu-item'; export {MdMenuTrigger} from './menu-trigger'; +export {MdMenuPanel} from './menu-panel'; +export {MenuPositionX, MenuPositionY} from './menu-positions'; @NgModule({