CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-imengyu--vue3-context-menu

A comprehensive context menu component library for Vue 3 applications with themes, keyboard navigation, and programmatic control

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

component-api.mddocs/

Component API

The component API provides Vue components for declarative context menu construction. This approach integrates seamlessly with Vue templates and is ideal for static menus, form-based interfaces, and component-driven architectures.

Capabilities

ContextMenu Component

Main context menu container component that manages menu display, positioning, and lifecycle.

declare component ContextMenu {
  props: {
    /** Menu configuration options */
    options: MenuOptions;
    /** Show/hide menu state */
    show: boolean;
  };
  emits: {
    /** Emitted when show state should change */
    'update:show': (show: boolean) => boolean;
    /** Emitted when menu closes */
    'close': () => boolean;
  };
  slots: {
    /** Default slot for menu items */
    default: {};
    /** Custom item renderer slot */
    itemRender: { 
      label: string; 
      icon: string; 
      disabled: boolean; 
      onClick: (e: MouseEvent) => void; 
      onMouseEnter: (e: MouseEvent) => void; 
    };
    /** Custom separator renderer slot */
    separatorRender: {};
    /** Custom icon renderer slot */
    itemIconRender: { label: string; icon: string; };
    /** Custom label renderer slot */
    itemLabelRender: { label: string; };
    /** Custom shortcut renderer slot */
    itemShortcutRender: { shortcut: string; };
    /** Custom right arrow renderer slot */
    itemRightArrowRender: {};
    /** Custom check mark renderer slot */
    itemCheckRender: { checked: boolean; };
  };
}

Usage Examples:

<template>
  <!-- Basic component usage -->
  <div @contextmenu="onRightClick">
    <context-menu v-model:show="showMenu" :options="menuOptions">
      <context-menu-item label="Copy" icon="copy" @click="handleCopy" />
      <context-menu-item label="Paste" icon="paste" @click="handlePaste" />
      <context-menu-separator />
      <context-menu-item label="Delete" icon="delete" @click="handleDelete" />
    </context-menu>
  </div>

  <!-- Advanced usage with nested menus -->
  <div @contextmenu="onAdvancedRightClick">
    <context-menu 
      v-model:show="showAdvancedMenu" 
      :options="advancedOptions"
      @close="onMenuClose"
    >
      <context-menu-item label="File Operations" icon="folder">
        <template #submenu="{ show }">
          <context-menu-group v-if="show" label="File">
            <context-menu-item label="New" shortcut="Ctrl+N" @click="createNew" />
            <context-menu-item label="Open" shortcut="Ctrl+O" @click="openFile" />
            <context-menu-separator />
            <context-menu-item label="Save" shortcut="Ctrl+S" @click="saveFile" />
          </context-menu-group>
        </template>
      </context-menu-item>
      
      <context-menu-item label="Edit" :disabled="!canEdit" @click="editItem" />
    </context-menu>
  </div>

  <!-- Custom rendering with slots -->
  <context-menu v-model:show="showCustomMenu" :options="customOptions">
    <template #itemRender="{ label, icon, onClick, onMouseEnter, disabled }">
      <div 
        :class="['custom-menu-item', { disabled }]"
        @click="onClick"
        @mouseenter="onMouseEnter"
      >
        <img v-if="icon" :src="icon" class="custom-icon" />
        <span class="custom-label">{{ label }}</span>
      </div>
    </template>
    
    <context-menu-item label="Custom Item" icon="/icons/custom.png" />
  </context-menu>
</template>

<script setup>
import { ref } from 'vue';

const showMenu = ref(false);
const menuOptions = ref({
  x: 0,
  y: 0,
  theme: 'default',
  zIndex: 1000
});

function onRightClick(e) {
  e.preventDefault();
  menuOptions.value.x = e.clientX;
  menuOptions.value.y = e.clientY;
  showMenu.value = true;
}

function onMenuClose() {
  console.log('Menu closed');
}
</script>

ContextMenuItem Component

Individual menu item component with support for icons, labels, shortcuts, and click handling.

