Fix iOS clipboard with contentEditable approach
- Try modern clipboard API first - For iOS: use contentEditable + special range selection - Set font-size 16px to prevent zoom on iOS - Properly throw error on failure for user feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+37
-16
@@ -679,40 +679,61 @@ class MotifApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async copyToClipboard(text: string): Promise<void> {
|
private async copyToClipboard(text: string): Promise<void> {
|
||||||
// iOS Safari needs the textarea fallback - clipboard API is unreliable
|
// Try modern clipboard API first
|
||||||
// Try textarea approach first for better iOS compatibility
|
if (navigator.clipboard?.writeText) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// Fall through to textarea method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for iOS Safari and older browsers
|
||||||
const ta = document.createElement('textarea');
|
const ta = document.createElement('textarea');
|
||||||
ta.value = text;
|
ta.value = text;
|
||||||
ta.style.position = 'fixed';
|
ta.style.position = 'fixed';
|
||||||
ta.style.left = '0';
|
|
||||||
ta.style.top = '0';
|
ta.style.top = '0';
|
||||||
ta.style.opacity = '0';
|
ta.style.left = '0';
|
||||||
ta.style.pointerEvents = 'none';
|
ta.style.width = '2em';
|
||||||
ta.setAttribute('readonly', ''); // Prevent keyboard on iOS
|
ta.style.height = '2em';
|
||||||
|
ta.style.padding = '0';
|
||||||
|
ta.style.border = 'none';
|
||||||
|
ta.style.outline = 'none';
|
||||||
|
ta.style.boxShadow = 'none';
|
||||||
|
ta.style.background = 'transparent';
|
||||||
|
ta.style.fontSize = '16px'; // Prevent zoom on iOS
|
||||||
|
ta.setAttribute('readonly', '');
|
||||||
document.body.appendChild(ta);
|
document.body.appendChild(ta);
|
||||||
|
|
||||||
// iOS specific selection
|
// iOS needs special handling
|
||||||
|
const isiOS = /ipad|iphone|ipod/i.test(navigator.userAgent);
|
||||||
|
if (isiOS) {
|
||||||
|
ta.contentEditable = 'true';
|
||||||
|
ta.readOnly = false;
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.selectNodeContents(ta);
|
range.selectNodeContents(ta);
|
||||||
const selection = window.getSelection();
|
const sel = window.getSelection();
|
||||||
if (selection) {
|
if (sel) {
|
||||||
selection.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
selection.addRange(range);
|
sel.addRange(range);
|
||||||
|
}
|
||||||
|
ta.setSelectionRange(0, 999999);
|
||||||
|
} else {
|
||||||
|
ta.select();
|
||||||
}
|
}
|
||||||
ta.setSelectionRange(0, text.length); // iOS needs this
|
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
try {
|
try {
|
||||||
success = document.execCommand('copy');
|
success = document.execCommand('copy');
|
||||||
} catch {
|
} catch {
|
||||||
success = false;
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.removeChild(ta);
|
document.body.removeChild(ta);
|
||||||
|
|
||||||
// Fallback to modern API if execCommand failed
|
if (!success) {
|
||||||
if (!success && navigator.clipboard?.writeText) {
|
throw new Error('Copy failed');
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user