0
# Editor State Management
1
2
The state management system provides complete control over editor state, transactions, selections, and the plugin system. It forms the core of ProseMirror's reactive architecture.
3
4
## Capabilities
5
6
### Editor State
7
8
Represents the complete state of an editor at a given moment.
9
10
```typescript { .api }
11
/**
12
* The state of a ProseMirror editor
13
*/
14
class EditorState {
15
/** The current document */
16
readonly doc: Node;
17
18
/** The current selection */
19
readonly selection: Selection;
20
21
/** The stored marks at the current position */
22
readonly storedMarks: Mark[] | null;
23
24
/** The schema used by this state */
25
readonly schema: Schema;
26
27
/** The plugins active in this state */
28
readonly plugins: Plugin[];
29
30
/** The current transaction being built */
31
readonly tr: Transaction;
32
33
/**
34
* Create a new editor state
35
*/
36
static create(config: EditorStateConfig): EditorState;
37
38
/**
39
* Apply a transaction to create a new state
40
*/
41
apply(tr: Transaction): EditorState;
42
43
/**
44
* Start a new transaction from this state
45
*/
46
get tr(): Transaction;
47
48
/**
49
* Reconfigure the state with new plugins or schema
50
*/
51
reconfigure(config: { plugins?: Plugin[]; schema?: Schema }): EditorState;
52
53
/**
54
* Serialize the state to JSON
55
*/
56
toJSON(pluginFields?: { [propName: string]: any }): any;
57
58
/**
59
* Create a state from a JSON representation
60
*/
61
static fromJSON(config: EditorStateConfig, json: any, pluginFields?: { [propName: string]: any }): EditorState;
62
}
63
64
interface EditorStateConfig {
65
/** The schema to use */
66
schema?: Schema;
67
68
/** The initial document */
69
doc?: Node;
70
71
/** The initial selection */
72
selection?: Selection;
73
74
/** The initial stored marks */
75
storedMarks?: Mark[];
76
77
/** The plugins to use */
78
plugins?: Plugin[];
79
}
80
```
81
82
**Usage Examples:**
83
84
```typescript
85
import { EditorState } from "@tiptap/pm/state";
86
import { schema } from "@tiptap/pm/schema-basic";
87
88
// Create a new state
89
const state = EditorState.create({
90
schema: schema,
91
doc: schema.node("doc", null, [
92
schema.node("paragraph", null, [schema.text("Hello world!")])
93
])
94
});
95
96
// Apply a transaction
97
const tr = state.tr.insertText("New text", 1);
98
const newState = state.apply(tr);
99
```
100
101
### Transactions
102
103
Represent atomic changes to the editor state.
104
105
```typescript { .api }
106
/**
107
* A transaction represents a set of changes to a document
108
*/
109
class Transaction extends Transform {
110
/** The editor state this transaction started from */
111
readonly doc: Node;
112
113
/** The current selection in this transaction */
114
selection: Selection;
115
116
/** The stored marks in this transaction */
117
storedMarks: Mark[] | null;
118
119
/** Time when this transaction was created */
120
readonly time: number;
121
122
/** Whether this transaction changes the document */
123
readonly docChanged: boolean;
124
125
/** Whether this transaction changes the selection */
126
readonly selectionSet: boolean;
127
128
/** Whether this transaction changes stored marks */
129
readonly storedMarksSet: boolean;
130
131
/** Whether this transaction is generic */
132
isGeneric: boolean;
133
134
/** Metadata attached to this transaction */
135
readonly meta: { [key: string]: any };
136
137
/**
138
* Replace the selection with the given content
139
*/
140
replaceSelection(slice: Slice): Transaction;
141
142
/**
143
* Replace the selection with the given node
144
*/
145
replaceSelectionWith(node: Node, inheritMarks?: boolean): Transaction;
146
147
/**
148
* Delete the current selection
149
*/
150
deleteSelection(): Transaction;
151
152
/**
153
* Insert text at the current position
154
*/
155
insertText(text: string, from?: number, to?: number): Transaction;
156
157
/**
158
* Set the selection
159
*/
160
setSelection(selection: Selection): Transaction;
161
162
/**
163
* Set the stored marks
164
*/
165
setStoredMarks(marks: Mark[] | null): Transaction;
166
167
/**
168
* Ensure the stored marks match the current selection
169
*/
170
ensureMarks(marks: Mark[]): Transaction;
171
172
/**
173
* Add a mark to the stored marks
174
*/
175
addStoredMark(mark: Mark): Transaction;
176
177
/**
178
* Remove a mark from the stored marks
179
*/
180
removeStoredMark(mark: Mark | MarkType): Transaction;
181
182
/**
183
* Set metadata on this transaction
184
*/
185
setMeta(key: string | Plugin | PluginKey, value: any): Transaction;
186
187
/**
188
* Get metadata from this transaction
189
*/
190
getMeta(key: string | Plugin | PluginKey): any;
191
192
/**
193
* Mark this transaction as not mergeable with previous history events
194
*/
195
setTime(time: number): Transaction;
196
197
/**
198
* Scroll the selection into view after applying this transaction
199
*/
200
scrollIntoView(): Transaction;
201
}
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
import { EditorState } from "@tiptap/pm/state";
208
209
// Start a transaction
210
const tr = state.tr;
211
212
// Chain operations
213
tr.insertText("Hello ")
214
.insertText("world!")
215
.setSelection(TextSelection.create(tr.doc, 1, 6));
216
217
// Apply the transaction
218
const newState = state.apply(tr);
219
```
220
221
### Selections
222
223
Represent the current selection in the editor.
224
225
```typescript { .api }
226
/**
227
* Base class for selections
228
*/
229
abstract class Selection {
230
/** Start position of the selection */
231
readonly $from: ResolvedPos;
232
233
/** End position of the selection */
234
readonly $to: ResolvedPos;
235
236
/** Start position as number */
237
readonly from: number;
238
239
/** End position as number */
240
readonly to: number;
241
242
/** Anchor position of the selection */
243
readonly $anchor: ResolvedPos;
244
245
/** Head position of the selection */
246
readonly $head: ResolvedPos;
247
248
/** Anchor position as number */
249
readonly anchor: number;
250
251
/** Head position as number */
252
readonly head: number;
253
254
/** Whether the selection is empty */
255
readonly empty: boolean;
256
257
/**
258
* Map the selection through a position mapping
259
*/
260
map(doc: Node, mapping: Mappable): Selection;
261
262
/**
263
* Get the content of this selection as a slice
264
*/
265
content(): Slice;
266
267
/**
268
* Check if this selection equals another selection
269
*/
270
eq(other: Selection): boolean;
271
272
/**
273
* Convert the selection to JSON
274
*/
275
toJSON(): any;
276
277
/**
278
* Create a selection from JSON
279
*/
280
static fromJSON(doc: Node, json: any): Selection;
281
282
/**
283
* Find a valid selection near the given position
284
*/
285
static near(pos: ResolvedPos, bias?: number): Selection;
286
287
/**
288
* Find the cursor wrapper around the given position
289
*/
290
static atStart(doc: Node): Selection;
291
292
/**
293
* Find the cursor wrapper at the end of the given node
294
*/
295
static atEnd(doc: Node): Selection;
296
}
297
298
/**
299
* A text selection between two positions
300
*/
301
class TextSelection extends Selection {
302
/**
303
* Create a text selection
304
*/
305
constructor($anchor: ResolvedPos, $head?: ResolvedPos);
306
307
/**
308
* Create a text selection between two positions
309
*/
310
static create(doc: Node, anchor: number, head?: number): TextSelection;
311
312
/**
313
* Create a text selection that includes the given range
314
*/
315
static between($anchor: ResolvedPos, $head: ResolvedPos, bias?: number): Selection;
316
}
317
318
/**
319
* A selection that selects a specific node
320
*/
321
class NodeSelection extends Selection {
322
/** The selected node */
323
readonly node: Node;
324
325
/**
326
* Create a node selection
327
*/
328
constructor($pos: ResolvedPos);
329
330
/**
331
* Create a node selection at the given position
332
*/
333
static create(doc: Node, from: number): NodeSelection;
334
335
/**
336
* Check if a node can be selected at the given position
337
*/
338
static isSelectable(node: Node): boolean;
339
}
340
341
/**
342
* A selection that selects everything in the document
343
*/
344
class AllSelection extends Selection {
345
/**
346
* Create an all selection for the given document
347
*/
348
constructor(doc: Node);
349
}
350
```
351
352
### Plugins
353
354
Extensibility system for adding functionality to the editor.
355
356
```typescript { .api }
357
/**
358
* A plugin provides additional functionality to an editor
359
*/
360
class Plugin {
361
/** The plugin's specification */
362
readonly spec: PluginSpec;
363
364
/**
365
* Create a new plugin
366
*/
367
constructor(spec: PluginSpec);
368
369
/**
370
* Get the plugin's state from an editor state
371
*/
372
getState(state: EditorState): any;
373
}
374
375
/**
376
* A key used to identify and access a plugin
377
*/
378
class PluginKey<T = any> {
379
/** The key name */
380
readonly key: string;
381
382
/**
383
* Create a new plugin key
384
*/
385
constructor(name?: string);
386
387
/**
388
* Get the plugin state from an editor state
389
*/
390
getState(state: EditorState): T | undefined;
391
392
/**
393
* Get the plugin from an editor state
394
*/
395
get(state: EditorState): Plugin | undefined;
396
}
397
398
interface PluginSpec {
399
/** Initial state for this plugin */
400
state?: StateField<any>;
401
402
/** Properties to add to the editor */
403
props?: EditorProps;
404
405
/** Key to identify this plugin */
406
key?: PluginKey;
407
408
/** View constructor for this plugin */
409
view?: (view: EditorView) => PluginView;
410
}
411
412
interface StateField<T> {
413
/** Initialize the plugin state */
414
init(config: EditorStateConfig, state: EditorState): T;
415
416
/** Update the plugin state when a transaction is applied */
417
apply(tr: Transaction, value: T, oldState: EditorState, newState: EditorState): T;
418
419
/** Serialize the plugin state to JSON */
420
toJSON?(value: T): any;
421
422
/** Deserialize the plugin state from JSON */
423
fromJSON?(config: EditorStateConfig, value: any, state: EditorState): T;
424
}
425
426
interface PluginView {
427
/** Called when the plugin view is created */
428
update?(view: EditorView, prevState: EditorState): void;
429
430
/** Called when the plugin view is destroyed */
431
destroy?(): void;
432
}
433
```
434
435
**Usage Examples:**
436
437
```typescript
438
import { Plugin, PluginKey, EditorState } from "@tiptap/pm/state";
439
440
// Create a simple plugin
441
const myPlugin = new Plugin({
442
state: {
443
init() {
444
return { clickCount: 0 };
445
},
446
apply(tr, value) {
447
if (tr.getMeta("click")) {
448
return { clickCount: value.clickCount + 1 };
449
}
450
return value;
451
}
452
}
453
});
454
455
// Create a plugin with a key
456
const myKey = new PluginKey("myPlugin");
457
const keyedPlugin = new Plugin({
458
key: myKey,
459
state: {
460
init() {
461
return { data: [] };
462
},
463
apply(tr, value) {
464
return value;
465
}
466
}
467
});
468
469
// Use in state creation
470
const state = EditorState.create({
471
schema: mySchema,
472
plugins: [myPlugin, keyedPlugin]
473
});
474
475
// Access plugin state
476
const pluginState = myKey.getState(state);
477
```
478
479
### Selection Bookmarks
480
481
Lightweight representations of selections for storage and restoration.
482
483
```typescript { .api }
484
/**
485
* A bookmark represents a selection that can be stored and restored
486
*/
487
interface SelectionBookmark {
488
/**
489
* Map the bookmark through a change set
490
*/
491
map(mapping: Mappable): SelectionBookmark;
492
493
/**
494
* Resolve the bookmark to a selection
495
*/
496
resolve(doc: Node): Selection;
497
}
498
```
499
500
## Selection Utilities
501
502
```typescript { .api }
503
/**
504
* Utility functions for working with selections
505
*/
506
507
/**
508
* Check if the given selection can be joined with the one before it
509
*/
510
function canJoin(doc: Node, pos: number): boolean;
511
512
/**
513
* Find the node before the given position
514
*/
515
function findWrapping(range: { $from: ResolvedPos; $to: ResolvedPos }, nodeType: NodeType, attrs?: Attrs): { type: NodeType; attrs: Attrs }[] | null;
516
517
/**
518
* Check if the given range can be lifted out of its parent
519
*/
520
function canLift(state: EditorState, range: { $from: ResolvedPos; $to: ResolvedPos }): boolean;
521
522
/**
523
* Check if the given range can be wrapped in the given node type
524
*/
525
function canWrap(range: { $from: ResolvedPos; $to: ResolvedPos }, nodeType: NodeType, attrs?: Attrs): boolean;
526
```
527
528
## Types
529
530
```typescript { .api }
531
interface EditorProps {
532
/** Called when a transaction is dispatched */
533
dispatchTransaction?: (tr: Transaction) => void;
534
535
/** Called to handle DOM events */
536
handleDOMEvents?: { [event: string]: (view: EditorView, event: Event) => boolean };
537
538
/** Called to handle key events */
539
handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;
540
handleKeyPress?: (view: EditorView, event: KeyboardEvent) => boolean;
541
542
/** Called to handle click events */
543
handleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
544
handleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
545
handleDoubleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
546
handleDoubleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
547
handleTripleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;
548
handleTripleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;
549
550
/** Called to transform pasted content */
551
transformPastedHTML?: (html: string) => string;
552
transformPastedText?: (text: string) => string;
553
554
/** Called when content is pasted */
555
handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean;
556
557
/** Called when content is dragged */
558
handleDrop?: (view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean;
559
560
/** Called to determine if scrolling should happen */
561
handleScrollToSelection?: (view: EditorView) => boolean;
562
563
/** Function to create node views */
564
nodeViews?: { [nodeName: string]: NodeViewConstructor };
565
566
/** Function to add decorations */
567
decorations?: (state: EditorState) => DecorationSet;
568
569
/** Attributes to add to the editor DOM element */
570
attributes?: (state: EditorState) => { [attr: string]: string };
571
572
/** CSS class to add to the editor */
573
class?: string;
574
}
575
576
interface Mappable {
577
map(pos: number, assoc?: number): number;
578
mapResult(pos: number, assoc?: number): MapResult;
579
}
580
581
interface MapResult {
582
pos: number;
583
deleted: boolean;
584
}
585
```