@tiptap/vue-3 2.6.6 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model';
1
+ import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions, Slice } from '@tiptap/pm/model';
2
2
  import { EditorState, Transaction } from '@tiptap/pm/state';
3
- import { Decoration, EditorProps, EditorView, NodeView } from '@tiptap/pm/view';
3
+ import { Decoration, EditorProps, EditorView, NodeView, NodeViewConstructor } from '@tiptap/pm/view';
4
4
  import { Editor } from './Editor.js';
5
5
  import { Extension } from './Extension.js';
6
6
  import { Commands, ExtensionConfig, MarkConfig, NodeConfig } from './index.js';
@@ -79,7 +79,24 @@ export interface EditorOptions {
79
79
  };
80
80
  enableInputRules: EnableRules;
81
81
  enablePasteRules: EnableRules;
82
- enableCoreExtensions: boolean;
82
+ /**
83
+ * Determines whether core extensions are enabled.
84
+ *
85
+ * If set to `false`, all core extensions will be disabled.
86
+ * To disable specific core extensions, provide an object where the keys are the extension names and the values are `false`.
87
+ * Extensions not listed in the object will remain enabled.
88
+ *
89
+ * @example
90
+ * // Disable all core extensions
91
+ * enabledCoreExtensions: false
92
+ *
93
+ * @example
94
+ * // Disable only the keymap core extension
95
+ * enabledCoreExtensions: { keymap: false }
96
+ *
97
+ * @default true
98
+ */
99
+ enableCoreExtensions?: boolean | Partial<Record<'editable' | 'clipboardTextSerializer' | 'commands' | 'focusEvents' | 'keymap' | 'tabindex', false>>;
83
100
  /**
84
101
  * If `true`, the editor will check the content for errors on initialization.
85
102
  * Emitting the `contentError` event if the content is invalid.
@@ -100,6 +117,8 @@ export interface EditorOptions {
100
117
  onFocus: (props: EditorEvents['focus']) => void;
101
118
  onBlur: (props: EditorEvents['blur']) => void;
102
119
  onDestroy: (props: EditorEvents['destroy']) => void;
120
+ onPaste: (e: ClipboardEvent, slice: Slice) => void;
121
+ onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void;
103
122
  }
104
123
  export type HTMLContent = string;
105
124
  export type JSONContent = {
@@ -170,19 +189,18 @@ export type ValuesOf<T> = T[keyof T];
170
189
  export type KeysWithTypeOf<T, Type> = {
171
190
  [P in keyof T]: T[P] extends Type ? P : never;
172
191
  }[keyof T];
192
+ export type Simplify<T> = {
193
+ [KeyType in keyof T]: T[KeyType];
194
+ } & {};
173
195
  export type DecorationWithType = Decoration & {
174
196
  type: NodeType;
175
197
  };
176
- export type NodeViewProps = {
177
- editor: Editor;
178
- node: ProseMirrorNode;
179
- decorations: DecorationWithType[];
198
+ export type NodeViewProps = Simplify<Omit<NodeViewRendererProps, 'decorations'> & {
199
+ decorations: readonly DecorationWithType[];
180
200
  selected: boolean;
181
- extension: Node;
182
- getPos: () => number;
183
201
  updateAttributes: (attributes: Record<string, any>) => void;
184
202
  deleteNode: () => void;
185
- };
203
+ }>;
186
204
  export interface NodeViewRendererOptions {
187
205
  stopEvent: ((props: {
188
206
  event: Event;
@@ -196,14 +214,16 @@ export interface NodeViewRendererOptions {
196
214
  contentDOMElementTag: string;
197
215
  }
198
216
  export type NodeViewRendererProps = {
217
+ node: Parameters<NodeViewConstructor>[0];
218
+ view: Parameters<NodeViewConstructor>[1];
219
+ getPos: () => number;
220
+ decorations: Parameters<NodeViewConstructor>[3];
221
+ innerDecorations: Parameters<NodeViewConstructor>[4];
199
222
  editor: Editor;
200
- node: ProseMirrorNode;
201
- getPos: (() => number) | boolean;
202
- HTMLAttributes: Record<string, any>;
203
- decorations: Decoration[];
204
223
  extension: Node;
224
+ HTMLAttributes: Record<string, any>;
205
225
  };
206
- export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView | {};
226
+ export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
207
227
  export type AnyCommands = Record<string, (...args: any[]) => Command>;
208
228
  export type UnionCommands<T = Command> = UnionToIntersection<ValuesOf<Pick<Commands<T>, KeysWithTypeOf<Commands<T>, {}>>>>;
209
229
  export type RawCommands = {
@@ -1,6 +1,6 @@
1
1
  import { NodeViewProps, NodeViewRenderer, NodeViewRendererOptions } from '@tiptap/core';
2
2
  import { Node as ProseMirrorNode } from '@tiptap/pm/model';
3
- import { Decoration } from '@tiptap/pm/view';
3
+ import { Decoration, DecorationSource } from '@tiptap/pm/view';
4
4
  import { Component, PropType } from 'vue';
5
5
  export declare const nodeViewProps: {
6
6
  editor: {
@@ -39,10 +39,12 @@ export declare const nodeViewProps: {
39
39
  export interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
40
40
  update: ((props: {
41
41
  oldNode: ProseMirrorNode;
42
- oldDecorations: Decoration[];
42
+ oldDecorations: readonly Decoration[];
43
+ oldInnerDecorations: DecorationSource;
43
44
  newNode: ProseMirrorNode;
44
- newDecorations: Decoration[];
45
+ newDecorations: readonly Decoration[];
46
+ innerDecorations: DecorationSource;
45
47
  updateProps: () => void;
46
48
  }) => boolean) | null;
47
49
  }
48
- export declare function VueNodeViewRenderer(component: Component, options?: Partial<VueNodeViewRendererOptions>): NodeViewRenderer;
50
+ export declare function VueNodeViewRenderer(component: Component<NodeViewProps>, options?: Partial<VueNodeViewRendererOptions>): NodeViewRenderer;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/vue-3",
3
3
  "description": "Vue components for tiptap",
4
- "version": "2.6.6",
4
+ "version": "2.7.0",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -29,17 +29,17 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@tiptap/extension-bubble-menu": "^2.6.6",
33
- "@tiptap/extension-floating-menu": "^2.6.6"
32
+ "@tiptap/extension-bubble-menu": "^2.7.0",
33
+ "@tiptap/extension-floating-menu": "^2.7.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@tiptap/core": "^2.6.6",
37
- "@tiptap/pm": "^2.6.6",
36
+ "@tiptap/core": "^2.7.0",
37
+ "@tiptap/pm": "^2.7.0",
38
38
  "vue": "^3.0.0"
39
39
  },
40
40
  "peerDependencies": {
41
- "@tiptap/core": "^2.6.6",
42
- "@tiptap/pm": "^2.6.6",
41
+ "@tiptap/core": "^2.7.0-pre.0",
42
+ "@tiptap/pm": "^2.7.0-pre.0",
43
43
  "vue": "^3.0.0"
44
44
  },
45
45
  "repository": {
package/src/Editor.ts CHANGED
@@ -75,7 +75,9 @@ export class Editor extends CoreEditor {
75
75
  handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[],
76
76
  ): void {
77
77
  super.registerPlugin(plugin, handlePlugins)
78
- this.reactiveState.value = this.view.state
78
+ if (this.reactiveState) {
79
+ this.reactiveState.value = this.view.state
80
+ }
79
81
  }
80
82
 
81
83
  /**
@@ -83,6 +85,8 @@ export class Editor extends CoreEditor {
83
85
  */
