Copy and paste text, HTML, images, and URLs, and hear when the clipboard changes
One.Clipboard reads and writes the system clipboard: plain text, HTML,
images, and on iOS URLs. It is UIPasteboard on iOS, ClipboardManager on
Android, and the async Clipboard API in the browser.
Every method returns a promise, and the same calls work on all three platforms. What a platform cannot do resolves an empty value instead of throwing, so shared code needs no platform checks.
import { Pressable, Text } from 'react-native'import { One } from 'one'
function CopyLink({ url }: { url: string }) { return ( <Pressable onPress={() => One.Clipboard.setString(url)}> <Text>Copy link</Text> </Pressable> )}setString resolves true once the text is on the clipboard, or false on
web when the browser refuses.
Native calls. Each method crosses to native code through a Nitro module. iOS reads and writes the general pasteboard on the main thread. Android uses the primary clip; reading it as text also turns a copied link or rich text into its text.
Text and HTML. setString(text, { inputFormat: 'html' }) writes one item
with an HTML version and a plain version, so apps that paste rich text get
the formatting and the rest get the text. getString({ preferredFormat: 'html' }) returns the HTML when there is some, and the plain text otherwise.
Images. setImage takes base64 image data and decodes it before writing,
so bad data rejects. Android writes the image to a PNG file in the app’s cache
and puts a content URI on the clipboard, which other apps read through One’s
file provider. getImage encodes whatever image is on the clipboard as PNG
or JPEG and returns it as a data URI with its pixel size, ready for an image
source.
URLs. iOS keeps URLs as their own type, so setUrl, getUrl, and
hasUrl work there. Android and web have no separate URL type: the three
calls resolve nothing, null, and false.
Change events. The first addListener starts one native observer:
UIPasteboard.changedNotification on iOS and a primary clip listener on
Android. Each change calls every subscriber with the content types now on
the clipboard. Removing the last subscription stops the observer.
| Method | Resolves |
|---|---|
| getString(options?) | The text, or an empty string |
| setString(text, options?) | Whether it was written |
| hasString() | Whether there is text |
| getImage(options) | The image, or null |
| setImage(base64) | Once written |
| hasImage() | Whether there is an image |
| getUrl() | The URL, or null |
| setUrl(url) | Once written |
| hasUrl() | Whether there is a URL |
| addListener(listener) | A subscription, at once |
| ClipboardGetImageOptions | Type | Description |
|---|---|---|
format (required)"png" | "jpeg"The encoding of the returned data. | "png" | "jpeg" | The encoding of the returned data. |
jpegQualitynumber = 1From 0 to 1. JPEG only. | number = 1 | From 0 to 1. JPEG only. |
| ClipboardImage | Type | Description |
|---|---|---|
datastringA data URI, with its data:image prefix. | string | A data URI, with its data:image prefix. |
size{ width: number; height: number }The size in pixels. | { width: number; height: number } | The size in pixels. |
| ClipboardEvent | Type | Description |
|---|---|---|
contentTypesClipboardContentType[]Any of plain-text, html, image, and url. | ClipboardContentType[] | Any of plain-text, html, image, and url. |
getString takes { preferredFormat } and setString takes
{ inputFormat }, each 'plainText' by default or 'html'.
| iOS | Android | Web | |
|---|---|---|---|
| Text and HTML | Yes | Yes | Yes |
| Images | Yes | As a cached PNG file | Where allowed |
| URLs | Yes | Empty | Empty |
| Change events | Yes | Yes | Never fire |
hasString, hasImage,
and hasUrl check without reading, so use them to decide whether to offer
a paste button.getImage returns a fresh PNG or JPEG of the
pixels, not the original file, so metadata is gone.false, or null rather than throwing.import { useEffect, useState } from 'react'import { Button } from 'react-native'import { One } from 'one'
function PasteButton({ onPaste }: { onPaste: (text: string) => void }) { const [hasText, setHasText] = useState(false)
useEffect(() => { One.Clipboard.hasString().then(setHasText) const subscription = One.Clipboard.addListener(({ contentTypes }) => { setHasText(contentTypes.includes('plain-text')) }) return () => subscription.remove() }, [])
return ( <Button title="Paste" disabled={!hasText} onPress={async () => onPaste(await One.Clipboard.getString())} /> )}The button follows the clipboard without reading it, so iOS shows its paste notice only when someone taps Paste.
import { One } from 'one'
async function copyQuote(author: string, quote: string) { await One.Clipboard.setString(`<blockquote>${quote}</blockquote><p>${author}</p>`, { inputFormat: 'html' })}Mail and notes apps paste the block quote with its formatting. Apps that take only plain text get the same string as text.
import { useState } from 'react'import { Button, Image, View } from 'react-native'import { One } from 'one'
function PasteImage() { const [uri, setUri] = useState<string | null>(null) return ( <View> <Button title="Paste image" onPress={async () => { const image = await One.Clipboard.getImage({ format: 'jpeg', jpegQuality: 0.8 }) setUri(image?.data ?? null) }} /> {uri && <Image source={{ uri }} style={{ width: 200, height: 200 }} />} </View> )}The data URI goes straight into source. JPEG at 0.8 keeps the string much
smaller than PNG for photos.
Edit this page on GitHub.