Skip to content

feat(material-experimental/mdc-chips): switch to evolution API #23931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions scripts/check-mdc-tests-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export const config = {

// This test checks something that isn't supported in the MDC form field.
'should propagate the dynamic `placeholder` value to the form field',

// Disabled, because the MDC-based chip input doesn't deal with focus escaping anymore.
'should not allow focus to escape when tabbing backwards',

// Disabled, because preventing the default action isn't required.
'should prevent the default click action when the chip is disabled',
],
'mdc-dialog': [
// These tests are verifying implementation details that are not relevant for MDC.
Expand Down
4 changes: 2 additions & 2 deletions src/dev-app/chips/chips-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ <h4>With avatar and icons</h4>
</mat-chip>

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

<mat-chip selected="true" color="warn">
<img src="https://material.angularjs.org/material2_assets/ngconf/Husi.png" matChipAvatar>
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
Husi
<button matChipRemove>
<mat-icon>cancel</mat-icon>
Expand Down
4 changes: 2 additions & 2 deletions src/dev-app/mdc-chips/mdc-chips-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ <h4>With avatar, icons, and color</h4>
</mat-chip>

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

<mat-chip highlighted="true" color="warn">
<img src="https://material.angularjs.org/material2_assets/ngconf/Husi.png" matChipAvatar>
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg" matChipAvatar>
Husi
<button matChipRemove>
<mat-icon>cancel</mat-icon>
Expand Down
22 changes: 19 additions & 3 deletions src/material-experimental/mdc-chips/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ ng_module(
"**/*.spec.ts",
],
),
assets = [":chips_scss"] + glob(["**/*.html"]),
assets = [
":chip_scss",
":chip_set_scss",
] + glob(["**/*.html"]),
deps = [
"//src:dev_mode_types",
"//src/material-experimental/mdc-core",
Expand All @@ -40,8 +43,21 @@ sass_library(
)

sass_binary(
name = "chips_scss",
src = "chips.scss",
name = "chip_scss",
src = "chip.scss",
include_paths = [
"external/npm/node_modules",
],
deps = [
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
"//src/material/core:core_scss_lib",
],
)

sass_binary(
name = "chip_set_scss",
src = "chip-set.scss",
include_paths = [
"external/npm/node_modules",
],
Expand Down
106 changes: 53 additions & 53 deletions src/material-experimental/mdc-chips/_chips-theme.scss
Original file line number Diff line number Diff line change
@@ -1,93 +1,93 @@
@use '@material/chips/deprecated' as mdc-chips;
@use '@material/chips/chip' as mdc-chip;
@use '@material/chips/chip-theme' as mdc-chip-theme;
@use '@material/chips/chip-set' as mdc-chip-set;
@use '@material/theme/theme-color' as mdc-theme-color;
@use '@material/theme/color-palette' as mdc-color-palette;
@use 'sass:color';
@use 'sass:map';
@use '../mdc-helpers/mdc-helpers';
@use '../../material/core/typography/typography';
@use '../../material/core/theming/theming';

@mixin _selected-color($color) {
@include mdc-chips.fill-color($color, $query: mdc-helpers.$mat-theme-styles-query);
@include mdc-chips.ink-color(text-primary-on-dark, $query: mdc-helpers.$mat-theme-styles-query);
@include mdc-chips.selected-ink-color-without-ripple_(
text-primary-on-dark,
$query: mdc-helpers.$mat-theme-styles-query
);
@include mdc-chips.leading-icon-color(text-primary-on-dark,
$query: mdc-helpers.$mat-theme-styles-query);
@include mdc-chips.trailing-icon-color(text-primary-on-dark,
$query: mdc-helpers.$mat-theme-styles-query);
// Customizes the appearance of a chip. Note that ideally we would be doing this using the
// `theme-styles` mixin, however it has the following problems:
// 1. Some of MDC's base styles have **very** high specificity. E.g. setting the background of a
// non-selected, enabled chip uses a selector like `.chip:not(.selected):not(.disabled)` instead of
// just `.chip`. This specificity increase has a ripple effect over all other components that are
// built on top of ours, making overrides extremely difficult and brittle.
// 2. Including the individual mixins allows us to avoid a lot of unnecessary CSS (~35kb in the
// dev app theme).
@mixin _chip-variant($background, $foreground) {
@include mdc-chip-theme.container-color($background);
@include mdc-chip-theme.icon-color($foreground);
@include mdc-chip-theme.trailing-action-color($foreground);
@include mdc-chip-theme.checkmark-color($foreground);
@include mdc-chip-theme.text-label-color($foreground);

// Technically the avatar is only supposed to have an image, but we also allow for icons.
// Set the color so the icons inherit the correct color.
.mat-mdc-chip-avatar {
color: $foreground;
}
}

@mixin _colored-chip($palette) {
$background: theming.get-color-from-palette($palette);
$foreground: theming.get-color-from-palette($palette, default-contrast);

&.mat-mdc-chip-selected,
&.mat-mdc-chip-highlighted {
@include _chip-variant($background, $foreground);
}
}

@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$primary: theming.get-color-from-palette(map.get($config, primary));
$accent: theming.get-color-from-palette(map.get($config, accent));
$warn: theming.get-color-from-palette(map.get($config, warn));
$background: map.get($config, background);
$unselected-background: theming.get-color-from-palette($background, unselected-chip);

// Save original values of MDC global variables. We need to save these so we can restore the
// variables to their original values and prevent unintended side effects from using this mixin.
$orig-mdc-chips-fill-color-default: mdc-chips.$fill-color-default;
$orig-mdc-chips-ink-color-default: mdc-chips.$ink-color-default;
$orig-mdc-chips-icon-color: mdc-chips.$icon-color;
$primary: map.get($config, primary);
$accent: map.get($config, accent);
$warn: map.get($config, warn);
$foreground: map.get($config, foreground);
$is-dark: map.get($config, is-dark);

@include mdc-helpers.mat-using-mdc-theme($config) {
mdc-chips.$fill-color-default:
color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%);
mdc-chips.$ink-color-default: rgba(mdc-theme-color.prop-value(on-surface), 0.87);
mdc-chips.$icon-color: mdc-theme-color.prop-value(on-surface);

@include mdc-chips.set-core-styles($query: mdc-helpers.$mat-theme-styles-query);
@include mdc-chips.without-ripple($query: mdc-helpers.$mat-theme-styles-query);
.mat-mdc-standard-chip {
@include _chip-variant(
color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%),
if($is-dark, mdc-color-palette.$grey-50, mdc-color-palette.$grey-900)
);

.mat-mdc-chip {
@include mdc-chips.fill-color-accessible($unselected-background,
$query: mdc-helpers.$mat-theme-styles-query);

// mdc-chip-fill-color-accessible includes mdc-chip-selected-ink-color which overrides the
// opacity so selected chips always show a ripple.
// Include the same mixins but use mdc-chip-selected-ink-color-without-ripple
&.mat-primary {
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
@include _selected-color($primary);
}
@include _colored-chip($primary);
}

&.mat-accent {
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
@include _selected-color($accent);
}
@include _colored-chip($accent);
}

&.mat-warn {
&.mdc-chip--selected, &.mat-mdc-chip-highlighted {
@include _selected-color($warn);
}
@include _colored-chip($warn);
}
}
}

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

