@portabletext/editor 1.53.1 → 1.54.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.
Files changed (66) hide show
  1. package/lib/_chunks-cjs/selection-point.cjs.map +1 -1
  2. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs.map +1 -1
  4. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +1 -1
  5. package/lib/_chunks-es/selection-point.js.map +1 -1
  6. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
  7. package/lib/_chunks-es/selector.is-selection-expanded.js.map +1 -1
  8. package/lib/_chunks-es/util.is-equal-selection-points.js.map +1 -1
  9. package/lib/behaviors/index.cjs.map +1 -1
  10. package/lib/behaviors/index.d.cts +68 -9
  11. package/lib/behaviors/index.d.ts +68 -9
  12. package/lib/behaviors/index.js.map +1 -1
  13. package/lib/index.cjs +529 -289
  14. package/lib/index.cjs.map +1 -1
  15. package/lib/index.d.cts +69 -13
  16. package/lib/index.d.ts +69 -13
  17. package/lib/index.js +533 -293
  18. package/lib/index.js.map +1 -1
  19. package/lib/plugins/index.d.cts +69 -9
  20. package/lib/plugins/index.d.ts +69 -9
  21. package/lib/selectors/index.cjs.map +1 -1
  22. package/lib/selectors/index.d.cts +31 -16
  23. package/lib/selectors/index.d.ts +31 -16
  24. package/lib/selectors/index.js.map +1 -1
  25. package/lib/utils/index.d.cts +27 -5
  26. package/lib/utils/index.d.ts +27 -5
  27. package/package.json +3 -3
  28. package/src/behaviors/behavior.abstract.annotation.ts +51 -0
  29. package/src/behaviors/behavior.abstract.delete.ts +55 -0
  30. package/src/behaviors/behavior.perform-event.ts +6 -15
  31. package/src/behaviors/behavior.types.action.ts +1 -1
  32. package/src/behaviors/behavior.types.event.ts +32 -8
  33. package/src/behaviors/behavior.types.guard.ts +1 -1
  34. package/src/editor/Editable.tsx +3 -0
  35. package/src/editor/PortableTextEditor.tsx +1 -6
  36. package/src/editor/create-editor.ts +5 -0
  37. package/src/{internal-utils/selection-elements.ts → editor/editor-dom.ts} +29 -21
  38. package/src/editor/editor-provider.tsx +1 -6
  39. package/src/editor/hooks/usePortableTextEditorSelection.tsx +6 -46
  40. package/src/editor/plugins/createWithPatches.ts +1 -5
  41. package/src/editor.ts +2 -0
  42. package/src/index.ts +2 -0
  43. package/src/operations/behavior.operation.child.set.ts +103 -0
  44. package/src/operations/behavior.operation.child.unset.ts +89 -0
  45. package/src/operations/behavior.operations.ts +18 -0
  46. package/src/selectors/selector.get-anchor-block.ts +3 -2
  47. package/src/selectors/selector.get-anchor-child.ts +2 -2
  48. package/src/selectors/selector.get-anchor-span.ts +2 -3
  49. package/src/selectors/selector.get-anchor-text-block.ts +3 -2
  50. package/src/selectors/selector.get-focus-child.ts +3 -6
  51. package/src/selectors/selector.get-focus-inline-object.ts +3 -7
  52. package/src/selectors/selector.get-focus-span.ts +3 -3
  53. package/src/selectors/selector.get-next-inline-object.ts +4 -7
  54. package/src/selectors/selector.get-previous-block.ts +2 -2
  55. package/src/selectors/selector.get-previous-inline-object.ts +4 -7
  56. package/src/selectors/selector.get-selected-blocks.ts +2 -3
  57. package/src/selectors/selector.get-selected-spans.test.ts +96 -0
  58. package/src/selectors/selector.get-selected-spans.ts +4 -3
  59. package/src/selectors/selector.get-selected-text-blocks.ts +4 -3
  60. package/src/selectors/selector.is-at-the-end-of-block.ts +3 -2
  61. package/src/selectors/selector.is-at-the-start-of-block.ts +3 -2
  62. package/src/types/block-offset.ts +2 -2
  63. package/src/types/paths.ts +13 -0
  64. package/src/utils/util.block-offset.ts +2 -4
  65. package/src/utils/util.get-block-end-point.ts +3 -2
  66. package/src/utils/util.get-block-start-point.ts +3 -2
