wcag-scanner 1.2.64 → 1.2.65

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.
@@ -1,12 +1,8 @@
1
1
  import React from 'react';
2
2
  export interface WcagDevOverlayProps {
3
- /** WCAG conformance level (default: 'AA') */
4
3
  level?: 'A' | 'AA' | 'AAA';
5
- /** Subset of rules to run. Omit to run all. */
6
4
  rules?: string[];
7
- /** Corner to anchor the overlay (default: 'bottom-right') */
8
5
  position?: 'bottom-right' | 'bottom-left';
9
- /** Delay in ms between DOM mutation and re-scan (default: 750) */
10
6
  debounce?: number;
11
7
  }
12
8
  export declare const WcagDevOverlay: React.FC<WcagDevOverlayProps>;
@@ -4,211 +4,106 @@ exports.WcagDevOverlay = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const browserScanner_1 = require("./browserScanner");
7
- // ─── Constants ───────────────────────────────────────────────────────────────
8
- const IMPACT_COLOR = {
9
- critical: '#ef4444',
10
- serious: '#f97316',
11
- moderate: '#eab308',
12
- minor: '#3b82f6',
7
+ // ─── Impact theme ──────────────────────────────────────────────────────────────
8
+ const IMPACT = {
9
+ critical: { color: '#dc2626', bg: '#fef2f2', border: '#fca5a5' },
10
+ serious: { color: '#ea580c', bg: '#fff7ed', border: '#fdba74' },
11
+ moderate: { color: '#ca8a04', bg: '#fefce8', border: '#fde047' },
12
+ minor: { color: '#2563eb', bg: '#eff6ff', border: '#93c5fd' },
13
13
  };
14
- const IMPACT_BG = {
15
- critical: '#fef2f2',
16
- serious: '#fff7ed',
17
- moderate: '#fefce8',
18
- minor: '#eff6ff',
19
- };
20
- const IMPACT_BORDER = {
21
- critical: '#fca5a5',
22
- serious: '#fdba74',
23
- moderate: '#fde047',
24
- minor: '#93c5fd',
25
- };
26
- // ─── Highlight helpers ────────────────────────────────────────────────────────
27
- let _prevHighlighted = null;
28
- let _prevOutline = '';
29
- let _prevBoxShadow = '';
30
- let _pinnedEl = null;
31
- let _pinnedOutline = '';
32
- let _pinnedBoxShadow = '';
14
+ const impactTheme = (impact) => { var _a; return (_a = IMPACT[impact]) !== null && _a !== void 0 ? _a : { color: '#6b7280', bg: '#f9fafb', border: '#e5e7eb' }; };
15
+ // ─── Highlight helpers ─────────────────────────────────────────────────────────
16
+ let _hovered = null;
17
+ let _pinned = null;
18
+ function restoreEl(saved) {
19
+ saved.el.style.outline = saved.outline;
20
+ saved.el.style.boxShadow = saved.shadow;
21
+ }
33
22
  function applyHighlight(el, color) {
34
- el.style.outline = `3px solid ${color}`;
35
- el.style.boxShadow = `0 0 0 6px ${color}33`;
23
+ el.style.outline = `2px solid ${color}`;
24
+ el.style.boxShadow = `0 0 0 4px ${color}28`;
36
25
  }
37
- function highlightElement(el, pinned = false) {
38
- if (!el)
39
- return;
40
- const htmlEl = el;
41
- if (pinned) {
42
- // Clear previous pin
43
- if (_pinnedEl && _pinnedEl !== el) {
44
- _pinnedEl.style.outline = _pinnedOutline;
45
- _pinnedEl.style.boxShadow = _pinnedBoxShadow;
46
- }
47
- _pinnedOutline = htmlEl.style.outline;
48
- _pinnedBoxShadow = htmlEl.style.boxShadow;
49
- _pinnedEl = el;
50
- applyHighlight(htmlEl, '#7c3aed');
51
- }
52
- else {
53
- if (_prevHighlighted) {
54
- _prevHighlighted.style.outline = _prevOutline;
55
- _prevHighlighted.style.boxShadow = _prevBoxShadow;
56
- _prevHighlighted = null;
57
- }
58
- // Don't hover-highlight if this element is pinned
59
- if (_pinnedEl === el)
60
- return;
61
- _prevOutline = htmlEl.style.outline;
62
- _prevBoxShadow = htmlEl.style.boxShadow;
63
- _prevHighlighted = el;
64
- applyHighlight(htmlEl, '#0ea5e9');
26
+ function hoverEl(el) {
27
+ if (_hovered) {
28
+ restoreEl(_hovered);
29
+ _hovered = null;
65
30
  }
66
- el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
31
+ if (!el || (_pinned === null || _pinned === void 0 ? void 0 : _pinned.el) === el)
32
+ return;
33
+ const h = el;
34
+ _hovered = { el: h, outline: h.style.outline, shadow: h.style.boxShadow };
35
+ applyHighlight(h, '#0ea5e9');
36
+ h.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
67
37
  }
68
38
  function clearHover() {
69
- if (_prevHighlighted) {
70
- _prevHighlighted.style.outline = _prevOutline;
71
- _prevHighlighted.style.boxShadow = _prevBoxShadow;
72
- _prevHighlighted = null;
39
+ if (_hovered) {
40
+ restoreEl(_hovered);
41
+ _hovered = null;
73
42
  }
74
43
  }
75
- function clearPin() {
76
- if (_pinnedEl) {
77
- _pinnedEl.style.outline = _pinnedOutline;
78
- _pinnedEl.style.boxShadow = _pinnedBoxShadow;
79
- _pinnedEl = null;
44
+ function pinEl(el) {
45
+ if ((_pinned === null || _pinned === void 0 ? void 0 : _pinned.el) === el) {
46
+ restoreEl(_pinned);
47
+ _pinned = null;
48
+ return false;
80
49
  }
50
+ if (_pinned)
51
+ restoreEl(_pinned);
52
+ if ((_hovered === null || _hovered === void 0 ? void 0 : _hovered.el) === el) {
53
+ restoreEl(_hovered);
54
+ _hovered = null;
55
+ }
56
+ const h = el;
57
+ _pinned = { el: h, outline: h.style.outline, shadow: h.style.boxShadow };
58
+ applyHighlight(h, '#7c3aed');
59
+ h.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
60
+ return true;
81
61
  }
82
62
  function clearAllHighlights() {
83
- clearHover();
84
- clearPin();
63
+ if (_hovered) {
64
+ restoreEl(_hovered);
65
+ _hovered = null;
66
+ }
67
+ if (_pinned) {
68
+ restoreEl(_pinned);
69
+ _pinned = null;
70
+ }
85
71
  }
86
- const ViolationCard = ({ item, index, pinned, onPin }) => {
87
- var _a, _b, _c;
72
+ const ViolationCard = ({ item, pinned, onPin }) => {
88
73
  const [expanded, setExpanded] = (0, react_1.useState)(false);
89
- const color = (_a = IMPACT_COLOR[item.impact]) !== null && _a !== void 0 ? _a : '#6b7280';
90
- const bg = (_b = IMPACT_BG[item.impact]) !== null && _b !== void 0 ? _b : '#f9fafb';
91
- const border = (_c = IMPACT_BORDER[item.impact]) !== null && _c !== void 0 ? _c : '#d1d5db';
92
- const cardStyle = {
93
- background: bg,
94
- border: `1px solid ${border}`,
95
- borderLeft: `4px solid ${color}`,
96
- borderRadius: 8,
97
- marginBottom: 8,
98
- overflow: 'hidden',
99
- transition: 'box-shadow 0.15s',
100
- boxShadow: pinned ? `0 0 0 2px ${color}` : undefined,
101
- };
102
- const headerRowStyle = {
103
- display: 'flex',
104
- alignItems: 'flex-start',
105
- gap: 8,
106
- padding: '10px 12px',
107
- cursor: item.domElement ? 'pointer' : 'default',
108
- };
109
- const badgeStyle = {
110
- display: 'inline-flex',
111
- alignItems: 'center',
112
- background: color,
113
- color: '#fff',
114
- fontSize: 9,
115
- fontWeight: 700,
116
- letterSpacing: '0.06em',
117
- textTransform: 'uppercase',
118
- borderRadius: 4,
119
- padding: '2px 6px',
120
- flexShrink: 0,
121
- marginTop: 2,
122
- };
123
- const descStyle = {
124
- fontSize: 13,
125
- fontWeight: 500,
126
- color: '#1e293b',
127
- lineHeight: 1.45,
128
- flex: 1,
129
- };
130
- const expandBtnStyle = {
131
- background: 'none',
132
- border: 'none',
133
- cursor: 'pointer',
134
- fontSize: 11,
135
- color: '#94a3b8',
136
- padding: '2px 4px',
137
- flexShrink: 0,
138
- marginTop: 1,
139
- lineHeight: 1,
140
- };
141
- const detailsStyle = {
142
- padding: '0 12px 10px',
143
- borderTop: `1px solid ${border}`,
144
- };
145
- const codeStyle = {
146
- fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
147
- fontSize: 11,
148
- background: '#0f172a',
149
- color: '#e2e8f0',
150
- borderRadius: 6,
151
- padding: '6px 10px',
152
- marginTop: 8,
153
- display: 'block',
154
- whiteSpace: 'pre-wrap',
155
- wordBreak: 'break-all',
156
- overflowWrap: 'break-word',
157
- maxHeight: 80,
158
- overflow: 'auto',
159
- };
160
- const pathStyle = {
161
- fontFamily: 'monospace',
162
- fontSize: 10,
163
- color: '#64748b',
164
- background: '#f1f5f9',
165
- border: '1px solid #e2e8f0',
166
- borderRadius: 4,
167
- padding: '3px 7px',
168
- marginTop: 6,
169
- display: 'inline-block',
170
- maxWidth: '100%',
171
- overflow: 'hidden',
172
- textOverflow: 'ellipsis',
173
- whiteSpace: 'nowrap',
174
- };
175
- const metaRowStyle = {
176
- display: 'flex',
177
- flexWrap: 'wrap',
178
- gap: 6,
179
- marginTop: 8,
180
- alignItems: 'center',
181
- };
182
- const wcagTagStyle = {
183
- fontSize: 10,
184
- fontWeight: 600,
185
- color: '#7c3aed',
186
- background: '#f5f3ff',
187
- border: '1px solid #ddd6fe',
188
- borderRadius: 4,
189
- padding: '2px 6px',
190
- };
191
- const helpStyle = {
192
- fontSize: 11,
193
- color: '#475569',
194
- fontStyle: 'italic',
195
- marginTop: 6,
196
- lineHeight: 1.5,
197
- };
198
- const pinIndicatorStyle = {
199
- fontSize: 10,
200
- color: '#7c3aed',
201
- fontWeight: 700,
202
- marginLeft: 4,
203
- };
204
- return ((0, jsx_runtime_1.jsxs)("div", { style: cardStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: headerRowStyle, onMouseEnter: () => highlightElement(item.domElement), onMouseLeave: () => clearHover(), onClick: () => item.domElement && onPin(item.domElement), title: item.domElement ? (pinned ? 'Click to unpin' : 'Click to pin element') : undefined, children: [(0, jsx_runtime_1.jsx)("span", { style: badgeStyle, children: item.impact }), (0, jsx_runtime_1.jsxs)("span", { style: descStyle, children: [item.description, pinned && (0, jsx_runtime_1.jsx)("span", { style: pinIndicatorStyle, children: "\uD83D\uDCCD" })] }), (0, jsx_runtime_1.jsx)("button", { style: expandBtnStyle, onClick: e => { e.stopPropagation(); setExpanded(x => !x); }, title: expanded ? 'Collapse' : 'Expand details', children: expanded ? '▲' : '▼' })] }), expanded && ((0, jsx_runtime_1.jsxs)("div", { style: detailsStyle, children: [item.elementPath && ((0, jsx_runtime_1.jsx)("span", { style: pathStyle, title: "Element path in DOM", children: item.elementPath })), item.snippet && ((0, jsx_runtime_1.jsx)("code", { style: codeStyle, children: item.snippet })), item.help && ((0, jsx_runtime_1.jsx)("div", { style: helpStyle, children: item.help })), item.wcag && item.wcag.length > 0 && ((0, jsx_runtime_1.jsx)("div", { style: metaRowStyle, children: item.wcag.map(w => ((0, jsx_runtime_1.jsxs)("span", { style: wcagTagStyle, children: ["WCAG ", w] }, w))) }))] }))] }));
74
+ const { color, bg, border } = impactTheme(item.impact);
75
+ const clickable = !!item.domElement;
76
+ return ((0, jsx_runtime_1.jsxs)("div", { style: {
77
+ background: bg,
78
+ borderRadius: 8,
79
+ marginBottom: 6,
80
+ overflow: 'hidden',
81
+ outline: pinned ? `2px solid ${color}` : `1px solid ${border}`,
82
+ outlineOffset: pinned ? 1 : 0,
83
+ borderLeft: `3px solid ${color}`,
84
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8, padding: '9px 10px', cursor: clickable ? 'pointer' : 'default' }, onMouseEnter: () => hoverEl(item.domElement), onMouseLeave: () => clearHover(), onClick: () => item.domElement && onPin(item.domElement), children: [(0, jsx_runtime_1.jsx)("span", { style: {
85
+ fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
86
+ background: color, color: '#fff', borderRadius: 4, padding: '2px 5px', flexShrink: 0, marginTop: 2,
87
+ }, children: item.impact }), (0, jsx_runtime_1.jsxs)("span", { style: { fontSize: 12, fontWeight: 500, color: '#1e293b', lineHeight: 1.5, flex: 1 }, children: [item.description, pinned && (0, jsx_runtime_1.jsx)("span", { style: { marginLeft: 5, fontSize: 10 }, children: "\uD83D\uDCCD" })] }), (0, jsx_runtime_1.jsx)("button", { style: { background: 'none', border: 'none', cursor: 'pointer', color: '#94a3b8', fontSize: 11, padding: '1px 4px', flexShrink: 0 }, onClick: e => { e.stopPropagation(); setExpanded(x => !x); }, title: expanded ? 'Collapse' : 'Show details', children: expanded ? '▲' : '▼' })] }), expanded && ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '0 10px 10px', borderTop: `1px solid ${border}` }, children: [item.elementPath && ((0, jsx_runtime_1.jsx)("code", { style: {
88
+ display: 'block', marginTop: 8, fontSize: 10, color: '#475569',
89
+ background: '#f1f5f9', border: '1px solid #e2e8f0', borderRadius: 4,
90
+ padding: '3px 7px', fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
91
+ }, children: item.elementPath })), item.snippet && ((0, jsx_runtime_1.jsx)("code", { style: {
92
+ display: 'block', marginTop: 6, fontSize: 10,
93
+ background: '#0f172a', color: '#e2e8f0', borderRadius: 5,
94
+ padding: '5px 8px', fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-all',
95
+ maxHeight: 72, overflow: 'auto',
96
+ }, children: item.snippet })), item.help && ((0, jsx_runtime_1.jsx)("p", { style: { margin: '7px 0 0', fontSize: 11, color: '#475569', fontStyle: 'italic', lineHeight: 1.5 }, children: item.help })), item.wcag && item.wcag.length > 0 && ((0, jsx_runtime_1.jsx)("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 7 }, children: item.wcag.map(w => ((0, jsx_runtime_1.jsxs)("span", { style: {
97
+ fontSize: 10, fontWeight: 600, color: '#7c3aed',
98
+ background: '#f5f3ff', border: '1px solid #ddd6fe', borderRadius: 4, padding: '1px 6px',
99
+ }, children: ["WCAG ", w] }, w))) }))] }))] }));
205
100
  };
