[lexical-react] Bug Fix: the node context menu leaves the native menu alone when it has nothing to show - #9022
Closed
LeSingh1 wants to merge 1 commit into
Closed
Conversation
… alone when it has nothing to show
## Description
`NodeContextMenuPlugin` suppresses the browser's context menu and opens its own before it knows whether it has anything to put in it:
```tsx
function onContextMenu(e: MouseEvent) {
e.preventDefault(); // <- unconditional
refs.setPositionReference({ ... });
let visibleItems: ContextMenuType[] = [];
if (items) {
editor.read(() => {
const node = $getNearestNodeFromDOMNode(e.target as Element) ?? $getRoot();
if (node) {
visibleItems = items!.filter(option =>
option.$showOn ? option.$showOn(node) : true,
);
}
});
}
...
setIsOpen(true); // <- unconditional
}
```
`$showOn` is documented as the per-node predicate that decides "whether the item is shown for the right-clicked node", so it is expected that a given node hides some items — and a node that hides *all* of them is the natural consequence of that, not a misuse. When it happens the user gets the worst of both: no native menu, because `preventDefault` already ran; no visible menu, because the rendered `<div>` has no children; and the page cannot be scrolled, because the plugin still mounts
```tsx
<FloatingOverlay lockScroll={true}>
```
around it. The only way out is to click somewhere to dismiss an overlay that shows nothing. Right-clicking to reach Copy, Paste, or Inspect on such a node is simply not possible.
This computes `visibleItems` first and returns early when it is empty, before `preventDefault` and before `setIsOpen(true)`. Everything else is unchanged; the item-building code just moves below the filter it always depended on. No API or serialization change.
## Test plan
New unit test `packages/lexical-react/src/__tests__/unit/LexicalNodeContextMenuPlugin.test.tsx`. The second case is the control — the menu must still replace the native one when it does have an item — and passes before and after.
### Before
```
❯ packages/lexical-react/src/__tests__/unit/LexicalNodeContextMenuPlugin.test.tsx (2 tests | 1 failed)
× leaves the native context menu alone when no item is shown
AssertionError: expected true to be false // Object.is equality
✓ opens and replaces the native context menu when an item is shown
Test Files 1 failed (1)
Tests 1 failed | 1 passed (2)
```
### After
```
Test Files 1 passed (1)
Tests 2 passed (2)
```
Package suite is unchanged:
```
$ npx vitest run packages/lexical-react
Test Files 32 passed (32)
Tests 184 passed (184)
```
LeSingh1
requested review from
acywatson,
etrepum,
fantactuka,
ivailop7,
potatowagon and
zurfyx
as code owners
August 9, 2026 05:30
|
@LeSingh1 is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
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
NodeContextMenuPluginsuppresses the browser's context menu and opens its own before it knows whether it has anything to put in it:$showOnis documented as the per-node predicate that decides "whether the item is shown for the right-clicked node", so it is expected that a given node hides some items — and a node that hides all of them is the natural consequence of that, not a misuse. When it happens the user gets the worst of both: no native menu, becausepreventDefaultalready ran; no visible menu, because the rendered<div>has no children; and the page cannot be scrolled, because the plugin still mountsaround it. The only way out is to click somewhere to dismiss an overlay that shows nothing. Right-clicking to reach Copy, Paste, or Inspect on such a node is simply not possible.
This computes
visibleItemsfirst and returns early when it is empty, beforepreventDefaultand beforesetIsOpen(true). Everything else is unchanged; the item-building code just moves below the filter it always depended on. No API or serialization change.Test plan
New unit test
packages/lexical-react/src/__tests__/unit/LexicalNodeContextMenuPlugin.test.tsx. The second case is the control — the menu must still replace the native one when it does have an item — and passes before and after.Before
After
Package suite is unchanged: