@portabletext/editor 1.50.8 → 1.52.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 (104) hide show
  1. package/lib/_chunks-cjs/{util.slice-blocks.cjs → selection-point.cjs} +26 -18
  2. package/lib/_chunks-cjs/selection-point.cjs.map +1 -0
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs +13 -10
  4. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +46 -46
  6. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs +21 -17
  8. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs.map +1 -1
  9. package/lib/_chunks-cjs/util.child-selection-point-to-block-offset.cjs +10 -10
  10. package/lib/_chunks-cjs/util.child-selection-point-to-block-offset.cjs.map +1 -1
  11. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs +5 -5
  12. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +1 -1
  13. package/lib/_chunks-cjs/util.merge-text-blocks.cjs +3 -3
  14. package/lib/_chunks-cjs/util.merge-text-blocks.cjs.map +1 -1
  15. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs +7 -14
  16. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -1
  17. package/lib/_chunks-es/{util.slice-blocks.js → selection-point.js} +26 -18
  18. package/lib/_chunks-es/selection-point.js.map +1 -0
  19. package/lib/_chunks-es/selector.get-text-before.js +13 -10
  20. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  21. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +21 -21
  22. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
  23. package/lib/_chunks-es/selector.is-selection-expanded.js +14 -10
  24. package/lib/_chunks-es/selector.is-selection-expanded.js.map +1 -1
  25. package/lib/_chunks-es/util.child-selection-point-to-block-offset.js +2 -2
  26. package/lib/_chunks-es/util.child-selection-point-to-block-offset.js.map +1 -1
  27. package/lib/_chunks-es/util.is-equal-selection-points.js +1 -1
  28. package/lib/_chunks-es/util.merge-text-blocks.js +1 -1
  29. package/lib/_chunks-es/util.selection-point-to-block-offset.js +4 -11
  30. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -1
  31. package/lib/behaviors/index.cjs.map +1 -1
  32. package/lib/behaviors/index.d.cts +25 -2010
  33. package/lib/behaviors/index.d.ts +25 -2010
  34. package/lib/behaviors/index.js.map +1 -1
  35. package/lib/index.cjs +515 -393
  36. package/lib/index.cjs.map +1 -1
  37. package/lib/index.d.cts +361 -34
  38. package/lib/index.d.ts +361 -34
  39. package/lib/index.js +471 -349
  40. package/lib/index.js.map +1 -1
  41. package/lib/plugins/index.cjs +11 -11
  42. package/lib/plugins/index.cjs.map +1 -1
  43. package/lib/plugins/index.d.cts +32 -1986
  44. package/lib/plugins/index.d.ts +32 -1986
  45. package/lib/plugins/index.js +1 -1
  46. package/lib/selectors/index.cjs +11 -7
  47. package/lib/selectors/index.cjs.map +1 -1
  48. package/lib/selectors/index.d.cts +2 -2648
  49. package/lib/selectors/index.d.ts +2 -2648
  50. package/lib/selectors/index.js +7 -3
  51. package/lib/selectors/index.js.map +1 -1
  52. package/lib/utils/index.cjs +25 -14
  53. package/lib/utils/index.cjs.map +1 -1
  54. package/lib/utils/index.d.cts +0 -2647
  55. package/lib/utils/index.d.ts +0 -2647
  56. package/lib/utils/index.js +14 -3
  57. package/lib/utils/index.js.map +1 -1
  58. package/package.json +14 -14
  59. package/src/behaviors/behavior.abstract.delete.ts +0 -2
  60. package/src/behaviors/behavior.abstract.insert.ts +8 -8
  61. package/src/behaviors/behavior.abstract.ts +0 -113
  62. package/src/behaviors/behavior.core.block-element.ts +9 -3
  63. package/src/behaviors/behavior.core.dnd.ts +328 -1
  64. package/src/behaviors/behavior.perform-event.ts +10 -0
  65. package/src/behaviors/behavior.types.action.ts +2 -0
  66. package/src/behaviors/behavior.types.event.ts +5 -0
  67. package/src/behaviors/behavior.types.guard.ts +2 -0
  68. package/src/converters/converter.portable-text.ts +2 -7
  69. package/src/converters/converter.text-html.ts +1 -3
  70. package/src/converters/converter.text-plain.ts +3 -5
  71. package/src/editor/Editable.tsx +6 -133
  72. package/src/editor/editor-machine.ts +15 -10
  73. package/src/editor/editor-selector.ts +0 -2
  74. package/src/editor/editor-snapshot.ts +0 -18
  75. package/src/internal-utils/create-test-snapshot.ts +0 -2
  76. package/src/internal-utils/event-position.ts +42 -30
  77. package/src/internal-utils/selection-block-keys.ts +7 -7
  78. package/src/internal-utils/selection-elements.ts +108 -0
  79. package/src/internal-utils/selection-focus-text.ts +13 -9
  80. package/src/internal-utils/selection-text.ts +9 -78
  81. package/src/internal-utils/terse-pt.test.ts +108 -26
  82. package/src/internal-utils/terse-pt.ts +132 -14
  83. package/src/operations/behavior.operation.decorator.add.ts +0 -2
  84. package/src/operations/behavior.operation.delete.ts +18 -13
  85. package/src/operations/behavior.operation.insert.block.ts +5 -1
  86. package/src/selection/selection-point.ts +22 -0
  87. package/src/selectors/selector.get-anchor-block.ts +6 -6
  88. package/src/selectors/selector.get-anchor-child.ts +6 -6
  89. package/src/selectors/selector.get-selected-spans.ts +16 -19
  90. package/src/selectors/selector.get-selected-text-blocks.ts +11 -19
  91. package/src/selectors/selector.get-selection-end-block.ts +30 -0
  92. package/src/selectors/selector.get-selection-start-block.ts +30 -0
  93. package/src/selectors/selector.get-text-before.ts +15 -16
  94. package/src/selectors/selector.get-trimmed-selection.ts +15 -21
  95. package/src/selectors/selector.is-point-after-selection.ts +11 -19
  96. package/src/selectors/selector.is-point-before-selection.ts +11 -19
  97. package/src/selectors/selectors.ts +23 -39
  98. package/src/utils/util.block-offset.ts +6 -7
  99. package/src/utils/util.child-selection-point-to-block-offset.ts +6 -7
  100. package/src/utils/util.selection-point-to-block-offset.ts +5 -6
  101. package/src/utils/util.slice-blocks.ts +11 -20
  102. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +0 -1
  103. package/lib/_chunks-es/util.slice-blocks.js.map +0 -1
  104. package/src/internal-utils/inline-object-selection.ts +0 -115
