@portabletext/editor 1.39.1 → 1.40.1

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 (99) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +12 -4
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/editor-provider.cjs +131 -109
  4. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  5. package/lib/_chunks-cjs/{parse-blocks.cjs → util.selection-point-to-block-offset.cjs} +74 -4
  6. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -0
  7. package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -2
  8. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  9. package/lib/_chunks-cjs/util.split-text-block.cjs +68 -0
  10. package/lib/_chunks-cjs/util.split-text-block.cjs.map +1 -0
  11. package/lib/_chunks-es/behavior.core.js +12 -4
  12. package/lib/_chunks-es/behavior.core.js.map +1 -1
  13. package/lib/_chunks-es/editor-provider.js +125 -103
  14. package/lib/_chunks-es/editor-provider.js.map +1 -1
  15. package/lib/_chunks-es/{parse-blocks.js → util.selection-point-to-block-offset.js} +76 -5
  16. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -0
  17. package/lib/_chunks-es/util.slice-blocks.js +2 -2
  18. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  19. package/lib/_chunks-es/util.split-text-block.js +70 -0
  20. package/lib/_chunks-es/util.split-text-block.js.map +1 -0
  21. package/lib/behaviors/index.d.cts +383 -111
  22. package/lib/behaviors/index.d.ts +383 -111
  23. package/lib/index.cjs +198 -195
  24. package/lib/index.cjs.map +1 -1
  25. package/lib/index.d.cts +345 -90
  26. package/lib/index.d.ts +345 -90
  27. package/lib/index.js +205 -202
  28. package/lib/index.js.map +1 -1
  29. package/lib/plugins/index.cjs +11 -11
  30. package/lib/plugins/index.cjs.map +1 -1
  31. package/lib/plugins/index.d.cts +335 -93
  32. package/lib/plugins/index.d.ts +335 -93
  33. package/lib/plugins/index.js +2 -2
  34. package/lib/selectors/index.d.cts +333 -81
  35. package/lib/selectors/index.d.ts +333 -81
  36. package/lib/utils/index.cjs +15 -87
  37. package/lib/utils/index.cjs.map +1 -1
  38. package/lib/utils/index.d.cts +386 -84
  39. package/lib/utils/index.d.ts +386 -84
  40. package/lib/utils/index.js +13 -86
  41. package/lib/utils/index.js.map +1 -1
  42. package/package.json +6 -6
  43. package/src/behavior-actions/behavior.action.decorator.add.ts +13 -2
  44. package/src/behaviors/behavior.core.block-objects.ts +32 -2
  45. package/src/behaviors/behavior.default.ts +38 -14
  46. package/src/behaviors/behavior.types.ts +5 -4
  47. package/src/converters/converter.portable-text.ts +9 -0
  48. package/src/converters/converter.text-plain.test.ts +5 -5
  49. package/src/converters/converter.text-plain.ts +12 -19
  50. package/src/editor/Editable.tsx +122 -68
  51. package/src/editor/PortableTextEditor.tsx +8 -8
  52. package/src/editor/__tests__/self-solving.test.tsx +1 -1
  53. package/src/editor/components/Element.tsx +2 -9
  54. package/src/editor/create-editor.ts +13 -5
  55. package/src/editor/editor-machine.ts +6 -2
  56. package/src/editor/editor-provider.tsx +11 -7
  57. package/src/editor/editor-selector.ts +4 -3
  58. package/src/editor/editor-snapshot.ts +2 -2
  59. package/src/editor/plugins/create-with-event-listeners.ts +2 -5
  60. package/src/editor/plugins/createWithPortableTextMarkModel.ts +1 -2
  61. package/src/internal-utils/block-keys.ts +9 -0
  62. package/src/internal-utils/collapse-selection.ts +36 -0
  63. package/src/internal-utils/compound-client-rect.ts +28 -0
  64. package/src/internal-utils/drag-selection.test.ts +507 -0
  65. package/src/internal-utils/drag-selection.ts +66 -0
  66. package/src/internal-utils/editor-selection.test.ts +40 -0
  67. package/src/internal-utils/editor-selection.ts +60 -0
  68. package/src/internal-utils/event-position.ts +55 -80
  69. package/src/internal-utils/inline-object-selection.ts +115 -0
  70. package/src/internal-utils/selection-block-keys.ts +20 -0
  71. package/src/internal-utils/selection-elements.ts +61 -0
  72. package/src/internal-utils/selection-focus-text.ts +38 -0
  73. package/src/internal-utils/selection-text.test.ts +23 -0
  74. package/src/internal-utils/selection-text.ts +90 -0
  75. package/src/internal-utils/split-string.ts +12 -0
  76. package/src/internal-utils/string-overlap.test.ts +14 -0
  77. package/src/internal-utils/string-overlap.ts +28 -0
  78. package/src/internal-utils/string-utils.ts +7 -0
  79. package/src/internal-utils/terse-pt.test.ts +60 -0
  80. package/src/internal-utils/terse-pt.ts +36 -0
  81. package/src/internal-utils/text-block-key.test.ts +30 -0
  82. package/src/internal-utils/text-block-key.ts +30 -0
  83. package/src/internal-utils/text-marks.test.ts +33 -0
  84. package/src/internal-utils/text-marks.ts +26 -0
  85. package/src/internal-utils/text-selection.test.ts +175 -0
  86. package/src/internal-utils/text-selection.ts +122 -0
  87. package/src/internal-utils/value-annotations.ts +31 -0
  88. package/src/internal-utils/values.ts +16 -5
  89. package/src/utils/index.ts +5 -0
  90. package/src/utils/util.block-offset-to-block-selection-point.ts +28 -0
  91. package/src/utils/util.block-offset-to-selection-point.ts +33 -0
  92. package/src/utils/util.block-offsets-to-selection.ts +3 -3
  93. package/src/utils/util.is-equal-selections.ts +20 -0
  94. package/src/utils/util.is-selection-collapsed.ts +15 -0
  95. package/src/utils/util.reverse-selection.ts +9 -5
  96. package/src/utils/util.selection-point-to-block-offset.ts +31 -0
  97. package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
  98. package/lib/_chunks-es/parse-blocks.js.map +0 -1
  99. package/src/editor/components/use-draggable.ts +0 -123
