Skip to content

[lexical-rich-text][lexical-plain-text] Spec hardening: call event.preventDefault() in dragover for HTML5 DnD compliance#8663

Merged
etrepum merged 3 commits into
facebook:mainfrom
sahiee-dev:fix/firefox-dragover-prevent-default
Jun 12, 2026
Merged

[lexical-rich-text][lexical-plain-text] Spec hardening: call event.preventDefault() in dragover for HTML5 DnD compliance#8663
etrepum merged 3 commits into
facebook:mainfrom
sahiee-dev:fix/firefox-dragover-prevent-default

Conversation

@sahiee-dev

@sahiee-dev sahiee-dev commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Description

Per the HTML5 DnD spec, an element must call event.preventDefault() in itsdragover handler to signal that it accepts drops. The native auto-accept exception applies only to <textarea> and <input type="text">, not to
contenteditable divs.

The user-visible drag-and-drop failure (#8014) was already fixed by #8373, which rewired the DROP_COMMAND handler to actually process the drop. Before #8373 the handler returned true without doing anything, so text never moved regardless of browser.

This PR hardens the dragover layer to match the spec:

  • registerRichText: add unconditional event.preventDefault() after the existing file-transfer guard in DRAGOVER_COMMAND. The decorator-specific call that was already there is now removed as redundant.
  • registerPlainText: add a new DRAGOVER_COMMAND handler with a file-transfer guard followed by event.preventDefault().

The change is safe and correct on all current browsers. It also guards against a future Firefox version enforcing the spec strictly, which would silently break drag-and-drop again.

Related: #8014

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Jun 12, 2026 3:26am
lexical-playground Ready Ready Preview, Comment Jun 12, 2026 3:26am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 10, 2026

@mayrang mayrang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decorator branch right after the new preventDefault is dead: line 1248 already calls preventDefault unconditionally, so the caretFromPoint / $isDecoratorNode block below it only re-calls it as a no-op. The // Won't work for DecoratorNode nor it's relevant comment no longer describes what runs either. Dropping the block and its comment would leave just the new line.

Separately, the new test asserts event.defaultPrevented === true on a synthetic dragover, but the existing dragSelectionToOffset helper (lines 49-71) only dispatches dragstart / drop / dragend — never dragover. If a future change re-breaks the gate, every existing drag test still passes; only the new one fails. Dispatching dragover inside that helper would cover the existing suite.

@sahiee-dev

Copy link
Copy Markdown
Contributor Author

Addressed: removed the dead decorator block and its stale comment. Also wired the dragover dispatch into dragSelectionToOffset with a defaultPrevented guard, so all four existing drag tests now cover the gate rather than just the new one.

@mayrang

mayrang commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

On the Before behavior — I instrumented the current playground.lexical.dev in Firefox (no fix from this PR applied) and ran a text-selection drag-and-drop. Bubble-phase capture (lexical handlers had already fired):

[dragover bubble] defaultPrevented: false, types: [...'application/x-lexical-editor', 'application/x-lexical-drag']
[drop bubble] defaultPrevented: true

So drop fires even with dragover defaultPrevented=false, and the text moves correctly. Firefox accepts the drop on contenteditable here without the spec-required dragover preventDefault; the lexical DROP_COMMAND handler then preventDefaults at drop time. The PR's user-visible Before doesn't reproduce on the current build.

Could you share the Firefox version + setup where the original report reproduces?

@mayrang

mayrang commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Following up on the Before check above, on the test side: the new dragover dispatch + throw could catch a hypothetical future regression (if someone removes the preventDefault call), but it doesn't function as a regression test for the user-visible issue #8014 actually reports (Firefox users unable to drag text). The existing tests already pass without this PR on the current Firefox build, so what the throw guards is the spec-compliance line itself — not the user-facing failure #8014 describes.

@potatowagon potatowagon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.

LGTM — Firefox drag-and-drop fix.

What I verified:

  • Two commits: (1) adds event.preventDefault() in DRAGOVER_COMMAND for both plain-text and rich-text editors, (2) refines the e2e test with an explicit dragover dispatch + defaultPrevented assertion as a regression guard.
  • Rich-text handler simplified from conditional (DecoratorNode-only) preventDefault to unconditional, which matches the HTML5 DnD spec — dragover must be cancelled for drop to fire.
  • Plain-text handler inlines the file-drop guard correctly.
  • CI all green (core unit 22.x + 24.x, browser, integrity, e2e canary chromium, CLA, Vercel).

No concerns. Safe to land.

@sahiee-dev

Copy link
Copy Markdown
Contributor Author

@mayrang Good catch on the version gap. The original report was against v0.38.2, it's worth checking whether #8373 already fixed the user visible symptom as a side effect, which would explain why the issue stayed open without being explicitly closed.
If that's the case, happy to reposition this as spec-compliance hardening or close if the maintainers consider it covered.

@sahiee-dev

Copy link
Copy Markdown
Contributor Author

Checked git history: #8373 already fixed the user-visible symptom. Before that PR the DROP_COMMAND handler returned true without actually processing the drop, so text never moved regardless of browser. That was the real bug, not the dragover gate.

@mayrang

mayrang commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Followup on your own git-history check above. Since you confirmed #8373 already covered the user-visible symptom of #8014:

  • the title "Fix Firefox drag-and-drop" and the body line "Firefox enforces the spec strictly" don't match current main (Firefox does drag-and-drop without this PR; my bubble capture also shows the drop being accepted without dragover preventDefault),
  • the test-plan Before — "text stays in place ... drop event never fires" — doesn't reproduce on current main,
  • the test-plan After is what main already shows without this PR,
  • and the new automated test asserts that preventDefault is dispatched, not that the user-visible bug reproduces without this PR.

What the PR actually does on top of main is spec hardening — calling preventDefault to follow the spec, even though no browser currently needs it.

Final call is the maintainers'. My read is that the title, body, and Before/After all need to change either way — retitle around spec hardening, or close if maintainers consider #8373 covers it. As-is, the earlier review back-and-forth went into framing that didn't match the diff.

@sahiee-dev sahiee-dev changed the title [lexical-rich-text][lexical-plain-text] Bug Fix: Fix Firefox drag-and-drop by calling event.preventDefault() in dragover [lexical-rich-text][lexical-plain-text] Spec hardening: call event.preventDefault() in dragover for HTML5 DnD compliance Jun 11, 2026
@sahiee-dev

Copy link
Copy Markdown
Contributor Author

Updated the title and description: the original framing ("Fix Firefox drag-and-drop") was based on a misdiagnosis. After checking git history, #8373 already fixed the user visible symptom by actually processing the drop in DROP_COMMAND. This PR is spec compliance hardening, not a bug fix. Title, body, and test plan updated to
reflect that accurately.

Happy to close if the maintainers consider #8373 sufficient coverage.

Comment on lines +406 to +411
// Files check is inlined (rather than using eventFiles() from
// @lexical/rich-text) because plain-text cannot import from rich-text.
const dataTransfer = event.dataTransfer;
if (dataTransfer !== null && dataTransfer.types.includes('Files')) {
return false;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best solution for something like this would be to move the function to @lexical/clipboard or @lexical/utils which both rich and plain text packages already depend on.

rich-text can just re-export it for compatibility

…fault() in dragover to fix Firefox drag-and-drop (facebook#8014)

Per the HTML5 DnD spec, a contenteditable div must call event.preventDefault()
in its dragover handler to become a valid drop target — without it, the browser
never fires the drop event. Chrome ignores this gap; Firefox enforces it strictly.

registerRichText's DRAGOVER_COMMAND handler only called preventDefault() for
DecoratorNode targets. For plain text drags it did nothing, so Firefox silently
swallowed the drop and $handleRichTextDrop never ran.

registerPlainText had no DRAGOVER handler at all, producing the same failure.

Fix: add an unconditional event.preventDefault() in the rich-text handler (after
the existing file-transfer guard) and add a new DRAGOVER handler to plain-text
with the same guard. Also adds an e2e test that directly asserts dragover sets
event.defaultPrevented, covering the gap left by the existing tests which bypass
dragover by dispatching drop directly.
- Remove dead decorator-node block from rich-text DRAGOVER handler
- Embed defaultPrevented assertion in dragSelectionToOffset helper so
  all four drag-drop tests fail if the Firefox gate regresses
- Remove now-redundant standalone dragover preventDefault test
- Add comment on plain-text Files check explaining the dependency
  direction constraint (cannot import eventFiles from rich-text)
… eventFiles to @lexical/utils

Move eventFiles from @lexical/rich-text to @lexical/utils so both
rich-text and plain-text can share it. Rich-text re-exports it from
@lexical/utils for backwards compatibility. Plain-text now uses
eventFiles instead of an inlined file-transfer check.
@etrepum etrepum added this pull request to the merge queue Jun 12, 2026
Merged via the queue into facebook:main with commit c3ed6ec Jun 12, 2026
46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants