Skip to content

Commit d3c00c3

Browse files
authored
feat(material-expeirmental/mdc-list): add support for focus/hover states and ripples (#19168)
* feat(material-expeirmental/mdc-list): add support for focus/hover states and ripples * add state styles * fix lint * address feedback * address feedback
1 parent a4171e0 commit d3c00c3

File tree

8 files changed

+114
-15
lines changed

8 files changed

+114
-15
lines changed

src/material-experimental/mdc-list/_list-theme.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
@import '@material/list/mixins.import';
2+
@import '@material/ripple/variables.import';
23
@import '../mdc-helpers/mdc-helpers';
34

45
@mixin mat-mdc-list-theme($theme) {
6+
$is-dark-theme: map-get($theme, is-dark);
7+
$state-opacities:
8+
if($is-dark-theme, $mdc-ripple-light-ink-opacities, $mdc-ripple-dark-ink-opacities);
9+
510
@include mat-using-mdc-theme($theme) {
611
@include mdc-list-without-ripple($query: $mat-theme-styles-query);
712
}
13+
14+
// MDC's state styles are tied in with their ripple. Since we don't use the MDC ripple, we need to
15+
// add the hover and focus states manually.
16+
.mat-mdc-list-item-interactive {
17+
&::before {
18+
background: if($is-dark-theme, white, black);
19+
}
20+
21+
&:hover::before {
22+
opacity: map-get($state-opacities, hover);
23+
}
24+
25+
&:focus::before {
26+
opacity: map-get($state-opacities, focus);
27+
}
28+
}
829
}
930

1031
@mixin mat-mdc-list-typography($config) {

src/material-experimental/mdc-list/action-list.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ import {MatListBase} from './list-base';
1919
styleUrls: ['list.css'],
2020
encapsulation: ViewEncapsulation.None,
2121
changeDetection: ChangeDetectionStrategy.OnPush,
22+
providers: [
23+
{provide: MatListBase, useExisting: MatActionList},
24+
]
2225
})
2326
export class MatActionList extends MatListBase {}

src/material-experimental/mdc-list/list-base.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,53 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AfterContentInit, Directive, ElementRef, NgZone, OnDestroy, QueryList} from '@angular/core';
10-
import {setLines} from '@angular/material/core';
9+
import {Platform} from '@angular/cdk/platform';
10+
import {
11+
AfterContentInit,
12+
Directive,
13+
ElementRef,
14+
HostBinding,
15+
NgZone,
16+
OnDestroy,
17+
QueryList
18+
} from '@angular/core';
19+
import {RippleConfig, RippleRenderer, RippleTarget, setLines} from '@angular/material/core';
1120
import {Subscription} from 'rxjs';
1221
import {startWith} from 'rxjs/operators';
1322

14-
export class MatListBase {}
23+
@Directive()
24+
export class MatListBase {
25+
// @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
26+
// metadata is not inherited by child classes, instead the host binding data is defined in a way
27+
// that can be inherited.
28+
// tslint:disable-next-line:no-host-decorator-in-concrete
29+
@HostBinding('class.mdc-list--non-interactive')
30+
_isNonInteractive: boolean;
31+
}
1532

1633
@Directive()
17-
export abstract class MatListItemBase implements AfterContentInit, OnDestroy {
34+
export abstract class MatListItemBase implements AfterContentInit, OnDestroy, RippleTarget {
1835
lines: QueryList<ElementRef<Element>>;
1936

37+
rippleConfig: RippleConfig = {};
38+
39+
rippleDisabled: boolean;
40+
2041
private _subscriptions = new Subscription();
2142

22-
constructor(protected _element: ElementRef, protected _ngZone: NgZone) {}
43+
private _rippleRenderer: RippleRenderer;
44+
45+
constructor(protected _element: ElementRef, protected _ngZone: NgZone, listBase: MatListBase,
46+
platform: Platform) {
47+
const el = this._element.nativeElement;
48+
this.rippleDisabled = listBase._isNonInteractive;
49+
if (!listBase._isNonInteractive) {
50+
el.classList.add('mat-mdc-list-item-interactive');
51+
}
52+
this._rippleRenderer =
53+
new RippleRenderer(this, this._ngZone, el, platform);
54+
this._rippleRenderer.setupTriggerEvents(el);
55+
}
2356

2457
ngAfterContentInit() {
2558
this._monitorLines();
@@ -44,5 +77,6 @@ export abstract class MatListItemBase implements AfterContentInit, OnDestroy {
4477

4578
ngOnDestroy() {
4679
this._subscriptions.unsubscribe();
80+
this._rippleRenderer._removeTriggerEvents();
4781
}
4882
}

src/material-experimental/mdc-list/list.scss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@
6363
}
6464
}
6565

66+
.mat-mdc-list-avatar {
67+
// MDC's styles don't specify this, but they probably should. It gives a nicer experience when the
68+
// image is not 1:1 aspect ratio.
69+
// See https://github.com/material-components/material-components-web/issues/5897
70+
object-fit: cover;
71+
}
72+
73+
// MDC expects that the list items are always `<li>`, since we actually use `<button>` in some
74+
// cases, we need to make sure it expands to fill the available width.
75+
.mat-mdc-list-item,
76+
.mat-mdc-list-option {
77+
width: 100%;
78+
box-sizing: border-box;
79+
}
80+
6681
// MDC doesn't have list dividers, so we use mat-divider and style appropriately.
6782
.mat-mdc-list-item,
6883
.mat-mdc-list-option {
@@ -81,3 +96,15 @@
8196
}
8297
}
8398
}
99+
100+
// MDC's state styles are included with their ripple which we don't use. Instead we add the focus
101+
// and hover styles ourselves using this pseudo-element
102+
.mat-mdc-list-item-interactive::before {
103+
content: '';
104+
position: absolute;
105+
top: 0;
106+
left: 0;
107+
bottom: 0;
108+
right: 0;
109+
opacity: 0;
110+
}

