-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
521 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<div class="md-sidenav-backdrop" | ||
(click)="(start_?.mode == 'side' || start_.close()) && (end_?.mode == 'side' || end_.close())" | ||
[class.md-sidenav-shown]="(start_?.mode != 'side' && start_.opened) || | ||
(end_?.mode != 'side' && end_.opened)"></div> | ||
|
||
<ng-content select="md-sidenav"></ng-content> | ||
|
||
<md-content [style.margin-left.px]="(left_?.mode == 'side' && left_.opened) ? left_.width : 0" | ||
[style.margin-right.px]="(right_?.mode == 'side' && right_.opened) ? right_.width : 0" | ||
[style.left.px]="(left_?.mode == 'push' && left_.opened) ? left_.width : 0" | ||
[style.right.px]="(right_?.mode == 'push' && right_.opened) ? right_.width : 0"> | ||
<ng-content></ng-content> | ||
</md-content> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
@import "default-theme"; | ||
@import "variables"; | ||
@import "shadows"; | ||
|
||
|
||
$md-sidenav-background-color: md-color($md-background, 100) !default; | ||
|
||
|
||
@mixin md-sidenav-transition($open, $close) { | ||
transform: translateX($close); | ||
|
||
&.md-sidenav-closed { | ||
visibility: hidden; | ||
} | ||
&.md-sidenav-closing { | ||
transform: translateX($close); | ||
will-change: transform; | ||
} | ||
&.md-sidenav-opening { | ||
visibility: visible; | ||
transform: translateX($open); | ||
will-change: transform; | ||
box-shadow: $md-shadow-bottom-z-1; | ||
} | ||
&.md-sidenav-opened { | ||
transform: translateX($open); | ||
box-shadow: $md-shadow-bottom-z-1; | ||
} | ||
} | ||
|
||
|
||
:host { | ||
position: relative; | ||
display: block; | ||
// Use a transform to create a new stacking context. | ||
transform: translate3D(0, 0, 0); | ||
overflow-x: hidden; | ||
|
||
transition: margin-left $swift-ease-out-duration $swift-ease-out-timing-function, | ||
margin-right $swift-ease-out-duration $swift-ease-out-timing-function; | ||
|
||
& > .md-sidenav-backdrop { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
z-index: $z-index-drawer; | ||
visibility: hidden; | ||
display: block; | ||
|
||
&.md-sidenav-shown { | ||
visibility: visible; | ||
background-color: rgba(0, 0, 0, 0.21); | ||
transition: background-color $swift-ease-out-duration $swift-ease-out-timing-function; | ||
} | ||
} | ||
|
||
& > md-content { | ||
display: block; | ||
position: relative; | ||
transition: margin-left $swift-ease-out-duration $swift-ease-out-timing-function, | ||
margin-right $swift-ease-out-duration $swift-ease-out-timing-function, | ||
left $swift-ease-out-duration $swift-ease-out-timing-function, | ||
right $swift-ease-out-duration $swift-ease-out-timing-function; | ||
} | ||
|
||
> md-sidenav { | ||
position: fixed; | ||
top: 0; | ||
bottom: 0; | ||
z-index: $z-index-drawer + 1; | ||
background-color: $md-sidenav-background-color; | ||
|
||
transition: transform $swift-ease-out-duration $swift-ease-out-timing-function; | ||
|
||
@include md-sidenav-transition(0, -100%); | ||
|
||
&.md-sidenav-side { | ||
z-index: $z-index-drawer - 1; | ||
} | ||
|
||
&.md-sidenav-end { | ||
right: 0; | ||
|
||
@include md-sidenav-transition(0, 100%); | ||
} | ||
} | ||
} | ||
|
||
|
||
:host-context([dir="rtl"]) { | ||
> md-sidenav { | ||
@include md-sidenav-transition(0, 100%); | ||
|
||
&.md-sidenav-end { | ||
left: 0; | ||
right: auto; | ||
|
||
@include md-sidenav-transition(0, -100%); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
import { | ||
AfterContentInit, | ||
Component, | ||
ContentChildren, | ||
ElementRef, | ||
EventEmitter, | ||
Host, | ||
HostBinding, | ||
Input, | ||
View, | ||
ViewEncapsulation, | ||
OnChanges, | ||
Optional, | ||
Output, | ||
Query, | ||
QueryList, | ||
SimpleChange | ||
} from 'angular2/core'; | ||
import {BaseException} from 'angular2/src/facade/exceptions'; | ||
import {Dir} from "../../directives/dir/dir"; | ||
import {OneOf} from "../../core/annotations/one-of"; | ||
|
||
|
||
/** | ||
* Exception thrown when a MdSidenavLayout is missing both sidenavs. | ||
*/ | ||
export class MdMissingSidenavException extends BaseException {} | ||
|
||
/** | ||
* Exception thrown when two MdSidenav are matching the same side. | ||
*/ | ||
export class MdDuplicatedSidenavException extends BaseException { | ||
constructor(align: string) { | ||
super(`A sidenav was already declared for 'align="${align}"'`); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* <md-sidenav> component. | ||
* | ||
* This component corresponds to the drawer of the sidenav. | ||
* | ||
* Please refer to README.md for examples on how to use it. | ||
*/ | ||
@Component({ | ||
selector: 'md-sidenav', | ||
template: '<ng-content></ng-content>', | ||
}) | ||
export class MdSidenav { | ||
/** Alignment of the sidenav (direction neutral); whether 'start' or 'end'. */ | ||
@Input() @OneOf('start', 'end') align: string = 'start'; | ||
|
||
/** Mode of the sidenav; whether 'over' or 'side'. */ | ||
@Input() @OneOf('push', 'over', 'side') mode: string = 'over'; | ||
|
||
/** Whether the sidenav is opened. */ | ||
@Input() set opened(v: boolean) { | ||
this.opened_ = v; | ||
this.transition_ = true; | ||
if (v) { | ||
this.onOpenStart.emit(null); | ||
} else { | ||
this.onCloseStart.emit(null); | ||
} | ||
} | ||
get opened(): boolean { return this.opened_; } | ||
|
||
/** Event emitted when the sidenav is being opened. Use this to synchronize animations. */ | ||
@Output('open-start') onOpenStart = new EventEmitter<void>(); | ||
|
||
/** Event emitted when the sidenav is fully opened. */ | ||
@Output('open') onOpen = new EventEmitter<void>(); | ||
|
||
/** Event emitted when the sidenav is being closed. Use this to synchronize animations. */ | ||
@Output('close-start') onCloseStart = new EventEmitter<void>(); | ||
|
||
/** Event emitted when the sidenav is fully closed. */ | ||
@Output('close') onClose = new EventEmitter<void>(); | ||
|
||
|
||
// TODO(hansl): Get rid of ElementRef. | ||
constructor(private elementRef_: ElementRef) { | ||
if (elementRef_) { | ||
elementRef_.nativeElement.addEventListener('transitionend', | ||
(e: TransitionEvent) => { | ||
if (e.target == elementRef_.nativeElement && e.propertyName == 'transform') { | ||
this.transition_ = false; | ||
if (this.opened_) { | ||
this.onOpen.emit(null); | ||
} else { | ||
this.onClose.emit(null); | ||
} | ||
} | ||
}, false); | ||
} | ||
} | ||
|
||
|
||
/** Width of the sidenav. */ | ||
get width() { | ||
if (this.elementRef_.nativeElement) { | ||
return this.elementRef_.nativeElement.offsetWidth; | ||
} | ||
return 0; | ||
} | ||
|
||
/** Open this sidenav, and return a Promise that will resolve when it's fully opened (or get | ||
* rejected if it didn't). */ | ||
open(): Promise<void> { | ||
return this.toggle(true); | ||
} | ||
|
||
/** | ||
* Close this sidenav, and return a Promise that will resolve when it's fully closed (or get | ||
* rejected if it didn't). | ||
*/ | ||
close(): Promise<void> { | ||
return this.toggle(false); | ||
} | ||
|
||
/** | ||
* Toggle this sidenav. This is equivalent to calling open() when it's already opened, or | ||
* close() when it's closed. | ||
* @param isOpen | ||
* @returns {Promise<void>} | ||
*/ | ||
toggle(isOpen: boolean = !this.opened): Promise<void> { | ||
// Shortcut it if we're already opened. | ||
if (isOpen === this.opened) { | ||
return Promise.resolve(); | ||
} | ||
|
||
this.opened = isOpen; | ||
|
||
// We hook up both onOpen and onClose, but we reject the promise if | ||
// the animation was cut or cancelled for some reason. | ||
return new Promise<void>((resolve, reject) => { | ||
const property = isOpen ? this.onOpen : this.onClose; | ||
const other = isOpen ? this.onClose : this.onOpen; | ||
const subscription = property.subscribe(() => { | ||
resolve(); | ||
property.remove(subscription); | ||
other.remove(otherSubscription); | ||
}); | ||
const otherSubscription = property.subscribe(() => { | ||
reject(); | ||
property.remove(subscription); | ||
other.remove(otherSubscription); | ||
}); | ||
}); | ||
} | ||
|
||
|
||
/************************************************************************************************ | ||
* Private members. | ||
*/ | ||
@HostBinding('class.md-sidenav-closing') private get isClosing_() { | ||
return !this.opened_ && this.transition_; | ||
} | ||
@HostBinding('class.md-sidenav-opening') private get isOpening_() { | ||
return this.opened_ && this.transition_; | ||
} | ||
@HostBinding('class.md-sidenav-closed') get isClosed() { | ||
return !this.opened_ && !this.transition_; | ||
} | ||
@HostBinding('class.md-sidenav-opened') get isOpened() { | ||
return this.opened_ && !this.transition_; | ||
} | ||
@HostBinding('class.md-sidenav-end') get isEnd() { | ||
return this.align == 'end'; | ||
} | ||
@HostBinding('class.md-sidenav-side') get modeSide() { | ||
return this.mode == 'side'; | ||
} | ||
@HostBinding('class.md-sidenav-over') get modeOver() { | ||
return this.mode == 'over'; | ||
} | ||
@HostBinding('class.md-sidenav-push') get modePush() { | ||
return this.mode == 'push'; | ||
} | ||
|
||
private transition_: boolean = false; | ||
private opened_: boolean = false; | ||
} | ||
|
||
|
||
/** | ||
* <md-sidenav-layout> component. | ||
*/ | ||
@Component({ | ||
selector: 'md-sidenav-layout', | ||
directives: [MdSidenav], | ||
templateUrl: './components/sidenav/sidenav.html', | ||
styleUrls: ['./components/sidenav/sidenav.css'], | ||
}) | ||
export class MdSidenavLayout implements AfterContentInit { | ||
@ContentChildren(MdSidenav) private drawers_: QueryList<MdSidenav>; | ||
|
||
get start() { return this.start_; } | ||
get end() { return this.end_; } | ||
|
||
private start_: MdSidenav; | ||
private end_: MdSidenav; | ||
private right_: MdSidenav; | ||
private left_: MdSidenav; | ||
|
||
private validateDrawers_() { | ||
this.start_ = this.end_ = null; | ||
if (this.drawers_.length === 0) { | ||
throw new MdMissingSidenavException(); | ||
} | ||
|
||
for (const drawer of this.drawers_.toArray()) { | ||
if (drawer.align == 'end') { | ||
if (this.end_) { | ||
throw new MdDuplicatedSidenavException('end'); | ||
} | ||
this.end_ = drawer; | ||
} else { | ||
if (this.start_) { | ||
throw new MdDuplicatedSidenavException('start'); | ||
} | ||
this.start_ = drawer; | ||
} | ||
} | ||
|
||
this.right_ = this.left_ = null; | ||
this.left_ = this.start_; | ||
this.right_ = this.end_; | ||
|
||
// Detect if we're LTR or RTL. | ||
if (this.dir_.dir == 'ltr') { | ||
this.left_ = this.start_; | ||
this.right_ = this.end_; | ||
} else { | ||
this.left_ = this.end_; | ||
this.right_ = this.start_; | ||
} | ||
} | ||
|
||
constructor(@Optional() @Host() private dir_: Dir) { | ||
this.dir_.onDirChange.subscribe(() => this.validateDrawers_()); | ||
} | ||
|
||
ngAfterContentInit() { | ||
// On changes, assert on consistency. | ||
this.drawers_.changes.subscribe(() => this.validateDrawers_()); | ||
this.validateDrawers_(); | ||
} | ||
} |
Oops, something went wrong.