jolt-ui
Version:
A web components based SPA framework
1,411 lines • 123 kB
JavaScript
var Kt = Object.defineProperty;
var St = (l) => {
throw TypeError(l);
};
var en = (l, r, e) => r in l ? Kt(l, r, { enumerable: !0, configurable: !0, writable: !0, value: e }) : l[r] = e;
var p = (l, r, e) => en(l, typeof r != "symbol" ? r + "" : r, e), Et = (l, r, e) => r.has(l) || St("Cannot " + e);
var d = (l, r, e) => (Et(l, r, "read from private field"), e ? e.call(l) : r.get(l)), w = (l, r, e) => r.has(l) ? St("Cannot add the same private member more than once") : r instanceof WeakSet ? r.add(l) : r.set(l, e), U = (l, r, e, a) => (Et(l, r, "write to private field"), a ? a.call(l, e) : r.set(l, e), e), je = (l, r, e) => (Et(l, r, "access private method"), e);
class bn {
/**
* Constructor for authenticator for application
* @param {AuthenticatorConfigs} configs
*/
constructor({ redirect: r, redirectCallback: e, dataField: a, app: o }) {
/**
* @type {Array<string>}
*/
p(this, "_authenticatedUserRoles");
/**
* Sets authenticated user and roles to authenticator AND application data
* @param {Object} configs
* @param {Object} configs.user - user object
* @param {Array<string>} [configs.roles] - array of strings with user roles. default = []
*/
p(this, "setAuthenticatedUser", ({ user: r, roles: e }) => {
this._authenticatedUser = r, e && (this._authenticatedUserRoles = e), this._dataField && this.app.setData(this._dataField, r);
});
/**
* Sets new user roles
* @param {Array<string>} roles
*/
p(this, "setUserRoles", (r) => {
this._authenticatedUserRoles = r;
});
/**
* Removes authenticated user and their roles
*/
p(this, "removeAuthenticatedUser", () => {
var r, e, a;
this._authenticatedUser = null, this._authenticatedUserRoles = null, (a = (e = (r = this.app) == null ? void 0 : r.router) == null ? void 0 : e.currentRoute) != null && a.authenticationRequired && this.app.router.redirect(this._redirect), this._dataField && this.app.removeData(this._dataField);
});
/**
* Checks if user roles contains any of the required roles
* @param {Array<string>} rolesRequired
* @returns {boolean}
*/
p(this, "hasRole", (r = []) => {
if (!this._authenticatedUser)
return !1;
if (r.length == 0)
return !0;
if (!this._authenticatedUserRoles)
return !1;
const e = new Set(this._authenticatedUserRoles);
return r.some((a) => e.has(a));
});
/**
* Returns all roles of current authenticated user
* @returns {Array<string>|null}
*/
p(this, "getRoles", () => this._authenticatedUserRoles);
/**
* Returns current authenticated user
* @returns {Object<string, any>}
*/
p(this, "getUser", () => this._authenticatedUser);
/**
* Checks if user is authenticated
* @returns {boolean}
*/
p(this, "isAuthenticated", () => !!this._authenticatedUser);
/**
* Performs unauthorized redirect
* @async
*/
p(this, "unauthorizedRedirect", async () => {
await this.app.router.redirect(this.redirect);
});
if (!r || !o)
throw new Error("Missing application instance or redirect route in Authenticator constructor");
this._app = o, this._redirect = r, this._redirectCallback = e, this._authenticatedUser = null, this._authenticatedUserRoles = null, this._dataField = a;
}
/**
* Getter for redirect callback
* @returns {CallableFunction}
*/
get redirectCallback() {
return this._redirectCallback;
}
/**
* Getter for application object on authenticator instance
* @returns {App}
*/
get app() {
return this._app;
}
/**
* Redirect location
* @returns {string}
*/
get redirect() {
return this._redirect;
}
}
var ee = {
name: "doT",
version: "1.1.1",
templateSettings: {
evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g,
interpolate: /\{\{=([\s\S]+?)\}\}/g,
encode: /\{\{!([\s\S]+?)\}\}/g,
use: /\{\{#([\s\S]+?)\}\}/g,
useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
defineParams: /^\s*([\w$]+):([\s\S]+)/,
conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
varname: "it",
strip: !0,
append: !0,
selfcontained: !1,
doNotSkipEncoded: !1
},
template: void 0,
//fn, compile template
compile: void 0,
//fn, for express
log: !0
};
ee.encodeHTMLSource = function(l) {
var r = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" }, e = l ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g;
return function(a) {
return a ? a.toString().replace(e, function(o) {
return r[o] || o;
}) : "";
};
};
var Nt = {
append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" },
split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" }
}, Z = /$^/;
function Lt(l, r, e) {
return (typeof r == "string" ? r : r.toString()).replace(l.define || Z, function(a, o, h, _) {
return o.indexOf("def.") === 0 && (o = o.substring(4)), o in e || (h === ":" ? (l.defineParams && _.replace(l.defineParams, function(g, b, y) {
e[o] = { arg: b, text: y };
}), o in e || (e[o] = _)) : new Function("def", "def['" + o + "']=" + _)(e)), "";
}).replace(l.use || Z, function(a, o) {
l.useParams && (o = o.replace(l.useParams, function(_, g, b, y) {
if (e[b] && e[b].arg && y) {
var R = (b + ":" + y).replace(/'|\\/g, "_");
return e.__exp = e.__exp || {}, e.__exp[R] = e[b].text.replace(new RegExp("(^|[^\\w$])" + e[b].arg + "([^\\w$])", "g"), "$1" + y + "$2"), g + "def.__exp['" + R + "']";
}
}));
var h = new Function("def", "return " + o)(e);
return h && Lt(l, h, e);
});
}
function oe(l) {
return l.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ");
}
ee.template = function(l, r, e) {
r = r || ee.templateSettings;
var a = r.append ? Nt.append : Nt.split, o, h = 0, _;
l = r.use || r.define ? Lt(r, l, e || {}) : l, l = l.replace(/<([a-zA-Z0-9\-]+)([^>]*)\sdata-bind="([^"]+)"([^>]*)>/g, (b, y, R, L, N) => {
var S = this.generateHash(), O = `<${y}${R} data-bind="${Ee(L)}" data-bind-id="${S}"${N}>`;
return O;
});
const g = document.createElement("div");
this.app._originalInnerHTML.call(g, l), g.querySelectorAll("[data-bind]").forEach((b) => {
const y = b.getAttribute("data-bind"), R = b.getAttribute("data-bind-id"), L = b.innerHTML;
r.dataBinds.has(y) || r.dataBinds.set(y, {});
const N = document.createElement("textarea");
N.innerHTML = L;
const S = r.dataBinds.get(y);
S[R] = N.value, r.dataBinds.set(y, S);
}), l = ("var out='" + (r.strip ? l.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g, " ").replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g, "") : l).replace(/'|\\/g, "\\$&").replace(r.interpolate || Z, function(b, y) {
return a.start + oe(y) + a.end;
}).replace(r.encode || Z, function(b, y) {
return o = !0, a.startencode + oe(y) + a.end;
}).replace(r.conditional || Z, function(b, y, R) {
return y ? R ? "';}else if(" + oe(R) + "){out+='" : "';}else{out+='" : R ? "';if(" + oe(R) + "){out+='" : "';}out+='";
}).replace(r.iterate || Z, function(b, y, R, L) {
return y ? (h += 1, _ = L || "i" + h, y = oe(y), "';var arr" + h + "=" + y + ";if(arr" + h + "){var " + R + "," + _ + "=-1,l" + h + "=arr" + h + ".length-1;while(" + _ + "<l" + h + "){" + R + "=arr" + h + "[" + _ + "+=1];out+='") : "';} } out+='";
}).replace(r.evaluate || Z, function(b, y) {
return "';" + oe(y) + "out+='";
}) + "';return out;").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").replace(/(\s|;|\}|^|\{)out\+='';/g, "$1").replace(/\+''/g, ""), o && (l = "var encodeHTML = " + ee.encodeHTMLSource.toString() + "(" + (r.doNotSkipEncoded || "") + ");" + l);
try {
return new Function(r.varname, l);
} catch (b) {
throw typeof console < "u" && console.log("Could not create a template function: " + l), b;
}
};
ee.compile = function(l, r) {
return ee.template(l, null, r);
};
const tn = {
evaluate: /\{\{([\s\S]+?)\}\}/g,
interpolate: /\{\{=([\s\S]+?)\}\}/g,
encode: /\{\{!([\s\S]+?)\}\}/g,
use: /\{\{#([\s\S]+?)\}\}/g,
define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
varname: "it",
strip: !0,
append: !0,
selfcontained: !1,
dataBinds: /* @__PURE__ */ new Map(),
def: {}
};
function nn(l = 1e4, r = 1) {
return Math.floor(Math.random() * (l - r + 1)) + r;
}
function En(l) {
return function(e) {
const a = window.structuredClone(l);
return [void 0, null].includes(a) || (this._values[e] = a), {
/**
* @this {CustomElement}
* @returns {any}
*/
get() {
return this._values[e];
},
/**
* @this {CustomElement}
* @param {any} value
*/
set(o) {
this._values[e] = o, this._refreshBoundElements(Ee(e));
}
};
};
}
function Vn(l) {
if ([void 0, null, ""].includes(l))
throw new Error("Missing or invalid query selector for querySelector getter factory");
return function() {
const e = l;
return {
/**
* Gets the first matching element within the current context.
* @this {CustomElement}
* @returns {Element|null} The first matching element or `null` if none is found.
*/
get() {
return this.querySelector(e);
}
};
};
}
function An(l) {
if ([void 0, null, ""].includes(l))
throw new Error("Missing or invalid query selector for querySelector getter factory");
return function() {
const e = l;
return {
/**
* Gets all matching elements within the current context.
* @this {CustomElement}
* @returns {NodeListOf<Element>}
*/
get() {
return this.querySelectorAll(e);
}
};
};
}
const Dt = String.raw, an = String.raw;
var Ve, ce, Ue, he, Q, Ae, Be, xe, We, ze, de, z, Qe, Ie, Ge, Je, Ye, Ze, Xe, Ke, et, tt, fe, pe, Ce, nt, at;
const rt = class rt extends HTMLElement {
constructor() {
super();
p(this, "_startInitilization", () => {
this.resolveInitialization = null, this.initComplete = new Promise((e) => {
this.resolveInitialization = e;
});
});
/**
* @type {Promise<void>}
*/
p(this, "initComplete");
/**
* Component methods
* @type {Object<string, CallableFunction}
*/
p(this, "_methods");
/**
* Component markup method
* @type {CallableFunction}
* @async
*/
p(this, "markup");
/**
* Component css method
* @type {Object<string, CallableFunction|string>}
*/
p(this, "css");
/**
* If the style should be scoped or not
* @type {boolean}
*/
w(this, Ve);
/**
* CSS style string
* @type {string}
*/
w(this, ce);
/**
* Methods that should run before initialization
* @type {Object<string, CallableFunction>}
*/
p(this, "_beforeInit");
/**
* Methods that should run after initialization
* @type {Object<string, CallableFunction>}
*/
p(this, "_beforeInitResolve");
/**
* Methods that should run after initialization
* @type {Object<string, CallableFunction>}
*/
p(this, "_afterInit");
/**
* Methods that should run before rerender
* @type {Object<string, CallableFunction>}
*/
p(this, "_beforeRerender");
/**
* Methods that should run after rerender
* @type {Object<string, CallableFunction>}
*/
p(this, "_afterRerender");
/**
* Methods that should run after element unmounts (in disconnectedCallback)
* @type {Object<string, CallableFunction>}
*/
p(this, "_afterDisconnect");
/**
* App element (main wrapper)
* @type {App}
*/
p(this, "app");
/**
* @type {CustomElement}
*/
p(this, "_parent");
/**
* Class for virtual render div
* @type {string}
*/
w(this, Ue, "virtual-render-div");
/**
* Getter/setter defined values
* @type {Object<string, any>}
*/
p(this, "_values", {});
/**
* Array with protected properties of the object
* @type {Array<string>}
*/
w(this, he);
/**
* Template engine settings
* @type {Object<string, any>}
*/
w(this, Q);
/**
* @type {Object<string, CallableFunction>}
*/
p(this, "_templateFunctions", {});
/**
* @type {Object<string, any>}
*/
p(this, "_templateVariables");
/**
* @type {Object<string, Object<string, CallableFunction>>}
*/
p(this, "_define");
p(this, "initElement", () => {
d(this, Ae).call(this);
});
/**
* Initilizer method of element. Triggered in the connectedCallback
* @returns {Promise<void>}
*/
w(this, Ae, async () => {
var a;
this._startInitilization(), this._abort = !1;
const e = (a = this.closest("[app-id]")) == null ? void 0 : a.app;
if (!e)
throw new Error("Could not find container with application.");
if (this.app = e, this.app.addEventListener(Y.ABORTROUTETRANSITION, d(this, Be)), this.rndId = nn(), this.hashId = this.getAttribute("data-hash-id") || this.generateHash(), this.getAttribute("data-hash-id") || this.setAttribute("data-hash-id", this.hashId), !this.getAttribute("data-parent-id")) {
let o = this.parentElement.closest("[data-hash-id]");
o && (this.setAttribute("data-parent-id", o.getAttribute("data-hash-id")), this._parent = o);
}
if (this._templateVariables = {}, U(this, Q, window.structuredClone(tn)), d(this, xe).call(this), U(this, he, Object.getOwnPropertyNames(this)), d(this, ze).call(this), d(this, We).call(this), await d(this, z).call(this, this._beforeInit), this.app.addEventListener(X.CHANGE, this._updateBoundAppDataParts), this.app.addEventListener(X.QUERYCHANGE, this._updateBoundQueryDataParts), await d(this, Qe).call(this), await this.render(), this._abort) {
this.resolveInitialization();
return;
}
await d(this, de).call(this), await d(this, z).call(this, this._beforeInitResolve), this.resolveInitialization(), await d(this, z).call(this, this._afterInit);
});
w(this, Be, (e) => {
this._abort = !0, this.resolveInitialization();
});
w(this, xe, () => {
for (const [e, a] of Object.entries(this._templateFunctions))
d(this, Q).def[e] = a.bind(this);
});
w(this, We, () => {
for (const [e, a] of Object.entries(this._define)) {
if (d(this, he).includes(e) || e.startsWith("#") || e.startsWith("_"))
throw new Error(`Illegal or protected property name. Can't assign property with name (${e}) that is protected or if it is of illegal format (startswith: # or _) to element ${this.tagName}`);
if (typeof a == "function")
Object.defineProperty(this, e, a.bind(this)(e));
else {
let o = {
get() {
return a.get.bind(this)();
}
};
a.set && (o = {
...o,
set(h) {
a.set.bind(this)(h), this._refreshBoundElements(e);
}
}), Object.defineProperty(this, e, o);
}
}
});
/**
* Refreshes inner html of all elements bound to this property
* @param {string} propertyName
*/
p(this, "_refreshBoundElements", (e) => {
var a;
this.renderTime = Date.now(), (a = this.querySelectorAll(`[data-bind="${e}"]`)) == null || a.forEach((o) => {
const h = d(this, Q).dataBinds.get(e);
if (!h)
return;
const _ = o.getAttribute("data-bind-id");
if (!_)
return;
const g = h[_];
g && (o.setAttribute("data-render-time", `${this.renderTime}`), o.innerHTML = g);
});
});
p(this, "_updateBoundAppDataParts", (e) => {
this._refreshBoundElements(`app.${e.detail.field}`);
});
p(this, "_updateBoundQueryDataParts", (e) => {
var a;
(a = e == null ? void 0 : e.detail) != null && a.key ? this._refreshBoundElements(`query.${e.detail.key}`) : this._refreshBoundElements("query");
});
w(this, ze, () => {
for (const [e, a] of Object.entries(this._methods)) {
if (d(this, he).includes(e) || e.startsWith("#") || e.startsWith("_"))
throw new Error(`Illegal or protected method name. Can't assign method with name (${e})
that is protected or if it is of illegal format (startswith: # or _) to element ${this.tagName}`);
try {
this[e] = a.bind(this);
} catch (o) {
throw new Error(`${a} is probably not a function. Failed to bind method ${a} to element ${this.tagName}.` + o);
}
}
this._methods = null;
});
w(this, de, async () => {
const e = [];
return Array.from(this.querySelectorAll("*")).filter(
(o) => o instanceof rt
).forEach((o) => {
e.push(o.initComplete);
}), await Promise.all(e);
});
p(this, "awaitElementsActivation", async () => await d(this, de).call(this));
/**
* Runs all methods in object
* @param {Object<string, CallableFunction>} methods
*/
w(this, z, async (e) => {
for (const [a, o] of Object.entries(e))
await o.bind(this)();
});
w(this, Qe, async () => {
var e, a;
an && (U(this, Ve, ((e = this == null ? void 0 : this.css) == null ? void 0 : e.scoped) || !1), U(this, ce, await ((a = this.css) == null ? void 0 : a.style.bind(this)()) || null), d(this, Ie).call(this));
});
w(this, Ie, () => {
if (!d(this, ce))
return;
const e = `[data-element="${this.tagName.toLowerCase()}"]`;
if (document.head.querySelector(e))
return;
const a = document.createElement("style");
if (a.textContent = d(this, ce), a.setAttribute("disabled", ""), a.setAttribute("data-element", this.tagName.toLowerCase()), document.head.appendChild(a), !d(this, Ve)) {
a.removeAttribute("disabled");
return;
}
const o = a.sheet, h = (g, b) => g.split(" ").map((y) => y.replace(/([a-zA-Z0-9\.\#\-_]+)([:].*)?/, (R, L, N) => L + b + (N || ""))).join(" "), _ = [];
for (let g of o.cssRules)
if (g instanceof CSSStyleRule) {
const b = g.selectorText.split(",").map((y) => h(y.trim(), e)).join(", ");
_.push(`${b} { ${g.style.cssText} }`);
} else if (g instanceof CSSMediaRule) {
const b = [];
for (let y of g.cssRules)
if (y instanceof CSSStyleRule) {
const R = y.selectorText.split(",").map((L) => h(L.trim(), e)).join(", ");
b.push(`${R} { ${y.style.cssText} }`);
}
_.push(`@media ${g.media.mediaText} { ${b.join(" ")} }`);
}
a.textContent = _.join(`
`), a.removeAttribute("disabled");
});
/**
* Should be implemented for template rendering
* @returns {Promise<string>}
* @async
*/
w(this, Ge, async () => {
if (!this.markup)
throw new Error(`Missing markup method for element ${this.tagName}`);
try {
return await this.markup();
} catch (e) {
throw new Error(`Failed to run markup method of element: ${this.tagName} - ` + e.message);
}
});
/**
* Renders template of element. Uses markup method.
* Adds event listeners to elements with appropriate attributes (df-<event>)
* @async
* @returns {Promise<void>}
*/
p(this, "render", async () => {
let e = await d(this, Ge).call(this);
this.innerHTML = e;
});
/**
* Parses the provided string template. Adds all
* required data-parent tags etc...
* @param {string} html
* @returns {string}
*/
w(this, Je, (e) => {
e = d(this, Ze).call(this, e), e = d(this, Xe).call(this, e), e = d(this, Ke).call(this, e);
const a = document.createElement("div");
return a.classList.add(d(this, Ue)), this.app._originalInsertAdjacentHTML.call(a, "afterbegin", e), this.lastRender = Date.now(), d(this, et).call(this, a), d(this, at).call(this, a);
});
/**
* @param {string} html
* @returns {string}
*/
w(this, Ye, (e) => {
try {
let o = ee.template.bind(this)(e, d(this, Q), void 0).bind(this)(this._templateVariables);
return this._templateVariables = {}, d(this, Je).call(this, o);
} catch {
console.error("Failed to run #dotJSengine for element: ", this);
}
});
/**
* Runs the dotJS render engine with the provided html string
* @param {string} html
* @returns {string}
*/
p(this, "_dotJSengine", (e) => d(this, Ye).call(this, e));
p(this, "getAttrs", (e) => {
const a = e.dataset, o = {};
for (const [h, _] of Object.entries(a))
if (!this.app._filterAttributeNames.includes(h))
try {
o[h] = JSON.parse(_.trim());
} catch {
o[h] = _;
}
return o;
});
/**
* Adds variable for template rendring
* @param {string} name
* @param {any} value
*/
p(this, "addTemplateVariable", (e, a) => {
this._templateVariables[e] = a;
});
p(this, "clearTemplateVariables", () => {
this._templateVariables = {};
});
/**
* Replaces shorthand "@<eventName>=" synatx with jolt-<eventName>
* @param {string} inputString
* @returns
*/
w(this, Ze, (e) => e.replace(/@(\w+)=/g, "jolt-$1="));
w(this, Xe, (e) => e.replace(/:(\w+)=/g, "data-$1="));
w(this, Ke, (e) => e.replace(/<([A-Z][a-zA-Z0-9]*|[a-z][a-zA-Z0-9]*)([^>]*)\s*(\/?)>/g, (a, o, h, _) => {
if (this.app._elements[o]) {
const b = this.app._elements[o].tagName;
return _ ? `<${b}${h}/>` : `<${b}${h}></${b}>`;
}
return a;
}));
/**
* Parses conditionals in html elements (df-if)
* @param {HTMLElement} elem - element whose contents should be parsed
*/
w(this, et, (e) => {
e.querySelectorAll("[jolt-show-if]").forEach((a) => {
const o = a.getAttribute("jolt-show-if");
[!1, "false", null, "null", void 0, "undefined"].includes(o) && a.remove();
});
});
/**
* Gets all arguments on the element with an df-{eventName} attribute. Parses all
* arguments that starts with a ":" and collects them into an object with key-value pairs.
* @param {HTMLElement|CustomElement} elem
* @returns {Object<string, string|number|object>}
*/
w(this, tt, (e) => this.getAttrs(e));
/**
* Returns the type-method pair of the assigned event
* or null if no event was assigned to the element
* @param {HTMLElement} elem
* @returns {Array<string, string>|null}
*/
p(this, "getEventTypeAndMethod", (e) => {
if (!e.attributes)
return [null, null];
for (const o of e.attributes)
if (o.name.startsWith("jolt-")) {
const h = e.getAttribute(o.name);
return [o.name, h];
}
return [null, null];
});
/**
* Sets event listeners on all elements
* @param {{element: HTMLElement, eventName: string, methodName: string}[]} elementsWithevents
*/
w(this, fe, (e) => {
for (let a of e) {
const o = a.element, h = a.eventName, _ = a.methodName;
if (o[`jolt-${h}:active`])
return;
const g = this._createEventListenerMethod(o, _);
o.addEventListener(h, g), o[`jolt-${h}:active`] = !0, o[`jolt-${h}:active-method-${_}`] = g;
}
});
/**
* Creates listener method for event listener of element
* @param {HTMLElement} elem - the HTMLElement with eventListener
* @param {string} methodName - name of the method
* @returns {CallableFunction}
*/
p(this, "_createEventListenerMethod", (e, a) => async (o) => {
let h = d(this, tt).call(this, e);
try {
h && Object.keys(h).length != 0 ? await this[a](e, o, h) : await this[a](e, o);
} catch (_) {
throw console.error(_), new Error(`Could not run method ${a} on element ${this.tagName}`);
}
});
/**
* Public acces to _createEventListenerMethod
* @param {HTMLElement} elem - the HTMLElement with eventListener
* @param {string} methodName - name of the method
* @returns {CallableFunction}
*/
p(this, "createEventListenerMethod", (e, a) => this._createEventListenerMethod(e, a));
/**
* Sets event listeners to elements in array
* @param {{element: HTMLElement, eventName: string, methodName: string}[]} elementsWithEvents
*/
p(this, "_setEventListeners", (e) => {
d(this, fe).call(this, e);
});
/**
* Finds all elements with event listeners
* @param {NodeListOf} allElements
* @returns {{element: HTMLElement, eventName: string, methodName: string}[]}
*/
w(this, pe, (e) => {
const a = [];
return e.forEach((o) => {
a.push(...d(this, Ce).call(this, o));
}), a;
});
/**
* Gathers event metadata from an element's attributes.
* @param {HTMLElement} elem - The DOM element from which to extract events.
* @returns {{element: HTMLElement, eventName: string, methodName: string}[]}
* An array of event objects, each containing the element, event name, and method name.
*/
w(this, Ce, (e) => {
const a = [];
return Array.from(e.attributes).forEach((o) => {
o.name.startsWith("jolt-") && !o.name.startsWith("jolt-show-if") && a.push({
element: e,
eventName: o.name.replace("jolt-", ""),
methodName: o.value
});
}), a;
});
/**
*
* @param {HTMLElement} elem
* @returns {{element: HTMLElement, eventName: string, methodName: string}[]}
*/
p(this, "_elementWithEvent", (e) => d(this, Ce).call(this, e));
p(this, "_allElementsWithEvents", (e) => d(this, pe).call(this, e));
/**
* Adds functionality to all elements in the markup of the element
* @returns {void}
*/
w(this, nt, () => {
const e = d(this, pe).call(this, this.querySelectorAll(`[data-parent-id="${this.hashId}"][data-render-time="${this.lastRender}"]`));
d(this, fe).call(this, e);
});
p(this, "_hydrate", () => {
d(this, nt).call(this);
});
/**
* Adds the parent name to each html element as a custom attribute (parent-name)
* @param {HTMLElement} div
* @returns {string}
*/
w(this, at, (e) => (e.querySelectorAll(":not([data-parent-id]:not(data-render-time))").forEach((a) => {
a.setAttribute("data-parent-id", this.hashId), a.setAttribute(
"data-render-time",
`${this.lastRender}`
);
}), e.innerHTML));
/**
* Triggers rerender of entire element
* @abstract
* @param {CustomEvent} [event]
*/
p(this, "rerender", async (e) => (d(this, Q).dataBinds = /* @__PURE__ */ new Map(), await d(this, z).call(this, this._beforeRerender), await this.render(), await d(this, de).call(this), await d(this, z).call(this, this._afterRerender)));
/**
* Generates random hash
* @param {Number} length
* @returns {string}
*/
p(this, "generateHash", (e = 16) => this.app.generateHash(e));
/**
* Convenience method for getting data from application storage
* @param {string} field
* @returns {any|undefined}
*/
p(this, "getData", (e) => this.app.getData(e));
/**
* Convenience method for setting data to application storage
* @param {string} field
* @param {any} data
* @throws {Error} - Missing field in app data structure
*/
p(this, "setData", (e, a) => {
this.app.setData(e, a);
});
/**
* Returns location.search params either as object (true) or as a string (false)
* Default: false
* @param {boolean} toObject
* @returns {string|Object<string, string>}
*/
p(this, "getQueryParams", (e = !1) => this.app.getQueryParams(e));
/**
* Makes fetch (GET) request for markup
* @param {string} url
* @returns {Promise<string>}
*/
p(this, "getHTMLtemplate", async (e) => {
var a, o;
try {
const h = await fetch(e, {
redirect: "manual"
});
return h.status == 200 ? await h.text() : (this._abort = !0, (a = this.app) != null && a.router ? this.app.router._abortPageLoad(h.status) : console.error(`Failed to fetch html markup for ${this.tagName} with response code ${h.status}`), "");
} catch {
return this._abort = !0, (o = this.app) != null && o.router ? this.app.router._abortPageLoad(500) : console.error(`Failed to fetch html markup for ${this.tagName}. Server failed to respond.`), "";
}
});
this.resolveInitialization = null, this.initComplete = null, this._startInitilization();
}
async connectedCallback() {
if ([!0, "true", "", "defer"].includes(this.getAttribute("defer"))) {
this.resolveInitialization();
return;
}
try {
await d(this, Ae).call(this);
} catch (e) {
this._abort = !0, this.resolveInitialization(), console.log(`Failed to initilize element ${this.tagName}`), console.error(e);
}
}
/**
* Disconnected callback for element
*/
disconnectedCallback() {
this.app.removeEventListener(X.CHANGE, this._updateBoundAppDataParts), this.app.removeEventListener(X.QUERYCHANGE, this._updateBoundQueryDataParts);
const e = document.head.querySelector(`style[data-parent-id="${this.hashId}"]`);
e && e.remove(), d(this, z).call(this, this._afterDisconnect);
}
/**
* Activates methods of all elements inside the designated HTML element
* that are part of the current CustomElement and have the data-render-time attribute the same as this.lastRender
* @param {HTMLElement|string} elem - element or valid querySelector string
*/
activateElement(e) {
typeof e == "string" && (e = this.querySelector(e));
const a = d(this, pe).call(
this,
// @ts-ignore
e.querySelectorAll(`[data-parent-id="${this.hashId}"][data-render-time="${this.lastRender}"]`)
);
d(this, fe).call(this, a);
}
get attrs() {
return this.getAttrs(this);
}
get variables() {
return this._templateVariables;
}
/**
* Getter for query parameters
*/
get queryParams() {
return this.app.queryParams;
}
/**
* Sets new query(search) params to url based on provided
* query parameters object
* @param {Object<string, string|number|boolean>} queryParamsObject
*/
set queryParams(e) {
this.app.queryParams = e;
}
/**
* Adds query parameters provided in object
* as key-value pairs
* @param {Object<string, string|number|boolean>} params
*/
addQueryParams(e) {
this.queryParams = {
...this.queryParams,
...e
};
}
/**
* Removes query parameters in provided array
* @param {Array<string>} names
*/
removeQueryParams(e) {
this.app.removeQueryParams(e);
}
/**
* Returns the parent CustomElement of current CustomElement if it exists. Top-level
* elements (direct children of the app container) don't have this property
* @returns {CustomElement|undefined}
*/
get parent() {
return this._parent;
}
/**
* Returns application router
* @returns {Router}
*/
get router() {
return this.app.router;
}
/**
* Returns url hash
* @returns {string}
*/
get hash() {
return this.app.hash;
}
/**
* Returns url port
* @returns {string}
*/
get port() {
return this.app.port;
}
/**
* Returns url hostname
* @returns {string}
*/
get hostname() {
return this.app.hostname;
}
/**
* Returns url host
* @returns {string}
*/
get host() {
return this.app.host;
}
/**
* Returns url pathname
* @returns {string}
*/
get pathname() {
return this.app.pathname;
}
/**
* Returns url origin
* @returns {string}
*/
get origin() {
return this.app.origin;
}
/**
* Returns route parameters (query string) as object
* @returns {string}
*/
get routeParameters() {
return this.app.router.routeParameters;
}
/**
* Returns data from application storage
* based on elements dataField property
* @returns {Object}
*/
get data() {
return this.app.getAllData(!0);
}
/**
* Returns render functions defined on the app object
* @returns {Object<string, CallableFunction>}
*/
get functions() {
return this.app.renderFunctions;
}
/**
* Returns application properties
* @returns {Object}
*/
get properties() {
return this.app.properties;
}
/**
* Returns object with registered app extensions
* @returns {Object<string, Extension>}
*/
get ext() {
return this.app.ext;
}
/**
* Getter for authenticator
* @returns {Authenticator}
*/
get authenticator() {
return this.app.authenticator;
}
/**
* Static method to generate html of this element
* @param {string} [hashId]
* @returns {string}
* @static
*/
static generate(e, a = null) {
a || (a = {});
let o = [];
for (const [_, g] of Object.entries(a))
o.push(`:${_}="${g}"`);
let h = o.length > 0 ? o.join(" ") : "";
return e ? Dt`<${this.tagName} data-hash-id="${e}" ${h}></${this.tagName}>` : Dt`<${this.tagName} ${h}></${this.tagName}>`;
}
};
Ve = new WeakMap(), ce = new WeakMap(), Ue = new WeakMap(), he = new WeakMap(), Q = new WeakMap(), Ae = new WeakMap(), Be = new WeakMap(), xe = new WeakMap(), We = new WeakMap(), ze = new WeakMap(), de = new WeakMap(), z = new WeakMap(), Qe = new WeakMap(), Ie = new WeakMap(), Ge = new WeakMap(), Je = new WeakMap(), Ye = new WeakMap(), Ze = new WeakMap(), Xe = new WeakMap(), Ke = new WeakMap(), et = new WeakMap(), tt = new WeakMap(), fe = new WeakMap(), pe = new WeakMap(), Ce = new WeakMap(), nt = new WeakMap(), at = new WeakMap(), /**
* Element tagName
* @type {string}
*/
p(rt, "tagName");
let G = rt;
function Cn({
tagName: l,
markup: r,
css: e = null,
methods: a = {},
beforeInit: o = {},
beforeInitResolve: h = {},
afterInit: _ = {},
beforeRerender: g = {},
afterRerender: b = {},
afterDisconnect: y = {},
define: R = {},
templateFunctions: L = {}
}) {
var S;
if (!l || !r)
throw new Error("Missing tagName or markup method in ElementFactory");
if (!((O) => /^[a-z]+(-[a-z]+)*$/.test(O))(l))
throw new Error("Element tagName must be in a valid kebab-case synatx");
return S = class extends G {
constructor() {
super();
/** @type {Object<string, Function>} */
p(this, "_methods", a);
/** @type {() => Promise<string>} */
p(this, "markup", r);
p(this, "css", e);
/** @type {Object<string, Function>} */
p(this, "_beforeInit", o);
/** @type {Object<string, Function>} */
p(this, "_beforeInitResolve", h);
/** @type {Object<string, Function>} */
p(this, "_afterInit", _);
/** @type {Object<string, Function>} */
p(this, "_beforeRerender", g);
/** @type {Object<string, Function>} */
p(this, "_afterRerender", b);
/** @type {Object<string, Function>} */
p(this, "_afterDisconnect", y);
/** @type {DefineMethods} */
p(this, "_define", R);
/** @type {Object<string, Function>} */
p(this, "_templateFunctions", L);
}
}, /** @type {string} */
p(S, "tagName", l), S;
}
const Y = {
START: "route-change.start",
FINISHED: "route-change.finished",
LAYOUTCHANGEFINISHED: "route-change.layout-change.finished",
ERRORPAGESTART: "route-change.error-page.start",
ERRORPAGEFINISHED: "route-change.error-page.finished",
ABORTROUTETRANSITION: "route-change.abort"
};
var Te, te, At, st, it, ot, lt, Pt, me, Se, ut, ct, ht, Ne, De, dt, ft, ge, pt;
class Tn {
/**
* Constructor for router
* @param {RouterConfigs} configs
*/
constructor({ baseUrl: r = "", routes: e, baseLayout: a, defaultTarget: o, pageNotFoundCode: h = 404, index: _ = "/", app: g }) {
w(this, te);
/**
* Application object
* @type {App}
*/
w(this, Te);
/**
* @type {Object}
* @property {string} route - app endpoint
* @property {Object<string, CustomElement>} handlers - elements to render (target - element pairs)
* @property {string} title - title of the page
* @property {Array<string>} [roles] - array with allowed user roles (optional)
* @property {CustomElement} [layout] - layout of page (optional, default=baseLayout)
* @property {Object<string, string|number>} params - url parameters
* @property {Array<Array<string, string, CustomElement>>} renderSequence - sequence in which elements were rendered (with target and hash ids)
*/
p(this, "_currentRoute");
/**
* Sorting method for Array.sort for routes
* @param {Array} a
* @param {Array} b
* @returns {Number}
*/
w(this, st, (r, e) => {
const a = e[0].length - r[0].length;
return a !== 0 ? a : r[0].includes("<str:") && e[0].includes("<int:") ? -1 : r[0].includes("<int:") && e[0].includes("<str:") ? 1 : 0;
});
/**
* Click handler method for routing
* @param {Event} event
*/
w(this, it, async (r) => {
var _, g, b, y;
const e = (_ = r == null ? void 0 : r.target) == null ? void 0 : _.matches("a"), a = (g = r == null ? void 0 : r.target) == null ? void 0 : g.closest("a"), o = e ? (b = r == null ? void 0 : r.target) == null ? void 0 : b.getAttribute("router-ignore") : a == null ? void 0 : a.getAttribute("router-ignore"), h = e ? (y = r == null ? void 0 : r.target) == null ? void 0 : y.href : a == null ? void 0 : a.href;
if ((e || a) && !o && h && !h.startsWith("mailto:")) {
if (r.preventDefault(), this._inTransition && this._transitionToRoute == h) {
r.preventDefault();
return;
}
this._inTransition && d(this, ct).call(this), this._inTransition = !0;
try {
this._transitionToRoute = h, await d(this, lt).call(this, h);
} catch {
this._abort || console.error("Routing failed for route: ", h), this._abort = !1;
}
this._transitionToRoute = "", this._inTransition = !1;
}
});
/**
* Pop state handler for routing (nav btns back/forth)
* @param {Event} event
*/
w(this, ot, async (r) => {
if (await this.route(), r.state && r.state.scrollPosition) {
const { x: e, y: a } = r.state.scrollPosition;
window.scrollTo(e, a);
}
});
/**
* Handles the clicked navigation aTag
* @param {string} href - the clicked a tag
*/
w(this, lt, async (r) => {
const e = d(this, ge).call(this);
history.pushState(e, null, r), await this.route();
});
/**
* Performs the actual route based on the current browser url
* @returns {Promise<void>}
*/
p(this, "route", async () => {
var a, o;
let r = location.pathname;
r = r.replace(this.baseUrl, ""), r === "" && (r = "/");
const e = je(this, te, Pt).call(this, r);
if (e && !((a = this.app) != null && a.authenticatorInstalled)) {
await d(this, me).call(this, e);
return;
}
if (e && ((o = this.app) != null && o.authenticatorInstalled)) {
if (!e.authenticationRequired) {
await d(this, me).call(this, e);
return;
}
if (e.authenticationRequired && this.app.authenticator.isAuthenticated && this.app.authenticator.hasRole((e == null ? void 0 : e.rolesRequired) || [])) {
await d(this, me).call(this, e);
return;
}
if (e.authenticationRequired && (!this.app.authenticator.isAuthenticated() || !this.app.authenticator.hasRole((e == null ? void 0 : e.rolesRequired) || []))) {
await this.app.authenticator.unauthorizedRedirect(), this.app.authenticator.redirectCallback && await this.app.authenticator.redirectCallback();
return;
}
}
await d(this, ut).call(this);
});
/**
* Performs redirect to provided path
* @param {string} pathname
*/
p(this, "redirect", async (r) => {
const e = `${this.baseUrl}${r}`, a = d(this, ge).call(this);
history.pushState(a, null, e), await this.route();
});
/**
* Loads home/index route
*/
p(this, "home", async () => {
const r = `${this.baseUrl}${this.index}`, e = d(this, ge).call(this);
history.pushState(e, null, r), await this.route();
});
/**
* Loads appropriate page according to matchedPath
* @param {Object<string, CustomElement|string|null>} matchedPath
*/
w(this, me, async (r) => {
var h;
d(this, ht).call(this);
const e = [];
await d(this, Se).call(this, r.layout), await this.app.querySelector(r.layout.tagName).initComplete;
const o = Object.entries(r.handlers);
for (const [_, [g, b]] of o.entries()) {
const y = this.app.querySelector(g);
if (!y)
throw new Error(`Failed to get target (${g}) container for route ${r.route} and handler (${b})`);
if (y.querySelector(b.tagName) && o.length != 1 && _ < o.length - 1)
continue;
const L = this.app.generateHash();
this.app._originalInnerHTML.call(y, b.generate(L, (h = r.attributes) == null ? void 0 : h[b.tagName]));
const N = this.app.querySelector(`[data-hash-id="${L}"]`);
N && await N.initComplete, e.push([g, L, b]);
}
d(this, pt).call(this, r.title), this._currentRoute = {
...r,
renderSequence: e,
href: window.location.href
}, d(this, Ne).call(this);
});
/**
* Renders layout of matched path of not already loaded.
* @param {CustomElement} matchedLayout
*/
w(this, Se, async (r) => {
if (!this.app.querySelector(r.tagName)) {
this.app.container.innerHTML = r.generate();
const e = this.app.querySelector(r.tagName);
await e.initComplete, d(this, De).call(this, e.tagName);
}
});
/**
* Loads error page
* @returns {Promise<void>}
*/
w(this, ut, async () => {
this.app.querySelector(this.baseLayout.tagName) || (this.app.container.innerHTML = this.baseLayout.generate(), await this.app.querySelector(this.baseLayout.tagName).initComplete, d(this, De).call(this, this.baseLayout.tagName));
const r = this.app.querySelector(this.defaultTarget);
if (!r)
return;
const e = this.app._errorPages[this.pageNotFoundCode].generate();
r.innerHTML = e, d(this, Ne).call(this);
});
/**
* Aborts page load
* @param {number|null} status
*/
p(this, "_abortPageLoad", async (r = null) => {
d(this, dt).call(this);
let e = this.defaultTarget, a = this.baseLayout;
this._currentRoute && (a = this._currentRoute.layout, e = this._currentRoute.renderSequence[0][0]), await d(this, Se).call(this, a);
const o = this.app.querySelector(e);
if (!o)
throw new Error(`Failed to get target (${e}) container for error page`);
Object.keys(this.app._errorPages).includes(`${r}`) || (r = 500);
const h = this.app._errorPages[r].generate();
o.innerHTML = h, d(this, ft).call(this);
});
w(this, ct, () => {
const r = new CustomEvent(Y.ABORTROUTETRANSITION, {
bubbles: !0,
cancelable: !0
});
this._abort = !0, this.app.container.dispatchEvent(r);
});
/**
* Emits route change start event
*/
w(this, ht, () => {
const r = new CustomEvent(Y.START, {
bubbles: !0,
cancelable: !0,
detail: {
...this._currentRoute
}
});
this.app.container.dispatchEvent(r);
});
/**
* Emits route change finished event
*/
w(this, Ne, () => {
const r = new CustomEvent(Y.FINISHED, {
bubbles: !0,
cancelable: !0,
detail: {
...this._currentRoute
}
});
this.app.container.dispatchEvent(r);
});
/**
* Emits layout generated finished event
* @param {string} tagName tagName of layout
*/
w(this, De, (r) => {
const e = new CustomEvent(Y.LAYOUTCHANGEFINISHED, {
bubbles: !0,
cancelable: !0,
detail: {
layout: r
}
});
this.app.container.dispatchEvent(e);
});
/**
* Emits abort page load start event
* @param {number} [status] status code of error
*/
w(this, dt, (r) => {
const e = new CustomEvent(Y.ERRORPAGESTART, {
bubbles: !0,
cancelable: !0,
detail: {
errorStatus: r,
errorPage: this.app._errorPages[r]
}
});
this.app.container.dispatchEvent(e);
});
/**
* Emits abort page load finished event
* @param {number} [status] status code of error
*/
w(this, ft, (r) => {
const e = new CustomEvent(Y.ERRORPAGEFINISHED, {
bubbles: !0,
cancelable: !0,
detail: {
errorStatus: r,
errorPage: this.app._errorPages[r]
}
});
this.app.container.dispatchEvent(e);
});
/**
* Gets current state of location (scroll position)
* @returns {Object<string, Object<string, Number>>}
*/
w(this, ge, () => ({
scrollPosition: {
x: window.scrollX,
y: window.scrollY
}
}));
/**
* Sets title of current page
* @param {string} title
* @returns {undefined}
*/
w(this, pt, (r) => {
const e = document.querySelector("title");
if (!e)
throw new Error("Missing title tag in page header. This is considered bad practice!");
if (!r)
return;
let a = r;
e.innerText = a;
});
/**
* Returns location.search params either as object (true) or as a string (false)
* Default: false
* @param {boolean} toObject
* @returns {string|Object<string, string>}
*/
p(this, "getQueryParams", (r = !1) => this.app.getQueryParams(r));
if (!g)
throw new Error("Missing application object in router constructor");
if (!e)
throw new Error("Missing routes object for router.");
if (!a && !(a instanceof G))
throw new Error("Missing base layout element for the application");
U(this, Te, g), this.baseLayout = a, this.pageNotFoundCode = h, typeof e == "function" && (e = e.bind(this)()), this.defaultTarget = o, this._inTransition = !1, this._transitionToRoute = "", this._abort = !1, this._parseRoutes({ routes: e }), this._baseUrl = r, this.index = _, this.app.addEventListener("click", d(this, it)), window.addEventListener("popstate", d(this, ot)), this._currentRoute = null;
}
/**
* Method for parsing routes tree
* @param {Object} configs
* @param {Object<string, string} configs.routes
* @param {string} configs.parentPath - The parent path (used during recursion)
* @param {Object} configs.parentHandlers - The parent handlers (used during recursion)
* @param {CustomElement} [configs.layout] - The current layout being applied (top-level routes can override this)
*/
_parseRoutes({ routes: r, parentPath: e = "", parentHandlers: a = {}, layout: o }) {
this.routeMap = new Map(Object.entries(je(this, te, At).call(this, { routes: r, parentPath: e, parentHandlers: a, layout: o }))), this.routeMap = this.sortRouteMap();
}
/**
* Method for sorting the routeMap object
* @returns {Map}
*/
sortRouteMap() {
const r = Array.from(this.routeMap.entries()).sort(d(this, st));
return new Map(r);
}
/**
* Returns the base url of the application
* @returns {string}
*/
get baseUrl() {
return this._baseUrl;
}
get hash() {
return this.app.hash;
}
get port() {
return this.app.port;
}
get hostname() {
return this.app.hostname;
}
get host() {
return location.host;
}
get pathname() {
return location.pathname;
}
get origin() {
return location.origin;
}
get app() {
return d(this, Te);
}
get currentRoute() {
return this._currentRoute;
}
}
Te = new WeakMap(), te = new WeakSet(), /**
* Method for parsing routes tree
* @param {Object} configs
* @param {Object<string, string} configs.routes
* @param {string} configs.parentPath - The parent path (used during recursion)
* @param {Object} configs.parentHandlers - The parent handlers (used during recursion)
* @param {CustomElement} [configs.layout] - The current layout being applied (top-level routes can override this)
*/
At = function({ routes: r, parentPath: e = "", parentHandlers: a = {}, layout: o }) {
let h = o;
h || (h = this.baseLayout);
const _ = {};
return r.forEach((g) => {
const b = e + g.path;
if (typeof g.handler != "function")
throw new Error("Route handler must be of type CustomElement from ElementFactory.");
const y = g.handler ? { ...a, [g.target]: g.handler } : { ...a };
g.handlers && Object.keys(g.handlers).forEach((L) => {
y[L] = g.handlers[L];
});
const R = g.layout || h;
_[b] = {
handlers: { ...y },
layout: R,
title: g == null ? void 0 : g.title,
// || this.app.appName,
roles: g.roles || null,
details: (g == null ? void 0 : g.details) || null,
attributes: g != null && g.attributes ? { [g.handler.tagName]: g.attri