Skip to content

Commit 3fb737c

Browse files
authored
fix(material/checkbox): avoid making the touch target a focus stop (#33584)
Along the same lines as #33558. Some screen readers detect which elements have `click` listeners and make them a focus stop, even if they're `aria-hidden`. These changes rework the checkbox so it doesn't have to rely on a `click` listener on the touch target by wrapping the `input` in the `label` instead.
1 parent d61a26d commit 3fb737c

4 files changed

Lines changed: 23 additions & 39 deletions

File tree

goldens/material/checkbox/index.api.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess
6363
get inputId(): string;
6464
// (undocumented)
6565
_isRippleDisabled(): boolean;
66-
_labelElement: ElementRef<HTMLInputElement>;
6766
labelPosition: 'before' | 'after';
6867
name: string | null;
6968
// (undocumented)
@@ -94,8 +93,6 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess
9493
_onInteractionEvent(event: Event): void;
9594
_onLabelTextChange(): void;
9695
_onTouched: () => any;
97-
// (undocumented)
98-
_onTouchTargetClick(): void;
9996
_preventBubblingFromLabel(event: MouseEvent): void;
10097
// (undocumented)
10198
registerOnChange(fn: (value: any) => void): void;
Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
<div mat-internal-form-field [labelPosition]="labelPosition" (click)="_preventBubblingFromLabel($event)">
2-
<div #checkbox class="mdc-checkbox">
1+
<label
2+
mat-internal-form-field
3+
[labelPosition]="labelPosition"
4+
[for]="inputId"
5+
(click)="_preventBubblingFromLabel($event)">
6+
<span #checkbox class="mdc-checkbox">
37
<!-- Render this element first so the input is on top. -->
4-
<div
5-
class="mat-mdc-checkbox-touch-target"
6-
(click)="_onTouchTargetClick()"
7-
aria-hidden="true"></div>
8+
<span class="mat-mdc-checkbox-touch-target" aria-hidden="true"></span>
89
<input #input
910
type="checkbox"
1011
class="mdc-checkbox__native-control"
@@ -28,30 +29,26 @@
2829
(blur)="_onBlur()"
2930
(click)="_onInputClick()"
3031
(change)="_onInteractionEvent($event)"/>
31-
<div class="mdc-checkbox__ripple" aria-hidden="true"></div>
32-
<div class="mdc-checkbox__background" aria-hidden="true">
32+
<span class="mdc-checkbox__ripple" aria-hidden="true"></span>
33+
<span class="mdc-checkbox__background" aria-hidden="true">
3334
<svg class="mdc-checkbox__checkmark"
3435
focusable="false"
3536
viewBox="0 0 24 24">
3637
<path class="mdc-checkbox__checkmark-path"
3738
fill="none"
3839
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
3940
</svg>
40-
<div class="mdc-checkbox__mixedmark"></div>
41-
</div>
42-
<div class="mat-mdc-checkbox-ripple mat-focus-indicator"
41+
<span class="mdc-checkbox__mixedmark"></span>
42+
</span>
43+
<span class="mat-mdc-checkbox-ripple mat-focus-indicator"
4344
mat-ripple
4445
aria-hidden="true"
4546
[matRippleTrigger]="checkbox"
4647
[matRippleDisabled]="disableRipple || disabled"
47-
[matRippleCentered]="true"></div>
48-
</div>
49-
<!--
50-
Avoid putting a click handler on the <label/> to fix duplicate navigation stop on Talk Back
51-
(#14385). Putting a click handler on the <label/> caused this bug because the browser produced
52-
an unnecessary accessibility tree node.
53-
-->
54-
<label class="mdc-label" #label [for]="inputId">
48+
[matRippleCentered]="true"></span>
49+
</span>
50+
51+
<span #label class="mat-internal-form-field-label mdc-label">
5552
<ng-content></ng-content>
56-
</label>
57-
</div>
53+
</span>
54+
</label>

src/material/checkbox/checkbox.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ $fallbacks: m3-checkbox.get-tokens();
4444

4545
label {
4646
cursor: default;
47+
}
48+
49+
.mat-internal-form-field-label {
4750
color: token-utils.slot(checkbox-disabled-label-color, $fallbacks);
4851

4952
@include cdk.high-contrast {
@@ -54,7 +57,7 @@ $fallbacks: m3-checkbox.get-tokens();
5457

5558
// The MDC styles result in extra padding if the label is present but empty. To fix this we hide
5659
// the label when it is empty.
57-
label:empty {
60+
.mat-internal-form-field-label:empty {
5861
display: none;
5962
}
6063

src/material/checkbox/checkbox.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,6 @@ export class MatCheckbox
205205
/** The native `<input type="checkbox">` element */
206206
@ViewChild('input') _inputElement!: ElementRef<HTMLInputElement>;
207207

208-
/** The native `<label>` element */
209-
@ViewChild('label') _labelElement!: ElementRef<HTMLInputElement>;
210-
211208
/** Tabindex for the checkbox. */
212209
@Input({transform: (value: unknown) => (value == null ? undefined : numberAttribute(value))})
213210
tabIndex: number;
@@ -515,16 +512,6 @@ export class MatCheckbox
515512
this._handleInputClick();
516513
}
517514

518-
_onTouchTargetClick() {
519-
this._handleInputClick();
520-
521-
if (!this.disabled) {
522-
// Normally the input should be focused already, but if the click
523-
// comes from the touch target, then we might have to focus it ourselves.
524-
this._inputElement.nativeElement.focus();
525-
}
526-
}
527-
528515
/**
529516
* Prevent click events that come from the `<label/>` element from bubbling. This prevents the
530517
* click handler on the host from triggering twice when clicking on the `<label/>` element. After
@@ -533,7 +520,7 @@ export class MatCheckbox
533520
* bubbles when the label is clicked.
534521
*/
535522
_preventBubblingFromLabel(event: MouseEvent) {
536-
if (!!event.target && this._labelElement.nativeElement.contains(event.target as HTMLElement)) {
523+
if (event.target && this._inputElement && event.target !== this._inputElement.nativeElement) {
537524
event.stopPropagation();
538525
}
539526
}

0 commit comments

Comments
 (0)