const codeBlocks = document.querySelectorAll("pre:has(code)"); codeBlocks.forEach((block) => { // Only add button if browser supports Clipboard API if (!navigator.clipboard) return; const button = createCopyButton(); block.appendChild(button); block.setAttribute("tabindex", 0); // Add keyboard a11y for
button.addEventListener("click", async () => { await copyCode(block, button); }); }); function createCopyButton() { const button = document.createElement("button"); setButtonAttributes(button, { type: "button", // button doesn't act as a submit button title: "copy code", "aria-label": "click to copy code", }); return button; } function setButtonAttributes(button, attributes) { for (const [key, value] of Object.entries(attributes)) { button.setAttribute(key, value); } } async function copyCode(block, button) { const code = block.querySelector("code"); let text = code.innerText; // remove "$" and space if exists at the beginning of the code text = text.replace(/^\$\s?/,""); try { await navigator.clipboard.writeText(text); updateButtonState(button, "copied", "code is copied!"); } catch { updateButtonState(button, "failed", "failed!"); } } function updateButtonState(button, statusClass, ariaLabel) { button.setAttribute("aria-live", "polite"); button.setAttribute("aria-label", ariaLabel); button.classList.add(statusClass); // Clear any existing timer if (button.dataset.timerId) clearTimeout(Number(button.dataset.timerId)); const timer = setTimeout(() => { button.classList.remove(statusClass); button.setAttribute("aria-label", "click to copy code"); }, 1000); button.dataset.timerId = timer; }