@@ -1,10 +1,11 @@
1
- import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
2
2
  import type {EventPosition} from '../internal-utils/event-position'
3
3
  import type {MIMEType} from '../internal-utils/mime-type'
4
4
  import type {OmitFromUnion, PickFromUnion, StrictExtract} from '../type-utils'
5
5
  import type {BlockOffset} from '../types/block-offset'
6
6
  import type {BlockWithOptionalKey} from '../types/block-with-optional-key'
7
7
  import type {EditorSelection} from '../types/editor'
8
+ import type {AnnotationPath, BlockPath, ChildPath} from '../types/paths'
8
9
 
9
10
  /**
10
11
  * @beta
@@ -63,6 +64,8 @@ const syntheticBehaviorEventTypes = [
63
64
  'annotation.remove',
64
65
  'block.set',
65
66
  'block.unset',
67
+ 'child.set',
68
+ 'child.unset',
66
69
  'decorator.add',
67
70
  'decorator.remove',
68
71
  'delete',
@@ -104,12 +107,22 @@ export type SyntheticBehaviorEvent =
104
107
  }
105
108
  | {
106
109
  type: StrictExtract<SyntheticBehaviorEventType, 'block.set'>
107
- at: [KeyedSegment]
110
+ at: BlockPath
108
111
  props: Record<string, unknown>
109
112
  }
110
113
  | {
111
114
  type: StrictExtract<SyntheticBehaviorEventType, 'block.unset'>
112
- at: [KeyedSegment]
115
+ at: BlockPath
116
+ props: Array<string>
117
+ }
118
+ | {
119
+ type: StrictExtract<SyntheticBehaviorEventType, 'child.set'>
120
+ at: ChildPath
121
+ props: {[prop: string]: unknown}
122
+ }
123
+ | {
124
+ type: StrictExtract<SyntheticBehaviorEventType, 'child.unset'>
125
+ at: ChildPath
113
126
  props: Array<string>
114
127
  }
115
128
  | {
@@ -174,8 +187,8 @@ export type SyntheticBehaviorEvent =
174
187
  }
175
188
  | {
176
189
  type: StrictExtract<SyntheticBehaviorEventType, 'move.block'>
177
- at: [KeyedSegment]
178
- to: [KeyedSegment]
190
+ at: BlockPath
191
+ to: BlockPath
179
192
  }
180
193
  | {
181
194
  type: StrictExtract<SyntheticBehaviorEventType, 'move.forward'>
@@ -208,10 +221,12 @@ export function isSyntheticBehaviorEvent(
208
221
  **************************************/
209
222
 
