facebook / lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.
https://lexical.dev
MIT License
17.5k stars 1.45k forks source link

Not working with Next.js #4924

Open osvald0 opened 8 months ago

osvald0 commented 8 months ago

I've used the plain example code in a next.js but it isn't working. Although it has some minor updates to adapt it to next.js the code is exact the same that the code in the codesandbox. I can't see any error, just the text area input is not editable. Can you tell me if I'm missing something?

Lexical version: 0.12.0

Steps To Reproduce

  1. Run the project
  2. Try to edit the text area input.

Link to code example:

github

The current behavior

The text are input is not editable.

The expected behavior

The text are input should be editable.

zignis commented 8 months ago

It's editable. The problem is that you might be clicking on the placeholder, which is a relatively positioned element as you have not added any styles to it. The actual content editable element sits just before the placeholder. Style the placeholder with position absolute and pointer-events: none so that the placeholder sits on the top of the content editable, like this: https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/ui/Placeholder.css

osvald0 commented 8 months ago

Thanks for you response. The placeholder style is there.

Screenshot 2023-08-27 at 01 53 32

zignis commented 8 months ago

Thanks for you response. The placeholder style is there.

Screenshot 2023-08-27 at 01 53 32

The content editable has contenteditable="false", make sure you pass editable: true in your editor config (passed to the composer)

osvald0 commented 8 months ago

Yes but it keeps the "false" value even when I pass contentEditable={true} to the ContentEditable lexical component.

'use client'

import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import TreeViewPlugin from "./plugins/TreeViewPlugin";
import EmoticonPlugin from "./plugins/EmoticonPlugin";
import MyCustomAutoFocusPlugin from "./plugins/MyCustomAutoFocusPlugin";
import editorConfig from "./editorConfig";
import onChange from "./onChange";

export default function Editor() {
  return (
    <LexicalComposer initialConfig={editorConfig}>
      <div className="editor-container">
        <PlainTextPlugin
          contentEditable={<ContentEditable contentEditable={true} className="editor-input" />}
          placeholder={<Placeholder />}
          ErrorBoundary={LexicalErrorBoundary}
        />
        <OnChangePlugin onChange={onChange} />
        <HistoryPlugin />
        <TreeViewPlugin />
        <EmoticonPlugin />
        <MyCustomAutoFocusPlugin />
      </div>
    </LexicalComposer>
  );
}

function Placeholder() {
  return <div className="editor-placeholder">Enter some plain text...</div>;
}
zignis commented 8 months ago

You have to pass editable: true in your editorConfig.js as such:

import { EmojiNode } from "./nodes/EmojiNode";
import ExampleTheme from "./themes/ExampleTheme";

const editorConfig = {
  theme: ExampleTheme,
  editable: true, // this make the editor editable
  onError(error) {
    throw error;
  },
  nodes: [EmojiNode]
};

export default editorConfig;
osvald0 commented 8 months ago

Is it working from your side? Because it doesn't work from my side.

zignis commented 8 months ago

I've created a codesandbox based off your updated repository and it's working fine on my side: https://codesandbox.io/p/github/zignis/nextjs-lexical-test-js

osvald0 commented 8 months ago

It's weird because it's working fine in codesandbox even if you don't add editable: true to the config.

zignis commented 8 months ago

I just dug into the source and found out that the editor is editable by default: https://github.com/facebook/lexical/blob/fa68893288693cb9be0b0b5d868b3f02fee14b3b/packages/lexical/src/LexicalEditor.ts#L395

The codesandbox is working fine, on my side, with and without editable: true. Can you please provide a reproducible example if it still doesn't work for you?

osvald0 commented 8 months ago

Yes the github example project I already shared is not working from my side when I run it locally.

👉 Link

Thanks for your help!

zignis commented 8 months ago

Seems like an issue with your browser, did you encounter any errors? Would you mind sharing your browser details? The implementation in your repo is working fine on my browser (tested on Firefox 117.0 and Chromium 116.0)

osvald0 commented 8 months ago

I tested it with Safari, Chrome and Firefox with the same result.

Screenshot 2023-08-27 at 13 19 12

Screenshot 2023-08-27 at 13 17 27

zignis commented 8 months ago

hmm interesting, this codesandbox created from your repo works fine for me. Lets wait for someone form the core team.

osvald0 commented 8 months ago

Thanks a lot for try to help me. My problem is only with Next because If I try the same code but in a project with react and vite it works fine.

dirheimerb commented 7 months ago

I initially had issues with Next. The problem is, the editor can't be SSR. So to fix this issue I did this:

import dynamic from 'next/dynamic';
const EditorContainer = dynamic(
  () => import('@/components/EditorComponent/EditorContainer'),
  { ssr: false },
);

Hope that helps!

Xananax commented 4 months ago

Using Next 14.

I found that it works (without dynamic imports) as long as you do not use placeholder. So for example, this works:

export function Editor({ markdown }: { markdown: string }) {
  const initialConfig: InitialConfigType = {
    nodes,
    editorState: () => {
      $convertFromMarkdownString(markdown, TRANSFORMERS);
    },
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <AutoLinkPlugin />
      <LinkPlugin />
      <CodeHighlightPlugin />
      <RichTextPlugin
        contentEditable={<ContentEditable />}
        placeholder={null}
        ErrorBoundary={LexicalErrorBoundary}
      />
      <MarkdownShortcutPlugin />
      <FloatingTextFormatToolbarPlugin />
    </LexicalComposer>
  );
}

But you get the initially collapsed content boxes as described in https://github.com/facebook/lexical/issues/4960

To work around that, I made my own placeholder plugin:

import { useEffect, useState } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";

/**
 * The placeholder feature of RichText/PlainText of lexical has hydrations issues with SSR.
 * This is a quick hack to show something initially and then hide it as soon as the editor
 * loads.
 */
export function MarkdownPlaceHolderPlugin({ children }: { children: React.ReactNode }) {
  const [editor] = useLexicalComposerContext();
  const [show, setShow] = useState(true);

  useEffect(() => {
    setShow(false);
  }, [editor]);

  return show ? <pre style={{ whiteSpace: "pre-wrap" }}>{children}</pre> : null;
}

And then use it:

export function Editor({ markdown }: { markdown: string }) {
  const initialConfig: InitialConfigType = {
    nodes,
    editorState: () => {
      $convertFromMarkdownString(markdown, TRANSFORMERS);
    },
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      {/* snip - same plugins as before */}
      <MarkdownPlaceHolderPlugin>{markdown}</MarkdownPlaceHolderPlugin>
    </LexicalComposer>
  );
}