Skip to content

[lexical-react][lexical-playground] FloatingUI Context Menu#7509

Merged
ivailop7 merged 22 commits intofacebook:mainfrom
ivailop7:context_menu_v2
May 31, 2025
Merged

[lexical-react][lexical-playground] FloatingUI Context Menu#7509
ivailop7 merged 22 commits intofacebook:mainfrom
ivailop7:context_menu_v2

Conversation

@ivailop7
Copy link
Copy Markdown
Collaborator

@ivailop7 ivailop7 commented May 5, 2025

A complete rewrite of the ContextMenu using FloatingUI without using the current LexicalMenu component. This makes the migration of the other typeahead menus to FloatingUI easier.

This PR introduces the following improvements:

  • Converts the 'li' items to 'buttons' (This leads to better overall accessibility)
  • The menu has the correct aria role
  • Correct positioning at edges and when page is scrolled
  • Typeahead accessibility (when context menu is open, pressing a keyboard character moves the selection)
  • Keyboard navigation (standard arrows navigation)
  • Better logic for conditional items rendering (display extra items when a node-of-type)
  • When displayed locks the page scroll and preserves focus correctly consistent with other editors
  • Support for disabled items and custom styling
  • Support for a separator and custom styling

@vercel
Copy link
Copy Markdown

vercel bot commented May 5, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lexical ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 31, 2025 0:24am
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 31, 2025 0:24am

@facebook-github-bot facebook-github-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 May 5, 2025
@ivailop7
Copy link
Copy Markdown
Collaborator Author

ivailop7 commented May 5, 2025

@etrepum This is ready to skim over. Playground is produced fine. I'll fix the types tomorrow. Most of the code is canonical as per the FloatingUI example for a context menu.

I had to make some creative choices for the conditional rendering and the editor.read for the underlying node, since the examples use pre-defined children to the main component and wanted to keep the main render components exposed to be overridden.

Welcoming interface comments.

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented May 6, 2025

The things I'd do differently in the interface:

  • Rename showOn and onSelect to $showOn and $onSelect to make it clear that they are called with editor context (I think the onSelect implementation is currently broken in this refactor for this reason, the delete node code depends on editor context)
  • Unify options and conditionalOptions, there doesn't really seem to be a reason to have them be separate and by having a single array you can control the exact order

As far as the implementation goes, just some things I'd simplify:

  • I don't think it's useful to bind showOn and onSelect to this because the ContextMenuOption class isn't exported and there's not any particularly useful state in there. If they wanted it bound to something they would probably prefer the object that it was defined in (per the constructor) and the caller could do that binding themselves if they really wanted to.
  • I generally don't like cloneElement, especially when not used to facilitate an eDSL where the user is specifying the children. This code would be simpler if the items array was props and then you can do the <ContextMenuItem {...getItemProps({ ...props, onClick() { props.onClick(); setIsOpen(false); }, /* … */ } })} /> instead of going through the trouble to create the JSX to turn it back into props to create the JSX again.

@ivailop7
Copy link
Copy Markdown
Collaborator Author

ivailop7 commented May 6, 2025

The things I'd do differently in the interface:

  • Rename showOn and onSelect to $showOn and $onSelect to make it clear that they are called with editor context (I think the onSelect implementation is currently broken in this refactor for this reason, the delete node code depends on editor context)

  • Unify options and conditionalOptions, there doesn't really seem to be a reason to have them be separate and by having a single array you can control the exact order

As far as the implementation goes, just some things I'd simplify:

  • I don't think it's useful to bind showOn and onSelect to this because the ContextMenuOption class isn't exported and there's not any particularly useful state in there. If they wanted it bound to something they would probably prefer the object that it was defined in (per the constructor) and the caller could do that binding themselves if they really wanted to.

  • I generally don't like cloneElement, especially when not used to facilitate an eDSL where the user is specifying the children. This code would be simpler if the items array was props and then you can do the <ContextMenuItem {...getItemProps({ ...props, onClick() { props.onClick(); setIsOpen(false); }, /* … */ } })} /> instead of going through the trouble to create the JSX to turn it back into props to create the JSX again.

Great! Thanks! I'll fix those up. The reason I split the conditional and default options was, that conditional options was intended as on object offering multiple items per 1 rule eval, vs 1 rule for 1 option, but forgot to do it.
I'll also think of moving the Item component to the main menu, will help me when I unify some of the internals with the other menus for later.

