pour-engine 1.16.0 → 1.17.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.
@@ -6,6 +6,6 @@
6
6
  export default {
7
7
  engine: {
8
8
  name: 'pour engine',
9
- version: '1.16.0',
9
+ version: '1.17.0',
10
10
  },
11
11
  };
@@ -52,7 +52,28 @@ export default {
52
52
  return !hasStrayText && effectiveChildren(child).every(isValidChild);
53
53
  };
54
54
  const invalid = effectiveChildren(element).filter((child) => !isValidChild(child));
55
- if (!invalid.length) return { status: 'pass' };
55
+ if (!invalid.length) {
56
+ // Every rendered item neutralised: role="none" on each <li> strips
57
+ // its listitem semantics while the <ul> keeps announcing "list" —
58
+ // Chromium exposes the list role over the none children (measured
59
+ // 2026-08-09), so readers hear a list that owns zero items around
60
+ // content they can plainly see. That is the same broken announcement
61
+ // as <ul><div>, reached via a different door. A PARTIAL
62
+ // neutralisation stays valid: hiding a decorative separator item
63
+ // from the count is what role="none" on an <li> is for.
64
+ const renderedItems = effectiveChildren(element).filter((child) =>
65
+ child.tagName === 'LI' && (!isRendered || isRendered(child)));
66
+ const neutralised = renderedItems.filter((child) =>
67
+ ['presentation', 'none'].includes(child.getAttribute('role') ?? ''));
68
+ if (renderedItems.length && neutralised.length === renderedItems.length) {
69
+ return {
70
+ status: 'fail',
71
+ message: `Every item in this list carries role="${neutralised[0].getAttribute('role')}", so the list announces itself with zero items while ${renderedItems.length} are visible — screen readers lose the count and the positions.`,
72
+ fix: 'Remove the role from the <li> elements, or neutralise the whole structure by putting role="presentation" (or the intended widget role) on the list element itself.',
73
+ };
74
+ }
75
+ return { status: 'pass' };
76
+ }
56
77
  const tags = [...new Set(invalid.map((child) => {
57
78
  const role = child.getAttribute('role');
58
79
  return `<${child.tagName.toLowerCase()}${role ? ` role="${role}"` : ''}>`;
@@ -7,7 +7,11 @@
7
7
  // naming counts. Toggle roles (checkbox/radio/switch) DO name from content.
8
8
  import { labelledByName } from '../../lib/accessible-name.js';
9
9
 
10
- const AUTHOR_ONLY = ['textbox', 'searchbox', 'combobox', 'listbox', 'spinbutton', 'slider'];
10
+ // progressbar and meter belong here too: ARIA 1.2 marks both roles
11
+ // "Accessible Name Required: True", they never name from content, and a
12
+ // nameless one announces a bare value — "25%, progress bar" — of nothing
13
+ // in particular (found live: unlabelled ant-progress widgets).
14
+ const AUTHOR_ONLY = ['textbox', 'searchbox', 'combobox', 'listbox', 'spinbutton', 'slider', 'progressbar', 'meter'];
11
15
  const FROM_CONTENT = ['checkbox', 'radio', 'switch'];
12
16
 
13
17
  function authorName(element) {
@@ -21,7 +25,7 @@ export default {
21
25
  id: 'aria-field-name',
22
26
  impact: 'serious',
23
27
  tags: ['wcag2a', 'wcag412'],
24
- help: 'ARIA form fields must have an accessible name',
28
+ help: 'ARIA fields and value widgets must have an accessible name',
25
29
  helpUrl: 'https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html',
26
30
  selector: [...AUTHOR_ONLY, ...FROM_CONTENT]
27
31
  .map((role) => `[role="${role}"]:not(input):not(select):not(textarea)`)
@@ -42,7 +46,7 @@ export default {
42
46
  }
43
47
  return {
44
48
  status: 'fail',
45
- message: `This role="${role}" field has no accessible name — screen readers announce the role with no idea what it's for.${
49
+ message: `This role="${role}" control has no accessible name — screen readers announce the role and value with no idea what it's for.${
46
50
  AUTHOR_ONLY.includes(role) ? ' (Content inside it does not count as a name for this role.)' : ''}`,
47
51
  fix: 'Add aria-label="…" or aria-labelledby pointing at its visible label.',
48
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pour-engine",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Accessibility (WCAG 2.2) audit engine written from the W3C specifications — runs entirely in the browser, no dependencies.",
5
5
  "type": "module",
6
6
  "main": "engine/index.js",