Source: components/context-menu.vue

<template>
    <q-menu ref="cm" style="z-index: 9999" touch-position :context-menu="contextMenu" @before-show="show($event)">
        <q-list dense style="min-width: 100px">
            <q-item v-for="item in options.filter(o => o.visible == undefined || o.visible == true)" clickable
                v-close-popup :key="item.label" @click="invokeCallback(item)">
                <q-item-section>{{ item.label }}</q-item-section>
            </q-item>
        </q-list>
    </q-menu>
</template>
<script>
/**
 * Vue component for configurable menus.
 *
 * @component ContextMenu
 * @example
 * <ContextMenu :options="options" />
 */
export default {
    name: "ContextMenu",
    props: {
        contextMenu: { type: Boolean, default: true },
        options: { type: Array }
    },
    data: () => ({
        shown: true,
    }),
    methods: {

        /**
         * Invokes the callback function for the selected item.
         * 
         * @param {Object} item - The selected item.
         */
        invokeCallback(item) {
            item.options = item.options ?? {};
            item.callback(item.options);
        },

        /**
         * Displays the context menu.
         *
         * @param {Object} menu - The menu object to be displayed.
         * @param {Event} e - The event object that triggered the display of the context menu.
         */
        show(e) {
            this.$store.clientCoordinates = [e.offsetX, e.offsetY];
            // see how many options are visible
            let visibleOption = null;
            let visibleOptionsCount = 0;
            for (let o of this.options) {
                if (o.visible || o.visible == undefined) {
                    visibleOption = o;
                    visibleOptionsCount++;
                }
            }
            if (visibleOptionsCount == 1 && visibleOption.runIfSingle) {
                // execute immediately if just one is visible
                this.$refs.cm.hide();
                this.invokeCallback(visibleOption);
                return false;
            } else if (visibleOptionsCount == 0) {
                // hide menu
                this.$refs.cm.hide();
                return false;
            }
            // otherwise menu will be shown
        },

        /**
         * Hides the context menu.
         */
        hide() {
            this.$refs.cm.hide();
        }
    }
}
</script>