@portabletext/editor 1.1.8 → 1.1.10

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.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -59,7 +59,7 @@
59
59
  "@portabletext/toolkit": "^2.0.15",
60
60
  "@sanity/block-tools": "^3.62.2",
61
61
  "@sanity/diff-match-patch": "^3.1.1",
62
- "@sanity/pkg-utils": "^6.11.4",
62
+ "@sanity/pkg-utils": "^6.11.6",
63
63
  "@sanity/schema": "^3.62.2",
64
64
  "@sanity/types": "^3.62.2",
65
65
  "@sanity/ui": "^2.8.10",
@@ -78,7 +78,7 @@
78
78
  "@types/react-dom": "^18.3.1",
79
79
  "@types/ws": "~8.5.12",
80
80
  "@vitejs/plugin-react": "^4.3.3",
81
- "@vitest/browser": "^2.1.3",
81
+ "@vitest/browser": "^2.1.4",
82
82
  "@xstate/react": "^4.1.3",
83
83
  "dotenv": "^16.4.5",
84
84
  "express": "^4.21.1",
@@ -96,7 +96,7 @@
96
96
  "ts-node": "^10.9.2",
97
97
  "typescript": "5.6.3",
98
98
  "vite": "^5.4.10",
99
- "vitest": "^2.1.3",
99
+ "vitest": "^2.1.4",
100
100
  "vitest-browser-react": "^0.0.3",
101
101
  "@sanity/gherkin-driver": "^0.0.1"
102
102
  },
@@ -124,7 +124,7 @@
124
124
  "lint:fix": "biome lint --write .",
125
125
  "test": "vitest --run",
126
126
  "test:watch": "vitest",
127
- "test:e2e": "jest --config=e2e-tests/e2e.config.ts",
128
- "test:e2e:watch": "jest --config=e2e-tests/e2e.config.ts --watch"
127
+ "test:e2e-legacy": "jest --config=e2e-tests/e2e.config.ts",
128
+ "test:e2e-legacy:watch": "jest --config=e2e-tests/e2e.config.ts --watch"
129
129
  }
130
130
  }
@@ -550,14 +550,6 @@ export const PortableTextEditable = forwardRef<
550
550
  if (onBeforeInput) {
551
551
  onBeforeInput(event)
552
552
  }
553
-
554
- if (!event.defaultPrevented && event.inputType === 'insertText') {
555
- editorActor.send({
556
- type: 'before insert text',
557
- nativeEvent: event,
558
- editor: slateEditor,
559
- })
560
- }
561
553
  },
562
554
  [onBeforeInput],
563
555
  )
@@ -646,11 +638,6 @@ export const PortableTextEditable = forwardRef<
646
638
  props.onKeyDown(event)
647
639
  }
648
640
  if (!event.isDefaultPrevented()) {
649
- editorActor.send({
650
- type: 'key down',
651
- nativeEvent: event.nativeEvent,
652
- editor: slateEditor,
653
- })
654
641
  slateEditor.pteWithHotKeys(event)
655
642
  }
656
643
  },
@@ -33,12 +33,29 @@ export const behaviorActionImplementations: BehaviourActionImplementations = {
33
33
  Transforms.setNodes(event.editor, {style: event.style}, {at})
34
34
  }
35
35
  },
36
+ 'delete backward': ({event}) => {
37
+ // Since this calls the native Editor method it will trigger a new behavior
38
+ // event
39
+ Editor.deleteBackward(event.editor, {unit: event.unit})
40
+ },
36
41
  'delete text': ({event}) => {
37
42
  Transforms.delete(event.editor, {
38
43
  at: toSlateRange(event.selection, event.editor)!,
39
44
  })
40
45
  },
46
+ 'insert break': ({event}) => {
47
+ // Since this calls the native Editor method it will trigger a new behavior
48
+ // event
49
+ Editor.insertBreak(event.editor)
50
+ },
51
+ 'insert soft break': ({event}) => {
52
+ // Since this calls the native Editor method it will trigger a new behavior
53
+ // event
54
+ Editor.insertSoftBreak(event.editor)
55
+ },
41
56
  'insert text': ({event}) => {
57
+ // Since this calls the native Editor method it will trigger a new behavior
58
+ // event
42
59
  Editor.insertText(event.editor, event.text)
43
60
  },
