[lexical] Bug Fix: Ignore beforeinput and input events in captured decorators#8740
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.
LGTM ✅ — Bug fix: Ignore beforeinput and input events in captured decorators.
What this does: Extracts the "is this input from a captured decorator?" check out of $handleInput and into a shared shouldIgnoreInputFromCapturedSelection helper. This helper is now called in both onBeforeInput (early-return before dispatchCommand(BEFORE_INPUT_COMMAND, ...)) and onInput (wrapping the updateEditorSync call). Previously, only the input event was filtered at the command handler level — beforeinput was never filtered, meaning decorator inputs (e.g. <input> elements inside DecoratorNodes) would still trigger BEFORE_INPUT_COMMAND and potentially corrupt editor state.
What I checked:
- Logic correctness: The helper checks two paths — (a) composed event target is directly inside a capturing decorator, and (b) the active element (via
getActiveElementDeepfor shadow DOM) is a capturing decorator. Both are existing guards, just factored out and applied earlier. - Behavioral change: Moving the guard from
$handleInput(afterupdateEditorSyncentry) toonInput(beforeupdateEditorSync) means no editor update cycle is started at all for captured decorator inputs. This is strictly better — avoids unnecessary commit overhead. - beforeinput coverage: The new early-return in
onBeforeInputfills a real gap. Without it, Firefox in particular firesbeforeinputthat bubbles to the contentEditable root even when focus is on an inner<input>. This could triggerBEFORE_INPUT_COMMANDhandlers that assume a Lexical selection context. - Test quality: 108-line browser test simulates exactly the Firefox scenario — dispatches untrusted
beforeinput/inputfrom a focused decorator<input>, thendocument.execCommand. Verifies neitherBEFORE_INPUT_COMMANDnorINPUT_COMMANDfire, and the native input still works (input.value === "a"). - www compat: No removed/renamed exports. No API surface changes. The fix only suppresses spurious events — www consumers of
BEFORE_INPUT_COMMAND/INPUT_COMMANDwill simply stop receiving events they should never have received. - Edge cases: Shadow DOM path handled via
getActiveElementDeep. Null checks onrootElement/activeElementpresent.rootElement.contains(activeElement)ensures only elements within this editor are checked.
CI: Core unit (22.x + 24.x), browser (all platforms), integrity, integration, e2e plain-text (linux + windows) — all GREEN. Remaining e2e (collab, mac, rich-text) still pending.
Ready to approve once remaining e2e completes.
|
If events from decorators are supposed to be ignored, why are key shortcuts propagates on to the editor? Screen.Recording.2026-06-23.at.23.25.24.mov |
|
This ignores them when they are captured in a native input, textarea, or some other active element that isn't a lexical editor. |
|
This change affects beforeinput & input events and how selection is mapped, keyboard shortcuts are handled by keydown events which doesn't have this check |
857d58b to
cae40f3
Compare
…ontrols Adds the event-layer guard for facebook#8738 on top of the selection-layer changes already on this branch. Firefox 152 changed how it dispatches beforeinput/input for native controls (such as an <input> inside a decorator): the event is retargeted to the editor root rather than the focused control, so its (composed) target no longer identifies the control. Lexical then processed the keystroke as editor input and inserted text outside the component, with a stationary caret producing reversed characters. The Firefox 152 behavior can only be reproduced on a real Firefox >= 152 build (Playwright cannot run it) and was verified manually. Add isInputEventTargetingCapturedSelection(), which detects that a beforeinput/input belongs to a captured decorator subtree by checking both the composed event target and the deep active element. The active-element signal is what survives Firefox 152's retargeting. onBeforeInput and onInput still run their updateEditorSync({event}) so the editor selection stays in sync with the DOM; they only skip dispatching BEFORE_INPUT_COMMAND / INPUT_COMMAND when the guard matches. This generalizes and replaces the narrower composed-target-only check that previously lived in $handleInput, and extends the same protection to beforeinput. Test (browser-mode, real layout/selection engine): LexicalFirefoxDecoratorRetarget covers both the event dispatched from the focused control and the event retargeted to the editor root; it asserts at CRITICAL priority that no input command is dispatched, and both cases fail without this guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6SoWfs6U6w2CK8BXQn8WS
Description
Firefox 152 changed how it dispatches beforeinput events which caused lexical to process text input when it should be captured in the decorator's native input (this also applies to controls added via DOMSlot, which work very similarly).
onBeforeInputeventcontext to the update forBEFORE_INPUT_COMMANDdispatch, but only dispatches when input is not capturedonInputisSelectionWithinEditor$isSelectionCapturedInDecoratorInputwith editor context (would fail silently before)$isSelectionCapturedInDecoratorInputisSelectionCapturedInDecoratorInput(it requires an active editor, wasn't named correctly before)$markSlotEditable__lexicalEditorproperty like the RootElement to let event handling know that their contents should be handled by lexicalCloses #8738
Test plan
New browser unit test to simulate this condition, no version of playwright has Firefox 152 yet so we can't do a meaningful e2e test