From cfc7c5580cd6eb9841770baa9e12e25136c0dea8 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 19 Jan 2017 18:44:54 -0800 Subject: [PATCH] fix(autocomplete): placeholder should float while panel is open --- src/lib/autocomplete/autocomplete-trigger.ts | 18 ++++++++++- src/lib/autocomplete/autocomplete.spec.ts | 33 ++++++++++++++++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 8869723786a2..e15e902a54d9 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -3,6 +3,7 @@ import { Directive, ElementRef, forwardRef, + Host, Input, NgZone, Optional, @@ -26,6 +27,7 @@ import 'rxjs/add/observable/of'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/switchMap'; +import {MdInputContainer, FloatPlaceholderType} from '../input/input-container'; /** * The following style constants are necessary to save here in order @@ -92,7 +94,8 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce constructor(private _element: ElementRef, private _overlay: Overlay, private _viewContainerRef: ViewContainerRef, - @Optional() private _dir: Dir, private _zone: NgZone) {} + @Optional() private _dir: Dir, private _zone: NgZone, + @Optional() @Host() private _inputContainer: MdInputContainer) {} ngAfterContentInit() { this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options).withWrap(); @@ -123,6 +126,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce } this._panelOpen = true; + this._floatPlaceholder('always'); } /** Closes the autocomplete suggestion panel. */ @@ -132,6 +136,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce } this._panelOpen = false; + this._floatPlaceholder('auto'); } /** @@ -214,6 +219,17 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce } } + /** + * 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. + */ + private _floatPlaceholder(state: FloatPlaceholderType): void { + if (this._inputContainer) { + this._inputContainer.floatPlaceholder = state; + } + } + /** * Given that we are not actually focusing active options, we must manually adjust scroll * to reveal options below the fold. First, we find the offset of the option from the top diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index e18973986ced..26fa905bf984 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -12,7 +12,7 @@ import {MdOption} from '../core/option/option'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler'; import {MdAutocomplete} from './autocomplete'; - +import {MdInputContainer} from '../input/input-container'; describe('MdAutocomplete', () => { let overlayContainerElement: HTMLElement; @@ -181,16 +181,36 @@ describe('MdAutocomplete', () => { .toEqual('', `Expected panel to close when options list is empty.`); }); })); + + it('should keep the label floating until the panel closes', () => { + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + + dispatchEvent('blur', input); + fixture.detectChanges(); + + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('always', 'Expected placeholder to keep floating on blur.'); + + const backdrop = + overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; + backdrop.click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.inputContainer.floatPlaceholder) + .toEqual('auto', 'Expected placeholder to return to auto state after panel closes.'); + }); + }); it('should have the correct text direction in RTL', () => { dir = 'rtl'; - const fixture = TestBed.createComponent(SimpleAutocomplete); - fixture.detectChanges(); + const rtlFixture = TestBed.createComponent(SimpleAutocomplete); + rtlFixture.detectChanges(); - fixture.componentInstance.trigger.openPanel(); - fixture.detectChanges(); + rtlFixture.componentInstance.trigger.openPanel(); + rtlFixture.detectChanges(); const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); expect(overlayPane.getAttribute('dir')).toEqual('rtl'); @@ -603,8 +623,8 @@ describe('MdAutocomplete', () => { // Expect option bottom minus the panel height (288 - 256 = 32) expect(scrollContainer.scrollTop).toEqual(32, `Expected panel to reveal the sixth option.`); }); - })); + })); }); describe('aria', () => { @@ -793,6 +813,7 @@ class SimpleAutocomplete implements OnDestroy { @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; @ViewChild(MdAutocomplete) panel: MdAutocomplete; + @ViewChild(MdInputContainer) inputContainer: MdInputContainer; @ViewChildren(MdOption) options: QueryList; states = [