From 9a685882b7085d911ff09eedacc367629e32348a Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 31 Jul 2023 11:07:00 -0400 Subject: [PATCH] feat(toast): add htmlAttributes property for passing attributes to buttons (#27855) Issue number: N/A --------- ## What is the current behavior? Buttons containing only icons are not accessible as there is no way to pass an `aria-label` attribute (or any other html attribute). ## What is the new behavior? - Adds the `htmlAttributes` property on the `ToastButton` interface - Passes the `htmlAttributes` to the buttons - Adds a test to verify `aria-label` and `aria-labelled-by` are passed to the button ## Does this introduce a breaking change? - [ ] Yes - [x] No --- .../src/components/toast/test/a11y/index.html | 19 ++++++++++++++++ .../components/toast/test/a11y/toast.e2e.ts | 22 +++++++++++++++---- core/src/components/toast/toast-interface.ts | 1 + core/src/components/toast/toast.tsx | 9 +++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/core/src/components/toast/test/a11y/index.html b/core/src/components/toast/test/a11y/index.html index b2eb88a7ae..3b9c9adb2d 100644 --- a/core/src/components/toast/test/a11y/index.html +++ b/core/src/components/toast/test/a11y/index.html @@ -34,6 +34,14 @@ Present Controller Toast + Present Aria Label Toast + + Update Inner Content @@ -41,6 +49,17 @@ const inlineToast = document.querySelector('#inline-toast'); inlineToast.buttons = ['Ok']; + const ariaLabelToast = document.querySelector('#aria-label-toast'); + ariaLabelToast.buttons = [ + { + icon: 'close', + htmlAttributes: { + 'aria-label': 'close button', + 'aria-labelledby': 'close-label', + }, + }, + ]; + const presentToast = async (opts) => { const toast = await toastController.create(opts); diff --git a/core/src/components/toast/test/a11y/toast.e2e.ts b/core/src/components/toast/test/a11y/toast.e2e.ts index 65b24a85b3..f0e1bac41f 100644 --- a/core/src/components/toast/test/a11y/toast.e2e.ts +++ b/core/src/components/toast/test/a11y/toast.e2e.ts @@ -11,10 +11,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { await page.goto(`/src/components/toast/test/a11y`, config); }); test('should not have any axe violations with inline toasts', async ({ page }) => { - const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); + const didPresent = await page.spyOnEvent('ionToastDidPresent'); await page.click('#inline-toast-trigger'); - await ionToastDidPresent.next(); + await didPresent.next(); /** * IonToast overlays the entire screen, so @@ -25,10 +25,10 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { expect(results.violations).toEqual([]); }); test('should not have any axe violations with controller toasts', async ({ page }) => { - const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); + const didPresent = await page.spyOnEvent('ionToastDidPresent'); await page.click('#controller-toast-trigger'); - await ionToastDidPresent.next(); + await didPresent.next(); /** * IonToast overlays the entire screen, so @@ -38,5 +38,19 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { const results = await new AxeBuilder({ page }).disableRules('color-contrast').analyze(); expect(results.violations).toEqual([]); }); + + test('should have aria-labelledby and aria-label added to the button when htmlAttributes is set', async ({ + page, + }) => { + const didPresent = await page.spyOnEvent('ionToastDidPresent'); + + await page.click('#aria-label-toast-trigger'); + await didPresent.next(); + + const toastButton = page.locator('#aria-label-toast .toast-button'); + + await expect(toastButton).toHaveAttribute('aria-labelledby', 'close-label'); + await expect(toastButton).toHaveAttribute('aria-label', 'close button'); + }); }); }); diff --git a/core/src/components/toast/toast-interface.ts b/core/src/components/toast/toast-interface.ts index 02108e0647..c2b3aca5d7 100644 --- a/core/src/components/toast/toast-interface.ts +++ b/core/src/components/toast/toast-interface.ts @@ -31,6 +31,7 @@ export interface ToastButton { side?: 'start' | 'end'; role?: 'cancel' | string; cssClass?: string | string[]; + htmlAttributes?: { [key: string]: any }; handler?: () => boolean | void | Promise; } diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index 4f397d94a7..0b783bc83e 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -405,7 +405,14 @@ export class Toast implements ComponentInterface, OverlayInterface { return (
{buttons.map((b) => ( -