@portabletext/editor 2.0.0 → 2.1.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-dts/behavior.types.action.d.cts +9 -9
- 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/lib/plugins/index.d.cts +3 -3
- package/package.json +8 -8
- 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/lib/plugins/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react12 from "react";
|
|
3
3
|
import React from "react";
|
|
4
4
|
/**
|
|
5
5
|
* @beta
|
|
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
|
|
|
181
181
|
*/
|
|
182
182
|
declare function MarkdownPlugin(props: {
|
|
183
183
|
config: MarkdownPluginConfig;
|
|
184
|
-
}):
|
|
184
|
+
}): react12.JSX.Element;
|
|
185
185
|
/**
|
|
186
186
|
* @beta
|
|
187
187
|
* Restrict the editor to one line. The plugin takes care of blocking
|
|
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
|
|
|
192
192
|
*
|
|
193
193
|
* @deprecated Install the plugin from `@portabletext/plugin-one-line`
|
|
194
194
|
*/
|
|
195
|
-
declare function OneLinePlugin():
|
|
195
|
+
declare function OneLinePlugin(): react12.JSX.Element;
|
|
196
196
|
export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -80,16 +80,16 @@
|
|
|
80
80
|
"slate-react": "0.117.4",
|
|
81
81
|
"use-effect-event": "^2.0.3",
|
|
82
82
|
"xstate": "^5.20.1",
|
|
83
|
-
"@portabletext/patches": "1.1.6",
|
|
84
83
|
"@portabletext/keyboard-shortcuts": "1.1.1",
|
|
85
|
-
"@portabletext/
|
|
84
|
+
"@portabletext/patches": "1.1.6",
|
|
85
|
+
"@portabletext/block-tools": "2.0.1"
|
|
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",
|
|
@@ -108,15 +108,15 @@
|
|
|
108
108
|
"react": "^19.1.0",
|
|
109
109
|
"react-dom": "^19.1.0",
|
|
110
110
|
"rxjs": "^7.8.2",
|
|
111
|
-
"typescript": "5.
|
|
111
|
+
"typescript": "5.9.2",
|
|
112
112
|
"vite": "^7.0.3",
|
|
113
113
|
"vitest": "^3.2.4",
|
|
114
114
|
"vitest-browser-react": "^1.0.0",
|
|
115
115
|
"racejar": "1.2.10"
|
|
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
|
+
}
|