90 lines
1.8 KiB
Vue
90 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, watch } from 'vue';
|
|
|
|
interface Item {
|
|
label: string;
|
|
action: () => void;
|
|
}
|
|
const props = defineProps<{ open: boolean; x: number; y: number; items: Item[] }>();
|
|
const emit = defineEmits<{ (e: 'update:open', v: boolean): void }>();
|
|
|
|
function close() {
|
|
emit('update:open', false);
|
|
}
|
|
function onDocClick() {
|
|
close();
|
|
}
|
|
function onKey(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') close();
|
|
}
|
|
|
|
watch(
|
|
() => props.open,
|
|
(open) => {
|
|
if (open) {
|
|
// defer so the opening contextmenu event doesn't immediately close it
|
|
setTimeout(() => {
|
|
document.addEventListener('click', onDocClick);
|
|
document.addEventListener('keydown', onKey);
|
|
});
|
|
} else {
|
|
document.removeEventListener('click', onDocClick);
|
|
document.removeEventListener('keydown', onKey);
|
|
}
|
|
},
|
|
);
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('click', onDocClick);
|
|
document.removeEventListener('keydown', onKey);
|
|
});
|
|
|
|
function run(item: Item) {
|
|
item.action();
|
|
close();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<ul
|
|
v-if="open"
|
|
class="ctx"
|
|
:style="{ left: x + 'px', top: y + 'px' }"
|
|
@click.stop
|
|
>
|
|
<li v-for="item in items" :key="item.label">
|
|
<button type="button" @click="run(item)">{{ item.label }}</button>
|
|
</li>
|
|
</ul>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.ctx {
|
|
position: fixed;
|
|
z-index: 1000;
|
|
min-width: 200px;
|
|
margin: 0;
|
|
padding: 6px;
|
|
list-style: none;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.ctx button {
|
|
width: 100%;
|
|
text-align: left;
|
|
border: 0;
|
|
background: transparent;
|
|
padding: 7px 10px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
}
|
|
.ctx button:hover {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
}
|
|
</style>
|