Skip to content

Commit c5482c9

Browse files
authoredJan 11, 2022
feat(material-experimental/mdc-chips): switch to evolution API (#23931)
* feat(material-experimental/mdc-chips): switch to evolution API Reworks the MDC-based chips to use the new `evolution` API instead of the deprecated one we're currently using. The new API comes with a lot of markup changes and some behavior differences. The new API also allows to remove the `GridKeyManager`, because the keyboard navigation is handled correctly by MDC. * refactor(material-experimental/mdc-chips): reduce specificity and bundle size Reworks the theme of the MDC-based chips to produce less specific and more compact CSS.
1 parent 5fcc634 commit c5482c9

38 files changed

+1659
-2199
lines changed
 

‎scripts/check-mdc-tests-config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export const config = {
3636

3737
// This test checks something that isn't supported in the MDC form field.
3838
'should propagate the dynamic `placeholder` value to the form field',
39+
40+
// Disabled, because the MDC-based chip input doesn't deal with focus escaping anymore.
41+
'should not allow focus to escape when tabbing backwards',
42+
43+
// Disabled, because preventing the default action isn't required.
44+
'should prevent the default click action when the chip is disabled',
3945
],
4046
'mdc-dialog': [
4147
// These tests are verifying implementation details that are not relevant for MDC.

‎src/dev-app/chips/chips-demo.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ <h4>With avatar and icons</h4>
7070
</mat-chip>
7171

7272
<mat-chip>
73-
<img src="https://material.angularjs.org/material2_assets/ngconf/Mal.png" matChipAvatar>
73+
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
7474
Mal
7575
</mat-chip>
7676

7777
<mat-chip selected="true" color="warn">
78-
<img src="https://material.angularjs.org/material2_assets/ngconf/Husi.png" matChipAvatar>
78+
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
7979
Husi
8080
<button matChipRemove>
8181
<mat-icon>cancel</mat-icon>

‎src/dev-app/mdc-chips/mdc-chips-demo.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ <h4>With avatar, icons, and color</h4>
5555
</mat-chip>
5656

5757
<mat-chip>
58-
<img src="https://material.angularjs.org/material2_assets/ngconf/Mal.png" matChipAvatar>
58+
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
5959
Mal
6060
</mat-chip>
6161

6262
<mat-chip highlighted="true" color="warn">
63-
<img src="https://material.angularjs.org/material2_assets/ngconf/Husi.png" matChipAvatar>
63+
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
6464
Husi
6565
<button matChipRemove>
6666
<mat-icon>cancel</mat-icon>

‎src/material-experimental/mdc-chips/BUILD.bazel

+19-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ ng_module(
1717
"**/*.spec.ts",
1818
],
1919
),
20-
assets = [":chips_scss"] + glob(["**/*.html"]),
20+
assets = [
21+
":chip_scss",
22+
":chip_set_scss",
23+
] + glob(["**/*.html"]),
2124
deps = [
2225
"//src:dev_mode_types",
2326
"//src/material-experimental/mdc-core",
@@ -40,8 +43,21 @@ sass_library(
4043
)
4144

4245
sass_binary(
43-
name = "chips_scss",
44-
src = "chips.scss",
46+
name = "chip_scss",
47+
src = "chip.scss",
48+
include_paths = [
49+
"external/npm/node_modules",
50+
],
51+
deps = [
52+
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
53+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
54+
"//src/material/core:core_scss_lib",
55+
],
56+
)
57+
58+
sass_binary(
59+
name = "chip_set_scss",
60+
src = "chip-set.scss",
4561
include_paths = [
4662
"external/npm/node_modules",
4763
],

‎src/material-experimental/mdc-chips/_chips-theme.scss

+53-53
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,93 @@
1-
@use '@material/chips/deprecated' as mdc-chips;
1+
@use '@material/chips/chip' as mdc-chip;
2+
@use '@material/chips/chip-theme' as mdc-chip-theme;
3+
@use '@material/chips/chip-set' as mdc-chip-set;
24
@use '@material/theme/theme-color' as mdc-theme-color;
5+
@use '@material/theme/color-palette' as mdc-color-palette;
36
@use 'sass:color';
47
@use 'sass:map';
58
@use '../mdc-helpers/mdc-helpers';
69
@use '../../material/core/typography/typography';
710
@use '../../material/core/theming/theming';
811

9-
@mixin _selected-color($color) {
10-
@include mdc-chips.fill-color($color, $query: mdc-helpers.$mat-theme-styles-query);
11-
@include mdc-chips.ink-color(text-primary-on-dark, $query: mdc-helpers.$mat-theme-styles-query);
12-
@include mdc-chips.selected-ink-color-without-ripple_(
13-
text-primary-on-dark,
14-
$query: mdc-helpers.$mat-theme-styles-query
15-
);
16-
@include mdc-chips.leading-icon-color(text-primary-on-dark,
17-
$query: mdc-helpers.$mat-theme-styles-query);
18-
@include mdc-chips.trailing-icon-color(text-primary-on-dark,
19-
$query: mdc-helpers.$mat-theme-styles-query);
12+
// Customizes the appearance of a chip. Note that ideally we would be doing this using the
13+
// `theme-styles` mixin, however it has the following problems:
14+
// 1. Some of MDC's base styles have **very** high specificity. E.g. setting the background of a
15+
// non-selected, enabled chip uses a selector like `.chip:not(.selected):not(.disabled)` instead of
16+
// just `.chip`. This specificity increase has a ripple effect over all other components that are
17+
// built on top of ours, making overrides extremely difficult and brittle.
18+
// 2. Including the individual mixins allows us to avoid a lot of unnecessary CSS (~35kb in the
19+
// dev app theme).
20+
@mixin _chip-variant($background, $foreground) {
21+
@include mdc-chip-theme.container-color($background);
22+
@include mdc-chip-theme.icon-color($foreground);
23+
@include mdc-chip-theme.trailing-action-color($foreground);
24+
@include mdc-chip-theme.checkmark-color($foreground);
25+
@include mdc-chip-theme.text-label-color($foreground);
26+
27+
// Technically the avatar is only supposed to have an image, but we also allow for icons.
28+
// Set the color so the icons inherit the correct color.
29+
.mat-mdc-chip-avatar {
30+
color: $foreground;
31+
}
32+
}
33+
34+
@mixin _colored-chip($palette) {
35+
$background: theming.get-color-from-palette($palette);
36+
$foreground: theming.get-color-from-palette($palette, default-contrast);
37+
38+
&.mat-mdc-chip-selected,
39+
&.mat-mdc-chip-highlighted {
40+
@include _chip-variant($background, $foreground);
41+
}
2042
}
2143

2244
@mixin color($config-or-theme) {
2345
$config: theming.get-color-config($config-or-theme);
24-
$primary: theming.get-color-from-palette(map.get($config, primary));
25-
$accent: theming.get-color-from-palette(map.get($config, accent));
26-
$warn: theming.get-color-from-palette(map.get($config, warn));
27-
$background: map.get($config, background);
28-
$unselected-background: theming.get-color-from-palette($background, unselected-chip);
29-
30-
// Save original values of MDC global variables. We need to save these so we can restore the
31-
// variables to their original values and prevent unintended side effects from using this mixin.
32-
$orig-mdc-chips-fill-color-default: mdc-chips.$fill-color-default;
33-
$orig-mdc-chips-ink-color-default: mdc-chips.$ink-color-default;
34-
$orig-mdc-chips-icon-color: mdc-chips.$icon-color;
46+
$primary: map.get($config, primary);
47+
$accent: map.get($config, accent);
48+
$warn: map.get($config, warn);
49+
$foreground: map.get($config, foreground);
50+
$is-dark: map.get($config, is-dark);
3551

3652
@include mdc-helpers.mat-using-mdc-theme($config) {
37-
mdc-chips.$fill-color-default:
38-
color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%);
39-
mdc-chips.$ink-color-default: rgba(mdc-theme-color.prop-value(on-surface), 0.87);
40-
mdc-chips.$icon-color: mdc-theme-color.prop-value(on-surface);
41-
42-
@include mdc-chips.set-core-styles($query: mdc-helpers.$mat-theme-styles-query);
43-
@include mdc-chips.without-ripple($query: mdc-helpers.$mat-theme-styles-query);
53+
.mat-mdc-standard-chip {
54+
@include _chip-variant(
55+
color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%),
56+
if($is-dark, mdc-color-palette.$grey-50, mdc-color-palette.$grey-900)
57+
);
4458

45-
.mat-mdc-chip {
46-
@include mdc-chips.fill-color-accessible($unselected-background,
47-
$query: mdc-helpers.$mat-theme-styles-query);
48-
49-
// mdc-chip-fill-color-accessible includes mdc-chip-selected-ink-color which overrides the
50-
// opacity so selected chips always show a ripple.
51-
// Include the same mixins but use mdc-chip-selected-ink-color-without-ripple
5259
&.mat-primary {
53-
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
54-
@include _selected-color($primary);
55-
}
60+
@include _colored-chip($primary);
5661
}
5762

5863
&.mat-accent {
59-
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
60-
@include _selected-color($accent);
61-
}
64+
@include _colored-chip($accent);
6265
}
6366

6467
&.mat-warn {
65-
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
66-
@include _selected-color($warn);
67-
}
68+
@include _colored-chip($warn);
6869
}
6970
}
7071
}
7172

72-
// Restore original values of MDC global variables.
73-
mdc-chips.$fill-color-default: $orig-mdc-chips-fill-color-default;
74-
mdc-chips.$ink-color-default: $orig-mdc-chips-ink-color-default;
75-
mdc-chips.$icon-color: $orig-mdc-chips-icon-color;
73+
.mat-mdc-chip-focus-overlay {
74+
background: map.get($foreground, base);
75+
}
7676
}
7777

