@svelte-put/copy
GithubCompatible with or powered directly by Svelte runes.
Installation
npm install --save-dev @svelte-put/copy
pnpm add -D @svelte-put/copy
yarn add -D @svelte-put/copy
New to Svelte 5? See Migration Guides.
Minimal Usage
By default, copy
will trigger a click
1 event listener on the same node 2 it is used on. The triggered listener will then copy innerText of said node 3 to the default clipboard. The next sections show how 1, 2, 3 can be customized.
Below is a minimal demo of copy
with default options. Try clicking on the green button to observe the text within being copied to the blue box.
<script lang="ts">
import { copy, type CopyDetail } from '@svelte-put/copy';
import { fade } from 'svelte/transition';
let copied = $state('');
function handleCopied(e: CustomEvent<CopyDetail>) {
copied = e.detail.text;
}
</script>
<div class="not-prose grid grid-cols-[1fr_auto_1fr] items-center gap-2">
<button class="c-btn" type="button" use:copy oncopied={handleCopied}>
<strong>Click</strong> <span>to copy this</span>
</button>
<p>-></p>
<div class="hl-success grid place-items-center self-stretch">
{#if copied}
<p in:fade={{ duration: 200 }}>
{copied}
</p>
{/if}
</div>
</div>
1 Customizing the Event Types
Pass one or more event types to the event
option.
<script>
import { copy } from '@svelte-put/copy';
</script>
<button type="button" use:copy={{ event: 'mousedown' }}>...</button>
<button type="button" use:copy={{ event: ['pointerenter', 'pointerleave'] }}>...</button>
2 Customizing the Trigger
The trigger
option, which typically takes an HTMLElement
, specifies the node to which the event listener is attached.
The copy button seen in code blocks on this site is powered by this very action.
A typical use case is clicking on a node to copy text within some other node. In the demo below, try clicking on the green button to copy text in the yellow-bordered box.
<script lang="ts">
import { copy, type CopyDetail } from '@svelte-put/copy';
import { fade } from 'svelte/transition';
let trigger: HTMLButtonElement | undefined = $state(undefined);
let copied = $state('');
function handleCopied(e: CustomEvent<CopyDetail>) {
copied = e.detail.text;
}
</script>
<div class="not-prose grid grid-cols-[0.5fr_auto_0.5fr_auto_1fr] items-center gap-4">
<button class="c-btn" type="button" bind:this={trigger}>Click</button>
<p>to</p>
<div
class="grid place-items-center border border-yellow-500 p-2"
use:copy={{ trigger }}
oncopied={handleCopied}
>
<p>copy this</p>
</div>
<p>-></p>
<div class="hl-success grid place-items-center self-stretch">
{#if copied}
<p in:fade={{ duration: 200 }}>
{copied}
</p>
{/if}
</div>
</div>
3 Customizing How Text is Copied
The text
option can receive a literal string or a sync/async callback that returns a string, which if provided, will be used for copying instead of the default innerText
attribute of the node the action is placed on.
Literal
<button use:copy={{ text: 'some static text' }}>Copy me</button>
Callback
Here, text
is a callback; its parameter contains information about the forwarded event, reference to node
(on which the action is placed), and the trigger
(to which event is attached).
<script lang="ts">
import { copy, type TextResolverInput } from '@svelte-put/copy';
import { fade } from 'svelte/transition';
let copied = $state('');
function copyText(input: TextResolverInput<'pointerdown'>) {
const { node } = input;
copied = `Custom - ${node.innerText}`;
return copied;
}
</script>
<div class="not-prose grid grid-cols-[1fr_auto_1fr] items-center gap-2">
<button class="c-btn" type="button" use:copy={{ event: 'pointerdown', text: copyText }}>
Click
</button>
<p>-></p>
<div class="hl-success grid place-items-center self-stretch">
{#if copied}
<p in:fade={{ duration: 200 }}>
{copied}
</p>
{/if}
</div>
</div>
copy
Event
Simulating the If the synthetic
option is set to true
, a "fake" copy event will be dispatched alongside the copied
CustomEvent, should that be of any use.
Note that since this synthetic copy
event is not "real", operations on clipboardData will have no effect on the actual copied text.
<script lang="ts">
import { copy } from '@svelte-put/copy';
import { fade } from 'svelte/transition';
let copied = $state('');
function onSyntheticCopy(e: ClipboardEvent) {
const clipboardData = e.clipboardData;
copied = clipboardData?.getData('text/plain') ?? '';
// clipboardData.setData will have no effect here
}
</script>
<div class="not-prose grid grid-cols-[1fr_auto_1fr] items-center gap-2">
<button class="c-btn" type="button" use:copy={{ synthetic: true }} oncopy={onSyntheticCopy}>
<strong>Click</strong> <span>synthetic copy</span>
</button>
<p>-></p>
<div class="hl-success grid place-items-center self-stretch">
{#if copied}
<p in:fade={{ duration: 200 }}>
{copied}
</p>
{/if}
</div>
</div>
copyToClipboard
Helper
The Behind the scene, copy
uses a copyToClipboard
helper (See its source code here). You may use this utility directly to build your own custom solution that fits your exact need.
import { copyToClipboard } from '@svelte-put/copy';
export function customCopy(text: string) {
copyToClipboard(text);
}
Migration Guides
V3 -> V4 (Svelte 5 in Runes mode)
When migrating to v4, you just need to update the event directive on:copy
to standard attribute onclickcopy
(remove :
).
<div use:copy on:copied>...</div>
<div use:copy oncopied>...</div>
Happy copying! 👨💻