gbu-accessibility-package 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/PACKAGE_SUMMARY.md +191 -0
- package/QUICK_START.md +137 -0
- package/README.md +305 -0
- package/cli.js +157 -0
- package/demo/demo.js +73 -0
- package/demo/sample.html +47 -0
- package/demo/sample.html.backup +47 -0
- package/example.js +121 -0
- package/index.js +8 -0
- package/lib/enhancer.js +163 -0
- package/lib/fixer.js +764 -0
- package/lib/tester.js +157 -0
- package/package.json +62 -0
package/cli.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accessibility Fixer CLI
|
|
5
|
+
* Command line interface for the accessibility fixer tool
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const AccessibilityFixer = require('./lib/fixer.js');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// Parse command line arguments
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const options = {
|
|
15
|
+
directory: '.',
|
|
16
|
+
language: 'ja',
|
|
17
|
+
backupFiles: true,
|
|
18
|
+
dryRun: false,
|
|
19
|
+
help: false
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Parse arguments
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
const arg = args[i];
|
|
25
|
+
|
|
26
|
+
switch (arg) {
|
|
27
|
+
case '--help':
|
|
28
|
+
case '-h':
|
|
29
|
+
options.help = true;
|
|
30
|
+
break;
|
|
31
|
+
case '--directory':
|
|
32
|
+
case '-d':
|
|
33
|
+
options.directory = args[++i];
|
|
34
|
+
break;
|
|
35
|
+
case '--language':
|
|
36
|
+
case '-l':
|
|
37
|
+
options.language = args[++i];
|
|
38
|
+
break;
|
|
39
|
+
case '--no-backup':
|
|
40
|
+
options.backupFiles = false;
|
|
41
|
+
break;
|
|
42
|
+
case '--dry-run':
|
|
43
|
+
options.dryRun = true;
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
if (!arg.startsWith('-')) {
|
|
47
|
+
options.directory = arg;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Show help
|
|
53
|
+
if (options.help) {
|
|
54
|
+
console.log(chalk.blue(`
|
|
55
|
+
🔧 Accessibility Fixer CLI
|
|
56
|
+
|
|
57
|
+
Usage: node cli.js [options] [directory]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
-d, --directory <path> Target directory (default: current directory)
|
|
61
|
+
-l, --language <lang> Language for lang attribute (default: ja)
|
|
62
|
+
--no-backup Don't create backup files
|
|
63
|
+
--dry-run Preview changes without applying
|
|
64
|
+
-h, --help Show this help message
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
node cli.js # Fix current directory
|
|
68
|
+
node cli.js ./src # Fix src directory
|
|
69
|
+
node cli.js -l en --dry-run ./dist # Preview fixes for dist directory in English
|
|
70
|
+
node cli.js --no-backup ./public # Fix without creating backups
|
|
71
|
+
|
|
72
|
+
Features:
|
|
73
|
+
✅ Alt attributes for images
|
|
74
|
+
✅ Lang attributes for HTML
|
|
75
|
+
✅ Role attributes for accessibility
|
|
76
|
+
✅ Context-aware text generation
|
|
77
|
+
✅ Automatic backups
|
|
78
|
+
`));
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Main function
|
|
83
|
+
async function main() {
|
|
84
|
+
console.log(chalk.blue('🚀 Starting Accessibility Fixer...'));
|
|
85
|
+
console.log(chalk.gray(`Directory: ${path.resolve(options.directory)}`));
|
|
86
|
+
console.log(chalk.gray(`Language: ${options.language}`));
|
|
87
|
+
console.log(chalk.gray(`Backup: ${options.backupFiles ? 'Yes' : 'No'}`));
|
|
88
|
+
console.log(chalk.gray(`Mode: ${options.dryRun ? 'Dry Run (Preview)' : 'Apply Changes'}`));
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
const fixer = new AccessibilityFixer({
|
|
92
|
+
language: options.language,
|
|
93
|
+
backupFiles: options.backupFiles,
|
|
94
|
+
dryRun: options.dryRun
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Fix HTML lang attributes
|
|
99
|
+
console.log(chalk.yellow('📝 Step 1: Fixing HTML lang attributes...'));
|
|
100
|
+
const langResults = await fixer.fixHtmlLang(options.directory);
|
|
101
|
+
const langFixed = langResults.filter(r => r.status === 'fixed').length;
|
|
102
|
+
console.log(chalk.green(`✅ Fixed lang attributes in ${langFixed} files`));
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
// Fix alt attributes
|
|
106
|
+
console.log(chalk.yellow('🖼️ Step 2: Fixing alt attributes...'));
|
|
107
|
+
const altResults = await fixer.fixEmptyAltAttributes(options.directory);
|
|
108
|
+
const altFixed = altResults.filter(r => r.status === 'fixed').length;
|
|
109
|
+
const totalAltIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
|
|
110
|
+
console.log(chalk.green(`✅ Fixed alt attributes in ${altFixed} files (${totalAltIssues} issues)`));
|
|
111
|
+
console.log('');
|
|
112
|
+
|
|
113
|
+
// Fix role attributes
|
|
114
|
+
console.log(chalk.yellow('🎭 Step 3: Fixing role attributes...'));
|
|
115
|
+
const roleResults = await fixer.fixRoleAttributes(options.directory);
|
|
116
|
+
const roleFixed = roleResults.filter(r => r.status === 'fixed').length;
|
|
117
|
+
const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
|
|
118
|
+
console.log(chalk.green(`✅ Fixed role attributes in ${roleFixed} files (${totalRoleIssues} issues)`));
|
|
119
|
+
console.log('');
|
|
120
|
+
|
|
121
|
+
// Summary
|
|
122
|
+
const totalFiles = new Set([
|
|
123
|
+
...langResults.map(r => r.file),
|
|
124
|
+
...altResults.map(r => r.file),
|
|
125
|
+
...roleResults.map(r => r.file)
|
|
126
|
+
]).size;
|
|
127
|
+
|
|
128
|
+
const totalFixed = new Set([
|
|
129
|
+
...langResults.filter(r => r.status === 'fixed').map(r => r.file),
|
|
130
|
+
...altResults.filter(r => r.status === 'fixed').map(r => r.file),
|
|
131
|
+
...roleResults.filter(r => r.status === 'fixed').map(r => r.file)
|
|
132
|
+
]).size;
|
|
133
|
+
|
|
134
|
+
const totalIssues = totalAltIssues + totalRoleIssues + langFixed;
|
|
135
|
+
|
|
136
|
+
console.log(chalk.blue('📊 Summary:'));
|
|
137
|
+
console.log(chalk.white(` Total files scanned: ${totalFiles}`));
|
|
138
|
+
console.log(chalk.green(` Files fixed: ${totalFixed}`));
|
|
139
|
+
console.log(chalk.yellow(` Total issues resolved: ${totalIssues}`));
|
|
140
|
+
|
|
141
|
+
if (options.dryRun) {
|
|
142
|
+
console.log(chalk.cyan('\n💡 This was a dry run. Use without --dry-run to apply changes.'));
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.green('\n🎉 All accessibility fixes completed successfully!'));
|
|
145
|
+
if (options.backupFiles) {
|
|
146
|
+
console.log(chalk.gray(' Backup files created with .backup extension'));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(chalk.red('❌ Error occurred:'), error.message);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Run the CLI
|
|
157
|
+
main();
|
package/demo/demo.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demo script to test accessibility package functionality
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { AccessibilityTester, AccessibilityFixer } = require('../index');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
async function runDemo() {
|
|
12
|
+
console.log(chalk.blue('🚀 Accessibility Package Demo'));
|
|
13
|
+
console.log(chalk.blue('===============================\n'));
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Test the fixer first
|
|
17
|
+
console.log(chalk.yellow('1. Testing Accessibility Fixer...'));
|
|
18
|
+
const fixer = new AccessibilityFixer({
|
|
19
|
+
language: 'ja',
|
|
20
|
+
dryRun: true // Don't actually modify files in demo
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Go to parent directory to find HTML files
|
|
24
|
+
const projectRoot = path.join(__dirname, '../..');
|
|
25
|
+
|
|
26
|
+
console.log(chalk.gray(` Scanning directory: ${projectRoot}`));
|
|
27
|
+
|
|
28
|
+
const langResults = await fixer.fixHtmlLang(projectRoot);
|
|
29
|
+
console.log(chalk.green(` ✅ Lang attribute check: ${langResults.length} files scanned`));
|
|
30
|
+
|
|
31
|
+
const altResults = await fixer.fixEmptyAltAttributes(projectRoot);
|
|
32
|
+
console.log(chalk.green(` ✅ Alt attribute check: ${altResults.length} files scanned`));
|
|
33
|
+
|
|
34
|
+
const mainSuggestions = await fixer.addMainLandmarks(projectRoot);
|
|
35
|
+
console.log(chalk.green(` ✅ Main landmark check: ${mainSuggestions.length} suggestions`));
|
|
36
|
+
|
|
37
|
+
console.log(chalk.yellow('\n2. Testing Accessibility Tester...'));
|
|
38
|
+
|
|
39
|
+
// Find HTML files in project
|
|
40
|
+
const fs = require('fs').promises;
|
|
41
|
+
const files = await fs.readdir(projectRoot);
|
|
42
|
+
const htmlFiles = files.filter(f => f.endsWith('.html')).slice(0, 3); // Test first 3 files
|
|
43
|
+
|
|
44
|
+
if (htmlFiles.length > 0) {
|
|
45
|
+
console.log(chalk.gray(` Found HTML files: ${htmlFiles.join(', ')}`));
|
|
46
|
+
|
|
47
|
+
const tester = new AccessibilityTester({
|
|
48
|
+
baseUrl: 'http://localhost:8080',
|
|
49
|
+
outputDir: path.join(projectRoot, 'accessibility-reports'),
|
|
50
|
+
pages: htmlFiles,
|
|
51
|
+
serverPort: 8080
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(chalk.gray(' Note: Actual testing requires a running server'));
|
|
55
|
+
console.log(chalk.green(' ✅ Tester configuration ready'));
|
|
56
|
+
} else {
|
|
57
|
+
console.log(chalk.yellow(' ⚠️ No HTML files found for testing'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green('\n✅ Demo completed successfully!'));
|
|
61
|
+
console.log(chalk.blue('\n📋 Package is working correctly. You can now use:'));
|
|
62
|
+
console.log(chalk.white(' a11y-fix all --dry-run # Preview fixes'));
|
|
63
|
+
console.log(chalk.white(' a11y-fix all # Apply fixes'));
|
|
64
|
+
console.log(chalk.white(' a11y-test run # Run accessibility tests'));
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(chalk.red(`❌ Demo failed: ${error.message}`));
|
|
68
|
+
console.error(chalk.gray(error.stack));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
runDemo();
|
package/demo/sample.html
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Demo HTML for Accessibility Testing</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Sample Website</h1>
|
|
8
|
+
|
|
9
|
+
<!-- Images without alt or role -->
|
|
10
|
+
<img src="logo.png">
|
|
11
|
+
<img src="banner.jpg" alt="">
|
|
12
|
+
<img src="icon.svg" alt="Home Icon">
|
|
13
|
+
|
|
14
|
+
<!-- Links without role -->
|
|
15
|
+
<a href="/home">Home</a>
|
|
16
|
+
<a href="/about">About Us</a>
|
|
17
|
+
|
|
18
|
+
<!-- Buttons without role -->
|
|
19
|
+
<button onclick="submit()">Submit Form</button>
|
|
20
|
+
<button type="button">Regular Button</button>
|
|
21
|
+
|
|
22
|
+
<!-- Clickable elements -->
|
|
23
|
+
<div onclick="navigate()" class="btn">Click Me</div>
|
|
24
|
+
<span onclick="toggle()">Toggle</span>
|
|
25
|
+
|
|
26
|
+
<!-- Navigation -->
|
|
27
|
+
<nav>
|
|
28
|
+
<ul class="nav-menu">
|
|
29
|
+
<li class="nav-item"><a href="/products">Products</a></li>
|
|
30
|
+
<li class="nav-item"><a href="/services">Services</a></li>
|
|
31
|
+
</ul>
|
|
32
|
+
</nav>
|
|
33
|
+
|
|
34
|
+
<!-- More content -->
|
|
35
|
+
<main>
|
|
36
|
+
<article>
|
|
37
|
+
<h2>Article Title</h2>
|
|
38
|
+
<p>Some content here...</p>
|
|
39
|
+
<img src="article-image.jpg" alt="">
|
|
40
|
+
</article>
|
|
41
|
+
</main>
|
|
42
|
+
|
|
43
|
+
<footer>
|
|
44
|
+
<p>© 2024 Demo Company</p>
|
|
45
|
+
</footer>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Demo HTML for Accessibility Testing</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Sample Website</h1>
|
|
8
|
+
|
|
9
|
+
<!-- Images without alt or role -->
|
|
10
|
+
<img src="logo.png">
|
|
11
|
+
<img src="banner.jpg" alt="">
|
|
12
|
+
<img src="icon.svg" alt="Home Icon">
|
|
13
|
+
|
|
14
|
+
<!-- Links without role -->
|
|
15
|
+
<a href="/home">Home</a>
|
|
16
|
+
<a href="/about">About Us</a>
|
|
17
|
+
|
|
18
|
+
<!-- Buttons without role -->
|
|
19
|
+
<button onclick="submit()">Submit Form</button>
|
|
20
|
+
<button type="button">Regular Button</button>
|
|
21
|
+
|
|
22
|
+
<!-- Clickable elements -->
|
|
23
|
+
<div onclick="navigate()" class="btn">Click Me</div>
|
|
24
|
+
<span onclick="toggle()">Toggle</span>
|
|
25
|
+
|
|
26
|
+
<!-- Navigation -->
|
|
27
|
+
<nav>
|
|
28
|
+
<ul class="nav-menu">
|
|
29
|
+
<li class="nav-item"><a href="/products">Products</a></li>
|
|
30
|
+
<li class="nav-item"><a href="/services">Services</a></li>
|
|
31
|
+
</ul>
|
|
32
|
+
</nav>
|
|
33
|
+
|
|
34
|
+
<!-- More content -->
|
|
35
|
+
<main>
|
|
36
|
+
<article>
|
|
37
|
+
<h2>Article Title</h2>
|
|
38
|
+
<p>Some content here...</p>
|
|
39
|
+
<img src="article-image.jpg" alt="">
|
|
40
|
+
</article>
|
|
41
|
+
</main>
|
|
42
|
+
|
|
43
|
+
<footer>
|
|
44
|
+
<p>© 2024 Demo Company</p>
|
|
45
|
+
</footer>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
package/example.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example usage of Accessibility Fixer
|
|
3
|
+
* Demonstrates different ways to use the tool
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const AccessibilityFixer = require('./lib/fixer.js');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
async function example1_BasicUsage() {
|
|
10
|
+
console.log(chalk.blue('\n📝 Example 1: Basic Usage'));
|
|
11
|
+
|
|
12
|
+
const fixer = new AccessibilityFixer({
|
|
13
|
+
language: 'ja',
|
|
14
|
+
backupFiles: true,
|
|
15
|
+
dryRun: true // Preview mode
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Fix all accessibility issues
|
|
19
|
+
await fixer.fixHtmlLang('./demo');
|
|
20
|
+
await fixer.fixEmptyAltAttributes('./demo');
|
|
21
|
+
await fixer.fixRoleAttributes('./demo');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function example2_EnglishProject() {
|
|
25
|
+
console.log(chalk.blue('\n📝 Example 2: English Project'));
|
|
26
|
+
|
|
27
|
+
const fixer = new AccessibilityFixer({
|
|
28
|
+
language: 'en',
|
|
29
|
+
backupFiles: false,
|
|
30
|
+
dryRun: false
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const results = await fixer.fixRoleAttributes('./demo');
|
|
34
|
+
console.log(`Fixed ${results.filter(r => r.status === 'fixed').length} files`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function example3_StepByStep() {
|
|
38
|
+
console.log(chalk.blue('\n📝 Example 3: Step by Step'));
|
|
39
|
+
|
|
40
|
+
const fixer = new AccessibilityFixer({
|
|
41
|
+
language: 'vi',
|
|
42
|
+
backupFiles: true,
|
|
43
|
+
dryRun: false
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Step 1: Fix lang attributes first
|
|
47
|
+
console.log('Step 1: Lang attributes...');
|
|
48
|
+
await fixer.fixHtmlLang('./demo');
|
|
49
|
+
|
|
50
|
+
// Step 2: Fix alt attributes
|
|
51
|
+
console.log('Step 2: Alt attributes...');
|
|
52
|
+
await fixer.fixEmptyAltAttributes('./demo');
|
|
53
|
+
|
|
54
|
+
// Step 3: Fix role attributes
|
|
55
|
+
console.log('Step 3: Role attributes...');
|
|
56
|
+
await fixer.fixRoleAttributes('./demo');
|
|
57
|
+
|
|
58
|
+
console.log('✅ All done!');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function example4_CustomConfig() {
|
|
62
|
+
console.log(chalk.blue('\n📝 Example 4: Custom Configuration'));
|
|
63
|
+
|
|
64
|
+
// Custom configuration for specific needs
|
|
65
|
+
const fixer = new AccessibilityFixer({
|
|
66
|
+
language: 'zh',
|
|
67
|
+
backupFiles: true,
|
|
68
|
+
dryRun: true
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Only fix specific issues
|
|
72
|
+
const altResults = await fixer.fixEmptyAltAttributes('./demo');
|
|
73
|
+
|
|
74
|
+
// Analyze results
|
|
75
|
+
const fixedFiles = altResults.filter(r => r.status === 'fixed');
|
|
76
|
+
const totalIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
|
|
77
|
+
|
|
78
|
+
console.log(`Found ${totalIssues} alt attribute issues in ${fixedFiles.length} files`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function example5_ErrorHandling() {
|
|
82
|
+
console.log(chalk.blue('\n📝 Example 5: Error Handling'));
|
|
83
|
+
|
|
84
|
+
const fixer = new AccessibilityFixer({
|
|
85
|
+
language: 'ja',
|
|
86
|
+
backupFiles: true,
|
|
87
|
+
dryRun: false
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const results = await fixer.fixRoleAttributes('./nonexistent-directory');
|
|
92
|
+
console.log('Results:', results);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(chalk.red('Error occurred:'), error.message);
|
|
95
|
+
// Handle error appropriately
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Run examples
|
|
100
|
+
async function runExamples() {
|
|
101
|
+
console.log(chalk.green('🚀 Accessibility Fixer Examples\n'));
|
|
102
|
+
|
|
103
|
+
await example1_BasicUsage();
|
|
104
|
+
await example2_EnglishProject();
|
|
105
|
+
await example3_StepByStep();
|
|
106
|
+
await example4_CustomConfig();
|
|
107
|
+
await example5_ErrorHandling();
|
|
108
|
+
|
|
109
|
+
console.log(chalk.green('\n✅ All examples completed!'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Uncomment to run examples
|
|
113
|
+
// runExamples();
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
example1_BasicUsage,
|
|
117
|
+
example2_EnglishProject,
|
|
118
|
+
example3_StepByStep,
|
|
119
|
+
example4_CustomConfig,
|
|
120
|
+
example5_ErrorHandling
|
|
121
|
+
};
|
package/index.js
ADDED
package/lib/enhancer.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility Enhancer
|
|
3
|
+
* JavaScript enhancements for better accessibility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class AccessibilityEnhancer {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.config = {
|
|
9
|
+
language: config.language || 'ja',
|
|
10
|
+
skipLinkText: config.skipLinkText || 'メインコンテンツにスキップ',
|
|
11
|
+
...config
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Generate JavaScript code for accessibility enhancements
|
|
16
|
+
generateEnhancementScript() {
|
|
17
|
+
return `
|
|
18
|
+
/**
|
|
19
|
+
* Accessibility Enhancements
|
|
20
|
+
* Auto-generated by Accessibility Toolkit
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
(function () {
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// Add accessibility labels to slider pagination buttons
|
|
27
|
+
function addSliderAccessibilityLabels() {
|
|
28
|
+
setTimeout(function () {
|
|
29
|
+
// Find all pagination containers
|
|
30
|
+
const paginationContainers = document.querySelectorAll('.splide-custom-pagination, .pagination');
|
|
31
|
+
|
|
32
|
+
paginationContainers.forEach(function (container) {
|
|
33
|
+
const buttons = container.querySelectorAll('button');
|
|
34
|
+
buttons.forEach(function (button, index) {
|
|
35
|
+
const slideNumber = index + 1;
|
|
36
|
+
if (!button.getAttribute('aria-label')) {
|
|
37
|
+
button.setAttribute('aria-label', 'スライド' + slideNumber + 'を表示');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Handle pagination buttons with data-index
|
|
43
|
+
const paginationButtons = document.querySelectorAll('[data-index]');
|
|
44
|
+
paginationButtons.forEach(function (button) {
|
|
45
|
+
const index = button.getAttribute('data-index');
|
|
46
|
+
const slideNumber = parseInt(index) + 1;
|
|
47
|
+
if (!button.getAttribute('aria-label')) {
|
|
48
|
+
button.setAttribute('aria-label', 'スライド' + slideNumber + 'を表示');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Add role="img" and aria-label to picture elements in slides
|
|
53
|
+
const sliderPictures = document.querySelectorAll('.splide__slide picture, .slide picture');
|
|
54
|
+
sliderPictures.forEach(function (picture) {
|
|
55
|
+
if (!picture.getAttribute('role')) {
|
|
56
|
+
picture.setAttribute('role', 'img');
|
|
57
|
+
}
|
|
58
|
+
if (!picture.getAttribute('aria-label')) {
|
|
59
|
+
const slide = picture.closest('.splide__slide, .slide');
|
|
60
|
+
const heading = slide ? slide.querySelector('h4, h3, h2, h1') : null;
|
|
61
|
+
const altText = picture.querySelector('img') ? picture.querySelector('img').getAttribute('alt') : '';
|
|
62
|
+
|
|
63
|
+
let ariaLabel = '';
|
|
64
|
+
if (heading && heading.textContent) {
|
|
65
|
+
ariaLabel = heading.textContent + 'の画像';
|
|
66
|
+
} else if (altText) {
|
|
67
|
+
ariaLabel = altText + 'の画像';
|
|
68
|
+
} else {
|
|
69
|
+
ariaLabel = 'スライド画像';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
picture.setAttribute('aria-label', ariaLabel);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}, 1000);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add skip link for keyboard navigation
|
|
79
|
+
function addSkipLink() {
|
|
80
|
+
const skipLink = document.createElement('a');
|
|
81
|
+
skipLink.href = '#main-content';
|
|
82
|
+
skipLink.textContent = '${this.config.skipLinkText}';
|
|
83
|
+
skipLink.className = 'skip-link sr-only';
|
|
84
|
+
skipLink.style.cssText = \`
|
|
85
|
+
position: absolute;
|
|
86
|
+
top: -40px;
|
|
87
|
+
left: 6px;
|
|
88
|
+
background: #000;
|
|
89
|
+
color: #fff;
|
|
90
|
+
padding: 8px;
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
z-index: 1000;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
\`;
|
|
96
|
+
|
|
97
|
+
skipLink.addEventListener('focus', function () {
|
|
98
|
+
this.style.top = '6px';
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
skipLink.addEventListener('blur', function () {
|
|
102
|
+
this.style.top = '-40px';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
document.body.insertBefore(skipLink, document.body.firstChild);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add main content ID for skip link target
|
|
109
|
+
function addMainContentId() {
|
|
110
|
+
const mainElement = document.querySelector('main');
|
|
111
|
+
if (mainElement && !mainElement.id) {
|
|
112
|
+
mainElement.id = 'main-content';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Fix missing form labels
|
|
117
|
+
function fixFormLabels() {
|
|
118
|
+
const inputs = document.querySelectorAll('input[type="text"], input[type="email"], input[type="tel"], textarea');
|
|
119
|
+
inputs.forEach(function(input) {
|
|
120
|
+
if (!input.getAttribute('aria-label') && !input.getAttribute('aria-labelledby')) {
|
|
121
|
+
const placeholder = input.getAttribute('placeholder');
|
|
122
|
+
if (placeholder) {
|
|
123
|
+
input.setAttribute('aria-label', placeholder);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Initialize all enhancements
|
|
130
|
+
function init() {
|
|
131
|
+
if (document.readyState === 'loading') {
|
|
132
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
133
|
+
addSkipLink();
|
|
134
|
+
addMainContentId();
|
|
135
|
+
addSliderAccessibilityLabels();
|
|
136
|
+
fixFormLabels();
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
addSkipLink();
|
|
140
|
+
addMainContentId();
|
|
141
|
+
addSliderAccessibilityLabels();
|
|
142
|
+
fixFormLabels();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Run again after delay for dynamic content
|
|
146
|
+
setTimeout(addSliderAccessibilityLabels, 2000);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
init();
|
|
150
|
+
})();
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Save enhancement script to file
|
|
155
|
+
async saveEnhancementScript(outputPath) {
|
|
156
|
+
const fs = require('fs').promises;
|
|
157
|
+
const script = this.generateEnhancementScript();
|
|
158
|
+
await fs.writeFile(outputPath, script);
|
|
159
|
+
return outputPath;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = AccessibilityEnhancer;
|