@portabletext/editor 1.50.6 → 1.50.7

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.
Files changed (34) hide show
  1. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs +1 -1
  2. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -1
  3. package/lib/_chunks-es/util.selection-point-to-block-offset.js +1 -1
  4. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -1
  5. package/lib/behaviors/index.d.cts +24 -16
  6. package/lib/behaviors/index.d.ts +24 -16
  7. package/lib/index.cjs +320 -252
  8. package/lib/index.cjs.map +1 -1
  9. package/lib/index.d.cts +24 -16
  10. package/lib/index.d.ts +24 -16
  11. package/lib/index.js +322 -254
  12. package/lib/index.js.map +1 -1
  13. package/lib/plugins/index.d.cts +24 -16
  14. package/lib/plugins/index.d.ts +24 -16
  15. package/lib/selectors/index.d.cts +24 -16
  16. package/lib/selectors/index.d.ts +24 -16
  17. package/lib/utils/index.d.cts +24 -16
  18. package/lib/utils/index.d.ts +24 -16
  19. package/package.json +4 -4
  20. package/src/behaviors/behavior.abstract.delete.ts +60 -0
  21. package/src/behaviors/behavior.abstract.split.ts +23 -13
  22. package/src/behaviors/behavior.types.event.ts +23 -16
  23. package/src/editor/plugins/create-with-event-listeners.ts +41 -1
  24. package/src/editor/sync-machine.ts +26 -20
  25. package/src/internal-utils/applyPatch.ts +298 -207
  26. package/src/internal-utils/slate-utils.ts +23 -0
  27. package/src/internal-utils/test-editor.tsx +45 -0
  28. package/src/operations/behavior.operation.delete.ts +36 -22
  29. package/src/operations/behavior.operations.ts +0 -27
  30. package/src/utils/util.is-selection-collapsed.ts +2 -1
  31. package/src/internal-utils/__tests__/dmpToOperations.test.ts +0 -207
  32. package/src/operations/behavior.operation.delete.backward.ts +0 -8
  33. package/src/operations/behavior.operation.delete.block.ts +0 -24
  34. package/src/operations/behavior.operation.delete.forward.ts +0 -8
@@ -15,30 +15,14 @@ import {
15
15
  makeDiff,
16
16
  parsePatch,
17
17
  } from '@sanity/diff-match-patch'
18
- import type {
19
- KeyedSegment,
20
- Path,
21
- PathSegment,
22
- PortableTextBlock,
23
- PortableTextChild,
24
- } from '@sanity/types'
25
- import {
26
- Element,
27
- Node,
28
- Text,
29
- Transforms,
30
- type Descendant,
31
- type Path as SlatePath,
32
- } from 'slate'
18
+ import type {Path, PortableTextBlock, PortableTextChild} from '@sanity/types'
19
+ import {Element, Node, Text, Transforms, type Descendant} from 'slate'
33
20
  import type {EditorSchema} from '../editor/editor-schema'
34
21
  import type {PortableTextSlateEditor} from '../types/editor'
35
- import {debugWithName} from './debug'
22
+ import {isKeyedSegment} from '../utils'
36
23
  import {isEqualToEmptyEditor, toSlateValue} from './values'
37
24
  import {KEY_TO_SLATE_ELEMENT} from './weakMaps'
38
25
 
39
- const debug = debugWithName('applyPatches')
40
- const debugVerbose = debug.enabled && true
41
-
42
26
  /**
43
27
  * Creates a function that can apply a patch onto a PortableTextSlateEditor.
44
28
  */
