@ckeditor/ckeditor5-engine
Version:
The editing engine of CKEditor 5 – the best browser-based rich text editor.
96 lines (95 loc) • 4.21 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/view/observer/fakeselectionobserver
*/
import { Observer } from './observer.js';
import { ViewSelection } from '../selection.js';
import { keyCodes } from '@ckeditor/ckeditor5-utils';
import { debounce } from 'es-toolkit/compat';
/**
* Fake selection observer class. If view selection is fake it is placed in dummy DOM container. This observer listens
* on {@link module:engine/view/document~ViewDocument#event:keydown keydown} events and handles moving
* fake view selection to the correct place if arrow keys are pressed.
* Fires {@link module:engine/view/document~ViewDocument#event:selectionChange selectionChange event} simulating natural behaviour of
* {@link module:engine/view/observer/selectionobserver~SelectionObserver SelectionObserver}.
*/
export class FakeSelectionObserver extends Observer {
/**
* Fires debounced event `selectionChangeDone`. It uses `es-toolkit#debounce` method to delay function call.
*/
_fireSelectionChangeDoneDebounced;
/**
* Creates new FakeSelectionObserver instance.
*/
constructor(view) {
super(view);
this._fireSelectionChangeDoneDebounced = debounce(data => {
this.document.fire('selectionChangeDone', data);
}, 200);
}
/**
* @inheritDoc
*/
observe() {
const document = this.document;
document.on('arrowKey', (eventInfo, data) => {
const selection = document.selection;
if (selection.isFake && this.isEnabled) {
// Prevents default key down handling - no selection change will occur.
data.preventDefault();
}
}, { context: '$capture' });
document.on('arrowKey', (eventInfo, data) => {
const selection = document.selection;
if (selection.isFake && this.isEnabled) {
this._handleSelectionMove(data.keyCode);
}
}, { priority: 'lowest' });
}
/**
* @inheritDoc
*/
stopObserving() { }
/**
* @inheritDoc
*/
destroy() {
super.destroy();
this._fireSelectionChangeDoneDebounced.cancel();
}
/**
* Handles collapsing view selection according to given key code. If left or up key is provided - new selection will be
* collapsed to left. If right or down key is pressed - new selection will be collapsed to right.
*
* This method fires {@link module:engine/view/document~ViewDocument#event:selectionChange} and
* {@link module:engine/view/document~ViewDocument#event:selectionChangeDone} events imitating behaviour of
* {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*/
_handleSelectionMove(keyCode) {
const selection = this.document.selection;
const newSelection = new ViewSelection(selection.getRanges(), { backward: selection.isBackward, fake: false });
// Left or up arrow pressed - move selection to start.
if (keyCode == keyCodes.arrowleft || keyCode == keyCodes.arrowup) {
newSelection.setTo(newSelection.getFirstPosition());
}
// Right or down arrow pressed - move selection to end.
if (keyCode == keyCodes.arrowright || keyCode == keyCodes.arrowdown) {
newSelection.setTo(newSelection.getLastPosition());
}
const data = {
oldSelection: selection,
newSelection,
domSelection: null
};
// Fire dummy selection change event.
this.document.fire('selectionChange', data);
// Call` #_fireSelectionChangeDoneDebounced` every time when `selectionChange` event is fired.
// This function is debounced what means that `selectionChangeDone` event will be fired only when
// defined int the function time will elapse since the last time the function was called.
// So `selectionChangeDone` will be fired when selection will stop changing.
this._fireSelectionChangeDoneDebounced(data);
}
}