@@ -23,18 +23,14 @@ import {
23
23
  type RenderElementProps,
24
24
  type RenderLeafProps,
25
25
  } from 'slate-react'
26
- import {getCompoundClientRect} from '../internal-utils/compound-client-rect'
27
26
  import {debugWithName} from '../internal-utils/debug'
28
- import {getDragSelection} from '../internal-utils/drag-selection'
29
27
  import {getEventPosition} from '../internal-utils/event-position'
30
28
  import {parseBlocks} from '../internal-utils/parse-blocks'
31
29
  import {toSlateRange} from '../internal-utils/ranges'
32
30
  import {normalizeSelection} from '../internal-utils/selection'
33
- import {getSelectionDomNodes} from '../internal-utils/selection-elements'
34
31
  import {slateRangeToSelection} from '../internal-utils/slate-utils'
35
32
  import {fromSlateValue} from '../internal-utils/values'
36
33
  import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
37
- import * as selectors from '../selectors'
38
34
  import type {
39
35
  EditorSelection,
40
36
  OnCopyFn,
@@ -50,13 +46,10 @@ import type {
50
46
  ScrollSelectionIntoViewFunction,
51
47
  } from '../types/editor'
52
48
  import type {HotkeyOptions} from '../types/options'
53
- import {isSelectionCollapsed} from '../utils'
54
- import {getSelectionEndPoint} from '../utils/util.get-selection-end-point'
55
49
  import {RenderElement} from './components/render-element'
56
50
  import {RenderLeaf} from './components/render-leaf'
57
51
  import {RenderText, type RenderTextProps} from './components/render-text'
58
52
  import {EditorActorContext} from './editor-actor-context'
59
- import {getEditorSnapshot} from './editor-selector'
60
53
  import {usePortableTextEditor} from './hooks/usePortableTextEditor'
61
54
  import {createWithHotkeys} from './plugins/createWithHotKeys'
62
55
  import {PortableTextEditor} from './PortableTextEditor'
@@ -811,131 +804,9 @@ export const PortableTextEditable = forwardRef<
811
804
  return
812
805
  }
