import { attachTooltipToHighlight, createHighlight, getElementsPositionInDocument, positionHighlight } from "../utils/highlight.js"; import { createWindowElement } from "../utils/window.js"; import { a11y } from "./a11y.js"; import { finder } from "@medv/finder"; import { perf } from "./perf.js"; const icon = ''; const rules = [...a11y, ...perf]; 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 (await rule.match(element)) { matches.push(element); } } } for (const element of matches) { if (audits.some((audit) => audit.auditedElement === element)) continue; await createAuditProblem(rule, element); } } if (audits.length > 0) { eventTarget.dispatchEvent( new CustomEvent("toggle-notification", { detail: { state: true } }) ); const auditListWindow = createWindowElement( `

Audits

${audits.length} problem${audits.length > 1 ? "s" : ""} found

` ); const auditListUl = document.createElement("ul"); auditListUl.id = "audit-list"; audits.forEach((audit, index) => { const resolvedRule = resolveAuditRule(audit.rule, audit.auditedElement); const card = document.createElement("astro-dev-toolbar-card"); card.shadowRoot.innerHTML = ` `; card.clickAction = () => { audit.highlightElement.scrollIntoView(); audit.highlightElement.focus(); }; const h3 = document.createElement("h3"); h3.innerText = finder(audit.auditedElement); card.appendChild(h3); const div = document.createElement("div"); const title = document.createElement("span"); title.classList.add("audit-title"); title.innerHTML = resolvedRule.title; div.appendChild(title); card.appendChild(div); auditListUl.appendChild(card); }); auditListWindow.appendChild(auditListUl); canvas.append(auditListWindow); } else { eventTarget.dispatchEvent( new CustomEvent("toggle-notification", { detail: { state: false } }) ); const window2 = createWindowElement( `

No accessibility or performance issues detected.

Nice work! This app scans the page and highlights common accessibility and performance issues for you, like a missing "alt" attribute on an image, or a image not using performant attributes.

` ); 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", { "data-audit-code": rule.code }); 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, rule }); } 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, "'"); } } }; export { audit_default as default };