@portabletext/editor 1.12.2 → 1.13.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.12.2",
3
+ "version": "1.13.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -46,7 +46,6 @@
46
46
  "@xstate/react": "^5.0.0",
47
47
  "debug": "^4.3.4",
48
48
  "get-random-values-esm": "^1.0.2",
49
- "is-hotkey-esm": "^1.0.0",
50
49
  "lodash": "^4.17.21",
51
50
  "lodash.startcase": "^4.4.0",
52
51
  "react-compiler-runtime": "19.0.0-beta-df7b47d-20241124",
@@ -60,7 +59,7 @@
60
59
  "@portabletext/toolkit": "^2.0.16",
61
60
  "@sanity/block-tools": "^3.65.1",
62
61
  "@sanity/diff-match-patch": "^3.1.1",
63
- "@sanity/pkg-utils": "^6.11.13",
62
+ "@sanity/pkg-utils": "^6.11.14",
64
63
  "@sanity/schema": "^3.65.1",
65
64
  "@sanity/types": "^3.65.1",
66
65
  "@testing-library/jest-dom": "^6.6.3",
@@ -70,8 +69,8 @@
70
69
  "@types/lodash.startcase": "^4.4.9",
71
70
  "@types/react": "^18.3.12",
72
71
  "@types/react-dom": "^18.3.1",
73
- "@typescript-eslint/eslint-plugin": "^8.15.0",
74
- "@typescript-eslint/parser": "^8.15.0",
72
+ "@typescript-eslint/eslint-plugin": "^8.16.0",
73
+ "@typescript-eslint/parser": "^8.16.0",
75
74
  "@vitejs/plugin-react": "^4.3.4",
76
75
  "@vitest/browser": "^2.1.5",
77
76
  "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
@@ -87,7 +86,7 @@
87
86
  "vite": "^5.4.11",
88
87
  "vitest": "^2.1.5",
89
88
  "vitest-browser-react": "^0.0.4",
90
- "racejar": "1.0.1"
89
+ "racejar": "1.1.0"
91
90
  },
92
91
  "peerDependencies": {
93
92
  "@sanity/block-tools": "^3.65.1",
@@ -112,6 +111,10 @@
112
111
  "dev": "pkg-utils watch",
113
112
  "lint:fix": "biome lint --write .",
114
113
  "test": "vitest --run",
115
- "test:watch": "vitest"
114
+ "test:watch": "vitest",
115
+ "test:chromium": "vitest --run --project chromium",
116
+ "test:chromium:watch": "vitest --project chromium",
117
+ "test:unit": "vitest --run --project unit",
118
+ "test:unit:watch": "vitest --project unit"
116
119
  }
117
120
  }
@@ -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
 
@@ -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.
@@ -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,6 +197,8 @@ 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<
@@ -1,4 +1,10 @@
1
- import {deleteBackward, deleteForward, insertText, Transforms} from 'slate'
1
+ import {
2
+ deleteBackward,
3
+ deleteForward,
4
+ insertText,
5
+ Path,
6
+ Transforms,
7
+ } from 'slate'
2
8
  import {ReactEditor} from 'slate-react'
3
9
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
4
10
  import {toSlateRange} from '../../utils/ranges'
@@ -86,6 +92,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
86
92
  Transforms.unsetNodes(action.editor, action.props, {at})
87
93
  }
88
94
  },
95
+ 'copy': () => {},
89
96
  'delete backward': ({action}) => {
90
97
  deleteBackward(action.editor, action.unit)
91
98
  },
@@ -188,9 +195,54 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
188
195
  'effect': ({action}) => {
189
196
  action.effect()
190
197
  },
191
- 'paste': ({action}) => {
192
- action.editor.insertData(action.clipboardData)
198
+ 'key.down': () => {},
199
+ 'key.up': () => {},
200
+ 'move block': ({action}) => {
201
+ const location = toSlateRange(
202
+ {
203
+ anchor: {
204
+ path: action.blockPath,
205
+ offset: 0,
206
+ },
207
+ focus: {
208
+ path: action.blockPath,
209
+ offset: 0,
210
+ },
211
+ },
212
+ action.editor,
213
+ )
214
+
215
+ if (!location) {
216
+ console.error('Unable to find Slate range from selection points')
217
+ return
218
+ }
219
+
220
+ const newLocation = toSlateRange(
221
+ {
222
+ anchor: {
223
+ path: action.to,
224
+ offset: 0,
225
+ },
226
+ focus: {
227
+ path: action.to,
228
+ offset: 0,
229
+ },
230
+ },
231
+ action.editor,
232
+ )
233
+
234
+ if (!newLocation) {
235
+ console.error('Unable to find Slate range from selection points')
236
+ return
237
+ }
238
+
239
+ Transforms.moveNodes(action.editor, {
240
+ at: location,
241
+ to: newLocation.anchor.path.slice(0, 1),
242
+ mode: 'highest',
243
+ })
193
244
  },
245
+ 'paste': () => {},
194
246
  'select': ({action}) => {
195
247
  const newSelection = toSlateRange(action.selection, action.editor)
196
248
 
@@ -200,6 +252,34 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
200
252
  Transforms.deselect(action.editor)
201
253
  }
202
254
  },
