mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
fix(segment, segment-button): use correct tablist and tab roles for screen readers (#23145)
* fix(segment, segment-button): change aria attributes for segment and segment-button * add axe test * Add tests, screen reader doc * add updated screen reader * fix(segment-button): move aria tags to host * verify nvda and talkback behavior * fix(segment-button): remove outline on focus * Update core/src/components/segment/test/basic/index.html Co-authored-by: Liam DeBeasi <liamdebeasi@icloud.com>
This commit is contained in:
40
core/package-lock.json
generated
40
core/package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/puppeteer": "^4.1.1",
|
||||
"@jest/core": "^26.6.3",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
@ -43,6 +44,21 @@
|
||||
"typescript": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@axe-core/puppeteer": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.1.1.tgz",
|
||||
"integrity": "sha512-Ao9N7HL//s26hdasx3Ba18tlJgxpoO+1SmIN6eSx5vC50dqYhiRU0xp6wBKWqzo10u1jpzl/s4RFsOAuolFMBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axe-core": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"puppeteer": ">=1.10.0 < 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
@ -2029,6 +2045,15 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axe-core": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.4.tgz",
|
||||
"integrity": "sha512-Pdgfv6iP0gNx9ejRGa3zE7Xgkj/iclXqLfe7BnatdZz0QnLZ3jrRHUVH8wNSdN68w05Sk3ShGTb3ydktMTooig==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-istanbul": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz",
|
||||
@ -13802,6 +13827,15 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@axe-core/puppeteer": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/puppeteer/-/puppeteer-4.1.1.tgz",
|
||||
"integrity": "sha512-Ao9N7HL//s26hdasx3Ba18tlJgxpoO+1SmIN6eSx5vC50dqYhiRU0xp6wBKWqzo10u1jpzl/s4RFsOAuolFMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axe-core": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
@ -15481,6 +15515,12 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
|
||||
"dev": true
|
||||
},
|
||||
"axe-core": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.4.tgz",
|
||||
"integrity": "sha512-Pdgfv6iP0gNx9ejRGa3zE7Xgkj/iclXqLfe7BnatdZz0QnLZ3jrRHUVH8wNSdN68w05Sk3ShGTb3ydktMTooig==",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-istanbul": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz",
|
||||
|
@ -36,6 +36,7 @@
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/puppeteer": "^4.1.1",
|
||||
"@jest/core": "^26.6.3",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
|
@ -166,6 +166,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// Segment Button: Hover
|
||||
// --------------------------------------------------
|
||||
|
||||
|
@ -86,13 +86,28 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private get tabIndex() {
|
||||
if (this.disabled) { return -1; }
|
||||
|
||||
const hasTabIndex = this.el.hasAttribute('tabindex');
|
||||
|
||||
if (hasTabIndex) {
|
||||
return this.el.getAttribute('tabindex');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { checked, type, disabled, hasIcon, hasLabel, layout, segmentEl } = this;
|
||||
const { checked, type, disabled, hasIcon, hasLabel, layout, segmentEl, tabIndex } = this;
|
||||
const mode = getIonMode(this);
|
||||
const hasSegmentColor = () => segmentEl !== null && segmentEl.color !== undefined;
|
||||
return (
|
||||
<Host
|
||||
role="tab"
|
||||
aria-selected={checked ? 'true' : 'false'}
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
tabIndex={tabIndex}
|
||||
class={{
|
||||
[mode]: true,
|
||||
'in-toolbar': hostContext('ion-toolbar', this.el),
|
||||
@ -113,7 +128,7 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
||||
>
|
||||
<button
|
||||
type={type}
|
||||
aria-pressed={checked ? 'true' : 'false'}
|
||||
tabIndex={-1}
|
||||
class="button-native"
|
||||
part="native"
|
||||
disabled={disabled}
|
||||
|
@ -425,6 +425,7 @@ export class Segment implements ComponentInterface {
|
||||
const mode = getIonMode(this);
|
||||
return (
|
||||
<Host
|
||||
role="tablist"
|
||||
onClick={this.onClick}
|
||||
class={createColorClasses(this.color, {
|
||||
[mode]: true,
|
||||
|
11
core/src/components/segment/test/a11y/e2e.ts
Normal file
11
core/src/components/segment/test/a11y/e2e.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { AxePuppeteer } from '@axe-core/puppeteer';
|
||||
|
||||
test('segment: axe', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/segment/test/a11y?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const results = await new AxePuppeteer(page).analyze();
|
||||
expect(results.violations.length).toEqual(0);
|
||||
});
|
31
core/src/components/segment/test/a11y/index.html
Normal file
31
core/src/components/segment/test/a11y/index.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Segment - a11y</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link href="../../../../../css/core.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Segment</h1>
|
||||
<ion-segment aria-label="Tab Options" color="dark" value="reading-list">
|
||||
<ion-segment-button value="bookmarks">
|
||||
<ion-label>Bookmarks</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="reading-list">
|
||||
<ion-label>Reading List</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="shared-links">
|
||||
<ion-label>Shared Links</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
24
core/src/components/segment/test/a11y/screen-readers.md
Normal file
24
core/src/components/segment/test/a11y/screen-readers.md
Normal file
@ -0,0 +1,24 @@
|
||||
"native" refers to this sample: https://w3c.github.io/aria-practices/examples/tabs/tabs-2/tabs.html
|
||||
|
||||
### Tabbing to Segment Button
|
||||
|
||||
| | native | Ionic |
|
||||
| ------------------------ | ------------------------------------------------ | ------------------------------------------------ |
|
||||
| VoiceOver macOS - Chrome | BOOKMARKS, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver macOS - Safari | BOOKMARKS, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver iOS | Bookmarks, tab | Bookmarks, tab |
|
||||
| Android TalkBack | Bookmarks, tab | Bookmarks, tab |
|
||||
| Windows NVDA | Tab Options, tab control, BOOKMARKS, tab, 1 of 3 | Tab Options, tab control, BOOKMARKS, tab, 1 of 3 |
|
||||
|
||||
### Selecting Segment Button
|
||||
|
||||
| | native | Ionic |
|
||||
| ------------------------ | -------------------------------------------------------- | ------------------------ |
|
||||
| VoiceOver macOS - Chrome | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver macOS - Safari | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group | BOOKMARKS, selected, tab, 1 of 3, Tab Options, tab group |
|
||||
| VoiceOver iOS | selected, Bookmarks, tab | selected, Bookmarks, tab |
|
||||
| Android TalkBack | selected | selected |
|
||||
| Windows NVDA | BOOKMARKS, tab, 1 of 3, selected | BOOKMARKS, tab, 1 of 3, selected |
|
||||
|
||||
Note: The `aria-label` for tablist is typically only read on the first interaction.
|
||||
|
Reference in New Issue
Block a user