Skip to content

feat(form-field): add support for separate label and placeholder #8223

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 2 commits into from
Nov 28, 2017
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
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