diff --git a/ng2-material/all.ts b/ng2-material/all.ts index 40a0e11d..55b1b612 100644 --- a/ng2-material/all.ts +++ b/ng2-material/all.ts @@ -54,13 +54,15 @@ export * from './components/switcher/switch'; import {MdSubheader} from "./components/subheader/subheader"; export * from './components/subheader/subheader'; +import {MdSidenav, SidenavService} from './components/sidenav/sidenav'; +export * from './components/sidenav/sidenav'; + import {MdToolbar} from './components/toolbar/toolbar'; export * from './components/toolbar/toolbar'; import {MdTabs, MdTab} from './components/tabs/tabs'; import {UrlResolver} from "angular2/compiler"; import {Media} from "./core/util/media"; -export * from './components/toolbar/toolbar'; /** * Collection of Material Design component directives. @@ -81,6 +83,7 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([ MdProgressLinear, MdProgressCircular, MdRadioButton, MdRadioGroup, + MdSidenav, MdSubheader, MdSwitch, MdToolbar, @@ -93,6 +96,7 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([ export const MATERIAL_PROVIDERS: any[] = [ MdDialog, Media, + SidenavService, MdRadioDispatcher, INPUT_VALIDATORS ]; diff --git a/ng2-material/components/sidenav/sidenav.scss b/ng2-material/components/sidenav/sidenav.scss index 2dca0037..3e88e612 100644 --- a/ng2-material/components/sidenav/sidenav.scss +++ b/ng2-material/components/sidenav/sidenav.scss @@ -6,9 +6,9 @@ $sidenav-mobile-width: 320px !default; $sidenav-desktop-width: 400px !default; $sidenav-min-space: 56px !default; -md-sidenav { +[md-sidenav] { box-sizing: border-box; - position: absolute; + position: fixed; flex-direction: column; z-index: $z-index-sidenav; @@ -97,13 +97,13 @@ md-sidenav { } @media screen and (min-width: $layout-breakpoint-sm) { - md-sidenav { + [md-sidenav] { max-width: $sidenav-desktop-width; } } @media screen and (max-width: $sidenav-desktop-width + $sidenav-min-space) { - md-sidenav { + [md-sidenav] { width: calc(100% - #{$sidenav-min-space}); min-width: calc(100% - #{$sidenav-min-space}); max-width: calc(100% - #{$sidenav-min-space}); @@ -120,6 +120,6 @@ md-sidenav { } -md-sidenav { - background-color: md-color($md-background,500); //'{{background-color}}'; +[md-sidenav] { + background-color: md-color($md-background,A100); //'{{background-color}}'; } diff --git a/ng2-material/components/sidenav/sidenav.ts b/ng2-material/components/sidenav/sidenav.ts index 6ca3cf66..b58727ef 100644 --- a/ng2-material/components/sidenav/sidenav.ts +++ b/ng2-material/components/sidenav/sidenav.ts @@ -1,7 +1,166 @@ -import {Directive} from 'angular2/core'; +import {Directive, Injectable} from 'angular2/core'; import {Input} from 'angular2/core'; +import {Animate} from "../../core/util/animate"; +import {MdDialog} from "../dialog/dialog"; +import {MdDialogRef} from "../dialog/dialog_ref"; +import {Media} from "../../core/util/media"; +import {MdBackdrop} from "../backdrop/backdrop"; +import {ElementRef} from "angular2/core"; +import {ContentChildren} from "angular2/core"; +import {OnDestroy} from "angular2/core"; +import {AfterContentInit} from "angular2/core"; +import {OnInit} from "angular2/core"; +import {QueryList} from "angular2/core"; +import {Component} from "angular2/core"; +import {ViewChild} from "angular2/core"; +import {ComponentRef} from "angular2/core"; +import {Renderer} from "angular2/core"; +import {DynamicComponentLoader} from "angular2/core"; +import {DOM} from "angular2/src/platform/dom/dom_adapter"; +import {Attribute} from "angular2/core"; +import {isPresent} from "angular2/src/facade/lang"; +import {forwardRef} from "angular2/core"; +import {Inject} from "angular2/core"; -@Directive({selector: 'md-sidenav'}) -export class MdSidenav { - @Input() public opened: boolean = true; +// TODO(jd): Lock open sidenav +// TODO(jd): Right alignment +// TODO(jd): Behaviors for testing +// - can lock open +// - service can find sidenavs by name + + +/** + * A slide-out navigation element that transitions in from the left or right. + * + * ```html + * + * ``` + */ +@Directive({ + selector: '[md-sidenav]' +}) +export class MdSidenav extends MdBackdrop implements OnInit, OnDestroy { + @Input('md-sidenav') + name: string = 'default'; + + private _backdropRef: ComponentRef = null; + + private _defaultContainer = DOM.query('body'); + + transitionClass: string = 'md-closed'; + transitionAddClass = false; + + constructor(public element: ElementRef, + public dcl: DynamicComponentLoader, + public renderer: Renderer, + @Inject(forwardRef(() => SidenavService)) public service: SidenavService, + @Attribute('md-sidenav') name: string) { + super(element); + DOM.addClass(this.element.nativeElement, this.transitionClass); + if (isPresent(name)) { + this.name = name; + } + } + + show(): Promise { + return this._createBackdrop(this.element).then(() => super.show()); + } + + _destroyBackdrop(): Promise { + if (this._backdropRef) { + this._backdropRef.dispose(); + this._backdropRef = null; + } + return Promise.resolve(); + } + + + toggle(): Promise { + return this.visible ? this.hide() : this.show(); + } + + isOpen(): boolean { + return this.visible; + } + + private _lockedOpen: boolean = false; + + isLockedOpen(): boolean { + return this._lockedOpen; + } + + + _createBackdrop(elementRef: ElementRef): Promise { + if (this._backdropRef) { + return Promise.resolve(this._backdropRef); + } + return this.dcl.loadNextToLocation(MdBackdrop, elementRef) + .then((componentRef) => { + let backdrop: MdBackdrop = componentRef.instance; + backdrop.clickClose = true; + this.renderer.setElementClass(componentRef.location, 'md-backdrop', true); + this.renderer.setElementClass(componentRef.location, 'md-opaque', true); + DOM.appendChild(this._defaultContainer, componentRef.location.nativeElement); + + this._backdropRef = componentRef; + // When this component is shown/hidden, sync the backdrop + this.onShowing.subscribe(() => backdrop.show()); + this.onHiding.subscribe(() => backdrop.hide()); + // If the sidenav is hidden, release the backdrop + this.onHidden.subscribe(() => this._destroyBackdrop()); + // If the backdrop is hidden, hide the nav + backdrop.onHiding.subscribe(() => this.hide()); + + return componentRef; + }); + } + + ngOnInit(): any { + this.service.register(this); + } + + ngOnDestroy(): any { + this.service.unregister(this); + } + +} + +@Injectable() +export class SidenavService { + private _instances: MdSidenav[] = []; + + register(instance: MdSidenav) { + this._instances.push(instance); + } + + unregister(instance: MdSidenav) { + this._instances = this._instances.filter((c: MdSidenav) => { + return c.name !== instance.name; + }); + } + + show(name: string): Promise { + let instance: MdSidenav = this._findInstance(name); + if (!instance) { + return Promise.reject('invalid container'); + } + return instance.show(); + } + + hide(name: string): Promise { + let instance: MdSidenav = this._findInstance(name); + if (!instance) { + return Promise.reject('invalid container'); + } + return instance.hide(); + } + + private _findInstance(name: string): MdSidenav { + return this._instances.filter((c: MdSidenav) => { + return c.name === name; + })[0]; + } }