@portabletext/editor 2.6.10-canary.0 → 2.7.1
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/selector.is-selecting-entire-blocks.cjs +4 -1
- package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +4 -1
- package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
- package/lib/_chunks-es/selector.is-selection-expanded.js +1 -1
- package/lib/_chunks-es/util.slice-text-block.js +1 -1
- package/lib/index.cjs +10772 -10731
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +10734 -10693
- package/lib/index.js.map +1 -1
- package/package.json +6 -6
- package/src/editor/components/render-block-object.tsx +11 -10
- package/src/editor/components/render-inline-object.tsx +12 -11
- package/src/editor/components/render-span.tsx +109 -149
- package/src/editor/components/render-text-block.tsx +45 -36
- package/src/editor/editor-provider.tsx +2 -3
- package/src/selectors/selector.is-overlapping-selection.test.ts +116 -0
- package/src/selectors/selector.is-overlapping-selection.ts +20 -4
- package/src/internal-utils/use-constant.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"slate-react": "0.117.4",
|
|
81
81
|
"xstate": "^5.21.0",
|
|
82
82
|
"@portabletext/block-tools": "^3.5.1",
|
|
83
|
-
"@portabletext/patches": "^1.1.8",
|
|
84
83
|
"@portabletext/keyboard-shortcuts": "^1.1.1",
|
|
84
|
+
"@portabletext/patches": "^1.1.8",
|
|
85
85
|
"@portabletext/schema": "^1.2.0"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
@@ -95,19 +95,19 @@
|
|
|
95
95
|
"@types/lodash.startcase": "^4.4.9",
|
|
96
96
|
"@types/react": "^19.1.11",
|
|
97
97
|
"@types/react-dom": "^19.1.7",
|
|
98
|
-
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
99
|
-
"@typescript-eslint/parser": "^8.39.1",
|
|
100
98
|
"@vitejs/plugin-react": "^4.7.0",
|
|
101
99
|
"@vitest/browser": "^3.2.4",
|
|
102
100
|
"@vitest/coverage-istanbul": "^3.2.4",
|
|
103
101
|
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
|
104
|
-
"eslint": "
|
|
102
|
+
"eslint": "^9.34.0",
|
|
103
|
+
"eslint-formatter-gha": "^1.6.0",
|
|
105
104
|
"eslint-plugin-react-hooks": "6.0.0-rc.2",
|
|
106
105
|
"jsdom": "^26.0.0",
|
|
107
106
|
"react": "^19.1.1",
|
|
108
107
|
"react-dom": "^19.1.1",
|
|
109
108
|
"rxjs": "^7.8.2",
|
|
110
109
|
"typescript": "5.9.2",
|
|
110
|
+
"typescript-eslint": "^8.41.0",
|
|
111
111
|
"vite": "^7.1.3",
|
|
112
112
|
"vitest": "^3.2.4",
|
|
113
113
|
"vitest-browser-react": "^1.0.1",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"scripts": {
|
|
132
132
|
"build": "pkg-utils build --strict --check --clean",
|
|
133
133
|
"check:lint": "biome lint .",
|
|
134
|
-
"check:react-compiler": "eslint --cache
|
|
134
|
+
"check:react-compiler": "eslint --cache .",
|
|
135
135
|
"check:types": "tsc",
|
|
136
136
|
"check:types:watch": "tsc --watch",
|
|
137
137
|
"clean": "del .turbo && del lib && del node_modules",
|
|
@@ -73,16 +73,17 @@ export function RenderBlockObject(props: {
|
|
|
73
73
|
draggable={!props.readOnly}
|
|
74
74
|
>
|
|
75
75
|
{props.renderBlock && legacySchemaType ? (
|
|
76
|
-
props.renderBlock
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
<props.renderBlock
|
|
77
|
+
editorElementRef={blockObjectRef}
|
|
78
|
+
focused={focused}
|
|
79
|
+
path={[{_key: props.element._key}]}
|
|
80
|
+
schemaType={legacySchemaType}
|
|
81
|
+
selected={selected}
|
|
82
|
+
type={legacySchemaType}
|
|
83
|
+
value={blockObject}
|
|
84
|
+
>
|
|
85
|
+
<RenderDefaultBlockObject blockObject={blockObject} />
|
|
86
|
+
</props.renderBlock>
|
|
86
87
|
) : (
|
|
87
88
|
<RenderDefaultBlockObject blockObject={blockObject} />
|
|
88
89
|
)}
|
|
@@ -79,17 +79,18 @@ export function RenderInlineObject(props: {
|
|
|
79
79
|
{props.children}
|
|
80
80
|
<span ref={inlineObjectRef} style={{display: 'inline-block'}}>
|
|
81
81
|
{props.renderChild && block && legacySchemaType ? (
|
|
82
|
-
props.renderChild
|
|
83
|
-
annotations
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
<props.renderChild
|
|
83
|
+
annotations={[]}
|
|
84
|
+
editorElementRef={inlineObjectRef}
|
|
85
|
+
selected={selected}
|
|
86
|
+
focused={focused}
|
|
87
|
+
path={[{_key: block._key}, 'children', {_key: props.element._key}]}
|
|
88
|
+
schemaType={legacySchemaType}
|
|
89
|
+
value={inlineObject}
|
|
90
|
+
type={legacySchemaType}
|
|
91
|
+
>
|
|
92
|
+
<RenderDefaultInlineObject inlineObject={inlineObject} />
|
|
93
|
+
</props.renderChild>
|
|
93
94
|
) : (
|
|
94
95
|
<RenderDefaultInlineObject inlineObject={inlineObject} />
|
|
95
96
|
)}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
+
import {isTextBlock} from '@portabletext/schema'
|
|
1
2
|
import {useSelector} from '@xstate/react'
|
|
2
|
-
import {
|
|
3
|
+
import {uniq} from 'lodash'
|
|
4
|
+
import {useContext, useMemo, useRef, type ReactElement} from 'react'
|
|
5
|
+
import {useSlateStatic, type RenderLeafProps} from 'slate-react'
|
|
3
6
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
type ReactElement,
|
|
12
|
-
} from 'react'
|
|
13
|
-
import {useSelected, useSlateStatic, type RenderLeafProps} from 'slate-react'
|
|
7
|
+
getFocusSpan,
|
|
8
|
+
isOverlappingSelection,
|
|
9
|
+
isSelectionCollapsed,
|
|
10
|
+
} from '../../selectors'
|
|
14
11
|
import type {
|
|
12
|
+
EditorSelection,
|
|
15
13
|
RenderAnnotationFunction,
|
|
16
14
|
RenderChildFunction,
|
|
17
15
|
RenderDecoratorFunction,
|
|
18
16
|
} from '../../types/editor'
|
|
19
17
|
import {EditorActorContext} from '../editor-actor-context'
|
|
20
|
-
import {
|
|
21
|
-
import {PortableTextEditor} from '../PortableTextEditor'
|
|
18
|
+
import {getEditorSnapshot} from '../editor-selector'
|
|
22
19
|
|
|
23
20
|
export interface RenderSpanProps extends RenderLeafProps {
|
|
24
21
|
children: ReactElement<any>
|
|
@@ -35,10 +32,66 @@ export function RenderSpan(props: RenderSpanProps) {
|
|
|
35
32
|
s.context.getLegacySchema(),
|
|
36
33
|
)
|
|
37
34
|
const spanRef = useRef<HTMLElement>(null)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A span is considered focused if the selection is collapsed and the caret
|
|
38
|
+
* is inside the span.
|
|
39
|
+
*/
|
|
40
|
+
const focused = useSelector(editorActor, (editorActorSnapshot) => {
|
|
41
|
+
const snapshot = getEditorSnapshot({
|
|
42
|
+
editorActorSnapshot,
|
|
43
|
+
slateEditorInstance: slateEditor,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (!snapshot.context.selection) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!isSelectionCollapsed(snapshot)) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const focusedSpan = getFocusSpan(snapshot)
|
|
55
|
+
|
|
56
|
+
if (!focusedSpan) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return focusedSpan.node._key === props.leaf._key
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A span is considered selected if editor selection is overlapping with the
|
|
65
|
+
* span selection points.
|
|
66
|
+
*/
|
|
67
|
+
const selected = useSelector(editorActor, (editorActorSnapshot) => {
|
|
68
|
+
const snapshot = getEditorSnapshot({
|
|
69
|
+
editorActorSnapshot,
|
|
70
|
+
slateEditorInstance: slateEditor,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
if (!snapshot.context.selection) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parent = props.children.props.parent
|
|
78
|
+
const block =
|
|
79
|
+
parent && isTextBlock(snapshot.context, parent) ? parent : undefined
|
|
80
|
+
const spanSelection: EditorSelection = block
|
|
81
|
+
? {
|
|
82
|
+
anchor: {
|
|
83
|
+
path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
|
|
84
|
+
offset: 0,
|
|
85
|
+
},
|
|
86
|
+
focus: {
|
|
87
|
+
path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
|
|
88
|
+
offset: props.leaf.text.length,
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
: null
|
|
92
|
+
|
|
93
|
+
return isOverlappingSelection(spanSelection)(snapshot)
|
|
94
|
+
})
|
|
42
95
|
|
|
43
96
|
const parent = props.children.props.parent
|
|
44
97
|
const block = parent && slateEditor.isTextBlock(parent) ? parent : undefined
|
|
@@ -75,106 +128,6 @@ export function RenderSpan(props: RenderSpanProps) {
|
|
|
75
128
|
return []
|
|
76
129
|
})
|
|
77
130
|
|
|
78
|
-
const shouldTrackSelectionAndFocus =
|
|
79
|
-
annotationMarkDefs.length > 0 && blockSelected
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (!shouldTrackSelectionAndFocus) {
|
|
83
|
-
setFocused(false)
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
sel &&
|
|
91
|
-
isEqual(sel.focus.path, path) &&
|
|
92
|
-
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
93
|
-
) {
|
|
94
|
-
startTransition(() => {
|
|
95
|
-
setFocused(true)
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
}, [shouldTrackSelectionAndFocus, path, portableTextEditor])
|
|
99
|
-
|
|
100
|
-
// Function to check if this leaf is currently inside the user's text selection
|
|
101
|
-
const setSelectedFromRange = useCallback(() => {
|
|
102
|
-
if (!shouldTrackSelectionAndFocus) {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const winSelection = window.getSelection()
|
|
107
|
-
|
|
108
|
-
if (!winSelection) {
|
|
109
|
-
setSelected(false)
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (winSelection && winSelection.rangeCount > 0) {
|
|
114
|
-
const range = winSelection.getRangeAt(0)
|
|
115
|
-
|
|
116
|
-
if (spanRef.current && range.intersectsNode(spanRef.current)) {
|
|
117
|
-
setSelected(true)
|
|
118
|
-
} else {
|
|
119
|
-
setSelected(false)
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
setSelected(false)
|
|
123
|
-
}
|
|
124
|
-
}, [shouldTrackSelectionAndFocus])
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (!shouldTrackSelectionAndFocus) {
|
|
128
|
-
return undefined
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const onBlur = editorActor.on('blurred', () => {
|
|
132
|
-
setFocused(false)
|
|
133
|
-
setSelected(false)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
const onFocus = editorActor.on('focused', () => {
|
|
137
|
-
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
sel &&
|
|
141
|
-
isEqual(sel.focus.path, path) &&
|
|
142
|
-
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
143
|
-
) {
|
|
144
|
-
setFocused(true)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
setSelectedFromRange()
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
const onSelection = editorActor.on('selection', (event) => {
|
|
151
|
-
if (
|
|
152
|
-
event.selection &&
|
|
153
|
-
isEqual(event.selection.focus.path, path) &&
|
|
154
|
-
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
155
|
-
) {
|
|
156
|
-
setFocused(true)
|
|
157
|
-
} else {
|
|
158
|
-
setFocused(false)
|
|
159
|
-
}
|
|
160
|
-
setSelectedFromRange()
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
return () => {
|
|
164
|
-
onBlur.unsubscribe()
|
|
165
|
-
onFocus.unsubscribe()
|
|
166
|
-
onSelection.unsubscribe()
|
|
167
|
-
}
|
|
168
|
-
}, [
|
|
169
|
-
editorActor,
|
|
170
|
-
path,
|
|
171
|
-
portableTextEditor,
|
|
172
|
-
setSelectedFromRange,
|
|
173
|
-
shouldTrackSelectionAndFocus,
|
|
174
|
-
])
|
|
175
|
-
|
|
176
|
-
useEffect(() => setSelectedFromRange(), [setSelectedFromRange])
|
|
177
|
-
|
|
178
131
|
let children = props.children
|
|
179
132
|
|
|
180
133
|
/**
|
|
@@ -186,16 +139,19 @@ export function RenderSpan(props: RenderSpanProps) {
|
|
|
186
139
|
)
|
|
187
140
|
|
|
188
141
|
if (path && legacyDecoratorSchemaType && props.renderDecorator) {
|
|
189
|
-
children =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
142
|
+
children = (
|
|
143
|
+
<props.renderDecorator
|
|
144
|
+
editorElementRef={spanRef}
|
|
145
|
+
focused={focused}
|
|
146
|
+
path={path}
|
|
147
|
+
selected={selected}
|
|
148
|
+
schemaType={legacyDecoratorSchemaType}
|
|
149
|
+
value={mark}
|
|
150
|
+
type={legacyDecoratorSchemaType}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</props.renderDecorator>
|
|
154
|
+
)
|
|
199
155
|
}
|
|
200
156
|
}
|
|
201
157
|
|
|
@@ -210,17 +166,18 @@ export function RenderSpan(props: RenderSpanProps) {
|
|
|
210
166
|
if (block && path && props.renderAnnotation) {
|
|
211
167
|
children = (
|
|
212
168
|
<span ref={spanRef}>
|
|
213
|
-
|
|
214
|
-
block
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
169
|
+
<props.renderAnnotation
|
|
170
|
+
block={block}
|
|
171
|
+
editorElementRef={spanRef}
|
|
172
|
+
focused={focused}
|
|
173
|
+
path={path}
|
|
174
|
+
selected={selected}
|
|
175
|
+
schemaType={legacyAnnotationSchemaType}
|
|
176
|
+
value={annotationMarkDef}
|
|
177
|
+
type={legacyAnnotationSchemaType}
|
|
178
|
+
>
|
|
179
|
+
{children}
|
|
180
|
+
</props.renderAnnotation>
|
|
224
181
|
</span>
|
|
225
182
|
)
|
|
226
183
|
} else {
|
|
@@ -238,17 +195,20 @@ export function RenderSpan(props: RenderSpanProps) {
|
|
|
238
195
|
) // Ensure object equality
|
|
239
196
|
|
|
240
197
|
if (child) {
|
|
241
|
-
children =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
198
|
+
children = (
|
|
199
|
+
<props.renderChild
|
|
200
|
+
annotations={annotationMarkDefs}
|
|
201
|
+
editorElementRef={spanRef}
|
|
202
|
+
focused={focused}
|
|
203
|
+
path={path}
|
|
204
|
+
schemaType={legacySchema.span}
|
|
205
|
+
selected={selected}
|
|
206
|
+
value={child}
|
|
207
|
+
type={legacySchema.span}
|
|
208
|
+
>
|
|
209
|
+
{children}
|
|
210
|
+
</props.renderChild>
|
|
211
|
+
)
|
|
252
212
|
}
|
|
253
213
|
}
|
|
254
214
|
|
|
@@ -60,16 +60,19 @@ export function RenderTextBlock(props: {
|
|
|
60
60
|
: undefined
|
|
61
61
|
|
|
62
62
|
if (legacyStyleSchemaType) {
|
|
63
|
-
children =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
children = (
|
|
64
|
+
<props.renderStyle
|
|
65
|
+
block={props.textBlock}
|
|
66
|
+
editorElementRef={blockRef}
|
|
67
|
+
focused={focused}
|
|
68
|
+
path={[{_key: props.textBlock._key}]}
|
|
69
|
+
schemaType={legacyStyleSchemaType}
|
|
70
|
+
selected={selected}
|
|
71
|
+
value={props.textBlock.style}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</props.renderStyle>
|
|
75
|
+
)
|
|
73
76
|
} else {
|
|
74
77
|
console.error(
|
|
75
78
|
`Unable to find Schema type for text block style ${props.textBlock.style}`,
|
|
@@ -83,17 +86,20 @@ export function RenderTextBlock(props: {
|
|
|
83
86
|
)
|
|
84
87
|
|
|
85
88
|
if (legacyListItemSchemaType) {
|
|
86
|
-
children =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
children = (
|
|
90
|
+
<props.renderListItem
|
|
91
|
+
block={props.textBlock}
|
|
92
|
+
editorElementRef={blockRef}
|
|
93
|
+
focused={focused}
|
|
94
|
+
level={props.textBlock.level ?? 1}
|
|
95
|
+
path={[{_key: props.textBlock._key}]}
|
|
96
|
+
selected={selected}
|
|
97
|
+
value={props.textBlock.listItem}
|
|
98
|
+
schemaType={legacyListItemSchemaType}
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</props.renderListItem>
|
|
102
|
+
)
|
|
97
103
|
} else {
|
|
98
104
|
console.error(
|
|
99
105
|
`Unable to find Schema type for text block list item ${props.textBlock.listItem}`,
|
|
@@ -145,21 +151,24 @@ export function RenderTextBlock(props: {
|
|
|
145
151
|
>
|
|
146
152
|
{dragPositionBlock === 'start' ? <DropIndicator /> : null}
|
|
147
153
|
<div ref={blockRef}>
|
|
148
|
-
{props.renderBlock
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
154
|
+
{props.renderBlock ? (
|
|
155
|
+
<props.renderBlock
|
|
156
|
+
editorElementRef={blockRef}
|
|
157
|
+
focused={focused}
|
|
158
|
+
level={props.textBlock.level}
|
|
159
|
+
listItem={props.textBlock.listItem}
|
|
160
|
+
path={[{_key: props.textBlock._key}]}
|
|
161
|
+
selected={selected}
|
|
162
|
+
schemaType={props.legacySchema.block}
|
|
163
|
+
style={props.textBlock.style}
|
|
164
|
+
type={props.legacySchema.block}
|
|
165
|
+
value={props.textBlock}
|
|
166
|
+
>
|
|
167
|
+
{children}
|
|
168
|
+
</props.renderBlock>
|
|
169
|
+
) : (
|
|
170
|
+
children
|
|
171
|
+
)}
|
|
163
172
|
</div>
|
|
164
173
|
{dragPositionBlock === 'end' ? <DropIndicator /> : null}
|
|
165
174
|
</div>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type React from 'react'
|
|
2
|
-
import {useEffect} from 'react'
|
|
2
|
+
import {useEffect, useState} from 'react'
|
|
3
3
|
import {Slate} from 'slate-react'
|
|
4
4
|
import type {EditorConfig} from '../editor'
|
|
5
5
|
import {stopActor} from '../internal-utils/stop-actor'
|
|
6
|
-
import useConstant from '../internal-utils/use-constant'
|
|
7
6
|
import {createInternalEditor} from './create-editor'
|
|
8
7
|
import {EditorActorContext} from './editor-actor-context'
|
|
9
8
|
import {EditorContext} from './editor-context'
|
|
@@ -42,7 +41,7 @@ export type EditorProviderProps = {
|
|
|
42
41
|
* @group Components
|
|
43
42
|
*/
|
|
44
43
|
export function EditorProvider(props: EditorProviderProps) {
|
|
45
|
-
const {internalEditor, portableTextEditor} =
|
|
44
|
+
const [{internalEditor, portableTextEditor}] = useState(() => {
|
|
46
45
|
const internalEditor = createInternalEditor(props.initialConfig)
|
|
47
46
|
const portableTextEditor = new PortableTextEditor({
|
|
48
47
|
editor: internalEditor.editor,
|
|
@@ -170,6 +170,122 @@ describe(isOverlappingSelection.name, () => {
|
|
|
170
170
|
).toBe(false)
|
|
171
171
|
})
|
|
172
172
|
|
|
173
|
+
test('partial span selection', () => {
|
|
174
|
+
expect(
|
|
175
|
+
isOverlappingSelection({
|
|
176
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
|
|
177
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
178
|
+
})(
|
|
179
|
+
snapshot({
|
|
180
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
|
|
181
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
182
|
+
}),
|
|
183
|
+
),
|
|
184
|
+
).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('expanded selection at the end edge of span', () => {
|
|
188
|
+
expect(
|
|
189
|
+
isOverlappingSelection({
|
|
190
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
|
|
191
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
192
|
+
})(
|
|
193
|
+
snapshot({
|
|
194
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
195
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
|
|
196
|
+
}),
|
|
197
|
+
),
|
|
198
|
+
).toBe(false)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('collapsed selection at the end edge of span', () => {
|
|
202
|
+
expect(
|
|
203
|
+
isOverlappingSelection({
|
|
204
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
|
|
205
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
206
|
+
})(
|
|
207
|
+
snapshot({
|
|
208
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
209
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
210
|
+
}),
|
|
211
|
+
),
|
|
212
|
+
).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('expanded selection at the start edge of span', () => {
|
|
216
|
+
expect(
|
|
217
|
+
isOverlappingSelection({
|
|
218
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
219
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 3},
|
|
220
|
+
})(
|
|
221
|
+
snapshot({
|
|
222
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
|
|
223
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
224
|
+
}),
|
|
225
|
+
),
|
|
226
|
+
).toBe(false)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('collapsed selection at the start edge of span', () => {
|
|
230
|
+
expect(
|
|
231
|
+
isOverlappingSelection({
|
|
232
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
233
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 3},
|
|
234
|
+
})(
|
|
235
|
+
snapshot({
|
|
236
|
+
anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
237
|
+
focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
240
|
+
).toBe(true)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('selecting entire span', () => {
|
|
244
|
+
const blockKey = defaultKeyGenerator()
|
|
245
|
+
const fooKey = defaultKeyGenerator()
|
|
246
|
+
const barKey = defaultKeyGenerator()
|
|
247
|
+
const bazKey = defaultKeyGenerator()
|
|
248
|
+
|
|
249
|
+
expect(
|
|
250
|
+
isOverlappingSelection({
|
|
251
|
+
anchor: {
|
|
252
|
+
path: [{_key: blockKey}, 'children', {_key: barKey}],
|
|
253
|
+
offset: 0,
|
|
254
|
+
},
|
|
255
|
+
focus: {
|
|
256
|
+
path: [{_key: blockKey}, 'children', {_key: barKey}],
|
|
257
|
+
offset: 3,
|
|
258
|
+
},
|
|
259
|
+
})(
|
|
260
|
+
createTestSnapshot({
|
|
261
|
+
context: {
|
|
262
|
+
value: [
|
|
263
|
+
{
|
|
264
|
+
_key: blockKey,
|
|
265
|
+
_type: 'block',
|
|
266
|
+
children: [
|
|
267
|
+
{_key: fooKey, _type: 'span', text: 'foo'},
|
|
268
|
+
{_key: barKey, _type: 'span', text: 'bar', marks: ['strong']},
|
|
269
|
+
{_key: bazKey, _type: 'span', text: 'baz'},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
selection: {
|
|
274
|
+
anchor: {
|
|
275
|
+
path: [{_key: blockKey}, 'children', {_key: barKey}],
|
|
276
|
+
offset: 0,
|
|
277
|
+
},
|
|
278
|
+
focus: {
|
|
279
|
+
path: [{_key: blockKey}, 'children', {_key: barKey}],
|
|
280
|
+
offset: 3,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
),
|
|
286
|
+
).toBe(true)
|
|
287
|
+
})
|
|
288
|
+
|
|
173
289
|
test('unknown block', () => {
|
|
174
290
|
const unknownBlockKey = defaultKeyGenerator()
|
|
175
291
|
|