declare component ContextMenuItem {
  props: {
    /** Menu item label text, VNode, or render function */
    label?: string | VNode | ((label: string) => VNode);
    /** Icon CSS class, VNode, or render function */
    icon?: string | VNode | ((icon: string) => VNode);
    /** Custom icon font class name */
    iconFontClass?: string;
    /** SVG symbol icon reference */
    svgIcon?: string;
    /** SVG element properties */
    svgProps?: SVGAttributes;
    /** Disable the menu item */
    disabled?: boolean;
    /** Hide the menu item */
    hidden?: boolean;
    /** Show check mark */
    checked?: boolean;
    /** Shortcut key display text */
    shortcut?: string;
    /** Reserve icon width space */
    preserveIconWidth?: boolean;
    /** Custom CSS class */
    customClass?: string;
    /** Custom render function */
    customRender?: VNode | ((item: MenuItem) => VNode);
    /** Click handler function */
    clickHandler?: (e: MouseEvent | KeyboardEvent) => void;
    /** Show right arrow indicator for submenus */
    showRightArrow?: boolean;
    /** Indicates if item has children/submenu */
    hasChildren?: boolean;
    /** Should close menu when this item is clicked */
    clickClose?: boolean;
    /** Allow click event when item has children */
    clickableWhenHasChildren?: boolean;
    /** Raw MenuItem data object */
    rawMenuItem?: MenuItem;
  };
  emits: {
    /** Emitted when item is clicked */
    'click': (e: MouseEvent | KeyboardEvent) => boolean;
    /** Emitted when submenu opens */
    'subMenuOpen': () => boolean;
    /** Emitted when submenu closes */
    'subMenuClose': () => boolean;
  };
  slots: {
    /** Default slot content */
    default: {};
    /** Icon slot */
    icon: {};
    /** Label slot */
    label: {};
    /** Shortcut slot */
    shortcut: {};
    /** Right arrow slot for submenus */
    rightArrow: {};
    /** Check mark slot */
    check: {};
    /** Submenu slot */
    submenu: { context: MenuItemContext; show: boolean; };
  };
}

Usage Examples:

<template>
  <!-- Basic menu items -->
  <context-menu-item label="Simple Item" @click="handleClick" />
  
  <!-- Item with icon and shortcut -->
  <context-menu-item 
    label="Save File" 
    icon="save-icon" 
    shortcut="Ctrl+S"
    @click="saveFile" 
  />
  
  <!-- Disabled and checked items -->
  <context-menu-item 
    label="Readonly Mode" 
    :disabled="!canEdit"
    :checked="isReadonly"
    @click="toggleReadonly" 
  />
  
  <!-- Item with custom icon slot -->
  <context-menu-item label="Custom Icon" @click="handleCustom">
    <template #icon>
      <svg class="custom-svg-icon">
        <use xlink:href="#my-icon"></use>
      </svg>
    </template>
  </context-menu-item>
  
  <!-- Item with submenu -->
  <context-menu-item label="Recent Files">
    <template #submenu="{ show, context }">
      <context-menu-group v-if="show" label="Recent">
        <context-menu-item 
          v-for="file in recentFiles" 
          :key="file.id"
          :label="file.name"
          @click="() => openFile(file)" 
        />
      </context-menu-group>
    </template>
  </context-menu-item>
  
  <!-- Item with custom rendering -->
  <context-menu-item 
    label="Custom Render"
    :custom-render="customItemRenderer"
    @click="handleCustomRender" 
  />
</template>

<script setup>
import { h } from 'vue';

function customItemRenderer(item) {
  return h('div', { class: 'custom-item-wrapper' }, [
    h('span', { class: 'custom-prefix' }, '★'),
    h('span', item.label),
    h('span', { class: 'custom-suffix' }, '→')
  ]);
}
</script>

ContextMenuSeparator Component

Visual separator component for grouping related menu items.

declare component ContextMenuSeparator {
  // No props or events
}

Usage:

<template>
  <context-menu>
    <context-menu-item label="Copy" @click="copy" />
    <context-menu-item label="Paste" @click="paste" />
    
    <!-- Visual separator -->
    <context-menu-separator />
    
    <context-menu-item label="Delete" @click="delete" />
  </context-menu>
</template>

ContextMenuGroup Component

Container component for creating nested submenus with optional group labels.

declare component ContextMenuGroup {
  props: {
    /** Group label text */
    label?: string;
    /** Icon CSS class or image path */
    icon?: string;
    /** Custom icon font class name */
    iconFontClass?: string;
    /** SVG symbol icon reference */
    svgIcon?: string;
    /** SVG element properties */
    svgProps?: SVGAttributes;
    /** Disable the group */
    disabled?: boolean;
    /** Hide the group */
    hidden?: boolean;
    /** Show check mark */
    checked?: boolean;
    /** Shortcut key display text */
    shortcut?: string;
    /** Reserve fixed-width icon area */
    preserveIconWidth?: boolean;
    /** Show right arrow indicator */
    showRightArrow?: boolean;
    /** Close menu when group is clicked */
    clickClose?: boolean;
    /** Adjust submenu position */
    adjustSubMenuPosition?: boolean;
    /** Maximum height of submenu */
    maxHeight?: number | string;
    /** Maximum width of submenu */
    maxWidth?: number | string;
    /** Minimum width of submenu */
    minWidth?: number | string;
    /** Custom CSS class */
    customClass?: string;
    /** Click handler function */
    clickHandler?: () => void;
  };
  slots: {
    /** Default slot for child menu items */
    default: {};
  };
}

Usage Examples:

