From fca02cb91c57983580689c3593a919ea5db05750 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 16 Nov 2017 13:44:01 -0800 Subject: [PATCH 1/2] fix(input): Add pure-CSS floating label logic that will work on... ...server-side-rendered pages. --- src/lib/form-field/_form-field-theme.scss | 27 +++++++++++++++++-- src/lib/form-field/form-field.scss | 12 +++++++++ src/lib/input/input.ts | 13 +++++++-- .../kitchen-sink/kitchen-sink.html | 4 +++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/lib/form-field/_form-field-theme.scss b/src/lib/form-field/_form-field-theme.scss index 3277aac5bb81..55dbfcaa971f 100644 --- a/src/lib/form-field/_form-field-theme.scss +++ b/src/lib/form-field/_form-field-theme.scss @@ -113,11 +113,19 @@ // 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) { 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); + translateZ(0.001px) scale(1); + -ms-transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) scale(1); width: (100% / $font-scale) + 0.0001; } +@mixin _mat-form-field-placeholder-float-nodedupe2($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) + translateZ(0.001px) scale(1) scale(1); + -ms-transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) scale(1) + scale(1); + width: (100% / $font-scale) + 0.0002; +} + @mixin mat-form-field-typography($config) { // The unit-less line-height from the font config. $line-height: mat-line-height($config, input); @@ -198,6 +206,21 @@ @include _mat-form-field-placeholder-float-nodedupe( $subscript-font-scale, $infix-padding, $infix-margin-top); } + + // Server-side rendered matInput with focus (used as a pure CSS stand-in for + // mat-form-field-should-float). + .mat-input-server:focus + .mat-form-field-placeholder-wrapper .mat-form-field-placeholder { + @include _mat-form-field-placeholder-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + + // Server-side rendered matInput with a placeholder attribute but placeholder not shown + // (used as a pure CSS stand-in for mat-form-field-should-float). + .mat-input-server[placeholder]:not(:placeholder-shown) + .mat-form-field-placeholder-wrapper + .mat-form-field-placeholder { + @include _mat-form-field-placeholder-float-nodedupe2( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } } .mat-form-field-placeholder-wrapper { diff --git a/src/lib/form-field/form-field.scss b/src/lib/form-field/form-field.scss index 9f1b72717736..839d71814d31 100644 --- a/src/lib/form-field/form-field.scss +++ b/src/lib/form-field/form-field.scss @@ -138,6 +138,18 @@ $mat-form-field-default-infix-width: 180px !default; } } +// Server-side rendered matInput with focus or a placeholder attribute but placeholder not shown +// (used as a pure CSS stand-in for mat-form-field-should-float). +.mat-input-server:focus + .mat-form-field-placeholder-wrapper .mat-form-field-placeholder, +.mat-input-server[placeholder]:not(:placeholder-shown) + .mat-form-field-placeholder-wrapper + .mat-form-field-placeholder { + display: none; + + .mat-form-field-can-float & { + display: block; + } +} + // Disable the placeholder animation when the control is not empty (this prevents placeholder // animating up when the value is set programmatically). .mat-form-field-placeholder:not(.mat-form-field-empty) { diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index cc4f82bc7875..c19b94286aef 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -16,7 +16,7 @@ import { Input, OnChanges, OnDestroy, - Optional, + Optional, PLATFORM_ID, Renderer2, Self } from '@angular/core'; @@ -26,6 +26,8 @@ import {MatFormFieldControl} from '@angular/material/form-field'; import {Subject} from 'rxjs/Subject'; import {getMatInputUnsupportedTypeError} from './input-errors'; import {MAT_INPUT_VALUE_ACCESSOR} from './input-value-accessor'; +import {isPlatformServer} from '@angular/common'; + // Invalid input type. Using one of these will throw an MatInputUnsupportedTypeError. const MAT_INPUT_INVALID_TYPES = [ @@ -50,6 +52,7 @@ let nextUniqueId = 0; exportAs: 'matInput', host: { 'class': 'mat-input-element mat-form-field-autofill-control', + '[class.mat-input-server]': '_isServer', // Native input properties that are overwritten by Angular inputs need to be synced with // the native input element. Otherwise property bindings for those don't work. '[attr.id]': 'id', @@ -86,6 +89,9 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, /** The aria-describedby attribute on the input for improved a11y. */ _ariaDescribedby: string; + /** Whether the component is being rendered on the server. */ + _isServer = false; + /** * Stream that emits whenever the state of the input changes such that the wrapping `MatFormField` * needs to run change detection. @@ -162,7 +168,8 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, @Optional() protected _parentForm: NgForm, @Optional() protected _parentFormGroup: FormGroupDirective, private _defaultErrorStateMatcher: ErrorStateMatcher, - @Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any) { + @Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any, + @Optional() @Inject(PLATFORM_ID) platformId?: Object) { // If no input value accessor was explicitly specified, use the element as the input value // accessor. this._inputValueAccessor = inputValueAccessor || this._elementRef.nativeElement; @@ -187,6 +194,8 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, } }); } + + this._isServer = !!(platformId && isPlatformServer(platformId)); } ngOnChanges() { diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index 6330e55cfc8d..cfb680ee394d 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -103,6 +103,10 @@

