ngx-tags
Version:
To install this library, run:
660 lines (648 loc) • 22.2 kB
JavaScript
import { BrowserModule } from '@angular/platform-browser';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { Subject } from 'rxjs';
import { filter, map, takeUntil } from 'rxjs/operators';
import { Component, ViewEncapsulation, Input, EventEmitter, Output, forwardRef, HostListener, ViewChild, ElementRef, Directive, NgModule } from '@angular/core';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const noop = (/**
* @return {?}
*/
() => { });
class NgxTagsValueAccessor {
constructor() {
this.onTouchedCallback = noop;
this.onChangeCallback = noop;
this._value = [];
}
/**
* @return {?}
*/
get value() {
return this._value;
}
// set accessor including call the onchange callback
/**
* @param {?} v
* @return {?}
*/
set value(v) {
if (v !== this._value) {
this._value = v;
this.onChangeCallback(v);
}
}
/**
* @param {?} value
* @return {?}
*/
writeValue(value) {
if (value !== this._value) {
this._value = value;
}
}
// From ControlValueAccessor interface
/**
* @param {?} fn
* @return {?}
*/
registerOnChange(fn) {
this.onChangeCallback = fn;
}
// From ControlValueAccessor interface
/**
* @param {?} fn
* @return {?}
*/
registerOnTouched(fn) {
this.onTouchedCallback = fn;
}
/**
* @param {?} isDisabled
* @return {?}
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/* tslint:disable:component-selector */
class NgTagComponent extends NgxTagsValueAccessor {
constructor() {
super();
this.isMenuOpen = false;
this.inputTag = '';
this.placeholder = 'add a tag';
this.actionTypes = {
'add': 'add',
'delete': 'delete',
'update': 'update',
};
this.tagLabel = 'tagLabel';
this.tagValue = 'tagValue';
this.clearOnBlur = true;
this.allowDupes = false;
this.onlyFromDropdown = false;
this.tagEditable = false;
this.change = new EventEmitter();
}
/**
* @return {?}
*/
get tags() {
return this._value;
}
/**
* @return {?}
*/
whenClickedOut() {
this.isMenuOpen = false;
}
/**
* @param {?} e
* @return {?}
*/
enter(e) {
if (this.onlyFromDropdown) {
e.stopProgation();
return false;
}
if (this.inputTag.trim()) {
this.addToModal(this.inputTag);
this.clearInput();
}
}
/**
* @param {?} $event
* @return {?}
*/
save($event) {
this._value[$event.index][this.tagLabel] = $event.item.trim();
this.emitChange(this.actionTypes.update, this._value[$event.index]);
}
/**
* @return {?}
*/
blur() {
if (this.clearOnBlur) {
this.clearInput();
}
}
/**
* @return {?}
*/
clearInput() {
this.inputTag = '';
}
/**
* @param {?} indx
* @return {?}
*/
del(indx) {
if (this.canDeleteTags) {
this._value.splice(indx, 1);
this.emitChange(this.actionTypes.delete, this._value);
}
}
/**
* @return {?}
*/
backspace() {
if (!this.removeLastOnBackspace && !this.inputTag.trim()) {
this._value.pop();
}
}
/**
* @return {?}
*/
input() {
/** @type {?} */
const _inputTag = this.inputTag.trim();
if (_inputTag) {
this._options = this.filterByInput(this.options);
this.isMenuOpen = true;
}
}
/**
* @private
* @param {?} items
* @return {?}
*/
filterByInput(items) {
return items.filter((/**
* @param {?} item
* @return {?}
*/
(item) => {
/** @type {?} */
const val = (typeof item !== 'string' && this.tagLabel) ? item[this.tagLabel] : item;
if (val.toLowerCase().includes(this.inputTag.trim().toLowerCase())) {
return true;
}
return false;
}));
}
/**
* @param {?} item
* @return {?}
*/
select(item) {
this.addToModal(item.value);
this.clearInput();
this.whenClickedOut();
}
/**
* @private
* @param {?} item
* @return {?}
*/
addToModal(item) {
if (this.hasReachedMaxTags()) {
return;
}
/** @type {?} */
const itemToAdd = this.createTag(item);
if (!this.allowDupes) {
if (this.isDuplicate(itemToAdd)) {
this._value.push(itemToAdd);
this.emitChange(this.actionTypes.add, itemToAdd);
}
}
else {
this._value.push(itemToAdd);
this.emitChange(this.actionTypes.add, itemToAdd);
}
}
/**
* @private
* @param {?} tag
* @return {?}
*/
createTag(tag) {
if (typeof tag !== 'string') {
return tag;
}
else {
return {
[this.tagValue]: tag,
[this.tagLabel]: tag
};
}
}
/**
* @return {?}
*/
hasReachedMaxTags() {
return this.maxTags ? (this.maxTags === this._value.length) : false;
}
/**
* @param {?} item
* @return {?}
*/
isDuplicate(item) {
return this._value.indexOf(item) === -1 ? true : false;
}
/**
* @private
* @param {?} type
* @param {?} value
* @return {?}
*/
emitChange(type, value) {
this.change.emit({ type: type, value: value });
}
}
NgTagComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-tags',
template: "<section class=\"ngx-tags\">\n <section class=\"tag-container\" [closeMenu]=\"isMenuOpen\" (whenClickedOut)=\"whenClickedOut()\" [ngClass]=\"{'ngx-tags-disabled':disabled}\" >\n <span class=\"tag\" *ngFor=\"let tag of tags;let $index = index\" [ngClass]=\"{'tag-readonly': tag.readonly}\">\n <ng-container [ngTemplateOutletContext]=\"{ item: tag, index: $index,tagLabel:tagLabel,tagEditable:tagEditable }\"\n [ngTemplateOutlet]=\"tagTemplate ? tagTemplate:defaultTagItemTemplate\">\n </ng-container>\n <a href=\"#\" class=\"close\" [hidden]=\"!canDeleteTags\" (click)=\"del($index)\"></a>\n </span>\n <span class=\"tag-input\" >\n <input type=\"text\" *ngIf=\"canAddTags\" [(ngModel)]=\"inputTag\" (blur)=\"blur()\"\n [placeholder]=\"placeholder\" (keydown.enter)=\"enter($event)\" (keydown.backspace)=\"backspace()\"\n (input)=\"input()\">\n </span>\n </section>\n <ngx-tags-dropdown [items]=\"_options\" [dropdownItemTemplate]=\"dropdownItemTemplate\" [tagLabel]=\"tagLabel\" [inputTag]=\"inputTag\" (select)=\"select($event) \" [hidden]=\"!isMenuOpen\"></ngx-tags-dropdown>\n</section>\n\n<ng-template #defaultTagItemTemplate let-item=\"item\" let-tagLabel=\"tagLabel\" let-input=\"input\" let-tagEditable=\"tagEditable\" let-index=\"index\">\n <ngx-tag [item]=\"item\" [tagEditable]=\"tagEditable\" [tagLabel]=\"tagLabel\" [index]=\"index\" (change)=\"save($event)\"></ngx-tag>\n</ng-template>",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef((/**
* @return {?}
*/
() => NgTagComponent)),
multi: true
}],
encapsulation: ViewEncapsulation.None,
styles: [".ngx-tags{position:relative;display:block;width:100%}.ngx-tags .ngx-tags-disabled{opacity:.7;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}.ngx-tags .tag-container{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;box-sizing:border-box}.ngx-tags .tag-container .tag,.ngx-tags .tag-container .tag-input{background:#9dc1ff;padding:.25rem;border-radius:.25rem;margin-right:.25rem;display:inline-flex;align-items:center;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.ngx-tags .tag-container .tag-input{background:#fff}.ngx-tags .tag-container .tag-input input{border:none;outline:0;min-width:140px;max-width:100%;height:100%;margin-left:4px;box-sizing:border-box;display:inline-block}.ngx-tags .tag-container .tag-readonly{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;opacity:.7}.ngx-tags .tag-container .close{position:relative;display:inline-block;width:10px;height:10px;opacity:.8}.ngx-tags .tag-container .close:hover{opacity:1}.ngx-tags .tag-container .close:after,.ngx-tags .tag-container .close:before{position:absolute;left:3px;content:' ';height:11px;width:2px;background-color:#333}.ngx-tags .tag-container .close:before{transform:rotate(45deg)}.ngx-tags .tag-container .close:after{transform:rotate(-45deg)}.item:hover,.keynav-active{background:#e1e4e8}.dropdown{margin:0;padding:0;border:1px solid #e1e4e8;box-shadow:0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12);border-radius:2px;max-height:200px;overflow-y:auto;position:absolute;background:#fff;display:block;width:100%;z-index:1}.item{padding:4px 2px;display:block}"]
}] }
];
/** @nocollapse */
NgTagComponent.ctorParameters = () => [];
NgTagComponent.propDecorators = {
tagTemplate: [{ type: Input }],
dropdownItemTemplate: [{ type: Input }],
options: [{ type: Input }],
maxTags: [{ type: Input }],
tagLabel: [{ type: Input }],
tagValue: [{ type: Input }],
removeLastOnBackspace: [{ type: Input }],
canDeleteTags: [{ type: Input }],
canAddTags: [{ type: Input }],
clearOnBlur: [{ type: Input }],
allowDupes: [{ type: Input }],
onlyFromDropdown: [{ type: Input }],
tagEditable: [{ type: Input }],
change: [{ type: Output }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const keyCodeConstant = {
UP: 'ArrowUp',
DOWN: 'ArrowDown',
LEFT: 'ArrowLeft',
RIGHT: 'ArrowRight',
ESC: 'Escape',
ENTER: 'Enter',
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/* tslint:disable:component-selector */
class DropdownComponent {
constructor() {
this._focusIndex = -1;
this.navKey$ = new Subject();
this.destroy$ = new Subject();
this.select = new EventEmitter();
}
/**
* @param {?} event
* @return {?}
*/
onKeydownHandler(event) {
this.navKey$.next(event);
}
/**
* @return {?}
*/
ngOnInit() {
this.navDropdown();
this.selectOnEnter();
}
/**
* @param {?} change
* @return {?}
*/
ngOnChanges(change) {
if (change.items) {
this._focusIndex = 0;
}
}
/**
* @return {?}
*/
selectOnEnter() {
this.navKey$
.pipe(map((/**
* @param {?} e
* @return {?}
*/
(e) => e.key)), filter((/**
* @param {?} key
* @return {?}
*/
(key) => this.items && this.items.length)), filter((/**
* @param {?} key
* @return {?}
*/
(key) => key === keyCodeConstant.ENTER)), takeUntil(this.destroy$))
.subscribe((/**
* @param {?} key
* @return {?}
*/
(key) => {
this.emitSelectedItem(this.items[this._focusIndex], this._focusIndex);
}));
}
/**
* @return {?}
*/
navDropdown() {
this.navKey$
.pipe(map((/**
* @param {?} e
* @return {?}
*/
(e) => e.key)), filter((/**
* @param {?} key
* @return {?}
*/
(key) => this.items && this.items.length)), filter((/**
* @param {?} key
* @return {?}
*/
(key) => key === keyCodeConstant.DOWN || key === keyCodeConstant.UP)), takeUntil(this.destroy$))
.subscribe((/**
* @param {?} key
* @return {?}
*/
(key) => {
if (this.items.length) {
/** @type {?} */
let tempFocusIndex = 0;
if (key === keyCodeConstant.DOWN) {
tempFocusIndex = this._focusIndex + 1;
tempFocusIndex = (tempFocusIndex === this.items.length) ? 0 : tempFocusIndex;
}
else if (key === keyCodeConstant.UP) {
tempFocusIndex = this._focusIndex - 1;
tempFocusIndex = (tempFocusIndex === -1) ? this.items.length - 1 : tempFocusIndex;
}
this._focusIndex = tempFocusIndex;
}
}));
}
/**
* @param {?} item
* @param {?} index
* @return {?}
*/
emitSelectedItem(item, index) {
this.select.emit({ key: index, value: item });
}
/**
* @return {?}
*/
ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.unsubscribe();
}
}
DropdownComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-tags-dropdown',
template: "<article class=\"dropdown\" #dropdown tabindex=\"0\">\n\t<a class=\"item\" *ngFor=\"let item of items;let $index =index\" (click)=\"emitSelectedItem(item,$index)\" [ngClass]=\"{'keynav-active':$index == _focusIndex}\" [InView]=\"$index == _focusIndex\" >\n <ng-container \n [ngTemplateOutletContext]=\"{ \n item: item, \n label: tagLabel, \n input: inputTag, \n index: index }\"\n [ngTemplateOutlet]=\"dropdownItemTemplate ? dropdownItemTemplate : defaultDropdownItemTemplate\">\n </ng-container>\n </a>\n</article>\n<ng-template \n #defaultDropdownItemTemplate\n let-item=\"item\"\n let-label=\"label\"\n let-input=\"input\"\n let-index=\"index\">\n <span > {{item[label]}}</span>\n</ng-template>\n",
encapsulation: ViewEncapsulation.None
}] }
];
/** @nocollapse */
DropdownComponent.ctorParameters = () => [];
DropdownComponent.propDecorators = {
items: [{ type: Input }],
tagLabel: [{ type: Input }],
inputTag: [{ type: Input }],
dropdownItemTemplate: [{ type: Input }],
select: [{ type: Output }],
onKeydownHandler: [{ type: HostListener, args: ['document:keydown', ['$event'],] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/* tslint:disable:component-selector */
class TagComponent {
constructor() {
this.change = new EventEmitter();
}
/**
* @return {?}
*/
get isEditable() {
return this.tagContent.nativeElement.contentEditable;
}
/**
* @param {?} flag
* @return {?}
*/
set isEditable(flag) {
this.tagContent.nativeElement.contentEditable = flag;
}
/**
* @param {?} e
* @return {?}
*/
dbclick(e) {
if (this.tagEditable) {
this.isEditable = true;
}
}
/**
* @param {?} event
* @return {?}
*/
keydownHandler(event) {
if (this.isEditable && event.key === keyCodeConstant.ESC) {
this.toggleEditMode();
}
}
/**
* @return {?}
*/
toggleEditMode() {
this.isEditable = !this.isEditable;
}
/**
* @param {?} e
* @return {?}
*/
save(e) {
e.preventDefault();
/** @type {?} */
const tagText = this.tagContent.nativeElement.innerHTML.trim();
if (tagText) {
this.change.emit({ item: tagText, index: this.index });
}
this.toggleEditMode();
}
/**
* @return {?}
*/
blur() {
this.isEditable = false;
}
}
TagComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-tag',
template: "<span (keydown.enter)=\"save($event)\" contenteditable=\"false\" (blur)=\"blur()\" #tagContent>\n {{(tagLabel) ? item[tagLabel] : item }}\n</span>\n\n\n"
}] }
];
/** @nocollapse */
TagComponent.ctorParameters = () => [];
TagComponent.propDecorators = {
item: [{ type: Input }],
tagLabel: [{ type: Input }],
index: [{ type: Input }],
tagEditable: [{ type: Input }],
change: [{ type: Output }],
tagContent: [{ type: ViewChild, args: ['tagContent',] }],
dbclick: [{ type: HostListener, args: ['dblclick', ['$event'],] }],
keydownHandler: [{ type: HostListener, args: ['keydown', ['$event'],] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/* tslint:disable:directive-selector */
class CloseMenuDirective {
/**
* @param {?} $elem
*/
constructor($elem) {
this.$elem = $elem;
this.whenClickedOut = new EventEmitter();
}
/**
* @param {?} e
* @return {?}
*/
onMouseUp(e) {
if (this.closeMenu) {
this.isOutside(e);
}
}
/**
* @param {?} e
* @return {?}
*/
isOutside(e) {
if (!this.$elem.nativeElement.contains(event.target) && this.$elem.nativeElement.style.display !== 'none') {
this.whenClickedOut.emit();
}
}
}
CloseMenuDirective.decorators = [
{ type: Directive, args: [{
selector: '[closeMenu]'
},] }
];
/** @nocollapse */
CloseMenuDirective.ctorParameters = () => [
{ type: ElementRef }
];
CloseMenuDirective.propDecorators = {
whenClickedOut: [{ type: Output }],
closeMenu: [{ type: Input }],
onMouseUp: [{ type: HostListener, args: ['document:mouseup', ['$event'],] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/* tslint:disable:directive-selector */
class InViewDirective {
/**
* @param {?} el
*/
constructor(el) {
this.el = el;
this.dropdown = this.el.nativeElement.closest('.dropdown');
}
/**
* @param {?} values
* @return {?}
*/
ngOnChanges(values) {
if (values.InView.currentValue) {
this.setInView();
}
}
/**
* @return {?}
*/
setInView() {
/** @type {?} */
const dropdown = this.el.nativeElement.closest('.dropdown');
/** @type {?} */
const currentPos = this.el.nativeElement.offsetTop;
/** @type {?} */
const dropdownScrollPos = dropdown.scrollTop;
/** @type {?} */
const dropdownHeight = dropdown.offsetHeight;
/** @type {?} */
const elHeight = this.el.nativeElement.offsetHeight;
if ((currentPos + elHeight) > (dropdownScrollPos + dropdownHeight)) {
dropdown.scrollTop = currentPos;
}
else if (dropdownScrollPos > currentPos) {
dropdown.scrollTop = currentPos;
}
}
}
InViewDirective.decorators = [
{ type: Directive, args: [{
selector: '[InView]'
},] }
];
/** @nocollapse */
InViewDirective.ctorParameters = () => [
{ type: ElementRef }
];
InViewDirective.propDecorators = {
InView: [{ type: Input }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class NgxTagsModule {
}
NgxTagsModule.decorators = [
{ type: NgModule, args: [{
imports: [BrowserModule, FormsModule],
declarations: [NgTagComponent, DropdownComponent, TagComponent, CloseMenuDirective, InViewDirective],
exports: [NgTagComponent]
},] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { NgxTagsModule, DropdownComponent as ɵc, TagComponent as ɵd, CloseMenuDirective as ɵe, InViewDirective as ɵf, NgTagComponent as ɵa, NgxTagsValueAccessor as ɵb };
//# sourceMappingURL=ngx-tags.js.map