slate icon indicating copy to clipboard operation
slate copied to clipboard

Editor Loses Focus on Tab – Must Click to Type

Open babalugats76 opened this issue 4 years ago • 5 comments

Do you want to request a feature or report a bug?

Bug? User Error?

What's the current behavior?

When tabbing into Slate's editor, the focus appears and then disappears.

tab-focus-issue

Recreate using this minimally viable example: CodeSandbox - Slate 0.58.1 - Tab Focus Issue

  1. Set focus to first editor
  2. Type "Mama said knock you out"
  3. Tab (to second editor)
  4. Try #2

You will not be able to immediately type into the second editor; the focus will appear to be "robbed" from it and you will need to click inside in order to enter content.

Slate: 0.58.1 Browser: Chrome OS: Windows

What's the expected behavior?

Should work much like its plain ole HTML cousin: Tab Focus Issue - Pure HTML

Upon tabbing in, focus should be preserved, allowing the end user to type immediately into the editor, i.e., without clicking into it first!

babalugats76 avatar May 21 '20 21:05 babalugats76

Seems suspiciously similar to #3634 (if not a duplicate).

In both cases:

  • Multiple editors are involved
  • The first editor works as expected
  • If the first editor has previously received focus, subsequent editors require explicit clicking or multiple focus() calls to allow typing

babalugats76 avatar May 23 '20 04:05 babalugats76

@babalugats76 I figured something out!

TL;DR; Workaround: Make sure that your onChange handler does not cause a re-render if the Slate value has not changed.

I've been struggling with a similar issue. Switching from one editor to another, results in the second editor to be focused but with no selection/cursor (user has to click it again to actually be able to type). It doesn't happen between all editors though, which was puzzling.

I found a way to make your example work. See this CodeSandbox

All I changed was making your handleEditorChange function return prevState if the value didn't change:

  const handleEditorChange = (value, field) => {
    setState((prevState) => {
+      if (prevState[field].value === value) {
+        return prevState;
+      }
      return {
        ...prevState,
        [field]: {
          ...prevState[field],
          value: value
        }
      };
    });
  };

Your example's handleEditorChange would always return a new state object, which makes the <TabIssue> component re-render even if the value didn't change. With my patch above, it will not re-render since the same state object is returned (React knows it doesn't need to re-render).

I made 2 even simpler examples to demonstrate the issue:

In Example 1 tabbing/clicking between the 2 editors works fine. It uses a state field for the root children array:

const [value1, setValue1] = useState([
  { type: "paragraph", children: [{ text: "" }] }
]);
// ...
<Slate editor={editor1} onChange={setValue1} value={value1}>
// ...

Notice that when onChange/setValue1 is called, React will only re-render the <App> component if the children array actually changed.

Example 2 has the same issue as your example. It wraps the root children array in an object:

const [value1, setValue1] = useState({
  wrapped: [{ type: "paragraph", children: [{ text: "" }] }]
});
// ...
<Slate
  editor={editor1}
  onChange={(wrapped) => setValue1({ wrapped })}
  value={value1.wrapped}
>
// ...

Notice that when onChange is called, React will always re-render the <App> component even if nothing changed, since it sets a new {wrapped} object.

I believe the issue occurs because blurring an editor also triggers it's onChange before the onChange is also triggered for the next editor when it gets focused. This means when editor 1 is focused and you tab (or click) to editor 2, then editor 1's state is first changed, which causes a re-render, and that somehow trips up Slate's ability to select anything in the second editor.

Not sure why... I'm looking through the Slate codebase to see if there's a possible fix so we don't need this workaround.

sebastianseilund avatar Nov 07 '20 21:11 sebastianseilund

The issue happens even with just 1 editor: If the <Slate> component's parent component is re-rendered in the same "tick"(?) as it gets focused. CodeSandbox example with just 1 editor

sebastianseilund avatar Nov 07 '20 22:11 sebastianseilund

@sebastianseilund Not sure about chromium but that fix doesn't work in firefox

iSplasher avatar Feb 24 '21 12:02 iSplasher

It is the default behavior of Tab, you can prevent in onKyedown handler

const onKeyDown = (e) => {
    if (e.key === "Tab") {
      e.preventDefault();
    }
  };
<Slate
     ......
      >
        <Editable
          onKeyDown={onKeyDown}
          ......
        />
      </Slate>

WenWenBo avatar Apr 04 '23 01:04 WenWenBo