813
806
 
814
- const snapshot = getEditorSnapshot({
815
- editorActorSnapshot: editorActor.getSnapshot(),
816
- slateEditorInstance: slateEditor,
817
- })
818
- const dragSelection = getDragSelection({
819
- eventSelection: position.selection,
820
- snapshot,
821
- })
822
-
823
- const selectingEntireBlocks = selectors.isSelectingEntireBlocks({
824
- ...snapshot,
825
- context: {
826
- ...snapshot.context,
827
- selection: dragSelection,
828
- },
829
- })
830
-
831
- const dragGhost = document.createElement('div')
832
-
833
- const draggedDomNodes = getSelectionDomNodes({
834
- snapshot: {
835
- ...snapshot,
836
- context: {
837
- ...snapshot.context,
838
- selection: dragSelection,
839
- },
840
- },
841
- slateEditor,
842
- })
843
-
844
- if (selectingEntireBlocks) {
845
- // Clone the DOM Nodes so they won't be visually clipped by scroll-containers etc.
846
- const clonedBlockNodes = draggedDomNodes.blockNodes.map((node) =>
847
- node.cloneNode(true),
848
- )
849
-
850
- for (const block of clonedBlockNodes) {
851
- if (block instanceof HTMLElement) {
852
- block.style.position = 'relative'
853
- }
854
- dragGhost.appendChild(block)
855
- }
856
-
857
- // A custom drag ghost element can be configured using this data attribute
858
- const customGhost = dragGhost.querySelector(
859
- '[data-pt-drag-ghost-element]',
860
- )
861
- if (customGhost) {
862
- dragGhost.replaceChildren(customGhost)
863
- }
864
-
865
- // Setting the `data-dragged` attribute so the consumer can style the element while it’s dragged
866
- dragGhost.setAttribute('data-dragged', '')
867
-
868
- dragGhost.style.position = 'absolute'
869
- dragGhost.style.left = '-99999px'
870
- dragGhost.style.boxSizing = 'border-box'
871
- document.body.appendChild(dragGhost)
872
-
873
- if (customGhost) {
874
- const customGhostRect = customGhost.getBoundingClientRect()
875
- const x = event.clientX - customGhostRect.left
876
- const y = event.clientY - customGhostRect.top
877
- dragGhost.style.width = `${customGhostRect.width}px`
878
- dragGhost.style.height = `${customGhostRect.height}px`
879
- event.dataTransfer.setDragImage(dragGhost, x, y)
880
- } else {
881
- const blocksDomRect = getCompoundClientRect(
882
- draggedDomNodes.blockNodes,
883
- )
884
- const x = event.clientX - blocksDomRect.left
885
- const y = event.clientY - blocksDomRect.top
886
- dragGhost.style.width = `${blocksDomRect.width}px`
887
- dragGhost.style.height = `${blocksDomRect.height}px`
888
- event.dataTransfer.setDragImage(dragGhost, x, y)
889
- }
890
- } else {
891
- const clonedChildNodes = draggedDomNodes.childNodes.map((node) =>
892
- node.cloneNode(true),
893
- )
894
-
895
- for (const child of clonedChildNodes) {
896
- dragGhost.appendChild(child)
897
- }
898
-
899
- dragGhost.style.position = 'absolute'
900
- dragGhost.style.left = '-99999px'
901
- dragGhost.style.boxSizing = 'border-box'
902
- document.body.appendChild(dragGhost)
903
-
904
- const childrenDomRect = getCompoundClientRect(
905
- draggedDomNodes.childNodes,
906
- )
907
- const x = event.clientX - childrenDomRect.left
908
- const y = event.clientY - childrenDomRect.top
909
- dragGhost.style.width = `${childrenDomRect.width}px`
910
- dragGhost.style.height = `${childrenDomRect.height}px`
911
-
912
- event.dataTransfer.setDragImage(dragGhost, x, y)
913
- }
914
-
915
- // Select drag selection
916
- // If the selection is expanded then we just select the end of the
917
- // selection
918
- editorActor.send({
919
- type: 'behavior event',
920
- behaviorEvent: {
921
- type: 'select',
922
- at: isSelectionCollapsed(dragSelection)
923
- ? dragSelection
924
- : {
925
- anchor: getSelectionEndPoint(dragSelection),
926
- focus: getSelectionEndPoint(dragSelection),
927
- backward: false,
928
- },
929
- },
930
- editor: slateEditor,
931
- })
932
-
933
807
  editorActor.send({
934
808
  type: 'dragstart',
935
- origin: {
936
- selection: dragSelection,
937
- },
938
- ghost: dragGhost,
809
+ origin: position,
939
810
  })
