UNPKG

@_mehrad/ngx-chips

Version:
897 lines 135 kB
// 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