UNPKG

4.18 kBPlain TextView Raw
1<template>
2 <ul
3 v-show="visible"
4 ref="contextmenu"
5 :class="contextmenuCls"
6 :style="style"
7 >
8 <slot />
9 </ul>
10</template>
11
12<script>
13 export default {
14 name: 'VContextmenu',
15
16 provide () {
17 return {
18 $$contextmenu: this,
19 }
20 },
21
22 props: {
23 eventType: {
24 type: String,
25 default: 'contextmenu',
26 },
27 theme: {
28 type: String,
29 default: 'default',
30 },
31 autoPlacement: {
32 type: Boolean,
33 default: true,
34 },
35 disabled: Boolean,
36 },
37
38 data () {
39 return {
40 visible: false,
41 references: [],
42 style: {
43 top: 0,
44 left: 0,
45 },
46 }
47 },
48 computed: {
49 clickOutsideHandler () {
50 return this.visible ? this.hide : () => {}
51 },
52 isClick () {
53 return this.eventType === 'click'
54 },
55 contextmenuCls () {
56 return [
57 'v-contextmenu',
58 `v-contextmenu--${this.theme}`,
59 ]
60 },
61 },
62
63 watch: {
64 visible (value) {
65 if (value) {
66 this.$emit('show', this)
67
68 document.body.addEventListener('click', this.handleBodyClick)
69 } else {
70 this.$emit('hide', this)
71
72 document.body.removeEventListener('click', this.handleBodyClick)
73 }
74 },
75 },
76 mounted () {
77 document.body.appendChild(this.$el)
78
79 if (window.$$VContextmenu) {
80 window.$$VContextmenu[this.$contextmenuId] = this
81 } else {
82 window.$$VContextmenu = { [this.$contextmenuId]: this }
83 }
84 },
85 beforeDestroy () {
86 document.body.removeChild(this.$el)
87
88 delete window.$$VContextmenu[this.$contextmenuId]
89
90 this.references.forEach((ref) => {
91 ref.el.removeEventListener(this.eventType, this.handleReferenceContextmenu)
92 })
93
94 document.body.removeEventListener('click', this.handleBodyClick)
95 },
96
97 methods: {
98 addRef (ref) {
99 // FIXME: 如何处理 removeRef?
100 this.references.push(ref)
101
102 ref.el.addEventListener(this.eventType, this.handleReferenceContextmenu)
103 },
104 handleReferenceContextmenu (event) {
105 event.preventDefault()
106
107 if (this.disabled) return
108
109 const reference = this.references.find(ref => ref.el.contains(event.target))
110
111 this.$emit('contextmenu', reference ? reference.vnode : null)
112
113 const eventX = event.pageX
114 const eventY = event.pageY
115
116 this.show()
117
118 this.$nextTick(() => {
119 const contextmenuPosition = {
120 top: eventY,
121 left: eventX,
122 }
123
124 if (this.autoPlacement) {
125 const contextmenuWidth = this.$refs.contextmenu.clientWidth
126 const contextmenuHeight = this.$refs.contextmenu.clientHeight
127
128 if (contextmenuHeight + eventY >= window.innerHeight) {
129 contextmenuPosition.top -= contextmenuHeight
130 }
131
132 if (contextmenuWidth + eventX >= window.innerWidth) {
133 contextmenuPosition.left -= contextmenuWidth
134 }
135 }
136
137 this.style = {
138 top: `${contextmenuPosition.top}px`,
139 left: `${contextmenuPosition.left}px`,
140 }
141 })
142 },
143 handleBodyClick (event) {
144 const notOutside = this.$el.contains(event.target) || (
145 this.isClick && this.references.some(ref => ref.el.contains(event.target))
146 )
147
148 if (!notOutside) {
149 this.visible = false
150 }
151 },
152 show (position) {
153 Object.keys(window.$$VContextmenu)
154 .forEach((key) => {
155 if (key !== this.$contextmenuId) {
156 window.$$VContextmenu[key].hide()
157 }
158 })
159
160 if (position) {
161 this.style = {
162 top: `${position.top}px`,
163 left: `${position.left}px`,
164 }
165 }
166
167 this.visible = true
168 },
169 hide () {
170 this.visible = false
171 },
172 hideAll () {
173 Object.keys(window.$$VContextmenu)
174 .forEach((key) => {
175 window.$$VContextmenu[key].hide()
176 })
177 },
178 },
179 }
180</script>