pour-engine 1.31.2 → 1.32.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
|
@@ -11,12 +11,28 @@ export default {
|
|
|
11
11
|
selector: 'dl:not([role])', // a role attribute replaces the dl semantics
|
|
12
12
|
evaluate(element) {
|
|
13
13
|
const invalid = [...element.children].filter((child) => !ALLOWED.has(child.tagName));
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
if (invalid.length) {
|
|
15
|
+
const tags = [...new Set(invalid.map((child) => `<${child.tagName.toLowerCase()}>`))].join(', ');
|
|
16
|
+
return {
|
|
17
|
+
status: 'fail',
|
|
18
|
+
message: `This <dl> contains ${tags} directly — only <dt>, <dd> (optionally grouped in <div>) are allowed, otherwise the term/description pairing breaks.`,
|
|
19
|
+
fix: 'Restructure the list into <dt>/<dd> pairs, or use a different element.',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// A <div> child is only a valid wrapper when it holds the dt/dd group
|
|
23
|
+
// ITSELF (HTML: each div contains one or more dt followed by one or
|
|
24
|
+
// more dd). Pairs buried another level down (dl > div > div > dt) fall
|
|
25
|
+
// out of the dl's content model, so the term/description association is
|
|
26
|
+
// no longer guaranteed to assistive technology.
|
|
27
|
+
const emptyWrappers = [...element.children].filter((child) =>
|
|
28
|
+
child.tagName === 'DIV' && ![...child.children].some((inner) => inner.tagName === 'DT' || inner.tagName === 'DD'));
|
|
29
|
+
if (emptyWrappers.length) {
|
|
30
|
+
return {
|
|
31
|
+
status: 'fail',
|
|
32
|
+
message: `${emptyWrappers.length} <div> wrapper(s) in this <dl> hold no <dt>/<dd> directly — the term/description pairing breaks when the pairs sit deeper than the wrapper.`,
|
|
33
|
+
fix: 'Make each <div> child of the <dl> contain its <dt>/<dd> pair directly, or flatten the pairs into the <dl> itself.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { status: 'pass' };
|
|
21
37
|
},
|
|
22
38
|
};
|
|
@@ -26,11 +26,13 @@ export default {
|
|
|
26
26
|
if (!isGrid || textCells < 4) return { status: 'pass' }; // likely layout or trivial
|
|
27
27
|
// Nested tables are the signature of legacy LAYOUT markup, not data —
|
|
28
28
|
// asserting "missing headers" there flags email-style scaffolding. A
|
|
29
|
-
// human can tell in a glance; the machine can't.
|
|
30
|
-
|
|
29
|
+
// human can tell in a glance; the machine can't. The signature reads in
|
|
30
|
+
// both directions: a table INSIDE another table (Hacker News's story
|
|
31
|
+
// list inside its page-scaffold table) is the same legacy layout.
|
|
32
|
+
if (!element.querySelector('th') && (element.querySelector('table') || element.parentElement?.closest('table'))) {
|
|
31
33
|
return {
|
|
32
34
|
status: 'incomplete',
|
|
33
|
-
message: 'This table has no header cells
|
|
35
|
+
message: 'This table has no header cells and is part of a nested-table structure — a layout-table signature. If it really presents data, mark its header cells with <th>; if it is layout scaffolding, add role="presentation".',
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -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
|
-
|
|
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