@portabletext/editor 1.36.6 → 1.38.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 (119) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +84 -49
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +1 -1
  4. package/lib/_chunks-cjs/editor-provider.cjs +919 -526
  5. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  6. package/lib/_chunks-cjs/{util.block-offsets-to-selection.cjs → parse-blocks.cjs} +36 -21
  7. package/lib/_chunks-cjs/parse-blocks.cjs.map +1 -0
  8. package/lib/_chunks-cjs/selector.get-text-before.cjs +2 -2
  9. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  10. package/lib/_chunks-cjs/{selector.is-active-style.cjs → selector.is-overlapping-selection.cjs} +144 -3
  11. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -0
  12. package/lib/_chunks-cjs/util.slice-blocks.cjs +12 -0
  13. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  14. package/lib/_chunks-es/behavior.core.js +84 -49
  15. package/lib/_chunks-es/behavior.core.js.map +1 -1
  16. package/lib/_chunks-es/behavior.markdown.js +1 -1
  17. package/lib/_chunks-es/editor-provider.js +911 -517
  18. package/lib/_chunks-es/editor-provider.js.map +1 -1
  19. package/lib/_chunks-es/{util.block-offsets-to-selection.js → parse-blocks.js} +37 -22
  20. package/lib/_chunks-es/parse-blocks.js.map +1 -0
  21. package/lib/_chunks-es/selector.get-text-before.js +1 -2
  22. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  23. package/lib/_chunks-es/{selector.is-active-style.js → selector.is-overlapping-selection.js} +146 -5
  24. package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -0
  25. package/lib/_chunks-es/util.slice-blocks.js +12 -0
  26. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  27. package/lib/behaviors/index.d.cts +10535 -4689
  28. package/lib/behaviors/index.d.ts +10535 -4689
  29. package/lib/index.cjs +582 -209
  30. package/lib/index.cjs.map +1 -1
  31. package/lib/index.d.cts +5297 -1178
  32. package/lib/index.d.ts +5297 -1178
  33. package/lib/index.js +591 -213
  34. package/lib/index.js.map +1 -1
  35. package/lib/plugins/index.cjs +2 -2
  36. package/lib/plugins/index.cjs.map +1 -1
  37. package/lib/plugins/index.d.cts +5297 -1178
  38. package/lib/plugins/index.d.ts +5297 -1178
  39. package/lib/plugins/index.js +2 -2
  40. package/lib/selectors/index.cjs +21 -103
  41. package/lib/selectors/index.cjs.map +1 -1
  42. package/lib/selectors/index.d.cts +5313 -1178
  43. package/lib/selectors/index.d.ts +5313 -1178
  44. package/lib/selectors/index.js +13 -96
  45. package/lib/selectors/index.js.map +1 -1
  46. package/lib/utils/index.cjs +4 -4
  47. package/lib/utils/index.cjs.map +1 -1
  48. package/lib/utils/index.d.cts +5297 -1178
  49. package/lib/utils/index.d.ts +5297 -1178
  50. package/lib/utils/index.js +3 -4
  51. package/lib/utils/index.js.map +1 -1
  52. package/package.json +15 -14
  53. package/src/behavior-actions/behavior.action.blur.ts +8 -0
  54. package/src/behavior-actions/behavior.action.decorator.add.ts +2 -1
  55. package/src/behavior-actions/behavior.action.delete.backward.ts +7 -0
  56. package/src/behavior-actions/behavior.action.delete.block.ts +24 -0
  57. package/src/behavior-actions/behavior.action.delete.forward.ts +7 -0
  58. package/src/behavior-actions/behavior.action.delete.text.ts +2 -1
  59. package/src/behavior-actions/behavior.action.delete.ts +1 -3
  60. package/src/behavior-actions/behavior.action.deserialization.failure.ts +9 -0
  61. package/src/behavior-actions/behavior.action.deserialization.success.ts +16 -0
  62. package/src/behavior-actions/behavior.action.effect.ts +7 -0
  63. package/src/behavior-actions/behavior.action.focus.ts +8 -0
  64. package/src/behavior-actions/behavior.action.insert-blocks.ts +118 -74
  65. package/src/behavior-actions/behavior.action.insert-break.ts +1 -0
  66. package/src/behavior-actions/{behavior.action.insert-block-object.ts → behavior.action.insert.block-object.ts} +9 -14
  67. package/src/behavior-actions/behavior.action.insert.block.ts +247 -2
  68. package/src/behavior-actions/behavior.action.insert.text-block.ts +33 -0
  69. package/src/behavior-actions/behavior.action.insert.text.ts +7 -0
  70. package/src/behavior-actions/behavior.action.move.block-down.ts +48 -0
  71. package/src/behavior-actions/behavior.action.move.block-up.ts +53 -0
  72. package/src/behavior-actions/behavior.action.move.block.ts +16 -0
  73. package/src/behavior-actions/behavior.action.noop.ts +5 -0
  74. package/src/behavior-actions/behavior.action.select.next-block.ts +44 -0
  75. package/src/behavior-actions/behavior.action.select.previous-block.ts +48 -0
  76. package/src/behavior-actions/behavior.action.select.ts +15 -0
  77. package/src/behavior-actions/behavior.action.serialization.failure.ts +9 -0
  78. package/src/behavior-actions/behavior.action.serialization.success.ts +14 -0
  79. package/src/behavior-actions/behavior.actions.ts +54 -212
  80. package/src/behaviors/behavior.core.block-objects.ts +35 -6
  81. package/src/behaviors/behavior.core.insert-break.ts +1 -0
  82. package/src/behaviors/behavior.core.ts +2 -0
  83. package/src/behaviors/behavior.default.ts +241 -33
  84. package/src/behaviors/behavior.types.ts +138 -20
  85. package/src/converters/converter.portable-text.ts +5 -2
  86. package/src/converters/converter.text-html.serialize.test.ts +4 -4
  87. package/src/converters/converter.text-html.ts +5 -2
  88. package/src/converters/converter.text-plain.test.ts +6 -6
  89. package/src/converters/converter.text-plain.ts +5 -2
  90. package/src/converters/converter.types.ts +3 -3
  91. package/src/editor/Editable.tsx +403 -48
  92. package/src/editor/components/Element.tsx +133 -18
  93. package/src/editor/components/use-draggable.ts +34 -102
  94. package/src/editor/editor-machine.ts +66 -10
  95. package/src/editor/editor-selector.ts +2 -0
  96. package/src/editor/editor-snapshot.ts +17 -0
  97. package/src/editor/plugins/create-with-event-listeners.ts +6 -40
  98. package/src/internal-utils/create-test-snapshot.ts +2 -0
  99. package/src/internal-utils/event-position.ts +210 -0
  100. package/src/internal-utils/slate-utils.ts +56 -0
  101. package/src/internal-utils/weakMaps.ts +1 -15
  102. package/src/selectors/index.ts +2 -0
  103. package/src/selectors/selector.get-focus-inline-object.ts +21 -0
  104. package/src/selectors/selector.is-overlapping-selection.test.ts +171 -0
  105. package/src/selectors/selector.is-overlapping-selection.ts +108 -4
  106. package/src/selectors/selector.is-point-after-selection.ts +3 -1
  107. package/src/selectors/selector.is-point-before-selection.ts +3 -1
  108. package/src/selectors/selector.is-selecting-entire-blocks.ts +34 -0
  109. package/lib/_chunks-cjs/selector.is-active-style.cjs.map +0 -1
  110. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +0 -1
  111. package/lib/_chunks-cjs/util.reverse-selection.cjs +0 -14
  112. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +0 -1
  113. package/lib/_chunks-es/selector.is-active-style.js.map +0 -1
  114. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +0 -1
  115. package/lib/_chunks-es/util.reverse-selection.js +0 -15
  116. package/lib/_chunks-es/util.reverse-selection.js.map +0 -1
  117. package/src/behavior-actions/behavior.action-utils.insert-block.ts +0 -61
  118. package/src/editor/__tests__/handleClick.test.tsx +0 -277
  119. package/src/editor/components/use-droppable.ts +0 -135
