From 69d4d4b192bc98bf3eb4c138e8b3dd52b6bd0405 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 21 Feb 2017 18:34:50 +0100 Subject: [PATCH] feat(select): add floatingPlaceholder option Adds the `floatingPlaceholder` option that can be used to disable the floating placeholders. Fixes #2569. Fixes #2963. --- src/demo-app/select/select-demo.html | 11 ++++- src/demo-app/select/select-demo.ts | 1 + src/lib/select/select.html | 9 +++- src/lib/select/select.spec.ts | 71 +++++++++++++++++++++++++--- src/lib/select/select.ts | 41 +++++++++++++++- 5 files changed, 122 insertions(+), 11 deletions(-) diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index 4471ebc92c54..821934b4b59f 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -18,7 +18,7 @@ + [floatPlaceholder]="floatPlaceholder" #drinkControl="ngModel"> {{ drink.viewValue }} @@ -27,6 +27,15 @@

Touched: {{ drinkControl.touched }}

Dirty: {{ drinkControl.dirty }}

Status: {{ drinkControl.control?.status }}

+

+ + +

+ diff --git a/src/demo-app/select/select-demo.ts b/src/demo-app/select/select-demo.ts index 0b369e0c137b..b731087b5624 100644 --- a/src/demo-app/select/select-demo.ts +++ b/src/demo-app/select/select-demo.ts @@ -14,6 +14,7 @@ export class SelectDemo { showSelect = false; currentDrink: string; latestChangeEvent: MdSelectChange; + floatPlaceholder: string = 'auto'; foodControl = new FormControl('pizza-1'); foods = [ diff --git a/src/lib/select/select.html b/src/lib/select/select.html index 4e36b2187800..d3ae2a6ca256 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -1,9 +1,14 @@
- {{ placeholder }} + {{ placeholder }} {{ selected?.viewValue }} +
diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 59c2e5de58da..ab82b27c9634 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -11,7 +11,7 @@ import { } from '@angular/core'; import {MdSelectModule} from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; -import {MdSelect} from './select'; +import {MdSelect, MdSelectFloatPlaceholderType} from './select'; import {MdOption} from '../core/option/option'; import {Dir} from '../core/rtl/dir'; import { @@ -35,6 +35,7 @@ describe('MdSelect', () => { SelectWithChangeEvent, CustomSelectAccessor, CompWithCustomSelect, + FloatPlaceholderSelect, SelectWithErrorSibling, ThrowsErrorOnInit, BasicSelectOnPush @@ -590,12 +591,12 @@ describe('MdSelect', () => { }); it('should float the placeholder when the panel is open and unselected', () => { - expect(fixture.componentInstance.select._placeholderState) + expect(fixture.componentInstance.select._getPlaceholderAnimationState()) .toEqual('', 'Expected placeholder to initially have a normal position.'); trigger.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._placeholderState) + expect(fixture.componentInstance.select._getPlaceholderAnimationState()) .toEqual('floating-ltr', 'Expected placeholder to animate up to floating position.'); const backdrop = @@ -603,7 +604,7 @@ describe('MdSelect', () => { backdrop.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._placeholderState) + expect(fixture.componentInstance.select._getPlaceholderAnimationState()) .toEqual('', 'Expected placeholder to animate back down to normal position.'); }); @@ -616,7 +617,7 @@ describe('MdSelect', () => { expect(placeholderEl.classList) .toContain('mat-floating-placeholder', 'Expected placeholder to display as floating.'); - expect(fixture.componentInstance.select._placeholderState) + expect(fixture.componentInstance.select._getPlaceholderAnimationState()) .toEqual('', 'Expected animation state to be empty to avoid animation.'); }); @@ -625,7 +626,8 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - expect(fixture.componentInstance.select._placeholderState).toEqual('floating-rtl'); + expect(fixture.componentInstance.select._getPlaceholderAnimationState()) + .toEqual('floating-rtl'); }); @@ -1285,6 +1287,39 @@ describe('MdSelect', () => { }); }); + describe('floatPlaceholder option', () => { + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = TestBed.createComponent(FloatPlaceholderSelect); + }); + + it('should be able to disable the floating placeholder', () => { + let placeholder = fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement; + + fixture.componentInstance.floatPlaceholder = 'never'; + fixture.detectChanges(); + + expect(placeholder.style.visibility).toBe('visible'); + expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy(); + + fixture.componentInstance.control.setValue('pizza-1'); + fixture.detectChanges(); + + expect(placeholder.style.visibility).toBe('hidden'); + expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBeFalsy(); + }); + + it('should be able to always float the placeholder', () => { + expect(fixture.componentInstance.control.value).toBeFalsy(); + + fixture.componentInstance.floatPlaceholder = 'always'; + fixture.detectChanges(); + + expect(fixture.componentInstance.select._getPlaceholderAnimationState()).toBe('floating-ltr'); + }); + }); + describe('with OnPush change detection', () => { let fixture: ComponentFixture; let trigger: HTMLElement; @@ -1309,6 +1344,7 @@ describe('MdSelect', () => { }); }); + @Component({ selector: 'basic-select', template: ` @@ -1529,6 +1565,29 @@ class BasicSelectOnPush { @ViewChildren(MdOption) options: QueryList; } +@Component({ + selector: 'floating-placeholder-select', + template: ` + + + {{ food.viewValue }} + + + `, +}) +class FloatPlaceholderSelect { + floatPlaceholder: MdSelectFloatPlaceholderType = 'auto'; + control = new FormControl(); + foods: any[] = [ + { value: 'steak-0', viewValue: 'Steak' }, + { value: 'pizza-1', viewValue: 'Pizza' }, + { value: 'tacos-2', viewValue: 'Tacos'} + ]; + + @ViewChild(MdSelect) select: MdSelect; +} + /** * TODO: Move this to core testing utility until Angular has event faking diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index c1c0a3e37978..1a92699e5437 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -73,6 +73,9 @@ export class MdSelectChange { constructor(public source: MdSelect, public value: any) { } } +/** Allowed values for the floatPlaceholder option. */ +export type MdSelectFloatPlaceholderType = 'always' | 'never' | 'auto'; + @Component({ moduleId: module.id, selector: 'md-select, mat-select', @@ -128,7 +131,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr private _placeholder: string; /** The animation state of the placeholder. */ - _placeholderState = ''; + private _placeholderState = ''; /** * The width of the trigger. Must be saved to set the min width of the overlay panel @@ -226,6 +229,14 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr get required() { return this._required; } set required(value: any) { this._required = coerceBooleanProperty(value); } + /** Whether to float the placeholder text. */ + @Input() + get floatPlaceholder(): MdSelectFloatPlaceholderType { return this._floatPlaceholder; } + set floatPlaceholder(value: MdSelectFloatPlaceholderType) { + this._floatPlaceholder = value || 'auto'; + } + private _floatPlaceholder: MdSelectFloatPlaceholderType = 'auto'; + /** Event emitted when the select has been opened. */ @Output() onOpen: EventEmitter = new EventEmitter(); @@ -280,7 +291,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return; } this._calculateOverlayPosition(); - this._placeholderState = this._isRtl() ? 'floating-rtl' : 'floating-ltr'; + this._placeholderState = this._floatPlaceholderState(); this._panelOpen = true; } @@ -588,6 +599,28 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return clampValue(0, optimalScrollPosition, maxScroll); } + /** + * Figures out the appropriate animation state for the placeholder. + */ + _getPlaceholderAnimationState(): string { + if (this.floatPlaceholder === 'never') { + return ''; + } + + if (this.floatPlaceholder === 'always') { + return this._floatPlaceholderState(); + } + + return this._placeholderState; + } + + /** + * Determines the CSS `visibility` of the placeholder element. + */ + _getPlaceholderVisibility(): 'visible'|'hidden' { + return (this.floatPlaceholder !== 'never' || !this.selected) ? 'visible' : 'hidden'; + } + /** * Calculates the y-offset of the select's overlay panel in relation to the * top start corner of the trigger. It has to be adjusted in order for the @@ -699,6 +732,10 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr return `50% ${originY}px 0px`; } + /** Figures out the floating placeholder state value. */ + private _floatPlaceholderState(): string { + return this._isRtl() ? 'floating-rtl' : 'floating-ltr'; + } } /** Clamps a value n between min and max values. */