mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
fix(modal): role attribute can be customized (#25804)
This commit is contained in:
@ -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"
|
||||||
|
|||||||
21
core/src/components/modal/test/a11y/index.html
Normal file
21
core/src/components/modal/test/a11y/index.html
Normal 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>
|
||||||
39
core/src/components/modal/test/a11y/modal.e2e.ts
Normal file
39
core/src/components/modal/test/a11y/modal.e2e.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user