@surea11y/core 1.5.0 → 1.6.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.
Files changed (145) hide show
  1. package/CHANGELOG.md +193 -149
  2. package/README.md +27 -6
  3. package/docs/ACT_RULE_MAPPING.md +243 -0
  4. package/docs/API_STABILITY.md +2 -2
  5. package/docs/BINDING_AUTHORS_GUIDE.md +3 -3
  6. package/docs/DESIGN_CHALLENGES.md +301 -0
  7. package/docs/ENGINE_OPTIONS.md +16 -4
  8. package/docs/I18N.md +4 -4
  9. package/docs/INTEGRATION.md +1 -1
  10. package/docs/LIMITATIONS.md +6 -4
  11. package/docs/REPORT.md +1 -1
  12. package/docs/RULE_AUTHORING.md +53 -25
  13. package/docs/RULE_CATALOG.md +1878 -169
  14. package/docs/RULE_TAXONOMY.md +2 -2
  15. package/docs/TROUBLESHOOTING.md +2 -2
  16. package/docs/WCAG_CONFORMANCE.md +25 -9
  17. package/package.json +3 -7
  18. package/src/baseline.js +3 -3
  19. package/src/checks/automatic/area-alt-present.js +2 -2
  20. package/src/checks/automatic/aria-allowed-attr.js +68 -10
  21. package/src/checks/automatic/aria-allowed-role.js +2 -2
  22. package/src/checks/automatic/aria-braille-equivalent.js +3 -3
  23. package/src/checks/automatic/aria-conditional-attr.js +5 -5
  24. package/src/checks/automatic/aria-deprecated-role.js +1 -1
  25. package/src/checks/automatic/aria-hidden-body.js +2 -2
  26. package/src/checks/automatic/aria-hidden-focus.js +5 -5
  27. package/src/checks/automatic/aria-prohibited-attr.js +18 -18
  28. package/src/checks/automatic/aria-prohibited-children.js +130 -37
  29. package/src/checks/automatic/aria-required-attr.js +60 -12
  30. package/src/checks/automatic/aria-required-children.js +21 -14
  31. package/src/checks/automatic/aria-required-parent.js +61 -9
  32. package/src/checks/automatic/aria-role-name-present.js +36 -22
  33. package/src/checks/automatic/aria-valid-attr-value.js +15 -12
  34. package/src/checks/automatic/aria-valid-attr.js +1 -1
  35. package/src/checks/automatic/autocomplete-valid.js +2 -2
  36. package/src/checks/automatic/binary-control-name-present.js +27 -5
  37. package/src/checks/automatic/button-name-present.js +92 -6
  38. package/src/checks/automatic/combobox-name-present.js +26 -6
  39. package/src/checks/automatic/contrast-computable.js +32 -0
  40. package/src/checks/automatic/contrast-enhanced.js +21 -1
  41. package/src/checks/automatic/contrast-minimum.js +21 -1
  42. package/src/checks/automatic/css-orientation-lock.js +96 -19
  43. package/src/checks/automatic/definition-list-children-valid.js +7 -8
  44. package/src/checks/automatic/deprecated-elements-not-used.js +1 -1
  45. package/src/checks/automatic/dialog-name-present.js +20 -2
  46. package/src/checks/automatic/duplicate-id-aria.js +5 -3
  47. package/src/checks/automatic/duplicate-id.js +198 -0
  48. package/src/checks/automatic/embed-text-alternative-present.js +2 -2
  49. package/src/checks/automatic/form-control-programmatic-label-present.js +19 -2
  50. package/src/checks/automatic/form-control-single-label.js +1 -1
  51. package/src/checks/automatic/iframe-focusable-content.js +63 -7
  52. package/src/checks/automatic/iframe-name-present.js +37 -3
  53. package/src/checks/automatic/iframe-title-unique.js +1 -1
  54. package/src/checks/automatic/img-alt-present.js +12 -4
  55. package/src/checks/automatic/label-in-name.js +172 -18
  56. package/src/checks/automatic/link-in-text-block.js +10 -10
  57. package/src/checks/automatic/link-name-present.js +22 -1
  58. package/src/checks/automatic/list-children-valid.js +6 -6
  59. package/src/checks/automatic/listbox-name-present.js +28 -8
  60. package/src/checks/automatic/listitem-parent-valid.js +4 -4
  61. package/src/checks/automatic/menuitem-name-present.js +20 -2
  62. package/src/checks/automatic/meta-refresh-no-exceptions.js +25 -14
  63. package/src/checks/automatic/meta-refresh-timing-absent.js +14 -7
  64. package/src/checks/automatic/meter-name-present.js +23 -4
  65. package/src/checks/automatic/nested-interactive-controls-absent.js +5 -5
  66. package/src/checks/automatic/option-name-present.js +23 -4
  67. package/src/checks/automatic/page-title-present.js +21 -3
  68. package/src/checks/automatic/presentational-children-focusable-absent.js +330 -0
  69. package/src/checks/automatic/progressbar-name-present.js +23 -4
  70. package/src/checks/automatic/role-img-alt-present.js +64 -16
  71. package/src/checks/automatic/searchbox-name-present.js +28 -8
  72. package/src/checks/automatic/server-side-image-map-absent.js +1 -1
  73. package/src/checks/automatic/slider-name-present.js +27 -6
  74. package/src/checks/automatic/spinbutton-name-present.js +28 -8
  75. package/src/checks/automatic/summary-name-present.js +18 -2
  76. package/src/checks/automatic/svg-image-text-alternative-present.js +1 -1
  77. package/src/checks/automatic/svg-text-alternative-present.js +13 -10
  78. package/src/checks/automatic/tab-name-present.js +21 -2
  79. package/src/checks/automatic/table-headers-attr-valid.js +43 -8
  80. package/src/checks/automatic/table-th-has-data-cells.js +61 -5
  81. package/src/checks/automatic/target-size-minimum.js +71 -53
  82. package/src/checks/automatic/td-has-header.js +5 -5
  83. package/src/checks/automatic/textbox-name-present.js +28 -8
  84. package/src/checks/automatic/tooltip-name-present.js +21 -2
  85. package/src/checks/automatic/treeitem-name-present.js +23 -4
  86. package/src/checks/automatic/valid-lang.js +92 -7
  87. package/src/checks/automatic/video-poster-text-alternative-present.js +1 -1
  88. package/src/checks/manual/accesskeys-manual.js +3 -3
  89. package/src/checks/manual/area-alt-decorative-manual.js +7 -0
  90. package/src/checks/manual/area-alt-quality-manual.js +6 -0
  91. package/src/checks/manual/aria-checked-state-mismatch-manual.js +5 -5
  92. package/src/checks/manual/aria-text-manual.js +4 -4
  93. package/src/checks/manual/bypass-blocks-present-manual.js +44 -26
  94. package/src/checks/manual/canvas-text-alternative-quality-manual.js +8 -0
  95. package/src/checks/manual/css-focus-indicator-suppressed-manual.js +444 -0
  96. package/src/checks/manual/embed-text-alternative-quality-manual.js +8 -0
  97. package/src/checks/manual/empty-heading-manual.js +58 -11
  98. package/src/checks/manual/empty-table-header-manual.js +8 -8
  99. package/src/checks/manual/focus-order-semantics-manual.js +15 -15
  100. package/src/checks/manual/form-control-label-quality-manual.js +453 -0
  101. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -2
  102. package/src/checks/manual/heading-order-manual.js +3 -3
  103. package/src/checks/manual/heading-quality-manual.js +338 -0
  104. package/src/checks/manual/identical-links-same-purpose-manual.js +73 -12
  105. package/src/checks/manual/image-redundant-alt-manual.js +4 -4
  106. package/src/checks/manual/img-alt-decorative-manual.js +211 -52
  107. package/src/checks/manual/img-alt-quality-manual.js +7 -0
  108. package/src/checks/manual/input-image-alt-decorative-manual.js +8 -0
  109. package/src/checks/manual/input-image-alt-quality-manual.js +5 -0
  110. package/src/checks/manual/label-title-only-manual.js +4 -4
  111. package/src/checks/manual/landmark-banner-is-top-level-manual.js +7 -7
  112. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +9 -9
  113. package/src/checks/manual/landmark-main-is-top-level-manual.js +6 -6
  114. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +6 -6
  115. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +6 -6
  116. package/src/checks/manual/landmark-no-duplicate-main-manual.js +2 -2
  117. package/src/checks/manual/landmark-one-main-manual.js +6 -6
  118. package/src/checks/manual/landmark-unique-manual.js +9 -9
  119. package/src/checks/manual/link-name-quality-manual.js +161 -32
  120. package/src/checks/manual/media-transcript-present-manual.js +2 -3
  121. package/src/checks/manual/meta-viewport-large-manual.js +2 -2
  122. package/src/checks/manual/mouse-only-event-handlers-manual.js +9 -9
  123. package/src/checks/manual/no-autoplay-audio-manual.js +3 -3
  124. package/src/checks/manual/object-text-alternative-quality-manual.js +8 -0
  125. package/src/checks/manual/p-as-heading-manual.js +4 -4
  126. package/src/checks/manual/page-has-heading-one-manual.js +6 -6
  127. package/src/checks/manual/page-title-patterns-manual.js +26 -3
  128. package/src/checks/manual/presentation-role-conflict-manual.js +55 -25
  129. package/src/checks/manual/region-manual.js +19 -19
  130. package/src/checks/manual/scope-attr-valid-manual.js +2 -2
  131. package/src/checks/manual/scrollable-region-focusable-manual.js +7 -7
  132. package/src/checks/manual/skip-link-manual.js +5 -5
  133. package/src/checks/manual/svg-text-alternative-quality-manual.js +9 -0
  134. package/src/checks/manual/tabindex-manual.js +2 -2
  135. package/src/checks/manual/table-duplicate-name-manual.js +2 -2
  136. package/src/checks/manual/table-fake-caption-manual.js +2 -2
  137. package/src/checks/manual/video-caption-manual.js +3 -3
  138. package/src/checks/manual-review.js +17 -1
  139. package/src/core.js +8965 -1647
  140. package/src/report.js +2 -2
  141. package/surea11y.browser.js +3768 -611
  142. package/surea11y.i18n.de.js +1 -1
  143. package/surea11y.i18n.es.js +1 -1
  144. package/surea11y.i18n.fr.js +1 -1
  145. package/bin/surea11y-core.js +0 -20
