Lexical

Lexical is an extensible text editor framework that provides excellent reli...

README

Lexical

GitHub Workflow Status Visit the NPM page Add yourself to our Discord Follow us on Twitter


Lexical is an extensible JavaScript web text-editor framework with an emphasis on reliability, accessibility, and performance. Lexical aims to provide a best-in-class developer experience, so you can easily prototype and build features with confidence. Combined with a highly extensible architecture, Lexical allows developers to create unique text editing experiences that scale in size and functionality.

For documentation and more information about Lexical, be sure to visit the Lexical website.

Here are some examples of what you can do with Lexical:




Overview:






Getting started with React


Note: Lexical is not only limited to React. Lexical can support any underlying DOM based library once bindings for that library have been created.


Install lexical and @lexical/react:

  1. ```
  2. npm install --save lexical @lexical/react
  3. ```

Below is an example of a basic plain text editor using lexical and @lexical/react (try it yourself).

  1. ``` js
  2. import {$getRoot, $getSelection} from 'lexical';
  3. import {useEffect} from 'react';

  4. import {LexicalComposer} from '@lexical/react/LexicalComposer';
  5. import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
  6. import {ContentEditable} from '@lexical/react/LexicalContentEditable';
  7. import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
  8. import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
  9. import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
  10. import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';

  11. const theme = {
  12.   // Theme styling goes here
  13.   // ...
  14. }

  15. // When the editor changes, you can get notified via the
  16. // LexicalOnChangePlugin!
  17. function onChange(editorState) {
  18.   editorState.read(() => {
  19.     // Read the contents of the EditorState here.
  20.     const root = $getRoot();
  21.     const selection = $getSelection();

  22.     console.log(root, selection);
  23.   });
  24. }

  25. // Lexical React plugins are React components, which makes them
  26. // highly composable. Furthermore, you can lazy load plugins if
  27. // desired, so you don't pay the cost for plugins until you
  28. // actually use them.
  29. function MyCustomAutoFocusPlugin() {
  30.   const [editor] = useLexicalComposerContext();

  31.   useEffect(() => {
  32.     // Focus the editor when the effect fires!
  33.     editor.focus();
  34.   }, [editor]);

  35.   return null;
  36. }

  37. // Catch any errors that occur during Lexical updates and log them
  38. // or throw them as needed. If you don't throw them, Lexical will
  39. // try to recover gracefully without losing user data.
  40. function onError(error) {
  41.   console.error(error);
  42. }

  43. function Editor() {
  44.   const initialConfig = {
  45.     namespace: 'MyEditor',
  46.     theme,
  47.     onError,
  48.   };

  49.   return (
  50.     <LexicalComposer initialConfig={initialConfig}>
  51.       <PlainTextPlugin
  52.         contentEditable={<ContentEditable />}
  53.         placeholder={<div>Enter some text...</div>}
  54.         ErrorBoundary={LexicalErrorBoundary}
  55.       />
  56.       <OnChangePlugin onChange={onChange} />
  57.       <HistoryPlugin />
  58.       <MyCustomAutoFocusPlugin />
  59.     </LexicalComposer>
  60.   );
  61. }
  62. ```

Lexical is a framework


The core of Lexical is a dependency-free text editor framework that allows developers to build powerful, simple and complex,
editor surfaces. Lexical has a few concepts that are worth exploring:

Editor instances


Editor instances are the core thing that wires everything together. You can attach a contenteditable DOM element to editor instances, and also
register listeners and commands. Most importantly, the editor allows for updates to its EditorState. You can create an editor instance
using the createEditor() API, however you normally don't have to worry when using framework bindings such as @lexical/react as this
is handled for you.

Editor States


An Editor State is the underlying data model that represents what you want to show on the DOM. Editor States contain two parts:

- a Lexical node tree
- a Lexical selection object

Editor States are immutable once created, and in order to create one, you must do so via editor.update(() => {...}). However, you
can also "hook" into an existing update using node transforms or command handlers – which are invoked as part of an existing update
workflow to prevent cascading/waterfalling of updates. You can retrieve the current editor state using editor.getEditorState().