src/material-experimental/mdc-list/list.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {Platform} from '@angular/cdk/platform';
910
import {
1011
ChangeDetectionStrategy,
1112
Component,
@@ -61,8 +62,13 @@ export class MatListSubheaderCssMatStyler {}
6162
styleUrls: ['list.css'],
6263
encapsulation: ViewEncapsulation.None,
6364
changeDetection: ChangeDetectionStrategy.OnPush,
65+
providers: [
66+
{provide: MatListBase, useExisting: MatList},
67+
]
6468
})
65-
export class MatList extends MatListBase {}
69+
export class MatList extends MatListBase {
70+
_isNonInteractive = true;
71+
}
6672

6773
@Component({
6874
selector: 'mat-list-item, a[mat-list-item], button[mat-list-item]',
@@ -78,7 +84,7 @@ export class MatListItem extends MatListItemBase {
7884
@ContentChildren(MatLine, {read: ElementRef, descendants: true}) lines:
7985
QueryList<ElementRef<Element>>;
8086

81-
constructor(element: ElementRef, ngZone: NgZone) {
82-
super(element, ngZone);
87+
constructor(element: ElementRef, ngZone: NgZone, listBase: MatListBase, platform: Platform) {
88+
super(element, ngZone, listBase, platform);
8389
}
8490
}

src/material-experimental/mdc-list/module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10-
import {MatLineModule} from '@angular/material/core';
10+
import {MatLineModule, MatRippleModule} from '@angular/material/core';
1111
import {MatDividerModule} from '@angular/material/divider';
1212
import {MatActionList} from './action-list';
1313
import {
@@ -21,7 +21,10 @@ import {MatNavList} from './nav-list';
2121
import {MatListOption, MatSelectionList} from './selection-list';
2222

2323
@NgModule({
24-
imports: [MatLineModule],
24+
imports: [
25+
MatLineModule,
26+
MatRippleModule,
27+
],
2528
exports: [
2629
MatList,
2730
MatActionList,

src/material-experimental/mdc-list/nav-list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ import {MatListBase} from './list-base';
2525
encapsulation: ViewEncapsulation.None,
2626
changeDetection: ChangeDetectionStrategy.OnPush,
2727
providers: [
28+
{provide: MatListBase, useExisting: MatNavList},
2829
/**
2930
* @deprecated Provider for `MatList` will be removed, use `MatNavList` instead.
3031
* @breaking-change 11.0.0
3132
*/
32-
{provide: MatList, useExisting: MatNavList}
33+
{provide: MatList, useExisting: MatNavList},
3334
]
3435
})
3536
export class MatNavList extends MatListBase {}

src/material-experimental/mdc-list/selection-list.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {Platform} from '@angular/cdk/platform';
910
import {
1011
ChangeDetectionStrategy,
1112
Component,
@@ -44,8 +45,11 @@ export class MatSelectionListChange {
4445
templateUrl: 'selection-list.html',
4546
styleUrls: ['list.css'],
4647
encapsulation: ViewEncapsulation.None,
47-
providers: [MAT_SELECTION_LIST_VALUE_ACCESSOR],
48-
changeDetection: ChangeDetectionStrategy.OnPush
48+
providers: [
49+
MAT_SELECTION_LIST_VALUE_ACCESSOR,
50+
{provide: MatListBase, useExisting: MatSelectionList}
51+
],
52+
changeDetection: ChangeDetectionStrategy.OnPush,
4953
})
5054
export class MatSelectionList extends MatListBase {}
5155

@@ -63,7 +67,7 @@ export class MatListOption extends MatListItemBase {
6367
@ContentChildren(MatLine, {read: ElementRef, descendants: true}) lines:
6468
QueryList<ElementRef<Element>>;
6569

66-
constructor(element: ElementRef, ngZone: NgZone) {
67-
super(element, ngZone);
70+
constructor(element: ElementRef, ngZone: NgZone, listBase: MatListBase, platform: Platform) {
71+
super(element, ngZone, listBase, platform);
6872
}
6973
}

0 commit comments

Comments
 (0)