├── .babelrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE ├── README.md ├── bower.json ├── index.html ├── package.json ├── src ├── tags-input.js └── tags-input.less ├── tags-input.css ├── tags-input.css.map ├── tags-input.js └── tags-input.js.map /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "modules": "umd", 3 | "loose": "all", 4 | "compact": true, 5 | "comments": false 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,.*rc,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | bower_components 17 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "browser": true 4 | } 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | bower.json 3 | README.md 4 | .babelrc 5 | .editorconfig 6 | .jshintrc 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jason Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tags-input 2 | ========== 3 | 4 | [![NPM Version](http://img.shields.io/npm/v/tags-input.svg?style=flat)](https://www.npmjs.org/package/tags-input) 5 | [![Bower Version](http://img.shields.io/bower/v/tags-input.svg?style=flat)](http://bower.io/search/?q=tags-input) 6 | [![Gitter Room](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/developit/tags-input) 7 | 8 | **Features:** 9 | 10 | - I said `` should be a thing. 11 | - With full keyboard, mouse and focus support. 12 | - Works with HTML5 `pattern` and `placeholder` attributes, too! 13 | - Native [`change`](https://developer.mozilla.org/en-US/docs/Web/Events/change) and [`input`](https://developer.mozilla.org/en-US/docs/Web/Events/input) _("live" change)_ events. 14 | - Using [preact](https://github.com/developit/preact)? (or react?) There's a wrapper called [preact-token-input](https://github.com/developit/preact-token-input) 15 | - Works in modern browsers and IE10+ 16 | 17 | **Screenshot:** 18 | 19 | > ![screenshot](http://cl.ly/image/3M3U1h1s2y0v/tags-screenshot.png) 20 | 21 | [JSFiddle Demo](http://jsfiddle.net/developit/d5w4jpxq/) 22 | 23 | --- 24 | 25 | 26 | Examples 27 | ======== 28 | 29 | It's easy to use! In addition to the example code, you'll also need to 30 | include `tags-input.css` - I didn't bundle it because that's a bit gross. 31 | 32 | **CommonJS:** 33 | 34 | ```js 35 | var tagsInput = require('tags-input'); 36 | 37 | // create a new tag input: 38 | var tags = document.createElement('input'); 39 | tags.setAttribute('type', 'tags'); 40 | tagsInput(tags); 41 | document.body.appendChild(tags); 42 | 43 | // enhance an existing input: 44 | tagsInput(document.querySelector('input[type="tags"]')); 45 | 46 | // or just enhance all tag inputs on the page: 47 | [].forEach.call(document.querySelectorAll('input[type="tags"]'), tagsInput); 48 | ``` 49 | 50 | **HTML Example:** 51 | 52 | ```html 53 | 54 | 55 | 56 |
57 | 61 |
62 | 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tags-input", 3 | "version": "1.0.2", 4 | "homepage": "https://github.com/developit/tags-input", 5 | "authors": [ 6 | "Jason Miller " 7 | ], 8 | "description": " like magic.", 9 | "main": "tags-input.js,tags-input.css", 10 | "moduleType": [ 11 | "amd", 12 | "node", 13 | "globals" 14 | ], 15 | "keywords": [ 16 | "tags", 17 | "input", 18 | "polyfill" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "**/*.min.js", 24 | "src", 25 | "bower_components", 26 | "LICENSE", 27 | "package.json", 28 | "README.md" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tags-input", 3 | "version": "1.1.1", 4 | "main": "tags-input.js", 5 | "description": " like magic.", 6 | "scripts": { 7 | "build": "npm run transpile && npm run less && npm run size", 8 | "transpile": "babel src --source-root src -s -d .", 9 | "less": "lessc --clean-css --source-map --source-map-less-inline --autoprefix=\"last 2 versions\" src/tags-input.less tags-input.css", 10 | "size": "echo \"gzip size: $(pretty-bytes $(gzip-size $npm_package_main))\"", 11 | "test": "jshint src/**.js", 12 | "prepublish": "npm run build", 13 | "release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/developit/tags-input.git" 18 | }, 19 | "devDependencies": { 20 | "babel": "^5.8.21", 21 | "gzip-size": "^3.0.0", 22 | "jshint": "^2.8.0", 23 | "less": "^2.5.1", 24 | "less-plugin-autoprefix": "^1.4.2", 25 | "less-plugin-clean-css": "^1.5.1", 26 | "pretty-bytes": "^2.0.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tags-input.js: -------------------------------------------------------------------------------- 1 | const BACKSPACE = 8, 2 | TAB = 9, 3 | ENTER = 13, 4 | LEFT = 37, 5 | RIGHT = 39, 6 | DELETE = 46, 7 | COMMA = 188; 8 | 9 | const SEPERATOR = ','; 10 | 11 | const COPY_PROPS = 'placeholder pattern spellcheck autocomplete autocapitalize autofocus accessKey accept lang minLength maxLength required'.split(' '); 12 | 13 | export default function tagsInput(input) { 14 | function createElement(type, name, text, attributes) { 15 | let el = document.createElement(type); 16 | if (name) el.className = name; 17 | if (text) el.textContent = text; 18 | for (let key in attributes) { 19 | el.setAttribute(`data-${key}`, attributes[key]); 20 | } 21 | return el; 22 | } 23 | 24 | function $(selector, all) { 25 | return all===true ? Array.prototype.slice.call(base.querySelectorAll(selector)) : base.querySelector(selector); 26 | } 27 | 28 | function getValue() { 29 | return $('.tag', true) 30 | .map( tag => tag.textContent ) 31 | .concat(base.input.value || []) 32 | .join(SEPERATOR); 33 | } 34 | 35 | function setValue(value) { 36 | $('.tag', true).forEach( t => base.removeChild(t) ); 37 | savePartialInput(value); 38 | } 39 | 40 | function save() { 41 | input.value = getValue(); 42 | input.dispatchEvent(new Event('change')); 43 | } 44 | 45 | // Return false if no need to add a tag 46 | function addTag(text) { 47 | // Add multiple tags if the user pastes in data with SEPERATOR already in it 48 | if (~text.indexOf(SEPERATOR)) text = text.split(SEPERATOR); 49 | if (Array.isArray(text)) return text.forEach(addTag); 50 | 51 | let tag = text && text.trim(); 52 | // Ignore if text is empty 53 | if (!tag) return false; 54 | 55 | // For duplicates, briefly highlight the existing tag 56 | if (!input.getAttribute('duplicates')) { 57 | let exisingTag = $(`[data-tag="${tag}"]`); 58 | if (exisingTag) { 59 | exisingTag.classList.add('dupe'); 60 | setTimeout( () => exisingTag.classList.remove('dupe') , 100); 61 | return false; 62 | } 63 | } 64 | 65 | base.insertBefore( 66 | createElement('span', 'tag', tag, { tag }), 67 | base.input 68 | ); 69 | } 70 | 71 | function select(el) { 72 | let sel = $('.selected'); 73 | if (sel) sel.classList.remove('selected'); 74 | if (el) el.classList.add('selected'); 75 | } 76 | 77 | function setInputWidth() { 78 | let last = $('.tag',true).pop(), 79 | w = base.offsetWidth; 80 | if (!w) return; 81 | base.input.style.width = Math.max( 82 | w - (last ? (last.offsetLeft+last.offsetWidth) : 5) - 5, 83 | w/4 84 | ) + 'px'; 85 | } 86 | 87 | function savePartialInput(value) { 88 | if (typeof value!=='string' && !Array.isArray(value)) { 89 | // If the base input does not contain a value, default to the original element passed 90 | value = base.input.value; 91 | } 92 | if (addTag(value)!==false) { 93 | base.input.value = ''; 94 | save(); 95 | setInputWidth(); 96 | } 97 | } 98 | 99 | function refocus(e) { 100 | if (e.target.classList.contains('tag')) select(e.target); 101 | if (e.target===base.input) return select(); 102 | base.input.focus(); 103 | e.preventDefault(); 104 | return false; 105 | } 106 | 107 | function caretAtStart(el) { 108 | try { 109 | return el.selectionStart === 0 && el.selectionEnd === 0; 110 | } 111 | catch(e) { 112 | return el.value === ''; 113 | } 114 | } 115 | 116 | 117 | let base = createElement('div', 'tags-input'), 118 | sib = input.nextSibling; 119 | 120 | input.parentNode[sib?'insertBefore':'appendChild'](base, sib); 121 | 122 | input.style.cssText = 'position:absolute;left:0;top:-99px;width:1px;height:1px;opacity:0.01;'; 123 | input.tabIndex = -1; 124 | 125 | let inputType = input.getAttribute('type'); 126 | if (!inputType || inputType === 'tags') { 127 | inputType = 'text'; 128 | } 129 | base.input = createElement('input'); 130 | base.input.setAttribute('type', inputType); 131 | COPY_PROPS.forEach( prop => { 132 | if (input[prop]!==base.input[prop]) { 133 | base.input[prop] = input[prop]; 134 | try { delete input[prop]; }catch(e){} 135 | } 136 | }); 137 | base.appendChild(base.input); 138 | 139 | input.addEventListener('focus', () => { 140 | base.input.focus(); 141 | }); 142 | 143 | base.input.addEventListener('focus', () => { 144 | base.classList.add('focus'); 145 | select(); 146 | }); 147 | 148 | base.input.addEventListener('blur', () => { 149 | base.classList.remove('focus'); 150 | select(); 151 | savePartialInput(); 152 | }); 153 | 154 | base.input.addEventListener('keydown', e => { 155 | let el = base.input, 156 | key = e.keyCode || e.which, 157 | selectedTag = $('.tag.selected'), 158 | atStart = caretAtStart(el), 159 | last = $('.tag',true).pop(); 160 | 161 | setInputWidth(); 162 | 163 | if (key===ENTER || key===COMMA || key===TAB) { 164 | if (!el.value && key!==COMMA) return; 165 | savePartialInput(); 166 | } 167 | else if (key===DELETE && selectedTag) { 168 | if (selectedTag.nextSibling!==base.input) select(selectedTag.nextSibling); 169 | base.removeChild(selectedTag); 170 | setInputWidth(); 171 | save(); 172 | } 173 | else if (key===BACKSPACE) { 174 | if (selectedTag) { 175 | select(selectedTag.previousSibling); 176 | base.removeChild(selectedTag); 177 | setInputWidth(); 178 | save(); 179 | } 180 | else if (last && atStart) { 181 | select(last); 182 | } 183 | else { 184 | return; 185 | } 186 | } 187 | else if (key===LEFT) { 188 | if (selectedTag) { 189 | if (selectedTag.previousSibling) { 190 | select(selectedTag.previousSibling); 191 | } 192 | } 193 | else if (!atStart) { 194 | return; 195 | } 196 | else { 197 | select(last); 198 | } 199 | } 200 | else if (key===RIGHT) { 201 | if (!selectedTag) return; 202 | select(selectedTag.nextSibling); 203 | } 204 | else { 205 | return select(); 206 | } 207 | 208 | e.preventDefault(); 209 | return false; 210 | }); 211 | 212 | // Proxy "input" (live change) events , update the first tag live as the user types 213 | // This means that users who only want one thing don't have to enter commas 214 | base.input.addEventListener('input', () => { 215 | input.value = getValue(); 216 | input.dispatchEvent(new Event('input')); 217 | }); 218 | 219 | // One tick after pasting, parse pasted text as CSV: 220 | base.input.addEventListener('paste', () => setTimeout(savePartialInput, 0)); 221 | 222 | base.addEventListener('mousedown', refocus); 223 | base.addEventListener('touchstart', refocus); 224 | 225 | base.setValue = setValue; 226 | base.getValue = getValue; 227 | 228 | // Add tags for existing values 229 | savePartialInput(input.value); 230 | } 231 | 232 | // make life easier: 233 | tagsInput.enhance = tagsInput.tagsInput = tagsInput; 234 | -------------------------------------------------------------------------------- /src/tags-input.less: -------------------------------------------------------------------------------- 1 | .tags-input { 2 | display: inline-block; 3 | padding: 0 2px; 4 | background: #FFF; 5 | border: 1px solid #CCC; 6 | width: 16em; 7 | border-radius: 2px; 8 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.1); 9 | 10 | .tag { 11 | display: inline-block; 12 | background: #EEE; 13 | color: #444; 14 | padding: 0 4px; 15 | margin: 2px; 16 | border: 1px solid #CCC; 17 | border-radius: 2px; 18 | font: inherit; 19 | user-select: none; 20 | cursor: pointer; 21 | transition: all 100ms ease; 22 | 23 | &.selected { 24 | background-color: #777; 25 | border-color: #777; 26 | color: #EEE; 27 | } 28 | 29 | &.dupe { 30 | transform: scale3d(1.2,1.2,1.2); 31 | background-color: #FCC; 32 | border-color: #700; 33 | } 34 | } 35 | 36 | input { 37 | appearance: none !important; 38 | display: inline-block !important; 39 | padding: 3px; 40 | margin: 0 !important; 41 | background: none !important; 42 | border: none !important; 43 | box-shadow: none !important; 44 | font: inherit !important; 45 | font-size: 100% !important; 46 | outline: none !important; 47 | } 48 | 49 | .selected ~ input { 50 | opacity: 0.3; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tags-input.css: -------------------------------------------------------------------------------- 1 | .tags-input{display:inline-block;padding:0 2px;background:#FFF;border:1px solid #CCC;width:16em;border-radius:2px;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.tags-input .tag{display:inline-block;background:#EEE;color:#444;padding:0 4px;margin:2px;border:1px solid #CCC;border-radius:2px;font:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;transition:all .1s ease}.tags-input .tag.selected{background-color:#777;border-color:#777;color:#EEE}.tags-input .tag.dupe{-webkit-transform:scale3d(1.2,1.2,1.2);transform:scale3d(1.2,1.2,1.2);background-color:#FCC;border-color:#700}.tags-input input{-webkit-appearance:none!important;-moz-appearance:none!important;appearance:none!important;display:inline-block!important;padding:3px;margin:0!important;background:0 0!important;border:none!important;box-shadow:none!important;font:inherit!important;font-size:100%!important;outline:0!important}.tags-input .selected~input{opacity:.3} 2 | /*# sourceMappingURL=tags-input.css.map */ -------------------------------------------------------------------------------- /tags-input.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/tags-input.less"],"names":[],"mappings":"AAAA,YACC,qBACA,cACA,gBACA,sBACA,WACA,kBACA,yCAAA,CAEA,iBACC,qBACA,gBACA,WACA,cACA,WACA,sBACA,kBACA,aACA,yBACA,AADA,sBACA,AADA,qBACA,AADA,iBACA,eACA,uBAAA,CAEC,0BACA,sBACA,kBACA,UAAA,CAGA,sBACA,uCACA,AADA,+BACA,sBACA,iBAAA,CAIF,kBACC,kCACA,AADA,+BACA,AADA,0BACA,+BACA,YACA,mBACA,yBACA,sBACA,0BACA,uBACA,yBACA,mBAAA,CAGS,4BACT,UAAA,CAAA","file":"tags-input.css","sourcesContent":[".tags-input {\n\tdisplay: inline-block;\n\tpadding: 0 2px;\n\tbackground: #FFF;\n\tborder: 1px solid #CCC;\n\twidth: 16em;\n\tborder-radius: 2px;\n\tbox-shadow: inset 0 1px 2px rgba(0,0,0,0.1);\n\n\t.tag {\n\t\tdisplay: inline-block;\n\t\tbackground: #EEE;\n\t\tcolor: #444;\n\t\tpadding: 0 4px;\n\t\tmargin: 2px;\n\t\tborder: 1px solid #CCC;\n\t\tborder-radius: 2px;\n\t\tfont: inherit;\n\t\tuser-select: none;\n\t\tcursor: pointer;\n\t\ttransition: all 100ms ease;\n\n\t\t&.selected {\n\t\t\tbackground-color: #777;\n\t\t\tborder-color: #777;\n\t\t\tcolor: #EEE;\n\t\t}\n\n\t\t&.dupe {\n\t\t\ttransform: scale3d(1.2,1.2,1.2);\n\t\t\tbackground-color: #FCC;\n\t\t\tborder-color: #700;\n\t\t}\n\t}\n\n\tinput {\n\t\tappearance: none !important;\n\t\tdisplay: inline-block !important;\n\t\tpadding: 3px;\n\t\tmargin: 0 !important;\n\t\tbackground: none !important;\n\t\tborder: none !important;\n\t\tbox-shadow: none !important;\n\t\tfont: inherit !important;\n\t\tfont-size: 100% !important;\n\t\toutline: none !important;\n\t}\n\n\t.selected ~ input {\n\t\topacity: 0.3;\n\t}\n}\n"]} -------------------------------------------------------------------------------- /tags-input.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){if(typeof define === 'function' && define.amd){define(['exports','module'],factory);}else if(typeof exports !== 'undefined' && typeof module !== 'undefined'){factory(exports,module);}else {var mod={exports:{}};factory(mod.exports,mod);global.tagsInput = mod.exports;}})(this,function(exports,module){'use strict';module.exports = tagsInput;var BACKSPACE=8,TAB=9,ENTER=13,LEFT=37,RIGHT=39,DELETE=46,COMMA=188;var SEPERATOR=',';var COPY_PROPS='placeholder pattern spellcheck autocomplete autocapitalize autofocus accessKey accept lang minLength maxLength required'.split(' ');function tagsInput(input){function createElement(type,name,text,attributes){var el=document.createElement(type);if(name)el.className = name;if(text)el.textContent = text;for(var key in attributes) {el.setAttribute('data-' + key,attributes[key]);}return el;}function $(selector,all){return all === true?Array.prototype.slice.call(base.querySelectorAll(selector)):base.querySelector(selector);}function getValue(){return $('.tag',true).map(function(tag){return tag.textContent;}).concat(base.input.value || []).join(SEPERATOR);}function setValue(value){$('.tag',true).forEach(function(t){return base.removeChild(t);});savePartialInput(value);}function save(){input.value = getValue();input.dispatchEvent(new Event('change'));}function addTag(text){if(~text.indexOf(SEPERATOR))text = text.split(SEPERATOR);if(Array.isArray(text))return text.forEach(addTag);var tag=text && text.trim();if(!tag)return false;if(!input.getAttribute('duplicates')){var _ret=(function(){var exisingTag=$('[data-tag="' + tag + '"]');if(exisingTag){exisingTag.classList.add('dupe');setTimeout(function(){return exisingTag.classList.remove('dupe');},100);return {v:false};}})();if(typeof _ret === 'object')return _ret.v;}base.insertBefore(createElement('span','tag',tag,{tag:tag}),base.input);}function select(el){var sel=$('.selected');if(sel)sel.classList.remove('selected');if(el)el.classList.add('selected');}function setInputWidth(){var last=$('.tag',true).pop(),w=base.offsetWidth;if(!w)return;base.input.style.width = Math.max(w - (last?last.offsetLeft + last.offsetWidth:5) - 5,w / 4) + 'px';}function savePartialInput(value){if(typeof value !== 'string' && !Array.isArray(value)){value = base.input.value;}if(addTag(value) !== false){base.input.value = '';save();setInputWidth();}}function refocus(e){if(e.target.classList.contains('tag'))select(e.target);if(e.target === base.input)return select();base.input.focus();e.preventDefault();return false;}var base=createElement('div','tags-input'),sib=input.nextSibling;input.parentNode[sib?'insertBefore':'appendChild'](base,sib);input.style.cssText = 'position:absolute;left:0;top:-99px;width:1px;height:1px;opacity:0.01;';input.tabIndex = -1;base.input = createElement('input');base.input.setAttribute('type','text');COPY_PROPS.forEach(function(prop){if(input[prop] !== base.input[prop]){base.input[prop] = input[prop];try{delete input[prop];}catch(e) {}}});base.appendChild(base.input);input.addEventListener('focus',function(){base.input.focus();});base.input.addEventListener('focus',function(){base.classList.add('focus');select();});base.input.addEventListener('blur',function(){base.classList.remove('focus');select();savePartialInput();});base.input.addEventListener('keydown',function(e){var el=base.input,key=e.keyCode || e.which,selectedTag=$('.tag.selected'),pos=el.selectionStart === el.selectionEnd && el.selectionStart,last=$('.tag',true).pop();setInputWidth();if(key === ENTER || key === COMMA || key === TAB){if(!el.value && key !== COMMA)return;savePartialInput();}else if(key === DELETE && selectedTag){if(selectedTag.nextSibling !== base.input)select(selectedTag.nextSibling);base.removeChild(selectedTag);setInputWidth();save();}else if(key === BACKSPACE){if(selectedTag){select(selectedTag.previousSibling);base.removeChild(selectedTag);setInputWidth();save();}else if(last && pos === 0){select(last);}else {return;}}else if(key === LEFT){if(selectedTag){if(selectedTag.previousSibling){select(selectedTag.previousSibling);}}else if(pos !== 0){return;}else {select(last);}}else if(key === RIGHT){if(!selectedTag)return;select(selectedTag.nextSibling);}else {return select();}e.preventDefault();return false;});base.input.addEventListener('input',function(){input.value = getValue();input.dispatchEvent(new Event('input'));});base.input.addEventListener('paste',function(){return setTimeout(savePartialInput,0);});base.addEventListener('mousedown',refocus);base.addEventListener('touchstart',refocus);base.setValue = setValue;base.getValue = getValue;savePartialInput(input.value);}tagsInput.enhance = tagsInput.tagsInput = tagsInput;}); 2 | //# sourceMappingURL=tags-input.js.map -------------------------------------------------------------------------------- /tags-input.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/tags-input.js"],"names":[],"mappings":"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","file":"tags-input.js","sourceRoot":"src","sourcesContent":["const BACKSPACE = 8,\n\tTAB = 9,\n\tENTER = 13,\n\tLEFT = 37,\n\tRIGHT = 39,\n\tDELETE = 46,\n\tCOMMA = 188;\n\nconst SEPERATOR = ',';\n\nconst COPY_PROPS = 'placeholder pattern spellcheck autocomplete autocapitalize autofocus accessKey accept lang minLength maxLength required'.split(' ');\n\nexport default function tagsInput(input) {\n\tfunction createElement(type, name, text, attributes) {\n\t\tlet el = document.createElement(type);\n\t\tif (name) el.className = name;\n\t\tif (text) el.textContent = text;\n\t\tfor (let key in attributes) {\n\t\t\tel.setAttribute(`data-${key}`, attributes[key]);\n\t\t}\n\t\treturn el;\n\t}\n\n\tfunction $(selector, all) {\n\t\treturn all===true ? Array.prototype.slice.call(base.querySelectorAll(selector)) : base.querySelector(selector);\n\t}\n\n\tfunction getValue() {\n\t\treturn $('.tag', true)\n\t\t\t.map( tag => tag.textContent )\n\t\t\t.concat(base.input.value || [])\n\t\t\t.join(SEPERATOR);\n\t}\n\n\tfunction setValue(value) {\n\t\t$('.tag', true).forEach( t => base.removeChild(t) );\n\t\tsavePartialInput(value);\n\t}\n\n\tfunction save() {\n\t\tinput.value = getValue();\n\t\tinput.dispatchEvent(new Event('change'));\n\t}\n\n\t// Return false if no need to add a tag\n\tfunction addTag(text) {\n\t\t// Add multiple tags if the user pastes in data with SEPERATOR already in it\n\t\tif (~text.indexOf(SEPERATOR)) text = text.split(SEPERATOR);\n\t\tif (Array.isArray(text)) return text.forEach(addTag);\n\n\t\tlet tag = text && text.trim();\n\t\t// Ignore if text is empty\n\t\tif (!tag) return false;\n\n\t\t// For duplicates, briefly highlight the existing tag\n\t\tif (!input.getAttribute('duplicates')) {\n\t\t\tlet exisingTag = $(`[data-tag=\"${tag}\"]`);\n\t\t\tif (exisingTag) {\n\t\t\t\texisingTag.classList.add('dupe');\n\t\t\t\tsetTimeout( () => exisingTag.classList.remove('dupe') , 100);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tbase.insertBefore(\n\t\t\tcreateElement('span', 'tag', tag, { tag }),\n\t\t\tbase.input\n\t\t);\n\t}\n\n\tfunction select(el) {\n\t\tlet sel = $('.selected');\n\t\tif (sel) sel.classList.remove('selected');\n\t\tif (el) el.classList.add('selected');\n\t}\n\n\tfunction setInputWidth() {\n\t\tlet last = $('.tag',true).pop(),\n\t\t\tw = base.offsetWidth;\n\t\tif (!w) return;\n\t\tbase.input.style.width = Math.max(\n\t\t\tw - (last ? (last.offsetLeft+last.offsetWidth) : 5) - 5,\n\t\t\tw/4\n\t\t) + 'px';\n\t}\n\n\tfunction savePartialInput(value) {\n\t\tif (typeof value!=='string' && !Array.isArray(value)) {\n\t\t\t// If the base input does not contain a value, default to the original element passed\n\t\t\tvalue = base.input.value;\n\t\t}\n\t\tif (addTag(value)!==false) {\n\t\t\tbase.input.value = '';\n\t\t\tsave();\n\t\t\tsetInputWidth();\n\t\t}\n\t}\n\n\tfunction refocus(e) {\n\t\tif (e.target.classList.contains('tag')) select(e.target);\n\t\tif (e.target===base.input) return select();\n\t\tbase.input.focus();\n\t\te.preventDefault();\n\t\treturn false;\n\t}\n\n\tlet base = createElement('div', 'tags-input'),\n\t\tsib = input.nextSibling;\n\tinput.parentNode[sib?'insertBefore':'appendChild'](base, sib);\n\n\tinput.style.cssText = 'position:absolute;left:0;top:-99px;width:1px;height:1px;opacity:0.01;';\n\tinput.tabIndex = -1;\n\n\tbase.input = createElement('input');\n\tbase.input.setAttribute('type', 'text');\n\tCOPY_PROPS.forEach( prop => {\n\t\tif (input[prop]!==base.input[prop]) {\n\t\t\tbase.input[prop] = input[prop];\n\t\t\ttry { delete input[prop]; }catch(e){}\n\t\t}\n\t});\n\tbase.appendChild(base.input);\n\n\tinput.addEventListener('focus', () => {\n\t\tbase.input.focus();\n\t});\n\n\tbase.input.addEventListener('focus', () => {\n\t\tbase.classList.add('focus');\n\t\tselect();\n\t});\n\n\tbase.input.addEventListener('blur', () => {\n\t\tbase.classList.remove('focus');\n\t\tselect();\n\t\tsavePartialInput();\n\t});\n\n\tbase.input.addEventListener('keydown', e => {\n\t\tlet el = base.input,\n\t\t\tkey = e.keyCode || e.which,\n\t\t\tselectedTag = $('.tag.selected'),\n\t\t\tpos = el.selectionStart===el.selectionEnd && el.selectionStart,\n\t\t\tlast = $('.tag',true).pop();\n\n\t\tsetInputWidth();\n\n\t\tif (key===ENTER || key===COMMA || key===TAB) {\n\t\t\tif (!el.value && key!==COMMA) return;\n\t\t\tsavePartialInput();\n\t\t}\n\t\telse if (key===DELETE && selectedTag) {\n\t\t\tif (selectedTag.nextSibling!==base.input) select(selectedTag.nextSibling);\n\t\t\tbase.removeChild(selectedTag);\n\t\t\tsetInputWidth();\n\t\t\tsave();\n\t\t}\n\t\telse if (key===BACKSPACE) {\n\t\t\tif (selectedTag) {\n\t\t\t\tselect(selectedTag.previousSibling);\n\t\t\t\tbase.removeChild(selectedTag);\n\t\t\t\tsetInputWidth();\n\t\t\t\tsave();\n\t\t\t}\n\t\t\telse if (last && pos===0) {\n\t\t\t\tselect(last);\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse if (key===LEFT) {\n\t\t\tif (selectedTag) {\n\t\t\t\tif (selectedTag.previousSibling) {\n\t\t\t\t\tselect(selectedTag.previousSibling);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (pos!==0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tselect(last);\n\t\t\t}\n\t\t}\n\t\telse if (key===RIGHT) {\n\t\t\tif (!selectedTag) return;\n\t\t\tselect(selectedTag.nextSibling);\n\t\t}\n\t\telse {\n\t\t\treturn select();\n\t\t}\n\n\t\te.preventDefault();\n\t\treturn false;\n\t});\n\n\t// Proxy \"input\" (live change) events , update the first tag live as the user types\n\t// This means that users who only want one thing don't have to enter commas\n\tbase.input.addEventListener('input', () => {\n\t\tinput.value = getValue();\n\t\tinput.dispatchEvent(new Event('input'));\n\t});\n\n\t// One tick after pasting, parse pasted text as CSV:\n\tbase.input.addEventListener('paste', () => setTimeout(savePartialInput, 0));\n\n\tbase.addEventListener('mousedown', refocus);\n\tbase.addEventListener('touchstart', refocus);\n\n\tbase.setValue = setValue;\n\tbase.getValue = getValue;\n\n\t// Add tags for existing values\n\tsavePartialInput(input.value);\n}\n\n// make life easier:\ntagsInput.enhance = tagsInput.tagsInput = tagsInput;\n"]} --------------------------------------------------------------------------------