vue3-context-menu
vue3-context-menu copied to clipboard
How to use svg paths from @mdi/js
Thanks for the component! I'm using the function call to build the menu on right button click.
<div @contextmenu="openContextMenu($event, item)">{{ item.title }}</div>
With the menu builder:
const openContextMenu = (event: MouseEvent, item: DocumentReferenceWithTitle): void => {
event.preventDefault();
const menuItems: {label: string; onClick: () => void}[] = [
{
label: "Show text",
onClick: showDocumentText
},
{
label: "Remove",
onClick: removeDocument
},
];
ContextMenu.showContextMenu({
x: event.x,
y: event.y,
items: menuItems
});
};
This works perfectly for text only menu, but I don't know how to use one of the icons from the @mdi/js
packages to decorate the entry.
From a previous attempt with a custom solution I created the SVG string with:
const svgIcon = (mdi: string): string => {
return `<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor"
stroke-width="0.2" style="margin-right: 7px; position: relative; top: 0" fill="currentColor"
stroke-linecap="round" stroke-linejoin="round"><path d="${mdi}"></path></svg>`;
};
Using it, for example:
import {mdiTrashCanOutline} from "@mdi/js";
const svg = svgIcon(mdiTrashCanOutline);
How could use this string instead of the symbol mechanism that I'm not been able to make it work. Thanks! mario
Here wat I have done, if anyone interested, but I continue hoping for a better solution.
- In the template of the main component of the application add (you should also silence eslint that does not like a template inside a template):
<template id="svg-template">
<svg stroke="currentColor" stroke-width="0.2" style="display: none"
fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
<defs />
</svg>
</template>
- In the component using the context menu add:
let defs: SVGDefsElement | null;
onMounted(() => {
defs = null;
const body = document.querySelector("body");
if(!body) return;
const template = document.querySelector<HTMLTemplateElement>("#svg-template");
if(!template) return;
defs = template.querySelector<SVGDefsElement>("defs");
});
- Last, in the routine that builds the menu, add (here I have a menu with two entries):
import {mdiTextBoxOutline, mdiTrashCanOutline} from "@mdi/js";
const html = `<symbol viewBox="0 0 24 24" id="text-box"><path d="${mdiTextBoxOutline}"></path></symbol>
<symbol viewBox="0 0 24 24" id="trash-can"><path d="${mdiTrashCanOutline}"></path></symbol>`;
if(defs) defs.innerHTML = html.replaceAll(/ {2,}/g, " ");
...
const menuItems: {label: string; svgIcon: string; svgProps: Record<string, string>; onClick?: () => void}[] = [
{
label: "Show text",
svgIcon: "#text-box",
svgProps: {fill: fillColor},
onClick: showDocumentText
},
...
Hope it helps mario
Simplified and made more elegant. I created a component that define the needed SVG symbols
<script setup lang="ts">
const props = defineProps<{
/** SVG path and id for icons to be called in the context menu */
icons: {path: string; id: string}[];
}>();
</script>
<template>
<svg stroke="currentColor" stroke-width="0.2"
fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
<defs>
<symbol v-for="co of props.icons" :id="co.id" :key="co.id" viewBox="0 0 24 24">
<path :d="co.path" />
</symbol>
</defs>
</svg>
</template>
Hope it helps. mario