pour-engine 1.38.0 → 1.41.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 +1 -1
- package/engine/AUDIT.html +24 -690
- package/engine/COVERAGE.md +4 -2
- package/engine/README.md +9 -4
- package/engine/index.js +34 -8
- package/engine/lib/accessible-name.js +81 -64
- package/engine/lib/contrast.js +111 -10
- package/engine/lib/dom.js +78 -18
- package/engine/lib/roles.js +18 -3
- package/engine/rules/best-practice/fieldset-legend.js +11 -5
- package/engine/rules/best-practice/landmark-unique.js +9 -1
- package/engine/rules/best-practice/region.js +10 -2
- package/engine/rules/wcag/1.1.1-area-alt.js +6 -1
- package/engine/rules/wcag/1.1.1-image-alt.js +2 -1
- package/engine/rules/wcag/1.1.1-svg-img-alt.js +14 -17
- package/engine/rules/wcag/1.3.1-aria-required-children.js +20 -8
- package/engine/rules/wcag/1.3.1-aria-required-parent.js +7 -10
- package/engine/rules/wcag/1.3.1-definition-list.js +42 -3
- package/engine/rules/wcag/1.3.1-form-label.js +12 -29
- package/engine/rules/wcag/1.3.1-list-structure.js +4 -3
- package/engine/rules/wcag/1.3.1-table-headers.js +23 -2
- package/engine/rules/wcag/1.3.4-orientation.js +117 -48
- package/engine/rules/wcag/1.3.5-autocomplete-valid.js +21 -8
- package/engine/rules/wcag/1.4.1-link-in-text-block.js +38 -8
- package/engine/rules/wcag/1.4.10-reflow.js +3 -3
- package/engine/rules/wcag/1.4.11-non-text-contrast.js +9 -1
- package/engine/rules/wcag/1.4.12-text-spacing.js +15 -2
- package/engine/rules/wcag/1.4.3-color-contrast.js +79 -7
- package/engine/rules/wcag/1.4.3-control-contrast.js +35 -6
- package/engine/rules/wcag/1.4.4-meta-viewport.js +7 -2
- package/engine/rules/wcag/2.1.1-scrollable-region-focusable.js +5 -5
- package/engine/rules/wcag/2.2.1-meta-refresh.js +39 -3
- package/engine/rules/wcag/2.2.2-pause-stop-hide.js +5 -5
- package/engine/rules/wcag/2.4.1-bypass-blocks.js +2 -2
- package/engine/rules/wcag/2.4.2-document-title.js +7 -8
- package/engine/rules/wcag/2.4.4-link-text-generic.js +6 -5
- package/engine/rules/wcag/2.4.9-link-text-generic-only.js +8 -23
- package/engine/rules/wcag/2.5.8-target-size.js +201 -47
- package/engine/rules/wcag/3.3.8-auth-field-obstruction.js +6 -12
- package/engine/rules/wcag/4.1.2-aria-attr-valid.js +4 -0
- package/engine/rules/wcag/4.1.2-aria-field-name.js +6 -5
- package/engine/rules/wcag/4.1.2-aria-valid-refs.js +8 -3
- package/engine/rules/wcag/4.1.2-button-name.js +1 -1
- package/engine/rules/wcag/4.1.2-composite-widget-name.js +1 -1
- package/engine/rules/wcag/4.1.2-invoker-target.js +2 -1
- package/engine/rules/wcag/4.1.2-label-for-valid.js +2 -2
- package/engine/rules/wcag/4.1.2-nested-interactive.js +20 -8
- package/engine/rules/wcag/4.1.2-valid-role.js +6 -1
- package/engine/wcag22.js +7 -7
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// WCAG SC 4.1.2 Name, Role, Value (Level A)
|
|
2
|
+
import { isInert } from '../../lib/dom.js';
|
|
2
3
|
// Deliberately excludes bare [tabindex]: a focusable container (e.g. a
|
|
3
4
|
// scrollable region with tabindex="0") holding links is correct markup.
|
|
4
5
|
const INTERACTIVE =
|
|
@@ -16,8 +17,7 @@ export default {
|
|
|
16
17
|
selector: INTERACTIVE,
|
|
17
18
|
evaluate(element, { isRendered }) {
|
|
18
19
|
// Only descendants a user can actually reach count: hidden inputs,
|
|
19
|
-
// disabled controls
|
|
20
|
-
// inside a control are inert markup, not competing tab stops.
|
|
20
|
+
// disabled controls and display:none templates are excluded.
|
|
21
21
|
// Two more shapes are unreachable by the same principle: aria-hidden
|
|
22
22
|
// content (AT ignores it wholesale — a decorative svg role="button"
|
|
23
23
|
// aria-hidden="true" inside a play button competes with nothing; if it
|
|
@@ -26,16 +26,28 @@ export default {
|
|
|
26
26
|
// role="button" with no tabindex is not in the tab order — keyboard
|
|
27
27
|
// users cannot reach it, so it cannot be a competing stop).
|
|
28
28
|
const NATIVE = 'a[href], button, input, select, textarea, summary, audio[controls], video[controls]';
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const nested = [...element.querySelectorAll(INTERACTIVE)].find((el) =>
|
|
34
|
-
!el.matches(':disabled') && !(el.hasAttribute('tabindex') && el.tabIndex < 0)
|
|
29
|
+
// Negative tabindex removes sequential focus navigation, not focusability.
|
|
30
|
+
// https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
|
|
31
|
+
const candidates = [...element.querySelectorAll(INTERACTIVE)].filter((el) =>
|
|
32
|
+
!el.matches(':disabled') && !isInert(el)
|
|
35
33
|
&& !(el.tagName === 'INPUT' && el.type === 'hidden') && isRendered(el)
|
|
36
34
|
&& !el.closest('[aria-hidden="true"]')
|
|
37
35
|
&& (el.matches(NATIVE) || el.hasAttribute('tabindex')));
|
|
36
|
+
// A child requiring review must not conceal a later definite finding.
|
|
37
|
+
const nested = candidates.find((el) => !(el.hasAttribute('tabindex') && el.tabIndex < 0)) ?? candidates[0];
|
|
38
38
|
if (!nested) return { status: 'pass' };
|
|
39
|
+
// A programmatically focusable child must not silently pass. In an
|
|
40
|
+
// ARIA widget its name and role can survive presentational-descendant
|
|
41
|
+
// processing, so nesting alone does not prove a 4.1.2 failure.
|
|
42
|
+
// Native links/buttons separately forbid interactive descendants.
|
|
43
|
+
// https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
|
|
44
|
+
if (nested.hasAttribute('tabindex') && nested.tabIndex < 0
|
|
45
|
+
&& !element.matches('a[href], button')) {
|
|
46
|
+
return {
|
|
47
|
+
status: 'incomplete',
|
|
48
|
+
message: `This control contains an element (<${nested.tagName.toLowerCase()}>) with a negative tabindex. It can still receive focus. Check that both controls expose the intended name and role, and that focusing and activating the child works correctly.`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
39
51
|
// <summary> is the one outer control HTML does not forbid this on: its
|
|
40
52
|
// content model is phrasing content with no "no interactive content
|
|
41
53
|
// descendant" clause (a and button both carry one), it maps to no ARIA
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// WCAG SC 4.1.2 Name, Role, Value (Level A)
|
|
2
2
|
import { attributesOf } from '../../lib/dom.js';
|
|
3
|
+
import { implicitRole } from '../../lib/roles.js';
|
|
3
4
|
// Concrete, usable roles — abstract roles are invalid in markup.
|
|
4
5
|
export const VALID_ROLES = new Set([
|
|
5
6
|
'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button',
|
|
@@ -108,7 +109,11 @@ export default {
|
|
|
108
109
|
// this is a question, not an assertion. Focusable or state-bearing
|
|
109
110
|
// elements keep failing: a focusable element left generic, or a state
|
|
110
111
|
// riding on a role the browser threw away, is the real 4.1.2 harm.
|
|
111
|
-
|
|
112
|
+
// An invalid role does not erase a native control's semantics. A
|
|
113
|
+
// misspelling on a button is a review question; its actual missing
|
|
114
|
+
// name or state, if any, belongs to the corresponding semantic rule.
|
|
115
|
+
const nativeRole = implicitRole(element);
|
|
116
|
+
if ((!focusable && !hasAriaProps) || (nativeRole && nativeRole !== 'generic')) {
|
|
112
117
|
return {
|
|
113
118
|
status: 'incomplete',
|
|
114
119
|
message: `role="${element.getAttribute('role')}" is not a valid ARIA role${hint}, so assistive technology ignores it and exposes the element's native <${tag}> semantics. Nothing is announced wrongly, but the author reached for a role that does not exist. Is the native <${tag}> role the right one here? If a different role was intended, that role is missing.`,
|
package/engine/wcag22.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// `automation` is an honest assessment:
|
|
6
6
|
// 'auto' — rules can meaningfully test this
|
|
7
|
-
// 'partial' — rules
|
|
8
|
-
// 'manual' —
|
|
7
|
+
// 'partial' — rules check aspects or nominate review candidates; humans still review
|
|
8
|
+
// 'manual' — no active rule; assessed through the human checklist
|
|
9
9
|
// The coverage report (scripts/wcag-coverage.js → src/engine/COVERAGE.md) joins
|
|
10
10
|
// this catalog with the rule registry to show real coverage.
|
|
11
11
|
|
|
@@ -37,11 +37,11 @@ export default [
|
|
|
37
37
|
sc('1.3.1', 'Info and Relationships', 'A', 'partial'),
|
|
38
38
|
sc('1.3.2', 'Meaningful Sequence', 'A', 'partial'), // partial: CSS-reorder divergence is measurable; whether meaning survives is the reader's call
|
|
39
39
|
sc('1.3.3', 'Sensory Characteristics', 'A', 'manual'),
|
|
40
|
-
sc('1.3.4', 'Orientation', 'AA', 'partial', '2.1'), // orientation-lock
|
|
41
|
-
sc('1.3.5', 'Identify Input Purpose', 'AA', 'partial', '2.1'), // partial: wrong tokens are
|
|
40
|
+
sc('1.3.4', 'Orientation', 'AA', 'partial', '2.1'), // orientation-lock asserts CSS root hides/rotations as ACT b33eff does, essential exception assumed absent; script locks stay a human check
|
|
41
|
+
sc('1.3.5', 'Identify Input Purpose', 'AA', 'partial', '2.1'), // partial: wrong tokens are asserted as ACT 73f2c2 does (search boxes asked); MISSING autocomplete on identity fields needs judgment
|
|
42
42
|
sc('1.3.6', 'Identify Purpose', 'AAA', 'manual', '2.1'),
|
|
43
43
|
sc('1.4.1', 'Use of Color', 'A', 'partial'), // link-in-text-block automates the link case
|
|
44
|
-
sc('1.4.2', 'Audio Control', 'A', 'partial'), //
|
|
44
|
+
sc('1.4.2', 'Audio Control', 'A', 'partial'), // declarative autoplay identifies candidates; audibility and alternative controls need review
|
|
45
45
|
sc('1.4.3', 'Contrast (Minimum)', 'AA', 'partial'),
|
|
46
46
|
sc('1.4.4', 'Resize Text', 'AA', 'partial'),
|
|
47
47
|
sc('1.4.5', 'Images of Text', 'AA', 'manual'),
|
|
@@ -75,9 +75,9 @@ export default [
|
|
|
75
75
|
sc('2.4.6', 'Headings and Labels', 'AA', 'manual'),
|
|
76
76
|
sc('2.4.7', 'Focus Visible', 'AA', 'partial'), // partial: focus-visible flags outline suppression; the indicator itself needs eyes
|
|
77
77
|
sc('2.4.8', 'Location', 'AAA', 'manual'),
|
|
78
|
-
sc('2.4.9', 'Link Purpose (Link Only)', 'AAA', 'partial'), //
|
|
78
|
+
sc('2.4.9', 'Link Purpose (Link Only)', 'AAA', 'partial'), // generic wording nominates candidates; whether wording explains the destination needs a reader
|
|
79
79
|
sc('2.4.10', 'Section Headings', 'AAA', 'manual'),
|
|
80
|
-
sc('2.4.11', 'Focus Not Obscured (Minimum)', 'AA', 'partial', '2.2'), //
|
|
80
|
+
sc('2.4.11', 'Focus Not Obscured (Minimum)', 'AA', 'partial', '2.2'), // covered resting targets are candidates; actual focus/scroll behavior needs review
|
|
81
81
|
sc('2.4.12', 'Focus Not Obscured (Enhanced)', 'AAA', 'manual', '2.2'),
|
|
82
82
|
sc('2.4.13', 'Focus Appearance', 'AAA', 'manual', '2.2'),
|
|
83
83
|
sc('2.5.1', 'Pointer Gestures', 'A', 'manual', '2.1'),
|
package/package.json
CHANGED