fix(modal): role attribute can be customized (#25804)

This commit is contained in:
Liam DeBeasi
2022-08-23 13:37:58 -05:00
committed by GitHub
parent a39d776f08
commit 037d579b2a
3 changed files with 70 additions and 4 deletions

View File

@ -18,7 +18,8 @@ import type {
} from '../../interface'; } from '../../interface';
import { findIonContent, printIonContentErrorMsg } from '../../utils/content'; import { findIonContent, printIonContentErrorMsg } from '../../utils/content';
import { CoreDelegate, attachComponent, detachComponent } from '../../utils/framework-delegate'; import { CoreDelegate, attachComponent, detachComponent } from '../../utils/framework-delegate';
import { raf } from '../../utils/helpers'; import { raf, inheritAttributes } from '../../utils/helpers';
import type { Attributes } from '../../utils/helpers';
import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard'; import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard';
import { printIonWarning } from '../../utils/logging'; import { printIonWarning } from '../../utils/logging';
import { BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays'; import { BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
@ -66,6 +67,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
private sortedBreakpoints?: number[]; private sortedBreakpoints?: number[];
private keyboardOpenCallback?: () => void; private keyboardOpenCallback?: () => void;
private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => Promise<void>; private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => Promise<void>;
private inheritedAttributes: Attributes = {};
private inline = false; private inline = false;
private workingDelegate?: FrameworkDelegate; private workingDelegate?: FrameworkDelegate;
@ -334,7 +336,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
} }
componentWillLoad() { componentWillLoad() {
const { breakpoints, initialBreakpoint, swipeToClose } = this; const { breakpoints, initialBreakpoint, swipeToClose, el } = this;
this.inheritedAttributes = inheritAttributes(el, ['role']);
/** /**
* If user has custom ID set then we should * If user has custom ID set then we should
@ -855,7 +859,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
}; };
render() { render() {
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior } = this; const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes } = this;
const showHandle = handle !== false && isSheetModal; const showHandle = handle !== false && isSheetModal;
const mode = getIonMode(this); const mode = getIonMode(this);
@ -867,8 +871,10 @@ export class Modal implements ComponentInterface, OverlayInterface {
<Host <Host
no-router no-router
aria-modal="true" aria-modal="true"
role="dialog"
tabindex="-1" tabindex="-1"
{...(htmlAttributes as any)} {...(htmlAttributes as any)}
{...inheritedAttributes}
style={{ style={{
zIndex: `${20000 + this.overlayIndex}`, zIndex: `${20000 + this.overlayIndex}`,
}} }}
@ -896,7 +902,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
{mode === 'ios' && <div class="modal-shadow"></div>} {mode === 'ios' && <div class="modal-shadow"></div>}
<div role="dialog" class="modal-wrapper ion-overlay-wrapper" part="content" ref={(el) => (this.wrapperEl = el)}> <div class="modal-wrapper ion-overlay-wrapper" part="content" ref={(el) => (this.wrapperEl = el)}>
{showHandle && ( {showHandle && (
<button <button
class="modal-handle" class="modal-handle"

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Modal - a11y</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<main>
<h1>Modal - a11y</h1>
<button id="open-modal">Open Modal</button>
<ion-modal aria-label="My custom label" trigger="open-modal">My content</ion-modal>
</main>
</body>
</html>

View File

@ -0,0 +1,39 @@
import AxeBuilder from '@axe-core/playwright';
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('modal: a11y', () => {
test.beforeEach(async ({ skip }) => {
skip.rtl();
skip.mode('md');
});
test('should not have accessibility violations', async ({ page }) => {
await page.goto(`/src/components/modal/test/a11y`);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const button = page.locator('#open-modal');
const modal = page.locator('ion-modal');
await expect(modal).toHaveAttribute('role', 'dialog');
await button.click();
await ionModalDidPresent.next();
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test('should allow for custom role', async ({ page }) => {
/**
* Note: This example should not be used in production.
* This only serves to check that `role` can be customized.
*/
await page.setContent(`
<ion-modal role="alertdialog"></ion-modal>
`);
const modal = page.locator('ion-modal');
await expect(modal).toHaveAttribute('role', 'alertdialog');
});
});