lighterjs
Version:
A light weight and class based vanilla JS UI framework, with Component, Router, and State handling.
2 lines (1 loc) • 19 kB
JavaScript
(function(h,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(h=typeof globalThis<"u"?globalThis:h||self,f(h.lighterjs={}))})(this,function(h){"use strict";var M=Object.defineProperty;var N=(h,f,p)=>f in h?M(h,f,{enumerable:!0,configurable:!0,writable:!0,value:p}):h[f]=p;var o=(h,f,p)=>(N(h,typeof f!="symbol"?f+"":f,p),p);let f;const p=new Uint8Array(16);function _(){if(!f&&(f=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!f))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return f(p)}const d=[];for(let a=0;a<256;++a)d.push((a+256).toString(16).slice(1));function k(a,t=0){return(d[a[t+0]]+d[a[t+1]]+d[a[t+2]]+d[a[t+3]]+"-"+d[a[t+4]]+d[a[t+5]]+"-"+d[a[t+6]]+d[a[t+7]]+"-"+d[a[t+8]]+d[a[t+9]]+"-"+d[a[t+10]]+d[a[t+11]]+d[a[t+12]]+d[a[t+13]]+d[a[t+14]]+d[a[t+15]]).toLowerCase()}const v={randomUUID:typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function C(a,t,e){if(v.randomUUID&&!t&&!a)return v.randomUUID();a=a||{};const s=a.random||(a.rng||_)();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 k(s)}class L{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 D{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 w{constructor(t,e,s){o(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 O=()=>C();let S=!1;const g=new w("LIGHTER.js ROUTER *****");class x{constructor(t,e,s,r){o(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 l=`Route's redirect cannot be the same as the route '${t[i].route}'.`;throw g.error(l),new Error(l)}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 l=await t[i].beforeDraw({route:t[i],curRouteData:this.curRouteData,curRoute:this.curRoute,basePath:this.basePath,componentProps:this.componentProps,prevRouteData:null,prevRoute:null});l&&(this.curRoute=this.basePath+l,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 l=this._getRouteParams(this.routes[i].route,this.curRoute);l&&(t[i].attachId=e,this._createNewView(t[i],l),r=!0,this.curRouteData=t[i],this.curRouteData.params=l,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}S=!0});o(this,"routeChangeListener",t=>{this.setRoute(),this.changeRoute(this.curRoute,{forceUpdate:!0,ignoreBasePath:!0,doNotSetState:!0}),this.curHistoryState=t.state||{}});o(this,"_createPageTitle",t=>{let e=t.title;return this.langFn&&(t.titleKey?e=this.langFn(t.titleKey):g.warn(`Router has a langFn defined, but route '${t.id}' is missing the titleKey.`)),!t.title&&!t.titleKey&&(g.warn(`Route '${t.id}' is missing the title. Setting id as title.`),e=t.id),this.titlePrefix+e+this.titleSuffix});o(this,"_createRouteState",()=>{const t=Object.assign({},this.nextHistoryState);return this.nextHistoryState={},this.curHistoryState={},t});o(this,"replaceRoute",(t,e)=>{let s=this.basePath;e&&(s=""),t=s+t;const r=this._createRouteState();window.history.replaceState(r,"",t)});o(this,"setNextHistoryState",t=>{this.nextHistoryState=Object.assign(this.nextHistoryState,t)});o(this,"setCurHistoryState",t=>{this.curHistoryState=Object.assign(this.curHistoryState,t),window.history.replaceState(this.curHistoryState,"")});o(this,"getCurHistoryState",()=>this.curHistoryState);o(this,"changeRoute",async(t,e)=>{e||(e={});const s=e.forceUpdate,r=e.ignoreBasePath,i=e.doNotSetState,l=e.replaceState;let n=this.basePath;r&&(n=""),t=n+t;for(let u=0;u<this.routes.length;u++)if(this._compareRoutes(this.routes[u].route,t,!0)&&this.routes[u].beforeDraw){const y=await this.routes[u].beforeDraw({route:this.routes[u],curRouteData:this.curRouteData,curRoute:this.curRoute,basePath:this.basePath,componentProps:this.componentProps,prevRouteData:this.prevRouteData,prevRoute:this.prevRouteData});y&&(t=n+y);break}for(let u=0;u<this.routes.length;u++)if(this.routes[u].redirect&&this._compareRoutes(this.routes[u].route,t)){t=this.routes[u].redirect;break}if(this._compareRoutes(t,this.curRoute)&&!s)return;if(s&&(this.curRouteData.component.discard(!0),this.curRouteData.component=null),!i&&!l){const u=this._createRouteState();window.history.pushState(u,"",t)}else l&&this.replaceRoute(t,!0);this.prevRoute=this.curRoute,this.setRoute();let c=!1;for(let u=0;u<this.routes.length;u++){if(this._compareRoutes(this.routes[u].route,t)&&!t.includes(":")){c=!0,this.prevRouteData=Object.assign({},this.curRouteData),this.curRouteData=this.routes[u],document.title=this._createPageTitle(this.routes[u]),this._createNewView(this.routes[u]);break}const y=this._getRouteParams(this.routes[u].route,t);if(y){c=!0,this.prevRouteData=Object.assign({},this.curRouteData),this.curRouteData=this.routes[u],this.curRouteData.params=y,document.title=this._createPageTitle(this.routes[u]),this._createNewView(this.routes[u],y);break}}c||this.notFound(),this.rcCallback(this.curRoute)});o(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 l=r.length;i.length>r.length&&(l=i.length);for(let n=0;n<l;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+"/"});o(this,"getRoute",t=>t?this.curRoute.replace(this.basePath,""):this.curRoute);o(this,"getRouteData",()=>({...this.curRouteData,prevRouteData:this.prevRouteData}));o(this,"getRouteParams",()=>this.curRouteData.params);o(this,"isCurrent",t=>this.basePath+t===this.curRoute||this.basePath+t===this.curRoute+"/"||this.basePath+t+"/"===this.curRoute);o(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+"/"});o(this,"addRoute",t=>{t.route=this.basePath+t.route,this.routes.push(t),this._compareRoutes(t.route,this.curRoute)&&(this.curRouteData=t)});o(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 g.error(e),new Error(e)}this.prevRouteData=Object.assign({},this.curRouteData),this.curRouteData=t,document.title=this._createPageTitle(t),this._createNewView(t)});o(this,"draw",()=>{var t;(t=this.prevRouteData)!=null&&t.component&&(this.prevRouteData.component.discard(!0),this.prevRouteData.component=null),this.curRouteData.component.draw()});o(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),h.RouterRef=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,S=!1});o(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))});o(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 l={};for(let n=0;n<i;n++)if(s[n]&&s[n].includes(":")){if(!r[n])return null;const c=s[n].replace(":","");l[c]=r[n]}else if(!s[n]===void 0||!r[n]===void 0||s[n]!==r[n])return null;return l});if(S){const n="Router has already been initiated. Only one router per app is allowed.";throw g.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 g.error(n),new Error(n)}if(!e){const n="Missing attachId parameter. "+i;throw g.error(n),new Error(n)}if(!s){const n="Missing rcCallback (route change callback) parameter / function. "+i;throw g.error(n),new Error(n)}let l=!1;for(let n=0;n<t.routes.length;n++){if(t.routes[n].is404&&(l=!0),!t.routes[n].id&&!t.routes[n].redirect){const c="Route is missing the 'id' property.";throw g.error(c),new Error(c)}if(!t.routes[n].route){const c=`Route '${t.routes[n].id}' is missing the 'route' property.`;throw g.error(c),new Error(c)}if(!t.routes[n].source&&!t.routes[n].redirect){const c=`Route '${t.routes[n].id}' is missing the 'source' property.`;throw g.error(c),new Error(c)}}if(!l){const n="Could not find 404 template.";throw g.error(n),new Error(n)}h.RouterRef=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)}}h.RouterRef=null;const A=a=>g.setCallback(a),T=a=>a?g.turnOff():g.turnOn(),b={},m=new w("LIGHTER.js COMPO *****");class I{constructor(t){o(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,m.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&&m.warn("For redraws the prepend property is ignored, because the DOM element is replaced."),b[e.id]||(b[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});o(this,"add",t=>{let e=t;return e.isComponent||(e=new I(t)),this.children[e.id]=e,e.props.attachId||(e.parent=this),e});o(this,"addDraw",t=>this.add(t).draw());o(this,"addListener",t=>{let{id:e,target:s,type:r,fn:i}=t;if(!r||!i)throw m.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 m.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=l=>i(l,this),s.addEventListener(r,t.fn),this.listeners[e]=t)});o(this,"removeListener",t=>{if(!t)throw m.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]});o(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 b[this.id],this.discarding=!1});o(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)}});o(this,"_createDefaultTemplate",t=>t!=null&&t.tag?`<${t.tag}></${t.tag}>`:"<div></div>");o(this,"_createElement",t=>{if(t&&!this.elem)throw m.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)});o(this,"getComponentById",t=>E(t));o(this,"getComponentElemById",t=>P(t));o(this,"_checkParentAndAttachId",()=>{if(!this.parent&&!this.props.attachId)throw m.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 m.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=C(),this.props={id:this.id,...t},b[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=h.RouterRef,this.template=(t==null?void 0:t.template)||this._createDefaultTemplate(t),t!=null&&t.preCreateElement&&this._createElement()}paint(){}addListeners(){}}const j=a=>m.setCallback(a),U=a=>a?m.turnOff():m.turnOn(),E=a=>b[a],P=a=>{const t=b[a];return t!=null&&t.elem?t.elem:document.getElementById(a)},R={};class H{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 l;if(i.length===1){if(r){R[t]=e;return}l=this.state[i[i.length-1]],this.state[t]=e,this._checkListeners(l,e,t);return}let n=r?R[i[0]]:this.state[i[0]];n===void 0&&(r?R[i[0]]=n={}:this.state[i[0]]=n={});for(let c=1;c<i.length-1;c++)n[i[c]]===void 0&&(n[i[c]]={}),n=n[i[c]];r?n[i[i.length-1]]=e:(l=n[i[i.length-1]],n[i[i.length-1]]=e,this._checkListeners(l,e,t)),s&&!r&&this.addListener(t,s)}get(t,e){if(!t)return;const s=t.split(".");if(s.length===1)return e?R[t]:this.state[t];let r=e?R[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(R[t]===void 0)return;delete R[t];return}if(this.state[t]===void 0)return;delete this.state[t];return}let r=e?R[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 R}setG(t,e){this.set(t,e,null,!0)}removeG(t){this.remove(t,!0)}}h.Component=I,h.LocalStorage=L,h.Logger=w,h.Router=x,h.SessionStorage=D,h.State=H,h.createUUID=O,h.getComponentById=E,h.getComponentElemById=P,h.isComponentLoggerQuiet=U,h.isRouterLoggerQuiet=T,h.setComponentLoggerCallback=j,h.setRouterLoggerCallback=A,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})});