UNPKG

lighterjs

Version:

A light weight and class based vanilla JS UI framework, with Component, Router, and State handling.

601 lines (600 loc) 24.9 kB
var C = Object.defineProperty; var _ = (a, t, e) => t in a ? C(a, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[t] = e; var h = (a, t, e) => (_(a, typeof t != "symbol" ? t + "" : t, e), e); let p; const k = new Uint8Array(16); function x() { if (!p && (p = typeof crypto < "u" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto), !p)) throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); return p(k); } const c = []; for (let a = 0; a < 256; ++a) c.push((a + 256).toString(16).slice(1)); function E(a, t = 0) { return (c[a[t + 0]] + c[a[t + 1]] + c[a[t + 2]] + c[a[t + 3]] + "-" + c[a[t + 4]] + c[a[t + 5]] + "-" + c[a[t + 6]] + c[a[t + 7]] + "-" + c[a[t + 8]] + c[a[t + 9]] + "-" + c[a[t + 10]] + c[a[t + 11]] + c[a[t + 12]] + c[a[t + 13]] + c[a[t + 14]] + c[a[t + 15]]).toLowerCase(); } const P = typeof crypto < "u" && crypto.randomUUID && crypto.randomUUID.bind(crypto), y = { randomUUID: P }; function S(a, t, e) { if (y.randomUUID && !t && !a) return y.randomUUID(); a = a || {}; const s = a.random || (a.rng || x)(); if (s[6] = s[6] & 15 | 64, s[8] = s[8] & 63 | 128, t) { e = e || 0; for (let r = 0; r < 16; ++r) t[e + r] = s[r]; return t; } return E(s); } class A { constructor(t) { this.keyPrefix = t || "", this.localStorageAvailable = this._lsTest(); } getItem(t, e) { return this.localStorageAvailable && this.checkIfItemExists(t) ? localStorage.getItem(this.keyPrefix + t) : e || null; } checkIfItemExists(t) { return this.localStorageAvailable ? Object.prototype.hasOwnProperty.call(localStorage, this.keyPrefix + t) : !1; } setItem(t, e) { return this.localStorageAvailable ? (localStorage.setItem(this.keyPrefix + t, e), !0) : !1; } removeItem(t) { return this.localStorageAvailable ? (this.checkIfItemExists(t) && localStorage.removeItem(this.keyPrefix + t), !0) : !1; } convertValue(t, e) { return typeof t == "boolean" ? e === "true" : typeof t == "number" ? Number(e) : e; } _lsTest() { const t = this.keyPrefix + "testLSAvailability"; try { return localStorage.setItem(t, t), localStorage.removeItem(t), !0; } catch { return !1; } } } class T { constructor(t) { this.keyPrefix = t || "", this.sessionStorageAvailable = this._lsTest(); } getItem(t, e) { return this.sessionStorageAvailable && this.checkIfItemExists(t) ? sessionStorage.getItem(this.keyPrefix + t) : e || null; } checkIfItemExists(t) { return this.sessionStorageAvailable ? Object.prototype.hasOwnProperty.call(sessionStorage, this.keyPrefix + t) : !1; } setItem(t, e) { return this.sessionStorageAvailable ? (sessionStorage.setItem(this.keyPrefix + t, e), !0) : !1; } removeItem(t) { return this.sessionStorageAvailable ? (this.checkIfItemExists(t) && sessionStorage.removeItem(this.keyPrefix + t), !0) : !1; } convertValue(t, e) { return typeof t == "boolean" ? e === "true" : typeof t == "number" ? Number(e) : e; } _lsTest() { const t = this.keyPrefix + "testSSAvailability"; try { return sessionStorage.setItem(t, t), sessionStorage.removeItem(t), !0; } catch { return !1; } } } class I { constructor(t, e, s) { h(this, "setCallback", (t) => this.callback = t); this.prefix = t || "", this.callback = e, this.showLogs = !0, this.showErrors = !0, this.showWarnings = !0, s && this.turnOff(); } log(...t) { this.callback && this.callback("log", ...t), this.showLogs && console.log(this.prefix, ...t); } error(...t) { this.callback && this.callback("error", ...t), this.showErrors && console.error(this.prefix, ...t); } warn(...t) { this.callback && this.callback("warning", ...t), this.showWarnings && console.warn(this.prefix, ...t); } turnOff() { this.showLogs = !1, this.showErrors = !1, this.showWarnings = !1; } turnOn() { this.showLogs = !0, this.showErrors = !0, this.showWarnings = !0; } } const H = () => S(); let b = !1; const d = new I("LIGHTER.js ROUTER *****"); class j { constructor(t, e, s, r) { h(this, "initRouter", async (t, e) => { this.setRoute(); let s = !1; if (this.curRoute.length < this.basePath.length && (this.curRoute = this.basePath + "/", s = !0), !t) { this.notFound(); return; } for (let i = 0; i < t.length; i++) { if (t[i].route = this.basePath + t[i].route, t[i].redirect) { if (t[i].redirect = this.basePath + t[i].redirect, t[i].redirect === t[i].route) { const o = `Route's redirect cannot be the same as the route '${t[i].route}'.`; throw d.error(o), new Error(o); } this._compareRoutes(t[i].route, this.curRoute) && (this.curRoute = t[i].redirect, s = !0); } if (this._compareRoutes(t[i].route, this.curRoute, !0) && t[i].beforeDraw) { const o = await t[i].beforeDraw({ route: t[i], curRouteData: this.curRouteData, curRoute: this.curRoute, basePath: this.basePath, componentProps: this.componentProps, prevRouteData: null, prevRoute: null }); o && (this.curRoute = this.basePath + o, s = !0); } } let r = !1; for (let i = 0; i < t.length; i++) { if (t[i].redirect) { this.routes.push(t[i]); continue; } if (t[i].attachId = e, this.routes.push(t[i]), this._compareRoutes(t[i].route, this.curRoute) && !this.curRoute.includes(":") && (t[i].attachId = e, this._createNewView(t[i]), r = !0, this.curRouteData = t[i], document.title = this._createPageTitle(t[i])), !r) { const o = this._getRouteParams(this.routes[i].route, this.curRoute); o && (t[i].attachId = e, this._createNewView(t[i], o), r = !0, this.curRouteData = t[i], this.curRouteData.params = o, document.title = this._createPageTitle(t[i])); } } if (r || this.notFound(), window.onpopstate = this.routeChangeListener, s) { const i = this._createRouteState(); window.history.pushState(i, "", this.curRoute), this.curHistoryState = i; } b = !0; }); h(this, "routeChangeListener", (t) => { this.setRoute(), this.changeRoute(this.curRoute, { forceUpdate: !0, ignoreBasePath: !0, doNotSetState: !0 }), this.curHistoryState = t.state || {}; }); h(this, "_createPageTitle", (t) => { let e = t.title; return this.langFn && (t.titleKey ? e = this.langFn(t.titleKey) : d.warn( `Router has a langFn defined, but route '${t.id}' is missing the titleKey.` )), !t.title && !t.titleKey && (d.warn(`Route '${t.id}' is missing the title. Setting id as title.`), e = t.id), this.titlePrefix + e + this.titleSuffix; }); h(this, "_createRouteState", () => { const t = Object.assign({}, this.nextHistoryState); return this.nextHistoryState = {}, this.curHistoryState = {}, t; }); h(this, "replaceRoute", (t, e) => { let s = this.basePath; e && (s = ""), t = s + t; const r = this._createRouteState(); window.history.replaceState(r, "", t); }); h(this, "setNextHistoryState", (t) => { this.nextHistoryState = Object.assign(this.nextHistoryState, t); }); h(this, "setCurHistoryState", (t) => { this.curHistoryState = Object.assign(this.curHistoryState, t), window.history.replaceState(this.curHistoryState, ""); }); h(this, "getCurHistoryState", () => this.curHistoryState); // Options: Object // - forceUpdate: Boolean // - ignoreBasePath: Boolean // - doNotSetState: Boolean // - replaceState: Boolean (if true, doNotSetState is also true, so no need to declare it) h(this, "changeRoute", async (t, e) => { e || (e = {}); const s = e.forceUpdate, r = e.ignoreBasePath, i = e.doNotSetState, o = e.replaceState; let n = this.basePath; r && (n = ""), t = n + t; for (let l = 0; l < this.routes.length; l++) if (this._compareRoutes(this.routes[l].route, t, !0) && this.routes[l].beforeDraw) { const m = await this.routes[l].beforeDraw({ route: this.routes[l], curRouteData: this.curRouteData, curRoute: this.curRoute, basePath: this.basePath, componentProps: this.componentProps, prevRouteData: this.prevRouteData, prevRoute: this.prevRouteData }); m && (t = n + m); break; } for (let l = 0; l < this.routes.length; l++) if (this.routes[l].redirect && this._compareRoutes(this.routes[l].route, t)) { t = this.routes[l].redirect; break; } if (this._compareRoutes(t, this.curRoute) && !s) return; if (s && (this.curRouteData.component.discard(!0), this.curRouteData.component = null), !i && !o) { const l = this._createRouteState(); window.history.pushState(l, "", t); } else o && this.replaceRoute(t, !0); this.prevRoute = this.curRoute, this.setRoute(); let u = !1; for (let l = 0; l < this.routes.length; l++) { if (this._compareRoutes(this.routes[l].route, t) && !t.includes(":")) { u = !0, this.prevRouteData = Object.assign({}, this.curRouteData), this.curRouteData = this.routes[l], document.title = this._createPageTitle(this.routes[l]), this._createNewView(this.routes[l]); break; } const m = this._getRouteParams(this.routes[l].route, t); if (m) { u = !0, this.prevRouteData = Object.assign({}, this.curRouteData), this.curRouteData = this.routes[l], this.curRouteData.params = m, document.title = this._createPageTitle(this.routes[l]), this._createNewView(this.routes[l], m); break; } } u || this.notFound(), this.rcCallback(this.curRoute); }); h(this, "_compareRoutes", (t, e, s) => { if (t = t.split("?")[0], e = e.split("?")[0], s && (t.includes(":") || e.includes(":"))) { const r = t.split("/"), i = e.split("/"); let o = r.length; i.length > r.length && (o = i.length); for (let n = 0; n < o; n++) r[n] && r[n].includes(":") ? r[n] = i[n] : i[n] && i[n].includes(":") && (i[n] = r[n]); t = r.join("/"), e = i.join("/"); } return t === e || t + "/" === e || t === e + "/"; }); h(this, "getRoute", (t) => t ? this.curRoute.replace(this.basePath, "") : this.curRoute); h(this, "getRouteData", () => ({ ...this.curRouteData, prevRouteData: this.prevRouteData })); h(this, "getRouteParams", () => this.curRouteData.params); h(this, "isCurrent", (t) => this.basePath + t === this.curRoute || this.basePath + t === this.curRoute + "/" || this.basePath + t + "/" === this.curRoute); h(this, "setRoute", () => { let t = location.pathname; t ? (t.length > 1 && t.substring(t.length - 1, t.length) === "/" && (t = t.substring(0, t.length - 1)), this.curRoute = t) : this.curRoute = this.basePath + "/"; }); h(this, "addRoute", (t) => { t.route = this.basePath + t.route, this.routes.push(t), this._compareRoutes(t.route, this.curRoute) && (this.curRouteData = t); }); h(this, "notFound", () => { let t; for (let e = 0; e < this.routes.length; e++) if (this.routes[e].is404) { t = this.routes[e]; break; } if (!t) { const e = "Could not find 404 template."; throw d.error(e), new Error(e); } this.prevRouteData = Object.assign({}, this.curRouteData), this.curRouteData = t, document.title = this._createPageTitle(t), this._createNewView(t); }); h(this, "draw", () => { var t; (t = this.prevRouteData) != null && t.component && (this.prevRouteData.component.discard(!0), this.prevRouteData.component = null), this.curRouteData.component.draw(); }); h(this, "remove", () => { var t, e; (t = this.prevRouteData) != null && t.component && this.prevRouteData.component.discard(!0), (e = this.curRouteData) != null && e.component && this.curRouteData.component.discard(!0), w = null, this.routes = [], this.nextHistoryState = {}, this.curHistoryState = {}, this.basePath = void 0, this.titlePrefix = void 0, this.titleSuffix = void 0, this.langFn = void 0, this.rcCallback = void 0, this.redirectRoute = void 0, this.curRouteData = void 0, this.prevRoute = void 0, this.prevRouteData = void 0, this.componentProps = void 0, b = !1; }); h(this, "_createNewView", (t, e) => { var s, r; t.component = new t.source({ ...this.componentProps, id: t.id, attachId: t.attachId, title: t.title, titleKey: t.titleKey, template: t.template, routeParams: e || {} }), (r = (s = t.component) == null ? void 0 : s.parent) != null && r.elem || (t.component.parent = t.component.getComponentById(t.attachId)); }); h(this, "_getRouteParams", (t, e) => { if (!t.includes(":")) return null; const s = t.split("/"); e = e.split("?")[0]; const r = e.split("/"); let i = s.length; r.length > s.length && (i = r.length); let o = {}; for (let n = 0; n < i; n++) if (s[n] && s[n].includes(":")) { if (!r[n]) return null; const u = s[n].replace(":", ""); o[u] = r[n]; } else if (!s[n] === void 0 || !r[n] === void 0 || s[n] !== r[n]) return null; return o; }); if (b) { const n = "Router has already been initiated. Only one router per app is allowed."; throw d.error(n), new Error(n); } const i = "Required params: new Route(routesData, attachId, rcCallback);"; if (!t || !t.routes || !t.routes.length) { const n = "Missing routesData parameter, routesData.routes, or it is an empty array. " + i; throw d.error(n), new Error(n); } if (!e) { const n = "Missing attachId parameter. " + i; throw d.error(n), new Error(n); } if (!s) { const n = "Missing rcCallback (route change callback) parameter / function. " + i; throw d.error(n), new Error(n); } let o = !1; for (let n = 0; n < t.routes.length; n++) { if (t.routes[n].is404 && (o = !0), !t.routes[n].id && !t.routes[n].redirect) { const u = "Route is missing the 'id' property."; throw d.error(u), new Error(u); } if (!t.routes[n].route) { const u = `Route '${t.routes[n].id}' is missing the 'route' property.`; throw d.error(u), new Error(u); } if (!t.routes[n].source && !t.routes[n].redirect) { const u = `Route '${t.routes[n].id}' is missing the 'source' property.`; throw d.error(u), new Error(u); } } if (!o) { const n = "Could not find 404 template."; throw d.error(n), new Error(n); } w = this, this.routes = [], this.nextHistoryState = {}, this.curHistoryState = {}, this.basePath = t.basePath || "", this.titlePrefix = t.titlePrefix || "", this.titleSuffix = t.titleSuffix || "", this.langFn = t.langFn, this.curRoute = this.basePath + "/", this.rcCallback = s, this.redirectRoute = null, this.curRouteData = { route: this.basePath + "/", source: null, params: {}, component: null }, this.prevRoute = null, this.prevRouteData = null, r || (r = {}), this.componentProps = r, this.initRouter(t.routes, e); } } let w = null; const U = (a) => d.setCallback(a), M = (a) => a ? d.turnOff() : d.turnOn(), R = {}, f = new I("LIGHTER.js COMPO *****"); class v { constructor(t) { h(this, "draw", (t) => { if (this.drawing || this.discarding) return this; this.drawing = !0; const e = { ...this.props, ...t }; if (t != null && t.id && t.id !== this.id || t != null && t._id && t.id !== this.id) throw this.drawing = !1, f.error( `ID of a component cannot be changed once it has been initialised. Old ID: "${this.id}", new ID: ${t.id || t._id}` ), new Error("ID cannot be changed"); t != null && t.prepend && f.warn( "For redraws the prepend property is ignored, because the DOM element is replaced." ), R[e.id] || (R[e.id] = this), this.props = e, this.elem && this.discard(!1, this.isDrawn), this._checkParentAndAttachId(), !this.template !== e.template && (this.template = e.template || this._createDefaultTemplate(e)), this._createElement(this.isDrawn), this.isDrawn || (e.prepend ? this.parent.elem.prepend(this.elem) : this.parent.elem.append(this.elem)), this.paint(e), this.addListeners(e); for (let s = 0; s < this.listenersToAdd.length; s++) this.listenersToAdd[s].targetId && (this.listenersToAdd[s].target = this.getComponentElemById(this.listenersToAdd[s].targetId)), this.addListener(this.listenersToAdd[s]); return this.listenersToAdd = [], this.drawing = !1, this.isDrawn = !0, this; }); h(this, "add", (t) => { let e = t; return e.isComponent || (e = new v(t)), this.children[e.id] = e, e.props.attachId || (e.parent = this), e; }); h(this, "addDraw", (t) => this.add(t).draw()); h(this, "addListener", (t) => { let { id: e, target: s, type: r, fn: i } = t; if (!r || !i) throw f.error( `Could not add listener, type, and/or fn missing. Listener props: ${JSON.stringify( t )}`, this ), new Error("Call stack"); if (e || (e = this.id, t.id = e), !s) { if (s = this.elem, s === null) throw f.error( `Could not add listener, target elem was given but is null. Listener props: ${JSON.stringify( t )}`, this ), new Error("Call stack"); t.target = s; } this.listeners[e] && this.removeListener(e), s && (t.fn = (o) => i(o, this), s.addEventListener(r, t.fn), this.listeners[e] = t); }); h(this, "removeListener", (t) => { if (!t) throw f.error( `Could not remove listener, id missing. Listener props: ${JSON.stringify(t)}`, this ), new Error("Call stack"); const { target: e, type: s, fn: r } = this.listeners[t]; e.removeEventListener(s, r), delete this.listeners[t]; }); h(this, "discard", (t, e) => { if (this.discarding) return; this.discarding = !0; let s = Object.keys(this.listeners); for (let r = 0; r < s.length; r++) this.removeListener(s[r]); s = Object.keys(this.children); for (let r = 0; r < s.length; r++) this.children[s[r]].discard(t), t && delete this.children[s[r]]; this.elem && (t || !e) && (this.elem.remove(), this.elem = null, this.isDrawn = !1), t && delete R[this.id], this.discarding = !1; }); h(this, "_setElemData", (t, e) => { var s; if (!(!t || !e)) { if ((s = e.classes) != null && s.length && t.classList.add(...e.classes), e.attributes) { const r = Object.keys(e.attributes); for (let i = 0; i < r.length; i++) t.setAttribute(r[i], e.attributes[r[i]]); } if (e.style) { const r = Object.keys(e.style); for (let i = 0; i < r.length; i++) t.style[r[i]] = e.style[r[i]]; } e.text && !e.template && (t.textContent = e.text), e._id && !t.getAttribute("id") && t.setAttribute("id", e._id); } }); h(this, "_createDefaultTemplate", (t) => t != null && t.tag ? `<${t.tag}></${t.tag}>` : "<div></div>"); h(this, "_createElement", (t) => { if (t && !this.elem) throw f.error(`Element not found to redraw, ID: ${this.id}.`), new Error("Cannot redraw"); const e = document.createElement("template"); if (e.innerHTML = this.template, t) { const s = e.content.firstChild.getAttribute("id"); s || e.content.firstChild.setAttribute("id", this.id), this.elem.replaceWith(e.content.firstChild), this.elem = document.getElementById(this.id), s || this.elem.removeAttribute("id"); } else this.elem = e.content.firstChild; this._setElemData(this.elem, this.props); }); h(this, "getComponentById", (t) => L(t)); h(this, "getComponentElemById", (t) => D(t)); h(this, "_checkParentAndAttachId", () => { if (!this.parent && !this.props.attachId) throw f.error( 'Component does not have a parent nor does it have an "attachId" as a prop. One of these is required. Either pass in an "attachId" as prop or attach this component to the parent component with "parentComponent.add()" method.' ), new Error("Call stack"); }); if (t != null && t.parent || t != null && t.children) throw f.error( `Component props contains a reserved keyword (parent or children. Props: ${JSON.stringify( t )}` ), new Error("Invalid Component props key."); t != null && t.id || t != null && t._id ? this.id = t.id || t._id : this.id = S(), this.props = { id: this.id, ...t }, R[this.id] = this, this.elem, this.parent, this.children = {}, this.listeners = {}, this.listenersToAdd = [], this.drawing = !1, this.discarding = !1, this.isDrawn = !1, this.isComponent = !0, this.props.attachId && (this.parent ? this.parent.elem = this.getComponentElemById(this.props.attachId) : this.parent = { elem: this.getComponentElemById(this.props.attachId) }), this.router = w, this.template = (t == null ? void 0 : t.template) || this._createDefaultTemplate(t), t != null && t.preCreateElement && this._createElement(); } paint() { } addListeners() { } } const N = (a) => f.setCallback(a), $ = (a) => a ? f.turnOff() : f.turnOn(), L = (a) => R[a], D = (a) => { const t = R[a]; return t != null && t.elem ? t.elem : document.getElementById(a); }, g = {}; class F { constructor(t) { this.initState = t, this.state = t || {}, this.listeners = [], this.listenerCallbacks = []; } set(t, e, s, r) { if (!t) return; const i = t.split("."); let o; if (i.length === 1) { if (r) { g[t] = e; return; } o = this.state[i[i.length - 1]], this.state[t] = e, this._checkListeners(o, e, t); return; } let n = r ? g[i[0]] : this.state[i[0]]; n === void 0 && (r ? g[i[0]] = n = {} : this.state[i[0]] = n = {}); for (let u = 1; u < i.length - 1; u++) n[i[u]] === void 0 && (n[i[u]] = {}), n = n[i[u]]; r ? n[i[i.length - 1]] = e : (o = n[i[i.length - 1]], n[i[i.length - 1]] = e, this._checkListeners(o, e, t)), s && !r && this.addListener(t, s); } get(t, e) { if (!t) return; const s = t.split("."); if (s.length === 1) return e ? g[t] : this.state[t]; let r = e ? g[s[0]] : this.state[s[0]]; for (let i = 1; i < s.length; i++) { if (r === void 0 || r[s[i]] === void 0) return; r = r[s[i]]; } return r; } remove(t, e) { if (!t) return; e || this.removeListener(t); const s = t.split("."); if (s.length === 1) { if (e) { if (g[t] === void 0) return; delete g[t]; return; } if (this.state[t] === void 0) return; delete this.state[t]; return; } let r = e ? g[s[0]] : this.state[s[0]]; for (let i = 1; i < s.length - 1; i++) { if (r === void 0 || r[s[i]] === void 0) return; r = r[s[i]]; } r !== void 0 && delete r[s[s.length - 1]]; } getObject() { return this.state; } addListener(t, e) { this.listeners.push(t), this.listenerCallbacks.push(e); } removeListener(t) { const e = this.listeners.indexOf(t); e > -1 && (this.listeners.splice(e, 1), this.listenerCallbacks.splice(e, 1)); } _checkListeners(t, e, s) { if (t === e) return; const r = this.listeners.indexOf(s); r > -1 && this.listenerCallbacks[r](e, t); } getKeys(t) { if (!t) return Object.keys(this.state); const e = t.split("."); let s = this.state[e[0]]; for (let r = 1; r < e.length - 1; r++) { if (s === void 0 || s[e[r]] === void 0) return; s = s[e[r]]; } return s === void 0 ? [] : Object.keys(s); } getG(t) { return this.get(t, !0); } getGObject() { return g; } setG(t, e) { this.set(t, e, null, !0); } removeG(t) { this.remove(t, !0); } } export { v as Component, A as LocalStorage, I as Logger, j as Router, w as RouterRef, T as SessionStorage, F as State, H as createUUID, L as getComponentById, D as getComponentElemById, $ as isComponentLoggerQuiet, M as isRouterLoggerQuiet, N as setComponentLoggerCallback, U as setRouterLoggerCallback };