Editor States are also fully serializable to JSON and can easily be serialized back into the editor using editor.parseEditorState().

Editor Updates


When you want to change something in an Editor State, you must do it via an update, editor.update(() => {...}). The closure passed
to the update call is important. It's a place where you have full "lexical" context of the active editor state, and it exposes
access to the underlying Editor State's node tree. We promote using $ prefixed functions in this context, as it signifies a place
where they can be used exclusively. Attempting to use them outside of an update will trigger a runtime error with an appropriate error.
For those familiar with React Hooks, you can think of these as having a similar functionality (except $ functions can be used in any order).

DOM Reconciler


Lexical has its own DOM reconciler that takes a set of Editor States (always the "current" and the "pending") and applies a "diff"
on them. It then uses this diff to update only the parts of the DOM that need changing. You can think of this as a kind-of virtual DOM,
except Lexical is able to skip doing much of the diffing work, as it knows what was mutated in a given update. The DOM reconciler
adopts performance optimizations that benefit the typical heuristics of a content editable – and is able to ensure consistency for
LTR and RTL languages automatically.

Listeners, Node Transforms and Commands


Outside of invoking updates, the bulk of work done with Lexical is via listeners, node transforms and commands. These all stem from
the editor and are prefixed with register. Another important feature is that all the register methods return a function to easily unsubscribe them. For example here is how you listen to an update to a Lexical editor:

  1. ``` js
  2. const unregisterListener = editor.registerUpdateListener(({editorState}) => {
  3.   // An update has occurred!
  4.   console.log(editorState);
  5. });

  6. // Ensure we remove the listener later!
  7. unregisterListener();
  8. ```

Commands are the communication system used to wire everything together in Lexical. Custom commands can be created using createCommand() and
dispatched to an editor using editor.dispatchCommand(command, payload). Lexical dispatches commands internally when key presses are triggered
and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority), and incoming commands are
propagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).

Working with Lexical


This section covers how to use Lexical, independently of any framework or library. For those intending to use Lexical in their React applications,
it's advisable to [check out the source-code for the hooks that are shipped in @lexical/react](https://github.com/facebook/lexical/tree/main/packages/lexical-react/src).

Creating an editor and using it


When you work with Lexical, you normally work with a single editor instance. An editor instance can be thought of as the one responsible
for wiring up an EditorState with the DOM. The editor is also the place where you can register custom nodes, add listeners, and transforms.

An editor instance can be created from the lexical package and accepts an optional configuration object that allows for theming and other options:

  1. ``` js
  2. import {createEditor} from 'lexical';

  3. const config = {
  4.   namespace: 'MyEditor',
  5.   theme: {
  6.     ...
  7.   },
  8. };

  9. const editor = createEditor(config);
  10. ```

Once you have an editor instance, when ready, you can associate the editor instance with a content editable `
` element in your document:

  1. ``` js
  2. const contentEditableElement = document.getElementById('editor');

  3. editor.setRootElement(contentEditableElement);
  4. ```

If you want to clear the editor instance from the element, you can pass null. Alternatively, you can switch to another element if need be,
just pass an alternative element reference to setRootElement().

Working with Editor States


With Lexical, the source of truth is not the DOM, but rather an underlying state model
that Lexical maintains and associates with an editor instance. You can get the latest
editor state from an editor by calling editor.getEditorState().

Editor states are serializable to JSON, and the editor instance provides a useful method
to deserialize stringified editor states.

  1. ``` js
  2. const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());

  3. const newEditorState = editor.parseEditorState(stringifiedEditorState);
  4. ```

Updating an editor


There are a few ways to update an editor instance:

- Trigger an update with editor.update()
- Setting the editor state via editor.setEditorState()
- Applying a change as part of an existing update via editor.registerNodeTransform()
- Using a command listener with editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority)

The most common way to update the editor is to use editor.update(). Calling this function
requires a function to be passed in that will provide access to mutate the underlying
editor state. When starting a fresh update, the current editor state is cloned and
used as the starting point. From a technical perspective, this means that Lexical leverages a technique
called double-buffering during updates. There's an editor state to represent what is current on
the screen, and another work-in-progress editor state that represents future changes.

