Skip to content

Commit

Permalink
Merge pull request #11 from developit/feature/es6
Browse files Browse the repository at this point in the history
Switch the project to ES6 (via babel) & LESS (via lessc).
  • Loading branch information
developit committed Aug 23, 2015
2 parents 05b02b3 + 78bd24a commit 711b9f2
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 287 deletions.
7 changes: 7 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"modules": "umd",
"moduleId": "tags-input",
"loose": "all",
"compact": true,
"comments": false
}
4 changes: 4 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"esnext": true,
"browser": true
}
20 changes: 18 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@
"version": "0.8.0",
"main": "tags-input.js",
"description": "<input type=\"tags\"> like magic.",
"scripts": {
"build": "npm run transpile && npm run less && npm run size",
"transpile": "babel src -s -d .",
"less": "lessc --clean-css --source-map --autoprefix=\"last 2 versions\" src/tags-input.less tags-input.css",
"size": "echo \"gzip size: $(pretty-bytes $(gzip-size $npm_package_main))\"",
"test": "jshint src/**.js",
"prepublish": "npm run build",
"release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"repository": {
"type": "git",
"url": "git://github.com/developit/tags-input.git"
},
"dependencies": {},
"devDependencies": {}
"devDependencies": {
"babel": "^5.8.21",
"gzip-size": "^3.0.0",
"jshint": "^2.8.0",
"less": "^2.5.1",
"less-plugin-autoprefix": "^1.4.2",
"less-plugin-clean-css": "^1.5.1",
"pretty-bytes": "^2.0.1"
}
}
210 changes: 210 additions & 0 deletions src/tags-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
const BACKSPACE = 8,
TAB = 9,
ENTER = 13,
LEFT = 37,
RIGHT = 39,
DELETE = 46,
COMMA = 188;

const SEPERATOR = ',';

const COPY_PROPS = 'placeholder pattern spellcheck autocomplete autocapitalize autofocus accessKey accept lang minLength maxLength required'.split(' ');

export default function tagsInput(input) {
function createElement(type, name, text, attributes) {
let el = document.createElement(type);
if (name) el.className = name;
if (text) el.textContent = text;
for (let key in attributes) {
el.setAttribute(`data-${key}`, attributes[key]);
}
return el;
}

function $(selector, all) {
return all ? Array.prototype.slice.call(base.querySelectorAll(selector)) : base.querySelector(selector);
}

function getValue() {
return $('.tag', true)
.map( tag => tag.textContent )
.concat(base.input.value || [])
.join(SEPERATOR);
}

function save() {
input.value = getValue();
input.dispatchEvent(new Event('change'));
}

// Return false if no need to add a tag
function addTag(text) {
// Add multiple tags if the user pastes in data with SEPERATOR already in it
if (~text.indexOf(SEPERATOR)) text = text.split(SEPERATOR);
if (Array.isArray(text)) return text.forEach(addTag);

let tag = text && text.trim();
// Ignore if text is empty
if (!tag) return false;

// For duplicates, briefly highlight the existing tag
if (!input.getAttribute('duplicates')) {
let exisingTag = $(`[data-tag="${tag}"]`);
if (exisingTag) {
exisingTag.classList.add('dupe');
setTimeout( () => exisingTag.classList.remove('dupe') , 100);
return false;
}
}

base.insertBefore(
createElement('span', 'tag', tag, { tag }),
base.input
);
}

function select(el) {
let sel = $('.selected');
if (sel) sel.classList.remove('selected');
if (el) el.classList.add('selected');
}

function setInputWidth() {
let 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() {
if (addTag(base.input.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;
}

let 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( prop => {
if (input.hasOwnProperty(prop)) {
base.input[prop] = input[prop];
}
});
base.appendChild(base.input);

delete input.pattern;
input.addEventListener('focus', () => {
base.input.focus();
});

base.input.addEventListener('focus', () => {
base.classList.add('focus');
select();
});

base.input.addEventListener('blur', () => {
base.classList.remove('focus');
select();
savePartialInput();
});

base.input.addEventListener('keydown', e => {
let key = e.keyCode || e.which,
selectedTag = $('.tag.selected'),
pos = this.selectionStart===this.selectionEnd && this.selectionStart,
last = $('.tag',true).pop();

setInputWidth();

if (key===ENTER || key===COMMA || key===TAB) {
if (!this.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;
});

// Proxy "input" (live change) events , update the first tag live as the user types
// This means that users who only want one thing don't have to enter commas
base.input.addEventListener('input', () => {
input.value = getValue();
input.dispatchEvent(new Event('input'));
});

// One tick after pasting, parse pasted text as CSV:
base.input.addEventListener('paste', () => {
setTimeout( () => savePartialInput(), 0);
});

base.addEventListener('mousedown', refocus);
base.addEventListener('touchstart', refocus);

// Add tags for existing values
input.value.split(SEPERATOR).forEach(addTag);
setInputWidth();
}

// make life easier:
tagsInput.enhance = tagsInput.tagsInput = tagsInput;
52 changes: 52 additions & 0 deletions src/tags-input.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.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,0.1);

.tag {
display: inline-block;
background: #EEE;
color: #444;
padding: 0 4px;
margin: 2px;
border: 1px solid #CCC;
border-radius: 2px;
font: inherit;
user-select: none;
cursor: pointer;
transition: all 100ms ease;

&.selected {
background-color: #777;
border-color: #777;
color: #EEE;
}

&.dupe {
transform: scale3d(1.2,1.2,1.2);
background-color: #FCC;
border-color: #700;
}
}

input {
appearance: none !important;
display: inline-block !important;
padding: 3px;
margin: 0 !important;
background: none !important;
border: none !important;
box-shadow: none !important;
font: inherit !important;
font-size: 100% !important;
outline: none !important;
}

.selected ~ input {
opacity: 0.3;
}
}
68 changes: 2 additions & 66 deletions tags-input.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tags-input.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 711b9f2

Please sign in to comment.