Skip to content

Commit fe14d88

Browse files
authored
feat(material-experimental/mdc-list): implement selection list (#20279)
* feat(material-experimental/mdc-list): implement selection list Implements the MDC-based selection list. Accessibility and various other concepts/features of the MDC list have been audited. Issues have been reported upstream and workarounds where possible and reasonable have been applied w/ proper comments. https://github.com/material-components/material-components-web/issues?q=is%3Aissue+author%3Adevversion+mdc-list+. Also various other remaining takss for the MDC-based list have been completed/resolved, except for implementing the dense attribute. It's unclear yet how that can work out. * fixup! feat(material-experimental/mdc-list): implement selection list Address feedback. Cleanup code and circular dependency.
1 parent f64b60d commit fe14d88

29 files changed

+3293
-306
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
/src/material-experimental/mdc-form-field/** @devversion @mmalerba
107107
/src/material-experimental/mdc-helpers/** @mmalerba
108108
/src/material-experimental/mdc-input/** @devversion @mmalerba
109-
/src/material-experimental/mdc-list/** @mmalerba
109+
/src/material-experimental/mdc-list/** @mmalerba @devversion
110110
/src/material-experimental/mdc-menu/** @crisbeto
111111
/src/material-experimental/mdc-select/** @crisbeto
112112
/src/material-experimental/mdc-progress-spinner/** @andrewseguin

src/dev-app/list/list-demo.html

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ <h2>Selection list</h2>
118118

119119
<mat-selection-list #groceries [ngModel]="selectedOptions"
120120
(ngModelChange)="onSelectedOptionsChange($event)"
121-
(change)="changeEventCount = changeEventCount + 1"
121+
(selectionChange)="changeEventCount = changeEventCount + 1"
122122
[disabled]="selectionListDisabled"
123123
[disableRipple]="selectionListRippleDisabled"
124124
color="primary">
@@ -180,4 +180,23 @@ <h2>Single Selection list</h2>
180180

181181
<p>Selected: {{favoriteOptions | json}}</p>
182182
</div>
183+
184+
<div>
185+
<h2>Line alignment</h2>
186+
187+
<mat-list>
188+
<mat-list-item *ngFor="let link of links">
189+
<span mat-line>{{ link.name }}</span>
190+
<span>Not in an <i>matLine</i></span>
191+
</mat-list-item>
192+
</mat-list>
193+
194+
<mat-selection-list>
195+
<mat-list-option value="first">First</mat-list-option>
196+
<mat-list-option value="second">
197+
<span matLine>Second</span>
198+
<span>Not in an <i>matLine</i></span>
199+
</mat-list-option>
200+
</mat-selection-list>
201+
</div>
183202
</div>

src/dev-app/mdc-list/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ ng_module(
1111
],
1212
deps = [
1313
"//src/material-experimental/mdc-button",
14-
"//src/material-experimental/mdc-checkbox",
1514
"//src/material-experimental/mdc-list",
1615
"//src/material/icon",
1716
"@npm//@angular/router",

src/dev-app/mdc-list/mdc-list-demo-module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {CommonModule} from '@angular/common';
1010
import {NgModule} from '@angular/core';
1111
import {FormsModule} from '@angular/forms';
1212
import {MatButtonModule} from '@angular/material-experimental/mdc-button';
13-
import {MatCheckboxModule} from '@angular/material-experimental/mdc-checkbox';
1413
import {MatListModule} from '@angular/material-experimental/mdc-list';
1514
import {MatIconModule} from '@angular/material/icon';
1615
import {RouterModule} from '@angular/router';
@@ -21,7 +20,6 @@ import {MdcListDemo} from './mdc-list-demo';
2120
CommonModule,
2221
FormsModule,
2322
MatButtonModule,
24-
MatCheckboxModule,
2523
MatIconModule,
2624
MatListModule,
2725
RouterModule.forChild([{path: '', component: MdcListDemo}]),

src/dev-app/mdc-list/mdc-list-demo.html

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ <h2>Selection list</h2>
118118

119119
<mat-selection-list #groceries [ngModel]="selectedOptions"
120120
(ngModelChange)="onSelectedOptionsChange($event)"
121-
(change)="changeEventCount = changeEventCount + 1"
121+
(selectionChange)="changeEventCount = changeEventCount + 1"
122122
[disabled]="selectionListDisabled"
123123
[disableRipple]="selectionListRippleDisabled"
124124
color="primary">
@@ -148,14 +148,16 @@ <h2>Selection list</h2>
148148
<p>Change Event Count {{changeEventCount}}</p>
149149
<p>Model Change Event Count {{modelChangeEventCount}}</p>
150150
<p>
151-
<mat-checkbox [(ngModel)]="selectionListDisabled">
152-
Disable Selection List
153-
</mat-checkbox>
151+
<label>
152+
Disable selection list
153+
<input type="checkbox" [(ngModel)]="selectionListDisabled">
154+
</label>
154155
</p>
155156
<p>
156-
<mat-checkbox [(ngModel)]="selectionListRippleDisabled">
157+
<label>
157158
Disable Selection List ripples
158-
</mat-checkbox>
159+
<input type="checkbox" [(ngModel)]="selectionListRippleDisabled">
160+
</label>
159161
</p>
160162
<p>
161163
<button mat-raised-button (click)="groceries.selectAll()">Select all</button>
@@ -180,4 +182,23 @@ <h2>Single Selection list</h2>
180182

181183
<p>Selected: {{favoriteOptions | json}}</p>
182184
</div>
185+
186+
<div>
187+
<h2>Line alignment</h2>
188+
189+
<mat-list>
190+
<mat-list-item *ngFor="let link of links">
191+
<span mat-line>{{ link.name }}</span>
192+
<span>Not in an <i>matLine</i></span>
193+
</mat-list-item>
194+
</mat-list>
195+
196+
<mat-selection-list>
197+
<mat-list-option value="first">First</mat-list-option>
198+
<mat-list-option value="second">
199+
<span matLine>Second</span>
200+
<span>Not in an <i>matLine</i></span>
201+
</mat-list-option>
202+
</mat-selection-list>
203+
</div>
183204
</div>

src/material-experimental/mdc-checkbox/_checkbox-theme.scss

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
@import '@material/theme/functions.import';
66
@import '../mdc-helpers/mdc-helpers';
77

8+
// Mixin that includes the checkbox theme styles with a given palette.
9+
// By default, the MDC checkbox always uses the `secondary` palette.
10+
@mixin _mdc-checkbox-styles-with-color($color) {
11+
$orig-mdc-checkbox-mark-color: $mdc-checkbox-mark-color;
12+
$mdc-checkbox-mark-color: mdc-theme-prop-value(on-#{$color}) !global;
13+
$orig-mdc-checkbox-baseline-theme-color: $mdc-checkbox-baseline-theme-color;
14+
$mdc-checkbox-baseline-theme-color: $color !global;
15+
16+
@include mdc-checkbox-without-ripple($query: $mat-theme-styles-query);
17+
18+
$mdc-checkbox-mark-color: $orig-mdc-checkbox-mark-color !global;
19+
$mdc-checkbox-baseline-theme-color: $orig-mdc-checkbox-baseline-theme-color !global;
20+
}
21+
822
@mixin mat-mdc-checkbox-color($config-or-theme) {
923
$config: mat-get-color-config($config-or-theme);
1024
$primary: mat-color(map-get($config, primary));
@@ -13,18 +27,14 @@
1327

1428
// Save original values of MDC global variables. We need to save these so we can restore the
1529
// variables to their original values and prevent unintended side effects from using this mixin.
16-
$orig-mdc-checkbox-mark-color: $mdc-checkbox-mark-color;
17-
$orig-mdc-checkbox-baseline-theme-color: $mdc-checkbox-baseline-theme-color;
1830
$orig-mdc-checkbox-border-color: $mdc-checkbox-border-color;
1931
$orig-mdc-checkbox-disabled-color: $mdc-checkbox-disabled-color;
2032

2133
@include mat-using-mdc-theme($config) {
22-
$mdc-checkbox-mark-color: mdc-theme-prop-value(on-primary) !global;
23-
$mdc-checkbox-baseline-theme-color: primary !global;
2434
$mdc-checkbox-border-color: rgba(mdc-theme-prop-value(on-surface), 0.54) !global;
2535
$mdc-checkbox-disabled-color: rgba(mdc-theme-prop-value(on-surface), 0.26) !global;
2636

27-
@include mdc-checkbox-without-ripple($query: $mat-theme-styles-query);
37+
@include _mdc-checkbox-styles-with-color(primary);
2838
@include mdc-form-field-core-styles($query: $mat-theme-styles-query);
2939

3040
// MDC's checkbox doesn't support a `color` property. We add support for it by adding a CSS
@@ -41,21 +51,15 @@
4151
}
4252

4353
&.mat-accent {
44-
$mdc-checkbox-mark-color: mdc-theme-prop-value(on-secondary) !global;
45-
$mdc-checkbox-baseline-theme-color: secondary !global;
46-
47-
@include mdc-checkbox-without-ripple($query: $mat-theme-styles-query);
54+
@include _mdc-checkbox-styles-with-color(secondary);
4855

4956
.mdc-checkbox--selected ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
5057
background: $accent;
5158
}
5259
}
5360

5461
&.mat-warn {
55-
$mdc-checkbox-mark-color: mdc-theme-prop-value(on-error) !global;
56-
$mdc-checkbox-baseline-theme-color: error !global;
57-
58-
@include mdc-checkbox-without-ripple($query: $mat-theme-styles-query);
62+
@include _mdc-checkbox-styles-with-color(error);
5963

6064
.mdc-checkbox--selected ~ .mat-mdc-checkbox-ripple .mat-ripple-element {
6165
background: $warn;
@@ -65,8 +69,6 @@
6569
}
6670

6771
// Restore original values of MDC global variables.
68-
$mdc-checkbox-mark-color: $orig-mdc-checkbox-mark-color !global;
69-
$mdc-checkbox-baseline-theme-color: $orig-mdc-checkbox-baseline-theme-color !global;
7072
$mdc-checkbox-border-color: $orig-mdc-checkbox-border-color !global;
7173
$mdc-checkbox-disabled-color: $orig-mdc-checkbox-disabled-color !global;
7274
}

src/material-experimental/mdc-list/BUILD.bazel

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ ng_module(
1919
"**/*.spec.ts",
2020
],
2121
),
22-
assets = [":list_scss"] + glob(["**/*.html"]),
22+
assets = [
23+
":list_scss",
24+
":list_option_scss",
25+
] + glob(["**/*.html"]),
2326
module_name = "@angular/material-experimental/mdc-list",
2427
deps = [
2528
"//src/cdk/collections",
@@ -34,6 +37,7 @@ sass_library(
3437
name = "mdc_list_scss_lib",
3538
srcs = glob(["**/_*.scss"]),
3639
deps = [
40+
"//src/material-experimental/mdc-checkbox:mdc_checkbox_scss_lib",
3741
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
3842
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
3943
],
@@ -51,6 +55,18 @@ sass_binary(
5155
],
5256
)
5357

58+
sass_binary(
59+
name = "list_option_scss",
60+
src = "list-option.scss",
61+
include_paths = [
62+
"external/npm/node_modules",
63+
],
64+
deps = [
65+
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
66+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
67+
],
68+
)
69+
5470
ng_test_library(
5571
name = "list_tests_lib",
5672
srcs = glob(
@@ -59,9 +75,13 @@ ng_test_library(
5975
),
6076
deps = [
6177
":mdc-list",
78+
"//src/cdk/keycodes",
6279
"//src/cdk/testing/private",
6380
"//src/cdk/testing/testbed",
81+
"//src/material/core",
82+
"@npm//@angular/forms",
6483
"@npm//@angular/platform-browser",
84+
"@npm//@material/list",
6585
],
6686
)
6787

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import '@material/ripple/variables.import';
2+
@import '../mdc-helpers/mdc-helpers';
3+
4+
// Mixin that provides colors for the various states of an interactive list-item. MDC
5+
// has integrated styles for these states but relies on their complex ripples for it.
6+
@mixin _mat-mdc-interactive-list-item-state-colors($config) {
7+
$is-dark-theme: map-get($config, is-dark);
8+
$state-opacities:
9+
if($is-dark-theme, $mdc-ripple-light-ink-opacities, $mdc-ripple-dark-ink-opacities);
10+
11+
.mat-mdc-list-item-interactive {
12+
&::before {
13+
background: if($is-dark-theme, white, black);
14+
}
15+
16+
&.mdc-list-item--selected::before {
17+
background: mat-color(map_get($config, primary));
18+
opacity: map-get($state-opacities, selected);
19+
}
20+
21+
&:focus::before {
22+
opacity: map-get($state-opacities, focus);
23+
}
24+
}
25+
26+
// MDC still shows focus/selected state if the option is disabled. Just the hover
27+
// styles should not show up.
28+
.mat-mdc-list-item-interactive:not(.mdc-list-item--disabled) {
29+
&:hover::before {
30+
opacity: map-get($state-opacities, hover);
31+
}
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@import '@material/theme/mixins.import';
2+
@import '@material/list/mixins.import';
3+
@import '@material/checkbox/mixins.import';
4+
5+
@import '../mdc-checkbox/checkbox-theme';
6+
7+
// Mixin that overrides the selected item and checkbox colors for list options. By
8+
// default, the MDC list uses the `primary` color for list items. The MDC checkbox
9+
// inside list options by default uses the `primary` color too.
10+
@mixin _mat-mdc-list-option-color-override($color) {
11+
& .mdc-list-item__meta, & .mdc-list-item__graphic {
12+
@include _mdc-checkbox-styles-with-color($color);
13+
}
14+
15+
&.mdc-list-item--selected {
16+
@include mdc-list-item-primary-text-ink-color($color);
17+
@include mdc-list-item-graphic-ink-color($color);
18+
19+
&::before {
20+
@include mdc-theme-prop(background, $color);
21+
}
22+
}
23+
}
24+
25+
@mixin _mat-mdc-list-option-density-styles($density-scale) {
26+
.mat-mdc-list-option {
27+
.mdc-list-item__meta, .mdc-list-item__graphic {
28+
.mdc-checkbox {
29+
@include mdc-checkbox-density($density-scale, $query: $mat-base-styles-query);
30+
}
31+
}
32+
}
33+
}
34+
35+
@mixin _mat-mdc-list-option-typography-styles() {
36+
.mat-mdc-list-option {
37+
.mdc-list-item__meta, .mdc-list-item__graphic {
38+
@include mdc-checkbox-without-ripple($query: $mat-typography-styles-query);
39+
}
40+
}
41+
}

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
11
@import '@material/density/functions.import';
22
@import '@material/list/variables.import';
33
@import '@material/list/mixins.import';
4-
@import '@material/ripple/variables.import';
4+
5+
@import './interactive-list-theme';
6+
@import './list-option-theme';
57
@import '../mdc-helpers/mdc-helpers';
68

9+
710
// TODO: implement mat-list[dense] once density system is in master
811

12+
913
@mixin mat-mdc-list-color($config-or-theme) {
1014
$config: mat-get-color-config($config-or-theme);
11-
$is-dark-theme: map-get($config, is-dark);
12-
$state-opacities:
13-
if($is-dark-theme, $mdc-ripple-light-ink-opacities, $mdc-ripple-dark-ink-opacities);
15+
16+
// MDC's state styles are tied in with their ripple. Since we don't use the MDC
17+
// ripple, we need to add the hover, focus and selected states manually.
18+
@include _mat-mdc-interactive-list-item-state-colors($config);
1419

1520
@include mat-using-mdc-theme($config) {
1621
@include mdc-list-without-ripple($query: $mat-theme-styles-query);
17-
}
1822

19-
// MDC's state styles are tied in with their ripple. Since we don't use the MDC ripple, we need to
20-
// add the hover and focus states manually.
21-
.mat-mdc-list-item-interactive {
22-
&::before {
23-
background: if($is-dark-theme, white, black);
23+
.mat-mdc-list-option {
24+
@include _mat-mdc-list-option-color-override(primary);
2425
}
25-
26-
&:hover::before {
27-
opacity: map-get($state-opacities, hover);
26+
.mat-mdc-list-option.mat-accent {
27+
@include _mat-mdc-list-option-color-override(secondary);
2828
}
29-
30-
&:focus::before {
31-
opacity: map-get($state-opacities, focus);
29+
.mat-mdc-list-option.mat-warn {
30+
@include _mat-mdc-list-option-color-override(error);
3231
}
3332
}
3433
}
@@ -42,18 +41,21 @@
4241
);
4342

4443
// MDC list provides a mixin called `mdc-list-single-line-density`, but we cannot use
45-
// that mixin the generated generated density styles are scoped to `.mdc-list-item`, while
44+
// that mixin, as the generated generated density styles are scoped to `.mdc-list-item`, while
4645
// the styles should actually only affect single-line list items. This has been reported as
4746
// a bug in the MDC repository: https://github.com/material-components/material-components-web/issues/5737.
4847
.mat-mdc-list-item-single-line {
4948
@include mdc-list-single-line-height($height);
5049
}
50+
51+
@include _mat-mdc-list-option-density-styles($density-scale);
5152
}
5253

5354
@mixin mat-mdc-list-typography($config-or-theme) {
5455
$config: mat-get-typography-config($config-or-theme);
5556
@include mat-using-mdc-typography($config) {
5657
@include mdc-list-without-ripple($query: $mat-typography-styles-query);
58+
@include _mat-mdc-list-option-typography-styles();
5759
}
5860
}
5961

0 commit comments

Comments
 (0)