Skip to content

Commit 02bea16

Browse files
committed
fix(material/button-toggle): Add checkmark indicators with hideSingleSelectionIndicator and hideMultipleSelectionIndicator input and config options
1 parent af7cd70 commit 02bea16

File tree

6 files changed

+157
-10
lines changed

6 files changed

+157
-10
lines changed

src/dev-app/button-toggle/button-toggle-demo.html

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@
66
<mat-checkbox (change)="isDisabled = $event.checked">Disable Button Toggle Items</mat-checkbox>
77
</p>
88

9+
<p>
10+
<mat-checkbox (change)="hideSingleSelectionIndicator = $event.checked">Hide Single Selection Indicator</mat-checkbox>
11+
</p>
12+
13+
<p>
14+
<mat-checkbox (change)="hideMultipleSelectionIndicator = $event.checked">Hide Multiple Selection Indicator</mat-checkbox>
15+
</p>
16+
917
<h1>Exclusive Selection</h1>
1018

1119
<section>
12-
<mat-button-toggle-group name="alignment" [vertical]="isVertical">
20+
<mat-button-toggle-group name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
1321
<mat-button-toggle value="left" [disabled]="isDisabled">
1422
<mat-icon>format_align_left</mat-icon>
1523
</mat-button-toggle>
@@ -26,7 +34,7 @@ <h1>Exclusive Selection</h1>
2634
</section>
2735

2836
<section>
29-
<mat-button-toggle-group appearance="legacy" name="alignment" [vertical]="isVertical">
37+
<mat-button-toggle-group appearance="legacy" name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
3038
<mat-button-toggle value="left" [disabled]="isDisabled">
3139
<mat-icon>format_align_left</mat-icon>
3240
</mat-button-toggle>
@@ -45,7 +53,7 @@ <h1>Exclusive Selection</h1>
4553
<h1>Disabled Group</h1>
4654

4755
<section>
48-
<mat-button-toggle-group name="checkbox" [vertical]="isVertical" [disabled]="isDisabled">
56+
<mat-button-toggle-group name="checkbox" [vertical]="isVertical" [disabled]="isDisabled" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
4957
<mat-button-toggle value="bold">
5058
<mat-icon>format_bold</mat-icon>
5159
</mat-button-toggle>
@@ -60,15 +68,15 @@ <h1>Disabled Group</h1>
6068

6169
<h1>Multiple Selection</h1>
6270
<section>
63-
<mat-button-toggle-group multiple [vertical]="isVertical">
71+
<mat-button-toggle-group multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
6472
<mat-button-toggle>Flour</mat-button-toggle>
6573
<mat-button-toggle>Eggs</mat-button-toggle>
6674
<mat-button-toggle>Sugar</mat-button-toggle>
6775
<mat-button-toggle [disabled]="isDisabled">Milk</mat-button-toggle>
6876
</mat-button-toggle-group>
6977
</section>
7078
<section>
71-
<mat-button-toggle-group appearance="legacy" multiple [vertical]="isVertical">
79+
<mat-button-toggle-group appearance="legacy" multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
7280
<mat-button-toggle>Flour</mat-button-toggle>
7381
<mat-button-toggle>Eggs</mat-button-toggle>
7482
<mat-button-toggle>Sugar</mat-button-toggle>
@@ -82,7 +90,7 @@ <h1>Single Toggle</h1>
8290

8391
<h1>Dynamic Exclusive Selection</h1>
8492
<section>
85-
<mat-button-toggle-group name="pies" [(ngModel)]="favoritePie" [vertical]="isVertical">
93+
<mat-button-toggle-group name="pies" [(ngModel)]="favoritePie" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
8694
@for (pie of pieOptions; track pie) {
8795
<mat-button-toggle [value]="pie">{{pie}}</mat-button-toggle>
8896
}

src/dev-app/button-toggle/button-toggle-demo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {MatIconModule} from '@angular/material/icon';
2323
export class ButtonToggleDemo {
2424
isVertical = false;
2525
isDisabled = false;
26+
hideSingleSelectionIndicator = false;
27+
hideMultipleSelectionIndicator = false;
2628
favoritePie = 'Apple';
2729
pieOptions = ['Apple', 'Cherry', 'Pecan', 'Lemon'];
2830
}

