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

view-and-rendering.mddocs/

0

# View and Rendering

1

2

The view system handles DOM rendering, user interaction, and visual decorations. It bridges between the abstract document model and the browser's DOM.

3

4

## Capabilities

5

6

### Editor View

7

8

The main interface for rendering and interacting with ProseMirror documents.

9

10

```typescript { .api }

11

/**

12

* The view component of a ProseMirror editor

13

*/

14

class EditorView {

15

/** The view's current state */

16

state: EditorState;

17

18

/** The DOM element containing the editor */

19

readonly dom: Element;

20

21

/** Whether the editor is editable */

22

editable: boolean;

23

24

/** Whether the editor has focus */

25

readonly hasFocus: boolean;

26

27

/** The root DOM element */

28

readonly root: Document | DocumentFragment;

29

30

/**

31

* Create a new editor view

32

*/

33

constructor(place: Element | ((p: Element) => void) | { mount: Element }, props: EditorProps);

34

35

/**

36

* Update the view's state

37

*/

38

updateState(state: EditorState): void;

39

40

/**

41

* Dispatch a transaction

42

*/

43

dispatch(tr: Transaction): void;

44

45

/**

46

* Focus the editor

47

*/

48

focus(): void;

49

50

/**

51

* Get the DOM position corresponding to the given document position

52

*/

53

coordsAtPos(pos: number, side?: number): { left: number; right: number; top: number; bottom: number };

54

55

/**

56

* Get the document position at the given DOM coordinates

57

*/

58

posAtCoords(coords: { left: number; top: number }): { pos: number; inside: number } | null;

59

60

/**

61

* Get the document position at the given DOM node and offset

62

*/

63

posAtDOM(node: Node, offset: number, bias?: number): number;

64

65

/**

66

* Get the DOM node and offset at the given document position

67

*/

68

domAtPos(pos: number, side?: number): { node: Node; offset: number };

69

70

/**

71

* Get the node view for the given DOM node

72

*/

73

nodeViewAtPos(pos: number): NodeView;

74

75

/**

76

* Get the document position before the given DOM node

77

*/

78

posBeforeNode(node: Node): number;

79

80

/**

81

* Get the document position after the given DOM node

82

*/

83

posAfterNode(node: Node): number;

84

85

/**

86

* Destroy the view

87

*/

88

destroy(): void;

89

90

/**

91

* Set the view's props

92

*/

93

setProps(props: EditorProps): void;

94

95

/**

96

* Update the view

97

*/

98

update(props: EditorProps): void;

99

}

100

101

interface EditorProps {

102

/** The editor state */

103

state: EditorState;

104

105

/** Function to dispatch transactions */

106

dispatchTransaction?: (tr: Transaction) => void;

107

108

/** Whether the editor should be editable */

109

editable?: (state: EditorState) => boolean;

110

111

/** Attributes for the editor DOM element */

112

attributes?: (state: EditorState) => { [name: string]: string } | null;

113

114

/** Function to handle DOM events */

115

handleDOMEvents?: { [name: string]: (view: EditorView, event: Event) => boolean };

116

117

/** Function to handle keyboard events */

118

handleKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean;

119

handleKeyPress?: (view: EditorView, event: KeyboardEvent) => boolean;

120

121

/** Function to handle click events */

122

handleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;

123

handleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;

124

handleDoubleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;

125

handleDoubleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;

126

handleTripleClick?: (view: EditorView, pos: number, event: MouseEvent) => boolean;

127

handleTripleClickOn?: (view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean;

128

129

/** Function to handle paste events */

130

handlePaste?: (view: EditorView, event: ClipboardEvent, slice: Slice) => boolean;

131

handleDrop?: (view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean;

132

133

/** Function to transform pasted content */

134

transformPastedHTML?: (html: string, view: EditorView) => string;

135

transformPastedText?: (text: string, view: EditorView) => string;

136

transformCopied?: (slice: Slice, view: EditorView) => Slice;

137

138

/** Function to create custom node views */

139

nodeViews?: { [name: string]: NodeViewConstructor };

140

141

/** Function to create decorations */

142

decorations?: (state: EditorState) => DecorationSet | null;

143

144

/** Function to handle scroll to selection */

145

handleScrollToSelection?: (view: EditorView) => boolean;

146

147

/** CSS class for the editor */

148

class?: string;

149

}

150

```

151

152

**Usage Examples:**

153

154

