@portabletext/editor 1.32.0 → 1.33.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.
Files changed (69) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +4 -4
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +19 -11
  4. package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs +127 -88
  6. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs +97 -0
  8. package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs.map +1 -0
  9. package/lib/_chunks-cjs/{parse-blocks.cjs → util.block-offsets-to-selection.cjs} +21 -2
  10. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -0
  11. package/lib/_chunks-cjs/util.reverse-selection.cjs +11 -0
  12. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
  13. package/lib/_chunks-es/behavior.core.js +1 -1
  14. package/lib/_chunks-es/behavior.core.js.map +1 -1
  15. package/lib/_chunks-es/behavior.markdown.js +18 -11
  16. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  17. package/lib/_chunks-es/plugin.event-listener.js +127 -87
  18. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  19. package/lib/_chunks-es/selector.get-trimmed-selection.js +100 -0
  20. package/lib/_chunks-es/selector.get-trimmed-selection.js.map +1 -0
  21. package/lib/_chunks-es/{parse-blocks.js → util.block-offsets-to-selection.js} +21 -1
  22. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -0
  23. package/lib/_chunks-es/util.reverse-selection.js +11 -0
  24. package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
  25. package/lib/behaviors/index.d.cts +1 -0
  26. package/lib/behaviors/index.d.ts +1 -0
  27. package/lib/index.d.cts +60 -0
  28. package/lib/index.d.ts +60 -0
  29. package/lib/plugins/index.cjs +295 -3
  30. package/lib/plugins/index.cjs.map +1 -1
  31. package/lib/plugins/index.d.cts +74 -1
  32. package/lib/plugins/index.d.ts +74 -1
  33. package/lib/plugins/index.js +300 -4
  34. package/lib/plugins/index.js.map +1 -1
  35. package/lib/selectors/index.cjs +51 -1
  36. package/lib/selectors/index.cjs.map +1 -1
  37. package/lib/selectors/index.d.cts +67 -0
  38. package/lib/selectors/index.d.ts +67 -0
  39. package/lib/selectors/index.js +53 -2
  40. package/lib/selectors/index.js.map +1 -1
  41. package/lib/utils/index.cjs +5 -4
  42. package/lib/utils/index.cjs.map +1 -1
  43. package/lib/utils/index.d.cts +16 -0
  44. package/lib/utils/index.d.ts +16 -0
  45. package/lib/utils/index.js +4 -3
  46. package/package.json +2 -2
  47. package/src/behavior-actions/behavior.action.decorator.add.ts +161 -0
  48. package/src/behavior-actions/behavior.action.delete.text.ts +54 -0
  49. package/src/behavior-actions/behavior.actions.ts +5 -43
  50. package/src/behaviors/behavior.markdown-emphasis.ts +395 -0
  51. package/src/behaviors/behavior.markdown.ts +11 -4
  52. package/src/behaviors/behavior.types.ts +1 -0
  53. package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -97
  54. package/src/plugins/plugin.markdown.tsx +11 -1
  55. package/src/selectors/index.ts +5 -0
  56. package/src/selectors/selector.get-anchor-block.ts +22 -0
  57. package/src/selectors/selector.get-anchor-child.ts +36 -0
  58. package/src/selectors/selector.get-anchor-span.ts +18 -0
  59. package/src/selectors/selector.get-anchor-text-block.ts +20 -0
  60. package/src/selectors/selector.get-trimmed-selection.test.ts +658 -0
  61. package/src/selectors/selector.get-trimmed-selection.ts +175 -0
  62. package/src/utils/index.ts +1 -0
  63. package/src/utils/util.block-offsets-to-selection.ts +36 -0
  64. package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
  65. package/lib/_chunks-cjs/util.is-empty-text-block.cjs +0 -14
  66. package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +0 -1
  67. package/lib/_chunks-es/parse-blocks.js.map +0 -1
  68. package/lib/_chunks-es/util.is-empty-text-block.js +0 -15
  69. package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
