Skip to content

Commit 74772e1

Browse files
karammalerba
authored andcommitted
fix(select): only animate placeholder when no selection (#2054)
1 parent 815d9c3 commit 74772e1

File tree

5 files changed

+52
-31
lines changed

5 files changed

+52
-31
lines changed

src/lib/select/select-animations.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ import {
2020
* depending on the text direction of the application.
2121
*/
2222
export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [
23-
state('normal', style({
24-
transform: `translate3d(0, 0, 0) scale(1)`
25-
})),
2623
state('floating-ltr', style({
27-
transform: `translate3d(-2px, -22px, 0) scale(0.75)`
24+
top: '-22px',
25+
left: '-2px',
26+
transform: `scale(0.75)`
2827
})),
2928
state('floating-rtl', style({
30-
transform: `translate3d(2px, -22px, 0) scale(0.75)`
29+
top: '-22px',
30+
left: '2px',
31+
transform: `scale(0.75)`
3132
})),
3233
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`))
3334
]);

src/lib/select/select.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
2-
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
2+
<span class="md-select-placeholder" [class.md-floating-placeholder]="this.selected"
3+
[@transformPlaceholder]="_placeholderState"> {{ placeholder }} </span>
34
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
45
<span class="md-select-arrow"></span>
56
</div>

src/lib/select/select.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,26 @@ md-select {
3232
}
3333

3434
.md-select-placeholder {
35+
position: relative;
3536
padding: 0 2px;
3637
transform-origin: left top;
3738

39+
// These values are duplicated from animation code in order to
40+
// allow placeholders to sometimes float without animating,
41+
// for example when the value is set programmatically.
42+
// TODO(kara): Change when animations API supports skipping animation.
43+
&.md-floating-placeholder {
44+
top: -22px;
45+
left: -2px;
46+
transform: scale(0.75);
47+
}
48+
3849
[dir='rtl'] & {
3950
transform-origin: right top;
51+
52+
&.md-floating-placeholder {
53+
left: 2px;
54+
}
4055
}
4156

4257
// TODO: Double-check accessibility of this style

src/lib/select/select.spec.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -456,38 +456,43 @@ describe('MdSelect', () => {
456456
trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
457457
});
458458

459-
it('should float the placeholder when the panel is open', () => {
460-
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('normal');
459+
it('should float the placeholder when the panel is open and unselected', () => {
460+
expect(fixture.componentInstance.select._placeholderState)
461+
.toEqual('', 'Expected placeholder to initially have a normal position.');
461462

462463
trigger.click();
463464
fixture.detectChanges();
464-
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr');
465+
expect(fixture.componentInstance.select._placeholderState)
466+
.toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.');
465467

466468
const backdrop =
467469
overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
468470
backdrop.click();
469471
fixture.detectChanges();
470472

471-
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('normal');
473+
expect(fixture.componentInstance.select._placeholderState)
474+
.toEqual('', 'Expected placeholder to animate back down to normal position.');
472475
});
473476

474-
it('should float the placeholder when there is a selection', () => {
475-
trigger.click();
477+
it('should float the placeholder without animation when value is set', () => {
478+
fixture.componentInstance.control.setValue('pizza-1');
476479
fixture.detectChanges();
477480

478-
const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
479-
option.click();
480-
fixture.detectChanges();
481+
const placeholderEl =
482+
fixture.debugElement.query(By.css('.md-select-placeholder')).nativeElement;
481483

482-
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr');
484+
expect(placeholderEl.classList)
485+
.toContain('md-floating-placeholder', 'Expected placeholder to display as floating.');
486+
expect(fixture.componentInstance.select._placeholderState)
487+
.toEqual('', 'Expected animation state to be empty to avoid animation.');
483488
});
484489

485490
it('should use the floating-rtl state when the dir is rtl', () => {
486491
dir.value = 'rtl';
487492

488493
trigger.click();
489494
fixture.detectChanges();
490-
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-rtl');
495+
expect(fixture.componentInstance.select._placeholderState).toEqual('floating-rtl');
491496
});
492497

493498
});

src/lib/select/select.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ElementRef,
66
EventEmitter,
77
Input,
8-
NgZone,
98
OnDestroy,
109
Optional,
1110
Output,
@@ -115,6 +114,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
115114
/** The scroll position of the overlay panel, calculated to center the selected option. */
116115
private _scrollTop = 0;
117116

117+
/** The animation state of the placeholder. */
118+
_placeholderState = '';
119+
118120
/** Manages keyboard events for options in the panel. */
119121
_keyManager: ListKeyManager;
120122

@@ -193,8 +195,8 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
193195
@Output() onClose = new EventEmitter();
194196

195197
constructor(private _element: ElementRef, private _renderer: Renderer,
196-
private _ngZone: NgZone, private _viewportRuler: ViewportRuler,
197-
@Optional() private _dir: Dir, @Optional() public _control: NgControl) {
198+
private _viewportRuler: ViewportRuler, @Optional() private _dir: Dir,
199+
@Optional() public _control: NgControl) {
198200
if (this._control) {
199201
this._control.valueAccessor = this;
200202
}
@@ -223,12 +225,16 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
223225
return;
224226
}
225227
this._calculateOverlayPosition();
228+
this._placeholderState = this._isRtl() ? 'floating-rtl' : 'floating-ltr';
226229
this._panelOpen = true;
227230
}
228231

229232
/** Closes the overlay panel and focuses the host element. */
230233
close(): void {
231234
this._panelOpen = false;
235+
if (!this._selected) {
236+
this._placeholderState = '';
237+
}
232238
this._focusHost();
233239
}
234240

@@ -242,7 +248,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
242248
// the select's child options have been created. It's necessary to call
243249
// writeValue() again after the options have been created to ensure any
244250
// initial view value is set.
245-
this._ngZone.onStable.first().subscribe(() => this.writeValue(value));
251+
Promise.resolve(null).then(() => this.writeValue(value));
246252
return;
247253
}
248254

@@ -300,15 +306,6 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
300306
return this._getTriggerRect().width;
301307
}
302308

303-
/** The animation state of the placeholder. */
304-
_getPlaceholderState(): string {
305-
if (this.panelOpen || this.selected) {
306-
return this._isRtl() ? 'floating-rtl' : 'floating-ltr';
307-
} else {
308-
return 'normal';
309-
}
310-
}
311-
312309
/** Ensures the panel opens if activated by the keyboard. */
313310
_handleKeydown(event: KeyboardEvent): void {
314311
if (event.keyCode === ENTER || event.keyCode === SPACE) {
@@ -403,7 +400,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
403400
private _onSelect(option: MdOption): void {
404401
this._selected = option;
405402
this._updateOptions();
406-
this.close();
403+
if (this.panelOpen) {
404+
this.close();
405+
}
407406
}
408407

409408
/** Deselect each option that doesn't match the current selection. */

0 commit comments

Comments
 (0)