pour-engine 1.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/config/project.config.js +11 -0
- package/engine/COVERAGE.md +115 -0
- package/engine/README.md +115 -0
- package/engine/index.js +258 -0
- package/engine/lib/accessible-name.js +126 -0
- package/engine/lib/contrast.js +628 -0
- package/engine/lib/dom.js +116 -0
- package/engine/lib/roles.js +125 -0
- package/engine/rules/best-practice/accesskeys.js +29 -0
- package/engine/rules/best-practice/aria-label-misuse.js +22 -0
- package/engine/rules/best-practice/button-type.js +15 -0
- package/engine/rules/best-practice/empty-heading.js +19 -0
- package/engine/rules/best-practice/fieldset-legend.js +35 -0
- package/engine/rules/best-practice/heading-order.js +30 -0
- package/engine/rules/best-practice/landmark-one-main.js +22 -0
- package/engine/rules/best-practice/landmark-top-level.js +46 -0
- package/engine/rules/best-practice/landmark-unique.js +84 -0
- package/engine/rules/best-practice/multiple-labels.js +23 -0
- package/engine/rules/best-practice/new-window-link.js +21 -0
- package/engine/rules/best-practice/no-autofocus.js +16 -0
- package/engine/rules/best-practice/page-heading-one.js +17 -0
- package/engine/rules/best-practice/positive-tabindex.js +17 -0
- package/engine/rules/best-practice/redundant-alt-phrase.js +22 -0
- package/engine/rules/best-practice/redundant-aria-label.js +18 -0
- package/engine/rules/best-practice/redundant-aria.js +32 -0
- package/engine/rules/best-practice/redundant-image-alt.js +20 -0
- package/engine/rules/best-practice/redundant-role.js +62 -0
- package/engine/rules/best-practice/redundant-tabindex.js +15 -0
- package/engine/rules/best-practice/region.js +73 -0
- package/engine/rules/index.js +179 -0
- package/engine/rules/wcag/1.1.1-area-alt.js +18 -0
- package/engine/rules/wcag/1.1.1-image-alt.js +48 -0
- package/engine/rules/wcag/1.1.1-input-image-alt.js +17 -0
- package/engine/rules/wcag/1.1.1-object-alt.js +17 -0
- package/engine/rules/wcag/1.1.1-svg-img-alt.js +34 -0
- package/engine/rules/wcag/1.2.2-media-captions.js +19 -0
- package/engine/rules/wcag/1.3.1-aria-required-children.js +66 -0
- package/engine/rules/wcag/1.3.1-aria-required-parent.js +49 -0
- package/engine/rules/wcag/1.3.1-definition-list.js +21 -0
- package/engine/rules/wcag/1.3.1-dlitem-parent.js +17 -0
- package/engine/rules/wcag/1.3.1-form-label.js +81 -0
- package/engine/rules/wcag/1.3.1-list-structure.js +59 -0
- package/engine/rules/wcag/1.3.1-listitem-parent.js +33 -0
- package/engine/rules/wcag/1.3.1-p-as-heading.js +25 -0
- package/engine/rules/wcag/1.3.1-table-headers.js +61 -0
- package/engine/rules/wcag/1.3.5-autocomplete-valid.js +38 -0
- package/engine/rules/wcag/1.4.1-link-in-text-block.js +74 -0
- package/engine/rules/wcag/1.4.10-reflow.js +25 -0
- package/engine/rules/wcag/1.4.11-non-text-contrast.js +109 -0
- package/engine/rules/wcag/1.4.12-text-spacing.js +71 -0
- package/engine/rules/wcag/1.4.2-audio-control.js +40 -0
- package/engine/rules/wcag/1.4.3-color-contrast.js +462 -0
- package/engine/rules/wcag/1.4.3-control-contrast.js +72 -0
- package/engine/rules/wcag/1.4.4-meta-viewport.js +22 -0
- package/engine/rules/wcag/1.4.6-contrast-enhanced.js +11 -0
- package/engine/rules/wcag/2.1.1-scrollable-region-focusable.js +48 -0
- package/engine/rules/wcag/2.2.1-meta-refresh.js +20 -0
- package/engine/rules/wcag/2.2.2-pause-stop-hide.js +25 -0
- package/engine/rules/wcag/2.4.1-bypass-blocks.js +36 -0
- package/engine/rules/wcag/2.4.11-focus-not-obscured.js +80 -0
- package/engine/rules/wcag/2.4.2-document-title.js +18 -0
- package/engine/rules/wcag/2.4.4-link-name.js +19 -0
- package/engine/rules/wcag/2.4.7-focus-visible.js +46 -0
- package/engine/rules/wcag/2.5.3-label-in-name.js +88 -0
- package/engine/rules/wcag/2.5.5-target-size-enhanced.js +14 -0
- package/engine/rules/wcag/2.5.8-target-size.js +159 -0
- package/engine/rules/wcag/3.1.1-html-lang.js +31 -0
- package/engine/rules/wcag/3.1.2-valid-lang-parts.js +20 -0
- package/engine/rules/wcag/3.3.8-auth-field-obstruction.js +37 -0
- package/engine/rules/wcag/4.1.2-aria-allowed-attr.js +44 -0
- package/engine/rules/wcag/4.1.2-aria-attr-valid.js +50 -0
- package/engine/rules/wcag/4.1.2-aria-field-name.js +54 -0
- package/engine/rules/wcag/4.1.2-aria-hidden-focus.js +48 -0
- package/engine/rules/wcag/4.1.2-aria-valid-refs.js +61 -0
- package/engine/rules/wcag/4.1.2-button-name.js +17 -0
- package/engine/rules/wcag/4.1.2-dialog-name.js +35 -0
- package/engine/rules/wcag/4.1.2-frame-title.js +17 -0
- package/engine/rules/wcag/4.1.2-label-for-valid.js +25 -0
- package/engine/rules/wcag/4.1.2-nested-interactive.js +30 -0
- package/engine/rules/wcag/4.1.2-role-required-aria.js +36 -0
- package/engine/rules/wcag/4.1.2-valid-role.js +52 -0
- package/engine/wcag22.js +116 -0
- package/engine/wcag30-draft.js +149 -0
- package/package.json +34 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// The WCAG 3.0 guideline catalog — derived from the W3C Working DRAFT of
|
|
2
|
+
// 3 March 2026 (https://www.w3.org/TR/wcag-3.0/). WCAG 3.0 is a work in
|
|
3
|
+
// progress: the W3C says it "may be updated, replaced, or obsoleted" and it
|
|
4
|
+
// is not appropriate for conformance claims. Everything this module exports
|
|
5
|
+
// is therefore marked draft, and every UI that shows it must say so.
|
|
6
|
+
//
|
|
7
|
+
// The draft has no A/AA/AAA levels and no success criteria; it organizes
|
|
8
|
+
// outcome-style guidelines (with core/supplemental requirements below them)
|
|
9
|
+
// under twelve functional groups. Our automated rules are written against
|
|
10
|
+
// WCAG 2.x; `scToGuideline` maps each 2.x criterion we automate to the
|
|
11
|
+
// draft guideline that covers the same user need, judged by reading both
|
|
12
|
+
// specs side by side. Revisit the mapping with every new draft.
|
|
13
|
+
|
|
14
|
+
export const draftInfo = {
|
|
15
|
+
id: 'wcag30-draft',
|
|
16
|
+
name: 'WCAG 3.0',
|
|
17
|
+
status: 'W3C Working Draft',
|
|
18
|
+
dated: '2026-03-03',
|
|
19
|
+
draft: true,
|
|
20
|
+
url: 'https://www.w3.org/TR/wcag-3.0/',
|
|
21
|
+
note: 'A draft under active development: findings are WCAG 2.x rule results mapped to draft guidelines. Not suitable for conformance claims.',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const g = (num, name, group, anchor) => ({
|
|
25
|
+
num,
|
|
26
|
+
name,
|
|
27
|
+
group,
|
|
28
|
+
url: `https://www.w3.org/TR/wcag-3.0/#${anchor}`,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// All 44 guidelines of the 2026-03-03 draft, in document order.
|
|
32
|
+
export const guidelines = [
|
|
33
|
+
// 2.1 Images and media
|
|
34
|
+
g('2.1.1', 'Image alternatives', 'Images and media', 'image-alternatives'),
|
|
35
|
+
g('2.1.2', 'Media alternatives', 'Images and media', 'media-alternatives'),
|
|
36
|
+
g('2.1.3', 'Non-text alternatives', 'Images and media', 'non-text-alternatives'),
|
|
37
|
+
g('2.1.4', 'Captions', 'Images and media', 'captions'),
|
|
38
|
+
g('2.1.5', 'Audio descriptions', 'Images and media', 'audio-descriptions'),
|
|
39
|
+
g('2.1.6', 'Figure captions', 'Images and media', 'figure-captions'),
|
|
40
|
+
g('2.1.7', 'Single sense', 'Images and media', 'single-sense'),
|
|
41
|
+
// 2.2 Text and wording
|
|
42
|
+
g('2.2.1', 'Text appearance', 'Text and wording', 'text-appearance'),
|
|
43
|
+
g('2.2.2', 'Text-to-speech', 'Text and wording', 'text-to-speech'),
|
|
44
|
+
g('2.2.3', 'Clear language', 'Text and wording', 'clear-language'),
|
|
45
|
+
// 2.3 Interactive components
|
|
46
|
+
g('2.3.1', 'Keyboard focus appearance', 'Interactive components', 'keyboard-focus-appearance'),
|
|
47
|
+
g('2.3.2', 'Pointer focus appearance', 'Interactive components', 'pointer-focus-appearance'),
|
|
48
|
+
g('2.3.3', 'Navigating content', 'Interactive components', 'navigating-content'),
|
|
49
|
+
g('2.3.4', 'Expected behavior', 'Interactive components', 'expected-behavior'),
|
|
50
|
+
g('2.3.5', 'Control information', 'Interactive components', 'control-information'),
|
|
51
|
+
// 2.4 Input / operation
|
|
52
|
+
g('2.4.1', 'Keyboard interface input', 'Input and operation', 'keyboard-interface-input'),
|
|
53
|
+
g('2.4.2', 'Physical or cognitive effort when using keyboard', 'Input and operation', 'physical-or-cognitive-effort-when-using-keyboard'),
|
|
54
|
+
g('2.4.3', 'Pointer input', 'Input and operation', 'pointer-input'),
|
|
55
|
+
g('2.4.4', 'Speech and voice input', 'Input and operation', 'speech-and-voice-input'),
|
|
56
|
+
g('2.4.5', 'Input operation', 'Input and operation', 'input-operation-0'),
|
|
57
|
+
g('2.4.6', 'Authentication', 'Input and operation', 'authentication'),
|
|
58
|
+
// 2.5 Error handling
|
|
59
|
+
g('2.5.1', 'Correct errors', 'Error handling', 'correct-errors'),
|
|
60
|
+
g('2.5.2', 'Prevent errors', 'Error handling', 'prevent-errors'),
|
|
61
|
+
// 2.6 Animation and movement
|
|
62
|
+
g('2.6.1', 'Avoid physical harm', 'Animation and movement', 'avoid-physical-harm'),
|
|
63
|
+
// 2.7 Layout
|
|
64
|
+
g('2.7.1', 'Recognizable layouts', 'Layout', 'recognizable-layouts'),
|
|
65
|
+
g('2.7.2', 'User orientation', 'Layout', 'user-orientation'),
|
|
66
|
+
g('2.7.3', 'Structure', 'Layout', 'structure'),
|
|
67
|
+
g('2.7.4', 'No obstruction', 'Layout', 'no-obstruction'),
|
|
68
|
+
// 2.8 Consistency across views
|
|
69
|
+
g('2.8.1', 'Consistency', 'Consistency across views', 'consistency'),
|
|
70
|
+
// 2.9 Process and task completion
|
|
71
|
+
g('2.9.1', 'Avoid exclusionary cognitive tasks', 'Process and task completion', 'avoid-exclusionary-cognitive-tasks'),
|
|
72
|
+
g('2.9.2', 'Adequate time', 'Process and task completion', 'adequate-time'),
|
|
73
|
+
g('2.9.3', 'Avoid deception', 'Process and task completion', 'avoid-deception'),
|
|
74
|
+
g('2.9.4', 'Retain information', 'Process and task completion', 'retain-information'),
|
|
75
|
+
g('2.9.5', 'Complete tasks', 'Process and task completion', 'complete-tasks'),
|
|
76
|
+
g('2.9.6', 'Unnecessary steps', 'Process and task completion', 'unnecessary-steps'),
|
|
77
|
+
// 2.10 Policy and protection
|
|
78
|
+
g('2.10.1', 'Risk', 'Policy and protection', 'risk'),
|
|
79
|
+
g('2.10.2', 'Algorithms', 'Policy and protection', 'algorithms'),
|
|
80
|
+
// 2.11 Help and feedback
|
|
81
|
+
g('2.11.1', 'Help available', 'Help and feedback', 'help-available'),
|
|
82
|
+
g('2.11.2', 'Feedback', 'Help and feedback', 'feedback'),
|
|
83
|
+
// 2.12 User control
|
|
84
|
+
g('2.12.1', 'Assistive technology control', 'User control', 'assistive-technology-control'),
|
|
85
|
+
g('2.12.2', 'Control text', 'User control', 'control-text'),
|
|
86
|
+
g('2.12.3', 'Adjustable viewport', 'User control', 'adjustable-viewport'),
|
|
87
|
+
g('2.12.4', 'Media control', 'User control', 'media-control'),
|
|
88
|
+
g('2.12.5', 'Content changes', 'User control', 'content-changes'),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
// WCAG 2.x criterion (as tagged on rules: wcag111 = SC 1.1.1) → the draft
|
|
92
|
+
// guideline covering the same user need. Only criteria with automated rules
|
|
93
|
+
// appear; the judgment is ours, made by reading both documents.
|
|
94
|
+
export const scToGuideline = {
|
|
95
|
+
111: '2.1.1', // Non-text Content → Image alternatives
|
|
96
|
+
122: '2.1.4', // Captions (Prerecorded) → Captions
|
|
97
|
+
131: '2.7.3', // Info and Relationships → Structure
|
|
98
|
+
135: '2.4.5', // Identify Input Purpose → Input operation
|
|
99
|
+
141: '2.2.1', // Use of Color (links in text) → Text appearance
|
|
100
|
+
142: '2.12.4', // Audio Control → Media control
|
|
101
|
+
143: '2.2.1', // Contrast (Minimum) → Text appearance
|
|
102
|
+
144: '2.12.3', // Resize Text (viewport zoom) → Adjustable viewport
|
|
103
|
+
146: '2.2.1', // Contrast (Enhanced) → Text appearance
|
|
104
|
+
211: '2.4.1', // Keyboard → Keyboard interface input
|
|
105
|
+
221: '2.9.2', // Timing Adjustable → Adequate time
|
|
106
|
+
222: '2.12.4', // Pause, Stop, Hide → Media control
|
|
107
|
+
241: '2.3.3', // Bypass Blocks → Navigating content
|
|
108
|
+
242: '2.7.2', // Page Titled → User orientation
|
|
109
|
+
244: '2.3.5', // Link Purpose (In Context) → Control information
|
|
110
|
+
255: '2.4.3', // Target Size (Enhanced) → Pointer input
|
|
111
|
+
258: '2.4.3', // Target Size (Minimum) → Pointer input
|
|
112
|
+
311: '2.2.2', // Language of Page → Text-to-speech
|
|
113
|
+
312: '2.2.2', // Language of Parts → Text-to-speech
|
|
114
|
+
332: '2.3.5', // Labels or Instructions → Control information
|
|
115
|
+
412: '2.12.1', // Name, Role, Value → Assistive technology control
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const guidelineByNum = new Map(guidelines.map((entry) => [entry.num, entry]));
|
|
119
|
+
const partlyAutomated = new Set(Object.values(scToGuideline));
|
|
120
|
+
|
|
121
|
+
/** The draft guideline (num/name/group/url) a rule's wcagNNN tag maps to. */
|
|
122
|
+
export function guidelineForTags(tags) {
|
|
123
|
+
for (const tag of tags ?? []) {
|
|
124
|
+
const sc = tag.match(/^wcag(\d{3,4})$/)?.[1];
|
|
125
|
+
if (sc && scToGuideline[sc]) return guidelineByNum.get(scToGuideline[sc]);
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The whole draft as a review checklist. Every guideline is listed — the
|
|
132
|
+
* draft is too young for any of it to be silently skipped — shaped like the
|
|
133
|
+
* 2.2 manual-review entries so existing UIs render it unchanged: `level` has
|
|
134
|
+
* no meaning in the draft (no conformance levels yet) and `principle`
|
|
135
|
+
* carries the draft's functional group.
|
|
136
|
+
*/
|
|
137
|
+
export function manualReviewChecklist() {
|
|
138
|
+
return guidelines.map(({ num, name, group, url }) => ({
|
|
139
|
+
num,
|
|
140
|
+
name,
|
|
141
|
+
level: 'draft',
|
|
142
|
+
principle: group,
|
|
143
|
+
url,
|
|
144
|
+
automation: partlyAutomated.has(num) ? 'partial' : 'manual',
|
|
145
|
+
draft: true,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default { draftInfo, guidelines, scToGuideline, guidelineForTags, manualReviewChecklist };
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pour-engine",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Accessibility (WCAG 2.2) audit engine written from the W3C specifications \u2014 runs entirely in the browser, no dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "engine/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./engine/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"engine",
|
|
12
|
+
"config",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/pourdev/pour-engine.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://pour.dev",
|
|
21
|
+
"author": "David Yarham",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"accessibility",
|
|
25
|
+
"a11y",
|
|
26
|
+
"wcag",
|
|
27
|
+
"wcag22",
|
|
28
|
+
"audit",
|
|
29
|
+
"testing"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
}
|
|
34
|
+
}
|