UNPKG

ngx-tagify

Version:

Angular library that wraps @yaireo/tagify

292 lines (285 loc) 11.8 kB
import Tagify from '@yaireo/tagify'; export { default as Tagify } from '@yaireo/tagify'; import * as i0 from '@angular/core'; import { Injectable, EventEmitter, forwardRef, Component, ViewChild, Input, Output, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject, BehaviorSubject, fromEvent, asyncScheduler } from 'rxjs'; import { takeUntil, throttleTime } from 'rxjs/operators'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; class TagifyService { constructor() { this.tagifyMap = new Map(); } /** * Adds a tagify instance, so it is available via service. Used internally. */ add(name, tagify) { if (this.tagifyMap.get(name)) { console.warn(`There already exists a tagify instance with name ${name}!`); return; } this.tagifyMap.set(name, tagify); } /** * Get tagify instance for full access to tagify API. */ get(name) { return this.tagifyMap.get(name); } /** * Removes a tagify instance from service. Used internally. */ remove(name) { this.tagifyMap.delete(name); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class TagifyComponent { set inputClass(v) { this.setTagsClass(v); this.inputClassValue = v; } set readonly(v) { this.readonlyValue = !!v; this.setReadonly(); } set disabled(v) { this.disabledValue = !!v; this.setDisabled(); } get value() { return this.valueData; } set value(v) { if (v !== this.valueData) { this.valueData = v; this.onChange(v); } } constructor(tagifyService, element) { this.tagifyService = tagifyService; this.element = element; this.valueType = 'undefined'; this.onChange = Function.prototype; this.onTouched = Function.prototype; this.unsubscribe$ = new Subject(); this.value$ = new BehaviorSubject(null); this.skip = false; this.inputClassValue = ''; this.readonlyValue = false; this.disabledValue = false; this.settings = {}; this.name = ''; this.add = new EventEmitter(); this.remove = new EventEmitter(); this.tInput = new EventEmitter(); } ngAfterViewInit() { this.settings.callbacks = this.settings.callbacks || {}; if (!Object.prototype.hasOwnProperty.call(this.settings.callbacks, 'add')) { this.settings.callbacks.add = () => this.add.emit({ tags: this.tagify.value, added: this.tagify.value[this.tagify.value.length - 1], }); } if (!Object.prototype.hasOwnProperty.call(this.settings.callbacks, 'remove')) { this.settings.callbacks.remove = () => this.remove.emit(this.tagify.value); } const innerText = this.element.nativeElement.textContent; this.tagify = new Tagify(this.inputRef.nativeElement, this.settings); // add to service if name is provided if (this.name.length) { this.tagifyService.add(this.name, this.tagify); } this.setReadonly(); this.setDisabled(); // if there is some text inside component, load this value and skip first change check if (innerText.length) { this.tagify.loadOriginalValues(innerText); this.skip = true; setTimeout(() => { this.setValue(); }); } // listen to value changes from outside this.value$.pipe(takeUntil(this.unsubscribe$)).subscribe((tags) => { if (tags === null) return; if (this.skip) { this.skip = false; return; } if (this.valueType === 'undefined') { this.valueType = typeof tags; } // if string is passed, e.g. via reactive forms if (typeof tags === 'string') { this.tagify.loadOriginalValues(tags); setTimeout(() => { this.setValue(); }); return; } // add all tags (already existing tags will be skipped this.tagify.addTags(tags, false, true); // remove all tags that are not part of value anymore this.tagify.value.forEach((v) => { if (!tags.find((t) => t.value === v.value)) { // somehow removeTags() with string parameter doesn't always find the tag element // this is a workaround for finding the right tag element const tagElm = this.tagify .getTagElms() .find((el) => el.attributes.getNamedItem('value').textContent === v.value); this.tagify.removeTags(tagElm); } }); }); // listen to tagify events this.tagify.on('input', (e) => { const value = 'value' in e.detail ? e.detail.value : e.detail.textContent; this.tInput.emit(value); if (this.valueType === 'string' && this.tagify.settings.mode === 'mix') { this.value = this.tagify.getMixedTagsAsString(); } }); fromEvent(this.tagify, 'change') .pipe( // throttle used to reduce number of value changes when adding/removing a bunch of tags throttleTime(0, asyncScheduler, { leading: false, trailing: true }), takeUntil(this.unsubscribe$)) .subscribe(() => { this.setValue(); }); // listen to suggestions updates if (this.whitelist) { this.whitelist.pipe(takeUntil(this.unsubscribe$)).subscribe((list) => { this.tagify.settings.whitelist = list; }); } } writeValue(tags) { this.value$.next(tags); } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } setValue() { if (this.valueType === 'string') { if (this.tagify.settings.mode === 'mix') { this.value = this.tagify.getMixedTagsAsString(); } else { this.value = this.tagify.DOM.originalInput.value; } } else { this.value = this.tagify.value.slice(); } } /** * Tagify creates a `tags` element to which the classes of the `input` element are applied. * Changes of `inputClass` are applied automatically to the `input` element, but have to be * manually applied to the `tags` element. */ setTagsClass(v) { const tagsElement = this.element.nativeElement.querySelector('tags'); if (tagsElement) { tagsElement.classList.remove(...this.inputClassValue.split(/\s+/)); tagsElement.classList.add(...v.split(/\s+/)); } } setReadonly() { if (this.tagify) { this.tagify.setReadonly(this.readonlyValue); } } setDisabled() { if (this.tagify) { this.tagify.setDisabled(this.disabledValue); } } ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); this.tagify.destroy(); if (this.name.length) { this.tagifyService.remove(this.name); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyComponent, deps: [{ token: TagifyService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: TagifyComponent, selector: "tagify", inputs: { settings: "settings", name: "name", whitelist: "whitelist", inputClass: "inputClass", readonly: "readonly", disabled: "disabled" }, outputs: { add: "add", remove: "remove", tInput: "tInput" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagifyComponent), multi: true, }, ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, static: true }], ngImport: i0, template: `<input [ngClass]="inputClassValue" #inputRef /> <span style="display: none"><ng-content></ng-content></span>`, isInline: true, dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyComponent, decorators: [{ type: Component, args: [{ selector: 'tagify', template: `<input [ngClass]="inputClassValue" #inputRef /> <span style="display: none"><ng-content></ng-content></span>`, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagifyComponent), multi: true, }, ], }] }], ctorParameters: () => [{ type: TagifyService }, { type: i0.ElementRef }], propDecorators: { inputRef: [{ type: ViewChild, args: ['inputRef', { static: true }] }], settings: [{ type: Input }], name: [{ type: Input }], whitelist: [{ type: Input }], inputClass: [{ type: Input }], readonly: [{ type: Input }], disabled: [{ type: Input }], add: [{ type: Output }], remove: [{ type: Output }], tInput: [{ type: Output }] } }); class TagifyModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: TagifyModule, declarations: [TagifyComponent], imports: [CommonModule], exports: [TagifyComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: TagifyModule, decorators: [{ type: NgModule, args: [{ declarations: [TagifyComponent], imports: [CommonModule], exports: [TagifyComponent], }] }] }); /* * Public API Surface of ngx-tagify */ /** * Generated bundle index. Do not edit. */ export { TagifyComponent, TagifyModule, TagifyService }; //# sourceMappingURL=ngx-tagify.mjs.map