Issue related to native `onBlur` event handling
See original GitHub issueLet 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 input
or 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.
-
The focus switches from within the Slate
Editor
to an element not contained into the editor DOM subtree. -
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).
-
The
document
visibilityState
changes tohidden
.
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:
- Created 6 years ago
- Reactions:1
- Comments:6 (5 by maintainers)
Top GitHub Comments
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.
@ianstormtaylor
No, the collection of blocks is inside a container block with
isVoid: false
. Something likeThe container block is rendered essentially as
So the content of the container can be edited while the header remains read-only. Here is a live sample.
Yes, you are right. When the editable element of the island is an
input
or atextarea
, it is easy to detect from thefocus
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 adiv
withcontentEditable={true}
containing a Slate block collection like the one above, from the case in which the editable element is still adiv
withcontentEditable={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 editablediv
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
andfocus
transforms exist? 😊