@@ -52,7 +52,16 @@ function runInPage(ctx) { /* see Runtime Contract */ }
52
52
  module.exports = { id, meta, runInPage };
53
53
  ```
54
54
 
55
- No other exports.
55
+ One optional fourth export: `applicability(ctx)`, a predicate the engine calls before
56
+ `runInPage` to decide whether the rule is in scope for this run at all. Fourteen rules
57
+ use it today (see §11.2). Export it alongside the other three when you need it:
58
+
59
+ ```js
60
+ module.exports = { id, meta, runInPage, applicability };
61
+ ```
62
+
63
+ Nothing else. `npm run validate:rules` enforces exactly this set, and rejects a fifth
64
+ export.
56
65
 
57
66
  ---
58
67
 
@@ -78,7 +87,7 @@ Examples observed:
78
87
 
79
88
  ## 4) Meta Contract (all keys used by current rules)
80
89
 
81
- Every rule defines a `meta` object. In the rule set you uploaded, the union of meta keys is:
90
+ Every rule defines a `meta` object. Across the shipped ruleset, the union of meta keys is:
82
91
 
83
92
  ### 4.1 Required top-level keys
84
93
 
@@ -177,8 +186,7 @@ occurrences.push(helpers.reportOccurrence(el, { summary: '…', hint: '…' }));
177
186
  ```
