Installation
Install the package:
npm install copy-component
Import the module to be handled by your bundler (or not). If you'd prefer to just embed the library in your code you can import it directly from a CDN, shown below.
import "copy-component";
<copy-component>
<p>
Hello<br />
world
</p>
<button slot="button">Copy</button>
</copy-component>
From CDN:
<script type="module" src="https://cdn.skypack.dev/copy-component?min"></script>
Here is an un-styled Codepen.
Basic usage
Any HTML inside of the copy-component
tag will be made copy-able. You will likely want to add in a button to trigger the copy, to do this you must set the attribute slot="button"
on your button (or a parent of the button). Formatting is preserved on copy.
Of course, all of the "Copy buttons" on this website use copy-component
😊.
<copy-component>
<p>
Hello<br />
world
</p>
<button slot="button">Copy</button>
</copy-component>
copy-component {
position: relative;
border: 2px lightgrey dashed;
}
copy-component > *:not([slot="button"]) {
margin: 1rem;
}
copy-component button {
position: absolute;
text-transform: uppercase;
background: rgba(0, 0, 0, 0.3);
color: white;
top: 0;
right: 0;
border: none;
}
copy-component button:active {
background: rgba(0, 0, 0, 0.8);
}
Limitations
In order to preserve the formatting of copied content you must wrap your content you want to copy in single parent dom node i.e.
<copy-component>
<div>
<p>Stuff to copy</p>
<br />
<p>Copy me too!</p>
</div>
<button slot="button">Copy me!</button>
</copy-component>
NOT
<copy-component>
<p>Stuff to copy</p>
<br />
<p>Copy me too!</p>
<button slot="button">Copy me!</button>
</copy-component>
This is due to how shadow dom works, it is not possible to get the formatted inner text of a single slot without also getting the text of the other slots (e.g. the button slot).
Events
Custom events are fired the following:
copy
- when copy succeeds
copy-failed
- when copy fails
<copy-component>
<p>
Hello<br />
world
</p>
<button id="copy-button" slot="button">Copy</button>
</copy-component>
document.body.addEventListener("copy", () => {
document.getElementById("copy-button").innerText = "Copied!";
});
copy-component {
position: relative;
display: flex;
border: 2px lightgrey dashed;
}
copy-component > *:not([slot="button"]) {
margin: 1rem;
}
copy-component button {
position: absolute;
text-transform: uppercase;
background: rgba(0, 0, 0, 0.5);
color: white;
top: 0;
right: 0;
border: none;
}
copy-component button:active {
background: rgba(0, 0, 0, 0.8);
}
Check out the codepen.
Markdown
Because Web Components are html you can wrap any markdown text/elements you like in a copy-component
e.g.
<copy-component
oncopy="this.querySelector('[slot=button]').textContent='Copied'"
style="display: block;"
>
```js console.log("hello world!"); ```
<button style="text-transform: uppercase" slot="button">Copy here</button>
</copy-component>
11ty
To automatically add copyable text snippets to your 11ty site from your markdown checkout this 11ty plugin.
```html
<p>Your blog post code</p>
```
plus `.eleventy.config.js`:
```js
eleventyConfig.addPlugin("eleventy-plugin-markdown-copy-button");
```
equals auto-copyable code:
```html
<p>Your blog post code</p>
```
If you're interested in this check out the 11ty plugin: https://github.com/Georgegriff/eleventy-plugin-markdown-copy-button
## Web Component library
Here's an example using the component within lit
```js lit-copy
import { LitElement, html, css } from "https://cdn.skypack.dev/lit";
// un-comment this, commented out because this website uses the component and can only import a web component once
//import "https://cdn.skypack.dev/copy-component";
class CopyToClipboard extends LitElement {
constructor() {
super();
this._copyText = "Copy text";
this._copiedText = "Copied!";
this._copyFailed = "Failed! 😞";
this.copyText = this._copyText;
}
static get styles() {
return css`
:host {
display: flex;
flex-direction: column;
margin: 0.5rem;
padding: 0.5rem;
position: relative;
border: 2px dashed #82212c;
max-height: 450px;
overflow: auto;
}
:host([hidden]) {
display: none;
}
button {
font-family: inherit;
text-transform: uppercase;
background: #82212c;
color: #1a1a1a;
font-size: 1rem;
font-weight: 600;
padding: 0.25rem 1.5rem;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0;
border: none;
cursor: pointer;
z-index: 1;
height: 2.5rem;
margin-left: auto;
}
button:active {
background: #5a363a;
}
.floating-btn {
position: absolute;
top: 0;
right: 0;
}
::slotted(*) {
margin: 0 !important;
height: 100%;
padding-bottom: 2.5rem;
}
`;
}
_onCopy() {
this.copyText = this._copiedText;
this.requestUpdate();
}
_onCopyFailed() {
this.copyText = this._copyFailed;
this.requestUpdate();
}
render() {
return html`
<copy-component
@copy=${this._onCopy.bind(this)}
@copy-failed=${this._onCopyFailed.bind(this)}
>
<slot></slot>
<button
slot="button"
class="floating-btn"
aria-label="Copy to clipboard"
>
${this.copyText}
</button>
</copy-component>
`;
}
}
customElements.define("copy-it", CopyToClipboard);
```
```html lit-copy
<copy-it>
<div>
<p>Some text to copy</p>
<br />
<p>Formatting is preserved!</p>
</div>
</copy-it>
```
Check out the [codepen](https://codepen.io/georgegriff/pen/jOGYvaY).
Thanks for reading the docs!
<div class="author-data">
<div class="sponsor">
<span class="sponsor-title">Did my content help you?</span>
<div>Share on social media, <a rel="noopener" href="https://twitter.com/intent/follow?screen_name=griffadev">follow me on twitter,</a> or
</div>
<div>
<a class="kofi" rel="noopener" href='https://ko-fi.com/G2G221OBA' target='_blank'><img height='36' style='border:0px;height:36px;' src='/images/kofi.png' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
</div>
</div>
<figure class="author-info">
<a rel="noopener" target="_blank" href="https://twitter.com/intent/follow?screen_name=griffadev"><img class="author-img" src="/images/griff.jpg" alt="Image of author: George Griffiths".></a>
<figcaption>
<span class="author-name">George Griffiths</span>
<span class="author-twitter"><a target="_blank" rel="noopener" href="https://twitter.com/intent/follow?screen_name=griffadev">@griffadev</a></span>
</figcaption>
</figure>
</div>
<div class="something-wrong">
<span>Something not quite right on this page? <a target="_blank" rel="noopener" href="https://github.com/Georgegriff/griffadev/issues/new?title=Content+correction:+.%2Fsrc%2Fdemos%2Fcopy-component%2Findex.md&body=Hello%2C%20I've%20noticed%20an%20issue%20in%3A%0A%20%20%20%20https%3A%2F%2Fgithub.com%2FGeorgegriff%2Fgriffadev%2Fblob%2Fmain%2Fsrc%2Fdemos%2Fcopy-component%2Findex.md%20%0A%0A**Describe%20the%20problem**%0A%0A%20%20A%20clear%20and%20concise%20description%20of%20what%20the%20problem%20is%0A%0A%20%20**Existing%20content**%0A%0A%20%20What%20is%20there%20at%20the%20moment%3F%0A%0A%20%20**Expected%20content**%0A%0A%20%20What%20would%20you%20expect%20instead%3F%20A%20PR%20is%20more%20than%20welcome%20%3Asmiley%3A%0A%0A%20%20**Screenshots**%0A%0A%20%20If%20applicable%2C%20add%20screenshots%20to%20help%20explain%20your%20problem%0A%0A%20%20**Additional%20context**%0A%0A%20%20Add%20any%20other%20context%20about%20the%20problem%20here.%0A%20%20%20%20">please let me know.</a></span>
</div>