@tiptap/vue-3 3.0.0-next.1 → 3.0.0-next.2

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,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
  }
package/src/useEditor.ts CHANGED
@@ -11,6 +11,12 @@ export const useEditor = (options: Partial<EditorOptions> = {}) => {
11
11
  })
12
12
 
13
13
  onBeforeUnmount(() => {
14
+ // Cloning root node (and its children) to avoid content being lost by destroy
15
+ const nodes = editor.value?.options.element
16
+ const newEl = nodes?.cloneNode(true) as HTMLElement
17
+
18
+ nodes?.parentNode?.replaceChild(newEl, nodes)
19
+
14
20
  editor.value?.destroy()
15
21
  })
16
22