```typescript

155

import { EditorView } from "@tiptap/pm/view";

156

import { EditorState } from "@tiptap/pm/state";

157

158

// Create a view

159

const view = new EditorView(document.querySelector("#editor"), {

160

state: EditorState.create({ schema: mySchema }),

161

dispatchTransaction(tr) {

162

const newState = view.state.apply(tr);

163

view.updateState(newState);

164

}

165

});

166

167

// Handle events

168

const view2 = new EditorView(document.querySelector("#editor2"), {

169

state: myState,

170

handleClick(view, pos, event) {

171

console.log("Clicked at position", pos);

172

return false; // Let other handlers run

173

},

174

handleKeyDown(view, event) {

175

if (event.key === "Escape") {

176

view.dom.blur();

177

return true; // Prevent other handlers

178

}

179

return false;

180

}

181

});

182

```

183

184

### Decorations

185

186

Visual modifications to the document that don't change its content.

187

188

```typescript { .api }

189

/**

190

* A decoration represents a visual modification to the document

191

*/

192

abstract class Decoration {

193

/** Start position of the decoration */

194

readonly from: number;

195

196

/** End position of the decoration */

197

readonly to: number;

198

199

/** Type of the decoration */

200

readonly type: DecorationSpec;

201

202

/**

203

* Create a widget decoration

204

*/

205

static widget(pos: number, dom: Element, spec?: WidgetDecorationSpec): Decoration;

206

207

/**

208

* Create an inline decoration

209

*/

210

static inline(from: number, to: number, attrs: DecorationAttrs, spec?: InlineDecorationSpec): Decoration;

211

212

/**

213

* Create a node decoration

214

*/

215

static node(from: number, to: number, attrs: DecorationAttrs, spec?: NodeDecorationSpec): Decoration;

216

217

/**

218

* Map the decoration through a change

219

*/

220

map(mapping: Mappable, node: Node, offset: number): Decoration | null;

221

222

/**

223

* Check if this decoration is equal to another

224

*/

225

eq(other: Decoration): boolean;

226

}

227

228

/**

229

* A set of decorations

230

*/

231

class DecorationSet {

232

/** The decorations in this set */

233

readonly local: Decoration[];

234

235

/** The child decoration sets */

236

readonly children: DecorationSet[];

237

238

/**

239

* The empty decoration set

240

*/

241

static empty: DecorationSet;

242

243

/**

244

* Create a decoration set from an array of decorations

245

*/

246

static create(doc: Node, decorations: Decoration[]): DecorationSet;

247

248

/**

249

* Find decorations in this set

250

*/

251

find(start?: number, end?: number, predicate?: (spec: any) => boolean): Decoration[];

252

253

/**

254

* Map this decoration set through a set of changes

255

*/

256

map(mapping: Mappable, doc: Node, options?: { onRemove?: (decorationSpec: any) => void }): DecorationSet;

257

258

/**

259

* Add decorations to this set

260

*/

261

add(doc: Node, decorations: Decoration[]): DecorationSet;

262

263

/**

264

* Remove decorations from this set

265

*/

266

remove(decorations: Decoration[]): DecorationSet;

267

}

268

269

interface DecorationSpec {

270

/** Include decorations from child nodes */

271

inclusiveStart?: boolean;

272

inclusiveEnd?: boolean;

273

274

/** Arbitrary data associated with the decoration */

275

spec?: any;

276

}

277

278

interface WidgetDecorationSpec extends DecorationSpec {

279

/** Side of the position to place the widget */

280

side?: number;

281

282

/** Marks to apply to the widget */

283

marks?: Mark[];

284

285

/** Stop events on this widget */

286

stopEvent?: (event: Event) => boolean;

287

288

/** Ignore mutations in this widget */

289

ignoreSelection?: boolean;

290

291

/** Key for widget deduplication */

292

key?: string;

293

}

294

295

interface InlineDecorationSpec extends DecorationSpec {

296

/** Include start boundary */

297

inclusiveStart?: boolean;

298

299

/** Include end boundary */

300

inclusiveEnd?: boolean;

301

}

302

303

interface NodeDecorationSpec extends DecorationSpec {

304

/** Remove the node if empty */

305

destroyEmpty?: boolean;

306

}

307

308

type DecorationAttrs = { [attr: string]: string };

309

```

310

311

**Usage Examples:**

312

313

```typescript

314

import { Decoration, DecorationSet } from "@tiptap/pm/view";

315

316

// Create decorations

317

const decorations = [

318

// Add a widget at position 10

319

Decoration.widget(10, document.createElement("span")),

320

321

// Add inline styling from position 5 to 15

322

Decoration.inline(5, 15, { class: "highlight" }),

323

324

// Add node styling to a node

325

Decoration.node(20, 25, { class: "special-node" })

326

];

327

328

// Create a decoration set

329

const decorationSet = DecorationSet.create(doc, decorations);

330

331

// Use in a plugin

332

const highlightPlugin = new Plugin({

333

props: {

334

decorations(state) {

335

// Find positions to highlight

336

const decorations = [];

337

state.doc.descendants((node, pos) => {

338

if (node.isText && node.text.includes("highlight")) {

339

decorations.push(

340

Decoration.inline(pos, pos + node.nodeSize, { class: "highlighted" })

341

);

342

}

343

});

344

return DecorationSet.create(state.doc, decorations);

345

}

346

}

347

});

348

```

