@portabletext/editor 1.39.0 → 1.40.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 (109) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +16 -5
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/editor-provider.cjs +158 -153
  4. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.get-text-before.cjs +0 -1
  6. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +0 -3
  8. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
  9. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs +0 -4
  10. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -1
  11. package/lib/_chunks-cjs/{parse-blocks.cjs → util.selection-point-to-block-offset.cjs} +74 -4
  12. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -0
  13. package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -2
  14. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  15. package/lib/_chunks-cjs/util.split-text-block.cjs +68 -0
  16. package/lib/_chunks-cjs/util.split-text-block.cjs.map +1 -0
  17. package/lib/_chunks-es/behavior.core.js +17 -6
  18. package/lib/_chunks-es/behavior.core.js.map +1 -1
  19. package/lib/_chunks-es/editor-provider.js +155 -150
  20. package/lib/_chunks-es/editor-provider.js.map +1 -1
  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-at-the-start-of-block.js +1 -4
  24. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  25. package/lib/_chunks-es/selector.is-overlapping-selection.js +1 -5
  26. package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -1
  27. package/lib/_chunks-es/{parse-blocks.js → util.selection-point-to-block-offset.js} +76 -5
  28. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -0
  29. package/lib/_chunks-es/util.slice-blocks.js +2 -2
  30. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  31. package/lib/_chunks-es/util.split-text-block.js +70 -0
  32. package/lib/_chunks-es/util.split-text-block.js.map +1 -0
  33. package/lib/behaviors/index.d.cts +1516 -911
  34. package/lib/behaviors/index.d.ts +1516 -911
  35. package/lib/index.cjs +197 -200
  36. package/lib/index.cjs.map +1 -1
  37. package/lib/index.d.cts +1247 -717
  38. package/lib/index.d.ts +1247 -717
  39. package/lib/index.js +204 -207
  40. package/lib/index.js.map +1 -1
  41. package/lib/plugins/index.cjs +11 -12
  42. package/lib/plugins/index.cjs.map +1 -1
  43. package/lib/plugins/index.d.cts +1238 -721
  44. package/lib/plugins/index.d.ts +1238 -721
  45. package/lib/plugins/index.js +3 -4
  46. package/lib/plugins/index.js.map +1 -1
  47. package/lib/selectors/index.d.cts +1237 -710
  48. package/lib/selectors/index.d.ts +1237 -710
  49. package/lib/utils/index.cjs +15 -87
  50. package/lib/utils/index.cjs.map +1 -1
  51. package/lib/utils/index.d.cts +1290 -713
  52. package/lib/utils/index.d.ts +1290 -713
  53. package/lib/utils/index.js +13 -86
  54. package/lib/utils/index.js.map +1 -1
  55. package/package.json +10 -10
  56. package/src/behavior-actions/behavior.action.decorator.add.ts +13 -2
  57. package/src/behaviors/behavior.core.block-objects.ts +32 -2
  58. package/src/behaviors/behavior.default.ts +59 -16
  59. package/src/behaviors/behavior.types.ts +67 -30
  60. package/src/editor/Editable.tsx +122 -68
  61. package/src/editor/PortableTextEditor.tsx +8 -8
  62. package/src/editor/__tests__/self-solving.test.tsx +1 -1
  63. package/src/editor/components/Element.tsx +1 -3
  64. package/src/editor/create-editor.ts +13 -5
  65. package/src/editor/editor-machine.ts +13 -3
  66. package/src/editor/editor-provider.tsx +11 -7
  67. package/src/editor/editor-selector.ts +4 -3
  68. package/src/editor/editor-snapshot.ts +2 -2
  69. package/src/editor/plugins/create-with-event-listeners.ts +18 -3
  70. package/src/editor/plugins/createWithPortableTextMarkModel.ts +1 -2
  71. package/src/internal-utils/block-keys.ts +9 -0
  72. package/src/internal-utils/collapse-selection.ts +36 -0
  73. package/src/internal-utils/compound-client-rect.ts +28 -0
  74. package/src/internal-utils/drag-selection.test.ts +507 -0
  75. package/src/internal-utils/drag-selection.ts +66 -0
  76. package/src/internal-utils/editor-selection.test.ts +40 -0
  77. package/src/internal-utils/editor-selection.ts +60 -0
  78. package/src/internal-utils/event-position.ts +55 -80
  79. package/src/internal-utils/inline-object-selection.ts +115 -0
  80. package/src/internal-utils/selection-block-keys.ts +20 -0
  81. package/src/internal-utils/selection-elements.ts +61 -0
  82. package/src/internal-utils/selection-focus-text.ts +38 -0
  83. package/src/internal-utils/selection-text.test.ts +23 -0
  84. package/src/internal-utils/selection-text.ts +90 -0
  85. package/src/internal-utils/split-string.ts +12 -0
  86. package/src/internal-utils/string-overlap.test.ts +14 -0
  87. package/src/internal-utils/string-overlap.ts +28 -0
  88. package/src/internal-utils/string-utils.ts +7 -0
  89. package/src/internal-utils/terse-pt.test.ts +60 -0
  90. package/src/internal-utils/terse-pt.ts +36 -0
  91. package/src/internal-utils/text-block-key.test.ts +30 -0
  92. package/src/internal-utils/text-block-key.ts +30 -0
  93. package/src/internal-utils/text-marks.test.ts +33 -0
  94. package/src/internal-utils/text-marks.ts +26 -0
  95. package/src/internal-utils/text-selection.test.ts +175 -0
  96. package/src/internal-utils/text-selection.ts +122 -0
  97. package/src/internal-utils/value-annotations.ts +31 -0
  98. package/src/internal-utils/values.ts +16 -5
  99. package/src/utils/index.ts +5 -0
  100. package/src/utils/util.block-offset-to-block-selection-point.ts +28 -0
  101. package/src/utils/util.block-offset-to-selection-point.ts +33 -0
  102. package/src/utils/util.block-offsets-to-selection.ts +3 -3
  103. package/src/utils/util.is-equal-selections.ts +20 -0
  104. package/src/utils/util.is-selection-collapsed.ts +15 -0
  105. package/src/utils/util.reverse-selection.ts +9 -5
  106. package/src/utils/util.selection-point-to-block-offset.ts +31 -0
  107. package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
  108. package/lib/_chunks-es/parse-blocks.js.map +0 -1
  109. package/src/editor/components/use-draggable.ts +0 -123
