web-signature
Version:
Primitive and fast framework for rendering web interfaces
2 lines (1 loc) • 6.77 kB
JavaScript
class e{element;instance;constructor(e,n){this.instance=e,this.element=n}}const n={"element-not-found":"Element not found for selector: #selector","prop-is-required":"Property '#prop' in component '#component' is required but not provided.","unsupported-type-for-property":"Unsupported type for property '#prop' in component '#component': #type","invalid-value-for-property":"Invalid value for property '#prop' in component '#component': #value (value: #attr)","multiple-root-elements":"Component '#component' must render a single root element. \n\t#elements","ref-collision":"Ref collision detected for ref '#ref' in component '#component'.",unknown:"An unknown error occurred.","unknown-from":"An unknown error occurred in component '#from'.","stack-overflow":"Stack Overflow detected: possible recursive component rendering."};let t=0;class r{components={};refs={};libs={};constructor(){}add(e,n){const t="string"==typeof n?n:e.name;this.components[t]&&console.warn(new Error(`Component with name ${t} already exists.`)),this.components[t]=e}register(e,...n){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists.`));const t=e.list().filter(e=>!(e.name in n));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:t.map(e=>e.name),dependencies:e.libs};for(const n of t)this.add(n.component,`${e.name}-${n.name}`)}lib(e){return this.libs[e]}libraries(){const e=e=>{let n=e.name;return e.version&&(n+=`@${e.version}`),e.author&&(n+=`#${e.author}`),n},n=(t,r=new Set)=>{const o={};for(const[i,s]of Object.entries(t)){const t=e(s);r.has(t)||(r.add(t),o[t]={components:s.components,dependencies:n(s.dependencies,r)})}return o};return n(this.libs)}contactWith(e,...n){const t=this.refs[e];if(!t)throw new Error(`Ref with name ${e} does not exist.`);const r=t.instance;return r.onContact?.(...n)}updateRef(e){const n=this.refs[e];if(!n)throw new Error(`Ref with name ${e} does not exist.`);const t=n.instance;let r="";try{r=t.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:t.name,err:e}}const o=document.createElement("template");var i;i=()=>{if(1!==o.content.children.length)throw new Error(`Component '${t.name}' must render a single root element.`);const e=o.content.firstElementChild;this.render(o.content),t.onRender?.(),n.element.replaceWith(e),n.element=e,t.onMount?.(e)},"string"==typeof r?(o.innerHTML=r.trim(),i()):r instanceof Promise&&r.then(e=>{o.innerHTML=e.trim(),i()}).catch(e=>{throw{id:"unknown-from",from:t.name,err:e}})}contact(e,t){new Promise((n,r)=>{try{const n=document.querySelector(e);if(!n)return void r({id:"element-not-found",selector:e});const o=document.createElement("div");o.innerHTML=n.innerHTML,this.render(o),n.replaceChildren(...Array.from(o.childNodes)),t&&t()}catch(e){e instanceof Error?e instanceof RangeError&&e.message.includes("stack")?r({id:"stack-overflow",err:e}):r({id:"unknown",err:e}):r(e)}}).catch(e=>{let t=n[e.id];throw Object.keys(e).filter(e=>!(e in["id","err"])).forEach(n=>{t=t.replace(new RegExp(`#${n}`,"gm"),String(e[n]))}),e.id in["unknown","unknown-from"]?console.error(`[${e.id}] ${t}`,e.err):console.error(`[${e.id}] ${t}`),"Page rendering was interrupted by Signature due to the above error."})}render(n){for(const r of Object.keys(this.components)){const o=this.components[r];for(const i of Array.from(n.querySelectorAll(r)).concat(Array.from(n.querySelectorAll(`[si-component="${r}"]`)))){const n=new o;if(n.onInit?.(),i instanceof HTMLElement){n.content=i.innerHTML.trim();for(const e of Object.keys(n.props)){const t=i.getAttribute(e);if(null===t){if(n.props[e].required)throw{id:"prop-is-required",component:r,prop:e};n.data[e]=null}else if(""===t){if(n.props[e].required)throw{id:"prop-is-required",component:r,prop:e};n.props[e].isValid(t)&&(n.data[e]=null)}else{let o;switch(n.props[e].type){case"boolean":o=Boolean(t);break;case"number":o=Number(t);break;case"string":o=String(t);break;case"array":try{o=JSON.parse(t)}catch(n){throw{id:"invalid-value-for-property",component:r,prop:e,value:t,attr:t}}break;default:if(n.props[e].required)throw{id:"unsupported-type-for-property",component:r,prop:e,type:n.props[e].type}}if(void 0!==o){if(!n.props[e].isValid(o))throw{id:"invalid-value-for-property",component:r,prop:e,value:o,attr:t};if(n.props[e].validate&&!n.props[e].validate(o))throw{id:"invalid-value-for-property",component:r,prop:e,value:o,attr:t};n.data[e]=o,n.onPropParsed?.(n.props[e],o)}}}n.onPropsParsed?.()}const s=document.createElement("template");let a="";try{a=n.render()}catch(e){if(e instanceof Error)throw{id:"unknown-from",from:n.name,err:e}}(e=>{if("string"==typeof a)s.innerHTML=a.trim(),e();else if(a instanceof Promise)try{a.then(n=>{s.innerHTML=n.trim(),e()}).catch(e=>{throw{id:"unknown-from",from:n.name,err:e}})}catch(e){console.log(1)}})(()=>{if(s.content.children.length>1)throw{id:"multiple-root-elements",component:r,elements:s.innerHTML};this.render(s.content),n.onRender?.();const o=s.content.firstElementChild;if(i.hasAttribute("ref")||n.options.generateRefIfNotSpecified){let s=i.getAttribute("ref");if(null===s&&(s=""),t++,""===s&&(s=`r${t}${Math.random().toString(36).substring(2,15)}${t}`),this.refs[s])throw{id:"ref-collision",ref:s,component:r};this.refs[s]=new e(n,o),o.setAttribute("ref",s),n.ref={id:s,contact:(...e)=>this.contactWith(s,...e),update:()=>this.updateRef(s)}}i.replaceWith(s.content),n.onMount?.(o)})}}}}class o{content;options={generateRefIfNotSpecified:!1};ref;props={};data={};onInit(){}onRender(){}onMount(e){}onContact(...e){}onPropsParsed(){}onPropParsed(e,n){}}class i{type;required=!0;validate;constructor(e,n=!0,t){this.type=e,this.required=n,this.validate=t||(()=>!0)}isValid(e){switch(this.type){case"boolean":return"boolean"==typeof e;case"number":return"number"==typeof e&&!isNaN(e);case"string":return"string"==typeof e;case"array":return Array.isArray(e);case"null":return null===e;default:return!1}}}class s{name;version;author;libs={};components={};constructor(e,n,t){this.name=e,this.author=n,this.version=t}add(e,n){const t="string"==typeof n?n:e.name;this.components[t]&&console.warn(new Error(`Component with name ${t} already exists.`)),this.components[t]=e}register(e,...n){this.libs[e.name]&&console.warn(new Error(`Library with name ${e.name} already exists in ${this.name}.`));const t=e.list().filter(e=>!(e.name in n));this.libs[e.name]={name:e.name,version:e.version,author:e.author,components:t.map(e=>e.name),dependencies:e.libs};for(const n of t)this.add(n.component,`${e.name}-${n.name}`)}get(e){return this.components[e]}lib(e){return this.libs[e]}list(){return Object.entries(this.components).map(([e,n])=>({component:n,name:e}))}}function a(){return new r}export{o as Component,s as Library,i as Prop,r as Signature,a as default};