@@ -0,0 +1,658 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+ import {describe, expect, test} from 'vitest'
3
+ import {compileSchemaDefinition, defineSchema} from '../editor/define-schema'
4
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
5
+ import {parseBlock} from '../internal-utils/parse-blocks'
6
+ import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
7
+ import type {EditorSelection} from '../types/editor'
8
+ import {getTrimmedSelection} from './selector.get-trimmed-selection'
9
+
10
+ const keyGenerator = createTestKeyGenerator()
11
+
12
+ function snapshot(
13
+ value: Array<Partial<PortableTextBlock>>,
14
+ selection: EditorSelection,
15
+ ) {
16
+ const schema = compileSchemaDefinition(
17
+ defineSchema({
18
+ blockObjects: [{name: 'image'}],
19
+ inlineObjects: [{name: 'stock-ticker'}],
20
+ }),
21
+ )
22
+
23
+ return {
24
+ context: {
25
+ activeDecorators: [],
26
+ converters: [],
27
+ keyGenerator,
28
+ schema,
29
+ selection,
30
+ value: value.flatMap((block) => {
31
+ const parsedBlock = parseBlock({
32
+ context: {
33
+ keyGenerator,
34
+ schema,
35
+ },
36
+ block,
37
+ options: {
38
+ refreshKeys: false,
39
+ },
40
+ })
41
+
42
+ return parsedBlock ? [parsedBlock] : []
43
+ }),
44
+ },
45
+ } satisfies EditorSnapshot
46
+ }
47
+
48
+ function createSpan(text: string, marks: Array<string> = []) {
49
+ return {
50
+ _key: keyGenerator(),
51
+ _type: 'span',
52
+ text,
53
+ marks,
54
+ }
55
+ }
56
+
57
+ function createBlock(children: PortableTextBlock['children']) {
58
+ return {
59
+ _key: keyGenerator(),
60
+ _type: 'block',
61
+ children,
62
+ }
63
+ }
64
+
65
+ function createStockTicker(symbol: string) {
66
+ return {
67
+ _key: keyGenerator(),
68
+ _type: 'stock-ticker',
69
+ symbol,
70
+ }
71
+ }
72
+
73
+ function createImage(src: string) {
74
+ return {
75
+ _key: keyGenerator(),
76
+ _type: 'image',
77
+ src,
78
+ }
79
+ }
80
+
81
+ describe(getTrimmedSelection.name, () => {
82
+ test('Sensible defaults', () => {
83
+ expect(getTrimmedSelection(snapshot([], null))).toBe(null)
84
+ expect(
85
+ getTrimmedSelection(snapshot([createBlock([createSpan('foo')])], null)),
86
+ ).toBe(null)
87
+ })
88
+
89
+ test('does not trim spans that have selected text', () => {
90
+ const foo = createSpan('foo')
91
+ const bar = createSpan('bar', ['strong'])
92
+ const baz = createSpan('baz')
93
+ const block = createBlock([foo, bar, baz])
94
+
95
+ expect(
96
+ getTrimmedSelection(
97
+ snapshot([block], {
98
+ anchor: {
99
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
100
+ offset: 0,
101
+ },
102
+ focus: {
103
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
104
+ offset: 3,
105
+ },
106
+ }),
107
+ ),
108
+ ).toEqual({
109
+ anchor: {
110
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
111
+ offset: 0,
112
+ },
113
+ focus: {
114
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
115
+ offset: 3,
116
+ },
117
+ })
118
+
119
+ expect(
120
+ getTrimmedSelection(
121
+ snapshot([block], {
122
+ anchor: {
123
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
124
+ offset: 2,
125
+ },
126
+ focus: {
127
+ path: [{_key: block._key}, 'children', {_key: baz._key}],
128
+ offset: 2,
129
+ },
130
+ }),
131
+ ),
132
+ ).toEqual({
133
+ anchor: {
134
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
135
+ offset: 2,
136
+ },
137
+ focus: {
138
+ path: [{_key: block._key}, 'children', {_key: baz._key}],
139
+ offset: 2,
140
+ },
141
+ })
142
+ })
143
+
144
+ test('trims spans that have no selected text', () => {
145
+ const foo = createSpan('foo')
146
+ const bar = createSpan('bar', ['strong'])
147
+ const baz = createSpan('baz')
148
+ const block = createBlock([foo, bar, baz])
149
+
150
+ expect(
151
+ getTrimmedSelection(
152
+ snapshot([block], {
153
+ anchor: {
154
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
155
+ offset: 3,
156
+ },
157
+ focus: {
158
+ path: [{_key: block._key}, 'children', {_key: baz._key}],
159
+ offset: 0,
160
+ },
161
+ }),
162
+ ),
163
+ ).toEqual({
164
+ anchor: {
165
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
166
+ offset: 0,
167
+ },
168
+ focus: {
169
+ path: [{_key: block._key}, 'children', {_key: bar._key}],
170
+ offset: 3,
171
+ },
172
+ })
173
+ })
174
+
175
+ test('trims inline objects at the start edge', () => {
176
+ const aapl = createStockTicker('AAPL')
177
+ const foo = createSpan('foo')
178
+ const block = createBlock([aapl, foo])
179
+
180
+ expect(
181
+ getTrimmedSelection(
182
+ snapshot([block], {
183
+ anchor: {
184
+ path: [{_key: block._key}, 'children', {_key: aapl._key}],
185
+ offset: 0,
186
+ },
187
+ focus: {
188
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
189
+ offset: 3,
190
+ },
191
+ }),
192
+ ),
193
+ ).toEqual({
194
+ anchor: {
195
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
196
+ offset: 0,
197
+ },
198
+ focus: {
199
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
200
+ offset: 3,
201
+ },
202
+ })
203
+ })
204
+
205
+ test('trims inline objects at the end edge', () => {
206
+ const foo = createSpan('foo')
207
+ const aapl = createStockTicker('AAPL')
208
+ const block = createBlock([foo, aapl])
209
+
210
+ expect(
211
+ getTrimmedSelection(
212
+ snapshot([block], {
213
+ anchor: {
214
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
215
+ offset: 0,
216
+ },
217
+ focus: {
218
+ path: [{_key: block._key}, 'children', {_key: aapl._key}],
219
+ offset: 0,
220
+ },
221
+ }),
222
+ ),
223
+ ).toEqual({
224
+ anchor: {
225
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
226
+ offset: 0,
227
+ },
228
+ focus: {
229
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
230
+ offset: 3,
231
+ },
232
+ })
233
+ })
234
+
235
+ test('trims empty spans', () => {
236
+ const empty1 = createSpan('')
237
+ const aapl = createStockTicker('AAPL')
238
+ const foo = createSpan('foo')
239
+ const nvda = createStockTicker('NVDA')
240
+ const empty2 = createSpan('')
241
+ const block = createBlock([empty1, aapl, foo, nvda, empty2])
242
+
243
+ expect(
244
+ getTrimmedSelection(
245
+ snapshot([block], {
246
+ anchor: {
247
+ path: [{_key: block._key}, 'children', {_key: empty1._key}],
248
+ offset: 0,
249
+ },
250
+ focus: {
251
+ path: [{_key: block._key}, 'children', {_key: empty2._key}],
252
+ offset: 0,
253
+ },
254
+ }),
255
+ ),
256
+ ).toEqual({
257
+ anchor: {
258
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
259
+ offset: 0,
260
+ },
261
+ focus: {
262
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
263
+ offset: 3,
264
+ },
265
+ })
266
+ })
267
+
268
+ test('trims off block at the start edge', () => {
269
+ const empty1 = createSpan('')
270
+ const aapl = createStockTicker('AAPL')
271
+ const foo = createSpan('foo')
272
+ const empty2 = createSpan('')
273
+ const nvda = createStockTicker('NVDA')
274
+ const bar = createSpan('bar')
275
+ const block1 = createBlock([foo, aapl, empty1])
276
+ const block2 = createBlock([empty2, nvda, bar])
277
+
278
+ expect(
279
+ getTrimmedSelection(
280
+ snapshot([block1, block2], {
281
+ anchor: {
282
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
283
+ offset: 3,
284
+ },
285
+ focus: {
286
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
287
+ offset: 3,
288
+ },
289
+ }),
290
+ ),
291
+ ).toEqual({
292
+ anchor: {
293
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
294
+ offset: 0,
295
+ },
296
+ focus: {
297
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
298
+ offset: 3,
299
+ },
300
+ })
301
+ })
302
+
303
+ test('trims off block at the end edge', () => {
304
+ const empty1 = createSpan('')
305
+ const aapl = createStockTicker('AAPL')
306
+ const foo = createSpan('foo')
307
+ const empty2 = createSpan('')
308
+ const nvda = createStockTicker('NVDA')
309
+ const bar = createSpan('bar')
310
+ const block1 = createBlock([foo, aapl, empty1])
311
+ const block2 = createBlock([empty2, nvda, bar])
312
+
313
+ expect(
314
+ getTrimmedSelection(
315
+ snapshot([block1, block2], {
316
+ anchor: {
317
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
318
+ offset: 0,
319
+ },
320
+ focus: {
321
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
322
+ offset: 0,
323
+ },
324
+ }),
325
+ ),
326
+ ).toEqual({
327
+ anchor: {
328
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
329
+ offset: 0,
330
+ },
331
+ focus: {
332
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
333
+ offset: 3,
334
+ },
335
+ })
336
+ })
337
+
338
+ test('ignores empty text block on start edge', () => {
339
+ const empty = createSpan('')
340
+ const block1 = createBlock([empty])
341
+ const foo = createSpan('foo')
342
+ const block2 = createBlock([foo])
343
+
344
+ expect(
345
+ getTrimmedSelection(
346
+ snapshot([block1, block2], {
347
+ anchor: {
348
+ path: [{_key: block1._key}, 'children', {_key: empty._key}],
349
+ offset: 0,
350
+ },
351
+ focus: {
352
+ path: [{_key: block2._key}, 'children', {_key: foo._key}],
353
+ offset: 3,
354
+ },
355
+ }),
356
+ ),
357
+ ).toEqual({
358
+ anchor: {
359
+ path: [{_key: block1._key}, 'children', {_key: empty._key}],
360
+ offset: 0,
361
+ },
362
+ focus: {
363
+ path: [{_key: block2._key}, 'children', {_key: foo._key}],
364
+ offset: 3,
365
+ },
366
+ })
367
+ })
368
+
369
+ test('ignores empty text block on end edge', () => {
370
+ const foo = createSpan('foo')
371
+ const block1 = createBlock([foo])
372
+ const empty = createSpan('')
373
+ const block2 = createBlock([empty])
374
+
375
+ expect(
376
+ getTrimmedSelection(
377
+ snapshot([block1, block2], {
378
+ anchor: {
379
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
380
+ offset: 0,
381
+ },
382
+ focus: {
383
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
384
+ offset: 0,
385
+ },
386
+ }),
387
+ ),
388
+ ).toEqual({
389
+ anchor: {
390
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
391
+ offset: 0,
392
+ },
393
+ focus: {
394
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
395
+ offset: 0,
396
+ },
397
+ })
398
+ })
399
+
400
+ test('ignores block object on start edge', () => {
401
+ const image1 = createImage('image1')
402
+ const empty1 = createSpan('')
403
+ const aapl = createStockTicker('AAPL')
404
+ const foo = createSpan('foo')
405
+ const nvda = createStockTicker('NVDA')
406
+ const empty2 = createSpan('')
407
+ const block = createBlock([empty1, aapl, foo, nvda, empty2])
408
+ const image2 = createImage('image2')
409
+
410
+ expect(
411
+ getTrimmedSelection(
412
+ snapshot([image1, block, image2], {
413
+ anchor: {
414
+ path: [{_key: image1._key}],
415
+ offset: 0,
416
+ },
417
+ focus: {
418
+ path: [{_key: block._key}, 'children', {_key: empty2._key}],
419
+ offset: 0,
420
+ },
421
+ }),
422
+ ),
423
+ ).toEqual({
424
+ anchor: {
425
+ path: [{_key: image1._key}],
426
+ offset: 0,
427
+ },
428
+ focus: {
429
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
430
+ offset: 3,
431
+ },
432
+ })
433
+ })
434
+
435
+ test('ignores block object on end edge', () => {
436
+ const image1 = createImage('image1')
437
+ const empty1 = createSpan('')
438
+ const aapl = createStockTicker('AAPL')
439
+ const foo = createSpan('foo')
440
+ const nvda = createStockTicker('NVDA')
441
+ const empty2 = createSpan('')
442
+ const block = createBlock([empty1, aapl, foo, nvda, empty2])
443
+ const image2 = createImage('image2')
444
+
445
+ expect(
446
+ getTrimmedSelection(
447
+ snapshot([image1, block, image2], {
448
+ anchor: {
449
+ path: [{_key: block._key}, 'children', {_key: empty1._key}],
450
+ offset: 0,
451
+ },
452
+ focus: {
453
+ path: [{_key: image2._key}],
454
+ offset: 0,
455
+ },
456
+ }),
457
+ ),
458
+ ).toEqual({
459
+ anchor: {
460
+ path: [{_key: block._key}, 'children', {_key: foo._key}],
461
+ offset: 0,
462
+ },
463
+ focus: {
464
+ path: [{_key: image2._key}],
465
+ offset: 0,
466
+ },
467
+ })
468
+ })
469
+
470
+ test('ignores block objects on start and end edge', () => {
471
+ const image1 = createImage('image1')
472
+ const empty1 = createSpan('')
473
+ const aapl = createStockTicker('AAPL')
474
+ const foo = createSpan('foo')
475
+ const nvda = createStockTicker('NVDA')
476
+ const empty2 = createSpan('')
477
+ const block = createBlock([empty1, aapl, foo, nvda, empty2])
478
+ const image2 = createImage('image2')
479
+
480
+ expect(
481
+ getTrimmedSelection(
482
+ snapshot([image1, block, image2], {
483
+ anchor: {
484
+ path: [{_key: image1._key}],
485
+ offset: 0,
486
+ },
487
+ focus: {
488
+ path: [{_key: image2._key}],
489
+ offset: 0,
490
+ },
491
+ }),
492
+ ),
493
+ ).toEqual({
494
+ anchor: {
495
+ path: [{_key: image1._key}],
496
+ offset: 0,
497
+ },
498
+ focus: {
499
+ path: [{_key: image2._key}],
500
+ offset: 0,
501
+ },
502
+ })
503
+ })
504
+
505
+ test('edge case', () => {
506
+ expect(
507
+ getTrimmedSelection(
508
+ snapshot(
509
+ [
510
+ {
511
+ _key: 'b0',
512
+ _type: 'block',
513
+ children: [
514
+ {
515
+ _key: 's0',
516
+ _type: 'span',
517
+ text: 'foo',
518
+ },
519
+ {
520
+ _key: 's1',
521
+ _type: 'span',
522
+ text: '',
523
+ },
524
+ ],
525
+ },
526
+ {
527
+ _key: 'b1',
528
+ _type: 'block',
529
+ children: [
530
+ {
531
+ _key: 's2',
532
+ _type: 'span',
533
+ text: '',
534
+ },
535
+ {
536
+ _key: 's3',
537
+ _type: 'span',
538
+ text: 'bar',
539
+ },
540
+ ],
541
+ },
542
+ ],
543
+ {
544
+ anchor: {
545
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
546
+ offset: 3,
547
+ },
548
+ focus: {
549
+ path: [{_key: 'b1'}, 'children', {_key: 's2'}],
550
+ offset: 0,
551
+ },
552
+ },
553
+ ),
554
+ ),
555
+ ).toEqual(null)
556
+ })
557
+
558
+ test('edge case #2', () => {
559
+ const foo = createSpan('foo')
560
+ const block1 = createBlock([foo])
561
+ const empty = createSpan('')
562
+ const block2 = createBlock([empty])
563
+ const bar = createSpan('bar')
564
+ const block3 = createBlock([bar])
565
+
566
+ expect(
567
+ getTrimmedSelection(
568
+ snapshot([block1, block2, block3], {
569
+ anchor: {
570
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
571
+ offset: 3,
572
+ },
573
+ focus: {
574
+ path: [{_key: block3._key}, 'children', {_key: bar._key}],
575
+ offset: 0,
576
+ },
577
+ }),
578
+ ),
579
+ ).toEqual({
580
+ anchor: {
581
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
582
+ offset: 0,
583
+ },
584
+ focus: {
585
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
586
+ offset: 0,
587
+ },
588
+ })
589
+ })
590
+
591
+ test('edge case #3', () => {
592
+ const foo = createSpan('foo')
593
+ const block1 = createBlock([foo])
594
+ const empty = createSpan('')
595
+ const block2 = createBlock([empty])
596
+ const bar = createSpan('bar')
597
+ const block3 = createBlock([bar])
598
+
599
+ expect(
600
+ getTrimmedSelection(
601
+ snapshot([block1, block2, block3], {
602
+ anchor: {
603
+ path: [{_key: block3._key}, 'children', {_key: bar._key}],
604
+ offset: 0,
605
+ },
606
+ focus: {
607
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
608
+ offset: 3,
609
+ },
610
+ backward: true,
611
+ }),
612
+ ),
613
+ ).toEqual({
614
+ anchor: {
615
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
616
+ offset: 0,
617
+ },
618
+ focus: {
619
+ path: [{_key: block2._key}, 'children', {_key: empty._key}],
620
+ offset: 0,
621
+ },
622
+ backward: true,
623
+ })
624
+ })
625
+
626
+ test('backwards selection', () => {
627
+ const foo = createSpan('foo')
628
+ const block1 = createBlock([foo])
629
+ const bar = createSpan('bar')
630
+ const block2 = createBlock([bar])
631
+
632
+ expect(
633
+ getTrimmedSelection(
634
+ snapshot([block1, block2], {
635
+ anchor: {
636
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
637
+ offset: 3,
638
+ },
639
+ focus: {
640
+ path: [{_key: block1._key}, 'children', {_key: foo._key}],
641
+ offset: 3,
642
+ },
643
+ backward: true,
644
+ }),
645
+ ),
646
+ ).toEqual({
647
+ anchor: {
648
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
649
+ offset: 3,
650
+ },
651
+ focus: {
652
+ path: [{_key: block2._key}, 'children', {_key: bar._key}],
653
+ offset: 0,
654
+ },
655
+ backward: true,
656
+ })
657
+ })
658
+ })