fix(web): paste clipboard text into the TEXT / MText editors

Ctrl+V was globally bound to the entity paste command, so it never pasted
into an open text editor, and even when routed there iced's clipboard read
is a no-op on the web. Route Ctrl+V to the focused editor and, on the web,
read the system clipboard via the browser's async Clipboard API (the
keypress is a user gesture, so the read is permitted):

- MText editor: insert at the caret (web + native).
- TEXT field: append on the web; native lets the text_input paste itself.
- No editor open: falls through to the entity paste command as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-20 17:05:02 +03:00
1 parent 54106ca9b0
commit e1c5917644
5 files changed
+77 -1

No files matched your search

+51
View File
@@ -4840,6 +4840,57 @@ impl OpenCADStudio {
}
Task::none()
}
// Ctrl+V. The MText editor and (on the web) the TEXT editor read the
// system clipboard asynchronously — the only paste path that works
// in the browser, where the synchronous clipboard the iced
// text_input expects is empty. With no editor open it falls through
// to the entity paste command.
Message::PasteShortcut => {
if self.mtext_editor.is_some() {
// Web reads via the browser's async Clipboard API (iced's
// sync clipboard read returns nothing there); native uses
// iced's clipboard.
#[cfg(target_arch = "wasm32")]
return Task::perform(
crate::sys::read_clipboard_text(),
Message::MTextPasteClip,
);
#[cfg(not(target_arch = "wasm32"))]
return iced::clipboard::read().map(Message::MTextPasteClip);
}
if self.text_inline.is_some() {
// Web: the iced text_input can't reach the async clipboard,
// so paste it ourselves. Native: the focused text_input
// already handled Ctrl+V — doing it here would duplicate.
#[cfg(target_arch = "wasm32")]
return Task::perform(
crate::sys::read_clipboard_text(),
Message::TextInlinePasteClip,
);
#[cfg(not(target_arch = "wasm32"))]
return Task::none();
}
Task::done(Message::Command("PASTECLIP".to_string()))
}
Message::MTextPasteClip(text) => {
if let Some(text) = text.filter(|t| !t.is_empty()) {
// CR/LF arrive as line breaks; MText keeps "\n", drop "\r".
self.mtext_type(&text.replace('\r', ""));
self.rebuild_mtext_preview();
}
Task::none()
}
Message::TextInlinePasteClip(text) => {
if let Some(text) = text.filter(|t| !t.is_empty()) {
// Single-line field: collapse newlines, append at the end.
let flat = text.replace(['\r', '\n'], " ");
if let Some(ed) = self.text_inline.as_mut() {
ed.value.push_str(&flat);
}
}
Task::none()
}
Message::TextInlineOk => {
self.text_inline_commit();
Task::none()