| 1 |
<template> |
| 2 |
<IvyPopover |
| 3 |
:visible="isOpen" |
| 4 |
popper-class="ivyforms-popper-context-menu" |
| 5 |
:popper-background="'level-1-foreground'" |
| 6 |
:width="props.width" |
| 7 |
placement="bottom-start" |
| 8 |
:virtual-ref="resolveContextMenuButtonRef" |
| 9 |
:show-arrow="false" |
| 10 |
:offset="props.offset" |
| 11 |
virtual-triggering |
| 12 |
> |
| 13 |
<component |
| 14 |
:is="contextMenuStore.contextMenuContent" |
| 15 |
:actions="contextMenuStore.contextMenuProps.actions" |
| 16 |
:entity-id="contextMenuStore.contextMenuProps.entityId" |
| 17 |
:selection-type="contextMenuStore.contextMenuProps.selectionType" |
| 18 |
@action-executed="handleActionExecuted" |
| 19 |
@close-menu="contextMenuStore.closeContextMenu" |
| 20 |
/> |
| 21 |
</IvyPopover> |
| 22 |
</template> |
| 23 |
|
| 24 |
<script setup lang="ts"> |
| 25 |
import { ref, computed, type ComponentPublicInstance } from 'vue' |
| 26 |
import { storeToRefs } from 'pinia' |
| 27 |
import { useContextMenuStore } from '@/stores/contextMenuStore.ts' |
| 28 |
import { watch } from 'vue' |
| 29 |
import { onClickOutside } from '@vueuse/core' |
| 30 |
import IvyPopover from '@/views/_components/popover/IvyPopover.vue' |
| 31 |
|
| 32 |
interface Props { |
| 33 |
width?: number |
| 34 |
offset?: number |
| 35 |
} |
| 36 |
|
| 37 |
const props = withDefaults(defineProps<Props>(), { |
| 38 |
width: 205, |
| 39 |
offset: undefined, |
| 40 |
}) |
| 41 |
|
| 42 |
const contextMenuStore = useContextMenuStore() |
| 43 |
const { isOpen } = storeToRefs(contextMenuStore) |
| 44 |
|
| 45 |
const stopClickOutsideListener = ref<null | (() => void)>(null) |
| 46 |
|
| 47 |
const handleActionExecuted = () => { |
| 48 |
if (!contextMenuStore.contextMenuProps.selectionType) { |
| 49 |
contextMenuStore.closeContextMenu() |
| 50 |
} |
| 51 |
} |
| 52 |
|
| 53 |
const menuProps = computed(() => contextMenuStore.contextMenuProps) |
| 54 |
|
| 55 |
type MaybeHTMLElement = HTMLElement | (ComponentPublicInstance & { $el: HTMLElement }) |
| 56 |
|
| 57 |
watch( |
| 58 |
() => menuProps.value.contextMenuButtonRef, |
| 59 |
(newRef) => { |
| 60 |
if (stopClickOutsideListener.value) { |
| 61 |
// Cleanup old listener |
| 62 |
stopClickOutsideListener.value() |
| 63 |
} |
| 64 |
if (newRef instanceof HTMLElement) { |
| 65 |
stopClickOutsideListener.value = onClickOutside(newRef, (event) => { |
| 66 |
// Don't close if selectionType is present and click is on a menu item |
| 67 |
if (contextMenuStore.contextMenuProps.selectionType) { |
| 68 |
const target = event.target as HTMLElement |
| 69 |
if (target.closest('.ivyforms-popper-context-menu')) { |
| 70 |
return |
| 71 |
} |
| 72 |
} |
| 73 |
contextMenuStore.closeContextMenu() |
| 74 |
}) |
| 75 |
} else if (newRef && newRef.$el && newRef.$el instanceof HTMLElement) { |
| 76 |
stopClickOutsideListener.value = onClickOutside(newRef.$el, (event) => { |
| 77 |
// Don't close if selectionType is present and click is on a menu item |
| 78 |
if (contextMenuStore.contextMenuProps.selectionType) { |
| 79 |
const target = event.target as HTMLElement |
| 80 |
if (target.closest('.ivyforms-popper-context-menu')) { |
| 81 |
return |
| 82 |
} |
| 83 |
} |
| 84 |
contextMenuStore.closeContextMenu() |
| 85 |
}) |
| 86 |
} else { |
| 87 |
stopClickOutsideListener.value = null |
| 88 |
} |
| 89 |
}, |
| 90 |
) |
| 91 |
|
| 92 |
const resolveContextMenuButtonRef = computed(() => { |
| 93 |
const ref = contextMenuStore.contextMenuProps.contextMenuButtonRef as |
| 94 |
| MaybeHTMLElement |
| 95 |
| null |
| 96 |
| undefined |
| 97 |
if (ref && '$el' in ref && ref.$el instanceof HTMLElement) { |
| 98 |
return ref.$el |
| 99 |
} |
| 100 |
if (ref instanceof HTMLElement) { |
| 101 |
return ref |
| 102 |
} |
| 103 |
return null |
| 104 |
}) |
| 105 |
|
| 106 |
// Watch for when the context menu closes and remove the click outside listener |
| 107 |
watch(isOpen, (newVal) => { |
| 108 |
if (!newVal && stopClickOutsideListener.value) { |
| 109 |
stopClickOutsideListener.value() |
| 110 |
stopClickOutsideListener.value = null |
| 111 |
} |
| 112 |
}) |
| 113 |
</script> |
| 114 |
|
| 115 |
<style lang="scss"> |
| 116 |
// Popper Context Menu |
| 117 |
.ivyforms-popper-context-menu { |
| 118 |
// Element Popper |
| 119 |
&.el-popper.el-popover { |
| 120 |
padding: 0; |
| 121 |
border-radius: 2px; |
| 122 |
background: var(--map-ground-level-2-foreground); |
| 123 |
box-shadow: |
| 124 |
0 1px 2px 0 rgba(18, 26, 38, 0.3), |
| 125 |
0 1px 3px 1px rgba(18, 26, 38, 0.15); |
| 126 |
} |
| 127 |
} |
| 128 |
</style> |
| 129 |
|