I'm submitting a...
Package: @progress/kendo-react-editor (schema/keymap from @progress/kendo-editor-common)
Current behavior
When the caret is inside an empty textblock nested in one or more div nodes, pressing Enter appears to do nothing. The document does change, but invisibly: liftEmptyBlock lifts the empty block out of its parent div, which renders pixel-identically. Depending on the structure, either the next Enter finally splits (block reached top level), or — when the empty block has siblings inside the div — every Enter keeps lifting/splitting the div wrapper and a visible new line is never produced.
This is very common in real content: HTML email bodies (Gmail/Outlook) wrap every line in <div>, producing structures like <div>line1</div><div><br></div><div>line2</div>. Users placing the caret on the blank line and pressing Enter get a "dead" key.
Expected behavior
Enter in an empty block nested in plain div wrappers should produce a visible new line (split), like it does at top level.
Root cause analysis
buildKeymap in @progress/kendo-editor-common/dist/es/config/keymap.js binds:
keys.Enter = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockKeepMarks);
Because the schema's div node is content: 'block*', an empty paragraph inside a div satisfies liftEmptyBlock's preconditions, so it wins the chain ahead of splitBlockKeepMarks. The lift is structurally correct ProseMirror behaviour, but with a styling-neutral div wrapper the result is visually indistinguishable from a no-op.
Minimal reproduction (headless, uses only your packages)
// All symbols re-exported by the editor package itself
const { ProseMirror } = require('@progress/kendo-react-editor');
const {
Schema, EditorState, TextSelection,
chainCommands, newlineInCode, createParagraphNear,
liftEmptyBlock, splitBlockKeepMarks,
} = ProseMirror;
const { nodes, marks } = require('@progress/kendo-editor-common');
const schema = new Schema({ nodes, marks });
const { doc, paragraph, div } = schema.nodes;
const enter = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockKeepMarks);
// <div><p>a</p><p></p><p>b</p></div> — caret in the empty middle paragraph
let d = doc.create(null, [div.create(null, [
paragraph.create(null, schema.text('a')),
paragraph.create(),
paragraph.create(null, schema.text('b')),
])]);
let state = EditorState.create({ doc: d });
state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 5)));
for (let i = 1; i <= 3; i++) {
enter(state, tr => { state = state.apply(tr); });
console.log(`Enter #${i}:`, state.doc.toString());
}
Output — no visible new line is ever created; the div wrapper is repeatedly split instead:
Enter #1: doc(div(paragraph("a")), div(paragraph, paragraph("b")))
Enter #2: doc(div(paragraph("a")), div, div(paragraph, paragraph("b")))
Enter #3: doc(div(paragraph("a")), div, div, div(paragraph, paragraph("b")))
In the Editor UI this reproduces by setting defaultContent to
<div>line1</div><div><br></div><div>line2</div>, clicking the blank middle line, and pressing Enter.
Suggested fix
In buildKeymap, guard liftEmptyBlock so it does not apply when all ancestors of the empty block are plain div nodes (no styling semantics), falling through to splitBlockKeepMarks instead. Lifting should be preserved for semantically meaningful wrappers (blockquote, list items).
Workaround we ship in production — a prepended keymap plugin:
const enterInEmptyDivBlock = (state, dispatch) => {
const { $cursor } = state.selection;
if (!$cursor || $cursor.parent.content.size > 0 || $cursor.depth < 2) return false;
for (let d = $cursor.depth - 1; d >= 1; d--) {
if ($cursor.node(d).type.name !== 'div') return false;
}
return splitBlockKeepMarks(state, dispatch);
};
Environment
- KendoReact version: @progress/kendo-react-editor 14.4.1 / @progress/kendo-editor-common 1.12.5
- React version: 18.3.1
- Browser: all (Chrome, Firefox, Safari — logic-level bug, not rendering)
- OS: all
I'm submitting a...
Package:
@progress/kendo-react-editor(schema/keymap from@progress/kendo-editor-common)Current behavior
When the caret is inside an empty textblock nested in one or more
divnodes, pressing Enter appears to do nothing. The document does change, but invisibly:liftEmptyBlocklifts the empty block out of its parentdiv, which renders pixel-identically. Depending on the structure, either the next Enter finally splits (block reached top level), or — when the empty block has siblings inside thediv— every Enter keeps lifting/splitting thedivwrapper and a visible new line is never produced.This is very common in real content: HTML email bodies (Gmail/Outlook) wrap every line in
<div>, producing structures like<div>line1</div><div><br></div><div>line2</div>. Users placing the caret on the blank line and pressing Enter get a "dead" key.Expected behavior
Enter in an empty block nested in plain
divwrappers should produce a visible new line (split), like it does at top level.Root cause analysis
buildKeymapin@progress/kendo-editor-common/dist/es/config/keymap.jsbinds:Because the schema's
divnode iscontent: 'block*', an empty paragraph inside adivsatisfiesliftEmptyBlock's preconditions, so it wins the chain ahead ofsplitBlockKeepMarks. The lift is structurally correct ProseMirror behaviour, but with a styling-neutraldivwrapper the result is visually indistinguishable from a no-op.Minimal reproduction (headless, uses only your packages)
Output — no visible new line is ever created; the
divwrapper is repeatedly split instead:In the Editor UI this reproduces by setting
defaultContentto<div>line1</div><div><br></div><div>line2</div>, clicking the blank middle line, and pressing Enter.Suggested fix
In
buildKeymap, guardliftEmptyBlockso it does not apply when all ancestors of the empty block are plaindivnodes (no styling semantics), falling through tosplitBlockKeepMarksinstead. Lifting should be preserved for semantically meaningful wrappers (blockquote, list items).Workaround we ship in production — a prepended keymap plugin:
Environment