@portabletext/editor 3.0.9 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "3.0.9",
3
+ "version": "3.1.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -145,7 +145,7 @@ export type SyntheticBehaviorEvent =
145
145
  /**
146
146
  * Defaults to character deletion.
147
147
  */
148
- unit?: 'character' | 'word' | 'line' | 'block'
148
+ unit?: 'character' | 'word' | 'line' | 'block' | 'child'
149
149
  }
150
150
  | {
151
151
  type: StrictExtract<SyntheticBehaviorEventType, 'history.redo'>
@@ -350,6 +350,20 @@ export class PortableTextEditor extends Component<
350
350
  editor.editable?.blur()
351
351
  }
352
352
 
353
+ /**
354
+ * @deprecated
355
+ * Use `editor.send(...)` instead
356
+ *
357
+ * ```
358
+ * const editor = useEditor()
359
+ * editor.send({
360
+ * type: 'delete',
361
+ * at: {...},
362
+ * direction: '...',
363
+ * unit: '...',
364
+ * })
365
+ * ```
366
+ */
353
367
  static delete = (
354
368
  editor: PortableTextEditor,
355
369
  selection: EditorSelection,
@@ -5,12 +5,9 @@ import type {
5
5
  PortableTextChild,
6
6
  PortableTextObject,
7
7
  } from '@sanity/types'
8
- import {Editor, Range, Element as SlateElement, Text, Transforms} from 'slate'
8
+ import {Editor, Range, Text, Transforms} from 'slate'
9
9
  import type {DOMNode} from 'slate-dom'
10
10
  import {ReactEditor} from 'slate-react'
11
- import {buildIndexMaps} from '../../internal-utils/build-index-maps'
12
- import {createPlaceholderBlock} from '../../internal-utils/create-placeholder-block'
13
- import {debugWithName} from '../../internal-utils/debug'
14
11
  import {
15
12
  isListItemActive,
16
13
  isStyleActive,
@@ -37,8 +34,6 @@ import type {EditorActor} from '../editor-machine'
37
34
  import {getEditorSnapshot} from '../editor-selector'
38
35
  import {SLATE_TO_PORTABLE_TEXT_RANGE} from '../weakMaps'
39
36
 
40
- const debug = debugWithName('API:editable')
41
-
42
37
  export function createEditableAPI(
43
38
  editor: PortableTextSlateEditor,
44
39
  editorActor: EditorActor,
@@ -431,84 +426,24 @@ export function createEditableAPI(
431
426
  selection: EditorSelection,
432
427
  options?: EditableAPIDeleteOptions,
433
428
  ): void => {
434
- if (selection) {
435
- const range = toSlateRange({
436
- context: {
437
- schema: editorActor.getSnapshot().context.schema,
438
- value: editor.value,
439
- selection,
440
- },
441
- blockIndexMap: editor.blockIndexMap,
442
- })
443
- const hasRange =
444
- range && range.anchor.path.length > 0 && range.focus.path.length > 0
445
- if (!hasRange) {
446
- throw new Error('Invalid range')
447
- }
448
- if (range) {
449
- if (!options?.mode || options?.mode === 'selected') {
450
- debug(`Deleting content in selection`)
451
- Transforms.delete(editor, {
452
- at: range,
453
- hanging: true,
454
- voids: true,
455
- })
456
- editor.onChange()
457
- return
458
- }
459
- if (options?.mode === 'blocks') {
460
- debug(`Deleting blocks touched by selection`)
461
- Transforms.removeNodes(editor, {
462
- at: range,
463
- voids: true,
464
- match: (node) => {
465
- return (
466
- editor.isTextBlock(node) ||
467
- (!editor.isTextBlock(node) && SlateElement.isElement(node))
468
- )
469
- },
470
- })
471
- }
472
- if (options?.mode === 'children') {
473
- debug(`Deleting children touched by selection`)
474
- Transforms.removeNodes(editor, {
475
- at: range,
476
- voids: true,
477
- match: (node) => {
478
- return (
479
- node._type === types.span.name || // Text children
480
- (!editor.isTextBlock(node) && SlateElement.isElement(node)) // inline blocks
481
- )
482
- },
483
- })
484
- }
485
- // If the editor was emptied, insert a placeholder block
486
- // directly into the editor's children. We don't want to do this
487
- // through a Transform (because that would trigger a change event
488
- // that would insert the placeholder into the actual value
489
- // which should remain empty)
490
- if (editor.children.length === 0) {
491
- const placeholderBlock = createPlaceholderBlock(
492
- editorActor.getSnapshot().context,
493
- )
494
- editor.children = [placeholderBlock]
495
- editor.value = [placeholderBlock]
496
-
497
- buildIndexMaps(
498
- {
499
- schema: editorActor.getSnapshot().context.schema,
500
- value: editor.value,
501
- },
502
- {
503
- blockIndexMap: editor.blockIndexMap,
504
- listIndexMap: editor.listIndexMap,
505
- },
506
- )
507
- }
508
-
509
- editor.onChange()
510
- }
429
+ if (!selection) {
430
+ return
511
431
  }
432
+
433
+ editorActor.send({
434
+ type: 'behavior event',
435
+ behaviorEvent: {
436
+ type: 'delete',
437
+ at: selection,
438
+ unit:
439
+ options?.mode === 'blocks'
440
+ ? 'block'
441
+ : options?.mode === 'children'
442
+ ? 'child'
443
+ : undefined,
444
+ },
445
+ editor,
446
+ })
512
447
  },
513
448
  removeAnnotation: <TSchemaType extends {name: string}>(
514
449
  type: TSchemaType,
@@ -41,7 +41,7 @@ export function createWithPatches({
41
41
  // The editor.value would no longer contain that information if the node is already deleted.
42
42
  let previousValue: PortableTextBlock[]
43
43
 
44
- const applyPatch = createApplyPatch(editorActor.getSnapshot().context.schema)
44
+ const applyPatch = createApplyPatch(editorActor.getSnapshot().context)
45
45
 
46
46
  return function withPatches(editor: PortableTextSlateEditor) {
47
47
  IS_PROCESSING_REMOTE_CHANGES.set(editor, false)
@@ -1,6 +1,7 @@
1
1
  import {Editor} from 'slate'
2
2
  import {isRedoing} from '../../history/slate-plugin.redoing'
3
3
  import {isUndoing} from '../../history/slate-plugin.undoing'
4
+ import {createPlaceholderBlock} from '../../internal-utils/create-placeholder-block'
4
5
  import {debugWithName} from '../../internal-utils/debug'
5
6
  import type {PortableTextSlateEditor} from '../../types/editor'
6
7
  import type {EditorActor} from '../editor-machine'
@@ -55,7 +56,10 @@ export function createWithPlaceholderBlock(
55
56
 
56
57
  if (isLonelyBlock && isBlockObject) {
57
58
  debug('Adding placeholder block')
58
- Editor.insertNode(editor, editor.pteCreateTextBlock({decorators: []}))
59
+ Editor.insertNode(
60
+ editor,
61
+ createPlaceholderBlock(editorActor.getSnapshot().context),
62
+ )
59
63
  }
60
64
  }
61
65
 
@@ -9,7 +9,6 @@ import {createWithPatches} from './createWithPatches'
9
9
  import {createWithPlaceholderBlock} from './createWithPlaceholderBlock'
10
10
  import {createWithPortableTextMarkModel} from './createWithPortableTextMarkModel'
11
11
  import {createWithSchemaTypes} from './createWithSchemaTypes'
12
- import {createWithUtils} from './createWithUtils'
13
12
  import {pluginUpdateSelection} from './slate-plugin.update-selection'
14
13
  import {pluginUpdateValue} from './slate-plugin.update-value'
15
14
 
@@ -48,9 +47,6 @@ export const withPlugins = <T extends Editor>(
48
47
 
49
48
  const withPlaceholderBlock = createWithPlaceholderBlock(editorActor)
50
49
 
51
- const withUtils = createWithUtils({
52
- editorActor,
53
- })
54
50
  const withEventListeners = createWithEventListeners(editorActor)
55
51
 
56
52
  // Ordering is important here, selection dealing last, data manipulation in the middle and core model stuff first.
@@ -59,16 +55,14 @@ export const withPlugins = <T extends Editor>(
59
55
  withObjectKeys(
60
56
  withPortableTextMarkModel(
61
57
  withPlaceholderBlock(
62
- withUtils(
63
- withUndoRedo(
64
- withPatches(
65
- pluginUpdateValue(
66
- editorActor.getSnapshot().context,
67
- pluginUpdateSelection({
68
- editorActor,
69
- editor: e,
70
- }),
71
- ),
58
+ withUndoRedo(
59
+ withPatches(
60
+ pluginUpdateValue(
61
+ editorActor.getSnapshot().context,
62
+ pluginUpdateSelection({
63
+ editorActor,
64
+ editor: e,
65
+ }),
72
66
  ),
73
67
  ),
74
68
  ),
@@ -17,6 +17,7 @@ import {
17
17
  type CallbackLogicFunction,
18
18
  } from 'xstate'
19
19
  import {pluginWithoutHistory} from '../history/slate-plugin.without-history'
20
+ import {createPlaceholderBlock} from '../internal-utils/create-placeholder-block'
20
21
  import {debugWithName} from '../internal-utils/debug'
21
22
  import {validateValue} from '../internal-utils/validateValue'
22
23
  import {toSlateBlock, VOID_CHILD_KEY} from '../internal-utils/values'
@@ -424,6 +425,7 @@ async function updateValue({
424
425
  debug('Value is empty')
425
426
 
426
427
  clearEditor({
428
+ context,
427
429
  slateEditor,
428
430
  doneSyncing,
429
431
  hadSelection,
@@ -573,10 +575,15 @@ async function* getStreamedBlocks({value}: {value: Array<PortableTextBlock>}) {
573
575
  * Remove all blocks and insert a placeholder block
574
576
  */
575
577
  function clearEditor({
578
+ context,
576
579
  slateEditor,
577
580
  doneSyncing,
578
581
  hadSelection,
579
582
  }: {
583
+ context: {
584
+ keyGenerator: () => string
585
+ schema: EditorSchema
586
+ }
580
587
  slateEditor: PortableTextSlateEditor
581
588
  doneSyncing: boolean
582
589
  hadSelection: boolean
@@ -601,11 +608,9 @@ function clearEditor({
601
608
  })
602
609
  })
603
610
 
604
- Transforms.insertNodes(
605
- slateEditor,
606
- slateEditor.pteCreateTextBlock({decorators: []}),
607
- {at: [0]},
608
- )
611
+ Transforms.insertNodes(slateEditor, createPlaceholderBlock(context), {
612
+ at: [0],
613
+ })
609
614
 
610
615
  // Add a new selection in the top of the document
611
616
  if (hadSelection) {
@@ -17,17 +17,18 @@ import {
17
17
  } from '@sanity/diff-match-patch'
18
18
  import type {Path, PortableTextBlock, PortableTextChild} from '@sanity/types'
19
19
  import {Editor, Element, Node, Text, Transforms, type Descendant} from 'slate'
20
- import type {EditorSchema} from '../editor/editor-schema'
20
+ import type {EditorContext} from '../editor/editor-snapshot'
21
21
  import {KEY_TO_SLATE_ELEMENT} from '../editor/weakMaps'
22
22
  import type {PortableTextSlateEditor} from '../types/editor'
23
23
  import {isKeyedSegment} from '../utils/util.is-keyed-segment'
24
+ import {createPlaceholderBlock} from './create-placeholder-block'
24
25
  import {isEqualToEmptyEditor, toSlateBlock} from './values'
25
26
 
26
27
  /**
27
28
  * Creates a function that can apply a patch onto a PortableTextSlateEditor.
28
29
  */
29
30
  export function createApplyPatch(
30
- schema: EditorSchema,
31
+ context: Pick<EditorContext, 'schema' | 'keyGenerator'>,
31
32
  ): (editor: PortableTextSlateEditor, patch: Patch) => boolean {
32
33
  return (editor: PortableTextSlateEditor, patch: Patch): boolean => {
33
34
  let changed = false
@@ -35,10 +36,10 @@ export function createApplyPatch(
35
36
  try {
36
37
  switch (patch.type) {
37
38
  case 'insert':
38
- changed = insertPatch(editor, patch, schema)
39
+ changed = insertPatch(context, editor, patch)
39
40
  break
40
41
  case 'unset':
41
- changed = unsetPatch(editor, patch)
42
+ changed = unsetPatch(context, editor, patch)
42
43
  break
43
44
  case 'set':
44
45
  changed = setPatch(editor, patch)
@@ -117,9 +118,9 @@ function diffMatchPatch(
117
118
  }
118
119
 
119
120
  function insertPatch(
121
+ context: Pick<EditorContext, 'schema' | 'keyGenerator'>,
120
122
  editor: PortableTextSlateEditor,
121
123
  patch: InsertPatch,
122
- schema: EditorSchema,
123
124
  ) {
124
125
  const block = findBlock(editor.children, patch.path)
125
126
 
@@ -137,7 +138,7 @@ function insertPatch(
137
138
  const blocksToInsert = items.map((item) =>
138
139
  toSlateBlock(
139
140
  item as PortableTextBlock,
140
- {schemaTypes: schema},
141
+ {schemaTypes: context.schema},
141
142
  KEY_TO_SLATE_ELEMENT.get(editor),
142
143
  ),
143
144
  )
@@ -145,7 +146,10 @@ function insertPatch(
145
146
  const normalizedIdx =
146
147
  position === 'after' ? targetBlockIndex + 1 : targetBlockIndex
147
148
 
148
- const editorWasEmptyBefore = isEqualToEmptyEditor(editor.value, schema)
149
+ const editorWasEmptyBefore = isEqualToEmptyEditor(
150
+ editor.value,
151
+ context.schema,
152
+ )
149
153
 
150
154
  Transforms.insertNodes(editor, blocksToInsert, {at: [normalizedIdx]})
151
155
 
@@ -177,7 +181,7 @@ function insertPatch(
177
181
 
178
182
  const childrenToInsert = toSlateBlock(
179
183
  {...block.node, children: items as PortableTextChild[]},
180
- {schemaTypes: schema},
184
+ {schemaTypes: context.schema},
181
185
  KEY_TO_SLATE_ELEMENT.get(editor),
182
186
  )
183
187
  const normalizedIdx =
@@ -414,7 +418,11 @@ function setPatch(editor: PortableTextSlateEditor, patch: SetPatch) {
414
418
  return true
415
419
  }
416
420
 
417
- function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
421
+ function unsetPatch(
422
+ context: Pick<EditorContext, 'keyGenerator' | 'schema'>,
423
+ editor: PortableTextSlateEditor,
424
+ patch: UnsetPatch,
425
+ ) {
418
426
  // Value
419
427
  if (patch.path.length === 0) {
420
428
  const previousSelection = editor.selection
@@ -428,7 +436,7 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
428
436
  Transforms.removeNodes(editor, {at: path})
429
437
  }
430
438
 
431
- Transforms.insertNodes(editor, editor.pteCreateTextBlock({decorators: []}))
439
+ Transforms.insertNodes(editor, createPlaceholderBlock(context))
432
440
  if (previousSelection) {
433
441
  Transforms.select(editor, {
434
442
  anchor: {path: [0, 0], offset: 0},
@@ -455,10 +463,7 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch) {
455
463
 
456
464
  Transforms.deselect(editor)
457
465
  Transforms.removeNodes(editor, {at: [block.index]})
458
- Transforms.insertNodes(
459
- editor,
460
- editor.pteCreateTextBlock({decorators: []}),
461
- )
466
+ Transforms.insertNodes(editor, createPlaceholderBlock(context))
462
467
 
463
468
  if (previousSelection) {
464
469
  Transforms.select(editor, {
@@ -1,9 +1,10 @@
1
- import {isTextBlock} from '@portabletext/schema'
1
+ import {isSpan, isTextBlock} from '@portabletext/schema'
2
2
  import {deleteText, Editor, Element, Range, Transforms} from 'slate'
3
3
  import {DOMEditor} from 'slate-dom'
4
4
  import {createPlaceholderBlock} from '../internal-utils/create-placeholder-block'
5
5
  import {slateRangeToSelection} from '../internal-utils/slate-utils'
6
6
  import {toSlateRange} from '../internal-utils/to-slate-range'
7
+ import {VOID_CHILD_KEY} from '../internal-utils/values'
7
8
  import type {PortableTextSlateEditor} from '../types/editor'
8
9
  import {getBlockKeyFromSelectionPoint} from '../utils/util.selection-point'
9
10
  import type {BehaviorOperationImplementation} from './behavior.operations'
@@ -74,6 +75,23 @@ export const deleteOperationImplementation: BehaviorOperationImplementation<
74
75
  return
75
76
  }
76
77
 
78
+ if (operation.unit === 'child') {
79
+ const range = at ?? operation.editor.selection ?? undefined
80
+
81
+ if (!range) {
82
+ throw new Error('Unable to delete children without a selection')
83
+ }
84
+
85
+ Transforms.removeNodes(operation.editor, {
86
+ at: range,
87
+ match: (node) =>
88
+ (isSpan(context, node) && node._key !== VOID_CHILD_KEY) ||
89
+ ('__inline' in node && node.__inline === true),
90
+ })
91
+
92
+ return
93
+ }
94
+
77
95
  if (operation.direction === 'backward' && operation.unit === 'line') {
78
96
  const range = at ?? operation.editor.selection ?? undefined
79
97
 
@@ -152,15 +152,6 @@ export interface PortableTextSlateEditor extends ReactEditor {
152
152
  */
153
153
  pteWithHotKeys: (event: KeyboardEvent<HTMLDivElement>) => void
154
154
 
155
- /**
156
- * Helper function that creates a text block
157
- */
158
- pteCreateTextBlock: (options: {
159
- decorators: Array<string>
160
- listItem?: string
161
- level?: number
162
- }) => Descendant
163
-
164
155
  /**
165
156
  * Undo
166
157
  */
@@ -1,52 +0,0 @@
1
- import {toSlateBlock} from '../../internal-utils/values'
2
- import type {PortableTextSlateEditor} from '../../types/editor'
3
- import type {EditorActor} from '../editor-machine'
4
-
5
- interface Options {
6
- editorActor: EditorActor
7
- }
8
-
9
- /**
10
- * This plugin makes various util commands available in the editor
11
- *
12
- */
13
- export function createWithUtils({editorActor}: Options) {
14
- return function withUtils(
15
- editor: PortableTextSlateEditor,
16
- ): PortableTextSlateEditor {
17
- editor.pteCreateTextBlock = (options: {
18
- decorators: Array<string>
19
- listItem?: string
20
- level?: number
21
- }) => {
22
- return toSlateBlock(
23
- {
24
- _type: editorActor.getSnapshot().context.schema.block.name,
25
- _key: editorActor.getSnapshot().context.keyGenerator(),
26
- style:
27
- editorActor.getSnapshot().context.schema.styles[0].name || 'normal',
28
- ...(options.listItem ? {listItem: options.listItem} : {}),
29
- ...(options.level ? {level: options.level} : {}),
30
- markDefs: [],
31
- children: [
32
- {
33
- _type: 'span',
34
- _key: editorActor.getSnapshot().context.keyGenerator(),
35
- text: '',
36
- marks: options.decorators.filter((decorator) =>
37
- editorActor
38
- .getSnapshot()
39
- .context.schema.decorators.find(
40
- ({name}) => name === decorator,
41
- ),
42
- ),
43
- },
44
- ],
45
- },
46
-
47
- {schemaTypes: editorActor.getSnapshot().context.schema},
48
- )
49
- }
50
- return editor
51
- }
52
- }