solarite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build.bat +3 -0
- package/build/build.js +139 -0
- package/build/lib/rollup.min.js +11 -0
- package/build/lib/source-map.min.js +1 -0
- package/build/lib/terser.min.js +1 -0
- package/dist/Solarite-debug.js +4143 -0
- package/dist/Solarite.js +3740 -0
- package/dist/Solarite.min.js +4 -0
- package/docs/index.md +423 -0
- package/docs/js/Playground.js +184 -0
- package/docs/js/codemirror/codemirror6.js +32036 -0
- package/docs/js/codemirror/themeSolarIce.js +312 -0
- package/docs/js/documentation.js +32 -0
- package/docs/js/ui/CodeEditor.js +840 -0
- package/docs/js/ui/DarkToggle.js +52 -0
- package/docs/js/ui/FlexResizer.js +142 -0
- package/docs/js/util/Draggable2.js +151 -0
- package/docs/js/util/Errors.js +9 -0
- package/docs/js/util/Html.js +147 -0
- package/docs/js/util/Icons.js +623 -0
- package/docs/js/util/Input.js +253 -0
- package/docs/js/util/Util.js +88 -0
- package/docs/js/util/delve.js +43 -0
- package/docs/media/FiraCode400.woff2 +0 -0
- package/docs/media/cabin-latin-700.woff2 +0 -0
- package/docs/media/documentation.css +93 -0
- package/docs/media/eternium.css +1123 -0
- package/docs/media/solarite-machine.webp +0 -0
- package/index.html +325 -0
- package/package.json +33 -0
- package/readme.md +3 -0
- package/src/solarite/ExprPath.js +554 -0
- package/src/solarite/MultiValueMap.js +65 -0
- package/src/solarite/NodeGroup.js +706 -0
- package/src/solarite/NodeGroupManager.js +582 -0
- package/src/solarite/Shell.js +307 -0
- package/src/solarite/Solarite.js +19 -0
- package/src/solarite/Template.js +85 -0
- package/src/solarite/Util.js +264 -0
- package/src/solarite/createSolarite.js +267 -0
- package/src/solarite/getArg.js +99 -0
- package/src/solarite/hash.js +101 -0
- package/src/solarite/r.js +143 -0
- package/src/solarite/udomdiff.js +233 -0
- package/src/solarite/watch.js +302 -0
- package/src/solarite/watch2.js +439 -0
- package/src/unused/FastLookupArray.js +54 -0
- package/src/unused/Hashes.js +339 -0
- package/src/unused/InUse.test.js +92 -0
- package/src/unused/InUseMap.js +98 -0
- package/src/unused/LinkedList.js +117 -0
- package/src/unused/LinkedList.test.js +115 -0
- package/src/unused/Perf.js +47 -0
- package/src/unused/Template.js +108 -0
- package/src/util/Errors.js +9 -0
- package/src/util/Util.js +88 -0
- package/src/util/delve.js +43 -0
- package/tests/Benchmark.test.js +319 -0
- package/tests/NodeGroup.test.js +115 -0
- package/tests/Shell.test.js +75 -0
- package/tests/Solarite.test.js +2896 -0
- package/tests/Testimony.js +602 -0
- package/tests/index.html +75 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Version 2024.6.24.0406
|
|
2
|
+
// License: MIT
|
|
3
|
+
// https://github.com/vorticode/Solarite
|
|
4
|
+
var Util$1={callback(...t){var e=!1;let l=async(...t)=>{let s=[];if(!e)for(let e=0;l.length>e;e++)s.push(l[e](...t));return await Promise.all(s)};l[Symbol.iterator]=()=>{let t=0;return{next(){return l.length>t?{value:l[t++],done:!1}:{done:!0}}}};for(let t of Object.getOwnPropertyNames(Array.prototype))"length"!==t&&"constructor"!==t&&(l[t]=Array.prototype[t]);l.l=0,Object.defineProperty(l,"length",{get:()=>l.l,set(t){l.l=t}}),l.remove=t=>{let e=l.findIndex((e=>e===t));-1!==e&&l.splice(e,1)},l.pause=()=>e=!0,l.resume=()=>e=!1;for(let e of t)l.push(e);return l}};function delve(t,e,l=delveDontCreate){let s=l!==delveDontCreate,i=e.length;if(!t&&!s&&i)return;let r=0;for(let n of e){if(void 0===t[n]){if(!s)return;i-1>r&&(t[n]=(e[r+1]+"").match(/^\d+$/)?[]:{})}s&&r===i-1&&(t[n]=l),t=t[n],r++}return t}let delveDontCreate={};function getArg(el,name,val=null,type=ArgType.String){let attrVal=el.getAttribute(name);if(null!==attrVal&&(val=attrVal),Array.isArray(type))return type.includes(val)?val:void 0;if("function"==typeof type)return type(val);if(type===ArgType.Bool){let t="string"==typeof val?val.toLowerCase():val;return!["false","0",!1,0,null,void 0].includes(t)}switch(type){case ArgType.Int:return parseInt(val);case ArgType.Float:return parseFloat(val);case ArgType.String:return val||"";case ArgType.JSON:try{return JSON.parse(val)}catch(t){return val}case ArgType.Eval:try{return eval(`(${val})`)}catch(t){return val}default:return val}}var ArgType={Bool:"Bool",Int:"Int",Float:"Float",String:"String",JSON:"JSON",Eval:"Eval"};let lastObjectId=1,objectIds=new WeakMap;function getObjectId(t,e=null){e=e||"~\f";let l=objectIds.get(t);return l||(l=e+lastObjectId++,objectIds.set(t,l)),l}let isHashing=!0;function toJSON(){return isHashing?getObjectId(this):this}function getObjectHash(t){let e;Node.prototype.toJSON!==toJSON&&(Node.prototype.toJSON=toJSON,(()=>{}).toJSON!==toJSON&&(Function.prototype.toJSON=toJSON)),isHashing=!0;try{e=JSON.stringify(t)}catch(l){e=getObjectHashCircular(t)}return isHashing=!1,e}function getObjectHashCircular(t){const e=new Set;return JSON.stringify(t,((t,l)=>{if("object"==typeof l&&null!==l){if(e.has(l))return getObjectId(l);e.add(l)}return l}))}class MultiValueMap{data={};add(t,e){let l=this.data,s=l[t];s||(s=new Set,l[t]=s),s.add(e)}getAll(t){return this.data[t]||[]}delete(t,e=void 0){let l,s=this.data,i=s[t];if(i)return void 0!==e?(i.delete(e),l=e):(l=i.values().next().value,i.delete(l)),0===i.size&&delete s[t],l}hasValue(t){let e=this.data,l=[];for(let s in e)e[s].has(t)&&l.push(s);return l}}let Util={bindStyles(t,e){let l=e.getAttribute("data-style");l||(e.constructor.styleId||(e.constructor.styleId=1),l=e.constructor.styleId++,e.setAttribute("data-style",l));let s=e.tagName.toLowerCase();for(let e of t.childNodes)if(3===e.nodeType){let t=e.textContent,i=t.replace(/:host(?=[^a-z0-9_])/gi,s+'[data-style="'+l+'"]');t!==i&&(e.textContent=i)}}},div=document.createElement("div"),isEvent=t=>t.startsWith("on")&&t in div;function camelToDashes(t){return(t=(t=(t=t.replace(/([a-z0-9])([A-Z])/g,"$1-$2")).replace(/([A-Z])([A-Z][a-z])/g,"$1-$2")).replace(/([a-zA-Z])([0-9])/g,"$1-$2")).toLowerCase()}function findArrayDiff(t,e){if(t.length!==e.length)return-1;let l=t.length;for(let s=0;l>s;s++)if(t[s]!==e[s])return s;return!1}function htmlContext(t){if(null===t)return state={...defaultState},state.context;for(let e=0;t.length>e;e++){const l=t[e];switch(state.context){case htmlContext.Text:"<"===l&&t[e+1].match(/[a-z!]/i)&&(state.context=htmlContext.Tag,state.buffer="");break;case htmlContext.Tag:if(">"===l)state.context=htmlContext.Text,state.quote=null,state.buffer="";else{if(" "===l&&!state.buffer)continue;" "===l||"/"===l||"?"===l?state.buffer="":'"'===l||"'"===l||"="===l?(state.context=htmlContext.Attribute,state.quote="="===l?null:l,state.buffer=""):state.buffer+=l}break;case htmlContext.Attribute:state.quote||state.buffer.length||'"'!==l&&"'"!==l?l===state.quote||!state.quote&&state.buffer.length?(state.context=htmlContext.Tag,state.quote=null,state.buffer=""):state.quote||">"!==l?" "!==l&&(state.buffer+=l):(state.context=htmlContext.Text,state.quote=null,state.buffer=""):state.quote=l}}return state.context}htmlContext.Attribute="Attribute",htmlContext.Text="Text",htmlContext.Tag="Tag";let defaultState={context:htmlContext.Text,quote:null,buffer:"",lastChar:null},state={...defaultState};class ExprPath{type;attrName;attrValue;attrNames;nodeBefore;nodeMarker;get parentNode(){return this.nodeMarker.parentNode}parentNg;nodeGroups=[];nodesCache;nodeBeforeIndex;nodeMarkerPath;expr;constructor(t,e,l=PathType.Content,s=null,i=null){this.nodeBefore=t,this.nodeMarker=e,this.type=l,this.attrName=s,this.attrValue=i,l===PathType.Multiple&&(this.attrNames=new Set)}applyMultipleAttribs(t,e){Array.isArray(e)&&(e=e.flat().join(" "));let l=this.attrNames;if(this.attrNames=new Set,e){let l=(e+"").split(/([\w-]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s]+))/g).map((t=>t.trim())).filter((t=>t.length));for(let e of l){let[l,s]=e.split(/\s*=\s*/);s=(s||"").replace(/^(['"])(.*)\1$/,"$2"),t.setAttribute(l,s),this.attrNames.add(l)}}for(let e of l)this.attrNames.has(e)||t.removeAttribute(e)}applyEventAttrib(t,e,l){let s,i=this.attrName.slice(2),r=[];if(Array.isArray(e)){for(let t=0;e.length>t;t++)if("function"==typeof e[t]){s=e[t],r=e.slice(t+1);break}s||(s=setValue,r=[e[0],e.slice(1),t],t.value=delve(e[0],e.slice(1)))}else s=e;let n=getObjectId(t)+i,[o,h]=nodeEvents[n]||[];if(nodeEventArgs[n]=r,o!==s){o&&t.removeEventListener(i,h);let e=s,a=s=>e.call(l,...r,s,t);nodeEvents[n]=[e,a],t.addEventListener(i,a)}}applyValueAttrib(t,e,l){let s=e[l];if(this.attrValue||!1!==s&&null!=s)if(this.attrValue||!0!==s){let i=[];if(this.attrValue){for(let t=this.attrValue.length-1;t>=0;t--)if(i.unshift(this.attrValue[t]),t>0){let t=e[l];!1!==t&&null!=t&&i.unshift(t),l--}l++}else i.unshift(s);let r=i.join("");t.setAttribute(this.attrName,r),this.attrName in t&&(t[this.attrName]=r)}else t.setAttribute(this.attrName,"");else t.removeAttribute(this.attrName);return l}clone(t){let e,l,s=t,i=this.nodeMarkerPath;for(let t=i.length-1;t>0;t--)s=s.childNodes[i[t]];let r=s.childNodes;return e=r[i[0]],this.nodeBefore&&(l=r[this.nodeBeforeIndex]),new ExprPath(l,e,this.type,this.attrName,this.attrValue)}clearNodesCache(){let t=this,e=this.parentNode;for(;t&&t.parentNode===e;)t.nodesCache=null,t=t.parentNg?.parentPath;!function t(l){for(let s of l.nodeGroups)if(s)for(let l of s.paths)l.type===PathType.Content&&l.parentNode===e&&(l.nodesCache=null,t(l))}(this)}fastClear(){let t=this.nodeBefore.parentNode;if(this.nodeBefore===t.firstChild&&this.nodeMarker===t.lastChild){let e=t.parentNode;if(e&&t===e.firstChild&&t===e.lastChild&&!t.hasAttribute("id")){let e=document.createElement(t.tagName);e.append(this.nodeBefore,this.nodeMarker);for(let l of t.attributes)e.setAttribute(l.name,l.value);t.replaceWith(e)}else t.innerHTML="",t.append(this.nodeBefore,this.nodeMarker);return!0}return!1}getNodes(){let t;t=[];let e=this.nodeBefore.nextSibling,l=this.nodeMarker;for(;e&&e!==l;)t.push(e),e=e.nextSibling;return this.nodesCache=t,t}getParentNode(){return this.nodeMarker.parentNode}removeNodeGroup(t){let e=this.nodeGroups.indexOf(t);this.nodeGroups.splice(e),t.parentPath=null,this.clearNodesCache()}}function setValue(t,e,l){let s=l.value;"number"===l.type&&(s=parseFloat(s)),delve(t,e,s)}const PathType={Content:"content",Multiple:"attrName",Value:"attrValue",Component:"component",Comment:"comment"};function getNodePath(t){let e=[];for(;;){let l=t.parentNode;if(!l)break;e.push([].indexOf.call(t.parentNode.childNodes,t)),t=l}return e}function resolveNodePath(t,e){for(let l=e.length-1;l>=0;l--)t=t.childNodes[e[l]];return t}let nodeEvents={},nodeEventArgs={};class Shell{fragment;paths=[];template;events=[];ids=[];scripts=[];styles=[];staticComponents=[];constructor(t=null){if(!t)return;let e=[],l="\x3c!--!✨!--\x3e",s={};htmlContext(null);for(let i=0;t.length>i;i++){let r=t[i],n=htmlContext(r);if(n===htmlContext.Attribute){let t,e;if(r.replace(/<[a-z][a-z0-9]*-[a-z0-9-]+/gi,((l,s)=>{t=s+1,e=l.slice(1)})),e){let l=e+"-redcomponent-placeholder";r=r.slice(0,t)+l+r.slice(t+e.length),s[e]=l}}e.push(r),t.length-1>i&&e.push(n===htmlContext.Text?l:String.fromCharCode(57344+i))}let i,r=document.createElement("template"),n=e.join("");for(let t in s)n=n.replaceAll(`</${t}>`,`</${s[t]}>`);n?r.innerHTML=n:r.content.append(document.createTextNode("")),this.fragment=r.content;let o=[];const h=document.createTreeWalker(r.content,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_COMMENT|NodeFilter.SHOW_TEXT);for(;i=h.nextNode();)if(o.map((t=>t.remove())),o=[],1===i.nodeType)for(let t of i.attributes){let e=t.name.match(/^[\ue000-\uf8ff]$/);if(e)this.paths.push(new ExprPath(null,i,PathType.Multiple)),i.removeAttribute(e[0]);else{let e=t.value.split(/[\ue000-\uf8ff]/g);e.length>1&&(this.paths.push(new ExprPath(null,i,PathType.Value,t.name,2!==e.length||e[0].length||e[1].length?e:null)),i.setAttribute(t.name,e.join("")))}}else if(i.nodeType===Node.COMMENT_NODE&&"!✨!"===i.nodeValue){let t,e=i.previousSibling;e||(e=document.createComment("PathStart:"+this.paths.length),i.parentNode.insertBefore(e,i)),!i.nextSibling||8===i.nextSibling.nodeType&&"!✨!"===i.nextSibling.textContent?(t=i,t.textContent="PathEnd:"+this.paths.length):(t=i.nextSibling,o.push(i));let l=new ExprPath(e,t,PathType.Content);this.paths.push(l)}else if(i.nodeType===Node.COMMENT_NODE){let t=i.textContent.split(/[\ue000-\uf8ff]/g);for(let e=0;t.length-1>e;e++){let t=new ExprPath(i.previousSibling,i);t.type=PathType.Comment,this.paths.push(t)}}else if(i.nodeType===Node.TEXT_NODE&&["SCRIPT","STYLE"].includes(i.parentNode?.nodeName)){let t=i.textContent.split(l);if(t.length>1){let e=[];for(let l=0;t.length>l;l++){let s=document.createTextNode(t[l]);i.parentNode.insertBefore(s,i),l>0&&e.push(s)}for(let t,l=0;t=e[l];l++){let e=new ExprPath(t.previousSibling,t,PathType.Content);this.paths.push(e)}o.push(i)}}o.map((t=>t.remove()));for(let t of this.fragment.querySelectorAll("[is]"))t.setAttribute("_is",t.getAttribute("is"));for(let t of this.paths)t.nodeBefore&&(t.nodeBeforeIndex=[].indexOf.call(t.nodeBefore.parentNode.childNodes,t.nodeBefore)),t.nodeMarkerPath=getNodePath(t.nodeMarker),t.type===PathType.Value&&1===t.nodeMarker.nodeType&&(t.nodeMarker.tagName.includes("-")||t.nodeMarker.hasAttribute("is"))&&(t.type=PathType.Component);this.findEmbeds()}findEmbeds(){this.scripts=[].map.call(this.fragment.querySelectorAll("scripts"),(t=>getNodePath(t))),this.styles=[].map.call(this.fragment.querySelectorAll("style"),(t=>getNodePath(t)));let t=this.fragment.querySelectorAll("[id],[data-id]");for(let e of t){let t=e.getAttribute("data-id")||e.getAttribute("id");if(div.hasOwnProperty(t))throw Error(`<${e.tagName.toLowerCase()} id="${t}"> can't override existing HTMLElement property.`)}this.ids=[].map.call(t,(t=>getNodePath(t)));for(let t of this.fragment.querySelectorAll("*")){for(let e of t.attributes)isEvent(e.name)&&this.events.push([e.name,getNodePath(t)]);(t.tagName.includes("-")||t.hasAttribute("_is"))&&(this.paths.find((e=>e.nodeMarker===t))||this.staticComponents.push(getNodePath(t)))}}static get(t){let e=shells.get(t);return e||(e=new Shell(t),shells.set(t,e)),e}}let shells=new WeakMap;class Template{exprs=[];html=[];replaceMode;hashedFields;parentPath;nodeGroup;paths=[];constructor(t,e){this.html=t,this.exprs=e}toJSON(){return this.hashedFields||(this.hashedFields=[getObjectId(this.html,"Html"),this.exprs]),this.hashedFields}toNode(){return(new NodeGroupManager).render(this)}getCloseKey(){return"@"+this.hashedFields[0]}}const udomdiff=(t,e,l,s)=>{const i=l.length;let r=e.length,n=i,o=0,h=0,a=null;for(;r>o||n>h;)if(r===o){const e=i>n?h?l[h-1].nextSibling:l[n-h]:s;for(;n>h;){let s=l[h++];t.insertBefore(s,e)}}else if(n===h)for(;r>o;){let l=e[o];a&&a.has(l)||t.removeChild(l),o++}else if(e[o]===l[h])o++,h++;else if(e[r-1]===l[n-1])r--,n--;else if(e[o]===l[n-1]&&l[h]===e[r-1]){const s=e[--r].nextSibling;let i=l[h++],a=e[o++];t.insertBefore(i,a.nextSibling);let f=l[--n];t.insertBefore(f,s),e[r]=l[n]}else{if(!a){a=new Map;let t=h;for(;n>t;)a.set(l[t],t++)}if(a.has(e[o])){const s=a.get(e[o]);if(s>h&&n>s){let i=o,f=1;for(;++i<r&&n>i&&a.get(e[i])===s+f;)f++;if(f>s-h){const i=e[o];for(;s>h;){let e=l[h++];t.insertBefore(e,i)}}else{let s=e[o++],i=l[h++];t.replaceChild(i,s)}}else o++}else{let l=e[o++];t.removeChild(l)}}return l};class TransformerInfo{constructor(t,e,l){this.path=t,this.transformer=e,this.hash=l}}let pathToTransformer=new MultiValueMap;function watchGet(...t){let e,l=t.at(-1);if("function"==typeof l)e=t.slice(0,-1);else if(1===t.length){e=[t[0].slice(0,-1)];let s=t[0].at(-1);l=(...t)=>t[0][s]}let s=[];for(let t of e){let e=delve(watchSet(t[0]),t.slice(1));s.push(e)}let i=l(...s);if(!(i instanceof Template)){let t=l;l=function(){return new Template(["",""],[t(...arguments)])},i=l(...s)}let r=getObjectHash(i);for(let t of e){let e=serializePath(t);pathToTransformer.add(e,new TransformerInfo(t,l,r))}return i}function watchSet(t){return!0===t?.$isProxy?t:new Proxy(t,new ProxyHandler$1(t))}function forEach(t,e){let l=delve(t[0],t.slice(1)),s=NodeGroupManager.get(t[0]);s.clearSubscribers&&(s.clearSubscribers=!1,s.pathToLoopInfo=new MultiValueMap);let i=[...l.map(((l,s)=>watchGet([...t,s],e)))],r=new Template(["",""],[i]),n=new LoopInfo(r,e);return s.pathToLoopInfo.add(serializePath(t),n),r}function serializePath(t){return JSON.stringify([getObjectId(t[0]),...t.slice(1).map((t=>t+""))])}class ProxyHandler$1{constructor(t,e=[]){this.root=t,this.path=e}get(t,e){if("$isProxy"===e)return!0;if("splice"===e&&Array.isArray(t))return(e,l,...s)=>{let i=NodeGroupManager.get(this.root);if(l){let t=[];for(let s=e;e+l>s;s++){let e=serializePath([this.root,...this.path,s+""]),l=delve(this.root,[...this.path,s]);for(let s of pathToTransformer.getAll(e)){let e=getObjectHash(s.transformer(l));t.push(e)}}i.changes.push(new Change("delete",this.root,[...this.path,e+""],t))}let r=t.splice(e,l);if(s.length){let l;for(let t of i.getLoopInfo([this.root,...this.path])){let r=delve(this.root,[...this.path,e]);if(r){let n=t.itemTransformer(r),o=getObjectHash(n);if(l=i.nodeGroupsAvailable.data[o],l){let r=getObjectHash(t.template),o=i.nodeGroupsAvailable.data[r]||[];for(let t of o)for(let r of l)r.startNode.parentNode===t.startNode.parentNode&&i.changes.push(new Change("insert",this.root,[...this.path,e+""],s,n))}}l||i.changes.push(new Change("insert",this.root,[...this.path,e+""],s))}t.splice(e,0,...s)}return r};let l=Reflect.get(t,e);if(l&&"object"==typeof l){let t=new ProxyHandler$1(this.root,[...this.path,e]);return new Proxy(l,t)}return l}set(t,e,l){return NodeGroupManager.get(this.root).changes.push(new Change("set",this.root,[...this.path,e],l)),Reflect.set(t,e,l)}}class Change{constructor(t,e,l,s,i=null){this.action=t,this.root=e,this.path=l,this.value=s,this.beforeTemplate=i,this.transformerInfo=[];for(let t=this.path.length;t>0;t--){let e=this.path.slice(0,t),l=getObjectHash([this.root,...e]);this.transformerInfo.push(...pathToTransformer.getAll(l))}}}let logGets=!1,gets=[],withinSet=0;function watch(t,...e){if(!e.length)return t?.$proxyHandler?t:new Proxy(t,new ProxyHandler(t));{let l={};for(let s of e)l[s]=t[s],Object.defineProperty(t,s,{get:()=>new Proxy(t,new ProxyHandler(t,[],l))[s],set(t){return watch(this)[s]=t}})}}class ProxyHandler{serializedPath;constructor(t,e=[],l=null){this.root=t,this.path=e,this.props=l}getSerializedPath(t=null){return this.serializedPath||(this.serializedPath=JSON.stringify([getObjectId(this.root),...this.path.map((t=>t+""))])),null!==t?this.serializedPath.slice(0,-1)+',"'+t+'"]':this.serializedPath}extend(t){t+="",assert(!this.root.$proxyHandler);let e=new ProxyHandler(this.root,[...this.path,t],this.props);return this.serializedPath&&(e.serializedPath=this.getSerializedPath(t)),e}get(t,e){if("$proxyHandler"===e)return this;if("$removeProxy"===e)return delve(this.props||this.root,this.path);if("map"===e&&Array.isArray(t)){let e=NodeGroupManager.get(this.root);return e.clearSubscribersIfNeeded(),l=>{let s,i=[],r=t=>{let r=[];for(let n=0;t.length>n;n++){let o=this.extend(n),h=t[n];h.$proxyHandler||(h=new Proxy(h,o));let a=l(h,n,t);r.push(a);let f=this.getSerializedPath(n),u=new Subscriber(l,a);u.parent=s,e.subscribers.add(f,u),i.push([f,u])}return new Template(["",""],[r])};withinSet||(s=new Subscriber(r,null,l));let n=r(t);return withinSet||(s.template=n,s.children=i,e.subscribers.add(this.getSerializedPath(),s)),n}}if("splice"!==e&&"fastSplice"!==e||!Array.isArray(t)){if("indexOf"===e&&Array.isArray(t))return e=>t.indexOf(e.$removeProxy||e);{let l=Reflect.get(t===this.root&&this.props?this.props:t,e);if(logGets&&gets.push([this.root,...this.path,e]),l&&"object"==typeof l){let t=this.extend(e);return new Proxy(l,t)}return l}}{let l=NodeGroupManager.get(this.root);return(s,i,...r)=>{let n=r.length-i,o=t.length;if(i)for(let t=s;s+i>t;t++){let e=this.getSerializedPath(t);for(let t of l.subscribers.data[e]||[]){let e=t.template.nodeGroup;for(let t of e.getNodes())t.remove();l.nodeGroupsAvailable.delete(e.exactKey,e)}delete l.subscribers.data[e]}if(n){let t=this.getSerializedPath(),h=[...l.subscribers.getAll(t)][0],a=t=>{let s=t+n,i=this.getSerializedPath(t),r=this.getSerializedPath(s),o=l.subscribers.data[i];if(delete l.subscribers.data[i],l.subscribers.data[r]=o,"splice"===e){let e=delve(this.root,this.path);for(let l of o){let i=l.template.nodeGroup,r=getProxy(e[t],this,this.path,s),n=h.itemTransformer(r,s).exprs;i.applyExprs(n)}}};if(n>0)for(let t=o-1;t>=s+r.length+i;t--)a(t);else for(let t=s+r.length+i;o>t;t++)a(t)}if(r.length){let t=this.getSerializedPath(),e=this.getSerializedPath(s),l=NodeGroupManager.get(this.root);for(let n of l.subscribers.getAll(t)){let t=o-i>s?[...l.subscribers.getAll(e)].map((t=>t.template.nodeGroup.startNode)):[n.template.nodeGroup.endNode];for(let e of t)for(let t=0;r.length>t;t++){let i=this.extend(s+t);assert(!r[t].$proxyHandler);let o=new Proxy(r[t],i),h=n.itemTransformer(o),a=l.getNodeGroup(h,null,!0);for(let t of a.getNodes())e.parentNode.insertBefore(t,e);let f=i.getSerializedPath(),u=new Subscriber(n.itemTransformer,h);u.parent=n,l.subscribers.add(f,u)}n.template.nodeGroup.parentPath.clearNodesCache(),n.template.nodeGroup.nodesCache=null}}return t.splice(s,i,...r)}}}set(t,e,l){withinSet++;let s=Reflect.set(t===this.root&&this.props?this.props:t,e,l),i=[...this.path,e+""];return this.notify(i),withinSet--,s}notify(t,e=new Set){let l=NodeGroupManager.get(this.root),s=getObjectId(this.root),i=t.length;for(;i>=1;){let r=t.slice(0,i),n=delve(this.root,r),o=JSON.stringify([s,...r]);for(let t of l.subscribers.getAll(o)){if(e.has(t))continue;if(t.children){for(let[e,s]of t.children)l.subscribers.delete(e,s);t.children=void 0}let s=getProxy(n,this,r),i=t.transformer(s).exprs;for(let e of t.template.nodeGroup.paths)e.clearNodesCache();t.template.nodeGroup.applyExprs(i),t.parent&&e.add(t.parent)}i--}}}function getProxy(t,e,l,s){return t}function watchFunction(t,e){e.clearSubscribersIfNeeded(),logGets=!0;let l=()=>new Template(["",""],[t()]),s=l();for(let t of gets){let i=new Subscriber(l,s);e.subscribers.add(serializePath(t),i)}return gets=[],logGets=!1,s}class Subscriber{parent;children;constructor(t,e,l=null){this.transformer=t,this.template=e,this.itemTransformer=l}}class NodeGroup{manager;parentPath;startNode;endNode;paths=[];exactKey;closeKey;inUse;nodesCache;styles;pseudoRoot;currentComponentProps={};constructor(t,e=null){this.template=t,this.manager=e,t.nodeGroup=this;let l=Shell.get(t.html),s=l.fragment.cloneNode(!0),i="boolean"==typeof t.replaceMode?t.replaceMode:1===s.children.length&&s.firstElementChild?.tagName.replace(/-REDCOMPONENT-PLACEHOLDER$/,"")===e?.rootEl?.tagName;i&&(this.pseudoRoot=s.firstElementChild);let r=i?s.firstElementChild.childNodes:s.childNodes;this.startNode=r[0],this.endNode=r[r.length-1];for(let t of l.paths){let e=t.clone(s);e.parentNg=this,this.paths.push(e)}this.activateEmbeds(s,l),this.applyExprs(t.exprs)}activateEmbeds(root,shell){for(let t of shell.staticComponents){let e=resolveNodePath(root,t);e.tagName!==this.pseudoRoot?.tagName&&this.createNewComponent(e)}if(this.manager.rootEl){if(!1!==this.manager.options.ids)for(let t of shell.ids){let e=resolveNodePath(root,t),l=e.getAttribute("data-id")||e.getAttribute("id");this.manager.rootEl[l]=e}if(!1!==this.manager.options.styles){shell.styles.length&&(this.styles=new Map);for(let t of shell.styles){let e=resolveNodePath(root,t);Util.bindStyles(e,this.manager.rootEl),this.styles.set(e,e.textContent)}}if(!1!==this.manager.options.scripts)for(let path of shell.scripts){let script=resolveNodePath(root,path);eval(script.textContent)}}}updateStyles(){if(this.styles)for(let[t,e]of this.styles)e!==t.textContent&&Util.bindStyles(t,this.manager.rootEl)}applyExprs(t){let e,l,s=t.length-1;for(let i of this.paths.toReversed()){if(e=t[s],i.type===PathType.Content)this.applyNodeExpr(i,e);else{let r=i.nodeMarker,n=this.manager.rootEl&&r===this.pseudoRoot?this.manager.rootEl:r;l&&l!==this.pseudoRoot&&l!==r&&Object.keys(this.currentComponentProps).length&&(this.applyComponentExprs(l,this.currentComponentProps),this.currentComponentProps={}),i.type===PathType.Multiple?i.applyMultipleAttribs(n,e):i.nodeMarker!==this.pseudoRoot&&i.type===PathType.Component?this.currentComponentProps[i.attrName]=e:i.type===PathType.Comment||(null===i.attrValue&&("function"==typeof e||Array.isArray(e))&&isEvent(i.attrName)?i.applyEventAttrib(n,e,this.manager.rootEl||this.startNode.parentNode):s=i.applyValueAttrib(n,t,s)),l=i.nodeMarker}s--}l&&l!==this.pseudoRoot&&Object.keys(this.currentComponentProps).length&&(this.applyComponentExprs(l,this.currentComponentProps),this.currentComponentProps={}),this.updateStyles(),this.nodesCache=null}applyNodeExpr(t,e){let l=[],s=t.nodeGroups,i=[];t.nodeGroups=[],this.applyOneExpr(e,t,l,i),this.existingTextNodes=null;let r=!1;if(i.length){for(let[e,s]of i){let i=this.manager.getNodeGroup(l[e],!1);i.parentPath=t;let n=i.getNodes();1===n.length?l[e]=n[0]:(l[e]=n,r=!0),t.nodeGroups[s]=i}r&&(l=l.flat())}let n=t.getNodes();t.nodesCache=l,!1!==findArrayDiff(n,l)&&(this.parentPath&&this.parentPath.clearNodesCache(),n.length&&!l.length&&t.fastClear(n,l)||udomdiff(t.parentNode,n,l,t.nodeMarker),this.saveOrphans(s,n))}saveOrphans(t,e){let l=new Map;for(let e of t)l.set(e.startNode,e);for(let t,s=0;t=e[s];s++){let i;if(!t.parentNode&&(i=l.get(t))){let l=document.createDocumentFragment(),r=i.endNode;for(;t!==r;)l.append(t),s++,t=e[s];l.append(r)}}}applyOneExpr(t,e,l,s){if(t instanceof Template){t.parentPath=e,t.nodegroup=this;let i=this.manager.getNodeGroup(t,!0);i?(l.push(...i.getNodes()),e.nodeGroups.push(i)):(s.push([l.length,e.nodeGroups.length]),l.push(t),e.nodeGroups.push(null))}else if(t instanceof Node)t instanceof DocumentFragment?l.push(...t.childNodes):l.push(t);else if(Array.isArray(t))for(let i of t)this.applyOneExpr(i,e,l,s);else if("function"==typeof t)t=watchFunction(t,this.manager),this.applyOneExpr(t,e,l,s);else{let s=void 0===t||!1===t||null===t?"":t+"",i=e.nodeBefore.nextSibling;if(3!==i.nodeType||i.nextSibling!==e.nodeMarker||l.includes(i)){this.existingTextNodes||(this.existingTextNodes=e.getNodes().filter((t=>3===t.nodeType)));let t=this.existingTextNodes.findIndex((t=>t.textContent===s));-1!==t?l.push(...this.existingTextNodes.splice(t,1)):l.push(e.parentNode.ownerDocument.createTextNode(s))}else i.textContent!==s&&(i.textContent=s),l.push(i)}}applyComponentExprs(t,e){let l=getObjectHash(e),s=t.tagName.endsWith("-REDCOMPONENT-PLACEHOLDER"),i=t.hasAttribute("_is");s||i?t=this.createNewComponent(t,s,e):t.render&&componentHash.get(t)!==l&&t.render(e),componentHash.set(t,l)}createNewComponent(t,e=void 0,l=void 0){void 0===e&&(e=!t.hasAttribute("_is"));let s=(e?t.tagName.endsWith("-REDCOMPONENT-PLACEHOLDER")?t.tagName.slice(0,-25):t.tagName:t.getAttribute("is")).toLowerCase(),i={...l||{}};if(t.attributes.length){l||(l={});for(let e of t.attributes)l.hasOwnProperty(e.name)||(l[e.name]=e.value)}let r=customElements.get(s);if(!r)throw Error(`The custom tag name ${s} is not registered.`);let n=[...t.childNodes];NodeGroupManager.pendingChildren.push(n);let o=new r(l,n);e||o.setAttribute("is",t.getAttribute("is").toLowerCase()),t.replaceWith(o);for(let t in l){let e=l[t];t.startsWith("on")&&"function"==typeof e&&o.addEventListener(t.slice(2),(t=>e(t,o)))}let h=t.getAttribute("data-id")||t.getAttribute("id");h&&(this.manager.rootEl[h]=o);for(let e of this.paths)e.nodeMarker===t&&(e.nodeMarker=o),e.nodeBefore===t&&(e.nodeBefore=o);this.startNode===t&&(this.startNode=o),this.endNode===t&&(this.endNode=o),o.renderFirstTime&&o.renderFirstTime();for(let e of t.attributes)"_is"!==e.name&&o.setAttribute(e.name,e.value);for(let t in i){let e=i[t];"boolean"==typeof e?!1!==e&&null!=e&&o.setAttribute(t,""):["number","bigint","string"].includes(typeof e)&&o.setAttribute(t,e)}return o}getNodes(){let t=this.nodesCache;if(t)return t;t=[];let e=this.startNode,l=this.endNode?.nextSibling;for(;e&&e!==l;)t.push(e),e=e.nextSibling;return this.nodesCache=t,t}getParentNode(){return this.startNode?.parentNode}}let componentHash=new WeakMap;class NodeGroupManager{rootEl;rootNg;changes=[];nodeGroupsAvailable=new MultiValueMap;nodeGroupsInUse=[];options={};constructor(t=null){this.rootEl=t}render(t,e={}){if(this.mutationWatcherEnabled=!1,this.options=e,this.clearSubscribers=!1,!t&&""!==t)return this.rootEl.outerHTML="",this.mutationWatcherEnabled=!0,null;if(1!==t.html?.length||t.html[0]){let e,l=this.getNodeGroup(t,!0);l||(e=this.getNodeGroup(t,!1));let s=!this.rootNg;this.rootNg=l||e;let i=this.rootNg.getParentNode();if(this.rootEl){if(s){let t;if(this.rootEl.childNodes.length&&(t=document.createDocumentFragment(),t.append(...this.rootEl.childNodes)),i instanceof DocumentFragment?this.rootEl.append(i):i&&this.rootEl.append(...i.childNodes),t){for(let e of this.rootEl.querySelectorAll("slot[name]")){let l=e.getAttribute("name");l&&e.append(...t.querySelectorAll(`[slot='${l}']`))}let e=this.rootEl.querySelector("slot:not([name])");e&&e.append(t)}}}else this.rootEl=i;let r=this.rootNg;if(r.pseudoRoot&&r.pseudoRoot!==this.rootEl&&s)for(let t of r.pseudoRoot.attributes)this.rootEl.hasAttribute(t.name)||this.rootEl.setAttribute(t.name,t.value);this.reset()}else this.rootEl.innerHTML="";return this.mutationWatcherEnabled=!0,this.rootEl}findAndDeleteExact(t,e=!0,l=void 0){let s=this.nodeGroupsAvailable.delete(t,l);if(s){let t=this.nodeGroupsAvailable.delete(s.closeKey,s);if(this.nodeGroupsInUse.push(s),s.inUse=!0,t.inUse=!0,e){let t=s;for(;t=t?.parentPath?.parentNg;)if(!t.inUse){t.inUse=!0;let e=this.nodeGroupsAvailable.delete(t.exactKey,t);this.nodeGroupsAvailable.delete(t.closeKey,t),e&&this.nodeGroupsInUse.push(t)}}for(let t of s.paths)for(let e of t.nodeGroups)e.inUse||this.findAndDeleteExact(e.exactKey,!1,e),e.inUse=!0;return s}return null}findAndDeleteClose(t,e,l=!0){let s=this.nodeGroupsAvailable.delete(t);if(s){if(this.nodeGroupsAvailable.delete(s.exactKey,s),s.inUse=!0,l){let t=s;for(;t=t?.parentPath?.parentNg;)if(!t.inUse){t.inUse=!0;let e=this.nodeGroupsAvailable.delete(t.exactKey,t);e=this.nodeGroupsAvailable.delete(t.closeKey,t)}}s.exactKey=e,s.closeKey=t,this.nodeGroupsInUse.push(s)}return s}getNodeGroup(t,e=null,l=!1){let s,i=getObjectHash(t);if(!0===e){if(s=this.findAndDeleteExact(i),!s)return null}else{let e=t.getCloseKey();s=l?null:this.findAndDeleteClose(e,i),s?(window.debug&&!window.ng&&(window.ng=s),s.applyExprs(t.exprs)):(s=new NodeGroup(t,this),s.exactKey=i,s.closeKey=e,l?this.nodeGroupsAvailable.add(s.exactKey,s):this.nodeGroupsInUse.push(s))}return s.parentPath&&(s.parentPath=null),s}reset(){let t=this.nodeGroupsAvailable;for(let e of this.nodeGroupsInUse)e.inUse=!1,t.add(e.exactKey,e),t.add(e.closeKey,e);this.nodeGroupsInUse=[],this.changes=[]}clearSubscribers=!1;getLoopInfo(t){let e=serializePath(t);return[...this.pathToLoopInfo.getAll(e)]}pathToLoopInfo=new MultiValueMap;subscribers=new MultiValueMap;clearSubscribersIfNeeded(){this.clearSubscribers&&(this.pathToLoopInfo=new MultiValueMap,this.subscribers=new MultiValueMap,this.clearSubscribers=!1)}static get(t){let e=nodeGroupManagers.get(t);return e||(e=new NodeGroupManager(t),nodeGroupManagers.set(t,e)),e}}NodeGroupManager.pendingChildren=[];let nodeGroupManagers=new WeakMap;class LoopInfo{constructor(t,e){this.template=t,this.itemTransformer=e}}function r(t=void 0,...e){if(Array.isArray(t))return new Template(t,e);if(!(t instanceof Node)){if("string"==typeof t||t instanceof String){t.match(/^\s^</)&&(t=t.trim());let e=document.createElement("template");return e.innerHTML=t,1===e.content.childNodes.length?e.content.firstChild:e.content}if(void 0===t)return(t,...e)=>r(t,...e).toNode();if(t instanceof Template)return(new NodeGroupManager).render(t);if("function"==typeof t){let e=t,l=e();if("string"==typeof l)throw Error(`Please add the "r" prefix before the string "${l}"`);let s=new NodeGroupManager;l.replaceMode=!0;let i=s.render(l);return i.render=(()=>{l=e(),s.render(l)}).bind(i),i}throw Error("Unsupported arguments.")}{let l=t,s=e[0];if(e[0]instanceof Template)NodeGroupManager.get(l).render(s,e[1]),!l.childNodes.length&&this&&l.append(this.rootNg.getParentNode());else{if(!e.length||e[0]){l.shadowRoot&&(l.innerHTML="");let t=e[0];return(e,...s)=>{rendered.add(l);let i=r(e,...s);return NodeGroupManager.get(l).render(i,t)}}NodeGroupManager.get(l).render(null,e[1])}}}let rendered=new WeakSet;function defineClass(t,e,l){if(!customElements.getName(t)){(e=e||camelToDashes(t.name)).includes("-")||(e+="-element");let s=null;l&&(s={extends:l}),customElements.define(e,t,s)}}let elementClasses={},connected=new WeakSet;function createSolarite(t=null){let e=HTMLElement;t&&!t.includes("-")&&(t=t.toLowerCase(),e=elementClasses[t],e||(e=document.createElement(t).constructor,elementClasses[t]=e));let l=new Proxy(e,{construct:(e,l,s)=>(defineClass(s,null,t),Reflect.construct(e,l,s))});return class extends l{onConnect=Util$1.callback();onFirstConnect=Util$1.callback();onDisconnect=Util$1.callback();constructor(t={}){super(),!0===t.render?this.render():!1===t.render&&rendered.add(this);let e=NodeGroupManager.pendingChildren.pop();e&&(this.querySelector("slot")||this).append(...e),Object.defineProperty(this,"html",{set(e){rendered.add(this),"string"==typeof e?(console.warn("Assigning to this.html without the r template prefix."),this.innerHTML=e):this.modifications=r(this,e,t)}})}renderFirstTime(){!rendered.has(this)&&this.render&&this.render()}connectedCallback(){this.renderFirstTime(),connected.has(this)||(connected.add(this),this.onFirstConnect()),this.onConnect()}disconnectedCallback(){this.onDisconnect()}static define(e=null){defineClass(this,e,t)}renderWatched(){let t=NodeGroupManager.get(this),e=[];for(let l of t.changes)if("set"===l.action)for(let s of l.transformerInfo){let l=s.hash,i=delve(watchSet(s.path[0]),s.path.slice(1)),r=s.transformer(i),n=getObjectHash(r),o=[...t.nodeGroupsAvailable.data[l]];for(let t of o)e.push([t,l,n,r.exprs,s])}else if("delete"===l.action)for(let e of l.value){let l=[...t.nodeGroupsAvailable.getAll(e)];for(let t of l){t.parentPath&&t.parentPath.clearNodesCache();for(let e of t.getNodes())e.remove()}}else if("insert"===l.action){let e=l.beforeTemplate?t.getNodeGroup(l.beforeTemplate,!0):null,s=[l.root,...l.path];for(let i of t.getLoopInfo([l.root,...l.path.slice(0,-1)])){let r=e?.startNode||i.template.parentPath.nodeMarker,n=0;for(let o of l.value){let l=i.itemTransformer,o=watchGet([...s.slice(0,-1),1*s.at(-1)+n+""],l);n++;let h=t.getNodeGroup(o,!1,!0);h.parentPath=e?.parentPath||i.template.parentPath;for(let t of h.getNodes())r.parentNode.insertBefore(t,r);h.parentPath&&h.parentPath.clearNodesCache()}}}for(let[l,s,i,r,n]of e)l.applyExprs(r),t.nodeGroupsAvailable.data[s].delete(l),l.exactKey=n.hash=i,t.nodeGroupsAvailable.add(l.exactKey,l);return t.changes=[],[]}getArg(t,e=null,l=ArgType.String){return getArg(this,t,e,l)}}}let Solarite=new Proxy(createSolarite(),{apply:(t,e,l)=>createSolarite(...l)});export{ArgType,Solarite,forEach,getArg,r,watch,watchGet,watchSet};
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Solarite Documentation
|
|
3
|
+
append-head: <script src="docs/js/ui/DarkToggle.js"></script><script type="module" src="docs/js/documentation.js"></script><link rel="stylesheet" href="docs/media/documentation.css"><link rel="stylesheet" href="/docs/media/eternium.css">
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- To create documentation: (1) Open in Typora. (2) Select the GitHub theme. (3) Export as html with styles to index.html. -->
|
|
8
|
+
|
|
9
|
+
# Solarite Docs
|
|
10
|
+
|
|
11
|
+
Solarite is a small (10KB min+gzip), fast, compilation-free JavaScript web component library that closely follows modern web standards.
|
|
12
|
+
|
|
13
|
+
This project is currently in ALPHA stage and not yet recommended for production code. This documentations is also incomplete.
|
|
14
|
+
|
|
15
|
+
If using Visual Studio Code, the Leet-Html extension is recommended to syntax highlight html inside template strings.
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Type here to edit this code!
|
|
19
|
+
import {Solarite, r} from '/src/solarite/Solarite.js';
|
|
20
|
+
|
|
21
|
+
class ShoppingList extends Solarite {
|
|
22
|
+
constructor(items=[]) {
|
|
23
|
+
super();
|
|
24
|
+
this.items = items;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addItem() {
|
|
28
|
+
this.items.push({name: '', qty: 0});
|
|
29
|
+
this.render();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
removeItem(item) {
|
|
33
|
+
this.items.splice(this.items.indexOf(item), 1);
|
|
34
|
+
this.render();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
this.html = r`
|
|
39
|
+
<shopping-list>
|
|
40
|
+
<style>
|
|
41
|
+
:host input { width: 50px }
|
|
42
|
+
</style>
|
|
43
|
+
<button onclick=${this.addItem}>Add Item</button>
|
|
44
|
+
${this.items.map(item => r`
|
|
45
|
+
<div style="display: flex; flex-direction: row">
|
|
46
|
+
<input value=${item.name} oninput=${[item, 'name']} placeholder="Name">
|
|
47
|
+
<input value=${item.qty} oninput=${[item, 'qty']}>
|
|
48
|
+
<button onclick=${[this.removeItem, item]}>x</button>
|
|
49
|
+
</div>
|
|
50
|
+
`)}
|
|
51
|
+
<pre>items = ${() => JSON.stringify(this.items, null, 4)}</pre>
|
|
52
|
+
</shopping-list>`
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
document.body.append(new ShoppingList()); // adds a child named <shopping-list>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
==TODO Does it re-render all Items on update?==
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
==TODO: Show benchmark==
|
|
63
|
+
|
|
64
|
+
- No custom build steps and no dependencies. Not even Node.js. Just `import` Solarite.js or Solarite.min.js.
|
|
65
|
+
- Creates native HTML Elements and Web Components which can be used anywhere in your document and alongside other libraries.
|
|
66
|
+
- No need to set up state. Instead, use any regular variables or data structures in html templates.
|
|
67
|
+
- Minimal updates on render
|
|
68
|
+
- Local (scoped) styles
|
|
69
|
+
- Two-way form element binding.
|
|
70
|
+
- Optional shadow DOM (coming soon)
|
|
71
|
+
- Optional JSX support (coming soon)
|
|
72
|
+
- MIT license. Free for commercial use. No attribution needed.
|
|
73
|
+
|
|
74
|
+
## Using
|
|
75
|
+
|
|
76
|
+
Import one of these pre-bundled es6 modules into your project:
|
|
77
|
+
|
|
78
|
+
- [RedComponent.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 76KB
|
|
79
|
+
- [RedComponent.min.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 21KB / 7KB gzipped
|
|
80
|
+
|
|
81
|
+
==TODO: NPM==
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
## Concepts
|
|
86
|
+
|
|
87
|
+
### Creating Web Components
|
|
88
|
+
|
|
89
|
+
In this minimal example, we make a new class called `MyComponent` and provide a `render()` function to set its html.
|
|
90
|
+
|
|
91
|
+
All browsers require custom web component names to have a dash in the middle. Red Component looks at the case of the class name and converts it to a name with dashes. If it can't find at least one place to put a dash, it will append `-element` to the end.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
95
|
+
|
|
96
|
+
class MyComponent extends Solarite {
|
|
97
|
+
name = 'Red Component';
|
|
98
|
+
render() {
|
|
99
|
+
this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
document.body.append(new MyComponent());
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A JetBrains IDE like [WebStorm](https://www.jetbrains.com/webstorm/), [PhpStorm](https://www.jetbrains.com/phpstorm/), or [IDEA](https://www.jetbrains.com/idea/) will syntax highlight the html template strings.
|
|
107
|
+
|
|
108
|
+
Note that the template strings use the `r` prefix. This `r` parses the html into a data structure that Red Component can use.
|
|
109
|
+
|
|
110
|
+
Alternatively, instead of instantiating the element in JavaScript, we could can instantiate the element directly from html. This only works if we first call `MyComponent.define()` so so that our element's tag name is mapped to our class:
|
|
111
|
+
|
|
112
|
+
```html2
|
|
113
|
+
<script>
|
|
114
|
+
// ...
|
|
115
|
+
// document.body.append(new MyComponent());
|
|
116
|
+
MyComponent.define()
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<my-component></my-component>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Internally, the `define()` function calculates the tag name from the class name and then calls the built-in [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define).
|
|
123
|
+
|
|
124
|
+
If you want the component to have a tag name that's different than the name derived from the class name, you can pass a different name to `define()`:
|
|
125
|
+
|
|
126
|
+
```javascript2
|
|
127
|
+
MyComponent.define('my-awesome-component')
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### The render() function, r, and this.html
|
|
131
|
+
|
|
132
|
+
The `r` function, when used as part of a template literal, converts the html and embedded expressions into a data structure. When that data structure is assigned to `this.html`, it updates the content of the web component. You can think of this like assigning to the browser's built-in `this.outerHTML` property, except in this case instead of replacing all of the content, only the changed elements are replaced, which is much faster.
|
|
133
|
+
|
|
134
|
+
The render() function is called automatically when an element is added to the DOM via [connectedCallback()](https://developer.mozilla.org/en-US/docs/Web/API/Web_components#connectedcallback).
|
|
135
|
+
|
|
136
|
+
Unlike other frameworks Red Component does not re-render automatically when data changes, so you should call the render() function manually as needed. This is a deliberate design choice to reduce "magic," since in some cases you may want to update internal data without rendering.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
Wrapping the web component's html in its tag name is optional. You could instead just assign the html for the child elements to `this.html`. But then you will have to set any attributes on your web component some other way.
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
144
|
+
|
|
145
|
+
class MyComponent extends Solarite {
|
|
146
|
+
name = 'Solarite';
|
|
147
|
+
render() {
|
|
148
|
+
// With optional element tags:
|
|
149
|
+
// this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
|
|
150
|
+
|
|
151
|
+
// Without optional element tags:
|
|
152
|
+
this.html = r`Hello <b>${this.name}!<b>`
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
document.body.append(new MyComponent());
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If you do provide the outer tag, its name must exactly match the "dashes" version of the class name, or a custom name if you pass one to `define()`.
|
|
160
|
+
|
|
161
|
+
### Inheriting from existing DOM elements.
|
|
162
|
+
|
|
163
|
+
Suppose you want to use a custom component for each `<tr>` in a `<table>`. Html won't allow you to put just any element as a child of table or tbody. In this case you can make your web component inherit from the browser's built in `<tr>` element:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
167
|
+
|
|
168
|
+
class LineItem extends Solarite('tr') {
|
|
169
|
+
constructor(user) {
|
|
170
|
+
super();
|
|
171
|
+
this.user = user;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
render() {
|
|
175
|
+
this.html = r`
|
|
176
|
+
<tr>
|
|
177
|
+
<td>${this.user.name}</td>
|
|
178
|
+
<td>${this.user.email}</td>
|
|
179
|
+
</tr>`
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
LineItem.define();
|
|
183
|
+
|
|
184
|
+
let table = document.createElement('table')
|
|
185
|
+
for (let i=0; i<10; i++) {
|
|
186
|
+
let user = {name: 'User ' + i, email: 'user'+i+'@example.com'};
|
|
187
|
+
table.append(new LineItem(user));
|
|
188
|
+
}
|
|
189
|
+
document.body.append(table)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Loops
|
|
193
|
+
|
|
194
|
+
Just as in some of the examples above, loops can be written with the build-in [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) function:
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
198
|
+
|
|
199
|
+
class TodoList extends Solarite {
|
|
200
|
+
render() {
|
|
201
|
+
this.html = r`
|
|
202
|
+
<todo-list>
|
|
203
|
+
${this.items.map(item =>
|
|
204
|
+
r`${item}<br>`
|
|
205
|
+
)}
|
|
206
|
+
</todo-list>`
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let list = new TodoList();
|
|
211
|
+
list.items = ['one', 'two', 'three'];
|
|
212
|
+
document.body.append(list); // calls render() if it hasn't been called already.
|
|
213
|
+
|
|
214
|
+
list.items[1] = '2';
|
|
215
|
+
list.render();
|
|
216
|
+
|
|
217
|
+
list.items.splice(1, 0, 'two and a half');
|
|
218
|
+
list.render();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Note that nested template literals must also have the `r` prefix. Otherwise they'll be rendered as escaped text instead of HTML elements.
|
|
222
|
+
|
|
223
|
+
When we change an element or add another element to the `items` list, calling `render()` only redraws the changed or new element. The other list items are not modified.
|
|
224
|
+
|
|
225
|
+
### Attributes
|
|
226
|
+
|
|
227
|
+
Attributes can be specified by inserting expressions inside a tag. An expression can be part or all of an attribute value, or a string specifying multiple whole attributes. For example:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
231
|
+
|
|
232
|
+
let style = 'width: 100px; height: 40px; background: orange';
|
|
233
|
+
let isEditable = true;
|
|
234
|
+
let height = 40;
|
|
235
|
+
|
|
236
|
+
class AttributeDemo extends Solarite {
|
|
237
|
+
render() {
|
|
238
|
+
this.html = r`
|
|
239
|
+
<attribute-demo class="big">
|
|
240
|
+
|
|
241
|
+
<div style=${style}>Look at me</div>
|
|
242
|
+
|
|
243
|
+
<div style="${'width: 100px'}; height: ${height}px; background: gray">Look at me</div>
|
|
244
|
+
|
|
245
|
+
<div style="width: 100px; height: 40px; background: red" ${'title="I have a title"'}>Hover me</div>
|
|
246
|
+
|
|
247
|
+
<div style="width: 100px; height: 40px; background: brown" contenteditable=${isEditable} >Edit me</div>
|
|
248
|
+
</attribute-demo>`
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
document.body.append(new AttributeDemo());
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Expressions can also toggle the presence of an attribute. In the last div above, if `isEditable` is false, null, or undefined, the contenteditable attribute will be removed.
|
|
255
|
+
|
|
256
|
+
Note that attributes can also be assigned to the root element, such as `class="big"` on the `<attribute-demo>` tag above.
|
|
257
|
+
|
|
258
|
+
### Events
|
|
259
|
+
|
|
260
|
+
Listen for events by assigning a function expression to any event attribute. Or by passing an array where the first item is a function and subsequent items are arguments to that function.
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
264
|
+
|
|
265
|
+
class EventDemo extends Solarite {
|
|
266
|
+
showMessage(message) {
|
|
267
|
+
alert(message);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
render() {
|
|
271
|
+
this.html = r`
|
|
272
|
+
<event-demo>
|
|
273
|
+
<div onclick=${()=>alert('I was clicked!')}>Click me</div>
|
|
274
|
+
<div onclick=${[this.showMessage, 'I too was clicked!']}>Click me</div>
|
|
275
|
+
</event-demo>`
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
document.body.append(new EventDemo());
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Event binding with an array containing a function and its arguments is slightly faster, since when render() is called, Red Component can see that the function hasn't changed, and it doesn't need to be unbound and rebound. But the performance difference is negligible in most cases.
|
|
282
|
+
|
|
283
|
+
Make sure to put your events inside `${...}` expressions, because classic events can't reference variables in the current scope.
|
|
284
|
+
|
|
285
|
+
### Two-Way Binding
|
|
286
|
+
|
|
287
|
+
==TODO: This demo should use auto-rendering==
|
|
288
|
+
|
|
289
|
+
Form elements can update the properties that provide their values if an event attribute such as `oninput` is assigned the path to a property to update:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
293
|
+
|
|
294
|
+
class BindingDemo extends Solarite {
|
|
295
|
+
|
|
296
|
+
constructor() {
|
|
297
|
+
super();
|
|
298
|
+
this.count = 0;
|
|
299
|
+
// autoRender(this, 'count');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
render() {
|
|
303
|
+
this.html = r`
|
|
304
|
+
<binding-demo>
|
|
305
|
+
<input type="number" value=${this.count} oninput=${[this, 'count']}>
|
|
306
|
+
<pre>count is ${this.count}</pre>
|
|
307
|
+
<button onclick=${()=>this.count=0}>Reset</button>
|
|
308
|
+
</binding-demo>`
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
document.body.append(new BindingDemo());
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
In addition to `<input>`, `<select>` and `<textarea>` can also use the `value` attribute to set their value on render. Likewise so can any custom web components that define a `value` property.
|
|
315
|
+
|
|
316
|
+
### Ids
|
|
317
|
+
|
|
318
|
+
Any element in the html with an `id` or `data-id` attribute is automatically bound to a property with the same name on the root element. But this only happens after `render()` is first called:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
322
|
+
|
|
323
|
+
class RaceTeam extends Solarite {
|
|
324
|
+
constructor() {
|
|
325
|
+
super();
|
|
326
|
+
|
|
327
|
+
// Id's are not set until render() is first called.
|
|
328
|
+
this.render();
|
|
329
|
+
|
|
330
|
+
// No need to render() again since we're changing the DOM manually.
|
|
331
|
+
this.driver.value = 'Mario';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
render() { this.html = r`
|
|
335
|
+
<race-team>
|
|
336
|
+
<input id="driver" value="Vermin Supreme">
|
|
337
|
+
<div data-id="car">Cutlas Supreme</div>
|
|
338
|
+
<div data-id="instructor.name">Lightning McQueen</div>
|
|
339
|
+
</race-team>`
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
let rt = new RaceTeam();
|
|
343
|
+
document.body.append(rt);
|
|
344
|
+
rt.car.style.border = '1px solid green';
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Ids that match built-in HTMLElement attribute names such as `title` or `disabled` are not allowed.
|
|
349
|
+
|
|
350
|
+
### Scoped Styles
|
|
351
|
+
|
|
352
|
+
Html with `style` elements will be rewritten so that the `:host` selector applies to the root element. This allows an element to specify styles that will apply only to itself and its children, while still inheriting styles from the rest of the document.
|
|
353
|
+
|
|
354
|
+
These local, "scoped" styles are implemented by:
|
|
355
|
+
|
|
356
|
+
1. Adding a `data-style` attribute to the root element with a unique, incrementing id value.
|
|
357
|
+
2. Replacing any `:host` selectors inside the style with `element-name[data-style="1"]`. For example the `:host` selector below becomes `fancy-text[data-style="1"]`.
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
import {Solarite, r} from '../dist/Solarite.js';
|
|
361
|
+
|
|
362
|
+
class FancyText extends Solarite {
|
|
363
|
+
render() {
|
|
364
|
+
return r`
|
|
365
|
+
<fancy-text>
|
|
366
|
+
<style>
|
|
367
|
+
:host { border: 10px dashed red } /* style for <fancy-text> */
|
|
368
|
+
:host p { text-shadow: 0 0 5px orange }
|
|
369
|
+
</style>
|
|
370
|
+
<p>I have a red border and shadow!</p>
|
|
371
|
+
</fancy-text>`
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
document.body.append(new FancyText());
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Note that if shadown-dom is used, the element will not rewrite the `:host` selector in styles, as browsers natively support the `:host` selector when inside shadow DOM.
|
|
378
|
+
|
|
379
|
+
### Sub Components
|
|
380
|
+
|
|
381
|
+
And constructors
|
|
382
|
+
|
|
383
|
+
Calling render() on a parent component will call it on sub-components too.
|
|
384
|
+
|
|
385
|
+
### Slots
|
|
386
|
+
|
|
387
|
+
### The r() function
|
|
388
|
+
|
|
389
|
+
### Classless Elements
|
|
390
|
+
|
|
391
|
+
### Watches (Experimental)
|
|
392
|
+
|
|
393
|
+
## Reference
|
|
394
|
+
|
|
395
|
+
## How it works
|
|
396
|
+
|
|
397
|
+
Suppose you're looping over an array of 100 objects and printing them to a list or table. Something like this:
|
|
398
|
+
|
|
399
|
+
```html2
|
|
400
|
+
<ul>
|
|
401
|
+
${tasks().map((task, index) => (
|
|
402
|
+
<li key={index} class={task.completed ? "completed" : ""}>
|
|
403
|
+
<input
|
|
404
|
+
type="checkbox"
|
|
405
|
+
checked={task.completed}
|
|
406
|
+
onChange={() => toggleTask(index)}
|
|
407
|
+
/>
|
|
408
|
+
{task.text}
|
|
409
|
+
<button onClick={() => deleteTask(index)}>Delete</button>
|
|
410
|
+
</li>
|
|
411
|
+
))}
|
|
412
|
+
</ul>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The code gets the array of raw strings created by tasks.map() using a template literal function. Then it creates a hash of each of those. Then it compares those hashes with the hashes from the last time rendering happened, and only update elements associated with the changed hashes.
|
|
416
|
+
|
|
417
|
+
## Differences from other Libraries
|
|
418
|
+
|
|
419
|
+
### React
|
|
420
|
+
|
|
421
|
+
### Lit.js
|
|
422
|
+
|
|
423
|
+
### Solid.js
|