Skip to content

Commit cfc7c55

Browse files
committed
fix(autocomplete): placeholder should float while panel is open
1 parent 00df845 commit cfc7c55

File tree

2 files changed

+44
-7
lines changed

2 files changed

+44
-7
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Directive,
44
ElementRef,
55
forwardRef,
6+
Host,
67
Input,
78
NgZone,
89
Optional,
@@ -26,6 +27,7 @@ import 'rxjs/add/observable/of';
2627
import 'rxjs/add/observable/merge';
2728
import 'rxjs/add/operator/startWith';
2829
import 'rxjs/add/operator/switchMap';
30+
import {MdInputContainer, FloatPlaceholderType} from '../input/input-container';
2931

3032
/**
3133
* The following style constants are necessary to save here in order
@@ -92,7 +94,8 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
9294

9395
constructor(private _element: ElementRef, private _overlay: Overlay,
9496
private _viewContainerRef: ViewContainerRef,
95-
@Optional() private _dir: Dir, private _zone: NgZone) {}
97+
@Optional() private _dir: Dir, private _zone: NgZone,
98+
@Optional() @Host() private _inputContainer: MdInputContainer) {}
9699

97100
ngAfterContentInit() {
98101
this._keyManager = new ActiveDescendantKeyManager(this.autocomplete.options).withWrap();
@@ -123,6 +126,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
123126
}
124127

125128
this._panelOpen = true;
129+
this._floatPlaceholder('always');
126130
}
127131

128132
/** Closes the autocomplete suggestion panel. */
@@ -132,6 +136,7 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
132136
}
133137

134138
this._panelOpen = false;
139+
this._floatPlaceholder('auto');
135140
}
136141

137142
/**
@@ -214,6 +219,17 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
214219
}
215220
}
216221

222+
/**
223+
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
224+
* This causes the value to jump when selecting an option with the mouse.
225+
* This method manually floats the placeholder until the panel can be closed.
226+
*/
227+
private _floatPlaceholder(state: FloatPlaceholderType): void {
228+
if (this._inputContainer) {
229+
this._inputContainer.floatPlaceholder = state;
230+
}
231+
}
232+
217233
/**
218234
* Given that we are not actually focusing active options, we must manually adjust scroll
219235
* 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: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {MdOption} from '../core/option/option';
1212
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
1313
import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
1414
import {MdAutocomplete} from './autocomplete';
15-
15+
import {MdInputContainer} from '../input/input-container';
1616

1717
describe('MdAutocomplete', () => {
1818
let overlayContainerElement: HTMLElement;
@@ -181,16 +181,36 @@ describe('MdAutocomplete', () => {
181181
.toEqual('', `Expected panel to close when options list is empty.`);
182182
});
183183
}));
184+
185+
it('should keep the label floating until the panel closes', () => {
186+
fixture.componentInstance.trigger.openPanel();
187+
fixture.detectChanges();
188+
189+
dispatchEvent('blur', input);
190+
fixture.detectChanges();
191+
192+
expect(fixture.componentInstance.inputContainer.floatPlaceholder)
193+
.toEqual('always', 'Expected placeholder to keep floating on blur.');
194+
195+
const backdrop =
196+
overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement;
197+
backdrop.click();
198+
fixture.detectChanges();
199+
200+
expect(fixture.componentInstance.inputContainer.floatPlaceholder)
201+
.toEqual('auto', 'Expected placeholder to return to auto state after panel closes.');
202+
});
203+
184204
});
185205

186206
it('should have the correct text direction in RTL', () => {
187207
dir = 'rtl';
188208

189-
const fixture = TestBed.createComponent(SimpleAutocomplete);
190-
fixture.detectChanges();
209+
const rtlFixture = TestBed.createComponent(SimpleAutocomplete);
210+
rtlFixture.detectChanges();
191211

192-
fixture.componentInstance.trigger.openPanel();
193-
fixture.detectChanges();
212+
rtlFixture.componentInstance.trigger.openPanel();
213+
rtlFixture.detectChanges();
194214

195215
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
196216
expect(overlayPane.getAttribute('dir')).toEqual('rtl');
@@ -603,8 +623,8 @@ describe('MdAutocomplete', () => {
603623
// Expect option bottom minus the panel height (288 - 256 = 32)
604624
expect(scrollContainer.scrollTop).toEqual(32, `Expected panel to reveal the sixth option.`);
605625
});
606-
}));
607626

627+
}));
608628
});
609629

610630
describe('aria', () => {
@@ -793,6 +813,7 @@ class SimpleAutocomplete implements OnDestroy {
793813

794814
@ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger;
795815
@ViewChild(MdAutocomplete) panel: MdAutocomplete;
816+
@ViewChild(MdInputContainer) inputContainer: MdInputContainer;
796817
@ViewChildren(MdOption) options: QueryList<MdOption>;
797818

798819
states = [

0 commit comments

Comments
 (0)