940
811
 
941
812
  editorActor.send({
@@ -943,11 +814,11 @@ export const PortableTextEditable = forwardRef<
943
814
  behaviorEvent: {
944
815
  type: 'drag.dragstart',
945
816
  originEvent: {
817
+ clientX: event.clientX,
818
+ clientY: event.clientY,
946
819
  dataTransfer: event.dataTransfer,
947
820
  },
948
- position: {
949
- selection: dragSelection,
950
- },
821
+ position,
951
822
  },
952
823
  editor: slateEditor,
953
824
  })
@@ -1079,6 +950,7 @@ export const PortableTextEditable = forwardRef<
1079
950
  originEvent: {
1080
951
  dataTransfer: event.dataTransfer,
1081
952
  },
953
+ dragOrigin: editorActor.getSnapshot().context.internalDrag?.origin,
1082
954
  position,
1083
955
  },
1084
956
  editor: slateEditor,
@@ -1117,6 +989,7 @@ export const PortableTextEditable = forwardRef<
1117
989
  originEvent: {
1118
990
  dataTransfer: event.dataTransfer,
1119
991
  },
992
+ dragOrigin: editorActor.getSnapshot().context.internalDrag?.origin,
1120
993
  position,
1121
994
  },
1122
995
  editor: slateEditor,
@@ -68,7 +68,6 @@ type InternalPatchEvent = NamespaceEvent<PatchEvent, 'internal'> & {
68
68
  * @internal
69
69
  */
70
70
  export type EditorActor = ActorRefFrom<typeof editorMachine>
71
- export type HasTag = ReturnType<EditorActor['getSnapshot']>['hasTag']
72
71
 
73
72
  /**
74
73
  * @internal
@@ -115,10 +114,14 @@ export type InternalEditorEvent =
115
114
  }
116
115
  | MutationEvent
117
116
  | InternalPatchEvent
117
+ | {
118
+ type: 'set drag ghost'
119
+ ghost: HTMLElement
120
+ }
118
121
  | {
119
122
  type: 'dragstart'
120
- origin: Pick<EventPosition, 'selection'>
121
123
  ghost?: HTMLElement
124
+ origin: Pick<EventPosition, 'selection'>
122
125
  }
123
126
  | {type: 'dragend'}
124
127
  | {type: 'drop'}
@@ -149,9 +152,9 @@ export const editorMachine = setup({
149
152
  selection: EditorSelection
150
153
  initialValue: Array<PortableTextBlock> | undefined
151
154
  internalDrag?: {
152
- ghost?: HTMLElement
153
155
  origin: Pick<EventPosition, 'selection'>
154
156
  }
157
+ dragGhost?: HTMLElement
155
158
  slateEditor?: PortableTextSlateEditor
156
159
  },
157
160
  events: {} as InternalEditorEvent,
@@ -274,10 +277,9 @@ export const editorMachine = setup({
274
277
  keyGenerator: context.keyGenerator,
275
278
  readOnly: self.getSnapshot().matches({'edit mode': 'read only'}),
276
279
  schema: context.schema,
277
- hasTag: (tag) => self.getSnapshot().hasTag(tag),
278
- internalDrag: context.internalDrag,
279
280
  }),
280
281
  nativeEvent: event.nativeEvent,
282
+ sendBack: (event) => self.send(event),
281
283
  })
282
284
  } catch (error) {
283
285
  console.error(
@@ -324,6 +326,9 @@ export const editorMachine = setup({
324
326
  emit(({event}) => ({...event, type: 'selection'})),
325
327
  ],
326
328
  },
329
+ 'set drag ghost': {
330
+ actions: assign({dragGhost: ({event}) => event.ghost}),
331
+ },
327
332
  },
328
333
  type: 'parallel',
329
334
  states: {
@@ -429,7 +434,6 @@ export const editorMachine = setup({
429
434
  actions: [
430
435
  assign({
431
436
  internalDrag: ({event}) => ({
432
- ghost: event.ghost,
433
437
  origin: event.origin,
434
438
  }),
435
439
  }),
@@ -497,20 +501,21 @@ export const editorMachine = setup({
497
501
  debug('exit: edit mode->editable->dragging internally')
498
502
  },
499
503
  ({context}) => {
500
- if (context.internalDrag?.ghost) {
504
+ if (context.dragGhost) {
501
505
  try {
502
- context.internalDrag.ghost.parentNode?.removeChild(
503
- context.internalDrag.ghost,
506
+ context.dragGhost.parentNode?.removeChild(
507
+ context.dragGhost,
504
508
  )
505
509
  } catch (error) {
506
510
  console.error(
507
511
  new Error(
508
- `Removing the internal drag ghost failed due to: ${error.message}`,
512
+ `Removing the drag ghost failed due to: ${error.message}`,
509
513
  ),
510
514
  )
511
515
  }
512
516
  }
513
517
  },
518
+ assign({dragGhost: undefined}),
514
519
  assign({internalDrag: undefined}),
515
520
  ],
516
521
  tags: ['dragging internally'],
@@ -86,8 +86,6 @@ export function getEditorSnapshot({
86
86
  markState: slateEditorInstance.markState,
87
87
  schema: editorActorSnapshot.context.schema,
88
88
  }),
89
- hasTag: (tag) => editorActorSnapshot.hasTag(tag),
90
- internalDrag: editorActorSnapshot.context.internalDrag,
91
89
  },
92
90
  }
93
91
  }
@@ -1,9 +1,7 @@
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'
4
3
  import {slateRangeToSelection} from '../internal-utils/slate-utils'
5
4
  import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
6
- import type {HasTag} from './editor-machine'
7
5
  import type {EditorSchema} from './editor-schema'
8
6
  import {getActiveAnnotations} from './get-active-annotations'
9
7
  import {getActiveDecorators} from './get-active-decorators'
@@ -32,12 +30,6 @@ export type EditorSnapshot = {
32
30
  beta: {
33
31
  activeAnnotations: Array<string>
34
32
  activeDecorators: Array<string>
35
- hasTag: HasTag
36
- internalDrag:
37
- | {
38
- origin: Pick<EventPosition, 'selection'>
39
- }
40
- | undefined
41
33
  }
42
34
  }
43
35
 
@@ -47,20 +39,12 @@ export function createEditorSnapshot({
47
39
  keyGenerator,
48
40
  readOnly,
49
41
  schema,
50
- hasTag,
51
- internalDrag,
52
42
  }: {
53
43
  converters: Array<Converter>
54
44
  editor: PortableTextSlateEditor
55
45
  keyGenerator: () => string
56
46
  readOnly: boolean
57
47
  schema: EditorSchema
58
- hasTag: HasTag
59
- internalDrag:
60
- | {
61
- origin: Pick<EventPosition, 'selection'>
62
- }
63
- | undefined
64
48
  }) {
65
49
  const selection = editor.selection
66
50
  ? slateRangeToSelection({
@@ -91,8 +75,6 @@ export function createEditorSnapshot({
91
75
  markState: editor.markState,
92
76
  schema,
93
77
  }),
94
- hasTag,
95
- internalDrag,
96
78
  },
97
79
  } satisfies EditorSnapshot
98
80
  }
@@ -19,8 +19,6 @@ export function createTestSnapshot(snapshot: {
19
19
  beta: {
20
20
  activeAnnotations: snapshot.beta?.activeAnnotations ?? [],
21
21
  activeDecorators: snapshot.beta?.activeDecorators ?? [],
22
- hasTag: snapshot.beta?.hasTag ?? (() => false),
23
- internalDrag: undefined,
24
22
  },
25
23
  }
26
24
  }
@@ -2,6 +2,7 @@ import {Editor, type BaseRange, type Node} from 'slate'
2
2
  import {DOMEditor, isDOMNode} from 'slate-dom'
3
3
  import type {EditorSchema, EditorSelection} from '..'
4
4
  import type {EditorActor} from '../editor/editor-machine'
5
+ import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
5
6
  import type {PortableTextSlateEditor} from '../types/editor'
6
7
  import * as utils from '../utils'
7
8
  import {
@@ -34,82 +35,93 @@ export function getEventPosition({
34
35
  return undefined
35
36
  }
36
37
 
37
- const node = getEventNode({slateEditor, event})
38
+ const eventNode = getEventNode({slateEditor, event})
38
39
 
39
- if (!node) {
40
+ if (!eventNode) {
40
41
  return undefined
41
42
  }
42
43
 
43
- const block = getNodeBlock({
44
+ const eventBlock = getNodeBlock({
44
45
  editor: slateEditor,
45
46
  schema: editorActor.getSnapshot().context.schema,
46
- node,
47
+ node: eventNode,
47
48
  })
48
-
49
- const positionBlock = getEventPositionBlock({node, slateEditor, event})
50
- const selection = getEventSelection({
49
+ const eventPositionBlock = getEventPositionBlock({
50
+ node: eventNode,
51
+ slateEditor,
52
+ event,
53
+ })
54
+ const eventSelection = getEventSelection({
51
55
  schema: editorActor.getSnapshot().context.schema,
52
56
  slateEditor,
53
57
  event,
54
58
  })
55
59
 
56
- if (block && positionBlock && !selection && !Editor.isEditor(node)) {
60
+ if (
61
+ eventBlock &&
62
+ eventPositionBlock &&
63
+ !eventSelection &&
64
+ !Editor.isEditor(eventNode)
65
+ ) {
66
+ // If we for some reason can't find the event selection, then we default to
67
+ // selecting the entire block that the event originates from.
57
68
  return {
58
- block: positionBlock,
69
+ block: eventPositionBlock,
59
70
  isEditor: false,
60
71
  selection: {
61
72
  anchor: utils.getBlockStartPoint({
62
73
  context: editorActor.getSnapshot().context,
63
74
  block: {
64
- node: block,
65
- path: [{_key: block._key}],
75
+ node: eventBlock,
76
+ path: [{_key: eventBlock._key}],
66
77
  },
67
78
  }),
68
79
  focus: utils.getBlockEndPoint({
69
80
  context: editorActor.getSnapshot().context,
70
81
  block: {
71
- node: block,
72
- path: [{_key: block._key}],
82
+ node: eventBlock,
83
+ path: [{_key: eventBlock._key}],
73
84
  },
74
85
  }),
75
86
  },
76
87
  }
77
88
  }
78
89
 
79
- if (!positionBlock || !selection) {
90
+ if (!eventPositionBlock || !eventSelection) {
80
91
  return undefined
81
92
  }
82
93
 
83
- const focusBlockPath = selection.focus.path.at(0)
84
- const focusBlockKey = utils.isKeyedSegment(focusBlockPath)
85
- ? focusBlockPath._key
86
- : undefined
94
+ const eventSelectionFocusBlockKey = getBlockKeyFromSelectionPoint(
95
+ eventSelection.focus,
96
+ )
87
97
 
88
- if (!focusBlockKey) {
98
+ if (eventSelectionFocusBlockKey === undefined) {
89
99
  return undefined
90
100
  }
91
101
 
92
102
  if (
93
- utils.isSelectionCollapsed(selection) &&
94
- block &&
95
- focusBlockKey !== block._key
103
+ utils.isSelectionCollapsed(eventSelection) &&
104
+ eventBlock &&
105
+ eventSelectionFocusBlockKey !== eventBlock._key
96
106
  ) {
107
+ // If the event block and event selection somehow don't match, then the
108
+ // event block takes precedence.
97
109
  return {
98
- block: positionBlock,
110
+ block: eventPositionBlock,
99
111
  isEditor: false,
100
112
  selection: {
101
113
  anchor: utils.getBlockStartPoint({
102
114
  context: editorActor.getSnapshot().context,
103
115
  block: {
104
- node: block,
105
- path: [{_key: block._key}],
116
+ node: eventBlock,
117
+ path: [{_key: eventBlock._key}],
106
118
  },
107
119
  }),
108
120
  focus: utils.getBlockEndPoint({
109
121
  context: editorActor.getSnapshot().context,
110
122
  block: {
111
- node: block,
112
- path: [{_key: block._key}],
123
+ node: eventBlock,
124
+ path: [{_key: eventBlock._key}],
113
125
  },
114
126
  }),
115
127
  },
@@ -117,9 +129,9 @@ export function getEventPosition({
117
129
  }
118
130
 
119
131
  return {
120
- block: positionBlock,
121
- isEditor: Editor.isEditor(node),
122
- selection,
132
+ block: eventPositionBlock,
133
+ isEditor: Editor.isEditor(eventNode),
134
+ selection: eventSelection,
123
135
  }
124
136
  }
125
137
 
@@ -1,20 +1,20 @@
1
+ import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
1
2
  import type {EditorSelection} from '../types/editor'
2
- import {isKeyedSegment} from '../utils'
3
3
 
4
4
  export function getSelectionBlockKeys(selection: EditorSelection) {
5
5
  if (!selection) {
6
6
  return undefined
7
7
  }
8
8
 
9
- if (
10
- !isKeyedSegment(selection.anchor.path[0]) ||
11
- !isKeyedSegment(selection.focus.path[0])
12
- ) {
9
+ const anchorBlockKey = getBlockKeyFromSelectionPoint(selection.anchor)
10
+ const focusBlockKey = getBlockKeyFromSelectionPoint(selection.focus)
11
+
12
+ if (anchorBlockKey === undefined || focusBlockKey === undefined) {
13
13
  return undefined
14
14
  }
15
15
 
16
16
  return {
17
- anchor: selection.anchor.path[0]._key,
18
- focus: selection.focus.path[0]._key,
17
+ anchor: anchorBlockKey,
18
+ focus: focusBlockKey,
19
19
  }
20
20
  }
@@ -1,9 +1,96 @@
1
1
  import {Editor} from 'slate'
2
2
  import {DOMEditor} from 'slate-dom'
3
3
  import type {EditorSnapshot} from '..'
4
+ import type {BehaviorEvent} from '../behaviors'
5
+ import type {PickFromUnion} from '../type-utils'
4
6
  import type {PortableTextSlateEditor} from '../types/editor'
5
7
  import {toSlateRange} from './ranges'
6
8
 
9
+ export type EditorDom = {
10
+ getBlockNodes: (snapshot: EditorSnapshot) => Array<Node>
11
+ getChildNodes: (snapshot: EditorSnapshot) => Array<Node>
12
+ /**
13
+ * Let the Editor set the drag ghost. This is to be sure that it will get
14
+ * properly removed again when the drag ends.
15
+ */
16
+ setDragGhost: ({
17
+ event,
18
+ ghost,
19
+ }: {
20
+ event: PickFromUnion<BehaviorEvent, 'type', 'drag.dragstart'>
21
+ ghost: {
22
+ element: HTMLElement
23
+ x: number
24
+ y: number
25
+ }
26
+ }) => void
27
+ }
28
+
29
+ export function createEditorDom(
30
+ sendBack: (event: {type: 'set drag ghost'; ghost: HTMLElement}) => void,
31
+ slateEditor: PortableTextSlateEditor,
32
+ ): EditorDom {
33
+ return {
34
+ getBlockNodes: (snapshot) => getBlockNodes(slateEditor, snapshot),
35
+ getChildNodes: (snapshot) => getChildNodes(slateEditor, snapshot),
36
+ setDragGhost: ({event, ghost}) => setDragGhost({sendBack, event, ghost}),
37
+ }
38
+ }
39
+
40
+ function getBlockNodes(
41
+ slateEditor: PortableTextSlateEditor,
42
+ snapshot: EditorSnapshot,
43
+ ) {
44
+ if (!snapshot.context.selection) {
45
+ return []
46
+ }
47
+
48
+ const range = toSlateRange(snapshot.context.selection, slateEditor)
49
+
50
+ if (!range) {
51
+ return []
52
+ }
53
+
54
+ const blockEntries = Array.from(
55
+ Editor.nodes(slateEditor, {
56
+ at: range,
57
+ mode: 'highest',
58
+ match: (n) => !Editor.isEditor(n),
59
+ }),
60
+ )
61
+
62
+ return blockEntries.map(([blockNode]) =>
63
+ DOMEditor.toDOMNode(slateEditor, blockNode),
64
+ )
65
+ }
66
+
67
+ function getChildNodes(
68
+ slateEditor: PortableTextSlateEditor,
69
+ snapshot: EditorSnapshot,
70
+ ) {
71
+ if (!snapshot.context.selection) {
72
+ return []
73
+ }
74
+
75
+ const range = toSlateRange(snapshot.context.selection, slateEditor)
76
+
77
+ if (!range) {
78
+ return []
79
+ }
80
+
81
+ const childEntries = Array.from(
82
+ Editor.nodes(slateEditor, {
83
+ at: range,
84
+ mode: 'lowest',
85
+ match: (n) => !Editor.isEditor(n),
86
+ }),
87
+ )
88
+
89
+ return childEntries.map(([childNode]) =>
90
+ DOMEditor.toDOMNode(slateEditor, childNode),
91
+ )
92
+ }
93
+
7
94
  export type SelectionDomNodes = {
8
95
  blockNodes: Array<Node>
9
96
  childNodes: Array<Node>
@@ -59,3 +146,24 @@ export function getSelectionDomNodes({
59
146
  ),
60
147
  }
61
148
  }
