@tanstack/devtools-a11y 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/dist/esm/core/components/IssueCard.d.ts +10 -0
  3. package/dist/esm/core/components/IssueCard.js +83 -0
  4. package/dist/esm/core/components/IssueCard.js.map +1 -0
  5. package/dist/esm/core/components/IssueList.d.ts +6 -0
  6. package/dist/esm/core/components/IssueList.js +134 -0
  7. package/dist/esm/core/components/IssueList.js.map +1 -0
  8. package/dist/esm/core/components/Settings.d.ts +6 -0
  9. package/dist/esm/core/components/Settings.js +251 -0
  10. package/dist/esm/core/components/Settings.js.map +1 -0
  11. package/dist/esm/core/components/Shell.d.ts +2 -0
  12. package/dist/esm/core/components/Shell.js +214 -0
  13. package/dist/esm/core/components/Shell.js.map +1 -0
  14. package/dist/esm/core/components/index.d.ts +2 -0
  15. package/dist/esm/core/components/index.js +14 -0
  16. package/dist/esm/core/components/index.js.map +1 -0
  17. package/dist/esm/core/contexts/allyContext.d.ts +17 -0
  18. package/dist/esm/core/contexts/allyContext.js +66 -0
  19. package/dist/esm/core/contexts/allyContext.js.map +1 -0
  20. package/dist/esm/core/core.d.ts +19 -0
  21. package/dist/esm/core/core.js +8 -0
  22. package/dist/esm/core/core.js.map +1 -0
  23. package/dist/esm/core/index.d.ts +9 -0
  24. package/dist/esm/core/index.js +9 -0
  25. package/dist/esm/core/index.js.map +1 -0
  26. package/dist/esm/core/production.d.ts +2 -0
  27. package/dist/esm/core/production.js +4 -0
  28. package/dist/esm/core/styles/styles.d.ts +85 -0
  29. package/dist/esm/core/styles/styles.js +547 -0
  30. package/dist/esm/core/styles/styles.js.map +1 -0
  31. package/dist/esm/core/types/types.d.ts +141 -0
  32. package/dist/esm/core/utils/ally-audit.utils.d.ts +19 -0
  33. package/dist/esm/core/utils/ally-audit.utils.js +226 -0
  34. package/dist/esm/core/utils/ally-audit.utils.js.map +1 -0
  35. package/dist/esm/core/utils/config.utils.d.ts +17 -0
  36. package/dist/esm/core/utils/config.utils.js +63 -0
  37. package/dist/esm/core/utils/config.utils.js.map +1 -0
  38. package/dist/esm/core/utils/custom-audit.utils.d.ts +13 -0
  39. package/dist/esm/core/utils/custom-audit.utils.js +426 -0
  40. package/dist/esm/core/utils/custom-audit.utils.js.map +1 -0
  41. package/dist/esm/core/utils/export-audit.uitls.d.ts +17 -0
  42. package/dist/esm/core/utils/export-audit.uitls.js +83 -0
  43. package/dist/esm/core/utils/export-audit.uitls.js.map +1 -0
  44. package/dist/esm/core/utils/ui.utils.d.ts +24 -0
  45. package/dist/esm/core/utils/ui.utils.js +330 -0
  46. package/dist/esm/core/utils/ui.utils.js.map +1 -0
  47. package/dist/esm/react/A11yDevtools.d.ts +5 -0
  48. package/dist/esm/react/A11yDevtools.js +8 -0
  49. package/dist/esm/react/A11yDevtools.js.map +1 -0
  50. package/dist/esm/react/index.d.ts +8 -0
  51. package/dist/esm/react/index.js +11 -0
  52. package/dist/esm/react/index.js.map +1 -0
  53. package/dist/esm/react/plugin.d.ts +12 -0
  54. package/dist/esm/react/plugin.js +11 -0
  55. package/dist/esm/react/plugin.js.map +1 -0
  56. package/dist/esm/react/production/A11yDevtools.d.ts +5 -0
  57. package/dist/esm/react/production/A11yDevtools.js +8 -0
  58. package/dist/esm/react/production/A11yDevtools.js.map +1 -0
  59. package/dist/esm/react/production/plugin.d.ts +7 -0
  60. package/dist/esm/react/production/plugin.js +11 -0
  61. package/dist/esm/react/production/plugin.js.map +1 -0
  62. package/dist/esm/react/production.d.ts +3 -0
  63. package/dist/esm/react/production.js +5 -0
  64. package/dist/esm/solid/A11yDevtools.d.ts +5 -0
  65. package/dist/esm/solid/A11yDevtools.js +8 -0
  66. package/dist/esm/solid/A11yDevtools.js.map +1 -0
  67. package/dist/esm/solid/index.d.ts +8 -0
  68. package/dist/esm/solid/index.js +9 -0
  69. package/dist/esm/solid/index.js.map +1 -0
  70. package/dist/esm/solid/plugin.d.ts +12 -0
  71. package/dist/esm/solid/plugin.js +11 -0
  72. package/dist/esm/solid/plugin.js.map +1 -0
  73. package/dist/esm/solid/production/A11yDevtools.d.ts +5 -0
  74. package/dist/esm/solid/production/A11yDevtools.js +8 -0
  75. package/dist/esm/solid/production/A11yDevtools.js.map +1 -0
  76. package/dist/esm/solid/production/plugin.d.ts +7 -0
  77. package/dist/esm/solid/production/plugin.js +11 -0
  78. package/dist/esm/solid/production/plugin.js.map +1 -0
  79. package/dist/esm/solid/production.d.ts +3 -0
  80. package/dist/esm/solid/production.js +3 -0
  81. package/package.json +110 -7
  82. package/src/core/components/IssueCard.tsx +75 -0
  83. package/src/core/components/IssueList.tsx +155 -0
  84. package/src/core/components/Settings.tsx +221 -0
  85. package/src/core/components/Shell.tsx +154 -0
  86. package/src/core/components/index.tsx +12 -0
  87. package/src/core/contexts/allyContext.tsx +118 -0
  88. package/src/core/core.tsx +11 -0
  89. package/src/core/index.ts +10 -0
  90. package/src/core/production.ts +5 -0
  91. package/src/core/styles/styles.ts +556 -0
  92. package/src/core/types/types.ts +177 -0
  93. package/src/core/utils/ally-audit.utils.ts +345 -0
  94. package/src/core/utils/config.utils.ts +68 -0
  95. package/src/core/utils/custom-audit.utils.ts +643 -0
  96. package/src/core/utils/export-audit.uitls.ts +180 -0
  97. package/src/core/utils/ui.utils.ts +483 -0
  98. package/src/react/A11yDevtools.ts +12 -0
  99. package/src/react/index.ts +16 -0
  100. package/src/react/plugin.ts +9 -0
  101. package/src/react/production/A11yDevtools.ts +11 -0
  102. package/src/react/production/plugin.ts +9 -0
  103. package/src/react/production.ts +7 -0
  104. package/src/solid/A11yDevtools.ts +11 -0
  105. package/src/solid/index.ts +14 -0
  106. package/src/solid/plugin.ts +9 -0
  107. package/src/solid/production/A11yDevtools.ts +10 -0
  108. package/src/solid/production/plugin.ts +9 -0
  109. package/src/solid/production.ts +5 -0
  110. package/README.md +0 -45
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Severity threshold for filtering issues
3
+ */
4
+ export type SeverityThreshold = 'critical' | 'serious' | 'moderate' | 'minor';
5
+ /**
6
+ * Rule set presets
7
+ */
8
+ export type RuleSetPreset = 'wcag2a' | 'wcag2aa' | 'wcag21aa' | 'wcag22aa' | 'section508' | 'best-practice' | 'all';
9
+ /**
10
+ * Rule categories (axe-core tags)
11
+ */
12
+ export type RuleCategory = 'all' | 'cat.aria' | 'cat.color' | 'cat.forms' | 'cat.keyboard' | 'cat.language' | 'cat.name-role-value' | 'cat.parsing' | 'cat.semantics' | 'cat.sensory-and-visual-cues' | 'cat.structure' | 'cat.tables' | 'cat.text-alternatives' | 'cat.time-and-media';
13
+ /**
14
+ * Represents a single node affected by an accessibility issue
15
+ */
16
+ export interface A11yNode {
17
+ /** CSS selector for the element */
18
+ selector: string;
19
+ /** HTML snippet of the element */
20
+ html: string;
21
+ /** XPath to the element (optional) */
22
+ xpath?: string;
23
+ /** Failure summary for this specific node */
24
+ failureSummary?: string;
25
+ }
26
+ /**
27
+ * Represents a single accessibility issue
28
+ */
29
+ export interface A11yIssue {
30
+ /** Unique identifier for this issue instance */
31
+ id: string;
32
+ /** The axe-core rule ID */
33
+ ruleId: string;
34
+ /** Impact severity level */
35
+ impact: SeverityThreshold;
36
+ /** Human-readable description of the issue */
37
+ message: string;
38
+ /** Detailed help text */
39
+ help: string;
40
+ /** URL to learn more about this issue */
41
+ helpUrl: string;
42
+ /** WCAG tags associated with this rule */
43
+ wcagTags: Array<string>;
44
+ /** DOM nodes affected by this issue */
45
+ nodes: Array<A11yNode>;
46
+ /** Whether this issue meets the current severity threshold */
47
+ meetsThreshold: boolean;
48
+ /** Timestamp when this issue was detected */
49
+ timestamp: number;
50
+ }
51
+ /**
52
+ * Summary statistics for an audit
53
+ */
54
+ export interface A11ySummary {
55
+ total: number;
56
+ critical: number;
57
+ serious: number;
58
+ moderate: number;
59
+ minor: number;
60
+ passes: number;
61
+ incomplete: number;
62
+ }
63
+ /**
64
+ * Result of an accessibility audit
65
+ */
66
+ export interface A11yAuditResult {
67
+ /** All issues found */
68
+ issues: Array<A11yIssue>;
69
+ /** Summary statistics */
70
+ summary: A11ySummary;
71
+ /** Timestamp when the audit was run */
72
+ timestamp: number;
73
+ /** URL of the page audited */
74
+ url: string;
75
+ /** Description of the context (document, selector, or element) */
76
+ context: string;
77
+ /** Time taken to run the audit in ms */
78
+ duration: number;
79
+ }
80
+ /**
81
+ * Configuration for custom rules
82
+ */
83
+ export interface CustomRulesConfig {
84
+ /** Enable click-handler-on-non-interactive rule (default: true) */
85
+ clickHandlerOnNonInteractive?: boolean;
86
+ /** Enable mouse-only-event-handlers rule (default: true) */
87
+ mouseOnlyEventHandlers?: boolean;
88
+ /** Enable static-element-interaction rule (default: true) */
89
+ staticElementInteraction?: boolean;
90
+ }
91
+ /**
92
+ * Options for running an audit
93
+ */
94
+ export interface A11yAuditOptions {
95
+ /** Minimum severity to report (default: 'serious') */
96
+ threshold?: SeverityThreshold;
97
+ /** DOM context to audit (default: document) */
98
+ context?: Document | Element | string;
99
+ /** Rule set preset to use (default: 'wcag21aa') */
100
+ ruleSet?: RuleSetPreset;
101
+ /** Specific rules to enable (overrides ruleSet) */
102
+ enabledRules?: Array<string>;
103
+ /** Specific rules to disable */
104
+ disabledRules?: Array<string>;
105
+ /** Selectors to exclude from auditing */
106
+ exclude?: Array<string>;
107
+ /** Configuration for custom rules (default: all enabled) */
108
+ customRules?: CustomRulesConfig;
109
+ }
110
+ /**
111
+ * Options for the A11y plugin
112
+ */
113
+ export interface A11yPluginOptions {
114
+ /** Minimum severity threshold (default: 'serious') */
115
+ threshold?: SeverityThreshold;
116
+ /** Rule set preset (default: 'wcag21aa') */
117
+ ruleSet?: RuleSetPreset;
118
+ /** Show visual overlays on page (default: true) */
119
+ showOverlays?: boolean;
120
+ /** Persist settings to localStorage (default: true) */
121
+ persistSettings?: boolean;
122
+ /** Rules to disable (by rule ID) */
123
+ disabledRules?: Array<string>;
124
+ }
125
+ /**
126
+ * Export format options
127
+ */
128
+ export type ExportFormat = 'json' | 'csv';
129
+ /**
130
+ * Export options
131
+ */
132
+ export interface ExportOptions {
133
+ /** Export format */
134
+ format: ExportFormat;
135
+ /** Include passing rules in export */
136
+ includePasses?: boolean;
137
+ /** Include incomplete rules in export */
138
+ includeIncomplete?: boolean;
139
+ /** Custom filename (without extension) */
140
+ filename?: string;
141
+ }
@@ -0,0 +1,19 @@
1
+ import { A11yAuditOptions, A11yAuditResult, A11yIssue, SeverityThreshold } from '../types/types.js';
2
+ /**
3
+ * Check if an impact level meets or exceeds the threshold
4
+ */
5
+ export declare function meetsThreshold(impact: SeverityThreshold | null | undefined, threshold: SeverityThreshold): boolean;
6
+ /**
7
+ * Run an accessibility audit using axe-core
8
+ */
9
+ export declare function runAudit(options?: A11yAuditOptions): Promise<A11yAuditResult>;
10
+ /**
11
+ * Get a list of all available axe-core rules plus custom rules
12
+ */
13
+ export declare function getAvailableRules(): Array<{
14
+ id: string;
15
+ description: string;
16
+ tags: Array<string>;
17
+ }>;
18
+ export declare const IMPACTS: readonly ["critical", "serious", "moderate", "minor"];
19
+ export declare const filterIssuesAboveThreshold: (issues: A11yAuditResult["issues"], threshold: SeverityThreshold) => A11yIssue[];
@@ -0,0 +1,226 @@
1
+ import { getCustomRules, runCustomRules } from "./custom-audit.utils.js";
2
+ import { SEVERITY_ORDER } from "./ui.utils.js";
3
+ import axe from "axe-core";
4
+ //#region src/core/utils/ally-audit.utils.ts
5
+ /**
6
+ * Severity levels mapped to numeric values for comparison
7
+ */
8
+ var IMPACT_SEVERITY = {
9
+ critical: 4,
10
+ serious: 3,
11
+ moderate: 2,
12
+ minor: 1
13
+ };
14
+ /**
15
+ * Rule set configurations for different presets
16
+ */
17
+ var RULE_SET_CONFIGS = {
18
+ wcag2a: { runOnly: {
19
+ type: "tag",
20
+ values: ["wcag2a"]
21
+ } },
22
+ wcag2aa: { runOnly: {
23
+ type: "tag",
24
+ values: ["wcag2a", "wcag2aa"]
25
+ } },
26
+ wcag21aa: { runOnly: {
27
+ type: "tag",
28
+ values: [
29
+ "wcag2a",
30
+ "wcag2aa",
31
+ "wcag21a",
32
+ "wcag21aa"
33
+ ]
34
+ } },
35
+ wcag22aa: { runOnly: {
36
+ type: "tag",
37
+ values: [
38
+ "wcag2a",
39
+ "wcag2aa",
40
+ "wcag21a",
41
+ "wcag21aa",
42
+ "wcag22aa"
43
+ ]
44
+ } },
45
+ section508: { runOnly: {
46
+ type: "tag",
47
+ values: ["section508"]
48
+ } },
49
+ "best-practice": { runOnly: {
50
+ type: "tag",
51
+ values: ["best-practice"]
52
+ } },
53
+ all: {}
54
+ };
55
+ /**
56
+ * Check if an impact level meets or exceeds the threshold
57
+ */
58
+ function meetsThreshold(impact, threshold) {
59
+ if (!impact) return false;
60
+ return IMPACT_SEVERITY[impact] >= IMPACT_SEVERITY[threshold];
61
+ }
62
+ /**
63
+ * Convert axe-core results to our issue format
64
+ */
65
+ function convertToIssues(results, threshold) {
66
+ const issues = [];
67
+ for (const violation of results.violations) {
68
+ const impact = violation.impact;
69
+ for (let i = 0; i < violation.nodes.length; i++) {
70
+ const node = violation.nodes[i];
71
+ const a11yNode = {
72
+ selector: node.target.join(", "),
73
+ html: node.html,
74
+ xpath: node.xpath?.join(" > "),
75
+ failureSummary: node.failureSummary
76
+ };
77
+ issues.push({
78
+ id: `${violation.id}-${i}-${Date.now()}`,
79
+ ruleId: violation.id,
80
+ impact: impact || "minor",
81
+ message: node.failureSummary || violation.description,
82
+ help: violation.help,
83
+ helpUrl: violation.helpUrl,
84
+ wcagTags: violation.tags.filter((tag) => tag.startsWith("wcag") || tag.startsWith("section508")),
85
+ nodes: [a11yNode],
86
+ meetsThreshold: meetsThreshold(impact, threshold),
87
+ timestamp: Date.now()
88
+ });
89
+ }
90
+ }
91
+ return issues;
92
+ }
93
+ /**
94
+ * Create summary statistics from issues array
95
+ * Used when combining axe-core results with custom rule results
96
+ */
97
+ function createSummary(axeResults, issues) {
98
+ const summary = {
99
+ total: issues.length,
100
+ critical: 0,
101
+ serious: 0,
102
+ moderate: 0,
103
+ minor: 0,
104
+ passes: axeResults.passes.length,
105
+ incomplete: axeResults.incomplete.length
106
+ };
107
+ for (const issue of issues) {
108
+ const impact = issue.impact;
109
+ if (impact === "critical") summary.critical++;
110
+ else if (impact === "serious") summary.serious++;
111
+ else if (impact === "moderate") summary.moderate++;
112
+ else summary.minor++;
113
+ }
114
+ return summary;
115
+ }
116
+ /**
117
+ * Get the context description for logging
118
+ */
119
+ function getContextDescription(context) {
120
+ if (typeof context === "string") return context;
121
+ if (context instanceof Document) return "document";
122
+ if (context instanceof Element) return context.tagName.toLowerCase() + (context.id ? `#${context.id}` : "");
123
+ return "unknown";
124
+ }
125
+ /**
126
+ * Default selectors to exclude from auditing (devtools panels, overlays, etc.)
127
+ */
128
+ var DEFAULT_EXCLUDE_SELECTORS = [
129
+ "[data-testid=\"tanstack_devtools\"]",
130
+ "[data-a11y-overlay]",
131
+ "[data-devtools]",
132
+ "[data-devtools-panel]"
133
+ ];
134
+ /**
135
+ * Run an accessibility audit using axe-core
136
+ */
137
+ async function runAudit(options = {}) {
138
+ const { threshold = "serious", context = document, ruleSet = "wcag21aa", enabledRules, disabledRules, exclude = [], customRules = {} } = options;
139
+ const allExclusions = [...DEFAULT_EXCLUDE_SELECTORS, ...exclude];
140
+ const startTime = performance.now();
141
+ const contextDescription = getContextDescription(context);
142
+ try {
143
+ const axeOptions = {
144
+ resultTypes: [
145
+ "violations",
146
+ "passes",
147
+ "incomplete"
148
+ ],
149
+ ...RULE_SET_CONFIGS[ruleSet]
150
+ };
151
+ if (enabledRules && enabledRules.length > 0) axeOptions.runOnly = {
152
+ type: "rule",
153
+ values: enabledRules
154
+ };
155
+ if (disabledRules && disabledRules.length > 0) {
156
+ const rules = {};
157
+ for (const ruleId of disabledRules) rules[ruleId] = { enabled: false };
158
+ axeOptions.rules = rules;
159
+ }
160
+ let auditContext = context;
161
+ if (allExclusions.length > 0) auditContext = {
162
+ include: [auditContext],
163
+ exclude: allExclusions.map((sel) => [sel])
164
+ };
165
+ const results = await axe.run(auditContext, axeOptions);
166
+ const axeIssues = convertToIssues(results, threshold);
167
+ const customRulesConfig = {
168
+ clickHandlerOnNonInteractive: customRules.clickHandlerOnNonInteractive !== false && !disabledRules?.includes("click-handler-on-non-interactive"),
169
+ mouseOnlyEventHandlers: customRules.mouseOnlyEventHandlers !== false && !disabledRules?.includes("mouse-only-event-handlers"),
170
+ staticElementInteraction: customRules.staticElementInteraction !== false && !disabledRules?.includes("static-element-interaction")
171
+ };
172
+ const customIssues = runCustomRules(typeof context === "string" ? document.querySelector(context) || document : context, customRulesConfig, threshold);
173
+ const allIssues = [...axeIssues, ...customIssues];
174
+ const duration = performance.now() - startTime;
175
+ return {
176
+ issues: allIssues,
177
+ summary: createSummary(results, allIssues),
178
+ timestamp: Date.now(),
179
+ url: typeof window !== "undefined" ? window.location.href : "",
180
+ context: contextDescription,
181
+ duration
182
+ };
183
+ } catch (error) {
184
+ const duration = performance.now() - startTime;
185
+ console.error("[A11y Audit] Error running axe-core:", error);
186
+ return {
187
+ issues: [],
188
+ summary: {
189
+ total: 0,
190
+ critical: 0,
191
+ serious: 0,
192
+ moderate: 0,
193
+ minor: 0,
194
+ passes: 0,
195
+ incomplete: 0
196
+ },
197
+ timestamp: Date.now(),
198
+ url: typeof window !== "undefined" ? window.location.href : "",
199
+ context: contextDescription,
200
+ duration
201
+ };
202
+ }
203
+ }
204
+ /**
205
+ * Get a list of all available axe-core rules plus custom rules
206
+ */
207
+ function getAvailableRules() {
208
+ const axeRules = axe.getRules().map((rule) => ({
209
+ id: rule.ruleId,
210
+ description: rule.description,
211
+ tags: rule.tags
212
+ }));
213
+ const customRules = getCustomRules();
214
+ return [...axeRules, ...customRules];
215
+ }
216
+ var IMPACTS = [
217
+ "critical",
218
+ "serious",
219
+ "moderate",
220
+ "minor"
221
+ ];
222
+ var filterIssuesAboveThreshold = (issues, threshold) => issues.filter((issue) => SEVERITY_ORDER[issue.impact] >= SEVERITY_ORDER[threshold]);
223
+ //#endregion
224
+ export { IMPACTS, filterIssuesAboveThreshold, getAvailableRules, meetsThreshold, runAudit };
225
+
226
+ //# sourceMappingURL=ally-audit.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ally-audit.utils.js","names":[],"sources":["../../../../src/core/utils/ally-audit.utils.ts"],"sourcesContent":["import axe from 'axe-core'\nimport {\n getCustomRules as getCustomRulesInternal,\n runCustomRules,\n} from './custom-audit.utils.js'\nimport { SEVERITY_ORDER } from './ui.utils.js'\n\n// types\nimport type { AxeResults, RuleObject, RunOptions } from 'axe-core'\nimport type {\n A11yAuditOptions,\n A11yAuditResult,\n A11yIssue,\n A11yNode,\n A11ySummary,\n CustomRulesConfig,\n RuleSetPreset,\n SeverityThreshold,\n} from '../types/types.js'\n\n/**\n * Severity levels mapped to numeric values for comparison\n */\nconst IMPACT_SEVERITY: Record<SeverityThreshold, number> = {\n critical: 4,\n serious: 3,\n moderate: 2,\n minor: 1,\n}\n\n/**\n * Rule set configurations for different presets\n */\nconst RULE_SET_CONFIGS: Record<RuleSetPreset, Partial<RunOptions>> = {\n wcag2a: {\n runOnly: {\n type: 'tag',\n values: ['wcag2a'],\n },\n },\n wcag2aa: {\n runOnly: {\n type: 'tag',\n values: ['wcag2a', 'wcag2aa'],\n },\n },\n wcag21aa: {\n runOnly: {\n type: 'tag',\n values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'],\n },\n },\n wcag22aa: {\n runOnly: {\n type: 'tag',\n values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'],\n },\n },\n section508: {\n runOnly: {\n type: 'tag',\n values: ['section508'],\n },\n },\n 'best-practice': {\n runOnly: {\n type: 'tag',\n values: ['best-practice'],\n },\n },\n all: {\n // Run all rules\n },\n}\n\n/**\n * Check if an impact level meets or exceeds the threshold\n */\nexport function meetsThreshold(\n impact: SeverityThreshold | null | undefined,\n threshold: SeverityThreshold,\n): boolean {\n if (!impact) return false\n return IMPACT_SEVERITY[impact] >= IMPACT_SEVERITY[threshold]\n}\n\n/**\n * Convert axe-core results to our issue format\n */\nfunction convertToIssues(\n results: AxeResults,\n threshold: SeverityThreshold,\n): Array<A11yIssue> {\n const issues: Array<A11yIssue> = []\n\n for (const violation of results.violations) {\n const impact = violation.impact as SeverityThreshold | undefined\n\n for (let i = 0; i < violation.nodes.length; i++) {\n const node = violation.nodes[i]!\n const selector = node.target.join(', ')\n\n const a11yNode: A11yNode = {\n selector,\n html: node.html,\n xpath: node.xpath?.join(' > '),\n failureSummary: node.failureSummary,\n }\n\n issues.push({\n id: `${violation.id}-${i}-${Date.now()}`,\n ruleId: violation.id,\n impact: impact || 'minor',\n message: node.failureSummary || violation.description,\n help: violation.help,\n helpUrl: violation.helpUrl,\n wcagTags: violation.tags.filter(\n (tag) => tag.startsWith('wcag') || tag.startsWith('section508'),\n ),\n nodes: [a11yNode],\n meetsThreshold: meetsThreshold(impact, threshold),\n timestamp: Date.now(),\n })\n }\n }\n\n return issues\n}\n\n/**\n * Create summary statistics from issues array\n * Used when combining axe-core results with custom rule results\n */\nfunction createSummary(\n axeResults: AxeResults,\n issues: Array<A11yIssue>,\n): A11ySummary {\n const summary: A11ySummary = {\n total: issues.length,\n critical: 0,\n serious: 0,\n moderate: 0,\n minor: 0,\n passes: axeResults.passes.length,\n incomplete: axeResults.incomplete.length,\n }\n\n for (const issue of issues) {\n const impact = issue.impact\n if (impact === 'critical') summary.critical++\n else if (impact === 'serious') summary.serious++\n else if (impact === 'moderate') summary.moderate++\n else {\n summary.minor++\n }\n }\n\n return summary\n}\n\n/**\n * Get the context description for logging\n */\nfunction getContextDescription(context: Document | Element | string): string {\n if (typeof context === 'string') {\n return context\n }\n if (context instanceof Document) {\n return 'document'\n }\n if (context instanceof Element) {\n return context.tagName.toLowerCase() + (context.id ? `#${context.id}` : '')\n }\n return 'unknown'\n}\n\n/**\n * Default selectors to exclude from auditing (devtools panels, overlays, etc.)\n */\nconst DEFAULT_EXCLUDE_SELECTORS = [\n // TanStack Devtools root container\n '[data-testid=\"tanstack_devtools\"]',\n // A11y overlay elements\n '[data-a11y-overlay]',\n // Common devtools patterns\n '[data-devtools]',\n '[data-devtools-panel]',\n]\n\n/**\n * Run an accessibility audit using axe-core\n */\nexport async function runAudit(\n options: A11yAuditOptions = {},\n): Promise<A11yAuditResult> {\n const {\n threshold = 'serious',\n context = document,\n ruleSet = 'wcag21aa',\n enabledRules,\n disabledRules,\n exclude = [],\n customRules = {},\n } = options\n\n // Merge user exclusions with default devtools exclusions\n const allExclusions = [...DEFAULT_EXCLUDE_SELECTORS, ...exclude]\n\n const startTime = performance.now()\n const contextDescription = getContextDescription(context)\n\n try {\n // Build axe-core options\n const axeOptions: RunOptions = {\n resultTypes: ['violations', 'passes', 'incomplete'],\n ...RULE_SET_CONFIGS[ruleSet],\n }\n\n // Handle specific rule configurations\n if (enabledRules && enabledRules.length > 0) {\n axeOptions.runOnly = {\n type: 'rule',\n values: enabledRules,\n }\n }\n\n // Build rules configuration for disabled rules\n if (disabledRules && disabledRules.length > 0) {\n const rules: RuleObject = {}\n for (const ruleId of disabledRules) {\n rules[ruleId] = { enabled: false }\n }\n axeOptions.rules = rules\n }\n\n // Determine the context to audit\n let auditContext: axe.ElementContext = context as axe.ElementContext\n\n // Add exclusions if specified (always include devtools exclusions)\n if (allExclusions.length > 0) {\n auditContext = {\n include: [auditContext as Element],\n exclude: allExclusions.map((sel) => [sel]),\n } as axe.ElementContext\n }\n\n // Run the axe-core audit\n const results = await axe.run(auditContext, axeOptions)\n\n // Convert axe-core results to our format\n const axeIssues = convertToIssues(results, threshold)\n\n // Run custom rules (if not all disabled)\n const customRulesConfig: CustomRulesConfig = {\n clickHandlerOnNonInteractive:\n customRules.clickHandlerOnNonInteractive !== false &&\n !disabledRules?.includes('click-handler-on-non-interactive'),\n mouseOnlyEventHandlers:\n customRules.mouseOnlyEventHandlers !== false &&\n !disabledRules?.includes('mouse-only-event-handlers'),\n staticElementInteraction:\n customRules.staticElementInteraction !== false &&\n !disabledRules?.includes('static-element-interaction'),\n }\n\n const contextElement =\n typeof context === 'string'\n ? document.querySelector(context) || document\n : context\n\n const customIssues = runCustomRules(\n contextElement,\n customRulesConfig,\n threshold,\n )\n\n // Merge all issues\n const allIssues = [...axeIssues, ...customIssues]\n\n const duration = performance.now() - startTime\n\n // Create summary from combined issues\n const summary = createSummary(results, allIssues)\n\n return {\n issues: allIssues,\n summary,\n timestamp: Date.now(),\n url: typeof window !== 'undefined' ? window.location.href : '',\n context: contextDescription,\n duration,\n }\n } catch (error) {\n const duration = performance.now() - startTime\n console.error('[A11y Audit] Error running axe-core:', error)\n\n return {\n issues: [],\n summary: {\n total: 0,\n critical: 0,\n serious: 0,\n moderate: 0,\n minor: 0,\n passes: 0,\n incomplete: 0,\n },\n timestamp: Date.now(),\n url: typeof window !== 'undefined' ? window.location.href : '',\n context: contextDescription,\n duration,\n }\n }\n}\n\n/**\n * Get a list of all available axe-core rules plus custom rules\n */\nexport function getAvailableRules(): Array<{\n id: string\n description: string\n tags: Array<string>\n}> {\n // Get axe-core rules\n const axeRules = axe.getRules().map((rule) => ({\n id: rule.ruleId,\n description: rule.description,\n tags: rule.tags,\n }))\n\n // Get custom rules\n const customRules = getCustomRulesInternal()\n\n return [...axeRules, ...customRules]\n}\n\nexport const IMPACTS = ['critical', 'serious', 'moderate', 'minor'] as const\n\nexport const filterIssuesAboveThreshold = (\n issues: A11yAuditResult['issues'],\n threshold: SeverityThreshold,\n) =>\n issues.filter(\n (issue) => SEVERITY_ORDER[issue.impact] >= SEVERITY_ORDER[threshold],\n )\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { A11yPluginOptions } from '../types/types.js';
2
+ /**
3
+ * Default plugin configuration
4
+ */
5
+ export declare const DEFAULT_CONFIG: Required<A11yPluginOptions>;
6
+ /**
7
+ * Load configuration from localStorage
8
+ */
9
+ export declare function loadConfig(): Required<A11yPluginOptions>;
10
+ /**
11
+ * Save configuration to localStorage
12
+ */
13
+ export declare function saveConfig(config: Partial<A11yPluginOptions>): void;
14
+ /**
15
+ * Merge user options with defaults
16
+ */
17
+ export declare function mergeConfig(options?: A11yPluginOptions): Required<A11yPluginOptions>;
@@ -0,0 +1,63 @@
1
+ //#region src/core/utils/config.utils.ts
2
+ var STORAGE_KEY = "tanstack-devtools-a11y-config";
3
+ /**
4
+ * Default plugin configuration
5
+ */
6
+ var DEFAULT_CONFIG = {
7
+ threshold: "serious",
8
+ ruleSet: "wcag21aa",
9
+ showOverlays: true,
10
+ persistSettings: true,
11
+ disabledRules: []
12
+ };
13
+ /**
14
+ * Load configuration from localStorage
15
+ */
16
+ function loadConfig() {
17
+ if (typeof localStorage === "undefined") return DEFAULT_CONFIG;
18
+ try {
19
+ const stored = localStorage.getItem(STORAGE_KEY);
20
+ if (stored) {
21
+ const parsed = JSON.parse(stored);
22
+ return {
23
+ ...DEFAULT_CONFIG,
24
+ ...parsed
25
+ };
26
+ }
27
+ } catch (error) {
28
+ console.warn("[A11y Config] Failed to load config from localStorage:", error);
29
+ }
30
+ return DEFAULT_CONFIG;
31
+ }
32
+ /**
33
+ * Save configuration to localStorage
34
+ */
35
+ function saveConfig(config) {
36
+ if (typeof localStorage === "undefined") return;
37
+ try {
38
+ const updated = {
39
+ ...loadConfig(),
40
+ ...config
41
+ };
42
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
43
+ } catch (error) {
44
+ console.warn("[A11y Config] Failed to save config to localStorage:", error);
45
+ }
46
+ }
47
+ /**
48
+ * Merge user options with defaults
49
+ */
50
+ function mergeConfig(options = {}) {
51
+ if (options.persistSettings !== false) return {
52
+ ...loadConfig(),
53
+ ...options
54
+ };
55
+ return {
56
+ ...DEFAULT_CONFIG,
57
+ ...options
58
+ };
59
+ }
60
+ //#endregion
61
+ export { mergeConfig, saveConfig };
62
+
63
+ //# sourceMappingURL=config.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.utils.js","names":[],"sources":["../../../../src/core/utils/config.utils.ts"],"sourcesContent":["import type { A11yPluginOptions } from '../types/types'\n\nconst STORAGE_KEY = 'tanstack-devtools-a11y-config'\n\n/**\n * Default plugin configuration\n */\nexport const DEFAULT_CONFIG: Required<A11yPluginOptions> = {\n threshold: 'serious',\n ruleSet: 'wcag21aa',\n showOverlays: true,\n persistSettings: true,\n disabledRules: [],\n}\n\n/**\n * Load configuration from localStorage\n */\nexport function loadConfig(): Required<A11yPluginOptions> {\n if (typeof localStorage === 'undefined') {\n return DEFAULT_CONFIG\n }\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored) as Partial<A11yPluginOptions>\n return { ...DEFAULT_CONFIG, ...parsed }\n }\n } catch (error) {\n console.warn(\n '[A11y Config] Failed to load config from localStorage:',\n error,\n )\n }\n\n return DEFAULT_CONFIG\n}\n\n/**\n * Save configuration to localStorage\n */\nexport function saveConfig(config: Partial<A11yPluginOptions>): void {\n if (typeof localStorage === 'undefined') {\n return\n }\n\n try {\n const current = loadConfig()\n const updated = { ...current, ...config }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(updated))\n } catch (error) {\n console.warn('[A11y Config] Failed to save config to localStorage:', error)\n }\n}\n\n/**\n * Merge user options with defaults\n */\nexport function mergeConfig(\n options: A11yPluginOptions = {},\n): Required<A11yPluginOptions> {\n if (options.persistSettings !== false) {\n const saved = loadConfig()\n return { ...saved, ...options }\n }\n return { ...DEFAULT_CONFIG, ...options }\n}\n"],"mappings":";AAEA,IAAM,cAAc;;;;AAKpB,IAAa,iBAA8C;CACzD,WAAW;CACX,SAAS;CACT,cAAc;CACd,iBAAiB;CACjB,eAAe,EAAE;CAClB;;;;AAKD,SAAgB,aAA0C;AACxD,KAAI,OAAO,iBAAiB,YAC1B,QAAO;AAGT,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,YAAY;AAChD,MAAI,QAAQ;GACV,MAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAO;IAAE,GAAG;IAAgB,GAAG;IAAQ;;UAElC,OAAO;AACd,UAAQ,KACN,0DACA,MACD;;AAGH,QAAO;;;;;AAMT,SAAgB,WAAW,QAA0C;AACnE,KAAI,OAAO,iBAAiB,YAC1B;AAGF,KAAI;EAEF,MAAM,UAAU;GAAE,GADF,YAAY;GACE,GAAG;GAAQ;AACzC,eAAa,QAAQ,aAAa,KAAK,UAAU,QAAQ,CAAC;UACnD,OAAO;AACd,UAAQ,KAAK,wDAAwD,MAAM;;;;;;AAO/E,SAAgB,YACd,UAA6B,EAAE,EACF;AAC7B,KAAI,QAAQ,oBAAoB,MAE9B,QAAO;EAAE,GADK,YAAY;EACP,GAAG;EAAS;AAEjC,QAAO;EAAE,GAAG;EAAgB,GAAG;EAAS"}
@@ -0,0 +1,13 @@
1
+ import { A11yIssue, CustomRulesConfig, SeverityThreshold } from '../types/types.js';
2
+ /**
3
+ * Run all enabled custom rules
4
+ */
5
+ export declare function runCustomRules(context?: Document | Element, config?: CustomRulesConfig, threshold?: SeverityThreshold): Array<A11yIssue>;
6
+ /**
7
+ * Get list of custom rule metadata (for UI display)
8
+ */
9
+ export declare function getCustomRules(): Array<{
10
+ id: string;
11
+ description: string;
12
+ tags: Array<string>;
13
+ }>;