200 lines
6.8 KiB
Plaintext
200 lines
6.8 KiB
Plaintext
import {
|
|
attachTooltipToHighlight,
|
|
createHighlight,
|
|
getElementsPositionInDocument,
|
|
positionHighlight
|
|
} from "../utils/highlight.js";
|
|
import { createWindowElement } from "../utils/window.js";
|
|
import { a11y } from "./a11y.js";
|
|
const icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1 20 16"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
|
|
const rules = [...a11y];
|
|
const dynamicAuditRuleKeys = ["title", "message"];
|
|
function resolveAuditRule(rule, element) {
|
|
let resolved = { ...rule };
|
|
for (const key of dynamicAuditRuleKeys) {
|
|
const value = rule[key];
|
|
if (typeof value === "string")
|
|
continue;
|
|
resolved[key] = value(element);
|
|
}
|
|
return resolved;
|
|
}
|
|
var audit_default = {
|
|
id: "astro:audit",
|
|
name: "Audit",
|
|
icon,
|
|
async init(canvas, eventTarget) {
|
|
let audits = [];
|
|
await lint();
|
|
document.addEventListener("astro:after-swap", async () => lint());
|
|
document.addEventListener("astro:page-load", async () => refreshLintPositions);
|
|
function onPageClick(event) {
|
|
const target = event.target;
|
|
if (!target)
|
|
return;
|
|
if (!target.closest)
|
|
return;
|
|
if (target.closest("astro-dev-toolbar"))
|
|
return;
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent("toggle-app", {
|
|
detail: {
|
|
state: false
|
|
}
|
|
})
|
|
);
|
|
}
|
|
eventTarget.addEventListener("app-toggled", (event) => {
|
|
if (event.detail.state === true) {
|
|
document.addEventListener("click", onPageClick, true);
|
|
} else {
|
|
document.removeEventListener("click", onPageClick, true);
|
|
}
|
|
});
|
|
async function lint() {
|
|
audits.forEach(({ highlightElement }) => {
|
|
highlightElement.remove();
|
|
});
|
|
audits = [];
|
|
canvas.getElementById("no-audit")?.remove();
|
|
const selectorCache = /* @__PURE__ */ new Map();
|
|
for (const rule of rules) {
|
|
const elements = selectorCache.get(rule.selector) ?? document.querySelectorAll(rule.selector);
|
|
let matches = [];
|
|
if (typeof rule.match === "undefined") {
|
|
matches = Array.from(elements);
|
|
} else {
|
|
for (const element of elements) {
|
|
if (rule.match(element)) {
|
|
matches.push(element);
|
|
}
|
|
}
|
|
}
|
|
for (const element of matches) {
|
|
await createAuditProblem(rule, element);
|
|
}
|
|
}
|
|
if (audits.length > 0) {
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent("toggle-notification", {
|
|
detail: {
|
|
state: true
|
|
}
|
|
})
|
|
);
|
|
} else {
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent("toggle-notification", {
|
|
detail: {
|
|
state: false
|
|
}
|
|
})
|
|
);
|
|
const window2 = createWindowElement(
|
|
`<style>
|
|
header {
|
|
display: flex;
|
|
}
|
|
|
|
h1 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
margin: 0;
|
|
font-size: 22px;
|
|
}
|
|
|
|
astro-dev-toolbar-icon {
|
|
width: 1em;
|
|
height: 1em;
|
|
padding: 8px;
|
|
display: block;
|
|
background: green;
|
|
border-radius: 9999px;
|
|
}
|
|
</style>
|
|
<header>
|
|
<h1><astro-dev-toolbar-icon icon="check-circle"></astro-dev-toolbar-icon>No accessibility issues detected.</h1>
|
|
</header>
|
|
<p>
|
|
Nice work! This app scans the page and highlights common accessibility issues for you, like a missing "alt" attribute on an image.
|
|
</p>
|
|
`
|
|
);
|
|
canvas.append(window2);
|
|
}
|
|
["scroll", "resize"].forEach((event) => {
|
|
window.addEventListener(event, refreshLintPositions);
|
|
});
|
|
}
|
|
function refreshLintPositions() {
|
|
const noAuditBlock = canvas.getElementById("no-audit");
|
|
if (noAuditBlock) {
|
|
const devOverlayRect = document.querySelector("astro-dev-toolbar")?.shadowRoot.querySelector("#dev-toolbar-root")?.getBoundingClientRect();
|
|
noAuditBlock.style.top = `${(devOverlayRect?.top ?? 0) - (devOverlayRect?.height ?? 0) - 16}px`;
|
|
}
|
|
audits.forEach(({ highlightElement, auditedElement }) => {
|
|
const rect = auditedElement.getBoundingClientRect();
|
|
positionHighlight(highlightElement, rect);
|
|
});
|
|
}
|
|
async function createAuditProblem(rule, originalElement) {
|
|
const computedStyle = window.getComputedStyle(originalElement);
|
|
const targetedElement = originalElement.children[0] || originalElement;
|
|
if (targetedElement.offsetParent === null || computedStyle.display === "none") {
|
|
return;
|
|
}
|
|
if (originalElement.nodeName === "IMG" && !originalElement.complete) {
|
|
return;
|
|
}
|
|
const rect = originalElement.getBoundingClientRect();
|
|
const highlight = createHighlight(rect, "warning");
|
|
const tooltip = buildAuditTooltip(rule, originalElement);
|
|
const { isFixed } = getElementsPositionInDocument(originalElement);
|
|
if (isFixed) {
|
|
tooltip.style.position = highlight.style.position = "fixed";
|
|
}
|
|
attachTooltipToHighlight(highlight, tooltip, originalElement);
|
|
canvas.append(highlight);
|
|
audits.push({ highlightElement: highlight, auditedElement: originalElement });
|
|
}
|
|
function buildAuditTooltip(rule, element) {
|
|
const tooltip = document.createElement("astro-dev-toolbar-tooltip");
|
|
const { title, message } = resolveAuditRule(rule, element);
|
|
tooltip.sections = [
|
|
{
|
|
icon: "warning",
|
|
title: escapeHtml(title)
|
|
},
|
|
{
|
|
content: escapeHtml(message)
|
|
}
|
|
];
|
|
const elementFile = element.getAttribute("data-astro-source-file");
|
|
const elementPosition = element.getAttribute("data-astro-source-loc");
|
|
if (elementFile) {
|
|
const elementFileWithPosition = elementFile + (elementPosition ? ":" + elementPosition : "");
|
|
tooltip.sections.push({
|
|
content: elementFileWithPosition.slice(
|
|
window.__astro_dev_toolbar__.root.length - 1
|
|
// We want to keep the final slash, so minus one.
|
|
),
|
|
clickDescription: "Click to go to file",
|
|
async clickAction() {
|
|
await fetch("/__open-in-editor?file=" + encodeURIComponent(elementFileWithPosition));
|
|
}
|
|
});
|
|
}
|
|
return tooltip;
|
|
}
|
|
function escapeHtml(unsafe) {
|
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
}
|
|
}
|
|
};
|
|
export {
|
|
audit_default as default
|
|
};
|