@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
@@ -0,0 +1,329 @@
1
+ import {
2
+ isKeySegment,
3
+ isPortableTextSpan,
4
+ isPortableTextTextBlock,
5
+ type KeyedSegment,
6
+ type PortableTextBlock,
7
+ type PortableTextListBlock,
8
+ type PortableTextObject,
9
+ type PortableTextSpan,
10
+ type PortableTextTextBlock,
11
+ } from '@sanity/types'
12
+ import {createGuards} from '../editor/behavior/behavior.guards'
13
+ import type {EditorSelector} from '../editor/editor-selector'
14
+
15
+ /**
16
+ * @alpha
17
+ */
18
+ export const selectionIsCollapsed: EditorSelector<boolean> = ({context}) => {
19
+ return (
20
+ JSON.stringify(context.selection?.anchor.path) ===
21
+ JSON.stringify(context.selection?.focus.path) &&
22
+ context.selection?.anchor.offset === context.selection?.focus.offset
23
+ )
24
+ }
25
+
26
+ /**
27
+ * @alpha
28
+ */
29
+ export const getFocusBlock: EditorSelector<
30
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
31
+ > = ({context}) => {
32
+ const key = context.selection
33
+ ? isKeySegment(context.selection.focus.path[0])
34
+ ? context.selection.focus.path[0]._key
35
+ : undefined
36
+ : undefined
37
+
38
+ const node = key
39
+ ? context.value.find((block) => block._key === key)
40
+ : undefined
41
+
42
+ return node && key ? {node, path: [{_key: key}]} : undefined
43
+ }
44
+
45
+ /**
46
+ * @alpha
47
+ */
48
+ export const getFocusListBlock: EditorSelector<
49
+ {node: PortableTextListBlock; path: [KeyedSegment]} | undefined
50
+ > = ({context}) => {
51
+ const guards = createGuards(context)
52
+ const focusBlock = getFocusBlock({context})
53
+
54
+ return focusBlock && guards.isListBlock(focusBlock.node)
55
+ ? {node: focusBlock.node, path: focusBlock.path}
56
+ : undefined
57
+ }
58
+
59
+ /**
60
+ * @alpha
61
+ */
62
+ export const getFocusTextBlock: EditorSelector<
63
+ {node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
64
+ > = ({context}) => {
65
+ const focusBlock = getFocusBlock({context})
66
+
67
+ return focusBlock && isPortableTextTextBlock(focusBlock.node)
68
+ ? {node: focusBlock.node, path: focusBlock.path}
69
+ : undefined
70
+ }
71
+
72
+ /**
73
+ * @alpha
74
+ */
75
+ export const getFocusBlockObject: EditorSelector<
76
+ {node: PortableTextObject; path: [KeyedSegment]} | undefined
77
+ > = ({context}) => {
78
+ const focusBlock = getFocusBlock({context})
79
+
80
+ return focusBlock && !isPortableTextTextBlock(focusBlock.node)
81
+ ? {node: focusBlock.node, path: focusBlock.path}
82
+ : undefined
83
+ }
84
+
85
+ /**
86
+ * @alpha
87
+ */
88
+ export const getFocusChild: EditorSelector<
89
+ | {
90
+ node: PortableTextObject | PortableTextSpan
91
+ path: [KeyedSegment, 'children', KeyedSegment]
92
+ }
93
+ | undefined
94
+ > = ({context}) => {
95
+ const focusBlock = getFocusTextBlock({context})
96
+
97
+ if (!focusBlock) {
98
+ return undefined
99
+ }
100
+
101
+ const key = context.selection
102
+ ? isKeySegment(context.selection.focus.path[2])
103
+ ? context.selection.focus.path[2]._key
104
+ : undefined
105
+ : undefined
106
+
107
+ const node = key
108
+ ? focusBlock.node.children.find((span) => span._key === key)
109
+ : undefined
110
+
111
+ return node && key
112
+ ? {node, path: [...focusBlock.path, 'children', {_key: key}]}
113
+ : undefined
114
+ }
115
+
116
+ /**
117
+ * @alpha
118
+ */
119
+ export const getFocusSpan: EditorSelector<
120
+ | {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
121
+ | undefined
122
+ > = ({context}) => {
123
+ const focusChild = getFocusChild({context})
124
+
125
+ return focusChild && isPortableTextSpan(focusChild.node)
126
+ ? {node: focusChild.node, path: focusChild.path}
127
+ : undefined
128
+ }
129
+
130
+ /**
131
+ * @alpha
132
+ */
133
+ export const getFirstBlock: EditorSelector<
134
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
135
+ > = ({context}) => {
136
+ const node = context.value[0]
137
+
138
+ return node ? {node, path: [{_key: node._key}]} : undefined
139
+ }
140
+
141
+ /**
142
+ * @alpha
143
+ */
144
+ export const getLastBlock: EditorSelector<
145
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
146
+ > = ({context}) => {
147
+ const node = context.value[context.value.length - 1]
148
+ ? context.value[context.value.length - 1]
149
+ : undefined
150
+
151
+ return node ? {node, path: [{_key: node._key}]} : undefined
152
+ }
153
+
154
+ /**
155
+ * @alpha
156
+ */
157
+ export const getSelectedBlocks: EditorSelector<
158
+ Array<{node: PortableTextBlock; path: [KeyedSegment]}>
159
+ > = ({context}) => {
160
+ if (!context.selection) {
161
+ return []
162
+ }
163
+
164
+ const selectedBlocks: Array<{node: PortableTextBlock; path: [KeyedSegment]}> =
165
+ []
166
+ const startKey = context.selection.backward
167
+ ? isKeySegment(context.selection.focus.path[0])
168
+ ? context.selection.focus.path[0]._key
169
+ : undefined
170
+ : isKeySegment(context.selection.anchor.path[0])
171
+ ? context.selection.anchor.path[0]._key
172
+ : undefined
173
+ const endKey = context.selection.backward
174
+ ? isKeySegment(context.selection.anchor.path[0])
175
+ ? context.selection.anchor.path[0]._key
176
+ : undefined
177
+ : isKeySegment(context.selection.focus.path[0])
178
+ ? context.selection.focus.path[0]._key
179
+ : undefined
180
+
181
+ if (!startKey || !endKey) {
182
+ return selectedBlocks
183
+ }
184
+
185
+ for (const block of context.value) {
186
+ if (block._key === startKey) {
187
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
188
+
189
+ if (startKey === endKey) {
190
+ break
191
+ }
192
+ continue
193
+ }
194
+
195
+ if (block._key === endKey) {
196
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
197
+ break
198
+ }
199
+
200
+ if (selectedBlocks.length > 0) {
201
+ selectedBlocks.push({node: block, path: [{_key: block._key}]})
202
+ }
203
+ }
204
+
205
+ return selectedBlocks
206
+ }
207
+
208
+ /**
209
+ * @alpha
210
+ */
211
+ export const getSelectionStartBlock: EditorSelector<
212
+ | {
213
+ node: PortableTextBlock
214
+ path: [KeyedSegment]
215
+ }
216
+ | undefined
217
+ > = ({context}) => {
218
+ if (!context.selection) {
219
+ return undefined
220
+ }
221
+
222
+ const key = context.selection.backward
223
+ ? isKeySegment(context.selection.focus.path[0])
224
+ ? context.selection.focus.path[0]._key
225
+ : undefined
226
+ : isKeySegment(context.selection.anchor.path[0])
227
+ ? context.selection.anchor.path[0]._key
228
+ : undefined
229
+
230
+ const node = key
231
+ ? context.value.find((block) => block._key === key)
232
+ : undefined
233
+
234
+ return node && key ? {node, path: [{_key: key}]} : undefined
235
+ }
236
+
237
+ /**
238
+ * @alpha
239
+ */
240
+ export const getSelectionEndBlock: EditorSelector<
241
+ | {
242
+ node: PortableTextBlock
243
+ path: [KeyedSegment]
244
+ }
245
+ | undefined
246
+ > = ({context}) => {
247
+ if (!context.selection) {
248
+ return undefined
249
+ }
250
+
251
+ const key = context.selection.backward
252
+ ? isKeySegment(context.selection.anchor.path[0])
253
+ ? context.selection.anchor.path[0]._key
254
+ : undefined
255
+ : isKeySegment(context.selection.focus.path[0])
256
+ ? context.selection.focus.path[0]._key
257
+ : undefined
258
+
259
+ const node = key
260
+ ? context.value.find((block) => block._key === key)
261
+ : undefined
262
+
263
+ return node && key ? {node, path: [{_key: key}]} : undefined
264
+ }
265
+
266
+ /**
267
+ * @alpha
268
+ */
269
+ export const getPreviousBlock: EditorSelector<
270
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
271
+ > = ({context}) => {
272
+ let previousBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
273
+ const selectionStartBlock = getSelectionStartBlock({context})
274
+
275
+ if (!selectionStartBlock) {
276
+ return undefined
277
+ }
278
+
279
+ let foundSelectionStartBlock = false
280
+
281
+ for (const block of context.value) {
282
+ if (block._key === selectionStartBlock.node._key) {
283
+ foundSelectionStartBlock = true
284
+ break
285
+ }
286
+
287
+ previousBlock = {node: block, path: [{_key: block._key}]}
288
+ }
289
+
290
+ if (foundSelectionStartBlock && previousBlock) {
291
+ return previousBlock
292
+ }
293
+
294
+ return undefined
295
+ }
296
+
297
+ /**
298
+ * @alpha
299
+ */
300
+ export const getNextBlock: EditorSelector<
301
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
302
+ > = ({context}) => {
303
+ let nextBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
304
+ const selectionEndBlock = getSelectionEndBlock({context})
305
+
306
+ if (!selectionEndBlock) {
307
+ return undefined
308
+ }
309
+
310
+ let foundSelectionEndBlock = false
311
+
312
+ for (const block of context.value) {
313
+ if (block._key === selectionEndBlock.node._key) {
314
+ foundSelectionEndBlock = true
315
+ continue
316
+ }
317
+
318
+ if (foundSelectionEndBlock) {
319
+ nextBlock = {node: block, path: [{_key: block._key}]}
320
+ break
321
+ }
322
+ }
323
+
324
+ if (foundSelectionEndBlock && nextBlock) {
325
+ return nextBlock
326
+ }
327
+
328
+ return undefined
329
+ }
@@ -133,66 +133,6 @@ export interface PortableTextSlateEditor extends ReactEditor {
133
133
  isTextSpan: (value: unknown) => value is PortableTextSpan
134
134
  isListBlock: (value: unknown) => value is PortableTextListBlock
135
135
 
136
- /**
137
- * Increments selected list items levels, or decrements them if `reverse` is true.
138
- *
139
- * @param reverse - if true, decrement instead of incrementing
140
- * @returns True if anything was incremented in the selection
141
- */
142
- pteIncrementBlockLevels: (reverse?: boolean) => boolean
143
-
144
- /**
145
- * Toggle selected blocks as listItem
146
- *
147
- * @param listStyle - Style of list item to toggle on/off
148
- */
149
- pteToggleListItem: (listStyle: string) => void
150
-
151
- /**
152
- * Set selected block as listItem
153
- *
154
- * @param listStyle - Style of list item to set
155
- */
156
- pteSetListItem: (listStyle: string) => void
157
-
158
- /**
159
- * Unset selected block as listItem
160
- *
161
- * @param listStyle - Style of list item to unset
162
- */
163
- pteUnsetListItem: (listStyle: string) => void
164
-
165
- /**
166
- * Ends a list
167
- *
168
- * @returns True if a list was ended in the selection
169
- */
170
- pteEndList: () => boolean
171
-
172
- /**
173
- * Toggle the selected block style
174
- *
175
- * @param style - The style name
176
- *
177
- */
178
- pteToggleBlockStyle: (style: string) => void
179
-
180
- /**
181
- * Test if the current selection has a certain block style
182
- *
183
- * @param style - The style name
184
- *
185
- */
186
- pteHasBlockStyle: (style: string) => boolean
187
-
188
- /**
189
- * Test if the current selection has a certain list style
190
- *
191
- * @param listStyle - Style name to check whether or not the selection has
192
- *
193
- */
194
- pteHasListStyle: (style: string) => boolean
195
-
196
136
  /**
197
137
  * Try to expand the current selection to a word
198
138
  */
@@ -1,61 +1,114 @@
1
1
  import {expect, test} from 'vitest'
2
2
  import {isHotkey, type KeyboardEventLike} from './is-hotkey'
3
3
 
4
- function e(value: string | number, ...modifiers: string[]) {
4
+ function e(
5
+ value: string | number,
6
+ modifiers: Array<'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'> = [],
7
+ ) {
5
8
  return {
6
9
  ...(typeof value === 'string' ? {key: value} : {keyCode: value}),
7
- altKey: modifiers.includes('alt'),
8
- ctrlKey: modifiers.includes('ctrl'),
9
- metaKey: modifiers.includes('meta'),
10
- shiftKey: modifiers.includes('shift'),
10
+ altKey: modifiers.includes('altKey'),
11
+ ctrlKey: modifiers.includes('ctrlKey'),
12
+ metaKey: modifiers.includes('metaKey'),
13
+ shiftKey: modifiers.includes('shiftKey'),
11
14
  } as KeyboardEventLike
12
15
  }
13
16
 
14
- type TestCase = [KeyboardEventLike, string, boolean]
15
-
16
- const testCases = [
17
- [e(83, 'meta'), 'Meta+S', true],
18
- [e(83, 'alt', 'meta'), 'Meta+Alt+s', true],
19
- [e(83, 'meta'), 'meta+s', true],
20
- [e(83, 'meta'), 'cmd+s', true],
21
- [e(32, 'meta'), 'cmd+space', true],
22
- [e(187, 'meta'), 'cmd+=', true],
23
- [e(83, 'ctrl'), 'mod+s', true],
24
- [e(16, 'shift'), 'shift', true],
25
- [e(93, 'meta'), 'meta', true],
26
- [e(65), 'a', true],
27
- [e(83, 'alt', 'meta'), 'cmd+s', false],
28
- [e('a', 'ctrl'), 'a', false],
29
- [e(83, 'alt', 'meta'), 'cmd+alt?+s', true],
30
- [e(83, 'meta'), 'cmd+alt?+s', true],
31
- [e('?'), '?', true],
32
- [e(13), 'enter', true],
33
- [e(65, 'meta'), 'cmd+a', true],
34
- [e(83, 'meta'), 'cmd+s', true],
35
- [e('s', 'meta'), 'Meta+S', true],
36
- [e('ß', 'alt', 'meta'), 'Meta+Alt+ß', true],
37
- [e('s', 'meta'), 'meta+s', true],
38
- [e('s', 'meta'), 'cmd+s', true],
39
- [e(' ', 'meta'), 'cmd+space', true],
40
- [e('+', 'meta'), 'cmd++', true],
41
- [e('s', 'ctrl'), 'mod+s', true],
42
- [e('Shift', 'shift'), 'shift', true],
43
- [e('a'), 'a', true],
44
- [e('s', 'alt', 'meta'), 'cmd+s', false],
45
- [e('a', 'ctrl'), 'a', false],
46
- [e('s', 'alt', 'meta'), 'cmd+alt?+s', true],
47
- [e('s', 'meta'), 'cmd+alt?+s', true],
48
- [e('Enter'), 'enter', true],
49
- [e('a', 'meta'), 'meta+a', true],
50
- [e('s', 'meta'), 'meta+s', true],
17
+ type TestCase = [string, KeyboardEventLike, boolean]
18
+
19
+ const testCases: TestCase[] = [
20
+ ['meta', e('Meta', ['metaKey']), true],
21
+ ['Meta', e('Meta', ['metaKey']), true],
22
+ ['meta', e(93, ['metaKey']), true],
23
+ ['Meta', e(93, ['metaKey']), true],
24
+
25
+ ['meta+s', e('s', ['metaKey']), true],
26
+ ['Meta+S', e('s', ['metaKey']), true],
27
+ ['meta+s', e(83, ['metaKey']), true],
28
+ ['Meta+S', e(83, ['metaKey']), true],
29
+
30
+ ['cmd+space', e(' ', ['metaKey']), true],
31
+ ['Cmd+Space', e(' ', ['metaKey']), true],
32
+ ['cmd+space', e(32, ['metaKey']), true],
33
+ ['Cmd+Space', e(32, ['metaKey']), true],
34
+
35
+ ['cmd+alt?+s', e('s', ['metaKey']), true],
36
+ ['cmd+alt?+s', e('s', ['metaKey', 'altKey']), true],
37
+ ['cmd+alt?+s', e(83, ['metaKey']), true],
38
+ ['cmd+alt?+s', e(83, ['metaKey', 'altKey']), true],
39
+
40
+ ['Cmd+Alt?+S', e('s', ['metaKey']), true],
41
+ ['Cmd+Alt?+S', e('s', ['metaKey', 'altKey']), true],
42
+ ['Cmd+Alt?+S', e(83, ['metaKey']), true],
43
+ ['Cmd+Alt?+S', e(83, ['metaKey', 'altKey']), true],
44
+
45
+ ['cmd+s', e('s', ['metaKey', 'altKey']), false],
46
+ ['Cmd+S', e('s', ['metaKey', 'altKey']), false],
47
+ ['cmd+s', e(83, ['metaKey', 'altKey']), false],
48
+ ['Cmd+S', e(83, ['metaKey', 'altKey']), false],
49
+
50
+ ['cmd+s', e('s', ['metaKey']), true],
51
+ ['Cmd+s', e('s', ['metaKey']), true],
52
+ ['cmd+s', e(83, ['metaKey']), true],
53
+ ['Cmd+s', e(83, ['metaKey']), true],
54
+
55
+ ['mod+s', e('s', ['ctrlKey']), true],
56
+ ['Mod+S', e('s', ['ctrlKey']), true],
57
+ ['mod+s', e(83, ['ctrlKey']), true],
58
+ ['Mod+S', e(83, ['ctrlKey']), true],
59
+
60
+ ['meta+alt+s', e('s', ['metaKey', 'altKey']), true],
61
+ ['Meta+Alt+S', e('s', ['metaKey', 'altKey']), true],
62
+ ['meta+alt+s', e(83, ['metaKey', 'altKey']), true],
63
+ ['Meta+Alt+S', e(83, ['metaKey', 'altKey']), true],
64
+
65
+ ['?', e('?'), true],
66
+ ['?', e('?', ['altKey']), false],
67
+
68
+ ['a', e('a'), true],
69
+ ['a', e('A'), true],
70
+ ['A', e('a'), true],
71
+ ['A', e('A'), true],
72
+ ['a', e(65), true],
73
+ ['A', e(65), true],
74
+
75
+ ['a', e('a', ['ctrlKey']), false],
76
+ ['A', e('a', ['ctrlKey']), false],
77
+ ['a', e(65, ['ctrlKey']), false],
78
+ ['A', e(65, ['ctrlKey']), false],
79
+
80
+ ['shift', e('Shift', ['shiftKey']), true],
81
+ ['Shift', e('Shift', ['shiftKey']), true],
82
+ ['shift', e(16, ['shiftKey']), true],
83
+ ['Shift', e(16, ['shiftKey']), true],
84
+
85
+ ['meta+a', e('a', ['metaKey']), true],
86
+ ['Meta+A', e('a', ['metaKey']), true],
87
+ ['cmd+a', e(65, ['metaKey']), true],
88
+ ['Cmd+A', e(65, ['metaKey']), true],
89
+
90
+ ['enter', e('Enter'), true],
91
+ ['Enter', e('Enter'), true],
92
+ ['enter', e(13), true],
93
+ ['Enter', e(13), true],
94
+ ['enter', e('Enter', ['shiftKey']), false],
95
+ ['Enter', e('Enter', ['shiftKey']), false],
96
+
97
+ ['cmd+=', e(187, ['metaKey']), true],
98
+ ['Cmd+=', e(187, ['metaKey']), true],
99
+ ['cmd++', e('+', ['metaKey']), true],
100
+ ['Cmd++', e('+', ['metaKey']), true],
101
+
102
+ ['meta+alt+ß', e('ß', ['metaKey', 'altKey']), true],
103
+ ['Meta+Alt+ß', e('ß', ['metaKey', 'altKey']), true],
51
104
  ] satisfies Array<TestCase>
52
105
 
53
106
  test(isHotkey.name, () => {
54
107
  for (const testCase of testCases) {
55
- expect(isHotkey(testCase[1], testCase[0])).toBe(testCase[2])
108
+ expect(isHotkey(testCase[0], testCase[1])).toBe(testCase[2])
56
109
  }
57
110
 
58
- expect(() => isHotkey('ctrlalt+k', e('k', 'ctrl', 'alt'))).toThrowError(
59
- 'Unknown modifier: "ctrlalt"',
60
- )
111
+ expect(() =>
112
+ isHotkey('ctrlalt+k', e('k', ['ctrlKey', 'altKey'])),
113
+ ).toThrowError('Unknown modifier: "ctrlalt"')
61
114
  })
@@ -1,6 +1,6 @@
1
1
  export interface KeyboardEventLike {
2
2
  key: string
3
- keyCode: number
3
+ keyCode?: number
4
4
  altKey: boolean
5
5
  ctrlKey: boolean
6
6
  metaKey: boolean
@@ -438,6 +438,11 @@ export function createOperationToPatches(
438
438
  const patches: Patch[] = []
439
439
  const block = beforeValue[operation.path[0]]
440
440
  const targetBlock = beforeValue[operation.newPath[0]]
441
+
442
+ if (!targetBlock) {
443
+ return patches
444
+ }
445
+
441
446
  if (operation.path.length === 1) {
442
447
  const position: InsertPosition =
443
448
  operation.path[0] > operation.newPath[0] ? 'before' : 'after'
@@ -7,10 +7,7 @@ import {
7
7
  type Point,
8
8
  type Path as SlatePath,
9
9
  } from 'slate'
10
- import type {
11
- EditorSelectionPoint,
12
- PortableTextMemberSchemaTypes,
13
- } from '../types/editor'
10
+ import type {PortableTextMemberSchemaTypes} from '../types/editor'
14
11
  import type {ObjectWithKeyAndType} from './ranges'
15
12
 
16
13
  export function createKeyedPath(
@@ -41,10 +38,7 @@ export function createKeyedPath(
41
38
  ) as Path
42
39
  }
43
40
 
44
- export function createArrayedPath(
45
- point: EditorSelectionPoint,
46
- editor: Editor,
47
- ): SlatePath {
41
+ export function toSlatePath(path: Path, editor: Editor): SlatePath {
48
42
  if (!editor) {
49
43
  return []
50
44
  }
@@ -52,8 +46,7 @@ export function createArrayedPath(
52
46
  Editor.nodes(editor, {
53
47
  at: [],
54
48
  match: (n) =>
55
- isKeySegment(point.path[0]) &&
56
- (n as Descendant)._key === point.path[0]._key,
49
+ isKeySegment(path[0]) && (n as Descendant)._key === path[0]._key,
57
50
  }),
58
51
  )[0] || [undefined, undefined]
59
52
  if (!block || !Element.isElement(block)) {
@@ -62,7 +55,7 @@ export function createArrayedPath(
62
55
  if (editor.isVoid(block)) {
63
56
  return [blockPath[0], 0]
64
57
  }
65
- const childPath = [point.path[2]]
58
+ const childPath = [path[2]]
66
59
  const childIndex = block.children.findIndex((child) =>
67
60
  isEqual([{_key: child._key}], childPath),
68
61
  )
@@ -4,7 +4,7 @@ import type {
4
4
  EditorSelectionPoint,
5
5
  PortableTextMemberSchemaTypes,
6
6
  } from '../types/editor'
7
- import {createArrayedPath, createKeyedPath} from './paths'
7
+ import {createKeyedPath, toSlatePath} from './paths'
8
8
 
9
9
  export interface ObjectWithKeyAndType {
10
10
  _key: string
@@ -50,11 +50,11 @@ export function toSlateRange(
50
50
  return null
51
51
  }
52
52
  const anchor = {
53
- path: createArrayedPath(selection.anchor, editor),
53
+ path: toSlatePath(selection.anchor.path, editor),
54
54
  offset: selection.anchor.offset,
55
55
  }
56
56
  const focus = {
57
- path: createArrayedPath(selection.focus, editor),
57
+ path: toSlatePath(selection.focus.path, editor),
58
58
  offset: selection.focus.offset,
59
59
  }
60
60
  if (focus.path.length === 0 || anchor.path.length === 0) {