// Shiki's HTML (svelte.config.js's highlight.highlighter hook) is plain,
// static markup with no interactivity of its own — the copy button and its
// click handling only exist once this runs client-side. Called from
// docs/+layout.svelte's afterNavigate, since the layout persists across
// docs navigations and wouldn't otherwise see a later page's code blocks.
const COPY_ICON = ``;
export function wireCodeCopyButtons() {
for (const pre of document.querySelectorAll("pre.shiki")) {
// afterNavigate can fire for a same-page hash-only navigation too, where
// this pre is the exact same DOM node as last time — skip it rather
// than stack a second button on top of the first.
if (pre.querySelector(":scope > .copy-button")) continue;
const code = pre.querySelector("code");
if (!code) continue;
const button = document.createElement("button");
button.type = "button";
button.className = "copy-button";
button.setAttribute("aria-label", "Copy code");
button.innerHTML = COPY_ICON;
pre.appendChild(button);
// One listener on the block itself, not the button — a click on the
// button bubbles up to it too, so "click anywhere on the code block"
// and "click the button" both take this one path.
pre.addEventListener("click", () => {
// A click that ends a manual text-selection drag (e.g. selecting one
// line to copy by hand) still fires a click event on mouseup — skip
// the whole-block copy then, rather than clobber a selection the
// reader just deliberately made with the button's own click handler.
const selection = window.getSelection();
if (selection && !selection.isCollapsed && pre.contains(selection.anchorNode)) {
return;
}
// Both `?.`s matter: navigator.clipboard is absent entirely on an
// insecure origin, and writeText itself can still reject (a denied
// permission) even when it exists. Either way, there's nothing more
// useful to do client-side than leave the text selectable as-is —
// the click feedback below runs regardless.
navigator.clipboard?.writeText(code.textContent ?? "")?.catch(() => {});
button.classList.add("copied");
setTimeout(() => button.classList.remove("copied"), 500);
});
}
}