UNPKG

@ckeditor/ckeditor5-core

Version:

The core architecture of CKEditor 5 – the best browser-based rich text editor.

4,440 lines • 167 kB
/**
 * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
 */
import { CKEditorError, Collection, Config, EmitterMixin, KeystrokeHandler, Locale, ObservableMixin, crc32, decodeLicenseKey, env, getDataFromElement, global, insertToPriorityArray, isFeatureBlockedByLicenseKey, logError, logWarning, releaseDate, setDataInElement, toArray, uid } from "@ckeditor/ckeditor5-utils";
import { get, isElement, isFunction, isPlainObject, set } from "es-toolkit/compat";
import { Conversion, DataController, EditingController, Model, StylesProcessor } from "@ckeditor/ckeditor5-engine";

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/plugin
*/
const PluginBase = /* #__PURE__ */ ObservableMixin();
/**
* The base class for CKEditor plugin classes.
*/
var Plugin = class extends PluginBase {
	/**
	* The editor instance.
	*
	* Note that most editors implement the {@link module:core/editor/editor~Editor#ui} property.
	* However, editors with an external UI (i.e. Bootstrap-based) or a headless editor may not have this property or
	* throw an error when accessing it.
	*
	* Because of above, to make plugins more universal, it is recommended to split features into:
	*  - The "editing" part that uses the {@link module:core/editor/editor~Editor} class without `ui` property.
	*  - The "UI" part that uses the {@link module:core/editor/editor~Editor} class and accesses `ui` property.
	*/
	editor;
	/**
	* Holds identifiers for {@link #forceDisabled} mechanism.
	*/
	_disableStack = /* @__PURE__ */ new Set();
	/**
	* @inheritDoc
	*/
	constructor(editor) {
		super();
		this.editor = editor;
		this.set("isEnabled", true);
	}
	/**
	* Disables the plugin.
	*
	* Plugin may be disabled by multiple features or algorithms (at once). When disabling a plugin, unique id should be passed
	* (e.g. feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the plugin.
	* The plugin becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
	*
	* Disabling and enabling a plugin:
	*
	* ```ts
	* plugin.isEnabled; // -> true
	* plugin.forceDisabled( 'MyFeature' );
	* plugin.isEnabled; // -> false
	* plugin.clearForceDisabled( 'MyFeature' );
	* plugin.isEnabled; // -> true
	* ```
	*
	* Plugin disabled by multiple features:
	*
	* ```ts
	* plugin.forceDisabled( 'MyFeature' );
	* plugin.forceDisabled( 'OtherFeature' );
	* plugin.clearForceDisabled( 'MyFeature' );
	* plugin.isEnabled; // -> false
	* plugin.clearForceDisabled( 'OtherFeature' );
	* plugin.isEnabled; // -> true
	* ```
	*
	* Multiple disabling with the same identifier is redundant:
	*
	* ```ts
	* plugin.forceDisabled( 'MyFeature' );
	* plugin.forceDisabled( 'MyFeature' );
	* plugin.clearForceDisabled( 'MyFeature' );
	* plugin.isEnabled; // -> true
	* ```
	*
	* **Note:** some plugins or algorithms may have more complex logic when it comes to enabling or disabling certain plugins,
	* so the plugin might be still disabled after {@link #clearForceDisabled} was used.
	*
	* @param id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the plugin.
	*/
	forceDisabled(id) {
		this._disableStack.add(id);
		if (this._disableStack.size == 1) {
			this.on("set:isEnabled", forceDisable$1, { priority: "highest" });
			this.isEnabled = false;
		}
	}
	/**
	* Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.
	*
	* @param id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
	*/
	clearForceDisabled(id) {
		this._disableStack.delete(id);
		if (this._disableStack.size == 0) {
			this.off("set:isEnabled", forceDisable$1);
			this.isEnabled = true;
		}
	}
	/**
	* @inheritDoc
	*/
	destroy() {
		this.stopListening();
	}
	/**
	* @inheritDoc
	*/
	static get isContextPlugin() {
		return false;
	}
	/**
	* @inheritDoc
	* @internal
	*/
	static get isOfficialPlugin() {
		return false;
	}
	/**
	* @inheritDoc
	* @internal
	*/
	static get isPremiumPlugin() {
		return false;
	}
};
/**
* Helper function that forces plugin to be disabled.
*/
function forceDisable$1(evt) {
	evt.return = false;
	evt.stop();
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/command
*/
const CommandBase = /* #__PURE__ */ ObservableMixin();
/**
* Base class for the CKEditor commands.
*
* Commands are the main way to manipulate the editor contents and state. They are mostly used by UI elements (or by other
* commands) to make changes in the model. Commands are available in every part of the code that has access to
* the {@link module:core/editor/editor~Editor editor} instance.
*
* Instances of registered commands can be retrieved from {@link module:core/editor/editor~Editor#commands `editor.commands`}.
* The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute `editor.execute()`}.
*
* By default, commands are disabled when the editor is in the {@link module:core/editor/editor~Editor#isReadOnly read-only} mode
* but commands with the {@link module:core/command~Command#affectsData `affectsData`} flag set to `false` will not be disabled.
*/
var Command = class extends CommandBase {
	/**
	* The editor on which this command will be used.
	*/
	editor;
	/**
	* A flag indicating whether a command's `isEnabled` state should be changed depending on where the document
	* selection is placed.
	*
	* By default, it is set to `true`. If the document selection is placed in a
	* {@link module:engine/model/model~Model#canEditAt non-editable} place (such as non-editable root), the command becomes disabled.
	*
	* The flag should be changed to `false` in a concrete command's constructor if the command should not change its `isEnabled`
	* accordingly to the document selection.
	*/
	_isEnabledBasedOnSelection;
	/**
	* A flag indicating whether a command execution changes the editor data or not.
	*
	* @see #affectsData
	*/
	_affectsData;
	/**
	* Holds identifiers for {@link #forceDisabled} mechanism.
	*/
	_disableStack;
	/**
	* `Command` class is commonly put in `config.plugins` array.
	*
	* This property helps with better error detection.
	*
	* @internal
	*/
	static get _throwErrorWhenUsedAsAPlugin() {
		return true;
	}
	/**
	* Creates a new `Command` instance.
	*
	* @param editor The editor on which this command will be used.
	*/
	constructor(editor) {
		super();
		this.editor = editor;
		this.set("value", void 0);
		this.set("isEnabled", false);
		this._affectsData = true;
		this._isEnabledBasedOnSelection = true;
		this._disableStack = /* @__PURE__ */ new Set();
		this.decorate("execute");
		this.listenTo(this.editor.model.document, "change", () => {
			this.refresh();
		});
		this.listenTo(editor, "change:isReadOnly", () => {
			this.refresh();
		});
		this.on("set:isEnabled", (evt) => {
			if (!this.affectsData) return;
			const selection = editor.model.document.selection;
			const canEditAtSelection = !(selection.getFirstPosition().root.rootName == "$graveyard") && editor.model.canEditAt(selection);
			if (editor.isReadOnly || this._isEnabledBasedOnSelection && !canEditAtSelection) {
				evt.return = false;
				evt.stop();
			}
		}, { priority: "highest" });
		this.on("execute", (evt) => {
			if (!this.isEnabled) evt.stop();
		}, { priority: "high" });
	}
	/**
	* A flag indicating whether a command execution changes the editor data or not.
	*
	* Commands with `affectsData` set to `false` will not be automatically disabled in
	* the {@link module:core/editor/editor~Editor#isReadOnly read-only mode} and
	* {@glink features/read-only#related-features other editor modes} with restricted user write permissions.
	*
	* **Note:** You do not have to set it for your every command. It is `true` by default.
	*
	* @default true
	*/
	get affectsData() {
		return this._affectsData;
	}
	set affectsData(affectsData) {
		this._affectsData = affectsData;
	}
	/**
	* Refreshes the command. The command should update its {@link #isEnabled} and {@link #value} properties
	* in this method.
	*
	* This method is automatically called when
	* {@link module:engine/model/document~ModelDocument#event:change any changes are applied to the document}.
	*/
	refresh() {
		this.isEnabled = true;
	}
	/**
	* Disables the command.
	*
	* Command may be disabled by multiple features or algorithms (at once). When disabling a command, unique id should be passed
	* (e.g. the feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the command.
	* The command becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
	*
	* Disabling and enabling a command:
	*
	* ```ts
	* command.isEnabled; // -> true
	* command.forceDisabled( 'MyFeature' );
	* command.isEnabled; // -> false
	* command.clearForceDisabled( 'MyFeature' );
	* command.isEnabled; // -> true
	* ```
	*
	* Command disabled by multiple features:
	*
	* ```ts
	* command.forceDisabled( 'MyFeature' );
	* command.forceDisabled( 'OtherFeature' );
	* command.clearForceDisabled( 'MyFeature' );
	* command.isEnabled; // -> false
	* command.clearForceDisabled( 'OtherFeature' );
	* command.isEnabled; // -> true
	* ```
	*
	* Multiple disabling with the same identifier is redundant:
	*
	* ```ts
	* command.forceDisabled( 'MyFeature' );
	* command.forceDisabled( 'MyFeature' );
	* command.clearForceDisabled( 'MyFeature' );
	* command.isEnabled; // -> true
	* ```
	*
	* **Note:** some commands or algorithms may have more complex logic when it comes to enabling or disabling certain commands,
	* so the command might be still disabled after {@link #clearForceDisabled} was used.
	*
	* @param id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the command.
	*/
	forceDisabled(id) {
		this._disableStack.add(id);
		if (this._disableStack.size == 1) {
			this.on("set:isEnabled", forceDisable, { priority: "highest" });
			this.isEnabled = false;
		}
	}
	/**
	* Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.
	*
	* @param id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
	*/
	clearForceDisabled(id) {
		this._disableStack.delete(id);
		if (this._disableStack.size == 0) {
			this.off("set:isEnabled", forceDisable);
			this.refresh();
		}
	}
	/**
	* Executes the command.
	*
	* A command may accept parameters. They will be passed from {@link module:core/editor/editor~Editor#execute `editor.execute()`}
	* to the command.
	*
	* The `execute()` method will automatically abort when the command is disabled ({@link #isEnabled} is `false`).
	* This behavior is implemented by a high priority listener to the {@link #event:execute} event.
	*
	* In order to see how to disable a command from "outside" see the {@link #isEnabled} documentation.
	*
	* This method may return a value, which would be forwarded all the way down to the
	* {@link module:core/editor/editor~Editor#execute `editor.execute()`}.
	*
	* @fires execute
	*/
	execute(...args) {}
	/**
	* Destroys the command.
	*/
	destroy() {
		this.stopListening();
	}
};
/**
* Helper function that forces command to be disabled.
*/
function forceDisable(evt) {
	evt.return = false;
	evt.stop();
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/multicommand
*/
/**
* A CKEditor command that aggregates other commands.
*
* This command is used to proxy multiple commands. The multi-command is enabled when
* at least one of its registered child commands is enabled.
* When executing a multi-command, the first enabled command with highest priority will be executed.
*
* ```ts
* const multiCommand = new MultiCommand( editor );
*
* const commandFoo = new Command( editor );
* const commandBar = new Command( editor );
*
* // Register a child command.
* multiCommand.registerChildCommand( commandFoo );
* // Register a child command with a low priority.
* multiCommand.registerChildCommand( commandBar, { priority: 'low' } );
*
* // Enable one of the commands.
* commandBar.isEnabled = true;
*
* multiCommand.execute(); // Will execute commandBar.
* ```
*/
var MultiCommand = class extends Command {
	/**
	* Registered child commands definitions.
	*/
	_childCommandsDefinitions = [];
	/**
	* @inheritDoc
	*/
	refresh() {}
	/**
	* Executes the first enabled command which has the highest priority of all registered child commands.
	*
	* @returns The value returned by the {@link module:core/command~Command#execute `command.execute()`}.
	*/
	execute(...args) {
		const command = this._getFirstEnabledCommand();
		return !!command && command.execute(args);
	}
	/**
	* Registers a child command.
	*
	* @param options An object with configuration options.
	* @param options.priority Priority of a command to register.
	*/
	registerChildCommand(command, options = {}) {
		insertToPriorityArray(this._childCommandsDefinitions, {
			command,
			priority: options.priority || "normal"
		});
		command.on("change:isEnabled", () => this._checkEnabled());
		this._checkEnabled();
	}
	/**
	* Checks if any of child commands is enabled.
	*/
	_checkEnabled() {
		this.isEnabled = !!this._getFirstEnabledCommand();
	}
	/**
	* Returns a first enabled command with the highest priority or `undefined` if none of them is enabled.
	*/
	_getFirstEnabledCommand() {
		const commandDefinition = this._childCommandsDefinitions.find(({ command }) => command.isEnabled);
		return commandDefinition && commandDefinition.command;
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/commandcollection
*/
/**
* Collection of commands. Its instance is available in {@link module:core/editor/editor~Editor#commands `editor.commands`}.
*/
var CommandCollection = class {
	/**
	* Command map.
	*/
	_commands;
	/**
	* Creates collection instance.
	*/
	constructor() {
		this._commands = /* @__PURE__ */ new Map();
	}
	/**
	* Registers a new command.
	*
	* @param commandName The name of the command.
	*/
	add(commandName, command) {
		this._commands.set(commandName, command);
	}
	/**
	* Retrieves a command from the collection.
	*
	* @param commandName The name of the command.
	*/
	get(commandName) {
		return this._commands.get(commandName);
	}
	/**
	* Executes a command.
	*
	* @param commandName The name of the command.
	* @param commandParams Command parameters.
	* @returns The value returned by the {@link module:core/command~Command#execute `command.execute()`}.
	*/
	execute(commandName, ...commandParams) {
		const command = this.get(commandName);
		if (!command)
 /**
		* Command does not exist.
		*
		* @error commandcollection-command-not-found
		* @param {string} commandName Name of the command.
		*/
		throw new CKEditorError("commandcollection-command-not-found", this, { commandName });
		return command.execute(...commandParams);
	}
	/**
	* Returns iterator of command names.
	*/
	*names() {
		yield* this._commands.keys();
	}
	/**
	* Returns iterator of command instances.
	*/
	*commands() {
		yield* this._commands.values();
	}
	/**
	* Iterable interface.
	*
	* Returns `[ commandName, commandInstance ]` pairs.
	*/
	[Symbol.iterator]() {
		return this._commands[Symbol.iterator]();
	}
	/**
	* Destroys all collection commands.
	*/
	destroy() {
		for (const command of this.commands()) command.destroy();
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/plugincollection
*/
const PluginCollectionBase = /* #__PURE__ */ EmitterMixin();
/**
* Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
*/
var PluginCollection = class extends PluginCollectionBase {
	_context;
	_plugins = /* @__PURE__ */ new Map();
	/**
	* A map of plugin constructors that can be retrieved by their names.
	*/
	_availablePlugins;
	/**
	* Map of {@link module:core/contextplugin~ContextPlugin context plugins} which can be retrieved by their constructors or instances.
	*/
	_contextPlugins;
	/**
	* Creates an instance of the plugin collection class.
	* Allows loading and initializing plugins and their dependencies.
	* Allows providing a list of already loaded plugins. These plugins will not be destroyed along with this collection.
	*
	* @param availablePlugins Plugins (constructors) which the collection will be able to use
	* when {@link module:core/plugincollection~PluginCollection#init} is used with the plugin names (strings, instead of constructors).
	* Usually, the editor will pass its built-in plugins to the collection so they can later be
	* used in `config.plugins` or `config.removePlugins` by names.
	* @param contextPlugins A list of already initialized plugins represented by a `[ PluginConstructor, pluginInstance ]` pair.
	*/
	constructor(context, availablePlugins = [], contextPlugins = []) {
		super();
		this._context = context;
		this._availablePlugins = /* @__PURE__ */ new Map();
		for (const PluginConstructor of availablePlugins) if (PluginConstructor.pluginName) this._availablePlugins.set(PluginConstructor.pluginName, PluginConstructor);
		this._contextPlugins = /* @__PURE__ */ new Map();
		for (const [PluginConstructor, pluginInstance] of contextPlugins) {
			this._contextPlugins.set(PluginConstructor, pluginInstance);
			this._contextPlugins.set(pluginInstance, PluginConstructor);
			if (PluginConstructor.pluginName) this._availablePlugins.set(PluginConstructor.pluginName, PluginConstructor);
		}
	}
	/**
	* Iterable interface.
	*
	* Returns `[ PluginConstructor, pluginInstance ]` pairs.
	*/
	*[Symbol.iterator]() {
		for (const entry of this._plugins) if (typeof entry[0] == "function") yield entry;
	}
	/**
	* Gets the plugin instance by its constructor or name.
	*
	* ```ts
	* // Check if 'Clipboard' plugin was loaded.
	* if ( editor.plugins.has( 'ClipboardPipeline' ) ) {
	* 	// Get clipboard plugin instance
	* 	const clipboard = editor.plugins.get( 'ClipboardPipeline' );
	*
	* 	this.listenTo( clipboard, 'inputTransformation', ( evt, data ) => {
	* 		// Do something on clipboard input.
	* 	} );
	* }
	* ```
	*
	* **Note**: This method will throw an error if a plugin is not loaded. Use `{@link #has editor.plugins.has()}`
	* to check if a plugin is available.
	*
	* @param key The plugin constructor or {@link module:core/plugin~PluginStaticMembers#pluginName name}.
	*/
	get(key) {
		const plugin = this._plugins.get(key);
		if (!plugin) {
			let pluginName = key;
			if (typeof key == "function") pluginName = key.pluginName || key.name;
			/**
			* The plugin is not loaded and could not be obtained.
			*
			* Plugin classes (constructors) need to be provided to the editor and must be loaded before they can be obtained from
			* the plugin collection.
			*
			* **Note**: You can use `{@link module:core/plugincollection~PluginCollection#has editor.plugins.has()}`
			* to check if a plugin was loaded.
			*
			* @error plugincollection-plugin-not-loaded
			* @param {string} plugin The name of the plugin which is not loaded.
			*/
			throw new CKEditorError("plugincollection-plugin-not-loaded", this._context, { plugin: pluginName });
		}
		return plugin;
	}
	/**
	* Checks if a plugin is loaded.
	*
	* ```ts
	* // Check if the 'Clipboard' plugin was loaded.
	* if ( editor.plugins.has( 'ClipboardPipeline' ) ) {
	* 	// Now use the clipboard plugin instance:
	* 	const clipboard = editor.plugins.get( 'ClipboardPipeline' );
	*
	* 	// ...
	* }
	* ```
	*
	* @param key The plugin constructor or {@link module:core/plugin~PluginStaticMembers#pluginName name}.
	*/
	has(key) {
		return this._plugins.has(key);
	}
	/**
	* Initializes a set of plugins and adds them to the collection.
	*
	* @param plugins An array of {@link module:core/plugin~PluginInterface plugin constructors}
	* or {@link module:core/plugin~PluginStaticMembers#pluginName plugin names}.
	* @param pluginsToRemove Names of the plugins or plugin constructors
	* that should not be loaded (despite being specified in the `plugins` array).
	* @param pluginsSubstitutions An array of {@link module:core/plugin~PluginInterface plugin constructors}
	* that will be used to replace plugins of the same names that were passed in `plugins` or that are in their dependency tree.
	* A useful option for replacing built-in plugins while creating tests (for mocking their APIs). Plugins that will be replaced
	* must follow these rules:
	*   * The new plugin must be a class.
	*   * The new plugin must be named.
	*   * Both plugins must not depend on other plugins.
	* @returns A promise which gets resolved once all plugins are loaded and available in the collection.
	*/
	async init(plugins, pluginsToRemove = [], pluginsSubstitutions = []) {
		const that = this;
		const context = this._context;
		findAvailablePluginConstructors(plugins);
		validatePlugins(plugins);
		const pluginConstructors = [...getPluginConstructors(plugins.filter((plugin) => !isPluginRemoved(plugin, pluginsToRemove)))];
		substitutePlugins(pluginConstructors, pluginsSubstitutions);
		const pluginInstances = loadPlugins(pluginConstructors);
		await initPlugins(pluginInstances, "init");
		await initPlugins(pluginInstances, "afterInit");
		return pluginInstances;
		function isPluginConstructor(plugin) {
			return typeof plugin === "function";
		}
		function isContextPlugin(plugin) {
			return isPluginConstructor(plugin) && !!plugin.isContextPlugin;
		}
		function isPluginRemoved(plugin, pluginsToRemove) {
			return pluginsToRemove.some((removedPlugin) => {
				if (removedPlugin === plugin) return true;
				if (getPluginName(plugin) === removedPlugin) return true;
				if (getPluginName(removedPlugin) === plugin) return true;
				return false;
			});
		}
		function getPluginName(plugin) {
			return isPluginConstructor(plugin) ? plugin.pluginName || plugin.name : plugin;
		}
		function findAvailablePluginConstructors(plugins, processed = /* @__PURE__ */ new Set()) {
			plugins.forEach((plugin) => {
				if (!isPluginConstructor(plugin)) return;
				if (processed.has(plugin)) return;
				processed.add(plugin);
				if (plugin.pluginName && !that._availablePlugins.has(plugin.pluginName)) that._availablePlugins.set(plugin.pluginName, plugin);
				if (plugin.requires) findAvailablePluginConstructors(plugin.requires, processed);
			});
		}
		function getPluginConstructors(plugins, processed = /* @__PURE__ */ new Set()) {
			return plugins.map((plugin) => {
				return isPluginConstructor(plugin) ? plugin : that._availablePlugins.get(plugin);
			}).reduce((result, plugin) => {
				if (processed.has(plugin)) return result;
				processed.add(plugin);
				if (plugin.requires) {
					validatePlugins(plugin.requires, plugin);
					getPluginConstructors(plugin.requires, processed).forEach((plugin) => result.add(plugin));
				}
				return result.add(plugin);
			}, /* @__PURE__ */ new Set());
		}
		function validatePlugins(plugins, parentPluginConstructor = null) {
			plugins.map((plugin) => {
				return isPluginConstructor(plugin) ? plugin : that._availablePlugins.get(plugin) || plugin;
			}).forEach((plugin) => {
				checkPluginConstructor(plugin);
				checkMissingPlugin(plugin, parentPluginConstructor);
				checkContextPlugin(plugin, parentPluginConstructor);
				checkRemovedPlugin(plugin, parentPluginConstructor);
			});
		}
		function checkPluginConstructor(plugin) {
			if (typeof plugin !== "function") return;
			if (plugin._throwErrorWhenUsedAsAPlugin)
 /**
			* The provided class constructor is not a plugin.
			*
			* This error is usually caused by passing an editor, context, or command
			* constructor in the `config.plugins` array.
			*
			* @param {String} name The name of the provided constructor.
			* @error plugincollection-plugin-invalid-constructor
			*/
			throw new CKEditorError("plugincollection-plugin-invalid-constructor", context, { name: plugin.name });
		}
		function checkMissingPlugin(plugin, parentPluginConstructor) {
			if (isPluginConstructor(plugin)) return;
			if (parentPluginConstructor)
 /**
			* A required "soft" dependency was not found on the plugin list.
			*
			* When configuring the editor, either prior to building (via
			* {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}) or when
			* creating a new instance of the editor (e.g. via
			* {@link module:core/editor/editorconfig~EditorConfig#plugins `config.plugins`}), you need to provide
			* some of the dependencies for other plugins that you used.
			*
			* This error is thrown when one of these dependencies was not provided. The name of the missing plugin
			* can be found in `missingPlugin` and the plugin that required it in `requiredBy`.
			*
			* In order to resolve it, you need to import the missing plugin and add it to the
			* current list of plugins (`Editor.builtinPlugins` or `config.plugins`/`config.extraPlugins`).
			*
			* Soft requirements were introduced in version 26.0.0. If you happen to stumble upon this error
			* when upgrading to version 26.0.0, read also the `Update to v26.x` section in the
			* {@glink updating/guides/updating-from-older-versions updating from older versions} guide.
			*
			* @error plugincollection-soft-required
			* @param {string} missingPlugin The name of the required plugin.
			* @param {string} requiredBy The name of the plugin that requires the other plugin.
			*/
			throw new CKEditorError("plugincollection-soft-required", context, {
				missingPlugin: plugin,
				requiredBy: getPluginName(parentPluginConstructor)
			});
			/**
			* A plugin is not available and could not be loaded.
			*
			* Plugin classes (constructors) need to be provided to the editor before they can be loaded by name.
			* This is usually done in the now deprecated CKEditor 5 builds by setting
			* the {@link module:core/editor/editor~Editor.builtinPlugins} property.
			*
			* **If you see this warning when using one of the deprecated CKEditor 5 Builds**,
			* it means that you tried to enable a plugin that was not included in that build. This may be due to a typo
			* in the plugin name or simply because that plugin was not a part of this build.
			*
			* **Predefined builds are no longer supported and you need to
			* {@glink updating/nim-migration/migration-to-new-installation-methods migrate to new installation methods}**.
			*
			* **If you see this warning when using one of the editor creators directly** (not a build), then it means
			* that you tried loading plugins by name. However, unlike CKEditor 4, CKEditor 5 does not implement a "plugin loader".
			* This means that CKEditor 5 does not know where to load the plugin modules from. Therefore, you need to
			* provide each plugin through a reference (as a constructor function). Check out the examples in the
			* {@glink getting-started/installation/cloud/quick-start Quick start} guide.
			*
			* @error plugincollection-plugin-not-found
			* @param {string} plugin The name of the plugin which could not be loaded.
			*/
			throw new CKEditorError("plugincollection-plugin-not-found", context, { plugin });
		}
		function checkContextPlugin(plugin, parentPluginConstructor) {
			if (!isContextPlugin(parentPluginConstructor)) return;
			if (isContextPlugin(plugin)) return;
			/**
			* If a plugin is a context plugin, all plugins it requires should also be context plugins
			* instead of plugins. In other words, if one plugin can be used in the context,
			* all its requirements should also be ready to be used in the context. Note that the context
			* provides only a part of the API provided by the editor. If one plugin needs a full
			* editor API, all plugins which require it are considered as plugins that need a full
			* editor API.
			*
			* @error plugincollection-context-required
			* @param {string} plugin The name of the required plugin.
			* @param {string} requiredBy The name of the parent plugin.
			*/
			throw new CKEditorError("plugincollection-context-required", context, {
				plugin: getPluginName(plugin),
				requiredBy: getPluginName(parentPluginConstructor)
			});
		}
		function checkRemovedPlugin(plugin, parentPluginConstructor) {
			if (!parentPluginConstructor) return;
			if (!isPluginRemoved(plugin, pluginsToRemove)) return;
			/**
			* Cannot load a plugin because one of its dependencies is listed in the `removePlugins` option.
			*
			* @error plugincollection-required
			* @param {string} plugin The name of the required plugin.
			* @param {string} requiredBy The name of the parent plugin.
			*/
			throw new CKEditorError("plugincollection-required", context, {
				plugin: getPluginName(plugin),
				requiredBy: getPluginName(parentPluginConstructor)
			});
		}
		function loadPlugins(pluginConstructors) {
			return pluginConstructors.map((PluginConstructor) => {
				let pluginInstance = that._contextPlugins.get(PluginConstructor);
				pluginInstance = pluginInstance || new PluginConstructor(context);
				that._add(PluginConstructor, pluginInstance);
				return pluginInstance;
			});
		}
		function initPlugins(pluginInstances, method) {
			return pluginInstances.reduce((promise, plugin) => {
				if (!plugin[method]) return promise;
				if (that._contextPlugins.has(plugin)) return promise;
				return promise.then(plugin[method].bind(plugin));
			}, Promise.resolve());
		}
		/**
		* Replaces plugin constructors with the specified set of plugins.
		*/
		function substitutePlugins(pluginConstructors, pluginsSubstitutions) {
			for (const pluginItem of pluginsSubstitutions) {
				if (typeof pluginItem != "function")
 /**
				* The plugin replacing an existing plugin must be a function.
				*
				* @error plugincollection-replace-plugin-invalid-type
				* @param {never} pluginItem The plugin item.
				*/
				throw new CKEditorError("plugincollection-replace-plugin-invalid-type", null, { pluginItem });
				const pluginName = pluginItem.pluginName;
				if (!pluginName)
 /**
				* The plugin replacing an existing plugin must have a name.
				*
				* @error plugincollection-replace-plugin-missing-name
				* @param {module:core/plugin~PluginConstructor} pluginItem The plugin item.
				*/
				throw new CKEditorError("plugincollection-replace-plugin-missing-name", null, { pluginItem });
				if (pluginItem.requires && pluginItem.requires.length)
 /**
				* The plugin replacing an existing plugin cannot depend on other plugins.
				*
				* @error plugincollection-plugin-for-replacing-cannot-have-dependencies
				* @param {string} pluginName The name of the plugin.
				*/
				throw new CKEditorError("plugincollection-plugin-for-replacing-cannot-have-dependencies", null, { pluginName });
				const pluginToReplace = that._availablePlugins.get(pluginName);
				if (!pluginToReplace)
 /**
				* The replaced plugin does not exist in the
				* {@link module:core/plugincollection~PluginCollection available plugins} collection.
				*
				* @error plugincollection-plugin-for-replacing-not-exist
				* @param {string} pluginName The name of the plugin.
				*/
				throw new CKEditorError("plugincollection-plugin-for-replacing-not-exist", null, { pluginName });
				const indexInPluginConstructors = pluginConstructors.indexOf(pluginToReplace);
				if (indexInPluginConstructors === -1) {
					if (that._contextPlugins.has(pluginToReplace)) return;
					/**
					* The replaced plugin will not be loaded so it cannot be replaced.
					*
					* @error plugincollection-plugin-for-replacing-not-loaded
					* @param {string} pluginName The name of the plugin.
					*/
					throw new CKEditorError("plugincollection-plugin-for-replacing-not-loaded", null, { pluginName });
				}
				if (pluginToReplace.requires && pluginToReplace.requires.length)
 /**
				* The replaced plugin cannot depend on other plugins.
				*
				* @error plugincollection-replaced-plugin-cannot-have-dependencies
				* @param {string} pluginName The name of the plugin.
				*/
				throw new CKEditorError("plugincollection-replaced-plugin-cannot-have-dependencies", null, { pluginName });
				pluginConstructors.splice(indexInPluginConstructors, 1, pluginItem);
				that._availablePlugins.set(pluginName, pluginItem);
			}
		}
	}
	/**
	* Destroys all loaded plugins.
	*/
	destroy() {
		const promises = [];
		for (const [, pluginInstance] of this) if (typeof pluginInstance.destroy == "function" && !this._contextPlugins.has(pluginInstance)) promises.push(pluginInstance.destroy());
		return Promise.all(promises);
	}
	/**
	* Adds the plugin to the collection. Exposed mainly for testing purposes.
	*
	* @param PluginConstructor The plugin constructor.
	* @param plugin The instance of the plugin.
	*/
	_add(PluginConstructor, plugin) {
		this._plugins.set(PluginConstructor, plugin);
		const pluginName = PluginConstructor.pluginName;
		if (!pluginName) return;
		if (this._plugins.has(pluginName))
 /**
		* Two plugins with the same {@link module:core/plugin~PluginStaticMembers#pluginName} were loaded.
		* This will lead to runtime conflicts between these plugins.
		*
		* In practice, this warning usually means that new plugins were added to an existing CKEditor 5 build.
		* Plugins should always be added to a source version of the editor (`@ckeditor/ckeditor5-editor-*`),
		* not to an editor imported from one of the `@ckeditor/ckeditor5-build-*` packages.
		*
		* Check your import paths and the list of plugins passed to
		* {@link module:core/editor/editor~Editor.create `Editor.create()`}
		* or specified in {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
		*
		* Predefined builds are a deprecated solution and we strongly advise
		* {@glink updating/nim-migration/migration-to-new-installation-methods migrating to new installation methods}.
		*
		* The second option is that your `node_modules/` directory contains duplicated versions of the same
		* CKEditor 5 packages. Normally, on clean installations, npm deduplicates packages in `node_modules/`, so
		* it may be enough to call `rm -rf node_modules && npm i`. However, if you installed conflicting versions
		* of some packages, their dependencies may need to be installed in more than one version which may lead to this
		* warning.
		*
		* Technically speaking, this error occurs because after adding a plugin to an existing editor build
		* the dependencies of this plugin are being duplicated.
		* They are already built into that editor build and now get added for the second time as dependencies
		* of the plugin you are installing.
		*
		* @error plugincollection-plugin-name-conflict
		* @param {string} pluginName The duplicated plugin name.
		* @param {module:core/plugin~PluginConstructor} plugin1 The first plugin constructor.
		* @param {module:core/plugin~PluginConstructor} plugin2 The second plugin constructor.
		*/
		throw new CKEditorError("plugincollection-plugin-name-conflict", null, {
			pluginName,
			plugin1: this._plugins.get(pluginName).constructor,
			plugin2: PluginConstructor
		});
		this._plugins.set(pluginName, plugin);
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/errorattribution/getsubnodes
*/
/**
* @internal
*/
function getSubNodes(head, excludedProperties = /* @__PURE__ */ new Set()) {
	const nodes = [head];
	const subNodes = /* @__PURE__ */ new Set();
	let nodeIndex = 0;
	while (nodes.length > nodeIndex) {
		const node = nodes[nodeIndex++];
		if (subNodes.has(node) || !shouldNodeBeIncluded(node) || excludedProperties.has(node)) continue;
		subNodes.add(node);
		if (Symbol.iterator in node) try {
			for (const n of node) nodes.push(n);
		} catch {}
		else for (const key in node) {
			if (key === "defaultValue") continue;
			nodes.push(node[key]);
		}
	}
	return subNodes;
}
function shouldNodeBeIncluded(node) {
	const type = Object.prototype.toString.call(node);
	const typeOfNode = typeof node;
	return !(typeOfNode === "number" || typeOfNode === "boolean" || typeOfNode === "string" || typeOfNode === "symbol" || typeOfNode === "function" || type === "[object Date]" || type === "[object RegExp]" || type === "[object Module]" || node === void 0 || node === null || node._watchdogExcluded || node instanceof EventTarget || node instanceof Event);
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* Editors that are currently ready.
*
* Error attribution walks object graphs, and that walk answers "is this reachable from that" — a test
* between two objects rather than a lookup. Naming what an error belongs to therefore means running the
* test once per live editor, and this is the list to run it against.
*
* Held weakly. An editor is removed when it is destroyed, but an integrator may simply drop one instead —
* a single-page application changing route, for example. Holding those strongly would keep an editor alive
* that nothing else refers to, turning a leak that garbage collection would have cleaned up into one that
* it cannot.
*
* Temporary. It exists only for as long as attribution walks graphs; once every object that may be passed
* as an error context can name its editor directly, the answer is a single property read and this module
* goes away. It is kept behind functions rather than exposed as the set itself, and it is deliberately not
* exported from the package index, so that removal is not a breaking change.
*/
const editors = /* @__PURE__ */ new Set();
/**
* Adds an editor to the list of the ones error attribution may be asked about.
*
* @internal
*/
function _addLiveEditor(editor) {
	editors.add(new WeakRef(editor));
}
/**
* Removes an editor from the list of the ones error attribution may be asked about.
*
* @internal
*/
function _removeLiveEditor(editor) {
	for (const reference of editors) if (reference.deref() === editor) {
		editors.delete(reference);
		return;
	}
}
/**
* The editors error attribution may be asked about, in the order they became ready.
*
* References whose editor has been collected are dropped on the way through. That is the only cleanup
* there is, and it is enough: the list is read whenever an error is reported, and an entry left behind
* until then costs one dead reference.
*
* @internal
*/
function* _getLiveEditors() {
	yield* read(editors);
}
/**
* Contexts an integrator created, held the same way and for the same reason as the editors above.
*
* A context created by an editor for itself is not here. It holds that editor alone, so asking it would
* only repeat what asking the editor already answered.
*/
const contexts = /* @__PURE__ */ new Set();
/**
* Adds a context to the list of the ones error attribution may be asked about.
*
* Adding one twice registers it once, the way adding to a set does. Removal takes one entry, so a second
* registration would outlive the removal on destroy and a destroyed context would keep being named.
*
* @internal
*/
function _addLiveContext(context) {
	if (_isLiveContext(context)) return;
	contexts.add(new WeakRef(context));
}
/**
* Removes a context from the list of the ones error attribution may be asked about.
*
* @internal
*/
function _removeLiveContext(context) {
	for (const reference of contexts) if (reference.deref() === context) {
		contexts.delete(reference);
		return;
	}
}
/**
* The contexts error attribution may be asked about, in the order they were created.
*
* @internal
*/
function* _getLiveContexts() {
	yield* read(contexts);
}
/**
* Whether the context is one of those attribution may name.
*
* @internal
*/
function _isLiveContext(context) {
	for (const liveContext of _getLiveContexts()) if (liveContext === context) return true;
	return false;
}
/**
* Yields what is still alive, dropping the references whose object has been collected on the way through.
*/
function* read(references) {
	for (const reference of references) {
		const value = reference.deref();
		if (value) yield value;
		else references.delete(reference);
	}
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/errorattribution/areconnectedthroughproperties
*/
/**
* Traverses both structures to find out whether there is a reference that is shared between both structures.
*
* @internal
*/
function areConnectedThroughProperties(target1, target2, excludedNodes = /* @__PURE__ */ new Set()) {
	if (target1 === target2 && isObject(target1)) return true;
	const subNodes1 = getSubNodes(target1, excludedNodes);
	const subNodes2 = getSubNodes(target2, excludedNodes);
	for (const node of subNodes1) if (subNodes2.has(node)) return true;
	return false;
}
function isObject(structure) {
	return typeof structure === "object" && structure !== null;
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editingkeystrokehandler
*/
/**
* A keystroke handler for editor editing. Its instance is available
* in {@link module:core/editor/editor~Editor#keystrokes} so plugins
* can register their keystrokes.
*
* E.g. an undo plugin would do this:
*
* ```ts
* editor.keystrokes.set( 'Ctrl+Z', 'undo' );
* editor.keystrokes.set( 'Ctrl+Shift+Z', 'redo' );
* editor.keystrokes.set( 'Ctrl+Y', 'redo' );
* ```
*/
var EditingKeystrokeHandler = class extends KeystrokeHandler {
	/**
	* The editor instance.
	*/
	editor;
	/**
	* Creates an instance of the keystroke handler.
	*/
	constructor(editor) {
		super();
		this.editor = editor;
	}
	/**
	* Registers a handler for the specified keystroke.
	*
	* The handler can be specified as a command name or a callback.
	*
	* @param keystroke Keystroke defined in a format accepted by
	* the {@link module:utils/keyboard~parseKeystroke} function.
	* @param callback If a string is passed, then the keystroke will
	* {@link module:core/editor/editor~Editor#execute execute a command}.
	* If a function, then it will be called with the
	* {@link module:engine/view/observer/keyobserver~ViewDocumentKeyEventData key event data} object and
	* a `cancel()` helper to both `preventDefault()` and `stopPropagation()` of the event.
	* @param options Additional options.
	* @param options.priority The priority of the keystroke callback. The higher the priority value
	* the sooner the callback will be executed. Keystrokes having the same priority
	* are called in the order they were added.
	*/
	set(keystroke, callback, options = {}) {
		if (typeof callback == "string") {
			const commandName = callback;
			callback = (evtData, cancel) => {
				this.editor.execute(commandName);
				cancel();
			};
		}
		super.set(keystroke, callback, options);
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/accessibility
*/
const DEFAULT_CATEGORY_ID = "contentEditing";
/**
* @internal
*/
const DEFAULT_GROUP_ID = "common";
/**
* A common namespace for various accessibility features of the editor.
*
* **Information about editor keystrokes**
*
* * The information about keystrokes available in the editor is stored in the {@link #keystrokeInfos} property.
* * New info entries can be added using the {@link #addKeystrokeInfoCategory}, {@link #addKeystrokeInfoGroup},
* and {@link #addKeystrokeInfos} methods.
*/
var Accessibility = class {
	/**
	* Stores information about keystrokes brought by editor features for the users to interact with the editor, mainly
	* keystroke combinations and their accessible labels.
	*
	* This information is particularly useful for screen reader and other assistive technology users. It gets displayed
	* by the {@link module:ui/editorui/accessibilityhelp/accessibilityhelp~AccessibilityHelp Accessibility help} dialog.
	*
	* Keystrokes are organized in categories and groups. They can be added using ({@link #addKeystrokeInfoCategory},
	* {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfos}) methods.
	*
	* Please note that:
	* * two categories are always available:
	*   * `'contentEditing'` for keystrokes related to content creation,
	*   * `'navigation'` for keystrokes related to navigation in the UI and the content.
	* * unless specified otherwise, new keystrokes are added into the `'contentEditing'` category and the `'common'`
	* keystroke group within that category while using the {@link #addKeystrokeInfos} method.
	*/
	keystrokeInfos = /* @__PURE__ */ new Map();
	/**
	* The editor instance.
	*/
	_editor;
	/**
	* @inheritDoc
	*/
	constructor(editor) {
		this._editor = editor;
		const isMenuBarVisible = editor.config.get("menuBar.isVisible");
		const t = editor.locale.t;
		this.addKeystrokeInfoCategory({
			id: DEFAULT_CATEGORY_ID,
			label: t("Content editing keystrokes"),
			description: t("These keyboard shortcuts allow for quick access to content editing features.")
		});
		const navigationKeystrokes = [
			{
				label: t("Close contextual balloons, dropdowns, and dialogs"),
				keystroke: "Esc"
			},
			{
				label: t("Open the accessibility help dialog"),
				keystroke: "Alt+0"
			},
			{
				label: t("Move focus between form fields (inputs, buttons, etc.)"),
				keystroke: [["Tab"], ["Shift+Tab"]]
			},
			{
				label: t("Move focus to the toolbar, navigate between toolbars"),
				keystroke: "Alt+F10",
				mayRequireFn: true
			},
			{
				label: t("Navigate through the toolbar or menu bar"),
				keystroke: [
					["arrowup"],
					["arrowright"],
					["arrowdown"],
					["arrowleft"]
				]
			},
			{
				label: t("Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content."),
				keystroke: [["Enter"], ["Space"]]
			}
		];
		if (isMenuBarVisible) navigationKeystrokes.push({
			label: t("Move focus to the menu bar, navigate between menu bars"),
			keystroke: "Alt+F9",
			mayRequireFn: true
		});
		this.addKeystrokeInfoCategory({
			id: "navigation",
			label: t("User interface and content navigation keystrokes"),
			description: t("Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface."),
			groups: [{
				id: "common",
				keystrokes: navigationKeystrokes
			}]
		});
	}
	/**
	* Adds a top-level category in the {@link #keystrokeInfos keystroke information database} with a label and optional description.
	*
	* Categories organize keystrokes and help users to find the right keystroke. Each category can have multiple groups
	* of keystrokes that narrow down the context in which the keystrokes are available. Every keystroke category comes
	* with a `'common'` group by default.
	*
	* By default, two categories are available:
	* * `'contentEditing'` for keystrokes related to content creation,
	* * `'navigation'` for keystrokes related to navigation in the UI and the content.
	*
	* To create a new keystroke category with new groups, use the following code:
	*
	* ```js
	* class MyPlugin extends Plugin {
	* 	// ...
	* 	init() {
	* 		const editor = this.editor;
	* 		const t = editor.t;
	*
	* 		// ...
	*
	* 		editor.accessibility.addKeystrokeInfoCategory( {
	* 			id: 'myCategory',
	* 			label: t( 'My category' ),
	* 			description: t( 'My category description.' ),
	* 			groups: [
	* 				{
	* 					id: 'myGroup',
	* 					label: t( 'My keystroke group' ),
	* 					keystrokes: [
	* 						{
	* 							label: t( 'Keystroke label 1' ),
	* 							keystroke: 'Ctrl+Shift+N'
	* 						},
	* 						{
	* 							label: t( 'Keystroke label 2' ),
	* 							keystroke: 'Ctrl+Shift+M'
	* 						}
	* 					]
	* 				}
	* 			]
	* 		};
	* 	}
	* }
	* ```
	*
	* See {@link #keystrokeInfos}, {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfos}.
	*/
	addKeystrokeInfoCategory({ id, label, description, groups }) {
		this.keystrokeInfos.set(id, {
			id,
			label,
			description,
			groups: /* @__PURE__ */ new Map()
		});
		this.addKeystrokeInfoGroup({
			categoryId: id,
			id: DEFAULT_GROUP_ID
		});
		if (groups) groups.forEach((group) => {
			this.addKeystrokeInfoGroup({
				categoryId: id,
				...group
			});
		});
	}
	/**
	* Adds a group of keystrokes in a specific category to the {@link #keystrokeInfos keystroke information database}.
	*
	* Groups narrow down the context in which the keystrokes are available. When `categoryId` is not specified,
	* the group goes to the `'contentEditing'` category (default).
	*
	* To create a new group within an existing category, use the following code:
	*
	* ```js
	* class MyPlugin extends Plugin {
	* 	// ...
	* 	init() {
	* 		const editor = this.editor;
	* 		const t = editor.t;
	*
	* 		// ...
	*
	* 		editor.accessibility.addKeystrokeInfoGroup( {
	* 			id: 'myGroup',
	* 			categoryId: 'navigation',
	* 			label: t( 'My keystroke group' ),
	* 			keystrokes: [
	* 				{
	* 					label: t( 'Keystroke label 1' ),
	* 					keystroke: 'Ctrl+Shift+N'
	* 				},
	* 				{
	* 					label: t( 'Keystroke label 2' ),
	* 					keystroke: 'Ctrl+Shift+M'
	* 				}
	* 			]
	* 		} );
	* 	}
	* }
	* ```
	*
	* See {@link #keystrokeInfos}, {@link #addKeystrokeInfoCategory}, and {@link #addKeystrokeInfos}.
	*/
	addKeystrokeInfoGroup({ categoryId = DEFAULT_CATEGORY_ID, id, label, keystrokes }) {
		const category = this.keystrokeInfos.get(categoryId);
		if (!category) throw new CKEditorError("accessibility-unknown-keystroke-info-category", this._editor, {
			groupId: id,
			categoryId
		});
		category.groups.set(id, {
			id,
			label,
			keystrokes: keystrokes || []
		});
	}
	/**
	* Adds information about keystrokes to the {@link #keystrokeInfos keystroke information database}.
	*
	* Keystrokes without specified `groupId` or `categoryId` go to the `'common'` group in the `'contentEditing'` category (default).
	*
	* To add a keystroke brought by your plugin (using default group and category), use the following code:
	*
	* ```js
	* class MyPlugin extends Plugin {
	* 	// ...
	* 	init() {
	* 		const editor = this.editor;
	* 		const t = editor.t;
	*
	* 		// ...
	*
	* 		editor.accessibility.addKeystrokeInfos( {
	* 			keystrokes: [
	* 				{
	* 					label: t( 'Keystroke label' ),
	* 					keystroke: 'CTRL+B'
	* 				}
	* 			]
	* 		} );
	* 	}
	* }
	* ```
	* To add a keystroke in a specific existing `'widget'` group in the default `'contentEditing'` category:
	*
	* ```js
	* class MyPlugin extends Plugin {
	* 	// ...
	* 	init() {
	* 		const editor = this.editor;
	* 		const t = editor.t;
	*
	* 		// ...
	*
	* 		editor.accessibility.addKeystrokeInfos( {
	* 			// Add a keystroke to the existing "widget" group.
	* 			groupId: 'widget',
	* 			keystrokes: [
	* 				{
	* 					label: t( 'A an action on a selected widget' ),
	* 					keystroke: 'Ctrl+D',
	* 				}
	* 			]
	* 		} );
	* 	}
	* }
	* ```
	*
	* To add a keystroke to another existing category (using default group):
	*
	* ```js
	* class MyPlugin extends Plugin {
	* 	// ...
	* 	init() {
	* 		const editor = this.editor;
	* 		const t = editor.t;
	*
	* 		// ...
	*
	* 		editor.accessibility.addKeystrokeInfos( {
	* 			// Add keystrokes to the "navigation" category (one of defaults).
	* 			categoryId: 'navigation',
	* 			keystrokes: [
	* 				{
	* 					label: t( 'Keystroke label' ),
	* 					keystroke: 'CTRL+B'
	* 				}
	* 			]
	* 		} );
	* 	}
	* }
	* ```
	*
	* See {@link #keystrokeInfos}, {@link #addKeystrokeInfoGroup}, and {@link #addKeystrokeInfoCategory}.
	*/
	addKeystrokeInfos({ categoryId = DEFAULT_CATEGORY_ID, groupId = DEFAULT_GROUP_ID, keystrokes }) {
		if (!this.keystrokeInfos.has(categoryId))
 /**
		* Cannot add keystrokes in an unknown category. Use
		* {@link module:core/accessibility~Accessibility#addKeystrokeInfoCategory}
		* to add a new category or make sure the specified category exists.
		*
		* @error accessibility-unknown-keystroke-info-category
		* @param {string} categoryId The id of the unknown keystroke category.
		* @param {module:core/accessibility~AddKeystrokeInfosData#keystrokes} keystrokes Keystroke definitions about to be added.
		*/
		throw new CKEditorError("accessibility-unknown-keystroke-info-category", this._editor, {
			categoryId,
			keystrokes
		});
		const category = this.keystrokeInfos.get(categoryId);
		if (!category.groups.has(groupId))
 /**
		* Cannot add keystrokes to an unknown group.
		*
		* Use {@link module:core/accessibility~Accessibility#addKeystrokeInfoGroup}
		* to add a new group or make sure the specified group exists.
		*
		* @error accessibility-unknown-keystroke-info-group
		* @param {string} groupId The id of the unknown keystroke group.
		* @param {string} categoryId The id of category the unknown group should belong to.
		* @param {module:core/accessibility~AddKeystrokeInfosData#keystrokes} keystrokes Keystroke definitions about to be added.
		*/
		throw new CKEditorError("accessibility-unknown-keystroke-info-group", this._editor, {
			groupId,
			categoryId,
			keystrokes
		});
		category.groups.get(groupId).keystrokes.push(...keystrokes);
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/utils/editorusagedata
*/
/**
* This part of the code is not executed in open-source implementations using a GPL key.
* It only runs when a specific license key is provided. If you are uncertain whether
* this applies to your installation, please contact our support team.
*
* @internal
*/
function getEditorUsageData(editor) {
	return {
		sessionId: getSessionId(),
		pageSessionId: getPageSessionID(),
		hostname: window.location.hostname,
		version: globalThis.CKEDITOR_VERSION,
		type: getEditorType(editor),
		plugins: getPluginsUsageData(editor.plugins),
		distribution: getDistributionUsageData(),
		env: getEnvUsageData(),
		integration: Object.create(null),
		menuBar: { isVisible: !!editor.config.get("menuBar.isVisible") },
		language: {
			ui: editor.locale.uiLanguage,
			content: editor.locale.contentLanguage
		},
		toolbar: {
			main: getToolbarUsageData(editor.config.get("toolbar")),
			block: getToolbarUsageData(editor.config.get("blockToolbar")),
			balloon: getToolbarUsageData(editor.config.get("balloonToolbar"))
		}
	};
}
function getEditorType(editor) {
	return Object.getPrototypeOf(editor).constructor.editorName;
}
function getPluginsUsageData(collection) {
	return Array.from(collection).filter(([PluginConstructor]) => !!PluginConstructor.pluginName).map(([PluginConstructor]) => {
		const { pluginName, isContextPlugin, isOfficialPlugin, isPremiumPlugin } = PluginConstructor;
		return {
			isContext: !!isContextPlugin,
			isOfficial: !!isOfficialPlugin,
			isPremium: !!isPremiumPlugin,
			name: pluginName
		};
	});
}
function getToolbarUsageData(toolbarConfig) {
	if (!toolbarConfig) return;
	const normalizedToolbar = Array.isArray(toolbarConfig) ? { items: toolbarConfig } : toolbarConfig;
	const flattenToolbarConfigNames = extractToolbarConfigItemsNames(normalizedToolbar.items || []);
	return {
		isMultiline: flattenToolbarConfigNames.includes("-"),
		shouldNotGroupWhenFull: !!normalizedToolbar.shouldNotGroupWhenFull,
		items: stripToolbarSeparatorItems(flattenToolbarConfigNames)
	};
	function stripToolbarSeparatorItems(items) {
		return items.filter((item) => item !== "|" && item !== "-");
	}
	function extractToolbarConfigItemsNames(items) {
		return items.flatMap((item) => {
			if (typeof item === "string") return [item];
			return extractToolbarConfigItemsNames(item.items);
		});
	}
}
function getDistributionUsageData() {
	return { channel: window[Symbol.for("cke distribution")] || "sh" };
}
function getEnvUsageData() {
	let os = "unknown";
	let browser = "unknown";
	if (env.isMac) os = "mac";
	else if (env.isWindows) os = "windows";
	else if (env.isiOS) os = "ios";
	else if (env.isAndroid) os = "android";
	if (env.isGecko) browser = "gecko";
	else if (env.isBlink) browser = "blink";
	else if (env.isSafari) browser = "safari";
	return {
		os,
		browser
	};
}
function getSessionId() {
	if (!localStorage.getItem("__ckeditor-session-id")) localStorage.setItem("__ckeditor-session-id", uid());
	return localStorage.getItem("__ckeditor-session-id");
}
function getPageSessionID() {
	global.window.CKEDITOR_PAGE_SESSION_ID = global.window.CKEDITOR_PAGE_SESSION_ID || uid();
	return global.window.CKEDITOR_PAGE_SESSION_ID;
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/editor
*/
const EditorBase = /* #__PURE__ */ ObservableMixin();
/**
* The class representing a basic, generic editor.
*
* Check out the list of its subclasses to learn about specific editor implementations.
*
* All editor implementations (like {@link module:editor-classic/classiceditor~ClassicEditor} or
* {@link module:editor-inline/inlineeditor~InlineEditor}) should extend this class. They can add their
* own methods and properties.
*
* When you are implementing a plugin, this editor represents the API
* which your plugin can expect to get when using its {@link module:core/plugin~Plugin#editor} property.
*
* This API should be sufficient in order to implement the "editing" part of your feature
* (schema definition, conversion, commands, keystrokes, etc.).
* It does not define the editor UI, which is available only if
* the specific editor implements also the {@link ~Editor#ui} property
* (as most editor implementations do).
*/
var Editor = class extends EditorBase {
	/**
	* A required name of the editor class. The name should reflect the constructor name.
	*/
	static get editorName() {
		return "Editor";
	}
	/**
	* A namespace for the accessibility features of the editor.
	*/
	accessibility;
	/**
	* Commands registered to the editor.
	*
	* Use the shorthand {@link #execute `editor.execute()`} method to execute commands:
	*
	* ```ts
	* // Execute the bold command:
	* editor.execute( 'bold' );
	*
	* // Check the state of the bold command:
	* editor.commands.get( 'bold' ).value;
	* ```
	*/
	commands;
	/**
	* Stores all configurations specific to this editor instance.
	*
	* ```ts
	* editor.config.get( 'image.toolbar' );
	* // -> [ 'imageStyle:block', 'imageStyle:side', '|', 'toggleImageCaption', 'imageTextAlternative' ]
	* ```
	*/
	config;
	/**
	* Conversion manager through which you can register model-to-view and view-to-model converters.
	*
	* See the {@link module:engine/conversion/conversion~Conversion} documentation to learn how to add converters.
	*/
	conversion;
	/**
	* The {@link module:engine/controller/datacontroller~DataController data controller}.
	* Used e.g. for setting and retrieving the editor data.
	*/
	data;
	/**
	* The {@link module:engine/controller/editingcontroller~EditingController editing controller}.
	* Controls user input and rendering the content for editing.
	*/
	editing;
	/**
	* The locale instance.
	*/
	locale;
	/**
	* The editor's model.
	*
	* The central point of the editor's abstract data model.
	*/
	model;
	/**
	* The plugins loaded and in use by this editor instance.
	*
	* ```ts
	* editor.plugins.get( 'ClipboardPipeline' ); // -> An instance of the clipboard pipeline plugin.
	* ```
	*/
	plugins;
	/**
	* An instance of the {@link module:core/editingkeystrokehandler~EditingKeystrokeHandler}.
	*
	* It allows setting simple keystrokes:
	*
	* ```ts
	* // Execute the bold command on Ctrl+E:
	* editor.keystrokes.set( 'Ctrl+E', 'bold' );
	*
	* // Execute your own callback:
	* editor.keystrokes.set( 'Ctrl+E', ( data, cancel ) => {
	* 	console.log( data.keyCode );
	*
	* 	// Prevent the default (native) action and stop the underlying keydown event
	* 	// so no other editor feature will interfere.
	* 	cancel();
	* } );
	* ```
	*
	* Note: Certain typing-oriented keystrokes (like <kbd>Backspace</kbd> or <kbd>Enter</kbd>) are handled
	* by a low-level mechanism and trying to listen to them via the keystroke handler will not work reliably.
	* To handle these specific keystrokes, see the events fired by the
	* {@link module:engine/view/document~ViewDocument editing view document} (`editor.editing.view.document`).
	*/
	keystrokes;
	/**
	* Shorthand for {@link module:utils/locale~Locale#t}.
	*
	* @see module:utils/locale~Locale#t
	*/
	t;
	/**
	* The default configuration which is built into the editor class.
	*
	* It was used in the now deprecated CKEditor 5 builds to provide the default configuration options
	* which are later used during the editor initialization.
	*
	* ```ts
	* ClassicEditor.defaultConfig = {
	* 	foo: 1,
	* 	bar: 2
	* };
	*
	* ClassicEditor
	* 	.create( sourceElement )
	* 	.then( editor => {
	* 		editor.config.get( 'foo' ); // -> 1
	* 		editor.config.get( 'bar' ); // -> 2
	* 	} );
	*
	* // The default options can be overridden by the configuration passed to create().
	* ClassicEditor
	* 	.create( { bar: 3 } )
	* 	.then( editor => {
	* 		editor.config.get( 'foo' ); // -> 1
	* 		editor.config.get( 'bar' ); // -> 3
	* 	} );
	* ```
	*
	* See also {@link module:core/editor/editor~Editor.builtinPlugins}.
	*/
	static defaultConfig;
	/**
	* An array of plugins built into this editor class.
	*
	* It is used in the now deprecated CKEditor 5 builds to provide a list of plugins which are later automatically initialized
	* during the editor initialization.
	*
	* They will be automatically initialized by the editor, unless listed in `config.removePlugins` and
	* unless `config.plugins` is passed.
	*
	* ```ts
	* // Build some plugins into the editor class first.
	* ClassicEditor.builtinPlugins = [ FooPlugin, BarPlugin ];
	*
	* // Normally, you need to define config.plugins, but since ClassicEditor.builtinPlugins was
	* // defined, now you can call create() without any configuration.
	* ClassicEditor
	* 	.create( sourceElement )
	* 	.then( editor => {
	* 		editor.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
	* 		editor.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
	* 	} );
	*
	* ClassicEditor
	* 	.create( {
	* 		// Do not initialize these plugins (note: it is defined by a string):
	* 		removePlugins: [ 'Foo' ]
	* 	} )
	* 	.then( editor => {
	* 		editor.plugins.get( FooPlugin ); // -> Undefined.
	* 		editor.config.get( BarPlugin ); // -> An instance of the Bar plugin.
	* 	} );
	*
	* ClassicEditor
	* 	.create( {
	* 		// Load only this plugin. It can also be defined by a string if
	* 		// this plugin was built into the editor class.
	* 		plugins: [ FooPlugin ]
	* 	} )
	* 	.then( editor => {
	* 		editor.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
	* 		editor.config.get( BarPlugin ); // -> Undefined.
	* 	} );
	* ```
	*
	* See also {@link module:core/editor/editor~Editor.defaultConfig}.
	*/
	static builtinPlugins;
	/**
	* The editor context.
	* When it is not provided through the configuration, the editor creates it.
	*/
	_context;
	/**
	* Objects that error attribution must not walk into when deciding whether something belongs to this
	* editor — see {@link module:core/context~Context#_errorExclusions}.
	*
	* Exposed here because `_context` is protected and attribution runs outside this class.
	*
	* It has to be a getter rather than a property. Editors sharing a context share one exclusion set, and
	* the set is not a member of itself, so holding it in a property would put the same object on every
	* editor — and the search would find it there and match them all. A getter lives on the prototype and
	* is not enumerable, so the search does not see it, and the only route left to the set runs through the
	* context, which is excluded.
	*
	* @internal
	*/
	get _errorExclusions() {
		return this._context._errorExclusions;
	}
	/**
	* A set of lock IDs for the {@link #isReadOnly} getter.
	*/
	_readOnlyLocks;
	/**
	* Holds attributes keys that were passed in
	* {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots.<rootName>.modelAttributes`}
	*  or {@link module:core/editor/editorconfig~EditorConfig#root `config.root.modelAttributes`}.
	*/
	_registeredRootsAttributesKeys = /* @__PURE__ */ new Set();
	/**
	* `Editor` class is commonly put in `config.plugins` array.
	*
	* This property helps with better error detection.
	*
	* @internal
	*/
	static get _throwErrorWhenUsedAsAPlugin() {
		return true;
	}
	/**
	* Creates a new instance of the editor class.
	*
	* Usually, not to be used directly. See the static {@link module:core/editor/editor~Editor.create `create()`} method.
	*
	* @param config The editor configuration.
	*/
	constructor(config = {}) {
		super();
		if (typeof config !== "object" || Array.isArray(config))
 /**
		* Editor configuration must be an object.
		*
		* A common cause of this error is passing an Editor class (for example
		* `ClassicEditor`) in the `config.plugins` array. In such case, the editor
		* constructor is called with an Editor or Context instance instead of
		* the configuration object.
		*
		* @error editor-config-invalid-type
		*/
		throw new CKEditorError("editor-config-invalid-type");
		if ("sanitizeHtml" in config)
 /**
		* Configuration property `config.sanitizeHtml` was removed in CKEditor version 43.1.0 and is no longer supported.
		*
		* Please use `config.htmlEmbed.sanitizeHtml` and/or `config.mergeFields.sanitizeHtml` instead.
		*
		* @error editor-config-sanitizehtml-not-supported
		*/
		throw new CKEditorError("editor-config-sanitizehtml-not-supported");
		const constructor = this.constructor;
		const { translations: defaultTranslations, ...defaultConfig } = constructor.defaultConfig || {};
		const { translations = defaultTranslations, ...rest } = config;
		const language = config.language || defaultConfig.language;
		this._context = config.context || new Context({
			language,
			translations
		});
		this._context._addEditor(this, !config.context);
		const availablePlugins = Array.from(constructor.builtinPlugins || []);
		this.config = new Config(rest, defaultConfig);
		this.config.define("plugins", availablePlugins);
		this.config.define(this._context._getEditorConfig());
		checkLicenseKeyIsDefined(this.config);
		this.plugins = new PluginCollection(this, availablePlugins, this._context.plugins);
		this.locale = this._context.locale;
		this.t = this.locale.t;
		this._readOnlyLocks = /* @__PURE__ */ new Set();
		this.commands = new CommandCollection();
		this.set("state", "initializing");
		this.once("ready", () => this.state = "ready", { priority: "high" });
		this.once("destroy", () => this.state = "destroyed", { priority: "high" });
		this.once("ready", () => _addLiveEditor(this), { priority: "high" });
		this.once("destroy", () => _removeLiveEditor(this), { priority: "high" });
		this.model = new Model(this.config);
		this.on("change:isReadOnly", () => {
			this.model.document.isReadOnly = this.isReadOnly;
		});
		const stylesProcessor = new StylesProcessor();
		this.data = new DataController(this.model, stylesProcessor);
		this.editing = new EditingController(this.model, stylesProcessor);
		this.editing.view.document.bind("isReadOnly").to(this);
		this.conversion = new Conversion([this.editing.downcastDispatcher, this.data.downcastDispatcher], this.data.upcastDispatcher);
		this.conversion.addAlias("dataDowncast", this.data.downcastDispatcher);
		this.conversion.addAlias("editingDowncast", this.editing.downcastDispatcher);
		this.keystrokes = new EditingKeystrokeHandler(this);
		this.keystrokes.listenTo(this.editing.view.document);
		this.accessibility = new Accessibility(this);
		verifyLicenseKey(this);
		function checkLicenseKeyIsDefined(config) {
			let licenseKey = config.get("licenseKey");
			if (!licenseKey && window.CKEDITOR_GLOBAL_LICENSE_KEY) {
				licenseKey = window.CKEDITOR_GLOBAL_LICENSE_KEY;
				config.set("licenseKey", licenseKey);
			}
			if (!licenseKey)
 /**
			* The `licenseKey` property is missing in the editor configuration.
			*
			* * If you are using the editor in a commercial setup, please provide your license key.
			* * If you still need to acquire a key, please [contact us](https://ckeditor.com/contact/) or
			*   [create a free account with a 14 day premium features trial](https://portal.ckeditor.com/checkout?plan=free).
			* * If you are using the editor under a GPL license or another license from our Open Source Initiative,
			*   use the 'GPL' license key instead.
			*
			* ```js
			* ClassicEditor.create( {
			* 	attachTo: document.querySelector( '#editor' ),
			* 	licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'.
			* 	// ... Other configuration options ...
			* } ) ;
			*
			* @error license-key-missing
			*/
			throw new CKEditorError("license-key-missing");
		}
		function verifyLicenseKey(editor) {
			const licenseKey = editor.config.get("licenseKey");
			const distributionChannel = window[Symbol.for("cke distribution")] || "sh";
			function blockEditor(reason, name) {
				editor.enableReadOnlyMode(Symbol("invalidLicense"));
				editor._showLicenseError(reason, name);
			}
			function hasAllRequiredFields(licensePayload) {
				return [
					"exp",
					"jti",
					"vc"
				].every((field) => field in licensePayload);
			}
			function getCrcInputData(licensePayload) {
				return Object.getOwnPropertyNames(licensePayload).sort().filter((key) => key != "vc" && licensePayload[key] != null).map((key) => licensePayload[key]);
			}
			function checkLicensedHosts(licensedHosts) {
				const { hostname } = new URL(window.location.href);
				if (licensedHosts.includes(hostname)) return true;
				const segments = hostname.split(".");
				return licensedHosts.filter((host) => host.includes("*")).map((host) => host.split(".")).filter((host) => host.length <= segments.length).map((host) => Array(segments.length - host.length).fill(host[0] === "*" ? "*" : "").concat(host)).some((octets) => segments.every((segment, index) => octets[index] === segment || octets[index] === "*"));
			}
			function warnAboutNonProductionLicenseKey(licenseType) {
				const capitalizedLicenseType = licenseType[0].toUpperCase() + licenseType.slice(1);
				const article = licenseType === "evaluation" ? "an" : "a";
				console.info(`%cCKEditor 5 ${capitalizedLicenseType} License`, "color: #ffffff; background: #743CCD; font-size: 14px; padding: 4px 8px; border-radius: 4px;");
				console.warn(`⚠️ You are using ${article} ${licenseType} license of CKEditor 5${licenseType === "trial" ? " which is for evaluation purposes only" : ""}. For production usage, please obtain a production license at https://portal.ckeditor.com/`);
			}
			if (licenseKey == "GPL") {
				if (distributionChannel == "cloud") blockEditor("distributionChannel");
				return;
			}
			const licensePayload = decodeLicenseKey(licenseKey);
			if (!licensePayload) {
				blockEditor("invalid");
				return;
			}
			if (!hasAllRequiredFields(licensePayload)) {
				blockEditor("invalid");
				return;
			}
			if (licensePayload.distributionChannel && !toArray(licensePayload.distributionChannel).includes(distributionChannel)) {
				blockEditor("distributionChannel");
				return;
			}
			if (crc32(getCrcInputData(licensePayload)) != licensePayload.vc.toLowerCase()) {
				blockEditor("invalid");
				return;
			}
			if (/* @__PURE__ */ new Date(licensePayload.exp * 1e3) < releaseDate) {
				blockEditor("expired");
				return;
			}
			const licensedHosts = licensePayload.licensedHosts;
			if (licensedHosts && licensedHosts.length > 0 && !checkLicensedHosts(licensedHosts)) {
				blockEditor("domainLimit");
				return;
			}
			if (["evaluation", "trial"].includes(licensePayload.licenseType) && licensePayload.exp * 1e3 < Date.now()) {
				blockEditor("expired");
				return;
			}
			if ([
				"development",
				"evaluation",
				"trial"
			].includes(licensePayload.licenseType)) {
				const { licenseType } = licensePayload;
				window.CKEDITOR_WARNING_SUPPRESSIONS = window.CKEDITOR_WARNING_SUPPRESSIONS || {};
				if (!window.CKEDITOR_WARNING_SUPPRESSIONS[licenseType]) {
					warnAboutNonProductionLicenseKey(licenseType);
					window.CKEDITOR_WARNING_SUPPRESSIONS[licenseType] = true;
				}
			}
			if (licensePayload.licenseType === "evaluation") {
				const timerId = setTimeout(() => {
					blockEditor("evaluationLimit");
				}, 6e5);
				editor.on("destroy", () => {
					clearTimeout(timerId);
				});
			}
			if (licensePayload.usageEndpoint) editor.once("ready", () => {
				const request = {
					requestId: uid(),
					requestTime: Math.round(Date.now() / 1e3),
					license: licenseKey,
					editor: collectUsageData(editor)
				};
				/**
				* This part of the code is not executed in open-source implementations using a GPL key.
				* It only runs when a specific license key is provided. If you are uncertain whether
				* this applies to your installation, please contact our support team.
				*/
				editor._sendUsageRequest(licensePayload.usageEndpoint, request).then((response) => {
					const { status, message } = response;
					if (message) console.warn(message);
					if (status != "ok") blockEditor("usageLimit");
				}, () => {
					/**
					* Your license key cannot be validated due to a network issue.
					* Please ensure that your setup does not block requests to the validation endpoint.
					*
					* @error license-key-validation-endpoint-not-reachable
					* @param {string} url The URL that was attempted to be reached for validation.
					*/
					logError("license-key-validation-endpoint-not-reachable", { url: licensePayload.usageEndpoint });
				});
			}, { priority: "high" });
		}
	}
	/**
	* Defines whether the editor is in the read-only mode.
	*
	* In read-only mode the editor {@link #commands commands} are disabled so it is not possible
	* to modify the document by using them. Also, the editable element(s) become non-editable.
	*
	* In order to make the editor read-only, you need to call the {@link #enableReadOnlyMode} method:
	*
	* ```ts
	* editor.enableReadOnlyMode( 'feature-id' );
	* ```
	*
	* Later, to turn off the read-only mode, call {@link #disableReadOnlyMode}:
	*
	* ```ts
	* editor.disableReadOnlyMode( 'feature-id' );
	* ```
	*
	* @readonly
	* @observable
	*/
	get isReadOnly() {
		return this._readOnlyLocks.size > 0;
	}
	set isReadOnly(value) {
		/**
		* The {@link module:core/editor/editor~Editor#isReadOnly Editor#isReadOnly} property is read-only since version `34.0.0`
		* and can be set only using {@link module:core/editor/editor~Editor#enableReadOnlyMode `Editor#enableReadOnlyMode( lockId )`} and
		* {@link module:core/editor/editor~Editor#disableReadOnlyMode `Editor#disableReadOnlyMode( lockId )`}.
		*
		* Usage before version `34.0.0`:
		*
		* ```ts
		* editor.isReadOnly = true;
		* editor.isReadOnly = false;
		* ```
		*
		* Usage since version `34.0.0`:
		*
		* ```ts
		* editor.enableReadOnlyMode( 'my-feature-id' );
		* editor.disableReadOnlyMode( 'my-feature-id' );
		* ```
		*
		* @error editor-isreadonly-has-no-setter
		*/
		throw new CKEditorError("editor-isreadonly-has-no-setter");
	}
	/**
	* Turns on the read-only mode in the editor.
	*
	* Editor can be switched to or out of the read-only mode by many features, under various circumstances. The editor supports locking
	* mechanism for the read-only mode. It enables easy control over the read-only mode when many features wants to turn it on or off at
	* the same time, without conflicting with each other. It guarantees that you will not make the editor editable accidentally (which
	* could lead to errors).
	*
	* Each read-only mode request is identified by a unique id (also called "lock"). If multiple plugins requested to turn on the
	* read-only mode, then, the editor will become editable only after all these plugins turn the read-only mode off (using the same ids).
	*
	* Note, that you cannot force the editor to disable the read-only mode if other plugins set it.
	*
	* After the first `enableReadOnlyMode()` call, the {@link #isReadOnly `isReadOnly` property} will be set to `true`:
	*
	* ```ts
	* editor.isReadOnly; // `false`.
	* editor.enableReadOnlyMode( 'my-feature-id' );
	* editor.isReadOnly; // `true`.
	* ```
	*
	* You can turn off the read-only mode ("clear the lock") using the {@link #disableReadOnlyMode `disableReadOnlyMode()`} method:
	*
	* ```ts
	* editor.enableReadOnlyMode( 'my-feature-id' );
	* // ...
	* editor.disableReadOnlyMode( 'my-feature-id' );
	* editor.isReadOnly; // `false`.
	* ```
	*
	* All "locks" need to be removed to enable editing:
	*
	* ```ts
	* editor.enableReadOnlyMode( 'my-feature-id' );
	* editor.enableReadOnlyMode( 'my-other-feature-id' );
	* // ...
	* editor.disableReadOnlyMode( 'my-feature-id' );
	* editor.isReadOnly; // `true`.
	* editor.disableReadOnlyMode( 'my-other-feature-id' );
	* editor.isReadOnly; // `false`.
	* ```
	*
	* @param lockId A unique ID for setting the editor to the read-only state.
	*/
	enableReadOnlyMode(lockId) {
		if (typeof lockId !== "string" && typeof lockId !== "symbol")
 /**
		* The lock ID is missing or it is not a string or symbol.
		*
		* @error editor-read-only-lock-id-invalid
		* @param {never} lockId Lock ID.
		*/
		throw new CKEditorError("editor-read-only-lock-id-invalid", null, { lockId });
		if (this._readOnlyLocks.has(lockId)) return;
		this._readOnlyLocks.add(lockId);
		if (this._readOnlyLocks.size === 1) this.fire("change:isReadOnly", "isReadOnly", true, false);
	}
	/**
	* Removes the read-only lock from the editor with given lock ID.
	*
	* When no lock is present on the editor anymore, then the {@link #isReadOnly `isReadOnly` property} will be set to `false`.
	*
	* @param lockId The lock ID for setting the editor to the read-only state.
	*/
	disableReadOnlyMode(lockId) {
		if (typeof lockId !== "string" && typeof lockId !== "symbol") throw new CKEditorError("editor-read-only-lock-id-invalid", null, { lockId });
		if (!this._readOnlyLocks.has(lockId)) return;
		this._readOnlyLocks.delete(lockId);
		if (this._readOnlyLocks.size === 0) this.fire("change:isReadOnly", "isReadOnly", false, true);
	}
	/**
	* Sets the data in the editor.
	*
	* ```ts
	* editor.setData( '<p>This is editor!</p>' );
	* ```
	*
	* If your editor implementation uses multiple roots, you should pass an object with keys corresponding
	* to the editor root names and values equal to the data that should be set in each root:
	*
	* ```ts
	* editor.setData( {
	*     header: '<p>Content for header part.</p>',
	*     content: '<p>Content for main part.</p>',
	*     footer: '<p>Content for footer part.</p>'
	* } );
	* ```
	*
	* By default the editor accepts HTML. This can be controlled by injecting a different data processor.
	* See the {@glink features/markdown Markdown output} guide for more details.
	*
	* @param data Input data.
	*/
	setData(data) {
		this.data.set(data);
	}
	/**
	* Gets the data from the editor.
	*
	* ```ts
	* editor.getData(); // -> '<p>This is editor!</p>'
	* ```
	*
	* If your editor implementation uses multiple roots, you should pass root name as one of the options:
	*
	* ```ts
	* editor.getData( { rootName: 'header' } ); // -> '<p>Content for header part.</p>'
	* ```
	*
	* By default, the editor outputs HTML. This can be controlled by injecting a different data processor.
	* See the {@glink features/markdown Markdown output} guide for more details.
	*
	* A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should
	* be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string.
	*
	* @param options Additional configuration for the retrieved data.
	* Editor features may introduce more configuration options that can be set through this parameter.
	* @param options.rootName Root name. Defaults to `'main'`.
	* @param options.trim Whether returned data should be trimmed. This option is set to `'empty'` by default,
	* which means that whenever editor content is considered empty, an empty string is returned. To turn off trimming
	* use `'none'`. In such cases exact content will be returned (for example `'<p>&nbsp;</p>'` for an empty editor).
	* @returns Output data.
	*/
	getData(options) {
		return this.data.get(options);
	}
	/**
	* Loads and initializes plugins specified in the configuration.
	*
	* @returns A promise which resolves once the initialization is completed, providing an array of loaded plugins.
	*/
	async initPlugins() {
		const config = this.config;
		const plugins = config.get("plugins");
		const removePlugins = config.get("removePlugins") || [];
		const extraPlugins = config.get("extraPlugins") || [];
		const substitutePlugins = config.get("substitutePlugins") || [];
		const loadedPlugins = await this.plugins.init(plugins.concat(extraPlugins), removePlugins, substitutePlugins);
		checkPluginsAllowedByLicenseKey(this);
		return loadedPlugins;
		function checkPluginsAllowedByLicenseKey(editor) {
			const licenseKey = editor.config.get("licenseKey");
			if (licenseKey === "GPL") return;
			const decodedPayload = decodeLicenseKey(licenseKey);
			if (!decodedPayload) return;
			const disallowedPlugin = [...editor.plugins].map(([pluginConstructor]) => pluginConstructor).find((pluginConstructor) => {
				if (!pluginConstructor.pluginName) return false;
				if (!pluginConstructor.licenseFeatureCode) return false;
				return isFeatureBlockedByLicenseKey(decodedPayload, pluginConstructor.licenseFeatureCode);
			});
			if (disallowedPlugin) {
				editor.enableReadOnlyMode(Symbol("invalidLicense"));
				editor._showLicenseError("pluginNotAllowed", disallowedPlugin.pluginName);
			}
		}
	}
	/**
	* Destroys the editor instance, releasing all resources used by it.
	*
	* **Note** The editor cannot be destroyed during the initialization phase so if it is called
	* while the editor {@link #state is being initialized}, it will wait for the editor initialization before destroying it.
	*
	* @fires destroy
	* @returns A promise that resolves once the editor instance is fully destroyed.
	*/
	async destroy() {
		if (this.state == "initializing") await new Promise((resolve) => this.once("ready", resolve));
		this.fire("destroy");
		this.stopListening();
		this.commands.destroy();
		await this.plugins.destroy();
		this.model.destroy();
		this.data.destroy();
		this.editing.destroy();
		this.keystrokes.destroy();
		await this._context._removeEditor(this);
	}
	/**
	* Executes the specified command with given parameters.
	*
	* Shorthand for:
	*
	* ```ts
	* editor.commands.get( commandName ).execute( ... );
	* ```
	*
	* @param commandName The name of the command to execute.
	* @param commandParams Command parameters.
	* @returns The value returned by the {@link module:core/commandcollection~CommandCollection#execute `commands.execute()`}.
	*/
	execute(commandName, ...commandParams) {
		try {
			return this.commands.execute(commandName, ...commandParams);
		} catch (err) {
			/* v8 ignore next -- @preserve */
			CKEditorError.rethrowUnexpectedError(err, this);
		}
	}
	/**
	* Focuses the editor.
	*
	* **Note** To explicitly focus the editing area of the editor, use the
	* {@link module:engine/view/view~EditingView#focus `editor.editing.view.focus()`} method of the editing view.
	*
	* Check out the {@glink framework/deep-dive/ui/focus-tracking#focus-in-the-editor-ui Focus in the editor UI} section
	* of the {@glink framework/deep-dive/ui/focus-tracking Deep dive into focus tracking} guide to learn more.
	*/
	focus() {
		this.editing.view.focus();
	}
	/**
	* Registers a given string as a root attribute key. Registered root attributes are added to
	* the {@link module:engine/model/schema~ModelSchema schema}.
	*
	* **Note:** Attributes passed in the configuration for multi-root editors
	* ({@link module:core/editor/editorconfig~EditorConfig#roots `config.roots.<rootName>.modelAttributes`}) or
	* single-root editors ({@link module:core/editor/editorconfig~EditorConfig#root `config.root.modelAttributes`})
	* are automatically registered when the editor is initialized. However, registering the same attribute twice
	* does not have any negative impact, so it is recommended to use this method in any feature that uses
	* root attributes.
	*
	* **Note:** Registered attributes are attached only to the generic `$root` schema element. A custom root
	* {@link module:core/editor/editorconfig~RootConfig#modelElement `modelElement`} must opt into the `$root`
	* attribute chain via `allowAttributesOf: '$root'` to inherit these attributes.
	* See the {@glink framework/deep-dive/schema#custom-root-elements Custom root elements} section of the
	* {@glink framework/deep-dive/schema Schema deep-dive} guide for more details.
	*/
	registerRootAttribute(key) {
		if (this._registeredRootsAttributesKeys.has(key)) return;
		this._registeredRootsAttributesKeys.add(key);
		this.editing.model.schema.extend("$root", { allowAttributes: key });
	}
	/**
	* Returns attributes for the specified root.
	* If no root name is provided, it returns attributes for the 'main' root by default.
	*
	* Note: all and only {@link ~Editor#registerRootAttribute registered} roots attributes will be returned.
	* If a registered root attribute is not set for a given root, `null` will be returned.
	*/
	getRootAttributes(rootName = "main") {
		const root = this.model.document.getRoot(rootName);
		if (!root)
 /**
		* The requested root does not exist. Please ensure that the provided root name
		* is correct and that the root has been properly initialized in the document.
		*
		* @error get-root-attributes-missing-root
		*/
		throw new CKEditorError("get-root-attributes-missing-root", this, { rootName });
		const rootAttributes = {};
		for (const key of this._registeredRootsAttributesKeys) rootAttributes[key] = root.hasAttribute(key) ? root.getAttribute(key) : null;
		return rootAttributes;
	}
	/**
	* Creates and initializes a new editor instance.
	*
	* This is an abstract method. Every editor type needs to implement its own initialization logic.
	*
	* See the `create()` methods of the existing editor types to learn how to use them:
	*
	* * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}
	* * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}
	* * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`}
	* * {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`}
	*/
	static create(...args) {
		throw new Error("This is an abstract method.");
	}
	/**
	* The {@link module:core/context~Context} class.
	*
	* Exposed as a static editor member for easier access in editor builds.
	*
	* A getter rather than a field, for the same reason as {@link ~Editor.onEditorError}: a field would
	* read `Context` while this class is being defined, and the error reporter puts this module and
	* `context.ts` in an import cycle, so at that moment `Context` is not always initialized yet.
	*/
	static get Context() {
		return Context;
	}
	/**
	* {@link module:core/errorreporter~onEditorError `onEditorError()`}, reachable without importing it.
	*
	* Code that is handed an editor class rather than importing one — a framework integration, above all —
	* cannot import from CKEditor at all. Importing anything as a value loads the npm build, and an
	* application that meant to load CKEditor from a CDN is then refused. Reading the function off the class
	* it was given avoids that, and it also lands on the copy of this package the editor itself came from.
	*
	* ```ts
	* const off = ClassicEditor.onEditorError( ( { error, source } ) => {
	* 	if ( source !== myEditor ) {
	* 		return;
	* 	}
	*
	* 	reportToMyErrorTracker( error );
	* } );
	* ```
	*
	* The same page-level registration as the exported function — not a registration scoped to this editor
	* class. Prefer the import wherever imports are an option.
	*
	* A getter rather than a field: this module and the error reporter already refer to each other, so
	* reading its export while defining this class would read it before it exists. A getter body runs on
	* access, long after both modules are done. It also keeps the property off the list of the class's own
	* enumerable properties, which a field would have joined.
	*/
	static get onEditorError() {
		return onEditorError;
	}
	_showLicenseError(reason, name) {
		setTimeout(() => {
			if (reason == "invalid")
 /**
			* The license key provided is invalid. Please ensure that it is copied correctly
			* from the [Customer Portal](http://portal.ckeditor.com). If the issue persists,
			* please [contact our customer support](https://ckeditor.com/contact/).
			*
			* @error invalid-license-key
			*/
			throw new CKEditorError("invalid-license-key");
			if (reason == "expired")
 /**
			* Your license key has expired.
			*
			* If you used our free trial, you either need to switch to
			* [open-source license](https://ckeditor.com/docs/ckeditor5/latest/getting-started/licensing/license-and-legal.html), or
			* in case of a commercial plan, change the trial key to production key or development key.
			* Switching from trial, you also need to align the editor configuration to the features available in your plan.
			*
			* If you already had one of our Cloud or Custom plans, please renew your license in the
			* [Customer Portal](https://portal.ckeditor.com).
			*
			* @error license-key-expired
			*/
			throw new CKEditorError("license-key-expired");
			if (reason == "domainLimit")
 /**
			* The provided license does not allow the editor to run on this domain.
			* Some license keys are restricted to local test environments only.
			* For more details, please refer to the
			* {@glink getting-started/licensing/license-key-and-activation#license-key-types license key type documentation}.
			*
			* @error license-key-domain-limit
			*/
			throw new CKEditorError("license-key-domain-limit");
			if (reason == "pluginNotAllowed") {
				const gluePluginName = name.replace(/(Editing|UI)$/, "");
				const containsGluePlugin = this.plugins.has(gluePluginName);
				/**
				* The plugin you are trying to use is not permitted under your current license.
				* Please check the available features on the
				* [Customer Portal](https://portal.ckeditor.com) or
				* [contact support](https://ckeditor.com/contact/) for more information.
				*
				* @error license-key-plugin-not-allowed
				* @param {String} pluginName The plugin you tried to load.
				*/
				throw new CKEditorError("license-key-plugin-not-allowed", null, { pluginName: containsGluePlugin ? gluePluginName : name });
			}
			if (reason == "featureNotAllowed")
 /**
			* The feature you are trying to use is not permitted under your current license.
			* Please check the available features on the
			* [Customer Portal](https://portal.ckeditor.com) or
			* [contact support](https://ckeditor.com/contact/) for more information.
			*
			* @error license-key-feature-not-allowed
			* @param {String} featureName The feature you tried to use.
			*/
			throw new CKEditorError("license-key-feature-not-allowed", null, { featureName: name });
			if (reason == "evaluationLimit")
 /**
			* You have exceeded the editor operation limit available for your evaluation license key.
			* Please restart the editor to continue using it.
			* {@glink getting-started/licensing/license-key-and-activation#license-key-types Read more about license key types}.
			*
			* @error license-key-evaluation-limit
			*/
			throw new CKEditorError("license-key-evaluation-limit");
			if (reason == "trialLimit")
 /**
			* You have exceeded the editor operation limit for your trial license key.
			* Please restart the editor to continue using it.
			* {@glink getting-started/licensing/license-key-and-activation#license-key-types Read more about license key types}.
			*
			* @error license-key-trial-limit
			*/
			throw new CKEditorError("license-key-trial-limit");
			if (reason == "developmentLimit")
 /**
			* You have exceeded the operation limit for your development license key within the editor.
			* Please restart the editor to continue using it.
			* {@glink getting-started/licensing/license-key-and-activation#license-key-types Read more about license key types}.
			*
			* @error license-key-development-limit
			*/
			throw new CKEditorError("license-key-development-limit");
			if (reason == "usageLimit")
 /**
			* You have reached the usage limit of your license key. This can occur in the following situations:
			*
			* * You are on a free subscription without a connected payment method and have exceeded the allowed usage threshold.
			* * Your account has overdue invoices and the grace period has ended.
			*
			* To extend the limit and restore access, please update the required details in the
			* [Customer Portal](https://portal.ckeditor.com) or
			* [contact our customer support](https://ckeditor.com/contact).
			*
			* @error license-key-usage-limit
			*/
			throw new CKEditorError("license-key-usage-limit");
			/* v8 ignore else -- @preserve */
			if (reason == "distributionChannel")
 /**
			* Your license does not allow the current distribution channel.
			*
			* These are the available distribution channels:
			* * Self-hosted - the editor is installed via npm or from a ZIP package
			* * Cloud - the editor is run from CDN
			*
			* The licenses available include:
			* * GPL license for open-source users.
			* * Commercial plans (Cloud or sales-assisted).
			*
			* The relation between distribution channels and licenses works as follows:
			* * With the 'GPL' license key, you may use the editor installed via npm or a ZIP package (self-hosted).
			* * With the CKEditor Cloud plans, you may use the editor via our CDN.
			* * With the CKEditor Custom plans, depending on your plan details, you can use the editor via npm
			*   or a ZIP package (self-hosted) or Cloud (CDN).
			*
			* {@glink getting-started/licensing/usage-based-billing#key-terms Read more about distributions in the documentation}.
			* Please verify your installation or [contact support](https://ckeditor.com/contact/) for assistance.
			* Should you need to migrate your installation from npm to CDN, please refer to our
			* [dedicated migration guides](https://ckeditor.com/docs/ckeditor5/latest/updating/migrations/vanilla-js.html).
			*
			* @error license-key-invalid-distribution-channel
			*/
			throw new CKEditorError("license-key-invalid-distribution-channel");
		}, 0);
		this._showLicenseError = () => {};
	}
	/**
	* This part of the code is _not_ executed in installations under the GPL license (with `config.licenseKey = 'GPL'`).
	*
	* It is only executed when a specific license key is provided. If you are uncertain whether
	* this applies to your installation, please contact our support team.
	*/
	async _sendUsageRequest(endpoint, request) {
		const headers = new Headers({ "Content-Type": "application/json" });
		const response = await fetch(new URL(endpoint), {
			method: "POST",
			headers,
			body: JSON.stringify(request)
		});
		if (!response.ok) throw new Error(`HTTP Response: ${response.status}`);
		return response.json();
	}
};
function collectUsageData(editor) {
	const collectedData = getEditorUsageData(editor);
	function setUsageData(path, value) {
		if (get(collectedData, path) !== void 0)
 /**
		* The error thrown when trying to set the usage data path that was already set.
		* Make sure that you are not setting the same path multiple times.
		*
		* @error editor-usage-data-path-already-set
		* @param {string} path The path that was already set.
		*/
		throw new CKEditorError("editor-usage-data-path-already-set", { path });
		set(collectedData, path, value);
	}
	editor.fire("collectUsageData", { setUsageData });
	return collectedData;
}
/**
* This error is thrown when trying to pass a `<textarea>` element to a `create()` function of an editor class.
*
* The only editor type which can be initialized on `<textarea>` elements is
* the {@glink getting-started/setup/editor-types#classic-editor classic editor}.
* This editor hides the passed element and inserts its own UI next to it. Other types of editors reuse the passed element as their root
* editable element and therefore `<textarea>` is not appropriate for them. Use a `<div>` or another text container instead:
*
* ```html
* <div id="editor">
* 	<p>Initial content.</p>
* </div>
* ```
*
* @error editor-wrong-element
*/
/**
* You are running [CKEditor 5 Long-term Support Edition (LTS)](https://ckeditor.com/ckeditor-5-lts/),
* but the provided license key does not include access to the LTS version, or the key is invalid.
*
* Please ensure that it is copied correctly from the [Customer Portal](http://portal.ckeditor.com/),
* and if the issue persists, please [contact our customer support](https://ckeditor.com/contact/).
*
* In case you intended to use the [LTS Edition](https://ckeditor.com/ckeditor-5-lts/),
* but have not yet made a purchase, please [contact our sales team](https://ckeditor.com/contact-sales/)
*
* If you did not intend to use LTS, please note that all releases in the v47.7 line are
* for LTS subscribers only. To use a non-LTS edition, either upgrade to v48 or newer,
* which is our standard [latest](https://ckeditor.com/docs/ckeditor5/latest/updating/guides/changelog.html)
* release line, or stay on a version 47.6.2 or earlier.
*
* @error license-key-lts-not-allowed
*/

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/resolveerrorsource
*/
/**
* Answers what an error came from, given the context it was thrown with.
*
* Returns every editor the error can be attributed to, or the contexts when no editor claims it. The list is
* empty when no answer can be given. That is not necessarily a bug: it also happens for a context that is
* `null`, omitted, or an object that shares nothing with any editor currently running.
*
* @internal
*/
function resolveErrorSources(context) {
	if (context instanceof Editor) return [context];
	if (context instanceof Context && _isLiveContext(context)) return [context];
	if (!context || typeof context != "object") return [];
	const editors = Array.from(_getLiveEditors()).filter((editor) => areConnectedThroughProperties(editor, context, editor._errorExclusions));
	if (editors.length) return editors;
	return Array.from(_getLiveContexts()).filter((liveContext) => areConnectedThroughProperties(liveContext, context) && !belongsToAnyEditorIn(liveContext, context));
}
/**
* Whether an editor in the context owns the error, whether or not that editor is ready.
*
* Everything in a context is reachable from it through {@link module:core/context~Context#editors}, so a
* context matches an error from any editor inside it. The loop above only asks ready editors, which leaves
* a window on either side of an editor's life — still starting up, or already tearing down — where the
* editor cannot answer for itself and the context would answer instead. A context has no ready state to
* filter on, so such an error would be reported when it should not be.
*
* Asking the editors first closes that window: an editor claims its own errors regardless of readiness,
* which keeps the context from claiming them, and the readiness check then drops them.
*/
function belongsToAnyEditorIn(liveContext, context) {
	return Array.from(liveContext.editors).some((editor) => areConnectedThroughProperties(editor, context, editor._errorExclusions));
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/errorreporter
*/
/**
* Watches `window` for errors that escaped an editor and hands them to registered callbacks.
*
* There is one instance per page, kept below as module state. It is a class rather than a set of closures
* so that tests can build a fresh one instead of fighting leftover module state.
*/
var EditorErrorReporter = class {
	/**
	* Holders rather than the callbacks themselves, so that registering the same function twice produces
	* two independent registrations and one unsubscribe call removes only its own.
	*/
	_registrations = /* @__PURE__ */ new Set();
	/**
	* Errors already handed out. An error can reach both listeners, and a rejected promise carrying an
	* error that also fired `error` would otherwise be reported twice.
	*/
	_reported = /* @__PURE__ */ new WeakSet();
	/**
	* Declared here rather than created in `_attach()`, so that the reference handed to
	* `removeEventListener()` is provably the one `addEventListener()` was given.
	*/
	_onError = (event) => {
		this._handle(event.error);
	};
	_onRejection = (event) => {
		this._handle(event.reason);
	};
	_isAttached = false;
	add(callback) {
		const registration = { callback };
		this._registrations.add(registration);
		this._attach();
		return () => {
			if (this._registrations.delete(registration)) this._detach();
		};
	}
	/**
	* Listeners go up with the first registration rather than on import, so a bundle that only imports this
	* module never touches `window`. Registering does, in line with the rest of the editor — this is browser
	* code.
	*/
	_attach() {
		if (this._isAttached || this._registrations.size === 0) return;
		this._isAttached = true;
		global.window.addEventListener("error", this._onError);
		global.window.addEventListener("unhandledrejection", this._onRejection);
	}
	_detach() {
		if (!this._isAttached || this._registrations.size > 0) return;
		this._isAttached = false;
		global.window.removeEventListener("error", this._onError);
		global.window.removeEventListener("unhandledrejection", this._onRejection);
	}
	_handle(error) {
		if (!isCKEditorError(error) || this._reported.has(error)) return;
		const sources = resolveErrorSources(error.context).filter((source) => !(source instanceof Editor) || source.state === "ready");
		if (!sources.length) return;
		this._reported.add(error);
		for (const source of sources) for (const { callback } of Array.from(this._registrations)) try {
			callback({
				error,
				source
			});
		} catch (callbackError) {
			console.error("An error happened in an editor error callback.", callbackError);
		}
	}
};
function isCKEditorError(error) {
	if (!(error instanceof Error)) return false;
	const candidate = error;
	return typeof candidate.is == "function" && candidate.is("CKEditorError");
}
const reporter = /* #__PURE__ -- @preserve */ new EditorErrorReporter();
/**
* Registers a callback for errors that escape an editor, and returns a function that unregisters it.
*
* ```ts
* const off = onEditorError( ( { error, source } ) => {
* 	if ( source !== myEditor ) {
* 		return;
* 	}
*
* 	reportToMyErrorTracker( error );
* } );
* ```
*
* There is one registration surface for the whole page rather than one per editor, so an integrator running
* several editors compares `source` with their own instance, as above. Registering the same callback twice
* gives two independent registrations, and each unregisters on its own.
*
* One error can reach the callback more than once, once for every editor it is attributed to — see
* {@link module:core/errorreporter~EditorErrorData#source}.
*
* Reporting only. Nothing is restarted and nothing is swallowed — the error still reaches the console.
*/
function onEditorError(callback) {
	return reporter.add(callback);
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/context
*/
/**
* Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}
* or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}
* in advanced application integrations.
*
* All configuration options passed to a context will be used as default options for the editor instances initialized in that context.
*
* {@link module:core/contextplugin~ContextPlugin Context plugins} passed to a context instance will be shared among all
* editor instances initialized in this context. These will be the same plugin instances for all the editors.
*
* **Note:** The context can only be initialized with {@link module:core/contextplugin~ContextPlugin context plugins}
* (e.g. [comments](https://ckeditor.com/collaboration/comments/)). Regular {@link module:core/plugin~Plugin plugins} require an
* editor instance to work and cannot be added to a context.
*
* **Note:** You can add a context plugin to an editor instance, though.
*
* If you are using multiple editor instances on one page and use any context plugins, create a context to share the configuration and
* plugins among these editors. Some plugins will use the information about all existing editors to better integrate between them.
*
* If you are using plugins that do not require an editor to work (e.g. [comments](https://ckeditor.com/collaboration/comments/)),
* enable and configure them using the context.
*
* If you are using only a single editor on each page, use {@link module:core/editor/editor~Editor.create `Editor.create()`} instead.
* In such a case, a context instance will be created by the editor instance in a transparent way.
*
* See {@link ~Context.create `Context.create()`} for usage examples.
*/
var Context = class {
	/**
	* Stores all the configurations specific to this context instance.
	*/
	config;
	/**
	* The plugins loaded and in use by this context instance.
	*/
	plugins;
	locale;
	/**
	* Shorthand for {@link module:utils/locale~Locale#t}.
	*/
	t;
	/**
	* A list of editors that this context instance is injected to.
	*/
	editors;
	/**
	* Everything reachable from this context, with the editors in it left out.
	*
	* Error attribution decides whether an object belongs to an editor by looking for anything the two have
	* in common. Every editor in a context can be reached from every other one through
	* {@link #editors}, so without excluding what the context itself owns, any error would look like it
	* belongs to any editor.
	*
	* Filled in by {@link ~Context#initPlugins}, so it covers a context created through
	* {@link ~Context.create} as well as one built by hand, as long as its plugins were initialized. A
	* context an editor made for itself never gets there, because the editor initializes its own plugins
	* instead: it holds that editor alone, so there is nothing to tell apart.
	*
	* Temporary, for as long as attribution walks object graphs.
	*
	* @internal
	*/
	_errorExclusions;
	/**
	* The default configuration which is built into the `Context` class.
	*
	* It was used in the now deprecated CKEditor 5 builds featuring `Context` to provide the default configuration options
	* which are later used during the context initialization.
	*
	* ```ts
	* Context.defaultConfig = {
	* 	foo: 1,
	* 	bar: 2
	* };
	*
	* Context
	* 	.create()
	* 	.then( context => {
	* 		context.config.get( 'foo' ); // -> 1
	* 		context.config.get( 'bar' ); // -> 2
	* 	} );
	*
	* // The default options can be overridden by the configuration passed to create().
	* Context
	* 	.create( { bar: 3 } )
	* 	.then( context => {
	* 		context.config.get( 'foo' ); // -> 1
	* 		context.config.get( 'bar' ); // -> 3
	* 	} );
	* ```
	*
	* See also {@link module:core/context~Context.builtinPlugins `Context.builtinPlugins`}
	* and {@link module:core/editor/editor~Editor.defaultConfig `Editor.defaultConfig`}.
	*/
	static defaultConfig;
	/**
	* An array of plugins built into the `Context` class.
	*
	* It was used in the now deprecated CKEditor 5 builds featuring `Context` to provide the default configuration options
	* which are later used during the context initialization.
	*
	* They will be automatically initialized by `Context` unless `config.plugins` is passed.
	*
	* ```ts
	* // Build some context plugins into the Context class first.
	* Context.builtinPlugins = [ FooPlugin, BarPlugin ];
	*
	* // Normally, you need to define config.plugins, but since Context.builtinPlugins was
	* // defined, now you can call create() without any configuration.
	* Context
	* 	.create()
	* 	.then( context => {
	* 		context.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
	* 		context.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
	* 	} );
	* ```
	*
	* See also {@link module:core/context~Context.defaultConfig `Context.defaultConfig`}
	* and {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
	*/
	static builtinPlugins;
	/**
	* Reference to the editor which created the context.
	* Null when the context was created outside of the editor.
	*
	* It is used to destroy the context when removing the editor that has created the context.
	*/
	_contextOwner = null;
	/**
	* Creates a context instance with a given configuration.
	*
	* Usually not to be used directly. See the static {@link module:core/context~Context.create `create()`} method.
	*
	* @param config The context configuration.
	*/
	constructor(config) {
		const { translations, ...rest } = config || {};
		this.config = new Config(rest, this.constructor.defaultConfig);
		const availablePlugins = this.constructor.builtinPlugins;
		this.config.define("plugins", availablePlugins);
		this.plugins = new PluginCollection(this, availablePlugins);
		const languageConfig = this.config.get("language") || {};
		this.locale = new Locale({
			uiLanguage: typeof languageConfig === "string" ? languageConfig : languageConfig.ui,
			contentLanguage: this.config.get("language.content"),
			translations
		});
		this.t = this.locale.t;
		this.editors = new Collection();
	}
	/**
	* Loads and initializes plugins specified in the configuration.
	*
	* @returns A promise which resolves once the initialization is completed, providing an array of loaded plugins.
	*/
	initPlugins() {
		const plugins = this.config.get("plugins") || [];
		const substitutePlugins = this.config.get("substitutePlugins") || [];
		for (const Plugin of plugins.concat(substitutePlugins)) {
			if (typeof Plugin != "function")
 /**
			* Only a constructor function is allowed as a {@link module:core/contextplugin~ContextPlugin context plugin}.
			*
			* @error context-initplugins-constructor-only
			*/
			throw new CKEditorError("context-initplugins-constructor-only", null, { Plugin });
			if (Plugin.isContextPlugin !== true)
 /**
			* Only a plugin marked as a {@link module:core/contextplugin~ContextPlugin.isContextPlugin context plugin}
			* is allowed to be used with a context.
			*
			* @error context-initplugins-invalid-plugin
			*/
			throw new CKEditorError("context-initplugins-invalid-plugin", null, { Plugin });
		}
		return this.plugins.init(plugins, [], substitutePlugins).then((loadedPlugins) => {
			this._errorExclusions = getSubNodes(this, new Set(this.editors));
			_addLiveContext(this);
			return loadedPlugins;
		});
	}
	/**
	* Destroys the context instance and all editors used with the context,
	* releasing all resources used by the context.
	*
	* @returns A promise that resolves once the context instance is fully destroyed.
	*/
	destroy() {
		_removeLiveContext(this);
		return Promise.all(Array.from(this.editors, (editor) => editor.destroy())).then(() => this.plugins.destroy());
	}
	/**
	* Adds a reference to the editor which is used with this context.
	*
	* When the given editor has created the context, the reference to this editor will be stored
	* as a {@link ~Context#_contextOwner}.
	*
	* This method should only be used by the editor.
	*
	* @internal
	* @param isContextOwner Stores the given editor as a context owner.
	*/
	_addEditor(editor, isContextOwner) {
		if (this._contextOwner)
 /**
		* Cannot add multiple editors to the context which is created by the editor.
		*
		* @error context-addeditor-private-context
		*/
		throw new CKEditorError("context-addeditor-private-context");
		this.editors.add(editor);
		if (isContextOwner) this._contextOwner = editor;
	}
	/**
	* Removes a reference to the editor which was used with this context.
	* When the context was created by the given editor, the context will be destroyed.
	*
	* This method should only be used by the editor.
	*
	* @internal
	* @return A promise that resolves once the editor is removed from the context or when the context was destroyed.
	*/
	_removeEditor(editor) {
		if (this.editors.has(editor)) this.editors.remove(editor);
		if (this._contextOwner === editor) return this.destroy();
		return Promise.resolve();
	}
	/**
	* Returns the context configuration which will be copied to the editors created using this context.
	*
	* The configuration returned by this method has the plugins configuration removed &ndash; plugins are shared with all editors
	* through another mechanism.
	*
	* This method should only be used by the editor.
	*
	* @internal
	* @returns Configuration as a plain object.
	*/
	_getEditorConfig() {
		const result = {};
		for (const name of this.config.names()) if (![
			"plugins",
			"removePlugins",
			"extraPlugins"
		].includes(name)) result[name] = this.config.get(name);
		return result;
	}
	/**
	* Creates and initializes a new context instance.
	*
	* ```ts
	* const commonConfig = { ... }; // Configuration for all the plugins and editors.
	* const editorPlugins = [ ... ]; // Regular plugins here.
	*
	* Context
	* 	.create( {
	* 		// Only context plugins here.
	* 		plugins: [ ... ],
	*
	* 		// Configure the language for all the editors (it cannot be overwritten).
	* 		language: { ... },
	*
	* 		// Configuration for context plugins.
	* 		comments: { ... },
	* 		...
	*
	* 		// Default configuration for editor plugins.
	* 		toolbar: { ... },
	* 		image: { ... },
	* 		...
	* 	} )
	* 	.then( context => {
	* 		const promises = [];
	*
	* 		promises.push( ClassicEditor.create(
	* 			document.getElementById( 'editor1' ),
	* 			{
	* 				editorPlugins,
	* 				context
	* 			}
	* 		) );
	*
	* 		promises.push( ClassicEditor.create(
	* 			document.getElementById( 'editor2' ),
	* 			{
	* 				editorPlugins,
	* 				context,
	* 				toolbar: { ... } // You can overwrite the configuration of the context.
	* 			}
	* 		) );
	*
	* 		return Promise.all( promises );
	* 	} );
	* ```
	*
	* @param config The context configuration.
	* @returns A promise resolved once the context is ready. The promise resolves with the created context instance.
	*/
	static create(config) {
		return new Promise((resolve) => {
			const context = new this(config);
			resolve(context.initPlugins().then(() => context));
		});
	}
	/**
	* {@link module:core/errorreporter~onEditorError `onEditorError()`}, reachable without importing it.
	*
	* The same accessor as {@link module:core/editor/editor~Editor.onEditorError `Editor.onEditorError`},
	* and here for the same reason. A context is the only handle some integrations have: a component that
	* provides a shared context holds no editor class at all.
	*/
	static get onEditorError() {
		return onEditorError;
	}
	/**
	* `Context` class is commonly put in `config.plugins` array.
	*
	* This property helps with better error detection.
	*
	* @internal
	*/
	static get _throwErrorWhenUsedAsAPlugin() {
		return true;
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/contextplugin
*/
const ContextPluginBase = /* #__PURE__ */ ObservableMixin();
/**
* The base class for {@link module:core/context~Context} plugin classes.
*
* A context plugin can either be initialized for an {@link module:core/editor/editor~Editor editor} or for
* a {@link module:core/context~Context context}. In other words, it can either
* work within one editor instance or with one or more editor instances that use a single context.
* It is the context plugin's role to implement handling for both modes.
*
* There are a few rules for interaction between the editor plugins and context plugins:
*
* * A context plugin can require another context plugin.
* * An {@link module:core/plugin~Plugin editor plugin} can require a context plugin.
* * A context plugin MUST NOT require an {@link module:core/plugin~Plugin editor plugin}.
*/
var ContextPlugin = class extends ContextPluginBase {
	/**
	* The context or editor instance.
	*/
	context;
	/**
	* Creates a new plugin instance.
	*/
	constructor(context) {
		super();
		this.context = context;
	}
	/**
	* @inheritDoc
	*/
	destroy() {
		this.stopListening();
	}
	/**
	* @inheritDoc
	*/
	static get isContextPlugin() {
		return true;
	}
	/**
	* @inheritDoc
	*/
	static get isOfficialPlugin() {
		return false;
	}
	/**
	* @inheritDoc
	*/
	static get isPremiumPlugin() {
		return false;
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* A plugin that records user actions and editor state changes for debugging purposes. It tracks commands execution, model operations,
* UI interactions, and document events. It just collects data locally, and does not send it anywhere, integrator is responsible
* for gathering data from this plugin for further processing.
*
* **Important! `ActionsRecorder` is an experimental feature, and may become deprecated.**
*
* By default, plugin stores latest 1000 action entries. Integrator can register an `onError` callback to collect those entries
* in case of exception. Integrator should augment this data with application specific data such as page-id or session-id,
* depending on the application. Augmented data should be processed by the integrator, for example integrator should send it
* to some data collecting endpoint for later analysis.
*
* Example:
*
* ```ts
* 	ClassicEditor
* 		.create( {
* 			plugins: [ ActionsRecorder, ... ],
* 			actionsRecorder: {
* 				maxEntries: 1000, // This is the default value and could be adjusted.
*
* 				onError( error, entries ) {
* 					console.error( 'ActionsRecorder - Error detected:', error );
* 					console.warn( 'Actions recorded before error:', entries );
*
* 					this.flushEntries();
*
* 					// Integrator should send and store the entries. The error is already in the last entry in serializable form.
* 				}
* 			}
* 		} )
* 		.then( ... )
* 		.catch( ... );
* ```
*
* Alternatively integrator could continuously collect actions in batches and send them to theirs endpoint for later analysis:
*
* ```ts
* 	ClassicEditor
* 		.create( {
* 			plugins: [ ActionsRecorder, ... ],
* 			actionsRecorder: {
* 				maxEntries: 50, // This is the batch size.
*
* 				onMaxEntries() {
* 					const entries = this.getEntries();
*
* 					this.flushEntries();
*
* 					console.log( 'ActionsRecorder - Batch of entries:', entries );
*
* 					// Integrator should send and store the entries.
* 				},
*
* 				onError( error, entries ) {
* 					console.error( 'ActionsRecorder - Error detected:', error );
* 					console.warn( 'Actions recorded before error:', entries );
*
* 					this.flushEntries();
*
* 					// Integrator should send and store the entries. The error is already in the last entry in serializable form.
* 				}
* 			}
* 		} )
* 		.then( ... )
* 		.catch( ... );
* ```
*
* See {@link module:core/actionsrecorderconfig~ActionsRecorderConfig plugin configuration} for more details.
*
*/
var ActionsRecorder = class {
	/**
	* The editor instance.
	*/
	editor;
	/**
	* Array storing all recorded action entries with their context and state snapshots.
	*/
	_entries = [];
	/**
	* Stack tracking nested action frames to maintain call hierarchy.
	*/
	_frameStack = [];
	/**
	* Set of already reported errors used to notify only once for each error (not on every try-catch nested block).
	*/
	_errors = /* @__PURE__ */ new Set();
	/**
	* Maximum number of action entries to keep in memory.
	*/
	_maxEntries;
	/**
	* Error callback.
	*/
	_errorCallback;
	/**
	* Filter function to determine which entries should be stored.
	*/
	_filterCallback;
	/**
	* Callback triggered every time count of recorded entries reaches maxEntries.
	*/
	_maxEntriesCallback;
	/**
	* @inheritDoc
	*/
	static get pluginName() {
		return "ActionsRecorder";
	}
	/**
	* @inheritDoc
	*/
	static get isOfficialPlugin() {
		return true;
	}
	/**
	* @inheritDoc
	*/
	constructor(editor) {
		this.editor = editor;
		editor.config.define("actionsRecorder.maxEntries", 1e3);
		const config = editor.config.get("actionsRecorder");
		this._maxEntries = config.maxEntries;
		this._filterCallback = config.onFilter;
		this._errorCallback = config.onError;
		this._maxEntriesCallback = config.onMaxEntries || this._maxEntriesDefaultHandler;
		this._tapCommands();
		this._tapOperationApply();
		this._tapModelMethods();
		this._tapModelSelection();
		this._tapComponentFactory();
		this._tapViewDocumentEvents();
	}
	/**
	* Returns all recorded action entries.
	*/
	getEntries() {
		return this._entries.slice();
	}
	/**
	* Flushes all recorded entries.
	*/
	flushEntries() {
		this._entries = [];
	}
	/**
	* Creates a new action frame and adds it to the recording stack.
	*
	* @param action The name/type of the action being recorded.
	* @param params Optional parameters associated with the event.
	* @returns The created call frame object.
	*/
	_enterFrame(action, params) {
		const callFrame = {
			timeStamp: (/* @__PURE__ */ new Date()).toISOString(),
			...this._frameStack.length && { parentEntry: this._frameStack.at(-1) },
			action,
			params: params?.map((param) => serializeValue(param)),
			before: this._buildStateSnapshot()
		};
		if (!this._filterCallback || this._filterCallback(callFrame, this._entries)) this._entries.push(callFrame);
		this._frameStack.push(callFrame);
		return callFrame;
	}
	/**
	* Closes an action frame and records its final state and results.
	*
	* @param callFrame The call frame to close.
	* @param result Optional result value from the action.
	* @param error Optional error that occurred during the action.
	*/
	_leaveFrame(callFrame, result, error) {
		const topFrame = this._frameStack.pop();
		if (!topFrame || topFrame !== callFrame) return;
		if (result !== void 0) topFrame.result = serializeValue(result);
		if (error) topFrame.error = serializeValue(error);
		topFrame.after = this._buildStateSnapshot();
		if (error) this._callErrorCallback(error);
		if (this._frameStack.length == 0) this._errors.clear();
		if (this._entries.length >= this._maxEntries) this._maxEntriesCallback();
	}
	/**
	* Builds a snapshot of the current editor state including document version,
	* read-only status, focus state, and model selection.
	*
	* @returns An object containing the current editor state snapshot.
	*/
	_buildStateSnapshot() {
		const { model, isReadOnly, editing } = this.editor;
		return {
			documentVersion: model.document.version,
			editorReadOnly: isReadOnly,
			editorFocused: editing.view.document.isFocused,
			modelSelection: serializeValue(model.document.selection)
		};
	}
	/**
	* Sets up recording for all editor commands, both existing and future ones.
	* Taps into the command execution to track when commands are run.
	*/
	_tapCommands() {
		for (const [commandName, command] of this.editor.commands) this._tapCommand(commandName, command);
		tapObjectMethod(this.editor.commands, "add", { before: (callContext, [commandName, command]) => {
			this._tapCommand(commandName, command);
			return false;
		} });
	}
	/**
	* Sets up recording for model operation applications.
	* Tracks when operations are applied to the model document.
	*/
	_tapOperationApply() {
		tapObjectMethod(this.editor.model, "applyOperation", {
			before: (callContext, [operation]) => {
				if (operation.baseVersion === null) return false;
				callContext.callFrame = this._enterFrame("model.applyOperation", [operation]);
				return true;
			},
			after: (callContext) => {
				this._leaveFrame(callContext.callFrame);
			},
			error: (callContext, error) => {
				this._leaveFrame(callContext.callFrame, void 0, error);
			}
		});
	}
	/**
	* Sets up recording for key model methods like insertContent, insertObject, and deleteContent.
	* These methods represent high-level model manipulation operations.
	*/
	_tapModelMethods() {
		for (const methodName of [
			"insertContent",
			"insertObject",
			"deleteContent"
		]) tapObjectMethod(this.editor.model, methodName, {
			before: (callContext, ...params) => {
				callContext.callFrame = this._enterFrame(`model.${methodName}`, params);
				return true;
			},
			after: (callContext, result) => {
				this._leaveFrame(callContext.callFrame, result);
			},
			error: (callContext, error) => {
				this._leaveFrame(callContext.callFrame, void 0, error);
			}
		});
	}
	/**
	* Sets up recording for model selection changes.
	* Tracks when the selection range, attributes, or markers change.
	*/
	_tapModelSelection() {
		this._tapFireMethod(this.editor.model.document.selection, [
			"change:range",
			"change:attribute",
			"change:marker"
		], { eventSource: "model-selection" });
	}
	/**
	* Sets up recording for a specific command execution.
	*
	* @param commandName The name of the command to record.
	* @param command The command instance to tap into.
	*/
	_tapCommand(commandName, command) {
		tapObjectMethod(command, "execute", {
			before: (callContext, params) => {
				callContext.callFrame = this._enterFrame(`commands.${commandName}:execute`, params);
				return true;
			},
			after: (callContext, result) => {
				this._leaveFrame(callContext.callFrame, result);
			},
			error: (callContext, error) => {
				this._leaveFrame(callContext.callFrame, void 0, error);
			}
		});
	}
	/**
	* Sets up recording for UI component factory creation and component interactions.
	* Tracks when components are created and their execute events.
	*/
	_tapComponentFactory() {
		tapObjectMethod(this.editor.ui.componentFactory, "create", {
			before: (callContext, [componentName]) => {
				callContext.componentName = componentName;
				callContext.callFrame = this._enterFrame(`component-factory.create:${componentName}`);
				return true;
			},
			after: (callContext, componentInstance) => {
				const executeContext = {
					...callContext,
					eventSource: `component.${callContext.componentName}`
				};
				if (typeof componentInstance.fire == "function") this._tapFireMethod(componentInstance, ["execute"], executeContext);
				if (typeof componentInstance.panelView?.fire == "function") this._tapFireMethod(componentInstance.panelView, ["execute"], executeContext);
				if (typeof componentInstance.buttonView?.actionView?.fire == "function") this._tapFireMethod(componentInstance.buttonView.actionView, ["execute"], executeContext);
				this._leaveFrame(callContext.callFrame);
			},
			error: (callContext, error) => {
				this._leaveFrame(callContext.callFrame, void 0, error);
			}
		});
	}
	/**
	* Sets up recording for view document events like clicks, keyboard input,
	* selection changes, and other user interactions.
	*/
	_tapViewDocumentEvents() {
		this._tapFireMethod(this.editor.editing.view.document, [
			"click",
			"mousedown",
			"mouseup",
			"pointerdown",
			"pointerup",
			"focus",
			"blur",
			"keydown",
			"keyup",
			"selectionChange",
			"compositionstart",
			"compositionend",
			"beforeinput",
			"mutations",
			"enter",
			"delete",
			"insertText",
			"paste",
			"copy",
			"cut",
			"dragstart",
			"drop"
		], { eventSource: "observers" });
	}
	/**
	* Sets up recording for specific events fired by an emitter object.
	*
	* @param emitter The object that fires events to be recorded.
	* @param eventNames Array of event names to record.
	* @param context Additional context to include with recorded events.
	*/
	_tapFireMethod(emitter, eventNames, context = {}) {
		tapObjectMethod(emitter, "fire", {
			before: (callContext, [eventInfoOrName, ...params]) => {
				const eventName = typeof eventInfoOrName == "string" ? eventInfoOrName : eventInfoOrName.name;
				if (!eventNames.includes(eventName)) return false;
				callContext.callFrame = this._enterFrame(`${callContext.eventSource}:${eventName}`, params);
				return true;
			},
			after: (callContext, result) => {
				this._leaveFrame(callContext.callFrame, result);
			},
			error: (callContext, error) => {
				this._leaveFrame(callContext.callFrame, void 0, error);
			}
		}, context);
	}
	/**
	* Triggers error callback.
	*/
	_callErrorCallback(error) {
		if (!this._errorCallback || this._errors.has(error)) return;
		this._errors.add(error);
		try {
			this._errorCallback(error, this.getEntries());
		} catch (observerError) {
			console.error("ActionsRecorder onError callback error:", observerError);
		}
	}
	/**
	* The default handler for maxEntries callback.
	*/
	_maxEntriesDefaultHandler() {
		this._entries.shift();
	}
};
/**
* Creates a wrapper around a method to record its calls, results, and errors.
*
* @internal
*
* @param object The object containing the method to tap.
* @param methodName The name of the method to tap.
* @param tap The tap configuration with before/after/error hooks.
* @param context Additional context to include with the method calls.
*/
function tapObjectMethod(object, methodName, tap, context = {}) {
	const originalMethod = object[methodName];
	if (originalMethod[Symbol.for("Tapped method")]) return;
	object[methodName] = (...args) => {
		const callContext = Object.assign({}, context);
		let shouldHandle;
		try {
			shouldHandle = tap.before?.(callContext, args);
			const result = originalMethod.apply(object, args);
			if (shouldHandle) tap.after?.(callContext, result);
			return result;
		} catch (error) {
			if (shouldHandle) tap.error?.(callContext, error);
			throw error;
		}
	};
	object[methodName][Symbol.for("Tapped method")] = originalMethod;
}
/**
* Serializes a value into a JSON-serializable format.
*
* @internal
*
* @param value The value to serialize.
* @param visited Set of already serialized objects to avoid circular references.
* @returns A JSON-serializable representation of the value.
*/
function serializeValue(value, visited = /* @__PURE__ */ new WeakSet()) {
	if (!value || [
		"boolean",
		"number",
		"string"
	].includes(typeof value)) return value;
	if (typeof value.toJSON == "function") {
		const jsonData = value.toJSON();
		if (isPlainObject(jsonData) || Array.isArray(jsonData) || [
			"string",
			"number",
			"boolean"
		].includes(typeof jsonData)) return serializeValue(jsonData, visited);
	}
	if (value instanceof Error) return {
		name: value.name,
		message: value.message,
		stack: value.stack
	};
	if (isTypeCheckable(value) || typeof value != "object") return {
		type: typeof value,
		constructor: value.constructor?.name || "unknown",
		string: String(value)
	};
	if (value instanceof File || value instanceof Blob || value instanceof FormData || value instanceof DataTransfer) return String(value);
	if (visited.has(value)) return;
	visited.add(value);
	if (Array.isArray(value)) return value.length ? value.map((item) => serializeValue(item, visited)) : void 0;
	const result = {};
	const ignoreFields = [];
	if (value.domEvent) {
		ignoreFields.push("domEvent", "domTarget", "view", "document");
		result.domEvent = serializeDomEvent(value.domEvent);
		result.target = serializeValue(value.target);
		if (value.dataTransfer) result.dataTransfer = {
			types: value.dataTransfer.types,
			htmlData: value.dataTransfer.getData("text/html"),
			files: serializeValue(value.dataTransfer.files)
		};
	}
	for (const [key, val] of Object.entries(value)) {
		if (key.startsWith("_") || ignoreFields.includes(key) || typeof val == "function") continue;
		const serializedValue = serializeValue(val, visited);
		if (serializedValue !== void 0) result[key] = serializedValue;
	}
	if (Symbol.iterator in value) {
		const items = Array.from(value[Symbol.iterator]()).map((item) => serializeValue(item, visited));
		if (items.length) result._items = items;
	}
	return Object.keys(result).length ? result : void 0;
}
/**
* Serializes a DOM event into a plain object representation.
*
* Extracts common properties from DOM events such as type, target information,
* coordinates, key codes, and other relevant event data for debugging purposes.
*
* @param event The DOM event to serialize.
* @returns A serialized object containing the event's key properties.
*/
function serializeDomEvent(event) {
	let serialized = {
		type: event.type,
		target: serializeDOMTarget(event.target)
	};
	if (event instanceof MouseEvent) serialized = {
		...serialized,
		button: event.button,
		buttons: event.buttons,
		ctrlKey: event.ctrlKey,
		shiftKey: event.shiftKey,
		altKey: event.altKey,
		metaKey: event.metaKey
	};
	if (event instanceof KeyboardEvent) serialized = {
		...serialized,
		key: event.key,
		code: event.code,
		keyCode: event.keyCode,
		ctrlKey: event.ctrlKey,
		shiftKey: event.shiftKey,
		altKey: event.altKey,
		metaKey: event.metaKey,
		repeat: event.repeat
	};
	if (event instanceof InputEvent) serialized = {
		...serialized,
		data: event.data,
		inputType: event.inputType,
		isComposing: event.isComposing
	};
	if (event instanceof PointerEvent) serialized = {
		...serialized,
		isPrimary: event.isPrimary
	};
	/**
	* Serializes a DOM event target into a plain object representation.
	*
	* @param target The DOM event target to serialize.
	* @returns A serialized object containing the target's information.
	*/
	function serializeDOMTarget(target) {
		if (!target) return null;
		if (target instanceof Element) return {
			tagName: target.tagName,
			className: target.className,
			id: target.id
		};
		if (target instanceof Window || target instanceof Document) return { type: target.constructor.name };
		return {};
	}
	return serialized;
}
/**
* Checks if a value is type-checkable, meaning it has an `is` method.
*/
function isTypeCheckable(value) {
	return value && typeof value.is === "function";
}
/* v8 ignore stop -- @preserve */

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/utils/attachtoform
*/
/**
* Checks if the editor is initialized on a `<textarea>` element that belongs to a form. If yes, it updates the editor's element
* content before submitting the form.
*
* This helper requires the {@link module:core/editor/utils/elementapimixin~ElementApi ElementApi interface}.
*
* @param editor Editor instance.
*/
function attachToForm(editor) {
	if (!isFunction(editor.updateSourceElement))
 /**
	* The editor passed to `attachToForm()` must implement the
	* {@link module:core/editor/utils/elementapimixin~ElementApi} interface.
	*
	* @error attachtoform-missing-elementapi-interface
	*/
	throw new CKEditorError("attachtoform-missing-elementapi-interface", editor);
	const sourceElement = editor.sourceElement;
	if (isTextArea(sourceElement) && sourceElement.form) {
		let originalSubmit;
		const form = sourceElement.form;
		const onSubmit = () => editor.updateSourceElement();
		if (isFunction(form.submit)) {
			originalSubmit = form.submit;
			form.submit = () => {
				onSubmit();
				originalSubmit.apply(form);
			};
		}
		form.addEventListener("submit", onSubmit);
		editor.on("destroy", () => {
			form.removeEventListener("submit", onSubmit);
			if (originalSubmit) form.submit = originalSubmit;
		});
	}
}
function isTextArea(sourceElement) {
	return !!sourceElement && sourceElement.tagName.toLowerCase() === "textarea";
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/utils/elementapimixin
*/
/**
* Implementation of the {@link module:core/editor/utils/elementapimixin~ElementApi}.
*/
function ElementApiMixin(base) {
	class Mixin extends base {
		sourceElement;
		updateSourceElement(data) {
			if (!this.sourceElement)
 /**
			* Cannot update the source element of a detached editor.
			*
			* The {@link module:core/editor/utils/elementapimixin~ElementApi#updateSourceElement `updateSourceElement()`}
			* method cannot be called if you did not pass an element to `Editor.create()`.
			*
			* @error editor-missing-sourceelement
			*/
			throw new CKEditorError("editor-missing-sourceelement", this);
			const shouldUpdateSourceElement = this.config.get("updateSourceElementOnDestroy");
			const isSourceElementTextArea = this.sourceElement instanceof HTMLTextAreaElement;
			if (!shouldUpdateSourceElement && !isSourceElementTextArea) {
				setDataInElement(this.sourceElement, "");
				return;
			}
			const dataToSet = typeof data === "string" ? data : this.data.get();
			setDataInElement(this.sourceElement, dataToSet);
		}
	}
	return Mixin;
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/utils/securesourceelement
*/
/**
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
*
* Running multiple editor instances on the same source element causes various issues and it is
* crucial this helper is called as soon as the source element is known to prevent collisions.
*
* @param editor Editor instance.
* @param sourceElement Element to bind with the editor instance.
*/
function secureSourceElement(editor, sourceElement) {
	if (sourceElement.ckeditorInstance)
 /**
	* A DOM element used to create the editor (e.g.
	* {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`})
	* has already been used to create another editor instance. Make sure each editor is
	* created with a unique DOM element.
	*
	* @error editor-source-element-already-used
	* @param {HTMLElement} element DOM element that caused the collision.
	*/
	throw new CKEditorError("editor-source-element-already-used", editor);
	sourceElement.ckeditorInstance = editor;
	editor.once("destroy", () => {
		delete sourceElement.ckeditorInstance;
	});
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
const CONFIG_FIELDS_AS_ROOT_ATTRIBUTES = [["description", "$description"], ["title", "$title"]];
/**
* Registers and initializes root attributes from the editor configuration:
*
* * the attributes from
* {@link module:core/editor/editorconfig~RootConfig#modelAttributes `config.root(s).<rootName>.modelAttributes`}, and
* * the {@link module:core/editor/editorconfig~RootConfig#description `description`} and
* {@link module:core/editor/editorconfig~RootConfig#title `title`} fields, stored as the `$description` and `$title`
* root attributes respectively.
*
* @internal
*/
function registerAndInitializeRootConfigAttributes(editor) {
	const rootsConfig = editor.config.get("roots");
	for (const [rootName, rootConfig] of Object.entries(rootsConfig)) {
		let modelAttributes = null;
		for (const [configField, attributeKey] of CONFIG_FIELDS_AS_ROOT_ATTRIBUTES) {
			if (rootConfig[configField] == null || rootConfig.modelAttributes && attributeKey in rootConfig.modelAttributes) continue;
			modelAttributes = modelAttributes || { ...rootConfig.modelAttributes };
			modelAttributes[attributeKey] = rootConfig[configField];
		}
		if (modelAttributes) editor.config.set(`roots.${rootName}.modelAttributes`, modelAttributes);
	}
	const normalizedRootsConfig = editor.config.get("roots");
	let hasRootAttributes = false;
	for (const rootConfig of Object.values(normalizedRootsConfig)) for (const key of Object.keys(rootConfig.modelAttributes || {})) {
		editor.registerRootAttribute(key);
		hasRootAttributes = true;
	}
	if (!hasRootAttributes) return;
	editor.data.once("init", () => {
		editor.model.enqueueChange({ isUndoable: false }, (writer) => {
			for (const [rootName, rootConfig] of Object.entries(normalizedRootsConfig)) {
				const root = editor.model.document.getRoot(rootName);
				if (!root) continue;
				for (const [key, value] of Object.entries(rootConfig.modelAttributes || {})) if (value !== null) writer.setAttribute(key, value, root);
			}
		});
	});
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* Normalizes the editor roots configuration. It ensures that all root configurations are defined in `config.roots`
* and that they have `initialData` defined.
*
* It normalizes a single-root configuration (where `config.root` is used) to a multi-root configuration
* (where all roots are defined in `config.roots`). This is considered a standard configuration format,
* so the editor features can always expect roots to be defined in `config.roots`.
*
* It also handles legacy configuration options, such as `config.initialData`, `config.placeholder`, and `config.label`.
*
* @internal
*/
function normalizeRootsConfig(sourceElementsOrData, config, defaultRootName = "main", separateAttachTo = false) {
	const mainRootConfig = config.get("root");
	const rootsConfig = config.get("roots") || Object.create(null);
	if (!isPlainObject(rootsConfig))
 /**
	* The {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots`} option
	* must be a plain object (an object literal or `Object.create( null )`). Objects with a
	* custom prototype (e.g. created via `Object.create( someProto )`) or class instances
	* are not accepted, because internal configuration processing relies on a known
	* prototype chain to detect nested configuration objects.
	*
	* Replace the value with a plain object before passing it to the editor:
	*
	* ```ts
	* // Wrong
	* MultiRootEditor.create( {
	*     roots: Object.assign( Object.create( {} ), { intro: { ... } } )
	* } );
	*
	* // Right
	* MultiRootEditor.create( {
	*     roots: { intro: { ... } }
	* } );
	* ```
	*
	* @error editor-create-roots-not-plain-object
	*/
	throw new CKEditorError("editor-create-roots-not-plain-object", null);
	if (mainRootConfig) {
		if (!defaultRootName)
 /**
		* The {@link module:core/editor/editorconfig~EditorConfig#root `config.root`} option is designed
		* for single-root editors and cannot be used with the
		* {@link module:editor-multi-root/multirooteditor~MultiRootEditor multi-root editor}.
		*
		* To configure a multi-root editor, define each root individually using
		* {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots`}:
		*
		* ```ts
		* MultiRootEditor.create( {
		* 	roots: {
		* 		header: { initialData: '<p>Header</p>' },
		* 		content: { initialData: '<p>Content</p>' }
		* 	}
		* } );
		* ```
		*
		* @error editor-create-multi-root-with-main
		*/
		throw new CKEditorError("editor-create-multi-root-with-main", null);
		else if (defaultRootName in rootsConfig)
 /**
		* Both {@link module:core/editor/editorconfig~EditorConfig#root `config.root`} and
		* `config.roots.main` are set, but they both configure the same default editing root,
		* which creates an ambiguity. Use one or the other:
		*
		* * {@link module:core/editor/editorconfig~EditorConfig#root `config.root`} for a single-root editor.
		* * {@link module:core/editor/editorconfig~EditorConfig#roots `config.roots`} when defining
		* multiple roots.
		*
		* @error editor-create-roots-with-main
		*/
		throw new CKEditorError("editor-create-roots-with-main", null);
	}
	if (defaultRootName && !rootsConfig[defaultRootName]) rootsConfig[defaultRootName] = mainRootConfig || Object.create(null);
	const sourceElementIsPlainObject = isSourceElementsOrDataRecord(sourceElementsOrData);
	const legacyInitialData = getLegacyInitialData(config, sourceElementIsPlainObject, defaultRootName);
	const rootNames = Array.from(/* @__PURE__ */ new Set([
		...sourceElementIsPlainObject ? Object.keys(sourceElementsOrData) : [],
		...Object.keys(rootsConfig),
		...Object.keys(legacyInitialData)
	]));
	for (const rootName of rootNames) {
		const rootConfig = rootsConfig[rootName] || Object.create(null);
		const sourceElementOrDataForRoot = sourceElementIsPlainObject ? sourceElementsOrData[rootName] : sourceElementsOrData;
		if (!separateAttachTo && isElement$1(sourceElementOrDataForRoot)) {
			if (rootConfig.element)
 /**
			* The root element is specified both as the first argument of the editor `create()` method
			* (e.g. {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`} or
			* {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`})
			* and in {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`}
			* (or `config.roots.<rootName>.element` for the
			* {@link module:editor-multi-root/multirooteditor~MultiRootEditor multi-root editor}).
			*
			* Passing the element as the first argument is deprecated. Remove it and use
			* {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`}
			* (or `config.roots.<rootName>.element` for the multi-root editor) instead.
			*
			* @error editor-create-root-element-overspecified
			*/
			throw new CKEditorError("editor-create-root-element-overspecified", null);
			rootConfig.element = sourceElementOrDataForRoot;
		}
		if (separateAttachTo && isElement$1(rootConfig.element)) {
			/**
			* The {@link module:editor-classic/classiceditor~ClassicEditor} cannot use an existing DOM element as
			* {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`} because the classic
			* editor replaces the source element with its own UI rather than editing inline within it. The passed
			* element is ignored along with its content, so any markup inside it will not be used as the editor's
			* initial data. Use {@link module:core/editor/editorconfig~EditorConfig#attachTo `config.attachTo`}
			* to specify the DOM element the editor should replace, or pass the initial data via
			* {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}.
			*
			* A tag name string (e.g. `'h1'`) or a
			* {@link module:core/editor/editorconfig~ViewRootElementDefinition view root element definition} are still
			* accepted - they describe the editable root element the classic editor creates inside its UI box.
			*
			* @error editor-create-root-element-not-supported
			*/
			logWarning("editor-create-root-element-not-supported");
			rootConfig.element = void 0;
		}
		if (rootConfig.initialData === void 0) {
			if (legacyInitialData[rootName] === void 0) {
				const rootConfigElement = isElement$1(rootConfig.element) ? rootConfig.element : void 0;
				rootConfig.initialData = getInitialData(sourceElementOrDataForRoot || rootConfigElement || separateAttachTo && config.get("attachTo") || "");
			} else if (sourceElementOrDataForRoot && !isElement$1(sourceElementOrDataForRoot))
 /**
			* The initial data is specified both as the first argument of the editor `create()` method
			* (e.g. {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`} or
			* {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`})
			* and in the deprecated {@link module:core/editor/editorconfig~EditorConfig#initialData `config.initialData`}.
			*
			* Passing initial data as the first argument is deprecated. Remove it and use
			* {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}
			* (or `config.roots.<rootName>.initialData` for the multi-root editor) instead.
			*
			* @error editor-create-initial-data-overspecified
			*/
			throw new CKEditorError("editor-create-initial-data-overspecified", null);
			else rootConfig.initialData = legacyInitialData[rootName];
		} else if (sourceElementOrDataForRoot && !isElement$1(sourceElementOrDataForRoot))
 /**
		* The initial data is specified both as the first argument of the editor `create()` method
		* (e.g. {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`} or
		* {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`})
		* and in {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}
		* (or `config.roots.<rootName>.initialData` for the multi-root editor).
		*
		* Passing initial data as the first argument is deprecated. Remove it and use
		* {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}
		* (or `config.roots.<rootName>.initialData` for the multi-root editor) instead.
		*
		* @error editor-create-root-initial-data-overspecified
		*/
		throw new CKEditorError("editor-create-root-initial-data-overspecified", null);
		else if (legacyInitialData[rootName] !== void 0)
 /**
		* The initial data is specified both in
		* {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}
		* (or `config.roots.<rootName>.initialData` for the multi-root editor) and in the deprecated
		* {@link module:core/editor/editorconfig~EditorConfig#initialData `config.initialData`}.
		*
		* The `config.initialData` option is deprecated. Remove it and use
		* {@link module:core/editor/editorconfig~RootConfig#initialData `config.root.initialData`}
		* (or `config.roots.<rootName>.initialData` for the multi-root editor) instead.
		*
		* @error editor-create-legacy-initial-data-overspecified
		*/
		throw new CKEditorError("editor-create-legacy-initial-data-overspecified", null);
		rootConfig.placeholder ??= getLegacyPlainConfigValue(config, "placeholder", rootName);
		rootConfig.label ??= getLegacyPlainConfigValue(config, "label", rootName);
		rootConfig.modelElement ||= "$root";
		rootConfig.element = normalizeViewRootElementDefinition(rootConfig.element);
		rootsConfig[rootName] = rootConfig;
	}
	if (separateAttachTo && isElement$1(sourceElementsOrData)) {
		if (config.get("attachTo"))
 /**
		* The element to attach the editor to is specified both as the first argument of
		* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}
		* and in {@link module:core/editor/editorconfig~EditorConfig#attachTo `config.attachTo`}.
		*
		* Passing the element as the first argument is deprecated. Remove it and use
		* {@link module:core/editor/editorconfig~EditorConfig#attachTo `config.attachTo`} instead.
		*
		* @error editor-create-attachto-overspecified
		*/
		throw new CKEditorError("editor-create-attachto-overspecified", null);
		config.set("attachTo", sourceElementsOrData);
	}
	if (!separateAttachTo && config.get("attachTo"))
 /**
	* The {@link module:core/editor/editorconfig~EditorConfig#attachTo `config.attachTo`} option is only
	* available for the {@link module:editor-classic/classiceditor~ClassicEditor} because it replaces the
	* given DOM element with its own UI. Other editor types (e.g. inline, balloon, decoupled) render inside
	* the root element directly, so `config.attachTo` is not applicable.
	*
	* Remove the `attachTo` option from the editor configuration and use
	* {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`}
	* (or `config.roots.<rootName>.element` for the multi-root editor) to specify the DOM element instead.
	*
	* @error editor-create-attachto-ignored
	*/
	throw new CKEditorError("editor-create-attachto-ignored", null);
	config.set("roots", rootsConfig);
}
/**
* Normalizes the parameters passed to the editor constructor when a single root is used. It supports both of the following signatures:
*
* ```ts
* new Editor( editorConfig: EditorConfig );
* new Editor( sourceElementOrData: HTMLElement | string, editorConfig: EditorConfig );
* ```
*
* @internal
*/
function normalizeSingleRootEditorConstructorParams(sourceElementOrDataOrConfig, editorConfig) {
	if (typeof sourceElementOrDataOrConfig === "string" || isElement$1(sourceElementOrDataOrConfig) || editorConfig && Object.keys(editorConfig).length) return {
		sourceElementOrData: sourceElementOrDataOrConfig,
		editorConfig
	};
	else return {
		sourceElementOrData: "",
		editorConfig: sourceElementOrDataOrConfig
	};
}
/**
* Normalizes the parameters passed to the editor constructor when a multi root is used. It supports both of the following signatures:
*
* ```ts
* new Editor( editorConfig: EditorConfig );
* new Editor( sourceElementsOrData: Record<string, string> | Record<string, HTMLElement>, editorConfig: EditorConfig );
* ```
*
* @internal
*/
function normalizeMultiRootEditorConstructorParams(sourceElementOrDataOrConfig, editorConfig) {
	if (editorConfig && Object.keys(editorConfig).length || Object.keys(sourceElementOrDataOrConfig).length == 0 || Object.values(sourceElementOrDataOrConfig).every((value) => typeof value === "string" || isElement$1(value))) return {
		sourceElementsOrData: sourceElementOrDataOrConfig,
		editorConfig
	};
	else return {
		sourceElementsOrData: {},
		editorConfig: sourceElementOrDataOrConfig
	};
}
/**
* Type guard to check if the provided value is a plain object containing source elements or data.
*/
function isSourceElementsOrDataRecord(sourceElementsOrData) {
	return !!sourceElementsOrData && typeof sourceElementsOrData == "object" && !Array.isArray(sourceElementsOrData) && !isElement$1(sourceElementsOrData);
}
/**
* Retrieve data from source element or return the string data as is.
*/
function getInitialData(sourceElementOrData) {
	return isElement$1(sourceElementOrData) ? getDataFromElement(sourceElementOrData) : sourceElementOrData;
}
/**
* Retrieve legacy configuration value for `initialData` from the config.
* Normalize single-root config so returned value is always an object with root names as keys.
*/
function getLegacyInitialData(config, sourceElementIsObject, defaultRootName) {
	return sourceElementIsObject || !defaultRootName ? config.get("initialData") || Object.create(null) : { [defaultRootName]: config.get("initialData") };
}
/**
* Retrieve legacy configuration value for `placeholder` or `label` from the config for a specific root.
*/
function getLegacyPlainConfigValue(config, key, rootName) {
	const legacyValue = config.get(key);
	if (legacyValue) return typeof legacyValue == "string" ? legacyValue : legacyValue[rootName];
}
/**
* Normalizes the value passed in {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`} into a
* canonical form (`HTMLElement` or {@link module:core/editor/editorconfig~ViewRootElementDefinition}) that the UI layer
* can consume directly.
*
* Accepts:
*
* * an existing `HTMLElement` - returned as is, so the editor wraps it in place,
* * a tag name string (e.g. `'h1'`) - turned into `{ name: 'h1' }`,
* * a {@link module:core/editor/editorconfig~ViewRootElementDefinition} object - reshaped so `classes` becomes an
* array and `attributes.class` is lifted into it.
*
* The `class` attribute may be passed either as the dedicated `classes` field (string or array of strings) or as a
* `class` key inside `attributes`. They are concatenated. The `style` attribute may be passed either as the `styles`
* object or as a `style` string inside `attributes`. When both are provided, the object form takes precedence and a
* warning is logged.
*
* The `<textarea>` tag name is rejected as it cannot host a rich-text editable.
*
* Already used internally by {@link ~normalizeRootsConfig `normalizeRootsConfig()`} for `config.root.element` /
* `config.roots.*.element`. Exported for callers that bypass the regular config-normalization pass, e.g.
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor#addRoot `MultiRootEditor.addRoot()`}
* and {@link module:editor-multi-root/multirooteditor~MultiRootEditor#createEditable `MultiRootEditor.createEditable()`}.
*
* @internal
*/
function normalizeViewRootElementDefinition(spec) {
	if (spec == null) return;
	if (isElement$1(spec)) {
		assertAllowedTagName(spec.tagName);
		return spec;
	}
	if (typeof spec === "string") {
		assertAllowedTagName(spec);
		return { name: spec };
	}
	const { name, classes, styles, attributes } = spec;
	if (name !== void 0) assertAllowedTagName(name);
	const { class: attrClass, style: attrStyle, ...restAttributes } = attributes || {};
	const hadAttrClass = !!attributes && "class" in attributes;
	const hasStyles = !!styles && Object.keys(styles).length > 0;
	const stylesOverrode = hasStyles && !!attrStyle;
	if (stylesOverrode)
 /**
	* Both the {@link module:core/editor/editorconfig~ViewRootElementDefinition#styles `styles`} object
	* and the {@link module:core/editor/editorconfig~ViewRootElementDefinition#attributes `attributes.style`}
	* string were provided in {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`}.
	* Provide one or the other - the object form takes precedence.
	*
	* @error editor-root-element-styles-overspecified
	*/
	logWarning("editor-root-element-styles-overspecified");
	const normalizedClasses = [...tokenizeClasses(classes), ...tokenizeClasses(attrClass)];
	const normalizedAttributes = { ...restAttributes };
	if (hadAttrClass) normalizedAttributes.class = "";
	if (stylesOverrode) normalizedAttributes.style = "";
	else if (attrStyle) normalizedAttributes.style = attrStyle;
	return {
		...name !== void 0 && { name },
		...normalizedClasses.length && { classes: normalizedClasses },
		...hasStyles && { styles },
		...Object.keys(normalizedAttributes).length && { attributes: normalizedAttributes }
	};
}
/**
* Splits a class value (string, array of strings, or `undefined`) into individual class tokens.
* Whitespace-separated tokens within a single string are split into separate array entries and empty entries are dropped.
*/
function tokenizeClasses(value) {
	if (!value) return [];
	return toArray(value).flatMap((token) => token.split(/\s+/)).filter(Boolean);
}
/**
* Throws when the given value cannot be used as an editable root tag name.
*
* Rejects values that are not a valid HTML tag name (non-strings, empty strings, names with whitespace,
* angle brackets, slashes, or other invalid characters) with `editor-wrong-element-name`.
*
* Also rejects the `<textarea>` and `<input>` tags with `editor-wrong-element` - they are form fields
* and cannot contain other HTML elements, so the editor cannot render rich-text content inside them.
*
* To fix the error, use a tag that can contain other elements - for example `'div'`, `'section'`, `'article'`,
* or a heading like `'h1'`. You can also omit the `name` field to use the default `'div'`.
*/
function assertAllowedTagName(name) {
	if (typeof name !== "string" || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(name))
 /**
	* The value supplied as the editable root tag name in
	* {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`} or
	* `config.roots.<rootName>.element` is not a valid HTML tag name.
	*
	* Valid HTML tag names start with a letter and contain only letters, digits, hyphens, or underscores
	* (e.g. `'div'`, `'h1'`, `'my-element'`; uppercase variants such as `'DIV'` and `'H1'` are also accepted).
	* Whitespace, angle brackets, slashes, empty strings, and non-string values are not allowed.
	*
	* @error editor-wrong-element-name
	*/
	throw new CKEditorError("editor-wrong-element-name", null, { name });
	if (["textarea", "input"].includes(name.toLowerCase()))
 /**
	* The DOM tag name specified in {@link module:core/editor/editorconfig~RootConfig#element `config.root.element`}
	* cannot be used as an editor's editable root. The `<textarea>` and `<input>` elements are form fields and
	* cannot contain other HTML elements, so the editor cannot render rich-text content inside them.
	*
	* To fix the error, use a tag that can contain other elements - for example `'div'`, `'section'`, `'article'`,
	* or a heading like `'h1'`. You can also omit the `name` field to use the default `'div'`.
	*
	* @error editor-wrong-element
	*/
	throw new CKEditorError("editor-wrong-element", null);
}
/**
* An alias for `isElement` from `es-toolkit/compat` with additional type guard.
*/
function isElement$1(value) {
	return isElement(value);
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/editor/utils/verifyrootelements
*/
/**
* Verifies that all model root elements are registered in the schema as limit elements.
*
* @internal
*/
function verifyRootElements(editor) {
	const schema = editor.model.schema;
	const document = editor.model.document;
	for (const root of document.roots) {
		if (root === document.graveyard || !root.isAttached()) continue;
		if (!schema.isLimit(root))
 /**
		* The model root element must be a {@link module:engine/model/schema~ModelSchemaItemDefinition#isLimit limit element}.
		* The element name specified in
		* {@link module:core/editor/editorconfig~RootConfig#modelElement `config.root.modelElement`}
		* (or `config.roots.<rootName>.modelElement`) must be registered in the schema
		* with `isLimit` set to `true`.
		*
		* @error editor-root-element-is-not-limit
		* @param rootName The name of the root that uses a non-limit element.
		* @param elementName The name of the model element used for the root.
		*/
		throw new CKEditorError("editor-root-element-is-not-limit", null, {
			rootName: root.rootName,
			elementName: root.name
		});
	}
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* Returns whether the model root with the given name accepts `$block` children according to the editor's schema.
*
* Call this once the plugin initialization phase is finished — typically during UI init or right after creating
* a new editable — so the schema reflects any plugin-registered root types or additional content rules.
*
* @internal
*/
function rootAcceptsBlocks(editor, rootName) {
	const root = editor.model.document.getRoot(rootName);
	return editor.model.schema.checkChild(root, "$block");
}

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module core/pendingactions
*/
/**
* The list of pending editor actions.
*
* This plugin should be used to synchronise plugins that execute long-lasting actions
* (e.g. file upload) with the editor integration. It gives the developer who integrates the editor
* an easy way to check if there are any actions pending whenever such information is needed.
* All plugins that register a pending action also provide a message about the action that is ongoing
* which can be displayed to the user. This lets them decide if they want to interrupt the action or wait.
*
* Adding and updating a pending action:
*
* ```ts
* const pendingActions = editor.plugins.get( 'PendingActions' );
* const action = pendingActions.add( 'Upload in progress: 0%.' );
*
* // You can update the message:
* action.message = 'Upload in progress: 10%.';
* ```
*
* Removing a pending action:
*
* ```ts
* const pendingActions = editor.plugins.get( 'PendingActions' );
* const action = pendingActions.add( 'Unsaved changes.' );
*
* pendingActions.remove( action );
* ```
*
* Getting pending actions:
*
* ```ts
* const pendingActions = editor.plugins.get( 'PendingActions' );
*
* const action1 = pendingActions.add( 'Action 1' );
* const action2 = pendingActions.add( 'Action 2' );
*
* pendingActions.first; // Returns action1
* Array.from( pendingActions ); // Returns [ action1, action2 ]
* ```
*
* This plugin is used by features like {@link module:upload/filerepository~FileRepository} to register their ongoing actions
* and by features like {@link module:autosave/autosave~Autosave} to detect whether there are any ongoing actions.
* Read more about saving the data in the
* {@glink getting-started/setup/getting-and-setting-data Saving and getting data} guide.
*/
var PendingActions = class extends ContextPlugin {
	/**
	* A list of pending actions.
	*/
	_actions;
	/**
	* @inheritDoc
	*/
	static get pluginName() {
		return "PendingActions";
	}
	/**
	* @inheritDoc
	*/
	static get isOfficialPlugin() {
		return true;
	}
	/**
	* @inheritDoc
	*/
	init() {
		this.set("hasAny", false);
		this._actions = new Collection({ idProperty: "_id" });
		this._actions.delegate("add", "remove").to(this);
	}
	/**
	* Adds an action to the list of pending actions.
	*
	* This method returns an action object with an observable message property.
	* The action object can be later used in the {@link #remove} method. It also allows you to change the message.
	*
	* @param message The action message.
	* @returns An observable object that represents a pending action.
	*/
	add(message) {
		if (typeof message !== "string")
 /**
		* The message must be a string.
		*
		* @error pendingactions-add-invalid-message
		*/
		throw new CKEditorError("pendingactions-add-invalid-message", this);
		const action = new (ObservableMixin())();
		action.set("message", message);
		this._actions.add(action);
		this.hasAny = true;
		return action;
	}
	/**
	* Removes an action from the list of pending actions.
	*
	* @param action An action object.
	*/
	remove(action) {
		this._actions.remove(action);
		this.hasAny = !!this._actions.length;
	}
	/**
	* Returns the first action from the list or null if the list is empty
	*
	* @returns The pending action object.
	*/
	get first() {
		return this._actions.get(0);
	}
	/**
	* Iterable interface.
	*/
	[Symbol.iterator]() {
		return this._actions[Symbol.iterator]();
	}
};

/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/

export { Accessibility, ActionsRecorder, Command, CommandCollection, Context, ContextPlugin, EditingKeystrokeHandler, Editor, ElementApiMixin, MultiCommand, PendingActions, Plugin, PluginCollection, DEFAULT_GROUP_ID as _DEFAULT_ACCESSIBILITY_GROUP_ID, getEditorUsageData as _getEditorUsageData, attachToForm, normalizeMultiRootEditorConstructorParams, normalizeRootsConfig, normalizeSingleRootEditorConstructorParams, normalizeViewRootElementDefinition, onEditorError, registerAndInitializeRootConfigAttributes, rootAcceptsBlocks, secureSourceElement, verifyRootElements };
//# sourceMappingURL=index.js.map