Skip to content

Commit 603b97b

Browse files
authored
Merge pull request #12 from mayank1513/multi-line-tags
multi line tag
2 parents f81440b + b736fc1 commit 603b97b

File tree

2 files changed

+65
-69
lines changed

2 files changed

+65
-69
lines changed

lib/TagInput.vue

Lines changed: 64 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface TagInputProps {
1010
tagBgColor?: string;
1111
tagClass?: string;
1212
customDelimiter?: string[] | string;
13+
singleLine: boolean
1314
}
1415
1516
const props = withDefaults(defineProps<TagInputProps>(), {
@@ -21,12 +22,14 @@ const props = withDefaults(defineProps<TagInputProps>(), {
2122
tagBgColor: "rgb(120, 54, 10)",
2223
tagClass: "",
2324
customDelimiter: () => [],
25+
singleLine: false
2426
});
2527
const emit = defineEmits(["update:modelValue"]);
2628
// Tags
2729
const tags = ref<string[]>(props.modelValue);
2830
const tagsClass = ref(props.tagClass);
2931
const newTag = ref("");
32+
const focused = ref(false);
3033
const id = Math.random().toString(36).substring(7);
3134
const customDelimiter = computed<string[] | string>(() => [
3235
...new Set(
@@ -79,14 +82,10 @@ const removeTag = (index: number) => {
7982
};
8083
8184
// positioning and handling tag change
82-
const paddingLeft = ref(10);
8385
const tagsUl = ref<HTMLUListElement | null>(null);
8486
const onTagsChange = () => {
85-
// position cursor
86-
const extraCushion = 15;
8787
tagsUl.value?.style.setProperty("--tagBgColor", props.tagBgColor);
8888
tagsUl.value?.style.setProperty("--tagTextColor", props.tagTextColor);
89-
paddingLeft.value = (tagsUl.value?.clientWidth || 0) + extraCushion;
9089
// scroll to end of tags
9190
tagsUl.value?.scrollTo(tagsUl.value.scrollWidth, 0);
9291
// emit value on tags change
@@ -117,50 +116,37 @@ const deleteLastTag = () => {
117116
}
118117
}
119118
};
119+
const inputElId = `tag-input${Math.random()}`
120120
</script>
121121

122122
<template>
123-
<div
124-
class="tag-input"
125-
:class="{ 'with-count': showCount, duplicate: noMatchingTag }"
126-
>
127-
<input
128-
v-model="newTag"
129-
type="text"
130-
:list="id"
131-
autocomplete="off"
132-
@keydown.enter="addTag(newTag)"
133-
@keydown.prevent.tab="addTag(newTag)"
134-
@keydown.delete="deleteLastTag()"
135-
@input="addTagIfDelem(newTag)"
136-
:style="{ 'padding-left': `${paddingLeft}px` }"
137-
/>
138-
139-
<datalist v-if="options" :id="id">
140-
<option v-for="option in availableOptions" :key="option" :value="option">
141-
{{ option }}
142-
</option>
143-
</datalist>
144-
145-
<ul class="tags" ref="tagsUl">
146-
<li
147-
v-for="(tag, index) in tags"
148-
:key="tag"
149-
:class="{
150-
duplicate: tag === duplicate,
151-
tag: tagsClass.length == 0,
152-
del: shouldDelete && index === tags.length - 1,
153-
[tagsClass]: true,
154-
}"
155-
>
123+
<label :for="inputElId">
124+
<ul class="tags" ref="tagsUl" tabindex="0" :class="{ duplicate, focused, noMatchingTag, singleLine }">
125+
<li v-for="(tag, index) in tags" :key="tag" :class="{
126+
duplicate: tag === duplicate,
127+
tag: tagsClass.length == 0,
128+
del: shouldDelete && index === tags.length - 1,
129+
[tagsClass]: true,
130+
}">
156131
{{ tag }}
157132
<button class="delete" @click="removeTag(index)">x</button>
158133
</li>
134+
<div class="tag-input">
135+
<input v-model="newTag" :id="inputElId" type="text" :list="id" autocomplete="off" @keydown.enter="addTag(newTag)"
136+
@keydown.prevent.tab="addTag(newTag)" @keydown.delete="deleteLastTag()" @input="addTagIfDelem(newTag)"
137+
placeholder="Enter tag" @focus="focused = true" @blur="focused = false" />
138+
139+
<datalist v-if="options" :id="id">
140+
<option v-for="option in availableOptions" :key="option" :value="option">
141+
{{ option }}
142+
</option>
143+
</datalist>
144+
</div>
145+
<div v-if="showCount" class="count">
146+
<span>{{ tags.length }}</span> tags
147+
</div>
159148
</ul>
160-
<div v-if="showCount" class="count">
161-
<span>{{ tags.length }}</span> tags
162-
</div>
163-
</div>
149+
</label>
164150
<small v-show="noMatchingTag" class="err">Custom tags not allowed</small>
165151
</template>
166152

@@ -171,23 +157,42 @@ const deleteLastTag = () => {
171157
172158
.tag-input {
173159
position: relative;
160+
width: 250px;
174161
}
175162
176-
ul {
163+
.tags {
177164
--tagBgColor: rgb(250, 104, 104);
178165
--tagTextColor: white;
179166
list-style: none;
180167
display: flex;
168+
flex-wrap: wrap;
181169
align-items: center;
182170
gap: 7px;
183171
margin: 0;
184-
padding: 0;
185-
position: absolute;
186-
top: 0;
187-
bottom: 0;
172+
padding: 10px;
188173
left: 10px;
189174
max-width: 75%;
190-
overflow-x: auto;
175+
border-bottom: 1px solid #5558;
176+
cursor: text;
177+
178+
&.singleLine {
179+
flex-wrap: nowrap;
180+
overflow: auto;
181+
}
182+
183+
&.focused {
184+
border-bottom: 2px solid #55fa;
185+
}
186+
187+
&.duplicate {
188+
border-bottom: 1px solid rgb(235, 27, 27);
189+
}
190+
191+
&.noMatchingTag {
192+
outline: rgb(235, 27, 27);
193+
border: 1px solid rgb(235, 27, 27);
194+
animation: shake1 0.5s;
195+
}
191196
}
192197
193198
.tag {
@@ -216,25 +221,15 @@ ul {
216221
animation: shake 1s;
217222
}
218223
219-
.duplicate input {
220-
outline: rgb(235, 27, 27);
221-
border: 1px solid rgb(235, 27, 27);
222-
animation: shake1 0.5s;
223-
}
224-
225224
input {
226-
width: 100%;
227-
padding: 10px;
225+
all: unset;
228226
}
229227
230228
.count {
231-
position: absolute;
232-
top: 50%;
233-
transform: translateY(-50%);
234-
right: 10px;
235-
display: block;
236229
font-size: 0.8rem;
237230
white-space: nowrap;
231+
flex-grow: 1;
232+
text-align: end;
238233
}
239234
240235
.count span {
@@ -243,51 +238,52 @@ input {
243238
border-radius: 2px;
244239
}
245240
246-
.with-count input {
247-
padding-right: 60px;
248-
}
249-
250-
.with-count ul {
251-
max-width: 60%;
252-
}
253-
254241
.err {
255242
color: red;
256243
}
257244
258245
@keyframes shake {
246+
259247
10%,
260248
90% {
261249
transform: scale(0.9) translate3d(-1px, 0, 0);
262250
}
251+
263252
20%,
264253
80% {
265254
transform: scale(0.9) translate3d(2px, 0, 0);
266255
}
256+
267257
30%,
268258
50%,
269259
70% {
270260
transform: scale(0.9) translate3d(-4px, 0, 0);
271261
}
262+
272263
40%,
273264
60% {
274265
transform: scale(0.9) translate3d(4px, 0, 0);
275266
}
276267
}
268+
277269
@keyframes shake1 {
270+
278271
10%,
279272
90% {
280273
transform: scale(0.99) translate3d(-1px, 0, 0);
281274
}
275+
282276
20%,
283277
80% {
284278
transform: scale(0.98) translate3d(2px, 0, 0);
285279
}
280+
286281
30%,
287282
50%,
288283
70% {
289284
transform: scale(1) translate3d(-4px, 0, 0);
290285
}
286+
291287
40%,
292288
60% {
293289
transform: scale(0.98) translate3d(4px, 0, 0);

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.4",
6+
"version": "1.0.5",
77
"type": "module",
88
"repository": {
99
"type": "git",

0 commit comments

Comments
 (0)