@portabletext/editor 1.11.2 → 1.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -49,7 +49,7 @@
49
49
  "is-hotkey-esm": "^1.0.0",
50
50
  "lodash": "^4.17.21",
51
51
  "lodash.startcase": "^4.4.0",
52
- "react-compiler-runtime": "19.0.0-beta-0dec889-20241115",
52
+ "react-compiler-runtime": "19.0.0-beta-df7b47d-20241124",
53
53
  "slate": "0.110.2",
54
54
  "slate-dom": "^0.111.0",
55
55
  "slate-react": "0.111.0",
@@ -74,11 +74,12 @@
74
74
  "@typescript-eslint/parser": "^8.15.0",
75
75
  "@vitejs/plugin-react": "^4.3.3",
76
76
  "@vitest/browser": "^2.1.5",
77
- "babel-plugin-react-compiler": "19.0.0-beta-0dec889-20241115",
77
+ "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
78
78
  "eslint": "8.57.1",
79
- "eslint-plugin-react-compiler": "19.0.0-beta-0dec889-20241115",
79
+ "eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
80
80
  "eslint-plugin-react-hooks": "^5.0.0",
81
81
  "jsdom": "^25.0.1",
82
+ "racejar": "1.0.0",
82
83
  "react": "^18.3.1",
83
84
  "react-dom": "^18.3.1",
84
85
  "rxjs": "^7.8.1",
@@ -86,8 +87,7 @@
86
87
  "typescript": "5.6.3",
87
88
  "vite": "^5.4.11",
88
89
  "vitest": "^2.1.5",
89
- "vitest-browser-react": "^0.0.3",
90
- "@sanity/gherkin-driver": "^0.0.1"
90
+ "vitest-browser-react": "^0.0.3"
91
91
  },