206
- // ─── Main Overlay ─────────────────────────────────────────────────────────────
101
+ // ─── Main Overlay ──────────────────────────────────────────────────────────────
207
102
  const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', debounce = 750, }) => {
208
- var _a, _b, _c, _d, _e;
103
+ var _a, _b, _c;
209
104
  const [open, setOpen] = (0, react_1.useState)(() => {
210
105
  try {
211
- return sessionStorage.getItem('wcag-overlay-open') === '1';
106
+ return sessionStorage.getItem('wcag-open') === '1';
212
107
  }
213
108
  catch (_a) {
214
109
  return false;
@@ -220,57 +115,49 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
220
115
  const [results, setResults] = (0, react_1.useState)(null);
221
116
  const [lastScan, setLastScan] = (0, react_1.useState)(null);
222
117
  const [pinnedEl, setPinnedEl] = (0, react_1.useState)(null);
223
- // Drag state
118
+ // Drag
224
119
  const [pos, setPos] = (0, react_1.useState)(null);
225
120
  const dragging = (0, react_1.useRef)(false);
226
121
  const dragOffset = (0, react_1.useRef)({ x: 0, y: 0 });
227
122
  const observerRef = (0, react_1.useRef)(null);
228
123
  const timerRef = (0, react_1.useRef)(null);
229
124
  const overlayRef = (0, react_1.useRef)(null);
230
- const panelRef = (0, react_1.useRef)(null);
231
- // ── Persist open state ──────────────────────────────────────────────────
125
+ const scanningRef = (0, react_1.useRef)(false);
126
+ const cooldownRef = (0, react_1.useRef)(false);
127
+ // ── Persist open state ────────────────────────────────────────────────────
232
128
  (0, react_1.useEffect)(() => {
233
129
  try {
234
- sessionStorage.setItem('wcag-overlay-open', open ? '1' : '0');
130
+ sessionStorage.setItem('wcag-open', open ? '1' : '0');
235
131
  }
236
- catch ( /* ignore */_a) { /* ignore */ }
132
+ catch ( /* */_a) { /* */ }
237
133
  if (!open)
238
134
  clearAllHighlights();
239
135
  }, [open]);
240
- // ── Scan ────────────────────────────────────────────────────────────────
241
- const scanningRef = (0, react_1.useRef)(false);
136
+ // ── Scan ──────────────────────────────────────────────────────────────────
242
137
  const scan = (0, react_1.useCallback)(async () => {
243
- var _a;
244
138
  if (scanningRef.current)
245
139
  return;
246
140
  scanningRef.current = true;
247
141
  setScanning(true);
248
- // Pause observer while scanning to prevent scan-triggered mutations causing rescans
249
- (_a = observerRef.current) === null || _a === void 0 ? void 0 : _a.disconnect();
250
142
  try {
251
- const opts = { level, rules };
252
- const res = await (0, browserScanner_1.scanBrowserPage)(opts);
143
+ const res = await (0, browserScanner_1.scanBrowserPage)({ level, rules });
253
144
  setResults(res);
254
145
  setLastScan(new Date());
255
146
  }
256
147
  finally {
257
148
  scanningRef.current = false;
258
149
  setScanning(false);
259
- // Reconnect observer after scan settles
260
- if (observerRef.current) {
261
- observerRef.current.observe(document.body, {
262
- childList: true,
263
- subtree: true,
264
- attributes: true,
265
- attributeFilter: ['class', 'hidden', 'aria-hidden', 'role', 'alt', 'src', 'href'],
266
- });
267
- }
150
+ // Cooldown prevents the observer from immediately re-triggering after scan
151
+ cooldownRef.current = true;
152
+ setTimeout(() => { cooldownRef.current = false; }, 1200);
268
153
  }
269
154
  }, [level, rules]);
270
155
  (0, react_1.useEffect)(() => { scan(); }, [scan]);
271
- // ── MutationObserver ────────────────────────────────────────────────────
156
+ // ── MutationObserver ──────────────────────────────────────────────────────
272
157
  (0, react_1.useEffect)(() => {
273
158
  observerRef.current = new MutationObserver((mutations) => {
159
+ if (scanningRef.current || cooldownRef.current)
160
+ return;
274
161
  if (overlayRef.current &&
275
162
  mutations.every(m => overlayRef.current.contains(m.target)))
276
163
  return;
@@ -282,8 +169,8 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
282
169
  childList: true,
283
170
  subtree: true,
284
171
  attributes: true,
285
- // 'style' intentionally excluded — our highlight helper modifies inline styles
286
- // on page elements which would cause an infinite rescan loop
172
+ // 'style' excluded — our highlight helper modifies inline styles on
173
+ // page elements which would otherwise cause an infinite rescan loop
287
174
  attributeFilter: ['class', 'hidden', 'aria-hidden', 'role', 'alt', 'src', 'href'],
288
175
  });
289
176
  return () => {
@@ -294,38 +181,26 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
294
181
  clearAllHighlights();
295
182
  };
296
183
  }, [scan, debounce]);
297
- // ── Keyboard shortcut Alt+Shift+W ───────────────────────────────────────
184
+ // ── Keyboard shortcut Alt+Shift+W ─────────────────────────────────────────
298
185
  (0, react_1.useEffect)(() => {
299
186
  const handler = (e) => {
300
- if (e.altKey && e.shiftKey && e.key === 'W') {
187
+ if (e.altKey && e.shiftKey && e.key === 'W')
301
188
  setOpen(o => !o);
302
- }
303
189
  };
304
190
  window.addEventListener('keydown', handler);
305
191
  return () => window.removeEventListener('keydown', handler);
306
192
  }, []);
307
- // ── Pin handler ─────────────────────────────────────────────────────────
193
+ // ── Pin handler ───────────────────────────────────────────────────────────
308
194
  const handlePin = (0, react_1.useCallback)((el) => {
309
- if (!el)
310
- return;
311
- if (_pinnedEl === el) {
312
- clearPin();
313
- setPinnedEl(null);
314
- }
315
- else {
316
- highlightElement(el, true);
317
- setPinnedEl(el);
318
- }
195
+ const nowPinned = pinEl(el);
196
+ setPinnedEl(nowPinned ? el : null);
319
197
  }, []);
320
- // ── Drag handlers ────────────────────────────────────────────────────────
198
+ // ── Drag ──────────────────────────────────────────────────────────────────
321
199
  const onDragStart = (e) => {
322
200
  var _a, _b, _c;
323
201
  dragging.current = true;
324
202
  const rect = (_a = overlayRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
325
- dragOffset.current = {
326
- x: e.clientX - ((_b = rect === null || rect === void 0 ? void 0 : rect.left) !== null && _b !== void 0 ? _b : 0),
327
- y: e.clientY - ((_c = rect === null || rect === void 0 ? void 0 : rect.top) !== null && _c !== void 0 ? _c : 0),
328
- };
203
+ dragOffset.current = { x: e.clientX - ((_b = rect === null || rect === void 0 ? void 0 : rect.left) !== null && _b !== void 0 ? _b : 0), y: e.clientY - ((_c = rect === null || rect === void 0 ? void 0 : rect.top) !== null && _c !== void 0 ? _c : 0) };
329
204
  e.preventDefault();
330
205
  };
331
206
  (0, react_1.useEffect)(() => {
@@ -337,179 +212,107 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
337
212
  const onUp = () => { dragging.current = false; };
338
213
  window.addEventListener('mousemove', onMove);
339
214
  window.addEventListener('mouseup', onUp);
340
- return () => {
341
- window.removeEventListener('mousemove', onMove);
342
- window.removeEventListener('mouseup', onUp);
343
- };
215
+ return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
344
216
  }, []);
345
- // ── Derived state ────────────────────────────────────────────────────────
346
- const violationCount = (_a = results === null || results === void 0 ? void 0 : results.violations.length) !== null && _a !== void 0 ? _a : 0;
347
- const warningCount = (_b = results === null || results === void 0 ? void 0 : results.warnings.length) !== null && _b !== void 0 ? _b : 0;
217
+ // ── Derived ───────────────────────────────────────────────────────────────
218
+ const vCount = (_a = results === null || results === void 0 ? void 0 : results.violations.length) !== null && _a !== void 0 ? _a : 0;
219
+ const wCount = (_b = results === null || results === void 0 ? void 0 : results.warnings.length) !== null && _b !== void 0 ? _b : 0;
220
+ const pCount = (_c = results === null || results === void 0 ? void 0 : results.passes.length) !== null && _c !== void 0 ? _c : 0;
348
221
  const isLeft = position === 'bottom-left';
349
222
  const rawItems = tab === 'violations' ? results === null || results === void 0 ? void 0 : results.violations : results === null || results === void 0 ? void 0 : results.warnings;
350
- const items = filter === 'all'
351
- ? rawItems
352
- : rawItems === null || rawItems === void 0 ? void 0 : rawItems.filter(i => i.impact === filter);
223
+ const items = filter === 'all' ? rawItems : rawItems === null || rawItems === void 0 ? void 0 : rawItems.filter(i => i.impact === filter);
353
224
  const isEmpty = !items || items.length === 0;
354
- const elapsed = lastScan
355
- ? (() => {
356
- const s = Math.round((Date.now() - lastScan.getTime()) / 1000);
357
- return s < 5 ? 'just now' : s < 60 ? `${s}s ago` : `${Math.round(s / 60)}m ago`;
358
- })()
359
- : '—';
360
- // ── Styles ───────────────────────────────────────────────────────────────
225
+ const elapsed = lastScan ? (() => {
226
+ const s = Math.round((Date.now() - lastScan.getTime()) / 1000);
227
+ return s < 5 ? 'just now' : s < 60 ? `${s}s ago` : `${Math.round(s / 60)}m ago`;
228
+ })() : '';
229
+ // ── Styles ────────────────────────────────────────────────────────────────
361
230
  const anchorStyle = pos
362
231
  ? { position: 'fixed', top: pos.y, left: pos.x, zIndex: 2147483647, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' }
363
- : {
364
- position: 'fixed',
365
- bottom: 20,
366
- [isLeft ? 'left' : 'right']: 20,
367
- zIndex: 2147483647,
368
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
369
- };
370
- const toggleStyle = {
371
- display: 'flex',
372
- alignItems: 'center',
373
- gap: 7,
374
- background: violationCount > 0 ? '#7c3aed' : scanning ? '#475569' : '#16a34a',
375
- color: '#fff',
376
- border: 'none',
377
- borderRadius: 24,
378
- padding: '9px 16px',
379
- fontSize: 13,
380
- fontWeight: 600,
381
- cursor: 'pointer',
382
- boxShadow: '0 4px 16px rgba(0,0,0,0.22)',
383
- transition: 'transform 0.1s, box-shadow 0.1s',
384
- userSelect: 'none',
385
- };
386
- const badgeCountStyle = {
387
- background: 'rgba(255,255,255,0.25)',
388
- borderRadius: 10,
389
- padding: '1px 7px',
390
- fontSize: 12,
391
- fontWeight: 700,
392
- };
393
- const panelStyle = {
394
- position: 'absolute',
395
- bottom: pos ? undefined : 54,
396
- top: pos ? 54 : undefined,
397
- [isLeft ? 'left' : 'right']: 0,
398
- width: 420,
399
- maxHeight: '75vh',
400
- background: '#fff',
401
- borderRadius: 12,
402
- boxShadow: '0 24px 64px rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.08)',
403
- display: 'flex',
404
- flexDirection: 'column',
405
- overflow: 'hidden',
406
- };
407
- const headerStyle = {
408
- display: 'flex',
409
- alignItems: 'center',
410
- padding: '11px 14px',
411
- background: '#0f172a',
412
- gap: 8,
413
- cursor: 'grab',
414
- userSelect: 'none',
415
- };
416
- const summaryStyle = {
417
- display: 'flex',
418
- gap: 8,
419
- padding: '10px 14px',
420
- borderBottom: '1px solid #e2e8f0',
421
- background: '#f8fafc',
422
- };
423
- const toolbarStyle = {
424
- display: 'flex',
425
- alignItems: 'center',
426
- borderBottom: '1px solid #e2e8f0',
427
- padding: '0 14px',
428
- gap: 4,
232
+ : { position: 'fixed', bottom: 20, [isLeft ? 'left' : 'right']: 20, zIndex: 2147483647, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' };
233
+ const btnColor = scanning ? '#64748b' : vCount > 0 ? '#7c3aed' : '#16a34a';
234
+ const s = {
235
+ toggle: {
236
+ display: 'flex', alignItems: 'center', gap: 6,
237
+ background: btnColor, color: '#fff', border: 'none', borderRadius: 22,
238
+ padding: '8px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer',
239
+ boxShadow: '0 2px 12px rgba(0,0,0,0.2)', userSelect: 'none',
240
+ },
241
+ badge: {
242
+ background: 'rgba(255,255,255,0.22)', borderRadius: 9,
243
+ padding: '1px 6px', fontSize: 11, fontWeight: 700,
244
+ },
245
+ panel: {
246
+ position: 'absolute',
247
+ bottom: pos ? undefined : 48,
248
+ top: pos ? 48 : undefined,
249
+ [isLeft ? 'left' : 'right']: 0,
250
+ width: 400,
251
+ maxHeight: '74vh',
252
+ background: '#ffffff',
253
+ borderRadius: 10,
254
+ boxShadow: '0 8px 40px rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.07)',
255
+ display: 'flex',
256
+ flexDirection: 'column',
257
+ overflow: 'hidden',
258
+ },
259
+ header: {
260
+ display: 'flex', alignItems: 'center', gap: 8,
261
+ padding: '10px 12px', background: '#0f172a',
262
+ cursor: 'grab', userSelect: 'none', flexShrink: 0,
263
+ },
264
+ headerBtn: {
265
+ background: 'none', border: 'none', cursor: 'pointer',
266
+ color: '#64748b', fontSize: 14, padding: '2px 5px', lineHeight: 1, borderRadius: 4,
267
+ },
268
+ summary: {
269
+ display: 'flex', gap: 6, padding: '8px 12px',
270
+ borderBottom: '1px solid #f1f5f9', background: '#f8fafc', flexShrink: 0,
271
+ },
272
+ toolbar: {
273
+ display: 'flex', alignItems: 'center',
274
+ borderBottom: '1px solid #f1f5f9', padding: '0 12px',
275
+ gap: 2, flexShrink: 0,
276
+ },
277
+ list: {
278
+ flex: 1, overflowY: 'auto', padding: '10px 10px',
279
+ },
280
+ footer: {
281
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
282
+ padding: '7px 12px', borderTop: '1px solid #f1f5f9',
283
+ background: '#f8fafc', fontSize: 10, color: '#94a3b8', flexShrink: 0,
284
+ },
429
285
  };
430
- const tabBtnStyle = (active) => ({
431
- padding: '8px 12px',
432
- fontSize: 13,
433
- fontWeight: active ? 600 : 400,
434
- color: active ? '#7c3aed' : '#64748b',
435
- cursor: 'pointer',
436
- background: 'none',
437
- border: 'none',
438
- // Use inset box-shadow instead of borderBottom to avoid shorthand/longhand conflict
286
+ const chip = (color, count) => ({
287
+ display: 'flex', alignItems: 'center', gap: 3, fontSize: 11, fontWeight: 600,
288
+ color: count > 0 ? color : '#94a3b8',
289
+ background: count > 0 ? `${color}14` : '#f1f5f9',
290
+ border: `1px solid ${count > 0 ? color + '35' : '#e2e8f0'}`,
291
+ borderRadius: 5, padding: '3px 8px', flexShrink: 0,
292
+ });
293
+ const tabBtn = (active) => ({
294
+ padding: '7px 10px', fontSize: 12, fontWeight: active ? 600 : 400,
295
+ color: active ? '#7c3aed' : '#64748b', cursor: 'pointer',
296
+ background: 'none', border: 'none',
439
297
  boxShadow: active ? 'inset 0 -2px 0 #7c3aed' : 'none',
440
298
  whiteSpace: 'nowrap',
441
299
  });
442
- const filterSelectStyle = {
443
- marginLeft: 'auto',
444
- fontSize: 11,
445
- border: '1px solid #e2e8f0',
446
- borderRadius: 6,
447
- padding: '3px 8px',
448
- color: '#475569',
449
- background: '#fff',
450
- cursor: 'pointer',
451
- outline: 'none',
452
- };
453
- const listStyle = {
454
- flex: 1,
455
- overflowY: 'auto',
456
- padding: '12px 12px',
457
- };
458
- const footerStyle = {
459
- display: 'flex',
460
- alignItems: 'center',
461
- justifyContent: 'space-between',
462
- padding: '8px 14px',
463
- borderTop: '1px solid #e2e8f0',
464
- background: '#f8fafc',
465
- fontSize: 11,
466
- color: '#94a3b8',
467
- };
468
- const rescanBtnStyle = {
469
- background: '#7c3aed',
470
- color: '#fff',
471
- border: 'none',
472
- borderRadius: 6,
473
- padding: '4px 12px',
474
- fontSize: 11,
475
- fontWeight: 600,
476
- cursor: scanning ? 'wait' : 'pointer',
477
- opacity: scanning ? 0.6 : 1,
478
- };
479
- const chipStyle = (color, active = true) => ({
480
- display: 'flex',
481
- alignItems: 'center',
482
- gap: 4,
483
- fontSize: 11,
484
- fontWeight: 600,
485
- color: active ? color : '#94a3b8',
486
- background: active ? `${color}18` : '#f1f5f9',
487
- border: `1px solid ${active ? color + '40' : '#e2e8f0'}`,
488
- borderRadius: 6,
489
- padding: '4px 10px',
490
- flexShrink: 0,
491
- });
492
- const iconBtnStyle = {
493
- background: 'none',
494
- border: 'none',
495
- cursor: 'pointer',
496
- color: '#94a3b8',
497
- padding: '3px 6px',
498
- fontSize: 14,
499
- lineHeight: 1,
500
- borderRadius: 4,
501
- transition: 'color 0.15s',
300
+ const rescanBtn = {
301
+ background: '#7c3aed', color: '#fff', border: 'none', borderRadius: 5,
302
+ padding: '4px 10px', fontSize: 10, fontWeight: 600,
303
+ cursor: scanning ? 'wait' : 'pointer', opacity: scanning ? 0.55 : 1,
502
304
  };
503
- return ((0, jsx_runtime_1.jsxs)("div", { ref: overlayRef, style: anchorStyle, "data-wcag-overlay": "true", children: [open && ((0, jsx_runtime_1.jsxs)("div", { ref: panelRef, style: panelStyle, role: "dialog", "aria-label": "WCAG Dev Inspector", children: [(0, jsx_runtime_1.jsxs)("div", { style: headerStyle, onMouseDown: onDragStart, children: [(0, jsx_runtime_1.jsx)("span", { style: { fontSize: 15, lineHeight: 1 }, children: "\u267F" }), (0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 700, fontSize: 13, flex: 1, color: '#f1f5f9', letterSpacing: '0.01em' }, children: "WCAG Dev Inspector" }), (0, jsx_runtime_1.jsx)("span", { style: {
504
- fontSize: 10, fontWeight: 700, color: '#a78bfa',
505
- background: '#4c1d95', borderRadius: 4, padding: '2px 7px', letterSpacing: '0.05em',
506
- }, children: level }), (0, jsx_runtime_1.jsx)("button", { onClick: scan, disabled: scanning, title: "Rescan page", style: { ...iconBtnStyle, color: scanning ? '#475569' : '#94a3b8', fontSize: 16 }, children: scanning ? '⏳' : '↻' }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setOpen(false), title: "Close (Alt+Shift+W)", style: { ...iconBtnStyle, fontSize: 15 }, children: "\u2715" })] }), (0, jsx_runtime_1.jsxs)("div", { style: summaryStyle, children: [(0, jsx_runtime_1.jsxs)("span", { style: chipStyle('#ef4444', violationCount > 0), children: ["\u2717 ", violationCount, " violation", violationCount !== 1 ? 's' : ''] }), (0, jsx_runtime_1.jsxs)("span", { style: chipStyle('#f97316', warningCount > 0), children: ["\u26A0 ", warningCount, " warning", warningCount !== 1 ? 's' : ''] }), (0, jsx_runtime_1.jsxs)("span", { style: chipStyle('#16a34a', ((_c = results === null || results === void 0 ? void 0 : results.passes.length) !== null && _c !== void 0 ? _c : 0) > 0), children: ["\u2713 ", (_d = results === null || results === void 0 ? void 0 : results.passes.length) !== null && _d !== void 0 ? _d : 0, " pass", ((_e = results === null || results === void 0 ? void 0 : results.passes.length) !== null && _e !== void 0 ? _e : 0) !== 1 ? 'es' : ''] })] }), (0, jsx_runtime_1.jsxs)("div", { style: toolbarStyle, children: [['violations', 'warnings'].map(t => ((0, jsx_runtime_1.jsx)("button", { style: tabBtnStyle(tab === t), onClick: () => setTab(t), children: t === 'violations'
507
- ? `Violations (${violationCount})`
508
- : `Warnings (${warningCount})` }, t))), (0, jsx_runtime_1.jsxs)("select", { style: filterSelectStyle, value: filter, onChange: e => setFilter(e.target.value), title: "Filter by impact", children: [(0, jsx_runtime_1.jsx)("option", { value: "all", children: "All impacts" }), (0, jsx_runtime_1.jsx)("option", { value: "critical", children: "Critical" }), (0, jsx_runtime_1.jsx)("option", { value: "serious", children: "Serious" }), (0, jsx_runtime_1.jsx)("option", { value: "moderate", children: "Moderate" }), (0, jsx_runtime_1.jsx)("option", { value: "minor", children: "Minor" })] })] }), (0, jsx_runtime_1.jsxs)("div", { style: listStyle, children: [scanning && !results && ((0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', color: '#94a3b8', padding: '32px 0', fontSize: 13 }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 24, marginBottom: 10 }, children: "\u23F3" }), "Scanning\u2026"] })), !scanning && isEmpty && ((0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', padding: '32px 0' }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 32, marginBottom: 10 }, children: tab === 'violations' ? '✅' : '🔕' }), (0, jsx_runtime_1.jsxs)("div", { style: { color: '#64748b', fontSize: 13, fontWeight: 500 }, children: ["No ", tab, " found"] }), filter !== 'all' && ((0, jsx_runtime_1.jsxs)("div", { style: { color: '#94a3b8', fontSize: 11, marginTop: 6 }, children: ["(filter: ", filter, " \u2014 ", (0, jsx_runtime_1.jsx)("button", { style: { background: 'none', border: 'none', color: '#7c3aed', cursor: 'pointer', fontSize: 11, padding: 0 }, onClick: () => setFilter('all'), children: "clear" }), ")"] }))] })), items === null || items === void 0 ? void 0 : items.map((item, i) => ((0, jsx_runtime_1.jsx)(ViolationCard, { item: item, index: i, pinned: pinnedEl === item.domElement && item.domElement != null, onPin: handlePin }, `${item.rule}-${i}`)))] }), (0, jsx_runtime_1.jsxs)("div", { style: footerStyle, children: [(0, jsx_runtime_1.jsxs)("span", { children: [pinnedEl ? '📍 element pinned · ' : '', "Scanned ", elapsed, results && ` · ${results.duration}ms`] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [pinnedEl && ((0, jsx_runtime_1.jsx)("button", { style: { ...rescanBtnStyle, background: '#64748b' }, onClick: () => { clearPin(); setPinnedEl(null); }, children: "Unpin" })), (0, jsx_runtime_1.jsx)("button", { style: rescanBtnStyle, onClick: scan, disabled: scanning, children: scanning ? 'Scanning…' : 'Rescan' })] })] })] })), (0, jsx_runtime_1.jsxs)("button", { style: toggleStyle, onClick: () => setOpen(o => !o), title: "Toggle WCAG Dev Inspector (Alt+Shift+W)", "aria-expanded": open, "aria-label": `WCAG Inspector: ${violationCount} violation${violationCount !== 1 ? 's' : ''}`, children: [(0, jsx_runtime_1.jsx)("span", { style: { fontSize: 16 }, children: "\u267F" }), scanning
509
- ? (0, jsx_runtime_1.jsx)("span", { style: { opacity: 0.8, fontSize: 12 }, children: "scanning\u2026" })
510
- : violationCount > 0
511
- ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { style: badgeCountStyle, children: violationCount }), " ", (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 12 }, children: "issues" })] })
512
- : (0, jsx_runtime_1.jsx)("span", { style: badgeCountStyle, children: "\u2713" })] })] }));
305
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: overlayRef, style: anchorStyle, "data-wcag-overlay": "true", children: [open && ((0, jsx_runtime_1.jsxs)("div", { style: s.panel, role: "dialog", "aria-label": "WCAG Dev Inspector", children: [(0, jsx_runtime_1.jsxs)("div", { style: s.header, onMouseDown: onDragStart, children: [(0, jsx_runtime_1.jsx)("span", { style: { fontSize: 14 }, children: "\u267F" }), (0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 700, fontSize: 13, flex: 1, color: '#f1f5f9' }, children: "WCAG Inspector" }), (0, jsx_runtime_1.jsx)("span", { style: {
306
+ fontSize: 9, fontWeight: 700, color: '#a78bfa',
307
+ background: '#4c1d95', borderRadius: 4, padding: '2px 6px', letterSpacing: '0.05em',
308
+ }, children: level }), (0, jsx_runtime_1.jsx)("button", { onClick: scan, disabled: scanning, title: "Rescan", style: s.headerBtn, children: scanning ? '⏳' : '↻' }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setOpen(false), title: "Close (Alt+Shift+W)", style: s.headerBtn, children: "\u2715" })] }), (0, jsx_runtime_1.jsxs)("div", { style: s.summary, children: [(0, jsx_runtime_1.jsxs)("span", { style: chip('#dc2626', vCount), children: ["\u2717 ", vCount, " violation", vCount !== 1 ? 's' : ''] }), (0, jsx_runtime_1.jsxs)("span", { style: chip('#ea580c', wCount), children: ["\u26A0 ", wCount, " warning", wCount !== 1 ? 's' : ''] }), (0, jsx_runtime_1.jsxs)("span", { style: chip('#16a34a', pCount), children: ["\u2713 ", pCount, " pass", pCount !== 1 ? 'es' : ''] })] }), (0, jsx_runtime_1.jsxs)("div", { style: s.toolbar, children: [(0, jsx_runtime_1.jsxs)("button", { style: tabBtn(tab === 'violations'), onClick: () => setTab('violations'), children: ["Violations (", vCount, ")"] }), (0, jsx_runtime_1.jsxs)("button", { style: tabBtn(tab === 'warnings'), onClick: () => setTab('warnings'), children: ["Warnings (", wCount, ")"] }), (0, jsx_runtime_1.jsxs)("select", { style: {
309
+ marginLeft: 'auto', fontSize: 10, border: '1px solid #e2e8f0',
310
+ borderRadius: 5, padding: '3px 6px', color: '#475569', background: '#fff', cursor: 'pointer', outline: 'none',
311
+ }, value: filter, onChange: e => setFilter(e.target.value), children: [(0, jsx_runtime_1.jsx)("option", { value: "all", children: "All" }), (0, jsx_runtime_1.jsx)("option", { value: "critical", children: "Critical" }), (0, jsx_runtime_1.jsx)("option", { value: "serious", children: "Serious" }), (0, jsx_runtime_1.jsx)("option", { value: "moderate", children: "Moderate" }), (0, jsx_runtime_1.jsx)("option", { value: "minor", children: "Minor" })] })] }), (0, jsx_runtime_1.jsxs)("div", { style: s.list, children: [scanning && !results && ((0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', color: '#94a3b8', padding: '28px 0', fontSize: 12 }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 22, marginBottom: 8 }, children: "\u23F3" }), "Scanning\u2026"] })), !scanning && isEmpty && ((0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', padding: '28px 0' }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 28, marginBottom: 8 }, children: tab === 'violations' ? '✅' : '🔕' }), (0, jsx_runtime_1.jsxs)("div", { style: { color: '#64748b', fontSize: 12, fontWeight: 500 }, children: ["No ", tab, " found"] }), filter !== 'all' && ((0, jsx_runtime_1.jsx)("button", { style: { marginTop: 6, fontSize: 11, color: '#7c3aed', background: 'none', border: 'none', cursor: 'pointer' }, onClick: () => setFilter('all'), children: "Clear filter" }))] })), items === null || items === void 0 ? void 0 : items.map((item, i) => ((0, jsx_runtime_1.jsx)(ViolationCard, { item: item, pinned: pinnedEl === item.domElement && item.domElement != null, onPin: handlePin }, `${item.rule}-${i}`)))] }), (0, jsx_runtime_1.jsxs)("div", { style: s.footer, children: [(0, jsx_runtime_1.jsxs)("span", { children: [pinnedEl ? '📍 pinned · ' : '', "Scanned ", elapsed, results ? ` · ${results.duration}ms` : ''] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [pinnedEl && ((0, jsx_runtime_1.jsx)("button", { style: { ...rescanBtn, background: '#64748b' }, onClick: () => { clearAllHighlights(); setPinnedEl(null); }, children: "Unpin" })), (0, jsx_runtime_1.jsx)("button", { style: rescanBtn, onClick: scan, disabled: scanning, children: scanning ? 'Scanning…' : 'Rescan' })] })] })] })), (0, jsx_runtime_1.jsxs)("button", { style: s.toggle, onClick: () => setOpen(o => !o), title: "Toggle WCAG Inspector (Alt+Shift+W)", "aria-expanded": open, children: [(0, jsx_runtime_1.jsx)("span", { style: { fontSize: 14 }, children: "\u267F" }), scanning
312
+ ? (0, jsx_runtime_1.jsx)("span", { style: { opacity: 0.8, fontSize: 11 }, children: "scanning\u2026" })
313
+ : vCount > 0
314
+ ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { style: s.badge, children: vCount }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 11 }, children: "issues" })] })
315
+ : (0, jsx_runtime_1.jsx)("span", { style: s.badge, children: "\u2713" })] })] }));
513
316
  };
