├── .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 | [](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 | [](https://codesandbox.io/s/vue-template-lrkul?fontsize=14)
21 |
22 | 
23 |
24 | # Props
25 |
26 |
27 |
28 |
29 | Prop |
30 | Type |
31 | Required |
32 | Default |
33 | Description |
34 |
35 |
36 |
37 |
38 | domains |
39 | Array |
40 | True |
41 | - |
42 | All domains that should be used to make a domain suggestions. |
43 |
44 |
45 | defaultDomains |
46 | Array |
47 | False |
48 | [] |
49 | Default domains that should be displayed once `@` is typed. |
50 |
51 |
52 | initialValue |
53 | String |
54 | False |
55 | "" |
56 | Initial value for the email field. |
57 |
58 |
59 | maxSuggestions |
60 | Number |
61 | False |
62 | 4 |
63 | How many domain suggestions should displayed. |
64 |
65 |
66 | closeOnClickOutside |
67 | Boolean |
68 | False |
69 | true |
70 | Hide the suggestion list if you click outside the list. |
71 |
72 |
73 | inputClasses |
74 | String|Array|Object |
75 | False |
76 | "" |
77 | Classes that will be apply to the email field. |
78 |
79 |
80 |
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 |
96 |
97 |
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 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
71 |
72 |
83 |
--------------------------------------------------------------------------------
/src/components/EmailDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
22 |
23 | -
36 | {{ username }}
37 | @{{ domain }}
38 |
39 |
40 |
41 |
42 |
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 |
--------------------------------------------------------------------------------