Skip to content

Commit b7709be

Browse files
committed
feat(list): Add single select mode.
1 parent 09dc459 commit b7709be

File tree

11 files changed

+210
-18
lines changed

11 files changed

+210
-18
lines changed

src/components-examples/material/list/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import {MatListModule} from '@angular/material/list';
55
import {ListOverviewExample} from './list-overview/list-overview-example';
66
import {ListSectionsExample} from './list-sections/list-sections-example';
77
import {ListSelectionExample} from './list-selection/list-selection-example';
8+
import {ListSingleSelectionExample} from './list-single-selection/list-single-selection-example';
89

910
export {
1011
ListOverviewExample,
1112
ListSectionsExample,
1213
ListSelectionExample,
14+
ListSingleSelectionExample,
1315
};
1416

1517
const EXAMPLES = [
1618
ListOverviewExample,
1719
ListSectionsExample,
1820
ListSelectionExample,
21+
ListSingleSelectionExample,
1922
];
2023

2124
@NgModule({
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/** No styles for this example. */
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<mat-selection-list #shoes [multiple]="false">
2+
<mat-list-option *ngFor="let shoe of typesOfShoes">
3+
{{shoe}}
4+
</mat-list-option>
5+
</mat-selection-list>
6+
7+
<p>
8+
Option selected: {{shoes.selectedOptions.selected}}
9+
</p>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Component} from '@angular/core';
2+
3+
/**
4+
* @title List with single selection
5+
*/
6+
@Component({
7+
selector: 'list-single-selection-example',
8+
styleUrls: ['list-single-selection-example.css'],
9+
templateUrl: 'list-single-selection-example.html',
10+
})
11+
export class ListSingleSelectionExample {
12+
typesOfShoes: string[] = ['Boots', 'Clogs', 'Loafers', 'Moccasins', 'Sneakers'];
13+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,22 @@ <h3 mat-subheader>Dogs</h3>
162162
<button mat-raised-button (click)="groceries.deselectAll()">Deselect all</button>
163163
</p>
164164
</div>
165+
166+
<div>
167+
<h2>Single Selection list</h2>
168+
169+
<mat-selection-list #favorite
170+
[(ngModel)]="favoriteOptions"
171+
[multiple]="false"
172+
color="primary">
173+
<h3 mat-subheader>Favorite Grocery</h3>
174+
175+
<mat-list-option value="bananas">Bananas</mat-list-option>
176+
<mat-list-option selected value="oranges">Oranges</mat-list-option>
177+
<mat-list-option value="apples">Apples</mat-list-option>
178+
<mat-list-option value="strawberries" color="warn">Strawberries</mat-list-option>
179+
</mat-selection-list>
180+
181+
<p>Selected: {{favoriteOptions | json}}</p>
182+
</div>
165183
</div>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export class ListDemo {
7070
this.modelChangeEventCount++;
7171
}
7272

73+
favoriteOptions: string[] = [];
74+
7375
alertItem(msg: string) {
7476
alert(msg);
7577
}

src/material/list/_list-theme.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
background: mat-color($background, 'hover');
3434
}
3535
}
36+
37+
.mat-selection-list-single-select .mat-list-option.mat-selected {
38+
background: mat-color($background, hover, 0.12);
39+
}
3640
}
3741

