mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(autocomplete): placeholder not animating on focus (#3992)
Fixes #5755.
This commit is contained in:
committed by
Kara
parent
e41d0f3e9b
commit
ff54969d17
@@ -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 {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[class.mat-form-field-float]="_canPlaceholderFloat"
|
||||
[class.mat-accent]="color == 'accent'"
|
||||
[class.mat-warn]="color == 'warn'"
|
||||
#placeholder
|
||||
*ngIf="_hasPlaceholder()">
|
||||
<ng-content select="md-placeholder, mat-placeholder"></ng-content>
|
||||
{{_control.placeholder}}
|
||||
|
||||
@@ -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<any>;
|
||||
@ContentChild(MdPlaceholder) _placeholderChild: MdPlaceholder;
|
||||
@ContentChildren(MdError) _errorChildren: QueryList<MdError>;
|
||||
@@ -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).
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user