@portabletext/editor 1.22.0 → 1.24.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 (53) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +65 -2
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/util.slice-blocks.cjs +26 -12
  4. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  5. package/lib/_chunks-es/behavior.core.js +65 -2
  6. package/lib/_chunks-es/behavior.core.js.map +1 -1
  7. package/lib/_chunks-es/util.slice-blocks.js +26 -12
  8. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  9. package/lib/behaviors/index.d.cts +1111 -44
  10. package/lib/behaviors/index.d.ts +1111 -44
  11. package/lib/index.cjs +542 -333
  12. package/lib/index.cjs.map +1 -1
  13. package/lib/index.d.cts +446 -1
  14. package/lib/index.d.ts +446 -1
  15. package/lib/index.js +546 -335
  16. package/lib/index.js.map +1 -1
  17. package/lib/selectors/index.d.cts +73 -0
  18. package/lib/selectors/index.d.ts +73 -0
  19. package/package.json +23 -18
  20. package/src/behavior-actions/behavior.action.data-transfer-set.ts +7 -0
  21. package/src/behavior-actions/behavior.action.insert-blocks.ts +61 -0
  22. package/src/behavior-actions/behavior.actions.ts +75 -0
  23. package/src/behaviors/behavior.core.deserialize.ts +46 -0
  24. package/src/behaviors/behavior.core.serialize.ts +44 -0
  25. package/src/behaviors/behavior.core.ts +7 -0
  26. package/src/behaviors/behavior.types.ts +39 -2
  27. package/src/converters/converter.json.ts +53 -0
  28. package/src/converters/converter.portable-text.deserialize.test.ts +686 -0
  29. package/src/converters/converter.portable-text.ts +59 -0
  30. package/src/converters/converter.text-html.deserialize.test.ts +349 -0
  31. package/src/converters/converter.text-html.serialize.test.ts +233 -0
  32. package/src/converters/converter.text-html.ts +61 -0
  33. package/src/converters/converter.text-plain.test.ts +241 -0
  34. package/src/converters/converter.text-plain.ts +91 -0
  35. package/src/converters/converter.ts +65 -0
  36. package/src/converters/converters.ts +11 -0
  37. package/src/editor/Editable.tsx +3 -13
  38. package/src/editor/create-editor.ts +3 -0
  39. package/src/editor/editor-machine.ts +25 -1
  40. package/src/editor/editor-selector.ts +1 -0
  41. package/src/editor/editor-snapshot.ts +5 -0
  42. package/src/editor/plugins/create-with-event-listeners.ts +44 -0
  43. package/src/internal-utils/asserters.ts +9 -0
  44. package/src/internal-utils/mime-type.ts +1 -0
  45. package/src/internal-utils/parse-blocks.ts +136 -0
  46. package/src/internal-utils/test-key-generator.ts +9 -0
  47. package/src/selectors/selector.get-selected-spans.test.ts +1 -0
  48. package/src/selectors/selector.get-selection-text.test.ts +1 -0
  49. package/src/selectors/selector.is-active-decorator.test.ts +1 -0
  50. package/src/utils/util.slice-blocks.test.ts +216 -35
  51. package/src/utils/util.slice-blocks.ts +37 -10
  52. package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +0 -181
  53. package/src/editor/plugins/createWithInsertData.ts +0 -425
@@ -15,11 +15,61 @@ import {
15
15
  PortableTextTextBlock,
16
16
  } from '@sanity/types'
17
17
 
18
+ declare type Converter<TMIMEType extends MIMEType = MIMEType> = {
19
+ mimeType: TMIMEType
20
+ serialize: Serializer<TMIMEType>
21
+ deserialize: Deserializer<TMIMEType>
22
+ }
23
+
24
+ declare type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
25
+ | {
26
+ type: 'serialize'
27
+ originEvent: 'copy' | 'cut' | 'unknown'
28
+ }
29
+ | {
30
+ type: 'serialization.failure'
31
+ mimeType: TMIMEType
32
+ reason: string
33
+ }
34
+ | {
35
+ type: 'serialization.success'
36
+ data: string
37
+ mimeType: TMIMEType
38
+ originEvent: 'copy' | 'cut' | 'unknown'
39
+ }
40
+ | {
41
+ type: 'deserialize'
42
+ data: string
43
+ }
44
+ | {
45
+ type: 'deserialization.failure'
46
+ mimeType: TMIMEType
47
+ reason: string
48
+ }
49
+ | {
50
+ type: 'deserialization.success'
51
+ data: Array<PortableTextBlock>
52
+ mimeType: TMIMEType
53
+ }
54
+
55
+ declare type Deserializer<TMIMEType extends MIMEType> = ({
56
+ context,
57
+ event,
58
+ }: {
59
+ context: EditorContext
60
+ event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'deserialize'>
61
+ }) => PickFromUnion<
62
+ ConverterEvent<TMIMEType>,
63
+ 'type',
64
+ 'deserialization.success' | 'deserialization.failure'
65
+ >
66
+
18
67
  /**
19
68
  * @public
20
69
  */
