@portabletext/editor 2.15.4 → 2.16.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.
@@ -5,6 +5,69 @@ import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
5
5
  import {getMarkState} from './selector.get-mark-state'
6
6
 
7
7
  describe(getMarkState.name, () => {
8
+ test('Scenario: Caret after annotation', () => {
9
+ const keyGenerator = createTestKeyGenerator()
10
+ const blockKey = keyGenerator()
11
+ const fooSpanKey = keyGenerator()
12
+ const barSpanKey = keyGenerator()
13
+ const bazSpanKey = keyGenerator()
14
+ const linkKey = keyGenerator()
15
+ const snapshot = createTestSnapshot({
16
+ context: {
17
+ keyGenerator,
18
+ value: [
19
+ {
20
+ _type: 'block',
21
+ _key: blockKey,
22
+ children: [
23
+ {
24
+ _type: 'span',
25
+ _key: fooSpanKey,
26
+ text: 'foo ',
27
+ marks: [],
28
+ },
29
+ {
30
+ _type: 'span',
31
+ _key: barSpanKey,
32
+ text: 'bar',
33
+ marks: [linkKey],
34
+ },
35
+ {
36
+ _type: 'span',
37
+ _key: bazSpanKey,
38
+ text: ' baz',
39
+ },
40
+ ],
41
+ markDefs: [
42
+ {
43
+ _type: 'link',
44
+ _key: linkKey,
45
+ href: 'https://portabletext.org',
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ selection: {
51
+ anchor: {
52
+ path: [{_key: blockKey}, 'children', {_key: barSpanKey}],
53
+ offset: 3,
54
+ },
55
+ focus: {
56
+ path: [{_key: blockKey}, 'children', {_key: barSpanKey}],
57
+ offset: 3,
58
+ },
59
+ backward: false,
60
+ },
61
+ },
62
+ })
63
+
64
+ expect(getMarkState(snapshot)).toEqual({
65
+ state: 'changed',
66
+ marks: [],
67
+ previousMarks: [linkKey],
68
+ })
69
+ })
70
+
8
71
  test('Scenario: Caret after annotated decorator', () => {
9
72
  const keyGenerator = createTestKeyGenerator()
10
73
  const blockKey = keyGenerator()
@@ -136,4 +199,127 @@ describe(getMarkState.name, () => {
136
199
  previousMarks: [linkKey, 'strong'],
137
200
  })
138
201
  })
202
+
203
+ test('Scenario: Caret in annotation, at the edge of decorator', () => {
204
+ const keyGenerator = createTestKeyGenerator()
205
+ const blockKey = keyGenerator()
206
+ const fooSpanKey = keyGenerator()
207
+ const barSpanKey = keyGenerator()
208
+ const linkKey = keyGenerator()
209
+ const snapshot = createTestSnapshot({
210
+ context: {
211
+ keyGenerator,
212
+ value: [
213
+ {
214
+ _type: 'block',
215
+ _key: blockKey,
216
+ children: [
217
+ {
218
+ _type: 'span',
219
+ _key: fooSpanKey,
220
+ text: 'foo',
221
+ marks: [linkKey],
222
+ },
223
+ {
224
+ _type: 'span',
225
+ _key: barSpanKey,
226
+ text: 'bar',
227
+ marks: [linkKey, 'strong'],
228
+ },
229
+ ],
230
+ },
231
+ ],
232
+ selection: {
233
+ anchor: {
234
+ path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
235
+ offset: 3,
236
+ },
237
+ focus: {
238
+ path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
239
+ offset: 3,
240
+ },
241
+ backward: false,
242
+ },
243
+ schema: compileSchema(
244
+ defineSchema({
245
+ annotations: [{name: 'link'}],
246
+ decorators: [{name: 'strong'}],
247
+ }),
248
+ ),
249
+ },
250
+ })
251
+
252
+ expect(getMarkState(snapshot)).toEqual({
253
+ state: 'unchanged',
254
+ marks: [linkKey],
255
+ })
256
+ })
257
+
258
+ test('Scenario: Caret between two annotations', () => {
259
+ const keyGenerator = createTestKeyGenerator()
260
+ const blockKey = keyGenerator()
261
+ const fooSpanKey = keyGenerator()
262
+ const barSpanKey = keyGenerator()
263
+ const linkKey = keyGenerator()
264
+ const commentKey = keyGenerator()
265
+ const snapshot = createTestSnapshot({
266
+ context: {
267
+ keyGenerator,
268
+ value: [
269
+ {
270
+ _type: 'block',
271
+ _key: blockKey,
272
+ children: [
273
+ {
274
+ _type: 'span',
275
+ _key: fooSpanKey,
276
+ text: 'foo',
277
+ marks: [linkKey],
278
+ },
279
+ {
280
+ _type: 'span',
281
+ _key: barSpanKey,
282
+ text: 'bar',
283
+ marks: [commentKey],
284
+ },
285
+ ],
286
+ markDefs: [
287
+ {
288
+ _type: 'link',
289
+ _key: linkKey,
290
+ href: 'https://portabletext.org',
291
+ },
292
+ {
293
+ _type: 'comment',
294
+ _key: commentKey,
295
+ text: 'Comment A',
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ selection: {
301
+ anchor: {
302
+ path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
303
+ offset: 3,
304
+ },
305
+ focus: {
306
+ path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
307
+ offset: 3,
308
+ },
309
+ backward: false,
310
+ },
311
+ schema: compileSchema(
312
+ defineSchema({
313
+ annotations: [{name: 'link'}, {name: 'comment'}],
314
+ }),
315
+ ),
316
+ },
317
+ })
318
+
319
+ expect(getMarkState(snapshot)).toEqual({
320
+ state: 'changed',
321
+ marks: [],
322
+ previousMarks: [linkKey],
323
+ })
324
+ })
139
325
  })
@@ -196,7 +196,6 @@ export const getMarkState: EditorSelector<MarkState | undefined> = (
196
196
  } else if (previousSpanHasSameAnnotation) {
197
197
  return {
198
198
  state: 'unchanged',
199
- previousMarks: marks,
200
199
  marks: focusSpan.node.marks ?? [],
201
200
  }
202
201
  } else if (!previousSpan) {
@@ -209,26 +208,33 @@ export const getMarkState: EditorSelector<MarkState | undefined> = (
209
208
  }
210
209
 
211
210
  if (atTheEndOfSpan) {
212
- if (
213
- (nextSpan &&
214
- nextSpanSharesSomeAnnotations &&
215
- nextSpanAnnotations.length < spanAnnotations.length) ||
216
- !nextSpanSharesSomeAnnotations
217
- ) {
211
+ if (!nextSpan) {
218
212
  return {
219
213
  state: 'changed',
220
214
  previousMarks: marks,
221
- marks: nextSpan?.node.marks ?? [],
215
+ marks: [],
222
216
  }
223
217
  }
224
218
 
225
- if (!nextSpan) {
219
+ if (nextSpanAnnotations.length > 0 && !nextSpanSharesSomeAnnotations) {
226
220
  return {
227
221
  state: 'changed',
228
222
  previousMarks: marks,
229
223
  marks: [],
230
224
  }
231
225
  }
226
+
227
+ if (
228
+ (nextSpanSharesSomeAnnotations &&
229
+ nextSpanAnnotations.length < spanAnnotations.length) ||
230
+ !nextSpanSharesSomeAnnotations
231
+ ) {
232
+ return {
233
+ state: 'changed',
234
+ previousMarks: marks,
235
+ marks: nextSpan?.node.marks ?? [],
236
+ }
237
+ }
232
238
  }
233
239
  }
234
240
 
@@ -0,0 +1,46 @@
1
+ import type {EditorSelector} from '../editor/editor-selector'
2
+ import {getBlockEndPoint} from '../utils/util.get-block-end-point'
3
+ import {getSelectionEndPoint} from '../utils/util.get-selection-end-point'
4
+ import {getFocusBlock} from './selector.get-focus-block'
5
+ import {getSelectionText} from './selector.get-selection-text'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getBlockTextAfter: EditorSelector<string> = (snapshot) => {
11
+ if (!snapshot.context.selection) {
12
+ return ''
13
+ }
14
+
15
+ const endPoint = getSelectionEndPoint(snapshot.context.selection)
16
+ const block = getFocusBlock({
17
+ ...snapshot,
18
+ context: {
19
+ ...snapshot.context,
20
+ selection: {
21
+ anchor: endPoint,
22
+ focus: endPoint,
23
+ },
24
+ },
25
+ })
26
+
27
+ if (!block) {
28
+ return ''
29
+ }
30
+
31
+ const endOfBlock = getBlockEndPoint({
32
+ context: snapshot.context,
33
+ block,
34
+ })
35
+
36
+ return getSelectionText({
37
+ ...snapshot,
38
+ context: {
39
+ ...snapshot.context,
40
+ selection: {
41
+ anchor: endPoint,
42
+ focus: endOfBlock,
43
+ },
44
+ },
45
+ })
46
+ }
@@ -1,4 +1,4 @@
1
- import type {Locator} from '@vitest/browser/context'
1
+ import type {Locator} from 'vitest/browser'
2
2
  import type {Editor} from '../../editor'
3
3
 
4
4
  /**
@@ -1,8 +1,8 @@
1
1
  import {defineSchema} from '@portabletext/schema'
2
2
  import {getTersePt, parseTersePt} from '@portabletext/test'
3
- import {userEvent} from '@vitest/browser/context'
4
3
  import {Given, Then, When} from 'racejar'
5
4
  import {assert, expect, vi} from 'vitest'
5
+ import {userEvent} from 'vitest/browser'
6
6
  import {getEditorSelection} from '../../internal-utils/editor-selection'
7
7
  import {getSelectionText} from '../../internal-utils/selection-text'
8
8
  import {getTextBlockKey} from '../../internal-utils/text-block-key'
@@ -1,10 +1,10 @@
1
1
  import {defineSchema, type SchemaDefinition} from '@portabletext/schema'
2
2
  import {createTestKeyGenerator} from '@portabletext/test'
3
3
  import type {PortableTextBlock} from '@sanity/types'
4
- import {page} from '@vitest/browser/context'
5
4
  import React from 'react'
6
5
  import {expect, vi} from 'vitest'
7
6
  import {render} from 'vitest-browser-react'
7
+ import {page} from 'vitest/browser'
8
8
  import type {Editor} from '../../editor'
9
9
  import {
10
10
  PortableTextEditable,
@@ -30,13 +30,12 @@ export async function createTestEditor(
30
30
  options: CreateTestEditorOptions = {},
31
31
  ): Promise<
32
32
  Pick<Context, 'editor' | 'locator'> & {
33
- rerender: (options?: CreateTestEditorOptions) => void
33
+ rerender: (options?: CreateTestEditorOptions) => Promise<void>
34
34
  }
35
35
  > {
36
36
  const editorRef = React.createRef<Editor>()
37
37
  const keyGenerator = options.keyGenerator ?? createTestKeyGenerator()
38
-
39
- const renderResult = render(
38
+ const renderResult = await render(
40
39
  <EditorProvider
41
40
  initialConfig={{
42
41
  keyGenerator: keyGenerator,
@@ -50,8 +49,8 @@ export async function createTestEditor(
50
49
  </EditorProvider>,
51
50
  )
52
51
 
53
- function rerender(newOptions?: CreateTestEditorOptions) {
54
- newOptions
52
+ async function rerender(newOptions?: CreateTestEditorOptions) {
53
+ await (newOptions
55
54
  ? renderResult.rerender(
56
55
  <EditorProvider
57
56
  initialConfig={{
@@ -77,7 +76,7 @@ export async function createTestEditor(
77
76
  <PortableTextEditable {...options.editableProps} />
78
77
  {options.children}
79
78
  </EditorProvider>,
80
- )
79
+ ))
81
80
  }
82
81
 
83
82
  const locator = page.getByRole('textbox')