Skip to content

Commit 997bf75

Browse files
authored
fix(multiple): consolidate and tokenize internal form field (#28236)
We have multiple components that depend on MDC's form field styles (not to be confused with our form field which uses MDC's text field). Currently each one includes the styles itself, both the structural ones and the theme. This is problematic because it ships the same styles multiple times and it means that we need to do refactors in multiple places. These changes move the styles into a common component that is reused between all instances. They also switch it to the new tokens-based theming API.
1 parent f3b0616 commit 997bf75

22 files changed

+185
-46
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
/src/material/core/line/** @andrewseguin
5454
/src/material/core/mdc-helpers/** @mmalerba
5555
/src/material/core/option/** @crisbeto
56+
/src/material/core/internal-form-field/** @crisbeto
5657
/src/material/core/placeholder/** @mmalerba
5758
/src/material/core/private/** @wagnermaciel
5859
/src/material/core/ripple/** @devversion

src/material/checkbox/_checkbox-theme.scss

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
@use 'sass:map';
22
@use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme;
3-
@use '@material/form-field' as mdc-form-field;
3+
@use '@material/form-field/form-field-theme' as mdc-form-field-theme;
44
@use '../core/style/sass-utils';
55
@use '../core/theming/theming';
66
@use '../core/theming/inspection';
77
@use '../core/typography/typography';
88
@use '../core/mdc-helpers/mdc-helpers';
99
@use '../core/tokens/m2/mdc/checkbox' as tokens-mdc-checkbox;
10+
@use '../core/tokens/m2/mdc/form-field' as tokens-mdc-form-field;
1011

1112
@mixin base($theme) {
1213
@if inspection.get-theme-version($theme) == 1 {
@@ -29,6 +30,8 @@
2930
}
3031

3132
.mat-mdc-checkbox {
33+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-color-tokens($theme));
34+
3235
&.mat-primary {
3336
@include mdc-checkbox-theme.theme(tokens-mdc-checkbox.get-color-tokens($theme, primary));
3437
}
@@ -37,11 +40,6 @@
3740
@include mdc-checkbox-theme.theme(tokens-mdc-checkbox.get-color-tokens($theme, warn));
3841
}
3942

40-
@include mdc-helpers.using-mdc-theme($theme) {
41-
// TODO(mmalerba): Switch to static-styles, theme-styles, and theme once they're available.
42-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-theme-styles-query);
43-
}
44-
4543
&.mat-mdc-checkbox-disabled label {
4644
// MDC should set the disabled color on the label, but doesn't, so we do it here instead.
4745
color: inspection.get-theme-color($theme, foreground, disabled-text);
@@ -60,10 +58,7 @@
6058
}
6159

6260
.mat-mdc-checkbox {
63-
@include mdc-helpers.using-mdc-typography($theme) {
64-
// TODO(mmalerba): Switch to static-styles, theme-styles, and theme once they're available.
65-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-typography-styles-query);
66-
}
61+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-typography-tokens($theme));
6762
}
6863
}
6964
}

src/material/checkbox/checkbox.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
<div class="mdc-form-field"
2-
[class.mdc-form-field--align-end]="labelPosition == 'before'"
3-
(click)="_preventBubblingFromLabel($event)">
1+
<div mat-internal-form-field [labelPosition]="labelPosition" (click)="_preventBubblingFromLabel($event)">
42
<div #checkbox class="mdc-checkbox">
53
<!-- Render this element first so the input is on top. -->
64
<div class="mat-mdc-checkbox-touch-target" (click)="_onTouchTargetClick()"></div>

src/material/checkbox/checkbox.scss

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
@use '@angular/cdk';
33
@use '@material/checkbox/checkbox' as mdc-checkbox;
44
@use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme;
5-
@use '@material/form-field' as mdc-form-field;
65
@use '@material/touch-target' as mdc-touch-target;
76
@use '@material/theme/custom-properties' as mdc-custom-properties;
87
@use '../core/mdc-helpers/mdc-helpers';
@@ -14,8 +13,6 @@
1413
@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) {
1514
// Add the checkbox static styles.
1615
@include mdc-checkbox.static-styles();
17-
// TODO(mmalerba): Switch to static-styles, theme-styles, and theme once they're available.
18-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-base-styles-query);
1916

2017
$mdc-checkbox-slots: tokens-mdc-checkbox.get-token-slots();
2118

src/material/checkbox/checkbox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
ValidationErrors,
3636
Validator,
3737
} from '@angular/forms';
38-
import {MatRipple} from '@angular/material/core';
38+
import {_MatInternalFormField, MatRipple} from '@angular/material/core';
3939
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
4040
import {FocusableOption} from '@angular/cdk/a11y';
4141
import {
@@ -112,7 +112,7 @@ const defaults = MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY();
112112
encapsulation: ViewEncapsulation.None,
113113
changeDetection: ChangeDetectionStrategy.OnPush,
114114
standalone: true,
115-
imports: [MatRipple],
115+
imports: [MatRipple, _MatInternalFormField],
116116
})
117117
export class MatCheckbox
118118
implements AfterViewInit, OnChanges, ControlValueAccessor, Validator, FocusableOption

src/material/core/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ng_module(
2323
":selection/pseudo-checkbox/pseudo-checkbox.css",
2424
":option/option.css",
2525
":option/optgroup.css",
26+
":internal-form-field/internal-form-field.css",
2627
] + glob(["**/*.html"]),
2728
deps = [
2829
"//src:dev_mode_types",
@@ -88,6 +89,12 @@ sass_binary(
8889
deps = [":core_scss_lib"],
8990
)
9091

92+
sass_binary(
93+
name = "internal_form_field_scss",
94+
src = "internal-form-field/internal-form-field.scss",
95+
deps = [":core_scss_lib"],
96+
)
97+
9198
sass_binary(
9299
name = "indigo_pink_prebuilt",
93100
src = "theming/prebuilt/indigo-pink.scss",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@use '@material/form-field/form-field-theme' as mdc-form-field-theme;
2+
@use '../tokens/m2/mdc/form-field' as tokens-mdc-form-field;
3+
@use '../style/sass-utils';
4+
@use '../theming/theming';
5+
@use '../theming/inspection';
6+
@use '../typography/typography';
7+
8+
@mixin base($theme) {}
9+
10+
@mixin color($theme) {
11+
@include sass-utils.current-selector-or-root() {
12+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-color-tokens($theme));
13+
}
14+
}
15+
16+
@mixin typography($theme) {
17+
@include sass-utils.current-selector-or-root() {
18+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-typography-tokens($theme));
19+
}
20+
}
21+
22+
@mixin density($theme) {}
23+
24+
@mixin theme($theme) {
25+
@include theming.private-check-duplicate-theme-styles($theme, 'mat-internal-form-field') {
26+
@include base($theme);
27+
@if inspection.theme-has($theme, color) {
28+
@include color($theme);
29+
}
30+
@if inspection.theme-has($theme, density) {
31+
@include density($theme);
32+
}
33+
@if inspection.theme-has($theme, typography) {
34+
@include typography($theme);
35+
}
36+
}
37+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@use '@material/form-field/form-field' as mdc-form-field;
2+
@use '@material/form-field/form-field-theme' as mdc-form-field-theme;
3+
@use '@material/typography/typography' as mdc-typography;
4+
@use '@material/theme/custom-properties' as mdc-custom-properties;
5+
@use '../mdc-helpers/mdc-helpers';
6+
@use '../tokens/m2/mdc/form-field' as tokens-mdc-form-field;
7+
8+
@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) {
9+
@include mdc-form-field.static-styles($query: mdc-helpers.$mdc-base-styles-query);
10+
@include mdc-form-field-theme.theme-styles(tokens-mdc-form-field.get-token-slots());
11+
}
12+
13+
.mat-internal-form-field {
14+
@include mdc-typography.smooth-font();
15+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core';
10+
11+
/**
12+
* Internal shared component used as a container in form field controls.
13+
* Not to be confused with `mat-form-field` which MDC calls a "text field".
14+
* @docs-private
15+
*/
16+
@Component({
17+
// Use a `div` selector to match the old markup closer.
18+
selector: 'div[mat-internal-form-field]',
19+
standalone: true,
20+
template: '<ng-content></ng-content>',
21+
styleUrls: ['internal-form-field.css'],
22+
encapsulation: ViewEncapsulation.None,
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
host: {
25+
'class': 'mdc-form-field mat-internal-form-field',
26+
'[class.mdc-form-field--align-end]': 'labelPosition === "before"',
27+
},
28+
})
29+
export class _MatInternalFormField {
30+
/** Position of the label relative to the content. */
31+
@Input({required: true}) labelPosition: 'before' | 'after';
32+
}

src/material/core/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export * from './option/index';
1616
export * from './private/index';
1717
export * from './ripple/index';
1818
export * from './selection/index';
19+
export {_MatInternalFormField} from './internal-form-field/internal-form-field';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
@use '../../../theming/inspection';
2+
@use '../../../style/sass-utils';
3+
@use '../../token-utils';
4+
5+
// The prefix used to generate the fully qualified name for tokens in this file.
6+
$prefix: (mdc, form-field);
7+
8+
// Tokens that can't be configured through Angular Material's current theming API,
9+
// but may be in a future version of the theming API.
10+
@function get-unthemable-tokens() {
11+
@return ();
12+
}
13+
14+
// Tokens that can be configured through Angular Material's color theming API.
15+
@function get-color-tokens($theme) {
16+
@return (
17+
label-text-color: inspection.get-theme-color($theme, foreground, text),
18+
);
19+
}
20+
21+
// Tokens that can be configured through Angular Material's typography theming API.
22+
@function get-typography-tokens($theme) {
23+
@return (
24+
label-text-font: inspection.get-theme-typography($theme, body-2, font-family),
25+
label-text-line-height: inspection.get-theme-typography($theme, body-2, line-height),
26+
label-text-size: inspection.get-theme-typography($theme, body-2, font-size),
27+
label-text-tracking: inspection.get-theme-typography($theme, body-2, letter-spacing),
28+
label-text-weight: inspection.get-theme-typography($theme, body-2, font-weight),
29+
);
30+
}
31+
32+
// Tokens that can be configured through Angular Material's density theming API.
33+
@function get-density-tokens($theme) {
34+
@return ();
35+
}
36+
37+
// Combines the tokens generated by the above functions into a single map with placeholder values.
38+
// This is used to create token slots.
39+
@function get-token-slots() {
40+
@return sass-utils.deep-merge-all(
41+
get-unthemable-tokens(),
42+
get-color-tokens(token-utils.$placeholder-color-config),
43+
get-typography-tokens(token-utils.$placeholder-typography-config),
44+
get-density-tokens(token-utils.$placeholder-density-config)
45+
);
46+
}

src/material/core/tokens/tests/test-validate-tokens.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
@use '@material/dialog/dialog-theme' as mdc-dialog-theme;
2626
@use '@material/textfield/filled-text-field-theme' as mdc-filled-text-field-theme;
2727
@use '@material/textfield/outlined-text-field-theme' as mdc-outlined-text-field-theme;
28+
@use '@material/form-field/form-field-theme' as mdc-form-field-theme;
2829
@use '@material/theme/validate' as mdc-validate;
2930

3031
@use '../m2/mdc/protected-button' as tokens-mdc-protected-button;
@@ -51,6 +52,7 @@
5152
@use '../m2/mdc/dialog' as tokens-mdc-dialog;
5253
@use '../m2/mdc/filled-text-field' as tokens-mdc-filled-text-field;
5354
@use '../m2/mdc/outlined-text-field' as tokens-mdc-outlined-text-field;
55+
@use '../m2/mdc/form-field' as tokens-mdc-form-field;
5456

5557
@mixin validate-slots($component, $slots, $reference) {
5658
@debug 'Validating #{$component}...';
@@ -184,3 +186,8 @@
184186
$slots: tokens-mdc-outlined-button.get-token-slots(),
185187
$reference: mdc-button-outlined-theme.$light-theme
186188
);
189+
@include validate-slots(
190+
$component: 'm2.mdc.form-field',
191+
$slots: tokens-mdc-form-field.get-token-slots(),
192+
$reference: mdc-form-field-theme.$light-theme
193+
);

src/material/radio/_radio-theme.scss

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
@use 'sass:map';
22
@use '@material/radio/radio-theme' as mdc-radio-theme;
3-
@use '@material/form-field' as mdc-form-field;
3+
@use '@material/form-field/form-field-theme' as mdc-form-field-theme;
44
@use '../core/mdc-helpers/mdc-helpers';
55
@use '../core/style/sass-utils';
66
@use '../core/theming/theming';
77
@use '../core/theming/inspection';
88
@use '../core/tokens/token-utils';
99
@use '../core/typography/typography';
10+
@use '../core/tokens/m2/mdc/form-field' as tokens-mdc-form-field;
1011
@use '../core/tokens/m2/mdc/radio' as tokens-mdc-radio;
1112
@use '../core/tokens/m2/mat/radio' as tokens-mat-radio;
1213

@@ -28,13 +29,9 @@
2829
@include _theme-from-tokens(inspection.get-theme-tokens($theme, color));
2930
}
3031
@else {
31-
@include mdc-helpers.using-mdc-theme($theme) {
32-
.mat-mdc-radio-button {
33-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-theme-styles-query);
34-
}
35-
}
36-
3732
.mat-mdc-radio-button {
33+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-color-tokens($theme));
34+
3835
&.mat-primary {
3936
@include mdc-radio-theme.theme(tokens-mdc-radio.get-color-tokens($theme, primary));
4037
@include token-utils.create-token-values(tokens-mat-radio.$prefix,
@@ -66,9 +63,7 @@
6663
}
6764

6865
.mat-mdc-radio-button {
69-
@include mdc-helpers.using-mdc-typography($theme) {
70-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-typography-styles-query);
71-
}
66+
@include mdc-form-field-theme.theme(tokens-mdc-form-field.get-typography-tokens($theme));
7267
}
7368
}
7469
}