3842
@mixin mat-list-typography($config) {

src/material/list/list-option.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[matRippleDisabled]="_isRippleDisabled()"></div>
88

99
<mat-pseudo-checkbox
10+
*ngIf="selectionList.multiple"
1011
[state]="selected ? 'checked' : 'unchecked'"
1112
[disabled]="disabled"></mat-pseudo-checkbox>
1213

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

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,13 @@ describe('MatSelectionList without forms', () => {
9393

9494
expect(selectList.selected.length).toBe(0);
9595
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('false');
96+
expect(listOptions[2].nativeElement.classList.contains('mat-selected')).toBe(false);
9697

9798
testListItem.toggle();
9899
fixture.detectChanges();
99100

100101
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('true');
102+
expect(listOptions[2].nativeElement.classList.contains('mat-selected')).toBe(true);
101103
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
102104
expect(selectList.selected.length).toBe(1);
103105
});
@@ -110,7 +112,9 @@ describe('MatSelectionList without forms', () => {
110112

111113
expect(selectList.selected.length).toBe(0);
112114
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('false');
115+
expect(listOptions[2].nativeElement.classList.contains('mat-selected')).toBe(false);
113116
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('false');
117+
expect(listOptions[1].nativeElement.classList.contains('mat-selected')).toBe(false);
114118

115119
testListItem.toggle();
116120
fixture.detectChanges();
@@ -120,7 +124,9 @@ describe('MatSelectionList without forms', () => {
120124

121125
expect(selectList.selected.length).toBe(2);
122126
expect(listOptions[2].nativeElement.getAttribute('aria-selected')).toBe('true');
127+
expect(listOptions[2].nativeElement.classList.contains('mat-selected')).toBe(true);
123128
expect(listOptions[1].nativeElement.getAttribute('aria-selected')).toBe('true');
129+
expect(listOptions[1].nativeElement.classList.contains('mat-selected')).toBe(true);
124130
expect(listOptions[1].nativeElement.getAttribute('aria-disabled')).toBe('false');
125131
expect(listOptions[2].nativeElement.getAttribute('aria-disabled')).toBe('false');
126132
});
@@ -882,6 +888,97 @@ describe('MatSelectionList without forms', () => {
882888
expect(listOption.classList).toContain('mat-list-item-with-avatar');
883889
});
884890
});
891+
892+
describe('with single selection', () => {
893+
let fixture: ComponentFixture<SelectionListWithListOptions>;
894+
let listOption: DebugElement[];
895+
let selectionList: DebugElement;
896+
897+
beforeEach(async(() => {
898+
TestBed.configureTestingModule({
899+
imports: [MatListModule],
900+
declarations: [
901+
SelectionListWithListOptions,
902+
],
903+
}).compileComponents();
904+
905+
fixture = TestBed.createComponent(SelectionListWithListOptions);
906+
fixture.componentInstance.multiple = false;
907+
listOption = fixture.debugElement.queryAll(By.directive(MatListOption));
908+
selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!;
909+
fixture.detectChanges();
910+
}));
911+
912+
it('should select one option at a time', () => {
913+
const testListItem1 = listOption[1].injector.get<MatListOption>(MatListOption);
914+
const testListItem2 = listOption[2].injector.get<MatListOption>(MatListOption);
915+
const selectList =
916+
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
917+
918+
expect(selectList.selected.length).toBe(0);
919+
920+
dispatchFakeEvent(testListItem1._getHostElement(), 'click');
921+
fixture.detectChanges();
922+
923+
expect(selectList.selected).toEqual([testListItem1]);
924+
925+
dispatchFakeEvent(testListItem2._getHostElement(), 'click');
926+
fixture.detectChanges();
927+
928+
expect(selectList.selected).toEqual([testListItem2]);
929+
});
930+
931+
it('should not show check boxes', () => {
932+
expect(fixture.nativeElement.querySelector('mat-pseudo-checkbox')).toBeFalsy();
933+
});
934+
935+
it('should not deselect the target option on click', () => {
936+
const testListItem1 = listOption[1].injector.get<MatListOption>(MatListOption);
937+
const selectList =
938+
selectionList.injector.get<MatSelectionList>(MatSelectionList).selectedOptions;
939+
940+
expect(selectList.selected.length).toBe(0);
941+
942+
dispatchFakeEvent(testListItem1._getHostElement(), 'click');
943+
fixture.detectChanges();
944+
945+
expect(selectList.selected).toEqual([testListItem1]);
946+
947+
dispatchFakeEvent(testListItem1._getHostElement(), 'click');
948+
fixture.detectChanges();
949+
950+
expect(selectList.selected).toEqual([testListItem1]);
951+
});
952+
953+
it('sanely handles toggling single/multiple mode after bootstrap', () => {
954+
const testListItem1 = listOption[1].injector.get<MatListOption>(MatListOption);
955+
const testListItem2 = listOption[2].injector.get<MatListOption>(MatListOption);
956+
const selected = () => selectionList.injector.get<MatSelectionList>(MatSelectionList)
957+
.selectedOptions.selected;
958+
959+
expect(selected().length).toBe(0);
960+
961+
dispatchFakeEvent(testListItem1._getHostElement(), 'click');
962+
fixture.detectChanges();
963+
964+
expect(selected()).toEqual([testListItem1]);
965+
966+
fixture.componentInstance.multiple = true;
967+
fixture.detectChanges();
968+
969+
expect(selected()).toEqual([testListItem1]);
970+
971+
dispatchFakeEvent(testListItem2._getHostElement(), 'click');
972+
fixture.detectChanges();
973+
974+
expect(selected()).toEqual([testListItem1, testListItem2]);
975+
976+
fixture.componentInstance.multiple = false;
977+
fixture.detectChanges();
978+
979+
expect(selected()).toEqual([testListItem1]);
980+
});
981+
});
885982
});
886983

887984
describe('MatSelectionList with forms', () => {
@@ -1255,7 +1352,8 @@ describe('MatSelectionList with forms', () => {
12551352
id="selection-list-1"
12561353
(selectionChange)="onValueChange($event)"
12571354
[disableRipple]="listRippleDisabled"
1258-
[color]="selectionListColor">
1355+
[color]="selectionListColor"
1356+
[multiple]="multiple">
12591357
<mat-list-option checkboxPosition="before" disabled="true" value="inbox"
12601358
[color]="firstOptionColor">
12611359
Inbox (disabled selection-option)
@@ -1274,6 +1372,7 @@ describe('MatSelectionList with forms', () => {
12741372
class SelectionListWithListOptions {
12751373
showLastOption: boolean = true;
12761374
listRippleDisabled = false;
1375+
multiple = true;
12771376
selectionListColor: ThemePalette;
12781377
firstOptionColor: ThemePalette;
12791378

0 commit comments

Comments
 (0)