@portabletext/editor 2.8.3 → 2.9.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.
@@ -2,7 +2,7 @@ import {createEditorDom} from '../editor/editor-dom'
2
2
  import type {EditorSchema} from '../editor/editor-schema'
3
3
  import type {EditorSnapshot} from '../editor/editor-snapshot'
4
4
  import {withApplyingBehaviorOperations} from '../editor/with-applying-behavior-operations'
5
- import {withUndoStep} from '../editor/with-undo-step'
5
+ import {clearUndoStep, createUndoStep} from '../editor/with-undo-step'
6
6
  import {debugWithName} from '../internal-utils/debug'
7
7
  import {performOperation} from '../operations/behavior.operations'
8
8
  import type {PortableTextSlateEditor} from '../types/editor'
@@ -15,8 +15,7 @@ import {
15
15
  isNativeBehaviorEvent,
16
16
  isSyntheticBehaviorEvent,
17
17
  type BehaviorEvent,
18
- type CustomBehaviorEvent,
19
- type SyntheticBehaviorEvent,
18
+ type ExternalBehaviorEvent,
20
19
  } from './behavior.types.event'
21
20
 
22
21
  const debug = debugWithName('behaviors:event')
@@ -43,7 +42,7 @@ export function performEvent({
43
42
  nativeEvent,
44
43
  sendBack,
45
44
  }: {
46
- mode: 'raise' | 'execute' | 'forward'
45
+ mode: 'send' | 'raise' | 'execute' | 'forward'
47
46
  behaviors: Array<Behavior>
48
47
  remainingEventBehaviors: Array<Behavior>
49
48
  event: BehaviorEvent
@@ -57,12 +56,13 @@ export function performEvent({
57
56
  }
58
57
  | undefined
59
58
  sendBack: (
60
- event:
61
- | {type: 'set drag ghost'; ghost: HTMLElement}
62
- | SyntheticBehaviorEvent
63
- | CustomBehaviorEvent,
59
+ event: {type: 'set drag ghost'; ghost: HTMLElement} | ExternalBehaviorEvent,
64
60
  ) => void
65
61
  }) {
62
+ if (mode === 'send' && !isNativeBehaviorEvent(event)) {
63
+ createUndoStep(editor)
64
+ }
65
+
66
66
  debug(`(${mode}:${eventCategory(event)})`, JSON.stringify(event, null, 2))
67
67
 
68
68
  const eventBehaviors = [
@@ -108,6 +108,10 @@ export function performEvent({
108
108
  if (eventBehaviors.length === 0 && isSyntheticBehaviorEvent(event)) {
109
109
  nativeEvent?.preventDefault()
110
110
 
111
+ if (mode === 'send') {
112
+ clearUndoStep(editor)
113
+ }
114
+
111
115
  withApplyingBehaviorOperations(editor, () => {
112
116
  debug(`(execute:${eventCategory(event)})`, JSON.stringify(event, null, 2))
113
117
 
@@ -199,76 +203,78 @@ export function performEvent({
199
203
  // we set up a new undo step.
200
204
  // All actions performed recursively from now will be squashed into this
201
205
  // undo step
202
- withUndoStep(editor, () => {
203
- for (const action of actions) {
204
- if (action.type === 'effect') {
205
- try {
206
- action.effect({
207
- send: sendBack,
208
- })
209
- } catch (error) {
210
- console.error(
211
- new Error(
212
- `Executing effect as a result of "${event.type}" failed due to: ${error.message}`,
213
- ),
214
- )
215
- }
216
-
217
- continue
218
- }
206
+ createUndoStep(editor)
219
207
 
220
- if (action.type === 'forward') {
221
- const remainingEventBehaviors = eventBehaviors.slice(
222
- eventBehaviorIndex + 1,
208
+ for (const action of actions) {
209
+ if (action.type === 'effect') {
210
+ try {
211
+ action.effect({
212
+ send: sendBack,
213
+ })
214
+ } catch (error) {
215
+ console.error(
216
+ new Error(
217
+ `Executing effect as a result of "${event.type}" failed due to: ${error.message}`,
218
+ ),
223
219
  )
220
+ }
224
221
 
225
- performEvent({
226
- mode: 'forward',
227
- behaviors,
228
- remainingEventBehaviors: remainingEventBehaviors,
229
- event: action.event,
230
- editor,
231
- keyGenerator,
232
- schema,
233
- getSnapshot,
234
- nativeEvent,
235
- sendBack,
236
- })
222
+ continue
223
+ }
237
224
 
238
- continue
239
- }
225
+ if (action.type === 'forward') {
226
+ const remainingEventBehaviors = eventBehaviors.slice(
227
+ eventBehaviorIndex + 1,
228
+ )
240
229
 
241
- if (action.type === 'raise') {
242
- performEvent({
243
- mode: 'raise',
244
- behaviors,
245
- remainingEventBehaviors: behaviors,
246
- event: action.event,
247
- editor,
248
- keyGenerator,
249
- schema,
250
- getSnapshot,
251
- nativeEvent,
252
- sendBack,
253
- })
230
+ performEvent({
231
+ mode: 'forward',
232
+ behaviors,
233
+ remainingEventBehaviors: remainingEventBehaviors,
234
+ event: action.event,
235
+ editor,
236
+ keyGenerator,
237
+ schema,
238
+ getSnapshot,
239
+ nativeEvent,
240
+ sendBack,
241
+ })
254
242
 
255
- continue
256
- }
243
+ continue
244
+ }
257
245
 
246
+ if (action.type === 'raise') {
258
247
  performEvent({
259
- mode: 'execute',
248
+ mode: 'raise',
260
249
  behaviors,
261
- remainingEventBehaviors: [],
250
+ remainingEventBehaviors: behaviors,
262
251
  event: action.event,
263
252
  editor,
264
253
  keyGenerator,
265
254
  schema,
266
255
  getSnapshot,
267
- nativeEvent: undefined,
256
+ nativeEvent,
268
257
  sendBack,
269
258
  })
259
+
260
+ continue
270
261
  }
271
- })
262
+
263
+ performEvent({
264
+ mode: 'execute',
265
+ behaviors,
266
+ remainingEventBehaviors: [],
267
+ event: action.event,
268
+ editor,
269
+ keyGenerator,
270
+ schema,
271
+ getSnapshot,
272
+ nativeEvent: undefined,
273
+ sendBack,
274
+ })
275
+ }
276
+
277
+ clearUndoStep(editor)
272
278
 
273
279
  continue
274
280
  }
@@ -341,6 +347,10 @@ export function performEvent({
341
347
  if (!defaultBehaviorOverwritten && isSyntheticBehaviorEvent(event)) {
342
348
  nativeEvent?.preventDefault()
343
349
 
350
+ if (mode === 'send') {
351
+ clearUndoStep(editor)
352
+ }
353
+
344
354
  withApplyingBehaviorOperations(editor, () => {
345
355
  debug(`(execute:${eventCategory(event)})`, JSON.stringify(event, null, 2))
346
356
 
@@ -3,6 +3,7 @@ import type {EditorSnapshot} from '../editor/editor-snapshot'
3
3
  import type {PickFromUnion} from '../type-utils'
4
4
  import type {
5
5
  CustomBehaviorEvent,
6
+ ExternalBehaviorEvent,
6
7
  NativeBehaviorEvent,
7
8
  SyntheticBehaviorEvent,
8
9
  } from './behavior.types.event'
@@ -48,7 +49,7 @@ export type BehaviorAction =
48
49
  * })
49
50
  * ```
50
51
  */
51
- send: (event: SyntheticBehaviorEvent | CustomBehaviorEvent) => void
52
+ send: (event: ExternalBehaviorEvent) => void
52
53
  }) => void
53
54
  }
54
55
 
@@ -228,6 +228,7 @@ const abstractBehaviorEventTypes = [
228
228
  'list item.toggle',
229
229
  'move.block down',
230
230
  'move.block up',
231
+ 'select.block',
231
232
  'select.previous block',
232
233
  'select.next block',
233
234
  'serialize',
@@ -415,6 +416,11 @@ type AbstractBehaviorEvent =
415
416
  type: StrictExtract<SyntheticBehaviorEventType, 'move.block up'>
416
417
  at: BlockPath
417
418
  }
419
+ | {
420
+ type: StrictExtract<SyntheticBehaviorEventType, 'select.block'>
421
+ at: BlockPath
422
+ select?: 'start' | 'end'
423
+ }
418
424
  | {
419
425
  type: StrictExtract<SyntheticBehaviorEventType, 'select.previous block'>
420
426
  select?: 'start' | 'end'
@@ -7,7 +7,6 @@ import {
7
7
  import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
8
8
  import {expect, test} from 'vitest'
9
9
  import type {EditorSelection} from '..'
10
- import {schemaDefinition} from '../../tests/PortableTextEditorTester'
11
10
  import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
12
11
  import {createConverterTextPlain} from './converter.text-plain'
13
12
 
@@ -84,7 +83,7 @@ function createSnapshot({
84
83
  }
85
84
 
86
85
  const converterTextPlain = createConverterTextPlain(
87
- compileSchemaDefinitionToPortableTextMemberSchemaTypes(schemaDefinition),
86
+ compileSchemaDefinitionToPortableTextMemberSchemaTypes(defineSchema({})),
88
87
  )
89
88
 
90
89
  test(converterTextPlain.serialize.name, () => {
@@ -17,7 +17,7 @@ import type {EditableAPI, PortableTextSlateEditor} from '../types/editor'
17
17
  import {createSlateEditor, type SlateEditor} from './create-slate-editor'
18
18
  import {createEditorDom} from './editor-dom'
19
19
  import type {EditorActor} from './editor-machine'
20
- import {editorMachine} from './editor-machine'
20
+ import {editorMachine, rerouteExternalBehaviorEvent} from './editor-machine'
21
21
  import {getEditorSnapshot} from './editor-selector'
22
22
  import {defaultKeyGenerator} from './key-generator'
23
23
  import {mutationMachine, type MutationActor} from './mutation-machine'
@@ -112,41 +112,13 @@ export function createInternalEditor(config: EditorConfig): {
112
112
  editorActor.send(event)
113
113
  break
114
114
 
115
- case 'blur':
116
- editorActor.send({
117
- type: 'blur',
118
- editor: slateEditor.instance,
119
- })
120
- break
121
-
122
- case 'focus':
123
- editorActor.send({
124
- type: 'focus',
125
- editor: slateEditor.instance,
126
- })
127
- break
128
-
129
- case 'insert.block object':
130
- editorActor.send({
131
- type: 'behavior event',
132
- behaviorEvent: {
133
- type: 'insert.block',
134
- block: {
135
- _type: event.blockObject.name,
136
- ...(event.blockObject.value ?? {}),
137
- },
138
- placement: event.placement,
139
- },
140
- editor: slateEditor.instance,
141
- })
142
- break
143
-
144
115
  default:
145
- editorActor.send({
146
- type: 'behavior event',
147
- behaviorEvent: event,
148
- editor: slateEditor.instance,
149
- })
116
+ editorActor.send(
117
+ rerouteExternalBehaviorEvent({
118
+ event,
119
+ slateEditor: slateEditor.instance,
120
+ }),
121
+ )
150
122
  }
151
123
  },
152
124
  on: (event, listener) => {
@@ -13,7 +13,10 @@ import {
13
13
  import type {BehaviorConfig} from '../behaviors/behavior.config'
14
14
  import {coreBehaviorsConfig} from '../behaviors/behavior.core'
15
15
  import {performEvent} from '../behaviors/behavior.perform-event'
16
- import type {BehaviorEvent} from '../behaviors/behavior.types.event'
16
+ import type {
17
+ BehaviorEvent,
18
+ ExternalBehaviorEvent,
19
+ } from '../behaviors/behavior.types.event'
17
20
  import type {Converter} from '../converters/converter.types'
18
21
  import {debugWithName} from '../internal-utils/debug'
19
22
  import type {EventPosition} from '../internal-utils/event-position'
@@ -134,6 +137,49 @@ export type InternalEditorEmittedEvent =
134
137
  | InternalPatchEvent
135
138
  | PatchesEvent
136
139
 
140
+ export function rerouteExternalBehaviorEvent({
141
+ event,
142
+ slateEditor,
143
+ }: {
144
+ event: ExternalBehaviorEvent
145
+ slateEditor: PortableTextSlateEditor
146
+ }): InternalEditorEvent {
147
+ switch (event.type) {
148
+ case 'blur':
149
+ return {
150
+ type: 'blur',
151
+ editor: slateEditor,
152
+ }
153
+
154
+ case 'focus':
155
+ return {
156
+ type: 'focus',
157
+ editor: slateEditor,
158
+ }
159
+
160
+ case 'insert.block object':
161
+ return {
162
+ type: 'behavior event',
163
+ behaviorEvent: {
164
+ type: 'insert.block',
165
+ block: {
166
+ _type: event.blockObject.name,
167
+ ...(event.blockObject.value ?? {}),
168
+ },
169
+ placement: event.placement,
170
+ },
171
+ editor: slateEditor,
172
+ }
173
+
174
+ default:
175
+ return {
176
+ type: 'behavior event',
177
+ behaviorEvent: event,
178
+ editor: slateEditor,
179
+ }
180
+ }
181
+ }
182
+
137
183
  /**
138
184
  * @internal
139
185
  */
@@ -263,7 +309,7 @@ export const editorMachine = setup({
263
309
  )
264
310
 
265
311
  performEvent({
266
- mode: 'raise',
312
+ mode: 'send',
267
313
  behaviors,
268
314
  remainingEventBehaviors: behaviors,
269
315
  event: event.behaviorEvent,
@@ -285,11 +331,12 @@ export const editorMachine = setup({
285
331
  return
286
332
  }
287
333
 
288
- self.send({
289
- type: 'behavior event',
290
- behaviorEvent: eventSentBack,
291
- editor: event.editor,
292
- })
334
+ self.send(
335
+ rerouteExternalBehaviorEvent({
336
+ event: eventSentBack,
337
+ slateEditor: event.editor,
338
+ }),
339
+ )
293
340
  },
294
341
  })
295
342
  } catch (error) {
@@ -25,3 +25,13 @@ export function withUndoStep(editor: Editor, fn: () => void) {
25
25
  export function getCurrentUndoStepId(editor: Editor) {
26
26
  return CURRENT_UNDO_STEP.get(editor)?.undoStepId
27
27
  }
28
+
29
+ export function createUndoStep(editor: Editor) {
30
+ CURRENT_UNDO_STEP.set(editor, {
31
+ undoStepId: defaultKeyGenerator(),
32
+ })
33
+ }
34
+
35
+ export function clearUndoStep(editor: Editor) {
36
+ CURRENT_UNDO_STEP.set(editor, undefined)
37
+ }
@@ -19,24 +19,30 @@ import {InternalEditorAfterRefPlugin} from '../plugins/plugin.internal.editor-ac
19
19
  import {InternalSlateEditorRefPlugin} from '../plugins/plugin.internal.slate-editor-ref'
20
20
  import type {PortableTextSlateEditor} from '../types/editor'
21
21
 
22
+ type CreateTestEditorOptions = {
23
+ initialValue?: Array<PortableTextBlock>
24
+ keyGenerator?: () => string
25
+ schemaDefinition?: SchemaDefinition
26
+ children?: React.ReactNode
27
+ editableProps?: PortableTextEditableProps
28
+ }
29
+
22
30
  export async function createTestEditor(
23
- options: {
24
- initialValue?: Array<PortableTextBlock>
25
- keyGenerator?: () => string
26
- schemaDefinition?: SchemaDefinition
27
- children?: React.ReactNode
28
- editableProps?: PortableTextEditableProps
29
- } = {},
30
- ): Promise<Pick<Context, 'editor' | 'locator'>> {
31
+ options: CreateTestEditorOptions = {},
32
+ ): Promise<
33
+ Pick<Context, 'editor' | 'locator'> & {
34
+ rerender: (options?: CreateTestEditorOptions) => void
35
+ }
36
+ > {
31
37
  const editorRef = React.createRef<Editor>()
32
38
  const editorActorRef = React.createRef<EditorActor>()
33
39
  const slateRef = React.createRef<PortableTextSlateEditor>()
34
40
  const keyGenerator = options.keyGenerator ?? createTestKeyGenerator()
35
41
 
36
- render(
42
+ const renderResult = render(
37
43
  <EditorProvider
38
44
  initialConfig={{
39
- keyGenerator,
45
+ keyGenerator: keyGenerator,
40
46
  schemaDefinition: options.schemaDefinition ?? defineSchema({}),
41
47
  initialValue: options.initialValue,
42
48
  }}
@@ -49,6 +55,40 @@ export async function createTestEditor(
49
55
  </EditorProvider>,
50
56
  )
51
57
 
58
+ function rerender(newOptions?: CreateTestEditorOptions) {
59
+ newOptions
60
+ ? renderResult.rerender(
61
+ <EditorProvider
62
+ initialConfig={{
63
+ keyGenerator: keyGenerator,
64
+ schemaDefinition: newOptions.schemaDefinition ?? defineSchema({}),
65
+ initialValue: newOptions.initialValue,
66
+ }}
67
+ >
68
+ <EditorRefPlugin ref={editorRef} />
69
+ <InternalEditorAfterRefPlugin ref={editorActorRef} />
70
+ <InternalSlateEditorRefPlugin ref={slateRef} />
71
+ <PortableTextEditable {...newOptions.editableProps} />
72
+ {newOptions.children}
73
+ </EditorProvider>,
74
+ )
75
+ : renderResult.rerender(
76
+ <EditorProvider
77
+ initialConfig={{
78
+ keyGenerator: keyGenerator,
79
+ schemaDefinition: options.schemaDefinition ?? defineSchema({}),
80
+ initialValue: options.initialValue,
81
+ }}
82
+ >
83
+ <EditorRefPlugin ref={editorRef} />
84
+ <InternalEditorAfterRefPlugin ref={editorActorRef} />
85
+ <InternalSlateEditorRefPlugin ref={slateRef} />
86
+ <PortableTextEditable {...options.editableProps} />
87
+ {options.children}
88
+ </EditorProvider>,
89
+ )
90
+ }
91
+
52
92
  const locator = page.getByRole('textbox')
53
93
 
54
94
  await vi.waitFor(() => expect.element(locator).toBeInTheDocument())
@@ -67,5 +107,6 @@ export async function createTestEditor(
67
107
  sendNativeEvent,
68
108
  },
69
109
  locator,
110
+ rerender,
70
111
  }
71
112
  }