slate: Selection is null after editor loses focus
Do you want to request a feature or report a bug?
Bug
What’s the current behavior?
Current behavior is that when you click into the toolbar or out of the document the editor’s selection becomes null.

https://codesandbox.io/s/fervent-bouman-ju71u?fontsize=14&hidenavigation=1&theme=dark
Tested on Firefox, Safari and Chrome although Firefox has different behavior. Firefox sometimes sets the cursor position to the end of the text in the editor depending on how you focus out of the editor.
Slate: 0.57.1 Browser: Chrome / Safari / Firefox OS: Mac
What’s the expected behavior?
Focus out shouldn’t erase editor selection.
Changes to Example Site To Produce Behavior
In order to test this we forked the rich text example and made sure the toolbar style buttons did not disable on focus out. Then we used the ReactEditor.focus method in the MarkdownButton component’s onMouseDown handler in the richtext.js file.
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 56
- Comments: 37 (4 by maintainers)
Here is my solution to this problem:
<Slate>tag. This is required, so onBlur event will be fired before your button is clicked.<Editable>tag. In that handler saveselectionto some property on theeditor. E.g.blurSelection.Transforms.select(editor, editor.blurSelection);(blurSelection is the name of a variable from step2.ReactEditor.focus(editor)to return focus, so users can just continue typing.Works well for all the basic commands my editor has: lists, numbers, formatting, headers, etc.
Similar thing happened when I tried to use dialog box in link example instead of alert. While image upload as well the editor focus loses and the image gets appended at the last node instead of at the cursor location. Is there a way to control or change focus?
Hey guys, what if you use
onMouseDowninstead ofonClick? This doesn’t reset focus for me.Found a work around for this issue thanks to a kind developer on slack channel. Writing here in case anybody needs it. Store the selection value just before the editor loses focus. In my case it was when I clicked on input field, so I stored it just when dialog box opens. Similarly it can be applied to image upload, iframes or any action where editor loses focus.
You can get around this issue by setting readOnly to true before opening the link input or focusing outside the editor, and then setting back to false when done.
However, this PR will need to be merged, since readOnly is broken right now.
https://github.com/ianstormtaylor/slate/pull/3388/files
Yeah, I am currently trying to do Transforms.setSelection with the value I got from the onBlur event , cant seem to make it work. Setting the editor.selection does seem to work correctly.
I just need to find a way to highlight this and generalize the onBlur Event.
I have found that this is checking the onSelectChange event from the dom to unselect , so my next thing is going to try to disable the unselection there , I will report back on my findings.
So a quick work around, is to not allow the editor to be unselected - You can either run your own logic, or you can just “monkey patch” Transforms.deselect to be a empty function in the begginging of your app, this worked like a charm, and I can seem where this is actually being used on the internals of Slate apart from Focus/UnFocus. So so far this is my go to solution. Better scenario would be to actually change Editable to not call deselect via a prop or something, and you woulc manually call deselect ( in case for multiple editors on the same page)
I don’t know if this is a bug or expected behaviour. But here if you have value of selection with you (which is
editorSelection.current) you can pass it down to editor (editor.selection = editorSelection.current) before passing editor toTransforms.insertNodeor anywhere else. If you want to show the selection, may be tryTransforms.setSelection or Transforms.select.I needed to sort this out to make a Slate editor look and act like a textarea, which has a border div that can be clicked to focus the editor and needs to remember the selection. It’s basically the same approach that @bearz describes. Also important is the
// @refresh resetcomment, which prevents crashing on React Fast Refresh in newer versions of React.@AleksandrHovhannisyan hey, I put some time into researching and going through other people’s code and came up with this prototype:
hope this helps anyone that comes across this issue!
edit: i’m using plate for my plugin system
One alternative solution: When focus leaves the editor, wrap the current selection in a fake custom selection node. Then, instead of relying on
editor.selectionbeing non-null, perform all operations and checks relative to that unique selection node. And instead of usingeditor.selection, you can now define a customeditor.getSelectionmethod that returns either the path/location of that fake selection or falls back toeditor.selection. Then remove the fake selection when the editor is re-focused.We did this in our code base and it works well; it allows you to implement a floating toolbar and have inline inputs next to your editor. I’m hoping to write an article/tutorial soon to clarify how all of this works and what the code looks like. But that’s the basic idea: Just like you have other inline nodes (hyperlinks and whatever else), you can also mock up a selection node that doesn’t get serialized/deserialized and is only a run-time helper.
The solution of @bearz works, but it still removes the selection visually, which is problematic for some use cases.
I want to change the font size for the selection via typing the font size into an input field, and for the user it is disturbing that the selection is no longer there visually, only “under the hood”.
Keeping on this, is there a way to actually keep it selected, as in highligting the text when losing focus ?
I was able to keep the selected text highlighted when clicking on a toolbar button by using the following prop on
Editable:That doesn’t stop the editor from getting set to
nullwhen clicking in the toolbardivbut outside of a button. Very confusing, have no idea why that happens.I also added a
preventDefault()at the end of theonMouseDownand it kept both selection and focus for me.Like others in this thread, I was also caught by the unexpected behaviour of
setSelect. I’ve created https://github.com/ianstormtaylor/slate/pull/4247 explaining the differencebasically,if you need to regain focus with the previous selection, use
selectinstead ofsetSelect@raitucarp I don’t have a project set up to try it out, but most likely keeping the ref and passing it to a ButtonComponent as a prop should do a trick. Example code (may contain errors as I haven’t written JSX in forever):
Consider setting CSS for toolbars buttons outside the slate editing area:
user-select: none;<button onClick={handleClick} style={{userSelect:'none'}}>test</button>Reference MDN: https://developer.mozilla.org/zh-CN/docs/Web/CSS/user-select After setting, theselectionwill not be lostYou can make it work even when splitting nodes, here is a basic example that works for me:
In FontColorPicker:
For some reason just running
ReactEditor.focus(this.props.editor);afterEditor.addMarkwas putting the cursor back to the start even with a timeout, but deselect + select works.Step 5 returns focus for me. I can continue to type exactly from the same place. Make sure that your command doesn’t modify selection under the hood. If you split nodes or change blocks that might be a case.
@davevilela Sorry for the tease, I know that’s not a ton of info to go off of. I’m still hoping I can get around to writing a blog post about it one of these days.
I know Notion does something very similar in its editor; when you highlight some text and try to insert a hyperlink, they insert a fake blue wrapper span styled to look like a regular selection, and then they delete it afterwards.
But that was also the case in the previous version? It’s not easy to keep the selection visually because it’s the same document. You could have a custom “Selection” plugin which draws a background behind the selection to have a visual effect maybe?