├── .npmignore ├── parcel-project ├── .gitignore ├── .hintrc ├── minimal-parcel-project.zip ├── index.html ├── index.js ├── package.json └── App.vue ├── library ├── env.d.ts ├── shims-vue.d.ts ├── ContextMenuIconCheck.vue ├── tsconfig.json ├── ContextMenuIconRight.vue ├── MenuBar.ts ├── index.ts ├── ContextMenuMutex.ts ├── ContextMenuSeparator.vue ├── vite.config.ts ├── MenuBarIconMenu.vue ├── MenuBar.scss ├── ContextMenu.vue ├── ContextMenuInstance.ts ├── ContextMenuGroup.vue ├── ContextMenuUtils.ts ├── MenuBar.vue └── ContextSubMenuWrapper.vue ├── examples ├── env.d.ts ├── css │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── iconfont.woff2 │ └── iconfont.css ├── shims-vue.d.ts ├── tsconfig.json ├── main.ts ├── tsconfig.app.json ├── tsconfig.node.json ├── vite.config.ts ├── index.html ├── router │ └── index.ts ├── views │ ├── MenuBar.vue │ ├── Theme.vue │ ├── ComponentCustomize.vue │ ├── BasicCustomize.vue │ ├── BasicComponent.vue │ └── ChangeContainer.vue ├── App.vue └── single-test.html ├── CHANGELOG.MD ├── .gitattributes ├── .npmrc ├── docs ├── demo-dark.png ├── demo-light.png ├── api │ ├── ContextMenuSeparator.md │ ├── MenuBar.md │ ├── ContextMenuItem.md │ ├── ContextMenu.md │ ├── ContextMenuGroup.md │ └── ContextMenuInstance.md ├── en │ ├── api │ │ ├── ContextMenuSeparator.md │ │ ├── MenuBar.md │ │ ├── ContextMenuItem.md │ │ ├── ContextMenu.md │ │ ├── ContextMenuGroup.md │ │ └── ContextMenuInstance.md │ ├── index.md │ ├── index.vue │ └── guide │ │ ├── custom-container.md │ │ ├── icon.md │ │ ├── theme.md │ │ ├── install.md │ │ └── useage.md ├── index.md ├── .vitepress │ ├── theme │ │ ├── index.ts │ │ └── MySandbox.vue │ └── config.mts ├── guide │ ├── start.md │ ├── custom-container.md │ ├── icon.md │ ├── theme.md │ ├── install.md │ └── useage.md ├── index.vue ├── index.scss └── change │ └── index.md ├── screenshot ├── first.png ├── example-mac.jpg ├── example-flat.jpg ├── example-win10.jpg ├── example-default.jpg ├── example-mac-dark.jpg ├── example-flat-dark.jpg ├── example-win10-dark.jpg └── example-default-dark.jpg ├── .gitignore ├── index.d.ts ├── LICENSE ├── package.json ├── README.CN.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /parcel-project/.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | dist/ -------------------------------------------------------------------------------- /library/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | Change log moved to [CHANGELOG](./docs/en/change/index.md). 2 | -------------------------------------------------------------------------------- /parcel-project/.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | #registry=https://registry.npmmirror.com/ 2 | registry=https://registry.npmjs.com/ 3 | -------------------------------------------------------------------------------- /docs/demo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/docs/demo-dark.png -------------------------------------------------------------------------------- /docs/demo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/docs/demo-light.png -------------------------------------------------------------------------------- /screenshot/first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/first.png -------------------------------------------------------------------------------- /examples/css/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/examples/css/iconfont.ttf -------------------------------------------------------------------------------- /examples/css/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/examples/css/iconfont.woff -------------------------------------------------------------------------------- /screenshot/example-mac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-mac.jpg -------------------------------------------------------------------------------- /examples/css/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/examples/css/iconfont.woff2 -------------------------------------------------------------------------------- /screenshot/example-flat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-flat.jpg -------------------------------------------------------------------------------- /screenshot/example-win10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-win10.jpg -------------------------------------------------------------------------------- /screenshot/example-default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-default.jpg -------------------------------------------------------------------------------- /screenshot/example-mac-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-mac-dark.jpg -------------------------------------------------------------------------------- /screenshot/example-flat-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-flat-dark.jpg -------------------------------------------------------------------------------- /screenshot/example-win10-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-win10-dark.jpg -------------------------------------------------------------------------------- /screenshot/example-default-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/screenshot/example-default-dark.jpg -------------------------------------------------------------------------------- /parcel-project/minimal-parcel-project.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue3-context-menu/HEAD/parcel-project/minimal-parcel-project.zip -------------------------------------------------------------------------------- /docs/api/ContextMenuSeparator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuSeparator 3 | --- 4 | 5 | # ContextMenuSeparator 6 | 7 | 菜单分隔符组件。应该与ContextMenuGroup一起使用。 8 | -------------------------------------------------------------------------------- /docs/en/api/ContextMenuSeparator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuSeparator 3 | --- 4 | 5 | # ContextMenuSeparator 6 | 7 | Menu separator component. Should be in ContextMenuGroup. -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue3-context-menu - 一个使用 Vue3 制作的简洁美观简单的右键菜单组件 3 | layout: home 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /library/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue3-context-menu - A simple, beautiful and simple context menu component made by Vue3 3 | layout: home 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /parcel-project/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /library/ContextMenuIconCheck.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /parcel-project/index.js: -------------------------------------------------------------------------------- 1 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 2 | import ContextMenu from '@imengyu/vue3-context-menu' 3 | import App from './App.vue' 4 | import { createApp } from 'vue' 5 | 6 | createApp(App).use(ContextMenu).mount('#app') -------------------------------------------------------------------------------- /examples/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import './css/iconfont.css' 5 | 6 | import ContextMenu from '../library/ContextMenuInstance' 7 | 8 | createApp(App) 9 | .use(router) 10 | .use(ContextMenu) 11 | .mount('#app') 12 | -------------------------------------------------------------------------------- /library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "./**/*", "./**/*.vue"], 4 | "exclude": ["./**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "../**/*", "../**/*.vue"], 4 | "exclude": ["../**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Bundler", 14 | "types": ["node"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import MySandbox from './MySandbox.vue'; 3 | import { Sandbox } from 'vitepress-plugin-sandpack'; 4 | import 'vitepress-plugin-sandpack/dist/style.css'; 5 | 6 | export default { 7 | ...DefaultTheme, 8 | enhanceApp(ctx) { 9 | DefaultTheme.enhanceApp(ctx); 10 | ctx.app.component('Sandbox', Sandbox); 11 | ctx.app.component('MySandbox', MySandbox); 12 | }, 13 | } -------------------------------------------------------------------------------- /examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | vueJsx(), 12 | ], 13 | base: './', 14 | resolve: { 15 | alias: { 16 | '@': fileURLToPath(new URL('./', import.meta.url)) 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /docs/guide/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 开始之前 3 | --- 4 | 5 | # 开始之前 6 | 7 | 文档中所示案例,你都可在 [Github 仓库](https://github.com/imengyu/vue3-context-menu/tree/main/examples/views) 中找到完整的源代码,也可以在 [这里查看所有示例的在线Demo](https://imengyu.top/pages/vue3-context-menu-demo/)。 8 | 9 | 作者开发不易,如果这个项目对您有帮助,希望你可以去 [Github](https://github.com/imengyu/vue3-context-menu) 或者 [Gitee](https://gitee.com/imengyu/vue3-context-menu) 帮我点个 ⭐ ,这将是对我极大的鼓励。谢谢啦 (●'◡'●) 10 | 11 | 如果你准备好了,那我们就开始吧~ 12 | 13 | [立即开始](./install.md) 14 | -------------------------------------------------------------------------------- /library/ContextMenuIconRight.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /library/MenuBar.ts: -------------------------------------------------------------------------------- 1 | import type { MenuOptions, MenuPopDirection } from "./ContextMenuDefine"; 2 | 3 | export interface MenuBarOptions extends Omit { 4 | /** 5 | * Set whether the current menu bar is collapsed, default is false. 6 | */ 7 | mini?: boolean; 8 | /** 9 | * Set the mian menu pop-up direction relative to coordinates. (With collapsed state) 10 | * 11 | * Default is `'bl'` 12 | * 13 | * @default 'bl' 14 | */ 15 | barPopDirection ?: MenuPopDirection, 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /lib 5 | /examples/dist 6 | /library/dist 7 | 8 | /examples/vue3-context-menu.css 9 | /examples/vue3-context-menu.umd.js 10 | 11 | # local env files 12 | .env.local 13 | .env.*.local 14 | .parcel-cache 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | pnpm-debug.log* 21 | 22 | # Editor directories and files 23 | .idea 24 | .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | 31 | /docs/.vitepress/cache/ 32 | /docs/.vitepress/dist/ 33 | -------------------------------------------------------------------------------- /library/index.ts: -------------------------------------------------------------------------------- 1 | import Inst from './ContextMenuInstance' 2 | export * from './ContextMenuInstance' 3 | export * from './MenuBar' 4 | import MenuBar from './MenuBar.vue'; 5 | import ContextMenu from './ContextMenu.vue'; 6 | import ContextMenuItem from './ContextMenuItem.vue'; 7 | import ContextMenuSeparator from './ContextMenuSeparator.vue'; 8 | import ContextMenuGroup from './ContextMenuGroup.vue'; 9 | 10 | export { 11 | ContextMenu, 12 | ContextMenuItem, 13 | ContextMenuSeparator, 14 | ContextMenuGroup, 15 | MenuBar, 16 | } 17 | 18 | export default Inst -------------------------------------------------------------------------------- /docs/api/MenuBar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MenuBar 3 | nav: 4 | title: API参考 5 | order: 3 6 | --- 7 | 8 | # MenuBar 9 | 10 | 菜单栏组件。 11 | 12 | ## Props 13 | 14 | | 属性 | 描述 | 类型 | 默认值 | 15 | | :----: | :----: | :----: | :----: | 16 | | options | 菜单相关定义 | [`MenuBarOptions`](#menubaroptions) | — | 17 | 18 | ## Slots 19 | 20 | | 插槽名 | 描述 | 参数 | 21 | | :----: | :----: | :----: | 22 | | prefix | 菜单栏前部渲染插槽 | - | 23 | | suffix | 菜单栏后部渲染插槽 | - | 24 | 25 | ## MenuBarOptions 26 | 27 | | 属性名 | 描述 | 类型 | 默认值 | 28 | | :----: | :----: | :----: | :----: | 29 | | mini | 指定当前菜单是否是迷你折叠模式 | `boolean` | `false` | 30 | | barPopDirection | 控制菜单栏一级子菜单弹出方向 | `MenuPopDirection` | `'bl'` | 31 | | ... | 其他参数均继承自 [MenuOptions](./ContextMenuInstance.md#menuoptions) | - | - | 32 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/MySandbox.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { ContextMenuInstance, MenuOptions } from './lib/ContextMenuDefine'; 2 | import ContextMenuGlobal from './lib/ContextMenuInstance'; 3 | 4 | export default ContextMenuGlobal; 5 | 6 | export * from './lib/ContextMenuDefine'; 7 | 8 | export * from './lib'; 9 | 10 | declare module 'vue3-context-menu' { 11 | } 12 | declare module 'vue' { 13 | export interface ComponentCustomProperties { 14 | /** 15 | * Show a ContextMenu . 16 | * @param options The options of this ContextMenu 17 | * @param customSlots You can provide some custom slots to customize the rendering style of the menu. These slots are the same as the slots of component ContextMenu. 18 | * @returns Menu instance 19 | */ 20 | $contextmenu: (options : MenuOptions, customSlots?: Record) => ContextMenuInstance; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /parcel-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@imengyu/vue3-context-menu", 3 | "version": "1.4.3", 4 | "description": "A context menu component for Vue3", 5 | "scripts": { 6 | "parcel-run": "parcel index.html", 7 | "parcel-build": "parcel build index.html" 8 | }, 9 | "devDependencies": { 10 | "@parcel/optimizer-data-url": "^2.12.0", 11 | "@parcel/transformer-inline-string": "^2.12.0", 12 | "@parcel/transformer-vue": "^2.12.0", 13 | "@tsconfig/node18": "^18.2.2", 14 | "@types/node": "^18.17.17", 15 | "@vue/compiler-sfc": "^3.5.12", 16 | "@vue/tsconfig": "^0.4.0", 17 | "parcel": "^2.12.0", 18 | "sass": "^1.69.5", 19 | "vue-loader": "^17.3.1", 20 | "vue-router": "^4.2.5" 21 | }, 22 | "dependencies": { 23 | "vue": "^3.3.4", 24 | "@imengyu/vue3-context-menu": "^1.4.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/ContextMenuMutex.ts: -------------------------------------------------------------------------------- 1 | import type { ContextMenuInstance } from "./ContextMenuDefine"; 2 | 3 | let currentOpenedContextMenu : ContextMenuInstance|null = null; 4 | 5 | 6 | export function checkOpenedContextMenu() : boolean { 7 | return currentOpenedContextMenu !== null; 8 | } 9 | export function addOpenedContextMenu(inst: ContextMenuInstance) : void { 10 | if (currentOpenedContextMenu) 11 | closeContextMenu(); 12 | currentOpenedContextMenu = inst; 13 | } 14 | export function removeOpenedContextMenu(inst: ContextMenuInstance) : void { 15 | if (inst === currentOpenedContextMenu) 16 | currentOpenedContextMenu = null; 17 | } 18 | /** 19 | * Close the currently open menu 20 | */ 21 | export function closeContextMenu() : void { 22 | if (currentOpenedContextMenu) { 23 | currentOpenedContextMenu.closeMenu(); 24 | currentOpenedContextMenu = null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/ContextMenuSeparator.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /docs/en/api/MenuBar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MenuBar 3 | --- 4 | 5 | # MenuBar 6 | 7 | Menu bar component. 8 | 9 | ## Props 10 | 11 | | Attribute | Description | Type | Default | 12 | | :----: | :----: | :----: | :----: | 13 | | options | Menu options | [`MenuBarOptions`](#menubaroptions) | — | 14 | 15 | ## Slots 16 | 17 | | Slot name | Description | Arguments | 18 | | :----: | :----: | :----: | 19 | | prefix | Rendering slot in front of menu bar | - | 20 | | suffix | The rendering slot behind the menu bar | - | 21 | 22 | ## MenuBarOptions 23 | 24 | | Property | Description | Type | Default | 25 | | :----: | :----: | :----: | :----: | 26 | | mini | Set whether the current menu bar is collapsed, default is false. | `boolean` | `false` | 27 | | barPopDirection | 控制菜单栏一级子菜单弹出方向 | `MenuPopDirection` | `'bl'` | 28 | | ... | All other parameters inherit from [MenuOptions](./ContextMenuInstance.md#menuoptions) | - | - | 29 | 30 | -------------------------------------------------------------------------------- /docs/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /docs/en/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /examples/css/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2648583 */ 3 | src: url('iconfont.woff2?t=1625231696258') format('woff2'), 4 | url('iconfont.woff?t=1625231696258') format('woff'), 5 | url('iconfont.ttf?t=1625231696258') format('truetype'); 6 | } 7 | 8 | .iconfont { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .icon-reload-:before { 17 | content: "\e898"; 18 | } 19 | 20 | .icon-print:before { 21 | content: "\e899"; 22 | } 23 | 24 | .icon-reload-1:before { 25 | content: "\e89e"; 26 | } 27 | 28 | .icon-save:before { 29 | content: "\e8a4"; 30 | } 31 | 32 | .icon-settings-1:before { 33 | content: "\e8a7"; 34 | } 35 | 36 | .icon-terminal:before { 37 | content: "\e8c2"; 38 | } 39 | 40 | .icon-yidong:before { 41 | content: "\e68c"; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /library/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import dts from 'vite-plugin-dts' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | vueJsx(), 13 | dts(), 14 | ], 15 | build: { 16 | lib: { 17 | entry: 'index.ts', 18 | name: 'vue3-context-menu', 19 | fileName: (format) => `vue3-context-menu.${format}.js`, 20 | }, 21 | rollupOptions: { 22 | external: ['vue'], 23 | output: { 24 | globals: { 25 | vue: 'Vue' 26 | }, 27 | assetFileNames: 'vue3-context-menu.[ext]', 28 | }, 29 | }, 30 | sourcemap: true, 31 | outDir: '../lib', 32 | }, 33 | resolve: { 34 | alias: { 35 | '@': fileURLToPath(new URL('./', import.meta.url)) 36 | } 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /library/MenuBarIconMenu.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 梦欤 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/guide/custom-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自定义菜单挂载容器 3 | order: 10 4 | --- 5 | 6 | # 自定义菜单挂载容器 7 | 8 | 允许您将菜单挂载至任意容器,如果不指定,默认挂载至 body。 9 | 10 | 通常可以用于类似编辑器的环境,只需要其中某个区域显示菜单,而其他地方不显示。 11 | 12 | 挂载容器修改后,菜单会显示在指定容器的内部,不会超出它。 13 | 14 | ## 设置挂载容器 15 | 16 | 只需要在 MenuOptions 上指定 getContainer 属性,即可设置挂载容器: 17 | 18 | ```ts 19 | ContextMenu.showContextMenu({ 20 | getContainer: () => myMenuContainer, //myMenuContainer 是挂载容器 21 | //... 22 | }) 23 | ``` 24 | 25 | > 注意: myMenuContainer 需要设置 `position: relative;` 样式,否则菜单无法定位。 26 | 27 | ## 坐标设置 28 | 29 | 之前菜单是按整个屏幕的范围显示的,传入x y是屏幕坐标,这没有问题。 30 | 31 | 但是更改挂载容器后,菜单显示区域发生了更改,所以菜单显示位置可能不正确。因此,您需要更改传入的x和y值,以确保显示位置正确。 32 | 33 | * `MenuOptions.x` 是从菜单到容器左边缘的距离; 34 | * `MenuOptions.y` 是从菜单到容器顶部边缘的距离; 35 | 36 | 您可能需要使用 `ContextMenu.transformMenuPosition` 转换菜单显示位置: 37 | 38 | ```ts 39 | function onContextMenu(e: MouseEvent) { 40 | const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY, myMenuContainer); //myMenuContainer 是挂载容器 41 | //show menu 42 | ContextMenu.showContextMenu({ 43 | x: scaledPosition.x, 44 | y: scaledPosition.y, 45 | //... 46 | }); 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /library/MenuBar.scss: -------------------------------------------------------------------------------- 1 | 2 | //Menu Bar 3 | //=================================================== 4 | .mx-menu-bar { 5 | flex: 1; 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | background-color: var(--mx-menu-backgroud); 10 | padding: 5px 0; 11 | 12 | &.mini { 13 | flex-grow: 0; 14 | } 15 | 16 | .mx-menu-bar-content { 17 | display: flex; 18 | flex-direction: row; 19 | align-items: center; 20 | } 21 | .mx-menu-bar-item { 22 | padding: 2px 8px; 23 | border-radius: 5px; 24 | user-select: none; 25 | background-color: var(--mx-menu-backgroud); 26 | color: var(--mx-menu-text); 27 | 28 | &:hover { 29 | background-color: var(--mx-menu-hover-backgroud); 30 | color: var(--mx-menu-hover-text); 31 | 32 | .mx-menu-bar-icon-menu { 33 | fill: var(--mx-menu-hover-text); 34 | } 35 | } 36 | &:active, &.active { 37 | background-color: var(--mx-menu-active-backgroud); 38 | color: var(--mx-menu-active-text); 39 | 40 | .mx-menu-bar-icon-menu { 41 | fill: var(--mx-menu-active-text); 42 | } 43 | } 44 | } 45 | 46 | .mx-menu-bar-icon-menu { 47 | fill: var(--mx-menu-text); 48 | width: var(--mx-menu-icon-size); 49 | height: var(--mx-menu-icon-size); 50 | } 51 | } 52 | 53 | .mx-menu-bar.flat { 54 | .mx-menu-bar-item { 55 | border-radius: 0; 56 | } 57 | } -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue3-context-menu 9 | 10 | 11 | 12 |
13 | 16 | Fork me on GitHub 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router' 2 | import BasicComponent from '../views/BasicComponent.vue' 3 | import BasicCustomize from '../views/BasicCustomize.vue' 4 | import BasicUseage from '../views/BasicUseage.vue' 5 | import Theme from '../views/Theme.vue' 6 | import ComponentCustomize from '../views/ComponentCustomize.vue' 7 | import MenuBar from '../views/MenuBar.vue' 8 | import ChangeContainer from '../views/ChangeContainer.vue' 9 | 10 | const routes: Array = [ 11 | { 12 | path: '/BasicComponent', 13 | name: 'BasicComponent', 14 | component: BasicComponent, 15 | }, 16 | { 17 | path: '/BasicCustomize', 18 | name: 'BasicCustomize', 19 | component: BasicCustomize, 20 | }, 21 | { 22 | path: '/ComponentCustomize', 23 | name: 'ComponentCustomize', 24 | component: ComponentCustomize, 25 | }, 26 | { 27 | path: '/', 28 | name: 'BasicUseage', 29 | component: BasicUseage, 30 | }, 31 | { 32 | path: '/Theme', 33 | name: 'Theme', 34 | component: Theme, 35 | }, 36 | { 37 | path: '/MenuBar', 38 | name: 'MenuBar', 39 | component: MenuBar, 40 | }, 41 | { 42 | path: '/ChangeContainer', 43 | name: 'ChangeContainer', 44 | component: ChangeContainer, 45 | } 46 | ] 47 | 48 | const router = createRouter({ 49 | history: createWebHashHistory(import.meta.env.BASE_URL), 50 | routes 51 | }) 52 | 53 | export default router 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@imengyu/vue3-context-menu", 3 | "version": "1.5.2", 4 | "description": "A context menu component for Vue3", 5 | "main": "lib/vue3-context-menu.umd.js", 6 | "module": "lib/vue3-context-menu.es.js", 7 | "files": [ 8 | "lib", 9 | "index.d.ts" 10 | ], 11 | "scripts": { 12 | "dev": "vite serve examples", 13 | "build-demo": "vite build examples", 14 | "build-lib": "vite build library", 15 | "build-types": "vue-tsc -p ./library/tsconfig.json --outDir ./lib --declaration --emitDeclarationOnly", 16 | "docs:dev": "cd docs && vitepress dev", 17 | "docs:build": "vitepress build ./docs" 18 | }, 19 | "devDependencies": { 20 | "@tsconfig/node18": "^18.2.2", 21 | "@types/node": "^18.17.17", 22 | "@vitejs/plugin-vue": "^4.3.4", 23 | "@vitejs/plugin-vue-jsx": "^3.0.2", 24 | "@vue/tsconfig": "^0.4.0", 25 | "markdown-it-container": "^4.0.0", 26 | "npm-run-all2": "^6.0.6", 27 | "rimraf": "^5.0.5", 28 | "sass": "^1.69.5", 29 | "typescript": "~5.2.0", 30 | "vite": "^4.4.9", 31 | "vite-plugin-dts": "^3.6.4", 32 | "vitepress": "^1.0.0-rc.31", 33 | "vitepress-plugin-sandpack": "^1.1.4", 34 | "vue": "^3.3.4", 35 | "vue-router": "^4.2.5", 36 | "vue-tsc": "^1.8.11" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/imengyu/vue3-context-menu.git" 41 | }, 42 | "keywords": [ 43 | "vue3", 44 | "vue", 45 | "context", 46 | "menu", 47 | "右键菜单", 48 | "菜单", 49 | "contextmenu" 50 | ], 51 | "author": "imengyu", 52 | "license": "MIT", 53 | "bugs": { 54 | "url": "https://github.com/imengyu/vue3-context-menu/issues" 55 | }, 56 | "homepage": "https://github.com/imengyu/vue3-context-menu#readme", 57 | "dependencies": { 58 | "@imengyu/vue-scroll-rect": "^0.1.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/api/ContextMenuItem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuItem 3 | --- 4 | 5 | # ContextMenuItem 6 | 7 | 菜单条目组件。 8 | 9 | ## Props 10 | 11 | | 属性 | 描述 | 类型 | 默认值 | 12 | | :----: | :----: | :----: | :----: | 13 | | label | 菜单项名称 | `string` | — | 14 | | icon | 菜单项图标 | `string` | — | 15 | | iconFontClass | 自定义图标字体类名 | `string` | — | `iconfont` | 16 | | preserveIconWidth | 是否应为没有图标的菜单项保留固定宽度的图标区域 | `boolean` | - | `true` | 17 | | svgIcon | 菜单项图标 svg,仅在 icon 为空时有效 | `string` | — | — | 18 | | svgProps | 当使用 svg 图标时,自定义 svg 标签属性 | `SVGAttributes` | — | — | 19 | | disabled | 是否禁用菜单项 | `boolean` | `false` | 20 | | checked | 是否选中菜单项 | `boolean` | — | `false` | 21 | | shortcut | 当前菜单项的快捷键指示,此快捷键只用于显示给用户看,快捷键的注册还是需要你自己处理 | `string` | — | `''` | 22 | | clickableWhenHasChildren | 指定当本菜单下有子菜单时,点击当前菜单是否触发点击事件 | `boolean` | `false` | 23 | | clickClose | 点击当前菜单项是否自动关闭整个菜单 | `boolean` | `true` | 24 | | customClass | 自定义子菜单class | `string` | — | 25 | | onClick | 菜单项点击事件 | `Function()` | — | 26 | 27 | ## Slots 28 | 29 | | 插槽名 | 描述 | 参数 | 30 | | :----: | :----: | :----: | 31 | | default | 当前条目整体渲染插槽 | - | 32 | | icon | 图标渲染插槽 | - | 33 | | label | 文字渲染插槽 | - | 34 | | shortcut | 快捷键标记渲染插槽 | - | 35 | | check | 复选框渲染插槽 | - | 36 | | rightArrow | 右侧箭头渲染插槽 | - | 37 | 38 | ## Click 39 | 40 | | 事件名 | 描述 | 参数 | 41 | | :----: | :----: | :----: | 42 | | click | 点击菜单时触发此事件 | - | 43 | | subMenuOpen | 子菜单打开时触发此事件 | - | 44 | | subMenuClose | 子菜单关闭时触发此事件 | - | 45 | 46 | ## MenuItemContext 47 | 48 | 菜单条目的控制项实例。 49 | 50 | ### `getSubMenuInstance(): ContextSubMenuInstance|undefined` 51 | 52 | 获取当前显示的子菜单实例。 53 | 54 | 返回值 55 | 56 | | 说明 | 57 | | :----: | 58 | | 返回当前子菜单的 [ContextSubMenuInstance](./ContextMenuGroup.md#contextsubmenuinstance),如果菜单未显示则返回undefined。 | 59 | 60 | ### `getElement(): HTMLElement` 61 | 62 | 获取当前子菜单元素。 63 | 64 | ### `showSubMenu(): void` 65 | 66 | 手动打开子菜单。 67 | 68 | ### `hideSubMenu(): void` 69 | 70 | 手动关闭子菜单。 71 | 72 | ### `isDisabledOrHidden(): boolean` 73 | 74 | 获取当前子菜单是否被隐藏或者禁用。 75 | -------------------------------------------------------------------------------- /docs/en/guide/custom-container.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Customize the menu mount container 3 | order: 4 4 | --- 5 | 6 | # Customize the menu mount container 7 | 8 | Allows you to mount the menu to any container. If not specified, it is mounted to the body by default. 9 | 10 | Usually, it can be used in an editor like environment: You only need to display the menu in one area, but not the other. 11 | 12 | After the mount container is modified, the menu will be displayed inside the specified container and will not exceed it. 13 | 14 | ## Set the container 15 | 16 | Just specify the getContainer property on MenuOptions to set the mount container: 17 | 18 | ```ts 19 | ContextMenu.showContextMenu({ 20 | getContainer: () => myMenuContainer, //myMenuContainer is the container 21 | //... 22 | }) 23 | ``` 24 | 25 | > Note: myMenuContainer needs to set `position: relative` style, otherwise the display position of the menu may be incorrect. 26 | 27 | ## Coordinate Settings 28 | 29 | Before, the menu was displayed according to the whole screen range. The input x y is the screen coordinate, which is no problem. 30 | 31 | After changing the mount container, the menu display area has changed, so the menu display location may be incorrect. Therefore, you need to change the x and y values to ensure that the display position is correct. 32 | 33 | * The MenuOptions.x is the distance from the menu to the left edge of the container; 34 | * The MenuOptions.y is the distance from the menu to the top edge of the container; 35 | 36 | You may need to use the `ContextMenu.transformMenuPosition` to transform coordinates of the menu position: 37 | 38 | ```ts 39 | function onContextMenu(e: MouseEvent) { 40 | const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY, myMenuContainer); //myMenuContainer is the container 41 | //show menu 42 | ContextMenu.showContextMenu({ 43 | x: scaledPosition.x, 44 | y: scaledPosition.y, 45 | //... 46 | }); 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/index.scss: -------------------------------------------------------------------------------- 1 | $primary-color: var(--vp-button-brand-bg); 2 | $text-color: var(--vp-c-text-1); 3 | $second-text-color: var(--vp-c-text-2); 4 | 5 | html[class="dark"] { 6 | .hidden-dark { 7 | visibility: hidden; 8 | } 9 | .hidden-light { 10 | visibility: visible; 11 | } 12 | } 13 | 14 | .hidden-light { 15 | visibility: hidden; 16 | } 17 | 18 | .big-home-area { 19 | max-width: 1152px; 20 | margin: 0 auto; 21 | } 22 | .big-title { 23 | margin-top: 100px; 24 | 25 | h1 { 26 | color:$primary-color; 27 | line-height: 64px; 28 | font-size: 56px; 29 | font-weight: bold; 30 | } 31 | p { 32 | font-size: 26px; 33 | line-height: 34px; 34 | color: $second-text-color; 35 | 36 | &.big { 37 | font-size: 36px; 38 | line-height: 55px; 39 | color: $text-color; 40 | font-weight: bold; 41 | } 42 | 43 | } 44 | } 45 | .big-demo { 46 | img { 47 | position: absolute; 48 | right: 300px; 49 | top: -60px; 50 | z-index: -1; 51 | } 52 | } 53 | 54 | @media screen and (max-width: 1600px) { 55 | .big-demo img { 56 | right: 200px; 57 | } 58 | } 59 | @media screen and (max-width: 1450px) { 60 | .big-demo img { 61 | right: 100px; 62 | } 63 | } 64 | @media screen and (max-width: 1200px) { 65 | .big-demo img { 66 | position: relative; 67 | margin-top: 30px; 68 | right: 0; 69 | top: 0; 70 | } 71 | .big-home-area { 72 | padding: 0 50px; 73 | } 74 | } 75 | 76 | .big-start { 77 | padding: 20px 0 40px 0; 78 | 79 | button { 80 | font-size: 16px; 81 | padding: 10px 20px; 82 | border-radius: 30px; 83 | outline: none; 84 | appearance: none; 85 | margin-right: 10px; 86 | 87 | border-color: var(--vp-button-alt-border); 88 | color: var(--vp-button-alt-text); 89 | background-color: var(--vp-button-alt-bg); 90 | 91 | &.primary { 92 | border: 1px solid var(--vp-button-brand-border); 93 | color: var(--vp-button-brand-text); 94 | background-color: var(--vp-button-brand-bg); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /library/ContextMenu.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 71 | -------------------------------------------------------------------------------- /parcel-project/App.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | -------------------------------------------------------------------------------- /docs/guide/icon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 菜单图标 3 | order: 3 4 | --- 5 | 6 | # 菜单图标 7 | 8 | 菜单组件不提供任何图标,如果您想添加图标,推荐使用 [iconfont](http://iconfont.cn) 图标库。 9 | 10 | ## 字体图标 11 | 12 | 使用 iconfont 图标库,导入字体后填写 `MenuItem` 的 `icon` 属性,即可在菜单项前面显示图标。 13 | 14 | * 如果字体名称不一样,可用 `MenuItem` 或者 `MenuOptions` 的 `iconFontClass` 属性指定其他字体。 15 | * 默认使用 `` 元素来显示图标。 16 | 17 | ## SVG 图标 18 | 19 | 支持使用 svg symbol 来显示图标: 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | 27 | 28 | ``` 29 | 30 | ```js 31 | this.$contextmenu({ 32 | items: [ 33 | { 34 | label: "Item with svg icon", 35 | svgIcon: "#icon-multiply", 36 | svgProps: { 37 | fill: '#f60', 38 | }, 39 | }, 40 | ], 41 | //...省略 42 | }); 43 | ``` 44 | 45 | > 注:使用 iconfont 库导出的图标库,会附带 `iconfont.js` ,你只需要在 html 中引入这个js,他会自动为你导入所有的 svg symbol,你可直接复制图标名称使用。 46 | 47 | ## 自定义渲染 48 | 49 | 你也可以通过菜单的插槽来完全自定义渲染图标,如: 50 | 51 | 在组件模式自定义图标: 52 | 53 | ```html 54 | 55 | 58 | 59 | ``` 60 | 61 | 自定义整个菜单的图标: 62 | 63 | ```html 64 | 68 | 72 | ... 73 | 74 | ``` 75 | 76 | 在函数模式自定义图标: 77 | 78 | ```js 79 | import { h } from 'vue'; 80 | 81 | { 82 | label: "Item with custom icon render", 83 | icon: h('img', { 84 | src: 'https://imengyu.top/assets/images/test/icon.png', 85 | style: { 86 | width: '20px', 87 | height: '20px', 88 | } 89 | }), 90 | divided: true, 91 | }, 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/api/ContextMenu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenu 3 | nav: 4 | title: API参考 5 | order: 3 6 | --- 7 | 8 | # ContextMenu 9 | 10 | 菜单主体组件。 11 | 12 | ## Props 13 | 14 | | 属性 | 描述 | 类型 | 默认值 | 15 | | :----: | :----: | :----: | :----: | 16 | | show(v-model) | 是否显示菜单 | `boolean` | — | 17 | | options | 菜单相关定义 | [`MenuOptions`](./ContextMenuInstance.md#menuoptions) | — | 18 | 19 | ## Events 20 | 21 | | 事件名 | 描述 | 参数 | 22 | | :----: | :----: | :----: | 23 | | close | 菜单关闭时触发此事件 | - | 24 | 25 | ## Slots 26 | 27 | | 插槽名 | 描述 | 参数 | 28 | | :----: | :----: | :----: | 29 | | itemRender | 当前菜单全局条目渲染插槽 | MenuItemRenderData | 30 | | itemIconRender | 当前菜单全局图标渲染插槽 | MenuItemRenderData | 31 | | itemLabelRender | 当前菜单全局文字渲染插槽 | MenuItemRenderData | 32 | | itemRightArrowRender | 当前菜单全局右侧箭头渲染插槽 | MenuItemRenderData | 33 | | itemCheckRender | 当前菜单全局复选框渲染插槽 | MenuItemRenderData | 34 | | itemShortcutRender | 当前菜单全局快捷键标记渲染插槽 | MenuItemRenderData | 35 | | separatorRender | 当前菜单分隔符渲染插槽 | - | 36 | 37 | ## ContextMenuInstance 38 | 39 | ContextMenu 组件接口,表示菜单基础实例。 40 | 41 | 你可以使用 `(this.$refs.myMenu as ContextMenuInstance)` 或者 `const mymenu = ref()` 来使用。 42 | 43 | ### `closeMenu(fromItem?: MenuItem|undefined): void` 44 | 45 | 关闭菜单. 46 | 47 | | 参数 | 说明 | 48 | | :----: | :----: | 49 | | fromItem | 最后单击的菜单项,将传递给 `MenuOptions.onClose` 回调,如果用户没有点击任何项,可以是 `undefined` 。 | 50 | 51 | ### `isClosed(): boolean` 52 | 53 | 检查当前菜单是否关闭。 54 | 55 | 返回值 56 | 57 | | 说明 | 58 | | :----: | 59 | | 当前实例是否已经关闭 | 60 | 61 | ### `getMenuRef(): ContextSubMenuInstance` 62 | 63 | 获取当前菜单根实例。 64 | 65 | 返回值 66 | 67 | | 说明 | 68 | | :----: | 69 | | 返回根的 [`ContextSubMenuInstance`](./ContextMenuGroup.md#contextsubmenuinstance),如果菜单未显示则返回 `undefined` 。 | 70 | 71 | ### `getMenuDimensions(): { width: number, height: number }` 72 | 73 | 获取当前菜单根的外框大小。 74 | 75 | 返回值 76 | 77 | | 说明 | 78 | | :----: | 79 | | 以像素为单位返回根菜单大小,如果菜单未显示则返回全零。 | 80 | 81 | ## MenuItemRenderData 结构 82 | 83 | | 属性名 | 描述 | 类型 | 84 | | :----: | :----: | :----: | 85 | | theme | 菜单主题 | `'light' 'dark'` | 86 | | isOpen | 指示当前菜单子菜单是否处于打开状态 | `boolean` | 87 | | hasChildren | 指示当前菜单是否有子级菜单 | `boolean` | 88 | | onClick | 自定义元素的点击事件回调,它用于菜单内部事件处理,当自定义渲染时,请回调此函数,否则菜单无法正常响应事件 | - | 89 | | onMouseEnter | 自定义元素的鼠标移入事件回调,它用于菜单内部事件处理,当自定义渲染时,请回调此函数,否则菜单无法正常响应事件 | - | 90 | | close | 调用此函数将关闭当前菜单 | - | 91 | | ... | 其他参数与 `MenuItem` 一致 | - | 92 | -------------------------------------------------------------------------------- /docs/guide/theme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 主题 3 | order: 5 4 | --- 5 | 6 | # 主题 7 | 8 | ## 内置主题 9 | 10 | 菜单内置了以下几种主题,你可以选择你喜欢的使用。 11 | 12 | 只需要在菜单选项中设置 `theme` 字段即可: 13 | 14 | ```js 15 | this.$contextmenu({ 16 | theme: 'mac dark', 17 | items: [ 18 | ], 19 | //省略... 20 | }); 21 | ``` 22 | 23 | |主题|说明|示意图| 24 | |--|--|--| 25 | |`default`|默认主题|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default.jpg)| 26 | |`default dark`|默认主题深色|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default-dark.jpg)| 27 | |`flat`|精简扁平菜单|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat.jpg)| 28 | |`flat dark`|精简扁平菜单深色|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat-dark.jpg)| 29 | |`win10`|仿Win10菜单白色|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10.jpg)| 30 | |`win10 dark`|仿Win10菜单黑色|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10-dark.jpg)| 31 | |`mac`|仿 macos catalina 菜单|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac.jpg)| 32 | |`mac dark`|仿 macos catalina 菜单深色模式|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac-dark.jpg)| 33 | 34 | ## 自定义主题 35 | 36 | 你可以新写你自己的主题,只需要保证主题名字与内置名字不同即可。 37 | 38 | 菜单所有的css样式定义都在 [`/ContextMenu.scss`](https://github.com/imengyu/vue3-context-menu/blob/main/library/ContextMenu.scss) 中。 39 | 你可以将所有样式复制出来,按需修改,存放在你的主题文件中。 40 | 41 | ### 自定义主题示例 42 | 43 | 例如下面是一个示例主题 `my-theme-name` 的实现,你自己的主题也可以按这个方式实现: 44 | 45 | ```scss 46 | .mx-context-menu.my-theme-name { 47 | & { 48 | //在这里覆盖默认css变量的值 49 | --mx-menu-backgroud: #ececec; 50 | --mx-menu-hover-backgroud: #0165e1; 51 | } 52 | 53 | //自定义菜单外层样式 54 | padding: 8px 0; 55 | box-shadow: 0px 5px 7px 1px var(--mx-menu-shadow-color); 56 | border: 1px solid var(--mx-menu-border-color); 57 | 58 | //自定义其他菜单部件的样式 59 | .mx-context-menu-item { 60 | border-radius: 5px; 61 | margin: 0 6px; 62 | padding: 3px 6px; 63 | } 64 | 65 | //这里篇幅有限,完整的每个部件的样式请在 ContextMenu.scss 中查看 66 | } 67 | .mx-menu-bar.my-theme-name { 68 | //自定义菜单栏的样式 69 | } 70 | ``` 71 | 72 | 然后导入主题文件,即可在 theme 中使用: 73 | 74 | ```js 75 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 76 | import '你的样式文件路径.scss' 77 | ``` -------------------------------------------------------------------------------- /docs/en/guide/icon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Menu icon 3 | order: 2 4 | --- 5 | 6 | # Menu icon 7 | 8 | The menu component does not provide any icons. If you want to add an icon, it is recommended to use [iconfont](http://iconfont.cn) Icon library. 9 | 10 | ## Font icon 11 | 12 | Use iconfont library: After importing, fill in the `icon` attribute of `MenuItem` to display the icon in front of the menu item. 13 | 14 | * If the font names are different, you can specify other fonts by writing the `iconFontClass` attribute of `MenuItem` or `MenuOptions`. 15 | * By default, the `` element is used to display icons 16 | 17 | ## SVG Icon 18 | 19 | Support the use of svg `` to display icons: 20 | 21 | ```html 22 | 23 | 24 | 25 | 26 | 27 | 28 | ``` 29 | 30 | ```js 31 | //Function mode 32 | this.$contextmenu({ 33 | items: [ 34 | { 35 | label: "Item with svg icon", 36 | svgIcon: "#icon-multiply", 37 | svgProps: { 38 | fill: '#f60', 39 | }, 40 | }, 41 | ], 42 | //... 43 | }); 44 | ``` 45 | 46 | ```js 47 | //Component mode 48 | 49 | ``` 50 | 51 | ## Customize icon 52 | 53 | You can also completely customize the rendering icon through the slot of the menu, such as: 54 | 55 | ```html 56 | 57 | 60 | 61 | ``` 62 | 63 | Customize the icon of the entire menu: 64 | 65 | ```html 66 | 70 | 74 | ... menu items ... 75 | 76 | ``` 77 | 78 | Customize icon in function mode: 79 | 80 | ```js 81 | import { h } from 'vue'; 82 | 83 | { 84 | label: "Item with custom icon render", 85 | icon: h('img', { 86 | src: 'https://imengyu.top/assets/images/test/icon.png', 87 | style: { 88 | width: '20px', 89 | height: '20px', 90 | } 91 | }), 92 | }, 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/en/api/ContextMenuItem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuItem 3 | --- 4 | 5 | # ContextMenuItem 6 | 7 | Menu item component. 8 | 9 | ## Props 10 | 11 | | Property | Description | Type | Default | 12 | | :----: | :----: | :----: | :----: | 13 | | label | The label of menu. | `string` | — | 14 | | icon | The icon for menu item. | `string` | — | 15 | | iconFontClass | Custom icon library font class name. | `string` | `iconfont` | 16 | | preserveIconWidth | Should a fixed-width icon area be reserved for menu items without icon. | `boolean` | `true` | 17 | | svgIcon | Display icons use svg symbol (``) , only valid when icon attribute is empty. | `string` | — | 18 | | svgProps | The user-defined attribute of the svg tag, which is valid when using `svgIcon`. | `SVGAttributes` | — | 19 | | disabled | Disable menu item? | `boolean` | `false` | 20 | | checked | Is this menu item checked? | `boolean` | `false` | 21 | | shortcut | Shortcut key text display on the right. The shortcut keys here are only for display. You need to handle the key events by yourself. | `string` | `''` | 22 | | clickableWhenHasChildren | When there are subitems in this item, is it allowed to trigger its own click event? | `boolean` | `false` | 23 | | clickClose | Should close menu when Click this menu item ? | `boolean` | `true` | 24 | | customClass | Custom submenu class. | `string` | — | 25 | | onClick | Menu item click event handler. | `Function()` | — | 26 | 27 | ## Slots 28 | 29 | | Slot name | Description | Arguments | 30 | | :----: | :----: | :----: | 31 | | default | Rendering slot for the current menu | - | 32 | | icon | Icon rendering slot | - | 33 | | label | Label rendering slot | - | 34 | | shortcut | Check mark render slot | MenuItemRenderData | 35 | | check | Shortcut key badge render slot | MenuItemRenderData | 36 | | rightArrow | Right Arrow rendering slot | - | 37 | 38 | ## Click 39 | 40 | | Event name | Description | Arguments | 41 | | :----: | :----: | :----: | 42 | | click | This event is triggered when the click this menu item | - | 43 | | subMenuOpen | Trigger this event when a submenu is opened | - | 44 | | subMenuClose | Trigger this event when the submenu is closed | - | 45 | 46 | ## MenuItemContext 47 | 48 | Control instance of a menu item. 49 | 50 | ### `getSubMenuInstance(): ContextSubMenuInstance|undefined` 51 | 52 | Get current showing submenu instance. 53 | 54 | Returns 55 | 56 | | Explan | 57 | | :----: | 58 | | Return [ContextSubMenuInstance](./ContextMenuGroup.md#contextsubmenuinstance) of current submenu, return undefined if menu is not showing. | 59 | 60 | ### `getElement(): HTMLElement` 61 | 62 | Get html Element of this item. 63 | 64 | ### `showSubMenu(): void` 65 | 66 | Show submenu of this item. 67 | 68 | ### `hideSubMenu(): void` 69 | 70 | Force hide submenu of this item. 71 | 72 | ### `isDisabledOrHidden(): boolean` 73 | 74 | Check is this item disabled or hidden. 75 | -------------------------------------------------------------------------------- /docs/en/guide/theme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Theme 3 | order: 3 4 | --- 5 | 6 | # Theme 7 | 8 | ## Built-in themes 9 | 10 | The menu contains the following themes, which you can choose to use. 11 | 12 | Just set the 'theme' field in the menu options: 13 | 14 | ```js 15 | this.$contextmenu({ 16 | theme: 'mac dark', 17 | items: [ 18 | ], 19 | //... 20 | }); 21 | ``` 22 | 23 | |Theme|Explain|Image| 24 | |--|--|--| 25 | |`default`|Default theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default.jpg)| 26 | |`default dark`|Default theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default-dark.jpg)| 27 | |`flat`|Simple flat theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat.jpg)| 28 | |`flat dark`|Simple flat theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat-dark.jpg)| 29 | |`win10`|Win10 like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10.jpg)| 30 | |`win10 dark`|Win10 like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10-dark.jpg)| 31 | |`mac`|Mac like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac.jpg)| 32 | |`mac dark`|Mac like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac-dark.jpg)| 33 | 34 | ## Custom Theme 35 | 36 | You can write your own theme, just make sure that the theme name is different from the built-in name. 37 | 38 | All CSS style definitions are in [`/ContextMenu.scss`](https://github.com/imengyu/vue3-context-menu/blob/main/library/ContextMenu.scss). You can copy all the styles, modify them as needed, and store them in your file. 39 | 40 | ### Custom Theme Example 41 | 42 | For example, the following is the implementation of an example theme `my-theme-name. Your own theme can also be implemented in this way: 43 | 44 | ```scss 45 | .mx-context-menu.my-theme-name { 46 | & { 47 | //Overwrite the value of the default css variable here 48 | --mx-menu-backgroud: #ececec; 49 | --mx-menu-hover-backgroud: #0165e1; 50 | } 51 | 52 | //Customize menu outer style 53 | padding: 8px 0; 54 | box-shadow: 0px 5px 7px 1px var(--mx-menu-shadow-color); 55 | border: 1px solid var(--mx-menu-border-color); 56 | 57 | //Customize the style of menu item 58 | .mx-context-menu-item { 59 | border-radius: 5px; 60 | margin: 0 6px; 61 | padding: 3px 6px; 62 | } 63 | 64 | //Please view the complete style of each part in ContextMenu.scss 65 | } 66 | .mx-menu-bar.my-theme-name { 67 | //Customize the style of the menu bar 68 | } 69 | ``` 70 | 71 | Then import the theme file and use it in theme: 72 | 73 | ```js 74 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 75 | import 'your-style-file-path.scss' 76 | ``` 77 | 78 | ```js 79 | this.$contextmenu({ 80 | theme: 'my-theme-name', 81 | items: [ 82 | ], 83 | //... 84 | }); 85 | ``` 86 | -------------------------------------------------------------------------------- /examples/views/MenuBar.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /docs/api/ContextMenuGroup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuGroup 3 | 4 | --- 5 | 6 | # ContextMenuGroup 7 | 8 | 子菜单组件。 9 | 10 | ## Props 11 | 12 | | 属性 | 描述 | 类型 | 默认值 | 13 | | :----: | :----: | :----: | :----: | 14 | | label | 菜单项名称 | `string` | — | 15 | | icon | 菜单项图标 | `string` | — | 16 | | iconFontClass | 自定义图标字体类名 | `string` | `iconfont` | 17 | | preserveIconWidth | 是否应为没有图标的菜单项保留固定宽度的图标区域 | `boolean` | `true` | 18 | | svgIcon | 菜单项图标 svg,仅在 icon 为空时有效 | `string` | — | 19 | | svgProps | 当使用 svg 图标时,自定义 svg 标签属性 | `SVGAttributes` | — | 20 | | disabled | 是否禁用菜单项 | `boolean` | `false` | 21 | | checked | 是否选中菜单项 | `boolean` | `false` | 22 | | shortcut | 当前菜单项的快捷键指示,此快捷键只用于显示给用户看,快捷键的注册还是需要你自己处理 | `string` | `''` | 23 | | clickableWhenHasChildren | 指定当本菜单下有子菜单时,点击当前菜单是否触发点击事件 | `boolean` | `false` | 24 | | adjustSubMenuPosition | 默认情况下,子菜单将自动调整其位置,以防止溢出容器。如果允许菜单溢出容器,可以将其设置为false | `boolean` | 继承自 `MenuOptions.adjustPosition` | 25 | | clickClose | 点击当前菜单项是否自动关闭整个菜单 | `boolean` | `true` | 26 | | customClass | 自定义子菜单class | `string` | — | 27 | | minWidth | 子菜单最小宽度 | `number` | `100` | 28 | | maxWidth | 子菜单最大宽度 | `number` | `600` | 29 | | onClick | 菜单项点击事件 | `Function()` | — | 30 | 31 | ## Slots 32 | 33 | | 插槽名 | 描述 | 参数 | 34 | | :----: | :----: | :----: | 35 | | default | 子菜单渲染插槽 | - | 36 | 37 | ## ContextMenuGroupRef 38 | 39 | ### `getSubMenuRef(): ContextSubMenuInstance` 40 | 41 | 获取当前子菜单实例。 42 | 43 | 返回值 44 | 45 | | 说明 | 46 | | :----: | 47 | | [`ContextSubMenuInstance`](#contextsubmenuinstance) 。 | 48 | 49 | ### `getMenuItemRef(): ContextSubMenuInstance` 50 | 51 | 获取当菜单条目控制实例。 52 | 53 | 返回值 54 | 55 | | 说明 | 56 | | :----: | 57 | | [`MenuItemContext`](./ContextMenuItem.md#menuitemcontext)。 | 58 | 59 | ## ContextSubMenuInstance 60 | 61 | 表示弹出菜单的控制实例,使用它可以控制菜单的更复杂的控制,包括获取菜单项,获取高度,位置,滚动,设置滚动数值,位置等等。 62 | 63 | 函数模式下可以从 `ContextMenuInstance.getMenuRef` 获取。 64 | 65 | ### `getSubmenuRoot(): HTMLElement` 66 | 67 | 获取当前子菜单的显示根元素。 68 | 69 | ### `getMenu(): HTMLElement` 70 | 71 | 获取当前子菜单的内容容器元素。 72 | 73 | ### `getChildItem(index: number) : MenuItemContext | undefined` 74 | 75 | 通过数组索引获取子菜单项控制实例,只有在显示父子菜单后才能检索子菜单项。 76 | 77 | 参数 78 | 79 | | 参数 | 说明 | 80 | | :----: | :----: | 81 | | index | 菜单项索引。 | 82 | 83 | 返回值 84 | 85 | | 说明 | 86 | | :----: | 87 | | 可以根据索引获取控制实例 [MenuItemContext](./ContextMenuItem.md#menuitemcontext),以实现对菜单项的实时控制。 | 88 | 89 | ### `getMenuDimensions(): { width: number, height: number }` 90 | 91 | 获取当前子菜单的大小。 92 | 93 | 返回值 94 | 95 | | 说明 | 96 | | :----: | 97 | | 以像素为单位返回根菜单大小,如果菜单未显示则返回全零。 | 98 | 99 | ### `getScrollValue(): number` 100 | 101 | 获取子菜单当前滚动值 (与 element.scrollTop 相同)。 102 | 103 | ### `getScrollHeight(): number` 104 | 105 | 获取子菜单当前滚动高度 (与 element.scrollHeight 相同)。 106 | 107 | ### `getMaxHeight(): number` 108 | 109 | 获取子菜单的最高高度。 110 | 111 | ### `getPosition(): { width: number, height: number }` 112 | 113 | 获取子菜单当前位置(相对于父项)。 114 | 115 | ### `setScrollValue(v: number): void` 116 | 117 | 设置子菜单当前滚动值 (与 element.scrollTop 相同)。 118 | 119 | 参数 120 | 121 | | 参数 | 说明 | 122 | | :----: | :----: | 123 | | v | 菜单滚动高度。 | 124 | 125 | ### `setPosition(x: number, y: number): void` 126 | 127 | 设置子菜单当前位置(相对于父项)。 128 | 129 | 参数 130 | 131 | | 参数 | 说明 | 132 | | :----: | :----: | 133 | | x | X轴位置 | 134 | | y | Y轴位置 | 135 | -------------------------------------------------------------------------------- /docs/en/api/ContextMenu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenu 3 | nav: 4 | title: API Reference 5 | order: 3 6 | --- 7 | 8 | # ContextMenu 9 | 10 | Menu component. 11 | 12 | ## Props 13 | 14 | | Attribute | Description | Type | Default | 15 | | :----: | :----: | :----: | :----: | 16 | | show(v-model) | Controls whether the menu is displayed | `boolean` | — | 17 | | options | Menu options, See [MenuOptions](./ContextMenuInstance.md#menuoptions) | `MenuOptions` | — | 18 | 19 | ## Events 20 | 21 | | Event name | Description | Arguments | 22 | | :----: | :----: | :----: | 23 | | close | This event is triggered when the menu is closed | - | 24 | 25 | ## Slots 26 | 27 | | Slot name | Description | Arguments | 28 | | :----: | :----: | :----: | 29 | | itemRender | Global menu item render slot | MenuItemRenderData | 30 | | itemIconRender | Global menu item icon render slot | MenuItemRenderData | 31 | | itemLabelRender | Global menu item label render slot | MenuItemRenderData | 32 | | itemRightArrowRender | Global menu item right arrow render slot | MenuItemRenderData | 33 | | itemCheckRender | Global menu check mark render slot | MenuItemRenderData | 34 | | itemShortcutRender | Global menu shortcut key badge render slot | MenuItemRenderData | 35 | | separatorRender | Global menu separator render slot | - | 36 | 37 | ## ContextMenuInstance 38 | 39 | ContextMenu Component ref interface, 40 | 41 | You can use `(this.$refs.myMenu as ContextMenuInstance)` or `const mymenu = ref()`. 42 | 43 | ### `closeMenu(fromItem?: MenuItem|undefined): void` 44 | 45 | Close this menu.. 46 | 47 | | Param | Explain | 48 | | :----: | :----: | 49 | | fromItem | The last clicked menu item, will pass to `MenuOptions.onClose` callback, if user does not click any item, can be `undefined`. | 50 | 51 | ### `isClosed(): boolean` 52 | 53 | Check if the menu is currently closed. 54 | 55 | Returns 56 | 57 | | Explain | 58 | | :----: | 59 | | Is this menu currently closed | 60 | 61 | ### `getMenuRef(): ContextSubMenuInstance` 62 | 63 | Get current Menu root instance. 64 | 65 | Returns 66 | 67 | | Explain | 68 | | :----: | 69 | | Return [`ContextSubMenuInstance`](./ContextMenuGroup.md#contextsubmenuinstance) of root, return undefined if menu is not showing. | 70 | 71 | ### `getMenuDimensions(): { width: number, height: number }` 72 | 73 | Get root menu size. 74 | 75 | Returns 76 | 77 | | Explain | 78 | | :----: | 79 | | Return root menu size in pixel, return all zero if menu is not showing. | 80 | 81 | ## `MenuItemRenderData` 82 | 83 | | Property | Description | Type | 84 | | :----: | :----: | :----: | 85 | | theme | Menu theme | `'light' 'dark'` | 86 | | isOpen | This value indicates whether the current menu submenu is open | `boolean` | 87 | | hasChildren | This value indicates whether the current menu has submenus | `boolean` | 88 | | onClick | Define the click event callback of the element, which is used for the internal event processing of the menu. When rendering item with slot, please call this function back, otherwise the menu cannot respond to the event normally | - | 89 | | onMouseEnter | Mouse in event callback of custom element. When rendering item with slot, please call this function back, otherwise the menu cannot respond to the event normally | - | 90 | | close | Calling this function will close the current menu | - | 91 | | ... | Other arguments are same with `MenuItem` | - | 92 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | 2 | # vue3-context-menu 3 | 4 | 一个使用 Vue3 制作的简洁美观简单的右键菜单组件 5 | 6 | ![截图](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/first.png) 7 | 8 | --- 9 | 10 | ## 特性 11 | 12 | * 简洁易用,体积小 13 | * 提供组件模式和函数模式,调用方便 14 | * 提供多个菜单主题供您使用 15 | * 可自定义 16 | 17 | ### 用法 18 | 19 | ``` 20 | npm install -save @imengyu/vue3-context-menu 21 | ``` 22 | 23 | 然后在 main.ts 中导入: 24 | 25 | ```js 26 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 27 | import ContextMenu from '@imengyu/vue3-context-menu' 28 | 29 | createApp(App).use(ContextMenu) 30 | ``` 31 | 32 | 然后你就可以在 vue 文件中使用菜单了: 33 | 34 | ```js 35 | import ContextMenu from '@imengyu/vue3-context-menu' 36 | 37 | onContextMenu(e : MouseEvent) { 38 | //prevent the browser's default menu 39 | e.preventDefault(); 40 | //show your menu 41 | ContextMenu.showContextMenu({ 42 | x: e.x, 43 | y: e.y, 44 | items: [ 45 | { 46 | label: "A menu item", 47 | onClick: () => { 48 | alert("You click a menu item"); 49 | } 50 | }, 51 | { 52 | label: "A submenu", 53 | children: [ 54 | { label: "Item1" }, 55 | { label: "Item2" }, 56 | { label: "Item3" }, 57 | ] 58 | }, 59 | ] 60 | }); 61 | } 62 | ``` 63 | 64 | 关于详细的用法,请参考文档。 65 | 66 | ## 文档 67 | 68 | [查看文档](https://docs.imengyu.top/vue3-context-menu-docs/) 69 | 70 | [查看在线演示](https://docs.imengyu.top/vue3-context-menu-demo/) 71 | 72 | ## 已有主题 73 | 74 | |theme|explain|example image| 75 | |--|--|--| 76 | |`default`|Default theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default.jpg)| 77 | |`default dark`|Default theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default-dark.jpg)| 78 | |`flat`|Simple flat theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat.jpg)| 79 | |`flat dark`|Simple flat theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat-dark.jpg)| 80 | |`win10`|Win10 like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10.jpg)| 81 | |`win10 dark`|Win10 like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10-dark.jpg)| 82 | |`mac`|Mac like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac.jpg)| 83 | |`mac dark`|Mac like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac-dark.jpg)| 84 | 85 | ## 广告: 作者其他的有用项目 86 | 87 | * [vue-code-layout A Vue editor layout component that like VSCode](https://github.com/imengyu/vue-code-layout) 88 | * [vue-dock-layout A Vue editor layout component that like Visual Studio](https://github.com/imengyu/vue-dock-layout) 89 | * [vue-dynamic-form A data driven form component for vue3](https://github.com/imengyu/vue-dynamic-form) 90 | 91 | ## 开发 92 | 93 | ```shell 94 | git clone git@github.com:imengyu/vue3-context-menu.git 95 | cd vue3-context-menu 96 | npm install 97 | npm run dev # Development serve project 98 | npm run build-demo # Build example project 99 | npm run build-lib # Build library project 100 | ``` 101 | 102 | ## License 103 | 104 | [MIT](./LICENSE) 105 | -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | 24 | 189 | -------------------------------------------------------------------------------- /examples/views/Theme.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | -------------------------------------------------------------------------------- /docs/en/api/ContextMenuGroup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ContextMenuGroup 3 | --- 4 | 5 | # ContextMenuGroup 6 | 7 | Submenu component. 8 | 9 | ## Props 10 | 11 | | Property | Description | Type | Default | 12 | | :----: | :----: | :----: | :----: | 13 | | label | The label of menu. | `string` | — | 14 | | icon | The icon for menu item. | `string` | — | 15 | | iconFontClass | Custom icon library font class name. | `string` | `iconfont` | 16 | | preserveIconWidth | Should a fixed-width icon area be reserved for menu items without icon. | `boolean` | `true` | 17 | | svgIcon | Display icons use svg symbol (``) , only valid when icon attribute is empty. | `string` | — | 18 | | svgProps | The user-defined attribute of the svg tag, which is valid when using `svgIcon`. | `SVGAttributes` | — | 19 | | disabled | Disable menu item? | `boolean` | `false` | 20 | | checked | Is this menu item checked? | `boolean` | `false` | 21 | | shortcut | Shortcut key text display on the right. The shortcut keys here are only for display. You need to handle the key events by yourself. | `string` | `''` | 22 | | clickableWhenHasChildren | When there are subitems in this item, is it allowed to trigger its own click event? | `boolean` | `false` | 23 | | adjustSubMenuPosition | By default, the submenu will automatically adjust its position to prevent it overflow the container. If you allow menu overflow containers, you can set this to false. | `boolean` | inherit from `MenuOptions.adjustPosition` | 24 | | clickClose | Should close menu when Click this menu item ? | `boolean` | `true` | 25 | | customClass | Custom submenu class. | `string` | — | 26 | | minWidth | Submenu minimum width (in pixels). | `number` | `100` | 27 | | maxWidth | Submenu maximum width (in pixels). | `number` | `600` | 28 | | onClick | Menu item click event handler. | `Function()` | — | 29 | 30 | ## Slots 31 | 32 | | Slot name | Description | Arguments | 33 | | :----: | :----: | :----: | 34 | | default | Submenu render slot | - | 35 | 36 | ## ContextMenuGroupRef 37 | 38 | ### `getSubMenuRef(): ContextSubMenuInstance` 39 | 40 | Get ContextSubMenuInstance of this group 41 | 42 | Returns 43 | 44 | | Explain | 45 | | :----: | 46 | | [`ContextSubMenuInstance`](#contextsubmenuinstance) 。 | 47 | 48 | ### `getMenuItemRef(): MenuItemContext` 49 | 50 | Get MenuItemContext of this item. 51 | 52 | Returns 53 | 54 | | Explain | 55 | | :----: | 56 | | [`MenuItemContext`](./ContextMenuItem.md#menuitemcontext)。 | 57 | 58 | ## ContextSubMenuInstance 59 | 60 | Define that Submenu holder component exposed props. Use it to control more complex controls of the menu, including getting menu items, getting height, position, scrolling, setting scroll values, position, and more. 61 | 62 | Can get by `ContextMenuInstance.getMenuRef`. 63 | 64 | ### `getSubmenuRoot(): HTMLElement` 65 | 66 | Get Root element of this submenu. 67 | 68 | ### `getMenu(): HTMLElement` 69 | 70 | Get Inner container element of this submenu. 71 | 72 | ### `getChildItem(index: number) : MenuItemContext | undefined` 73 | 74 | Get child menu item by array index, Only after the parent submenu is displayed can the child items be retrieved. 75 | 76 | Param 77 | 78 | | Param | Explain | 79 | | :----: | :----: | 80 | | index | Array index | 81 | 82 | Rturns 83 | 84 | | Explain | 85 | | :----: | 86 | | You can obtain control instance [MenuItemContext](./ContextMenuItem.md#menuitemcontext) according to the index to control menu items. | 87 | 88 | ### `getMenuDimensions(): { width: number, height: number }` 89 | 90 | Get submenu root element size. 91 | 92 | Returns 93 | 94 | | Explain | 95 | | :----: | 96 | | Return root menu size in pixel, return all zero if menu is not showing. | 97 | 98 | ### `getScrollValue(): number` 99 | 100 | Get submenu current scroll value (same as element.scrollTop). 101 | 102 | ### `getScrollHeight(): number` 103 | 104 | Get submenu max scroll height (same as element.scrollHeight). 105 | 106 | ### `getMaxHeight(): number` 107 | 108 | Get max submenu height. 109 | 110 | ### `getPosition(): { width: number, height: number }` 111 | 112 | Get submenu current position (Relative to the parent item). 113 | 114 | ### `setScrollValue(v: number): void` 115 | 116 | Set submenu current scroll value (same as element.scrollTop). 117 | 118 | Param 119 | 120 | | Param | Explain | 121 | | :----: | :----: | 122 | | v | element.scrollTop | 123 | 124 | ### `setPosition(x: number, y: number): void` 125 | 126 | Set submenu current position (Relative to the parent item). 127 | 128 | Param 129 | 130 | | Param | Explain | 131 | | :----: | :----: | 132 | | x | X position | 133 | | y | Y position | 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | vue3-context-menu 3 | --- 4 | A context menu component for Vue3 5 | 6 | ![Screenshot](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/first.png) 7 | 8 | English | [简体中文](https://github.com/imengyu/vue3-context-menu/blob/main/README.CN.md) 9 | 10 | --- 11 | 12 | ## Features 13 | 14 | * Simple and easy to use, small size 15 | * Provide component mode and function mode 16 | * Provide multiple theme styles for your use 17 | * Customizable 18 | 19 | ## Documentation 20 | 21 | [View Documentation](https://docs.imengyu.top/vue3-context-menu-docs/en/) 22 | 23 | [Click here View online Demo](https://docs.imengyu.top/vue3-context-menu-demo/) 24 | 25 | ### Usage 26 | 27 | ``` 28 | npm install -save @imengyu/vue3-context-menu 29 | ``` 30 | 31 | Then import in the main.ts file: 32 | 33 | ```js 34 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 35 | import ContextMenu from '@imengyu/vue3-context-menu' 36 | 37 | createApp(App).use(ContextMenu) 38 | ``` 39 | 40 | Then you can use the ContextMenu in the .vue file: 41 | 42 | ```js 43 | import ContextMenu from '@imengyu/vue3-context-menu' 44 | 45 | onContextMenu(e : MouseEvent) { 46 | //prevent the browser's default menu 47 | e.preventDefault(); 48 | //show your menu 49 | ContextMenu.showContextMenu({ 50 | x: e.x, 51 | y: e.y, 52 | items: [ 53 | { 54 | label: "A menu item", 55 | onClick: () => { 56 | alert("You click a menu item"); 57 | } 58 | }, 59 | { 60 | label: "A submenu", 61 | children: [ 62 | { label: "Item1" }, 63 | { label: "Item2" }, 64 | { label: "Item3" }, 65 | ] 66 | }, 67 | ] 68 | }); 69 | } 70 | ``` 71 | 72 | Or component: 73 | 74 | ```html 75 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | 91 | ```js 92 | data() { 93 | return { 94 | show: false, 95 | optionsComponent: { 96 | zIndex: 3, 97 | minWidth: 230, 98 | x: 500, 99 | y: 200 100 | }, 101 | } 102 | }, 103 | methods: { 104 | onButtonClick(e : MouseEvent) { 105 | //Show component mode menu 106 | this.show = true; 107 | this.options.x = e.x; 108 | this.options.y = e.y; 109 | }, 110 | } 111 | ``` 112 | 113 | ## Built-in themes 114 | 115 | |theme|explain|example image| 116 | |--|--|--| 117 | |`default`|Default theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default.jpg)| 118 | |`default dark`|Default theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-default-dark.jpg)| 119 | |`flat`|Simple flat theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat.jpg)| 120 | |`flat dark`|Simple flat theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-flat-dark.jpg)| 121 | |`win10`|Win10 like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10.jpg)| 122 | |`win10 dark`|Win10 like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-win10-dark.jpg)| 123 | |`mac`|Mac like theme|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac.jpg)| 124 | |`mac dark`|Mac like theme with dark|![example-default-dark.jpg](https://raw.githubusercontent.com/imengyu/vue3-context-menu/main/screenshot/example-mac-dark.jpg)| 125 | 126 | ## AD: Author's other project 127 | 128 | * [vue-code-layout A Vue editor layout component that like VSCode](https://github.com/imengyu/vue-code-layout) 129 | * [vue-dock-layout A Vue editor layout component that like Visual Studio](https://github.com/imengyu/vue-dock-layout) 130 | * [vue-dynamic-form A data driven form component for vue3](https://github.com/imengyu/vue-dynamic-form) 131 | 132 | ## Development 133 | 134 | ```shell 135 | git clone git@github.com:imengyu/vue3-context-menu.git 136 | cd vue3-context-menu 137 | npm install 138 | npm run dev # Development serve project 139 | npm run build-demo # Build example project 140 | npm run build-lib # Build library project 141 | ``` 142 | 143 | ## License 144 | 145 | [MIT](./LICENSE) 146 | -------------------------------------------------------------------------------- /library/ContextMenuInstance.ts: -------------------------------------------------------------------------------- 1 | import { h, ref, render } from "vue"; 2 | import type { App, Slot } from "vue"; 3 | import type { ContextMenuInstance } from "./ContextMenuDefine"; 4 | import type { MenuOptions } from "./ContextMenuDefine"; 5 | import { checkOpenedContextMenu, closeContextMenu } from "./ContextMenuMutex"; 6 | import { genContainer, transformMenuPosition } from "./ContextMenuUtils"; 7 | import ContextMenuConstructor from './ContextMenu.vue' 8 | import ContextSubMenuWrapperConstructor from './ContextSubMenuWrapper.vue' 9 | import ContextSubMenuConstructor from './ContextSubMenu.vue' 10 | import ContextMenuItemConstructor from './ContextMenuItem.vue' 11 | import ContextMenuGroupConstructor from './ContextMenuGroup.vue' 12 | import ContextMenuSeparatorConstructor from './ContextMenuSeparator.vue' 13 | 14 | function initInstance( 15 | options: MenuOptions, 16 | container: HTMLElement, 17 | isNew: boolean, 18 | customSlots?: Record, 19 | ) { 20 | const show = ref(true); 21 | const vnode = h(ContextSubMenuWrapperConstructor, { 22 | options: options, 23 | show: show, 24 | container: container, 25 | isFullScreenContainer: !isNew, 26 | onCloseAnimFinished: () => { 27 | render(null, container); 28 | }, 29 | onClose: (item: undefined) => { 30 | options.onClose?.(item); 31 | show.value = false; 32 | }, 33 | }, customSlots); 34 | render(vnode, container); 35 | return vnode.component; 36 | } 37 | 38 | //Show global contextmenu 39 | function $contextmenu(options : MenuOptions, customSlots?: Record) { 40 | const container = genContainer(options); 41 | const component = initInstance(options, container.container, container.isNew, customSlots); 42 | return (component as unknown as Record).exposed as ContextMenuInstance; 43 | } 44 | 45 | export default { 46 | /** 47 | * For Vue install 48 | * @param app 49 | */ 50 | install(app: App) : void { 51 | app.config.globalProperties.$contextmenu = $contextmenu; 52 | app.component('ContextMenu', ContextMenuConstructor); 53 | app.component('ContextMenuItem', ContextMenuItemConstructor); 54 | app.component('ContextMenuGroup', ContextMenuGroupConstructor); 55 | app.component('ContextMenuSperator', ContextMenuSeparatorConstructor); 56 | app.component('ContextMenuSeparator', ContextMenuSeparatorConstructor); 57 | app.component('ContextSubMenu', ContextSubMenuConstructor); 58 | }, 59 | /** 60 | * Show a ContextMenu in page, same as `this.$contextmenu` 61 | * 62 | * For example: 63 | * 64 | * ```ts 65 | * onContextMenu(e : MouseEvent) { 66 | * //prevent the browser's default menu 67 | * e.preventDefault(); 68 | * //show your menu 69 | * ContextMenu.showContextMenu({ 70 | * x: e.x, 71 | * y: e.y, 72 | * items: [ 73 | * { 74 | * label: "A menu item", 75 | * onClick: () => { 76 | * alert("You click a menu item"); 77 | * } 78 | * }, 79 | * { 80 | * label: "A submenu", 81 | * children: [ 82 | * { label: "Item1" }, 83 | * { label: "Item2" }, 84 | * { label: "Item3" }, 85 | * ] 86 | * }, 87 | * ] 88 | * }); 89 | * } 90 | * ``` 91 | * 92 | * You can pass customSlots to custom rendering this menu. 93 | * 94 | * For example, custom rendering #itemRender and #separatorRender: 95 | * ```ts 96 | * ContextMenu.showContextMenu({ 97 | * ... 98 | * } as MenuOptions, { 99 | * //Use slot in function mode 100 | * itemRender: ({ disabled, label, icon, showRightArrow, onClick, onMouseEnter }) => [ h('div', { 101 | * class: 'my-menu-item'+(disabled?' disabled':''), 102 | * onMouseenter: onMouseEnter, 103 | * onClick: onClick, 104 | * }, [ 105 | * icon ? h('img', { src: icon }) : h('div', { class: 'icon-place-holder' }), 106 | * h('span', label), 107 | * showRightArrow ? h('span', { class: 'right-arraw' }, '>>') : h('div'), 108 | * ]) ], 109 | * separatorRender: () => [ h('div', { class: 'my-menu-sperator' }) ] 110 | * }) 111 | * ``` 112 | * 113 | * @param options The options of ContextMenu 114 | * @param customSlots You can provide some custom slots to customize the rendering style of the menu. These slots are the same as the slots of component ContextMenu. 115 | * @returns Menu instance 116 | */ 117 | showContextMenu(options : MenuOptions, customSlots?: Record) : ContextMenuInstance { 118 | return $contextmenu(options, customSlots); 119 | }, 120 | /** 121 | * Get if there is a menu open now. 122 | */ 123 | isAnyContextMenuOpen() { 124 | return checkOpenedContextMenu(); 125 | }, 126 | /** 127 | * Close the currently open menu 128 | */ 129 | closeContextMenu, 130 | //Tools 131 | transformMenuPosition, 132 | } 133 | 134 | -------------------------------------------------------------------------------- /examples/single-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | vue3-context-menu test 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
{{ message }}
12 |
13 | 14 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 安装 3 | --- 4 | 5 | # 安装 6 | 7 | ```shell 8 | npm install -save @imengyu/vue3-context-menu 9 | ``` 10 | 11 | vue3-context-menu 仅支持 Vue3,如果你在使用 Vue2,可以试试下面的库。 12 | 13 | * [xunleif2e/vue-context-menu](https://github.com/xunleif2e/vue-context-menu) 14 | * [GitHub-Laziji/menujs](https://github.com/GitHub-Laziji/menujs) (本库就是启发自它) 15 | 16 | ## 全局导入组件 17 | 18 | 建议你全局导入组件,这样使用最方便。 19 | 20 | ```js 21 | //main.js 22 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 23 | import ContextMenu from '@imengyu/vue3-context-menu' 24 | 25 | createApp(App).use(ContextMenu).mount('#app') 26 | ``` 27 | 28 | ## 局部导入组件 29 | 30 | 首先导入样式文件: 31 | 32 | ```js 33 | //main.js 34 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 35 | ``` 36 | 37 | 然后在你需要使用的地方导入组件使用: 38 | 39 | ```vue 40 | 57 | ``` 58 | 59 | ## 开始使用 60 | 61 | 安装完成后您就可以 [开始使用](./useage.md) 了。 62 | 63 | ## 其他问题 64 | 65 | ### 如何在 CDN 引用的单页应用中使用菜单组件 66 | 67 | 在不使用脚手架等打包工具下,使用单页应用的 Vue 文件中,你需要修改下导入方式,用法与使用脚手架情况下一致。 68 | 69 | 你需要在html中引入这两个文件,文件可以在 npm 包的 lib 目录下找到。 70 | 71 | * vue3-context-menu.umd.js 72 | * vue3-context-menu.css 73 | 74 | 注册菜单组件: 75 | 76 | ```js 77 | createApp(...) 78 | .use(window['vue3-context-menu'].default) 79 | ``` 80 | 81 | 注册后即可像正常项目一样使用了。下方是一个完整案例: 82 | 83 | ```html 84 | 85 | 86 | 87 | vue3-context-menu test 88 | 89 | 90 | 91 | 92 | 93 |
94 |
{{ message }}
95 |
96 | 166 | 167 | 168 | ``` 169 | 170 | ### 修改注册全局 `$contextmenu` 函数和组件名称 171 | 172 | 如果你在全局导入的时候,发现本库菜单默认的名称与你已使用的库组件名称冲突,你可以手动注册,修改默认的名称: 173 | 174 | ```js 175 | //main.js 176 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'; 177 | import ContextMenuGlobal, { ContextMenu, ContextMenuGroup, ContextMenuSeparator, ContextMenuItem } from '@imengyu/vue3-context-menu'; 178 | 179 | const app = createApp(App); 180 | 181 | //取消默认注册的函数 182 | //app.use(ContextMenu) 183 | 184 | //重新指定注册组件的名称 185 | app.component('my-context-menu', ContextMenu) 186 | app.component('my-context-menu-group', ContextMenuGroup) 187 | app.component('my-context-menu-separator', ContextMenuSeparator) 188 | app.component('my-context-menu-item', ContextMenuItem) 189 | 190 | //重新注册挂载到 Vue 全局上的 $contextmenu 函数。 191 | app.config.globalProperties.$mycontextmenu = ContextMenuGlobal.showContextMenu; 192 | 193 | app.mount('#app') 194 | ``` 195 | -------------------------------------------------------------------------------- /library/ContextMenuGroup.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /library/ContextMenuUtils.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, toRefs } from "vue"; 2 | import type { VNode } from "vue"; 3 | import type { MenuOptions } from "./ContextMenuDefine"; 4 | import { MenuConstOptions } from "./ContextMenuDefine"; 5 | 6 | /** 7 | * Get absolute y position of HTMLElement 8 | * @param e Element 9 | * @param stopNode Specify the node for recursive termination, default to body 10 | * @returns 11 | */ 12 | export function getTop(e: HTMLElement, stopNode?: HTMLElement): number { 13 | let offset = e.offsetTop; 14 | if (e.offsetParent != null && e.offsetParent != stopNode) { 15 | offset -= e.offsetParent.scrollTop; 16 | offset += getTop(e.offsetParent as HTMLElement, stopNode); 17 | } 18 | return offset; 19 | } 20 | /** 21 | * Get absolute x position of HTMLElement 22 | * @param e Element 23 | * @param stopNode Specify the node for recursive termination, default to body 24 | * @returns 25 | */ 26 | export function getLeft(e: HTMLElement, stopNode?: HTMLElement): number { 27 | let offset = e.offsetLeft; 28 | if (e.offsetParent != null && e.offsetParent != stopNode) { 29 | offset -= e.offsetParent.scrollLeft; 30 | offset += getLeft(e.offsetParent as HTMLElement, stopNode); 31 | } 32 | return offset; 33 | } 34 | 35 | /** 36 | * If your `body` element is in a scaled state (e.g. `transform: scale(0.5)`), 37 | * this may lead to the wrong position of the menu display. 38 | * You can use this function to transform the menu display position: 39 | * 40 | * ```ts 41 | * 42 | import ContextMenu from '@imengyu/vue3-context-menu' 43 | 44 | function onContextMenu(e: MouseEvent) { 45 | const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY); 46 | //Full code of menuData is in `/examples/views/InScaledBody.vue` 47 | menuData.x = scaledPosition.x; 48 | menuData.y = scaledPosition.y; 49 | //show menu 50 | ContextMenu.showContextMenu(menuData); 51 | } 52 | * ``` 53 | * @param e Current click element 54 | * @param offsetX MouseEvent.offsetX 55 | * @param offsetY MouseEvent.offsetY 56 | */ 57 | export function transformMenuPosition(e: HTMLElement, offsetX: number, offsetY: number, container?: HTMLElement): { 58 | x: number, 59 | y: number, 60 | } { 61 | return { 62 | x: getLeft(e, container) + offsetX, 63 | y: getTop(e, container) + offsetY, 64 | }; 65 | } 66 | 67 | const DEFAULT_CONTAINER_ID = 'mx-menu-default-container'; 68 | const GEN_CONTAINER_ID = 'mx-menu-container-'; 69 | const GEN_SUB_CONTAINER_ID = 'mx-menu-sub-container-'; 70 | let containerId = 0; 71 | let subContainerId = 0; 72 | 73 | export function genSubContainerId() : string { 74 | return GEN_SUB_CONTAINER_ID + (subContainerId++); 75 | } 76 | export function removeContainer(container: HTMLElement) : void { 77 | container.parentNode?.removeChild(container); 78 | } 79 | export function genContainer(options: MenuOptions) : { 80 | eleId: string, 81 | container: HTMLElement, 82 | isNew: boolean, 83 | } { 84 | const { getContainer, zIndex } = options; 85 | 86 | if (getContainer) { 87 | const container = typeof getContainer === 'function' ? getContainer() : getContainer; 88 | if (container) { 89 | let eleId = container.getAttribute('id'); 90 | if (!eleId) { 91 | eleId = GEN_CONTAINER_ID + (containerId++); 92 | container.setAttribute('id', eleId); 93 | } 94 | return { 95 | eleId, 96 | container, 97 | isNew: false, 98 | }; 99 | } 100 | } 101 | 102 | let container = document.getElementById(DEFAULT_CONTAINER_ID); 103 | if (!container) { 104 | container = document.createElement('div'); 105 | container.setAttribute('id', DEFAULT_CONTAINER_ID); 106 | container.setAttribute('class', 'mx-menu-ghost-host fullscreen'); 107 | document.body.appendChild(container); 108 | } 109 | container.style.zIndex = zIndex?.toString() || MenuConstOptions.defaultZindex.toString(); 110 | return { 111 | eleId: DEFAULT_CONTAINER_ID, 112 | container, 113 | isNew: true, 114 | }; 115 | } 116 | 117 | export function hashCode(str: string) { 118 | let hash = 0; 119 | for (let i = 0; i < str.length; i++) { 120 | const chr = str.charCodeAt(i); 121 | hash = ((hash << 5) - hash) + chr; 122 | hash |= 0; // 转为32位整数 123 | } 124 | return hash; 125 | } 126 | 127 | /** 128 | * Number to px string 129 | * @param value 130 | * @returns 131 | */ 132 | export function solveNumberOrStringSize(value: string|number) : string { 133 | return typeof value === 'number' ? `${value}px` : value; 134 | } 135 | 136 | /** 137 | * Render a VNode 138 | */ 139 | export const VNodeRender = defineComponent({ 140 | props: { 141 | /** 142 | * Can be VNode or (data: unknown) => VNode 143 | */ 144 | vnode: { 145 | type: null, 146 | }, 147 | /** 148 | * If vnode is a callback, this data will be passed to the callback first parameter. 149 | * @default null 150 | */ 151 | data: { 152 | type: null, 153 | default: null, 154 | }, 155 | }, 156 | setup(props) { 157 | const { vnode, data } = toRefs(props); 158 | return () => typeof vnode.value === 'function' ? (vnode.value as unknown as (data: unknown) => VNode)(data.value) : vnode.value as unknown as VNode; 159 | }, 160 | }) 161 | 162 | /** 163 | * 从对象中移除指定的键,并返回一个新对象,新对象不包含该键 164 | * @param obj 要操作的对象 165 | * @param key 要移除的键 166 | * @returns 移除指定键后的新对象 167 | */ 168 | export function removeObjectKey>(obj: T, key: string) { 169 | const other = { ...obj }; 170 | delete other[key]; 171 | return other; 172 | } -------------------------------------------------------------------------------- /docs/api/ContextMenuInstance.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 函数模式 3 | --- 4 | 5 | # 函数模式 6 | 7 | ## ContextMenu 8 | 9 | ```ts 10 | import ContextMenu from '@imengyu/vue3-context-menu' 11 | ``` 12 | 13 | 菜单全局函数。 14 | 15 | ### `ContextMenu.showContextMenu(options: MenuOptions, customSlots?: Record)` 16 | 17 | 显示菜单. 18 | 19 | | 参数 | 说明 | 20 | | :----: | :----: | 21 | | options | 菜单的数据 | 22 | | customSlots | 这些插槽允许您自定义当前菜单的样式,这些插槽的名称与 [组件模式 ContextMenu](./ContextMenu.md) 中的插槽名称相同,具体自定义方式,可参考[自定义文档](../guide/customize.md)。 | 23 | 24 | 返回值 25 | 26 | | 类型 | 说明 | 27 | | :----: | :----: | 28 | | ContextMenuInstance | 当前菜单的实例,可调用 `closeMenu` 关闭菜单;可调用 `isClosed` 检查当前实例是否已经关闭 | 29 | 30 | ### `ContextMenu.closeContextMenu()` 31 | 32 | 手动关闭当前打开的菜单. 33 | 34 | ### `this.$contextmenu` 35 | 36 | 与 `ContextMenu.showContextMenu` 相同,但此函数注册到 Vue 全局属性中,可以在Vue实例中直接使用。 37 | 38 | ### `ContextMenu.transformMenuPosition` 39 | 40 | 如果你的 `body` 元素处于缩放状态 (例如添加了样式 `transform: scale(0.5)`), 这可能会导致菜单显示位置不正确。 41 | 你可以使用此函数来转换至正确的菜单显示位置: 42 | 43 | ```ts 44 | import ContextMenu from '@imengyu/vue3-context-menu' 45 | 46 | function onContextMenu(e: MouseEvent) { 47 | const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY); 48 | //完整示例代码位于 `/examples/views/InScaledBody.vue` 49 | menuData.x = scaledPosition.x; 50 | menuData.y = scaledPosition.y; 51 | //显示菜单 52 | ContextMenu.showContextMenu(menuData); 53 | } 54 | ``` 55 | 56 | ## MenuOptions 57 | 58 | | 属性 | 描述 | 类型 | 可选值 | 默认值 | 59 | | :----: | :----: | :----: | :----: | :----: | 60 | | items |
菜单结构信息
| `MenuItem[]` | — | — | 61 | | x | 菜单显示X坐标 | `number` | — | `0` | 62 | | y | 菜单显示Y坐标 | `number` | — | `0` | 63 | | xOffset | 子菜单与父菜单X的偏移 | `number` | — | `0` | 64 | | yOffset | 子菜单与父菜单Y的偏移 | `number` | — | `0` | 65 | | iconFontClass | 自定义图标字体类名 | `string` | — | `iconfont` | 66 | | zIndex | 菜单的`z-index` | `number` | — | `2` | 67 | | customClass | 自定义菜单类名 | `string` | — | — | 68 | | minWidth | 主菜单最小宽度(像素) | `number` or `string` | — | `100` | 69 | | maxWidth | 主菜单最大宽度(像素) | `number` or `string` | — | `600` | 70 | | maxHeight | 主菜单最大高度(像素) | `number` | — | - | 71 | | keyboardControl | 设置用户是否可以使用键盘键控制当前菜单 | `boolean` | — | `true` | 72 | | theme | 菜单的[主题](../guide/theme.md) | `string` | `'default' 'dark' 'flat' 'win10' 'mac'` | `default` | 73 | | preserveIconWidth | 是否应为没有图标的菜单项保留固定宽度的图标区域 | `boolean` | - | `true` | 74 | | closeWhenScroll | 用户滚动鼠标时是否关闭菜单 | `boolean` | - | `true` | 75 | | mouseScroll | 设置用户是否可以在菜单区域中使用鼠标滚轮来滚动长菜单。 | `boolean` | — | `false` | 76 | | adjustPadding | 自动调整菜单时的上下边距 | `{ x: number, y: number }` or `number` | — | `10` | 77 | | adjustPosition | 默认情况下,菜单将自动调整其位置,以防止溢出容器。如果允许菜单溢出容器,则可以将其设置为false。 | `boolean` | — | `true` | 78 | | direction | 设置主菜单相对于坐标的弹出方向。如果 `adjustPosition` 为 `true` ,则菜单会根据可用空间自动调整弹出方向。 | `'br'|'b'|'bl'|'tr'|'t'|'tl'|'l'|'r'` | — | `'br'` | 79 | | ignoreClickClassName | 若菜单项中的元素有这个className,单击它将忽略事件。| `string` | — | 80 | | menuTransitionProps | 设置菜单显示隐藏时的 Vue Transition 组件属性。 | `TransitionProps` | — | 81 | | clickCloseOnOutside | 设置是否当用户点击其他位置时应关闭菜单。| `boolean` | — | `true` | 82 | | clickCloseClassName | 若菜单项中的元素有这个className,单击它将忽略事件,并且点击后会关闭菜单。| `string` | — | 83 | | updownButtonSpaceholder | 决定菜单项中的上/下按钮是否需要空白占位。设置这个变量的目的是因为有些菜单主题会在菜单上下添加空白边距,这一部分空白边距刚好可以放置上/下按钮。如果你的自定义主题菜单中没有空白边距,则可以设置此字段为上/下按提供要空白占位,防止遮挡菜单条目。 | `boolean` | — | `false` | 84 | | getContainer | 自定义菜单挂载容器。[详情请参考](../guide/custom-container.md) | `HTMLElement` or `(() => HTMLElement)` | — | — | 85 | | onClose | 菜单关闭事件回调(通常在函数模式使用)。参数 lastClickItem 指示了触发关闭的菜单项(仅函数模式有效),如果用户没有点击菜单项而是点击其他地方关闭,则参数为 `undefined`。 | `((lastClickItem: MenuItem|undefined) => void)` | — | — | 86 | 87 | ## MenuItem 88 | 89 | | 属性 | 描述 | 类型 | 可选值 | 默认值 | 90 | | :----: | :----: | :----: | :----: | :----: | 91 | | label |
菜单项名称,可传入VNode
| `string` or `VNode` or `((label: string) => VNode)` | — | — | 92 | | icon | 菜单项图标,可传入VNode | `string` or `VNode` or `((icon: string) => VNode)` | — | — | 93 | | iconFontClass | 自定义图标字体类名 | `string` | — | `iconfont` | 94 | | preserveIconWidth | 是否应为没有图标的菜单项保留固定宽度的图标区域 | `boolean` | - | `true` | 95 | | svgIcon | 菜单项图标 svg,仅在 icon 为空时有效 | `string` | — | — | 96 | | svgProps | 当使用 svg 图标时,自定义 svg 标签属性 | `SVGAttributes` | — | — | 97 | | disabled | 是否禁用菜单项 | `boolean` | — | `false` | 98 | | hidden | 是否隐藏菜单项 | `boolean` | — | `false` | 99 | | checked | 是否选中菜单项 | `boolean` | — | `false` | 100 | | shortcut | 当前菜单项的快捷键指示,此快捷键只用于显示给用户看,快捷键的注册还是需要你自己处理 | `string` | — | `''` | 101 | | adjustSubMenuPosition | 默认情况下,子菜单将自动调整其位置,以防止溢出容器。如果允许菜单溢出容器,可以将其设置为false | `boolean` | — | 继承自 `MenuOptions.adjustPosition` | 102 | | clickableWhenHasChildren | 指定当本菜单下有子菜单时,点击当前菜单是否触发点击事件 | `boolean` | — | `false` | 103 | | clickClose | 点击当前菜单项是否自动关闭整个菜单 | `boolean` | — | `true` | 104 | | divided | 是否显示分割线。分割线显示逻辑
  • `true` or `'down'`: 分割线显示在菜单项下方。
  • `'up'`: 分割线显示在菜单项上方。
  • `'self'`: 把当前菜单项变成分割线。
  • `false`: 没有分割线。
| `boolean` or `'up'` or `'down'` or `'self'` | — | `false` | 105 | | customClass | 自定义子菜单class | `string` | — | — | 106 | | minWidth | 子菜单最小宽度(像素) | `number` or `string` | — | `100` | 107 | | maxWidth | 子菜单最大宽度(像素) | `number` or `string` | — | `600` | 108 | | maxHeight | 子菜单最大高度(像素) | `number` | — | - | 109 | | direction | 设置子菜单的弹出方向。如果 `adjustPosition` 为 `true` ,则菜单会根据可用空间自动调整弹出方向。 | `'br' or 'b' or 'bl' or 'tr' or 't' or 'tl' or 'l' or 'r'` | — | 继承自 `MenuOptions.direction` | 110 | | onClick | 菜单项点击事件 | `Function()` | — | — | 111 | | onSubMenuClose | 子菜单关闭事件回调 | `(() => void)` | — | — | 112 | | onSubMenuOpen | 子菜单打开事件回调 | `(() => void)` | — | — | 113 | | customRender | 菜单项整体自定义渲染回调 | `VNode` or `((item: MenuItemRenderData) => VNode)` | — | — | 114 | | children | 子菜单结构信息 | `MenuItem[]` | — | — | 115 | -------------------------------------------------------------------------------- /library/MenuBar.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 197 | 198 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress'; 2 | import { renderSandbox } from 'vitepress-plugin-sandpack'; 3 | import container from 'markdown-it-container'; 4 | 5 | export default defineConfig({ 6 | base: '/vue3-context-menu-docs/', 7 | lang: 'zh-CN', 8 | title: 'vue3-context-menu', 9 | description: '一个使用 Vue3 制作的简洁美观简单的右键菜单组件', 10 | locales: { 11 | root: { 12 | label: '中文', 13 | lang: 'zh', 14 | }, 15 | en: { 16 | label: 'English', 17 | lang: 'en', 18 | description: 'A simple, beautiful and simple context menu component made by Vue3', 19 | themeConfig: { 20 | socialLinks: [ 21 | { icon: 'github', link: 'https://github.com/imengyu/vue3-context-menu' }, 22 | ], 23 | footer: { 24 | message: 'Released under the MIT License.', 25 | copyright: 'Copyright © 2022 imengyu.top' 26 | }, 27 | nav: [ 28 | { text: 'Guide', link: '/en/guide/install' }, 29 | { text: 'API Reference', link: '/en/api/ContextMenu' }, 30 | { text: 'Changelog', link: '/en/change/index' }, 31 | ], 32 | sidebar: { 33 | '/en/guide/': [ 34 | { 35 | text: 'Start', 36 | items: [ 37 | { text: 'Install', link: '/en/guide/install' }, 38 | { text: 'Useage', link: '/en/guide/useage' }, 39 | { text: 'Menu icon', link: '/en/guide/icon' }, 40 | ] 41 | }, 42 | { 43 | text: 'Theme and Customize', 44 | items: [ 45 | { text: 'Theme', link: '/en/guide/theme' }, 46 | { text: 'Customize Styles and Rendering', link: '/en/guide/customize' }, 47 | { text: 'Customize the menu mount container', link: '/en/guide/custom-container' }, 48 | ] 49 | }, 50 | ], 51 | '/en/api/': [ 52 | { 53 | text: 'API Reference', 54 | items: [ 55 | { text: 'Function mode', link: '/en/api/ContextMenuInstance' }, 56 | { 57 | text: 'Component mode', 58 | items: [ 59 | { text: 'ContextMenu', link: '/en/api/ContextMenu' }, 60 | { text: 'ContextMenuGroup', link: '/en/api/ContextMenuGroup' }, 61 | { text: 'ContextMenuItem', link: '/en/api/ContextMenuItem' }, 62 | { text: 'ContextMenuSeparator', link: '/en/api/ContextMenuSeparator' }, 63 | { text: 'MenuBar', link: '/en/api/MenuBar' }, 64 | ] 65 | }, 66 | ] 67 | }, 68 | ] 69 | } 70 | } 71 | }, 72 | }, 73 | themeConfig: { 74 | socialLinks: [ 75 | { 76 | icon: { 77 | svg: '' 78 | }, 79 | link: 'https://gitee.com/imengyu/vue3-context-menu' 80 | }, 81 | { icon: 'github', link: 'https://github.com/imengyu/vue3-context-menu' }, 82 | ], 83 | footer: { 84 | message: 'Released under the MIT License.', 85 | copyright: 'Copyright © 2022 imengyu.top' 86 | }, 87 | nav: [ 88 | { text: '教程', link: '/guide/install' }, 89 | { text: 'API 参考', link: '/api/ContextMenu' }, 90 | { text: '更新日志', link: '/change/index' }, 91 | ], 92 | sidebar: { 93 | '/guide/': [ 94 | { 95 | text: '起步', 96 | items: [ 97 | { text: '开始之前', link: '/guide/start' }, 98 | { text: '安装', link: '/guide/install' }, 99 | { text: '如何使用', link: '/guide/useage' }, 100 | { text: '菜单图标', link: '/guide/icon' }, 101 | ] 102 | }, 103 | { 104 | text: '美化与自定义', 105 | items: [ 106 | { text: '主题', link: '/guide/theme' }, 107 | { text: '自定义样式和渲染', link: '/guide/customize' }, 108 | { text: '自定义菜单挂载容器', link: '/guide/custom-container' }, 109 | ] 110 | }, 111 | ], 112 | '/api/': [ 113 | { 114 | text: 'API 参考', 115 | items: [ 116 | { text: '函数模式', link: '/api/ContextMenuInstance' }, 117 | { 118 | text: '组件模式', 119 | items: [ 120 | { text: 'ContextMenu', link: '/api/ContextMenu' }, 121 | { text: 'ContextMenuGroup', link: '/api/ContextMenuGroup' }, 122 | { text: 'ContextMenuItem', link: '/api/ContextMenuItem' }, 123 | { text: 'ContextMenuSeparator', link: '/api/ContextMenuSeparator' }, 124 | { text: 'MenuBar', link: '/api/MenuBar' }, 125 | ] 126 | }, 127 | ] 128 | }, 129 | ] 130 | }, 131 | }, 132 | markdown: { 133 | config(md) { 134 | md 135 | // the second parameter is html tag name 136 | .use(container, 'sandbox', { 137 | render (tokens, idx) { 138 | return renderSandbox(tokens, idx, 'sandbox'); 139 | }, 140 | }) 141 | .use(container, 'my-sandbox', { 142 | render (tokens, idx) { 143 | return renderSandbox(tokens, idx, 'my-sandbox'); 144 | }, 145 | }); 146 | }, 147 | }, 148 | }); -------------------------------------------------------------------------------- /docs/guide/useage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何使用 3 | order: 2 4 | --- 5 | 6 | # 如何使用 7 | 8 | ## 显示菜单 9 | 10 | 显示菜单有两种方式: 11 | 12 | 第一种是函数模式,可以使用 `this.$contextmenu` 或者 `showContextMenu` 全局函数,通过菜单数据显示一个右键菜单: 13 | 14 | ```js 15 | import ContextMenu from '@imengyu/vue3-context-menu' 16 | 17 | onContextMenu(e : MouseEvent) { 18 | //prevent the browser's default menu 19 | e.preventDefault(); 20 | //show your menu 21 | this.$contextmenu({ 22 | x: e.x, 23 | y: e.y, 24 | items: [ 25 | { 26 | label: "A menu item", 27 | onClick: () => { 28 | alert("You click a menu item"); 29 | } 30 | }, 31 | { 32 | label: "A submenu", 33 | children: [ 34 | { label: "Item1" }, 35 | { label: "Item2" }, 36 | { label: "Item3" }, 37 | ] 38 | }, 39 | ] 40 | }); 41 | 42 | //这个函数与 this.$contextmenu 一致 43 | ContextMenu.showContextMenu({ ... }); 44 | } 45 | ``` 46 | 47 | > 注:`this.$contextmenu` 只能在模板或者选项式函数中使用。 48 | 49 | 第二种是组件模式,可以使用组件显示菜单: 50 | 51 | ```html 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ```js 69 | data() { 70 | return { 71 | show: false, 72 | //For component 73 | optionsComponent: { 74 | zIndex: 3, 75 | minWidth: 230, 76 | x: 500, 77 | y: 200 78 | }, 79 | } 80 | }, 81 | methods: { 82 | onButtonClick(e : MouseEvent) { 83 | //显示组件菜单 84 | this.show = true; 85 | this.options.x = e.x; 86 | this.options.y = e.y; 87 | }, 88 | } 89 | ``` 90 | 91 | ## 动态控制菜单 92 | 93 | ### 函数模式 94 | 95 | 你只需要将菜单数据声明为响应式数据,即可动态修改菜单: 96 | 97 | ```js 98 | const menuData = reactive({ 99 | items: [ 100 | { 101 | label: 'Simple item', 102 | onClick: () => alert('Click Simple item'), 103 | }, 104 | ] 105 | }); 106 | 107 | //也可以在函数模式下动态控制菜单 108 | ContextMenu.showContextMenu(menuData); 109 | 110 | //可以在显示菜单后随时更改属性: 111 | menuData.items[0].label = 'My label CHANGED!'; //更改文本 112 | menuData.items[0].hidden = true; //更改是否隐藏 113 | ``` 114 | 115 | ### 组件模式 116 | 117 | 组件模式动态控制就更简单了,你可以使用 v-if 指令动态控制显示,也可以直接绑定菜单参数至变量上,更改就和普通组件一样。 118 | 119 | 下面的示例展示了组件模式下动态显示/隐藏菜单,动态修改菜单项文字: 120 | 121 | ```vue 122 | 133 | 134 | 165 | ``` 166 | 167 | ## 菜单栏 168 | 169 | 本库附带了一个菜单栏组件,因为右键菜单的功能和菜单栏非常相似,所以一并做到库里了,你也可以使用这个组件实现桌面程序的主菜单栏功能。 170 | 171 | 在线示例可以参考[这里](https://imengyu.top/pages/vue3-context-menu-demo/#/MenuBar)。 172 | 173 | ```vue 174 | 177 | 178 | 237 | ``` 238 | -------------------------------------------------------------------------------- /examples/views/ComponentCustomize.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 150 | 151 | -------------------------------------------------------------------------------- /examples/views/BasicCustomize.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /docs/en/guide/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Install 3 | order: 0 4 | --- 5 | 6 | # Install 7 | 8 | ```shell 9 | npm install -save @imengyu/vue3-context-menu 10 | ``` 11 | 12 | vue3-context-menu only supports Vue3. If you are using Vue2, you can try the following library. 13 | 14 | * [xunleif2e/vue-context-menu](https://github.com/xunleif2e/vue-context-menu) 15 | * [GitHub-Laziji/menujs](https://github.com/GitHub-Laziji/menujs) (This library is inspired by it) 16 | 17 | ## Global import 18 | 19 | It is recommended that you import components globally for the most convenient use. 20 | 21 | ```js 22 | //main.js 23 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 24 | import ContextMenu from '@imengyu/vue3-context-menu' 25 | 26 | createApp(App).use(ContextMenu).mount('#app') 27 | ``` 28 | 29 | ## Local Registration 30 | 31 | First import the style file: 32 | 33 | ```js 34 | //main.js 35 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 36 | ``` 37 | 38 | Then import components where you need to use them: 39 | 40 | ```vue 41 | 62 | 63 | 102 | ``` 103 | 104 | ## Getting Started 105 | 106 | [Getting Started](./useage.md) 107 | 108 | ## Other questions 109 | 110 | ### How to use menu components in global build of Vue single page 111 | 112 | In Vue files that use single page applications without using vite, webpack or other packaging tools, you need to change the import. 113 | 114 | You need to import these two files in html. The files can be found in the lib directory of the npm package. 115 | 116 | * vue3-context-menu.umd.js 117 | * vue3-context-menu.css 118 | 119 | Register menu components: 120 | 121 | ```js 122 | createApp(...) 123 | .use(window['vue3-context-menu'].default) 124 | ``` 125 | 126 | After registration, it can be used as a normal project. The following is a complete case: 127 | 128 | ```html 129 | 130 | 131 | 132 | vue3-context-menu test 133 | 134 | 135 | 136 | 137 | 138 |
139 |
{{ message }}
140 |
141 | 211 | 212 | 213 | ``` 214 | 215 | ### Modify and register global '$contextmenu' function and component names 216 | 217 | If you find that the default name of the library menu conflicts with the name of the library component you have used during global import, you can manually register and modify the default name: 218 | 219 | ```js 220 | //main.js 221 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'; 222 | import ContextMenuGlobal, { ContextMenu, ContextMenuGroup, ContextMenuSeparator, ContextMenuItem } from '@imengyu/vue3-context-menu'; 223 | 224 | const app = createApp(App); 225 | 226 | //Do not use the default registration function 227 | //app.use(ContextMenu) 228 | 229 | //Customize the name of a component 230 | app.component('my-context-menu', ContextMenu) 231 | app.component('my-context-menu-group', ContextMenuGroup) 232 | app.component('my-context-menu-separator', ContextMenuSeparator) 233 | app.component('my-context-menu-item', ContextMenuItem) 234 | 235 | //Modify global '$contextmenu' function name 236 | app.config.globalProperties.$mycontextmenu = ContextMenuGlobal.showContextMenu; 237 | 238 | app.mount('#app') 239 | ``` 240 | -------------------------------------------------------------------------------- /examples/views/BasicComponent.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 192 | 193 | -------------------------------------------------------------------------------- /docs/en/guide/useage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | order: 1 4 | --- 5 | 6 | # Usage 7 | 8 | ## Import 9 | 10 | ```js 11 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 12 | import ContextMenu from '@imengyu/vue3-context-menu' 13 | 14 | createApp(App).use(ContextMenu) 15 | ``` 16 | 17 | ## Show menu 18 | 19 | There are two ways to display menus: 20 | 21 | The first is the function mode. You can use `this.$contextmenu` or `showContextMenu` global function displays a menu with menu data: 22 | 23 | ::: my-sandbox {template=vue3-ts} 24 | 25 | ```ts /src/main.ts 26 | import { createApp } from 'vue' 27 | import App from './App.vue' 28 | import ContextMenu from '@imengyu/vue3-context-menu' 29 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 30 | 31 | createApp(App) 32 | .use(ContextMenu) 33 | .mount('#app') 34 | ``` 35 | 36 | ```vue /src/App.vue [active] 37 | 66 | 67 | 70 | ``` 71 | 72 | ::: 73 | 74 | > Note: `this.$contextmenu` can only be used in templates or optional api. 75 | 76 | The second is the component mode. You can use the component and template to display the menu: 77 | 78 | ::: my-sandbox {template=vue3-ts} 79 | 80 | ```ts /src/main.ts 81 | import { createApp } from 'vue' 82 | import App from './App.vue' 83 | import ContextMenu from '@imengyu/vue3-context-menu' 84 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 85 | 86 | createApp(App) 87 | .use(ContextMenu) 88 | .mount('#app') 89 | ``` 90 | 91 | ```vue /src/App.vue [active] 92 | 109 | 110 | 134 | ``` 135 | 136 | ::: 137 | 138 | ## Dynamic change menu 139 | 140 | You only need to declare the menu data as responsive data, so that you can dynamically modify the menu: 141 | 142 | ### Function mode 143 | 144 | ```js 145 | const menuData = reactive({ 146 | items: [ 147 | { 148 | label: 'Simple item', 149 | onClick: () => alert('Click Simple item'), 150 | }, 151 | ] 152 | }); 153 | 154 | //Use in function mode 155 | ContextMenu.showContextMenu(menuData); 156 | 157 | //You can change properties at any time after the menu is displayed: 158 | menuData.items[0].label = 'My label CHANGED!'; //Change label 159 | menuData.items[0].hidden = true; //Change hidden 160 | ``` 161 | 162 | ### Component mode 163 | 164 | The dynamic control of component mode is simpler. You can use the `v-if` directive to dynamically control the display, or you can directly bind menu parameters to variables. The changes are just like normal components. 165 | 166 | The following example shows how to dynamically display/hide menus and dynamically modify menu item text in component mode: 167 | 168 | ```vue 169 | 180 | 181 | 212 | ``` 213 | 214 | ## Menu bar 215 | 216 | This library comes with a menu bar component, which is very similar to the function of the right-click menu and the menu bar. Therefore, it is integrated into the library, and you can also use this component to implement the main menu bar function of desktop programs. 217 | 218 | ::: my-sandbox {template=vue3-ts} 219 | 220 | ```ts /src/main.ts 221 | import { createApp } from 'vue' 222 | import App from './App.vue' 223 | import ContextMenu from '@imengyu/vue3-context-menu' 224 | import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' 225 | 226 | createApp(App) 227 | .use(ContextMenu) 228 | .mount('#app') 229 | ``` 230 | 231 | ```vue /src/App.vue [active] 232 | 233 | 236 | 237 | 296 | ``` 297 | 298 | ::: 299 | -------------------------------------------------------------------------------- /docs/en/api/ContextMenuInstance.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Function mode 3 | 4 | --- 5 | 6 | # Function mode 7 | 8 | ## ContextMenu 9 | 10 | ```ts 11 | import ContextMenu from '@imengyu/vue3-context-menu' 12 | ``` 13 | 14 | Global Function 15 | 16 | ### `ContextMenu.showContextMenu(options: MenuOptions, customSlots?: Record)` 17 | 18 | Show context menu. 19 | 20 | | Param | Description | 21 | | :----: | :----: | 22 | | options | The options of menu. | 23 | | customSlots | These slots to allow you to customize the style of the current menu, the names of these slots are the same as those in the [component mode](#ContextMenu). For customization methods, please refer in [here](../guide/customize.md). | 24 | 25 | Return 26 | 27 | | Type | Description | 28 | | :----: | :----: | 29 | | ContextMenuInstance | The instance of the current menu, can calling `closeMenu` to close this menu, calling `isClosed` to check if the menu is currently closed. | 30 | 31 | ### `ContextMenu.closeContextMenu()` 32 | 33 | Manually close the currently open context menu. 34 | 35 | ### `this.$contextmenu` 36 | 37 | Same as `ContextMenu.showContextMenu` but this function is registered to vue global property. 38 | 39 | ### `ContextMenu.transformMenuPosition` 40 | 41 | If your `body` element is in a scaled state (e.g. `transform: scale(0.5)`), this may lead to the wrong position of the menu display. 42 | You can use this function to convert the correct menu display position: 43 | 44 | ```ts 45 | import ContextMenu from '@imengyu/vue3-context-menu' 46 | 47 | function onContextMenu(e: MouseEvent) { 48 | const scaledPosition = ContextMenu.transformMenuPosition(e.target as HTMLElement, e.offsetX, e.offsetY); 49 | //Full code of menuData is in `/examples/views/InScaledBody.vue` 50 | menuData.x = scaledPosition.x; 51 | menuData.y = scaledPosition.y; 52 | //show menu 53 | ContextMenu.showContextMenu(menuData); 54 | } 55 | ``` 56 | 57 | ## MenuOptions 58 | 59 | | Property | Description | Type | Optional value | Default | 60 | | :----: | :----: | :----: | :----: | :----: | 61 | | items |
The items for this menu.
| `MenuItem[]` | — | — | 62 | | x | Menu display x position. | `number` | — | `0` | 63 | | y | Menu display y position. | `number` | — | `0` | 64 | | xOffset | X-coordinate offset of submenu and parent menu. | `number` | — | `0` | 65 | | yOffset | Y-coordinate offset of submenu and parent menu. | `number` | — | `0` | 66 | | iconFontClass | Custom icon library font class name. (global). Only for css font icon, If you use the svg icon, you do not need to use this. | `string` | — | `iconfont` | 67 | | zIndex | The `z-index` of this menu | `number` | — | `2` | 68 | | iconFontClass | Custom icon library font class name. | `string` | — | `iconfont` | 69 | | preserveIconWidth | Should a fixed-width icon area be reserved for menu items without icon. | `boolean` | - | `true` | 70 | | keyboardControl | Set whether the user can use keyboard keys to control the current menu. | `boolean` | — | `true` | 71 | | closeWhenScroll | Set whether to close the menu when the user scrolls the mouse. | `boolean` | - | `true` | 72 | | mouseScroll | Set whether users can use the mouse scroll wheel to scroll through long menus in the menu area. | `boolean` | — | `false` | 73 | | theme | The [theme](../guide/theme.md) of this menu. | `string` | `'default' 'dark' 'flat' 'win10' 'mac'` | `default` | 74 | | minWidth | Submenu minimum width (in pixels). | `number` or `string` | — | `100` | 75 | | maxWidth | Submenu maximum width (in pixels). | `number` or `string` | — | `600` | 76 | | maxHeight | Submenu maximum height (in pixels). | `number` | — | - | 77 | | adjustPadding | Padding for submenu position adjust. | `{ x: number, y: number }` or `number` | — | `{ x:0, y: 10 }` | 78 | | adjustPosition | By default, the menu will automatically adjust its position to prevent it overflow the container. If you allow menu overflow containers, you can set this to false. | `boolean` | — | `true` | 79 | | direction | Set the mian menu pop-up direction relative to coordinates. Default is `'br'`, if `adjustPosition` is true then the menu will determine the pop-up direction based on its distance from the screen edge. | `'br' or 'b' or 'bl' or 'tr' or 't' or 'tl' or 'l' or 'r'` | — | `'br'` | 80 | | menuTransitionProps | The Vue Transition props used when menu show or hide. | `TransitionProps` | — | 81 | | ignoreClickClassName | If your element in menu item has this className, click it will ignore event. | `string` | — | 82 | | clickCloseOnOutside | Set should close menu when the user click on other places. | `boolean` | — | `true` | 83 | | clickCloseClassName | If your element in menu item has this className, click it will ignore event and close hole menu. | `string` | — | 84 | | updownButtonSpaceholder | Determine whether the up/down buttons in the menu item require space holder. The purpose of this variable is because some menu themes add blank padding above and below the menu, which are just enough to place up/down buttons. If there is no blank padding in your custom menu theme, you can set this field to provide blank space for up/down buttons to prevent obscuring menu items. | `boolean` | — | `false` | 85 | | getContainer | Return the mounted node for MenuRoot. [Guide](../guide/custom-container.md) | `HTMLElement` or `(() => HTMLElement)` | — | — | 86 | | onClose | This event emit when this menu is closing. (Usually used in function mode). Param lastClickItem The last clicked menu item, if user does not click any item, it is `undefined`. This param only valid in function mode. | `((lastClickItem: MenuItem|undefined) => void)` | — | — | 87 | 88 | ## MenuItem 89 | 90 | | Property | Description | Type | Optional value | Default | 91 | | :----: | :----: | :----: | :----: | :----: | 92 | | label |
The label of menu.
| `string` or `VNode` or `((label: string) => VNode)` | — | — | 93 | | icon | The icon for menu item. | `string` or `VNode` or `((icon: string) => VNode)` | — | — | 94 | | iconFontClass | Custom icon library font class name. | `string` | — | `iconfont` | 95 | | preserveIconWidth | Should a fixed-width icon area be reserved for menu items without icon. | `boolean` | - | `true` | 96 | | svgIcon | Display icons use svg symbol (``) , only valid when icon attribute is empty. | `string` | — | — | 97 | | svgProps | The user-defined attribute of the svg tag, which is valid when using `svgIcon`. | `SVGAttributes` | — | — | 98 | | disabled | Disable menu item? | `boolean` | — | `false` | 99 | | hidden | Hide menu item? | `boolean` | — | `false` | 100 | | checked | Is this menu item checked? | `boolean` | — | `false` | 101 | | shortcut | Shortcut key text display on the right. The shortcut keys here are only for display. You need to handle the key events by yourself. | `string` | — | `''` | 102 | | adjustSubMenuPosition | By default, the submenu will automatically adjust its position to prevent it overflow the container. If you allow menu overflow containers, you can set this to false. | `boolean` | — | inherit from `MenuOptions.adjustPosition` | 103 | | clickableWhenHasChildren | When there are subitems in this item, is it allowed to trigger its own click event? | `boolean` | — | `false` | 104 | | clickClose | Should close menu when Click this menu item ? | `boolean` | — | `true` | 105 | | divided | Should show Separator?
  • `true` or `'down'`: Separator is show below menu.
  • `'up'`: Separator is show above menu.
  • `'self'`: Mark this item is a Separator.
  • `false`: No Separator.
| `boolean` or `'up'` or `'down'` or `'self'` | — | `false` |`boolean` | — | `false` | 106 | | customClass | Custom submenu class. | `string` | — | — | 107 | | minWidth | Submenu minimum width (in pixels). | `number` or `string` | — | `100` | 108 | | maxWidth | Submenu maximum width (in pixels). | `number` or `string` | — | `600` | 109 | | maxHeight | Submenu maximum height (in pixels). | `number` | — | - | 110 | | direction | Set the submenu pop-up direction relative to coordinates. Default is inherted from `MenuOptions.direction`, if `adjustSubMenuPosition` is true then the submenu will determine the pop-up direction based on its distance from the screen edge. | `'br' or 'b' or 'bl' or 'tr' or 't' or 'tl' or 'l' or 'r'` | — | inherit from `MenuOptions.direction` | 111 | | onClick | Menu item click event handler. | `Function()` | — | — | 112 | | onSubMenuClose | This event emit when submenu of this item is closing. | `(() => void)` | — | — | 113 | | onSubMenuOpen | This event emit when submenu of this item is showing. | `(() => void)` | — | — | 114 | | customRender | A custom render callback that allows you to customize the rendering of the current item. | `VNode` or `((item: MenuItemRenderData) => VNode)` | — | — | 115 | | children | Submenu items. | `MenuItem[]` | — | — | 116 | -------------------------------------------------------------------------------- /library/ContextSubMenuWrapper.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 255 | 256 | 270 | -------------------------------------------------------------------------------- /examples/views/ChangeContainer.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | -------------------------------------------------------------------------------- /docs/change/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志 3 | nav: 4 | title: 更新日志 5 | order: 5 6 | --- 7 | 8 | ## v1.5.2 - 2025/08/04 9 | 10 | * [Fix] 修复键盘控制失效问题 ([#124](https://github.com/imengyu/vue3-context-menu/issues/124))。 11 | * [Fix] 修复键盘控制在过长滚动区域中没有正确滚动显示问题。 12 | 13 | ## v1.5.1 - 2025/06/19 14 | 15 | * [Fix] 更新 vue-scroll-rect 版本,修复系统滚动条在 Firefox 错误显示问题。 ([#121](https://github.com/imengyu/vue3-context-menu/issues/121)) 16 | * [Fix] 修复菜单 Transition 无效问题。 ([#122](https://github.com/imengyu/vue3-context-menu/issues/122)) 17 | 18 | ## v1.5.0 - 2025/05/19 19 | 20 | * [Add] 新增 maxHeight 用于控制子菜单最高高度。 ([#120](https://github.com/imengyu/vue3-context-menu/issues/120)) 21 | * [Change] 调整子菜单弹出自动位置;子菜单滚动容器更新,现在支持在条目显示变更后自动调整容器大小。 22 | * [Fix] 阻止菜单滚动事件触发至上层元素。 ([#116](https://github.com/imengyu/vue3-context-menu/issues/116)) 23 | 24 | ## v1.4.8 - 2025/04/16 25 | 26 | * [Added] 参考 VSCode 的菜单增加菜单的溢出滚动与滚动条功能。 ([#116](https://github.com/imengyu/vue3-context-menu/issues/116)) 27 | * [Removed] 移除原有菜单上下滚动按钮功能。 28 | * [Fix] 修复子菜单溢出间距调整不正确的问题。 ([#111](https://github.com/imengyu/vue3-context-menu/issues/111)) 29 | * [Change] 子菜单现在将在顶层容器中显示而不是之前的逐级嵌套。 30 | * [Docs] 更改分隔符的拼写错误。 31 | 32 | ## v1.4.6 - 2025/03/28 33 | 34 | * [Fix] 修复 `ContextMenuItem` `slot` 插槽在自定义时未正常显示的问题。 ([#113](https://github.com/imengyu/vue3-context-menu/issues/113)) 35 | * [Fix] 修复键盘事件不正确的穿透至下方组件问题。 ([#113](https://github.com/imengyu/vue3-context-menu/issues/113)) 36 | 37 | ## v1.4.5 - 2025/03/13 38 | 39 | * [Fix] 修复 `ContextMenuGroup` 组件没有显示对应插槽的问题。 (Thanks @kovalewvladimir [#112](https://github.com/imengyu/vue3-context-menu/issues/112)) 40 | 41 | ## v1.4.4 - 2024/12/06 42 | 43 | * [Change] 在 package.json 中添加 es 定义。 ([#106](https://github.com/imengyu/vue3-context-menu/issues/106)) 44 | * [Fix] 修复 MenuBar 组件没有发出 `onSubMenuOpen` 事件的问题。 45 | 46 | ## v1.4.3 - 2024/10/10 47 | 48 | * [Change] 更新挂载到 Vue 全局实例上的定义 (Thanks @linspw [#103](https://github.com/imengyu/vue3-context-menu/pull/103)) 49 | * [Fix] 为菜单项的 checked 和 hidden 添加了 ComputedRef 定义,防止在使用 Computed 时提示错误。 50 | 51 | ## v1.4.2 - 2024/07/31 52 | 53 | * [Added] 添加了 `ContextMenuInstance.getMenuRef` 用于在函数模式获取根子菜单实例。 54 | * [Added] 为 `ContextSubMenu` 暴露了 `ContextSubMenuInstance` 的多个方法,以实现对菜单项目更复杂的控制,包括获取菜单项,获取高度,位置,滚动,设置滚动数值,位置等等,参见 [ContextSubMenuInstance](../api/ContextMenuGroup.md#contextmenugroup)。 55 | * [Added] 为函数模式的菜单项目事件 `onSubMenuOpen`、`onSubMenuClose` 增加了 [`MenuItemContext`](../api/ContextMenuItem.md#menuitemcontext) 条目菜单实例参数。 56 | * [Fix] 暴露ContextMenu的高度和宽度和Ref对象 (Thanks @ZhiZunJava [#97](https://github.com/imengyu/vue3-context-menu/pull/97)) 57 | * [Fix] 修复当body设置了zoom时,组件的位置发生异常的问题 (Thanks @lizhen789 [#94](https://github.com/imengyu/vue3-context-menu/pull/94)) 58 | 59 | ## v1.4.1 - 2024/04/22 60 | 61 | * [Fix] 修复组件模式下设置 show = false 关闭菜单后再打开需要打开两次问题 ([#88](https://github.com/imengyu/vue3-context-menu/issues/88)) 62 | * [Added] 添加了 `MenuOptions.onClickOnOutside` 用于在 `clickCloseOnOutside = false` 时自定义处理点击菜单外部逻辑 ([#87](https://github.com/imengyu/vue3-context-menu/issues/87)) 63 | 64 | ## v1.4.0 - 2024/04/10 65 | 66 | * [Fix] 组件模式下 `options.theme` 支持动态修改 ([#82](https://github.com/imengyu/vue3-context-menu/issues/82)) 67 | 68 | ## v1.3.9 - 2024/03/16 69 | 70 | * [Fix] 修改 `padding-right` 为 `padding-inline-end`,以更好地支持RTL模式 (Thanks @hooray, [#83](https://github.com/imengyu/vue3-context-menu/pull/83)) 71 | 72 | ## v1.3.8 - 2024/02/04 73 | 74 | * [Added] 添加 `MenuBarOptions.barPopDirection` 属性用于控制菜单栏一级子菜单弹出方向。 75 | * [Fix] 修复 VNodeRender 的类型告警。 76 | 77 | ## v1.3.7 - 2024/01/23 78 | 79 | * [Added] 添加 `MenuOptions.menuTransitionProps` 属性用于实现菜单显示/隐藏的过度动效。 ([#80](https://github.com/imengyu/vue3-context-menu/issues/80)) 80 | 81 | ## v1.3.6 - 2024/01/17 82 | 83 | * [Added] 添加 `isClosed` 实例方法用于检查当前菜单实例是否已经关闭。 ([#77](https://github.com/imengyu/vue3-context-menu/issues/77)) 84 | * [Added] 添加 `clickCloseOnOutside` 选项允许配置用户点击外部是否关闭菜单。 ([#76](https://github.com/imengyu/vue3-context-menu/issues/76)) 85 | * [Fix] 修复 VNodeRenderer 发出的类型警告。 86 | * [Docs] 添加 `--mx-menu-open-hover-background` 和 `--mx-menu-open-hover-text` 的说明。(Thanks @croatialu) 87 | * [Docs] 修改拼写 (Thanks @cheqianxiao) 88 | 89 | ## v1.3.4 - 2023/12/13 90 | 91 | * [Added] 添加了 `MenuOptions.mouseScroll` 来定义是否可以用滚轮来滚动长菜单。 92 | * [Added] 允许在上下按钮上使用滚轮来滚动长菜单。Thanks @jonathanzuniga ([#56](https://github.com/imengyu/vue3-context-menu/issues/56)). 93 | * [Fix] 修复长菜单条目显示不完全问题。 94 | * [Fix] 修复了自定义挂载容器添加zIndex的问题。 95 | * [Fix] 修复了菜单溢出容器问题。 96 | 97 | ## v1.3.1 - 2023/07/23 98 | 99 | * [Added] 在函数模式下的 onClose 回调中添加了触发关闭的菜单项参数,用于获取用户点击了哪个菜单项关闭了菜单。 ([#55](https://github.com/imengyu/vue3-context-menu/issues/55)). 100 | * [Fix] 修复 MenuBar 弹出菜单位置不正确问题。 ([#54](https://github.com/imengyu/vue3-context-menu/issues/54)). 101 | 102 | ## v1.3.1 - 2023/07/12 103 | 104 | * [Added] `MenuItem.divider` 添加了 `'up'|'down'|'self'` 选项用于控制分割线方向。 105 | 106 | ## v1.3.0 - 2023/06/20 107 | 108 | * [Added] 添加了 `MenuOptions.direction` 和 `MenuItem.direction` 允许控制菜单弹出方向。 109 | * [Added] 添加了 `clickCloseClassName` 和 `clickCloseClassName` 用于忽略某些自定义子元素的点击事件 ([#48](https://github.com/imengyu/vue3-context-menu/issues/48)) 110 | * [Added] 在渲染插槽参数中添加了 `close` 回调用于关闭整个菜单。 111 | * [Change] `MenuOptions.adjustPadding` 默认值修改为 `{ x:0, y: 10 }`. 112 | * [Change] 菜单溢出调整已经更改,现在菜单不会溢出屏幕 113 | * [Fix] 修复菜单限制最大宽度后文字溢出显示问题。 114 | * [Fix] 修复菜单在过窄的页面中溢出问题 ([#49](https://github.com/imengyu/vue3-context-menu/issues/49)) 115 | * [Fix] 修复 closeWhenScroll 在移动端上不工作的问题 ([#47](https://github.com/imengyu/vue3-context-menu/issues/47)) 116 | * [Fix] 修复数字类型的minWidth和maxWidth无效的问题 ([#46](https://github.com/imengyu/vue3-context-menu/issues/46)) 117 | 118 | ## v1.2.10 - 2023/05/27 119 | 120 | * [Fix] 修复onClick回调中的 this 指向错误 121 | * [Fix] 修复 win10 主题丢失问题 122 | 123 | ## v1.2.7 - 2023/05/27 124 | 125 | * [Added] 新增了 MenuBar 菜单栏组件。 126 | * [Fix] 使用Teleport渲染菜单,修复插槽相关数据不正常问题 ([#45](https://github.com/imengyu/vue3-context-menu/issues/45)) 127 | 128 | ## v1.2.6 - 2023/4/29 129 | 130 | * [Added] 为函数模式添加了 `onClose`(菜单关闭)、`onSubMenuOpen`(子菜单打开)、`onSubMenuClose`(子菜单关闭) 事件回调。([#41](https://github.com/imengyu/vue3-context-menu/issues/41)) 131 | * [Added] 新增了两个css变量: `--mx-menu-open-hover-backgroud`(菜单项打开并且鼠标悬浮时背景颜色)、`--mx-menu-open-hover-text`(菜单项打开并且鼠标悬浮时文字颜色) 132 | * [Changed] 修改了 mac 和 win10.light 两个主题的显示效果。 133 | 134 | ## v1.2.5 - 2023/4/9 135 | 136 | * [Fix] 修复了 1.2.4 丢失 .d.ts 文件的问题 ([#38](https://github.com/imengyu/vue3-context-menu/issues/38)) 137 | 138 | ## v1.2.4 - 2023/4/8 139 | 140 | * [Fix] 添加了 `MenuOptions.adjustPosition` 属性,以允许控制最外层菜单是否在溢出容器时自动调整位置 ([#34](https://github.com/imengyu/vue3-context-menu/issues/34)) 141 | * [Fix] 修复了在可滚动的长页面下菜单定位不正确问题 (Thasks @jamespltan [#35](https://github.com/imengyu/vue3-context-menu/pull/35)) ([#34](https://github.com/imengyu/vue3-context-menu/issues/34)) 142 | * [Fix] 修改菜单项的 icon、label属性类型验证 (Thasks @kjellmf [#36](https://github.com/imengyu/vue3-context-menu/pull/36)) 143 | 144 | ## v1.2.3 - 2023/2/15 145 | 146 | * [Fix] 修改菜单组件导出与定义不一致的问题(1.2.0-1.2.2) ([#30](https://github.com/imengyu/vue3-context-menu/issues/30)) 147 | 148 | ## v1.2.2 - 2023/1/29 149 | 150 | * [Fix] 修改菜单错误拦截键盘事件的问题 ([#27](https://github.com/imengyu/vue3-context-menu/issues/27)) 151 | 152 | ## v1.2.0 - 2023/1/28 153 | 154 | * [Added] 添加了 `MenuItem.shortcut` 用于显示当前菜单项的快捷键提示。 155 | * [Added] 添加了 `MenuItem.preserveIconWidth` 用于设置是否应为没有图标的菜单项保留固定宽度的图标区域。 156 | * [Added] 添加了 `MenuItem.checked` 用于指定当前菜单项是否被打勾。 157 | * [Added] 添加了新的三种[主题](../guide/theme.md),抽离了CSS,提供了一些[CSS变量](../guide/customize.md#css变量),允许你方便的修改颜色。支持自定义主题。 158 | * [Fix] 修改全局菜单返回实例调用 closeMenu 无法关闭菜单的问题 ([#26](https://github.com/imengyu/vue3-context-menu/issues/26)) 159 | * [Changed] 菜单的样式重写,拆分至单独的 SCSS 中。 160 | * [Changed] ⚠ 菜单的右箭头现在使用单独的 SVG 图片(之前是嵌入在css中的base64 data),如果你使用 `.mx-right-arrow` 样式自定义右箭头图片,需要特殊处理一下,隐藏svg。 161 | 162 | ## v1.1.9 - 2023/1/6 163 | 164 | * 修改全局菜单插槽未正确传递丢失参数的问题 ([#25](https://github.com/imengyu/vue3-context-menu/issues/25)) 165 | 166 | ## v1.1.8 - 2023/1/5 167 | 168 | * 修改函数模式下 divide=true 的条目 设置 hidden 为 true 后分割线没有隐藏的问题 ([#24](https://github.com/imengyu/vue3-context-menu/issues/24)) 169 | * 修复了重复创建默认容器的问题 ([#24](https://github.com/imengyu/vue3-context-menu/issues/24)) 170 | * 文档修改。 171 | 172 | ## v1.1.7 - 2022/12/20 173 | 174 | * [Added] 添加了 `MenuOptions.getContainer` 允许你自定义菜单所挂载的容器。 ([#17](https://github.com/imengyu/vue3-context-menu/issues/17), [#19](https://github.com/imengyu/vue3-context-menu/issues/19)) 175 | * [Added] 添加了 `MenuOptions.adjustPadding` 可设置菜单自动位置调整的边距。 176 | * [Added] 添加了单独的文档站点,整理文档以让大家阅读更清晰。 177 | * 修改了菜单自动调整位置不正确的问题. 178 | 179 | ## v1.1.6 - 2022/12/20 180 | 181 | * [Added] 添加了 `MenuItem.hidden` 属性可动态控制函数模式下菜单项的显示和隐藏。 ([#20](https://github.com/imengyu/vue3-context-menu/issues/20)) 182 | * [Added] 添加了 `ContextMenu.transformMenuPosition` 函数,以解决缩放 body 时菜单位置不正确的问题。 ([#17](https://github.com/imengyu/vue3-context-menu/issues/17), [#19](https://github.com/imengyu/vue3-context-menu/issues/19)) 183 | 184 | ## v1.1.5 - 2022/12/8 185 | 186 | * 修复了获取窗口高度不正确,导致调整菜单高度不正确的问题。 187 | 188 | ## v1.1.4 - 2022/11/29 189 | 190 | * 修复了 #16. 191 | 192 | ## v1.1.3 - 2022/11/28 193 | 194 | * [Added] 添加子菜单打开状态数据: 195 | * 添加了 css `.mx-context-menu-item.open` 表示当前菜单的子菜单已打开。 ([#15](https://github.com/imengyu/vue3-context-menu/issues/15)) 196 | * 在渲染插槽参数 (`MenuItemRenderData`) 添加了属性 `isOpen` 表示当前菜单的子菜单是否打开。 197 | * 在渲染插槽参数 (`MenuItemRenderData`) 添加了属性 `hasChildren` 指示当前菜单是否具有子菜单。 198 | * [Added] 添加了svg图标支持。 (`svgIcon` 和 `svgProps` 属性). 199 | * [Change] 子菜单打开时,父菜单现在将保持高亮显示。 200 | 201 | ## v1.1.2 - 2022/11/06 202 | 203 | * 修复未正确删除绑定事件的问题。 ([#14](https://github.com/imengyu/vue3-context-menu/pull/14) Thanks @liumingye) 204 | 205 | ## v1.1.1 - 2022/09/05 206 | 207 | * [Added] 添加了 `closeWhenScoll` 属性,用于控制滚动鼠标时是否关闭菜单。 208 | * [Added] 添加了 `closeMenu` 实例函数用于关闭菜单。 209 | * [Added] 添加了 `closeContextMenu` 全局函数,可用于关闭当前打开的菜单。 210 | * 修复了在嵌套容器的情况下打开的菜单不会消失的问题。 211 | 212 | ## v1.1.0 - 2022/08/27 213 | 214 | * [Added] 支持组件模式。 215 | * [Added] 支持暗色主题。 216 | * [Added] 支持自定义槽自定义菜单。 217 | 218 | ## v1.0.9 - 2022/01/14 219 | 220 | * 添加 Composition API 全局函数 。 221 | * 修复了单击空白位置关闭菜单的问题([#2](https://github.com/imengyu/vue3-context-menu/issues/2)。 222 | * 修复了单击禁用项目并仍然触发单击事件的问题([#3](https://github.com/imengyu/vue3-context-menu/issues/3))。 223 | 224 | ## 2021/07/25 225 | 226 | 正式发布。 227 | --------------------------------------------------------------------------------