question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Issue related to native `onBlur` event handling

See original GitHub issue

Let me start introducing a notation. By edit island I mean a DOM subtree with a structure similar to

<div contentEditable={false}>
  <div>Not Editable Content</div>
  <EditableElement />
</div>

where EditableElement is an element which contains an editable text. It can be a native element, like inputor textarea, a javascript based editor different from Slate or a collection of Slate nodes.

The code responsible to handle browser onBlur and onFocus events has been rewritten a lot of times. Every time, one issue is fixed and two or more are introduced. 😉

In the past, I made two changes to onBlur and onFocus (PR #693 and #692) to fix keyboard navigation issues in presence of edit islands with editable elements consisting of Slate block collections. After commit 619870808699ed853ca153419e616b764c639762 an issue (#749) concerning embedded input has been opened, the handler code has been modified to fix that bug, but this has raised again the problems I managed to solve with my PRs.

Here, I’ll try to show what is the root of these issues.

There are three main different (from the Slate point of view) cases in which browsers fire blur events.

  1. The focus switches from within the Slate Editor to an element not contained into the editor DOM subtree.

  2. The Slate editor DOM subtree contains an edit island and the cursor goes from an element within the editor subtree but outside the edit island to an element inside the edit island (or vice versa).

  3. The document visibilityState changes to hidden.

The case gives us headaches is the second.

The task of the handlers is to decide if the focus state of the Slate editor must change or not in response to blur and focus events fired by the browser.

All the versions of the onBlur event handling code take this decision based on the relative inclusion relations among the element losing focus, the one that gains focus and the editor container. This is a reasonable choice but it can’t always give the right answer: there are cases in which the relations are the same but the editor must have different behaviors.

For example, let’s consider two Slate editor instances containing custom blocks with edit islands. Let the island editable elements be:

  • an input element in the first case
  • a collection of Slate blocks in the second.

When the caret entering the edit island triggers the onBlur event, the relative inclusion relations among the elements losing and gaining the focus and the editor container are the same but in the first case the editor must be blurred because it is no longer responsible of the editing process while in the second case it has to retain its focus state since it is still Slate that must take care of the editing.

So, what really matters is to establish if the element which gains focus is still managed by Slate or not. But this is impossible to do at level of Content component, simply, because it does not have this info. Only the author of a custom node knows for sure if it uses an external tool or not to handle editing.

I think the best we can do is to adopt a clear policy.

My propose is this. When the editor main component (Content) loses focus in favour of an element belonging to its subtree, Slate should ignore the onBlur event and leave the author of the custom node to put the editor in read-only mode or change its focus state as needed to avoid unwanted interferences.

Of course, authors must be informed and to this aim we have to add a clear statement about this policy in the doc section dedicated to custom nodes.

Moreover, according to this policy, we have to add to the input element of the video component in the Embeds example an onFocus handler like this:

onFocus = (e) => {
  const { state, editor } = this.props
  const next = state
    .transform()
    .blur()
    .apply()
  e.stopPropagation()
  editor.onChange(next)
}

If you agree, I can make a PR implementing these changes. 😊

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
ianstormtaylorcommented, May 31, 2017

Hmm gotcha! I think using an attribute might actually end up less confusing, because for managing focus/blur it sounds like people will need to understand a lot more about how the DOM events interact. Which isn’t ideal I think.

But that is true that neither solution is great. I’d be open to an attribute I think.

1reaction
AlbertHilbcommented, May 31, 2017

@ianstormtaylor

In the collection of Slate blocks case, is that inside a second, separate Slate editor in a isVoid: true element of the first?

No, the collection of blocks is inside a container block with isVoid: false. Something like

{
  "kind": "block",
  "type": "environment",
  "data": {
    "envName": "definition"
  },
  nodes: [
    {
      "kind": "block",
      "type": "default",
      "nodes": [
        {
          "kind": "text",
          "text": "First paragraph of a math object definition."
        }
      ]
    },
    {
      "kind": "block",
      "type": "default",
      "nodes": [
        {
          "kind": "text",
          "text": "Second paragraph of a math object definition."
        }
      ]
    }
  ]
}

The container block is rendered essentially as

<section contentEditable={false}>
  <header>Definition.</header>
  <div contentEditable={true}  suppressContentEditableWarning>
    {props.children}
  </div>
</section>

So the content of the container can be edited while the header remains read-only. Here is a live sample.

in the case we can surely detect that one

Yes, you are right. When the editable element of the island is an input or a textarea, it is easy to detect from the focus target nature that the editor must be blurred. But in the general case is less simple. How can we distinguish, for example, the case in which the editable element is a div with contentEditable={true} containing a Slate block collection like the one above, from the case in which the editable element is still a div with contentEditable={true} but it is managed by an external tool (CodeMirror for example). Yet distinguishing this two cases is essential because when the caret enters the editable div Slate should retain the focus in the former case whereas it should be blurred in the latter.

Of course, we can try to guess or ask authors to give us an hint, for example adding an attribute to editable element inside the island. But I think it is safer to ask authors to put the Slate editor in the right focus state by themselves when needed. Isn’t for this blur and focus transforms exist? 😊

Read more comments on GitHub >

github_iconTop Results From Across the Web

Element: blur event - Web APIs | MDN
The blur event fires when an element has lost focus. The event does not bubble, but the related focusout event that follows does...
Read more >
onblur Event
The onblur event occurs when an element loses focus. ... The onblur event is often used with form validation (when the user leaves...
Read more >
javascript - React and blur event
The question is: How can I get my onBlur method fire only when the focus gets out of the table? IOW: How can...
Read more >
Catching the blur event on an element and its children
When user is tabbing between these menu items, blur event is triggered every time on the parent, followed by the focus event on...
Read more >
The difference between onBlur vs onChange for React text ...
What is onBlur event in React ... React onBlur behaves just like the native JavaScript version of blur. ... Every time you get...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found