Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)


### Bug Fixes

* **angular:** add missing 'compareWith' input to standalone ion-radio-group ([#29870](https://github.com/ionic-team/ionic-framework/issues/29870)) ([47ba703](https://github.com/ionic-team/ionic-framework/commit/47ba703a57d1ca506f943f6b790d0bf7583d79cb)), closes [#29826](https://github.com/ionic-team/ionic-framework/issues/29826)
* **backdrop:** remove tabindex for improved accessibility ([#29956](https://github.com/ionic-team/ionic-framework/issues/29956)) ([7294e96](https://github.com/ionic-team/ionic-framework/commit/7294e969bb913692eaf28e54860614f445132713)), closes [#29773](https://github.com/ionic-team/ionic-framework/issues/29773)
* **input, textarea:** ensure screen readers announce helper and error text when focused ([#29958](https://github.com/ionic-team/ionic-framework/issues/29958)) ([5a73145](https://github.com/ionic-team/ionic-framework/commit/5a7314553a8def87bd19275640c92dd72a6ef1a4))
* **overlay:** hide from screen readers while animating ([#29951](https://github.com/ionic-team/ionic-framework/issues/29951)) ([cb60073](https://github.com/ionic-team/ionic-framework/commit/cb6007363a8d42b5f126945427c2bfc3d7209c21)), closes [#29857](https://github.com/ionic-team/ionic-framework/issues/29857)
* **overlays:** do not hide root when toast appears ([#29962](https://github.com/ionic-team/ionic-framework/issues/29962)) ([322d7c9](https://github.com/ionic-team/ionic-framework/commit/322d7c98cf6613df0b0db3f119e3f892e6a17e7b)), closes [#29773](https://github.com/ionic-team/ionic-framework/issues/29773)
* **overlays:** hide the focus trap div from screen readers ([#29970](https://github.com/ionic-team/ionic-framework/issues/29970)) ([c3b58f1](https://github.com/ionic-team/ionic-framework/commit/c3b58f1620bcb74db43e3983ef570b7b982abd83)), closes [#29858](https://github.com/ionic-team/ionic-framework/issues/29858)
* **vue:** incorrect view rendered when using router.go(-n) ([#29877](https://github.com/ionic-team/ionic-framework/issues/29877)) ([e32fbe0](https://github.com/ionic-team/ionic-framework/commit/e32fbe02102fe80db29f73c26496a40852032354)), closes [#28201](https://github.com/ionic-team/ionic-framework/issues/28201) [#28201](https://github.com/ionic-team/ionic-framework/issues/28201) [#29847](https://github.com/ionic-team/ionic-framework/issues/29847)





## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16)


Expand Down
15 changes: 15 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [8.3.4](https://github.com/ionic-team/ionic-framework/compare/v8.3.3...v8.3.4) (2024-10-30)


### Bug Fixes

* **backdrop:** remove tabindex for improved accessibility ([#29956](https://github.com/ionic-team/ionic-framework/issues/29956)) ([7294e96](https://github.com/ionic-team/ionic-framework/commit/7294e969bb913692eaf28e54860614f445132713)), closes [#29773](https://github.com/ionic-team/ionic-framework/issues/29773)
* **input, textarea:** ensure screen readers announce helper and error text when focused ([#29958](https://github.com/ionic-team/ionic-framework/issues/29958)) ([5a73145](https://github.com/ionic-team/ionic-framework/commit/5a7314553a8def87bd19275640c92dd72a6ef1a4))
* **overlay:** hide from screen readers while animating ([#29951](https://github.com/ionic-team/ionic-framework/issues/29951)) ([cb60073](https://github.com/ionic-team/ionic-framework/commit/cb6007363a8d42b5f126945427c2bfc3d7209c21)), closes [#29857](https://github.com/ionic-team/ionic-framework/issues/29857)
* **overlays:** do not hide root when toast appears ([#29962](https://github.com/ionic-team/ionic-framework/issues/29962)) ([322d7c9](https://github.com/ionic-team/ionic-framework/commit/322d7c98cf6613df0b0db3f119e3f892e6a17e7b)), closes [#29773](https://github.com/ionic-team/ionic-framework/issues/29773)
* **overlays:** hide the focus trap div from screen readers ([#29970](https://github.com/ionic-team/ionic-framework/issues/29970)) ([c3b58f1](https://github.com/ionic-team/ionic-framework/commit/c3b58f1620bcb74db43e3983ef570b7b982abd83)), closes [#29858](https://github.com/ionic-team/ionic-framework/issues/29858)





## [8.3.3](https://github.com/ionic-team/ionic-framework/compare/v8.3.2...v8.3.3) (2024-10-16)

**Note:** Version bump only for package @ionic/core
Expand Down
4 changes: 2 additions & 2 deletions core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "8.3.3",
"version": "8.3.4",
"description": "Base components for Ionic",
"keywords": [
"ionic",
Expand Down
4 changes: 2 additions & 2 deletions core/src/components/action-sheet/action-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
>
<ion-backdrop tappable={this.backdropDismiss} />

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>

<div class="action-sheet-wrapper ion-overlay-wrapper" ref={(el) => (this.wrapperEl = el)}>
<div class="action-sheet-container">
Expand Down Expand Up @@ -446,7 +446,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
</div>
</div>

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>
</Host>
);
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
>
<ion-backdrop tappable={this.backdropDismiss} />

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>

<div class="alert-wrapper ion-overlay-wrapper" ref={(el) => (this.wrapperEl = el)}>
<div class="alert-head">
Expand Down Expand Up @@ -788,7 +788,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
{this.renderAlertButtons()}
</div>

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>
</Host>
);
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/components/alert/test/a11y/alert.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {

await expect(page).toHaveScreenshot(screenshot(`alert-radio-scale`));
});
test('should scale text on larger font sizes with text fields', async ({ page }) => {
test('should scale text on larger font sizes with text fields', async ({ page, skip }) => {
// TODO(ROU-8158): unskip this test when a solution is found
skip.browser('chromium', 'Rendering is flaky in Chrome.');

await page.setContent(
`
<style>
Expand Down
1 change: 0 additions & 1 deletion core/src/components/backdrop/backdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class Backdrop implements ComponentInterface {
const mode = getIonMode(this);
return (
<Host
tabindex="-1"
aria-hidden="true"
class={{
[mode]: true,
Expand Down
29 changes: 27 additions & 2 deletions core/src/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { getCounterText } from './input.utils';
export class Input implements ComponentInterface {
private nativeInput?: HTMLInputElement;
private inputId = `ion-input-${inputIds++}`;
private helperTextId = `${this.inputId}-helper-text`;
private errorTextId = `${this.inputId}-error-text`;
private inheritedAttributes: Attributes = {};
private isComposing = false;
private slotMutationController?: SlotMutationController;
Expand Down Expand Up @@ -573,9 +575,30 @@ export class Input implements ComponentInterface {
* Renders the helper text or error text values
*/
private renderHintText() {
const { helperText, errorText } = this;
const { helperText, errorText, helperTextId, errorTextId } = this;

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

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

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

if (helperText) {
return helperTextId;
}

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

private renderCounter() {
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() === this.errorTextId}
{...this.inheritedAttributes}
/>
{this.clearInput && !readonly && !disabled && (
Expand Down
55 changes: 55 additions & 0 deletions core/src/components/input/test/bottom-content/input.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(helperText).toHaveText('my helper');
await expect(errorText).toBeHidden();
});
test('input should have an aria-describedby attribute when helper text is present', async ({ page }) => {
await page.setContent(
`<ion-input helper-text="my helper" error-text="my error" label="my input"></ion-input>`,
config
);

const input = page.locator('ion-input input');
const helperText = page.locator('ion-input .helper-text');
const helperTextId = await helperText.getAttribute('id');
const ariaDescribedBy = await input.getAttribute('aria-describedby');

expect(ariaDescribedBy).toBe(helperTextId);
});
test('error text should be visible when input is invalid', async ({ page }) => {
await page.setContent(
`<ion-input class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my input"></ion-input>`,
Expand Down Expand Up @@ -96,6 +109,48 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
const errorText = page.locator('ion-input .error-text');
await expect(errorText).toHaveScreenshot(screenshot(`input-error-custom-color`));
});
test('input should have an aria-describedby attribute when error text is present', async ({ page }) => {
await page.setContent(
`<ion-input class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my input"></ion-input>`,
config
);

const input = page.locator('ion-input input');
const errorText = page.locator('ion-input .error-text');
const errorTextId = await errorText.getAttribute('id');
const ariaDescribedBy = await input.getAttribute('aria-describedby');

expect(ariaDescribedBy).toBe(errorTextId);
});
test('input should have aria-invalid attribute when input is invalid', async ({ page }) => {
await page.setContent(
`<ion-input class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my input"></ion-input>`,
config
);

const input = page.locator('ion-input input');

await expect(input).toHaveAttribute('aria-invalid');
});
test('input should not have aria-invalid attribute when input is valid', async ({ page }) => {
await page.setContent(
`<ion-input helper-text="my helper" error-text="my error" label="my input"></ion-input>`,
config
);

const input = page.locator('ion-input input');

await expect(input).not.toHaveAttribute('aria-invalid');
});
test('input should not have aria-describedby attribute when no hint or error text is present', async ({
page,
}) => {
await page.setContent(`<ion-input label="my input"></ion-input>`, config);

const input = page.locator('ion-input input');

await expect(input).not.toHaveAttribute('aria-describedby');
});
});
test.describe('input: hint text rendering', () => {
test.describe('regular inputs', () => {
Expand Down
4 changes: 2 additions & 2 deletions core/src/components/loading/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
>
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss} />

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>

<div class="loading-wrapper ion-overlay-wrapper">
{spinner && (
Expand All @@ -368,7 +368,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
{message !== undefined && this.renderLoadingMessage(msgId)}
</div>

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>
</Host>
);
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/components/picker-legacy/picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
>
<ion-backdrop visible={this.showBackdrop} tappable={this.backdropDismiss}></ion-backdrop>

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>

<div class="picker-wrapper ion-overlay-wrapper" role="dialog">
<div class="picker-toolbar">
Expand All @@ -395,7 +395,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
</div>
</div>

<div tabindex="0"></div>
<div tabindex="0" aria-hidden="true"></div>
</Host>
);
}
Expand Down
55 changes: 55 additions & 0 deletions core/src/components/textarea/test/bottom-content/textarea.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(helperText).toHaveText('my helper');
await expect(errorText).toBeHidden();
});
test('textarea should have an aria-describedby attribute when helper text is present', async ({ page }) => {
await page.setContent(
`<ion-textarea helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea textarea');
const helperText = page.locator('ion-textarea .helper-text');
const helperTextId = await helperText.getAttribute('id');
const ariaDescribedBy = await textarea.getAttribute('aria-describedby');

expect(ariaDescribedBy).toBe(helperTextId);
});
test('error text should be visible when textarea is invalid', async ({ page }) => {
await page.setContent(
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
Expand Down Expand Up @@ -55,6 +68,48 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
const errorText = page.locator('ion-textarea .error-text');
await expect(errorText).toHaveScreenshot(screenshot(`textarea-error-custom-color`));
});
test('textarea should have an aria-describedby attribute when error text is present', async ({ page }) => {
await page.setContent(
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea textarea');
const errorText = page.locator('ion-textarea .error-text');
const errorTextId = await errorText.getAttribute('id');
const ariaDescribedBy = await textarea.getAttribute('aria-describedby');

expect(ariaDescribedBy).toBe(errorTextId);
});
test('textarea should have aria-invalid attribute when input is invalid', async ({ page }) => {
await page.setContent(
`<ion-textarea class="ion-invalid ion-touched" helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea textarea');

await expect(textarea).toHaveAttribute('aria-invalid');
});
test('textarea should not have aria-invalid attribute when input is valid', async ({ page }) => {
await page.setContent(
`<ion-textarea helper-text="my helper" error-text="my error" label="my textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea textarea');

await expect(textarea).not.toHaveAttribute('aria-invalid');
});
test('textarea should not have aria-describedby attribute when no hint or error text is present', async ({
page,
}) => {
await page.setContent(`<ion-textarea label="my textarea"></ion-textarea>`, config);

const textarea = page.locator('ion-textarea textarea');

await expect(textarea).not.toHaveAttribute('aria-describedby');
});
});
test.describe('textarea: hint text rendering', () => {
test.describe('regular textareas', () => {
Expand Down
Loading
Loading