UNPKG

@tinkoff/angular-contenteditable-accessor

Version:

This is a ControlValueAccessor for using Angular forms with contenteditable elements

175 lines (169 loc) 7.59 kB
import * as i0 from '@angular/core'; import { SecurityContext, ElementRef, Renderer2, forwardRef, Directive, Inject, HostListener, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i1 from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser'; /* * This is a barebones contenteditable {@link ControlValueAccessor} allowing you to use * Angular forms with native contenteditable HTML. For security reasons you might want * to consider sanitizing pasted/dropped content before using it. Also make sure that * you do not set any dangerous content as control value yourself, because directive * just outputs control value as-is. */ class ContenteditableValueAccessor { constructor(elementRef, renderer, sanitizer) { this.elementRef = elementRef; this.renderer = renderer; this.sanitizer = sanitizer; /* * MutationObserver IE11 fallback (as opposed to input event for modern browsers). * When mutation removes a tag, i.e. delete is pressed on the last remaining character * inside a tag — callback is triggered before the DOM is actually changed, therefore * setTimeout is used */ this.observer = new MutationObserver(() => { setTimeout(() => { this.onChange(this.processValue(this.elementRef.nativeElement.innerHTML)); }); }); /* * onTouch callback that marks control as touched and allows FormHooks use */ this.onTouched = () => { }; /* * onChange callback that writes value to control and allows FormHooks use */ this.onChange = () => { }; } /* * To support IE11 MutationObserver is used to monitor changes to the content */ ngAfterViewInit() { this.observer.observe(this.elementRef.nativeElement, { characterData: true, childList: true, subtree: true, }); } /* * Disconnect MutationObserver IE11 fallback on destroy */ ngOnDestroy() { this.observer.disconnect(); } /* * Listen to input events to write innerHTML value into control, * also disconnect MutationObserver as it is not needed if this * event works in current browser */ onInput() { this.observer.disconnect(); this.onChange(this.processValue(this.elementRef.nativeElement.innerHTML)); } /* * Listen to blur event to mark control as touched */ onBlur() { this.onTouched(); } /* * Reacts to external change * * @see {@link ControlValueAccessor#writeValue} */ writeValue(value) { this.renderer.setProperty(this.elementRef.nativeElement, 'innerHTML', this.processValue(value)); } /* * Registers onChange callback * * @see {@link ControlValueAccessor#registerOnChange} */ registerOnChange(onChange) { this.onChange = onChange; } /* * Registers onTouch callback * * @see {@link ControlValueAccessor#registerOnTouched} */ registerOnTouched(onTouched) { this.onTouched = onTouched; } /* * Sets disabled state by setting contenteditable attribute to true/false * * @see {@link ControlValueAccessor#setDisabledState} */ setDisabledState(disabled) { this.renderer.setAttribute(this.elementRef.nativeElement, 'contenteditable', String(!disabled)); } /* * null is treated as empty string to prevent IE11 outputting 'null', * also single <br> is replaced with empty string when passed to the control */ processValue(value) { var _a; const processed = String(value === null || value === undefined ? '' : value); return ((_a = this.sanitizer.sanitize(SecurityContext.HTML, processed.trim() === '<br>' ? '' : processed)) !== null && _a !== void 0 ? _a : ''); } } /** @nocollapse */ ContenteditableValueAccessor.ɵfac = function ContenteditableValueAccessor_Factory(t) { return new (t || ContenteditableValueAccessor)(i0.ɵɵdirectiveInject(ElementRef), i0.ɵɵdirectiveInject(Renderer2), i0.ɵɵdirectiveInject(DomSanitizer)); }; /** @nocollapse */ ContenteditableValueAccessor.ɵdir = /** @pureOrBreakMyCode */ i0.ɵɵdefineDirective({ type: ContenteditableValueAccessor, selectors: [["", "contenteditable", "", "formControlName", ""], ["", "contenteditable", "", "formControl", ""], ["", "contenteditable", "", "ngModel", ""]], hostBindings: function ContenteditableValueAccessor_HostBindings(rf, ctx) { if (rf & 1) { i0.ɵɵlistener("input", function ContenteditableValueAccessor_input_HostBindingHandler() { return ctx.onInput(); })("blur", function ContenteditableValueAccessor_blur_HostBindingHandler() { return ctx.onBlur(); }); } }, features: [i0.ɵɵProvidersFeature([ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef((() => ContenteditableValueAccessor)), multi: true, }, ])] }); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContenteditableValueAccessor, [{ type: Directive, args: [{ selector: '[contenteditable][formControlName], [contenteditable][formControl], [contenteditable][ngModel]', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef((() => ContenteditableValueAccessor)), multi: true, }, ], }] }], function () { return [{ type: i0.ElementRef, decorators: [{ type: Inject, args: [ElementRef] }] }, { type: i0.Renderer2, decorators: [{ type: Inject, args: [Renderer2] }] }, { type: i1.DomSanitizer, decorators: [{ type: Inject, args: [DomSanitizer] }] }]; }, { onInput: [{ type: HostListener, args: ['input'] }], onBlur: [{ type: HostListener, args: ['blur'] }] }); })(); class ContenteditableValueAccessorModule { } /** @nocollapse */ ContenteditableValueAccessorModule.ɵfac = function ContenteditableValueAccessorModule_Factory(t) { return new (t || ContenteditableValueAccessorModule)(); }; /** @nocollapse */ ContenteditableValueAccessorModule.ɵmod = /** @pureOrBreakMyCode */ i0.ɵɵdefineNgModule({ type: ContenteditableValueAccessorModule }); /** @nocollapse */ ContenteditableValueAccessorModule.ɵinj = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjector({}); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContenteditableValueAccessorModule, [{ type: NgModule, args: [{ declarations: [ContenteditableValueAccessor], exports: [ContenteditableValueAccessor], }] }], null, null); })(); (function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(ContenteditableValueAccessorModule, { declarations: [ContenteditableValueAccessor], exports: [ContenteditableValueAccessor] }); })(); /* * Public API Surface of @tinkoff/angular-contenteditable-accessor */ /** * Generated bundle index. Do not edit. */ export { ContenteditableValueAccessor, ContenteditableValueAccessorModule }; //# sourceMappingURL=tinkoff-angular-contenteditable-accessor.js.map