@@ -48,14 +32,6 @@ export function createApplyPatch(
48
32
  return (editor: PortableTextSlateEditor, patch: Patch): boolean => {
49
33
  let changed = false
50
34
 
51
- // Save some CPU cycles by not stringifying unless enabled
52
- if (debugVerbose) {
53
- debug(
54
- '\n\nNEW PATCH =============================================================',
55
- )
56
- debug(JSON.stringify(patch, null, 2))
57
- }
58
-
59
35
  try {
60
36
  switch (patch.type) {
61
37
  case 'insert':
@@ -70,8 +46,6 @@ export function createApplyPatch(
70
46
  case 'diffMatchPatch':
71
47
  changed = diffMatchPatch(editor, patch)
72
48
  break
73
- default:
74
- debug('Unhandled patch', patch.type)
75
49
  }
76
50
  } catch (err) {
77
51
  console.error(err)
@@ -81,64 +55,63 @@ export function createApplyPatch(
81
55
  }
82
56
  }
83
57
 
84
- /**
85
- * Apply a remote diff match patch to the current PTE instance.
86
- * Note meant for external consumption, only exported for testing purposes.
87
- *
88
- * @param editor - Portable text slate editor instance
89
- * @param patch - The PTE diff match patch operation to apply
90
- * @returns true if the patch was applied, false otherwise
91
- * @internal
92
- */
93
- export function diffMatchPatch(
58
+ function diffMatchPatch(
94
59
  editor: Pick<
95
60
  PortableTextSlateEditor,
96
61
  'children' | 'isTextBlock' | 'apply' | 'selection' | 'onChange'
97
62
  >,
98
63
  patch: DiffMatchPatch,
99
64
  ): boolean {
100
- const {block, child, childPath} = findBlockAndChildFromPath(
101
- editor,
102
- patch.path,
103
- )
65
+ const block = findBlock(editor.children, patch.path)
66
+
104
67
  if (!block) {
105
- debug('Block not found')
106
68
  return false
107
69
  }
108
- if (!child || !childPath) {
109
- debug('Child not found')
70
+
71
+ const child = findBlockChild(block, patch.path)
72
+
73
+ if (!child) {
110
74
  return false
111
75
  }
76
+
112
77
  const isSpanTextDiffMatchPatch =
113
78
  block &&
114
- editor.isTextBlock(block) &&
79
+ editor.isTextBlock(block.node) &&
115
80
  patch.path.length === 4 &&
116
81
  patch.path[1] === 'children' &&
117
82
  patch.path[3] === 'text'
118
83
 
119
- if (!isSpanTextDiffMatchPatch || !Text.isText(child)) {
84
+ if (!isSpanTextDiffMatchPatch || !Text.isText(child.node)) {
120
85
  return false
121
86
  }
122
87
 
123
88
  const patches = parsePatch(patch.value)
124
- const [newValue] = diffMatchPatchApplyPatches(patches, child.text, {
89
+ const [newValue] = diffMatchPatchApplyPatches(patches, child.node.text, {
125
90
  allowExceedingIndices: true,
126
91
  })
127
- const diff = cleanupEfficiency(makeDiff(child.text, newValue), 5)
92
+ const diff = cleanupEfficiency(makeDiff(child.node.text, newValue), 5)
128
93
 
129
- debugState(editor, 'before')
130
94
  let offset = 0
131
95
  for (const [op, text] of diff) {
132
96
  if (op === DIFF_INSERT) {
133
- editor.apply({type: 'insert_text', path: childPath, offset, text})
97
+ editor.apply({
98
+ type: 'insert_text',
99
+ path: [block.index, child.index],
100
+ offset,
101
+ text,
102
+ })
134
103
  offset += text.length
135
104
  } else if (op === DIFF_DELETE) {
136
- editor.apply({type: 'remove_text', path: childPath, offset: offset, text})
105
+ editor.apply({
106
+ type: 'remove_text',
107
+ path: [block.index, child.index],
108
+ offset: offset,
109
+ text,
110
+ })
137
111
  } else if (op === DIFF_EQUAL) {
138
112
  offset += text.length
139
113
  }
140
114
  }
141
- debugState(editor, 'after')
142
115
 
143
116
  return true
144
117
  }
@@ -148,20 +121,16 @@ function insertPatch(
148
121
  patch: InsertPatch,
149
122
  schema: EditorSchema,
150
123
  ) {
151
- const {
152
- block: targetBlock,
153
- child: targetChild,
154
- blockPath: targetBlockPath,
155
- childPath: targetChildPath,
156
- } = findBlockAndChildFromPath(editor, patch.path)
157
- if (!targetBlock || !targetBlockPath) {
158
- debug('Block not found')
124
+ const block = findBlock(editor.children, patch.path)
125
+
126
+ if (!block) {
159
127
  return false
160
128
  }
129
+
161
130
  if (patch.path.length > 1 && patch.path[1] !== 'children') {
162
- debug('Ignoring patch targeting void value')
163
131
  return false
164
132
  }
133
+
165
134
  // Insert blocks
166
135
  if (patch.path.length === 1) {
167
136
  const {items, position} = patch
@@ -170,13 +139,10 @@ function insertPatch(
170
139
  {schemaTypes: schema},
171
140
  KEY_TO_SLATE_ELEMENT.get(editor),
172
141
  ) as Descendant[]
173
- const targetBlockIndex = targetBlockPath[0]
142
+ const targetBlockIndex = block.index
174
143
  const normalizedIdx =
175
144
  position === 'after' ? targetBlockIndex + 1 : targetBlockIndex
176
145
 
177
- debug(`Inserting blocks at path [${normalizedIdx}]`)
178
- debugState(editor, 'before')
179
-
180
146
  const editorWasEmptyBefore = isEqualToEmptyEditor(editor.children, schema)
181
147
 
182
148
  Transforms.insertNodes(editor, blocksToInsert, {at: [normalizedIdx]})
@@ -191,34 +157,33 @@ function insertPatch(
191
157
  })
192
158
  }
193
159
 
194
- debugState(editor, 'after')
195
160
  return true
196
161
  }
162
+
197
163
  // Insert children
198
164
  const {items, position} = patch
199
- if (!targetChild || !targetChildPath) {
200
- debug('Child not found')
165
+
166
+ const targetChild = findBlockChild(block, patch.path)
167
+
168
+ if (!targetChild) {
201
169
  return false
202
170
  }
203
- const childrenToInsert =
204
- targetBlock &&
205
- toSlateValue(
206
- [{...targetBlock, children: items as PortableTextChild[]}],
207
- {schemaTypes: schema},
208
- KEY_TO_SLATE_ELEMENT.get(editor),
209
- )
210
- const targetChildIndex = targetChildPath[1]
171
+
172
+ const childrenToInsert = toSlateValue(
173
+ [{...block.node, children: items as PortableTextChild[]}],
174
+ {schemaTypes: schema},
175
+ KEY_TO_SLATE_ELEMENT.get(editor),
176
+ )
211
177
  const normalizedIdx =
212
- position === 'after' ? targetChildIndex + 1 : targetChildIndex
213
- const childInsertPath = [targetChildPath[0], normalizedIdx]
214
- debug(`Inserting children at path ${childInsertPath}`)
215
- debugState(editor, 'before')
178
+ position === 'after' ? targetChild.index + 1 : targetChild.index
179
+ const childInsertPath = [block.index, normalizedIdx]
180
+
216
181
  if (childrenToInsert && Element.isElement(childrenToInsert[0])) {
217
182
  Transforms.insertNodes(editor, childrenToInsert[0].children, {
218
183
  at: childInsertPath,
219
184
  })
220
185
  }
221
- debugState(editor, 'after')
186
+
222
187
  return true
223
188
  }
224
189
 
@@ -228,110 +193,169 @@ function setPatch(editor: PortableTextSlateEditor, patch: SetPatch) {
228
193
  value = {}
229
194
  value[patch.path[3]] = patch.value
230
195
  }
231
- const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
232
- editor,
233
- patch.path,
234
- )
196
+
197
+ const block = findBlock(editor.children, patch.path)
235
198
 
236
199
  if (!block) {
237
- debug('Block not found')
238
200
  return false
239
201
  }
240
- const isTextBlock = editor.isTextBlock(block)
202
+
203
+ const isTextBlock = editor.isTextBlock(block.node)
241
204
 
242
205
  // Ignore patches targeting nested void data, like 'markDefs'
243
206
  if (isTextBlock && patch.path.length > 1 && patch.path[1] !== 'children') {
244
- debug('Ignoring setting void value')
245
207
  return false
246
208
  }
247
209
 
248
- debugState(editor, 'before')
210
+ const child = findBlockChild(block, patch.path)
249
211
 
250
212
  // If this is targeting a text block child
251
- if (isTextBlock && child && childPath) {
252
- if (Text.isText(value) && Text.isText(child)) {
253
- const newText = child.text
254
- const oldText = value.text
255
- if (oldText !== newText) {
256
- debug('Setting text property')
257
- editor.apply({
258
- type: 'remove_text',
259
- path: childPath,
260
- offset: 0,
261
- text: newText,
262
- })
263
- editor.apply({
264
- type: 'insert_text',
265
- path: childPath,
266
- offset: 0,
267
- text: value.text,
268
- })
269
- // call OnChange here to emit the new selection
270
- // the user's selection might be interfering with
271
- editor.onChange()
213
+ if (isTextBlock && child) {
214
+ if (Text.isText(child.node)) {
215
+ if (Text.isText(value)) {
216
+ const oldText = child.node.text
217
+ const newText = value.text
218
+ if (oldText !== newText) {
219
+ editor.apply({
220
+ type: 'remove_text',
221
+ path: [block.index, child.index],
222
+ offset: 0,
223
+ text: oldText,
224
+ })
225
+ editor.apply({
226
+ type: 'insert_text',
227
+ path: [block.index, child.index],
228
+ offset: 0,
229
+ text: newText,
230
+ })
231
+ // call OnChange here to emit the new selection
232
+ // the user's selection might be interfering with
233
+ editor.onChange()
234
+ }
235
+ } else {
236
+ // Setting non-text span property
237
+
238
+ const propPath = patch.path.slice(3)
239
+ const propEntry = propPath.at(0)
240
+ const reservedProps = ['_key', '_type', 'text']
241
+
242
+ if (propEntry === undefined) {
243
+ return false
244
+ }
245
+
246
+ if (
247
+ typeof propEntry === 'string' &&
248
+ reservedProps.includes(propEntry)
249
+ ) {
250
+ return false
251
+ }
252
+
253
+ const newNode = applyAll(child.node, [
254
+ {
255
+ ...patch,
256
+ path: propPath,
257
+ },
258
+ ])
259
+
260
+ Transforms.setNodes(editor, newNode, {at: [block.index, child.index]})
272
261
  }
273
262
  } else {
274
- debug('Setting non-text property')
275
- editor.apply({
276
- type: 'set_node',
277
- path: childPath,
278
- properties: {},
279
- newProperties: value as Partial<Node>,
280
- })
263
+ // Setting inline object property
264
+
265
+ const propPath = patch.path.slice(3)
266
+ const reservedProps = ['_key', '_type', 'children', '__inline']
267
+ const propEntry = propPath.at(0)
268
+
269
+ if (propEntry === undefined) {
270
+ return false
271
+ }
272
+
273
+ if (typeof propEntry === 'string' && reservedProps.includes(propEntry)) {
274
+ return false
275
+ }
276
+
277
+ // If the child is an inline object, we need to apply the patch to the
278
+ // `value` property object.
279
+ const value =
280
+ 'value' in child.node && typeof child.node.value === 'object'
281
+ ? child.node.value
282
+ : {}
283
+
284
+ const newValue = applyAll(value, [
285
+ {
286
+ ...patch,
287
+ path: patch.path.slice(3),
288
+ },
289
+ ])
290
+
291
+ Transforms.setNodes(
292
+ editor,
293
+ {...child.node, value: newValue},
294
+ {at: [block.index, child.index]},
295
+ )
281
296
  }
297
+
282
298
  return true
283
- } else if (Element.isElement(block) && patch.path.length === 1 && blockPath) {
284
- debug('Setting block property')
299
+ } else if (Element.isElement(block.node) && patch.path.length === 1) {
285
300
  const {children, ...nextRest} = value as unknown as PortableTextBlock
286
- const {children: prevChildren, ...prevRest} = block || {children: undefined}
301
+ const {children: prevChildren, ...prevRest} = block.node || {
302
+ children: undefined,
303
+ }
304
+
287
305
  // Set any block properties
288
306
  editor.apply({
289
307
  type: 'set_node',
290
- path: blockPath,
308
+ path: [block.index],
291
309
  properties: {...prevRest},
292
310
  newProperties: nextRest,
293
311
  })
312
+
294
313
  // Replace the children in the block
295
314
  // Note that children must be explicitly inserted, and can't be set with set_node
296
- debug('Setting children')
297
- block.children.forEach((c, cIndex) => {
315
+ const blockNode = block.node
316
+
317
+ blockNode.children.forEach((child, childIndex) => {
298
318
  editor.apply({
299
319
  type: 'remove_node',
300
- path: blockPath.concat(block.children.length - 1 - cIndex),
301
- node: c,
320
+ path: [block.index, blockNode.children.length - 1 - childIndex],
321
+ node: child,
302
322
  })
303
323
  })
324
+
304
325
  if (Array.isArray(children)) {
305
- children.forEach((c, cIndex) => {
326
+ children.forEach((child, childIndex) => {
306
327
  editor.apply({
307
328
  type: 'insert_node',
308
- path: blockPath.concat(cIndex),
309
- node: c,
329
+ path: [block.index, childIndex],
330
+ node: child,
310
331
  })
311
332
  })
312
333
  }
313
- } else if (block && 'value' in block) {
334
+ } else if (block && 'value' in block.node) {
314
335
  if (patch.path.length > 1 && patch.path[1] !== 'children') {
315
- const newVal = applyAll(block.value, [
336
+ const newVal = applyAll(block.node.value, [
316
337
  {
317
338
  ...patch,
318
339
  path: patch.path.slice(1),
319
340
  },
320
341
  ])
321
- Transforms.setNodes(editor, {...block, value: newVal}, {at: blockPath})
342
+
343
+ Transforms.setNodes(
344
+ editor,
345
+ {...block.node, value: newVal},
346
+ {at: [block.index]},
347
+ )
322
348
  } else {
323
349
  return false
324
350
  }
325
351
  }
326
- debugState(editor, 'after')
352
+
327
353
  return true
328
354
  }
329
355
 
330
356
  function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
331
357
  // Value
332
358
  if (patch.path.length === 0) {
333
- debug('Removing everything')
334
- debugState(editor, 'before')
335
359
  const previousSelection = editor.selection
336
360
  Transforms.deselect(editor)
337
361
 
@@ -352,126 +376,193 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
352
376
  }
353
377
  // call OnChange here to emit the new selection
354
378
  editor.onChange()
355
- debugState(editor, 'after')
356
379
  return true
357
380
  }
358
- const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
359
- editor,
360
- patch.path,
361
- )
381
+
382
+ const block = findBlock(editor.children, patch.path)
383
+
384
+ if (!block) {
385
+ return false
386
+ }
362
387
 
363
388
  // Single blocks
364
389
  if (patch.path.length === 1) {
365
- if (!block || !blockPath) {
366
- debug('Block not found')
390
+ Transforms.removeNodes(editor, {at: [block.index]})
391
+
392
+ return true
393
+ }
394
+
395
+ const child = findBlockChild(block, patch.path)
396
+
397
+ // Unset on text block children
398
+ if (editor.isTextBlock(block.node) && child) {
399
+ if (patch.path[1] === 'children' && patch.path.length === 3) {
400
+ Transforms.removeNodes(editor, {at: [block.index, child.index]})
401
+
402
+ return true
403
+ }
404
+ }
405
+
406
+ if (child && !Text.isText(child.node)) {
407
+ // Unsetting inline object property
408
+
409
+ const propPath = patch.path.slice(3)
410
+ const propEntry = propPath.at(0)
411
+ const reservedProps = ['_key', '_type', 'children', '__inline']
412
+
413
+ if (propEntry === undefined) {
367
414
  return false
368
415
  }
369
- const blockIndex = blockPath[0]
370
- debug(`Removing block at path [${blockIndex}]`)
371
- debugState(editor, 'before')
372
416
 
373
- Transforms.removeNodes(editor, {at: [blockIndex]})
374
- debugState(editor, 'after')
417
+ if (typeof propEntry === 'string' && reservedProps.includes(propEntry)) {
418
+ // All custom properties are stored on the `value` property object.
419
+ // If you try to unset any of the other top-level properties it's a
420
+ // no-op.
421
+ return false
422
+ }
423
+
424
+ const value =
425
+ 'value' in child.node && typeof child.node.value === 'object'
426
+ ? child.node.value
427
+ : {}
428
+
429
+ const newValue = applyAll(value, [
430
+ {
431
+ ...patch,
432
+ path: patch.path.slice(3),
433
+ },
434
+ ])
435
+
436
+ Transforms.setNodes(
437
+ editor,
438
+ {...child.node, value: newValue},
439
+ {at: [block.index, child.index]},
440
+ )
441
+
375
442
  return true
376
443
  }
377
444
 
378
- // Unset on text block children
379
- if (
380
- editor.isTextBlock(block) &&
381
- patch.path[1] === 'children' &&
382
- patch.path.length === 3
383
- ) {
384
- if (!child || !childPath) {
385
- debug('Child not found')
445
+ if (child && Text.isText(child.node)) {
446
+ const propPath = patch.path.slice(3)
447
+ const propEntry = propPath.at(0)
448
+ const reservedProps = ['_key', '_type']
449
+
450
+ if (propEntry === undefined) {
386
451
  return false
387
452
  }
388
- debug(`Unsetting child at path ${JSON.stringify(childPath)}`)
389
- debugState(editor, 'before')
390
- if (debugVerbose) {
391
- debug(`Removing child at path ${JSON.stringify(childPath)}`)
453
+
454
+ if (typeof propEntry === 'string' && reservedProps.includes(propEntry)) {
455
+ return false
392
456
  }
393
- Transforms.removeNodes(editor, {at: childPath})
394
- debugState(editor, 'after')
457
+
458
+ if (typeof propEntry === 'string' && propEntry === 'text') {
459
+ editor.apply({
460
+ type: 'remove_text',
461
+ path: [block.index, child.index],
462
+ offset: 0,
463
+ text: child.node.text,
464
+ })
465
+
466
+ return true
467
+ }
468
+
469
+ const newNode = applyAll(child.node, [
470
+ {
471
+ ...patch,
472
+ path: propPath,
473
+ },
474
+ ])
475
+
476
+ const removedProperties = Object.keys(child.node).filter(
477
+ (property) => newNode[property] === undefined,
478
+ )
479
+
480
+ Transforms.unsetNodes(editor, removedProperties, {
481
+ at: [block.index, child.index],
482
+ })
483
+
395
484
  return true
396
485
  }
397
- return false
398
- }
399
486
 
400
- function isKeyedSegment(segment: PathSegment): segment is KeyedSegment {
401
- return typeof segment === 'object' && '_key' in segment
402
- }
487
+ if (!child) {
488
+ if ('value' in block.node) {
489
+ const newVal = applyAll(block.node.value, [
490
+ {
491
+ ...patch,
492
+ path: patch.path.slice(1),
493
+ },
494
+ ])
403
495
 
404
- function debugState(
405
- editor: Pick<
406
- PortableTextSlateEditor,
407
- 'children' | 'isTextBlock' | 'apply' | 'selection'
408
- >,
409
- stateName: string,
410
- ) {
411
- if (!debugVerbose) {
412
- return
496
+ Transforms.setNodes(
497
+ editor,
498
+ {...block.node, value: newVal},
499
+ {at: [block.index]},
500
+ )
501
+
502
+ return true
503
+ }
504
+
505
+ return false
413
506
  }
414
507
 
415
- debug(`Children ${stateName}:`, JSON.stringify(editor.children, null, 2))
416
- debug(`Selection ${stateName}: `, JSON.stringify(editor.selection, null, 2))
508
+ return false
417
509
  }
418
510
 
419
- function findBlockFromPath(
420
- editor: Pick<
421
- PortableTextSlateEditor,
422
- 'children' | 'isTextBlock' | 'apply' | 'selection' | 'onChange'
423
- >,
511
+ function findBlock(
512
+ children: Descendant[],
424
513
  path: Path,
425
- ): {block?: Descendant; path?: SlatePath} {
514
+ ): {node: Descendant; index: number} | undefined {
426
515
  let blockIndex = -1
427
- const block = editor.children.find((node: Descendant, index: number) => {
516
+
517
+ const block = children.find((node: Descendant, index: number) => {
428
518
  const isMatch = isKeyedSegment(path[0])
429
519
  ? node._key === path[0]._key
430
520
  : index === path[0]
521
+
431
522
  if (isMatch) {
432
523
  blockIndex = index
433
524
  }
525
+
434
526
  return isMatch
435
527
  })
528
+
436
529
  if (!block) {
437
- return {}
530
+ return undefined
438
531
  }
439
- return {block, path: [blockIndex] as SlatePath}
532
+
533
+ return {node: block, index: blockIndex}
440
534
  }
441
535
 
442
- function findBlockAndChildFromPath(
443
- editor: Pick<
444
- PortableTextSlateEditor,
445
- 'children' | 'isTextBlock' | 'apply' | 'selection' | 'onChange'
446
- >,
536
+ function findBlockChild(
537
+ block: {node: Descendant; index: number},
447
538
  path: Path,
448
- ): {
449
- child?: Descendant
450
- childPath?: SlatePath
451
- block?: Descendant
452
- blockPath?: SlatePath
453
- } {
454
- const {block, path: blockPath} = findBlockFromPath(editor, path)
455
- if (!(Element.isElement(block) && path[1] === 'children')) {
456
- return {block, blockPath, child: undefined, childPath: undefined}
539
+ ): {node: Descendant; index: number} | undefined {
540
+ const blockNode = block.node
541
+
542
+ if (!Element.isElement(blockNode) || path[1] !== 'children') {
543
+ return undefined
457
544
  }
545
+
458
546
  let childIndex = -1
459
- const child = block.children.find((node, index: number) => {
547
+
548
+ const child = blockNode.children.find((node, index: number) => {
460
549
  const isMatch = isKeyedSegment(path[2])
461
550
  ? node._key === path[2]._key
462
551
  : index === path[2]
552
+
463
553
  if (isMatch) {
464
554
  childIndex = index
465
555
  }
556
+
466
557
  return isMatch
467
558
  })
559
+
468
560
  if (!child) {
469
- return {block, blockPath, child: undefined, childPath: undefined}
561
+ return undefined
470
562
  }
563
+
471
564
  return {
472
- block,
473
- child,
474
- blockPath,
475
- childPath: blockPath?.concat(childIndex) as SlatePath,
565
+ node: child,
566
+ index: childIndex,
476
567
  }
477
568
  }