pour-engine 1.44.2 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config/project.config.js
CHANGED
package/engine/README.md
CHANGED
|
@@ -111,6 +111,21 @@ pour engine therefore reports on **every** in-scope criterion, one of two ways:
|
|
|
111
111
|
in-scope criteria (filtered by the requested WCAG version and level)
|
|
112
112
|
that automation can't judge, for a host to render however it wants.
|
|
113
113
|
|
|
114
|
+
### WCAG 3 Working Draft
|
|
115
|
+
|
|
116
|
+
A tier tag, `wcag3-bronze`, `wcag3-silver` or `wcag3-gold`, reports against
|
|
117
|
+
the WCAG 3.0 Working Draft of 10 September 2026 instead (`wcag3.js`). The
|
|
118
|
+
rules are still WCAG 2 rules: each is matched, by reading both documents,
|
|
119
|
+
to the draft requirement its failure also fails, and a rule with no such
|
|
120
|
+
requirement does not run (`UNMAPPED_RULES` gives each reason). Where the
|
|
121
|
+
draft leaves a value to be determined, text contrast among them, the
|
|
122
|
+
rule's WCAG 2 value stands in. `results.standard` is stamped draft,
|
|
123
|
+
`manualReview` becomes the tier's provisions, and each rule result carries
|
|
124
|
+
`wcag3`. The draft's tiers build on conformance and leave their counts to
|
|
125
|
+
be decided, so pour reads them as: Bronze, the core requirements and the
|
|
126
|
+
content assertions; Silver, plus every supplemental requirement; Gold,
|
|
127
|
+
plus the assertions about the organization. WCAG 2 stays the default.
|
|
128
|
+
|
|
114
129
|
The checklist accounts for the criteria; it does not mean every page state
|
|
115
130
|
or possible failure has been tested. A snapshot audit still needs human
|
|
116
131
|
assessment, including interaction states and any checks reported as skipped.
|
package/engine/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import config from '../config/project.config.js';
|
|
5
5
|
import rules from './rules/index.js';
|
|
6
6
|
import wcagCatalog from './wcag22.js';
|
|
7
|
+
import { ruleMatchesTags, tierFromTags, manualReviewChecklist, requirementsForRule, standardFor } from './wcag3.js';
|
|
7
8
|
import { isVisible, isRendered, cssPath, htmlSnippet, ownText, collectRoots, resetDOMCaches, releaseDOMCaches } from './lib/dom.js';
|
|
8
9
|
import { accessibleName } from './lib/accessible-name.js';
|
|
9
10
|
import { resetAuditCaches } from './lib/contrast.js';
|
|
@@ -13,12 +14,6 @@ export const version = config.engine.version;
|
|
|
13
14
|
|
|
14
15
|
const ruleHelpers = { isVisible, isRendered, cssPath, htmlSnippet, ownText, accessibleName };
|
|
15
16
|
|
|
16
|
-
/** Does this rule match the requested tag selection? Empty/absent = all rules. */
|
|
17
|
-
function ruleMatchesTags(rule, tags) {
|
|
18
|
-
if (!tags?.length) return true;
|
|
19
|
-
return rule.tags.some((tag) => tags.includes(tag));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/** Which WCAG version/level was requested, judged from the selected tags. */
|
|
23
18
|
function scopeFromTags(tags) {
|
|
24
19
|
if (!tags?.length) return { version: '2.2', level: 'AAA' }; // no filter → everything
|
|
@@ -87,7 +82,11 @@ function toResultNode(element, outcome) {
|
|
|
87
82
|
* @param {Document|Element} context - what to scan
|
|
88
83
|
* @param {{ tags?: string[], exclude?: string, signal?: AbortSignal }} options -
|
|
89
84
|
* rule selection by tag (wcag2a…wcag22aa, best-practice), empty selects
|
|
90
|
-
* every rule;
|
|
85
|
+
* every rule; a WCAG 3 tier tag (wcag3-bronze, -silver, -gold) selects
|
|
86
|
+
* the rules mapped to that tier's draft requirements and reframes the
|
|
87
|
+
* report against the WCAG 3.0 Working Draft: results.standard is
|
|
88
|
+
* stamped draft, the manual checklist becomes the tier's provisions,
|
|
89
|
+
* and each rule result carries the requirements it maps to (wcag3); exclude is a CSS selector — elements matching it, or inside
|
|
91
90
|
* a match (including across shadow boundaries), are left out of every
|
|
92
91
|
* rule; signal aborts the run between rules (throws AbortError)
|
|
93
92
|
* @param {(progress: object) => void} [onProgress] - called before each rule
|
|
@@ -98,15 +97,17 @@ function toResultNode(element, outcome) {
|
|
|
98
97
|
*/
|
|
99
98
|
export async function run(context = document, options = {}, onProgress) {
|
|
100
99
|
const auditStarted = performance.now();
|
|
100
|
+
const tier = tierFromTags(options.tags);
|
|
101
101
|
const results = {
|
|
102
102
|
testEngine: { name, version },
|
|
103
103
|
timestamp: new Date().toISOString(),
|
|
104
104
|
url: context.location?.href ?? context.ownerDocument?.location?.href ?? '',
|
|
105
|
+
standard: tier ? standardFor(tier) : null,
|
|
105
106
|
violations: [],
|
|
106
107
|
passes: [],
|
|
107
108
|
incomplete: [],
|
|
108
109
|
inapplicable: [],
|
|
109
|
-
manualReview: manualReviewCriteria(options.tags),
|
|
110
|
+
manualReview: tier ? manualReviewChecklist(tier) : manualReviewCriteria(options.tags),
|
|
110
111
|
ruleTimings: [],
|
|
111
112
|
};
|
|
112
113
|
|
|
@@ -251,6 +252,9 @@ export async function run(context = document, options = {}, onProgress) {
|
|
|
251
252
|
description: rule.help,
|
|
252
253
|
helpUrl: rule.helpUrl,
|
|
253
254
|
};
|
|
255
|
+
// In a WCAG 3 audit, the draft requirements this rule speaks to (empty
|
|
256
|
+
// for a best-practice rule, which maps to none).
|
|
257
|
+
if (tier) ruleResult.wcag3 = requirementsForRule(rule.id);
|
|
254
258
|
if (!elements.length) results.inapplicable.push({ ...ruleResult, nodes: [] });
|
|
255
259
|
if (buckets.fail.length) results.violations.push({ ...ruleResult, nodes: buckets.fail });
|
|
256
260
|
if (buckets.incomplete.length) results.incomplete.push({ ...ruleResult, nodes: buckets.incomplete });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// WCAG SC 1.4.1 Use of Color (Level A)
|
|
2
|
-
import { parseColor, contrastRatio, composite, effectiveBackground, backgroundImageSource } from '../../lib/contrast.js';
|
|
2
|
+
import { parseColor, contrastRatio, composite, effectiveBackground, backgroundImageSource, asRgb } from '../../lib/contrast.js';
|
|
3
3
|
|
|
4
4
|
// Split a computed list value ("a, b(c, d), e") on the commas OUTSIDE
|
|
5
5
|
// parentheses: gradients and colour functions carry commas of their own.
|
|
@@ -274,6 +274,17 @@ export default {
|
|
|
274
274
|
// WCAG technique G183: 3:1 against the surrounding text is a sufficient
|
|
275
275
|
// colour difference for identifying a link.
|
|
276
276
|
if (ratio >= 3) return { status: 'pass' };
|
|
277
|
+
// The pair the ratio was taken from, for the highlight card and the
|
|
278
|
+
// checker link: the link's colour and the prose's, as judged (composited
|
|
279
|
+
// where translucent), plus the background where it is a known solid.
|
|
280
|
+
const backdrop = effectiveBackground(parent);
|
|
281
|
+
const data = {
|
|
282
|
+
link: asRgb(linkColor),
|
|
283
|
+
surrounding: asRgb(textColor),
|
|
284
|
+
...(backdrop && (backdrop.a ?? 1) === 1 ? { background: asRgb(backdrop) } : {}),
|
|
285
|
+
ratio: Number(ratio.toFixed(2)),
|
|
286
|
+
required: 3,
|
|
287
|
+
};
|
|
277
288
|
|
|
278
289
|
// 2026-08-25 overnight audit: text-decoration propagates from an
|
|
279
290
|
// ancestor to its inline descendants and a descendant's "none" cannot
|
|
@@ -371,6 +382,7 @@ export default {
|
|
|
371
382
|
? 'This link has no underline and its colour differs from the surrounding text in hue alone, with no luminance difference: the distinction disappears entirely without colour vision, the exact failure F73 describes.'
|
|
372
383
|
: `This link has no underline and only ${shown}:1 colour difference from the surrounding text, below the 3:1 WCAG technique G183 asks for when colour is the only thing marking a link.`,
|
|
373
384
|
fix: 'Underline links inside text (text-decoration: underline), or add a non-colour indicator.',
|
|
385
|
+
data,
|
|
374
386
|
};
|
|
375
387
|
},
|
|
376
388
|
};
|
|
@@ -577,7 +577,7 @@ async function sampledVerdict(source, foreground, required, doc, overlays = [],
|
|
|
577
577
|
* 1.4.3 Contrast (Minimum), AA — 4.5:1, or 3:1 for large text
|
|
578
578
|
* 1.4.6 Contrast (Enhanced), AAA — 7:1, or 4.5:1 for large text
|
|
579
579
|
*/
|
|
580
|
-
export function createContrastRule({ id, tags, help, helpUrl, thresholds }) {
|
|
580
|
+
export function createContrastRule({ id, name, tags, help, helpUrl, thresholds }) {
|
|
581
581
|
// When the colour pair already clears the LARGE-scale minimum, the miss
|
|
582
582
|
// is about scale, not colour: name the boundary (14pt bold / 18pt, bold
|
|
583
583
|
// meaning BOLD_WEIGHT and up), or
|
|
@@ -608,6 +608,9 @@ export function createContrastRule({ id, tags, help, helpUrl, thresholds }) {
|
|
|
608
608
|
|
|
609
609
|
const rule = {
|
|
610
610
|
id,
|
|
611
|
+
// The display name both callers pass: dropped here until 2026-09-21, so
|
|
612
|
+
// the UI titled these two rules by their ids.
|
|
613
|
+
name,
|
|
611
614
|
impact: 'serious',
|
|
612
615
|
tags,
|
|
613
616
|
help,
|
package/engine/wcag3.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
// The WCAG 3.0 Working Draft of 10 September 2026, as a catalog the engine
|
|
2
|
+
// can report against. https://www.w3.org/TR/2026/WD-wcag-3.0-20260910/
|
|
3
|
+
//
|
|
4
|
+
// WCAG 3.0 is unfinished. The W3C marks every section of it Developing or
|
|
5
|
+
// Exploratory and says the requirements will be added, combined, removed
|
|
6
|
+
// and reworded. Everything here is therefore stamped draft, and every
|
|
7
|
+
// surface that shows it must say so. WCAG 2 stays the default everywhere.
|
|
8
|
+
//
|
|
9
|
+
// The draft replaces success criteria and A/AA/AAA with three kinds of
|
|
10
|
+
// provision under each guideline: core requirements (all must be met to
|
|
11
|
+
// conform), supplemental requirements (beyond conformance) and assertions
|
|
12
|
+
// (documented statements about an organization's practice, which no tool
|
|
13
|
+
// can check). Its Reporting section (4.1.4, Exploratory) lays out tiers
|
|
14
|
+
// that build on conformance: Bronze and Silver add a to-be-decided number
|
|
15
|
+
// of supplemental requirements and content assertions, Gold adds the
|
|
16
|
+
// assertions about the organization. Until the draft fixes those numbers,
|
|
17
|
+
// pour reads the tiers this way, and says so:
|
|
18
|
+
// Bronze: the core requirements, plus the content assertions to make
|
|
19
|
+
// Silver: Bronze plus every supplemental requirement
|
|
20
|
+
// Gold: Silver plus the assertions about the organization
|
|
21
|
+
//
|
|
22
|
+
// The draft tags each assertion Content or Organization in principle but
|
|
23
|
+
// not yet one by one; an assertion about a style guide, policy or process
|
|
24
|
+
// the organization keeps is read as Organization, any other as Content.
|
|
25
|
+
//
|
|
26
|
+
// No rule here was written for WCAG 3. The rules test WCAG 2 criteria;
|
|
27
|
+
// RULE_REQUIREMENTS names, rule by rule, the draft requirement that a
|
|
28
|
+
// failure of that rule also fails, judged by reading both documents. A
|
|
29
|
+
// rule with no such requirement (UNMAPPED_RULES, with the reason) does
|
|
30
|
+
// not run in a WCAG 3 audit. Where the draft leaves a value to be
|
|
31
|
+
// determined (text contrast, text spacing), the rule's WCAG 2 value
|
|
32
|
+
// stands in, and the report says the values are WCAG 2's.
|
|
33
|
+
|
|
34
|
+
export const draftInfo = {
|
|
35
|
+
id: 'wcag3-draft',
|
|
36
|
+
name: 'WCAG 3.0',
|
|
37
|
+
status: 'Working Draft',
|
|
38
|
+
dated: '2026-09-10',
|
|
39
|
+
draft: true,
|
|
40
|
+
url: 'https://www.w3.org/TR/2026/WD-wcag-3.0-20260910/',
|
|
41
|
+
note: 'An unfinished standard. Findings come from WCAG 2 rules matched to draft requirements, with WCAG 2 values where the draft has none yet. Not for conformance claims.',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const TIERS = ['bronze', 'silver', 'gold'];
|
|
45
|
+
export const TIER_LABELS = { bronze: 'Bronze', silver: 'Silver', gold: 'Gold' };
|
|
46
|
+
|
|
47
|
+
// Which provision types each tier takes in. Cumulative, as in the draft.
|
|
48
|
+
const TIER_TYPES = {
|
|
49
|
+
bronze: new Set(['core', 'content']),
|
|
50
|
+
silver: new Set(['core', 'content', 'supplemental']),
|
|
51
|
+
gold: new Set(['core', 'content', 'supplemental', 'organization']),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const TYPE_LABELS = {
|
|
55
|
+
core: 'Core',
|
|
56
|
+
supplemental: 'Supplemental',
|
|
57
|
+
content: 'Assertion',
|
|
58
|
+
organization: 'Assertion',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Every requirement and assertion of the draft, in document order, under
|
|
62
|
+
// its guideline. The one recommended practice (2.7.2.7) is left out: the
|
|
63
|
+
// draft says recommended practices are not needed to conform or to reach
|
|
64
|
+
// any tier. Guidelines with nothing past Exploratory (2.1.2 Figure
|
|
65
|
+
// captions, 2.12.2 Control text) have no provisions to list.
|
|
66
|
+
const RAW = [
|
|
67
|
+
g('2.1.1', 'Image alternatives'),
|
|
68
|
+
p('2.1.1.1', 'Images detectable', 'core', 'x2-1-1-1-images-detectable'),
|
|
69
|
+
p('2.1.1.2', 'Decorative images hidden', 'core', 'x2-1-1-2-decorative-images-hidden'),
|
|
70
|
+
p('2.1.1.3', 'Image alternatives available', 'core', 'x2-1-1-3-image-alternatives-available'),
|
|
71
|
+
p('2.1.1.4', 'Image alternatives equivalent', 'core', 'x2-1-1-4-image-alternatives-equivalent'),
|
|
72
|
+
g('2.1.3', 'Non-text alternatives'),
|
|
73
|
+
p('2.1.3.1', 'Non-text content not relied on', 'core', 'x2-1-3-1-non-text-content-not-relied-on'),
|
|
74
|
+
g('2.1.4', 'Transcripts'),
|
|
75
|
+
p('2.1.4.1', 'Transcripts findable', 'core', 'x2-1-4-1-transcripts-findable'),
|
|
76
|
+
p('2.1.4.2', 'Dialogue transcripts available (prerecorded)', 'core', 'x2-1-4-2-dialogue-transcripts-available-prerecorded'),
|
|
77
|
+
p('2.1.4.3', 'Dialogue transcripts available (live)', 'core', 'x2-1-4-3-dialogue-transcripts-available-live'),
|
|
78
|
+
p('2.1.4.4', 'Transcripts equivalent (prerecorded)', 'core', 'x2-1-4-4-transcripts-equivalent-prerecorded'),
|
|
79
|
+
p('2.1.4.5', 'Descriptive transcripts available', 'core', 'x2-1-4-5-descriptive-transcripts-available'),
|
|
80
|
+
p('2.1.4.6', 'Speakers identified in transcripts', 'supplemental', 'x2-1-4-6-speakers-identified-in-transcripts'),
|
|
81
|
+
p('2.1.4.7', 'Speaker language identified in transcripts', 'supplemental', 'x2-1-4-7-speaker-language-identified-in-transcripts'),
|
|
82
|
+
p('2.1.4.8', 'Sounds identified in transcripts', 'core', 'x2-1-4-8-sounds-identified-in-transcripts'),
|
|
83
|
+
p('2.1.4.9', 'Visual information identified in transcripts', 'core', 'x2-1-4-9-visual-information-identified-in-transcripts'),
|
|
84
|
+
p('2.1.4.10', 'Transcripts style guide', 'organization', 'x2-1-4-10-transcripts-style-guide'),
|
|
85
|
+
p('2.1.4.11', 'Transcripts usability testing', 'content', 'x2-1-4-11-transcripts-usability-testing'),
|
|
86
|
+
p('2.1.4.12', 'Transcripts reviewed by content authors', 'content', 'x2-1-4-12-transcripts-reviewed-by-content-authors'),
|
|
87
|
+
g('2.1.5', 'Captions'),
|
|
88
|
+
p('2.1.5.1', 'Captions adjustable', 'supplemental', 'x2-1-5-1-captions-adjustable'),
|
|
89
|
+
p('2.1.5.2', 'Captions available (prerecorded)', 'core', 'x2-1-5-2-captions-available-prerecorded'),
|
|
90
|
+
p('2.1.5.3', 'Captions equivalent (prerecorded)', 'core', 'x2-1-5-3-captions-equivalent-prerecorded'),
|
|
91
|
+
p('2.1.5.4', 'Captions available (live)', 'core', 'x2-1-5-4-captions-available-live'),
|
|
92
|
+
p('2.1.5.5', 'Caption language adjustable', 'supplemental', 'x2-1-5-5-caption-language-adjustable'),
|
|
93
|
+
p('2.1.5.6', 'Captions controllable', 'supplemental', 'x2-1-5-6-captions-controllable'),
|
|
94
|
+
p('2.1.5.7', 'Captions unobstructed', 'supplemental', 'x2-1-5-7-captions-unobstructed'),
|
|
95
|
+
p('2.1.5.8', 'Captions synchronized', 'core', 'x2-1-5-8-captions-synchronized'),
|
|
96
|
+
p('2.1.5.9', 'Captions centered (immersive)', 'core', 'x2-1-5-9-captions-centered-immersive'),
|
|
97
|
+
p('2.1.5.10', 'Direction indicated (immersive)', 'core', 'x2-1-5-10-direction-indicated-immersive'),
|
|
98
|
+
p('2.1.5.11', 'Speakers identified in captions', 'supplemental', 'x2-1-5-11-speakers-identified-in-captions'),
|
|
99
|
+
p('2.1.5.12', 'Speaker language identified in captions', 'supplemental', 'x2-1-5-12-speaker-language-identified-in-captions'),
|
|
100
|
+
p('2.1.5.13', 'Sounds identified in captions', 'core', 'x2-1-5-13-sounds-identified-in-captions'),
|
|
101
|
+
p('2.1.5.14', 'Captions style guide', 'organization', 'x2-1-5-14-captions-style-guide'),
|
|
102
|
+
p('2.1.5.15', 'Captions usability testing', 'content', 'x2-1-5-15-captions-usability-testing'),
|
|
103
|
+
p('2.1.5.16', 'Captions reviewed by content authors', 'content', 'x2-1-5-16-captions-reviewed-by-content-authors'),
|
|
104
|
+
g('2.1.6', 'Audio descriptions'),
|
|
105
|
+
p('2.1.6.1', 'Audio descriptions available (prerecorded)', 'core', 'x2-1-6-1-audio-descriptions-available-prerecorded'),
|
|
106
|
+
p('2.1.6.2', 'Audio descriptions equivalent (prerecorded)', 'core', 'x2-1-6-2-audio-descriptions-equivalent-prerecorded'),
|
|
107
|
+
p('2.1.6.3', 'Audio descriptions synchronized', 'core', 'x2-1-6-3-audio-descriptions-synchronized'),
|
|
108
|
+
p('2.1.6.4', 'Audio descriptions available (live)', 'supplemental', 'x2-1-6-4-audio-descriptions-available-live'),
|
|
109
|
+
p('2.1.6.5', 'Extended audio descriptions available', 'core', 'x2-1-6-5-extended-audio-descriptions-available'),
|
|
110
|
+
p('2.1.6.6', 'Extended audio descriptions equivalent', 'core', 'x2-1-6-6-extended-audio-descriptions-equivalent'),
|
|
111
|
+
p('2.1.6.7', 'Audio description language adjustable', 'supplemental', 'x2-1-6-7-audio-description-language-adjustable'),
|
|
112
|
+
p('2.1.6.8', 'Audio descriptions controllable', 'supplemental', 'x2-1-6-8-audio-descriptions-controllable'),
|
|
113
|
+
p('2.1.6.9', 'Speakers identified in audio descriptions', 'supplemental', 'x2-1-6-9-speakers-identified-in-audio-descriptions'),
|
|
114
|
+
p('2.1.6.10', 'Speaker language identified in audio descriptions', 'supplemental', 'x2-1-6-10-speaker-language-identified-in-audio-descriptions'),
|
|
115
|
+
p('2.1.6.11', 'Sounds identified in audio descriptions', 'core', 'x2-1-6-11-sounds-identified-in-audio-descriptions'),
|
|
116
|
+
p('2.1.6.12', 'Visual information identified in audio descriptions', 'core', 'x2-1-6-12-visual-information-identified-in-audio-descriptions'),
|
|
117
|
+
p('2.1.6.13', 'Audio descriptions style guide', 'organization', 'x2-1-6-13-audio-descriptions-style-guide'),
|
|
118
|
+
p('2.1.6.14', 'Audio descriptions usability testing', 'content', 'x2-1-6-14-audio-descriptions-usability-testing'),
|
|
119
|
+
p('2.1.6.15', 'Audio descriptions reviewed by content authors', 'content', 'x2-1-6-15-audio-descriptions-reviewed-by-content-authors'),
|
|
120
|
+
g('2.1.7', 'Sign language'),
|
|
121
|
+
p('2.1.7.1', 'Sign language available (prerecorded)', 'supplemental', 'x2-1-7-1-sign-language-available-prerecorded'),
|
|
122
|
+
p('2.1.7.2', 'Sign language controllable', 'supplemental', 'x2-1-7-2-sign-language-controllable'),
|
|
123
|
+
p('2.1.7.3', 'Sign language policy (live)', 'organization', 'x2-1-7-3-sign-language-policy-live'),
|
|
124
|
+
g('2.1.8', 'Single sense'),
|
|
125
|
+
p('2.1.8.1', 'Hue not relied on', 'core', 'x2-1-8-1-hue-not-relied-on'),
|
|
126
|
+
p('2.1.8.2', 'Graphical object contrast sufficient', 'core', 'x2-1-8-2-graphical-object-contrast-sufficient'),
|
|
127
|
+
p('2.1.8.3', 'Visual depth not relied on', 'core', 'x2-1-8-3-visual-depth-not-relied-on'),
|
|
128
|
+
p('2.1.8.4', 'Sound not relied on', 'core', 'x2-1-8-4-sound-not-relied-on'),
|
|
129
|
+
p('2.1.8.5', 'Spatial audio not relied on', 'core', 'x2-1-8-5-spatial-audio-not-relied-on'),
|
|
130
|
+
g('2.1.9', 'Accessible media player'),
|
|
131
|
+
p('2.1.9.1', 'Accessible video player selected', 'content', 'x2-1-9-1-accessible-video-player-selected'),
|
|
132
|
+
p('2.1.9.2', 'Accessible audio player selected', 'content', 'x2-1-9-2-accessible-audio-player-selected'),
|
|
133
|
+
g('2.2.1', 'Text appearance'),
|
|
134
|
+
p('2.2.1.1', 'Blocks of text readable (minimum)', 'core', 'x2-2-1-1-blocks-of-text-readable-minimum'),
|
|
135
|
+
p('2.2.1.2', 'Text style readable (minimum)', 'core', 'x2-2-1-2-text-style-readable-minimum'),
|
|
136
|
+
p('2.2.1.3', 'Text contrast sufficient (minimum)', 'core', 'x2-2-1-3-text-contrast-sufficient-minimum'),
|
|
137
|
+
p('2.2.1.4', 'Blocks of text adjustable', 'core', 'x2-2-1-4-blocks-of-text-adjustable'),
|
|
138
|
+
p('2.2.1.5', 'Text style adjustable', 'core', 'x2-2-1-5-text-style-adjustable'),
|
|
139
|
+
p('2.2.1.6', 'Text size adjustable', 'core', 'x2-2-1-6-text-size-adjustable'),
|
|
140
|
+
p('2.2.1.7', 'Text color adjustable', 'core', 'x2-2-1-7-text-color-adjustable'),
|
|
141
|
+
p('2.2.1.8', 'Blocks of text readable (enhanced)', 'supplemental', 'x2-2-1-8-blocks-of-text-readable-enhanced'),
|
|
142
|
+
p('2.2.1.9', 'Text style readable (enhanced)', 'supplemental', 'x2-2-1-9-text-style-readable-enhanced'),
|
|
143
|
+
p('2.2.1.10', 'Text contrast sufficient (enhanced)', 'supplemental', 'x2-2-1-10-text-contrast-sufficient-enhanced'),
|
|
144
|
+
p('2.2.1.11', 'Text customizations retained', 'supplemental', 'x2-2-1-11-text-customizations-retained'),
|
|
145
|
+
g('2.2.2', 'Text-to-speech'),
|
|
146
|
+
p('2.2.2.1', 'Text detectable', 'core', 'x2-2-2-1-text-detectable'),
|
|
147
|
+
p('2.2.2.2', 'Human language detectable', 'core', 'x2-2-2-2-human-language-detectable'),
|
|
148
|
+
p('2.2.2.3', 'Numerical metadata available', 'core', 'x2-2-2-3-numerical-metadata-available'),
|
|
149
|
+
g('2.2.3', 'Clear language'),
|
|
150
|
+
p('2.2.3.1', 'Abbreviations explained', 'core', 'x2-2-3-1-abbreviations-explained'),
|
|
151
|
+
p('2.2.3.2', 'Non-literal language explained', 'supplemental', 'x2-2-3-2-non-literal-language-explained'),
|
|
152
|
+
p('2.2.3.3', 'Summaries available', 'core', 'x2-2-3-3-summaries-available'),
|
|
153
|
+
p('2.2.3.4', 'Common words used', 'supplemental', 'x2-2-3-4-common-words-used'),
|
|
154
|
+
p('2.2.3.5', 'Diacritics available', 'core', 'x2-2-3-5-diacritics-available'),
|
|
155
|
+
p('2.2.3.6', 'No nested clauses', 'supplemental', 'x2-2-3-6-no-nested-clauses'),
|
|
156
|
+
p('2.2.3.7', 'No unnecessary words', 'supplemental', 'x2-2-3-7-no-unnecessary-words'),
|
|
157
|
+
p('2.2.3.8', 'Clear language review', 'organization', 'x2-2-3-8-clear-language-review'),
|
|
158
|
+
p('2.2.3.9', 'Visual aids review', 'organization', 'x2-2-3-9-visual-aids-review'),
|
|
159
|
+
g('2.3.1', 'Keyboard focus appearance'),
|
|
160
|
+
p('2.3.1.1', 'Default focus indicator used', 'supplemental', 'x2-3-1-1-default-focus-indicator-used'),
|
|
161
|
+
p('2.3.1.2', 'Focus indicator contrast sufficient', 'core', 'x2-3-1-2-focus-indicator-contrast-sufficient'),
|
|
162
|
+
p('2.3.1.3', 'Focus indicator size sufficient', 'supplemental', 'x2-3-1-3-focus-indicator-size-sufficient'),
|
|
163
|
+
p('2.3.1.4', 'Focus indicator style guide', 'organization', 'x2-3-1-4-focus-indicator-style-guide'),
|
|
164
|
+
g('2.3.2', 'Pointer focus appearance'),
|
|
165
|
+
p('2.3.2.1', 'Pointer activation indicated (minimum)', 'core', 'x2-3-2-1-pointer-activation-indicated-minimum'),
|
|
166
|
+
p('2.3.2.2', 'Pointer activation indicated (enhanced)', 'supplemental', 'x2-3-2-2-pointer-activation-indicated-enhanced'),
|
|
167
|
+
p('2.3.2.3', 'Pointer contrast sufficient', 'core', 'x2-3-2-3-pointer-contrast-sufficient'),
|
|
168
|
+
p('2.3.2.4', 'Default pointer used', 'supplemental', 'x2-3-2-4-default-pointer-used'),
|
|
169
|
+
p('2.3.2.5', 'Pointer focus indicated', 'core', 'x2-3-2-5-pointer-focus-indicated'),
|
|
170
|
+
p('2.3.2.6', 'Pointer visible', 'core', 'x2-3-2-6-pointer-visible'),
|
|
171
|
+
p('2.3.2.7', 'Enhanced pointer available', 'supplemental', 'x2-3-2-7-enhanced-pointer-available'),
|
|
172
|
+
g('2.3.3', 'Navigating content'),
|
|
173
|
+
p('2.3.3.1', 'Focus relevant', 'core', 'x2-3-3-1-focus-relevant'),
|
|
174
|
+
p('2.3.3.2', 'Focus retained', 'supplemental', 'x2-3-3-2-focus-retained'),
|
|
175
|
+
p('2.3.3.3', 'Focus order meaningful', 'core', 'x2-3-3-3-focus-order-meaningful'),
|
|
176
|
+
g('2.3.4', 'Expected behavior'),
|
|
177
|
+
p('2.3.4.1', 'Consistent interactions', 'content', 'x2-3-4-1-consistent-interactions'),
|
|
178
|
+
p('2.3.4.2', 'Consistent control location', 'supplemental', 'x2-3-4-2-consistent-control-location'),
|
|
179
|
+
p('2.3.4.3', 'Conventional pattern used', 'content', 'x2-3-4-3-conventional-pattern-used'),
|
|
180
|
+
g('2.3.5', 'Control information'),
|
|
181
|
+
p('2.3.5.1', 'Interactive element contrast sufficient', 'core', 'x2-3-5-1-interactive-element-contrast-sufficient'),
|
|
182
|
+
p('2.3.5.2', 'Interactive element names available', 'core', 'x2-3-5-2-interactive-element-names-available'),
|
|
183
|
+
p('2.3.5.3', 'Changes to elements notified', 'core', 'x2-3-5-3-changes-to-elements-notified'),
|
|
184
|
+
p('2.3.5.4', 'Input constraints used', 'core', 'x2-3-5-4-input-constraints-used'),
|
|
185
|
+
p('2.3.5.5', 'Label included in programmatic name', 'core', 'x2-3-5-5-label-included-in-programmatic-name'),
|
|
186
|
+
p('2.3.5.6', 'Roles, values, states, properties available', 'core', 'x2-3-5-6-roles-values-states-properties-available'),
|
|
187
|
+
g('2.4.1', 'Keyboard interface input'),
|
|
188
|
+
p('2.4.1.1', 'Keyboard operable', 'core', 'x2-4-1-1-keyboard-operable'),
|
|
189
|
+
p('2.4.1.2', 'Keyboard accessible', 'core', 'x2-4-1-2-keyboard-accessible'),
|
|
190
|
+
p('2.4.1.3', 'Bidirectional navigation', 'core', 'x2-4-1-3-bidirectional-navigation'),
|
|
191
|
+
p('2.4.1.4', 'Custom keys documented', 'supplemental', 'x2-4-1-4-custom-keys-documented'),
|
|
192
|
+
p('2.4.1.5', 'No keyboard conflicts', 'core', 'x2-4-1-5-no-keyboard-conflicts'),
|
|
193
|
+
p('2.4.1.6', 'Focus placed', 'supplemental', 'x2-4-1-6-focus-placed'),
|
|
194
|
+
p('2.4.1.7', 'No keyboard traps', 'core', 'x2-4-1-7-no-keyboard-traps'),
|
|
195
|
+
p('2.4.1.8', 'Focus user-controlled', 'core', 'x2-4-1-8-focus-user-controlled'),
|
|
196
|
+
p('2.4.1.9', 'Focus movement relevant', 'supplemental', 'x2-4-1-9-focus-movement-relevant'),
|
|
197
|
+
g('2.4.2', 'Physical or cognitive effort when using keyboard'),
|
|
198
|
+
p('2.4.2.1', 'Navigation keys described', 'supplemental', 'x2-4-2-1-navigation-keys-described'),
|
|
199
|
+
p('2.4.2.2', 'No repetitive adjacent interactive elements', 'supplemental', 'x2-4-2-2-no-repetitive-adjacent-interactive-elements'),
|
|
200
|
+
p('2.4.2.3', 'Keyboard effort comparable', 'content', 'x2-4-2-3-keyboard-effort-comparable'),
|
|
201
|
+
g('2.4.3', 'Pointer input'),
|
|
202
|
+
p('2.4.3.1', 'Pointer activation controllable', 'core', 'x2-4-3-1-pointer-activation-controllable'),
|
|
203
|
+
p('2.4.3.2', 'Simple pointer input available', 'core', 'x2-4-3-2-simple-pointer-input-available'),
|
|
204
|
+
p('2.4.3.3', 'Consistent pointer cancellation', 'supplemental', 'x2-4-3-3-consistent-pointer-cancellation'),
|
|
205
|
+
p('2.4.3.4', 'Pointer pressure not relied on', 'core', 'x2-4-3-4-pointer-pressure-not-relied-on'),
|
|
206
|
+
p('2.4.3.5', 'Pointer speed not relied on', 'core', 'x2-4-3-5-pointer-speed-not-relied-on'),
|
|
207
|
+
g('2.4.4', 'Speech and voice input'),
|
|
208
|
+
p('2.4.4.1', 'Speech not relied on', 'core', 'x2-4-4-1-speech-not-relied-on'),
|
|
209
|
+
p('2.4.4.2', 'Real-time text available', 'core', 'x2-4-4-2-real-time-text-available'),
|
|
210
|
+
p('2.4.4.3', 'Generated speech testing', 'content', 'x2-4-4-3-generated-speech-testing'),
|
|
211
|
+
g('2.4.5', 'Input operation'),
|
|
212
|
+
p('2.4.5.1', 'Hover or focus content dismissible', 'core', 'x2-4-5-1-hover-or-focus-content-dismissible'),
|
|
213
|
+
p('2.4.5.2', 'Hover content persistent', 'core', 'x2-4-5-2-hover-content-persistent'),
|
|
214
|
+
p('2.4.5.3', 'Hover or focus content persistent', 'supplemental', 'x2-4-5-3-hover-or-focus-content-persistent'),
|
|
215
|
+
p('2.4.5.4', 'Path-based gesture not relied on', 'core', 'x2-4-5-4-path-based-gesture-not-relied-on'),
|
|
216
|
+
p('2.4.5.5', 'Input method flexible', 'supplemental', 'x2-4-5-5-input-method-flexible'),
|
|
217
|
+
p('2.4.5.6', 'Body movements not relied on', 'core', 'x2-4-5-6-body-movements-not-relied-on'),
|
|
218
|
+
p('2.4.5.7', 'Eye tracking not relied on', 'core', 'x2-4-5-7-eye-tracking-not-relied-on'),
|
|
219
|
+
p('2.4.5.8', 'Pointer accessible', 'core', 'x2-4-5-8-pointer-accessible'),
|
|
220
|
+
g('2.4.6', 'Authentication'),
|
|
221
|
+
p('2.4.6.1', 'Biometrics not relied on', 'core', 'x2-4-6-1-biometrics-not-relied-on'),
|
|
222
|
+
p('2.4.6.2', 'Voice identification not relied on', 'core', 'x2-4-6-2-voice-identification-not-relied-on'),
|
|
223
|
+
g('2.5.1', 'Correct errors'),
|
|
224
|
+
p('2.5.1.1', 'Error notifications available', 'core', 'x2-5-1-1-error-notifications-available'),
|
|
225
|
+
p('2.5.1.2', 'Error suggestions provided', 'supplemental', 'x2-5-1-2-error-suggestions-provided'),
|
|
226
|
+
p('2.5.1.3', 'Errors indicated in multiple ways', 'supplemental', 'x2-5-1-3-errors-indicated-in-multiple-ways'),
|
|
227
|
+
p('2.5.1.4', 'Error messages persistent', 'supplemental', 'x2-5-1-4-error-messages-persistent'),
|
|
228
|
+
p('2.5.1.5', 'Errors associated', 'core', 'x2-5-1-5-errors-associated'),
|
|
229
|
+
p('2.5.1.6', 'Error messages collocated', 'supplemental', 'x2-5-1-6-error-messages-collocated'),
|
|
230
|
+
g('2.5.2', 'Prevent errors'),
|
|
231
|
+
p('2.5.2.1', 'Errors preventable', 'core', 'x2-5-2-1-errors-preventable'),
|
|
232
|
+
p('2.5.2.2', 'Submission status notified', 'supplemental', 'x2-5-2-2-submission-status-notified'),
|
|
233
|
+
p('2.5.2.3', 'Data entry validated', 'supplemental', 'x2-5-2-3-data-entry-validated'),
|
|
234
|
+
p('2.5.2.4', 'Error prevention review', 'content', 'x2-5-2-4-error-prevention-review'),
|
|
235
|
+
g('2.6.1', 'Avoid physical harm'),
|
|
236
|
+
p('2.6.1.1', 'No flashing over threshold', 'core', 'x2-6-1-1-no-flashing-over-threshold'),
|
|
237
|
+
p('2.6.1.2', 'No flashing over threshold (no exceptions)', 'supplemental', 'x2-6-1-2-no-flashing-over-threshold-no-exceptions'),
|
|
238
|
+
p('2.6.1.3', 'No visual motion', 'core', 'x2-6-1-3-no-visual-motion'),
|
|
239
|
+
p('2.6.1.4', 'No visual motion (no exceptions)', 'supplemental', 'x2-6-1-4-no-visual-motion-no-exceptions'),
|
|
240
|
+
p('2.6.1.5', 'Trigger warning available', 'core', 'x2-6-1-5-trigger-warning-available'),
|
|
241
|
+
p('2.6.1.6', 'Haptic stimulation adjustable', 'core', 'x2-6-1-6-haptic-stimulation-adjustable'),
|
|
242
|
+
p('2.6.1.7', 'Audio shifting adjustable', 'core', 'x2-6-1-7-audio-shifting-adjustable'),
|
|
243
|
+
p('2.6.1.8', 'Safe content review', 'content', 'x2-6-1-8-safe-content-review'),
|
|
244
|
+
g('2.7.1', 'Recognizable layouts'),
|
|
245
|
+
p('2.7.1.1', 'Conventional layout review', 'content', 'x2-7-1-1-conventional-layout-review'),
|
|
246
|
+
g('2.7.2', 'User orientation'),
|
|
247
|
+
p('2.7.2.1', 'Page/view title available', 'core', 'x2-7-2-1-page-view-title-available'),
|
|
248
|
+
p('2.7.2.2', 'Location within product review', 'content', 'x2-7-2-2-location-within-product-review'),
|
|
249
|
+
p('2.7.2.3', 'All steps listed', 'core', 'x2-7-2-3-all-steps-listed'),
|
|
250
|
+
p('2.7.2.4', 'Current step indicated', 'core', 'x2-7-2-4-current-step-indicated'),
|
|
251
|
+
p('2.7.2.5', 'Page/view change notified', 'core', 'x2-7-2-5-page-view-change-notified'),
|
|
252
|
+
p('2.7.2.6', 'Return to start supported', 'supplemental', 'x2-7-2-6-return-to-start-supported'),
|
|
253
|
+
g('2.7.3', 'Structure'),
|
|
254
|
+
p('2.7.3.1', 'Relationships detectable', 'supplemental', 'x2-7-3-1-relationships-detectable'),
|
|
255
|
+
p('2.7.3.2', 'Blocks of content available (minimum)', 'core', 'x2-7-3-2-blocks-of-content-available-minimum'),
|
|
256
|
+
p('2.7.3.3', 'Sections labeled', 'core', 'x2-7-3-3-sections-labeled'),
|
|
257
|
+
p('2.7.3.4', 'Heading structure available', 'supplemental', 'x2-7-3-4-heading-structure-available'),
|
|
258
|
+
p('2.7.3.5', 'Order detectable', 'core', 'x2-7-3-5-order-detectable'),
|
|
259
|
+
p('2.7.3.6', 'Blocks of content available (enhanced)', 'supplemental', 'x2-7-3-6-blocks-of-content-available-enhanced'),
|
|
260
|
+
p('2.7.3.7', 'Clear structure review', 'organization', 'x2-7-3-7-clear-structure-review'),
|
|
261
|
+
p('2.7.3.8', 'Key information usability testing', 'content', 'x2-7-3-8-key-information-usability-testing'),
|
|
262
|
+
g('2.7.4', 'No obstruction'),
|
|
263
|
+
p('2.7.4.1', 'Overlay content dismissible', 'core', 'x2-7-4-1-overlay-content-dismissible'),
|
|
264
|
+
g('2.8.1', 'Consistency'),
|
|
265
|
+
p('2.8.1.1', 'Consistent structural order', 'supplemental', 'x2-8-1-1-consistent-structural-order'),
|
|
266
|
+
p('2.8.1.2', 'Consistent navigation order', 'supplemental', 'x2-8-1-2-consistent-navigation-order'),
|
|
267
|
+
p('2.8.1.3', 'Consistent navigation labels', 'supplemental', 'x2-8-1-3-consistent-navigation-labels'),
|
|
268
|
+
g('2.9.1', 'Avoid exclusionary cognitive tasks'),
|
|
269
|
+
p('2.9.1.1', 'Automated entry allowed', 'core', 'x2-9-1-1-automated-entry-allowed'),
|
|
270
|
+
p('2.9.1.2', 'Cognitive test alternatives available', 'core', 'x2-9-1-2-cognitive-test-alternatives-available'),
|
|
271
|
+
g('2.9.2', 'Adequate time'),
|
|
272
|
+
p('2.9.2.1', 'Timeout adjustable', 'supplemental', 'x2-9-2-1-timeout-adjustable'),
|
|
273
|
+
p('2.9.2.2', 'No time limits', 'supplemental', 'x2-9-2-2-no-time-limits'),
|
|
274
|
+
p('2.9.2.3', 'No unnecessary time limits', 'content', 'x2-9-2-3-no-unnecessary-time-limits'),
|
|
275
|
+
p('2.9.2.4', 'Time limits conveyed', 'supplemental', 'x2-9-2-4-time-limits-conveyed'),
|
|
276
|
+
g('2.9.3', 'Avoid deception'),
|
|
277
|
+
p('2.9.3.1', 'Preselections visible', 'core', 'x2-9-3-1-preselections-visible'),
|
|
278
|
+
p('2.9.3.2', 'Deceptive practices usability testing', 'content', 'x2-9-3-2-deceptive-practices-usability-testing'),
|
|
279
|
+
p('2.9.3.3', 'Deceptive messaging expert review', 'content', 'x2-9-3-3-deceptive-messaging-expert-review'),
|
|
280
|
+
g('2.9.4', 'Retain information'),
|
|
281
|
+
p('2.9.4.1', 'Going back supported', 'supplemental', 'x2-9-4-1-going-back-supported'),
|
|
282
|
+
p('2.9.4.2', 'No redundant entry', 'supplemental', 'x2-9-4-2-no-redundant-entry'),
|
|
283
|
+
p('2.9.4.3', 'Progress saved', 'supplemental', 'x2-9-4-3-progress-saved'),
|
|
284
|
+
g('2.9.5', 'Complete tasks'),
|
|
285
|
+
p('2.9.5.1', 'Required action available', 'supplemental', 'x2-9-5-1-required-action-available'),
|
|
286
|
+
p('2.9.5.2', 'Information requirements available at start', 'supplemental', 'x2-9-5-2-information-requirements-available-at-start'),
|
|
287
|
+
p('2.9.5.3', 'Process instructions available', 'supplemental', 'x2-9-5-3-process-instructions-available'),
|
|
288
|
+
g('2.9.6', 'Unnecessary steps'),
|
|
289
|
+
p('2.9.6.1', 'Usability testing for unnecessary steps', 'content', 'x2-9-6-1-usability-testing-for-unnecessary-steps'),
|
|
290
|
+
g('2.10.1', 'Risk'),
|
|
291
|
+
p('2.10.1.1', 'Consequences of choices explained', 'core', 'x2-10-1-1-consequences-of-choices-explained'),
|
|
292
|
+
p('2.10.1.2', 'Consequences explained before agreement', 'supplemental', 'x2-10-1-2-consequences-explained-before-agreement'),
|
|
293
|
+
p('2.10.1.3', 'Diverse disabilities considered', 'content', 'x2-10-1-3-diverse-disabilities-considered'),
|
|
294
|
+
p('2.10.1.4', 'Algorithm inclusivity review', 'organization', 'x2-10-1-4-algorithm-inclusivity-review'),
|
|
295
|
+
g('2.10.2', 'Algorithms'),
|
|
296
|
+
p('2.10.2.1', 'Inclusive data set', 'content', 'x2-10-2-1-inclusive-data-set'),
|
|
297
|
+
p('2.10.2.2', 'No harm from algorithms', 'content', 'x2-10-2-2-no-harm-from-algorithms'),
|
|
298
|
+
g('2.11.1', 'Help available'),
|
|
299
|
+
p('2.11.1.1', 'Consistent help available', 'supplemental', 'x2-11-1-1-consistent-help-available'),
|
|
300
|
+
p('2.11.1.2', 'Contextual help available', 'supplemental', 'x2-11-1-2-contextual-help-available'),
|
|
301
|
+
p('2.11.1.3', 'Disabled controls explained', 'supplemental', 'x2-11-1-3-disabled-controls-explained'),
|
|
302
|
+
p('2.11.1.4', 'Sensory characteristics not relied on', 'core', 'x2-11-1-4-sensory-characteristics-not-relied-on'),
|
|
303
|
+
p('2.11.1.5', 'Supported decision-making review', 'content', 'x2-11-1-5-supported-decision-making-review'),
|
|
304
|
+
p('2.11.1.6', 'Help usability testing', 'content', 'x2-11-1-6-help-usability-testing'),
|
|
305
|
+
g('2.11.2', 'Feedback'),
|
|
306
|
+
p('2.11.2.1', 'Feedback mechanism available', 'supplemental', 'x2-11-2-1-feedback-mechanism-available'),
|
|
307
|
+
g('2.12.1', 'Assistive technology control'),
|
|
308
|
+
p('2.12.1.1', 'Assistive technology supported', 'core', 'x2-12-1-1-assistive-technology-supported'),
|
|
309
|
+
p('2.12.1.2', 'User settings supported', 'core', 'x2-12-1-2-user-settings-supported'),
|
|
310
|
+
p('2.12.1.3', 'Virtual cursor supported', 'core', 'x2-12-1-3-virtual-cursor-supported'),
|
|
311
|
+
p('2.12.1.4', 'Notifications adjustable', 'supplemental', 'x2-12-1-4-notifications-adjustable'),
|
|
312
|
+
g('2.12.3', 'Adjustable viewport'),
|
|
313
|
+
p('2.12.3.1', 'Orientation supported (minimum)', 'core', 'x2-12-3-1-orientation-supported-minimum'),
|
|
314
|
+
p('2.12.3.2', 'Orientation supported (enhanced)', 'supplemental', 'x2-12-3-2-orientation-supported-enhanced'),
|
|
315
|
+
p('2.12.3.3', 'Text reflow supported', 'supplemental', 'x2-12-3-3-text-reflow-supported'),
|
|
316
|
+
p('2.12.3.4', 'Layout reflow supported', 'supplemental', 'x2-12-3-4-layout-reflow-supported'),
|
|
317
|
+
g('2.12.4', 'Media control'),
|
|
318
|
+
p('2.12.4.1', 'Page/view audio adjustable', 'core', 'x2-12-4-1-page-view-audio-adjustable'),
|
|
319
|
+
p('2.12.4.2', 'Media alternatives searchable', 'supplemental', 'x2-12-4-2-media-alternatives-searchable'),
|
|
320
|
+
p('2.12.4.3', 'Media alternatives controllable', 'supplemental', 'x2-12-4-3-media-alternatives-controllable'),
|
|
321
|
+
p('2.12.4.4', 'Media chapters available', 'supplemental', 'x2-12-4-4-media-chapters-available'),
|
|
322
|
+
g('2.12.5', 'Content changes'),
|
|
323
|
+
p('2.12.5.1', 'Change of content notified', 'core', 'x2-12-5-1-change-of-content-notified'),
|
|
324
|
+
p('2.12.5.2', 'Change of focus notified', 'core', 'x2-12-5-2-change-of-focus-notified'),
|
|
325
|
+
p('2.12.5.3', 'Change of user agent notified', 'core', 'x2-12-5-3-change-of-user-agent-notified'),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
function g(num, name) { return { guideline: true, num, name }; }
|
|
329
|
+
function p(num, name, type, anchor) { return { num, name, type, anchor }; }
|
|
330
|
+
|
|
331
|
+
export const provisions = (() => {
|
|
332
|
+
const list = [];
|
|
333
|
+
let guideline = null;
|
|
334
|
+
for (const entry of RAW) {
|
|
335
|
+
if (entry.guideline) { guideline = entry; continue; }
|
|
336
|
+
list.push({
|
|
337
|
+
num: entry.num,
|
|
338
|
+
name: entry.name,
|
|
339
|
+
type: entry.type,
|
|
340
|
+
guideline: `${guideline.num} ${guideline.name}`,
|
|
341
|
+
url: `${draftInfo.url}#${entry.anchor}`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return list;
|
|
345
|
+
})();
|
|
346
|
+
|
|
347
|
+
const provisionByNum = new Map(provisions.map((entry) => [entry.num, entry]));
|
|
348
|
+
|
|
349
|
+
// Rule id → the draft requirements its failure also fails.
|
|
350
|
+
export const RULE_REQUIREMENTS = {
|
|
351
|
+
'area-alt': ['2.1.1.3', '2.3.5.2'],
|
|
352
|
+
'canvas-alt': ['2.1.3.1'],
|
|
353
|
+
'embed-alt': ['2.1.3.1'],
|
|
354
|
+
'image-alt': ['2.1.1.3'],
|
|
355
|
+
'input-image-alt': ['2.1.1.3', '2.3.5.2'],
|
|
356
|
+
'object-alt': ['2.1.3.1'],
|
|
357
|
+
'svg-img-alt': ['2.1.1.3'],
|
|
358
|
+
'audio-transcript': ['2.1.4.2'],
|
|
359
|
+
'media-captions': ['2.1.5.2'],
|
|
360
|
+
'video-audio-description': ['2.1.6.1'],
|
|
361
|
+
'aria-required-children': ['2.3.5.6'],
|
|
362
|
+
'aria-required-parent': ['2.3.5.6'],
|
|
363
|
+
'definition-list': ['2.7.3.1'],
|
|
364
|
+
'dlitem-parent': ['2.7.3.1'],
|
|
365
|
+
'form-label': ['2.3.5.2'],
|
|
366
|
+
'list-structure': ['2.7.3.1'],
|
|
367
|
+
'listitem-parent': ['2.7.3.1'],
|
|
368
|
+
'p-as-heading': ['2.7.3.4'],
|
|
369
|
+
'table-headers': ['2.7.3.1'],
|
|
370
|
+
// A lock to the platform's own default orientation meets 2.12.3.1; only
|
|
371
|
+
// the supplemental both-orientations requirement fails for every lock.
|
|
372
|
+
'orientation-lock': ['2.12.3.2'],
|
|
373
|
+
'region-purpose': ['2.7.3.3'],
|
|
374
|
+
'link-in-text-block': ['2.1.8.1'],
|
|
375
|
+
'audio-control': ['2.12.4.1'],
|
|
376
|
+
'color-contrast': ['2.2.1.3'],
|
|
377
|
+
'control-contrast': ['2.2.1.3'],
|
|
378
|
+
'meta-viewport': ['2.2.1.6'],
|
|
379
|
+
'color-contrast-enhanced': ['2.2.1.10'],
|
|
380
|
+
'reflow': ['2.12.3.4'],
|
|
381
|
+
'non-text-contrast': ['2.3.5.1'],
|
|
382
|
+
'scrollable-region-focusable': ['2.4.1.2'],
|
|
383
|
+
'meta-refresh': ['2.9.2.1'],
|
|
384
|
+
'pause-stop-hide': ['2.6.1.3'],
|
|
385
|
+
'video-loop-motion': ['2.6.1.3'],
|
|
386
|
+
'meta-refresh-no-exceptions': ['2.9.2.2'],
|
|
387
|
+
'reduced-motion': ['2.12.1.2'],
|
|
388
|
+
'bypass-blocks': ['2.7.3.2'],
|
|
389
|
+
'document-title': ['2.7.2.1'],
|
|
390
|
+
'visual-order-divergence': ['2.3.3.3'],
|
|
391
|
+
'link-name': ['2.3.5.2'],
|
|
392
|
+
'link-text-generic': ['2.3.5.2'],
|
|
393
|
+
'heading-label-placeholder': ['2.7.3.3', '2.3.5.2'],
|
|
394
|
+
'focus-visible': ['2.3.1.2'],
|
|
395
|
+
'section-heading': ['2.7.3.4'],
|
|
396
|
+
// Path-based gestures (2.4.5.4) and multipoint ones (2.4.3.2 lists them
|
|
397
|
+
// among complex pointer inputs).
|
|
398
|
+
'pointer-gesture-alternative': ['2.4.5.4', '2.4.3.2'],
|
|
399
|
+
'drag-alternative': ['2.4.3.2'],
|
|
400
|
+
'html-lang': ['2.2.2.2'],
|
|
401
|
+
'valid-lang-parts': ['2.2.2.2'],
|
|
402
|
+
'error-message-linkage': ['2.5.1.5'],
|
|
403
|
+
'financial-form-confirmation': ['2.5.2.1'],
|
|
404
|
+
'redundant-entry': ['2.9.4.2'],
|
|
405
|
+
'auth-field-obstruction': ['2.9.1.1'],
|
|
406
|
+
'captcha-alternative': ['2.9.1.2'],
|
|
407
|
+
'aria-allowed-attr': ['2.3.5.6'],
|
|
408
|
+
'aria-attr-valid': ['2.3.5.6'],
|
|
409
|
+
'aria-field-name': ['2.3.5.2'],
|
|
410
|
+
'aria-hidden-focus': ['2.3.5.6'],
|
|
411
|
+
'aria-state-unreachable': ['2.3.5.6'],
|
|
412
|
+
'aria-valid-refs': ['2.3.5.6'],
|
|
413
|
+
'button-name': ['2.3.5.2'],
|
|
414
|
+
'composite-widget-name': ['2.3.5.2'],
|
|
415
|
+
'frame-title': ['2.7.3.3'],
|
|
416
|
+
'invoker-target': ['2.3.5.6'],
|
|
417
|
+
'label-for-valid': ['2.3.5.2'],
|
|
418
|
+
'nested-interactive': ['2.3.5.6'],
|
|
419
|
+
'role-required-aria': ['2.3.5.6'],
|
|
420
|
+
'summary-name': ['2.3.5.2'],
|
|
421
|
+
'valid-role': ['2.3.5.6'],
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// WCAG rules that stay out of a WCAG 3 audit, and why. Best-practice rules
|
|
425
|
+
// are not listed: they run in either standard when best practices are on.
|
|
426
|
+
export const UNMAPPED_RULES = {
|
|
427
|
+
'reading-order-divergence': 'The draft has no requirement on reading sequence; focus order (2.3.3.3) is a different outcome.',
|
|
428
|
+
'autocomplete-valid': 'An invalid token hinders autofill but does not prevent automated entry (2.9.1.1), and the draft has no identify-purpose requirement.',
|
|
429
|
+
'text-justified': 'The draft asks that justification can be adjusted (2.2.1.4), not that text is unjustified by default.',
|
|
430
|
+
'text-spacing': 'The rule applies letter and word spacing, which neither adjustable-text requirement (2.2.1.4, 2.2.1.5) lists, so a clipping it finds may not fail the draft.',
|
|
431
|
+
'aria-label-misuse': 'It concerns labels on plain containers; the draft asks for names on interactive elements (2.3.5.2, 2.3.5.6) and on meaningful blocks only (2.7.3.3).',
|
|
432
|
+
'link-text-generic-only': 'The draft does not say whether a name may take its purpose from context; the context-aware link-text-generic rule stands for 2.3.5.2.',
|
|
433
|
+
'focus-not-obscured': 'The draft has no focus-obscured requirement.',
|
|
434
|
+
'focus-not-obscured-enhanced': 'The draft has no focus-obscured requirement.',
|
|
435
|
+
'target-size': 'The draft has no target size requirement.',
|
|
436
|
+
'target-size-enhanced': 'The draft has no target size requirement.',
|
|
437
|
+
'on-input-navigation': 'The draft\'s change notifications (2.7.2.5, 2.12.5) cover different triggers from a change of context on input.',
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/** The WCAG 3 tier a tag list asks for: wcag3-bronze, -silver or -gold,
|
|
441
|
+
* the highest when several are present; null for a WCAG 2 audit. */
|
|
442
|
+
export function tierFromTags(tags) {
|
|
443
|
+
let found = null;
|
|
444
|
+
for (const tag of tags ?? []) {
|
|
445
|
+
const tier = /^wcag3-(bronze|silver|gold)$/.exec(tag)?.[1];
|
|
446
|
+
if (tier && (!found || TIERS.indexOf(tier) > TIERS.indexOf(found))) found = tier;
|
|
447
|
+
}
|
|
448
|
+
return found;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** The draft requirements a rule speaks to, as { num, name, type, url }. */
|
|
452
|
+
export function requirementsForRule(ruleId) {
|
|
453
|
+
return (RULE_REQUIREMENTS[ruleId] ?? []).map((num) => {
|
|
454
|
+
const { name, type, url } = provisionByNum.get(num);
|
|
455
|
+
return { num, name, type: TYPE_LABELS[type], url };
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Does the tier take in any requirement this rule tests? */
|
|
460
|
+
export function ruleInTier(ruleId, tier) {
|
|
461
|
+
const types = TIER_TYPES[tier];
|
|
462
|
+
return Boolean(types) && (RULE_REQUIREMENTS[ruleId] ?? [])
|
|
463
|
+
.some((num) => types.has(provisionByNum.get(num).type));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Rule selection shared by every runner. A tag list selects the rules that
|
|
468
|
+
* carry any of its tags; a WCAG 3 tier tag also selects the rules its
|
|
469
|
+
* requirements map to. An empty or absent list selects every rule.
|
|
470
|
+
*/
|
|
471
|
+
export function ruleMatchesTags(rule, tags) {
|
|
472
|
+
if (!tags?.length) return true;
|
|
473
|
+
if (rule.tags.some((tag) => tags.includes(tag))) return true;
|
|
474
|
+
const tier = tierFromTags(tags);
|
|
475
|
+
return Boolean(tier) && ruleInTier(rule.id, tier);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* The tier as a human checklist, shaped like the WCAG 2 manual-review
|
|
480
|
+
* entries: `level` carries the provision type and `principle` the
|
|
481
|
+
* guideline. Every provision in the tier is listed, since no rule was
|
|
482
|
+
* written for any of them; the ones a rule speaks to are 'partial'.
|
|
483
|
+
*/
|
|
484
|
+
export function manualReviewChecklist(tier) {
|
|
485
|
+
const types = TIER_TYPES[tier];
|
|
486
|
+
if (!types) return [];
|
|
487
|
+
const partly = new Set(Object.values(RULE_REQUIREMENTS).flat());
|
|
488
|
+
return provisions
|
|
489
|
+
.filter((entry) => types.has(entry.type))
|
|
490
|
+
.map(({ num, name, type, guideline, url }) => ({
|
|
491
|
+
num,
|
|
492
|
+
name,
|
|
493
|
+
level: TYPE_LABELS[type],
|
|
494
|
+
principle: guideline,
|
|
495
|
+
url,
|
|
496
|
+
automation: partly.has(num) ? 'partial' : 'manual',
|
|
497
|
+
draft: true,
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** The results header for a WCAG 3 audit. */
|
|
502
|
+
export function standardFor(tier) {
|
|
503
|
+
return { ...draftInfo, tier, tierLabel: TIER_LABELS[tier] };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export default {
|
|
507
|
+
draftInfo, TIERS, TIER_LABELS, provisions, RULE_REQUIREMENTS, UNMAPPED_RULES,
|
|
508
|
+
tierFromTags, requirementsForRule, ruleInTier, ruleMatchesTags, manualReviewChecklist, standardFor,
|
|
509
|
+
};
|
package/package.json
CHANGED