UNPKG

@ckeditor/ckeditor5-ui

Version:

The UI framework and standard UI library of CKEditor 5.

297 lines (296 loc) • 10.8 kB
/** * @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 ui/dialog/dialog */ import type View from '../view.js'; import { type Editor, Plugin } from '@ckeditor/ckeditor5-core'; import DialogView, { DialogViewPosition } from './dialogview.js'; import type { DialogActionButtonDefinition } from './dialogactionsview.js'; import type { KeystrokeHandlerOptions } from '@ckeditor/ckeditor5-utils'; /** * The dialog controller class. It is used to show and hide the {@link module:ui/dialog/dialogview~DialogView}. */ export default class Dialog extends Plugin { /** * The name of the currently visible dialog view instance. * * @observable */ id: string | null; /** * The currently visible dialog view instance. */ view?: DialogView; /** * The `Dialog` plugin instance which most recently showed the dialog. * * Only one dialog can be visible at once, even if there are many editor instances on the page. * If an editor wants to show a dialog, it should first hide the dialog that is already opened. * But only the `Dialog` instance that showed the dialog is able able to properly hide it. * This is why we need to store it in a globally available space (static property). * This way every `Dialog` plugin in every editor is able to correctly close any open dialog window. */ private static _visibleDialogPlugin; /** * A flag indicating whether the dialog is currently visible. * * @observable */ isOpen: boolean; /** * A configurable callback called when the dialog is hidden. */ private _onHide; /** * @inheritDoc */ static get pluginName(): "Dialog"; /** * @inheritDoc */ static get isOfficialPlugin(): true; /** * @inheritDoc */ constructor(editor: Editor); /** * @inheritDoc */ destroy(): void; /** * Initiates listeners for the `show` and `hide` events emitted by this plugin. * * We could not simply decorate the {@link #show} and {@link #hide} methods to fire events, * because they would be fired in the wrong order &ndash; first would be `show` and then `hide` * (because showing the dialog actually starts with hiding the previously visible one). * Hence, we added private methods {@link #_show} and {@link #_hide} which are called on events * in the desired sequence. */ private _initShowHideListeners; /** * Initiates keystroke handler for toggling the focus between the editor and the dialog view. */ private _initFocusToggler; /** * Provides an integration between the root attaching and detaching and positioning of the view. */ private _initMultiRootIntegration; /** * Displays a dialog window. * * This method requires a {@link ~DialogDefinition} that defines the dialog's content, title, icon, action buttons, etc. * * For example, the following definition will create a dialog with: * * A header consisting of an icon, a title, and a "Close" button (it is added by default). * * A content consisting of a view with a single paragraph. * * A footer consisting of two buttons: "Yes" and "No". * * ```js * // Create the view that will be used as the dialog's content. * const textView = new View( locale ); * * textView.setTemplate( { * tag: 'div', * attributes: { * style: { * padding: 'var(--ck-spacing-large)', * whiteSpace: 'initial', * width: '100%', * maxWidth: '500px' * }, * tabindex: -1 * }, * children: [ * 'Lorem ipsum dolor sit amet...' * ] * } ); * * // Show the dialog. * editor.plugins.get( 'Dialog' ).show( { * id: 'myDialog', * icon: 'myIcon', // This should be an SVG string. * title: 'My dialog', * content: textView, * actionButtons: [ * { * label: t( 'Yes' ), * class: 'ck-button-action', * withText: true, * onExecute: () => dialog.hide() * }, * { * label: t( 'No' ), * withText: true, * onExecute: () => dialog.hide() * } * ] * } ); * ``` * * By specifying the {@link ~DialogDefinition#onShow} and {@link ~DialogDefinition#onHide} callbacks * it is also possible to add callbacks that will be called when the dialog is shown or hidden. * * For example, the callbacks in the following definition: * * Disable the default behavior of the <kbd>Esc</kbd> key. * * Fire a custom event when the dialog gets hidden. * * ```js * editor.plugins.get( 'Dialog' ).show( { * // ... * onShow: dialog => { * dialog.view.on( 'close', ( evt, data ) => { * // Only prevent the event from the "Esc" key - do not affect the other ways of closing the dialog. * if ( data.source === 'escKeyPress' ) { * evt.stop(); * } * } ); * }, * onHide: dialog => { * dialog.fire( 'dialogDestroyed' ); * } * } ); * ``` * * Internally, calling this method: * 1. Hides the currently visible dialog (if any) calling the {@link #hide} method * (fires the {@link ~DialogHideEvent hide event}). * 2. Fires the {@link ~DialogShowEvent show event} which allows for adding callbacks that customize the * behavior of the dialog. * 3. Shows the dialog. */ show(dialogDefinition: DialogDefinition): void; /** * Handles creating the {@link module:ui/dialog/dialogview~DialogView} instance and making it visible. */ private _show; /** * Hides the dialog. This method is decorated to enable interacting on the {@link ~DialogHideEvent hide event}. * * See {@link #show}. */ hide(): void; /** * Destroys the {@link module:ui/dialog/dialogview~DialogView} and cleans up the stored dialog state. */ private _hide; /** * Makes the <body> unscrollable (e.g. when the modal shows up). */ private _lockBodyScroll; /** * Makes the <body> scrollable again (e.g. once the modal hides). */ private _unlockBodyScroll; } /** * The definition that describes a dialog window and its content. Passed to the {@link module:ui/dialog/dialog~Dialog#show} method. */ export interface DialogDefinition { /** * A unique identifier of the dialog. It allows for distinguishing between different dialogs and their visibility. * For instance, when open, the ID of the currently visible dialog is stored in {@link module:ui/dialog/dialog~Dialog#id}. * * The `id` is also passed along the {@link module:ui/dialog/dialog~DialogShowEvent} and {@link module:ui/dialog/dialog~DialogHideEvent} * events. */ id: string; /** * The SVG string of an icon displayed in dialogs's header. Used only when {@link #title} is also set * and the header is displayed. * * See more in {@link module:ui/icon/iconview~IconView#content}. */ icon?: string; /** * A title displayed in the dialogs's header. It also works as an accessible name of the dialog used by assistive technologies. * * When not set, the header is not displayed. Affects {@link #icon} and {@link #hasCloseButton}. */ title?: string; /** * A flag indicating whether the dialog should have a close button in the header. * `true` by default. Works when {@link #title} is also set and the header is displayed. * * **Note**: If you hide the close button, make sure that the dialog can be closed in another way. */ hasCloseButton?: boolean; /** * The content of the dialog. It can be a single {@link module:ui/view~View} or an array of views. */ content?: View | Array<View>; /** * The action buttons displayed in the dialog's footer. */ actionButtons?: Array<DialogActionButtonDefinition>; /** * An additional CSS class set on the outermost (`.ck.ck-dialog`) container element allowing for visual customization. */ className?: string; /** * When set to `true`, the dialog will become a modal, that is, it will block the UI until it is closed. */ isModal?: boolean; /** * Available dialog positions. By default `DialogViewPosition.EDITOR_CENTER` is used for {@link #isModal non-modals} * and `DialogViewPosition.SCREEN_CENTER` for modals. * * {@link module:ui/dialog/dialogview#DialogViewPosition Learn more} about available positions. */ position?: typeof DialogViewPosition[keyof typeof DialogViewPosition]; /** * A callback called when the dialog shows up with a `low` priority. It allows for setting up the dialog's {@link #content}. */ onShow?: (dialog: Dialog) => void; /** * A callback called when the dialog hides with a `low` priority. * It allows for cleaning up (for example, resetting) the dialog's {@link #content}. */ onHide?: (dialog: Dialog) => void; /** * Options that will be passed to the {@link module:utils/keystrokehandler~KeystrokeHandler keystroke handler} of the dialog view. * * See {@link module:utils/keystrokehandler~KeystrokeHandlerOptions KeystrokeHandlerOptions} to learn more about the available options. */ keystrokeHandlerOptions?: KeystrokeHandlerOptions; } /** * An event fired after {@link module:ui/dialog/dialog~Dialog#show} is called. You can use it to customize the behavior * of any dialog. * * ```js * import { DialogViewPosition } from 'ckeditor5/src/ui.js'; * * // ... * * // Changes the position of the "Find and Replace" dialog. * editor.plugins.get( 'Dialog' ).on( 'show:findAndReplace', ( evt, data ) => { * Object.assign( data, { position: DialogViewPosition.EDITOR_BOTTOM_CENTER } ); * }, { priority: 'high' } ); * ``` * * @eventName ~Dialog#show */ export type DialogShowEvent = { name: 'show' | `show:${string}`; args: [dialogDefinition: DialogDefinition]; }; /** * An event fired after {@link module:ui/dialog/dialog~Dialog#hide} is called. You can use it to customize the behavior * of any dialog. * * ```js * // Logs after the "Find and Replace" dialog gets hidden * editor.plugins.get( 'Dialog' ).on( 'hide:findAndReplace', () => { * console.log( 'The "Find and Replace" dialog was hidden.' ); * } ); * ``` * * @eventName ~Dialog#hide */ export type DialogHideEvent = { name: 'hide' | `hide:${string}`; args: []; };