class CKEditorContextComponent extends HTMLElement {
static get observedAttributes() {
return ['plugins', 'config'];
}
/** @type {import('ckeditor5').Context|null} */
instance = null;
/** @type {Promise} */
instancePromise = Promise.withResolvers();
/** @type {Set} */
#connectedEditors = new Set();
async connectedCallback() {
try {
execIfDOMReady(() => this.#initializeContext());
} catch (error) {
console.error('Failed to initialize context:', error);
this.dispatchEvent(new CustomEvent('context-error', { detail: error }));
}
}
async attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== null && oldValue !== newValue) {
await this.#initializeContext();
}
}
async disconnectedCallback() {
if (this.instance) {
await this.instance.destroy();
this.instance = null;
}
}
/**
* Register editor component with this context
*
* @param {CKEditorComponent} editor
*/
registerEditor(editor) {
this.#connectedEditors.add(editor);
}
/**
* Unregister editor component from this context
*
* @param {CKEditorComponent} editor
*/
unregisterEditor(editor) {
this.#connectedEditors.delete(editor);
}
/**
* Initialize CKEditor context with shared configuration
*
* @private
*/
async #initializeContext() {
if (this.instance) {
this.instancePromise = Promise.withResolvers();
await this.instance.destroy();
this.instance = null;
}
// Broadcast context initialization event
window.dispatchEvent(
new CustomEvent('ckeditor:context:attach:before', { detail: { element: this } })
);
const { Context, ContextWatchdog } = await import('ckeditor5');
const plugins = await this.#getPlugins();
const config = this.#getConfig();
// Broadcast context mounting event with configuration
window.dispatchEvent(
new CustomEvent('ckeditor:context:attach', { detail: { config, element: this } })
);
this.instance = new ContextWatchdog(Context, {
crashNumberLimit: 10
});
await this.instance.create({
...config,
plugins
});
this.instance.on('itemError', (...args) => {
console.error('Context item error:', ...args);
});
this.instancePromise.resolve(this.instance);
this.dispatchEvent(new CustomEvent('context-ready', { detail: this.instance }));
// Reinitialize connected editors.
await Promise.all(
[...this.#connectedEditors].map(editor => editor.reinitializeEditor())
);
}
async #getPlugins() {
const raw = this.getAttribute('plugins');
return loadAsyncImports(raw ? JSON.parse(raw) : []);
}
/**
* Gets context configuration with resolved element references.
*
* @private
*/
#getConfig() {
const config = JSON.parse(this.getAttribute('config') || '{}');
return resolveElementReferences(config);
}
}
customElements.define('ckeditor-context-component', CKEditorContextComponent);