@a13y/devtools 0.1.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.
@@ -0,0 +1,452 @@
1
+ import { isDevelopment } from '@a13y/core/runtime/env';
2
+
3
+ // @a13y/devtools - Development-time validators (tree-shakeable in production)
4
+
5
+
6
+ // src/runtime/warnings/warning-system.ts
7
+ var Styles = {
8
+ reset: "\x1B[0m",
9
+ bold: "\x1B[1m",
10
+ dim: "\x1B[2m",
11
+ blue: "\x1B[34m",
12
+ cyan: "\x1B[36m",
13
+ gray: "\x1B[90m",
14
+ // Backgrounds
15
+ bgRed: "\x1B[41m",
16
+ bgYellow: "\x1B[43m",
17
+ bgBlue: "\x1B[44m"
18
+ };
19
+ var style = (text, ...styles) => {
20
+ return `${styles.join("")}${text}${Styles.reset}`;
21
+ };
22
+ var WarningSystemClass = class {
23
+ constructor() {
24
+ this.config = {
25
+ enabled: true,
26
+ minSeverity: "warn",
27
+ showElement: true,
28
+ showStackTrace: true,
29
+ deduplicate: true,
30
+ onWarning: () => {
31
+ }
32
+ };
33
+ this.warningCache = /* @__PURE__ */ new Set();
34
+ }
35
+ /**
36
+ * Configure the warning system
37
+ */
38
+ configure(config) {
39
+ this.config = { ...this.config, ...config };
40
+ }
41
+ /**
42
+ * Emit a warning
43
+ */
44
+ warn(warning) {
45
+ if (!this.config.enabled) {
46
+ return;
47
+ }
48
+ const severityLevel = { info: 0, warn: 1, error: 2 };
49
+ if (severityLevel[warning.severity] < severityLevel[this.config.minSeverity]) {
50
+ return;
51
+ }
52
+ if (this.config.deduplicate) {
53
+ const key = this.getWarningKey(warning);
54
+ if (this.warningCache.has(key)) {
55
+ return;
56
+ }
57
+ this.warningCache.add(key);
58
+ }
59
+ if (this.config.onWarning) {
60
+ this.config.onWarning(warning);
61
+ }
62
+ this.printWarning(warning);
63
+ }
64
+ /**
65
+ * Clear warning cache
66
+ */
67
+ clearCache() {
68
+ this.warningCache.clear();
69
+ }
70
+ /**
71
+ * Generate a unique key for a warning
72
+ */
73
+ getWarningKey(warning) {
74
+ const parts = [warning.code, warning.message];
75
+ if (warning.element) {
76
+ const tag = warning.element.tagName.toLowerCase();
77
+ const id = warning.element.id;
78
+ const classes = Array.from(warning.element.classList).join(".");
79
+ parts.push(`${tag}#${id}.${classes}`);
80
+ }
81
+ return parts.join("|");
82
+ }
83
+ /**
84
+ * Print warning to console
85
+ */
86
+ printWarning(warning) {
87
+ const { severity, code, category, message, element, wcag, fixes } = warning;
88
+ const badge = this.getSeverityBadge(severity);
89
+ console.group(
90
+ `${badge} ${style(code, Styles.bold, Styles.cyan)} ${style(category, Styles.dim)}`
91
+ );
92
+ console.log(style(message, Styles.bold));
93
+ if (element && this.config.showElement) {
94
+ console.log(style("\nElement:", Styles.bold));
95
+ console.log(element);
96
+ }
97
+ if (wcag) {
98
+ console.log(style("\nWCAG:", Styles.bold), `${wcag.criterion} (Level ${wcag.level})`);
99
+ console.log(style("Learn more:", Styles.blue), wcag.url);
100
+ }
101
+ if (fixes.length > 0) {
102
+ console.log(style("\nHow to fix:", Styles.bold, Styles.cyan));
103
+ fixes.forEach((fix, index) => {
104
+ console.log(`${index + 1}. ${fix.description}`);
105
+ if (fix.example) {
106
+ console.log(style("\n Example:", Styles.dim));
107
+ console.log(style(` ${fix.example}`, Styles.dim, Styles.gray));
108
+ }
109
+ if (fix.learnMoreUrl) {
110
+ console.log(style(" Learn more:", Styles.blue), fix.learnMoreUrl);
111
+ }
112
+ });
113
+ }
114
+ if (severity === "error" && this.config.showStackTrace) {
115
+ console.log(style("\nStack trace:", Styles.dim));
116
+ console.trace();
117
+ }
118
+ console.groupEnd();
119
+ console.log("");
120
+ }
121
+ /**
122
+ * Get severity badge for console
123
+ */
124
+ getSeverityBadge(severity) {
125
+ switch (severity) {
126
+ case "error":
127
+ return style(" ERROR ", Styles.bold, Styles.bgRed);
128
+ case "warn":
129
+ return style(" WARN ", Styles.bold, Styles.bgYellow);
130
+ case "info":
131
+ return style(" INFO ", Styles.bold, Styles.bgBlue);
132
+ }
133
+ }
134
+ };
135
+ var WarningSystem = new WarningSystemClass();
136
+ var createWarning = (partial) => {
137
+ return {
138
+ fixes: [],
139
+ ...partial
140
+ };
141
+ };
142
+
143
+ // src/runtime/warnings/warning-types.ts
144
+ var WarningCodes = {
145
+ // Focus Management (001-099)
146
+ FOCUS_LOST: "A13Y001",
147
+ // Keyboard Navigation (100-199)
148
+ NOT_KEYBOARD_ACCESSIBLE: "A13Y100",
149
+ MISSING_KEYBOARD_HANDLER: "A13Y101",
150
+ INVALID_TABINDEX: "A13Y102",
151
+ // Accessible Name (200-299)
152
+ MISSING_ACCESSIBLE_NAME: "A13Y200",
153
+ REDUNDANT_ARIA: "A13Y303",
154
+ MISSING_REQUIRED_ARIA: "A13Y304"};
155
+ var WCAGUrls = {
156
+ "2.1.1": "https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html",
157
+ "2.4.3": "https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html",
158
+ "4.1.2": "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html"};
159
+
160
+ // src/runtime/invariants/invariants.ts
161
+ var invariant = (condition, message) => {
162
+ if (!isDevelopment()) {
163
+ return;
164
+ }
165
+ if (!condition) {
166
+ throw new Error(`[@a13y/devtools] Invariant violation: ${message}`);
167
+ }
168
+ };
169
+ var assertHasAccessibleName = (element, context) => {
170
+ if (!isDevelopment()) {
171
+ return;
172
+ }
173
+ import('@a13y/core/runtime/aria').then(({ getAccessibleName }) => {
174
+ const name = getAccessibleName(element);
175
+ if (!name || name.trim().length === 0) {
176
+ WarningSystem.warn(
177
+ createWarning({
178
+ code: WarningCodes.MISSING_ACCESSIBLE_NAME,
179
+ severity: "error",
180
+ category: "accessible-name",
181
+ message: `Element is missing an accessible name${context ? ` in ${context}` : ""}`,
182
+ element,
183
+ wcag: {
184
+ criterion: "4.1.2",
185
+ level: "A",
186
+ url: WCAGUrls["4.1.2"]
187
+ },
188
+ fixes: [
189
+ {
190
+ description: "Add an aria-label attribute",
191
+ example: `<${element.tagName.toLowerCase()} aria-label="Descriptive name">`
192
+ },
193
+ {
194
+ description: "Add text content",
195
+ example: `<${element.tagName.toLowerCase()}>Click me</${element.tagName.toLowerCase()}>`
196
+ },
197
+ {
198
+ description: "Use aria-labelledby to reference another element",
199
+ example: `<${element.tagName.toLowerCase()} aria-labelledby="label-id">`
200
+ }
201
+ ]
202
+ })
203
+ );
204
+ }
205
+ }).catch(() => {
206
+ });
207
+ };
208
+ var assertKeyboardAccessible = (element, context) => {
209
+ if (!isDevelopment()) {
210
+ return;
211
+ }
212
+ const isButton = element instanceof HTMLButtonElement;
213
+ const isLink = element instanceof HTMLAnchorElement && element.hasAttribute("href");
214
+ const isInput = element instanceof HTMLInputElement;
215
+ const hasTabindex = element.hasAttribute("tabindex");
216
+ const tabindex = hasTabindex ? parseInt(element.getAttribute("tabindex") || "0", 10) : -1;
217
+ const isAccessible = isButton || isLink || isInput || hasTabindex && tabindex >= 0;
218
+ if (!isAccessible) {
219
+ WarningSystem.warn(
220
+ createWarning({
221
+ code: WarningCodes.NOT_KEYBOARD_ACCESSIBLE,
222
+ severity: "error",
223
+ category: "keyboard-navigation",
224
+ message: `Element is not keyboard accessible${context ? ` in ${context}` : ""}`,
225
+ element,
226
+ wcag: {
227
+ criterion: "2.1.1",
228
+ level: "A",
229
+ url: WCAGUrls["2.1.1"]
230
+ },
231
+ fixes: [
232
+ {
233
+ description: "Use a semantic HTML element (button, a, input)",
234
+ example: `<button type="button">Click me</button>`
235
+ },
236
+ {
237
+ description: 'Add tabindex="0" to make it focusable',
238
+ example: `<${element.tagName.toLowerCase()} tabindex="0">`
239
+ },
240
+ {
241
+ description: "Add keyboard event handlers (Enter, Space)",
242
+ example: `element.addEventListener('keydown', (e) => {
243
+ if (e.key === 'Enter' || e.key === ' ') {
244
+ e.preventDefault();
245
+ // Handle activation
246
+ }
247
+ });`
248
+ }
249
+ ]
250
+ })
251
+ );
252
+ }
253
+ const hasClickHandler = element.getAttribute("onclick") !== null;
254
+ if (hasClickHandler && !isButton && !isLink) {
255
+ WarningSystem.warn(
256
+ createWarning({
257
+ code: WarningCodes.MISSING_KEYBOARD_HANDLER,
258
+ severity: "warn",
259
+ category: "keyboard-navigation",
260
+ message: `Element has click handler but no keyboard handlers${context ? ` in ${context}` : ""}`,
261
+ element,
262
+ wcag: {
263
+ criterion: "2.1.1",
264
+ level: "A",
265
+ url: WCAGUrls["2.1.1"]
266
+ },
267
+ fixes: [
268
+ {
269
+ description: "Add onKeyDown handler for Enter and Space keys",
270
+ example: `const handleKeyDown = (e: KeyboardEvent) => {
271
+ if (e.key === 'Enter' || e.key === ' ') {
272
+ e.preventDefault();
273
+ onClick(e);
274
+ }
275
+ };`
276
+ }
277
+ ]
278
+ })
279
+ );
280
+ }
281
+ };
282
+ var assertValidTabindex = (element) => {
283
+ if (!isDevelopment()) {
284
+ return;
285
+ }
286
+ const tabindex = element.getAttribute("tabindex");
287
+ if (tabindex === null) {
288
+ return;
289
+ }
290
+ const value = parseInt(tabindex, 10);
291
+ if (value > 0) {
292
+ WarningSystem.warn(
293
+ createWarning({
294
+ code: WarningCodes.INVALID_TABINDEX,
295
+ severity: "warn",
296
+ category: "keyboard-navigation",
297
+ message: `Positive tabindex (${value}) creates confusing tab order`,
298
+ element,
299
+ wcag: {
300
+ criterion: "2.4.3",
301
+ level: "A",
302
+ url: WCAGUrls["2.4.3"]
303
+ },
304
+ fixes: [
305
+ {
306
+ description: 'Use tabindex="0" to add element to natural tab order',
307
+ example: `<div tabindex="0">Focusable in natural order</div>`
308
+ },
309
+ {
310
+ description: 'Use tabindex="-1" to remove from tab order (programmatic focus only)',
311
+ example: `<div tabindex="-1">Not in tab order</div>`
312
+ },
313
+ {
314
+ description: "Restructure DOM to achieve desired focus order",
315
+ learnMoreUrl: "https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html"
316
+ }
317
+ ]
318
+ })
319
+ );
320
+ }
321
+ };
322
+ var assertValidAriaAttributes = (element) => {
323
+ if (!isDevelopment()) {
324
+ return;
325
+ }
326
+ const role = element.getAttribute("role");
327
+ const ariaAttributes = Array.from(element.attributes).filter(
328
+ (attr) => attr.name.startsWith("aria-")
329
+ );
330
+ if (!role && ariaAttributes.length > 0 && !isSemanticElement(element)) {
331
+ WarningSystem.warn(
332
+ createWarning({
333
+ code: WarningCodes.MISSING_REQUIRED_ARIA,
334
+ severity: "warn",
335
+ category: "aria-usage",
336
+ message: "Element has ARIA attributes but no explicit role",
337
+ element,
338
+ wcag: {
339
+ criterion: "4.1.2",
340
+ level: "A",
341
+ url: WCAGUrls["4.1.2"]
342
+ },
343
+ fixes: [
344
+ {
345
+ description: "Add an appropriate role attribute",
346
+ example: `<div role="button" aria-pressed="false">Toggle</div>`
347
+ },
348
+ {
349
+ description: "Use a semantic HTML element instead",
350
+ example: `<button aria-pressed="false">Toggle</button>`
351
+ }
352
+ ]
353
+ })
354
+ );
355
+ }
356
+ if (role && isSemanticElement(element)) {
357
+ const semanticRole = getImplicitRole(element);
358
+ if (role === semanticRole) {
359
+ WarningSystem.warn(
360
+ createWarning({
361
+ code: WarningCodes.REDUNDANT_ARIA,
362
+ severity: "info",
363
+ category: "aria-usage",
364
+ message: `Role "${role}" is redundant on <${element.tagName.toLowerCase()}>`,
365
+ element,
366
+ fixes: [
367
+ {
368
+ description: "Remove the redundant role attribute",
369
+ example: `<${element.tagName.toLowerCase()}> (role="${role}" is implicit)`
370
+ }
371
+ ]
372
+ })
373
+ );
374
+ }
375
+ }
376
+ };
377
+ var assertFocusVisible = (context) => {
378
+ if (!isDevelopment()) {
379
+ return;
380
+ }
381
+ setTimeout(() => {
382
+ const activeElement = document.activeElement;
383
+ if (!activeElement || activeElement === document.body) {
384
+ WarningSystem.warn(
385
+ createWarning({
386
+ code: WarningCodes.FOCUS_LOST,
387
+ severity: "warn",
388
+ category: "focus-management",
389
+ message: `Focus was lost${context ? ` after ${context}` : ""}`,
390
+ wcag: {
391
+ criterion: "2.4.3",
392
+ level: "A",
393
+ url: WCAGUrls["2.4.3"]
394
+ },
395
+ fixes: [
396
+ {
397
+ description: "Ensure focus is moved to an appropriate element",
398
+ example: `// After closing dialog
399
+ const returnElement = document.getElementById('trigger-button');
400
+ returnElement?.focus();`
401
+ },
402
+ {
403
+ description: "Use FocusManager to save and restore focus",
404
+ example: `import { FocusManager } from '@a13y/core/runtime/focus';
405
+
406
+ const restore = FocusManager.saveFocus();
407
+ // ... perform action ...
408
+ restore();`
409
+ }
410
+ ]
411
+ })
412
+ );
413
+ }
414
+ }, 100);
415
+ };
416
+ var isSemanticElement = (element) => {
417
+ const tag = element.tagName.toLowerCase();
418
+ const semanticTags = [
419
+ "button",
420
+ "a",
421
+ "input",
422
+ "select",
423
+ "textarea",
424
+ "nav",
425
+ "main",
426
+ "article",
427
+ "section",
428
+ "header",
429
+ "footer",
430
+ "aside"
431
+ ];
432
+ return semanticTags.includes(tag);
433
+ };
434
+ var getImplicitRole = (element) => {
435
+ const tag = element.tagName.toLowerCase();
436
+ const roleMap = {
437
+ button: "button",
438
+ a: "link",
439
+ nav: "navigation",
440
+ main: "main",
441
+ article: "article",
442
+ section: "region",
443
+ header: "banner",
444
+ footer: "contentinfo",
445
+ aside: "complementary"
446
+ };
447
+ return roleMap[tag] || null;
448
+ };
449
+
450
+ export { assertFocusVisible, assertHasAccessibleName, assertKeyboardAccessible, assertValidAriaAttributes, assertValidTabindex, invariant };
451
+ //# sourceMappingURL=index.js.map
452
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/runtime/warnings/warning-system.ts","../../../src/runtime/warnings/warning-types.ts","../../../src/runtime/invariants/invariants.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @a13y/devtools - Warning System\n * Structured warning system with styled console output\n */\n\nimport type { AccessibilityWarning, WarningSeverity } from './warning-types';\n\n/**\n * Console styling for warnings\n */\nconst Styles = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n\n // Colors\n red: '\\x1b[31m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n\n // Backgrounds\n bgRed: '\\x1b[41m',\n bgYellow: '\\x1b[43m',\n bgBlue: '\\x1b[44m',\n} as const;\n\n/**\n * Format a message with console styling\n */\nconst style = (text: string, ...styles: string[]): string => {\n return `${styles.join('')}${text}${Styles.reset}`;\n};\n\n/**\n * Configuration for warning system\n */\nexport interface WarningSystemConfig {\n /**\n * Enable/disable warnings\n * @default true in development\n */\n enabled?: boolean;\n\n /**\n * Minimum severity to show\n * @default 'warn'\n */\n minSeverity?: WarningSeverity;\n\n /**\n * Show element in console\n * @default true\n */\n showElement?: boolean;\n\n /**\n * Show stack traces for errors\n * @default true\n */\n showStackTrace?: boolean;\n\n /**\n * Deduplicate warnings\n * @default true\n */\n deduplicate?: boolean;\n\n /**\n * Custom warning handler\n */\n onWarning?: (warning: AccessibilityWarning) => void;\n}\n\n/**\n * Warning system class\n */\nclass WarningSystemClass {\n private config: Required<WarningSystemConfig> = {\n enabled: true,\n minSeverity: 'warn',\n showElement: true,\n showStackTrace: true,\n deduplicate: true,\n onWarning: () => {},\n };\n\n private warningCache = new Set<string>();\n\n /**\n * Configure the warning system\n */\n configure(config: WarningSystemConfig): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Emit a warning\n */\n warn(warning: AccessibilityWarning): void {\n if (!this.config.enabled) {\n return;\n }\n\n // Check severity threshold\n const severityLevel = { info: 0, warn: 1, error: 2 };\n if (severityLevel[warning.severity] < severityLevel[this.config.minSeverity]) {\n return;\n }\n\n // Deduplicate\n if (this.config.deduplicate) {\n const key = this.getWarningKey(warning);\n if (this.warningCache.has(key)) {\n return;\n }\n this.warningCache.add(key);\n }\n\n // Custom handler\n if (this.config.onWarning) {\n this.config.onWarning(warning);\n }\n\n // Console output\n this.printWarning(warning);\n }\n\n /**\n * Clear warning cache\n */\n clearCache(): void {\n this.warningCache.clear();\n }\n\n /**\n * Generate a unique key for a warning\n */\n private getWarningKey(warning: AccessibilityWarning): string {\n const parts = [warning.code, warning.message];\n\n if (warning.element) {\n // Use element's tag, id, and classes for uniqueness\n const tag = warning.element.tagName.toLowerCase();\n const id = warning.element.id;\n const classes = Array.from(warning.element.classList).join('.');\n parts.push(`${tag}#${id}.${classes}`);\n }\n\n return parts.join('|');\n }\n\n /**\n * Print warning to console\n */\n private printWarning(warning: AccessibilityWarning): void {\n const { severity, code, category, message, element, wcag, fixes } = warning;\n\n // Severity badge\n const badge = this.getSeverityBadge(severity);\n\n // Header\n console.group(\n `${badge} ${style(code, Styles.bold, Styles.cyan)} ${style(category, Styles.dim)}`\n );\n\n // Message\n console.log(style(message, Styles.bold));\n\n // Element\n if (element && this.config.showElement) {\n console.log(style('\\nElement:', Styles.bold));\n console.log(element);\n }\n\n // WCAG info\n if (wcag) {\n console.log(style('\\nWCAG:', Styles.bold), `${wcag.criterion} (Level ${wcag.level})`);\n console.log(style('Learn more:', Styles.blue), wcag.url);\n }\n\n // Fix suggestions\n if (fixes.length > 0) {\n console.log(style('\\nHow to fix:', Styles.bold, Styles.cyan));\n fixes.forEach((fix, index) => {\n console.log(`${index + 1}. ${fix.description}`);\n\n if (fix.example) {\n console.log(style('\\n Example:', Styles.dim));\n console.log(style(` ${fix.example}`, Styles.dim, Styles.gray));\n }\n\n if (fix.learnMoreUrl) {\n console.log(style(' Learn more:', Styles.blue), fix.learnMoreUrl);\n }\n });\n }\n\n // Stack trace for errors\n if (severity === 'error' && this.config.showStackTrace) {\n console.log(style('\\nStack trace:', Styles.dim));\n console.trace();\n }\n\n console.groupEnd();\n console.log(''); // Empty line for spacing\n }\n\n /**\n * Get severity badge for console\n */\n private getSeverityBadge(severity: WarningSeverity): string {\n switch (severity) {\n case 'error':\n return style(' ERROR ', Styles.bold, Styles.bgRed);\n case 'warn':\n return style(' WARN ', Styles.bold, Styles.bgYellow);\n case 'info':\n return style(' INFO ', Styles.bold, Styles.bgBlue);\n }\n }\n}\n\n/**\n * Singleton warning system\n */\nexport const WarningSystem = new WarningSystemClass();\n\n/**\n * Helper to create a warning\n */\nexport const createWarning = (\n partial: Omit<AccessibilityWarning, 'fixes'> & {\n fixes?: AccessibilityWarning['fixes'];\n }\n): AccessibilityWarning => {\n return {\n fixes: [],\n ...partial,\n };\n};\n","/**\n * @a13y/devtools - Warning Types\n * Type definitions for accessibility warnings\n */\n\n/**\n * Warning severity level\n */\nexport type WarningSeverity = 'error' | 'warn' | 'info';\n\n/**\n * WCAG level for the violation\n */\nexport type WCAGLevel = 'A' | 'AA' | 'AAA';\n\n/**\n * Category of accessibility issue\n */\nexport type WarningCategory =\n | 'focus-management'\n | 'keyboard-navigation'\n | 'aria-usage'\n | 'accessible-name'\n | 'semantic-html'\n | 'wcag-compliance'\n | 'performance';\n\n/**\n * Fix suggestion for a warning\n */\nexport interface FixSuggestion {\n /**\n * Description of how to fix the issue\n */\n description: string;\n\n /**\n * Code example showing the fix\n */\n example?: string;\n\n /**\n * Link to documentation\n */\n learnMoreUrl?: string;\n}\n\n/**\n * Accessibility warning\n */\nexport interface AccessibilityWarning {\n /**\n * Unique warning code (e.g., \"A13Y001\")\n */\n code: string;\n\n /**\n * Severity level\n */\n severity: WarningSeverity;\n\n /**\n * Category of the issue\n */\n category: WarningCategory;\n\n /**\n * Human-readable message\n */\n message: string;\n\n /**\n * Element that caused the warning\n */\n element?: Element;\n\n /**\n * WCAG criterion violated\n */\n wcag?: {\n criterion: string;\n level: WCAGLevel;\n url: string;\n };\n\n /**\n * Suggestions for fixing the issue\n */\n fixes: FixSuggestion[];\n\n /**\n * Additional context\n */\n context?: Record<string, unknown>;\n}\n\n/**\n * Warning code definitions\n */\nexport const WarningCodes = {\n // Focus Management (001-099)\n FOCUS_LOST: 'A13Y001',\n FOCUS_NOT_VISIBLE: 'A13Y002',\n FOCUS_TRAP_BROKEN: 'A13Y003',\n FOCUS_ORDER_INVALID: 'A13Y004',\n FOCUS_NOT_RESTORED: 'A13Y005',\n\n // Keyboard Navigation (100-199)\n NOT_KEYBOARD_ACCESSIBLE: 'A13Y100',\n MISSING_KEYBOARD_HANDLER: 'A13Y101',\n INVALID_TABINDEX: 'A13Y102',\n ROVING_TABINDEX_BROKEN: 'A13Y103',\n MISSING_ESC_HANDLER: 'A13Y104',\n\n // Accessible Name (200-299)\n MISSING_ACCESSIBLE_NAME: 'A13Y200',\n EMPTY_ACCESSIBLE_NAME: 'A13Y201',\n DUPLICATE_ID: 'A13Y202',\n INVALID_LABELLEDBY: 'A13Y203',\n PLACEHOLDER_AS_LABEL: 'A13Y204',\n\n // ARIA Usage (300-399)\n INVALID_ARIA_ROLE: 'A13Y300',\n INVALID_ARIA_ATTRIBUTE: 'A13Y301',\n CONFLICTING_ARIA: 'A13Y302',\n REDUNDANT_ARIA: 'A13Y303',\n MISSING_REQUIRED_ARIA: 'A13Y304',\n INVALID_ARIA_VALUE: 'A13Y305',\n\n // Semantic HTML (400-499)\n DIV_BUTTON: 'A13Y400',\n DIV_LINK: 'A13Y401',\n MISSING_LANDMARK: 'A13Y402',\n INVALID_NESTING: 'A13Y403',\n\n // WCAG Compliance (500-599)\n CONTRAST_INSUFFICIENT: 'A13Y500',\n TARGET_SIZE_TOO_SMALL: 'A13Y501',\n ANIMATION_NO_REDUCE_MOTION: 'A13Y502',\n\n // Performance (600-699)\n TOO_MANY_LIVE_REGIONS: 'A13Y600',\n EXCESSIVE_ANNOUNCEMENTS: 'A13Y601',\n} as const;\n\n/**\n * WCAG criterion URLs\n */\nexport const WCAGUrls = {\n '1.3.1': 'https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html',\n '1.4.3': 'https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html',\n '2.1.1': 'https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html',\n '2.1.2': 'https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html',\n '2.4.3': 'https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html',\n '2.4.7': 'https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html',\n '2.5.5': 'https://www.w3.org/WAI/WCAG22/Understanding/target-size.html',\n '4.1.2': 'https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html',\n '4.1.3': 'https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html',\n} as const;\n","/**\n * @a13y/devtools - Invariants\n * Development-only assertions for accessibility constraints\n */\n\nimport { isDevelopment } from '@a13y/core/runtime/env';\nimport { createWarning, WarningSystem } from '../warnings/warning-system';\nimport { WarningCodes, WCAGUrls } from '../warnings/warning-types';\n\n/**\n * Assert that a condition is true, throw error if false (development only)\n */\nexport const invariant = (condition: boolean, message: string): void => {\n if (!isDevelopment()) {\n return;\n }\n\n if (!condition) {\n throw new Error(`[@a13y/devtools] Invariant violation: ${message}`);\n }\n};\n\n/**\n * Assert that an element has an accessible name\n */\nexport const assertHasAccessibleName = (element: Element, context?: string): void => {\n if (!isDevelopment()) {\n return;\n }\n\n // Import getAccessibleName at runtime to avoid circular deps\n import('@a13y/core/runtime/aria')\n .then(({ getAccessibleName }) => {\n const name = getAccessibleName(element);\n\n if (!name || name.trim().length === 0) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.MISSING_ACCESSIBLE_NAME,\n severity: 'error',\n category: 'accessible-name',\n message: `Element is missing an accessible name${context ? ` in ${context}` : ''}`,\n element,\n wcag: {\n criterion: '4.1.2',\n level: 'A',\n url: WCAGUrls['4.1.2'],\n },\n fixes: [\n {\n description: 'Add an aria-label attribute',\n example: `<${element.tagName.toLowerCase()} aria-label=\"Descriptive name\">`,\n },\n {\n description: 'Add text content',\n example: `<${element.tagName.toLowerCase()}>Click me</${element.tagName.toLowerCase()}>`,\n },\n {\n description: 'Use aria-labelledby to reference another element',\n example: `<${element.tagName.toLowerCase()} aria-labelledby=\"label-id\">`,\n },\n ],\n })\n );\n }\n })\n .catch(() => {\n // Silently fail if @a13y/core is not available\n });\n};\n\n/**\n * Assert that an element is keyboard accessible\n */\nexport const assertKeyboardAccessible = (element: Element, context?: string): void => {\n if (!isDevelopment()) {\n return;\n }\n\n const isButton = element instanceof HTMLButtonElement;\n const isLink = element instanceof HTMLAnchorElement && element.hasAttribute('href');\n const isInput = element instanceof HTMLInputElement;\n const hasTabindex = element.hasAttribute('tabindex');\n const tabindex = hasTabindex ? parseInt(element.getAttribute('tabindex') || '0', 10) : -1;\n\n // Check if element is naturally keyboard accessible or has tabindex >= 0\n const isAccessible = isButton || isLink || isInput || (hasTabindex && tabindex >= 0);\n\n if (!isAccessible) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.NOT_KEYBOARD_ACCESSIBLE,\n severity: 'error',\n category: 'keyboard-navigation',\n message: `Element is not keyboard accessible${context ? ` in ${context}` : ''}`,\n element,\n wcag: {\n criterion: '2.1.1',\n level: 'A',\n url: WCAGUrls['2.1.1'],\n },\n fixes: [\n {\n description: 'Use a semantic HTML element (button, a, input)',\n example: `<button type=\"button\">Click me</button>`,\n },\n {\n description: 'Add tabindex=\"0\" to make it focusable',\n example: `<${element.tagName.toLowerCase()} tabindex=\"0\">`,\n },\n {\n description: 'Add keyboard event handlers (Enter, Space)',\n example: `element.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n // Handle activation\n }\n});`,\n },\n ],\n })\n );\n }\n\n // Check for click handler without keyboard handler\n const hasClickHandler = element.getAttribute('onclick') !== null;\n if (hasClickHandler && !isButton && !isLink) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.MISSING_KEYBOARD_HANDLER,\n severity: 'warn',\n category: 'keyboard-navigation',\n message: `Element has click handler but no keyboard handlers${context ? ` in ${context}` : ''}`,\n element,\n wcag: {\n criterion: '2.1.1',\n level: 'A',\n url: WCAGUrls['2.1.1'],\n },\n fixes: [\n {\n description: 'Add onKeyDown handler for Enter and Space keys',\n example: `const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick(e);\n }\n};`,\n },\n ],\n })\n );\n }\n};\n\n/**\n * Assert that tabindex value is valid\n */\nexport const assertValidTabindex = (element: Element): void => {\n if (!isDevelopment()) {\n return;\n }\n\n const tabindex = element.getAttribute('tabindex');\n if (tabindex === null) {\n return;\n }\n\n const value = parseInt(tabindex, 10);\n\n // Warn about positive tabindex (antipattern)\n if (value > 0) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.INVALID_TABINDEX,\n severity: 'warn',\n category: 'keyboard-navigation',\n message: `Positive tabindex (${value}) creates confusing tab order`,\n element,\n wcag: {\n criterion: '2.4.3',\n level: 'A',\n url: WCAGUrls['2.4.3'],\n },\n fixes: [\n {\n description: 'Use tabindex=\"0\" to add element to natural tab order',\n example: `<div tabindex=\"0\">Focusable in natural order</div>`,\n },\n {\n description: 'Use tabindex=\"-1\" to remove from tab order (programmatic focus only)',\n example: `<div tabindex=\"-1\">Not in tab order</div>`,\n },\n {\n description: 'Restructure DOM to achieve desired focus order',\n learnMoreUrl: 'https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html',\n },\n ],\n })\n );\n }\n};\n\n/**\n * Assert that ARIA attributes are valid for the element's role\n */\nexport const assertValidAriaAttributes = (element: Element): void => {\n if (!isDevelopment()) {\n return;\n }\n\n const role = element.getAttribute('role');\n const ariaAttributes = Array.from(element.attributes).filter((attr) =>\n attr.name.startsWith('aria-')\n );\n\n // Check for ARIA attributes without role\n if (!role && ariaAttributes.length > 0 && !isSemanticElement(element)) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.MISSING_REQUIRED_ARIA,\n severity: 'warn',\n category: 'aria-usage',\n message: 'Element has ARIA attributes but no explicit role',\n element,\n wcag: {\n criterion: '4.1.2',\n level: 'A',\n url: WCAGUrls['4.1.2'],\n },\n fixes: [\n {\n description: 'Add an appropriate role attribute',\n example: `<div role=\"button\" aria-pressed=\"false\">Toggle</div>`,\n },\n {\n description: 'Use a semantic HTML element instead',\n example: `<button aria-pressed=\"false\">Toggle</button>`,\n },\n ],\n })\n );\n }\n\n // Check for redundant ARIA on semantic elements\n if (role && isSemanticElement(element)) {\n const semanticRole = getImplicitRole(element);\n if (role === semanticRole) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.REDUNDANT_ARIA,\n severity: 'info',\n category: 'aria-usage',\n message: `Role \"${role}\" is redundant on <${element.tagName.toLowerCase()}>`,\n element,\n fixes: [\n {\n description: 'Remove the redundant role attribute',\n example: `<${element.tagName.toLowerCase()}> (role=\"${role}\" is implicit)`,\n },\n ],\n })\n );\n }\n }\n};\n\n/**\n * Assert that focus is visible after an action\n */\nexport const assertFocusVisible = (context?: string): void => {\n if (!isDevelopment()) {\n return;\n }\n\n // Delay check to allow focus to settle\n setTimeout(() => {\n const activeElement = document.activeElement;\n\n if (!activeElement || activeElement === document.body) {\n WarningSystem.warn(\n createWarning({\n code: WarningCodes.FOCUS_LOST,\n severity: 'warn',\n category: 'focus-management',\n message: `Focus was lost${context ? ` after ${context}` : ''}`,\n wcag: {\n criterion: '2.4.3',\n level: 'A',\n url: WCAGUrls['2.4.3'],\n },\n fixes: [\n {\n description: 'Ensure focus is moved to an appropriate element',\n example: `// After closing dialog\nconst returnElement = document.getElementById('trigger-button');\nreturnElement?.focus();`,\n },\n {\n description: 'Use FocusManager to save and restore focus',\n example: `import { FocusManager } from '@a13y/core/runtime/focus';\n\nconst restore = FocusManager.saveFocus();\n// ... perform action ...\nrestore();`,\n },\n ],\n })\n );\n }\n }, 100);\n};\n\n/**\n * Check if element is a semantic HTML element\n */\nconst isSemanticElement = (element: Element): boolean => {\n const tag = element.tagName.toLowerCase();\n const semanticTags = [\n 'button',\n 'a',\n 'input',\n 'select',\n 'textarea',\n 'nav',\n 'main',\n 'article',\n 'section',\n 'header',\n 'footer',\n 'aside',\n ];\n return semanticTags.includes(tag);\n};\n\n/**\n * Get implicit ARIA role of an element\n */\nconst getImplicitRole = (element: Element): string | null => {\n const tag = element.tagName.toLowerCase();\n\n const roleMap: Record<string, string> = {\n button: 'button',\n a: 'link',\n nav: 'navigation',\n main: 'main',\n article: 'article',\n section: 'region',\n header: 'banner',\n footer: 'contentinfo',\n aside: 'complementary',\n };\n\n return roleMap[tag] || null;\n};\n"]}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @a13y/devtools - ARIA Validator
3
+ * Validates ARIA attributes and usage
4
+ */
5
+ /**
6
+ * ARIA validator class
7
+ */
8
+ declare class AriaValidator {
9
+ /**
10
+ * Validate ARIA attributes on an element
11
+ */
12
+ validateElement(element: Element): void;
13
+ /**
14
+ * Validate accessible name
15
+ */
16
+ validateAccessibleName(element: Element, context?: string): void;
17
+ /**
18
+ * Validate role attribute
19
+ */
20
+ private validateRole;
21
+ /**
22
+ * Validate ARIA attribute
23
+ */
24
+ private validateAriaAttribute;
25
+ /**
26
+ * Validate ID references in ARIA attributes
27
+ */
28
+ private validateIdReferences;
29
+ /**
30
+ * Validate required ARIA props for a role
31
+ */
32
+ private validateRequiredProps;
33
+ /**
34
+ * Check for redundant ARIA
35
+ */
36
+ private checkRedundantAria;
37
+ /**
38
+ * Check for conflicting ARIA attributes
39
+ */
40
+ private checkConflictingAria;
41
+ /**
42
+ * Get all ARIA attributes from an element
43
+ */
44
+ private getAriaAttributes;
45
+ }
46
+ /**
47
+ * Singleton ARIA validator
48
+ */
49
+ declare const ariaValidator: AriaValidator;
50
+
51
+ /**
52
+ * @a13y/devtools - Focus Validator
53
+ * Validates focus management and visual focus indicators
54
+ */
55
+ /**
56
+ * Focus validator class
57
+ */
58
+ declare class FocusValidator {
59
+ private focusHistory;
60
+ private isActive;
61
+ /**
62
+ * Start monitoring focus changes
63
+ */
64
+ start(): void;
65
+ /**
66
+ * Stop monitoring focus changes
67
+ */
68
+ stop(): void;
69
+ /**
70
+ * Validate that focus is visible
71
+ */
72
+ validateFocusVisible(element: Element): void;
73
+ /**
74
+ * Validate focus trap
75
+ */
76
+ validateFocusTrap(container: Element, expectedTrapped: boolean): void;
77
+ /**
78
+ * Validate focus order
79
+ */
80
+ validateFocusOrder(container: Element): void;
81
+ /**
82
+ * Track focus restoration after actions
83
+ */
84
+ expectFocusRestoration(expectedElement: Element, action: string): void;
85
+ private handleFocusIn;
86
+ private handleFocusOut;
87
+ private getFocusableElements;
88
+ }
89
+ /**
90
+ * Singleton focus validator
91
+ */
92
+ declare const focusValidator: FocusValidator;
93
+
94
+ /**
95
+ * @a13y/devtools - Keyboard Validator
96
+ * Validates keyboard accessibility
97
+ */
98
+ /**
99
+ * Keyboard validator class
100
+ */
101
+ declare class KeyboardValidator {
102
+ /**
103
+ * Validate that interactive elements are keyboard accessible
104
+ */
105
+ validateInteractiveElement(element: Element): void;
106
+ /**
107
+ * Validate that a container's children are reachable via keyboard
108
+ */
109
+ validateContainer(container: Element): void;
110
+ /**
111
+ * Validate roving tabindex implementation
112
+ */
113
+ validateRovingTabindex(container: Element): void;
114
+ /**
115
+ * Check for escape key handler in dialogs/modals
116
+ */
117
+ validateEscapeHandler(container: Element, shouldHaveEscape: boolean): void;
118
+ /**
119
+ * Analyze an element for keyboard accessibility
120
+ */
121
+ private analyzeElement;
122
+ /**
123
+ * Check if element is focusable
124
+ */
125
+ private isFocusable;
126
+ /**
127
+ * Find all interactive elements in a container
128
+ */
129
+ private findInteractiveElements;
130
+ /**
131
+ * Check for div/span styled as button (antipattern)
132
+ */
133
+ private checkForDivButton;
134
+ }
135
+ /**
136
+ * Singleton keyboard validator
137
+ */
138
+ declare const keyboardValidator: KeyboardValidator;
139
+
140
+ export { AriaValidator, FocusValidator, KeyboardValidator, ariaValidator, focusValidator, keyboardValidator };