+
{{ getNodeIcon(node) }}
{{ node.label }}
{{ node.count }}
@@ -202,7 +272,7 @@ function getNodeClass(node: TopicNode): string {
-
+
{{ getNodeIcon(child) }}
{{ child.label }}
{{ child.count }}
@@ -214,6 +284,7 @@ function getNodeClass(node: TopicNode): string {
:key="gc.id"
:class="['tree-node child', { selected: gc.fullPath === selectedTopic, subscribed: gc.isSubscribed }]"
@click="handleNodeClick(gc)"
+ @contextmenu.prevent="handleContextMenu($event, gc)"
>
{{ getNodeIcon(gc) }}
{{ gc.label }}
@@ -225,6 +296,8 @@ function getNodeClass(node: TopicNode): string {
+
+
diff --git a/src/composables/useContextMenu.ts b/src/composables/useContextMenu.ts
new file mode 100644
index 0000000..6f4abdd
--- /dev/null
+++ b/src/composables/useContextMenu.ts
@@ -0,0 +1,41 @@
+import { onMounted, onUnmounted } from 'vue'
+
+const contextMenuHandlers = new Set<(e: MouseEvent) => void>()
+
+/**
+ * Register a custom right-click handler.
+ * The handler should call `e.preventDefault()` if it handles the event.
+ */
+export function registerContextMenu(handler: (e: MouseEvent) => void) {
+ contextMenuHandlers.add(handler)
+}
+
+export function unregisterContextMenu(handler: (e: MouseEvent) => void) {
+ contextMenuHandlers.delete(handler)
+}
+
+/**
+ * Global context menu controller.
+ * In production: prevents default browser context menu everywhere.
+ * In debug (dev mode): allows native context menu.
+ * Custom handlers registered via registerContextMenu take priority.
+ */
+export function useContextMenuController() {
+ function onContextMenu(e: MouseEvent) {
+ for (const handler of contextMenuHandlers) {
+ handler(e)
+ if (e.defaultPrevented) return
+ }
+ if (import.meta.env.PROD) {
+ e.preventDefault()
+ }
+ }
+
+ onMounted(() => {
+ document.addEventListener('contextmenu', onContextMenu)
+ })
+
+ onUnmounted(() => {
+ document.removeEventListener('contextmenu', onContextMenu)
+ })
+}
\ No newline at end of file