mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 18:54:11 +08:00
fix(checkbox): use a native input to fix a11y issues with axe and screen readers (#22402)
fixes #21644 fixes #20517 fixes #17796
This commit is contained in:
@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { CheckboxChangeEventDetail, Color, StyleEventDetail } from '../../interface';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { getAriaLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@ -22,7 +22,7 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
export class Checkbox implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
private buttonEl?: HTMLElement;
|
||||
private focusEl?: HTMLElement;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@ -54,11 +54,11 @@ export class Checkbox implements ComponentInterface {
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked`
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked`
|
||||
* property for that.
|
||||
*
|
||||
* The value of a toggle is analogous to the value of a `<input type="checkbox">`,
|
||||
* it's only used when the toggle participates in a native `<form>`.
|
||||
* The value of a checkbox is analogous to the value of an `<input type="checkbox">`,
|
||||
* it's only used when the checkbox participates in a native `<form>`.
|
||||
*/
|
||||
@Prop() value = 'on';
|
||||
|
||||
@ -68,12 +68,12 @@ export class Checkbox implements ComponentInterface {
|
||||
@Event() ionChange!: EventEmitter<CheckboxChangeEventDetail>;
|
||||
|
||||
/**
|
||||
* Emitted when the toggle has focus.
|
||||
* Emitted when the checkbox has focus.
|
||||
*/
|
||||
@Event() ionFocus!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* Emitted when the toggle loses focus.
|
||||
* Emitted when the checkbox loses focus.
|
||||
*/
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
@ -109,12 +109,15 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
if (this.focusEl) {
|
||||
this.focusEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
private onClick = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setFocus();
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
@ -129,14 +132,11 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputId, indeterminate, disabled, checked, value, color, el } = this;
|
||||
const labelId = inputId + '-lbl';
|
||||
const { color, checked, disabled, el, indeterminate, inputId, name, value } = this;
|
||||
const mode = getIonMode(this);
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
renderHiddenInput(true, el, this.name, (checked ? value : ''), disabled);
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
|
||||
let path = indeterminate
|
||||
? <path d="M6 12L18 12" part="mark" />
|
||||
@ -151,10 +151,10 @@ export class Checkbox implements ComponentInterface {
|
||||
return (
|
||||
<Host
|
||||
onClick={this.onClick}
|
||||
role="checkbox"
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-labelledby={labelId}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@ -167,14 +167,18 @@ export class Checkbox implements ComponentInterface {
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
{path}
|
||||
</svg>
|
||||
<button
|
||||
type="button"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
ref={btnEl => this.buttonEl = btnEl}
|
||||
>
|
||||
</button>
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={focusEl => this.focusEl = focusEl}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user