From ce8bb032df192e6e592f70588b648d8d3f4026b8 Mon Sep 17 00:00:00 2001 From: Michael Benford Date: Mon, 17 Mar 2014 11:17:54 -0300 Subject: [PATCH] chore(build): Re-add build folder to Git Re-add build folder to Git so users can get the latest compiled version of all available files. --- .gitignore | 1 - build/ng-tags-input.css | 121 +++++++ build/ng-tags-input.js | 701 ++++++++++++++++++++++++++++++++++++ build/ng-tags-input.min.css | 1 + build/ng-tags-input.min.js | 1 + 5 files changed, 824 insertions(+), 1 deletion(-) create mode 100644 build/ng-tags-input.css create mode 100644 build/ng-tags-input.js create mode 100644 build/ng-tags-input.min.css create mode 100644 build/ng-tags-input.min.js diff --git a/.gitignore b/.gitignore index a65c96b9..4bb33e5d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ .settings/ node_modules/ coverage/ -build/ *.log diff --git a/build/ng-tags-input.css b/build/ng-tags-input.css new file mode 100644 index 00000000..daac5465 --- /dev/null +++ b/build/ng-tags-input.css @@ -0,0 +1,121 @@ +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::-ms-clear { + display: none; +} + +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; +} diff --git a/build/ng-tags-input.js b/build/ng-tags-input.js new file mode 100644 index 00000000..1c90382b --- /dev/null +++ b/build/ng-tags-input.js @@ -0,0 +1,701 @@ +/*! + * ngTagsInput v2.0.0 + * http://mbenford.github.io/ngTagsInput + * + * Copyright (c) 2013-2014 Michael Benford + * License: MIT + * + * Generated at 2014-03-16 01:16:56 -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 {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=^[a-zA-Z0-9\s]+$] 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 {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; + + getTagText = function(tag) { + return tag[options.displayProperty]; + }; + + setTagText = function(tag, text) { + tag[options.displayProperty] = text; + }; + + 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 (tagText.length >= options.minLength && options.allowedTagsPattern.test(tagText)) { + + if (options.replaceSpacesWithDashes) { + tagText = tagText.replace(/\s/g, '-'); + } + + setTagText(tag, tagText); + + if (!findInObjectArray(self.items, tag, options.displayProperty)) { + self.items.push(tag); + + events.trigger('tag-added', { $tag: tag }); + } + else { + events.trigger('duplicate-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'] + }); + + $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 duplicate-tag', function() { + scope.newTag = ''; + }) + .on('tag-added tag-removed', function() { + ngModelCtrl.$setViewValue(scope.tags); + }) + .on('input-change', function() { + tagList.selected = null; + }) + .on('input-blur', function() { + if (options.addOnBlur) { + tagList.addText(scope.newTag); + } + }); + + scope.newTag = ''; + + 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); + }; + + 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; + + if (isModifier || hotkeys.indexOf(key) === -1) { + return; + } + + if (key === KEYS.enter && options.addOnEnter || + key === KEYS.comma && options.addOnComma || + key === KEYS.space && options.addOnSpace) { + + tagList.addText(scope.newTag); + + scope.$apply(); + e.preventDefault(); + } + else if (key === KEYS.backspace && scope.newTag.length === 0) { + var tag = tagList.removeLast(); + if (tag && options.enableEditingLastTag) { + scope.newTag = tag[options.displayProperty]; + } + + scope.$apply(); + e.preventDefault(); + } + }) + .on('focus', function() { + if (scope.hasFocus) { + return; + } + scope.hasFocus = true; + 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, '&') + .replace(//g, '>'); + } + + 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, '$1'); + return $sce.trustAsHtml(text); + }; + + scope.track = function(item) { + return getItemText(item); + }; + + tagsInput + .on('tag-added duplicate-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.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 default configuration options for tagsInput and autoComplete directives. It's also used internally to parse and + * initialize options from HTML attributes. + */ +tagsInput.provider('tagsInputConfig', function() { + var globalDefaults = {}; + + /** + * @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; + }; + + 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 interpolatedValue = attrs[key] && $interpolate(attrs[key])(scope.$parent), + converter = converters[value[0]], + getDefault = function(key) { + var globalValue = globalDefaults[directive] && globalDefaults[directive][key]; + return angular.isDefined(globalValue) ? globalValue : value[1]; + }; + + scope.options[key] = interpolatedValue ? converter(interpolatedValue) : getDefault(key); + }); + } + }; + }]; +}); + + +/* HTML templates */ +tagsInput.run(["$templateCache", function($templateCache) { + $templateCache.put('ngTagsInput/tags-input.html', + "
" + ); + + $templateCache.put('ngTagsInput/auto-complete.html', + "
" + ); +}]); + +}()); \ No newline at end of file diff --git a/build/ng-tags-input.min.css b/build/ng-tags-input.min.css new file mode 100644 index 00000000..3beda876 --- /dev/null +++ b/build/ng-tags-input.min.css @@ -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::-ms-clear{display:none}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} \ No newline at end of file diff --git a/build/ng-tags-input.min.js b/build/ng-tags-input.min.js new file mode 100644 index 00000000..ce6e415b --- /dev/null +++ b/build/ng-tags-input.min.js @@ -0,0 +1 @@ +/*! ngTagsInput v2.0.0 License: MIT */!function(){"use strict";function a(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(b,c){return angular.forEach(a[b],function(a){a.call(null,c)}),this}}}function b(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a}function c(a,b,c){for(var d=null,e=0;e=a.minLength&&a.allowedTagsPattern.test(h)&&(a.replaceSpacesWithDashes&&(h=h.replace(/\s/g,"-")),e(g,h),c(f.items,g,a.displayProperty)?b.trigger("duplicate-tag",{$tag:g}):(f.items.push(g),b.trigger("tag-added",{$tag:g}))),g},f.remove=function(a){var c=f.items.splice(a,1)[0];return b.trigger("tag-removed",{$tag:c}),c},f.removeLast=function(){var b,c=f.items.length-1;return a.enableEditingLastTag||f.selected?(f.selected=null,b=f.remove(c)):f.selected||(f.selected=f.items[c]),b},f}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdded:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(b,c,d){g.load("tagsInput",b,c,{placeholder:[String,"Add a tag"],tabindex:[Number],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number],maxTags:[Number],displayProperty:[String,"text"]}),b.events=new a,b.tagList=new h(b.options,b.events),this.registerAutocomplete=function(){var a=d.find("input");return a.on("keydown",function(a){b.events.trigger("input-keydown",a)}),{addTag:function(a){return b.tagList.add(a)},focusInput:function(){a[0].focus()},getTags:function(){return b.tags},getOptions:function(){return b.options},on:function(a,c){return b.events.on(a,c),this}}}}],link:function(a,c,g,h){var i=[d.enter,d.comma,d.space,d.backspace],j=a.tagList,k=a.events,l=a.options,m=c.find("input");k.on("tag-added",a.onTagAdded).on("tag-removed",a.onTagRemoved).on("tag-added duplicate-tag",function(){a.newTag=""}).on("tag-added tag-removed",function(){h.$setViewValue(a.tags)}).on("input-change",function(){j.selected=null}).on("input-blur",function(){l.addOnBlur&&j.addText(a.newTag)}),a.newTag="",a.getDisplayText=function(a){return a[l.displayProperty].trim()},a.track=function(a){return a[l.displayProperty]},a.newTagChange=function(){k.trigger("input-change",a.newTag)},a.$watch("tags",function(c){a.tags=b(c,l.displayProperty),j.items=a.tags}),a.$watch("tags.length",function(a){h.$setValidity("maxTags",angular.isUndefined(l.maxTags)||a<=l.maxTags),h.$setValidity("minTags",angular.isUndefined(l.minTags)||a>=l.minTags)}),m.on("keydown",function(b){if(!b.isImmediatePropagationStopped||!b.isImmediatePropagationStopped()){var c=b.keyCode,e=b.shiftKey||b.altKey||b.ctrlKey||b.metaKey;if(!e&&-1!==i.indexOf(c))if(c===d.enter&&l.addOnEnter||c===d.comma&&l.addOnComma||c===d.space&&l.addOnSpace)j.addText(a.newTag),a.$apply(),b.preventDefault();else if(c===d.backspace&&0===a.newTag.length){var f=j.removeLast();f&&l.enableEditingLastTag&&(a.newTag=f[l.displayProperty]),a.$apply(),b.preventDefault()}}}).on("focus",function(){a.hasFocus||(a.hasFocus=!0,a.$apply())}).on("blur",function(){e(function(){var b=f.prop("activeElement"),d=b===m[0],e=c[0].contains(b);(d||!e)&&(a.hasFocus=!1,k.trigger("input-blur"))})}),c.find("div").on("click",function(){m[0].focus()})}}}]),e.directive("autoComplete",["$document","$timeout","$sce","tagsInputConfig",function(a,e,f,g){function h(a,d){var f,g,h,i={};return g=function(a,b){return a.filter(function(a){return!c(b,a,d.tagsInput.displayProperty)})},i.reset=function(){h=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null,e.cancel(f)},i.show=function(){i.selected=null,i.visible=!0},i.load=function(c,j){return c.length0?i.show():i.reset())})},d.debounceDelay,!1)))},i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.reset(),i}function i(a){return a.replace(/&/g,"&").replace(//g,">")}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",link:function(b,c,e,j){var k,l,m,n,o,p=[d.enter,d.tab,d.escape,d.up,d.down];g.load("autoComplete",b,e,{debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10]}),m=b.options,l=j.registerAutocomplete(),m.tagsInput=l.getOptions(),k=new h(b.source,m),n=function(a){return a[m.tagsInput.displayProperty]},o=m.highlightMatchedText?function(a,b){var c=new RegExp(b,"gi");return a.replace(c,"**$&**")}:function(a){return a},b.suggestionList=k,b.addSuggestion=function(){var a=!1;return k.selected&&(l.addTag(k.selected),k.reset(),l.focusInput(),a=!0),a},b.highlight=function(a){var b=n(a);return b=o(b,k.query),b=i(b),b=b.replace(/\*\*(.+?)\*\*/g,"$1"),f.trustAsHtml(b)},b.track=function(a){return n(a)},l.on("tag-added duplicate-tag",function(){k.reset()}).on("input-change",function(a){a?k.load(a,l.getTags()):k.reset()}).on("input-keydown",function(a){var c,e;if(-1!==p.indexOf(a.keyCode)){var f=!1;a.stopImmediatePropagation=function(){f=!0,a.stopPropagation()},a.isImmediatePropagationStopped=function(){return f},k.visible&&(c=a.keyCode,e=!1,c===d.down?(k.selectNext(),e=!0):c===d.up?(k.selectPrior(),e=!0):c===d.escape?(k.reset(),e=!0):(c===d.enter||c===d.tab)&&(e=b.addSuggestion()),e&&(a.preventDefault(),a.stopImmediatePropagation(),b.$apply()))}}).on("input-blur",function(){k.reset()}),a.on("click",function(){k.visible&&(k.reset(),b.$apply())})}}}]),e.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),e.directive("tiAutosize",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){var e,f,g=3;e=angular.element(''),e.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),b.parent().append(e),f=function(a){var d,f=a;return angular.isString(f)&&0===f.length&&(f=c.placeholder),f&&(e.text(f),e.css("display",""),d=e.prop("offsetWidth"),e.css("display","none")),b.css("width",d?d+g+"px":""),a},d.$parsers.unshift(f),d.$formatters.unshift(f)}}}),e.provider("tagsInputConfig",function(){var a={};this.setDefaults=function(b,c){return a[b]=c,this},this.$get=["$interpolate",function(b){var c={};return c[String]=function(a){return a},c[Number]=function(a){return parseInt(a,10)},c[Boolean]=function(a){return"true"===a.toLowerCase()},c[RegExp]=function(a){return new RegExp(a)},{load:function(d,e,f,g){e.options={},angular.forEach(g,function(g,h){var i=f[h]&&b(f[h])(e.$parent),j=c[g[0]],k=function(b){var c=a[d]&&a[d][b];return angular.isDefined(c)?c:g[1]};e.options[h]=i?j(i):k(h)})}}}]}),e.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
'),a.put("ngTagsInput/auto-complete.html",'
')}])}(); \ No newline at end of file