@@ -0,0 +1,507 @@
1
+ import {describe, expect, test} from 'vitest'
2
+ import {compileSchemaDefinition, defineSchema} from '../editor/define-schema'
3
+ import type {EditorSelection} from '../types/editor'
4
+ import {createTestSnapshot} from './create-test-snapshot'
5
+ import {getDragSelection} from './drag-selection'
6
+ import {createTestKeyGenerator} from './test-key-generator'
7
+
8
+ describe(getDragSelection.name, () => {
9
+ const keyGenerator = createTestKeyGenerator()
10
+ const foo = {
11
+ _key: keyGenerator(),
12
+ _type: 'block',
13
+ children: [
14
+ {
15
+ _key: keyGenerator(),
16
+ _type: 'span',
17
+ text: 'foo',
18
+ },
19
+ {
20
+ _key: keyGenerator(),
21
+ _type: 'stock-ticker',
22
+ },
23
+ {
24
+ _key: keyGenerator(),
25
+ _type: 'span',
26
+ text: 'bar',
27
+ },
28
+ ],
29
+ }
30
+ const fooPath = [{_key: foo._key}, 'children', {_key: foo.children[0]._key}]
31
+ const stockTickerPath = [
32
+ {_key: foo._key},
33
+ 'children',
34
+ {_key: foo.children[1]._key},
35
+ ]
36
+ const barPath = [{_key: foo._key}, 'children', {_key: foo.children[2]._key}]
37
+ const baz = {
38
+ _key: keyGenerator(),
39
+ _type: 'block',
40
+ children: [
41
+ {
42
+ _key: keyGenerator(),
43
+ _type: 'span',
44
+ text: 'bar',
45
+ },
46
+ ],
47
+ }
48
+ const image = {
49
+ _key: keyGenerator(),
50
+ _type: 'image',
51
+ }
52
+ const imagePath = [{_key: image._key}]
53
+
54
+ function snapshot(selection: EditorSelection) {
55
+ return createTestSnapshot({
56
+ context: {
57
+ keyGenerator,
58
+ schema: compileSchemaDefinition(
59
+ defineSchema({blockObjects: [{name: 'image'}]}),
60
+ ),
61
+ selection,
62
+ value: [foo, baz, image],
63
+ },
64
+ })
65
+ }
66
+
67
+ test('dragging one text block', () => {
68
+ expect(
69
+ getDragSelection({
70
+ eventSelection: {
71
+ anchor: {
72
+ path: fooPath,
73
+ offset: 0,
74
+ },
75
+ focus: {
76
+ path: fooPath,
77
+ offset: 0,
78
+ },
79
+ },
80
+ snapshot: snapshot(null),
81
+ }),
82
+ ).toEqual({
83
+ anchor: {
84
+ path: fooPath,
85
+ offset: 0,
86
+ },
87
+ focus: {
88
+ path: barPath,
89
+ offset: 3,
90
+ },
91
+ })
92
+ })
93
+
94
+ test('dragging one text block with an existing selection', () => {
95
+ expect(
96
+ getDragSelection({
97
+ eventSelection: {
98
+ anchor: {
99
+ path: fooPath,
100
+ offset: 0,
101
+ },
102
+ focus: {
103
+ path: fooPath,
104
+ offset: 0,
105
+ },
106
+ },
107
+ snapshot: snapshot({
108
+ anchor: {
109
+ path: fooPath,
110
+ offset: 0,
111
+ },
112
+ focus: {
113
+ path: fooPath,
114
+ offset: 3,
115
+ },
116
+ }),
117
+ }),
118
+ )
119
+ })
120
+
121
+ test('dragging one text block with a selection elsewhere', () => {
122
+ expect(
123
+ getDragSelection({
124
+ eventSelection: {
125
+ anchor: {
126
+ path: fooPath,
127
+ offset: 0,
128
+ },
129
+ focus: {
130
+ path: fooPath,
131
+ offset: 0,
132
+ },
133
+ },
134
+ snapshot: snapshot({
135
+ anchor: {
136
+ path: imagePath,
137
+ offset: 0,
138
+ },
139
+ focus: {
140
+ path: imagePath,
141
+ offset: 0,
142
+ },
143
+ }),
144
+ }),
145
+ ).toEqual({
146
+ anchor: {
147
+ path: fooPath,
148
+ offset: 0,
149
+ },
150
+ focus: {
151
+ path: barPath,
152
+ offset: 3,
153
+ },
154
+ })
155
+ })
156
+
157
+ test('dragging one block object with a selection elsewhere', () => {
158
+ expect(
159
+ getDragSelection({
160
+ eventSelection: {
161
+ anchor: {
162
+ path: imagePath,
163
+ offset: 0,
164
+ },
165
+ focus: {
166
+ path: imagePath,
167
+ offset: 0,
168
+ },
169
+ },
170
+ snapshot: snapshot({
171
+ anchor: {
172
+ path: fooPath,
173
+ offset: 0,
174
+ },
175
+ focus: {
176
+ path: fooPath,
177
+ offset: 0,
178
+ },
179
+ }),
180
+ }),
181
+ ).toEqual({
182
+ anchor: {
183
+ path: imagePath,
184
+ offset: 0,
185
+ },
186
+ focus: {
187
+ path: imagePath,
188
+ offset: 0,
189
+ },
190
+ })
191
+ })
192
+
193
+ test('dragging one block object with an expanded selection elsewhere', () => {
194
+ expect(
195
+ getDragSelection({
196
+ eventSelection: {
197
+ anchor: {
198
+ path: imagePath,
199
+ offset: 0,
200
+ },
201
+ focus: {
202
+ path: imagePath,
203
+ offset: 0,
204
+ },
205
+ },
206
+ snapshot: snapshot({
207
+ anchor: {
208
+ path: fooPath,
209
+ offset: 1,
210
+ },
211
+ focus: {
212
+ path: fooPath,
213
+ offset: 2,
214
+ },
215
+ }),
216
+ }),
217
+ ).toEqual({
218
+ anchor: {
219
+ path: imagePath,
220
+ offset: 0,
221
+ },
222
+ focus: {
223
+ path: imagePath,
224
+ offset: 0,
225
+ },
226
+ })
227
+ })
228
+
229
+ test('dragging a text block with an expanded selected', () => {
230
+ expect(
231
+ getDragSelection({
232
+ eventSelection: {
233
+ anchor: {
234
+ path: fooPath,
235
+ offset: 0,
236
+ },
237
+ focus: {
238
+ path: fooPath,
239
+ offset: 0,
240
+ },
241
+ },
242
+ snapshot: snapshot({
243
+ anchor: {
244
+ path: fooPath,
245
+ offset: 0,
246
+ },
247
+ focus: {
248
+ path: imagePath,
249
+ offset: 0,
250
+ },
251
+ }),
252
+ }),
253
+ ).toEqual({
254
+ anchor: {
255
+ path: fooPath,
256
+ offset: 0,
257
+ },
258
+ focus: {
259
+ path: imagePath,
260
+ offset: 0,
261
+ },
262
+ })
263
+ })
264
+
265
+ test('dragging a block object with an expanded selected', () => {
266
+ expect(
267
+ getDragSelection({
268
+ eventSelection: {
269
+ anchor: {
270
+ path: imagePath,
271
+ offset: 0,
272
+ },
273
+ focus: {
274
+ path: imagePath,
275
+ offset: 0,
276
+ },
277
+ },
278
+ snapshot: snapshot({
279
+ anchor: {
280
+ path: fooPath,
281
+ offset: 0,
282
+ },
283
+ focus: {
284
+ path: imagePath,
285
+ offset: 0,
286
+ },
287
+ }),
288
+ }),
289
+ ).toEqual({
290
+ anchor: {
291
+ path: fooPath,
292
+ offset: 0,
293
+ },
294
+ focus: {
295
+ path: imagePath,
296
+ offset: 0,
297
+ },
298
+ })
299
+ })
300
+
301
+ test('dragging inline object', () => {
302
+ expect(
303
+ getDragSelection({
304
+ eventSelection: {
305
+ anchor: {
306
+ path: stockTickerPath,
307
+ offset: 0,
308
+ },
309
+ focus: {
310
+ path: stockTickerPath,
311
+ offset: 0,
312
+ },
313
+ },
314
+ snapshot: snapshot(null),
315
+ }),
316
+ ).toEqual({
317
+ anchor: {
318
+ path: stockTickerPath,
319
+ offset: 0,
320
+ },
321
+ focus: {
322
+ path: stockTickerPath,
323
+ offset: 0,
324
+ },
325
+ })
326
+ })
327
+
328
+ test('dragging inline object already selected', () => {
329
+ expect(
330
+ getDragSelection({
331
+ eventSelection: {
332
+ anchor: {
333
+ path: stockTickerPath,
334
+ offset: 0,
335
+ },
336
+ focus: {
337
+ path: stockTickerPath,
338
+ offset: 0,
339
+ },
340
+ },
341
+ snapshot: snapshot({
342
+ anchor: {
343
+ path: stockTickerPath,
344
+ offset: 0,
345
+ },
346
+ focus: {
347
+ path: stockTickerPath,
348
+ offset: 0,
349
+ },
350
+ }),
351
+ }),
352
+ ).toEqual({
353
+ anchor: {
354
+ path: stockTickerPath,
355
+ offset: 0,
356
+ },
357
+ focus: {
358
+ path: stockTickerPath,
359
+ offset: 0,
360
+ },
361
+ })
362
+ })
363
+
364
+ test('dragging inline object with selection elsewhere', () => {
365
+ expect(
366
+ getDragSelection({
367
+ eventSelection: {
368
+ anchor: {
369
+ path: stockTickerPath,
370
+ offset: 0,
371
+ },
372
+ focus: {
373
+ path: stockTickerPath,
374
+ offset: 0,
375
+ },
376
+ },
377
+ snapshot: snapshot({
378
+ anchor: {
379
+ path: fooPath,
380
+ offset: 0,
381
+ },
382
+ focus: {
383
+ path: fooPath,
384
+ offset: 0,
385
+ },
386
+ }),
387
+ }),
388
+ ).toEqual({
389
+ anchor: {
390
+ path: stockTickerPath,
391
+ offset: 0,
392
+ },
393
+ focus: {
394
+ path: stockTickerPath,
395
+ offset: 0,
396
+ },
397
+ })
398
+ })
399
+
400
+ test('dragging inline object with expanded selection elsewhere', () => {
401
+ expect(
402
+ getDragSelection({
403
+ eventSelection: {
404
+ anchor: {
405
+ path: stockTickerPath,
406
+ offset: 0,
407
+ },
408
+ focus: {
409
+ path: stockTickerPath,
410
+ offset: 0,
411
+ },
412
+ },
413
+ snapshot: snapshot({
414
+ anchor: {
415
+ path: fooPath,
416
+ offset: 0,
417
+ },
418
+ focus: {
419
+ path: fooPath,
420
+ offset: 3,
421
+ },
422
+ }),
423
+ }),
424
+ ).toEqual({
425
+ anchor: {
426
+ path: stockTickerPath,
427
+ offset: 0,
428
+ },
429
+ focus: {
430
+ path: stockTickerPath,
431
+ offset: 0,
432
+ },
433
+ })
434
+ })
435
+
436
+ test('dragging inline object in an expanded selection', () => {
437
+ expect(
438
+ getDragSelection({
439
+ eventSelection: {
440
+ anchor: {
441
+ path: stockTickerPath,
442
+ offset: 0,
443
+ },
444
+ focus: {
445
+ path: stockTickerPath,
446
+ offset: 0,
447
+ },
448
+ },
449
+ snapshot: snapshot({
450
+ anchor: {
451
+ path: fooPath,
452
+ offset: 2,
453
+ },
454
+ focus: {
455
+ path: barPath,
456
+ offset: 2,
457
+ },
458
+ }),
459
+ }),
460
+ ).toEqual({
461
+ anchor: {
462
+ path: stockTickerPath,
463
+ offset: 0,
464
+ },
465
+ focus: {
466
+ path: stockTickerPath,
467
+ offset: 0,
468
+ },
469
+ })
470
+ })
471
+
472
+ test('dragging text block with inline object selected', () => {
473
+ expect(
474
+ getDragSelection({
475
+ eventSelection: {
476
+ anchor: {
477
+ path: fooPath,
478
+ offset: 0,
479
+ },
480
+ focus: {
481
+ path: fooPath,
482
+ offset: 0,
483
+ },
484
+ },
485
+ snapshot: snapshot({
486
+ anchor: {
487
+ path: stockTickerPath,
488
+ offset: 0,
489
+ },
490
+ focus: {
491
+ path: stockTickerPath,
492
+ offset: 0,
493
+ },
494
+ }),
495
+ }),
496
+ ).toEqual({
497
+ anchor: {
498
+ path: fooPath,
499
+ offset: 0,
500
+ },
501
+ focus: {
502
+ path: barPath,
503
+ offset: 3,
504
+ },
505
+ })
506
+ })
507
+ })
@@ -0,0 +1,66 @@
1
+ import type {EditorSnapshot} from '..'
2
+ import * as selectors from '../selectors'
3
+ import * as utils from '../utils'
4
+ import type {EventPosition} from './event-position'
5
+
6
+ export function getDragSelection({
7
+ eventSelection,
8
+ snapshot,
9
+ }: {
10
+ eventSelection: EventPosition['selection']
11
+ snapshot: EditorSnapshot
12
+ }) {
13
+ let dragSelection = eventSelection
14
+
15
+ const collapsedSelection = selectors.isSelectionCollapsed({
16
+ ...snapshot,
17
+ context: {
18
+ ...snapshot.context,
19
+ selection: eventSelection,
20
+ },
21
+ })
22
+ const focusTextBlock = selectors.getFocusTextBlock({
23
+ ...snapshot,
24
+ context: {
25
+ ...snapshot.context,
26
+ selection: eventSelection,
27
+ },
28
+ })
29
+ const focusSpan = selectors.getFocusSpan({
30
+ ...snapshot,
31
+ context: {
32
+ ...snapshot.context,
33
+ selection: eventSelection,
34
+ },
35
+ })
36
+
37
+ if (collapsedSelection && focusTextBlock && focusSpan) {
38
+ // Looks like we are dragging an empty span
39
+ // Let's drag the entire block instead
40
+ dragSelection = {
41
+ anchor: utils.getBlockStartPoint(focusTextBlock),
42
+ focus: utils.getBlockEndPoint(focusTextBlock),
43
+ }
44
+ }
45
+
46
+ const selectedBlocks = selectors.getSelectedBlocks(snapshot)
47
+
48
+ if (
49
+ snapshot.context.selection &&
50
+ selectors.isSelectionExpanded(snapshot) &&
51
+ selectors.isOverlappingSelection(eventSelection)(snapshot) &&
52
+ selectedBlocks.length > 1
53
+ ) {
54
+ const selectionStartBlock = selectors.getSelectionStartBlock(snapshot)
55
+ const selectionEndBlock = selectors.getSelectionEndBlock(snapshot)
56
+
57
+ if (selectionStartBlock && selectionEndBlock) {
58
+ dragSelection = {
59
+ anchor: utils.getBlockStartPoint(selectionStartBlock),
60
+ focus: utils.getBlockEndPoint(selectionEndBlock),
61
+ }
62
+ }
63
+ }
64
+
65
+ return dragSelection
66
+ }
@@ -0,0 +1,40 @@
1
+ import {expect, test} from 'vitest'
2
+ import {getEditorSelection} from './editor-selection'
3
+
4
+ test(getEditorSelection.name, () => {
5
+ const image = {
6
+ _type: 'image',
7
+ _key: 'i1',
8
+ }
9
+ const splitBlock = {
10
+ _type: 'block',
11
+ _key: 'b1',
12
+ children: [
13
+ {_type: 'span', _key: 's1', text: 'foo '},
14
+ {_type: 'span', _key: 's2', text: 'bar'},
15
+ {_type: 'span', _key: 's3', text: ' baz'},
16
+ ],
17
+ }
18
+ const blockWithStockTicker = {
19
+ _type: 'block',
20
+ _key: 'b2',
21
+ children: [
22
+ {_type: 'span', _key: 's1', text: ''},
23
+ {_type: 'stock-ticker', _key: 'st1'},
24
+ {_type: 'span', _key: 's2', text: ''},
25
+ ],
26
+ }
27
+
28
+ expect(getEditorSelection([image, splitBlock])).toEqual({
29
+ anchor: {path: [{_key: 'i1'}], offset: 0},
30
+ focus: {path: [{_key: 'b1'}, 'children', {_key: 's3'}], offset: 4},
31
+ })
32
+ expect(getEditorSelection([splitBlock, image])).toEqual({
33
+ anchor: {path: [{_key: 'b1'}, 'children', {_key: 's1'}], offset: 0},
34
+ focus: {path: [{_key: 'i1'}], offset: 0},
35
+ })
36
+ expect(getEditorSelection([blockWithStockTicker, splitBlock])).toEqual({
37
+ anchor: {path: [{_key: 'b2'}, 'children', {_key: 's1'}], offset: 0},
38
+ focus: {path: [{_key: 'b1'}, 'children', {_key: 's3'}], offset: 4},
39
+ })
40
+ })
@@ -0,0 +1,60 @@
1
+ import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
2
+ import type {PortableTextBlock} from '@sanity/types'
3
+ import type {EditorSelection, EditorSelectionPoint} from '../types/editor'
4
+
5
+ export function getEditorSelection(
6
+ blocks: Array<PortableTextBlock> | undefined,
7
+ ): EditorSelection {
8
+ if (!blocks) {
9
+ throw new Error('No value found')
10
+ }
11
+
12
+ let anchor: EditorSelectionPoint | undefined
13
+ let focus: EditorSelectionPoint | undefined
14
+ const firstBlock = blocks[0]
15
+ const lastBlock = blocks[blocks.length - 1]
16
+
17
+ if (isPortableTextBlock(firstBlock)) {
18
+ anchor = {
19
+ path: [
20
+ {_key: firstBlock._key},
21
+ 'children',
22
+ {_key: firstBlock.children[0]._key},
23
+ ],
24
+ offset: 0,
25
+ }
26
+ } else {
27
+ anchor = {
28
+ path: [{_key: firstBlock._key}],
29
+ offset: 0,
30
+ }
31
+ }
32
+
33
+ const lastChild = isPortableTextBlock(lastBlock)
34
+ ? lastBlock.children[lastBlock.children.length - 1]
35
+ : undefined
36
+ if (
37
+ isPortableTextBlock(lastBlock) &&
38
+ lastChild &&
39
+ isPortableTextSpan(lastChild)
40
+ ) {
41
+ focus = {
42
+ path: [{_key: lastBlock._key}, 'children', {_key: lastChild._key}],
43
+ offset: lastChild.text.length ?? 0,
44
+ }
45
+ } else {
46
+ focus = {
47
+ path: [{_key: lastBlock._key}],
48
+ offset: 0,
49
+ }
50
+ }
51
+
52
+ if (!anchor || !focus) {
53
+ throw new Error('No selection found')
54
+ }
55
+
56
+ return {
57
+ anchor,
58
+ focus,
59
+ }
60
+ }