@ckeditor/ckeditor5-ui
Version:
The UI framework and standard UI library of CKEditor 5.
131 lines (130 loc) • 4.98 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module ui/editableui/editableuiview
*/
import View from '../view.js';
/**
* The editable UI view class.
*/
export default class EditableUIView extends View {
/**
* The name of the editable UI view.
*/
name = null;
/**
* The editing view instance the editable is related to. Editable uses the editing
* view to dynamically modify its certain DOM attributes after {@link #render rendering}.
*
* **Note**: The DOM attributes are performed by the editing view and not UI
* {@link module:ui/view~View#bindTemplate template bindings} because once rendered,
* the editable DOM element must remain under the full control of the engine to work properly.
*/
_editingView;
/**
* The element which is the main editable element (usually the one with `contentEditable="true"`).
*/
_editableElement;
/**
* Whether an external {@link #_editableElement} was passed into the constructor, which also means
* the view will not render its {@link #template}.
*/
_hasExternalElement;
/**
* Creates an instance of EditableUIView class.
*
* @param locale The locale instance.
* @param editingView The editing view instance the editable is related to.
* @param editableElement The editable element. If not specified, this view
* should create it. Otherwise, the existing element should be used.
*/
constructor(locale, editingView, editableElement) {
super(locale);
this.setTemplate({
tag: 'div',
attributes: {
class: [
'ck',
'ck-content',
'ck-editor__editable',
'ck-rounded-corners'
],
lang: locale.contentLanguage,
dir: locale.contentLanguageDirection
}
});
this.set('isFocused', false);
this._editableElement = editableElement;
this._hasExternalElement = !!this._editableElement;
this._editingView = editingView;
}
/**
* Renders the view by either applying the {@link #template} to the existing
* {@link module:ui/editableui/editableuiview~EditableUIView#_editableElement} or assigning {@link #element}
* as {@link module:ui/editableui/editableuiview~EditableUIView#_editableElement}.
*/
render() {
super.render();
if (this._hasExternalElement) {
this.template.apply(this.element = this._editableElement);
}
else {
this._editableElement = this.element;
}
this.on('change:isFocused', () => this._updateIsFocusedClasses());
this._updateIsFocusedClasses();
}
/**
* @inheritDoc
*/
destroy() {
if (this._hasExternalElement) {
this.template.revert(this._editableElement);
}
super.destroy();
}
/**
* Whether an external {@link #_editableElement} was passed into the constructor, which also means
* the view will not render its {@link #template}.
*/
get hasExternalElement() {
return this._hasExternalElement;
}
/**
* Updates the `ck-focused` and `ck-blurred` CSS classes on the {@link #element} according to
* the {@link #isFocused} property value using the {@link #_editingView editing view} API.
*/
_updateIsFocusedClasses() {
const editingView = this._editingView;
if (editingView.isRenderingInProgress) {
updateAfterRender(this);
}
else {
update(this);
}
function update(view) {
editingView.change(writer => {
const viewRoot = editingView.document.getRoot(view.name);
writer.addClass(view.isFocused ? 'ck-focused' : 'ck-blurred', viewRoot);
writer.removeClass(view.isFocused ? 'ck-blurred' : 'ck-focused', viewRoot);
});
}
// In a case of a multi-root editor, a callback will be attached more than once (one callback for each root).
// While executing one callback the `isRenderingInProgress` observable is changing what causes executing another
// callback and render is called inside the already pending render.
// We need to be sure that callback is executed only when the value has changed from `true` to `false`.
// See https://github.com/ckeditor/ckeditor5/issues/1676.
function updateAfterRender(view) {
editingView.once('change:isRenderingInProgress', (evt, name, value) => {
if (!value) {
update(view);
}
else {
updateAfterRender(view);
}
});
}
}
}