Fix clipboard fallback selection inside dialogs (#433)
This commit is contained in:
1 parent
e738b6ce33
commit
8a8db0eb94
5 files changed
+73
-21
No files matched your search
+17
-20
@@ -1,35 +1,32 @@
|
||||
// Clipboard helper shared by the room screen (invite code / link) and 干员调配 (导出 / 导入 payloads).
|
||||
//
|
||||
// Lives here rather than in a screen: `screens/room.js` imports `screens/loadout.js` (the 干员调配 button), so loadout
|
||||
// importing the helper back from room would be a cycle.
|
||||
|
||||
/**
|
||||
* Copy text to the clipboard (async API with a textarea fallback for insecure contexts).
|
||||
* @param {string} text
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
// Shared by invite links, loadouts and settings exports, including LAN / insecure contexts.
|
||||
/** @param {string} text @returns {Promise<boolean>} */
|
||||
export async function copyText(text) {
|
||||
try {
|
||||
if (globalThis.navigator?.clipboard && globalThis.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
} catch { /* use the selected-text fallback */ }
|
||||
let ta, prev;
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
prev = document.activeElement;
|
||||
ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.setAttribute('readonly', '');
|
||||
// 16 px: iOS zooms into smaller focused fields; `readonly` keeps the keyboard away
|
||||
// Keep the temporary selection inside the active focus trap (#433). The 16 px readonly field avoids iOS zoom
|
||||
// and its keyboard; do not disable the dialog's focus protection or copy a selection redirected to another field.
|
||||
ta.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0;font-size:16px';
|
||||
document.body.appendChild(ta);
|
||||
const host = prev?.closest?.('[role="dialog"][aria-modal="true"], .guide') || document.body;
|
||||
host.appendChild(ta);
|
||||
ta.focus({ preventScroll: true });
|
||||
ta.select();
|
||||
// iOS Safari ignores select() on a textarea: an explicit range is what it copies (LAN play over http has no
|
||||
// navigator.clipboard, so this path is the one iPhones / iPads take)
|
||||
try { ta.setSelectionRange(0, ta.value.length); } catch { /* ignore */ }
|
||||
const ok = document.execCommand('copy');
|
||||
ta.remove();
|
||||
return ok;
|
||||
ta.setSelectionRange(0, ta.value.length); // iOS Safari ignores select() alone
|
||||
if (document.activeElement !== ta || ta.selectionStart !== 0 || ta.selectionEnd !== ta.value.length) return false;
|
||||
return !!document.execCommand('copy');
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
ta?.remove();
|
||||
if (prev?.isConnected) prev.focus?.({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user