21
70
  export declare type EditorContext = {
22
71
  activeDecorators: Array<string>
72
+ converters: Array<Converter>
23
73
  keyGenerator: () => string
24
74
  schema: EditorSchema
25
75
  selection: EditorSelection
@@ -289,6 +339,17 @@ export declare const isSelectionCollapsed: EditorSelector<boolean>
289
339
  */
290
340
  export declare const isSelectionExpanded: EditorSelector<boolean>
291
341
 
342
+ declare type MIMEType = `${string}/${string}`
343
+
344
+ /**
345
+ * @alpha
346
+ */
347
+ declare type PickFromUnion<
348
+ TUnion,
349
+ TTagKey extends keyof TUnion,
350
+ TPickedTags extends TUnion[TTagKey],
351
+ > = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
352
+
292
353
  /** @internal */
293
354
  export declare type PortableTextMemberSchemaTypes = {
294
355
  annotations: (ObjectSchemaType & {
@@ -304,4 +365,16 @@ export declare type PortableTextMemberSchemaTypes = {
304
365
  lists: BlockListDefinition[]
305
366
  }
306
367
 
368
+ declare type Serializer<TMIMEType extends MIMEType> = ({
369
+ context,
370
+ event,
371
+ }: {
372
+ context: EditorContext
373
+ event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'serialize'>
374
+ }) => PickFromUnion<
375
+ ConverterEvent<TMIMEType>,
376
+ 'type',
377
+ 'serialization.success' | 'serialization.failure'
378
+ >
379
+
307
380
  export {}
@@ -15,11 +15,61 @@ import {
15
15
  PortableTextTextBlock,
16
16
  } from '@sanity/types'
17
17
 
18
+ declare type Converter<TMIMEType extends MIMEType = MIMEType> = {
19
+ mimeType: TMIMEType
20
+ serialize: Serializer<TMIMEType>
21
+ deserialize: Deserializer<TMIMEType>
22
+ }
23
+
24
+ declare type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
25
+ | {
26
+ type: 'serialize'
27
+ originEvent: 'copy' | 'cut' | 'unknown'
28
+ }
29
+ | {
30
+ type: 'serialization.failure'
31
+ mimeType: TMIMEType
32
+ reason: string
33
+ }
34
+ | {
35
+ type: 'serialization.success'
36
+ data: string
37
+ mimeType: TMIMEType
38
+ originEvent: 'copy' | 'cut' | 'unknown'
39
+ }
40
+ | {
41
+ type: 'deserialize'
42
+ data: string
43
+ }
44
+ | {
45
+ type: 'deserialization.failure'
46
+ mimeType: TMIMEType
47
+ reason: string
48
+ }
49
+ | {
50
+ type: 'deserialization.success'
51
+ data: Array<PortableTextBlock>
52
+ mimeType: TMIMEType
53
+ }
54
+
55
+ declare type Deserializer<TMIMEType extends MIMEType> = ({
56
+ context,
57
+ event,
58
+ }: {
59
+ context: EditorContext
60
+ event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'deserialize'>
61
+ }) => PickFromUnion<
62
+ ConverterEvent<TMIMEType>,
63
+ 'type',
64
+ 'deserialization.success' | 'deserialization.failure'
65
+ >
66
+
18
67
  /**
19
68
  * @public
20
69
  */
21
70
  export declare type EditorContext = {
22
71
  activeDecorators: Array<string>
72
+ converters: Array<Converter>
23
73
  keyGenerator: () => string
24
74
  schema: EditorSchema
25
75
  selection: EditorSelection
@@ -289,6 +339,17 @@ export declare const isSelectionCollapsed: EditorSelector<boolean>
289
339
  */
290
340
  export declare const isSelectionExpanded: EditorSelector<boolean>
291
341
 
342
+ declare type MIMEType = `${string}/${string}`
343
+
344
+ /**
345
+ * @alpha
346
+ */
347
+ declare type PickFromUnion<
348
+ TUnion,
349
+ TTagKey extends keyof TUnion,
350
+ TPickedTags extends TUnion[TTagKey],
351
+ > = TUnion extends Record<TTagKey, TPickedTags> ? TUnion : never
352
+
292
353
  /** @internal */
293
354
  export declare type PortableTextMemberSchemaTypes = {
294
355
  annotations: (ObjectSchemaType & {
@@ -304,4 +365,16 @@ export declare type PortableTextMemberSchemaTypes = {
304
365
  lists: BlockListDefinition[]
305
366
  }
306
367
 
368
+ declare type Serializer<TMIMEType extends MIMEType> = ({
369
+ context,
370
+ event,
371
+ }: {
372
+ context: EditorContext
373
+ event: PickFromUnion<ConverterEvent<TMIMEType>, 'type', 'serialize'>
374
+ }) => PickFromUnion<
375
+ ConverterEvent<TMIMEType>,
376
+ 'type',
377
+ 'serialization.success' | 'serialization.failure'
378
+ >
379
+
307
380
  export {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -61,26 +61,27 @@
61
61
  "src"
62
62
  ],
63
63
  "dependencies": {
64
+ "@portabletext/to-html": "^2.0.13",
64
65
  "@xstate/react": "^5.0.2",
65
66
  "debug": "^4.4.0",
66
67
  "get-random-values-esm": "^1.0.2",
67
68
  "lodash": "^4.17.21",
68
69
  "lodash.startcase": "^4.4.0",
69
- "react-compiler-runtime": "19.0.0-beta-55955c9-20241229",
70
+ "react-compiler-runtime": "19.0.0-beta-decd7b8-20250118",
70
71
  "slate": "0.112.0",
71
72
  "slate-dom": "^0.111.0",
72
- "slate-react": "0.112.0",
73
+ "slate-react": "0.112.1",
73
74
  "use-effect-event": "^1.0.2",
74
75
  "xstate": "^5.19.2",
75
- "@portabletext/block-tools": "1.1.0",
76
- "@portabletext/patches": "1.1.1"
76
+ "@portabletext/block-tools": "1.1.1",
77
+ "@portabletext/patches": "1.1.2"
77
78
  },
78
79
  "devDependencies": {
79
80
  "@portabletext/toolkit": "^2.0.16",
80
- "@sanity/diff-match-patch": "^3.1.2",
81
- "@sanity/pkg-utils": "^7.0.1",
82
- "@sanity/schema": "^3.70.0",
83
- "@sanity/types": "^3.70.0",
81
+ "@sanity/diff-match-patch": "^3.2.0",
82
+ "@sanity/pkg-utils": "^7.0.2",
83
+ "@sanity/schema": "^3.71.0",
84
+ "@sanity/types": "^3.71.0",
84
85
  "@testing-library/jest-dom": "^6.6.3",
85
86
  "@testing-library/react": "^16.2.0",
86
87
  "@types/debug": "^4.1.12",
@@ -91,11 +92,11 @@
91
92
  "@typescript-eslint/eslint-plugin": "^8.18.1",
92
93
  "@typescript-eslint/parser": "^8.18.1",
93
94
  "@vitejs/plugin-react": "^4.3.4",
94
- "@vitest/browser": "^2.1.8",
95
- "@vitest/coverage-istanbul": "^2.1.8",
96
- "babel-plugin-react-compiler": "19.0.0-beta-e552027-20250112",
95
+ "@vitest/browser": "^3.0.2",
96
+ "@vitest/coverage-istanbul": "^3.0.2",
97
+ "babel-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
97
98
  "eslint": "8.57.1",
98
- "eslint-plugin-react-compiler": "19.0.0-beta-e552027-20250112",
99
+ "eslint-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118",
99
100
  "eslint-plugin-react-hooks": "^5.1.0",
100
101
  "jsdom": "^26.0.0",
101
102
  "react": "^19.0.0",
@@ -103,13 +104,13 @@
103
104
  "rxjs": "^7.8.1",
104
105
  "typescript": "5.7.3",
105
106
  "vite": "^6.0.4",
106
- "vitest": "^2.1.8",
107
+ "vitest": "^3.0.2",
107
108
  "vitest-browser-react": "^0.0.4",
108
109
  "racejar": "1.1.1"
109
110
  },
110
111
  "peerDependencies": {
111
- "@sanity/schema": "^3.70.0",
112
- "@sanity/types": "^3.70.0",
112
+ "@sanity/schema": "^3.71.0",
113
+ "@sanity/types": "^3.71.0",
113
114
  "react": "^16.9 || ^17 || ^18 || ^19",
114
115
  "rxjs": "^7.8.1"
115
116
  },
@@ -129,8 +130,12 @@
129
130
  "lint:fix": "biome lint --write .",
130
131
  "test": "vitest --run",
131
132
  "test:watch": "vitest",
132
- "test:chromium": "vitest --run --project chromium",
133
- "test:chromium:watch": "vitest --project chromium",
133
+ "test:chromium": "vitest --run --project \"browser (chromium)\"",
134
+ "test:chromium:watch": "vitest --project \"browser (chromium)\"",
135
+ "test:firefox": "vitest --run --project \"browser (firefox)\"",
136
+ "test:firefox:watch": "vitest --project \"browser (firefox)\"",
137
+ "test:webkit": "vitest --run --project \"browser (webkit)\"",
138
+ "test:webkit:watch": "vitest --project \"browser (webkit)\"",
134
139
  "test:unit": "vitest --run --project unit",
135
140
  "test:unit:watch": "vitest --project unit"
136
141
  }
@@ -0,0 +1,7 @@
1
+ import type {BehaviorActionImplementation} from './behavior.actions'
2
+
3
+ export const dataTransferSetActionImplementation: BehaviorActionImplementation<
4
+ 'data transfer.set'
5
+ > = ({action}) => {
6
+ action.dataTransfer.setData(action.mimeType, action.data)
7
+ }
@@ -0,0 +1,61 @@
1
+ import {isEqual, uniq} from 'lodash'
2
+ import {Editor, Transforms} from 'slate'
3
+ import {isEqualToEmptyEditor, toSlateValue} from '../internal-utils/values'
4
+ import type {BehaviorActionImplementation} from './behavior.actions'
5
+
6
+ export const insertBlocksActionImplementation: BehaviorActionImplementation<
7
+ 'insert.blocks'
8
+ > = ({context, action}) => {
9
+ const fragment = toSlateValue(action.blocks, {schemaTypes: context.schema})
10
+
11
+ if (!action.editor.selection) {
12
+ return
13
+ }
14
+ // Ensure that markDefs for any annotations inside this fragment are copied over to the focused text block.
15
+ const [focusBlock, focusPath] = Editor.node(
16
+ action.editor,
17
+ action.editor.selection,
18
+ {
19
+ depth: 1,
20
+ },
21
+ )
22
+
23
+ if (
24
+ action.editor.isTextBlock(focusBlock) &&
25
+ action.editor.isTextBlock(fragment[0])
26
+ ) {
27
+ const {markDefs} = focusBlock
28
+ if (!isEqual(markDefs, fragment[0].markDefs)) {
29
+ Transforms.setNodes(
30
+ action.editor,
31
+ {
32
+ markDefs: uniq([
33
+ ...(fragment[0].markDefs || []),
34
+ ...(markDefs || []),
35
+ ]),
36
+ },
37
+ {at: focusPath, mode: 'lowest', voids: false},
38
+ )
39
+ }
40
+ }
41
+
42
+ const isPasteToEmptyEditor = isEqualToEmptyEditor(
43
+ action.editor.children,
44
+ context.schema,
45
+ )
46
+
47
+ if (isPasteToEmptyEditor) {
48
+ // Special case for pasting directly into an empty editor (a placeholder block).
49
+ // When pasting content starting with multiple empty blocks,
50
+ // `editor.insertFragment` can potentially duplicate the keys of
51
+ // the placeholder block because of operations that happen
52
+ // inside `editor.insertFragment` (involves an `insert_node` operation).
53
+ // However by splitting the placeholder block first in this situation we are good.
54
+ Transforms.splitNodes(action.editor, {at: [0, 0]})
55
+ action.editor.insertFragment(fragment)
56
+ Transforms.removeNodes(action.editor, {at: [0]})
57
+ } else {
58
+ // All other inserts
59
+ action.editor.insertFragment(fragment)
60
+ }
61
+ }
@@ -28,7 +28,9 @@ import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
28
28
  import type {PickFromUnion} from '../type-utils'
29
29
  import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
30
30
  import {insertBlock} from './behavior.action-utils.insert-block'
31
+ import {dataTransferSetActionImplementation} from './behavior.action.data-transfer-set'
31
32
  import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
33
+ import {insertBlocksActionImplementation} from './behavior.action.insert-blocks'
32
34
  import {
33
35
  insertBreakActionImplementation,
34
36
  insertSoftBreakActionImplementation,
@@ -75,6 +77,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
75
77
  'blur': ({action}) => {
76
78
  ReactEditor.blur(action.editor)
77
79
  },
80
+ 'data transfer.set': dataTransferSetActionImplementation,
78
81
  'decorator.add': addDecoratorActionImplementation,
79
82
  'decorator.remove': removeDecoratorActionImplementation,
80
83
  'decorator.toggle': toggleDecoratorActionImplementation,
@@ -143,6 +146,22 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
143
146
  at: range,
144
147
  })
145
148
  },
149
+ 'deserialization.failure': ({action}) => {
150
+ console.error(
151
+ `Deserialization of ${action.mimeType} failed with reason ${action.reason}`,
152
+ )
153
+ },
154
+ 'deserialization.success': ({context, action}) => {
155
+ insertBlocksActionImplementation({
156
+ context,
157
+ action: {
158
+ type: 'insert.blocks',
159
+ blocks: action.data,
160
+ editor: action.editor,
161
+ },
162
+ })
163
+ },
164
+ 'insert.blocks': insertBlocksActionImplementation,
146
165
  'insert.block object': insertBlockObjectActionImplementation,
147
166
  'insert.break': insertBreakActionImplementation,
148
167
  'insert.inline object': insertInlineObjectActionImplementation,
@@ -260,6 +279,20 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
260
279
 
261
280
  Transforms.select(action.editor, nextBlockPath)
262
281
  },
282
+ 'serialization.failure': ({action}) => {
283
+ console.error(
284
+ `Serialization of ${action.mimeType} failed with reason ${action.reason}`,
285
+ )
286
+ },
287
+ 'serialization.success': ({context, action}) => {
288
+ dataTransferSetActionImplementation({
289
+ context,
290
+ action: {
291
+ ...action,
292
+ type: 'data transfer.set',
293
+ },
294
+ })
295
+ },
263
296
  'style.toggle': toggleStyleActionImplementation,
264
297
  'style.add': addStyleActionImplementation,
265
298
  'style.remove': removeStyleActionImplementation,
@@ -457,6 +490,13 @@ function performDefaultAction({
457
490
  })
458
491
  break
459
492
  }
493
+ case 'data transfer.set': {
494
+ behaviorActionImplementations['data transfer.set']({
495
+ context,
496
+ action,
497
+ })
498
+ break
499
+ }
460
500
  case 'decorator.toggle': {
461
501
  behaviorActionImplementations['decorator.toggle']({
462
502
  context,
@@ -478,6 +518,20 @@ function performDefaultAction({
478
518
  })
479
519
  break
480
520
  }
521
+ case 'deserialization.failure': {
522
+ behaviorActionImplementations['deserialization.failure']({
523
+ context,
524
+ action,
525
+ })
526
+ break
527
+ }
528
+ case 'deserialization.success': {
529
+ behaviorActionImplementations['deserialization.success']({
530
+ context,
531
+ action,
532
+ })
533
+ break
534
+ }
481
535
  case 'focus': {
482
536
  behaviorActionImplementations.focus({
483
537
  context,
@@ -485,6 +539,13 @@ function performDefaultAction({
485
539
  })
486
540
  break
487
541
  }
542
+ case 'insert.blocks': {
543
+ behaviorActionImplementations['insert.blocks']({
544
+ context,
545
+ action,
546
+ })
547
+ break
548
+ }
488
549
  case 'insert.block object': {
489
550
  behaviorActionImplementations['insert.block object']({
490
551
  context,
@@ -534,6 +595,20 @@ function performDefaultAction({
534
595
  })
535
596
  break
536
597
  }
598
+ case 'serialization.failure': {
599
+ behaviorActionImplementations['serialization.failure']({
600
+ context,
601
+ action,
602
+ })
603
+ break
604
+ }
605
+ case 'serialization.success': {
606
+ behaviorActionImplementations['serialization.success']({
607
+ context,
608
+ action,
609
+ })
610
+ break
611
+ }
537
612
  default: {
538
613
  behaviorActionImplementations['style.toggle']({
539
614
  context,
@@ -0,0 +1,46 @@
1
+ import {defineBehavior, raise} from './behavior.types'
2
+
3
+ export const coreDeserializeBehavior = defineBehavior({
4
+ on: 'deserialize',
5
+ guard: ({context, event}) => {
6
+ const deserializeEvents = context.converters.flatMap((converter) => {
7
+ const data = event.dataTransfer.getData(converter.mimeType)
8
+
9
+ if (!data) {
10
+ return []
11
+ }
12
+
13
+ return [
14
+ converter.deserialize({context, event: {type: 'deserialize', data}}),
15
+ ]
16
+ })
17
+
18
+ const firstSuccess = deserializeEvents.find(
19
+ (deserializeEvent) => deserializeEvent.type === 'deserialization.success',
20
+ )
21
+
22
+ if (!firstSuccess) {
23
+ return {
24
+ type: 'deserialization.failure',
25
+ mimeType: '*/*',
26
+ reason: deserializeEvents
27
+ .map((deserializeEvent) =>
28
+ deserializeEvent.type === 'deserialization.failure'
29
+ ? deserializeEvent.reason
30
+ : '',
31
+ )
32
+ .join(', '),
33
+ } as const
34
+ }
35
+
36
+ return firstSuccess
37
+ },
38
+ actions: [
39
+ ({event}, deserializeEvent) => [
40
+ raise({
41
+ ...deserializeEvent,
42
+ dataTransfer: event.dataTransfer,
43
+ }),
44
+ ],
45
+ ],
46
+ })
@@ -0,0 +1,44 @@
1
+ import {defineBehavior, raise} from './behavior.types'
2
+
3
+ export const coreSerializeBehaviors = {
4
+ 'serialize': defineBehavior({
5
+ on: 'serialize',
6
+ guard: ({context, event}) => {
7
+ if (context.converters.length === 0) {
8
+ return false
9
+ }
10
+
11
+ const serializeEvents = context.converters.map((converter) =>
12
+ converter.serialize({context, event}),
13
+ )
14
+
15
+ if (serializeEvents.length === 0) {
16
+ return false
17
+ }
18
+
19
+ return serializeEvents
20
+ },
21
+ actions: [
22
+ ({event}, serializeEvents) =>
23
+ serializeEvents.map((serializeEvent) =>
24
+ raise({
25
+ ...serializeEvent,
26
+ dataTransfer: event.dataTransfer,
27
+ }),
28
+ ),
29
+ ],
30
+ }),
31
+ 'serialization.success': defineBehavior({
32
+ on: 'serialization.success',
33
+ actions: [
34
+ ({event}) => [
35
+ raise({
36
+ type: 'data transfer.set',
37
+ data: event.data,
38
+ dataTransfer: event.dataTransfer,
39
+ mimeType: event.mimeType,
40
+ }),
41
+ ],
42
+ ],
43
+ }),
44
+ }
@@ -1,6 +1,8 @@
1
1
  import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
2
2
  import {coreDecoratorBehaviors} from './behavior.core.decorators'
3
+ import {coreDeserializeBehavior} from './behavior.core.deserialize'
3
4
  import {coreListBehaviors} from './behavior.core.lists'
5
+ import {coreSerializeBehaviors} from './behavior.core.serialize'
4
6
  import {defineBehavior} from './behavior.types'
5
7
 
6
8
  const softReturn = defineBehavior({
@@ -17,6 +19,7 @@ export const coreBehaviors = [
17
19
  coreDecoratorBehaviors.emShortcut,
18
20
  coreDecoratorBehaviors.underlineShortcut,
19
21
  coreDecoratorBehaviors.codeShortcut,
22
+ coreDeserializeBehavior,
20
23
  coreBlockObjectBehaviors.arrowDownOnLonelyBlockObject,
21
24
  coreBlockObjectBehaviors.arrowUpOnLonelyBlockObject,
22
25
  coreBlockObjectBehaviors.breakingBlockObject,
@@ -27,6 +30,8 @@ export const coreBehaviors = [
27
30
  coreListBehaviors.clearListOnEnter,
28
31
  coreListBehaviors.indentListOnTab,
29
32
  coreListBehaviors.unindentListOnShiftTab,
33
+ coreSerializeBehaviors.serialize,
34
+ coreSerializeBehaviors['serialization.success'],
30
35
  ]
31
36
 
32
37
  /**
@@ -35,6 +40,8 @@ export const coreBehaviors = [
35
40
  export const coreBehavior = {
36
41
  softReturn,
37
42
  decorators: coreDecoratorBehaviors,
43
+ deserialize: coreDeserializeBehavior,
38
44
  blockObjects: coreBlockObjectBehaviors,
39
45
  lists: coreListBehaviors,
46
+ ...coreSerializeBehaviors,
40
47
  }
@@ -1,7 +1,13 @@
1
- import type {KeyedSegment, PortableTextTextBlock} from '@sanity/types'
1
+ import type {
2
+ KeyedSegment,
3
+ PortableTextBlock,
4
+ PortableTextTextBlock,
5
+ } from '@sanity/types'
2
6
  import type {TextUnit} from 'slate'
3
7
  import type {TextInsertTextOptions} from 'slate/dist/interfaces/transforms/text'
8
+ import type {ConverterEvent} from '../converters/converter'
4
9
  import type {EditorContext} from '../editor/editor-snapshot'
10
+ import type {MIMEType} from '../internal-utils/mime-type'
5
11
  import type {OmitFromUnion, PickFromUnion} from '../type-utils'
6
12
  import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
7
13
 
@@ -25,6 +31,12 @@ export type SyntheticBehaviorEvent =
25
31
  | {
26
32
  type: 'blur'
27
33
  }
34
+ | {
35
+ type: 'data transfer.set'
36
+ data: string
37
+ dataTransfer: DataTransfer
38
+ mimeType: MIMEType
39
+ }
28
40
  | {
29
41
  type: 'decorator.toggle'
30
42
  decorator: string
@@ -40,6 +52,10 @@ export type SyntheticBehaviorEvent =
40
52
  | {
41
53
  type: 'focus'
42
54
  }
55
+ | {
56
+ type: 'insert.blocks'
57
+ blocks: Array<PortableTextBlock>
58
+ }
43
59
  | {
44
60
  type: 'insert.block object'
45
61
  placement: 'auto' | 'after' | 'before'
@@ -78,6 +94,14 @@ export type SyntheticBehaviorEvent =
78
94
  type: 'style.toggle'
79
95
  style: string
80
96
  }
97
+ | (PickFromUnion<
98
+ ConverterEvent,
99
+ 'type',
100
+ | 'deserialization.failure'
101
+ | 'deserialization.success'
102
+ | 'serialization.failure'
103
+ | 'serialization.success'
104
+ > & {dataTransfer: DataTransfer})
81
105
 
82
106
  /**
83
107
  * @beta
@@ -87,6 +111,10 @@ export type NativeBehaviorEvent =
87
111
  type: 'copy'
88
112
  data: DataTransfer
89
113
  }
114
+ | {
115
+ type: 'deserialize'
116
+ dataTransfer: DataTransfer
117
+ }
90
118
  | {
91
119
  type: 'key.down'
92
120
  keyboardEvent: Pick<
@@ -105,6 +133,11 @@ export type NativeBehaviorEvent =
105
133
  type: 'paste'
106
134
  data: DataTransfer
107
135
  }
136
+ | {
137
+ type: 'serialize'
138
+ originEvent: 'copy' | 'cut' | 'unknown'
139
+ dataTransfer: DataTransfer
140
+ }
108
141
 
109
142
  /**
110
143
  * @beta
@@ -261,7 +294,11 @@ export type BehaviorEvent =
261
294
  export type Behavior<
262
295
  TBehaviorEventType extends BehaviorEvent['type'] = BehaviorEvent['type'],
263
296
  TGuardResponse = true,
264
- TBehaviorEvent extends BehaviorEvent = BehaviorEvent,
297
+ TBehaviorEvent extends BehaviorEvent = PickFromUnion<
298
+ BehaviorEvent,
299
+ 'type',
300
+ TBehaviorEventType
301
+ >,
265
302
  > = {
266
303
  /**
267
304
  * The internal editor event that triggers this behavior.