facebook / lexical

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

Bug: Editor is focused when the initialConfig editorState converts from markdown and registerNodeTransform has been used #4474

Open darsee opened 1 year ago

darsee commented 1 year ago

In the codesandbox linked below, the editor is focused on mount when the initialConfig editorState converts a markdown heading and registerNodeTransform has been used. Is this expected?

Lexical version: 0.10.0

Steps To Reproduce

  1. Open the codesandbox below
  2. Observe that the editor is initially focused (cursor is visible)
  3. Change value to test (uncomment line 27)
  4. Reload the browser preview, observe that the editor is not focused (cursor is not visible)
  5. Change value back to # test and comment out <TestPlugin />
  6. Reload the browser preview, observe that the editor is not focused (cursor is not visible)

Link to code example:

https://codesandbox.io/s/lexical-editor-focused-after-node-transform-sfm1wn?file=/src/Editor.js

The current behavior

On reloading the browser preview, the editor is initially focused (cursor is visible).

The expected behavior

I expect the editor not to be initially focused unless using @lexical/react/LexicalAutoFocusPlugin?

aquarius-wing commented 1 year ago

I fix it in PR

Hope it will help.

The code is small, you can edit file in your local file, which path is node_modules/@lexical/markdown/LexicalMarkdown.dev.js#290

After rerun the start, you can check it yourself.

darsee commented 1 year ago

Hey @aquarius-wing, thanks for the PR!

I don't think it solves the issue for me though - the problem in the scenario above is that the editor is focused when it loads but I am expecting it not to be focused until I click it. Your PR (if I understand correctly) causes the editor to be focused even if the markdown conversion doesn't match on anything - I'm not sure if this behaviour would be expected.

aquarius-wing commented 1 year ago

So, is it possible that I misunderstood your meaning? I thought you wanted the focus to be only when text is inputted. So, you mean the opposite, that even when '# text' is inputted, it should not be focused? I'm quite confused. 😂

darsee commented 1 year ago

even when '# text' is inputted, it should not be focused?

Yes, exactly 😄 I expect the editor not to be focused until I click it.

aquarius-wing commented 1 year ago

I feel that it should be up to the lexical official to determine whether a separate configuration item is needed to control whether to auto-focus.

danchurch-alphasights commented 1 year ago

Hi @darsee!

I ran into a very similar problem with $convertFromMarkdownString recently.

I was able to work around it by:

useEffect(() => {
  // Set the editor to not editable - it won't take focus while in that state
  editor.setEditable(false);

  const unregister = editor.registerNodeTransform(TextNode, (textNode) => {
    console.log("node transformed", textNode);
  });

  // Set the editor back to editable a frame later
  requestAnimationFrame(() => editor.setEditable(true));

  return unregister;
}, [editor]);
The pattern could be encapsulated in a function to use in multiple places
The code sandbox was in Javascript, so I put the types in comments ```Javascript const whileEditorDisabled = /* */(editor /* : LexicalEditor */, fn /* : () => R */) /* : R */ => { editor.setEditable(false); const value = fn(); requestAnimationFrame(() => editor.setEditable(true)); return value; }; ``` Usage: ```Javascript useEffect(() => { return whileEditorDisabled(editor, () => editor.registerNodeTransform( TextNode, (textNode) => console.log("node transformed", textNode) )); }, [editor]); ```

Using this pattern in the code sandbox seems to work!

Probably not a perfect solution, but hopefully it's able to help you with a short-term solution

darsee commented 1 year ago

Hey @danchurch-alphasights!

Thanks a lot for the input - this looks like a very clever workaround.

I need to see if I can apply it to my actual use case where registerNodeTransform is called inside useAutoLink in LexicalAutoLinkPlugin:

https://github.com/facebook/lexical/blob/5595519202f9241e3e2b8ab3b240c787c58484e2/packages/lexical-react/src/LexicalAutoLinkPlugin.ts#L293-L309

My current workaround is to conditionally use AutoLinkPlugin only if the editor is focused (which we track in state for other reasons anyway) but that feels very hacky.

vedkribhu commented 1 year ago

Any updates here? This is very irritating issue as registerNodeTransform is focusing the editor. The workaround mentioned above are hacky and leads to more issues where we have to track focus state of the editor to ensure consistent behaviour.

nikitastryuk commented 1 year ago

+1 looks like there is no good way to set initial value without focusing the editor

vmihda commented 7 months ago

Hey, try this

export const LoadInitialContent: React.FC<{ initHtml: string }> = ({ initHtml = '' }) => {
  const [editor] = useLexicalComposerContext();
  const isFirstRender = useRef(true);

  useLayoutEffect(() => {
    editor.update(() => {
      if (isFirstRender.current && !!initHtml) {
        isFirstRender.current = false;

        const parser = new DOMParser();
        const dom = parser.parseFromString(initHtml, 'text/html');
        const nodes = $generateNodesFromDOM(editor, dom);

        const root = $getRoot();
        root.clear();
        $setSelection(null);
        root.append(...nodes); 
        editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);
      }
    });
  }, [initHtml, isFirstRender.current]);

  return null;
};