Skip to content

Commit d57e6ac

Browse files
committed
feat(selection-list): support specifying theme color
Currently the selection list always uses the `accent` color for the underlying pseudo checkbox of list options. In order to make this configurable by the user, we add a `color` input that matches our other usages of the `color` input in order to be consistent. Since we want to allow that a color can be specified for the `<mat-selection-list>` and that it's possible to overwrite the color for an individual list option, we don't use the color mixin as it brings in a lot of overhead and also doesn't make it easy to provide the desired precedence for individual options. Also we **can't** set the color class on the `<mat-selection-list>` as the pseudo checkbox will then ignore the color class set for the individual list option. Closes #15234
1 parent 3ae6eb2 commit d57e6ac

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,14 @@ <h2>Selection list</h2>
120120
(ngModelChange)="onSelectedOptionsChange($event)"
121121
(change)="changeEventCount = changeEventCount + 1"
122122
[disabled]="selectionListDisabled"
123-
[disableRipple]="selectionListRippleDisabled">
123+
[disableRipple]="selectionListRippleDisabled"
124+
color="primary">
124125
<h3 mat-subheader>Groceries</h3>
125126

126127
<mat-list-option value="bananas" checkboxPosition="before">Bananas</mat-list-option>
127128
<mat-list-option selected value="oranges">Oranges</mat-list-option>
128-
<mat-list-option value="apples">Apples</mat-list-option>
129-
<mat-list-option value="strawberries">Strawberries</mat-list-option>
129+
<mat-list-option value="apples" color="accent">Apples</mat-list-option>
130+
<mat-list-option value="strawberries" color="warn">Strawberries</mat-list-option>
130131
</mat-selection-list>
131132

132133
<mat-selection-list [disableRipple]="selectionListRippleDisabled">

