release: WRNexusJS 0.2.75
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import type { DevToolbarRule } from "./types.ts";
|
||||
import { accessibleName, createIssue } from "./helpers.ts";
|
||||
|
||||
export const accessibilityRules: DevToolbarRule[] = [
|
||||
{
|
||||
id: "a11y/image-alt",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "error",
|
||||
description: "Images need alternative text.",
|
||||
run: ({ root }) =>
|
||||
[...root.querySelectorAll("img:not([alt])")].map((element) =>
|
||||
createIssue({
|
||||
ruleId: "a11y/image-alt",
|
||||
category: "accessibility",
|
||||
severity: "error",
|
||||
title: "Image is missing alt text",
|
||||
message: 'Add alt text for meaningful images, or alt="" for decorative images.',
|
||||
element,
|
||||
recommendation: "Describe the image's purpose in a concise alt attribute.",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "a11y/control-name",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "error",
|
||||
description: "Interactive controls need accessible names.",
|
||||
run: ({ root }) =>
|
||||
[...root.querySelectorAll("button, a[href], [role='button']")]
|
||||
.filter((element) => !accessibleName(element))
|
||||
.map((element) =>
|
||||
createIssue({
|
||||
ruleId: "a11y/control-name",
|
||||
category: "accessibility",
|
||||
severity: "error",
|
||||
title: "Control has no accessible name",
|
||||
message: "Screen readers cannot identify this control.",
|
||||
element,
|
||||
recommendation: "Add visible text, aria-label, or aria-labelledby.",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "a11y/form-label",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "error",
|
||||
description: "Form controls need labels.",
|
||||
run: ({ root }) =>
|
||||
[...root.querySelectorAll("input:not([type='hidden']), select, textarea")]
|
||||
.filter((element) => {
|
||||
const id = element.id;
|
||||
return (
|
||||
!element.getAttribute("aria-label") &&
|
||||
!element.getAttribute("aria-labelledby") &&
|
||||
!(id && root.querySelector(`label[for='${CSS.escape(id)}']`)) &&
|
||||
!element.closest("label")
|
||||
);
|
||||
})
|
||||
.map((element) =>
|
||||
createIssue({
|
||||
ruleId: "a11y/form-label",
|
||||
category: "accessibility",
|
||||
severity: "error",
|
||||
title: "Form control has no label",
|
||||
message: "Users of assistive technology may not know what this field is for.",
|
||||
element,
|
||||
recommendation: "Associate a label using for/id or aria-labelledby.",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "a11y/duplicate-id",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "error",
|
||||
description: "IDs must be unique.",
|
||||
run: ({ root }) => {
|
||||
const seen = new Set<string>();
|
||||
const duplicates: Element[] = [];
|
||||
for (const element of root.querySelectorAll("[id]")) {
|
||||
if (seen.has(element.id)) duplicates.push(element);
|
||||
else seen.add(element.id);
|
||||
}
|
||||
return duplicates.map((element) =>
|
||||
createIssue({
|
||||
ruleId: "a11y/duplicate-id",
|
||||
category: "accessibility",
|
||||
severity: "error",
|
||||
title: "Duplicate element ID",
|
||||
message: `The ID “${element.id}” is used more than once.`,
|
||||
element,
|
||||
recommendation: "Use a unique ID for every element.",
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "a11y/heading-order",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "warning",
|
||||
description: "Heading levels should not skip.",
|
||||
run: ({ root }) => {
|
||||
const issues = [];
|
||||
let previous = 0;
|
||||
for (const element of root.querySelectorAll("h1,h2,h3,h4,h5,h6")) {
|
||||
const level = Number(element.tagName.slice(1));
|
||||
if (previous && level > previous + 1)
|
||||
issues.push(
|
||||
createIssue({
|
||||
ruleId: "a11y/heading-order",
|
||||
category: "accessibility",
|
||||
severity: "warning",
|
||||
title: "Heading level is skipped",
|
||||
message: `Heading jumps from h${previous} to h${level}.`,
|
||||
element,
|
||||
recommendation: "Use headings in a logical hierarchy.",
|
||||
}),
|
||||
);
|
||||
previous = level;
|
||||
}
|
||||
return issues;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "a11y/positive-tabindex",
|
||||
category: "accessibility",
|
||||
defaultSeverity: "warning",
|
||||
description: "Positive tabindex disrupts natural keyboard order.",
|
||||
run: ({ root }) =>
|
||||
[...root.querySelectorAll("[tabindex]")]
|
||||
.filter((el) => Number(el.getAttribute("tabindex")) > 0)
|
||||
.map((element) =>
|
||||
createIssue({
|
||||
ruleId: "a11y/positive-tabindex",
|
||||
category: "accessibility",
|
||||
severity: "warning",
|
||||
title: "Positive tabindex used",
|
||||
message: "Positive tabindex values create an unexpected keyboard focus order.",
|
||||
element,
|
||||
recommendation: 'Use tabindex="0" or rely on native document order.',
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user