mat-contenteditable
Version:
Angular contenteditable directive for Angular forms and Material Design
691 lines (680 loc) • 27 kB
JavaScript
import { __extends, __decorate, __metadata, __param } from 'tslib';
import { ElementRef, Renderer2, Optional, Self, Input, HostBinding, HostListener, Directive, Host, ViewContainerRef, NgModule } from '@angular/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
import { mixinErrorState, ErrorStateMatcher } from '@angular/material/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Subject } from 'rxjs';
import { CKEditorComponent } from '@ckeditor/ckeditor5-angular';
// Boilerplate for applying mixins to MatInput.
/** @docs-private */
var MatInputBase = /** @class */ (function () {
function MatInputBase(_defaultErrorStateMatcher, _parentForm, _parentFormGroup,
/** @docs-private */
ngControl) {
this._defaultErrorStateMatcher = _defaultErrorStateMatcher;
this._parentForm = _parentForm;
this._parentFormGroup = _parentFormGroup;
this.ngControl = ngControl;
}
return MatInputBase;
}());
var _MatInputMixinBase = mixinErrorState(MatInputBase);
var MatContenteditableDirective = /** @class */ (function (_super) {
__extends(MatContenteditableDirective, _super);
function MatContenteditableDirective(elementRef, renderer, ngControl, _parentForm, _parentFormGroup, _defaultErrorStateMatcher) {
var _this = _super.call(this, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl) || this;
_this.elementRef = elementRef;
_this.renderer = renderer;
_this.ngControl = ngControl;
_this.stateChanges = new Subject();
_this.id = "mat-input-" + MatContenteditableDirective_1.nextId++;
_this.focused = false;
_this.contentEmpty = ['<br>', '<div><br></div>'];
_this._required = false;
_this._disabled = false;
_this.controlType = 'mat-input';
_this.describedBy = '';
_this.propValueAccessor = 'innerHTML';
// Setting the value accessor directly (instead of using
// the providers) to avoid running into a circular import.
if (_this.ngControl != null) {
_this.ngControl.valueAccessor = _this;
}
return _this;
}
MatContenteditableDirective_1 = MatContenteditableDirective;
Object.defineProperty(MatContenteditableDirective.prototype, "value", {
get: function () { return this.elementRef.nativeElement[this.propValueAccessor]; },
set: function (value) {
if (value !== this.value) {
this.elementRef.nativeElement[this.propValueAccessor] = value;
this.stateChanges.next();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatContenteditableDirective.prototype, "placeholder", {
get: function () {
return this._placeholder;
},
set: function (plh) {
this._placeholder = plh;
this.stateChanges.next();
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatContenteditableDirective.prototype, "empty", {
get: function () {
return !this.value || this.contentEmpty.includes(this.value);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatContenteditableDirective.prototype, "shouldLabelFloat", {
get: function () { return this.focused || !this.empty; },
enumerable: true,
configurable: true
});
Object.defineProperty(MatContenteditableDirective.prototype, "required", {
get: function () {
return this._required;
},
set: function (req) {
this._required = coerceBooleanProperty(req);
this.stateChanges.next();
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatContenteditableDirective.prototype, "disabled", {
get: function () {
return this._disabled;
},
set: function (dis) {
this._disabled = coerceBooleanProperty(dis);
this.stateChanges.next();
},
enumerable: true,
configurable: true
});
MatContenteditableDirective.prototype.ngDoCheck = function () {
if (this.ngControl) {
// We need to re-evaluate this on every change detection cycle, because there are some
// error triggers that we can't subscribe to (e.g. parent form submissions). This means
// that whatever logic is in here has to be super lean or we risk destroying the performance.
this.updateErrorState();
}
};
MatContenteditableDirective.prototype.callOnChange = function () {
if (typeof this.onChange === 'function') {
this.onChange(this.elementRef.nativeElement[this.propValueAccessor]);
}
};
MatContenteditableDirective.prototype.callOnFocused = function () {
if (this.focused !== true) {
this.focused = true;
this.stateChanges.next();
}
};
MatContenteditableDirective.prototype.callOnTouched = function () {
if (typeof this.onTouched === 'function') {
this.onTouched();
}
if (this.focused !== false) {
this.focused = false;
this.stateChanges.next();
}
};
MatContenteditableDirective.prototype.setDescribedByIds = function (ids) {
this.describedBy = ids.join(' ');
};
MatContenteditableDirective.prototype.onContainerClick = function () { this.elementRef.nativeElement.focus(); };
/**
* Writes a new value to the element.
* This method will be called by the forms API to write
* to the view when programmatic (model -> view) changes are requested.
*
* See: [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor#members)
*/
MatContenteditableDirective.prototype.writeValue = function (value) {
var normalizedValue = value == null ? '' : value;
this.renderer.setProperty(this.elementRef.nativeElement, this.propValueAccessor, normalizedValue);
};
/**
* Registers a callback function that should be called when
* the control's value changes in the UI.
*
* This is called by the forms API on initialization so it can update
* the form model when values propagate from the view (view -> model).
*/
MatContenteditableDirective.prototype.registerOnChange = function (fn) {
this.onChange = fn;
};
/**
* Registers a callback function that should be called when the control receives a blur event.
* This is called by the forms API on initialization so it can update the form model on blur.
*/
MatContenteditableDirective.prototype.registerOnTouched = function (fn) {
this.onTouched = fn;
};
/**
* This function is called by the forms API when the control status changes to or from "DISABLED".
* Depending on the value, it should enable or disable the appropriate DOM element.
*/
MatContenteditableDirective.prototype.setDisabledState = function (isDisabled) {
if (isDisabled) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'disabled', 'true');
this.removeDisabledState = this.renderer.listen(this.elementRef.nativeElement, 'keydown', this.listenerDisabledState);
}
else {
if (this.removeDisabledState) {
this.renderer.removeAttribute(this.elementRef.nativeElement, 'disabled');
this.removeDisabledState();
}
}
};
MatContenteditableDirective.prototype.listenerDisabledState = function (e) {
e.preventDefault();
};
var MatContenteditableDirective_1;
/**
* Implemented as part of MatFormFieldControl.
* See https://material.angular.io/guide/creating-a-custom-form-field-control
*/
MatContenteditableDirective.nextId = 0;
MatContenteditableDirective.ctorParameters = function () { return [
{ type: ElementRef },
{ type: Renderer2 },
{ type: NgControl, decorators: [{ type: Optional }, { type: Self }] },
{ type: NgForm, decorators: [{ type: Optional }] },
{ type: FormGroupDirective, decorators: [{ type: Optional }] },
{ type: ErrorStateMatcher }
]; };
__decorate([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], MatContenteditableDirective.prototype, "value", null);
__decorate([
HostBinding(),
__metadata("design:type", Object)
], MatContenteditableDirective.prototype, "id", void 0);
__decorate([
Input(),
__metadata("design:type", Object),
__metadata("design:paramtypes", [Object])
], MatContenteditableDirective.prototype, "placeholder", null);
__decorate([
Input(),
__metadata("design:type", Array)
], MatContenteditableDirective.prototype, "contentEmpty", void 0);
__decorate([
Input(),
__metadata("design:type", Object),
__metadata("design:paramtypes", [Object])
], MatContenteditableDirective.prototype, "required", null);
__decorate([
Input(),
__metadata("design:type", Object),
__metadata("design:paramtypes", [Object])
], MatContenteditableDirective.prototype, "disabled", null);
__decorate([
HostBinding('attr.aria-invalid'),
__metadata("design:type", Boolean)
], MatContenteditableDirective.prototype, "errorState", void 0);
__decorate([
Input(),
__metadata("design:type", ErrorStateMatcher)
], MatContenteditableDirective.prototype, "errorStateMatcher", void 0);
__decorate([
HostBinding('attr.aria-describedby'),
__metadata("design:type", Object)
], MatContenteditableDirective.prototype, "describedBy", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], MatContenteditableDirective.prototype, "propValueAccessor", void 0);
__decorate([
HostListener('input'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], MatContenteditableDirective.prototype, "callOnChange", null);
__decorate([
HostListener('focus'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], MatContenteditableDirective.prototype, "callOnFocused", null);
__decorate([
HostListener('blur'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], MatContenteditableDirective.prototype, "callOnTouched", null);
MatContenteditableDirective = MatContenteditableDirective_1 = __decorate([
Directive({
selector: '[contenteditable]',
providers: [
{ provide: MatFormFieldControl, useExisting: MatContenteditableDirective_1 },
]
}),
__param(2, Optional()), __param(2, Self()),
__param(3, Optional()),
__param(4, Optional()),
__metadata("design:paramtypes", [ElementRef,
Renderer2,
NgControl,
NgForm,
FormGroupDirective,
ErrorStateMatcher])
], MatContenteditableDirective);
return MatContenteditableDirective;
}(_MatInputMixinBase));
var MatCkeditorDirective = /** @class */ (function (_super) {
__extends(MatCkeditorDirective, _super);
function MatCkeditorDirective(editor, viewRef, ngControl, _parentForm, _parentFormGroup, _defaultErrorStateMatcher) {
var _this = _super.call(this, _defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl) || this;
_this.editor = editor;
_this.viewRef = viewRef;
_this.ngControl = ngControl;
_this.stateChanges = new Subject();
_this.id = "mat-input-" + MatCkeditorDirective_1.nextId++;
// Need support from Ckeditor
_this.placeholder = '';
_this.contentEmpty = ['<br>', '<p> </p>'];
_this.focused = false;
_this.required = false;
_this.controlType = 'mat-input';
_this.describedBy = '';
return _this;
}
MatCkeditorDirective_1 = MatCkeditorDirective;
Object.defineProperty(MatCkeditorDirective.prototype, "value", {
get: function () {
return !!this.editor.editorInstance && this.editor.editorInstance.getData();
},
set: function (value) {
if (value !== this.value) {
this.editor.data = value;
this.stateChanges.next();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatCkeditorDirective.prototype, "empty", {
get: function () {
return !this.value || this.contentEmpty.includes(this.value);
},
enumerable: true,
configurable: true
});
Object.defineProperty(MatCkeditorDirective.prototype, "shouldLabelFloat", {
get: function () { return this.focused || !this.empty; },
enumerable: true,
configurable: true
});
Object.defineProperty(MatCkeditorDirective.prototype, "disabled", {
get: function () {
return this.editor.disabled;
},
set: function (isDisabled) {
this.editor.setDisabledState(isDisabled);
this.stateChanges.next();
},
enumerable: true,
configurable: true
});
MatCkeditorDirective.prototype.ngOnInit = function () {
var _this = this;
this.editor.blur.subscribe(function () {
_this.focused = false;
_this.stateChanges.next();
});
this.editor.focus.subscribe(function () {
_this.focused = true;
_this.stateChanges.next();
});
};
MatCkeditorDirective.prototype.ngDoCheck = function () {
if (this.ngControl) {
// We need to re-evaluate this on every change detection cycle, because there are some
// error triggers that we can't subscribe to (e.g. parent form submissions). This means
// that whatever logic is in here has to be super lean or we risk destroying the performance.
this.updateErrorState();
}
};
MatCkeditorDirective.prototype.setDescribedByIds = function (ids) {
this.describedBy = ids.join(' ');
};
MatCkeditorDirective.prototype.onContainerClick = function () {
if (this.editor.editorInstance) {
this.editor.editorInstance.editing.view.focus();
this.stateChanges.next();
}
};
var MatCkeditorDirective_1;
/**
* Implemented as part of MatFormFieldControl.
* See https://material.angular.io/guide/creating-a-custom-form-field-control
*/
MatCkeditorDirective.nextId = 0;
MatCkeditorDirective.ctorParameters = function () { return [
{ type: CKEditorComponent, decorators: [{ type: Host }, { type: Self }, { type: Optional }] },
{ type: ViewContainerRef },
{ type: NgControl, decorators: [{ type: Optional }, { type: Self }] },
{ type: NgForm, decorators: [{ type: Optional }] },
{ type: FormGroupDirective, decorators: [{ type: Optional }] },
{ type: ErrorStateMatcher }
]; };
__decorate([
Input(),
__metadata("design:type", String),
__metadata("design:paramtypes", [String])
], MatCkeditorDirective.prototype, "value", null);
__decorate([
HostBinding(),
__metadata("design:type", Object)
], MatCkeditorDirective.prototype, "id", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], MatCkeditorDirective.prototype, "placeholder", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], MatCkeditorDirective.prototype, "contentEmpty", void 0);
__decorate([
Input(),
__metadata("design:type", Object)
], MatCkeditorDirective.prototype, "required", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean),
__metadata("design:paramtypes", [Boolean])
], MatCkeditorDirective.prototype, "disabled", null);
__decorate([
HostBinding('attr.aria-invalid'),
__metadata("design:type", Boolean)
], MatCkeditorDirective.prototype, "errorState", void 0);
__decorate([
Input(),
__metadata("design:type", ErrorStateMatcher)
], MatCkeditorDirective.prototype, "errorStateMatcher", void 0);
__decorate([
HostBinding('attr.aria-describedby'),
__metadata("design:type", Object)
], MatCkeditorDirective.prototype, "describedBy", void 0);
MatCkeditorDirective = MatCkeditorDirective_1 = __decorate([
Directive({
selector: '[matCkeditor]',
providers: [
{ provide: MatFormFieldControl, useExisting: MatCkeditorDirective_1 },
]
}),
__param(0, Host()), __param(0, Self()), __param(0, Optional()),
__param(2, Optional()), __param(2, Self()),
__param(3, Optional()),
__param(4, Optional()),
__metadata("design:paramtypes", [CKEditorComponent,
ViewContainerRef,
NgControl,
NgForm,
FormGroupDirective,
ErrorStateMatcher])
], MatCkeditorDirective);
return MatCkeditorDirective;
}(_MatInputMixinBase));
var MatCkeditorBalloonDirective = /** @class */ (function (_super) {
__extends(MatCkeditorBalloonDirective, _super);
function MatCkeditorBalloonDirective() {
return _super !== null && _super.apply(this, arguments) || this;
}
MatCkeditorBalloonDirective_1 = MatCkeditorBalloonDirective;
Object.defineProperty(MatCkeditorBalloonDirective.prototype, "toolbar", {
set: function (show) {
if (this.editor && show !== this.toolbarOpen) {
var balloon = this.editor.editorInstance.plugins.get('BalloonToolbar');
if (show) {
this.showToolbar(balloon);
}
else {
balloon.hide();
this.toolbarOpen = false;
}
}
},
enumerable: true,
configurable: true
});
MatCkeditorBalloonDirective.prototype.ngOnInit = function () {
var _this = this;
_super.prototype.ngOnInit.call(this);
this.editor.ready.subscribe(function (editor) {
var balloon = editor.plugins.get('BalloonToolbar');
balloon.stopListening(editor.model.document.selection, 'change:range');
balloon.stopListening(balloon, '_selectionChangeDebounced');
});
this.editor.focus.subscribe(function () {
if (_this.toolbarOpen) {
var balloon = _this.editor.editorInstance.plugins.get('BalloonToolbar');
_this.showToolbar(balloon);
}
});
};
MatCkeditorBalloonDirective.prototype.showToolbar = function (balloon) {
if (!balloon._balloon.hasView(balloon.toolbarView)) {
balloon.listenTo(this.editor.editorInstance.ui, 'update', function () {
balloon._balloon.updatePosition(balloon._getBalloonPositionData());
});
balloon._balloon.add({
view: balloon.toolbarView,
position: balloon._getBalloonPositionData(),
balloonClassName: 'ck-toolbar-container'
});
this.toolbarOpen = true;
}
};
var MatCkeditorBalloonDirective_1;
__decorate([
Input(),
__metadata("design:type", Boolean),
__metadata("design:paramtypes", [Boolean])
], MatCkeditorBalloonDirective.prototype, "toolbar", null);
MatCkeditorBalloonDirective = MatCkeditorBalloonDirective_1 = __decorate([
Directive({
selector: '[matCkeditorBalloon]',
providers: [
{ provide: MatFormFieldControl, useExisting: MatCkeditorBalloonDirective_1 },
]
})
], MatCkeditorBalloonDirective);
return MatCkeditorBalloonDirective;
}(MatCkeditorDirective));
var FormFieldSizerDirective = /** @class */ (function () {
function FormFieldSizerDirective(renderer, elementRef) {
this.renderer = renderer;
this.elementRef = elementRef;
}
FormFieldSizerDirective.prototype.ngAfterContentInit = function () {
this.updateSize();
};
FormFieldSizerDirective.prototype.updateSize = function () {
var _this = this;
var infix = this.getElement('mat-form-field-infix');
this.renderer.removeStyle(infix, 'min-height');
setTimeout(function () {
var wrapper = _this.getElement('mat-form-field-wrapper');
var offset = _this.elementRef.nativeElement.offsetHeight -
wrapper.offsetHeight -
parseFloat(getComputedStyle(wrapper).marginTop) -
parseFloat(getComputedStyle(wrapper).marginBottom) +
parseFloat(getComputedStyle(infix).height);
_this.renderer.setStyle(infix, 'min-height', offset + "px");
});
};
FormFieldSizerDirective.prototype.getElement = function (name) {
return this.elementRef.nativeElement.getElementsByClassName(name).item(0);
};
FormFieldSizerDirective.ctorParameters = function () { return [
{ type: Renderer2 },
{ type: ElementRef }
]; };
FormFieldSizerDirective = __decorate([
Directive({
selector: '[formFieldSizer]'
}),
__metadata("design:paramtypes", [Renderer2,
ElementRef])
], FormFieldSizerDirective);
return FormFieldSizerDirective;
}());
var MatContenteditableModule = /** @class */ (function () {
function MatContenteditableModule() {
}
MatContenteditableModule = __decorate([
NgModule({
imports: [],
declarations: [MatContenteditableDirective, FormFieldSizerDirective],
exports: [MatContenteditableDirective, FormFieldSizerDirective],
})
], MatContenteditableModule);
return MatContenteditableModule;
}());
var MatCkeditorModule = /** @class */ (function () {
function MatCkeditorModule() {
}
MatCkeditorModule = __decorate([
NgModule({
imports: [],
declarations: [MatCkeditorDirective, MatCkeditorBalloonDirective],
exports: [MatCkeditorDirective, MatCkeditorBalloonDirective],
})
], MatCkeditorModule);
return MatCkeditorModule;
}());
function getText(html) {
if (html) {
var element = document.createElement('span');
element.innerHTML = html;
return element.textContent.replace(/\s/g, '');
}
return '';
}
/**
* @description
* Provides a set of built-in validators that can be used by form controls.
*
* A validator is a function that processes a `FormControl` or collection of
* controls and returns an error map or null. A null map means that validation has passed.
*
* @see [Form Validation](/guide/form-validation)
*
* @publicApi
*/
var HtmlValidators = /** @class */ (function () {
function HtmlValidators() {
}
/**
* @description
* Validator that requires the control have a non-empty value.
*
* @usageNotes
*
* ### Validate that the field is non-empty
*
* ```typescript
* const control = new FormControl('', Validators.required);
*
* console.log(control.errors); // {required: true}
* ```
*
* @returns An error map with the `required` property
* if the validation check fails, otherwise `null`.
*
*/
HtmlValidators.required = function (control) {
var text = getText(control.value);
return text ? null : { 'required': true };
};
/**
* @description
* Validator that requires the length of the control's value to be greater than or equal
* to the provided minimum length. This validator is also provided by default if you use the
* the HTML5 `minlength` attribute.
*
* @usageNotes
*
* ### Validate that the field has a minimum of 3 characters
*
* ```typescript
* const control = new FormControl('ng', Validators.minLength(3));
*
* console.log(control.errors); // {minlength: {requiredLength: 3, actualLength: 2}}
* ```
*
* ```html
* <input minlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `minlength` if the validation check fails, otherwise `null`.
*/
HtmlValidators.minLength = function (minLength) {
var fn = function (control) {
var text = getText(control.value);
if (text) {
return text.length < minLength ?
{ 'minlength': { 'requiredLength': minLength, 'actualLength': text.length } } :
null;
}
return null; // don't validate empty values to allow optional controls
};
return fn;
};
/**
* @description
* Validator that requires the length of the control's value to be less than or equal
* to the provided maximum length. This validator is also provided by default if you use the
* the HTML5 `maxlength` attribute.
*
* @usageNotes
*
* ### Validate that the field has maximum of 5 characters
*
* ```typescript
* const control = new FormControl('Angular', Validators.maxLength(5));
*
* console.log(control.errors); // {maxlength: {requiredLength: 5, actualLength: 7}}
* ```
*
* ```html
* <input maxlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `maxlength` property if the validation check fails, otherwise `null`.
*/
HtmlValidators.maxLength = function (maxLength) {
var fn = function (control) {
var text = getText(control.value);
return text.length > maxLength ?
{ 'maxlength': { 'requiredLength': maxLength, 'actualLength': text.length } } :
null;
};
return fn;
};
return HtmlValidators;
}());
/*
* Public API Surface of mat-contenteditable
*/
/**
* Generated bundle index. Do not edit.
*/
export { FormFieldSizerDirective, HtmlValidators, MatCkeditorBalloonDirective, MatCkeditorDirective, MatCkeditorModule, MatContenteditableDirective, MatContenteditableModule, _MatInputMixinBase };
//# sourceMappingURL=mat-contenteditable.js.map