@ckeditor/ckeditor5-image
Version:
Image feature for CKEditor 5.
169 lines (168 loc) • 6.12 kB
JavaScript
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module image/imageresize/imagecustomresizeui
*/
import { Plugin } from 'ckeditor5/src/core.js';
import { ContextualBalloon, clickOutsideHandler, CssTransitionDisablerMixin } from 'ckeditor5/src/ui.js';
import { getBalloonPositionData } from '../image/ui/utils.js';
import { getSelectedImageWidthInUnits } from './utils/getselectedimagewidthinunits.js';
import ImageCustomResizeFormView from './ui/imagecustomresizeformview.js';
import { getSelectedImagePossibleResizeRange } from './utils/getselectedimagepossibleresizerange.js';
/**
* The custom resize image UI plugin.
*
* The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}.
*/
export default class ImageCustomResizeUI extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ContextualBalloon];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageCustomResizeUI';
}
/**
* @inheritDoc
*/
destroy() {
super.destroy();
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
if (this._form) {
this._form.destroy();
}
}
/**
* Creates the {@link module:image/imageresize/ui/imagecustomresizeformview~ImageCustomResizeFormView}
* form.
*/
_createForm(unit) {
const editor = this.editor;
this._balloon = this.editor.plugins.get('ContextualBalloon');
this._form = new (CssTransitionDisablerMixin(ImageCustomResizeFormView))(editor.locale, unit, getFormValidators(editor));
// Render the form so its #element is available for clickOutsideHandler.
this._form.render();
this.listenTo(this._form, 'submit', () => {
if (this._form.isValid()) {
editor.execute('resizeImage', {
width: this._form.sizeWithUnits
});
this._hideForm(true);
}
});
// Update balloon position when form error label is added .
this.listenTo(this._form.labeledInput, 'change:errorText', () => {
editor.ui.update();
});
this.listenTo(this._form, 'cancel', () => {
this._hideForm(true);
});
// Close the form on Esc key press.
this._form.keystrokes.set('Esc', (data, cancel) => {
this._hideForm(true);
cancel();
});
// Close on click outside of balloon panel element.
clickOutsideHandler({
emitter: this._form,
activator: () => this._isVisible,
contextElements: () => [this._balloon.view.element],
callback: () => this._hideForm()
});
}
/**
* Shows the {@link #_form} in the {@link #_balloon}.
*
* @internal
*/
_showForm(unit) {
if (this._isVisible) {
return;
}
if (!this._form) {
this._createForm(unit);
}
const editor = this.editor;
const labeledInput = this._form.labeledInput;
this._form.disableCssTransitions();
this._form.resetFormStatus();
if (!this._isInBalloon) {
this._balloon.add({
view: this._form,
position: getBalloonPositionData(editor)
});
}
// Make sure that each time the panel shows up, the field remains in sync with the value of
// the command. If the user typed in the input, then canceled the balloon (`labeledInput#value`
// stays unaltered) and re-opened it without changing the value of the command, they would see the
// old value instead of the actual value of the command.
const currentParsedWidth = getSelectedImageWidthInUnits(editor, unit);
const initialInputValue = currentParsedWidth ? currentParsedWidth.value.toFixed(1) : '';
const possibleRange = getSelectedImagePossibleResizeRange(editor, unit);
labeledInput.fieldView.value = labeledInput.fieldView.element.value = initialInputValue;
if (possibleRange) {
Object.assign(labeledInput.fieldView, {
min: possibleRange.lower.toFixed(1),
max: Math.ceil(possibleRange.upper).toFixed(1)
});
}
this._form.labeledInput.fieldView.select();
this._form.enableCssTransitions();
}
/**
* Removes the {@link #_form} from the {@link #_balloon}.
*
* @param focusEditable Controls whether the editing view is focused afterwards.
*/
_hideForm(focusEditable = false) {
if (!this._isInBalloon) {
return;
}
// Blur the input element before removing it from DOM to prevent issues in some browsers.
// See https://github.com/ckeditor/ckeditor5/issues/1501.
if (this._form.focusTracker.isFocused) {
this._form.saveButtonView.focus();
}
this._balloon.remove(this._form);
if (focusEditable) {
this.editor.editing.view.focus();
}
}
/**
* Returns `true` when the {@link #_form} is the visible view in the {@link #_balloon}.
*/
get _isVisible() {
return !!this._balloon && this._balloon.visibleView === this._form;
}
/**
* Returns `true` when the {@link #_form} is in the {@link #_balloon}.
*/
get _isInBalloon() {
return !!this._balloon && this._balloon.hasView(this._form);
}
}
/**
* Returns image resize form validation callbacks.
*
* @param editor Editor instance.
*/
function getFormValidators(editor) {
const t = editor.t;
return [
form => {
if (form.rawSize.trim() === '') {
return t('The value must not be empty.');
}
if (form.parsedSize === null) {
return t('The value should be a plain number.');
}
}
];
}