src/material/button-toggle/button-toggle.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@
99
[attr.aria-labelledby]="ariaLabelledby"
1010
(click)="_onButtonClick()">
1111
<span class="mat-button-toggle-label-content">
12+
<!-- Render checkmark at the beginning for single-selection. -->
13+
@if (buttonToggleGroup && checked && !buttonToggleGroup.multiple && !buttonToggleGroup.hideSingleSelectionIndicator) {
14+
<mat-pseudo-checkbox
15+
class="mat-mdc-option-pseudo-checkbox"
16+
[disabled]="disabled"
17+
state="checked"
18+
aria-hidden="true"
19+
appearance="minimal"></mat-pseudo-checkbox>
20+
}
21+
<!-- Render checkmark at the beginning for multiple-selection. -->
22+
@if (buttonToggleGroup && checked && buttonToggleGroup.multiple && !buttonToggleGroup.hideMultipleSelectionIndicator) {
23+
<mat-pseudo-checkbox
24+
class="mat-mdc-option-pseudo-checkbox"
25+
[disabled]="disabled"
26+
state="checked"
27+
aria-hidden="true"
28+
appearance="minimal"></mat-pseudo-checkbox>
29+
}
1230
<ng-content></ng-content>
1331
</span>
1432
</button>

src/material/button-toggle/button-toggle.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
$standard-padding: 0 12px !default;
1111
$legacy-padding: 0 16px !default;
12+
$checkmark-padding: 12px !default;
1213

1314
// TODO(crisbeto): these variables aren't used anymore and should be removed.
1415
$legacy-height: 36px !default;
@@ -90,6 +91,14 @@ $_standard-tokens: (
9091
.mat-icon svg {
9192
vertical-align: top;
9293
}
94+
95+
.mat-pseudo-checkbox {
96+
margin-right: $checkmark-padding;
97+
[dir='rtl'] & {
98+
margin-right: 0;
99+
margin-left: $checkmark-padding;
100+
}
101+
}
93102
}
94103

95104
.mat-button-toggle-checked {

src/material/button-toggle/button-toggle.spec.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import {dispatchMouseEvent} from '@angular/cdk/testing/private';
2-
import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core';
1+
import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing/private';
2+
import {
3+
Component,
4+
DebugElement,
5+
Provider,
6+
QueryList,
7+
Type,
8+
ViewChild,
9+
ViewChildren,
10+
} from '@angular/core';
311
import {CommonModule} from '@angular/common';
412
import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing';
513
import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms';
614
import {By} from '@angular/platform-browser';
715
import {
16+
MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,
817
MatButtonToggle,
918
MatButtonToggleChange,
19+
MatButtonToggleDefaultOptions,
1020
MatButtonToggleGroup,
1121
MatButtonToggleModule,
1222
} from './index';
@@ -546,6 +556,13 @@ describe('MatButtonToggle without forms', () => {
546556
expect(groupInstance.value).toBeFalsy();
547557
expect(groupInstance.selected).toBeFalsy();
548558
}));
559+
560+
it('should show checkmark indicator by default', () => {
561+
buttonToggleLabelElements[0].click();
562+
fixture.detectChanges();
563+
564+
expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(1);
565+
});
549566
});
550567

551568
describe('with initial value and change event', () => {
@@ -701,6 +718,14 @@ describe('MatButtonToggle without forms', () => {
701718
groupInstance.value = 'not-an-array';
702719
}).toThrowError(/Value must be an array/);
703720
});
721+
722+
it('should show checkmark indicator by default', () => {
723+
buttonToggleLabelElements[0].click();
724+
buttonToggleLabelElements[1].click();
725+
fixture.detectChanges();
726+
727+
expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(2);
728+
});
704729
});
705730

706731
describe('as standalone', () => {
@@ -876,6 +901,52 @@ describe('MatButtonToggle without forms', () => {
876901
});
877902
});
878903

