diff --git a/docs/src/demos/Extensions/BubbleMenu/index.vue b/docs/src/demos/Extensions/BubbleMenu/index.vue
index 1c1d249c..b5805588 100644
--- a/docs/src/demos/Extensions/BubbleMenu/index.vue
+++ b/docs/src/demos/Extensions/BubbleMenu/index.vue
@@ -1,24 +1,28 @@
-
+
+
+
+
+
diff --git a/docs/src/docPages/api/extensions/bubble-menu.md b/docs/src/docPages/api/extensions/bubble-menu.md
new file mode 100644
index 00000000..b66305c4
--- /dev/null
+++ b/docs/src/docPages/api/extensions/bubble-menu.md
@@ -0,0 +1,39 @@
+# Bubble Menu
+[](https://www.npmjs.com/package/@tiptap/extension-bubble-menu)
+[](https://npmcharts.com/compare/@tiptap/extension-bubble-menu?minimal=true)
+
+This extension will make a contextual menu appear near a selection of text.
+
+## Installation
+```bash
+# with npm
+npm install @tiptap/extension-bubble-menu
+# with Yarn
+yarn add @tiptap/extension-bubble-menu
+```
+
+## Settings
+| Option | Type | Default | Description |
+| ------------ | ------------- | --------- | ----------- |
+| element | `HTMLElement` | `null` | |
+| keepInBounds | `Boolean` | `true` | |
+
+## Source code
+[packages/extension-bubble-menu/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-bubble-menu/)
+
+## Vanilla JavaScript
+```js
+import { Editor } from '@tiptap/core'
+import BubbleMenu from '@tiptap/extension-bubble-menu'
+
+new Editor({
+ extensions: [
+ BubbleMenu.configure({
+ element: document.querySelector('.menu'),
+ }),
+ ],
+})
+```
+
+## Vue
+
diff --git a/docs/src/links.yaml b/docs/src/links.yaml
index 0afecb59..9c7b1c0d 100644
--- a/docs/src/links.yaml
+++ b/docs/src/links.yaml
@@ -195,6 +195,8 @@
# - title: Annotation
# link: /api/extensions/annotation
# type: draft
+ - title: BubbleMenu
+ link: /api/extensions/bubble-menu
- title: CharacterCount
link: /api/extensions/character-count
- title: Collaboration
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index dbc818a5..ee08562b 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -1,4 +1,6 @@
-import { EditorState, Plugin, Transaction } from 'prosemirror-state'
+import {
+ EditorState, Plugin, PluginKey, Transaction,
+} from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { Schema, DOMParser, Node } from 'prosemirror-model'
import elementFromString from './utilities/elementFromString'
@@ -172,10 +174,19 @@ export class Editor extends EventEmitter {
*
* @param name The plugins name
*/
- public unregisterPlugin(name: string): void {
+ public unregisterPlugin(nameOrPluginKey: string | PluginKey): void {
+ if (this.isDestroyed) {
+ return
+ }
+
+ const name = typeof nameOrPluginKey === 'string'
+ ? `${nameOrPluginKey}$`
+ // @ts-ignore
+ : nameOrPluginKey.key
+
const state = this.state.reconfigure({
// @ts-ignore
- plugins: this.state.plugins.filter(plugin => !plugin.key.startsWith(`${name}$`)),
+ plugins: this.state.plugins.filter(plugin => !plugin.key.startsWith(name)),
})
this.view.updateState(state)
diff --git a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts
index 0768b521..24d78c7d 100644
--- a/packages/extension-bubble-menu/src/bubble-menu-plugin.ts
+++ b/packages/extension-bubble-menu/src/bubble-menu-plugin.ts
@@ -3,13 +3,13 @@ import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { coordsAtPos } from './helpers'
-export interface BubbleMenuPluginOptions {
+export interface BubbleMenuPluginProps {
editor: Editor,
element: HTMLElement,
keepInBounds: boolean,
}
-export type BubbleMenuViewOptions = BubbleMenuPluginOptions & {
+export type BubbleMenuViewOptions = BubbleMenuPluginProps & {
view: EditorView,
}
@@ -137,7 +137,7 @@ export class BubbleMenuView {
export const BubbleMenuPluginKey = new PluginKey('menuBubble')
-export const BubbleMenuPlugin = (options: BubbleMenuPluginOptions) => {
+export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
return new Plugin({
key: BubbleMenuPluginKey,
view: view => new BubbleMenuView({ view, ...options }),
diff --git a/packages/extension-bubble-menu/src/bubble-menu.ts b/packages/extension-bubble-menu/src/bubble-menu.ts
index b0948c31..49faba65 100644
--- a/packages/extension-bubble-menu/src/bubble-menu.ts
+++ b/packages/extension-bubble-menu/src/bubble-menu.ts
@@ -1,7 +1,7 @@
import { Extension } from '@tiptap/core'
-import { BubbleMenuPlugin, BubbleMenuPluginOptions } from './bubble-menu-plugin'
+import { BubbleMenuPlugin, BubbleMenuPluginProps } from './bubble-menu-plugin'
-export type BubbleMenuOptions = Omit
+export type BubbleMenuOptions = Omit
export const BubbleMenu = Extension.create({
name: 'bubbleMenu',
diff --git a/packages/extension-bubble-menu/src/index.ts b/packages/extension-bubble-menu/src/index.ts
index 71fe7f0d..df47407f 100644
--- a/packages/extension-bubble-menu/src/index.ts
+++ b/packages/extension-bubble-menu/src/index.ts
@@ -1,5 +1,6 @@
import { BubbleMenu } from './bubble-menu'
export * from './bubble-menu'
+export * from './bubble-menu-plugin'
export default BubbleMenu
diff --git a/packages/vue-2/package.json b/packages/vue-2/package.json
index 3a3740a8..a067ef15 100644
--- a/packages/vue-2/package.json
+++ b/packages/vue-2/package.json
@@ -26,6 +26,7 @@
"vue": "^2.6.12"
},
"dependencies": {
+ "@tiptap/extension-bubble-menu": "^2.0.0-beta.1",
"prosemirror-view": "^1.18.2"
}
}
diff --git a/packages/vue-2/src/BubbleMenu.ts b/packages/vue-2/src/BubbleMenu.ts
new file mode 100644
index 00000000..f442b627
--- /dev/null
+++ b/packages/vue-2/src/BubbleMenu.ts
@@ -0,0 +1,45 @@
+import Vue, { PropType } from 'vue'
+import { BubbleMenuPlugin, BubbleMenuPluginKey, BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'
+
+export const BubbleMenu = Vue.extend({
+ name: 'BubbleMenu',
+
+ props: {
+ editor: {
+ type: Object as PropType,
+ required: true,
+ },
+
+ keepInBounds: {
+ type: Boolean as PropType,
+ default: true,
+ },
+ },
+
+ watch: {
+ editor: {
+ immediate: true,
+ handler(editor: BubbleMenuPluginProps['editor']) {
+ if (!editor) {
+ return
+ }
+
+ this.$nextTick(() => {
+ editor.registerPlugin(BubbleMenuPlugin({
+ editor,
+ element: this.$el as HTMLElement,
+ keepInBounds: this.keepInBounds,
+ }))
+ })
+ },
+ },
+ },
+
+ render(createElement) {
+ return createElement('div', {}, this.$slots.default)
+ },
+
+ beforeDestroy() {
+ this.editor.unregisterPlugin(BubbleMenuPluginKey)
+ },
+})
diff --git a/packages/vue-2/src/index.ts b/packages/vue-2/src/index.ts
index 9dbf56fb..4233fd98 100644
--- a/packages/vue-2/src/index.ts
+++ b/packages/vue-2/src/index.ts
@@ -1,4 +1,5 @@
export * from '@tiptap/core'
+export * from './BubbleMenu'
export { Editor } from './Editor'
export * from './EditorContent'
export * from './VueRenderer'