accessibility-server-mcp 1.0.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.
- package/LICENSE +21 -0
- package/README.md +762 -0
- package/config/wcag-rules.json +252 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/dist/test-manual.d.ts +6 -0
- package/dist/test-manual.d.ts.map +1 -0
- package/dist/test-manual.js +66 -0
- package/dist/test-manual.js.map +1 -0
- package/dist/tools/accessibility-tester.d.ts +56 -0
- package/dist/tools/accessibility-tester.d.ts.map +1 -0
- package/dist/tools/accessibility-tester.js +317 -0
- package/dist/tools/accessibility-tester.js.map +1 -0
- package/dist/tools/color-contrast.d.ts +49 -0
- package/dist/tools/color-contrast.d.ts.map +1 -0
- package/dist/tools/color-contrast.js +237 -0
- package/dist/tools/color-contrast.js.map +1 -0
- package/dist/tools/wcag-validator.d.ts +43 -0
- package/dist/tools/wcag-validator.d.ts.map +1 -0
- package/dist/tools/wcag-validator.js +249 -0
- package/dist/tools/wcag-validator.js.map +1 -0
- package/dist/tools/website-accessibility-tester.d.ts +103 -0
- package/dist/tools/website-accessibility-tester.d.ts.map +1 -0
- package/dist/tools/website-accessibility-tester.js +228 -0
- package/dist/tools/website-accessibility-tester.js.map +1 -0
- package/dist/types/accessibility.d.ts +172 -0
- package/dist/types/accessibility.d.ts.map +1 -0
- package/dist/types/accessibility.js +5 -0
- package/dist/types/accessibility.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mcp.d.ts +70 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +5 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/utils/browser-manager.d.ts +83 -0
- package/dist/utils/browser-manager.d.ts.map +1 -0
- package/dist/utils/browser-manager.js +292 -0
- package/dist/utils/browser-manager.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +107 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/report-generator.d.ts +31 -0
- package/dist/utils/report-generator.d.ts.map +1 -0
- package/dist/utils/report-generator.js +252 -0
- package/dist/utils/report-generator.js.map +1 -0
- package/dist/utils/website-crawler.d.ts +66 -0
- package/dist/utils/website-crawler.d.ts.map +1 -0
- package/dist/utils/website-crawler.js +306 -0
- package/dist/utils/website-crawler.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
import { Logger, ReportGenerator } from '../utils/index.js';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
/**
|
|
6
|
+
* Unified accessibility tester for both URLs and HTML content
|
|
7
|
+
*/
|
|
8
|
+
export class AccessibilityTester {
|
|
9
|
+
logger;
|
|
10
|
+
browserManager;
|
|
11
|
+
constructor(browserManager) {
|
|
12
|
+
this.logger = new Logger().child({ component: 'AccessibilityTester' });
|
|
13
|
+
this.browserManager = browserManager;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Test accessibility for either URL or HTML content
|
|
17
|
+
*/
|
|
18
|
+
async testTarget(input) {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
try {
|
|
21
|
+
this.logger.info('Starting accessibility test', {
|
|
22
|
+
type: input.type,
|
|
23
|
+
level: input.level,
|
|
24
|
+
wcagLevel: input.wcagLevel
|
|
25
|
+
});
|
|
26
|
+
let result;
|
|
27
|
+
if (input.type === 'url') {
|
|
28
|
+
result = await this.testURL(input.target, input.wcagLevel, input.level === 'full');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
result = await this.testHTML(input.target, input.wcagLevel, input.level === 'full');
|
|
32
|
+
}
|
|
33
|
+
result.testDuration = Date.now() - startTime;
|
|
34
|
+
this.logger.info('Accessibility test completed', {
|
|
35
|
+
type: input.type,
|
|
36
|
+
violations: result.violations.length,
|
|
37
|
+
score: result.summary.score
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.logger.error('Error during accessibility test', error instanceof Error ? error : new Error(String(error)));
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Test URL accessibility using Puppeteer and axe-core
|
|
48
|
+
*/
|
|
49
|
+
async testURL(url, wcagLevel, fullAnalysis) {
|
|
50
|
+
let page = null;
|
|
51
|
+
try {
|
|
52
|
+
// Create page with default viewport
|
|
53
|
+
page = await this.browserManager.createPage({
|
|
54
|
+
viewport: { width: 1920, height: 1080 },
|
|
55
|
+
timeout: 30000
|
|
56
|
+
});
|
|
57
|
+
// Navigate to the URL
|
|
58
|
+
await this.browserManager.navigateToURL(page, url, {
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
waitUntil: 'domcontentloaded'
|
|
61
|
+
});
|
|
62
|
+
// Wait for dynamic content
|
|
63
|
+
await page.waitForTimeout(1000);
|
|
64
|
+
// Inject axe-core
|
|
65
|
+
await this.injectAxeCore(page);
|
|
66
|
+
// Wait for page to be ready
|
|
67
|
+
await page.waitForFunction(() => document.readyState === 'complete', {
|
|
68
|
+
timeout: 5000
|
|
69
|
+
}).catch(() => {
|
|
70
|
+
this.logger.warn('Page did not reach complete state within 5 seconds');
|
|
71
|
+
});
|
|
72
|
+
// Run axe scan
|
|
73
|
+
const axeResults = await this.runAxeScan(page, wcagLevel, fullAnalysis);
|
|
74
|
+
// Get metadata
|
|
75
|
+
const metadata = await this.getPageMetadata(page, wcagLevel);
|
|
76
|
+
// Transform results
|
|
77
|
+
const result = this.transformAxeResults(axeResults, url, metadata);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (page) {
|
|
82
|
+
await this.browserManager.closePage(page);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Test HTML content using JSDOM and axe-core
|
|
88
|
+
*/
|
|
89
|
+
async testHTML(htmlContent, wcagLevel, fullAnalysis) {
|
|
90
|
+
try {
|
|
91
|
+
// Create JSDOM instance
|
|
92
|
+
const dom = new JSDOM(htmlContent, {
|
|
93
|
+
runScripts: 'dangerously',
|
|
94
|
+
resources: 'usable',
|
|
95
|
+
pretendToBeVisual: true
|
|
96
|
+
});
|
|
97
|
+
// Inject axe-core into JSDOM
|
|
98
|
+
const axeSource = await this.getAxeSource();
|
|
99
|
+
// Create script element and inject axe
|
|
100
|
+
const script = dom.window.document.createElement('script');
|
|
101
|
+
script.text = axeSource;
|
|
102
|
+
dom.window.document.head.appendChild(script);
|
|
103
|
+
// Wait for axe to be available
|
|
104
|
+
await new Promise((resolve, reject) => {
|
|
105
|
+
const checkAxe = () => {
|
|
106
|
+
if (typeof dom.window.axe !== 'undefined') {
|
|
107
|
+
resolve();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
setTimeout(checkAxe, 100);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
checkAxe();
|
|
114
|
+
// Timeout after 5 seconds
|
|
115
|
+
setTimeout(() => reject(new Error('Timeout waiting for axe in JSDOM')), 5000);
|
|
116
|
+
});
|
|
117
|
+
// Configure and run axe
|
|
118
|
+
const axeConfig = this.buildAxeConfig(wcagLevel, fullAnalysis);
|
|
119
|
+
const axeResults = await new Promise((resolve, reject) => {
|
|
120
|
+
dom.window.axe.run(dom.window.document, axeConfig, (err, results) => {
|
|
121
|
+
if (err)
|
|
122
|
+
reject(err);
|
|
123
|
+
else
|
|
124
|
+
resolve(results);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
// Create metadata
|
|
128
|
+
const metadata = {
|
|
129
|
+
userAgent: 'JSDOM',
|
|
130
|
+
viewport: { width: 1024, height: 768 },
|
|
131
|
+
wcagLevel
|
|
132
|
+
};
|
|
133
|
+
// Transform results
|
|
134
|
+
return this.transformAxeResults(axeResults, undefined, metadata);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
this.logger.error('Error testing HTML content', error instanceof Error ? error : new Error(String(error)));
|
|
138
|
+
throw new Error(`HTML testing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Inject axe-core into a Puppeteer page
|
|
143
|
+
*/
|
|
144
|
+
async injectAxeCore(page) {
|
|
145
|
+
try {
|
|
146
|
+
// Check if axe already exists
|
|
147
|
+
const axeExists = await page.evaluate(() => typeof window.axe !== 'undefined');
|
|
148
|
+
if (axeExists) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Try local file first
|
|
152
|
+
try {
|
|
153
|
+
const axePath = path.resolve('node_modules', 'axe-core', 'axe.min.js');
|
|
154
|
+
await page.addScriptTag({ path: axePath });
|
|
155
|
+
await page.waitForFunction(() => typeof window.axe !== 'undefined', {
|
|
156
|
+
timeout: 5000
|
|
157
|
+
});
|
|
158
|
+
this.logger.debug('Injected axe-core from local file');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Fall back to CDN
|
|
163
|
+
await page.addScriptTag({
|
|
164
|
+
url: 'https://unpkg.com/axe-core@latest/axe.min.js'
|
|
165
|
+
});
|
|
166
|
+
await page.waitForFunction(() => typeof window.axe !== 'undefined', {
|
|
167
|
+
timeout: 10000
|
|
168
|
+
});
|
|
169
|
+
this.logger.debug('Injected axe-core from CDN');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
throw new Error(`Failed to inject axe-core: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get axe-core source code for JSDOM injection
|
|
178
|
+
*/
|
|
179
|
+
async getAxeSource() {
|
|
180
|
+
try {
|
|
181
|
+
const axePath = path.resolve('node_modules', 'axe-core', 'axe.min.js');
|
|
182
|
+
return await fs.readFile(axePath, 'utf8');
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
throw new Error(`Failed to read axe-core source: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Build axe configuration
|
|
190
|
+
*/
|
|
191
|
+
buildAxeConfig(wcagLevel, fullAnalysis) {
|
|
192
|
+
const config = {
|
|
193
|
+
resultTypes: ['violations', 'passes'],
|
|
194
|
+
reporter: 'v2'
|
|
195
|
+
};
|
|
196
|
+
// Add WCAG level tags
|
|
197
|
+
const tags = [];
|
|
198
|
+
switch (wcagLevel) {
|
|
199
|
+
case 'AAA':
|
|
200
|
+
tags.push('wcag2aaa');
|
|
201
|
+
// fallthrough
|
|
202
|
+
case 'AA':
|
|
203
|
+
tags.push('wcag2aa');
|
|
204
|
+
// fallthrough
|
|
205
|
+
case 'A':
|
|
206
|
+
tags.push('wcag2a');
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
// Add version tags - default to 2.1
|
|
210
|
+
tags.push('wcag21');
|
|
211
|
+
// Add analysis depth tags
|
|
212
|
+
if (fullAnalysis) {
|
|
213
|
+
tags.push('best-practice');
|
|
214
|
+
}
|
|
215
|
+
config.tags = tags;
|
|
216
|
+
return config;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Run axe accessibility scan on a page
|
|
220
|
+
*/
|
|
221
|
+
async runAxeScan(page, wcagLevel, fullAnalysis) {
|
|
222
|
+
const axeConfig = this.buildAxeConfig(wcagLevel, fullAnalysis);
|
|
223
|
+
return await page.evaluate((config) => {
|
|
224
|
+
return window.axe.run(document, config);
|
|
225
|
+
}, axeConfig);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get page metadata
|
|
229
|
+
*/
|
|
230
|
+
async getPageMetadata(page, wcagLevel) {
|
|
231
|
+
const [userAgent, viewport] = await Promise.all([
|
|
232
|
+
page.evaluate(() => navigator.userAgent),
|
|
233
|
+
page.viewport()
|
|
234
|
+
]);
|
|
235
|
+
return {
|
|
236
|
+
userAgent,
|
|
237
|
+
viewport: viewport || { width: 1920, height: 1080 },
|
|
238
|
+
wcagLevel
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Transform axe results to our format
|
|
243
|
+
*/
|
|
244
|
+
transformAxeResults(axeResults, url, metadata) {
|
|
245
|
+
const violations = axeResults.violations.map((violation) => ({
|
|
246
|
+
id: violation.id,
|
|
247
|
+
impact: violation.impact || 'moderate',
|
|
248
|
+
description: violation.description,
|
|
249
|
+
help: violation.help,
|
|
250
|
+
helpUrl: violation.helpUrl,
|
|
251
|
+
nodes: violation.nodes.map((node) => this.transformNode(node)),
|
|
252
|
+
tags: violation.tags || [],
|
|
253
|
+
wcagLevel: this.getWcagLevelFromTags(violation.tags),
|
|
254
|
+
wcagVersions: ['2.1'] // Simplified to just 2.1
|
|
255
|
+
}));
|
|
256
|
+
const passes = axeResults.passes.map((rule) => ({
|
|
257
|
+
id: rule.id,
|
|
258
|
+
description: rule.description,
|
|
259
|
+
impact: rule.impact,
|
|
260
|
+
tags: rule.tags || [],
|
|
261
|
+
nodes: rule.nodes?.map((node) => this.transformNode(node)) || []
|
|
262
|
+
}));
|
|
263
|
+
const result = {
|
|
264
|
+
url,
|
|
265
|
+
timestamp: new Date().toISOString(),
|
|
266
|
+
testDuration: 0, // Set by caller
|
|
267
|
+
violations,
|
|
268
|
+
passes,
|
|
269
|
+
summary: {
|
|
270
|
+
totalViolations: 0,
|
|
271
|
+
violationsBySeverity: { critical: 0, serious: 0, moderate: 0, minor: 0 },
|
|
272
|
+
passedRules: 0,
|
|
273
|
+
score: 0,
|
|
274
|
+
compliance: { A: false, AA: false, AAA: false }
|
|
275
|
+
},
|
|
276
|
+
metadata: metadata || {
|
|
277
|
+
userAgent: 'Unknown',
|
|
278
|
+
viewport: { width: 1920, height: 1080 },
|
|
279
|
+
wcagLevel: 'AA'
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
// Generate summary
|
|
283
|
+
result.summary = ReportGenerator.generateTestSummary(result);
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Transform axe node to our format
|
|
288
|
+
*/
|
|
289
|
+
transformNode(node) {
|
|
290
|
+
return {
|
|
291
|
+
html: node.html,
|
|
292
|
+
target: node.target,
|
|
293
|
+
xpath: node.xpath,
|
|
294
|
+
ancestry: node.ancestry,
|
|
295
|
+
failureSummary: node.failureSummary,
|
|
296
|
+
any: node.any?.map((check) => ({
|
|
297
|
+
id: check.id,
|
|
298
|
+
data: check.data,
|
|
299
|
+
relatedNodes: check.relatedNodes?.map((rn) => ({
|
|
300
|
+
html: rn.html,
|
|
301
|
+
target: rn.target
|
|
302
|
+
}))
|
|
303
|
+
}))
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Extract WCAG level from tags
|
|
308
|
+
*/
|
|
309
|
+
getWcagLevelFromTags(tags) {
|
|
310
|
+
if (tags.includes('wcag2aaa'))
|
|
311
|
+
return 'AAA';
|
|
312
|
+
if (tags.includes('wcag2aa'))
|
|
313
|
+
return 'AA';
|
|
314
|
+
return 'A';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=accessibility-tester.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility-tester.js","sourceRoot":"","sources":["../../src/tools/accessibility-tester.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ColorContrastResult } from '../types/index.js';
|
|
2
|
+
import type { ColorContrastInput } from '../types/mcp.js';
|
|
3
|
+
/**
|
|
4
|
+
* Color contrast tester for WCAG compliance
|
|
5
|
+
*/
|
|
6
|
+
export declare class ColorContrastTester {
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private readonly contrastRequirements;
|
|
9
|
+
constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Check color contrast for WCAG compliance
|
|
12
|
+
*/
|
|
13
|
+
checkContrast(input: ColorContrastInput): Promise<ColorContrastResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Parse color string to Color object
|
|
16
|
+
*/
|
|
17
|
+
private parseColor;
|
|
18
|
+
/**
|
|
19
|
+
* Calculate contrast ratio between two colors
|
|
20
|
+
* Formula from WCAG: (L1 + 0.05) / (L2 + 0.05)
|
|
21
|
+
* where L1 is the relative luminance of the lighter color
|
|
22
|
+
* and L2 is the relative luminance of the darker color
|
|
23
|
+
*/
|
|
24
|
+
private calculateContrastRatio;
|
|
25
|
+
/**
|
|
26
|
+
* Calculate relative luminance of a color
|
|
27
|
+
* Formula from WCAG: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
|
|
28
|
+
*/
|
|
29
|
+
private getRelativeLuminance;
|
|
30
|
+
/**
|
|
31
|
+
* Determine if text is considered "large" according to WCAG
|
|
32
|
+
* Large text is 18pt+ normal or 14pt+ bold
|
|
33
|
+
* 18pt = 24px, 14pt = ~18.7px
|
|
34
|
+
*/
|
|
35
|
+
private isLargeText;
|
|
36
|
+
/**
|
|
37
|
+
* Calculate accessibility score (0-100)
|
|
38
|
+
*/
|
|
39
|
+
private calculateAccessibilityScore;
|
|
40
|
+
/**
|
|
41
|
+
* Generate recommendations for improving color contrast
|
|
42
|
+
*/
|
|
43
|
+
private generateRecommendations;
|
|
44
|
+
/**
|
|
45
|
+
* Adjust color to meet contrast requirements
|
|
46
|
+
*/
|
|
47
|
+
private adjustColorForContrast;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=color-contrast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-contrast.d.ts","sourceRoot":"","sources":["../../src/tools/color-contrast.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAa,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,iBAAiB,CAAC;AAExE;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAGhC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAanC;;IAMF;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+D5E;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAU9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAanC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0D/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAwD/B"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
import { Logger } from '../utils/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Color contrast tester for WCAG compliance
|
|
5
|
+
*/
|
|
6
|
+
export class ColorContrastTester {
|
|
7
|
+
logger;
|
|
8
|
+
// WCAG contrast requirements
|
|
9
|
+
contrastRequirements = {
|
|
10
|
+
A: {
|
|
11
|
+
normal: 3.0,
|
|
12
|
+
large: 3.0
|
|
13
|
+
},
|
|
14
|
+
AA: {
|
|
15
|
+
normal: 4.5,
|
|
16
|
+
large: 3.0
|
|
17
|
+
},
|
|
18
|
+
AAA: {
|
|
19
|
+
normal: 7.0,
|
|
20
|
+
large: 4.5
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
constructor() {
|
|
24
|
+
this.logger = new Logger().child({ component: 'ColorContrastTester' });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check color contrast for WCAG compliance
|
|
28
|
+
*/
|
|
29
|
+
async checkContrast(input) {
|
|
30
|
+
this.logger.info('Starting color contrast analysis', {
|
|
31
|
+
foreground: input.foreground,
|
|
32
|
+
background: input.background,
|
|
33
|
+
fontSize: input.fontSize,
|
|
34
|
+
isBold: input.isBold
|
|
35
|
+
});
|
|
36
|
+
// Parse colors
|
|
37
|
+
const foregroundColor = this.parseColor(input.foreground);
|
|
38
|
+
const backgroundColor = this.parseColor(input.background);
|
|
39
|
+
if (!foregroundColor || !backgroundColor) {
|
|
40
|
+
throw new Error('Invalid color format provided');
|
|
41
|
+
}
|
|
42
|
+
// Calculate contrast ratio
|
|
43
|
+
const contrastRatio = this.calculateContrastRatio(foregroundColor, backgroundColor);
|
|
44
|
+
// Determine if text is considered "large" according to WCAG
|
|
45
|
+
const isLargeText = this.isLargeText(input.fontSize, input.isBold);
|
|
46
|
+
// Check WCAG compliance
|
|
47
|
+
const wcagAA = {
|
|
48
|
+
normal: contrastRatio >= this.contrastRequirements.AA.normal,
|
|
49
|
+
large: contrastRatio >= this.contrastRequirements.AA.large
|
|
50
|
+
};
|
|
51
|
+
const wcagAAA = {
|
|
52
|
+
normal: contrastRatio >= this.contrastRequirements.AAA.normal,
|
|
53
|
+
large: contrastRatio >= this.contrastRequirements.AAA.large
|
|
54
|
+
};
|
|
55
|
+
// Calculate accessibility score using AA as default
|
|
56
|
+
const score = this.calculateAccessibilityScore(contrastRatio, isLargeText, 'AA');
|
|
57
|
+
// Generate recommendations if needed
|
|
58
|
+
const recommendations = this.generateRecommendations(contrastRatio, isLargeText, 'AA', foregroundColor, backgroundColor);
|
|
59
|
+
const result = {
|
|
60
|
+
foreground: foregroundColor.hex(),
|
|
61
|
+
background: backgroundColor.hex(),
|
|
62
|
+
ratio: Math.round(contrastRatio * 100) / 100,
|
|
63
|
+
wcagAA,
|
|
64
|
+
wcagAAA,
|
|
65
|
+
score,
|
|
66
|
+
recommendations: recommendations.length > 0 ? recommendations : undefined
|
|
67
|
+
};
|
|
68
|
+
this.logger.info('Color contrast analysis completed', {
|
|
69
|
+
ratio: result.ratio,
|
|
70
|
+
score: result.score
|
|
71
|
+
});
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse color string to Color object
|
|
76
|
+
*/
|
|
77
|
+
parseColor(colorString) {
|
|
78
|
+
try {
|
|
79
|
+
return Color(colorString);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.logger.warn('Failed to parse color', { color: colorString, error });
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Calculate contrast ratio between two colors
|
|
88
|
+
* Formula from WCAG: (L1 + 0.05) / (L2 + 0.05)
|
|
89
|
+
* where L1 is the relative luminance of the lighter color
|
|
90
|
+
* and L2 is the relative luminance of the darker color
|
|
91
|
+
*/
|
|
92
|
+
calculateContrastRatio(color1, color2) {
|
|
93
|
+
const luminance1 = this.getRelativeLuminance(color1);
|
|
94
|
+
const luminance2 = this.getRelativeLuminance(color2);
|
|
95
|
+
const lighter = Math.max(luminance1, luminance2);
|
|
96
|
+
const darker = Math.min(luminance1, luminance2);
|
|
97
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculate relative luminance of a color
|
|
101
|
+
* Formula from WCAG: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
|
|
102
|
+
*/
|
|
103
|
+
getRelativeLuminance(color) {
|
|
104
|
+
const rgb = color.rgb().array();
|
|
105
|
+
const [r = 0, g = 0, b = 0] = rgb.map(c => {
|
|
106
|
+
c = c / 255;
|
|
107
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
108
|
+
});
|
|
109
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Determine if text is considered "large" according to WCAG
|
|
113
|
+
* Large text is 18pt+ normal or 14pt+ bold
|
|
114
|
+
* 18pt = 24px, 14pt = ~18.7px
|
|
115
|
+
*/
|
|
116
|
+
isLargeText(fontSize, isBold) {
|
|
117
|
+
if (isBold && fontSize >= 19)
|
|
118
|
+
return true; // 14pt bold
|
|
119
|
+
if (!isBold && fontSize >= 24)
|
|
120
|
+
return true; // 18pt normal
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Calculate accessibility score (0-100)
|
|
125
|
+
*/
|
|
126
|
+
calculateAccessibilityScore(ratio, isLargeText, wcagLevel) {
|
|
127
|
+
const requirements = this.contrastRequirements[wcagLevel];
|
|
128
|
+
const targetRatio = isLargeText ? requirements.large : requirements.normal;
|
|
129
|
+
if (ratio >= targetRatio) {
|
|
130
|
+
// Excellent score if meets or exceeds requirements
|
|
131
|
+
return Math.min(100, Math.round((ratio / targetRatio) * 90 + 10));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Poor score if doesn't meet requirements
|
|
135
|
+
return Math.round((ratio / targetRatio) * 60);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Generate recommendations for improving color contrast
|
|
140
|
+
*/
|
|
141
|
+
generateRecommendations(ratio, isLargeText, wcagLevel, foregroundColor, backgroundColor) {
|
|
142
|
+
const recommendations = [];
|
|
143
|
+
const requirements = this.contrastRequirements[wcagLevel];
|
|
144
|
+
const targetRatio = isLargeText ? requirements.large : requirements.normal;
|
|
145
|
+
if (ratio < targetRatio) {
|
|
146
|
+
const improvement = targetRatio / ratio;
|
|
147
|
+
recommendations.push(`Current ratio ${ratio.toFixed(2)}:1 does not meet WCAG ${wcagLevel} requirements (${targetRatio}:1 needed)`);
|
|
148
|
+
// Suggest darkening foreground or lightening background
|
|
149
|
+
const fgLuminance = this.getRelativeLuminance(foregroundColor);
|
|
150
|
+
const bgLuminance = this.getRelativeLuminance(backgroundColor);
|
|
151
|
+
if (fgLuminance > bgLuminance) {
|
|
152
|
+
// Light text on dark background
|
|
153
|
+
recommendations.push('Try darkening the background color or lightening the text color');
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Dark text on light background
|
|
157
|
+
recommendations.push('Try darkening the text color or lightening the background color');
|
|
158
|
+
}
|
|
159
|
+
// Specific color suggestions
|
|
160
|
+
try {
|
|
161
|
+
const improvedForeground = this.adjustColorForContrast(foregroundColor, backgroundColor, targetRatio, 'foreground');
|
|
162
|
+
const improvedBackground = this.adjustColorForContrast(backgroundColor, foregroundColor, targetRatio, 'background');
|
|
163
|
+
if (improvedForeground) {
|
|
164
|
+
recommendations.push(`Suggested text color: ${improvedForeground.hex()}`);
|
|
165
|
+
}
|
|
166
|
+
if (improvedBackground) {
|
|
167
|
+
recommendations.push(`Suggested background color: ${improvedBackground.hex()}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.logger.warn('Failed to generate color suggestions', { error: String(error) });
|
|
172
|
+
}
|
|
173
|
+
// Font size suggestions
|
|
174
|
+
if (!isLargeText) {
|
|
175
|
+
const largeTextRequirement = requirements.large;
|
|
176
|
+
if (ratio >= largeTextRequirement) {
|
|
177
|
+
recommendations.push('Alternatively, increase font size to 24px+ or use 19px+ bold text');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return recommendations;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Adjust color to meet contrast requirements
|
|
185
|
+
*/
|
|
186
|
+
adjustColorForContrast(colorToAdjust, fixedColor, targetRatio, type) {
|
|
187
|
+
try {
|
|
188
|
+
const fixedLuminance = this.getRelativeLuminance(fixedColor);
|
|
189
|
+
let targetLuminance;
|
|
190
|
+
if (type === 'foreground') {
|
|
191
|
+
// Adjust foreground color
|
|
192
|
+
if (fixedLuminance > 0.5) {
|
|
193
|
+
// Light background, need dark foreground
|
|
194
|
+
targetLuminance = (fixedLuminance + 0.05) / targetRatio - 0.05;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Dark background, need light foreground
|
|
198
|
+
targetLuminance = targetRatio * (fixedLuminance + 0.05) - 0.05;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Adjust background color
|
|
203
|
+
const fgLuminance = this.getRelativeLuminance(colorToAdjust);
|
|
204
|
+
if (fgLuminance > 0.5) {
|
|
205
|
+
// Light foreground, need dark background
|
|
206
|
+
targetLuminance = (fgLuminance + 0.05) / targetRatio - 0.05;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Dark foreground, need light background
|
|
210
|
+
targetLuminance = targetRatio * (fgLuminance + 0.05) - 0.05;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Clamp luminance to valid range
|
|
214
|
+
targetLuminance = Math.max(0, Math.min(1, targetLuminance));
|
|
215
|
+
// Convert luminance back to color
|
|
216
|
+
const hsl = colorToAdjust.hsl().object();
|
|
217
|
+
const h = hsl.h ?? 0;
|
|
218
|
+
const s = hsl.s ?? 0;
|
|
219
|
+
let l = hsl.l ?? 0;
|
|
220
|
+
const adjustmentFactor = targetLuminance / this.getRelativeLuminance(colorToAdjust);
|
|
221
|
+
if (adjustmentFactor < 1) {
|
|
222
|
+
// Need to darken
|
|
223
|
+
l = Math.max(0, l * adjustmentFactor);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// Need to lighten
|
|
227
|
+
l = Math.min(100, l + (100 - l) * (adjustmentFactor - 1));
|
|
228
|
+
}
|
|
229
|
+
return Color.hsl(h, s, l);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
this.logger.warn('Failed to adjust color', { error: String(error) });
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=color-contrast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color-contrast.js","sourceRoot":"","sources":["../../src/tools/color-contrast.ts"],"names":[],"mappings":"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"}
|