From 03aa619b9190b14bc3d5984ab3e0a7c7f1032e1e Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 23 May 2026 14:56:04 +0200 Subject: [PATCH] Improve legacy clipboard copy handling Refactor ClipboardManager._legacyCopy to better handle focus and selection when copying. The textarea is now appended to the active element's parent (or body) and placed off-screen with aria-hidden and readonly attributes. The code preserves and restores the previous document selection and active element, uses focus({preventScroll: true}) to avoid scrolling, and returns the execCommand('copy') result. This makes legacy copy behavior more robust and less disruptive to the page state. --- frontend/src/utils/index.js | 54 +++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 70ac4a84..ffc89a5f 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -550,31 +550,45 @@ export class ClipboardManager { } static _legacyCopy(text) { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.setAttribute('readonly', ''); + textarea.setAttribute('aria-hidden', 'true'); + textarea.style.position = 'absolute'; + textarea.style.left = '-9999px'; + textarea.style.top = '0'; + textarea.style.opacity = '1'; + + const active = document.activeElement; + const host = (active && active !== document.body && active.parentElement) + ? active.parentElement + : document.body; + host.appendChild(textarea); + + const prevSelection = document.getSelection()?.rangeCount + ? document.getSelection().getRangeAt(0) + : null; + + let ok = false; try { - const textarea = document.createElement('textarea'); - textarea.value = text; - textarea.setAttribute('readonly', ''); - textarea.style.position = 'fixed'; - textarea.style.top = '0'; - textarea.style.left = '0'; - textarea.style.width = '1em'; - textarea.style.height = '1em'; - textarea.style.padding = '0'; - textarea.style.border = '0'; - textarea.style.outline = 'none'; - textarea.style.boxShadow = 'none'; - textarea.style.background = 'transparent'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - textarea.focus(); + textarea.focus({ preventScroll: true }); textarea.select(); textarea.setSelectionRange(0, text.length); - const ok = document.execCommand('copy'); - document.body.removeChild(textarea); - return ok; + ok = document.execCommand('copy'); } catch { - return false; + ok = false; } + + host.removeChild(textarea); + if (active && typeof active.focus === 'function') { + try { active.focus({ preventScroll: true }); } catch { /* ignore */ } + } + if (prevSelection) { + const sel = document.getSelection(); + sel?.removeAllRanges(); + sel?.addRange(prevSelection); + } + return ok; } }