Merge pull request #2883 from mbenford/master

[author] Add ngTagsInput 2.0.0
This commit is contained in:
Thomas Davis
2014-03-27 15:47:01 +10:00
5 changed files with 895 additions and 1 deletions
@@ -0,0 +1,129 @@
tags-input *, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
tags-input .host {
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
tags-input .host:active {
outline: none;
}
tags-input .tags {
-moz-appearance: textfield;
-webkit-appearance: textfield;
padding: 1px;
overflow: hidden;
word-wrap: break-word;
cursor: text;
background-color: white;
border: 1px solid darkgray;
box-shadow: 1px 1px 1px 0 lightgray inset;
}
tags-input .tags.focused {
outline: none;
-webkit-box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
-moz-box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
}
tags-input .tags .tag-list {
margin: 0;
padding: 0;
list-style-type: none;
}
tags-input .tags .tag-item {
margin: 2px;
padding: 0 5px;
display: inline-block;
float: left;
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
height: 26px;
line-height: 25px;
border: 1px solid #acacac;
border-radius: 3px;
background: -webkit-linear-gradient(top, #f0f9ff 0%, #cbebff 47%, #a1dbff 100%);
background: linear-gradient(to bottom, #f0f9ff 0%, #cbebff 47%, #a1dbff 100%);
}
tags-input .tags .tag-item.selected {
background: -webkit-linear-gradient(top, #febbbb 0%, #fe9090 45%, #ff5c5c 100%);
background: linear-gradient(to bottom, #febbbb 0%, #fe9090 45%, #ff5c5c 100%);
}
tags-input .tags .tag-item .remove-button {
margin: 0 0 0 5px;
padding: 0;
border: none;
background: none;
cursor: pointer;
vertical-align: middle;
font: bold 16px Arial, sans-serif;
color: #585858;
}
tags-input .tags .tag-item .remove-button:active {
color: red;
}
tags-input .tags .input {
border: 0;
outline: none;
margin: 2px;
padding: 0;
padding-left: 5px;
float: left;
height: 26px;
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
tags-input .tags .input.invalid-tag {
color: red;
}
tags-input .tags .input::-ms-clear {
display: none;
}
tags-input.ng-invalid .tags {
-webkit-box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
-moz-box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
}
tags-input .autocomplete {
margin-top: 5px;
position: absolute;
padding: 5px 0;
z-index: 999;
width: 100%;
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
tags-input .autocomplete .suggestion-list {
margin: 0;
padding: 0;
list-style-type: none;
}
tags-input .autocomplete .suggestion-item {
padding: 5px 10px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font: 16px "Helvetica Neue", Helvetica, Arial, sans-serif;
color: black;
background-color: white;
}
tags-input .autocomplete .suggestion-item.selected {
color: white;
background-color: #0097cf;
}
tags-input .autocomplete .suggestion-item.selected em {
color: white;
background-color: #0097cf;
}
tags-input .autocomplete .suggestion-item em {
font: normal bold 16px "Helvetica Neue", Helvetica, Arial, sans-serif;
color: black;
background-color: white;
}
@@ -0,0 +1,763 @@
/*!
* ngTagsInput v2.0.0
* http://mbenford.github.io/ngTagsInput
*
* Copyright (c) 2013-2014 Michael Benford
* License: MIT
*
* Generated at 2014-03-26 01:52:40 -0300
*/
(function() {
'use strict';
var KEYS = {
backspace: 8,
tab: 9,
enter: 13,
escape: 27,
space: 32,
up: 38,
down: 40,
comma: 188
};
function SimplePubSub() {
var events = {};
return {
on: function(names, handler) {
names.split(' ').forEach(function(name) {
if (!events[name]) {
events[name] = [];
}
events[name].push(handler);
});
return this;
},
trigger: function(name, args) {
angular.forEach(events[name], function(handler) {
handler.call(null, args);
});
return this;
}
};
}
function makeObjectArray(array, key) {
array = array || [];
if (array.length > 0 && !angular.isObject(array[0])) {
array.forEach(function(item, index) {
array[index] = {};
array[index][key] = item;
});
}
return array;
}
function findInObjectArray(array, obj, key) {
var item = null;
for (var i = 0; i < array.length; i++) {
// I'm aware of the internationalization issues regarding toLowerCase()
// but I couldn't come up with a better solution right now
if (array[i][key].toLowerCase() === obj[key].toLowerCase()) {
item = array[i];
break;
}
}
return item;
}
var tagsInput = angular.module('ngTagsInput', []);
/**
* @ngdoc directive
* @name tagsInput.directive:tagsInput
*
* @description
* Renders an input box with tag editing support.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} [displayProperty=text] Property to be rendered as the tag label.
* @param {number=} tabindex Tab order of the control.
* @param {string=} [placeholder=Add a tag] Placeholder text for the control.
* @param {number=} [minLength=3] Minimum length for a new tag.
* @param {number=} maxLength Maximum length allowed for a new tag.
* @param {number=} minTags Sets minTags validation error key if the number of tags added is less than minTags.
* @param {number=} maxTags Sets maxTags validation error key if the number of tags added is greater than maxTags.
* @param {boolean=} [allowLeftoverText=false] Sets leftoverText validation error key if there is any leftover text in
* the input element when the directive loses focus.
* @param {string=} [removeTagSymbol=×] Symbol character for the remove tag button.
* @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key.
* @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key.
* @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key.
* @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus.
* @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes.
* @param {string=} [allowedTagsPattern=^&#91;a-zA-Z0-9\s&#93;+$] Regular expression that determines whether a new tag is valid.
* @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into
* the new tag input box instead of being removed when the backspace key
* is pressed and the input box is empty.
* @param {boolean=} [addFromAutocompleteOnly=false] Flag indicating that only tags coming from the autocomplete list will be allowed.
* When this flag is true, addOnEnter, addOnComma, addOnSpace, addOnBlur and
* allowLeftoverText values are ignored.
* @param {expression} onTagAdded Expression to evaluate upon adding a new tag. The new tag is available as $tag.
* @param {expression} onTagRemoved Expression to evaluate upon removing an existing tag. The removed tag is available as $tag.
*/
tagsInput.directive('tagsInput', ["$timeout","$document","tagsInputConfig", function($timeout, $document, tagsInputConfig) {
function TagList(options, events) {
var self = {}, getTagText, setTagText, tagIsValid;
getTagText = function(tag) {
return tag[options.displayProperty];
};
setTagText = function(tag, text) {
tag[options.displayProperty] = text;
};
tagIsValid = function(tag) {
var tagText = getTagText(tag);
return tagText.length >= options.minLength &&
tagText.length <= (options.maxLength || tagText.length) &&
options.allowedTagsPattern.test(tagText) &&
!findInObjectArray(self.items, tag, options.displayProperty);
};
self.items = [];
self.addText = function(text) {
var tag = {};
setTagText(tag, text);
return self.add(tag);
};
self.add = function(tag) {
var tagText = getTagText(tag).trim();
if (options.replaceSpacesWithDashes) {
tagText = tagText.replace(/\s/g, '-');
}
setTagText(tag, tagText);
if (tagIsValid(tag)) {
self.items.push(tag);
events.trigger('tag-added', { $tag: tag });
}
else {
events.trigger('invalid-tag', { $tag: tag });
}
return tag;
};
self.remove = function(index) {
var tag = self.items.splice(index, 1)[0];
events.trigger('tag-removed', { $tag: tag });
return tag;
};
self.removeLast = function() {
var tag, lastTagIndex = self.items.length - 1;
if (options.enableEditingLastTag || self.selected) {
self.selected = null;
tag = self.remove(lastTagIndex);
}
else if (!self.selected) {
self.selected = self.items[lastTagIndex];
}
return tag;
};
return self;
}
return {
restrict: 'E',
require: 'ngModel',
scope: {
tags: '=ngModel',
onTagAdded: '&',
onTagRemoved: '&'
},
replace: false,
transclude: true,
templateUrl: 'ngTagsInput/tags-input.html',
controller: ["$scope","$attrs","$element", function($scope, $attrs, $element) {
tagsInputConfig.load('tagsInput', $scope, $attrs, {
placeholder: [String, 'Add a tag'],
tabindex: [Number],
removeTagSymbol: [String, String.fromCharCode(215)],
replaceSpacesWithDashes: [Boolean, true],
minLength: [Number, 3],
maxLength: [Number],
addOnEnter: [Boolean, true],
addOnSpace: [Boolean, false],
addOnComma: [Boolean, true],
addOnBlur: [Boolean, true],
allowedTagsPattern: [RegExp, /.+/],
enableEditingLastTag: [Boolean, false],
minTags: [Number],
maxTags: [Number],
displayProperty: [String, 'text'],
allowLeftoverText: [Boolean, false],
addFromAutocompleteOnly: [Boolean, false]
});
$scope.events = new SimplePubSub();
$scope.tagList = new TagList($scope.options, $scope.events);
this.registerAutocomplete = function() {
var input = $element.find('input');
input.on('keydown', function(e) {
$scope.events.trigger('input-keydown', e);
});
return {
addTag: function(tag) {
return $scope.tagList.add(tag);
},
focusInput: function() {
input[0].focus();
},
getTags: function() {
return $scope.tags;
},
getOptions: function() {
return $scope.options;
},
on: function(name, handler) {
$scope.events.on(name, handler);
return this;
}
};
};
}],
link: function(scope, element, attrs, ngModelCtrl) {
var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace],
tagList = scope.tagList,
events = scope.events,
options = scope.options,
input = element.find('input');
events
.on('tag-added', scope.onTagAdded)
.on('tag-removed', scope.onTagRemoved)
.on('tag-added', function() {
scope.newTag.text = '';
})
.on('tag-added tag-removed', function() {
ngModelCtrl.$setViewValue(scope.tags);
})
.on('invalid-tag', function() {
scope.newTag.invalid = true;
})
.on('input-change', function() {
tagList.selected = null;
scope.newTag.invalid = null;
})
.on('input-focus', function() {
ngModelCtrl.$setValidity('leftoverText', true);
})
.on('input-blur', function() {
if (!options.addFromAutocompleteOnly) {
if (options.addOnBlur) {
tagList.addText(scope.newTag.text);
}
ngModelCtrl.$setValidity('leftoverText', options.allowLeftoverText ? true : !scope.newTag.text);
}
});
scope.newTag = { text: '', invalid: null };
scope.getDisplayText = function(tag) {
return tag[options.displayProperty].trim();
};
scope.track = function(tag) {
return tag[options.displayProperty];
};
scope.newTagChange = function() {
events.trigger('input-change', scope.newTag.text);
};
scope.$watch('tags', function(value) {
scope.tags = makeObjectArray(value, options.displayProperty);
tagList.items = scope.tags;
});
scope.$watch('tags.length', function(value) {
ngModelCtrl.$setValidity('maxTags', angular.isUndefined(options.maxTags) || value <= options.maxTags);
ngModelCtrl.$setValidity('minTags', angular.isUndefined(options.minTags) || value >= options.minTags);
});
input
.on('keydown', function(e) {
// This hack is needed because jqLite doesn't implement stopImmediatePropagation properly.
// I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon.
// https://github.com/angular/angular.js/pull/4833
if (e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) {
return;
}
var key = e.keyCode,
isModifier = e.shiftKey || e.altKey || e.ctrlKey || e.metaKey,
addKeys = {},
shouldAdd, shouldRemove;
if (isModifier || hotkeys.indexOf(key) === -1) {
return;
}
addKeys[KEYS.enter] = options.addOnEnter;
addKeys[KEYS.comma] = options.addOnComma;
addKeys[KEYS.space] = options.addOnSpace;
shouldAdd = !options.addFromAutocompleteOnly && addKeys[key];
shouldRemove = !shouldAdd && key === KEYS.backspace && scope.newTag.text.length === 0;
if (shouldAdd) {
tagList.addText(scope.newTag.text);
scope.$apply();
e.preventDefault();
}
else if (shouldRemove) {
var tag = tagList.removeLast();
if (tag && options.enableEditingLastTag) {
scope.newTag.text = tag[options.displayProperty];
}
scope.$apply();
e.preventDefault();
}
})
.on('focus', function() {
if (scope.hasFocus) {
return;
}
scope.hasFocus = true;
events.trigger('input-focus');
scope.$apply();
})
.on('blur', function() {
$timeout(function() {
var activeElement = $document.prop('activeElement'),
lostFocusToBrowserWindow = activeElement === input[0],
lostFocusToChildElement = element[0].contains(activeElement);
if (lostFocusToBrowserWindow || !lostFocusToChildElement) {
scope.hasFocus = false;
events.trigger('input-blur');
}
});
});
element.find('div').on('click', function() {
input[0].focus();
});
}
};
}]);
/**
* @ngdoc directive
* @name tagsInput.directive:autoComplete
*
* @description
* Provides autocomplete support for the tagsInput directive.
*
* @param {expression} source Expression to evaluate upon changing the input content. The input value is available as
* $query. The result of the expression must be a promise that eventually resolves to an
* array of strings.
* @param {number=} [debounceDelay=100] Amount of time, in milliseconds, to wait before evaluating the expression in
* the source option after the last keystroke.
* @param {number=} [minLength=3] Minimum number of characters that must be entered before evaluating the expression
* in the source option.
* @param {boolean=} [highlightMatchedText=true] Flag indicating that the matched text will be highlighted in the
* suggestions list.
* @param {number=} [maxResultsToShow=10] Maximum number of results to be displayed at a time.
*/
tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputConfig", function($document, $timeout, $sce, tagsInputConfig) {
function SuggestionList(loadFn, options) {
var self = {}, debouncedLoadId, getDifference, lastPromise;
getDifference = function(array1, array2) {
return array1.filter(function(item) {
return !findInObjectArray(array2, item, options.tagsInput.displayProperty);
});
};
self.reset = function() {
lastPromise = null;
self.items = [];
self.visible = false;
self.index = -1;
self.selected = null;
self.query = null;
$timeout.cancel(debouncedLoadId);
};
self.show = function() {
self.selected = null;
self.visible = true;
};
self.load = function(query, tags) {
if (query.length < options.minLength) {
self.reset();
return;
}
$timeout.cancel(debouncedLoadId);
debouncedLoadId = $timeout(function() {
self.query = query;
var promise = loadFn({ $query: query });
lastPromise = promise;
promise.then(function(items) {
if (promise !== lastPromise) {
return;
}
items = makeObjectArray(items.data || items, options.tagsInput.displayProperty);
self.items = getDifference(items, tags);
if (self.items.length > 0) {
self.show();
}
else {
self.reset();
}
});
}, options.debounceDelay, false);
};
self.selectNext = function() {
self.select(++self.index);
};
self.selectPrior = function() {
self.select(--self.index);
};
self.select = function(index) {
if (index < 0) {
index = self.items.length - 1;
}
else if (index >= self.items.length) {
index = 0;
}
self.index = index;
self.selected = self.items[index];
};
self.reset();
return self;
}
function encodeHTML(value) {
return value.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
return {
restrict: 'E',
require: '^tagsInput',
scope: { source: '&' },
templateUrl: 'ngTagsInput/auto-complete.html',
link: function(scope, element, attrs, tagsInputCtrl) {
var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down],
suggestionList, tagsInput, options, getItemText, markdown;
tagsInputConfig.load('autoComplete', scope, attrs, {
debounceDelay: [Number, 100],
minLength: [Number, 3],
highlightMatchedText: [Boolean, true],
maxResultsToShow: [Number, 10]
});
options = scope.options;
tagsInput = tagsInputCtrl.registerAutocomplete();
options.tagsInput = tagsInput.getOptions();
suggestionList = new SuggestionList(scope.source, options);
getItemText = function(item) {
return item[options.tagsInput.displayProperty];
};
if (options.highlightMatchedText) {
markdown = function(item, text) {
var expression = new RegExp(text, 'gi');
return item.replace(expression, '**$&**');
};
}
else {
markdown = function(item) {
return item;
};
}
scope.suggestionList = suggestionList;
scope.addSuggestion = function() {
var added = false;
if (suggestionList.selected) {
tagsInput.addTag(suggestionList.selected);
suggestionList.reset();
tagsInput.focusInput();
added = true;
}
return added;
};
scope.highlight = function(item) {
var text = getItemText(item);
text = markdown(text, suggestionList.query);
text = encodeHTML(text);
text = text.replace(/\*\*(.+?)\*\*/g, '<em>$1</em>');
return $sce.trustAsHtml(text);
};
scope.track = function(item) {
return getItemText(item);
};
tagsInput
.on('tag-added invalid-tag', function() {
suggestionList.reset();
})
.on('input-change', function(value) {
if (value) {
suggestionList.load(value, tagsInput.getTags());
} else {
suggestionList.reset();
}
})
.on('input-keydown', function(e) {
var key, handled;
if (hotkeys.indexOf(e.keyCode) === -1) {
return;
}
// This hack is needed because jqLite doesn't implement stopImmediatePropagation properly.
// I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon.
// https://github.com/angular/angular.js/pull/4833
var immediatePropagationStopped = false;
e.stopImmediatePropagation = function() {
immediatePropagationStopped = true;
e.stopPropagation();
};
e.isImmediatePropagationStopped = function() {
return immediatePropagationStopped;
};
if (suggestionList.visible) {
key = e.keyCode;
handled = false;
if (key === KEYS.down) {
suggestionList.selectNext();
handled = true;
}
else if (key === KEYS.up) {
suggestionList.selectPrior();
handled = true;
}
else if (key === KEYS.escape) {
suggestionList.reset();
handled = true;
}
else if (key === KEYS.enter || key === KEYS.tab) {
handled = scope.addSuggestion();
}
if (handled) {
e.preventDefault();
e.stopImmediatePropagation();
scope.$apply();
}
}
})
.on('input-blur', function() {
suggestionList.reset();
});
$document.on('click', function() {
if (suggestionList.visible) {
suggestionList.reset();
scope.$apply();
}
});
}
};
}]);
/**
* @ngdoc directive
* @name tagsInput.directive:tiTranscludeAppend
*
* @description
* Re-creates the old behavior of ng-transclude. Used internally by tagsInput directive.
*/
tagsInput.directive('tiTranscludeAppend', function() {
return function(scope, element, attrs, ctrl, transcludeFn) {
transcludeFn(function(clone) {
element.append(clone);
});
};
});
/**
* @ngdoc directive
* @name tagsInput.directive:tiAutosize
*
* @description
* Automatically sets the input's width so its content is always visible. Used internally by tagsInput directive.
*/
tagsInput.directive('tiAutosize', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var THRESHOLD = 3,
span, resize;
span = angular.element('<span class="input"></span>');
span.css('display', 'none')
.css('visibility', 'hidden')
.css('width', 'auto')
.css('white-space', 'pre');
element.parent().append(span);
resize = function(originalValue) {
var value = originalValue, width;
if (angular.isString(value) && value.length === 0) {
value = attrs.placeholder;
}
if (value) {
span.text(value);
span.css('display', '');
width = span.prop('offsetWidth');
span.css('display', 'none');
}
element.css('width', width ? width + THRESHOLD + 'px' : '');
return originalValue;
};
ctrl.$parsers.unshift(resize);
ctrl.$formatters.unshift(resize);
}
};
});
/**
* @ngdoc service
* @name tagsInput.service:tagsInputConfig
*
* @description
* Sets global configuration settings for both tagsInput and autoComplete directives. It's also used internally to parse and
* initialize options from HTML attributes.
*/
tagsInput.provider('tagsInputConfig', function() {
var globalDefaults = {}, interpolationStatus = {};
/**
* @ngdoc method
* @name setDefaults
* @description Sets the default configuration option for a directive.
* @methodOf tagsInput.service:tagsInputConfig
*
* @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'.
* @param {object} defaults Object containing options and their values.
*
* @returns {object} The service itself for chaining purposes.
*/
this.setDefaults = function(directive, defaults) {
globalDefaults[directive] = defaults;
return this;
};
/***
* @ngdoc method
* @name setActiveInterpolation
* @description Sets active interpolation for a set of options.
* @methodOf tagsInput.service:tagsInputConfig
*
* @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'.
* @param {object} options Object containing which options should have interpolation turned on at all times.
*
* @returns {object} The service itself for chaining purposes.
*/
this.setActiveInterpolation = function(directive, options) {
interpolationStatus[directive] = options;
return this;
};
this.$get = ["$interpolate", function($interpolate) {
var converters = {};
converters[String] = function(value) { return value; };
converters[Number] = function(value) { return parseInt(value, 10); };
converters[Boolean] = function(value) { return value.toLowerCase() === 'true'; };
converters[RegExp] = function(value) { return new RegExp(value); };
return {
load: function(directive, scope, attrs, options) {
scope.options = {};
angular.forEach(options, function(value, key) {
var type, localDefault, converter, getDefault, updateValue;
type = value[0];
localDefault = value[1];
converter = converters[type];
getDefault = function() {
var globalValue = globalDefaults[directive] && globalDefaults[directive][key];
return angular.isDefined(globalValue) ? globalValue : localDefault;
};
updateValue = function(value) {
scope.options[key] = value ? converter(value) : getDefault();
};
if (interpolationStatus[directive] && interpolationStatus[directive][key]) {
attrs.$observe(key, function(value) {
updateValue(value);
});
}
else {
updateValue(attrs[key] && $interpolate(attrs[key])(scope.$parent));
}
});
}
};
}];
});
/* HTML templates */
tagsInput.run(["$templateCache", function($templateCache) {
$templateCache.put('ngTagsInput/tags-input.html',
"<div class=\"host\" tabindex=\"-1\" ti-transclude-append=\"\"><div class=\"tags\" ng-class=\"{focused: hasFocus}\"><ul class=\"tag-list\"><li class=\"tag-item\" ng-repeat=\"tag in tagList.items track by track(tag)\" ng-class=\"{ selected: tag == tagList.selected }\"><span>{{getDisplayText(tag)}}</span> <a class=\"remove-button\" ng-click=\"tagList.remove($index)\">{{options.removeTagSymbol}}</a></li></ul><input class=\"input\" placeholder=\"{{options.placeholder}}\" tabindex=\"{{options.tabindex}}\" ng-model=\"newTag.text\" ng-change=\"newTagChange()\" ng-trim=\"false\" ng-class=\"{'invalid-tag': newTag.invalid}\" ti-autosize=\"\"></div></div>"
);
$templateCache.put('ngTagsInput/auto-complete.html',
"<div class=\"autocomplete\" ng-show=\"suggestionList.visible\"><ul class=\"suggestion-list\"><li class=\"suggestion-item\" ng-repeat=\"item in suggestionList.items | limitTo:options.maxResultsToShow track by track(item)\" ng-class=\"{selected: item == suggestionList.selected}\" ng-click=\"addSuggestion()\" ng-mouseenter=\"suggestionList.select($index)\" ng-bind-html=\"highlight(item)\"></li></ul></div>"
);
}]);
}());
+1
View File
@@ -0,0 +1 @@
:after,:before,tags-input *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ng-tags-input",
"version": "1.1.1",
"version": "2.0.0",
"description": "Tags input directive for AngularJS",
"filename": "ng-tags-input.min.js",
"license": "MIT",