diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e75ed831e06d..f7e92f9db827 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,10 +12,10 @@ /src/material/dialog/** @andrewseguin @crisbeto /src/material/divider/** @andrewseguin @crisbeto /src/material/expansion/** @andrewseguin -/src/material/form-field/** @mmalerba +/src/material/legacy-form-field/** @mmalerba /src/material/grid-list/** @andrewseguin /src/material/icon/** @andrewseguin -/src/material/input/** @mmalerba +/src/material/legacy-input/** @mmalerba /src/material/list/** @andrewseguin @crisbeto @devversion /src/material/menu/** @crisbeto /src/material/paginator/** @andrewseguin @@ -39,6 +39,7 @@ /src/material/legacy-tooltip/** @andrewseguin /src/material/tooltip/** @andrewseguin /src/material/tree/** @jelbourn @andrewseguin +/src/material/input/** @devversion @mmalerba # Angular Material core /src/material/core/* @andrewseguin @@ -114,7 +115,6 @@ /src/material-experimental/mdc-core/** @crisbeto /src/material-experimental/mdc-dialog/** @devversion /src/material-experimental/mdc-form-field/** @devversion @mmalerba -/src/material-experimental/mdc-input/** @devversion @mmalerba /src/material-experimental/mdc-list/** @mmalerba @devversion /src/material-experimental/mdc-menu/** @crisbeto /src/material-experimental/mdc-select/** @crisbeto @@ -329,7 +329,7 @@ /tools/public_api_guard/material/form-field** @mmalerba /tools/public_api_guard/material/grid-list** @andrewseguin /tools/public_api_guard/material/icon** @andrewseguin -/tools/public_api_guard/material/input** @mmalerba +/tools/public_api_guard/material/legacy-input** @mmalerba /tools/public_api_guard/material/list** @andrewseguin @crisbeto @devversion /tools/public_api_guard/material/menu** @crisbeto /tools/public_api_guard/material/paginator** @andrewseguin diff --git a/.ng-dev/commit-message.mts b/.ng-dev/commit-message.mts index 36af3c60fb67..404c5f3dd454 100644 --- a/.ng-dev/commit-message.mts +++ b/.ng-dev/commit-message.mts @@ -47,8 +47,8 @@ export const commitMessage: CommitMessageConfig = { 'material-experimental/mdc-chips', 'material-experimental/mdc-core', 'material-experimental/mdc-dialog', - 'material-experimental/mdc-form-field', - 'material-experimental/mdc-input', + 'material/form-field', + 'material/input', 'material-experimental/mdc-list', 'material-experimental/mdc-menu', 'material-experimental/mdc-paginator', @@ -83,9 +83,10 @@ export const commitMessage: CommitMessageConfig = { 'material/divider', 'material/expansion', 'material/form-field', + 'material/legacy-form-field', 'material/grid-list', 'material/icon', - 'material/input', + 'material/legacy-input', 'material/list', 'material/menu', 'material/paginator', diff --git a/integration/size-test/material-experimental/mdc-form-field/BUILD.bazel b/integration/size-test/material-experimental/mdc-form-field/BUILD.bazel index 960d335c5524..4030cd1032b9 100644 --- a/integration/size-test/material-experimental/mdc-form-field/BUILD.bazel +++ b/integration/size-test/material-experimental/mdc-form-field/BUILD.bazel @@ -4,8 +4,8 @@ size_test( name = "basic", file = "basic.ts", deps = [ - "//src/material-experimental/mdc-form-field", - "//src/material-experimental/mdc-input", + "//src/material/form-field", + "//src/material/input", ], ) @@ -13,7 +13,7 @@ size_test( name = "advanced", file = "advanced.ts", deps = [ - "//src/material-experimental/mdc-form-field", - "//src/material-experimental/mdc-input", + "//src/material/form-field", + "//src/material/input", ], ) diff --git a/integration/size-test/material-experimental/mdc-form-field/advanced.ts b/integration/size-test/material-experimental/mdc-form-field/advanced.ts index 0c6b2180f49b..5efcce71d668 100644 --- a/integration/size-test/material-experimental/mdc-form-field/advanced.ts +++ b/integration/size-test/material-experimental/mdc-form-field/advanced.ts @@ -1,6 +1,6 @@ import {Component, NgModule} from '@angular/core'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; -import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; /** * Advanced component using `MatFormField` and `MatInput` in combination with content diff --git a/integration/size-test/material-experimental/mdc-form-field/basic.ts b/integration/size-test/material-experimental/mdc-form-field/basic.ts index 24f0b8cad171..2e5b64f7f8e9 100644 --- a/integration/size-test/material-experimental/mdc-form-field/basic.ts +++ b/integration/size-test/material-experimental/mdc-form-field/basic.ts @@ -1,6 +1,6 @@ import {Component, NgModule} from '@angular/core'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; -import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; /** * Basic component using `MatFormField` and `MatInput`. Other parts of the form-field diff --git a/integration/size-test/material/form-field/BUILD.bazel b/integration/size-test/material/form-field/BUILD.bazel index 4030cd1032b9..276d78be6260 100644 --- a/integration/size-test/material/form-field/BUILD.bazel +++ b/integration/size-test/material/form-field/BUILD.bazel @@ -4,8 +4,8 @@ size_test( name = "basic", file = "basic.ts", deps = [ - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", ], ) @@ -13,7 +13,7 @@ size_test( name = "advanced", file = "advanced.ts", deps = [ - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", ], ) diff --git a/integration/size-test/material/form-field/advanced.ts b/integration/size-test/material/form-field/advanced.ts index 5efcce71d668..5cef5937137e 100644 --- a/integration/size-test/material/form-field/advanced.ts +++ b/integration/size-test/material/form-field/advanced.ts @@ -1,6 +1,6 @@ import {Component, NgModule} from '@angular/core'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; +import {MatLegacyInputModule} from '@angular/material/legacy-input'; /** * Advanced component using `MatFormField` and `MatInput` in combination with content @@ -20,7 +20,7 @@ import {MatInputModule} from '@angular/material/input'; export class TestComponent {} @NgModule({ - imports: [MatInputModule, MatFormFieldModule], + imports: [MatLegacyInputModule, MatLegacyFormFieldModule], declarations: [TestComponent], bootstrap: [TestComponent], }) diff --git a/integration/size-test/material/form-field/basic.ts b/integration/size-test/material/form-field/basic.ts index 2e5b64f7f8e9..5a15d1279511 100644 --- a/integration/size-test/material/form-field/basic.ts +++ b/integration/size-test/material/form-field/basic.ts @@ -1,6 +1,6 @@ import {Component, NgModule} from '@angular/core'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; +import {MatLegacyInputModule} from '@angular/material/legacy-input'; /** * Basic component using `MatFormField` and `MatInput`. Other parts of the form-field @@ -17,7 +17,7 @@ import {MatInputModule} from '@angular/material/input'; export class TestComponent {} @NgModule({ - imports: [MatInputModule, MatFormFieldModule], + imports: [MatLegacyInputModule, MatLegacyFormFieldModule], declarations: [TestComponent], bootstrap: [TestComponent], }) diff --git a/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel b/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel index 24bcbbf2d24a..e51734034bda 100644 --- a/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel +++ b/src/components-examples/material-experimental/mdc-form-field/BUILD.bazel @@ -12,8 +12,8 @@ ng_module( deps = [ "//src/cdk/a11y", "//src/cdk/coercion", - "//src/material-experimental/mdc-form-field", - "//src/material-experimental/mdc-input", + "//src/material/form-field", + "//src/material/input", "//src/material/icon", "@npm//@angular/common", "@npm//@angular/forms", diff --git a/src/components-examples/material-experimental/mdc-form-field/index.ts b/src/components-examples/material-experimental/mdc-form-field/index.ts index 8d9283dc91ea..0a1eeddf2220 100644 --- a/src/components-examples/material-experimental/mdc-form-field/index.ts +++ b/src/components-examples/material-experimental/mdc-form-field/index.ts @@ -1,7 +1,7 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {ReactiveFormsModule} from '@angular/forms'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; import { MdcFormFieldCustomControlExample, diff --git a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts index 2067e9424108..773b1aab9093 100644 --- a/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts +++ b/src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts @@ -2,8 +2,7 @@ import {FocusMonitor} from '@angular/cdk/a11y'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {Component, ElementRef, Inject, Input, OnDestroy, Optional, Self} from '@angular/core'; import {ControlValueAccessor, FormBuilder, NgControl, Validators} from '@angular/forms'; -import {MatFormField, MatFormFieldControl} from '@angular/material-experimental/mdc-form-field'; -import {MAT_FORM_FIELD} from '@angular/material/form-field'; +import {MatFormField, MatFormFieldControl, MAT_FORM_FIELD} from '@angular/material/form-field'; import {Subject} from 'rxjs'; /** @title Form field with custom telephone number input control. */ diff --git a/src/material-experimental/_index.scss b/src/material-experimental/_index.scss index 8e9c85f32250..8d84ce425aea 100644 --- a/src/material-experimental/_index.scss +++ b/src/material-experimental/_index.scss @@ -35,10 +35,6 @@ mdc-chips-density, mdc-chips-theme; @forward './mdc-dialog/dialog-theme' as mdc-dialog-* show mdc-dialog-color, mdc-dialog-typography, mdc-dialog-density, mdc-dialog-theme; -@forward './mdc-form-field/form-field-theme' as mdc-form-field-* show mdc-form-field-color, - mdc-form-field-typography, mdc-form-field-density, mdc-form-field-theme; -@forward './mdc-input/input-theme' as mdc-input-* show mdc-input-color, mdc-input-typography, - mdc-input-density, mdc-input-theme; @forward './mdc-list/list-theme' as mdc-list-* show mdc-list-color, mdc-list-typography, mdc-list-density, mdc-list-theme; @forward './mdc-menu/menu-theme' as mdc-menu-* show mdc-menu-color, mdc-menu-typography, diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index 77cb94860957..b70549541c5e 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -12,10 +12,6 @@ entryPoints = [ "mdc-core/testing", "mdc-dialog", "mdc-dialog/testing", - "mdc-form-field", - "mdc-form-field/testing", - "mdc-input", - "mdc-input/testing", "mdc-list", "mdc-list/testing", "mdc-menu", diff --git a/src/material-experimental/mdc-autocomplete/BUILD.bazel b/src/material-experimental/mdc-autocomplete/BUILD.bazel index 6ed7e72315c5..17e76d77d528 100644 --- a/src/material-experimental/mdc-autocomplete/BUILD.bazel +++ b/src/material-experimental/mdc-autocomplete/BUILD.bazel @@ -59,8 +59,8 @@ ng_test_library( "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material-experimental/mdc-core", - "//src/material-experimental/mdc-form-field", - "//src/material-experimental/mdc-input", + "//src/material/form-field", + "//src/material/input", "@npm//@angular/forms", "@npm//@angular/platform-browser", "@npm//rxjs", diff --git a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts index 8c405c20eb64..b63429430a37 100644 --- a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts +++ b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts @@ -37,8 +37,8 @@ import { } from '@angular/core/testing'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatOption, MatOptionSelectionChange} from '@angular/material-experimental/mdc-core'; -import {MatFormField, MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; -import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatFormField, MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {EMPTY, Observable, Subject, Subscription} from 'rxjs'; diff --git a/src/material-experimental/mdc-chips/BUILD.bazel b/src/material-experimental/mdc-chips/BUILD.bazel index 82e2a96aba93..c5669c61fd82 100644 --- a/src/material-experimental/mdc-chips/BUILD.bazel +++ b/src/material-experimental/mdc-chips/BUILD.bazel @@ -24,7 +24,7 @@ ng_module( deps = [ "//src:dev_mode_types", "//src/material-experimental/mdc-core", - "//src/material-experimental/mdc-form-field", + "//src/material/form-field", "@npm//@angular/animations", "@npm//@angular/common", "@npm//@angular/core", @@ -78,8 +78,8 @@ ng_test_library( "//src/cdk/testing", "//src/cdk/testing/private", "//src/material-experimental/mdc-core", - "//src/material-experimental/mdc-form-field", - "//src/material-experimental/mdc-input", + "//src/material/form-field", + "//src/material/input", "@npm//@angular/animations", "@npm//@angular/common", "@npm//@angular/forms", diff --git a/src/material-experimental/mdc-chips/chip-grid.spec.ts b/src/material-experimental/mdc-chips/chip-grid.spec.ts index f0b34e39c298..20f78a7c3901 100644 --- a/src/material-experimental/mdc-chips/chip-grid.spec.ts +++ b/src/material-experimental/mdc-chips/chip-grid.spec.ts @@ -31,8 +31,8 @@ import { } from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import {FormControl, FormsModule, NgForm, ReactiveFormsModule, Validators} from '@angular/forms'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; -import {MatInputModule} from '@angular/material-experimental/mdc-input'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; import {By} from '@angular/platform-browser'; import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MatChipEvent, MatChipGrid, MatChipInputEvent, MatChipRow, MatChipsModule} from './index'; diff --git a/src/material-experimental/mdc-chips/chip-grid.ts b/src/material-experimental/mdc-chips/chip-grid.ts index cdf0c486b859..ea0c1259937b 100644 --- a/src/material-experimental/mdc-chips/chip-grid.ts +++ b/src/material-experimental/mdc-chips/chip-grid.ts @@ -38,7 +38,7 @@ import { ErrorStateMatcher, mixinErrorState, } from '@angular/material-experimental/mdc-core'; -import {MatFormFieldControl} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldControl} from '@angular/material/form-field'; import {MatChipTextControl} from './chip-text-control'; import {Observable, Subject, merge} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; diff --git a/src/material-experimental/mdc-chips/chip-input.spec.ts b/src/material-experimental/mdc-chips/chip-input.spec.ts index 46c6b52d9505..58f1f91e6aec 100644 --- a/src/material-experimental/mdc-chips/chip-input.spec.ts +++ b/src/material-experimental/mdc-chips/chip-input.spec.ts @@ -4,7 +4,7 @@ import {PlatformModule} from '@angular/cdk/platform'; import {dispatchKeyboardEvent} from '../../cdk/testing/private'; import {Component, DebugElement, ViewChild} from '@angular/core'; import {waitForAsync, ComponentFixture, fakeAsync, TestBed, flush} from '@angular/core/testing'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldModule} from '@angular/material/form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {Subject} from 'rxjs'; diff --git a/src/material-experimental/mdc-chips/chip-input.ts b/src/material-experimental/mdc-chips/chip-input.ts index 5894cbdaea64..776f7887d35f 100644 --- a/src/material-experimental/mdc-chips/chip-input.ts +++ b/src/material-experimental/mdc-chips/chip-input.ts @@ -20,7 +20,7 @@ import { Optional, Output, } from '@angular/core'; -import {MatFormField, MAT_FORM_FIELD} from '@angular/material-experimental/mdc-form-field'; +import {MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field'; import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './tokens'; import {MatChipGrid} from './chip-grid'; import {MatChipTextControl} from './chip-text-control'; diff --git a/src/material-experimental/mdc-core/color/_all-color.import.scss b/src/material-experimental/mdc-core/color/_all-color.import.scss index 655dbce5ed37..6227424ff2d6 100644 --- a/src/material-experimental/mdc-core/color/_all-color.import.scss +++ b/src/material-experimental/mdc-core/color/_all-color.import.scss @@ -50,23 +50,6 @@ $mat-mdc-table-mdc-data-table-selected-row-fill-color, $mat-mdc-table-mdc-data-table-sort-icon-active-color, $mat-mdc-table-mdc-data-table-sort-icon-color, $mat-mdc-table-mdc-data-table-stroke-color, $mat-mdc-table-mdc-data-table-table-divider-color; @forward '../../mdc-paginator/paginator-variables' as mat-mdc-paginator-*; -@forward '../../mdc-form-field/form-field-sizing'; -@forward '../../mdc-form-field/form-field-native-select' hide private-form-field-native-select, -private-form-field-native-select-color; -@forward '../../mdc-form-field/form-field-native-select' as mat-mdc-* hide -$mat-mdc-mat-form-field-select-arrow-height, $mat-mdc-mat-form-field-select-arrow-width, -$mat-mdc-mat-form-field-select-horizontal-end-padding; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' hide -private-text-field-refresh-theme-variables; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' as mat-mdc-* hide -$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, -$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, -$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, -$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, -$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, -$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, -$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, -$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; @forward '../core-theme.import'; @forward '../../mdc-autocomplete/autocomplete-theme' as mat-mdc-autocomplete-*; @forward '../../mdc-dialog/dialog-theme' as mat-mdc-dialog-*; @@ -76,11 +59,6 @@ $mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholde @forward '../../mdc-menu/menu-theme' as mat-mdc-menu-*; @forward '../../mdc-paginator/paginator-theme' as mat-mdc-paginator-*; @forward '../../mdc-progress-spinner/progress-spinner-theme' as mat-mdc-progress-spinner-*; -@forward '../../mdc-input/input-theme' as mat-mdc-input-*; -@forward '../../mdc-form-field/form-field-density' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-subscript' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-focus-overlay' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-theme' as mat-mdc-form-field-*; @forward '../theming/all-theme'; @forward 'all-color' hide all-mdc-component-colors; diff --git a/src/material-experimental/mdc-core/density/_all-density.import.scss b/src/material-experimental/mdc-core/density/_all-density.import.scss index def9fe9846fa..543d38c3ee77 100644 --- a/src/material-experimental/mdc-core/density/_all-density.import.scss +++ b/src/material-experimental/mdc-core/density/_all-density.import.scss @@ -50,23 +50,6 @@ $mat-mdc-table-mdc-data-table-selected-row-fill-color, $mat-mdc-table-mdc-data-table-sort-icon-active-color, $mat-mdc-table-mdc-data-table-sort-icon-color, $mat-mdc-table-mdc-data-table-stroke-color, $mat-mdc-table-mdc-data-table-table-divider-color; @forward '../../mdc-paginator/paginator-variables' as mat-mdc-paginator-*; -@forward '../../mdc-form-field/form-field-sizing'; -@forward '../../mdc-form-field/form-field-native-select' hide private-form-field-native-select, -private-form-field-native-select-color; -@forward '../../mdc-form-field/form-field-native-select' as mat-mdc-* hide -$mat-mdc-mat-form-field-select-arrow-height, $mat-mdc-mat-form-field-select-arrow-width, -$mat-mdc-mat-form-field-select-horizontal-end-padding; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' hide -private-text-field-refresh-theme-variables; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' as mat-mdc-* hide -$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, -$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, -$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, -$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, -$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, -$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, -$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, -$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; @forward '../core-theme.import'; @forward '../../mdc-autocomplete/autocomplete-theme' as mat-mdc-autocomplete-*; @forward '../../mdc-dialog/dialog-theme' as mat-mdc-dialog-*; @@ -76,11 +59,6 @@ $mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholde @forward '../../mdc-menu/menu-theme' as mat-mdc-menu-*; @forward '../../mdc-paginator/paginator-theme' as mat-mdc-paginator-*; @forward '../../mdc-progress-spinner/progress-spinner-theme' as mat-mdc-progress-spinner-*; -@forward '../../mdc-input/input-theme' as mat-mdc-input-*; -@forward '../../mdc-form-field/form-field-density' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-subscript' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-focus-overlay' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-theme' as mat-mdc-form-field-*; @forward '../theming/all-theme'; @forward 'all-density' show all-mdc-component-densities; diff --git a/src/material-experimental/mdc-core/theming/BUILD.bazel b/src/material-experimental/mdc-core/theming/BUILD.bazel index ec134b889eb2..7373e3c95e4d 100644 --- a/src/material-experimental/mdc-core/theming/BUILD.bazel +++ b/src/material-experimental/mdc-core/theming/BUILD.bazel @@ -26,8 +26,8 @@ sass_library( "//src/material-experimental/mdc-chips:mdc_chips_scss_lib", "//src/material-experimental/mdc-core:mdc_core_scss_lib", "//src/material-experimental/mdc-dialog:mdc_dialog_scss_lib", - "//src/material-experimental/mdc-form-field:mdc_form_field_scss_lib", - "//src/material-experimental/mdc-input:mdc_input_scss_lib", + "//src/material/form-field:form_field_scss_lib", + "//src/material/input:input_scss_lib", "//src/material-experimental/mdc-list:mdc_list_scss_lib", "//src/material-experimental/mdc-menu:mdc_menu_scss_lib", "//src/material-experimental/mdc-paginator:mdc_paginator_scss_lib", diff --git a/src/material-experimental/mdc-core/theming/_all-theme.import.scss b/src/material-experimental/mdc-core/theming/_all-theme.import.scss index 107806bd8b9d..032dd5ba52a7 100644 --- a/src/material-experimental/mdc-core/theming/_all-theme.import.scss +++ b/src/material-experimental/mdc-core/theming/_all-theme.import.scss @@ -50,23 +50,6 @@ $mat-mdc-table-mdc-data-table-selected-row-fill-color, $mat-mdc-table-mdc-data-table-sort-icon-active-color, $mat-mdc-table-mdc-data-table-sort-icon-color, $mat-mdc-table-mdc-data-table-stroke-color, $mat-mdc-table-mdc-data-table-table-divider-color; @forward '../../mdc-paginator/paginator-variables' as mat-mdc-paginator-*; -@forward '../../mdc-form-field/form-field-sizing'; -@forward '../../mdc-form-field/form-field-native-select' hide private-form-field-native-select, -private-form-field-native-select-color; -@forward '../../mdc-form-field/form-field-native-select' as mat-mdc-* hide -$mat-mdc-mat-form-field-select-arrow-height, $mat-mdc-mat-form-field-select-arrow-width, -$mat-mdc-mat-form-field-select-horizontal-end-padding; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' hide -private-text-field-refresh-theme-variables; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' as mat-mdc-* hide -$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, -$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, -$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, -$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, -$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, -$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, -$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, -$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; @forward '../core-theme.import'; @forward '../../mdc-autocomplete/autocomplete-theme' as mat-mdc-autocomplete-*; @forward '../../mdc-dialog/dialog-theme' as mat-mdc-dialog-*; @@ -76,11 +59,6 @@ $mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholde @forward '../../mdc-menu/menu-theme' as mat-mdc-menu-*; @forward '../../mdc-paginator/paginator-theme' as mat-mdc-paginator-*; @forward '../../mdc-progress-spinner/progress-spinner-theme' as mat-mdc-progress-spinner-*; -@forward '../../mdc-input/input-theme' as mat-mdc-input-*; -@forward '../../mdc-form-field/form-field-density' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-subscript' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-focus-overlay' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-theme' as mat-mdc-form-field-*; @forward 'all-theme' hide all-mdc-component-themes;; @import '../core-theme'; @@ -99,8 +77,6 @@ $mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholde @import '../../mdc-table/table-theme'; @import '../../mdc-paginator/paginator-theme'; @import '../../mdc-progress-spinner/progress-spinner-theme'; -@import '../../mdc-input/input-theme'; -@import '../../mdc-form-field/form-field-theme'; @import '../../../material/core/core'; @import '../../../material/core/core-theme'; @import '../../../material/core/theming/theming'; diff --git a/src/material-experimental/mdc-core/theming/_all-theme.scss b/src/material-experimental/mdc-core/theming/_all-theme.scss index 7997be9023c6..cabc571723f8 100644 --- a/src/material-experimental/mdc-core/theming/_all-theme.scss +++ b/src/material-experimental/mdc-core/theming/_all-theme.scss @@ -19,8 +19,6 @@ @use '../../mdc-table/table-theme'; @use '../../mdc-paginator/paginator-theme'; @use '../../mdc-progress-spinner/progress-spinner-theme'; -@use '../../mdc-input/input-theme'; -@use '../../mdc-form-field/form-field-theme'; @mixin all-mdc-component-themes($theme-or-color-config) { $dedupe-key: 'angular-material-mdc-theme'; @@ -45,8 +43,8 @@ @include slider-theme.theme($theme-or-color-config); @include snack-bar-theme.theme($theme-or-color-config); @include table-theme.theme($theme-or-color-config); - @include form-field-theme.theme($theme-or-color-config); - @include input-theme.theme($theme-or-color-config); + @include mat.form-field-theme($theme-or-color-config); + @include mat.input-theme($theme-or-color-config); @include tabs-theme.theme($theme-or-color-config); @include mat.tooltip-theme($theme-or-color-config); } diff --git a/src/material-experimental/mdc-core/typography/_all-typography.import.scss b/src/material-experimental/mdc-core/typography/_all-typography.import.scss index 7b093da7780d..231fadc3d7af 100644 --- a/src/material-experimental/mdc-core/typography/_all-typography.import.scss +++ b/src/material-experimental/mdc-core/typography/_all-typography.import.scss @@ -50,23 +50,6 @@ $mat-mdc-table-mdc-data-table-selected-row-fill-color, $mat-mdc-table-mdc-data-table-sort-icon-active-color, $mat-mdc-table-mdc-data-table-sort-icon-color, $mat-mdc-table-mdc-data-table-stroke-color, $mat-mdc-table-mdc-data-table-table-divider-color; @forward '../../mdc-paginator/paginator-variables' as mat-mdc-paginator-*; -@forward '../../mdc-form-field/form-field-sizing'; -@forward '../../mdc-form-field/form-field-native-select' hide private-form-field-native-select, -private-form-field-native-select-color; -@forward '../../mdc-form-field/form-field-native-select' as mat-mdc-* hide -$mat-mdc-mat-form-field-select-arrow-height, $mat-mdc-mat-form-field-select-arrow-width, -$mat-mdc-mat-form-field-select-horizontal-end-padding; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' hide -private-text-field-refresh-theme-variables; -@forward '../../mdc-form-field/mdc-text-field-theme-variable-refresh' as mat-mdc-* hide -$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, -$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, -$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, -$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, -$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, -$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, -$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, -$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; @forward '../core-theme.import'; @forward '../../mdc-autocomplete/autocomplete-theme' as mat-mdc-autocomplete-*; @forward '../../mdc-dialog/dialog-theme' as mat-mdc-dialog-*; @@ -76,11 +59,6 @@ $mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholde @forward '../../mdc-menu/menu-theme' as mat-mdc-menu-*; @forward '../../mdc-paginator/paginator-theme' as mat-mdc-paginator-*; @forward '../../mdc-progress-spinner/progress-spinner-theme' as mat-mdc-progress-spinner-*; -@forward '../../mdc-input/input-theme' as mat-mdc-input-*; -@forward '../../mdc-form-field/form-field-density' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-subscript' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-focus-overlay' as mat-mdc-*; -@forward '../../mdc-form-field/form-field-theme' as mat-mdc-form-field-*; @forward '../theming/all-theme'; @forward 'all-typography' hide define-mdc-typography-config, all-mdc-component-typographies; @forward 'all-typography' as mat-mdc-typography-* hide diff --git a/src/material-experimental/mdc-form-field/BUILD.bazel b/src/material-experimental/mdc-form-field/BUILD.bazel deleted file mode 100644 index 014cca7eb5a3..000000000000 --- a/src/material-experimental/mdc-form-field/BUILD.bazel +++ /dev/null @@ -1,71 +0,0 @@ -load( - "//tools:defaults.bzl", - "ng_module", - "sass_binary", - "sass_library", -) - -package(default_visibility = ["//visibility:public"]) - -ng_module( - name = "mdc-form-field", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - assets = [":form_field_scss"] + glob(["**/*.html"]), - deps = [ - "//src:dev_mode_types", - "//src/cdk/bidi", - "//src/cdk/observers", - "//src/cdk/platform", - "//src/material-experimental/mdc-core", - "//src/material/form-field", - "@npm//@angular/forms", - "@npm//rxjs", - ], -) - -sass_library( - name = "mdc_form_field_scss_lib", - srcs = [ - "_form-field-theme.scss", - "_mdc-text-field-theme-variable-refresh.scss", - ], - deps = [ - ":form_field_partials", - "//:mdc_sass_lib", - "//src/material:sass_lib", - "//src/material/core:core_scss_lib", - ], -) - -sass_binary( - name = "form_field_scss", - src = "form-field.scss", - deps = [ - ":form_field_partials", - "//:mdc_sass_lib", - "//src/material:sass_lib", - "//src/material/core:core_scss_lib", - ], -) - -sass_library( - name = "form_field_partials", - srcs = [ - "_form-field-density.scss", - "_form-field-focus-overlay.scss", - "_form-field-high-contrast.scss", - "_form-field-native-select.scss", - "_form-field-sizing.scss", - "_form-field-subscript.scss", - "_mdc-text-field-structure-overrides.scss", - "_mdc-text-field-textarea-overrides.scss", - ], - deps = [ - "//:mdc_sass_lib", - "//src/cdk:sass_lib", - "//src/material:sass_lib", - ], -) diff --git a/src/material-experimental/mdc-form-field/README.md b/src/material-experimental/mdc-form-field/README.md deleted file mode 100644 index 2ead52c61fb2..000000000000 --- a/src/material-experimental/mdc-form-field/README.md +++ /dev/null @@ -1,83 +0,0 @@ -This is a prototype of an alternate version of `` built on top of -[MDC Web](https://github.com/material-components/material-components-web). This component is -experimental and should not be used in production. - -## How to use -Assuming your application is already up and running using Angular Material, you can add this -component by following these steps: - -1. Install `@angular/material-experimental` and MDC Web: - - ```bash - npm i material-components-web @angular/material-experimental - ``` - -2. In your `angular.json`, make sure `node_modules/` is listed as a Sass include path. This is - needed for the Sass compiler to be able to find the MDC Web Sass files. - - ```json - ... - "styles": [ - "src/styles.scss" - ], - "stylePreprocessorOptions": { - "includePaths": [ - "node_modules/" - ] - }, - ... - ``` - -3. Import the experimental `MatFormFieldModule` and add it to the module that declares your - component: - - ```ts - import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; - - @NgModule({ - declarations: [MyComponent], - imports: [MatFormFieldModule], - }) - export class MyModule {} - ``` - -4. Use `` in your component's template, just like you would use the normal - form-field. - -5. Ensure color and typography styles for `@angular/material-experimental` are set up. Either - use a custom theme and use the `mat-mdc-form-field-theme` mixin, or use a prebuilt theme - from `@angular/material-experimental/mdc-core/theming/prebuilt`. - -## API differences - -In the Material Design specification, text fields with the `standard` and `legacy` appearance -can no longer be found. These appearances will be removed for the standard -`@angular/material/form-field` in the future. - -The experimental MDC-based form-field no longer has support for these appearances. The form-field -uses the `fill` appearance by default, but also supports the `outline` appearance. - -Due to the removal of the `legacy` appearance, the form-field no longer [promotes placeholders -to labels](https://material.angular.io/components/form-field/overview#form-field-appearance-variants). -This means that form-fields which use the default `legacy` need to be migrated to use the -`` element if they only had a `placeholder`. - - -```html - - - -``` - -needs to be changed to: - -```html - - Full name - - -``` - -Other than the removal of the `legacy` and `standard` appearances, the API of form-field -matches the one from `@angular/material/from-field`. Simply replace imports to -`@angular/material/form-field` with imports to `@angular/material-experimental/mdc-form-field`. diff --git a/src/material-experimental/mdc-form-field/_form-field-theme.import.scss b/src/material-experimental/mdc-form-field/_form-field-theme.import.scss deleted file mode 100644 index bf828a439cf5..000000000000 --- a/src/material-experimental/mdc-form-field/_form-field-theme.import.scss +++ /dev/null @@ -1,29 +0,0 @@ -@forward '../../material/core/theming/theming.import'; -@forward 'form-field-sizing'; -@forward 'form-field-native-select' hide private-form-field-native-select, -private-form-field-native-select-color; -@forward 'form-field-native-select' as mat-mdc-* hide $mat-mdc-mat-form-field-select-arrow-height, -$mat-mdc-mat-form-field-select-arrow-width, $mat-mdc-mat-form-field-select-horizontal-end-padding; -@forward 'mdc-text-field-theme-variable-refresh' hide private-text-field-refresh-theme-variables; -@forward 'mdc-text-field-theme-variable-refresh' as mat-mdc-* hide -$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, -$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, -$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, -$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, -$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, -$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, -$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, -$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; -@forward '../../material/core/style/layout-common.import'; -@forward 'form-field-density' as mat-mdc-*; -@forward 'form-field-subscript' as mat-mdc-*; -@forward 'form-field-focus-overlay' as mat-mdc-*; -@forward '../../cdk/a11y/index.import'; -@forward 'form-field-theme' hide color, density, theme, typography; -@forward 'form-field-theme' as mat-mdc-form-field-* hide mat-mdc-form-field-text-field-color-styles; - -@import 'form-field-density'; -@import 'form-field-subscript'; -@import 'form-field-focus-overlay'; -@import 'form-field-native-select'; -@import 'mdc-text-field-theme-variable-refresh'; diff --git a/src/material-experimental/mdc-form-field/_form-field-theme.scss b/src/material-experimental/mdc-form-field/_form-field-theme.scss deleted file mode 100644 index 0df0eb47a04c..000000000000 --- a/src/material-experimental/mdc-form-field/_form-field-theme.scss +++ /dev/null @@ -1,147 +0,0 @@ -@use '@angular/material' as mat; -@use '@material/textfield' as mdc-textfield; -@use '@material/floating-label' as mdc-floating-label; -@use '@material/notched-outline' as mdc-notched-outline; -@use '@material/line-ripple' as mdc-line-ripple; -@use '@material/theme/theme-color' as mdc-theme-color; -@use '@material/typography/typography' as mdc-typography; - -@use './form-field-density'; -@use './form-field-subscript'; -@use './form-field-focus-overlay'; -@use './form-field-native-select'; -@use './mdc-text-field-theme-variable-refresh'; - - -// Mixin that overwrites the default MDC text-field color styles to be based on -// the given theme palette. The MDC text-field is styled using `primary` by default. -@mixin _color-styles($palette-name, - $query: mat.$private-mdc-theme-styles-query) { - $orig-focused-label-color: mdc-textfield.$focused-label-color; - mdc-textfield.$focused-label-color: rgba(mdc-theme-color.prop-value($palette-name), 0.87); - - @include mdc-textfield.caret-color($palette-name, $query); - @include mdc-textfield.line-ripple-color($palette-name, $query); - - .mdc-text-field--focused { - @include mdc-textfield.focused_($query); - } - - .mdc-text-field--invalid { - @include mdc-textfield.invalid_($query); - } - - .mdc-text-field--outlined { - @include mdc-textfield.focused-outline-color($palette-name, $query); - } - - mdc-textfield.$focused-label-color: $orig-focused-label-color; -} - -@mixin color($config-or-theme) { - $config: mat.get-color-config($config-or-theme); - @include mat.private-using-mdc-theme($config) { - @include mdc-text-field-theme-variable-refresh.private-text-field-refresh-theme-variables() { - @include mdc-textfield.without-ripple($query: mat.$private-mdc-theme-styles-query); - @include mdc-floating-label.core-styles($query: mat.$private-mdc-theme-styles-query); - @include mdc-notched-outline.core-styles($query: mat.$private-mdc-theme-styles-query); - @include mdc-line-ripple.core-styles($query: mat.$private-mdc-theme-styles-query); - @include form-field-subscript.private-form-field-subscript-color(); - @include form-field-focus-overlay.private-form-field-focus-overlay-color(); - @include form-field-native-select.private-form-field-native-select-color($config); - - .mat-mdc-form-field.mat-accent { - @include _color-styles(secondary); - } - - .mat-mdc-form-field.mat-warn { - @include _color-styles(error); - } - - // This fixes an issue where the notch appears to be thicker than the rest of the outline when - // zoomed in on Chrome. The border inconsistency seems to be some kind of rendering artifact - // that arises from a combination of the fact that the notch contains text, while the leading - // and trailing outline do not, combined with the fact that the border is semi-transparent. - // Experimentally, I discovered that adding a transparent left border fixes the inconsistency. - // Note: class name is repeated to achieve sufficient specificity over the various MDC states. - // (hover, focus, etc.) - // TODO(mmalerba): port this fix into MDC - .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field { - &.mat-mdc-form-field .mdc-notched-outline__notch { - border-left: 1px solid transparent; - } - } - - [dir='rtl'] { - .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field { - &.mat-mdc-form-field .mdc-notched-outline__notch { - border-left: none; - border-right: 1px solid transparent; - } - } - } - } - } -} - -@mixin typography($config-or-theme) { - $config: mat.private-typography-to-2018-config( - mat.get-typography-config($config-or-theme)); - @include mat.private-using-mdc-typography($config) { - @include mdc-textfield.without-ripple($query: mat.$private-mdc-typography-styles-query); - @include mdc-floating-label.core-styles($query: mat.$private-mdc-typography-styles-query); - @include mdc-notched-outline.core-styles($query: mat.$private-mdc-typography-styles-query); - @include mdc-line-ripple.core-styles($query: mat.$private-mdc-typography-styles-query); - @include form-field-subscript.private-form-field-subscript-typography($config); - - // MDC uses `subtitle1` for the input value, placeholder and floating label. The spec - // shows `body1` for text fields though, so we manually override the typography. - // Note: Form controls inherit the typography from the parent form field. - .mat-mdc-form-field, - .mat-mdc-floating-label { - @include mdc-typography.typography(body1, $query: mat.$private-mdc-typography-styles-query); - } - - // Above, we updated the floating label to use the `body1` typography level. The MDC notched - // outline overrides this accidentally (only when the label floats) to a `rem`-based value. - // This results in different label widths when floated/docked and ultimately breaks the notch - // width as it has been measured in the docked state (where `body1` is applied). We try to - // unset these styles set by the `mdc-notched-outline`: - // https://github.com/material-components/material-components-web/blob/master/packages/mdc-notched-outline/_mixins.scss#L272-L292. - .mat-mdc-form-field .mdc-text-field--outlined { - // For the non-upgraded notch label (i.e. when rendered on the server), also - // use the correct `body1` typography level. - .mdc-floating-label--float-above { - font-size: calc(#{ - mdc-typography.get-size(body1)} * var(--mat-mdc-form-field-floating-label-scale, 0.75)); - } - .mdc-notched-outline--upgraded .mdc-floating-label--float-above { - font-size: mdc-typography.get-size(body1); - } - } - } -} - -@mixin density($config-or-theme) { - $density-scale: mat.get-density-config($config-or-theme); - @include form-field-density.private-form-field-density($density-scale); -} - -@mixin theme($theme-or-color-config) { - $theme: mat.private-legacy-get-theme($theme-or-color-config); - @include mat.private-check-duplicate-theme-styles($theme, 'mat-mdc-form-field') { - $color: mat.get-color-config($theme); - $density: mat.get-density-config($theme); - $typography: mat.get-typography-config($theme); - - @if $color != null { - @include color($color); - } - @if $density != null { - @include density($density); - } - @if $typography != null { - @include typography($typography); - } - } -} diff --git a/src/material-experimental/mdc-form-field/form-field.html b/src/material-experimental/mdc-form-field/form-field.html deleted file mode 100644 index 12a3f43e6585..000000000000 --- a/src/material-experimental/mdc-form-field/form-field.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -
-
-
-
- - - -
- -
- -
-
- -
- -
- - - - - -
- -
- -
-
- -
-
- -
-
- -
-
- -
- -
- {{hintLabel}} - -
- -
-
diff --git a/src/material-experimental/mdc-form-field/form-field.scss b/src/material-experimental/mdc-form-field/form-field.scss deleted file mode 100644 index 1c20b30f1a7d..000000000000 --- a/src/material-experimental/mdc-form-field/form-field.scss +++ /dev/null @@ -1,149 +0,0 @@ -@use '@angular/material' as mat; -@use '@material/textfield' as mdc-textfield; -@use '@material/floating-label' as mdc-floating-label; -@use '@material/notched-outline' as mdc-notched-outline; -@use '@material/line-ripple' as mdc-line-ripple; -@use './form-field-sizing'; -@use './form-field-subscript'; -@use './form-field-focus-overlay'; -@use './form-field-high-contrast'; -@use './form-field-native-select'; -@use './mdc-text-field-textarea-overrides'; -@use './mdc-text-field-structure-overrides'; - -// Base styles for MDC text-field, notched-outline, floating label and line-ripple. -@include mat.private-disable-mdc-fallback-declarations { - @include mdc-textfield.without-ripple( - $query: mat.$private-mdc-base-styles-without-animation-query); - @include mdc-floating-label.core-styles( - $query: mat.$private-mdc-base-styles-without-animation-query); - @include mdc-notched-outline.core-styles( - $query: mat.$private-mdc-base-styles-without-animation-query); - @include mdc-line-ripple.core-styles( - $query: mat.$private-mdc-base-styles-without-animation-query); -} - -// MDC text-field overwrites. -@include mdc-text-field-textarea-overrides.private-text-field-textarea-overrides(); -@include mdc-text-field-structure-overrides.private-text-field-structure-overrides(); - -// Include the subscript, focus-overlay, native select and high-contrast styles. -@include form-field-subscript.private-form-field-subscript(); -@include form-field-focus-overlay.private-form-field-focus-overlay(); -@include form-field-native-select.private-form-field-native-select(); -@include form-field-high-contrast.private-form-field-high-contrast(); - -// Host element of the form-field. It contains the mdc-text-field wrapper -// and the subscript wrapper. -.mat-mdc-form-field { - // The scale to use for the form-field's label when its in the floating position. - --mat-mdc-form-field-floating-label-scale: 0.75; - - display: inline-flex; - // This container contains the text-field and the subscript. The subscript - // should be displayed below the text-field. Hence the column direction. - flex-direction: column; - // This allows the form-field to shrink down when used inside flex or grid layouts. - min-width: 0; - // To avoid problems with text-align. - text-align: left; - - [dir='rtl'] & { - text-align: right; - } -} - -// Container that contains the prefixes, infix and suffixes. These elements should -// be aligned vertically in the baseline and in a single row. -.mat-mdc-form-field-flex { - display: inline-flex; - align-items: baseline; - box-sizing: border-box; - width: 100%; -} - -// The MDC text-field should stretch to the width of the host `` element. -// This allows developers to control the width without needing custom CSS overrides. -.mat-mdc-text-field-wrapper { - width: 100%; -} - -// Vertically center icons. -.mat-mdc-form-field-icon-prefix, -.mat-mdc-form-field-icon-suffix { - align-self: center; - // The line-height can cause the prefix/suffix container to be taller than the actual icons, - // breaking the vertical centering. To prevent this we set the line-height to 0. - line-height: 0; -} - -// The prefix/suffix needs a little extra padding between the icon and the infix. Because we need to -// support arbitrary height input elements, we use a different DOM structure for prefix and suffix -// icons, and therefore can't rely on MDC for these styles. -.mat-mdc-form-field-icon-prefix, -[dir='rtl'] .mat-mdc-form-field-icon-suffix { - padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0; -} -.mat-mdc-form-field-icon-suffix, -[dir='rtl'] .mat-mdc-form-field-icon-prefix { - padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding; -} - -.mat-mdc-form-field-icon-prefix, -.mat-mdc-form-field-icon-suffix { - & > .mat-icon { - padding: 12px; - // It's common for apps to apply `box-sizing: border-box` - // globally which will break the alignment. - box-sizing: content-box; - } -} - -// Scale down icons in the subscript and floating label to be the same size -// as the text. -.mat-mdc-form-field-subscript-wrapper, -.mat-mdc-form-field label { - .mat-icon { - width: 1em; - height: 1em; - font-size: inherit; - } -} - -// Infix that contains the projected content (usually an input or a textarea). We ensure -// that the projected form-field control and content can stretch as needed, but we also -// apply a default infix width to make the form-field's look natural. -.mat-mdc-form-field-infix { - flex: auto; - min-width: 0; - width: form-field-sizing.$mat-form-field-default-infix-width; - // Needed so that the floating label does not overlap with prefixes or suffixes. - position: relative; - box-sizing: border-box; -} - -// In the form-field theme, we add a 1px left margin to the notch to fix a rendering bug in Chrome. -// Here we apply negative margin to offset the effect on the layout and a clip-path to ensure the -// left border is completely hidden. (Though the border is transparent, it still creates a triangle -// shaped artifact where it meets the top and bottom borders.) -.mat-mdc-form-field .mdc-notched-outline__notch { - margin-left: -1px; - @include mat.private-clip-path(inset(-9em -999em -9em 1px)); - - [dir='rtl'] & { - margin-left: 0; - margin-right: -1px; - @include mat.private-clip-path(inset(-9em 1px -9em -999em)); - } -} - -// In order to make it possible for developers to disable animations for form-fields, -// we only activate the animation styles if animations are not explicitly disabled. -.mat-mdc-form-field:not(.mat-form-field-no-animations) { - @include mat.private-disable-mdc-fallback-declarations { - @include mdc-textfield.without-ripple($query: animation); - @include mdc-floating-label.core-styles($query: animation); - @include mdc-notched-outline.core-styles($query: animation); - @include mdc-line-ripple.core-styles($query: animation); - } -} diff --git a/src/material-experimental/mdc-form-field/form-field.ts b/src/material-experimental/mdc-form-field/form-field.ts deleted file mode 100644 index 94e06cf22b71..000000000000 --- a/src/material-experimental/mdc-form-field/form-field.ts +++ /dev/null @@ -1,695 +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 {Directionality} from '@angular/cdk/bidi'; -import {Platform} from '@angular/cdk/platform'; -import { - AfterContentChecked, - AfterContentInit, - AfterViewInit, - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - ContentChild, - ContentChildren, - ElementRef, - Inject, - InjectionToken, - Input, - NgZone, - OnDestroy, - Optional, - QueryList, - ViewChild, - ViewEncapsulation, -} from '@angular/core'; -import {AbstractControlDirective} from '@angular/forms'; -import {ThemePalette} from '@angular/material-experimental/mdc-core'; -import { - getMatFormFieldDuplicatedHintError, - getMatFormFieldMissingControlError, - MAT_FORM_FIELD, - matFormFieldAnimations, - MatFormFieldControl, -} from '@angular/material/form-field'; -import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; -import {merge, Subject} from 'rxjs'; -import {takeUntil} from 'rxjs/operators'; -import {MAT_ERROR, MatError} from './directives/error'; -import {MatFormFieldFloatingLabel} from './directives/floating-label'; -import {MatHint} from './directives/hint'; -import {MatLabel} from './directives/label'; -import {MatFormFieldLineRipple} from './directives/line-ripple'; -import {MatFormFieldNotchedOutline} from './directives/notched-outline'; -import {MAT_PREFIX, MatPrefix} from './directives/prefix'; -import {MAT_SUFFIX, MatSuffix} from './directives/suffix'; -import {DOCUMENT} from '@angular/common'; -import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; - -/** Type for the available floatLabel values. */ -export type FloatLabelType = 'always' | 'auto'; - -/** Possible appearance styles for the form field. */ -export type MatFormFieldAppearance = 'fill' | 'outline'; - -/** Behaviors for how the subscript height is set. */ -export type SubscriptSizing = 'fixed' | 'dynamic'; - -/** - * Represents the default options for the form field that can be configured - * using the `MAT_FORM_FIELD_DEFAULT_OPTIONS` injection token. - */ -export interface MatFormFieldDefaultOptions { - /** Default form field appearance style. */ - appearance?: MatFormFieldAppearance; - /** Default color of the form field. */ - color?: ThemePalette; - /** Whether the required marker should be hidden by default. */ - hideRequiredMarker?: boolean; - /** - * Whether the label for form fields should by default float `always`, - * `never`, or `auto` (only when necessary). - */ - floatLabel?: FloatLabelType; - /** Whether the form field should reserve space for one line by default. */ - subscriptSizing?: SubscriptSizing; -} - -/** - * Injection token that can be used to configure the - * default options for all form field within an app. - */ -export const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken( - 'MAT_FORM_FIELD_DEFAULT_OPTIONS', -); - -let nextUniqueId = 0; - -/** Default appearance used by the form field. */ -const DEFAULT_APPEARANCE: MatFormFieldAppearance = 'fill'; - -/** - * Whether the label for form fields should by default float `always`, - * `never`, or `auto`. - */ -const DEFAULT_FLOAT_LABEL: FloatLabelType = 'auto'; - -/** Default way that the subscript element height is set. */ -const DEFAULT_SUBSCRIPT_SIZING: SubscriptSizing = 'fixed'; - -/** - * Default transform for docked floating labels in a MDC text-field. This value has been - * extracted from the MDC text-field styles because we programmatically modify the docked - * label transform, but do not want to accidentally discard the default label transform. - */ -const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`; - -/** - * Horizontal padding in pixels used by the MDC for the wrapper containing infix. - * This value is extracted from MDC's Sass variables. See `$padding-horizontal`. - */ -const WRAPPER_HORIZONTAL_PADDING = 16; - -/** Container for form controls that applies Material Design styling and behavior. */ -@Component({ - selector: 'mat-form-field', - exportAs: 'matFormField', - templateUrl: './form-field.html', - styleUrls: ['./form-field.css'], - animations: [matFormFieldAnimations.transitionMessages], - host: { - 'class': 'mat-mdc-form-field', - '[class.mat-mdc-form-field-label-always-float]': '_shouldAlwaysFloat()', - '[class.mat-mdc-form-field-has-icon-prefix]': '_hasIconPrefix', - '[class.mat-mdc-form-field-has-icon-suffix]': '_hasIconSuffix', - - // Note that these classes reuse the same names as the non-MDC version, because they can be - // considered a public API since custom form controls may use them to style themselves. - // See https://github.com/angular/components/pull/20502#discussion_r486124901. - '[class.mat-form-field-invalid]': '_control.errorState', - '[class.mat-form-field-disabled]': '_control.disabled', - '[class.mat-form-field-autofilled]': '_control.autofilled', - '[class.mat-form-field-no-animations]': '_animationMode === "NoopAnimations"', - '[class.mat-form-field-appearance-fill]': 'appearance == "fill"', - '[class.mat-form-field-appearance-outline]': 'appearance == "outline"', - '[class.mat-form-field-hide-placeholder]': '_hasFloatingLabel() && !_shouldLabelFloat()', - '[class.mat-focused]': '_control.focused', - '[class.mat-primary]': 'color !== "accent" && color !== "warn"', - '[class.mat-accent]': 'color === "accent"', - '[class.mat-warn]': 'color === "warn"', - '[class.ng-untouched]': '_shouldForward("untouched")', - '[class.ng-touched]': '_shouldForward("touched")', - '[class.ng-pristine]': '_shouldForward("pristine")', - '[class.ng-dirty]': '_shouldForward("dirty")', - '[class.ng-valid]': '_shouldForward("valid")', - '[class.ng-invalid]': '_shouldForward("invalid")', - '[class.ng-pending]': '_shouldForward("pending")', - }, - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [{provide: MAT_FORM_FIELD, useExisting: MatFormField}], -}) -export class MatFormField - implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy -{ - @ViewChild('textField') _textField: ElementRef; - @ViewChild('iconPrefixContainer') _iconPrefixContainer: ElementRef; - @ViewChild('textPrefixContainer') _textPrefixContainer: ElementRef; - @ViewChild(MatFormFieldFloatingLabel) _floatingLabel: MatFormFieldFloatingLabel | undefined; - @ViewChild(MatFormFieldNotchedOutline) _notchedOutline: MatFormFieldNotchedOutline | undefined; - @ViewChild(MatFormFieldLineRipple) _lineRipple: MatFormFieldLineRipple | undefined; - - @ContentChild(MatLabel) _labelChildNonStatic: MatLabel | undefined; - @ContentChild(MatLabel, {static: true}) _labelChildStatic: MatLabel | undefined; - @ContentChild(MatFormFieldControl) _formFieldControl: MatFormFieldControl; - @ContentChildren(MAT_PREFIX, {descendants: true}) _prefixChildren: QueryList; - @ContentChildren(MAT_SUFFIX, {descendants: true}) _suffixChildren: QueryList; - @ContentChildren(MAT_ERROR, {descendants: true}) _errorChildren: QueryList; - @ContentChildren(MatHint, {descendants: true}) _hintChildren: QueryList; - - /** Whether the required marker should be hidden. */ - @Input() - get hideRequiredMarker(): boolean { - return this._hideRequiredMarker; - } - set hideRequiredMarker(value: BooleanInput) { - this._hideRequiredMarker = coerceBooleanProperty(value); - } - private _hideRequiredMarker = false; - - /** The color palette for the form field. */ - @Input() color: ThemePalette = 'primary'; - - /** Whether the label should always float or float as the user types. */ - @Input() - get floatLabel(): FloatLabelType { - return this._floatLabel || this._defaults?.floatLabel || DEFAULT_FLOAT_LABEL; - } - set floatLabel(value: FloatLabelType) { - if (value !== this._floatLabel) { - this._floatLabel = value; - // For backwards compatibility. Custom form field controls or directives might set - // the "floatLabel" input and expect the form field view to be updated automatically. - // e.g. autocomplete trigger. Ideally we'd get rid of this and the consumers would just - // emit the "stateChanges" observable. TODO(devversion): consider removing. - this._changeDetectorRef.markForCheck(); - } - } - private _floatLabel: FloatLabelType; - - /** The form field appearance style. */ - @Input() - get appearance(): MatFormFieldAppearance { - return this._appearance; - } - set appearance(value: MatFormFieldAppearance) { - const oldValue = this._appearance; - const newAppearance = value || this._defaults?.appearance || DEFAULT_APPEARANCE; - if (typeof ngDevMode === 'undefined' || ngDevMode) { - if (newAppearance !== 'fill' && newAppearance !== 'outline') { - throw new Error( - `MatFormField: Invalid appearance "${newAppearance}", valid values are "fill" or "outline".`, - ); - } - } - this._appearance = newAppearance; - if (this._appearance === 'outline' && this._appearance !== oldValue) { - this._refreshOutlineNotchWidth(); - - // If the appearance has been switched to `outline`, the label offset needs to be updated. - // The update can happen once the view has been re-checked, but not immediately because - // the view has not been updated and the notched-outline floating label is not present. - this._needsOutlineLabelOffsetUpdateOnStable = true; - } - } - private _appearance: MatFormFieldAppearance = DEFAULT_APPEARANCE; - - /** - * Whether the form field should reserve space for one line of hint/error text (default) - * or to have the spacing grow from 0px as needed based on the size of the hint/error content. - * Note that when using dynamic sizing, layout shifts will occur when hint/error text changes. - */ - @Input() - get subscriptSizing(): SubscriptSizing { - return this._subscriptSizing || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING; - } - set subscriptSizing(value: SubscriptSizing) { - this._subscriptSizing = value || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING; - } - private _subscriptSizing: SubscriptSizing | null = null; - - /** Text for the form field hint. */ - @Input() - get hintLabel(): string { - return this._hintLabel; - } - set hintLabel(value: string) { - this._hintLabel = value; - this._processHints(); - } - private _hintLabel = ''; - - _hasIconPrefix = false; - _hasTextPrefix = false; - _hasIconSuffix = false; - _hasTextSuffix = false; - - // Unique id for the internal form field label. - readonly _labelId = `mat-mdc-form-field-label-${nextUniqueId++}`; - - // Unique id for the hint label. - readonly _hintLabelId = `mat-mdc-hint-${nextUniqueId++}`; - - /** State of the mat-hint and mat-error animations. */ - _subscriptAnimationState = ''; - - /** Width of the label element (at scale=1). */ - _labelWidth = 0; - - /** Gets the current form field control */ - get _control(): MatFormFieldControl { - return this._explicitFormFieldControl || this._formFieldControl; - } - set _control(value) { - this._explicitFormFieldControl = value; - } - - private _destroyed = new Subject(); - private _isFocused: boolean | null = null; - private _explicitFormFieldControl: MatFormFieldControl; - private _needsOutlineLabelOffsetUpdateOnStable = false; - - constructor( - private _elementRef: ElementRef, - private _changeDetectorRef: ChangeDetectorRef, - private _ngZone: NgZone, - private _dir: Directionality, - private _platform: Platform, - @Optional() - @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) - private _defaults?: MatFormFieldDefaultOptions, - @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string, - @Inject(DOCUMENT) private _document?: any, - ) { - if (_defaults) { - if (_defaults.appearance) { - this.appearance = _defaults.appearance; - } - this._hideRequiredMarker = Boolean(_defaults?.hideRequiredMarker); - if (_defaults.color) { - this.color = _defaults.color; - } - } - } - - ngAfterViewInit() { - // Initial focus state sync. This happens rarely, but we want to account for - // it in case the form field control has "focused" set to true on init. - this._updateFocusState(); - // Initial notch width update. This is needed in case the text-field label floats - // on initialization, and renders inside of the notched outline. - this._refreshOutlineNotchWidth(); - // Make sure fonts are loaded before calculating the width. - // zone.js currently doesn't patch the FontFaceSet API so two calls to - // _refreshOutlineNotchWidth is needed for this to work properly in async tests. - // Furthermore if the font takes a long time to load we want the outline notch to be close - // to the correct width from the start then correct itself when the fonts load. - if (this._document?.fonts?.ready) { - this._document.fonts.ready.then(() => { - this._refreshOutlineNotchWidth(); - this._changeDetectorRef.markForCheck(); - }); - } else { - // FontFaceSet is not supported in IE - setTimeout(() => this._refreshOutlineNotchWidth(), 100); - } - // Enable animations now. This ensures we don't animate on initial render. - this._subscriptAnimationState = 'enter'; - // Because the above changes a value used in the template after it was checked, we need - // to trigger CD or the change might not be reflected if there is no other CD scheduled. - this._changeDetectorRef.detectChanges(); - } - - ngAfterContentInit() { - this._assertFormFieldControl(); - this._initializeControl(); - this._initializeSubscript(); - this._initializePrefixAndSuffix(); - this._initializeOutlineLabelOffsetSubscriptions(); - } - - ngAfterContentChecked() { - this._assertFormFieldControl(); - } - - ngOnDestroy() { - this._destroyed.next(); - this._destroyed.complete(); - } - - /** - * Gets the id of the label element. If no label is present, returns `null`. - */ - getLabelId(): string | null { - return this._hasFloatingLabel() ? this._labelId : null; - } - - /** - * Gets an ElementRef for the element that a overlay attached to the form field - * should be positioned relative to. - */ - getConnectedOverlayOrigin(): ElementRef { - return this._textField || this._elementRef; - } - - /** Animates the placeholder up and locks it in position. */ - _animateAndLockLabel(): void { - // This is for backwards compatibility only. Consumers of the form field might use - // this method. e.g. the autocomplete trigger. This method has been added to the non-MDC - // form field because setting "floatLabel" to "always" caused the label to float without - // animation. This is different in MDC where the label always animates, so this method - // is no longer necessary. There doesn't seem any benefit in adding logic to allow changing - // the floating label state without animations. The non-MDC implementation was inconsistent - // because it always animates if "floatLabel" is set away from "always". - // TODO(devversion): consider removing this method when releasing the MDC form field. - if (this._hasFloatingLabel()) { - this.floatLabel = 'always'; - } - } - - /** Initializes the registered form field control. */ - private _initializeControl() { - const control = this._control; - - if (control.controlType) { - this._elementRef.nativeElement.classList.add( - `mat-mdc-form-field-type-${control.controlType}`, - ); - } - - // Subscribe to changes in the child control state in order to update the form field UI. - control.stateChanges.subscribe(() => { - this._updateFocusState(); - this._syncDescribedByIds(); - this._changeDetectorRef.markForCheck(); - }); - - // Run change detection if the value changes. - if (control.ngControl && control.ngControl.valueChanges) { - control.ngControl.valueChanges - .pipe(takeUntil(this._destroyed)) - .subscribe(() => this._changeDetectorRef.markForCheck()); - } - } - - private _checkPrefixAndSuffixTypes() { - this._hasIconPrefix = !!this._prefixChildren.find(p => !p._isText); - this._hasTextPrefix = !!this._prefixChildren.find(p => p._isText); - this._hasIconSuffix = !!this._suffixChildren.find(s => !s._isText); - this._hasTextSuffix = !!this._suffixChildren.find(s => s._isText); - } - - /** Initializes the prefix and suffix containers. */ - private _initializePrefixAndSuffix() { - this._checkPrefixAndSuffixTypes(); - // Mark the form field as dirty whenever the prefix or suffix children change. This - // is necessary because we conditionally display the prefix/suffix containers based - // on whether there is projected content. - merge(this._prefixChildren.changes, this._suffixChildren.changes).subscribe(() => { - this._checkPrefixAndSuffixTypes(); - this._changeDetectorRef.markForCheck(); - }); - } - - /** - * Initializes the subscript by validating hints and synchronizing "aria-describedby" ids - * with the custom form field control. Also subscribes to hint and error changes in order - * to be able to validate and synchronize ids on change. - */ - private _initializeSubscript() { - // Re-validate when the number of hints changes. - this._hintChildren.changes.subscribe(() => { - this._processHints(); - this._changeDetectorRef.markForCheck(); - }); - - // Update the aria-described by when the number of errors changes. - this._errorChildren.changes.subscribe(() => { - this._syncDescribedByIds(); - this._changeDetectorRef.markForCheck(); - }); - - // Initial mat-hint validation and subscript describedByIds sync. - this._validateHints(); - this._syncDescribedByIds(); - } - - /** Throws an error if the form field's control is missing. */ - private _assertFormFieldControl() { - if (!this._control && (typeof ngDevMode === 'undefined' || ngDevMode)) { - throw getMatFormFieldMissingControlError(); - } - } - - private _updateFocusState() { - // Usually the MDC foundation would call "activateFocus" and "deactivateFocus" whenever - // certain DOM events are emitted. This is not possible in our implementation of the - // form field because we support abstract form field controls which are not necessarily - // of type input, nor do we have a reference to a native form field control element. Instead - // we handle the focus by checking if the abstract form field control focused state changes. - if (this._control.focused && !this._isFocused) { - this._isFocused = true; - this._lineRipple?.activate(); - } else if (!this._control.focused && (this._isFocused || this._isFocused === null)) { - this._isFocused = false; - this._lineRipple?.deactivate(); - } - - this._textField?.nativeElement.classList.toggle( - 'mdc-text-field--focused', - this._control.focused, - ); - } - - /** - * The floating label in the docked state needs to account for prefixes. The horizontal offset - * is calculated whenever the appearance changes to `outline`, the prefixes change, or when the - * form field is added to the DOM. This method sets up all subscriptions which are needed to - * trigger the label offset update. In general, we want to avoid performing measurements often, - * so we rely on the `NgZone` as indicator when the offset should be recalculated, instead of - * checking every change detection cycle. - */ - private _initializeOutlineLabelOffsetSubscriptions() { - // Whenever the prefix changes, schedule an update of the label offset. - this._prefixChildren.changes.subscribe( - () => (this._needsOutlineLabelOffsetUpdateOnStable = true), - ); - - // Note that we have to run outside of the `NgZone` explicitly, in order to avoid - // throwing users into an infinite loop if `zone-patch-rxjs` is included. - this._ngZone.runOutsideAngular(() => { - this._ngZone.onStable.pipe(takeUntil(this._destroyed)).subscribe(() => { - if (this._needsOutlineLabelOffsetUpdateOnStable) { - this._needsOutlineLabelOffsetUpdateOnStable = false; - this._updateOutlineLabelOffset(); - } - }); - }); - - this._dir.change - .pipe(takeUntil(this._destroyed)) - .subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true)); - } - - /** Whether the floating label should always float or not. */ - _shouldAlwaysFloat() { - return this.floatLabel === 'always'; - } - - _hasOutline() { - return this.appearance === 'outline'; - } - - /** - * Whether the label should display in the infix. Labels in the outline appearance are - * displayed as part of the notched-outline and are horizontally offset to account for - * form field prefix content. This won't work in server side rendering since we cannot - * measure the width of the prefix container. To make the docked label appear as if the - * right offset has been calculated, we forcibly render the label inside the infix. Since - * the label is part of the infix, the label cannot overflow the prefix content. - */ - _forceDisplayInfixLabel() { - return !this._platform.isBrowser && this._prefixChildren.length && !this._shouldLabelFloat(); - } - - _hasFloatingLabel() { - return !!this._labelChildNonStatic || !!this._labelChildStatic; - } - - _shouldLabelFloat() { - return this._control.shouldLabelFloat || this._shouldAlwaysFloat(); - } - - /** - * Determines whether a class from the AbstractControlDirective - * should be forwarded to the host element. - */ - _shouldForward(prop: keyof AbstractControlDirective): boolean { - const control = this._control ? this._control.ngControl : null; - return control && control[prop]; - } - - /** Determines whether to display hints or errors. */ - _getDisplayedMessages(): 'error' | 'hint' { - return this._errorChildren && this._errorChildren.length > 0 && this._control.errorState - ? 'error' - : 'hint'; - } - - /** Refreshes the width of the outline-notch, if present. */ - _refreshOutlineNotchWidth() { - if (!this._hasOutline() || !this._floatingLabel) { - return; - } - this._labelWidth = this._floatingLabel.getWidth(); - } - - /** Does any extra processing that is required when handling the hints. */ - private _processHints() { - this._validateHints(); - this._syncDescribedByIds(); - } - - /** - * Ensure that there is a maximum of one of each "mat-hint" alignment specified. The hint - * label specified set through the input is being considered as "start" aligned. - * - * This method is a noop if Angular runs in production mode. - */ - private _validateHints() { - if (this._hintChildren && (typeof ngDevMode === 'undefined' || ngDevMode)) { - let startHint: MatHint; - let endHint: MatHint; - this._hintChildren.forEach((hint: MatHint) => { - if (hint.align === 'start') { - if (startHint || this.hintLabel) { - throw getMatFormFieldDuplicatedHintError('start'); - } - startHint = hint; - } else if (hint.align === 'end') { - if (endHint) { - throw getMatFormFieldDuplicatedHintError('end'); - } - endHint = hint; - } - }); - } - } - - /** - * Sets the list of element IDs that describe the child control. This allows the control to update - * its `aria-describedby` attribute accordingly. - */ - private _syncDescribedByIds() { - if (this._control) { - let ids: string[] = []; - - // TODO(wagnermaciel): Remove the type check when we find the root cause of this bug. - if ( - this._control.userAriaDescribedBy && - typeof this._control.userAriaDescribedBy === 'string' - ) { - ids.push(...this._control.userAriaDescribedBy.split(' ')); - } - - if (this._getDisplayedMessages() === 'hint') { - const startHint = this._hintChildren - ? this._hintChildren.find(hint => hint.align === 'start') - : null; - const endHint = this._hintChildren - ? this._hintChildren.find(hint => hint.align === 'end') - : null; - - if (startHint) { - ids.push(startHint.id); - } else if (this._hintLabel) { - ids.push(this._hintLabelId); - } - - if (endHint) { - ids.push(endHint.id); - } - } else if (this._errorChildren) { - ids.push(...this._errorChildren.map(error => error.id)); - } - - this._control.setDescribedByIds(ids); - } - } - - /** - * Updates the horizontal offset of the label in the outline appearance. In the outline - * appearance, the notched-outline and label are not relative to the infix container because - * the outline intends to surround prefixes, suffixes and the infix. This means that the - * floating label by default overlaps prefixes in the docked state. To avoid this, we need to - * horizontally offset the label by the width of the prefix container. The MDC text-field does - * not need to do this because they use a fixed width for prefixes. Hence, they can simply - * incorporate the horizontal offset into their default text-field styles. - */ - private _updateOutlineLabelOffset() { - if (!this._platform.isBrowser || !this._hasOutline() || !this._floatingLabel) { - return; - } - const floatingLabel = this._floatingLabel.element; - // If no prefix is displayed, reset the outline label offset from potential - // previous label offset updates. - if (!(this._iconPrefixContainer || this._textPrefixContainer)) { - floatingLabel.style.transform = ''; - return; - } - // If the form field is not attached to the DOM yet (e.g. in a tab), we defer - // the label offset update until the zone stabilizes. - if (!this._isAttachedToDom()) { - this._needsOutlineLabelOffsetUpdateOnStable = true; - return; - } - const iconPrefixContainer = this._iconPrefixContainer?.nativeElement; - const textPrefixContainer = this._textPrefixContainer?.nativeElement; - const iconPrefixContainerWidth = iconPrefixContainer?.getBoundingClientRect().width ?? 0; - const textPrefixContainerWidth = textPrefixContainer?.getBoundingClientRect().width ?? 0; - // If the directionality is RTL, the x-axis transform needs to be inverted. This - // is because `transformX` does not change based on the page directionality. - const labelHorizontalOffset = - (this._dir.value === 'rtl' ? -1 : 1) * - // If there's an icon prefix, we subtract the default horizontal padding as we - // reset the horizontal padding in CSS too. - ((iconPrefixContainer ? iconPrefixContainerWidth - WRAPPER_HORIZONTAL_PADDING : 0) + - textPrefixContainerWidth); - - // Update the translateX of the floating label to account for the prefix container, - // but allow the CSS to override this setting via a CSS variable when the label is - // floating. - floatingLabel.style.transform = `var( - --mat-mdc-form-field-label-transform, - ${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset}px - )`; - } - - /** Checks whether the form field is attached to the DOM. */ - private _isAttachedToDom(): boolean { - const element: HTMLElement = this._elementRef.nativeElement; - if (element.getRootNode) { - const rootNode = element.getRootNode(); - // If the element is inside the DOM the root node will be either the document - // or the closest shadow root, otherwise it'll be the element itself. - return rootNode && rootNode !== element; - } - // Otherwise fall back to checking if it's in the document. This doesn't account for - // shadow DOM, however browser that support shadow DOM should support `getRootNode` as well. - return document.documentElement!.contains(element); - } -} diff --git a/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts b/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts deleted file mode 100644 index 4e1bb1b485c0..000000000000 --- a/src/material-experimental/mdc-form-field/testing/form-field-harness.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; -import {MatInputModule} from '@angular/material-experimental/mdc-input'; -import {MatAutocompleteModule} from '@angular/material-experimental/mdc-autocomplete'; -import {MatInputHarness} from '@angular/material-experimental/mdc-input/testing'; -import {MatSelectModule} from '@angular/material-experimental/mdc-select'; -import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testing'; -import {runHarnessTests} from '@angular/material/form-field/testing/shared.spec'; -import {MatDatepickerModule} from '@angular/material/datepicker'; -import {MatNativeDateModule} from '@angular/material-experimental/mdc-core'; -import { - MatDatepickerInputHarness, - MatDateRangeInputHarness, -} from '@angular/material/datepicker/testing'; -import {MatFormFieldHarness} from './form-field-harness'; - -describe('MDC-based MatFormFieldHarness', () => { - runHarnessTests( - [ - MatFormFieldModule, - MatAutocompleteModule, - MatInputModule, - MatSelectModule, - MatNativeDateModule, - MatDatepickerModule, - ], - { - formFieldHarness: MatFormFieldHarness as any, - inputHarness: MatInputHarness, - selectHarness: MatSelectHarness, - datepickerInputHarness: MatDatepickerInputHarness, - dateRangeInputHarness: MatDateRangeInputHarness, - isMdcImplementation: true, - }, - ); -}); diff --git a/src/material-experimental/mdc-form-field/testing/form-field-harness.ts b/src/material-experimental/mdc-form-field/testing/form-field-harness.ts deleted file mode 100644 index 948b31149ac0..000000000000 --- a/src/material-experimental/mdc-form-field/testing/form-field-harness.ts +++ /dev/null @@ -1,84 +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 {ComponentHarnessConstructor, HarnessPredicate} from '@angular/cdk/testing'; -import { - FormFieldHarnessFilters, - _MatFormFieldHarnessBase, -} from '@angular/material/form-field/testing'; -import {MatInputHarness} from '@angular/material-experimental/mdc-input/testing'; -import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testing'; -import { - MatDatepickerInputHarness, - MatDateRangeInputHarness, -} from '@angular/material/datepicker/testing'; - -// TODO(devversion): support support chip list harness -/** Possible harnesses of controls which can be bound to a form-field. */ -export type FormFieldControlHarness = - | MatInputHarness - | MatSelectHarness - | MatDatepickerInputHarness - | MatDateRangeInputHarness; - -/** Harness for interacting with a MDC-based form-field's in tests. */ -export class MatFormFieldHarness extends _MatFormFieldHarnessBase { - static hostSelector = '.mat-mdc-form-field'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a form field with specific - * attributes. - * @param options Options for filtering which form field instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with( - this: ComponentHarnessConstructor, - options: FormFieldHarnessFilters = {}, - ): HarnessPredicate { - return new HarnessPredicate(this, options) - .addOption('floatingLabelText', options.floatingLabelText, async (harness, text) => - HarnessPredicate.stringMatches(await harness.getLabel(), text), - ) - .addOption( - 'hasErrors', - options.hasErrors, - async (harness, hasErrors) => (await harness.hasErrors()) === hasErrors, - ); - } - - protected _prefixContainer = this.locatorForOptional('.mat-mdc-form-field-text-prefix'); - protected _suffixContainer = this.locatorForOptional('.mat-mdc-form-field-text-suffix'); - protected _label = this.locatorForOptional('.mdc-floating-label'); - protected _errors = this.locatorForAll('.mat-mdc-form-field-error'); - protected _hints = this.locatorForAll('.mat-mdc-form-field-hint'); - protected _inputControl = this.locatorForOptional(MatInputHarness); - protected _selectControl = this.locatorForOptional(MatSelectHarness); - protected _datepickerInputControl = this.locatorForOptional(MatDatepickerInputHarness); - protected _dateRangeInputControl = this.locatorForOptional(MatDateRangeInputHarness); - private _mdcTextField = this.locatorFor('.mat-mdc-text-field-wrapper'); - - /** Gets the appearance of the form-field. */ - async getAppearance(): Promise<'fill' | 'outline'> { - const textFieldEl = await this._mdcTextField(); - if (await textFieldEl.hasClass('mdc-text-field--outlined')) { - return 'outline'; - } - return 'fill'; - } - - /** Whether the form-field has a label. */ - async hasLabel(): Promise { - return (await this._label()) !== null; - } - - /** Whether the label is currently floating. */ - async isLabelFloating(): Promise { - const labelEl = await this._label(); - return labelEl !== null ? await labelEl.hasClass('mdc-floating-label--float-above') : false; - } -} diff --git a/src/material-experimental/mdc-paginator/BUILD.bazel b/src/material-experimental/mdc-paginator/BUILD.bazel index 105b3854a3cf..8fdeb3773e69 100644 --- a/src/material-experimental/mdc-paginator/BUILD.bazel +++ b/src/material-experimental/mdc-paginator/BUILD.bazel @@ -34,7 +34,7 @@ sass_library( deps = [ "//:mdc_sass_lib", "//src/material:sass_lib", - "//src/material-experimental/mdc-form-field:mdc_form_field_scss_lib", + "//src/material/form-field:form_field_scss_lib", "//src/material/core:core_scss_lib", ], ) diff --git a/src/material-experimental/mdc-paginator/_paginator-theme.scss b/src/material-experimental/mdc-paginator/_paginator-theme.scss index f88716251a87..edfc7ed8e1d8 100644 --- a/src/material-experimental/mdc-paginator/_paginator-theme.scss +++ b/src/material-experimental/mdc-paginator/_paginator-theme.scss @@ -3,7 +3,7 @@ @use '@angular/material' as mat; @use '@material/density' as mdc-density; @use '@material/typography' as mdc-typography; -@use '../mdc-form-field/form-field-theme'; +@use '../../material/form-field/form-field-theme'; @use './paginator-variables'; diff --git a/src/material-experimental/mdc-paginator/paginator.ts b/src/material-experimental/mdc-paginator/paginator.ts index 07362ad122c0..7b5bf8454b8c 100644 --- a/src/material-experimental/mdc-paginator/paginator.ts +++ b/src/material-experimental/mdc-paginator/paginator.ts @@ -16,7 +16,7 @@ import { ViewEncapsulation, } from '@angular/core'; import {MatPaginatorIntl, _MatPaginatorBase} from '@angular/material/paginator'; -import {MatFormFieldAppearance} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldAppearance} from '@angular/material/form-field'; // Note that while `MatPaginatorDefaultOptions` and `MAT_PAGINATOR_DEFAULT_OPTIONS` are identical // between the MDC and non-MDC versions, we have to duplicate them, because the type of diff --git a/src/material-experimental/mdc-select/BUILD.bazel b/src/material-experimental/mdc-select/BUILD.bazel index ca387a96b48c..df891721c639 100644 --- a/src/material-experimental/mdc-select/BUILD.bazel +++ b/src/material-experimental/mdc-select/BUILD.bazel @@ -15,7 +15,7 @@ ng_module( deps = [ "//src/cdk/overlay", "//src/material-experimental/mdc-core", - "//src/material-experimental/mdc-form-field", + "//src/material/form-field", "//src/material/select", ], ) @@ -53,7 +53,7 @@ ng_test_library( "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material-experimental/mdc-core", - "//src/material-experimental/mdc-form-field", + "//src/material/form-field", "//src/material/core", "//src/material/select", "@npm//@angular/forms", diff --git a/src/material-experimental/mdc-select/module.ts b/src/material-experimental/mdc-select/module.ts index c30694ef87d6..808bdd3e161c 100644 --- a/src/material-experimental/mdc-select/module.ts +++ b/src/material-experimental/mdc-select/module.ts @@ -10,7 +10,7 @@ import {OverlayModule} from '@angular/cdk/overlay'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatOptionModule} from '@angular/material-experimental/mdc-core'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldModule} from '@angular/material/form-field'; import {CdkScrollableModule} from '@angular/cdk/scrolling'; import {MAT_SELECT_SCROLL_STRATEGY_PROVIDER} from '@angular/material/select'; import {MatSelect, MatSelectTrigger} from './select'; diff --git a/src/material-experimental/mdc-select/select.spec.ts b/src/material-experimental/mdc-select/select.spec.ts index cadb10f7c139..639187546e11 100644 --- a/src/material-experimental/mdc-select/select.spec.ts +++ b/src/material-experimental/mdc-select/select.spec.ts @@ -58,7 +58,7 @@ import { FloatLabelType, MatFormFieldModule, MAT_FORM_FIELD_DEFAULT_OPTIONS, -} from '@angular/material-experimental/mdc-form-field'; +} from '@angular/material/form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {LiveAnnouncer} from '@angular/cdk/a11y'; diff --git a/src/material-experimental/mdc-select/testing/BUILD.bazel b/src/material-experimental/mdc-select/testing/BUILD.bazel index ed138ba80317..13baffdcd71d 100644 --- a/src/material-experimental/mdc-select/testing/BUILD.bazel +++ b/src/material-experimental/mdc-select/testing/BUILD.bazel @@ -27,7 +27,7 @@ ng_test_library( srcs = glob(["**/*.spec.ts"]), deps = [ ":testing", - "//src/material-experimental/mdc-form-field", + "//src/material/form-field", "//src/material-experimental/mdc-select", "//src/material/select/testing:harness_tests_lib", ], diff --git a/src/material-experimental/mdc-select/testing/select-harness.spec.ts b/src/material-experimental/mdc-select/testing/select-harness.spec.ts index e3457834b369..af2288f16e48 100644 --- a/src/material-experimental/mdc-select/testing/select-harness.spec.ts +++ b/src/material-experimental/mdc-select/testing/select-harness.spec.ts @@ -1,5 +1,5 @@ import {MatSelectModule} from '@angular/material-experimental/mdc-select'; -import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; +import {MatFormFieldModule} from '@angular/material/form-field'; import {runHarnessTests} from '@angular/material/select/testing/shared.spec'; import {MatSelectHarness} from './index'; diff --git a/src/material/_index.scss b/src/material/_index.scss index 4878a7024b51..626341f40b77 100644 --- a/src/material/_index.scss +++ b/src/material/_index.scss @@ -82,11 +82,15 @@ divider-typography; @forward './expansion/expansion-theme' as expansion-* show expansion-theme, expansion-color, expansion-typography; -@forward './form-field/form-field-theme' as form-field-* show form-field-theme, form-field-color, - form-field-typography; +@forward './form-field/form-field-theme' as form-field-* show form-field-theme, + form-field-color, form-field-typography; +@forward './legacy-form-field/form-field-theme' as legacy-form-field-* show legacy-form-field-theme, + legacy-form-field-color, legacy-form-field-typography; @forward './grid-list/grid-list-theme' as grid-list-* show grid-list-theme, grid-list-color, grid-list-typography; @forward './icon/icon-theme' as icon-* show icon-theme, icon-color, icon-typography; +@forward './legacy-input/input-theme' as legacy-input-* show legacy-input-theme, legacy-input-color, + legacy-input-typography; @forward './input/input-theme' as input-* show input-theme, input-color, input-typography; @forward './list/list-theme' as list-* show list-theme, list-color, list-typography; @forward './menu/menu-theme' as menu-* show menu-theme, menu-color, menu-typography; diff --git a/src/material/_theming.scss b/src/material/_theming.scss index c0073d03bae0..5d144286c123 100644 --- a/src/material/_theming.scss +++ b/src/material/_theming.scss @@ -19,10 +19,10 @@ @forward './dialog/dialog-legacy-index'; @forward './divider/divider-legacy-index'; @forward './expansion/expansion-legacy-index'; -@forward './form-field/form-field-legacy-index'; +@forward './legacy-form-field/form-field-legacy-index'; @forward './grid-list/grid-list-legacy-index'; @forward './icon/icon-legacy-index'; -@forward './input/input-legacy-index'; +@forward './legacy-input/input-legacy-index'; @forward './list/list-legacy-index'; @forward './menu/menu-legacy-index'; @forward './paginator/paginator-legacy-index'; diff --git a/src/material/autocomplete/BUILD.bazel b/src/material/autocomplete/BUILD.bazel index 56a505e85a38..957116a250ce 100644 --- a/src/material/autocomplete/BUILD.bazel +++ b/src/material/autocomplete/BUILD.bazel @@ -28,7 +28,7 @@ ng_module( "//src/cdk/portal", "//src/cdk/scrolling", "//src/material/core", - "//src/material/form-field", + "//src/material/legacy-form-field", "@npm//@angular/common", "@npm//@angular/core", "@npm//@angular/forms", @@ -66,8 +66,8 @@ ng_test_library( "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material/core", - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", "@npm//@angular/forms", "@npm//@angular/platform-browser", "@npm//rxjs", diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index cf470be674ba..58a1fc1cda35 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -45,7 +45,7 @@ import { _MatOptionBase, MatOptionSelectionChange, } from '@angular/material/core'; -import {MAT_FORM_FIELD, MatFormField} from '@angular/material/form-field'; +import {MAT_FORM_FIELD, MatLegacyFormField} from '@angular/material/legacy-form-field'; import {defer, fromEvent, merge, Observable, of as observableOf, Subject, Subscription} from 'rxjs'; import {delay, filter, map, switchMap, take, tap, startWith} from 'rxjs/operators'; @@ -202,7 +202,7 @@ export abstract class _MatAutocompleteTriggerBase private _changeDetectorRef: ChangeDetectorRef, @Inject(MAT_AUTOCOMPLETE_SCROLL_STRATEGY) scrollStrategy: any, @Optional() private _dir: Directionality, - @Optional() @Inject(MAT_FORM_FIELD) @Host() private _formField: MatFormField, + @Optional() @Inject(MAT_FORM_FIELD) @Host() private _formField: MatLegacyFormField, @Optional() @Inject(DOCUMENT) private _document: any, private _viewportRuler: ViewportRuler, @Optional() diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index 95f2ea30df31..9b005f793de1 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -37,13 +37,13 @@ import { } from '@angular/core/testing'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatOption, MatOptionSelectionChange} from '@angular/material/core'; -import {MatFormField, MatFormFieldModule} from '@angular/material/form-field'; +import {MatLegacyFormField, MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {EMPTY, Observable, Subject, Subscription} from 'rxjs'; import {map, startWith} from 'rxjs/operators'; -import {MatInputModule} from '../input/index'; +import {MatLegacyInputModule} from '../legacy-input/index'; import { getMatAutocompleteMissingPanelError, @@ -66,8 +66,8 @@ describe('MatAutocomplete', () => { TestBed.configureTestingModule({ imports: [ MatAutocompleteModule, - MatFormFieldModule, - MatInputModule, + MatLegacyFormFieldModule, + MatLegacyInputModule, FormsModule, ReactiveFormsModule, NoopAnimationsModule, @@ -3441,7 +3441,7 @@ class SimpleAutocomplete implements OnDestroy { @ViewChild(MatAutocompleteTrigger, {static: true}) trigger: MatAutocompleteTrigger; @ViewChild(MatAutocomplete) panel: MatAutocomplete; - @ViewChild(MatFormField) formField: MatFormField; + @ViewChild(MatLegacyFormField) formField: MatLegacyFormField; @ViewChildren(MatOption) options: QueryList; states: {code: string; name: string; height?: number; disabled?: boolean}[] = [ diff --git a/src/material/chips/BUILD.bazel b/src/material/chips/BUILD.bazel index afd2f11e9b29..e68bc0966952 100644 --- a/src/material/chips/BUILD.bazel +++ b/src/material/chips/BUILD.bazel @@ -25,7 +25,7 @@ ng_module( "//src/cdk/keycodes", "//src/cdk/platform", "//src/material/core", - "//src/material/form-field", + "//src/material/legacy-form-field", "@npm//@angular/core", "@npm//@angular/forms", "@npm//rxjs", @@ -62,8 +62,8 @@ ng_test_library( "//src/cdk/testing", "//src/cdk/testing/private", "//src/material/core", - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", "@npm//@angular/animations", "@npm//@angular/forms", "@npm//@angular/platform-browser", diff --git a/src/material/chips/chip-input.spec.ts b/src/material/chips/chip-input.spec.ts index 66b848404e65..8a71a471da6e 100644 --- a/src/material/chips/chip-input.spec.ts +++ b/src/material/chips/chip-input.spec.ts @@ -4,7 +4,7 @@ import {PlatformModule} from '@angular/cdk/platform'; import {dispatchKeyboardEvent} from '../../cdk/testing/private'; import {Component, DebugElement, ViewChild} from '@angular/core'; import {waitForAsync, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; -import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {Subject} from 'rxjs'; @@ -23,7 +23,7 @@ describe('MatChipInput', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [PlatformModule, MatChipsModule, MatFormFieldModule, NoopAnimationsModule], + imports: [PlatformModule, MatChipsModule, MatLegacyFormFieldModule, NoopAnimationsModule], declarations: [TestChipInput], providers: [ { @@ -211,7 +211,7 @@ describe('MatChipInput', () => { TestBed.resetTestingModule() .configureTestingModule({ - imports: [MatChipsModule, MatFormFieldModule, PlatformModule, NoopAnimationsModule], + imports: [MatChipsModule, MatLegacyFormFieldModule, PlatformModule, NoopAnimationsModule], declarations: [TestChipInput], providers: [ { diff --git a/src/material/chips/chip-list.spec.ts b/src/material/chips/chip-list.spec.ts index 528657485d66..0295e29d46ce 100644 --- a/src/material/chips/chip-list.spec.ts +++ b/src/material/chips/chip-list.spec.ts @@ -43,11 +43,11 @@ import { ReactiveFormsModule, Validators, } from '@angular/forms'; -import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; import {By} from '@angular/platform-browser'; import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations'; import {Subject} from 'rxjs'; -import {MatInputModule} from '../input/index'; +import {MatLegacyInputModule} from '../legacy-input/index'; import {MatChip} from './chip'; import {MatChipInputEvent} from './chip-input'; import {MatChipEvent, MatChipList, MatChipRemove, MatChipsModule} from './index'; @@ -1498,8 +1498,8 @@ describe('MatChipList', () => { FormsModule, ReactiveFormsModule, MatChipsModule, - MatFormFieldModule, - MatInputModule, + MatLegacyFormFieldModule, + MatLegacyInputModule, animationsModule, ], declarations: [component], diff --git a/src/material/chips/chip-list.ts b/src/material/chips/chip-list.ts index 9fbeab038666..64c2991f193f 100644 --- a/src/material/chips/chip-list.ts +++ b/src/material/chips/chip-list.ts @@ -36,7 +36,7 @@ import { Validators, } from '@angular/forms'; import {CanUpdateErrorState, ErrorStateMatcher, mixinErrorState} from '@angular/material/core'; -import {MatFormFieldControl} from '@angular/material/form-field'; +import {MatLegacyFormFieldControl} from '@angular/material/legacy-form-field'; import {merge, Observable, Subject, Subscription} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; import {MatChip, MatChipEvent, MatChipSelectionChange} from './chip'; @@ -104,7 +104,7 @@ export class MatChipListChange { '(keydown)': '_keydown($event)', '[id]': '_uid', }, - providers: [{provide: MatFormFieldControl, useExisting: MatChipList}], + providers: [{provide: MatLegacyFormFieldControl, useExisting: MatChipList}], styleUrls: ['chips.css'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, @@ -112,7 +112,7 @@ export class MatChipListChange { export class MatChipList extends _MatChipListBase implements - MatFormFieldControl, + MatLegacyFormFieldControl, ControlValueAccessor, AfterContentInit, DoCheck, diff --git a/src/material/chips/testing/BUILD.bazel b/src/material/chips/testing/BUILD.bazel index ff8574b59c46..4493a9fd1212 100644 --- a/src/material/chips/testing/BUILD.bazel +++ b/src/material/chips/testing/BUILD.bazel @@ -27,7 +27,7 @@ ng_test_library( "//src/cdk/testing/private", "//src/cdk/testing/testbed", "//src/material/chips", - "//src/material/form-field", + "//src/material/legacy-form-field", "//src/material/icon", "//src/material/icon/testing", "@npm//@angular/platform-browser", diff --git a/src/material/chips/testing/shared.spec.ts b/src/material/chips/testing/shared.spec.ts index fac5a9f9e332..03f2f54f3038 100644 --- a/src/material/chips/testing/shared.spec.ts +++ b/src/material/chips/testing/shared.spec.ts @@ -4,7 +4,7 @@ import {HarnessLoader, parallel, TestKey} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {MatChipsModule} from '@angular/material/chips'; -import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; import {MatIconModule} from '@angular/material/icon'; import {MatIconHarness} from '@angular/material/icon/testing'; import {MatChipListHarness} from './chip-list-harness'; @@ -31,7 +31,7 @@ export function runHarnessTests( beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [chipsModule, MatFormFieldModule, NoopAnimationsModule, iconModule], + imports: [chipsModule, MatLegacyFormFieldModule, NoopAnimationsModule, iconModule], declarations: [ChipsHarnessTest], }).compileComponents(); diff --git a/src/material/config.bzl b/src/material/config.bzl index fceb708aa522..0e6f469dd741 100644 --- a/src/material/config.bzl +++ b/src/material/config.bzl @@ -29,11 +29,18 @@ entryPoints = [ "expansion", "expansion/testing", "form-field", + "form-field/testing", + "form-field/testing/control", + "legacy-form-field", + "legacy-form-field/testing", "grid-list", "grid-list/testing", "icon", "icon/testing", "input", + "input/testing", + "legacy-input", + "legacy-input/testing", "list", "list/testing", "menu", @@ -73,9 +80,6 @@ entryPoints = [ "tooltip/testing", "tree", "tree/testing", - "form-field/testing", - "form-field/testing/control", - "input/testing", "select/testing", ] diff --git a/src/material/datepicker/BUILD.bazel b/src/material/datepicker/BUILD.bazel index 7612ac9db708..16489c2bb528 100644 --- a/src/material/datepicker/BUILD.bazel +++ b/src/material/datepicker/BUILD.bazel @@ -35,8 +35,8 @@ ng_module( "//src/cdk/portal", "//src/material/button", "//src/material/core", - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", "@npm//@angular/animations", "@npm//@angular/common", "@npm//@angular/core", @@ -110,8 +110,8 @@ ng_test_library( "//src/cdk/scrolling", "//src/cdk/testing/private", "//src/material/core", - "//src/material/form-field", - "//src/material/input", + "//src/material/legacy-form-field", + "//src/material/legacy-input", "//src/material/testing", "@npm//@angular/common", "@npm//@angular/forms", diff --git a/src/material/datepicker/date-range-input.spec.ts b/src/material/datepicker/date-range-input.spec.ts index 5a89efd43495..7869f760369f 100644 --- a/src/material/datepicker/date-range-input.spec.ts +++ b/src/material/datepicker/date-range-input.spec.ts @@ -13,8 +13,8 @@ import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {OverlayContainer} from '@angular/cdk/overlay'; import {ErrorStateMatcher, MatNativeDateModule} from '@angular/material/core'; import {MatDatepickerModule} from './datepicker-module'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; +import {MatLegacyInputModule} from '@angular/material/legacy-input'; import {dispatchFakeEvent, dispatchKeyboardEvent} from '../../cdk/testing/private'; import {FocusMonitor} from '@angular/cdk/a11y'; import {BACKSPACE} from '@angular/cdk/keycodes'; @@ -32,8 +32,8 @@ describe('MatDateRangeInput', () => { imports: [ FormsModule, MatDatepickerModule, - MatFormFieldModule, - MatInputModule, + MatLegacyFormFieldModule, + MatLegacyInputModule, NoopAnimationsModule, ReactiveFormsModule, MatNativeDateModule, diff --git a/src/material/datepicker/date-range-input.ts b/src/material/datepicker/date-range-input.ts index 9af6b2d299b2..f2162c377008 100644 --- a/src/material/datepicker/date-range-input.ts +++ b/src/material/datepicker/date-range-input.ts @@ -22,7 +22,11 @@ import { OnChanges, SimpleChanges, } from '@angular/core'; -import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field'; +import { + MatLegacyFormFieldControl, + MatLegacyFormField, + MAT_FORM_FIELD, +} from '@angular/material/legacy-form-field'; import {ThemePalette, DateAdapter} from '@angular/material/core'; import {NgControl, ControlContainer} from '@angular/forms'; import {Subject, merge, Subscription} from 'rxjs'; @@ -62,13 +66,13 @@ let nextUniqueId = 0; changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ - {provide: MatFormFieldControl, useExisting: MatDateRangeInput}, + {provide: MatLegacyFormFieldControl, useExisting: MatDateRangeInput}, {provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput}, ], }) export class MatDateRangeInput implements - MatFormFieldControl>, + MatLegacyFormFieldControl>, MatDatepickerControl, MatDateRangeInputParent, MatDateRangePickerInput, @@ -255,7 +259,7 @@ export class MatDateRangeInput private _elementRef: ElementRef, @Optional() @Self() control: ControlContainer, @Optional() private _dateAdapter: DateAdapter, - @Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField, + @Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatLegacyFormField, ) { if (!_dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); diff --git a/src/material/datepicker/datepicker-actions.spec.ts b/src/material/datepicker/datepicker-actions.spec.ts index e4fd8fb34104..9ec0563b0a14 100644 --- a/src/material/datepicker/datepicker-actions.spec.ts +++ b/src/material/datepicker/datepicker-actions.spec.ts @@ -3,8 +3,8 @@ import {ComponentFixture, TestBed, flush, fakeAsync, tick} from '@angular/core/t import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {MatNativeDateModule} from '@angular/material/core'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; +import {MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; +import {MatLegacyInputModule} from '@angular/material/legacy-input'; import {CommonModule} from '@angular/common'; import {MatDatepickerModule} from './datepicker-module'; import {MatDatepicker} from './datepicker'; @@ -16,8 +16,8 @@ describe('MatDatepickerActions', () => { CommonModule, FormsModule, MatDatepickerModule, - MatFormFieldModule, - MatInputModule, + MatLegacyFormFieldModule, + MatLegacyInputModule, NoopAnimationsModule, ReactiveFormsModule, MatNativeDateModule, diff --git a/src/material/datepicker/datepicker-input.ts b/src/material/datepicker/datepicker-input.ts index fee59cf0148b..17ba8a83a196 100644 --- a/src/material/datepicker/datepicker-input.ts +++ b/src/material/datepicker/datepicker-input.ts @@ -9,8 +9,8 @@ import {Directive, ElementRef, forwardRef, Inject, Input, OnDestroy, Optional} from '@angular/core'; import {NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidatorFn, Validators} from '@angular/forms'; import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats, ThemePalette} from '@angular/material/core'; -import {MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field'; -import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input'; +import {MatLegacyFormField, MAT_FORM_FIELD} from '@angular/material/legacy-form-field'; +import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/legacy-input'; import {Subscription} from 'rxjs'; import {MatDatepickerInputBase, DateFilterFn} from './datepicker-input-base'; import {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base'; @@ -124,7 +124,7 @@ export class MatDatepickerInput elementRef: ElementRef, @Optional() dateAdapter: DateAdapter, @Optional() @Inject(MAT_DATE_FORMATS) dateFormats: MatDateFormats, - @Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField, + @Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatLegacyFormField, ) { super(elementRef, dateAdapter, dateFormats); this._validator = Validators.compose(super._getValidators()); diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index da36cea93bd8..22f449f7f750 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -30,13 +30,13 @@ import { NG_VALIDATORS, } from '@angular/forms'; import {MAT_DATE_LOCALE, MatNativeDateModule, NativeDateModule} from '@angular/material/core'; -import {MatFormField, MatFormFieldModule} from '@angular/material/form-field'; +import {MatLegacyFormField, MatLegacyFormFieldModule} from '@angular/material/legacy-form-field'; import {DEC, JAN, JUL, JUN, SEP} from '../testing'; import {By} from '@angular/platform-browser'; import {_supportsShadowDom} from '@angular/cdk/platform'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {Subject} from 'rxjs'; -import {MatInputModule} from '../input/index'; +import {MatLegacyInputModule} from '../legacy-input/index'; import {MatDatepicker} from './datepicker'; import {MatDatepickerInput} from './datepicker-input'; import {MatDatepickerToggle} from './datepicker-toggle'; @@ -62,8 +62,8 @@ describe('MatDatepicker', () => { imports: [ FormsModule, MatDatepickerModule, - MatFormFieldModule, - MatInputModule, + MatLegacyFormFieldModule, + MatLegacyInputModule, NoopAnimationsModule, ReactiveFormsModule, ...imports, @@ -2597,7 +2597,7 @@ class DatepickerWithCustomIcon {} class FormFieldDatepicker { @ViewChild('d') datepicker: MatDatepicker; @ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput; - @ViewChild(MatFormField) formField: MatFormField; + @ViewChild(MatLegacyFormField) formField: MatLegacyFormField; } @Component({ diff --git a/src/material/form-field/BUILD.bazel b/src/material/form-field/BUILD.bazel index 0a76bfb1e825..d4b54bbd2d6b 100644 --- a/src/material/form-field/BUILD.bazel +++ b/src/material/form-field/BUILD.bazel @@ -14,85 +14,56 @@ ng_module( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), - assets = [ - ":form-field.css", - ":form-field-fill.css", - ":form-field-input.css", - ":form-field-legacy.css", - ":form-field-outline.css", - ":form-field-standard.css", - ] + glob(["**/*.html"]), + assets = [":form_field_scss"] + glob(["**/*.html"]), deps = [ "//src:dev_mode_types", "//src/cdk/bidi", - "//src/cdk/coercion", "//src/cdk/observers", "//src/cdk/platform", "//src/material/core", - "@npm//@angular/animations", - "@npm//@angular/common", - "@npm//@angular/core", "@npm//@angular/forms", - "@npm//@angular/platform-browser", "@npm//rxjs", ], ) sass_library( name = "form_field_scss_lib", - srcs = glob(["**/_*.scss"]), - deps = ["//src/material/core:core_scss_lib"], -) - -sass_binary( - name = "form_field_scss", - src = "form-field.scss", - deps = [ - "//src/cdk:sass_lib", - "//src/material/core:core_scss_lib", - ], -) - -sass_binary( - name = "form_field_fill_scss", - src = "form-field-fill.scss", - deps = [ - "//src/cdk:sass_lib", - "//src/material/core:core_scss_lib", + srcs = [ + "_form-field-theme.scss", + "_mdc-text-field-theme-variable-refresh.scss", ], -) - -sass_binary( - name = "form_field_input_scss", - src = "form-field-input.scss", deps = [ - "//src/cdk:sass_lib", + ":form_field_partials", + "//:mdc_sass_lib", "//src/material/core:core_scss_lib", ], ) sass_binary( - name = "form_field_legacy_scss", - src = "form-field-legacy.scss", + name = "form_field_scss", + src = "form-field.scss", deps = [ - "//src/cdk:sass_lib", + ":form_field_partials", + "//:mdc_sass_lib", + "//src/material:sass_lib", "//src/material/core:core_scss_lib", ], ) -sass_binary( - name = "form_field_outline_scss", - src = "form-field-outline.scss", - deps = [ - "//src/cdk:sass_lib", - "//src/material/core:core_scss_lib", +sass_library( + name = "form_field_partials", + srcs = [ + "_form-field-density.scss", + "_form-field-focus-overlay.scss", + "_form-field-high-contrast.scss", + "_form-field-native-select.scss", + "_form-field-sizing.scss", + "_form-field-subscript.scss", + "_mdc-text-field-structure-overrides.scss", + "_mdc-text-field-textarea-overrides.scss", ], -) - -sass_binary( - name = "form_field_standard_scss", - src = "form-field-standard.scss", deps = [ + "//:mdc_sass_lib", "//src/cdk:sass_lib", "//src/material/core:core_scss_lib", ], diff --git a/src/material-experimental/mdc-form-field/_form-field-density.import.scss b/src/material/form-field/_form-field-density.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-density.import.scss rename to src/material/form-field/_form-field-density.import.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-density.scss b/src/material/form-field/_form-field-density.scss similarity index 98% rename from src/material-experimental/mdc-form-field/_form-field-density.scss rename to src/material/form-field/_form-field-density.scss index 2f75b9942faa..48dbb63b1f01 100644 --- a/src/material-experimental/mdc-form-field/_form-field-density.scss +++ b/src/material/form-field/_form-field-density.scss @@ -1,8 +1,8 @@ @use 'sass:map'; @use 'sass:math'; -@use '@angular/material' as mat; @use '@material/density' as mdc-density; @use '@material/textfield' as mdc-textfield; +@use '../core/theming/theming'; @use './form-field-sizing'; @@ -40,7 +40,7 @@ // specification. In order to support density, we need to adjust the vertical spacing to be // based on the density scale. @mixin private-form-field-density($config-or-theme) { - $density-scale: mat.get-density-config($config-or-theme); + $density-scale: theming.get-density-config($config-or-theme); // Height of the form field that is based on the current density scale. $height: mdc-density.prop-value( $density-config: mdc-textfield.$density-config, diff --git a/src/material-experimental/mdc-form-field/_form-field-focus-overlay.import.scss b/src/material/form-field/_form-field-focus-overlay.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-focus-overlay.import.scss rename to src/material/form-field/_form-field-focus-overlay.import.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-focus-overlay.scss b/src/material/form-field/_form-field-focus-overlay.scss similarity index 95% rename from src/material-experimental/mdc-form-field/_form-field-focus-overlay.scss rename to src/material/form-field/_form-field-focus-overlay.scss index 2d98b5363134..1c53779cfafa 100644 --- a/src/material-experimental/mdc-form-field/_form-field-focus-overlay.scss +++ b/src/material/form-field/_form-field-focus-overlay.scss @@ -1,6 +1,6 @@ -@use '@angular/material' as mat; @use '@material/ripple/functions' as mdc-ripple-functions; @use '@material/textfield' as mdc-textfield; +@use '../core/style/layout-common'; // MDC text-field uses `@material/ripple` in order to show a focus and hover effect for // text-fields. This is unnecessary because the ripples bring in a lot of unnecessary @@ -11,7 +11,7 @@ // uses the exact same logic to compute the colors as in the default MDC text-field ripples. @mixin private-form-field-focus-overlay() { .mat-mdc-form-field-focus-overlay { - @include mat.private-fill; + @include layout-common.fill; opacity: 0; } } diff --git a/src/material-experimental/mdc-form-field/_form-field-high-contrast.scss b/src/material/form-field/_form-field-high-contrast.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-high-contrast.scss rename to src/material/form-field/_form-field-high-contrast.scss diff --git a/src/material/form-field/_form-field-legacy-index.scss b/src/material/form-field/_form-field-legacy-index.scss deleted file mode 100644 index 659211b89b10..000000000000 --- a/src/material/form-field/_form-field-legacy-index.scss +++ /dev/null @@ -1,24 +0,0 @@ -@forward 'form-field-fill-theme' hide $fill-dedupe, fill-color, fill-theme, fill-typography, -private-form-field-fill-density; -@forward 'form-field-fill-theme' as mat-* hide $mat-fill-dedupe, mat-fill-color, -mat-fill-label-floating, mat-fill-theme, mat-fill-typography; -@forward 'form-field-fill-theme' as mat-form-field-* hide mat-form-field-fill-label-floating, -mat-form-field-private-form-field-fill-density; -@forward 'form-field-legacy-theme' hide $legacy-dedupe, legacy-color, legacy-theme, -legacy-typography, private-form-field-legacy-density; -@forward 'form-field-legacy-theme' as mat-* hide $mat-legacy-dedupe, mat-legacy-color, -mat-legacy-label-floating, mat-legacy-label-floating-print, mat-legacy-theme, mat-legacy-typography; -@forward 'form-field-legacy-theme' as mat-form-field-* hide mat-form-field-legacy-label-floating, -mat-form-field-legacy-label-floating-print, mat-form-field-private-form-field-legacy-density; -@forward 'form-field-outline-theme' hide $outline-dedupe, outline-color, outline-theme, -outline-typography, private-form-field-outline-density; -@forward 'form-field-outline-theme' as mat-* hide $mat-outline-dedupe, mat-outline-color, -mat-outline-label-floating, mat-outline-theme, mat-outline-typography; -@forward 'form-field-outline-theme' as mat-form-field-* hide mat-form-field-outline-label-floating, -mat-form-field-private-form-field-outline-density; -@forward 'form-field-standard-theme' as mat-* hide mat-standard-color, mat-standard-theme, -mat-standard-typography; -@forward 'form-field-standard-theme' as mat-form-field-* hide -mat-form-field-private-form-field-standard-density; -@forward 'form-field-theme' hide $dedupe, color, density, theme, typography; -@forward 'form-field-theme' as mat-form-field-* hide mat-form-field-label-floating; diff --git a/src/material-experimental/mdc-form-field/_form-field-native-select.import.scss b/src/material/form-field/_form-field-native-select.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-native-select.import.scss rename to src/material/form-field/_form-field-native-select.import.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-native-select.scss b/src/material/form-field/_form-field-native-select.scss similarity index 95% rename from src/material-experimental/mdc-form-field/_form-field-native-select.scss rename to src/material/form-field/_form-field-native-select.scss index fcef128f44c9..b06205f0401c 100644 --- a/src/material-experimental/mdc-form-field/_form-field-native-select.scss +++ b/src/material/form-field/_form-field-native-select.scss @@ -1,7 +1,8 @@ @use 'sass:map'; @use 'sass:math'; -@use '@angular/material' as mat; @use '@material/theme/theme-color' as mdc-theme-color; +@use '../core/theming/palette'; +@use '../core/mdc-helpers/mdc-helpers'; // Width of the Material Design form-field select arrow. $mat-form-field-select-arrow-width: 10px; @@ -73,7 +74,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt } @mixin private-form-field-native-select-color($config) { - @include mat.private-using-mdc-theme($config) { + @include mdc-helpers.using-mdc-theme($config) { // These values are taken from the MDC select implementation: // https://github.com/material-components/material-components-web/blob/master/packages/mdc-select/_select-theme.scss $dropdown-icon-color: rgba(mdc-theme-color.prop-value(on-surface), 0.54); @@ -87,11 +88,11 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt // reset the color of the options to something dark. @if (map.get($config, is-dark)) { option { - color: mat.$private-dark-primary-text; + color: palette.$dark-primary-text; } option:disabled { - color: mat.$private-dark-disabled-text; + color: palette.$dark-disabled-text; } } } diff --git a/src/material-experimental/mdc-form-field/_form-field-sizing.import.scss b/src/material/form-field/_form-field-sizing.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-sizing.import.scss rename to src/material/form-field/_form-field-sizing.import.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-sizing.scss b/src/material/form-field/_form-field-sizing.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-sizing.scss rename to src/material/form-field/_form-field-sizing.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-subscript.import.scss b/src/material/form-field/_form-field-subscript.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_form-field-subscript.import.scss rename to src/material/form-field/_form-field-subscript.import.scss diff --git a/src/material-experimental/mdc-form-field/_form-field-subscript.scss b/src/material/form-field/_form-field-subscript.scss similarity index 90% rename from src/material-experimental/mdc-form-field/_form-field-subscript.scss rename to src/material/form-field/_form-field-subscript.scss index 9393fd7e96d6..bc11c802e3d6 100644 --- a/src/material-experimental/mdc-form-field/_form-field-subscript.scss +++ b/src/material/form-field/_form-field-subscript.scss @@ -1,10 +1,10 @@ - -@use '@angular/material' as mat; @use '@material/textfield' as mdc-textfield; @use '@material/theme/theme' as mdc-theme; @use '@material/typography' as mdc-typography; @use '@material/textfield/variables' as mdc-textfield-variables; +@use '../core/theming/theming'; +@use '../core/mdc-helpers/mdc-helpers'; @use './form-field-sizing'; @mixin private-form-field-subscript() { @@ -71,12 +71,12 @@ // We need to define our own typography for the subscript because we don't include MDC's // helper text in our MDC based form field @mixin private-form-field-subscript-typography($config-or-theme) { - $config: mat.get-typography-config($config-or-theme); + $config: theming.get-typography-config($config-or-theme); // The subscript wrapper has a minimum height to avoid that the form-field // jumps when hints or errors are displayed. .mat-mdc-form-field-subscript-wrapper, .mat-mdc-form-field-bottom-align::before { - @include mdc-typography.typography(caption, $query: mat.$private-mdc-typography-styles-query); + @include mdc-typography.typography(caption, $query: mdc-helpers.$mdc-typography-styles-query); } } diff --git a/src/material/form-field/_form-field-theme.import.scss b/src/material/form-field/_form-field-theme.import.scss index 36523a2d3eb4..bf828a439cf5 100644 --- a/src/material/form-field/_form-field-theme.import.scss +++ b/src/material/form-field/_form-field-theme.import.scss @@ -1,29 +1,29 @@ -@forward '../core/style/form-common.import'; -@forward 'form-field-fill-theme' as mat-* hide $mat-fill-dedupe, mat-fill-color, mat-fill-theme, -mat-fill-typography; -@forward 'form-field-fill-theme' as mat-form-field-* hide -mat-form-field-private-form-field-fill-density; -@forward 'form-field-legacy-theme' as mat-* hide $mat-legacy-dedupe, mat-legacy-color, -mat-legacy-theme, mat-legacy-typography; -@forward 'form-field-legacy-theme' as mat-form-field-* hide -mat-form-field-private-form-field-legacy-density; -@forward 'form-field-outline-theme' as mat-* hide $mat-outline-dedupe, mat-outline-color, -mat-outline-theme, mat-outline-typography; -@forward 'form-field-outline-theme' as mat-form-field-* hide -mat-form-field-private-form-field-outline-density; -@forward '../core/typography/typography-utils.import'; -@forward 'form-field-standard-theme' as mat-* hide mat-standard-color, mat-standard-theme, -mat-standard-typography; -@forward 'form-field-standard-theme' as mat-form-field-* hide -mat-form-field-private-form-field-standard-density; -@forward 'form-field-theme' hide $dedupe, color, density, theme, typography; -@forward 'form-field-theme' as mat-form-field-* hide mat-form-field-label-floating; +@forward '../../material/core/theming/theming.import'; +@forward 'form-field-sizing'; +@forward 'form-field-native-select' hide private-form-field-native-select, +private-form-field-native-select-color; +@forward 'form-field-native-select' as mat-mdc-* hide $mat-mdc-mat-form-field-select-arrow-height, +$mat-mdc-mat-form-field-select-arrow-width, $mat-mdc-mat-form-field-select-horizontal-end-padding; +@forward 'mdc-text-field-theme-variable-refresh' hide private-text-field-refresh-theme-variables; +@forward 'mdc-text-field-theme-variable-refresh' as mat-mdc-* hide +$mat-mdc-mdc-text-field-background, $mat-mdc-mdc-text-field-bottom-line-hover, +$mat-mdc-mdc-text-field-bottom-line-idle, $mat-mdc-mdc-text-field-disabled-background, +$mat-mdc-mdc-text-field-disabled-border, $mat-mdc-mdc-text-field-disabled-border-border, +$mat-mdc-mdc-text-field-disabled-ink-color, $mat-mdc-mdc-text-field-disabled-label-color, +$mat-mdc-mdc-text-field-disabled-placeholder-ink-color, $mat-mdc-mdc-text-field-focused-label-color, +$mat-mdc-mdc-text-field-ink-color, $mat-mdc-mdc-text-field-label, +$mat-mdc-mdc-text-field-outlined-disabled-border, $mat-mdc-mdc-text-field-outlined-hover-border, +$mat-mdc-mdc-text-field-outlined-idle-border, $mat-mdc-mdc-text-field-placeholder-ink-color; +@forward '../../material/core/style/layout-common.import'; +@forward 'form-field-density' as mat-mdc-*; +@forward 'form-field-subscript' as mat-mdc-*; +@forward 'form-field-focus-overlay' as mat-mdc-*; +@forward '../../cdk/a11y/index.import'; +@forward 'form-field-theme' hide color, density, theme, typography; +@forward 'form-field-theme' as mat-mdc-form-field-* hide mat-mdc-form-field-text-field-color-styles; -@import '../core/theming/palette'; -@import '../core/theming/theming'; -@import '../core/style/form-common'; -@import '../core/typography/typography-utils'; -@import './form-field-fill-theme.scss'; -@import './form-field-legacy-theme.scss'; -@import './form-field-outline-theme.scss'; -@import './form-field-standard-theme.scss'; +@import 'form-field-density'; +@import 'form-field-subscript'; +@import 'form-field-focus-overlay'; +@import 'form-field-native-select'; +@import 'mdc-text-field-theme-variable-refresh'; diff --git a/src/material/form-field/_form-field-theme.scss b/src/material/form-field/_form-field-theme.scss index 772e7168ff54..62c9f99b2c33 100644 --- a/src/material/form-field/_form-field-theme.scss +++ b/src/material/form-field/_form-field-theme.scss @@ -1,254 +1,132 @@ -@use 'sass:map'; -@use 'sass:math'; +@use '@material/textfield' as mdc-textfield; +@use '@material/floating-label' as mdc-floating-label; +@use '@material/notched-outline' as mdc-notched-outline; +@use '@material/line-ripple' as mdc-line-ripple; +@use '@material/theme/theme-color' as mdc-theme-color; +@use '@material/typography/typography' as mdc-typography; + @use '../core/theming/theming'; @use '../core/typography/typography'; -@use '../core/typography/typography-utils'; - -@use './form-field-fill-theme.scss'; -@use './form-field-legacy-theme.scss'; -@use './form-field-outline-theme.scss'; -@use './form-field-standard-theme.scss'; +@use '../core/mdc-helpers/mdc-helpers'; +@use './form-field-density'; +@use './form-field-subscript'; +@use './form-field-focus-overlay'; +@use './form-field-native-select'; +@use './mdc-text-field-theme-variable-refresh'; -// Color styles that apply to all appearances of the form-field. -@mixin color($config-or-theme) { - $config: theming.get-color-config($config-or-theme); - $primary: map.get($config, primary); - $accent: map.get($config, accent); - $warn: map.get($config, warn); - $background: map.get($config, background); - $foreground: map.get($config, foreground); - $is-dark-theme: map.get($config, is-dark); - // Label colors. Required is used for the `*` star shown in the label. - $label-color: - theming.get-color-from-palette($foreground, secondary-text, if($is-dark-theme, 0.7, 0.6)); - $focused-label-color: theming.get-color-from-palette($primary, text); - $required-label-color: theming.get-color-from-palette($accent, text); +// Mixin that overwrites the default MDC text-field color styles to be based on +// the given theme palette. The MDC text-field is styled using `primary` by default. +@mixin _color-styles($palette-name, + $query: mdc-helpers.$mdc-theme-styles-query) { + $orig-focused-label-color: mdc-textfield.$focused-label-color; + mdc-textfield.$focused-label-color: rgba(mdc-theme-color.prop-value($palette-name), 0.87); - // Underline colors. - $underline-color-base: - theming.get-color-from-palette($foreground, divider, if($is-dark-theme, 1, 0.87)); - $underline-color-accent: theming.get-color-from-palette($accent, text); - $underline-color-warn: theming.get-color-from-palette($warn, text); - $underline-focused-color: theming.get-color-from-palette($primary, text); + @include mdc-textfield.caret-color($palette-name, $query); + @include mdc-textfield.line-ripple-color($palette-name, $query); - .mat-form-field-label { - color: $label-color; + .mdc-text-field--focused { + @include mdc-textfield.focused_($query); } - .mat-hint { - color: $label-color; + .mdc-text-field--invalid { + @include mdc-textfield.invalid_($query); } - .mat-form-field.mat-focused .mat-form-field-label { - color: $focused-label-color; - - &.mat-accent { - color: $underline-color-accent; - } - - &.mat-warn { - color: $underline-color-warn; - } + .mdc-text-field--outlined { + @include mdc-textfield.focused-outline-color($palette-name, $query); } - .mat-focused .mat-form-field-required-marker { - color: $required-label-color; - } - - .mat-form-field-ripple { - background-color: $underline-color-base; - } - - .mat-form-field.mat-focused { - .mat-form-field-ripple { - background-color: $underline-focused-color; + mdc-textfield.$focused-label-color: $orig-focused-label-color; +} - &.mat-accent { - background-color: $underline-color-accent; +@mixin color($config-or-theme) { + $config: theming.get-color-config($config-or-theme); + @include mdc-helpers.using-mdc-theme($config) { + @include mdc-text-field-theme-variable-refresh.private-text-field-refresh-theme-variables() { + @include mdc-textfield.without-ripple($query: mdc-helpers.$mdc-theme-styles-query); + @include mdc-floating-label.core-styles($query: mdc-helpers.$mdc-theme-styles-query); + @include mdc-notched-outline.core-styles($query: mdc-helpers.$mdc-theme-styles-query); + @include mdc-line-ripple.core-styles($query: mdc-helpers.$mdc-theme-styles-query); + @include form-field-subscript.private-form-field-subscript-color(); + @include form-field-focus-overlay.private-form-field-focus-overlay-color(); + @include form-field-native-select.private-form-field-native-select-color($config); + + .mat-mdc-form-field.mat-accent { + @include _color-styles(secondary); } - &.mat-warn { - background-color: $underline-color-warn; + .mat-mdc-form-field.mat-warn { + @include _color-styles(error); } - } - } - - .mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid) { - .mat-form-field-infix::after { - color: $underline-focused-color; - } - &.mat-accent .mat-form-field-infix::after { - color: $underline-color-accent; - } - - &.mat-warn .mat-form-field-infix::after { - 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.mat-form-field-invalid { - .mat-form-field-label { - color: $underline-color-warn; - - &.mat-accent, - .mat-form-field-required-marker { - color: $underline-color-warn; + // This fixes an issue where the notch appears to be thicker than the rest of the outline when + // zoomed in on Chrome. The border inconsistency seems to be some kind of rendering artifact + // that arises from a combination of the fact that the notch contains text, while the leading + // and trailing outline do not, combined with the fact that the border is semi-transparent. + // Experimentally, I discovered that adding a transparent left border fixes the inconsistency. + // Note: class name is repeated to achieve sufficient specificity over the various MDC states. + // (hover, focus, etc.) + // TODO(mmalerba): port this fix into MDC + .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field { + &.mat-mdc-form-field .mdc-notched-outline__notch { + border-left: 1px solid transparent; + } } - } - .mat-form-field-ripple, - .mat-form-field-ripple.mat-accent { - background-color: $underline-color-warn; + [dir='rtl'] { + .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field { + &.mat-mdc-form-field .mdc-notched-outline__notch { + border-left: none; + border-right: 1px solid transparent; + } + } + } } } - - .mat-error { - color: $underline-color-warn; - } - - @include form-field-legacy-theme.legacy-color($config); - @include form-field-standard-theme.standard-color($config); - @include form-field-fill-theme.fill-color($config); - @include form-field-outline-theme.outline-color($config); -} - -// 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; - -// Applies a floating label above the form field control itself. -@mixin _label-floating($font-scale, $infix-padding, $infix-margin-top) { - transform: translateY(-$infix-margin-top - $infix-padding + $dedupe) - scale($font-scale); - width: math.div(100%, $font-scale) + $dedupe; - - $dedupe: $dedupe + 0.00001 !global; } @mixin typography($config-or-theme) { - $config: typography.private-typography-to-2014-config( + $config: typography.private-typography-to-2018-config( theming.get-typography-config($config-or-theme)); - // The unit-less line-height from the font config. - $line-height: typography-utils.line-height($config, input); - - // The amount to scale the font for the floating label and subscript. - $subscript-font-scale: 0.75; - // The amount to scale the font for the prefix and suffix icons. - $prefix-suffix-icon-font-scale: 1.5; - - // 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. - // If the line-height is given as a unitless number, coerce it to `em`. - $infix-margin-top: $subscript-font-scale * - typography-utils.private-coerce-unitless-to-em($line-height); - // Font size to use for the label and subscript text. - $subscript-font-size: $subscript-font-scale * 100%; - // Font size to use for the for the prefix and suffix icons. - $prefix-suffix-icon-font-size: $prefix-suffix-icon-font-scale * 100%; - // 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. - $subscript-margin-top: math.div(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. - $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; - - .mat-form-field { - @include typography-utils.typography-level($config, input); - } - - .mat-form-field-wrapper { - padding-bottom: $wrapper-padding-bottom; - } - - .mat-form-field-prefix, - .mat-form-field-suffix { - // Allow icons in a prefix or suffix to adapt to the correct size. - .mat-icon { - font-size: $prefix-suffix-icon-font-size; - line-height: $line-height; + @include mdc-helpers.using-mdc-typography($config) { + @include mdc-textfield.without-ripple($query: mdc-helpers.$mdc-typography-styles-query); + @include mdc-floating-label.core-styles($query: mdc-helpers.$mdc-typography-styles-query); + @include mdc-notched-outline.core-styles($query: mdc-helpers.$mdc-typography-styles-query); + @include mdc-line-ripple.core-styles($query: mdc-helpers.$mdc-typography-styles-query); + @include form-field-subscript.private-form-field-subscript-typography($config); + + // MDC uses `subtitle1` for the input value, placeholder and floating label. The spec + // shows `body1` for text fields though, so we manually override the typography. + // Note: Form controls inherit the typography from the parent form field. + .mat-mdc-form-field, + .mat-mdc-floating-label { + @include mdc-typography.typography(body1, $query: mdc-helpers.$mdc-typography-styles-query); } - // Allow icon buttons in a prefix or suffix to adapt to the correct size. - .mat-icon-button { - height: $prefix-suffix-icon-font-scale * 1em; - width: $prefix-suffix-icon-font-scale * 1em; - - .mat-icon { - height: typography-utils.private-coerce-unitless-to-em($line-height); - line-height: $line-height; + // Above, we updated the floating label to use the `body1` typography level. The MDC notched + // outline overrides this accidentally (only when the label floats) to a `rem`-based value. + // This results in different label widths when floated/docked and ultimately breaks the notch + // width as it has been measured in the docked state (where `body1` is applied). We try to + // unset these styles set by the `mdc-notched-outline`: + // https://github.com/material-components/material-components-web/blob/master/packages/mdc-notched-outline/_mixins.scss#L272-L292. + .mat-mdc-form-field .mdc-text-field--outlined { + // For the non-upgraded notch label (i.e. when rendered on the server), also + // use the correct `body1` typography level. + .mdc-floating-label--float-above { + font-size: calc(#{ + mdc-typography.get-size(body1)} * var(--mat-mdc-form-field-floating-label-scale, 0.75)); + } + .mdc-notched-outline--upgraded .mdc-floating-label--float-above { + font-size: mdc-typography.get-size(body1); } } } - - .mat-form-field-infix { - padding: $infix-padding 0; - // Throws off the baseline if we do it as a real margin, so we do it as a border instead. - border-top: $infix-margin-top solid transparent; - } - - .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 _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 _label-floating( - $subscript-font-scale, $infix-padding, $infix-margin-top); - } - } - - .mat-form-field-label-wrapper { - top: -$infix-margin-top; - padding-top: $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 { - font-size: $subscript-font-size; - 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% - #{math.div($wrapper-padding-bottom, $subscript-font-scale)}); - } - - @include form-field-legacy-theme.legacy-typography($config); - @include form-field-standard-theme.standard-typography($config); - @include form-field-fill-theme.fill-typography($config); - @include form-field-outline-theme.outline-typography($config); } @mixin density($config-or-theme) { $density-scale: theming.get-density-config($config-or-theme); - @include form-field-legacy-theme.private-form-field-legacy-density($density-scale); - @include form-field-standard-theme.private-form-field-standard-density($density-scale); - @include form-field-fill-theme.private-form-field-fill-density($density-scale); - @include form-field-outline-theme.private-form-field-outline-density($density-scale); + @include form-field-density.private-form-field-density($density-scale); } @mixin theme($theme-or-color-config) { diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.import.scss b/src/material/form-field/_mdc-text-field-structure-overrides.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.import.scss rename to src/material/form-field/_mdc-text-field-structure-overrides.import.scss diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss b/src/material/form-field/_mdc-text-field-structure-overrides.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss rename to src/material/form-field/_mdc-text-field-structure-overrides.scss diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.import.scss b/src/material/form-field/_mdc-text-field-textarea-overrides.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.import.scss rename to src/material/form-field/_mdc-text-field-textarea-overrides.import.scss diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss b/src/material/form-field/_mdc-text-field-textarea-overrides.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-textarea-overrides.scss rename to src/material/form-field/_mdc-text-field-textarea-overrides.scss diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.import.scss b/src/material/form-field/_mdc-text-field-theme-variable-refresh.import.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.import.scss rename to src/material/form-field/_mdc-text-field-theme-variable-refresh.import.scss diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss b/src/material/form-field/_mdc-text-field-theme-variable-refresh.scss similarity index 100% rename from src/material-experimental/mdc-form-field/_mdc-text-field-theme-variable-refresh.scss rename to src/material/form-field/_mdc-text-field-theme-variable-refresh.scss diff --git a/src/material-experimental/mdc-form-field/directives/error.ts b/src/material/form-field/directives/error.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/error.ts rename to src/material/form-field/directives/error.ts diff --git a/src/material-experimental/mdc-form-field/directives/floating-label.ts b/src/material/form-field/directives/floating-label.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/floating-label.ts rename to src/material/form-field/directives/floating-label.ts diff --git a/src/material-experimental/mdc-form-field/directives/hint.ts b/src/material/form-field/directives/hint.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/hint.ts rename to src/material/form-field/directives/hint.ts diff --git a/src/material-experimental/mdc-form-field/directives/label.ts b/src/material/form-field/directives/label.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/label.ts rename to src/material/form-field/directives/label.ts diff --git a/src/material-experimental/mdc-form-field/directives/line-ripple.ts b/src/material/form-field/directives/line-ripple.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/line-ripple.ts rename to src/material/form-field/directives/line-ripple.ts diff --git a/src/material-experimental/mdc-form-field/directives/notched-outline.html b/src/material/form-field/directives/notched-outline.html similarity index 100% rename from src/material-experimental/mdc-form-field/directives/notched-outline.html rename to src/material/form-field/directives/notched-outline.html diff --git a/src/material-experimental/mdc-form-field/directives/notched-outline.ts b/src/material/form-field/directives/notched-outline.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/notched-outline.ts rename to src/material/form-field/directives/notched-outline.ts diff --git a/src/material-experimental/mdc-form-field/directives/prefix.ts b/src/material/form-field/directives/prefix.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/prefix.ts rename to src/material/form-field/directives/prefix.ts diff --git a/src/material-experimental/mdc-form-field/directives/suffix.ts b/src/material/form-field/directives/suffix.ts similarity index 100% rename from src/material-experimental/mdc-form-field/directives/suffix.ts rename to src/material/form-field/directives/suffix.ts diff --git a/src/material/form-field/form-field-module.ts b/src/material/form-field/form-field-module.ts deleted file mode 100644 index 122f7d6caea5..000000000000 --- a/src/material/form-field/form-field-module.ts +++ /dev/null @@ -1,35 +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 {ObserversModule} from '@angular/cdk/observers'; -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {MatCommonModule} from '@angular/material/core'; -import {MatError} from './error'; -import {MatFormField} from './form-field'; -import {MatHint} from './hint'; -import {MatLabel} from './label'; -import {MatPlaceholder} from './placeholder'; -import {MatPrefix} from './prefix'; -import {MatSuffix} from './suffix'; - -@NgModule({ - declarations: [MatError, MatFormField, MatHint, MatLabel, MatPlaceholder, MatPrefix, MatSuffix], - imports: [CommonModule, MatCommonModule, ObserversModule], - exports: [ - MatCommonModule, - MatError, - MatFormField, - MatHint, - MatLabel, - MatPlaceholder, - MatPrefix, - MatSuffix, - ], -}) -export class MatFormFieldModule {} diff --git a/src/material/form-field/form-field.html b/src/material/form-field/form-field.html index 5141b1d545f9..12a3f43e6585 100644 --- a/src/material/form-field/form-field.html +++ b/src/material/form-field/form-field.html @@ -1,92 +1,96 @@ -
-
+ + - -
-
-
-
-
-
-
-
-
-
-
+ *Note*: We add aria-owns as a workaround for an issue in JAWS & NVDA where the label isn't + read if it comes before the control in the DOM. + --> + +
-
- +
+
+
+
+ + +
-
- - - - -
-
-
- -
+
+
+ +
-
- -
{{hintLabel}}
- -
- -
+
+ {{hintLabel}} + +
+
diff --git a/src/material/form-field/form-field.md b/src/material/form-field/form-field.md index ae33e1f9b2ac..c6178afc29f5 100644 --- a/src/material/form-field/form-field.md +++ b/src/material/form-field/form-field.md @@ -29,7 +29,7 @@ There are a couple differences in behavior to be aware of between the different We recommend that text prefix and suffixes in the `fill` and `outline` appearances only be used in conjunction with the `floatLabel="always"` option. This is because the resting label and the input value do not have the same alignment, and it is therefore impossible to align the prefix or suffix -in a way that looks good when compared with both. In the `standard` and `legacy` appearances, the +in a way that looks good when compared with both. In the `standard` and `legacy` appearances, the resting label and input value align, so this isn't an issue. We plan to improve support for text prefix and suffixes in the future so they can be used without `floatLabel="always"`. diff --git a/src/material/form-field/form-field.scss b/src/material/form-field/form-field.scss index 9455b53098d4..5bd78a2d12c6 100644 --- a/src/material/form-field/form-field.scss +++ b/src/material/form-field/form-field.scss @@ -1,21 +1,51 @@ -@use '@angular/cdk'; - -@use '../core/style/variables'; - -// Styles that apply to all appearances of the form-field. - -// Min amount of space between start and end hint. -$hint-min-space: 1em !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. -$default-infix-width: 180px !default; - - -.mat-form-field { - display: inline-block; - position: relative; +@use '@material/textfield' as mdc-textfield; +@use '@material/floating-label' as mdc-floating-label; +@use '@material/notched-outline' as mdc-notched-outline; +@use '@material/line-ripple' as mdc-line-ripple; +@use '../core/mdc-helpers/mdc-helpers'; +@use '../core/style/vendor-prefixes'; +@use './form-field-sizing'; +@use './form-field-subscript'; +@use './form-field-focus-overlay'; +@use './form-field-high-contrast'; +@use './form-field-native-select'; +@use './mdc-text-field-textarea-overrides'; +@use './mdc-text-field-structure-overrides'; + +// Base styles for MDC text-field, notched-outline, floating label and line-ripple. +@include mdc-helpers.disable-mdc-fallback-declarations { + @include mdc-textfield.without-ripple( + $query: mdc-helpers.$mdc-base-styles-without-animation-query); + @include mdc-floating-label.core-styles( + $query: mdc-helpers.$mdc-base-styles-without-animation-query); + @include mdc-notched-outline.core-styles( + $query: mdc-helpers.$mdc-base-styles-without-animation-query); + @include mdc-line-ripple.core-styles( + $query: mdc-helpers.$mdc-base-styles-without-animation-query); +} + +// MDC text-field overwrites. +@include mdc-text-field-textarea-overrides.private-text-field-textarea-overrides(); +@include mdc-text-field-structure-overrides.private-text-field-structure-overrides(); + +// Include the subscript, focus-overlay, native select and high-contrast styles. +@include form-field-subscript.private-form-field-subscript(); +@include form-field-focus-overlay.private-form-field-focus-overlay(); +@include form-field-native-select.private-form-field-native-select(); +@include form-field-high-contrast.private-form-field-high-contrast(); + +// Host element of the form-field. It contains the mdc-text-field wrapper +// and the subscript wrapper. +.mat-mdc-form-field { + // The scale to use for the form-field's label when its in the floating position. + --mat-mdc-form-field-floating-label-scale: 0.75; + display: inline-flex; + // This container contains the text-field and the subscript. The subscript + // should be displayed below the text-field. Hence the column direction. + flex-direction: column; + // This allows the form-field to shrink down when used inside flex or grid layouts. + min-width: 0; // To avoid problems with text-align. text-align: left; @@ -24,230 +54,97 @@ $default-infix-width: 180px !default; } } -// Global wrapper. We need to apply margin to the element for spacing, but -// cannot apply it to the host element directly. -.mat-form-field-wrapper { - position: relative; -} - -// We use a flex layout to baseline align the prefix and suffix elements. -// The underline is outside of it so it can cover all of the elements under this flex container. -.mat-form-field-flex { +// Container that contains the prefixes, infix and suffixes. These elements should +// be aligned vertically in the baseline and in a single row. +.mat-mdc-form-field-flex { display: inline-flex; align-items: baseline; box-sizing: border-box; width: 100%; } -.mat-form-field-prefix, -.mat-form-field-suffix { - white-space: nowrap; - flex: none; - position: relative; -} - -.mat-form-field-infix { - display: block; - position: relative; - flex: auto; - min-width: 0; - width: $default-infix-width; - - // In high contrast mode IE/Edge will render all of the borders, even if they're transparent. - // Since we can't remove the border altogether or replace it with a margin, because it'll throw - // off the baseline, and we can't use a base64-encoded 1x1 transparent image because of CSP, - // we work around it by setting a linear gradient that goes from `transparent` to `transparent`. - @include cdk.high-contrast(active, off) { - border-image: linear-gradient(transparent, transparent); - } -} - -// Used to hide the label overflow on IE, since IE doesn't take transform into account when -// determining overflow. -.mat-form-field-label-wrapper { - position: absolute; - left: 0; - box-sizing: content-box; +// The MDC text-field should stretch to the width of the host `` element. +// This allows developers to control the width without needing custom CSS overrides. +.mat-mdc-text-field-wrapper { width: 100%; - height: 100%; - overflow: hidden; - pointer-events: none; // We shouldn't catch mouse events (let them through). - - [dir='rtl'] & { - // Usually this isn't necessary since the element is 100% wide, but - // when we've got a `select` node, we need to set a `max-width` on it. - left: auto; - right: 0; - } } -// The label itself. This is invisible unless it is. The logic to show it is -// basically `empty || (float && (!empty || focused))`. Float is dependent on the -// `floatingPlaceholder` property. -.mat-form-field-label { - // The label is after the form field control, but needs to be aligned top-left of the infix
. - position: absolute; - left: 0; - - font: inherit; - pointer-events: none; // We shouldn't catch mouse events (let them through). - - // Put ellipsis text overflow. - width: 100%; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - - transform-origin: 0 0; - transition: transform variables.$swift-ease-out-duration - variables.$swift-ease-out-timing-function, color variables.$swift-ease-out-duration - variables.$swift-ease-out-timing-function, width variables.$swift-ease-out-duration - variables.$swift-ease-out-timing-function; - - // Hide the label initially, and only show it when it's floating or the control is empty. - display: none; - - [dir='rtl'] & { - transform-origin: 100% 0; - left: auto; - right: 0; - } - - .mat-form-field-disabled & { - @include cdk.high-contrast(active, off) { - color: GrayText; - } - } -} - -.mat-form-field-empty.mat-form-field-label, -.mat-form-field-can-float.mat-form-field-should-float .mat-form-field-label { - display: block; -} - -// Pseudo-class for Chrome and Safari auto-fill to move the label to the floating position. -// This is necessary because these browsers do not actually fire any events when a form auto-fill is -// occurring. Once the autofill is committed, a change event happen and the regular mat-form-field -// classes take over to fulfill this behaviour. -// @breaking-change 8.0.0 will rely on AutofillMonitor instead. -.mat-form-field-autofill-control:-webkit-autofill + .mat-form-field-label-wrapper - .mat-form-field-label { - // The form field will be considered empty if it is autofilled, and therefore the label will - // be shown. Therefore we need to override it to hidden... - display: none; - - // ...and re-show it only if it's able to float. - .mat-form-field-can-float & { - display: block; - transition: none; - } -} - -// 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). -// stylelint-disable material/no-prefixes -.mat-input-server:focus + .mat-form-field-label-wrapper .mat-form-field-label, -.mat-input-server[placeholder]:not(:placeholder-shown) + .mat-form-field-label-wrapper - .mat-form-field-label { - display: none; - - .mat-form-field-can-float & { - display: block; - } -} -// stylelint-enable material/no-prefixes - -// Disable the label animation when the control is not empty (this prevents label -// animating up when the value is set programmatically). -.mat-form-field-label:not(.mat-form-field-empty) { - transition: none; +// Vertically center icons. +.mat-mdc-form-field-icon-prefix, +.mat-mdc-form-field-icon-suffix { + align-self: center; + // The line-height can cause the prefix/suffix container to be taller than the actual icons, + // breaking the vertical centering. To prevent this we set the line-height to 0. + line-height: 0; } -.mat-form-field-underline { - position: absolute; - width: 100%; - // Need this so that the underline doesn't block the hover effect. - pointer-events: none; - // We transform the height slightly higher to fix inconsistent underline height for some DPIs. - // Without this we observed that at zoom levels between 50% and 100% some form-field underlines - // would disappear. The issue appears to be related to fractional pixels since (e.g. underlines - // with their top at x.6 would disappear, but ones with there top at x.7 were fine). The exact - // fractions that caused problems seemed to depend on the screen resolution and zoom level. We - // experimentally discovered that adding a very slight scale factor fixes the issue. - // `scale3d` must be used rather than `scaleY`, as `scaleY` causes a jitter effect in Chrome. - transform: scale3d(1, 1.0001, 1); +// The prefix/suffix needs a little extra padding between the icon and the infix. Because we need to +// support arbitrary height input elements, we use a different DOM structure for prefix and suffix +// icons, and therefore can't rely on MDC for these styles. +.mat-mdc-form-field-icon-prefix, +[dir='rtl'] .mat-mdc-form-field-icon-suffix { + padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0; } - -.mat-form-field-ripple { - position: absolute; - left: 0; - width: 100%; - transform-origin: 50%; - transform: scaleX(0.5); - opacity: 0; - transition: background-color variables.$swift-ease-in-duration - variables.$swift-ease-in-timing-function; +.mat-mdc-form-field-icon-suffix, +[dir='rtl'] .mat-mdc-form-field-icon-prefix { + padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding; } -.mat-form-field.mat-focused, -.mat-form-field.mat-form-field-invalid { - .mat-form-field-ripple { - opacity: 1; - // Note that we transition this to `none`, rather than `scaleX(1)`, because the scaling - // seems to cause some rendering artifacts on adjacent form fields (see #21612). - transform: none; - transition: transform 300ms variables.$swift-ease-out-timing-function, - opacity 100ms variables.$swift-ease-out-timing-function, - background-color 300ms variables.$swift-ease-out-timing-function; +.mat-mdc-form-field-icon-prefix, +.mat-mdc-form-field-icon-suffix { + & > .mat-icon { + padding: 12px; + // It's common for apps to apply `box-sizing: border-box` + // globally which will break the alignment. + box-sizing: content-box; } } -// 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 -} - -// Scale down icons in the label and hint to be the same size as the text. -.mat-form-field-subscript-wrapper, -.mat-form-field-label-wrapper { +// Scale down icons in the subscript and floating label to be the same size +// as the text. +.mat-mdc-form-field-subscript-wrapper, +.mat-mdc-form-field label { .mat-icon { width: 1em; height: 1em; font-size: inherit; - vertical-align: baseline; } } -// Clears the floats on the hints. This is necessary for the hint animation to work. -.mat-form-field-hint-wrapper { - display: flex; -} - -// Spacer used to make sure start and end hints have enough space between them. -.mat-form-field-hint-spacer { - flex: 1 0 $hint-min-space; -} - -// Single error message displayed beneath the form field underline. -.mat-error { - display: block; -} - -// Element that can used to reliably align content in relation to the form field control. -.mat-form-field-control-wrapper { +// Infix that contains the projected content (usually an input or a textarea). We ensure +// that the projected form-field control and content can stretch as needed, but we also +// apply a default infix width to make the form-field's look natural. +.mat-mdc-form-field-infix { + flex: auto; + min-width: 0; + width: form-field-sizing.$mat-form-field-default-infix-width; + // Needed so that the floating label does not overlap with prefixes or suffixes. position: relative; + box-sizing: border-box; } -.mat-form-field-hint-end { - order: 1; +// In the form-field theme, we add a 1px left margin to the notch to fix a rendering bug in Chrome. +// Here we apply negative margin to offset the effect on the layout and a clip-path to ensure the +// left border is completely hidden. (Though the border is transparent, it still creates a triangle +// shaped artifact where it meets the top and bottom borders.) +.mat-mdc-form-field .mdc-notched-outline__notch { + margin-left: -1px; + @include vendor-prefixes.clip-path(inset(-9em -999em -9em 1px)); + + [dir='rtl'] & { + margin-left: 0; + margin-right: -1px; + @include vendor-prefixes.clip-path(inset(-9em 1px -9em -999em)); + } } -.mat-form-field._mat-animation-noopable { - .mat-form-field-label, - .mat-form-field-ripple { - transition: none; +// In order to make it possible for developers to disable animations for form-fields, +// we only activate the animation styles if animations are not explicitly disabled. +.mat-mdc-form-field:not(.mat-form-field-no-animations) { + @include mdc-helpers.disable-mdc-fallback-declarations { + @include mdc-textfield.without-ripple($query: animation); + @include mdc-floating-label.core-styles($query: animation); + @include mdc-notched-outline.core-styles($query: animation); + @include mdc-line-ripple.core-styles($query: animation); } } diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index aba6b78a23fc..b8ceb36f9528 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -5,9 +5,8 @@ * 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 {Directionality} from '@angular/cdk/bidi'; -import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; +import {Platform} from '@angular/cdk/platform'; import { AfterContentChecked, AfterContentInit, @@ -22,52 +21,42 @@ import { InjectionToken, Input, NgZone, + OnDestroy, Optional, QueryList, ViewChild, ViewEncapsulation, - OnDestroy, } from '@angular/core'; -import {CanColor, mixinColor, ThemePalette} from '@angular/material/core'; -import {fromEvent, merge, Subject} from 'rxjs'; -import {startWith, take, takeUntil} from 'rxjs/operators'; -import {MAT_ERROR, MatError} from './error'; +import {AbstractControlDirective} from '@angular/forms'; +import {ThemePalette} from '@angular/material/core'; +import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; +import {merge, Subject} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; +import {MAT_ERROR, MatError} from './directives/error'; +import {MatFormFieldFloatingLabel} from './directives/floating-label'; +import {MatHint} from './directives/hint'; +import {MatLabel} from './directives/label'; +import {MatFormFieldLineRipple} from './directives/line-ripple'; +import {MatFormFieldNotchedOutline} from './directives/notched-outline'; +import {MAT_PREFIX, MatPrefix} from './directives/prefix'; +import {MAT_SUFFIX, MatSuffix} from './directives/suffix'; +import {DOCUMENT} from '@angular/common'; +import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {matFormFieldAnimations} from './form-field-animations'; import {MatFormFieldControl} from './form-field-control'; import { getMatFormFieldDuplicatedHintError, getMatFormFieldMissingControlError, - getMatFormFieldPlaceholderConflictError, } from './form-field-errors'; -import {_MAT_HINT, MatHint} from './hint'; -import {MatLabel} from './label'; -import {MatPlaceholder} from './placeholder'; -import {MAT_PREFIX, MatPrefix} from './prefix'; -import {MAT_SUFFIX, MatSuffix} from './suffix'; -import {Platform} from '@angular/cdk/platform'; -import {AbstractControlDirective} from '@angular/forms'; -import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; -let nextUniqueId = 0; -const floatingLabelScale = 0.75; -const outlineGapPadding = 5; - -/** - * Boilerplate for applying mixins to MatFormField. - * @docs-private - */ -const _MatFormFieldBase = mixinColor( - class { - constructor(public _elementRef: ElementRef) {} - }, - 'primary', -); +/** Type for the available floatLabel values. */ +export type FloatLabelType = 'always' | 'auto'; /** Possible appearance styles for the form field. */ -export type MatFormFieldAppearance = 'legacy' | 'standard' | 'fill' | 'outline'; +export type MatFormFieldAppearance = 'fill' | 'outline'; -/** Possible values for the "floatLabel" form field input. */ -export type FloatLabelType = 'always' | 'never' | 'auto'; +/** Behaviors for how the subscript height is set. */ +export type SubscriptSizing = 'fixed' | 'dynamic'; /** * Represents the default options for the form field that can be configured @@ -85,8 +74,17 @@ export interface MatFormFieldDefaultOptions { * `never`, or `auto` (only when necessary). */ floatLabel?: FloatLabelType; + /** Whether the form field should reserve space for one line by default. */ + subscriptSizing?: SubscriptSizing; } +/** + * Injection token that can be used to inject an instances of `MatFormField`. It serves + * as alternative token to the actual `MatFormField` class which would cause unnecessary + * retention of the `MatFormField` class and its component metadata. + */ +export const MAT_FORM_FIELD = new InjectionToken('MatFormField'); + /** * Injection token that can be used to configure the * default options for all form field within an app. @@ -95,44 +93,60 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken('MatFormField'); +const DEFAULT_FLOAT_LABEL: FloatLabelType = 'auto'; + +/** Default way that the subscript element height is set. */ +const DEFAULT_SUBSCRIPT_SIZING: SubscriptSizing = 'fixed'; + +/** + * Default transform for docked floating labels in a MDC text-field. This value has been + * extracted from the MDC text-field styles because we programmatically modify the docked + * label transform, but do not want to accidentally discard the default label transform. + */ +const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`; + +/** + * Horizontal padding in pixels used by the MDC for the wrapper containing infix. + * This value is extracted from MDC's Sass variables. See `$padding-horizontal`. + */ +const WRAPPER_HORIZONTAL_PADDING = 16; /** Container for form controls that applies Material Design styling and behavior. */ @Component({ selector: 'mat-form-field', exportAs: 'matFormField', - templateUrl: 'form-field.html', - // MatInput is a directive and can't have styles, so we need to include its styles here - // in form-field-input.css. 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', - 'form-field-fill.css', - 'form-field-input.css', - 'form-field-legacy.css', - 'form-field-outline.css', - 'form-field-standard.css', - ], + templateUrl: './form-field.html', + styleUrls: ['./form-field.css'], animations: [matFormFieldAnimations.transitionMessages], host: { - 'class': 'mat-form-field', - '[class.mat-form-field-appearance-standard]': 'appearance == "standard"', - '[class.mat-form-field-appearance-fill]': 'appearance == "fill"', - '[class.mat-form-field-appearance-outline]': 'appearance == "outline"', - '[class.mat-form-field-appearance-legacy]': 'appearance == "legacy"', + 'class': 'mat-mdc-form-field', + '[class.mat-mdc-form-field-label-always-float]': '_shouldAlwaysFloat()', + '[class.mat-mdc-form-field-has-icon-prefix]': '_hasIconPrefix', + '[class.mat-mdc-form-field-has-icon-suffix]': '_hasIconSuffix', + + // Note that these classes reuse the same names as the non-MDC version, because they can be + // considered a public API since custom form controls may use them to style themselves. + // See https://github.com/angular/components/pull/20502#discussion_r486124901. '[class.mat-form-field-invalid]': '_control.errorState', - '[class.mat-form-field-can-float]': '_canLabelFloat()', - '[class.mat-form-field-should-float]': '_shouldLabelFloat()', - '[class.mat-form-field-has-label]': '_hasFloatingLabel()', - '[class.mat-form-field-hide-placeholder]': '_hideControlPlaceholder()', '[class.mat-form-field-disabled]': '_control.disabled', '[class.mat-form-field-autofilled]': '_control.autofilled', + '[class.mat-form-field-no-animations]': '_animationMode === "NoopAnimations"', + '[class.mat-form-field-appearance-fill]': 'appearance == "fill"', + '[class.mat-form-field-appearance-outline]': 'appearance == "outline"', + '[class.mat-form-field-hide-placeholder]': '_hasFloatingLabel() && !_shouldLabelFloat()', '[class.mat-focused]': '_control.focused', + '[class.mat-primary]': 'color !== "accent" && color !== "warn"', + '[class.mat-accent]': 'color === "accent"', + '[class.mat-warn]': 'color === "warn"', '[class.ng-untouched]': '_shouldForward("untouched")', '[class.ng-touched]': '_shouldForward("touched")', '[class.ng-pristine]': '_shouldForward("pristine")', @@ -140,43 +154,28 @@ export const MAT_FORM_FIELD = new InjectionToken('MatFormField'); '[class.ng-valid]': '_shouldForward("valid")', '[class.ng-invalid]': '_shouldForward("invalid")', '[class.ng-pending]': '_shouldForward("pending")', - '[class._mat-animation-noopable]': '!_animationsEnabled', }, - inputs: ['color'], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [{provide: MAT_FORM_FIELD, useExisting: MatFormField}], }) export class MatFormField - extends _MatFormFieldBase - implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy, CanColor + implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy { - /** - * Whether the outline gap needs to be calculated - * immediately on the next change detection run. - */ - private _outlineGapCalculationNeededImmediately = false; - - /** Whether the outline gap needs to be calculated next time the zone has stabilized. */ - private _outlineGapCalculationNeededOnStable = false; - - private readonly _destroyed = new Subject(); - - /** The form field appearance style. */ - @Input() - get appearance(): MatFormFieldAppearance { - return this._appearance; - } - set appearance(value: MatFormFieldAppearance) { - const oldValue = this._appearance; - - this._appearance = value || this._defaults?.appearance || 'legacy'; - - if (this._appearance === 'outline' && oldValue !== value) { - this._outlineGapCalculationNeededOnStable = true; - } - } - _appearance: MatFormFieldAppearance; + @ViewChild('textField') _textField: ElementRef; + @ViewChild('iconPrefixContainer') _iconPrefixContainer: ElementRef; + @ViewChild('textPrefixContainer') _textPrefixContainer: ElementRef; + @ViewChild(MatFormFieldFloatingLabel) _floatingLabel: MatFormFieldFloatingLabel | undefined; + @ViewChild(MatFormFieldNotchedOutline) _notchedOutline: MatFormFieldNotchedOutline | undefined; + @ViewChild(MatFormFieldLineRipple) _lineRipple: MatFormFieldLineRipple | undefined; + + @ContentChild(MatLabel) _labelChildNonStatic: MatLabel | undefined; + @ContentChild(MatLabel, {static: true}) _labelChildStatic: MatLabel | undefined; + @ContentChild(MatFormFieldControl) _formFieldControl: MatFormFieldControl; + @ContentChildren(MAT_PREFIX, {descendants: true}) _prefixChildren: QueryList; + @ContentChildren(MAT_SUFFIX, {descendants: true}) _suffixChildren: QueryList; + @ContentChildren(MAT_ERROR, {descendants: true}) _errorChildren: QueryList; + @ContentChildren(MatHint, {descendants: true}) _hintChildren: QueryList; /** Whether the required marker should be hidden. */ @Input() @@ -188,21 +187,66 @@ export class MatFormField } private _hideRequiredMarker = false; - /** Override for the logic that disables the label animation in certain cases. */ - private _showAlwaysAnimate = false; + /** The color palette for the form field. */ + @Input() color: ThemePalette = 'primary'; - /** Whether the floating label should always float or not. */ - _shouldAlwaysFloat(): boolean { - return this.floatLabel === 'always' && !this._showAlwaysAnimate; + /** Whether the label should always float or float as the user types. */ + @Input() + get floatLabel(): FloatLabelType { + return this._floatLabel || this._defaults?.floatLabel || DEFAULT_FLOAT_LABEL; } + set floatLabel(value: FloatLabelType) { + if (value !== this._floatLabel) { + this._floatLabel = value; + // For backwards compatibility. Custom form field controls or directives might set + // the "floatLabel" input and expect the form field view to be updated automatically. + // e.g. autocomplete trigger. Ideally we'd get rid of this and the consumers would just + // emit the "stateChanges" observable. TODO(devversion): consider removing. + this._changeDetectorRef.markForCheck(); + } + } + private _floatLabel: FloatLabelType; - /** Whether the label can float or not. */ - _canLabelFloat(): boolean { - return this.floatLabel !== 'never'; + /** The form field appearance style. */ + @Input() + get appearance(): MatFormFieldAppearance { + return this._appearance; + } + set appearance(value: MatFormFieldAppearance) { + const oldValue = this._appearance; + const newAppearance = value || this._defaults?.appearance || DEFAULT_APPEARANCE; + if (typeof ngDevMode === 'undefined' || ngDevMode) { + if (newAppearance !== 'fill' && newAppearance !== 'outline') { + throw new Error( + `MatFormField: Invalid appearance "${newAppearance}", valid values are "fill" or "outline".`, + ); + } + } + this._appearance = newAppearance; + if (this._appearance === 'outline' && this._appearance !== oldValue) { + this._refreshOutlineNotchWidth(); + + // If the appearance has been switched to `outline`, the label offset needs to be updated. + // The update can happen once the view has been re-checked, but not immediately because + // the view has not been updated and the notched-outline floating label is not present. + this._needsOutlineLabelOffsetUpdateOnStable = true; + } } + private _appearance: MatFormFieldAppearance = DEFAULT_APPEARANCE; - /** State of the mat-hint and mat-error animations. */ - _subscriptAnimationState: string = ''; + /** + * Whether the form field should reserve space for one line of hint/error text (default) + * or to have the spacing grow from 0px as needed based on the size of the hint/error content. + * Note that when using dynamic sizing, layout shifts will occur when hint/error text changes. + */ + @Input() + get subscriptSizing(): SubscriptSizing { + return this._subscriptSizing || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING; + } + set subscriptSizing(value: SubscriptSizing) { + this._subscriptSizing = value || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING; + } + private _subscriptSizing: SubscriptSizing | null = null; /** Text for the form field hint. */ @Input() @@ -215,86 +259,104 @@ export class MatFormField } private _hintLabel = ''; - // Unique id for the hint label. - readonly _hintLabelId: string = `mat-hint-${nextUniqueId++}`; + _hasIconPrefix = false; + _hasTextPrefix = false; + _hasIconSuffix = false; + _hasTextSuffix = false; - // Unique id for the label element. - readonly _labelId = `mat-form-field-label-${nextUniqueId++}`; + // Unique id for the internal form field label. + readonly _labelId = `mat-mdc-form-field-label-${nextUniqueId++}`; - /** - * 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.appearance !== 'legacy' && this._floatLabel === 'never' ? 'auto' : this._floatLabel; - } - set floatLabel(value: FloatLabelType) { - if (value !== this._floatLabel) { - this._floatLabel = value || this._getDefaultFloatLabelState(); - this._changeDetectorRef.markForCheck(); - } - } - private _floatLabel: FloatLabelType; + // Unique id for the hint label. + readonly _hintLabelId = `mat-mdc-hint-${nextUniqueId++}`; - /** Whether the Angular animations are enabled. */ - _animationsEnabled: boolean; + /** State of the mat-hint and mat-error animations. */ + _subscriptAnimationState = ''; - @ViewChild('connectionContainer', {static: true}) _connectionContainerRef: ElementRef; - @ViewChild('inputContainer') _inputContainerRef: ElementRef; - @ViewChild('label') private _label: ElementRef; + /** Width of the label element (at scale=1). */ + _labelWidth = 0; - @ContentChild(MatFormFieldControl) _controlNonStatic: MatFormFieldControl; - @ContentChild(MatFormFieldControl, {static: true}) _controlStatic: MatFormFieldControl; - get _control() { - // TODO(crisbeto): we need this workaround in order to support both Ivy and ViewEngine. - // We should clean this up once Ivy is the default renderer. - return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic; + /** Gets the current form field control */ + get _control(): MatFormFieldControl { + return this._explicitFormFieldControl || this._formFieldControl; } set _control(value) { this._explicitFormFieldControl = value; } - private _explicitFormFieldControl: MatFormFieldControl; - @ContentChild(MatLabel) _labelChildNonStatic: MatLabel; - @ContentChild(MatLabel, {static: true}) _labelChildStatic: MatLabel; - @ContentChild(MatPlaceholder) _placeholderChild: MatPlaceholder; - - @ContentChildren(MAT_ERROR, {descendants: true}) _errorChildren: QueryList; - @ContentChildren(_MAT_HINT, {descendants: true}) _hintChildren: QueryList; - @ContentChildren(MAT_PREFIX, {descendants: true}) _prefixChildren: QueryList; - @ContentChildren(MAT_SUFFIX, {descendants: true}) _suffixChildren: QueryList; + private _destroyed = new Subject(); + private _isFocused: boolean | null = null; + private _explicitFormFieldControl: MatFormFieldControl; + private _needsOutlineLabelOffsetUpdateOnStable = false; constructor( - elementRef: ElementRef, + private _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _dir: Directionality, + private _ngZone: NgZone, + private _dir: Directionality, + private _platform: Platform, @Optional() @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) - private _defaults: MatFormFieldDefaultOptions, - private _platform: Platform, - private _ngZone: NgZone, - @Optional() @Inject(ANIMATION_MODULE_TYPE) _animationMode: string, + private _defaults?: MatFormFieldDefaultOptions, + @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string, + @Inject(DOCUMENT) private _document?: any, ) { - super(elementRef); - - this.floatLabel = this._getDefaultFloatLabelState(); - this._animationsEnabled = _animationMode !== 'NoopAnimations'; - - // Set the default through here so we invoke the setter on the first run. - this.appearance = _defaults?.appearance || 'legacy'; if (_defaults) { - this._hideRequiredMarker = Boolean(_defaults.hideRequiredMarker); + if (_defaults.appearance) { + this.appearance = _defaults.appearance; + } + this._hideRequiredMarker = Boolean(_defaults?.hideRequiredMarker); if (_defaults.color) { - this.color = this.defaultColor = _defaults.color; + this.color = _defaults.color; } } } + ngAfterViewInit() { + // Initial focus state sync. This happens rarely, but we want to account for + // it in case the form field control has "focused" set to true on init. + this._updateFocusState(); + // Initial notch width update. This is needed in case the text-field label floats + // on initialization, and renders inside of the notched outline. + this._refreshOutlineNotchWidth(); + // Make sure fonts are loaded before calculating the width. + // zone.js currently doesn't patch the FontFaceSet API so two calls to + // _refreshOutlineNotchWidth is needed for this to work properly in async tests. + // Furthermore if the font takes a long time to load we want the outline notch to be close + // to the correct width from the start then correct itself when the fonts load. + if (this._document?.fonts?.ready) { + this._document.fonts.ready.then(() => { + this._refreshOutlineNotchWidth(); + this._changeDetectorRef.markForCheck(); + }); + } else { + // FontFaceSet is not supported in IE + setTimeout(() => this._refreshOutlineNotchWidth(), 100); + } + // Enable animations now. This ensures we don't animate on initial render. + this._subscriptAnimationState = 'enter'; + // Because the above changes a value used in the template after it was checked, we need + // to trigger CD or the change might not be reflected if there is no other CD scheduled. + this._changeDetectorRef.detectChanges(); + } + + ngAfterContentInit() { + this._assertFormFieldControl(); + this._initializeControl(); + this._initializeSubscript(); + this._initializePrefixAndSuffix(); + this._initializeOutlineLabelOffsetSubscriptions(); + } + + ngAfterContentChecked() { + this._assertFormFieldControl(); + } + + ngOnDestroy() { + this._destroyed.next(); + this._destroyed.complete(); + } + /** * Gets the id of the label element. If no label is present, returns `null`. */ @@ -303,25 +365,41 @@ export class MatFormField } /** - * Gets an ElementRef for the element that a overlay attached to the form field should be - * positioned relative to. + * Gets an ElementRef for the element that a overlay attached to the form field + * should be positioned relative to. */ getConnectedOverlayOrigin(): ElementRef { - return this._connectionContainerRef || this._elementRef; + return this._textField || this._elementRef; } - ngAfterContentInit() { - this._validateControlChild(); + /** Animates the placeholder up and locks it in position. */ + _animateAndLockLabel(): void { + // This is for backwards compatibility only. Consumers of the form field might use + // this method. e.g. the autocomplete trigger. This method has been added to the non-MDC + // form field because setting "floatLabel" to "always" caused the label to float without + // animation. This is different in MDC where the label always animates, so this method + // is no longer necessary. There doesn't seem any benefit in adding logic to allow changing + // the floating label state without animations. The non-MDC implementation was inconsistent + // because it always animates if "floatLabel" is set away from "always". + // TODO(devversion): consider removing this method when releasing the MDC form field. + if (this._hasFloatingLabel()) { + this.floatLabel = 'always'; + } + } + /** Initializes the registered form field control. */ + private _initializeControl() { const control = this._control; if (control.controlType) { - this._elementRef.nativeElement.classList.add(`mat-form-field-type-${control.controlType}`); + this._elementRef.nativeElement.classList.add( + `mat-mdc-form-field-type-${control.controlType}`, + ); } // Subscribe to changes in the child control state in order to update the form field UI. - control.stateChanges.pipe(startWith(null)).subscribe(() => { - this._validatePlaceholders(); + control.stateChanges.subscribe(() => { + this._updateFocusState(); this._syncDescribedByIds(); this._changeDetectorRef.markForCheck(); }); @@ -332,102 +410,143 @@ export class MatFormField .pipe(takeUntil(this._destroyed)) .subscribe(() => this._changeDetectorRef.markForCheck()); } + } - // Note that we have to run outside of the `NgZone` explicitly, - // in order to avoid throwing users into an infinite loop - // if `zone-patch-rxjs` is included. - this._ngZone.runOutsideAngular(() => { - this._ngZone.onStable.pipe(takeUntil(this._destroyed)).subscribe(() => { - if (this._outlineGapCalculationNeededOnStable) { - this.updateOutlineGap(); - } - }); - }); + private _checkPrefixAndSuffixTypes() { + this._hasIconPrefix = !!this._prefixChildren.find(p => !p._isText); + this._hasTextPrefix = !!this._prefixChildren.find(p => p._isText); + this._hasIconSuffix = !!this._suffixChildren.find(s => !s._isText); + this._hasTextSuffix = !!this._suffixChildren.find(s => s._isText); + } - // Run change detection and update the outline if the suffix or prefix changes. + /** Initializes the prefix and suffix containers. */ + private _initializePrefixAndSuffix() { + this._checkPrefixAndSuffixTypes(); + // Mark the form field as dirty whenever the prefix or suffix children change. This + // is necessary because we conditionally display the prefix/suffix containers based + // on whether there is projected content. merge(this._prefixChildren.changes, this._suffixChildren.changes).subscribe(() => { - this._outlineGapCalculationNeededOnStable = true; + this._checkPrefixAndSuffixTypes(); this._changeDetectorRef.markForCheck(); }); + } + /** + * Initializes the subscript by validating hints and synchronizing "aria-describedby" ids + * with the custom form field control. Also subscribes to hint and error changes in order + * to be able to validate and synchronize ids on change. + */ + private _initializeSubscript() { // Re-validate when the number of hints changes. - this._hintChildren.changes.pipe(startWith(null)).subscribe(() => { + this._hintChildren.changes.subscribe(() => { this._processHints(); this._changeDetectorRef.markForCheck(); }); // Update the aria-described by when the number of errors changes. - this._errorChildren.changes.pipe(startWith(null)).subscribe(() => { + this._errorChildren.changes.subscribe(() => { this._syncDescribedByIds(); this._changeDetectorRef.markForCheck(); }); - if (this._dir) { - this._dir.change.pipe(takeUntil(this._destroyed)).subscribe(() => { - if (typeof requestAnimationFrame === 'function') { - this._ngZone.runOutsideAngular(() => { - requestAnimationFrame(() => this.updateOutlineGap()); - }); - } else { - this.updateOutlineGap(); - } - }); - } + // Initial mat-hint validation and subscript describedByIds sync. + this._validateHints(); + this._syncDescribedByIds(); } - ngAfterContentChecked() { - this._validateControlChild(); - if (this._outlineGapCalculationNeededImmediately) { - this.updateOutlineGap(); + /** Throws an error if the form field's control is missing. */ + private _assertFormFieldControl() { + if (!this._control && (typeof ngDevMode === 'undefined' || ngDevMode)) { + throw getMatFormFieldMissingControlError(); } } - ngAfterViewInit() { - // Avoid animations on load. - this._subscriptAnimationState = 'enter'; - this._changeDetectorRef.detectChanges(); - } + private _updateFocusState() { + // Usually the MDC foundation would call "activateFocus" and "deactivateFocus" whenever + // certain DOM events are emitted. This is not possible in our implementation of the + // form field because we support abstract form field controls which are not necessarily + // of type input, nor do we have a reference to a native form field control element. Instead + // we handle the focus by checking if the abstract form field control focused state changes. + if (this._control.focused && !this._isFocused) { + this._isFocused = true; + this._lineRipple?.activate(); + } else if (!this._control.focused && (this._isFocused || this._isFocused === null)) { + this._isFocused = false; + this._lineRipple?.deactivate(); + } - ngOnDestroy() { - this._destroyed.next(); - this._destroyed.complete(); + this._textField?.nativeElement.classList.toggle( + 'mdc-text-field--focused', + this._control.focused, + ); } /** - * Determines whether a class from the AbstractControlDirective - * should be forwarded to the host element. + * The floating label in the docked state needs to account for prefixes. The horizontal offset + * is calculated whenever the appearance changes to `outline`, the prefixes change, or when the + * form field is added to the DOM. This method sets up all subscriptions which are needed to + * trigger the label offset update. In general, we want to avoid performing measurements often, + * so we rely on the `NgZone` as indicator when the offset should be recalculated, instead of + * checking every change detection cycle. */ - _shouldForward(prop: keyof AbstractControlDirective): boolean { - const control = this._control ? this._control.ngControl : null; - return control && control[prop]; - } + private _initializeOutlineLabelOffsetSubscriptions() { + // Whenever the prefix changes, schedule an update of the label offset. + this._prefixChildren.changes.subscribe( + () => (this._needsOutlineLabelOffsetUpdateOnStable = true), + ); + + // Note that we have to run outside of the `NgZone` explicitly, in order to avoid + // throwing users into an infinite loop if `zone-patch-rxjs` is included. + this._ngZone.runOutsideAngular(() => { + this._ngZone.onStable.pipe(takeUntil(this._destroyed)).subscribe(() => { + if (this._needsOutlineLabelOffsetUpdateOnStable) { + this._needsOutlineLabelOffsetUpdateOnStable = false; + this._updateOutlineLabelOffset(); + } + }); + }); - _hasPlaceholder() { - return !!((this._control && this._control.placeholder) || this._placeholderChild); + this._dir.change + .pipe(takeUntil(this._destroyed)) + .subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true)); } - _hasLabel() { - return !!(this._labelChildNonStatic || this._labelChildStatic); + /** Whether the floating label should always float or not. */ + _shouldAlwaysFloat() { + return this.floatLabel === 'always'; } - _shouldLabelFloat() { - return ( - this._canLabelFloat() && - ((this._control && this._control.shouldLabelFloat) || this._shouldAlwaysFloat()) - ); + _hasOutline() { + return this.appearance === 'outline'; } - _hideControlPlaceholder() { - // 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()) - ); + /** + * Whether the label should display in the infix. Labels in the outline appearance are + * displayed as part of the notched-outline and are horizontally offset to account for + * form field prefix content. This won't work in server side rendering since we cannot + * measure the width of the prefix container. To make the docked label appear as if the + * right offset has been calculated, we forcibly render the label inside the infix. Since + * the label is part of the infix, the label cannot overflow the prefix content. + */ + _forceDisplayInfixLabel() { + return !this._platform.isBrowser && this._prefixChildren.length && !this._shouldLabelFloat(); } _hasFloatingLabel() { - // In the legacy appearance the placeholder is promoted to a label if no label is given. - return this._hasLabel() || (this.appearance === 'legacy' && this._hasPlaceholder()); + return !!this._labelChildNonStatic || !!this._labelChildStatic; + } + + _shouldLabelFloat() { + return this._control.shouldLabelFloat || this._shouldAlwaysFloat(); + } + + /** + * Determines whether a class from the AbstractControlDirective + * should be forwarded to the host element. + */ + _shouldForward(prop: keyof AbstractControlDirective): boolean { + const control = this._control ? this._control.ngControl : null; + return control && control[prop]; } /** Determines whether to display hints or errors. */ @@ -437,38 +556,12 @@ export class MatFormField : 'hint'; } - /** Animates the placeholder up and locks it in position. */ - _animateAndLockLabel(): void { - if (this._hasFloatingLabel() && this._canLabelFloat()) { - // If animations are disabled, we shouldn't go in here, - // because the `transitionend` will never fire. - if (this._animationsEnabled && this._label) { - this._showAlwaysAnimate = true; - - fromEvent(this._label.nativeElement, 'transitionend') - .pipe(take(1)) - .subscribe(() => { - this._showAlwaysAnimate = false; - }); - } - - this.floatLabel = 'always'; - this._changeDetectorRef.markForCheck(); - } - } - - /** - * Ensure that there is only one placeholder (either `placeholder` attribute on the child control - * or child element with the `mat-placeholder` directive). - */ - private _validatePlaceholders() { - if ( - this._control.placeholder && - this._placeholderChild && - (typeof ngDevMode === 'undefined' || ngDevMode) - ) { - throw getMatFormFieldPlaceholderConflictError(); + /** Refreshes the width of the outline-notch, if present. */ + _refreshOutlineNotchWidth() { + if (!this._hasOutline() || !this._floatingLabel) { + return; } + this._labelWidth = this._floatingLabel.getWidth(); } /** Does any extra processing that is required when handling the hints. */ @@ -478,8 +571,10 @@ export class MatFormField } /** - * Ensure that there is a maximum of one of each `` alignment specified, with the - * attribute being considered as `align="start"`. + * Ensure that there is a maximum of one of each "mat-hint" alignment specified. The hint + * label specified set through the input is being considered as "start" aligned. + * + * This method is a noop if Angular runs in production mode. */ private _validateHints() { if (this._hintChildren && (typeof ngDevMode === 'undefined' || ngDevMode)) { @@ -501,11 +596,6 @@ export class MatFormField } } - /** Gets the default float label state. */ - private _getDefaultFloatLabelState(): FloatLabelType { - return (this._defaults && this._defaults.floatLabel) || 'auto'; - } - /** * Sets the list of element IDs that describe the child control. This allows the control to update * its `aria-describedby` attribute accordingly. @@ -547,106 +637,63 @@ export class MatFormField } } - /** Throws an error if the form field's control is missing. */ - protected _validateControlChild() { - if (!this._control && (typeof ngDevMode === 'undefined' || ngDevMode)) { - throw getMatFormFieldMissingControlError(); - } - } - /** - * Updates the width and position of the gap in the outline. Only relevant for the outline - * appearance. + * Updates the horizontal offset of the label in the outline appearance. In the outline + * appearance, the notched-outline and label are not relative to the infix container because + * the outline intends to surround prefixes, suffixes and the infix. This means that the + * floating label by default overlaps prefixes in the docked state. To avoid this, we need to + * horizontally offset the label by the width of the prefix container. The MDC text-field does + * not need to do this because they use a fixed width for prefixes. Hence, they can simply + * incorporate the horizontal offset into their default text-field styles. */ - updateOutlineGap() { - const labelEl = this._label ? this._label.nativeElement : null; - const container = this._connectionContainerRef.nativeElement; - const outlineStartSelector = '.mat-form-field-outline-start'; - const outlineGapSelector = '.mat-form-field-outline-gap'; - - // getBoundingClientRect isn't available on the server. - if (this.appearance !== 'outline' || !this._platform.isBrowser) { + private _updateOutlineLabelOffset() { + if (!this._platform.isBrowser || !this._hasOutline() || !this._floatingLabel) { return; } - - // If there is no content, set the gap elements to zero. - if (!labelEl || !labelEl.children.length || !labelEl.textContent!.trim()) { - const gapElements = container.querySelectorAll( - `${outlineStartSelector}, ${outlineGapSelector}`, - ); - for (let i = 0; i < gapElements.length; i++) { - gapElements[i].style.width = '0'; - } + const floatingLabel = this._floatingLabel.element; + // If no prefix is displayed, reset the outline label offset from potential + // previous label offset updates. + if (!(this._iconPrefixContainer || this._textPrefixContainer)) { + floatingLabel.style.transform = ''; return; } - - // If the element is not present in the DOM, the outline gap will need to be calculated - // the next time it is checked and in the DOM. - if (!this._isAttachedToDOM()) { - this._outlineGapCalculationNeededImmediately = true; + // If the form field is not attached to the DOM yet (e.g. in a tab), we defer + // the label offset update until the zone stabilizes. + if (!this._isAttachedToDom()) { + this._needsOutlineLabelOffsetUpdateOnStable = true; return; } - - let startWidth = 0; - let gapWidth = 0; - - const startEls = container.querySelectorAll(outlineStartSelector); - const gapEls = container.querySelectorAll(outlineGapSelector); - - if (this._label && this._label.nativeElement.children.length) { - const containerRect = container.getBoundingClientRect(); - - // If the container's width and height are zero, it means that the element is - // invisible and we can't calculate the outline gap. Mark the element as needing - // to be checked the next time the zone stabilizes. We can't do this immediately - // on the next change detection, because even if the element becomes visible, - // the `ClientRect` won't be recalculated immediately. We reset the - // `_outlineGapCalculationNeededImmediately` flag some we don't run the checks twice. - if (containerRect.width === 0 && containerRect.height === 0) { - this._outlineGapCalculationNeededOnStable = true; - this._outlineGapCalculationNeededImmediately = false; - return; - } - - const containerStart = this._getStartEnd(containerRect); - const labelChildren = labelEl.children; - const labelStart = this._getStartEnd(labelChildren[0].getBoundingClientRect()); - let labelWidth = 0; - - for (let i = 0; i < labelChildren.length; i++) { - labelWidth += (labelChildren[i] as HTMLElement).offsetWidth; - } - startWidth = Math.abs(labelStart - containerStart) - outlineGapPadding; - gapWidth = labelWidth > 0 ? labelWidth * floatingLabelScale + outlineGapPadding * 2 : 0; - } - - for (let i = 0; i < startEls.length; i++) { - startEls[i].style.width = `${startWidth}px`; - } - for (let i = 0; i < gapEls.length; i++) { - gapEls[i].style.width = `${gapWidth}px`; - } - - this._outlineGapCalculationNeededOnStable = this._outlineGapCalculationNeededImmediately = - false; - } - - /** Gets the start end of the rect considering the current directionality. */ - private _getStartEnd(rect: ClientRect): number { - return this._dir && this._dir.value === 'rtl' ? rect.right : rect.left; + const iconPrefixContainer = this._iconPrefixContainer?.nativeElement; + const textPrefixContainer = this._textPrefixContainer?.nativeElement; + const iconPrefixContainerWidth = iconPrefixContainer?.getBoundingClientRect().width ?? 0; + const textPrefixContainerWidth = textPrefixContainer?.getBoundingClientRect().width ?? 0; + // If the directionality is RTL, the x-axis transform needs to be inverted. This + // is because `transformX` does not change based on the page directionality. + const labelHorizontalOffset = + (this._dir.value === 'rtl' ? -1 : 1) * + // If there's an icon prefix, we subtract the default horizontal padding as we + // reset the horizontal padding in CSS too. + ((iconPrefixContainer ? iconPrefixContainerWidth - WRAPPER_HORIZONTAL_PADDING : 0) + + textPrefixContainerWidth); + + // Update the translateX of the floating label to account for the prefix container, + // but allow the CSS to override this setting via a CSS variable when the label is + // floating. + floatingLabel.style.transform = `var( + --mat-mdc-form-field-label-transform, + ${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset}px + )`; } /** Checks whether the form field is attached to the DOM. */ - private _isAttachedToDOM(): boolean { + private _isAttachedToDom(): boolean { const element: HTMLElement = this._elementRef.nativeElement; - if (element.getRootNode) { const rootNode = element.getRootNode(); // If the element is inside the DOM the root node will be either the document // or the closest shadow root, otherwise it'll be the element itself. return rootNode && rootNode !== element; } - // Otherwise fall back to checking if it's in the document. This doesn't account for // shadow DOM, however browser that support shadow DOM should support `getRootNode` as well. return document.documentElement!.contains(element); diff --git a/src/material-experimental/mdc-form-field/module.ts b/src/material/form-field/module.ts similarity index 94% rename from src/material-experimental/mdc-form-field/module.ts rename to src/material/form-field/module.ts index c6dc3136ef6a..30be5a262bc1 100644 --- a/src/material-experimental/mdc-form-field/module.ts +++ b/src/material/form-field/module.ts @@ -9,7 +9,7 @@ import {ObserversModule} from '@angular/cdk/observers'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {MatCommonModule} from '@angular/material-experimental/mdc-core'; +import {MatCommonModule} from '@angular/material/core'; import {MatError} from './directives/error'; import {MatFormFieldFloatingLabel} from './directives/floating-label'; import {MatHint} from './directives/hint'; diff --git a/src/material/form-field/prefix.ts b/src/material/form-field/prefix.ts deleted file mode 100644 index bc8af2c2a3a1..000000000000 --- a/src/material/form-field/prefix.ts +++ /dev/null @@ -1,23 +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 {Directive, InjectionToken} from '@angular/core'; - -/** - * Injection token that can be used to reference instances of `MatPrefix`. It serves as - * alternative token to the actual `MatPrefix` class which could cause unnecessary - * retention of the class and its directive metadata. - */ -export const MAT_PREFIX = new InjectionToken('MatPrefix'); - -/** Prefix to be placed in front of the form field. */ -@Directive({ - selector: '[matPrefix]', - providers: [{provide: MAT_PREFIX, useExisting: MatPrefix}], -}) -export class MatPrefix {} diff --git a/src/material/form-field/public-api.ts b/src/material/form-field/public-api.ts index b50b3d471bf7..f7abc0b02d83 100644 --- a/src/material/form-field/public-api.ts +++ b/src/material/form-field/public-api.ts @@ -6,14 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -export * from './form-field-module'; -export * from './error'; +export * from './directives/label'; +export * from './directives/error'; +export * from './directives/hint'; +export * from './directives/prefix'; +export * from './directives/suffix'; export * from './form-field'; -export {MatFormFieldControl} from './form-field-control'; +export * from './module'; +export * from './form-field-control'; export * from './form-field-errors'; -export * from './hint'; -export * from './placeholder'; -export * from './prefix'; -export * from './suffix'; -export * from './label'; export * from './form-field-animations'; diff --git a/src/material/form-field/suffix.ts b/src/material/form-field/suffix.ts deleted file mode 100644 index edc35cbf1838..000000000000 --- a/src/material/form-field/suffix.ts +++ /dev/null @@ -1,23 +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 {Directive, InjectionToken} from '@angular/core'; - -/** - * Injection token that can be used to reference instances of `MatSuffix`. It serves as - * alternative token to the actual `MatSuffix` class which could cause unnecessary - * retention of the class and its directive metadata. - */ -export const MAT_SUFFIX = new InjectionToken('MatSuffix'); - -/** Suffix to be placed at the end of the form field. */ -@Directive({ - selector: '[matSuffix]', - providers: [{provide: MAT_SUFFIX, useExisting: MatSuffix}], -}) -export class MatSuffix {} diff --git a/src/material/form-field/testing/BUILD.bazel b/src/material/form-field/testing/BUILD.bazel index f18f14caca5a..bbf5544b5d2e 100644 --- a/src/material/form-field/testing/BUILD.bazel +++ b/src/material/form-field/testing/BUILD.bazel @@ -10,10 +10,10 @@ ts_library( ), deps = [ "//src/cdk/testing", + "//src/material/input/testing", + "//src/material-experimental/mdc-select/testing", "//src/material/datepicker/testing", "//src/material/form-field/testing/control", - "//src/material/input/testing", - "//src/material/select/testing", ], ) @@ -42,21 +42,23 @@ ng_test_library( exclude = ["shared.spec.ts"], ), deps = [ - ":harness_tests_lib", ":testing", - "//src/material/autocomplete", + ":harness_tests_lib", + "//src/material-experimental/mdc-autocomplete", "//src/material/core", - "//src/material/datepicker", - "//src/material/datepicker/testing", "//src/material/form-field", "//src/material/input", "//src/material/input/testing", - "//src/material/select", - "//src/material/select/testing", + "//src/material-experimental/mdc-select", + "//src/material-experimental/mdc-select/testing", + "//src/material/datepicker", + "//src/material/datepicker/testing", ], ) ng_web_test_suite( name = "unit_tests", - deps = [":unit_tests_lib"], + deps = [ + ":unit_tests_lib", + ], ) diff --git a/src/material/form-field/testing/form-field-harness.spec.ts b/src/material/form-field/testing/form-field-harness.spec.ts index af03e6249db0..a0528d7c0218 100644 --- a/src/material/form-field/testing/form-field-harness.spec.ts +++ b/src/material/form-field/testing/form-field-harness.spec.ts @@ -1,20 +1,19 @@ -import {MatAutocompleteModule} from '@angular/material/autocomplete'; -import {MatNativeDateModule} from '@angular/material/core'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; +import {MatAutocompleteModule} from '@angular/material-experimental/mdc-autocomplete'; +import {MatInputHarness} from '@angular/material/input/testing'; +import {MatSelectModule} from '@angular/material-experimental/mdc-select'; +import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testing'; +import {runHarnessTests} from './shared.spec'; import {MatDatepickerModule} from '@angular/material/datepicker'; +import {MatNativeDateModule} from '@angular/material/core'; import { MatDatepickerInputHarness, MatDateRangeInputHarness, } from '@angular/material/datepicker/testing'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MatInputHarness} from '@angular/material/input/testing'; -import {MatSelectModule} from '@angular/material/select'; -import {MatSelectHarness} from '@angular/material/select/testing'; - import {MatFormFieldHarness} from './form-field-harness'; -import {runHarnessTests} from './shared.spec'; -describe('Non-MDC-based MatFormFieldHarness', () => { +describe('MDC-based MatFormFieldHarness', () => { runHarnessTests( [ MatFormFieldModule, @@ -30,7 +29,7 @@ describe('Non-MDC-based MatFormFieldHarness', () => { selectHarness: MatSelectHarness, datepickerInputHarness: MatDatepickerInputHarness, dateRangeInputHarness: MatDateRangeInputHarness, - isMdcImplementation: false, + isMdcImplementation: true, }, ); }); diff --git a/src/material/form-field/testing/form-field-harness.ts b/src/material/form-field/testing/form-field-harness.ts index ecfdd8a64e5e..804b50ff6221 100644 --- a/src/material/form-field/testing/form-field-harness.ts +++ b/src/material/form-field/testing/form-field-harness.ts @@ -15,23 +15,15 @@ import { parallel, TestElement, } from '@angular/cdk/testing'; +import {MatInputHarness} from '@angular/material/input/testing'; +import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control'; +import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testing'; import { MatDatepickerInputHarness, MatDateRangeInputHarness, } from '@angular/material/datepicker/testing'; -import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control'; -import {MatInputHarness} from '@angular/material/input/testing'; -import {MatSelectHarness} from '@angular/material/select/testing'; import {FormFieldHarnessFilters} from './form-field-harness-filters'; -// TODO(devversion): support support chip list harness -/** Possible harnesses of controls which can be bound to a form-field. */ -export type FormFieldControlHarness = - | MatInputHarness - | MatSelectHarness - | MatDatepickerInputHarness - | MatDateRangeInputHarness; - export abstract class _MatFormFieldHarnessBase< ControlHarness extends MatFormFieldControlHarness, > extends ComponentHarness { @@ -210,18 +202,29 @@ export abstract class _MatFormFieldHarnessBase< } } -/** Harness for interacting with a standard Material form-field's in tests. */ +// TODO(devversion): support support chip list harness +/** Possible harnesses of controls which can be bound to a form-field. */ +export type FormFieldControlHarness = + | MatInputHarness + | MatSelectHarness + | MatDatepickerInputHarness + | MatDateRangeInputHarness; + +/** Harness for interacting with a MDC-based form-field's in tests. */ export class MatFormFieldHarness extends _MatFormFieldHarnessBase { - static hostSelector = '.mat-form-field'; + static hostSelector = '.mat-mdc-form-field'; /** - * Gets a `HarnessPredicate` that can be used to search for a `MatFormFieldHarness` that meets - * certain criteria. + * Gets a `HarnessPredicate` that can be used to search for a form field with specific + * attributes. * @param options Options for filtering which form field instances are considered a match. * @return a `HarnessPredicate` configured with the given options. */ - static with(options: FormFieldHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(MatFormFieldHarness, options) + static with( + this: ComponentHarnessConstructor, + options: FormFieldHarnessFilters = {}, + ): HarnessPredicate { + return new HarnessPredicate(this, options) .addOption('floatingLabelText', options.floatingLabelText, async (harness, text) => HarnessPredicate.stringMatches(await harness.getLabel(), text), ) @@ -232,44 +235,34 @@ export class MatFormFieldHarness extends _MatFormFieldHarnessBase { - const hostClasses = await (await this.host()).getAttribute('class'); - if (hostClasses !== null) { - const appearanceMatch = hostClasses.match( - /mat-form-field-appearance-(legacy|standard|fill|outline)(?:$| )/, - ); - if (appearanceMatch) { - return appearanceMatch[1] as 'legacy' | 'standard' | 'fill' | 'outline'; - } + async getAppearance(): Promise<'fill' | 'outline'> { + const textFieldEl = await this._mdcTextField(); + if (await textFieldEl.hasClass('mdc-text-field--outlined')) { + return 'outline'; } - throw Error('Could not determine appearance of form-field.'); + return 'fill'; } /** Whether the form-field has a label. */ async hasLabel(): Promise { - return (await this.host()).hasClass('mat-form-field-has-label'); + return (await this._label()) !== null; } /** Whether the label is currently floating. */ async isLabelFloating(): Promise { - const host = await this.host(); - const [hasLabel, shouldFloat] = await parallel(() => [ - this.hasLabel(), - host.hasClass('mat-form-field-should-float'), - ]); - // If there is no label, the label conceptually can never float. The `should-float` class - // is just always set regardless of whether the label is displayed or not. - return hasLabel && shouldFloat; + const labelEl = await this._label(); + return labelEl !== null ? await labelEl.hasClass('mdc-floating-label--float-above') : false; } } diff --git a/src/material/form-field/testing/public-api.ts b/src/material/form-field/testing/public-api.ts index 63b240a0298a..5e9ec384a6a1 100644 --- a/src/material/form-field/testing/public-api.ts +++ b/src/material/form-field/testing/public-api.ts @@ -11,5 +11,5 @@ // need to import the base form-field control harness through a separate entry-point. export {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control'; -export * from './form-field-harness'; export * from './form-field-harness-filters'; +export * from './form-field-harness'; diff --git a/src/material/legacy-core/theming/_all-theme.scss b/src/material/legacy-core/theming/_all-theme.scss index 1240358ba867..61f6c136ae9a 100644 --- a/src/material/legacy-core/theming/_all-theme.scss +++ b/src/material/legacy-core/theming/_all-theme.scss @@ -2,6 +2,8 @@ @use '../../legacy-card/card-theme'; @use '../../legacy-progress-bar/progress-bar-theme'; @use '../../legacy-tooltip/tooltip-theme'; +@use '../../legacy-input/input-theme'; +@use '../../legacy-form-field/form-field-theme'; // Create a theme. @mixin all-legacy-component-themes($theme-or-color-config) { @@ -10,6 +12,8 @@ @include card-theme.theme($theme-or-color-config); @include progress-bar-theme.theme($theme-or-color-config); @include tooltip-theme.theme($theme-or-color-config); + @include input-theme.theme($theme-or-color-config); + @include form-field-theme.theme($theme-or-color-config); } } diff --git a/src/material/legacy-core/typography/_all-typography.scss b/src/material/legacy-core/typography/_all-typography.scss index b201bcacefb1..c38d1aa852b8 100644 --- a/src/material/legacy-core/typography/_all-typography.scss +++ b/src/material/legacy-core/typography/_all-typography.scss @@ -3,6 +3,8 @@ @use '../../legacy-card/card-theme'; @use '../../legacy-progress-bar/progress-bar-theme'; @use '../../legacy-tooltip/tooltip-theme'; +@use '../../legacy-input/input-theme'; +@use '../../legacy-form-field/form-field-theme'; // Includes all of the typographic styles. @mixin all-legacy-component-typographies($config-or-theme: null) { @@ -23,6 +25,8 @@ @include card-theme.typography($config); @include progress-bar-theme.typography($config); @include tooltip-theme.typography($config); + @include input-theme.typography($config); + @include form-field-theme.typography($config); } // @deprecated Use `all-legacy-component-typographies`. diff --git a/src/material/legacy-form-field/BUILD.bazel b/src/material/legacy-form-field/BUILD.bazel new file mode 100644 index 000000000000..9323fc88c3b0 --- /dev/null +++ b/src/material/legacy-form-field/BUILD.bazel @@ -0,0 +1,110 @@ +load( + "//tools:defaults.bzl", + "markdown_to_html", + "ng_module", + "sass_binary", + "sass_library", +) + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "legacy-form-field", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + assets = [ + ":form-field.css", + ":form-field-fill.css", + ":form-field-input.css", + ":form-field-legacy.css", + ":form-field-outline.css", + ":form-field-standard.css", + ] + glob(["**/*.html"]), + deps = [ + "//src:dev_mode_types", + "//src/cdk/bidi", + "//src/cdk/coercion", + "//src/cdk/observers", + "//src/cdk/platform", + "//src/material/core", + "//src/material/form-field", + "@npm//@angular/animations", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//@angular/forms", + "@npm//@angular/platform-browser", + "@npm//rxjs", + ], +) + +sass_library( + name = "legacy_form_field_scss_lib", + srcs = glob(["**/_*.scss"]), + deps = ["//src/material/core:core_scss_lib"], +) + +sass_binary( + name = "legacy_form_field_scss", + src = "form-field.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "form_field_fill_scss", + src = "form-field-fill.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "form_field_input_scss", + src = "form-field-input.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "form_field_legacy_scss", + src = "form-field-legacy.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "form_field_outline_scss", + src = "form-field-outline.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +sass_binary( + name = "form_field_standard_scss", + src = "form-field-standard.scss", + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + +markdown_to_html( + name = "overview", + srcs = [":form-field.md"], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) diff --git a/src/material/legacy-form-field/README.md b/src/material/legacy-form-field/README.md new file mode 100644 index 000000000000..7a1234123cc2 --- /dev/null +++ b/src/material/legacy-form-field/README.md @@ -0,0 +1 @@ +Please see the official documentation at https://material.angular.io/components/component/form-field diff --git a/src/material/form-field/_form-field-fill-theme.import.scss b/src/material/legacy-form-field/_form-field-fill-theme.import.scss similarity index 100% rename from src/material/form-field/_form-field-fill-theme.import.scss rename to src/material/legacy-form-field/_form-field-fill-theme.import.scss diff --git a/src/material/form-field/_form-field-fill-theme.scss b/src/material/legacy-form-field/_form-field-fill-theme.scss similarity index 100% rename from src/material/form-field/_form-field-fill-theme.scss rename to src/material/legacy-form-field/_form-field-fill-theme.scss diff --git a/src/material/legacy-form-field/_form-field-legacy-index.scss b/src/material/legacy-form-field/_form-field-legacy-index.scss new file mode 100644 index 000000000000..fcb2b6ed620e --- /dev/null +++ b/src/material/legacy-form-field/_form-field-legacy-index.scss @@ -0,0 +1,29 @@ +@forward 'form-field-fill-theme' hide $fill-dedupe, fill-color, fill-theme, fill-typography, + private-form-field-fill-density; +@forward 'form-field-fill-theme' as mat-legacy-* hide $mat-legacy-fill-dedupe, + mat-legacy-fill-color, mat-legacy-fill-label-floating, mat-legacy-fill-theme, + mat-legacy-fill-typography; +@forward 'form-field-fill-theme' as mat-legacy-form-field-* hide + mat-legacy-form-field-fill-label-floating, mat-legacy-form-field-private-form-field-fill-density; +@forward 'form-field-legacy-theme' hide $legacy-dedupe, legacy-color, legacy-theme, + legacy-typography, private-form-field-legacy-density; +@forward 'form-field-legacy-theme' as mat-legacy-* hide $mat-legacy-legacy-dedupe, + mat-legacy-legacy-color, mat-legacy-legacy-label-floating, mat-legacy-legacy-label-floating-print, + mat-legacy-legacy-theme, mat-legacy-legacy-typography; +@forward 'form-field-legacy-theme' as mat-legacy-form-field-* hide + mat-legacy-form-field-legacy-label-floating, mat-legacy-form-field-legacy-label-floating-print, + mat-legacy-form-field-private-form-field-legacy-density; +@forward 'form-field-outline-theme' hide $outline-dedupe, outline-color, outline-theme, + outline-typography, private-form-field-outline-density; +@forward 'form-field-outline-theme' as mat-legacy-* hide $mat-legacy-outline-dedupe, + mat-legacy-outline-color, mat-legacy-outline-label-floating, mat-legacy-outline-theme, + mat-legacy-outline-typography; +@forward 'form-field-outline-theme' as mat-legacy-form-field-* hide + mat-legacy-form-field-outline-label-floating, + mat-legacy-form-field-private-form-field-outline-density; +@forward 'form-field-standard-theme' as mat-legacy-* hide mat-legacy-standard-color, + mat-legacy-standard-theme, mat-legacy-standard-typography; +@forward 'form-field-standard-theme' as mat-legacy-form-field-* hide + mat-legacy-form-field-private-form-field-standard-density; +@forward 'form-field-theme' hide $dedupe, color, density, theme, typography; +@forward 'form-field-theme' as mat-legacy-form-field-* hide mat-legacy-form-field-label-floating; diff --git a/src/material/form-field/_form-field-legacy-theme.import.scss b/src/material/legacy-form-field/_form-field-legacy-theme.import.scss similarity index 67% rename from src/material/form-field/_form-field-legacy-theme.import.scss rename to src/material/legacy-form-field/_form-field-legacy-theme.import.scss index 5ddf9b7bbfea..8909b920c92a 100644 --- a/src/material/form-field/_form-field-legacy-theme.import.scss +++ b/src/material/legacy-form-field/_form-field-legacy-theme.import.scss @@ -1,11 +1,12 @@ @forward '../core/style/form-common.import'; @forward '../core/typography/typography-utils.import'; @forward 'form-field-legacy-theme' hide $legacy-dedupe, legacy-color, legacy-theme, -legacy-typography, private-form-field-legacy-density; + legacy-typography, private-form-field-legacy-density; @forward 'form-field-legacy-theme' as mat-* hide $mat-legacy-dedupe, mat-legacy-color, -mat-legacy-label-floating, mat-legacy-label-floating-print, mat-legacy-theme, mat-legacy-typography; + mat-legacy-label-floating, mat-legacy-label-floating-print, mat-legacy-theme, + mat-legacy-typography; @forward 'form-field-legacy-theme' as mat-form-field-* hide mat-form-field-legacy-label-floating, -mat-form-field-legacy-label-floating-print, mat-form-field-private-form-field-legacy-density; + mat-form-field-legacy-label-floating-print, mat-form-field-private-form-field-legacy-density; @import '../core/theming/palette'; @import '../core/theming/theming'; diff --git a/src/material/form-field/_form-field-legacy-theme.scss b/src/material/legacy-form-field/_form-field-legacy-theme.scss similarity index 100% rename from src/material/form-field/_form-field-legacy-theme.scss rename to src/material/legacy-form-field/_form-field-legacy-theme.scss diff --git a/src/material/form-field/_form-field-outline-theme.import.scss b/src/material/legacy-form-field/_form-field-outline-theme.import.scss similarity index 100% rename from src/material/form-field/_form-field-outline-theme.import.scss rename to src/material/legacy-form-field/_form-field-outline-theme.import.scss diff --git a/src/material/form-field/_form-field-outline-theme.scss b/src/material/legacy-form-field/_form-field-outline-theme.scss similarity index 100% rename from src/material/form-field/_form-field-outline-theme.scss rename to src/material/legacy-form-field/_form-field-outline-theme.scss diff --git a/src/material/form-field/_form-field-standard-theme.import.scss b/src/material/legacy-form-field/_form-field-standard-theme.import.scss similarity index 100% rename from src/material/form-field/_form-field-standard-theme.import.scss rename to src/material/legacy-form-field/_form-field-standard-theme.import.scss diff --git a/src/material/form-field/_form-field-standard-theme.scss b/src/material/legacy-form-field/_form-field-standard-theme.scss similarity index 100% rename from src/material/form-field/_form-field-standard-theme.scss rename to src/material/legacy-form-field/_form-field-standard-theme.scss diff --git a/src/material/legacy-form-field/_form-field-theme.import.scss b/src/material/legacy-form-field/_form-field-theme.import.scss new file mode 100644 index 000000000000..36523a2d3eb4 --- /dev/null +++ b/src/material/legacy-form-field/_form-field-theme.import.scss @@ -0,0 +1,29 @@ +@forward '../core/style/form-common.import'; +@forward 'form-field-fill-theme' as mat-* hide $mat-fill-dedupe, mat-fill-color, mat-fill-theme, +mat-fill-typography; +@forward 'form-field-fill-theme' as mat-form-field-* hide +mat-form-field-private-form-field-fill-density; +@forward 'form-field-legacy-theme' as mat-* hide $mat-legacy-dedupe, mat-legacy-color, +mat-legacy-theme, mat-legacy-typography; +@forward 'form-field-legacy-theme' as mat-form-field-* hide +mat-form-field-private-form-field-legacy-density; +@forward 'form-field-outline-theme' as mat-* hide $mat-outline-dedupe, mat-outline-color, +mat-outline-theme, mat-outline-typography; +@forward 'form-field-outline-theme' as mat-form-field-* hide +mat-form-field-private-form-field-outline-density; +@forward '../core/typography/typography-utils.import'; +@forward 'form-field-standard-theme' as mat-* hide mat-standard-color, mat-standard-theme, +mat-standard-typography; +@forward 'form-field-standard-theme' as mat-form-field-* hide +mat-form-field-private-form-field-standard-density; +@forward 'form-field-theme' hide $dedupe, color, density, theme, typography; +@forward 'form-field-theme' as mat-form-field-* hide mat-form-field-label-floating; + +@import '../core/theming/palette'; +@import '../core/theming/theming'; +@import '../core/style/form-common'; +@import '../core/typography/typography-utils'; +@import './form-field-fill-theme.scss'; +@import './form-field-legacy-theme.scss'; +@import './form-field-outline-theme.scss'; +@import './form-field-standard-theme.scss'; diff --git a/src/material/legacy-form-field/_form-field-theme.scss b/src/material/legacy-form-field/_form-field-theme.scss new file mode 100644 index 000000000000..191b28ab6a39 --- /dev/null +++ b/src/material/legacy-form-field/_form-field-theme.scss @@ -0,0 +1,271 @@ +@use 'sass:map'; +@use 'sass:math'; +@use '../core/theming/theming'; +@use '../core/typography/typography'; +@use '../core/typography/typography-utils'; + +@use './form-field-fill-theme.scss'; +@use './form-field-legacy-theme.scss'; +@use './form-field-outline-theme.scss'; +@use './form-field-standard-theme.scss'; + +// Color styles that apply to all appearances of the form-field. +@mixin color($config-or-theme) { + $config: theming.get-color-config($config-or-theme); + $primary: map.get($config, primary); + $accent: map.get($config, accent); + $warn: map.get($config, warn); + $background: map.get($config, background); + $foreground: map.get($config, foreground); + $is-dark-theme: map.get($config, is-dark); + + // Label colors. Required is used for the `*` star shown in the label. + $label-color: + theming.get-color-from-palette($foreground, secondary-text, if($is-dark-theme, 0.7, 0.6)); + $focused-label-color: theming.get-color-from-palette($primary, text); + $required-label-color: theming.get-color-from-palette($accent, text); + + // Underline colors. + $underline-color-base: + theming.get-color-from-palette($foreground, divider, if($is-dark-theme, 1, 0.87)); + $underline-color-accent: theming.get-color-from-palette($accent, text); + $underline-color-warn: theming.get-color-from-palette($warn, text); + $underline-focused-color: theming.get-color-from-palette($primary, text); + + .mat-form-field-label { + color: $label-color; + } + + .mat-hint { + color: $label-color; + } + + .mat-form-field.mat-focused .mat-form-field-label { + color: $focused-label-color; + + &.mat-accent { + color: $underline-color-accent; + } + + &.mat-warn { + color: $underline-color-warn; + } + } + + .mat-focused .mat-form-field-required-marker { + color: $required-label-color; + } + + .mat-form-field-ripple { + background-color: $underline-color-base; + } + + .mat-form-field.mat-focused { + .mat-form-field-ripple { + background-color: $underline-focused-color; + + &.mat-accent { + background-color: $underline-color-accent; + } + + &.mat-warn { + background-color: $underline-color-warn; + } + } + } + + .mat-form-field-type-mat-native-select.mat-focused:not(.mat-form-field-invalid) { + .mat-form-field-infix::after { + color: $underline-focused-color; + } + + &.mat-accent .mat-form-field-infix::after { + color: $underline-color-accent; + } + + &.mat-warn .mat-form-field-infix::after { + 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.mat-form-field-invalid { + .mat-form-field-label { + color: $underline-color-warn; + + &.mat-accent, + .mat-form-field-required-marker { + color: $underline-color-warn; + } + } + + .mat-form-field-ripple, + .mat-form-field-ripple.mat-accent { + background-color: $underline-color-warn; + } + } + + .mat-error { + color: $underline-color-warn; + } + + @include form-field-legacy-theme.legacy-color($config); + @include form-field-standard-theme.standard-color($config); + @include form-field-fill-theme.fill-color($config); + @include form-field-outline-theme.outline-color($config); +} + +// 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; + +// Applies a floating label above the form field control itself. +@mixin _label-floating($font-scale, $infix-padding, $infix-margin-top) { + transform: translateY(-$infix-margin-top - $infix-padding + $dedupe) + scale($font-scale); + width: math.div(100%, $font-scale) + $dedupe; + + $dedupe: $dedupe + 0.00001 !global; +} + +@mixin typography($config-or-theme) { + $config: typography.private-typography-to-2014-config( + theming.get-typography-config($config-or-theme)); + // The unit-less line-height from the font config. + $line-height: typography-utils.line-height($config, input); + + // The amount to scale the font for the floating label and subscript. + $subscript-font-scale: 0.75; + // The amount to scale the font for the prefix and suffix icons. + $prefix-suffix-icon-font-scale: 1.5; + + // 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. + // If the line-height is given as a unitless number, coerce it to `em`. + $infix-margin-top: $subscript-font-scale * + typography-utils.private-coerce-unitless-to-em($line-height); + // Font size to use for the label and subscript text. + $subscript-font-size: $subscript-font-scale * 100%; + // Font size to use for the for the prefix and suffix icons. + $prefix-suffix-icon-font-size: $prefix-suffix-icon-font-scale * 100%; + // 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. + $subscript-margin-top: math.div(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. + $wrapper-padding-bottom: ($subscript-margin-top + $line-height) * $subscript-font-scale; + + .mat-form-field { + @include typography-utils.typography-level($config, input); + } + + .mat-form-field-wrapper { + padding-bottom: $wrapper-padding-bottom; + } + + .mat-form-field-prefix, + .mat-form-field-suffix { + // Allow icons in a prefix or suffix to adapt to the correct size. + .mat-icon { + font-size: $prefix-suffix-icon-font-size; + line-height: $line-height; + } + + // Allow icon buttons in a prefix or suffix to adapt to the correct size. + .mat-icon-button { + height: $prefix-suffix-icon-font-scale * 1em; + width: $prefix-suffix-icon-font-scale * 1em; + + .mat-icon { + height: typography-utils.private-coerce-unitless-to-em($line-height); + line-height: $line-height; + } + } + } + + .mat-form-field-infix { + padding: $infix-padding 0; + // Throws off the baseline if we do it as a real margin, so we do it as a border instead. + border-top: $infix-margin-top solid transparent; + } + + .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 _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 _label-floating( + $subscript-font-scale, $infix-padding, $infix-margin-top); + } + } + + .mat-form-field-label-wrapper { + top: -$infix-margin-top; + padding-top: $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 { + font-size: $subscript-font-size; + 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% - #{math.div($wrapper-padding-bottom, $subscript-font-scale)}); + } + + @include form-field-legacy-theme.legacy-typography($config); + @include form-field-standard-theme.standard-typography($config); + @include form-field-fill-theme.fill-typography($config); + @include form-field-outline-theme.outline-typography($config); +} + +@mixin density($config-or-theme) { + $density-scale: theming.get-density-config($config-or-theme); + @include form-field-legacy-theme.private-form-field-legacy-density($density-scale); + @include form-field-standard-theme.private-form-field-standard-density($density-scale); + @include form-field-fill-theme.private-form-field-fill-density($density-scale); + @include form-field-outline-theme.private-form-field-outline-density($density-scale); +} + +@mixin theme($theme-or-color-config) { + $theme: theming.private-legacy-get-theme($theme-or-color-config); + @include theming.private-check-duplicate-theme-styles($theme, 'legacy-mat-form-field') { + $color: theming.get-color-config($theme); + $density: theming.get-density-config($theme); + $typography: theming.get-typography-config($theme); + + @if $color != null { + @include color($color); + } + @if $density != null { + @include density($density); + } + @if $typography != null { + @include typography($typography); + } + } +} diff --git a/src/material/form-field/error.ts b/src/material/legacy-form-field/error.ts similarity index 64% rename from src/material/form-field/error.ts rename to src/material/legacy-form-field/error.ts index 196652f3aa0b..dfccaf672dd8 100644 --- a/src/material/form-field/error.ts +++ b/src/material/legacy-form-field/error.ts @@ -6,17 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core'; +import {Attribute, Directive, ElementRef, Input} from '@angular/core'; +import {MAT_ERROR} from '@angular/material/form-field'; let nextUniqueId = 0; -/** - * Injection token that can be used to reference instances of `MatError`. It serves as - * alternative token to the actual `MatError` class which could cause unnecessary - * retention of the class and its directive metadata. - */ -export const MAT_ERROR = new InjectionToken('MatError'); - /** Single error message to be shown underneath the form field. */ @Directive({ selector: 'mat-error', @@ -25,9 +19,9 @@ export const MAT_ERROR = new InjectionToken('MatError'); '[attr.id]': 'id', 'aria-atomic': 'true', }, - providers: [{provide: MAT_ERROR, useExisting: MatError}], + providers: [{provide: MAT_ERROR, useExisting: MatLegacyError}], }) -export class MatError { +export class MatLegacyError { @Input() id: string = `mat-error-${nextUniqueId++}`; constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) { diff --git a/src/material/form-field/form-field-fill.scss b/src/material/legacy-form-field/form-field-fill.scss similarity index 100% rename from src/material/form-field/form-field-fill.scss rename to src/material/legacy-form-field/form-field-fill.scss diff --git a/src/material/form-field/form-field-input.scss b/src/material/legacy-form-field/form-field-input.scss similarity index 100% rename from src/material/form-field/form-field-input.scss rename to src/material/legacy-form-field/form-field-input.scss diff --git a/src/material/form-field/form-field-legacy.scss b/src/material/legacy-form-field/form-field-legacy.scss similarity index 100% rename from src/material/form-field/form-field-legacy.scss rename to src/material/legacy-form-field/form-field-legacy.scss diff --git a/src/material/legacy-form-field/form-field-module.ts b/src/material/legacy-form-field/form-field-module.ts new file mode 100644 index 000000000000..2d8a8ae1a96f --- /dev/null +++ b/src/material/legacy-form-field/form-field-module.ts @@ -0,0 +1,43 @@ +/** + * @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 {ObserversModule} from '@angular/cdk/observers'; +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; +import {MatLegacyError} from './error'; +import {MatLegacyFormField} from './form-field'; +import {MatLegacyHint} from './hint'; +import {MatLegacyLabel} from './label'; +import {MatLegacyPlaceholder} from './placeholder'; +import {MatLegacyPrefix} from './prefix'; +import {MatLegacySuffix} from './suffix'; + +@NgModule({ + declarations: [ + MatLegacyError, + MatLegacyFormField, + MatLegacyHint, + MatLegacyLabel, + MatLegacyPlaceholder, + MatLegacyPrefix, + MatLegacySuffix, + ], + imports: [CommonModule, MatCommonModule, ObserversModule], + exports: [ + MatCommonModule, + MatLegacyError, + MatLegacyFormField, + MatLegacyHint, + MatLegacyLabel, + MatLegacyPlaceholder, + MatLegacyPrefix, + MatLegacySuffix, + ], +}) +export class MatLegacyFormFieldModule {} diff --git a/src/material/form-field/form-field-outline.scss b/src/material/legacy-form-field/form-field-outline.scss similarity index 100% rename from src/material/form-field/form-field-outline.scss rename to src/material/legacy-form-field/form-field-outline.scss diff --git a/src/material/form-field/form-field-standard.scss b/src/material/legacy-form-field/form-field-standard.scss similarity index 100% rename from src/material/form-field/form-field-standard.scss rename to src/material/legacy-form-field/form-field-standard.scss diff --git a/src/material/legacy-form-field/form-field.html b/src/material/legacy-form-field/form-field.html new file mode 100644 index 000000000000..5141b1d545f9 --- /dev/null +++ b/src/material/legacy-form-field/form-field.html @@ -0,0 +1,92 @@ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+ + + + + + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
{{hintLabel}}
+ +
+ +
+
+
diff --git a/src/material/legacy-form-field/form-field.md b/src/material/legacy-form-field/form-field.md new file mode 100644 index 000000000000..f09fb8229ce9 --- /dev/null +++ b/src/material/legacy-form-field/form-field.md @@ -0,0 +1,203 @@ +`` is a component used to wrap several Angular Material components and apply common +[Text field](https://material.io/guidelines/components/text-fields.html) styles such as the +underline, floating label, and hint messages. + +In this document, "form field" refers to the wrapper component `` and +"form field control" refers to the component that the `` is wrapping +(e.g. the input, textarea, select, etc.) + +The following Angular Material components are designed to work inside a ``: +* [`` & `