178
187
 
179
188
  It attaches the element for the engine to finalize, which is how `selector`,
180
- `html` and `structuralPath` get filled in centrally instead of in each of the
181
- 124 rules.
189
+ `html` and `structuralPath` get filled in centrally instead of in each rule.
182
190
 
183
191
  **This is a performance contract, not just a convenience.** Every occurrence
184
192
  gets a `structuralPath`. Given the element, the engine computes it directly.
@@ -268,15 +276,19 @@ const nodes = helpers.queryAllSmart
268
276
  : helpers.queryAll('img');
269
277
  ```
270
278
 
271
- Shadow traversal is opt-in via engine option:
279
+ Shadow traversal is on by default. It is the caller who opts out:
272
280
  ```js
273
- engineOptions: { includeShadowDom: true }
281
+ engineOptions: { includeShadowDom: false } // light DOM only
274
282
  ```
283
+ So write the rule assuming open shadow roots are in scope; `queryAllSmart` honours the
284
+ caller's choice for you. Closed roots are unreachable either way.
275
285
 
276
286
  ### 6.2 Reporting note for Shadow DOM
277
287
 
278
- Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate nodes in Shadow DOM.
279
- Therefore: **always include `html` in occurrences**.
288
+ Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate a node
289
+ inside a shadow root — which is why `html` matters as the "which element" signal there.
290
+ You get both for free by reporting the element through `helpers.reportOccurrence` (§4.3);
291
+ there is nothing extra to do for shadow DOM specifically.
280
292
 
281
293
  ---
282
294
 
@@ -290,7 +302,8 @@ data: {
290
302
  }
291
303
  ```