92
92
  "peerDependencies": {
93
93
  "@sanity/block-tools": "^3.64.3",
@@ -0,0 +1,61 @@
1
+ import {Editor, Transforms, type Descendant} from 'slate'
2
+ import type {
3
+ PortableTextMemberSchemaTypes,
4
+ PortableTextSlateEditor,
5
+ } from '../../types/editor'
6
+ import {isEqualToEmptyEditor} from '../../utils/values'
7
+
8
+ export function insertBlock({
9
+ block,
10
+ placement,
11
+ editor,
12
+ schema,
13
+ }: {
14
+ block: Descendant
15
+ placement: 'auto' | 'after'
16
+ editor: PortableTextSlateEditor
17
+ schema: PortableTextMemberSchemaTypes
18
+ }) {
19
+ if (!editor.selection) {
20
+ const lastBlock = Array.from(
21
+ Editor.nodes(editor, {
22
+ match: (n) => !Editor.isEditor(n),
23
+ at: [],
24
+ reverse: true,
25
+ }),
26
+ )[0]
27
+
28
+ // If there is no selection, let's just insert the new block at the
29
+ // end of the document
30
+ Editor.insertNode(editor, block)
31
+
32
+ if (lastBlock && isEqualToEmptyEditor([lastBlock[0]], schema)) {
33
+ // And if the last block was an empty text block, let's remove
34
+ // that too
35
+ Transforms.removeNodes(editor, {at: lastBlock[1]})
36
+ }
37
+ } else {
38
+ const [focusBlock, focusBlockPath] = Array.from(
39
+ Editor.nodes(editor, {
40
+ at: editor.selection.focus.path.slice(0, 1),
41
+ match: (n) => !Editor.isEditor(n),
42
+ }),
43
+ )[0] ?? [undefined, undefined]
44
+
45
+ if (placement === 'after') {
46
+ const nextPath = [focusBlockPath[0] + 1]
47
+
48
+ Transforms.insertNodes(editor, block, {at: nextPath})
49
+ Transforms.select(editor, {
50
+ anchor: {path: [nextPath[0], 0], offset: 0},
51
+ focus: {path: [nextPath[0], 0], offset: 0},
52
+ })
53
+ } else {
54
+ Editor.insertNode(editor, block)
55
+ }
56
+
57
+ if (focusBlock && isEqualToEmptyEditor([focusBlock], schema)) {
58
+ Transforms.removeNodes(editor, {at: focusBlockPath})
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,25 @@
1
+ import {toSlateValue} from '../../utils/values'
2
+ import {insertBlock} from './behavior.action-utils.insert-block'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const insertBlockObjectActionImplementation: BehaviorActionImplementation<
6
+ 'insert block object'
7
+ > = ({context, action}) => {
8
+ const block = toSlateValue(
9
+ [
10
+ {
11
+ _key: context.keyGenerator(),
12
+ _type: action.blockObject.name,
13
+ ...(action.blockObject.value ? action.blockObject.value : {}),
14
+ },
15
+ ],
16
+ {schemaTypes: context.schema},
17
+ )[0]
18
+
19
+ insertBlock({
20
+ block,
21
+ placement: action.placement,
22
+ editor: action.editor,
23
+ schema: context.schema,
24
+ })
25
+ }
@@ -1,16 +1,11 @@
1
- import {
2
- deleteBackward,
3
- deleteForward,
4
- Editor,
5
- insertText,
6
- Transforms,
7
- } from 'slate'
1
+ import {deleteBackward, deleteForward, insertText, Transforms} from 'slate'
8
2
  import {ReactEditor} from 'slate-react'
9
3
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
10
4
  import {toSlateRange} from '../../utils/ranges'
5
+ import {fromSlateValue, toSlateValue} from '../../utils/values'
6
+ import {KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
11
7
  import {
12
8
  addAnnotationActionImplementation,
13
- insertBlockObjectActionImplementation,
14
9
  removeAnnotationActionImplementation,
15
10
  toggleAnnotationActionImplementation,
16
11
  } from '../plugins/createWithEditableAPI'
@@ -19,6 +14,8 @@ import {
19
14
  removeDecoratorActionImplementation,
20
15
  toggleDecoratorActionImplementation,
21
16
  } from '../plugins/createWithPortableTextMarkModel'
17
+ import {insertBlock} from './behavior.action-utils.insert-block'
18
+ import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
22
19
  import {
23
20
  insertBreakActionImplementation,
24
21
  insertSoftBreakActionImplementation,
@@ -29,6 +26,7 @@ import type {
29
26
  BehaviorEvent,
30
27
  PickFromUnion,
31
28
  } from './behavior.types'
29
+ import {blockOffsetToSpanSelectionPoint} from './behavior.utils.block-offset'
32
30
 
33
31
  export type BehaviorActionContext = {
34
32
  keyGenerator: () => string
@@ -94,25 +92,61 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
94
92
  'delete forward': ({action}) => {
95
93
  deleteForward(action.editor, action.unit)
96
94
  },
97
- 'delete': ({action}) => {
98
- const location = toSlateRange(action.selection, action.editor)
95
+ 'delete block': ({action}) => {
96
+ const range = toSlateRange(
97
+ {
98
+ anchor: {path: action.blockPath, offset: 0},
99
+ focus: {path: action.blockPath, offset: 0},
100
+ },
101
+ action.editor,
102
+ )
99
103
 
100
- if (!location) {
101
- console.error(
102
- `Could not find Slate location from selection ${action.selection}`,
103
- )
104
+ if (!range) {
105
+ console.error('Unable to find Slate range from selection points')
104
106
  return
105
107
  }
106
108
 
107
- if (location.anchor.path.length === 1 && location.focus.path.length === 1) {
108
- Transforms.removeNodes(action.editor, {
109
- at: location,
110
- })
111
- } else {
112
- Transforms.delete(action.editor, {
113
- at: location,
114
- })
109
+ Transforms.removeNodes(action.editor, {
110
+ at: range,
111
+ })
112
+ },
113
+ 'delete text': ({context, action}) => {
114
+ const value = fromSlateValue(
115
+ action.editor.children,
116
+ context.schema.block.name,
117
+ KEY_TO_VALUE_ELEMENT.get(action.editor),
118
+ )
119
+
120
+ const anchor = blockOffsetToSpanSelectionPoint({
121
+ value,
122
+ blockOffset: action.anchor,
123
+ })
124
+ const focus = blockOffsetToSpanSelectionPoint({
125
+ value,
126
+ blockOffset: action.focus,
127
+ })
128
+
129
+ if (!anchor || !focus) {
130
+ console.error('Unable to find anchor or focus selection point')
131
+ return
115
132
  }
133
+
134
+ const range = toSlateRange(
135
+ {
136
+ anchor,
137
+ focus,
138
+ },
139
+ action.editor,
140
+ )
141
+
142
+ if (!range) {
143
+ console.error('Unable to find Slate range from selection points')
144
+ return
145
+ }
146
+
147
+ Transforms.delete(action.editor, {
148
+ at: range,
149
+ })
116
150
  },
117
151
  'insert block object': insertBlockObjectActionImplementation,
118
152
  'insert break': insertBreakActionImplementation,
@@ -122,18 +156,33 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
122
156
  insertText(action.editor, action.text)
123
157
  },
124
158
  'insert text block': ({context, action}) => {
125
- Editor.insertNode(action.editor, {
126
- _key: context.keyGenerator(),
127
- _type: context.schema.block.name,
128
- style: context.schema.styles[0].value ?? 'normal',
129
- markDefs: [],
130
- children: [
159
+ const block = toSlateValue(
160
+ [
131
161
  {
132
162
  _key: context.keyGenerator(),
133
- _type: 'span',
134
- text: '',
163
+ _type: context.schema.block.name,
164
+ style: context.schema.styles[0].value ?? 'normal',
165
+ markDefs: [],
166
+ children: action.textBlock?.children?.map((child) => ({
167
+ ...child,
168
+ _key: context.keyGenerator(),
169
+ })) ?? [
170
+ {
171
+ _type: context.schema.span.name,
172
+ _key: context.keyGenerator(),
173
+ text: '',
174
+ },
175
+ ],
135
176
  },
136
177
  ],
178
+ {schemaTypes: context.schema},
179
+ )[0]
180
+
181
+ insertBlock({
182
+ block,
183
+ editor: action.editor,
184
+ schema: context.schema,
185
+ placement: action.placement,
137
186
  })
138
187
  },
139
188
  'effect': ({action}) => {
@@ -169,8 +218,15 @@ export function performAction({
169
218
  action: BehaviorAction
170
219
  }) {
171
220
  switch (action.type) {
172
- case 'delete': {
173
- behaviorActionImplementations.delete({
221
+ case 'delete block': {
222
+ behaviorActionImplementations['delete block']({
223
+ context,
224
+ action,
225
+ })
226
+ break
227
+ }
228
+ case 'delete text': {
229
+ behaviorActionImplementations['delete text']({
174
230
  context,
175
231
  action,
176
232
  })
@@ -16,7 +16,7 @@ const breakingBlockObject = defineBehavior({
16
16
 
17
17
  return !!focusBlockObject
18
18
  },
19
- actions: [() => [{type: 'insert text block', decorators: []}]],
19
+ actions: [() => [{type: 'insert text block', placement: 'after'}]],
20
20
  })
21
21
 
22
22
  const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
@@ -42,11 +42,8 @@ const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
42
42
  actions: [
43
43
  (_, {focusTextBlock, previousBlock}) => [
44
44
  {
45
- type: 'delete',
46
- selection: {
47
- anchor: {path: focusTextBlock.path, offset: 0},
48
- focus: {path: focusTextBlock.path, offset: 0},
49
- },
45
+ type: 'delete block',
46
+ blockPath: focusTextBlock.path,
50
47
  },
51
48
  {
52
49
  type: 'select',
@@ -82,11 +79,8 @@ const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
82
79
  actions: [
83
80
  (_, {focusTextBlock, nextBlock}) => [
84
81
  {
85
- type: 'delete',
86
- selection: {
87
- anchor: {path: focusTextBlock.path, offset: 0},
88
- focus: {path: focusTextBlock.path, offset: 0},
89
- },
82
+ type: 'delete block',
83
+ blockPath: focusTextBlock.path,
90
84
  },
91
85
  {
92
86
  type: 'select',