@portabletext/editor 1.11.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -79,6 +79,7 @@
79
79
  "eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
80
80
  "eslint-plugin-react-hooks": "^5.0.0",
81
81
  "jsdom": "^25.0.1",
82
+ "racejar": "1.0.0",
82
83
  "react": "^18.3.1",
83
84
  "react-dom": "^18.3.1",
84
85
  "rxjs": "^7.8.1",
@@ -86,8 +87,7 @@
86
87
  "typescript": "5.6.3",
87
88
  "vite": "^5.4.11",
88
89
  "vitest": "^2.1.5",
89
- "vitest-browser-react": "^0.0.3",
90
- "@sanity/gherkin-driver": "^0.0.1"
90
+ "vitest-browser-react": "^0.0.3"
91
91
  },
92
92
  "peerDependencies": {
93
93
  "@sanity/block-tools": "^3.64.3",
@@ -0,0 +1,61 @@
1
+ import {Editor, Transforms, type Descendant} from 'slate'
2
+ import type {
3
+ PortableTextMemberSchemaTypes,
4
+ PortableTextSlateEditor,
5
+ } from '../../types/editor'
6
+ import {isEqualToEmptyEditor} from '../../utils/values'
7
+
8
+ export function insertBlock({
9
+ block,
10
+ placement,
11
+ editor,
12
+ schema,
13
+ }: {
14
+ block: Descendant
15
+ placement: 'auto' | 'after'
16
+ editor: PortableTextSlateEditor
17
+ schema: PortableTextMemberSchemaTypes
18
+ }) {
19
+ if (!editor.selection) {
20
+ const lastBlock = Array.from(
21
+ Editor.nodes(editor, {
22
+ match: (n) => !Editor.isEditor(n),
23
+ at: [],
24
+ reverse: true,
25
+ }),
26
+ )[0]
27
+
28
+ // If there is no selection, let's just insert the new block at the
29
+ // end of the document
30
+ Editor.insertNode(editor, block)
31
+
32
+ if (lastBlock && isEqualToEmptyEditor([lastBlock[0]], schema)) {
33
+ // And if the last block was an empty text block, let's remove
34
+ // that too
35
+ Transforms.removeNodes(editor, {at: lastBlock[1]})
36
+ }
37
+ } else {
38
+ const [focusBlock, focusBlockPath] = Array.from(
39
+ Editor.nodes(editor, {
40
+ at: editor.selection.focus.path.slice(0, 1),
41
+ match: (n) => !Editor.isEditor(n),
42
+ }),
43
+ )[0] ?? [undefined, undefined]
44
+
45
+ if (placement === 'after') {
46
+ const nextPath = [focusBlockPath[0] + 1]
47
+
48
+ Transforms.insertNodes(editor, block, {at: nextPath})
49
+ Transforms.select(editor, {
50
+ anchor: {path: [nextPath[0], 0], offset: 0},
51
+ focus: {path: [nextPath[0], 0], offset: 0},
52
+ })
53
+ } else {
54
+ Editor.insertNode(editor, block)
55
+ }
56
+
57
+ if (focusBlock && isEqualToEmptyEditor([focusBlock], schema)) {
58
+ Transforms.removeNodes(editor, {at: focusBlockPath})
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,25 @@
1
+ import {toSlateValue} from '../../utils/values'
2
+ import {insertBlock} from './behavior.action-utils.insert-block'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const insertBlockObjectActionImplementation: BehaviorActionImplementation<
6
+ 'insert block object'
7
+ > = ({context, action}) => {
8
+ const block = toSlateValue(
9
+ [
10
+ {
11
+ _key: context.keyGenerator(),
12
+ _type: action.blockObject.name,
13
+ ...(action.blockObject.value ? action.blockObject.value : {}),
14
+ },
15
+ ],
16
+ {schemaTypes: context.schema},
17
+ )[0]
18
+
19
+ insertBlock({
20
+ block,
21
+ placement: action.placement,
22
+ editor: action.editor,
23
+ schema: context.schema,
24
+ })
25
+ }
@@ -1,16 +1,11 @@
1
- import {
2
- deleteBackward,
3
- deleteForward,
4
- Editor,
5
- insertText,
6
- Transforms,
7
- } from 'slate'
1
+ import {deleteBackward, deleteForward, insertText, Transforms} from 'slate'
8
2
  import {ReactEditor} from 'slate-react'
9
3
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
10
4
  import {toSlateRange} from '../../utils/ranges'
5
+ import {fromSlateValue, toSlateValue} from '../../utils/values'
6
+ import {KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
11
7
  import {
12
8
  addAnnotationActionImplementation,
13
- insertBlockObjectActionImplementation,
14
9
  removeAnnotationActionImplementation,
15
10
  toggleAnnotationActionImplementation,
16
11
  } from '../plugins/createWithEditableAPI'
@@ -19,6 +14,8 @@ import {
19
14
  removeDecoratorActionImplementation,
20
15
  toggleDecoratorActionImplementation,
21
16
  } from '../plugins/createWithPortableTextMarkModel'
17
+ import {insertBlock} from './behavior.action-utils.insert-block'
18
+ import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
22
19
  import {
23
20
  insertBreakActionImplementation,
24
21
  insertSoftBreakActionImplementation,
@@ -29,6 +26,7 @@ import type {
29
26
  BehaviorEvent,
30
27
  PickFromUnion,
31
28
  } from './behavior.types'
29
+ import {blockOffsetToSpanSelectionPoint} from './behavior.utils.block-offset'
32
30
 
33
31
  export type BehaviorActionContext = {
34
32
  keyGenerator: () => string
@@ -94,25 +92,61 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
94
92
  'delete forward': ({action}) => {
95
93
  deleteForward(action.editor, action.unit)
96
94
  },
97
- 'delete': ({action}) => {
98
- const location = toSlateRange(action.selection, action.editor)
95
+ 'delete block': ({action}) => {
96
+ const range = toSlateRange(
97
+ {
98
+ anchor: {path: action.blockPath, offset: 0},
99
+ focus: {path: action.blockPath, offset: 0},
100
+ },
101
+ action.editor,
102
+ )
99
103
 
100
- if (!location) {
101
- console.error(
102
- `Could not find Slate location from selection ${action.selection}`,
103
- )
104
+ if (!range) {
105
+ console.error('Unable to find Slate range from selection points')
104
106
  return
105
107
  }
106
108
 
107
- if (location.anchor.path.length === 1 && location.focus.path.length === 1) {
108
- Transforms.removeNodes(action.editor, {
109
- at: location,
110
- })
111
- } else {
112
- Transforms.delete(action.editor, {
113
- at: location,
114
- })
109
+ Transforms.removeNodes(action.editor, {
110
+ at: range,
111
+ })
112
+ },
113
+ 'delete text': ({context, action}) => {
114
+ const value = fromSlateValue(
115
+ action.editor.children,
116
+ context.schema.block.name,
117
+ KEY_TO_VALUE_ELEMENT.get(action.editor),
118
+ )
119
+
120
+ const anchor = blockOffsetToSpanSelectionPoint({
121
+ value,
122
+ blockOffset: action.anchor,
123
+ })
124
+ const focus = blockOffsetToSpanSelectionPoint({
125
+ value,
126
+ blockOffset: action.focus,
127
+ })
128
+
129
+ if (!anchor || !focus) {
130
+ console.error('Unable to find anchor or focus selection point')
131
+ return
115
132
  }
133
+
134
+ const range = toSlateRange(
135
+ {
136
+ anchor,
137
+ focus,
138
+ },
139
+ action.editor,
140
+ )
141
+
142
+ if (!range) {
143
+ console.error('Unable to find Slate range from selection points')
144
+ return
145
+ }
146
+
147
+ Transforms.delete(action.editor, {
148
+ at: range,
149
+ })
116
150
  },
117
151
  'insert block object': insertBlockObjectActionImplementation,
118
152
  'insert break': insertBreakActionImplementation,
@@ -122,18 +156,33 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
122
156
  insertText(action.editor, action.text)
123
157
  },
124
158
  'insert text block': ({context, action}) => {
125
- Editor.insertNode(action.editor, {
126
- _key: context.keyGenerator(),
127
- _type: context.schema.block.name,
128
- style: context.schema.styles[0].value ?? 'normal',
129
- markDefs: [],
130
- children: [
159
+ const block = toSlateValue(
160
+ [
131
161
  {
132
162
  _key: context.keyGenerator(),
133
- _type: 'span',
134
- text: '',
163
+ _type: context.schema.block.name,
164
+ style: context.schema.styles[0].value ?? 'normal',
165
+ markDefs: [],
166
+ children: action.textBlock?.children?.map((child) => ({
167
+ ...child,
168
+ _key: context.keyGenerator(),
169
+ })) ?? [
170
+ {
171
+ _type: context.schema.span.name,
172
+ _key: context.keyGenerator(),
173
+ text: '',
174
+ },
175
+ ],
135
176
  },
136
177
  ],
178
+ {schemaTypes: context.schema},
179
+ )[0]
180
+
181
+ insertBlock({
182
+ block,
183
+ editor: action.editor,
184
+ schema: context.schema,
185
+ placement: action.placement,
137
186
  })
138
187
  },
139
188
  'effect': ({action}) => {
@@ -169,8 +218,15 @@ export function performAction({
169
218
  action: BehaviorAction
170
219
  }) {
171
220
  switch (action.type) {
172
- case 'delete': {
173
- behaviorActionImplementations.delete({
221
+ case 'delete block': {
222
+ behaviorActionImplementations['delete block']({
223
+ context,
224
+ action,
225
+ })
226
+ break
227
+ }
228
+ case 'delete text': {
229
+ behaviorActionImplementations['delete text']({
174
230
  context,
175
231
  action,
176
232
  })
@@ -16,7 +16,7 @@ const breakingBlockObject = defineBehavior({
16
16
 
17
17
  return !!focusBlockObject
18
18
  },
19
- actions: [() => [{type: 'insert text block', decorators: []}]],
19
+ actions: [() => [{type: 'insert text block', placement: 'after'}]],
20
20
  })
21
21
 
22
22
  const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
@@ -42,11 +42,8 @@ const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
42
42
  actions: [
43
43
  (_, {focusTextBlock, previousBlock}) => [
44
44
  {
45
- type: 'delete',
46
- selection: {
47
- anchor: {path: focusTextBlock.path, offset: 0},
48
- focus: {path: focusTextBlock.path, offset: 0},
49
- },
45
+ type: 'delete block',
46
+ blockPath: focusTextBlock.path,
50
47
  },
51
48
  {
52
49
  type: 'select',
@@ -82,11 +79,8 @@ const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
82
79
  actions: [
83
80
  (_, {focusTextBlock, nextBlock}) => [
84
81
  {
85
- type: 'delete',
86
- selection: {
87
- anchor: {path: focusTextBlock.path, offset: 0},
88
- focus: {path: focusTextBlock.path, offset: 0},
89
- },
82
+ type: 'delete block',
83
+ blockPath: focusTextBlock.path,
90
84
  },
91
85
  {
92
86
  type: 'select',
@@ -1,11 +1,15 @@
1
1
  import {isPortableTextSpan} from '@portabletext/toolkit'
2
+ import {isPortableTextTextBlock} from '@sanity/types'
2
3
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
3
4
  import {defineBehavior} from './behavior.types'
4
5
  import {
6
+ getFocusBlock,
5
7
  getFocusSpan,
6
8
  getFocusTextBlock,
9
+ getTextBlockText,
7
10
  selectionIsCollapsed,
8
11
  } from './behavior.utils'
12
+ import {spanSelectionPointToBlockOffset} from './behavior.utils.block-offset'
9
13
 
10
14
  /**
11
15
  * @alpha
@@ -53,8 +57,25 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
53
57
  return false
54
58
  }
55
59
 
56
- const caretAtTheEndOfQuote = context.selection.focus.offset === 1
57
- const looksLikeMarkdownQuote = /^>/.test(focusSpan.node.text)
60
+ const blockOffset = spanSelectionPointToBlockOffset({
61
+ value: context.value,
62
+ selectionPoint: {
63
+ path: [
64
+ {_key: focusTextBlock.node._key},
65
+ 'children',
66
+ {_key: focusSpan.node._key},
67
+ ],
68
+ offset: context.selection.focus.offset,
69
+ },
70
+ })
71
+
72
+ if (!blockOffset) {
73
+ return false
74
+ }
75
+
76
+ const blockText = getTextBlockText(focusTextBlock.node)
77
+ const caretAtTheEndOfQuote = blockOffset.offset === 1
78
+ const looksLikeMarkdownQuote = /^>/.test(blockText)
58
79
  const blockquoteStyle = config.blockquoteStyle?.({schema: context.schema})
59
80
 
60
81
  if (
@@ -62,7 +83,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
62
83
  looksLikeMarkdownQuote &&
63
84
  blockquoteStyle !== undefined
64
85
  ) {
65
- return {focusTextBlock, focusSpan, style: blockquoteStyle}
86
+ return {focusTextBlock, style: blockquoteStyle}
66
87
  }
67
88
 
68
89
  return false
@@ -74,7 +95,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
74
95
  text: ' ',
75
96
  },
76
97
  ],
77
- (_, {focusTextBlock, focusSpan, style}) => [
98
+ (_, {focusTextBlock, style}) => [
78
99
  {
79
100
  type: 'unset block',
80
101
  props: ['listItem', 'level'],
@@ -86,22 +107,20 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
86
107
  paths: [focusTextBlock.path],
87
108
  },
88
109
  {
89
- type: 'delete',
90
- selection: {
91
- anchor: {
92
- path: focusSpan.path,
93
- offset: 0,
94
- },
95
- focus: {
96
- path: focusSpan.path,
97
- offset: 2,
98
- },
110
+ type: 'delete text',
111
+ anchor: {
112
+ path: focusTextBlock.path,
113
+ offset: 0,
114
+ },
115
+ focus: {
116
+ path: focusTextBlock.path,
117
+ offset: 2,
99
118
  },
100
119
  },
101
120
  ],
102
121
  ],
103
122
  })
104
- const automaticBreak = defineBehavior({
123
+ const automaticHr = defineBehavior({
105
124
  on: 'insert text',
106
125
  guard: ({context, event}) => {
107
126
  const hrCharacter =
@@ -117,13 +136,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
117
136
  return false
118
137
  }
119
138
 
120
- const breakObject = config.horizontalRuleObject?.({
139
+ const hrObject = config.horizontalRuleObject?.({
121
140
  schema: context.schema,
122
141
  })
123
142
  const focusBlock = getFocusTextBlock(context)
124
143
  const selectionCollapsed = selectionIsCollapsed(context)
125
144
 
126
- if (!breakObject || !focusBlock || !selectionCollapsed) {
145
+ if (!hrObject || !focusBlock || !selectionCollapsed) {
127
146
  return false
128
147
  }
129
148
 
@@ -133,7 +152,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
133
152
  .join('')
134
153
 
135
154
  if (onlyText && blockText === `${hrCharacter}${hrCharacter}`) {
136
- return {breakObject, focusBlock, hrCharacter}
155
+ return {hrObject, focusBlock, hrCharacter}
137
156
  }
138
157
 
139
158
  return false
@@ -145,31 +164,71 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
145
164
  text: hrCharacter,
146
165
  },
147
166
  ],
148
- (_, {breakObject, focusBlock}) => [
167
+ (_, {hrObject, focusBlock}) => [
149
168
  {
150
169
  type: 'insert block object',
151
- ...breakObject,
170
+ placement: 'after',
171
+ blockObject: hrObject,
152
172
  },
153
173
  {
154
- type: 'delete',
155
- selection: {
156
- anchor: {
157
- path: focusBlock.path,
158
- offset: 0,
159
- },
160
- focus: {
161
- path: focusBlock.path,
162
- offset: 0,
163
- },
164
- },
174
+ type: 'delete block',
175
+ blockPath: focusBlock.path,
165
176
  },
166
177
  {
167
178
  type: 'insert text block',
168
- decorators: [],
179
+ placement: 'after',
169
180
  },
170
181
  ],
171
182
  ],
172
183
  })
184
+ const automaticHrOnPaste = defineBehavior({
185
+ on: 'paste',
186
+ guard: ({context, event}) => {
187
+ const text = event.clipboardData.getData('text/plain')
188
+ const hrRegExp = /^(---)$|(___)$|(\*\*\*)$/gm
189
+ const hrCharacters = text.match(hrRegExp)?.[0]
190
+ const hrObject = config.horizontalRuleObject?.({
191
+ schema: context.schema,
192
+ })
193
+ const focusBlock = getFocusBlock(context)
194
+
195
+ if (!hrCharacters || !hrObject || !focusBlock) {
196
+ return false
197
+ }
198
+
199
+ return {hrCharacters, hrObject, focusBlock}
200
+ },
201
+ actions: [
202
+ (_, {hrCharacters}) => [
203
+ {
204
+ type: 'insert text',
205
+ text: hrCharacters,
206
+ },
207
+ ],
208
+ (_, {hrObject, focusBlock}) =>
209
+ isPortableTextTextBlock(focusBlock.node)
210
+ ? [
211
+ {
212
+ type: 'insert text block',
213
+ textBlock: {children: focusBlock.node.children},
214
+ placement: 'after',
215
+ },
216
+ {
217
+ type: 'insert block object',
218
+ blockObject: hrObject,
219
+ placement: 'after',
220
+ },
221
+ {type: 'delete block', blockPath: focusBlock.path},
222
+ ]
223
+ : [
224
+ {
225
+ type: 'insert block object',
226
+ blockObject: hrObject,
227
+ placement: 'after',
228
+ },
229
+ ],
230
+ ],
231
+ })
173
232
  const automaticHeadingOnSpace = defineBehavior({
174
233
  on: 'insert text',
175
234
  guard: ({context, event}) => {
@@ -187,11 +246,28 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
187
246
  return false
188
247
  }
189
248
 
190
- const markdownHeadingSearch = /^#+/.exec(focusSpan.node.text)
249
+ const blockOffset = spanSelectionPointToBlockOffset({
250
+ value: context.value,
251
+ selectionPoint: {
252
+ path: [
253
+ {_key: focusTextBlock.node._key},
254
+ 'children',
255
+ {_key: focusSpan.node._key},
256
+ ],
257
+ offset: context.selection.focus.offset,
258
+ },
259
+ })
260
+
261
+ if (!blockOffset) {
262
+ return false
263
+ }
264
+
265
+ const blockText = getTextBlockText(focusTextBlock.node)
266
+ const markdownHeadingSearch = /^#+/.exec(blockText)
191
267
  const level = markdownHeadingSearch
192
268
  ? markdownHeadingSearch[0].length
193
269
  : undefined
194
- const caretAtTheEndOfHeading = context.selection.focus.offset === level
270
+ const caretAtTheEndOfHeading = blockOffset.offset === level
195
271
 
196
272
  if (!caretAtTheEndOfHeading) {
197
273
  return false
@@ -205,7 +281,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
205
281
  if (level !== undefined && style !== undefined) {
206
282
  return {
207
283
  focusTextBlock,
208
- focusSpan,
209
284
  style: style,
210
285
  level,
211
286
  }
@@ -220,7 +295,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
220
295
  text: ' ',
221
296
  },
222
297
  ],
223
- (_, {focusTextBlock, focusSpan, style, level}) => [
298
+ (_, {focusTextBlock, style, level}) => [
224
299
  {
225
300
  type: 'unset block',
226
301
  props: ['listItem', 'level'],
@@ -232,16 +307,14 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
232
307
  paths: [focusTextBlock.path],
233
308
  },
234
309
  {
235
- type: 'delete',
236
- selection: {
237
- anchor: {
238
- path: focusSpan.path,
239
- offset: 0,
240
- },
241
- focus: {
242
- path: focusSpan.path,
243
- offset: level + 1,
244
- },
310
+ type: 'delete text',
311
+ anchor: {
312
+ path: focusTextBlock.path,
313
+ offset: 0,
314
+ },
315
+ focus: {
316
+ path: focusTextBlock.path,
317
+ offset: level + 1,
245
318
  },
246
319
  },
247
320
  ],
@@ -301,12 +374,29 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
301
374
  return false
302
375
  }
303
376
 
377
+ const blockOffset = spanSelectionPointToBlockOffset({
378
+ value: context.value,
379
+ selectionPoint: {
380
+ path: [
381
+ {_key: focusTextBlock.node._key},
382
+ 'children',
383
+ {_key: focusSpan.node._key},
384
+ ],
385
+ offset: context.selection.focus.offset,
386
+ },
387
+ })
388
+
389
+ if (!blockOffset) {
390
+ return false
391
+ }
392
+
393
+ const blockText = getTextBlockText(focusTextBlock.node)
304
394
  const defaultStyle = config.defaultStyle?.({schema: context.schema})
305
- const looksLikeUnorderedList = /^(-|\*)/.test(focusSpan.node.text)
395
+ const looksLikeUnorderedList = /^(-|\*)/.test(blockText)
306
396
  const unorderedListStyle = config.unorderedListStyle?.({
307
397
  schema: context.schema,
308
398
  })
309
- const caretAtTheEndOfUnorderedList = context.selection.focus.offset === 1
399
+ const caretAtTheEndOfUnorderedList = blockOffset.offset === 1
310
400
 
311
401
  if (
312
402
  defaultStyle &&
@@ -316,7 +406,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
316
406
  ) {
317
407
  return {
318
408
  focusTextBlock,
319
- focusSpan,
320
409
  listItem: unorderedListStyle,
321
410
  listItemLength: 1,
322
411
  style: defaultStyle,
@@ -337,7 +426,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
337
426
  ) {
338
427
  return {
339
428
  focusTextBlock,
340
- focusSpan,
341
429
  listItem: orderedListStyle,
342
430
  listItemLength: 2,
343
431
  style: defaultStyle,
@@ -353,7 +441,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
353
441
  text: ' ',
354
442
  },
355
443
  ],
356
- (_, {focusTextBlock, focusSpan, style, listItem, listItemLength}) => [
444
+ (_, {focusTextBlock, style, listItem, listItemLength}) => [
357
445
  {
358
446
  type: 'set block',
359
447
  listItem,
@@ -362,16 +450,14 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
362
450
  paths: [focusTextBlock.path],
363
451
  },
364
452
  {
365
- type: 'delete',
366
- selection: {
367
- anchor: {
368
- path: focusSpan.path,
369
- offset: 0,
370
- },
371
- focus: {
372
- path: focusSpan.path,
373
- offset: listItemLength + 1,
374
- },
453
+ type: 'delete text',
454
+ anchor: {
455
+ path: focusTextBlock.path,
456
+ offset: 0,
457
+ },
458
+ focus: {
459
+ path: focusTextBlock.path,
460
+ offset: listItemLength + 1,
375
461
  },
376
462
  },
377
463
  ],
@@ -380,8 +466,9 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
380
466
 
381
467
  const markdownBehaviors = [
382
468
  automaticBlockquoteOnSpace,
383
- automaticBreak,
384
469
  automaticHeadingOnSpace,
470
+ automaticHr,
471
+ automaticHrOnPaste,
385
472
  clearStyleOnBackspace,
386
473
  automaticListOnSpace,
387
474
  ]