@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.
- package/lib/_chunks-cjs/behavior.core.cjs +16 -5
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/editor-provider.cjs +158 -153
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs +0 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +0 -3
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs +0 -4
- package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -1
- package/lib/_chunks-cjs/{parse-blocks.cjs → util.selection-point-to-block-offset.cjs} +74 -4
- package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -2
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-cjs/util.split-text-block.cjs +68 -0
- package/lib/_chunks-cjs/util.split-text-block.cjs.map +1 -0
- package/lib/_chunks-es/behavior.core.js +17 -6
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/editor-provider.js +155 -150
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/_chunks-es/selector.get-text-before.js +1 -2
- package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js +1 -4
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
- package/lib/_chunks-es/selector.is-overlapping-selection.js +1 -5
- package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -1
- package/lib/_chunks-es/{parse-blocks.js → util.selection-point-to-block-offset.js} +76 -5
- package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -0
- package/lib/_chunks-es/util.slice-blocks.js +2 -2
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/_chunks-es/util.split-text-block.js +70 -0
- package/lib/_chunks-es/util.split-text-block.js.map +1 -0
- package/lib/behaviors/index.d.cts +1516 -911
- package/lib/behaviors/index.d.ts +1516 -911
- package/lib/index.cjs +197 -200
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +1247 -717
- package/lib/index.d.ts +1247 -717
- package/lib/index.js +204 -207
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.cjs +11 -12
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.cts +1238 -721
- package/lib/plugins/index.d.ts +1238 -721
- package/lib/plugins/index.js +3 -4
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.d.cts +1237 -710
- package/lib/selectors/index.d.ts +1237 -710
- package/lib/utils/index.cjs +15 -87
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +1290 -713
- package/lib/utils/index.d.ts +1290 -713
- package/lib/utils/index.js +13 -86
- package/lib/utils/index.js.map +1 -1
- package/package.json +10 -10
- package/src/behavior-actions/behavior.action.decorator.add.ts +13 -2
- package/src/behaviors/behavior.core.block-objects.ts +32 -2
- package/src/behaviors/behavior.default.ts +59 -16
- package/src/behaviors/behavior.types.ts +67 -30
- package/src/editor/Editable.tsx +122 -68
- package/src/editor/PortableTextEditor.tsx +8 -8
- package/src/editor/__tests__/self-solving.test.tsx +1 -1
- package/src/editor/components/Element.tsx +1 -3
- package/src/editor/create-editor.ts +13 -5
- package/src/editor/editor-machine.ts +13 -3
- package/src/editor/editor-provider.tsx +11 -7
- package/src/editor/editor-selector.ts +4 -3
- package/src/editor/editor-snapshot.ts +2 -2
- package/src/editor/plugins/create-with-event-listeners.ts +18 -3
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +1 -2
- package/src/internal-utils/block-keys.ts +9 -0
- package/src/internal-utils/collapse-selection.ts +36 -0
- package/src/internal-utils/compound-client-rect.ts +28 -0
- package/src/internal-utils/drag-selection.test.ts +507 -0
- package/src/internal-utils/drag-selection.ts +66 -0
- package/src/internal-utils/editor-selection.test.ts +40 -0
- package/src/internal-utils/editor-selection.ts +60 -0
- package/src/internal-utils/event-position.ts +55 -80
- package/src/internal-utils/inline-object-selection.ts +115 -0
- package/src/internal-utils/selection-block-keys.ts +20 -0
- package/src/internal-utils/selection-elements.ts +61 -0
- package/src/internal-utils/selection-focus-text.ts +38 -0
- package/src/internal-utils/selection-text.test.ts +23 -0
- package/src/internal-utils/selection-text.ts +90 -0
- package/src/internal-utils/split-string.ts +12 -0
- package/src/internal-utils/string-overlap.test.ts +14 -0
- package/src/internal-utils/string-overlap.ts +28 -0
- package/src/internal-utils/string-utils.ts +7 -0
- package/src/internal-utils/terse-pt.test.ts +60 -0
- package/src/internal-utils/terse-pt.ts +36 -0
- package/src/internal-utils/text-block-key.test.ts +30 -0
- package/src/internal-utils/text-block-key.ts +30 -0
- package/src/internal-utils/text-marks.test.ts +33 -0
- package/src/internal-utils/text-marks.ts +26 -0
- package/src/internal-utils/text-selection.test.ts +175 -0
- package/src/internal-utils/text-selection.ts +122 -0
- package/src/internal-utils/value-annotations.ts +31 -0
- package/src/internal-utils/values.ts +16 -5
- package/src/utils/index.ts +5 -0
- package/src/utils/util.block-offset-to-block-selection-point.ts +28 -0
- package/src/utils/util.block-offset-to-selection-point.ts +33 -0
- package/src/utils/util.block-offsets-to-selection.ts +3 -3
- package/src/utils/util.is-equal-selections.ts +20 -0
- package/src/utils/util.is-selection-collapsed.ts +15 -0
- package/src/utils/util.reverse-selection.ts +9 -5
- package/src/utils/util.selection-point-to-block-offset.ts +31 -0
- package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
- package/lib/_chunks-es/parse-blocks.js.map +0 -1
- package/src/editor/components/use-draggable.ts +0 -123
package/src/editor/Editable.tsx
CHANGED
|
@@ -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
|
|
448
|
-
|
|
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
|
|
492
|
-
|
|
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
|
|
548
|
-
|
|
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
|
|
614
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
48
|
+
TEditor extends InternalEditor | undefined = undefined,
|
|
49
49
|
> = PropsWithChildren<
|
|
50
|
-
TEditor extends
|
|
50
|
+
TEditor extends InternalEditor
|
|
51
51
|
? {
|
|
52
52
|
/**
|
|
53
|
-
* @
|
|
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<
|
|
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:
|
|
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
|
|
137
|
+
this.editor = props.editor as InternalEditor
|
|
138
138
|
} else {
|
|
139
|
-
this.editor =
|
|
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 '
|
|
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
|
-
{
|
|
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
|
|
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
|
|
97
|
+
return createInternalEditorFromActor(editorActor)
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
export function
|
|
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(
|
|
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
|
|
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
|
-
| {
|
|
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 {
|
|
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
|
|
44
|
-
const editorActor =
|
|
45
|
-
const 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
|
-
[
|
|
55
|
+
[internalEditor],
|
|
52
56
|
)
|
|
53
57
|
|
|
54
58
|
return (
|
|
55
|
-
<EditorContext.Provider value={
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
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,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
|
+
}
|