292
304
 
293
- This is consistent across your uploaded rule family.
305
+ Pass the `eligInfo` you already computed for the element; the fallback object above is
306
+ for the case where a rule has none to give.
294
307
 
295
308
  ---
296
309
 
@@ -319,34 +332,48 @@ Manual:
319
332
 
320
333
  ---
321
334
 
322
- ## 9) Occurrence object shape (repo reality)
335
+ ## 9) Occurrence object shape
323
336
 
324
- Typical pattern:
337
+ Report the element and let the engine finish the object (§4.3):
325
338
 
326
339
  ```js
327
- occurrences.push({
328
- selector,
329
- html,
330
- summary: '…',
331
- hint: '…',
332
- i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
333
- data: { visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] } }
334
- });
340
+ occurrences.push(
341
+ helpers.reportOccurrence(el, {
342
+ summary: '…',
343
+ hint: '…',
344
+ i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
345
+ data: { visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] } }
346
+ })
347
+ );
335
348
  ```
336
349
 
337
- Observed properties:
338
- - `selector` (or sometimes `selectorStr`)
339
- - `html`
350
+ What a rule supplies:
340
351
  - `summary`
341
352
  - `hint`
342
353
  - `i18n` (`summaryKey`, `hintKey`, `params`)
343
354
  - `data` (includes `visibilityFilter`)
344
355
 
356
+ What the engine fills in from the reported element:
357
+ - `selector`
358
+ - `html`
359
+ - `structuralPath`
360
+
361
+ Setting `selector`/`html` yourself still works and still wins — a handful of rules whose
362
+ finding is not a single element (the contrast rules report text runs) do exactly that. It
363
+ is the exception, not the pattern to copy.
364
+
345
365
  ---
346
366
 
347
367
  ## 10) Structured doc comment block
348
368
 
349
- Keep the structured header comment (`@rule`, `@atomic`, `@summary`, `@standard`, `@sc`, `@applicability`, `@expectation`).
369
+ Keep the structured header comment (`@check`, `@atomic`, `@summary`, `@standard`, `@sc`,
370
+ `@applicability`, `@expectation`). The id goes on `@check` — `@rule` is not a tag this
371
+ repo uses. `docs/RULE_TEMPLATE.js` has the full block to copy.
372
+
373
+ `@applicability` and `@expectation` are consumer-facing: `scripts/generate-rule-catalog.js`
374
+ reads them straight from the source and publishes them per rule in
375
+ [`RULE_CATALOG.md`](./RULE_CATALOG.md#rule-reference). Write them for someone deciding
376
+ whether a result applies to their page, and rerun `npm run docs:rule-catalog` after editing them.
350
377
 
351
378
  ---
352
379
 
@@ -452,8 +479,9 @@ After adding or changing any fixture, regenerate the index:
452
479
  npm run fixtures:index
453
480
  ```
454
481
 
455
- This writes `tests/fixtures/INDEX.md` (human-readable) and `tests/fixtures/index.json`
482
+ This writes `tests/fixtures/INDEX.md` (human-readable), `tests/fixtures/index.json`
456
483
  (machine-readable — every rule, its fixture path, and parsed pass/fail/cantTell case
457
- counts, for external tooling to enumerate and load fixtures directly). Commit both
484
+ counts, for external tooling to enumerate and load fixtures directly) and
485
+ `tests/fixtures/index.html` (the same listing as a browsable page). Commit all three
458
486
  alongside the fixture and test changes. A rule shipped without its fixture is treated
459
487
  the same as a rule shipped without tests — not done.