Skip to content

Commit ebe2fed

Browse files
committed
fix(autocomplete): placeholder should float while panel is open
1 parent fbed180 commit ebe2fed

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
AfterContentInit, Directive, ElementRef, Input, ViewContainerRef, Optional, OnDestroy
2+
AfterContentInit, Directive, ElementRef, Host, Input, ViewContainerRef, Optional, OnDestroy
33
} from '@angular/core';
44
import {NgControl} from '@angular/forms';
55
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
@@ -15,6 +15,7 @@ import 'rxjs/add/observable/merge';
1515
import {Dir} from '../core/rtl/dir';
1616
import 'rxjs/add/operator/startWith';
1717
import 'rxjs/add/operator/switchMap';
18+
import {MdInputContainer, FloatPlaceholderType} from '../input/input-container';
1819

1920
/**
2021
* The following style constants are necessary to save here in order
@@ -59,7 +60,8 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
5960

6061
constructor(private _element: ElementRef, private _overlay: Overlay,
6162
private _viewContainerRef: ViewContainerRef,
62-
@Optional() private _controlDir: NgControl, @Optional() private _dir: Dir) {}
63+
@Optional() private _controlDir: NgControl, @Optional() private _dir: Dir,
64+
@Optional() @Host() private _inputContainer: MdInputContainer) {}
6365

6466
ngAfterContentInit() {
6567
this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options);
@@ -90,6 +92,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
9092
}
9193

9294
this._panelOpen = true;
95+
this._floatPlaceholder('always');
9396
}
9497

9598
/** Closes the autocomplete suggestion panel. */
@@ -99,6 +102,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
99102
}
100103

101104
this._panelOpen = false;
105+
this._floatPlaceholder('auto');
102106
}
103107

104108
/**
@@ -135,6 +139,17 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
135139
}
136140
}
137141

142+
/**
143+
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
144+
* This causes the value to jump when selecting an option with the mouse.
145+
* This method manually floats the placeholder until the panel can be closed.
146+
*/
147+
private _floatPlaceholder(state: FloatPlaceholderType): void {
148+
if (this._inputContainer) {
149+
this._inputContainer.floatPlaceholder = state;
150+
}
151+
}
152+
138153
/**
139154
* Given that we are not actually focusing active options, we must manually adjust scroll
140155
* to reveal options below the fold. First, we find the offset of the option from the top

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Subscription} from 'rxjs/Subscription';
1010
import {ENTER, DOWN_ARROW, SPACE} from '../core/keyboard/keycodes';
1111
import {MdOption} from '../core/option/option';
1212
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
13+
import {MdInputContainer} from '../input/input-container';
1314

1415
describe('MdAutocomplete', () => {
1516
let overlayContainerElement: HTMLElement;
@@ -172,16 +173,35 @@ describe('MdAutocomplete', () => {
172173
});
173174
}));
174175

176+
it('should keep the label floating until the panel closes', () => {
177+
fixture.componentInstance.trigger.openPanel();
178+
fixture.detectChanges();
179+
180+
dispatchEvent('blur', input);
181+
fixture.detectChanges();
182+
183+
expect(fixture.componentInstance.inputContainer.floatPlaceholder)
184+
.toEqual('always', 'Expected placeholder to keep floating on blur.');
185+
186+
const backdrop =
187+
overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
188+
backdrop.click();
189+
fixture.detectChanges();
190+
191+
expect(fixture.componentInstance.inputContainer.floatPlaceholder)
192+
.toEqual('auto', 'Expected placeholder to return to auto state after panel closes.');
193+
});
194+
175195
});
176196

177197
it('should have the correct text direction in RTL', () => {
178198
dir = 'rtl';
179199

180-
const fixture = TestBed.createComponent(SimpleAutocomplete);
181-
fixture.detectChanges();
200+
const rtlFixture = TestBed.createComponent(SimpleAutocomplete);
201+
rtlFixture.detectChanges();
182202

183-
fixture.componentInstance.trigger.openPanel();
184-
fixture.detectChanges();
203+
rtlFixture.componentInstance.trigger.openPanel();
204+
rtlFixture.detectChanges();
185205

186206
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
187207
expect(overlayPane.getAttribute('dir')).toEqual('rtl');
@@ -405,6 +425,7 @@ describe('MdAutocomplete', () => {
405425
[1, 2, 3, 4, 5].forEach(() => {
406426
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
407427
});
428+
408429
fixture.detectChanges();
409430

410431
// Expect option bottom minus the panel height (288 - 256 = 32)
@@ -595,6 +616,7 @@ class SimpleAutocomplete implements OnDestroy {
595616
valueSub: Subscription;
596617

597618
@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
619+
@ViewChild(MdInputContainer) inputContainer: MdInputContainer;
598620
@ViewChildren(MdOption) options: QueryList<MdOption>;
599621

600622
states = [

0 commit comments

Comments
 (0)