@tiptap/vue-2 3.29.2 → 3.30.1
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.
- package/dist/index.cjs +74 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -1
- package/dist/menus/index.cjs +31 -30
- package/dist/menus/index.cjs.map +1 -1
- package/dist/menus/index.d.cts +1 -0
- package/dist/menus/index.d.ts +1 -0
- package/dist/menus/index.js +31 -30
- package/dist/menus/index.js.map +1 -1
- package/package.json +7 -7
- package/src/VueWidgetRenderer.ts +135 -0
- package/src/index.ts +1 -0
- package/src/menus/FloatingMenu.ts +42 -34
- package/src/utils/createWidgetConstructor.ts +38 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/vue-2",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.30.1",
|
|
4
4
|
"description": "Vue components for tiptap",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tiptap",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"vue": "^2.7.16",
|
|
55
|
-
"@tiptap/core": "^3.
|
|
56
|
-
"@tiptap/pm": "^3.
|
|
55
|
+
"@tiptap/core": "^3.30.1",
|
|
56
|
+
"@tiptap/pm": "^3.30.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"vue": "^2.6.0",
|
|
60
|
-
"@tiptap/core": "3.
|
|
61
|
-
"@tiptap/pm": "3.
|
|
60
|
+
"@tiptap/core": "3.30.1",
|
|
61
|
+
"@tiptap/pm": "3.30.1"
|
|
62
62
|
},
|
|
63
63
|
"optionalDependencies": {
|
|
64
|
-
"@tiptap/extension-bubble-menu": "^3.
|
|
65
|
-
"@tiptap/extension-floating-menu": "^3.
|
|
64
|
+
"@tiptap/extension-bubble-menu": "^3.30.1",
|
|
65
|
+
"@tiptap/extension-floating-menu": "^3.30.1"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsup"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createWidgetDecoration } from '@tiptap/core'
|
|
2
|
+
import type {
|
|
3
|
+
Editor as CoreEditor,
|
|
4
|
+
WidgetDecoration,
|
|
5
|
+
WidgetDecorationOptions,
|
|
6
|
+
WidgetRenderer,
|
|
7
|
+
} from '@tiptap/core'
|
|
8
|
+
import type { Component, VueConstructor } from 'vue'
|
|
9
|
+
|
|
10
|
+
import type { Editor } from './Editor.js'
|
|
11
|
+
import { createWidgetConstructor } from './utils/createWidgetConstructor.js'
|
|
12
|
+
import { VueRenderer } from './VueRenderer.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Props every widget-decoration component receives in addition to the props you
|
|
16
|
+
* pass through `VueWidgetRenderer`. Declare the ones you use on your component.
|
|
17
|
+
*/
|
|
18
|
+
export interface VueWidgetDecorationProps {
|
|
19
|
+
editor: CoreEditor
|
|
20
|
+
getPos: () => number | undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VueWidgetRendererOptions<
|
|
24
|
+
P extends Record<string, any> = object,
|
|
25
|
+
> extends WidgetDecorationOptions {
|
|
26
|
+
/**
|
|
27
|
+
* The editor instance.
|
|
28
|
+
*/
|
|
29
|
+
editor: Editor
|
|
30
|
+
/**
|
|
31
|
+
* The document position the widget is rendered at.
|
|
32
|
+
*/
|
|
33
|
+
pos: number
|
|
34
|
+
/**
|
|
35
|
+
* A stable, position-independent identifier for the widget.
|
|
36
|
+
* Reusing the same key keeps the component mounted across re-renders.
|
|
37
|
+
* Good: `comment-${id}`. Bad: paragraph index or document position.
|
|
38
|
+
*/
|
|
39
|
+
key: string
|
|
40
|
+
/**
|
|
41
|
+
* Props passed to the component (merged with {@link VueWidgetDecorationProps}).
|
|
42
|
+
* The component must render a single root element.
|
|
43
|
+
*/
|
|
44
|
+
props?: P
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const WIDGET_CACHE = Symbol('tiptapVue2WidgetCache')
|
|
48
|
+
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
51
|
+
|
|
52
|
+
/** The `VueRenderer` plus the warning wrapper the widget cache drives. */
|
|
53
|
+
interface WidgetRendererEntry extends WidgetRenderer {
|
|
54
|
+
renderer: VueRenderer
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Vue 2 fixes the prop list at construction. Keys absent on first mount are
|
|
58
|
+
// never declared, so updateProps silently drops them on later updates.
|
|
59
|
+
function warnUndeclaredProps(renderer: VueRenderer, props: Record<string, any>, key: string): void {
|
|
60
|
+
if (!isDev) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const declared = renderer.ref.$props ?? {}
|
|
65
|
+
|
|
66
|
+
for (const name of Object.keys(props)) {
|
|
67
|
+
if (!(name in declared)) {
|
|
68
|
+
console.warn(
|
|
69
|
+
`[tiptap warn]: VueWidgetRenderer prop "${name}" was not passed on first mount for widget "${key}".`,
|
|
70
|
+
'Vue 2 cannot declare new props after mount, so this value is ignored.',
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Wrap the renderer so every prop push warns, whether it comes from the first
|
|
77
|
+
// render or from the deferred flush.
|
|
78
|
+
function withPropWarnings(renderer: VueRenderer, key: string): WidgetRendererEntry {
|
|
79
|
+
return {
|
|
80
|
+
renderer,
|
|
81
|
+
updateProps: props => {
|
|
82
|
+
warnUndeclaredProps(renderer, props, key)
|
|
83
|
+
renderer.updateProps(props)
|
|
84
|
+
},
|
|
85
|
+
destroy: () => renderer.destroy(),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Renders a Vue 2 component into a ProseMirror widget decoration.
|
|
91
|
+
* Reuses Tiptap's `VueRenderer` so the component is mounted under the editor's
|
|
92
|
+
* content component (inject/provide works as usual). Use a stable `key` for
|
|
93
|
+
* stateful widgets. The component must render a single root element.
|
|
94
|
+
* @example
|
|
95
|
+
* addDecorations() {
|
|
96
|
+
* return {
|
|
97
|
+
* create: ({ editor, state }) =>
|
|
98
|
+
* findMatches(state.doc).map(match =>
|
|
99
|
+
* VueWidgetRenderer(MyWidget, {
|
|
100
|
+
* editor, pos: match.pos, key: `match-${match.id}`,
|
|
101
|
+
* props: { label: match.label },
|
|
102
|
+
* }),
|
|
103
|
+
* ),
|
|
104
|
+
* }
|
|
105
|
+
* }
|
|
106
|
+
*/
|
|
107
|
+
export function VueWidgetRenderer<P extends Record<string, any> = object>(
|
|
108
|
+
component: Component,
|
|
109
|
+
options: VueWidgetRendererOptions<P>,
|
|
110
|
+
): WidgetDecoration {
|
|
111
|
+
const { editor, key, props = {} as P } = options
|
|
112
|
+
|
|
113
|
+
return createWidgetDecoration<WidgetRendererEntry>({
|
|
114
|
+
// Forwards editor, pos, key and the ProseMirror widget options unchanged.
|
|
115
|
+
...options,
|
|
116
|
+
props,
|
|
117
|
+
cacheKey: WIDGET_CACHE,
|
|
118
|
+
// `view` is not passed on purpose: Vue 2 deeply observes props and
|
|
119
|
+
// observing a ProseMirror view corrupts its internals.
|
|
120
|
+
context: getPos => ({ editor, getPos }),
|
|
121
|
+
create: renderProps => {
|
|
122
|
+
const base = (editor.contentComponent?.$options as any)?._base as VueConstructor | undefined
|
|
123
|
+
const Constructor = createWidgetConstructor(base, component, renderProps)
|
|
124
|
+
|
|
125
|
+
return withPropWarnings(
|
|
126
|
+
new VueRenderer(Constructor, {
|
|
127
|
+
parent: editor.contentComponent,
|
|
128
|
+
propsData: renderProps,
|
|
129
|
+
}),
|
|
130
|
+
key,
|
|
131
|
+
)
|
|
132
|
+
},
|
|
133
|
+
materialize: entry => entry.renderer.element as HTMLElement,
|
|
134
|
+
})
|
|
135
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface FloatingMenuInterface extends Vue {
|
|
|
16
16
|
appendTo: FloatingMenuPluginProps['appendTo']
|
|
17
17
|
shouldShow: FloatingMenuPluginProps['shouldShow']
|
|
18
18
|
getPluginKey: () => FloatingMenuPluginProps['pluginKey']
|
|
19
|
+
isDestroyed: boolean
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const FloatingMenu: Component = {
|
|
@@ -58,39 +59,38 @@ export const FloatingMenu: Component = {
|
|
|
58
59
|
},
|
|
59
60
|
},
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
editor
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
62
|
+
mounted(this: FloatingMenuInterface) {
|
|
63
|
+
const editor = this.editor
|
|
64
|
+
const el = this.$el as HTMLElement
|
|
65
|
+
|
|
66
|
+
if (!editor || !el) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
el.style.visibility = 'hidden'
|
|
71
|
+
el.style.position = 'absolute'
|
|
72
|
+
|
|
73
|
+
// Remove element from DOM; plugin will re-parent it when shown
|
|
74
|
+
el.remove()
|
|
75
|
+
|
|
76
|
+
this.$nextTick(() => {
|
|
77
|
+
if (this.isDestroyed) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
editor.registerPlugin(
|
|
82
|
+
FloatingMenuPlugin({
|
|
83
|
+
pluginKey: this.getPluginKey(),
|
|
84
|
+
editor,
|
|
85
|
+
element: el,
|
|
86
|
+
updateDelay: this.updateDelay,
|
|
87
|
+
resizeDelay: this.resizeDelay,
|
|
88
|
+
options: this.options,
|
|
89
|
+
appendTo: this.appendTo,
|
|
90
|
+
shouldShow: this.shouldShow,
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
94
|
},
|
|
95
95
|
|
|
96
96
|
render(this: FloatingMenuInterface, createElement: CreateElement) {
|
|
@@ -109,7 +109,15 @@ export const FloatingMenu: Component = {
|
|
|
109
109
|
},
|
|
110
110
|
|
|
111
111
|
beforeDestroy(this: FloatingMenuInterface) {
|
|
112
|
-
this.
|
|
112
|
+
this.isDestroyed = true
|
|
113
|
+
|
|
114
|
+
const editor = this.editor
|
|
115
|
+
|
|
116
|
+
if (!editor) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
editor.unregisterPlugin(this.getPluginKey())
|
|
113
121
|
},
|
|
114
122
|
|
|
115
123
|
methods: {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Component, VueConstructor } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { Vue } from '../Vue.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds the Vue 2 constructor for a widget component.
|
|
7
|
+
*
|
|
8
|
+
* Extends the editor's own Vue constructor so the widget shares its context,
|
|
9
|
+
* then auto-declares every passed prop the component does not declare itself.
|
|
10
|
+
* Vue 2 fixes the prop list at construction, so undeclared keys would be
|
|
11
|
+
* dropped. Existing declarations keep their type, default and validator.
|
|
12
|
+
*
|
|
13
|
+
* @param base The editor's Vue constructor, or undefined to fall back to `Vue`.
|
|
14
|
+
* @param component The widget component.
|
|
15
|
+
* @param props The props the widget is mounted with.
|
|
16
|
+
* @returns A constructor that accepts every key in `props`.
|
|
17
|
+
* @example
|
|
18
|
+
* createWidgetConstructor(undefined, MyWidget, { editor, getPos, label: 'a' })
|
|
19
|
+
*/
|
|
20
|
+
export function createWidgetConstructor(
|
|
21
|
+
base: VueConstructor | undefined,
|
|
22
|
+
component: Component,
|
|
23
|
+
props: Record<string, any>,
|
|
24
|
+
): VueConstructor {
|
|
25
|
+
const VueBase = base ?? Vue
|
|
26
|
+
const Extended = VueBase.extend(component as any)
|
|
27
|
+
const declaredProps = {
|
|
28
|
+
...(Extended as unknown as { options: { props?: Record<string, any> } }).options.props,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const name of Object.keys(props)) {
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(declaredProps, name)) {
|
|
33
|
+
declaredProps[name] = null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return Extended.extend({ props: declaredProps })
|
|
38
|
+
}
|