7878
@mixin typography($config-or-theme) {
7979
$config: typography.private-typography-to-2018-config(
8080
theming.get-typography-config($config-or-theme));
81-
@include mdc-chips.set-core-styles($query: mdc-helpers.$mat-typography-styles-query);
81+
@include mdc-chip-set.core-styles($query: mdc-helpers.$mat-typography-styles-query);
8282
@include mdc-helpers.mat-using-mdc-typography($config) {
83-
@include mdc-chips.without-ripple($query: mdc-helpers.$mat-typography-styles-query);
83+
@include mdc-chip.without-ripple-styles($query: mdc-helpers.$mat-typography-styles-query);
8484
}
8585
}
8686

8787
@mixin density($config-or-theme) {
8888
$density-scale: theming.get-density-config($config-or-theme);
8989
.mat-mdc-chip {
90-
@include mdc-chips.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
90+
@include mdc-chip-theme.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
9191
}
9292
}
9393

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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 {
10+
AfterViewInit,
11+
ChangeDetectorRef,
12+
Directive,
13+
ElementRef,
14+
Inject,
15+
Input,
16+
OnChanges,
17+
OnDestroy,
18+
SimpleChanges,
19+
} from '@angular/core';
20+
import {DOCUMENT} from '@angular/common';
21+
import {
22+
MDCChipActionAdapter,
23+
MDCChipActionFoundation,
24+
MDCChipActionType,
25+
MDCChipPrimaryActionFoundation,
26+
} from '@material/chips';
27+
import {emitCustomEvent} from './emit-event';
28+
import {
29+
CanDisable,
30+
HasTabIndex,
31+
mixinDisabled,
32+
mixinTabIndex,
33+
} from '@angular/material-experimental/mdc-core';
34+
35+
const _MatChipActionMixinBase = mixinTabIndex(mixinDisabled(class {}), -1);
36+
37+
/**
38+
* Interactive element within a chip.
39+
* @docs-private
40+
*/
41+
@Directive({
42+
selector: '[matChipAction]',
43+
inputs: ['disabled', 'tabIndex'],
44+
host: {
45+
'class': 'mdc-evolution-chip__action mat-mdc-chip-action',
46+
'[class.mdc-evolution-chip__action--primary]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`,
47+
// Note that while our actions are interactive, we have to add the `--presentational` class,
48+
// in order to avoid some super-specific `:hover` styles from MDC.
49+
'[class.mdc-evolution-chip__action--presentational]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`,
50+
'[class.mdc-evolution-chip__action--trailing]': `_getFoundation().actionType() === ${MDCChipActionType.TRAILING}`,
51+
'[attr.tabindex]': '(disabled || !isInteractive) ? null : tabIndex',
52+
'[attr.disabled]': "disabled ? '' : null",
53+
'[attr.aria-disabled]': 'disabled',
54+
'(click)': '_handleClick($event)',
55+
'(keydown)': '_handleKeydown($event)',
56+
},
57+
})
58+
export class MatChipAction
59+
extends _MatChipActionMixinBase
60+
implements AfterViewInit, OnDestroy, CanDisable, HasTabIndex, OnChanges
61+
{
62+
private _document: Document;
63+
private _foundation: MDCChipActionFoundation;
64+
private _adapter: MDCChipActionAdapter = {
65+
focus: () => this.focus(),
66+
getAttribute: (name: string) => this._elementRef.nativeElement.getAttribute(name),
67+
setAttribute: (name: string, value: string) => {
68+
// MDC tries to update the tabindex directly in the DOM when navigating using the keyboard
69+
// which overrides our own handling. If we detect such a case, assign it to the same property
70+
// as the Angular binding in order to maintain consistency.
71+
if (name === 'tabindex') {
72+
this._updateTabindex(parseInt(value));
73+
} else {
74+
this._elementRef.nativeElement.setAttribute(name, value);
75+
}
76+
},
77+
removeAttribute: (name: string) => {
78+
if (name !== 'tabindex') {
79+
this._elementRef.nativeElement.removeAttribute(name);
80+
}
81+
},
82+
getElementID: () => this._elementRef.nativeElement.id,
83+
emitEvent: <T>(eventName: string, data: T) => {
84+
emitCustomEvent<T>(this._elementRef.nativeElement, this._document, eventName, data, true);
85+
},
86+
};
87+
88+
/** Whether the action is interactive. */
89+
@Input() isInteractive = true;
90+
91+
_handleClick(_event: MouseEvent) {
92+
// Usually these events can't happen while the chip is disabled since the browser won't
93+
// allow them which is what MDC seems to rely on, however the event can be faked in tests.
94+
if (!this.disabled && this.isInteractive) {
95+
this._foundation.handleClick();
96+
}
97+
}
98+
99+
_handleKeydown(event: KeyboardEvent) {
100+
// Usually these events can't happen while the chip is disabled since the browser won't
101+
// allow them which is what MDC seems to rely on, however the event can be faked in tests.
102+
if (!this.disabled && this.isInteractive) {
103+
this._foundation.handleKeydown(event);
104+
}
105+
}
106+
107+
protected _createFoundation(adapter: MDCChipActionAdapter): MDCChipActionFoundation {
108+
return new MDCChipPrimaryActionFoundation(adapter);
109+
}
110+
111+
constructor(
112+
public _elementRef: ElementRef,
113+
@Inject(DOCUMENT) _document: any,
114+
private _changeDetectorRef: ChangeDetectorRef,
115+
) {
116+
super();
117+
this._foundation = this._createFoundation(this._adapter);
118+
119+
if (_elementRef.nativeElement.nodeName === 'BUTTON') {
120+
_elementRef.nativeElement.setAttribute('type', 'button');
121+
}
122+
}
123+
124+
ngAfterViewInit() {
125+
this._foundation.init();
126+
this._foundation.setDisabled(this.disabled);
127+
}
128+
129+
ngOnChanges(changes: SimpleChanges) {
130+
if (changes['disabled']) {
131+
this._foundation.setDisabled(this.disabled);
132+
}
133+
}
134+
135+
ngOnDestroy() {
136+
this._foundation.destroy();
137+
}
138+
139+
focus() {
140+
this._elementRef.nativeElement.focus();
141+
}
142+
143+
_getFoundation() {
144+
return this._foundation;
145+
}
146+
147+
_updateTabindex(value: number) {
148+
this.tabIndex = value;
149+
this._changeDetectorRef.markForCheck();
150+
}
151+
}

‎src/material-experimental/mdc-chips/chip-edit-input.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ describe('MDC-based MatChipEditInput', () => {
4646
});
4747

4848
@Component({
49-
template: `<span matChipEditInput></span>`,
49+
template: `<mat-chip><span matChipEditInput></span></mat-chip>`,
5050
})
5151
class ChipEditInputContainer {}

‎src/material-experimental/mdc-chips/chip-edit-input.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {DOCUMENT} from '@angular/common';
1616
@Directive({
1717
selector: 'span[matChipEditInput]',
1818
host: {
19-
'class': 'mdc-chip__primary-action mat-chip-edit-input',
19+
'class': 'mat-chip-edit-input',
2020
'role': 'textbox',
2121
'tabindex': '-1',
2222
'contenteditable': 'true',

0 commit comments

Comments
 (0)
Please sign in to comment.