UNPKG

jolt-ui

Version:

A web components based SPA framework

1,411 lines 123 kB
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 = { "&": "&#38;", "<": "&#60;", ">": "&#62;", '"': "&#34;", "'": "&#39;", "/": "&#47;" }, 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