mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(select): add floatingPlaceholder option (#2571)
Adds the `floatingPlaceholder` option that can be used to disable the floating placeholders. Fixes #2569. Fixes #2963.
This commit is contained in:
committed by
mmalerba
parent
6595ad87c2
commit
bb2392fccd
@@ -18,7 +18,7 @@
|
||||
|
||||
<md-card>
|
||||
<md-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="isRequired" [disabled]="isDisabled"
|
||||
#drinkControl="ngModel">
|
||||
[floatPlaceholder]="floatPlaceholder" #drinkControl="ngModel">
|
||||
<md-option *ngFor="let drink of drinks" [value]="drink.value" [disabled]="drink.disabled">
|
||||
{{ drink.viewValue }}
|
||||
</md-option>
|
||||
@@ -27,6 +27,15 @@
|
||||
<p> Touched: {{ drinkControl.touched }} </p>
|
||||
<p> Dirty: {{ drinkControl.dirty }} </p>
|
||||
<p> Status: {{ drinkControl.control?.status }} </p>
|
||||
<p>
|
||||
<label for="floating-placeholder">Floating placeholder:</label>
|
||||
<select [(ngModel)]="floatPlaceholder" id="floating-placeholder">
|
||||
<option value="auto">Auto</option>
|
||||
<option value="always">Always</option>
|
||||
<option value="never">Never</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<button md-button (click)="currentDrink='water-2'">SET VALUE</button>
|
||||
<button md-button (click)="isRequired=!isRequired">TOGGLE REQUIRED</button>
|
||||
<button md-button (click)="isDisabled=!isDisabled">TOGGLE DISABLED</button>
|
||||
|
||||
@@ -14,6 +14,7 @@ export class SelectDemo {
|
||||
showSelect = false;
|
||||
currentDrink: string;
|
||||
latestChangeEvent: MdSelectChange;
|
||||
floatPlaceholder: string = 'auto';
|
||||
foodControl = new FormControl('pizza-1');
|
||||
|
||||
foods = [
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<div class="mat-select-trigger" cdk-overlay-origin (click)="toggle()" #origin="cdkOverlayOrigin" #trigger>
|
||||
<span class="mat-select-placeholder" [class.mat-floating-placeholder]="this.selected"
|
||||
[@transformPlaceholder]="_placeholderState" [style.width.px]="_selectedValueWidth"> {{ placeholder }} </span>
|
||||
<span
|
||||
class="mat-select-placeholder"
|
||||
[class.mat-floating-placeholder]="selected"
|
||||
[@transformPlaceholder]="_getPlaceholderAnimationState()"
|
||||
[style.visibility]="_getPlaceholderVisibility()"
|
||||
[style.width.px]="_selectedValueWidth"> {{ placeholder }} </span>
|
||||
<span class="mat-select-value" *ngIf="selected">
|
||||
<span class="mat-select-value-text">{{ selected?.viewValue }}</span>
|
||||
</span>
|
||||
|
||||
<span class="mat-select-arrow"></span>
|
||||
<span class="mat-select-underline"></span>
|
||||
</div>
|
||||
|
||||
@@ -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<FloatPlaceholderSelect>;
|
||||
|
||||
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<BasicSelectOnPush>;
|
||||
let trigger: HTMLElement;
|
||||
@@ -1309,6 +1344,7 @@ describe('MdSelect', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'basic-select',
|
||||
template: `
|
||||
@@ -1529,6 +1565,29 @@ class BasicSelectOnPush {
|
||||
@ViewChildren(MdOption) options: QueryList<MdOption>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'floating-placeholder-select',
|
||||
template: `
|
||||
<md-select placeholder="Food I want to eat right now" [formControl]="control"
|
||||
[floatPlaceholder]="floatPlaceholder">
|
||||
<md-option *ngFor="let food of foods" [value]="food.value">
|
||||
{{ food.viewValue }}
|
||||
</md-option>
|
||||
</md-select>
|
||||
`,
|
||||
})
|
||||
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
|
||||
|
||||
@@ -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<void> = new EventEmitter<void>();
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user