diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts
index 207701b3291..c0d0281f1a4 100644
--- a/src/lib/autocomplete/autocomplete-trigger.ts
+++ b/src/lib/autocomplete/autocomplete-trigger.ts
@@ -109,9 +109,9 @@ export function getMdAutocompleteMissingPanelError(): Error {
'[attr.aria-owns]': 'autocomplete?.id',
// Note: we use `focusin`, as opposed to `focus`, in order to open the panel
// a little earlier. This avoids issues where IE delays the focusing of the input.
- '(focusin)': 'openPanel()',
- '(input)': '_handleInput($event)',
+ '(focusin)': '_handleFocus()',
'(blur)': '_onTouched()',
+ '(input)': '_handleInput($event)',
'(keydown)': '_handleKeydown($event)',
},
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
@@ -169,26 +169,8 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** Opens the autocomplete suggestion panel. */
openPanel(): void {
- if (!this.autocomplete) {
- throw getMdAutocompleteMissingPanelError();
- }
-
- if (!this._overlayRef) {
- this._createOverlay();
- } else {
- /** Update the panel width, in case the host width has changed */
- this._overlayRef.getState().width = this._getHostWidth();
- this._overlayRef.updateSize();
- }
-
- if (this._overlayRef && !this._overlayRef.hasAttached()) {
- this._overlayRef.attach(this._portal);
- this._closingActionsSubscription = this._subscribeToClosingActions();
- }
-
- this.autocomplete._setVisibility();
+ this._attachOverlay();
this._floatPlaceholder();
- this._panelOpen = true;
}
/** Closes the autocomplete suggestion panel. */
@@ -327,14 +309,25 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
}
}
+ _handleFocus(): void {
+ this._attachOverlay();
+ this._floatPlaceholder(true);
+ }
+
/**
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
* This causes the value to jump when selecting an option with the mouse.
* This method manually floats the placeholder until the panel can be closed.
+ * @param shouldAnimate Whether the placeholder should be animated when it is floated.
*/
- private _floatPlaceholder(): void {
+ private _floatPlaceholder(shouldAnimate = false): void {
if (this._formField && this._formField.floatPlaceholder === 'auto') {
- this._formField.floatPlaceholder = 'always';
+ if (shouldAnimate) {
+ this._formField._animateAndLockPlaceholder();
+ } else {
+ this._formField.floatPlaceholder = 'always';
+ }
+
this._manuallyFloatingPlaceholder = true;
}
}
@@ -450,9 +443,27 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
});
}
- private _createOverlay(): void {
- this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
- this._overlayRef = this._overlay.create(this._getOverlayConfig());
+ private _attachOverlay(): void {
+ if (!this.autocomplete) {
+ throw getMdAutocompleteMissingPanelError();
+ }
+
+ if (!this._overlayRef) {
+ this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
+ this._overlayRef = this._overlay.create(this._getOverlayConfig());
+ } else {
+ /** Update the panel width, in case the host width has changed */
+ this._overlayRef.getState().width = this._getHostWidth();
+ this._overlayRef.updateSize();
+ }
+
+ if (this._overlayRef && !this._overlayRef.hasAttached()) {
+ this._overlayRef.attach(this._portal);
+ this._closingActionsSubscription = this._subscribeToClosingActions();
+ }
+
+ this.autocomplete._setVisibility();
+ this._panelOpen = true;
}
private _getOverlayConfig(): OverlayState {
diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts
index f9bf7961fb2..91f7f6a5e0f 100644
--- a/src/lib/autocomplete/autocomplete.spec.ts
+++ b/src/lib/autocomplete/autocomplete.spec.ts
@@ -377,6 +377,16 @@ describe('MdAutocomplete', () => {
.toContain('mat-autocomplete-visible', 'Expected panel to be visible.');
}));
+ it('should animate the placeholder when the input is focused', () => {
+ const inputContainer = fixture.componentInstance.formField;
+
+ spyOn(inputContainer, '_animateAndLockPlaceholder');
+ expect(inputContainer._animateAndLockPlaceholder).not.toHaveBeenCalled();
+
+ dispatchFakeEvent(fixture.debugElement.query(By.css('input')).nativeElement, 'focusin');
+ expect(inputContainer._animateAndLockPlaceholder).toHaveBeenCalled();
+ });
+
});
it('should have the correct text direction in RTL', () => {
diff --git a/src/lib/form-field/form-field.html b/src/lib/form-field/form-field.html
index cfb7bfedab8..d34e8b7c40f 100644
--- a/src/lib/form-field/form-field.html
+++ b/src/lib/form-field/form-field.html
@@ -16,6 +16,7 @@
[class.mat-form-field-float]="_canPlaceholderFloat"
[class.mat-accent]="color == 'accent'"
[class.mat-warn]="color == 'warn'"
+ #placeholder
*ngIf="_hasPlaceholder()">
{{_control.placeholder}}
diff --git a/src/lib/form-field/form-field.ts b/src/lib/form-field/form-field.ts
index eba04bde979..bf545b58453 100644
--- a/src/lib/form-field/form-field.ts
+++ b/src/lib/form-field/form-field.ts
@@ -35,13 +35,14 @@ import {
MD_PLACEHOLDER_GLOBAL_OPTIONS,
PlaceholderOptions
} from '../core/placeholder/placeholder-options';
-import {startWith} from '@angular/cdk/rxjs';
+import {startWith, first} from '@angular/cdk/rxjs';
import {MdError} from './error';
import {MdFormFieldControl} from './form-field-control';
import {MdHint} from './hint';
import {MdPlaceholder} from './placeholder';
import {MdPrefix} from './prefix';
import {MdSuffix} from './suffix';
+import {fromEvent} from 'rxjs/observable/fromEvent';
let nextUniqueId = 0;
@@ -104,8 +105,13 @@ export class MdFormField implements AfterViewInit, AfterContentInit, AfterConten
}
private _hideRequiredMarker: boolean;
+ /** Override for the logic that disables the placeholder animation in certain cases. */
+ private _showAlwaysAnimate = false;
+
/** Whether the floating label should always float or not. */
- get _shouldAlwaysFloat() { return this._floatPlaceholder === 'always'; }
+ get _shouldAlwaysFloat() {
+ return this._floatPlaceholder === 'always' && !this._showAlwaysAnimate;
+ }
/** Whether the placeholder can float or not. */
get _canPlaceholderFloat() { return this._floatPlaceholder !== 'never'; }
@@ -139,6 +145,7 @@ export class MdFormField implements AfterViewInit, AfterContentInit, AfterConten
/** Reference to the form field's underline element. */
@ViewChild('underline') underlineRef: ElementRef;
@ViewChild('connectionContainer') _connectionContainerRef: ElementRef;
+ @ViewChild('placeholder') private _placeholder: ElementRef;
@ContentChild(MdFormFieldControl) _control: MdFormFieldControl;
@ContentChild(MdPlaceholder) _placeholderChild: MdPlaceholder;
@ContentChildren(MdError) _errorChildren: QueryList;
@@ -210,6 +217,20 @@ export class MdFormField implements AfterViewInit, AfterContentInit, AfterConten
this._control.errorState) ? 'error' : 'hint';
}
+ /** Animates the placeholder up and locks it in position. */
+ _animateAndLockPlaceholder(): void {
+ if (this._placeholder && this._canPlaceholderFloat) {
+ this._showAlwaysAnimate = true;
+ this._floatPlaceholder = 'always';
+
+ first.call(fromEvent(this._placeholder.nativeElement, 'transitionend')).subscribe(() => {
+ this._showAlwaysAnimate = false;
+ });
+
+ this._changeDetectorRef.markForCheck();
+ }
+ }
+
/**
* Ensure that there is only one placeholder (either `placeholder` attribute on the child control
* or child element with the `md-placeholder` directive).
diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts
index 3c8b344ca4d..ade499ec53d 100644
--- a/src/lib/input/input.spec.ts
+++ b/src/lib/input/input.spec.ts
@@ -15,7 +15,7 @@ import {MdInputModule} from './index';
import {MdInput} from './input';
import {Platform} from '../core/platform/platform';
import {PlatformModule} from '../core/platform/index';
-import {wrappedErrorMessage, dispatchFakeEvent} from '@angular/cdk/testing';
+import {wrappedErrorMessage, dispatchFakeEvent, createFakeEvent} from '@angular/cdk/testing';
import {
MdFormField,
MdFormFieldModule,
@@ -656,6 +656,33 @@ describe('MdInput without forms', function () {
expect(container.classList).toContain('mat-focused');
});
+
+ it('should be able to animate the placeholder up and lock it in position', () => {
+ let fixture = TestBed.createComponent(MdInputTextTestController);
+ fixture.detectChanges();
+
+ let inputContainer = fixture.debugElement.query(By.directive(MdFormField))
+ .componentInstance as MdFormField;
+ let placeholder = fixture.debugElement.query(By.css('.mat-input-placeholder')).nativeElement;
+
+ expect(inputContainer.floatPlaceholder).toBe('auto');
+
+ inputContainer._animateAndLockPlaceholder();
+ fixture.detectChanges();
+
+ expect(inputContainer._shouldAlwaysFloat).toBe(false);
+ expect(inputContainer.floatPlaceholder).toBe('always');
+
+ const fakeEvent = Object.assign(createFakeEvent('transitionend'), {
+ propertyName: 'transform'
+ });
+
+ placeholder.dispatchEvent(fakeEvent);
+ fixture.detectChanges();
+
+ expect(inputContainer._shouldAlwaysFloat).toBe(true);
+ expect(inputContainer.floatPlaceholder).toBe('always');
+ });
});
describe('MdInput with forms', () => {