Skip to content

fix(select): only animate placeholder when no selection #2054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
15 changes: 15 additions & 0 deletions src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,26 @@ md-select {
}

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

// These values are duplicated from animation code in order to
// allow placeholders to sometimes float without animating,
// for example when the value is set programmatically.
// TODO(kara): Change when animations API supports skipping animation.
&.md-floating-placeholder {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need comment explaining why this is duplicated from the animation code.

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');
});

});
Expand Down
27 changes: 13 additions & 14 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
ElementRef,
EventEmitter,
Input,
NgZone,
OnDestroy,
Optional,
Output,
Expand Down Expand Up @@ -115,6 +114,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
/** The scroll position of the overlay panel, calculated to center the selected option. */
private _scrollTop = 0;

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

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

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

constructor(private _element: ElementRef, private _renderer: Renderer,
private _ngZone: NgZone, private _viewportRuler: ViewportRuler,
@Optional() private _dir: Dir, @Optional() public _control: NgControl) {
private _viewportRuler: ViewportRuler, @Optional() private _dir: Dir,
@Optional() public _control: NgControl) {
if (this._control) {
this._control.valueAccessor = this;
}
Expand Down Expand Up @@ -223,12 +225,16 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
return;
}
this._calculateOverlayPosition();
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 @@ -242,7 +248,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));
Promise.resolve(null).then(() => this.writeValue(value));
return;
}

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

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

/** Ensures the panel opens if activated by the keyboard. */
_handleKeydown(event: KeyboardEvent): void {
if (event.keyCode === ENTER || event.keyCode === SPACE) {
Expand Down Expand Up @@ -403,7 +400,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