@portabletext/editor 1.33.2 → 1.33.3

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/plugin.event-listener.cjs +12 -8
  2. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +28 -22
  4. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
  5. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs +4 -2
  6. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -1
  7. package/lib/_chunks-cjs/util.reverse-selection.cjs +34 -15
  8. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
  9. package/lib/_chunks-es/plugin.event-listener.js +12 -8
  10. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  11. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +29 -23
  12. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  13. package/lib/_chunks-es/util.block-offsets-to-selection.js +4 -2
  14. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -1
  15. package/lib/_chunks-es/util.reverse-selection.js +34 -15
  16. package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
  17. package/lib/plugins/index.cjs +8 -4
  18. package/lib/plugins/index.cjs.map +1 -1
  19. package/lib/plugins/index.js +8 -4
  20. package/lib/plugins/index.js.map +1 -1
  21. package/lib/selectors/index.d.cts +2 -0
  22. package/lib/selectors/index.d.ts +2 -0
  23. package/lib/utils/index.d.cts +2 -0
  24. package/lib/utils/index.d.ts +2 -0
  25. package/package.json +2 -2
  26. package/src/behaviors/behavior.markdown-emphasis.ts +4 -0
  27. package/src/editor/PortableTextEditor.tsx +1 -1
  28. package/src/plugins/plugin.event-listener.tsx +1 -1
  29. package/src/selectors/selector.get-caret-word-selection.ts +9 -0
  30. package/src/selectors/selector.get-selection-text.test.ts +383 -36
  31. package/src/selectors/selector.get-selection-text.ts +57 -42
  32. package/src/utils/util.block-offset.test.ts +312 -0
  33. package/src/utils/util.block-offset.ts +39 -7
  34. package/src/utils/util.block-offsets-to-selection.ts +2 -0
@@ -1,16 +1,369 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
1
2
  import {expect, test} from 'vitest'
2
- import type {EditorSchema, EditorSelection, EditorSnapshot} from '.'
3
+ import type {EditorSelection, EditorSnapshot} from '.'
4
+ import {compileSchemaDefinition, defineSchema} from '../editor/define-schema'
3
5
  import {getSelectionText} from './selector.get-selection-text'
4
6
 