@mixin typography($config-or-theme) {
$config: typography.private-typography-to-2018-config(
theming.get-typography-config($config-or-theme));
@include mdc-chips.set-core-styles($query: mdc-helpers.$mat-typography-styles-query);
@include mdc-chip-set.core-styles($query: mdc-helpers.$mat-typography-styles-query);
@include mdc-helpers.mat-using-mdc-typography($config) {
@include mdc-chips.without-ripple($query: mdc-helpers.$mat-typography-styles-query);
@include mdc-chip.without-ripple-styles($query: mdc-helpers.$mat-typography-styles-query);
}
}

@mixin density($config-or-theme) {
$density-scale: theming.get-density-config($config-or-theme);
.mat-mdc-chip {
@include mdc-chips.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
@include mdc-chip-theme.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
}
}

Expand Down
151 changes: 151 additions & 0 deletions src/material-experimental/mdc-chips/chip-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* @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 {
AfterViewInit,
ChangeDetectorRef,
Directive,
ElementRef,
Inject,
Input,
OnChanges,
OnDestroy,
SimpleChanges,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {
MDCChipActionAdapter,
MDCChipActionFoundation,
MDCChipActionType,
MDCChipPrimaryActionFoundation,
} from '@material/chips';
import {emitCustomEvent} from './emit-event';
import {
CanDisable,
HasTabIndex,
mixinDisabled,
mixinTabIndex,
} from '@angular/material-experimental/mdc-core';

const _MatChipActionMixinBase = mixinTabIndex(mixinDisabled(class {}), -1);

/**
* Interactive element within a chip.
* @docs-private
*/
@Directive({
selector: '[matChipAction]',
inputs: ['disabled', 'tabIndex'],
host: {
'class': 'mdc-evolution-chip__action mat-mdc-chip-action',
'[class.mdc-evolution-chip__action--primary]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`,
// Note that while our actions are interactive, we have to add the `--presentational` class,
// in order to avoid some super-specific `:hover` styles from MDC.
'[class.mdc-evolution-chip__action--presentational]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`,
'[class.mdc-evolution-chip__action--trailing]': `_getFoundation().actionType() === ${MDCChipActionType.TRAILING}`,
'[attr.tabindex]': '(disabled || !isInteractive) ? null : tabIndex',
'[attr.disabled]': "disabled ? '' : null",
'[attr.aria-disabled]': 'disabled',
'(click)': '_handleClick($event)',
'(keydown)': '_handleKeydown($event)',
},
})
export class MatChipAction
extends _MatChipActionMixinBase
implements AfterViewInit, OnDestroy, CanDisable, HasTabIndex, OnChanges
{
private _document: Document;
private _foundation: MDCChipActionFoundation;
private _adapter: MDCChipActionAdapter = {
focus: () => this.focus(),
getAttribute: (name: string) => this._elementRef.nativeElement.getAttribute(name),
setAttribute: (name: string, value: string) => {
// MDC tries to update the tabindex directly in the DOM when navigating using the keyboard
// which overrides our own handling. If we detect such a case, assign it to the same property
// as the Angular binding in order to maintain consistency.
if (name === 'tabindex') {
this._updateTabindex(parseInt(value));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use Number(value) here instead? IIRC Number is more predictable (e.g. doesn't interpret a leading 0 as octal)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been using parseInt for cases like this everywhere else so I went along with it. I also think that it's unlikely for somebody to write out tabindex="007".

} else {
this._elementRef.nativeElement.setAttribute(name, value);
}
},
removeAttribute: (name: string) => {
if (name !== 'tabindex') {
this._elementRef.nativeElement.removeAttribute(name);
}
},
getElementID: () => this._elementRef.nativeElement.id,
emitEvent: <T>(eventName: string, data: T) => {
emitCustomEvent<T>(this._elementRef.nativeElement, this._document, eventName, data, true);
},
};

/** Whether the action is interactive. */
@Input() isInteractive = true;

_handleClick(_event: MouseEvent) {
// Usually these events can't happen while the chip is disabled since the browser won't
// allow them which is what MDC seems to rely on, however the event can be faked in tests.
if (!this.disabled && this.isInteractive) {
this._foundation.handleClick();
}
}

_handleKeydown(event: KeyboardEvent) {
// Usually these events can't happen while the chip is disabled since the browser won't
// allow them which is what MDC seems to rely on, however the event can be faked in tests.
if (!this.disabled && this.isInteractive) {
this._foundation.handleKeydown(event);
}
}

protected _createFoundation(adapter: MDCChipActionAdapter): MDCChipActionFoundation {
return new MDCChipPrimaryActionFoundation(adapter);
}

constructor(
public _elementRef: ElementRef,
@Inject(DOCUMENT) _document: any,
private _changeDetectorRef: ChangeDetectorRef,
) {
super();
this._foundation = this._createFoundation(this._adapter);

if (_elementRef.nativeElement.nodeName === 'BUTTON') {
_elementRef.nativeElement.setAttribute('type', 'button');
}
}

ngAfterViewInit() {
this._foundation.init();
this._foundation.setDisabled(this.disabled);
}

ngOnChanges(changes: SimpleChanges) {
if (changes['disabled']) {
this._foundation.setDisabled(this.disabled);
}
}

ngOnDestroy() {
this._foundation.destroy();
}

focus() {
this._elementRef.nativeElement.focus();
}

_getFoundation() {
return this._foundation;
}

_updateTabindex(value: number) {
this.tabIndex = value;
this._changeDetectorRef.markForCheck();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ describe('MDC-based MatChipEditInput', () => {
});

@Component({
template: `<span matChipEditInput></span>`,
template: `<mat-chip><span matChipEditInput></span></mat-chip>`,
})
class ChipEditInputContainer {}
2 changes: 1 addition & 1 deletion src/material-experimental/mdc-chips/chip-edit-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {DOCUMENT} from '@angular/common';
@Directive({
selector: 'span[matChipEditInput]',
host: {
'class': 'mdc-chip__primary-action mat-chip-edit-input',
'class': 'mat-chip-edit-input',
'role': 'textbox',
'tabindex': '-1',
'contenteditable': 'true',
Expand Down
Loading