UNPKG

ngx-tagify

Version:

Angular library that wraps @yaireo/tagify

270 lines (263 loc) 13 kB
import Tagify from '@yaireo/tagify'; export { default as Tagify } from '@yaireo/tagify'; import * as i0 from '@angular/core'; import { Injectable, inject, ElementRef, viewChild, input, output, effect, forwardRef, Component, NgModule } from '@angular/core'; import { NgClass } from '@angular/common'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subject, BehaviorSubject, pairwise, fromEvent, asyncScheduler } from 'rxjs'; import { takeUntil, throttleTime } from 'rxjs/operators'; import { toObservable } from '@angular/core/rxjs-interop'; 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: "20.3.15", ngImport: i0, type: TagifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class TagifyComponent { get value() { return this.valueData; } set value(v) { if (v !== this.valueData) { this.valueData = v; this.onChange(v); } } constructor() { this.valueType = 'undefined'; this.onChange = Function.prototype; this.onTouched = Function.prototype; this.unsubscribe$ = new Subject(); this.value$ = new BehaviorSubject(null); this.skip = false; this.tagifyService = inject(TagifyService); this.element = inject((ElementRef)); this.inputRef = viewChild('inputRef', ...(ngDevMode ? [{ debugName: "inputRef" }] : [])); this.settings = input({}, ...(ngDevMode ? [{ debugName: "settings" }] : [])); this.name = input('', ...(ngDevMode ? [{ debugName: "name" }] : [])); this.whitelist = input(...(ngDevMode ? [undefined, { debugName: "whitelist" }] : [])); this.inputClass = input('', ...(ngDevMode ? [{ debugName: "inputClass" }] : [])); this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : [])); this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : [])); this.add = output(); this.remove = output(); this.tInput = output(); effect(() => { const readonly = this.readonly(); if (this.tagify) { this.tagify.setReadonly(readonly); } }); effect(() => { const disabled = this.disabled(); if (this.tagify) { this.tagify.setDisabled(disabled); } }); toObservable(this.inputClass) .pipe(pairwise()) .subscribe(([oldValue, newValue]) => { this.setTagsClass(oldValue, newValue); }); } ngAfterViewInit() { const settings = this.settings(); settings.callbacks = settings.callbacks || {}; if (!Object.prototype.hasOwnProperty.call(settings.callbacks, 'add')) { settings.callbacks.add = () => this.add.emit({ tags: this.tagify.value, added: this.tagify.value[this.tagify.value.length - 1], }); } if (!Object.prototype.hasOwnProperty.call(settings.callbacks, 'remove')) { settings.callbacks.remove = () => this.remove.emit(this.tagify.value); } const innerText = this.element.nativeElement.textContent; this.tagify = new Tagify(this.inputRef().nativeElement, settings); // add to service if name is provided if (this.name().length) { this.tagifyService.add(this.name(), this.tagify); } this.tagify.setReadonly(this.readonly()); this.tagify.setDisabled(this.disabled()); // 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 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(oldClass, newClass) { const tagsElement = this.element.nativeElement.querySelector('tags'); if (tagsElement) { tagsElement.classList.remove(...oldClass.split(/\s+/)); tagsElement.classList.add(...newClass.split(/\s+/)); } } ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); this.tagify.destroy(); const instanceName = this.name(); if (instanceName.length) { this.tagifyService.remove(instanceName); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.15", type: TagifyComponent, isStandalone: true, selector: "tagify", inputs: { settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, whitelist: { classPropertyName: "whitelist", publicName: "whitelist", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, 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, isSignal: true }], ngImport: i0, template: `<input [ngClass]="inputClass()" #inputRef /> <span style="display: none"><ng-content></ng-content></span>`, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyComponent, decorators: [{ type: Component, args: [{ selector: 'tagify', imports: [NgClass], template: `<input [ngClass]="inputClass()" #inputRef /> <span style="display: none"><ng-content></ng-content></span>`, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TagifyComponent), multi: true, }, ], }] }], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['inputRef', { isSignal: true }] }], settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], whitelist: [{ type: i0.Input, args: [{ isSignal: true, alias: "whitelist", required: false }] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], add: [{ type: i0.Output, args: ["add"] }], remove: [{ type: i0.Output, args: ["remove"] }], tInput: [{ type: i0.Output, args: ["tInput"] }] } }); class TagifyModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: TagifyModule, imports: [TagifyComponent], exports: [TagifyComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TagifyModule, decorators: [{ type: NgModule, args: [{ imports: [TagifyComponent], exports: [TagifyComponent], }] }] }); /* * Public API Surface of ngx-tagify */ /** * Generated bundle index. Do not edit. */ export { TagifyComponent, TagifyModule, TagifyService }; //# sourceMappingURL=ngx-tagify.mjs.map