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:
Mislav Marohnić 2018-08-12 16:38:24 +02:00 коммит произвёл David Graham
Родитель 74afacdf9a
Коммит 7343b2590a
2 изменённых файлов: 33 добавлений и 28 удалений

Просмотреть файл

@ -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()
}