Creating an update is typically an async process that allows Lexical to batch multiple updates together in
a single update – improving performance. When Lexical is ready to commit the update to
the DOM, the underlying mutations and changes in the update will form a new immutable
editor state. Calling editor.getEditorState() will then return the latest editor state
based on the changes from the update.

Here's an example of how you can update an editor instance:

  1. ``` js
  2. import {$getRoot, $getSelection, $createParagraphNode} from 'lexical';

  3. // Inside the `editor.update` you can use special $ prefixed helper functions.
  4. // These functions cannot be used outside the closure, and will error if you try.
  5. // (If you're familiar with React, you can imagine these to be a bit like using a hook
  6. // outside of a React function component).
  7. editor.update(() => {
  8.   // Get the RootNode from the EditorState
  9.   const root = $getRoot();

  10.   // Get the selection from the EditorState
  11.   const selection = $getSelection();

  12.   // Create a new ParagraphNode
  13.   const paragraphNode = $createParagraphNode();

  14.   // Create a new TextNode
  15.   const textNode = $createTextNode('Hello world');

  16.   // Append the text node to the paragraph
  17.   paragraphNode.append(textNode);

  18.   // Finally, append the paragraph to the root
  19.   root.append(paragraphNode);
  20. });
  21. ```

If you want to know when the editor updates so you can react to the changes, you can add an update
listener to the editor, as shown below:

  1. ``` js
  2. editor.registerUpdateListener(({editorState}) => {
  3.   // The latest EditorState can be found as `editorState`.
  4.   // To read the contents of the EditorState, use the following API:

  5.   editorState.read(() => {
  6.     // Just like editor.update(), .read() expects a closure where you can use
  7.     // the $ prefixed helper functions.
  8.   });
  9. });
  10. ```

Creating custom Lexical nodes



Contributing to Lexical


1. Clone this repository

2. Install dependencies

   - npm install

3. Start local server and run tests
   - npm run start
   - npm run test-e2e-chromium to run only chromium e2e tests
     - The server needs to be running for the e2e tests

npm run start will start both the dev server and collab server. If you don't need collab, use npm run dev to start just the dev server.

Optional but recommended, use VSCode for development


1.  Download and install VSCode

    - Download from here (it’s recommended to use the unmodified version)

2.  Install extensions
      - Make sure to follow the setup steps in the README
    - Prettier
      - Set prettier as the default formatter in editor.defaultFormatter
      - Optional: set format on save editor.formatOnSave
    - ESlint

Documentation



Browser Support


- Firefox 52+
- Chrome 49+
- Edge 79+ (when Edge switched to Chromium)
- Safari 11+
- iOS 11+ (Safari)
- iPad OS 13+ (Safari)
- Android Chrome 72+

Note: Lexical does not support Internet Explorer or legacy versions of Edge.

Contributing


1. Create a new branch
   - git checkout -b my-new-branch
2. Commit your changes
   - git commit -a -m 'Description of the changes'
     - There are many ways of doing this and this is just a suggestion
3. Push your branch to GitHub
   - git push origin my-new-branch
4. Go to the repository page in GitHub and click on "Compare & pull request"
   - The GitHub CLI allows you to skip the web interface for this step (and much more)

Support


If you have any questions about Lexical, would like to discuss a bug report, or have questions about new integrations, feel free to join us at our Discord server.

Lexical engineers are checking this regularly.

Running tests


- npm run test-unit runs only unit tests.
- npm run test-e2e-chromium runs only chromium e2e tests.
- npm run debug-test-e2e-chromium runs only chromium e2e tests in head mode for debugging.
- npm run test-e2e-firefox runs only firefox e2e tests.
- npm run debug-test-e2e-firefox runs only firefox e2e tests in head mode for debugging.
- npm run test-e2e-webkit runs only webkit e2e tests.
- npm run debug-test-e2e-webkit runs only webkit e2e tests in head mode for debugging.

License


Lexical is MIT licensed.