feat(snackbar): add timeout for snackbar (#1856)

This commit is contained in:
Joey Perrott
2016-11-17 09:57:05 -08:00
committed by Kara
parent 8caf9a6553
commit 6ce63962ec
8 changed files with 86 additions and 15 deletions
@@ -10,6 +10,15 @@
[(ngModel)]="actionButtonLabel"></md-input>
</md-checkbox>
</div>
<div>
<md-checkbox [(ngModel)]="setAutoHide">
<p *ngIf="!setAutoHide">Auto hide after duration</p>
<md-input type="number" class="demo-button-label-input"
*ngIf="setAutoHide"
placeholder="Auto Hide Duration in ms"
[(ngModel)]="autoHide"></md-input>
</md-checkbox>
</div>
</div>
<button md-raised-button (click)="open()">OPEN</button>
+6 -2
View File
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {MdSnackBar} from '@angular/material';
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
@Component({
moduleId: module.id,
@@ -10,11 +10,15 @@ export class SnackBarDemo {
message: string = 'Snack Bar opened.';
actionButtonLabel: string = 'Retry';
action: boolean = false;
setAutoHide: boolean = true;
autoHide: number = 0;
constructor(
public snackBar: MdSnackBar) { }
open() {
this.snackBar.open(this.message, this.action && this.actionButtonLabel);
let config = new MdSnackBarConfig();
config.duration = this.autoHide;
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
}
}
+1
View File
@@ -15,6 +15,7 @@
| `viewContainerRef: ViewContainerRef` | The view container ref to attach the snack bar to. |
| `role: AriaLivePoliteness = 'assertive'` | The politeness level to announce the snack bar with. |
| `announcementMessage: string` | The message to announce with the snack bar. |
| `duration: number` | The length of time in milliseconds to wait before autodismissing the snack bar. |
### Example
+3
View File
@@ -13,4 +13,7 @@ export class MdSnackBarConfig {
/** The view container to place the overlay for the snack bar into. */
viewContainerRef?: ViewContainerRef = null;
/** The length of time in milliseconds to wait before automatically dismissing the snack bar. */
duration?: number = 0;
}
+21 -7
View File
@@ -41,7 +41,7 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
host: {
'role': 'alert',
'[@state]': 'animationState',
'(@state.done)': 'markAsExited($event)'
'(@state.done)': 'onAnimationEnd($event)'
},
animations: [
trigger('state', [
@@ -58,7 +58,10 @@ export class MdSnackBarContainer extends BasePortalHost {
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;
/** Subject for notifying that the snack bar has exited from view. */
private _onExit: Subject<any> = new Subject();
private onExit: Subject<any> = new Subject();
/** Subject for notifying that the snack bar has finished entering the view. */
private onEnter: Subject<any> = new Subject();
/** The state of the snack bar animations. */
animationState: SnackBarState = 'initial';
@@ -87,15 +90,21 @@ export class MdSnackBarContainer extends BasePortalHost {
/** Begin animation of the snack bar exiting from view. */
exit(): Observable<void> {
this.animationState = 'complete';
return this._onExit.asObservable();
return this.onExit.asObservable();
}
/** Mark snack bar as exited from the view. */
markAsExited(event: AnimationTransitionEvent) {
/** Handle end of animations, updating the state of the snackbar. */
onAnimationEnd(event: AnimationTransitionEvent) {
if (event.toState === 'void' || event.toState === 'complete') {
this._ngZone.run(() => {
this._onExit.next();
this._onExit.complete();
this.onExit.next();
this.onExit.complete();
});
}
if (event.toState === 'visible') {
this._ngZone.run(() => {
this.onEnter.next();
this.onEnter.complete();
});
}
}
@@ -104,4 +113,9 @@ export class MdSnackBarContainer extends BasePortalHost {
enter(): void {
this.animationState = 'visible';
}
/** Returns an observable resolving when the enter animation completes. */
_onEnter(): Observable<void> {
return this.onEnter.asObservable();
}
}
+16
View File
@@ -19,6 +19,9 @@ export class MdSnackBarRef<T> {
/** Subject for notifying the user that the snack bar has closed. */
private _afterClosed: Subject<any> = new Subject();
/** Subject for notifying the user that the snack bar has opened and appeared. */
private _afterOpened: Subject<any>;
/** Subject for notifying the user that the snack bar action was called. */
private _onAction: Subject<any> = new Subject();
@@ -51,11 +54,24 @@ export class MdSnackBarRef<T> {
}
}
/** Marks the snackbar as opened */
_open(): void {
if (!this._afterOpened.closed) {
this._afterOpened.next();
this._afterOpened.complete();
}
}
/** Gets an observable that is notified when the snack bar is finished closing. */
afterDismissed(): Observable<void> {
return this._afterClosed.asObservable();
}
/** Gets an observable that is notified when the snack bar has opened and appeared. */
afterOpened(): Observable<void> {
return this.containerInstance._onEnter();
}
/** Gets an observable that is notified when the snack bar action is called. */
onAction(): Observable<void> {
return this._onAction.asObservable();
+22 -4
View File
@@ -9,6 +9,7 @@ import {
} from '@angular/core/testing';
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
import {MdSnackBarConfig} from './snack-bar-config';
import {OverlayContainer, MdLiveAnnouncer} from '../core';
import {SimpleSnackBar} from './simple-snack-bar';
@@ -280,13 +281,30 @@ describe('MdSnackBar', () => {
viewContainerFixture.detectChanges();
flushMicrotasks();
viewContainerFixture.whenStable().then(() => {
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
expect(actionObservableCompleted).toBeTruthy('Expected the snack bar to notify of action');
});
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
expect(actionObservableCompleted).toBeTruthy('Expected the snack bar to notify of action');
tick(500);
}));
it('should dismiss automatically after a specified timeout', fakeAsync(() => {
let dismissObservableCompleted = false;
let config = new MdSnackBarConfig();
config.duration = 250;
let snackBarRef = snackBar.open('content', 'test', config);
snackBarRef.afterDismissed().subscribe(null, null, () => {
dismissObservableCompleted = true;
});
viewContainerFixture.detectChanges();
flushMicrotasks();
expect(dismissObservableCompleted).toBeFalsy('Expected the snack bar not to be dismissed');
tick(1000);
viewContainerFixture.detectChanges();
flushMicrotasks();
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
}));
});
@Directive({selector: 'dir-with-view-container'})
+8 -2
View File
@@ -22,8 +22,6 @@ import {MdSnackBarContainer} from './snack-bar-container';
import {SimpleSnackBar} from './simple-snack-bar';
import {extendObject} from '../core/util/object-extend';
// TODO(josephperrott): Automate dismiss after timeout.
/**
* Service to dispatch Material Design snack bar messages.
@@ -64,6 +62,14 @@ export class MdSnackBar {
} else {
snackBarRef.containerInstance.enter();
}
// If a dismiss timeout is provided, set up dismiss based on after the snackbar is opened.
if (config.duration > 0) {
snackBarRef.afterOpened().subscribe(() => {
setTimeout(() => snackBarRef.dismiss(), config.duration);
});
}
this._live.announce(config.announcementMessage, config.politeness);
this._snackBarRef = snackBarRef;
return this._snackBarRef;