mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(selection-list): fix option value coercion and selection events (#6901)
* fix(selection-list): fix md-list-option value coercion and selection event emitters * md-list-option value is no longer coerced to a boolean value * md-list-option EventEmitters selectedChange and deselected now emit an event when an option is selected/deselected Fixes #6864 * review feedback changes * review feedback changes * review feedback changes * review feedback changes
This commit is contained in:
committed by
Andrew Seguin
parent
074f6cea62
commit
80671bf2e8
@@ -4,7 +4,7 @@ import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
import {Component, DebugElement} from '@angular/core';
|
||||
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MatListModule, MatListOption, MatSelectionList} from './index';
|
||||
import {MatListModule, MatListOption, MatSelectionList, MatListOptionChange} from './index';
|
||||
|
||||
|
||||
describe('MatSelectionList', () => {
|
||||
@@ -483,9 +483,88 @@ describe('MatSelectionList', () => {
|
||||
expect(listItemContent.nativeElement.classList).toContain('mat-list-item-content-reverse');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('with multiple values', () => {
|
||||
let fixture: ComponentFixture<SelectionListWithMultipleValues>;
|
||||
let listOption: DebugElement[];
|
||||
let listItemEl: DebugElement;
|
||||
let selectionList: DebugElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatListModule],
|
||||
declarations: [
|
||||
SelectionListWithMultipleValues
|
||||
],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(SelectionListWithMultipleValues);
|
||||
listOption = fixture.debugElement.queryAll(By.directive(MatListOption));
|
||||
listItemEl = fixture.debugElement.query(By.css('.mat-list-item'));
|
||||
selectionList = fixture.debugElement.query(By.directive(MatSelectionList));
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should have a value for each item', () => {
|
||||
expect(listOption[0].componentInstance.value).toBe(1);
|
||||
expect(listOption[1].componentInstance.value).toBe('a');
|
||||
expect(listOption[2].componentInstance.value).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with option selected events', () => {
|
||||
let fixture: ComponentFixture<SelectionListWithOptionEvents>;
|
||||
let testComponent: SelectionListWithOptionEvents;
|
||||
let listOption: DebugElement[];
|
||||
let selectionList: DebugElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatListModule],
|
||||
declarations: [
|
||||
SelectionListWithOptionEvents
|
||||
],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(SelectionListWithOptionEvents);
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
listOption = fixture.debugElement.queryAll(By.directive(MatListOption));
|
||||
selectionList = fixture.debugElement.query(By.directive(MatSelectionList));
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should trigger the selected and deselected events when clicked in succession.', () => {
|
||||
|
||||
let selected: boolean = false;
|
||||
|
||||
spyOn(testComponent, 'onOptionSelectionChange')
|
||||
.and.callFake((event: MatListOptionChange) => {
|
||||
selected = event.selected;
|
||||
});
|
||||
|
||||
listOption[0].nativeElement.click();
|
||||
expect(testComponent.onOptionSelectionChange).toHaveBeenCalledTimes(1);
|
||||
expect(selected).toBe(true);
|
||||
|
||||
listOption[0].nativeElement.click();
|
||||
expect(testComponent.onOptionSelectionChange).toHaveBeenCalledTimes(2);
|
||||
expect(selected).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id="selection-list-1">
|
||||
<mat-list-option checkboxPosition="before" disabled="true" value="inbox">
|
||||
@@ -507,17 +586,17 @@ class SelectionListWithListOptions {
|
||||
}
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id = "selection-list-2">
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-selection-list id="selection-list-2">
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Inbox (disabled selection-option)
|
||||
</mat-list-option>
|
||||
<mat-list-option id = "testSelect" checkboxPosition = "after">
|
||||
<mat-list-option id="testSelect" checkboxPosition="after">
|
||||
Starred
|
||||
</mat-list-option>
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Sent Mail
|
||||
</mat-list-option>
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Drafts
|
||||
</mat-list-option>
|
||||
</mat-selection-list>`})
|
||||
@@ -525,17 +604,17 @@ class SelectionListWithCheckboxPositionAfter {
|
||||
}
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id = "selection-list-3" [disabled] = true>
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-selection-list id="selection-list-3" [disabled]=true>
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Inbox (disabled selection-option)
|
||||
</mat-list-option>
|
||||
<mat-list-option id = "testSelect" checkboxPosition = "after">
|
||||
<mat-list-option id="testSelect" checkboxPosition="after">
|
||||
Starred
|
||||
</mat-list-option>
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Sent Mail
|
||||
</mat-list-option>
|
||||
<mat-list-option checkboxPosition = "after">
|
||||
<mat-list-option checkboxPosition="after">
|
||||
Drafts
|
||||
</mat-list-option>
|
||||
</mat-selection-list>`})
|
||||
@@ -559,8 +638,8 @@ class SelectionListWithSelectedOption {
|
||||
}
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id = "selection-list-4">
|
||||
<mat-list-option checkboxPosition = "after" class="test-focus" id="123">
|
||||
<mat-selection-list id="selection-list-4">
|
||||
<mat-list-option checkboxPosition="after" class="test-focus" id="123">
|
||||
Inbox
|
||||
</mat-list-option>
|
||||
</mat-selection-list>`})
|
||||
@@ -579,3 +658,28 @@ class SelectionListWithTabindexBinding {
|
||||
tabIndex: number;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id="selection-list-5">
|
||||
<mat-list-option [value]="1" checkboxPosition="after">
|
||||
1
|
||||
</mat-list-option>
|
||||
<mat-list-option value="a" checkboxPosition="after">
|
||||
a
|
||||
</mat-list-option>
|
||||
<mat-list-option [value]="true" checkboxPosition="after">
|
||||
true
|
||||
</mat-list-option>
|
||||
</mat-selection-list>`})
|
||||
class SelectionListWithMultipleValues {
|
||||
}
|
||||
|
||||
@Component({template: `
|
||||
<mat-selection-list id="selection-list-6">
|
||||
<mat-list-option (selectionChange)="onOptionSelectionChange($event)">
|
||||
Inbox
|
||||
</mat-list-option>
|
||||
</mat-selection-list>`})
|
||||
class SelectionListWithOptionEvents {
|
||||
onOptionSelectionChange: (event?: MatListOptionChange) => void = () => {};
|
||||
}
|
||||
|
||||
@@ -51,15 +51,18 @@ export const _MatSelectionListMixinBase =
|
||||
export class MatListOptionBase {}
|
||||
export const _MatListOptionMixinBase = mixinDisableRipple(MatListOptionBase);
|
||||
|
||||
/** Event emitted by a selection-list whenever the state of an option is changed. */
|
||||
export interface MatSelectionListOptionEvent {
|
||||
option: MatListOption;
|
||||
/** Change event object emitted by MatListOption */
|
||||
export class MatListOptionChange {
|
||||
/** The source MatListOption of the event. */
|
||||
source: MatListOption;
|
||||
/** The new `selected` value of the option. */
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for list-options of selection-list. Each list-option can automatically
|
||||
* generate a checkbox and can put current item into the selectionModel of selection-list
|
||||
* if the current item is checked.
|
||||
* if the current item is selected.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -86,7 +89,6 @@ export interface MatSelectionListOptionEvent {
|
||||
export class MatListOption extends _MatListOptionMixinBase
|
||||
implements AfterContentInit, OnInit, OnDestroy, FocusableOption, CanDisableRipple {
|
||||
private _lineSetter: MatLineSetter;
|
||||
private _selected: boolean = false;
|
||||
private _disabled: boolean = false;
|
||||
|
||||
/** Whether the option has focus. */
|
||||
@@ -97,34 +99,31 @@ export class MatListOption extends _MatListOptionMixinBase
|
||||
/** Whether the label should appear before or after the checkbox. Defaults to 'after' */
|
||||
@Input() checkboxPosition: 'before' | 'after' = 'after';
|
||||
|
||||
/** Whether the option is disabled. */
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return (this.selectionList && this.selectionList.disabled) || this._disabled;
|
||||
}
|
||||
set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
|
||||
|
||||
/** Value of the option */
|
||||
@Input() value: any;
|
||||
|
||||
/** Whether the option is disabled. */
|
||||
@Input()
|
||||
get disabled() { return (this.selectionList && this.selectionList.disabled) || this._disabled; }
|
||||
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }
|
||||
|
||||
/** Whether the option is selected. */
|
||||
@Input()
|
||||
get selected() { return this._selected; }
|
||||
get selected(): boolean { return this.selectionList.selectedOptions.isSelected(this); }
|
||||
set selected(value: boolean) {
|
||||
const isSelected = coerceBooleanProperty(value);
|
||||
|
||||
if (isSelected !== this._selected) {
|
||||
const selectionModel = this.selectionList.selectedOptions;
|
||||
|
||||
this._selected = isSelected;
|
||||
isSelected ? selectionModel.select(this) : selectionModel.deselect(this);
|
||||
if (isSelected !== this.selected) {
|
||||
this.selectionList.selectedOptions.toggle(this);
|
||||
this._changeDetector.markForCheck();
|
||||
this.selectionChange.emit(this._createChangeEvent());
|
||||
}
|
||||
}
|
||||
|
||||
/** Emitted when the option is selected. */
|
||||
@Output() selectChange = new EventEmitter<MatSelectionListOptionEvent>();
|
||||
|
||||
/** Emitted when the option is deselected. */
|
||||
@Output() deselected = new EventEmitter<MatSelectionListOptionEvent>();
|
||||
/** Emitted when the option is selected or deselected. */
|
||||
@Output() selectionChange = new EventEmitter<MatListOptionChange>();
|
||||
|
||||
constructor(private _renderer: Renderer2,
|
||||
private _element: ElementRef,
|
||||
@@ -174,6 +173,16 @@ export class MatListOption extends _MatListOptionMixinBase
|
||||
this.selectionList._setFocusedOption(this);
|
||||
}
|
||||
|
||||
/** Creates a selection event object from the specified option. */
|
||||
private _createChangeEvent(option: MatListOption = this): MatListOptionChange {
|
||||
const event = new MatListOptionChange();
|
||||
|
||||
event.source = option;
|
||||
event.selected = option.selected;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/** Retrieves the DOM element of the component host. */
|
||||
_getHostElement(): HTMLElement {
|
||||
return this._element.nativeElement;
|
||||
|
||||
Reference in New Issue
Block a user