@portabletext/editor 1.1.1 → 1.1.2

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 (51) hide show
  1. package/README.md +3 -0
  2. package/lib/index.d.mts +1667 -0
  3. package/lib/index.d.ts +1667 -0
  4. package/lib/index.esm.js +305 -153
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +305 -154
  7. package/lib/index.js.map +1 -1
  8. package/lib/index.mjs +305 -153
  9. package/lib/index.mjs.map +1 -1
  10. package/package.json +23 -22
  11. package/src/editor/Editable.tsx +30 -31
  12. package/src/editor/PortableTextEditor.tsx +23 -6
  13. package/src/editor/__tests__/PortableTextEditor.test.tsx +9 -9
  14. package/src/editor/__tests__/PortableTextEditorTester.tsx +2 -5
  15. package/src/editor/__tests__/RangeDecorations.test.tsx +2 -2
  16. package/src/editor/__tests__/handleClick.test.tsx +27 -7
  17. package/src/editor/__tests__/insert-block.test.tsx +4 -4
  18. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +7 -7
  19. package/src/editor/__tests__/self-solving.test.tsx +176 -0
  20. package/src/editor/components/Leaf.tsx +28 -23
  21. package/src/editor/components/Synchronizer.tsx +60 -32
  22. package/src/editor/editor-machine.ts +195 -0
  23. package/src/editor/hooks/usePortableTextEditorSelection.tsx +11 -13
  24. package/src/editor/hooks/useSyncValue.test.tsx +9 -9
  25. package/src/editor/hooks/useSyncValue.ts +14 -13
  26. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +1 -1
  27. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +28 -28
  28. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +17 -17
  29. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +8 -8
  30. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +5 -5
  31. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +2 -2
  32. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +46 -46
  33. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +22 -11
  34. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +9 -9
  35. package/src/editor/plugins/createWithInsertData.ts +4 -8
  36. package/src/editor/plugins/createWithObjectKeys.ts +7 -0
  37. package/src/editor/plugins/createWithPatches.ts +5 -6
  38. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +10 -2
  39. package/src/editor/plugins/createWithPortableTextMarkModel.ts +20 -4
  40. package/src/editor/plugins/createWithPortableTextSelections.ts +4 -5
  41. package/src/editor/plugins/createWithSchemaTypes.ts +9 -0
  42. package/src/editor/plugins/index.ts +18 -8
  43. package/src/index.ts +9 -3
  44. package/src/utils/__tests__/dmpToOperations.test.ts +1 -1
  45. package/src/utils/__tests__/operationToPatches.test.ts +61 -61
  46. package/src/utils/__tests__/patchToOperations.test.ts +39 -39
  47. package/src/utils/__tests__/ranges.test.ts +1 -1
  48. package/src/utils/__tests__/valueNormalization.test.tsx +14 -2
  49. package/src/utils/__tests__/values.test.ts +17 -17
  50. package/src/utils/validateValue.ts +0 -22
  51. package/src/editor/__tests__/utils.ts +0 -44
@@ -1,6 +1,6 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
1
  import {render, waitFor} from '@testing-library/react'
3
2
  import {createRef, type RefObject} from 'react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