<template>
  <!-- Basic group with label -->
  <context-menu-group label="File Operations">
    <context-menu-item label="New" @click="createNew" />
    <context-menu-item label="Open" @click="openFile" />
    <context-menu-item label="Save" @click="saveFile" />
  </context-menu-group>
  
  <!-- Nested groups -->
  <context-menu-group label="Edit">
    <context-menu-item label="Undo" @click="undo" />
    <context-menu-item label="Redo" @click="redo" />
    
    <context-menu-group label="Selection">
      <context-menu-item label="Select All" @click="selectAll" />
      <context-menu-item label="Select None" @click="selectNone" />
      <context-menu-item label="Invert Selection" @click="invertSelection" />
    </context-menu-group>
  </context-menu-group>
  
  <!-- Dynamic group content -->
  <context-menu-group label="Recent Projects">
    <context-menu-item 
      v-for="project in recentProjects" 
      :key="project.id"
      :label="project.name"
      :icon="project.icon"
      @click="() => openProject(project)" 
    />
    <context-menu-separator v-if="recentProjects.length > 0" />
    <context-menu-item label="Clear Recent" @click="clearRecent" />
  </context-menu-group>
</template>

Component Integration Patterns

Reactive Menu State

<template>
  <div class="editor" @contextmenu="showContextMenu">
    <context-menu v-model:show="menuVisible" :options="menuConfig">
      <context-menu-item 
        label="Cut" 
        :disabled="!hasSelection"
        shortcut="Ctrl+X"
        @click="cutText" 
      />
      <context-menu-item 
        label="Copy" 
        :disabled="!hasSelection"
        shortcut="Ctrl+C"
        @click="copyText" 
      />
      <context-menu-item 
        label="Paste" 
        :disabled="!canPaste"
        shortcut="Ctrl+V"
        @click="pasteText" 
      />
      <context-menu-separator />
      <context-menu-item 
        label="Select All" 
        shortcut="Ctrl+A"
        @click="selectAllText" 
      />
    </context-menu>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const menuVisible = ref(false);
const selectedText = ref('');
const clipboard = ref('');

const menuConfig = ref({
  x: 0,
  y: 0,
  theme: 'default'
});

const hasSelection = computed(() => selectedText.value.length > 0);
const canPaste = computed(() => clipboard.value.length > 0);

function showContextMenu(e) {
  e.preventDefault();
  menuConfig.value.x = e.clientX;
  menuConfig.value.y = e.clientY;
  menuVisible.value = true;
}
</script>

Menu with Form Integration

<template>
  <form @submit="submitForm">
    <input 
      v-model="formData.name" 
      @contextmenu="showFieldMenu"
      data-field="name"
    />
    
    <context-menu v-model:show="fieldMenuVisible" :options="fieldMenuConfig">
      <context-menu-item label="Clear Field" @click="clearCurrentField" />
      <context-menu-item label="Reset to Default" @click="resetCurrentField" />
      <context-menu-separator />
      <context-menu-item label="Validate Field" @click="validateCurrentField" />
      <context-menu-group label="Auto-fill">
        <context-menu-item 
          v-for="suggestion in currentFieldSuggestions" 
          :key="suggestion"
          :label="suggestion"
          @click="() => setFieldValue(suggestion)" 
        />
      </context-menu-group>
    </context-menu>
  </form>
</template>

<script setup>
import { ref, computed } from 'vue';

const fieldMenuVisible = ref(false);
const currentField = ref('');
const formData = ref({ name: '', email: '' });

const currentFieldSuggestions = computed(() => {
  if (currentField.value === 'name') {
    return ['John Doe', 'Jane Smith', 'Admin User'];
  }
  return [];
});

function showFieldMenu(e) {
  e.preventDefault();
  currentField.value = e.target.dataset.field;
  fieldMenuConfig.value.x = e.clientX;
  fieldMenuConfig.value.y = e.clientY;
  fieldMenuVisible.value = true;
}
</script>

Component Reference Interface

When using template refs with context menu components:

interface ContextMenuComponentRef extends ContextMenuInstance {
  /** Close the menu */
  closeMenu(): void;
  /** Check if menu is closed */
  isClosed(): boolean;
  /** Get menu reference for advanced control */
  getMenuRef(): ContextSubMenuInstance | undefined;
  /** Get menu dimensions */
  getMenuDimensions(): { width: number, height: number };
}

interface ContextMenuGroupRef {
  /** Get submenu instance */
  getSubMenuRef(): ContextSubMenuInstance;
  /** Get menu item reference */
  getMenuItemRef(): ContextSubMenuInstance;
}

Usage with Template Refs:

<template>
  <context-menu ref="menuRef" v-model:show="show" :options="options">
    <!-- menu items -->
  </context-menu>
  
  <button @click="controlMenu">Control Menu</button>
</template>

<script setup>
import { ref } from 'vue';

const menuRef = ref<ContextMenuComponentRef>();

function controlMenu() {
  if (menuRef.value) {
    if (menuRef.value.isClosed()) {
      // Menu is closed, could trigger show
    } else {
      // Menu is open, close it
      menuRef.value.closeMenu();
    }
  }
}
</script>

docs

component-api.md

functional-api.md

index.md

menu-bar.md

themes.md

tile.json