0
# Vue Renderers
1
2
Utilities for creating custom Vue-based node and mark views, enabling full Vue component integration within editor content.
3
4
## Capabilities
5
6
### VueRenderer Class
7
8
Utility class for rendering Vue components inside the editor with full component lifecycle support.
9
10
```typescript { .api }
11
/**
12
* Renders Vue components inside the editor
13
* Handles component lifecycle and Vue context integration
14
*/
15
class VueRenderer {
16
constructor(component: Component, options: VueRendererOptions);
17
18
/** The editor instance */
19
editor: Editor;
20
21
/** The Vue component to render */
22
component: Component;
23
24
/** The DOM element container */
25
el: Element | null;
26
27
/** Reactive props object */
28
props: Record<string, any>;
29
30
/** Get the rendered DOM element */
31
get element(): Element | null;
32
33
/** Get the component reference (exposed API or proxy) */
34
get ref(): any;
35
36
/**
37
* Update component props reactively
38
* @param props - New props to merge
39
*/
40
updateProps(props?: Record<string, any>): void;
41
42
/**
43
* Render the component and return rendered details
44
* @private Internal method for component rendering
45
*/
46
renderComponent(): {
47
vNode: ReturnType<typeof h> | null;
48
destroy: () => void;
49
el: Element | null;
50
};
51
52
/** Destroy the renderer and cleanup */
53
destroy(): void;
54
}
55
56
interface VueRendererOptions {
57
/** Editor instance */
58
editor: Editor;
59
/** Initial props for the component */
60
props?: Record<string, any>;
61
}
62
```
63
64
**Key Features:**
65
- Full Vue component lifecycle support
66
- Reactive props updates
67
- Vue app context integration
68
- Support for both Composition and Options API
69
- Automatic cleanup and memory management
70
71
**Usage Examples:**
72
73
```typescript
74
import { VueRenderer } from "@tiptap/vue-3";
75
import MyComponent from "./MyComponent.vue";
76
77
// Create a renderer
78
const renderer = new VueRenderer(MyComponent, {
79
editor: editorInstance,
80
props: {
81
title: "Hello World",
82
count: 0,
83
},
84
});
85
86
// Access the DOM element
87
const element = renderer.element;
88
89
// Access component methods/data
90
const componentRef = renderer.ref;
91
componentRef.someMethod();
92
93
// Update props
94
renderer.updateProps({ count: 1 });
95
96
// Cleanup
97
renderer.destroy();
98
```
99
100
### VueNodeViewRenderer Function
101
102
Creates node view renderers for Vue components, allowing custom node types to be rendered as Vue components.
103
104
```typescript { .api }
105
/**
106
* Creates a node view renderer for Vue components
107
* @param component - Vue component to render
108
* @param options - Optional configuration
109
* @returns NodeViewRenderer function
110
*/
111
function VueNodeViewRenderer(
112
component: Component,
113
options?: VueNodeViewRendererOptions
114
): NodeViewRenderer;
115
116
interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
117
/**
118
* Custom update function to control when node view updates
119
* @param props - Update parameters
120
* @returns Whether to update the node view
121
*/
122
update?: ((props: {
123
oldNode: ProseMirrorNode;
124
oldDecorations: readonly Decoration[];
125
oldInnerDecorations: DecorationSource;
126
newNode: ProseMirrorNode;
127
newDecorations: readonly Decoration[];
128
innerDecorations: DecorationSource;
129
updateProps: () => void;
130
}) => boolean) | null;
131
}
132
133
const nodeViewProps: {
134
editor: { type: PropType<Editor>; required: true };
135
node: { type: PropType<ProseMirrorNode>; required: true };
136
decorations: { type: PropType<DecorationWithType[]>; required: true };
137
selected: { type: PropType<boolean>; required: true };
138
extension: { type: PropType<Node>; required: true };
139
getPos: { type: PropType<() => number | undefined>; required: true };
140
updateAttributes: { type: PropType<(attributes: Record<string, any>) => void>; required: true };
141
deleteNode: { type: PropType<() => void>; required: true };
142
view: { type: PropType<EditorView>; required: true };
143
innerDecorations: { type: PropType<DecorationSource>; required: true };
144
HTMLAttributes: { type: PropType<Record<string, any>>; required: true };
145
};
146
```
147
148
**Usage Examples:**
149
150
```typescript
151
// Define a custom node extension
152
import { Node } from "@tiptap/core";
153
import { VueNodeViewRenderer } from "@tiptap/vue-3";
154
import CustomNodeComponent from "./CustomNodeComponent.vue";
155
156
const CustomNode = Node.create({
157
name: 'customNode',
158
159
group: 'block',
160
161
content: 'inline*',
162
163
parseHTML() {
164
return [{ tag: 'div[data-type="custom-node"]' }];
165
},
166
167
renderHTML({ HTMLAttributes }) {
168
return ['div', { 'data-type': 'custom-node', ...HTMLAttributes }, 0];
169
},
170
171
addNodeView() {
172
return VueNodeViewRenderer(CustomNodeComponent);
173
},
174
});
175
176
// Use in editor
177
const editor = useEditor({
178
extensions: [StarterKit, CustomNode],
179
content: '<p>Hello world</p>',
180
});
181
```
182
183
**Custom Node Component:**
184
185
```typescript
186
<!-- CustomNodeComponent.vue -->
187
<template>
188
<NodeViewWrapper class="custom-node">
189
<div class="node-header">
190
<button @click="deleteNode">Delete</button>
191
<span>{{ node.attrs.title || 'Untitled' }}</span>
192
</div>
193
<NodeViewContent class="content" />
194
</NodeViewWrapper>
195
</template>
196
197
<script setup>
198
import { NodeViewContent, NodeViewWrapper, nodeViewProps } from "@tiptap/vue-3";
199
200
const props = defineProps(nodeViewProps);
201
202
const deleteNode = () => {
203
props.deleteNode();
204
};
205
</script>
206
```
207
208
### VueMarkViewRenderer Function
209
210
Creates mark view renderers for Vue components, allowing custom mark types to be rendered as Vue components.
211
212
```typescript { .api }
213
/**
214
* Creates a mark view renderer for Vue components
215
* @param component - Vue component to render
216
* @param options - Optional configuration
217
* @returns MarkViewRenderer function
218
*/
219
function VueMarkViewRenderer(
220
component: Component,
221
options?: VueMarkViewRendererOptions
222
): MarkViewRenderer;
223
224
interface VueMarkViewRendererOptions extends MarkViewRendererOptions {
225
/** HTML tag to render as */
226
as?: string;
227
/** CSS class name */
228
className?: string;
229
/** HTML attributes */
230
attrs?: { [key: string]: string };
231
}
232
233
const markViewProps: {
234
editor: { type: PropType<Editor>; required: true };
235
mark: { type: PropType<Mark>; required: true };
236
extension: { type: PropType<Mark>; required: true };
237
inline: { type: PropType<boolean>; required: true };
238
view: { type: PropType<EditorView>; required: true };
239
updateAttributes: { type: PropType<(attributes: Record<string, any>) => void>; required: true };
240
HTMLAttributes: { type: PropType<Record<string, any>>; required: true };
241
};
242
243
/**
244
* Component for rendering mark view content
245
*/
246
const MarkViewContent: DefineComponent<{
247
as?: {
248
type: PropType<string>;
249
default: 'span';
250
};
251
}>;
252
```
253
254
**Usage Examples:**
255
256
```typescript
257
// Define a custom mark extension
258
import { Mark } from "@tiptap/core";
259
import { VueMarkViewRenderer } from "@tiptap/vue-3";
260
import CustomMarkComponent from "./CustomMarkComponent.vue";
261
262
const CustomMark = Mark.create({
263
name: 'customMark',
264
265
parseHTML() {
266
return [{ tag: 'span[data-type="custom-mark"]' }];
267
},
268
269
renderHTML({ HTMLAttributes }) {
270
return ['span', { 'data-type': 'custom-mark', ...HTMLAttributes }, 0];
271
},
272
273
addMarkView() {
274
return VueMarkViewRenderer(CustomMarkComponent);
275
},
276
});
277
```
278
279
**Custom Mark Component:**
280
281
```typescript
282
<!-- CustomMarkComponent.vue -->
283
<template>
284
<span class="custom-mark" :style="markStyle">
285
<MarkViewContent />
286
<button @click="removeMark" class="remove-btn">×</button>
287
</span>
288
</template>
289
290
<script setup>
291
import { MarkViewContent, markViewProps } from "@tiptap/vue-3";
292
import { computed } from 'vue';
293
294
const props = defineProps(markViewProps);
295
296
const markStyle = computed(() => ({
297
backgroundColor: props.mark.attrs.color || '#ffeb3b',
298
padding: '2px 4px',
299
borderRadius: '2px',
300
}));
301
302
const removeMark = () => {
303
props.updateAttributes(null); // Remove the mark
304
};
305
</script>
306
```
307
308
### Advanced Renderer Patterns
309
310
**Node View with State Management:**
311
312
```typescript
313
<!-- StatefulNodeComponent.vue -->
314
<template>
315
<NodeViewWrapper>
316
<div class="stateful-node">
317
<div class="controls">
318
<button @click="increment">Count: {{ count }}</button>
319
<button @click="updateAttributes({ persistent: count })">Save</button>
320
</div>
321
<NodeViewContent />
322
</div>
323
</NodeViewWrapper>
324
</template>
325
326
<script setup>
327
import { ref, onMounted } from 'vue';
328
import { NodeViewWrapper, NodeViewContent, nodeViewProps } from "@tiptap/vue-3";
329
330
const props = defineProps(nodeViewProps);
331
332
const count = ref(props.node.attrs.persistent || 0);
333
334
const increment = () => {
335
count.value++;
336
};
337
338
onMounted(() => {
339
// Component lifecycle works normally
340
console.log('Node view mounted');
341
});
342
</script>
343
```
344
345
**Custom Update Logic:**
346
347
```typescript
348
import { VueNodeViewRenderer } from "@tiptap/vue-3";
349
350
const CustomNode = Node.create({
351
addNodeView() {
352
return VueNodeViewRenderer(CustomNodeComponent, {
353
update: ({ oldNode, newNode, updateProps }) => {
354
// Only update if specific attributes changed
355
if (oldNode.attrs.title !== newNode.attrs.title) {
356
updateProps();
357
return true;
358
}
359
return false;
360
},
361
});
362
},
363
});
364
```
365
366
## Types
367
368
```typescript { .api }
369
interface NodeViewProps {
370
editor: Editor;
371
node: ProseMirrorNode;
372
decorations: DecorationWithType[];
373
selected: boolean;
374
extension: Node;
375
getPos: () => number | undefined;
376
updateAttributes: (attributes: Record<string, any>) => void;
377
deleteNode: () => void;
378
view: EditorView;
379
innerDecorations: DecorationSource;
380
HTMLAttributes: Record<string, any>;
381
}
382
383
interface MarkViewProps {
384
editor: Editor;
385
mark: ProseMirrorMark;
386
extension: Mark;
387
inline: boolean;
388
view: EditorView;
389
updateAttributes: (attributes: Record<string, any>) => void;
390
HTMLAttributes: Record<string, any>;
391
}
392
393
type Component = DefineComponent | ComponentOptions;
394
type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
395
type MarkViewRenderer = (props: MarkViewRendererProps) => MarkView;
396
```