├── .browserslistrc ├── .commitlintrc.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .huskyrc.js ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── build ├── rollup.config.vue2.js └── rollup.config.vue3.js ├── changelog.config.js ├── dev ├── vue2 │ ├── .nvmrc │ ├── cdn_usage.html │ ├── package.json │ ├── serve.js │ ├── serve.vue │ └── yarn.lock └── vue3 │ ├── .nvmrc │ ├── cdn_usage.html │ ├── package.json │ ├── serve.js │ ├── serve.vue │ └── yarn.lock ├── dist ├── vue2 │ ├── index.js │ ├── v-dropdown-menu.cjs │ ├── v-dropdown-menu.css │ ├── v-dropdown-menu.global.js │ ├── v-dropdown-menu.global.min.js │ ├── v-dropdown-menu.min.cjs │ ├── v-dropdown-menu.min.mjs │ ├── v-dropdown-menu.mjs │ ├── v-dropdown-menu.umd.js │ └── v-dropdown-menu.umd.min.js └── vue3 │ ├── index.js │ ├── v-dropdown-menu.cjs │ ├── v-dropdown-menu.css │ ├── v-dropdown-menu.global.js │ ├── v-dropdown-menu.global.min.js │ ├── v-dropdown-menu.min.cjs │ ├── v-dropdown-menu.min.mjs │ ├── v-dropdown-menu.mjs │ ├── v-dropdown-menu.umd.js │ └── v-dropdown-menu.umd.min.js ├── docs ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .nvmrc ├── README.md ├── app.config.ts ├── components │ ├── AppDropdown.vue │ ├── AppDropdownAdvanced.vue │ ├── AppDropdownWithCloser.vue │ └── AppSpinner.vue ├── content │ ├── 0.index.md │ ├── 1.guide │ │ ├── 0.index.md │ │ ├── 1.registration │ │ │ ├── 0.vue3.md │ │ │ └── 1.vue2.md │ │ ├── 2.usage.md │ │ └── _dir.yml │ └── 2.demo.md ├── nuxt.config.ts ├── package.json ├── public │ ├── favicon.ico │ └── media │ │ ├── logo.png │ │ └── preview.jpg ├── renovate.json ├── tokens.config.ts ├── tsconfig.json └── yarn.lock ├── meta ├── logo.png └── preview.jpg ├── package.json ├── shims-vue.d.ts ├── src ├── vue2 │ ├── entry.js │ ├── v-dropdown-menu.scss │ └── v-dropdown-menu.vue └── vue3 │ ├── entry.js │ ├── v-dropdown-menu.scss │ └── v-dropdown-menu.vue ├── tsconfig.json ├── vue2 ├── README.md └── index.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | current node 2 | last 2 versions and > 2% 3 | ie > 10 4 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = crlf 7 | charset = utf-8 8 | indent_size = 2 9 | tab_width = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | /vue2 3 | /vue3 4 | /docs 5 | /meta 6 | coverage 7 | node_modules 8 | package.json 9 | package-lock.json 10 | yarn.lock 11 | pnpm-lock.yaml 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | jest: true 7 | }, 8 | parser: 'vue-eslint-parser', 9 | parserOptions: { 10 | parser: '@typescript-eslint/parser', 11 | sourceType: 'module', 12 | ecmaFeatures: { 13 | legacyDecorators: true 14 | }, 15 | warnOnUnsupportedTypeScriptVersion: false, 16 | templateTokenizer: { 17 | pug: 'vue-eslint-parser-template-tokenizer-pug' 18 | } 19 | }, 20 | extends: ['plugin:vue/strongly-recommended', 'eslint-config-prettier', 'prettier'], 21 | plugins: ['@typescript-eslint', 'prettier'], 22 | rules: { 23 | 'prettier/prettier': [ 24 | 'error', 25 | { 26 | endOfLine: 'crlf' 27 | } 28 | ], 29 | 'max-len': [ 30 | 'error', 31 | { 32 | code: 150, 33 | ignoreComments: true, 34 | ignoreStrings: true, 35 | ignoreTemplateLiterals: true 36 | } 37 | ], 38 | 'no-multiple-empty-lines': [2, { max: 2 }], 39 | semi: ['error', 'never'], 40 | 'arrow-parens': ['error', 'as-needed'], 41 | 'no-extend-native': 'off', 42 | 'space-before-function-paren': 'off', 43 | camelcase: 'off', 44 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 45 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 46 | 'no-throw-literal': 'off', 47 | 'no-param-reassign': [ 48 | 'error', 49 | { 50 | props: false 51 | } 52 | ], 53 | '@typescript-eslint/no-var-requires': 'off', 54 | 'vue/component-definition-name-casing': ['error', 'PascalCase'], 55 | 'vue/multi-word-component-names': 'off', 56 | 'vue/component-tags-order': ['error', { order: ['template', 'script', 'style', 'docs'] }], 57 | 'vue/padding-line-between-blocks': ['error'], 58 | 'vue/block-lang': [ 59 | 'error', 60 | { 61 | script: { 62 | allowNoLang: true, 63 | lang: 'ts' 64 | }, 65 | style: { 66 | allowNoLang: true, 67 | lang: 'scss' 68 | } 69 | } 70 | ], 71 | 'vue/no-empty-component-block': 'off', 72 | 'vue/valid-template-root': 'off', 73 | 'vue/no-static-inline-styles': 'off', 74 | 'vue/require-prop-types': ['error'], 75 | 'vue/require-default-prop': ['error'], 76 | 'vue/attribute-hyphenation': ['error', 'always'], 77 | 'vue/v-on-event-hyphenation': ['error', 'always'], 78 | 'vue/html-self-closing': 'off', 79 | 'vue/no-v-html': 'off', 80 | 'vue/order-in-components': ['error'], 81 | 'padding-line-between-statements': [ 82 | 'error', 83 | { blankLine: 'always', prev: '*', next: 'return' }, 84 | { blankLine: 'always', prev: '*', next: 'if' }, 85 | { blankLine: 'always', prev: '*', next: 'switch' }, 86 | { blankLine: 'always', prev: '*', next: 'for' }, 87 | { blankLine: 'always', prev: '*', next: 'function' }, 88 | { blankLine: 'never', prev: 'import', next: 'import' }, 89 | { blankLine: 'always', prev: 'import', next: 'export' }, 90 | { blankLine: 'always', prev: 'expression', next: 'export' }, 91 | { blankLine: 'always', prev: 'import', next: 'expression' } 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # This file has been generated using '.gitattributes Generator'. 2 | # You can generate yours at http://ihopepeace.github.io/gitattributes_generator 3 | 4 | * text=auto 5 | 6 | # Explicitly declare text files that should be normalized and converted 7 | # to native line endings on checkout. 8 | 9 | # Declare files that should have CRLF line endings on checkout. 10 | *.js eol=crlf 11 | *.ts eol=crlf 12 | *.vue eol=crlf 13 | *.css eol=crlf 14 | *.scss eol=crlf 15 | *.json eol=crlf 16 | 17 | # Declare files that should have LF line endings on checkout. 18 | 19 | # Declare files that are truly binary and shouldn't be modified. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node template 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Dependency directories 34 | node_modules/ 35 | jspm_packages/ 36 | package-lock.json 37 | 38 | # TypeScript v1 declaration files 39 | typings/ 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # parcel-bundler cache (https://parceljs.org/) 60 | .cache 61 | 62 | # next.js build output 63 | .next 64 | 65 | # nuxt.js build output 66 | .nuxt 67 | 68 | # vuepress build output 69 | .vuepress/dist 70 | 71 | # Serverless directories 72 | .serverless 73 | 74 | # IDE 75 | .idea 76 | 77 | # Service worker 78 | sw.* 79 | 80 | #misc 81 | demo/node_modules 82 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', 4 | 'pre-commit': 'lint-staged' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ./demo 2 | ./dev 3 | ./docs 4 | .vscode 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.x.x 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | /vue2 3 | /vue3 4 | /docs 5 | /meta 6 | coverage 7 | node_modules 8 | package.json 9 | package-lock.json 10 | yarn.lock 11 | pnpm-lock.yaml 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pugPrintWidth: 140, 3 | pugSingleQuote: false, 4 | pugAttributeSeparator: 'none', 5 | pugEmptyAttributes: 'none', 6 | singleQuote: true, 7 | printWidth: 140, 8 | trailingComma: 'none', 9 | tabWidth: 2, 10 | semi: false, 11 | bracketSpacing: true, 12 | arrowParens: 'avoid', 13 | pugSortAttributesBeginning: [ 14 | '^cols$', 15 | '^v-else$', 16 | '^v-for$', 17 | '^:key$', 18 | '^v-if$', 19 | '^v-else-if$', 20 | '^v-on$', 21 | '^v-bind$', 22 | '^ref$', 23 | '^v-model', 24 | '^name$', 25 | '^:?type$', 26 | '^:value$', 27 | '^v-text$', 28 | '^:?label$', 29 | '^:headers$', 30 | '^:items$', 31 | '^:?item-text$', 32 | '^:?item-value$', 33 | '^:?item-disabled$', 34 | '^:?placeholder$', 35 | '^:?src$', 36 | '^:?color$', 37 | '^:?text-color$', 38 | '^:?icon$', 39 | '^:?small$' 40 | ], 41 | pugSortAttributesEnd: [ 42 | '^:?hint$', 43 | '^:?persistent-hint$', 44 | '^prepend-', 45 | '^@click:prepend', 46 | '^append-', 47 | '^@click:append', 48 | '^:to$', 49 | '^exact$', 50 | '^:(?!(width|height|loading|disabled|data-))', 51 | '^target$', 52 | '^:?width$', 53 | '^:?height$', 54 | '^:loading$', 55 | '^:disabled$', 56 | '^:?data-', 57 | '^@click', 58 | '^@' 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist 2 | /vue2 3 | /docs 4 | /meta 5 | *.min.* 6 | assets/**/*/vendor 7 | static/ 8 | public/ 9 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | //https://github.com/constverum/stylelint-config-rational-order 4 | 'stylelint-config-rational-order', 5 | // https://github.com/shannonmoeller/stylelint-config-prettier 6 | 'stylelint-config-prettier' 7 | ], 8 | overrides: [ 9 | { 10 | customSyntax: 'postcss-scss', 11 | files: ['**/*.{css,sass,scss,less,stylus}'] 12 | }, 13 | { 14 | customSyntax: 'postcss-html', 15 | files: ['*.vue', '**/*.vue'] 16 | } 17 | ], 18 | plugins: [ 19 | // https://github.com/kristerkari/stylelint-scss#list-of-rules 20 | 'stylelint-scss', 21 | // https://github.com/hudochenkov/stylelint-order 22 | 'stylelint-order', 23 | 'stylelint-config-rational-order/plugin' 24 | ], 25 | rules: { 26 | indentation: [ 27 | 2, 28 | { 29 | baseIndentLevel: 0 30 | } 31 | ], 32 | 'color-named': 'never', 33 | 'color-function-notation': 'legacy', 34 | 'at-rule-no-unknown': null, 35 | 'declaration-empty-line-before': [ 36 | 'always', 37 | { 38 | except: ['after-declaration'], 39 | ignore: ['after-comment', 'first-nested', 'inside-single-line-block'], 40 | severity: 'warning' 41 | } 42 | ], 43 | 'rule-empty-line-before': [ 44 | 'always', 45 | { 46 | ignore: ['after-comment', 'first-nested'], 47 | severity: 'warning' 48 | } 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "stylelint.vscode-stylelint", 5 | "editorconfig.editorconfig", 6 | "octref.vetur", 7 | "fallenmax.mithril-emmet" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "emmet.showAbbreviationSuggestions": true, 3 | "emmet.showExpandedAbbreviation": "always", 4 | "emmet.includeLanguages": { 5 | "vue-html": "html", 6 | "vue": "jade" 7 | }, 8 | "files.insertFinalNewline": true, 9 | "editor.insertSpaces": true, 10 | "editor.detectIndentation": false, 11 | "css.validate": false, 12 | "less.validate": false, 13 | "scss.validate": false, 14 | "stylelint.validate": ["css", "scss", "html", "vue"], 15 | "editor.codeActionsOnSave": { 16 | "source.fixAll.stylelint": true, 17 | "source.fixAll.eslint": true 18 | }, 19 | "editor.tabSize": 2, 20 | "explorer.compactFolders": false 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.0.4 4 | 5 | [compare changes](https://github.com/selimdoyranli/v-dropdown-menu/compare/v2.0.3...v2.0.4) 6 | 7 | 8 | ### 🩹 Fixes 9 | 10 | - Css file export fix ([92f78c8](https://github.com/selimdoyranli/v-dropdown-menu/commit/92f78c8)) 11 | 12 | ### ❤️ Contributors 13 | 14 | - Selimdoyranli ([@selimdoyranli](http://github.com/selimdoyranli)) 15 | 16 | ## v2.0.3 17 | 18 | [compare changes](https://github.com/selimdoyranli/v-dropdown-menu/compare/e3f1570...v2.0.3) 19 | 20 | 21 | ### 🏡 Chore 22 | 23 | - Rebrand ([18c431f](https://github.com/selimdoyranli/v-dropdown-menu/commit/18c431f)) 24 | 25 | ### ❤️ Contributors 26 | 27 | - Selimdoyranli ([@selimdoyranli](http://github.com/selimdoyranli)) 28 | 29 | ## v2.0.2 30 | 31 | [compare changes](https://github.com/selimdoyranli/v-dropdown-menu/compare/v2.0.1...v2.0.2) 32 | 33 | 34 | ### 📖 Documentation 35 | 36 | - Update docs ([2976033](https://github.com/selimdoyranli/v-dropdown-menu/commit/2976033)) 37 | 38 | ### 📦 Build 39 | 40 | - Update npm scripts ([a9a69a2](https://github.com/selimdoyranli/v-dropdown-menu/commit/a9a69a2)) 41 | 42 | ### 🏡 Chore 43 | 44 | - Changelog rename ([ac3ac3b](https://github.com/selimdoyranli/v-dropdown-menu/commit/ac3ac3b)) 45 | - Changelog rename ([2a925ea](https://github.com/selimdoyranli/v-dropdown-menu/commit/2a925ea)) 46 | 47 | ### ❤️ Contributors 48 | 49 | - Selimdoyranli ([@selimdoyranli](http://github.com/selimdoyranli)) 50 | 51 | ## v2.0.1 52 | 53 | [compare changes](https://github.com/selimdoyranli/v-dropdown-menu/compare/e3c1f82...v2.0.1) 54 | 55 | 56 | ### 💅 Refactors 57 | 58 | - **build:** Refactor build ([90590fd](https://github.com/selimdoyranli/v-dropdown-menu/commit/90590fd)) 59 | 60 | ### ❤️ Contributors 61 | 62 | - Selimdoyranli ([@selimdoyranli](http://github.com/selimdoyranli)) 63 | 64 | ## v2.0.0 65 | 66 | [compare changes](https://github.com/selimdoyranli/v-dropdown-menu/compare/1.3.1...v2.0.0) 67 | 68 | 69 | ### 🚀 Enhancements 70 | 71 | - Vue3 support ([095b053](https://github.com/selimdoyranli/v-dropdown-menu/commit/095b053)) 72 | 73 | ### 🎨 Styles 74 | 75 | - Code formatting ([2a268c1](https://github.com/selimdoyranli/v-dropdown-menu/commit/2a268c1)) 76 | 77 | ### ❤️ Contributors 78 | 79 | - Selimdoyranli ([@selimdoyranli](http://github.com/selimdoyranli)) 80 | 81 | ## 1.3.2 82 | 83 | - Added isOpen prop watcher. You can use external trigger. 84 | ## 1.3.1 85 | 86 | - Doc updated 87 | 88 | ## 1.3.0 89 | 90 | - Transition feature 91 | - Css hover > vue modifier hover 92 | - Renames (menuZIndex prop name > containerZIndex) 93 | - Clean code 94 | 95 | ## 1.2.0 96 | 97 | - Dropup feature 98 | - Callbacks feature 99 | - dropdown-closer class > dropdown-closer attribute 100 | - Improved ux 101 | 102 | ## 1.1.2 >= 103 | Base developments. 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 selimdoyranli 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version][npm-version-src]][npm-version-href] 2 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 3 | [![changelog][changelog-src]][changelog-href] 4 | [![License][license-src]][license-href] 5 | 6 |

7 | 8 | 9 | 10 |

11 | 12 |

13 | v-dropdown-menu 14 |

15 | 16 |

17 | Customizable dropdown menu for vue 🟩🔽 18 |

19 | 20 |

21 | 22 | 23 | 24 |

25 | 26 |

27 | GitHub stars 28 |

29 | 30 |

31 | Website 32 |

33 | 34 |
35 | Sponsorship 💖
36 | GitHub
37 | Buy me a coffee
38 |
39 | 40 | ## Features 41 | - ⚡️ Lightweight 42 | - 🎨 Interactive 43 | - 🛠️ Customizable 44 | - 👶🏻 Easy implementation 45 | - 📦 Vue2 & Vue3 support 46 | - 💉 SSR compatible 47 | 48 | ## Getting Started 49 | 50 | ### Try it Online ⚡️ 51 | 52 | [DEMO](https://v-dropdown-menu.vercel.app/demo) 53 | 54 | 55 | ## Installation 56 | 57 | ```js 58 | yarn add v-dropdown-menu # or npm i v-dropdown-menu 59 | ``` 60 | 61 | ### Vue3 62 | 63 | #### Global Register 64 | 65 | ```js 66 | import { createApp } from 'vue' 67 | import App from './App.vue' 68 | import DropdownMenu from 'v-dropdown-menu' 69 | import 'v-dropdown-menu/css' 70 | 71 | const app = createApp(App) 72 | 73 | app.use(DropdownMenu) 74 | app.mount('#app') 75 | ``` 76 | 77 | #### Local Register 78 | ```html 79 | 83 | ``` 84 | #### Via CDN 85 | ```js 86 | 87 | 88 | 89 | 90 | 96 | ``` 97 | 98 | ### Vue2 99 | 100 | #### Global Register 101 | 102 | ```js 103 | import Vue from "vue" 104 | import DropdownMenu from "v-dropdown-menu/vue2" 105 | import 'v-dropdown-menu/vue2/css' 106 | 107 | Vue.use(DropdownMenu); 108 | ``` 109 | 110 | #### Local Register 111 | ```js 112 | import DropdownMenu from "v-dropdown-menu/vue2" 113 | import 'v-dropdown-menu/vue2/css' 114 | 115 | export default { 116 | components: { 117 | DropdownMenu 118 | } 119 | } 120 | ``` 121 | #### Via CDN 122 | ```js 123 | 124 | 125 | 126 | 127 | 134 | ``` 135 |   136 | 137 | ### Usage 138 | 139 | ```html 140 | 141 | 144 | 145 | 146 | 147 | 154 | 155 | 156 | 157 | 158 | ``` 159 | 160 | #### Props 161 | | Name | Description | Type| Options| Default | 162 | |--|--|--|--|--| 163 | |isOpen|Show or hide for dropdown|Boolean|`true` , `false` | false 164 | |mode|Open variant|String|`click` , `hover` | click 165 | | dropup |Open the menu upwards | Boolean | `true` , `false` | false 166 | |direction|Menu container direction|String|`left` , `right` , `center` | left 167 | |closeOnClickOutside|closes dropdown menu when click outside|Booelan|`true` , `false` | true 168 | |withDropdownCloser| If there is an element in the menu with **dropdown-closer** attribute, clicking on it closes the menu.|Boolean|`true` , `false` | false 169 | |containerZIndex|z-index of menu container|String| .| 994 170 | |overlay|background overlay of dropdown menu (only for click mode) |Boolean| `true` , `false`| true 171 | |overlayBgColor|background-color of overlay |String| ex: `rgba(1, 35, 83, 0.8)`| rgba(0, 0, 0, 0.2) 172 | |overlayZIndex|z-index of overlay|String| .| 992 173 | |transition|custom vue transition for menu|String| .| default 174 | 175 | #### Slots 176 | |Name| Description | 177 | |--|--| 178 | |trigger|trigger for dropdown menu | 179 | |header|header of menu container (optional)| 180 | |body|content of menu (optional)| 181 | |footer|footer of menu container (optional)| 182 | 183 | #### Events (only for click mode) 184 | | | 185 | |--| 186 | | `@opened="dispatchEvent"`| 187 | | `@closed="dispatchEvent"`| 188 | 189 | 190 | --- 191 | 192 | ## Development 193 | 194 | ### Vue3 195 | 196 | ```bash 197 | yarn build:vue3 # build for vue3 198 | ``` 199 | 200 | ```bash 201 | # Serve 202 | 203 | cd dev/vue3 204 | 205 | yarn install 206 | yarn serve 207 | ``` 208 | 209 | ### Vue2 210 | 211 | ```bash 212 | yarn build:vue2 # build for vue2 213 | ``` 214 | 215 | ```bash 216 | # Serve 217 | 218 | cd dev/vue2 219 | 220 | yarn install 221 | yarn serve 222 | ``` 223 | 224 | ### Vue 2&3 225 | 226 | ```bash 227 | yarn build # build for vue2 and vue3 228 | ``` 229 | 230 | #### Linter 231 | 232 | ```bash 233 | # run eslint 234 | yarn lint:eslint 235 | 236 | # run eslint fix 237 | yarn lint:eslint:fix 238 | 239 | # run stylelint 240 | yarn lint:stylelint 241 | 242 | # run stylelint fix 243 | yarn lint:stylelint:fix 244 | 245 | # run prettier 246 | yarn prettier 247 | 248 | ``` 249 | 250 | ## Sponsorship 251 | 252 | You can sponsor me for the continuity of my projects: 253 | 254 |

255 | 256 | 257 | 258 |

259 | 260 |

261 | 262 | 263 | 264 |

265 | 266 | ## License 267 | 268 | [MIT License](./LICENSE) 269 | 270 | Copyright (c) selimdoyranli 271 | 272 | 273 | [npm-version-src]: https://img.shields.io/npm/v/v-dropdown-menu/latest.svg 274 | [npm-version-href]: https://npmjs.com/package/v-dropdown-menu 275 | 276 | [npm-downloads-src]: https://img.shields.io/npm/dt/v-dropdown-menu.svg 277 | [npm-downloads-href]: https://npmjs.com/package/v-dropdown-menu 278 | 279 | [codecov-src]: https://img.shields.io/codecov/c/github/selimdoyranli/v-dropdown-menu.svg 280 | [codecov-href]: https://codecov.io/gh/selimdoyranli/v-dropdown-menu 281 | 282 | [changelog-src]: https://img.shields.io/static/v1?label=%F0%9F%93%96&message=Release%20Notes%20|%20CHANGELOG&color=blue 283 | [changelog-href]: ./CHANGELOG.md 284 | 285 | [license-src]: https://img.shields.io/badge/License-MIT-blue.svg 286 | [license-href]: https://npmjs.com/package/v-dropdown-menu/LICENSE 287 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const devPresets = ['@vue/babel-preset-app'] 2 | const buildPresets = [ 3 | [ 4 | '@babel/preset-env', 5 | // Config for @babel/preset-env 6 | { 7 | // Example: Always transpile optional chaining/nullish coalescing 8 | // include: [ 9 | // /(optional-chaining|nullish-coalescing)/ 10 | // ], 11 | } 12 | ], 13 | '@babel/preset-typescript' 14 | ] 15 | module.exports = { 16 | presets: process.env.NODE_ENV === 'development' ? devPresets : buildPresets 17 | } 18 | -------------------------------------------------------------------------------- /build/rollup.config.vue2.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import vue from 'vue-prev-rollup-plugin-vue' 4 | import alias from '@rollup/plugin-alias' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import replace from '@rollup/plugin-replace' 7 | import babel from '@rollup/plugin-babel' 8 | import css from 'rollup-plugin-css-only' 9 | import { terser } from 'rollup-plugin-terser' 10 | import minimist from 'minimist' 11 | 12 | // Get browserslist config and remove ie from es build targets 13 | const esbrowserslist = fs 14 | .readFileSync('./.browserslistrc') 15 | .toString() 16 | .split('\n') 17 | .filter(entry => entry && entry.substring(0, 2) !== 'ie') 18 | 19 | const argv = minimist(process.argv.slice(2)) 20 | 21 | const projectRoot = path.resolve(__dirname, '..') 22 | 23 | const baseConfig = { 24 | input: 'src/vue2/entry.js', 25 | plugins: { 26 | preVue: [ 27 | alias({ 28 | resolve: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 29 | entries: { 30 | '@': path.resolve(projectRoot, 'src') 31 | } 32 | }) 33 | ], 34 | replace: { 35 | 'process.env.NODE_ENV': JSON.stringify('production'), 36 | 'process.env.ES_BUILD': JSON.stringify('false'), 37 | preventAssignment: true 38 | }, 39 | css: { 40 | output: 'dist/vue2/v-dropdown-menu.css' 41 | }, 42 | vue: { 43 | css: false, 44 | template: { 45 | isProduction: true 46 | } 47 | }, 48 | babel: { 49 | exclude: 'node_modules/**', 50 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 51 | babelHelpers: 'bundled' 52 | }, 53 | terser: {} 54 | } 55 | } 56 | 57 | // ESM/UMD/IIFE shared settings: externals 58 | // Refer to https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency 59 | const external = [ 60 | // list external dependencies, exactly the way it is written in the import statement. 61 | // eg. 'jquery' 62 | 'vue' 63 | ] 64 | 65 | // UMD/IIFE shared settings: output.globals 66 | // Refer to https://rollupjs.org/guide/en#output-globals for details 67 | const globals = { 68 | // Provide global variable names to replace your external imports 69 | // eg. jquery: '$' 70 | vue: 'Vue' 71 | } 72 | 73 | // Customize configs for individual targets 74 | const buildFormats = [] 75 | 76 | if (!argv.format || argv.format === 'umd') { 77 | const umdConfig = { 78 | ...baseConfig, 79 | external, 80 | output: [ 81 | { 82 | file: 'vue2/index.js', 83 | format: 'umd', 84 | exports: 'named', 85 | globals, 86 | name: 'DropdownMenu', 87 | plugins: [ 88 | terser({ 89 | ...baseConfig.plugins.terser 90 | }) 91 | ] 92 | }, 93 | { 94 | file: 'dist/vue2/index.js', 95 | format: 'umd', 96 | exports: 'named', 97 | globals, 98 | name: 'DropdownMenu', 99 | plugins: [ 100 | terser({ 101 | ...baseConfig.plugins.terser 102 | }) 103 | ] 104 | }, 105 | { 106 | file: 'dist/vue2/v-dropdown-menu.umd.js', 107 | format: 'umd', 108 | exports: 'named', 109 | globals, 110 | name: 'DropdownMenu' 111 | }, 112 | { 113 | file: 'dist/vue2/v-dropdown-menu.umd.min.js', 114 | format: 'umd', 115 | exports: 'named', 116 | globals, 117 | name: 'DropdownMenu', 118 | plugins: [ 119 | terser({ 120 | ...baseConfig.plugins.terser 121 | }) 122 | ] 123 | }, 124 | { 125 | file: 'dist/vue2/v-dropdown-menu.global.js', 126 | format: 'umd', 127 | globals, 128 | name: 'DropdownMenu' 129 | }, 130 | { 131 | file: 'dist/vue2/v-dropdown-menu.global.min.js', 132 | format: 'umd', 133 | globals, 134 | name: 'DropdownMenu', 135 | plugins: [ 136 | terser({ 137 | ...baseConfig.plugins.terser 138 | }) 139 | ] 140 | } 141 | ], 142 | plugins: [ 143 | replace({ 144 | ...baseConfig.plugins.replace 145 | }), 146 | ...baseConfig.plugins.preVue, 147 | css({ 148 | ...baseConfig.plugins.css 149 | }), 150 | vue({ 151 | ...baseConfig.plugins.vue 152 | }), 153 | babel({ 154 | ...baseConfig.plugins.babel, 155 | presets: [ 156 | [ 157 | '@babel/preset-env', 158 | { 159 | targets: esbrowserslist 160 | } 161 | ] 162 | ] 163 | }), 164 | commonjs() 165 | ] 166 | } 167 | buildFormats.push(umdConfig) 168 | } 169 | 170 | if (!argv.format || argv.format === 'es') { 171 | const esConfig = { 172 | ...baseConfig, 173 | external, 174 | output: [ 175 | { 176 | file: 'dist/vue2/v-dropdown-menu.mjs', 177 | format: 'esm', 178 | exports: 'named' 179 | }, 180 | { 181 | file: 'dist/vue2/v-dropdown-menu.min.mjs', 182 | format: 'esm', 183 | exports: 'named', 184 | plugins: [ 185 | terser({ 186 | ...baseConfig.plugins.terser 187 | }) 188 | ] 189 | } 190 | ], 191 | plugins: [ 192 | replace({ 193 | ...baseConfig.plugins.replace, 194 | 'process.env.ES_BUILD': JSON.stringify('true') 195 | }), 196 | ...baseConfig.plugins.preVue, 197 | css({ 198 | ...baseConfig.plugins.css 199 | }), 200 | vue({ 201 | ...baseConfig.plugins.vue 202 | }), 203 | babel({ 204 | ...baseConfig.plugins.babel, 205 | presets: [ 206 | [ 207 | '@babel/preset-env', 208 | { 209 | targets: esbrowserslist 210 | } 211 | ] 212 | ] 213 | }), 214 | commonjs() 215 | ] 216 | } 217 | buildFormats.push(esConfig) 218 | } 219 | 220 | if (!argv.format || argv.format === 'cjs') { 221 | const cjsConfig = { 222 | ...baseConfig, 223 | external, 224 | output: [ 225 | { 226 | compact: true, 227 | file: 'dist/vue2/v-dropdown-menu.cjs', 228 | format: 'cjs', 229 | name: 'DropdownMenu', 230 | exports: 'named', 231 | globals 232 | }, 233 | { 234 | compact: true, 235 | file: 'dist/vue2/v-dropdown-menu.min.cjs', 236 | format: 'cjs', 237 | name: 'DropdownMenu', 238 | exports: 'named', 239 | globals, 240 | plugins: [ 241 | terser({ 242 | ...baseConfig.plugins.terser 243 | }) 244 | ] 245 | } 246 | ], 247 | plugins: [ 248 | replace(baseConfig.plugins.replace), 249 | ...baseConfig.plugins.preVue, 250 | css({ 251 | ...baseConfig.plugins.css 252 | }), 253 | vue({ 254 | ...baseConfig.plugins.vue, 255 | template: { 256 | ...baseConfig.plugins.vue.template, 257 | optimizeSSR: true 258 | } 259 | }), 260 | babel(baseConfig.plugins.babel), 261 | commonjs() 262 | ] 263 | } 264 | buildFormats.push(cjsConfig) 265 | } 266 | 267 | // Export config 268 | export default buildFormats 269 | -------------------------------------------------------------------------------- /build/rollup.config.vue3.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import vue from 'vue-next-rollup-plugin-vue' 4 | import alias from '@rollup/plugin-alias' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import replace from '@rollup/plugin-replace' 7 | import babel from '@rollup/plugin-babel' 8 | import scss from 'rollup-plugin-scss' 9 | import css from 'rollup-plugin-css-only' 10 | import { terser } from 'rollup-plugin-terser' 11 | import minimist from 'minimist' 12 | 13 | // Get browserslist config and remove ie from es build targets 14 | const esbrowserslist = fs 15 | .readFileSync('./.browserslistrc') 16 | .toString() 17 | .split('\n') 18 | .filter(entry => entry && entry.substring(0, 2) !== 'ie') 19 | 20 | const argv = minimist(process.argv.slice(2)) 21 | 22 | const projectRoot = path.resolve(__dirname, '..') 23 | 24 | const baseConfig = { 25 | input: 'src/vue3/entry.js', 26 | plugins: { 27 | preVue: [ 28 | alias({ 29 | resolve: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 30 | entries: { 31 | '@': path.resolve(projectRoot, 'src') 32 | } 33 | }) 34 | ], 35 | replace: { 36 | 'process.env.NODE_ENV': JSON.stringify('production'), 37 | 'process.env.ES_BUILD': JSON.stringify('false'), 38 | preventAssignment: true 39 | }, 40 | scss: { 41 | fileName: 'v-dropdown-menu.css', 42 | outputStyle: 'compressed' 43 | }, 44 | css: { 45 | output: 'dist/vue3/v-dropdown-menu.css' 46 | }, 47 | vue: { 48 | css: false, 49 | template: { 50 | isProduction: true 51 | } 52 | }, 53 | babel: { 54 | exclude: 'node_modules/**', 55 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'], 56 | babelHelpers: 'bundled' 57 | }, 58 | terser: {} 59 | } 60 | } 61 | 62 | // ESM/UMD/IIFE shared settings: externals 63 | // Refer to https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency 64 | const external = [ 65 | // list external dependencies, exactly the way it is written in the import statement. 66 | // eg. 'jquery' 67 | 'vue' 68 | ] 69 | 70 | // UMD/IIFE shared settings: output.globals 71 | // Refer to https://rollupjs.org/guide/en#output-globals for details 72 | const globals = { 73 | // Provide global variable names to replace your external imports 74 | // eg. jquery: '$' 75 | vue: 'Vue' 76 | } 77 | 78 | // Customize configs for individual targets 79 | const buildFormats = [] 80 | 81 | if (!argv.format || argv.format === 'umd') { 82 | const umdConfig = { 83 | ...baseConfig, 84 | external, 85 | output: [ 86 | { 87 | file: 'dist/vue3/index.js', 88 | format: 'umd', 89 | exports: 'named', 90 | globals, 91 | name: 'DropdownMenu', 92 | plugins: [ 93 | terser({ 94 | ...baseConfig.plugins.terser 95 | }) 96 | ] 97 | }, 98 | { 99 | file: 'dist/vue3/v-dropdown-menu.umd.js', 100 | format: 'umd', 101 | exports: 'named', 102 | globals, 103 | name: 'DropdownMenu' 104 | }, 105 | { 106 | file: 'dist/vue3/v-dropdown-menu.umd.min.js', 107 | format: 'umd', 108 | exports: 'named', 109 | globals, 110 | name: 'DropdownMenu', 111 | plugins: [ 112 | terser({ 113 | ...baseConfig.plugins.terser 114 | }) 115 | ] 116 | }, 117 | { 118 | file: 'dist/vue3/v-dropdown-menu.global.js', 119 | format: 'umd', 120 | globals, 121 | name: 'DropdownMenu' 122 | }, 123 | { 124 | file: 'dist/vue3/v-dropdown-menu.global.min.js', 125 | format: 'umd', 126 | globals, 127 | name: 'DropdownMenu', 128 | plugins: [ 129 | terser({ 130 | ...baseConfig.plugins.terser 131 | }) 132 | ] 133 | } 134 | ], 135 | plugins: [ 136 | replace({ 137 | ...baseConfig.plugins.replace 138 | }), 139 | ...baseConfig.plugins.preVue, 140 | scss({ 141 | ...baseConfig.plugins.scss 142 | }), 143 | css({ 144 | ...baseConfig.plugins.css 145 | }), 146 | vue({ 147 | ...baseConfig.plugins.vue 148 | }), 149 | babel({ 150 | ...baseConfig.plugins.babel, 151 | presets: [ 152 | [ 153 | '@babel/preset-env', 154 | { 155 | targets: esbrowserslist 156 | } 157 | ] 158 | ] 159 | }), 160 | commonjs() 161 | ] 162 | } 163 | buildFormats.push(umdConfig) 164 | } 165 | 166 | if (!argv.format || argv.format === 'es') { 167 | const esConfig = { 168 | ...baseConfig, 169 | external, 170 | output: [ 171 | { 172 | file: 'dist/vue3/v-dropdown-menu.mjs', 173 | format: 'esm', 174 | exports: 'named' 175 | }, 176 | { 177 | file: 'dist/vue3/v-dropdown-menu.min.mjs', 178 | format: 'esm', 179 | exports: 'named', 180 | plugins: [ 181 | terser({ 182 | ...baseConfig.plugins.terser 183 | }) 184 | ] 185 | } 186 | ], 187 | plugins: [ 188 | replace({ 189 | ...baseConfig.plugins.replace, 190 | 'process.env.ES_BUILD': JSON.stringify('true') 191 | }), 192 | ...baseConfig.plugins.preVue, 193 | scss({ 194 | ...baseConfig.plugins.scss 195 | }), 196 | css({ 197 | ...baseConfig.plugins.css 198 | }), 199 | vue({ 200 | ...baseConfig.plugins.vue 201 | }), 202 | babel({ 203 | ...baseConfig.plugins.babel, 204 | presets: [ 205 | [ 206 | '@babel/preset-env', 207 | { 208 | targets: esbrowserslist 209 | } 210 | ] 211 | ] 212 | }), 213 | commonjs() 214 | ] 215 | } 216 | buildFormats.push(esConfig) 217 | } 218 | 219 | if (!argv.format || argv.format === 'cjs') { 220 | const cjsConfig = { 221 | ...baseConfig, 222 | external, 223 | output: [ 224 | { 225 | compact: true, 226 | file: 'dist/vue3/v-dropdown-menu.cjs', 227 | format: 'cjs', 228 | name: 'DropdownMenu', 229 | exports: 'named', 230 | globals 231 | }, 232 | { 233 | compact: true, 234 | file: 'dist/vue3/v-dropdown-menu.min.cjs', 235 | format: 'cjs', 236 | name: 'DropdownMenu', 237 | exports: 'named', 238 | globals, 239 | plugins: [ 240 | terser({ 241 | ...baseConfig.plugins.terser 242 | }) 243 | ] 244 | } 245 | ], 246 | plugins: [ 247 | replace(baseConfig.plugins.replace), 248 | ...baseConfig.plugins.preVue, 249 | scss({ 250 | ...baseConfig.plugins.scss 251 | }), 252 | css({ 253 | ...baseConfig.plugins.css 254 | }), 255 | vue({ 256 | ...baseConfig.plugins.vue, 257 | template: { 258 | ...baseConfig.plugins.vue.template, 259 | optimizeSSR: true 260 | } 261 | }), 262 | babel(baseConfig.plugins.babel), 263 | commonjs() 264 | ] 265 | } 266 | buildFormats.push(cjsConfig) 267 | } 268 | 269 | // Export config 270 | export default buildFormats 271 | -------------------------------------------------------------------------------- /changelog.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: { 3 | feat: { title: '🚀 Enhancements', semver: 'minor' }, 4 | perf: { title: '🔥 Performance', semver: 'patch' }, 5 | fix: { title: '🩹 Fixes', semver: 'patch' }, 6 | refactor: { title: '💅 Refactors', semver: 'patch' }, 7 | docs: { title: '📖 Documentation', semver: 'patch' }, 8 | build: { title: '📦 Build', semver: 'patch' }, 9 | types: { title: '🌊 Types', semver: 'patch' }, 10 | chore: { title: '🏡 Chore' }, 11 | examples: { title: '🏀 Examples' }, 12 | test: { title: '✅ Tests' }, 13 | style: { title: '🎨 Styles' }, 14 | ci: { title: '🤖 CI' } 15 | }, 16 | cwd: null, 17 | from: '', 18 | to: '', 19 | output: 'CHANGELOG.md', 20 | scopeMap: {}, 21 | tokens: { 22 | github: process.env.CHANGELOGEN_TOKENS_GITHUB || process.env.GITHUB_TOKEN || process.env.GH_TOKEN 23 | }, 24 | templates: { 25 | commitMessage: 'chore(release): v{{newVersion}}', 26 | tagMessage: 'v{{newVersion}}', 27 | tagBody: 'v{{newVersion}}' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dev/vue2/.nvmrc: -------------------------------------------------------------------------------- 1 | v16.x.x 2 | -------------------------------------------------------------------------------- /dev/vue2/cdn_usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 |
32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /dev/vue2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-dropdown-menu-vue2-serve", 3 | "scripts": { 4 | "serve": "vue-cli-service serve serve.js" 5 | }, 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "@vue/cli-service": "^4.5.10", 9 | "vue": "^2.6.12" 10 | }, 11 | "peerDependencies": { 12 | "vue": "^2.6.12" 13 | }, 14 | "engines": { 15 | "node": ">=12" 16 | }, 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /dev/vue2/serve.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Dev from './serve.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(Dev) 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /dev/vue2/serve.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /dev/vue3/.nvmrc: -------------------------------------------------------------------------------- 1 | v16.x.x 2 | -------------------------------------------------------------------------------- /dev/vue3/cdn_usage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 31 |
32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /dev/vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-dropdown-menu-vue3-serve", 3 | "scripts": { 4 | "serve": "vue-cli-service serve serve.js" 5 | }, 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "@vue/cli-service": "^4.5.10", 9 | "vue": "^3.0.0" 10 | }, 11 | "peerDependencies": { 12 | "vue": "^3.0.0" 13 | }, 14 | "engines": { 15 | "node": ">=12" 16 | }, 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /dev/vue3/serve.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Dev from './serve.vue' 3 | 4 | const app = createApp(Dev) 5 | app.mount('#app') 6 | -------------------------------------------------------------------------------- /dev/vue3/serve.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /dist/vue2/index.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu={})}(this,(function(e){"use strict";function n(e,n,t,o,i,s,r,d,l,a){"boolean"!=typeof r&&(l=d,d=r,r=!1);const u="function"==typeof t?t.options:t;let p;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,i&&(u.functional=!0)),o&&(u._scopeId=o),s?(p=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(s)},u._ssrRegister=p):n&&(p=r?function(e){n.call(this,a(e,this.$root.$options.shadowRoot))}:function(e){n.call(this,d(e))}),p)if(u.functional){const e=u.render;u.render=function(n,t){return p.call(t),e(n,t)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,p):[p]}return t}const t=n({render:function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{click:function(n){n.preventDefault(),e.menu.isOpen=!e.menu.isOpen}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],ref:"overlayRef",staticClass:"v-dropdown-menu__overlay",style:{"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex},on:{mousedown:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}}):e._e()],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass(){return this.menu.isOpen?`${this.baseClassName}--active`:null},modeClass(){return"click"===this.menu.mode?`${this.baseClassName}--mode-click`:`${this.baseClassName}--mode-hover`},dropupClass(){return this.menu.dropup?`${this.baseClassName}--dropup`:null},directionClass(){let e=null;return e="left"===this.menu.direction?`${this.baseClassName}--direction-left`:"center"===this.menu.direction?`${this.baseClassName}--direction-center`:`${this.baseClassName}--direction-right`,e}},watch:{isOpen(e){"click"===this.menu.mode&&(e?setTimeout((()=>{this.show()}),1):setTimeout((()=>{this.hide()}),1))},"menu.isOpen"(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted(){this.dropdownCloser(),this.$nextTick((()=>{this.menu.closeOnClickOutside&&this.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show(){this.menu.isOpen=!0},hide(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser(){if(this.menu.withDropdownCloser){this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{this.menu.isOpen=!1}))}))}},closeDropdownOnPopState(){window.addEventListener("popstate",(()=>{this.menu.isOpen&&(this.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,undefined,!1,void 0,void 0,void 0),o=function(e){o.installed||(o.installed=!0,e.component("DropdownMenu",t))},i={install:o};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(i)}t.install=o,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.css: -------------------------------------------------------------------------------- 1 | .v-dropdown-menu{position:relative;display:inline-block}.v-dropdown-menu__trigger{position:relative}.v-dropdown-menu__container{position:absolute;top:100%;bottom:auto;min-width:230px;max-width:100%;overflow:hidden;background-color:#fff;border:1px solid #ddd}.v-dropdown-menu--dropup .v-dropdown-menu__container{top:auto;bottom:100%}.v-dropdown-menu--direction-left .v-dropdown-menu__container{left:0}.v-dropdown-menu--direction-center .v-dropdown-menu__container{left:50%;transform:translateX(-50%) translateY(0)}.v-dropdown-menu--direction-right .v-dropdown-menu__container{right:0}.v-dropdown-menu__overlay{position:fixed;top:0;left:0;width:100%;height:100vh}.v-dropdown-menu .default-enter-active{transition:all .2s ease}.v-dropdown-menu .default-leave-active{transition:all .2s cubic-bezier(1,.5,.8,1)}.v-dropdown-menu .default-enter,.v-dropdown-menu .default-leave-to{transform:translateY(12px);opacity:0}.v-dropdown-menu--mode-hover .default-enter,.v-dropdown-menu--mode-hover .default-leave-active{transition-delay:.4s}.v-dropdown-menu--dropup .default-enter,.v-dropdown-menu--dropup .default-leave-to{transform:translateY(-12px)}.v-dropdown-menu--dropup.v-dropdown-menu--direction-center .default-enter,.v-dropdown-menu--dropup.v-dropdown-menu--direction-center .default-leave-to{transform:translateX(-50%) translateY(-12px)}.v-dropdown-menu--direction-center .default-enter,.v-dropdown-menu--direction-center .default-leave-to{transform:translateX(-50%) translateY(12px)} -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.global.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu=n()}(this,(function(){"use strict";function e(e,n,t,o,i,s,r,d,l,a){"boolean"!=typeof r&&(l=d,d=r,r=!1);const u="function"==typeof t?t.options:t;let p;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,i&&(u.functional=!0)),o&&(u._scopeId=o),s?(p=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(s)},u._ssrRegister=p):n&&(p=r?function(e){n.call(this,a(e,this.$root.$options.shadowRoot))}:function(e){n.call(this,d(e))}),p)if(u.functional){const e=u.render;u.render=function(n,t){return p.call(t),e(n,t)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,p):[p]}return t}const n=e({render:function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{click:function(n){n.preventDefault(),e.menu.isOpen=!e.menu.isOpen}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],ref:"overlayRef",staticClass:"v-dropdown-menu__overlay",style:{"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex},on:{mousedown:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}}):e._e()],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass(){return this.menu.isOpen?`${this.baseClassName}--active`:null},modeClass(){return"click"===this.menu.mode?`${this.baseClassName}--mode-click`:`${this.baseClassName}--mode-hover`},dropupClass(){return this.menu.dropup?`${this.baseClassName}--dropup`:null},directionClass(){let e=null;return e="left"===this.menu.direction?`${this.baseClassName}--direction-left`:"center"===this.menu.direction?`${this.baseClassName}--direction-center`:`${this.baseClassName}--direction-right`,e}},watch:{isOpen(e){"click"===this.menu.mode&&(e?setTimeout((()=>{this.show()}),1):setTimeout((()=>{this.hide()}),1))},"menu.isOpen"(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted(){this.dropdownCloser(),this.$nextTick((()=>{this.menu.closeOnClickOutside&&this.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show(){this.menu.isOpen=!0},hide(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser(){if(this.menu.withDropdownCloser){this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{this.menu.isOpen=!1}))}))}},closeDropdownOnPopState(){window.addEventListener("popstate",(()=>{this.menu.isOpen&&(this.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,undefined,!1,void 0,void 0,void 0),t=function(e){t.installed||(t.installed=!0,e.component("DropdownMenu",n))},o={install:t};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(o)}return n.install=t,n})); 2 | -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.min.cjs: -------------------------------------------------------------------------------- 1 | "use strict";function e(e,n,t,o,i,s,r,d,a,l){"boolean"!=typeof r&&(a=d,d=r,r=!1);const u="function"==typeof t?t.options:t;let c;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,i&&(u.functional=!0)),o&&(u._scopeId=o),s?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,a(e)),e&&e._registeredComponents&&e._registeredComponents.add(s)},u._ssrRegister=c):n&&(c=r?function(e){n.call(this,l(e,this.$root.$options.shadowRoot))}:function(e){n.call(this,d(e))}),c)if(u.functional){const e=u.render;u.render=function(n,t){return c.call(t),e(n,t)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,c):[c]}return t}Object.defineProperty(exports,"__esModule",{value:!0});var n=e({render:function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[e._ssrNode('
',"
",[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[e._ssrNode('
',"
",[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e._ssrNode(e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?'
":"\x3c!----\x3e")],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data:function(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass:function(){return this.menu.isOpen?"".concat(this.baseClassName,"--active"):null},modeClass:function(){return"click"===this.menu.mode?"".concat(this.baseClassName,"--mode-click"):"".concat(this.baseClassName,"--mode-hover")},dropupClass:function(){return this.menu.dropup?"".concat(this.baseClassName,"--dropup"):null},directionClass:function(){return"left"===this.menu.direction?"".concat(this.baseClassName,"--direction-left"):"center"===this.menu.direction?"".concat(this.baseClassName,"--direction-center"):"".concat(this.baseClassName,"--direction-right")}},watch:{isOpen:function(e){var n=this;"click"===this.menu.mode&&(e?setTimeout((function(){n.show()}),1):setTimeout((function(){n.hide()}),1))},"menu.isOpen":function(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted:function(){var e=this;this.dropdownCloser(),this.$nextTick((function(){e.menu.closeOnClickOutside&&e.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy:function(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show:function(){this.menu.isOpen=!0},hide:function(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside:function(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside:function(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside:function(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser:function(){var e=this;this.menu.withDropdownCloser&&this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((function(n){n.addEventListener("click",(function(){e.menu.isOpen=!1}))}))},closeDropdownOnPopState:function(){var e=this;window.addEventListener("popstate",(function(){e.menu.isOpen&&(e.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState:function(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,"data-v-cf2c3d36",!1,void 0,void 0,void 0),t=function(e){t.installed||(t.installed=!0,e.component("DropdownMenu",n))},o={install:t},i=null;"undefined"!=typeof window?i=window.Vue:"undefined"!=typeof global&&(i=global.Vue),i&&i.use(o),n.install=t,exports.default=n; -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.min.mjs: -------------------------------------------------------------------------------- 1 | function e(e,t,n,o,s,i,r,d,a,l){"boolean"!=typeof r&&(a=d,d=r,r=!1);const u="function"==typeof n?n.options:n;let p;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,s&&(u.functional=!0)),o&&(u._scopeId=o),i?(p=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),t&&t.call(this,a(e)),e&&e._registeredComponents&&e._registeredComponents.add(i)},u._ssrRegister=p):t&&(p=r?function(e){t.call(this,l(e,this.$root.$options.shadowRoot))}:function(e){t.call(this,d(e))}),p)if(u.functional){const e=u.render;u.render=function(t,n){return p.call(n),e(t,n)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,p):[p]}return n}const t=e({render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[n("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{click:function(t){t.preventDefault(),e.menu.isOpen=!e.menu.isOpen}}},[e._t("trigger")],2),n("transition",{attrs:{name:e.menu.transition}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[n("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),n("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),n("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[n("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{mouseover:function(t){return t.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(t){return t.preventDefault(),e.hide.apply(null,arguments)}}},[e._t("trigger")],2),n("transition",{attrs:{name:e.menu.transition}},[n("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(t){return t.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(t){return t.preventDefault(),e.hide.apply(null,arguments)}}},[n("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),n("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),n("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?n("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],ref:"overlayRef",staticClass:"v-dropdown-menu__overlay",style:{"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex},on:{mousedown:function(t){return t.preventDefault(),e.hide.apply(null,arguments)}}}):e._e()],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass(){return this.menu.isOpen?`${this.baseClassName}--active`:null},modeClass(){return"click"===this.menu.mode?`${this.baseClassName}--mode-click`:`${this.baseClassName}--mode-hover`},dropupClass(){return this.menu.dropup?`${this.baseClassName}--dropup`:null},directionClass(){let e=null;return e="left"===this.menu.direction?`${this.baseClassName}--direction-left`:"center"===this.menu.direction?`${this.baseClassName}--direction-center`:`${this.baseClassName}--direction-right`,e}},watch:{isOpen(e){"click"===this.menu.mode&&(e?setTimeout((()=>{this.show()}),1):setTimeout((()=>{this.hide()}),1))},"menu.isOpen"(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted(){this.dropdownCloser(),this.$nextTick((()=>{this.menu.closeOnClickOutside&&this.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show(){this.menu.isOpen=!0},hide(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser(){if(this.menu.withDropdownCloser){this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{this.menu.isOpen=!1}))}))}},closeDropdownOnPopState(){window.addEventListener("popstate",(()=>{this.menu.isOpen&&(this.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,undefined,!1,void 0,void 0,void 0),n=function(e){n.installed||(n.installed=!0,e.component("DropdownMenu",t))};t.install=n;export{t as default}; 2 | -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.mjs: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // 4 | // 5 | // 6 | // 7 | // 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | // 22 | // 23 | // 24 | // 25 | // 26 | // 27 | // 28 | // 29 | // 30 | // 31 | // 32 | // 33 | // 34 | // 35 | // 36 | // 37 | // 38 | // 39 | // 40 | // 41 | // 42 | // 43 | // 44 | 45 | var script = { 46 | name: 'DropdownMenu', 47 | props: { 48 | isOpen: { 49 | type: Boolean, 50 | required: false, 51 | default: false 52 | }, 53 | mode: { 54 | type: String, 55 | required: false, 56 | default: 'click' 57 | }, 58 | dropup: { 59 | type: Boolean, 60 | required: false, 61 | default: false 62 | }, 63 | direction: { 64 | type: String, 65 | required: false, 66 | default: 'left' 67 | }, 68 | closeOnClickOutside: { 69 | type: Boolean, 70 | required: false, 71 | default: true 72 | }, 73 | withDropdownCloser: { 74 | type: Boolean, 75 | required: false, 76 | default: false 77 | }, 78 | containerZIndex: { 79 | type: String, 80 | required: false, 81 | default: '994' 82 | }, 83 | overlay: { 84 | type: Boolean, 85 | required: false, 86 | default: true 87 | }, 88 | overlayBgColor: { 89 | type: String, 90 | required: false, 91 | default: 'rgba(0, 0, 0, 0.2)' 92 | }, 93 | overlayZIndex: { 94 | type: String, 95 | required: false, 96 | default: '992' 97 | }, 98 | transition: { 99 | type: String, 100 | required: false, 101 | default: 'default' 102 | } 103 | }, 104 | data() { 105 | return { 106 | baseClassName: 'v-dropdown-menu', 107 | menu: { 108 | isOpen: this.isOpen, 109 | mode: this.mode, 110 | dropup: this.dropup, 111 | direction: this.direction, 112 | closeOnClickOutside: this.closeOnClickOutside, 113 | withDropdownCloser: this.withDropdownCloser, 114 | containerZIndex: this.containerZIndex, 115 | overlay: this.overlay, 116 | overlayBgColor: this.overlayBgColor, 117 | overlayZIndex: this.overlayZIndex, 118 | transition: this.transition 119 | } 120 | }; 121 | }, 122 | computed: { 123 | activeClass() { 124 | return this.menu.isOpen ? `${this.baseClassName}--active` : null; 125 | }, 126 | modeClass() { 127 | return this.menu.mode === 'click' ? `${this.baseClassName}--mode-click` : `${this.baseClassName}--mode-hover`; 128 | }, 129 | dropupClass() { 130 | return this.menu.dropup ? `${this.baseClassName}--dropup` : null; 131 | }, 132 | directionClass() { 133 | let menuDirection = null; 134 | if (this.menu.direction === 'left') { 135 | menuDirection = `${this.baseClassName}--direction-left`; 136 | } else if (this.menu.direction === 'center') { 137 | menuDirection = `${this.baseClassName}--direction-center`; 138 | } else { 139 | menuDirection = `${this.baseClassName}--direction-right`; 140 | } 141 | return menuDirection; 142 | } 143 | }, 144 | watch: { 145 | isOpen(value) { 146 | if (this.menu.mode === 'click') { 147 | if (value) { 148 | setTimeout(() => { 149 | this.show(); 150 | }, 1); // wait, bypass for closeOnClickOutside 151 | } else { 152 | setTimeout(() => { 153 | this.hide(); 154 | }, 1); // wait, bypass for closeOnClickOutside 155 | } 156 | } 157 | }, 158 | 159 | 'menu.isOpen'(value) { 160 | if (this.menu.mode === 'click') { 161 | if (value) { 162 | this.$emit('opened', this.$props); 163 | } else { 164 | this.$emit('closed', this.$props); 165 | } 166 | } 167 | } 168 | }, 169 | mounted() { 170 | this.dropdownCloser(); 171 | this.$nextTick(() => { 172 | if (this.menu.closeOnClickOutside) { 173 | this.registerCloseDropdownOnClickOutside(); 174 | } 175 | }); 176 | this.closeDropdownOnPopState(); 177 | }, 178 | beforeDestroy() { 179 | this.destroyCloseDropdownOnClickOutside(); 180 | this.destroyCloseDropdownOnPopState(); 181 | }, 182 | methods: { 183 | show() { 184 | this.menu.isOpen = true; 185 | }, 186 | hide() { 187 | this.menu.isOpen = false; 188 | }, 189 | registerCloseDropdownOnClickOutside() { 190 | window.addEventListener('click', this.closeDropdownOnClickOutside); 191 | }, 192 | closeDropdownOnClickOutside(e) { 193 | if (this.menu.isOpen) { 194 | if (!this.$refs.rootRef.contains(e.target)) { 195 | this.menu.isOpen = false; 196 | } 197 | } 198 | }, 199 | destroyCloseDropdownOnClickOutside() { 200 | if (this.menu.closeOnClickOutside) { 201 | window.removeEventListener('click', this.closeDropdownOnClickOutside); 202 | } 203 | }, 204 | dropdownCloser() { 205 | if (this.menu.withDropdownCloser) { 206 | const dropdown = this.$refs.rootRef; 207 | dropdown.querySelectorAll('[dropdown-closer]').forEach(element => { 208 | element.addEventListener('click', () => { 209 | this.menu.isOpen = false; 210 | }); 211 | }); 212 | } 213 | }, 214 | closeDropdownOnPopState() { 215 | window.addEventListener('popstate', () => { 216 | if (this.menu.isOpen) { 217 | this.menu.isOpen = false; 218 | } 219 | }); 220 | }, 221 | destroyCloseDropdownOnPopState() { 222 | window.removeEventListener('popstate', this.closeDropdownOnPopState); 223 | } 224 | } 225 | }; 226 | 227 | function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) { 228 | if (typeof shadowMode !== 'boolean') { 229 | createInjectorSSR = createInjector; 230 | createInjector = shadowMode; 231 | shadowMode = false; 232 | } 233 | // Vue.extend constructor export interop. 234 | const options = typeof script === 'function' ? script.options : script; 235 | // render functions 236 | if (template && template.render) { 237 | options.render = template.render; 238 | options.staticRenderFns = template.staticRenderFns; 239 | options._compiled = true; 240 | // functional template 241 | if (isFunctionalTemplate) { 242 | options.functional = true; 243 | } 244 | } 245 | // scopedId 246 | if (scopeId) { 247 | options._scopeId = scopeId; 248 | } 249 | let hook; 250 | if (moduleIdentifier) { 251 | // server build 252 | hook = function (context) { 253 | // 2.3 injection 254 | context = 255 | context || // cached call 256 | (this.$vnode && this.$vnode.ssrContext) || // stateful 257 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional 258 | // 2.2 with runInNewContext: true 259 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { 260 | context = __VUE_SSR_CONTEXT__; 261 | } 262 | // inject component styles 263 | if (style) { 264 | style.call(this, createInjectorSSR(context)); 265 | } 266 | // register component module identifier for async chunk inference 267 | if (context && context._registeredComponents) { 268 | context._registeredComponents.add(moduleIdentifier); 269 | } 270 | }; 271 | // used by ssr in case component is cached and beforeCreate 272 | // never gets called 273 | options._ssrRegister = hook; 274 | } 275 | else if (style) { 276 | hook = shadowMode 277 | ? function (context) { 278 | style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot)); 279 | } 280 | : function (context) { 281 | style.call(this, createInjector(context)); 282 | }; 283 | } 284 | if (hook) { 285 | if (options.functional) { 286 | // register for functional component in vue file 287 | const originalRender = options.render; 288 | options.render = function renderWithStyleInjection(h, context) { 289 | hook.call(context); 290 | return originalRender(h, context); 291 | }; 292 | } 293 | else { 294 | // inject component registration as beforeCreate hook 295 | const existing = options.beforeCreate; 296 | options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; 297 | } 298 | } 299 | return script; 300 | } 301 | 302 | /* script */ 303 | const __vue_script__ = script; 304 | /* template */ 305 | var __vue_render__ = function () { 306 | var _vm = this; 307 | var _h = _vm.$createElement; 308 | var _c = _vm._self._c || _h; 309 | return _c('div', { 310 | ref: "rootRef", 311 | staticClass: "v-dropdown-menu", 312 | class: [_vm.activeClass, _vm.modeClass, _vm.dropupClass, _vm.directionClass] 313 | }, [_vm.menu.mode === 'click' ? [_c('div', { 314 | ref: "triggerRef", 315 | staticClass: "v-dropdown-menu__trigger", 316 | on: { 317 | "click": function ($event) { 318 | $event.preventDefault(); 319 | _vm.menu.isOpen = !_vm.menu.isOpen; 320 | } 321 | } 322 | }, [_vm._t("trigger")], 2), _c('transition', { 323 | attrs: { 324 | "name": _vm.menu.transition 325 | } 326 | }, [_c('div', { 327 | directives: [{ 328 | name: "show", 329 | rawName: "v-show", 330 | value: _vm.menu.isOpen, 331 | expression: "menu.isOpen" 332 | }], 333 | staticClass: "v-dropdown-menu__container", 334 | style: { 335 | 'z-index': _vm.menu.containerZIndex 336 | } 337 | }, [_c('div', { 338 | staticClass: "v-dropdown-menu__header" 339 | }, [_vm._t("header")], 2), _c('div', { 340 | staticClass: "v-dropdown-menu__body" 341 | }, [_vm._t("body")], 2), _c('div', { 342 | staticClass: "v-dropdown-menu__footer" 343 | }, [_vm._t("footer")], 2)])])] : _vm._e(), _vm.menu.mode === 'hover' ? [_c('div', { 344 | ref: "triggerRef", 345 | staticClass: "v-dropdown-menu__trigger", 346 | on: { 347 | "mouseover": function ($event) { 348 | $event.preventDefault(); 349 | return _vm.show.apply(null, arguments); 350 | }, 351 | "mouseleave": function ($event) { 352 | $event.preventDefault(); 353 | return _vm.hide.apply(null, arguments); 354 | } 355 | } 356 | }, [_vm._t("trigger")], 2), _c('transition', { 357 | attrs: { 358 | "name": _vm.menu.transition 359 | } 360 | }, [_c('div', { 361 | directives: [{ 362 | name: "show", 363 | rawName: "v-show", 364 | value: _vm.menu.isOpen, 365 | expression: "menu.isOpen" 366 | }], 367 | staticClass: "v-dropdown-menu__container", 368 | style: { 369 | 'z-index': _vm.menu.containerZIndex 370 | }, 371 | on: { 372 | "mouseover": function ($event) { 373 | $event.preventDefault(); 374 | return _vm.show.apply(null, arguments); 375 | }, 376 | "mouseleave": function ($event) { 377 | $event.preventDefault(); 378 | return _vm.hide.apply(null, arguments); 379 | } 380 | } 381 | }, [_c('div', { 382 | staticClass: "v-dropdown-menu__header" 383 | }, [_vm._t("header")], 2), _c('div', { 384 | staticClass: "v-dropdown-menu__body" 385 | }, [_vm._t("body")], 2), _c('div', { 386 | staticClass: "v-dropdown-menu__footer" 387 | }, [_vm._t("footer")], 2)])])] : _vm._e(), _vm.menu.overlay && _vm.menu.closeOnClickOutside && _vm.menu.mode === 'click' ? _c('div', { 388 | directives: [{ 389 | name: "show", 390 | rawName: "v-show", 391 | value: _vm.menu.isOpen, 392 | expression: "menu.isOpen" 393 | }], 394 | ref: "overlayRef", 395 | staticClass: "v-dropdown-menu__overlay", 396 | style: { 397 | 'background-color': _vm.menu.overlayBgColor, 398 | 'z-index': _vm.menu.overlayZIndex 399 | }, 400 | on: { 401 | "mousedown": function ($event) { 402 | $event.preventDefault(); 403 | return _vm.hide.apply(null, arguments); 404 | } 405 | } 406 | }) : _vm._e()], 2); 407 | }; 408 | var __vue_staticRenderFns__ = []; 409 | 410 | /* style */ 411 | const __vue_inject_styles__ = undefined; 412 | /* scoped */ 413 | const __vue_scope_id__ = undefined; 414 | /* module identifier */ 415 | const __vue_module_identifier__ = undefined; 416 | /* functional template */ 417 | const __vue_is_functional_template__ = false; 418 | /* style inject */ 419 | 420 | /* style inject SSR */ 421 | 422 | /* style inject shadow dom */ 423 | 424 | const __vue_component__ = /*#__PURE__*/normalizeComponent({ 425 | render: __vue_render__, 426 | staticRenderFns: __vue_staticRenderFns__ 427 | }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, false, undefined, undefined, undefined); 428 | 429 | // Import vue component 430 | 431 | // install function executed by Vue.use() 432 | const install = function installDropdownMenu(Vue) { 433 | if (install.installed) return; 434 | install.installed = true; 435 | Vue.component('DropdownMenu', __vue_component__); 436 | }; 437 | 438 | // Inject install function into component - allows component 439 | // to be registered via Vue.use() as well as Vue.component() 440 | __vue_component__.install = install; 441 | 442 | // It's possible to expose named exports when writing components that can 443 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 444 | // export const RollupDemoDirective = component; 445 | 446 | export { __vue_component__ as default }; 447 | -------------------------------------------------------------------------------- /dist/vue2/v-dropdown-menu.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu={})}(this,(function(e){"use strict";function n(e,n,t,o,i,s,r,d,l,a){"boolean"!=typeof r&&(l=d,d=r,r=!1);const u="function"==typeof t?t.options:t;let p;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,i&&(u.functional=!0)),o&&(u._scopeId=o),s?(p=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(s)},u._ssrRegister=p):n&&(p=r?function(e){n.call(this,a(e,this.$root.$options.shadowRoot))}:function(e){n.call(this,d(e))}),p)if(u.functional){const e=u.render;u.render=function(n,t){return p.call(t),e(n,t)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,p):[p]}return t}const t=n({render:function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{click:function(n){n.preventDefault(),e.menu.isOpen=!e.menu.isOpen}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],ref:"overlayRef",staticClass:"v-dropdown-menu__overlay",style:{"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex},on:{mousedown:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}}):e._e()],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass(){return this.menu.isOpen?`${this.baseClassName}--active`:null},modeClass(){return"click"===this.menu.mode?`${this.baseClassName}--mode-click`:`${this.baseClassName}--mode-hover`},dropupClass(){return this.menu.dropup?`${this.baseClassName}--dropup`:null},directionClass(){let e=null;return e="left"===this.menu.direction?`${this.baseClassName}--direction-left`:"center"===this.menu.direction?`${this.baseClassName}--direction-center`:`${this.baseClassName}--direction-right`,e}},watch:{isOpen(e){"click"===this.menu.mode&&(e?setTimeout((()=>{this.show()}),1):setTimeout((()=>{this.hide()}),1))},"menu.isOpen"(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted(){this.dropdownCloser(),this.$nextTick((()=>{this.menu.closeOnClickOutside&&this.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show(){this.menu.isOpen=!0},hide(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser(){if(this.menu.withDropdownCloser){this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{this.menu.isOpen=!1}))}))}},closeDropdownOnPopState(){window.addEventListener("popstate",(()=>{this.menu.isOpen&&(this.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,undefined,!1,void 0,void 0,void 0),o=function(e){o.installed||(o.installed=!0,e.component("DropdownMenu",t))},i={install:o};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(i)}t.install=o,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /dist/vue3/index.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],o):o((e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu={},e.Vue)}(this,(function(e,o){"use strict";var n=o.defineComponent({name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},setup(e,n){let{emit:t}=n;const r="v-dropdown-menu",i=o.ref(null),d=o.ref(null),l=o.ref(null),s=o.reactive({isOpen:e.isOpen,mode:e.mode,dropup:e.dropup,direction:e.direction,closeOnClickOutside:e.closeOnClickOutside,withDropdownCloser:e.withDropdownCloser,containerZIndex:e.containerZIndex,overlay:e.overlay,overlayBgColor:e.overlayBgColor,overlayZIndex:e.overlayZIndex,transition:e.transition}),a=o.computed((()=>s.isOpen?`${r}--active`:null)),c=o.computed((()=>"click"===s.mode?`${r}--mode-click`:`${r}--mode-hover`)),u=o.computed((()=>s.dropup?`${r}--dropup`:null)),p=o.computed((()=>{let e=null;return e="left"===s.direction?`${r}--direction-left`:"center"===s.direction?`${r}--direction-center`:`${r}--direction-right`,e}));o.watch((()=>e.isOpen),(e=>{"click"===s.mode&&(e?setTimeout((()=>{m()}),1):setTimeout((()=>{v()}),1))})),o.watch((()=>s.isOpen),(o=>{"click"===s.mode&&t(o?"opened":"closed",e)})),o.onMounted((()=>{h(),o.nextTick((()=>{s.closeOnClickOutside&&f()})),g()})),o.onBeforeUnmount((()=>{y(),O()}));const m=()=>{s.isOpen=!0},v=()=>{s.isOpen=!1},f=()=>{window.addEventListener("click",w)},w=e=>{s.isOpen&&(i.value.contains(e.target)||(s.isOpen=!1))},y=()=>{s.closeOnClickOutside&&window.removeEventListener("click",w)},h=()=>{if(s.withDropdownCloser){i.value.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{s.isOpen=!1}))}))}},g=()=>{window.addEventListener("popstate",(()=>{s.isOpen&&(s.isOpen=!1)}))},O=()=>{window.removeEventListener("popstate",g)};return{rootRef:i,triggerRef:d,overlayRef:l,menu:s,show:m,hide:v,activeClass:a,modeClass:c,dropupClass:u,directionClass:p}}});const t={class:"v-dropdown-menu__header"},r={class:"v-dropdown-menu__body"},i={class:"v-dropdown-menu__footer"},d={class:"v-dropdown-menu__header"},l={class:"v-dropdown-menu__body"},s={class:"v-dropdown-menu__footer"};n.render=function(e,n,a,c,u,p){return o.openBlock(),o.createElementBlock("div",{class:o.normalizeClass(["v-dropdown-menu",[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]]),ref:"rootRef"},["click"===e.menu.mode?(o.openBlock(),o.createElementBlock(o.Fragment,{key:0},[o.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onClick:n[0]||(n[0]=o.withModifiers((o=>e.menu.isOpen=!e.menu.isOpen),["prevent"]))},[o.renderSlot(e.$slots,"trigger")],512),o.createVNode(o.Transition,{mode:"out-in",name:e.menu.transition},{default:o.withCtx((()=>[o.withDirectives(o.createElementVNode("div",{class:"v-dropdown-menu__container",style:o.normalizeStyle({"z-index":e.menu.containerZIndex})},[o.createElementVNode("div",t,[o.renderSlot(e.$slots,"header")]),o.createElementVNode("div",r,[o.renderSlot(e.$slots,"body")]),o.createElementVNode("div",i,[o.renderSlot(e.$slots,"footer")])],4),[[o.vShow,e.menu.isOpen]])])),_:3},8,["name"])],64)):o.createCommentVNode("",!0),"hover"===e.menu.mode?(o.openBlock(),o.createElementBlock(o.Fragment,{key:1},[o.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onMouseover:n[1]||(n[1]=o.withModifiers((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[2]||(n[2]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[o.renderSlot(e.$slots,"trigger")],544),o.createVNode(o.Transition,{name:e.menu.transition},{default:o.withCtx((()=>[o.withDirectives(o.createElementVNode("div",{class:"v-dropdown-menu__container",style:o.normalizeStyle({"z-index":e.menu.containerZIndex}),onMouseover:n[3]||(n[3]=o.withModifiers((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[4]||(n[4]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[o.createElementVNode("div",d,[o.renderSlot(e.$slots,"header")]),o.createElementVNode("div",l,[o.renderSlot(e.$slots,"body")]),o.createElementVNode("div",s,[o.renderSlot(e.$slots,"footer")])],36),[[o.vShow,e.menu.isOpen]])])),_:3},8,["name"])],64)):o.createCommentVNode("",!0),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?o.withDirectives((o.openBlock(),o.createElementBlock("div",{key:2,class:"v-dropdown-menu__overlay",ref:"overlayRef",style:o.normalizeStyle({"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex}),onMousedown:n[5]||(n[5]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},null,36)),[[o.vShow,e.menu.isOpen]]):o.createCommentVNode("",!0)],2)};var a=(()=>{const e=n;return e.install=o=>{o.component("DropdownMenu",e)},e})();e.default=a,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.cjs: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var vue=require('vue');var script = vue.defineComponent({ 2 | name: 'DropdownMenu', 3 | props: { 4 | isOpen: { 5 | type: Boolean, 6 | required: false, 7 | default: false 8 | }, 9 | mode: { 10 | type: String, 11 | required: false, 12 | default: 'click' 13 | }, 14 | dropup: { 15 | type: Boolean, 16 | required: false, 17 | default: false 18 | }, 19 | direction: { 20 | type: String, 21 | required: false, 22 | default: 'left' 23 | }, 24 | closeOnClickOutside: { 25 | type: Boolean, 26 | required: false, 27 | default: true 28 | }, 29 | withDropdownCloser: { 30 | type: Boolean, 31 | required: false, 32 | default: false 33 | }, 34 | containerZIndex: { 35 | type: String, 36 | required: false, 37 | default: '994' 38 | }, 39 | overlay: { 40 | type: Boolean, 41 | required: false, 42 | default: true 43 | }, 44 | overlayBgColor: { 45 | type: String, 46 | required: false, 47 | default: 'rgba(0, 0, 0, 0.2)' 48 | }, 49 | overlayZIndex: { 50 | type: String, 51 | required: false, 52 | default: '992' 53 | }, 54 | transition: { 55 | type: String, 56 | required: false, 57 | default: 'default' 58 | } 59 | }, 60 | setup: function setup(props, _ref) { 61 | var emit = _ref.emit; 62 | var baseClassName = 'v-dropdown-menu'; 63 | var rootRef = vue.ref(null); 64 | var triggerRef = vue.ref(null); 65 | var overlayRef = vue.ref(null); 66 | var menu = vue.reactive({ 67 | isOpen: props.isOpen, 68 | mode: props.mode, 69 | dropup: props.dropup, 70 | direction: props.direction, 71 | closeOnClickOutside: props.closeOnClickOutside, 72 | withDropdownCloser: props.withDropdownCloser, 73 | containerZIndex: props.containerZIndex, 74 | overlay: props.overlay, 75 | overlayBgColor: props.overlayBgColor, 76 | overlayZIndex: props.overlayZIndex, 77 | transition: props.transition 78 | }); 79 | var activeClass = vue.computed(function () { 80 | return menu.isOpen ? "".concat(baseClassName, "--active") : null; 81 | }); 82 | var modeClass = vue.computed(function () { 83 | return menu.mode === 'click' ? "".concat(baseClassName, "--mode-click") : "".concat(baseClassName, "--mode-hover"); 84 | }); 85 | var dropupClass = vue.computed(function () { 86 | return menu.dropup ? "".concat(baseClassName, "--dropup") : null; 87 | }); 88 | var directionClass = vue.computed(function () { 89 | var menuDirection = null; 90 | if (menu.direction === 'left') { 91 | menuDirection = "".concat(baseClassName, "--direction-left"); 92 | } else if (menu.direction === 'center') { 93 | menuDirection = "".concat(baseClassName, "--direction-center"); 94 | } else { 95 | menuDirection = "".concat(baseClassName, "--direction-right"); 96 | } 97 | return menuDirection; 98 | }); 99 | vue.watch(function () { 100 | return props.isOpen; 101 | }, function (value) { 102 | if (menu.mode === 'click') { 103 | if (value) { 104 | setTimeout(function () { 105 | show(); 106 | }, 1); // wait, bypass for closeOnClickOutside 107 | } else { 108 | setTimeout(function () { 109 | hide(); 110 | }, 1); // wait, bypass for closeOnClickOutside 111 | } 112 | } 113 | }); 114 | 115 | vue.watch(function () { 116 | return menu.isOpen; 117 | }, function (value) { 118 | if (menu.mode === 'click') { 119 | if (value) { 120 | emit('opened', props); 121 | } else { 122 | emit('closed', props); 123 | } 124 | } 125 | }); 126 | vue.onMounted(function () { 127 | dropdownCloser(); 128 | vue.nextTick(function () { 129 | if (menu.closeOnClickOutside) { 130 | registerCloseDropdownOnClickOutside(); 131 | } 132 | }); 133 | closeDropdownOnPopState(); 134 | }); 135 | vue.onBeforeUnmount(function () { 136 | destroyCloseDropdownOnClickOutside(); 137 | destroyCloseDropdownOnPopState(); 138 | }); 139 | 140 | // Methods 141 | var show = function show() { 142 | menu.isOpen = true; 143 | }; 144 | var hide = function hide() { 145 | menu.isOpen = false; 146 | }; 147 | var registerCloseDropdownOnClickOutside = function registerCloseDropdownOnClickOutside() { 148 | window.addEventListener('click', closeDropdownOnClickOutside); 149 | }; 150 | var closeDropdownOnClickOutside = function closeDropdownOnClickOutside(e) { 151 | if (menu.isOpen) { 152 | if (!rootRef.value.contains(e.target)) { 153 | menu.isOpen = false; 154 | } 155 | } 156 | }; 157 | var destroyCloseDropdownOnClickOutside = function destroyCloseDropdownOnClickOutside() { 158 | if (menu.closeOnClickOutside) { 159 | window.removeEventListener('click', closeDropdownOnClickOutside); 160 | } 161 | }; 162 | var dropdownCloser = function dropdownCloser() { 163 | if (menu.withDropdownCloser) { 164 | var dropdown = rootRef.value; 165 | dropdown.querySelectorAll('[dropdown-closer]').forEach(function (element) { 166 | element.addEventListener('click', function () { 167 | menu.isOpen = false; 168 | }); 169 | }); 170 | } 171 | }; 172 | var closeDropdownOnPopState = function closeDropdownOnPopState() { 173 | window.addEventListener('popstate', function () { 174 | if (menu.isOpen) { 175 | menu.isOpen = false; 176 | } 177 | }); 178 | }; 179 | var destroyCloseDropdownOnPopState = function destroyCloseDropdownOnPopState() { 180 | window.removeEventListener('popstate', closeDropdownOnPopState); 181 | }; 182 | return { 183 | rootRef: rootRef, 184 | triggerRef: triggerRef, 185 | overlayRef: overlayRef, 186 | menu: menu, 187 | show: show, 188 | hide: hide, 189 | activeClass: activeClass, 190 | modeClass: modeClass, 191 | dropupClass: dropupClass, 192 | directionClass: directionClass 193 | }; 194 | } 195 | });var _hoisted_1 = { 196 | class: "v-dropdown-menu__header" 197 | }; 198 | var _hoisted_2 = { 199 | class: "v-dropdown-menu__body" 200 | }; 201 | var _hoisted_3 = { 202 | class: "v-dropdown-menu__footer" 203 | }; 204 | var _hoisted_4 = { 205 | class: "v-dropdown-menu__header" 206 | }; 207 | var _hoisted_5 = { 208 | class: "v-dropdown-menu__body" 209 | }; 210 | var _hoisted_6 = { 211 | class: "v-dropdown-menu__footer" 212 | }; 213 | function render(_ctx, _cache, $props, $setup, $data, $options) { 214 | return vue.openBlock(), vue.createElementBlock("div", { 215 | class: vue.normalizeClass(["v-dropdown-menu", [_ctx.activeClass, _ctx.modeClass, _ctx.dropupClass, _ctx.directionClass]]), 216 | ref: "rootRef" 217 | }, [_ctx.menu.mode === 'click' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 218 | key: 0 219 | }, [vue.createElementVNode("div", { 220 | class: "v-dropdown-menu__trigger", 221 | ref: "triggerRef", 222 | onClick: _cache[0] || (_cache[0] = vue.withModifiers(function ($event) { 223 | return _ctx.menu.isOpen = !_ctx.menu.isOpen; 224 | }, ["prevent"])) 225 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 512), vue.createVNode(vue.Transition, { 226 | mode: "out-in", 227 | name: _ctx.menu.transition 228 | }, { 229 | default: vue.withCtx(function () { 230 | return [vue.withDirectives(vue.createElementVNode("div", { 231 | class: "v-dropdown-menu__container", 232 | style: vue.normalizeStyle({ 233 | 'z-index': _ctx.menu.containerZIndex 234 | }) 235 | }, [vue.createElementVNode("div", _hoisted_1, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_2, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_3, [vue.renderSlot(_ctx.$slots, "footer")])], 4), [[vue.vShow, _ctx.menu.isOpen]])]; 236 | }), 237 | _: 3 238 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.mode === 'hover' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 239 | key: 1 240 | }, [vue.createElementVNode("div", { 241 | class: "v-dropdown-menu__trigger", 242 | ref: "triggerRef", 243 | onMouseover: _cache[1] || (_cache[1] = vue.withModifiers(function () { 244 | return _ctx.show && _ctx.show.apply(_ctx, arguments); 245 | }, ["prevent"])), 246 | onMouseleave: _cache[2] || (_cache[2] = vue.withModifiers(function () { 247 | return _ctx.hide && _ctx.hide.apply(_ctx, arguments); 248 | }, ["prevent"])) 249 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 544), vue.createVNode(vue.Transition, { 250 | name: _ctx.menu.transition 251 | }, { 252 | default: vue.withCtx(function () { 253 | return [vue.withDirectives(vue.createElementVNode("div", { 254 | class: "v-dropdown-menu__container", 255 | style: vue.normalizeStyle({ 256 | 'z-index': _ctx.menu.containerZIndex 257 | }), 258 | onMouseover: _cache[3] || (_cache[3] = vue.withModifiers(function () { 259 | return _ctx.show && _ctx.show.apply(_ctx, arguments); 260 | }, ["prevent"])), 261 | onMouseleave: _cache[4] || (_cache[4] = vue.withModifiers(function () { 262 | return _ctx.hide && _ctx.hide.apply(_ctx, arguments); 263 | }, ["prevent"])) 264 | }, [vue.createElementVNode("div", _hoisted_4, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_5, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_6, [vue.renderSlot(_ctx.$slots, "footer")])], 36), [[vue.vShow, _ctx.menu.isOpen]])]; 265 | }), 266 | _: 3 267 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.overlay && _ctx.menu.closeOnClickOutside && _ctx.menu.mode === 'click' ? vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", { 268 | key: 2, 269 | class: "v-dropdown-menu__overlay", 270 | ref: "overlayRef", 271 | style: vue.normalizeStyle({ 272 | 'background-color': _ctx.menu.overlayBgColor, 273 | 'z-index': _ctx.menu.overlayZIndex 274 | }), 275 | onMousedown: _cache[5] || (_cache[5] = vue.withModifiers(function () { 276 | return _ctx.hide && _ctx.hide.apply(_ctx, arguments); 277 | }, ["prevent"])) 278 | }, null, 36)), [[vue.vShow, _ctx.menu.isOpen]]) : vue.createCommentVNode("", true)], 2); 279 | }script.render = render;// Import vue component 280 | 281 | // Default export is installable instance of component. 282 | // IIFE injects install function into component, allowing component 283 | // to be registered via Vue.use() as well as Vue.component(), 284 | var entry = /*#__PURE__*/(function () { 285 | // Assign InstallableComponent type 286 | var installable = script; 287 | 288 | // Attach install function executed by Vue.use() 289 | installable.install = function (app) { 290 | app.component('DropdownMenu', installable); 291 | }; 292 | return installable; 293 | })(); 294 | 295 | // It's possible to expose named exports when writing components that can 296 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 297 | // export const RollupDemoDirective = directive; 298 | exports["default"]=entry; -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.css: -------------------------------------------------------------------------------- 1 | .v-dropdown-menu{position:relative;display:inline-block}.v-dropdown-menu__trigger{position:relative}.v-dropdown-menu__container{position:absolute;top:100%;bottom:auto;min-width:230px;max-width:100%;overflow:hidden;background-color:#fff;border:1px solid #ddd}.v-dropdown-menu--dropup .v-dropdown-menu__container{top:auto;bottom:100%}.v-dropdown-menu--direction-left .v-dropdown-menu__container{left:0}.v-dropdown-menu--direction-center .v-dropdown-menu__container{left:50%;transform:translateX(-50%) translateY(0)}.v-dropdown-menu--direction-right .v-dropdown-menu__container{right:0}.v-dropdown-menu__overlay{position:fixed;top:0;left:0;width:100%;height:100vh}.v-dropdown-menu .default-enter-active{transition:all .2s ease}.v-dropdown-menu .default-leave-active{transition:all .2s cubic-bezier(1, 0.5, 0.8, 1)}.v-dropdown-menu .default-enter-from,.v-dropdown-menu .default-leave-to{transform:translateY(12px);opacity:0}.v-dropdown-menu--mode-hover .default-enter-from,.v-dropdown-menu--mode-hover .default-leave-active{transition-delay:.4s}.v-dropdown-menu--dropup .default-enter-from,.v-dropdown-menu--dropup .default-leave-to{transform:translateY(-12px)}.v-dropdown-menu--dropup.v-dropdown-menu--direction-center .default-enter-from,.v-dropdown-menu--dropup.v-dropdown-menu--direction-center .default-leave-to{transform:translateX(-50%) translateY(-12px)}.v-dropdown-menu--direction-center .default-enter-from,.v-dropdown-menu--direction-center .default-leave-to{transform:translateX(-50%) translateY(12px)} -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.global.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue')) : 3 | typeof define === 'function' && define.amd ? define(['vue'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DropdownMenu = factory(global.Vue)); 5 | })(this, (function (vue) { 'use strict'; 6 | 7 | var script = vue.defineComponent({ 8 | name: 'DropdownMenu', 9 | props: { 10 | isOpen: { 11 | type: Boolean, 12 | required: false, 13 | default: false 14 | }, 15 | mode: { 16 | type: String, 17 | required: false, 18 | default: 'click' 19 | }, 20 | dropup: { 21 | type: Boolean, 22 | required: false, 23 | default: false 24 | }, 25 | direction: { 26 | type: String, 27 | required: false, 28 | default: 'left' 29 | }, 30 | closeOnClickOutside: { 31 | type: Boolean, 32 | required: false, 33 | default: true 34 | }, 35 | withDropdownCloser: { 36 | type: Boolean, 37 | required: false, 38 | default: false 39 | }, 40 | containerZIndex: { 41 | type: String, 42 | required: false, 43 | default: '994' 44 | }, 45 | overlay: { 46 | type: Boolean, 47 | required: false, 48 | default: true 49 | }, 50 | overlayBgColor: { 51 | type: String, 52 | required: false, 53 | default: 'rgba(0, 0, 0, 0.2)' 54 | }, 55 | overlayZIndex: { 56 | type: String, 57 | required: false, 58 | default: '992' 59 | }, 60 | transition: { 61 | type: String, 62 | required: false, 63 | default: 'default' 64 | } 65 | }, 66 | setup(props, _ref) { 67 | let { 68 | emit 69 | } = _ref; 70 | const baseClassName = 'v-dropdown-menu'; 71 | const rootRef = vue.ref(null); 72 | const triggerRef = vue.ref(null); 73 | const overlayRef = vue.ref(null); 74 | const menu = vue.reactive({ 75 | isOpen: props.isOpen, 76 | mode: props.mode, 77 | dropup: props.dropup, 78 | direction: props.direction, 79 | closeOnClickOutside: props.closeOnClickOutside, 80 | withDropdownCloser: props.withDropdownCloser, 81 | containerZIndex: props.containerZIndex, 82 | overlay: props.overlay, 83 | overlayBgColor: props.overlayBgColor, 84 | overlayZIndex: props.overlayZIndex, 85 | transition: props.transition 86 | }); 87 | const activeClass = vue.computed(() => { 88 | return menu.isOpen ? `${baseClassName}--active` : null; 89 | }); 90 | const modeClass = vue.computed(() => { 91 | return menu.mode === 'click' ? `${baseClassName}--mode-click` : `${baseClassName}--mode-hover`; 92 | }); 93 | const dropupClass = vue.computed(() => { 94 | return menu.dropup ? `${baseClassName}--dropup` : null; 95 | }); 96 | const directionClass = vue.computed(() => { 97 | let menuDirection = null; 98 | if (menu.direction === 'left') { 99 | menuDirection = `${baseClassName}--direction-left`; 100 | } else if (menu.direction === 'center') { 101 | menuDirection = `${baseClassName}--direction-center`; 102 | } else { 103 | menuDirection = `${baseClassName}--direction-right`; 104 | } 105 | return menuDirection; 106 | }); 107 | vue.watch(() => props.isOpen, value => { 108 | if (menu.mode === 'click') { 109 | if (value) { 110 | setTimeout(() => { 111 | show(); 112 | }, 1); // wait, bypass for closeOnClickOutside 113 | } else { 114 | setTimeout(() => { 115 | hide(); 116 | }, 1); // wait, bypass for closeOnClickOutside 117 | } 118 | } 119 | }); 120 | 121 | vue.watch(() => menu.isOpen, value => { 122 | if (menu.mode === 'click') { 123 | if (value) { 124 | emit('opened', props); 125 | } else { 126 | emit('closed', props); 127 | } 128 | } 129 | }); 130 | vue.onMounted(() => { 131 | dropdownCloser(); 132 | vue.nextTick(() => { 133 | if (menu.closeOnClickOutside) { 134 | registerCloseDropdownOnClickOutside(); 135 | } 136 | }); 137 | closeDropdownOnPopState(); 138 | }); 139 | vue.onBeforeUnmount(() => { 140 | destroyCloseDropdownOnClickOutside(); 141 | destroyCloseDropdownOnPopState(); 142 | }); 143 | 144 | // Methods 145 | const show = () => { 146 | menu.isOpen = true; 147 | }; 148 | const hide = () => { 149 | menu.isOpen = false; 150 | }; 151 | const registerCloseDropdownOnClickOutside = () => { 152 | window.addEventListener('click', closeDropdownOnClickOutside); 153 | }; 154 | const closeDropdownOnClickOutside = e => { 155 | if (menu.isOpen) { 156 | if (!rootRef.value.contains(e.target)) { 157 | menu.isOpen = false; 158 | } 159 | } 160 | }; 161 | const destroyCloseDropdownOnClickOutside = () => { 162 | if (menu.closeOnClickOutside) { 163 | window.removeEventListener('click', closeDropdownOnClickOutside); 164 | } 165 | }; 166 | const dropdownCloser = () => { 167 | if (menu.withDropdownCloser) { 168 | const dropdown = rootRef.value; 169 | dropdown.querySelectorAll('[dropdown-closer]').forEach(element => { 170 | element.addEventListener('click', () => { 171 | menu.isOpen = false; 172 | }); 173 | }); 174 | } 175 | }; 176 | const closeDropdownOnPopState = () => { 177 | window.addEventListener('popstate', () => { 178 | if (menu.isOpen) { 179 | menu.isOpen = false; 180 | } 181 | }); 182 | }; 183 | const destroyCloseDropdownOnPopState = () => { 184 | window.removeEventListener('popstate', closeDropdownOnPopState); 185 | }; 186 | return { 187 | rootRef, 188 | triggerRef, 189 | overlayRef, 190 | menu, 191 | show, 192 | hide, 193 | activeClass, 194 | modeClass, 195 | dropupClass, 196 | directionClass 197 | }; 198 | } 199 | }); 200 | 201 | const _hoisted_1 = { 202 | class: "v-dropdown-menu__header" 203 | }; 204 | const _hoisted_2 = { 205 | class: "v-dropdown-menu__body" 206 | }; 207 | const _hoisted_3 = { 208 | class: "v-dropdown-menu__footer" 209 | }; 210 | const _hoisted_4 = { 211 | class: "v-dropdown-menu__header" 212 | }; 213 | const _hoisted_5 = { 214 | class: "v-dropdown-menu__body" 215 | }; 216 | const _hoisted_6 = { 217 | class: "v-dropdown-menu__footer" 218 | }; 219 | function render(_ctx, _cache, $props, $setup, $data, $options) { 220 | return vue.openBlock(), vue.createElementBlock("div", { 221 | class: vue.normalizeClass(["v-dropdown-menu", [_ctx.activeClass, _ctx.modeClass, _ctx.dropupClass, _ctx.directionClass]]), 222 | ref: "rootRef" 223 | }, [_ctx.menu.mode === 'click' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 224 | key: 0 225 | }, [vue.createElementVNode("div", { 226 | class: "v-dropdown-menu__trigger", 227 | ref: "triggerRef", 228 | onClick: _cache[0] || (_cache[0] = vue.withModifiers($event => _ctx.menu.isOpen = !_ctx.menu.isOpen, ["prevent"])) 229 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 512), vue.createVNode(vue.Transition, { 230 | mode: "out-in", 231 | name: _ctx.menu.transition 232 | }, { 233 | default: vue.withCtx(() => [vue.withDirectives(vue.createElementVNode("div", { 234 | class: "v-dropdown-menu__container", 235 | style: vue.normalizeStyle({ 236 | 'z-index': _ctx.menu.containerZIndex 237 | }) 238 | }, [vue.createElementVNode("div", _hoisted_1, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_2, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_3, [vue.renderSlot(_ctx.$slots, "footer")])], 4), [[vue.vShow, _ctx.menu.isOpen]])]), 239 | _: 3 240 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.mode === 'hover' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 241 | key: 1 242 | }, [vue.createElementVNode("div", { 243 | class: "v-dropdown-menu__trigger", 244 | ref: "triggerRef", 245 | onMouseover: _cache[1] || (_cache[1] = vue.withModifiers(function () { 246 | return _ctx.show && _ctx.show(...arguments); 247 | }, ["prevent"])), 248 | onMouseleave: _cache[2] || (_cache[2] = vue.withModifiers(function () { 249 | return _ctx.hide && _ctx.hide(...arguments); 250 | }, ["prevent"])) 251 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 544), vue.createVNode(vue.Transition, { 252 | name: _ctx.menu.transition 253 | }, { 254 | default: vue.withCtx(() => [vue.withDirectives(vue.createElementVNode("div", { 255 | class: "v-dropdown-menu__container", 256 | style: vue.normalizeStyle({ 257 | 'z-index': _ctx.menu.containerZIndex 258 | }), 259 | onMouseover: _cache[3] || (_cache[3] = vue.withModifiers(function () { 260 | return _ctx.show && _ctx.show(...arguments); 261 | }, ["prevent"])), 262 | onMouseleave: _cache[4] || (_cache[4] = vue.withModifiers(function () { 263 | return _ctx.hide && _ctx.hide(...arguments); 264 | }, ["prevent"])) 265 | }, [vue.createElementVNode("div", _hoisted_4, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_5, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_6, [vue.renderSlot(_ctx.$slots, "footer")])], 36), [[vue.vShow, _ctx.menu.isOpen]])]), 266 | _: 3 267 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.overlay && _ctx.menu.closeOnClickOutside && _ctx.menu.mode === 'click' ? vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", { 268 | key: 2, 269 | class: "v-dropdown-menu__overlay", 270 | ref: "overlayRef", 271 | style: vue.normalizeStyle({ 272 | 'background-color': _ctx.menu.overlayBgColor, 273 | 'z-index': _ctx.menu.overlayZIndex 274 | }), 275 | onMousedown: _cache[5] || (_cache[5] = vue.withModifiers(function () { 276 | return _ctx.hide && _ctx.hide(...arguments); 277 | }, ["prevent"])) 278 | }, null, 36)), [[vue.vShow, _ctx.menu.isOpen]]) : vue.createCommentVNode("", true)], 2); 279 | } 280 | 281 | script.render = render; 282 | 283 | // Import vue component 284 | 285 | // Default export is installable instance of component. 286 | // IIFE injects install function into component, allowing component 287 | // to be registered via Vue.use() as well as Vue.component(), 288 | var entry = /*#__PURE__*/(() => { 289 | // Assign InstallableComponent type 290 | const installable = script; 291 | 292 | // Attach install function executed by Vue.use() 293 | installable.install = app => { 294 | app.component('DropdownMenu', installable); 295 | }; 296 | return installable; 297 | })(); 298 | 299 | // It's possible to expose named exports when writing components that can 300 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 301 | // export const RollupDemoDirective = directive; 302 | 303 | return entry; 304 | 305 | })); 306 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.global.min.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o(require("vue")):"function"==typeof define&&define.amd?define(["vue"],o):(e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu=o(e.Vue)}(this,(function(e){"use strict";var o=e.defineComponent({name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},setup(o,n){let{emit:t}=n;const r="v-dropdown-menu",i=e.ref(null),d=e.ref(null),l=e.ref(null),s=e.reactive({isOpen:o.isOpen,mode:o.mode,dropup:o.dropup,direction:o.direction,closeOnClickOutside:o.closeOnClickOutside,withDropdownCloser:o.withDropdownCloser,containerZIndex:o.containerZIndex,overlay:o.overlay,overlayBgColor:o.overlayBgColor,overlayZIndex:o.overlayZIndex,transition:o.transition}),a=e.computed((()=>s.isOpen?`${r}--active`:null)),c=e.computed((()=>"click"===s.mode?`${r}--mode-click`:`${r}--mode-hover`)),u=e.computed((()=>s.dropup?`${r}--dropup`:null)),p=e.computed((()=>{let e=null;return e="left"===s.direction?`${r}--direction-left`:"center"===s.direction?`${r}--direction-center`:`${r}--direction-right`,e}));e.watch((()=>o.isOpen),(e=>{"click"===s.mode&&(e?setTimeout((()=>{m()}),1):setTimeout((()=>{v()}),1))})),e.watch((()=>s.isOpen),(e=>{"click"===s.mode&&t(e?"opened":"closed",o)})),e.onMounted((()=>{h(),e.nextTick((()=>{s.closeOnClickOutside&&f()})),g()})),e.onBeforeUnmount((()=>{y(),k()}));const m=()=>{s.isOpen=!0},v=()=>{s.isOpen=!1},f=()=>{window.addEventListener("click",w)},w=e=>{s.isOpen&&(i.value.contains(e.target)||(s.isOpen=!1))},y=()=>{s.closeOnClickOutside&&window.removeEventListener("click",w)},h=()=>{if(s.withDropdownCloser){i.value.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{s.isOpen=!1}))}))}},g=()=>{window.addEventListener("popstate",(()=>{s.isOpen&&(s.isOpen=!1)}))},k=()=>{window.removeEventListener("popstate",g)};return{rootRef:i,triggerRef:d,overlayRef:l,menu:s,show:m,hide:v,activeClass:a,modeClass:c,dropupClass:u,directionClass:p}}});const n={class:"v-dropdown-menu__header"},t={class:"v-dropdown-menu__body"},r={class:"v-dropdown-menu__footer"},i={class:"v-dropdown-menu__header"},d={class:"v-dropdown-menu__body"},l={class:"v-dropdown-menu__footer"};return o.render=function(o,s,a,c,u,p){return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["v-dropdown-menu",[o.activeClass,o.modeClass,o.dropupClass,o.directionClass]]),ref:"rootRef"},["click"===o.menu.mode?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onClick:s[0]||(s[0]=e.withModifiers((e=>o.menu.isOpen=!o.menu.isOpen),["prevent"]))},[e.renderSlot(o.$slots,"trigger")],512),e.createVNode(e.Transition,{mode:"out-in",name:o.menu.transition},{default:e.withCtx((()=>[e.withDirectives(e.createElementVNode("div",{class:"v-dropdown-menu__container",style:e.normalizeStyle({"z-index":o.menu.containerZIndex})},[e.createElementVNode("div",n,[e.renderSlot(o.$slots,"header")]),e.createElementVNode("div",t,[e.renderSlot(o.$slots,"body")]),e.createElementVNode("div",r,[e.renderSlot(o.$slots,"footer")])],4),[[e.vShow,o.menu.isOpen]])])),_:3},8,["name"])],64)):e.createCommentVNode("",!0),"hover"===o.menu.mode?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onMouseover:s[1]||(s[1]=e.withModifiers((function(){return o.show&&o.show(...arguments)}),["prevent"])),onMouseleave:s[2]||(s[2]=e.withModifiers((function(){return o.hide&&o.hide(...arguments)}),["prevent"]))},[e.renderSlot(o.$slots,"trigger")],544),e.createVNode(e.Transition,{name:o.menu.transition},{default:e.withCtx((()=>[e.withDirectives(e.createElementVNode("div",{class:"v-dropdown-menu__container",style:e.normalizeStyle({"z-index":o.menu.containerZIndex}),onMouseover:s[3]||(s[3]=e.withModifiers((function(){return o.show&&o.show(...arguments)}),["prevent"])),onMouseleave:s[4]||(s[4]=e.withModifiers((function(){return o.hide&&o.hide(...arguments)}),["prevent"]))},[e.createElementVNode("div",i,[e.renderSlot(o.$slots,"header")]),e.createElementVNode("div",d,[e.renderSlot(o.$slots,"body")]),e.createElementVNode("div",l,[e.renderSlot(o.$slots,"footer")])],36),[[e.vShow,o.menu.isOpen]])])),_:3},8,["name"])],64)):e.createCommentVNode("",!0),o.menu.overlay&&o.menu.closeOnClickOutside&&"click"===o.menu.mode?e.withDirectives((e.openBlock(),e.createElementBlock("div",{key:2,class:"v-dropdown-menu__overlay",ref:"overlayRef",style:e.normalizeStyle({"background-color":o.menu.overlayBgColor,"z-index":o.menu.overlayZIndex}),onMousedown:s[5]||(s[5]=e.withModifiers((function(){return o.hide&&o.hide(...arguments)}),["prevent"]))},null,36)),[[e.vShow,o.menu.isOpen]]):e.createCommentVNode("",!0)],2)},(()=>{const e=o;return e.install=o=>{o.component("DropdownMenu",e)},e})()})); 2 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.min.cjs: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("vue"),n=e.defineComponent({name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},setup:function(n,o){var t=o.emit,r="v-dropdown-menu",i=e.ref(null),d=e.ref(null),l=e.ref(null),c=e.reactive({isOpen:n.isOpen,mode:n.mode,dropup:n.dropup,direction:n.direction,closeOnClickOutside:n.closeOnClickOutside,withDropdownCloser:n.withDropdownCloser,containerZIndex:n.containerZIndex,overlay:n.overlay,overlayBgColor:n.overlayBgColor,overlayZIndex:n.overlayZIndex,transition:n.transition}),u=e.computed((function(){return c.isOpen?"".concat(r,"--active"):null})),a=e.computed((function(){return"click"===c.mode?"".concat(r,"--mode-click"):"".concat(r,"--mode-hover")})),s=e.computed((function(){return c.dropup?"".concat(r,"--dropup"):null})),p=e.computed((function(){return"left"===c.direction?"".concat(r,"--direction-left"):"center"===c.direction?"".concat(r,"--direction-center"):"".concat(r,"--direction-right")}));e.watch((function(){return n.isOpen}),(function(e){"click"===c.mode&&(e?setTimeout((function(){m()}),1):setTimeout((function(){f()}),1))})),e.watch((function(){return c.isOpen}),(function(e){"click"===c.mode&&t(e?"opened":"closed",n)})),e.onMounted((function(){h(),e.nextTick((function(){c.closeOnClickOutside&&v()})),O()})),e.onBeforeUnmount((function(){y(),g()}));var m=function(){c.isOpen=!0},f=function(){c.isOpen=!1},v=function(){window.addEventListener("click",w)},w=function(e){c.isOpen&&(i.value.contains(e.target)||(c.isOpen=!1))},y=function(){c.closeOnClickOutside&&window.removeEventListener("click",w)},h=function(){c.withDropdownCloser&&i.value.querySelectorAll("[dropdown-closer]").forEach((function(e){e.addEventListener("click",(function(){c.isOpen=!1}))}))},O=function(){window.addEventListener("popstate",(function(){c.isOpen&&(c.isOpen=!1)}))},g=function(){window.removeEventListener("popstate",O)};return{rootRef:i,triggerRef:d,overlayRef:l,menu:c,show:m,hide:f,activeClass:u,modeClass:a,dropupClass:s,directionClass:p}}}),o={class:"v-dropdown-menu__header"},t={class:"v-dropdown-menu__body"},r={class:"v-dropdown-menu__footer"},i={class:"v-dropdown-menu__header"},d={class:"v-dropdown-menu__body"},l={class:"v-dropdown-menu__footer"};n.render=function(n,c,u,a,s,p){return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["v-dropdown-menu",[n.activeClass,n.modeClass,n.dropupClass,n.directionClass]]),ref:"rootRef"},["click"===n.menu.mode?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onClick:c[0]||(c[0]=e.withModifiers((function(e){return n.menu.isOpen=!n.menu.isOpen}),["prevent"]))},[e.renderSlot(n.$slots,"trigger")],512),e.createVNode(e.Transition,{mode:"out-in",name:n.menu.transition},{default:e.withCtx((function(){return[e.withDirectives(e.createElementVNode("div",{class:"v-dropdown-menu__container",style:e.normalizeStyle({"z-index":n.menu.containerZIndex})},[e.createElementVNode("div",o,[e.renderSlot(n.$slots,"header")]),e.createElementVNode("div",t,[e.renderSlot(n.$slots,"body")]),e.createElementVNode("div",r,[e.renderSlot(n.$slots,"footer")])],4),[[e.vShow,n.menu.isOpen]])]})),_:3},8,["name"])],64)):e.createCommentVNode("",!0),"hover"===n.menu.mode?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onMouseover:c[1]||(c[1]=e.withModifiers((function(){return n.show&&n.show.apply(n,arguments)}),["prevent"])),onMouseleave:c[2]||(c[2]=e.withModifiers((function(){return n.hide&&n.hide.apply(n,arguments)}),["prevent"]))},[e.renderSlot(n.$slots,"trigger")],544),e.createVNode(e.Transition,{name:n.menu.transition},{default:e.withCtx((function(){return[e.withDirectives(e.createElementVNode("div",{class:"v-dropdown-menu__container",style:e.normalizeStyle({"z-index":n.menu.containerZIndex}),onMouseover:c[3]||(c[3]=e.withModifiers((function(){return n.show&&n.show.apply(n,arguments)}),["prevent"])),onMouseleave:c[4]||(c[4]=e.withModifiers((function(){return n.hide&&n.hide.apply(n,arguments)}),["prevent"]))},[e.createElementVNode("div",i,[e.renderSlot(n.$slots,"header")]),e.createElementVNode("div",d,[e.renderSlot(n.$slots,"body")]),e.createElementVNode("div",l,[e.renderSlot(n.$slots,"footer")])],36),[[e.vShow,n.menu.isOpen]])]})),_:3},8,["name"])],64)):e.createCommentVNode("",!0),n.menu.overlay&&n.menu.closeOnClickOutside&&"click"===n.menu.mode?e.withDirectives((e.openBlock(),e.createElementBlock("div",{key:2,class:"v-dropdown-menu__overlay",ref:"overlayRef",style:e.normalizeStyle({"background-color":n.menu.overlayBgColor,"z-index":n.menu.overlayZIndex}),onMousedown:c[5]||(c[5]=e.withModifiers((function(){return n.hide&&n.hide.apply(n,arguments)}),["prevent"]))},null,36)),[[e.vShow,n.menu.isOpen]]):e.createCommentVNode("",!0)],2)};var c=function(){var e=n;return e.install=function(n){n.component("DropdownMenu",e)},e}();exports.default=c; -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.min.mjs: -------------------------------------------------------------------------------- 1 | import{defineComponent as e,ref as n,reactive as o,computed as r,watch as t,onMounted as i,nextTick as d,onBeforeUnmount as l,openBlock as s,createElementBlock as u,normalizeClass as a,Fragment as c,createElementVNode as p,withModifiers as v,renderSlot as m,createVNode as f,Transition as w,withCtx as y,withDirectives as O,normalizeStyle as g,vShow as h,createCommentVNode as _}from"vue";var C=e({name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},setup(e,s){let{emit:u}=s;const a="v-dropdown-menu",c=n(null),p=n(null),v=n(null),m=o({isOpen:e.isOpen,mode:e.mode,dropup:e.dropup,direction:e.direction,closeOnClickOutside:e.closeOnClickOutside,withDropdownCloser:e.withDropdownCloser,containerZIndex:e.containerZIndex,overlay:e.overlay,overlayBgColor:e.overlayBgColor,overlayZIndex:e.overlayZIndex,transition:e.transition}),f=r((()=>m.isOpen?`${a}--active`:null)),w=r((()=>"click"===m.mode?`${a}--mode-click`:`${a}--mode-hover`)),y=r((()=>m.dropup?`${a}--dropup`:null)),O=r((()=>{let e=null;return e="left"===m.direction?`${a}--direction-left`:"center"===m.direction?`${a}--direction-center`:`${a}--direction-right`,e}));t((()=>e.isOpen),(e=>{"click"===m.mode&&(e?setTimeout((()=>{g()}),1):setTimeout((()=>{h()}),1))})),t((()=>m.isOpen),(n=>{"click"===m.mode&&u(n?"opened":"closed",e)})),i((()=>{$(),d((()=>{m.closeOnClickOutside&&_()})),x()})),l((()=>{k(),q()}));const g=()=>{m.isOpen=!0},h=()=>{m.isOpen=!1},_=()=>{window.addEventListener("click",C)},C=e=>{m.isOpen&&(c.value.contains(e.target)||(m.isOpen=!1))},k=()=>{m.closeOnClickOutside&&window.removeEventListener("click",C)},$=()=>{if(m.withDropdownCloser){c.value.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{m.isOpen=!1}))}))}},x=()=>{window.addEventListener("popstate",(()=>{m.isOpen&&(m.isOpen=!1)}))},q=()=>{window.removeEventListener("popstate",x)};return{rootRef:c,triggerRef:p,overlayRef:v,menu:m,show:g,hide:h,activeClass:f,modeClass:w,dropupClass:y,directionClass:O}}});const k={class:"v-dropdown-menu__header"},$={class:"v-dropdown-menu__body"},x={class:"v-dropdown-menu__footer"},q={class:"v-dropdown-menu__header"},B={class:"v-dropdown-menu__body"},I={class:"v-dropdown-menu__footer"};C.render=function(e,n,o,r,t,i){return s(),u("div",{class:a(["v-dropdown-menu",[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]]),ref:"rootRef"},["click"===e.menu.mode?(s(),u(c,{key:0},[p("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onClick:n[0]||(n[0]=v((n=>e.menu.isOpen=!e.menu.isOpen),["prevent"]))},[m(e.$slots,"trigger")],512),f(w,{mode:"out-in",name:e.menu.transition},{default:y((()=>[O(p("div",{class:"v-dropdown-menu__container",style:g({"z-index":e.menu.containerZIndex})},[p("div",k,[m(e.$slots,"header")]),p("div",$,[m(e.$slots,"body")]),p("div",x,[m(e.$slots,"footer")])],4),[[h,e.menu.isOpen]])])),_:3},8,["name"])],64)):_("",!0),"hover"===e.menu.mode?(s(),u(c,{key:1},[p("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onMouseover:n[1]||(n[1]=v((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[2]||(n[2]=v((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[m(e.$slots,"trigger")],544),f(w,{name:e.menu.transition},{default:y((()=>[O(p("div",{class:"v-dropdown-menu__container",style:g({"z-index":e.menu.containerZIndex}),onMouseover:n[3]||(n[3]=v((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[4]||(n[4]=v((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[p("div",q,[m(e.$slots,"header")]),p("div",B,[m(e.$slots,"body")]),p("div",I,[m(e.$slots,"footer")])],36),[[h,e.menu.isOpen]])])),_:3},8,["name"])],64)):_("",!0),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?O((s(),u("div",{key:2,class:"v-dropdown-menu__overlay",ref:"overlayRef",style:g({"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex}),onMousedown:n[5]||(n[5]=v((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},null,36)),[[h,e.menu.isOpen]]):_("",!0)],2)};var Z=(()=>{const e=C;return e.install=n=>{n.component("DropdownMenu",e)},e})();export{Z as default}; 2 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.mjs: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref, reactive, computed, watch, onMounted, nextTick, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, Fragment, createElementVNode, withModifiers, renderSlot, createVNode, Transition, withCtx, withDirectives, normalizeStyle, vShow, createCommentVNode } from 'vue'; 2 | 3 | var script = defineComponent({ 4 | name: 'DropdownMenu', 5 | props: { 6 | isOpen: { 7 | type: Boolean, 8 | required: false, 9 | default: false 10 | }, 11 | mode: { 12 | type: String, 13 | required: false, 14 | default: 'click' 15 | }, 16 | dropup: { 17 | type: Boolean, 18 | required: false, 19 | default: false 20 | }, 21 | direction: { 22 | type: String, 23 | required: false, 24 | default: 'left' 25 | }, 26 | closeOnClickOutside: { 27 | type: Boolean, 28 | required: false, 29 | default: true 30 | }, 31 | withDropdownCloser: { 32 | type: Boolean, 33 | required: false, 34 | default: false 35 | }, 36 | containerZIndex: { 37 | type: String, 38 | required: false, 39 | default: '994' 40 | }, 41 | overlay: { 42 | type: Boolean, 43 | required: false, 44 | default: true 45 | }, 46 | overlayBgColor: { 47 | type: String, 48 | required: false, 49 | default: 'rgba(0, 0, 0, 0.2)' 50 | }, 51 | overlayZIndex: { 52 | type: String, 53 | required: false, 54 | default: '992' 55 | }, 56 | transition: { 57 | type: String, 58 | required: false, 59 | default: 'default' 60 | } 61 | }, 62 | setup(props, _ref) { 63 | let { 64 | emit 65 | } = _ref; 66 | const baseClassName = 'v-dropdown-menu'; 67 | const rootRef = ref(null); 68 | const triggerRef = ref(null); 69 | const overlayRef = ref(null); 70 | const menu = reactive({ 71 | isOpen: props.isOpen, 72 | mode: props.mode, 73 | dropup: props.dropup, 74 | direction: props.direction, 75 | closeOnClickOutside: props.closeOnClickOutside, 76 | withDropdownCloser: props.withDropdownCloser, 77 | containerZIndex: props.containerZIndex, 78 | overlay: props.overlay, 79 | overlayBgColor: props.overlayBgColor, 80 | overlayZIndex: props.overlayZIndex, 81 | transition: props.transition 82 | }); 83 | const activeClass = computed(() => { 84 | return menu.isOpen ? `${baseClassName}--active` : null; 85 | }); 86 | const modeClass = computed(() => { 87 | return menu.mode === 'click' ? `${baseClassName}--mode-click` : `${baseClassName}--mode-hover`; 88 | }); 89 | const dropupClass = computed(() => { 90 | return menu.dropup ? `${baseClassName}--dropup` : null; 91 | }); 92 | const directionClass = computed(() => { 93 | let menuDirection = null; 94 | if (menu.direction === 'left') { 95 | menuDirection = `${baseClassName}--direction-left`; 96 | } else if (menu.direction === 'center') { 97 | menuDirection = `${baseClassName}--direction-center`; 98 | } else { 99 | menuDirection = `${baseClassName}--direction-right`; 100 | } 101 | return menuDirection; 102 | }); 103 | watch(() => props.isOpen, value => { 104 | if (menu.mode === 'click') { 105 | if (value) { 106 | setTimeout(() => { 107 | show(); 108 | }, 1); // wait, bypass for closeOnClickOutside 109 | } else { 110 | setTimeout(() => { 111 | hide(); 112 | }, 1); // wait, bypass for closeOnClickOutside 113 | } 114 | } 115 | }); 116 | 117 | watch(() => menu.isOpen, value => { 118 | if (menu.mode === 'click') { 119 | if (value) { 120 | emit('opened', props); 121 | } else { 122 | emit('closed', props); 123 | } 124 | } 125 | }); 126 | onMounted(() => { 127 | dropdownCloser(); 128 | nextTick(() => { 129 | if (menu.closeOnClickOutside) { 130 | registerCloseDropdownOnClickOutside(); 131 | } 132 | }); 133 | closeDropdownOnPopState(); 134 | }); 135 | onBeforeUnmount(() => { 136 | destroyCloseDropdownOnClickOutside(); 137 | destroyCloseDropdownOnPopState(); 138 | }); 139 | 140 | // Methods 141 | const show = () => { 142 | menu.isOpen = true; 143 | }; 144 | const hide = () => { 145 | menu.isOpen = false; 146 | }; 147 | const registerCloseDropdownOnClickOutside = () => { 148 | window.addEventListener('click', closeDropdownOnClickOutside); 149 | }; 150 | const closeDropdownOnClickOutside = e => { 151 | if (menu.isOpen) { 152 | if (!rootRef.value.contains(e.target)) { 153 | menu.isOpen = false; 154 | } 155 | } 156 | }; 157 | const destroyCloseDropdownOnClickOutside = () => { 158 | if (menu.closeOnClickOutside) { 159 | window.removeEventListener('click', closeDropdownOnClickOutside); 160 | } 161 | }; 162 | const dropdownCloser = () => { 163 | if (menu.withDropdownCloser) { 164 | const dropdown = rootRef.value; 165 | dropdown.querySelectorAll('[dropdown-closer]').forEach(element => { 166 | element.addEventListener('click', () => { 167 | menu.isOpen = false; 168 | }); 169 | }); 170 | } 171 | }; 172 | const closeDropdownOnPopState = () => { 173 | window.addEventListener('popstate', () => { 174 | if (menu.isOpen) { 175 | menu.isOpen = false; 176 | } 177 | }); 178 | }; 179 | const destroyCloseDropdownOnPopState = () => { 180 | window.removeEventListener('popstate', closeDropdownOnPopState); 181 | }; 182 | return { 183 | rootRef, 184 | triggerRef, 185 | overlayRef, 186 | menu, 187 | show, 188 | hide, 189 | activeClass, 190 | modeClass, 191 | dropupClass, 192 | directionClass 193 | }; 194 | } 195 | }); 196 | 197 | const _hoisted_1 = { 198 | class: "v-dropdown-menu__header" 199 | }; 200 | const _hoisted_2 = { 201 | class: "v-dropdown-menu__body" 202 | }; 203 | const _hoisted_3 = { 204 | class: "v-dropdown-menu__footer" 205 | }; 206 | const _hoisted_4 = { 207 | class: "v-dropdown-menu__header" 208 | }; 209 | const _hoisted_5 = { 210 | class: "v-dropdown-menu__body" 211 | }; 212 | const _hoisted_6 = { 213 | class: "v-dropdown-menu__footer" 214 | }; 215 | function render(_ctx, _cache, $props, $setup, $data, $options) { 216 | return openBlock(), createElementBlock("div", { 217 | class: normalizeClass(["v-dropdown-menu", [_ctx.activeClass, _ctx.modeClass, _ctx.dropupClass, _ctx.directionClass]]), 218 | ref: "rootRef" 219 | }, [_ctx.menu.mode === 'click' ? (openBlock(), createElementBlock(Fragment, { 220 | key: 0 221 | }, [createElementVNode("div", { 222 | class: "v-dropdown-menu__trigger", 223 | ref: "triggerRef", 224 | onClick: _cache[0] || (_cache[0] = withModifiers($event => _ctx.menu.isOpen = !_ctx.menu.isOpen, ["prevent"])) 225 | }, [renderSlot(_ctx.$slots, "trigger")], 512), createVNode(Transition, { 226 | mode: "out-in", 227 | name: _ctx.menu.transition 228 | }, { 229 | default: withCtx(() => [withDirectives(createElementVNode("div", { 230 | class: "v-dropdown-menu__container", 231 | style: normalizeStyle({ 232 | 'z-index': _ctx.menu.containerZIndex 233 | }) 234 | }, [createElementVNode("div", _hoisted_1, [renderSlot(_ctx.$slots, "header")]), createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "body")]), createElementVNode("div", _hoisted_3, [renderSlot(_ctx.$slots, "footer")])], 4), [[vShow, _ctx.menu.isOpen]])]), 235 | _: 3 236 | }, 8, ["name"])], 64)) : createCommentVNode("", true), _ctx.menu.mode === 'hover' ? (openBlock(), createElementBlock(Fragment, { 237 | key: 1 238 | }, [createElementVNode("div", { 239 | class: "v-dropdown-menu__trigger", 240 | ref: "triggerRef", 241 | onMouseover: _cache[1] || (_cache[1] = withModifiers(function () { 242 | return _ctx.show && _ctx.show(...arguments); 243 | }, ["prevent"])), 244 | onMouseleave: _cache[2] || (_cache[2] = withModifiers(function () { 245 | return _ctx.hide && _ctx.hide(...arguments); 246 | }, ["prevent"])) 247 | }, [renderSlot(_ctx.$slots, "trigger")], 544), createVNode(Transition, { 248 | name: _ctx.menu.transition 249 | }, { 250 | default: withCtx(() => [withDirectives(createElementVNode("div", { 251 | class: "v-dropdown-menu__container", 252 | style: normalizeStyle({ 253 | 'z-index': _ctx.menu.containerZIndex 254 | }), 255 | onMouseover: _cache[3] || (_cache[3] = withModifiers(function () { 256 | return _ctx.show && _ctx.show(...arguments); 257 | }, ["prevent"])), 258 | onMouseleave: _cache[4] || (_cache[4] = withModifiers(function () { 259 | return _ctx.hide && _ctx.hide(...arguments); 260 | }, ["prevent"])) 261 | }, [createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "header")]), createElementVNode("div", _hoisted_5, [renderSlot(_ctx.$slots, "body")]), createElementVNode("div", _hoisted_6, [renderSlot(_ctx.$slots, "footer")])], 36), [[vShow, _ctx.menu.isOpen]])]), 262 | _: 3 263 | }, 8, ["name"])], 64)) : createCommentVNode("", true), _ctx.menu.overlay && _ctx.menu.closeOnClickOutside && _ctx.menu.mode === 'click' ? withDirectives((openBlock(), createElementBlock("div", { 264 | key: 2, 265 | class: "v-dropdown-menu__overlay", 266 | ref: "overlayRef", 267 | style: normalizeStyle({ 268 | 'background-color': _ctx.menu.overlayBgColor, 269 | 'z-index': _ctx.menu.overlayZIndex 270 | }), 271 | onMousedown: _cache[5] || (_cache[5] = withModifiers(function () { 272 | return _ctx.hide && _ctx.hide(...arguments); 273 | }, ["prevent"])) 274 | }, null, 36)), [[vShow, _ctx.menu.isOpen]]) : createCommentVNode("", true)], 2); 275 | } 276 | 277 | script.render = render; 278 | 279 | // Import vue component 280 | 281 | // Default export is installable instance of component. 282 | // IIFE injects install function into component, allowing component 283 | // to be registered via Vue.use() as well as Vue.component(), 284 | var entry = /*#__PURE__*/(() => { 285 | // Assign InstallableComponent type 286 | const installable = script; 287 | 288 | // Attach install function executed by Vue.use() 289 | installable.install = app => { 290 | app.component('DropdownMenu', installable); 291 | }; 292 | return installable; 293 | })(); 294 | 295 | // It's possible to expose named exports when writing components that can 296 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 297 | // export const RollupDemoDirective = directive; 298 | 299 | export { entry as default }; 300 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) : 3 | typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DropdownMenu = {}, global.Vue)); 5 | })(this, (function (exports, vue) { 'use strict'; 6 | 7 | var script = vue.defineComponent({ 8 | name: 'DropdownMenu', 9 | props: { 10 | isOpen: { 11 | type: Boolean, 12 | required: false, 13 | default: false 14 | }, 15 | mode: { 16 | type: String, 17 | required: false, 18 | default: 'click' 19 | }, 20 | dropup: { 21 | type: Boolean, 22 | required: false, 23 | default: false 24 | }, 25 | direction: { 26 | type: String, 27 | required: false, 28 | default: 'left' 29 | }, 30 | closeOnClickOutside: { 31 | type: Boolean, 32 | required: false, 33 | default: true 34 | }, 35 | withDropdownCloser: { 36 | type: Boolean, 37 | required: false, 38 | default: false 39 | }, 40 | containerZIndex: { 41 | type: String, 42 | required: false, 43 | default: '994' 44 | }, 45 | overlay: { 46 | type: Boolean, 47 | required: false, 48 | default: true 49 | }, 50 | overlayBgColor: { 51 | type: String, 52 | required: false, 53 | default: 'rgba(0, 0, 0, 0.2)' 54 | }, 55 | overlayZIndex: { 56 | type: String, 57 | required: false, 58 | default: '992' 59 | }, 60 | transition: { 61 | type: String, 62 | required: false, 63 | default: 'default' 64 | } 65 | }, 66 | setup(props, _ref) { 67 | let { 68 | emit 69 | } = _ref; 70 | const baseClassName = 'v-dropdown-menu'; 71 | const rootRef = vue.ref(null); 72 | const triggerRef = vue.ref(null); 73 | const overlayRef = vue.ref(null); 74 | const menu = vue.reactive({ 75 | isOpen: props.isOpen, 76 | mode: props.mode, 77 | dropup: props.dropup, 78 | direction: props.direction, 79 | closeOnClickOutside: props.closeOnClickOutside, 80 | withDropdownCloser: props.withDropdownCloser, 81 | containerZIndex: props.containerZIndex, 82 | overlay: props.overlay, 83 | overlayBgColor: props.overlayBgColor, 84 | overlayZIndex: props.overlayZIndex, 85 | transition: props.transition 86 | }); 87 | const activeClass = vue.computed(() => { 88 | return menu.isOpen ? `${baseClassName}--active` : null; 89 | }); 90 | const modeClass = vue.computed(() => { 91 | return menu.mode === 'click' ? `${baseClassName}--mode-click` : `${baseClassName}--mode-hover`; 92 | }); 93 | const dropupClass = vue.computed(() => { 94 | return menu.dropup ? `${baseClassName}--dropup` : null; 95 | }); 96 | const directionClass = vue.computed(() => { 97 | let menuDirection = null; 98 | if (menu.direction === 'left') { 99 | menuDirection = `${baseClassName}--direction-left`; 100 | } else if (menu.direction === 'center') { 101 | menuDirection = `${baseClassName}--direction-center`; 102 | } else { 103 | menuDirection = `${baseClassName}--direction-right`; 104 | } 105 | return menuDirection; 106 | }); 107 | vue.watch(() => props.isOpen, value => { 108 | if (menu.mode === 'click') { 109 | if (value) { 110 | setTimeout(() => { 111 | show(); 112 | }, 1); // wait, bypass for closeOnClickOutside 113 | } else { 114 | setTimeout(() => { 115 | hide(); 116 | }, 1); // wait, bypass for closeOnClickOutside 117 | } 118 | } 119 | }); 120 | 121 | vue.watch(() => menu.isOpen, value => { 122 | if (menu.mode === 'click') { 123 | if (value) { 124 | emit('opened', props); 125 | } else { 126 | emit('closed', props); 127 | } 128 | } 129 | }); 130 | vue.onMounted(() => { 131 | dropdownCloser(); 132 | vue.nextTick(() => { 133 | if (menu.closeOnClickOutside) { 134 | registerCloseDropdownOnClickOutside(); 135 | } 136 | }); 137 | closeDropdownOnPopState(); 138 | }); 139 | vue.onBeforeUnmount(() => { 140 | destroyCloseDropdownOnClickOutside(); 141 | destroyCloseDropdownOnPopState(); 142 | }); 143 | 144 | // Methods 145 | const show = () => { 146 | menu.isOpen = true; 147 | }; 148 | const hide = () => { 149 | menu.isOpen = false; 150 | }; 151 | const registerCloseDropdownOnClickOutside = () => { 152 | window.addEventListener('click', closeDropdownOnClickOutside); 153 | }; 154 | const closeDropdownOnClickOutside = e => { 155 | if (menu.isOpen) { 156 | if (!rootRef.value.contains(e.target)) { 157 | menu.isOpen = false; 158 | } 159 | } 160 | }; 161 | const destroyCloseDropdownOnClickOutside = () => { 162 | if (menu.closeOnClickOutside) { 163 | window.removeEventListener('click', closeDropdownOnClickOutside); 164 | } 165 | }; 166 | const dropdownCloser = () => { 167 | if (menu.withDropdownCloser) { 168 | const dropdown = rootRef.value; 169 | dropdown.querySelectorAll('[dropdown-closer]').forEach(element => { 170 | element.addEventListener('click', () => { 171 | menu.isOpen = false; 172 | }); 173 | }); 174 | } 175 | }; 176 | const closeDropdownOnPopState = () => { 177 | window.addEventListener('popstate', () => { 178 | if (menu.isOpen) { 179 | menu.isOpen = false; 180 | } 181 | }); 182 | }; 183 | const destroyCloseDropdownOnPopState = () => { 184 | window.removeEventListener('popstate', closeDropdownOnPopState); 185 | }; 186 | return { 187 | rootRef, 188 | triggerRef, 189 | overlayRef, 190 | menu, 191 | show, 192 | hide, 193 | activeClass, 194 | modeClass, 195 | dropupClass, 196 | directionClass 197 | }; 198 | } 199 | }); 200 | 201 | const _hoisted_1 = { 202 | class: "v-dropdown-menu__header" 203 | }; 204 | const _hoisted_2 = { 205 | class: "v-dropdown-menu__body" 206 | }; 207 | const _hoisted_3 = { 208 | class: "v-dropdown-menu__footer" 209 | }; 210 | const _hoisted_4 = { 211 | class: "v-dropdown-menu__header" 212 | }; 213 | const _hoisted_5 = { 214 | class: "v-dropdown-menu__body" 215 | }; 216 | const _hoisted_6 = { 217 | class: "v-dropdown-menu__footer" 218 | }; 219 | function render(_ctx, _cache, $props, $setup, $data, $options) { 220 | return vue.openBlock(), vue.createElementBlock("div", { 221 | class: vue.normalizeClass(["v-dropdown-menu", [_ctx.activeClass, _ctx.modeClass, _ctx.dropupClass, _ctx.directionClass]]), 222 | ref: "rootRef" 223 | }, [_ctx.menu.mode === 'click' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 224 | key: 0 225 | }, [vue.createElementVNode("div", { 226 | class: "v-dropdown-menu__trigger", 227 | ref: "triggerRef", 228 | onClick: _cache[0] || (_cache[0] = vue.withModifiers($event => _ctx.menu.isOpen = !_ctx.menu.isOpen, ["prevent"])) 229 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 512), vue.createVNode(vue.Transition, { 230 | mode: "out-in", 231 | name: _ctx.menu.transition 232 | }, { 233 | default: vue.withCtx(() => [vue.withDirectives(vue.createElementVNode("div", { 234 | class: "v-dropdown-menu__container", 235 | style: vue.normalizeStyle({ 236 | 'z-index': _ctx.menu.containerZIndex 237 | }) 238 | }, [vue.createElementVNode("div", _hoisted_1, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_2, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_3, [vue.renderSlot(_ctx.$slots, "footer")])], 4), [[vue.vShow, _ctx.menu.isOpen]])]), 239 | _: 3 240 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.mode === 'hover' ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { 241 | key: 1 242 | }, [vue.createElementVNode("div", { 243 | class: "v-dropdown-menu__trigger", 244 | ref: "triggerRef", 245 | onMouseover: _cache[1] || (_cache[1] = vue.withModifiers(function () { 246 | return _ctx.show && _ctx.show(...arguments); 247 | }, ["prevent"])), 248 | onMouseleave: _cache[2] || (_cache[2] = vue.withModifiers(function () { 249 | return _ctx.hide && _ctx.hide(...arguments); 250 | }, ["prevent"])) 251 | }, [vue.renderSlot(_ctx.$slots, "trigger")], 544), vue.createVNode(vue.Transition, { 252 | name: _ctx.menu.transition 253 | }, { 254 | default: vue.withCtx(() => [vue.withDirectives(vue.createElementVNode("div", { 255 | class: "v-dropdown-menu__container", 256 | style: vue.normalizeStyle({ 257 | 'z-index': _ctx.menu.containerZIndex 258 | }), 259 | onMouseover: _cache[3] || (_cache[3] = vue.withModifiers(function () { 260 | return _ctx.show && _ctx.show(...arguments); 261 | }, ["prevent"])), 262 | onMouseleave: _cache[4] || (_cache[4] = vue.withModifiers(function () { 263 | return _ctx.hide && _ctx.hide(...arguments); 264 | }, ["prevent"])) 265 | }, [vue.createElementVNode("div", _hoisted_4, [vue.renderSlot(_ctx.$slots, "header")]), vue.createElementVNode("div", _hoisted_5, [vue.renderSlot(_ctx.$slots, "body")]), vue.createElementVNode("div", _hoisted_6, [vue.renderSlot(_ctx.$slots, "footer")])], 36), [[vue.vShow, _ctx.menu.isOpen]])]), 266 | _: 3 267 | }, 8, ["name"])], 64)) : vue.createCommentVNode("", true), _ctx.menu.overlay && _ctx.menu.closeOnClickOutside && _ctx.menu.mode === 'click' ? vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", { 268 | key: 2, 269 | class: "v-dropdown-menu__overlay", 270 | ref: "overlayRef", 271 | style: vue.normalizeStyle({ 272 | 'background-color': _ctx.menu.overlayBgColor, 273 | 'z-index': _ctx.menu.overlayZIndex 274 | }), 275 | onMousedown: _cache[5] || (_cache[5] = vue.withModifiers(function () { 276 | return _ctx.hide && _ctx.hide(...arguments); 277 | }, ["prevent"])) 278 | }, null, 36)), [[vue.vShow, _ctx.menu.isOpen]]) : vue.createCommentVNode("", true)], 2); 279 | } 280 | 281 | script.render = render; 282 | 283 | // Import vue component 284 | 285 | // Default export is installable instance of component. 286 | // IIFE injects install function into component, allowing component 287 | // to be registered via Vue.use() as well as Vue.component(), 288 | var entry = /*#__PURE__*/(() => { 289 | // Assign InstallableComponent type 290 | const installable = script; 291 | 292 | // Attach install function executed by Vue.use() 293 | installable.install = app => { 294 | app.component('DropdownMenu', installable); 295 | }; 296 | return installable; 297 | })(); 298 | 299 | // It's possible to expose named exports when writing components that can 300 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 301 | // export const RollupDemoDirective = directive; 302 | 303 | exports["default"] = entry; 304 | 305 | Object.defineProperty(exports, '__esModule', { value: true }); 306 | 307 | })); 308 | -------------------------------------------------------------------------------- /dist/vue3/v-dropdown-menu.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],o):o((e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu={},e.Vue)}(this,(function(e,o){"use strict";var n=o.defineComponent({name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},setup(e,n){let{emit:t}=n;const r="v-dropdown-menu",i=o.ref(null),d=o.ref(null),l=o.ref(null),s=o.reactive({isOpen:e.isOpen,mode:e.mode,dropup:e.dropup,direction:e.direction,closeOnClickOutside:e.closeOnClickOutside,withDropdownCloser:e.withDropdownCloser,containerZIndex:e.containerZIndex,overlay:e.overlay,overlayBgColor:e.overlayBgColor,overlayZIndex:e.overlayZIndex,transition:e.transition}),a=o.computed((()=>s.isOpen?`${r}--active`:null)),c=o.computed((()=>"click"===s.mode?`${r}--mode-click`:`${r}--mode-hover`)),u=o.computed((()=>s.dropup?`${r}--dropup`:null)),p=o.computed((()=>{let e=null;return e="left"===s.direction?`${r}--direction-left`:"center"===s.direction?`${r}--direction-center`:`${r}--direction-right`,e}));o.watch((()=>e.isOpen),(e=>{"click"===s.mode&&(e?setTimeout((()=>{m()}),1):setTimeout((()=>{v()}),1))})),o.watch((()=>s.isOpen),(o=>{"click"===s.mode&&t(o?"opened":"closed",e)})),o.onMounted((()=>{h(),o.nextTick((()=>{s.closeOnClickOutside&&f()})),g()})),o.onBeforeUnmount((()=>{y(),O()}));const m=()=>{s.isOpen=!0},v=()=>{s.isOpen=!1},f=()=>{window.addEventListener("click",w)},w=e=>{s.isOpen&&(i.value.contains(e.target)||(s.isOpen=!1))},y=()=>{s.closeOnClickOutside&&window.removeEventListener("click",w)},h=()=>{if(s.withDropdownCloser){i.value.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{s.isOpen=!1}))}))}},g=()=>{window.addEventListener("popstate",(()=>{s.isOpen&&(s.isOpen=!1)}))},O=()=>{window.removeEventListener("popstate",g)};return{rootRef:i,triggerRef:d,overlayRef:l,menu:s,show:m,hide:v,activeClass:a,modeClass:c,dropupClass:u,directionClass:p}}});const t={class:"v-dropdown-menu__header"},r={class:"v-dropdown-menu__body"},i={class:"v-dropdown-menu__footer"},d={class:"v-dropdown-menu__header"},l={class:"v-dropdown-menu__body"},s={class:"v-dropdown-menu__footer"};n.render=function(e,n,a,c,u,p){return o.openBlock(),o.createElementBlock("div",{class:o.normalizeClass(["v-dropdown-menu",[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]]),ref:"rootRef"},["click"===e.menu.mode?(o.openBlock(),o.createElementBlock(o.Fragment,{key:0},[o.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onClick:n[0]||(n[0]=o.withModifiers((o=>e.menu.isOpen=!e.menu.isOpen),["prevent"]))},[o.renderSlot(e.$slots,"trigger")],512),o.createVNode(o.Transition,{mode:"out-in",name:e.menu.transition},{default:o.withCtx((()=>[o.withDirectives(o.createElementVNode("div",{class:"v-dropdown-menu__container",style:o.normalizeStyle({"z-index":e.menu.containerZIndex})},[o.createElementVNode("div",t,[o.renderSlot(e.$slots,"header")]),o.createElementVNode("div",r,[o.renderSlot(e.$slots,"body")]),o.createElementVNode("div",i,[o.renderSlot(e.$slots,"footer")])],4),[[o.vShow,e.menu.isOpen]])])),_:3},8,["name"])],64)):o.createCommentVNode("",!0),"hover"===e.menu.mode?(o.openBlock(),o.createElementBlock(o.Fragment,{key:1},[o.createElementVNode("div",{class:"v-dropdown-menu__trigger",ref:"triggerRef",onMouseover:n[1]||(n[1]=o.withModifiers((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[2]||(n[2]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[o.renderSlot(e.$slots,"trigger")],544),o.createVNode(o.Transition,{name:e.menu.transition},{default:o.withCtx((()=>[o.withDirectives(o.createElementVNode("div",{class:"v-dropdown-menu__container",style:o.normalizeStyle({"z-index":e.menu.containerZIndex}),onMouseover:n[3]||(n[3]=o.withModifiers((function(){return e.show&&e.show(...arguments)}),["prevent"])),onMouseleave:n[4]||(n[4]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},[o.createElementVNode("div",d,[o.renderSlot(e.$slots,"header")]),o.createElementVNode("div",l,[o.renderSlot(e.$slots,"body")]),o.createElementVNode("div",s,[o.renderSlot(e.$slots,"footer")])],36),[[o.vShow,e.menu.isOpen]])])),_:3},8,["name"])],64)):o.createCommentVNode("",!0),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?o.withDirectives((o.openBlock(),o.createElementBlock("div",{key:2,class:"v-dropdown-menu__overlay",ref:"overlayRef",style:o.normalizeStyle({"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex}),onMousedown:n[5]||(n[5]=o.withModifiers((function(){return e.hide&&e.hide(...arguments)}),["prevent"]))},null,36)),[[o.vShow,e.menu.isOpen]]):o.createCommentVNode("",!0)],2)};var a=(()=>{const e=n;return e.install=o=>{o.component("DropdownMenu",e)},e})();e.default=a,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /docs/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .output 4 | .nuxt -------------------------------------------------------------------------------- /docs/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@nuxt/eslint-config', 4 | rules: { 5 | 'vue/max-attributes-per-line': 'off', 6 | 'vue/multi-word-component-names': 'off' 7 | } 8 | } -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | 14 | .vercel 15 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | v18.x.x 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docus Starter 2 | 3 | Starter template for [Docus](https://docus.dev). 4 | 5 | ## Clone 6 | 7 | Clone the repository (using `nuxi`): 8 | 9 | ```bash 10 | npx nuxi init -t themes/docus 11 | ``` 12 | 13 | ## Setup 14 | 15 | Install dependencies: 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | ## Development 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | ## Edge Side Rendering 28 | 29 | Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. 30 | 31 | Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). 32 | 33 | ```bash 34 | yarn build 35 | ``` 36 | 37 | ## Static Generation 38 | 39 | Use the `generate` command to build your application. 40 | 41 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 42 | 43 | ```bash 44 | yarn generate 45 | ``` 46 | 47 | ## Preview build 48 | 49 | You might want to preview the result of your build locally, to do so, run the following command: 50 | 51 | ```bash 52 | yarn preview 53 | ``` 54 | 55 | --- 56 | 57 | For a detailed explanation of how things work, check out [Docus](https://docus.dev). 58 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | docus: { 3 | title: 'v-dropdown-menu', 4 | description: 'Customizable dropdown menu for vue 🟩🔽', 5 | image: '/media/preview.jpg', 6 | socials: { 7 | twitter: 'selimdoyranli', 8 | github: 'selimdoyranli/v-dropdown-menu' 9 | }, 10 | aside: { 11 | level: 0, 12 | exclude: [] 13 | }, 14 | header: { 15 | logo: false, 16 | }, 17 | footer: { 18 | iconLinks: [ 19 | { 20 | href: 'https://github.com/sponsors/selimdoyranli', 21 | icon: 'simple-icons:githubsponsors' 22 | } 23 | ] 24 | } 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /docs/components/AppDropdown.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /docs/components/AppDropdownAdvanced.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | 36 | 118 | -------------------------------------------------------------------------------- /docs/components/AppDropdownWithCloser.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 26 | 27 | 37 | -------------------------------------------------------------------------------- /docs/components/AppSpinner.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /docs/content/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: v-dropdown-menu 3 | navigation: false 4 | layout: page 5 | --- 6 | 7 | ::block-hero 8 | --- 9 | cta: 10 | - Get Started 11 | - /guide 12 | secondary: 13 | - Open on GitHub → 14 | - https://github.com/selimdoyranli/v-dropdown-menu 15 | snippet: npm install v-dropdown-menu 16 | --- 17 | 18 | #title 19 | v-dropdown-menu 20 | 21 | #description 22 | Customizable dropdown menu for vue 🟩🔽 23 | :: 24 | 25 | ::card-grid 26 | #title 27 | Features 28 | 29 | #root 30 | :ellipsis 31 | 32 | #default 33 | ::card 34 | #title 35 | 🛠️ Customizable 36 | #description 37 | Customize the appearance and functionality as you wish 38 | :: 39 | ::card 40 | #title 41 | 📦 Vue2 & Vue3 42 | #description 43 | You're ready for all versions with Vue2 and Vue3 support 44 | :: 45 | ::card 46 | #title 47 | 💉 SSR 48 | #description 49 | Compatible with your Vue Server side rendering projects and Nuxt SSR 50 | :: 51 | :: 52 | -------------------------------------------------------------------------------- /docs/content/1.guide/0.index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Customizable dropdown menu for vue 🟩🔽 4 | 5 | ![v-dropdown-menu](https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/master/meta/logo.png){width="256"} 6 |

7 | ⚡️ Lightweight   8 | 🎨 Interactive   9 | 🛠️ Customizable   10 | 👶🏻 Easy implementation   11 | 📦 Vue2 & Vue3 support   12 | 💉 SSR compatible   13 |

14 | 15 | ![v-dropdown-menu](https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/master/meta/preview.jpg){width="320"} 16 | 17 | ## Installation 18 | 19 | Install the v-dropdown-menu 20 | 21 | ::code-group 22 | 23 | ```bash [npm] 24 | npm i v-dropdown-menu 25 | ``` 26 | 27 | ```bash [yarn] 28 | yarn add v-dropdown-menu 29 | ``` 30 | 31 | :: 32 | 33 | ::alert{type="success"} 34 | ✨ Well done! Ready for registration now 35 | :: 36 | -------------------------------------------------------------------------------- /docs/content/1.guide/1.registration/0.vue3.md: -------------------------------------------------------------------------------- 1 | # Vue3 2 | 3 | #### Global Register 4 | 5 | ```js 6 | import { createApp } from 'vue' 7 | import App from './App.vue' 8 | import DropdownMenu from 'v-dropdown-menu' 9 | import 'v-dropdown-menu/css' 10 | 11 | const app = createApp(App) 12 | 13 | app.use(DropdownMenu) 14 | app.mount('#app') 15 | ``` 16 | 17 | #### Local Register 18 | ```html 19 | 23 | ``` 24 | #### Via CDN 25 | ```js 26 | 27 | 28 | 29 | 30 | 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/content/1.guide/1.registration/1.vue2.md: -------------------------------------------------------------------------------- 1 | # Vue2 2 | 3 | #### Global Register 4 | 5 | ```js 6 | import Vue from "vue" 7 | import DropdownMenu from "v-dropdown-menu/vue2" 8 | import 'v-dropdown-menu/vue2/css' 9 | 10 | Vue.use(DropdownMenu); 11 | ``` 12 | 13 | #### Local Register 14 | ```js 15 | import DropdownMenu from "v-dropdown-menu/vue2" 16 | import 'v-dropdown-menu/vue2/css' 17 | 18 | export default { 19 | components: { 20 | DropdownMenu 21 | } 22 | } 23 | ``` 24 | #### Via CDN 25 | ```js 26 | 27 | 28 | 29 | 30 | 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/content/1.guide/2.usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ```html 4 | 5 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | #### Props 25 | | Name | Description | Type| Options| Default | 26 | |--|--|--|--|--| 27 | |isOpen|Show or hide for dropdown|Boolean|`true` , `false` | false 28 | |mode|Open variant|String|`click` , `hover` | click 29 | | dropup |Open the menu upwards | Boolean | `true` , `false` | false 30 | |direction|Menu container direction|String|`left` , `right` , `center` | left 31 | |closeOnClickOutside|closes dropdown menu when click outside|Booelan|`true` , `false` | true 32 | |withDropdownCloser| If there is an element in the menu with **dropdown-closer** attribute, clicking on it closes the menu.|Boolean|`true` , `false` | false 33 | |containerZIndex|z-index of menu container|String| .| 994 34 | |overlay|background overlay of dropdown menu (only for click mode) |Boolean| `true` , `false`| true 35 | |overlayBgColor|background-color of overlay |String| ex: `rgba(1, 35, 83, 0.8)`| rgba(0, 0, 0, 0.2) 36 | |overlayZIndex|z-index of overlay|String| .| 992 37 | |transition|custom vue transition for menu|String| .| default 38 | 39 | #### Slots 40 | |Name| Description | 41 | |--|--| 42 | |trigger|trigger for dropdown menu | 43 | |header|header of menu container (optional)| 44 | |body|content of menu (optional)| 45 | |footer|footer of menu container (optional)| 46 | 47 | #### Events (only for click mode) 48 | | | 49 | |--| 50 | | `@opened="dispatchEvent"`| 51 | | `@closed="dispatchEvent"`| 52 | 53 | 54 | --- 55 | -------------------------------------------------------------------------------- /docs/content/1.guide/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Guide 2 | -------------------------------------------------------------------------------- /docs/content/2.demo.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | #### Basic, no options 4 | 5 | ::code-group 6 | 7 | ::code-block{label="Preview" preview} 8 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 9 | ::div 10 | ::AppDropdown 11 | :: 12 | :: 13 | 14 | ```html [Template] 15 | 16 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | ``` 33 | :: 34 | 35 | #### Default opened, no overlay 36 | 37 | ::code-group 38 | 39 | ::code-block{label="Preview" preview} 40 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 41 | ::div 42 | ::AppDropdown{:isOpen="true" :overlay="false"} 43 | :: 44 | :: 45 | 46 | ```html [Template] 47 | 48 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 | ``` 65 | :: 66 | 67 | #### Default opened, no overlay, no close when click the outside 68 | 69 | ::code-group 70 | 71 | ::code-block{label="Preview" preview} 72 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 73 | ::div 74 | ::AppDropdown{:isOpen="true" :overlay="false" :close-on-click-outside="false"} 75 | :: 76 | :: 77 | 78 | ```html [Template] 79 | 80 | 83 | 84 | 85 | 86 | 93 | 94 | 95 | 96 | ``` 97 | :: 98 | 99 | #### Hover mode 100 | 101 | ::code-group 102 | 103 | ::code-block{label="Preview" preview} 104 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 105 | ::div 106 | ::AppDropdown{mode="hover"} 107 | :: 108 | :: 109 | 110 | ```html [Template] 111 | 112 | 115 | 116 | 117 | 118 | 125 | 126 | 127 | 128 | ``` 129 | :: 130 | 131 | #### Dropup 132 | 133 | ::code-group 134 | 135 | ::code-block{label="Preview" preview} 136 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 137 | ::div 138 | ::AppDropdown{:dropup="true"} 139 | :: 140 | :: 141 | 142 | ```html [Template] 143 | 144 | 147 | 148 | 149 | 150 | 157 | 158 | 159 | 160 | ``` 161 | :: 162 | 163 | #### Direction right 164 | 165 | ::code-group 166 | 167 | ::code-block{label="Preview" preview} 168 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 169 | ::div 170 | ::AppDropdown{direction="right"} 171 | :: 172 | :: 173 | 174 | ```html [Template] 175 | 176 | 179 | 180 | 181 | 182 | 189 | 190 | 191 | 192 | ``` 193 | :: 194 | 195 | #### Direction center 196 | 197 | ::code-group 198 | 199 | ::code-block{label="Preview" preview} 200 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 201 | ::div 202 | ::AppDropdown{direction="center"} 203 | :: 204 | :: 205 | 206 | ```html [Template] 207 | 208 | 211 | 212 | 213 | 214 | 221 | 222 | 223 | 224 | ``` 225 | :: 226 | 227 | #### Custom background color for overlay 228 | 229 | ::code-group 230 | 231 | ::code-block{label="Preview" preview} 232 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 233 | ::div 234 | ::AppDropdown{overlay-bg-color="rgba(1, 35, 83, 0.8)"} 235 | :: 236 | :: 237 | 238 | ```html [Template] 239 | 240 | 243 | 244 | 245 | 246 | 253 | 254 | 255 | 256 | ``` 257 | :: 258 | 259 | #### With dropdown closer 260 | 261 | ::code-group 262 | 263 | ::code-block{label="Preview" preview} 264 | ::div{style="min-height: 50vh; display: grid; place-items: center;"} 265 | ::div 266 | ::AppDropdownWithCloser 267 | :: 268 | :: 269 | 270 | ```html [Template] 271 | 272 | 275 | 276 | 277 | 278 | 286 | 287 | 288 | 289 | ``` 290 | :: 291 | 292 | #### Customized style & callbacks 293 | `Check your browser console for callbacks` 294 | 295 | ::code-group 296 | 297 | ::code-block{label="Preview" preview} 298 | ::div{style="min-height: 90vh; display: grid; place-items: center;"} 299 | ::div 300 | ::AppDropdownAdvanced 301 | :: 302 | :: 303 | 304 | ```html [Template] 305 | 306 | 309 | 310 | 311 | 312 | 319 | 320 | 321 | 322 | ``` 323 | 324 | ```css [Style (Scss)] 325 | .v-dropdown-menu { 326 | $this: &; 327 | 328 | &.custom-style & { 329 | &__container { 330 | border: 0; 331 | border-radius: 5px; 332 | box-shadow: 0 0 24px 0 rgba(0, 0, 0, 0.12); 333 | transform: translateY(-50px); 334 | #{$this} { 335 | &__header { 336 | display: flex; 337 | justify-content: center; 338 | padding: 5px; 339 | background-color: #eee; 340 | } 341 | 342 | &__body { 343 | ul { 344 | margin: 0; 345 | padding: 0; 346 | 347 | li { 348 | list-style: none; 349 | 350 | a { 351 | display: flex; 352 | padding: 1rem; 353 | color: #666; 354 | font-size: 16px; 355 | text-decoration: none; 356 | 357 | &:hover { 358 | background-color: #f2f2f2; 359 | } 360 | } 361 | } 362 | } 363 | } 364 | 365 | &__footer { 366 | display: flex; 367 | justify-content: center; 368 | padding: 5px; 369 | color: #fff; 370 | background-color: #012353; 371 | } 372 | } 373 | } 374 | } 375 | 376 | &.custom-style { 377 | // Custom Transition - Zoom Effect 378 | .zoom-enter-active { 379 | transition: all 0.6s cubic-bezier(0.075, 0.82, 0.165, 1); 380 | } 381 | 382 | .zoom-enter-from, 383 | .zoom-leave-to { 384 | transform: translateY(50px) scale(0.5); 385 | transform-origin: center top; 386 | opacity: 0; 387 | } 388 | &#{$this}--mode-hover { 389 | .zoom-enter-from, 390 | .zoom-leave-to { 391 | transition-delay: 0.4s; 392 | } 393 | } 394 | } 395 | } 396 | 397 | ``` 398 | 399 | ```js [Script] 400 | 409 | ``` 410 | :: 411 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { createResolver } from '@nuxt/kit' 2 | 3 | const { resolve } = createResolver(import.meta.url) 4 | 5 | export default defineNuxtConfig({ 6 | extends: '@nuxt-themes/docus', 7 | ssr: true, 8 | components: [ 9 | { 10 | prefix: '', 11 | path: resolve('./components'), 12 | global: true 13 | }, 14 | ] 15 | }) 16 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docus-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "preview": "nuxi preview", 10 | "lint": "eslint ." 11 | }, 12 | "devDependencies": { 13 | "@nuxt-themes/docus": "^1.9.0", 14 | "@nuxt/eslint-config": "^0.1.1", 15 | "eslint": "^8.35.0", 16 | "nuxt": "3.3.3" 17 | }, 18 | "dependencies": { 19 | "v-dropdown-menu": "^2.0.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/bea96f065f5ccc48776e5505637d195e3bbd7903/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/bea96f065f5ccc48776e5505637d195e3bbd7903/docs/public/media/logo.png -------------------------------------------------------------------------------- /docs/public/media/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/bea96f065f5ccc48776e5505637d195e3bbd7903/docs/public/media/preview.jpg -------------------------------------------------------------------------------- /docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ], 5 | "lockFileMaintenance": { 6 | "enabled": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTheme } from 'pinceau' 2 | 3 | export default defineTheme({ 4 | color: { 5 | primary: { 6 | 50: "#E5F8E5", 7 | 100: "#CCF2CC", 8 | 200: "#99CC99", 9 | 300: "#66CC66", 10 | 400: "#33CC33", 11 | 500: "#00CC00", 12 | 600: "#008500", 13 | 700: "#006600", 14 | 800: "#004200", 15 | 900: "#002100", 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /meta/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/bea96f065f5ccc48776e5505637d195e3bbd7903/meta/logo.png -------------------------------------------------------------------------------- /meta/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selimdoyranli/v-dropdown-menu/bea96f065f5ccc48776e5505637d195e3bbd7903/meta/preview.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-dropdown-menu", 3 | "version": "2.0.4", 4 | "description": "Customizable dropdown menu for vue 🟩🔽", 5 | "keywords": [ 6 | "vue dropdown", 7 | "vue dropdown menu", 8 | "vue3 dropdown", 9 | "vue2 dropdown" 10 | ], 11 | "author": "selimdoyranli", 12 | "contributors": [ 13 | "selimdoyranli (https://selimdoyranli.com)" 14 | ], 15 | "bugs": "selimdoyranli@gmail.com", 16 | "homepage": "https://v-dropdown-menu.vercel.app", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/selimdoyranli/v-dropdown-menu" 20 | }, 21 | "main": "dist/vue3/index.js", 22 | "module": "dist/vue3/v-dropdown-menu.mjs", 23 | "browser": "dist/vue3/v-dropdown-menu.global.js", 24 | "unpkg": "dist/vue3/v-dropdown-menu.global.js", 25 | "exports": { 26 | ".": { 27 | "require": "./dist/vue3/v-dropdown-menu.cjs", 28 | "import": "./dist/vue3/index.js", 29 | "browser": "./dist/vue3/v-dropdown-menu.global.js", 30 | "umd": "./dist/vue3/v-dropdown-menu.umd.js", 31 | "unpkg": "./dist/vue3/v-dropdown-menu.global.js", 32 | "module": "./dist/vue3/v-dropdown-menu.mjs" 33 | }, 34 | "./css": "./dist/vue3/v-dropdown-menu.css", 35 | "./vue2": "./dist/vue2/index.js", 36 | "./vue2/css": "./dist/vue2/v-dropdown-menu.css" 37 | }, 38 | "files": [ 39 | "dist", 40 | "vue2", 41 | "meta", 42 | "CHANGELOG.md" 43 | ], 44 | "sideEffects": false, 45 | "scripts": { 46 | "build:vue3": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue3.js", 47 | "build:umd:vue3": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue3.js --format umd", 48 | "build:es:vue3": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue3.js --format es", 49 | "build:cjs:vue3": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue3.js --format cjs", 50 | "build:vue2": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue2.js", 51 | "build:umd:vue2": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue2.js --format umd", 52 | "build:es:vue2": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue2.js --format es", 53 | "build:cjs:vue2": "cross-env NODE_ENV=production rollup --config build/rollup.config.vue2.js --format cjs", 54 | "build": "yarn build:vue3 && yarn build:vue2", 55 | "commit": "cz", 56 | "changelog": "changelogen", 57 | "lint:eslint": "eslint --ext .js,.ts,.json,.vue,.pug --ignore-path .gitignore --ignore-path .eslintignore .", 58 | "lint:eslint:fix": "eslint --fix --ext .js,.ts,.json,.vue,.pug --ignore-path .gitignore --ignore-path .eslintignore .", 59 | "lint:stylelint": "stylelint \"**/*.{css,sass,scss,less,stylus,vue}\" --ignore-path .stylelintignore", 60 | "lint:stylelint:fix": "stylelint --fix \"**/*.{css,sass,scss,less,stylus,vue}\" --ignore-path .stylelintignore", 61 | "prettier": "prettier --config ./.prettierrc.js --ignore-path ./.prettierignore --write \"**/*.{js,ts,json,css,scss,vue,html,pug}\" --end-of-line crlf" 62 | }, 63 | "lint-staged": { 64 | "*.{ts,js,vue}": [ 65 | "yarn lint:eslint", 66 | "yarn prettier" 67 | ], 68 | "**/*.{css,sass,scss,less,stylus,vue}": [ 69 | "yarn lint:stylelint", 70 | "yarn prettier" 71 | ] 72 | }, 73 | "dependencies": {}, 74 | "devDependencies": { 75 | "@babel/core": "^7.12.10", 76 | "@babel/plugin-transform-modules-umd": "^7.12.1", 77 | "@babel/preset-env": "^7.12.10", 78 | "@babel/preset-typescript": "^7.21.5", 79 | "@commitlint/cli": "^17.6.3", 80 | "@commitlint/config-conventional": "^17.6.3", 81 | "@prettier/plugin-pug": "^2.4.1", 82 | "@rollup/plugin-alias": "^3.1.1", 83 | "@rollup/plugin-babel": "^6.0.3", 84 | "@rollup/plugin-commonjs": "^24.1.0", 85 | "@rollup/plugin-node-resolve": "^15.0.2", 86 | "@rollup/plugin-replace": "^5.0.2", 87 | "@types/node": "^18.13.0", 88 | "@types/stylelint": "14.0.0", 89 | "@typescript-eslint/eslint-plugin": "^5.59.2", 90 | "@typescript-eslint/parser": "^5.14.0", 91 | "@vue/compiler-sfc": "^3.0.4", 92 | "autoprefixer": "^10.1.0", 93 | "babel-core": "^7.0.0-bridge.0", 94 | "babel-plugin-rename-umd-globals": "^1.0.0", 95 | "changelogen": "^0.5.3", 96 | "commitizen": "^4.3.0", 97 | "cross-env": "^7.0.3", 98 | "cz-conventional-changelog": "^3.3.0", 99 | "eslint": "^8.34.0", 100 | "eslint-config-prettier": "^8.6.0", 101 | "eslint-loader": "^4.0.2", 102 | "eslint-plugin-prettier": "^4.2.1", 103 | "eslint-plugin-vue": "^9.9.0", 104 | "flush-promises": "^1.0.2", 105 | "husky": "4.2.5", 106 | "lint-staged": "^13.2.2", 107 | "postcss": "^8.2.1", 108 | "postcss-html": "^1.5.0", 109 | "postcss-scss": "^4.0.6", 110 | "prettier": "^2.8.4", 111 | "pug": "^3.0.2", 112 | "pug-plain-loader": "^1.1.0", 113 | "rollup": "^2.34.2", 114 | "rollup-plugin-css-only": "2.0.0", 115 | "rollup-plugin-postcss": "^4.0.0", 116 | "rollup-plugin-scss": "^4.0.0", 117 | "rollup-plugin-terser": "^7.0.2", 118 | "sass": "^1.58.0", 119 | "sass-loader": "10.1.1", 120 | "stylelint": "14.16.1", 121 | "stylelint-config-prettier": "^9.0.5", 122 | "stylelint-config-rational-order": "^0.1.2", 123 | "stylelint-order": "^6.0.2", 124 | "stylelint-scss": "^4.4.0", 125 | "typescript": "^4.1.2", 126 | "vue-eslint-parser": "^9.1.0", 127 | "vue-eslint-parser-template-tokenizer-pug": "^0.4.10", 128 | "vue-next": "npm:vue@^3.2.20", 129 | "vue-next-rollup-plugin-vue": "npm:rollup-plugin-vue@^6.0.0", 130 | "vue-prev": "npm:vue@^2.6.14", 131 | "vue-prev-composition-api": "npm:@vue/composition-api@^1.2.4", 132 | "vue-prev-rollup-plugin-vue": "npm:rollup-plugin-vue@^5.1.9", 133 | "vue-template-compiler": "^2.6.14" 134 | }, 135 | "engines": { 136 | "node": ">=12" 137 | }, 138 | "license": "MIT", 139 | "config": { 140 | "commitizen": { 141 | "path": "cz-conventional-changelog" 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue-next' 3 | 4 | const Component: DefineComponent<{}, {}, any> 5 | export default Component 6 | } 7 | -------------------------------------------------------------------------------- /src/vue2/entry.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import component from './v-dropdown-menu.vue' 3 | 4 | // install function executed by Vue.use() 5 | const install = function installDropdownMenu(Vue) { 6 | if (install.installed) return 7 | install.installed = true 8 | Vue.component('DropdownMenu', component) 9 | } 10 | 11 | // Create module definition for Vue.use() 12 | const plugin = { 13 | install 14 | } 15 | 16 | // To auto-install on non-es builds, when vue is found 17 | // eslint-disable-next-line no-redeclare 18 | /* global window, global */ 19 | if ('false' === process.env.ES_BUILD) { 20 | let GlobalVue = null 21 | 22 | if (typeof window !== 'undefined') { 23 | GlobalVue = window.Vue 24 | } else if (typeof global !== 'undefined') { 25 | GlobalVue = global.Vue 26 | } 27 | 28 | if (GlobalVue) { 29 | GlobalVue.use(plugin) 30 | } 31 | } 32 | 33 | // Inject install function into component - allows component 34 | // to be registered via Vue.use() as well as Vue.component() 35 | component.install = install 36 | 37 | // Export component by default 38 | export default component 39 | 40 | // It's possible to expose named exports when writing components that can 41 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 42 | // export const RollupDemoDirective = component; 43 | -------------------------------------------------------------------------------- /src/vue2/v-dropdown-menu.scss: -------------------------------------------------------------------------------- 1 | .v-dropdown-menu { 2 | $this: &; 3 | $slide-offset: 12px; 4 | 5 | position: relative; 6 | display: inline-block; 7 | 8 | &__trigger { 9 | position: relative; 10 | } 11 | 12 | &__container { 13 | position: absolute; 14 | top: 100%; 15 | bottom: auto; 16 | min-width: 230px; 17 | max-width: 100%; 18 | overflow: hidden; 19 | background-color: #fff; 20 | border: 1px solid #ddd; 21 | } 22 | 23 | &--dropup & { 24 | &__container { 25 | top: auto; 26 | bottom: 100%; 27 | } 28 | } 29 | 30 | &--direction-left & { 31 | &__container { 32 | left: 0; 33 | } 34 | } 35 | 36 | &--direction-center & { 37 | &__container { 38 | left: 50%; 39 | transform: translateX(-50%) translateY(0); 40 | } 41 | } 42 | 43 | &--direction-right & { 44 | &__container { 45 | right: 0; 46 | } 47 | } 48 | 49 | &__overlay { 50 | position: fixed; 51 | top: 0; 52 | left: 0; 53 | width: 100%; 54 | height: 100vh; 55 | } 56 | 57 | // Default Transition 58 | .default-enter-active { 59 | transition: all 0.2s ease; 60 | } 61 | 62 | .default-leave-active { 63 | transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); 64 | } 65 | 66 | .default-enter, 67 | .default-leave-to { 68 | transform: translateY($slide-offset); 69 | opacity: 0; 70 | } 71 | 72 | &--mode-hover { 73 | .default-enter, 74 | .default-leave-active { 75 | transition-delay: 0.4s; 76 | } 77 | } 78 | 79 | &--dropup { 80 | .default-enter, 81 | .default-leave-to { 82 | transform: translateY(-$slide-offset); 83 | } 84 | &#{$this}--direction-center { 85 | .default-enter, 86 | .default-leave-to { 87 | transform: translateX(-50%) translateY(-$slide-offset); 88 | } 89 | } 90 | } 91 | 92 | &--direction-center { 93 | .default-enter, 94 | .default-leave-to { 95 | transform: translateX(-50%) translateY($slide-offset); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/vue2/v-dropdown-menu.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /src/vue3/entry.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import component from './v-dropdown-menu.vue' 3 | 4 | // Default export is installable instance of component. 5 | // IIFE injects install function into component, allowing component 6 | // to be registered via Vue.use() as well as Vue.component(), 7 | export default /*#__PURE__*/ (() => { 8 | // Assign InstallableComponent type 9 | const installable = component 10 | 11 | // Attach install function executed by Vue.use() 12 | installable.install = app => { 13 | app.component('DropdownMenu', installable) 14 | } 15 | 16 | return installable 17 | })() 18 | 19 | // It's possible to expose named exports when writing components that can 20 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 21 | // export const RollupDemoDirective = directive; 22 | -------------------------------------------------------------------------------- /src/vue3/v-dropdown-menu.scss: -------------------------------------------------------------------------------- 1 | .v-dropdown-menu { 2 | $this: &; 3 | $slide-offset: 12px; 4 | 5 | position: relative; 6 | display: inline-block; 7 | 8 | &__trigger { 9 | position: relative; 10 | } 11 | 12 | &__container { 13 | position: absolute; 14 | top: 100%; 15 | bottom: auto; 16 | min-width: 230px; 17 | max-width: 100%; 18 | overflow: hidden; 19 | background-color: #fff; 20 | border: 1px solid #ddd; 21 | } 22 | 23 | &--dropup & { 24 | &__container { 25 | top: auto; 26 | bottom: 100%; 27 | } 28 | } 29 | 30 | &--direction-left & { 31 | &__container { 32 | left: 0; 33 | } 34 | } 35 | 36 | &--direction-center & { 37 | &__container { 38 | left: 50%; 39 | transform: translateX(-50%) translateY(0); 40 | } 41 | } 42 | 43 | &--direction-right & { 44 | &__container { 45 | right: 0; 46 | } 47 | } 48 | 49 | &__overlay { 50 | position: fixed; 51 | top: 0; 52 | left: 0; 53 | width: 100%; 54 | height: 100vh; 55 | } 56 | 57 | // Default Transition 58 | .default-enter-active { 59 | transition: all 0.2s ease; 60 | } 61 | 62 | .default-leave-active { 63 | transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); 64 | } 65 | 66 | .default-enter-from, 67 | .default-leave-to { 68 | transform: translateY($slide-offset); 69 | opacity: 0; 70 | } 71 | 72 | &--mode-hover { 73 | .default-enter-from, 74 | .default-leave-active { 75 | transition-delay: 0.4s; 76 | } 77 | } 78 | 79 | &--dropup { 80 | .default-enter-from, 81 | .default-leave-to { 82 | transform: translateY(-$slide-offset); 83 | } 84 | &#{$this}--direction-center { 85 | .default-enter-from, 86 | .default-leave-to { 87 | transform: translateX(-50%) translateY(-$slide-offset); 88 | } 89 | } 90 | } 91 | 92 | &--direction-center { 93 | .default-enter-from, 94 | .default-leave-to { 95 | transform: translateX(-50%) translateY($slide-offset); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/vue3/v-dropdown-menu.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "declaration": true, 7 | "declarationDir": "dist/types", 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "experimentalDecorators": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "newLine": "crlf", 18 | "types": ["node", "@types/node", "vue-prev", "vue-next"], 19 | "paths": { 20 | "@/*": ["src/*"] 21 | }, 22 | "plugins": [ 23 | { 24 | "transform": "@zerollup/ts-transform-paths", 25 | "exclude": ["*"] 26 | } 27 | ], 28 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 29 | }, 30 | "exclude": ["node_modules", "dist", "**/*.spec.ts"] 31 | } 32 | -------------------------------------------------------------------------------- /vue2/README.md: -------------------------------------------------------------------------------- 1 | entry point for vue2 include 2 | 3 | ```js 4 | import DropdownMenu from 'v-dropdown-menu/vue2' 5 | ``` 6 | -------------------------------------------------------------------------------- /vue2/index.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).DropdownMenu={})}(this,(function(e){"use strict";function n(e,n,t,o,i,s,r,d,l,a){"boolean"!=typeof r&&(l=d,d=r,r=!1);const u="function"==typeof t?t.options:t;let p;if(e&&e.render&&(u.render=e.render,u.staticRenderFns=e.staticRenderFns,u._compiled=!0,i&&(u.functional=!0)),o&&(u._scopeId=o),s?(p=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,l(e)),e&&e._registeredComponents&&e._registeredComponents.add(s)},u._ssrRegister=p):n&&(p=r?function(e){n.call(this,a(e,this.$root.$options.shadowRoot))}:function(e){n.call(this,d(e))}),p)if(u.functional){const e=u.render;u.render=function(n,t){return p.call(t),e(n,t)}}else{const e=u.beforeCreate;u.beforeCreate=e?[].concat(e,p):[p]}return t}const t=n({render:function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("div",{ref:"rootRef",staticClass:"v-dropdown-menu",class:[e.activeClass,e.modeClass,e.dropupClass,e.directionClass]},["click"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{click:function(n){n.preventDefault(),e.menu.isOpen=!e.menu.isOpen}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),"hover"===e.menu.mode?[t("div",{ref:"triggerRef",staticClass:"v-dropdown-menu__trigger",on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[e._t("trigger")],2),t("transition",{attrs:{name:e.menu.transition}},[t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],staticClass:"v-dropdown-menu__container",style:{"z-index":e.menu.containerZIndex},on:{mouseover:function(n){return n.preventDefault(),e.show.apply(null,arguments)},mouseleave:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}},[t("div",{staticClass:"v-dropdown-menu__header"},[e._t("header")],2),t("div",{staticClass:"v-dropdown-menu__body"},[e._t("body")],2),t("div",{staticClass:"v-dropdown-menu__footer"},[e._t("footer")],2)])])]:e._e(),e.menu.overlay&&e.menu.closeOnClickOutside&&"click"===e.menu.mode?t("div",{directives:[{name:"show",rawName:"v-show",value:e.menu.isOpen,expression:"menu.isOpen"}],ref:"overlayRef",staticClass:"v-dropdown-menu__overlay",style:{"background-color":e.menu.overlayBgColor,"z-index":e.menu.overlayZIndex},on:{mousedown:function(n){return n.preventDefault(),e.hide.apply(null,arguments)}}}):e._e()],2)},staticRenderFns:[]},undefined,{name:"DropdownMenu",props:{isOpen:{type:Boolean,required:!1,default:!1},mode:{type:String,required:!1,default:"click"},dropup:{type:Boolean,required:!1,default:!1},direction:{type:String,required:!1,default:"left"},closeOnClickOutside:{type:Boolean,required:!1,default:!0},withDropdownCloser:{type:Boolean,required:!1,default:!1},containerZIndex:{type:String,required:!1,default:"994"},overlay:{type:Boolean,required:!1,default:!0},overlayBgColor:{type:String,required:!1,default:"rgba(0, 0, 0, 0.2)"},overlayZIndex:{type:String,required:!1,default:"992"},transition:{type:String,required:!1,default:"default"}},data(){return{baseClassName:"v-dropdown-menu",menu:{isOpen:this.isOpen,mode:this.mode,dropup:this.dropup,direction:this.direction,closeOnClickOutside:this.closeOnClickOutside,withDropdownCloser:this.withDropdownCloser,containerZIndex:this.containerZIndex,overlay:this.overlay,overlayBgColor:this.overlayBgColor,overlayZIndex:this.overlayZIndex,transition:this.transition}}},computed:{activeClass(){return this.menu.isOpen?`${this.baseClassName}--active`:null},modeClass(){return"click"===this.menu.mode?`${this.baseClassName}--mode-click`:`${this.baseClassName}--mode-hover`},dropupClass(){return this.menu.dropup?`${this.baseClassName}--dropup`:null},directionClass(){let e=null;return e="left"===this.menu.direction?`${this.baseClassName}--direction-left`:"center"===this.menu.direction?`${this.baseClassName}--direction-center`:`${this.baseClassName}--direction-right`,e}},watch:{isOpen(e){"click"===this.menu.mode&&(e?setTimeout((()=>{this.show()}),1):setTimeout((()=>{this.hide()}),1))},"menu.isOpen"(e){"click"===this.menu.mode&&(e?this.$emit("opened",this.$props):this.$emit("closed",this.$props))}},mounted(){this.dropdownCloser(),this.$nextTick((()=>{this.menu.closeOnClickOutside&&this.registerCloseDropdownOnClickOutside()})),this.closeDropdownOnPopState()},beforeDestroy(){this.destroyCloseDropdownOnClickOutside(),this.destroyCloseDropdownOnPopState()},methods:{show(){this.menu.isOpen=!0},hide(){this.menu.isOpen=!1},registerCloseDropdownOnClickOutside(){window.addEventListener("click",this.closeDropdownOnClickOutside)},closeDropdownOnClickOutside(e){this.menu.isOpen&&(this.$refs.rootRef.contains(e.target)||(this.menu.isOpen=!1))},destroyCloseDropdownOnClickOutside(){this.menu.closeOnClickOutside&&window.removeEventListener("click",this.closeDropdownOnClickOutside)},dropdownCloser(){if(this.menu.withDropdownCloser){this.$refs.rootRef.querySelectorAll("[dropdown-closer]").forEach((e=>{e.addEventListener("click",(()=>{this.menu.isOpen=!1}))}))}},closeDropdownOnPopState(){window.addEventListener("popstate",(()=>{this.menu.isOpen&&(this.menu.isOpen=!1)}))},destroyCloseDropdownOnPopState(){window.removeEventListener("popstate",this.closeDropdownOnPopState)}}},undefined,false,undefined,!1,void 0,void 0,void 0),o=function(e){o.installed||(o.installed=!0,e.component("DropdownMenu",t))},i={install:o};{let e=null;"undefined"!=typeof window?e=window.Vue:"undefined"!=typeof global&&(e=global.Vue),e&&e.use(i)}t.install=o,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})})); 2 | --------------------------------------------------------------------------------