Skip to content

Commit

Permalink
fix(select): only animate placeholder when no selection
Browse files Browse the repository at this point in the history
  • Loading branch information
kara committed Dec 2, 2016
1 parent 26eb7ce commit a00d8c8
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 28 deletions.
11 changes: 6 additions & 5 deletions src/lib/select/select-animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import {
* depending on the text direction of the application.
*/
export const transformPlaceholder: AnimationEntryMetadata = trigger('transformPlaceholder', [
state('normal', style({
transform: `translate3d(0, 0, 0) scale(1)`
})),
state('floating-ltr', style({
transform: `translate3d(-2px, -22px, 0) scale(0.75)`
top: '-22px',
left: '-2px',
transform: `scale(0.75)`
})),
state('floating-rtl', style({
transform: `translate3d(2px, -22px, 0) scale(0.75)`
top: '-22px',
left: '2px',
transform: `scale(0.75)`
})),
transition('* => *', animate(`400ms cubic-bezier(0.25, 0.8, 0.25, 1)`))
]);
Expand Down
3 changes: 2 additions & 1 deletion src/lib/select/select.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div class="md-select-trigger" overlay-origin (click)="toggle()" #origin="overlayOrigin" #trigger>
<span class="md-select-placeholder" [@transformPlaceholder]="_getPlaceholderState()"> {{ placeholder }} </span>
<span class="md-select-placeholder" [class.md-floating-placeholder]="this._selected"
[@transformPlaceholder]="_placeholderState"> {{ placeholder }} </span>
<span class="md-select-value" *ngIf="selected"> {{ selected?.viewValue }} </span>
<span class="md-select-arrow"></span>
</div>
Expand Down
11 changes: 11 additions & 0 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,22 @@ md-select {
}

.md-select-placeholder {
position: relative;
padding: 0 2px;
transform-origin: left top;

&.md-floating-placeholder {
top: -22px;
left: -2px;
transform: scale(0.75);
}

[dir='rtl'] & {
transform-origin: right top;

&.md-floating-placeholder {
left: 2px;
}
}

// TODO: Double-check accessibility of this style
Expand Down
27 changes: 16 additions & 11 deletions src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,38 +456,43 @@ describe('MdSelect', () => {
trigger = fixture.debugElement.query(By.css('.md-select-trigger')).nativeElement;
});

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

trigger.click();
fixture.detectChanges();
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr');
expect(fixture.componentInstance.select._placeholderState)
.toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.');

const backdrop =
overlayContainerElement.querySelector('.md-overlay-backdrop') as HTMLElement;
backdrop.click();
fixture.detectChanges();

expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('normal');
expect(fixture.componentInstance.select._placeholderState)
.toEqual('', 'Expected placeholder to animate back down to normal position.');
});

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

const option = overlayContainerElement.querySelector('md-option') as HTMLElement;
option.click();
fixture.detectChanges();
const placeholderEl =
fixture.debugElement.query(By.css('.md-select-placeholder')).nativeElement;

expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-ltr');
expect(placeholderEl.classList)
.toContain('md-floating-placeholder', 'Expected placeholder to display as floating.');
expect(fixture.componentInstance.select._placeholderState)
.toEqual('', 'Expected animation state to be empty to avoid animation.');
});

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

trigger.click();
fixture.detectChanges();
expect(fixture.componentInstance.select._getPlaceholderState()).toEqual('floating-rtl');
expect(fixture.componentInstance.select._placeholderState).toEqual('floating-rtl');
});

it('should use the ltr panel state when the dir is ltr', () => {
Expand Down
22 changes: 11 additions & 11 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
/** Whether the select is disabled. */
private _disabled: boolean = false;

/** The animation state of the placeholder. */
_placeholderState = '';

/** Manages keyboard events for options in the panel. */
_keyManager: ListKeyManager;

Expand Down Expand Up @@ -163,12 +166,16 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
if (this.disabled) {
return;
}
this._placeholderState = this._isRtl() ? 'floating-rtl' : 'floating-ltr';
this._panelOpen = true;
}

/** Closes the overlay panel and focuses the host element. */
close(): void {
this._panelOpen = false;
if (!this._selected) {
this._placeholderState = '';
}
this._focusHost();
}

Expand All @@ -182,7 +189,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
// the select's child options have been created. It's necessary to call
// writeValue() again after the options have been created to ensure any
// initial view value is set.
this._ngZone.onStable.first().subscribe(() => this.writeValue(value));
this._ngZone.onMicrotaskEmpty.first().subscribe(() => this.writeValue(value));
return;
}

Expand Down Expand Up @@ -240,15 +247,6 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
return this.trigger.nativeElement.getBoundingClientRect().width;
}

/** The animation state of the placeholder. */
_getPlaceholderState(): string {
if (this.panelOpen || this.selected) {
return this._isRtl() ? 'floating-rtl' : 'floating-ltr';
} else {
return 'normal';
}
}

/** The animation state of the overlay panel. */
_getPanelState(): string {
return this._isRtl() ? `${this._transformOrigin}-rtl` : `${this._transformOrigin}-ltr`;
Expand Down Expand Up @@ -340,7 +338,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
private _onSelect(option: MdOption): void {
this._selected = option;
this._updateOptions();
this.close();
if (this.panelOpen) {
this.close();
}
}

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

0 comments on commit a00d8c8

Please sign in to comment.