* Provides a fallback clipboard method for when browsers do not have access * to the clipboard API (the browser is older, or the deployment is only running * on HTTP, when the clipboard API is only available in secure contexts). * * It feels silly that you have to make a whole dummy input just t
(textToCopy: string)
| 122 | * @see {@link https://web.dev/patterns/clipboard/copy-text?hl=en} |
| 123 | */ |
| 124 | function simulateClipboardWrite(textToCopy: string): boolean { |
| 125 | const previousFocusTarget = document.activeElement; |
| 126 | const dummyInput = document.createElement("input"); |
| 127 | // Keep the dummy input inside an open dialog so focus traps allow |
| 128 | // execCommand("copy") to select it. |
| 129 | const activeDialog = |
| 130 | previousFocusTarget instanceof HTMLElement |
| 131 | ? previousFocusTarget.closest(DIALOG_SELECTOR) |
| 132 | : undefined; |
| 133 | const dummyInputContainer = |
| 134 | activeDialog ?? document.querySelector(DIALOG_SELECTOR) ?? document.body; |
| 135 | |
| 136 | // Have to add test ID to dummy element for mocking purposes in tests |
| 137 | dummyInput.setAttribute("data-testid", HTTP_FALLBACK_DATA_ID); |
| 138 | |
| 139 | // Using visually-hidden styling to ensure that inserting the element doesn't |
| 140 | // cause any content reflows on the page (removes any risk of UI flickers). |
| 141 | // Can't use visibility:hidden or display:none, because then the elements |
| 142 | // can't receive focus, which is needed for the execCommand method to work |
| 143 | const style = dummyInput.style; |
| 144 | style.display = "inline-block"; |
| 145 | style.position = "absolute"; |
| 146 | style.overflow = "hidden"; |
| 147 | style.clip = "rect(0 0 0 0)"; |
| 148 | style.clipPath = "rect(0 0 0 0)"; |
| 149 | style.height = "1px"; |
| 150 | style.width = "1px"; |
| 151 | style.margin = "-1px"; |
| 152 | style.padding = "0"; |
| 153 | style.border = "0"; |
| 154 | |
| 155 | dummyInputContainer.appendChild(dummyInput); |
| 156 | dummyInput.value = textToCopy; |
| 157 | dummyInput.focus(); |
| 158 | dummyInput.select(); |
| 159 | |
| 160 | /** |
| 161 | * The document.execCommand method is officially deprecated. Browsers are free |
| 162 | * to remove the method entirely or choose to turn it into a no-op function |
| 163 | * that always returns false. You cannot make any assumptions about how its |
| 164 | * core functionality will be removed. |
| 165 | * |
| 166 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Clipboard} |
| 167 | */ |
| 168 | let copySuccessful: boolean; |
| 169 | try { |
| 170 | copySuccessful = document?.execCommand("copy") ?? false; |
| 171 | } catch { |
| 172 | copySuccessful = false; |
| 173 | } |
| 174 | |
| 175 | dummyInput.remove(); |
| 176 | if (previousFocusTarget instanceof HTMLElement) { |
| 177 | previousFocusTarget.focus(); |
| 178 | } |
| 179 | |
| 180 | return copySuccessful; |
| 181 | } |