Icon

Input

+ + + + From 8bf43a896739964570c49eb9b21f23fcbd8257b8 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 17 Nov 2017 10:36:33 -0800 Subject: [PATCH 2/2] address comments --- src/lib/form-field/_form-field-theme.scss | 47 +++++++---------------- src/lib/input/input.ts | 8 ++-- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/lib/form-field/_form-field-theme.scss b/src/lib/form-field/_form-field-theme.scss index 55dbfcaa971f..1f815b4fb2cd 100644 --- a/src/lib/form-field/_form-field-theme.scss +++ b/src/lib/form-field/_form-field-theme.scss @@ -90,6 +90,12 @@ } } +// Used to make instances of the _mat-form-field-placeholder-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; + // Applies a floating placeholder above the form field control itself. @mixin _mat-form-field-placeholder-floating($font-scale, $infix-padding, $infix-margin-top) { // We use perspective to fix the text blurriness as described here: @@ -97,33 +103,14 @@ // 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); + 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) scale($font-scale); + -ms-transform: translateY(-$infix-margin-top - $infix-padding + $dedupe) scale($font-scale); - width: 100% / $font-scale; -} + width: 100% / $font-scale + $dedupe; -// This is a total duplicate of the mixin above with insignificant values added to the rules. -// This exists because the mixin is used in two places. When Google's CSS Optimizer runs over this -// css (after compiling from sass), it combines those two declarations into one. However, one of -// 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) { - transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) - translateZ(0.001px) scale(1); - -ms-transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) scale(1); - width: (100% / $font-scale) + 0.0001; -} - -@mixin _mat-form-field-placeholder-float-nodedupe2($font-scale, $infix-padding, $infix-margin-top) { - transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) perspective(100px) - translateZ(0.001px) scale(1) scale(1); - -ms-transform: translateY(-$infix-margin-top - $infix-padding) scale($font-scale) scale(1) - scale(1); - width: (100% / $font-scale) + 0.0002; + $dedupe: $dedupe + 0.00001 !global; } @mixin mat-form-field-typography($config) { @@ -196,20 +183,14 @@ } .mat-form-field-can-float { - &.mat-form-field-should-float .mat-form-field-placeholder { + &.mat-form-field-should-float .mat-form-field-placeholder, + .mat-input-server:focus + .mat-form-field-placeholder-wrapper .mat-form-field-placeholder { @include _mat-form-field-placeholder-floating( - $subscript-font-scale, $infix-padding, $infix-margin-top); + $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( - $subscript-font-scale, $infix-padding, $infix-margin-top); - } - - // Server-side rendered matInput with focus (used as a pure CSS stand-in for - // mat-form-field-should-float). - .mat-input-server:focus + .mat-form-field-placeholder-wrapper .mat-form-field-placeholder { @include _mat-form-field-placeholder-floating( $subscript-font-scale, $infix-padding, $infix-margin-top); } @@ -218,7 +199,7 @@ // (used as a pure CSS stand-in for mat-form-field-should-float). .mat-input-server[placeholder]:not(:placeholder-shown) + .mat-form-field-placeholder-wrapper .mat-form-field-placeholder { - @include _mat-form-field-placeholder-float-nodedupe2( + @include _mat-form-field-placeholder-floating( $subscript-font-scale, $infix-padding, $infix-margin-top); } } diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index c19b94286aef..51c0a842b1e2 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -16,7 +16,7 @@ import { Input, OnChanges, OnDestroy, - Optional, PLATFORM_ID, + Optional, Renderer2, Self } from '@angular/core'; @@ -26,7 +26,6 @@ import {MatFormFieldControl} from '@angular/material/form-field'; import {Subject} from 'rxjs/Subject'; import {getMatInputUnsupportedTypeError} from './input-errors'; import {MAT_INPUT_VALUE_ACCESSOR} from './input-value-accessor'; -import {isPlatformServer} from '@angular/common'; // Invalid input type. Using one of these will throw an MatInputUnsupportedTypeError. @@ -168,8 +167,7 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, @Optional() protected _parentForm: NgForm, @Optional() protected _parentFormGroup: FormGroupDirective, private _defaultErrorStateMatcher: ErrorStateMatcher, - @Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any, - @Optional() @Inject(PLATFORM_ID) platformId?: Object) { + @Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any) { // If no input value accessor was explicitly specified, use the element as the input value // accessor. this._inputValueAccessor = inputValueAccessor || this._elementRef.nativeElement; @@ -195,7 +193,7 @@ export class MatInput implements MatFormFieldControl, OnChanges, OnDestroy, }); } - this._isServer = !!(platformId && isPlatformServer(platformId)); + this._isServer = !this._platform.isBrowser; } ngOnChanges() {