@ckeditor/ckeditor5-engine
Version:
The editing engine of CKEditor 5 – the best browser-based rich text editor.
94 lines (93 loc) • 3.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 engine/model/liveposition
*/
import { ModelPosition } from './position.js';
import { CKEditorError, EmitterMixin } from '@ckeditor/ckeditor5-utils';
/**
* `ModelLivePosition` is a type of {@link module:engine/model/position~ModelPosition Position}
* that updates itself as {@link module:engine/model/document~ModelDocument document}
* is changed through operations. It may be used as a bookmark.
*
* **Note:** Contrary to {@link module:engine/model/position~ModelPosition}, `ModelLivePosition` works only in roots that are
* {@link module:engine/model/rootelement~ModelRootElement}.
* If {@link module:engine/model/documentfragment~ModelDocumentFragment} is passed, error will be thrown.
*
* **Note:** Be very careful when dealing with `ModelLivePosition`. Each `ModelLivePosition` instance bind events that might
* have to be unbound.
* Use {@link module:engine/model/liveposition~ModelLivePosition#detach} whenever you don't need `ModelLivePosition` anymore.
*/
export class ModelLivePosition extends /* #__PURE__ */ EmitterMixin(ModelPosition) {
/**
* Creates a live position.
*
* @see module:engine/model/position~ModelPosition
*/
constructor(root, path, stickiness = 'toNone') {
super(root, path, stickiness);
if (!this.root.is('rootElement')) {
/**
* LivePosition's root has to be an instance of ModelRootElement.
*
* @error model-liveposition-root-not-rootelement
*/
throw new CKEditorError('model-liveposition-root-not-rootelement', root);
}
bindWithDocument.call(this);
}
/**
* Unbinds all events previously bound by `ModelLivePosition`. Use it whenever you don't need `ModelLivePosition` instance
* anymore (i.e. when leaving scope in which it was declared or before re-assigning variable that was
* referring to it).
*/
detach() {
this.stopListening();
}
/**
* Creates a {@link module:engine/model/position~ModelPosition position instance}, which is equal to this live position.
*/
toPosition() {
return new ModelPosition(this.root, this.path.slice(), this.stickiness);
}
/**
* Creates a `ModelLivePosition` instance that is equal to position.
*/
static fromPosition(position, stickiness) {
return new this(position.root, position.path.slice(), stickiness ? stickiness : position.stickiness);
}
}
// The magic of type inference using `is` method is centralized in `TypeCheckable` class.
// Proper overload would interfere with that.
ModelLivePosition.prototype.is = function (type) {
return type === 'livePosition' || type === 'model:livePosition' ||
// From super.is(). This is highly utilised method and cannot call super. See ckeditor/ckeditor5#6529.
type == 'position' || type === 'model:position';
};
/**
* Binds this `ModelLivePosition` to the {@link module:engine/model/document~ModelDocument document} that owns
* this position's {@link module:engine/model/position~ModelPosition#root root}.
*/
function bindWithDocument() {
this.listenTo(this.root.document.model, 'applyOperation', (event, args) => {
const operation = args[0];
if (!operation.isDocumentOperation) {
return;
}
transform.call(this, operation);
}, { priority: 'low' });
}
/**
* Updates this position accordingly to the updates applied to the model. Bases on change events.
*/
function transform(operation) {
const result = this.getTransformedByOperation(operation);
if (!this.isEqual(result)) {
const oldPosition = this.toPosition();
this.path = result.path;
this.root = result.root;
this.fire('change', oldPosition);
}
}