or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collaboration.mdcommands-and-editing.mdcursors-and-enhancements.mdhistory.mdindex.mdinput-and-keymaps.mdmarkdown.mdmenus-and-ui.mdmodel-and-schema.mdschema-definitions.mdstate-management.mdtables.mdtransformations.mdview-and-rendering.md

state-management.mddocs/

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

```