7
+ const brokenBlock = {
8
+ _type: 'block',
9
+ _key: 'b0',
10
+ style: 'normal',
11
+ markDefs: [],
12
+ children: [
13
+ {
14
+ _key: 's0',
15
+ _type: 'span',
16
+ text: '',
17
+ },
18
+ {
19
+ _key: 's1',
20
+ _type: 'stock-ticker',
21
+ },
22
+ {
23
+ _key: 's2',
24
+ _type: 'span',
25
+ text: 'b',
26
+ },
27
+ {
28
+ _key: 's3',
29
+ _type: 'span',
30
+ text: 'a',
31
+ },
32
+ {
33
+ _key: 's4',
34
+ _type: 'span',
35
+ text: 'r',
36
+ },
37
+ {
38
+ _key: 's5',
39
+ _type: 'stock-ticker',
40
+ },
41
+ {
42
+ _key: 's6',
43
+ _type: 'span',
44
+ text: '',
45
+ },
46
+ ],
47
+ }
48
+ const bazBlock = {
49
+ _type: 'block',
50
+ _key: 'b1',
51
+ style: 'normal',
52
+ markDefs: [],
53
+ children: [
54
+ {
55
+ _key: 's7',
56
+ _type: 'span',
57
+ text: 'baz',
58
+ },
59
+ ],
60
+ }
61
+ const imageBlock = {
62
+ _type: 'image',
63
+ _key: 'b2',
64
+ }
65
+
5
66
  test(getSelectionText.name, () => {
6
- function snapshot(selection: EditorSelection): EditorSnapshot {
67
+ function snapshot(
68
+ value: Array<PortableTextBlock>,
69
+ selection: EditorSelection,
70
+ ): EditorSnapshot {
7
71
  return {
8
72
  context: {
9
73
  converters: [],
10
- schema: {} as EditorSchema,
74
+ schema: compileSchemaDefinition(
75
+ defineSchema({
76
+ inlineObjects: [{name: 'stock-ticker'}],
77
+ }),
78
+ ),
11
79
  keyGenerator: () => '',
12
80
  activeDecorators: [],
13
- value: [
81
+ value,
82
+ selection,
83
+ },
84
+ }
85
+ }
86
+
87
+ expect(
88
+ getSelectionText(
89
+ snapshot(
90
+ [
91
+ {
92
+ _key: 'k0',
93
+ _type: 'block',
94
+ children: [
95
+ {
96
+ _type: 'span',
97
+ _key: 'k1',
98
+ text: 'f',
99
+ marks: ['strong'],
100
+ },
101
+ {
102
+ _type: 'span',
103
+ _key: 'k2',
104
+ marks: ['strong', 'em'],
105
+ text: 'oo b',
106
+ },
107
+ {
108
+ _type: 'span',
109
+ _key: 'k3',
110
+ marks: ['strong', 'em', 'underline'],
111
+ text: 'a',
112
+ },
113
+ {
114
+ _type: 'span',
115
+ _key: 'k4',
116
+ marks: ['strong', 'underline'],
117
+ text: 'r ba',
118
+ },
119
+ {
120
+ _type: 'span',
121
+ _key: 'k5',
122
+ marks: ['strong'],
123
+ text: 'z',
124
+ },
125
+ ],
126
+ },
127
+ ],
128
+ {
129
+ anchor: {path: [{_key: 'k0'}, 'children', {_key: 'k1'}], offset: 0},
130
+ focus: {path: [{_key: 'k0'}, 'children', {_key: 'k3'}], offset: 0},
131
+ },
132
+ ),
133
+ ),
134
+ ).toBe('foo b')
135
+ expect(
136
+ getSelectionText(
137
+ snapshot(
138
+ [
139
+ {
140
+ _key: 'b0',
141
+ _type: 'block',
142
+ children: [{_key: 's0', _type: 'span', text: 'foo bar'}],
143
+ },
144
+ ],
145
+ {
146
+ anchor: {
147
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
148
+ offset: 0,
149
+ },
150
+ focus: {
151
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
152
+ offset: 3,
153
+ },
154
+ },
155
+ ),
156
+ ),
157
+ ).toBe('foo')
158
+ expect(
159
+ getSelectionText(
160
+ snapshot(
161
+ [
162
+ {
163
+ _key: 'b0',
164
+ _type: 'block',
165
+ children: [{_key: 's0', _type: 'span', text: 'foo bar'}],
166
+ },
167
+ ],
168
+ {
169
+ anchor: {
170
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
171
+ offset: 3,
172
+ },
173
+ focus: {
174
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
175
+ offset: 7,
176
+ },
177
+ },
178
+ ),
179
+ ),
180
+ ).toBe(' bar')
181
+ expect(
182
+ getSelectionText(
183
+ snapshot([brokenBlock], {
184
+ anchor: {
185
+ path: [{_key: 'b0'}, 'children', {_key: 's0'}],
186
+ offset: 0,
187
+ },
188
+ focus: {
189
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
190
+ offset: 0,
191
+ },
192
+ }),
193
+ ),
194
+ ).toBe('bar')
195
+ expect(
196
+ getSelectionText(
197
+ snapshot([brokenBlock], {
198
+ anchor: {
199
+ path: [{_key: 'b0'}, 'children', {_key: 's1'}],
200
+ offset: 0,
201
+ },
202
+ focus: {
203
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
204
+ offset: 0,
205
+ },
206
+ }),
207
+ ),
208
+ ).toBe('bar')
209
+ expect(
210
+ getSelectionText(
211
+ snapshot([brokenBlock], {
212
+ anchor: {
213
+ path: [{_key: 'b0'}, 'children', {_key: 's2'}],
214
+ offset: 0,
215
+ },
216
+ focus: {
217
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
218
+ offset: 0,
219
+ },
220
+ }),
221
+ ),
222
+ ).toBe('bar')
223
+ expect(
224
+ getSelectionText(
225
+ snapshot([brokenBlock], {
226
+ anchor: {
227
+ path: [{_key: 'b0'}, 'children', {_key: 's2'}],
228
+ offset: 1,
229
+ },
230
+ focus: {
231
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
232
+ offset: 0,
233
+ },
234
+ }),
235
+ ),
236
+ ).toBe('ar')
237
+ expect(
238
+ getSelectionText(
239
+ snapshot([brokenBlock], {
240
+ anchor: {
241
+ path: [{_key: 'b0'}, 'children', {_key: 's3'}],
242
+ offset: 0,
243
+ },
244
+ focus: {
245
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
246
+ offset: 0,
247
+ },
248
+ }),
249
+ ),
250
+ ).toBe('ar')
251
+ expect(
252
+ getSelectionText(
253
+ snapshot([brokenBlock], {
254
+ anchor: {
255
+ path: [{_key: 'b0'}, 'children', {_key: 's3'}],
256
+ offset: 1,
257
+ },
258
+ focus: {
259
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
260
+ offset: 0,
261
+ },
262
+ }),
263
+ ),
264
+ ).toBe('r')
265
+ expect(
266
+ getSelectionText(
267
+ snapshot([brokenBlock], {
268
+ anchor: {
269
+ path: [{_key: 'b0'}, 'children', {_key: 's4'}],
270
+ offset: 0,
271
+ },
272
+ focus: {
273
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
274
+ offset: 0,
275
+ },
276
+ }),
277
+ ),
278
+ ).toBe('r')
279
+ expect(
280
+ getSelectionText(
281
+ snapshot([brokenBlock], {
282
+ anchor: {
283
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
284
+ offset: 0,
285
+ },
286
+ focus: {
287
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
288
+ offset: 0,
289
+ },
290
+ }),
291
+ ),
292
+ ).toBe('')
293
+ expect(
294
+ getSelectionText(
295
+ snapshot([brokenBlock], {
296
+ anchor: {
297
+ path: [{_key: 'b0'}, 'children', {_key: 's6'}],
298
+ offset: 0,
299
+ },
300
+ focus: {
301
+ path: [{_key: 'b0'}, 'children', {_key: 's5'}],
302
+ offset: 0,
303
+ },
304
+ }),
305
+ ),
306
+ ).toBe('')
307
+ expect(
308
+ getSelectionText(
309
+ snapshot([brokenBlock, bazBlock], {
310
+ anchor: {
311
+ path: [{_key: 'b0'}, 'children', {_key: 's3'}],
312
+ offset: 0,
313
+ },
314
+ focus: {
315
+ path: [{_key: 'b1'}, 'children', {_key: 's7'}],
316
+ offset: 2,
317
+ },
318
+ }),
319
+ ),
320
+ ).toBe('arba')
321
+ expect(
322
+ getSelectionText(
323
+ snapshot([brokenBlock, bazBlock], {
324
+ anchor: {
325
+ path: [{_key: 'b0'}, 'children', {_key: 's3'}],
326
+ offset: 0,
327
+ },
328
+ focus: {
329
+ path: [{_key: 'b1'}, 'children', {_key: 's7'}],
330
+ offset: 0,
331
+ },
332
+ }),
333
+ ),
334
+ ).toBe('ar')
335
+ expect(
336
+ getSelectionText(
337
+ snapshot([brokenBlock, imageBlock, bazBlock], {
338
+ anchor: {
339
+ path: [{_key: imageBlock._key}],
340
+ offset: 0,
341
+ },
342
+ focus: {
343
+ path: [{_key: 'b1'}, 'children', {_key: 's7'}],
344
+ offset: 0,
345
+ },
346
+ }),
347
+ ),
348
+ ).toBe('')
349
+ expect(
350
+ getSelectionText(
351
+ snapshot([brokenBlock, imageBlock, bazBlock], {
352
+ anchor: {
353
+ path: [{_key: imageBlock._key}],
354
+ offset: 0,
355
+ },
356
+ focus: {
357
+ path: [{_key: 'b1'}, 'children', {_key: 's7'}],
358
+ offset: 1,
359
+ },
360
+ }),
361
+ ),
362
+ ).toBe('b')
363
+ expect(
364
+ getSelectionText(
365
+ snapshot(
366
+ [
14
367
  {
15
368
  _type: 'block',
16
369
  _key: 'e0-k8',
@@ -38,39 +391,33 @@ test(getSelectionText.name, () => {
38
391
  ],
39
392
  },
40
393
  ],
41
- selection,
42
- },
43
- }
44
- }
45
-
46
- expect(
47
- getSelectionText(
48
- snapshot({
49
- anchor: {
50
- path: [
51
- {
52
- _key: 'e0-k8',
53
- },
54
- 'children',
55
- {
56
- _key: 'e0-k7',
57
- },
58
- ],
59
- offset: 0,
60
- },
61
- focus: {
62
- path: [
63
- {
64
- _key: 'e0-k8',
65
- },
66
- 'children',
67
- {
68
- _key: 'e0-k10',
69
- },
70
- ],
71
- offset: 1,
394
+ {
395
+ anchor: {
396
+ path: [
397
+ {
398
+ _key: 'e0-k8',
399
+ },
400
+ 'children',
401
+ {
402
+ _key: 'e0-k7',
403
+ },
404
+ ],
405
+ offset: 0,
406
+ },
407
+ focus: {
408
+ path: [
409
+ {
410
+ _key: 'e0-k8',
411
+ },
412
+ 'children',
413
+ {
414
+ _key: 'e0-k10',
415
+ },
416
+ ],
417
+ offset: 1,
418
+ },
72
419
  },
73
- }),
420
+ ),
74
421
  ),
75
422
  ).toBe(':bar')
76
423
  })
@@ -23,62 +23,77 @@ export const getSelectionText: EditorSelector<string> = ({context}) => {
23
23
  return text
24
24
  }
25
25
 
26
- for (const block of value) {
27
- if (
28
- isKeyedSegment(forwardSelection.anchor.path[0]) &&
29
- block._key !== forwardSelection.anchor.path[0]._key
30
- ) {
31
- continue
32
- }
26
+ const startBlockKey = isKeyedSegment(forwardSelection.anchor.path[0])
27
+ ? forwardSelection.anchor.path[0]._key
28
+ : undefined
29
+ const endBlockKey = isKeyedSegment(forwardSelection.focus.path[0])
30
+ ? forwardSelection.focus.path[0]._key
31
+ : undefined
32
+ const startChildKey = isKeyedSegment(forwardSelection.anchor.path[2])
33
+ ? forwardSelection.anchor.path[2]._key
34
+ : undefined
35
+ const endChildKey = isKeyedSegment(forwardSelection.focus.path[2])
36
+ ? forwardSelection.focus.path[2]._key
37
+ : undefined
38
+ let startFound = false
33
39
 
34
- if (!isPortableTextTextBlock(block)) {
35
- continue
36
- }
40
+ if (!startBlockKey || !endBlockKey) {
41
+ return text
42
+ }
37
43
 
38
- for (const child of block.children) {
39
- if (isPortableTextSpan(child)) {
40
- if (
41
- isKeyedSegment(forwardSelection.anchor.path[2]) &&
42
- child._key === forwardSelection.anchor.path[2]._key &&
43
- isKeyedSegment(forwardSelection.focus.path[2]) &&
44
- child._key === forwardSelection.focus.path[2]._key
45
- ) {
46
- text =
47
- text +
48
- child.text.slice(
49
- forwardSelection.anchor.offset,
50
- forwardSelection.focus.offset,
51
- )
44
+ for (const block of value) {
45
+ if (block._key === startBlockKey) {
46
+ if (!isPortableTextTextBlock(block)) {
47
+ continue
48
+ }
52
49
 
53
- break
50
+ for (const child of block.children) {
51
+ if (child._key === startChildKey) {
52
+ startFound = true
54
53
  }
55
54
 
56
- if (
57
- isKeyedSegment(forwardSelection.anchor.path[2]) &&
58
- child._key === forwardSelection.anchor.path[2]._key
59
- ) {
60
- text = text + child.text.slice(forwardSelection.anchor.offset)
55
+ if (!startFound) {
61
56
  continue
62
57
  }
63
58
 
64
- if (
65
- isKeyedSegment(forwardSelection.focus.path[2]) &&
66
- child._key === forwardSelection.focus.path[2]._key
67
- ) {
68
- text = text + child.text.slice(0, forwardSelection.focus.offset)
69
- break
59
+ if (isPortableTextSpan(child) && startChildKey) {
60
+ if (child._key === startChildKey && child._key === endChildKey) {
61
+ text =
62
+ text +
63
+ child.text.slice(
64
+ forwardSelection.anchor.offset,
65
+ forwardSelection.focus.offset,
66
+ )
67
+ } else if (child._key === startChildKey) {
68
+ text = text + child.text.slice(forwardSelection.anchor.offset)
69
+ } else if (child._key === endChildKey) {
70
+ text = text + child.text.slice(0, forwardSelection.focus.offset)
71
+ } else {
72
+ text = text + child.text
73
+ }
70
74
  }
71
75
 
72
- if (text.length > 0) {
73
- text = text + child.text
76
+ if (child._key === endChildKey) {
77
+ break
74
78
  }
75
79
  }
80
+ continue
76
81
  }
77
82
 
78
- if (
79
- isKeyedSegment(forwardSelection.focus.path[0]) &&
80
- block._key === forwardSelection.focus.path[0]._key
81
- ) {
83
+ if (block._key === endBlockKey) {
84
+ if (!isPortableTextTextBlock(block)) {
85
+ continue
86
+ }
87
+
88
+ for (const child of block.children) {
89
+ if (isPortableTextSpan(child) && endChildKey) {
90
+ text = text + child.text.slice(0, forwardSelection.focus.offset)
91
+ }
92
+
93
+ if (child._key === endChildKey) {
94
+ break
95
+ }
96
+ }
82
97
  break
83
98
  }
84
99
  }