/
MenuBubble.js
85 lines (67 loc) · 1.9 KB
/
MenuBubble.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { Plugin } from 'prosemirror-state'
class Menu {
constructor({ options, editorView }) {
this.options = {
...{
element: null,
onUpdate: () => false,
},
...options,
}
this.editorView = editorView
this.isActive = false
this.left = 0
this.bottom = 0
this.editorView.dom.addEventListener('blur', this.hide.bind(this))
}
update(view, lastState) {
const { state } = view
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) {
return
}
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
this.hide()
return
}
// Otherwise, reposition it and update its content
const { from, to } = state.selection
// These are in screen coordinates
const start = view.coordsAtPos(from)
const end = view.coordsAtPos(to)
// The box in which the tooltip is positioned, to use as base
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.isActive = true
this.left = parseInt(left - box.left, 10)
this.bottom = parseInt(box.bottom - start.top, 10)
this.sendUpdate()
}
sendUpdate() {
this.options.onUpdate({
isActive: this.isActive,
left: this.left,
bottom: this.bottom,
})
}
hide(event) {
if (event && event.relatedTarget) {
return
}
this.isActive = false
this.sendUpdate()
}
destroy() {
this.editorView.dom.removeEventListener('blur', this.hide)
}
}
export default function (options) {
return new Plugin({
view(editorView) {
return new Menu({ editorView, options })
},
})
}