Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(material/core): add checkmark for single-select #25962

Merged
merged 1 commit into from Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/dev-app/autocomplete/BUILD.bazel
Expand Up @@ -13,6 +13,7 @@ ng_module(
"//src/material/autocomplete",
"//src/material/button",
"//src/material/card",
"//src/material/checkbox",
"//src/material/form-field",
"//src/material/input",
"@npm//@angular/forms",
Expand Down
21 changes: 14 additions & 7 deletions src/dev-app/autocomplete/autocomplete-demo.html
Expand Up @@ -10,7 +10,8 @@
<mat-label>State</mat-label>
<input matInput [matAutocomplete]="reactiveAuto" [formControl]="stateCtrl">
</mat-form-field>
<mat-autocomplete #reactiveAuto="matAutocomplete" [displayWith]="displayFn">
<mat-autocomplete #reactiveAuto="matAutocomplete" [displayWith]="displayFn"
[hideSingleSelectionIndicator]="reactiveHideSingleSelectionIndicator">
<mat-option *ngFor="let state of tempStates" [value]="state">
<span>{{ state.name }}</span>
<span class="demo-secondary-text"> ({{ state.code }}) </span>
Expand All @@ -23,11 +24,11 @@
<button mat-button (click)="stateCtrl.enabled ? stateCtrl.disable() : stateCtrl.enable()">
TOGGLE DISABLED
</button>
<select [(ngModel)]="reactiveStatesTheme">
<option *ngFor="let theme of availableThemes" [value]="theme.value">
{{theme.name}}
</option>
</select>
</mat-card-actions>
<mat-card-actions>
<mat-checkbox [(ngModel)]="reactiveHideSingleSelectionIndicator">
Hide Single-Selection Indicator
</mat-checkbox>
</mat-card-actions>

</mat-card>
Expand All @@ -42,7 +43,8 @@
<mat-label>State</mat-label>
<input matInput [matAutocomplete]="tdAuto" [(ngModel)]="currentState"
(ngModelChange)="tdStates = filterStates(currentState)" [disabled]="tdDisabled">
<mat-autocomplete #tdAuto="matAutocomplete">
<mat-autocomplete #tdAuto="matAutocomplete"
[hideSingleSelectionIndicator]="templateHideSingleSelectionIndicator">
<mat-option *ngFor="let state of tdStates" [value]="state.name">
<span>{{ state.name }}</span>
</mat-option>
Expand All @@ -61,6 +63,11 @@
</option>
</select>
</mat-card-actions>
<mat-card-actions>
<mat-checkbox [(ngModel)]="templateHideSingleSelectionIndicator">
Hide Single-Selection Indicator
</mat-checkbox>
</mat-card-actions>

</mat-card>

Expand Down
6 changes: 6 additions & 0 deletions src/dev-app/autocomplete/autocomplete-demo.ts
Expand Up @@ -12,6 +12,7 @@ import {CommonModule} from '@angular/common';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatInputModule} from '@angular/material/input';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
Expand All @@ -38,6 +39,7 @@ export interface StateGroup {
MatAutocompleteModule,
MatButtonModule,
MatCardModule,
MatCheckboxModule,
MatInputModule,
ReactiveFormsModule,
],
Expand All @@ -52,6 +54,7 @@ export class AutocompleteDemo {
tdStates: State[];

tdDisabled = false;
hideSingleSelectionIndicators = false;

reactiveStatesTheme: ThemePalette = 'primary';
templateStatesTheme: ThemePalette = 'primary';
Expand All @@ -62,6 +65,9 @@ export class AutocompleteDemo {
{value: 'warn', name: 'Warn'},
];

reactiveHideSingleSelectionIndicator = false;
templateHideSingleSelectionIndicator = false;

@ViewChild(NgModel) modelDir: NgModel;

groupedStates: StateGroup[];
Expand Down
27 changes: 21 additions & 6 deletions src/dev-app/checkbox/checkbox-demo.html
Expand Up @@ -64,14 +64,29 @@ <h1>mat-checkbox: Basic Example</h1>
</div>

<h1>Pseudo checkboxes</h1>
<mat-pseudo-checkbox></mat-pseudo-checkbox>
<mat-pseudo-checkbox [disabled]="true"></mat-pseudo-checkbox>
<div>
<h2>Full appearance</h2>
<mat-pseudo-checkbox></mat-pseudo-checkbox>
<mat-pseudo-checkbox [disabled]="true"></mat-pseudo-checkbox>

<mat-pseudo-checkbox state="checked"></mat-pseudo-checkbox>
<mat-pseudo-checkbox state="checked" [disabled]="true"></mat-pseudo-checkbox>

<mat-pseudo-checkbox state="indeterminate"></mat-pseudo-checkbox>
<mat-pseudo-checkbox state="indeterminate" [disabled]="true"></mat-pseudo-checkbox>
<div>
<div>
<h2>Minimal appearance</h2>
<mat-pseudo-checkbox appearance="minimal"></mat-pseudo-checkbox>
<mat-pseudo-checkbox appearance="minimal" [disabled]="true"></mat-pseudo-checkbox>

<mat-pseudo-checkbox state="checked"></mat-pseudo-checkbox>
<mat-pseudo-checkbox state="checked" [disabled]="true"></mat-pseudo-checkbox>
<mat-pseudo-checkbox appearance="minimal" state="checked"></mat-pseudo-checkbox>
<mat-pseudo-checkbox appearance="minimal" state="checked" [disabled]="true"></mat-pseudo-checkbox>

<mat-pseudo-checkbox state="indeterminate"></mat-pseudo-checkbox>
<mat-pseudo-checkbox state="indeterminate" [disabled]="true"></mat-pseudo-checkbox>
<mat-pseudo-checkbox appearance="minimal" state="indeterminate"></mat-pseudo-checkbox>
<mat-pseudo-checkbox appearance="minimal" state="indeterminate" [disabled]="true">
</mat-pseudo-checkbox>
<div>

<h1>Nested Checklist</h1>
<mat-checkbox-demo-nested-checklist></mat-checkbox-demo-nested-checklist>
Expand Down
1 change: 1 addition & 0 deletions src/dev-app/select/BUILD.bazel
Expand Up @@ -12,6 +12,7 @@ ng_module(
deps = [
"//src/material/button",
"//src/material/card",
"//src/material/checkbox",
"//src/material/form-field",
"//src/material/icon",
"//src/material/input",
Expand Down
46 changes: 43 additions & 3 deletions src/dev-app/select/select-demo.html
Expand Up @@ -12,8 +12,8 @@
<mat-label>Drink</mat-label>
<mat-select [(ngModel)]="currentDrink" [required]="drinksRequired"
[disabled]="drinksDisabled" #drinkControl="ngModel">
<mat-option>None</mat-option>
<mat-option *ngFor="let drink of drinks" [value]="drink.value" [disabled]="drink.disabled">
<mat-option [disabled]="drinksOptionsDisabled">None</mat-option>
<mat-option *ngFor="let drink of drinks" [value]="drink.value" [disabled]="drinksOptionsDisabled">
{{ drink.viewValue }}
</mat-option>
</mat-select>
Expand Down Expand Up @@ -52,6 +52,7 @@
<button mat-button (click)="currentDrink='water-2'">SET VALUE</button>
<button mat-button (click)="drinksRequired=!drinksRequired">TOGGLE REQUIRED</button>
<button mat-button (click)="drinksDisabled=!drinksDisabled">TOGGLE DISABLED</button>
<button mat-button (click)="drinksOptionsDisabled=!drinksOptionsDisabled">TOGGLE DISABLED OPTIONS</button>
<button mat-button (click)="drinkControl.reset()">RESET</button>
</mat-card-content>
</mat-card>
Expand All @@ -64,7 +65,7 @@
<mat-label>Pokemon</mat-label>
<mat-select multiple [(ngModel)]="currentPokemon"
[required]="pokemonRequired" [disabled]="pokemonDisabled" #pokemonControl="ngModel">
<mat-option *ngFor="let creature of pokemon" [value]="creature.value">
<mat-option *ngFor="let creature of pokemon" [value]="creature.value" [disabled]="pokemonOptionsDisabled">
{{ creature.viewValue }}
</mat-option>
</mat-select>
Expand All @@ -82,6 +83,7 @@
<button mat-button (click)="setPokemonValue()">SET VALUE</button>
<button mat-button (click)="pokemonRequired=!pokemonRequired">TOGGLE REQUIRED</button>
<button mat-button (click)="pokemonDisabled=!pokemonDisabled">TOGGLE DISABLED</button>
<button mat-button (click)="pokemonOptionsDisabled=!pokemonOptionsDisabled">TOGGLE DISABLED OPTIONS</button>
<button mat-button (click)="pokemonControl.reset()">RESET</button>
</mat-card-content>
</mat-card>
Expand Down Expand Up @@ -356,3 +358,41 @@ <h4>Error message with errorStateMatcher</h4>
</form>
</mat-card-content>
</mat-card>

<mat-card class="demo-card demo-narrow">
<mat-card-subtitle>Narrow</mat-card-subtitle>
<mat-card-content>
<p class="demo-narrow-sandwich">
<mat-form-field>
<mat-label>Bread</mat-label>
<mat-select [(ngModel)]="sandwichBread"
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
<mat-option *ngFor="let bread of breads" [value]="bread.value">
{{ bread.viewValue }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Meat</mat-label>
<mat-select [(ngModel)]="sandwichMeat"
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
<mat-option *ngFor="let meat of meats" [value]="meat.value">
{{ meat.viewValue }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Cheese</mat-label>
<mat-select [(ngModel)]="sandwichCheese"
[hideSingleSelectionIndicator]="sandwichHideSingleSelectionIndicator">
<mat-option *ngFor="let cheese of cheeses" [value]="cheese.value">
{{ cheese.viewValue }}
</mat-option>
</mat-select>
</mat-form-field>
</p>
<mat-checkbox [(ngModel)]="sandwichHideSingleSelectionIndicator">
Hide Single-Selection Indicator
</mat-checkbox>
</mat-card-content>
</mat-card>
9 changes: 9 additions & 0 deletions src/dev-app/select/select-demo.scss
Expand Up @@ -25,3 +25,12 @@
.demo-card {
margin: 30px 0;
}

.demo-narrow {
max-width: 450px;

.demo-narrow-sandwich {
display: flex;
gap: 16px;
}
}
46 changes: 38 additions & 8 deletions src/dev-app/select/select-demo.ts
Expand Up @@ -16,6 +16,7 @@ import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
import {MatInputModule} from '@angular/material/input';
import {MatCheckboxModule} from '@angular/material/checkbox';

/** Error any time control is invalid */
export class MyErrorStateMatcher implements ErrorStateMatcher {
Expand All @@ -37,6 +38,7 @@ export class MyErrorStateMatcher implements ErrorStateMatcher {
FormsModule,
MatButtonModule,
MatCardModule,
MatCheckboxModule,
MatIconModule,
MatInputModule,
MatSelectModule,
Expand All @@ -48,7 +50,9 @@ export class SelectDemo {
drinkObjectRequired = false;
pokemonRequired = false;
drinksDisabled = false;
drinksOptionsDisabled = false;
pokemonDisabled = false;
pokemonOptionsDisabled = false;
showSelect = false;
currentDrink: string;
currentDrinkObject: {} | undefined = {value: 'tea-5', viewValue: 'Tea'};
Expand All @@ -66,6 +70,12 @@ export class SelectDemo {
compareByValue = true;
selectFormControl = new FormControl('', Validators.required);

sandwichBread = '';
sandwichMeat = '';
sandwichCheese = '';

sandwichHideSingleSelectionIndicator = false;

foods = [
{value: null, viewValue: 'None'},
{value: 'steak-0', viewValue: 'Steak'},
Expand All @@ -74,19 +84,19 @@ export class SelectDemo {
];

drinks = [
{value: 'coke-0', viewValue: 'Coke', disabled: false},
{value: 'coke-0', viewValue: 'Coke'},
{
value: 'long-name-1',
viewValue: 'Decaf Chocolate Brownie Vanilla Gingerbread Frappuccino',
disabled: false,
},
{value: 'water-2', viewValue: 'Water', disabled: false},
{value: 'pepper-3', viewValue: 'Dr. Pepper', disabled: false},
{value: 'coffee-4', viewValue: 'Coffee', disabled: false},
{value: 'tea-5', viewValue: 'Tea', disabled: false},
{value: 'juice-6', viewValue: 'Orange juice', disabled: false},
{value: 'wine-7', viewValue: 'Wine', disabled: false},
{value: 'milk-8', viewValue: 'Milk', disabled: true},
{value: 'water-2', viewValue: 'Water'},
{value: 'pepper-3', viewValue: 'Dr. Pepper'},
{value: 'coffee-4', viewValue: 'Coffee'},
{value: 'tea-5', viewValue: 'Tea'},
{value: 'juice-6', viewValue: 'Orange juice'},
{value: 'wine-7', viewValue: 'Wine'},
{value: 'milk-8', viewValue: 'Milk'},
];

pokemon = [
Expand Down Expand Up @@ -149,6 +159,26 @@ export class SelectDemo {
{value: 'indramon-5', viewValue: 'Indramon'},
];

breads = [
{value: 'white', viewValue: 'White'},
{value: 'white', viewValue: 'Wheat'},
{value: 'white', viewValue: 'Sourdough'},
];

meats = [
{value: 'turkey', viewValue: 'Turkey'},
{value: 'bacon', viewValue: 'Bacon'},
{value: 'veggiePatty', viewValue: 'Veggie Patty'},
{value: 'tuna', viewValue: 'Tuna'},
];

cheeses = [
{value: 'none', viewValue: 'None'},
{value: 'swiss', viewValue: 'Swiss'},
{value: 'american', viewValue: 'American'},
{value: 'cheddar', viewValue: 'Cheddar'},
];

toggleDisabled() {
this.foodControl.enabled ? this.foodControl.disable() : this.foodControl.enable();
}
Expand Down
47 changes: 47 additions & 0 deletions src/material/autocomplete/autocomplete.spec.ts
Expand Up @@ -46,6 +46,7 @@ import {map, startWith} from 'rxjs/operators';
import {
getMatAutocompleteMissingPanelError,
MatAutocomplete,
MatAutocompleteDefaultOptions,
MatAutocompleteModule,
MatAutocompleteOrigin,
MatAutocompleteSelectedEvent,
Expand Down Expand Up @@ -3412,6 +3413,50 @@ describe('MDC-based MatAutocomplete', () => {

subscription.unsubscribe();
}));

describe('a11y', () => {
it('should display checkmark for selection by default', () => {
const fixture = createComponent(AutocompleteWithNgModel);
fixture.componentInstance.selectedState = 'New York';
fixture.detectChanges();

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();

dispatchFakeEvent(document.querySelector('mat-option')!, 'click');
fixture.detectChanges();

const selectedOption = document.querySelector('mat-option[aria-selected="true"');
expect(selectedOption).withContext('Expected an option to be selected.').not.toBeNull();
expect(selectedOption?.querySelector('.mat-pseudo-checkbox.mat-pseudo-checkbox-minimal'))
.withContext(
'Expected selection option to have a pseudo-checkbox with "minimal" appearance.',
)
.toBeTruthy();
});
});

describe('with token to hide single selection indicator', () => {
it('should not display checkmark', () => {
const defaultOptions: MatAutocompleteDefaultOptions = {
hideSingleSelectionIndicator: true,
};
const fixture = createComponent(AutocompleteWithNgModel, [
{provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, useValue: defaultOptions},
]);
fixture.detectChanges();

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();

dispatchFakeEvent(document.querySelector('mat-option')!, 'click');
fixture.detectChanges();

const selectedOption = document.querySelector('mat-option[aria-selected="true"');
expect(selectedOption).withContext('Expected an option to be selected.').not.toBeNull();
expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(0);
});
});
});

const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
Expand Down Expand Up @@ -3576,6 +3621,8 @@ class AutocompleteWithNgModel {
selectedState: string;
states = ['New York', 'Washington', 'Oregon'];

@ViewChild(MatAutocompleteTrigger, {static: true}) trigger: MatAutocompleteTrigger;

constructor() {
this.filteredStates = this.states.slice();
}
Expand Down