pour-engine 1.44.1 → 1.44.2
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
|
@@ -10,6 +10,40 @@
|
|
|
10
10
|
// Keep this in step with 3.1.2-valid-lang-parts.js.
|
|
11
11
|
const LANG_PATTERN = /^([a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*|[xXiI](-[a-zA-Z0-9]{1,8})+)$/;
|
|
12
12
|
|
|
13
|
+
/** Elements that hold no words and draw nothing. */
|
|
14
|
+
const WORDLESS = 'script, style, template, link, meta, base';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Does this document hold nothing a language could apply to? 3.1.1 asks
|
|
18
|
+
* that "the default human language of each Web page can be programmatically
|
|
19
|
+
* determined", and a document with no words has no human language to
|
|
20
|
+
* determine: the placeholder about:blank frame a page parks for later, a
|
|
21
|
+
* frame a script has not written into yet. Its html element has no lang
|
|
22
|
+
* because it has no anything, and the frame's real fault, where it has one,
|
|
23
|
+
* is its missing title (4.1.2, frame-title). 3.1.2 draws the same line:
|
|
24
|
+
* a lang can only fail through words it governs.
|
|
25
|
+
*
|
|
26
|
+
* Blank is kept narrow so a doubtful document keeps its failure: no title
|
|
27
|
+
* text (a title is spoken), no text in the body, and no element in the
|
|
28
|
+
* body beyond scripts and metadata. Any other element counts as
|
|
29
|
+
* content, since an image, a control, a nested frame or a custom element
|
|
30
|
+
* can all carry words this check does not look for. An application shell
|
|
31
|
+
* waiting on its script (<div id="root"></div>) is therefore not blank.
|
|
32
|
+
*/
|
|
33
|
+
function blankDocument(element) {
|
|
34
|
+
const doc = element.ownerDocument;
|
|
35
|
+
if (doc.title?.trim()) return false;
|
|
36
|
+
const body = doc.body;
|
|
37
|
+
if (!body) return true;
|
|
38
|
+
// Direct children only: once every element child is wordless, the one
|
|
39
|
+
// place left for words is a text node of the body itself, and a
|
|
40
|
+
// script's source is not words.
|
|
41
|
+
return [...body.childNodes].every((node) => {
|
|
42
|
+
if (node.nodeType === 3) return !node.data.trim();
|
|
43
|
+
return node.nodeType !== 1 || node.matches(WORDLESS);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
export default {
|
|
14
48
|
id: 'html-lang',
|
|
15
49
|
name: 'Page language',
|
|
@@ -20,6 +54,7 @@ export default {
|
|
|
20
54
|
selector: 'html',
|
|
21
55
|
visibleOnly: false,
|
|
22
56
|
evaluate(element) {
|
|
57
|
+
if (blankDocument(element)) return { status: 'pass' };
|
|
23
58
|
const lang = element.getAttribute('lang')?.trim();
|
|
24
59
|
if (!element.hasAttribute('lang')) {
|
|
25
60
|
return {
|
|
@@ -46,6 +46,27 @@ const HANDLED_ELSEWHERE = new Set(['label', 'labelledby']);
|
|
|
46
46
|
// Those are aria-state-unreachable's, which imports this.
|
|
47
47
|
export const NATIVE_STATE = { readonly: 'readonly', required: 'required', disabled: 'disabled', checked: 'checked' };
|
|
48
48
|
|
|
49
|
+
// What the author most likely meant, for the attributes that turn up most
|
|
50
|
+
// on real pages, so the fix names the repair and not just the fault. Each
|
|
51
|
+
// says where ARIA 1.2 puts the attribute (its "Used in Roles" list) and what
|
|
52
|
+
// serves the same purpose on this role. An attribute with no entry gets the
|
|
53
|
+
// general advice. Added 2026-09-21 after inclusion.hsbc.com marked the
|
|
54
|
+
// chosen language with aria-selected on a link, where "move it to the
|
|
55
|
+
// control that toggles this one" pointed nowhere: the repair there is
|
|
56
|
+
// aria-current.
|
|
57
|
+
const HINT = {
|
|
58
|
+
selected: (role) => (role === 'cell'
|
|
59
|
+
? 'A table whose cells can be selected is a grid: aria-selected needs role="grid" on the table, which makes its cells gridcells.'
|
|
60
|
+
: `aria-selected belongs to option, tab, row and gridcell. To mark the current item in a set of ${role === 'link' ? 'links' : 'items'}, such as the current page, language or step, use aria-current, which is global and allowed on this role.`),
|
|
61
|
+
expanded: (role) => (role === 'textbox' || role === 'searchbox'
|
|
62
|
+
? 'A text field that opens a list of suggestions is a combobox: aria-expanded needs role="combobox", which ARIA in HTML allows on a text input, with aria-controls pointing at the list.'
|
|
63
|
+
: 'aria-expanded belongs on the control that opens and closes the content, a button in most cases, and not on the content or its container. Put it on that control; if this element is itself what the user presses, it should be a button.'),
|
|
64
|
+
checked: (role) => (role === 'button'
|
|
65
|
+
? 'A button that stays on or off takes aria-pressed, not aria-checked.'
|
|
66
|
+
: 'aria-checked belongs to checkbox, radio, switch, option and the checkable menu items. For something the user ticks, use a native checkbox or radio input.'),
|
|
67
|
+
pressed: () => 'aria-pressed belongs to button alone. If this element switches something on and off, make it a button element.',
|
|
68
|
+
};
|
|
69
|
+
|
|
49
70
|
/** The element's role and the non-global aria- attributes (names without
|
|
50
71
|
* the prefix) that role does not support, or null when there are none or
|
|
51
72
|
* the role is not modelled. Shared with aria-state-unreachable. */
|
|
@@ -66,6 +87,15 @@ export function unsupportedAria(element) {
|
|
|
66
87
|
return names.length ? { role, names } : null;
|
|
67
88
|
}
|
|
68
89
|
|
|
90
|
+
/** The repair for each unsupported attribute: its own hint where there is
|
|
91
|
+
* one, the general advice for the rest. */
|
|
92
|
+
function repair(attrs, role) {
|
|
93
|
+
const hinted = attrs.filter((name) => HINT[name]).map((name) => HINT[name](role));
|
|
94
|
+
const rest = attrs.filter((name) => !HINT[name]).map((name) => `aria-${name}`);
|
|
95
|
+
if (rest.length) hinted.push(`Move ${rest.join('/')} to the element whose role supports it.`);
|
|
96
|
+
return hinted.join(' ');
|
|
97
|
+
}
|
|
98
|
+
|
|
69
99
|
export default {
|
|
70
100
|
id: 'aria-allowed-attr',
|
|
71
101
|
name: 'Allowed ARIA attributes',
|
|
@@ -86,7 +116,7 @@ export default {
|
|
|
86
116
|
return {
|
|
87
117
|
status: 'fail',
|
|
88
118
|
message: `${names.join(', ')} is not supported on role "${role}" (ARIA 1.2 §8.6), so user agents drop it and assistive technology never sees it. Nothing is announced wrongly, but nothing is announced at all. If the element really has that state, it is invisible to a screen reader; if it does not, the attribute is stray and no one is affected, but ARIA 1.2 still says authors MUST NOT write it.`,
|
|
89
|
-
fix:
|
|
119
|
+
fix: `${repair(disallowed, role)} If the element has no such state, remove the attribute. Adding a role to this element to make the attribute legal is rarely right: the host language restricts which roles each element may take.`,
|
|
90
120
|
};
|
|
91
121
|
},
|
|
92
122
|
};
|
package/package.json
CHANGED