/** Copy only after a user action; WebViews may not expose the async Clipboard API. */ export async function copyText(text: string): Promise { if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return; } catch { // Embedded browsers can deny the async API while allowing a selected copy. } } const active = document.activeElement; const selection = window.getSelection(); const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index).cloneRange(), ) : []; const inputSelection = active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement ? ([ active.selectionStart, active.selectionEnd, active.selectionDirection, ] as const) : null; const field = document.createElement('textarea'); field.value = text; field.readOnly = true; field.style.cssText = 'position:fixed;opacity:0;pointer-events:none;font-size:16px'; (active?.closest('[role="dialog"]') ?? document.body).append(field); try { field.focus({ preventScroll: true }); field.select(); field.setSelectionRange(0, text.length); if (!document.execCommand('copy')) { throw new Error('Clipboard access unavailable'); } } finally { field.remove(); if (active instanceof HTMLElement) { active.focus({ preventScroll: true }); } if (selection) { selection.removeAllRanges(); ranges.forEach((range) => selection.addRange(range)); } if ( inputSelection && inputSelection[0] !== null && inputSelection[1] !== null && (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) ) { active.setSelectionRange( inputSelection[0], inputSelection[1], inputSelection[2] ?? undefined, ); } } }