diff --git a/src/demo-app/a11y/autocomplete/autocomplete-a11y.html b/src/demo-app/a11y/autocomplete/autocomplete-a11y.html index e84762443315..95c5df20d85e 100644 --- a/src/demo-app/a11y/autocomplete/autocomplete-a11y.html +++ b/src/demo-app/a11y/autocomplete/autocomplete-a11y.html @@ -2,7 +2,8 @@

Filtering and selection

Select your favorite state

- Search for a state + diff --git a/src/demo-app/a11y/datepicker/datepicker-a11y.html b/src/demo-app/a11y/datepicker/datepicker-a11y.html index 9b1301e668ac..48692bf20fbd 100644 --- a/src/demo-app/a11y/datepicker/datepicker-a11y.html +++ b/src/demo-app/a11y/datepicker/datepicker-a11y.html @@ -1,13 +1,13 @@

Choose a date (e.g. choose your date of birth)

+ Date of birth + required> @@ -44,14 +44,14 @@

Choose a date with touch UI (e.g. choose a payment date on mobile)

Choose date with startAt, min and max (e.g. schedule a departing and returning flight)

+ Departure date + required> @@ -65,13 +65,13 @@

Choose date with startAt, min and max (e.g. schedule a departing and returni + Return date + [max]="maxTripDate"> @@ -89,6 +89,7 @@

Choose date with startAt, min and max (e.g. schedule a departing and returni

Choose date with date filter (e.g. schedule a doctor's appointment)

+ Appointment date Choose date with date filter (e.g. schedule a doctor's appointment)

[min]="minAppointmentDate" [max]="maxAppointmentDate" [matDatepickerFilter]="weekdaysOnly" - required - placeholder="Appointment date"> + required> diff --git a/src/demo-app/a11y/dialog/dialog-address-form-a11y.html b/src/demo-app/a11y/dialog/dialog-address-form-a11y.html index abbeb3440645..7c0644ef6b0c 100644 --- a/src/demo-app/a11y/dialog/dialog-address-form-a11y.html +++ b/src/demo-app/a11y/dialog/dialog-address-form-a11y.html @@ -3,36 +3,44 @@

Company

- + Company (disabled) +
- + First name + - + Long last name that will be truncated +

- + Address + - + Address 2 +

- + City + - + State + - + Postal code + {{postalCode.value.length}} / 5
diff --git a/src/demo-app/a11y/input/input-a11y.html b/src/demo-app/a11y/input/input-a11y.html index cb9d19dfb345..006b85707ac3 100644 --- a/src/demo-app/a11y/input/input-a11y.html +++ b/src/demo-app/a11y/input/input-a11y.html @@ -1,18 +1,20 @@

Basic input box (e.g. name field)

- + First name + - + Last name +

Input with hint (e.g. password field)

- + Password +

[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null" [disabled]="inputDisabled" - placeholder="Pick a date" (dateInput)="onDateInput($event)" (dateChange)="onDateChange($event)"> Input disabled datepicker

+ Input disabled + [matDatepickerFilter]="filterOdd ? dateFilter : null" disabled> @@ -98,9 +101,9 @@

Input disabled via FormControl

+ FormControl disabled + [max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null"> @@ -114,9 +117,9 @@

Input disabled, datepicker popup enabled

+ Input disabled, datepicker enabled + [max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null"> @@ -126,9 +129,9 @@

Datepicker with value property binding

+ Value binding + [max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null"> diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index dcb68eb03872..a4e2fba1eecb 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -16,28 +16,34 @@

Dialog dimensions

- + Width + - + Height +

- + Min width + - + Min height +

- + Max width + - + Max height +

@@ -45,19 +51,23 @@

Dialog position

- + Top + - + Bottom +

- + Left + - + Right +

@@ -65,7 +75,8 @@

Dialog backdrop

- + Backdrop class +

@@ -75,7 +86,8 @@

Other options

- + Button alignment + Start End Center @@ -85,7 +97,8 @@

Other options

- + Dialog message +

@@ -104,7 +117,8 @@

Other options

It's Jazz!

- + How much? +

{{ data.message }}

diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 8e4ba9e3bd7a..b519069b7dc3 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -92,7 +92,8 @@ export class DialogDemo {

It's Jazz!

- + How much? +

{{ data.message }}

diff --git a/src/demo-app/expansion/expansion-demo.html b/src/demo-app/expansion/expansion-demo.html index 9e4ecb03f322..d3b0a94b375f 100644 --- a/src/demo-app/expansion/expansion-demo.html +++ b/src/demo-app/expansion/expansion-demo.html @@ -19,11 +19,13 @@

Single Expansion Panel


- + Collapsed height + - + Expanded height +
diff --git a/src/demo-app/input/input-demo.html b/src/demo-app/input/input-demo.html index d61f52c99508..c9932a43e58a 100644 --- a/src/demo-app/input/input-demo.html +++ b/src/demo-app/input/input-demo.html @@ -3,44 +3,51 @@ - + Company (disabled) +
- + First name + - + Long last name that will be truncated +

- + Address + - + Address 2 +

@@ -377,10 +410,13 @@

Textarea

Forms - + + Reactive + - + Template +
- + Delayed value +
@@ -509,10 +546,10 @@

Regular <textarea>

<textarea> with mat-form-field

+ Autosized textarea + matAutosizeMaxRows="10">
@@ -526,3 +563,56 @@

<textarea> with ngModel

+ + + Appearance + + + Legacy appearance + + This field is required + Please type something here + + + + Standard appearance + + This field is required + Please type something here + + + + Box appearance + + This field is required + Please type something here + + +
- + City + - + State + - + Postal code + mode_edit {{postalCode.value.length}} / 5 @@ -59,12 +66,14 @@

Regular

- + Example + This field is required - + Email + This field is required @@ -77,7 +86,8 @@

Regular

With hint

- + Example + This field is required Please type something here @@ -87,8 +97,8 @@

With hint

Inside a form

- + Example + This field is required @@ -97,8 +107,8 @@

Inside a form

With a custom error function

+ Example @@ -113,21 +123,24 @@

With a custom error function

Text

- + Amount + .00

Icons

- + Amount + attach_money mode_edit

Icon buttons

- + Amount + @@ -139,37 +152,46 @@

Icon buttons

Input

- + Default color + - + Accent color + - + Warn color +

Textarea

- + Default color + - + Accent color + - + Warn color +

With error

- + Default color + This field is required - + Accent color + This field is required - + Warn color + This field is required
@@ -181,9 +203,9 @@

With error

Input

+ Character count (100 max) @@ -195,9 +217,9 @@

Input

Textarea

+ Character count (100 max) {{characterCountTextareaHintExample.value.length}} / 100 @@ -212,7 +234,8 @@

Textarea

Hello  - + First name + , how are you? @@ -220,35 +243,40 @@

Textarea

- + Disabled field + - + Required field +

- + 100% width label +

- + Character count (100 max) + {{input.value.length}} / 100

- + Show hint label +

- - I favorite bold placeholder - + + I favorite bold label + I also home italic hint labels @@ -256,13 +284,15 @@

Textarea

- + Show hint label with character count + {{hintLabelWithCharCount.value.length}}

- + Enter text to count + Enter some text to count how many characters are in it. The character count is shown on the right. @@ -305,7 +335,8 @@

Textarea

- + Label +

@@ -318,17 +349,20 @@

Textarea

- + Prefixed +

Example: 
- + Suffixed + .00 €
Both: - + Email address + email   @gmail.com @@ -358,10 +392,9 @@

Textarea

{{i+1}} + Value
+ + + +
+ + Legacy appearance + + This field is required + Please type something here + + + + Standard appearance + + This field is required + Please type something here + + + + Box appearance + + This field is required + Please type something here + +
+
+ diff --git a/src/demo-app/input/input-demo.ts b/src/demo-app/input/input-demo.ts index 67cc69667381..04ed022cb3b6 100644 --- a/src/demo-app/input/input-demo.ts +++ b/src/demo-app/input/input-demo.ts @@ -52,6 +52,10 @@ export class InputDemo { delayedFormControl = new FormControl(''); model = 'hello'; + legacyAppearance: string; + standardAppearance: string; + boxAppearance: string; + constructor() { setTimeout(() => this.delayedFormControl.setValue('hello'), 100); } diff --git a/src/demo-app/ripple/ripple-demo.html b/src/demo-app/ripple/ripple-demo.html index 98feef4e9d23..9b0b8180cc3e 100644 --- a/src/demo-app/ripple/ripple-demo.html +++ b/src/demo-app/ripple/ripple-demo.html @@ -28,10 +28,12 @@
- + Ripple radius + - + Ripple color +
diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index dd1ca63c25a5..0d199f581348 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -7,7 +7,8 @@ ngModel - Drink + None @@ -50,7 +51,8 @@ - Pokemon + {{ creature.viewValue }} @@ -78,7 +80,8 @@ Without Angular forms - + Digimon + None {{ creature.viewValue }} @@ -97,7 +100,8 @@ - + Pokemon + @@ -114,11 +118,11 @@ compareWith - + Drink + {{ drink.viewValue }} @@ -146,7 +150,8 @@ - + Food I would like to eat + {{ food.viewValue }} @@ -167,7 +172,8 @@ - + Starter pokemon + {{ creature.viewValue }} diff --git a/src/demo-app/snack-bar/snack-bar-demo.html b/src/demo-app/snack-bar/snack-bar-demo.html index 1bdb4819e7aa..9593e6034324 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.html +++ b/src/demo-app/snack-bar/snack-bar-demo.html @@ -25,10 +25,10 @@

SnackBar demo

Show button on snack bar

+ Snack bar action label
@@ -37,10 +37,10 @@

SnackBar demo

Auto hide after duration

+ Auto hide duration in ms
diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index 0dd4274921ba..40f2828e2a0b 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -6,12 +6,14 @@

Linear Vertical Stepper Demo using a single form

Fill out your name - + First name + This field is required - + Last name + This field is required
@@ -24,7 +26,8 @@

Linear Vertical Stepper Demo using a single form

Fill out your email address
- + Email address + The input is invalid.
@@ -49,11 +52,13 @@

Linear Horizontal Stepper Demo using a different form for each step

Fill out your name - + First name + This field is required - + Last name + This field is required
@@ -66,7 +71,8 @@

Linear Horizontal Stepper Demo using a different form for each step

Fill out your email address - + Email address + The input is invalid
@@ -93,11 +99,13 @@

Vertical Stepper Demo

Fill out your name - + First name + - + Last name +
@@ -109,7 +117,8 @@

Vertical Stepper Demo

Fill out your phone number
- + Phone number +
@@ -122,7 +131,8 @@

Vertical Stepper Demo

Fill out your address
- + Address +
@@ -143,11 +153,13 @@

Horizontal Stepper Demo with Text Label

- + First name + - + Last name +
@@ -156,7 +168,8 @@

Horizontal Stepper Demo with Text Label

- + Phone number +
@@ -166,7 +179,8 @@

Horizontal Stepper Demo with Text Label

- + Address +
@@ -187,7 +201,8 @@

Horizontal Stepper Demo with Templated Label

{{step.label}} - + Answer +
@@ -200,7 +215,8 @@

Stepper with autosize textarea

- + Autosize textarea + diff --git a/src/demo-app/table/table-demo.html b/src/demo-app/table/table-demo.html index f25906d05ca4..5fdf813f6bf9 100644 --- a/src/demo-app/table/table-demo.html +++ b/src/demo-app/table/table-demo.html @@ -214,7 +214,8 @@

MatTable Using 'When' Rows for Interactive Details

MatTable With MatTableDataSource Example

- + Filter users +
diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index 8b85ef763f3d..8c1ac8261ad0 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -89,7 +89,8 @@

Tab Group Demo - Dynamic Tabs



- + Tab label +

@@ -187,7 +189,8 @@

Tab Group Demo - Fixed Height



- + Tab label +
@@ -217,7 +220,8 @@

Async Tabs



- + Tab label +
@@ -283,7 +287,8 @@

Tabs with autosize textarea

- + Autosize textarea +
diff --git a/src/lib/form-field/BUILD.bazel b/src/lib/form-field/BUILD.bazel index f88d4b2e648d..77b35dda754e 100644 --- a/src/lib/form-field/BUILD.bazel +++ b/src/lib/form-field/BUILD.bazel @@ -9,6 +9,9 @@ ng_module( module_name = "@angular/material/form-field", assets = [ ":form_field_css", + ":form_field_box_css", + ":form_field_legacy_css", + ":form_field_standard_css", "//src/lib/input:input_css" ], deps = [ @@ -19,14 +22,31 @@ ng_module( tsconfig = ":tsconfig-build.json", ) - sass_binary( name = "form_field_scss", src = "form-field.scss", deps = ["//src/lib/core:core_scss_lib"], ) -# TODO(jelbourn): remove this when sass_binary supports specifying an output filename and dir. +sass_binary( + name = "form_field_box_scss", + src = "form-field-box.scss", + deps = ["//src/lib/core:core_scss_lib"], +) + +sass_binary( + name = "form_field_legacy_scss", + src = "form-field-legacy.scss", + deps = ["//src/lib/core:core_scss_lib"], +) + +sass_binary( + name = "form_field_standard_scss", + src = "form-field-standard.scss", + deps = ["//src/lib/core:core_scss_lib"], +) + +# TODO(jelbourn): remove these when sass_binary supports specifying an output filename and dir. # Copy the output of the sass_binary such that the filename and path match what we expect. genrule( name = "form_field_css", @@ -35,3 +55,23 @@ genrule( cmd = "cat $(locations :form_field_scss) > $@", ) +genrule( + name = "form_field_box_css", + srcs = [":form_field_box_scss"], + outs = ["form-field-box.css"], + cmd = "cat $(locations :form_field_box_scss) > $@", +) + +genrule( + name = "form_field_legacy_css", + srcs = [":form_field_legacy_scss"], + outs = ["form-field-legacy.css"], + cmd = "cat $(locations :form_field_legacy_scss) > $@", +) + +genrule( + name = "form_field_standard_css", + srcs = [":form_field_standard_scss"], + outs = ["form-field-standard.css"], + cmd = "cat $(locations :form_field_standard_scss) > $@", +) diff --git a/src/lib/form-field/_form-field-box-theme.scss b/src/lib/form-field/_form-field-box-theme.scss new file mode 100644 index 000000000000..7242c2307a0f --- /dev/null +++ b/src/lib/form-field/_form-field-box-theme.scss @@ -0,0 +1,93 @@ +@import '../core/theming/palette'; +@import '../core/theming/theming'; +@import '../core/style/form-common'; +@import '../core/typography/typography-utils'; + + +// Theme styles that only apply to the box appearance of the form-field. + +@mixin mat-form-field-box-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $box-background: mat-color($foreground, base, if($is-dark-theme, 0.1, 0.06)); + $box-disabled-background: mat-color($foreground, base, if($is-dark-theme, 0.05, 0.03)); + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-box { + .mat-form-field-flex { + background-color: $box-background; + } + + &.mat-form-field-disabled .mat-form-field-flex { + background-color: $box-disabled-background; + } + + .mat-form-field-underline::before { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline::before { + background-color: transparent; + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-box-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-box-label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-box-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-box-dedupe; + + $mat-form-field-box-dedupe: $mat-form-field-box-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-box-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The padding on the infix. Mocks show half of the text size. + $infix-padding: 0.5em; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The amount we offset the label in the box appearance. + $box-appearance-label-offset: -0.5em * $line-height; + + .mat-form-field-appearance-box { + .mat-form-field-label { + margin-top: $box-appearance-label-offset; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding + $box-appearance-label-offset, + $infix-margin-top); + } + + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding + $box-appearance-label-offset, + $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-label-floating( + $subscript-font-scale, $infix-padding + $box-appearance-label-offset, + $infix-margin-top); + } + } + } +} diff --git a/src/lib/form-field/_form-field-legacy-theme.scss b/src/lib/form-field/_form-field-legacy-theme.scss new file mode 100644 index 000000000000..6e09a89f1579 --- /dev/null +++ b/src/lib/form-field/_form-field-legacy-theme.scss @@ -0,0 +1,123 @@ +@import '../core/theming/palette'; +@import '../core/theming/theming'; +@import '../core/style/form-common'; +@import '../core/typography/typography-utils'; + + +// Theme styles that only apply to the legacy appearance of the form-field. + +@mixin mat-form-field-legacy-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-legacy { + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +// Used to make instances of the _mat-form-field-label-floating mixin negligibly different, +// and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some +// of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters +// an unknown pseudo-class it will discard the entire rule set. +$mat-form-field-legacy-dedupe: 0; + +// Applies a floating label above the form field control itself. +@mixin _mat-form-field-legacy-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 + // translateZ fixes. + transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) + translateZ(0.001px + $mat-form-field-legacy-dedupe); + // The tricks above used to smooth out the animation on chrome and firefox actually make things + // worse on IE, so we don't include them in the IE version. + -ms-transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-legacy-dedupe) + scale($font-scale); + + width: 100% / $font-scale + $mat-form-field-legacy-dedupe; + + $mat-form-field-legacy-dedupe: $mat-form-field-legacy-dedupe + 0.00001 !global; +} + +@mixin mat-form-field-legacy-typography($config) { + // The unit-less line-height from the font config. + $line-height: mat-line-height($config, input); + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount of space between the top of the line and the top of the actual text + // (as a fraction of the font-size). + $line-spacing: ($line-height - 1) / 2; + // The padding on the infix. Mocks show half of the text size, but seem to measure from the edge + // of the text itself, not the edge of the line; therefore we subtract off the line spacing. + $infix-padding: 0.5em - $line-spacing; + // The margin applied to the form-field-infix to reserve space for the floating label. + $infix-margin-top: 1em * $line-height * $subscript-font-scale; + // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. + // Mocks show half of the text size, but this margin is applied to an element with the subscript + // text font size, so we need to divide by the scale factor to make it half of the original text + // size. We again need to subtract off the line spacing since the mocks measure to the edge of the + // text, not the edge of the line. + $subscript-margin-top: 0.5em / $subscript-font-scale - ($line-spacing * 2); + // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's + // absolutely positioned. This is a combination of the subscript's margin and line-height, but we + // need to multiply by the subscript font scale factor since the wrapper has a larger font size. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field-appearance-legacy { + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-infix { + padding: $infix-padding 0; + } + + &.mat-form-field-can-float { + &.mat-form-field-should-float .mat-form-field-label, + .mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + .mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a label attribute but label not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[label]:not(:label-shown) + .mat-form-field-label-wrapper + .mat-form-field-label { + @include _mat-form-field-legacy-label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label { + top: $infix-margin-top + $infix-padding; + } + + .mat-form-field-underline { + // We want the underline to start at the end of the content box, not the padding box, + // so we move it up by the padding amount. + bottom: $wrapper-padding-bottom; + } + + .mat-form-field-subscript-wrapper { + margin-top: $subscript-margin-top; + + // We want the subscript to start at the end of the content box, not the padding box, + // so we move it up by the padding amount (adjusted for the smaller font size); + top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); + } + } +} diff --git a/src/lib/form-field/_form-field-standard-theme.scss b/src/lib/form-field/_form-field-standard-theme.scss new file mode 100644 index 000000000000..01d7e123c3e5 --- /dev/null +++ b/src/lib/form-field/_form-field-standard-theme.scss @@ -0,0 +1,26 @@ +@import '../core/theming/palette'; +@import '../core/theming/theming'; +@import '../core/style/form-common'; +@import '../core/typography/typography-utils'; + + +// Theme styles that only apply to the standard appearance of the form-field. + +@mixin mat-form-field-standard-theme($theme) { + $foreground: map-get($theme, foreground); + $is-dark-theme: map-get($theme, is-dark); + + $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + + .mat-form-field-appearance-standard { + .mat-form-field-underline { + background-color: $underline-color; + } + + &.mat-form-field-disabled .mat-form-field-underline { + @include mat-control-disabled-underline($underline-color); + } + } +} + +@mixin mat-form-field-standard-typography($config) {} diff --git a/src/lib/form-field/_form-field-theme.scss b/src/lib/form-field/_form-field-theme.scss index ee21101bc024..2641e2c86363 100644 --- a/src/lib/form-field/_form-field-theme.scss +++ b/src/lib/form-field/_form-field-theme.scss @@ -2,8 +2,13 @@ @import '../core/theming/theming'; @import '../core/style/form-common'; @import '../core/typography/typography-utils'; +@import 'form-field-box-theme.scss'; +@import 'form-field-legacy-theme.scss'; +@import 'form-field-standard-theme.scss'; +// Theme styles that apply to all appearances of the form-field. + @mixin mat-form-field-theme($theme) { $primary: map-get($theme, primary); $accent: map-get($theme, accent); @@ -18,7 +23,7 @@ $required-label-color: mat-color($accent); // Underline colors. - $underline-color: mat-color($foreground, divider, if($is-dark-theme, 0.7, 0.42)); + $underline-color-base: mat-color($foreground, divider, if($is-dark-theme, 1, 0.87)); $underline-color-accent: mat-color($accent); $underline-color-warn: mat-color($warn); $underline-focused-color: mat-color($primary); @@ -47,30 +52,28 @@ color: $required-label-color; } - .mat-form-field-underline { - background-color: $underline-color; - } - - .mat-form-field-disabled .mat-form-field-underline { - @include mat-control-disabled-underline($underline-color); + .mat-form-field-ripple { + background-color: $underline-color-base; } - .mat-form-field-ripple { - background-color: $underline-focused-color; + .mat-form-field.mat-focused { + .mat-form-field-ripple { + background-color: $underline-focused-color; - &.mat-accent { - background-color: $underline-color-accent; - } + &.mat-accent { + background-color: $underline-color-accent; + } - &.mat-warn { - background-color: $underline-color-warn; + &.mat-warn { + background-color: $underline-color-warn; + } } } // Styling for the error state of the form field. Note that while the same can be // 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.mat-form-field-invalid { .mat-form-field-label { color: $underline-color-warn; @@ -88,29 +91,25 @@ .mat-error { color: $underline-color-warn; } + + @include mat-form-field-legacy-theme($theme); + @include mat-form-field-standard-theme($theme); + @include mat-form-field-box-theme($theme); } // Used to make instances of the _mat-form-field-label-floating mixin negligibly different, // and prevent Google's CSS Optimizer from collapsing the declarations. This is needed because some // of the selectors contain pseudo-classes not recognized in all browsers. If a browser encounters // an unknown pseudo-class it will discard the entire rule set. -$dedupe: 0; +$mat-form-field-dedupe: 0; // 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 - // translateZ fixes. - transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) - translateZ(0.001px + $dedupe); - // The tricks above used to smooth out the animation on chrome and firefox actually make things - // worse on IE, so we don't include them in the IE version. - -ms-transform: translateY(-$infix-margin-top - $infix-padding + $dedupe) scale($font-scale); - - width: 100% / $font-scale + $dedupe; - - $dedupe: $dedupe + 0.00001 !global; + transform: translateY(-$infix-margin-top - $infix-padding + $mat-form-field-dedupe) + scale($font-scale); + width: 100% / $font-scale + $mat-form-field-dedupe; + + $mat-form-field-dedupe: $mat-form-field-dedupe + 0.00001 !global; } @mixin mat-form-field-typography($config) { @@ -122,12 +121,8 @@ $dedupe: 0; // The amount to scale the font for the prefix and suffix icons. $prefix-suffix-icon-font-scale: 1.5; - // The amount of space between the top of the line and the top of the actual text - // (as a fraction of the font-size). - $line-spacing: ($line-height - 1) / 2; - // The padding on the infix. Mocks show half of the text size, but seem to measure from the edge - // of the text itself, not the edge of the line; therefore we subtract off the line spacing. - $infix-padding: 0.5em - $line-spacing; + // The padding on the infix. Mocks show half of the text size. + $infix-padding: 0.5em; // The margin applied to the form-field-infix to reserve space for the floating label. $infix-margin-top: 1em * $line-height * $subscript-font-scale; // Font size to use for the label and subscript text. @@ -137,9 +132,8 @@ $dedupe: 0; // The space between the bottom of the .mat-form-field-flex area and the subscript wrapper. // Mocks show half of the text size, but this margin is applied to an element with the subscript // text font size, so we need to divide by the scale factor to make it half of the original text - // size. We again need to subtract off the line spacing since the mocks measure to the edge of the - // text, not the edge of the line. - $subscript-margin-top: 0.5em / $subscript-font-scale - ($line-spacing * 2); + // size. + $subscript-margin-top: 0.5em / $subscript-font-scale; // The padding applied to the form-field-wrapper to reserve space for the subscript, since it's // absolutely positioned. This is a combination of the subscript's margin and line-height, but we // need to multiply by the subscript font scale factor since the wrapper has a larger font size. @@ -227,4 +221,8 @@ $dedupe: 0; // so we move it up by the padding amount (adjusted for the smaller font size); top: calc(100% - #{$wrapper-padding-bottom / $subscript-font-scale}); } + + @include mat-form-field-legacy-typography($config); + @include mat-form-field-standard-typography($config); + @include mat-form-field-box-typography($config); } diff --git a/src/lib/form-field/form-field-box.scss b/src/lib/form-field/form-field-box.scss new file mode 100644 index 000000000000..a939dd6bfba1 --- /dev/null +++ b/src/lib/form-field/form-field-box.scss @@ -0,0 +1,63 @@ +@import '../core/style/variables'; +@import '../core/style/vendor-prefixes'; + + +// Styles that only apply to the box appearance of the form-field. + +// The border radius for the form field box. +$mat-form-field-box-border-radius: 4px !default; +// The height of the underline at the bottom of the form field box. +$mat-form-field-box-underline-height: 2px !default; +// The horizontal padding between the edge of the form field box and the start of the text. +$mat-form-field-box-side-padding: 1em !default; +// The vertical padding between the edge of the form field box and the start of the text as well as +// between the floating label and the value. +$mat-form-field-box-line-spacing: 0.5em !default; +// The scale of the subscript and floating label text w.r.t the value text. +$mat-form-field-box-subscript-font-scale: 0.75 !default; +// The horizontal padding between the edge of the subscript box and the start of the subscript text. +$mat-form-field-box-subscript-padding: + $mat-form-field-box-side-padding / $mat-form-field-box-subscript-font-scale; + + +.mat-form-field-appearance-box { + .mat-form-field-flex { + border-radius: $mat-form-field-box-border-radius; + padding: $mat-form-field-box-line-spacing $mat-form-field-box-side-padding 0 + $mat-form-field-box-side-padding; + } + + .mat-form-field-underline { + height: $mat-form-field-box-border-radius; + border-radius: 0 0 $mat-form-field-box-border-radius $mat-form-field-box-border-radius; + overflow: hidden; + } + + .mat-form-field-underline::before { + content: ''; + display: block; + position: absolute; + bottom: 0; + height: $mat-form-field-box-underline-height; + width: 100%; + } + + .mat-form-field-ripple { + bottom: 0; + height: $mat-form-field-box-underline-height; + } + + // Note that we need this specific of a selector because we don't want + // the hover effect to show when the user hovers over the hints. + &:not(.mat-form-field-disabled) .mat-form-field-flex:hover ~ .mat-form-field-underline { + .mat-form-field-ripple { + opacity: 1; + transform: none; + transition: opacity 600ms $swift-ease-out-timing-function; + } + } + + .mat-form-field-subscript-wrapper { + padding: 0 $mat-form-field-box-subscript-padding; + } +} diff --git a/src/lib/form-field/form-field-legacy.scss b/src/lib/form-field/form-field-legacy.scss new file mode 100644 index 000000000000..301fea946862 --- /dev/null +++ b/src/lib/form-field/form-field-legacy.scss @@ -0,0 +1,36 @@ +@import '../core/style/variables'; +@import '../core/style/vendor-prefixes'; + + +// Styles that only apply to the legacy appearance of the form-field. + +// The height of the underline. +$mat-form-field-legacy-underline-height: 1px !default; + + +.mat-form-field-appearance-legacy { + .mat-form-field-label { + transform: perspective(100px); + -ms-transform: none; + } + + // The underline is what's shown under the control, its prefix and its suffix. + // The ripple is the blue animation coming on top of it. + .mat-form-field-underline { + height: $mat-form-field-legacy-underline-height; + } + + .mat-form-field-ripple { + top: 0; + height: $mat-form-field-legacy-underline-height * 2; + } + + &.mat-form-field-disabled .mat-form-field-underline { + background-position: 0; + background-color: transparent; + } + + &.mat-form-field-invalid:not(.mat-focused) .mat-form-field-ripple { + height: $mat-form-field-legacy-underline-height; + } +} diff --git a/src/lib/form-field/form-field-standard.scss b/src/lib/form-field/form-field-standard.scss new file mode 100644 index 000000000000..a79a00c00612 --- /dev/null +++ b/src/lib/form-field/form-field-standard.scss @@ -0,0 +1,47 @@ +@import '../core/style/variables'; +@import '../core/style/vendor-prefixes'; + + +// Styles that only apply to the standard appearance of the form-field. + +// The height of the underline. +$mat-form-field-standard-underline-height: 1px !default; +// The bottom margin of the underline (used to push it up to align with box appearance underline). +$mat-form-field-standard-underline-margin-bottom: 1px !default; +// The padding between the top of the form field and the label text (used to align the standard +// form field with the box appearance). +$mat-form-field-standard-padding-top: 0.5em !default; + + +.mat-form-field-appearance-standard { + .mat-form-field-flex { + padding-top: $mat-form-field-standard-padding-top; + } + + // The underline is what's shown under the control, its prefix and its suffix. + // The ripple is the blue animation coming on top of it. + .mat-form-field-underline { + height: $mat-form-field-standard-underline-height; + margin-bottom: $mat-form-field-standard-underline-margin-bottom; + } + + .mat-form-field-ripple { + top: 0; + height: $mat-form-field-standard-underline-height * 2; + } + + &.mat-form-field-disabled .mat-form-field-underline { + background-position: 0; + background-color: transparent; + } + + // Note that we need this specific of a selector because we don't want + // the hover effect to show when the user hovers over the hints. + &:not(.mat-form-field-disabled) .mat-form-field-flex:hover ~ .mat-form-field-underline { + .mat-form-field-ripple { + opacity: 1; + transform: none; + transition: opacity 600ms $swift-ease-out-timing-function; + } + } +} diff --git a/src/lib/form-field/form-field.scss b/src/lib/form-field/form-field.scss index 974a46997586..5e07a9513420 100644 --- a/src/lib/form-field/form-field.scss +++ b/src/lib/form-field/form-field.scss @@ -2,10 +2,10 @@ @import '../core/style/vendor-prefixes'; +// Styles that apply to all appearances of the form-field. + // Min amount of space between start and end hint. $mat-form-field-hint-min-space: 1em !default; -// The height of the underline. -$mat-form-field-underline-height: 1px !default; // Infix stretches to fit the container, but naturally wants to be this wide. We set this in order // to have a a consistent natural size for the various types of controls that can go in a form // field. @@ -35,6 +35,7 @@ $mat-form-field-default-infix-width: 180px !default; .mat-form-field-flex { display: inline-flex; align-items: baseline; + box-sizing: border-box; width: 100%; } @@ -96,10 +97,6 @@ $mat-form-field-default-infix-width: 180px !default; text-overflow: ellipsis; overflow: hidden; - // The perspective helps smooth out animations on Chrome and Firefox but isn't needed on IE. - transform: perspective(100px); - -ms-transform: none; - transform-origin: 0 0; transition: transform $swift-ease-out-duration $swift-ease-out-timing-function, color $swift-ease-out-duration $swift-ease-out-timing-function, @@ -155,49 +152,38 @@ $mat-form-field-default-infix-width: 180px !default; transition: none; } -// The underline is what's shown under the control, its prefix and its suffix. -// The ripple is the blue animation coming on top of it. .mat-form-field-underline { position: absolute; - height: $mat-form-field-underline-height; width: 100%; + // Need this so that the underline doesn't block the hover effect. + pointer-events: none; +} - .mat-form-field-disabled & { - background-position: 0; - background-color: transparent; - } +.mat-form-field-ripple { + position: absolute; + left: 0; + width: 100%; + transform-origin: 50%; + transform: scaleX(0.5); + opacity: 0; + transition: background-color $swift-ease-in-duration $swift-ease-in-timing-function; +} +.mat-form-field.mat-focused, +.mat-form-field.mat-form-field-invalid { .mat-form-field-ripple { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: $mat-form-field-underline-height * 2; - transform-origin: 50%; - transform: scaleX(0.5); - visibility: hidden; - opacity: 0; - transition: background-color $swift-ease-in-duration $swift-ease-in-timing-function; - - .mat-form-field-invalid:not(.mat-focused) & { - height: $mat-form-field-underline-height; - } - - .mat-focused &, - .mat-form-field-invalid & { - visibility: visible; - opacity: 1; - transform: scaleX(1); - transition: transform 300ms $swift-ease-out-timing-function, - opacity 100ms $swift-ease-out-timing-function, - background-color 300ms $swift-ease-out-timing-function; - } + opacity: 1; + transform: scaleX(1); + transition: transform 300ms $swift-ease-out-timing-function, + opacity 100ms $swift-ease-out-timing-function, + background-color 300ms $swift-ease-out-timing-function; } } // Wrapper for the hints and error messages. .mat-form-field-subscript-wrapper { position: absolute; + box-sizing: border-box; width: 100%; overflow: hidden; // prevents multi-line errors from overlapping the control } diff --git a/src/lib/form-field/form-field.ts b/src/lib/form-field/form-field.ts index e84da2d09ad3..7ded8522915e 100644 --- a/src/lib/form-field/form-field.ts +++ b/src/lib/form-field/form-field.ts @@ -7,8 +7,6 @@ */ import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {take} from 'rxjs/operators/take'; -import {startWith} from 'rxjs/operators/startWith'; import { AfterContentChecked, AfterContentInit, @@ -26,9 +24,12 @@ import { ViewChild, ViewEncapsulation, } from '@angular/core'; -import {FloatLabelType, MAT_LABEL_GLOBAL_OPTIONS, LabelOptions} from '@angular/material/core'; +import {FloatLabelType, LabelOptions, MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material/core'; import {fromEvent} from 'rxjs/observable/fromEvent'; +import {startWith} from 'rxjs/operators/startWith'; +import {take} from 'rxjs/operators/take'; import {MatError} from './error'; +import {matFormFieldAnimations} from './form-field-animations'; import {MatFormFieldControl} from './form-field-control'; import { getMatFormFieldDuplicatedHintError, @@ -36,16 +37,18 @@ import { getMatFormFieldPlaceholderConflictError, } from './form-field-errors'; import {MatHint} from './hint'; -import {MatPlaceholder} from './placeholder'; import {MatLabel} from './label'; +import {MatPlaceholder} from './placeholder'; import {MatPrefix} from './prefix'; import {MatSuffix} from './suffix'; -import {matFormFieldAnimations} from './form-field-animations'; let nextUniqueId = 0; +export type MatFormFieldAppearance = 'legacy' | 'standard' | 'box'; + + /** Container for form controls that applies Material Design styling and behavior. */ @Component({ moduleId: module.id, @@ -56,10 +59,19 @@ let nextUniqueId = 0; // MatInput is a directive and can't have styles, so we need to include its styles here. // The MatInput styles are fairly minimal so it shouldn't be a big deal for people who // aren't using MatInput. - styleUrls: ['form-field.css', '../input/input.css'], + styleUrls: [ + 'form-field.css', + 'form-field-box.css', + 'form-field-legacy.css', + 'form-field-standard.css', + '../input/input.css', + ], animations: [matFormFieldAnimations.transitionMessages], host: { 'class': 'mat-input-container mat-form-field', + '[class.mat-form-field-appearance-standard]': 'appearance == "standard"', + '[class.mat-form-field-appearance-box]': 'appearance == "box"', + '[class.mat-form-field-appearance-legacy]': 'appearance == "legacy"', '[class.mat-input-invalid]': '_control.errorState', '[class.mat-form-field-invalid]': '_control.errorState', '[class.mat-form-field-can-float]': '_canLabelFloat', @@ -86,6 +98,9 @@ let nextUniqueId = 0; export class MatFormField implements AfterViewInit, AfterContentInit, AfterContentChecked { private _labelOptions: LabelOptions; + /** The form-field appearance style. */ + @Input() appearance: MatFormFieldAppearance = 'legacy'; + /** Color of the form field underline, based on the theme. */ @Input() color: 'primary' | 'accent' | 'warn' = 'primary'; @@ -107,11 +122,11 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte /** Whether the floating label should always float or not. */ get _shouldAlwaysFloat() { - return this._floatLabel === 'always' && !this._showAlwaysAnimate; + return this.floatLabel === 'always' && !this._showAlwaysAnimate; } /** Whether the label can float or not. */ - get _canLabelFloat() { return this._floatLabel !== 'never'; } + get _canLabelFloat() { return this.floatLabel !== 'never'; } /** State of the mat-hint and mat-error animations. */ _subscriptAnimationState: string = ''; @@ -133,12 +148,21 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte * @deprecated Use floatLabel instead. */ @Input() - get floatPlaceholder(): FloatLabelType { return this._floatLabel; } + get floatPlaceholder(): FloatLabelType { return this.floatLabel; } set floatPlaceholder(value: FloatLabelType) { this.floatLabel = value; } - /** Whether the label should always float, never float or float as the user types. */ + /** + * Whether the label should always float, never float or float as the user types. + * + * Note: only the legacy appearance supports the `never` option. `never` was originally added as a + * way to make the floating label emulate the behavior of a standard input placeholder. However + * the form field now supports both floating labels and placeholders. Therefore in the non-legacy + * appearances the `never` option has been disabled in favor of just using the placeholder. + */ @Input() - get floatLabel(): FloatLabelType { return this._floatLabel; } + get floatLabel(): FloatLabelType { + return this.appearance !== 'legacy' && this._floatLabel === 'never' ? 'auto' : this._floatLabel; + } set floatLabel(value: FloatLabelType) { if (value !== this._floatLabel) { this._floatLabel = value || this._labelOptions.float || 'auto'; @@ -232,11 +256,14 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte } _hideControlPlaceholder() { - return !this._hasLabel() || !this._shouldLabelFloat(); + // In the legacy appearance the placeholder is promoted to a label if no label is given. + return this.appearance === 'legacy' && !this._hasLabel() || + this._hasLabel() && !this._shouldLabelFloat(); } _hasFloatingLabel() { - return this._hasLabel() || this._hasPlaceholder(); + // In the legacy appearance the placeholder is promoted to a label if no label is given. + return this._hasLabel() || this.appearance === 'legacy' && this._hasPlaceholder(); } /** Determines whether to display hints or errors. */ @@ -249,7 +276,7 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte _animateAndLockLabel(): void { if (this._hasFloatingLabel() && this._canLabelFloat) { this._showAlwaysAnimate = true; - this._floatLabel = 'always'; + this.floatLabel = 'always'; fromEvent(this._label.nativeElement, 'transitionend').pipe(take(1)).subscribe(() => { this._showAlwaysAnimate = false; diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts index a74de097c758..a8893544a7c0 100644 --- a/src/lib/input/input.spec.ts +++ b/src/lib/input/input.spec.ts @@ -1,7 +1,7 @@ import {Platform, PlatformModule} from '@angular/cdk/platform'; import {createFakeEvent, dispatchFakeEvent, wrappedErrorMessage} from '@angular/cdk/testing'; import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; -import {ComponentFixture, inject, TestBed, fakeAsync, flush} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing'; import { FormControl, FormGroup, @@ -12,16 +12,17 @@ import { Validators, } from '@angular/forms'; import { - MAT_LABEL_GLOBAL_OPTIONS, - ShowOnDirtyErrorStateMatcher, ErrorStateMatcher, FloatLabelType, + MAT_LABEL_GLOBAL_OPTIONS, + ShowOnDirtyErrorStateMatcher, } from '@angular/material/core'; import { getMatFormFieldDuplicatedHintError, getMatFormFieldMissingControlError, getMatFormFieldPlaceholderConflictError, MatFormField, + MatFormFieldAppearance, MatFormFieldModule, } from '@angular/material/form-field'; import {By} from '@angular/platform-browser'; @@ -1132,6 +1133,76 @@ describe('MatInput with forms', () => { })); }); +describe('MatInput with appearance', () => { + const nonLegacyAppearances: MatFormFieldAppearance[] = ['standard', 'box']; + let fixture: ComponentFixture; + let testComponent: MatInputWithAppearance; + let containerEl: HTMLElement; + + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + MatFormFieldModule, + MatInputModule, + NoopAnimationsModule, + PlatformModule, + ReactiveFormsModule, + ], + declarations: [ + MatInputWithAppearance, + ], + }); + + TestBed.compileComponents(); + })); + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(MatInputWithAppearance); + fixture.detectChanges(); + testComponent = fixture.componentInstance; + containerEl = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; + })); + + it('legacy appearance should promote placeholder to label', fakeAsync(() => { + testComponent.appearance = 'legacy'; + fixture.detectChanges(); + + expect(containerEl.classList).toContain('mat-form-field-appearance-legacy'); + expect(testComponent.formField._hasFloatingLabel()).toBe(true); + expect(testComponent.formField._hideControlPlaceholder()).toBe(true); + })); + + it('non-legacy appearances should not promote placeholder to label', fakeAsync(() => { + for (let appearance of nonLegacyAppearances) { + testComponent.appearance = appearance; + fixture.detectChanges(); + + expect(containerEl.classList).toContain(`mat-form-field-appearance-${appearance}`); + expect(testComponent.formField._hasFloatingLabel()).toBe(false); + expect(testComponent.formField._hideControlPlaceholder()).toBe(false); + } + })); + + it('legacy appearance should respect float never', fakeAsync(() => { + testComponent.appearance = 'legacy'; + fixture.detectChanges(); + + expect(containerEl.classList).toContain('mat-form-field-appearance-legacy'); + expect(testComponent.formField.floatLabel).toBe('never'); + })); + + it('non-legacy appearances should not respect float never', fakeAsync(() => { + for (let appearance of nonLegacyAppearances) { + testComponent.appearance = appearance; + fixture.detectChanges(); + + expect(containerEl.classList).toContain(`mat-form-field-appearance-${appearance}`); + expect(testComponent.formField.floatLabel).toBe('auto'); + } + })); +}); + @Component({ template: ` @@ -1467,3 +1538,15 @@ class MatInputWithReadonlyInput {} class MatInputWithLabelAndPlaceholder { floatLabel: FloatLabelType; } + +@Component({ + template: ` + + + + ` +}) +class MatInputWithAppearance { + @ViewChild(MatFormField) formField: MatFormField; + appearance: MatFormFieldAppearance; +} diff --git a/src/lib/select/select.scss b/src/lib/select/select.scss index 7939c435dbd7..1a8305512d7b 100644 --- a/src/lib/select/select.scss +++ b/src/lib/select/select.scss @@ -45,6 +45,11 @@ $mat-select-placeholder-arrow-space: 2 * ($mat-select-arrow-size + $mat-select-a .mat-select-arrow-wrapper { display: table-cell; vertical-align: middle; + + // When used in a box appearance form-field the arrow should be centered in the box. + .mat-form-field-appearance-box & { + transform: translateY(-50%); + } } .mat-select-arrow { @@ -100,5 +105,7 @@ $mat-select-placeholder-arrow-space: 2 * ($mat-select-arrow-size + $mat-select-a // Remove the transition to prevent the placeholder // from overlapping when the label comes back down. transition: none; + // Prevents the '...' from showing on the parent element. + display: block; } }