@_mehrad/ngx-chips
Version:
Tag Input component for Angular
897 lines • 135 kB
JavaScript
// angular
import { Component, forwardRef, HostBinding, Input, Output, EventEmitter, ViewChild, ViewChildren, ContentChildren, ContentChild, TemplateRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
// rx
import { debounceTime, filter, map, first } from 'rxjs';
// ng2-tag-input
import { TagInputAccessor } from '../../core/accessor';
import { listen } from '../../core/helpers/listen';
import * as constants from '../../core/constants';
import { TagInputForm } from '../tag-input-form/tag-input-form.component';
import { TagComponent } from '../tag/tag.component';
import { animations } from './animations';
import { defaults } from '../../defaults';
import { TagInputDropdown } from '../dropdown/tag-input-dropdown.component';
import * as i0 from "@angular/core";
import * as i1 from "../../core/providers/drag-provider";
import * as i2 from "@angular/common";
import * as i3 from "../tag-input-form/tag-input-form.component";
import * as i4 from "../tag/tag.component";
const CUSTOM_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TagInputComponent),
multi: true
};
export class TagInputComponent extends TagInputAccessor {
constructor(renderer, dragProvider) {
super();
this.renderer = renderer;
this.dragProvider = dragProvider;
/**
* @name separatorKeys
* @desc keyboard keys with which a user can separate items
*/
this.separatorKeys = defaults.tagInput.separatorKeys;
/**
* @name separatorKeyCodes
* @desc keyboard key codes with which a user can separate items
*/
this.separatorKeyCodes = defaults.tagInput.separatorKeyCodes;
/**
* @name placeholder
* @desc the placeholder of the input text
*/
this.placeholder = defaults.tagInput.placeholder;
/**
* @name secondaryPlaceholder
* @desc placeholder to appear when the input is empty
*/
this.secondaryPlaceholder = defaults.tagInput.secondaryPlaceholder;
/**
* @name maxItems
* @desc maximum number of items that can be added
*/
this.maxItems = defaults.tagInput.maxItems;
/**
* @name validators
* @desc array of Validators that are used to validate the tag before it gets appended to the list
*/
this.validators = defaults.tagInput.validators;
/**
* @name asyncValidators
* @desc array of AsyncValidator that are used to validate the tag before it gets appended to the list
*/
this.asyncValidators = defaults.tagInput.asyncValidators;
/**
* - if set to true, it will only possible to add items from the autocomplete
* @name onlyFromAutocomplete
*/
this.onlyFromAutocomplete = defaults.tagInput.onlyFromAutocomplete;
/**
* @name errorMessages
*/
this.errorMessages = defaults.tagInput.errorMessages;
/**
* @name theme
*/
this.theme = defaults.tagInput.theme;
/**
* @name onTextChangeDebounce
*/
this.onTextChangeDebounce = defaults.tagInput.onTextChangeDebounce;
/**
* - custom id assigned to the input
* @name id
*/
this.inputId = defaults.tagInput.inputId;
/**
* - custom class assigned to the input
*/
this.inputClass = defaults.tagInput.inputClass;
/**
* - option to clear text input when the form is blurred
* @name clearOnBlur
*/
this.clearOnBlur = defaults.tagInput.clearOnBlur;
/**
* - hideForm
* @name clearOnBlur
*/
this.hideForm = defaults.tagInput.hideForm;
/**
* @name addOnBlur
*/
this.addOnBlur = defaults.tagInput.addOnBlur;
/**
* @name addOnPaste
*/
this.addOnPaste = defaults.tagInput.addOnPaste;
/**
* - pattern used with the native method split() to separate patterns in the string pasted
* @name pasteSplitPattern
*/
this.pasteSplitPattern = defaults.tagInput.pasteSplitPattern;
/**
* @name blinkIfDupe
*/
this.blinkIfDupe = defaults.tagInput.blinkIfDupe;
/**
* @name removable
*/
this.removable = defaults.tagInput.removable;
/**
* @name editable
*/
this.editable = defaults.tagInput.editable;
/**
* @name allowDupes
*/
this.allowDupes = defaults.tagInput.allowDupes;
/**
* @description if set to true, the newly added tags will be added as strings, and not objects
* @name modelAsStrings
*/
this.modelAsStrings = defaults.tagInput.modelAsStrings;
/**
* @name trimTags
*/
this.trimTags = defaults.tagInput.trimTags;
/**
* @name ripple
*/
this.ripple = defaults.tagInput.ripple;
/**
* @name tabindex
* @desc pass through the specified tabindex to the input
*/
this.tabindex = defaults.tagInput.tabIndex;
/**
* @name disable
*/
this.disable = defaults.tagInput.disable;
/**
* @name dragZone
*/
this.dragZone = defaults.tagInput.dragZone;
/**
* @name onRemoving
*/
this.onRemoving = defaults.tagInput.onRemoving;
/**
* @name onAdding
*/
this.onAdding = defaults.tagInput.onAdding;
/**
* @name animationDuration
*/
this.animationDuration = defaults.tagInput.animationDuration;
/**
* @name onAdd
* @desc event emitted when adding a new item
*/
this.onAdd = new EventEmitter();
/**
* @name onRemove
* @desc event emitted when removing an existing item
*/
this.onRemove = new EventEmitter();
/**
* @name onSelect
* @desc event emitted when selecting an item
*/
this.onSelect = new EventEmitter();
/**
* @name onFocus
* @desc event emitted when the input is focused
*/
this.onFocus = new EventEmitter();
/**
* @name onFocus
* @desc event emitted when the input is blurred
*/
this.onBlur = new EventEmitter();
/**
* @name onTextChange
* @desc event emitted when the input value changes
*/
this.onTextChange = new EventEmitter();
/**
* - output triggered when text is pasted in the form
* @name onPaste
*/
this.onPaste = new EventEmitter();
/**
* - output triggered when tag entered is not valid
* @name onValidationError
*/
this.onValidationError = new EventEmitter();
/**
* - output triggered when tag is edited
* @name onTagEdited
*/
this.onTagEdited = new EventEmitter();
/**
* @name isLoading
*/
this.isLoading = false;
/**
* @name listeners
* @desc array of events that get fired using @fireEvents
*/
this.listeners = {
[constants.KEYDOWN]: [],
[constants.KEYUP]: []
};
/**
* @description emitter for the 2-way data binding inputText value
* @name inputTextChange
*/
this.inputTextChange = new EventEmitter();
/**
* @description private variable to bind get/set
* @name inputTextValue
*/
this.inputTextValue = '';
this.errors = [];
/**
* @name appendTag
* @param tag {TagModel}
*/
this.appendTag = (tag, index = this.items.length) => {
const items = this.items;
const model = this.modelAsStrings ? tag[this.identifyBy] : tag;
this.items = [
...items.slice(0, index),
model,
...items.slice(index, items.length)
];
};
/**
* @name createTag
* @param model
*/
this.createTag = (model) => {
const trim = (val, key) => {
return typeof val === 'string' ? val.trim() : val[key];
};
return {
...typeof model !== 'string' ? model : {},
[this.displayBy]: this.trimTags ? trim(model, this.displayBy) : model,
[this.identifyBy]: this.trimTags ? trim(model, this.identifyBy) : model
};
};
/**
*
* @param tag
* @param isFromAutocomplete
*/
this.isTagValid = (tag, fromAutocomplete = false) => {
const selectedItem = this.dropdown ? this.dropdown.selectedItem : undefined;
const value = this.getItemDisplay(tag).trim();
if (selectedItem && !fromAutocomplete || !value) {
return false;
}
const dupe = this.findDupe(tag, fromAutocomplete);
// if so, give a visual cue and return false
if (!this.allowDupes && dupe && this.blinkIfDupe) {
const model = this.tags.find(item => {
return this.getItemValue(item.model) === this.getItemValue(dupe);
});
if (model) {
model.blink();
}
}
const isFromAutocomplete = fromAutocomplete && this.onlyFromAutocomplete;
const assertions = [
// 1. there must be no dupe OR dupes are allowed
!dupe || this.allowDupes,
// 2. check max items has not been reached
!this.maxItemsReached,
// 3. check item comes from autocomplete or onlyFromAutocomplete is false
((isFromAutocomplete) || !this.onlyFromAutocomplete)
];
return assertions.filter(Boolean).length === assertions.length;
};
/**
* @name onPasteCallback
* @param data
*/
this.onPasteCallback = async (data) => {
const getText = () => {
const isIE = Boolean(window.clipboardData);
const clipboardData = isIE ? (window.clipboardData) : data.clipboardData;
const type = isIE ? 'Text' : 'text/plain';
return clipboardData === null ? '' : clipboardData.getData(type) || '';
};
const text = getText();
const requests = text
.split(this.pasteSplitPattern)
.map(item => {
const tag = this.createTag(item);
this.setInputValue(tag[this.displayBy]);
return this.onAddingRequested(false, tag);
});
const resetInput = () => setTimeout(() => this.setInputValue(''), 50);
Promise.all(requests).then(() => {
this.onPaste.emit(text);
resetInput();
})
.catch(resetInput);
};
}
/**
* @name inputText
*/
get inputText() {
return this.inputTextValue;
}
/**
* @name inputText
* @param text
*/
set inputText(text) {
this.inputTextValue = text;
this.inputTextChange.emit(text);
}
/**
* @desc removes the tab index if it is set - it will be passed through to the input
* @name tabindexAttr
*/
get tabindexAttr() {
return this.tabindex !== '' ? '-1' : '';
}
/**
* @name ngAfterViewInit
*/
ngAfterViewInit() {
// set up listeners
this.setUpKeypressListeners();
this.setupSeparatorKeysListener();
this.setUpInputKeydownListeners();
if (this.onTextChange.observers.length) {
this.setUpTextChangeSubscriber();
}
// if clear on blur is set to true, subscribe to the event and clear the text's form
if (this.clearOnBlur || this.addOnBlur) {
this.setUpOnBlurSubscriber();
}
// if addOnPaste is set to true, register the handler and add items
if (this.addOnPaste) {
this.setUpOnPasteListener();
}
const statusChanges$ = this.inputForm.form.statusChanges;
statusChanges$.pipe(filter((status) => status !== 'PENDING')).subscribe(() => {
this.errors = this.inputForm.getErrorMessages(this.errorMessages);
});
this.isProgressBarVisible$ = statusChanges$.pipe(map((status) => {
return status === 'PENDING' || this.isLoading;
}));
// if hideForm is set to true, remove the input
if (this.hideForm) {
this.inputForm.destroy();
}
}
/**
* @name ngOnInit
*/
ngOnInit() {
// if the number of items specified in the model is > of the value of maxItems
// degrade gracefully and let the max number of items to be the number of items in the model
// though, warn the user.
const hasReachedMaxItems = this.maxItems !== undefined &&
this.items &&
this.items.length > this.maxItems;
if (hasReachedMaxItems) {
this.maxItems = this.items.length;
console.warn(constants.MAX_ITEMS_WARNING);
}
// Setting editable to false to fix problem with tags in IE still being editable when
// onlyFromAutocomplete is true
this.editable = this.onlyFromAutocomplete ? false : this.editable;
this.setAnimationMetadata();
}
/**
* @name onRemoveRequested
* @param tag
* @param index
*/
onRemoveRequested(tag, index) {
return new Promise(resolve => {
const subscribeFn = (model) => {
this.removeItem(model, index);
resolve(tag);
};
this.onRemoving ?
this.onRemoving(tag)
.pipe(first())
.subscribe(subscribeFn) : subscribeFn(tag);
});
}
/**
* @name onAddingRequested
* @param fromAutocomplete {boolean}
* @param tag {TagModel}
* @param index? {number}
* @param giveupFocus? {boolean}
*/
onAddingRequested(fromAutocomplete, tag, index, giveupFocus) {
return new Promise((resolve, reject) => {
const subscribeFn = (model) => {
return this
.addItem(fromAutocomplete, model, index, giveupFocus)
.then(resolve)
.catch(reject);
};
return this.onAdding ?
this.onAdding(tag)
.pipe(first())
.subscribe(subscribeFn, reject) : subscribeFn(tag);
});
}
/**
* @name selectItem
* @desc selects item passed as parameter as the selected tag
* @param item
* @param emit
*/
selectItem(item, emit = true) {
const isReadonly = item && typeof item !== 'string' && item.readonly;
if (isReadonly || this.selectedTag === item) {
return;
}
this.selectedTag = item;
if (emit) {
this.onSelect.emit(item);
}
}
/**
* @name fireEvents
* @desc goes through the list of the events for a given eventName, and fires each of them
* @param eventName
* @param $event
*/
fireEvents(eventName, $event) {
this.listeners[eventName].forEach(listener => listener.call(this, $event));
}
/**
* @name handleKeydown
* @desc handles action when the user hits a keyboard key
* @param data
*/
handleKeydown(data) {
const event = data.event;
const key = event.keyCode || event.which;
const shiftKey = event.shiftKey || false;
switch (constants.KEY_PRESS_ACTIONS[key]) {
case constants.ACTIONS_KEYS.DELETE:
if (this.selectedTag && this.removable) {
const index = this.items.indexOf(this.selectedTag);
this.onRemoveRequested(this.selectedTag, index);
}
break;
case constants.ACTIONS_KEYS.SWITCH_PREV:
this.moveToTag(data.model, constants.PREV);
break;
case constants.ACTIONS_KEYS.SWITCH_NEXT:
this.moveToTag(data.model, constants.NEXT);
break;
case constants.ACTIONS_KEYS.TAB:
if (shiftKey) {
if (this.isFirstTag(data.model)) {
return;
}
this.moveToTag(data.model, constants.PREV);
}
else {
if (this.isLastTag(data.model) && (this.disable || this.maxItemsReached)) {
return;
}
this.moveToTag(data.model, constants.NEXT);
}
break;
default:
return;
}
// prevent default behaviour
event.preventDefault();
}
async onFormSubmit() {
try {
await this.onAddingRequested(false, this.formValue);
}
catch {
return;
}
}
/**
* @name setInputValue
* @param value
*/
setInputValue(value, emitEvent = true) {
const control = this.getControl();
// update form value with the transformed item
control.setValue(value, { emitEvent });
}
/**
* @name getControl
*/
getControl() {
return this.inputForm.value;
}
/**
* @name focus
* @param applyFocus
* @param displayAutocomplete
*/
focus(applyFocus = false, displayAutocomplete = false) {
if (this.dragProvider.getState('dragging')) {
return;
}
this.selectItem(undefined, false);
if (applyFocus) {
this.inputForm.focus();
this.onFocus.emit(this.formValue);
}
}
/**
* @name blur
*/
blur() {
this.onTouched();
this.onBlur.emit(this.formValue);
}
/**
* @name hasErrors
*/
hasErrors() {
return !!this.inputForm && this.inputForm.hasErrors();
}
/**
* @name isInputFocused
*/
isInputFocused() {
return !!this.inputForm && this.inputForm.isInputFocused();
}
/**
* - this is the one way I found to tell if the template has been passed and it is not
* the template for the menu item
* @name hasCustomTemplate
*/
hasCustomTemplate() {
const template = this.templates ? this.templates.first : undefined;
const menuTemplate = this.dropdown && this.dropdown.templates ?
this.dropdown.templates.first : undefined;
return Boolean(template && template !== menuTemplate);
}
/**
* @name maxItemsReached
*/
get maxItemsReached() {
return this.maxItems !== undefined &&
this.items.length >= this.maxItems;
}
/**
* @name formValue
*/
get formValue() {
const form = this.inputForm.value;
return form ? form.value : '';
}
/**3
* @name onDragStarted
* @param event
* @param index
*/
onDragStarted(event, tag, index) {
event.stopPropagation();
const item = { zone: this.dragZone, tag, index };
this.dragProvider.setSender(this);
this.dragProvider.setDraggedItem(event, item);
this.dragProvider.setState({ dragging: true, index });
}
/**
* @name onDragOver
* @param event
*/
onDragOver(event, index) {
this.dragProvider.setState({ dropping: true });
this.dragProvider.setReceiver(this);
event.preventDefault();
}
/**
* @name onTagDropped
* @param event
* @param index
*/
onTagDropped(event, index) {
const item = this.dragProvider.getDraggedItem(event);
if (!item || item.zone !== this.dragZone) {
return;
}
this.dragProvider.onTagDropped(item.tag, item.index, index);
event.preventDefault();
event.stopPropagation();
}
/**
* @name isDropping
*/
isDropping() {
const isReceiver = this.dragProvider.receiver === this;
const isDropping = this.dragProvider.getState('dropping');
return Boolean(isReceiver && isDropping);
}
/**
* @name onTagBlurred
* @param changedElement {TagModel}
* @param index {number}
*/
onTagBlurred(changedElement, index) {
this.items[index] = changedElement;
this.blur();
}
/**
* @name trackBy
* @param items
*/
trackBy(index, item) {
return item[this.identifyBy];
}
/**
* @name updateEditedTag
* @param tag
*/
updateEditedTag(tag) {
this.onTagEdited.emit(tag);
}
/**
* @name moveToTag
* @param item
* @param direction
*/
moveToTag(item, direction) {
const isLast = this.isLastTag(item);
const isFirst = this.isFirstTag(item);
const stopSwitch = (direction === constants.NEXT && isLast) ||
(direction === constants.PREV && isFirst);
if (stopSwitch) {
this.focus(true);
return;
}
const offset = direction === constants.NEXT ? 1 : -1;
const index = this.getTagIndex(item) + offset;
const tag = this.getTagAtIndex(index);
return tag.select.call(tag);
}
/**
* @name isFirstTag
* @param item {TagModel}
*/
isFirstTag(item) {
return this.tags.first.model === item;
}
/**
* @name isLastTag
* @param item {TagModel}
*/
isLastTag(item) {
return this.tags.last.model === item;
}
/**
* @name getTagIndex
* @param item
*/
getTagIndex(item) {
const tags = this.tags.toArray();
return tags.findIndex(tag => tag.model === item);
}
/**
* @name getTagAtIndex
* @param index
*/
getTagAtIndex(index) {
const tags = this.tags.toArray();
return tags[index];
}
/**
* @name removeItem
* @desc removes an item from the array of the model
* @param tag {TagModel}
* @param index {number}
*/
removeItem(tag, index) {
this.items = this.getItemsWithout(index);
// if the removed tag was selected, set it as undefined
if (this.selectedTag === tag) {
this.selectItem(undefined, false);
}
// focus input
this.focus(true, false);
// emit remove event
this.onRemove.emit(tag);
}
/**
* @name addItem
* @desc adds the current text model to the items array
* @param fromAutocomplete {boolean}
* @param item {TagModel}
* @param index? {number}
* @param giveupFocus? {boolean}
*/
addItem(fromAutocomplete = false, item, index, giveupFocus) {
const display = this.getItemDisplay(item);
const tag = this.createTag(item);
if (fromAutocomplete) {
this.setInputValue(this.getItemValue(item, true));
}
return new Promise((resolve, reject) => {
/**
* @name reset
*/
const reset = () => {
// reset control and focus input
this.setInputValue('');
if (giveupFocus) {
this.focus(false, false);
}
else {
// focus input
this.focus(true, false);
}
resolve(display);
};
const appendItem = () => {
this.appendTag(tag, index);
// emit event
this.onAdd.emit(tag);
if (!this.dropdown) {
return;
}
this.dropdown.hide();
if (this.dropdown.showDropdownIfEmpty) {
this.dropdown.show();
}
};
const status = this.inputForm.form.status;
const isTagValid = this.isTagValid(tag, fromAutocomplete);
const onValidationError = () => {
this.onValidationError.emit(tag);
return reject();
};
if (status === 'VALID' && isTagValid) {
appendItem();
return reset();
}
if (status === 'INVALID' || !isTagValid) {
reset();
return onValidationError();
}
if (status === 'PENDING') {
const statusUpdate$ = this.inputForm.form.statusChanges;
return statusUpdate$
.pipe(filter(statusUpdate => statusUpdate !== 'PENDING'), first())
.subscribe((statusUpdate) => {
if (statusUpdate === 'VALID' && isTagValid) {
appendItem();
return reset();
}
else {
reset();
return onValidationError();
}
});
}
});
}
/**
* @name setupSeparatorKeysListener
*/
setupSeparatorKeysListener() {
const useSeparatorKeys = this.separatorKeyCodes.length > 0 || this.separatorKeys.length > 0;
const listener = ($event) => {
const hasKeyCode = this.separatorKeyCodes.indexOf($event.keyCode) >= 0;
const hasKey = this.separatorKeys.indexOf($event.key) >= 0;
// the keyCode of keydown event is 229 when IME is processing the key event.
const isIMEProcessing = $event.keyCode === 229;
if (hasKeyCode || (hasKey && !isIMEProcessing)) {
$event.preventDefault();
this.onAddingRequested(false, this.formValue)
.catch(() => { });
}
};
listen.call(this, constants.KEYDOWN, listener, useSeparatorKeys);
}
/**
* @name setUpKeypressListeners
*/
setUpKeypressListeners() {
const listener = ($event) => {
const isCorrectKey = $event.keyCode === 37 || $event.keyCode === 8;
if (isCorrectKey &&
!this.formValue &&
this.items.length) {
this.tags.last.select.call(this.tags.last);
}
};
// setting up the keypress listeners
listen.call(this, constants.KEYDOWN, listener);
}
/**
* @name setUpKeydownListeners
*/
setUpInputKeydownListeners() {
this.inputForm.onKeydown.subscribe(event => {
if (event.key === 'Backspace' && this.formValue.trim() === '') {
event.preventDefault();
}
});
}
/**
* @name setUpOnPasteListener
*/
setUpOnPasteListener() {
const input = this.inputForm.input.nativeElement;
// attach listener to input
this.renderer.listen(input, 'paste', (event) => {
this.onPasteCallback(event);
event.preventDefault();
return true;
});
}
/**
* @name setUpTextChangeSubscriber
*/
setUpTextChangeSubscriber() {
this.inputForm.form
.valueChanges
.pipe(debounceTime(this.onTextChangeDebounce))
.subscribe((value) => {
this.onTextChange.emit(value.item);
});
}
/**
* @name setUpOnBlurSubscriber
*/
setUpOnBlurSubscriber() {
const filterFn = () => {
const isVisible = this.dropdown && this.dropdown.isVisible;
return !isVisible && !!this.formValue;
};
this.inputForm
.onBlur
.pipe(debounceTime(100), filter(filterFn))
.subscribe(() => {
const reset = () => this.setInputValue('');
if (this.addOnBlur) {
return this
.onAddingRequested(false, this.formValue, undefined, true)
.then(reset)
.catch(reset);
}
reset();
});
}
/**
* @name findDupe
* @param tag
* @param isFromAutocomplete
*/
findDupe(tag, isFromAutocomplete) {
const identifyBy = isFromAutocomplete ? this.dropdown.identifyBy : this.identifyBy;
const id = tag[identifyBy];
return this.items.find(item => this.getItemValue(item) === id);
}
/**
* @name setAnimationMetadata
*/
setAnimationMetadata() {
this.animationMetadata = {
value: 'in',
params: { ...this.animationDuration }
};
}
}
TagInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TagInputComponent, deps: [{ token: i0.Renderer2 }, { token: i1.DragProvider }], target: i0.ɵɵFactoryTarget.Component });
TagInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: TagInputComponent, selector: "tag-input", inputs: { separatorKeys: "separatorKeys", separatorKeyCodes: "separatorKeyCodes", placeholder: "placeholder", secondaryPlaceholder: "secondaryPlaceholder", maxItems: "maxItems", validators: "validators", asyncValidators: "asyncValidators", onlyFromAutocomplete: "onlyFromAutocomplete", errorMessages: "errorMessages", theme: "theme", onTextChangeDebounce: "onTextChangeDebounce", inputId: "inputId", inputClass: "inputClass", clearOnBlur: "clearOnBlur", hideForm: "hideForm", addOnBlur: "addOnBlur", addOnPaste: "addOnPaste", pasteSplitPattern: "pasteSplitPattern", blinkIfDupe: "blinkIfDupe", removable: "removable", editable: "editable", allowDupes: "allowDupes", modelAsStrings: "modelAsStrings", trimTags: "trimTags", inputText: "inputText", ripple: "ripple", tabindex: "tabindex", disable: "disable", dragZone: "dragZone", onRemoving: "onRemoving", onAdding: "onAdding", animationDuration: "animationDuration" }, outputs: { onAdd: "onAdd", onRemove: "onRemove", onSelect: "onSelect", onFocus: "onFocus", onBlur: "onBlur", onTextChange: "onTextChange", onPaste: "onPaste", onValidationError: "onValidationError", onTagEdited: "onTagEdited", inputTextChange: "inputTextChange" }, host: { properties: { "attr.tabindex": "this.tabindexAttr" } }, providers: [CUSTOM_ACCESSOR], queries: [{ propertyName: "dropdown", first: true, predicate: TagInputDropdown, descendants: true }, { propertyName: "templates", predicate: TemplateRef }], viewQueries: [{ propertyName: "inputForm", first: true, predicate: TagInputForm, descendants: true }, { propertyName: "tags", predicate: TagComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div\n [ngClass]=\"theme\"\n class=\"ng2-tag-input\"\n (click)=\"focus(true, false)\"\n [attr.tabindex]=\"-1\"\n (drop)=\"dragZone ? onTagDropped($event, undefined) : undefined\"\n (dragenter)=\"dragZone ? onDragOver($event) : undefined\"\n (dragover)=\"dragZone ? onDragOver($event) : undefined\"\n (dragend)=\"dragZone ? dragProvider.onDragEnd() : undefined\"\n [class.ng2-tag-input--dropping]=\"isDropping()\"\n [class.ng2-tag-input--disabled]=\"disable\"\n [class.ng2-tag-input--loading]=\"isLoading\"\n [class.ng2-tag-input--invalid]=\"hasErrors()\"\n [class.ng2-tag-input--focused]=\"isInputFocused()\"\n>\n\n <!-- TAGS -->\n <div class=\"ng2-tags-container\">\n <tag\n *ngFor=\"let item of items; let i = index; trackBy: trackBy\"\n (onSelect)=\"selectItem(item)\"\n (onRemove)=\"onRemoveRequested(item, i)\"\n (onKeyDown)=\"handleKeydown($event)\"\n (onTagEdited)=\"updateEditedTag($event)\"\n (onBlur)=\"onTagBlurred($event, i)\"\n draggable=\"{{ editable }}\"\n (dragstart)=\"dragZone ? onDragStarted($event, item, i) : undefined\"\n (drop)=\"dragZone ? onTagDropped($event, i) : undefined\"\n (dragenter)=\"dragZone ? onDragOver($event) : undefined\"\n (dragover)=\"dragZone ? onDragOver($event, i) : undefined\"\n (dragleave)=\"dragZone ? dragProvider.onDragEnd() : undefined\"\n [canAddTag]=\"isTagValid\"\n [attr.tabindex]=\"0\"\n [disabled]=\"disable\"\n [@animation]=\"animationMetadata\"\n [hasRipple]=\"ripple\"\n [index]=\"i\"\n [removable]=\"removable\"\n [editable]=\"editable\"\n [displayBy]=\"displayBy\"\n [identifyBy]=\"identifyBy\"\n [template]=\"!!hasCustomTemplate() ? templates.first : undefined\"\n [draggable]=\"dragZone\"\n [model]=\"item\"\n >\n </tag>\n\n <tag-input-form\n (onSubmit)=\"onFormSubmit()\"\n (onBlur)=\"blur()\"\n (click)=\"dropdown ? dropdown.show() : undefined\"\n (onKeydown)=\"fireEvents('keydown', $event)\"\n (onKeyup)=\"fireEvents('keyup', $event)\"\n [inputText]=\"inputText\"\n [disabled]=\"disable\"\n [validators]=\"validators\"\n [asyncValidators]=\"asyncValidators\"\n [hidden]=\"maxItemsReached\"\n [placeholder]=\"items.length ? placeholder : secondaryPlaceholder\"\n [inputClass]=\"inputClass\"\n [inputId]=\"inputId\"\n [tabindex]=\"tabindex\"\n >\n </tag-input-form>\n </div>\n\n <div\n class=\"progress-bar\"\n *ngIf=\"isProgressBarVisible$ | async\"\n ></div>\n</div>\n\n<!-- ERRORS -->\n<div\n *ngIf=\"hasErrors()\"\n [ngClass]=\"theme\"\n class=\"error-messages\"\n>\n <p\n *ngFor=\"let error of errors\"\n class=\"error-message\"\n >\n <span>{{ error }}</span>\n </p>\n</div>\n<ng-content></ng-content>\n", styles: [".dark tag:focus{box-shadow:0 0 0 1px #323232}.ng2-tag-input.bootstrap3-info{background-color:#fff;display:inline-block;color:#555;vertical-align:middle;max-width:100%;height:42px;line-height:44px}.ng2-tag-input.bootstrap3-info input{border:none;box-shadow:none;outline:none;background-color:transparent;padding:0 6px;margin:0;width:auto;max-width:inherit}.ng2-tag-input.bootstrap3-info .form-control input::-moz-placeholder{color:#777;opacity:1}.ng2-tag-input.bootstrap3-info .form-control input:-ms-input-placeholder{color:#777}.ng2-tag-input.bootstrap3-info .form-control input::-webkit-input-placeholder{color:#777}.ng2-tag-input.bootstrap3-info input:focus{border:none;box-shadow:none}.bootstrap3-info.ng2-tag-input.ng2-tag-input--focused{box-shadow:inset 0 1px 1px #0006;border:1px solid #ccc}.bootstrap3-info.ng2-tag-input.ng2-tag-input--invalid{box-shadow:inset 0 1px 1px #d9534f}.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;transition:all .25s;padding:.25rem 0;min-height:32px;cursor:text;border-bottom:2px solid #efefef}.ng2-tag-input:focus{outline:0}.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.ng2-tag-input.ng2-tag-input--focused{border-bottom:2px solid #2196F3}.ng2-tag-input.ng2-tag-input--invalid{border-bottom:2px solid #f44336}.ng2-tag-input.ng2-tag-input--loading{border:none}.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.ng2-tag-input form{margin:.1em 0}.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.minimal.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:1px solid transparent}.minimal.ng2-tag-input:focus{outline:0}.minimal.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.minimal.ng2-tag-input.ng2-tag-input--loading{border:none}.minimal.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.minimal.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.dark.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:2px solid #444}.dark.ng2-tag-input:focus{outline:0}.dark.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.dark.ng2-tag-input.ng2-tag-input--loading{border:none}.dark.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.dark.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.bootstrap.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:2px solid #efefef}.bootstrap.ng2-tag-input:focus{outline:0}.bootstrap.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.bootstrap.ng2-tag-input.ng2-tag-input--focused{border-bottom:2px solid #0275d8}.bootstrap.ng2-tag-input.ng2-tag-input--invalid{border-bottom:2px solid #d9534f}.bootstrap.ng2-tag-input.ng2-tag-input--loading{border:none}.bootstrap.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.bootstrap.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.bootstrap3-info.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;padding:4px;cursor:text;box-shadow:inset 0 1px 1px #00000013;border-radius:4px}.bootstrap3-info.ng2-tag-input:focus{outline:0}.bootstrap3-info.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.bootstrap3-info.ng2-tag-input.ng2-tag-input--invalid{border-bottom:1px solid #d9534f}.bootstrap3-info.ng2-tag-input.ng2-tag-input--loading{border:none}.bootstrap3-info.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.bootstrap3-info.ng2-tag-input form{margin:.1em 0}.bootstrap3-info.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.error-message{font-size:.8em;color:#f44336;margin:.5em 0 0}.bootstrap .error-message{color:#d9534f}.progress-bar,.progress-bar:before{height:2px;width:100%;margin:0}.progress-bar{background-color:#2196f3;display:flex;position:absolute;bottom:0}.progress-bar:before{background-color:#82c4f8;content:\"\";animation:running-progress 2s cubic-bezier(.4,0,.2,1) infinite}@keyframes running-progress{0%{margin-left:0;margin-right:100%}50%{margin-left:25%;margin-right:0}to{margin-left:100%;margin-right:0}}tag{display:flex;flex-direction:row;flex-wrap:wrap;font-family:Roboto,Helvetica Neue,sans-serif;font-weight:400;font-size:1em;letter-spacing:.05rem;color:#444;border-radius:16px;transition:all .3s;margin:.1rem .3rem .1rem 0;padding:.08rem .45rem;height:32px;line-height:34px;background:#efefef;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;outline:0;cursor:pointer;position:relative}tag:not(.readonly):not(.tag--editing):focus{background:#2196F3;color:#fff;box-shadow:0 2px 3px 1px #d4d1d1}tag:not(.readonly):not(.tag--editing):active{background:#0d8aee;color:#fff;box-shadow:0 2px 3px 1px #d4d1d1}tag:not(:focus):not(.tag--editing):not(:active):not(.readonly):hover{background:#e2e2e2;color:initial;box-shadow:0 2px 3px 1px #d4d1d1}tag.readonly{cursor:default}tag.readonly:focus,tag:focus{outline:0}tag.tag--editing{background-color:#fff;border:1px solid #ccc;cursor:text}.minimal tag{display:flex;flex-direction:row;flex-wrap:wrap;border-radius:0;background:#f9f9f9;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;outline:0;cursor:pointer;position:relative}.minimal tag:not(.readonly):not(.tag--editing):focus{background:#d0d0d0;color:initial}.minimal tag:not(.readonly):not(.tag--editing):active{background:#d0d0d0;color:initial}.minimal tag:not(:focus):not(.tag--editing):not(:active):not(.readonly):hover{background:#ececec}.minimal tag.readonly{cursor:default}.minimal tag.readonly:focus,.minimal tag:focus{outline:0}.minimal tag.tag--editing{cursor:text}.dark tag{display:flex;flex-direction:row;flex-wrap:wrap;color:#f9f9f9;border-radius:3px;background:#444;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;outline:0;cursor:pointer;position:relative}.dark tag:not(.readonly):not(.tag--editing):focus{background:#efefef;color:#444}.dark tag:not(:focus):not(.tag--editing):not(:active):not(.readonly):hover{background:#2b2b2b;color:#f9f9f9}.dark tag.readonly{cursor:default}.dark tag.readonly:focus,.dark tag:focus{outline:0}.dark tag.tag--editing{cursor:text}.bootstrap tag{display:flex;flex-direction:row;flex-wrap:wrap;color:#f9f9f9;border-radius:.25rem;background:#0275d8;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;outline:0;cursor:pointer;position:relative}.bootstrap tag:not(.readonly):not(.tag--editing):focus{background:#025aa5}.bootstrap tag:not(.readonly):not(.tag--editing):active{background:#025aa5}.bootstrap tag:not(:focus):not(.tag--editing):not(:active):not(.readonly):hover{background:#0267bf;color:#f9f9f9}.bootstrap tag.readonly{cursor:default}.bootstrap tag.readonly:focus,.bootstrap tag:focus{outline:0}.bootstrap tag.tag--editing{cursor:text}.bootstrap3-info tag{display:flex;flex-direction:row;flex-wrap:wrap;font-family:inherit;font-weight:400;font-size:95%;color:#fff;border-radius:.25em;background:#5bc0de;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;outline:0;cursor:pointer;position:relative;padding:.25em .6em;text-align:center;white-space:nowrap}.bootstrap3-info tag:not(.readonly):not(.tag--editing):focus{background:#28a1c5}.bootstrap3-info tag:not(.readonly):not(.tag--editing):active{background:#28a1c5}.bootstrap3-info tag:not(:focus):not(.tag--editing):not(:active):not(.readonly):hover{background:#46b8da;color:#fff}.bootstrap3-info tag.readonly{cursor:default}.bootstrap3-info tag.readonly:focus,.bootstrap3-info tag:focus{outline:0}.bootstrap3-info tag.tag--editing{cursor:text}:host{display:block}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.TagInputForm, selector: "tag-input-form", inputs: ["placeholder", "validators", "asyncValidators", "inputId", "inputClass", "tabindex", "disabled", "inputText"], outputs: ["onSubmit", "onBlur", "onFocus", "onKeyup", "onKeydown", "inputTextChange"] }, { kind: "component", type: i4.TagComponent, selector: "tag", inputs: ["model", "removable", "editable", "template", "displayBy", "identifyBy", "index", "hasRipple", "disabled", "canAddTag"], outputs: ["onSelect", "onRemove", "onBlur", "onKeyDown", "onTagEdited"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], animations: animations });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TagInputComponent, decorators: [{
type: Component,
args: [{ selector: 'tag-input', providers: [CUSTOM_ACCESSOR], animations: animations, template: "<div\n [ngClass]=\"theme\"\n class=\"ng2-tag-input\"\n (click)=\"focus(true, false)\"\n [attr.tabindex]=\"-1\"\n (drop)=\"dragZone ? onTagDropped($event, undefined) : undefined\"\n (dragenter)=\"dragZone ? onDragOver($event) : undefined\"\n (dragover)=\"dragZone ? onDragOver($event) : undefined\"\n (dragend)=\"dragZone ? dragProvider.onDragEnd() : undefined\"\n [class.ng2-tag-input--dropping]=\"isDropping()\"\n [class.ng2-tag-input--disabled]=\"disable\"\n [class.ng2-tag-input--loading]=\"isLoading\"\n [class.ng2-tag-input--invalid]=\"hasErrors()\"\n [class.ng2-tag-input--focused]=\"isInputFocused()\"\n>\n\n <!-- TAGS -->\n <div class=\"ng2-tags-container\">\n <tag\n *ngFor=\"let item of items; let i = index; trackBy: trackBy\"\n (onSelect)=\"selectItem(item)\"\n (onRemove)=\"onRemoveRequested(item, i)\"\n (onKeyDown)=\"handleKeydown($event)\"\n (onTagEdited)=\"updateEditedTag($event)\"\n (onBlur)=\"onTagBlurred($event, i)\"\n draggable=\"{{ editable }}\"\n (dragstart)=\"dragZone ? onDragStarted($event, item, i) : undefined\"\n (drop)=\"dragZone ? onTagDropped($event, i) : undefined\"\n (dragenter)=\"dragZone ? onDragOver($event) : undefined\"\n (dragover)=\"dragZone ? onDragOver($event, i) : undefined\"\n (dragleave)=\"dragZone ? dragProvider.onDragEnd() : undefined\"\n [canAddTag]=\"isTagValid\"\n [attr.tabindex]=\"0\"\n [disabled]=\"disable\"\n [@animation]=\"animationMetadata\"\n [hasRipple]=\"ripple\"\n [index]=\"i\"\n [removable]=\"removable\"\n [editable]=\"editable\"\n [displayBy]=\"displayBy\"\n [identifyBy]=\"identifyBy\"\n [template]=\"!!hasCustomTemplate() ? templates.first : undefined\"\n [draggable]=\"dragZone\"\n [model]=\"item\"\n >\n </tag>\n\n <tag-input-form\n (onSubmit)=\"onFormSubmit()\"\n (onBlur)=\"blur()\"\n (click)=\"dropdown ? dropdown.show() : undefined\"\n (onKeydown)=\"fireEvents('keydown', $event)\"\n (onKeyup)=\"fireEvents('keyup', $event)\"\n [inputText]=\"inputText\"\n [disabled]=\"disable\"\n [validators]=\"validators\"\n [asyncValidators]=\"asyncValidators\"\n [hidden]=\"maxItemsReached\"\n [placeholder]=\"items.length ? placeholder : secondaryPlaceholder\"\n [inputClass]=\"inputClass\"\n [inputId]=\"inputId\"\n [tabindex]=\"tabindex\"\n >\n </tag-input-form>\n </div>\n\n <div\n class=\"progress-bar\"\n *ngIf=\"isProgressBarVisible$ | async\"\n ></div>\n</div>\n\n<!-- ERRORS -->\n<div\n *ngIf=\"hasErrors()\"\n [ngClass]=\"theme\"\n class=\"error-messages\"\n>\n <p\n *ngFor=\"let error of errors\"\n class=\"error-message\"\n >\n <span>{{ error }}</span>\n </p>\n</div>\n<ng-content></ng-content>\n", styles: [".dark tag:focus{box-shadow:0 0 0 1px #323232}.ng2-tag-input.bootstrap3-info{background-color:#fff;display:inline-block;color:#555;vertical-align:middle;max-width:100%;height:42px;line-height:44px}.ng2-tag-input.bootstrap3-info input{border:none;box-shadow:none;outline:none;background-color:transparent;padding:0 6px;margin:0;width:auto;max-width:inherit}.ng2-tag-input.bootstrap3-info .form-control input::-moz-placeholder{color:#777;opacity:1}.ng2-tag-input.bootstrap3-info .form-control input:-ms-input-placeholder{color:#777}.ng2-tag-input.bootstrap3-info .form-control input::-webkit-input-placeholder{color:#777}.ng2-tag-input.bootstrap3-info input:focus{border:none;box-shadow:none}.bootstrap3-info.ng2-tag-input.ng2-tag-input--focused{box-shadow:inset 0 1px 1px #0006;border:1px solid #ccc}.bootstrap3-info.ng2-tag-input.ng2-tag-input--invalid{box-shadow:inset 0 1px 1px #d9534f}.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;transition:all .25s;padding:.25rem 0;min-height:32px;cursor:text;border-bottom:2px solid #efefef}.ng2-tag-input:focus{outline:0}.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.ng2-tag-input.ng2-tag-input--focused{border-bottom:2px solid #2196F3}.ng2-tag-input.ng2-tag-input--invalid{border-bottom:2px solid #f44336}.ng2-tag-input.ng2-tag-input--loading{border:none}.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.ng2-tag-input form{margin:.1em 0}.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.minimal.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:1px solid transparent}.minimal.ng2-tag-input:focus{outline:0}.minimal.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.minimal.ng2-tag-input.ng2-tag-input--loading{border:none}.minimal.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.minimal.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.dark.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:2px solid #444}.dark.ng2-tag-input:focus{outline:0}.dark.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.dark.ng2-tag-input.ng2-tag-input--loading{border:none}.dark.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.dark.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.bootstrap.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;cursor:text;border-bottom:2px solid #efefef}.bootstrap.ng2-tag-input:focus{outline:0}.bootstrap.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.bootstrap.ng2-tag-input.ng2-tag-input--focused{border-bottom:2px solid #0275d8}.bootstrap.ng2-tag-input.ng2-tag-input--invalid{border-bottom:2px solid #d9534f}.bootstrap.ng2-tag-input.ng2-tag-input--loading{border:none}.bootstrap.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.bootstrap.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.bootstrap3-info.ng2-tag-input{display:block;flex-direction:row;flex-wrap:wrap;position:relative;padding:4px;cursor:text;box-shadow:inset 0 1px 1px #00000013;border-radius:4px}.bootstrap3-info.ng2-tag-input:focus{outline:0}.bootstrap3-info.ng2-tag-input.ng2-tag-input--dropping{opacity:.7}.bootstrap3-info.ng2-tag-input.ng2-tag-input--invalid{border-bottom:1px solid #d9534f}.bootstrap3-info.ng2-tag-input.ng2-tag-input--loading{border:none}.bootstrap3-info.ng2-tag-input.ng2-tag-input--disabled{opacity:.5;cursor:not-allowed}.bootstrap3-info.ng2-tag-input form{margin:.1em 0}.bootstrap3-info.ng2-tag-input .ng2-tags-container{flex-wrap:wrap;display:flex}.error-message{fon