lexical
lexical copied to clipboard
Bug: Cannot escape lists with Enter key
Lexical version: 0.12
Steps To Reproduce
- Create a list in RichTextPlugin
- Press Enter twice. The list continues forever; there is no way to escape it Expected: Pressing enter in an empty list reduces the list by 1 level (or exits it when it's the first level)
Demo: https://codesandbox.io/s/lexical-plain-text-example-forked-9lq9y8
(This bug was previously reported and closed, but it appears to have reoccured: https://github.com/facebook/lexical/issues/4266)
@pistachiomatt try to import ListPlugin. Tested and it worked for me here! I hope it helps.
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import editorConfig from "./editorConfig";
// New component
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
export default function Editor() {
return (
<LexicalComposer initialConfig={editorConfig}>
<div className="editor-container">
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
/>
{/* New Component */}
<ListPlugin />
</div>
</LexicalComposer>
);
}
function Placeholder() {
return <div className="editor-placeholder">Enter some plain text...</div>;
}
Thanks! It works. A bit strange that a plugin is required to fix a bug. Perhaps this ticket should be to merge the plugin into core.
If lists are in your editor, you already have it imported, so I highly doubt that's the reason for it, especially since I'm also seeing this.
I had the same issue, but only rarely, and there is no difference between the extracted jsons between a list that works correctly with the enter key, and one that creates infinite lists and can't be escaped. Honestly unsure what could be causing it, but it does seem very strange overall, and I can't even provide a repro with a codebase, because I can't pin down the exact reason it is happening. I literally copy-pasted the content of one editor where the bug appeared into another and it no longer showed up.
I have this issue as well, im using Vanilla js, i cannot exit the list with double enter keypress
I also encountered this bug.
It seems that the issue was caused by inadvertently adding libraries that are already defined by default within 'lexical', such as 'yarn add @lexical/code @lexical/markdown'.
Once I removed all lexical libraries other than 'lexical' and '@lexical/react', everything started working smoothly.
I am experiencing this too.
I followed what @Atsuyoshi-Funahashi mentioned, removing all other lexical libraries, which may have had an impact as I'm sure I've seen it working on my implementation. But, I cannot seem to get it to work at all now. I'm sure I did have it working before.
I'll continue to dig and I will feedback if I can work out what the cause is.
Ok, interestingly when I render my component separately in Storybook I don't get this issue occurring. However I do when it's embedded in my application. In storybook I am just using my WYSIWYGEditor component, whereas In my application I am wrapping the editor in a react-hook-form Controller to set the value on change with some debounce logic, like so:
import { Control, Controller } from "react-hook-form";
import { Item } from "../../../external";
import WYSIWYGEditor from "../../wysiwyg";
export interface WYSIWYGEditorProps {
item: Item;
isSaving: boolean;
viewOnly: boolean;
control: Control<Item>;
}
const ItemDescription = ({
isSaving,
viewOnly,
control,
item,
}: WYSIWYGEditorProps): JSX.Element => {
return (
<>
<Controller
name="description"
control={control}
render={({
field: { onChange, value, name },
}) => {
return (
<WYSIWYGEditor
isSaving={isSaving}
setValue={onChange}
value={value}
viewOnly={viewOnly}
namespace={`${item.itemId}${name}`}
/>
);
}}
></Controller>
</>
);
};
export default ItemDescription;
I've nailed it down to, it only happens when I pre-populate the editor with the initial value, with a list in it.
My code to load the initial value is a plugin that I've added:
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $generateNodesFromDOM } from "@lexical/html";
import { $createParagraphNode, $getRoot } from "lexical";
type LoadInitialValuePluginProps = {
namespace: string;
initialValue?: string;
};
export function LoadInitialValuePlugin({
initialValue,
}: LoadInitialValuePluginProps) {
const [editor] = useLexicalComposerContext();
const [hasWritten, setHasWritten] = useState(false);
// Run useEffect with no dependencies - this will only run at load
useEffect(() => {
editor.update(
() => {
if (initialValue && !hasWritten) {
// Make this only run once
setHasWritten(true);
// In the browser you can use the native DOMParser API to parse the HTML string.
// Once you have the DOM instance it's easy to generate LexicalNodes.
const parser = new DOMParser();
const v = parser.parseFromString(initialValue, "text/html");
// Retrieve the root
const root = $getRoot();
// Clear the root
root.clear();
// Generate nodes from the DOM, which generates HTML nodes
const nodes = $generateNodesFromDOM(editor, v);
const para = $createParagraphNode();
para.append(...nodes);
root.append(para);
}
},
{
onUpdate: () => {
// Debugging purposes
console.info(editor.toJSON());
},
}
);
}, []);
return null;
}
This is how it looks in my editor:
This is the generated state:
Details
{
"children": [
{
"children": [
{
"children": [
{
"detail": 0,
"format": 1,
"mode": "normal",
"style": "",
"text": "Hello, this is a new description",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1
},
{
"children": [
{
"detail": 0,
"format": 2,
"mode": "normal",
"style": "",
"text": "This is some italic text",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1
},
{
"children": [
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "This is a list item",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 1
},
{
"children": [
{
"detail": 0,
"format": 0,
"mode": "normal",
"style": "",
"text": "This is another list item",
"type": "text",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "listitem",
"version": 1,
"value": 2
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "list",
"version": 1,
"listType": "number",
"start": 1,
"tag": "ol"
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "paragraph",
"version": 1
}
],
"direction": "ltr",
"format": "",
"indent": 0,
"type": "root",
"version": 1
}
I fixed it in my case now!
Content view:
Here is a diff between me entering content, and the initial load
The obvious thing is that it's wrapped in an extra paragraph.
I've updated my code that populates the initial value on load to no longer wrap the content in a paragraph.
My initial code is in my previous comment above - now my nodes are populated in the root with this:
const nodes = $generateNodesFromDOM(editor, v);
root.append(...nodes);
Now it all works as expected for me.
I feel like there might be a bug here, I'm not entirely sure why wrapping it in a paragraph would do this - there is potentially a bug in the function $handleListInsertParagraph in the lexical-list package. I will dig further into it when I have some more time.
I hope that helps someone else at least!
Edit
I Still having this problem on my app today.
And after I dig into the code compare to playground, I found out that I didn't import ListPlugin from "@lexical/react/LexicalListPlugin". After added this plugin I can escape list through Enter key.