src/lib/list/selection-list.spec.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
ViewChildren,
1515
} from '@angular/core';
1616
import {async, ComponentFixture, fakeAsync, TestBed, tick, flush} from '@angular/core/testing';
17-
import {MatRipple, defaultRippleAnimationConfig} from '@angular/material/core';
17+
import {MatRipple, defaultRippleAnimationConfig, ThemePalette} from '@angular/material/core';
1818
import {By} from '@angular/platform-browser';
1919
import {
2020
MatListModule,
@@ -123,6 +123,33 @@ describe('MatSelectionList without forms', () => {
123123
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
124124
});
125125

126+
it('should be able to specify a color for list options', () => {
127+
const optionNativeElements = listOptions.map(option => option.nativeElement);
128+
129+
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary')))
130+
.toBe(true);
131+
expect(optionNativeElements.every(option => !option.classList.contains('mat-warn')))
132+
.toBe(true);
133+
134+
// All options will be set to the "warn" color.
135+
fixture.componentInstance.selectionListColor = 'warn';
136+
fixture.detectChanges();
137+
138+
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary')))
139+
.toBe(true);
140+
expect(optionNativeElements.every(option => option.classList.contains('mat-warn')))
141+
.toBe(true);
142+
143+
// Color will be set explicitly for an option and should take precedence.
144+
fixture.componentInstance.firstOptionColor = 'primary';
145+
fixture.detectChanges();
146+
147+
expect(optionNativeElements[0].classList).toContain('mat-primary');
148+
expect(optionNativeElements[0].classList).not.toContain('mat-warn');
149+
expect(optionNativeElements.slice(1).every(option => option.classList.contains('mat-warn')))
150+
.toBe(true);
151+
});
152+
126153
it('should be able to deselect an option', () => {
127154
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
128155
let selectList =
@@ -1122,8 +1149,10 @@ describe('MatSelectionList with forms', () => {
11221149
<mat-selection-list
11231150
id="selection-list-1"
11241151
(selectionChange)="onValueChange($event)"
1125-
[disableRipple]="listRippleDisabled">
1126-
<mat-list-option checkboxPosition="before" disabled="true" value="inbox">
1152+
[disableRipple]="listRippleDisabled"
1153+
[color]="selectionListColor">
1154+
<mat-list-option checkboxPosition="before" disabled="true" value="inbox"
1155+
[color]="firstOptionColor">
11271156
Inbox (disabled selection-option)
11281157
</mat-list-option>
11291158
<mat-list-option id="testSelect" checkboxPosition="before" class="test-native-focus"
@@ -1140,6 +1169,8 @@ describe('MatSelectionList with forms', () => {
11401169
class SelectionListWithListOptions {
11411170
showLastOption: boolean = true;
11421171
listRippleDisabled = false;
1172+
selectionListColor: ThemePalette;
1173+
firstOptionColor: ThemePalette;
11431174

11441175
onValueChange(_change: MatSelectionListChange) {}
11451176
}

src/lib/list/selection-list.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
MatLine,
4747
setLines,
4848
mixinDisableRipple,
49+
ThemePalette,
4950
} from '@angular/material/core';
5051
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
5152
import {Subscription} from 'rxjs';
@@ -97,6 +98,11 @@ export class MatSelectionListChange {
9798
'tabindex': '-1',
9899
'[class.mat-list-item-disabled]': 'disabled',
99100
'[class.mat-list-item-with-avatar]': '_avatar || _icon',
101+
// Manually set the "primary" or "warn" class if the color has been explicitly
102+
// set to "primary" or "warn". The pseudo checkbox picks up these classes for
103+
// its theme. The accent theme palette is the default and doesn't need to be set.
104+
'[class.mat-primary]': 'color === "primary"',
105+
'[class.mat-warn]': 'color === "warn"',
100106
'[attr.aria-selected]': 'selected.toString()',
101107
'[attr.aria-disabled]': 'disabled.toString()',
102108
},
@@ -121,6 +127,12 @@ export class MatListOption extends _MatListOptionMixinBase
121127
/** Whether the label should appear before or after the checkbox. Defaults to 'after' */
122128
@Input() checkboxPosition: 'before' | 'after' = 'after';
123129

130+
/** Theme color of the list option. This sets the color of the checkbox. */
131+
@Input()
132+
get color(): ThemePalette { return this._color || this.selectionList.color; }
133+
set color(newValue: ThemePalette) { this._color = newValue; }
134+
private _color: ThemePalette;
135+
124136
/** Value of the option */
125137
@Input()
126138
get value(): any { return this._value; }
@@ -316,6 +328,9 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
316328
/** Tabindex of the selection list. */
317329
@Input() tabIndex: number = 0;
318330

331+
/** Theme color of the selection list. This sets the checkbox color for all list options. */
332+
@Input() color: ThemePalette = 'accent';
333+
319334
/**
320335
* Function used for comparing an option against the selected value when determining which
321336
* options should appear as selected. The first argument is the value of an options. The second
@@ -389,8 +404,10 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
389404

390405
ngOnChanges(changes: SimpleChanges) {
391406
const disableRippleChanges = changes.disableRipple;
407+
const colorChanges = changes.color;
392408

393-
if (disableRippleChanges && !disableRippleChanges.firstChange) {
409+
if ((disableRippleChanges && !disableRippleChanges.firstChange) ||
410+
(colorChanges && colorChanges.firstChange)) {
394411
this._markOptionsForCheck();
395412
}
396413
}

tools/public_api_guard/lib/list.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export declare class MatListOption extends _MatListOptionMixinBase implements Af
4848
_lines: QueryList<MatLine>;
4949
_text: ElementRef;
5050
checkboxPosition: 'before' | 'after';
51+
color: ThemePalette;
5152
disabled: any;
5253
selected: boolean;
5354
selectionList: MatSelectionList;
@@ -84,6 +85,7 @@ export declare class MatNavList extends _MatListMixinBase implements CanDisableR
8485
export declare class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption, CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy, OnChanges {
8586
_keyManager: FocusKeyManager<MatListOption>;
8687
_onTouched: () => void;
88+
color: ThemePalette;
8789
compareWith: (o1: any, o2: any) => boolean;
8890
disabled: boolean;
8991
options: QueryList<MatListOption>;

0 commit comments

Comments
 (0)