UNPKG

@_mehrad/ngx-chips

Version:
1 lines 141 kB
{"version":3,"file":"_mehrad-ngx-chips.mjs","sources":["../../modules/core/pipes/highlight.pipe.ts","../../modules/core/constants/index.ts","../../modules/core/providers/drag-provider.ts","../../modules/defaults.ts","../../modules/core/providers/options-provider.ts","../../modules/core/accessor.ts","../../modules/core/helpers/listen.ts","../../modules/components/tag-input-form/tag-input-form.component.ts","../../modules/components/tag-input-form/tag-input-form.template.html","../../modules/components/tag/tag-ripple.component.ts","../../modules/components/icon/icon.ts","../../modules/components/icon/icon.html","../../modules/components/tag/tag.component.ts","../../modules/components/tag/tag.template.html","../../modules/components/tag-input/animations.ts","../../modules/components/dropdown/tag-input-dropdown.component.ts","../../modules/components/dropdown/tag-input-dropdown.template.html","../../modules/components/tag-input/tag-input.ts","../../modules/components/tag-input/tag-input.template.html","../../modules/tag-input.module.ts","../../modules/_mehrad-ngx-chips.ts"],"sourcesContent":["import {Pipe, PipeTransform} from '@angular/core';\n\nconst escape = s => s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n\n@Pipe({\n name: 'highlight'\n})\nexport class HighlightPipe implements PipeTransform {\n /**\n * @name transform\n * @param value {string}\n * @param arg {string}\n */\n public transform(value: string, arg: string): string {\n if (!arg.trim()) {\n return value;\n }\n\n try {\n const regex = new RegExp(`(${escape(arg)})`, 'i');\n return value.replace(regex, '<b>$1</b>');\n } catch (e) {\n return value;\n }\n }\n}\n","/*\n** constants and default values for <tag-input>\n */\n\nexport const PLACEHOLDER = '+ Tag';\nexport const SECONDARY_PLACEHOLDER = 'Enter a new tag';\nexport const KEYDOWN = 'keydown';\nexport const KEYUP = 'keyup';\nexport const FOCUS = 'focus';\nexport const MAX_ITEMS_WARNING = 'The number of items specified was greater than the property max-items.';\n\nexport const ACTIONS_KEYS = {\n DELETE: 'DELETE',\n SWITCH_PREV: 'SWITCH_PREV',\n SWITCH_NEXT: 'SWITCH_NEXT',\n TAB: 'TAB'\n};\n\nexport const KEY_PRESS_ACTIONS = {\n 8: ACTIONS_KEYS.DELETE,\n 46: ACTIONS_KEYS.DELETE,\n 37: ACTIONS_KEYS.SWITCH_PREV,\n 39: ACTIONS_KEYS.SWITCH_NEXT,\n 9: ACTIONS_KEYS.TAB\n};\n\nexport const DRAG_AND_DROP_KEY = 'Text';\nexport const NEXT = 'NEXT';\nexport const PREV = 'PREV';\n\n","import { TagInputComponent } from '../../components/tag-input/tag-input';\nimport { TagModel } from '../tag-model';\n\nimport { Injectable } from '@angular/core';\n\nexport declare interface DraggedTag {\n index: number;\n tag: TagModel;\n zone: string;\n}\n\nimport { DRAG_AND_DROP_KEY } from '../../core/constants';\n\nexport declare interface State {\n dragging: boolean;\n dropping: boolean;\n index: number | undefined;\n}\n\nexport declare type StateProperty = keyof State;\n\n@Injectable()\nexport class DragProvider {\n public sender: TagInputComponent;\n public receiver: TagInputComponent;\n\n public state: State = {\n dragging: false,\n dropping: false,\n index: undefined\n };\n\n /**\n * @name setDraggedItem\n * @param event\n * @param tag\n */\n public setDraggedItem(event: DragEvent, tag: DraggedTag): void {\n if (event && event.dataTransfer) {\n event.dataTransfer.setData(DRAG_AND_DROP_KEY, JSON.stringify(tag));\n }\n }\n\n /**\n * @name getDraggedItem\n * @param event\n */\n public getDraggedItem(event: DragEvent): DraggedTag | undefined {\n if (event && event.dataTransfer) {\n const data = event.dataTransfer.getData(DRAG_AND_DROP_KEY);\n try {\n return JSON.parse(data) as DraggedTag;\n } catch {\n return;\n }\n }\n }\n\n /**\n * @name setSender\n * @param sender\n */\n public setSender(sender: TagInputComponent): void {\n this.sender = sender;\n }\n\n /**\n * @name setReceiver\n * @param receiver\n */\n public setReceiver(receiver: TagInputComponent): void {\n this.receiver = receiver;\n }\n\n /**\n * @name onTagDropped\n * @param tag\n * @param indexDragged\n * @param indexDropped\n */\n public onTagDropped(tag: TagModel, indexDragged: number, indexDropped?: number): void {\n this.onDragEnd();\n\n this.sender.onRemoveRequested(tag, indexDragged);\n this.receiver.onAddingRequested(false, tag, indexDropped);\n }\n\n /**\n * @name setState\n * @param state\n */\n public setState(state: { [K in StateProperty]?: State[K] }): void {\n this.state = { ...this.state, ...state };\n }\n\n /**\n * @name getState\n * @param key\n */\n public getState(key?: StateProperty): State | State[StateProperty] {\n return key ? this.state[key] : this.state;\n }\n\n /**\n * @name onDragEnd\n */\n public onDragEnd(): void {\n this.setState({\n dragging: false,\n dropping: false,\n index: undefined\n });\n }\n}\n","import { Observable } from 'rxjs';\nimport { ValidatorFn, AsyncValidatorFn } from '@angular/forms';\n\nimport { SECONDARY_PLACEHOLDER, PLACEHOLDER } from './core/constants/index';\nimport { TagInputDropdown } from './components/dropdown/tag-input-dropdown.component';\nimport { TagModel } from './core/tag-model';\n\nexport interface TagInputOptions {\n separatorKeys: string[];\n separatorKeyCodes: number[];\n maxItems: number;\n placeholder: string;\n secondaryPlaceholder: string;\n validators: ValidatorFn[];\n asyncValidators: AsyncValidatorFn[];\n onlyFromAutocomplete: boolean;\n errorMessages: { [key: string]: string; };\n theme: '';\n onTextChangeDebounce: number;\n inputId: string | null;\n inputClass: string;\n clearOnBlur: boolean;\n hideForm: boolean;\n addOnBlur: boolean;\n addOnPaste: boolean;\n pasteSplitPattern: string | RegExp;\n blinkIfDupe: boolean;\n removable: boolean;\n editable: boolean;\n allowDupes: boolean;\n modelAsStrings: boolean;\n trimTags: boolean;\n ripple: boolean;\n tabIndex: string;\n disable: boolean;\n dragZone: string;\n onRemoving?: (tag: TagModel) => Observable<TagModel>;\n onAdding?: (tag: TagModel) => Observable<TagModel>;\n displayBy: string;\n identifyBy: string;\n animationDuration: {\n enter: string,\n leave: string\n };\n}\n\nexport interface TagInputDropdownOptions {\n displayBy: string;\n identifyBy: string;\n appendToBody: boolean;\n offset: string;\n focusFirstElement: boolean;\n showDropdownIfEmpty: boolean;\n minimumTextLength: number;\n limitItemsTo: number;\n keepOpen: boolean;\n zIndex: number;\n dynamicUpdate: boolean;\n matchingFn: (value: string, target: TagModel) => boolean;\n}\n\nexport const defaults = {\n tagInput: <TagInputOptions>{\n separatorKeys: [],\n separatorKeyCodes: [],\n maxItems: Infinity,\n placeholder: PLACEHOLDER,\n secondaryPlaceholder: SECONDARY_PLACEHOLDER,\n validators: [],\n asyncValidators: [],\n onlyFromAutocomplete: false,\n errorMessages: {},\n theme: '',\n onTextChangeDebounce: 250,\n inputId: null,\n inputClass: '',\n clearOnBlur: false,\n hideForm: false,\n addOnBlur: false,\n addOnPaste: false,\n pasteSplitPattern: ',',\n blinkIfDupe: true,\n removable: true,\n editable: false,\n allowDupes: false,\n modelAsStrings: false,\n trimTags: true,\n ripple: true,\n tabIndex: '',\n disable: false,\n dragZone: '',\n onRemoving: undefined,\n onAdding: undefined,\n displayBy: 'display',\n identifyBy: 'value',\n animationDuration: {\n enter: '250ms',\n leave: '150ms'\n }\n },\n dropdown: <TagInputDropdownOptions>{\n displayBy: 'display',\n identifyBy: 'value',\n appendToBody: true,\n offset: '50 0',\n focusFirstElement: false,\n showDropdownIfEmpty: false,\n minimumTextLength: 1,\n limitItemsTo: Infinity,\n keepOpen: true,\n dynamicUpdate: true,\n zIndex: 1000,\n matchingFn\n }\n};\n\n/**\n * @name matchingFn\n * @param this\n * @param value\n * @param target\n */\nfunction matchingFn(this: TagInputDropdown, value: string, target: TagModel): boolean {\n const targetValue = target[this.displayBy].toString();\n\n return targetValue && targetValue\n .toLowerCase()\n .indexOf(value.toLowerCase()) >= 0;\n}\n","import { defaults, TagInputOptions, TagInputDropdownOptions } from '../../defaults';\n\nexport type Options = {\n tagInput?: {\n [P in keyof TagInputOptions]?: TagInputOptions[P];\n };\n dropdown?: {\n [P in keyof TagInputDropdownOptions]?: TagInputDropdownOptions[P];\n }\n};\n\nexport class OptionsProvider {\n public static defaults = defaults;\n\n public setOptions(options: Options): void {\n OptionsProvider.defaults.tagInput = {...defaults.tagInput, ...options.tagInput};\n OptionsProvider.defaults.dropdown = {...defaults.dropdown, ...options.dropdown};\n }\n}\n\nexport { TagInputDropdownOptions, TagInputOptions };\n","import { ControlValueAccessor } from '@angular/forms';\nimport { Input, Directive } from '@angular/core';\nimport { OptionsProvider } from './providers/options-provider';\nimport { TagInputDropdown } from '../components/dropdown/tag-input-dropdown.component';\nimport { TagModel } from './tag-model';\n\nexport function isObject(obj: any): boolean {\n return obj === Object(obj);\n}\n\n@Directive()\nexport class TagInputAccessor implements ControlValueAccessor {\n private _items: TagModel[] = [];\n private _onTouchedCallback: () => void;\n private _onChangeCallback: (items: TagModel[]) => void;\n\n public dropdown: TagInputDropdown;\n\n /**\n * @name displayBy\n */\n @Input() public displayBy: string = OptionsProvider.defaults.tagInput.displayBy;\n\n /**\n * @name identifyBy\n */\n @Input() public identifyBy: string = OptionsProvider.defaults.tagInput.identifyBy;\n\n public get items(): TagModel[] {\n return this._items;\n }\n\n public set items(items: TagModel[]) {\n this._items = items;\n this._onChangeCallback(this._items);\n }\n\n public onTouched() {\n this._onTouchedCallback();\n }\n\n public writeValue(items: any[]) {\n this._items = items || [];\n }\n\n public registerOnChange(fn: any) {\n this._onChangeCallback = fn;\n }\n\n public registerOnTouched(fn: any) {\n this._onTouchedCallback = fn;\n }\n\n /**\n * @name getItemValue\n * @param item\n * @param fromDropdown\n */\n public getItemValue(item: TagModel, fromDropdown = false): string {\n const property = fromDropdown && this.dropdown ? this.dropdown.identifyBy : this.identifyBy;\n return isObject(item) ? item[property] : item;\n }\n\n /**\n * @name getItemDisplay\n * @param item\n * @param fromDropdown\n */\n public getItemDisplay(item: TagModel, fromDropdown = false): string {\n const property = fromDropdown && this.dropdown ? this.dropdown.displayBy : this.displayBy;\n return isObject(item) ? item[property] : item;\n }\n\n /**\n * @name getItemsWithout\n * @param index\n */\n protected getItemsWithout(index: number): TagModel[] {\n return this.items.filter((item, position) => position !== index);\n }\n}\n","\n/**\n * @name listen\n * @param listenerType\n * @param action\n * @param condition\n */\nexport function listen(listenerType: string, action: () => any, condition = true): void {\n // if the event provided does not exist, throw an error\n if (!this.listeners.hasOwnProperty(listenerType)) {\n throw new Error('The event entered may be wrong');\n }\n\n // if a condition is present and is false, exit early\n if (!condition) {\n return;\n }\n\n // fire listener\n this.listeners[listenerType].push(action);\n}\n","import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';\nimport { AsyncValidatorFn, UntypedFormControl, UntypedFormGroup, ValidatorFn } from '@angular/forms';\n\n@Component({\n selector: 'tag-input-form',\n styleUrls: ['./tag-input-form.style.scss'],\n templateUrl: './tag-input-form.template.html'\n})\nexport class TagInputForm implements OnInit, OnChanges {\n /**\n * @name onSubmit\n */\n @Output() public onSubmit: EventEmitter<any> = new EventEmitter();\n\n /**\n * @name onBlur\n */\n @Output() public onBlur: EventEmitter<any> = new EventEmitter();\n\n /**\n * @name onFocus\n */\n @Output() public onFocus: EventEmitter<any> = new EventEmitter();\n\n /**\n * @name onKeyup\n */\n @Output() public onKeyup: EventEmitter<any> = new EventEmitter();\n\n /**\n * @name onKeydown\n */\n @Output() public onKeydown: EventEmitter<any> = new EventEmitter();\n\n /**\n * @name inputTextChange\n */\n @Output() public inputTextChange: EventEmitter<string> = new EventEmitter();\n\n // inputs\n\n /**\n * @name placeholder\n */\n @Input() public placeholder: string;\n\n /**\n * @name validators\n */\n @Input() public validators: ValidatorFn[] = [];\n\n /**\n * @name asyncValidators\n * @desc array of AsyncValidator that are used to validate the tag before it gets appended to the list\n */\n @Input() public asyncValidators: AsyncValidatorFn[] = [];\n\n /**\n * @name inputId\n */\n @Input() public inputId: string;\n\n /**\n * @name inputClass\n */\n @Input() public inputClass: string;\n\n /**\n * @name tabindex\n * @desc pass through the specified tabindex to the input\n */\n @Input() public tabindex = '';\n\n /**\n * @name disabled\n */\n @Input() public disabled = false;\n\n /**\n * @name input\n */\n @ViewChild('input') public input;\n\n /**\n * @name form\n */\n public form: UntypedFormGroup;\n\n /**\n * @name inputText\n */\n @Input()\n public get inputText(): string {\n return this.item.value;\n }\n\n /**\n * @name inputText\n * @param text {string}\n */\n public set inputText(text: string) {\n this.item.setValue(text);\n\n this.inputTextChange.emit(text);\n }\n\n private readonly item: UntypedFormControl = new UntypedFormControl({ value: '', disabled: this.disabled });\n\n ngOnInit() {\n this.item.setValidators(this.validators);\n this.item.setAsyncValidators(this.asyncValidators);\n\n // creating form\n this.form = new UntypedFormGroup({\n item: this.item\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.disabled && !changes.disabled.firstChange) {\n if (changes.disabled.currentValue) {\n this.form.controls['item'].disable();\n } else {\n this.form.controls['item'].enable();\n }\n }\n }\n\n /**\n * @name value\n */\n public get value(): UntypedFormControl {\n return this.form.get('item') as UntypedFormControl;\n }\n\n /**\n * @name isInputFocused\n */\n public isInputFocused(): boolean {\n const doc = typeof document !== 'undefined' ? document : undefined;\n return doc ? doc.activeElement === this.input.nativeElement : false;\n }\n\n /**\n * @name getErrorMessages\n * @param messages\n */\n public getErrorMessages(messages: { [key: string]: string }): string[] {\n return Object.keys(messages)\n .filter(err => this.value.hasError(err))\n .map(err => messages[err]);\n }\n\n /**\n * @name hasErrors\n */\n public hasErrors(): boolean {\n const { dirty, value, valid } = this.form;\n return dirty && value.item && !valid;\n }\n\n /**\n * @name focus\n */\n public focus(): void {\n this.input.nativeElement.focus();\n }\n\n /**\n * @name blur\n */\n public blur(): void {\n this.input.nativeElement.blur();\n }\n\n /**\n * @name getElementPosition\n */\n public getElementPosition(): ClientRect {\n return this.input.nativeElement.getBoundingClientRect();\n }\n\n /**\n * - removes input from the component\n * @name destroy\n */\n public destroy(): void {\n const input = this.input.nativeElement;\n input.parentElement.removeChild(input);\n }\n\n /**\n * @name onKeyDown\n * @param $event\n */\n public onKeyDown($event) {\n this.inputText = this.value.value;\n if ($event.key === 'Enter') {\n this.submit($event);\n } else {\n return this.onKeydown.emit($event);\n }\n }\n\n /**\n * @name onKeyUp\n * @param $event\n */\n public onKeyUp($event) {\n this.inputText = this.value.value;\n return this.onKeyup.emit($event);\n }\n\n /**\n * @name submit\n */\n public submit($event: any): void {\n $event.preventDefault();\n this.onSubmit.emit($event);\n }\n}\n","<!-- form -->\n<form (ngSubmit)=\"submit($event)\" [formGroup]=\"form\">\n <input #input\n\n type=\"text\"\n class=\"ng2-tag-input__text-input\"\n autocomplete=\"off\"\n tabindex=\"{{ disabled ? -1 : tabindex ? tabindex : 0 }}\"\n minlength=\"1\"\n formControlName=\"item\"\n\n [ngClass]=\"inputClass\"\n [attr.id]=\"inputId\"\n [attr.placeholder]=\"placeholder\"\n [attr.aria-label]=\"placeholder\"\n [attr.tabindex]=\"tabindex\"\n [attr.disabled]=\"disabled ? disabled : null\"\n\n (focus)=\"onFocus.emit($event)\"\n (blur)=\"onBlur.emit($event)\"\n (keydown)=\"onKeyDown($event)\"\n (keyup)=\"onKeyUp($event)\"\n />\n</form>\n","import {\n Component,\n Input\n} from '@angular/core';\n\n\nimport {\n animate,\n trigger,\n style,\n transition,\n keyframes,\n state\n} from '@angular/animations';\n\n@Component({\n selector: 'tag-ripple',\n styles: [`\n :host {\n width: 100%;\n height: 100%;\n left: 0;\n overflow: hidden;\n position: absolute;\n }\n\n .tag-ripple {\n background: rgba(0, 0, 0, 0.1);\n top: 50%;\n left: 50%;\n height: 100%;\n transform: translate(-50%, -50%);\n position: absolute;\n }\n `],\n template: `\n <div class=\"tag-ripple\" [@ink]=\"state\"></div>\n `,\n animations: [\n trigger('ink', [\n state('none', style({width: 0, opacity: 0})),\n transition('none => clicked', [\n animate(300, keyframes([\n style({opacity: 1, offset: 0, width: '30%', borderRadius: '100%'}),\n style({opacity: 1, offset: 0.5, width: '50%'}),\n style({opacity: 0.5, offset: 1, width: '100%', borderRadius: '16px'})\n ]))\n ])\n ])\n ]\n})\nexport class TagRipple {\n @Input() public state = 'none';\n}\n","import { Component } from '@angular/core';\n\n@Component({\n selector: 'delete-icon',\n templateUrl: './icon.html',\n styleUrls: ['./icon.scss']\n})\nexport class DeleteIconComponent {}\n","<span>\n <svg\n height=\"16px\"\n viewBox=\"0 0 32 32\"\n width=\"16px\"\n >\n <path\n d=\"M17.459,16.014l8.239-8.194c0.395-0.391,0.395-1.024,0-1.414c-0.394-0.391-1.034-0.391-1.428,0 l-8.232,8.187L7.73,6.284c-0.394-0.395-1.034-0.395-1.428,0c-0.394,0.396-0.394,1.037,0,1.432l8.302,8.303l-8.332,8.286 c-0.394,0.391-0.394,1.024,0,1.414c0.394,0.391,1.034,0.391,1.428,0l8.325-8.279l8.275,8.276c0.394,0.395,1.034,0.395,1.428,0 c0.394-0.396,0.394-1.037,0-1.432L17.459,16.014z\"\n fill=\"#121313\"\n />\n </svg>\n</span>","import {\n Component,\n Input,\n Output,\n EventEmitter,\n TemplateRef,\n ElementRef,\n HostListener,\n HostBinding,\n ViewChild,\n ChangeDetectorRef,\n Renderer2\n} from '@angular/core';\n\nimport { TagModel } from '../../core/tag-model';\nimport { TagRipple } from '../tag/tag-ripple.component';\n\n// mocking navigator\nconst navigator = typeof window !== 'undefined' ? window.navigator : {\n userAgent: 'Chrome',\n vendor: 'Google Inc'\n};\n\nconst isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);\n\n@Component({\n selector: 'tag',\n templateUrl: './tag.template.html',\n styleUrls: ['./tag-component.style.scss']\n})\nexport class TagComponent {\n /**\n * @name model {TagModel}\n */\n @Input()\n public model: TagModel;\n\n /**\n * @name removable {boolean}\n */\n @Input()\n public removable: boolean;\n\n /**\n * @name editable {boolean}\n */\n @Input()\n public editable: boolean;\n\n /**\n * @name template {TemplateRef<any>}\n */\n @Input()\n public template: TemplateRef<any>;\n\n /**\n * @name displayBy {string}\n */\n @Input()\n public displayBy: string;\n\n /**\n * @name identifyBy {string}\n */\n @Input()\n public identifyBy: string;\n\n /**\n * @name index {number}\n */\n @Input()\n public index: number;\n\n /**\n * @name hasRipple\n */\n @Input()\n public hasRipple: boolean;\n\n /**\n * @name disabled\n */\n @Input()\n public disabled = false;\n\n /**\n * @name canAddTag\n */\n @Input()\n public canAddTag: (tag: TagModel) => boolean;\n\n /**\n * @name onSelect\n */\n @Output()\n public onSelect: EventEmitter<TagModel> = new EventEmitter<TagModel>();\n\n /**\n * @name onRemove\n */\n @Output()\n public onRemove: EventEmitter<TagModel> = new EventEmitter<TagModel>();\n\n /**\n * @name onBlur\n */\n @Output()\n public onBlur: EventEmitter<TagModel> = new EventEmitter<TagModel>();\n\n /**\n * @name onKeyDown\n */\n @Output()\n public onKeyDown: EventEmitter<any> = new EventEmitter<any>();\n\n /**\n * @name onTagEdited\n */\n @Output()\n public onTagEdited: EventEmitter<TagModel> = new EventEmitter<TagModel>();\n\n /**\n * @name readonly {boolean}\n */\n public get readonly(): boolean {\n return typeof this.model !== 'string' && this.model.readonly === true;\n }\n\n /**\n * @name editing\n */\n public editing = false;\n\n /**\n * @name moving\n */\n @HostBinding('class.moving')\n public moving: boolean;\n\n /**\n * @name rippleState\n */\n public rippleState = 'none';\n\n /**\n * @name ripple {TagRipple}\n */\n @ViewChild(TagRipple)\n public ripple: TagRipple;\n\n constructor(\n public element: ElementRef,\n public renderer: Renderer2,\n private cdRef: ChangeDetectorRef\n ) {}\n\n /**\n * @name select\n */\n public select($event?: MouseEvent): void {\n if (this.readonly || this.disabled) {\n return;\n }\n\n if ($event) {\n $event.stopPropagation();\n }\n\n this.focus();\n\n this.onSelect.emit(this.model);\n }\n\n /**\n * @name remove\n */\n public remove($event: MouseEvent): void {\n $event.stopPropagation();\n this.onRemove.emit(this);\n }\n\n /**\n * @name focus\n */\n public focus(): void {\n this.element.nativeElement.focus();\n }\n\n public move(): void {\n this.moving = true;\n }\n\n /**\n * @name keydown\n * @param event\n */\n @HostListener('keydown', ['$event'])\n public keydown(event: KeyboardEvent): void {\n if (this.editing) {\n if (event.keyCode === 13) {\n return this.disableEditMode(event);\n }\n } else {\n this.onKeyDown.emit({ event, model: this.model });\n }\n }\n\n /**\n * @name blink\n */\n public blink(): void {\n const classList = this.element.nativeElement.classList;\n classList.add('blink');\n\n setTimeout(() => classList.remove('blink'), 50);\n }\n\n /**\n * @name toggleEditMode\n */\n public toggleEditMode(): void {\n if (this.editable) {\n return this.editing ? undefined : this.activateEditMode();\n }\n }\n\n /**\n * @name onBlurred\n * @param event\n */\n public onBlurred(event: any): void {\n // Checks if it is editable first before handeling the onBlurred event in order to prevent\n // a bug in IE where tags are still editable with onlyFromAutocomplete set to true\n if (!this.editable) {\n return;\n }\n\n this.disableEditMode();\n\n const value: string = event.target.innerText;\n const result =\n typeof this.model === 'string'\n ? value\n : { ...this.model, [this.displayBy]: value };\n\n this.onBlur.emit(result);\n }\n\n /**\n * @name getDisplayValue\n * @param item\n */\n public getDisplayValue(item: TagModel): string {\n return typeof item === 'string' ? item : item[this.displayBy];\n }\n\n /**\n * @desc returns whether the ripple is visible or not\n * only works in Chrome\n * @name isRippleVisible\n */\n public get isRippleVisible(): boolean {\n return !this.readonly && !this.editing && isChrome && this.hasRipple;\n }\n\n /**\n * @name disableEditMode\n * @param $event\n */\n public disableEditMode($event?: Event): void {\n const classList = this.element.nativeElement.classList;\n const input = this.getContentEditableText();\n\n this.editing = false;\n classList.remove('tag--editing');\n\n if (!input) {\n this.setContentEditableText(this.model);\n return;\n }\n\n this.storeNewValue(input);\n this.cdRef.detectChanges();\n\n if ($event) {\n $event.preventDefault();\n }\n }\n\n /**\n * @name isDeleteIconVisible\n */\n public isDeleteIconVisible(): boolean {\n return (\n !this.readonly && !this.disabled && this.removable && !this.editing\n );\n }\n\n /**\n * @name getContentEditableText\n */\n private getContentEditableText(): string {\n const input = this.getContentEditable();\n\n return input ? input.innerText.trim() : '';\n }\n\n /**\n * @name setContentEditableText\n * @param model\n */\n private setContentEditableText(model: TagModel) {\n const input = this.getContentEditable();\n const value = this.getDisplayValue(model);\n\n input.innerText = value;\n }\n\n /**\n * @name\n */\n private activateEditMode(): void {\n const classList = this.element.nativeElement.classList;\n classList.add('tag--editing');\n\n this.editing = true;\n }\n\n /**\n * @name storeNewValue\n * @param input\n */\n private storeNewValue(input: string): void {\n const exists = (tag: TagModel) => {\n return typeof tag === 'string'\n ? tag === input\n : tag[this.displayBy] === input;\n };\n\n const hasId = () => {\n return this.model[this.identifyBy] !== this.model[this.displayBy];\n };\n\n // if the value changed, replace the value in the model\n if (exists(this.model)) {\n return;\n }\n\n const model =\n typeof this.model === 'string'\n ? input\n : {\n index: this.index,\n [this.identifyBy]: hasId()\n ? this.model[this.identifyBy]\n : input,\n [this.displayBy]: input\n };\n\n if (this.canAddTag(model)) {\n this.onTagEdited.emit({ tag: model, index: this.index });\n } else {\n this.setContentEditableText(this.model);\n }\n }\n\n /**\n * @name getContentEditable\n */\n private getContentEditable(): HTMLInputElement {\n return this.element.nativeElement.querySelector('[contenteditable]');\n }\n}\n","<div (click)=\"select($event)\"\n (dblclick)=\"toggleEditMode()\"\n (mousedown)=\"rippleState='clicked'\"\n (mouseup)=\"rippleState='none'\"\n [ngSwitch]=\"!!template\"\n [class.disabled]=\"disabled\"\n [attr.tabindex]=\"-1\"\n [attr.aria-label]=\"getDisplayValue(model)\">\n\n <div *ngSwitchCase=\"true\" [attr.contenteditable]=\"editing\">\n <!-- CUSTOM TEMPLATE -->\n <ng-template\n [ngTemplateOutletContext]=\"{ item: model, index: index }\"\n [ngTemplateOutlet]=\"template\">\n </ng-template>\n </div>\n\n <div *ngSwitchCase=\"false\" class=\"tag-wrapper\">\n <!-- TAG NAME -->\n <div [attr.contenteditable]=\"editing\"\n [attr.title]=\"getDisplayValue(model)\"\n class=\"tag__text inline\"\n spellcheck=\"false\"\n (keydown.enter)=\"disableEditMode($event)\"\n (keydown.escape)=\"disableEditMode($event)\"\n (click)=\"editing ? $event.stopPropagation() : undefined\"\n (blur)=\"onBlurred($event)\">\n {{ getDisplayValue(model) }}\n </div>\n\n <!-- 'X' BUTTON -->\n <delete-icon\n aria-label=\"Remove tag\"\n role=\"button\"\n (click)=\"remove($event)\"\n *ngIf=\"isDeleteIconVisible()\">\n </delete-icon>\n </div>\n</div>\n\n<tag-ripple [state]=\"rippleState\"\n [attr.tabindex]=\"-1\"\n *ngIf=\"isRippleVisible\">\n</tag-ripple>\n","import {\n animate,\n trigger,\n style,\n transition,\n keyframes,\n state,\n AnimationTriggerMetadata\n} from '@angular/animations';\n\n/**\n * @name animations\n */\nexport const animations = [\n trigger('animation', [\n state('in', style({\n opacity: 1\n })),\n state('out', style({\n opacity: 0\n })),\n transition(':enter', [\n animate('{{ enter }}', keyframes([\n style({opacity: 0, offset: 0, transform: 'translate(0px, 20px)'}),\n style({opacity: 0.3, offset: 0.3, transform: 'translate(0px, -10px)'}),\n style({opacity: 0.5, offset: 0.5, transform: 'translate(0px, 0px)'}),\n style({opacity: 0.75, offset: 0.75, transform: 'translate(0px, 5px)'}),\n style({opacity: 1, offset: 1, transform: 'translate(0px, 0px)'})\n ]))\n ]),\n transition(':leave', [\n animate('{{ leave }}', keyframes([\n style({opacity: 1, transform: 'translateX(0)', offset: 0}),\n style({opacity: 1, transform: 'translateX(-15px)', offset: 0.7}),\n style({opacity: 0, transform: 'translateX(100%)', offset: 1.0})\n ]))\n ])\n ])\n];\n","import {\n Component,\n ContentChildren,\n EventEmitter,\n HostListener,\n Injector,\n Input,\n QueryList,\n TemplateRef,\n ViewChild,\n AfterViewInit\n} from '@angular/core';\n\n// rx\nimport { Observable } from 'rxjs';\nimport { filter, first, debounceTime, distinctUntilChanged } from 'rxjs/operators';\n\nimport { Ng2Dropdown, Ng2MenuItem } from 'ng2-material-dropdown';\nimport { defaults } from '../../defaults';\nimport { TagInputComponent } from '../tag-input/tag-input';\nimport {TagModel} from '../../core/tag-model';\n\n@Component({\n selector: 'tag-input-dropdown',\n templateUrl: './tag-input-dropdown.template.html'\n})\nexport class TagInputDropdown implements AfterViewInit {\n /**\n * @name dropdown\n */\n @ViewChild(Ng2Dropdown) public dropdown: Ng2Dropdown;\n\n /**\n * @name menuTemplate\n * @desc reference to the template if provided by the user\n */\n @ContentChildren(TemplateRef) public templates: QueryList<TemplateRef<any>>;\n\n /**\n * @name offset\n */\n @Input() public offset: string = defaults.dropdown.offset;\n\n /**\n * @name focusFirstElement\n */\n @Input() public focusFirstElement = defaults.dropdown.focusFirstElement;\n\n /**\n * - show autocomplete dropdown if the value of input is empty\n * @name showDropdownIfEmpty\n */\n @Input() public showDropdownIfEmpty = defaults.dropdown.showDropdownIfEmpty;\n\n /**\n * @description observable passed as input which populates the autocomplete items\n * @name autocompleteObservable\n */\n @Input() public autocompleteObservable: (text: string) => Observable<any>;\n\n /**\n * - desc minimum text length in order to display the autocomplete dropdown\n * @name minimumTextLength\n */\n @Input() public minimumTextLength = defaults.dropdown.minimumTextLength;\n\n /**\n * - number of items to display in the autocomplete dropdown\n * @name limitItemsTo\n */\n @Input() public limitItemsTo: number = defaults.dropdown.limitItemsTo;\n\n /**\n * @name displayBy\n */\n @Input() public displayBy = defaults.dropdown.displayBy;\n\n /**\n * @name identifyBy\n */\n @Input() public identifyBy = defaults.dropdown.identifyBy;\n\n /**\n * @description a function a developer can use to implement custom matching for the autocomplete\n * @name matchingFn\n */\n @Input() public matchingFn: (value: string, target: TagModel) => boolean =\n defaults.dropdown.matchingFn;\n\n /**\n * @name appendToBody\n */\n @Input() public appendToBody = defaults.dropdown.appendToBody;\n\n /**\n * @name keepOpen\n * @description option to leave dropdown open when adding a new item\n */\n @Input() public keepOpen = defaults.dropdown.keepOpen;\n\n /**\n * @name dynamicUpdate\n */\n @Input() public dynamicUpdate = defaults.dropdown.dynamicUpdate;\n\n /**\n * @name zIndex\n */\n @Input() public zIndex = defaults.dropdown.zIndex;\n\n /**\n * list of items that match the current value of the input (for autocomplete)\n * @name items\n */\n public items: TagModel[] = [];\n\n /**\n * @name tagInput\n */\n public tagInput: TagInputComponent = this.injector.get(TagInputComponent);\n\n /**\n * @name _autocompleteItems\n */\n private _autocompleteItems: TagModel[] = [];\n\n /**\n * @name autocompleteItems\n * @param items\n */\n public set autocompleteItems(items: TagModel[]) {\n this._autocompleteItems = items;\n }\n\n /**\n * @name autocompleteItems\n * @desc array of items that will populate the autocomplete\n */\n @Input() public get autocompleteItems(): TagModel[] {\n const items = this._autocompleteItems;\n\n if (!items) {\n return [];\n }\n\n return items.map((item: TagModel) => {\n return typeof item === 'string'\n ? {\n [this.displayBy]: item,\n [this.identifyBy]: item\n }\n : item;\n });\n }\n\n constructor(private readonly injector: Injector) {}\n\n /**\n * @name ngAfterviewInit\n */\n ngAfterViewInit(): void {\n this.onItemClicked().subscribe((item: Ng2MenuItem) => {\n this.requestAdding(item);\n });\n\n // reset itemsMatching array when the dropdown is hidden\n this.onHide().subscribe(this.resetItems);\n\n const DEBOUNCE_TIME = 200;\n const KEEP_OPEN = this.keepOpen;\n\n this.tagInput.onTextChange\n .asObservable()\n .pipe(\n distinctUntilChanged(),\n debounceTime(DEBOUNCE_TIME),\n filter((value: string) => {\n if (KEEP_OPEN === false) {\n return value.length > 0;\n }\n\n return true;\n })\n )\n .subscribe(this.show);\n }\n\n /**\n * @name updatePosition\n */\n public updatePosition(): void {\n const position = this.tagInput.inputForm.getElementPosition();\n\n this.dropdown.menu.updatePosition(position, this.dynamicUpdate);\n }\n\n /**\n * @name isVisible\n */\n public get isVisible(): boolean {\n return this.dropdown.menu.dropdownState.menuState.isVisible;\n }\n\n /**\n * @name onHide\n */\n public onHide(): EventEmitter<Ng2Dropdown> {\n return this.dropdown.onHide;\n }\n\n /**\n * @name onItemClicked\n */\n public onItemClicked() {\n return this.dropdown.onItemClicked;\n }\n\n /**\n * @name selectedItem\n */\n public get selectedItem(): Ng2MenuItem {\n return this.dropdown.menu.dropdownState.dropdownState.selectedItem;\n }\n\n /**\n * @name state\n */\n public get state(): any {\n return this.dropdown.menu.dropdownState;\n }\n\n /**\n *\n * @name show\n */\n public show = (): void => {\n const maxItemsReached =\n this.tagInput.items.length === this.tagInput.maxItems;\n const value = this.getFormValue();\n const hasMinimumText = value.trim().length >= this.minimumTextLength;\n const position = this.calculatePosition();\n const items = this.getMatchingItems(value);\n const hasItems = items.length > 0;\n const isHidden = this.isVisible === false;\n const showDropdownIfEmpty = this.showDropdownIfEmpty && hasItems && !value;\n const isDisabled = this.tagInput.disable;\n\n const shouldShow =\n isHidden && ((hasItems && hasMinimumText) || showDropdownIfEmpty);\n const shouldHide = this.isVisible && !hasItems;\n\n if (this.autocompleteObservable && hasMinimumText) {\n return this.getItemsFromObservable(value);\n }\n\n if (\n (!this.showDropdownIfEmpty && !value) ||\n maxItemsReached ||\n isDisabled\n ) {\n return this.dropdown.hide();\n }\n\n this.setItems(items);\n\n if (shouldShow) {\n this.dropdown.show(position);\n } else if (shouldHide) {\n this.hide();\n }\n };\n\n /**\n * @name hide\n */\n public hide(): void {\n this.resetItems();\n this.dropdown.hide();\n }\n\n /**\n * @name scrollListener\n */\n @HostListener('window:scroll')\n public scrollListener(): void {\n if (!this.isVisible || !this.dynamicUpdate) {\n return;\n }\n\n this.updatePosition();\n }\n\n /**\n * @name onWindowBlur\n */\n @HostListener('window:blur')\n public onWindowBlur(): void {\n this.dropdown.hide();\n }\n\n /**\n * @name getFormValue\n */\n private getFormValue(): string {\n const formValue = this.tagInput.formValue;\n return formValue ? formValue.toString().trim() : '';\n }\n\n /**\n * @name calculatePosition\n */\n private calculatePosition(): ClientRect {\n return this.tagInput.inputForm.getElementPosition();\n }\n\n /**\n * @name requestAdding\n * @param item {Ng2MenuItem}\n */\n private requestAdding = async (item: Ng2MenuItem) => {\n const tag = this.createTagModel(item);\n await this.tagInput.onAddingRequested(true, tag).catch(() => {});\n };\n\n /**\n * @name createTagModel\n * @param item\n */\n private createTagModel(item: Ng2MenuItem): TagModel {\n const display =\n typeof item.value === 'string' ? item.value : item.value[this.displayBy];\n const value =\n typeof item.value === 'string' ? item.value : item.value[this.identifyBy];\n\n return {\n ...item.value,\n [this.tagInput.displayBy]: display,\n [this.tagInput.identifyBy]: value\n };\n }\n\n /**\n *\n * @param value {string}\n */\n private getMatchingItems(value: string): TagModel[] {\n if (!value && !this.showDropdownIfEmpty) {\n return [];\n }\n\n const dupesAllowed = this.tagInput.allowDupes;\n\n return this.autocompleteItems.filter((item: TagModel) => {\n const hasValue = dupesAllowed\n ? false\n : this.tagInput.tags.some(tag => {\n const identifyBy = this.tagInput.identifyBy;\n const model =\n typeof tag.model === 'string' ? tag.model : tag.model[identifyBy];\n\n return model === item[this.identifyBy];\n });\n\n return this.matchingFn(value, item) && hasValue === false;\n });\n }\n\n /**\n * @name setItems\n */\n private setItems(items: TagModel[]): void {\n this.items = items.slice(0, this.limitItemsTo || items.length);\n }\n\n /**\n * @name resetItems\n */\n private resetItems = (): void => {\n this.items = [];\n };\n\n /**\n * @name populateItems\n * @param data\n */\n private populateItems(data: any): TagInputDropdown {\n this.autocompleteItems = data.map(item => {\n return typeof item === 'string'\n ? {\n [this.displayBy]: item,\n [this.identifyBy]: item\n }\n : item;\n });\n\n return this;\n }\n\n /**\n * @name getItemsFromObservable\n * @param text\n */\n private getItemsFromObservable = (text: string): void => {\n this.setLoadingState(true);\n\n const subscribeFn = (data: any[]) => {\n // hide loading animation\n this.setLoadingState(false)\n // add items\n .populateItems(data);\n\n this.setItems(this.getMatchingItems(text));\n\n if (this.items.length) {\n this.dropdown.show(this.calculatePosition());\n } else {\n this.dropdown.hide();\n }\n };\n\n this.autocompleteObservable(text)\n .pipe(first())\n .subscribe(subscribeFn, () => this.setLoadingState(false));\n };\n\n /**\n * @name setLoadingState\n * @param state\n */\n private setLoadingState(state: boolean): TagInputDropdown {\n this.tagInput.isLoading = state;\n\n return this;\n }\n}\n","<ng2-dropdown [dynamicUpdate]=\"dynamicUpdate\">\n <ng2-dropdown-menu [focusFirstElement]=\"focusFirstElement\"\n [zIndex]=\"zIndex\"\n [appendToBody]=\"appendToBody\"\n [offset]=\"offset\">\n <ng2-menu-item *ngFor=\"let item of items; let index = index; let last = last\"\n [value]=\"item\"\n [ngSwitch]=\"!!templates.length\">\n\n <span *ngSwitchCase=\"false\"\n [innerHTML]=\"item[displayBy] | highlight : tagInput.inputForm.value.value\">\n </span>\n\n <ng-template *ngSwitchDefault\n [ngTemplateOutlet]=\"templates.first\"\n [ngTemplateOutletContext]=\"{ item: item, index: index, last: last }\">\n </ng-template>\n </ng2-menu-item>\n </ng2-dropdown-menu>\n</ng2-dropdown>\n","// angular\nimport {\n Component,\n forwardRef,\n HostBinding,\n Input,\n Output,\n EventEmitter,\n Renderer2,\n ViewChild,\n ViewChildren,\n ContentChildren,\n ContentChild,\n OnInit,\n TemplateRef,\n QueryList,\n AfterViewInit\n} from '@angular/core';\n\nimport {\n AsyncValidatorFn,\n UntypedFormControl,\n NG_VALUE_ACCESSOR,\n ValidatorFn\n} from '@angular/forms';\n\n// rx\nimport { Observable, debounceTime, filter, map, first } from 'rxjs';\n\n// ng2-tag-input\nimport { TagInputAccessor } from '../../core/accessor';\nimport { TagModel } from '../../core/tag-model';\n\nimport { listen } from '../../core/helpers/listen';\nimport * as constants from '../../core/constants';\n\nimport { DragProvider, DraggedTag } from '../../core/providers/drag-provider';\n\nimport { TagInputForm } from '../tag-input-form/tag-input-form.component';\nimport { TagComponent } from '../tag/tag.component';\n\nimport { animations } from './animations';\nimport { defaults } from '../../defaults';\nimport { TagInputDropdown } from '../dropdown/tag-input-dropdown.component';\n\nconst CUSTOM_ACCESSOR = {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => TagInputComponent),\n multi: true\n};\n\n@Component({\n selector: 'tag-input',\n providers: [CUSTOM_ACCESSOR],\n styleUrls: ['./tag-input.style.scss'],\n templateUrl: './tag-input.template.html',\n animations\n})\nexport class TagInputComponent extends TagInputAccessor implements OnInit, AfterViewInit {\n /**\n * @name separatorKeys\n * @desc keyboard keys with which a user can separate items\n */\n @Input() public separatorKeys: string[] = defaults.tagInput.separatorKeys;\n\n /**\n * @name separatorKeyCodes\n * @desc keyboard key codes with which a user can separate items\n */\n @Input() public separatorKeyCodes: number[] = defaults.tagInput.separatorKeyCodes;\n\n /**\n * @name placeholder\n * @desc the placeholder of the input text\n */\n @Input() public placeholder: string = defaults.tagInput.placeholder;\n\n /**\n * @name secondaryPlaceholder\n * @desc placeholder to appear when the input is empty\n */\n @Input() public secondaryPlaceholder: string = defaults.tagInput.secondaryPlaceholder;\n\n /**\n * @name maxItems\n * @desc maximum number of items that can be added\n */\n @Input() public maxItems: number = defaults.tagInput.maxItems;\n\n /**\n * @name validators\n * @desc array of Validators that are used to validate the tag before it gets appended to the list\n */\n @Input() public validators: ValidatorFn[] = defaults.tagInput.validators;\n\n /**\n * @name asyncValidators\n * @desc array of AsyncValidator that are used to validate the tag before it gets appended to the list\n */\n @Input() public asyncValidators: AsyncValidatorFn[] = defaults.tagInput.asyncValidators;\n\n /**\n * - if set to true, it will only possible to add items from the autocomplete\n * @name onlyFromAutocomplete\n */\n @Input() public onlyFromAutocomplete = defaults.tagInput.onlyFromAutocomplete;\n\n /**\n * @name errorMessages\n */\n @Input() public errorMessages: { [key: string]: string } = defaults.tagInput.errorMessages;\n\n /**\n * @name theme\n */\n @Input() public theme: string = defaults.tagInput.theme;\n\n /**\n * @name onTextChangeDebounce\n */\n @Input() public onTextChangeDebounce = defaults.tagInput.onTextChangeDebounce;\n\n /**\n * - custom id assigned to the input\n * @name id\n */\n @Input() public inputId = defaults.tagInput.inputId;\n\n /**\n * - custom class assigned to the input\n */\n @Input() public inputClass: string = defaults.tagInput.inputClass;\n\n /**\n * - option to clear text input when the form is blurred\n * @name clearOnBlur\n */\n @Input() public clearOnBlur: boolean = defaults.tagInput.clearOnBlur;\n\n /**\n * - hideForm\n * @name clearOnBlur\n */\n @Input() public hideForm: boolean = defaults.tagInput.hideForm;\n\n /**\n * @name addOnBlur\n */\n @Input() public addOnBlur: boolean = defaults.tagInput.addOnBlur;\n\n /**\n * @name addOnPaste\n */\n @Input() public addOnPaste: boolean = defaults.tagInput.addOnPaste;\n\n /**\n * - pattern used with the native method split() to separate patterns in the string pasted\n * @name pasteSplitPattern\n */\n @Input() public pasteSplitPattern = defaults.tagInput.pasteSplitPattern;\n\n /**\n * @name blinkIfDupe\n */\n @Input() public blinkIfDupe = defaults.tagInput.blinkIfDupe;\n\n /**\n * @name removable\n */\n @Input() public removable = defaults.tagInput.removable;\n\n /**\n * @name editable\n */\n @Input() public editable: boolean = defaults.tagInput.editable;\n\n /**\n * @name allowDupes\n */\n @Input() public allowDupes = defaults.tagInput.allowDupes;\n\n /**\n * @description if set to true, the newly added tags will be added as strings, and not objects\n * @name modelAsStrings\n */\n @Input() public modelAsStrings = defaults.tagInput.modelAsStrings;\n\n /**\n * @name trimTags\n */\n @Input() public trimTags = defaults.tagInput.trimTags;\n\n /**\n * @name inputText\n */\n @Input() public get inputText(): string {\n return this.inputTextValue;\n }\n\n /**\n * @name ripple\n */\n @Input() public ripple: boolean = defaults.tagInput.ripple;\n\n /**\n * @name tabindex\n * @desc pass through the specified tabindex to the input\n */\n @Input() public tabindex: string = defaults.tagInput.tabIndex;\n\n /**\n * @name disable\n */\n @Input() public disable: boolean = defaults.tagInput.disable;\n\n /**\n * @name dragZone\n */\n @Input() public dragZone: string = defaults.tagInput.dragZone;\n\n /**\n * @name onRemoving\n */\n @Input() public onRemoving = defaults.tagInput.onRemoving;\n\n /**\n * @name onAdding\n */\n @Input() public onAdding = defaults.tagInput.onAdding;\n\n /**\n * @name animationDuration\n */\n @Input() public animationDuration = defaults.tagInput.animationDuration;\n\n /**\n * @name onAdd\n * @desc event emitted when adding a new item\n */\n @Output() public onAdd = new EventEmitter<TagModel>