4
  import {
5
5
  PortableTextEditorTester,
6
6
  schemaType,
@@ -29,7 +29,7 @@ describe('plugin:withPortableTextMarksModel', () => {
29
29
  style: 'normal',
30
30
  },
31
31
  ]
32
- const onChange = jest.fn()
32
+ const onChange = vi.fn()
33
33
  await waitFor(() => {
34
34
  render(
35
35
  <PortableTextEditorTester
@@ -50,21 +50,21 @@ describe('plugin:withPortableTextMarksModel', () => {
50
50
  })
51
51
  PortableTextEditor.toggleMark(editor, 'strong')
52
52
  expect(PortableTextEditor.getValue(editor)).toMatchInlineSnapshot(`
53
- Array [
54
- Object {
53
+ [
54
+ {
55
55
  "_key": "a",
56
56
  "_type": "myTestBlockType",
57
- "children": Array [
58
- Object {
57
+ "children": [
58
+ {
59
59
  "_key": "a1",
60
60
  "_type": "span",
61
- "marks": Array [
61
+ "marks": [
62
62
  "strong",
63
63
  ],
64
64
  "text": "1234",
65
65
  },
66
66
  ],
67
- "markDefs": Array [],
67
+ "markDefs": [],
68
68
  "style": "normal",
69
69
  },
70
70
  ]
@@ -79,35 +79,35 @@ describe('plugin:withPortableTextMarksModel', () => {
79
79
  PortableTextEditor.toggleMark(editorRef.current, 'strong')
80
80
  expect(PortableTextEditor.getValue(editorRef.current))
81
81
  .toMatchInlineSnapshot(`
82
- Array [
83
- Object {
82
+ [
83
+ {
84
84
  "_key": "a",
85
85
  "_type": "myTestBlockType",
86
- "children": Array [
87
- Object {
86
+ "children": [
87
+ {
88
88
  "_key": "a1",
89
89
  "_type": "span",
90
- "marks": Array [
90
+ "marks": [
91
91
  "strong",
92
92
  ],
93
93
  "text": "1",
94
94
  },
95
- Object {
95
+ {
96
96
  "_key": "2",
97
97
  "_type": "span",
98
- "marks": Array [],
98
+ "marks": [],
99
99
  "text": "23",
100
100
  },
101
- Object {
101
+ {
102
102
  "_key": "1",
103
103
  "_type": "span",
104
- "marks": Array [
104
+ "marks": [
105
105
  "strong",
106
106
  ],
107
107
  "text": "4",
108
108
  },
109
109
  ],
110
- "markDefs": Array [],
110
+ "markDefs": [],
111
111
  "style": "normal",
112
112
  },
113
113
  ]
@@ -122,21 +122,21 @@ describe('plugin:withPortableTextMarksModel', () => {
122
122
  })
123
123
  PortableTextEditor.toggleMark(editor, 'strong')
124
124
  expect(PortableTextEditor.getValue(editor)).toMatchInlineSnapshot(`
125
- Array [
126
- Object {
125
+ [
126
+ {
127
127
  "_key": "a",
128
128
  "_type": "myTestBlockType",
129
- "children": Array [
130
- Object {
129
+ "children": [
130
+ {
131
131
  "_key": "a1",
132
132
  "_type": "span",
133
- "marks": Array [
133
+ "marks": [
134
134
  "strong",
135
135
  ],
136
136
  "text": "1234",
137
137
  },
138
138
  ],
139
- "markDefs": Array [],
139
+ "markDefs": [],
140
140
  "style": "normal",
141
141
  },
142
142
  ]
@@ -175,7 +175,7 @@ Array [
175
175
  style: 'normal',
176
176
  },
177
177
  ]
178
- const onChange = jest.fn()
178
+ const onChange = vi.fn()
179
179
 
180
180
  render(
181
181
  <PortableTextEditorTester
@@ -307,7 +307,7 @@ Array [
307
307
  offset: 0,
308
308
  },
309
309
  }
310
- const onChange = jest.fn()
310
+ const onChange = vi.fn()
311
311
  await waitFor(() => {
312
312
  render(
313
313
  <PortableTextEditorTester
@@ -324,47 +324,47 @@ Array [
324
324
  PortableTextEditor.select(editor, sel)
325
325
  PortableTextEditor.delete(editor, sel)
326
326
  expect(PortableTextEditor.getValue(editor)).toMatchInlineSnapshot(`
327
- Array [
328
- Object {
327
+ [
328
+ {
329
329
  "_key": "5fc57af23597",
330
330
  "_type": "myTestBlockType",
331
- "children": Array [
332
- Object {
331
+ "children": [
332
+ {
333
333
  "_key": "be1c67c6971a",
334
334
  "_type": "span",
335
- "marks": Array [],
335
+ "marks": [],
336
336
  "text": "This is a ",
337
337
  },
338
- Object {
338
+ {
339
339
  "_key": "11c8c9f783a8",
340
340
  "_type": "span",
341
- "marks": Array [
341
+ "marks": [
342
342
  "fde1fd54b544",
343
343
  ],
344
344
  "text": "link",
345
345
  },
346
- Object {
346
+ {
347
347
  "_key": "576c748e0cd2",
348
348
  "_type": "span",
349
- "marks": Array [],
349
+ "marks": [],
350
350
  "text": "This is ",
351
351
  },
352
- Object {
352
+ {
353
353
  "_key": "f3d73d3833bf",
354
354
  "_type": "span",
355
- "marks": Array [
355
+ "marks": [
356
356
  "7b6d3d5de30c",
357
357
  ],
358
358
  "text": "another",
359
359
  },
360
360
  ],
361
- "markDefs": Array [
362
- Object {
361
+ "markDefs": [
362
+ {
363
363
  "_key": "fde1fd54b544",
364
364
  "_type": "link",
365
365
  "url": "1",
366
366
  },
367
- Object {
367
+ {
368
368
  "_key": "7b6d3d5de30c",
369
369
  "_type": "link",
370
370
  "url": "2",
@@ -419,7 +419,7 @@ Array [
419
419
  focus: {path: [{_key: 'bb'}, 'children', {_key: 'sb'}], offset: 0},
420
420
  anchor: {path: [{_key: 'bb'}, 'children', {_key: 'sb'}], offset: 0},
421
421
  }
422
- const onChange = jest.fn()
422
+ const onChange = vi.fn()
423
423
 
424
424
  render(
425
425
  <PortableTextEditorTester
@@ -522,7 +522,7 @@ Array [
522
522
  style: 'normal',
523
523
  },
524
524
  ]
525
- const onChange = jest.fn()
525
+ const onChange = vi.fn()
526
526
 
527
527
  render(
528
528
  <PortableTextEditorTester
@@ -592,7 +592,7 @@ Array [
592
592
  style: 'normal',
593
593
  },
594
594
  ]
595
- const onChange = jest.fn()
595
+ const onChange = vi.fn()
596
596
  await waitFor(() => {
597
597
  render(
598
598
  <PortableTextEditorTester
@@ -647,7 +647,7 @@ Array [
647
647
  style: 'normal',
648
648
  },
649
649
  ]
650
- const onChange = jest.fn()
650
+ const onChange = vi.fn()
651
651
  await waitFor(() => {
652
652
  render(
653
653
  <PortableTextEditorTester
@@ -703,7 +703,7 @@ Array [
703
703
  style: 'normal',
704
704
  },
705
705
  ]
706
- const onChange = jest.fn()
706
+ const onChange = vi.fn()
707
707
  await waitFor(() => {
708
708
  render(
709
709
  <PortableTextEditorTester
@@ -859,7 +859,7 @@ Array [
859
859
  ],
860
860
  },
861
861
  ]
862
- const onChange = jest.fn()
862
+ const onChange = vi.fn()
863
863
  await waitFor(() => {
864
864
  render(
865
865
  <PortableTextEditorTester
@@ -1,6 +1,6 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
1
  import {render, waitFor} from '@testing-library/react'
3
2
  import {createRef, type RefObject} from 'react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
4
  import {
5
5
  PortableTextEditorTester,
6
6
  schemaType,
@@ -41,7 +41,7 @@ const initialValue = [
41
41
  describe('plugin:withPortableTextSelections', () => {
42
42
  it('will report that a selection is made backward', async () => {
43
43
  const editorRef: RefObject<PortableTextEditor> = createRef()
44
- const onChange = jest.fn()
44
+ const onChange = vi.fn()
45
45
  render(
46
46
  <PortableTextEditorTester
47
47
  onChange={onChange}
@@ -54,34 +54,45 @@ describe('plugin:withPortableTextSelections', () => {
54
54
  anchor: {path: [{_key: 'b'}, 'children', {_key: 'b1'}], offset: 9},
55
55
  focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 7},
56
56
  }
57
+
58
+ await waitFor(() => {
59
+ if (editorRef.current) {
60
+ expect(onChange).toHaveBeenCalledWith({
61
+ type: 'value',
62
+ value: initialValue,
63
+ })
64
+ expect(onChange).toHaveBeenCalledWith({type: 'ready'})
65
+ }
66
+ })
67
+
57
68
  await waitFor(() => {
58
69
  if (editorRef.current) {
59
70
  PortableTextEditor.focus(editorRef.current)
60
71
  PortableTextEditor.select(editorRef.current, initialSelection)
61
72
  expect(PortableTextEditor.getSelection(editorRef.current))
62
73
  .toMatchInlineSnapshot(`
63
- Object {
64
- "anchor": Object {
74
+ {
75
+ "anchor": {
65
76
  "offset": 9,
66
- "path": Array [
67
- Object {
77
+ "path": [
78
+ {
68
79
  "_key": "b",
69
80
  },
70
81
  "children",
71
- Object {
82
+ {
72
83
  "_key": "b1",
73
84
  },
74
85
  ],
75
86
  },
76
87
  "backward": true,
77
- "focus": Object {
88
+ "focus": {
78
89
  "offset": 7,
79
- "path": Array [
80
- Object {
90
+ "path": [
91
+ {
81
92
  "_key": "a",
82
93
  },
83
94
  "children",
84
- Object {
95
+ {
85
96
  "_key": "a1",
86
97
  },
87
98
  ],
@@ -1,6 +1,6 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
1
  import {render, waitFor} from '@testing-library/react'
3
2
  import {createRef, type RefObject} from 'react'
3
+ import {describe, expect, it, vi} from 'vitest'
4
4
  import {
5
5
  PortableTextEditorTester,
6
6
  schemaType,
@@ -46,7 +46,7 @@ const initialSelection = {
46
46
  describe('plugin:withUndoRedo', () => {
47
47
  it('preserves the keys when undoing ', async () => {
48
48
  const editorRef: RefObject<PortableTextEditor> = createRef()
49
- const onChange = jest.fn()
49
+ const onChange = vi.fn()
50
50
  render(
51
51
  <PortableTextEditorTester
52
52
  onChange={onChange}
@@ -77,19 +77,19 @@ describe('plugin:withUndoRedo', () => {
77
77
  )
78
78
  expect(PortableTextEditor.getValue(editorRef.current))
79
79
  .toMatchInlineSnapshot(`
80
- Array [
81
- Object {
80
+ [
81
+ {
82
82
  "_key": "a",
83
83
  "_type": "myTestBlockType",
84
- "children": Array [
85
- Object {
84
+ "children": [
85
+ {
86
86
  "_key": "a1",
87
87
  "_type": "span",
88
- "marks": Array [],
88
+ "marks": [],
89
89
  "text": "Block A",
90
90
  },
91
91
  ],
92
- "markDefs": Array [],
92
+ "markDefs": [],
93
93
  "style": "normal",
94
94
  },
95
95
  ]
@@ -103,7 +103,7 @@ describe('plugin:withUndoRedo', () => {
103
103
  })
104
104
  it('preserves the keys when redoing ', async () => {
105
105
  const editorRef: RefObject<PortableTextEditor> = createRef()
106
- const onChange = jest.fn()
106
+ const onChange = vi.fn()
107
107
 
108
108
  render(
109
109
  <PortableTextEditorTester
@@ -15,6 +15,7 @@ import {
15
15
  isEqualToEmptyEditor,
16
16
  toSlateValue,
17
17
  } from '../../utils/values'
18
+ import type {EditorActor} from '../editor-machine'
18
19
 
19
20
  const debug = debugWithName('plugin:withInsertData')
20
21
 
@@ -23,7 +24,7 @@ const debug = debugWithName('plugin:withInsertData')
23
24
  *
24
25
  */
25
26
  export function createWithInsertData(
26
- change$: EditorChanges,
27
+ editorActor: EditorActor,
27
28
  schemaTypes: PortableTextMemberSchemaTypes,
28
29
  keyGenerator: () => string,
29
30
  ) {
@@ -158,9 +159,8 @@ export function createWithInsertData(
158
159
  // Bail out if it's not valid
159
160
  if (!validation.valid && !validation.resolution?.autoResolve) {
160
161
  const errorDescription = `${validation.resolution?.description}`
161
- change$.next({
162
+ editorActor.send({
162
163
  type: 'error',
163
- level: 'warning',
164
164
  name: 'pasteError',
165
165
  description: errorDescription,
166
166
  data: validation,
@@ -180,7 +180,6 @@ export function createWithInsertData(
180
180
  debug('No selection, not inserting')
181
181
  return false
182
182
  }
183
- change$.next({type: 'loading', isLoading: true}) // This could potentially take some time
184
183
  const html = data.getData('text/html')
185
184
  const text = data.getData('text/plain')
186
185
 
@@ -232,9 +231,8 @@ export function createWithInsertData(
232
231
  // Bail out if it's not valid
233
232
  if (!validation.valid) {
234
233
  const errorDescription = `Could not validate the resulting portable text to insert.\n${validation.resolution?.description}\nTry to insert as plain text (shift-paste) instead.`
235
- change$.next({
234
+ editorActor.send({
236
235
  type: 'error',
237
- level: 'warning',
238
236
  name: 'pasteError',
239
237
  description: errorDescription,
240
238
  data: validation,
@@ -246,10 +244,8 @@ export function createWithInsertData(
246
244
  `Inserting ${insertedType} fragment at ${JSON.stringify(editor.selection)}`,
247
245
  )
248
246
  _insertFragment(editor, fragment, schemaTypes)
249
- change$.next({type: 'loading', isLoading: false})
250
247
  return true
251
248
  }
252
- change$.next({type: 'loading', isLoading: false})
253
249
  return false
254
250
  }
255
251
 
@@ -5,12 +5,14 @@ import type {
5
5
  } from '../../types/editor'
6
6
  import {isChangingRemotely} from '../../utils/withChanges'
7
7
  import {isRedoing, isUndoing} from '../../utils/withUndoRedo'
8
+ import type {EditorActor} from '../editor-machine'
8
9
 
9
10
  /**
10
11
  * This plugin makes sure that every new node in the editor get a new _key prop when created
11
12
  *
12
13
  */
13
14
  export function createWithObjectKeys(
15
+ editorActor: EditorActor,
14
16
  schemaTypes: PortableTextMemberSchemaTypes,
15
17
  keyGenerator: () => string,
16
18
  ) {
@@ -75,12 +77,17 @@ export function createWithObjectKeys(
75
77
  if (Element.isElement(node) && node._type === schemaTypes.block.name) {
76
78
  // Set key on block itself
77
79
  if (!node._key) {
80
+ editorActor.send({type: 'normalizing'})
78
81
  Transforms.setNodes(editor, {_key: keyGenerator()}, {at: path})
82
+ editorActor.send({type: 'done normalizing'})
83
+ return
79
84
  }
80
85
  // Set keys on it's children
81
86
  for (const [child, childPath] of Node.children(editor, path)) {
82
87
  if (!child._key) {
88
+ editorActor.send({type: 'normalizing'})
83
89
  Transforms.setNodes(editor, {_key: keyGenerator()}, {at: childPath})
90
+ editorActor.send({type: 'done normalizing'})
84
91
  return
85
92
  }
86
93
  }
@@ -1,5 +1,4 @@
1
1
  import {insert, setIfMissing, unset, type Patch} from '@portabletext/patches'
2
- import type {Subject} from 'rxjs'
3
2
  import {
4
3
  Editor,
5
4
  type Descendant,
@@ -14,7 +13,6 @@ import {
14
13
  type SplitNodeOperation,
15
14
  } from 'slate'
16
15
  import type {
17
- EditorChange,
18
16
  PatchObservable,
19
17
  PortableTextMemberSchemaTypes,
20
18
  PortableTextSlateEditor,
@@ -32,6 +30,7 @@ import {
32
30
  PATCHING,
33
31
  withoutPatching,
34
32
  } from '../../utils/withoutPatching'
33
+ import type {EditorActor} from '../editor-machine'
35
34
  import {withoutSaving} from './createWithUndoRedo'
36
35
 
37
36
  const debug = debugWithName('plugin:withPatches')
@@ -81,7 +80,7 @@ export interface PatchFunctions {
81
80
  }
82
81
 
83
82
  interface Options {
84
- change$: Subject<EditorChange>
83
+ editorActor: EditorActor
85
84
  keyGenerator: () => string
86
85
  patches$?: PatchObservable
87
86
  patchFunctions: PatchFunctions
@@ -90,7 +89,7 @@ interface Options {
90
89
  }
91
90
 
92
91
  export function createWithPatches({
93
- change$,
92
+ editorActor,
94
93
  patches$,
95
94
  patchFunctions,
96
95
  readOnly,
@@ -281,7 +280,7 @@ export function createWithPatches({
281
280
  )
282
281
  ) {
283
282
  patches = [...patches, unset([])]
284
- change$.next({
283
+ editorActor.send({
285
284
  type: 'unset',
286
285
  previousValue: fromSlateValue(
287
286
  previousChildren,
@@ -299,7 +298,7 @@ export function createWithPatches({
299
298
  // Emit all patches
300
299
  if (patches.length > 0) {
301
300
  patches.forEach((patch) => {
302
- change$.next({
301
+ editorActor.send({
303
302
  type: 'patch',
304
303
  patch: {...patch, origin: 'local'},
305
304
  })
@@ -4,10 +4,12 @@ import type {
4
4
  PortableTextSlateEditor,
5
5
  } from '../../types/editor'
6
6
  import {debugWithName} from '../../utils/debug'
7
+ import type {EditorActor} from '../editor-machine'
7
8
 
8
9
  const debug = debugWithName('plugin:withPortableTextBlockStyle')
9
10
 
10
11
  export function createWithPortableTextBlockStyle(
12
+ editorActor: EditorActor,
11
13
  types: PortableTextMemberSchemaTypes,
12
14
  ): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
13
15
  const defaultStyle = types.styles[0].value
@@ -17,9 +19,10 @@ export function createWithPortableTextBlockStyle(
17
19
  // Extend Slate's default normalization to reset split node to normal style
18
20
  // if there is no text at the right end of the split.
19
21
  const {normalizeNode} = editor
22
+
20
23
  editor.normalizeNode = (nodeEntry) => {
21
- normalizeNode(nodeEntry)
22
24
  const [, path] = nodeEntry
25
+
23
26
  for (const op of editor.operations) {
24
27
  if (
25
28
  op.type === 'split_node' &&
@@ -32,16 +35,21 @@ export function createWithPortableTextBlockStyle(
32
35
  const [child] = Editor.node(editor, [op.path[0] + 1, 0])
33
36
  if (SlateText.isText(child) && child.text === '') {
34
37
  debug(`Normalizing split node to ${defaultStyle} style`, op)
38
+ editorActor.send({type: 'normalizing'})
35
39
  Transforms.setNodes(
36
40
  editor,
37
41
  {style: defaultStyle},
38
42
  {at: [op.path[0] + 1], voids: false},
39
43
  )
40
- break
44
+ editorActor.send({type: 'done normalizing'})
45
+ return
41
46
  }
42
47
  }
43
48
  }
49
+
50
+ normalizeNode(nodeEntry)
44
51
  }
52
+
45
53
  editor.pteHasBlockStyle = (style: string): boolean => {
46
54
  if (!editor.selection) {
47
55
  return false