add renderless MenuBubble

This commit is contained in:
Philipp Kühn 2018-11-04 18:00:37 +01:00
parent bc4a5743ff
commit c2d8e15530
4 changed files with 61 additions and 23 deletions

View File

@ -95,6 +95,11 @@
opacity: 0;
transition: opacity 0.2s, visibility 0.2s;
&.is-active {
opacity: 1;
visibility: visible;
}
&__button {
display: inline-flex;
background: transparent;

View File

@ -1,7 +1,12 @@
<template>
<div class="editor">
<menu-bubble class="menububble" :editor="editor">
<template slot-scope="{ commands, isActive }">
<menu-bubble :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="menububble"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<button
class="menububble__button"
@ -27,7 +32,7 @@
<icon name="code" />
</button>
</template>
</div>
</menu-bubble>
<editor-content class="editor__content" :editor="editor" />

View File

@ -7,29 +7,44 @@ export default {
type: Object,
},
},
data() {
return {
menu: {
isActive: false,
left: 0,
bottom: 0,
},
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (editor) {
this.$nextTick(() => {
editor.registerPlugin(MenuBubble(this.$el))
editor.registerPlugin(MenuBubble({
element: this.$el,
onUpdate: menu => {
this.menu = menu
},
}))
})
}
},
},
},
render(createElement) {
render() {
if (!this.editor) {
return null
}
return createElement('div', this.$scopedSlots.default({
return this.$scopedSlots.default({
focused: this.editor.view.focused,
focus: this.editor.focus,
commands: this.editor.commands,
isActive: this.editor.isActive.bind(this.editor),
markAttrs: this.editor.markAttrs.bind(this.editor),
}))
menu: this.menu,
})
},
}

View File

@ -1,12 +1,19 @@
import { Plugin } from 'prosemirror-state'
class Toolbar {
class Menu {
constructor({ element, editorView }) {
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.element = element
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
this.isActive = false
this.left = 0
this.bottom = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
@ -26,7 +33,6 @@ class Toolbar {
}
// Otherwise, reposition it and update its content
this.show()
const { from, to } = state.selection
// These are in screen coordinates
@ -34,18 +40,25 @@ class Toolbar {
const end = view.coordsAtPos(to)
// The box in which the tooltip is positioned, to use as base
const box = this.element.offsetParent.getBoundingClientRect()
const box = this.options.element.offsetParent.getBoundingClientRect()
// Find a center-ish x position from the selection endpoints (when
// crossing lines, end may be more to the left)
const left = Math.max((start.left + end.left) / 2, start.left + 3)
this.element.style.left = `${left - box.left}px`
this.element.style.bottom = `${box.bottom - start.top}px`
this.isActive = true
this.left = parseInt(left - box.left, 10)
this.bottom = parseInt(box.bottom - start.top, 10)
this.sendUpdate()
}
show() {
this.element.style.visibility = 'visible'
this.element.style.opacity = 1
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
left: this.left,
bottom: this.bottom,
})
}
hide(event) {
@ -53,8 +66,8 @@ class Toolbar {
return
}
this.element.style.visibility = 'hidden'
this.element.style.opacity = 0
this.isActive = false
this.sendUpdate()
}
destroy() {
@ -63,10 +76,10 @@ class Toolbar {
}
export default function (element) {
export default function (options) {
return new Plugin({
view(editorView) {
return new Toolbar({ editorView, element })
return new Menu({ editorView, options })
},
})
}