diff --git a/src/demo-app/snack-bar/snack-bar-demo.html b/src/demo-app/snack-bar/snack-bar-demo.html
index 7e1a56e0e35f..4d0634e59b74 100644
--- a/src/demo-app/snack-bar/snack-bar-demo.html
+++ b/src/demo-app/snack-bar/snack-bar-demo.html
@@ -3,6 +3,20 @@
SnackBar demo
Message:
+
+
Position in page:
+
+ Start
+ End
+ Left
+ Right
+ Center
+
+
+ Top
+ Bottom
+
+
Show button on snack bar
@@ -27,7 +41,6 @@ SnackBar demo
-
Add extra class to container
diff --git a/src/demo-app/snack-bar/snack-bar-demo.ts b/src/demo-app/snack-bar/snack-bar-demo.ts
index d61bed273a58..6f905ce052ba 100644
--- a/src/demo-app/snack-bar/snack-bar-demo.ts
+++ b/src/demo-app/snack-bar/snack-bar-demo.ts
@@ -1,5 +1,11 @@
import {Component, ViewEncapsulation} from '@angular/core';
-import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
+import {
+ MdSnackBar,
+ MdSnackBarConfig,
+ MdSnackBarHorizontalPosition,
+ MdSnackBarVerticalPosition,
+ Dir,
+} from '@angular/material';
@Component({
moduleId: module.id,
@@ -15,13 +21,18 @@ export class SnackBarDemo {
setAutoHide: boolean = true;
autoHide: number = 10000;
addExtraClass: boolean = false;
+ horizontalPosition: MdSnackBarHorizontalPosition = 'center';
+ verticalPosition: MdSnackBarVerticalPosition = 'bottom';
- constructor(public snackBar: MdSnackBar) { }
+ constructor(public snackBar: MdSnackBar, private dir: Dir) { }
open() {
let config = new MdSnackBarConfig();
+ config.verticalPosition = this.verticalPosition;
+ config.horizontalPosition = this.horizontalPosition;
config.duration = this.autoHide;
config.extraClasses = this.addExtraClass ? ['party'] : undefined;
+ config.direction = this.dir.value;
this.snackBar.open(this.message, this.action ? this.actionButtonLabel : undefined, config);
}
}
diff --git a/src/lib/snack-bar/snack-bar-config.ts b/src/lib/snack-bar/snack-bar-config.ts
index 85f02041e42e..4f0f2c1fa9cc 100644
--- a/src/lib/snack-bar/snack-bar-config.ts
+++ b/src/lib/snack-bar/snack-bar-config.ts
@@ -12,6 +12,12 @@ import {Direction} from '@angular/cdk/bidi';
export const MD_SNACK_BAR_DATA = new InjectionToken('MdSnackBarData');
+/** Possible values for horizontalPosition on MdSnackBarConfig. */
+export type MdSnackBarHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right';
+
+/** Possible values for verticalPosition on MdSnackBarConfig. */
+export type MdSnackBarVerticalPosition = 'top' | 'bottom';
+
/**
* Configuration used when opening a snack-bar.
*/
@@ -36,4 +42,10 @@ export class MdSnackBarConfig {
/** Data being injected into the child component. */
data?: any = null;
+
+ /** The horizontal position to place the snack bar. */
+ horizontalPosition?: MdSnackBarHorizontalPosition = 'center';
+
+ /** The vertical position to place the snack bar. */
+ verticalPosition?: MdSnackBarVerticalPosition = 'bottom';
}
diff --git a/src/lib/snack-bar/snack-bar-container.scss b/src/lib/snack-bar/snack-bar-container.scss
index 0ffe0b93b791..164040d9b079 100644
--- a/src/lib/snack-bar/snack-bar-container.scss
+++ b/src/lib/snack-bar/snack-bar-container.scss
@@ -3,18 +3,36 @@
$mat-snack-bar-padding: 14px 24px !default;
$mat-snack-bar-min-width: 288px !default;
$mat-snack-bar-max-width: 568px !default;
+$mat-snack-bar-spacing-margin: 24px !default;
.mat-snack-bar-container {
border-radius: 2px;
box-sizing: content-box;
display: block;
+ margin: $mat-snack-bar-spacing-margin;
max-width: $mat-snack-bar-max-width;
min-width: $mat-snack-bar-min-width;
padding: $mat-snack-bar-padding;
- // Initial transformation is applied to start snack bar out of view.
+ // Initial transformation is applied to start snack bar out of view, below its target position.
transform: translateY(100%);
+ /**
+ * Removes margin of snack bars which are center positioned horizontally. This
+ * is done to align snack bars to the edge of the view vertically to match spec.
+ */
+ &.mat-snack-bar-center {
+ margin: 0;
+ }
+
+ /**
+ * To allow for animations from a 'top' vertical position to animate in a downward
+ * direction, set the translation to start the snack bar above the target position.
+ */
+ &.mat-snack-bar-top {
+ transform: translateY(-100%);
+ }
+
@include cdk-high-contrast {
border: solid 1px;
}
diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts
index e0acfc2b1766..1c8c1f884006 100644
--- a/src/lib/snack-bar/snack-bar-container.ts
+++ b/src/lib/snack-bar/snack-bar-container.ts
@@ -38,7 +38,7 @@ import {Subject} from 'rxjs/Subject';
import {MdSnackBarConfig} from './snack-bar-config';
-export type SnackBarState = 'initial' | 'visible' | 'complete' | 'void';
+export type SnackBarState = 'visible' | 'hidden' | 'void';
// TODO(jelbourn): we can't use constants from animation.ts here because you can't use
// a text interpolation in anything that is analyzed statically with ngc (for AoT compile).
@@ -59,15 +59,22 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
host: {
'role': 'alert',
'class': 'mat-snack-bar-container',
- '[@state]': 'animationState',
+ '[@state]': 'getAnimationState()',
'(@state.done)': 'onAnimationEnd($event)'
},
animations: [
trigger('state', [
- state('void, initial, complete', style({transform: 'translateY(100%)'})),
- state('visible', style({transform: 'translateY(0%)'})),
- transition('visible => complete', animate(HIDE_ANIMATION)),
- transition('initial => visible, void => visible', animate(SHOW_ANIMATION)),
+ // Animation from top.
+ state('visible-top', style({transform: 'translateY(0%)'})),
+ state('hidden-top', style({transform: 'translateY(-100%)'})),
+ transition('visible-top => hidden-top', animate(HIDE_ANIMATION)),
+ transition('void => visible-top', animate(SHOW_ANIMATION)),
+ // Animation from bottom.
+ state('visible-bottom', style({transform: 'translateY(0%)'})),
+ state('hidden-bottom', style({transform: 'translateY(100%)'})),
+ transition('visible-bottom => hidden-bottom', animate(HIDE_ANIMATION)),
+ transition('void => visible-bottom',
+ animate(SHOW_ANIMATION)),
])
],
})
@@ -85,7 +92,7 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
_onEnter: Subject = new Subject();
/** The state of the snack bar animations. */
- animationState: SnackBarState = 'initial';
+ private _animationState: SnackBarState;
/** The snack bar configuration. */
snackBarConfig: MdSnackBarConfig;
@@ -98,6 +105,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
super();
}
+ /**
+ * Gets the current animation state both combining one of the possibilities from
+ * SnackBarState and the vertical location.
+ */
+ getAnimationState(): string {
+ return `${this._animationState}-${this.snackBarConfig.verticalPosition}`;
+ }
+
/** Attach a component portal as content to this snack bar container. */
attachComponentPortal(portal: ComponentPortal): ComponentRef {
if (this._portalHost.hasAttached()) {
@@ -112,6 +127,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
}
}
+ if (this.snackBarConfig.horizontalPosition === 'center') {
+ this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-center');
+ }
+
+ if (this.snackBarConfig.verticalPosition === 'top') {
+ this._renderer.addClass(this._elementRef.nativeElement, 'mat-snack-bar-top');
+ }
+
return this._portalHost.attachComponentPortal(portal);
}
@@ -122,11 +145,11 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
/** Handle end of animations, updating the state of the snackbar. */
onAnimationEnd(event: AnimationEvent) {
- if (event.toState === 'void' || event.toState === 'complete') {
+ if (event.toState === 'void' || event.toState.startsWith('hidden')) {
this._completeExit();
}
- if (event.toState === 'visible') {
+ if (event.toState.startsWith('visible')) {
// Note: we shouldn't use `this` inside the zone callback,
// because it can cause a memory leak.
const onEnter = this._onEnter;
@@ -141,14 +164,14 @@ export class MdSnackBarContainer extends BasePortalHost implements OnDestroy {
/** Begin animation of snack bar entrance into view. */
enter(): void {
if (!this._destroyed) {
- this.animationState = 'visible';
+ this._animationState = 'visible';
this._changeDetectorRef.detectChanges();
}
}
/** Begin animation of the snack bar exiting from view. */
exit(): Observable {
- this.animationState = 'complete';
+ this._animationState = 'hidden';
return this._onExit;
}
diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts
index 2d57d390f381..fdd1da844e82 100644
--- a/src/lib/snack-bar/snack-bar.spec.ts
+++ b/src/lib/snack-bar/snack-bar.spec.ts
@@ -22,8 +22,6 @@ import {
} from './index';
-// TODO(josephperrott): Update tests to mock waiting for time to complete for animations.
-
describe('MdSnackBar', () => {
let snackBar: MdSnackBar;
let liveAnnouncer: LiveAnnouncer;
@@ -66,7 +64,7 @@ describe('MdSnackBar', () => {
});
it('should have the role of alert', () => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
snackBar.open(simpleMessage, simpleActionLabel, config);
let containerElement = overlayContainerElement.querySelector('snack-bar-container')!;
@@ -92,7 +90,7 @@ describe('MdSnackBar', () => {
}));
it('should open a simple message with a button', () => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, simpleActionLabel, config);
viewContainerFixture.detectChanges();
@@ -100,7 +98,8 @@ describe('MdSnackBar', () => {
expect(snackBarRef.instance instanceof SimpleSnackBar)
.toBe(true, 'Expected the snack bar content component to be SimpleSnackBar');
expect(snackBarRef.instance.snackBarRef)
- .toBe(snackBarRef, 'Expected the snack bar reference to be placed in the component instance');
+ .toBe(snackBarRef,
+ 'Expected the snack bar reference to be placed in the component instance');
let messageElement = overlayContainerElement.querySelector('snack-bar-container')!;
expect(messageElement.textContent)
@@ -115,7 +114,7 @@ describe('MdSnackBar', () => {
});
it('should open a simple message with no button', () => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
viewContainerFixture.detectChanges();
@@ -133,7 +132,7 @@ describe('MdSnackBar', () => {
});
it('should dismiss the snack bar and remove itself from the view', async(() => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let dismissObservableCompleted = false;
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
@@ -183,33 +182,38 @@ describe('MdSnackBar', () => {
}));
it('should set the animation state to visible on entry', () => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
viewContainerFixture.detectChanges();
- expect(snackBarRef.containerInstance.animationState)
- .toBe('visible', `Expected the animation state would be 'visible'.`);
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
+ snackBarRef.dismiss();
+
+ viewContainerFixture.detectChanges();
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
});
it('should set the animation state to complete on exit', () => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
snackBarRef.dismiss();
viewContainerFixture.detectChanges();
- expect(snackBarRef.containerInstance.animationState)
- .toBe('complete', `Expected the animation state would be 'complete'.`);
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
});
it(`should set the old snack bar animation state to complete and the new snack bar animation
state to visible on entry of new snack bar`, async(() => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
let dismissObservableCompleted = false;
viewContainerFixture.detectChanges();
- expect(snackBarRef.containerInstance.animationState)
- .toBe('visible', `Expected the animation state would be 'visible'.`);
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
let config2 = {viewContainerRef: testViewContainerRef};
let snackBarRef2 = snackBar.open(simpleMessage, undefined, config2);
@@ -221,16 +225,17 @@ describe('MdSnackBar', () => {
viewContainerFixture.whenStable().then(() => {
expect(dismissObservableCompleted).toBe(true);
- expect(snackBarRef.containerInstance.animationState)
- .toBe('complete', `Expected the animation state would be 'complete'.`);
- expect(snackBarRef2.containerInstance.animationState)
- .toBe('visible', `Expected the animation state would be 'visible'.`);
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('hidden-bottom', `Expected the animation state would be 'hidden-bottom'.`);
+ expect(snackBarRef2.containerInstance.getAnimationState())
+ .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
});
}));
it('should open a new snackbar after dismissing a previous snackbar', async(() => {
- let config = {viewContainerRef: testViewContainerRef};
+ let config: MdSnackBarConfig = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, 'Dismiss', config);
+
viewContainerFixture.detectChanges();
snackBarRef.dismiss();
@@ -243,7 +248,8 @@ describe('MdSnackBar', () => {
// Wait for the snackbar open animation to finish.
viewContainerFixture.whenStable().then(() => {
- expect(snackBarRef.containerInstance.animationState).toBe('visible');
+ expect(snackBarRef.containerInstance.getAnimationState())
+ .toBe('visible-bottom', `Expected the animation state would be 'visible-bottom'.`);
});
});
}));
@@ -506,6 +512,240 @@ describe('MdSnackBar with parent MdSnackBar', () => {
}));
});
+
+describe('MdSnackBar Positioning', () => {
+ let snackBar: MdSnackBar;
+ let liveAnnouncer: LiveAnnouncer;
+ let overlayContainerEl: HTMLElement;
+
+ let testViewContainerRef: ViewContainerRef;
+ let viewContainerFixture: ComponentFixture;
+
+ let simpleMessage = 'Burritos are here!';
+ let simpleActionLabel = 'pickup';
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [MdSnackBarModule, SnackBarTestModule, NoopAnimationsModule],
+ providers: [
+ {provide: OverlayContainer, useFactory: () => {
+ overlayContainerEl = document.createElement('div');
+ return {getContainerElement: () => overlayContainerEl};
+ }}
+ ],
+ });
+ TestBed.compileComponents();
+ }));
+
+ beforeEach(inject([MdSnackBar, LiveAnnouncer], (sb: MdSnackBar, la: LiveAnnouncer) => {
+ snackBar = sb;
+ liveAnnouncer = la;
+ }));
+
+ afterEach(() => {
+ overlayContainerEl.innerHTML = '';
+ liveAnnouncer.ngOnDestroy();
+ });
+
+ beforeEach(() => {
+ viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer);
+
+ viewContainerFixture.detectChanges();
+ testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer;
+ });
+
+ it('should default to bottom center', () => {
+ let config: MdSnackBarConfig = {};
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeTruthy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeFalsy();
+ expect(overlayPaneEl.style.marginBottom).toBe('0px', 'Expected margin-bottom to be "0px"');
+ expect(overlayPaneEl.style.marginTop).toBe('', 'Expected margin-top to be ""');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should be in the bottom left corner', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'bottom',
+ horizontalPosition: 'left'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeFalsy();
+ expect(overlayPaneEl.style.marginBottom).toBe('0px', 'Expected margin-bottom to be "0px"');
+ expect(overlayPaneEl.style.marginTop).toBe('', 'Expected margin-top to be ""');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('0px', 'Expected margin-left to be "0px"');
+ });
+
+ it('should be in the bottom right corner', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'bottom',
+ horizontalPosition: 'right'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeFalsy();
+ expect(overlayPaneEl.style.marginBottom).toBe('0px', 'Expected margin-bottom to be "0px"');
+ expect(overlayPaneEl.style.marginTop).toBe('', 'Expected margin-top to be ""');
+ expect(overlayPaneEl.style.marginRight).toBe('0px', 'Expected margin-right to be "0px"');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should be in the bottom center', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'bottom',
+ horizontalPosition: 'center'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeTruthy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeFalsy();
+ expect(overlayPaneEl.style.marginBottom).toBe('0px', 'Expected margin-bottom to be "0px"');
+ expect(overlayPaneEl.style.marginTop).toBe('', 'Expected margin-top to be ""');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should be in the top left corner', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'left'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('0px', 'Expected margin-left to be "0px"');
+ });
+
+ it('should be in the top right corner', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'right'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('0px', 'Expected margin-right to be "0px"');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should be in the top center', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'center'
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeTruthy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should handle start based on direction (rtl)', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'start',
+ direction: 'rtl',
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('0px', 'Expected margin-right to be "0px"');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+ it('should handle start based on direction (ltr)', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'start',
+ direction: 'ltr',
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('0px', 'Expected margin-left to be "0px"');
+ });
+
+
+ it('should handle end based on direction (rtl)', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'end',
+ direction: 'rtl',
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('', 'Expected margin-right to be ""');
+ expect(overlayPaneEl.style.marginLeft).toBe('0px', 'Expected margin-left to be "0px"');
+ });
+
+ it('should handle end based on direction (ltr)', () => {
+ let config: MdSnackBarConfig = {
+ verticalPosition: 'top',
+ horizontalPosition: 'end',
+ direction: 'ltr',
+ };
+ snackBar.open(simpleMessage, simpleActionLabel, config);
+ let containerEl = overlayContainerEl.querySelector('snack-bar-container') as HTMLElement;
+ let overlayPaneEl = overlayContainerEl.querySelector('.cdk-overlay-pane') as HTMLElement;
+
+ expect(containerEl.classList.contains('mat-snack-bar-center')).toBeFalsy();
+ expect(containerEl.classList.contains('mat-snack-bar-top')).toBeTruthy();
+ expect(overlayPaneEl.style.marginBottom).toBe('', 'Expected margin-bottom to be ""');
+ expect(overlayPaneEl.style.marginTop).toBe('0px', 'Expected margin-top to be "0px"');
+ expect(overlayPaneEl.style.marginRight).toBe('0px', 'Expected margin-right to be "0px"');
+ expect(overlayPaneEl.style.marginLeft).toBe('', 'Expected margin-left to be ""');
+ });
+
+});
+
+
@Directive({selector: 'dir-with-view-container'})
class DirectiveWithViewContainer {
constructor(public viewContainerRef: ViewContainerRef) { }
diff --git a/src/lib/snack-bar/snack-bar.ts b/src/lib/snack-bar/snack-bar.ts
index 8c2b89799024..8696895c04ad 100644
--- a/src/lib/snack-bar/snack-bar.ts
+++ b/src/lib/snack-bar/snack-bar.ts
@@ -153,11 +153,32 @@ export class MdSnackBar {
* @param config The user-specified snack bar config.
*/
private _createOverlay(config: MdSnackBarConfig): OverlayRef {
- const state = new OverlayState({
- direction: config.direction,
- positionStrategy: this._overlay.position().global().centerHorizontally().bottom('0')
- });
+ const state = new OverlayState();
+ state.direction = config.direction;
+
+ let positionStrategy = this._overlay.position().global();
+ // Set horizontal position.
+ const isRtl = config.direction === 'rtl';
+ const isLeft = (
+ config.horizontalPosition === 'left' ||
+ (config.horizontalPosition === 'start' && !isRtl) ||
+ (config.horizontalPosition === 'end' && isRtl));
+ const isRight = !isLeft && config.horizontalPosition !== 'center';
+ if (isLeft) {
+ positionStrategy.left('0');
+ } else if (isRight) {
+ positionStrategy.right('0');
+ } else {
+ positionStrategy.centerHorizontally();
+ }
+ // Set horizontal position.
+ if (config.verticalPosition === 'top') {
+ positionStrategy.top('0');
+ } else {
+ positionStrategy.bottom('0');
+ }
+ state.positionStrategy = positionStrategy;
return this._overlay.create(state);
}