diff --git a/src/lib/checkbox/checkbox.scss b/src/lib/checkbox/checkbox.scss
index 66ba03b216eb..26a2d2ea5469 100644
--- a/src/lib/checkbox/checkbox.scss
+++ b/src/lib/checkbox/checkbox.scss
@@ -187,7 +187,6 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
}
.mat-checkbox {
- cursor: pointer;
font-family: $mat-font-family;
// Animation
@@ -195,6 +194,10 @@ $_mat-checkbox-mark-stroke-size: 2 / 15 * $mat-checkbox-size !default;
mat-elevation-transition-property-value();
}
+.mat-checkbox-label {
+ cursor: pointer;
+}
+
.mat-checkbox-layout {
// `cursor: inherit` ensures that the wrapper element gets the same cursor as the mat-checkbox
// (e.g. pointer by default, regular when disabled), instead of the browser default.
diff --git a/src/lib/checkbox/checkbox.spec.ts b/src/lib/checkbox/checkbox.spec.ts
index 5133a8df58d5..32d255aa05f5 100644
--- a/src/lib/checkbox/checkbox.spec.ts
+++ b/src/lib/checkbox/checkbox.spec.ts
@@ -190,17 +190,6 @@ describe('MdCheckbox', () => {
expect(inputElement.disabled).toBe(false);
});
- it('should not have a ripple when disabled', () => {
- let rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
- expect(rippleElement).toBeTruthy('Expected an enabled checkbox to have a ripple');
-
- testComponent.isDisabled = true;
- fixture.detectChanges();
-
- rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
- expect(rippleElement).toBeFalsy('Expected a disabled checkbox not to have a ripple');
- });
-
it('should not toggle `checked` state upon interation while disabled', () => {
testComponent.isDisabled = true;
fixture.detectChanges();
@@ -324,6 +313,44 @@ describe('MdCheckbox', () => {
expect(document.activeElement).toBe(inputElement);
});
+ describe('ripple elements', () => {
+
+ it('should show ripples on label mousedown', () => {
+ expect(checkboxNativeElement.querySelector('.mat-ripple-element')).toBeFalsy();
+
+ dispatchFakeEvent(labelElement, 'mousedown');
+ dispatchFakeEvent(labelElement, 'mouseup');
+
+ expect(checkboxNativeElement.querySelectorAll('.mat-ripple-element').length).toBe(1);
+ });
+
+ it('should not have a ripple when disabled', () => {
+ let rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
+ expect(rippleElement).toBeTruthy('Expected an enabled checkbox to have a ripple');
+
+ testComponent.isDisabled = true;
+ fixture.detectChanges();
+
+ rippleElement = checkboxNativeElement.querySelector('[md-ripple]');
+ expect(rippleElement).toBeFalsy('Expected a disabled checkbox not to have a ripple');
+ });
+
+ it('should remove ripple if mdRippleDisabled input is set', async(() => {
+ testComponent.disableRipple = true;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
+ .toBe(0, 'Expect no [md-ripple] in checkbox');
+
+ testComponent.disableRipple = false;
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
+ .toBe(1, 'Expect [md-ripple] in checkbox');
+ }));
+
+ });
+
describe('color behaviour', () => {
it('should apply class based on color attribute', () => {
testComponent.checkboxColor = 'primary';
@@ -544,19 +571,6 @@ describe('MdCheckbox', () => {
expect(inputElement.tabIndex).toBe(13);
});
- it('should remove ripple if mdRippleDisabled input is set', async(() => {
- testComponent.disableRipple = true;
- fixture.detectChanges();
-
- expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(0, 'Expect no [md-ripple] in checkbox');
-
- testComponent.disableRipple = false;
- fixture.detectChanges();
-
- expect(checkboxNativeElement.querySelectorAll('[md-ripple]').length)
- .toBe(1, 'Expect [md-ripple] in checkbox');
- }));
});
describe('with multiple checkboxes', () => {
@@ -678,6 +692,7 @@ describe('MdCheckbox', () => {
[(indeterminate)]="isIndeterminate"
[disabled]="isDisabled"
[color]="checkboxColor"
+ [disableRipple]="disableRipple"
(change)="changeCount = changeCount + 1"
(click)="onCheckboxClick($event)"
(change)="onCheckboxChange($event)">
@@ -691,6 +706,7 @@ class SingleCheckbox {
isRequired: boolean = false;
isIndeterminate: boolean = false;
isDisabled: boolean = false;
+ disableRipple: boolean = false;
parentElementClicked: boolean = false;
parentElementKeyedUp: boolean = false;
lastKeydownEvent: Event = null;
@@ -728,14 +744,12 @@ class MultipleCheckboxes { }
template: `
+ [disabled]="isDisabled">
`,
})
class CheckboxWithTabIndex {
customTabIndex: number = 7;
isDisabled: boolean = false;
- disableRipple: boolean = false;
}
/** Simple test component with an aria-label set. */
@@ -771,3 +785,10 @@ class CheckboxWithChangeEvent {
class CheckboxWithFormControl {
formControl = new FormControl();
}
+
+// TODO(devversion): replace with global utility once pull request #2943 is merged.
+function dispatchFakeEvent(element: HTMLElement, eventName: string): void {
+ let event = document.createEvent('Event');
+ event.initEvent(eventName, true, true);
+ element.dispatchEvent(event);
+}
diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts
index ba8fe29e96f3..270161c6d6dd 100644
--- a/src/lib/checkbox/checkbox.ts
+++ b/src/lib/checkbox/checkbox.ts
@@ -397,9 +397,6 @@ export class MdCheckbox implements ControlValueAccessor {
return `mat-checkbox-anim-${animSuffix}`;
}
- _getHostElement() {
- return this._elementRef.nativeElement;
- }
}
diff --git a/src/lib/core/compatibility/compatibility.ts b/src/lib/core/compatibility/compatibility.ts
index 7038678a2c7f..e38237937693 100644
--- a/src/lib/core/compatibility/compatibility.ts
+++ b/src/lib/core/compatibility/compatibility.ts
@@ -50,7 +50,6 @@ export const MAT_ELEMENTS_SELECTOR = `
mat-grid-tile-header,
mat-hint,
mat-icon,
- mat-ink-bar,
mat-list,
mat-list-item,
mat-menu,
@@ -68,8 +67,6 @@ export const MAT_ELEMENTS_SELECTOR = `
mat-slider,
mat-spinner,
mat-tab,
- mat-tab-body,
- mat-tab-header,
mat-tab-group,
mat-toolbar`;
@@ -113,7 +110,6 @@ export const MD_ELEMENTS_SELECTOR = `
md-grid-tile-header,
md-hint,
md-icon,
- md-ink-bar,
md-list,
md-list-item,
md-menu,
@@ -131,8 +127,6 @@ export const MD_ELEMENTS_SELECTOR = `
md-slider,
md-spinner,
md-tab,
- md-tab-body,
- md-tab-header,
md-tab-group,
md-toolbar`;
diff --git a/src/lib/core/overlay/overlay-ref.ts b/src/lib/core/overlay/overlay-ref.ts
index f614ba7b3bcb..0a8e42d2ffda 100644
--- a/src/lib/core/overlay/overlay-ref.ts
+++ b/src/lib/core/overlay/overlay-ref.ts
@@ -52,7 +52,7 @@ export class OverlayRef implements PortalHost {
* @returns Resolves when the overlay has been detached.
*/
detach(): Promise
{
- this._detachBackdrop();
+ this.detachBackdrop();
// When the overlay is detached, the pane element should disable pointer events.
// This is necessary because otherwise the pane element will cover the page and disable
@@ -70,7 +70,7 @@ export class OverlayRef implements PortalHost {
this._state.positionStrategy.dispose();
}
- this._detachBackdrop();
+ this.detachBackdrop();
this._portalHost.dispose();
}
@@ -154,7 +154,7 @@ export class OverlayRef implements PortalHost {
}
/** Detaches the backdrop (if any) associated with the overlay. */
- private _detachBackdrop(): void {
+ detachBackdrop(): void {
let backdropToDetach = this._backdropElement;
if (backdropToDetach) {
diff --git a/src/lib/core/overlay/position/connected-position-strategy.spec.ts b/src/lib/core/overlay/position/connected-position-strategy.spec.ts
index 8829ee367394..067a87c64939 100644
--- a/src/lib/core/overlay/position/connected-position-strategy.spec.ts
+++ b/src/lib/core/overlay/position/connected-position-strategy.spec.ts
@@ -261,6 +261,32 @@ describe('ConnectedPositionStrategy', () => {
'Expected overlay to be re-aligned to the trigger in the previous position.');
});
+ it('should default to the initial position, if no positions fit in the viewport', () => {
+ // Use the fake viewport ruler because we don't know *exactly* how big the viewport is.
+ fakeViewportRuler.fakeRect = {
+ top: 0, left: 0, width: 500, height: 500, right: 500, bottom: 500
+ };
+ positionBuilder = new OverlayPositionBuilder(fakeViewportRuler);
+
+ // Make the origin element taller than the viewport.
+ originElement.style.height = '1000px';
+ originElement.style.top = '0';
+ originRect = originElement.getBoundingClientRect();
+
+ strategy = positionBuilder.connectedTo(
+ fakeElementRef,
+ {originX: 'start', originY: 'top'},
+ {overlayX: 'start', overlayY: 'bottom'});
+
+ strategy.apply(overlayElement);
+ strategy.recalculateLastPosition();
+
+ let overlayRect = overlayElement.getBoundingClientRect();
+
+ expect(overlayRect.bottom).toBe(originRect.top,
+ 'Expected overlay to be re-aligned to the trigger in the initial position.');
+ });
+
it('should position a panel properly when rtl', () => {
// must make the overlay longer than the origin to properly test attachment
overlayElement.style.width = `500px`;
diff --git a/src/lib/core/overlay/position/connected-position-strategy.ts b/src/lib/core/overlay/position/connected-position-strategy.ts
index 44b739bcebde..2eda42461e93 100644
--- a/src/lib/core/overlay/position/connected-position-strategy.ts
+++ b/src/lib/core/overlay/position/connected-position-strategy.ts
@@ -151,10 +151,10 @@ export class ConnectedPositionStrategy implements PositionStrategy {
const originRect = this._origin.getBoundingClientRect();
const overlayRect = this._pane.getBoundingClientRect();
const viewportRect = this._viewportRuler.getViewportRect();
+ const lastPosition = this._lastConnectedPosition || this._preferredPositions[0];
- let originPoint = this._getOriginConnectionPoint(originRect, this._lastConnectedPosition);
- let overlayPoint =
- this._getOverlayPoint(originPoint, overlayRect, viewportRect, this._lastConnectedPosition);
+ let originPoint = this._getOriginConnectionPoint(originRect, lastPosition);
+ let overlayPoint = this._getOverlayPoint(originPoint, overlayRect, viewportRect, lastPosition);
this._setElementPosition(this._pane, overlayPoint);
}
diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts
index f375af0c6b08..afd308a2aed3 100644
--- a/src/lib/core/ripple/ripple.spec.ts
+++ b/src/lib/core/ripple/ripple.spec.ts
@@ -75,6 +75,7 @@ describe('MdRipple', () => {
}
describe('basic ripple', () => {
+ let rippleDirective: MdRipple;
const TARGET_HEIGHT = 200;
const TARGET_WIDTH = 300;
@@ -83,7 +84,8 @@ describe('MdRipple', () => {
fixture = TestBed.createComponent(BasicRippleContainer);
fixture.detectChanges();
- rippleTarget = fixture.debugElement.nativeElement.querySelector('[mat-ripple]');
+ rippleTarget = fixture.nativeElement.querySelector('[mat-ripple]');
+ rippleDirective = fixture.componentInstance.ripple;
});
it('creates ripple on mousedown', () => {
@@ -111,15 +113,29 @@ describe('MdRipple', () => {
}));
it('creates ripples when manually triggered', () => {
- let rippleComponent = fixture.debugElement.componentInstance.ripple as MdRipple;
-
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
- rippleComponent.launch(0, 0);
+ rippleDirective.launch(0, 0);
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(1);
});
+ it('creates manual ripples with the default ripple config', () => {
+ expect(rippleTarget.querySelectorAll('.mat-ripple-element').length).toBe(0);
+
+ // Calculate the diagonal distance and divide it by two for the center radius.
+ let radius = Math.sqrt(TARGET_HEIGHT * TARGET_HEIGHT + TARGET_WIDTH * TARGET_WIDTH) / 2;
+
+ rippleDirective.centered = true;
+ rippleDirective.launch(0, 0);
+
+ let rippleElement = rippleTarget.querySelector('.mat-ripple-element') as HTMLElement;
+
+ expect(rippleElement).toBeTruthy();
+ expect(parseFloat(rippleElement.style.left)).toBeCloseTo(TARGET_WIDTH / 2 - radius, 1);
+ expect(parseFloat(rippleElement.style.top)).toBeCloseTo(TARGET_HEIGHT / 2 - radius, 1);
+ });
+
it('sizes ripple to cover element', () => {
let elementRect = rippleTarget.getBoundingClientRect();
@@ -382,13 +398,13 @@ describe('MdRipple', () => {
@Component({
template: `
-
`,
})
class BasicRippleContainer {
- @ViewChild(MdRipple) ripple: MdRipple;
+ @ViewChild('ripple') ripple: MdRipple;
}
@Component({
diff --git a/src/lib/core/ripple/ripple.ts b/src/lib/core/ripple/ripple.ts
index 549510f26c98..d2bd9b6cae2c 100644
--- a/src/lib/core/ripple/ripple.ts
+++ b/src/lib/core/ripple/ripple.ts
@@ -17,6 +17,7 @@ import {SCROLL_DISPATCHER_PROVIDER} from '../overlay/scroll/scroll-dispatcher';
@Directive({
selector: '[md-ripple], [mat-ripple]',
+ exportAs: 'mdRipple',
host: {
'[class.mat-ripple]': 'true',
'[class.mat-ripple-unbounded]': 'unbounded'
@@ -77,7 +78,7 @@ export class MdRipple implements OnChanges, OnDestroy {
}
this._rippleRenderer.rippleDisabled = this.disabled;
- this._updateRippleConfig();
+ this._rippleRenderer.rippleConfig = this.rippleConfig;
}
ngOnDestroy() {
@@ -86,13 +87,13 @@ export class MdRipple implements OnChanges, OnDestroy {
}
/** Launches a manual ripple at the specified position. */
- launch(pageX: number, pageY: number, config?: RippleConfig) {
+ launch(pageX: number, pageY: number, config = this.rippleConfig) {
this._rippleRenderer.fadeInRipple(pageX, pageY, config);
}
- /** Updates the ripple configuration with the input values. */
- private _updateRippleConfig() {
- this._rippleRenderer.rippleConfig = {
+ /** Ripple configuration from the directive's input values. */
+ get rippleConfig(): RippleConfig {
+ return {
centered: this.centered,
speedFactor: this.speedFactor,
radius: this.radius,
diff --git a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
index 0c35c8b24346..fe85a4ce4ab0 100644
--- a/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
+++ b/src/lib/core/selection/pseudo-checkbox/_pseudo-checkbox-theme.scss
@@ -8,9 +8,6 @@
$warn: map-get($theme, warn);
$background: map-get($theme, background);
- // The color of the checkbox's checkmark / mixedmark.
- $checkbox-mark-color: mat-color($background, background);
-
// NOTE(traviskaufman): While the spec calls for translucent blacks/whites for disabled colors,
// this does not work well with elements layered on top of one another. To get around this we
// blend the colors together based on the base color and the theme background.
@@ -18,8 +15,12 @@
$black-26pct-opacity-on-light: #b0b0b0;
$disabled-color: if($is-dark-theme, $white-30pct-opacity-on-dark, $black-26pct-opacity-on-light);
- .mat-pseudo-checkbox::after {
- color: $checkbox-mark-color;
+ .mat-pseudo-checkbox {
+ color: mat-color(map-get($theme, foreground), secondary-text);
+
+ &::after {
+ color: mat-color($background, background);
+ }
}
.mat-pseudo-checkbox-checked, .mat-pseudo-checkbox-indeterminate {
diff --git a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
index b9799238cd86..4bf82038dde2 100644
--- a/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
+++ b/src/lib/core/selection/pseudo-checkbox/pseudo-checkbox.scss
@@ -29,6 +29,10 @@ $_mat-pseudo-checkmark-size: $mat-checkbox-size - (2 * $_mat-pseudo-checkbox-pad
border-bottom: $mat-checkbox-border-width solid currentColor;
transition: opacity $mat-checkbox-transition-duration $mat-linear-out-slow-in-timing-function;
}
+
+ &.mat-pseudo-checkbox-checked, &.mat-pseudo-checkbox-indeterminate {
+ border: none;
+ }
}
.mat-pseudo-checkbox-disabled {
diff --git a/src/lib/core/theming/_palette.scss b/src/lib/core/theming/_palette.scss
index a211a0836914..e30ce6d2a9ad 100644
--- a/src/lib/core/theming/_palette.scss
+++ b/src/lib/core/theming/_palette.scss
@@ -8,6 +8,10 @@
$black-87-opacity: rgba(black, 0.87);
$white-87-opacity: rgba(white, 0.87);
+$black-12-opacity: rgba(black, 0.12);
+$white-12-opacity: rgba(white, 0.12);
+$black-6-opacity: rgba(black, 0.06);
+$white-6-opacity: rgba(white, 0.06);
$mat-red: (
50: #ffebee,
@@ -649,8 +653,9 @@ $mat-light-theme-background: (
hover: rgba(black, 0.04), // TODO(kara): check style with Material Design UX
card: white,
dialog: white,
- disabled-button: rgba(black, 0.12),
+ disabled-button: $black-12-opacity,
raised-button: white,
+ focused-button: $black-6-opacity,
);
// Background palette for dark themes.
@@ -661,15 +666,16 @@ $mat-dark-theme-background: (
hover: rgba(white, 0.04), // TODO(kara): check style with Material Design UX
card: map_get($mat-grey, 800),
dialog: map_get($mat-grey, 800),
- disabled-button: rgba(white, 0.12),
+ disabled-button: $white-12-opacity,
raised-button: map-get($mat-grey, 800),
+ focused-button: $white-6-opacity,
);
// Foreground palette for light themes.
$mat-light-theme-foreground: (
base: black,
- divider: rgba(black, 0.12),
- dividers: rgba(black, 0.12),
+ divider: $black-12-opacity,
+ dividers: $black-12-opacity,
disabled: rgba(black, 0.38),
disabled-button: rgba(black, 0.38),
disabled-text: rgba(black, 0.38),
@@ -683,8 +689,8 @@ $mat-light-theme-foreground: (
// Foreground palette for dark themes.
$mat-dark-theme-foreground: (
base: white,
- divider: rgba(white, 0.12),
- dividers: rgba(white, 0.12),
+ divider: $white-12-opacity,
+ dividers: $white-12-opacity,
disabled: rgba(white, 0.3),
disabled-button: rgba(white, 0.3),
disabled-text: rgba(white, 0.3),
diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts
index c9948e53b7b4..bb54a4d79ac7 100644
--- a/src/lib/dialog/dialog-container.ts
+++ b/src/lib/dialog/dialog-container.ts
@@ -5,18 +5,28 @@ import {
ViewEncapsulation,
NgZone,
OnDestroy,
- Renderer,
+ animate,
+ state,
+ style,
+ transition,
+ trigger,
+ AnimationTransitionEvent,
+ EventEmitter,
} from '@angular/core';
import {BasePortalHost, ComponentPortal, PortalHostDirective, TemplatePortal} from '../core';
import {MdDialogConfig} from './dialog-config';
-import {MdDialogRef} from './dialog-ref';
import {MdDialogContentAlreadyAttachedError} from './dialog-errors';
import {FocusTrap} from '../core/a11y/focus-trap';
import 'rxjs/add/operator/first';
+/** Possible states for the dialog container animation. */
+export type MdDialogContainerAnimationState = 'void' | 'enter' | 'exit' | 'exit-start';
+
+
/**
* Internal component that wraps user-provided dialog content.
+ * Animation is based on https://material.io/guidelines/motion/choreography.html.
* @docs-private
*/
@Component({
@@ -24,11 +34,21 @@ import 'rxjs/add/operator/first';
selector: 'md-dialog-container, mat-dialog-container',
templateUrl: 'dialog-container.html',
styleUrls: ['dialog.css'],
+ encapsulation: ViewEncapsulation.None,
+ animations: [
+ trigger('slideDialog', [
+ state('void', style({ transform: 'translateY(25%) scale(0.9)', opacity: 0 })),
+ state('enter', style({ transform: 'translateY(0%) scale(1)', opacity: 1 })),
+ state('exit', style({ transform: 'translateY(25%)', opacity: 0 })),
+ transition('* => *', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)')),
+ ])
+ ],
host: {
'[class.mat-dialog-container]': 'true',
'[attr.role]': 'dialogConfig?.role',
+ '[@slideDialog]': '_state',
+ '(@slideDialog.done)': '_onAnimationDone($event)',
},
- encapsulation: ViewEncapsulation.None,
})
export class MdDialogContainer extends BasePortalHost implements OnDestroy {
/** The portal host inside of this container into which the dialog content will be loaded. */
@@ -38,15 +58,18 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
@ViewChild(FocusTrap) _focusTrap: FocusTrap;
/** Element that was focused before the dialog was opened. Save this to restore upon close. */
- private _elementFocusedBeforeDialogWasOpened: Element = null;
+ private _elementFocusedBeforeDialogWasOpened: HTMLElement = null;
/** The dialog configuration. */
dialogConfig: MdDialogConfig;
- /** Reference to the open dialog. */
- dialogRef: MdDialogRef;
+ /** State of the dialog animation. */
+ _state: MdDialogContainerAnimationState = 'enter';
+
+ /** Emits the current animation state whenever it changes. */
+ _onAnimationStateChange = new EventEmitter();
- constructor(private _ngZone: NgZone, private _renderer: Renderer) {
+ constructor(private _ngZone: NgZone) {
super();
}
@@ -87,20 +110,43 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
// ready in instances where change detection has to run first. To deal with this, we simply
// wait for the microtask queue to be empty.
this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
- this._elementFocusedBeforeDialogWasOpened = document.activeElement;
+ this._elementFocusedBeforeDialogWasOpened = document.activeElement as HTMLElement;
this._focusTrap.focusFirstTabbableElement();
});
}
+ /**
+ * Kicks off the leave animation.
+ * @docs-private
+ */
+ _exit(): void {
+ this._state = 'exit';
+ this._onAnimationStateChange.emit('exit-start');
+ }
+
+ /**
+ * Callback, invoked whenever an animation on the host completes.
+ * @docs-private
+ */
+ _onAnimationDone(event: AnimationTransitionEvent) {
+ this._onAnimationStateChange.emit(event.toState as MdDialogContainerAnimationState);
+ }
+
ngOnDestroy() {
// When the dialog is destroyed, return focus to the element that originally had it before
// the dialog was opened. Wait for the DOM to finish settling before changing the focus so
// that it doesn't end up back on the . Also note that we need the extra check, because
// IE can set the `activeElement` to null in some cases.
- if (this._elementFocusedBeforeDialogWasOpened) {
- this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
- this._renderer.invokeElementMethod(this._elementFocusedBeforeDialogWasOpened, 'focus');
- });
- }
+ this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
+ let toFocus = this._elementFocusedBeforeDialogWasOpened as HTMLElement;
+
+ // We need to check whether the focus method exists at all, because IE seems to throw an
+ // exception, even if the element is the document.body.
+ if (toFocus && 'focus' in toFocus) {
+ toFocus.focus();
+ }
+
+ this._onAnimationStateChange.complete();
+ });
}
}
diff --git a/src/lib/dialog/dialog-ref.ts b/src/lib/dialog/dialog-ref.ts
index bbbd31664792..fa70a0b78741 100644
--- a/src/lib/dialog/dialog-ref.ts
+++ b/src/lib/dialog/dialog-ref.ts
@@ -1,7 +1,7 @@
import {OverlayRef} from '../core';
-import {MdDialogConfig} from './dialog-config';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
+import {MdDialogContainer, MdDialogContainerAnimationState} from './dialog-container';
// TODO(jelbourn): resizing
@@ -18,16 +18,30 @@ export class MdDialogRef {
/** Subject for notifying the user that the dialog has finished closing. */
private _afterClosed: Subject = new Subject();
- constructor(private _overlayRef: OverlayRef, public config: MdDialogConfig) { }
+ /** Result to be passed to afterClosed. */
+ private _result: any;
+
+ constructor(private _overlayRef: OverlayRef, public _containerInstance: MdDialogContainer) {
+ _containerInstance._onAnimationStateChange.subscribe(
+ (state: MdDialogContainerAnimationState) => {
+ if (state === 'exit-start') {
+ // Transition the backdrop in parallel with the dialog.
+ this._overlayRef.detachBackdrop();
+ } else if (state === 'exit') {
+ this._overlayRef.dispose();
+ this._afterClosed.next(this._result);
+ this._afterClosed.complete();
+ }
+ });
+ }
/**
* Close the dialog.
* @param dialogResult Optional result to return to the dialog opener.
*/
close(dialogResult?: any): void {
- this._overlayRef.dispose();
- this._afterClosed.next(dialogResult);
- this._afterClosed.complete();
+ this._result = dialogResult;
+ this._containerInstance._exit();
}
/**
diff --git a/src/lib/dialog/dialog.md b/src/lib/dialog/dialog.md
index ca24c862cda5..4bf40be69e99 100644
--- a/src/lib/dialog/dialog.md
+++ b/src/lib/dialog/dialog.md
@@ -17,7 +17,7 @@ The `MdDialogRef` provides a handle on the opened dialog. It can be used to clos
receive notification when the dialog has been closed.
```ts
-dialogRef.afterClosed.then(result => {
+dialogRef.afterClosed.subscribe(result => {
console.log(`Dialog result: ${result}`); // Pizza!
});
diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts
index dbb02ac81aed..48abaa1f438c 100644
--- a/src/lib/dialog/dialog.spec.ts
+++ b/src/lib/dialog/dialog.spec.ts
@@ -15,12 +15,13 @@ import {NgModule,
Injector,
Inject,
} from '@angular/core';
+import {By} from '@angular/platform-browser';
import {MdDialogModule} from './index';
import {MdDialog} from './dialog';
-import {OverlayContainer} from '../core';
+import {MdDialogContainer} from './dialog-container';
+import {OverlayContainer, ESCAPE} from '../core';
import {MdDialogRef} from './dialog-ref';
import {MD_DIALOG_DATA} from './dialog-injector';
-import {ESCAPE} from '../core/keyboard/keycodes';
describe('MdDialog', () => {
@@ -109,38 +110,35 @@ describe('MdDialog', () => {
expect(dialogContainerElement.getAttribute('role')).toBe('alertdialog');
});
- it('should close a dialog and get back a result', () => {
- let dialogRef = dialog.open(PizzaMsg, {
- viewContainerRef: testViewContainerRef
- });
+ it('should close a dialog and get back a result', async(() => {
+ let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
+ let afterCloseCallback = jasmine.createSpy('afterClose callback');
+ dialogRef.afterClosed().subscribe(afterCloseCallback);
+ dialogRef.close('Charmander');
viewContainerFixture.detectChanges();
- let afterCloseResult: string;
- dialogRef.afterClosed().subscribe(result => {
- afterCloseResult = result;
+ viewContainerFixture.whenStable().then(() => {
+ expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
+ expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
});
-
- dialogRef.close('Charmander');
-
- expect(afterCloseResult).toBe('Charmander');
- expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
- });
+ }));
- it('should close a dialog via the escape key', () => {
+ it('should close a dialog via the escape key', async(() => {
dialog.open(PizzaMsg, {
viewContainerRef: testViewContainerRef
});
- viewContainerFixture.detectChanges();
-
dispatchKeydownEvent(document, ESCAPE);
+ viewContainerFixture.detectChanges();
- expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
- });
+ viewContainerFixture.whenStable().then(() => {
+ expect(overlayContainerElement.querySelector('md-dialog-container')).toBeNull();
+ });
+ }));
- it('should close when clicking on the overlay backdrop', () => {
+ it('should close when clicking on the overlay backdrop', async(() => {
dialog.open(PizzaMsg, {
viewContainerRef: testViewContainerRef
});
@@ -148,10 +146,14 @@ describe('MdDialog', () => {
viewContainerFixture.detectChanges();
let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
+
backdrop.click();
+ viewContainerFixture.detectChanges();
- expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
- });
+ viewContainerFixture.whenStable().then(() => {
+ expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
+ });
+ }));
it('should notify the observers if a dialog has been opened', () => {
let ref: MdDialogRef;
@@ -163,7 +165,7 @@ describe('MdDialog', () => {
})).toBe(ref);
});
- it('should notify the observers if all open dialogs have finished closing', () => {
+ it('should notify the observers if all open dialogs have finished closing', async(() => {
const ref1 = dialog.open(PizzaMsg, {
viewContainerRef: testViewContainerRef
});
@@ -177,10 +179,19 @@ describe('MdDialog', () => {
});
ref1.close();
- expect(allClosed).toBeFalsy();
- ref2.close();
- expect(allClosed).toBeTruthy();
- });
+ viewContainerFixture.detectChanges();
+
+ viewContainerFixture.whenStable().then(() => {
+ expect(allClosed).toBeFalsy();
+
+ ref2.close();
+ viewContainerFixture.detectChanges();
+
+ viewContainerFixture.whenStable().then(() => {
+ expect(allClosed).toBeTruthy();
+ });
+ });
+ }));
it('should should override the width of the overlay pane', () => {
dialog.open(PizzaMsg, {
@@ -262,7 +273,7 @@ describe('MdDialog', () => {
expect(overlayPane.style.marginRight).toBe('125px');
});
- it('should close all of the dialogs', () => {
+ it('should close all of the dialogs', async(() => {
dialog.open(PizzaMsg);
dialog.open(PizzaMsg);
dialog.open(PizzaMsg);
@@ -270,10 +281,47 @@ describe('MdDialog', () => {
expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(3);
dialog.closeAll();
+ viewContainerFixture.detectChanges();
+
+ viewContainerFixture.whenStable().then(() => {
+ expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
+ });
+ }));
+
+ it('should set the proper animation states', () => {
+ let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
+ let dialogContainer: MdDialogContainer =
+ viewContainerFixture.debugElement.query(By.directive(MdDialogContainer)).componentInstance;
+
+ expect(dialogContainer._state).toBe('enter');
+
+ dialogRef.close();
- expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0);
+ expect(dialogContainer._state).toBe('exit');
});
+ it('should emit an event with the proper animation state', async(() => {
+ let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
+ let dialogContainer: MdDialogContainer =
+ viewContainerFixture.debugElement.query(By.directive(MdDialogContainer)).componentInstance;
+ let spy = jasmine.createSpy('animation state callback');
+
+ dialogContainer._onAnimationStateChange.subscribe(spy);
+ viewContainerFixture.detectChanges();
+
+ viewContainerFixture.whenStable().then(() => {
+ expect(spy).toHaveBeenCalledWith('enter');
+
+ dialogRef.close();
+ viewContainerFixture.detectChanges();
+ expect(spy).toHaveBeenCalledWith('exit-start');
+
+ viewContainerFixture.whenStable().then(() => {
+ expect(spy).toHaveBeenCalledWith('exit');
+ });
+ });
+ }));
+
describe('passing in data', () => {
it('should be able to pass in data', () => {
let config = {
@@ -318,7 +366,6 @@ describe('MdDialog', () => {
});
viewContainerFixture.detectChanges();
-
dispatchKeydownEvent(document, ESCAPE);
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeTruthy();
@@ -385,13 +432,15 @@ describe('MdDialog', () => {
viewContainerFixture.detectChanges();
});
- it('should close the dialog when clicking on the close button', () => {
+ it('should close the dialog when clicking on the close button', async(() => {
expect(overlayContainerElement.querySelectorAll('.mat-dialog-container').length).toBe(1);
(overlayContainerElement.querySelector('button[md-dialog-close]') as HTMLElement).click();
- expect(overlayContainerElement.querySelectorAll('.mat-dialog-container').length).toBe(0);
- });
+ viewContainerFixture.whenStable().then(() => {
+ expect(overlayContainerElement.querySelectorAll('.mat-dialog-container').length).toBe(0);
+ });
+ }));
it('should not close the dialog if [md-dialog-close] is applied on a non-button node', () => {
expect(overlayContainerElement.querySelectorAll('.mat-dialog-container').length).toBe(1);
@@ -401,14 +450,16 @@ describe('MdDialog', () => {
expect(overlayContainerElement.querySelectorAll('.mat-dialog-container').length).toBe(1);
});
- it('should allow for a user-specified aria-label on the close button', () => {
+ it('should allow for a user-specified aria-label on the close button', async(() => {
let button = overlayContainerElement.querySelector('button[md-dialog-close]');
dialogRef.componentInstance.closeButtonAriaLabel = 'Best close button ever';
viewContainerFixture.detectChanges();
- expect(button.getAttribute('aria-label')).toBe('Best close button ever');
- });
+ viewContainerFixture.whenStable().then(() => {
+ expect(button.getAttribute('aria-label')).toBe('Best close button ever');
+ });
+ }));
it('should override the "type" attribute of the close button', () => {
let button = overlayContainerElement.querySelector('button[md-dialog-close]');
@@ -452,33 +503,39 @@ describe('MdDialog with a parent MdDialog', () => {
overlayContainerElement.innerHTML = '';
});
- it('should close dialogs opened by a parent when calling closeAll on a child MdDialog', () => {
- parentDialog.open(PizzaMsg);
- fixture.detectChanges();
+ it('should close dialogs opened by a parent when calling closeAll on a child MdDialog',
+ async(() => {
+ parentDialog.open(PizzaMsg);
+ fixture.detectChanges();
- expect(overlayContainerElement.textContent)
- .toContain('Pizza', 'Expected a dialog to be opened');
+ expect(overlayContainerElement.textContent)
+ .toContain('Pizza', 'Expected a dialog to be opened');
- childDialog.closeAll();
- fixture.detectChanges();
+ childDialog.closeAll();
+ fixture.detectChanges();
- expect(overlayContainerElement.textContent.trim())
- .toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
- });
+ fixture.whenStable().then(() => {
+ expect(overlayContainerElement.textContent.trim())
+ .toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent');
+ });
+ }));
- it('should close dialogs opened by a child when calling closeAll on a parent MdDialog', () => {
- childDialog.open(PizzaMsg);
- fixture.detectChanges();
+ it('should close dialogs opened by a child when calling closeAll on a parent MdDialog',
+ async(() => {
+ childDialog.open(PizzaMsg);
+ fixture.detectChanges();
- expect(overlayContainerElement.textContent)
- .toContain('Pizza', 'Expected a dialog to be opened');
+ expect(overlayContainerElement.textContent)
+ .toContain('Pizza', 'Expected a dialog to be opened');
- parentDialog.closeAll();
- fixture.detectChanges();
+ parentDialog.closeAll();
+ fixture.detectChanges();
- expect(overlayContainerElement.textContent.trim())
- .toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
- });
+ fixture.whenStable().then(() => {
+ expect(overlayContainerElement.textContent.trim())
+ .toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child');
+ });
+ }));
});
diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts
index 90cba743464a..3cc1c57ebe67 100644
--- a/src/lib/dialog/dialog.ts
+++ b/src/lib/dialog/dialog.ts
@@ -9,10 +9,7 @@ import {MdDialogConfig} from './dialog-config';
import {MdDialogRef} from './dialog-ref';
import {MdDialogContainer} from './dialog-container';
import {TemplatePortal} from '../core/portal/portal';
-
-
-// TODO(jelbourn): animations
-
+import 'rxjs/add/operator/first';
/**
@@ -135,16 +132,13 @@ export class MdDialog {
config?: MdDialogConfig): MdDialogRef {
// Create a reference to the dialog we're creating in order to give the user a handle
// to modify and close it.
- let dialogRef = > new MdDialogRef(overlayRef, config);
+ let dialogRef = new MdDialogRef(overlayRef, dialogContainer) as MdDialogRef;
if (!config.disableClose) {
// When the dialog backdrop is clicked, we want to close it.
overlayRef.backdropClick().first().subscribe(() => dialogRef.close());
}
- // Set the dialogRef to the container so that it can use the ref to close the dialog.
- dialogContainer.dialogRef = dialogRef;
-
// We create an injector specifically for the component we're instantiating so that it can
// inject the MdDialogRef. This allows a component loaded inside of a dialog to close itself
// and, optionally, to return a value.
@@ -217,7 +211,9 @@ export class MdDialog {
private _handleKeydown(event: KeyboardEvent): void {
let topDialog = this._openDialogs[this._openDialogs.length - 1];
- if (event.keyCode === ESCAPE && topDialog && !topDialog.config.disableClose) {
+ if (event.keyCode === ESCAPE && topDialog &&
+ !topDialog._containerInstance.dialogConfig.disableClose) {
+
topDialog.close();
}
}
diff --git a/src/lib/icon/icon-registry.ts b/src/lib/icon/icon-registry.ts
index bea95e419fa7..a10e93b04ec7 100644
--- a/src/lib/icon/icon-registry.ts
+++ b/src/lib/icon/icon-registry.ts
@@ -11,6 +11,7 @@ import 'rxjs/add/operator/do';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/finally';
import 'rxjs/add/operator/catch';
+import 'rxjs/add/observable/throw';
/**
diff --git a/src/lib/icon/icon.ts b/src/lib/icon/icon.ts
index 260cf0ac2a71..d5922050f5a7 100644
--- a/src/lib/icon/icon.ts
+++ b/src/lib/icon/icon.ts
@@ -17,7 +17,7 @@ import {
import {HttpModule, Http} from '@angular/http';
import {DomSanitizer} from '@angular/platform-browser';
import {MdError, CompatibilityModule} from '../core';
-import {MdIconRegistry} from './icon-registry';
+import {MdIconRegistry, MdIconNameNotFoundError} from './icon-registry';
export {MdIconRegistry} from './icon-registry';
/** Exception thrown when an invalid icon name is passed to an md-icon component. */
@@ -153,7 +153,7 @@ export class MdIcon implements OnChanges, OnInit, AfterViewChecked {
const [namespace, iconName] = this._splitIconName(this.svgIcon);
this._mdIconRegistry.getNamedSvgIcon(iconName, namespace).first().subscribe(
svg => this._setSvgElement(svg),
- (err: any) => console.log(`Error retrieving icon: ${err}`));
+ (err: MdIconNameNotFoundError) => console.log(`Error retrieving icon: ${err.message}`));
}
}
if (this._usingFontIcon()) {
diff --git a/src/lib/select/select.html b/src/lib/select/select.html
index 4e36b2187800..d3ae2a6ca256 100644
--- a/src/lib/select/select.html
+++ b/src/lib/select/select.html
@@ -1,9 +1,14 @@
- {{ placeholder }}
+ {{ placeholder }}
{{ selected?.viewValue }}
+
diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts
index 59c2e5de58da..ab82b27c9634 100644
--- a/src/lib/select/select.spec.ts
+++ b/src/lib/select/select.spec.ts
@@ -11,7 +11,7 @@ import {
} from '@angular/core';
import {MdSelectModule} from './index';
import {OverlayContainer} from '../core/overlay/overlay-container';
-import {MdSelect} from './select';
+import {MdSelect, MdSelectFloatPlaceholderType} from './select';
import {MdOption} from '../core/option/option';
import {Dir} from '../core/rtl/dir';
import {
@@ -35,6 +35,7 @@ describe('MdSelect', () => {
SelectWithChangeEvent,
CustomSelectAccessor,
CompWithCustomSelect,
+ FloatPlaceholderSelect,
SelectWithErrorSibling,
ThrowsErrorOnInit,
BasicSelectOnPush
@@ -590,12 +591,12 @@ describe('MdSelect', () => {
});
it('should float the placeholder when the panel is open and unselected', () => {
- expect(fixture.componentInstance.select._placeholderState)
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState())
.toEqual('', 'Expected placeholder to initially have a normal position.');
trigger.click();
fixture.detectChanges();
- expect(fixture.componentInstance.select._placeholderState)
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState())
.toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.');
const backdrop =
@@ -603,7 +604,7 @@ describe('MdSelect', () => {
backdrop.click();
fixture.detectChanges();
- expect(fixture.componentInstance.select._placeholderState)
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState())
.toEqual('', 'Expected placeholder to animate back down to normal position.');
});
@@ -616,7 +617,7 @@ describe('MdSelect', () => {
expect(placeholderEl.classList)
.toContain('mat-floating-placeholder', 'Expected placeholder to display as floating.');
- expect(fixture.componentInstance.select._placeholderState)
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState())
.toEqual('', 'Expected animation state to be empty to avoid animation.');
});
@@ -625,7 +626,8 @@ describe('MdSelect', () => {
trigger.click();
fixture.detectChanges();
- expect(fixture.componentInstance.select._placeholderState).toEqual('floating-rtl');
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState())
+ .toEqual('floating-rtl');
});
@@ -1285,6 +1287,39 @@ describe('MdSelect', () => {
});
});
+ describe('floatPlaceholder option', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FloatPlaceholderSelect);
+ });
+
+ it('should be able to disable the floating placeholder', () => {
+ let placeholder = fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;
+
+ fixture.componentInstance.floatPlaceholder = 'never';
+ fixture.detectChanges();
+
+ expect(placeholder.style.visibility).toBe('visible');
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy();
+
+ fixture.componentInstance.control.setValue('pizza-1');
+ fixture.detectChanges();
+
+ expect(placeholder.style.visibility).toBe('hidden');
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy();
+ });
+
+ it('should be able to always float the placeholder', () => {
+ expect(fixture.componentInstance.control.value).toBeFalsy();
+
+ fixture.componentInstance.floatPlaceholder = 'always';
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBe('floating-ltr');
+ });
+ });
+
describe('with OnPush change detection', () => {
let fixture: ComponentFixture;
let trigger: HTMLElement;
@@ -1309,6 +1344,7 @@ describe('MdSelect', () => {
});
});
+
@Component({
selector: 'basic-select',
template: `
@@ -1529,6 +1565,29 @@ class BasicSelectOnPush {
@ViewChildren(MdOption) options: QueryList;
}
+@Component({
+ selector: 'floating-placeholder-select',
+ template: `
+
+
+ {{ food.viewValue }}
+
+
+ `,
+})
+class FloatPlaceholderSelect {
+ floatPlaceholder: MdSelectFloatPlaceholderType = 'auto';
+ control = new FormControl();
+ foods: any[] = [
+ { value: 'steak-0', viewValue: 'Steak' },
+ { value: 'pizza-1', viewValue: 'Pizza' },
+ { value: 'tacos-2', viewValue: 'Tacos'}
+ ];
+
+ @ViewChild(MdSelect) select: MdSelect;
+}
+
/**
* TODO: Move this to core testing utility until Angular has event faking
diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts
index c1c0a3e37978..1a92699e5437 100644
--- a/src/lib/select/select.ts
+++ b/src/lib/select/select.ts
@@ -73,6 +73,9 @@ export class MdSelectChange {
constructor(public source: MdSelect, public value: any) { }
}
+/** Allowed values for the floatPlaceholder option. */
+export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto';
+
@Component({
moduleId: module.id,
selector: 'md-select, mat-select',
@@ -128,7 +131,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
private _placeholder: string;
/** The animation state of the placeholder. */
- _placeholderState = '';
+ private _placeholderState = '';
/**
* The width of the trigger. Must be saved to set the min width of the overlay panel
@@ -226,6 +229,14 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
get required() { return this._required; }
set required(value: any) { this._required = coerceBooleanProperty(value); }
+ /** Whether to float the placeholder text. */
+ @Input()
+ get floatPlaceholder(): MdSelectFloatPlaceholderType { return this._floatPlaceholder; }
+ set floatPlaceholder(value: MdSelectFloatPlaceholderType) {
+ this._floatPlaceholder = value || 'auto';
+ }
+ private _floatPlaceholder: MdSelectFloatPlaceholderType = 'auto';
+
/** Event emitted when the select has been opened. */
@Output() onOpen: EventEmitter = new EventEmitter();
@@ -280,7 +291,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
return;
}
this._calculateOverlayPosition();
- this._placeholderState = this._isRtl() ? 'floating-rtl' : 'floating-ltr';
+ this._placeholderState = this._floatPlaceholderState();
this._panelOpen = true;
}
@@ -588,6 +599,28 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
return clampValue(0, optimalScrollPosition, maxScroll);
}
+ /**
+ * Figures out the appropriate animation state for the placeholder.
+ */
+ _getPlaceholderAnimationState(): string {
+ if (this.floatPlaceholder === 'never') {
+ return '';
+ }
+
+ if (this.floatPlaceholder === 'always') {
+ return this._floatPlaceholderState();
+ }
+
+ return this._placeholderState;
+ }
+
+ /**
+ * Determines the CSS `visibility` of the placeholder element.
+ */
+ _getPlaceholderVisibility(): 'visible'|'hidden' {
+ return (this.floatPlaceholder !== 'never' || !this.selected) ? 'visible' : 'hidden';
+ }
+
/**
* Calculates the y-offset of the select's overlay panel in relation to the
* top start corner of the trigger. It has to be adjusted in order for the
@@ -699,6 +732,10 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
return `50% ${originY}px 0px`;
}
+ /** Figures out the floating placeholder state value. */
+ private _floatPlaceholderState(): string {
+ return this._isRtl() ? 'floating-rtl' : 'floating-ltr';
+ }
}
/** Clamps a value n between min and max values. */
diff --git a/src/lib/slide-toggle/slide-toggle.html b/src/lib/slide-toggle/slide-toggle.html
index 2e4f7c9ae571..239d647d814a 100644
--- a/src/lib/slide-toggle/slide-toggle.html
+++ b/src/lib/slide-toggle/slide-toggle.html
@@ -12,7 +12,7 @@
+ [mdRippleDisabled]="disableRipple || disabled">