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
```