210
223
  const abstractBehaviorEventTypes = [
224
+ 'annotation.set',
211
225
  'annotation.toggle',
212
226
  'decorator.toggle',
213
227
  'delete.backward',
214
228
  'delete.block',
229
+ 'delete.child',
215
230
  'delete.forward',
216
231
  'delete.text',
217
232
  'deserialize',
@@ -240,6 +255,11 @@ export type AbstractBehaviorEventType =
240
255
  (typeof abstractBehaviorEventTypes)[number]
241
256
 
242
257
  type AbstractBehaviorEvent =
258
+ | {
259
+ type: StrictExtract<SyntheticBehaviorEventType, 'annotation.set'>
260
+ at: AnnotationPath
261
+ props: Record<string, unknown>
262
+ }
243
263
  | {
244
264
  type: StrictExtract<SyntheticBehaviorEventType, 'annotation.toggle'>
245
265
  annotation: {
@@ -258,7 +278,11 @@ type AbstractBehaviorEvent =
258
278
  }
259
279
  | {
260
280
  type: StrictExtract<SyntheticBehaviorEventType, 'delete.block'>
261
- at: [KeyedSegment]
281
+ at: BlockPath
282
+ }
283
+ | {
284
+ type: StrictExtract<SyntheticBehaviorEventType, 'delete.child'>
285
+ at: ChildPath
262
286
  }
263
287
  | {
264
288
  type: StrictExtract<SyntheticBehaviorEventType, 'delete.forward'>
@@ -359,11 +383,11 @@ type AbstractBehaviorEvent =
359
383
  }
360
384
  | {
361
385
  type: StrictExtract<SyntheticBehaviorEventType, 'move.block down'>
362
- at: [KeyedSegment]
386
+ at: BlockPath
363
387
  }
364
388
  | {
365
389
  type: StrictExtract<SyntheticBehaviorEventType, 'move.block up'>
366
- at: [KeyedSegment]
390
+ at: BlockPath
367
391
  }
368
392
  | {
369
393
  type: StrictExtract<SyntheticBehaviorEventType, 'select.previous block'>
@@ -1,5 +1,5 @@
1
+ import type {EditorDom} from '../editor/editor-dom'
1
2
  import type {EditorSnapshot} from '../editor/editor-snapshot'
2
- import type {EditorDom} from '../internal-utils/selection-elements'
3
3
 
4
4
  /**
5
5
  * @beta
@@ -543,6 +543,9 @@ export const PortableTextEditable = forwardRef<
543
543
  )
544
544
  ) {
545
545
  Transforms.select(slateEditor, Editor.start(slateEditor, []))
546
+ }
547
+
548
+ if (slateEditor.selection) {
546
549
  slateEditor.onChange()
547
550
  }
548
551
  }
@@ -30,7 +30,6 @@ import {createInternalEditor, type InternalEditor} from './create-editor'
30
30
  import {EditorActorContext} from './editor-actor-context'
31
31
  import type {EditorActor} from './editor-machine'
32
32
  import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
33
- import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
34
33
  import type {MutationActor} from './mutation-machine'
35
34
  import {RelayActorContext} from './relay-actor-context'
36
35
  import type {RelayActor} from './relay-machine'
@@ -288,11 +287,7 @@ export class PortableTextEditor extends Component<
288
287
  initialValue={this.editor._internal.slateEditor.initialValue}
289
288
  >
290
289
  <PortableTextEditorContext.Provider value={this}>
291
- <PortableTextEditorSelectionProvider
292
- editorActor={this.editor._internal.editorActor}
293
- >
294
- {this.props.children}
295
- </PortableTextEditorSelectionProvider>
290
+ {this.props.children}
296
291
  </PortableTextEditorContext.Provider>
297
292
  </Slate>
298
293
  </RelayActorContext.Provider>
@@ -9,6 +9,7 @@ import {corePriority} from '../priority/priority.core'
9
9
  import {createEditorPriority} from '../priority/priority.types'
10
10
  import type {EditableAPI, PortableTextSlateEditor} from '../types/editor'
11
11
  import {createSlateEditor, type SlateEditor} from './create-slate-editor'
12
+ import {createEditorDom} from './editor-dom'
12
13
  import type {EditorActor} from './editor-machine'
13
14
  import {editorMachine} from './editor-machine'
14
15
  import {
@@ -64,6 +65,10 @@ export function createInternalEditor(config: EditorConfig): {
64
65
  })
65
66
 
66
67
  const editor = {
68
+ dom: createEditorDom(
69
+ (event) => editorActor.send(event),
70
+ slateEditor.instance,
71
+ ),
67
72
  getSnapshot: () =>
68
73
  getEditorSnapshot({
69
74
  editorActorSnapshot: editorActor.getSnapshot(),
@@ -2,9 +2,9 @@ import {Editor} from 'slate'
2
2
  import {DOMEditor} from 'slate-dom'
3
3
  import type {EditorSnapshot} from '..'
4
4
  import type {BehaviorEvent} from '../behaviors'
5
+ import {toSlateRange} from '../internal-utils/ranges'
5
6
  import type {PickFromUnion} from '../type-utils'
6
7
  import type {PortableTextSlateEditor} from '../types/editor'
7
- import {toSlateRange} from './ranges'
8
8
 
9
9
  export type EditorDom = {
10
10
  getBlockNodes: (snapshot: EditorSnapshot) => Array<Node>
@@ -51,17 +51,21 @@ function getBlockNodes(
51
51
  return []
52
52
  }
53
53
 
54
- const blockEntries = Array.from(
55
- Editor.nodes(slateEditor, {
56
- at: range,
57
- mode: 'highest',
58
- match: (n) => !Editor.isEditor(n),
59
- }),
60
- )
54
+ try {
55
+ const blockEntries = Array.from(
56
+ Editor.nodes(slateEditor, {
57
+ at: range,
58
+ mode: 'highest',
59
+ match: (n) => !Editor.isEditor(n),
60
+ }),
61
+ )
61
62
 
62
- return blockEntries.map(([blockNode]) =>
63
- DOMEditor.toDOMNode(slateEditor, blockNode),
64
- )
63
+ return blockEntries.map(([blockNode]) =>
64
+ DOMEditor.toDOMNode(slateEditor, blockNode),
65
+ )
66
+ } catch {
67
+ return []
68
+ }
65
69
  }
66
70
 
67
71
  function getChildNodes(
@@ -78,17 +82,21 @@ function getChildNodes(
78
82
  return []
79
83
  }
80
84
 
81
- const childEntries = Array.from(
82
- Editor.nodes(slateEditor, {
83
- at: range,
84
- mode: 'lowest',
85
- match: (n) => !Editor.isEditor(n),
86
- }),
87
- )
85
+ try {
86
+ const childEntries = Array.from(
87
+ Editor.nodes(slateEditor, {
88
+ at: range,
89
+ mode: 'lowest',
90
+ match: (n) => !Editor.isEditor(n),
91
+ }),
92
+ )
88
93
 
89
- return childEntries.map(([childNode]) =>
90
- DOMEditor.toDOMNode(slateEditor, childNode),
91
- )
94
+ return childEntries.map(([childNode]) =>
95
+ DOMEditor.toDOMNode(slateEditor, childNode),
96
+ )
97
+ } catch {
98
+ return []
99
+ }
92
100
  }
93
101
 
94
102
  export type SelectionDomNodes = {
@@ -8,7 +8,6 @@ import {createInternalEditor} from './create-editor'
8
8
  import {EditorActorContext} from './editor-actor-context'
9
9
  import {EditorContext} from './editor-context'
10
10
  import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
11
- import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
12
11
  import {
13
12
  PortableTextEditor,
14
13
  type PortableTextEditorProps,
@@ -93,11 +92,7 @@ export function EditorProvider(props: EditorProviderProps) {
93
92
  }
94
93
  >
95
94
  <PortableTextEditorContext.Provider value={portableTextEditor}>
96
- <PortableTextEditorSelectionProvider
97
- editorActor={internalEditor.actors.editorActor}
98
- >
99
- {props.children}
100
- </PortableTextEditorSelectionProvider>
95
+ {props.children}
101
96
  </PortableTextEditorContext.Provider>
102
97
  </Slate>
103
98
  </RelayActorContext.Provider>
@@ -1,19 +1,6 @@
1
- import {
2
- createContext,
3
- startTransition,
4
- useContext,
5
- useEffect,
6
- useState,
7
- } from 'react'
8
- import {debugWithName} from '../../internal-utils/debug'
1
+ import {startTransition, useContext, useEffect, useState} from 'react'
9
2
  import type {EditorSelection} from '../../types/editor'
10
- import type {EditorActor} from '../editor-machine'
11
-
12
- /**
13
- * A React context for sharing the editor selection.
14
- */
15
- const PortableTextEditorSelectionContext =
16
- createContext<EditorSelection | null>(null)
3
+ import {EditorActorContext} from '../editor-actor-context'
17
4
 
18
5
  /**
19
6
  * @deprecated Use `useEditorSelector` to get the current editor selection.
@@ -21,48 +8,21 @@ const PortableTextEditorSelectionContext =
21
8
  * Get the current editor selection from the React context.
22
9
  */
23
10
  export const usePortableTextEditorSelection = (): EditorSelection => {
24
- const selection = useContext(PortableTextEditorSelectionContext)
25
-
26
- if (selection === undefined) {
27
- throw new Error(
28
- `The \`usePortableTextEditorSelection\` hook must be used inside the <PortableTextEditor> component's context.`,
29
- )
30
- }
31
- return selection
32
- }
33
- const debug = debugWithName('component:PortableTextEditor:SelectionProvider')
34
- const debugVerbose = debug.enabled && false
35
-
36
- /**
37
- * @internal
38
- */
39
- export function PortableTextEditorSelectionProvider(
40
- props: React.PropsWithChildren<{
41
- editorActor: EditorActor
42
- }>,
43
- ) {
11
+ const editorActor = useContext(EditorActorContext)
44
12
  const [selection, setSelection] = useState<EditorSelection>(null)
45
13
 
46
- // Subscribe to, and handle changes from the editor
47
14
  useEffect(() => {
48
- debug('Subscribing to selection changes')
49
- const subscription = props.editorActor.on('selection', (event) => {
15
+ const subscription = editorActor.on('selection', (event) => {
50
16
  // Set the selection state in a transition, we don't need the state immediately.
51
17
  startTransition(() => {
52
- if (debugVerbose) debug('Setting selection')
53
18
  setSelection(event.selection)
54
19
  })
55
20
  })
56
21
 
57
22
  return () => {
58
- debug('Unsubscribing to selection changes')
59
23
  subscription.unsubscribe()
60
24
  }
61
- }, [props.editorActor])
25
+ }, [editorActor])
62
26
 
63
- return (
64
- <PortableTextEditorSelectionContext.Provider value={selection}>
65
- {props.children}
66
- </PortableTextEditorSelectionContext.Provider>
67
- )
27
+ return selection
68
28
  }
@@ -267,11 +267,7 @@ export function createWithPatches({
267
267
  type: 'internal.patch',
268
268
  patch: {...patch, origin: 'local'},
269
269
  operationId: getCurrentOperationId(editor),
270
- value: fromSlateValue(
271
- editor.children,
272
- editorActor.getSnapshot().context.schema.block.name,
273
- KEY_TO_VALUE_ELEMENT.get(editor),
274
- ),
270
+ value: editor.value,
275
271
  })
276
272
  }
277
273
  }
package/src/editor.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  import type {ActorRef, EventObject, Snapshot} from 'xstate'
7
7
  import type {Behavior} from './behaviors/behavior.types.behavior'
8
8
  import type {ExternalBehaviorEvent} from './behaviors/behavior.types.event'
9
+ import type {EditorDom} from './editor/editor-dom'
9
10
  import type {ExternalEditorEvent} from './editor/editor-machine'
10
11
  import type {SchemaDefinition} from './editor/editor-schema'
11
12
  import type {EditorSnapshot} from './editor/editor-snapshot'
@@ -51,6 +52,7 @@ export type EditorEvent =
51
52
  * @public
52
53
  */
53
54
  export type Editor = {
55
+ dom: EditorDom
54
56
  getSnapshot: () => EditorSnapshot
55
57
  /**
56
58
  * @beta
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export type {Patch} from '@portabletext/patches'
2
2
  export type {
3
3
  PortableTextBlock,
4
4
  PortableTextChild,
5
+ PortableTextObject,
5
6
  PortableTextSpan,
6
7
  } from '@sanity/types'
7
8
  export type {Editor, EditorConfig, EditorEvent} from './editor'
@@ -79,3 +80,4 @@ export type {
79
80
  ValueChange,
80
81
  } from './types/editor'
81
82
  export type {HotkeyOptions} from './types/options'
83
+ export type {AnnotationPath, BlockPath, ChildPath} from './types/paths'
@@ -0,0 +1,103 @@
1
+ import {Editor, Element, Transforms} from 'slate'
2
+ import {toSlateRange} from '../internal-utils/ranges'
3
+ import type {BehaviorOperationImplementation} from './behavior.operations'
4
+
5
+ export const childSetOperationImplementation: BehaviorOperationImplementation<
6
+ 'child.set'
7
+ > = ({context, operation}) => {
8
+ const location = toSlateRange(
9
+ {
10
+ anchor: {path: operation.at, offset: 0},
11
+ focus: {path: operation.at, offset: 0},
12
+ },
13
+ operation.editor,
14
+ )
15
+
16
+ if (!location) {
17
+ throw new Error(
18
+ `Unable to convert ${JSON.stringify(operation.at)} into a Slate Range`,
19
+ )
20
+ }
21
+
22
+ const childEntry = Editor.node(operation.editor, location, {depth: 2})
23
+ const child = childEntry?.[0]
24
+ const childPath = childEntry?.[1]
25
+
26
+ if (!child || !childPath) {
27
+ throw new Error(`Unable to find child at ${JSON.stringify(operation.at)}`)
28
+ }
29
+
30
+ if (operation.editor.isTextSpan(child)) {
31
+ const {_type, text, ...rest} = operation.props
32
+
33
+ Transforms.setNodes(
34
+ operation.editor,
35
+ {
36
+ ...child,
37
+ ...rest,
38
+ },
39
+ {at: childPath},
40
+ )
41
+
42
+ if (typeof text === 'string') {
43
+ if (child.text !== text) {
44
+ operation.editor.apply({
45
+ type: 'remove_text',
46
+ path: childPath,
47
+ offset: 0,
48
+ text: child.text,
49
+ })
50
+
51
+ operation.editor.apply({
52
+ type: 'insert_text',
53
+ path: childPath,
54
+ offset: 0,
55
+ text,
56
+ })
57
+ }
58
+ }
59
+
60
+ return
61
+ }
62
+
63
+ if (Element.isElement(child)) {
64
+ const definition = context.schema.inlineObjects.find(
65
+ (definition) => definition.name === child._type,
66
+ )
67
+
68
+ if (!definition) {
69
+ throw new Error(
70
+ `Unable to find schema definition for Inline Object type ${child._type}`,
71
+ )
72
+ }
73
+
74
+ const value =
75
+ 'value' in child && typeof child.value === 'object' ? child.value : {}
76
+ const {_type, _key, ...rest} = operation.props
77
+
78
+ for (const prop in rest) {
79
+ if (!definition.fields.some((field) => field.name === prop)) {
80
+ delete rest[prop]
81
+ }
82
+ }
83
+
84
+ Transforms.setNodes(
85
+ operation.editor,
86
+ {
87
+ ...child,
88
+ _key: typeof _key === 'string' ? _key : child._key,
89
+ value: {
90
+ ...value,
91
+ ...rest,
92
+ },
93
+ },
94
+ {at: childPath},
95
+ )
96
+
97
+ return
98
+ }
99
+
100
+ throw new Error(
101
+ `Unable to determine the type of child at ${JSON.stringify(operation.at)}`,
102
+ )
103
+ }
@@ -0,0 +1,89 @@
1
+ import {applyAll} from '@portabletext/patches'
2
+ import {Editor, Element, Transforms} from 'slate'
3
+ import {toSlateRange} from '../internal-utils/ranges'
4
+ import type {BehaviorOperationImplementation} from './behavior.operations'
5
+
6
+ export const childUnsetOperationImplementation: BehaviorOperationImplementation<
7
+ 'child.unset'
8
+ > = ({context, operation}) => {
9
+ const location = toSlateRange(
10
+ {
11
+ anchor: {path: operation.at, offset: 0},
12
+ focus: {path: operation.at, offset: 0},
13
+ },
14
+ operation.editor,
15
+ )
16
+
17
+ if (!location) {
18
+ throw new Error(
19
+ `Unable to convert ${JSON.stringify(operation.at)} into a Slate Range`,
20
+ )
21
+ }
22
+
23
+ const childEntry = Editor.node(operation.editor, location, {depth: 2})
24
+ const child = childEntry?.[0]
25
+ const childPath = childEntry?.[1]
26
+
27
+ if (!child || !childPath) {
28
+ throw new Error(`Unable to find child at ${JSON.stringify(operation.at)}`)
29
+ }
30
+
31
+ if (operation.editor.isTextSpan(child)) {
32
+ if (operation.props.includes('text')) {
33
+ operation.editor.apply({
34
+ type: 'remove_text',
35
+ path: childPath,
36
+ offset: 0,
37
+ text: child.text,
38
+ })
39
+ }
40
+
41
+ const newNode: Record<string, unknown> = {}
42
+
43
+ for (const prop of operation.props) {
44
+ if (prop === '_type') {
45
+ // It's not allowed to unset the _type of a span
46
+ continue
47
+ }
48
+
49
+ if (prop === '_key') {
50
+ newNode._key = context.keyGenerator()
51
+ continue
52
+ }
53
+
54
+ newNode[prop] = null
55
+ }
56
+
57
+ Transforms.setNodes(operation.editor, newNode, {at: childPath})
58
+
59
+ return
60
+ }
61
+
62
+ if (Element.isElement(child)) {
63
+ const value =
64
+ 'value' in child && typeof child.value === 'object' ? child.value : {}
65
+ const patches = operation.props.map((prop) => ({
66
+ type: 'unset' as const,
67
+ path: [prop],
68
+ }))
69
+ const newValue = applyAll(value, patches)
70
+
71
+ Transforms.setNodes(
72
+ operation.editor,
73
+ {
74
+ ...child,
75
+ _key: operation.props.includes('_key')
76
+ ? context.keyGenerator()
77
+ : child._key,
78
+ value: newValue,
79
+ },
80
+ {at: childPath},
81
+ )
82
+
83
+ return
84
+ }
85
+
86
+ throw new Error(
87
+ `Unable to determine the type of child at ${JSON.stringify(operation.at)}`,
88
+ )
89
+ }
@@ -14,6 +14,8 @@ import {addAnnotationOperationImplementation} from './behavior.operation.annotat
14
14
  import {removeAnnotationOperationImplementation} from './behavior.operation.annotation.remove'
15
15
  import {blockSetOperationImplementation} from './behavior.operation.block.set'
16
16
  import {blockUnsetOperationImplementation} from './behavior.operation.block.unset'
17
+ import {childSetOperationImplementation} from './behavior.operation.child.set'
18
+ import {childUnsetOperationImplementation} from './behavior.operation.child.unset'
17
19
  import {decoratorAddOperationImplementation} from './behavior.operation.decorator.add'
18
20
  import {deleteOperationImplementation} from './behavior.operation.delete'
19
21
  import {insertInlineObjectOperationImplementation} from './behavior.operation.insert-inline-object'
@@ -58,6 +60,8 @@ const behaviorOperationImplementations: BehaviorOperationImplementations = {
58
60
  'annotation.remove': removeAnnotationOperationImplementation,
59
61
  'block.set': blockSetOperationImplementation,
60
62
  'block.unset': blockUnsetOperationImplementation,
63
+ 'child.set': childSetOperationImplementation,
64
+ 'child.unset': childUnsetOperationImplementation,
61
65
  'decorator.add': decoratorAddOperationImplementation,
62
66
  'decorator.remove': removeDecoratorOperationImplementation,
63
67
  'delete': deleteOperationImplementation,
@@ -110,6 +114,20 @@ export function performOperation({
110
114
  })
111
115
  break
112
116
  }
117
+ case 'child.set': {
118
+ behaviorOperationImplementations['child.set']({
119
+ context,
120
+ operation: operation,
121
+ })
122
+ break
123
+ }
124
+ case 'child.unset': {
125
+ behaviorOperationImplementations['child.unset']({
126
+ context,
127
+ operation: operation,
128
+ })
129
+ break
130
+ }
113
131
  case 'decorator.add': {
114
132
  behaviorOperationImplementations['decorator.add']({
115
133
  context,
@@ -1,12 +1,13 @@
1
- import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
1
+ import type {PortableTextBlock} from '@sanity/types'
2
2
  import type {EditorSelector} from '../editor/editor-selector'
3
3
  import {getBlockKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {BlockPath} from '../types/paths'
4
5
 
5
6
  /**
6
7
  * @public
7
8
  */
8
9
  export const getAnchorBlock: EditorSelector<
9
- {node: PortableTextBlock; path: [KeyedSegment]} | undefined
10
+ {node: PortableTextBlock; path: BlockPath} | undefined
10
11
  > = (snapshot) => {
11
12
  if (!snapshot.context.selection) {
12
13
  return undefined
@@ -1,7 +1,7 @@
1
- import type {KeyedSegment} from '@portabletext/patches'
2
1
  import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
3
2
  import type {EditorSelector} from '../editor/editor-selector'
4
3
  import {getChildKeyFromSelectionPoint} from '../selection/selection-point'
4
+ import type {ChildPath} from '../types/paths'
5
5
  import {getAnchorTextBlock} from './selector.get-anchor-text-block'
6
6
 
7
7
  /**
@@ -10,7 +10,7 @@ import {getAnchorTextBlock} from './selector.get-anchor-text-block'
10
10
  export const getAnchorChild: EditorSelector<
11
11
  | {
12
12
  node: PortableTextObject | PortableTextSpan
13
- path: [KeyedSegment, 'children', KeyedSegment]
13
+ path: ChildPath
14
14
  }
15
15
  | undefined
16
16
  > = (snapshot) => {
@@ -1,14 +1,13 @@
1
- import type {KeyedSegment} from '@portabletext/patches'
2
1
  import {isPortableTextSpan, type PortableTextSpan} from '@sanity/types'
3
2
  import type {EditorSelector} from '../editor/editor-selector'
3
+ import type {ChildPath} from '../types/paths'
4
4
  import {getAnchorChild} from './selector.get-anchor-child'
5
5
 
6
6
  /**
7
7
  * @public
8
8
  */
9
9
  export const getAnchorSpan: EditorSelector<
10
- | {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
11
- | undefined
10
+ {node: PortableTextSpan; path: ChildPath} | undefined
12
11
  > = (snapshot) => {
13
12
  const anchorChild = getAnchorChild(snapshot)
14
13