diff --git a/src/dev-app/button-toggle/button-toggle-demo.html b/src/dev-app/button-toggle/button-toggle-demo.html index b2cfc8186f3e..474b5383790b 100644 --- a/src/dev-app/button-toggle/button-toggle-demo.html +++ b/src/dev-app/button-toggle/button-toggle-demo.html @@ -9,7 +9,7 @@

Exclusive Selection

- + format_align_left @@ -26,7 +26,7 @@

Exclusive Selection

- + format_align_left diff --git a/src/material/button-toggle/button-toggle.html b/src/material/button-toggle/button-toggle.html index 9a2113709715..301b3826dbc1 100644 --- a/src/material/button-toggle/button-toggle.html +++ b/src/material/button-toggle/button-toggle.html @@ -1,20 +1,20 @@ - + + diff --git a/src/material/button-toggle/button-toggle.scss b/src/material/button-toggle/button-toggle.scss index e12094d2c5d5..ea5137c804ec 100644 --- a/src/material/button-toggle/button-toggle.scss +++ b/src/material/button-toggle/button-toggle.scss @@ -272,4 +272,12 @@ $_standard-tokens: ( &::-moz-focus-inner { border: 0; } + + opacity: 0.01; + z-index: 100; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; } diff --git a/src/material/button-toggle/button-toggle.spec.ts b/src/material/button-toggle/button-toggle.spec.ts index 7f33cc6668f9..b0ff25da91c6 100644 --- a/src/material/button-toggle/button-toggle.spec.ts +++ b/src/material/button-toggle/button-toggle.spec.ts @@ -97,7 +97,7 @@ describe('MatButtonToggle with forms', () => { buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MatButtonToggle)); buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); innerButtons = buttonToggleDebugElements.map( - debugEl => debugEl.query(By.css('button'))!.nativeElement, + debugEl => debugEl.query(By.css('input'))!.nativeElement, ); fixture.detectChanges(); @@ -256,7 +256,7 @@ describe('MatButtonToggle with forms', () => { const fixture = TestBed.createComponent(ButtonToggleGroupWithIndirectDescendantToggles); fixture.detectChanges(); - const button = fixture.nativeElement.querySelector('.mat-button-toggle button'); + const button = fixture.nativeElement.querySelector('.mat-button-toggle input'); const groupDebugElement = fixture.debugElement.query(By.directive(MatButtonToggleGroup))!; const groupInstance = groupDebugElement.injector.get(MatButtonToggleGroup); @@ -359,7 +359,7 @@ describe('MatButtonToggle without forms', () => { buttonToggleNativeElements = buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); buttonToggleLabelElements = fixture.debugElement - .queryAll(By.css('button')) + .queryAll(By.css('input')) .map(debugEl => debugEl.nativeElement); buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); @@ -401,7 +401,7 @@ describe('MatButtonToggle without forms', () => { }); it('should disable the underlying button when the group is disabled', () => { - const buttons = buttonToggleNativeElements.map(toggle => toggle.querySelector('button')!); + const buttons = buttonToggleNativeElements.map(toggle => toggle.querySelector('input')!); expect(buttons.every(input => input.disabled)).toBe(false); @@ -595,7 +595,7 @@ describe('MatButtonToggle without forms', () => { buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(MatButtonToggle)); buttonToggleNativeElements = buttonToggleDebugElements.map(debugEl => debugEl.nativeElement); buttonToggleLabelElements = fixture.debugElement - .queryAll(By.css('button')) + .queryAll(By.css('input')) .map(debugEl => debugEl.nativeElement); buttonToggleInstances = buttonToggleDebugElements.map(debugEl => debugEl.componentInstance); })); @@ -612,7 +612,7 @@ describe('MatButtonToggle without forms', () => { expect(buttonToggleInstances.every(buttonToggle => !buttonToggle.checked)).toBe(true); const nativeCheckboxLabel = buttonToggleDebugElements[0].query( - By.css('button'), + By.css('input'), )!.nativeElement; nativeCheckboxLabel.click(); @@ -638,7 +638,7 @@ describe('MatButtonToggle without forms', () => { it('should check a button toggle upon interaction with underlying native checkbox', () => { const nativeCheckboxButton = buttonToggleDebugElements[0].query( - By.css('button'), + By.css('input'), )!.nativeElement; nativeCheckboxButton.click(); @@ -722,7 +722,7 @@ describe('MatButtonToggle without forms', () => { )!.nativeElement; buttonToggleInstance = buttonToggleDebugElement.componentInstance; buttonToggleButtonElement = buttonToggleNativeElement.querySelector( - 'button', + 'input', )! as HTMLButtonElement; })); @@ -761,7 +761,7 @@ describe('MatButtonToggle without forms', () => { })); it('should focus on underlying input element when focus() is called', () => { - const nativeButton = buttonToggleDebugElement.query(By.css('button'))!.nativeElement; + const nativeButton = buttonToggleDebugElement.query(By.css('input'))!.nativeElement; expect(document.activeElement).not.toBe(nativeButton); buttonToggleInstance.focus(); @@ -790,7 +790,7 @@ describe('MatButtonToggle without forms', () => { const fixture = TestBed.createComponent(StandaloneButtonToggle); const checkboxDebugElement = fixture.debugElement.query(By.directive(MatButtonToggle))!; const checkboxNativeElement = checkboxDebugElement.nativeElement; - const buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement; + const buttonElement = checkboxNativeElement.querySelector('input') as HTMLButtonElement; fixture.detectChanges(); expect(buttonElement.hasAttribute('aria-label')).toBe(false); @@ -800,7 +800,7 @@ describe('MatButtonToggle without forms', () => { const fixture = TestBed.createComponent(ButtonToggleWithAriaLabel); const checkboxDebugElement = fixture.debugElement.query(By.directive(MatButtonToggle))!; const checkboxNativeElement = checkboxDebugElement.nativeElement; - const buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement; + const buttonElement = checkboxNativeElement.querySelector('input') as HTMLButtonElement; fixture.detectChanges(); expect(buttonElement.getAttribute('aria-label')).toBe('Super effective'); @@ -825,7 +825,7 @@ describe('MatButtonToggle without forms', () => { const fixture = TestBed.createComponent(ButtonToggleWithAriaLabelledby); checkboxDebugElement = fixture.debugElement.query(By.directive(MatButtonToggle))!; checkboxNativeElement = checkboxDebugElement.nativeElement; - buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement; + buttonElement = checkboxNativeElement.querySelector('input') as HTMLButtonElement; fixture.detectChanges(); expect(buttonElement.getAttribute('aria-labelledby')).toBe('some-id'); @@ -835,7 +835,7 @@ describe('MatButtonToggle without forms', () => { const fixture = TestBed.createComponent(StandaloneButtonToggle); checkboxDebugElement = fixture.debugElement.query(By.directive(MatButtonToggle))!; checkboxNativeElement = checkboxDebugElement.nativeElement; - buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement; + buttonElement = checkboxNativeElement.querySelector('input') as HTMLButtonElement; fixture.detectChanges(); expect(buttonElement.getAttribute('aria-labelledby')).toBe(null); @@ -847,7 +847,7 @@ describe('MatButtonToggle without forms', () => { const fixture = TestBed.createComponent(ButtonToggleWithTabindex); fixture.detectChanges(); - const button = fixture.nativeElement.querySelector('.mat-button-toggle button'); + const button = fixture.nativeElement.querySelector('.mat-button-toggle input'); expect(button.getAttribute('tabindex')).toBe('3'); }); @@ -866,7 +866,7 @@ describe('MatButtonToggle without forms', () => { fixture.detectChanges(); const host = fixture.nativeElement.querySelector('.mat-button-toggle'); - const button = host.querySelector('button'); + const button = host.querySelector('input'); expect(document.activeElement).not.toBe(button); @@ -891,7 +891,7 @@ describe('MatButtonToggle without forms', () => { const hostNode: HTMLElement = fixture.nativeElement.querySelector('.mat-button-toggle'); expect(hostNode.hasAttribute('name')).toBe(false); - expect(hostNode.querySelector('button')!.getAttribute('name')).toBe('custom-name'); + expect(hostNode.querySelector('input')!.getAttribute('name')).toBe('custom-name'); }); it( diff --git a/src/material/button-toggle/button-toggle.ts b/src/material/button-toggle/button-toggle.ts index b5ea5c549226..5861c7fdb36b 100644 --- a/src/material/button-toggle/button-toggle.ts +++ b/src/material/button-toggle/button-toggle.ts @@ -106,8 +106,8 @@ export class MatButtonToggleChange { {provide: MAT_BUTTON_TOGGLE_GROUP, useExisting: MatButtonToggleGroup}, ], host: { - 'role': 'group', 'class': 'mat-button-toggle-group', + '[role]': "multiple ? 'group' : 'radiogroup'", '[attr.aria-disabled]': 'disabled', '[class.mat-button-toggle-vertical]': 'vertical', '[class.mat-button-toggle-group-appearance-standard]': 'appearance === "standard"', @@ -417,8 +417,13 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { */ @Input('aria-labelledby') ariaLabelledby: string | null = null; + /** Type of the button toggle. Either 'radio' or 'button'. */ + get type(): string { + return this._isSingleSelector() ? 'radio' : 'button'; + } + /** Underlying native `button` element. */ - @ViewChild('button') _buttonElement: ElementRef; + @ViewChild('input') _inputElement: ElementRef; /** The parent button toggle group (exclusive selection). Optional. */ buttonToggleGroup: MatButtonToggleGroup; @@ -536,7 +541,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { /** Focuses the button. */ focus(options?: FocusOptions): void { - this._buttonElement.nativeElement.focus(options); + this._inputElement.nativeElement.focus(options); } /** Checks the button toggle due to an interaction with the underlying native button. */ @@ -554,6 +559,15 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { this.change.emit(new MatButtonToggleChange(this, this.value)); } + /** + * Stop propagation on the change event. + * Otherwise the change event, from the input element, will bubble up and + * emit its event object to the `change` output. + */ + _onInteractionEvent(event: Event) { + event.stopPropagation(); + } + /** * Marks the button toggle as needing checking for change detection. * This method is exposed because the parent button toggle group will directly @@ -573,6 +587,15 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { return this.name || null; } + /** Get the aria-pressed attribute value. */ + _getAriaPressed(): boolean | null { + // When the toggle stands alone, or in multiple selection mode, use aria-pressed attribute. + if (!this._isSingleSelector()) { + return this.checked; + } + return null; + } + /** Whether the toggle is in single selection mode. */ private _isSingleSelector(): boolean { return this.buttonToggleGroup && !this.buttonToggleGroup.multiple; diff --git a/tools/public_api_guard/material/button-toggle.md b/tools/public_api_guard/material/button-toggle.md index 2c3fa8aaa840..9cb64bd630c3 100644 --- a/tools/public_api_guard/material/button-toggle.md +++ b/tools/public_api_guard/material/button-toggle.md @@ -34,7 +34,6 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { set appearance(value: MatButtonToggleAppearance); ariaLabel: string; ariaLabelledby: string | null; - _buttonElement: ElementRef; get buttonId(): string; buttonToggleGroup: MatButtonToggleGroup; readonly change: EventEmitter; @@ -44,8 +43,10 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { set disabled(value: boolean); disableRipple: boolean; focus(options?: FocusOptions): void; + _getAriaPressed(): boolean | null; _getButtonName(): string | null; id: string; + _inputElement: ElementRef; _markForCheck(): void; name: string; // (undocumented) @@ -61,7 +62,9 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { // (undocumented) ngOnInit(): void; _onButtonClick(): void; + _onInteractionEvent(event: Event): void; tabIndex: number | null; + get type(): string; value: any; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration;