From 2ce1bb024cb5c877227944b4f40bb19183071af5 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 4 Nov 2017 22:19:36 +0000 Subject: [PATCH] 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. --- .github/CODEOWNERS | 1 + .../creating-a-custom-form-field-control.md | 12 +- src/demo-app/a11y/input/input-a11y.html | 8 +- .../autocomplete/autocomplete-demo.html | 2 +- src/demo-app/input/input-demo.html | 105 +++++++++++- src/demo-app/input/input-demo.ts | 11 ++ src/demo-app/select/select-demo.html | 4 +- src/demo-app/select/select-demo.ts | 2 +- src/lib/autocomplete/autocomplete-trigger.ts | 38 ++--- src/lib/autocomplete/autocomplete.spec.ts | 60 +++---- src/lib/chips/chip-list.ts | 2 +- src/lib/core/label/label-options.ts | 20 +++ .../core/placeholder/placeholder-options.ts | 20 --- src/lib/core/public-api.ts | 11 +- src/lib/core/style/_form-common.scss | 11 ++ src/lib/form-field/_form-field-theme.scss | 32 ++-- src/lib/form-field/form-field-control.ts | 8 +- src/lib/form-field/form-field-module.ts | 3 + src/lib/form-field/form-field.html | 21 ++- src/lib/form-field/form-field.md | 34 ++-- src/lib/form-field/form-field.scss | 33 ++-- src/lib/form-field/form-field.ts | 79 +++++---- src/lib/form-field/label.ts | 16 ++ src/lib/form-field/placeholder.ts | 2 +- src/lib/form-field/public-api.ts | 2 +- src/lib/input/_input-theme.scss | 7 + src/lib/input/input.scss | 16 +- src/lib/input/input.spec.ts | 152 ++++++++++++------ src/lib/input/input.ts | 2 +- src/lib/select/_select-theme.scss | 4 + src/lib/select/select.html | 12 +- src/lib/select/select.scss | 9 +- src/lib/select/select.spec.ts | 76 ++++----- src/lib/select/select.ts | 2 +- src/material-examples/example-module.ts | 10 +- .../form-field-custom-control-example.ts | 4 +- .../form-field-label-example.css} | 0 .../form-field-label-example.html} | 13 +- .../form-field-label-example.ts | 19 +++ .../form-field-placeholder-example.ts | 19 --- 40 files changed, 578 insertions(+), 304 deletions(-) create mode 100644 src/lib/core/label/label-options.ts delete mode 100644 src/lib/core/placeholder/placeholder-options.ts create mode 100644 src/lib/form-field/label.ts rename src/material-examples/{form-field-placeholder/form-field-placeholder-example.css => form-field-label/form-field-label-example.css} (100%) rename src/material-examples/{form-field-placeholder/form-field-placeholder-example.html => form-field-label/form-field-label-example.html} (71%) create mode 100644 src/material-examples/form-field-label/form-field-label-example.ts delete mode 100644 src/material-examples/form-field-placeholder/form-field-placeholder-example.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 01fa90356e08..ff979c81b6ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/guides/creating-a-custom-form-field-control.md b/guides/creating-a-custom-form-field-control.md index e421d9a28cd7..768f0e64c928 100644 --- a/guides/creating-a-custom-form-field-control.md +++ b/guides/creating-a-custom-form-field-control.md @@ -58,7 +58,7 @@ class MyTelInput { tel = tel || new MyTel('', '', ''); this.parts.setValue({area: tel.area, exchange: tel.exchange, subscriber: tel.subscriber}); } - + constructor(fb: FormBuilder) { this.parts = fb.group({ 'area': '', @@ -98,7 +98,7 @@ the `MatFormFieldControl` interface, see the #### `value` This property allows someone to set or get the value of our control. Its type should be the same -type we used for the type parameter when we implemented `MatFormFieldControl`. Since our component +type we used for the type parameter when we implemented `MatFormFieldControl`. Since our component already has a value property, we don't need to do anything for this one. #### `stateChanges` @@ -212,16 +212,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; } ``` @@ -307,7 +307,7 @@ just need to apply the given IDs to our host element. ```ts @HostBinding('attr.aria-describedby') describedBy = ''; - + setDescribedByIds(ids: string[]) { this.describedBy = ids.join(' '); } diff --git a/src/demo-app/a11y/input/input-a11y.html b/src/demo-app/a11y/input/input-a11y.html index 8129ee38d788..cb9d19dfb345 100644 --- a/src/demo-app/a11y/input/input-a11y.html +++ b/src/demo-app/a11y/input/input-a11y.html @@ -1,9 +1,9 @@

Basic input box (e.g. name field)

- + - +
@@ -34,12 +34,12 @@

Input with error message (e.g. email field)

Input with prefix & suffix (e.g. currency converter)

- + $ = - + ‎¥‎ diff --git a/src/demo-app/autocomplete/autocomplete-demo.html b/src/demo-app/autocomplete/autocomplete-demo.html index ea1486b288a6..04321b056aa5 100644 --- a/src/demo-app/autocomplete/autocomplete-demo.html +++ b/src/demo-app/autocomplete/autocomplete-demo.html @@ -6,7 +6,7 @@
Reactive value: {{ stateCtrl.value | json }}
Reactive dirty: {{ stateCtrl.dirty }}
- + diff --git a/src/demo-app/input/input-demo.html b/src/demo-app/input/input-demo.html index deeb7cdaf4cd..f78f62e0dd34 100644 --- a/src/demo-app/input/input-demo.html +++ b/src/demo-app/input/input-demo.html @@ -384,12 +384,115 @@

Textarea

- +
+ + + Floating labels + + +
+ + + + + + + + + + Only label + + + + + Label and placeholder + + + + + Always float + + + + + Never float + + + + + Label and placeholder element + The placeholder element + + +
+ +
+ + + Value + + + + + + Value + + + + + Only label + + Value + + + + + Label and placeholder + + Value + + + + + Always float + + Value + + + + + Never float + + Value + + + + + Label and placeholder element + The placeholder element + + Value + + +
+ + + + +
+
+ Textarea Autosize diff --git a/src/demo-app/input/input-demo.ts b/src/demo-app/input/input-demo.ts index 8545e3c0c71c..67cc69667381 100644 --- a/src/demo-app/input/input-demo.ts +++ b/src/demo-app/input/input-demo.ts @@ -29,6 +29,7 @@ export class InputDemo { hideRequiredMarker: boolean; ctrlDisabled = false; textareaNgModelValue: string; + placeholderTestControl = new FormControl('', Validators.required); name: string; errorMessageExample1: string; @@ -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(); + } } diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index 1ce8ef756f15..dd1ca63c25a5 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -6,7 +6,7 @@ ngModel - + None @@ -24,7 +24,7 @@

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

- diff --git a/src/demo-app/select/select-demo.ts b/src/demo-app/select/select-demo.ts index 5ed9b2cd70b4..f97649fdd1cd 100644 --- a/src/demo-app/select/select-demo.ts +++ b/src/demo-app/select/select-demo.ts @@ -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'; diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 58d145dde1ea..3dde93fea665 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -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; @@ -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. */ @@ -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(); } @@ -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; } } diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 7d5bc2abe538..1bdfaf586fd4 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -63,7 +63,7 @@ describe('MatAutocomplete', () => { AutocompleteWithOnPushDelay, AutocompleteWithNativeInput, AutocompleteWithoutPanel, - AutocompleteWithFormsAndNonfloatingPlaceholder, + AutocompleteWithFormsAndNonfloatingLabel, AutocompleteWithGroups, AutocompleteWithSelectEvent, ], @@ -284,8 +284,8 @@ describe('MatAutocomplete', () => { it('should keep the label floating until the panel closes', async(() => { fixture.componentInstance.trigger.openPanel(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('always', 'Expected placeholder to float as soon as panel opens.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('always', 'Expected label to float as soon as panel opens.'); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -295,8 +295,8 @@ describe('MatAutocomplete', () => { options[1].click(); fixture.detectChanges(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('auto', 'Expected placeholder to return to auto state after panel closes.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('auto', 'Expected label to return to auto state after panel closes.'); }); })); @@ -312,13 +312,13 @@ describe('MatAutocomplete', () => { .toBe(false, `Expected panel state to stay closed.`); }); - it('should not mess with placeholder placement if set to never', async(() => { - fixture.componentInstance.placeholder = 'never'; + it('should not mess with label placement if set to never', async(() => { + fixture.componentInstance.floatLabel = 'never'; fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('never', 'Expected placeholder to stay static.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('never', 'Expected label to stay static.'); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -328,18 +328,18 @@ describe('MatAutocomplete', () => { options[1].click(); fixture.detectChanges(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('never', 'Expected placeholder to stay in static state after close.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('never', 'Expected label to stay in static state after close.'); }); })); - it('should not mess with placeholder placement if set to always', async(() => { - fixture.componentInstance.placeholder = 'always'; + it('should not mess with label placement if set to always', async(() => { + fixture.componentInstance.floatLabel = 'always'; fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('always', 'Expected placeholder to stay elevated on open.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('always', 'Expected label to stay elevated on open.'); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -349,8 +349,8 @@ describe('MatAutocomplete', () => { options[1].click(); fixture.detectChanges(); - expect(fixture.componentInstance.formField.floatPlaceholder) - .toEqual('always', 'Expected placeholder to stay elevated after close.'); + expect(fixture.componentInstance.formField.floatLabel) + .toEqual('always', 'Expected label to stay elevated after close.'); }); })); @@ -385,14 +385,14 @@ describe('MatAutocomplete', () => { .toContain('mat-autocomplete-visible', 'Expected panel to be visible.'); })); - it('should animate the placeholder when the input is focused', () => { + it('should animate the label when the input is focused', () => { const inputContainer = fixture.componentInstance.formField; - spyOn(inputContainer, '_animateAndLockPlaceholder'); - expect(inputContainer._animateAndLockPlaceholder).not.toHaveBeenCalled(); + spyOn(inputContainer, '_animateAndLockLabel'); + expect(inputContainer._animateAndLockLabel).not.toHaveBeenCalled(); dispatchFakeEvent(fixture.debugElement.query(By.css('input')).nativeElement, 'focusin'); - expect(inputContainer._animateAndLockPlaceholder).toHaveBeenCalled(); + expect(inputContainer._animateAndLockLabel).toHaveBeenCalled(); }); it('should provide the open state of the panel', async(() => { @@ -1500,19 +1500,19 @@ describe('MatAutocomplete', () => { }).not.toThrow(); })); - it('should hide the placeholder with a preselected form control value ' + - 'and a disabled floating placeholder', fakeAsync(() => { - const fixture = TestBed.createComponent(AutocompleteWithFormsAndNonfloatingPlaceholder); + it('should hide the label with a preselected form control value ' + + 'and a disabled floating label', fakeAsync(() => { + const fixture = TestBed.createComponent(AutocompleteWithFormsAndNonfloatingLabel); fixture.detectChanges(); tick(); fixture.detectChanges(); const input = fixture.nativeElement.querySelector('input'); - const placeholder = fixture.nativeElement.querySelector('.mat-form-field-placeholder'); + const label = fixture.nativeElement.querySelector('.mat-form-field-label'); expect(input.value).toBe('California'); - expect(placeholder.classList).not.toContain('mat-form-field-empty'); + expect(label.classList).not.toContain('mat-form-field-empty'); })); it('should transfer the mat-autocomplete classes to the panel element', fakeAsync(() => { @@ -1659,7 +1659,7 @@ describe('MatAutocomplete', () => { @Component({ template: ` - + @@ -1674,7 +1674,7 @@ class SimpleAutocomplete implements OnDestroy { stateCtrl = new FormControl(); filteredStates: any[]; valueSub: Subscription; - placeholder = 'auto'; + floatLabel = 'auto'; width: number; @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger; @@ -1885,7 +1885,7 @@ class AutocompleteWithoutPanel { @Component({ template: ` - + @@ -1894,7 +1894,7 @@ class AutocompleteWithoutPanel { ` }) -class AutocompleteWithFormsAndNonfloatingPlaceholder { +class AutocompleteWithFormsAndNonfloatingLabel { formControl = new FormControl('California'); } diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index a0a94d46164b..5cfb7e073e53 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -243,7 +243,7 @@ export class MatChipList implements MatFormFieldControl, ControlValueAccess return (!this._chipInput || this._chipInput.empty) && this.chips.length === 0; } - get shouldPlaceholderFloat(): boolean { + get shouldLabelFloat(): boolean { return !this.empty || this.focused; } diff --git a/src/lib/core/label/label-options.ts b/src/lib/core/label/label-options.ts new file mode 100644 index 000000000000..ca84af854d2e --- /dev/null +++ b/src/lib/core/label/label-options.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {InjectionToken} from '@angular/core'; + +/** InjectionToken that can be used to specify the global labe options. */ +export const MAT_LABEL_GLOBAL_OPTIONS = + new InjectionToken('mat-label-global-options'); + +/** Type for the available floatLabel values. */ +export type FloatLabelType = 'always' | 'never' | 'auto'; + +export interface LabelOptions { + float?: FloatLabelType; +} diff --git a/src/lib/core/placeholder/placeholder-options.ts b/src/lib/core/placeholder/placeholder-options.ts deleted file mode 100644 index 66a3c1257f69..000000000000 --- a/src/lib/core/placeholder/placeholder-options.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import { InjectionToken } from '@angular/core'; - -/** InjectionToken that can be used to specify the global placeholder options. */ -export const MAT_PLACEHOLDER_GLOBAL_OPTIONS = - new InjectionToken('mat-placeholder-global-options'); - -/** Type for the available floatPlaceholder values. */ -export type FloatPlaceholderType = 'always' | 'never' | 'auto'; - -export interface PlaceholderOptions { - float?: FloatPlaceholderType; -} diff --git a/src/lib/core/public-api.ts b/src/lib/core/public-api.ts index 688cf9199f01..c1ef88ff5d92 100644 --- a/src/lib/core/public-api.ts +++ b/src/lib/core/public-api.ts @@ -14,10 +14,19 @@ export * from './gestures/gesture-annotations'; export * from './gestures/gesture-config'; export * from './line/line'; export * from './option/index'; -export * from './placeholder/placeholder-options'; +export * from './label/label-options'; export * from './ripple/index'; export * from './selection/index'; export * from './style/index'; +/** @deprecated */ +export {MAT_LABEL_GLOBAL_OPTIONS as MAT_PLACEHOLDER_GLOBAL_OPTIONS} from './label/label-options'; + +/** @deprecated */ +export {FloatLabelType as FloatPlaceholderType} from './label/label-options'; + +/** @deprecated */ +export {LabelOptions as PlaceholderOptions} from './label/label-options'; + // TODO: don't have this export * from './testing/month-constants'; diff --git a/src/lib/core/style/_form-common.scss b/src/lib/core/style/_form-common.scss index 19d0cee24698..a7c0e8db46e1 100644 --- a/src/lib/core/style/_form-common.scss +++ b/src/lib/core/style/_form-common.scss @@ -1,3 +1,5 @@ +@import '../theming/theming'; + // Renders a gradient for showing the dashed line when the input is disabled. // Unlike using a border, a gradient allows us to adjust the spacing of the dotted line // to match the Material Design spec. @@ -6,3 +8,12 @@ background-size: 4px 1px; background-repeat: repeat-x; } + +// Figures out the color of the placeholder for a form control. +// Used primarily to prevent the various form controls from +// becoming out of sync since these colors aren't in a pallette. +@function _mat-control-placeholder-color($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + @return mat-color($foreground, secondary-text, if($is-dark-theme, 0.5, 0.42)); +} diff --git a/src/lib/form-field/_form-field-theme.scss b/src/lib/form-field/_form-field-theme.scss index 3277aac5bb81..679244d2c957 100644 --- a/src/lib/form-field/_form-field-theme.scss +++ b/src/lib/form-field/_form-field-theme.scss @@ -12,8 +12,8 @@ $foreground: map-get($theme, foreground); $is-dark-theme: map-get($theme, is-dark); - // Placeholder colors. Required is used for the `*` star shown in the placeholder. - $placeholder-color: mat-color($foreground, secondary-text); + // Label colors. Required is used for the `*` star shown in the placeholder. + $label-color: mat-color($foreground, secondary-text); $focused-placeholder-color: mat-color($primary); $required-placeholder-color: mat-color($accent); @@ -23,15 +23,15 @@ $underline-color-warn: mat-color($warn); $underline-focused-color: mat-color($primary); - .mat-form-field-placeholder { - color: $placeholder-color; + .mat-form-field-label { + color: $label-color; } .mat-hint { color: mat-color($foreground, secondary-text); } - .mat-focused .mat-form-field-placeholder { + .mat-focused .mat-form-field-label { color: $focused-placeholder-color; &.mat-accent { @@ -71,7 +71,7 @@ // achieved with the ng-* classes, we use this approach in order to ensure that the same // logic is used to style the error state and to show the error messages. .mat-form-field-invalid { - .mat-form-field-placeholder { + .mat-form-field-label { color: $underline-color-warn; &.mat-accent, @@ -90,8 +90,8 @@ } } -// Applies a floating placeholder above the form field control itself. -@mixin _mat-form-field-placeholder-floating($font-scale, $infix-padding, $infix-margin-top) { +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-label-floating($font-scale, $infix-padding, $infix-margin-top) { // We use perspective to fix the text blurriness as described here: // http://www.useragentman.com/blog/2014/05/04/fixing-typography-inside-of-2-d-css-transforms/ // This results in a small jitter after the label floats on Firefox, which the @@ -111,7 +111,7 @@ // those places uses `:-webkit-autofill`. When Firefox encounters this unknown pseuedo-class, // it ignores the entire rule. To work around this, we force one of the delcarations to be // technically different but still render the same by adding a tiny value to the transform / width. -@mixin _mat-form-field-placeholder-float-nodedupe($font-scale, $infix-padding, $infix-margin-top) { +@mixin _mat-form-field-label-float-nodedupe($font-scale, $infix-padding, $infix-margin-top) { transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) translateZ(0.002px); -ms-transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale); @@ -188,24 +188,24 @@ } .mat-form-field-can-float { - &.mat-form-field-should-float .mat-form-field-placeholder { - @include _mat-form-field-placeholder-floating( + &.mat-form-field-should-float .mat-form-field-label { + @include _mat-form-field-label-floating( $subscript-font-scale, $infix-padding, $infix-margin-top); } - .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-placeholder-wrapper - .mat-form-field-placeholder { - @include _mat-form-field-placeholder-float-nodedupe( + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-label-float-nodedupe( $subscript-font-scale, $infix-padding, $infix-margin-top); } } - .mat-form-field-placeholder-wrapper { + .mat-form-field-label-wrapper { top: -$infix-margin-top; padding-top: $infix-margin-top; } - .mat-form-field-placeholder { + .mat-form-field-label { top: $infix-margin-top + $infix-padding; } diff --git a/src/lib/form-field/form-field-control.ts b/src/lib/form-field/form-field-control.ts index 7846a0b656c6..8359f9a85c81 100644 --- a/src/lib/form-field/form-field-control.ts +++ b/src/lib/form-field/form-field-control.ts @@ -37,7 +37,13 @@ export abstract class MatFormFieldControl { readonly empty: boolean; /** Whether the `MatFormField` label should try to float. */ - readonly shouldPlaceholderFloat: boolean; + readonly shouldLabelFloat?: boolean; + + /** + * Whether the `MatFormField` placeholder should try to float. + * @deprecated Use `shouldLabelFloat` instead. + */ + readonly shouldPlaceholderFloat?: boolean; /** Whether the control is required. */ readonly required: boolean; diff --git a/src/lib/form-field/form-field-module.ts b/src/lib/form-field/form-field-module.ts index dd4dff39789a..17bd1dd92e5b 100644 --- a/src/lib/form-field/form-field-module.ts +++ b/src/lib/form-field/form-field-module.ts @@ -15,6 +15,7 @@ import {MatHint} from './hint'; import {MatPlaceholder} from './placeholder'; import {MatPrefix} from './prefix'; import {MatSuffix} from './suffix'; +import {MatLabel} from './label'; @NgModule({ @@ -25,6 +26,7 @@ import {MatSuffix} from './suffix'; MatPlaceholder, MatPrefix, MatSuffix, + MatLabel, ], imports: [ CommonModule, @@ -37,6 +39,7 @@ import {MatSuffix} from './suffix'; MatPlaceholder, MatPrefix, MatSuffix, + MatLabel, ], }) export class MatFormFieldModule {} diff --git a/src/lib/form-field/form-field.html b/src/lib/form-field/form-field.html index f78d16a64000..efc1e015db93 100644 --- a/src/lib/form-field/form-field.html +++ b/src/lib/form-field/form-field.html @@ -8,24 +8,31 @@

- + -
diff --git a/src/lib/form-field/form-field.md b/src/lib/form-field/form-field.md index 19d954a98e26..c28164137e08 100644 --- a/src/lib/form-field/form-field.md +++ b/src/lib/form-field/form-field.md @@ -13,35 +13,39 @@ The following Angular Material components are designed to work inside a ` -### Floating placeholder +### Floating label -The floating placeholder is a text label displayed on top of the form field control when -the control does not contain any text. By default, when text is present the floating placeholder -floats above the form field control. +The floating label is a text label displayed on top of the form field control when +the control does not contain any text. By default, when text is present the floating label +floats above the form field control. The label for a form field can be defined either through a +`mat-label` element, by setting the `placeholder` attribute on the form control or using the +`mat-placeholder` element. If there are multiple clashing values (e.g. a `placeholder` and a +label) the `mat-label` will take precedence and the `placeholder` will be shown only when there is +no value. Placeholder text can be specified using the `placeholder` property on the form field control, or by adding a `` element inside the form field. Only one of these options should be used, specifying both will raise an error. If the form field control is marked with a `required` attribute, an asterisk will be appended to the -placeholder to indicate the fact that it is a required field. If unwanted, this can be disabled by +label to indicate the fact that it is a required field. If unwanted, this can be disabled by setting the `hideRequiredMarker` property on `` -The `floatPlaceholder` property of `` can be used to change this default floating -behavior. It can set to `never` to hide the placeholder instead of float it when text is present in -the form field control. It can be set to `always` to float the placeholder even when no text is +The `floatLabel` property of `` can be used to change this default floating +behavior. It can set to `never` to hide the label instead of float it when text is present in +the form field control. It can be set to `always` to float the label even when no text is present in the form field control. It can also be set to `auto` to restore the default behavior. - + -Global default placeholder options can be specified by setting providing a value for -`MAT_PLACEHOLDER_GLOBAL_OPTIONS` in your application's root module. Like the property, the global +Global default label options can be specified by setting providing a value for +`MAT_LABEL_GLOBAL_OPTIONS` in your application's root module. Like the property, the global setting can be either `always`, `never`, or `auto`. ```ts @NgModule({ providers: [ - {provide: MAT_PLACEHOLDER_GLOBAL_OPTIONS, useValue: {float: 'always'}} + {provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: {float: 'always'}} ] }) ``` @@ -96,7 +100,7 @@ information on this see the guide on ### Theming `` has a `color` property which can be set to `primary`, `accent`, or `warn`. This -will set the color of the form field underline and floating placeholder based on the theme colors +will set the color of the form field underline and floating label based on the theme colors of your app. `` inherits its `font-size` from its parent element. This can be overridden to an @@ -112,8 +116,8 @@ mat-form-field.mat-form-field { ### Accessibility -If a floating placeholder is specified, it will be automatically used as the label for the form -field control. If no floating placeholder is specified, the user should label the form field control +If a floating label is specified, it will be automatically used as the label for the form +field control. If no floating label is specified, the user should label the form field control themselves using `aria-label`, `aria-labelledby` or `