Skip to content

Commit

Permalink
fix(material-experimental/mdc-radio): add accessible touch targets (#…
Browse files Browse the repository at this point in the history
…22994)

Sets up accessible touch targets on the MDC-based radio button.

Also hides the touch targets on the two lowest densities. This is something I forgot to do in #22892.

Fixes #22991.

(cherry picked from commit 150d5af)
  • Loading branch information
crisbeto authored and mmalerba committed Jun 22, 2021
1 parent 539e4cd commit 6074fd1
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 15 deletions.
6 changes: 6 additions & 0 deletions src/material-experimental/mdc-checkbox/_checkbox-theme.scss
Expand Up @@ -125,6 +125,12 @@
$query: mdc-helpers.$mat-base-styles-query
);
}

@if ($density-scale == -2 or $density-scale == 'minimum') {
.mat-mdc-checkbox-touch-target {
display: none;
}
}
}

@mixin theme($theme-or-color-config) {
Expand Down
6 changes: 6 additions & 0 deletions src/material-experimental/mdc-radio/_radio-theme.scss
Expand Up @@ -58,6 +58,12 @@
.mat-mdc-radio-button .mdc-radio {
@include mdc-radio-theme.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
}

@if ($density-scale == -2 or $density-scale == 'minimum') {
.mat-mdc-radio-touch-target {
display: none;
}
}
}

@mixin theme($theme-or-color-config) {
Expand Down
4 changes: 3 additions & 1 deletion src/material-experimental/mdc-radio/radio.html
@@ -1,6 +1,8 @@
<div class="mdc-form-field" #formField
[class.mdc-form-field--align-end]="labelPosition == 'before'">
<div class="mdc-radio" [ngClass]="_classes">
<!-- Render this element first so the input is on top. -->
<div class="mat-mdc-radio-touch-target" (click)="_onInputInteraction($event)"></div>
<input #input class="mdc-radio__native-control" type="radio"
[id]="inputId"
[checked]="checked"
Expand All @@ -12,7 +14,7 @@
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
[attr.aria-describedby]="ariaDescribedby"
(change)="_onInputChange($event)">
(change)="_onInputInteraction($event)">
<div class="mdc-radio__background">
<div class="mdc-radio__outer-circle"></div>
<div class="mdc-radio__inner-circle"></div>
Expand Down
14 changes: 14 additions & 0 deletions src/material-experimental/mdc-radio/radio.scss
@@ -1,6 +1,7 @@
@use '@material/radio/radio' as mdc-radio;
@use '@material/radio/radio-theme' as mdc-radio-theme;
@use '@material/form-field' as mdc-form-field;
@use '@material/touch-target' as mdc-touch-target;
@use '../mdc-helpers/mdc-helpers';
@use '../../cdk/a11y';
@use '../../material/core/style/layout-common';
Expand All @@ -25,6 +26,19 @@
@include mdc-radio.without-ripple($query: animation);
}

// Element used to provide a larger tap target for users on touch devices.
.mat-mdc-radio-touch-target {
@include mdc-touch-target.touch-target(
$set-width: true,
$query: mdc-helpers.$mat-base-styles-query);

[dir='rtl'] & {
left: 0;
right: 50%;
transform: translate(50%, -50%);
}
}

// Note that this creates a square box around the circle, however it's consistent with
// how IE/Edge treat native radio buttons in high contrast mode. We can't turn the border
// into a dotted one, because it's too thick which causes the circles to look off.
Expand Down
2 changes: 1 addition & 1 deletion src/material/radio/radio.html
Expand Up @@ -16,7 +16,7 @@
[attr.aria-label]="ariaLabel"
[attr.aria-labelledby]="ariaLabelledby"
[attr.aria-describedby]="ariaDescribedby"
(change)="_onInputChange($event)"
(change)="_onInputInteraction($event)"
(click)="_onInputClick($event)">

<!-- The ripple comes after the input so that we can target it with a CSS
Expand Down
23 changes: 11 additions & 12 deletions src/material/radio/radio.ts
Expand Up @@ -588,24 +588,23 @@ export abstract class _MatRadioButtonBase extends _MatRadioButtonMixinBase imple
event.stopPropagation();
}

/**
* Triggered when the radio button received a click or the input recognized any change.
* Clicking on a label element, will trigger a change event on the associated input.
*/
_onInputChange(event: Event) {
/** Triggered when the radio button receives an interaction from the user. */
_onInputInteraction(event: Event) {
// We always have to 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.
event.stopPropagation();

const groupValueChanged = this.radioGroup && this.value !== this.radioGroup.value;
this.checked = true;
this._emitChangeEvent();
if (!this.checked && !this.disabled) {
const groupValueChanged = this.radioGroup && this.value !== this.radioGroup.value;
this.checked = true;
this._emitChangeEvent();

if (this.radioGroup) {
this.radioGroup._controlValueAccessorChangeFn(this.value);
if (groupValueChanged) {
this.radioGroup._emitChangeEvent();
if (this.radioGroup) {
this.radioGroup._controlValueAccessorChangeFn(this.value);
if (groupValueChanged) {
this.radioGroup._emitChangeEvent();
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tools/public_api_guard/material/radio.d.ts
Expand Up @@ -25,8 +25,8 @@ export declare abstract class _MatRadioButtonBase extends _MatRadioButtonMixinBa
constructor(radioGroup: _MatRadioGroupBase<_MatRadioButtonBase>, elementRef: ElementRef, _changeDetector: ChangeDetectorRef, _focusMonitor: FocusMonitor, _radioDispatcher: UniqueSelectionDispatcher, animationMode?: string, _providerOverride?: MatRadioDefaultOptions | undefined, tabIndex?: string);
_isRippleDisabled(): boolean;
_markForCheck(): void;
_onInputChange(event: Event): void;
_onInputClick(event: Event): void;
_onInputInteraction(event: Event): void;
protected _setDisabled(value: boolean): void;
focus(options?: FocusOptions, origin?: FocusOrigin): void;
ngAfterViewInit(): void;
Expand Down

0 comments on commit 6074fd1

Please sign in to comment.