mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
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
This commit is contained in:
@ -34,6 +34,14 @@
|
||||
Present Controller Toast
|
||||
</ion-button>
|
||||
|
||||
<ion-button id="aria-label-toast-trigger">Present Aria Label Toast</ion-button>
|
||||
<ion-toast
|
||||
id="aria-label-toast"
|
||||
trigger="aria-label-toast-trigger"
|
||||
header="Aria Label Toast Header"
|
||||
message="Aria Label Toast Message"
|
||||
></ion-toast>
|
||||
|
||||
<ion-button onclick="updateContent()">Update Inner Content</ion-button>
|
||||
</main>
|
||||
</ion-app>
|
||||
@ -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);
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ export interface ToastButton {
|
||||
side?: 'start' | 'end';
|
||||
role?: 'cancel' | string;
|
||||
cssClass?: string | string[];
|
||||
htmlAttributes?: { [key: string]: any };
|
||||
handler?: () => boolean | void | Promise<boolean | void>;
|
||||
}
|
||||
|
||||
|
@ -405,7 +405,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
||||
return (
|
||||
<div class={buttonGroupsClasses}>
|
||||
{buttons.map((b) => (
|
||||
<button type="button" class={buttonClass(b)} tabIndex={0} onClick={() => this.buttonClick(b)} part="button">
|
||||
<button
|
||||
{...b.htmlAttributes}
|
||||
type="button"
|
||||
class={buttonClass(b)}
|
||||
tabIndex={0}
|
||||
onClick={() => this.buttonClick(b)}
|
||||
part="button"
|
||||
>
|
||||
<div class="toast-button-inner">
|
||||
{b.icon && (
|
||||
<ion-icon
|
||||
|
Reference in New Issue
Block a user