src/material/radio/radio.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
<div class="mdc-form-field" #formField
2-
[class.mdc-form-field--align-end]="labelPosition == 'before'">
1+
<div mat-internal-form-field [labelPosition]="labelPosition" #formField>
32
<div class="mdc-radio" [class.mdc-radio--disabled]="disabled">
43
<!-- Render this element first so the input is on top. -->
54
<div class="mat-mdc-radio-touch-target" (click)="_onTouchTargetClick($event)"></div>
@@ -19,7 +18,7 @@
1918
<div class="mdc-radio__inner-circle"></div>
2019
</div>
2120
<div mat-ripple class="mat-radio-ripple mat-mdc-focus-indicator"
22-
[matRippleTrigger]="formField"
21+
[matRippleTrigger]="_rippleTrigger.nativeElement"
2322
[matRippleDisabled]="_isRippleDisabled()"
2423
[matRippleCentered]="true">
2524
<div class="mat-ripple-element mat-radio-persistent-ripple"></div>

src/material/radio/radio.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@use 'sass:map';
22
@use '@material/radio/radio' as mdc-radio;
33
@use '@material/radio/radio-theme' as mdc-radio-theme;
4-
@use '@material/form-field' as mdc-form-field;
54
@use '@material/touch-target' as mdc-touch-target;
65
@use '@material/ripple' as mdc-ripple;
76
@use '@material/theme/custom-properties' as mdc-custom-properties;
@@ -14,7 +13,6 @@
1413

