@portabletext/editor 1.36.0 → 1.36.2
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/editor-provider.cjs +25 -13
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
- package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/editor-provider.js +25 -13
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
- package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/behaviors/index.d.cts +7 -10
- package/lib/behaviors/index.d.ts +7 -10
- package/lib/index.cjs +140 -104
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +13 -26
- package/lib/index.d.ts +13 -26
- package/lib/index.js +142 -106
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.cts +2 -7
- package/lib/plugins/index.d.ts +2 -7
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +8 -13
- package/lib/selectors/index.d.ts +8 -13
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +3 -6
- package/lib/utils/index.d.ts +3 -6
- package/lib/utils/index.js.map +1 -1
- package/package.json +14 -13
- package/src/behavior-actions/behavior.actions.ts +2 -2
- package/src/behaviors/behavior.decorator-pair.ts +2 -1
- package/src/behaviors/index.ts +0 -8
- package/src/converters/converter.text-html.deserialize.test.ts +1 -1
- package/src/converters/converter.text-html.serialize.test.ts +1 -1
- package/src/converters/converter.text-html.ts +8 -0
- package/src/converters/converter.text-plain.test.ts +1 -1
- package/src/converters/converter.text-plain.ts +8 -0
- package/src/editor/Editable.tsx +1 -1
- package/src/editor/__tests__/PortableTextEditor.test.tsx +16 -20
- package/src/editor/components/Element.tsx +26 -18
- package/src/editor/components/drop-indicator.tsx +14 -0
- package/src/editor/components/{DraggableBlock.tsx → use-draggable.ts} +42 -163
- package/src/editor/components/use-droppable.ts +135 -0
- package/src/editor/create-slate-editor.tsx +0 -3
- package/src/editor/sync-machine.ts +1 -0
- package/src/index.ts +48 -12
- package/src/internal-utils/create-test-snapshot.ts +1 -1
- package/src/plugins/plugin.decorator-shortcut.ts +1 -1
- package/src/plugins/plugin.markdown.tsx +1 -0
- package/src/selectors/index.ts +0 -8
- package/src/selectors/selector.get-active-annotations.test.ts +1 -1
- package/src/selectors/selector.get-caret-word-selection.test.ts +1 -1
- package/src/selectors/selector.get-selected-spans.test.ts +2 -1
- package/src/selectors/selector.get-selection-end-point.ts +1 -1
- package/src/selectors/selector.get-selection-start-point.ts +1 -1
- package/src/selectors/selector.get-selection-text.test.ts +1 -1
- package/src/selectors/selector.get-selection.ts +2 -1
- package/src/selectors/selector.get-value.ts +1 -1
- package/src/selectors/selector.is-active-decorator.test.ts +1 -1
- package/src/types/editor.ts +6 -16
- package/src/types/slate.ts +1 -1
- package/src/utils/index.ts +0 -1
- package/src/utils/util.block-offsets-to-selection.ts +1 -1
- package/src/utils/util.is-span.ts +1 -1
- package/src/utils/util.is-text-block.ts +1 -1
- package/src/utils/util.merge-text-blocks.ts +1 -1
- package/src/utils/util.slice-blocks.ts +1 -1
- package/src/utils/util.split-text-block.ts +4 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
2
2
|
import {describe, expect, test} from 'vitest'
|
|
3
|
+
import type {EditorSelection} from '..'
|
|
3
4
|
import {
|
|
4
5
|
compileSchemaDefinition,
|
|
5
6
|
defineSchema,
|
|
6
7
|
type SchemaDefinition,
|
|
7
8
|
} from '../editor/define-schema'
|
|
8
9
|
import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
|
|
9
|
-
import type {EditorSelection} from '../utils'
|
|
10
10
|
import {converterTextHtml} from './converter.text-html'
|
|
11
11
|
import {coreConverters} from './converters.core'
|
|
12
12
|
|
|
@@ -56,6 +56,14 @@ export const converterTextHtml = defineConverter({
|
|
|
56
56
|
},
|
|
57
57
|
) as Array<PortableTextBlock>
|
|
58
58
|
|
|
59
|
+
if (blocks.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
type: 'deserialization.failure',
|
|
62
|
+
mimeType: 'text/html',
|
|
63
|
+
reason: 'No blocks deserialized',
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
return {
|
|
60
68
|
type: 'deserialization.success',
|
|
61
69
|
data: blocks,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
2
2
|
import {expect, test} from 'vitest'
|
|
3
|
+
import type {EditorSelection} from '..'
|
|
3
4
|
import {
|
|
4
5
|
compileSchemaDefinition,
|
|
5
6
|
defineSchema,
|
|
6
7
|
type SchemaDefinition,
|
|
7
8
|
} from '../editor/define-schema'
|
|
8
9
|
import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
|
|
9
|
-
import type {EditorSelection} from '../utils'
|
|
10
10
|
import {converterTextPlain} from './converter.text-plain'
|
|
11
11
|
import {coreConverters} from './converters.core'
|
|
12
12
|
|
|
@@ -71,6 +71,14 @@ export const converterTextPlain = defineConverter({
|
|
|
71
71
|
},
|
|
72
72
|
) as Array<PortableTextBlock>
|
|
73
73
|
|
|
74
|
+
if (blocks.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
type: 'deserialization.failure',
|
|
77
|
+
mimeType: 'text/plain',
|
|
78
|
+
reason: 'No blocks deserialized',
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
return {
|
|
75
83
|
type: 'deserialization.success',
|
|
76
84
|
data: blocks,
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -114,7 +114,7 @@ export type PortableTextEditableProps = Omit<
|
|
|
114
114
|
* @public
|
|
115
115
|
*
|
|
116
116
|
*
|
|
117
|
-
* The core component that renders the editor. Must be placed within the {@link
|
|
117
|
+
* The core component that renders the editor. Must be placed within the {@link EditorProvider} component.
|
|
118
118
|
*
|
|
119
119
|
* @example
|
|
120
120
|
* ```tsx
|
|
@@ -52,32 +52,28 @@ describe('initialization', () => {
|
|
|
52
52
|
class="pt-block pt-text-block pt-text-block-style-normal"
|
|
53
53
|
data-slate-node="element"
|
|
54
54
|
>
|
|
55
|
-
<div
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
<div>
|
|
56
|
+
<span
|
|
57
|
+
data-slate-node="text"
|
|
58
|
+
>
|
|
59
59
|
<span
|
|
60
|
-
|
|
60
|
+
contenteditable="false"
|
|
61
|
+
style="position: absolute; user-select: none; pointer-events: none; left: 0px; right: 0px;"
|
|
62
|
+
>
|
|
63
|
+
Jot something down here
|
|
64
|
+
</span>
|
|
65
|
+
<span
|
|
66
|
+
data-slate-leaf="true"
|
|
61
67
|
>
|
|
62
68
|
<span
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
>
|
|
66
|
-
Jot something down here
|
|
67
|
-
</span>
|
|
68
|
-
<span
|
|
69
|
-
data-slate-leaf="true"
|
|
69
|
+
data-slate-length="0"
|
|
70
|
+
data-slate-zero-width="n"
|
|
70
71
|
>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
data-slate-zero-width="n"
|
|
74
|
-
>
|
|
75
|
-
|
|
76
|
-
<br />
|
|
77
|
-
</span>
|
|
72
|
+
|
|
73
|
+
<br />
|
|
78
74
|
</span>
|
|
79
75
|
</span>
|
|
80
|
-
</
|
|
76
|
+
</span>
|
|
81
77
|
</div>
|
|
82
78
|
</div>
|
|
83
79
|
</div>
|
|
@@ -30,7 +30,9 @@ import type {
|
|
|
30
30
|
RenderStyleFunction,
|
|
31
31
|
} from '../../types/editor'
|
|
32
32
|
import {DefaultBlockObject, DefaultInlineObject} from './DefaultObject'
|
|
33
|
-
import {
|
|
33
|
+
import {DropIndicator} from './drop-indicator'
|
|
34
|
+
import {useDraggable} from './use-draggable'
|
|
35
|
+
import {useDroppable} from './use-droppable'
|
|
34
36
|
|
|
35
37
|
const debug = debugWithName('components:Element')
|
|
36
38
|
const debugRenders = false
|
|
@@ -77,6 +79,8 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
77
79
|
const focused =
|
|
78
80
|
(selected && editor.selection && Range.isCollapsed(editor.selection)) ||
|
|
79
81
|
false
|
|
82
|
+
const droppable = useDroppable({element, blockRef, readOnly})
|
|
83
|
+
const draggable = useDraggable({element, blockRef, readOnly})
|
|
80
84
|
|
|
81
85
|
const value = useMemo(
|
|
82
86
|
() =>
|
|
@@ -235,20 +239,18 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
235
239
|
const propsOrDefaultRendered = renderBlock
|
|
236
240
|
? renderBlock(renderProps as BlockRenderProps)
|
|
237
241
|
: children
|
|
242
|
+
|
|
238
243
|
return (
|
|
239
244
|
<div
|
|
240
245
|
key={element._key}
|
|
241
246
|
{...attributes}
|
|
242
247
|
className={className}
|
|
243
248
|
spellCheck={spellCheck}
|
|
249
|
+
{...droppable.droppableProps}
|
|
244
250
|
>
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
blockRef={blockRef}
|
|
249
|
-
>
|
|
250
|
-
<div ref={blockRef}>{propsOrDefaultRendered}</div>
|
|
251
|
-
</DraggableBlock>
|
|
251
|
+
{droppable.isDraggingOverTop ? <DropIndicator /> : null}
|
|
252
|
+
<div ref={blockRef}>{propsOrDefaultRendered}</div>
|
|
253
|
+
{droppable.isDraggingOverBottom ? <DropIndicator /> : null}
|
|
252
254
|
</div>
|
|
253
255
|
)
|
|
254
256
|
}
|
|
@@ -303,17 +305,23 @@ export const Element: FunctionComponent<ElementProps> = ({
|
|
|
303
305
|
}
|
|
304
306
|
|
|
305
307
|
return (
|
|
306
|
-
<div
|
|
308
|
+
<div
|
|
309
|
+
key={element._key}
|
|
310
|
+
{...attributes}
|
|
311
|
+
className={className}
|
|
312
|
+
{...droppable.droppableProps}
|
|
313
|
+
{...draggable.draggableProps}
|
|
314
|
+
>
|
|
315
|
+
{droppable.isDraggingOverTop ? <DropIndicator /> : null}
|
|
307
316
|
{children}
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
</DraggableBlock>
|
|
317
|
+
<div ref={blockRef} contentEditable={false}>
|
|
318
|
+
{renderedBlockFromProps ? (
|
|
319
|
+
renderedBlockFromProps
|
|
320
|
+
) : (
|
|
321
|
+
<DefaultBlockObject value={value} />
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
{droppable.isDraggingOverBottom ? <DropIndicator /> : null}
|
|
317
325
|
</div>
|
|
318
326
|
)
|
|
319
327
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useCallback,
|
|
3
3
|
useEffect,
|
|
4
|
-
useMemo,
|
|
5
4
|
useRef,
|
|
6
5
|
useState,
|
|
7
6
|
type DragEvent,
|
|
8
|
-
type
|
|
9
|
-
type ReactNode,
|
|
7
|
+
type RefObject,
|
|
10
8
|
} from 'react'
|
|
11
|
-
import {
|
|
9
|
+
import {Path, Transforms, type Element as SlateElement} from 'slate'
|
|
12
10
|
import {ReactEditor, useSlateStatic} from 'slate-react'
|
|
13
11
|
import {debugWithName} from '../../internal-utils/debug'
|
|
14
12
|
import {
|
|
@@ -18,91 +16,36 @@ import {
|
|
|
18
16
|
IS_DRAGGING_ELEMENT_TARGET,
|
|
19
17
|
} from '../../internal-utils/weakMaps'
|
|
20
18
|
|
|
21
|
-
const debug = debugWithName('
|
|
22
|
-
const debugRenders = false
|
|
19
|
+
const debug = debugWithName('useDraggable')
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
blockRef: MutableRefObject<HTMLDivElement | null>
|
|
21
|
+
type Draggable = {
|
|
22
|
+
draggableProps: {
|
|
23
|
+
draggable: boolean
|
|
24
|
+
onDragStart?: (event: DragEvent) => void
|
|
25
|
+
onDrag?: (event: DragEvent) => void
|
|
26
|
+
onDragEnd?: (event: DragEvent) => void
|
|
27
|
+
}
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
children,
|
|
40
|
-
element,
|
|
41
|
-
readOnly,
|
|
42
|
-
blockRef,
|
|
43
|
-
}: DraggableBlockProps) => {
|
|
30
|
+
export function useDraggable(props: {
|
|
31
|
+
element: SlateElement
|
|
32
|
+
readOnly: boolean
|
|
33
|
+
blockRef: RefObject<HTMLDivElement | null>
|
|
34
|
+
}): Draggable {
|
|
44
35
|
const editor = useSlateStatic()
|
|
45
36
|
const dragGhostRef = useRef<HTMLElement>(undefined)
|
|
46
|
-
const [isDragOver, setIsDragOver] = useState(false)
|
|
47
|
-
const isVoid = useMemo(
|
|
48
|
-
() => Editor.isVoid(editor, element),
|
|
49
|
-
[editor, element],
|
|
50
|
-
)
|
|
51
|
-
const isInline = useMemo(
|
|
52
|
-
() => Editor.isInline(editor, element),
|
|
53
|
-
[editor, element],
|
|
54
|
-
)
|
|
55
|
-
|
|
56
37
|
const [blockElement, setBlockElement] = useState<HTMLElement | null>(null)
|
|
57
38
|
|
|
58
39
|
useEffect(
|
|
59
40
|
() =>
|
|
60
41
|
setBlockElement(
|
|
61
|
-
blockRef
|
|
42
|
+
props.blockRef
|
|
43
|
+
? props.blockRef.current
|
|
44
|
+
: ReactEditor.toDOMNode(editor, props.element),
|
|
62
45
|
),
|
|
63
|
-
[editor, element, blockRef],
|
|
46
|
+
[editor, props.element, props.blockRef],
|
|
64
47
|
)
|
|
65
48
|
|
|
66
|
-
// Note: this is called not for the dragging block, but for the targets when the block is dragged over them
|
|
67
|
-
const handleDragOver = useCallback(
|
|
68
|
-
(event: DragEvent) => {
|
|
69
|
-
const isMyDragOver = IS_DRAGGING_BLOCK_ELEMENT.get(editor)
|
|
70
|
-
// debug('Drag over', blockElement)
|
|
71
|
-
if (!isMyDragOver || !blockElement) {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
event.preventDefault()
|
|
75
|
-
event.dataTransfer.dropEffect = 'move'
|
|
76
|
-
IS_DRAGGING_ELEMENT_TARGET.set(editor, element)
|
|
77
|
-
const elementRect = blockElement.getBoundingClientRect()
|
|
78
|
-
const offset = elementRect.top
|
|
79
|
-
const height = elementRect.height
|
|
80
|
-
const Y = event.pageY
|
|
81
|
-
const loc = Math.abs(offset - Y)
|
|
82
|
-
let position: 'top' | 'bottom' = 'bottom'
|
|
83
|
-
if (element === editor.children[0]) {
|
|
84
|
-
position = 'top'
|
|
85
|
-
} else if (loc < height / 2) {
|
|
86
|
-
position = 'top'
|
|
87
|
-
IS_DRAGGING_BLOCK_TARGET_POSITION.set(editor, position)
|
|
88
|
-
} else {
|
|
89
|
-
position = 'bottom'
|
|
90
|
-
IS_DRAGGING_BLOCK_TARGET_POSITION.set(editor, position)
|
|
91
|
-
}
|
|
92
|
-
if (isMyDragOver === element) {
|
|
93
|
-
event.dataTransfer.dropEffect = 'none'
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
setIsDragOver(true)
|
|
97
|
-
},
|
|
98
|
-
[blockElement, editor, element],
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
// Note: this is called not for the dragging block, but for the targets when the block is dragged over them
|
|
102
|
-
const handleDragLeave = useCallback(() => {
|
|
103
|
-
setIsDragOver(false)
|
|
104
|
-
}, [])
|
|
105
|
-
|
|
106
49
|
// Note: this is called for the dragging block
|
|
107
50
|
const handleDragEnd = useCallback(
|
|
108
51
|
(event: DragEvent) => {
|
|
@@ -119,7 +62,7 @@ export const DraggableBlock = ({
|
|
|
119
62
|
const dragPosition = IS_DRAGGING_BLOCK_TARGET_POSITION.get(editor)
|
|
120
63
|
IS_DRAGGING_BLOCK_TARGET_POSITION.delete(editor)
|
|
121
64
|
let targetPath = ReactEditor.findPath(editor, targetBlock)
|
|
122
|
-
const myPath = ReactEditor.findPath(editor, element)
|
|
65
|
+
const myPath = ReactEditor.findPath(editor, props.element)
|
|
123
66
|
const isBefore = Path.isBefore(myPath, targetPath)
|
|
124
67
|
if (dragPosition === 'bottom' && !isBefore) {
|
|
125
68
|
// If it is already at the bottom, don't do anything.
|
|
@@ -154,7 +97,7 @@ export const DraggableBlock = ({
|
|
|
154
97
|
return
|
|
155
98
|
}
|
|
156
99
|
debug(
|
|
157
|
-
`Moving element ${element._key} from path ${JSON.stringify(myPath)} to ${JSON.stringify(
|
|
100
|
+
`Moving element ${props.element._key} from path ${JSON.stringify(myPath)} to ${JSON.stringify(
|
|
158
101
|
targetPath,
|
|
159
102
|
)} (${dragPosition})`,
|
|
160
103
|
)
|
|
@@ -164,29 +107,14 @@ export const DraggableBlock = ({
|
|
|
164
107
|
}
|
|
165
108
|
debug('No target element, not doing anything')
|
|
166
109
|
},
|
|
167
|
-
[editor, element],
|
|
168
|
-
)
|
|
169
|
-
// Note: this is called not for the dragging block, but for the drop target
|
|
170
|
-
const handleDrop = useCallback(
|
|
171
|
-
(event: DragEvent) => {
|
|
172
|
-
if (IS_DRAGGING_BLOCK_ELEMENT.get(editor)) {
|
|
173
|
-
debug('On drop (prevented)', element)
|
|
174
|
-
event.preventDefault()
|
|
175
|
-
event.stopPropagation()
|
|
176
|
-
setIsDragOver(false)
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
[editor, element],
|
|
110
|
+
[editor, props.element],
|
|
180
111
|
)
|
|
112
|
+
|
|
181
113
|
// Note: this is called for the dragging block
|
|
182
114
|
const handleDrag = useCallback(
|
|
183
115
|
(event: DragEvent) => {
|
|
184
|
-
if (!isVoid) {
|
|
185
|
-
IS_DRAGGING_BLOCK_ELEMENT.delete(editor)
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
116
|
IS_DRAGGING.set(editor, true)
|
|
189
|
-
IS_DRAGGING_BLOCK_ELEMENT.set(editor, element)
|
|
117
|
+
IS_DRAGGING_BLOCK_ELEMENT.set(editor, props.element)
|
|
190
118
|
event.stopPropagation() // Stop propagation so that leafs don't get this and take focus/selection!
|
|
191
119
|
|
|
192
120
|
const target = event.target
|
|
@@ -195,18 +123,12 @@ export const DraggableBlock = ({
|
|
|
195
123
|
target.style.opacity = '1'
|
|
196
124
|
}
|
|
197
125
|
},
|
|
198
|
-
[editor, element
|
|
126
|
+
[editor, props.element],
|
|
199
127
|
)
|
|
200
128
|
|
|
201
129
|
// Note: this is called for the dragging block
|
|
202
130
|
const handleDragStart = useCallback(
|
|
203
131
|
(event: DragEvent) => {
|
|
204
|
-
if (!isVoid || isInline) {
|
|
205
|
-
debug('Not dragging block')
|
|
206
|
-
IS_DRAGGING_BLOCK_ELEMENT.delete(editor)
|
|
207
|
-
IS_DRAGGING.set(editor, false)
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
132
|
debug('Drag start')
|
|
211
133
|
IS_DRAGGING.set(editor, true)
|
|
212
134
|
if (event.dataTransfer) {
|
|
@@ -244,69 +166,26 @@ export const DraggableBlock = ({
|
|
|
244
166
|
}
|
|
245
167
|
handleDrag(event)
|
|
246
168
|
},
|
|
247
|
-
[blockElement, editor, handleDrag
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
const isDraggingOverFirstBlock =
|
|
251
|
-
isDragOver && editor.children[0] === IS_DRAGGING_ELEMENT_TARGET.get(editor)
|
|
252
|
-
const isDraggingOverLastBlock =
|
|
253
|
-
isDragOver &&
|
|
254
|
-
editor.children[editor.children.length - 1] ===
|
|
255
|
-
IS_DRAGGING_ELEMENT_TARGET.get(editor)
|
|
256
|
-
const dragPosition = IS_DRAGGING_BLOCK_TARGET_POSITION.get(editor)
|
|
257
|
-
|
|
258
|
-
const isDraggingOverTop =
|
|
259
|
-
isDraggingOverFirstBlock ||
|
|
260
|
-
(isDragOver &&
|
|
261
|
-
!isDraggingOverFirstBlock &&
|
|
262
|
-
!isDraggingOverLastBlock &&
|
|
263
|
-
dragPosition === 'top')
|
|
264
|
-
const isDraggingOverBottom =
|
|
265
|
-
isDraggingOverLastBlock ||
|
|
266
|
-
(isDragOver &&
|
|
267
|
-
!isDraggingOverFirstBlock &&
|
|
268
|
-
!isDraggingOverLastBlock &&
|
|
269
|
-
dragPosition === 'bottom')
|
|
270
|
-
|
|
271
|
-
const dropIndicator = useMemo(
|
|
272
|
-
() => (
|
|
273
|
-
<div
|
|
274
|
-
className="pt-drop-indicator"
|
|
275
|
-
style={{
|
|
276
|
-
position: 'absolute',
|
|
277
|
-
width: '100%',
|
|
278
|
-
height: 1,
|
|
279
|
-
borderBottom: '1px solid currentColor',
|
|
280
|
-
zIndex: 5,
|
|
281
|
-
}}
|
|
282
|
-
/>
|
|
283
|
-
),
|
|
284
|
-
[],
|
|
169
|
+
[blockElement, editor, handleDrag],
|
|
285
170
|
)
|
|
286
171
|
|
|
287
|
-
if (readOnly) {
|
|
288
|
-
return
|
|
172
|
+
if (props.readOnly) {
|
|
173
|
+
return {
|
|
174
|
+
draggableProps: {
|
|
175
|
+
draggable: false,
|
|
176
|
+
onDragStart: undefined,
|
|
177
|
+
onDrag: undefined,
|
|
178
|
+
onDragEnd: undefined,
|
|
179
|
+
},
|
|
180
|
+
}
|
|
289
181
|
}
|
|
290
182
|
|
|
291
|
-
|
|
292
|
-
|
|
183
|
+
return {
|
|
184
|
+
draggableProps: {
|
|
185
|
+
draggable: true,
|
|
186
|
+
onDragStart: handleDragStart,
|
|
187
|
+
onDrag: handleDrag,
|
|
188
|
+
onDragEnd: handleDragEnd,
|
|
189
|
+
},
|
|
293
190
|
}
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<div
|
|
297
|
-
draggable={isVoid}
|
|
298
|
-
onDragStart={handleDragStart}
|
|
299
|
-
onDrag={handleDrag}
|
|
300
|
-
onDragOver={handleDragOver}
|
|
301
|
-
onDragLeave={handleDragLeave}
|
|
302
|
-
onDragEnd={handleDragEnd}
|
|
303
|
-
onDrop={handleDrop}
|
|
304
|
-
>
|
|
305
|
-
{isDraggingOverTop && dropIndicator}
|
|
306
|
-
{children}
|
|
307
|
-
{isDraggingOverBottom && dropIndicator}
|
|
308
|
-
</div>
|
|
309
|
-
)
|
|
310
191
|
}
|
|
311
|
-
|
|
312
|
-
DraggableBlock.displayName = 'DraggableBlock'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import {useCallback, useEffect, useState, type DragEvent} from 'react'
|
|
3
|
+
import type {Element as SlateElement} from 'slate'
|
|
4
|
+
import {ReactEditor, useSlateStatic} from 'slate-react'
|
|
5
|
+
import {debugWithName} from '../../internal-utils/debug'
|
|
6
|
+
import {
|
|
7
|
+
IS_DRAGGING_BLOCK_ELEMENT,
|
|
8
|
+
IS_DRAGGING_BLOCK_TARGET_POSITION,
|
|
9
|
+
IS_DRAGGING_ELEMENT_TARGET,
|
|
10
|
+
} from '../../internal-utils/weakMaps'
|
|
11
|
+
|
|
12
|
+
const debug = debugWithName('useDroppable')
|
|
13
|
+
|
|
14
|
+
type Droppable = {
|
|
15
|
+
droppableProps: {
|
|
16
|
+
onDragOver?: (event: DragEvent) => void
|
|
17
|
+
onDragLeave?: () => void
|
|
18
|
+
onDrop?: (event: DragEvent) => void
|
|
19
|
+
}
|
|
20
|
+
isDraggingOverTop: boolean
|
|
21
|
+
isDraggingOverBottom: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useDroppable(props: {
|
|
25
|
+
element: SlateElement
|
|
26
|
+
blockRef: React.RefObject<HTMLDivElement | null>
|
|
27
|
+
readOnly: boolean
|
|
28
|
+
}): Droppable {
|
|
29
|
+
const editor = useSlateStatic()
|
|
30
|
+
const [isDragOver, setIsDragOver] = useState(false)
|
|
31
|
+
const [blockElement, setBlockElement] = useState<HTMLElement | null>(null)
|
|
32
|
+
|
|
33
|
+
useEffect(
|
|
34
|
+
() =>
|
|
35
|
+
setBlockElement(
|
|
36
|
+
props.blockRef
|
|
37
|
+
? props.blockRef.current
|
|
38
|
+
: ReactEditor.toDOMNode(editor, props.element),
|
|
39
|
+
),
|
|
40
|
+
[editor, props.element, props.blockRef],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const handleDragOver = useCallback(
|
|
44
|
+
(event: DragEvent) => {
|
|
45
|
+
const isMyDragOver = IS_DRAGGING_BLOCK_ELEMENT.get(editor)
|
|
46
|
+
// debug('Drag over', blockElement)
|
|
47
|
+
if (!isMyDragOver || !blockElement) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
event.preventDefault()
|
|
51
|
+
event.dataTransfer.dropEffect = 'move'
|
|
52
|
+
IS_DRAGGING_ELEMENT_TARGET.set(editor, props.element)
|
|
53
|
+
const elementRect = blockElement.getBoundingClientRect()
|
|
54
|
+
const offset = elementRect.top
|
|
55
|
+
const height = elementRect.height
|
|
56
|
+
const Y = event.pageY
|
|
57
|
+
const loc = Math.abs(offset - Y)
|
|
58
|
+
let position: 'top' | 'bottom' = 'bottom'
|
|
59
|
+
if (props.element === editor.children[0]) {
|
|
60
|
+
position = 'top'
|
|
61
|
+
} else if (loc < height / 2) {
|
|
62
|
+
position = 'top'
|
|
63
|
+
IS_DRAGGING_BLOCK_TARGET_POSITION.set(editor, position)
|
|
64
|
+
} else {
|
|
65
|
+
position = 'bottom'
|
|
66
|
+
IS_DRAGGING_BLOCK_TARGET_POSITION.set(editor, position)
|
|
67
|
+
}
|
|
68
|
+
if (isMyDragOver === props.element) {
|
|
69
|
+
event.dataTransfer.dropEffect = 'none'
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
setIsDragOver(true)
|
|
73
|
+
},
|
|
74
|
+
[blockElement, editor, props.element],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const handleDragLeave = useCallback(() => {
|
|
78
|
+
setIsDragOver(false)
|
|
79
|
+
}, [])
|
|
80
|
+
|
|
81
|
+
const handleDrop = useCallback(
|
|
82
|
+
(event: DragEvent) => {
|
|
83
|
+
if (IS_DRAGGING_BLOCK_ELEMENT.get(editor)) {
|
|
84
|
+
debug('On drop (prevented)', props.element)
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
event.stopPropagation()
|
|
87
|
+
setIsDragOver(false)
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
[editor, props.element],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const isDraggingOverFirstBlock =
|
|
94
|
+
isDragOver && editor.children[0] === IS_DRAGGING_ELEMENT_TARGET.get(editor)
|
|
95
|
+
const isDraggingOverLastBlock =
|
|
96
|
+
isDragOver &&
|
|
97
|
+
editor.children[editor.children.length - 1] ===
|
|
98
|
+
IS_DRAGGING_ELEMENT_TARGET.get(editor)
|
|
99
|
+
const dragPosition = IS_DRAGGING_BLOCK_TARGET_POSITION.get(editor)
|
|
100
|
+
|
|
101
|
+
const isDraggingOverTop =
|
|
102
|
+
isDraggingOverFirstBlock ||
|
|
103
|
+
(isDragOver &&
|
|
104
|
+
!isDraggingOverFirstBlock &&
|
|
105
|
+
!isDraggingOverLastBlock &&
|
|
106
|
+
dragPosition === 'top')
|
|
107
|
+
const isDraggingOverBottom =
|
|
108
|
+
isDraggingOverLastBlock ||
|
|
109
|
+
(isDragOver &&
|
|
110
|
+
!isDraggingOverFirstBlock &&
|
|
111
|
+
!isDraggingOverLastBlock &&
|
|
112
|
+
dragPosition === 'bottom')
|
|
113
|
+
|
|
114
|
+
if (props.readOnly) {
|
|
115
|
+
return {
|
|
116
|
+
droppableProps: {
|
|
117
|
+
onDragOver: undefined,
|
|
118
|
+
onDragLeave: undefined,
|
|
119
|
+
onDrop: undefined,
|
|
120
|
+
},
|
|
121
|
+
isDraggingOverTop: false,
|
|
122
|
+
isDraggingOverBottom: false,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
droppableProps: {
|
|
128
|
+
onDragOver: handleDragOver,
|
|
129
|
+
onDragLeave: handleDragLeave,
|
|
130
|
+
onDrop: handleDrop,
|
|
131
|
+
},
|
|
132
|
+
isDraggingOverTop,
|
|
133
|
+
isDraggingOverBottom,
|
|
134
|
+
}
|
|
135
|
+
}
|