diff --git a/src/lib/select/select-animations.ts b/src/lib/select/select-animations.ts index 677633853868..765158994b19 100644 --- a/src/lib/select/select-animations.ts +++ b/src/lib/select/select-animations.ts @@ -20,14 +20,15 @@ import { * depending on the text direction of the application. */ export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [ - state('normal', style({ - transform: `translate3d(0, 0, 0) scale(1)` - })), state('floating-ltr', style({ - transform: `translate3d(-2px, -22px, 0) scale(0.75)` + top: '-22px', + left: '-2px', + transform: `scale(0.75)` })), state('floating-rtl', style({ - transform: `translate3d(2px, -22px, 0) scale(0.75)` + top: '-22px', + left: '2px', + transform: `scale(0.75)` })), transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`)) ]); diff --git a/src/lib/select/select.html b/src/lib/select/select.html index 0de3f7ae2162..0d39c63ac590 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -1,5 +1,6 @@
- {{ placeholder }} + {{ placeholder }} {{ selected?.viewValue }}
diff --git a/src/lib/select/select.scss b/src/lib/select/select.scss index 849294dfb2d0..0489f1fb09a8 100644 --- a/src/lib/select/select.scss +++ b/src/lib/select/select.scss @@ -32,11 +32,26 @@ md-select { } .md-select-placeholder { + position: relative; padding: 0 2px; transform-origin: left top; + // These values are duplicated from animation code in order to + // allow placeholders to sometimes float without animating, + // for example when the value is set programmatically. + // TODO(kara): Change when animations API supports skipping animation. + &.md-floating-placeholder { + top: -22px; + left: -2px; + transform: scale(0.75); + } + [dir='rtl'] & { transform-origin: right top; + + &.md-floating-placeholder { + left: 2px; + } } // TODO: Double-check accessibility of this style diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 023d4d3e8c66..4e9f9c7a80ae 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -456,30 +456,35 @@ describe('MdSelect', () => { trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement; }); - it('should float the placeholder when the panel is open', () => { - expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('normal'); + it('should float the placeholder when the panel is open and unselected', () => { + expect(fixture.componentInstance.select._placeholderState) + .toEqual('', 'Expected placeholder to initially have a normal position.'); trigger.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr'); + expect(fixture.componentInstance.select._placeholderState) + .toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.'); const backdrop = overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement; backdrop.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('normal'); + expect(fixture.componentInstance.select._placeholderState) + .toEqual('', 'Expected placeholder to animate back down to normal position.'); }); - it('should float the placeholder when there is a selection', () => { - trigger.click(); + it('should float the placeholder without animation when value is set', () => { + fixture.componentInstance.control.setValue('pizza-1'); fixture.detectChanges(); - const option = overlayContainerElement.querySelector('md-option') as HTMLElement; - option.click(); - fixture.detectChanges(); + const placeholderEl = + fixture.debugElement.query(By.css('.md-select-placeholder')).nativeElement; - expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr'); + expect(placeholderEl.classList) + .toContain('md-floating-placeholder', 'Expected placeholder to display as floating.'); + expect(fixture.componentInstance.select._placeholderState) + .toEqual('', 'Expected animation state to be empty to avoid animation.'); }); it('should use the floating-rtl state when the dir is rtl', () => { @@ -487,7 +492,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-rtl'); + expect(fixture.componentInstance.select._placeholderState).toEqual('floating-rtl'); }); }); diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 974bac7ad6f6..4820f180bf57 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -5,7 +5,6 @@ import { ElementRef, EventEmitter, Input, - NgZone, OnDestroy, Optional, Output, @@ -115,6 +114,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr /** The scroll position of the overlay panel, calculated to center the selected option. */ private _scrollTop = 0; + /** The animation state of the placeholder. */ + _placeholderState = ''; + /** Manages keyboard events for options in the panel. */ _keyManager: ListKeyManager; @@ -193,8 +195,8 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr @Output() onClose = new EventEmitter(); constructor(private _element: ElementRef, private _renderer: Renderer, - private _ngZone: NgZone, private _viewportRuler: ViewportRuler, - @Optional() private _dir: Dir, @Optional() public _control: NgControl) { + private _viewportRuler: ViewportRuler, @Optional() private _dir: Dir, + @Optional() public _control: NgControl) { if (this._control) { this._control.valueAccessor = this; } @@ -223,12 +225,16 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return; } this._calculateOverlayPosition(); + this._placeholderState = this._isRtl() ? 'floating-rtl' : 'floating-ltr'; this._panelOpen = true; } /** Closes the overlay panel and focuses the host element. */ close(): void { this._panelOpen = false; + if (!this._selected) { + this._placeholderState = ''; + } this._focusHost(); } @@ -242,7 +248,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr // the select's child options have been created. It's necessary to call // writeValue() again after the options have been created to ensure any // initial view value is set. - this._ngZone.onStable.first().subscribe(() => this.writeValue(value)); + Promise.resolve(null).then(() => this.writeValue(value)); return; } @@ -300,15 +306,6 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return this._getTriggerRect().width; } - /** The animation state of the placeholder. */ - _getPlaceholderState(): string { - if (this.panelOpen || this.selected) { - return this._isRtl() ? 'floating-rtl' : 'floating-ltr'; - } else { - return 'normal'; - } - } - /** Ensures the panel opens if activated by the keyboard. */ _handleKeydown(event: KeyboardEvent): void { if (event.keyCode === ENTER || event.keyCode === SPACE) { @@ -403,7 +400,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr private _onSelect(option: MdOption): void { this._selected = option; this._updateOptions(); - this.close(); + if (this.panelOpen) { + this.close(); + } } /** Deselect each option that doesn't match the current selection. */