wcag-scanner 1.2.65 → 1.2.67
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/README.md +107 -83
- package/dist/index.d.ts +5 -24
- package/dist/index.js +11 -126
- package/dist/middleware/express.js +42 -18
- package/dist/react/WcagDevOverlay.d.ts +2 -0
- package/dist/react/WcagDevOverlay.js +282 -154
- package/dist/react/browserScanner.d.ts +11 -1
- package/dist/react/browserScanner.js +107 -22
- package/dist/react/gemini.d.ts +12 -0
- package/dist/react/gemini.js +60 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +10 -1
- package/dist/rules/backgroundImages.d.ts +8 -0
- package/dist/rules/backgroundImages.js +116 -0
- package/dist/rules/contrast.js +52 -23
- package/dist/rules/images.js +0 -91
- package/dist/rules/presets.d.ts +8 -0
- package/dist/rules/presets.js +19 -0
- package/dist/scanner.js +14 -12
- package/dist/types/index.d.ts +3 -0
- package/package.json +16 -15
- package/dist/ai/suggestions.d.ts +0 -11
- package/dist/ai/suggestions.js +0 -103
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -160
- package/dist/wasm/index.d.ts +0 -19
- package/dist/wasm/index.js +0 -53
|
@@ -4,117 +4,264 @@ 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
|
-
|
|
7
|
+
const gemini_1 = require("./gemini");
|
|
8
|
+
// ─── Impact colours ───────────────────────────────────────────────────────────
|
|
8
9
|
const IMPACT = {
|
|
9
10
|
critical: { color: '#dc2626', bg: '#fef2f2', border: '#fca5a5' },
|
|
10
11
|
serious: { color: '#ea580c', bg: '#fff7ed', border: '#fdba74' },
|
|
11
12
|
moderate: { color: '#ca8a04', bg: '#fefce8', border: '#fde047' },
|
|
12
13
|
minor: { color: '#2563eb', bg: '#eff6ff', border: '#93c5fd' },
|
|
13
14
|
};
|
|
14
|
-
const
|
|
15
|
+
const theme = (impact) => { var _a; return (_a = IMPACT[impact]) !== null && _a !== void 0 ? _a : { color: '#6b7280', bg: '#f9fafb', border: '#e5e7eb' }; };
|
|
15
16
|
// ─── Highlight helpers ─────────────────────────────────────────────────────────
|
|
17
|
+
const HIGHLIGHT_ID = 'wcag-dev-highlight';
|
|
18
|
+
function getHighlightLayer() {
|
|
19
|
+
let layer = document.getElementById(HIGHLIGHT_ID);
|
|
20
|
+
if (layer)
|
|
21
|
+
return layer;
|
|
22
|
+
layer = document.createElement('div');
|
|
23
|
+
layer.id = HIGHLIGHT_ID;
|
|
24
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
25
|
+
Object.assign(layer.style, {
|
|
26
|
+
position: 'fixed',
|
|
27
|
+
top: '0',
|
|
28
|
+
left: '0',
|
|
29
|
+
width: '0',
|
|
30
|
+
height: '0',
|
|
31
|
+
display: 'none',
|
|
32
|
+
pointerEvents: 'none',
|
|
33
|
+
zIndex: '2147483646',
|
|
34
|
+
borderRadius: '10px',
|
|
35
|
+
boxSizing: 'border-box',
|
|
36
|
+
transition: 'opacity 80ms ease-out, transform 80ms ease-out',
|
|
37
|
+
transform: 'translateZ(0)',
|
|
38
|
+
});
|
|
39
|
+
const label = document.createElement('div');
|
|
40
|
+
label.setAttribute('data-wcag-highlight-label', 'true');
|
|
41
|
+
Object.assign(label.style, {
|
|
42
|
+
position: 'absolute',
|
|
43
|
+
top: '-30px',
|
|
44
|
+
left: '0',
|
|
45
|
+
padding: '4px 8px',
|
|
46
|
+
borderRadius: '999px',
|
|
47
|
+
color: '#fff',
|
|
48
|
+
fontSize: '11px',
|
|
49
|
+
fontWeight: '700',
|
|
50
|
+
letterSpacing: '0.03em',
|
|
51
|
+
boxShadow: '0 6px 18px rgba(15,23,42,0.22)',
|
|
52
|
+
whiteSpace: 'nowrap',
|
|
53
|
+
maxWidth: '280px',
|
|
54
|
+
overflow: 'hidden',
|
|
55
|
+
textOverflow: 'ellipsis',
|
|
56
|
+
});
|
|
57
|
+
layer.appendChild(label);
|
|
58
|
+
document.body.appendChild(layer);
|
|
59
|
+
return layer;
|
|
60
|
+
}
|
|
16
61
|
let _hovered = null;
|
|
17
62
|
let _pinned = null;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
63
|
+
let _highlightRaf = 0;
|
|
64
|
+
let _highlightCleanup = null;
|
|
65
|
+
function labelForElement(el) {
|
|
66
|
+
const htmlEl = el;
|
|
67
|
+
const parts = [el.tagName.toLowerCase()];
|
|
68
|
+
if (htmlEl.id)
|
|
69
|
+
parts.push(`#${htmlEl.id}`);
|
|
70
|
+
const classes = typeof htmlEl.className === 'string'
|
|
71
|
+
? htmlEl.className.trim().split(/\s+/).filter(Boolean).slice(0, 2)
|
|
72
|
+
: [];
|
|
73
|
+
if (classes.length)
|
|
74
|
+
parts.push(`.${classes.join('.')}`);
|
|
75
|
+
return parts.join('');
|
|
25
76
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
77
|
+
function renderHighlight(el, mode) {
|
|
78
|
+
const rect = el.getBoundingClientRect();
|
|
79
|
+
const layer = getHighlightLayer();
|
|
80
|
+
const label = layer.querySelector('[data-wcag-highlight-label="true"]');
|
|
81
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
82
|
+
layer.style.display = 'none';
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const color = mode === 'pinned' ? '#7c3aed' : '#0ea5e9';
|
|
86
|
+
const glow = mode === 'pinned' ? 'rgba(124,58,237,0.35)' : 'rgba(14,165,233,0.28)';
|
|
87
|
+
Object.assign(layer.style, {
|
|
88
|
+
display: 'block',
|
|
89
|
+
top: `${Math.max(rect.top - 4, 0)}px`,
|
|
90
|
+
left: `${Math.max(rect.left - 4, 0)}px`,
|
|
91
|
+
width: `${Math.max(rect.width + 8, 12)}px`,
|
|
92
|
+
height: `${Math.max(rect.height + 8, 12)}px`,
|
|
93
|
+
border: `3px solid ${color}`,
|
|
94
|
+
background: `${color}0d`,
|
|
95
|
+
boxShadow: `0 0 0 4px ${glow}, 0 14px 34px rgba(15,23,42,0.16)`,
|
|
96
|
+
opacity: '1',
|
|
97
|
+
});
|
|
98
|
+
if (label) {
|
|
99
|
+
label.textContent = mode === 'pinned' ? `Pinned ${labelForElement(el)}` : `Inspecting ${labelForElement(el)}`;
|
|
100
|
+
label.style.background = color;
|
|
101
|
+
label.style.top = rect.top < 48 ? `${rect.height + 10}px` : '-30px';
|
|
30
102
|
}
|
|
31
|
-
|
|
103
|
+
}
|
|
104
|
+
function syncHighlightPosition() {
|
|
105
|
+
cancelAnimationFrame(_highlightRaf);
|
|
106
|
+
_highlightRaf = requestAnimationFrame(() => {
|
|
107
|
+
if (_pinned) {
|
|
108
|
+
renderHighlight(_pinned, 'pinned');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (_hovered) {
|
|
112
|
+
renderHighlight(_hovered, 'hover');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const layer = document.getElementById(HIGHLIGHT_ID);
|
|
116
|
+
if (layer)
|
|
117
|
+
layer.style.display = 'none';
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function ensureHighlightTracking() {
|
|
121
|
+
if (_highlightCleanup)
|
|
32
122
|
return;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
123
|
+
const update = () => syncHighlightPosition();
|
|
124
|
+
window.addEventListener('scroll', update, true);
|
|
125
|
+
window.addEventListener('resize', update);
|
|
126
|
+
_highlightCleanup = () => {
|
|
127
|
+
window.removeEventListener('scroll', update, true);
|
|
128
|
+
window.removeEventListener('resize', update);
|
|
129
|
+
_highlightCleanup = null;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function maybeStopHighlightTracking() {
|
|
133
|
+
if (_hovered || _pinned || !_highlightCleanup)
|
|
134
|
+
return;
|
|
135
|
+
_highlightCleanup();
|
|
136
|
+
}
|
|
137
|
+
function hoverEl(el) {
|
|
138
|
+
_hovered = !el || _pinned === el ? null : el;
|
|
139
|
+
ensureHighlightTracking();
|
|
140
|
+
syncHighlightPosition();
|
|
37
141
|
}
|
|
38
142
|
function clearHover() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
143
|
+
_hovered = null;
|
|
144
|
+
syncHighlightPosition();
|
|
145
|
+
maybeStopHighlightTracking();
|
|
43
146
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
147
|
+
/** Returns true if now pinned, false if unpinned */
|
|
148
|
+
function togglePin(el) {
|
|
149
|
+
if (_pinned === el) {
|
|
47
150
|
_pinned = null;
|
|
151
|
+
syncHighlightPosition();
|
|
152
|
+
maybeStopHighlightTracking();
|
|
48
153
|
return false;
|
|
49
154
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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' });
|
|
155
|
+
_hovered = null;
|
|
156
|
+
_pinned = el;
|
|
157
|
+
ensureHighlightTracking();
|
|
158
|
+
syncHighlightPosition();
|
|
159
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
60
160
|
return true;
|
|
61
161
|
}
|
|
62
162
|
function clearAllHighlights() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
163
|
+
cancelAnimationFrame(_highlightRaf);
|
|
164
|
+
_hovered = null;
|
|
165
|
+
_pinned = null;
|
|
166
|
+
const layer = document.getElementById(HIGHLIGHT_ID);
|
|
167
|
+
if (layer)
|
|
168
|
+
layer.style.display = 'none';
|
|
169
|
+
maybeStopHighlightTracking();
|
|
71
170
|
}
|
|
72
|
-
const ViolationCard = ({ item, pinned, onPin }) => {
|
|
171
|
+
const ViolationCard = ({ item, pinned, onPin, apiKey }) => {
|
|
73
172
|
const [expanded, setExpanded] = (0, react_1.useState)(false);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
173
|
+
const [aiState, setAiState] = (0, react_1.useState)('idle');
|
|
174
|
+
const [suggestion, setSuggestion] = (0, react_1.useState)(null);
|
|
175
|
+
const [aiError, setAiError] = (0, react_1.useState)('');
|
|
176
|
+
const { color, bg, border } = theme(item.impact);
|
|
177
|
+
const fetchAi = async () => {
|
|
178
|
+
if (!apiKey || aiState === 'loading')
|
|
179
|
+
return;
|
|
180
|
+
setAiState('loading');
|
|
181
|
+
setAiError('');
|
|
182
|
+
try {
|
|
183
|
+
const s = await (0, gemini_1.getAiSuggestion)(apiKey, item);
|
|
184
|
+
setSuggestion(s);
|
|
185
|
+
setAiState('done');
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
setAiError(e instanceof Error ? e.message : 'Unknown error');
|
|
189
|
+
setAiState('error');
|
|
190
|
+
}
|
|
191
|
+
};
|
|
76
192
|
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
77
|
-
background: bg,
|
|
78
|
-
borderRadius: 8,
|
|
79
|
-
marginBottom: 6,
|
|
80
|
-
overflow: 'hidden',
|
|
193
|
+
background: bg, borderRadius: 8, marginBottom: 6, overflow: 'hidden',
|
|
81
194
|
outline: pinned ? `2px solid ${color}` : `1px solid ${border}`,
|
|
82
195
|
outlineOffset: pinned ? 1 : 0,
|
|
83
196
|
borderLeft: `3px solid ${color}`,
|
|
84
|
-
}, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8, padding: '9px 10px', cursor:
|
|
197
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8, padding: '9px 10px', cursor: item.domElement ? 'pointer' : 'default' }, onMouseEnter: () => hoverEl(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: {
|
|
85
198
|
fontSize: 9, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
|
|
86
199
|
background: color, color: '#fff', borderRadius: 4, padding: '2px 5px', flexShrink: 0, marginTop: 2,
|
|
87
200
|
}, 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
201
|
display: 'block', marginTop: 8, fontSize: 10, color: '#475569',
|
|
89
202
|
background: '#f1f5f9', border: '1px solid #e2e8f0', borderRadius: 4,
|
|
90
|
-
padding: '3px 7px', fontFamily: 'monospace', overflow: 'hidden',
|
|
91
|
-
|
|
203
|
+
padding: '3px 7px', fontFamily: 'monospace', overflow: 'hidden',
|
|
204
|
+
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
205
|
+
}, title: item.elementSelector, children: item.elementPath })), item.snippet && ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
92
206
|
display: 'block', marginTop: 6, fontSize: 10,
|
|
93
207
|
background: '#0f172a', color: '#e2e8f0', borderRadius: 5,
|
|
94
|
-
padding: '5px 8px', fontFamily: 'monospace', whiteSpace: 'pre-wrap',
|
|
95
|
-
maxHeight: 72, overflow: 'auto',
|
|
208
|
+
padding: '5px 8px', fontFamily: 'monospace', whiteSpace: 'pre-wrap',
|
|
209
|
+
wordBreak: 'break-all', maxHeight: 72, overflow: 'auto',
|
|
96
210
|
}, 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
211
|
fontSize: 10, fontWeight: 600, color: '#7c3aed',
|
|
98
212
|
background: '#f5f3ff', border: '1px solid #ddd6fe', borderRadius: 4, padding: '1px 6px',
|
|
99
|
-
}, children: ["WCAG ", w] }, w))) }))
|
|
213
|
+
}, children: ["WCAG ", w] }, w))) })), apiKey && ((0, jsx_runtime_1.jsxs)("div", { style: { marginTop: 10, borderTop: `1px solid ${border}`, paddingTop: 8 }, children: [aiState === 'idle' && ((0, jsx_runtime_1.jsx)("button", { onClick: fetchAi, style: {
|
|
214
|
+
fontSize: 10, fontWeight: 600, color: '#7c3aed',
|
|
215
|
+
background: '#f5f3ff', border: '1px solid #ddd6fe', borderRadius: 5,
|
|
216
|
+
padding: '3px 9px', cursor: 'pointer',
|
|
217
|
+
}, children: "\u2728 Get AI fix" })), aiState === 'loading' && ((0, jsx_runtime_1.jsx)("span", { style: { fontSize: 10, color: '#94a3b8' }, children: "\u23F3 Asking Gemini\u2026" })), aiState === 'error' && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("span", { style: { fontSize: 10, color: '#dc2626' }, children: ["\u26A0 ", aiError] }), (0, jsx_runtime_1.jsx)("button", { onClick: fetchAi, style: { marginLeft: 8, fontSize: 10, color: '#7c3aed', background: 'none', border: 'none', cursor: 'pointer' }, children: "Retry" })] })), aiState === 'done' && suggestion && ((0, jsx_runtime_1.jsxs)("div", { children: [suggestion.code && ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
218
|
+
display: 'block', fontSize: 10, background: '#0f172a', color: '#86efac',
|
|
219
|
+
borderRadius: 5, padding: '5px 8px', fontFamily: 'monospace',
|
|
220
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: 100, overflow: 'auto',
|
|
221
|
+
}, children: suggestion.code })), suggestion.explanation && ((0, jsx_runtime_1.jsx)("p", { style: { margin: '5px 0 0', fontSize: 11, color: '#16a34a', fontStyle: 'italic' }, children: suggestion.explanation })), (0, jsx_runtime_1.jsx)("button", { onClick: () => { setSuggestion(null); setAiState('idle'); }, style: { marginTop: 4, fontSize: 10, color: '#94a3b8', background: 'none', border: 'none', cursor: 'pointer' }, children: "Clear" })] }))] }))] }))] }));
|
|
222
|
+
};
|
|
223
|
+
const SettingsPanel = ({ apiKey, preset, onSave, onPresetChange }) => {
|
|
224
|
+
const [draft, setDraft] = (0, react_1.useState)(apiKey);
|
|
225
|
+
const [saved, setSaved] = (0, react_1.useState)(false);
|
|
226
|
+
const save = () => {
|
|
227
|
+
(0, gemini_1.setStoredApiKey)(draft);
|
|
228
|
+
onSave(draft);
|
|
229
|
+
setSaved(true);
|
|
230
|
+
setTimeout(() => setSaved(false), 2000);
|
|
231
|
+
};
|
|
232
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '16px 14px', flex: 1, overflowY: 'auto' }, children: [(0, jsx_runtime_1.jsxs)("p", { style: { margin: '0 0 12px', fontSize: 12, color: '#475569', lineHeight: 1.6 }, children: ["Enter your ", (0, jsx_runtime_1.jsx)("strong", { children: "Google Gemini API key" }), " to get AI-powered fix suggestions for each violation. The key is stored in ", (0, jsx_runtime_1.jsx)("code", { style: { fontSize: 11, background: '#f1f5f9', padding: '1px 4px', borderRadius: 3 }, children: "localStorage" }), " and never leaves your browser."] }), (0, jsx_runtime_1.jsx)("label", { style: { display: 'block', fontSize: 11, fontWeight: 600, color: '#374151', marginBottom: 4 }, children: "Gemini API Key" }), (0, jsx_runtime_1.jsx)("input", { type: "password", value: draft, onChange: e => setDraft(e.target.value), placeholder: "AIza\u2026", style: {
|
|
233
|
+
width: '100%', boxSizing: 'border-box', fontSize: 12,
|
|
234
|
+
border: '1px solid #d1d5db', borderRadius: 6, padding: '7px 10px',
|
|
235
|
+
outline: 'none', fontFamily: 'monospace',
|
|
236
|
+
background: '#f9fafb',
|
|
237
|
+
}, onKeyDown: e => { if (e.key === 'Enter')
|
|
238
|
+
save(); } }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: 10, display: 'flex', gap: 8, alignItems: 'center' }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: save, style: {
|
|
239
|
+
background: '#7c3aed', color: '#fff', border: 'none', borderRadius: 6,
|
|
240
|
+
padding: '6px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
241
|
+
}, children: saved ? '✓ Saved' : 'Save key' }), draft && ((0, jsx_runtime_1.jsx)("button", { onClick: () => { setDraft(''); (0, gemini_1.setStoredApiKey)(''); onSave(''); }, style: { fontSize: 11, color: '#94a3b8', background: 'none', border: 'none', cursor: 'pointer' }, children: "Clear" }))] }), (0, jsx_runtime_1.jsxs)("p", { style: { marginTop: 16, fontSize: 10, color: '#9ca3af', lineHeight: 1.6 }, children: ["Get a free key at", ' ', (0, jsx_runtime_1.jsx)("span", { style: { color: '#7c3aed', fontWeight: 600 }, children: "aistudio.google.com" }), ' ', "\u2192 Get API key. The free tier is sufficient for development use."] }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: 18, paddingTop: 14, borderTop: '1px solid #e2e8f0' }, children: [(0, jsx_runtime_1.jsx)("label", { style: { display: 'block', fontSize: 11, fontWeight: 600, color: '#374151', marginBottom: 4 }, children: "Scan Preset" }), (0, jsx_runtime_1.jsxs)("select", { value: preset, onChange: e => onPresetChange(e.target.value), style: {
|
|
242
|
+
width: '100%', boxSizing: 'border-box', fontSize: 12,
|
|
243
|
+
border: '1px solid #d1d5db', borderRadius: 6, padding: '7px 10px',
|
|
244
|
+
outline: 'none', background: '#f9fafb', color: '#111827',
|
|
245
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "fast", children: "Fast" }), (0, jsx_runtime_1.jsx)("option", { value: "full", children: "Full" })] }), (0, jsx_runtime_1.jsx)("p", { style: { margin: '8px 0 0', fontSize: 10, color: '#64748b', lineHeight: 1.6 }, children: "`fast` runs the default rule set. `full` also includes heavier checks like `backgroundImages`." })] })] }));
|
|
100
246
|
};
|
|
101
247
|
// ─── Main Overlay ──────────────────────────────────────────────────────────────
|
|
102
|
-
const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', debounce = 750, }) => {
|
|
248
|
+
const WcagDevOverlay = ({ level = 'AA', preset = 'fast', rules, position = 'bottom-right', debounce = 750, }) => {
|
|
103
249
|
var _a, _b, _c;
|
|
104
|
-
const [open, setOpen] = (0, react_1.useState)(() => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
250
|
+
const [open, setOpen] = (0, react_1.useState)(() => { try {
|
|
251
|
+
return sessionStorage.getItem('wcag-open') === '1';
|
|
252
|
+
}
|
|
253
|
+
catch (_a) {
|
|
254
|
+
return false;
|
|
255
|
+
} });
|
|
256
|
+
const [view, setView] = (0, react_1.useState)('list');
|
|
112
257
|
const [tab, setTab] = (0, react_1.useState)('violations');
|
|
113
258
|
const [filter, setFilter] = (0, react_1.useState)('all');
|
|
114
259
|
const [scanning, setScanning] = (0, react_1.useState)(false);
|
|
115
260
|
const [results, setResults] = (0, react_1.useState)(null);
|
|
116
261
|
const [lastScan, setLastScan] = (0, react_1.useState)(null);
|
|
117
262
|
const [pinnedEl, setPinnedEl] = (0, react_1.useState)(null);
|
|
263
|
+
const [apiKey, setApiKey] = (0, react_1.useState)(() => (0, gemini_1.getStoredApiKey)());
|
|
264
|
+
const [activePreset, setActivePreset] = (0, react_1.useState)(preset);
|
|
118
265
|
// Drag
|
|
119
266
|
const [pos, setPos] = (0, react_1.useState)(null);
|
|
120
267
|
const dragging = (0, react_1.useRef)(false);
|
|
@@ -124,6 +271,8 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
|
|
|
124
271
|
const overlayRef = (0, react_1.useRef)(null);
|
|
125
272
|
const scanningRef = (0, react_1.useRef)(false);
|
|
126
273
|
const cooldownRef = (0, react_1.useRef)(false);
|
|
274
|
+
const pendingScanRef = (0, react_1.useRef)(false);
|
|
275
|
+
const scanTokenRef = (0, react_1.useRef)(0);
|
|
127
276
|
// ── Persist open state ────────────────────────────────────────────────────
|
|
128
277
|
(0, react_1.useEffect)(() => {
|
|
129
278
|
try {
|
|
@@ -135,42 +284,55 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
|
|
|
135
284
|
}, [open]);
|
|
136
285
|
// ── Scan ──────────────────────────────────────────────────────────────────
|
|
137
286
|
const scan = (0, react_1.useCallback)(async () => {
|
|
138
|
-
if (
|
|
287
|
+
if (timerRef.current) {
|
|
288
|
+
clearTimeout(timerRef.current);
|
|
289
|
+
timerRef.current = null;
|
|
290
|
+
}
|
|
291
|
+
if (scanningRef.current) {
|
|
292
|
+
pendingScanRef.current = true;
|
|
139
293
|
return;
|
|
294
|
+
}
|
|
295
|
+
const token = ++scanTokenRef.current;
|
|
296
|
+
// Set cooldown BEFORE clearing scanningRef to close the observer gap
|
|
297
|
+
cooldownRef.current = true;
|
|
140
298
|
scanningRef.current = true;
|
|
141
299
|
setScanning(true);
|
|
142
300
|
try {
|
|
143
|
-
const res = await (0, browserScanner_1.scanBrowserPage)({ level, rules });
|
|
144
|
-
|
|
145
|
-
|
|
301
|
+
const res = await (0, browserScanner_1.scanBrowserPage)({ level, preset: activePreset, rules });
|
|
302
|
+
if (token === scanTokenRef.current) {
|
|
303
|
+
setResults(res);
|
|
304
|
+
setLastScan(new Date());
|
|
305
|
+
}
|
|
146
306
|
}
|
|
147
307
|
finally {
|
|
148
308
|
scanningRef.current = false;
|
|
149
309
|
setScanning(false);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
310
|
+
window.setTimeout(() => {
|
|
311
|
+
cooldownRef.current = false;
|
|
312
|
+
if (pendingScanRef.current) {
|
|
313
|
+
pendingScanRef.current = false;
|
|
314
|
+
void scan();
|
|
315
|
+
}
|
|
316
|
+
}, 300);
|
|
153
317
|
}
|
|
154
|
-
}, [level, rules]);
|
|
318
|
+
}, [activePreset, level, rules]);
|
|
155
319
|
(0, react_1.useEffect)(() => { scan(); }, [scan]);
|
|
156
320
|
// ── MutationObserver ──────────────────────────────────────────────────────
|
|
157
321
|
(0, react_1.useEffect)(() => {
|
|
158
322
|
observerRef.current = new MutationObserver((mutations) => {
|
|
159
323
|
if (scanningRef.current || cooldownRef.current)
|
|
160
324
|
return;
|
|
161
|
-
if (overlayRef.current &&
|
|
162
|
-
mutations.every(m => overlayRef.current.contains(m.target)))
|
|
325
|
+
if (overlayRef.current && mutations.every(m => overlayRef.current.contains(m.target)))
|
|
163
326
|
return;
|
|
164
327
|
if (timerRef.current)
|
|
165
328
|
clearTimeout(timerRef.current);
|
|
166
|
-
timerRef.current = setTimeout(
|
|
329
|
+
timerRef.current = setTimeout(() => {
|
|
330
|
+
timerRef.current = null;
|
|
331
|
+
void scan();
|
|
332
|
+
}, debounce);
|
|
167
333
|
});
|
|
168
334
|
observerRef.current.observe(document.body, {
|
|
169
|
-
childList: true,
|
|
170
|
-
subtree: true,
|
|
171
|
-
attributes: true,
|
|
172
|
-
// 'style' excluded — our highlight helper modifies inline styles on
|
|
173
|
-
// page elements which would otherwise cause an infinite rescan loop
|
|
335
|
+
childList: true, subtree: true, attributes: true,
|
|
174
336
|
attributeFilter: ['class', 'hidden', 'aria-hidden', 'role', 'alt', 'src', 'href'],
|
|
175
337
|
});
|
|
176
338
|
return () => {
|
|
@@ -181,33 +343,30 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
|
|
|
181
343
|
clearAllHighlights();
|
|
182
344
|
};
|
|
183
345
|
}, [scan, debounce]);
|
|
184
|
-
// ── Keyboard
|
|
346
|
+
// ── Keyboard Alt+Shift+W ──────────────────────────────────────────────────
|
|
185
347
|
(0, react_1.useEffect)(() => {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
window.addEventListener('keydown', handler);
|
|
191
|
-
return () => window.removeEventListener('keydown', handler);
|
|
348
|
+
const h = (e) => { if (e.altKey && e.shiftKey && e.key === 'W')
|
|
349
|
+
setOpen(o => !o); };
|
|
350
|
+
window.addEventListener('keydown', h);
|
|
351
|
+
return () => window.removeEventListener('keydown', h);
|
|
192
352
|
}, []);
|
|
193
|
-
// ── Pin
|
|
353
|
+
// ── Pin ───────────────────────────────────────────────────────────────────
|
|
194
354
|
const handlePin = (0, react_1.useCallback)((el) => {
|
|
195
|
-
const nowPinned =
|
|
355
|
+
const nowPinned = togglePin(el);
|
|
196
356
|
setPinnedEl(nowPinned ? el : null);
|
|
197
357
|
}, []);
|
|
198
358
|
// ── Drag ──────────────────────────────────────────────────────────────────
|
|
199
359
|
const onDragStart = (e) => {
|
|
200
360
|
var _a, _b, _c;
|
|
201
361
|
dragging.current = true;
|
|
202
|
-
const
|
|
203
|
-
dragOffset.current = { x: e.clientX - ((_b =
|
|
362
|
+
const r = (_a = overlayRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
363
|
+
dragOffset.current = { x: e.clientX - ((_b = r === null || r === void 0 ? void 0 : r.left) !== null && _b !== void 0 ? _b : 0), y: e.clientY - ((_c = r === null || r === void 0 ? void 0 : r.top) !== null && _c !== void 0 ? _c : 0) };
|
|
204
364
|
e.preventDefault();
|
|
205
365
|
};
|
|
206
366
|
(0, react_1.useEffect)(() => {
|
|
207
367
|
const onMove = (e) => {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
setPos({ x: e.clientX - dragOffset.current.x, y: e.clientY - dragOffset.current.y });
|
|
368
|
+
if (dragging.current)
|
|
369
|
+
setPos({ x: e.clientX - dragOffset.current.x, y: e.clientY - dragOffset.current.y });
|
|
211
370
|
};
|
|
212
371
|
const onUp = () => { dragging.current = false; };
|
|
213
372
|
window.addEventListener('mousemove', onMove);
|
|
@@ -230,58 +389,10 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
|
|
|
230
389
|
const anchorStyle = pos
|
|
231
390
|
? { position: 'fixed', top: pos.y, left: pos.x, zIndex: 2147483647, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' }
|
|
232
391
|
: { position: 'fixed', bottom: 20, [isLeft ? 'left' : 'right']: 20, zIndex: 2147483647, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' };
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
},
|
|
392
|
+
const btnBg = scanning ? '#64748b' : vCount > 0 ? '#7c3aed' : '#16a34a';
|
|
393
|
+
const iconBtnStyle = {
|
|
394
|
+
background: 'none', border: 'none', cursor: 'pointer',
|
|
395
|
+
color: '#64748b', fontSize: 14, padding: '2px 5px', lineHeight: 1, borderRadius: 4,
|
|
285
396
|
};
|
|
286
397
|
const chip = (color, count) => ({
|
|
287
398
|
display: 'flex', alignItems: 'center', gap: 3, fontSize: 11, fontWeight: 600,
|
|
@@ -302,17 +413,34 @@ const WcagDevOverlay = ({ level = 'AA', rules, position = 'bottom-right', deboun
|
|
|
302
413
|
padding: '4px 10px', fontSize: 10, fontWeight: 600,
|
|
303
414
|
cursor: scanning ? 'wait' : 'pointer', opacity: scanning ? 0.55 : 1,
|
|
304
415
|
};
|
|
305
|
-
|
|
416
|
+
const viewActive = (v) => ({
|
|
417
|
+
...iconBtnStyle,
|
|
418
|
+
color: view === v ? '#7c3aed' : '#64748b',
|
|
419
|
+
background: view === v ? '#f5f3ff' : 'none',
|
|
420
|
+
});
|
|
421
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: overlayRef, style: anchorStyle, "data-wcag-overlay": "true", children: [open && ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
422
|
+
position: 'absolute', bottom: pos ? undefined : 48, top: pos ? 48 : undefined,
|
|
423
|
+
[isLeft ? 'left' : 'right']: 0, width: 410, maxHeight: '75vh',
|
|
424
|
+
background: '#fff', borderRadius: 10,
|
|
425
|
+
boxShadow: '0 8px 40px rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.07)',
|
|
426
|
+
display: 'flex', flexDirection: 'column', overflow: 'hidden',
|
|
427
|
+
}, role: "dialog", "aria-label": "WCAG Dev Inspector", children: [(0, jsx_runtime_1.jsxs)("div", { style: {
|
|
428
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
429
|
+
padding: '10px 12px', background: '#0f172a',
|
|
430
|
+
cursor: 'grab', userSelect: 'none', flexShrink: 0,
|
|
431
|
+
}, 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
432
|
fontSize: 9, fontWeight: 700, color: '#a78bfa',
|
|
307
433
|
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:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
434
|
+
}, children: level }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setView(v => v === 'settings' ? 'list' : 'settings'), style: viewActive('settings'), title: "Settings", children: "\u2699" }), (0, jsx_runtime_1.jsx)("button", { onClick: scan, disabled: scanning, title: "Rescan", style: iconBtnStyle, children: scanning ? '⏳' : '↻' }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setOpen(false), title: "Close (Alt+Shift+W)", style: iconBtnStyle, children: "\u2715" })] }), view === 'settings' ? ((0, jsx_runtime_1.jsx)(SettingsPanel, { apiKey: apiKey, preset: activePreset, onSave: k => setApiKey(k), onPresetChange: setActivePreset })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 6, padding: '8px 12px', borderBottom: '1px solid #f1f5f9', background: '#f8fafc', flexShrink: 0 }, 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' : ''] }), apiKey && (0, jsx_runtime_1.jsx)("span", { style: { ...chip('#7c3aed', 1), marginLeft: 'auto' }, title: "AI suggestions enabled", children: "\u2728 AI" })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', borderBottom: '1px solid #f1f5f9', padding: '0 12px', gap: 2, flexShrink: 0 }, 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: { marginLeft: 'auto', fontSize: 10, border: '1px solid #e2e8f0', borderRadius: 5, padding: '3px 6px', color: '#475569', background: '#fff', cursor: 'pointer', outline: 'none' }, 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: { flex: 1, overflowY: 'auto', padding: '10px' }, 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, apiKey: apiKey }, `${item.rule}-${i}`)))] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '7px 12px', borderTop: '1px solid #f1f5f9', background: '#f8fafc', fontSize: 10, color: '#94a3b8', flexShrink: 0 }, 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: {
|
|
435
|
+
display: 'flex', alignItems: 'center', gap: 6,
|
|
436
|
+
background: btnBg, color: '#fff', border: 'none', borderRadius: 22,
|
|
437
|
+
padding: '8px 14px', fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
|
438
|
+
boxShadow: '0 2px 12px rgba(0,0,0,0.2)', userSelect: 'none',
|
|
439
|
+
}, 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
440
|
? (0, jsx_runtime_1.jsx)("span", { style: { opacity: 0.8, fontSize: 11 }, children: "scanning\u2026" })
|
|
313
441
|
: vCount > 0
|
|
314
|
-
? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { style:
|
|
315
|
-
: (0, jsx_runtime_1.jsx)("span", { style:
|
|
442
|
+
? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { style: { background: 'rgba(255,255,255,0.22)', borderRadius: 9, padding: '1px 6px', fontSize: 11, fontWeight: 700 }, children: vCount }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 11 }, children: " issues" })] })
|
|
443
|
+
: (0, jsx_runtime_1.jsx)("span", { style: { background: 'rgba(255,255,255,0.22)', borderRadius: 9, padding: '1px 6px', fontSize: 11, fontWeight: 700 }, children: "\u2713" })] })] }));
|
|
316
444
|
};
|
|
317
445
|
exports.WcagDevOverlay = WcagDevOverlay;
|
|
318
446
|
exports.default = exports.WcagDevOverlay;
|
|
@@ -2,10 +2,13 @@ import { ScannerOptions, Violation, Warning, Pass } from '../types';
|
|
|
2
2
|
export interface AnnotatedViolation extends Violation {
|
|
3
3
|
domElement?: Element;
|
|
4
4
|
elementPath?: string;
|
|
5
|
+
/** Precise nth-child CSS selector — use for querySelector to find element */
|
|
6
|
+
elementSelector?: string;
|
|
5
7
|
}
|
|
6
8
|
export interface AnnotatedWarning extends Warning {
|
|
7
9
|
domElement?: Element;
|
|
8
10
|
elementPath?: string;
|
|
11
|
+
elementSelector?: string;
|
|
9
12
|
}
|
|
10
13
|
export interface BrowserScanResults {
|
|
11
14
|
violations: AnnotatedViolation[];
|
|
@@ -14,5 +17,12 @@ export interface BrowserScanResults {
|
|
|
14
17
|
duration: number;
|
|
15
18
|
}
|
|
16
19
|
export declare function scanBrowserPage(options?: ScannerOptions): Promise<BrowserScanResults>;
|
|
17
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Build a precise nth-child CSS selector path for an element.
|
|
22
|
+
* This is unambiguous and always finds the exact element.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getNthChildSelector(el: Element): string;
|
|
25
|
+
/** Build a human-readable breadcrumb label for an element. */
|
|
18
26
|
export declare function getElementPath(el: Element): string;
|
|
27
|
+
export declare function findElement(item: Violation | Warning, doc: Document): Element | null;
|
|
28
|
+
export declare function findBySnippet(snippet: string, doc: Document): Element | null;
|