UNPKG

@ckeditor/ckeditor5-image

Version:

Image feature for CKEditor 5.

174 lines (173 loc) 6.45 kB
/** * @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/imagetextalternative/imagetextalternativeui */ import { Plugin, icons } from 'ckeditor5/src/core.js'; import { ButtonView, ContextualBalloon, clickOutsideHandler, CssTransitionDisablerMixin } from 'ckeditor5/src/ui.js'; import TextAlternativeFormView from './ui/textalternativeformview.js'; import { repositionContextualBalloon, getBalloonPositionData } from '../image/ui/utils.js'; /** * The image text alternative UI plugin. * * The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}. */ export default class ImageTextAlternativeUI extends Plugin { /** * @inheritDoc */ static get requires() { return [ContextualBalloon]; } /** * @inheritDoc */ static get pluginName() { return 'ImageTextAlternativeUI'; } /** * @inheritDoc */ init() { this._createButton(); } /** * @inheritDoc */ destroy() { super.destroy(); // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341). if (this._form) { this._form.destroy(); } } /** * Creates a button showing the balloon panel for changing the image text alternative and * registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}. */ _createButton() { const editor = this.editor; const t = editor.t; editor.ui.componentFactory.add('imageTextAlternative', locale => { const command = editor.commands.get('imageTextAlternative'); const view = new ButtonView(locale); view.set({ label: t('Change image text alternative'), icon: icons.textAlternative, tooltip: true }); view.bind('isEnabled').to(command, 'isEnabled'); view.bind('isOn').to(command, 'value', value => !!value); this.listenTo(view, 'execute', () => { this._showForm(); }); return view; }); } /** * Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView} * form. */ _createForm() { const editor = this.editor; const view = editor.editing.view; const viewDocument = view.document; const imageUtils = editor.plugins.get('ImageUtils'); this._balloon = this.editor.plugins.get('ContextualBalloon'); this._form = new (CssTransitionDisablerMixin(TextAlternativeFormView))(editor.locale); // Render the form so its #element is available for clickOutsideHandler. this._form.render(); this.listenTo(this._form, 'submit', () => { editor.execute('imageTextAlternative', { newValue: this._form.labeledInput.fieldView.element.value }); this._hideForm(true); }); 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(); }); // Reposition the balloon or hide the form if an image widget is no longer selected. this.listenTo(editor.ui, 'update', () => { if (!imageUtils.getClosestSelectedImageWidget(viewDocument.selection)) { this._hideForm(true); } else if (this._isVisible) { repositionContextualBalloon(editor); } }); // 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}. */ _showForm() { if (this._isVisible) { return; } if (!this._form) { this._createForm(); } const editor = this.editor; const command = editor.commands.get('imageTextAlternative'); const labeledInput = this._form.labeledInput; this._form.disableCssTransitions(); 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. // https://github.com/ckeditor/ckeditor5-image/issues/114 labeledInput.fieldView.value = labeledInput.fieldView.element.value = command.value || ''; 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); } }