149
+
150
+ function setDragGhost({
151
+ sendBack,
152
+ event,
153
+ ghost,
154
+ }: {
155
+ sendBack: (event: {type: 'set drag ghost'; ghost: HTMLElement}) => void
156
+ event: PickFromUnion<BehaviorEvent, 'type', 'drag.dragstart'>
157
+ ghost: {
158
+ element: HTMLElement
159
+ x: number
160
+ y: number
161
+ }
162
+ }) {
163
+ event.originEvent.dataTransfer.setDragImage(ghost.element, ghost.x, ghost.y)
164
+
165
+ sendBack({
166
+ type: 'set drag ghost',
167
+ ghost: ghost.element,
168
+ })
169
+ }
@@ -1,7 +1,10 @@
1
1
  import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
2
2
  import type {PortableTextBlock} from '@sanity/types'
3
+ import {
4
+ getBlockKeyFromSelectionPoint,
5
+ getChildKeyFromSelectionPoint,
6
+ } from '../selection/selection-point'
3
7
  import type {EditorSelection} from '../types/editor'
4
- import {isKeyedSegment} from '../utils'
5
8
 
6
9
  export function getSelectionFocusText(
7
10
  value: Array<PortableTextBlock> | undefined,
@@ -11,20 +14,21 @@ export function getSelectionFocusText(
11
14
  return undefined
12
15
  }
13
16
 
17
+ const focusBlockKey = getBlockKeyFromSelectionPoint(selection.focus)
18
+ const focusChildKey = getChildKeyFromSelectionPoint(selection.focus)
19
+
20
+ if (focusBlockKey === undefined || focusChildKey === undefined) {
21
+ return undefined
22
+ }
23
+
14
24
  let text: string | undefined
15
25
 
16
26
  for (const block of value) {
17
27
  if (isPortableTextBlock(block)) {
18
- if (
19
- isKeyedSegment(selection.focus.path[0]) &&
20
- block._key === selection.focus.path[0]._key
21
- ) {
28
+ if (block._key === focusBlockKey) {
22
29
  for (const child of block.children) {
23
30
  if (isPortableTextSpan(child)) {
24
- if (
25
- isKeyedSegment(selection.focus.path[2]) &&
26
- child._key === selection.focus.path[2]._key
27
- ) {
31
+ if (child._key === focusChildKey) {
28
32
  text = child.text
29
33
  break
30
34
  }