` which\n * wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}\n * it is the editable element itself (as there is no other wrapper). However, in\n * {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not\n * come with a single \"main\" HTML element (its editable element and toolbar are separate).\n *\n * This property can be understood as a shorthand for retrieving the element that a specific editor integration\n * considers to be its main DOM element.\n */\n get element() {\n return null;\n }\n /**\n * Fires the {@link module:ui/editorui/editorui~EditorUI#event:update `update`} event.\n *\n * This method should be called when the editor UI (e.g. positions of its balloons) needs to be updated due to\n * some environmental change which CKEditor 5 is not aware of (e.g. resize of a container in which it is used).\n */\n update() {\n this.fire('update');\n }\n /**\n * Destroys the UI.\n */\n destroy() {\n this.stopListening();\n this.focusTracker.destroy();\n this.tooltipManager.destroy(this.editor);\n this.poweredBy.destroy();\n // Clean–up the references to the CKEditor instance stored in the native editable DOM elements.\n for (const domElement of this._editableElementsMap.values()) {\n domElement.ckeditorInstance = null;\n this.editor.keystrokes.stopListening(domElement);\n }\n this._editableElementsMap = new Map();\n this._focusableToolbarDefinitions = [];\n }\n /**\n * Stores the native DOM editable element used by the editor under a unique name.\n *\n * Also, registers the element in the editor to maintain the accessibility of the UI. When the user is editing text in a focusable\n * editable area, they can use the
Alt +
F10 keystroke to navigate over editor toolbars. See {@link #addToolbar}.\n *\n * @param rootName The unique name of the editable element.\n * @param domElement The native DOM editable element.\n */\n setEditableElement(rootName, domElement) {\n this._editableElementsMap.set(rootName, domElement);\n // Put a reference to the CKEditor instance in the editable native DOM element.\n // It helps 3rd–party software (browser extensions, other libraries) access and recognize\n // CKEditor 5 instances (editing roots) and use their API (there is no global editor\n // instance registry).\n if (!domElement.ckeditorInstance) {\n domElement.ckeditorInstance = this.editor;\n }\n // Register the element, so it becomes available for Alt+F10 and Esc navigation.\n this.focusTracker.add(domElement);\n const setUpKeystrokeHandler = () => {\n // The editing view of the editor is already listening to keystrokes from DOM roots (see: KeyObserver).\n // Do not duplicate listeners.\n if (this.editor.editing.view.getDomRoot(rootName)) {\n return;\n }\n this.editor.keystrokes.listenTo(domElement);\n };\n // For editable elements set by features after EditorUI is ready (e.g. source editing).\n if (this.isReady) {\n setUpKeystrokeHandler();\n }\n // For editable elements set while the editor is being created (e.g. DOM roots).\n else {\n this.once('ready', setUpKeystrokeHandler);\n }\n }\n /**\n * Removes the editable from the editor UI. Removes all handlers added by {@link #setEditableElement}.\n *\n * @param rootName The name of the editable element to remove.\n */\n removeEditableElement(rootName) {\n const domElement = this._editableElementsMap.get(rootName);\n if (!domElement) {\n return;\n }\n this._editableElementsMap.delete(rootName);\n this.editor.keystrokes.stopListening(domElement);\n this.focusTracker.remove(domElement);\n domElement.ckeditorInstance = null;\n }\n /**\n * Returns the editable editor element with the given name or null if editable does not exist.\n *\n * @param rootName The editable name.\n */\n getEditableElement(rootName = 'main') {\n return this._editableElementsMap.get(rootName);\n }\n /**\n * Returns array of names of all editor editable elements.\n */\n getEditableElementsNames() {\n return this._editableElementsMap.keys();\n }\n /**\n * Adds a toolbar to the editor UI. Used primarily to maintain the accessibility of the UI.\n *\n * Focusable toolbars can be accessed (focused) by users by pressing the
Alt +
F10 keystroke.\n * Successive keystroke presses navigate over available toolbars.\n *\n * @param toolbarView A instance of the toolbar to be registered.\n */\n addToolbar(toolbarView, options = {}) {\n if (toolbarView.isRendered) {\n this.focusTracker.add(toolbarView.element);\n this.editor.keystrokes.listenTo(toolbarView.element);\n }\n else {\n toolbarView.once('render', () => {\n this.focusTracker.add(toolbarView.element);\n this.editor.keystrokes.listenTo(toolbarView.element);\n });\n }\n this._focusableToolbarDefinitions.push({ toolbarView, options });\n }\n /**\n * Stores all editable elements used by the editor instance.\n *\n * @deprecated\n */\n get _editableElements() {\n /**\n * The {@link module:ui/editorui/editorui~EditorUI#_editableElements `EditorUI#_editableElements`} property has been\n * deprecated and will be removed in the near future. Please use\n * {@link module:ui/editorui/editorui~EditorUI#setEditableElement `setEditableElement()`} and\n * {@link module:ui/editorui/editorui~EditorUI#getEditableElement `getEditableElement()`} methods instead.\n *\n * @error editor-ui-deprecated-editable-elements\n * @param editorUI Editor UI instance the deprecated property belongs to.\n */\n console.warn('editor-ui-deprecated-editable-elements: ' +\n 'The EditorUI#_editableElements property has been deprecated and will be removed in the near future.', { editorUI: this });\n return this._editableElementsMap;\n }\n /**\n * Returns viewport offsets object:\n *\n * ```js\n * {\n * \ttop: Number,\n * \tright: Number,\n * \tbottom: Number,\n * \tleft: Number\n * }\n * ```\n *\n * Only top property is currently supported.\n */\n _readViewportOffsetFromConfig() {\n const editor = this.editor;\n const viewportOffsetConfig = editor.config.get('ui.viewportOffset');\n if (viewportOffsetConfig) {\n return viewportOffsetConfig;\n }\n // Not present in EditorConfig type, because it's legacy. Hence the `as` expression.\n const legacyOffsetConfig = editor.config.get('toolbar.viewportTopOffset');\n // Fall back to deprecated toolbar config.\n if (legacyOffsetConfig) {\n /**\n * The {@link module:core/editor/editorconfig~EditorConfig#toolbar `EditorConfig#toolbar.viewportTopOffset`}\n * property has been deprecated and will be removed in the near future. Please use\n * {@link module:core/editor/editorconfig~EditorConfig#ui `EditorConfig#ui.viewportOffset`} instead.\n *\n * @error editor-ui-deprecated-viewport-offset-config\n */\n console.warn('editor-ui-deprecated-viewport-offset-config: ' +\n 'The `toolbar.vieportTopOffset` configuration option is deprecated. ' +\n 'It will be removed from future CKEditor versions. Use `ui.viewportOffset.top` instead.');\n return { top: legacyOffsetConfig };\n }\n // More keys to come in the future.\n return { top: 0 };\n }\n /**\n * Starts listening for
Alt +
F10 and
Esc keystrokes in the context of focusable\n * {@link #setEditableElement editable elements} and {@link #addToolbar toolbars}\n * to allow users navigate across the UI.\n */\n _initFocusTracking() {\n const editor = this.editor;\n const editingView = editor.editing.view;\n let lastFocusedForeignElement;\n let candidateDefinitions;\n // Focus the next focusable toolbar on
Alt +
F10.\n editor.keystrokes.set('Alt+F10', (data, cancel) => {\n const focusedElement = this.focusTracker.focusedElement;\n // Focus moved out of a DOM element that\n // * is not a toolbar,\n // * does not belong to the editing view (e.g. source editing).\n if (Array.from(this._editableElementsMap.values()).includes(focusedElement) &&\n !Array.from(editingView.domRoots.values()).includes(focusedElement)) {\n lastFocusedForeignElement = focusedElement;\n }\n const currentFocusedToolbarDefinition = this._getCurrentFocusedToolbarDefinition();\n // * When focusing a toolbar for the first time, set the array of definitions for successive presses of Alt+F10.\n // This ensures, the navigation works always the same and no pair of toolbars takes over\n // (e.g. image and table toolbars when a selected image is inside a cell).\n // * It could be that the focus went to the toolbar by clicking a toolbar item (e.g. a dropdown). In this case,\n // there were no candidates so they must be obtained (#12339).\n if (!currentFocusedToolbarDefinition || !candidateDefinitions) {\n candidateDefinitions = this._getFocusableCandidateToolbarDefinitions();\n }\n // In a single Alt+F10 press, check all candidates but if none were focused, don't go any further.\n // This prevents an infinite loop.\n for (let i = 0; i < candidateDefinitions.length; i++) {\n const candidateDefinition = candidateDefinitions.shift();\n // Put the first definition to the back of the array. This allows circular navigation over all toolbars\n // on successive presses of Alt+F10.\n candidateDefinitions.push(candidateDefinition);\n // Don't focus the same toolbar again. If you did, this would move focus from the nth focused toolbar item back to the\n // first item as per ToolbarView#focus() if the user navigated inside the toolbar.\n if (candidateDefinition !== currentFocusedToolbarDefinition &&\n this._focusFocusableCandidateToolbar(candidateDefinition)) {\n // Clean up after a current visible toolbar when switching to the next one.\n if (currentFocusedToolbarDefinition && currentFocusedToolbarDefinition.options.afterBlur) {\n currentFocusedToolbarDefinition.options.afterBlur();\n }\n break;\n }\n }\n cancel();\n });\n // Blur the focused toolbar on
Esc and bring the focus back to its origin.\n editor.keystrokes.set('Esc', (data, cancel) => {\n const focusedToolbarDef = this._getCurrentFocusedToolbarDefinition();\n if (!focusedToolbarDef) {\n return;\n }\n // Bring focus back to where it came from before focusing the toolbar:\n // 1. If it came from outside the engine view (e.g. source editing), move it there.\n if (lastFocusedForeignElement) {\n lastFocusedForeignElement.focus();\n lastFocusedForeignElement = null;\n }\n // 2. There are two possibilities left:\n // 2.1. It could be that the focus went from an editable element in the view (root or nested).\n // 2.2. It could be the focus went straight to the toolbar before even focusing the editing area.\n // In either case, just focus the view editing. The focus will land where it belongs.\n else {\n editor.editing.view.focus();\n }\n // Clean up after the toolbar if there is anything to do there.\n if (focusedToolbarDef.options.afterBlur) {\n focusedToolbarDef.options.afterBlur();\n }\n cancel();\n });\n }\n /**\n * Returns definitions of toolbars that could potentially be focused, sorted by their importance for the user.\n *\n * Focusable toolbars candidates are either:\n * * already visible,\n * * have `beforeFocus()` set in their {@link module:ui/editorui/editorui~FocusableToolbarDefinition definition} that suggests that\n * they might show up when called. Keep in mind that determining whether a toolbar will show up (and become focusable) is impossible\n * at this stage because it depends on its implementation, that in turn depends on the editing context (selection).\n *\n * **Note**: Contextual toolbars take precedence over regular toolbars.\n */\n _getFocusableCandidateToolbarDefinitions() {\n const definitions = [];\n for (const toolbarDef of this._focusableToolbarDefinitions) {\n const { toolbarView, options } = toolbarDef;\n if (isVisible(toolbarView.element) || options.beforeFocus) {\n definitions.push(toolbarDef);\n }\n }\n // Contextual and already visible toolbars have higher priority. If both are true, the toolbar will always focus first.\n // For instance, a selected widget toolbar vs inline editor toolbar: both are visible but the widget toolbar is contextual.\n definitions.sort((defA, defB) => getToolbarDefinitionWeight(defA) - getToolbarDefinitionWeight(defB));\n return definitions;\n }\n /**\n * Returns a definition of the toolbar that is currently visible and focused (one of its children has focus).\n *\n * `null` is returned when no toolbar is currently focused.\n */\n _getCurrentFocusedToolbarDefinition() {\n for (const definition of this._focusableToolbarDefinitions) {\n if (definition.toolbarView.element && definition.toolbarView.element.contains(this.focusTracker.focusedElement)) {\n return definition;\n }\n }\n return null;\n }\n /**\n * Focuses a focusable toolbar candidate using its definition.\n *\n * @param candidateToolbarDefinition A definition of the toolbar to focus.\n * @returns `true` when the toolbar candidate was focused. `false` otherwise.\n */\n _focusFocusableCandidateToolbar(candidateToolbarDefinition) {\n const { toolbarView, options: { beforeFocus } } = candidateToolbarDefinition;\n if (beforeFocus) {\n beforeFocus();\n }\n // If it didn't show up after beforeFocus(), it's not focusable at all.\n if (!isVisible(toolbarView.element)) {\n return false;\n }\n toolbarView.focus();\n return true;\n }\n /**\n * Provides an integration between {@link #viewportOffset} and {@link module:utils/dom/scroll~scrollViewportToShowTarget}.\n * It allows the UI-agnostic engine method to consider user-configured viewport offsets specific for the integration.\n *\n * @param evt The `scrollToTheSelection` event info.\n * @param data The payload carried by the `scrollToTheSelection` event.\n */\n _handleScrollToTheSelection(evt, data) {\n const configuredViewportOffset = {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n ...this.viewportOffset\n };\n data.viewportOffset.top += configuredViewportOffset.top;\n data.viewportOffset.bottom += configuredViewportOffset.bottom;\n data.viewportOffset.left += configuredViewportOffset.left;\n data.viewportOffset.right += configuredViewportOffset.right;\n }\n}\n/**\n * Returns a number (weight) for a toolbar definition. Visible toolbars have a higher priority and so do\n * contextual toolbars (displayed in the context of a content, for instance, an image toolbar).\n *\n * A standard invisible toolbar is the heaviest. A visible contextual toolbar is the lightest.\n *\n * @param toolbarDef A toolbar definition to be weighted.\n */\nfunction getToolbarDefinitionWeight(toolbarDef) {\n const { toolbarView, options } = toolbarDef;\n let weight = 10;\n // Prioritize already visible toolbars. They should get focused first.\n if (isVisible(toolbarView.element)) {\n weight--;\n }\n // Prioritize contextual toolbars. They are displayed at the selection.\n if (options.isContextual) {\n weight--;\n }\n return weight;\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./editorui.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/editorui/editoruiview\n */\nimport View from '../view.js';\nimport BodyCollection from './bodycollection.js';\nimport '../../theme/components/editorui/editorui.css';\n/**\n * The editor UI view class. Base class for the editor main views.\n */\nexport default class EditorUIView extends View {\n /**\n * Creates an instance of the editor UI view class.\n *\n * @param locale The locale instance.\n */\n constructor(locale) {\n super(locale);\n this.body = new BodyCollection(locale);\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n this.body.attachToDom();\n }\n /**\n * @inheritDoc\n */\n destroy() {\n this.body.detachFromDom();\n return super.destroy();\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/editorui/boxed/boxededitoruiview\n */\nimport EditorUIView from '../editoruiview.js';\nimport LabelView from '../../label/labelview.js';\n/**\n * The boxed editor UI view class. This class represents an editor interface\n * consisting of a toolbar and an editable area, enclosed within a box.\n */\nexport default class BoxedEditorUIView extends EditorUIView {\n /**\n * Creates an instance of the boxed editor UI view class.\n *\n * @param locale The locale instance..\n */\n constructor(locale) {\n super(locale);\n this.top = this.createCollection();\n this.main = this.createCollection();\n this._voiceLabelView = this._createVoiceLabel();\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-reset',\n 'ck-editor',\n 'ck-rounded-corners'\n ],\n role: 'application',\n dir: locale.uiLanguageDirection,\n lang: locale.uiLanguage,\n 'aria-labelledby': this._voiceLabelView.id\n },\n children: [\n this._voiceLabelView,\n {\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-editor__top',\n 'ck-reset_all'\n ],\n role: 'presentation'\n },\n children: this.top\n },\n {\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-editor__main'\n ],\n role: 'presentation'\n },\n children: this.main\n }\n ]\n });\n }\n /**\n * Creates a voice label view instance.\n */\n _createVoiceLabel() {\n const t = this.t;\n const voiceLabel = new LabelView();\n voiceLabel.text = t('Rich Text Editor');\n voiceLabel.extendTemplate({\n attributes: {\n class: 'ck-voice-label'\n }\n });\n return voiceLabel;\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/editableui/editableuiview\n */\nimport View from '../view.js';\n/**\n * The editable UI view class.\n */\nexport default class EditableUIView extends View {\n /**\n * Creates an instance of EditableUIView class.\n *\n * @param locale The locale instance.\n * @param editingView The editing view instance the editable is related to.\n * @param editableElement The editable element. If not specified, this view\n * should create it. Otherwise, the existing element should be used.\n */\n constructor(locale, editingView, editableElement) {\n super(locale);\n /**\n * The name of the editable UI view.\n */\n this.name = null;\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-content',\n 'ck-editor__editable',\n 'ck-rounded-corners'\n ],\n lang: locale.contentLanguage,\n dir: locale.contentLanguageDirection\n }\n });\n this.set('isFocused', false);\n this._editableElement = editableElement;\n this._hasExternalElement = !!this._editableElement;\n this._editingView = editingView;\n }\n /**\n * Renders the view by either applying the {@link #template} to the existing\n * {@link module:ui/editableui/editableuiview~EditableUIView#_editableElement} or assigning {@link #element}\n * as {@link module:ui/editableui/editableuiview~EditableUIView#_editableElement}.\n */\n render() {\n super.render();\n if (this._hasExternalElement) {\n this.template.apply(this.element = this._editableElement);\n }\n else {\n this._editableElement = this.element;\n }\n this.on('change:isFocused', () => this._updateIsFocusedClasses());\n this._updateIsFocusedClasses();\n }\n /**\n * @inheritDoc\n */\n destroy() {\n if (this._hasExternalElement) {\n this.template.revert(this._editableElement);\n }\n super.destroy();\n }\n /**\n * Whether an external {@link #_editableElement} was passed into the constructor, which also means\n * the view will not render its {@link #template}.\n */\n get hasExternalElement() {\n return this._hasExternalElement;\n }\n /**\n * Updates the `ck-focused` and `ck-blurred` CSS classes on the {@link #element} according to\n * the {@link #isFocused} property value using the {@link #_editingView editing view} API.\n */\n _updateIsFocusedClasses() {\n const editingView = this._editingView;\n if (editingView.isRenderingInProgress) {\n updateAfterRender(this);\n }\n else {\n update(this);\n }\n function update(view) {\n editingView.change(writer => {\n const viewRoot = editingView.document.getRoot(view.name);\n writer.addClass(view.isFocused ? 'ck-focused' : 'ck-blurred', viewRoot);\n writer.removeClass(view.isFocused ? 'ck-blurred' : 'ck-focused', viewRoot);\n });\n }\n // In a case of a multi-root editor, a callback will be attached more than once (one callback for each root).\n // While executing one callback the `isRenderingInProgress` observable is changing what causes executing another\n // callback and render is called inside the already pending render.\n // We need to be sure that callback is executed only when the value has changed from `true` to `false`.\n // See https://github.com/ckeditor/ckeditor5/issues/1676.\n function updateAfterRender(view) {\n editingView.once('change:isRenderingInProgress', (evt, name, value) => {\n if (!value) {\n update(view);\n }\n else {\n updateAfterRender(view);\n }\n });\n }\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/editableui/inline/inlineeditableuiview\n */\nimport EditableUIView from '../editableuiview.js';\n/**\n * The inline editable UI class implementing an inline {@link module:ui/editableui/editableuiview~EditableUIView}.\n */\nexport default class InlineEditableUIView extends EditableUIView {\n /**\n * Creates an instance of the InlineEditableUIView class.\n *\n * @param locale The locale instance.\n * @param editingView The editing view instance the editable is related to.\n * @param editableElement The editable element. If not specified, the\n * {@link module:ui/editableui/editableuiview~EditableUIView}\n * will create it. Otherwise, the existing element will be used.\n * @param options Additional configuration of the view.\n * @param options.label A function that gets called with the instance of this view as an argument\n * and should return a string that represents the label of the editable for assistive technologies. If not provided,\n * a default label generator is used.\n */\n constructor(locale, editingView, editableElement, options = {}) {\n super(locale, editingView, editableElement);\n const t = locale.t;\n this.extendTemplate({\n attributes: {\n role: 'textbox',\n class: 'ck-editor__editable_inline'\n }\n });\n this._generateLabel = options.label || (() => t('Editor editing area: %0', this.name));\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n const editingView = this._editingView;\n editingView.change(writer => {\n const viewRoot = editingView.document.getRoot(this.name);\n writer.setAttribute('aria-label', this._generateLabel(this), viewRoot);\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/notification/notification\n */\n/* globals window */\nimport { ContextPlugin } from '@ckeditor/ckeditor5-core';\n/**\n * The Notification plugin.\n *\n * This plugin sends a few types of notifications: `success`, `info` and `warning`. The notifications need to be\n * handled and displayed by a plugin responsible for showing the UI of the notifications. Using this plugin for dispatching\n * notifications makes it possible to switch the notifications UI.\n *\n * Note that every unhandled and not stopped `warning` notification will be displayed as a system alert.\n * See {@link module:ui/notification/notification~Notification#showWarning}.\n */\nexport default class Notification extends ContextPlugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'Notification';\n }\n /**\n * @inheritDoc\n */\n init() {\n // Each unhandled and not stopped `show:warning` event is displayed as a system alert.\n this.on('show:warning', (evt, data) => {\n window.alert(data.message); // eslint-disable-line no-alert\n }, { priority: 'lowest' });\n }\n /**\n * Shows a success notification.\n *\n * By default, it fires the {@link #event:show:success `show:success` event} with the given `data`. The event namespace can be extended\n * using the `data.namespace` option. For example:\n *\n * ```ts\n * showSuccess( 'Image is uploaded.', {\n * \tnamespace: 'upload:image'\n * } );\n * ```\n *\n * will fire the `show:success:upload:image` event.\n *\n * You can provide the title of the notification:\n *\n * ```ts\n * showSuccess( 'Image is uploaded.', {\n * \ttitle: 'Image upload success'\n * } );\n * ```\n *\n * @param message The content of the notification.\n * @param data Additional data.\n * @param data.namespace Additional event namespace.\n * @param data.title The title of the notification.\n */\n showSuccess(message, data = {}) {\n this._showNotification({\n message,\n type: 'success',\n namespace: data.namespace,\n title: data.title\n });\n }\n /**\n * Shows an information notification.\n *\n * By default, it fires the {@link #event:show:info `show:info` event} with the given `data`. The event namespace can be extended\n * using the `data.namespace` option. For example:\n *\n * ```ts\n * showInfo( 'Editor is offline.', {\n * \tnamespace: 'editor:status'\n * } );\n * ```\n *\n * will fire the `show:info:editor:status` event.\n *\n * You can provide the title of the notification:\n *\n * ```ts\n * showInfo( 'Editor is offline.', {\n * \ttitle: 'Network information'\n * } );\n * ```\n *\n * @param message The content of the notification.\n * @param data Additional data.\n * @param data.namespace Additional event namespace.\n * @param data.title The title of the notification.\n */\n showInfo(message, data = {}) {\n this._showNotification({\n message,\n type: 'info',\n namespace: data.namespace,\n title: data.title\n });\n }\n /**\n * Shows a warning notification.\n *\n * By default, it fires the {@link #event:show:warning `show:warning` event}\n * with the given `data`. The event namespace can be extended using the `data.namespace` option. For example:\n *\n * ```ts\n * showWarning( 'Image upload error.', {\n * \tnamespace: 'upload:image'\n * } );\n * ```\n *\n * will fire the `show:warning:upload:image` event.\n *\n * You can provide the title of the notification:\n *\n * ```ts\n * showWarning( 'Image upload error.', {\n * \ttitle: 'Upload failed'\n * } );\n * ```\n *\n * Note that each unhandled and not stopped `warning` notification will be displayed as a system alert.\n * The plugin responsible for displaying warnings should `stop()` the event to prevent displaying it as an alert:\n *\n * ```ts\n * notifications.on( 'show:warning', ( evt, data ) => {\n * \t// Do something with the data.\n *\n * \t// Stop this event to prevent displaying it as an alert.\n * \tevt.stop();\n * } );\n * ```\n *\n * You can attach many listeners to the same event and `stop()` this event in a listener with a low priority:\n *\n * ```ts\n * notifications.on( 'show:warning', ( evt, data ) => {\n * \t// Show the warning in the UI, but do not stop it.\n * } );\n *\n * notifications.on( 'show:warning', ( evt, data ) => {\n * \t// Log the warning to some error tracker.\n *\n * \t// Stop this event to prevent displaying it as an alert.\n * \tevt.stop();\n * }, { priority: 'low' } );\n * ```\n *\n * @param message The content of the notification.\n * @param data Additional data.\n * @param data.namespace Additional event namespace.\n * @param data.title The title of the notification.\n */\n showWarning(message, data = {}) {\n this._showNotification({\n message,\n type: 'warning',\n namespace: data.namespace,\n title: data.title\n });\n }\n /**\n * Fires the `show` event with the specified type, namespace and message.\n *\n * @param data The message data.\n * @param data.message The content of the notification.\n * @param data.type The type of the message.\n * @param data.namespace Additional event namespace.\n * @param data.title The title of the notification.\n */\n _showNotification(data) {\n const event = data.namespace ?\n `show:${data.type}:${data.namespace}` :\n `show:${data.type}`;\n this.fire(event, {\n message: data.message,\n type: data.type,\n title: data.title || ''\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/model\n */\nimport { ObservableMixin } from '@ckeditor/ckeditor5-utils';\nimport { extend } from 'lodash-es';\n/**\n * The base MVC model class.\n */\nexport default class Model extends ObservableMixin() {\n /**\n * Creates a new Model instance.\n *\n * @param attributes The model state attributes to be defined during the instance creation.\n * @param properties The (out of state) properties to be appended to the instance during creation.\n */\n constructor(attributes, properties) {\n super();\n // Extend this instance with the additional (out of state) properties.\n if (properties) {\n extend(this, properties);\n }\n // Initialize the attributes.\n if (attributes) {\n this.set(attributes);\n }\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./balloonrotator.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./fakepanel.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/panel/balloon/contextualballoon\n */\nimport BalloonPanelView from './balloonpanelview.js';\nimport View from '../../view.js';\nimport ButtonView from '../../button/buttonview.js';\nimport { Plugin, icons } from '@ckeditor/ckeditor5-core';\nimport { CKEditorError, FocusTracker, Rect, toUnit } from '@ckeditor/ckeditor5-utils';\nimport '../../../theme/components/panel/balloonrotator.css';\nimport '../../../theme/components/panel/fakepanel.css';\nconst toPx = toUnit('px');\n/**\n * Provides the common contextual balloon for the editor.\n *\n * The role of this plugin is to unify the contextual balloons logic, simplify views management and help\n * avoid the unnecessary complexity of handling multiple {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView}\n * instances in the editor.\n *\n * This plugin allows for creating single or multiple panel stacks.\n *\n * Each stack may have multiple views, with the one on the top being visible. When the visible view is removed from the stack,\n * the previous view becomes visible.\n *\n * It might be useful to implement nested navigation in a balloon. For instance, a toolbar view may contain a link button.\n * When you click it, a link view (which lets you set the URL) is created and put on top of the toolbar view, so the link panel\n * is displayed. When you finish editing the link and close (remove) the link view, the toolbar view is visible again.\n *\n * However, there are cases when there are multiple independent balloons to be displayed, for instance, if the selection\n * is inside two inline comments at the same time. For such cases, you can create two independent panel stacks.\n * The contextual balloon plugin will create a navigation bar to let the users switch between these panel stacks using the \"Next\"\n * and \"Previous\" buttons.\n *\n * If there are no views in the current stack, the balloon panel will try to switch to the next stack. If there are no\n * panels in any stack, the balloon panel will be hidden.\n *\n * **Note**: To force the balloon panel to show only one view, even if there are other stacks, use the `singleViewMode=true` option\n * when {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon#add adding} a view to a panel.\n *\n * From the implementation point of view, the contextual ballon plugin is reusing a single\n * {@link module:ui/panel/balloon/balloonpanelview~BalloonPanelView} instance to display multiple contextual balloon\n * panels in the editor. It also creates a special {@link module:ui/panel/balloon/contextualballoon~RotatorView rotator view},\n * used to manage multiple panel stacks. Rotator view is a child of the balloon panel view and the parent of the specific\n * view you want to display. If there is more than one panel stack to be displayed, the rotator view will add a\n * navigation bar. If there is only one stack, the rotator view is transparent (it does not add any UI elements).\n */\nexport default class ContextualBalloon extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'ContextualBalloon';\n }\n /**\n * @inheritDoc\n */\n constructor(editor) {\n super(editor);\n /**\n * The map of views and their stacks.\n */\n this._viewToStack = new Map();\n /**\n * The map of IDs and stacks.\n */\n this._idToStack = new Map();\n /**\n * The common balloon panel view.\n */\n this._view = null;\n /**\n * Rotator view embedded in the contextual balloon.\n * Displays the currently visible view in the balloon and provides navigation for switching stacks.\n */\n this._rotatorView = null;\n /**\n * Displays fake panels under the balloon panel view when multiple stacks are added to the balloon.\n */\n this._fakePanelsView = null;\n this.positionLimiter = () => {\n const view = this.editor.editing.view;\n const viewDocument = view.document;\n const editableElement = viewDocument.selection.editableElement;\n if (editableElement) {\n return view.domConverter.mapViewToDom(editableElement.root);\n }\n return null;\n };\n this.set('visibleView', null);\n this.set('_numberOfStacks', 0);\n this.set('_singleViewMode', false);\n }\n /**\n * @inheritDoc\n */\n destroy() {\n super.destroy();\n if (this._view) {\n this._view.destroy();\n }\n if (this._rotatorView) {\n this._rotatorView.destroy();\n }\n if (this._fakePanelsView) {\n this._fakePanelsView.destroy();\n }\n }\n /**\n * The common balloon panel view.\n */\n get view() {\n if (!this._view) {\n this._createPanelView();\n }\n return this._view;\n }\n /**\n * Returns `true` when the given view is in one of the stacks. Otherwise returns `false`.\n */\n hasView(view) {\n return Array.from(this._viewToStack.keys()).includes(view);\n }\n /**\n * Adds a new view to the stack and makes it visible if the current stack is visible\n * or it is the first view in the balloon.\n *\n * @param data The configuration of the view.\n * @param data.stackId The ID of the stack that the view is added to. Defaults to `'main'`.\n * @param data.view The content of the balloon.\n * @param data.position Positioning options.\n * @param data.balloonClassName An additional CSS class added to the {@link #view balloon} when visible.\n * @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow. Defaults to `true`.\n * @param data.singleViewMode Whether the view should be the only visible view even if other stacks were added. Defaults to `false`.\n */\n add(data) {\n if (!this._view) {\n this._createPanelView();\n }\n if (this.hasView(data.view)) {\n /**\n * Trying to add configuration of the same view more than once.\n *\n * @error contextualballoon-add-view-exist\n */\n throw new CKEditorError('contextualballoon-add-view-exist', [this, data]);\n }\n const stackId = data.stackId || 'main';\n // If new stack is added, creates it and show view from this stack.\n if (!this._idToStack.has(stackId)) {\n this._idToStack.set(stackId, new Map([[data.view, data]]));\n this._viewToStack.set(data.view, this._idToStack.get(stackId));\n this._numberOfStacks = this._idToStack.size;\n if (!this._visibleStack || data.singleViewMode) {\n this.showStack(stackId);\n }\n return;\n }\n const stack = this._idToStack.get(stackId);\n if (data.singleViewMode) {\n this.showStack(stackId);\n }\n // Add new view to the stack.\n stack.set(data.view, data);\n this._viewToStack.set(data.view, stack);\n // And display it if is added to the currently visible stack.\n if (stack === this._visibleStack) {\n this._showView(data);\n }\n }\n /**\n * Removes the given view from the stack. If the removed view was visible,\n * the view preceding it in the stack will become visible instead.\n * When there is no view in the stack, the next stack will be displayed.\n * When there are no more stacks, the balloon will hide.\n *\n * @param view A view to be removed from the balloon.\n */\n remove(view) {\n if (!this.hasView(view)) {\n /**\n * Trying to remove the configuration of the view not defined in the stack.\n *\n * @error contextualballoon-remove-view-not-exist\n */\n throw new CKEditorError('contextualballoon-remove-view-not-exist', [this, view]);\n }\n const stack = this._viewToStack.get(view);\n if (this._singleViewMode && this.visibleView === view) {\n this._singleViewMode = false;\n }\n // When visible view will be removed we need to show a preceding view or next stack\n // if a view is the only view in the stack.\n if (this.visibleView === view) {\n if (stack.size === 1) {\n if (this._idToStack.size > 1) {\n this._showNextStack();\n }\n else {\n this.view.hide();\n this.visibleView = null;\n this._rotatorView.hideView();\n }\n }\n else {\n this._showView(Array.from(stack.values())[stack.size - 2]);\n }\n }\n if (stack.size === 1) {\n this._idToStack.delete(this._getStackId(stack));\n this._numberOfStacks = this._idToStack.size;\n }\n else {\n stack.delete(view);\n }\n this._viewToStack.delete(view);\n }\n /**\n * Updates the position of the balloon using the position data of the first visible view in the stack.\n * When new position data is given, the position data of the currently visible view will be updated.\n *\n * @param position Position options.\n */\n updatePosition(position) {\n if (position) {\n this._visibleStack.get(this.visibleView).position = position;\n }\n this.view.pin(this._getBalloonPosition());\n this._fakePanelsView.updatePosition();\n }\n /**\n * Shows the last view from the stack of a given ID.\n */\n showStack(id) {\n this.visibleStack = id;\n const stack = this._idToStack.get(id);\n if (!stack) {\n /**\n * Trying to show a stack that does not exist.\n *\n * @error contextualballoon-showstack-stack-not-exist\n */\n throw new CKEditorError('contextualballoon-showstack-stack-not-exist', this);\n }\n if (this._visibleStack === stack) {\n return;\n }\n this._showView(Array.from(stack.values()).pop());\n }\n /**\n * Initializes view instances.\n */\n _createPanelView() {\n this._view = new BalloonPanelView(this.editor.locale);\n this.editor.ui.view.body.add(this._view);\n this.editor.ui.focusTracker.add(this._view.element);\n this._rotatorView = this._createRotatorView();\n this._fakePanelsView = this._createFakePanelsView();\n }\n /**\n * Returns the stack of the currently visible view.\n */\n get _visibleStack() {\n return this._viewToStack.get(this.visibleView);\n }\n /**\n * Returns the ID of the given stack.\n */\n _getStackId(stack) {\n const entry = Array.from(this._idToStack.entries()).find(entry => entry[1] === stack);\n return entry[0];\n }\n /**\n * Shows the last view from the next stack.\n */\n _showNextStack() {\n const stacks = Array.from(this._idToStack.values());\n let nextIndex = stacks.indexOf(this._visibleStack) + 1;\n if (!stacks[nextIndex]) {\n nextIndex = 0;\n }\n this.showStack(this._getStackId(stacks[nextIndex]));\n }\n /**\n * Shows the last view from the previous stack.\n */\n _showPrevStack() {\n const stacks = Array.from(this._idToStack.values());\n let nextIndex = stacks.indexOf(this._visibleStack) - 1;\n if (!stacks[nextIndex]) {\n nextIndex = stacks.length - 1;\n }\n this.showStack(this._getStackId(stacks[nextIndex]));\n }\n /**\n * Creates a rotator view.\n */\n _createRotatorView() {\n const view = new RotatorView(this.editor.locale);\n const t = this.editor.locale.t;\n this.view.content.add(view);\n // Hide navigation when there is only a one stack & not in single view mode.\n view.bind('isNavigationVisible').to(this, '_numberOfStacks', this, '_singleViewMode', (value, isSingleViewMode) => {\n return !isSingleViewMode && value > 1;\n });\n // Update balloon position after toggling navigation.\n view.on('change:isNavigationVisible', () => (this.updatePosition()), { priority: 'low' });\n // Update stacks counter value.\n view.bind('counter').to(this, 'visibleView', this, '_numberOfStacks', (visibleView, numberOfStacks) => {\n if (numberOfStacks < 2) {\n return '';\n }\n const current = Array.from(this._idToStack.values()).indexOf(this._visibleStack) + 1;\n return t('%0 of %1', [current, numberOfStacks]);\n });\n view.buttonNextView.on('execute', () => {\n // When current view has a focus then move focus to the editable before removing it,\n // otherwise editor will lost focus.\n if (view.focusTracker.isFocused) {\n this.editor.editing.view.focus();\n }\n this._showNextStack();\n });\n view.buttonPrevView.on('execute', () => {\n // When current view has a focus then move focus to the editable before removing it,\n // otherwise editor will lost focus.\n if (view.focusTracker.isFocused) {\n this.editor.editing.view.focus();\n }\n this._showPrevStack();\n });\n return view;\n }\n /**\n * Creates a fake panels view.\n */\n _createFakePanelsView() {\n const view = new FakePanelsView(this.editor.locale, this.view);\n view.bind('numberOfPanels').to(this, '_numberOfStacks', this, '_singleViewMode', (number, isSingleViewMode) => {\n const showPanels = !isSingleViewMode && number >= 2;\n return showPanels ? Math.min(number - 1, 2) : 0;\n });\n view.listenTo(this.view, 'change:top', () => view.updatePosition());\n view.listenTo(this.view, 'change:left', () => view.updatePosition());\n this.editor.ui.view.body.add(view);\n return view;\n }\n /**\n * Sets the view as the content of the balloon and attaches the balloon using position\n * options of the first view.\n *\n * @param data Configuration.\n * @param data.view The view to show in the balloon.\n * @param data.balloonClassName Additional class name which will be added to the {@link #view balloon}.\n * @param data.withArrow Whether the {@link #view balloon} should be rendered with an arrow.\n */\n _showView({ view, balloonClassName = '', withArrow = true, singleViewMode = false }) {\n this.view.class = balloonClassName;\n this.view.withArrow = withArrow;\n this._rotatorView.showView(view);\n this.visibleView = view;\n this.view.pin(this._getBalloonPosition());\n this._fakePanelsView.updatePosition();\n if (singleViewMode) {\n this._singleViewMode = true;\n }\n }\n /**\n * Returns position options of the last view in the stack.\n * This keeps the balloon in the same position when the view is changed.\n */\n _getBalloonPosition() {\n let position = Array.from(this._visibleStack.values()).pop().position;\n if (position) {\n // Use the default limiter if none has been specified.\n if (!position.limiter) {\n // Don't modify the original options object.\n position = Object.assign({}, position, {\n limiter: this.positionLimiter\n });\n }\n // Don't modify the original options object.\n position = Object.assign({}, position, {\n viewportOffsetConfig: this.editor.ui.viewportOffset\n });\n }\n return position;\n }\n}\n/**\n * Rotator view is a helper class for the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon}.\n * It is used for displaying the last view from the current stack and providing navigation buttons for switching stacks.\n * See the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon ContextualBalloon} documentation to learn more.\n */\nexport class RotatorView extends View {\n /**\n * @inheritDoc\n */\n constructor(locale) {\n super(locale);\n const t = locale.t;\n const bind = this.bindTemplate;\n this.set('isNavigationVisible', true);\n this.focusTracker = new FocusTracker();\n this.buttonPrevView = this._createButtonView(t('Previous'), icons.previousArrow);\n this.buttonNextView = this._createButtonView(t('Next'), icons.nextArrow);\n this.content = this.createCollection();\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-balloon-rotator'\n ],\n 'z-index': '-1'\n },\n children: [\n {\n tag: 'div',\n attributes: {\n class: [\n 'ck-balloon-rotator__navigation',\n bind.to('isNavigationVisible', value => value ? '' : 'ck-hidden')\n ]\n },\n children: [\n this.buttonPrevView,\n {\n tag: 'span',\n attributes: {\n class: [\n 'ck-balloon-rotator__counter'\n ]\n },\n children: [\n {\n text: bind.to('counter')\n }\n ]\n },\n this.buttonNextView\n ]\n },\n {\n tag: 'div',\n attributes: {\n class: 'ck-balloon-rotator__content'\n },\n children: this.content\n }\n ]\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n this.focusTracker.add(this.element);\n }\n /**\n * @inheritDoc\n */\n destroy() {\n super.destroy();\n this.focusTracker.destroy();\n }\n /**\n * Shows a given view.\n *\n * @param view The view to show.\n */\n showView(view) {\n this.hideView();\n this.content.add(view);\n }\n /**\n * Hides the currently displayed view.\n */\n hideView() {\n this.content.clear();\n }\n /**\n * Creates a navigation button view.\n *\n * @param label The button label.\n * @param icon The button icon.\n */\n _createButtonView(label, icon) {\n const view = new ButtonView(this.locale);\n view.set({\n label,\n icon,\n tooltip: true\n });\n return view;\n }\n}\n/**\n * Displays additional layers under the balloon when multiple stacks are added to the balloon.\n */\nclass FakePanelsView extends View {\n /**\n * @inheritDoc\n */\n constructor(locale, balloonPanelView) {\n super(locale);\n const bind = this.bindTemplate;\n this.set('top', 0);\n this.set('left', 0);\n this.set('height', 0);\n this.set('width', 0);\n this.set('numberOfPanels', 0);\n this.content = this.createCollection();\n this._balloonPanelView = balloonPanelView;\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck-fake-panel',\n bind.to('numberOfPanels', number => number ? '' : 'ck-hidden')\n ],\n style: {\n top: bind.to('top', toPx),\n left: bind.to('left', toPx),\n width: bind.to('width', toPx),\n height: bind.to('height', toPx)\n }\n },\n children: this.content\n });\n this.on('change:numberOfPanels', (evt, name, next, prev) => {\n if (next > prev) {\n this._addPanels(next - prev);\n }\n else {\n this._removePanels(prev - next);\n }\n this.updatePosition();\n });\n }\n _addPanels(number) {\n while (number--) {\n const view = new View();\n view.setTemplate({ tag: 'div' });\n this.content.add(view);\n this.registerChild(view);\n }\n }\n _removePanels(number) {\n while (number--) {\n const view = this.content.last;\n this.content.remove(view);\n this.deregisterChild(view);\n view.destroy();\n }\n }\n /**\n * Updates coordinates of fake panels.\n */\n updatePosition() {\n if (this.numberOfPanels) {\n const { top, left } = this._balloonPanelView;\n const { width, height } = new Rect(this._balloonPanelView.element);\n Object.assign(this, { top, left, width, height });\n }\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./stickypanel.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/panel/sticky/stickypanelview\n */\nimport View from '../../view.js';\nimport Template from '../../template.js';\nimport { global, toUnit, Rect } from '@ckeditor/ckeditor5-utils';\n// @if CK_DEBUG_STICKYPANEL // const {\n// @if CK_DEBUG_STICKYPANEL // \tdefault: RectDrawer,\n// @if CK_DEBUG_STICKYPANEL // \tdiagonalStylesBlack\n// @if CK_DEBUG_STICKYPANEL // } = require( '@ckeditor/ckeditor5-utils/tests/_utils/rectdrawer' );\nimport '../../../theme/components/panel/stickypanel.css';\nconst toPx = toUnit('px');\n/**\n * The sticky panel view class.\n */\nexport default class StickyPanelView extends View {\n /**\n * @inheritDoc\n */\n constructor(locale) {\n super(locale);\n const bind = this.bindTemplate;\n this.set('isActive', false);\n this.set('isSticky', false);\n this.set('limiterElement', null);\n this.set('limiterBottomOffset', 50);\n this.set('viewportTopOffset', 0);\n this.set('_marginLeft', null);\n this.set('_isStickyToTheBottomOfLimiter', false);\n this.set('_stickyTopOffset', null);\n this.set('_stickyBottomOffset', null);\n this.content = this.createCollection();\n this._contentPanelPlaceholder = new Template({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-sticky-panel__placeholder'\n ],\n style: {\n display: bind.to('isSticky', isSticky => isSticky ? 'block' : 'none'),\n height: bind.to('isSticky', isSticky => {\n return isSticky ? toPx(this._contentPanelRect.height) : null;\n })\n }\n }\n }).render();\n this.contentPanelElement = new Template({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-sticky-panel__content',\n // Toggle class of the panel when \"sticky\" state changes in the view.\n bind.if('isSticky', 'ck-sticky-panel__content_sticky'),\n bind.if('_isStickyToTheBottomOfLimiter', 'ck-sticky-panel__content_sticky_bottom-limit')\n ],\n style: {\n width: bind.to('isSticky', isSticky => {\n return isSticky ? toPx(this._contentPanelPlaceholder.getBoundingClientRect().width) : null;\n }),\n top: bind.to('_stickyTopOffset', value => value ? toPx(value) : value),\n bottom: bind.to('_stickyBottomOffset', value => value ? toPx(value) : value),\n marginLeft: bind.to('_marginLeft')\n }\n },\n children: this.content\n }).render();\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-sticky-panel'\n ]\n },\n children: [\n this._contentPanelPlaceholder,\n this.contentPanelElement\n ]\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n // Check if the panel should go into the sticky state immediately.\n this.checkIfShouldBeSticky();\n // Update sticky state of the panel as the window and ancestors are being scrolled.\n this.listenTo(global.document, 'scroll', () => {\n this.checkIfShouldBeSticky();\n }, { useCapture: true });\n // Synchronize with `model.isActive` because sticking an inactive panel is pointless.\n this.listenTo(this, 'change:isActive', () => {\n this.checkIfShouldBeSticky();\n });\n }\n /**\n * Analyzes the environment to decide whether the panel should be sticky or not.\n * Then handles the positioning of the panel.\n */\n checkIfShouldBeSticky() {\n // @if CK_DEBUG_STICKYPANEL // RectDrawer.clear();\n if (!this.limiterElement || !this.isActive) {\n this._unstick();\n return;\n }\n const limiterRect = new Rect(this.limiterElement);\n let visibleLimiterRect = limiterRect.getVisible();\n if (visibleLimiterRect) {\n const windowRect = new Rect(global.window);\n windowRect.top += this.viewportTopOffset;\n windowRect.height -= this.viewportTopOffset;\n visibleLimiterRect = visibleLimiterRect.getIntersection(windowRect);\n }\n // @if CK_DEBUG_STICKYPANEL // if ( visibleLimiterRect ) {\n // @if CK_DEBUG_STICKYPANEL // \tRectDrawer.draw( visibleLimiterRect,\n // @if CK_DEBUG_STICKYPANEL // \t\t{ outlineWidth: '3px', opacity: '.8', outlineColor: 'red', outlineOffset: '-3px' },\n // @if CK_DEBUG_STICKYPANEL // \t\t'Visible anc'\n // @if CK_DEBUG_STICKYPANEL // \t);\n // @if CK_DEBUG_STICKYPANEL // }\n // @if CK_DEBUG_STICKYPANEL //\n // @if CK_DEBUG_STICKYPANEL // RectDrawer.draw( limiterRect,\n // @if CK_DEBUG_STICKYPANEL // \t{ outlineWidth: '3px', opacity: '.8', outlineColor: 'green', outlineOffset: '-3px' },\n // @if CK_DEBUG_STICKYPANEL // \t'Limiter'\n // @if CK_DEBUG_STICKYPANEL // );\n // Stick the panel only if\n // * the limiter's ancestors are intersecting with each other so that some of their rects are visible,\n // * and the limiter's top edge is above the visible ancestors' top edge.\n if (visibleLimiterRect && limiterRect.top < visibleLimiterRect.top) {\n // @if CK_DEBUG_STICKYPANEL // RectDrawer.draw( visibleLimiterRect,\n // @if CK_DEBUG_STICKYPANEL // \t{ outlineWidth: '3px', opacity: '.8', outlineColor: 'fuchsia', outlineOffset: '-3px',\n // @if CK_DEBUG_STICKYPANEL // \t\tbackgroundColor: 'rgba(255, 0, 255, .3)' },\n // @if CK_DEBUG_STICKYPANEL // \t'Visible limiter'\n // @if CK_DEBUG_STICKYPANEL // );\n const visibleLimiterTop = visibleLimiterRect.top;\n // Check if there's a change the panel can be sticky to the bottom of the limiter.\n if (visibleLimiterTop + this._contentPanelRect.height + this.limiterBottomOffset > visibleLimiterRect.bottom) {\n const stickyBottomOffset = Math.max(limiterRect.bottom - visibleLimiterRect.bottom, 0) + this.limiterBottomOffset;\n // @if CK_DEBUG_STICKYPANEL // const stickyBottomOffsetRect = new Rect( {\n // @if CK_DEBUG_STICKYPANEL // \ttop: limiterRect.bottom - stickyBottomOffset, left: 0, right: 2000,\n // @if CK_DEBUG_STICKYPANEL // \tbottom: limiterRect.bottom - stickyBottomOffset, width: 2000, height: 1\n // @if CK_DEBUG_STICKYPANEL // } );\n // @if CK_DEBUG_STICKYPANEL // RectDrawer.draw( stickyBottomOffsetRect,\n // @if CK_DEBUG_STICKYPANEL // \t{ outlineWidth: '1px', opacity: '.8', outlineColor: 'black' },\n // @if CK_DEBUG_STICKYPANEL // \t'Sticky bottom offset'\n // @if CK_DEBUG_STICKYPANEL // );\n // Check if sticking the panel to the bottom of the limiter does not cause it to suddenly\n // move upwards if there's not enough space for it.\n if (limiterRect.bottom - stickyBottomOffset > limiterRect.top + this._contentPanelRect.height) {\n this._stickToBottomOfLimiter(stickyBottomOffset);\n }\n else {\n this._unstick();\n }\n }\n else {\n if (this._contentPanelRect.height + this.limiterBottomOffset < limiterRect.height) {\n this._stickToTopOfAncestors(visibleLimiterTop);\n }\n else {\n this._unstick();\n }\n }\n }\n else {\n this._unstick();\n }\n // @if CK_DEBUG_STICKYPANEL // console.clear();\n // @if CK_DEBUG_STICKYPANEL // console.log( 'isSticky', this.isSticky );\n // @if CK_DEBUG_STICKYPANEL // console.log( '_isStickyToTheBottomOfLimiter', this._isStickyToTheBottomOfLimiter );\n // @if CK_DEBUG_STICKYPANEL // console.log( '_stickyTopOffset', this._stickyTopOffset );\n // @if CK_DEBUG_STICKYPANEL // console.log( '_stickyBottomOffset', this._stickyBottomOffset );\n // @if CK_DEBUG_STICKYPANEL // if ( visibleLimiterRect ) {\n // @if CK_DEBUG_STICKYPANEL // \tRectDrawer.draw( visibleLimiterRect,\n // @if CK_DEBUG_STICKYPANEL // \t\t{ ...diagonalStylesBlack,\n // @if CK_DEBUG_STICKYPANEL // \t\t\toutlineWidth: '3px', opacity: '.8', outlineColor: 'orange', outlineOffset: '-3px',\n // @if CK_DEBUG_STICKYPANEL // \t\t\tbackgroundColor: 'rgba(0, 0, 255, .2)' },\n // @if CK_DEBUG_STICKYPANEL // \t\t'visibleLimiterRect'\n // @if CK_DEBUG_STICKYPANEL // \t);\n // @if CK_DEBUG_STICKYPANEL // }\n }\n /**\n * Sticks the panel at the given CSS `top` offset.\n *\n * @private\n * @param topOffset\n */\n _stickToTopOfAncestors(topOffset) {\n this.isSticky = true;\n this._isStickyToTheBottomOfLimiter = false;\n this._stickyTopOffset = topOffset;\n this._stickyBottomOffset = null;\n this._marginLeft = toPx(-global.window.scrollX);\n }\n /**\n * Sticks the panel at the bottom of the limiter with a given CSS `bottom` offset.\n *\n * @private\n * @param stickyBottomOffset\n */\n _stickToBottomOfLimiter(stickyBottomOffset) {\n this.isSticky = true;\n this._isStickyToTheBottomOfLimiter = true;\n this._stickyTopOffset = null;\n this._stickyBottomOffset = stickyBottomOffset;\n this._marginLeft = toPx(-global.window.scrollX);\n }\n /**\n * Unsticks the panel putting it back to its original position.\n *\n * @private\n */\n _unstick() {\n this.isSticky = false;\n this._isStickyToTheBottomOfLimiter = false;\n this._stickyTopOffset = null;\n this._stickyBottomOffset = null;\n this._marginLeft = null;\n }\n /**\n * Returns the bounding rect of the {@link #contentPanelElement}.\n *\n * @private\n */\n get _contentPanelRect() {\n return new Rect(this.contentPanelElement);\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/search/text/searchtextqueryview\n */\nimport ButtonView from '../../button/buttonview.js';\nimport IconView from '../../icon/iconview.js';\nimport LabeledFieldView from '../../labeledfield/labeledfieldview.js';\nimport { createLabeledInputText } from '../../labeledfield/utils.js';\nimport { icons } from '@ckeditor/ckeditor5-core';\n/**\n * A search input field for the {@link module:ui/search/text/searchtextview~SearchTextView} component.\n *\n * @internal\n * @extends module:ui/labeledfield/labeledfieldview~LabeledFieldView\n */\nexport default class SearchTextQueryView extends LabeledFieldView {\n /**\n * @inheritDoc\n */\n constructor(locale, config) {\n const t = locale.t;\n const viewConfig = Object.assign({}, {\n showResetButton: true,\n showIcon: true,\n creator: createLabeledInputText\n }, config);\n super(locale, viewConfig.creator);\n this.label = config.label;\n this._viewConfig = viewConfig;\n if (this._viewConfig.showIcon) {\n this.iconView = new IconView();\n this.iconView.content = icons.loupe;\n this.fieldWrapperChildren.add(this.iconView, 0);\n this.extendTemplate({\n attributes: {\n class: 'ck-search__query_with-icon'\n }\n });\n }\n if (this._viewConfig.showResetButton) {\n this.resetButtonView = new ButtonView(locale);\n this.resetButtonView.set({\n label: t('Clear'),\n icon: icons.cancel,\n class: 'ck-search__reset',\n isVisible: false,\n tooltip: true\n });\n this.resetButtonView.on('execute', () => {\n this.reset();\n this.focus();\n this.fire('reset');\n });\n this.resetButtonView.bind('isVisible').to(this.fieldView, 'isEmpty', isEmpty => !isEmpty);\n this.fieldWrapperChildren.add(this.resetButtonView);\n this.extendTemplate({\n attributes: {\n class: 'ck-search__query_with-reset'\n }\n });\n }\n }\n /**\n * Resets the search field to its default state.\n */\n reset() {\n this.fieldView.reset();\n if (this._viewConfig.showResetButton) {\n this.resetButtonView.isVisible = false;\n }\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport View from '../view.js';\n/**\n * A view displaying an information text related to different states of {@link module:ui/search/text/searchtextview~SearchTextView}.\n *\n * @internal\n */\nexport default class SearchInfoView extends View {\n /**\n * @inheritDoc\n */\n constructor() {\n super();\n const bind = this.bindTemplate;\n this.set({\n isVisible: false,\n primaryText: '',\n secondaryText: ''\n });\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-search__info',\n bind.if('isVisible', 'ck-hidden', value => !value)\n ],\n tabindex: -1\n },\n children: [\n {\n tag: 'span',\n children: [\n {\n text: [bind.to('primaryText')]\n }\n ]\n },\n {\n tag: 'span',\n children: [\n {\n text: [bind.to('secondaryText')]\n }\n ]\n }\n ]\n });\n }\n /**\n * Focuses the view\n */\n focus() {\n this.element.focus();\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/search/searchresultsview\n */\nimport View from '../view.js';\nimport { FocusTracker } from '@ckeditor/ckeditor5-utils';\nimport { default as FocusCycler } from '../focuscycler.js';\n/**\n * A sub-component of {@link module:ui/search/text/searchtextview~SearchTextView}. It hosts the filtered and the information views.\n */\nexport default class SearchResultsView extends View {\n /**\n * @inheritDoc\n */\n constructor(locale) {\n super(locale);\n this.children = this.createCollection();\n this.focusTracker = new FocusTracker();\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-search__results'\n ],\n tabindex: -1\n },\n children: this.children\n });\n this._focusCycler = new FocusCycler({\n focusables: this.children,\n focusTracker: this.focusTracker\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n for (const child of this.children) {\n this.focusTracker.add(child.element);\n }\n }\n /**\n * Focuses the view.\n */\n focus() {\n this._focusCycler.focusFirst();\n }\n /**\n * Focuses the first child view.\n */\n focusFirst() {\n this._focusCycler.focusFirst();\n }\n /**\n * Focuses the last child view.\n */\n focusLast() {\n this._focusCycler.focusLast();\n }\n}\n","import toString from './toString.js';\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g,\n reHasRegExpChar = RegExp(reRegExpChar.source);\n\n/**\n * Escapes the `RegExp` special characters \"^\", \"$\", \"\\\", \".\", \"*\", \"+\",\n * \"?\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\", and \"|\" in `string`.\n *\n * @static\n * @memberOf _\n * @since 3.0.0\n * @category String\n * @param {string} [string=''] The string to escape.\n * @returns {string} Returns the escaped string.\n * @example\n *\n * _.escapeRegExp('[lodash](https://lodash.com/)');\n * // => '\\[lodash\\]\\(https://lodash\\.com/\\)'\n */\nfunction escapeRegExp(string) {\n string = toString(string);\n return (string && reHasRegExpChar.test(string))\n ? string.replace(reRegExpChar, '\\\\$&')\n : string;\n}\n\nexport default escapeRegExp;\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./search.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/search/text/searchtextview\n*/\nimport { FocusTracker, KeystrokeHandler } from '@ckeditor/ckeditor5-utils';\nimport View from '../../view.js';\nimport { default as SearchTextQueryView } from './searchtextqueryview.js';\nimport SearchInfoView from '../searchinfoview.js';\nimport SearchResultsView from '../searchresultsview.js';\nimport FocusCycler from '../../focuscycler.js';\nimport { escapeRegExp } from 'lodash-es';\nimport '../../../theme/components/search/search.css';\n/**\n * A search component that allows filtering of an arbitrary view based on a search query\n * specified by the user in a text field.\n *\n *```ts\n * // This view must specify the `filter()` and `focus()` methods.\n * const filteredView = ...;\n *\n * const searchView = new SearchTextView( locale, {\n * \tsearchFieldLabel: 'Search list items',\n * \tfilteredView\n * } );\n *\n * view.render();\n *\n * document.body.append( view.element );\n * ```\n */\nexport default class SearchTextView extends View {\n /**\n * Creates an instance of the {@link module:ui/search/text/searchtextview~SearchTextView} class.\n *\n * @param locale The localization services instance.\n * @param config Configuration of the view.\n */\n constructor(locale, config) {\n super(locale);\n this._config = config;\n this.filteredView = config.filteredView;\n this.queryView = this._createSearchTextQueryView();\n this.focusTracker = new FocusTracker();\n this.keystrokes = new KeystrokeHandler();\n this.resultsView = new SearchResultsView(locale);\n this.children = this.createCollection();\n this.focusableChildren = this.createCollection([this.queryView, this.resultsView]);\n this.set('isEnabled', true);\n this.set('resultsCount', 0);\n this.set('totalItemsCount', 0);\n if (config.infoView && config.infoView.instance) {\n this.infoView = config.infoView.instance;\n }\n else {\n this.infoView = new SearchInfoView();\n this._enableDefaultInfoViewBehavior();\n this.on('render', () => {\n // Initial search that determines if there are any searchable items\n // and displays the corresponding info text.\n this.search('');\n });\n }\n this.resultsView.children.addMany([this.infoView, this.filteredView]);\n this.focusCycler = new FocusCycler({\n focusables: this.focusableChildren,\n focusTracker: this.focusTracker,\n keystrokeHandler: this.keystrokes,\n actions: {\n // Navigate form fields backwards using the Shift + Tab keystroke.\n focusPrevious: 'shift + tab',\n // Navigate form fields forwards using the Tab key.\n focusNext: 'tab'\n }\n });\n this.on('search', (evt, { resultsCount, totalItemsCount }) => {\n this.resultsCount = resultsCount;\n this.totalItemsCount = totalItemsCount;\n });\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-search',\n config.class || null\n ],\n tabindex: '-1'\n },\n children: this.children\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n this.children.addMany([\n this.queryView,\n this.resultsView\n ]);\n const stopPropagation = (data) => data.stopPropagation();\n for (const focusableChild of this.focusableChildren) {\n this.focusTracker.add(focusableChild.element);\n }\n // Start listening for the keystrokes coming from #element.\n this.keystrokes.listenTo(this.element);\n // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's\n // keystroke handler would take over the key management in the URL input. We need to prevent\n // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.\n this.keystrokes.set('arrowright', stopPropagation);\n this.keystrokes.set('arrowleft', stopPropagation);\n this.keystrokes.set('arrowup', stopPropagation);\n this.keystrokes.set('arrowdown', stopPropagation);\n }\n /**\n * Focuses the {@link #queryView}.\n */\n focus() {\n this.queryView.focus();\n }\n /**\n * Resets the component to its initial state.\n */\n reset() {\n this.queryView.reset();\n this.search('');\n }\n /**\n * Searches the {@link #filteredView} for the given query.\n *\n * @internal\n * @param query The search query string.\n */\n search(query) {\n const regExp = query ? new RegExp(escapeRegExp(query), 'ig') : null;\n const filteringResults = this.filteredView.filter(regExp);\n this.fire('search', { query, ...filteringResults });\n }\n /**\n * Creates a search field view based on configured creator..\n */\n _createSearchTextQueryView() {\n const queryView = new SearchTextQueryView(this.locale, this._config.queryView);\n this.listenTo(queryView.fieldView, 'input', () => {\n this.search(queryView.fieldView.element.value);\n });\n queryView.on('reset', () => this.reset());\n queryView.bind('isEnabled').to(this);\n return queryView;\n }\n /**\n * Initializes the default {@link #infoView} behavior with default text labels when no custom info view\n * was specified in the view config.\n */\n _enableDefaultInfoViewBehavior() {\n const t = this.locale.t;\n const infoView = this.infoView;\n this.on('search', (evt, data) => {\n if (!data.resultsCount) {\n const defaultTextConfig = this._config.infoView && this._config.infoView.text;\n let primaryText, secondaryText;\n if (data.totalItemsCount) {\n if (defaultTextConfig && defaultTextConfig.notFound) {\n primaryText = defaultTextConfig.notFound.primary;\n secondaryText = defaultTextConfig.notFound.secondary;\n }\n else {\n primaryText = t('No results found');\n secondaryText = '';\n }\n }\n else {\n if (defaultTextConfig && defaultTextConfig.noSearchableItems) {\n primaryText = defaultTextConfig.noSearchableItems.primary;\n secondaryText = defaultTextConfig.noSearchableItems.secondary;\n }\n else {\n primaryText = t('No searchable items');\n secondaryText = '';\n }\n }\n infoView.set({\n primaryText: normalizeInfoText(primaryText, data),\n secondaryText: normalizeInfoText(secondaryText, data),\n isVisible: true\n });\n }\n else {\n infoView.set({\n isVisible: false\n });\n }\n });\n function normalizeInfoText(text, { query, resultsCount, totalItemsCount }) {\n return typeof text === 'function' ? text(query, resultsCount, totalItemsCount) : text;\n }\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./autocomplete.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/autocomplete/autocompleteview\n*/\nimport { getOptimalPosition, global, toUnit, Rect } from '@ckeditor/ckeditor5-utils';\nimport SearchTextView from '../search/text/searchtextview.js';\nimport '../../theme/components/autocomplete/autocomplete.css';\n/**\n * The autocomplete component's view class. It extends the {@link module:ui/search/text/searchtextview~SearchTextView} class\n * with a floating {@link #resultsView} that shows up when the user starts typing and hides when they blur\n * the component.\n */\nclass AutocompleteView extends SearchTextView {\n /**\n * @inheritDoc\n */\n constructor(locale, config) {\n super(locale, config);\n this._config = config;\n const toPx = toUnit('px');\n this.extendTemplate({\n attributes: {\n class: ['ck-autocomplete']\n }\n });\n const bindResultsView = this.resultsView.bindTemplate;\n this.resultsView.set('isVisible', false);\n this.resultsView.set('_position', 's');\n this.resultsView.set('_width', 0);\n this.resultsView.extendTemplate({\n attributes: {\n class: [\n bindResultsView.if('isVisible', 'ck-hidden', value => !value),\n bindResultsView.to('_position', value => `ck-search__results_${value}`)\n ],\n style: {\n width: bindResultsView.to('_width', toPx)\n }\n }\n });\n // Update the visibility of the results view when the user focuses or blurs the component.\n // This is also integration for the `resetOnBlur` configuration.\n this.focusTracker.on('change:isFocused', (evt, name, isFocused) => {\n this._updateResultsVisibility();\n if (isFocused) {\n // Reset the scroll position of the results view whenever the autocomplete reopens.\n this.resultsView.element.scrollTop = 0;\n }\n else if (config.resetOnBlur) {\n this.queryView.reset();\n }\n });\n // Update the visibility of the results view when the user types in the query field.\n // This is an integration for `queryMinChars` configuration.\n // This is an integration for search results changing length and the #resultsView requiring to be repositioned.\n this.on('search', () => {\n this._updateResultsVisibility();\n this._updateResultsViewWidthAndPosition();\n });\n // Hide the results view when the user presses the ESC key.\n this.keystrokes.set('esc', (evt, cancel) => {\n // Let the DOM event pass through if the focus is in the query view.\n if (!this.resultsView.isVisible) {\n return;\n }\n // Focus the query view first and only then close the results view. Otherwise, if the focus\n // was in the results view, it will get lost.\n this.queryView.focus();\n this.resultsView.isVisible = false;\n cancel();\n });\n // Update the position of the results view when the user scrolls the page.\n // TODO: This needs to be debounced down the road.\n this.listenTo(global.document, 'scroll', () => {\n this._updateResultsViewWidthAndPosition();\n });\n // Hide the results when the component becomes disabled.\n this.on('change:isEnabled', () => {\n this._updateResultsVisibility();\n });\n // Update the value of the query field when the user selects a result.\n this.filteredView.on('execute', (evt, { value }) => {\n // Focus the query view first to avoid losing the focus.\n this.focus();\n // Resetting the view will ensure that the #queryView will update its empty state correctly.\n // This prevents bugs related to dynamic labels or auto-grow when re-setting the same value\n // to #queryView.fieldView.value (which does not trigger empty state change) to an\n // #queryView.fieldView.element that has been changed by the user.\n this.reset();\n // Update the value of the query field.\n this.queryView.fieldView.value = this.queryView.fieldView.element.value = value;\n // Finally, hide the results view. The focus has been moved earlier so this is safe.\n this.resultsView.isVisible = false;\n });\n // Update the position and width of the results view when it becomes visible.\n this.resultsView.on('change:isVisible', () => {\n this._updateResultsViewWidthAndPosition();\n });\n }\n /**\n * Updates the position of the results view on demand.\n */\n _updateResultsViewWidthAndPosition() {\n if (!this.resultsView.isVisible) {\n return;\n }\n this.resultsView._width = new Rect(this.queryView.fieldView.element).width;\n const optimalResultsPosition = AutocompleteView._getOptimalPosition({\n element: this.resultsView.element,\n target: this.queryView.element,\n fitInViewport: true,\n positions: AutocompleteView.defaultResultsPositions\n });\n // _getOptimalPosition will return null if there is no optimal position found (e.g. target is off the viewport).\n this.resultsView._position = optimalResultsPosition ? optimalResultsPosition.name : 's';\n }\n /**\n * Updates the visibility of the results view on demand.\n */\n _updateResultsVisibility() {\n const queryMinChars = typeof this._config.queryMinChars === 'undefined' ? 0 : this._config.queryMinChars;\n const queryLength = this.queryView.fieldView.element.value.length;\n this.resultsView.isVisible = this.focusTracker.isFocused && this.isEnabled && queryLength >= queryMinChars;\n }\n}\n/**\n * Positions for the autocomplete results view. Two positions are defined by default:\n * * `s` - below the search field,\n * * `n` - above the search field.\n */\nAutocompleteView.defaultResultsPositions = [\n (fieldRect => {\n return {\n top: fieldRect.bottom,\n left: fieldRect.left,\n name: 's'\n };\n }),\n ((fieldRect, resultsRect) => {\n return {\n top: fieldRect.top - resultsRect.height,\n left: fieldRect.left,\n name: 'n'\n };\n })\n];\n/**\n * A function used to calculate the optimal position for the dropdown panel.\n */\nAutocompleteView._getOptimalPosition = getOptimalPosition;\nexport default AutocompleteView;\n","/**\n * The base implementation of `_.propertyOf` without support for deep paths.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Function} Returns the new accessor function.\n */\nfunction basePropertyOf(object) {\n return function(key) {\n return object == null ? undefined : object[key];\n };\n}\n\nexport default basePropertyOf;\n","import basePropertyOf from './_basePropertyOf.js';\n\n/** Used to map characters to HTML entities. */\nvar htmlEscapes = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n\n/**\n * Used by `_.escape` to convert characters to HTML entities.\n *\n * @private\n * @param {string} chr The matched character to escape.\n * @returns {string} Returns the escaped character.\n */\nvar escapeHtmlChar = basePropertyOf(htmlEscapes);\n\nexport default escapeHtmlChar;\n","import escapeHtmlChar from './_escapeHtmlChar.js';\nimport toString from './toString.js';\n\n/** Used to match HTML entities and HTML characters. */\nvar reUnescapedHtml = /[&<>\"']/g,\n reHasUnescapedHtml = RegExp(reUnescapedHtml.source);\n\n/**\n * Converts the characters \"&\", \"<\", \">\", '\"', and \"'\" in `string` to their\n * corresponding HTML entities.\n *\n * **Note:** No other characters are escaped. To escape additional\n * characters use a third-party library like [_he_](https://mths.be/he).\n *\n * Though the \">\" character is escaped for symmetry, characters like\n * \">\" and \"/\" don't need escaping in HTML and have no special meaning\n * unless they're part of a tag or unquoted attribute value. See\n * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)\n * (under \"semi-related fun fact\") for more details.\n *\n * When working with HTML you should always\n * [quote attribute values](http://wonko.com/post/html-escaping) to reduce\n * XSS vectors.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category String\n * @param {string} [string=''] The string to escape.\n * @returns {string} Returns the escaped string.\n * @example\n *\n * _.escape('fred, barney, & pebbles');\n * // => 'fred, barney, & pebbles'\n */\nfunction escape(string) {\n string = toString(string);\n return (string && reHasUnescapedHtml.test(string))\n ? string.replace(reUnescapedHtml, escapeHtmlChar)\n : string;\n}\n\nexport default escape;\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./highlightedtext.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./spinner.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/toolbar/balloon/balloontoolbar\n */\nimport ContextualBalloon from '../../panel/balloon/contextualballoon.js';\nimport ToolbarView from '../toolbarview.js';\nimport BalloonPanelView, { generatePositions } from '../../panel/balloon/balloonpanelview.js';\nimport normalizeToolbarConfig from '../normalizetoolbarconfig.js';\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { FocusTracker, Rect, ResizeObserver, env, global, toUnit } from '@ckeditor/ckeditor5-utils';\nimport { debounce } from 'lodash-es';\nconst toPx = toUnit('px');\n/**\n * The contextual toolbar.\n *\n * It uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.\n */\nexport default class BalloonToolbar extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'BalloonToolbar';\n }\n /**\n * @inheritDoc\n */\n static get requires() {\n return [ContextualBalloon];\n }\n /**\n * @inheritDoc\n */\n constructor(editor) {\n super(editor);\n /**\n * An instance of the resize observer that allows to respond to changes in editable's geometry\n * so the toolbar can stay within its boundaries (and group toolbar items that do not fit).\n *\n * **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the\n * {@link module:core/editor/editorconfig~EditorConfig#balloonToolbar configuration}.\n *\n * **Note:** Created in {@link #init}.\n */\n this._resizeObserver = null;\n this._balloonConfig = normalizeToolbarConfig(editor.config.get('balloonToolbar'));\n this.toolbarView = this._createToolbarView();\n this.focusTracker = new FocusTracker();\n // Wait for the EditorUI#init. EditableElement is not available before.\n editor.ui.once('ready', () => {\n this.focusTracker.add(editor.ui.getEditableElement());\n this.focusTracker.add(this.toolbarView.element);\n });\n // Register the toolbar so it becomes available for Alt+F10 and Esc navigation.\n editor.ui.addToolbar(this.toolbarView, {\n beforeFocus: () => this.show(true),\n afterBlur: () => this.hide(),\n isContextual: true\n });\n this._balloon = editor.plugins.get(ContextualBalloon);\n this._fireSelectionChangeDebounced = debounce(() => this.fire('_selectionChangeDebounced'), 200);\n // The appearance of the BalloonToolbar method is event–driven.\n // It is possible to stop the #show event and this prevent the toolbar from showing up.\n this.decorate('show');\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const selection = editor.model.document.selection;\n // Show/hide the toolbar on editable focus/blur.\n this.listenTo(this.focusTracker, 'change:isFocused', (evt, name, isFocused) => {\n const isToolbarVisible = this._balloon.visibleView === this.toolbarView;\n if (!isFocused && isToolbarVisible) {\n this.hide();\n }\n else if (isFocused) {\n this.show();\n }\n });\n // Hide the toolbar when the selection is changed by a direct change or has changed to collapsed.\n this.listenTo(selection, 'change:range', (evt, data) => {\n if (data.directChange || selection.isCollapsed) {\n this.hide();\n }\n // Fire internal `_selectionChangeDebounced` event to use it for showing\n // the toolbar after the selection stops changing.\n this._fireSelectionChangeDebounced();\n });\n // Show the toolbar when the selection stops changing.\n this.listenTo(this, '_selectionChangeDebounced', () => {\n if (this.editor.editing.view.document.isFocused) {\n this.show();\n }\n });\n if (!this._balloonConfig.shouldNotGroupWhenFull) {\n this.listenTo(editor, 'ready', () => {\n const editableElement = editor.ui.view.editable.element;\n // Set #toolbarView's max-width on the initialization and update it on the editable resize.\n this._resizeObserver = new ResizeObserver(editableElement, entry => {\n // The max-width equals 90% of the editable's width for the best user experience.\n // The value keeps the balloon very close to the boundaries of the editable and limits the cases\n // when the balloon juts out from the editable element it belongs to.\n this.toolbarView.maxWidth = toPx(entry.contentRect.width * .9);\n });\n });\n }\n // Listen to the toolbar view and whenever it changes its geometry due to some items being\n // grouped or ungrouped, update the position of the balloon because a shorter/longer toolbar\n // means the balloon could be pointing at the wrong place. Once updated, the balloon will point\n // at the right selection in the content again.\n // https://github.com/ckeditor/ckeditor5/issues/6444\n this.listenTo(this.toolbarView, 'groupedItemsUpdate', () => {\n this._updatePosition();\n });\n // Creates toolbar components based on given configuration.\n // This needs to be done when all plugins are ready.\n editor.ui.once('ready', () => {\n this.toolbarView.fillFromConfig(this._balloonConfig, this.editor.ui.componentFactory);\n });\n }\n /**\n * Creates the toolbar view instance.\n */\n _createToolbarView() {\n const t = this.editor.locale.t;\n const shouldGroupWhenFull = !this._balloonConfig.shouldNotGroupWhenFull;\n const toolbarView = new ToolbarView(this.editor.locale, {\n shouldGroupWhenFull,\n isFloating: true\n });\n toolbarView.ariaLabel = t('Editor contextual toolbar');\n toolbarView.render();\n return toolbarView;\n }\n /**\n * Shows the toolbar and attaches it to the selection.\n *\n * Fires {@link #event:show} event which can be stopped to prevent the toolbar from showing up.\n *\n * @param showForCollapsedSelection When set `true`, the toolbar will show despite collapsed selection in the\n * editing view.\n */\n show(showForCollapsedSelection = false) {\n const editor = this.editor;\n const selection = editor.model.document.selection;\n const schema = editor.model.schema;\n // Do not add the toolbar to the balloon stack twice.\n if (this._balloon.hasView(this.toolbarView)) {\n return;\n }\n // Do not show the toolbar when the selection is collapsed.\n if (selection.isCollapsed && !showForCollapsedSelection) {\n return;\n }\n // Do not show the toolbar when there is more than one range in the selection and they fully contain selectable elements.\n // See https://github.com/ckeditor/ckeditor5/issues/6443.\n if (selectionContainsOnlyMultipleSelectables(selection, schema)) {\n return;\n }\n // Don not show the toolbar when all components inside are disabled\n // see https://github.com/ckeditor/ckeditor5-ui/issues/269.\n if (Array.from(this.toolbarView.items).every((item) => item.isEnabled !== undefined && !item.isEnabled)) {\n return;\n }\n // Update the toolbar position when the editor ui should be refreshed.\n this.listenTo(this.editor.ui, 'update', () => {\n this._updatePosition();\n });\n // Add the toolbar to the common editor contextual balloon.\n this._balloon.add({\n view: this.toolbarView,\n position: this._getBalloonPositionData(),\n balloonClassName: 'ck-toolbar-container'\n });\n }\n /**\n * Hides the toolbar.\n */\n hide() {\n if (this._balloon.hasView(this.toolbarView)) {\n this.stopListening(this.editor.ui, 'update');\n this._balloon.remove(this.toolbarView);\n }\n }\n /**\n * Returns positioning options for the {@link #_balloon}. They control the way balloon is attached\n * to the selection.\n */\n _getBalloonPositionData() {\n const editor = this.editor;\n const view = editor.editing.view;\n const viewDocument = view.document;\n const viewSelection = viewDocument.selection;\n // Get direction of the selection.\n const isBackward = viewDocument.selection.isBackward;\n return {\n // Because the target for BalloonPanelView is a Rect (not DOMRange), it's geometry will stay fixed\n // as the window scrolls. To let the BalloonPanelView follow such Rect, is must be continuously\n // computed and hence, the target is defined as a function instead of a static value.\n // https://github.com/ckeditor/ckeditor5-ui/issues/195\n target: () => {\n const range = isBackward ? viewSelection.getFirstRange() : viewSelection.getLastRange();\n const rangeRects = Rect.getDomRangeRects(view.domConverter.viewRangeToDom(range));\n // Select the proper range rect depending on the direction of the selection.\n if (isBackward) {\n return rangeRects[0];\n }\n else {\n // Ditch the zero-width \"orphan\" rect in the next line for the forward selection if there's\n // another one preceding it. It is not rendered as a selection by the web browser anyway.\n // https://github.com/ckeditor/ckeditor5-ui/issues/308\n if (rangeRects.length > 1 && rangeRects[rangeRects.length - 1].width === 0) {\n rangeRects.pop();\n }\n return rangeRects[rangeRects.length - 1];\n }\n },\n positions: this._getBalloonPositions(isBackward)\n };\n }\n /**\n * Updates the position of the {@link #_balloon} to make up for changes:\n *\n * * in the geometry of the selection it is attached to (e.g. the selection moved in the viewport or expanded or shrunk),\n * * or the geometry of the balloon toolbar itself (e.g. the toolbar has grouped or ungrouped some items and it is shorter or longer).\n */\n _updatePosition() {\n this._balloon.updatePosition(this._getBalloonPositionData());\n }\n /**\n * @inheritDoc\n */\n destroy() {\n super.destroy();\n this.stopListening();\n this._fireSelectionChangeDebounced.cancel();\n this.toolbarView.destroy();\n this.focusTracker.destroy();\n if (this._resizeObserver) {\n this._resizeObserver.destroy();\n }\n }\n /**\n * Returns toolbar positions for the given direction of the selection.\n */\n _getBalloonPositions(isBackward) {\n const isSafariIniOS = env.isSafari && env.isiOS;\n // https://github.com/ckeditor/ckeditor5/issues/7707\n const positions = isSafariIniOS ? generatePositions({\n // 20px when zoomed out. Less then 20px when zoomed in; the \"radius\" of the native selection handle gets\n // smaller as the user zooms in. No less than the default v-offset, though.\n heightOffset: Math.max(BalloonPanelView.arrowHeightOffset, Math.round(20 / global.window.visualViewport.scale))\n }) : BalloonPanelView.defaultPositions;\n return isBackward ? [\n positions.northWestArrowSouth,\n positions.northWestArrowSouthWest,\n positions.northWestArrowSouthEast,\n positions.northWestArrowSouthMiddleEast,\n positions.northWestArrowSouthMiddleWest,\n positions.southWestArrowNorth,\n positions.southWestArrowNorthWest,\n positions.southWestArrowNorthEast,\n positions.southWestArrowNorthMiddleWest,\n positions.southWestArrowNorthMiddleEast\n ] : [\n positions.southEastArrowNorth,\n positions.southEastArrowNorthEast,\n positions.southEastArrowNorthWest,\n positions.southEastArrowNorthMiddleEast,\n positions.southEastArrowNorthMiddleWest,\n positions.northEastArrowSouth,\n positions.northEastArrowSouthEast,\n positions.northEastArrowSouthWest,\n positions.northEastArrowSouthMiddleEast,\n positions.northEastArrowSouthMiddleWest\n ];\n }\n}\n/**\n * Returns \"true\" when the selection has multiple ranges and each range contains a selectable element\n * and nothing else.\n */\nfunction selectionContainsOnlyMultipleSelectables(selection, schema) {\n // It doesn't contain multiple objects if there is only one range.\n if (selection.rangeCount === 1) {\n return false;\n }\n return [...selection.getRanges()].every(range => {\n const element = range.getContainedElement();\n return element && schema.isSelectable(element);\n });\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./blocktoolbar.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/toolbar/block/blockbuttonview\n */\nimport ButtonView from '../../button/buttonview.js';\nimport { toUnit } from '@ckeditor/ckeditor5-utils';\nimport '../../../theme/components/toolbar/blocktoolbar.css';\nconst toPx = toUnit('px');\n/**\n * The block button view class.\n *\n * This view represents a button attached next to block element where the selection is anchored.\n *\n * See {@link module:ui/toolbar/block/blocktoolbar~BlockToolbar}.\n */\nexport default class BlockButtonView extends ButtonView {\n /**\n * @inheritDoc\n */\n constructor(locale) {\n super(locale);\n const bind = this.bindTemplate;\n // Hide button on init.\n this.isVisible = false;\n this.isToggleable = true;\n this.set('top', 0);\n this.set('left', 0);\n this.extendTemplate({\n attributes: {\n class: 'ck-block-toolbar-button',\n style: {\n top: bind.to('top', val => toPx(val)),\n left: bind.to('left', val => toPx(val))\n }\n }\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/toolbar/block/blocktoolbar\n */\n/* global window */\nimport { Plugin } from '@ckeditor/ckeditor5-core';\nimport { Rect, ResizeObserver, toUnit } from '@ckeditor/ckeditor5-utils';\nimport BlockButtonView from './blockbuttonview.js';\nimport BalloonPanelView from '../../panel/balloon/balloonpanelview.js';\nimport ToolbarView, { NESTED_TOOLBAR_ICONS } from '../toolbarview.js';\nimport clickOutsideHandler from '../../bindings/clickoutsidehandler.js';\nimport normalizeToolbarConfig from '../normalizetoolbarconfig.js';\nconst toPx = toUnit('px');\n/**\n * The block toolbar plugin.\n *\n * This plugin provides a button positioned next to the block of content where the selection is anchored.\n * Upon clicking the button, a dropdown providing access to editor features shows up, as configured in\n * {@link module:core/editor/editorconfig~EditorConfig#blockToolbar}.\n *\n * By default, the button is displayed next to all elements marked in {@link module:engine/model/schema~Schema}\n * as `$block` for which the toolbar provides at least one option.\n *\n * By default, the button is attached so its right boundary is touching the\n * {@link module:engine/view/editableelement~EditableElement}:\n *\n * ```\n * __ |\n * | || This is a block of content that the\n * ¯¯ | button is attached to. This is a\n * | block of content that the button is\n * | attached to.\n * ```\n *\n * The position of the button can be adjusted using the CSS `transform` property:\n *\n * ```css\n * .ck-block-toolbar-button {\n * \ttransform: translateX( -10px );\n * }\n * ```\n *\n * ```\n * __ |\n * | | | This is a block of content that the\n * ¯¯ | button is attached to. This is a\n * | block of content that the button is\n * | attached to.\n * ```\n *\n * **Note**: If you plan to run the editor in a right–to–left (RTL) language, keep in mind the button\n * will be attached to the **right** boundary of the editable area. In that case, make sure the\n * CSS position adjustment works properly by adding the following styles:\n *\n * ```css\n * .ck[dir=\"rtl\"] .ck-block-toolbar-button {\n * \ttransform: translateX( 10px );\n * }\n * ```\n */\nexport default class BlockToolbar extends Plugin {\n /**\n * @inheritDoc\n */\n static get pluginName() {\n return 'BlockToolbar';\n }\n /**\n * @inheritDoc\n */\n constructor(editor) {\n super(editor);\n /**\n * An instance of the resize observer that allows to respond to changes in editable's geometry\n * so the toolbar can stay within its boundaries (and group toolbar items that do not fit).\n *\n * **Note**: Used only when `shouldNotGroupWhenFull` was **not** set in the\n * {@link module:core/editor/editorconfig~EditorConfig#blockToolbar configuration}.\n */\n this._resizeObserver = null;\n this._blockToolbarConfig = normalizeToolbarConfig(this.editor.config.get('blockToolbar'));\n this.toolbarView = this._createToolbarView();\n this.panelView = this._createPanelView();\n this.buttonView = this._createButtonView();\n // Close the #panelView upon clicking outside of the plugin UI.\n clickOutsideHandler({\n emitter: this.panelView,\n contextElements: [this.panelView.element, this.buttonView.element],\n activator: () => this.panelView.isVisible,\n callback: () => this._hidePanel()\n });\n }\n /**\n * @inheritDoc\n */\n init() {\n const editor = this.editor;\n const t = editor.t;\n const editBlockText = t('Click to edit block');\n const dragToMoveText = t('Drag to move');\n const editBlockLabel = t('Edit block');\n const isDragDropBlockToolbarPluginLoaded = editor.plugins.has('DragDropBlockToolbar');\n const label = isDragDropBlockToolbarPluginLoaded ? `${editBlockText}\\n${dragToMoveText}` : editBlockLabel;\n this.buttonView.label = label;\n if (isDragDropBlockToolbarPluginLoaded) {\n this.buttonView.element.dataset.ckeTooltipClass = 'ck-tooltip_multi-line';\n }\n // Hides panel on a direct selection change.\n this.listenTo(editor.model.document.selection, 'change:range', (evt, data) => {\n if (data.directChange) {\n this._hidePanel();\n }\n });\n this.listenTo(editor.ui, 'update', () => this._updateButton());\n // `low` priority is used because of https://github.com/ckeditor/ckeditor5-core/issues/133.\n this.listenTo(editor, 'change:isReadOnly', () => this._updateButton(), { priority: 'low' });\n this.listenTo(editor.ui.focusTracker, 'change:isFocused', () => this._updateButton());\n // Reposition button on resize.\n this.listenTo(this.buttonView, 'change:isVisible', (evt, name, isVisible) => {\n if (isVisible) {\n // Keep correct position of button and panel on window#resize.\n this.buttonView.listenTo(window, 'resize', () => this._updateButton());\n }\n else {\n // Stop repositioning button when is hidden.\n this.buttonView.stopListening(window, 'resize');\n // Hide the panel when the button disappears.\n this._hidePanel();\n }\n });\n // Register the toolbar so it becomes available for Alt+F10 and Esc navigation.\n editor.ui.addToolbar(this.toolbarView, {\n beforeFocus: () => this._showPanel(),\n afterBlur: () => this._hidePanel()\n });\n // Fills the toolbar with its items based on the configuration.\n // This needs to be done after all plugins are ready.\n editor.ui.once('ready', () => {\n this.toolbarView.fillFromConfig(this._blockToolbarConfig, this.editor.ui.componentFactory);\n // Hide panel before executing each button in the panel.\n for (const item of this.toolbarView.items) {\n item.on('execute', () => this._hidePanel(true), { priority: 'high' });\n }\n });\n }\n /**\n * @inheritDoc\n */\n destroy() {\n super.destroy();\n // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).\n this.panelView.destroy();\n this.buttonView.destroy();\n this.toolbarView.destroy();\n if (this._resizeObserver) {\n this._resizeObserver.destroy();\n }\n }\n /**\n * Creates the {@link #toolbarView}.\n */\n _createToolbarView() {\n const t = this.editor.locale.t;\n const shouldGroupWhenFull = !this._blockToolbarConfig.shouldNotGroupWhenFull;\n const toolbarView = new ToolbarView(this.editor.locale, {\n shouldGroupWhenFull,\n isFloating: true\n });\n toolbarView.ariaLabel = t('Editor block content toolbar');\n return toolbarView;\n }\n /**\n * Creates the {@link #panelView}.\n */\n _createPanelView() {\n const editor = this.editor;\n const panelView = new BalloonPanelView(editor.locale);\n panelView.content.add(this.toolbarView);\n panelView.class = 'ck-toolbar-container';\n editor.ui.view.body.add(panelView);\n editor.ui.focusTracker.add(panelView.element);\n // Close #panelView on `Esc` press.\n this.toolbarView.keystrokes.set('Esc', (evt, cancel) => {\n this._hidePanel(true);\n cancel();\n });\n return panelView;\n }\n /**\n * Creates the {@link #buttonView}.\n */\n _createButtonView() {\n const editor = this.editor;\n const t = editor.t;\n const buttonView = new BlockButtonView(editor.locale);\n const iconFromConfig = this._blockToolbarConfig.icon;\n const icon = NESTED_TOOLBAR_ICONS[iconFromConfig] || iconFromConfig || NESTED_TOOLBAR_ICONS.dragIndicator;\n buttonView.set({\n label: t('Edit block'),\n icon,\n withText: false\n });\n // Bind the panelView observable properties to the buttonView.\n buttonView.bind('isOn').to(this.panelView, 'isVisible');\n buttonView.bind('tooltip').to(this.panelView, 'isVisible', isVisible => !isVisible);\n // Toggle the panelView upon buttonView#execute.\n this.listenTo(buttonView, 'execute', () => {\n if (!this.panelView.isVisible) {\n this._showPanel();\n }\n else {\n this._hidePanel(true);\n }\n });\n editor.ui.view.body.add(buttonView);\n editor.ui.focusTracker.add(buttonView.element);\n return buttonView;\n }\n /**\n * Shows or hides the button.\n * When all the conditions for displaying the button are matched, it shows the button. Hides otherwise.\n */\n _updateButton() {\n const editor = this.editor;\n const model = editor.model;\n const view = editor.editing.view;\n // Hides the button when the editor is not focused.\n if (!editor.ui.focusTracker.isFocused) {\n this._hideButton();\n return;\n }\n // Hides the button when the selection is in non-editable place.\n if (!editor.model.canEditAt(editor.model.document.selection)) {\n this._hideButton();\n return;\n }\n // Get the first selected block, button will be attached to this element.\n const modelTarget = Array.from(model.document.selection.getSelectedBlocks())[0];\n // Hides the button when there is no enabled item in toolbar for the current block element.\n if (!modelTarget || Array.from(this.toolbarView.items).every((item) => !item.isEnabled)) {\n this._hideButton();\n return;\n }\n // Get DOM target element.\n const domTarget = view.domConverter.mapViewToDom(editor.editing.mapper.toViewElement(modelTarget));\n // Show block button.\n this.buttonView.isVisible = true;\n // Make sure that the block toolbar panel is resized properly.\n this._setupToolbarResize();\n // Attach block button to target DOM element.\n this._attachButtonToElement(domTarget);\n // When panel is opened then refresh it position to be properly aligned with block button.\n if (this.panelView.isVisible) {\n this._showPanel();\n }\n }\n /**\n * Hides the button.\n */\n _hideButton() {\n this.buttonView.isVisible = false;\n }\n /**\n * Shows the {@link #toolbarView} attached to the {@link #buttonView}.\n * If the toolbar is already visible, then it simply repositions it.\n */\n _showPanel() {\n // Usually, the only way to show the toolbar is by pressing the block button. It makes it impossible for\n // the toolbar to show up when the button is invisible (feature does not make sense for the selection then).\n // The toolbar navigation using Alt+F10 does not access the button but shows the panel directly using this method.\n // So we need to check whether this is possible first.\n if (!this.buttonView.isVisible) {\n return;\n }\n const wasVisible = this.panelView.isVisible;\n // So here's the thing: If there was no initial panelView#show() or these two were in different order, the toolbar\n // positioning will break in RTL editors. Weird, right? What you show know is that the toolbar\n // grouping works thanks to:\n //\n // * the ResizeObserver, which kicks in as soon as the toolbar shows up in DOM (becomes visible again).\n // * the observable ToolbarView#maxWidth, which triggers re-grouping when changed.\n //\n // Here are the possible scenarios:\n //\n // 1. (WRONG ❌) If the #maxWidth is set when the toolbar is invisible, it won't affect item grouping (no DOMRects, no grouping).\n // Then, when panelView.pin() is called, the position of the toolbar will be calculated for the old\n // items grouping state, and when finally ResizeObserver kicks in (hey, the toolbar is visible now, right?)\n // it will group/ungroup some items and the length of the toolbar will change. But since in RTL the toolbar\n // is attached on the right side and the positioning uses CSS \"left\", it will result in the toolbar shifting\n // to the left and being displayed in the wrong place.\n // 2. (WRONG ❌) If the panelView.pin() is called first and #maxWidth set next, then basically the story repeats. The balloon\n // calculates the position for the old toolbar grouping state, then the toolbar re-groups items and because\n // it is positioned using CSS \"left\" it will move.\n // 3. (RIGHT ✅) We show the panel first (the toolbar does re-grouping but it does not matter), then the #maxWidth\n // is set allowing the toolbar to re-group again and finally panelView.pin() does the positioning when the\n // items grouping state is stable and final.\n //\n // https://github.com/ckeditor/ckeditor5/issues/6449, https://github.com/ckeditor/ckeditor5/issues/6575\n this.panelView.show();\n const editableElement = this._getSelectedEditableElement();\n this.toolbarView.maxWidth = this._getToolbarMaxWidth(editableElement);\n this.panelView.pin({\n target: this.buttonView.element,\n limiter: editableElement\n });\n if (!wasVisible) {\n this.toolbarView.items.get(0).focus();\n }\n }\n /**\n * Returns currently selected editable, based on the model selection.\n */\n _getSelectedEditableElement() {\n const selectedModelRootName = this.editor.model.document.selection.getFirstRange().root.rootName;\n return this.editor.ui.getEditableElement(selectedModelRootName);\n }\n /**\n * Hides the {@link #toolbarView}.\n *\n * @param focusEditable When `true`, the editable will be focused after hiding the panel.\n */\n _hidePanel(focusEditable) {\n this.panelView.isVisible = false;\n if (focusEditable) {\n this.editor.editing.view.focus();\n }\n }\n /**\n * Attaches the {@link #buttonView} to the target block of content.\n *\n * @param targetElement Target element.\n */\n _attachButtonToElement(targetElement) {\n const contentStyles = window.getComputedStyle(targetElement);\n const editableRect = new Rect(this._getSelectedEditableElement());\n const contentPaddingTop = parseInt(contentStyles.paddingTop, 10);\n // When line height is not an integer then treat it as \"normal\".\n // MDN says that 'normal' == ~1.2 on desktop browsers.\n const contentLineHeight = parseInt(contentStyles.lineHeight, 10) || parseInt(contentStyles.fontSize, 10) * 1.2;\n const buttonRect = new Rect(this.buttonView.element);\n const contentRect = new Rect(targetElement);\n let positionLeft;\n if (this.editor.locale.uiLanguageDirection === 'ltr') {\n positionLeft = editableRect.left - buttonRect.width;\n }\n else {\n positionLeft = editableRect.right;\n }\n const positionTop = contentRect.top + contentPaddingTop + (contentLineHeight - buttonRect.height) / 2;\n buttonRect.moveTo(positionLeft, positionTop);\n const absoluteButtonRect = buttonRect.toAbsoluteRect();\n this.buttonView.top = absoluteButtonRect.top;\n this.buttonView.left = absoluteButtonRect.left;\n }\n /**\n * Creates a resize observer that observes selected editable and resizes the toolbar panel accordingly.\n */\n _setupToolbarResize() {\n const editableElement = this._getSelectedEditableElement();\n // Do this only if the automatic grouping is turned on.\n if (!this._blockToolbarConfig.shouldNotGroupWhenFull) {\n // If resize observer is attached to a different editable than currently selected editable, re-attach it.\n if (this._resizeObserver && this._resizeObserver.element !== editableElement) {\n this._resizeObserver.destroy();\n this._resizeObserver = null;\n }\n if (!this._resizeObserver) {\n this._resizeObserver = new ResizeObserver(editableElement, () => {\n this.toolbarView.maxWidth = this._getToolbarMaxWidth(editableElement);\n });\n }\n }\n }\n /**\n * Gets the {@link #toolbarView} max-width, based on given `editableElement` width plus the distance between the farthest\n * edge of the {@link #buttonView} and the editable.\n *\n * @returns A maximum width that toolbar can have, in pixels.\n */\n _getToolbarMaxWidth(editableElement) {\n const editableRect = new Rect(editableElement);\n const buttonRect = new Rect(this.buttonView.element);\n const isRTL = this.editor.locale.uiLanguageDirection === 'rtl';\n const offset = isRTL ? (buttonRect.left - editableRect.right) + buttonRect.width : editableRect.left - buttonRect.left;\n return toPx(editableRect.width + offset);\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubarmenubutton.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/menubar/menubarmenubuttonview\n */\nimport IconView from '../icon/iconview.js';\nimport ButtonView from '../button/buttonview.js';\nimport dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg';\nimport '../../theme/components/menubar/menubarmenubutton.css';\n/**\n * A menu {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#buttonView} class. Buttons like this one\n * open both top-level bar menus as well as sub-menus.\n */\nexport default class MenuBarMenuButtonView extends ButtonView {\n /**\n * Creates an instance of the menu bar button view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n const bind = this.bindTemplate;\n this.set({\n withText: true,\n role: 'menuitem'\n });\n this.arrowView = this._createArrowView();\n this.extendTemplate({\n attributes: {\n class: [\n 'ck-menu-bar__menu__button'\n ],\n 'aria-haspopup': true,\n 'aria-expanded': this.bindTemplate.to('isOn', value => String(value)),\n 'data-cke-tooltip-disabled': bind.to('isOn')\n },\n on: {\n 'mouseenter': bind.to('mouseenter')\n }\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n this.children.add(this.arrowView);\n }\n /**\n * Creates the {@link #arrowView} instance.\n */\n _createArrowView() {\n const arrowView = new IconView();\n arrowView.content = dropdownArrowIcon;\n arrowView.extendTemplate({\n attributes: {\n class: 'ck-menu-bar__menu__button__arrow'\n }\n });\n return arrowView;\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubarmenulistitem.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport ListItemView from '../list/listitemview.js';\nimport '../../theme/components/menubar/menubarmenulistitem.css';\n/**\n * A menu bar list item view, a child of {@link module:ui/menubar/menubarmenulistview~MenuBarMenuListView}.\n *\n * Populate this item with a {@link module:ui/menubar/menubarmenulistitembuttonview~MenuBarMenuListItemButtonView} instance\n * or a {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} instance to create a sub-menu.\n */\nexport default class MenuBarMenuListItemView extends ListItemView {\n /**\n * Creates an instance of the list item view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale, parentMenuView) {\n super(locale);\n const bind = this.bindTemplate;\n this.extendTemplate({\n attributes: {\n class: [\n 'ck-menu-bar__menu__item'\n ]\n },\n on: {\n 'mouseenter': bind.to('mouseenter')\n }\n });\n this.delegate('mouseenter').to(parentMenuView);\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/menubar/utils\n */\nimport clickOutsideHandler from '../bindings/clickoutsidehandler.js';\nimport MenuBarMenuListItemView from './menubarmenulistitemview.js';\nimport { cloneDeep } from 'lodash-es';\nimport { logWarning } from '@ckeditor/ckeditor5-utils';\nconst NESTED_PANEL_HORIZONTAL_OFFSET = 5;\n/**\n * Behaviors of the {@link module:ui/menubar/menubarview~MenuBarView} component.\n */\nexport const MenuBarBehaviors = {\n /**\n * When the bar is already open:\n * * Opens the menu when the user hovers over its button.\n * * Closes open menu when another menu's button gets hovered.\n */\n toggleMenusAndFocusItemsOnHover(menuBarView) {\n menuBarView.on('menu:mouseenter', evt => {\n // This works only when the menu bar has already been open and the user hover over the menu bar.\n if (!menuBarView.isOpen) {\n return;\n }\n for (const menuView of menuBarView.menus) {\n // @if CK_DEBUG_MENU_BAR // const wasOpen = menuView.isOpen;\n const pathLeaf = evt.path[0];\n const isListItemContainingMenu = pathLeaf instanceof MenuBarMenuListItemView && pathLeaf.children.first === menuView;\n menuView.isOpen = (evt.path.includes(menuView) || isListItemContainingMenu) && menuView.isEnabled;\n // @if CK_DEBUG_MENU_BAR // if ( wasOpen !== menuView.isOpen ) {\n // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] toggleMenusAndFocusItemsOnHover(): Toggle',\n // @if CK_DEBUG_MENU_BAR // \tlogMenu( menuView ), 'isOpen', menuView.isOpen\n // @if CK_DEBUG_MENU_BAR // );\n // @if CK_DEBUG_MENU_BAR // }\n }\n evt.source.focus();\n });\n },\n /**\n * Moves between top-level menus using the arrow left and right keys.\n *\n * If the menubar has already been open, the arrow keys move focus between top-level menu buttons and open them.\n * If the menubar is closed, the arrow keys only move focus between top-level menu buttons.\n */\n focusCycleMenusOnArrows(menuBarView) {\n const isContentRTL = menuBarView.locale.uiLanguageDirection === 'rtl';\n menuBarView.on('menu:arrowright', evt => {\n cycleTopLevelMenus(evt.source, isContentRTL ? -1 : 1);\n });\n menuBarView.on('menu:arrowleft', evt => {\n cycleTopLevelMenus(evt.source, isContentRTL ? 1 : -1);\n });\n function cycleTopLevelMenus(currentMenuView, step) {\n const currentIndex = menuBarView.children.getIndex(currentMenuView);\n const isCurrentMenuViewOpen = currentMenuView.isOpen;\n const menusCount = menuBarView.children.length;\n const menuViewToOpen = menuBarView.children.get((currentIndex + menusCount + step) % menusCount);\n currentMenuView.isOpen = false;\n if (isCurrentMenuViewOpen) {\n menuViewToOpen.isOpen = true;\n }\n menuViewToOpen.buttonView.focus();\n }\n },\n /**\n * Closes the entire sub-menu structure when the bar is closed. This prevents sub-menus from being open if the user\n * closes the entire bar, and then re-opens some top-level menu.\n */\n closeMenusWhenTheBarCloses(menuBarView) {\n menuBarView.on('change:isOpen', () => {\n if (!menuBarView.isOpen) {\n menuBarView.menus.forEach(menuView => {\n menuView.isOpen = false;\n // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenusWhenTheBarCloses(): Closing', logMenu( menuView ) );\n });\n }\n });\n },\n /**\n * Handles the following case:\n * 1. Hover to open a sub-menu (A). The button has focus.\n * 2. Press arrow up/down to move focus to another sub-menu (B) button.\n * 3. Press arrow right to open the sub-menu (B).\n * 4. The sub-menu (A) should close as it would with `toggleMenusAndFocusItemsOnHover()`.\n */\n closeMenuWhenAnotherOnTheSameLevelOpens(menuBarView) {\n menuBarView.on('menu:change:isOpen', (evt, name, isOpen) => {\n if (isOpen) {\n menuBarView.menus\n .filter(menuView => {\n return evt.source.parentMenuView === menuView.parentMenuView &&\n evt.source !== menuView &&\n menuView.isOpen;\n }).forEach(menuView => {\n menuView.isOpen = false;\n // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenuWhenAnotherOpens(): Closing', logMenu( menuView ) );\n });\n }\n });\n },\n /**\n * Closes the bar when the user clicked outside of it (page body, editor root, etc.).\n */\n closeOnClickOutside(menuBarView) {\n clickOutsideHandler({\n emitter: menuBarView,\n activator: () => menuBarView.isOpen,\n callback: () => menuBarView.close(),\n contextElements: () => menuBarView.children.map(child => child.element)\n });\n }\n};\n/**\n * Behaviors of the {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} component.\n */\nexport const MenuBarMenuBehaviors = {\n /**\n * If the button of the menu is focused, pressing the arrow down key should open the panel and focus it.\n * This is analogous to the {@link module:ui/dropdown/dropdownview~DropdownView}.\n */\n openAndFocusPanelOnArrowDownKey(menuView) {\n menuView.keystrokes.set('arrowdown', (data, cancel) => {\n if (menuView.focusTracker.focusedElement === menuView.buttonView.element) {\n if (!menuView.isOpen) {\n menuView.isOpen = true;\n }\n menuView.panelView.focus();\n cancel();\n }\n });\n },\n /**\n * Open the menu on the right arrow key press. This allows for navigating to sub-menus using the keyboard.\n */\n openOnArrowRightKey(menuView) {\n const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowleft' : 'arrowright';\n menuView.keystrokes.set(keystroke, (data, cancel) => {\n if (menuView.focusTracker.focusedElement !== menuView.buttonView.element || !menuView.isEnabled) {\n return;\n }\n // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] openOnArrowRightKey(): Opening', logMenu( menuView ) );\n if (!menuView.isOpen) {\n menuView.isOpen = true;\n }\n menuView.panelView.focus();\n cancel();\n });\n },\n /**\n * Opens the menu on its button click. Note that this behavior only opens but never closes the menu (unlike\n * {@link module:ui/dropdown/dropdownview~DropdownView}).\n */\n openOnButtonClick(menuView) {\n menuView.buttonView.on('execute', () => {\n menuView.isOpen = true;\n menuView.panelView.focus();\n });\n },\n /**\n * Toggles the menu on its button click. This behavior is analogous to {@link module:ui/dropdown/dropdownview~DropdownView}.\n */\n toggleOnButtonClick(menuView) {\n menuView.buttonView.on('execute', () => {\n menuView.isOpen = !menuView.isOpen;\n if (menuView.isOpen) {\n menuView.panelView.focus();\n }\n });\n },\n /**\n * Closes the menu on the right left key press. This allows for navigating to sub-menus using the keyboard.\n */\n closeOnArrowLeftKey(menuView) {\n const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowright' : 'arrowleft';\n menuView.keystrokes.set(keystroke, (data, cancel) => {\n if (menuView.isOpen) {\n menuView.isOpen = false;\n menuView.focus();\n cancel();\n }\n });\n },\n /**\n * Closes the menu on the esc key press. This allows for navigating to sub-menus using the keyboard.\n */\n closeOnEscKey(menuView) {\n menuView.keystrokes.set('esc', (data, cancel) => {\n if (menuView.isOpen) {\n menuView.isOpen = false;\n menuView.focus();\n cancel();\n }\n });\n },\n /**\n * Closes the menu when its parent menu also closed. This prevents from orphaned open menus when the parent menu re-opens.\n */\n closeOnParentClose(menuView) {\n menuView.parentMenuView.on('change:isOpen', (evt, name, isOpen) => {\n if (!isOpen && evt.source === menuView.parentMenuView) {\n // @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeOnParentClose(): Closing', logMenu( menuView ) );\n menuView.isOpen = false;\n }\n });\n }\n};\n// @if CK_DEBUG_MENU_BAR // function logMenu( menuView: MenuBarMenuView ) {\n// @if CK_DEBUG_MENU_BAR //\treturn `\"${ menuView.buttonView.label }\"`;\n// @if CK_DEBUG_MENU_BAR // }\n/**\n * Contains every positioning function used by {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} that decides where the\n * {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView} should be placed.\n *\n * Top-level menu positioning functions:\n *\n *\t┌──────┐\n *\t│ │\n *\t├──────┴────────┐\n *\t│ │\n *\t│ │\n *\t│ │\n *\t│ SE │\n *\t└───────────────┘\n *\n *\t ┌──────┐\n *\t │ │\n *\t┌────────┴──────┤\n *\t│ │\n *\t│ │\n *\t│ │\n *\t│ SW │\n *\t└───────────────┘\n *\n *\t┌───────────────┐\n *\t│ NW │\n *\t│ │\n *\t│ │\n *\t│ │\n *\t└────────┬──────┤\n *\t │ │\n *\t └──────┘\n *\n *\t┌───────────────┐\n *\t│ NE │\n *\t│ │\n *\t│ │\n *\t│ │\n *\t├──────┬────────┘\n *\t│ │\n *\t└──────┘\n *\n * Sub-menu positioning functions:\n *\n *\t┌──────┬───────────────┐\n *\t│ │ │\n *\t└──────┤ │\n *\t │ │\n *\t │ ES │\n *\t └───────────────┘\n *\n *\t┌───────────────┬──────┐\n *\t│ │ │\n *\t│ ├──────┘\n *\t│ │\n *\t│ WS │\n *\t└───────────────┘\n *\n *\t ┌───────────────┐\n *\t │ EN │\n *\t │ │\n *\t┌──────┤ │\n *\t│ │ │\n *\t└──────┴───────────────┘\n *\n *\t┌───────────────┐\n *\t│ WN │\n *\t│ │\n *\t│ ├──────┐\n *\t│ │ │\n *\t└───────────────┴──────┘\n */\nexport const MenuBarMenuViewPanelPositioningFunctions = {\n southEast: buttonRect => {\n return {\n top: buttonRect.bottom,\n left: buttonRect.left,\n name: 'se'\n };\n },\n southWest: (buttonRect, panelRect) => {\n return {\n top: buttonRect.bottom,\n left: buttonRect.left - panelRect.width + buttonRect.width,\n name: 'sw'\n };\n },\n northEast: (buttonRect, panelRect) => {\n return {\n top: buttonRect.top - panelRect.height,\n left: buttonRect.left,\n name: 'ne'\n };\n },\n northWest: (buttonRect, panelRect) => {\n return {\n top: buttonRect.top - panelRect.height,\n left: buttonRect.left - panelRect.width + buttonRect.width,\n name: 'nw'\n };\n },\n eastSouth: buttonRect => {\n return {\n top: buttonRect.top,\n left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,\n name: 'es'\n };\n },\n eastNorth: (buttonRect, panelRect) => {\n return {\n top: buttonRect.top - panelRect.height,\n left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,\n name: 'en'\n };\n },\n westSouth: (buttonRect, panelRect) => {\n return {\n top: buttonRect.top,\n left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,\n name: 'ws'\n };\n },\n westNorth: (buttonRect, panelRect) => {\n return {\n top: buttonRect.top - panelRect.height,\n left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,\n name: 'wn'\n };\n }\n};\n/**\n * The default items {@link module:core/editor/editorconfig~EditorConfig#menuBar configuration} of the\n * {@link module:ui/menubar/menubarview~MenuBarView} component. It contains names of all menu bar components\n * registered in the {@link module:ui/componentfactory~ComponentFactory component factory} (available in the project).\n *\n * **Note**: Menu bar component names provided by core editor features are prefixed with `menuBar:` in order to distinguish\n * them from components referenced by the {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}, for instance,\n * `'menuBar:bold'` is a menu bar button but `'bold'` is a toolbar button.\n *\n * Below is the preset menu bar structure (the default value of `config.menuBar.items` property):\n *\n * ```ts\n * [\n * \t{\n * \t\tmenuId: 'file',\n * \t\tlabel: 'File',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'export',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:exportPdf',\n * \t\t\t\t\t'menuBar:exportWord'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'import',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:importWord'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'revisionHistory',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:revisionHistory'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'edit',\n * \t\tlabel: 'Edit',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'undo',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:undo',\n * \t\t\t\t\t'menuBar:redo'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'selectAll',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:selectAll'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'findAndReplace',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:findAndReplace'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'view',\n * \t\tlabel: 'View',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'sourceEditing',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:sourceEditing'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'showBlocks',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:showBlocks'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'restrictedEditingException',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:restrictedEditingException'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'insert',\n * \t\tlabel: 'Insert',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'insertMainWidgets',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:uploadImage',\n * \t\t\t\t\t'menuBar:ckbox',\n * \t\t\t\t\t'menuBar:ckfinder',\n * \t\t\t\t\t'menuBar:insertTable'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'insertInline',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:link',\n * \t\t\t\t\t'menuBar:comment'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'insertMinorWidgets',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:insertTemplate',\n * \t\t\t\t\t'menuBar:blockQuote',\n * \t\t\t\t\t'menuBar:codeBlock',\n * \t\t\t\t\t'menuBar:htmlEmbed'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'insertStructureWidgets',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:horizontalLine',\n * \t\t\t\t\t'menuBar:pageBreak',\n * \t\t\t\t\t'menuBar:tableOfContents'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'restrictedEditing',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:restrictedEditing'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'format',\n * \t\tlabel: 'Format',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'textAndFont',\n * \t\t\t\titems: [\n * \t\t\t\t\t{\n * \t\t\t\t\t\tmenuId: 'text',\n * \t\t\t\t\t\tlabel: 'Text',\n * \t\t\t\t\t\tgroups: [\n * \t\t\t\t\t\t\t{\n * \t\t\t\t\t\t\t\tgroupId: 'basicStyles',\n * \t\t\t\t\t\t\t\titems: [\n * \t\t\t\t\t\t\t\t\t'menuBar:bold',\n * \t\t\t\t\t\t\t\t\t'menuBar:italic',\n * \t\t\t\t\t\t\t\t\t'menuBar:underline',\n * \t\t\t\t\t\t\t\t\t'menuBar:strikethrough',\n * \t\t\t\t\t\t\t\t\t'menuBar:superscript',\n * \t\t\t\t\t\t\t\t\t'menuBar:subscript',\n * \t\t\t\t\t\t\t\t\t'menuBar:code'\n * \t\t\t\t\t\t\t\t]\n * \t\t\t\t\t\t\t},\n * \t\t\t\t\t\t\t{\n * \t\t\t\t\t\t\t\tgroupId: 'textPartLanguage',\n * \t\t\t\t\t\t\t\titems: [\n * \t\t\t\t\t\t\t\t\t'menuBar:textPartLanguage'\n * \t\t\t\t\t\t\t\t]\n * \t\t\t\t\t\t\t}\n * \t\t\t\t\t\t]\n * \t\t\t\t\t},\n * \t\t\t\t\t{\n * \t\t\t\t\t\tmenuId: 'font',\n * \t\t\t\t\t\tlabel: 'Font',\n * \t\t\t\t\t\tgroups: [\n * \t\t\t\t\t\t\t{\n * \t\t\t\t\t\t\t\tgroupId: 'fontProperties',\n * \t\t\t\t\t\t\t\titems: [\n * \t\t\t\t\t\t\t\t\t'menuBar:fontSize',\n * \t\t\t\t\t\t\t\t\t'menuBar:fontFamily'\n * \t\t\t\t\t\t\t\t]\n * \t\t\t\t\t\t\t},\n * \t\t\t\t\t\t\t{\n * \t\t\t\t\t\t\t\tgroupId: 'fontColors',\n * \t\t\t\t\t\t\t\titems: [\n * \t\t\t\t\t\t\t\t\t'menuBar:fontColor',\n * \t\t\t\t\t\t\t\t\t'menuBar:fontBackgroundColor'\n * \t\t\t\t\t\t\t\t]\n * \t\t\t\t\t\t\t},\n * \t\t\t\t\t\t\t{\n * \t\t\t\t\t\t\t\tgroupId: 'highlight',\n * \t\t\t\t\t\t\t\titems: [\n * \t\t\t\t\t\t\t\t\t'menuBar:highlight'\n * \t\t\t\t\t\t\t\t]\n * \t\t\t\t\t\t\t}\n * \t\t\t\t\t\t]\n * \t\t\t\t\t},\n * \t\t\t\t\t'menuBar:heading'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'list',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:bulletedList',\n * \t\t\t\t\t'menuBar:numberedList',\n * \t\t\t\t\t'menuBar:todoList'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'indent',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:alignment',\n * \t\t\t\t\t'menuBar:indent',\n * \t\t\t\t\t'menuBar:outdent'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'caseChange',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:caseChange'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'removeFormat',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:removeFormat'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'tools',\n * \t\tlabel: 'Tools',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'aiTools',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:aiAssistant',\n * \t\t\t\t\t'menuBar:aiCommands'\n * \t\t\t\t]\n * \t\t\t},\n * \t\t\t{\n * \t\t\t\tgroupId: 'tools',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:trackChanges',\n * \t\t\t\t\t'menuBar:commentsArchive'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t},\n * \t{\n * \t\tmenuId: 'help',\n * \t\tlabel: 'Help',\n * \t\tgroups: [\n * \t\t\t{\n * \t\t\t\tgroupId: 'help',\n * \t\t\t\titems: [\n * \t\t\t\t\t'menuBar:accessibilityHelp'\n * \t\t\t\t]\n * \t\t\t}\n * \t\t]\n * \t}\n * ];\n * ```\n *\n * The menu bar can be customized using the `config.menuBar.removeItems` and `config.menuBar.addItems` properties.\n */\n// **NOTE: Whenever you make changes to this value, reflect it in the documentation above!**\nexport const DefaultMenuBarItems = [\n {\n menuId: 'file',\n label: 'File',\n groups: [\n {\n groupId: 'export',\n items: [\n 'menuBar:exportPdf',\n 'menuBar:exportWord'\n ]\n },\n {\n groupId: 'import',\n items: [\n 'menuBar:importWord'\n ]\n },\n {\n groupId: 'revisionHistory',\n items: [\n 'menuBar:revisionHistory'\n ]\n }\n ]\n },\n {\n menuId: 'edit',\n label: 'Edit',\n groups: [\n {\n groupId: 'undo',\n items: [\n 'menuBar:undo',\n 'menuBar:redo'\n ]\n },\n {\n groupId: 'selectAll',\n items: [\n 'menuBar:selectAll'\n ]\n },\n {\n groupId: 'findAndReplace',\n items: [\n 'menuBar:findAndReplace'\n ]\n }\n ]\n },\n {\n menuId: 'view',\n label: 'View',\n groups: [\n {\n groupId: 'sourceEditing',\n items: [\n 'menuBar:sourceEditing'\n ]\n },\n {\n groupId: 'showBlocks',\n items: [\n 'menuBar:showBlocks'\n ]\n },\n {\n groupId: 'restrictedEditingException',\n items: [\n 'menuBar:restrictedEditingException'\n ]\n }\n ]\n },\n {\n menuId: 'insert',\n label: 'Insert',\n groups: [\n {\n groupId: 'insertMainWidgets',\n items: [\n 'menuBar:uploadImage',\n 'menuBar:ckbox',\n 'menuBar:ckfinder',\n 'menuBar:insertTable'\n ]\n },\n {\n groupId: 'insertInline',\n items: [\n 'menuBar:link',\n 'menuBar:comment'\n ]\n },\n {\n groupId: 'insertMinorWidgets',\n items: [\n 'menuBar:insertTemplate',\n 'menuBar:blockQuote',\n 'menuBar:codeBlock',\n 'menuBar:htmlEmbed'\n ]\n },\n {\n groupId: 'insertStructureWidgets',\n items: [\n 'menuBar:horizontalLine',\n 'menuBar:pageBreak',\n 'menuBar:tableOfContents'\n ]\n },\n {\n groupId: 'restrictedEditing',\n items: [\n 'menuBar:restrictedEditing'\n ]\n }\n ]\n },\n {\n menuId: 'format',\n label: 'Format',\n groups: [\n {\n groupId: 'textAndFont',\n items: [\n {\n menuId: 'text',\n label: 'Text',\n groups: [\n {\n groupId: 'basicStyles',\n items: [\n 'menuBar:bold',\n 'menuBar:italic',\n 'menuBar:underline',\n 'menuBar:strikethrough',\n 'menuBar:superscript',\n 'menuBar:subscript',\n 'menuBar:code'\n ]\n },\n {\n groupId: 'textPartLanguage',\n items: [\n 'menuBar:textPartLanguage'\n ]\n }\n ]\n },\n {\n menuId: 'font',\n label: 'Font',\n groups: [\n {\n groupId: 'fontProperties',\n items: [\n 'menuBar:fontSize',\n 'menuBar:fontFamily'\n ]\n },\n {\n groupId: 'fontColors',\n items: [\n 'menuBar:fontColor',\n 'menuBar:fontBackgroundColor'\n ]\n },\n {\n groupId: 'highlight',\n items: [\n 'menuBar:highlight'\n ]\n }\n ]\n },\n 'menuBar:heading'\n ]\n },\n {\n groupId: 'list',\n items: [\n 'menuBar:bulletedList',\n 'menuBar:numberedList',\n 'menuBar:todoList'\n ]\n },\n {\n groupId: 'indent',\n items: [\n 'menuBar:alignment',\n 'menuBar:indent',\n 'menuBar:outdent'\n ]\n },\n {\n groupId: 'caseChange',\n items: [\n 'menuBar:caseChange'\n ]\n },\n {\n groupId: 'removeFormat',\n items: [\n 'menuBar:removeFormat'\n ]\n }\n ]\n },\n {\n menuId: 'tools',\n label: 'Tools',\n groups: [\n {\n groupId: 'aiTools',\n items: [\n 'menuBar:aiAssistant',\n 'menuBar:aiCommands'\n ]\n },\n {\n groupId: 'tools',\n items: [\n 'menuBar:trackChanges',\n 'menuBar:commentsArchive'\n ]\n }\n ]\n },\n {\n menuId: 'help',\n label: 'Help',\n groups: [\n {\n groupId: 'help',\n items: [\n 'menuBar:accessibilityHelp'\n ]\n }\n ]\n }\n];\n/**\n * Performs a cleanup and normalization of the menu bar configuration.\n */\nexport function normalizeMenuBarConfig(config) {\n let configObject;\n // The integrator specified the config as an object but without items. Let's give them defaults but respect their\n // additions and removals.\n if (!('items' in config) || !config.items) {\n configObject = {\n items: cloneDeep(DefaultMenuBarItems),\n addItems: [],\n removeItems: [],\n isVisible: true,\n isUsingDefaultConfig: true,\n ...config\n };\n }\n // The integrator specified the config as an object and there are items there. Let's take it as it is.\n else {\n configObject = {\n items: config.items,\n removeItems: [],\n addItems: [],\n isVisible: true,\n isUsingDefaultConfig: false,\n ...config\n };\n }\n return configObject;\n}\n/**\n * Processes a normalized menu bar config and returns a config clone with the following modifications:\n *\n * * Removed components that are not available in the component factory,\n * * Removed obsolete separators,\n * * Purged empty menus,\n * * Localized top-level menu labels.\n */\nexport function processMenuBarConfig({ normalizedConfig, locale, componentFactory }) {\n const configClone = cloneDeep(normalizedConfig);\n handleRemovals(normalizedConfig, configClone);\n handleAdditions(normalizedConfig, configClone);\n purgeUnavailableComponents(normalizedConfig, configClone, componentFactory);\n purgeEmptyMenus(normalizedConfig, configClone);\n localizeMenuLabels(configClone, locale);\n return configClone;\n}\n/**\n * Removes items from the menu bar config based on user `removeItems` configuration. Users can remove\n * individual items, groups, or entire menus. For each removed item, a warning is logged if the item\n * was not found in the configuration.\n */\nfunction handleRemovals(originalConfig, config) {\n const itemsToBeRemoved = config.removeItems;\n const successfullyRemovedItems = [];\n // Remove top-level menus.\n config.items = config.items.filter(({ menuId }) => {\n if (itemsToBeRemoved.includes(menuId)) {\n successfullyRemovedItems.push(menuId);\n return false;\n }\n return true;\n });\n walkConfigMenus(config.items, menuDefinition => {\n // Remove groups from menus.\n menuDefinition.groups = menuDefinition.groups.filter(({ groupId }) => {\n if (itemsToBeRemoved.includes(groupId)) {\n successfullyRemovedItems.push(groupId);\n return false;\n }\n return true;\n });\n // Remove sub-menus and items from groups.\n for (const groupDefinition of menuDefinition.groups) {\n groupDefinition.items = groupDefinition.items.filter(item => {\n const itemId = getIdFromGroupItem(item);\n if (itemsToBeRemoved.includes(itemId)) {\n successfullyRemovedItems.push(itemId);\n return false;\n }\n return true;\n });\n }\n });\n for (const itemName of itemsToBeRemoved) {\n if (!successfullyRemovedItems.includes(itemName)) {\n /**\n * There was a problem processing the configuration of the menu bar. The item with the given\n * name does could not be removed from the menu bar configuration.\n *\n * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed\n * to provide a menu bar item has not been loaded or there is a typo in the\n * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.\n *\n * @error menu-bar-item-could-not-be-removed\n * @param menuBarConfig The full configuration of the menu bar.\n * @param itemName The name of the item that was not removed from the menu bar.\n */\n logWarning('menu-bar-item-could-not-be-removed', {\n menuBarConfig: originalConfig,\n itemName\n });\n }\n }\n}\n/**\n * Handles the `config.menuBar.addItems` configuration. It allows for adding menus, groups, and items at arbitrary\n * positions in the menu bar. If the position does not exist, a warning is logged.\n */\nfunction handleAdditions(originalConfig, config) {\n const itemsToBeAdded = config.addItems;\n const successFullyAddedItems = [];\n for (const itemToAdd of itemsToBeAdded) {\n const relation = getRelationFromPosition(itemToAdd.position);\n const relativeId = getRelativeIdFromPosition(itemToAdd.position);\n // Adding a menu.\n if (isMenuBarMenuAddition(itemToAdd)) {\n if (!relativeId) {\n // Adding a top-level menu at the beginning of the menu bar.\n if (relation === 'start') {\n config.items.unshift(itemToAdd.menu);\n successFullyAddedItems.push(itemToAdd);\n }\n // Adding a top-level menu at the end of the menu bar.\n else if (relation === 'end') {\n config.items.push(itemToAdd.menu);\n successFullyAddedItems.push(itemToAdd);\n }\n }\n else {\n const topLevelMenuDefinitionIndex = config.items.findIndex(menuDefinition => menuDefinition.menuId === relativeId);\n // Adding a top-level menu somewhere between existing menu bar menus.\n if (topLevelMenuDefinitionIndex != -1) {\n if (relation === 'before') {\n config.items.splice(topLevelMenuDefinitionIndex, 0, itemToAdd.menu);\n successFullyAddedItems.push(itemToAdd);\n }\n else if (relation === 'after') {\n config.items.splice(topLevelMenuDefinitionIndex + 1, 0, itemToAdd.menu);\n successFullyAddedItems.push(itemToAdd);\n }\n }\n // Adding a sub-menu to an existing items group.\n else {\n const wasAdded = addMenuOrItemToGroup(config, itemToAdd.menu, relativeId, relation);\n if (wasAdded) {\n successFullyAddedItems.push(itemToAdd);\n }\n }\n }\n }\n // Adding a group.\n else if (isMenuBarMenuGroupAddition(itemToAdd)) {\n walkConfigMenus(config.items, menuDefinition => {\n if (menuDefinition.menuId === relativeId) {\n // Add a group at the start of a menu.\n if (relation === 'start') {\n menuDefinition.groups.unshift(itemToAdd.group);\n successFullyAddedItems.push(itemToAdd);\n }\n // Add a group at the end of a menu.\n else if (relation === 'end') {\n menuDefinition.groups.push(itemToAdd.group);\n successFullyAddedItems.push(itemToAdd);\n }\n }\n else {\n const relativeGroupIndex = menuDefinition.groups.findIndex(group => group.groupId === relativeId);\n if (relativeGroupIndex !== -1) {\n // Add a group before an existing group in a menu.\n if (relation === 'before') {\n menuDefinition.groups.splice(relativeGroupIndex, 0, itemToAdd.group);\n successFullyAddedItems.push(itemToAdd);\n }\n // Add a group after an existing group in a menu.\n else if (relation === 'after') {\n menuDefinition.groups.splice(relativeGroupIndex + 1, 0, itemToAdd.group);\n successFullyAddedItems.push(itemToAdd);\n }\n }\n }\n });\n }\n // Adding an item to an existing items group.\n else {\n const wasAdded = addMenuOrItemToGroup(config, itemToAdd.item, relativeId, relation);\n if (wasAdded) {\n successFullyAddedItems.push(itemToAdd);\n }\n }\n }\n for (const addedItemConfig of itemsToBeAdded) {\n if (!successFullyAddedItems.includes(addedItemConfig)) {\n /**\n * There was a problem processing the configuration of the menu bar. The configured item could not be added\n * because the position it was supposed to be added to does not exist.\n *\n * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed\n * to provide a menu bar item has not been loaded or there is a typo in the\n * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.\n *\n * @error menu-bar-item-could-not-be-removed\n * @param menuBarConfig The full configuration of the menu bar.\n * @param itemName The name of the item that was not removed from the menu bar.\n */\n logWarning('menu-bar-item-could-not-be-added', {\n menuBarConfig: originalConfig,\n addedItemConfig\n });\n }\n }\n}\n/**\n * Handles adding a sub-menu or an item into a group. The logic is the same for both cases.\n */\nfunction addMenuOrItemToGroup(config, itemOrMenuToAdd, relativeId, relation) {\n let wasAdded = false;\n walkConfigMenus(config.items, menuDefinition => {\n for (const { groupId, items: groupItems } of menuDefinition.groups) {\n // Avoid infinite loops.\n if (wasAdded) {\n return;\n }\n if (groupId === relativeId) {\n // Adding an item/menu at the beginning of a group.\n if (relation === 'start') {\n groupItems.unshift(itemOrMenuToAdd);\n wasAdded = true;\n }\n // Adding an item/menu at the end of a group.\n else if (relation === 'end') {\n groupItems.push(itemOrMenuToAdd);\n wasAdded = true;\n }\n }\n else {\n // Adding an item/menu relative to an existing item/menu.\n const relativeItemIndex = groupItems.findIndex(groupItem => {\n return getIdFromGroupItem(groupItem) === relativeId;\n });\n if (relativeItemIndex !== -1) {\n if (relation === 'before') {\n groupItems.splice(relativeItemIndex, 0, itemOrMenuToAdd);\n wasAdded = true;\n }\n else if (relation === 'after') {\n groupItems.splice(relativeItemIndex + 1, 0, itemOrMenuToAdd);\n wasAdded = true;\n }\n }\n }\n }\n });\n return wasAdded;\n}\n/**\n * Removes components from the menu bar configuration that are not available in the factory and would\n * not be instantiated. Warns about missing components if the menu bar configuration was specified by the user.\n */\nfunction purgeUnavailableComponents(originalConfig, config, componentFactory) {\n walkConfigMenus(config.items, menuDefinition => {\n for (const groupDefinition of menuDefinition.groups) {\n groupDefinition.items = groupDefinition.items.filter(item => {\n const isItemUnavailable = typeof item === 'string' && !componentFactory.has(item);\n // The default configuration contains all possible editor features. But integrators' editors rarely load\n // every possible feature. This is why we do not want to log warnings about unavailable items for the default config\n // because they would show up in almost every integration. If the configuration has been provided by\n // the integrator, on the other hand, then these warnings bring value.\n if (isItemUnavailable && !config.isUsingDefaultConfig) {\n /**\n * There was a problem processing the configuration of the menu bar. The item with the given\n * name does not exist so it was omitted when rendering the menu bar.\n *\n * This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed\n * to provide a menu bar item has not been loaded or there is a typo in the\n * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.\n *\n * Make sure the plugin responsible for this menu bar item is loaded and the menu bar configuration\n * is correct, e.g. {@link module:basic-styles/bold/boldui~BoldUI} is loaded for the `'menuBar:bold'`\n * menu bar item.\n *\n * @error menu-bar-item-unavailable\n * @param menuBarConfig The full configuration of the menu bar.\n * @param parentMenuConfig The config of the menu the unavailable component was defined in.\n * @param componentName The name of the unavailable component.\n */\n logWarning('menu-bar-item-unavailable', {\n menuBarConfig: originalConfig,\n parentMenuConfig: cloneDeep(menuDefinition),\n componentName: item\n });\n }\n return !isItemUnavailable;\n });\n }\n });\n}\n/**\n * Removes empty menus from the menu bar configuration to improve the visual UX. Such menus can occur\n * when some plugins responsible for providing menu bar items have not been loaded and some part of\n * the configuration populated menus using these components exclusively.\n */\nfunction purgeEmptyMenus(originalConfig, config) {\n const isUsingDefaultConfig = config.isUsingDefaultConfig;\n let wasSubMenuPurged = false;\n // Purge top-level menus.\n config.items = config.items.filter(menuDefinition => {\n if (!menuDefinition.groups.length) {\n warnAboutEmptyMenu(originalConfig, menuDefinition, isUsingDefaultConfig);\n return false;\n }\n return true;\n });\n // Warn if there were no top-level menus left in the menu bar after purging.\n if (!config.items.length) {\n warnAboutEmptyMenu(originalConfig, originalConfig, isUsingDefaultConfig);\n return;\n }\n // Purge sub-menus and groups.\n walkConfigMenus(config.items, menuDefinition => {\n // Get rid of empty groups.\n menuDefinition.groups = menuDefinition.groups.filter(groupDefinition => {\n if (!groupDefinition.items.length) {\n wasSubMenuPurged = true;\n return false;\n }\n return true;\n });\n // Get rid of empty sub-menus.\n for (const groupDefinition of menuDefinition.groups) {\n groupDefinition.items = groupDefinition.items.filter(item => {\n // If no groups were left after removing empty ones.\n if (isMenuDefinition(item) && !item.groups.length) {\n warnAboutEmptyMenu(originalConfig, item, isUsingDefaultConfig);\n wasSubMenuPurged = true;\n return false;\n }\n return true;\n });\n }\n });\n if (wasSubMenuPurged) {\n // The config is walked from the root to the leaves so if anything gets removed, we need to re-run the\n // whole process because it could've affected parents.\n purgeEmptyMenus(originalConfig, config);\n }\n}\nfunction warnAboutEmptyMenu(originalConfig, emptyMenuConfig, isUsingDefaultConfig) {\n if (isUsingDefaultConfig) {\n return;\n }\n /**\n * There was a problem processing the configuration of the menu bar. One of the menus\n * is empty so it was omitted when rendering the menu bar.\n *\n * This warning usually shows up when some {@link module:core/plugin~Plugin plugins} responsible for\n * providing menu bar items have not been loaded and the\n * {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration} was not updated.\n *\n * Make sure all necessary editor plugins are loaded and/or update the menu bar configuration\n * to account for the missing menu items.\n *\n * @error menu-bar-menu-empty\n * @param menuBarConfig The full configuration of the menu bar.\n * @param emptyMenuConfig The definition of the menu that has no child items.\n */\n logWarning('menu-bar-menu-empty', {\n menuBarConfig: originalConfig,\n emptyMenuConfig\n });\n}\n/**\n * Localizes the user-config using pre-defined localized category labels.\n */\nfunction localizeMenuLabels(config, locale) {\n const t = locale.t;\n const localizedCategoryLabels = {\n // Top-level categories.\n 'File': t({\n string: 'File',\n id: 'MENU_BAR_MENU_FILE'\n }),\n 'Edit': t({\n string: 'Edit',\n id: 'MENU_BAR_MENU_EDIT'\n }),\n 'View': t({\n string: 'View',\n id: 'MENU_BAR_MENU_VIEW'\n }),\n 'Insert': t({\n string: 'Insert',\n id: 'MENU_BAR_MENU_INSERT'\n }),\n 'Format': t({\n string: 'Format',\n id: 'MENU_BAR_MENU_FORMAT'\n }),\n 'Tools': t({\n string: 'Tools',\n id: 'MENU_BAR_MENU_TOOLS'\n }),\n 'Help': t({\n string: 'Help',\n id: 'MENU_BAR_MENU_HELP'\n }),\n // Sub-menus.\n 'Text': t({\n string: 'Text',\n id: 'MENU_BAR_MENU_TEXT'\n }),\n 'Font': t({\n string: 'Font',\n id: 'MENU_BAR_MENU_FONT'\n })\n };\n walkConfigMenus(config.items, definition => {\n if (definition.label in localizedCategoryLabels) {\n definition.label = localizedCategoryLabels[definition.label];\n }\n });\n}\n/**\n * Recursively visits all menu definitions in the config and calls the callback for each of them.\n */\nfunction walkConfigMenus(definition, callback) {\n if (Array.isArray(definition)) {\n for (const topLevelMenuDefinition of definition) {\n walk(topLevelMenuDefinition);\n }\n }\n function walk(menuDefinition) {\n callback(menuDefinition);\n for (const groupDefinition of menuDefinition.groups) {\n for (const groupItem of groupDefinition.items) {\n if (isMenuDefinition(groupItem)) {\n walk(groupItem);\n }\n }\n }\n }\n}\nfunction isMenuBarMenuAddition(definition) {\n return typeof definition === 'object' && 'menu' in definition;\n}\nfunction isMenuBarMenuGroupAddition(definition) {\n return typeof definition === 'object' && 'group' in definition;\n}\nfunction getRelationFromPosition(position) {\n if (position.startsWith('start')) {\n return 'start';\n }\n else if (position.startsWith('end')) {\n return 'end';\n }\n else if (position.startsWith('after')) {\n return 'after';\n }\n else {\n return 'before';\n }\n}\nfunction getRelativeIdFromPosition(position) {\n const match = position.match(/^[^:]+:(.+)/);\n if (match) {\n return match[1];\n }\n return null;\n}\nfunction getIdFromGroupItem(item) {\n return typeof item === 'string' ? item : item.menuId;\n}\nfunction isMenuDefinition(definition) {\n return typeof definition === 'object' && 'menuId' in definition;\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubarmenupanel.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport View from '../view.js';\nimport '../../theme/components/menubar/menubarmenupanel.css';\n/**\n * A view representing a {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView} of a menu.\n */\nexport default class MenuBarMenuPanelView extends View {\n /**\n * Creates an instance of the menu panel view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n const bind = this.bindTemplate;\n this.set('isVisible', false);\n this.set('position', 'se');\n this.children = this.createCollection();\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-reset',\n 'ck-menu-bar__menu__panel',\n bind.to('position', value => `ck-menu-bar__menu__panel_position_${value}`),\n bind.if('isVisible', 'ck-hidden', value => !value)\n ],\n tabindex: '-1'\n },\n children: this.children,\n on: {\n // Drag and drop in the panel should not break the selection in the editor.\n // https://github.com/ckeditor/ckeditor5-ui/issues/228\n selectstart: bind.to(evt => {\n if (evt.target.tagName.toLocaleLowerCase() === 'input') {\n return;\n }\n evt.preventDefault();\n })\n }\n });\n }\n /**\n * Focuses the first child of the panel (default) or the last one if the `direction` is `-1`.\n */\n focus(direction = 1) {\n if (this.children.length) {\n if (direction === 1) {\n this.children.first.focus();\n }\n else {\n this.children.last.focus();\n }\n }\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubarmenu.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/menubar/menubarmenuview\n */\nimport { FocusTracker, KeystrokeHandler, getOptimalPosition } from '@ckeditor/ckeditor5-utils';\nimport MenuBarMenuButtonView from './menubarmenubuttonview.js';\nimport { MenuBarMenuBehaviors, MenuBarMenuViewPanelPositioningFunctions } from './utils.js';\nimport View from '../view.js';\nimport { default as MenuBarMenuPanelView } from './menubarmenupanelview.js';\nimport '../../theme/components/menubar/menubarmenu.css';\n/**\n * A menu view for the {@link module:ui/menubar/menubarview~MenuBarView}. Menus are building blocks of the menu bar,\n * they host other sub-menus and menu items (buttons) that users can interact with.\n */\nclass MenuBarMenuView extends View {\n /**\n * Creates an instance of the menu view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n const bind = this.bindTemplate;\n this.buttonView = new MenuBarMenuButtonView(locale);\n this.buttonView.delegate('mouseenter').to(this);\n this.buttonView.bind('isOn', 'isEnabled').to(this, 'isOpen', 'isEnabled');\n this.panelView = new MenuBarMenuPanelView(locale);\n this.panelView.bind('isVisible').to(this, 'isOpen');\n this.keystrokes = new KeystrokeHandler();\n this.focusTracker = new FocusTracker();\n this.set('isOpen', false);\n this.set('isEnabled', true);\n this.set('panelPosition', 'w');\n this.set('class', undefined);\n this.set('parentMenuView', null);\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-menu-bar__menu',\n bind.to('class'),\n bind.if('isEnabled', 'ck-disabled', value => !value),\n bind.if('parentMenuView', 'ck-menu-bar__menu_top-level', value => !value)\n ]\n },\n children: [\n this.buttonView,\n this.panelView\n ]\n });\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n this.focusTracker.add(this.buttonView.element);\n this.focusTracker.add(this.panelView.element);\n // Listen for keystrokes coming from within #element.\n this.keystrokes.listenTo(this.element);\n MenuBarMenuBehaviors.closeOnEscKey(this);\n this._repositionPanelOnOpen();\n }\n // For now, this method cannot be called in the render process because the `parentMenuView` may be assigned\n // after the rendering process.\n //\n // TODO: We should reconsider the way we handle this logic.\n /**\n * Attach all keyboard behaviors for the menu bar view.\n *\n * @internal\n */\n _attachBehaviors() {\n // Top-level menus.\n if (!this.parentMenuView) {\n this._propagateArrowKeystrokeEvents();\n MenuBarMenuBehaviors.openAndFocusPanelOnArrowDownKey(this);\n MenuBarMenuBehaviors.toggleOnButtonClick(this);\n }\n else {\n MenuBarMenuBehaviors.openOnButtonClick(this);\n MenuBarMenuBehaviors.openOnArrowRightKey(this);\n MenuBarMenuBehaviors.closeOnArrowLeftKey(this);\n MenuBarMenuBehaviors.closeOnParentClose(this);\n }\n }\n /**\n * Fires `arrowright` and `arrowleft` events when the user pressed corresponding arrow keys.\n */\n _propagateArrowKeystrokeEvents() {\n this.keystrokes.set('arrowright', (data, cancel) => {\n this.fire('arrowright');\n cancel();\n });\n this.keystrokes.set('arrowleft', (data, cancel) => {\n this.fire('arrowleft');\n cancel();\n });\n }\n /**\n * Sets the position of the panel when the menu opens. The panel is positioned\n * so that it optimally uses the available space in the viewport.\n */\n _repositionPanelOnOpen() {\n // Let the menu control the position of the panel. The position must be updated every time the menu is open.\n this.on('change:isOpen', (evt, name, isOpen) => {\n if (!isOpen) {\n return;\n }\n const optimalPanelPosition = MenuBarMenuView._getOptimalPosition({\n element: this.panelView.element,\n target: this.buttonView.element,\n fitInViewport: true,\n positions: this._panelPositions\n });\n this.panelView.position = (optimalPanelPosition ? optimalPanelPosition.name : this._panelPositions[0].name);\n });\n }\n /**\n * @inheritDoc\n */\n focus() {\n this.buttonView.focus();\n }\n /**\n * Positioning functions for the {@link #panelView} . They change depending on the role of the menu (top-level vs sub-menu) in\n * the {@link module:ui/menubar/menubarview~MenuBarView menu bar} and the UI language direction.\n */\n get _panelPositions() {\n const { southEast, southWest, northEast, northWest, westSouth, eastSouth, westNorth, eastNorth } = MenuBarMenuViewPanelPositioningFunctions;\n if (this.locale.uiLanguageDirection === 'ltr') {\n if (this.parentMenuView) {\n return [eastSouth, eastNorth, westSouth, westNorth];\n }\n else {\n return [southEast, southWest, northEast, northWest];\n }\n }\n else {\n if (this.parentMenuView) {\n return [westSouth, westNorth, eastSouth, eastNorth];\n }\n else {\n return [southWest, southEast, northWest, northEast];\n }\n }\n }\n}\n/**\n * A function used to calculate the optimal position for the dropdown panel.\n *\n * Referenced for unit testing purposes.\n */\nMenuBarMenuView._getOptimalPosition = getOptimalPosition;\nexport default MenuBarMenuView;\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport ListView from '../list/listview.js';\n/**\n * A list of menu bar items, a child of {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView}.\n *\n * Use this class to create a list of items (options, buttons) to be displayed in a menu bar.\n *\n * To populate this list, use {@link module:ui/menubar/menubarmenulistitemview~MenuBarMenuListItemView} instances.\n */\nexport default class MenuBarMenuListView extends ListView {\n /**\n * Creates an instance of the list view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n this.role = 'menu';\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubarmenulistitembutton.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport ButtonView from '../button/buttonview.js';\nimport '../../theme/components/menubar/menubarmenulistitembutton.css';\n/**\n * A menu bar list button view. Buttons like this one execute user actions.\n */\nexport default class MenuBarMenuListItemButtonView extends ButtonView {\n /**\n * Creates an instance of the menu bar list button view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n this.set({\n withText: true,\n withKeystroke: true,\n tooltip: false,\n role: 'menuitem'\n });\n this.extendTemplate({\n attributes: {\n class: ['ck-menu-bar__menu__item__button']\n }\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport FileDialogButtonView from '../button/filedialogbuttonview.js';\nimport '../../theme/components/menubar/menubarmenulistitembutton.css';\n/**\n * A menu bar list file dialog button view. Buttons like this one execute user actions.\n *\n * This component provides a button that opens the native file selection dialog.\n */\nexport default class MenuBarMenuListItemFileDialogButtonView extends FileDialogButtonView {\n /**\n * Creates an instance of the menu bar list button view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n this.set({\n withText: true,\n withKeystroke: true,\n tooltip: false,\n role: 'menuitem'\n });\n this.extendTemplate({\n attributes: {\n class: ['ck-menu-bar__menu__item__button']\n }\n });\n }\n}\n","import api from \"!../../../../../style-loader/dist/runtime/injectStylesIntoStyleTag.js\";\n import content from \"!!../../../../../css-loader/dist/cjs.js!../../../../../postcss-loader/dist/cjs.js??ruleSet[1].rules[2].use[2]!./menubar.css\";\n\nvar options = {\"injectType\":\"singletonStyleTag\",\"attributes\":{\"data-cke\":true}};\n\noptions.insert = \"head\";\noptions.singleton = true;\n\nvar update = api(content, options);\n\n\n\nexport default content.locals || {};","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\n/**\n * @module ui/menubar/menubarview\n */\nimport { logWarning } from '@ckeditor/ckeditor5-utils';\nimport View from '../view.js';\nimport { isObject } from 'lodash-es';\nimport ListItemView from '../list/listitemview.js';\nimport ListSeparatorView from '../list/listseparatorview.js';\nimport MenuBarMenuView from './menubarmenuview.js';\nimport MenuBarMenuListView from './menubarmenulistview.js';\nimport MenuBarMenuListItemView from './menubarmenulistitemview.js';\nimport MenuBarMenuListItemButtonView from './menubarmenulistitembuttonview.js';\nimport MenuBarMenuListItemFileDialogButtonView from './menubarmenulistitemfiledialogbuttonview.js';\nimport { MenuBarBehaviors, processMenuBarConfig } from './utils.js';\nconst EVENT_NAME_DELEGATES = ['mouseenter', 'arrowleft', 'arrowright', 'change:isOpen'];\nimport '../../theme/components/menubar/menubar.css';\n/**\n * The application menu bar component. It brings a set of top-level menus (and sub-menus) that can be used\n * to organize and access a large number of buttons.\n */\nexport default class MenuBarView extends View {\n /**\n * Creates an instance of the menu bar view.\n *\n * @param locale The localization services instance.\n */\n constructor(locale) {\n super(locale);\n /**\n * A list of {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} instances registered in the menu bar.\n *\n * @observable\n */\n this.menus = [];\n const t = locale.t;\n this.set('isOpen', false);\n this._setupIsOpenUpdater();\n this.children = this.createCollection();\n // @if CK_DEBUG_MENU_BAR // // Logs events in the main event bus of the component.\n // @if CK_DEBUG_MENU_BAR // this.on( 'menu', ( evt, data ) => {\n // @if CK_DEBUG_MENU_BAR // \tconsole.log( `MenuBarView:${ evt.name }`, evt.path.map( view => view.element ) );\n // @if CK_DEBUG_MENU_BAR // } );\n this.setTemplate({\n tag: 'div',\n attributes: {\n class: [\n 'ck',\n 'ck-menu-bar'\n ],\n 'aria-label': t('Editor menu bar'),\n role: 'menubar'\n },\n children: this.children\n });\n }\n /**\n * A utility that expands a plain menu bar configuration into a structure of menus (also: sub-menus)\n * and items using a given {@link module:ui/componentfactory~ComponentFactory component factory}.\n *\n * See the {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar} in the editor\n * configuration reference to learn how to configure the menu bar.\n */\n fillFromConfig(config, componentFactory) {\n const locale = this.locale;\n const processedConfig = processMenuBarConfig({\n normalizedConfig: config,\n locale,\n componentFactory\n });\n const topLevelCategoryMenuViews = processedConfig.items.map(menuDefinition => this._createMenu({\n componentFactory,\n menuDefinition\n }));\n this.children.addMany(topLevelCategoryMenuViews);\n }\n /**\n * @inheritDoc\n */\n render() {\n super.render();\n MenuBarBehaviors.toggleMenusAndFocusItemsOnHover(this);\n MenuBarBehaviors.closeMenusWhenTheBarCloses(this);\n MenuBarBehaviors.closeMenuWhenAnotherOnTheSameLevelOpens(this);\n MenuBarBehaviors.focusCycleMenusOnArrows(this);\n MenuBarBehaviors.closeOnClickOutside(this);\n }\n /**\n * Focuses the menu bar.\n */\n focus() {\n if (this.children.first) {\n this.children.first.focus();\n }\n }\n /**\n * Closes all menus in the bar.\n */\n close() {\n for (const topLevelCategoryMenuView of this.children) {\n topLevelCategoryMenuView.isOpen = false;\n }\n }\n /**\n * Registers a menu view in the menu bar. Every {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} instance must be registered\n * in the menu bar to be properly managed.\n */\n registerMenu(menuView, parentMenuView = null) {\n if (parentMenuView) {\n menuView.delegate(...EVENT_NAME_DELEGATES).to(parentMenuView);\n menuView.parentMenuView = parentMenuView;\n }\n else {\n menuView.delegate(...EVENT_NAME_DELEGATES).to(this, name => 'menu:' + name);\n }\n menuView._attachBehaviors();\n this.menus.push(menuView);\n }\n /**\n * Creates a {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} based on the given definition.\n */\n _createMenu({ componentFactory, menuDefinition, parentMenuView }) {\n const locale = this.locale;\n const menuView = new MenuBarMenuView(locale);\n this.registerMenu(menuView, parentMenuView);\n menuView.buttonView.set({\n label: menuDefinition.label\n });\n // Defer the creation of the menu structure until it gets open. This is a performance optimization\n // that shortens the time needed to create the editor.\n menuView.once('change:isOpen', () => {\n const listView = new MenuBarMenuListView(locale);\n listView.ariaLabel = menuDefinition.label;\n menuView.panelView.children.add(listView);\n listView.items.addMany(this._createMenuItems({ menuDefinition, parentMenuView: menuView, componentFactory }));\n });\n return menuView;\n }\n /**\n * Creates a {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} items based on the given definition.\n */\n _createMenuItems({ menuDefinition, parentMenuView, componentFactory }) {\n const locale = this.locale;\n const items = [];\n for (const menuGroupDefinition of menuDefinition.groups) {\n for (const itemDefinition of menuGroupDefinition.items) {\n const menuItemView = new MenuBarMenuListItemView(locale, parentMenuView);\n if (isObject(itemDefinition)) {\n menuItemView.children.add(this._createMenu({\n componentFactory,\n menuDefinition: itemDefinition,\n parentMenuView\n }));\n }\n else {\n const componentView = this._createMenuItemContentFromFactory({\n componentName: itemDefinition,\n componentFactory,\n parentMenuView\n });\n if (!componentView) {\n continue;\n }\n menuItemView.children.add(componentView);\n }\n items.push(menuItemView);\n }\n // Separate groups with a separator.\n if (menuGroupDefinition !== menuDefinition.groups[menuDefinition.groups.length - 1]) {\n items.push(new ListSeparatorView(locale));\n }\n }\n return items;\n }\n /**\n * Uses the component factory to create a content of the menu item (a button or a sub-menu).\n */\n _createMenuItemContentFromFactory({ componentName, parentMenuView, componentFactory }) {\n const componentView = componentFactory.create(componentName);\n if (!(componentView instanceof MenuBarMenuView ||\n componentView instanceof MenuBarMenuListItemButtonView ||\n componentView instanceof MenuBarMenuListItemFileDialogButtonView)) {\n /**\n * Adding unsupported components to the {@link module:ui/menubar/menubarview~MenuBarView} is not possible.\n *\n * A component should be either a {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} (sub-menu) or a\n * {@link module:ui/menubar/menubarmenulistitembuttonview~MenuBarMenuListItemButtonView} (button).\n *\n * @error menu-bar-component-unsupported\n * @param componentName A name of the unsupported component used in the configuration.\n * @param componentView An unsupported component view.\n */\n logWarning('menu-bar-component-unsupported', {\n componentName,\n componentView\n });\n return null;\n }\n this._registerMenuTree(componentView, parentMenuView);\n // Close the whole menu bar when a component is executed.\n componentView.on('execute', () => {\n this.close();\n });\n return componentView;\n }\n /**\n * Checks component and its children recursively and calls {@link #registerMenu}\n * for each item that is {@link module:ui/menubar/menubarmenuview~MenuBarMenuView}.\n *\n * @internal\n */\n _registerMenuTree(componentView, parentMenuView) {\n if (!(componentView instanceof MenuBarMenuView)) {\n componentView.delegate('mouseenter').to(parentMenuView);\n return;\n }\n this.registerMenu(componentView, parentMenuView);\n const menuBarItemsList = componentView.panelView.children\n .filter(child => child instanceof MenuBarMenuListView)[0];\n if (!menuBarItemsList) {\n componentView.delegate('mouseenter').to(parentMenuView);\n return;\n }\n const nonSeparatorItems = menuBarItemsList.items.filter(item => item instanceof ListItemView);\n for (const item of nonSeparatorItems) {\n this._registerMenuTree(item.children.get(0), componentView);\n }\n }\n /**\n * Manages the state of the {@link #isOpen} property of the menu bar. Because the state is a sum of individual\n * top-level menus' states, it's necessary to listen to their changes and update the state accordingly.\n *\n * Additionally, it prevents from unnecessary changes of `isOpen` when one top-level menu opens and another closes\n * (regardless of in which order), maintaining a stable `isOpen === true` in that situation.\n */\n _setupIsOpenUpdater() {\n let closeTimeout;\n // TODO: This is not the prettiest approach but at least it's simple.\n this.on('menu:change:isOpen', (evt, name, isOpen) => {\n clearTimeout(closeTimeout);\n if (isOpen) {\n this.isOpen = true;\n }\n else {\n closeTimeout = setTimeout(() => {\n this.isOpen = Array.from(this.children).some(menuView => menuView.isOpen);\n }, 0);\n }\n });\n }\n}\n","/**\n * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license\n */\nimport { EditorUI, normalizeToolbarConfig, normalizeMenuBarConfig, DialogView } from 'ckeditor5/src/ui.js';\nimport { enablePlaceholder } from 'ckeditor5/src/engine.js';\nimport { ElementReplacer, Rect } from 'ckeditor5/src/utils.js';\n/**\n * The classic editor UI class.\n */\nexport default class ClassicEditorUI extends EditorUI {\n /**\n * Creates an instance of the classic editor UI class.\n *\n * @param editor The editor instance.\n * @param view The view of the UI.\n */\n constructor(editor, view) {\n super(editor);\n this.view = view;\n this._toolbarConfig = normalizeToolbarConfig(editor.config.get('toolbar'));\n // We use config.define in ClassicEditor, there will always be some configuration.\n this._menuBarConfig = normalizeMenuBarConfig(editor.config.get('menuBar') || {});\n this._elementReplacer = new ElementReplacer();\n this.listenTo(editor.editing.view, 'scrollToTheSelection', this._handleScrollToTheSelectionWithStickyPanel.bind(this));\n }\n /**\n * @inheritDoc\n */\n get element() {\n return this.view.element;\n }\n /**\n * Initializes the UI.\n *\n * @param replacementElement The DOM element that will be the source for the created editor.\n */\n init(replacementElement) {\n const editor = this.editor;\n const view = this.view;\n const editingView = editor.editing.view;\n const editable = view.editable;\n const editingRoot = editingView.document.getRoot();\n // The editable UI and editing root should share the same name. Then name is used\n // to recognize the particular editable, for instance in ARIA attributes.\n editable.name = editingRoot.rootName;\n view.render();\n // The editable UI element in DOM is available for sure only after the editor UI view has been rendered.\n // But it can be available earlier if a DOM element has been passed to BalloonEditor.create().\n const editableElement = editable.element;\n // Register the editable UI view in the editor. A single editor instance can aggregate multiple\n // editable areas (roots) but the classic editor has only one.\n this.setEditableElement(editable.name, editableElement);\n // Let the editable UI element respond to the changes in the global editor focus\n // tracker. It has been added to the same tracker a few lines above but, in reality, there are\n // many focusable areas in the editor, like balloons, toolbars or dropdowns and as long\n // as they have focus, the editable should act like it is focused too (although technically\n // it isn't), e.g. by setting the proper CSS class, visually announcing focus to the user.\n // Doing otherwise will result in editable focus styles disappearing, once e.g. the\n // toolbar gets focused.\n view.editable.bind('isFocused').to(this.focusTracker);\n // Bind the editable UI element to the editing view, making it an end– and entry–point\n // of the editor's engine. This is where the engine meets the UI.\n editingView.attachDomRoot(editableElement);\n // If an element containing the initial data of the editor was provided, replace it with\n // an editor instance's UI in DOM until the editor is destroyed. For instance, a