@ckeditor/ckeditor5-widget
Version:
Widget API for CKEditor 5.
232 lines (231 loc) • 12.4 kB
TypeScript
/**
* @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 widget/widgettypearound/widgettypearound
*/
import { Plugin } from '@ckeditor/ckeditor5-core';
import { Enter } from '@ckeditor/ckeditor5-enter';
import { Delete } from '@ckeditor/ckeditor5-typing';
import '../../theme/widgettypearound.css';
/**
* A plugin that allows users to type around widgets where normally it is impossible to place the caret due
* to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
* the first (or last) child of its parent or between two block widgets.
*
* This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and injects the user interface
* with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
* user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
* in it so that users can type (or insert content, paste, etc.) straight away.
*/
export declare class WidgetTypeAround extends Plugin {
/**
* A reference to the model widget element that has the fake caret active
* on either side of it. It is later used to remove CSS classes associated with the fake caret
* when the widget no longer needs it.
*/
private _currentFakeCaretModelElement;
/**
* @inheritDoc
*/
static get pluginName(): "WidgetTypeAround";
/**
* @inheritDoc
*/
static get isOfficialPlugin(): true;
/**
* @inheritDoc
*/
static get requires(): readonly [typeof Enter, typeof Delete];
/**
* @inheritDoc
*/
init(): void;
/**
* @inheritDoc
*/
destroy(): void;
/**
* Inserts a new paragraph next to a widget element with the selection anchored in it.
*
* **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
* the viewport to the selection in the inserted paragraph.
*
* @param widgetModelElement The model widget element next to which a paragraph is inserted.
* @param position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
*/
private _insertParagraph;
/**
* A wrapper for the {@link module:utils/emittermixin~Emitter#listenTo} method that executes the callbacks only
* when the plugin {@link #isEnabled is enabled}.
*
* @param emitter The object that fires the event.
* @param event The name of the event.
* @param callback The function to be called on event.
* @param options Additional options.
*/
private _listenToIfEnabled;
/**
* Similar to {@link #_insertParagraph}, this method inserts a paragraph except that it
* does not expect a position. Instead, it performs the insertion next to a selected widget
* according to the `widget-type-around` model selection attribute value (fake caret position).
*
* Because this method requires the `widget-type-around` attribute to be set,
* the insertion can only happen when the widget's fake caret is active (e.g. activated
* using the keyboard).
*
* @returns Returns `true` when the paragraph was inserted (the attribute was present) and `false` otherwise.
*/
private _insertParagraphAccordingToFakeCaretPosition;
/**
* Creates a listener in the editing conversion pipeline that injects the widget type around
* UI into every single widget instance created in the editor.
*
* The UI is delivered as a {@link module:engine/view/uielement~ViewUIElement}
* wrapper which renders DOM buttons that users can use to insert paragraphs.
*/
private _enableTypeAroundUIInjection;
/**
* Brings support for the fake caret that appears when either:
*
* * the selection moves to a widget from a position next to it using arrow keys,
* * the arrow key is pressed when the widget is already selected.
*
* The fake caret lets the user know that they can start typing or just press
* <kbd>Enter</kbd> to insert a paragraph at the position next to a widget as suggested by the fake caret.
*
* The fake caret disappears when the user changes the selection or the editor
* gets blurred.
*
* The whole idea is as follows:
*
* 1. A user does one of the 2 scenarios described at the beginning.
* 2. The "keydown" listener is executed and the decision is made whether to show or hide the fake caret.
* 3. If it should show up, the `widget-type-around` model selection attribute is set indicating
* on which side of the widget it should appear.
* 4. The selection dispatcher reacts to the selection attribute and sets CSS classes responsible for the
* fake caret on the view widget.
* 5. If the fake caret should disappear, the selection attribute is removed and the dispatcher
* does the CSS class clean-up in the view.
* 6. Additionally, `change:range` and `FocusTracker#isFocused` listeners also remove the selection
* attribute (the former also removes widget CSS classes).
*/
private _enableTypeAroundFakeCaretActivationUsingKeyboardArrows;
/**
* A listener executed on each "keydown" in the view document, a part of
* {@link #_enableTypeAroundFakeCaretActivationUsingKeyboardArrows}.
*
* It decides whether the arrow keypress should activate the fake caret or not (also whether it should
* be deactivated).
*
* The fake caret activation is done by setting the `widget-type-around` model selection attribute
* in this listener, and stopping and preventing the event that would normally be handled by the widget
* plugin that is responsible for the regular keyboard navigation near/across all widgets (that
* includes inline widgets, which are ignored by the widget type around plugin).
*/
private _handleArrowKeyPress;
/**
* Handles the keyboard navigation on "keydown" when a widget is currently selected and activates or deactivates
* the fake caret for that widget, depending on the current value of the `widget-type-around` model
* selection attribute and the direction of the pressed arrow key.
*
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
private _handleArrowKeyPressOnSelectedWidget;
/**
* Handles the keyboard navigation on "keydown" when **no** widget is selected but the selection is **directly** next
* to one and upon the fake caret should become active for this widget upon arrow keypress
* (AKA entering/selecting the widget).
*
* **Note**: This code mirrors the implementation from the widget plugin but also adds the selection attribute.
* Unfortunately, there is no safe way to let the widget plugin do the selection part first and then just set the
* selection attribute here in the widget type around plugin. This is why this code must duplicate some from the widget plugin.
*
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
private _handleArrowKeyPressWhenSelectionNextToAWidget;
/**
* Handles the keyboard navigation on "keydown" when a widget is currently selected (together with some other content)
* and the widget is the first or last element in the selection. It activates or deactivates the fake caret for that widget.
*
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
* process the event any further. Returns `false` otherwise.
*/
private _handleArrowKeyPressWhenNonCollapsedSelection;
/**
* Registers a `mousedown` listener for the view document which intercepts events
* coming from the widget type around UI, which happens when a user clicks one of the buttons
* that insert a paragraph next to a widget.
*/
private _enableInsertingParagraphsOnButtonClick;
/**
* Creates the <kbd>Enter</kbd> key listener on the view document that allows the user to insert a paragraph
* near the widget when either:
*
* * The fake caret was first activated using the arrow keys,
* * The entire widget is selected in the model.
*
* In the first case, the new paragraph is inserted according to the `widget-type-around` selection
* attribute (see {@link #_handleArrowKeyPress}).
*
* In the second case, the new paragraph is inserted based on whether a soft (<kbd>Shift</kbd>+<kbd>Enter</kbd>) keystroke
* was pressed or not.
*/
private _enableInsertingParagraphsOnEnterKeypress;
/**
* Similar to the {@link #_enableInsertingParagraphsOnEnterKeypress}, it allows the user
* to insert a paragraph next to a widget when the fake caret was activated using arrow
* keys but it responds to typing instead of <kbd>Enter</kbd>.
*
* Listener enabled by this method will insert a new paragraph according to the `widget-type-around`
* model selection attribute as the user simply starts typing, which creates the impression that the fake caret
* behaves like a real one rendered by the browser (AKA your text appears where the caret was).
*
* **Note**: At the moment this listener creates 2 undo steps: one for the `insertParagraph` command
* and another one for actual typing. It is not a disaster but this may need to be fixed
* sooner or later.
*/
private _enableInsertingParagraphsOnTypingKeystroke;
/**
* It creates a "delete" event listener on the view document to handle cases when the <kbd>Delete</kbd> or <kbd>Backspace</kbd>
* is pressed and the fake caret is currently active.
*
* The fake caret should create an illusion of a real browser caret so that when it appears before or after
* a widget, pressing <kbd>Delete</kbd> or <kbd>Backspace</kbd> should remove a widget or delete the content
* before or after a widget (depending on the content surrounding the widget).
*/
private _enableDeleteIntegration;
/**
* Attaches the {@link module:engine/model/model~Model#event:insertContent} event listener that, for instance, allows the user to paste
* content near a widget when the fake caret is first activated using the arrow keys.
*
* The content is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
*/
private _enableInsertContentIntegration;
/**
* Attaches the {@link module:engine/model/model~Model#event:insertObject} event listener that modifies the
* `options.findOptimalPosition`parameter to position of fake caret in relation to selected element
* to reflect user's intent of desired insertion position.
*
* The object is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
*/
private _enableInsertObjectIntegration;
/**
* Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
* caret is active.
*
* This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
* before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
* plain text pasting.
*/
private _enableDeleteContentIntegration;
}