@portabletext/editor 1.0.18 → 1.1.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 (75) hide show
  1. package/lib/index.d.mts +140 -66
  2. package/lib/index.d.ts +140 -66
  3. package/lib/index.esm.js +1164 -410
  4. package/lib/index.esm.js.map +1 -1
  5. package/lib/index.js +1164 -410
  6. package/lib/index.js.map +1 -1
  7. package/lib/index.mjs +1164 -410
  8. package/lib/index.mjs.map +1 -1
  9. package/package.json +8 -4
  10. package/src/editor/Editable.tsx +107 -36
  11. package/src/editor/PortableTextEditor.tsx +47 -12
  12. package/src/editor/__tests__/PortableTextEditor.test.tsx +42 -15
  13. package/src/editor/__tests__/PortableTextEditorTester.tsx +50 -38
  14. package/src/editor/__tests__/RangeDecorations.test.tsx +0 -1
  15. package/src/editor/__tests__/handleClick.test.tsx +28 -9
  16. package/src/editor/__tests__/insert-block.test.tsx +22 -6
  17. package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +30 -62
  18. package/src/editor/__tests__/utils.ts +10 -3
  19. package/src/editor/components/DraggableBlock.tsx +36 -13
  20. package/src/editor/components/Element.tsx +59 -17
  21. package/src/editor/components/Leaf.tsx +106 -68
  22. package/src/editor/components/SlateContainer.tsx +12 -5
  23. package/src/editor/components/Synchronizer.tsx +5 -2
  24. package/src/editor/hooks/usePortableTextEditor.ts +2 -2
  25. package/src/editor/hooks/usePortableTextEditorSelection.tsx +9 -3
  26. package/src/editor/hooks/useSyncValue.test.tsx +9 -4
  27. package/src/editor/hooks/useSyncValue.ts +199 -130
  28. package/src/editor/nodes/DefaultAnnotation.tsx +6 -3
  29. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +25 -7
  30. package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +26 -9
  31. package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +15 -5
  32. package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +60 -19
  33. package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +4 -2
  34. package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +4 -2
  35. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +61 -550
  36. package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +6 -3
  37. package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +30 -13
  38. package/src/editor/plugins/createWithEditableAPI.ts +354 -115
  39. package/src/editor/plugins/createWithHotKeys.ts +41 -121
  40. package/src/editor/plugins/createWithInsertBreak.ts +166 -27
  41. package/src/editor/plugins/createWithInsertData.ts +60 -23
  42. package/src/editor/plugins/createWithMaxBlocks.ts +5 -2
  43. package/src/editor/plugins/createWithObjectKeys.ts +7 -3
  44. package/src/editor/plugins/createWithPatches.ts +60 -16
  45. package/src/editor/plugins/createWithPlaceholderBlock.ts +7 -3
  46. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +17 -7
  47. package/src/editor/plugins/createWithPortableTextLists.ts +21 -8
  48. package/src/editor/plugins/createWithPortableTextMarkModel.ts +301 -155
  49. package/src/editor/plugins/createWithPortableTextSelections.ts +4 -2
  50. package/src/editor/plugins/createWithSchemaTypes.ts +25 -9
  51. package/src/editor/plugins/createWithUndoRedo.ts +107 -24
  52. package/src/editor/plugins/createWithUtils.ts +32 -10
  53. package/src/editor/plugins/index.ts +31 -10
  54. package/src/types/editor.ts +44 -15
  55. package/src/types/options.ts +4 -2
  56. package/src/types/slate.ts +2 -2
  57. package/src/utils/__tests__/dmpToOperations.test.ts +38 -13
  58. package/src/utils/__tests__/operationToPatches.test.ts +3 -2
  59. package/src/utils/__tests__/patchToOperations.test.ts +15 -4
  60. package/src/utils/__tests__/ranges.test.ts +8 -3
  61. package/src/utils/__tests__/valueNormalization.test.tsx +12 -4
  62. package/src/utils/__tests__/values.test.ts +0 -1
  63. package/src/utils/applyPatch.ts +71 -20
  64. package/src/utils/getPortableTextMemberSchemaTypes.ts +30 -15
  65. package/src/utils/operationToPatches.ts +126 -43
  66. package/src/utils/paths.ts +24 -7
  67. package/src/utils/ranges.ts +12 -5
  68. package/src/utils/selection.ts +19 -7
  69. package/src/utils/validateValue.ts +118 -45
  70. package/src/utils/values.ts +31 -10
  71. package/src/utils/weakMaps.ts +18 -8
  72. package/src/utils/withChanges.ts +4 -2
  73. package/src/editor/plugins/__tests__/withHotkeys.test.tsx +0 -212
  74. package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +0 -220
  75. package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +0 -133
