UNPKG

18.3 kBJavaScriptView Raw
1import { isRef, defineComponent, ref, computed, reactive, watch, onBeforeUnmount, provide, withDirectives, createVNode, vShow, Teleport, isVNode, nextTick, inject } from 'vue';
2
3var bind = function bind(el, binding) {
4 var _binding$instance;
5
6 var contextmenuKey = binding.arg;
7
8 if (!contextmenuKey) {
9 console.error("参数有误");
10 return;
11 }
12
13 var contextmenuOptions = binding.value;
14 var contextmenuRef = isRef(contextmenuKey) ? contextmenuKey.value : (_binding$instance = binding.instance) === null || _binding$instance === void 0 ? void 0 : _binding$instance.$refs[contextmenuKey];
15
16 if (!contextmenuRef) {
17 console.error("\u6CA1\u6709\u627E\u5230 ".concat(contextmenuKey, " \u5BF9\u5E94\u7684\u5B9E\u4F8B"));
18 return;
19 }
20
21 if (typeof contextmenuRef.addReference !== "function") {
22 console.error("".concat(contextmenuKey, " \u5BF9\u5E94\u7684\u5B9E\u4F8B\u4E0D\u662F VContextmenu"));
23 return;
24 }
25
26 el.$contextmenuKey = contextmenuKey;
27 contextmenuRef.addReference(el, contextmenuOptions);
28};
29
30var unbind = function unbind(el, binding) {
31 var _binding$instance2;
32
33 var contextmenuKey = el.$contextmenuKey;
34 if (!contextmenuKey) return;
35 var contextmenuRef = (_binding$instance2 = binding.instance) === null || _binding$instance2 === void 0 ? void 0 : _binding$instance2.$refs[contextmenuKey];
36 contextmenuRef === null || contextmenuRef === void 0 ? void 0 : contextmenuRef.removeReference(el);
37};
38
39var rebind = function rebind(el, binding) {
40 unbind(el, binding);
41 bind(el, binding);
42};
43
44var contextmenuDirective = {
45 mounted: bind,
46 updated: rebind,
47 beforeUnmount: unbind
48};
49
50var CLASSES = {
51 contextmenu: "v-contextmenu",
52 // 根元素
53 contextmenuIcon: "v-contextmenu-icon",
54 // icon
55 contextmenuInner: "v-contextmenu-inner",
56 // 菜单根元素
57 contextmenuDivider: "v-contextmenu-divider",
58 // 分割线
59 contextmenuItem: "v-contextmenu-item",
60 // 单个菜单
61 contextmenuItemHover: "v-contextmenu-item--hover",
62 // 单个菜单激活状态
63 contextmenuItemDisabled: "v-contextmenu-item--disabled",
64 // 单个菜单禁用状态
65 contextmenuGroup: "v-contextmenu-group",
66 // 按钮组根元素
67 contextmenuGroupTitle: "v-contextmenu-group__title",
68 // 按钮组标题
69 contextmenuGroupMenus: "v-contextmenu-group__menus",
70 // 按钮组按钮容器
71 contextmenuSubmenu: "v-contextmenu-submenu",
72 // 子菜单容器
73 contextmenuSubmenuTitle: "v-contextmenu-submenu__title",
74 // 子菜单标题
75 contextmenuSubmenuMenus: "v-contextmenu-submenu__menus",
76 // 子菜单
77 contextmenuSubmenuMenusTop: "v-contextmenu-submenu__menus--top",
78 // 子菜单 Top
79 contextmenuSubmenuMenusRight: "v-contextmenu-submenu__menus--right",
80 // 子菜单 Right
81 contextmenuSubmenuMenusBottom: "v-contextmenu-submenu__menus--bottom",
82 // 子菜 Bottom单
83 contextmenuSubmenuMenusLeft: "v-contextmenu-submenu__menus--left",
84 // 子菜单 Left
85 contextmenuSubmenuArrow: "v-contextmenu-submenu__arrow" // 子菜单标题 icon
86
87};
88
89function _isSlot(s) {
90 return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s);
91}
92
93var DEFAULT_REFERENCE_OPTIONS = {
94 trigger: ["contextmenu"]
95};
96var Contextmenu = defineComponent({
97 name: "VContextmenu",
98 props: {
99 modelValue: {
100 type: Boolean,
101 default: false
102 },
103 autoAjustPlacement: {
104 type: Boolean,
105 default: true
106 },
107 disabled: {
108 type: Boolean,
109 default: false
110 },
111 teleport: {
112 type: [String, Object],
113 default: function _default() {
114 return "body";
115 }
116 } // destroyOnHide: {
117 // type: Boolean,
118 // default: false,
119 // },
120
121 },
122 emits: ["show", "hide", "update:modelValue"],
123 setup: function setup(props, _ref) {
124 var emit = _ref.emit;
125 var contextmenuRef = ref(null);
126 var visible = ref(props.modelValue || false);
127
128 var toggle = function toggle(value) {
129 visible.value = value;
130 emit("update:modelValue", value);
131 };
132
133 var position = ref({
134 top: 0,
135 left: 0
136 });
137 var style = computed(function () {
138 return {
139 top: "".concat(position.value.top, "px"),
140 left: "".concat(position.value.left, "px")
141 };
142 });
143 var currentOptions = ref(null);
144
145 var show = function show(evt, options) {
146 var targetOptions = evt instanceof Event ? options : evt;
147 var autoAjustPlacement = (targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.autoAjustPlacement) || props.autoAjustPlacement;
148 var targetPosition = {
149 top: (targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.top) || 0,
150 left: (targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.left) || 0
151 };
152
153 if (evt instanceof Event) {
154 var _targetOptions$top, _targetOptions$left;
155
156 evt.preventDefault();
157 targetPosition.top = (_targetOptions$top = targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.top) !== null && _targetOptions$top !== void 0 ? _targetOptions$top : evt.pageY;
158 targetPosition.left = (_targetOptions$left = targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.left) !== null && _targetOptions$left !== void 0 ? _targetOptions$left : evt.pageX;
159 }
160
161 toggle(true);
162 nextTick(function () {
163 if (autoAjustPlacement) {
164 var el = contextmenuRef.value;
165 if (!el) return;
166 var width = el.clientWidth;
167 var height = el.clientHeight;
168
169 if (height + targetPosition.top >= window.innerHeight + window.scrollY) {
170 var targetTop = targetPosition.top - height;
171
172 if (targetTop > window.scrollY) {
173 targetPosition.top = targetTop;
174 }
175 }
176
177 if (width + targetPosition.left >= window.innerWidth + window.scrollX) {
178 var targetWidth = targetPosition.left - width;
179
180 if (targetWidth > window.scrollX) {
181 targetPosition.left = targetWidth;
182 }
183 }
184 }
185
186 position.value = targetPosition; // TODO: 添加回掉参数
187
188 emit("show");
189 });
190 };
191
192 var hide = function hide() {
193 currentOptions.value = null;
194 toggle(false); // TODO: 添加回掉参数
195
196 emit("hide");
197 };
198
199 var references = reactive(new Map());
200 var currentReference = ref();
201 var currentReferenceOptions = computed(function () {
202 return currentReference.value && references.get(currentReference.value);
203 });
204
205 var addReference = function addReference(el, options) {
206 var triggers = function () {
207 if (options !== null && options !== void 0 && options.trigger) {
208 return Array.isArray(options.trigger) ? options.trigger : [options.trigger];
209 }
210
211 return DEFAULT_REFERENCE_OPTIONS.trigger;
212 }();
213
214 var handler = function handler(evt) {
215 if (props.disabled) return;
216 currentReference.value = el;
217 show(evt, {});
218 };
219
220 triggers.forEach(function (eventType) {
221 el.addEventListener(eventType, handler);
222 });
223 references.set(el, {
224 triggers: triggers,
225 handler: handler
226 });
227 };
228
229 var removeReference = function removeReference(el) {
230 var options = references.get(el);
231 if (!options) return;
232 options.triggers.forEach(function (eventType) {
233 el.removeEventListener(eventType, options.handler);
234 });
235 references.delete(el);
236 };
237
238 var onBodyClick = function onBodyClick(evt) {
239 if (!evt.target || !contextmenuRef.value || !currentReference.value) return;
240 var notOutside = contextmenuRef.value.contains(evt.target) || currentReferenceOptions.value && currentReferenceOptions.value.triggers.includes("click") && currentReference.value.contains(evt.target);
241
242 if (!notOutside) {
243 toggle(false);
244 }
245 }; // watch(props.modelValue, (value) => {
246 // if (value !== visible.value) {
247 // toggle(value);
248 // }
249 // });
250
251
252 watch(visible, function (value) {
253 if (value) {
254 document.addEventListener("click", onBodyClick);
255 } else {
256 document.removeEventListener("click", onBodyClick);
257 }
258 });
259 onBeforeUnmount(function () {
260 document.removeEventListener("click", onBodyClick);
261 });
262 provide("visible", visible);
263 provide("autoAjustPlacement", props.autoAjustPlacement);
264 provide("show", show);
265 provide("hide", hide);
266 return {
267 visible: visible,
268 style: style,
269 currentReferenceOptions: currentReferenceOptions,
270 currentOptions: currentOptions,
271 contextmenuRef: contextmenuRef,
272 addReference: addReference,
273 removeReference: removeReference,
274 toggle: toggle,
275 show: show,
276 hide: hide
277 };
278 },
279 methods: {
280 renderContent: function renderContent() {
281 var _this$$slots$default, _this$$slots;
282
283 return withDirectives(createVNode("div", {
284 "class": CLASSES.contextmenu,
285 "ref": "contextmenuRef",
286 "style": this.style
287 }, [createVNode("ul", {
288 "class": CLASSES.contextmenuInner
289 }, [(_this$$slots$default = (_this$$slots = this.$slots).default) === null || _this$$slots$default === void 0 ? void 0 : _this$$slots$default.call(_this$$slots, {
290 triggerOptions: "currentReferenceOptions",
291 options: "currentOptions"
292 })])]), [[vShow, "visible"]]);
293 }
294 },
295 render: function render() {
296 var _slot;
297
298 if (!this.visible) return null;
299 return this.teleport ? createVNode(Teleport, {
300 "to": this.teleport
301 }, _isSlot(_slot = this.renderContent()) ? _slot : {
302 default: function _default() {
303 return [_slot];
304 }
305 }) : this.renderContent();
306 }
307});
308
309function _defineProperty(obj, key, value) {
310 if (key in obj) {
311 Object.defineProperty(obj, key, {
312 value: value,
313 enumerable: true,
314 configurable: true,
315 writable: true
316 });
317 } else {
318 obj[key] = value;
319 }
320
321 return obj;
322}
323
324var ContextmenuItem = defineComponent({
325 name: "VContextmenuItem",
326 props: {
327 disabled: {
328 type: Boolean,
329 default: false
330 },
331 hideOnClick: {
332 type: Boolean,
333 default: true
334 }
335 },
336 emits: ["click", "mouseenter", "mouseleave"],
337 setup: function setup(props, _ref) {
338 var emit = _ref.emit;
339 var rootHide = inject("hide");
340 var hover = ref(false);
341 var classes = computed(function () {
342 var _ref2;
343
344 return _ref2 = {}, _defineProperty(_ref2, CLASSES.contextmenuItem, true), _defineProperty(_ref2, CLASSES.contextmenuItemDisabled, props.disabled), _defineProperty(_ref2, CLASSES.contextmenuItemHover, hover.value), _ref2;
345 });
346
347 var handleClick = function handleClick(evt) {
348 if (props.disabled) return;
349 emit("click", evt);
350 props.hideOnClick && (rootHide === null || rootHide === void 0 ? void 0 : rootHide());
351 };
352
353 var handleMouseenter = function handleMouseenter(evt) {
354 if (props.disabled) return;
355 hover.value = true;
356 emit("mouseenter", evt);
357 };
358
359 var handleMouseleave = function handleMouseleave(evt) {
360 if (props.disabled) return;
361 hover.value = false;
362 emit("mouseleave", evt);
363 };
364
365 return {
366 classes: classes,
367 handleClick: handleClick,
368 handleMouseenter: handleMouseenter,
369 handleMouseleave: handleMouseleave
370 };
371 },
372 render: function render() {
373 var _this$$slots$default, _this$$slots;
374
375 return createVNode("li", {
376 "class": this.classes,
377 "onClick": this.handleClick,
378 "onMouseenter": this.handleMouseenter,
379 "onMouseleave": this.handleMouseleave
380 }, [(_this$$slots$default = (_this$$slots = this.$slots).default) === null || _this$$slots$default === void 0 ? void 0 : _this$$slots$default.call(_this$$slots)]);
381 }
382});
383
384var ContextmenuDivider = defineComponent({
385 name: "VContextmenuDivider",
386 render: function render() {
387 return createVNode("li", {
388 "class": CLASSES.contextmenuDivider
389 }, null);
390 }
391});
392
393var ContextmenuIcon = defineComponent({
394 name: "VContextmenuIcon",
395 props: {
396 name: {
397 type: String,
398 required: true
399 }
400 },
401 render: function render() {
402 return createVNode("i", {
403 "class": [CLASSES.contextmenuIcon, "".concat(CLASSES.contextmenuIcon, "-").concat(this.name)]
404 }, null);
405 }
406});
407
408var ContextmenuSubmenu = defineComponent({
409 name: "VContextmenuSubmenu",
410 props: {
411 title: {
412 type: String,
413 required: true
414 },
415 disabled: {
416 type: Boolean,
417 default: false
418 }
419 },
420 emits: ["mouseenter", "mouseleave"],
421 setup: function setup(props, _ref) {
422 var emit = _ref.emit;
423 var submenuRef = ref(null);
424 var autoAjustPlacement = inject("autoAjustPlacement");
425 var placements = ref(["top", "right"]);
426 var hover = ref(false);
427
428 var handleMouseenter = function handleMouseenter(evt) {
429 if (props.disabled) return;
430 hover.value = true;
431 emit("mouseenter", evt);
432 nextTick(function () {
433 var targetPlacements = [];
434
435 if (autoAjustPlacement) {
436 var target = evt.target;
437 var targetDimension = target.getBoundingClientRect();
438 if (!submenuRef.value) return;
439 var submenuWidth = submenuRef.value.clientWidth;
440 var submenuHeight = submenuRef.value.clientHeight;
441
442 if (targetDimension.right + submenuWidth >= window.innerWidth) {
443 targetPlacements.push("left");
444 } else {
445 targetPlacements.push("right");
446 }
447
448 if (targetDimension.bottom + submenuHeight >= window.innerHeight) {
449 targetPlacements.push("bottom");
450 } else {
451 targetPlacements.push("top");
452 }
453 }
454
455 placements.value = targetPlacements;
456 });
457 };
458
459 var handleMouseleave = function handleMouseleave(evt) {
460 if (props.disabled) return;
461 hover.value = false;
462 emit("mouseleave", evt);
463 };
464
465 var titleClasses = computed(function () {
466 var _ref2;
467
468 return _ref2 = {}, _defineProperty(_ref2, CLASSES.contextmenuItem, true), _defineProperty(_ref2, CLASSES.contextmenuSubmenuTitle, true), _defineProperty(_ref2, CLASSES.contextmenuItemHover, hover.value), _defineProperty(_ref2, CLASSES.contextmenuItemDisabled, props.disabled), _ref2;
469 });
470 var menusClasses = computed(function () {
471 var _ref3;
472
473 return _ref3 = {}, _defineProperty(_ref3, CLASSES.contextmenu, true), _defineProperty(_ref3, CLASSES.contextmenuSubmenuMenus, true), _defineProperty(_ref3, CLASSES.contextmenuSubmenuMenusTop, placements.value.includes("top")), _defineProperty(_ref3, CLASSES.contextmenuSubmenuMenusRight, placements.value.includes("right")), _defineProperty(_ref3, CLASSES.contextmenuSubmenuMenusBottom, placements.value.includes("bottom")), _defineProperty(_ref3, CLASSES.contextmenuSubmenuMenusLeft, placements.value.includes("left")), _ref3;
474 });
475 return {
476 hover: hover,
477 submenuRef: submenuRef,
478 titleClasses: titleClasses,
479 menusClasses: menusClasses,
480 handleMouseenter: handleMouseenter,
481 handleMouseleave: handleMouseleave
482 };
483 },
484 render: function render() {
485 var _this$$slots$title, _this$$slots, _this$$slots$default, _this$$slots2;
486
487 return createVNode("li", {
488 "class": CLASSES.contextmenuSubmenu,
489 "onMouseenter": this.handleMouseenter,
490 "onMouseleave": this.handleMouseleave
491 }, [createVNode("div", {
492 "class": this.titleClasses
493 }, [((_this$$slots$title = (_this$$slots = this.$slots).title) === null || _this$$slots$title === void 0 ? void 0 : _this$$slots$title.call(_this$$slots)) || this.title, createVNode("span", {
494 "class": CLASSES.contextmenuSubmenuArrow
495 }, [createVNode(ContextmenuIcon, {
496 "name": "right-arrow"
497 }, null)])]), this.hover ? createVNode("div", {
498 "ref": "submenuRef",
499 "class": this.menusClasses
500 }, [createVNode("ul", {
501 "class": CLASSES.contextmenuInner
502 }, [(_this$$slots$default = (_this$$slots2 = this.$slots).default) === null || _this$$slots$default === void 0 ? void 0 : _this$$slots$default.call(_this$$slots2)])]) : null]);
503 }
504});
505
506var ContextmenuGroup = defineComponent({
507 name: "VContextmenuGroup",
508 props: {
509 title: {
510 type: String,
511 default: undefined
512 },
513 maxWidth: {
514 type: [Number, String],
515 default: undefined
516 }
517 },
518 setup: function setup(props) {
519 var style = computed(function () {
520 if (!props.maxWidth) return;
521 return {
522 "max-width": typeof props.maxWidth === "number" ? "".concat(props.maxWidth, "px") : props.maxWidth,
523 "overflow-x": "auto"
524 };
525 });
526 return {
527 style: style
528 };
529 },
530 methods: {
531 renderTitle: function renderTitle() {
532 var _this$$slots$title, _this$$slots;
533
534 var content = ((_this$$slots$title = (_this$$slots = this.$slots).title) === null || _this$$slots$title === void 0 ? void 0 : _this$$slots$title.call(_this$$slots)) || this.title;
535 return content ? createVNode("div", {
536 "class": CLASSES.contextmenuGroupTitle
537 }, [content]) : null;
538 }
539 },
540 render: function render() {
541 var _this$$slots$default, _this$$slots2;
542
543 return createVNode("li", {
544 "class": CLASSES.contextmenuGroup
545 }, [this.renderTitle(), createVNode("ul", {
546 "style": this.style,
547 "class": CLASSES.contextmenuGroupMenus
548 }, [(_this$$slots$default = (_this$$slots2 = this.$slots).default) === null || _this$$slots$default === void 0 ? void 0 : _this$$slots$default.call(_this$$slots2)])]);
549 }
550});
551
552var version = "3.0.0";
553
554var install = function install(app) {
555 app.directive("contextmenu", contextmenuDirective);
556 app.component(Contextmenu.name, Contextmenu);
557 app.component(ContextmenuItem.name, ContextmenuItem);
558 app.component(ContextmenuDivider.name, ContextmenuDivider);
559 app.component(ContextmenuSubmenu.name, ContextmenuSubmenu);
560 app.component(ContextmenuGroup.name, ContextmenuGroup);
561};
562var VContextmenu = {
563 install: install,
564 version: version
565};
566
567export default VContextmenu;
568export { Contextmenu, ContextmenuDivider, ContextmenuGroup, ContextmenuItem, ContextmenuSubmenu, contextmenuDirective as directive, install, version };