84
86
  public unregisterPlugin(nameOrPluginKey: string | PluginKey): void {
85
87
  super.unregisterPlugin(nameOrPluginKey)
86
- this.reactiveState.value = this.view.state
88
+ if (this.reactiveState) {
89
+ this.reactiveState.value = this.view.state
90
+ }
87
91
  }
88
92
  }
@@ -1,20 +1,15 @@
1
+ /* eslint-disable no-underscore-dangle */
1
2
  import {
2
3
  DecorationWithType,
3
4
  NodeView,
4
5
  NodeViewProps,
5
6
  NodeViewRenderer,
6
7
  NodeViewRendererOptions,
7
- NodeViewRendererProps,
8
8
  } from '@tiptap/core'
9
9
  import { Node as ProseMirrorNode } from '@tiptap/pm/model'
10
- import { Decoration, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
10
+ import { Decoration, DecorationSource, NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
11
11
  import {
12
- Component,
13
- defineComponent,
14
- PropType,
15
- provide,
16
- Ref,
17
- ref,
12
+ Component, defineComponent, PropType, provide, Ref, ref,
18
13
  } from 'vue'
19
14
 
20
15
  import { Editor } from './Editor.js'
@@ -58,13 +53,15 @@ export const nodeViewProps = {
58
53
  export interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
59
54
  update:
60
55
  | ((props: {
61
- oldNode: ProseMirrorNode
62
- oldDecorations: Decoration[]
63
- newNode: ProseMirrorNode
64
- newDecorations: Decoration[]
65
- updateProps: () => void
56
+ oldNode: ProseMirrorNode;
57
+ oldDecorations: readonly Decoration[];
58
+ oldInnerDecorations: DecorationSource;
59
+ newNode: ProseMirrorNode;
60
+ newDecorations: readonly Decoration[];
61
+ innerDecorations: DecorationSource;
62
+ updateProps: () => void;
66
63
  }) => boolean)
67
- | null
64
+ | null;
68
65
  }
69
66
 
70
67
  class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions> {
@@ -73,16 +70,19 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
73
70
  decorationClasses!: Ref<string>
74
71
 
75
72
  mount() {
76
- const props: NodeViewProps = {
73
+ const props = {
77
74
  editor: this.editor,
78
75
  node: this.node,
79
- decorations: this.decorations,
76
+ decorations: this.decorations as DecorationWithType[],
77
+ innerDecorations: this.innerDecorations,
78
+ view: this.view,
80
79
  selected: false,
81
80
  extension: this.extension,
81
+ HTMLAttributes: this.HTMLAttributes,
82
82
  getPos: () => this.getPos(),
83
83
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
84
84
  deleteNode: () => this.deleteNode(),
85
- }
85
+ } satisfies NodeViewProps
86
86
 
87
87
  const onDragStart = this.onDragStart.bind(this)
88
88
 
@@ -117,12 +117,19 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
117
117
  __file: this.component.__file,
118
118
  })
119
119
 
120
+ this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this)
121
+ this.editor.on('selectionUpdate', this.handleSelectionUpdate)
122
+
120
123
  this.renderer = new VueRenderer(extendedComponent, {
121
124
  editor: this.editor,
122
125
  props,
123
126
  })
124
127
  }
