import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source"]
async copy() {
const text = this.sourceTarget.value || this.sourceTarget.textContent
try {
await navigator.clipboard.writeText(text)
} catch (_) {
const range = document.createRange()
range.selectNodeContents(this.sourceTarget)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
document.execCommand("copy")
sel.removeAllRanges()
}
this.element.dispatchEvent(new CustomEvent("clipboard:copied", { bubbles: true }))
}
}
<div data-controller="clipboard" data-action="clipboard:copied->toast#show">
<pre data-clipboard-target="source"><%= @snip.code_blocks.first.code %></pre>
<button type="button" data-action="clipboard#copy">Copy</button>
</div>
Copy buttons are deceptively tricky across browsers. This Stimulus controller uses the Clipboard API when available, falls back to execCommand, and provides a hook for “Copied!” UI.