349

350

### Node Views

351

352

Custom rendering and behavior for specific node types.

353

354

```typescript { .api }

355

/**

356

* Interface for custom node views

357

*/

358

interface NodeView {

359

/** The DOM element representing this node */

360

dom: Element;

361

362

/** The DOM element holding the node's content */

363

contentDOM?: Element;

364

365

/**

366

* Update the node view when the node changes

367

*/

368

update?(node: Node, decorations: Decoration[], innerDecorations: DecorationSource): boolean;

369

370

/**

371

* Select the node view

372

*/

373

selectNode?(): void;

374

375

/**

376

* Deselect the node view

377

*/

378

deselectNode?(): void;

379

380

/**

381

* Set the selection inside the node view

382

*/

383

setSelection?(anchor: number, head: number, root: Document | ShadowRoot): void;

384

385

/**

386

* Stop an event from bubbling

387

*/

388

stopEvent?(event: Event): boolean;

389

390

/**

391

* Ignore mutations in the content

392

*/

393

ignoreMutation?(mutation: MutationRecord): boolean;

394

395

/**

396

* Destroy the node view

397

*/

398

destroy?(): void;

399

}

400

401

/**

402

* Constructor for node views

403

*/

404

type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number, decorations: Decoration[], innerDecorations: DecorationSource) => NodeView;

405

406

/**

407

* Source of decorations for a node view

408

*/

409

interface DecorationSource {

410

forChild(node: Node, type: string): DecorationSource;

411

eq(other: DecorationSource): boolean;

412

}

413

```

414

415

**Usage Examples:**

416

417

```typescript

418

import { EditorView } from "@tiptap/pm/view";

419

420

// Create a custom node view

421

function createImageView(node, view, getPos) {

422

const dom = document.createElement("div");

423

dom.className = "image-node";

424

425

const img = document.createElement("img");

426

img.src = node.attrs.src;

427

img.alt = node.attrs.alt;

428

dom.appendChild(img);

429

430

return {

431

dom,

432

update(node) {

433

if (node.type.name !== "image") return false;

434

img.src = node.attrs.src;

435

img.alt = node.attrs.alt;

436

return true;

437

},

438

selectNode() {

439

dom.classList.add("selected");

440

},

441

deselectNode() {

442

dom.classList.remove("selected");

443

}

444

};

445

}

446

447

// Use the node view

448

const view = new EditorView(element, {

449

state: myState,

450

nodeViews: {

451

image: createImageView

452

}

453

});

454

```

455

456

### Mark Views

457

458

Custom rendering for mark types.

459

460

```typescript { .api }

461

/**

462

* Interface for custom mark views

463

*/

464

interface MarkView {

465

/** The DOM element representing this mark */

466

dom: Element;

467

468

/**

469

* Update the mark view when the mark changes

470

*/

471

update?(mark: Mark): boolean;

472

473

/**

474

* Destroy the mark view

475

*/

476

destroy?(): void;

477

}

478

479

/**

480

* Constructor for mark views

481

*/

482

type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView;

483

```

484

485

## Coordinate and Position Utilities

486

487

```typescript { .api }

488

/**

489

* Utilities for working with coordinates and positions

490

*/

491

492

/**

493

* Get the bounding box for a range of content

494

*/

495

function coordsAtPos(view: EditorView, pos: number, side?: number): { left: number; right: number; top: number; bottom: number };

496

497

/**

498

* Get the position at the given coordinates

499

*/

500

function posAtCoords(view: EditorView, coords: { left: number; top: number }): { pos: number; inside: number } | null;

501

502

/**

503

* Check if a position is at the start of a text block

504

*/

505

function atStartOfTextblock(view: EditorView, dir: "up" | "down" | "left" | "right" | "forward" | "backward"): boolean;

506

507

/**

508

* Check if a position is at the end of a text block

509

*/

510

function atEndOfTextblock(view: EditorView, dir: "up" | "down" | "left" | "right" | "forward" | "backward"): boolean;

511

```

512

513

## Types

514

515

```typescript { .api }

516

interface Mappable {

517

map(pos: number, assoc?: number): number;

518

mapResult(pos: number, assoc?: number): MapResult;

519

}

520

521

interface MapResult {

522

pos: number;

523

deleted: boolean;

524

}

525

526

type DOMNode = Element | Text | Comment;

527

528

interface ClipboardEvent extends Event {

529

clipboardData: DataTransfer;

530

}

531

532

interface DragEvent extends MouseEvent {

533

dataTransfer: DataTransfer;

534

}

535

```