904+
describe('with tokens to hide checkmark selection indicators', () => {
905+
beforeEach(() => {
906+
TestBed.configureTestingModule({
907+
imports: [
908+
MatButtonToggleModule,
909+
ButtonTogglesInsideButtonToggleGroup,
910+
ButtonTogglesInsideButtonToggleGroupMultiple,
911+
],
912+
providers: [
913+
{
914+
provide: MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS,
915+
useValue: {
916+
hideSingleSelectionIndicator: true,
917+
hideMultipleSelectionIndicator: true,
918+
},
919+
},
920+
],
921+
});
922+
923+
TestBed.compileComponents();
924+
});
925+
926+
it('should hide checkmark indicator for single selection', () => {
927+
const fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroup);
928+
fixture.detectChanges();
929+
930+
fixture.debugElement.query(By.css('button')).nativeElement.click();
931+
fixture.detectChanges();
932+
933+
expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(0);
934+
});
935+
936+
it('should hide checkmark indicator for multiple selection', () => {
937+
const fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroupMultiple);
938+
fixture.detectChanges();
939+
940+
// Check all button toggles in the group
941+
fixture.debugElement
942+
.queryAll(By.css('button'))
943+
.forEach(toggleButton => toggleButton.nativeElement.click());
944+
fixture.detectChanges();
945+
946+
expect(document.querySelectorAll('.mat-pseudo-checkbox').length).toBe(0);
947+
});
948+
});
949+
879950
it('should not throw on init when toggles are repeated and there is an initial value', () => {
880951
const fixture = TestBed.createComponent(RepeatedButtonTogglesWithPreselectedValue);
881952

src/material/button-toggle/button-toggle.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
booleanAttribute,
3434
} from '@angular/core';
3535
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
36-
import {MatRipple} from '@angular/material/core';
36+
import {MatRipple, MatPseudoCheckbox} from '@angular/material/core';
3737

3838
/**
3939
* @deprecated No longer used.
@@ -54,6 +54,10 @@ export interface MatButtonToggleDefaultOptions {
5454
* setting an appearance on a button toggle or group.
5555
*/
5656
appearance?: MatButtonToggleAppearance;
57+
/** Whetehr icon indicators should be hidden for single-selection button toggle groups. */
58+
hideSingleSelectionIndicator?: boolean;
59+
/** Whether icon indicators should be hidden for multiple-selection button toggle groups. */
60+
hideMultipleSelectionIndicator?: boolean;
5761
}
5862

5963
/**
@@ -62,8 +66,19 @@ export interface MatButtonToggleDefaultOptions {
6266
*/
6367
export const MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS = new InjectionToken<MatButtonToggleDefaultOptions>(
6468
'MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS',
69+
{
70+
providedIn: 'root',
71+
factory: MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY,
72+
},
6573
);
6674

75+
export function MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY(): MatButtonToggleDefaultOptions {
76+
return {
77+
hideSingleSelectionIndicator: false,
78+
hideMultipleSelectionIndicator: false,
79+
};
80+
}
81+
6782
/**
6883
* Injection token that can be used to reference instances of `MatButtonToggleGroup`.
6984
* It serves as alternative token to the actual `MatButtonToggleGroup` class which
@@ -215,6 +230,28 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
215230
@Output() readonly change: EventEmitter<MatButtonToggleChange> =
216231
new EventEmitter<MatButtonToggleChange>();
217232

233+
/** Whether checkmark indicator for single-selection button toggle groups is hidden. */
234+
@Input({transform: booleanAttribute})
235+
get hideSingleSelectionIndicator(): boolean {
236+
return this._hideSingleSelectionIndicator;
237+
}
238+
set hideSingleSelectionIndicator(value: boolean) {
239+
this._hideSingleSelectionIndicator = value;
240+
this._markButtonsForCheck();
241+
}
242+
private _hideSingleSelectionIndicator: boolean;
243+
244+
/** Whether checkmark indicator for multiple-selection button toggle groups is hidden. */
245+
@Input({transform: booleanAttribute})
246+
get hideMultipleSelectionIndicator(): boolean {
247+
return this._hideMultipleSelectionIndicator;
248+
}
249+
set hideMultipleSelectionIndicator(value: boolean) {
250+
this._hideMultipleSelectionIndicator = value;
251+
this._markButtonsForCheck();
252+
}
253+
private _hideMultipleSelectionIndicator: boolean;
254+
218255
constructor(
219256
private _changeDetector: ChangeDetectorRef,
220257
@Optional()
@@ -223,6 +260,8 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
223260
) {
224261
this.appearance =
225262
defaultOptions && defaultOptions.appearance ? defaultOptions.appearance : 'standard';
263+
this.hideSingleSelectionIndicator = defaultOptions?.hideSingleSelectionIndicator ?? false;
264+
this.hideMultipleSelectionIndicator = defaultOptions?.hideMultipleSelectionIndicator ?? false;
226265
}
227266

228267
ngOnInit() {
@@ -401,7 +440,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
401440
'role': 'presentation',
402441
},
403442
standalone: true,
404-
imports: [MatRipple],
443+
imports: [MatRipple, MatPseudoCheckbox],
405444
})
406445
export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
407446
private _checked = false;

0 commit comments

Comments
 (0)