UNPKG

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