[lexical-utils] Bug Fix: Prioritize file transfer over text/html when pasting browser-copied images#8699
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
potatowagon
left a comment
There was a problem hiding this comment.
Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.
Assessment: LGTM ✅
Summary: This PR fixes a real UX issue where browser-copied images (right-click → Copy Image) were being imported as remote URL <img> tags via text/html rather than as actual File objects via DRAG_DROP_PASTE. The fix adds an optional filesOnly parameter to eventFiles() that suppresses hasContent when files are present, letting the file transfer path win.
What I checked:
- Logic correctness: The
!filesOnly &&guard is correctly placed — whenfilesOnly=trueand files are present,hasContentreturnsfalse, which letsfiles.length > 0 && !hasTextContentpass in the PASTE_COMMAND handler. This correctly routes toDRAG_DROP_PASTE. - Backward compatibility / www compat: The new
filesOnlyparameter is optional (defaults toundefined/falsy), so all existing callers (including internal www) are unaffected. The only caller usingfilesOnly=trueis the PASTE_COMMAND in@lexical/rich-text. No removed/renamed exports. Safe for www. - Edge cases:
- Files-only paste (no text/html): still works —
hasFiles=true,hasContent=falseregardless offilesOnlyflag. - Text-only paste (no files):
hasFiles=falseso thefiles.length > 0guard prevents DRAG_DROP_PASTE.hasContentisfalsewhenfilesOnly=truebut this is harmless since no files means the guard fails anyway.⚠️ Wait — actually looking at the last test case: whenfilesOnly=truewithtext/htmlbut no files,hasContentreturnsfalse. This means if some other code path relied onhasContentbeingtruefor text-only pastes while passingfilesOnly=true, it would break. But since only the rich-text PASTE_COMMAND uses this flag, and it only checkshasTextContentin thefiles.length > 0branch, this is safe. - DragEvent path: same logic applies, also tested.
- Files-only paste (no text/html): still works —
- Test coverage: 109-line test file with 7 test cases covering: no DataTransfer, text-only, files-only, mixed (default vs filesOnly), DragEvent, and the edge case of filesOnly with no files. Excellent coverage.
- No regressions: The only production code change in
@lexical/rich-textis passingtrueas the second arg toeventFiles(). The behavior only changes when clipboard contains BOTH files AND text/html simultaneously — a narrow, well-defined scenario (browser image copy).
CI Status: CLA ✅, Vercel ✅. Full test matrix (unit, browser, e2e, integrity) pending — PR is very fresh. Recommend waiting for full CI green before merging.
Verdict: Clean, well-scoped fix with good documentation comments and comprehensive tests. Ready to approve once full CI passes.
c5cedd9 to
895357f
Compare
|
FYI — you can run |
…tead of HTML (facebook#8681) When the user copies an image from a browser webpage via right-click Copy Image, the clipboard simultaneously holds two MIME types: Files (the actual image file) and text/html (an img element pointing to the remote URL). The PASTE_COMMAND handler in @lexical/rich-text was only dispatching DRAG_DROP_PASTE when files.length > 0 && !hasTextContent. Because text/html was always counted as text content, DRAG_DROP_PASTE was never dispatched and the image was silently imported as HTML instead of as the actual File object. Fix: In the PASTE_COMMAND handler, also check for the absence of text/plain as a second condition for preferring the file over the HTML fallback. Applications such as Word place a rasterized image in the Files slot alongside text/plain, text/html, and text/rtf when copying rich text. The presence of text/plain is the reliable signal that the primary intent is a text paste. A browser-copied image never puts text/plain in the clipboard, so checking !hasPlainText correctly distinguishes the two cases without affecting eventFiles signature or any other call sites. The dispatch condition becomes: files.length > 0 && (!hasTextContent || !hasPlainText) This handles all cases correctly: - Files only (no text): !hasTextContent is true, DRAG_DROP_PASTE fires - Browser image (Files + text/html, no text/plain): !hasPlainText is true, DRAG_DROP_PASTE fires with the actual image file - Word text (Files + text/html + text/plain): both conditions are false, falls through to HTML import as before eventFiles in @lexical/utils is unchanged. The fix is entirely contained in the PASTE_COMMAND handler in @lexical/rich-text. Added unit tests documenting the eventFiles return values for each clipboard scenario and a regression test for the Word text copy case.
895357f to
862983a
Compare
|
What is the use case to always prefer files vs. html? Isn't this something that an application can fairly easily decide on by registering PASTE_COMMAND in their app and calling |
|
@etrepum Thank you for the feedback. this is a fair architectural concern. You're right that changing the default in registerRichText imposes a policy decision on all downstream applications. An app without a DRAG_DROP_PASTE handler would previously get a fallback HTML img element; with our change it gets nothing. That's a meaningful behavior change for library consumers who haven't opted into image handling. The original problem (issue #8681) is that applications which DO handle DRAG_DROP_PASTE like the playground can never receive browser-copied images because the Files + text/html clipboard combination always falls through to HTML import. If you'd prefer to keep the default unchanged, we see two paths forward: App-level override (as you suggest): Document that apps which want file-first behavior should register their own PASTE_COMMAND handler at a higher priority (e.g. COMMAND_PRIORITY_EDITOR + 1) to intercept pastes with files and no text/plain. We can write that documentation and/or add a utility export. Keep the current fix but make it opt-in: Instead of changing the default in registerRichText, export a registerRichTextWithFilePaste() variant or an optional config flag, so apps that want this behavior can enable it without affecting others. Happy to go whichever direction you prefer just let me know and I'll update the PR accordingly. |
Fixes #8681
Problem
When a user copies an image from a browser webpage via the context menu
("Copy Image"), the clipboard simultaneously holds two MIME types:
Files- the actual image filetext/html- an<img>element pointing to the remote URLThe
PASTE_COMMANDhandler in@lexical/rich-textdispatchesDRAG_DROP_PASTEonly whenfiles.length > 0 && !hasTextContent.Because
eventFiles()in@lexical/utilsalways countedtext/htmlastext content,
hasTextContentwastruefor browser-copied images, soDRAG_DROP_PASTEwas never dispatched and the image was silently importedas HTML instead of as the actual File object.
Solution
Added an optional
filesOnly?: booleanparameter toeventFiles. Whentrueand files are present,hasContentis returned asfalseevenif
text/htmlortext/plainare also present in the DataTransfer.The PASTE_COMMAND handler in
registerRichTextnow passeseventFiles(event, true)so that a file transfer takes priority overany accompanying HTML fallback.
The change is purely additive:
false, so all existing call sites(DROP_COMMAND, DRAGOVER_COMMAND, DRAGSTART_COMMAND) are unaffected.
Testing
clipboard payloads with files only, text only, and the mixed
Files + text/html case that triggered the original bug.