44
61
  'insert text block': ({context, event}) => {
@@ -1,37 +1,19 @@
1
- import {isHotkey} from 'is-hotkey-esm'
2
1
  import {defineBehavior} from './behavior.types'
3
2
  import {getFocusBlockObject} from './behavior.utils'
4
3
 
5
- const overwriteSoftReturn = defineBehavior({
6
- on: 'key down',
7
- guard: ({event}) => isHotkey('shift+enter', event.nativeEvent),
8
- actions: [
9
- ({event}) => {
10
- event.nativeEvent.preventDefault()
11
- return {type: 'insert text', text: '\n'}
12
- },
13
- ],
4
+ const softReturn = defineBehavior({
5
+ on: 'insert soft break',
6
+ actions: [() => ({type: 'insert text', text: '\n'})],
14
7
  })
15
8
 
16
- const enterOnVoidBlock = defineBehavior({
17
- on: 'key down',
18
- guard: ({context, event}) => {
19
- const isEnter = isHotkey('enter', event.nativeEvent)
20
-
21
- if (!isEnter) {
22
- return false
23
- }
24
-
9
+ const breakingVoidBlock = defineBehavior({
10
+ on: 'insert break',
11
+ guard: ({context}) => {
25
12
  const focusBlockObject = getFocusBlockObject(context)
26
13
 
27
14
  return !!focusBlockObject
28
15
  },
29
- actions: [
30
- ({event}) => {
31
- event.nativeEvent.preventDefault()
32
- return {type: 'insert text block', decorators: []}
33
- },
34
- ],
16
+ actions: [() => ({type: 'insert text block', decorators: []})],
35
17
  })
36
18
 
37
- export const coreBehaviors = [overwriteSoftReturn, enterOnVoidBlock]
19
+ export const coreBehaviors = [softReturn, breakingVoidBlock]
@@ -1,4 +1,5 @@
1
1
  import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
2
+ import type {TextUnit} from 'slate'
2
3
  import type {
3
4
  EditorSelection,
4
5
  PortableTextMemberSchemaTypes,
@@ -19,14 +20,22 @@ export type BehaviorContext = {
19
20
  */
20
21
  export type BehaviorEvent =
21
22
  | {
22
- type: 'key down'
23
- nativeEvent: KeyboardEvent
24
- editor: PortableTextSlateEditor
23
+ type: 'delete backward'
24
+ unit: TextUnit
25
+ default: () => void
25
26
  }
26
27
  | {
27
- type: 'before insert text'
28
- nativeEvent: InputEvent
29
- editor: PortableTextSlateEditor
28
+ type: 'insert soft break'
29
+ default: () => void
30
+ }
31
+ | {
32
+ type: 'insert break'
33
+ default: () => void
34
+ }
35
+ | {
36
+ type: 'insert text'
37
+ text: string
38
+ default: () => void
30
39
  }
31
40
 
32
41
  /**
@@ -48,9 +57,11 @@ export type BehaviorGuard<
48
57
  */
49
58
  export type BehaviorActionIntend =
50
59
  | {
51
- type: 'insert text'
52
- text: string
53
- }
60
+ [TBehaviorEvent in BehaviorEvent as TBehaviorEvent['type']]: Omit<
61
+ TBehaviorEvent,
62
+ 'default'
63
+ >
64
+ }[BehaviorEvent['type']]
54
65
  | {
55
66
  type: 'insert text block'
56
67
  decorators: Array<string>
@@ -14,6 +14,7 @@ import type {
14
14
  EditorSelection,
15
15
  InvalidValueResolution,
16
16
  PortableTextMemberSchemaTypes,
17
+ PortableTextSlateEditor,
17
18
  } from '../types/editor'
18
19
  import {toPortableTextRange} from '../utils/ranges'
19
20
  import {fromSlateValue} from '../utils/values'
@@ -65,7 +66,11 @@ export type MutationEvent = {
65
66
  type EditorEvent =
66
67
  | {type: 'normalizing'}
67
68
  | {type: 'done normalizing'}
68
- | BehaviorEvent
69
+ | {
70
+ type: 'behavior event'
71
+ behaviorEvent: BehaviorEvent
72
+ editor: PortableTextSlateEditor
73
+ }
69
74
  | BehaviorAction
70
75
  | {
71
76
  type: 'update schema'
@@ -153,13 +158,14 @@ export const editorMachine = setup({
153
158
  pendingEvents: [],
154
159
  }),
155
160
  'handle behavior event': enqueueActions(({context, event, enqueue}) => {
156
- assertEvent(event, ['key down', 'before insert text'])
161
+ assertEvent(event, ['behavior event'])
157
162
 
158
163
  const eventBehaviors = context.behaviors.filter(
159
- (behavior) => behavior.on === event.type,
164
+ (behavior) => behavior.on === event.behaviorEvent.type,
160
165
  )
161
166
 
162
167
  if (eventBehaviors.length === 0) {
168
+ event.behaviorEvent.default()
163
169
  return
164
170
  }
165
171
 
@@ -178,6 +184,7 @@ export const editorMachine = setup({
178
184
  console.warn(
179
185
  `Unable to handle event ${event.type} due to missing selection`,
180
186
  )
187
+ event.behaviorEvent.default()
181
188
  return
182
189
  }
183
190
 
@@ -187,11 +194,13 @@ export const editorMachine = setup({
187
194
  selection,
188
195
  } satisfies BehaviorContext
189
196
 
197
+ let behaviorOverwritten = false
198
+
190
199
  for (const eventBehavior of eventBehaviors) {
191
200
  const shouldRun =
192
201
  eventBehavior.guard?.({
193
202
  context: behaviorContext,
194
- event,
203
+ event: event.behaviorEvent,
195
204
  }) ?? true
196
205
 
197
206
  if (!shouldRun) {
@@ -199,7 +208,10 @@ export const editorMachine = setup({
199
208
  }
200
209
 
201
210
  const actions = eventBehavior.actions.map((action) =>
202
- action({context: behaviorContext, event}, shouldRun),
211
+ action(
212
+ {context: behaviorContext, event: event.behaviorEvent},
213
+ shouldRun,
214
+ ),
203
215
  )
204
216
 
205
217
  for (const action of actions) {
@@ -207,12 +219,18 @@ export const editorMachine = setup({
207
219
  continue
208
220
  }
209
221
 
222
+ behaviorOverwritten = true
223
+
210
224
  enqueue.raise({
211
225
  ...action,
212
226
  editor: event.editor,
213
227
  })
214
228
  }
215
229
  }
230
+
231
+ if (!behaviorOverwritten) {
232
+ event.behaviorEvent.default()
233
+ }
216
234
  }),
217
235
  },
218
236
  actors: {
@@ -244,18 +262,22 @@ export const editorMachine = setup({
244
262
  'loading': {actions: emit({type: 'loading'})},
245
263
  'done loading': {actions: emit({type: 'done loading'})},
246
264
  'update schema': {actions: 'assign schema'},
247
- 'key down': {
248
- actions: ['handle behavior event'],
249
- },
250
- 'before insert text': {
251
- actions: ['handle behavior event'],
252
- },
265
+ 'behavior event': {actions: 'handle behavior event'},
253
266
  'apply block style': {
254
267
  actions: [behaviorActionImplementations['apply block style']],
255
268
  },
269
+ 'delete backward': {
270
+ actions: [behaviorActionImplementations['delete backward']],
271
+ },
256
272
  'delete text': {
257
273
  actions: [behaviorActionImplementations['delete text']],
258
274
  },
275
+ 'insert break': {
276
+ actions: [behaviorActionImplementations['insert break']],
277
+ },
278
+ 'insert soft break': {
279
+ actions: [behaviorActionImplementations['insert soft break']],
280
+ },
259
281
  'insert text': {
260
282
  actions: [behaviorActionImplementations['insert text']],
261
283
  },
@@ -0,0 +1,60 @@
1
+ import type {Editor} from 'slate'
2
+ import type {EditorActor} from '../editor-machine'
3
+
4
+ export function createWithEventListeners(editorActor: EditorActor) {
5
+ return function withEventListeners(editor: Editor) {
6
+ const {deleteBackward, insertBreak, insertSoftBreak, insertText} = editor
7
+
8
+ editor.deleteBackward = (unit) => {
9
+ editorActor.send({
10
+ type: 'behavior event',
11
+ behaviorEvent: {
12
+ type: 'delete backward',
13
+ unit,
14
+ default: () => deleteBackward(unit),
15
+ },
16
+ editor,
17
+ })
18
+ return
19
+ }
20
+
21
+ editor.insertBreak = () => {
22
+ editorActor.send({
23
+ type: 'behavior event',
24
+ behaviorEvent: {
25
+ type: 'insert break',
26
+ default: insertBreak,
27
+ },
28
+ editor,
29
+ })
30
+ return
31
+ }
32
+
33
+ editor.insertSoftBreak = () => {
34
+ editorActor.send({
35
+ type: 'behavior event',
36
+ behaviorEvent: {
37
+ type: 'insert soft break',
38
+ default: insertSoftBreak,
39
+ },
40
+ editor,
41
+ })
42
+ return
43
+ }
44
+
45
+ editor.insertText = (text, options) => {
46
+ editorActor.send({
47
+ type: 'behavior event',
48
+ behaviorEvent: {
49
+ type: 'insert text',
50
+ text,
51
+ default: () => insertText(text, options),
52
+ },
53
+ editor,
54
+ })
55
+ return
56
+ }
57
+
58
+ return editor
59
+ }
60
+ }
@@ -57,6 +57,8 @@ export function createWithInsertBreak(
57
57
  editor,
58
58
  editor.pteCreateTextBlock({
59
59
  decorators: focusAnnotations.length === 0 ? focusDecorators : [],
60
+ listItem: focusBlock.listItem,
61
+ level: focusBlock.level,
60
62
  }),
61
63
  )
62
64
 
@@ -84,6 +86,8 @@ export function createWithInsertBreak(
84
86
  editor,
85
87
  editor.pteCreateTextBlock({
86
88
  decorators: [],
89
+ listItem: focusBlock.listItem,
90
+ level: focusBlock.level,
87
91
  }),
88
92
  )
89
93
 
@@ -383,6 +383,18 @@ export function createWithPortableTextMarkModel(
383
383
  }
384
384
  }
385
385
  }
386
+
387
+ if (atTheBeginningOfSpan && !spanIsEmpty && !!previousSpan) {
388
+ Transforms.insertNodes(editor, {
389
+ _type: 'span',
390
+ _key: editorActor.getSnapshot().context.keyGenerator(),
391
+ text: op.text,
392
+ marks: (previousSpan.marks ?? []).filter((mark) =>
393
+ decorators.includes(mark),
394
+ ),
395
+ })
396
+ return
397
+ }
386
398
  }
387
399
  }
388
400
 
@@ -75,13 +75,19 @@ export function createWithUtils({
75
75
  }
76
76
  }
77
77
 
78
- editor.pteCreateTextBlock = (options: {decorators: Array<string>}) => {
78
+ editor.pteCreateTextBlock = (options: {
79
+ decorators: Array<string>
80
+ listItem?: string
81
+ level?: number
82
+ }) => {
79
83
  const block = toSlateValue(
80
84
  [
81
85
  {
82
86
  _type: schemaTypes.block.name,
83
87
  _key: editorActor.getSnapshot().context.keyGenerator(),
84
88
  style: schemaTypes.styles[0].value || 'normal',
89
+ ...(options.listItem ? {listItem: options.listItem} : {}),
90
+ ...(options.level ? {level: options.level} : {}),
85
91
  markDefs: [],
86
92
  children: [
87
93
  {
@@ -3,6 +3,7 @@ import type {BaseOperation, Editor, Node, NodeEntry} from 'slate'
3
3
  import type {PortableTextSlateEditor} from '../../types/editor'
4
4
  import type {createEditorOptions} from '../../types/options'
5
5
  import {createOperationToPatches} from '../../utils/operationToPatches'
6
+ import {createWithEventListeners} from './create-with-event-listeners'
6
7
  import {createWithEditableAPI} from './createWithEditableAPI'
7
8
  import {createWithInsertBreak} from './createWithInsertBreak'
8
9
  import {createWithMaxBlocks} from './createWithMaxBlocks'
@@ -109,6 +110,7 @@ export const withPlugins = <T extends Editor>(
109
110
  editorActor,
110
111
  schemaTypes,
111
112
  )
113
+ const withEventListeners = createWithEventListeners(editorActor)
112
114
 
113
115
  e.destroy = () => {
114
116
  const originalFunctions = originalFnMap.get(e)
@@ -145,18 +147,20 @@ export const withPlugins = <T extends Editor>(
145
147
 
146
148
  // Ordering is important here, selection dealing last, data manipulation in the middle and core model stuff first.
147
149
  return {
148
- editor: withSchemaTypes(
149
- withObjectKeys(
150
- withPortableTextMarkModel(
151
- withPortableTextBlockStyle(
152
- withPortableTextLists(
153
- withPlaceholderBlock(
154
- withUtils(
155
- withMaxBlocks(
156
- withUndoRedo(
157
- withPatches(
158
- withPortableTextSelections(
159
- withEditableAPI(withInsertBreak(e)),
150
+ editor: withEventListeners(
151
+ withSchemaTypes(
152
+ withObjectKeys(
153
+ withPortableTextMarkModel(
154
+ withPortableTextBlockStyle(
155
+ withPortableTextLists(
156
+ withPlaceholderBlock(
157
+ withUtils(
158
+ withMaxBlocks(
159
+ withUndoRedo(
160
+ withPatches(
161
+ withPortableTextSelections(
162
+ withEditableAPI(withInsertBreak(e)),
163
+ ),
160
164
  ),
161
165
  ),
162
166
  ),
@@ -222,7 +222,11 @@ export interface PortableTextSlateEditor extends ReactEditor {
222
222
  /**
223
223
  * Helper function that creates a text block
224
224
  */
225
- pteCreateTextBlock: (options: {decorators: Array<string>}) => Descendant
225
+ pteCreateTextBlock: (options: {
226
+ decorators: Array<string>
227
+ listItem?: string
228
+ level?: number
229
+ }) => Descendant
226
230
 
227
231
  /**
228
232
  * Undo