pour-engine 1.18.0 → 1.19.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.18.0',
9
+ version: '1.19.0',
10
10
  },
11
11
  };
@@ -108,9 +108,14 @@ function reachableRects(element, rect) {
108
108
  }
109
109
  }
110
110
  // Nothing could be probed at all (entirely out of viewport,
111
- // pointer-events: none, nothing laid out): don't know, and don't-know must
112
- // not silence a finding, so the whole rect stands.
113
- if (!testable) return [rect];
111
+ // pointer-events: none, nothing laid out): the OCCLUSION question is
112
+ // unanswerable, and don't-know must not silence a finding — but the
113
+ // fragment GEOMETRY is measurable regardless, so the fragments stand,
114
+ // never the bounding box. A wrapped link's box is mostly leading and
115
+ // line-gap no pointer can activate; counting that empty space as
116
+ // reachable made an offscreen two-line neighbour crowd a small target
117
+ // its painted lines never came near (ja.wikipedia's word list).
118
+ if (!testable) return probes;
114
119
  return reachable;
115
120
  }
116
121
 
@@ -212,6 +217,17 @@ export function createTargetSizeRule({ id, tags, help, helpUrl, min, spacingExce
212
217
  // link cards, overlay + inner button) — not two targets crowding.
213
218
  const encloses = (a, b) =>
214
219
  a.left <= b.left && a.right >= b.right && a.top <= b.top && a.bottom >= b.bottom;
220
+ // Enclosure proved on the element's PAINT: true only when a single
221
+ // painted fragment contains the other rect. For unwrapped elements the
222
+ // one fragment is the box, so this is `encloses` exactly; a wrapped
223
+ // element's fragments are each line it actually paints, and a box that
224
+ // merely spans them encloses nothing. The box test runs first as a
225
+ // cheap superset — no fragment can enclose what the box doesn't.
226
+ const paintedEncloses = (target, targetBox, innerBox) => {
227
+ if (!encloses(targetBox, innerBox)) return false;
228
+ const fragments = [...target.getClientRects()].filter((f) => f.width > 0 && f.height > 0);
229
+ return fragments.length <= 1 || fragments.some((f) => encloses(f, innerBox));
230
+ };
215
231
 
216
232
  // ── Obscured targets ──────────────────────────────────────────────
217
233
  // The criterion measures the region that ACCEPTS the pointer action.
@@ -399,7 +415,15 @@ export function createTargetSizeRule({ id, tags, help, helpUrl, min, spacingExce
399
415
  const crowds = (other, j) => {
400
416
  if (j === i || !laidOut[j]) return false;
401
417
  if (other.contains(element) || element.contains(other)) return false; // same control, nested markup
402
- if (encloses(rects[j], rects[i]) || encloses(rects[i], rects[j])) return false;
418
+ // "One control drawn twice" (stretched-link overlays) is a claim
419
+ // about PAINT, so it must be proved on a painted fragment, not on
420
+ // the bounding box: a two-line wrapped link's box swallows a small
421
+ // same-line neighbour while its painted lines sit nowhere near it
422
+ // — a different control entirely, and dismissing it here silenced
423
+ // the honest fragment test below. A single-fragment element's one
424
+ // fragment IS its box, so genuine overlays are dismissed exactly
425
+ // as before.
426
+ if (paintedEncloses(other, rects[j], rects[i]) || paintedEncloses(element, rects[i], rects[j])) return false;
403
427
  const within = (box) => {
404
428
  if (undersized[j]) {
405
429
  const c = { x: box.left + box.width / 2, y: box.top + box.height / 2 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pour-engine",
3
- "version": "1.18.0",
3
+ "version": "1.19.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",