primevue-mirror/components/contextmenu/ContextMenu.vue

269 lines
7.1 KiB
Vue
Raw Normal View History

2022-09-06 12:03:37 +00:00
<template>
<Portal :appendTo="appendTo">
<transition name="p-contextmenu" @enter="onEnter" @leave="onLeave" @after-leave="onAfterLeave">
2022-09-14 11:26:01 +00:00
<div v-if="visible" :ref="containerRef" :class="containerClass" v-bind="$attrs">
2022-09-06 12:03:37 +00:00
<ContextMenuSub :model="model" :root="true" @leaf-click="onLeafClick" :template="$slots.item" :exact="exact" />
</div>
</transition>
</Portal>
</template>
<script>
2022-09-14 11:26:01 +00:00
import { DomHandler, ZIndexUtils } from 'primevue/utils';
2022-09-06 12:03:37 +00:00
import ContextMenuSub from './ContextMenuSub.vue';
import Portal from 'primevue/portal';
export default {
name: 'ContextMenu',
inheritAttrs: false,
props: {
2022-09-14 11:26:01 +00:00
model: {
2022-09-06 12:03:37 +00:00
type: Array,
default: null
},
appendTo: {
type: String,
default: 'body'
},
autoZIndex: {
type: Boolean,
default: true
},
baseZIndex: {
type: Number,
default: 0
},
global: {
type: Boolean,
default: false
},
exact: {
type: Boolean,
default: true
}
},
target: null,
outsideClickListener: null,
resizeListener: null,
documentContextMenuListener: null,
pageX: null,
pageY: null,
container: null,
data() {
return {
visible: false
};
},
beforeUnmount() {
this.unbindResizeListener();
this.unbindOutsideClickListener();
this.unbindDocumentContextMenuListener();
if (this.container && this.autoZIndex) {
ZIndexUtils.clear(this.container);
}
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.container = null;
},
mounted() {
if (this.global) {
this.bindDocumentContextMenuListener();
}
},
methods: {
itemClick(event) {
const item = event.item;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (item.command) {
item.command(event);
event.originalEvent.preventDefault();
}
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.hide();
},
toggle(event) {
2022-09-14 11:26:01 +00:00
if (this.visible) this.hide();
else this.show(event);
2022-09-06 12:03:37 +00:00
},
onLeafClick() {
this.hide();
},
show(event) {
this.pageX = event.pageX;
this.pageY = event.pageY;
2022-09-14 11:26:01 +00:00
if (this.visible) this.position();
else this.visible = true;
2022-09-06 12:03:37 +00:00
event.stopPropagation();
event.preventDefault();
},
hide() {
this.visible = false;
},
onEnter(el) {
this.position();
this.bindOutsideClickListener();
this.bindResizeListener();
if (this.autoZIndex) {
ZIndexUtils.set('menu', el, this.baseZIndex + this.$primevue.config.zIndex.menu);
}
},
onLeave() {
this.unbindOutsideClickListener();
this.unbindResizeListener();
},
onAfterLeave(el) {
if (this.autoZIndex) {
ZIndexUtils.clear(el);
}
},
position() {
let left = this.pageX + 1;
let top = this.pageY + 1;
let width = this.container.offsetParent ? this.container.offsetWidth : DomHandler.getHiddenElementOuterWidth(this.container);
let height = this.container.offsetParent ? this.container.offsetHeight : DomHandler.getHiddenElementOuterHeight(this.container);
let viewport = DomHandler.getViewport();
//flip
if (left + width - document.body.scrollLeft > viewport.width) {
left -= width;
}
//flip
if (top + height - document.body.scrollTop > viewport.height) {
top -= height;
}
//fit
if (left < document.body.scrollLeft) {
left = document.body.scrollLeft;
}
//fit
if (top < document.body.scrollTop) {
top = document.body.scrollTop;
}
this.container.style.left = left + 'px';
this.container.style.top = top + 'px';
},
bindOutsideClickListener() {
if (!this.outsideClickListener) {
this.outsideClickListener = (event) => {
if (this.visible && this.container && !this.container.contains(event.target) && !event.ctrlKey) {
this.hide();
}
};
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
document.addEventListener('click', this.outsideClickListener);
}
},
unbindOutsideClickListener() {
if (this.outsideClickListener) {
document.removeEventListener('click', this.outsideClickListener);
this.outsideClickListener = null;
}
},
bindResizeListener() {
if (!this.resizeListener) {
this.resizeListener = () => {
if (this.visible && !DomHandler.isTouchDevice()) {
this.hide();
}
};
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
window.addEventListener('resize', this.resizeListener);
}
},
unbindResizeListener() {
if (this.resizeListener) {
window.removeEventListener('resize', this.resizeListener);
this.resizeListener = null;
}
},
bindDocumentContextMenuListener() {
if (!this.documentContextMenuListener) {
this.documentContextMenuListener = (event) => {
this.show(event);
};
document.addEventListener('contextmenu', this.documentContextMenuListener);
}
},
unbindDocumentContextMenuListener() {
2022-09-14 11:26:01 +00:00
if (this.documentContextMenuListener) {
2022-09-06 12:03:37 +00:00
document.removeEventListener('contextmenu', this.documentContextMenuListener);
this.documentContextMenuListener = null;
}
},
containerRef(el) {
this.container = el;
}
},
computed: {
containerClass() {
2022-09-14 11:26:01 +00:00
return [
'p-contextmenu p-component',
{
'p-input-filled': this.$primevue.config.inputStyle === 'filled',
'p-ripple-disabled': this.$primevue.config.ripple === false
}
];
2022-09-06 12:03:37 +00:00
}
},
components: {
2022-09-14 11:26:01 +00:00
ContextMenuSub: ContextMenuSub,
Portal: Portal
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
};
2022-09-06 12:03:37 +00:00
</script>
<style>
.p-contextmenu {
position: absolute;
}
.p-contextmenu ul {
margin: 0;
padding: 0;
list-style: none;
}
.p-contextmenu .p-submenu-list {
position: absolute;
min-width: 100%;
z-index: 1;
}
.p-contextmenu .p-menuitem-link {
cursor: pointer;
display: flex;
align-items: center;
text-decoration: none;
overflow: hidden;
position: relative;
}
.p-contextmenu .p-menuitem-text {
line-height: 1;
}
.p-contextmenu .p-menuitem {
position: relative;
}
.p-contextmenu .p-menuitem-link .p-submenu-icon {
margin-left: auto;
}
.p-contextmenu-enter-from {
opacity: 0;
}
.p-contextmenu-enter-active {
transition: opacity 250ms;
}
</style>