Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 10 additions & 4 deletions core/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import type {
} from '../../interface';
import { findIonContent, printIonContentErrorMsg } from '../../utils/content';
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 { printIonWarning } from '../../utils/logging';
import { BACKDROP, activeAnimations, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays';
Expand Down Expand Up @@ -66,6 +67,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
private sortedBreakpoints?: number[];
private keyboardOpenCallback?: () => void;
private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => Promise<void>;
private inheritedAttributes: Attributes = {};

private inline = false;
private workingDelegate?: FrameworkDelegate;
Expand Down Expand Up @@ -334,7 +336,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
}

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
Expand Down Expand Up @@ -855,7 +859,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
};

render() {
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior } = this;
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes } = this;

const showHandle = handle !== false && isSheetModal;
const mode = getIonMode(this);
Expand All @@ -867,8 +871,10 @@ export class Modal implements ComponentInterface, OverlayInterface {
<Host
no-router
aria-modal="true"
role="dialog"
tabindex="-1"
{...(htmlAttributes as any)}
{...inheritedAttributes}
style={{
zIndex: `${20000 + this.overlayIndex}`,
}}
Expand Down Expand Up @@ -896,7 +902,7 @@ export class Modal implements ComponentInterface, OverlayInterface {

{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 && (
<button
class="modal-handle"
Expand Down
21 changes: 21 additions & 0 deletions core/src/components/modal/test/a11y/index.html
Original file line number Diff line number Diff line change
@@ -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>
39 changes: 39 additions & 0 deletions core/src/components/modal/test/a11y/modal.e2e.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});