Skip to content

Commit

Permalink
feat(form-field): add support for separate label and placeholder (#8223)
Browse files Browse the repository at this point in the history
* feat(form-field): add support for separate label and placeholder

* Adds the ability for the user to specify both a label and placeholder text for a form field.
* Renames all of the "floating placeholder" related terminology to refer to a "floating label" instead.
* Aligns the input placeholder color with the Material spec.

Fixes #6194.

* fix: add transition to placeholder text
  • Loading branch information
crisbeto authored and tinayuangao committed Nov 28, 2017
1 parent 65cd1a1 commit d6fec35
Show file tree
Hide file tree
Showing 40 changed files with 617 additions and 314 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
/src/lib/core/line/** @jelbourn
/src/lib/core/option/** @kara @crisbeto
/src/lib/core/placeholder/** @kara @mmalerba
/src/lib/core/label/** @kara @mmalerba
/src/lib/core/ripple/** @devversion
/src/lib/core/selection/** @tinayuangao @jelbourn
/src/lib/core/selection/pseudo*/** @crisbeto @jelbourn
Expand Down
6 changes: 3 additions & 3 deletions guides/creating-a-custom-form-field-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,16 @@ get empty() {
}
```

#### `shouldPlaceholderFloat`
#### `shouldLabelFloat`

This property is used to indicate whether the placeholder should be in the floating position. We'll
This property is used to indicate whether the label should be in the floating position. We'll
use the same logic as `matInput` and float the placeholder when the input is focused or non-empty.
Since the placeholder will be overlapping our control when when it's not floating, we should hide
the `` characters when it's not floating.

```ts
@HostBinding('class.floating')
get shouldPlaceholderFloat() {
get shouldLabelFloat() {
return this.focused || !this.empty;
}
```
Expand Down
8 changes: 4 additions & 4 deletions src/demo-app/a11y/input/input-a11y.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<section>
<h2>Basic input box (e.g. name field)</h2>
<mat-form-field floatPlaceholder="never">
<mat-form-field floatLabel="never">
<input matInput placeholder="First name" [(ngModel)]="firstName">
</mat-form-field>
<mat-form-field floatPlaceholder="never">
<mat-form-field floatLabel="never">
<input matInput placeholder="Last name" [(ngModel)]="lastName">
</mat-form-field>
</section>
Expand Down Expand Up @@ -34,12 +34,12 @@ <h2>Input with error message (e.g. email field)</h2>

<section>
<h2>Input with prefix & suffix (e.g. currency converter)</h2>
<mat-form-field floatPlaceholder="always">
<mat-form-field floatLabel="always">
<input matInput type="number" placeholder="USD" [(ngModel)]="usd">
<span matPrefix>$</span>
</mat-form-field>
=
<mat-form-field floatPlaceholder="always">
<mat-form-field floatLabel="always">
<input matInput type="number" placeholder="JPY" [(ngModel)]="jpy">
<span matPrefix>‎¥‎</span>
</mat-form-field>
Expand Down
2 changes: 1 addition & 1 deletion src/demo-app/autocomplete/autocomplete-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div>Reactive value: {{ stateCtrl.value | json }}</div>
<div>Reactive dirty: {{ stateCtrl.dirty }}</div>

<mat-form-field floatPlaceholder="never">
<mat-form-field floatLabel="never">
<input matInput placeholder="State" [matAutocomplete]="reactiveAuto" [formControl]="stateCtrl">
<mat-autocomplete #reactiveAuto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let state of reactiveStates | async" [value]="state">
Expand Down
105 changes: 104 additions & 1 deletion src/demo-app/input/input-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,115 @@ <h4>Textarea</h4>
</button>
<div>
<mat-form-field>
<input matInput placeholder="delayed value" [formControl]="delayedFormControl">
<input matInput placeholder="delayed value" [formControl]="delayedFormControl">
</mat-form-field>
</div>
</mat-card-content>
</mat-card>


<mat-card class="demo-card demo-basic">
<mat-toolbar color="primary">Floating labels</mat-toolbar>

<mat-card-content>
<div>
<mat-form-field>
<input matInput [formControl]="placeholderTestControl">
</mat-form-field>

<mat-form-field>
<input matInput [formControl]="placeholderTestControl" placeholder="Only placeholder">
</mat-form-field>

<mat-form-field>
<mat-label>Only label</mat-label>
<input matInput [formControl]="placeholderTestControl">
</mat-form-field>

<mat-form-field>
<mat-label>Label and placeholder</mat-label>
<input matInput [formControl]="placeholderTestControl" placeholder="This is the placeholder">
</mat-form-field>

<mat-form-field floatLabel="always">
<mat-label>Always float</mat-label>
<input matInput [formControl]="placeholderTestControl" placeholder="This is the placeholder">
</mat-form-field>

<mat-form-field floatLabel="never">
<mat-label>Never float</mat-label>
<input matInput [formControl]="placeholderTestControl" placeholder="This is the placeholder">
</mat-form-field>

<mat-form-field>
<mat-label>Label and placeholder element</mat-label>
<mat-placeholder>The placeholder element</mat-placeholder>
<input matInput [formControl]="placeholderTestControl">
</mat-form-field>
</div>

<div>
<mat-form-field>
<mat-select [formControl]="placeholderTestControl">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field>
<mat-select [formControl]="placeholderTestControl" placeholder="Only placeholder">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field>
<mat-label>Only label</mat-label>
<mat-select [formControl]="placeholderTestControl">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field>
<mat-label>Label and placeholder</mat-label>
<mat-select [formControl]="placeholderTestControl" placeholder="This is the placeholder">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field floatLabel="always">
<mat-label>Always float</mat-label>
<mat-select [formControl]="placeholderTestControl" placeholder="This is the placeholder">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field floatLabel="never">
<mat-label>Never float</mat-label>
<mat-select [formControl]="placeholderTestControl" placeholder="This is the placeholder">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>

<mat-form-field>
<mat-label>Label and placeholder element</mat-label>
<mat-placeholder>The placeholder element</mat-placeholder>
<mat-select [formControl]="placeholderTestControl">
<mat-option value="Value">Value</mat-option>
</mat-select>
</mat-form-field>
</div>

<button
mat-raised-button
color="primary"
(click)="togglePlaceholderTestValue()">Toggle value</button>

<button
mat-raised-button
color="primary"
(click)="togglePlaceholderTestTouched()">Toggle touched</button>
</mat-card-content>
</mat-card>

<mat-card class="demo-card demo-basic">
<mat-toolbar color="primary">Textarea Autosize</mat-toolbar>
<mat-card-content>
Expand Down
11 changes: 11 additions & 0 deletions src/demo-app/input/input-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class InputDemo {
hideRequiredMarker: boolean;
ctrlDisabled = false;
textareaNgModelValue: string;
placeholderTestControl = new FormControl('', Validators.required);

name: string;
errorMessageExample1: string;
Expand Down Expand Up @@ -73,4 +74,14 @@ export class InputDemo {
return false;
}
};

togglePlaceholderTestValue() {
this.placeholderTestControl.setValue(this.placeholderTestControl.value === '' ? 'Value' : '');
}

togglePlaceholderTestTouched() {
this.placeholderTestControl.touched ?
this.placeholderTestControl.markAsUntouched() :
this.placeholderTestControl.markAsTouched();
}
}
4 changes: 2 additions & 2 deletions src/demo-app/select/select-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<mat-card>
<mat-card-subtitle>ngModel</mat-card-subtitle>

<mat-form-field [floatPlaceholder]="floatPlaceholder" [color]="drinksTheme">
<mat-form-field [floatLabel]="floatLabel" [color]="drinksTheme">
<mat-select placeholder="Drink" [(ngModel)]="currentDrink" [required]="drinksRequired"
[disabled]="drinksDisabled" #drinkControl="ngModel">
<mat-option>None</mat-option>
Expand All @@ -24,7 +24,7 @@
<p> Status: {{ drinkControl.control?.status }} </p>
<p>
<label for="floating-placeholder">Floating placeholder:</label>
<select [(ngModel)]="floatPlaceholder" id="floating-placeholder">
<select [(ngModel)]="floatLabel" id="floating-placeholder">
<option value="auto">Auto</option>
<option value="always">Always</option>
<option value="never">Never</option>
Expand Down
2 changes: 1 addition & 1 deletion src/demo-app/select/select-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class SelectDemo {
currentPokemonFromGroup: string;
currentDigimon: string;
latestChangeEvent: MatSelectChange;
floatPlaceholder: string = 'auto';
floatLabel: string = 'auto';
foodControl = new FormControl('pizza-1');
topHeightCtrl = new FormControl(0);
drinksTheme = 'primary';
Expand Down
38 changes: 19 additions & 19 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** Strategy that is used to position the panel. */
private _positionStrategy: ConnectedPositionStrategy;

/** Whether or not the placeholder state is being overridden. */
private _manuallyFloatingPlaceholder = false;
/** Whether or not the label state is being overridden. */
private _manuallyFloatingLabel = false;

/** The subscription for closing actions (some are bound to document). */
private _closingActionsSubscription: Subscription;
Expand Down Expand Up @@ -163,7 +163,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** Opens the autocomplete suggestion panel. */
openPanel(): void {
this._attachOverlay();
this._floatPlaceholder();
this._floatLabel();
}

/** Closes the autocomplete suggestion panel. */
Expand All @@ -173,14 +173,14 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._closingActionsSubscription.unsubscribe();
}

this._resetPlaceholder();
this._resetLabel();

if (this._panelOpen) {
this.autocomplete._isOpen = this._panelOpen = false;

// We need to trigger change detection manually, because
// `fromEvent` doesn't seem to do it at the proper time.
// This ensures that the placeholder is reset when the
// This ensures that the label is reset when the
// user clicks outside.
this._changeDetectorRef.detectChanges();
}
Expand Down Expand Up @@ -307,33 +307,33 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
_handleFocus(): void {
if (!this._element.nativeElement.readOnly) {
this._attachOverlay();
this._floatPlaceholder(true);
this._floatLabel(true);
}
}

/**
* In "auto" mode, the placeholder will animate down as soon as focus is lost.
* In "auto" mode, the label will animate down as soon as focus is lost.
* This causes the value to jump when selecting an option with the mouse.
* This method manually floats the placeholder until the panel can be closed.
* @param shouldAnimate Whether the placeholder should be animated when it is floated.
* This method manually floats the label until the panel can be closed.
* @param shouldAnimate Whether the label should be animated when it is floated.
*/
private _floatPlaceholder(shouldAnimate = false): void {
if (this._formField && this._formField.floatPlaceholder === 'auto') {
private _floatLabel(shouldAnimate = false): void {
if (this._formField && this._formField.floatLabel === 'auto') {
if (shouldAnimate) {
this._formField._animateAndLockPlaceholder();
this._formField._animateAndLockLabel();
} else {
this._formField.floatPlaceholder = 'always';
this._formField.floatLabel = 'always';
}

this._manuallyFloatingPlaceholder = true;
this._manuallyFloatingLabel = true;
}
}

/** If the placeholder has been manually elevated, return it to its normal state. */
private _resetPlaceholder(): void {
if (this._manuallyFloatingPlaceholder) {
this._formField.floatPlaceholder = 'auto';
this._manuallyFloatingPlaceholder = false;
/** If the label has been manually elevated, return it to its normal state. */
private _resetLabel(): void {
if (this._manuallyFloatingLabel) {
this._formField.floatLabel = 'auto';
this._manuallyFloatingLabel = false;
}
}

Expand Down
Loading

0 comments on commit d6fec35

Please sign in to comment.