@ctrl/ngx-codemirror
Version:
CodeMirror wrapper for Angular
258 lines (253 loc) • 10.3 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, forwardRef, Component, ChangeDetectionStrategy, Input, Output, ViewChild, NgModule } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
function normalizeLineEndings(str) {
if (!str) {
return str;
}
return str.replace(/\r\n|\r/g, '\n');
}
class CodemirrorComponent {
_differs;
_ngZone;
/* class applied to the created textarea */
className = '';
/* name applied to the created textarea */
name = 'codemirror';
/* autofocus setting applied to the created textarea */
autoFocus = false;
/**
* set options for codemirror
* @link http://codemirror.net/doc/manual.html#config
*/
set options(value) {
this._options = value;
if (!this._differ && value) {
this._differ = this._differs.find(value).create();
}
}
/* preserve previous scroll position after updating value */
preserveScrollPosition = false;
/* called when the text cursor is moved */
cursorActivity = new EventEmitter();
/* called when the editor is focused or loses focus */
focusChange = new EventEmitter();
/* called when the editor is scrolled */
// eslint-disable-next-line @angular-eslint/no-output-native
scroll = new EventEmitter();
/* called when file(s) are dropped */
// eslint-disable-next-line @angular-eslint/no-output-native
drop = new EventEmitter();
/* called when codeMirror instance is initiated on the component */
codeMirrorLoaded = new EventEmitter();
ref;
value = '';
disabled = false;
isFocused = false;
codeMirror;
/**
* either global variable or required library
*/
_codeMirror;
_differ;
_options;
constructor(_differs, _ngZone) {
this._differs = _differs;
this._ngZone = _ngZone;
}
get codeMirrorGlobal() {
if (this._codeMirror) {
return this._codeMirror;
}
// in order to allow for universal rendering, we import Codemirror runtime with `require` to prevent node errors
this._codeMirror = typeof CodeMirror !== 'undefined' ? CodeMirror : import('codemirror');
return this._codeMirror;
}
ngAfterViewInit() {
this._ngZone.runOutsideAngular(async () => {
const codeMirrorObj = await this.codeMirrorGlobal;
const codeMirror = codeMirrorObj?.default ? codeMirrorObj.default : codeMirrorObj;
this.codeMirror = codeMirror.fromTextArea(this.ref.nativeElement, this._options);
this.codeMirror.on('cursorActivity', cm => this._ngZone.run(() => this.cursorActive(cm)));
this.codeMirror.on('scroll', this.scrollChanged.bind(this));
this.codeMirror.on('blur', () => this._ngZone.run(() => this.focusChanged(false)));
this.codeMirror.on('focus', () => this._ngZone.run(() => this.focusChanged(true)));
this.codeMirror.on('change', (cm, change) => this._ngZone.run(() => this.codemirrorValueChanged(cm, change)));
this.codeMirror.on('drop', (cm, e) => {
this._ngZone.run(() => this.dropFiles(cm, e));
});
this.codeMirror.setValue(this.value);
this.codeMirrorLoaded.emit(this);
});
}
ngDoCheck() {
if (!this._differ) {
return;
}
// check options have not changed
const changes = this._differ.diff(this._options);
if (changes) {
changes.forEachChangedItem(option => this.setOptionIfChanged(option.key, option.currentValue));
changes.forEachAddedItem(option => this.setOptionIfChanged(option.key, option.currentValue));
changes.forEachRemovedItem(option => this.setOptionIfChanged(option.key, option.currentValue));
}
}
ngOnDestroy() {
// is there a lighter-weight way to remove the cm instance?
if (this.codeMirror) {
this.codeMirror.toTextArea();
}
}
codemirrorValueChanged(cm, change) {
const cmVal = cm.getValue();
if (this.value !== cmVal) {
this.value = cmVal;
this.onChange(this.value);
}
}
setOptionIfChanged(optionName, newValue) {
if (!this.codeMirror) {
return;
}
// cast to any to handle strictly typed option names
// could possibly import settings strings available in the future
this.codeMirror.setOption(optionName, newValue);
}
focusChanged(focused) {
this.onTouched();
this.isFocused = focused;
this.focusChange.emit(focused);
}
scrollChanged(cm) {
this.scroll.emit(cm.getScrollInfo());
}
cursorActive(cm) {
this.cursorActivity.emit(cm);
}
dropFiles(cm, e) {
this.drop.emit([cm, e]);
}
/** Implemented as part of ControlValueAccessor. */
writeValue(value) {
if (value === null || value === undefined) {
return;
}
if (!this.codeMirror) {
this.value = value;
return;
}
const cur = this.codeMirror.getValue();
if (value !== cur && normalizeLineEndings(cur) !== normalizeLineEndings(value)) {
this.value = value;
if (this.preserveScrollPosition) {
const prevScrollPosition = this.codeMirror.getScrollInfo();
this.codeMirror.setValue(this.value);
this.codeMirror.scrollTo(prevScrollPosition.left, prevScrollPosition.top);
}
else {
this.codeMirror.setValue(this.value);
}
}
}
/** Implemented as part of ControlValueAccessor. */
registerOnChange(fn) {
this.onChange = fn;
}
/** Implemented as part of ControlValueAccessor. */
registerOnTouched(fn) {
this.onTouched = fn;
}
/** Implemented as part of ControlValueAccessor. */
setDisabledState(isDisabled) {
this.disabled = isDisabled;
this.setOptionIfChanged('readOnly', this.disabled);
}
/** Implemented as part of ControlValueAccessor. */
onChange = (_) => { };
/** Implemented as part of ControlValueAccessor. */
onTouched = () => { };
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorComponent, deps: [{ token: i0.KeyValueDiffers }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.1", type: CodemirrorComponent, selector: "ngx-codemirror", inputs: { className: "className", name: "name", autoFocus: "autoFocus", options: "options", preserveScrollPosition: "preserveScrollPosition" }, outputs: { cursorActivity: "cursorActivity", focusChange: "focusChange", scroll: "scroll", drop: "drop", codeMirrorLoaded: "codeMirrorLoaded" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CodemirrorComponent),
multi: true,
},
], viewQueries: [{ propertyName: "ref", first: true, predicate: ["ref"], descendants: true }], ngImport: i0, template: `
<textarea
[name]="name"
class="ngx-codemirror {{ className }}"
[class.ngx-codemirror--focused]="isFocused"
autocomplete="off"
[autofocus]="autoFocus"
#ref
>
</textarea>
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorComponent, decorators: [{
type: Component,
args: [{
selector: 'ngx-codemirror',
template: `
<textarea
[name]="name"
class="ngx-codemirror {{ className }}"
[class.ngx-codemirror--focused]="isFocused"
autocomplete="off"
[autofocus]="autoFocus"
#ref
>
</textarea>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CodemirrorComponent),
multi: true,
},
],
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
}]
}], ctorParameters: function () { return [{ type: i0.KeyValueDiffers }, { type: i0.NgZone }]; }, propDecorators: { className: [{
type: Input
}], name: [{
type: Input
}], autoFocus: [{
type: Input
}], options: [{
type: Input
}], preserveScrollPosition: [{
type: Input
}], cursorActivity: [{
type: Output
}], focusChange: [{
type: Output
}], scroll: [{
type: Output
}], drop: [{
type: Output
}], codeMirrorLoaded: [{
type: Output
}], ref: [{
type: ViewChild,
args: ['ref']
}] } });
class CodemirrorModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorModule, declarations: [CodemirrorComponent], exports: [CodemirrorComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.1", ngImport: i0, type: CodemirrorModule, decorators: [{
type: NgModule,
args: [{
exports: [CodemirrorComponent],
declarations: [CodemirrorComponent],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { CodemirrorComponent, CodemirrorModule };
//# sourceMappingURL=ctrl-ngx-codemirror.mjs.map