One last question I have is, should I add a new plugin in the react package and export ContextMenu2 and make this opt-in and make the breaking change later when we have all menus migrated vs now.

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented May 6, 2025

Maybe call it NodeContextMenu and deprecate the other one?

@etrepum
Copy link
Copy Markdown
Collaborator

etrepum commented May 27, 2025

It seems that there's something in here that causes the collab tests to fail a lot more frequently, I restarted the tests and there are still a bunch of failures

@ivailop7
Copy link
Copy Markdown
Collaborator Author

It seems that there's something in here that causes the collab tests to fail a lot more frequently, I restarted the tests and there are still a bunch of failures

Yeah, consistently pass locally, but not in CI, will play with a diff playwright syntax, maybe that's the key. Will redo the tests tonight.

Copy link
Copy Markdown
Collaborator

@etrepum etrepum left a comment

Choose a reason for hiding this comment

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

I think this looks good but we should add some API docs to LexicalNodeContextMenuPlugin.

Maybe the ContextMenu export should be renamed to NodeContextMenuPlugin? It doesn't really follow the naming convention of the other plugin modules where something with the same name as the module (sans the Lexical prefix) is exported

@ivailop7
Copy link
Copy Markdown
Collaborator Author

ivailop7 commented May 31, 2025

I think this looks good but we should add some API docs to LexicalNodeContextMenuPlugin.

Maybe the ContextMenu export should be renamed to NodeContextMenuPlugin? It doesn't really follow the naming convention of the other plugin modules where something with the same name as the module (sans the Lexical prefix) is exported

Happy to rename it, I want to complete the LexicalMenu migration for all menus and then I'll do API docs in a separate PR.
Also, thanks for the tests fix.

@ivailop7 ivailop7 added this pull request to the merge queue May 31, 2025
Merged via the queue into facebook:main with commit 6b0dd45 May 31, 2025
39 checks passed
fantactuka pushed a commit that referenced this pull request Aug 11, 2025
GermanJablo added a commit to payloadcms/payload that referenced this pull request Sep 3, 2025
Fixes #13386

Below I write a clarification to copy and paste into the release note,
based on our latest upgrade of Lexical [in
v3.29.0](https://github.com/payloadcms/payload/releases/tag/v3.29.0).

## Important
This release upgrades the lexical dependency from 0.28.0 to 0.34.0.

If you installed lexical manually, update it to 0.34.0. Installing
lexical manually is not recommended, as it may break between updates,
and our re-exported versions should be used. See the [yellow banner
box](https://payloadcms.com/docs/rich-text/custom-features) for details.

If you still encounter richtext-lexical errors, do the following, in
this order:

- Delete node_modules
- Delete your lockfile (e.g. pnpm-lock.json)
- Reinstall your dependencies (e.g. pnpm install)

### Lexical Breaking Changes

The following Lexical releases describe breaking changes. We recommend
reading them if you're using Lexical APIs directly
(`@payloadcms/richtext-lexical/lexical/*`).

- [v.0.33.0](https://github.com/facebook/lexical/releases/tag/v0.33.0)
- [v.0.30.0](https://github.com/facebook/lexical/releases/tag/v0.30.0)
- [v.0.29.0](https://github.com/facebook/lexical/releases/tag/v0.29.0)

___

TODO:
- [x] facebook/lexical#7719
- [x] facebook/lexical#7362
- [x] facebook/lexical#7707
- [x] facebook/lexical#7388
- [x] facebook/lexical#7357
- [x] facebook/lexical#7352
- [x] facebook/lexical#7472
- [x] facebook/lexical#7556
- [x] facebook/lexical#7417
- [x] facebook/lexical#1036
- [x] facebook/lexical#7509
- [x] facebook/lexical#7693
- [x] facebook/lexical#7408
- [x] facebook/lexical#7450
- [x] facebook/lexical#7415
- [x] facebook/lexical#7368
- [x] facebook/lexical#7372
- [x] facebook/lexical#7572
- [x] facebook/lexical#7558
- [x] facebook/lexical#7613
- [x] facebook/lexical#7405
- [x] facebook/lexical#7420
- [x] facebook/lexical#7662

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211202581885926
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.

3 participants