@portabletext/editor 2.0.0 → 2.1.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/index.cjs +22 -7
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +22 -7
- package/lib/index.js.map +1 -1
- package/package.json +18 -18
- package/src/behaviors/behavior.core.lists.ts +22 -9
- package/src/editor/__tests__/RangeDecorations.test.tsx +60 -0
- package/src/editor/range-decorations-machine.ts +2 -0
- package/src/utils/util.at-the-beginning-of-block.ts +32 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -78,26 +78,26 @@
|
|
|
78
78
|
"slate": "0.117.2",
|
|
79
79
|
"slate-dom": "^0.117.4",
|
|
80
80
|
"slate-react": "0.117.4",
|
|
81
|
-
"use-effect-event": "^
|
|
81
|
+
"use-effect-event": "^1.0.2",
|
|
82
82
|
"xstate": "^5.20.1",
|
|
83
|
-
"@portabletext/
|
|
83
|
+
"@portabletext/block-tools": "2.0.2",
|
|
84
84
|
"@portabletext/keyboard-shortcuts": "1.1.1",
|
|
85
|
-
"@portabletext/
|
|
85
|
+
"@portabletext/patches": "1.1.6"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@portabletext/toolkit": "^2.0.17",
|
|
89
89
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
90
90
|
"@sanity/pkg-utils": "^7.9.6",
|
|
91
|
-
"@sanity/schema": "^4.0
|
|
92
|
-
"@sanity/types": "^4.0
|
|
91
|
+
"@sanity/schema": "^4.2.0",
|
|
92
|
+
"@sanity/types": "^4.2.0",
|
|
93
93
|
"@testing-library/react": "^16.3.0",
|
|
94
94
|
"@types/debug": "^4.1.12",
|
|
95
95
|
"@types/lodash": "^4.17.16",
|
|
96
96
|
"@types/lodash.startcase": "^4.4.9",
|
|
97
|
-
"@types/react": "^19.1.
|
|
98
|
-
"@types/react-dom": "^19.1.
|
|
99
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
100
|
-
"@typescript-eslint/parser": "^8.
|
|
97
|
+
"@types/react": "^19.1.9",
|
|
98
|
+
"@types/react-dom": "^19.1.7",
|
|
99
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
100
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
101
101
|
"@vitejs/plugin-react": "^4.6.0",
|
|
102
102
|
"@vitest/browser": "^3.2.4",
|
|
103
103
|
"@vitest/coverage-istanbul": "^3.2.4",
|
|
@@ -105,18 +105,18 @@
|
|
|
105
105
|
"eslint": "8.57.1",
|
|
106
106
|
"eslint-plugin-react-hooks": "0.0.0-experimental-dffacc7b-20250717",
|
|
107
107
|
"jsdom": "^26.0.0",
|
|
108
|
-
"react": "^19.1.
|
|
109
|
-
"react-dom": "^19.1.
|
|
108
|
+
"react": "^19.1.1",
|
|
109
|
+
"react-dom": "^19.1.1",
|
|
110
110
|
"rxjs": "^7.8.2",
|
|
111
|
-
"typescript": "5.
|
|
112
|
-
"vite": "^7.0.
|
|
111
|
+
"typescript": "5.9.2",
|
|
112
|
+
"vite": "^7.0.6",
|
|
113
113
|
"vitest": "^3.2.4",
|
|
114
|
-
"vitest-browser-react": "^1.0.
|
|
115
|
-
"racejar": "1.2.
|
|
114
|
+
"vitest-browser-react": "^1.0.1",
|
|
115
|
+
"racejar": "1.2.11"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
|
-
"@sanity/schema": "^4.0
|
|
119
|
-
"@sanity/types": "^4.0
|
|
118
|
+
"@sanity/schema": "^4.2.0",
|
|
119
|
+
"@sanity/types": "^4.2.0",
|
|
120
120
|
"react": "^18.3 || ^19",
|
|
121
121
|
"rxjs": "^7.8.2"
|
|
122
122
|
},
|
|
@@ -2,6 +2,7 @@ import {isListBlock, isTextBlock} from '../internal-utils/parse-blocks'
|
|
|
2
2
|
import {defaultKeyboardShortcuts} from '../keyboard-shortcuts/default-keyboard-shortcuts'
|
|
3
3
|
import * as selectors from '../selectors'
|
|
4
4
|
import {getBlockEndPoint} from '../utils'
|
|
5
|
+
import {isAtTheBeginningOfBlock} from '../utils/util.at-the-beginning-of-block'
|
|
5
6
|
import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
|
|
6
7
|
import {raise} from './behavior.types.action'
|
|
7
8
|
import {defineBehavior} from './behavior.types.behavior'
|
|
@@ -11,23 +12,26 @@ const MAX_LIST_LEVEL = 10
|
|
|
11
12
|
const clearListOnBackspace = defineBehavior({
|
|
12
13
|
on: 'delete.backward',
|
|
13
14
|
guard: ({snapshot}) => {
|
|
14
|
-
const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
|
|
15
15
|
const focusTextBlock = selectors.getFocusTextBlock(snapshot)
|
|
16
|
-
const focusSpan = selectors.getFocusSpan(snapshot)
|
|
17
16
|
|
|
18
|
-
if (!
|
|
17
|
+
if (!focusTextBlock) {
|
|
19
18
|
return false
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if (focusTextBlock.node.level !== 1) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
25
24
|
|
|
26
|
-
if (
|
|
27
|
-
|
|
25
|
+
if (
|
|
26
|
+
!isAtTheBeginningOfBlock({
|
|
27
|
+
context: snapshot.context,
|
|
28
|
+
block: focusTextBlock.node,
|
|
29
|
+
})
|
|
30
|
+
) {
|
|
31
|
+
return false
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
return
|
|
34
|
+
return {focusTextBlock}
|
|
31
35
|
},
|
|
32
36
|
actions: [
|
|
33
37
|
(_, {focusTextBlock}) => [
|
|
@@ -132,6 +136,15 @@ const mergeTextIntoListOnBackspace = defineBehavior({
|
|
|
132
136
|
return false
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
if (
|
|
140
|
+
!isAtTheBeginningOfBlock({
|
|
141
|
+
context: snapshot.context,
|
|
142
|
+
block: focusTextBlock.node,
|
|
143
|
+
})
|
|
144
|
+
) {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
if (!isListBlock(snapshot.context, previousBlock.node)) {
|
|
136
149
|
return false
|
|
137
150
|
}
|
|
@@ -33,6 +33,7 @@ describe('RangeDecorations', () => {
|
|
|
33
33
|
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
34
34
|
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
35
35
|
},
|
|
36
|
+
payload: {id: 'a'},
|
|
36
37
|
},
|
|
37
38
|
]
|
|
38
39
|
|
|
@@ -85,6 +86,7 @@ describe('RangeDecorations', () => {
|
|
|
85
86
|
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
86
87
|
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
87
88
|
},
|
|
89
|
+
payload: {id: 'a'},
|
|
88
90
|
},
|
|
89
91
|
]
|
|
90
92
|
rerender(
|
|
@@ -111,6 +113,7 @@ describe('RangeDecorations', () => {
|
|
|
111
113
|
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
112
114
|
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 4},
|
|
113
115
|
},
|
|
116
|
+
payload: {id: 'a'},
|
|
114
117
|
},
|
|
115
118
|
]
|
|
116
119
|
rerender(
|
|
@@ -138,6 +141,7 @@ describe('RangeDecorations', () => {
|
|
|
138
141
|
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
139
142
|
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
140
143
|
},
|
|
144
|
+
payload: {id: 'a'},
|
|
141
145
|
},
|
|
142
146
|
]
|
|
143
147
|
rerender(
|
|
@@ -156,5 +160,61 @@ describe('RangeDecorations', () => {
|
|
|
156
160
|
'updated-with-different',
|
|
157
161
|
])
|
|
158
162
|
})
|
|
163
|
+
|
|
164
|
+
// Update the range decorations with a new payload
|
|
165
|
+
rangeDecorations = [
|
|
166
|
+
{
|
|
167
|
+
component: RangeDecorationTestComponent,
|
|
168
|
+
selection: {
|
|
169
|
+
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
170
|
+
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
171
|
+
},
|
|
172
|
+
payload: {id: 'b'},
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
rerender(
|
|
176
|
+
<PortableTextEditorTester
|
|
177
|
+
keyGenerator={createTestKeyGenerator()}
|
|
178
|
+
onChange={onChange}
|
|
179
|
+
rangeDecorations={rangeDecorations}
|
|
180
|
+
ref={editorRef}
|
|
181
|
+
schemaType={schemaType}
|
|
182
|
+
value={value}
|
|
183
|
+
/>,
|
|
184
|
+
)
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect([
|
|
187
|
+
rangeDecorationIteration,
|
|
188
|
+
'updated-with-different-payload',
|
|
189
|
+
]).toEqual([4, 'updated-with-different-payload'])
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Update the range decorations with a new payload again
|
|
193
|
+
rangeDecorations = [
|
|
194
|
+
{
|
|
195
|
+
component: RangeDecorationTestComponent,
|
|
196
|
+
selection: {
|
|
197
|
+
anchor: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 0},
|
|
198
|
+
focus: {path: [{_key: '123'}, 'children', {_key: '567'}], offset: 2},
|
|
199
|
+
},
|
|
200
|
+
payload: {id: 'c'},
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
rerender(
|
|
204
|
+
<PortableTextEditorTester
|
|
205
|
+
keyGenerator={createTestKeyGenerator()}
|
|
206
|
+
onChange={onChange}
|
|
207
|
+
rangeDecorations={rangeDecorations}
|
|
208
|
+
ref={editorRef}
|
|
209
|
+
schemaType={schemaType}
|
|
210
|
+
value={value}
|
|
211
|
+
/>,
|
|
212
|
+
)
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
expect([
|
|
215
|
+
rangeDecorationIteration,
|
|
216
|
+
'updated-with-different-payload',
|
|
217
|
+
]).toEqual([5, 'updated-with-different-payload'])
|
|
218
|
+
})
|
|
159
219
|
})
|
|
160
220
|
})
|
|
@@ -254,6 +254,7 @@ export const rangeDecorationsMachine = setup({
|
|
|
254
254
|
(decoratedRange) => ({
|
|
255
255
|
anchor: decoratedRange.rangeDecoration.selection?.anchor,
|
|
256
256
|
focus: decoratedRange.rangeDecoration.selection?.focus,
|
|
257
|
+
payload: decoratedRange.rangeDecoration.payload,
|
|
257
258
|
}),
|
|
258
259
|
)
|
|
259
260
|
|
|
@@ -261,6 +262,7 @@ export const rangeDecorationsMachine = setup({
|
|
|
261
262
|
(rangeDecoration) => ({
|
|
262
263
|
anchor: rangeDecoration.selection?.anchor,
|
|
263
264
|
focus: rangeDecoration.selection?.focus,
|
|
265
|
+
payload: rangeDecoration.payload,
|
|
264
266
|
}),
|
|
265
267
|
)
|
|
266
268
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
3
|
+
import {isTextBlock} from '../internal-utils/parse-blocks'
|
|
4
|
+
import {getChildKeyFromSelectionPoint} from '../selection/selection-point'
|
|
5
|
+
import {isSelectionCollapsed} from './util.is-selection-collapsed'
|
|
6
|
+
|
|
7
|
+
export function isAtTheBeginningOfBlock({
|
|
8
|
+
context,
|
|
9
|
+
block,
|
|
10
|
+
}: {
|
|
11
|
+
context: EditorContext
|
|
12
|
+
block: PortableTextBlock
|
|
13
|
+
}) {
|
|
14
|
+
if (!isTextBlock(context, block)) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!context.selection) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!isSelectionCollapsed(context.selection)) {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const focusSpanKey = getChildKeyFromSelectionPoint(context.selection.focus)
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
focusSpanKey === block.children[0]._key &&
|
|
30
|
+
context.selection.focus.offset === 0
|
|
31
|
+
)
|
|
32
|
+
}
|