@portabletext/editor 1.36.6 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-cjs/behavior.core.cjs +84 -49
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/behavior.markdown.cjs +1 -1
- package/lib/_chunks-cjs/editor-provider.cjs +919 -526
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-cjs/{util.block-offsets-to-selection.cjs → parse-blocks.cjs} +36 -21
- package/lib/_chunks-cjs/parse-blocks.cjs.map +1 -0
- package/lib/_chunks-cjs/selector.get-text-before.cjs +2 -2
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/{selector.is-active-style.cjs → selector.is-overlapping-selection.cjs} +144 -3
- package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs +12 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js +84 -49
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/behavior.markdown.js +1 -1
- package/lib/_chunks-es/editor-provider.js +911 -517
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/_chunks-es/{util.block-offsets-to-selection.js → parse-blocks.js} +37 -22
- package/lib/_chunks-es/parse-blocks.js.map +1 -0
- 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-active-style.js → selector.is-overlapping-selection.js} +146 -5
- package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -0
- package/lib/_chunks-es/util.slice-blocks.js +12 -0
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/behaviors/index.d.cts +10535 -4689
- package/lib/behaviors/index.d.ts +10535 -4689
- package/lib/index.cjs +582 -209
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +5297 -1178
- package/lib/index.d.ts +5297 -1178
- package/lib/index.js +591 -213
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.cjs +2 -2
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.cts +5297 -1178
- package/lib/plugins/index.d.ts +5297 -1178
- package/lib/plugins/index.js +2 -2
- package/lib/selectors/index.cjs +21 -103
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +5313 -1178
- package/lib/selectors/index.d.ts +5313 -1178
- package/lib/selectors/index.js +13 -96
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.cjs +4 -4
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +5297 -1178
- package/lib/utils/index.d.ts +5297 -1178
- package/lib/utils/index.js +3 -4
- package/lib/utils/index.js.map +1 -1
- package/package.json +15 -14
- package/src/behavior-actions/behavior.action.blur.ts +8 -0
- package/src/behavior-actions/behavior.action.decorator.add.ts +2 -1
- package/src/behavior-actions/behavior.action.delete.backward.ts +7 -0
- package/src/behavior-actions/behavior.action.delete.block.ts +24 -0
- package/src/behavior-actions/behavior.action.delete.forward.ts +7 -0
- package/src/behavior-actions/behavior.action.delete.text.ts +2 -1
- package/src/behavior-actions/behavior.action.delete.ts +1 -3
- package/src/behavior-actions/behavior.action.deserialization.failure.ts +9 -0
- package/src/behavior-actions/behavior.action.deserialization.success.ts +16 -0
- package/src/behavior-actions/behavior.action.effect.ts +7 -0
- package/src/behavior-actions/behavior.action.focus.ts +8 -0
- package/src/behavior-actions/behavior.action.insert-blocks.ts +118 -74
- package/src/behavior-actions/behavior.action.insert-break.ts +1 -0
- package/src/behavior-actions/{behavior.action.insert-block-object.ts → behavior.action.insert.block-object.ts} +9 -14
- package/src/behavior-actions/behavior.action.insert.block.ts +247 -2
- package/src/behavior-actions/behavior.action.insert.text-block.ts +33 -0
- package/src/behavior-actions/behavior.action.insert.text.ts +7 -0
- package/src/behavior-actions/behavior.action.move.block-down.ts +48 -0
- package/src/behavior-actions/behavior.action.move.block-up.ts +53 -0
- package/src/behavior-actions/behavior.action.move.block.ts +16 -0
- package/src/behavior-actions/behavior.action.noop.ts +5 -0
- package/src/behavior-actions/behavior.action.select.next-block.ts +44 -0
- package/src/behavior-actions/behavior.action.select.previous-block.ts +48 -0
- package/src/behavior-actions/behavior.action.select.ts +15 -0
- package/src/behavior-actions/behavior.action.serialization.failure.ts +9 -0
- package/src/behavior-actions/behavior.action.serialization.success.ts +14 -0
- package/src/behavior-actions/behavior.actions.ts +54 -212
- package/src/behaviors/behavior.core.block-objects.ts +35 -6
- package/src/behaviors/behavior.core.insert-break.ts +1 -0
- package/src/behaviors/behavior.core.ts +2 -0
- package/src/behaviors/behavior.default.ts +241 -33
- package/src/behaviors/behavior.types.ts +138 -20
- package/src/converters/converter.portable-text.ts +5 -2
- package/src/converters/converter.text-html.serialize.test.ts +4 -4
- package/src/converters/converter.text-html.ts +5 -2
- package/src/converters/converter.text-plain.test.ts +6 -6
- package/src/converters/converter.text-plain.ts +5 -2
- package/src/converters/converter.types.ts +3 -3
- package/src/editor/Editable.tsx +403 -48
- package/src/editor/components/Element.tsx +133 -18
- package/src/editor/components/use-draggable.ts +34 -102
- package/src/editor/editor-machine.ts +66 -10
- package/src/editor/editor-selector.ts +2 -0
- package/src/editor/editor-snapshot.ts +17 -0
- package/src/editor/plugins/create-with-event-listeners.ts +6 -40
- package/src/internal-utils/create-test-snapshot.ts +2 -0
- package/src/internal-utils/event-position.ts +210 -0
- package/src/internal-utils/slate-utils.ts +56 -0
- package/src/internal-utils/weakMaps.ts +1 -15
- package/src/selectors/index.ts +2 -0
- package/src/selectors/selector.get-focus-inline-object.ts +21 -0
- package/src/selectors/selector.is-overlapping-selection.test.ts +171 -0
- package/src/selectors/selector.is-overlapping-selection.ts +108 -4
- package/src/selectors/selector.is-point-after-selection.ts +3 -1
- package/src/selectors/selector.is-point-before-selection.ts +3 -1
- package/src/selectors/selector.is-selecting-entire-blocks.ts +34 -0
- package/lib/_chunks-cjs/selector.is-active-style.cjs.map +0 -1
- package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +0 -1
- package/lib/_chunks-cjs/util.reverse-selection.cjs +0 -14
- package/lib/_chunks-cjs/util.reverse-selection.cjs.map +0 -1
- package/lib/_chunks-es/selector.is-active-style.js.map +0 -1
- package/lib/_chunks-es/util.block-offsets-to-selection.js.map +0 -1
- package/lib/_chunks-es/util.reverse-selection.js +0 -15
- package/lib/_chunks-es/util.reverse-selection.js.map +0 -1
- package/src/behavior-actions/behavior.action-utils.insert-block.ts +0 -61
- package/src/editor/__tests__/handleClick.test.tsx +0 -277
- package/src/editor/components/use-droppable.ts +0 -135
|
@@ -61,15 +61,8 @@ export function createWithEventListeners(
|
|
|
61
61
|
}
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
const {
|
|
65
|
-
|
|
66
|
-
deleteForward,
|
|
67
|
-
insertBreak,
|
|
68
|
-
insertData,
|
|
69
|
-
insertText,
|
|
70
|
-
select,
|
|
71
|
-
setFragmentData,
|
|
72
|
-
} = editor
|
|
64
|
+
const {deleteBackward, deleteForward, insertBreak, insertText, select} =
|
|
65
|
+
editor
|
|
73
66
|
|
|
74
67
|
editor.deleteBackward = (unit) => {
|
|
75
68
|
if (isApplyingBehaviorActions(editor)) {
|
|
@@ -121,20 +114,8 @@ export function createWithEventListeners(
|
|
|
121
114
|
return
|
|
122
115
|
}
|
|
123
116
|
|
|
124
|
-
editor.insertData = (
|
|
125
|
-
|
|
126
|
-
insertData(dataTransfer)
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
editorActor.send({
|
|
131
|
-
type: 'behavior event',
|
|
132
|
-
behaviorEvent: {
|
|
133
|
-
type: 'deserialize',
|
|
134
|
-
dataTransfer,
|
|
135
|
-
},
|
|
136
|
-
editor,
|
|
137
|
-
})
|
|
117
|
+
editor.insertData = () => {
|
|
118
|
+
console.warn('Unexpected call to .insertData(...)')
|
|
138
119
|
return
|
|
139
120
|
}
|
|
140
121
|
|
|
@@ -236,23 +217,8 @@ export function createWithEventListeners(
|
|
|
236
217
|
return
|
|
237
218
|
}
|
|
238
219
|
|
|
239
|
-
editor.setFragmentData = (
|
|
240
|
-
|
|
241
|
-
setFragmentData(dataTransfer)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
dataTransfer.clearData()
|
|
246
|
-
|
|
247
|
-
editorActor.send({
|
|
248
|
-
type: 'behavior event',
|
|
249
|
-
behaviorEvent: {
|
|
250
|
-
type: 'serialize',
|
|
251
|
-
dataTransfer,
|
|
252
|
-
originEvent: originEvent ?? 'unknown',
|
|
253
|
-
},
|
|
254
|
-
editor,
|
|
255
|
-
})
|
|
220
|
+
editor.setFragmentData = () => {
|
|
221
|
+
console.warn('Unexpected call to .setFragmentData(...)')
|
|
256
222
|
return
|
|
257
223
|
}
|
|
258
224
|
|
|
@@ -13,11 +13,13 @@ export function createTestSnapshot(snapshot: {
|
|
|
13
13
|
snapshot.context?.schema ?? compileSchemaDefinition(defineSchema({})),
|
|
14
14
|
keyGenerator: snapshot.context?.keyGenerator ?? createTestKeyGenerator(),
|
|
15
15
|
activeDecorators: snapshot.context?.activeDecorators ?? [],
|
|
16
|
+
readOnly: snapshot.context?.readOnly ?? false,
|
|
16
17
|
value: snapshot.context?.value ?? [],
|
|
17
18
|
selection: snapshot.context?.selection ?? null,
|
|
18
19
|
},
|
|
19
20
|
beta: {
|
|
20
21
|
hasTag: snapshot.beta?.hasTag ?? (() => false),
|
|
22
|
+
internalDrag: undefined,
|
|
21
23
|
},
|
|
22
24
|
}
|
|
23
25
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import {Editor, type BaseRange} from 'slate'
|
|
2
|
+
import {DOMEditor, isDOMNode} from 'slate-dom'
|
|
3
|
+
import {ReactEditor} from 'slate-react'
|
|
4
|
+
import type {EditorSelection, EditorSnapshot} from '..'
|
|
5
|
+
import * as selectors from '../selectors'
|
|
6
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
7
|
+
import * as utils from '../utils'
|
|
8
|
+
import {toPortableTextRange} from './ranges'
|
|
9
|
+
|
|
10
|
+
export type EventPosition = {
|
|
11
|
+
block: 'start' | 'end'
|
|
12
|
+
/**
|
|
13
|
+
* Did the event origin from the editor DOM node itself or from a child node?
|
|
14
|
+
*/
|
|
15
|
+
isEditor: boolean
|
|
16
|
+
selection: NonNullable<EditorSelection>
|
|
17
|
+
}
|
|
18
|
+
export type EventPositionBlock = EventPosition['block']
|
|
19
|
+
|
|
20
|
+
export function getEventPosition({
|
|
21
|
+
snapshot,
|
|
22
|
+
slateEditor,
|
|
23
|
+
event,
|
|
24
|
+
}: {
|
|
25
|
+
snapshot: EditorSnapshot
|
|
26
|
+
slateEditor: PortableTextSlateEditor
|
|
27
|
+
event: DragEvent | ClipboardEvent | MouseEvent
|
|
28
|
+
}): EventPosition | undefined {
|
|
29
|
+
if (!DOMEditor.hasTarget(slateEditor, event.target)) {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const node = DOMEditor.toSlateNode(slateEditor, event.target)
|
|
34
|
+
|
|
35
|
+
if (isClipboardEvent(event)) {
|
|
36
|
+
const selection = snapshot.context.selection
|
|
37
|
+
|
|
38
|
+
if (!selection) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
block: 'end',
|
|
44
|
+
isEditor: Editor.isEditor(node),
|
|
45
|
+
selection,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const block = getEventPositionBlock({slateEditor, event})
|
|
50
|
+
const selection = getEventPositionSelection({snapshot, slateEditor, event})
|
|
51
|
+
|
|
52
|
+
if (!block || !selection) {
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
block,
|
|
58
|
+
isEditor: Editor.isEditor(node),
|
|
59
|
+
selection,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getEventPositionBlock({
|
|
64
|
+
slateEditor,
|
|
65
|
+
event,
|
|
66
|
+
}: {
|
|
67
|
+
slateEditor: PortableTextSlateEditor
|
|
68
|
+
event: DragEvent | MouseEvent
|
|
69
|
+
}): EventPositionBlock | undefined {
|
|
70
|
+
if (!ReactEditor.hasTarget(slateEditor, event.target)) {
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const node = ReactEditor.toSlateNode(slateEditor, event.target)
|
|
75
|
+
const element = ReactEditor.toDOMNode(slateEditor, node)
|
|
76
|
+
const elementRect = element.getBoundingClientRect()
|
|
77
|
+
const top = elementRect.top
|
|
78
|
+
const height = elementRect.height
|
|
79
|
+
const location = Math.abs(top - event.pageY)
|
|
80
|
+
|
|
81
|
+
return location < height / 2 ? 'start' : 'end'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getEventPositionSelection({
|
|
85
|
+
snapshot,
|
|
86
|
+
slateEditor,
|
|
87
|
+
event,
|
|
88
|
+
}: {
|
|
89
|
+
snapshot: EditorSnapshot
|
|
90
|
+
slateEditor: PortableTextSlateEditor
|
|
91
|
+
event: DragEvent | MouseEvent
|
|
92
|
+
}): EditorSelection {
|
|
93
|
+
const range = getSlateRangeFromEvent(slateEditor, event)
|
|
94
|
+
|
|
95
|
+
const selection = range
|
|
96
|
+
? toPortableTextRange(
|
|
97
|
+
snapshot.context.value,
|
|
98
|
+
range,
|
|
99
|
+
snapshot.context.schema,
|
|
100
|
+
)
|
|
101
|
+
: null
|
|
102
|
+
|
|
103
|
+
if (!selection) {
|
|
104
|
+
return selection
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const collapsedSelection = selectors.isSelectionCollapsed({
|
|
108
|
+
...snapshot,
|
|
109
|
+
context: {
|
|
110
|
+
...snapshot.context,
|
|
111
|
+
selection,
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
const focusTextBlock = selectors.getFocusTextBlock({
|
|
115
|
+
...snapshot,
|
|
116
|
+
context: {
|
|
117
|
+
...snapshot.context,
|
|
118
|
+
selection,
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
const focusSpan = selectors.getFocusSpan({
|
|
122
|
+
...snapshot,
|
|
123
|
+
context: {
|
|
124
|
+
...snapshot.context,
|
|
125
|
+
selection,
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
event.type === 'dragstart' &&
|
|
131
|
+
collapsedSelection &&
|
|
132
|
+
focusTextBlock &&
|
|
133
|
+
focusSpan
|
|
134
|
+
) {
|
|
135
|
+
// Looks like we are dragging an empty span. Let's drag the entire block
|
|
136
|
+
// instead
|
|
137
|
+
|
|
138
|
+
const blockStartPoint = utils.getBlockStartPoint(focusTextBlock)
|
|
139
|
+
const blockEndPoint = utils.getBlockEndPoint(focusTextBlock)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
anchor: blockStartPoint,
|
|
143
|
+
focus: blockEndPoint,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return selection
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getSlateRangeFromEvent(
|
|
151
|
+
editor: PortableTextSlateEditor,
|
|
152
|
+
event: DragEvent | MouseEvent,
|
|
153
|
+
) {
|
|
154
|
+
if (!event.target) {
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!isDOMNode(event.target)) {
|
|
159
|
+
return undefined
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const window = DOMEditor.getWindow(editor)
|
|
163
|
+
|
|
164
|
+
let domRange: Range | undefined
|
|
165
|
+
|
|
166
|
+
if (window.document.caretPositionFromPoint !== undefined) {
|
|
167
|
+
const position = window.document.caretPositionFromPoint(
|
|
168
|
+
event.clientX,
|
|
169
|
+
event.clientY,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if (position) {
|
|
173
|
+
domRange = window.document.createRange()
|
|
174
|
+
domRange.setStart(position.offsetNode, position.offset)
|
|
175
|
+
domRange.setEnd(position.offsetNode, position.offset)
|
|
176
|
+
}
|
|
177
|
+
} else if (window.document.caretRangeFromPoint !== undefined) {
|
|
178
|
+
// Use WebKit-proprietary fallback method
|
|
179
|
+
domRange =
|
|
180
|
+
window.document.caretRangeFromPoint(event.clientX, event.clientY) ??
|
|
181
|
+
undefined
|
|
182
|
+
} else {
|
|
183
|
+
console.warn(
|
|
184
|
+
'Neither caretPositionFromPoint nor caretRangeFromPoint is supported',
|
|
185
|
+
)
|
|
186
|
+
return undefined
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!domRange) {
|
|
190
|
+
return undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let range: BaseRange | undefined
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
range = DOMEditor.toSlateRange(editor, domRange, {
|
|
197
|
+
exactMatch: false,
|
|
198
|
+
// It can still throw even with this option set to true
|
|
199
|
+
suppressThrow: false,
|
|
200
|
+
})
|
|
201
|
+
} catch {}
|
|
202
|
+
|
|
203
|
+
return range
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isClipboardEvent(
|
|
207
|
+
event: DragEvent | ClipboardEvent | MouseEvent,
|
|
208
|
+
): event is ClipboardEvent {
|
|
209
|
+
return event.type === 'copy' || event.type === 'cut' || event.type === 'paste'
|
|
210
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {Editor, Node, type Path} from 'slate'
|
|
2
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
3
|
+
|
|
4
|
+
export function getFocusBlock({
|
|
5
|
+
editor,
|
|
6
|
+
}: {
|
|
7
|
+
editor: PortableTextSlateEditor
|
|
8
|
+
}): [node: Node, path: Path] | [undefined, undefined] {
|
|
9
|
+
if (!editor.selection) {
|
|
10
|
+
return [undefined, undefined]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const focusBlock = Array.from(
|
|
14
|
+
Editor.nodes(editor, {
|
|
15
|
+
at: editor.selection.focus.path.slice(0, 1),
|
|
16
|
+
match: (n) => !Editor.isEditor(n),
|
|
17
|
+
}),
|
|
18
|
+
).at(0)
|
|
19
|
+
|
|
20
|
+
return focusBlock ?? [undefined, undefined]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getFocusChild({
|
|
24
|
+
editor,
|
|
25
|
+
}: {
|
|
26
|
+
editor: PortableTextSlateEditor
|
|
27
|
+
}): [node: Node, path: Path] | [undefined, undefined] {
|
|
28
|
+
const [focusBlock, focusBlockPath] = getFocusBlock({editor})
|
|
29
|
+
const childIndex = editor.selection?.focus.path.at(1)
|
|
30
|
+
|
|
31
|
+
if (!focusBlock || !focusBlockPath || childIndex === undefined) {
|
|
32
|
+
return [undefined, undefined]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const focusChild = Node.child(focusBlock, childIndex)
|
|
36
|
+
|
|
37
|
+
return focusChild
|
|
38
|
+
? [focusChild, [...focusBlockPath, childIndex]]
|
|
39
|
+
: [undefined, undefined]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getLastBlock({
|
|
43
|
+
editor,
|
|
44
|
+
}: {
|
|
45
|
+
editor: PortableTextSlateEditor
|
|
46
|
+
}): [node: Node, path: Path] | [undefined, undefined] {
|
|
47
|
+
const lastBlock = Array.from(
|
|
48
|
+
Editor.nodes(editor, {
|
|
49
|
+
match: (n) => !Editor.isEditor(n),
|
|
50
|
+
at: [],
|
|
51
|
+
reverse: true,
|
|
52
|
+
}),
|
|
53
|
+
).at(0)
|
|
54
|
+
|
|
55
|
+
return lastBlock ?? [undefined, undefined]
|
|
56
|
+
}
|
|
@@ -1,24 +1,10 @@
|
|
|
1
|
-
import type {Editor,
|
|
1
|
+
import type {Editor, Range} from 'slate'
|
|
2
2
|
import type {EditorSelection} from '..'
|
|
3
3
|
|
|
4
4
|
// Is the editor currently receiving remote changes that are being applied to the content?
|
|
5
5
|
export const IS_PROCESSING_REMOTE_CHANGES: WeakMap<Editor, boolean> =
|
|
6
6
|
new WeakMap()
|
|
7
7
|
|
|
8
|
-
// Is the editor dragging something?
|
|
9
|
-
export const IS_DRAGGING: WeakMap<Editor, boolean> = new WeakMap()
|
|
10
|
-
// Is the editor dragging a element?
|
|
11
|
-
export const IS_DRAGGING_BLOCK_ELEMENT: WeakMap<Editor, Element> = new WeakMap()
|
|
12
|
-
|
|
13
|
-
// When dragging elements, this will be the target element
|
|
14
|
-
export const IS_DRAGGING_ELEMENT_TARGET: WeakMap<Editor, Element> =
|
|
15
|
-
new WeakMap()
|
|
16
|
-
// Target position for dragging over a block
|
|
17
|
-
export const IS_DRAGGING_BLOCK_TARGET_POSITION: WeakMap<
|
|
18
|
-
Editor,
|
|
19
|
-
'top' | 'bottom'
|
|
20
|
-
> = new WeakMap()
|
|
21
|
-
|
|
22
8
|
export const KEY_TO_SLATE_ELEMENT: WeakMap<Editor, any | undefined> =
|
|
23
9
|
new WeakMap()
|
|
24
10
|
export const KEY_TO_VALUE_ELEMENT: WeakMap<Editor, any | undefined> =
|
package/src/selectors/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export {isSelectingEntireBlocks} from './selector.is-selecting-entire-blocks'
|
|
1
2
|
export {getActiveAnnotations} from './selector.get-active-annotations'
|
|
2
3
|
export {getActiveListItem} from './selector.get-active-list-item'
|
|
3
4
|
export {getActiveStyle} from './selector.get-active-style'
|
|
@@ -7,6 +8,7 @@ export {getAnchorSpan} from './selector.get-anchor-span'
|
|
|
7
8
|
export {getAnchorTextBlock} from './selector.get-anchor-text-block'
|
|
8
9
|
export {getBlockOffsets} from './selector.get-block-offsets'
|
|
9
10
|
export {getCaretWordSelection} from './selector.get-caret-word-selection'
|
|
11
|
+
export {getFocusInlineObject} from './selector.get-focus-inline-object'
|
|
10
12
|
export {getNextInlineObject} from './selector.get-next-inline-object'
|
|
11
13
|
export {getPreviousInlineObject} from './selector.get-previous-inline-object'
|
|
12
14
|
export {getSelectedSlice} from './selector.get-selected-slice'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPortableTextSpan,
|
|
3
|
+
type KeyedSegment,
|
|
4
|
+
type PortableTextObject,
|
|
5
|
+
} from '@sanity/types'
|
|
6
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
7
|
+
import {getFocusChild} from './selectors'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export const getFocusInlineObject: EditorSelector<
|
|
13
|
+
| {node: PortableTextObject; path: [KeyedSegment, 'children', KeyedSegment]}
|
|
14
|
+
| undefined
|
|
15
|
+
> = (snapshot) => {
|
|
16
|
+
const focusChild = getFocusChild(snapshot)
|
|
17
|
+
|
|
18
|
+
return focusChild && !isPortableTextSpan(focusChild.node)
|
|
19
|
+
? {node: focusChild.node, path: focusChild.path}
|
|
20
|
+
: undefined
|
|
21
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {describe, expect, test} from 'vitest'
|
|
2
|
+
import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
|
|
3
|
+
import type {EditorSelection} from '../types/editor'
|
|
4
|
+
import {isOverlappingSelection} from './selector.is-overlapping-selection'
|
|
5
|
+
|
|
6
|
+
function snapshot(selection: EditorSelection) {
|
|
7
|
+
return createTestSnapshot({
|
|
8
|
+
context: {
|
|
9
|
+
selection,
|
|
10
|
+
value: [
|
|
11
|
+
{_type: 'image', _key: 'k0'},
|
|
12
|
+
{
|
|
13
|
+
_type: 'block',
|
|
14
|
+
_key: 'k1',
|
|
15
|
+
children: [
|
|
16
|
+
{_type: 'span', _key: 'k3', text: 'foo'},
|
|
17
|
+
{_type: 'stock-ticker', _key: 'k4'},
|
|
18
|
+
{_type: 'span', _key: 'k5', text: 'bar'},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{_type: 'image', _key: 'k2'},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe(isOverlappingSelection.name, () => {
|
|
28
|
+
test('null', () => {
|
|
29
|
+
expect(isOverlappingSelection(null)(snapshot(null))).toBe(false)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('fully selected block object', () => {
|
|
33
|
+
expect(
|
|
34
|
+
isOverlappingSelection({
|
|
35
|
+
anchor: {path: [{_key: 'k0'}], offset: 0},
|
|
36
|
+
focus: {path: [{_key: 'k0'}], offset: 0},
|
|
37
|
+
})(
|
|
38
|
+
snapshot({
|
|
39
|
+
anchor: {path: [{_key: 'k0'}], offset: 0},
|
|
40
|
+
focus: {path: [{_key: 'k0'}], offset: 0},
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('block object inside selection', () => {
|
|
47
|
+
expect(
|
|
48
|
+
isOverlappingSelection({
|
|
49
|
+
anchor: {path: [{_key: 'k0'}], offset: 0},
|
|
50
|
+
focus: {path: [{_key: 'k0'}], offset: 0},
|
|
51
|
+
})(
|
|
52
|
+
snapshot({
|
|
53
|
+
anchor: {path: [{_key: 'k0'}], offset: 0},
|
|
54
|
+
focus: {path: [{_key: 'k2'}], offset: 0},
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('fully selected inline object', () => {
|
|
61
|
+
expect(
|
|
62
|
+
isOverlappingSelection({
|
|
63
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
64
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
65
|
+
})(
|
|
66
|
+
snapshot({
|
|
67
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
68
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('inline object inside selection', () => {
|
|
75
|
+
expect(
|
|
76
|
+
isOverlappingSelection({
|
|
77
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
78
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
79
|
+
})(
|
|
80
|
+
snapshot({
|
|
81
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
82
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
83
|
+
}),
|
|
84
|
+
),
|
|
85
|
+
).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('selection right before', () => {
|
|
89
|
+
expect(
|
|
90
|
+
isOverlappingSelection({
|
|
91
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
|
|
92
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
93
|
+
})(
|
|
94
|
+
snapshot({
|
|
95
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
96
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
97
|
+
}),
|
|
98
|
+
),
|
|
99
|
+
).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('selection overlapping from the start', () => {
|
|
103
|
+
expect(
|
|
104
|
+
isOverlappingSelection({
|
|
105
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
|
|
106
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
107
|
+
})(
|
|
108
|
+
snapshot({
|
|
109
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
110
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('selection right after', () => {
|
|
117
|
+
expect(
|
|
118
|
+
isOverlappingSelection({
|
|
119
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
120
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 2},
|
|
121
|
+
})(
|
|
122
|
+
snapshot({
|
|
123
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
124
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('selection overlapping from the end', () => {
|
|
131
|
+
expect(
|
|
132
|
+
isOverlappingSelection({
|
|
133
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
134
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 2},
|
|
135
|
+
})(
|
|
136
|
+
snapshot({
|
|
137
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
138
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
139
|
+
}),
|
|
140
|
+
),
|
|
141
|
+
).toBe(true)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('before inline object', () => {
|
|
145
|
+
expect(
|
|
146
|
+
isOverlappingSelection({
|
|
147
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
148
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
149
|
+
})(
|
|
150
|
+
snapshot({
|
|
151
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
152
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
).toBe(false)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('after inline object', () => {
|
|
159
|
+
expect(
|
|
160
|
+
isOverlappingSelection({
|
|
161
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 2},
|
|
162
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 2},
|
|
163
|
+
})(
|
|
164
|
+
snapshot({
|
|
165
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
166
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k4'}], offset: 0},
|
|
167
|
+
}),
|
|
168
|
+
),
|
|
169
|
+
).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
})
|