Skip to content

Commit 7e45880

Browse files
authored
Merge pull request #13 from mayank1513/update-docs
Update docs + update styles, support passing custom input params
2 parents 603b97b + 595428f commit 7e45880

File tree

12 files changed

+455
-69
lines changed

12 files changed

+455
-69
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
name: test
22

3-
on: [push, pull_request]
4-
3+
on:
4+
push:
5+
pull_request:
6+
schedule:
7+
- cron: '0 8 * * *' # every 30 minutes
58
jobs:
69
test:
710
runs-on: ubuntu-latest

lib/TagInput.vue

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, watch, nextTick, onMounted, computed } from "vue";
2+
import { ref, watch, nextTick, onMounted, computed, InputHTMLAttributes } from "vue";
33
defineOptions({ name: "TagInput" });
44
export interface TagInputProps {
55
modelValue: string[];
@@ -10,7 +10,8 @@ export interface TagInputProps {
1010
tagBgColor?: string;
1111
tagClass?: string;
1212
customDelimiter?: string[] | string;
13-
singleLine: boolean
13+
singleLine?: boolean
14+
inputProps?: InputHTMLAttributes
1415
}
1516
1617
const props = withDefaults(defineProps<TagInputProps>(), {
@@ -30,7 +31,7 @@ const tags = ref<string[]>(props.modelValue);
3031
const tagsClass = ref(props.tagClass);
3132
const newTag = ref("");
3233
const focused = ref(false);
33-
const id = Math.random().toString(36).substring(7);
34+
const activeOptionInd = ref(-1);
3435
const customDelimiter = computed<string[] | string>(() => [
3536
...new Set(
3637
(typeof props.customDelimiter == "string"
@@ -56,6 +57,7 @@ function handleNoMatchingTag() {
5657
newTag.value = v.slice(0, v.length - 1);
5758
}
5859
const addTag = (tag: string) => {
60+
console.log({ tag })
5961
tag = tag.trim();
6062
if (!tag) return; // prevent empty tag
6163
// only allow predefined tags when allowCustom is false
@@ -71,6 +73,7 @@ const addTag = (tag: string) => {
7173
}
7274
tags.value.push(tag);
7375
newTag.value = ""; // reset newTag
76+
activeOptionInd.value = -1;
7477
};
7578
const addTagIfDelem = (tag: string) => {
7679
if (!customDelimiter.value || customDelimiter.value.length == 0) return;
@@ -96,8 +99,8 @@ onMounted(onTagsChange);
9699
97100
// options
98101
const availableOptions = computed(() => {
99-
if (!props.options) return false;
100-
return props.options.filter((option) => !tags.value.includes(option));
102+
if (!props.options) return [];
103+
return props.options.filter((option) => newTag.value && !tags.value.includes(option) && option.match(new RegExp(newTag.value, 'i')));
101104
});
102105
103106
const shouldDelete = ref<boolean>(false);
@@ -116,7 +119,8 @@ const deleteLastTag = () => {
116119
}
117120
}
118121
};
119-
const inputElId = `tag-input${Math.random()}`
122+
const id = Math.random().toString(36).substring(7);
123+
const inputElId = `tag-input${id}`
120124
</script>
121125

122126
<template>
@@ -132,15 +136,19 @@ const inputElId = `tag-input${Math.random()}`
132136
<button class="delete" @click="removeTag(index)">x</button>
133137
</li>
134138
<div class="tag-input">
135-
<input v-model="newTag" :id="inputElId" type="text" :list="id" autocomplete="off" @keydown.enter="addTag(newTag)"
139+
<input v-model="newTag" :id="inputElId" type="text" autocomplete="off"
140+
@keydown.enter="addTag(activeOptionInd > -1 ? availableOptions[activeOptionInd] : newTag)"
136141
@keydown.prevent.tab="addTag(newTag)" @keydown.delete="deleteLastTag()" @input="addTagIfDelem(newTag)"
137-
placeholder="Enter tag" @focus="focused = true" @blur="focused = false" />
142+
@keydown.down="activeOptionInd = (activeOptionInd + 1) % availableOptions.length"
143+
@keydown.up="activeOptionInd = (availableOptions.length + activeOptionInd - 1) % availableOptions.length"
144+
placeholder="Enter tag" @focus="focused = true" @blur="focused = false" v-bind="inputProps" />
138145

139-
<datalist v-if="options" :id="id">
140-
<option v-for="option in availableOptions" :key="option" :value="option">
146+
<ul class="options">
147+
<li v-for="(option, i) in availableOptions" :key="option" @click="addTag(option)"
148+
:class="{ active: i === activeOptionInd }">
141149
{{ option }}
142-
</option>
143-
</datalist>
150+
</li>
151+
</ul>
144152
</div>
145153
<div v-if="showCount" class="count">
146154
<span>{{ tags.length }}</span> tags
@@ -155,6 +163,32 @@ const inputElId = `tag-input${Math.random()}`
155163
box-sizing: border-box;
156164
}
157165
166+
.options {
167+
position: absolute;
168+
top: 35px;
169+
list-style-type: none;
170+
padding: 0;
171+
visibility: hidden;
172+
transition: visibility 1s;
173+
overflow: auto;
174+
}
175+
176+
input:focus~.options {
177+
visibility: visible;
178+
}
179+
180+
.options li {
181+
padding: 10px;
182+
background: #333;
183+
color: #eee;
184+
cursor: pointer;
185+
}
186+
187+
.options li:hover,
188+
.options li.active {
189+
background: #555;
190+
}
191+
158192
.tag-input {
159193
position: relative;
160194
width: 250px;
@@ -171,7 +205,6 @@ const inputElId = `tag-input${Math.random()}`
171205
margin: 0;
172206
padding: 10px;
173207
left: 10px;
174-
max-width: 75%;
175208
border-bottom: 1px solid #5558;
176209
cursor: text;
177210

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
44
"private": false,
55
"description": "A versatile tag input component built with Vue 3 Composition API",
6-
"version": "1.0.5",
6+
"version": "1.1.0",
77
"type": "module",
88
"repository": {
99
"type": "git",

src/App.vue

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
<script setup lang="ts">
2-
import { RouterView, RouterLink } from "vue-router";
2+
import { computed } from "vue";
3+
import { RouterView, RouterLink, useRoute } from "vue-router";
4+
5+
const examples = [
6+
["Autocomplete", "autocomplete"],
7+
["Custom Delimeter", "custom-delimeter"],
8+
["Input Params", "custom-placeholder"]
9+
];
10+
const path = computed(() => useRoute().path);
311
</script>
412

513
<template>
@@ -9,15 +17,30 @@ import { RouterView, RouterLink } from "vue-router";
917
A versatile tag input component built with Vue 3 Composition API.
1018
<hr />
1119
<p>
12-
<router-link to="/">Home</router-link>
13-
<router-link to="/getting-started">Getting Started</router-link>
14-
<details>
15-
<summary>Examples</summary>
16-
</details>
20+
<router-link to="/" :class="{ active: path === '/' }">Features</router-link>
21+
<router-link to="/getting-started" :class="{ active: path === '/getting-started' }">Getting Started</router-link>
22+
<details open>
23+
<summary>Examples</summary>
24+
<router-link v-for="example in examples" :to="`/examples/${example[1]}`"
25+
:class="{ active: path === `/examples/${example[1]}` }">{{ example[0] }}</router-link>
26+
</details>
1727
</p>
1828
</aside>
1929
<main>
2030
<router-view />
31+
32+
<div class="grow"></div>
33+
<footer>
34+
<p>
35+
License:
36+
<a href="https://github.com/mayank1513/tag-input/blob/master/LICENSE" target="_blank"
37+
rel="noopener noreferrer">MIT</a>
38+
<br />
39+
<br />
40+
Copyright © 2023
41+
<a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a>
42+
</p>
43+
</footer>
2144
</main>
2245
</div>
2346
</template>
@@ -28,43 +51,95 @@ body {
2851
padding: 0;
2952
margin: 0;
3053
}
31-
a {
32-
color: inherit;
33-
display: block;
34-
padding: 10px;
54+
55+
details {
56+
padding: 15px;
57+
58+
summary {
59+
margin-left: -5px;
60+
padding-bottom: 5px;
61+
cursor: pointer;
62+
}
3563
}
64+
3665
#app {
3766
font-family: Avenir, Helvetica, Arial, sans-serif;
3867
-webkit-font-smoothing: antialiased;
3968
-moz-osx-font-smoothing: grayscale;
4069
text-align: center;
4170
color: #2c3e50;
4271
}
72+
4373
.container {
4474
display: flex;
4575
height: 100vh;
4676
width: 100vw;
4777
overflow: hidden;
4878
}
79+
4980
.container aside {
50-
width: 400px;
81+
$w: 300px;
82+
width: $w;
83+
min-width: $w;
84+
max-width: $w;
5185
background: #1e2a31;
5286
overflow: auto;
5387
padding: 10px;
5488
color: #b0c4cf;
5589
box-shadow: 0 0 5px #1e2a31;
90+
91+
a {
92+
color: inherit;
93+
display: block;
94+
padding: 10px 60px;
95+
text-decoration: none;
96+
margin: 0 -50px;
97+
98+
&.active,
99+
&:hover {
100+
font-weight: bold;
101+
background: #ff52;
102+
}
103+
104+
&.active {
105+
background: #ff55;
106+
}
107+
}
108+
56109
p {
57110
text-align: start;
58111
}
59112
}
113+
114+
pre {
115+
padding: 10px 15px;
116+
width: 900px;
117+
border-radius: 5px;
118+
background: #5552;
119+
border: 1px solid #0005;
120+
font-size: 16px;
121+
font-weight: 500;
122+
overflow: auto;
123+
}
124+
60125
.container main {
126+
display: flex;
127+
flex-direction: column;
128+
text-align: start;
61129
overflow: auto;
62130
flex-grow: 1;
63-
padding: 10px;
131+
padding: 20px 30px;
132+
padding-bottom: 10px;
133+
}
134+
135+
.grow {
136+
flex-grow: 1;
64137
}
138+
65139
@media (max-width: 600px) {
66140
.container {
67141
flex-direction: column;
142+
68143
aside {
69144
height: 320px;
70145
overflow: hidden;

src/pages/Features.vue

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
import TagInput from "../../lib/TagInput.vue";
4+
import "@mayank1513/vue-tag-input/style.css";
5+
6+
const options = [
7+
"No dependencies",
8+
"Autocompletion",
9+
"Keep Focused",
10+
"Fast Settup",
11+
"Mini Sized",
12+
"Customizable",
13+
"Backspace/Delete to remove tag",
14+
"Turns red when backspace/delete is pressed",
15+
"Examples",
16+
"Docs",
17+
"Copy/Paste",
18+
]
19+
const tags = ref<string[]>([...options]);
20+
</script>
21+
22+
<template>
23+
<div class="main">
24+
<h1>Vue Tag Input</h1>
25+
A versetile tag input component built with Vue 3 Composition API.
26+
<p dir="auto" class="git-tags">
27+
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
28+
src="https://camo.githubusercontent.com/b3cefbae109b4ce0f8ae576eeec878b6e4761780cdb5c1f6508e8f6133d7ef57/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406d6179616e6b313531332f7675652d7461672d696e7075742e7376673f636f6c6f72423d677265656e"
29+
alt="Version" data-canonical-src="https://img.shields.io/npm/v/@mayank1513/vue-tag-input.svg?colorB=green"
30+
style="max-width: 100%" /></a>
31+
<a href="https://codecov.io/gh/mayank1513/tag-input" rel="nofollow"><img
32+
src="https://camo.githubusercontent.com/43b7960de497ebbf7b33571e3b30fb1bfc9827125d0d7d8aebdda3f2bbc8ae35/68747470733a2f2f636f6465636f762e696f2f67682f6d6179616e6b313531332f7461672d696e7075742f67726170682f62616467652e737667"
33+
alt="codecov" data-canonical-src="https://codecov.io/gh/mayank1513/tag-input/graph/badge.svg"
34+
style="max-width: 100%" /></a>
35+
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
36+
src="https://camo.githubusercontent.com/ac8040288c4017663796556c36c2ba7445de7a2b216e0ff837b315bb2df91169/68747470733a2f2f696d672e6a7364656c6976722e636f6d2f696d672e736869656c64732e696f2f6e706d2f64742f406d6179616e6b313531332f7675652d7461672d696e7075742e737667"
37+
alt="Downloads"
38+
data-canonical-src="https://img.jsdelivr.com/img.shields.io/npm/dt/@mayank1513/vue-tag-input.svg"
39+
style="max-width: 100%" /></a>
40+
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
41+
src="https://camo.githubusercontent.com/edf5ba88cf7dc251c58b4311f905f16345a57570992d1cccd4fe14a024fb9ffa/68747470733a2f2f696d672e736869656c64732e696f2f62756e646c6570686f6269612f6d696e7a69702f406d6179616e6b313531332f7675652d7461672d696e707574"
42+
alt="npm bundle size" data-canonical-src="https://img.shields.io/bundlephobia/minzip/@mayank1513/vue-tag-input"
43+
style="max-width: 100%" /></a>
44+
<a href="https://github.com/mayank1513/tag-input/actions/workflows/publish-to-npm-on-new-release.yml"><img
45+
src="https://github.com/mayank1513/tag-input/actions/workflows/test.yml/badge.svg"
46+
alt="Publish to npm and GitHub" style="max-width: 100%" /></a>
47+
<a href="https://www.codementor.io/@mayank1513?refer=badge" rel="nofollow"><img
48+
src="https://github.com/mayank1513/tag-input/raw/master/codementor.svg" alt="Get help"
49+
style="max-width: 100%" /></a>
50+
</p>
51+
52+
<br />
53+
<tag-input v-model="tags" :options="options" />
54+
<ul dir="auto">
55+
<li>✅ No dependencies</li>
56+
<li>
57+
✅ Input box stays focused - no need to re-focus the input =&gt; better
58+
UX
59+
</li>
60+
<li>✅ Autocompletion</li>
61+
<li>✅ Fast setup</li>
62+
<li>✅ Works with Vuex</li>
63+
<li>✅ Small size: 1.6 kB gzipped</li>
64+
<li>✅ Many customization options</li>
65+
<li>✅ Delete tags on backspace / delete key</li>
66+
<li>
67+
✅ Confirm before delete - tags turns red when backspace is pressed,
68+
gets deleted when backspace is pressed again
69+
</li>
70+
<li>✅ Works well with copy &amp; paste</li>
71+
<li>✅ Examples &amp; Docs</li>
72+
</ul>
73+
</div>
74+
</template>
75+
76+
<style scoped>
77+
.main {
78+
display: flex;
79+
flex-direction: column;
80+
text-align: start;
81+
max-width: 1100px;
82+
}
83+
84+
.grow {
85+
flex-grow: 1;
86+
}
87+
88+
.git-tags {
89+
display: flex;
90+
gap: 15px;
91+
}
92+
</style>

0 commit comments

Comments
 (0)