514
317
  exports.WcagDevOverlay = WcagDevOverlay;
515
318
  exports.default = exports.WcagDevOverlay;
@@ -39,13 +39,16 @@ async function scanBrowserPage(options = {}) {
39
39
  // rule failed in browser context — skip silently
40
40
  }
41
41
  }
42
+ // Exclude any elements that live inside the WCAG overlay itself
43
+ const overlayRoot = document.querySelector('[data-wcag-overlay-root]');
44
+ const isInOverlay = (el) => el != null && overlayRoot != null && overlayRoot.contains(el);
42
45
  const annotate = (item) => {
43
46
  const el = findElement(item, document);
44
47
  return { ...item, domElement: el !== null && el !== void 0 ? el : undefined, elementPath: el ? getElementPath(el) : undefined };
45
48
  };
46
49
  return {
47
- violations: violations.map(annotate),
48
- warnings: warnings.map(annotate),
50
+ violations: violations.map(annotate).filter(v => { var _a; return !isInOverlay((_a = v.domElement) !== null && _a !== void 0 ? _a : null); }),
51
+ warnings: warnings.map(annotate).filter(w => { var _a; return !isInOverlay((_a = w.domElement) !== null && _a !== void 0 ? _a : null); }),
49
52
  passes,
50
53
  duration: Math.round(performance.now() - start),
51
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wcag-scanner",
3
- "version": "1.2.64",
3
+ "version": "1.2.65",
4
4
  "description": "Scan HTML for WCAG accessibility violations with AI-powered fix suggestions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",