clipboard.ts58 lines · main
| 1 | import { noop } from 'lodash' |
| 2 | import { toast } from 'sonner' |
| 3 | |
| 4 | type ClipboardText = string | Promise<string> |
| 5 | |
| 6 | /** |
| 7 | * Copy text content (string or Promise<string>) into Clipboard. Safari doesn't support write text into clipboard async, |
| 8 | * so if you need to load text content async before coping, please use Promise<string> for the 1st arg. |
| 9 | * |
| 10 | * IF YOU NEED TO CHANGE THIS FUNCTION, PLEASE TEST IT IN SAFARI with a promised string. Expiring URL to a file in a |
| 11 | * private bucket will do. |
| 12 | * |
| 13 | * Copied code from https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/ |
| 14 | */ |
| 15 | export const copyToClipboard = async (str: ClipboardText, callback = noop) => { |
| 16 | const focused = window.document.hasFocus() |
| 17 | if (!focused) { |
| 18 | toast.error('Unable to copy to clipboard') |
| 19 | return |
| 20 | } |
| 21 | |
| 22 | try { |
| 23 | if (typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) { |
| 24 | // NOTE: Safari locks down the clipboard API to only work when triggered |
| 25 | // by a direct user interaction. You can't use it async in a promise. |
| 26 | // But! You can wrap the promise in a ClipboardItem, and give that to |
| 27 | // the clipboard API. |
| 28 | // Found this on https://developer.apple.com/forums/thread/691873 |
| 29 | const text = new ClipboardItem({ |
| 30 | 'text/plain': Promise.resolve(str).then((text) => new Blob([text], { type: 'text/plain' })), |
| 31 | }) |
| 32 | |
| 33 | let resolve = () => {} |
| 34 | let reject = () => {} |
| 35 | const promise = new Promise<void>((res, rej) => { |
| 36 | resolve = res |
| 37 | reject = rej |
| 38 | }) |
| 39 | // Safari also seems to require that the promise resolve soon after the |
| 40 | // clipboard write call, adding a setTimeout with 0 delay seems to work. |
| 41 | // Returning the promise to ensure the caller can await the clipboard |
| 42 | // copy operation intuitively. |
| 43 | setTimeout(() => { |
| 44 | navigator.clipboard.write([text]).then(callback).then(resolve).catch(reject) |
| 45 | }, 0) |
| 46 | return promise |
| 47 | } |
| 48 | |
| 49 | // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write, |
| 50 | // but those are behind `dom.events.asyncClipboard.clipboardItem` preference. |
| 51 | // Good news is that other than Safari, Firefox does not care about |
| 52 | // Clipboard API being used async in a Promise. |
| 53 | await Promise.resolve(str).then((text) => navigator.clipboard?.writeText(text)) |
| 54 | callback() |
| 55 | } catch { |
| 56 | toast.error('Unable to copy to clipboard') |
| 57 | } |
| 58 | } |