Skip to content

fix(material-experimental/mdc-list): support noop animations #23117

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 1 commit into from
Jul 9, 2021
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
11 changes: 9 additions & 2 deletions src/material-experimental/mdc-list/list-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
RippleTarget,
setLines,
} from '@angular/material-experimental/mdc-core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {numbers} from '@material/ripple';
import {Subscription} from 'rxjs';
import {startWith} from 'rxjs/operators';
Expand All @@ -54,12 +55,16 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
/** Host element for the list item. */
_hostElement: HTMLElement;

/** Whether animations are disabled. */
_noopAnimations: boolean;

@ContentChildren(MatListAvatarCssMatStyler, {descendants: false}) _avatars: QueryList<never>;
@ContentChildren(MatListIconCssMatStyler, {descendants: false}) _icons: QueryList<never>;

@Input()
get disableRipple(): boolean {
return this.disabled || this._disableRipple || this._listBase.disableRipple;
return this.disabled || this._disableRipple || this._listBase.disableRipple ||
this._noopAnimations;
}
set disableRipple(value: boolean) { this._disableRipple = coerceBooleanProperty(value); }
private _disableRipple: boolean = false;
Expand Down Expand Up @@ -90,12 +95,14 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy, Ri
constructor(public _elementRef: ElementRef<HTMLElement>, protected _ngZone: NgZone,
private _listBase: MatListBase, private _platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS)
globalRippleOptions?: RippleGlobalOptions) {
globalRippleOptions?: RippleGlobalOptions,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {
// We have to clone the object, because we don't want to mutate a global value when we assign
// the `animation` further down. The downside of doing this is that the ripple renderer won't
// pick up dynamic changes to `disabled`, but it's not something we officially support.
this.rippleConfig = {...(globalRippleOptions || {})};
this._hostElement = this._elementRef.nativeElement;
this._noopAnimations = animationMode === 'NoopAnimations';

if (!this.rippleConfig.animation) {
this.rippleConfig.animation = {
Expand Down
31 changes: 19 additions & 12 deletions src/material-experimental/mdc-list/list-option.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,29 @@
@use '../../cdk/a11y';
@use './list-option-trailing-avatar-compat';

// The MDC-based list-option uses the MDC checkbox for the selection indicators.
// We need to ensure that the checkbox styles are included for the list-option.
@include mdc-checkbox.without-ripple($query: mdc-helpers.$mat-base-styles-query);

// For compatibility with the non-MDC list, we support avatars that are shown at the end
// of the list option. We create a class similar to MDC's `--trailing-icon` one.
@include list-option-trailing-avatar-compat.core-styles($query: mdc-helpers.$mat-base-styles-query);

// The internal checkbox is purely decorative, but because it's an `input`, the user can still
// focus it by tabbing or clicking. Furthermore, `mat-list-option` has the `option` role which
// doesn't allow a nested `input`. We use `display: none` both to remove it from the tab order
// and to prevent focus from reaching it through the screen reader's forms mode. Ideally we'd
// remove the `input` completely, but we can't because MDC uses a `:checked` selector to
// toggle the selected styles.
.mat-mdc-list-option .mdc-checkbox__native-control {
display: none;
.mat-mdc-list-option {
// The MDC-based list-option uses the MDC checkbox for the selection indicators.
// We need to ensure that the checkbox styles are not included for the list-option.
@include mdc-checkbox.without-ripple(
$query: mdc-helpers.$mat-base-styles-without-animation-query);

&:not(._mat-animation-noopable) {
@include mdc-checkbox.without-ripple($query: animation);
}

// The internal checkbox is purely decorative, but because it's an `input`, the user can still
// focus it by tabbing or clicking. Furthermore, `mat-list-option` has the `option` role which
// doesn't allow a nested `input`. We use `display: none` both to remove it from the tab order
// and to prevent focus from reaching it through the screen reader's forms mode. Ideally we'd
// remove the `input` completely, but we can't because MDC uses a `:checked` selector to
// toggle the selected styles.
.mdc-checkbox__native-control {
display: none;
}
}

@include a11y.high-contrast(active, off) {
Expand Down
7 changes: 5 additions & 2 deletions src/material-experimental/mdc-list/list-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
RippleGlobalOptions,
ThemePalette,
} from '@angular/material-experimental/mdc-core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {MatListBase, MatListItemBase} from './list-base';
import {LIST_OPTION, ListOption, MatListOptionCheckboxPosition} from './list-option-types';

Expand Down Expand Up @@ -79,6 +80,7 @@ export interface SelectionList extends MatListBase {
'[class.mdc-list-item--with-trailing-checkbox]': '_hasCheckboxAt("after")',
'[class.mat-accent]': 'color !== "primary" && color !== "warn"',
'[class.mat-warn]': 'color === "warn"',
'[class._mat-animation-noopable]': '_noopAnimations',
'(blur)': '_handleBlur()',
},
templateUrl: 'list-option.html',
Expand Down Expand Up @@ -144,8 +146,9 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit
platform: Platform,
@Inject(SELECTION_LIST) public _selectionList: SelectionList,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalRippleOptions?: RippleGlobalOptions) {
super(element, ngZone, _selectionList, platform, globalRippleOptions);
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalRippleOptions?: RippleGlobalOptions,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {
super(element, ngZone, _selectionList, platform, globalRippleOptions, animationMode);

// By default, we mark all options as unselected. The MDC list foundation will
// automatically update the attribute based on selection. Note that we need to
Expand Down
7 changes: 5 additions & 2 deletions src/material-experimental/mdc-list/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
MAT_RIPPLE_GLOBAL_OPTIONS,
RippleGlobalOptions,
} from '@angular/material-experimental/mdc-core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {MatListBase, MatListItemBase} from './list-base';

@Component({
Expand Down Expand Up @@ -54,6 +55,7 @@ export class MatList extends MatListBase {}
// the trailing meta class. Note that we also add this even if there is no projected `meta`
// content. This is because there is no good way to check for remaining projected content.
'[class.mdc-list-item--with-trailing-meta]': 'lines.length !== 0',
'[class._mat-animation-noopable]': '_noopAnimations',
},
templateUrl: 'list-item.html',
encapsulation: ViewEncapsulation.None,
Expand All @@ -69,7 +71,8 @@ export class MatListItem extends MatListItemBase {
ngZone: NgZone,
listBase: MatListBase,
platform: Platform,
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalRippleOptions?: RippleGlobalOptions) {
super(element, ngZone, listBase, platform, globalRippleOptions);
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalRippleOptions?: RippleGlobalOptions,
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {
super(element, ngZone, listBase, platform, globalRippleOptions, animationMode);
}
}