Simplify `copyInput`, `copyNode`, `copyText` method signatures
They don't accept `button` as 1st argument anymore since that wouldn't make much sense when these methods get exported. The `button` argument was only used to eventually fire the synthetic `copy` event, which was problematic for several reasons: 1. The native `copy` event is supposed to be fired *before* the clipboard is modified and cancelling the event is supposed to cancel the copy operation as well; https://w3c.github.io/clipboard-apis/#clipboard-event-copy 2. The `copy` event is supposed to expose `clipboardData` property that allows for modification of the data that gets copied; https://w3c.github.io/clipboard-apis/#override-copy 3. The `clipboard.writeText()` function might trigger a `copy` event in the future, so we might end up with double events; 4. The non-async fallback code using `execCommand('copy')` fires the native `copy` event from different elements than the `<clipboard-copy>` button itself. For these reasons, I've opted to not simulate the synthetic `copy` event at all. The updated methods now also return a Promise, which allows the caller to potentially react to `clipboard.writeText()` finishing.
This commit is contained in:
Родитель
74afacdf9a
Коммит
7343b2590a
|
@ -6,26 +6,24 @@ function copy(button: HTMLElement) {
|
|||
const id = button.getAttribute('for')
|
||||
const text = button.getAttribute('value')
|
||||
if (text) {
|
||||
copyText(button, text)
|
||||
copyText(text)
|
||||
} else if (id) {
|
||||
copyTarget(button, id)
|
||||
const node = button.ownerDocument.getElementById(id)
|
||||
if (node) copyTarget(node)
|
||||
}
|
||||
}
|
||||
|
||||
function copyTarget(button: Element, id: string) {
|
||||
const content = button.ownerDocument.getElementById(id)
|
||||
if (!content) return
|
||||
|
||||
function copyTarget(content: Element) {
|
||||
if (content instanceof HTMLInputElement || content instanceof HTMLTextAreaElement) {
|
||||
if (content.type === 'hidden') {
|
||||
copyText(button, content.value)
|
||||
copyText(content.value)
|
||||
} else {
|
||||
copyInput(button, content)
|
||||
copyInput(content)
|
||||
}
|
||||
} else if (content instanceof HTMLAnchorElement && content.hasAttribute('href')) {
|
||||
copyText(button, content.href)
|
||||
} else {
|
||||
copyNode(button, content)
|
||||
copyNode(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,16 @@ function createNode(text: string): Element {
|
|||
return node
|
||||
}
|
||||
|
||||
export function copyNode(button: Element, node: Element) {
|
||||
if (writeAsync(button, node.textContent)) return
|
||||
export function copyNode(node: Element): Promise<void> {
|
||||
if ('clipboard' in navigator) {
|
||||
// eslint-disable-next-line flowtype/no-flow-fix-me-comments
|
||||
// $FlowFixMe Clipboard is not defined in Flow yet.
|
||||
return navigator.clipboard.writeText(node.textContent)
|
||||
}
|
||||
|
||||
const selection = getSelection()
|
||||
if (selection == null) {
|
||||
return
|
||||
return Promise.reject(new Error())
|
||||
}
|
||||
|
||||
selection.removeAllRanges()
|
||||
|
@ -26,22 +30,34 @@ export function copyNode(button: Element, node: Element) {
|
|||
|
||||
document.execCommand('copy')
|
||||
selection.removeAllRanges()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
export function copyText(button: Element, text: string) {
|
||||
if (writeAsync(button, text)) return
|
||||
export function copyText(text: string): Promise<void> {
|
||||
if ('clipboard' in navigator) {
|
||||
// eslint-disable-next-line flowtype/no-flow-fix-me-comments
|
||||
// $FlowFixMe Clipboard is not defined in Flow yet.
|
||||
return navigator.clipboard.writeText(text)
|
||||
}
|
||||
|
||||
const body = document.body
|
||||
if (!body) return
|
||||
if (!body) {
|
||||
return Promise.reject(new Error())
|
||||
}
|
||||
|
||||
const node = createNode(text)
|
||||
body.appendChild(node)
|
||||
copyNode(button, node)
|
||||
copyNode(node)
|
||||
body.removeChild(node)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
export function copyInput(button: Element, node: HTMLInputElement | HTMLTextAreaElement) {
|
||||
if (writeAsync(button, node.value)) return
|
||||
export function copyInput(node: HTMLInputElement | HTMLTextAreaElement): Promise<void> {
|
||||
if ('clipboard' in navigator) {
|
||||
// eslint-disable-next-line flowtype/no-flow-fix-me-comments
|
||||
// $FlowFixMe Clipboard is not defined in Flow yet.
|
||||
return navigator.clipboard.writeText(node.value)
|
||||
}
|
||||
|
||||
node.select()
|
||||
document.execCommand('copy')
|
||||
|
@ -49,14 +65,5 @@ export function copyInput(button: Element, node: HTMLInputElement | HTMLTextArea
|
|||
if (selection != null) {
|
||||
selection.removeAllRanges()
|
||||
}
|
||||
}
|
||||
|
||||
function writeAsync(button: Element, text: string): boolean {
|
||||
const clipboard = navigator.clipboard
|
||||
if (!clipboard) return false
|
||||
|
||||
clipboard.writeText(text).then(function() {
|
||||
button.dispatchEvent(new CustomEvent('copy', {bubbles: true}))
|
||||
})
|
||||
return true
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче