@portabletext/editor 1.12.3 → 1.14.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 (75) hide show
  1. package/README.md +1 -1
  2. package/lib/_chunks-cjs/selector.get-text-before.cjs +320 -0
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -0
  4. package/lib/_chunks-es/selector.get-text-before.js +321 -0
  5. package/lib/_chunks-es/selector.get-text-before.js.map +1 -0
  6. package/lib/{index.esm.js → index.cjs} +1954 -1513
  7. package/lib/index.cjs.map +1 -0
  8. package/lib/{index.d.mts → index.d.cts} +4249 -304
  9. package/lib/index.d.ts +4249 -304
  10. package/lib/index.js +1974 -1488
  11. package/lib/index.js.map +1 -1
  12. package/lib/selectors/index.cjs +35 -0
  13. package/lib/selectors/index.cjs.map +1 -0
  14. package/lib/selectors/index.d.cts +243 -0
  15. package/lib/selectors/index.d.ts +243 -0
  16. package/lib/selectors/index.js +36 -0
  17. package/lib/selectors/index.js.map +1 -0
  18. package/package.json +25 -17
  19. package/src/editor/Editable.tsx +61 -6
  20. package/src/editor/PortableTextEditor.tsx +19 -4
  21. package/src/editor/__tests__/handleClick.test.tsx +4 -4
  22. package/src/editor/behavior/behavior.action.insert-block-object.ts +1 -1
  23. package/src/editor/behavior/behavior.action.insert-break.ts +24 -27
  24. package/src/editor/behavior/behavior.action.insert-inline-object.ts +58 -0
  25. package/src/editor/behavior/behavior.action.insert-span.ts +1 -1
  26. package/src/editor/behavior/behavior.action.list-item.ts +100 -0
  27. package/src/editor/behavior/behavior.action.style.ts +108 -0
  28. package/src/editor/behavior/behavior.action.text-block.set.ts +25 -0
  29. package/src/editor/behavior/behavior.action.text-block.unset.ts +17 -0
  30. package/src/editor/behavior/behavior.actions.ts +266 -75
  31. package/src/editor/behavior/behavior.code-editor.ts +76 -0
  32. package/src/editor/behavior/behavior.core.block-objects.ts +52 -19
  33. package/src/editor/behavior/behavior.core.decorators.ts +9 -6
  34. package/src/editor/behavior/behavior.core.lists.ts +139 -17
  35. package/src/editor/behavior/behavior.core.ts +7 -2
  36. package/src/editor/behavior/behavior.guards.ts +28 -0
  37. package/src/editor/behavior/behavior.links.ts +9 -9
  38. package/src/editor/behavior/behavior.markdown.ts +69 -80
  39. package/src/editor/behavior/behavior.types.ts +121 -60
  40. package/src/editor/{use-editor.ts → create-editor.ts} +13 -8
  41. package/src/editor/editor-event-listener.tsx +2 -2
  42. package/src/editor/editor-machine.ts +57 -15
  43. package/src/editor/editor-provider.tsx +5 -5
  44. package/src/editor/editor-selector.ts +49 -0
  45. package/src/editor/editor-snapshot.ts +22 -0
  46. package/src/editor/get-value.ts +11 -0
  47. package/src/editor/plugins/create-with-event-listeners.ts +93 -5
  48. package/src/editor/plugins/createWithEditableAPI.ts +69 -20
  49. package/src/editor/plugins/createWithHotKeys.ts +0 -101
  50. package/src/editor/plugins/createWithPortableTextBlockStyle.ts +1 -55
  51. package/src/editor/plugins/with-plugins.ts +4 -8
  52. package/src/editor/{behavior/behavior.utils.block-offset.test.ts → utils/utils.block-offset.test.ts} +1 -1
  53. package/src/editor/{behavior/behavior.utils.block-offset.ts → utils/utils.block-offset.ts} +1 -8
  54. package/src/editor/{behavior/behavior.utils.reverse-selection.ts → utils/utils.reverse-selection.ts} +3 -5
  55. package/src/editor/utils/utils.ts +21 -0
  56. package/src/index.ts +13 -9
  57. package/src/selectors/index.ts +15 -0
  58. package/src/selectors/selector.get-active-list-item.ts +37 -0
  59. package/src/{editor/behavior/behavior.utils.get-selection-text.ts → selectors/selector.get-selection-text.ts} +10 -15
  60. package/src/selectors/selector.get-text-before.ts +41 -0
  61. package/src/selectors/selectors.ts +329 -0
  62. package/src/types/editor.ts +0 -60
  63. package/src/utils/is-hotkey.test.ts +99 -46
  64. package/src/utils/is-hotkey.ts +1 -1
  65. package/src/utils/operationToPatches.ts +5 -0
  66. package/src/utils/paths.ts +4 -11
  67. package/src/utils/ranges.ts +3 -3
  68. package/lib/index.esm.js.map +0 -1
  69. package/lib/index.mjs +0 -7372
  70. package/lib/index.mjs.map +0 -1
  71. package/src/editor/behavior/behavior.utils.ts +0 -218
  72. package/src/editor/behavior/behavior.utilts.get-text-before.ts +0 -31
  73. package/src/editor/plugins/createWithPortableTextLists.ts +0 -172
  74. /package/src/editor/{behavior/behavior.utils.get-start-point.ts → utils/utils.get-start-point.ts} +0 -0
  75. /package/src/editor/{behavior/behavior.utils.is-keyed-segment.ts → utils/utils.is-keyed-segment.ts} +0 -0
@@ -420,9 +420,19 @@ export const PortableTextEditable = forwardRef<
420
420
  if (result !== undefined) {
421
421
  event.preventDefault()
422
422
  }
423
+ } else if (event.nativeEvent.clipboardData) {
424
+ editorActor.send({
425
+ type: 'behavior event',
426
+ behaviorEvent: {
427
+ type: 'copy',
428
+ data: event.nativeEvent.clipboardData,
429
+ },
430
+ editor: slateEditor,
431
+ nativeEvent: event,
432
+ })
423
433
  }
424
434
  },
425
- [onCopy],
435
+ [onCopy, editorActor, slateEditor],
426
436
  )
427
437
 
428
438
  // Handle incoming pasting events in the editor
@@ -473,15 +483,14 @@ export const PortableTextEditable = forwardRef<
473
483
  editorActor.send({type: 'done loading'})
474
484
  })
475
485
  } else if (event.nativeEvent.clipboardData) {
476
- event.preventDefault()
477
-
478
486
  editorActor.send({
479
487
  type: 'behavior event',
480
488
  behaviorEvent: {
481
489
  type: 'paste',
482
- clipboardData: event.nativeEvent.clipboardData,
490
+ data: event.nativeEvent.clipboardData,
483
491
  },
484
492
  editor: slateEditor,
493
+ nativeEvent: event,
485
494
  })
486
495
  }
487
496
 
@@ -558,7 +567,7 @@ export const PortableTextEditable = forwardRef<
558
567
  onBlur(event)
559
568
  }
560
569
  if (!event.isPropagationStopped()) {
561
- editorActor.send({type: 'blur', event})
570
+ editorActor.send({type: 'blurred', event})
562
571
  }
563
572
  },
564
573
  [editorActor, onBlur],
@@ -659,8 +668,53 @@ export const PortableTextEditable = forwardRef<
659
668
  if (!event.isDefaultPrevented()) {
660
669
  slateEditor.pteWithHotKeys(event)
661
670
  }
671
+ if (!event.isDefaultPrevented()) {
672
+ editorActor.send({
673
+ type: 'behavior event',
674
+ behaviorEvent: {
675
+ type: 'key.down',
676
+ keyboardEvent: {
677
+ key: event.key,
678
+ code: event.code,
679
+ altKey: event.altKey,
680
+ ctrlKey: event.ctrlKey,
681
+ metaKey: event.metaKey,
682
+ shiftKey: event.shiftKey,
683
+ },
684
+ },
685
+ editor: slateEditor,
686
+ nativeEvent: event,
687
+ })
688
+ }
689
+ },
690
+ [props, editorActor, slateEditor],
691
+ )
692
+
693
+ const handleKeyUp = useCallback(
694
+ (event: KeyboardEvent<HTMLDivElement>) => {
695
+ if (props.onKeyUp) {
696
+ props.onKeyUp(event)
697
+ }
698
+ if (!event.isDefaultPrevented()) {
699
+ editorActor.send({
700
+ type: 'behavior event',
701
+ behaviorEvent: {
702
+ type: 'key.up',
703
+ keyboardEvent: {
704
+ key: event.key,
705
+ code: event.code,
706
+ altKey: event.altKey,
707
+ ctrlKey: event.ctrlKey,
708
+ metaKey: event.metaKey,
709
+ shiftKey: event.shiftKey,
710
+ },
711
+ },
712
+ editor: slateEditor,
713
+ nativeEvent: event,
714
+ })
715
+ }
662
716
  },
663
- [props, slateEditor],
717
+ [props, editorActor, slateEditor],
664
718
  )