255
+ 'select previous block': ({action}) => {
256
+ if (!action.editor.selection) {
257
+ console.error('Unable to select previous block without a selection')
258
+ return
259
+ }
260
+
261
+ const blockPath = action.editor.selection.focus.path.slice(0, 1)
262
+
263
+ if (!Path.hasPrevious(blockPath)) {
264
+ console.error("There's no previous block to select")
265
+ return
266
+ }
267
+
268
+ const previousBlockPath = Path.previous(blockPath)
269
+
270
+ Transforms.select(action.editor, previousBlockPath)
271
+ },
272
+ 'select next block': ({action}) => {
273
+ if (!action.editor.selection) {
274
+ console.error('Unable to select next block without a selection')
275
+ return
276
+ }
277
+
278
+ const blockPath = action.editor.selection.focus.path.slice(0, 1)
279
+ const nextBlockPath = [blockPath[0] + 1]
280
+
281
+ Transforms.select(action.editor, nextBlockPath)
282
+ },
203
283
  'reselect': ({action}) => {
204
284
  const selection = action.editor.selection
205
285
 
@@ -253,6 +333,13 @@ export function performAction({
253
333
  })
254
334
  break
255
335
  }
336
+ case 'move block': {
337
+ behaviorActionImplementations['move block']({
338
+ context,
339
+ action,
340
+ })
341
+ break
342
+ }
256
343
  case 'set block': {
257
344
  behaviorActionImplementations['set block']({
258
345
  context,
@@ -281,6 +368,20 @@ export function performAction({
281
368
  })
282
369
  break
283
370
  }
371
+ case 'select previous block': {
372
+ behaviorActionImplementations['select previous block']({
373
+ context,
374
+ action,
375
+ })
376
+ break
377
+ }
378
+ case 'select next block': {
379
+ behaviorActionImplementations['select next block']({
380
+ context,
381
+ action,
382
+ })
383
+ break
384
+ }
284
385
  case 'reselect': {
285
386
  behaviorActionImplementations.reselect({
286
387
  context,
@@ -323,6 +424,13 @@ function performDefaultAction({
323
424
  })
324
425
  break
325
426
  }
427
+ case 'copy': {
428
+ behaviorActionImplementations.copy({
429
+ context,
430
+ action,
431
+ })
432
+ break
433
+ }
326
434
  case 'decorator.add': {
327
435
  behaviorActionImplementations['decorator.add']({
328
436
  context,
@@ -386,6 +494,20 @@ function performDefaultAction({
386
494
  })
387
495
  break
388
496
  }
497
+ case 'key.down': {
498
+ behaviorActionImplementations['key.down']({
499
+ context,
500
+ action,
501
+ })
502
+ break
503
+ }
504
+ case 'key.up': {
505
+ behaviorActionImplementations['key.up']({
506
+ context,
507
+ action,
508
+ })
509
+ break
510
+ }
389
511
  default: {
390
512
  behaviorActionImplementations.paste({
391
513
  context,
@@ -0,0 +1,86 @@
1
+ import {isHotkey} from '../../utils/is-hotkey'
2
+ import {defineBehavior} from './behavior.types'
3
+ import {
4
+ getFocusBlock,
5
+ getNextBlock,
6
+ getPreviousBlock,
7
+ selectionIsCollapsed,
8
+ } from './behavior.utils'
9
+
10
+ /**
11
+ * @alpha
12
+ */
13
+ export type CodeEditorBehaviorsConfig = {
14
+ moveBlockUpShortcut: string
15
+ moveBlockDownShortcut: string
16
+ }
17
+
18
+ /**
19
+ * @alpha
20
+ */
21
+ export function createCodeEditorBehaviors(config: CodeEditorBehaviorsConfig) {
22
+ return [
23
+ defineBehavior({
24
+ on: 'key.down',
25
+ guard: ({context, event}) => {
26
+ const isAltArrowUp = isHotkey(
27
+ config.moveBlockUpShortcut,
28
+ event.keyboardEvent,
29
+ )
30
+
31
+ if (!isAltArrowUp || !selectionIsCollapsed(context)) {
32
+ return false
33
+ }
34
+
35
+ const focusBlock = getFocusBlock(context)
36
+ const previousBlock = getPreviousBlock(context)
37
+
38
+ if (focusBlock && previousBlock) {
39
+ return {focusBlock, previousBlock}
40
+ }
41
+
42
+ return false
43
+ },
44
+ actions: [
45
+ (_, {focusBlock, previousBlock}) => [
46
+ {
47
+ type: 'move block',
48
+ blockPath: focusBlock.path,
49
+ to: previousBlock.path,
50
+ },
51
+ ],
52
+ ],
53
+ }),
54
+ defineBehavior({
55
+ on: 'key.down',
56
+ guard: ({context, event}) => {
57
+ const isAltArrowDown = isHotkey(
58
+ config.moveBlockDownShortcut,
59
+ event.keyboardEvent,
60
+ )
61
+
62
+ if (!isAltArrowDown || !selectionIsCollapsed(context)) {
63
+ return false
64
+ }
65
+
66
+ const focusBlock = getFocusBlock(context)
67
+ const nextBlock = getNextBlock(context)
68
+
69
+ if (focusBlock && nextBlock) {
70
+ return {focusBlock, nextBlock}
71
+ }
72
+
73
+ return false
74
+ },
75
+ actions: [
76
+ (_, {focusBlock, nextBlock}) => [
77
+ {
78
+ type: 'move block',
79
+ blockPath: focusBlock.path,
80
+ to: nextBlock.path,
81
+ },
82
+ ],
83
+ ],
84
+ }),
85
+ ]
86
+ }
@@ -1,4 +1,5 @@
1
1
  import {isPortableTextTextBlock} from '@sanity/types'
2
+ import {isHotkey} from '../../utils/is-hotkey'
2
3
  import {defineBehavior} from './behavior.types'
3
4
  import {
4
5
  getFocusBlockObject,
@@ -9,12 +10,42 @@ import {
9
10
  selectionIsCollapsed,
10
11
  } from './behavior.utils'
11
12
 
13
+ const arrowDownOnLonelyBlockObject = defineBehavior({
14
+ on: 'key.down',
15
+ guard: ({context, event}) => {
16
+ const isArrowDown = isHotkey('ArrowDown', event.keyboardEvent)
17
+ const focusBlockObject = getFocusBlockObject(context)
18
+ const nextBlock = getNextBlock(context)
19
+
20
+ return isArrowDown && focusBlockObject && !nextBlock
21
+ },
22
+ actions: [() => [{type: 'insert text block', placement: 'after'}]],
23
+ })
24
+
25
+ const arrowUpOnLonelyBlockObject = defineBehavior({
26
+ on: 'key.down',
27
+ guard: ({context, event}) => {
28
+ const isArrowUp = isHotkey('ArrowUp', event.keyboardEvent)
29
+ const focusBlockObject = getFocusBlockObject(context)
30
+ const previousBlock = getPreviousBlock(context)
31
+
32
+ return isArrowUp && focusBlockObject && !previousBlock
33
+ },
34
+ actions: [
35
+ () => [
36
+ {type: 'insert text block', placement: 'before'},
37
+ {type: 'select previous block'},
38
+ ],
39
+ ],
40
+ })
41
+
12
42
  const breakingBlockObject = defineBehavior({
13
43
  on: 'insert break',
14
44
  guard: ({context}) => {
15
45
  const focusBlockObject = getFocusBlockObject(context)
46
+ const collapsedSelection = selectionIsCollapsed(context)
16
47
 
17
- return !!focusBlockObject
48
+ return collapsedSelection && focusBlockObject !== undefined
18
49
  },
19
50
  actions: [() => [{type: 'insert text block', placement: 'after'}]],
20
51
  })
@@ -94,6 +125,8 @@ const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
94
125
  })
95
126
 
96
127
  export const coreBlockObjectBehaviors = {
128
+ arrowDownOnLonelyBlockObject,
129
+ arrowUpOnLonelyBlockObject,
97
130
  breakingBlockObject,
98
131
  deletingEmptyTextBlockAfterBlockObject,
99
132
  deletingEmptyTextBlockBeforeBlockObject,
@@ -16,6 +16,8 @@ export const coreBehaviors = [
16
16
  coreDecoratorBehaviors.decoratorAdd,
17
17
  coreDecoratorBehaviors.decoratorRemove,
18
18
  coreDecoratorBehaviors.decoratorToggle,
19
+ coreBlockObjectBehaviors.arrowDownOnLonelyBlockObject,
20
+ coreBlockObjectBehaviors.arrowUpOnLonelyBlockObject,
19
21
  coreBlockObjectBehaviors.breakingBlockObject,
20
22
  coreBlockObjectBehaviors.deletingEmptyTextBlockAfterBlockObject,
21
23
  coreBlockObjectBehaviors.deletingEmptyTextBlockBeforeBlockObject,
@@ -20,7 +20,7 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
20
20
  on: 'paste',
21
21
  guard: ({context, event}) => {
22
22
  const selectionCollapsed = selectionIsCollapsed(context)
23
- const text = event.clipboardData.getData('text/plain')
23
+ const text = event.data.getData('text/plain')
24
24
  const url = looksLikeUrl(text) ? text : undefined
25
25
  const annotation =
26
26
  url !== undefined
@@ -52,7 +52,7 @@ export function createLinkBehaviors(config: LinkBehaviorsConfig) {
52
52
  return false
53
53
  }
54
54
 
55
- const text = event.clipboardData.getData('text/plain')
55
+ const text = event.data.getData('text/plain')
56
56
  const url = looksLikeUrl(text) ? text : undefined
57
57
  const annotation =
58
58
  url !== undefined
@@ -190,7 +190,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
190
190
  const automaticHrOnPaste = defineBehavior({
191
191
  on: 'paste',
192
192
  guard: ({context, event}) => {
193
- const text = event.clipboardData.getData('text/plain')
193
+ const text = event.data.getData('text/plain')
194
194
  const hrRegExp = /^(---)$|(___)$|(\*\*\*)$/gm
195
195
  const hrCharacters = text.match(hrRegExp)?.[0]
196
196
  const hrObject = config.horizontalRuleObject?.({
@@ -45,6 +45,10 @@ export type BehaviorEvent =
45
45
  value: {[prop: string]: unknown}
46
46
  }
47
47
  }
48
+ | {
49
+ type: 'copy'
50
+ data: DataTransfer
51
+ }
48
52
  | {
49
53
  type: 'decorator.add'
50
54
  decorator: string
@@ -81,7 +85,21 @@ export type BehaviorEvent =
81
85
  }
82
86
  | {
83
87
  type: 'paste'
84
- clipboardData: NonNullable<ClipboardEvent['clipboardData']>
88
+ data: DataTransfer
89
+ }
90
+ | {
91
+ type: 'key.down'
92
+ keyboardEvent: Pick<
93
+ KeyboardEvent,
94
+ 'key' | 'code' | 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'
95
+ >
96
+ }
97
+ | {
98
+ type: 'key.up'
99
+ keyboardEvent: Pick<
100
+ KeyboardEvent,
101
+ 'key' | 'code' | 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'
102
+ >
85
103
  }
86
104
 
87
105
  /**
@@ -127,6 +145,11 @@ export type BehaviorActionIntend =
127
145
  children?: PortableTextTextBlock['children']
128
146
  }
129
147
  }
148
+ | {
149
+ type: 'move block'
150
+ blockPath: [KeyedSegment]
151
+ to: [KeyedSegment]
152
+ }
130
153
  | {
131
154
  type: 'set block'
132
155
  paths: Array<[KeyedSegment]>
@@ -156,6 +179,12 @@ export type BehaviorActionIntend =
156
179
  type: 'select'
157
180
  selection: EditorSelection
158
181
  }
182
+ | {
183
+ type: 'select previous block'
184
+ }
185
+ | {
186
+ type: 'select next block'
187
+ }
159
188
  | {
160
189
  type: 'reselect'
161
190
  }
@@ -208,7 +237,13 @@ export type BehaviorActionIntendSet<
208
237
  event: PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
209
238
  },
210
239
  guardResponse: TGuardResponse,
211
- ) => Array<BehaviorActionIntend>
240
+ ) => Array<
241
+ OmitFromUnion<
242
+ BehaviorActionIntend,
243
+ 'type',
244
+ 'copy' | 'key.down' | 'key.up' | 'paste'
245
+ >
246
+ >
212
247
 
213
248
  /**
214
249
  * @alpha