How can we allow HTML blocks to 'break out' when the user hits enter? In essence, when in an h1, if I hit enter, I want to set the default behavior to 'break out' of the h1 into a p.
<p>, not an <h1>.<h1>This is handled in Draft.js with this plugin: https://github.com/icelab/draft-js-block-breakout-plugin.

you might want to look at this: https://github.com/ianstormtaylor/slate-soft-break
Hey @markbao good question. I've actually implemented similar functionality for my own editor, and will probably pull it out into a separate plugin early next week.
The general idea is we want to have an onKeyDown event handler, which will both splitBlock() and setBlock('my-default-type'). (By default the core plugin just does a splitBlock().)
Off the top of my head it might look like:
function onKeyDown(e, state) {
if (state.startBlock != 'header-one') return
return state
.transform()
.splitBlock()
.setBlock('paragraph')
.apply()
}
So, is there any methods to do what @markbao said? sorry, but i still cant understand.
onKeyDown = (e, change) => {
if (e.keyCode === 13 && !e.shiftKey) {
change.setBlock('paragraph')
this.onChange(change)
}
}
this code still not doing what we want - it changes previous block to 'paragrap', and if you will do change.splitBlock().setBlock('paragraph') it will add 2 blocks instead of one.
What im doing wrong?
Hey @iKorso if you do a return rather than this.onChange(change) it will work:
onKeyDown = (e, change) => {
if (e.keyCode === 13 && !e.shiftKey) {
return change.splitBlock().setBlock('paragraph')
}
}
insertBlock worked for me:
onKeyDown = (e, change) => {
if (e.keyCode === 13 && !e.shiftKey) {
return change.splitBlock().insertBlock('paragraph')
}
}
Has anyone experienced problems with rendering
Got it working in 0.57 with:
if (event.key === 'Enter' && !event.shiftKey) {
setTimeout(() => {
Transforms.setNodes(editor, { type: 'paragraph' })
}, 0)
}
Some sort of timing issue — either due to rendering, the key down, or Slate. Not ideal so will dig into it.
@simpleshadow I did something similar but eliminated the need for a setTimeout because it was causing the new line to temporarily glitch. I just override the default behavior of the enter key and instead manually insert a new paragraph node with empty text:
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
const newLine = {
type: "paragraph",
children: [
{
text: "",
marks: []
}
]
};
Transforms.insertNodes(editor, newLine);
}
@zxkxyz 👍
I've had a couple of other scenarios where I need setTimeout so a series of transforms occur on the next tick while allowing the original command to still apply.
For example, when overriding insertText with a series of transforms, I allow the insertText call to complete (so all other plugin overrides can apply) and then have my transform series apply on the next tick. Not sure if this helper is right, but has been working for me!
// withBlurt.ts
export const withBlurt = <T extends Editor>(editor: T, options: { debug?: boolean } = { debug: false}) => {
const e = editor as T & BlurtEditor
e.nextTick = cb => {
setTimeout(cb, 0)
}
return e
}
// replaceBoldMatch.ts
export const replaceBoldMatch = (...) => {
...
Transforms.insertText(editor, '*')
editor.nextTick(() => {
Transforms.delete(editor, {
unit: 'character',
at: matchedPoint,
distance: matchedLength + 1,
})
Editor.addMark(editor, type, true)
Editor.insertText(editor, addText)
Editor.removeMark(editor, type)
})
}
I had issues with some of the solutions above:
To fix this:
if (event.key === 'Enter') {
const selectedElement = Node.descendant(editor, editor.selection.anchor.path.slice(0, -1));
// Replace 'title' with the type of the element which you wish to "break out" from
if (selectedElement.type === 'title') {
event.preventDefault();
const selectedLeaf = Node.descendant(editor, editor.selection.anchor.path);
if (selectedLeaf.text.length === editor.selection.anchor.offset) {
Transforms.insertNodes(editor, {
type: 'paragraph',
children: [{text: '', marks: []}],
});
}
else {
Transforms.splitNodes(editor);
Transforms.setNodes(editor, {type: 'paragraph'});
}
}
}
@PaddyMann Thanks that solution is working for me!
I just had to tweak for my use-case (check-lists) Transforms.splitNodes(editor, { always: true }) so the node always splits even when editor.selection.anchor.offset === 0.
Just a note on UI flicker with the setTimeout approach - it's possible that such an approach might work without the flicker if a promise is used instead since the callback should be a "microtask" instead of a new task (or "macrotask"?). Using a promise to schedule an asynchronous callback is already used internally in Slate, in create-editor.ts for example:
Promise.resolve().then(() => {
FLUSHING.set(editor, false)
editor.onChange()
editor.operations = []
})
The task / microtask distinction is only something I learned about recently after looking into the difference between Promise.resolve().then vs setTimeout, so I don't know much about the actual difference in effect.
https://stackoverflow.com/q/38752620/828584
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
The quote from javascript.info (Event loop: microtasks and macrotasks) suggests that this _would_ avoid the ui flicker since rendering should be delayed until after all microtasks:
Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else
Most helpful comment
Hey @markbao good question. I've actually implemented similar functionality for my own editor, and will probably pull it out into a separate plugin early next week.
The general idea is we want to have an
onKeyDownevent handler, which will bothsplitBlock()andsetBlock('my-default-type'). (By default the core plugin just does asplitBlock().)Off the top of my head it might look like: