├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .np-config.js ├── .prettierrc ├── .travis.yml ├── README.md ├── babel.config.js ├── demo └── demo-documentation.gif ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── components │ └── EmailDropdown.vue └── main.js └── tests └── unit ├── .eslintrc.js └── email-dropdown.spec.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "eslint:recommended"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 10 | "vue/no-side-effects-in-computed-properties": "off", 11 | "max-len": ["error", { code: 120 }] 12 | }, 13 | parserOptions: { 14 | parser: "babel-eslint" 15 | }, 16 | overrides: [ 17 | { 18 | files: ["**/__tests__/*.{j,t}s?(x)"], 19 | env: { 20 | mocha: true 21 | } 22 | } 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | package-lock.json 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /.np-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cleanup: false, 3 | publish: false 4 | }; -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | os: linux 3 | node_js: 4 | - 10 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm run test 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-email-dropdown 2 | 3 | A Vue component for autocomplete email domains 4 | 5 | [![Build Status](https://travis-ci.org/DannyFeliz/vue-email-dropdown.svg?branch=master)](https://travis-ci.org/DannyFeliz/vue-email-dropdown) 6 | [](https://www.npmjs.com/package/vue-email-dropdown) 7 | [](https://www.npmjs.com/package/vue-email-dropdown) 8 | 9 | # Features 10 | 11 | - Allows passing a list of domains to be used in for the suggestions. 12 | - Allows passing a list of default domains that are going to be used when type `@`. 13 | - Closes the list by pressing Esc. 14 | - Allows the navigate the list by pressing Up / Down. 15 | - Closes the list on click outside. 16 | - Allows configuring the list size. 17 | 18 | # Demo 19 | 20 | [![Edit Demo vue-email-dropdown](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/vue-template-lrkul?fontsize=14) 21 | 22 | ![Demo](https://raw.githubusercontent.com/DannyFeliz/vue-email-dropdown/master/demo/demo-documentation.gif) 23 | 24 | # Props 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
PropTypeRequiredDefaultDescription
domainsArrayTrue-All domains that should be used to make a domain suggestions.
defaultDomainsArrayFalse[]Default domains that should be displayed once `@` is typed.
initialValueStringFalse""Initial value for the email field.
maxSuggestionsNumberFalse4How many domain suggestions should displayed.
closeOnClickOutsideBooleanFalsetrueHide the suggestion list if you click outside the list.
inputClassesString|Array|ObjectFalse""Classes that will be apply to the email field.
81 | 82 | ## Installation 83 | 84 | ```bash 85 | npm install vue-email-dropdown --save 86 | 87 | # or with yarn 88 | 89 | yarn add vue-email-dropdown 90 | ``` 91 | 92 | ## Usage 93 | 94 | ```vue 95 | 98 | 99 | 128 | ``` 129 | 130 | ## Development setup 131 | 132 | ``` 133 | npm install 134 | ``` 135 | 136 | ### Compiles and hot-reloads for development 137 | 138 | ``` 139 | npm run serve 140 | ``` 141 | 142 | ### Compiles and minifies for production 143 | 144 | ``` 145 | npm run build 146 | ``` 147 | 148 | ### Run your unit tests 149 | 150 | ``` 151 | npm run test 152 | ``` 153 | 154 | ### Lints and fixes files 155 | 156 | ``` 157 | npm run lint 158 | ``` 159 | 160 | ### Customize configuration 161 | 162 | See [Configuration Reference](https://cli.vuejs.org/config/). 163 | 164 | # Contributing 165 | 166 | 1. Fork it () 167 | 2. Create your feature branch (`git checkout -b feature/fooBar`) 168 | 3. Commit your changes (`git commit -am 'Add some fooBar'`) 169 | 4. Push to the branch (`git push origin feature/fooBar`) 170 | 5. Create a new Pull Request 171 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/demo-documentation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DannyFeliz/vue-email-dropdown/25a7f9338dd0a003fde8197fbf2cac43d3a6d0fc/demo/demo-documentation.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-email-dropdown", 3 | "version": "2.2.3", 4 | "main": "./dist/vue-email-dropdown.umd.min.js", 5 | "files": [ 6 | "dist/vue-email-dropdown.umd.min.js", 7 | "dist/vue-email-dropdown.css" 8 | ], 9 | "keywords": [ 10 | "vue", 11 | "vue-dropdown", 12 | "vue-email", 13 | "vue-email-dropdown", 14 | "autocomplete", 15 | "autocomplete-email", 16 | "vue-autocomplete-email" 17 | ], 18 | "author": "Danny Feliz ", 19 | "license": "MIT", 20 | "bugs": "https://github.com/DannyFeliz/vue-email-dropdown/issues/new", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/DannyFeliz/vue-email-dropdown" 24 | }, 25 | "scripts": { 26 | "serve": "vue-cli-service serve", 27 | "build": "vue-cli-service build", 28 | "bundle": "./node_modules/@vue/cli-service/bin/vue-cli-service.js build --target lib --name vue-email-dropdown ./src/components/EmailDropdown.vue", 29 | "generate-tag": "npm run bundle && ./node_modules/np/source/cli.js", 30 | "publish": "npm publish --access public", 31 | "test": "vue-cli-service test:unit", 32 | "test:watch": "vue-cli-service test:unit --watch", 33 | "lint": "vue-cli-service lint" 34 | }, 35 | "dependencies": { 36 | "core-js": "^3.4.7", 37 | "keycoder": "^1.1.1", 38 | "v-click-outside": "^3.0.0", 39 | "vue": "^2.6.10" 40 | }, 41 | "devDependencies": { 42 | "@vue/cli-plugin-babel": "^4.1.1", 43 | "@vue/cli-plugin-eslint": "^4.1.1", 44 | "@vue/cli-plugin-unit-mocha": "^4.1.1", 45 | "@vue/cli-service": "^4.1.1", 46 | "@vue/test-utils": "1.0.0-beta.30", 47 | "babel-eslint": "^10.0.3", 48 | "chai": "^4.2.0", 49 | "deep-equal-in-any-order": "^1.0.21", 50 | "eslint": "^6.7.2", 51 | "eslint-plugin-vue": "^6.0.1", 52 | "np": "^5.2.1", 53 | "sass": "^1.23.7", 54 | "sass-loader": "^8.0.0", 55 | "vue-template-compiler": "^2.6.10" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DannyFeliz/vue-email-dropdown/25a7f9338dd0a003fde8197fbf2cac43d3a6d0fc/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-email-dropdown 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 71 | 72 | 83 | -------------------------------------------------------------------------------- /src/components/EmailDropdown.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 240 | 241 | 328 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App), 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | } -------------------------------------------------------------------------------- /tests/unit/email-dropdown.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import { expect } from "chai"; 3 | import { shallowMount } from "@vue/test-utils"; 4 | import EmailDropdown from "@/components/EmailDropdown.vue"; 5 | 6 | const deepEqualInAnyOrder = require("deep-equal-in-any-order"); 7 | const chai = require("chai"); 8 | 9 | chai.use(deepEqualInAnyOrder); 10 | 11 | describe("EmailDropdown.vue", () => { 12 | let propsData; 13 | 14 | beforeEach(() => { 15 | propsData = { 16 | initialValue: "hello@", 17 | domains: ["google.com"], 18 | defaultDomains: ["hotmail.com", "outlook.com"], 19 | maxSuggestions: 4, 20 | closeOnClickOutside: true 21 | }; 22 | }); 23 | 24 | it("renders without show suggestions if the email does not include '@'", () => { 25 | propsData.initialValue = "hello"; 26 | 27 | const wrapper = shallowMount(EmailDropdown, { 28 | propsData 29 | }); 30 | 31 | expect(wrapper.find(".email-dropdown-list").exists()).to.be.false; 32 | }); 33 | 34 | it("shows suggestions with default domains", () => { 35 | const wrapper = shallowMount(EmailDropdown, { 36 | propsData 37 | }); 38 | 39 | const dropdownItems = wrapper.findAll(".email-dropdown-item"); 40 | expect(dropdownItems).to.have.length(2); 41 | expect(dropdownItems.at(0).text()).to.equal("hello@hotmail.com"); 42 | expect(dropdownItems.at(1).text()).to.equal("hello@outlook.com"); 43 | }); 44 | 45 | it("shows suggestions based in initialValue", () => { 46 | propsData.domains = ["gmail.com", "google.com", "outlook.com"]; 47 | propsData.initialValue = "hello@g"; 48 | 49 | const wrapper = shallowMount(EmailDropdown, { 50 | propsData 51 | }); 52 | 53 | const dropdownItems = wrapper.findAll(".email-dropdown-item"); 54 | 55 | expect(dropdownItems).to.have.length(2); 56 | expect(dropdownItems.at(0).text()).to.be.equal("hello@gmail.com"); 57 | expect(dropdownItems.at(1).text()).to.be.equal("hello@google.com"); 58 | }); 59 | 60 | it("hides suggestions if email match the suggestion", () => { 61 | propsData.initialValue = "hello@google.com"; 62 | 63 | const wrapper = shallowMount(EmailDropdown, { 64 | propsData 65 | }); 66 | 67 | expect(wrapper.text()).to.be.empty; 68 | expect(wrapper.find(".email-dropdown-list").exists()).to.be.false; 69 | expect(wrapper.vm.isOptionSelected).to.be.true; 70 | }); 71 | 72 | it("filter the suggestion list when type in the input", async () => { 73 | propsData.initialValue = "hello@g"; 74 | propsData.domains = ["gmail.com", "google.com"]; 75 | 76 | const wrapper = shallowMount(EmailDropdown, { 77 | propsData 78 | }); 79 | 80 | expect(wrapper.findAll(".email-dropdown-item")).to.have.length(2); 81 | wrapper.find("input").setValue("hello@gma"); 82 | await Vue.nextTick(); 83 | expect(wrapper.findAll(".email-dropdown-item")).to.have.length(1); 84 | expect(wrapper.find(".email-dropdown-item").text()).to.be.equal("hello@gmail.com"); 85 | }); 86 | 87 | it("hides suggestion list if remove '@' from the email", async () => { 88 | const wrapper = shallowMount(EmailDropdown, { 89 | propsData 90 | }); 91 | 92 | expect(wrapper.findAll(".email-dropdown-item")).to.have.length(2); 93 | wrapper.find("input").setValue("hello"); 94 | await Vue.nextTick(); 95 | expect(wrapper.find(".email-dropdown-list").exists()).to.be.false; 96 | }); 97 | 98 | describe("events", () => { 99 | it("emits 'input' on email change", async () => { 100 | propsData.domains = ["gmail.com", "google.com"]; 101 | 102 | const wrapper = shallowMount(EmailDropdown, { 103 | propsData 104 | }); 105 | 106 | wrapper.find("input").setValue("hello@gmail"); 107 | await Vue.nextTick(); 108 | expect(wrapper.emitted().input[0]).to.have.length(1); 109 | expect(wrapper.emitted().input[0][0]).to.be.equal("hello@gmail"); 110 | wrapper.find("input").setValue("hello@gmail."); 111 | await Vue.nextTick(); 112 | expect(wrapper.emitted().input[1]).to.have.length(1); 113 | expect(wrapper.emitted().input[1][0]).to.be.equal("hello@gmail."); 114 | }); 115 | }); 116 | 117 | describe("computed", () => { 118 | describe("includesAt", () => { 119 | it("returns 'true' if email includes '@'", () => { 120 | const wrapper = shallowMount(EmailDropdown, { 121 | propsData 122 | }); 123 | 124 | expect(wrapper.vm.includesAt).to.be.true; 125 | }); 126 | 127 | it("returns 'false' if email does not includes '@'", () => { 128 | propsData.initialValue = "hello"; 129 | 130 | const wrapper = shallowMount(EmailDropdown, { 131 | propsData 132 | }); 133 | 134 | expect(wrapper.vm.includesAt).to.be.false; 135 | }); 136 | }); 137 | 138 | describe("username", () => { 139 | it("returns the email without domain", () => { 140 | propsData.initialValue = "hello@google.com"; 141 | 142 | const wrapper = shallowMount(EmailDropdown, { 143 | propsData 144 | }); 145 | 146 | expect(wrapper.vm.username).to.be.equal("hello"); 147 | }); 148 | }); 149 | 150 | describe("domain", () => { 151 | it("returns the email domain", () => { 152 | propsData.initialValue = "hello@google.com"; 153 | 154 | const wrapper = shallowMount(EmailDropdown, { 155 | propsData 156 | }); 157 | 158 | expect(wrapper.vm.domain).to.be.equal("google.com"); 159 | }); 160 | }); 161 | 162 | describe("isOptionSelected", () => { 163 | it("returns 'true' if the email match an item from the suggestion list", () => { 164 | propsData.initialValue = "hello@google.com"; 165 | 166 | const wrapper = shallowMount(EmailDropdown, { 167 | propsData 168 | }); 169 | 170 | expect(wrapper.vm.isOptionSelected).to.be.true; 171 | }); 172 | 173 | it("returns 'false' if the email doesn't match an item from the suggestion list", () => { 174 | propsData.initialValue = "hello@google.com"; 175 | 176 | const wrapper = shallowMount(EmailDropdown, { 177 | propsData 178 | }); 179 | 180 | expect(wrapper.vm.isOptionSelected).to.be.true; 181 | }); 182 | }); 183 | 184 | describe("domainsList", () => { 185 | it("returns an empty array if includesAt is 'false'", () => { 186 | propsData.initialValue = "hello"; 187 | 188 | const wrapper = shallowMount(EmailDropdown, { 189 | propsData 190 | }); 191 | 192 | expect(wrapper.vm.includesAt).to.be.false; 193 | expect(wrapper.vm.domainsList).to.be.empty; 194 | }); 195 | 196 | it("returns default domains if doesn't have domain", () => { 197 | const wrapper = shallowMount(EmailDropdown, { 198 | propsData 199 | }); 200 | expect(wrapper.vm.includesAt).to.be.true; 201 | expect(wrapper.vm.domain).to.be.have.length(0); 202 | expect(wrapper.vm.defaultDomains).to.be.have.length.gt(0); 203 | expect(wrapper.vm.domainsList).to.deep.equalInAnyOrder(propsData.defaultDomains); 204 | }); 205 | 206 | it("returns the domains based on 'domain'", () => { 207 | propsData.domains = ["google.com", "gmail.com"]; 208 | propsData.initialValue = "hello@g"; 209 | 210 | const wrapper = shallowMount(EmailDropdown, { 211 | propsData 212 | }); 213 | expect(wrapper.vm.includesAt).to.be.true; 214 | expect(wrapper.vm.domain).to.be.have.length(1); 215 | expect(wrapper.vm.domainsList).to.deep.equalInAnyOrder(propsData.domains); 216 | }); 217 | 218 | it("return an empty list if doesn't have default domains", () => { 219 | propsData.domains = ["google.com", "gmail.com"]; 220 | propsData.defaultDomains = []; 221 | propsData.initialValue = "hello@"; 222 | 223 | const wrapper = shallowMount(EmailDropdown, { 224 | propsData 225 | }); 226 | 227 | expect(wrapper.vm.domainsList).to.be.have.length(0); 228 | }); 229 | }); 230 | 231 | describe("suggestionList", () => { 232 | it("returns a suggestion domain list based on the domain list", () => { 233 | propsData.domains = ["google.com", "gmail.com"]; 234 | propsData.initialValue = "hello@g"; 235 | 236 | const expected = ["hello@gmail.com", "hello@google.com"]; 237 | 238 | const wrapper = shallowMount(EmailDropdown, { 239 | propsData 240 | }); 241 | 242 | expect(wrapper.vm.suggestionList).to.deep.equalInAnyOrder(expected); 243 | }); 244 | 245 | it("returns a suggestion domain list using the default domains", () => { 246 | propsData.defaultDomains = ["yahoo.com", "mns.com"]; 247 | 248 | const expected = ["hello@yahoo.com", "hello@mns.com"]; 249 | const wrapper = shallowMount(EmailDropdown, { 250 | propsData 251 | }); 252 | expect(wrapper.vm.includesAt).to.be.true; 253 | expect(wrapper.vm.domain).to.be.have.length(0); 254 | expect(wrapper.vm.suggestionList).to.deep.equalInAnyOrder(expected); 255 | }); 256 | }); 257 | 258 | describe("shouldShowList", () => { 259 | it("returns 'true' if domainsList is 'true' and isOptionSelected is 'false'", () => { 260 | propsData.initialValue = "hello@"; 261 | const wrapper = shallowMount(EmailDropdown, { 262 | propsData 263 | }); 264 | 265 | expect(wrapper.vm.domainsList).to.have.length.gt(0); 266 | expect(wrapper.vm.isOptionSelected).to.be.false; 267 | expect(wrapper.vm.shouldShowList).to.be.true; 268 | }); 269 | 270 | it("returns 'false' if 'domainsList' is empty", () => { 271 | propsData.initialValue = "hello"; 272 | const wrapper = shallowMount(EmailDropdown, { 273 | propsData 274 | }); 275 | expect(wrapper.vm.domainsList).to.be.eql([]); 276 | expect(wrapper.vm.shouldShowList).to.be.false; 277 | }); 278 | 279 | it("returns 'false' if 'isOptionSelected' is false", () => { 280 | propsData.initialValue = "hello"; 281 | const wrapper = shallowMount(EmailDropdown, { 282 | propsData 283 | }); 284 | 285 | expect(wrapper.vm.isOptionSelected).to.be.false; 286 | expect(wrapper.vm.shouldShowList).to.be.false; 287 | }); 288 | }); 289 | }); 290 | }); 291 | --------------------------------------------------------------------------------