Skip to content

Commit

Permalink
fix(input): announce helper and error text in screen readers
Browse files Browse the repository at this point in the history
  • Loading branch information
thetaPC committed Oct 21, 2024
1 parent be7561d commit dc55ac6
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 54 deletions.
29 changes: 28 additions & 1 deletion core/src/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,28 @@ export class Input implements ComponentInterface {
private renderHintText() {
const { helperText, errorText } = this;

return [<div class="helper-text">{helperText}</div>, <div class="error-text">{errorText}</div>];
return [
<div id={HELPER_TEXT_ID} class="helper-text">
{helperText}
</div>,
<div id={ERROR_TEXT_ID} class="error-text">
{errorText}
</div>,
];
}

private getHintTextID(): string | undefined {
const { el, helperText, errorText } = this;

if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
return ERROR_TEXT_ID;
}

if (helperText) {
return HELPER_TEXT_ID;
}

return undefined;
}

private renderCounter() {
Expand Down Expand Up @@ -700,6 +721,8 @@ export class Input implements ComponentInterface {
const hasValue = this.hasValue();
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;

console.log('el', this.el);
console.log('id', this.getHintTextID());
/**
* If the label is stacked, it should always sit above the input.
* For floating labels, the label should move above the input if
Expand Down Expand Up @@ -777,6 +800,8 @@ export class Input implements ComponentInterface {
onKeyDown={this.onKeydown}
onCompositionstart={this.onCompositionStart}
onCompositionend={this.onCompositionEnd}
aria-describedby={this.getHintTextID()}
aria-invalid={this.getHintTextID() === ERROR_TEXT_ID}
{...this.inheritedAttributes}
/>
{this.clearInput && !readonly && !disabled && (
Expand Down Expand Up @@ -817,3 +842,5 @@ export class Input implements ComponentInterface {
}

let inputIds = 0;
const HELPER_TEXT_ID = `${'helper-text-' + inputIds}`;
const ERROR_TEXT_ID = `${'error-text-' + inputIds}`;
79 changes: 26 additions & 53 deletions core/src/components/input/test/bottom-content/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,67 +49,40 @@
</ion-header>

<ion-content id="content" class="ion-padding">
<div class="grid">
<div class="grid-item">
<h2>No Hint</h2>
<ion-input label="Email"></ion-input>
</div>
<ion-input
type="email"
fill="solid"
label="Email"
label-placement="floating"
helper-text="Enter a valid email"
error-text="Invalid email"
></ion-input>

<div class="grid-item">
<h2>Helper Hint</h2>
<ion-input label="Email" helper-text="Enter your email"></ion-input>
</div>
<script>
const input = document.querySelector('ion-input');

<div class="grid-item">
<h2>Error Hint</h2>
<ion-input
class="ion-touched ion-invalid"
label="Email"
error-text="Please enter a valid email"
></ion-input>
</div>
input.addEventListener('ionInput', (ev) => validate(ev));
input.addEventListener('ionBlur', () => markTouched());

<div class="grid-item">
<h2>Custom Error Color</h2>
<ion-input
class="ion-touched ion-invalid custom-error-color"
label="Email"
error-text="Please enter a valid email"
></ion-input>
</div>
const validateEmail = (email) => {
return email.match(
/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
);
};

<div class="grid-item">
<h2>Counter</h2>
<ion-input label="Email" counter="true" maxlength="100"></ion-input>
</div>
const validate = (ev) => {
const value = ev.target.value;

<div class="grid-item">
<h2>Custom Counter</h2>
<ion-input id="custom-counter" label="Email" counter="true" maxlength="100"></ion-input>
</div>
input.classList.remove('ion-valid');
input.classList.remove('ion-invalid');

<div class="grid-item">
<h2>Counter with Helper</h2>
<ion-input label="Email" counter="true" maxlength="100" helper-text="Enter an email"></ion-input>
</div>
if (value === '') return;

<div class="grid-item">
<h2>Counter with Error</h2>
<ion-input
class="ion-touched ion-invalid"
label="Email"
counter="true"
maxlength="100"
error-text="Please enter a valid email"
></ion-input>
</div>
</div>
validateEmail(value) ? input.classList.add('ion-valid') : input.classList.add('ion-invalid');
};

<script>
const customCounterInput = document.querySelector('ion-input#custom-counter');
customCounterInput.counterFormatter = (inputLength, maxLength) => {
const length = maxLength - inputLength;
return `${maxLength - inputLength} characters left`;
const markTouched = () => {
input.classList.add('ion-touched');
};
</script>
</ion-content>
Expand Down

0 comments on commit dc55ac6

Please sign in to comment.