Current section
Files
Jump to
Current section
Files
dist/index.mjs
function E(n, t) {
let e = null;
return (...i) => {
e && clearTimeout(e), e = setTimeout(() => {
t(...i);
}, n);
};
}
class b {
/**
* The DOM element the hook is attached to.
* It includes an `instance` property to hold the hook instance.
*/
el;
/**
* The LiveView socket instance, providing connection to the server.
*/
liveSocket;
/**
* Pushes an event from the client to the LiveView server process.
* @param _event The name of the event.
* @param _payload The data to send with the event.
* @param _callback An optional function to be called with the server's reply.
*/
pushEvent;
/**
* Pushes an event to another hook on the page.
* @param _selector The CSS selector of the target element with the hook.
* @param _event The name of the event.
* @param _payload The data to send with the event.
* @param _callback An optional function to be called with the reply.
*/
pushEventTo;
/**
* Registers a handler for an event pushed from the server.
* @param _event The name of the event to handle.
* @param _callback The function to execute when the event is received.
*/
handleEvent;
}
function f(n) {
return {
/**
* The mounted lifecycle callback for the LiveView hook object.
* It creates an instance of the user-defined hook class and sets up the necessary properties and methods.
*/
mounted() {
const t = new n();
this.el.instance = t, t.el = this.el, t.liveSocket = this.liveSocket, t.pushEvent = (e, i, o) => this.pushEvent(e, i, o), t.pushEventTo = (e, i, o, r) => this.pushEventTo(e, i, o, r), t.handleEvent = (e, i) => this.handleEvent(e, i), t.mounted?.();
},
/**
* The beforeUpdate lifecycle callback that delegates to the hook instance.
*/
beforeUpdate() {
this.el.instance.beforeUpdate?.();
},
/**
* The destroyed lifecycle callback that delegates to the hook instance.
*/
destroyed() {
this.el.instance.destroyed?.();
},
/**
* The disconnected lifecycle callback that delegates to the hook instance.
*/
disconnected() {
this.el.instance.disconnected?.();
},
/**
* The reconnected lifecycle callback that delegates to the hook instance.
*/
reconnected() {
this.el.instance.reconnected?.();
}
};
}
function y(n, t) {
const e = Object.entries(n).map(([i, o]) => [i, t(o, i)]);
return Object.fromEntries(e);
}
function w(n) {
if (n === null)
return null;
const t = Number.parseInt(n, 10);
return Number.isNaN(t) ? null : t;
}
class l {
static the = new l();
/**
* Map of registered editors.
*/
editors = /* @__PURE__ */ new Map();
/**
* Map of callbacks that are waiting for an editor to be registered.
*/
callbacks = /* @__PURE__ */ new Map();
/**
* Private constructor to enforce singleton pattern.
*/
constructor() {
}
/**
* Executes a function on an editor.
* If the editor is not yet registered, it will wait for it to be registered.
*
* @param editorId The ID of the editor.
* @param fn The function to execute.
* @returns A promise that resolves with the result of the function.
*/
execute(t, e) {
const { callbacks: i, editors: o } = this, r = o.get(t);
return r ? Promise.resolve(e(r)) : new Promise((s) => {
const a = async (c) => s(await e(c));
this.callbacks.has(t) || i.set(t, []), i.set(t, [
...i.get(t),
a
]);
});
}
/**
* Registers an editor.
*
* @param editorId The ID of the editor.
* @param editor The editor instance.
*/
register(t, e) {
const { editors: i, callbacks: o } = this, r = o.get(t);
if (i.has(t))
throw new Error(`Editor with ID "${t}" is already registered.`);
i.set(t, e), r && (r.forEach((s) => s(e)), o.delete(t)), this.editors.size === 1 && this.register(null, e);
}
/**
* Un-registers an editor.
*
* @param editorId The ID of the editor.
*/
unregister(t) {
const { editors: e, callbacks: i } = this;
if (!e.has(t))
throw new Error(`Editor with ID "${t}" is not registered.`);
t && this.editors.get(null) === e.get(t) && this.unregister(null), e.delete(t), i.delete(t);
}
/**
* Gets all registered editors.
*/
getEditors() {
return Array.from(this.editors.values());
}
/**
* Checks if an editor with the given ID is registered.
*
* @param editorId The ID of the editor.
* @returns `true` if the editor is registered, `false` otherwise.
*/
hasEditor(t) {
return this.editors.has(t);
}
/**
* Gets a promise that resolves with the editor instance for the given ID.
* If the editor is not registered yet, it will wait for it to be registered.
*
* @param editorId The ID of the editor.
* @returns A promise that resolves with the editor instance.
*/
waitForEditor(t) {
return this.execute(t, (e) => e);
}
/**
* Destroys all registered editors and clears the registry.
* This will call the `destroy` method on each editor.
*/
async destroyAllEditors() {
const t = Array.from(this.editors.values()).map((e) => e.destroy());
this.editors.clear(), this.callbacks.clear(), await Promise.all(t);
}
}
class k extends b {
/**
* The name of the hook.
*/
mountedPromise = null;
/**
* Attributes for the editable instance.
*/
get attrs() {
const t = {
editableId: this.el.getAttribute("id"),
editorId: this.el.getAttribute("data-cke-editor-id") || null,
rootName: this.el.getAttribute("data-cke-editable-root-name"),
initialValue: this.el.getAttribute("data-cke-editable-initial-value") || ""
};
return Object.defineProperty(this, "attrs", {
value: t,
writable: !1,
configurable: !1,
enumerable: !0
}), t;
}
/**
* Mounts the editable component.
*/
async mounted() {
const { editableId: t, editorId: e, rootName: i, initialValue: o } = this.attrs, r = this.el.querySelector(`#${t}_input`);
this.mountedPromise = l.the.execute(e, (s) => {
const { ui: a, editing: c, model: h } = s;
if (h.document.getRoot(i))
return;
s.addRoot(i, {
isUndoable: !1,
data: o
});
const u = this.el.querySelector("[data-cke-editable-content]"), d = a.view.createEditable(i, u);
a.addEditable(d), c.view.forceRender(), r && v(r, s, i);
});
}
/**
* Destroys the editable component. Unmounts root from the editor.
*/
async destroyed() {
const { editorId: t, rootName: e } = this.attrs;
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, await l.the.execute(t, (i) => {
const o = i.model.document.getRoot(e);
o && (i.detachEditable(o), i.detachRoot(e, !1));
});
}
}
const P = f(k);
function v(n, t, e) {
const i = () => {
n.value = t.getData({ rootName: e });
};
t.model.document.on("change:data", E(100, i)), i();
}
function p(n) {
return ["inline", "classic", "balloon", "decoupled"].includes(n);
}
async function I(n) {
const t = await import("ckeditor5"), i = {
inline: t.InlineEditor,
balloon: t.BalloonEditor,
classic: t.ClassicEditor,
decoupled: t.DecoupledEditor,
multiroot: t.MultiRootEditor
}[n];
if (!i)
throw new Error(`Unsupported editor type: ${n}`);
return i;
}
async function A(n) {
const t = await import("ckeditor5");
let e = null;
const i = n.map(async (o) => {
const { [o]: r } = t;
if (r)
return r;
if (!e)
try {
e = await import("ckeditor5-premium-features");
} catch (a) {
console.error(`Failed to load premium package: ${a}`);
}
const { [o]: s } = e || {};
if (s)
return s;
throw new Error(`Plugin "${o}" not found in base or premium packages.`);
});
return Promise.all(i);
}
function m(n) {
const t = document.querySelectorAll(
[
`[data-cke-editor-id="${n}"][data-cke-editable-root-name]`,
"[data-cke-editable-root-name]:not([data-cke-editor-id])"
].join(", ")
);
return Array.from(t).reduce((e, i) => {
const o = i.getAttribute("data-cke-editable-root-name"), r = i.getAttribute("data-cke-editable-initial-value") || "", s = i.querySelector("[data-cke-editable-content]");
return !o || !s ? e : {
...e,
[o]: {
content: s,
initialValue: r
}
};
}, /* @__PURE__ */ Object.create({}));
}
const g = ["inline", "classic", "balloon", "decoupled", "multiroot"];
function H(n) {
const t = n.getAttribute("cke-preset");
if (!t)
throw new Error('CKEditor5 hook requires a "cke-preset" attribute on the element.');
const { type: e, config: i, license: o } = JSON.parse(t);
if (!e || !i || !o)
throw new Error('CKEditor5 hook configuration must include "editor", "config", and "license" properties.');
if (!g.includes(e))
throw new Error(`Invalid editor type: ${e}. Must be one of: ${g.join(", ")}.`);
return {
type: e,
config: i,
license: o
};
}
function V(n, t) {
const { editing: e } = n;
e.view.change((i) => {
i.setStyle("height", `${t}px`, e.view.document.getRoot());
});
}
class $ extends b {
/**
* The name of the hook.
*/
editorPromise = null;
/**
* Attributes for the editor instance.
*/
get attrs() {
const t = {
editorId: this.el.getAttribute("id"),
preset: H(this.el),
editableHeight: w(this.el.getAttribute("cke-editable-height"))
};
return Object.defineProperty(this, "attrs", {
value: t,
writable: !1,
configurable: !1,
enumerable: !0
}), t;
}
/**
* Mounts the editor component.
*/
async mounted() {
this.editorPromise = this.createEditor(), l.the.register(this.attrs.editorId, await this.editorPromise);
}
/**
* Destroys the editor instance when the component is destroyed.
* This is important to prevent memory leaks and ensure that the editor is properly cleaned up.
*/
async destroyed() {
this.el.style.display = "none", (await this.editorPromise)?.destroy(), this.editorPromise = null, l.the.unregister(this.attrs.editorId);
}
/**
* Creates the CKEditor instance.
*/
createEditor = async () => {
const { preset: t, editorId: e, editableHeight: i } = this.attrs, { type: o, license: r, config: { plugins: s, ...a } } = t, c = await I(o), h = S(e, o), u = await c.create(
h,
{
...a,
initialData: N(e, o),
licenseKey: r.key,
plugins: await A(s)
}
);
if (p(o)) {
const d = document.getElementById(`${e}_input`);
d && C(d, u), i && V(u, i);
}
return u;
};
}
function C(n, t) {
const e = () => {
n.value = t.getData();
};
t.model.document.on("change:data", E(250, e)), e();
}
function S(n, t) {
if (p(t))
return document.getElementById(`${n}_editor`);
const e = m(n);
return y(e, ({ content: i }) => i);
}
function N(n, t) {
if (t === "decoupled") {
const i = m(n).main?.initialValue;
if (i)
return i;
}
if (p(t))
return document.getElementById(n)?.getAttribute("cke-initial-value") || "";
const e = m(n);
return y(e, ({ initialValue: i }) => i);
}
const R = f($);
class T extends b {
/**
* The name of the hook.
*/
mountedPromise = null;
/**
* Attributes for the editable instance.
*/
get attrs() {
const t = {
editorId: this.el.getAttribute("data-cke-editor-id") || null,
name: this.el.getAttribute("data-cke-ui-part-name")
};
return Object.defineProperty(this, "attrs", {
value: t,
writable: !1,
configurable: !1,
enumerable: !0
}), t;
}
/**
* Mounts the editable component.
*/
async mounted() {
const { editorId: t, name: e } = this.attrs;
this.mountedPromise = l.the.execute(t, (i) => {
const { ui: o } = i, r = x(e), s = o.view[r];
if (!s) {
console.error(`Unknown UI part name: "${e}". Supported names are "toolbar" and "menubar".`);
return;
}
this.el.appendChild(s.element);
});
}
/**
* Destroys the editable component. Unmounts root from the editor.
*/
async destroyed() {
this.el.style.display = "none", await this.mountedPromise, this.mountedPromise = null, this.el.innerHTML = "";
}
}
function x(n) {
switch (n) {
case "toolbar":
return "toolbar";
case "menubar":
return "menuBarView";
default:
return null;
}
}
const O = f(T), U = {
CKEditor5: R,
CKEditable: P,
CKUIPart: O
};
export {
l as EditorsRegistry,
U as Hooks
};
//# sourceMappingURL=index.mjs.map