@kodalabs-io/eqo 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 +162 -0
- package/action.yml +77 -0
- package/dist/chunk-WKI3N5NX.js +3258 -0
- package/dist/cli/index.js +209 -0
- package/dist/en-US-JQN64XYI.js +300 -0
- package/dist/fr-FR-NZKLCQE5.js +291 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.js +941 -0
- package/dist/worker.js +9 -0
- package/package.json +115 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
analyze,
|
|
4
|
+
error,
|
|
5
|
+
generateDefaultConfig,
|
|
6
|
+
loadConfig,
|
|
7
|
+
loadTranslations,
|
|
8
|
+
printError,
|
|
9
|
+
printInfo,
|
|
10
|
+
printProgress,
|
|
11
|
+
printReport,
|
|
12
|
+
printSuccess,
|
|
13
|
+
writeReports
|
|
14
|
+
} from "../chunk-WKI3N5NX.js";
|
|
15
|
+
|
|
16
|
+
// src/cli/index.ts
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
|
|
19
|
+
// src/cli/commands/analyze.ts
|
|
20
|
+
import path from "path";
|
|
21
|
+
import pc from "picocolors";
|
|
22
|
+
function parseThreshold(value) {
|
|
23
|
+
if (value === void 0) return void 0;
|
|
24
|
+
const num = Number(value);
|
|
25
|
+
if (Number.isNaN(num) || num < 0 || num > 100) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return num;
|
|
29
|
+
}
|
|
30
|
+
function sanitizeAnnotation(str) {
|
|
31
|
+
return str.replace(/::/g, ": :").replace(/[\r\n]/g, " ").replace(/,/g, " ");
|
|
32
|
+
}
|
|
33
|
+
async function runAnalyze(options) {
|
|
34
|
+
if (options.staticOnly && options.runtimeOnly) {
|
|
35
|
+
printError("Cannot use both --static-only and --runtime-only");
|
|
36
|
+
return { exitCode: 1 };
|
|
37
|
+
}
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
let config;
|
|
40
|
+
try {
|
|
41
|
+
config = await loadConfig(cwd, options.config);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
44
|
+
return { exitCode: 1 };
|
|
45
|
+
}
|
|
46
|
+
const locale = options.locale ?? config.locale ?? "en-US";
|
|
47
|
+
await loadTranslations(locale);
|
|
48
|
+
const thresholdOverride = parseThreshold(options.threshold);
|
|
49
|
+
if (thresholdOverride === null) {
|
|
50
|
+
printError(`Invalid --threshold "${options.threshold}". Must be a number between 0 and 100.`);
|
|
51
|
+
}
|
|
52
|
+
if (thresholdOverride === null) return { exitCode: 1 };
|
|
53
|
+
const complianceThreshold = thresholdOverride ?? config.thresholds?.complianceRate ?? 0;
|
|
54
|
+
const failOn = thresholdOverride !== void 0 ? "threshold" : config.thresholds?.failOn ?? "threshold";
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(pc.bold(pc.cyan(" eqo \u2014 RGAA v4.1.2 Accessibility Analyzer")));
|
|
57
|
+
console.log(pc.dim(` ${(/* @__PURE__ */ new Date()).toISOString()}`));
|
|
58
|
+
console.log("");
|
|
59
|
+
if (!options.runtimeOnly) {
|
|
60
|
+
printProgress("Static analysis (source files)");
|
|
61
|
+
}
|
|
62
|
+
let report;
|
|
63
|
+
try {
|
|
64
|
+
report = await analyze(config, {
|
|
65
|
+
staticOnly: options.staticOnly,
|
|
66
|
+
runtimeOnly: options.runtimeOnly,
|
|
67
|
+
projectRoot: cwd,
|
|
68
|
+
signal: options.signal
|
|
69
|
+
});
|
|
70
|
+
} catch (err) {
|
|
71
|
+
printError(`Analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
return { exitCode: 1 };
|
|
73
|
+
}
|
|
74
|
+
if (options.signal?.aborted) {
|
|
75
|
+
return { exitCode: 130 };
|
|
76
|
+
}
|
|
77
|
+
printSuccess("Analysis complete");
|
|
78
|
+
const writtenPaths = await writeReports(report, config.output);
|
|
79
|
+
if (writtenPaths.length === 0 && config.output.length > 0) {
|
|
80
|
+
printError("All report writes failed. Check file permissions and disk space.");
|
|
81
|
+
}
|
|
82
|
+
for (const p of writtenPaths) {
|
|
83
|
+
printInfo(`Report written to ${pc.underline(path.relative(cwd, p))}`);
|
|
84
|
+
}
|
|
85
|
+
printReport(report);
|
|
86
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
87
|
+
for (const issue of report.issues) {
|
|
88
|
+
if (issue.severity !== "error" && issue.severity !== "warning") continue;
|
|
89
|
+
const level = issue.severity === "error" ? "error" : "warning";
|
|
90
|
+
const loc = issue.file ? `file=${sanitizeAnnotation(issue.file)},line=${issue.line ?? 1},col=${issue.column ?? 1}` : "file=unknown";
|
|
91
|
+
const title = sanitizeAnnotation(`RGAA ${issue.criterionId}`);
|
|
92
|
+
const msg = sanitizeAnnotation(issue.messageKey);
|
|
93
|
+
console.log(`::${level} ${loc},title=${title}::${msg}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const pct = Math.round(report.summary.complianceRate * 100);
|
|
97
|
+
const hasErrors = report.issues.some((i) => i.severity === "error");
|
|
98
|
+
let shouldFail = false;
|
|
99
|
+
if (failOn === "none" || complianceThreshold === 0) {
|
|
100
|
+
shouldFail = false;
|
|
101
|
+
} else if (failOn === "error" && hasErrors) {
|
|
102
|
+
shouldFail = true;
|
|
103
|
+
printError(
|
|
104
|
+
`Analysis produced ${report.issues.filter((i) => i.severity === "error").length} error(s).`
|
|
105
|
+
);
|
|
106
|
+
} else if (failOn === "threshold" && complianceThreshold > 0 && pct < complianceThreshold) {
|
|
107
|
+
shouldFail = true;
|
|
108
|
+
printError(
|
|
109
|
+
`Compliance rate ${pct}% is below the required threshold of ${complianceThreshold}%.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return { exitCode: shouldFail ? 1 : 0 };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/cli/commands/init.ts
|
|
116
|
+
import { readFile, writeFile } from "fs/promises";
|
|
117
|
+
import path2 from "path";
|
|
118
|
+
import pc2 from "picocolors";
|
|
119
|
+
async function runInit(options) {
|
|
120
|
+
const cwd = process.cwd();
|
|
121
|
+
const configPath = path2.join(cwd, "rgaa.config.ts");
|
|
122
|
+
let projectName = options.projectName;
|
|
123
|
+
if (!projectName) {
|
|
124
|
+
try {
|
|
125
|
+
const pkg = JSON.parse(
|
|
126
|
+
await readFile(path2.join(cwd, "package.json"), "utf-8"),
|
|
127
|
+
(key, value) => {
|
|
128
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
projectName = pkg.name ?? void 0;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err.code !== "ENOENT") throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const content = generateDefaultConfig({
|
|
140
|
+
baseUrl: options.baseUrl ?? "http://localhost:3000",
|
|
141
|
+
...projectName !== void 0 ? { projectName } : {},
|
|
142
|
+
locale: options.locale ?? "en-US"
|
|
143
|
+
});
|
|
144
|
+
if (!options.force) {
|
|
145
|
+
try {
|
|
146
|
+
await writeFile(configPath, content, { flag: "wx", encoding: "utf-8" });
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err.code === "EEXIST") {
|
|
149
|
+
printError(
|
|
150
|
+
`Configuration file already exists at ${pc2.underline(
|
|
151
|
+
"rgaa.config.ts"
|
|
152
|
+
)}. Use --force to overwrite.`
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
await writeFile(configPath, content, "utf-8");
|
|
160
|
+
}
|
|
161
|
+
printSuccess(`Configuration file created at ${pc2.underline("rgaa.config.ts")}`);
|
|
162
|
+
console.log("");
|
|
163
|
+
printInfo("Next steps:");
|
|
164
|
+
console.log(` 1. Edit ${pc2.bold("rgaa.config.ts")} to configure your pages and output formats`);
|
|
165
|
+
console.log(` 2. Run ${pc2.bold("npx eqo analyze")} to start your first audit`);
|
|
166
|
+
console.log(
|
|
167
|
+
` 3. Add the report to your ${pc2.bold("/accessibility")} page using the generated JSON`
|
|
168
|
+
);
|
|
169
|
+
console.log("");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/cli/index.ts
|
|
173
|
+
process.on("unhandledRejection", (err) => {
|
|
174
|
+
error("core", `Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
|
177
|
+
var abortController = new AbortController();
|
|
178
|
+
process.on("SIGINT", () => {
|
|
179
|
+
console.log("\n[eqo] Interrupted. Cleaning up...");
|
|
180
|
+
abortController.abort();
|
|
181
|
+
setTimeout(() => process.exit(130), 3e3).unref();
|
|
182
|
+
});
|
|
183
|
+
var VERSION = (true ? "1.0.0" : null) ?? "0.1.0";
|
|
184
|
+
var program = new Command();
|
|
185
|
+
program.name("eqo").description("RGAA v4.1.2 accessibility compliance analyzer for NextJS projects").version(VERSION);
|
|
186
|
+
program.command("analyze").alias("check").description("Run a full RGAA v4.1.2 accessibility audit").option("-c, --config <path>", "Path to the configuration file (default: rgaa.config.ts)").option("--static-only", "Run static source analysis only (skip browser)").option("--runtime-only", "Run browser analysis only (skip static)").option(
|
|
187
|
+
"-t, --threshold <number>",
|
|
188
|
+
"Override minimum compliance rate (0\u2013100). 0 disables CI blocking."
|
|
189
|
+
).option("-l, --locale <locale>", "Override report locale (e.g., en-US, fr-FR)").action(async (options) => {
|
|
190
|
+
const result = await runAnalyze({
|
|
191
|
+
config: options.config,
|
|
192
|
+
staticOnly: options.staticOnly,
|
|
193
|
+
runtimeOnly: options.runtimeOnly,
|
|
194
|
+
threshold: options.threshold,
|
|
195
|
+
locale: options.locale,
|
|
196
|
+
signal: abortController.signal
|
|
197
|
+
});
|
|
198
|
+
process.exit(result.exitCode);
|
|
199
|
+
});
|
|
200
|
+
program.command("init").description("Create a default rgaa.config.ts in the current directory").option("--base-url <url>", "Base URL of your app (default: http://localhost:3000)").option("--project-name <name>", "Project name shown in reports").option("-l, --locale <locale>", "Default report locale (default: en-US)").option("-f, --force", "Overwrite existing configuration file").action(async (options) => {
|
|
201
|
+
await runInit({
|
|
202
|
+
baseUrl: options.baseUrl,
|
|
203
|
+
projectName: options.projectName,
|
|
204
|
+
locale: options.locale,
|
|
205
|
+
force: options.force
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
program.parse(process.argv);
|
|
209
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// src/i18n/en-US.ts
|
|
2
|
+
var enUS = {
|
|
3
|
+
themes: {
|
|
4
|
+
1: "Images",
|
|
5
|
+
2: "Frames",
|
|
6
|
+
3: "Colors",
|
|
7
|
+
4: "Multimedia",
|
|
8
|
+
5: "Tables",
|
|
9
|
+
6: "Links",
|
|
10
|
+
7: "Scripts",
|
|
11
|
+
8: "Mandatory Elements",
|
|
12
|
+
9: "Structure",
|
|
13
|
+
10: "Presentation",
|
|
14
|
+
11: "Forms",
|
|
15
|
+
12: "Navigation",
|
|
16
|
+
13: "Consultation"
|
|
17
|
+
},
|
|
18
|
+
criteria: {
|
|
19
|
+
"1.1": "Does each informative image have a text alternative?",
|
|
20
|
+
"1.2": "Is each decorative image correctly ignored by assistive technologies?",
|
|
21
|
+
"1.3": "For each informative image with a text alternative, is the alternative relevant?",
|
|
22
|
+
"1.4": "For each image used as a CAPTCHA or test, is the text alternative describing its nature and function?",
|
|
23
|
+
"1.5": "For each CAPTCHA image, is there an alternative access method that does not rely on visual identification?",
|
|
24
|
+
"1.6": "Does each informative image have a detailed description if necessary?",
|
|
25
|
+
"1.7": "For each image with a detailed description, is the description relevant?",
|
|
26
|
+
"1.8": "Does each text image carrying information have the same information in styled text (when no replacement mechanism exists)?",
|
|
27
|
+
"1.9": "Is each image caption correctly linked to the corresponding image?",
|
|
28
|
+
"2.1": "Does each frame have a title attribute?",
|
|
29
|
+
"2.2": "For each frame with a title, is the title relevant?",
|
|
30
|
+
"3.1": "Is information not conveyed by color alone?",
|
|
31
|
+
"3.2": "Does the contrast between text color and background meet the minimum required level?",
|
|
32
|
+
"3.3": "Does the contrast of user interface components and graphical elements meet the minimum required level?",
|
|
33
|
+
"4.1": "Does each pre-recorded time-based media have, if necessary, a text transcript or audio description?",
|
|
34
|
+
"4.2": "For each pre-recorded time-based media with a text transcript or audio description, is it relevant?",
|
|
35
|
+
"4.3": "Does each pre-recorded synchronized media have, if necessary, synchronized captions?",
|
|
36
|
+
"4.4": "For each pre-recorded synchronized media with synchronized captions, are the captions relevant?",
|
|
37
|
+
"4.5": "Does each pre-recorded time-based media have, if necessary, an audio description?",
|
|
38
|
+
"4.6": "For each pre-recorded time-based media with an audio description, is it relevant?",
|
|
39
|
+
"4.7": "Is each time-based media clearly identifiable?",
|
|
40
|
+
"4.8": "Does each non-time-based media have, if necessary, an alternative?",
|
|
41
|
+
"4.9": "For each non-time-based media with an alternative, is the alternative relevant?",
|
|
42
|
+
"4.10": "Is each automatically triggered sound controllable by the user?",
|
|
43
|
+
"4.11": "Can each time-based media be controlled by the keyboard and any pointing device?",
|
|
44
|
+
"4.12": "Can each non-time-based media be controlled by the keyboard and any pointing device?",
|
|
45
|
+
"4.13": "Is each time-based and non-time-based media compatible with assistive technologies?",
|
|
46
|
+
"5.1": "Does each complex data table have a summary?",
|
|
47
|
+
"5.2": "For each complex data table with a summary, is the summary relevant?",
|
|
48
|
+
"5.3": "For each layout table, does the linearized content remain comprehensible?",
|
|
49
|
+
"5.4": "For each data table with a title, is the title correctly associated with the table?",
|
|
50
|
+
"5.5": "For each data table with a title, is the title relevant?",
|
|
51
|
+
"5.6": "For each data table, are column and row headers properly identified?",
|
|
52
|
+
"5.7": "For each data table, is the appropriate technique used to associate data with headers?",
|
|
53
|
+
"5.8": "Does each layout table refrain from using structural table elements?",
|
|
54
|
+
"6.1": "Is each link explicit?",
|
|
55
|
+
"6.2": "Does each link have a label?",
|
|
56
|
+
"7.1": "Is each script, if necessary, compatible with assistive technologies?",
|
|
57
|
+
"7.2": "For each script with an alternative, is the alternative relevant?",
|
|
58
|
+
"7.3": "Is each script controllable by the keyboard and any pointing device?",
|
|
59
|
+
"7.4": "For each script that triggers a context change, is the user warned?",
|
|
60
|
+
"7.5": "In each web page, are status messages correctly rendered by assistive technologies?",
|
|
61
|
+
"8.1": "Is each web page defined by a document type?",
|
|
62
|
+
"8.2": "For each web page, is the generated source code valid according to the specified document type?",
|
|
63
|
+
"8.3": "In each web page, is the default language present?",
|
|
64
|
+
"8.4": "For each web page with a default language, is the language code relevant?",
|
|
65
|
+
"8.5": "Does each web page have a page title?",
|
|
66
|
+
"8.6": "For each web page with a title, is the title relevant?",
|
|
67
|
+
"8.7": "In each web page, is each language change indicated in the source code?",
|
|
68
|
+
"8.8": "In each web page, is each language change code valid and relevant?",
|
|
69
|
+
"8.9": "In each web page, tags must not be used solely for presentational purposes.",
|
|
70
|
+
"8.10": "In each web page, are reading direction changes indicated?",
|
|
71
|
+
"9.1": "In each web page, is information structured through the appropriate use of headings?",
|
|
72
|
+
"9.2": "In each web page, is the document structure coherent?",
|
|
73
|
+
"9.3": "In each web page, is each list correctly structured?",
|
|
74
|
+
"9.4": "In each web page, is each quotation correctly indicated?",
|
|
75
|
+
"10.1": "In the website, are style sheets used to control the presentation of information?",
|
|
76
|
+
"10.2": "In each web page, does visible informative content remain present when stylesheets are disabled?",
|
|
77
|
+
"10.3": "In each web page, does information remain comprehensible when stylesheets are disabled?",
|
|
78
|
+
"10.4": "In each web page, does text remain legible when text size is increased by 200%?",
|
|
79
|
+
"10.5": "In each web page, are background and foreground color declarations present together?",
|
|
80
|
+
"10.6": "In each web page, is each link whose nature is not obvious visually distinguishable from surrounding text?",
|
|
81
|
+
"10.7": "In each web page, is focus visibility ensured for each element receiving keyboard focus?",
|
|
82
|
+
"10.8": "For each web page, are hidden contents intended to be ignored by assistive technologies?",
|
|
83
|
+
"10.9": "In each web page, information must not be conveyed solely by shape, size, or position.",
|
|
84
|
+
"10.10": "In each web page, information must not be given by shape or size alone.",
|
|
85
|
+
"10.11": "For each web page, can contents be presented without horizontal scrolling when viewport is 320 CSS pixels wide?",
|
|
86
|
+
"10.12": "In each web page, can text spacing properties be adjusted without loss of content or functionality?",
|
|
87
|
+
"10.13": "In each web page, are additional contents appearing on keyboard focus or pointer hover dismissible, hoverable, and persistent?",
|
|
88
|
+
"10.14": "In each web page, are additional contents triggered via CSS visible to all users?",
|
|
89
|
+
"11.1": "Does each form field have a label?",
|
|
90
|
+
"11.2": "Is each label associated with a form field relevant?",
|
|
91
|
+
"11.3": "In each form, is each label associated with a field visible?",
|
|
92
|
+
"11.4": "In each form, are labels and their associated fields adjacent?",
|
|
93
|
+
"11.5": "In each form, are fields of the same nature grouped together when necessary?",
|
|
94
|
+
"11.6": "In each form, does each group of fields of the same nature have a legend?",
|
|
95
|
+
"11.7": "In each form, is each legend associated with a group of fields relevant?",
|
|
96
|
+
"11.8": "In each form, are options of the same nature in a choice list grouped?",
|
|
97
|
+
"11.9": "In each form, is each button label relevant?",
|
|
98
|
+
"11.10": "In each form, is input validation used appropriately?",
|
|
99
|
+
"11.11": "In each form, is input validation accompanied by suggestions when needed?",
|
|
100
|
+
"11.12": "For each form that modifies or deletes data, or transmits answers to a test, can the user verify, correct, and confirm the data before submission?",
|
|
101
|
+
"11.13": "Can the purpose of a form input field be determined to facilitate autocomplete?",
|
|
102
|
+
"12.1": "Does each set of pages have at least two navigation systems?",
|
|
103
|
+
"12.2": "In each set of pages, are the navigation menu and navigation bars consistent?",
|
|
104
|
+
"12.3": "Is the site map page relevant?",
|
|
105
|
+
"12.4": "In each set of pages, is the site map accessible from any page?",
|
|
106
|
+
"12.5": "In each set of pages, is the search engine accessible from any page?",
|
|
107
|
+
"12.6": "Are grouping areas of content present on multiple web pages identifiable with HTML structural elements?",
|
|
108
|
+
"12.7": "In each web page, is a skip link or quick access link to the main content area present?",
|
|
109
|
+
"12.8": "In each web page, is the tab order coherent?",
|
|
110
|
+
"12.9": "In each web page, does navigation not contain a keyboard trap?",
|
|
111
|
+
"12.10": "In each web page, do keyboard shortcuts using a single character key meet requirements?",
|
|
112
|
+
"12.11": "In each web page, are additional contents appearing on hover, focus, or interaction controllable?",
|
|
113
|
+
"13.1": "For each web page, does the user have control over each time limit?",
|
|
114
|
+
"13.2": "In each web page, must the opening of a new window not be triggered automatically?",
|
|
115
|
+
"13.3": "In each web page, does each downloadable office document have, if necessary, an accessible version?",
|
|
116
|
+
"13.4": "For each downloadable document with an accessible version, is this version up to date?",
|
|
117
|
+
"13.5": "In each web page, does each cryptic content have an alternative?",
|
|
118
|
+
"13.6": "In each web page, is each cryptic content alternative visible?",
|
|
119
|
+
"13.7": "In each web page, are sudden brightness changes or flash effects controlled?",
|
|
120
|
+
"13.8": "In each web page, is each moving or blinking content controllable by the user?",
|
|
121
|
+
"13.9": "In each web page, can the proposed content be consulted regardless of screen orientation?",
|
|
122
|
+
"13.10": "In each web page, are multi-point or path-based gestures operable with a single pointer?",
|
|
123
|
+
"13.11": "In each web page, can actions triggered by a pointing device be cancelled?",
|
|
124
|
+
"13.12": "In each web page, can functionalities using device motion be operated with user interface components?"
|
|
125
|
+
},
|
|
126
|
+
tests: {
|
|
127
|
+
"1.1.1": 'Each informative <img> or role="img" element has a text alternative',
|
|
128
|
+
"1.1.2": "Each informative <area> element has a text alternative",
|
|
129
|
+
"1.1.3": 'Each <input type="image"> element has a text alternative',
|
|
130
|
+
"1.1.5": 'Each informative <svg role="img"> has an accessible name',
|
|
131
|
+
"1.1.6": 'Each informative <object type="image/..."> has a text alternative',
|
|
132
|
+
"1.1.7": 'Each informative <embed type="image/..."> has a text alternative',
|
|
133
|
+
"1.1.8": "Each informative <canvas> has a text alternative",
|
|
134
|
+
"1.2.1": 'Each decorative <img> has alt="" and no other text alternative attribute',
|
|
135
|
+
"1.2.4": 'Each decorative <svg> has aria-hidden="true" and no text alternative',
|
|
136
|
+
"1.9.1": "Each image caption is linked to the image via <figure> and <figcaption>",
|
|
137
|
+
"2.1.1": "Each <iframe> has a title attribute",
|
|
138
|
+
"2.2.1": "Each <iframe> title is non-empty and relevant",
|
|
139
|
+
"5.4.1": "Each data table title is associated via <caption> or aria-labelledby",
|
|
140
|
+
"5.6.1": "Each column header uses <th> or appropriate role",
|
|
141
|
+
"5.6.2": 'Each row header uses <th> with scope="row" or appropriate role',
|
|
142
|
+
"5.8.1": "Layout tables do not use <th>, <caption>, or headers attributes",
|
|
143
|
+
"6.2.1": "Each link has a non-empty accessible name",
|
|
144
|
+
"8.1.1": "Each page has a DOCTYPE declaration",
|
|
145
|
+
"8.2.1": "No duplicate id attributes exist on the same page",
|
|
146
|
+
"8.3.1": "The <html> element has a lang attribute",
|
|
147
|
+
"8.4.1": "The lang attribute value is a valid BCP 47 language code",
|
|
148
|
+
"8.5.1": "Each page has a <title> element",
|
|
149
|
+
"8.6.1": "Each page <title> is non-empty",
|
|
150
|
+
"8.9.1": "Presentational tags (b, i, u, blink, marquee) are not used for pure styling",
|
|
151
|
+
"9.1.1": "Headings are present on the page",
|
|
152
|
+
"9.1.2": "Heading levels are not skipped",
|
|
153
|
+
"9.1.3": "Only one <h1> exists per page",
|
|
154
|
+
"9.3.1": "<ul>, <ol>, and <dl> are correctly structured",
|
|
155
|
+
"11.1.1": "Each <input> has an associated <label> or ARIA label",
|
|
156
|
+
"11.1.2": "Each <textarea> has an associated <label> or ARIA label",
|
|
157
|
+
"11.1.3": "Each <select> has an associated <label> or ARIA label",
|
|
158
|
+
"11.6.1": "Each <fieldset> has a <legend>",
|
|
159
|
+
"11.6.2": 'Each group with role="group" or role="radiogroup" has aria-labelledby or aria-label',
|
|
160
|
+
"11.9.1": "Each <button> has a non-empty accessible name",
|
|
161
|
+
"11.9.2": 'Each <input type="submit"> and <input type="button"> has a non-empty value',
|
|
162
|
+
"11.13.1": "Form inputs with personal data have an appropriate autocomplete attribute",
|
|
163
|
+
"12.6.1": "Main landmark regions use <header>, <nav>, <main>, <footer> or role equivalents",
|
|
164
|
+
"12.7.1": "A skip link to the main content is present and functional",
|
|
165
|
+
"12.9.1": "Keyboard navigation does not create a focus trap"
|
|
166
|
+
},
|
|
167
|
+
issues: {
|
|
168
|
+
"img.missing-alt": "Image is missing a text alternative (alt attribute)",
|
|
169
|
+
"img.missing-alt-on-role-img": 'Element with role="img" is missing an accessible name (aria-label or aria-labelledby)',
|
|
170
|
+
"img.empty-alt-missing": "Informative image has no text alternative",
|
|
171
|
+
"img.input-image-missing-alt": '<input type="image"> is missing a text alternative (alt attribute)',
|
|
172
|
+
"img.svg-missing-accessible-name": '<svg role="img"> is missing an accessible name',
|
|
173
|
+
"img.decorative-has-alt": 'Decorative image should have alt="" (empty) and no other text alternative',
|
|
174
|
+
"img.decorative-svg-not-hidden": 'Decorative <svg> must have aria-hidden="true"',
|
|
175
|
+
"img.figcaption-not-in-figure": "<figcaption> must be a direct child of <figure>",
|
|
176
|
+
"img.figure-missing-img": "<figure> containing a <figcaption> should also contain an image",
|
|
177
|
+
"frame.missing-title": "<iframe> is missing a title attribute",
|
|
178
|
+
"frame.empty-title": "<iframe> has an empty title attribute",
|
|
179
|
+
"table.missing-caption": "Data table is missing a <caption> or accessible name",
|
|
180
|
+
"table.th-missing-scope": "<th> is missing a scope attribute",
|
|
181
|
+
"table.layout-has-th": 'Layout table (role="presentation") contains <th> element',
|
|
182
|
+
"table.layout-has-caption": 'Layout table (role="presentation") contains a <caption> element',
|
|
183
|
+
"link.missing-label": "Link has no accessible name (no text content, aria-label, or aria-labelledby)",
|
|
184
|
+
"link.empty-label": "Link has an empty accessible name",
|
|
185
|
+
"html.missing-lang": "<html> element is missing a lang attribute",
|
|
186
|
+
"html.empty-lang": "<html> element has an empty lang attribute",
|
|
187
|
+
"html.invalid-lang": "Invalid language code on <html> element: {lang}",
|
|
188
|
+
"html.missing-title": "Page is missing a <title> element",
|
|
189
|
+
"html.empty-title": "Page <title> is empty",
|
|
190
|
+
"html.presentational-tag": "<{tag}> used for purely presentational purposes",
|
|
191
|
+
"html.duplicate-id": 'Duplicate id="{id}" found on the page',
|
|
192
|
+
"heading.skipped-level": "Heading level skipped: <h{from}> followed by <h{to}>",
|
|
193
|
+
"heading.multiple-h1": "Multiple <h1> elements found on the same page",
|
|
194
|
+
"heading.no-headings": "No headings found on the page",
|
|
195
|
+
"list.invalid-child": "<{parent}> contains an invalid direct child <{child}> (expected <li>)",
|
|
196
|
+
"list.item-outside-list": "<li> is not a child of <ul> or <ol>",
|
|
197
|
+
"form.missing-label": "<{tag}> is missing an accessible label (no <label>, aria-label, or aria-labelledby)",
|
|
198
|
+
"form.fieldset-missing-legend": "<fieldset> is missing a <legend> element",
|
|
199
|
+
"form.group-missing-label": 'Element with role="{role}" is missing an accessible name',
|
|
200
|
+
"form.button-missing-label": "<button> is missing an accessible name",
|
|
201
|
+
"form.submit-empty-value": '<input type="{type}"> is missing a value attribute',
|
|
202
|
+
"form.missing-autocomplete": 'Form field collecting "{purpose}" data is missing an autocomplete attribute',
|
|
203
|
+
"a11y.color-contrast": "Insufficient color contrast ratio: {ratio} (required: {required})",
|
|
204
|
+
"a11y.focus-not-visible": "Element does not have a visible focus indicator",
|
|
205
|
+
"a11y.keyboard-trap": "Keyboard focus is trapped inside this element",
|
|
206
|
+
"a11y.missing-skip-link": "No skip navigation link found",
|
|
207
|
+
"a11y.missing-landmark": 'Page is missing main landmark region (<main> or role="main")',
|
|
208
|
+
"a11y.status-message-missing-role": 'Status message container is missing aria-live or role="status"'
|
|
209
|
+
},
|
|
210
|
+
remediation: {
|
|
211
|
+
"img.missing-alt": 'Add alt="Description of the image" or alt="" if the image is decorative',
|
|
212
|
+
"img.missing-alt-on-role-img": 'Add aria-label="Description" or aria-labelledby pointing to a visible text element',
|
|
213
|
+
"img.input-image-missing-alt": 'Add alt="Button action description" to describe the button purpose',
|
|
214
|
+
"img.svg-missing-accessible-name": "Add <title> inside the SVG or aria-label on the element",
|
|
215
|
+
"img.decorative-has-alt": 'Set alt="" and remove title, aria-label, and aria-labelledby attributes',
|
|
216
|
+
"img.decorative-svg-not-hidden": 'Add aria-hidden="true" to the <svg> element',
|
|
217
|
+
"frame.missing-title": 'Add a title attribute: <iframe title="Frame description">',
|
|
218
|
+
"frame.empty-title": "Provide a meaningful, non-empty title describing the frame content",
|
|
219
|
+
"table.missing-caption": "Add <caption>Table title</caption> as the first child of <table>",
|
|
220
|
+
"table.th-missing-scope": 'Add scope="col" for column headers or scope="row" for row headers',
|
|
221
|
+
"table.layout-has-th": 'Replace <th> with <td> in layout tables, or remove role="presentation"',
|
|
222
|
+
"link.missing-label": "Add visible text content, an aria-label attribute, or use aria-labelledby",
|
|
223
|
+
"html.missing-lang": '<html lang="en"> (or your page language code)',
|
|
224
|
+
"html.invalid-lang": "Use a valid BCP 47 language code (e.g., en, fr, en-US, fr-FR)",
|
|
225
|
+
"html.missing-title": "Add a <title>Page title \u2014 Site name</title> in the <head>",
|
|
226
|
+
"html.empty-title": "Provide a descriptive page title",
|
|
227
|
+
"html.presentational-tag": "Use CSS for styling instead of presentational HTML elements",
|
|
228
|
+
"html.duplicate-id": "Ensure each id attribute is unique within the page",
|
|
229
|
+
"heading.skipped-level": "Maintain a logical heading hierarchy without skipping levels",
|
|
230
|
+
"heading.multiple-h1": "Use only one <h1> per page to identify the main topic",
|
|
231
|
+
"heading.no-headings": "Add headings to structure your page content",
|
|
232
|
+
"list.invalid-child": "Use only <li> as direct children of <ul> and <ol>",
|
|
233
|
+
"form.missing-label": 'Use <label for="inputId"> or add aria-label / aria-labelledby to the input',
|
|
234
|
+
"form.fieldset-missing-legend": "Add <legend>Group title</legend> as the first child of <fieldset>",
|
|
235
|
+
"form.button-missing-label": "Add text content or an aria-label to the button",
|
|
236
|
+
"form.missing-autocomplete": 'Add autocomplete="{purpose}" to help users fill in personal data',
|
|
237
|
+
"a11y.color-contrast": "Adjust text or background color to achieve a minimum 4.5:1 contrast ratio (3:1 for large text)",
|
|
238
|
+
"a11y.focus-not-visible": "Ensure :focus styles are not removed (outline: none is not allowed without a custom indicator)",
|
|
239
|
+
"a11y.missing-skip-link": 'Add <a href="#main-content" class="sr-only focus:not-sr-only">Skip to content</a>',
|
|
240
|
+
"a11y.missing-landmark": 'Wrap the main content in a <main> element or add role="main"'
|
|
241
|
+
},
|
|
242
|
+
automationLevel: {
|
|
243
|
+
full: "Fully automated",
|
|
244
|
+
partial: "Partially automated",
|
|
245
|
+
manual: "Requires manual review"
|
|
246
|
+
},
|
|
247
|
+
criterionStatus: {
|
|
248
|
+
validated: "Validated",
|
|
249
|
+
invalidated: "Invalidated",
|
|
250
|
+
"not-applicable": "Not applicable",
|
|
251
|
+
"needs-review": "Needs review"
|
|
252
|
+
},
|
|
253
|
+
severity: {
|
|
254
|
+
error: "Error",
|
|
255
|
+
warning: "Warning",
|
|
256
|
+
notice: "Notice"
|
|
257
|
+
},
|
|
258
|
+
report: {
|
|
259
|
+
title: "RGAA v4.1.2 Accessibility Report",
|
|
260
|
+
generated: "Generated",
|
|
261
|
+
project: "Project",
|
|
262
|
+
pages: "Pages analyzed",
|
|
263
|
+
summary: "Summary",
|
|
264
|
+
totalCriteria: "Total criteria",
|
|
265
|
+
applicable: "Applicable",
|
|
266
|
+
validated: "Validated",
|
|
267
|
+
invalidated: "Invalidated",
|
|
268
|
+
notApplicable: "Not applicable",
|
|
269
|
+
needsReview: "Needs review",
|
|
270
|
+
complianceRate: "Compliance rate",
|
|
271
|
+
themes: "Themes",
|
|
272
|
+
issues: "Issues",
|
|
273
|
+
noIssues: "No issues found",
|
|
274
|
+
file: "File",
|
|
275
|
+
line: "Line",
|
|
276
|
+
element: "Element",
|
|
277
|
+
page: "Page",
|
|
278
|
+
criterion: "Criterion",
|
|
279
|
+
test: "Test",
|
|
280
|
+
severity: "Severity",
|
|
281
|
+
remediation: "How to fix",
|
|
282
|
+
automationDisclaimer: "Note: This report covers only automatically verifiable criteria. Criteria marked as 'Needs review' require manual inspection. The compliance rate reflects automated checks only."
|
|
283
|
+
},
|
|
284
|
+
cli: {
|
|
285
|
+
analyzing: "Analyzing RGAA v4.1.2 compliance\u2026",
|
|
286
|
+
staticPhase: "Static analysis (source files)",
|
|
287
|
+
runtimePhase: "Runtime analysis (rendered pages)",
|
|
288
|
+
reportWritten: "Report written to {path}",
|
|
289
|
+
done: "Analysis complete",
|
|
290
|
+
failed: "Analysis failed",
|
|
291
|
+
thresholdExceeded: "Compliance rate {rate}% is below the required threshold of {threshold}%",
|
|
292
|
+
noConfig: "No configuration file found. Run `eqo init` to create one.",
|
|
293
|
+
configCreated: "Configuration file created at {path}"
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var en_US_default = enUS;
|
|
297
|
+
export {
|
|
298
|
+
en_US_default as default
|
|
299
|
+
};
|
|
300
|
+
//# sourceMappingURL=en-US-JQN64XYI.js.map
|