125
128
 
129
+ /**
130
+ * Return the DOM element.
131
+ * This is the element that will be used to display the node view.
132
+ */
126
133
  get dom() {
127
134
  if (!this.renderer.element || !this.renderer.element.hasAttribute('data-node-view-wrapper')) {
128
135
  throw Error('Please use the NodeViewWrapper component for your node view.')
@@ -131,6 +138,10 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
131
138
  return this.renderer.element as HTMLElement
132
139
  }
133
140
 
141
+ /**
142
+ * Return the content DOM element.
143
+ * This is the element that will be used to display the rich-text content of the node.
144
+ */
134
145
  get contentDOM() {
135
146
  if (this.node.isLeaf) {
136
147
  return null
@@ -139,8 +150,43 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
139
150
  return this.dom.querySelector('[data-node-view-content]') as HTMLElement | null
140
151
  }
141
152
 
142
- update(node: ProseMirrorNode, decorations: DecorationWithType[]) {
143
- const updateProps = (props?: Record<string, any>) => {
153
+ /**
154
+ * On editor selection update, check if the node is selected.
155
+ * If it is, call `selectNode`, otherwise call `deselectNode`.
156
+ */
157
+ handleSelectionUpdate() {
158
+ const { from, to } = this.editor.state.selection
159
+ const pos = this.getPos()
160
+
161
+ if (typeof pos !== 'number') {
162
+ return
163
+ }
164
+
165
+ if (from <= pos && to >= pos + this.node.nodeSize) {
166
+ if (this.renderer.props.selected) {
167
+ return
168
+ }
169
+
170
+ this.selectNode()
171
+ } else {
172
+ if (!this.renderer.props.selected) {
173
+ return
174
+ }
175
+
176
+ this.deselectNode()
177
+ }
178
+ }
179
+
180
+ /**
181
+ * On update, update the React component.
182
+ * To prevent unnecessary updates, the `update` option can be used.
183
+ */
184
+ update(
185
+ node: ProseMirrorNode,
186
+ decorations: readonly Decoration[],
187
+ innerDecorations: DecorationSource,
188
+ ): boolean {
189
+ const rerenderComponent = (props?: Record<string, any>) => {
144
190
  this.decorationClasses.value = this.getDecorationClasses()
145
191
  this.renderer.updateProps(props)
146
192
  }
@@ -148,16 +194,20 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
148
194
  if (typeof this.options.update === 'function') {
149
195
  const oldNode = this.node
150
196
  const oldDecorations = this.decorations
197
+ const oldInnerDecorations = this.innerDecorations
151
198
 
152
199
  this.node = node
153
200
  this.decorations = decorations
201
+ this.innerDecorations = innerDecorations
154
202
 
155
203
  return this.options.update({
156
204
  oldNode,
157
205
  oldDecorations,
158
206
  newNode: node,
159
207
  newDecorations: decorations,
160
- updateProps: () => updateProps({ node, decorations }),
208
+ oldInnerDecorations,
209
+ innerDecorations,
210
+ updateProps: () => rerenderComponent({ node, decorations, innerDecorations }),
161
211
  })
162
212
  }
163
213
 
@@ -165,18 +215,23 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
165
215
  return false
166
216
  }
167
217
 
168
- if (node === this.node && this.decorations === decorations) {
218
+ if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
169
219
  return true
170
220
  }
171
221
 
172
222
  this.node = node
173
223
  this.decorations = decorations
224
+ this.innerDecorations = innerDecorations
174
225
 
175
- updateProps({ node, decorations })
226
+ rerenderComponent({ node, decorations, innerDecorations })
176
227
 
177
228
  return true
178
229
  }
179
230
 
231
+ /**
232
+ * Select the node.
233
+ * Add the `selected` prop and the `ProseMirror-selectednode` class.
234
+ */
180
235
  selectNode() {
181
236
  this.renderer.updateProps({
182
237
  selected: true,
@@ -186,6 +241,10 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
186
241
  }
187
242
  }
188
243
 
244
+ /**
245
+ * Deselect the node.
246
+ * Remove the `selected` prop and the `ProseMirror-selectednode` class.
247
+ */
189
248
  deselectNode() {
190
249
  this.renderer.updateProps({
191
250
  selected: false,
@@ -207,26 +266,26 @@ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions
207
266
 
208
267
  destroy() {
209
268
  this.renderer.destroy()
269
+ this.editor.off('selectionUpdate', this.handleSelectionUpdate)
210
270
  }
211
271
  }
212
272
 
213
273
  export function VueNodeViewRenderer(
214
- component: Component,
274
+ component: Component<NodeViewProps>,
215
275
  options?: Partial<VueNodeViewRendererOptions>,
216
276
  ): NodeViewRenderer {
217
- return (props: NodeViewRendererProps) => {
277
+ return props => {
218
278
  // try to get the parent component
219
279
  // this is important for vue devtools to show the component hierarchy correctly
220
280
  // maybe it’s `undefined` because <editor-content> isn’t rendered yet
221
281
  if (!(props.editor as Editor).contentComponent) {
222
- return {}
282
+ return {} as unknown as ProseMirrorNodeView
223
283
  }
224
284
  // check for class-component and normalize if neccessary
225
285
  const normalizedComponent = typeof component === 'function' && '__vccOpts' in component
226
- // eslint-disable-next-line no-underscore-dangle
227
- ? component.__vccOpts as Component
286
+ ? (component.__vccOpts as Component)
228
287
  : component
229
288
 
230
- return new VueNodeView(normalizedComponent, props, options) as unknown as ProseMirrorNodeView
289
+ return new VueNodeView(normalizedComponent, props, options)
231
290
  }
232
291
  }