@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.
@@ -1,5 +1,5 @@
1
1
  import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.cjs";
2
- import * as react22 from "react";
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
- }): react22.JSX.Element;
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(): react22.JSX.Element;
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.0.0",
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/block-tools": "2.0.0"
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.1",
92
- "@sanity/types": "^4.0.1",
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.8.3",
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.1",
119
- "@sanity/types": "^4.0.1",
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 (!selectionCollapsed || !focusTextBlock || !focusSpan) {
17
+ if (!focusTextBlock) {
19
18
  return false
20
19
  }
21
20
 
22
- const atTheBeginningOfBLock =
23
- focusTextBlock.node.children[0]._key === focusSpan.node._key &&
24
- snapshot.context.selection?.focus.offset === 0
21
+ if (focusTextBlock.node.level !== 1) {
22
+ return false
23
+ }
25
24
 
26
- if (atTheBeginningOfBLock && focusTextBlock.node.level === 1) {
27
- return {focusTextBlock}
25
+ if (
26
+ !isAtTheBeginningOfBlock({
27
+ context: snapshot.context,
28
+ block: focusTextBlock.node,
29
+ })
30
+ ) {
31
+ return false
28
32
  }
29
33
 
30
- return false
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
+ }