pour-engine 1.31.1 → 1.31.3

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.31.1',
9
+ version: '1.31.3',
10
10
  },
11
11
  };
@@ -1,17 +1,19 @@
1
1
  // WCAG SC 1.3.5 Identify Input Purpose (Level AA)
2
2
  // WCAG 1.3.5: autocomplete tokens from the HTML spec's input-purposes list.
3
+ // Membership set, so order is free: kept a-z rather than mirroring the spec
4
+ // table's own sequence.
3
5
  const TOKENS = new Set([
4
- 'on', 'off', 'name', 'honorific-prefix', 'given-name', 'additional-name', 'family-name',
5
- 'honorific-suffix', 'nickname', 'organization-title', 'username', 'new-password',
6
- 'current-password', 'one-time-code', 'organization', 'street-address', 'address-line1',
7
- 'address-line2', 'address-line3', 'address-level1', 'address-level2', 'address-level3',
8
- 'address-level4', 'country', 'country-name', 'postal-code', 'cc-name', 'cc-given-name',
9
- 'cc-additional-name', 'cc-family-name', 'cc-number', 'cc-exp', 'cc-exp-month', 'cc-exp-year',
10
- 'cc-csc', 'cc-type', 'transaction-currency', 'transaction-amount', 'language', 'bday',
11
- 'bday-day', 'bday-month', 'bday-year', 'sex', 'url', 'photo', 'tel', 'tel-country-code',
12
- 'tel-national', 'tel-area-code', 'tel-local', 'tel-local-prefix', 'tel-local-suffix',
13
- 'tel-extension', 'email', 'impp', 'webauthn', 'shipping', 'billing', 'home', 'work',
14
- 'mobile', 'fax', 'pager',
6
+ 'additional-name', 'address-level1', 'address-level2', 'address-level3', 'address-level4',
7
+ 'address-line1', 'address-line2', 'address-line3', 'bday', 'bday-day', 'bday-month',
8
+ 'bday-year', 'billing', 'cc-additional-name', 'cc-csc', 'cc-exp', 'cc-exp-month',
9
+ 'cc-exp-year', 'cc-family-name', 'cc-given-name', 'cc-name', 'cc-number', 'cc-type',
10
+ 'country', 'country-name', 'current-password', 'email', 'family-name', 'fax', 'given-name',
11
+ 'home', 'honorific-prefix', 'honorific-suffix', 'impp', 'language', 'mobile', 'name',
12
+ 'new-password', 'nickname', 'off', 'on', 'one-time-code', 'organization',
13
+ 'organization-title', 'pager', 'photo', 'postal-code', 'sex', 'shipping', 'street-address',
14
+ 'tel', 'tel-area-code', 'tel-country-code', 'tel-extension', 'tel-local', 'tel-local-prefix',
15
+ 'tel-local-suffix', 'tel-national', 'transaction-amount', 'transaction-currency', 'url',
16
+ 'username', 'webauthn', 'work',
15
17
  ]);
16
18
 
17
19
  export default {
@@ -26,20 +26,31 @@ export default {
26
26
  if (ownText(parent).replace(/\s+/g, '').length < 10) return { status: 'pass' };
27
27
 
28
28
  const style = getComputedStyle(element);
29
- if ((style.textDecorationLine ?? style.textDecoration ?? '').includes('underline')) return { status: 'pass' };
30
- // Underline substitutes count as non-colour cues too: border-bottom
31
- // "underlines", box-shadow underlines, and background chips/pills all
32
- // mark the link by shape, not colour alone.
33
- if (parseFloat(style.borderBottomWidth) > 0 && style.borderBottomStyle !== 'none') return { status: 'pass' };
34
- if (style.boxShadow && style.boxShadow !== 'none') return { status: 'pass' };
35
- const ownBackground = parseColor(style.backgroundColor);
36
- if (ownBackground && ownBackground.a > 0) return { status: 'pass' };
37
- if (style.backgroundImage !== 'none') return { status: 'pass' }; // gradient/image underline technique
38
-
39
- // A clear weight difference is a non-colour distinction too.
40
29
  const parentStyle = getComputedStyle(parent);
41
30
  const weight = (s) => parseInt(s.fontWeight, 10) || 400;
42
- if (Math.abs(weight(style) - weight(parentStyle)) >= 300) return { status: 'pass' };
31
+ // A cue counts wherever it is painted: sites routinely reset
32
+ // text-decoration on the anchor and underline (or embolden) an inner
33
+ // span that carries the text, so every visible text-bearing element in
34
+ // the link vouches, not just the anchor. The >1px rect floor keeps
35
+ // 1×1 sr-only clip spans from vouching for a cue nobody can see.
36
+ const cueBearers = [style];
37
+ for (const el of element.querySelectorAll('*')) {
38
+ if (!el.textContent.trim()) continue;
39
+ if ([...el.getClientRects()].some((r) => r.width > 1 && r.height > 1)) cueBearers.push(getComputedStyle(el));
40
+ }
41
+ for (const s of cueBearers) {
42
+ if ((s.textDecorationLine ?? s.textDecoration ?? '').includes('underline')) return { status: 'pass' };
43
+ // Underline substitutes count as non-colour cues too: border-bottom
44
+ // "underlines", box-shadow underlines, and background chips/pills all
45
+ // mark the link by shape, not colour alone.
46
+ if (parseFloat(s.borderBottomWidth) > 0 && s.borderBottomStyle !== 'none') return { status: 'pass' };
47
+ if (s.boxShadow && s.boxShadow !== 'none') return { status: 'pass' };
48
+ const ownBackground = parseColor(s.backgroundColor);
49
+ if (ownBackground && ownBackground.a > 0) return { status: 'pass' };
50
+ if (s.backgroundImage !== 'none') return { status: 'pass' }; // gradient/image underline technique
51
+ // A clear weight difference is a non-colour distinction too.
52
+ if (Math.abs(weight(s) - weight(parentStyle)) >= 300) return { status: 'pass' };
53
+ }
43
54
 
44
55
  const linkColor = parseColor(style.color);
45
56
  const textColor = parseColor(parentStyle.color);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pour-engine",
3
- "version": "1.31.1",
3
+ "version": "1.31.3",
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",