UNPKG

@ckeditor/ckeditor5-vue

Version:

Official Vue.js 3+ component for CKEditor 5 – the best browser-based rich text editor.

1,196 lines 51.4 kB
(function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("@ckeditor/ckeditor5-integrations-common"), require("lodash-es")) : typeof define === "function" && define.amd ? define([ "exports", "vue", "@ckeditor/ckeditor5-integrations-common", "lodash-es" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.CKEDITOR_VUE = {}, global.Vue, global.CKEDITOR_INTEGRATIONS_COMMON, global._)); })(this, function(exports, vue, _ckeditor_ckeditor5_integrations_common, lodash_es) { Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion vue = __toESM(vue, 1); //#region src/plugins/VueIntegrationUsageDataPlugin.ts /** * @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 */ /** * 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. */ var VueIntegrationUsageDataPlugin = (0, _ckeditor_ckeditor5_integrations_common.createIntegrationUsageDataPlugin)("vue", { version: "8.2.0", frameworkVersion: vue.version }); /** * Appends all integration plugins to the editor configuration. * * @param editorConfig The editor configuration. * @returns The editor configuration with all integration plugins appended. */ function appendUsageDataPluginToConfig(editorConfig) { /** * Do not modify the editor configuration if the editor is using a free license. */ if ((0, _ckeditor_ckeditor5_integrations_common.isCKEditorFreeLicense)(editorConfig.licenseKey)) return editorConfig; return (0, _ckeditor_ckeditor5_integrations_common.appendExtraPluginsToEditorConfig)(editorConfig, [VueIntegrationUsageDataPlugin]); } //#endregion //#region src/utils/cleanupOrphanEditorElements.ts /** * @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 */ /** * Removes all DOM elements injected by a specific CKEditor instance. * Call this before assigning a new instance (e.g. in the 'restart' watchdog handler), * because the watchdog does not clean up the previous editor's DOM on its own. */ function cleanupOrphanEditorElements(editor) { var _editor$ui, _editor$ui2, _editor$editing; const uiElement = (_editor$ui = editor.ui) === null || _editor$ui === void 0 ? void 0 : _editor$ui.element; if (uiElement === null || uiElement === void 0 ? void 0 : uiElement.isConnected) uiElement.remove(); const bodyCollectionContainer = (_editor$ui2 = editor.ui) === null || _editor$ui2 === void 0 || (_editor$ui2 = _editor$ui2.view) === null || _editor$ui2 === void 0 || (_editor$ui2 = _editor$ui2.body) === null || _editor$ui2 === void 0 ? void 0 : _editor$ui2._bodyCollectionContainer; if (bodyCollectionContainer === null || bodyCollectionContainer === void 0 ? void 0 : bodyCollectionContainer.isConnected) bodyCollectionContainer.remove(); const editingView = (_editor$editing = editor.editing) === null || _editor$editing === void 0 ? void 0 : _editor$editing.view; if (editingView) for (const domRoot of editingView.domRoots.values()) { if (!(domRoot instanceof HTMLElement)) continue; domRoot.removeAttribute("contenteditable"); domRoot.removeAttribute("role"); domRoot.removeAttribute("aria-label"); domRoot.removeAttribute("aria-multiline"); domRoot.removeAttribute("spellcheck"); domRoot.classList.remove("ck", "ck-content", "ck-editor__editable", "ck-rounded-corners", "ck-editor__editable_inline", "ck-blurred", "ck-focused"); } } //#endregion //#region src/utils/wrapWithWatchdogIfPresent.ts /** * @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 */ var EDITOR_WATCHDOG_SYMBOL = Symbol.for("vue-editor-watchdog"); /** * Returns an editor constructor optionally wrapped with EditorWatchdog. */ function resolveEditorConstructor(Editor, disableWatchdog, watchdogConfig) { return disableWatchdog ? Editor : wrapWithWatchdogIfPresent(Editor, watchdogConfig); } /** * `EditorWatchdog#create` method does not return editor instance (returns `undefined` instead). * This function wraps editor constructor with EditorWatchdog and returns fake constructor that * returns editor instance assigned to initialized watchdog. * * It stores watchdog instance in hidden symbol assigned to editor. It simplifies storing both * instances in component's state (it's no longer required to store them separately). * * @param Editor The Editor creator to wrap. * @param watchdogConfig Watchdog configuration. * @returns The Editor creator wrapped with a watchdog. */ function wrapWithWatchdogIfPresent(Editor, watchdogConfig) { const { EditorWatchdog } = Editor; if (!EditorWatchdog) return Editor; const watchdog = new EditorWatchdog(Editor, watchdogConfig); watchdog.setCreator(async (...args) => { const editor = await Editor.create(...args); editor[EDITOR_WATCHDOG_SYMBOL] = watchdog; return editor; }); return { ...Editor, editorName: Editor.editorName, create: async (...args) => { await watchdog.create(...args); return watchdog.editor; } }; } /** * Unwraps the EditorWatchdog from the editor instance. * * @param editor Editor with attached watchdog. */ function unwrapEditorWatchdog(editor) { var _editor$EDITOR_WATCHD; return (_editor$EDITOR_WATCHD = editor[EDITOR_WATCHDOG_SYMBOL]) !== null && _editor$EDITOR_WATCHD !== void 0 ? _editor$EDITOR_WATCHD : null; } /** * Attaches common EditorWatchdog runtime error handling. */ function attachEditorWatchdogErrorHandler(editor, { isUnmounted, onError }) { const watchdog = unwrapEditorWatchdog(editor); if (!watchdog) return null; watchdog.on("error", (_, { error, causesRestart }) => { if (isUnmounted()) return; onError({ error, causesRestart, watchdog, editor: watchdog.editor }); }); return watchdog; } /** * It destroys the editor watchdog if it is assigned to the editor. If it is not, the editor is destroyed. * * @param editor Editor with attached watchdog. */ async function destroyEditorWithWatchdog(editor) { const watchdog = unwrapEditorWatchdog(editor); if (watchdog) await watchdog.destroy(); else await editor.destroy(); } //#endregion //#region src/composables/useIsUnmounted.ts /** * @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 */ function useIsUnmounted() { const isUnmounted = (0, vue.ref)(false); (0, vue.onBeforeUnmount)(() => { isUnmounted.value = true; }); return isUnmounted; } //#endregion //#region src/composables/useEditorLifecycleEvents.ts /** * @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 */ /** * Hook that watches editor lifecycle events and maps them to Vue event emitters. */ function useEditorLifecycleEvents(instance, emit) { (0, vue.watch)(instance, (newInstance) => { /* istanbul ignore if -- @preserve - Defensive check, instance never becomes undefined. */ if (!newInstance) return; const { document } = newInstance.editing.view; document.on("focus", (evt) => emit("focus", evt, newInstance)); document.on("blur", (evt) => emit("blur", evt, newInstance)); emit("ready", newInstance); newInstance.once("destroy", () => { emit("destroy", newInstance); }); }, { flush: "post" }); } //#endregion //#region src/composables/useEditorVModel.ts /** * @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 */ var INPUT_EVENT_DEBOUNCE_WAIT$1 = 300; /** * Hook that synchronizes editor state with currently set vue model. */ function useEditorVModel({ disableTwoWayDataBinding, emit, instance, model }) { const lastEditorData = (0, vue.ref)(); const isUnmounted = useIsUnmounted(); /** * Updates the internal cache and emits Vue-compatible events. */ function assignEditorDataToModel(editor, evt = null) { const data = lastEditorData.value = editor.data.get(); emit("update:modelValue", data, evt, editor); emit("input", data, evt, editor); } (0, vue.watch)(model, (newModel) => { if (instance.value && newModel !== lastEditorData.value) instance.value.data.set(newModel); }); (0, vue.watch)(instance, (newInstance, _oldInstance, onCleanup) => { /* istanbul ignore if -- @preserve - Defensive check, instance never becomes undefined. */ if (!newInstance) return; const emitDebouncedInputEvent = (0, lodash_es.debounce)((evt) => { if ((0, vue.toValue)(disableTwoWayDataBinding) || isUnmounted.value) return; assignEditorDataToModel(newInstance, evt); }, INPUT_EVENT_DEBOUNCE_WAIT$1, { leading: true }); newInstance.model.document.on("change:data", emitDebouncedInputEvent); newInstance.once("destroy", () => { emitDebouncedInputEvent.cancel(); }); onCleanup(() => { emitDebouncedInputEvent.cancel(); }); }); return { lastEditorData, assignEditorDataToModel }; } //#endregion //#region src/composables/useEditorReadOnly.ts /** * @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 */ var INTEGRATION_READ_ONLY_LOCK_ID = "Lock from Vue integration (@ckeditor/ckeditor5-vue)"; /** * Hook that toggles readonly state on provided instance. */ function useEditorReadOnly(instance, disabled) { (0, vue.watchEffect)(() => { const editor = (0, vue.toValue)(instance); const isDisabled = !!(0, vue.toValue)(disabled); if (editor) toggleEditorReadOnly(editor, isDisabled); }, { flush: "sync" }); } /** * Toggles editor to readonly state. */ function toggleEditorReadOnly(editor, readOnly) { if (readOnly) editor.enableReadOnlyMode(INTEGRATION_READ_ONLY_LOCK_ID); else editor.disableReadOnlyMode(INTEGRATION_READ_ONLY_LOCK_ID); } //#endregion //#region src/composables/useEditorVersionCheck.ts /** * @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 */ /** * Hook that check if integration is compatible with installed version of the editor. */ function useEditorVersionCheck() { switch ((0, _ckeditor_ckeditor5_integrations_common.compareInstalledCKBaseVersion)("42.0.0")) { case null: console.warn("Cannot find the \"CKEDITOR_VERSION\" in the \"window\" scope."); break; case -1: console.warn("The <CKEditor> component requires using CKEditor 5 in version 42+ or nightly build."); break; } } //#endregion //#region src/utils/isClassicEditor.ts /** * @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 */ function isClassicEditor(Editor) { return !Editor.editorName || Editor.editorName === "ClassicEditor"; } //#endregion //#region src/composables/useEditorElementDefinition.ts /** * @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 */ /** * Picks editor element definition from config if provided. */ function useEditorElementDefinition({ Editor, config, defaultElementName }) { return (0, vue.computed)(() => { const _config = (0, vue.toValue)(config); if (!isClassicEditor((0, vue.toValue)(Editor))) { var _config$roots$main$el, _config$roots, _config$root; const customElementDefinition = (_config$roots$main$el = (_config$roots = _config.roots) === null || _config$roots === void 0 || (_config$roots = _config$roots.main) === null || _config$roots === void 0 ? void 0 : _config$roots.element) !== null && _config$roots$main$el !== void 0 ? _config$roots$main$el : (_config$root = _config.root) === null || _config$root === void 0 ? void 0 : _config$root.element; if (customElementDefinition) return customElementDefinition; } return (0, vue.toValue)(defaultElementName); }); } //#endregion //#region src/utils/normalizeEditorElementDefinition.ts /** * @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 an editor element definition into a structured object. * * @param definition The definition to normalize. * @returns A strictly typed object definition containing at least the element name. */ function normalizeEditorElementDefinition(definition) { if (typeof HTMLElement !== "undefined" && definition instanceof HTMLElement) throw new Error("An HTMLElement cannot be used as an editor element definition. Please pass a string or an object definition."); if (typeof definition !== "object" || definition === null) return { name: definition }; return definition; } //#endregion //#region src/DynamicElement.vue var DynamicElement_default = /* @__PURE__ */ (0, vue.defineComponent)({ __name: "DynamicElement", props: { definition: { default: null } }, setup(__props, { expose: __expose }) { const props = __props; const elementRef = (0, vue.ref)(); __expose({ elementRef }); const definition = (0, vue.computed)(() => { var _props$definition; return normalizeEditorElementDefinition((_props$definition = props.definition) !== null && _props$definition !== void 0 ? _props$definition : "div"); }); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(definition.value.name), (0, vue.mergeProps)({ ref_key: "elementRef", ref: elementRef }, definition.value.attributes, { class: definition.value.classes, style: definition.value.styles }), null, 16, ["class", "style"]); }; } }); //#endregion //#region src/Ckeditor.vue var Ckeditor_default = /* @__PURE__ */ (0, vue.defineComponent)({ name: "CKEditor", __name: "Ckeditor", props: /*@__PURE__*/ (0, vue.mergeModels)({ editor: {}, config: { default: () => ({}) }, disabled: { type: Boolean, default: false }, disableTwoWayDataBinding: { type: Boolean, default: false }, watchdogConfig: {}, disableWatchdog: { type: Boolean, default: false }, tagName: { default: "div" } }, { "modelValue": { type: String, default: "" }, "modelModifiers": {} }), emits: /*@__PURE__*/ (0, vue.mergeModels)([ "ready", "destroy", "blur", "focus", "input", "update:modelValue", "error" ], ["update:modelValue"]), setup(__props, { expose: __expose, emit: __emit }) { const model = (0, vue.useModel)(__props, "modelValue"); const props = __props; const emit = __emit; const currentInstance = (0, vue.getCurrentInstance)(); const hasErrorHandler = () => { var _currentInstance$vnod; return !!(currentInstance === null || currentInstance === void 0 || (_currentInstance$vnod = currentInstance.vnode.props) === null || _currentInstance$vnod === void 0 ? void 0 : _currentInstance$vnod.onError); }; const editorElementRef = (0, vue.ref)(); const instance = (0, vue.ref)(); const isUnmounted = useIsUnmounted(); const { lastEditorData, assignEditorDataToModel } = useEditorVModel({ disableTwoWayDataBinding: () => props.disableTwoWayDataBinding, model, emit, instance }); const elementDefinition = useEditorElementDefinition({ Editor: () => props.editor, config: () => props.config, defaultElementName: () => props.tagName }); useEditorVersionCheck(); useEditorLifecycleEvents(instance, emit); useEditorReadOnly(instance, () => props.disabled); __expose({ instance, lastEditorData }); (0, vue.onMounted)(async () => { const supports = (0, _ckeditor_ckeditor5_integrations_common.getInstalledCKBaseFeatures)(); let editorConfig = appendUsageDataPluginToConfig({ ...props.config }); let prevModelValue = model.value; if (model.value) editorConfig = (0, _ckeditor_ckeditor5_integrations_common.assignInitialDataToEditorConfig)(editorConfig, model.value, true); const Constructor = resolveEditorConstructor(props.editor, props.disableWatchdog, props.watchdogConfig); try { var _editorElementRef$val; const domElement = (_editorElementRef$val = editorElementRef.value) === null || _editorElementRef$val === void 0 ? void 0 : _editorElementRef$val.elementRef; if (!domElement) throw new Error("Editor element is not available. Make sure the component is mounted."); const editor = await (supports.elementConfigAttachment ? Constructor.create((0, _ckeditor_ckeditor5_integrations_common.assignElementToEditorConfig)(Constructor, domElement, editorConfig)) : Constructor.create(domElement, editorConfig)); if (isUnmounted.value) { await destroyEditorWithWatchdog(editor); return; } if (model.value !== prevModelValue) editor.data.set(model.value); const watchdog = attachEditorWatchdogErrorHandler(editor, { isUnmounted: () => isUnmounted.value, onError: ({ error, watchdog, editor, causesRestart }) => { if (!hasErrorHandler()) console.error(error); emit("error", error, { phase: "runtime", watchdog, editor, causesRestart }); } }); if (watchdog) watchdog.on("restart", () => { try { if (instance.value && isClassicEditor(Constructor)) cleanupOrphanEditorElements(instance.value); } catch (err) { console.error(err); } if (!isUnmounted.value) { instance.value = (0, vue.markRaw)(watchdog.editor); assignEditorDataToModel(instance.value); } }); instance.value = (0, vue.markRaw)(editor); } catch (error) { if (isUnmounted.value) return; if (!hasErrorHandler()) console.error(error); emit("error", error, { phase: "initialization" }); } }); (0, vue.onBeforeUnmount)(async () => { const editor = instance.value; if (!editor) return; instance.value = void 0; await destroyEditorWithWatchdog(editor); }); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createBlock)(DynamicElement_default, { ref_key: "editorElementRef", ref: editorElementRef, definition: (0, vue.unref)(elementDefinition) }, null, 8, ["definition"]); }; } }); //#endregion //#region src/CkeditorElement.vue var CkeditorElement_default = /* @__PURE__ */ (0, vue.defineComponent)({ name: "CkeditorElement", __name: "CkeditorElement", props: { editor: { default: null }, element: { default: "toolbar" } }, setup(__props) { const props = __props; const uiRef = (0, vue.ref)(); (0, vue.watchEffect)((onCleanup) => { var _editor$ui$view$menuB, _editor$ui$view$toolb; const editor = props.editor; const uiContainer = uiRef.value; if (!editor || !uiContainer) return; const uiElement = props.element === "menuBar" ? (_editor$ui$view$menuB = editor.ui.view.menuBarView) === null || _editor$ui$view$menuB === void 0 ? void 0 : _editor$ui$view$menuB.element : (_editor$ui$view$toolb = editor.ui.view.toolbar) === null || _editor$ui$view$toolb === void 0 ? void 0 : _editor$ui$view$toolb.element; if (!uiElement) return; uiContainer.appendChild(uiElement); onCleanup(() => { if (uiContainer.contains(uiElement)) uiContainer.removeChild(uiElement); }); }, { flush: "post" }); return (_ctx, _cache) => { return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", { ref_key: "uiRef", ref: uiRef }, null, 512); }; } }); //#endregion //#region src/multiroot/constants.ts /** * @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 */ var ROOT_EDITABLE_OPTIONS_ATTRIBUTE = "$rootEditableOptions"; //#endregion //#region src/multiroot/MultiRootEditorEditable.vue var MultiRootEditorEditable_default = /* @__PURE__ */ (0, vue.defineComponent)({ name: "CkeditorMultiRootEditable", __name: "MultiRootEditorEditable", props: { id: { default: void 0 }, rootName: {}, editor: { default: null }, editableOptions: { default: null } }, setup(__props) { const props = __props; const editorElementRef = (0, vue.ref)(); const root = (0, vue.computed)(() => { var _props$editor$model$d, _props$editor; return (_props$editor$model$d = (_props$editor = props.editor) === null || _props$editor === void 0 ? void 0 : _props$editor.model.document.getRoot(props.rootName)) !== null && _props$editor$model$d !== void 0 ? _props$editor$model$d : null; }); const rootEditableOptions = (0, vue.computed)(() => { var _props$editableOption; const currentRoot = root.value; if (!currentRoot) return null; return { ...(_props$editableOption = props.editableOptions) !== null && _props$editableOption !== void 0 ? _props$editableOption : currentRoot.getAttribute(ROOT_EDITABLE_OPTIONS_ATTRIBUTE) }; }); const elementDefinition = (0, vue.computed)(() => { var _options$element, _props$id; const options = rootEditableOptions.value; if (!options) return null; const normalizedDefinition = normalizeEditorElementDefinition((_options$element = options.element) !== null && _options$element !== void 0 ? _options$element : { name: "div" }); return { ...normalizedDefinition, attributes: { ...normalizedDefinition.attributes, id: (_props$id = props.id) !== null && _props$id !== void 0 ? _props$id : props.rootName } }; }); (0, vue.watchEffect)((onCleanup) => { var _editorElementRef$val; const editor = props.editor; const currentRoot = root.value; const options = rootEditableOptions.value; const element = (_editorElementRef$val = editorElementRef.value) === null || _editorElementRef$val === void 0 ? void 0 : _editorElementRef$val.elementRef; if (!editor || !currentRoot || !options || !element) return; if (editor.ui.getEditableElement(props.rootName)) editor.detachEditable(currentRoot); const editable = editor.ui.view.createEditable(props.rootName, element, options.label); editable.isInlineRoot = !editor.model.schema.checkChild(currentRoot, "$block"); editor.ui.addEditable(editable, options.placeholder); editor.editing.view.forceRender(); onCleanup(() => { if (editor.state === "destroyed") return; if (editor.model.document.getRoot(props.rootName) === currentRoot) editor.detachEditable(currentRoot); }); }, { flush: "post" }); return (_ctx, _cache) => { return elementDefinition.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(DynamicElement_default, { key: 0, ref_key: "editorElementRef", ref: editorElementRef, definition: elementDefinition.value }, null, 8, ["definition"])) : (0, vue.createCommentVNode)("", true); }; } }); //#endregion //#region src/multiroot/useMultiRootEditor.ts /** * @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 */ var INPUT_EVENT_DEBOUNCE_WAIT = 300; var EDITOR_DESTROYED_BEFORE_READY_MESSAGE = "The editor was destroyed before it became ready."; function useMultiRootEditor(options) { const isUnmounted = useIsUnmounted(); const instance = (0, vue.ref)(); const data = (0, vue.ref)(cloneData((0, vue.toValue)(options.data))); const rootsAttributes = (0, vue.ref)(normalizeRootsAttributes((0, vue.toValue)(options.rootsAttributes), data.value)); const roots = (0, vue.ref)(Object.keys(data.value)); const shouldUpdateEditor = (0, vue.ref)(false); const lastEditorData = (0, vue.ref)(); const lastEditorRootsAttributes = (0, vue.ref)(); let editorReadyCallbacks = []; useEditorReadOnly(instance, () => (0, vue.toValue)(options.disabled)); (0, vue.watch)(() => (0, vue.toValue)(options.data), (newData) => { if (instance.value && lastEditorData.value && areRecordsEqual(newData, lastEditorData.value)) return; setData(cloneData(newData)); }, { deep: true }); (0, vue.watch)(() => (0, vue.toValue)(options.rootsAttributes), (newRootsAttributes) => { const sourceRootsAttributes = cloneRootsAttributes(newRootsAttributes); const normalizedRootsAttributes = normalizeRootsAttributes(newRootsAttributes, data.value); const nextRootsAttributes = instance.value ? mergeRootsAttributes(rootsAttributes.value, normalizedRootsAttributes, data.value) : normalizedRootsAttributes; const shouldEmitNormalizedRootsAttributes = !!instance.value && !areRecordsEqual(nextRootsAttributes, sourceRootsAttributes); if (instance.value && lastEditorRootsAttributes.value && areRecordsEqual(nextRootsAttributes, lastEditorRootsAttributes.value)) { if (shouldEmitNormalizedRootsAttributes) { setRootsAttributes(nextRootsAttributes); emitRootsAttributes(null, instance.value); } return; } setRootsAttributes(nextRootsAttributes); if (shouldEmitNormalizedRootsAttributes) emitRootsAttributes(null, instance.value); }, { deep: true }); (0, vue.watch)([data, rootsAttributes], () => { const editor = instance.value; if (!editor || !shouldUpdateEditor.value) return; shouldUpdateEditor.value = false; syncEditorWithState(editor); }, { deep: true, flush: "post" }); (0, vue.watch)(instance, async (newInstance, _oldInstance, onCleanup) => { var _options$onReady; /* istanbul ignore if -- @preserve - Defensive check, instance can only be set to an editor here. */ if (!newInstance) return; let isCurrentInstance = true; const editor = newInstance; const modelDocument = editor.model.document; const viewDocument = editor.editing.view.document; const emitDebouncedDataUpdate = (0, lodash_es.debounce)((event) => { updateStateFromEditor(editor, event); }, INPUT_EVENT_DEBOUNCE_WAIT, { leading: true }); const onChangeDataListener = (event) => onChangeData(editor, event, emitDebouncedDataUpdate); const onAddRootListener = (event, root) => onAddRoot(editor, event, root); const onDetachRootListener = (event, root) => onDetachRoot(editor, event, root); const onFocusListener = (event) => { var _options$onFocus; return (_options$onFocus = options.onFocus) === null || _options$onFocus === void 0 ? void 0 : _options$onFocus.call(options, event, editor); }; const onBlurListener = (event) => { var _options$onBlur; return (_options$onBlur = options.onBlur) === null || _options$onBlur === void 0 ? void 0 : _options$onBlur.call(options, event, editor); }; const onDestroyListener = () => { var _options$onDestroy; emitDebouncedDataUpdate.cancel(); (_options$onDestroy = options.onDestroy) === null || _options$onDestroy === void 0 || _options$onDestroy.call(options, editor); }; modelDocument.on("change:data", onChangeDataListener); editor.on("addRoot", onAddRootListener); editor.on("detachRoot", onDetachRootListener); viewDocument.on("focus", onFocusListener); viewDocument.on("blur", onBlurListener); editor.once("destroy", onDestroyListener); onCleanup(() => { isCurrentInstance = false; modelDocument.off("change:data", onChangeDataListener); editor.off("addRoot", onAddRootListener); editor.off("detachRoot", onDetachRootListener); viewDocument.off("focus", onFocusListener); viewDocument.off("blur", onBlurListener); emitDebouncedDataUpdate.cancel(); }); await (0, vue.nextTick)(); /* istanbul ignore if -- @preserve - Instance replacement/unmount before deferred ready is a defensive race guard. */ if (!isCurrentInstance || isUnmounted.value || instance.value !== editor) return; (_options$onReady = options.onReady) === null || _options$onReady === void 0 || _options$onReady.call(options, editor); resolveEditorReadyCallbacks(editor); }, { flush: "post" }); (0, vue.onMounted)(initializeEditor); (0, vue.onBeforeUnmount)(async () => { const editor = instance.value; rejectEditorReadyCallbacks(/* @__PURE__ */ new Error(EDITOR_DESTROYED_BEFORE_READY_MESSAGE)); if (!editor) return; instance.value = void 0; forceAssignFakeEditableElements(editor); await destroyEditorWithWatchdog(editor); }); async function initializeEditor() { const creationData = cloneData(data.value); const creationRootsAttributes = cloneRootsAttributes(rootsAttributes.value); const Constructor = resolveEditorConstructor((0, vue.toValue)(options.editor), !!(0, vue.toValue)(options.disableWatchdog), (0, vue.toValue)(options.watchdogConfig)); try { const editor = await createEditor(Constructor, creationData, creationRootsAttributes); if (isUnmounted.value) { rejectEditorReadyCallbacks(/* @__PURE__ */ new Error(EDITOR_DESTROYED_BEFORE_READY_MESSAGE)); forceAssignFakeEditableElements(editor); await destroyEditorWithWatchdog(editor); return; } const watchdog = attachEditorWatchdogErrorHandler(editor, { isUnmounted: () => isUnmounted.value, onError: ({ error, watchdog, editor, causesRestart }) => { reportError(error, { phase: "runtime", watchdog, editor, causesRestart }); } }); if (watchdog) watchdog.on("restart", () => { try { /* istanbul ignore else -- @preserve - Restart is only handled after an editor instance is assigned. */ if (instance.value) cleanupOrphanEditorElements(instance.value); } catch (err) { console.error(err); } /* istanbul ignore if -- @preserve - Restart without an editor or after unmount is a watchdog edge case. */ if (isUnmounted.value || !watchdog.editor) return; const restartedEditor = watchdog.editor; instance.value = (0, vue.markRaw)(restartedEditor); syncStateFromEditor(restartedEditor); emitData(null, restartedEditor); emitRootsAttributes(null, restartedEditor); }); instance.value = (0, vue.markRaw)(editor); if (areRecordsEqual(data.value, creationData) && areRecordsEqual(rootsAttributes.value, creationRootsAttributes)) syncStateFromEditor(editor); else { syncEditorWithState(editor); syncStateFromEditor(editor); } } catch (error) { rejectEditorReadyCallbacks(error); /* istanbul ignore if -- @preserve - Initialization errors after unmount are intentionally ignored. */ if (isUnmounted.value) return; reportError(error, { phase: "initialization" }); } } async function createEditor(Constructor, initialData, initialRootsAttributes) { let editorConfig = (0, _ckeditor_ckeditor5_integrations_common.assignAttributesPropToMultiRootEditorConfig)(initialRootsAttributes, { ...(0, vue.toValue)(options.config) }); editorConfig = appendUsageDataPluginToConfig(editorConfig); const { initialData: mergedInitialData, ...mergedConfig } = (0, _ckeditor_ckeditor5_integrations_common.assignInitialDataToMultirootEditorConfig)(initialData, editorConfig); const supports = (0, _ckeditor_ckeditor5_integrations_common.getInstalledCKBaseFeatures)(); const Editor = Constructor; return await (supports.elementConfigAttachment ? Editor.create({ ...mergedConfig, initialData: mergedInitialData }) : Editor.create(mergedInitialData, mergedConfig)); } function syncEditorWithState(editor) { const desiredData = cloneData(data.value); const desiredRootsAttributes = normalizeRootsAttributes(rootsAttributes.value, desiredData); const editorData = editor.getFullData(); const editorRootsAttributes = normalizeRootsAttributes(editor.getRootsAttributes(), editorData); const { addedKeys: newRoots, removedKeys: removedRoots } = getRecordDiff(editorData, desiredData); const modifiedRoots = Object.keys(desiredData).filter((rootName) => editorData[rootName] !== void 0 && editorData[rootName] !== desiredData[rootName]); const rootsWithChangedAttributes = Object.keys(desiredRootsAttributes).filter((rootName) => editorData[rootName] !== void 0 && !areRecordsEqual(editorRootsAttributes[rootName], desiredRootsAttributes[rootName])); rootsAttributes.value = desiredRootsAttributes; editor.model.change((writer) => { handleNewRoots(editor, newRoots, desiredData, desiredRootsAttributes); handleRemovedRoots(editor, removedRoots); if (modifiedRoots.length) updateEditorData(editor, modifiedRoots, desiredData); if (rootsWithChangedAttributes.length) updateEditorAttributes(editor, writer, rootsWithChangedAttributes, desiredRootsAttributes); }); } function syncStateFromEditor(editor) { data.value = cloneData(editor.getFullData()); rootsAttributes.value = normalizeRootsAttributes(editor.getRootsAttributes(), data.value); roots.value = Object.keys(data.value); } function onChangeData(editor, event, emitDebouncedDataUpdate) { var _options$onChange; emitDebouncedDataUpdate(event); (_options$onChange = options.onChange) === null || _options$onChange === void 0 || _options$onChange.call(options, event, editor); } function updateStateFromEditor(editor, event) { if ((0, vue.toValue)(options.disableTwoWayDataBinding) || isUnmounted.value) return; const editorData = cloneData(editor.getFullData()); const editorRootsAttributes = normalizeRootsAttributes(editor.getRootsAttributes(), editorData); const shouldEmitData = !areRecordsEqual(data.value, editorData); const shouldEmitRootsAttributes = !areRecordsEqual(rootsAttributes.value, editorRootsAttributes); data.value = editorData; rootsAttributes.value = editorRootsAttributes; roots.value = Object.keys(editorData); lastEditorData.value = cloneData(editorData); lastEditorRootsAttributes.value = cloneRootsAttributes(editorRootsAttributes); if (shouldEmitData) emitData(event, editor); if (shouldEmitRootsAttributes) emitRootsAttributes(event, editor); } function onAddRoot(editor, event, root) { const rootName = root.rootName; if (!(0, vue.toValue)(options.disableTwoWayDataBinding)) { data.value = { ...data.value, [rootName]: editor.getData({ rootName }) }; rootsAttributes.value = normalizeRootsAttributes({ ...rootsAttributes.value, [rootName]: editor.getRootAttributes(rootName) }, data.value); emitData(event, editor); emitRootsAttributes(event, editor); } roots.value = unique([...roots.value, rootName]); } function onDetachRoot(editor, event, root) { const rootName = root.rootName; if (!(0, vue.toValue)(options.disableTwoWayDataBinding)) { const newData = { ...data.value }; const newRootsAttributes = { ...rootsAttributes.value }; delete newData[rootName]; delete newRootsAttributes[rootName]; data.value = newData; rootsAttributes.value = newRootsAttributes; emitData(event, editor); emitRootsAttributes(event, editor); } roots.value = roots.value.filter((currentRootName) => currentRootName !== rootName); } function emitData(event, editor) { var _options$onUpdateData; lastEditorData.value = cloneData(data.value); (_options$onUpdateData = options.onUpdateData) === null || _options$onUpdateData === void 0 || _options$onUpdateData.call(options, cloneData(data.value), event, editor); } function emitRootsAttributes(event, editor) { var _options$onUpdateRoot; lastEditorRootsAttributes.value = cloneRootsAttributes(rootsAttributes.value); (_options$onUpdateRoot = options.onUpdateRootsAttributes) === null || _options$onUpdateRoot === void 0 || _options$onUpdateRoot.call(options, cloneRootsAttributes(rootsAttributes.value), event, editor); } function reportError(error, description) { /* istanbul ignore else -- @preserve - The Vue component always provides an error callback. */ if (options.onError) options.onError(error, description); else console.error(error); } function setData(newData) { shouldUpdateEditor.value = true; data.value = cloneData(newData); rootsAttributes.value = normalizeRootsAttributes(rootsAttributes.value, data.value); if (!instance.value) roots.value = Object.keys(data.value); } function setRootsAttributes(newRootsAttributes) { shouldUpdateEditor.value = true; rootsAttributes.value = normalizeRootsAttributes(newRootsAttributes, data.value); } async function addRoot({ name, data: rootData = "", attributes = {}, editableOptions, ...rootOptions }) { const editor = await waitForEditor(); const supports = (0, _ckeditor_ckeditor5_integrations_common.getInstalledCKBaseFeatures)(); editor.model.change(() => { const mappedAttributes = { ...attributes, ...editableOptions && { ["$rootEditableOptions"]: editableOptions } }; for (const key of Object.keys(mappedAttributes)) editor.registerRootAttribute(key); let options = { isUndoable: true, ...rootOptions }; if (supports.rootsConfigEntry) options = { ...options, initialData: rootData, modelAttributes: mappedAttributes }; else options = { ...options, data: rootData, attributes: mappedAttributes }; editor.addRoot(name, options); }); } async function removeRoot(name) { const editor = await waitForEditor(); editor.model.change(() => { editor.detachRoot(name, true); }); } function waitForEditor() { if (instance.value) return Promise.resolve(instance.value); if (isUnmounted.value) return Promise.reject(/* @__PURE__ */ new Error(EDITOR_DESTROYED_BEFORE_READY_MESSAGE)); return new Promise((resolve, reject) => { editorReadyCallbacks.push({ resolve, reject }); }); } function resolveEditorReadyCallbacks(editor) { editorReadyCallbacks.forEach(({ resolve }) => resolve(editor)); editorReadyCallbacks = []; } function rejectEditorReadyCallbacks(error) { editorReadyCallbacks.forEach(({ reject }) => reject(error)); editorReadyCallbacks = []; } return { instance, roots, data, rootsAttributes, setData, setRootsAttributes, addRoot, removeRoot }; } function handleNewRoots(editor, rootNames, data, rootsAttributes) { const supports = (0, _ckeditor_ckeditor5_integrations_common.getInstalledCKBaseFeatures)(); for (const rootName of rootNames) { const rootAttributes = { ...editor.model.document.getRoot(rootName) && editor.getRootAttributes(rootName), ...rootsAttributes[rootName] }; const rootData = data[rootName]; for (const key of Object.keys(rootAttributes)) editor.registerRootAttribute(key); let options = { isUndoable: true }; if (supports.rootsConfigEntry) options = { ...options, initialData: rootData, modelAttributes: rootAttributes }; else options = { ...options, data: rootData, attributes: rootAttributes }; editor.addRoot(rootName, options); } } function handleRemovedRoots(editor, rootNames) { for (const rootName of rootNames) editor.detachRoot(rootName, true); } function updateEditorData(editor, rootNames, data) { const dataToUpdate = rootNames.reduce((result, rootName) => ({ ...result, [rootName]: data[rootName] }), Object.create(null)); editor.data.set(dataToUpdate, { suppressErrorInCollaboration: true }); } function updateEditorAttributes(editor, writer, rootNames, rootsAttributes) { for (const rootName of rootNames) { const rootAttributes = rootsAttributes[rootName]; for (const key of Object.keys(rootAttributes)) editor.registerRootAttribute(key); const root = editor.model.document.getRoot(rootName); /* istanbul ignore if -- @preserve - Attribute updates are only requested for existing roots. */ if (!root) continue; writer.setAttributes(rootAttributes, root); } } function forceAssignFakeEditableElements(editor) { const initializeEditableWithFakeElement = (editable) => { if (editable.name && !editor.editing.view.getDomRoot(editable.name)) editor.editing.view.attachDomRoot(document.createElement("div"), editable.name); }; Object.values(editor.ui.view.editables).forEach(initializeEditableWithFakeElement); } function normalizeRootsAttributes(rootsAttributes, data) { /* istanbul ignore next -- @preserve - Direct composable usage may omit roots attributes. */ const source = rootsAttributes !== null && rootsAttributes !== void 0 ? rootsAttributes : {}; return Object.keys(data).reduce((result, rootName) => { result[rootName] = { ...source[rootName] }; return result; }, Object.create(null)); } function mergeRootsAttributes(previousRootsAttributes, nextRootsAttributes, data) { return Object.keys(data).reduce((result, rootName) => { result[rootName] = { ...previousRootsAttributes[rootName], ...nextRootsAttributes[rootName] }; return result; }, Object.create(null)); } function cloneData(data) { return { ...data }; } function cloneRootsAttributes(rootsAttributes) { /* istanbul ignore next -- @preserve - Direct composable usage may omit roots attributes. */ return Object.keys(rootsAttributes !== null && rootsAttributes !== void 0 ? rootsAttributes : {}).reduce((result, rootName) => { result[rootName] = { ...rootsAttributes[rootName] }; return result; }, Object.create(null)); } function getRecordDiff(previousState, newState) { const previousStateKeys = Object.keys(previousState); const newStateKeys = Object.keys(newState); return { addedKeys: newStateKeys.filter((key) => !previousStateKeys.includes(key)), removedKeys: previousStateKeys.filter((key) => !newStateKeys.includes(key)) }; } function areRecordsEqual(first, second) { return JSON.stringify(first) === JSON.stringify(second); } function unique(values) { return [...new Set(values)]; } //#endregion //#region src/CkeditorMultiRoot.vue var CkeditorMultiRoot_default = /* @__PURE__ */ (0, vue.defineComponent)({ name: "CkeditorMultiRoot", __name: "CkeditorMultiRoot", props: /*@__PURE__*/ (0, vue.mergeModels)({ editor: {}, config: { default: () => ({}) }, disabled: { type: Boolean, default: false }, disableTwoWayDataBinding: { type: Boolean, default: false }, watchdogConfig: {}, disableWatchdog: { type: Boolean, default: false } }, { "modelValue": { default: () => ({}) }, "modelModifiers": {}, "rootsAttributes": { default: () => ({}) }, "rootsAttributesModifiers": {} }), emits: /*@__PURE__*/ (0, vue.mergeModels)([ "ready", "destroy", "blur", "focus", "input", "update:modelValue", "update:rootsAttributes", "error", "change" ], ["update:modelValue", "update:rootsAttributes"]), setup(__props, { expose: __expose, emit: __emit }) { const model = (0, vue.useModel)(__props, "modelValue"); const rootsAttributesModel = (0, vue.useModel)(__props, "rootsAttributes"); const props = __props; const emit = __emit; const currentInstance = (0, vue.getCurrentInstance)(); const hasErrorHandler = () => { var _currentInstance$vnod; return !!(currentInstance === null || currentInstance === void 0 || (_currentInstance$vnod = currentInstance.vnode.props) === null || _currentInstance$vnod === void 0 ? void 0 : _currentInstance$vnod.onError); }; const { instance, roots, data, rootsAttributes, addRoot, removeRoot } = useMultiRootEditor({ editor: () => props.editor, config: () => props.config, data: model, rootsAttributes: rootsAttributesModel, disabled: () => props.disabled, disableWatchdog: () => props.disableWatchdog, disableTwoWayDataBinding: () => props.disableTwoWayDataBinding, watchdogConfig: () => props.watchdogConfig, onReady: (editor) => emit("ready", editor), onDestroy: (editor) => emit("destroy", editor), onFocus: (event, editor) => emit("focus", event, editor), onBlur: (event, editor) => emit("blur", event, editor), onChange: (event, editor) => emit("change", event, editor), onUpdateData: (data, event, editor) => { emit("update:modelValue", data, event, editor); emit("input", data, event, editor); }, onUpdateRootsAttributes: (rootsAttributes, event, editor) => { emit("update:rootsAttributes", rootsAttributes, event, editor); }, onError: (error, description) => { if (!hasErrorHandler()) console.error(error); emit("error", error, description); } }); __expose({ instance, roots, data, rootsAttributes, addRoot, removeRoot }); return (_ctx, _cache) => { return (0, vue.renderSlot)(_ctx.$slots, "default", { editor: (0, vue.unref)(instance), roots: (0, vue.unref)(roots), data: (0, vue.unref)(data), attributes: (0, vue.unref)(rootsAttributes), rootsAttributes: (0, vue.unref)(rootsAttributes), addRoot: (0, vue.unref)(addRoot), removeRoot: (0, vue.unref)(removeRoot) }, () => [ (0, vue.createVNode)(CkeditorElement_default, { editor: (0, vue.unref)(instance), element: "menuBar" }, null, 8, ["editor"]), (0, vue.createVNode)(CkeditorElement_default, { editor: (0, vue.unref)(instance) }, null, 8, ["editor"]), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)((0, vue.unref)(roots), (rootName) => { return (0, vue.openBlock)(), (0, vue.createBlock)(MultiRootEditorEditable_default, { id: rootName, key: rootName, "root-name": rootName, editor: (0, vue.unref)(instance) }, null, 8, [ "id", "root-name", "editor" ]); }), 128)) ]); }; } }); //#endregion //#region src/composables/useAsync.ts /** * @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 composable that executes an async function and provides the result. * * @param asyncFunc The async function to execute. * @returns The result of the async function. * @example * * ```ts * const { loading, data, error } = useAsync( async () => { * const response = await fetch( 'https://api.example.com/data' ); * return response.json(); * } ); * ``` */ var useAsync = (asyncFunc) => { const lastQueryUUID = (0, vue.ref)(null); const error = (0, vue.ref)(null); const data = (0, vue.ref)(null); const loading = (0, vue.computed)(() => lastQueryUUID.value !== null); (0, vue.watchEffect)(async () => { const currentQueryUID = (0, _ckeditor_ckeditor5_integrations_common.uid)(); lastQueryUUID.value = currentQueryUID; data.value = null; error.value = null; const shouldDiscardQuery = () => lastQueryUUID.value !== currentQueryUID; try { const result = await asyncFunc(); if (!shouldDiscardQuery()) data.value = result; } catch (err) { console.error(err); if (!shouldDiscardQuery()) error.value = err; } finally { if (!shouldDiscardQuery()) lastQueryUUID.value = null; } }); return { loading: (0, vue.shallowReadonly)(loading), data: (0, vue.shallowReadonly)(data), error: (0, vue.shallowReadonly)(error) }; }; //#endregion //#region src/useCKEditorCloud.ts /** * @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 composable function that loads CKEditor Cloud services. * * @param config The configuration of the CKEditor Cloud services. * @returns The result of the loaded CKEditor Cloud services. * @template Config The type of the CKEditor Cloud configuration. * @example * ```ts * const { data } = useCKEditorCloud( { * version: '43.0.0', * languages: [ 'en', 'de' ], * premium: true * } ); * * if ( data.value ) { * const { CKEditor, CKEditorPremiumFeatures } = data.value; * const { Paragraph } = CKEditor; * * // .. * } */ function useCKEditorCloud(config) { return useAsync(() => (0, _ckeditor_ckeditor5_integrations_common.loadCKEditorCloud)((0, vue.toValue)(config))); } //#endregion //#region src/plugin.ts /** * @license Copyright (c) 2003-2026, CKSource Holdi