1514
@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) {
1615
@include mdc-radio.static-styles($query: mdc-helpers.$mdc-base-styles-query);
17-
@include mdc-form-field.core-styles($query: mdc-helpers.$mdc-base-styles-query);
1816

1917
.mat-mdc-radio-button {
2018
-webkit-tap-highlight-color: transparent;

src/material/radio/radio.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
ViewChild,
3333
ViewEncapsulation,
3434
} from '@angular/core';
35-
import {MatRipple, ThemePalette} from '@angular/material/core';
35+
import {_MatInternalFormField, MatRipple, ThemePalette} from '@angular/material/core';
3636
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
3737
import {UniqueSelectionDispatcher} from '@angular/cdk/collections';
3838
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
@@ -370,7 +370,7 @@ export class MatRadioGroup implements AfterContentInit, OnDestroy, ControlValueA
370370
encapsulation: ViewEncapsulation.None,
371371
changeDetection: ChangeDetectionStrategy.OnPush,
372372
standalone: true,
373-
imports: [MatRipple],
373+
imports: [MatRipple, _MatInternalFormField],
374374
})
375375
export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy {
376376
private _uniqueId: string = `mat-radio-${++nextUniqueId}`;
@@ -525,6 +525,10 @@ export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy
525525
/** The native `<input type=radio>` element */
526526
@ViewChild('input') _inputElement: ElementRef<HTMLInputElement>;
527527

528+
/** Trigger elements for the ripple events. */
529+
@ViewChild('formField', {read: ElementRef, static: true})
530+
_rippleTrigger: ElementRef<HTMLElement>;
531+
528532
/** Whether animations are disabled. */
529533
_noopAnimations: boolean;
530534

0 commit comments

Comments
 (0)