input2tags 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +204 -0
- package/dist/input2tags.esm.min.js +1 -0
- package/dist/input2tags.min.css +1 -0
- package/dist/input2tags.min.js +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Filipe Laborde-Basto
|
|
4
|
+
Copyright (c) 2022 Raihan Kabir
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# input-tags
|
|
2
|
+
## Version 2.01
|
|
3
|
+
**Check the CodePen.io [basic example](https://codepen.io/mindflowgo/pen/PwYNQVe); [autocomplete example](https://codepen.io/mindflowgo/pen/MYgyVgg).**
|
|
4
|
+
|
|
5
|
+
*Project objective: simple but powerful vanilla ES6 javascript (code: 350 lines) input tag generator for any input fields; with auto-completion lists.*
|
|
6
|
+
|
|
7
|
+
Based off the inspiration work of [github.com/rk4bir/simple-tags-input](https://github.com/rk4bir/simple-tags-input); using his idea and CSS but then rewritten for ES6 and more features. Can record special keys (Meta, Alt, Tab, MouseLeft, VolumeUp, etc) as key presses.
|
|
8
|
+
|
|
9
|
+
This project is mobile-friendly: but you may want to prevent scrolling of screen depending on your needs.
|
|
10
|
+
|
|
11
|
+
## Demo
|
|
12
|
+
[Demos & Instructions](https://mindflowgo.github.io/input-tags/)
|
|
13
|
+
|
|
14
|
+
Video illustrations:
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
*Only required tags are inputId*
|
|
21
|
+
- **allowDelete**: allow the [x] deleting of tags (default true)
|
|
22
|
+
- **allowDuplicates**: allow duplicate tags (default false)
|
|
23
|
+
- **allowSpaces**: allow spaces in tags (default false)
|
|
24
|
+
- **allowCustomKeys**: enables special character handling (default false)
|
|
25
|
+
- **autocomplete**: array of auto-complete options
|
|
26
|
+
- **initialTags**: array of initial tags to display
|
|
27
|
+
- **targetEl**: target list element (if UL) or parent for the created list element
|
|
28
|
+
- **onAdd**: a function called before adding the tag text (returns text or modified version)
|
|
29
|
+
- **onDelete**: a function called before deleting a tag (returns true if allowed, false otherwise)
|
|
30
|
+
- **onInput**: a function called after new user input received
|
|
31
|
+
- **onChange**: a function called after change to tags (new tag added, re-arranged, deleted tag)
|
|
32
|
+
|
|
33
|
+
## Methods
|
|
34
|
+
*With a valid InputTags() instance you have these methods:*
|
|
35
|
+
- **getTags()**: get the list of tags created
|
|
36
|
+
- **setTags([])**: set the list of tags
|
|
37
|
+
- **addTag(tag)**: add a new tag to input tags instance
|
|
38
|
+
- **deleteTag(index)**: remove a tag, the index of it's placement
|
|
39
|
+
- **showAutocomplete(query)**: showes autoComplete with matches for the query
|
|
40
|
+
- **hideAutocomplete()**
|
|
41
|
+
- **destroy()**: remove the instance
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
There are 3 steps to using it
|
|
45
|
+
1. Include the CSS & JS files (importing it into a script type=module)
|
|
46
|
+
2. Have an empty list (UL) and an input box (INPUT)
|
|
47
|
+
3. Run the function: const inputTags = new InputTags({ inputId: "tagsInput", listId: "tagsList" });
|
|
48
|
+
|
|
49
|
+
That's it!
|
|
50
|
+
|
|
51
|
+
### BASIC Example
|
|
52
|
+
> Check the [CodePen.io example](https://codepen.io/mindflowgo/pen/PwYNQVe).
|
|
53
|
+
|
|
54
|
+
#### Step 1 - Include Files (change path to match where they are)
|
|
55
|
+
```html
|
|
56
|
+
<head>
|
|
57
|
+
<link rel="stylesheet" href="https://unpkg.com/input-tags@latest/style.css">
|
|
58
|
+
</head>
|
|
59
|
+
|
|
60
|
+
<script type="module">
|
|
61
|
+
import InputTags from "https://unpkg.com/input-tags@latest"
|
|
62
|
+
</script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Step 2 - Insert needed HTML into your code
|
|
66
|
+
```html
|
|
67
|
+
<div>
|
|
68
|
+
<ul id="tagsList"></ul>
|
|
69
|
+
|
|
70
|
+
<input type="text" id="tagsInput" spellcheck="false" placeholder="Enter a tag" />
|
|
71
|
+
</div>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Step 3 - Run Javascript (to initialize INPUT field)
|
|
75
|
+
```javascript
|
|
76
|
+
const inputTags = new InputTags({ inputId: "tagsInput", listId: "tagsList" });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Quick example html**
|
|
80
|
+
```html
|
|
81
|
+
<!DOCTYPE html>
|
|
82
|
+
<html lang="en">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="UTF-8">
|
|
85
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
87
|
+
<!-- Bootstrap 5 not used by Input Tags -->
|
|
88
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
89
|
+
|
|
90
|
+
<!-- Only CSS used by InputTags -->
|
|
91
|
+
<link rel="stylesheet" href="https://unpkg.com/input-tags@latest/style.css">
|
|
92
|
+
</head>
|
|
93
|
+
|
|
94
|
+
<body>
|
|
95
|
+
<div class="container mt-5">
|
|
96
|
+
<h1 class="text-center mb-4">Tags Input</h1>
|
|
97
|
+
|
|
98
|
+
<div class="mb-3">
|
|
99
|
+
<p class="mb-2">Tag List:</p>
|
|
100
|
+
<!-- specify the ul LIST element to show the tags -->
|
|
101
|
+
<ul id="myTagList"><li><strong>List:</strong></li></ul>
|
|
102
|
+
<!-- include the input box to input the tags -->
|
|
103
|
+
<p><i>Type something and press Enter</i></p>
|
|
104
|
+
<input type="text" id="tagsInput" class="form-control mt-2" spellcheck="false" placeholder="Enter a tag" />
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="mb-3">
|
|
108
|
+
<p class="mb-2">List results: <strong><span id="tagsData"></span></strong></p>
|
|
109
|
+
<div class="mt-5 d-grid gap-2 d-md-flex justify-content-md-start">
|
|
110
|
+
<button class="btn btn-secondary ms-md-2" onClick="btnAddTag('hello')">Add Tag 'hello'</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!--Simple tags input implementation-->
|
|
116
|
+
<script type="module">
|
|
117
|
+
import InputTags from "https://unpkg.com/input-tags@latest"
|
|
118
|
+
|
|
119
|
+
const inputEl = document.getElementById('tagsInput');
|
|
120
|
+
const inputTags = new InputTags(inputEl, {
|
|
121
|
+
autocomplete: ['apple', 'banana', 'cherry'],
|
|
122
|
+
// initialTags: ['one','two','three'], // pre-populate (1)
|
|
123
|
+
targetEl: document.getElementById('myTagList'), // pre-populate (2)
|
|
124
|
+
onChange: (tags) => document.getElementById('tagsData').innerHTML = tags?.join(',') || ''
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// show initial tags by adding something
|
|
128
|
+
setTimeout( ()=>inputTags.addTag('Auto-Add'), 100)
|
|
129
|
+
|
|
130
|
+
// export module functions for DOM
|
|
131
|
+
window.btnAddTag = (tag) => inputTags.addTag(tag);
|
|
132
|
+
</script>
|
|
133
|
+
</body>
|
|
134
|
+
</html>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### AutoComplete Example
|
|
138
|
+
> Check the [CodePen.io example](https://codepen.io/mindflowgo/pen/MYgyVgg).
|
|
139
|
+
|
|
140
|
+
#### Step 1 - Include Files (change path to match where they are)
|
|
141
|
+
```html
|
|
142
|
+
<head>
|
|
143
|
+
<link rel="stylesheet" href="https://unpkg.com/input-tags@latest/style.css">
|
|
144
|
+
</head>
|
|
145
|
+
|
|
146
|
+
<script type="module">
|
|
147
|
+
import InputTags from "https://unpkg.com/input-tags@latest"
|
|
148
|
+
</script>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Step 2 - Insert needed HTML into your code
|
|
152
|
+
Really just having an input box to enter tags is all that is needed. The package can use
|
|
153
|
+
an existing list (UL) otherwise it will create one and pre-pend above the INPUT box.
|
|
154
|
+
```html
|
|
155
|
+
<div>
|
|
156
|
+
<input type="text" id="tagsInput" spellcheck="false" placeholder="Enter a tag" />
|
|
157
|
+
|
|
158
|
+
<button onClick="btnAddTag('hello')">Add Tag 'hello'</button>
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Step 3 - Run Javascript (to initialize INPUT field)
|
|
163
|
+
```javascript
|
|
164
|
+
const inputEl = document.getElementById('tagsInput');
|
|
165
|
+
const inputTags = new InputTags(inputEl, {
|
|
166
|
+
autocomplete: ['apple', 'banana', 'cherry'],
|
|
167
|
+
initialTags: ['one','two','three'], // pre-populate (1)
|
|
168
|
+
// targetEl: document.getElementById('myTagList'), // pre-populate (2)
|
|
169
|
+
// onChange: (tags) => document.getElementById('tagsData').innerHTML = tags?.join(',') || ''
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// export module functions for DOM access if needed
|
|
173
|
+
window.inputTags = inputTags;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Advanced Ideas
|
|
177
|
+
You can use the 4 hooks to limit characters allow in inputs, prevent certain tags from being created, or others from being deleted (with these hooks: onInput, onAdd, onDelete)
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
const inputEl = document.getElementById('tagsInput');
|
|
181
|
+
const inputTags = new InputTags(inputEl, {
|
|
182
|
+
targetEl: document.getElementById('myList'),
|
|
183
|
+
autocomplete: ['apple', 'banana', 'cherry', 'pear', 'pineapple'],
|
|
184
|
+
// allowCustomKeys: true,
|
|
185
|
+
// onInput: (value,e) => customKeyHandling(value,e),
|
|
186
|
+
onInput,
|
|
187
|
+
onAdd,
|
|
188
|
+
onDelete,
|
|
189
|
+
onChange: (tags) => document.getElementById('tagsOutput').value = tags?.join(',') || '',
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
- *AutoComplete*: Triggering display of autocomplete: see example/advanced.html "Show AutoComplete" button.
|
|
193
|
+
```html
|
|
194
|
+
<button onClick="showList('apple')">Show AutoComplete List</button>
|
|
195
|
+
```
|
|
196
|
+
```javascript
|
|
197
|
+
window.showList = (search) => inputTags.showAutocomplete(search);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
####
|
|
201
|
+
If you modify the javascript in src/inputtags.js, you can build it with `npm run build` for distribution.
|
|
202
|
+
|
|
203
|
+
Before forking and spreading another version, please contact the author with a PR if you have improvements
|
|
204
|
+
you would like. I'd be happy to integrate improvements.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class Input2Tags{constructor(t,e={}){this.input=t,this.opts={allowDelete:!0,allowDuplicates:!1,allowSpaces:!1,allowCustomKeys:!1,autocomplete:[],initialTags:[],targetEl:null,onAdd:null,onDelete:null,onInput:null,onChange:null,...e},this.tags=[],this.abortController=new AbortController,this.dragAbortController=null,this.dragTarget=null,this.#t()}#t(){const t=this.opts.targetEl;t&&"UL"===t.tagName?(this.list=t,Array.from(this.list.children).forEach(t=>{(t.dataset.tag||t.querySelector("span"))&&t.remove()})):(this.list=document.createElement("ul"),this.list.className="tagsList",this.list.setAttribute("role","list"),t?t.appendChild(this.list):this.input.parentNode.insertBefore(this.list,this.input)),this.placeholder=document.createElement("li"),this.placeholder.className="tagsDragPlaceholder",this.autocomplete=document.createElement("div"),this.autocomplete.className="tagsAutocompleteList",this.autocomplete.style.display="none",this.input.insertAdjacentElement("afterend",this.autocomplete),this.opts.allowCustomKeys||this.input.addEventListener("input",this.#e.bind(this),{signal:this.abortController.signal}),this.input.addEventListener("keydown",this.#s.bind(this),{signal:this.abortController.signal}),this.list.addEventListener("pointerdown",this.#i.bind(this),{signal:this.abortController.signal}),this.#o()}#o(){const t=this.opts.initialTags;if(t&&t.length)return void t.forEach(t=>this.addTag(t.trim()));const e=this.list.querySelectorAll("li");e.forEach(t=>{const e=t.textContent.replace("×","").trim();e&&this.addTag(e,t)}),e&&this.opts.onChange&&this.opts.onChange()}#e(t){let e=t.target.value;this.opts.onInput&&(t.target.value=this.opts.onInput(e)),this.showAutocomplete(e.trim())}#s(t){let e=this.input.value.trim();if(this.opts.onInput&&this.opts.allowCustomKeys)return void this.opts.onInput(e,t);if("Backspace"===t.key&&!e&&this.tags.length)return t.preventDefault(),void this.deleteTag(this.tags.length-1);("Enter"===t.key||!this.opts.allowSpaces&&","===t.key||this.opts.allowSpaces&&" "===t.key&&e.includes(","))&&e&&(t.preventDefault(),this.addTag(e),this.input.value="",this.hideAutocomplete())}showAutocomplete(t){if(!this.opts.autocomplete.length||!t)return void this.hideAutocomplete();const e=this.opts.autocomplete.filter(e=>e.toLowerCase().includes(t.toLowerCase())&&!this.tags.includes(e)).slice(0,8);if(!e.length)return void this.hideAutocomplete();const s=document.createElement("ul");e.forEach(t=>{const e=document.createElement("li");e.textContent=t,e.addEventListener("click",()=>{this.addTag(t),this.input.value="",this.hideAutocomplete()},{signal:this.abortController.signal}),s.appendChild(e)}),this.autocomplete.innerHTML="",this.autocomplete.appendChild(s),this.autocomplete.style.display="block"}hideAutocomplete(){this.autocomplete.style.display="none"}#i(t){const e=t.target.closest("li");e&&e.dataset.tag&&"SPAN"!==t.target.tagName&&(t.preventDefault(),this.dragData={el:e,startIndex:[...this.list.children].indexOf(e)},e.classList.add("tagsDragThis"),e.before(this.placeholder),this.dragAbortController?.abort(),this.dragAbortController=new AbortController,document.addEventListener("pointermove",this.#a.bind(this),{signal:this.dragAbortController.signal}),document.addEventListener("pointerup",this.#l.bind(this),{signal:this.dragAbortController.signal,once:!0}),document.addEventListener("touchend",this.#l.bind(this),{signal:this.dragAbortController.signal,once:!0}))}#a(t){if(!this.dragData)return;const e=document.elementFromPoint(t.clientX,t.clientY)?.closest("li");if(!e||e===this.dragData.el||e===this.placeholder||!e.dataset.tag)return;this.#n();const s=e.getBoundingClientRect(),i=t.clientX<s.left+s.width/2;this.dragTarget=e,e.classList.add("tagsDragOver"),this.list.insertBefore(this.placeholder,i?e:e.nextSibling)}#l(t){if(this.dragData){const{el:t}=this.dragData;this.list.insertBefore(t,this.placeholder)}this.#n(),this.placeholder.remove();const e=this.tags.join("|");this.tags=[];for(const t of this.list.querySelectorAll("li[data-tag]"))this.tags.push(t.dataset.tag),t.classList.remove("tagsDragThis");this.opts.onChange&&e!==this.tags.join("|")&&this.opts.onChange(this.tags),this.dragAbortController?.abort(),this.dragAbortController=null,this.dragData=null,this.input.focus()}#n(){this.dragTarget&&(this.dragTarget.classList.remove("tagsDragOver"),this.dragTarget=null)}#r(t){if(!this.opts.allowDelete)return;const e=document.createElement("span");e.classList.add("tagDelete"),e.innerHTML="×",e.style.cursor="pointer",e.addEventListener("click",e=>{e.stopPropagation(),this.#h(t)},{signal:this.abortController.signal}),t.appendChild(e)}#h(t){const e=t.dataset.tag;if(!(!this.opts.onDelete||this.opts.onDelete(e)))return;t.remove();const s=this.tags.indexOf(e);s>-1&&(this.tags.splice(s,1),this.opts.onChange&&this.opts.onChange(this.tags))}#d(){this.list.querySelectorAll("li").forEach(t=>{t.classList.remove("tagsDragThis","tagsDragOver"),t.dataset.tag&&(t.style.cursor="grab")}),this.placeholder.parentNode&&this.placeholder.remove(),this.dragAbortController?.abort(),this.dragAbortController=null,this.dragData=null,this.dragTarget=null}deleteTag(t){const e=this.list.querySelectorAll("li[data-tag]")[t];e&&this.#h(e)}addTag(t,e){if(!t||!this.opts.allowDuplicates&&this.tags.includes(t))return;if(this.opts.onAdd&&!e&&(t=this.opts.onAdd(t)),!t)return;const s=e||document.createElement("li");s.textContent=t,s.dataset.tag=t,s.setAttribute("role","listitem"),s.setAttribute("tabindex","0"),s.style.cursor="grab",e&&s.querySelector("span")?.remove(),this.#r(s),e||this.list.appendChild(s),this.tags.push(t),this.opts.onChange&&this.opts.onChange(this.tags)}getTags(){return[...this.tags]}setTags(t){this.tags=[],this.list.querySelectorAll("li[data-tag]").forEach(t=>t.remove()),t.forEach(t=>this.addTag(t))}resetDragState(){this.#d()}destroy(){this.#d(),this.abortController.abort(),this.list?.remove(),this.autocomplete?.remove()}}export default Input2Tags;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.tagsList{border:none;display:flex;flex-wrap:wrap;padding-left:0}.tagsList LI{background:#d4d7ff;border:1px solid #e3e1e1;border-radius:5px;color:#333;list-style:none;margin-right:2px;padding:5px 28px 5px 10px;position:relative}.tagsList .tagDelete{background:#dfdfdf;border-radius:2px;color:grey;cursor:pointer!important;font-size:18px;height:100%;justify-content:center;padding:3px 0 0 6px;position:absolute;right:0;top:0;width:24px}.tagsAutocompleteList span{position:relative}.tagsAutocompleteList ul{background:#fff;border:1px solid #d3d3d3;border-radius:5px;display:block;margin:5px 0 0;max-height:300px;overflow-y:auto;padding:0;position:absolute;z-index:99}.tagsAutocompleteList li{border-bottom:1px dotted #eee;cursor:pointer;list-style:none;padding:5px}.tagsAutocompleteList li:hover{background:#eee;font-weight:600}.tagsDragThis{filter:grayscale(80%);opacity:.5}.tagsDragThis:after{content:"↑"}.tagsDragPlaceholder{border:2px dashed #999!important;margin:0 1px;opacity:.7;padding:0}.tagsDragPlaceholder:before{color:green;content:"↓";font-size:16px}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class Input2Tags{constructor(t,e={}){this.input=t,this.opts={allowDelete:!0,allowDuplicates:!1,allowSpaces:!1,allowCustomKeys:!1,autocomplete:[],initialTags:[],targetEl:null,onAdd:null,onDelete:null,onInput:null,onChange:null,...e},this.tags=[],this.abortController=new AbortController,this.dragAbortController=null,this.dragTarget=null,this.#t()}#t(){const t=this.opts.targetEl;t&&"UL"===t.tagName?(this.list=t,Array.from(this.list.children).forEach(t=>{(t.dataset.tag||t.querySelector("span"))&&t.remove()})):(this.list=document.createElement("ul"),this.list.className="tagsList",this.list.setAttribute("role","list"),t?t.appendChild(this.list):this.input.parentNode.insertBefore(this.list,this.input)),this.placeholder=document.createElement("li"),this.placeholder.className="tagsDragPlaceholder",this.autocomplete=document.createElement("div"),this.autocomplete.className="tagsAutocompleteList",this.autocomplete.style.display="none",this.input.insertAdjacentElement("afterend",this.autocomplete),this.opts.allowCustomKeys||this.input.addEventListener("input",this.#e.bind(this),{signal:this.abortController.signal}),this.input.addEventListener("keydown",this.#s.bind(this),{signal:this.abortController.signal}),this.list.addEventListener("pointerdown",this.#i.bind(this),{signal:this.abortController.signal}),this.#o()}#o(){const t=this.opts.initialTags;if(t&&t.length)return void t.forEach(t=>this.addTag(t.trim()));const e=this.list.querySelectorAll("li");e.forEach(t=>{const e=t.textContent.replace("×","").trim();e&&this.addTag(e,t)}),e&&this.opts.onChange&&this.opts.onChange()}#e(t){let e=t.target.value;this.opts.onInput&&(t.target.value=this.opts.onInput(e)),this.showAutocomplete(e.trim())}#s(t){let e=this.input.value.trim();if(this.opts.onInput&&this.opts.allowCustomKeys)return void this.opts.onInput(e,t);if("Backspace"===t.key&&!e&&this.tags.length)return t.preventDefault(),void this.deleteTag(this.tags.length-1);("Enter"===t.key||!this.opts.allowSpaces&&","===t.key||this.opts.allowSpaces&&" "===t.key&&e.includes(","))&&e&&(t.preventDefault(),this.addTag(e),this.input.value="",this.hideAutocomplete())}showAutocomplete(t){if(!this.opts.autocomplete.length||!t)return void this.hideAutocomplete();const e=this.opts.autocomplete.filter(e=>e.toLowerCase().includes(t.toLowerCase())&&!this.tags.includes(e)).slice(0,8);if(!e.length)return void this.hideAutocomplete();const s=document.createElement("ul");e.forEach(t=>{const e=document.createElement("li");e.textContent=t,e.addEventListener("click",()=>{this.addTag(t),this.input.value="",this.hideAutocomplete()},{signal:this.abortController.signal}),s.appendChild(e)}),this.autocomplete.innerHTML="",this.autocomplete.appendChild(s),this.autocomplete.style.display="block"}hideAutocomplete(){this.autocomplete.style.display="none"}#i(t){const e=t.target.closest("li");e&&e.dataset.tag&&"SPAN"!==t.target.tagName&&(t.preventDefault(),this.dragData={el:e,startIndex:[...this.list.children].indexOf(e)},e.classList.add("tagsDragThis"),e.before(this.placeholder),this.dragAbortController?.abort(),this.dragAbortController=new AbortController,document.addEventListener("pointermove",this.#a.bind(this),{signal:this.dragAbortController.signal}),document.addEventListener("pointerup",this.#l.bind(this),{signal:this.dragAbortController.signal,once:!0}),document.addEventListener("touchend",this.#l.bind(this),{signal:this.dragAbortController.signal,once:!0}))}#a(t){if(!this.dragData)return;const e=document.elementFromPoint(t.clientX,t.clientY)?.closest("li");if(!e||e===this.dragData.el||e===this.placeholder||!e.dataset.tag)return;this.#n();const s=e.getBoundingClientRect(),i=t.clientX<s.left+s.width/2;this.dragTarget=e,e.classList.add("tagsDragOver"),this.list.insertBefore(this.placeholder,i?e:e.nextSibling)}#l(t){if(this.dragData){const{el:t}=this.dragData;this.list.insertBefore(t,this.placeholder)}this.#n(),this.placeholder.remove();const e=this.tags.join("|");this.tags=[];for(const t of this.list.querySelectorAll("li[data-tag]"))this.tags.push(t.dataset.tag),t.classList.remove("tagsDragThis");this.opts.onChange&&e!==this.tags.join("|")&&this.opts.onChange(this.tags),this.dragAbortController?.abort(),this.dragAbortController=null,this.dragData=null,this.input.focus()}#n(){this.dragTarget&&(this.dragTarget.classList.remove("tagsDragOver"),this.dragTarget=null)}#r(t){if(!this.opts.allowDelete)return;const e=document.createElement("span");e.classList.add("tagDelete"),e.innerHTML="×",e.style.cursor="pointer",e.addEventListener("click",e=>{e.stopPropagation(),this.#h(t)},{signal:this.abortController.signal}),t.appendChild(e)}#h(t){const e=t.dataset.tag;if(!(!this.opts.onDelete||this.opts.onDelete(e)))return;t.remove();const s=this.tags.indexOf(e);s>-1&&(this.tags.splice(s,1),this.opts.onChange&&this.opts.onChange(this.tags))}#d(){this.list.querySelectorAll("li").forEach(t=>{t.classList.remove("tagsDragThis","tagsDragOver"),t.dataset.tag&&(t.style.cursor="grab")}),this.placeholder.parentNode&&this.placeholder.remove(),this.dragAbortController?.abort(),this.dragAbortController=null,this.dragData=null,this.dragTarget=null}deleteTag(t){const e=this.list.querySelectorAll("li[data-tag]")[t];e&&this.#h(e)}addTag(t,e){if(!t||!this.opts.allowDuplicates&&this.tags.includes(t))return;if(this.opts.onAdd&&!e&&(t=this.opts.onAdd(t)),!t)return;const s=e||document.createElement("li");s.textContent=t,s.dataset.tag=t,s.setAttribute("role","listitem"),s.setAttribute("tabindex","0"),s.style.cursor="grab",e&&s.querySelector("span")?.remove(),this.#r(s),e||this.list.appendChild(s),this.tags.push(t),this.opts.onChange&&this.opts.onChange(this.tags)}getTags(){return[...this.tags]}setTags(t){this.tags=[],this.list.querySelectorAll("li[data-tag]").forEach(t=>t.remove()),t.forEach(t=>this.addTag(t))}resetDragState(){this.#d()}destroy(){this.#d(),this.abortController.abort(),this.list?.remove(),this.autocomplete?.remove()}};if (typeof window !== 'undefined') window.Input2Tags = Input2Tags; else if (typeof module !== 'undefined' && module.exports) module.exports = Input2Tags; else if (typeof define === 'function' && define.amd) define(() => Input2Tags);
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "input2tags",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Simple, but powerful vanilla javascript input2tags creator, with autocomplete.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"input",
|
|
7
|
+
"tags",
|
|
8
|
+
"es6",
|
|
9
|
+
"autocomplete",
|
|
10
|
+
"input-tags",
|
|
11
|
+
"input2tags",
|
|
12
|
+
"labels"
|
|
13
|
+
],
|
|
14
|
+
"author": "Filipe Laborde <fil@rezox.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"main": "dist/input2tags.min.js",
|
|
17
|
+
"style": "dist/input2tags.min.css",
|
|
18
|
+
"module": "dist/input2tags.esm.min.js",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/input2tags.esm.min.js",
|
|
22
|
+
"require": "./dist/input2tags.min.js",
|
|
23
|
+
"default": "./dist/input2tags.min.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/input2tags.esm.min.js",
|
|
28
|
+
"dist/input2tags.min.js",
|
|
29
|
+
"dist/input2tags.min.css",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build:js:esm": "terser src/input2tags.js -c -m -o dist/input2tags.esm.min.js",
|
|
36
|
+
"build:js:classic": "terser src/input2tags.js -c -m -o dist/input2tags.min.js && node package.mjs dist/input2tags.min.js",
|
|
37
|
+
"build:css": "postcss src/input2tags.css -u cssnano --no-map -o dist/input2tags.min.css",
|
|
38
|
+
"build": "npm run build:js:esm && npm run build:js:classic && npm run build:css"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/mindflowgo/input2tags.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/mindflowgo/input2tags/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/mindflowgo/input2tags#readme",
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"cssnano": "^7.1.2",
|
|
50
|
+
"postcss": "^8.5.6",
|
|
51
|
+
"postcss-cli": "^11.0.1",
|
|
52
|
+
"terser": "^5.44.1"
|
|
53
|
+
}
|
|
54
|
+
}
|