From 1e7f680426b566ea1b8961417050e3937ab24744 Mon Sep 17 00:00:00 2001 From: Jarek Krochmalski Date: Sun, 6 Sep 2026 17:03:48 +0530 Subject: [PATCH] clipboard copy falls back correctly inside dialogs --- src/lib/utils/clipboard.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/utils/clipboard.ts b/src/lib/utils/clipboard.ts index 5e4ea16..0e97c2e 100644 --- a/src/lib/utils/clipboard.ts +++ b/src/lib/utils/clipboard.ts @@ -12,20 +12,32 @@ export async function copyToClipboard(text: string): Promise { } } - // Fallback: hidden textarea + execCommand for HTTP contexts + // Fallback: hidden textarea + execCommand for HTTP (insecure) contexts, where + // navigator.clipboard is unavailable. The textarea must live INSIDE the current + // focus-trap (e.g. a shadcn/bits-ui Dialog): appended to document.body it sits + // outside the trap, which yanks focus back to the dialog before execCommand runs, + // so the copy silently fails and pastes stale clipboard content (#1488). Mount it + // in the open dialog when there is one, else document.body. try { + const openDialog = document.querySelector('[role="dialog"]'); + const host = openDialog ?? document.body; + const active = document.activeElement as HTMLElement | null; + const textarea = document.createElement('textarea'); textarea.value = text; + textarea.setAttribute('readonly', ''); textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '-9999px'; textarea.style.opacity = '0'; - document.body.appendChild(textarea); + host.appendChild(textarea); textarea.focus(); textarea.select(); textarea.setSelectionRange(0, textarea.value.length); const ok = document.execCommand('copy'); - document.body.removeChild(textarea); + host.removeChild(textarea); + // Restore focus to whatever the user had (keeps the dialog's focus-trap happy). + active?.focus?.(); if (ok) return true; } catch { /* fall through */