@@ -5,8 +5,11 @@ import type {
5
5
  PortableTextTextBlock,
6
6
  } from '@sanity/types'
7
7
  import {
8
+ useContext,
9
+ useEffect,
8
10
  useMemo,
9
11
  useRef,
12
+ useState,
10
13
  type FunctionComponent,
11
14
  type JSX,
12
15
  type ReactElement,
@@ -18,9 +21,12 @@ import {
18
21
  useSlateStatic,
19
22
  type RenderElementProps,
20
23
  } from 'slate-react'
24
+ import {defineBehavior} from '../../behaviors'
21
25
  import {debugWithName} from '../../internal-utils/debug'
26
+ import type {EventPositionBlock} from '../../internal-utils/event-position'
22
27
  import {fromSlateValue} from '../../internal-utils/values'
23
28
  import {KEY_TO_VALUE_ELEMENT} from '../../internal-utils/weakMaps'
29
+ import * as selectors from '../../selectors'
24
30
  import type {
25
31
  BlockRenderProps,
26
32
  PortableTextMemberSchemaTypes,
@@ -29,10 +35,10 @@ import type {
29
35
  RenderListItemFunction,
30
36
  RenderStyleFunction,
31
37
  } from '../../types/editor'
38
+ import {EditorActorContext} from '../editor-actor-context'
32
39
  import {DefaultBlockObject, DefaultInlineObject} from './DefaultObject'
33
40
  import {DropIndicator} from './drop-indicator'
34
41
  import {useDraggable} from './use-draggable'
35
- import {useDroppable} from './use-droppable'
36
42
 
37
43
  const debug = debugWithName('components:Element')
38
44
  const debugRenders = false
@@ -72,24 +78,135 @@ export const Element: FunctionComponent<ElementProps> = ({
72
78
  renderStyle,
73
79
  spellCheck,
74
80
  }) => {
75
- const editor = useSlateStatic()
81
+ const editorActor = useContext(EditorActorContext)
82
+ const slateEditor = useSlateStatic()
76
83
  const selected = useSelected()
77
84
  const blockRef = useRef<HTMLDivElement | null>(null)
78
85
  const inlineBlockObjectRef = useRef(null)
79
86
  const focused =
80
- (selected && editor.selection && Range.isCollapsed(editor.selection)) ||
87
+ (selected &&
88
+ slateEditor.selection &&
89
+ Range.isCollapsed(slateEditor.selection)) ||
81
90
  false
82
- const droppable = useDroppable({element, blockRef, readOnly})
83
- const draggable = useDraggable({element, blockRef, readOnly})
91
+ const [dragPositionBlock, setDragPositionBlock] =
92
+ useState<EventPositionBlock>()
93
+ const draggable = useDraggable({element, readOnly, blockRef})
94
+
95
+ useEffect(() => {
96
+ const behavior = defineBehavior({
97
+ on: 'drag.dragover',
98
+ guard: ({snapshot, event}) => {
99
+ const dropFocusBlock = selectors.getFocusBlock({
100
+ ...snapshot,
101
+ context: {
102
+ ...snapshot.context,
103
+ selection: event.position.selection,
104
+ },
105
+ })
106
+
107
+ if (!dropFocusBlock || dropFocusBlock.node._key !== element._key) {
108
+ return false
109
+ }
110
+
111
+ const dragOrigin = snapshot.beta.internalDrag?.origin
112
+
113
+ if (!dragOrigin) {
114
+ return false
115
+ }
116
+
117
+ const draggedBlocks = selectors.getSelectedBlocks({
118
+ ...snapshot,
119
+ context: {
120
+ ...snapshot.context,
121
+ selection: dragOrigin.selection,
122
+ },
123
+ })
124
+
125
+ if (
126
+ draggedBlocks.some(
127
+ (draggedBlock) => draggedBlock.node._key === element._key,
128
+ )
129
+ ) {
130
+ return false
131
+ }
132
+
133
+ const draggingEntireBlocks = selectors.isSelectingEntireBlocks({
134
+ ...snapshot,
135
+ context: {
136
+ ...snapshot.context,
137
+ selection: dragOrigin.selection,
138
+ },
139
+ })
140
+
141
+ return draggingEntireBlocks
142
+ },
143
+ actions: [
144
+ ({event}) => [
145
+ {
146
+ type: 'effect',
147
+ effect: () => {
148
+ setDragPositionBlock(event.position.block)
149
+ },
150
+ },
151
+ {
152
+ type: 'noop',
153
+ },
154
+ ],
155
+ ],
156
+ })
157
+
158
+ editorActor.send({
159
+ type: 'add behavior',
160
+ behavior,
161
+ })
162
+
163
+ return () => {
164
+ editorActor.send({
165
+ type: 'remove behavior',
166
+ behavior,
167
+ })
168
+ }
169
+ }, [editorActor, element._key])
170
+
171
+ useEffect(() => {
172
+ const behavior = defineBehavior({
173
+ on: 'drag.*',
174
+ guard: ({event}) => {
175
+ return event.type !== 'drag.dragover'
176
+ },
177
+ actions: [
178
+ () => [
179
+ {
180
+ type: 'effect',
181
+ effect: () => {
182
+ setDragPositionBlock(undefined)
183
+ },
184
+ },
185
+ ],
186
+ ],
187
+ })
188
+
189
+ editorActor.send({
190
+ type: 'add behavior',
191
+ behavior,
192
+ })
193
+
194
+ return () => {
195
+ editorActor.send({
196
+ type: 'remove behavior',
197
+ behavior,
198
+ })
199
+ }
200
+ }, [editorActor])
84
201
 
85
202
  const value = useMemo(
86
203
  () =>
87
204
  fromSlateValue(
88
205
  [element],
89
206
  schemaTypes.block.name,
90
- KEY_TO_VALUE_ELEMENT.get(editor),
207
+ KEY_TO_VALUE_ELEMENT.get(slateEditor),
91
208
  )[0],
92
- [editor, element, schemaTypes.block.name],
209
+ [slateEditor, element, schemaTypes.block.name],
93
210
  )
94
211
 
95
212
  let renderedBlock = children
@@ -107,9 +224,9 @@ export const Element: FunctionComponent<ElementProps> = ({
107
224
  }
108
225
 
109
226
  // Test for inline objects first
110
- if (editor.isInline(element)) {
111
- const path = ReactEditor.findPath(editor, element)
112
- const [block] = Editor.node(editor, path, {depth: 1})
227
+ if (slateEditor.isInline(element)) {
228
+ const path = ReactEditor.findPath(slateEditor, element)
229
+ const [block] = Editor.node(slateEditor, path, {depth: 1})
113
230
  const schemaType = schemaTypes.inlineObjects.find(
114
231
  (_type) => _type.name === element._type,
115
232
  )
@@ -192,7 +309,7 @@ export const Element: FunctionComponent<ElementProps> = ({
192
309
  className += ` pt-list-item pt-list-item-${element.listItem} pt-list-item-level-${level || 1}`
193
310
  }
194
311
 
195
- if (editor.isListBlock(value) && isListItem && element.listItem) {
312
+ if (slateEditor.isListBlock(value) && isListItem && element.listItem) {
196
313
  const listType = schemaTypes.lists.find(
197
314
  (item) => item.value === element.listItem,
198
315
  )
@@ -246,11 +363,10 @@ export const Element: FunctionComponent<ElementProps> = ({
246
363
  {...attributes}
247
364
  className={className}
248
365
  spellCheck={spellCheck}
249
- {...droppable.droppableProps}
250
366
  >
251
- {droppable.isDraggingOverTop ? <DropIndicator /> : null}
367
+ {dragPositionBlock === 'start' ? <DropIndicator /> : null}
252
368
  <div ref={blockRef}>{propsOrDefaultRendered}</div>
253
- {droppable.isDraggingOverBottom ? <DropIndicator /> : null}
369
+ {dragPositionBlock === 'end' ? <DropIndicator /> : null}
254
370
  </div>
255
371
  )
256
372
  }
@@ -274,7 +390,7 @@ export const Element: FunctionComponent<ElementProps> = ({
274
390
  const block = fromSlateValue(
275
391
  [element],
276
392
  schemaTypes.block.name,
277
- KEY_TO_VALUE_ELEMENT.get(editor),
393
+ KEY_TO_VALUE_ELEMENT.get(slateEditor),
278
394
  )[0]
279
395
 
280
396
  let renderedBlockFromProps: JSX.Element | undefined
@@ -309,10 +425,9 @@ export const Element: FunctionComponent<ElementProps> = ({
309
425
  key={element._key}
310
426
  {...attributes}
311
427
  className={className}
312
- {...droppable.droppableProps}
313
428
  {...draggable.draggableProps}
314
429
  >
315
- {droppable.isDraggingOverTop ? <DropIndicator /> : null}
430
+ {dragPositionBlock === 'start' ? <DropIndicator /> : null}
316
431
  {children}
317
432
  <div ref={blockRef} contentEditable={false}>
318
433
  {renderedBlockFromProps ? (
@@ -321,7 +436,7 @@ export const Element: FunctionComponent<ElementProps> = ({
321
436
  <DefaultBlockObject value={value} />
322
437
  )}
323
438
  </div>
324
- {droppable.isDraggingOverBottom ? <DropIndicator /> : null}
439
+ {dragPositionBlock === 'end' ? <DropIndicator /> : null}
325
440
  </div>
326
441
  )
327
442
  }
@@ -1,28 +1,21 @@
1
1
  import {
2
2
  useCallback,
3
+ useContext,
3
4
  useEffect,
4
- useRef,
5
5
  useState,
6
6
  type DragEvent,
7
7
  type RefObject,
8
8
  } from 'react'
9
- import {Path, Transforms, type Element as SlateElement} from 'slate'
9
+ import type {Element as SlateElement} from 'slate'
10
10
  import {ReactEditor, useSlateStatic} from 'slate-react'
11
- import {debugWithName} from '../../internal-utils/debug'
12
- import {
13
- IS_DRAGGING,
14
- IS_DRAGGING_BLOCK_ELEMENT,
15
- IS_DRAGGING_BLOCK_TARGET_POSITION,
16
- IS_DRAGGING_ELEMENT_TARGET,
17
- } from '../../internal-utils/weakMaps'
18
-
19
- const debug = debugWithName('useDraggable')
11
+ import {getEventPosition} from '../../internal-utils/event-position'
12
+ import {EditorActorContext} from '../editor-actor-context'
13
+ import {getEditorSnapshot} from '../editor-selector'
20
14
 
21
15
  type Draggable = {
22
16
  draggableProps: {
23
17
  draggable: boolean
24
18
  onDragStart?: (event: DragEvent) => void
25
- onDrag?: (event: DragEvent) => void
26
19
  onDragEnd?: (event: DragEvent) => void
27
20
  }
28
21
  }
@@ -32,8 +25,8 @@ export function useDraggable(props: {
32
25
  readOnly: boolean
33
26
  blockRef: RefObject<HTMLDivElement | null>
34
27
  }): Draggable {
28
+ const editorActor = useContext(EditorActorContext)
35
29
  const editor = useSlateStatic()
36
- const dragGhostRef = useRef<HTMLElement>(undefined)
37
30
  const [blockElement, setBlockElement] = useState<HTMLElement | null>(null)
38
31
 
39
32
  useEffect(
@@ -46,95 +39,26 @@ export function useDraggable(props: {
46
39
  [editor, props.element, props.blockRef],
47
40
  )
48
41
 
49
- // Note: this is called for the dragging block
50
- const handleDragEnd = useCallback(
51
- (event: DragEvent) => {
52
- const targetBlock = IS_DRAGGING_ELEMENT_TARGET.get(editor)
53
- if (targetBlock) {
54
- IS_DRAGGING.set(editor, false)
55
- event.preventDefault()
56
- event.stopPropagation()
57
- IS_DRAGGING_ELEMENT_TARGET.delete(editor)
58
- if (dragGhostRef.current) {
59
- debug('Removing drag ghost')
60
- document.body.removeChild(dragGhostRef.current)
61
- }
62
- const dragPosition = IS_DRAGGING_BLOCK_TARGET_POSITION.get(editor)
63
- IS_DRAGGING_BLOCK_TARGET_POSITION.delete(editor)
64
- let targetPath = ReactEditor.findPath(editor, targetBlock)
65
- const myPath = ReactEditor.findPath(editor, props.element)
66
- const isBefore = Path.isBefore(myPath, targetPath)
67
- if (dragPosition === 'bottom' && !isBefore) {
68
- // If it is already at the bottom, don't do anything.
69
- if (targetPath[0] >= editor.children.length - 1) {
70
- debug('target is already at the bottom, not moving')
71
- return
72
- }
73
- const originalPath = targetPath
74
- targetPath = Path.next(targetPath)
75
- debug(
76
- `Adjusting targetPath from ${JSON.stringify(originalPath)} to ${JSON.stringify(
77
- targetPath,
78
- )}`,
79
- )
80
- }
81
- if (
82
- dragPosition === 'top' &&
83
- isBefore &&
84
- targetPath[0] !== editor.children.length - 1
85
- ) {
86
- const originalPath = targetPath
87
- targetPath = Path.previous(targetPath)
88
- debug(
89
- `Adjusting targetPath from ${JSON.stringify(originalPath)} to ${JSON.stringify(
90
- targetPath,
91
- )}`,
92
- )
93
- }
94
- if (Path.equals(targetPath, myPath)) {
95
- event.preventDefault()
96
- debug('targetPath and myPath is the same, not moving')
97
- return
98
- }
99
- debug(
100
- `Moving element ${props.element._key} from path ${JSON.stringify(myPath)} to ${JSON.stringify(
101
- targetPath,
102
- )} (${dragPosition})`,
103
- )
104
- Transforms.moveNodes(editor, {at: myPath, to: targetPath})
105
- editor.onChange()
106
- return
107
- }
108
- debug('No target element, not doing anything')
109
- },
110
- [editor, props.element],
111
- )
42
+ const handleDragEnd = useCallback(() => {
43
+ editorActor.send({type: 'dragend'})
44
+ }, [editorActor])
112
45
 
113
- // Note: this is called for the dragging block
114
- const handleDrag = useCallback(
46
+ const handleDragStart = useCallback(
115
47
  (event: DragEvent) => {
116
- IS_DRAGGING.set(editor, true)
117
- IS_DRAGGING_BLOCK_ELEMENT.set(editor, props.element)
118
- event.stopPropagation() // Stop propagation so that leafs don't get this and take focus/selection!
119
-
120
- const target = event.target
48
+ const position = getEventPosition({
49
+ snapshot: getEditorSnapshot({
50
+ editorActorSnapshot: editorActor.getSnapshot(),
51
+ slateEditorInstance: editor,
52
+ }),
53
+ slateEditor: editor,
54
+ event: event.nativeEvent,
55
+ })
121
56
 
122
- if (target instanceof HTMLElement) {
123
- target.style.opacity = '1'
57
+ if (!position) {
58
+ console.error('Could not find position for dragstart event')
59
+ return
124
60
  }
125
- },
126
- [editor, props.element],
127
- )
128
61
 
129
- // Note: this is called for the dragging block
130
- const handleDragStart = useCallback(
131
- (event: DragEvent) => {
132
- debug('Drag start')
133
- IS_DRAGGING.set(editor, true)
134
- if (event.dataTransfer) {
135
- event.dataTransfer.setData('application/portable-text', 'something')
136
- event.dataTransfer.effectAllowed = 'move'
137
- }
138
62
  // Clone blockElement so that it will not be visually clipped by scroll-containers etc.
139
63
  // The application that uses the portable-text-editor may indicate the element used as
140
64
  // drag ghost by adding a truthy data attribute 'data-pt-drag-ghost-element' to a HTML element.
@@ -151,7 +75,6 @@ export function useDraggable(props: {
151
75
  dragGhost.setAttribute('data-dragged', '')
152
76
 
153
77
  if (document.body) {
154
- dragGhostRef.current = dragGhost
155
78
  dragGhost.style.position = 'absolute'
156
79
  dragGhost.style.left = '-99999px'
157
80
  dragGhost.style.boxSizing = 'border-box'
@@ -162,11 +85,22 @@ export function useDraggable(props: {
162
85
  dragGhost.style.width = `${rect.width}px`
163
86
  dragGhost.style.height = `${rect.height}px`
164
87
  event.dataTransfer.setDragImage(dragGhost, x, y)
88
+
89
+ editorActor.send({
90
+ type: 'dragstart',
91
+ origin: position,
92
+ ghost: dragGhost,
93
+ })
94
+ return
165
95
  }
96
+
97
+ editorActor.send({
98
+ type: 'dragstart',
99
+ origin: position,
100
+ })
166
101
  }
167
- handleDrag(event)
168
102
  },
169
- [blockElement, editor, handleDrag],
103
+ [blockElement, editor, editorActor],
170
104
  )
171
105
 
172
106
  if (props.readOnly) {
@@ -174,7 +108,6 @@ export function useDraggable(props: {
174
108
  draggableProps: {
175
109
  draggable: false,
176
110
  onDragStart: undefined,
177
- onDrag: undefined,
178
111
  onDragEnd: undefined,
179
112
  },
180
113
  }
@@ -184,7 +117,6 @@ export function useDraggable(props: {
184
117
  draggableProps: {
185
118
  draggable: true,
186
119
  onDragStart: handleDragStart,
187
- onDrag: handleDrag,
188
120
  onDragEnd: handleDragEnd,
189
121
  },
190
122
  }
@@ -14,13 +14,17 @@ import {coreBehaviors} from '../behaviors/behavior.core'
14
14
  import {defaultBehaviors} from '../behaviors/behavior.default'
15
15
  import {
16
16
  isCustomBehaviorEvent,
17
+ isDragBehaviorEvent,
18
+ isMouseBehaviorEvent,
17
19
  type Behavior,
18
20
  type CustomBehaviorEvent,
21
+ type DataBehaviorEvent,
19
22
  type InternalBehaviorAction,
20
23
  type NativeBehaviorEvent,
21
24
  type SyntheticBehaviorEvent,
22
25
  } from '../behaviors/behavior.types'
23
26
  import type {Converter} from '../converters/converter.types'
27
+ import type {EventPosition} from '../internal-utils/event-position'
24
28
  import type {NamespaceEvent} from '../type-utils'
25
29
  import type {
26
30
  EditorSelection,
@@ -181,7 +185,10 @@ export type InternalEditorEvent =
181
185
  }
182
186
  | {
183
187
  type: 'behavior event'
184
- behaviorEvent: SyntheticBehaviorEvent | NativeBehaviorEvent
188
+ behaviorEvent:
189
+ | DataBehaviorEvent
190
+ | SyntheticBehaviorEvent
191
+ | NativeBehaviorEvent
185
192
  editor: PortableTextSlateEditor
186
193
  defaultActionCallback?: () => void
187
194
  nativeEvent?: {preventDefault: () => void}
@@ -199,7 +206,7 @@ export type InternalEditorEvent =
199
206
  | NamespaceEvent<EditorEmittedEvent, 'notify'>
200
207
  | NamespaceEvent<UnsetEvent, 'notify'>
201
208
  | SyntheticBehaviorEvent
202
- | {type: 'dragstart'}
209
+ | {type: 'dragstart'; origin: EventPosition; ghost?: HTMLElement}
203
210
  | {type: 'dragend'}
204
211
  | {type: 'drop'}
205
212
 
@@ -232,6 +239,10 @@ export const editorMachine = setup({
232
239
  maxBlocks: number | undefined
233
240
  selection: EditorSelection
234
241
  value: Array<PortableTextBlock> | undefined
242
+ internalDrag?: {
243
+ ghost?: HTMLElement
244
+ origin: EventPosition
245
+ }
235
246
  },
236
247
  events: {} as InternalEditorEvent,
237
248
  emitted: {} as InternalEditorEmittedEvent,
@@ -313,10 +324,13 @@ export const editorMachine = setup({
313
324
 
314
325
  const defaultAction =
315
326
  event.type === 'custom behavior event' ||
327
+ isDragBehaviorEvent(event.behaviorEvent) ||
316
328
  event.behaviorEvent.type === 'copy' ||
329
+ event.behaviorEvent.type === 'cut' ||
317
330
  event.behaviorEvent.type === 'deserialize' ||
318
331
  event.behaviorEvent.type === 'key.down' ||
319
332
  event.behaviorEvent.type === 'key.up' ||
333
+ isMouseBehaviorEvent(event.behaviorEvent) ||
320
334
  event.behaviorEvent.type === 'paste' ||
321
335
  event.behaviorEvent.type === 'serialize'
322
336
  ? undefined
@@ -332,10 +346,20 @@ export const editorMachine = setup({
332
346
  const eventBehaviors = [
333
347
  ...context.behaviors.values(),
334
348
  ...defaultBehaviors,
335
- ].filter(
336
- (behavior) =>
337
- behavior.on === '*' || behavior.on === event.behaviorEvent.type,
338
- )
349
+ ].filter((behavior) => {
350
+ if (behavior.on === '*') {
351
+ return true
352
+ }
353
+
354
+ if (isDragBehaviorEvent(event.behaviorEvent)) {
355
+ return (
356
+ behavior.on === 'drag.*' ||
357
+ behavior.on === event.behaviorEvent.type
358
+ )
359
+ }
360
+
361
+ return behavior.on === event.behaviorEvent.type
362
+ })
339
363
 
340
364
  if (eventBehaviors.length === 0) {
341
365
  if (defaultActionCallback) {
@@ -360,7 +384,10 @@ export const editorMachine = setup({
360
384
  withApplyingBehaviorActions(event.editor, () => {
361
385
  try {
362
386
  performAction({
363
- context,
387
+ context: {
388
+ keyGenerator: context.keyGenerator,
389
+ schema: context.schema,
390
+ },
364
391
  action: defaultAction,
365
392
  })
366
393
  } catch (error) {
@@ -379,8 +406,10 @@ export const editorMachine = setup({
379
406
  converters: [...context.converters],
380
407
  editor: event.editor,
381
408
  keyGenerator: context.keyGenerator,
409
+ readOnly: self.getSnapshot().matches({'edit mode': 'read only'}),
382
410
  schema: context.schema,
383
411
  hasTag: (tag) => self.getSnapshot().hasTag(tag),
412
+ internalDrag: context.internalDrag,
384
413
  })
385
414
 
386
415
  let behaviorOverwritten = false
@@ -440,7 +469,13 @@ export const editorMachine = setup({
440
469
  }
441
470
 
442
471
  try {
443
- performAction({context, action: internalAction})
472
+ performAction({
473
+ context: {
474
+ keyGenerator: context.keyGenerator,
475
+ schema: context.schema,
476
+ },
477
+ action: internalAction,
478
+ })
444
479
  } catch (error) {
445
480
  console.error(
446
481
  new Error(
@@ -483,7 +518,10 @@ export const editorMachine = setup({
483
518
  withApplyingBehaviorActions(event.editor, () => {
484
519
  try {
485
520
  performAction({
486
- context,
521
+ context: {
522
+ keyGenerator: context.keyGenerator,
523
+ schema: context.schema,
524
+ },
487
525
  action: defaultAction,
488
526
  })
489
527
  } catch (error) {
@@ -656,10 +694,28 @@ export const editorMachine = setup({
656
694
  states: {
657
695
  'idle': {
658
696
  on: {
659
- dragstart: {target: 'dragging internally'},
697
+ dragstart: {
698
+ actions: [
699
+ assign({
700
+ internalDrag: ({event}) => ({
701
+ ghost: event.ghost,
702
+ origin: event.origin,
703
+ }),
704
+ }),
705
+ ],
706
+ target: 'dragging internally',
707
+ },
660
708
  },
661
709
  },
662
710
  'dragging internally': {
711
+ exit: [
712
+ ({context}) => {
713
+ if (context.internalDrag?.ghost) {
714
+ document.body.removeChild(context.internalDrag.ghost)
715
+ }
716
+ },
717
+ assign({internalDrag: undefined}),
718
+ ],
663
719
  tags: ['dragging internally'],
664
720
  on: {
665
721
  dragend: {target: 'idle'},
@@ -73,12 +73,14 @@ export function getEditorSnapshot({
73
73
  slateEditorInstance,
74
74
  }),
75
75
  keyGenerator: editorActorSnapshot.context.keyGenerator,
76
+ readOnly: editorActorSnapshot.matches({'edit mode': 'read only'}),
76
77
  schema: editorActorSnapshot.context.schema,
77
78
  selection: editorActorSnapshot.context.selection,
78
79
  value: getValue({editorActorSnapshot, slateEditorInstance}),
79
80
  },
80
81
  beta: {
81
82
  hasTag: (tag) => editorActorSnapshot.hasTag(tag),
83
+ internalDrag: editorActorSnapshot.context.internalDrag,
82
84
  },
83
85
  }
84
86
  }
@@ -1,5 +1,6 @@
1
1
  import type {PortableTextBlock} from '@sanity/types'
2
2
  import type {Converter} from '../converters/converter.types'
3
+ import type {EventPosition} from '../internal-utils/event-position'
3
4
  import {toPortableTextRange} from '../internal-utils/ranges'
4
5
  import {fromSlateValue} from '../internal-utils/values'
5
6
  import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
@@ -15,6 +16,7 @@ export type EditorContext = {
15
16
  activeDecorators: Array<string>
16
17
  converters: Array<Converter>
17
18
  keyGenerator: () => string
19
+ readOnly: boolean
18
20
  schema: EditorSchema
19
21
  selection: EditorSelection
20
22
  value: Array<PortableTextBlock>
@@ -31,6 +33,11 @@ export type EditorSnapshot = {
31
33
  */
32
34
  beta: {
33
35
  hasTag: HasTag
36
+ internalDrag:
37
+ | {
38
+ origin: EventPosition
39
+ }
40
+ | undefined
34
41
  }
35
42
  }
36
43
 
@@ -38,14 +45,22 @@ export function createEditorSnapshot({
38
45
  converters,
39
46
  editor,
40
47
  keyGenerator,
48
+ readOnly,
41
49
  schema,
42
50
  hasTag,
51
+ internalDrag,
43
52
  }: {
44
53
  converters: Array<Converter>
45
54
  editor: PortableTextSlateEditor
46
55
  keyGenerator: () => string
56
+ readOnly: boolean
47
57
  schema: EditorSchema
48
58
  hasTag: HasTag
59
+ internalDrag:
60
+ | {
61
+ origin: EventPosition
62
+ }
63
+ | undefined
49
64
  }) {
50
65
  const value = fromSlateValue(
51
66
  editor.children,
@@ -61,6 +76,7 @@ export function createEditorSnapshot({
61
76
  }),
62
77
  converters,
63
78
  keyGenerator,
79
+ readOnly,
64
80
  schema,
65
81
  selection,
66
82
  value,
@@ -70,6 +86,7 @@ export function createEditorSnapshot({
70
86
  context,
71
87
  beta: {
72
88
  hasTag,
89
+ internalDrag,
73
90
  },
74
91
  } satisfies EditorSnapshot
75
92
  }