665
719
 
666
720
  const scrollSelectionIntoViewToSlate = useMemo(() => {
@@ -753,6 +807,7 @@ export const PortableTextEditable = forwardRef<
753
807
  onDOMBeforeInput={handleOnBeforeInput}
754
808
  onFocus={handleOnFocus}
755
809
  onKeyDown={handleKeyDown}
810
+ onKeyUp={handleKeyUp}
756
811
  onPaste={handlePaste}
757
812
  readOnly={readOnly}
758
813
  // We have implemented our own placeholder logic with decorations.
@@ -28,13 +28,13 @@ import {debugWithName} from '../utils/debug'
28
28
  import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSchemaTypes'
29
29
  import {compileType} from '../utils/schema'
30
30
  import {Synchronizer} from './components/Synchronizer'
31
+ import {createEditor, type Editor} from './create-editor'
31
32
  import {EditorActorContext} from './editor-actor-context'
32
33
  import type {EditorActor} from './editor-machine'
33
34
  import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
34
35
  import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
35
36
  import {defaultKeyGenerator} from './key-generator'
36
37
  import type {AddedAnnotationPaths} from './plugins/createWithEditableAPI'
37
- import {createEditor, type Editor} from './use-editor'
38
38
 
39
39
  const debug = debugWithName('component:PortableTextEditor')
40
40
 
@@ -148,7 +148,7 @@ export class PortableTextEditor extends Component<
148
148
 
149
149
  this.schemaTypes =
150
150
  this.editor._internal.editorActor.getSnapshot().context.schema
151
- this.editable = this.editor.editable
151
+ this.editable = this.editor._internal.editable
152
152
  }
153
153
 
154
154
  componentDidUpdate(prevProps: PortableTextEditorProps) {
@@ -204,7 +204,10 @@ export class PortableTextEditor extends Component<
204
204
  }
205
205
 
206
206
  public setEditable = (editable: EditableAPI) => {
207
- this.editor.editable = {...this.editor.editable, ...editable}
207
+ this.editor._internal.editable = {
208
+ ...this.editor._internal.editable,
209
+ ...editable,
210
+ }
208
211
  }
209
212
 
210
213
  render() {
@@ -235,7 +238,7 @@ export class PortableTextEditor extends Component<
235
238
  />
236
239
  <Synchronizer
237
240
  editorActor={this.editor._internal.editorActor}
238
- getValue={this.editor.editable.getValue}
241
+ getValue={this.editor._internal.editable.getValue}
239
242
  portableTextEditor={this}
240
243
  slateEditor={this.editor._internal.slateEditor.instance}
241
244
  />
@@ -442,6 +445,10 @@ export function RouteEventsToChanges(props: {
442
445
  debug('Subscribing to editor changes')
443
446
  const sub = props.editorActor.on('*', (event) => {
444
447
  switch (event.type) {
448
+ case 'blurred': {
449
+ handleChange({type: 'blur', event: event.event})
450
+ break
451
+ }
445
452
  case 'patch':
446
453
  handleChange(event)
447
454
  break
@@ -479,7 +486,15 @@ export function RouteEventsToChanges(props: {
479
486
  case 'annotation.add':
480
487
  case 'annotation.remove':
481
488
  case 'annotation.toggle':
489
+ case 'blur':
490
+ case 'decorator.add':
491
+ case 'decorator.remove':
492
+ case 'decorator.toggle':
482
493
  case 'focus':
494
+ case 'insert.block object':
495
+ case 'insert.inline object':
496
+ case 'list item.toggle':
497
+ case 'style.toggle':
483
498
  case 'patches':
484
499
  case 'readOnly toggled':
485
500
  break
@@ -76,7 +76,7 @@ describe('adds empty text block if its needed', () => {
76
76
 
77
77
  const element = await getEditableElement(component)
78
78
 
79
- await waitFor(async () => {
79
+ await waitFor(() => {
80
80
  if (editorRef.current && element) {
81
81
  PortableTextEditor.focus(editorRef.current)
82
82
  PortableTextEditor.select(editorRef.current, initialSelection)
@@ -127,7 +127,7 @@ describe('adds empty text block if its needed', () => {
127
127
  )
128
128
  const element = await getEditableElement(component)
129
129
 
130
- await waitFor(async () => {
130
+ await waitFor(() => {
131
131
  if (editorRef.current && element) {
132
132
  PortableTextEditor.focus(editorRef.current)
133
133
  PortableTextEditor.select(editorRef.current, initialSelection)
@@ -193,7 +193,7 @@ describe('adds empty text block if its needed', () => {
193
193
 
194
194
  const element = await getEditableElement(component)
195
195
 
196
- await waitFor(async () => {
196
+ await waitFor(() => {
197
197
  if (editorRef.current && element) {
198
198
  PortableTextEditor.focus(editorRef.current)
199
199
  PortableTextEditor.select(editorRef.current, initialSelection)
@@ -263,7 +263,7 @@ describe('adds empty text block if its needed', () => {
263
263
  const inlineType = editor?.schemaTypes.inlineObjects.find(
264
264
  (t) => t.name === 'someObject',
265
265
  )
266
- await waitFor(async () => {
266
+ await waitFor(() => {
267
267
  if (editor && inlineType && element) {
268
268
  PortableTextEditor.focus(editor)
269
269
  PortableTextEditor.select(editor, initialSelection)
@@ -3,7 +3,7 @@ import {insertBlock} from './behavior.action-utils.insert-block'
3
3
  import type {BehaviorActionImplementation} from './behavior.actions'
4
4
 
5
5
  export const insertBlockObjectActionImplementation: BehaviorActionImplementation<
6
- 'insert block object'
6
+ 'insert.block object'
7
7
  > = ({context, action}) => {
8
8
  const block = toSlateValue(
9
9
  [
@@ -4,7 +4,7 @@ import type {SlateTextBlock, VoidElement} from '../../types/slate'
4
4
  import type {BehaviorActionImplementation} from './behavior.actions'
5
5
 
6
6
  export const insertBreakActionImplementation: BehaviorActionImplementation<
7
- 'insert break'
7
+ 'insert.break'
8
8
  > = ({context, action}) => {
9
9
  const keyGenerator = context.keyGenerator
10
10
  const schema = context.schema
@@ -23,15 +23,16 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
23
23
  }),
24
24
  )[0] ?? [undefined]
25
25
  const focusDecorators =
26
- focusSpan.marks?.filter((mark) =>
26
+ focusSpan?.marks?.filter((mark) =>
27
27
  schema.decorators.some((decorator) => decorator.value === mark),
28
28
  ) ?? []
29
29
  const focusAnnotations =
30
- focusSpan.marks?.filter(
30
+ focusSpan?.marks?.filter(
31
31
  (mark) =>
32
32
  !schema.decorators.some((decorator) => decorator.value === mark),
33
33
  ) ?? []
34
34
 
35
+ const anchorBlockPath = editor.selection.anchor.path.slice(0, 1)
35
36
  const focusBlockPath = editor.selection.focus.path.slice(0, 1)
36
37
  const focusBlock = Node.descendant(editor, focusBlockPath) as
37
38
  | SlateTextBlock
@@ -39,45 +40,37 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
39
40
 
40
41
  if (editor.isTextBlock(focusBlock)) {
41
42
  const [start, end] = Range.edges(editor.selection)
43
+ const lastFocusBlockChild =
44
+ focusBlock.children[focusBlock.children.length - 1]
45
+ const atTheEndOfBlock = isEqual(start, {
46
+ path: [...focusBlockPath, focusBlock.children.length - 1],
47
+ offset: editor.isTextSpan(lastFocusBlockChild)
48
+ ? lastFocusBlockChild.text.length
49
+ : 0,
50
+ })
42
51
  const atTheStartOfBlock = isEqual(end, {
43
52
  path: [...focusBlockPath, 0],
44
53
  offset: 0,
45
54
  })
46
55
 
47
- if (atTheStartOfBlock && Range.isCollapsed(editor.selection)) {
56
+ if (atTheEndOfBlock && Range.isCollapsed(editor.selection)) {
48
57
  Editor.insertNode(
49
58
  editor,
50
59
  editor.pteCreateTextBlock({
51
- decorators: focusAnnotations.length === 0 ? focusDecorators : [],
60
+ decorators: [],
52
61
  listItem: focusBlock.listItem,
53
62
  level: focusBlock.level,
54
63
  }),
55
64
  )
56
65
 
57
- const [nextBlockPath] = Path.next(focusBlockPath)
58
-
59
- Transforms.select(editor, {
60
- anchor: {path: [nextBlockPath, 0], offset: 0},
61
- focus: {path: [nextBlockPath, 0], offset: 0},
62
- })
63
-
64
66
  return
65
67
  }
66
68
 
67
- const lastFocusBlockChild =
68
- focusBlock.children[focusBlock.children.length - 1]
69
- const atTheEndOfBlock = isEqual(start, {
70
- path: [...focusBlockPath, focusBlock.children.length - 1],
71
- offset: editor.isTextSpan(lastFocusBlockChild)
72
- ? lastFocusBlockChild.text.length
73
- : 0,
74
- })
75
-
76
- if (atTheEndOfBlock && Range.isCollapsed(editor.selection)) {
69
+ if (atTheStartOfBlock && Range.isCollapsed(editor.selection)) {
77
70
  Editor.insertNode(
78
71
  editor,
79
72
  editor.pteCreateTextBlock({
80
- decorators: [],
73
+ decorators: focusAnnotations.length === 0 ? focusDecorators : [],
81
74
  listItem: focusBlock.listItem,
82
75
  level: focusBlock.level,
83
76
  }),
@@ -85,7 +78,7 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
85
78
 
86
79
  const [nextBlockPath] = Path.next(focusBlockPath)
87
80
 
88
- Transforms.setSelection(editor, {
81
+ Transforms.select(editor, {
89
82
  anchor: {path: [nextBlockPath, 0], offset: 0},
90
83
  focus: {path: [nextBlockPath, 0], offset: 0},
91
84
  })
@@ -93,9 +86,11 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
93
86
  return
94
87
  }
95
88
 
89
+ const selectionAcrossBlocks = anchorBlockPath[0] !== focusBlockPath[0]
90
+
96
91
  const isInTheMiddleOfNode = !atTheStartOfBlock && !atTheEndOfBlock
97
92
 
98
- if (isInTheMiddleOfNode) {
93
+ if (isInTheMiddleOfNode && !selectionAcrossBlocks) {
99
94
  Editor.withoutNormalizing(editor, () => {
100
95
  if (!editor.selection) {
101
96
  return
@@ -202,15 +197,17 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
202
197
  return
203
198
  }
204
199
  }
200
+
201
+ Transforms.splitNodes(editor, {always: true})
205
202
  }
206
203
 
207
204
  export const insertSoftBreakActionImplementation: BehaviorActionImplementation<
208
- 'insert soft break'
205
+ 'insert.soft break'
209
206
  > = ({context, action}) => {
210
207
  // This mimics Slate's internal which also just does a regular insert break
211
208
  // when soft-breaking
212
209
  insertBreakActionImplementation({
213
210
  context,
214
- action: {...action, type: 'insert break'},
211
+ action: {...action, type: 'insert.break'},
215
212
  })
216
213
  }
@@ -0,0 +1,58 @@
1
+ import {Editor, Transforms, type Element} from 'slate'
2
+ import {toSlateValue} from '../../utils/values'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const insertInlineObjectActionImplementation: BehaviorActionImplementation<
6
+ 'insert.inline object'
7
+ > = ({context, action}) => {
8
+ if (
9
+ !context.schema.inlineObjects.some(
10
+ (inlineObject) => inlineObject.name === action.inlineObject.name,
11
+ )
12
+ ) {
13
+ console.error('Unable to insert unknown inline object')
14
+ return
15
+ }
16
+
17
+ if (!action.editor.selection) {
18
+ console.error('Unable to insert inline object without selection')
19
+ return
20
+ }
21
+
22
+ const [focusTextBlock] = Array.from(
23
+ Editor.nodes(action.editor, {
24
+ at: action.editor.selection.focus.path,
25
+ match: (node) => action.editor.isTextBlock(node),
26
+ }),
27
+ ).at(0) ?? [undefined, undefined]
28
+
29
+ if (!focusTextBlock) {
30
+ console.error('Unable to perform action without focus text block')
31
+ return
32
+ }
33
+
34
+ const block = toSlateValue(
35
+ [
36
+ {
37
+ _type: context.schema.block.name,
38
+ _key: context.keyGenerator(),
39
+ children: [
40
+ {
41
+ _type: action.inlineObject.name,
42
+ _key: context.keyGenerator(),
43
+ ...(action.inlineObject.value ?? {}),
44
+ },
45
+ ],
46
+ },
47
+ ],
48
+ {schemaTypes: context.schema},
49
+ ).at(0) as unknown as Element
50
+ const child = block?.children.at(0)
51
+
52
+ if (!child) {
53
+ console.error('Unable to insert inline object')
54
+ return
55
+ }
56
+
57
+ Transforms.insertNodes(action.editor, child)
58
+ }
@@ -2,7 +2,7 @@ import {Editor, Transforms} from 'slate'
2
2
  import type {BehaviorActionImplementation} from './behavior.actions'
3
3
 
4
4
  export const insertSpanActionImplementation: BehaviorActionImplementation<
5
- 'insert span'
5
+ 'insert.span'
6
6
  > = ({context, action}) => {
7
7
  if (!action.editor.selection) {
8
8
  console.error('Unable to perform action without selection', action)
@@ -0,0 +1,100 @@
1
+ import {Editor, Transforms} from 'slate'
2
+ import type {BehaviorActionImplementation} from '../behavior/behavior.actions'
3
+ import {createGuards} from '../behavior/behavior.guards'
4
+
5
+ export const toggleListItemActionImplementation: BehaviorActionImplementation<
6
+ 'list item.toggle'
7
+ > = ({context, action}) => {
8
+ const isActive = isListItemActive({
9
+ editor: action.editor,
10
+ listItem: action.listItem,
11
+ })
12
+
13
+ if (isActive) {
14
+ removeListItemActionImplementation({
15
+ context,
16
+ action: {...action, type: 'list item.remove'},
17
+ })
18
+ } else {
19
+ addListItemActionImplementation({
20
+ context,
21
+ action: {...action, type: 'list item.add'},
22
+ })
23
+ }
24
+ }
25
+
26
+ export const removeListItemActionImplementation: BehaviorActionImplementation<
27
+ 'list item.remove'
28
+ > = ({context, action}) => {
29
+ if (!action.editor.selection) {
30
+ return
31
+ }
32
+
33
+ const guards = createGuards(context)
34
+
35
+ const selectedBlocks = [
36
+ ...Editor.nodes(action.editor, {
37
+ at: action.editor.selection,
38
+ match: (node) => guards.isListBlock(node),
39
+ }),
40
+ ]
41
+
42
+ for (const [, at] of selectedBlocks) {
43
+ Transforms.unsetNodes(action.editor, ['listItem', 'level'], {at})
44
+ }
45
+ }
46
+
47
+ export const addListItemActionImplementation: BehaviorActionImplementation<
48
+ 'list item.add'
49
+ > = ({context, action}) => {
50
+ if (!action.editor.selection) {
51
+ return
52
+ }
53
+
54
+ const guards = createGuards(context)
55
+
56
+ const selectedBlocks = [
57
+ ...Editor.nodes(action.editor, {
58
+ at: action.editor.selection,
59
+ match: (node) => guards.isTextBlock(node),
60
+ }),
61
+ ]
62
+
63
+ for (const [, at] of selectedBlocks) {
64
+ Transforms.setNodes(
65
+ action.editor,
66
+ {
67
+ level: 1,
68
+ listItem: action.listItem,
69
+ },
70
+ {at},
71
+ )
72
+ }
73
+ }
74
+
75
+ export function isListItemActive({
76
+ editor,
77
+ listItem,
78
+ }: {
79
+ editor: Editor
80
+ listItem: string
81
+ }): boolean {
82
+ if (!editor.selection) {
83
+ return false
84
+ }
85
+
86
+ const selectedBlocks = [
87
+ ...Editor.nodes(editor, {
88
+ at: editor.selection,
89
+ match: (node) => editor.isTextBlock(node),
90
+ }),
91
+ ]
92
+
93
+ if (selectedBlocks.length > 0) {
94
+ return selectedBlocks.every(
95
+ ([node]) => editor.isListBlock(node) && node.listItem === listItem,
96
+ )
97
+ }
98
+
99
+ return false
100
+ }
@@ -0,0 +1,108 @@
1
+ import {Editor, Transforms} from 'slate'
2
+ import type {BehaviorActionImplementation} from './behavior.actions'
3
+ import {createGuards} from './behavior.guards'
4
+
5
+ export const toggleStyleActionImplementation: BehaviorActionImplementation<
6
+ 'style.toggle'
7
+ > = ({context, action}) => {
8
+ const isActive = isStyleActive({
9
+ editor: action.editor,
10
+ style: action.style,
11
+ })
12
+
13
+ if (isActive) {
14
+ removeStyleActionImplementation({
15
+ context,
16
+ action: {...action, type: 'style.remove'},
17
+ })
18
+ } else {
19
+ addStyleActionImplementation({
20
+ context,
21
+ action: {...action, type: 'style.add'},
22
+ })
23
+ }
24
+ }
25
+
26
+ export const removeStyleActionImplementation: BehaviorActionImplementation<
27
+ 'style.remove'
28
+ > = ({context, action}) => {
29
+ if (!action.editor.selection) {
30
+ return
31
+ }
32
+
33
+ const defaultStyle = context.schema.styles[0].value
34
+ const guards = createGuards(context)
35
+
36
+ const selectedBlocks = [
37
+ ...Editor.nodes(action.editor, {
38
+ at: action.editor.selection,
39
+ match: (node) => guards.isTextBlock(node),
40
+ }),
41
+ ]
42
+
43
+ for (const [, at] of selectedBlocks) {
44
+ Transforms.setNodes(
45
+ action.editor,
46
+ {
47
+ style: defaultStyle,
48
+ },
49
+ {
50
+ at,
51
+ },
52
+ )
53
+ }
54
+ }
55
+
56
+ export const addStyleActionImplementation: BehaviorActionImplementation<
57
+ 'style.add'
58
+ > = ({context, action}) => {
59
+ if (!action.editor.selection) {
60
+ return
61
+ }
62
+
63
+ const guards = createGuards(context)
64
+
65
+ const selectedBlocks = [
66
+ ...Editor.nodes(action.editor, {
67
+ at: action.editor.selection,
68
+ match: (node) => guards.isTextBlock(node),
69
+ }),
70
+ ]
71
+
72
+ for (const [, at] of selectedBlocks) {
73
+ Transforms.setNodes(
74
+ action.editor,
75
+ {
76
+ style: action.style,
77
+ },
78
+ {
79
+ at,
80
+ },
81
+ )
82
+ }
83
+ }
84
+
85
+ export function isStyleActive({
86
+ editor,
87
+ style,
88
+ }: {
89
+ editor: Editor
90
+ style: string
91
+ }): boolean {
92
+ if (!editor.selection) {
93
+ return false
94
+ }
95
+
96
+ const selectedBlocks = [
97
+ ...Editor.nodes(editor, {
98
+ at: editor.selection,
99
+ match: (node) => editor.isTextBlock(node),
100
+ }),
101
+ ]
102
+
103
+ if (selectedBlocks.length > 0) {
104
+ return selectedBlocks.every(([node]) => node.style === style)
105
+ }
106
+
107
+ return false
108
+ }
@@ -0,0 +1,25 @@
1
+ import {Transforms} from 'slate'
2
+ import {toSlateRange} from '../../utils/ranges'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const textBlockSetActionImplementation: BehaviorActionImplementation<
6
+ 'text block.set'
7
+ > = ({action}) => {
8
+ const at = toSlateRange(
9
+ {
10
+ anchor: {path: action.at, offset: 0},
11
+ focus: {path: action.at, offset: 0},
12
+ },
13
+ action.editor,
14
+ )!
15
+
16
+ Transforms.setNodes(
17
+ action.editor,
18
+ {
19
+ ...(action.style ? {style: action.style} : {}),
20
+ ...(action.listItem ? {listItem: action.listItem} : {}),
21
+ ...(action.level ? {level: action.level} : {}),
22
+ },
23
+ {at},
24
+ )
25
+ }
@@ -0,0 +1,17 @@
1
+ import {Transforms} from 'slate'
2
+ import {toSlateRange} from '../../utils/ranges'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const textBlockUnsetActionImplementation: BehaviorActionImplementation<
6
+ 'text block.unset'
7
+ > = ({action}) => {
8
+ const at = toSlateRange(
9
+ {
10
+ anchor: {path: action.at, offset: 0},
11
+ focus: {path: action.at, offset: 0},
12
+ },
13
+ action.editor,
14
+ )!
15
+
16
+ Transforms.unsetNodes(action.editor, action.props, {at})
17
+ }