@@ -19,7 +19,6 @@ import {
19
19
  import {
20
20
  Editor,
21
21
  Path,
22
- Element as SlateElement,
23
22
  Range as SlateRange,
24
23
  Transforms,
25
24
  type BaseRange,
@@ -34,7 +33,9 @@ import {
34
33
  type RenderElementProps,
35
34
  type RenderLeafProps,
36
35
  } from 'slate-react'
36
+ import {getCompoundClientRect} from '../internal-utils/compound-client-rect'
37
37
  import {debugWithName} from '../internal-utils/debug'
38
+ import {getDragSelection} from '../internal-utils/drag-selection'
38
39
  import {getEventPosition} from '../internal-utils/event-position'
39
40
  import {parseBlocks} from '../internal-utils/parse-blocks'
40
41
  import {
@@ -43,7 +44,9 @@ import {
43
44
  toSlateRange,
44
45
  } from '../internal-utils/ranges'
45
46
  import {normalizeSelection} from '../internal-utils/selection'
47
+ import {getSelectionDomNodes} from '../internal-utils/selection-elements'
46
48
  import {fromSlateValue, isEqualToEmptyEditor} from '../internal-utils/values'
49
+ import * as selectors from '../selectors'
47
50
  import type {
48
51
  EditorSelection,
49
52
  OnCopyFn,
@@ -444,14 +447,8 @@ export const PortableTextEditable = forwardRef<
444
447
  event.stopPropagation()
445
448
  event.preventDefault()
446
449
 
447
- const position = getEventPosition({
448
- snapshot: getEditorSnapshot({
449
- editorActorSnapshot: editorActor.getSnapshot(),
450
- slateEditorInstance: slateEditor,
451
- }),
452
- slateEditor,
453
- event: event.nativeEvent,
454
- })
450
+ const selection = editorActor.getSnapshot().context.selection
451
+ const position = selection ? {selection} : undefined
455
452
 
456
453
  if (!position) {
457
454
  console.warn('Could not find position for copy event')
@@ -488,14 +485,8 @@ export const PortableTextEditable = forwardRef<
488
485
  event.stopPropagation()
489
486
  event.preventDefault()
490
487
 
491
- const position = getEventPosition({
492
- snapshot: getEditorSnapshot({
493
- editorActorSnapshot: editorActor.getSnapshot(),
494
- slateEditorInstance: slateEditor,
495
- }),
496
- slateEditor,
497
- event: event.nativeEvent,
498
- })
488
+ const selection = editorActor.getSnapshot().context.selection
489
+ const position = selection ? {selection} : undefined
499
490
 
500
491
  if (!position) {
501
492
  console.warn('Could not find position for cut event')
@@ -544,14 +535,8 @@ export const PortableTextEditable = forwardRef<
544
535
  if (!result || !result.insert) {
545
536
  debug('No result from custom paste handler, pasting normally')
546
537
 
547
- const position = getEventPosition({
548
- snapshot: getEditorSnapshot({
549
- editorActorSnapshot: editorActor.getSnapshot(),
550
- slateEditorInstance: slateEditor,
551
- }),
552
- slateEditor,
553
- event: event.nativeEvent,
554
- })
538
+ const selection = editorActor.getSnapshot().context.selection
539
+ const position = selection ? {selection} : undefined
555
540
 
556
541
  if (!position) {
557
542
  console.warn('Could not find position for paste event')
@@ -610,14 +595,8 @@ export const PortableTextEditable = forwardRef<
610
595
  event.preventDefault()
611
596
  event.stopPropagation()
612
597
 
613
- const position = getEventPosition({
614
- snapshot: getEditorSnapshot({
615
- editorActorSnapshot: editorActor.getSnapshot(),
616
- slateEditorInstance: slateEditor,
617
- }),
618
- slateEditor,
619
- event: event.nativeEvent,
620
- })
598
+ const selection = editorActor.getSnapshot().context.selection
599
+ const position = selection ? {selection} : undefined
621
600
 
622
601
  if (!position) {
623
602
  console.warn('Could not find position for paste event')
@@ -680,10 +659,7 @@ export const PortableTextEditable = forwardRef<
680
659
  }
681
660
 
682
661
  const position = getEventPosition({
683
- snapshot: getEditorSnapshot({
684
- editorActorSnapshot: editorActor.getSnapshot(),
685
- slateEditorInstance: slateEditor,
686
- }),
662
+ schema: editorActor.getSnapshot().context.schema,
687
663
  slateEditor,
688
664
  event: event.nativeEvent,
689
665
  })
@@ -962,10 +938,7 @@ export const PortableTextEditable = forwardRef<
962
938
 
963
939
  if (!event.isDefaultPrevented() && !event.isPropagationStopped()) {
964
940
  const position = getEventPosition({
965
- snapshot: getEditorSnapshot({
966
- editorActorSnapshot: editorActor.getSnapshot(),
967
- slateEditorInstance: slateEditor,
968
- }),
941
+ schema: editorActor.getSnapshot().context.schema,
969
942
  slateEditor,
970
943
  event: event.nativeEvent,
971
944
  })
@@ -975,25 +948,113 @@ export const PortableTextEditable = forwardRef<
975
948
  return
976
949
  }
977
950
 
978
- if (ReactEditor.hasTarget(slateEditor, event.target)) {
979
- const node = ReactEditor.toSlateNode(slateEditor, event.target)
980
- const path = ReactEditor.findPath(slateEditor, node)
981
- const voidMatch =
982
- (SlateElement.isElement(node) &&
983
- Editor.isVoid(slateEditor, node)) ||
984
- Editor.void(slateEditor, {at: path, voids: true})
985
-
986
- // If starting a drag on a void node, make sure it is selected
987
- // so that it shows up in the selection's fragment.
988
- if (voidMatch) {
989
- const range = Editor.range(slateEditor, path)
990
- Transforms.select(slateEditor, range)
951
+ const snapshot = getEditorSnapshot({
952
+ editorActorSnapshot: editorActor.getSnapshot(),
953
+ slateEditorInstance: slateEditor,
954
+ })
955
+ const dragSelection = getDragSelection({
956
+ eventSelection: position.selection,
957
+ snapshot,
958
+ })
959
+
960
+ const selectingEntireBlocks = selectors.isSelectingEntireBlocks({
961
+ ...snapshot,
962
+ context: {
963
+ ...snapshot.context,
964
+ selection: dragSelection,
965
+ },
966
+ })
967
+
968
+ const dragGhost = document.createElement('div')
969
+
970
+ const draggedDomNodes = getSelectionDomNodes({
971
+ snapshot: {
972
+ ...snapshot,
973
+ context: {
974
+ ...snapshot.context,
975
+ selection: dragSelection,
976
+ },
977
+ },
978
+ slateEditor,
979
+ })
980
+
981
+ if (selectingEntireBlocks) {
982
+ // Clone the DOM Nodes so they won't be visually clipped by scroll-containers etc.
983
+ const clonedBlockNodes = draggedDomNodes.blockNodes.map((node) =>
984
+ node.cloneNode(true),
985
+ )
986
+
987
+ for (const block of clonedBlockNodes) {
988
+ if (block instanceof HTMLElement) {
989
+ block.style.position = 'relative'
990
+ }
991
+ dragGhost.appendChild(block)
992
+ }
993
+
994
+ // A custom drag ghost element can be configured using this data attribute
995
+ const customGhost = dragGhost.querySelector(
996
+ '[data-pt-drag-ghost-element]',
997
+ )
998
+ if (customGhost) {
999
+ dragGhost.replaceChildren(customGhost)
991
1000
  }
1001
+
1002
+ // Setting the `data-dragged` attribute so the consumer can style the element while it’s dragged
1003
+ dragGhost.setAttribute('data-dragged', '')
1004
+
1005
+ dragGhost.style.position = 'absolute'
1006
+ dragGhost.style.left = '-99999px'
1007
+ dragGhost.style.boxSizing = 'border-box'
1008
+ document.body.appendChild(dragGhost)
1009
+
1010
+ if (customGhost) {
1011
+ const customGhostRect = customGhost.getBoundingClientRect()
1012
+ const x = event.clientX - customGhostRect.left
1013
+ const y = event.clientY - customGhostRect.top
1014
+ dragGhost.style.width = `${customGhostRect.width}px`
1015
+ dragGhost.style.height = `${customGhostRect.height}px`
1016
+ event.dataTransfer.setDragImage(dragGhost, x, y)
1017
+ } else {
1018
+ const blocksDomRect = getCompoundClientRect(
1019
+ draggedDomNodes.blockNodes,
1020
+ )
1021
+ const x = event.clientX - blocksDomRect.left
1022
+ const y = event.clientY - blocksDomRect.top
1023
+ dragGhost.style.width = `${blocksDomRect.width}px`
1024
+ dragGhost.style.height = `${blocksDomRect.height}px`
1025
+ event.dataTransfer.setDragImage(dragGhost, x, y)
1026
+ }
1027
+ } else {
1028
+ const clonedChildNodes = draggedDomNodes.childNodes.map((node) =>
1029
+ node.cloneNode(true),
1030
+ )
1031
+
1032
+ for (const child of clonedChildNodes) {
1033
+ dragGhost.appendChild(child)
1034
+ }
1035
+
1036
+ dragGhost.style.position = 'absolute'
1037
+ dragGhost.style.left = '-99999px'
1038
+ dragGhost.style.boxSizing = 'border-box'
1039
+ document.body.appendChild(dragGhost)
1040
+
1041
+ const childrenDomRect = getCompoundClientRect(
1042
+ draggedDomNodes.childNodes,
1043
+ )
1044
+ const x = event.clientX - childrenDomRect.left
1045
+ const y = event.clientY - childrenDomRect.top
1046
+ dragGhost.style.width = `${childrenDomRect.width}px`
1047
+ dragGhost.style.height = `${childrenDomRect.height}px`
1048
+
1049
+ event.dataTransfer.setDragImage(dragGhost, x, y)
992
1050
  }
993
1051
 
994
1052
  editorActor.send({
995
1053
  type: 'dragstart',
996
- origin: position,
1054
+ origin: {
1055
+ selection: dragSelection,
1056
+ },
1057
+ ghost: dragGhost,
997
1058
  })
998
1059
 
999
1060
  editorActor.send({
@@ -1003,7 +1064,9 @@ export const PortableTextEditable = forwardRef<
1003
1064
  originEvent: {
1004
1065
  dataTransfer: event.dataTransfer,
1005
1066
  },
1006
- position,
1067
+ position: {
1068
+ selection: dragSelection,
1069
+ },
1007
1070
  },
1008
1071
  editor: slateEditor,
1009
1072
  })
@@ -1067,10 +1130,7 @@ export const PortableTextEditable = forwardRef<
1067
1130
 
1068
1131
  if (!event.isDefaultPrevented() && !event.isPropagationStopped()) {
1069
1132
  const position = getEventPosition({
1070
- snapshot: getEditorSnapshot({
1071
- editorActorSnapshot: editorActor.getSnapshot(),
1072
- slateEditorInstance: slateEditor,
1073
- }),
1133
+ schema: editorActor.getSnapshot().context.schema,
1074
1134
  slateEditor,
1075
1135
  event: event.nativeEvent,
1076
1136
  })
@@ -1104,10 +1164,7 @@ export const PortableTextEditable = forwardRef<
1104
1164
 
1105
1165
  if (!event.isDefaultPrevented() && !event.isPropagationStopped()) {
1106
1166
  const position = getEventPosition({
1107
- snapshot: getEditorSnapshot({
1108
- editorActorSnapshot: editorActor.getSnapshot(),
1109
- slateEditorInstance: slateEditor,
1110
- }),
1167
+ schema: editorActor.getSnapshot().context.schema,
1111
1168
  slateEditor,
1112
1169
  event: event.nativeEvent,
1113
1170
  })
@@ -1142,10 +1199,7 @@ export const PortableTextEditable = forwardRef<
1142
1199
 
1143
1200
  if (!event.isDefaultPrevented() && !event.isPropagationStopped()) {
1144
1201
  const position = getEventPosition({
1145
- snapshot: getEditorSnapshot({
1146
- editorActorSnapshot: editorActor.getSnapshot(),
1147
- slateEditorInstance: slateEditor,
1148
- }),
1202
+ schema: editorActor.getSnapshot().context.schema,
1149
1203
  slateEditor,
1150
1204
  event: event.nativeEvent,
1151
1205
  })
@@ -27,7 +27,7 @@ import type {
27
27
  PortableTextMemberSchemaTypes,
28
28
  } from '../types/editor'
29
29
  import {Synchronizer} from './components/Synchronizer'
30
- import {createEditor, type Editor} from './create-editor'
30
+ import {createInternalEditor, type InternalEditor} from './create-editor'
31
31
  import {createEditorSchema} from './create-editor-schema'
32
32
  import {EditorActorContext} from './editor-actor-context'
33
33
  import type {EditorActor} from './editor-machine'
@@ -45,12 +45,12 @@ const debug = debugWithName('component:PortableTextEditor')
45
45
  * @deprecated Use `EditorProvider` instead
46
46
  */
47
47
  export type PortableTextEditorProps<
48
- TEditor extends Editor | undefined = undefined,
48
+ TEditor extends InternalEditor | undefined = undefined,
49
49
  > = PropsWithChildren<
50
- TEditor extends Editor
50
+ TEditor extends InternalEditor
51
51
  ? {
52
52
  /**
53
- * @alpha
53
+ * @internal
54
54
  */
55
55
  editor: TEditor
56
56
  }
@@ -110,7 +110,7 @@ export type PortableTextEditorProps<
110
110
  * @deprecated Use `EditorProvider` instead
111
111
  */
112
112
  export class PortableTextEditor extends Component<
113
- PortableTextEditorProps<Editor | undefined>
113
+ PortableTextEditorProps<InternalEditor | undefined>
114
114
  > {
115
115
  public static displayName = 'PortableTextEditor'
116
116
  /**
@@ -124,7 +124,7 @@ export class PortableTextEditor extends Component<
124
124
  /**
125
125
  * The editor instance
126
126
  */
127
- private editor: Editor
127
+ private editor: InternalEditor
128
128
  /*
129
129
  * The editor API (currently implemented with Slate).
130
130
  */
@@ -134,9 +134,9 @@ export class PortableTextEditor extends Component<
134
134
  super(props)
135
135
 
136
136
  if (props.editor) {
137
- this.editor = props.editor as Editor
137
+ this.editor = props.editor as InternalEditor
138
138
  } else {
139
- this.editor = createEditor({
139
+ this.editor = createInternalEditor({
140
140
  keyGenerator: props.keyGenerator ?? defaultKeyGenerator,
141
141
  schema: props.schemaType,
142
142
  initialValue: props.value,
@@ -4,7 +4,7 @@ import type {PortableTextBlock, PortableTextSpan} from '@sanity/types'
4
4
  import {render, waitFor} from '@testing-library/react'
5
5
  import {createRef, type ComponentProps, type RefObject} from 'react'
6
6
  import {describe, expect, it, vi} from 'vitest'
7
- import {getTextSelection} from '../../../gherkin-tests/gherkin-step-helpers'
7
+ import {getTextSelection} from '../../internal-utils/text-selection'
8
8
  import {PortableTextEditable} from '../Editable'
9
9
  import {PortableTextEditor} from '../PortableTextEditor'
10
10
 
@@ -38,7 +38,6 @@ import type {
38
38
  import {EditorActorContext} from '../editor-actor-context'
39
39
  import {DefaultBlockObject, DefaultInlineObject} from './DefaultObject'
40
40
  import {DropIndicator} from './drop-indicator'
41
- import {useDraggable} from './use-draggable'
42
41
 
43
42
  const debug = debugWithName('components:Element')
44
43
  const debugRenders = false
@@ -90,7 +89,6 @@ export const Element: FunctionComponent<ElementProps> = ({
90
89
  false
91
90
  const [dragPositionBlock, setDragPositionBlock] =
92
91
  useState<EventPositionBlock>()
93
- const draggable = useDraggable({element, readOnly, blockRef})
94
92
 
95
93
  useEffect(() => {
96
94
  const behavior = defineBehavior({
@@ -425,7 +423,7 @@ export const Element: FunctionComponent<ElementProps> = ({
425
423
  key={element._key}
426
424
  {...attributes}
427
425
  className={className}
428
- {...draggable.draggableProps}
426
+ draggable={!readOnly}
429
427
  >
430
428
  {dragPositionBlock === 'start' ? <DropIndicator /> : null}
431
429
  {children}
@@ -78,6 +78,9 @@ export type Editor = {
78
78
  registerBehavior: (config: {behavior: Behavior}) => () => void
79
79
  send: (event: EditorEvent) => void
80
80
  on: ActorRef<Snapshot<unknown>, EventObject, EditorEmittedEvent>['on']
81
+ }
82
+
83
+ export type InternalEditor = Editor & {
81
84
  _internal: {
82
85
  editable: EditableAPI
83
86
  editorActor: EditorActor
@@ -85,21 +88,24 @@ export type Editor = {
85
88
  }
86
89
  }
87
90
 
88
- export function createEditor(config: EditorConfig): Editor {
91
+ export function createInternalEditor(config: EditorConfig): InternalEditor {
89
92
  const editorActor = createActor(editorMachine, {
90
93
  input: editorConfigToMachineInput(config),
91
94
  })
92
95
  editorActor.start()
93
96
 
94
- return createEditorFromActor(editorActor)
97
+ return createInternalEditorFromActor(editorActor)
95
98
  }
96
99
 
97
- export function useCreateEditor(config: EditorConfig): Editor {
100
+ export function useCreateInternalEditor(config: EditorConfig): InternalEditor {
98
101
  const editorActor = useActorRef(editorMachine, {
99
102
  input: editorConfigToMachineInput(config),
100
103
  })
101
104
 
102
- return useMemo(() => createEditorFromActor(editorActor), [editorActor])
105
+ return useMemo(
106
+ () => createInternalEditorFromActor(editorActor),
107
+ [editorActor],
108
+ )
103
109
  }
104
110
 
105
111
  function editorConfigToMachineInput(config: EditorConfig) {
@@ -120,7 +126,9 @@ function editorConfigToMachineInput(config: EditorConfig) {
120
126
  } as const
121
127
  }
122
128
 
123
- function createEditorFromActor(editorActor: EditorActor): Editor {
129
+ function createInternalEditorFromActor(
130
+ editorActor: EditorActor,
131
+ ): InternalEditor {
124
132
  const slateEditor = createSlateEditor({editorActor})
125
133
  const editable = createEditableAPI(slateEditor.instance, editorActor)
126
134
 
@@ -16,6 +16,7 @@ import {
16
16
  isClipboardBehaviorEvent,
17
17
  isCustomBehaviorEvent,
18
18
  isDragBehaviorEvent,
19
+ isInputBehaviorEvent,
19
20
  isKeyboardBehaviorEvent,
20
21
  isMouseBehaviorEvent,
21
22
  type Behavior,
@@ -208,7 +209,11 @@ export type InternalEditorEvent =
208
209
  | NamespaceEvent<EditorEmittedEvent, 'notify'>
209
210
  | NamespaceEvent<UnsetEvent, 'notify'>
210
211
  | SyntheticBehaviorEvent
211
- | {type: 'dragstart'; origin: EventPosition; ghost?: HTMLElement}
212
+ | {
213
+ type: 'dragstart'
214
+ origin: Pick<EventPosition, 'selection'>
215
+ ghost?: HTMLElement
216
+ }
212
217
  | {type: 'dragend'}
213
218
  | {type: 'drop'}
214
219
 
@@ -243,7 +248,7 @@ export const editorMachine = setup({
243
248
  value: Array<PortableTextBlock> | undefined
244
249
  internalDrag?: {
245
250
  ghost?: HTMLElement
246
- origin: EventPosition
251
+ origin: Pick<EventPosition, 'selection'>
247
252
  }
248
253
  },
249
254
  events: {} as InternalEditorEvent,
@@ -328,9 +333,10 @@ export const editorMachine = setup({
328
333
  event.type === 'custom behavior event' ||
329
334
  isClipboardBehaviorEvent(event.behaviorEvent) ||
330
335
  isDragBehaviorEvent(event.behaviorEvent) ||
336
+ isInputBehaviorEvent(event.behaviorEvent) ||
331
337
  isKeyboardBehaviorEvent(event.behaviorEvent) ||
332
- event.behaviorEvent.type === 'deserialize' ||
333
338
  isMouseBehaviorEvent(event.behaviorEvent) ||
339
+ event.behaviorEvent.type === 'deserialize' ||
334
340
  event.behaviorEvent.type === 'serialize'
335
341
  ? undefined
336
342
  : ({
@@ -364,6 +370,10 @@ export const editorMachine = setup({
364
370
  )
365
371
  }
366
372
 
373
+ if (isInputBehaviorEvent(event.behaviorEvent)) {
374
+ return behavior.on === 'input.*'
375
+ }
376
+
367
377
  if (isKeyboardBehaviorEvent(event.behaviorEvent)) {
368
378
  return (
369
379
  behavior.on === 'keyboard.*' ||
@@ -1,7 +1,11 @@
1
1
  import React, {useMemo} from 'react'
2
2
  import {Slate} from 'slate-react'
3
3
  import {Synchronizer} from './components/Synchronizer'
4
- import {useCreateEditor, type Editor, type EditorConfig} from './create-editor'
4
+ import {
5
+ useCreateInternalEditor,
6
+ type Editor,
7
+ type EditorConfig,
8
+ } from './create-editor'
5
9
  import {EditorActorContext} from './editor-actor-context'
6
10
  import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
7
11
  import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
@@ -40,19 +44,19 @@ export type EditorProviderProps = {
40
44
  * @group Components
41
45
  */
42
46
  export function EditorProvider(props: EditorProviderProps) {
43
- const editor = useCreateEditor(props.initialConfig)
44
- const editorActor = editor._internal.editorActor
45
- const slateEditor = editor._internal.slateEditor
47
+ const internalEditor = useCreateInternalEditor(props.initialConfig)
48
+ const editorActor = internalEditor._internal.editorActor
49
+ const slateEditor = internalEditor._internal.slateEditor
46
50
  const portableTextEditor = useMemo(
47
51
  () =>
48
52
  new PortableTextEditor({
49
- editor,
53
+ editor: internalEditor,
50
54
  } as unknown as PortableTextEditorProps),
51
- [editor],
55
+ [internalEditor],
52
56
  )
53
57
 
54
58
  return (
55
- <EditorContext.Provider value={editor}>
59
+ <EditorContext.Provider value={internalEditor}>
56
60
  <RouteEventsToChanges
57
61
  editorActor={editorActor}
58
62
  onChange={(change) => {
@@ -1,6 +1,6 @@
1
1
  import {useSelector} from '@xstate/react'
2
2
  import type {PortableTextSlateEditor} from '../types/editor'
3
- import type {Editor} from './create-editor'
3
+ import type {Editor, InternalEditor} from './create-editor'
4
4
  import type {EditorActor} from './editor-machine'
5
5
  import type {EditorSnapshot} from './editor-snapshot'
6
6
  import {getActiveDecorators} from './get-active-decorators'
@@ -45,11 +45,12 @@ export function useEditorSelector<TSelected>(
45
45
  compare: (a: TSelected, b: TSelected) => boolean = defaultCompare,
46
46
  ) {
47
47
  return useSelector(
48
- editor._internal.editorActor,
48
+ (editor as InternalEditor)._internal.editorActor,
49
49
  (editorActorSnapshot) => {
50
50
  const snapshot = getEditorSnapshot({
51
51
  editorActorSnapshot,
52
- slateEditorInstance: editor._internal.slateEditor.instance,
52
+ slateEditorInstance: (editor as InternalEditor)._internal.slateEditor
53
+ .instance,
53
54
  })
54
55
 
55
56
  return selector(snapshot)
@@ -35,7 +35,7 @@ export type EditorSnapshot = {
35
35
  hasTag: HasTag
36
36
  internalDrag:
37
37
  | {
38
- origin: EventPosition
38
+ origin: Pick<EventPosition, 'selection'>
39
39
  }
40
40
  | undefined
41
41
  }
@@ -58,7 +58,7 @@ export function createEditorSnapshot({
58
58
  hasTag: HasTag
59
59
  internalDrag:
60
60
  | {
61
- origin: EventPosition
61
+ origin: Pick<EventPosition, 'selection'>
62
62
  }
63
63
  | undefined
64
64
  }) {
@@ -114,9 +114,24 @@ export function createWithEventListeners(
114
114
  return
115
115
  }
116
116
 
117
- editor.insertData = () => {
118
- console.warn('Unexpected call to .insertData(...)')
119
- return
117
+ editor.insertData = (dataTransfer) => {
118
+ if (isApplyingBehaviorActions(editor)) {
119
+ throw new Error('Unexpected call to .insertData(...)')
120
+ }
121
+
122
+ editorActor.send({
123
+ type: 'behavior event',
124
+ behaviorEvent: {
125
+ type: 'deserialize',
126
+ originEvent: {
127
+ type: 'input.*',
128
+ originEvent: {
129
+ dataTransfer,
130
+ },
131
+ },
132
+ },
133
+ editor,
134
+ })
120
135
  }
121
136
 
122
137
  editor.insertSoftBreak = () => {
@@ -800,9 +800,8 @@ export const toggleDecoratorActionImplementation: BehaviorActionImplementation<
800
800
  decoratorAddActionImplementation({
801
801
  context,
802
802
  action: {
803
+ ...action,
803
804
  type: 'decorator.add',
804
- editor: action.editor,
805
- decorator: action.decorator,
806
805
  },
807
806
  })
808
807
  }
@@ -0,0 +1,9 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+
3
+ export function getBlockKeys(value: Array<PortableTextBlock> | undefined) {
4
+ if (!value) {
5
+ return []
6
+ }
7
+
8
+ return value.map((block) => block._key)
9
+ }
@@ -0,0 +1,36 @@
1
+ import type {EditorSelection} from '../types/editor'
2
+
3
+ export function collapseSelection(
4
+ selection: EditorSelection,
5
+ direction: 'start' | 'end',
6
+ ): EditorSelection {
7
+ if (!selection) {
8
+ return selection
9
+ }
10
+
11
+ if (direction === 'start') {
12
+ return selection.backward
13
+ ? {
14
+ anchor: selection.focus,
15
+ focus: selection.focus,
16
+ backward: false,
17
+ }
18
+ : {
19
+ anchor: selection.anchor,
20
+ focus: selection.anchor,
21
+ backward: false,
22
+ }
23
+ }
24
+
25
+ return selection.backward
26
+ ? {
27
+ anchor: selection.anchor,
28
+ focus: selection.anchor,
29
+ backward: false,
30
+ }
31
+ : {
32
+ anchor: selection.focus,
33
+ focus: selection.focus,
34
+ backward: false,
35
+ }
36
+ }
@@ -0,0 +1,28 @@
1
+ export function getCompoundClientRect(nodes: Array<Node>): DOMRect {
2
+ if (nodes.length === 0) {
3
+ return new DOMRect(0, 0, 0, 0)
4
+ }
5
+
6
+ const elements = nodes.filter((node) => node instanceof Element)
7
+
8
+ const firstRect = elements.at(0)?.getBoundingClientRect()
9
+
10
+ if (!firstRect) {
11
+ return new DOMRect(0, 0, 0, 0)
12
+ }
13
+
14
+ let left = firstRect.left
15
+ let top = firstRect.top
16
+ let right = firstRect.right
17
+ let bottom = firstRect.bottom
18
+
19
+ for (let i = 1; i < elements.length; i++) {
20
+ const rect = elements[i].getBoundingClientRect()
21
+ left = Math.min(left, rect.left)
22
+ top = Math.min(top, rect.top)
23
+ right = Math.max(right, rect.right)
24
+ bottom = Math.max(bottom, rect.bottom)
25
+ }
26
+
27
+ return new DOMRect(left, top, right - left, bottom - top)
28
+ }