[lexical-playground] Bug Fix: EquationNode click → NodeSelection + empty-input Backspace removes#8534
Merged
Conversation
…lection and remove on empty Backspace EquationComponent had no path that turned a click or an empty-input Backspace into a lexical-level selection / deletion. A single click on the rendered KaTeX would leave `$getSelection()` null (lexical's generic click handler does not promote DecoratorNode clicks to a NodeSelection), and Backspace inside an already-empty inline editor would sit on the host node forever. Two changes, mirroring the ImageComponent pattern: - Register `CLICK_COMMAND` so a click whose target is inside the keyed `<div|span class="editor-equation">` sets / clears a NodeSelection on this node (shift+click toggles). The command-level hook fires after lexical's own selection normalization, so the NodeSelection survives the surrounding `selectionchange` flow. An effect mirrors `isSelected` onto the keyed DOM as a `focused` class so the existing `.editor-equation.focused` outline rule applies. Dropped the previous auto-open-on-NodeSelection branch — it would now fire on every single click; the double-click gesture remains the only path into the inline `EquationEditor`. - Wire an `onDeleteEmpty` callback through `EquationEditor`'s `onKeyDown`: when the input value is empty and the user presses Backspace, remove the host EquationNode and put the caret at the end of the previous sibling (if any).
…NodeSelection Enter
After the click-to-NodeSelection fix in the previous commit, pressing
Enter while an EquationNode is the lone NodeSelection routed through
RichTextExtension's generic block-decorator branch and landed on a
root-level RangeSelection element point (e.g. `{key: root, offset: 1,
type: element}` for an equation that is root's only child). The
default Enter / typing path from that element point does not surface
a new paragraph, so the editor appeared stuck — Enter and typing did
nothing visible.
Handle the case directly: when this equation is the only selected
node and the user presses Enter, insert a fresh empty paragraph right
after the equation and select it. The caret lands in a text-bearing
paragraph and subsequent typing flows normally.
… + empty-input delete
Two follow-ups on the click → NodeSelection fix:
- Drop the `tabIndex={-1}` and `role="button"` attributes from
KatexRenderer's wrapper span. With tabIndex, focus settled inside
the equation's decorator subtree on click and rich-text's
`KEY_BACKSPACE_COMMAND` short-circuited via
`$isTargetWithinDecorator(event.target)`. Removing the attributes
matches the ImageComponent pattern — focus stays on the
contenteditable root, so Backspace fires through
`DELETE_CHARACTER_COMMAND` → `NodeSelection.deleteNodes()`.
- Branch `onDeleteEmpty` on `node.isInline()`. Inline path clears
the selection and preserves the wrapper paragraph
(`node.remove(true)`). Block path keeps the existing prevSibling /
paragraph-replace logic. The inline path now removes the host
EquationNode on empty-input Backspace, though commit still emits
the same "Lexical node does not exist in active editor state"
error that main already produces for inline-equation deletion —
the stale ref originates in lexical's updateListener chain and is
left as a follow-up.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…or KatexRenderer attribute removal
Match the spec snapshot to the actual KatexRenderer output now that
`role="button"` and `tabIndex={-1}` are gone from the wrapper span.
…rer attribute removal Same follow-up as the previous EquationNode spec fix — replace the expected `<span role="button" tabindex="-1">` markup with `<span>` to match the new KatexRenderer output.
…KatexRenderer attribute removal Last spec with the stale `<span role="button" tabindex="-1">` markup.
etrepum
approved these changes
May 22, 2026
This was referenced May 22, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
EquationNode(playground's KaTeX wrapper, aDecoratorNode) had no path that turned a single click into aNodeSelection, so downstream commands (Backspace, copy, Enter, arrow keys) had nothing to act on. A block equation that ends up as the sole root child becomes uninteractive (no caret, no selection, no way to delete), and the inline LaTeX editor's empty-input Backspace was a dead end.Fix
Mirror the
ImageComponent/PollComponentpattern:CLICK_COMMANDonEquationComponent(COMMAND_PRIORITY_LOW). A click inside the keyededitor-equationDOM sets aNodeSelection; shift+click toggles. The command-level hook fires after lexical's own selection normalization, so theNodeSelectionsurvives the surroundingselectionchangeflow.isSelectedonto the keyed wrapper as afocusedclass so the existing.editor-equation.focusedoutline renders.KEY_ENTER_COMMAND:NodeSelectionon a block equation inserts an empty paragraph after the equation and selects it. Inline equation inserts the paragraph after the wrapper (insertAfter on the inline equation itself would nest paragraph-in-paragraph).tabIndex={-1}androle="button"fromKatexRenderer. WithtabIndex, focus stayed inside the decorator subtree on click andKEY_BACKSPACE_COMMANDshort-circuited via$isTargetWithinDecorator(event.target). Removing them matchesImageComponent— focus stays on the contenteditable root, Backspace routes throughDELETE_CHARACTER_COMMAND→NodeSelection.deleteNodes().EquationEditor'sonKeyDownto anonDeleteEmptycallback: when the LaTeX input is already empty and Backspace fires, remove the host node. Block path selects the previous sibling's end (or replaces with an empty paragraph if none); inline path clears the selection andnode.remove(true)to preserve the wrapper paragraph.Known limitation
Deleting an inline equation (from any path, including main on this branch's base) emits a console error:
Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.Rough debugging: the throw lands in
$commitPendingUpdates→triggerListeners('update', ...)(LexicalUpdates.ts:714), i.e. after the equation has already been removed and the editor state is committing. One of the registered update listeners reads a node reference that is no longer in the active NodeMap. The trigger reproduces on plain main too, not just on this branch — the fix here doesn't add the bug, it surfaces it more often because the new flows reach this path.Splitting it off as a follow-up because identifying which listener holds the stale reference means stepping through lexical's internal update / mutation listener chain, and reshaping that chain is well outside the playground change in this PR. Better as a focused follow-up against lexical core once the trigger is isolated.
Closes #8533.
Test plan
pnpm tsc --noEmit -p tsconfig.jsonclean.NodeSelection, outline appears. Backspace removes the equation and replaces it with an empty paragraph (caret in the paragraph).NodeSelectioninserts an empty paragraph after the equation, caret inside ready for typing.NodeSelection, outline. Backspace removes the equation; caret stays in the surrounding paragraph.