@@ -1,212 +0,0 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
- import {render, waitFor} from '@testing-library/react'
3
- import {createRef, type RefObject} from 'react'
4
-
5
- import {PortableTextEditorTester, schemaType} from '../../__tests__/PortableTextEditorTester'
6
- import {getEditableElement, triggerKeyboardEvent} from '../../__tests__/utils'
7
- import {PortableTextEditor} from '../../PortableTextEditor'
8
-
9
- const newBlock = {
10
- _type: 'myTestBlockType',
11
- _key: '3',
12
- style: 'normal',
13
- markDefs: [],
14
- children: [
15
- {
16
- _type: 'span',
17
- _key: '2',
18
- text: '',
19
- marks: [],
20
- },
21
- ],
22
- }
23
- describe('plugin:withHotkeys: .ArrowDown', () => {
24
- it('a new block is added if the user is focused on the only block which is void, and presses arrow down.', async () => {
25
- const initialValue = [
26
- {
27
- _key: 'a',
28
- _type: 'someObject',
29
- },
30
- ]
31
-
32
- const initialSelection = {
33
- focus: {path: [{_key: 'a'}], offset: 0},
34
- anchor: {path: [{_key: 'a'}], offset: 0},
35
- }
36
-
37
- const editorRef: RefObject<PortableTextEditor> = createRef()
38
- const onChange = jest.fn()
39
- const component = render(
40
- <PortableTextEditorTester
41
- onChange={onChange}
42
- ref={editorRef}
43
- schemaType={schemaType}
44
- value={initialValue}
45
- />,
46
- )
47
- const element = await getEditableElement(component)
48
-
49
- const editor = editorRef.current
50
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
51
- await waitFor(async () => {
52
- if (editor && inlineType && editor) {
53
- PortableTextEditor.focus(editor)
54
- PortableTextEditor.select(editor, initialSelection)
55
- PortableTextEditor.insertBreak(editor)
56
- await triggerKeyboardEvent('ArrowDown', element)
57
-
58
- const value = PortableTextEditor.getValue(editor)
59
- expect(value).toEqual([initialValue[0], newBlock])
60
- }
61
- })
62
- })
63
- it('a new block is added if the user is focused on the last block which is void, and presses arrow down.', async () => {
64
- const initialValue = [
65
- {
66
- _type: 'myTestBlockType',
67
- _key: 'a',
68
- style: 'normal',
69
- markDefs: [],
70
- children: [
71
- {
72
- _type: 'span',
73
- _key: 'a1',
74
- text: 'This is the first block',
75
- marks: [],
76
- },
77
- ],
78
- },
79
- {
80
- _key: 'b',
81
- _type: 'someObject',
82
- },
83
- ]
84
- const initialSelection = {
85
- focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
86
- anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
87
- }
88
-
89
- const editorRef: RefObject<PortableTextEditor> = createRef()
90
- const onChange = jest.fn()
91
- const component = render(
92
- <PortableTextEditorTester
93
- onChange={onChange}
94
- ref={editorRef}
95
- schemaType={schemaType}
96
- value={initialValue}
97
- />,
98
- )
99
- const element = await getEditableElement(component)
100
-
101
- const editor = editorRef.current
102
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
103
- await waitFor(async () => {
104
- if (editor && inlineType && element) {
105
- PortableTextEditor.focus(editor)
106
- PortableTextEditor.select(editor, initialSelection)
107
- await triggerKeyboardEvent('ArrowDown', element)
108
- const value = PortableTextEditor.getValue(editor)
109
- // Arrow down on the text block should not add a new block
110
- expect(value).toEqual(initialValue)
111
- // Focus on the object block
112
- PortableTextEditor.select(editor, {
113
- focus: {path: [{_key: 'b'}], offset: 0},
114
- anchor: {path: [{_key: 'b'}], offset: 0},
115
- })
116
- await triggerKeyboardEvent('ArrowDown', element)
117
- const value2 = PortableTextEditor.getValue(editor)
118
- expect(value2).toEqual([
119
- initialValue[0],
120
- initialValue[1],
121
- {
122
- _type: 'myTestBlockType',
123
- _key: '3',
124
- style: 'normal',
125
- markDefs: [],
126
- children: [
127
- {
128
- _type: 'span',
129
- _key: '2',
130
- text: '',
131
- marks: [],
132
- },
133
- ],
134
- },
135
- ])
136
- }
137
- })
138
- })
139
- })
140
- describe('plugin:withHotkeys: .ArrowUp', () => {
141
- it('a new block is added at the top, when pressing arrow up, because first block is void, the new block can be deleted with backspace.', async () => {
142
- const initialValue = [
143
- {
144
- _key: 'b',
145
- _type: 'someObject',
146
- },
147
- {
148
- _type: 'myTestBlockType',
149
- _key: 'a',
150
- style: 'normal',
151
- markDefs: [],
152
- children: [
153
- {
154
- _type: 'span',
155
- _key: 'a1',
156
- text: 'This is the first block',
157
- marks: [],
158
- },
159
- ],
160
- },
161
- ]
162
-
163
- const initialSelection = {
164
- focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
165
- anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 2},
166
- }
167
-
168
- const editorRef: RefObject<PortableTextEditor> = createRef()
169
- const onChange = jest.fn()
170
- const component = render(
171
- <PortableTextEditorTester
172
- onChange={onChange}
173
- ref={editorRef}
174
- schemaType={schemaType}
175
- value={initialValue}
176
- />,
177
- )
178
- const element = await getEditableElement(component)
179
-
180
- const editor = editorRef.current
181
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
182
- await waitFor(async () => {
183
- if (editor && inlineType && element) {
184
- PortableTextEditor.focus(editor)
185
- PortableTextEditor.select(editor, initialSelection)
186
- await triggerKeyboardEvent('ArrowUp', element)
187
- // Arrow down on the text block should not add a new block
188
- expect(PortableTextEditor.getValue(editor)).toEqual(initialValue)
189
- // Focus on the object block
190
- PortableTextEditor.select(editor, {
191
- focus: {path: [{_key: 'b'}], offset: 0},
192
- anchor: {path: [{_key: 'b'}], offset: 0},
193
- })
194
- await triggerKeyboardEvent('ArrowUp', element)
195
- expect(PortableTextEditor.getValue(editor)).toEqual([
196
- newBlock,
197
- initialValue[0],
198
- initialValue[1],
199
- ])
200
- // Pressing arrow up again won't add a new block
201
- await triggerKeyboardEvent('ArrowUp', element)
202
- expect(PortableTextEditor.getValue(editor)).toEqual([
203
- newBlock,
204
- initialValue[0],
205
- initialValue[1],
206
- ])
207
- await triggerKeyboardEvent('Backspace', element)
208
- expect(PortableTextEditor.getValue(editor)).toEqual(initialValue)
209
- }
210
- })
211
- })
212
- })
@@ -1,220 +0,0 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
- import {render, waitFor} from '@testing-library/react'
3
- import {createRef, type RefObject} from 'react'
4
-
5
- import {PortableTextEditorTester, schemaType} from '../../__tests__/PortableTextEditorTester'
6
- import {PortableTextEditor} from '../../PortableTextEditor'
7
-
8
- const initialValue = [
9
- {
10
- _key: 'a',
11
- _type: 'myTestBlockType',
12
- children: [
13
- {
14
- _key: 'a1',
15
- _type: 'span',
16
- marks: [],
17
- text: 'Block A',
18
- },
19
- ],
20
- markDefs: [],
21
- style: 'normal',
22
- },
23
- {
24
- _key: 'b',
25
- _type: 'myTestBlockType',
26
- children: [
27
- {
28
- _key: 'b1',
29
- _type: 'span',
30
- marks: [],
31
- text: 'Block B',
32
- },
33
- ],
34
- markDefs: [],
35
- style: 'normal',
36
- },
37
- ]
38
-
39
- describe('plugin:withInsertBreak: "enter"', () => {
40
- it('keeps text block key if enter is pressed at the start of the block, creating a new one in "before" position', async () => {
41
- const initialSelection = {
42
- focus: {path: [{_key: 'b'}, 'children', {_key: 'b1'}], offset: 0},
43
- anchor: {path: [{_key: 'b'}, 'children', {_key: 'b1'}], offset: 0},
44
- }
45
- const editorRef: RefObject<PortableTextEditor> = createRef()
46
- const onChange = jest.fn()
47
-
48
- render(
49
- <PortableTextEditorTester
50
- onChange={onChange}
51
- ref={editorRef}
52
- schemaType={schemaType}
53
- value={initialValue}
54
- />,
55
- )
56
-
57
- await waitFor(() => {
58
- if (editorRef.current) {
59
- expect(onChange).toHaveBeenCalledWith({type: 'value', value: initialValue})
60
- expect(onChange).toHaveBeenCalledWith({type: 'ready'})
61
- }
62
- })
63
-
64
- const editor = editorRef.current
65
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
66
- await waitFor(async () => {
67
- if (editor && inlineType) {
68
- PortableTextEditor.focus(editor)
69
- PortableTextEditor.select(editor, initialSelection)
70
- PortableTextEditor.insertBreak(editor)
71
-
72
- const value = PortableTextEditor.getValue(editor)
73
- expect(value).toEqual([
74
- initialValue[0],
75
- {
76
- _type: 'myTestBlockType',
77
- _key: '3',
78
- style: 'normal',
79
- markDefs: [],
80
- children: [
81
- {
82
- _type: 'span',
83
- _key: '2',
84
- text: '',
85
- marks: [],
86
- },
87
- ],
88
- },
89
- initialValue[1],
90
- ])
91
- }
92
- })
93
- })
94
- it('inserts the new block after if key enter is pressed at the start of the block, creating a new one in "after" position if the block is empty', async () => {
95
- const initialSelection = {
96
- focus: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 0},
97
- anchor: {path: [{_key: 'a'}, 'children', {_key: 'a1'}], offset: 0},
98
- }
99
- const emptyBlock = {
100
- _key: 'a',
101
- _type: 'myTestBlockType',
102
- children: [
103
- {
104
- _key: 'a1',
105
- _type: 'span',
106
- marks: [],
107
- text: '',
108
- },
109
- ],
110
- markDefs: [],
111
- style: 'normal',
112
- }
113
- const editorRef: RefObject<PortableTextEditor> = createRef()
114
- const onChange = jest.fn()
115
-
116
- render(
117
- <PortableTextEditorTester
118
- onChange={onChange}
119
- ref={editorRef}
120
- schemaType={schemaType}
121
- value={[emptyBlock]}
122
- />,
123
- )
124
-
125
- await waitFor(() => {
126
- if (editorRef.current) {
127
- expect(onChange).toHaveBeenCalledWith({type: 'value', value: [emptyBlock]})
128
- expect(onChange).toHaveBeenCalledWith({type: 'ready'})
129
- }
130
- })
131
-
132
- const editor = editorRef.current
133
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
134
- await waitFor(async () => {
135
- if (editor && inlineType) {
136
- PortableTextEditor.focus(editor)
137
- PortableTextEditor.select(editor, initialSelection)
138
- PortableTextEditor.insertBreak(editor)
139
-
140
- const value = PortableTextEditor.getValue(editor)
141
- expect(value).toEqual([
142
- emptyBlock,
143
- {
144
- _key: '2',
145
- _type: 'myTestBlockType',
146
- markDefs: [],
147
- style: 'normal',
148
- children: [
149
- {
150
- _key: '1',
151
- _type: 'span',
152
- marks: [],
153
- text: '',
154
- },
155
- ],
156
- },
157
- ])
158
- }
159
- })
160
- })
161
- it('splits the text block key if enter is pressed at the middle of the block', async () => {
162
- const initialSelection = {
163
- focus: {path: [{_key: 'b'}, 'children', {_key: 'b1'}], offset: 2},
164
- anchor: {path: [{_key: 'b'}, 'children', {_key: 'b1'}], offset: 2},
165
- }
166
-
167
- const editorRef: RefObject<PortableTextEditor> = createRef()
168
- const onChange = jest.fn()
169
- render(
170
- <PortableTextEditorTester
171
- onChange={onChange}
172
- ref={editorRef}
173
- schemaType={schemaType}
174
- value={initialValue}
175
- />,
176
- )
177
- const editor = editorRef.current
178
- const inlineType = editor?.schemaTypes.inlineObjects.find((t) => t.name === 'someObject')
179
- await waitFor(async () => {
180
- if (editor && inlineType) {
181
- PortableTextEditor.focus(editor)
182
- PortableTextEditor.select(editor, initialSelection)
183
- PortableTextEditor.insertBreak(editor)
184
-
185
- const value = PortableTextEditor.getValue(editor)
186
- expect(value).toEqual([
187
- initialValue[0],
188
- {
189
- _key: 'b',
190
- _type: 'myTestBlockType',
191
- children: [
192
- {
193
- _key: 'b1',
194
- _type: 'span',
195
- marks: [],
196
- text: 'Bl',
197
- },
198
- ],
199
- markDefs: [],
200
- style: 'normal',
201
- },
202
- {
203
- _key: '2',
204
- _type: 'myTestBlockType',
205
- markDefs: [],
206
- style: 'normal',
207
- children: [
208
- {
209
- _key: '1',
210
- _type: 'span',
211
- marks: [],
212
- text: 'ock B',
213
- },
214
- ],
215
- },
216
- ])
217
- }
218
- })
219
- })
220
- })
@@ -1,133 +0,0 @@
1
- import {describe, expect, it, jest} from '@jest/globals'
2
- /* eslint-disable max-nested-callbacks */
3
- import {render, waitFor} from '@testing-library/react'
4
- import {createRef, type RefObject} from 'react'
5
-
6
- import {PortableTextEditorTester, schemaType} from '../../__tests__/PortableTextEditorTester'
7
- import {PortableTextEditor} from '../../PortableTextEditor'
8
-
9
- describe('plugin:withPlaceholderBlock', () => {
10
- describe('removing nodes', () => {
11
- it("should insert an empty text block if it's removing the only block", async () => {
12
- const editorRef: RefObject<PortableTextEditor> = createRef()
13
- const initialValue = [
14
- {
15
- _key: '5fc57af23597',
16
- _type: 'someObject',
17
- },
18
- ]
19
- const onChange = jest.fn()
20
- await waitFor(() => {
21
- render(
22
- <PortableTextEditorTester
23
- onChange={onChange}
24
- ref={editorRef}
25
- schemaType={schemaType}
26
- value={initialValue}
27
- />,
28
- )
29
- })
30
-
31
- await waitFor(() => {
32
- if (editorRef.current) {
33
- PortableTextEditor.focus(editorRef.current)
34
-
35
- PortableTextEditor.delete(
36
- editorRef.current,
37
- {
38
- focus: {path: [{_key: '5fc57af23597'}], offset: 0},
39
- anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
40
- },
41
- {mode: 'blocks'},
42
- )
43
-
44
- const value = PortableTextEditor.getValue(editorRef.current)
45
-
46
- expect(value).toEqual([
47
- {
48
- _type: 'myTestBlockType',
49
- _key: '3',
50
- style: 'normal',
51
- markDefs: [],
52
- children: [
53
- {
54
- _type: 'span',
55
- _key: '2',
56
- text: '',
57
- marks: [],
58
- },
59
- ],
60
- },
61
- ])
62
- }
63
- })
64
- })
65
- it('should not insert a new block if we have more blocks available', async () => {
66
- const editorRef: RefObject<PortableTextEditor> = createRef()
67
- const initialValue = [
68
- {
69
- _key: '5fc57af23597',
70
- _type: 'someObject',
71
- },
72
- {
73
- _type: 'myTestBlockType',
74
- _key: 'existingBlock',
75
- style: 'normal',
76
- markDefs: [],
77
- children: [
78
- {
79
- _type: 'span',
80
- _key: '2',
81
- text: '',
82
- marks: [],
83
- },
84
- ],
85
- },
86
- ]
87
- const onChange = jest.fn()
88
- await waitFor(() => {
89
- render(
90
- <PortableTextEditorTester
91
- onChange={onChange}
92
- ref={editorRef}
93
- schemaType={schemaType}
94
- value={initialValue}
95
- />,
96
- )
97
- })
98
-
99
- await waitFor(() => {
100
- if (editorRef.current) {
101
- PortableTextEditor.focus(editorRef.current)
102
-
103
- PortableTextEditor.delete(
104
- editorRef.current,
105
- {
106
- focus: {path: [{_key: '5fc57af23597'}], offset: 0},
107
- anchor: {path: [{_key: '5fc57af23597'}], offset: 0},
108
- },
109
- {mode: 'blocks'},
110
- )
111
-
112
- const value = PortableTextEditor.getValue(editorRef.current)
113
- expect(value).toEqual([
114
- {
115
- _type: 'myTestBlockType',
116
- _key: 'existingBlock',
117
- style: 'normal',
118
- markDefs: [],
119
- children: [
120
- {
121
- _type: 'span',
122
- _key: '2',
123
- text: '',
124
- marks: [],
125
- },
126
- ],
127
- },
128
- ])
129
- }
130
- })
131
- })
132
- })
133
- })