feat(selection-list): support specifying theme color (#15237)

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
This commit is contained in:
Paul Gschwendtner
2019-02-22 16:11:32 +01:00
committed by Jeremy Elbourn
parent 48d17afc14
commit 66d38fc596
4 changed files with 58 additions and 7 deletions
+4 -3
View File
@@ -120,13 +120,14 @@
(ngModelChange)="onSelectedOptionsChange($event)"
(change)="changeEventCount = changeEventCount + 1"
[disabled]="selectionListDisabled"
[disableRipple]="selectionListRippleDisabled">
[disableRipple]="selectionListRippleDisabled"
color="primary">
<h3 mat-subheader>Groceries</h3>
<mat-list-option value="bananas" checkboxPosition="before">Bananas</mat-list-option>
<mat-list-option selected value="oranges">Oranges</mat-list-option>
<mat-list-option value="apples">Apples</mat-list-option>
<mat-list-option value="strawberries">Strawberries</mat-list-option>
<mat-list-option value="apples" color="accent">Apples</mat-list-option>
<mat-list-option value="strawberries" color="warn">Strawberries</mat-list-option>
</mat-selection-list>
<mat-selection-list [disableRipple]="selectionListRippleDisabled">
+34 -3
View File
@@ -14,7 +14,7 @@ import {
ViewChildren,
} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick, flush} from '@angular/core/testing';
import {MatRipple, defaultRippleAnimationConfig} from '@angular/material/core';
import {MatRipple, defaultRippleAnimationConfig, ThemePalette} from '@angular/material/core';
import {By} from '@angular/platform-browser';
import {
MatListModule,
@@ -123,6 +123,33 @@ describe('MatSelectionList without forms', () => {
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
});
it('should be able to specify a color for list options', () => {
const optionNativeElements = listOptions.map(option => option.nativeElement);
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary')))
.toBe(true);
expect(optionNativeElements.every(option => !option.classList.contains('mat-warn')))
.toBe(true);
// All options will be set to the "warn" color.
fixture.componentInstance.selectionListColor = 'warn';
fixture.detectChanges();
expect(optionNativeElements.every(option => !option.classList.contains('mat-primary')))
.toBe(true);
expect(optionNativeElements.every(option => option.classList.contains('mat-warn')))
.toBe(true);
// Color will be set explicitly for an option and should take precedence.
fixture.componentInstance.firstOptionColor = 'primary';
fixture.detectChanges();
expect(optionNativeElements[0].classList).toContain('mat-primary');
expect(optionNativeElements[0].classList).not.toContain('mat-warn');
expect(optionNativeElements.slice(1).every(option => option.classList.contains('mat-warn')))
.toBe(true);
});
it('should be able to deselect an option', () => {
let testListItem = listOptions[2].injector.get<MatListOption>(MatListOption);
let selectList =
@@ -1122,8 +1149,10 @@ describe('MatSelectionList with forms', () => {
<mat-selection-list
id="selection-list-1"
(selectionChange)="onValueChange($event)"
[disableRipple]="listRippleDisabled">
<mat-list-option checkboxPosition="before" disabled="true" value="inbox">
[disableRipple]="listRippleDisabled"
[color]="selectionListColor">
<mat-list-option checkboxPosition="before" disabled="true" value="inbox"
[color]="firstOptionColor">
Inbox (disabled selection-option)
</mat-list-option>
<mat-list-option id="testSelect" checkboxPosition="before" class="test-native-focus"
@@ -1140,6 +1169,8 @@ describe('MatSelectionList with forms', () => {
class SelectionListWithListOptions {
showLastOption: boolean = true;
listRippleDisabled = false;
selectionListColor: ThemePalette;
firstOptionColor: ThemePalette;
onValueChange(_change: MatSelectionListChange) {}
}
+18 -1
View File
@@ -46,6 +46,7 @@ import {
MatLine,
setLines,
mixinDisableRipple,
ThemePalette,
} from '@angular/material/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {Subscription} from 'rxjs';
@@ -97,6 +98,11 @@ export class MatSelectionListChange {
'tabindex': '-1',
'[class.mat-list-item-disabled]': 'disabled',
'[class.mat-list-item-with-avatar]': '_avatar || _icon',
// Manually set the "primary" or "warn" class if the color has been explicitly
// set to "primary" or "warn". The pseudo checkbox picks up these classes for
// its theme. The accent theme palette is the default and doesn't need to be set.
'[class.mat-primary]': 'color === "primary"',
'[class.mat-warn]': 'color === "warn"',
'[attr.aria-selected]': 'selected.toString()',
'[attr.aria-disabled]': 'disabled.toString()',
},
@@ -121,6 +127,12 @@ export class MatListOption extends _MatListOptionMixinBase
/** Whether the label should appear before or after the checkbox. Defaults to 'after' */
@Input() checkboxPosition: 'before' | 'after' = 'after';
/** Theme color of the list option. This sets the color of the checkbox. */
@Input()
get color(): ThemePalette { return this._color || this.selectionList.color; }
set color(newValue: ThemePalette) { this._color = newValue; }
private _color: ThemePalette;
/** Value of the option */
@Input()
get value(): any { return this._value; }
@@ -316,6 +328,9 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
/** Tabindex of the selection list. */
@Input() tabIndex: number = 0;
/** Theme color of the selection list. This sets the checkbox color for all list options. */
@Input() color: ThemePalette = 'accent';
/**
* Function used for comparing an option against the selected value when determining which
* 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
ngOnChanges(changes: SimpleChanges) {
const disableRippleChanges = changes.disableRipple;
const colorChanges = changes.color;
if (disableRippleChanges && !disableRippleChanges.firstChange) {
if ((disableRippleChanges && !disableRippleChanges.firstChange) ||
(colorChanges && !colorChanges.firstChange)) {
this._markOptionsForCheck();
}
}
+2
View File
@@ -48,6 +48,7 @@ export declare class MatListOption extends _MatListOptionMixinBase implements Af
_lines: QueryList<MatLine>;
_text: ElementRef;
checkboxPosition: 'before' | 'after';
color: ThemePalette;
disabled: any;
selected: boolean;
selectionList: MatSelectionList;
@@ -84,6 +85,7 @@ export declare class MatNavList extends _MatListMixinBase implements CanDisableR
export declare class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption, CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy, OnChanges {
_keyManager: FocusKeyManager<MatListOption>;
_onTouched: () => void;
color: ThemePalette;
compareWith: (o1: any, o2: any) => boolean;
disabled: boolean;
options: QueryList<MatListOption>;