diff --git a/.changeset/silver-snakes-perform.md b/.changeset/silver-snakes-perform.md
new file mode 100644
index 000000000..1143ace00
--- /dev/null
+++ b/.changeset/silver-snakes-perform.md
@@ -0,0 +1,5 @@
+---
+'slate': patch
+---
+
+Fix `Error: Cannot get the start point in the node at path [...] because it has no start text node` caused by normalizing a document where some elements have no children
diff --git a/packages/slate/src/interfaces/editor.ts b/packages/slate/src/interfaces/editor.ts
index d6b4eb440..cbc3cd85b 100644
--- a/packages/slate/src/interfaces/editor.ts
+++ b/packages/slate/src/interfaces/editor.ts
@@ -16,6 +16,7 @@ import {
RangeRef,
Span,
Text,
+ Transforms,
} from '..'
import {
DIRTY_PATHS,
@@ -988,6 +989,27 @@ export const Editor: EditorInterface = {
}
Editor.withoutNormalizing(editor, () => {
+ /*
+ Fix dirty elements with no children.
+ editor.normalizeNode() does fix this, but some normalization fixes also require it to work.
+ Running an initial pass avoids the catch-22 race condition.
+ */
+ for (const dirtyPath of getDirtyPaths(editor)) {
+ if (Node.has(editor, dirtyPath)) {
+ const [node, _] = Editor.node(editor, dirtyPath)
+
+ // Add a text child to elements with no children.
+ // This is safe to do in any order, by definition it can't cause other paths to change.
+ if (Element.isElement(node) && node.children.length === 0) {
+ const child = { text: '' }
+ Transforms.insertNodes(editor, child, {
+ at: dirtyPath.concat(0),
+ voids: true,
+ })
+ }
+ }
+ }
+
const max = getDirtyPaths(editor).length * 42 // HACK: better way?
let m = 0
diff --git a/packages/slate/test/transforms/normalization/move_node.tsx b/packages/slate/test/transforms/normalization/move_node.tsx
new file mode 100644
index 000000000..9a21a805d
--- /dev/null
+++ b/packages/slate/test/transforms/normalization/move_node.tsx
@@ -0,0 +1,21 @@
+/** @jsx jsx */
+import { Transforms } from 'slate'
+import { jsx } from '../..'
+
+export const input = (
+
+ one
+ two
+
+)
+export const run = editor => {
+ Transforms.moveNodes(editor, { at: [0, 0], to: [1, 0] })
+}
+export const output = (
+
+
+
+
+ onetwo
+
+)
diff --git a/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx b/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx
new file mode 100644
index 000000000..21db92713
--- /dev/null
+++ b/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx
@@ -0,0 +1,84 @@
+/** @jsx jsx */
+import { Editor, Transforms } from 'slate'
+import { jsx } from '../..'
+
+export const input = (
+
+
+
+ one
+
+
+
+
+ two
+
+
+
+)
+export const run = editor => {
+ Editor.withoutNormalizing(editor, () => {
+ const operations = [
+ {
+ type: 'split_node',
+ path: [0, 1],
+ position: 0,
+ properties: { inline: true },
+ },
+ {
+ type: 'split_node',
+ path: [0],
+ position: 1,
+ properties: {},
+ },
+ {
+ type: 'split_node',
+ path: [2, 1, 0],
+ position: 0,
+ properties: {},
+ },
+ {
+ type: 'split_node',
+ path: [2, 1],
+ position: 0,
+ properties: { inline: true },
+ },
+ {
+ type: 'split_node',
+ path: [2],
+ position: 1,
+ properties: {},
+ },
+ { type: 'insert_node', path: [2, 1], node: { text: '' } },
+ ]
+ operations.forEach(editor.apply)
+ })
+}
+export const output = (
+
+
+
+
+
+
+
+
+
+
+ one
+
+
+
+
+
+
+
+
+
+
+
+ two
+
+
+
+)