diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b57581..f0a30f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v1.0.23 (2026-07-06) + +### 修复 +- **组件 `$.` 属性响应式绑定修复**: + - 注释了组件属性初始化时的 `node.removeAttribute(attr.name)`,以防止 `$.` 开头的组件属性在第一次赋完初值后被从 DOM 上永久删除,从而使其能够滑入后置的指令收集队列进行 `_initBinding` 注册,建立了完整的 Proxy 驱动链。 + ## v1.0.22 (2026-07-04) ### 核心增强与样式合并优化 diff --git a/dist/state.js b/dist/state.js index 23bae78..cb8ddf3 100644 --- a/dist/state.js +++ b/dist/state.js @@ -501,7 +501,6 @@ if (prop[i]) o = o[_a2 = prop[i]] ?? (o[_a2] = {}); } o[prop[prop.length - 1]] = result; - node.removeAttribute(attr.name); } }); _makeComponent(node.tagName, node, scanObj); diff --git a/dist/state.min.js b/dist/state.min.js index 2a0b560..a9ff69c 100644 --- a/dist/state.min.js +++ b/dist/state.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ApigoState=e.ApigoState||{})}(this,function(e){"use strict";var t,s;const n={clone:e=>JSON.parse(JSON.stringify(e)),base64:e=>btoa(String.fromCharCode(...(new TextEncoder).encode(e))),unbase64:e=>(new TextDecoder).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0))),urlbase64:e=>n.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>n.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const s in t)void 0===e[s]&&(e[s]=t[s])},copyFunction:(e,t,...s)=>{s.forEach(s=>e[s]=t[s].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,s=0;return{add:n=>(e+=n,t++,s=e/t),get:()=>s,clear:()=>{e=0,t=0,s=0}}},newTimeCount:()=>{let e=0,t=0,s=0;return{start:()=>e=(new Date).getTime(),end:()=>{const n=(new Date).getTime(),a=n-e;return e=n,t+=a,s++,a},avg:()=>t/s}}},a=(e,t)=>t?e.querySelector(t):document.querySelector(e),i=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);globalThis.Util=n,globalThis.$=a,globalThis.$$=i;let r=null,o=null;const l=e=>r=e,c=e=>o=e,d=new Set,h=e=>d.add(e);function u(e={},t=null,s=null){const n={},a=new Map,i=new Map,l=(e,t)=>(i.has(e)||i.set(e,new Set),t?i.get(e).add(t):i.get(e).clear(),()=>i.get(e).delete(t)),c=(e,t)=>{i.has(e)&&i.set(e,new Set),i.get(e).delete(t)},h=t||(e=>n[e]),u=s||((e,t)=>n[e]=t);return Object.assign(n,e),new Proxy(n,{get:(e,t)=>"__watch"===t?l:"__unwatch"===t?c:"__isProxy"===t||(r&&(a.has(t)||a.set(t,new Set),a.get(t).add(r),r.node._states||(r.node._states=new Set),r.node._states.add(a)),h(t)),set(e,t,s){if(h(t)!==s&&u(t,s),i.has(t)&&i.get(t).forEach(n=>{const a=n(s);void 0!==a&&(s=a,e[t]=s)}),i.has(null)&&i.get(null).forEach(e=>e(s)),a.has(t)){const e=a.get(t);for(const t of e)t.node.isConnected?o!==t.node&&d.forEach(e=>e(t)):e.delete(t)}return!0}})}globalThis.NewState=u;let f=new URLSearchParams("undefined"!=typeof globalThis&&(null==(s=null==(t=globalThis.location)?void 0:t.hash)?void 0:s.substring(1))||"");const b=u({},e=>n.safeJson(f.get(e)),(e,t)=>{const s=f.get(e),n=void 0===t?void 0:JSON.stringify(t);s===n||null===s&&void 0===n||(void 0===t?f.delete(e):f.set(e,n),globalThis.location.hash="#"+f.toString())});"undefined"!=typeof globalThis&&globalThis.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=globalThis.location.hash)?void 0:e.substring(1))||""),s=new Set([...f.keys(),...t.keys()]);f=t,s.forEach(e=>b[e]=b[e])});const m=u({},e=>n.safeJson(localStorage.getItem(e)),(e,t)=>{const s=localStorage.getItem(e),n=void 0===t?void 0:JSON.stringify(t);s===n||null===s&&void 0===n||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,n))}),p=u({exitBlocks:0});globalThis.Hash=b,globalThis.LocalStorage=m,globalThis.State=p;let g=!1;const _=e=>{g=e},A=new Map;function y(e,t,s,n){const a={...n||{},...t||{}},i=Object.keys(a),r=Object.values(a),o=e+i.join(",");try{let t=A.get(o);return t||(t=new Function("Hash","LocalStorage","State",...i,e),A.set(o,t)),t.apply(s,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...r])}catch(a){return g||console.error(a,n,[e,n,t,s]),null}}function v(e,t,s,n){return e.includes("${")?y("return `"+e+"`",t,s,n):y("return "+e,t,s,n)}const E=new Map,$=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,s=null,...n)=>{E.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,s,n):$.push([e,s,n])},exists:e=>E.has(e.toUpperCase()),getSetupFunction:e=>E.get(e.toUpperCase()),_addTemplate:(e,t,s)=>{if(t){const s=document.createElement("TEMPLATE");s.setAttribute("component",e.toUpperCase()),s.content.appendChild(t),document.body.appendChild(s)}s&&s.forEach(e=>document.body.appendChild(e))},_initPending:()=>{$.forEach(([e,t,s])=>T._addTemplate(e,t,s)),$.length=0}};function N(e,t,s,n={}){e.attributes&&Array.from(e.attributes).forEach(e=>{if("class"!==e.name)if("style"===e.name)t.hasAttribute("style")?t.setAttribute("style",`${e.value}; ${t.getAttribute("style")}`):t.setAttribute("style",e.value);else if(t.hasAttribute(e.name)){const s=["$class","st-class"].includes(e.name),n=["$style","st-style"].includes(e.name);if(s||n){const n=t.getAttribute(e.name),a=e.value,i=s?" ":"; ",r=n.includes("${")?n:`\${${n}}`,o=a.includes("${")?a:`\${${a}}`;t.setAttribute(e.name,`${o}${i}${r}`)}}else t.setAttribute(e.name,e.value)});const a=[...t.classList];t.className="",t.classList.add(...e.classList),t.classList.add(...a);const i="TEMPLATE"===t.tagName?t.content:t,r="TEMPLATE"===e.tagName?e.content.childNodes:e.childNodes;Array.from(r).forEach(e=>i.appendChild(e)),e.tagName&&T.exists(e.tagName)&&O(e.tagName,t,s,n)}function O(e,t,s,n={}){if(n[e])return;n[e]=!0,s.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const a=T.getSetupFunction(e),r={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(r[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=u(t.state||{});const o=T.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&N(a,t,s,n),i(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",N(r[t],e,s,n))})}}a&&a(t)}let S=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,s)=>t.hasOwnProperty(s)?t[s]:e):e;const j=e=>S=e,x=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const s=t.split("||").map(e=>e.trim()),n={};if(s.length>1){const e=s[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>n[e.substring(1,e.length-1)]=s[t+1]||"")}return S(s[0],n)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,s){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),s):t.call(this,e,s)}}function C(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&C(e)}))}function M(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;l(e);let s=e.exp?e.tpl?v(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof s)try{s=v(s,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(l(null),e.lastResult=s,e.prop){const n=e.prop;let a=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref},e._thisObj=t._thisObj}),t._renderedNodes=[t._children]):(C(t),t._renderedNodes=[]);else if("each"===n)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",a=t.getAttribute("key");let i,r;if(s instanceof Map)i=Array.from(s.keys()),r=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);i=new Array(e.length);for(let t=0;te[t]}else i=Object.keys(s),r=e=>s[e];t._keyedNodes||(t._keyedNodes=new Map);const o=new Map,l=[];i.forEach((s,i)=>{const c=r(s),d=a?c&&"object"==typeof c?c[a]:c:s,h=null==d||o.has(d)?`st_key_${i}`:d;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(a=>{t.parentNode.insertBefore(a,t),a._ref[n]=s,a._ref[e]=c,L(a)})):(u=[],t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref,[n]:s,[e]:c},i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),u.push(i)})),o.set(h,u),l.push(u)}),t._keyedNodes.forEach(e=>e.forEach(e=>{C(e),e.remove()})),t._keyedNodes=o,t._renderedNodes=l}else C(t),t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||s||(y(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const n=s instanceof Array?s.includes(t.value):!!s;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?Promise.resolve().then(()=>{t.value!==String(s??"")&&(t.value=s)}):t.isContentEditable&&t.innerHTML!==String(s??"")&&(t.innerHTML=s);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:s}))}else if(["checked","disabled","readonly"].includes(n)&&(s=!!s),"boolean"==typeof s)s?t.setAttribute(n,""):t.removeAttribute(n);else if(void 0!==s)if("string"!=typeof s&&(s=JSON.stringify(s)),"text"===n)t.textContent=s??"";else if("html"===n)t.innerHTML=s??"";else if("IMG"===t.tagName&&"src"===n&&s.includes(".svg"))t.setAttribute("_src",s??"");else if("class"===n){void 0===t._staticClasses&&(t._staticClasses=t.getAttribute("class")||"");const e=t._staticClasses,n=s?e?`${s} ${e}`:s:e;t.setAttribute("class",n.trim().replace(/\s+/g," "))}else if("style"===n){void 0===t._staticStyles&&(t._staticStyles=t.getAttribute("style")||"");const e=t._staticStyles,n=s?e?`${s}; ${e}`:s:e;t.setAttribute("style",n.trim().replace(/;;+/g,";").replace(/^;+\s*|;\s*$/g,""))}else t.setAttribute(n,s??"")}}function w(e){e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),M(e)}h(e=>M(e));const L=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=x(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;if(e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=x(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0),"TEMPLATE"!==e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("$each")||e.hasAttribute("st-if")||e.hasAttribute("st-each")||e.hasAttribute("$$if")||e.hasAttribute("$$each")||e.hasAttribute("st-st-if")||e.hasAttribute("st-st-each"))){const s=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))&&["as","index"].includes(t.name)).forEach(t=>{s.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(s,e),s.content.appendChild(e),s._ref=e._ref,void L(s,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),s=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(e.name)),n=s[s.length-1];t.setAttribute(n.name,n.value),e.removeAttribute(n.name),["$each","st-each","$$each","st-st-each"].includes(n.name)&&Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(s=>{t.setAttribute(s.name,s.value),e.removeAttribute(s.name)}),Array.from(e.content.childNodes).forEach(e=>t.content.appendChild(e)),e.content.appendChild(t),t._ref=e._ref}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const s=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");s&&(Array.from(t.attributes).forEach(e=>s.setAttribute(e.name,e.value)),t.replaceWith(s))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let s=e;for(;s&&void 0===s._thisObj;)s=s.parentNode;t.thisObj=s?s._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}t.extendVars&&Object.assign(e._ref,t.extendVars),function(e,t){if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>M({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));T.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(s=>{var n;if(s.name.startsWith("$.")){const a=s.name.slice(2);let i=x(s.value);t.thisObj&&i.includes("this.")&&(i=i.replace(/\bthis\./g,"this.parent."));const r=v(i,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let o=e;const l=a.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[]));let s=[];"TEMPLATE"===e.tagName?["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].forEach(t=>e.hasAttribute(t)&&s.push(e.getAttributeNode(t))):s=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&e._thisObj!==t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,s.forEach(s=>{let n=0;s.name.startsWith("$$")||s.name.startsWith("st-st-")?n=2:(s.name.startsWith("$")||s.name.startsWith("st-"))&&(n=1);const a=2===n?s.name.startsWith("$$")?s.name.slice(2):s.name.slice(6):1===n?s.name.startsWith("$")?s.name.slice(1):s.name.slice(3):s.name;let i=s.value;if(e.removeAttribute(s.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:i,exp:n});else if(a.startsWith("on")){const s=a.slice(2);"update"===s&&(e._hasOnUpdate=!0),"load"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"unload"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(s,s=>y(i,{event:s,thisNode:e,...s.detail||{}},t.thisObj||e,e._ref||{}))}else{if("bind"===a){const s=["INPUT","TEXTAREA"].includes(e.tagName)&&["textarea","text","password","email","number","search","url","tel"].includes(e.type||"text")||e.isContentEditable;e.addEventListener(s?"input":"change",s=>{let n=e.isContentEditable?s.target.innerHTML:"checkbox"===e.type?s.target.checked:s.target.files||s.target.value||s.detail;c(e),_(!0),"checkbox"===e.type&&e._checkboxMultiMode?y(`!!checked ? (!${i}.includes(val) && ${i}.push(val)) : (index = ${i}.indexOf(val), index > -1 && ${i}.splice(index, 1))`,{val:e.value,checked:n,thisNode:e},t.thisObj||e,e._ref||{}):y(`${i} = val`,{val:n,thisNode:e},t.thisObj||e,e._ref||{}),_(!1),c(null)})}else"text"!==a||i||(i=e.textContent,e.textContent="");i&&(i=x(i),w({node:e,attr:a,tpl:i,exp:n}))}}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)}(e,{...t});[...e.childNodes||[]].forEach(s=>L(s,{thisObj:t.thisObj,extendVars:{...e._ref}}))},k=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._states&&e._states.forEach(t=>{for(const[s,n]of t)for(const t of n)t.node===e&&n.delete(t)}),e.childNodes&&e.childNodes.forEach(e=>k(e)))};if(globalThis.Component=T,globalThis.SetTranslator=j,globalThis.__unsafeRefreshState=L,"undefined"!=typeof document){const e=()=>{globalThis.Component&&globalThis.Component._initPending&&globalThis.Component._initPending();const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&L(e)}),e.removedNodes.forEach(e=>k(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),L(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}const P=L;e.$=a,e.$$=i,e.Component=T,e.Hash=b,e.LocalStorage=m,e.NewState=u,e.SetTranslator=j,e.State=p,e.Util=n,e.__unsafeRefreshState=P,e._returnCode=v,e._runCode=y,e.onNotifyUpdate=h,e.setActiveBinding=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ApigoState=e.ApigoState||{})}(this,function(e){"use strict";var t,s;const n={clone:e=>JSON.parse(JSON.stringify(e)),base64:e=>btoa(String.fromCharCode(...(new TextEncoder).encode(e))),unbase64:e=>(new TextDecoder).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0))),urlbase64:e=>n.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>n.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const s in t)void 0===e[s]&&(e[s]=t[s])},copyFunction:(e,t,...s)=>{s.forEach(s=>e[s]=t[s].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,s=0;return{add:n=>(e+=n,t++,s=e/t),get:()=>s,clear:()=>{e=0,t=0,s=0}}},newTimeCount:()=>{let e=0,t=0,s=0;return{start:()=>e=(new Date).getTime(),end:()=>{const n=(new Date).getTime(),a=n-e;return e=n,t+=a,s++,a},avg:()=>t/s}}},a=(e,t)=>t?e.querySelector(t):document.querySelector(e),i=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);globalThis.Util=n,globalThis.$=a,globalThis.$$=i;let r=null,o=null;const l=e=>r=e,c=e=>o=e,d=new Set,h=e=>d.add(e);function u(e={},t=null,s=null){const n={},a=new Map,i=new Map,l=(e,t)=>(i.has(e)||i.set(e,new Set),t?i.get(e).add(t):i.get(e).clear(),()=>i.get(e).delete(t)),c=(e,t)=>{i.has(e)&&i.set(e,new Set),i.get(e).delete(t)},h=t||(e=>n[e]),u=s||((e,t)=>n[e]=t);return Object.assign(n,e),new Proxy(n,{get:(e,t)=>"__watch"===t?l:"__unwatch"===t?c:"__isProxy"===t||(r&&(a.has(t)||a.set(t,new Set),a.get(t).add(r),r.node._states||(r.node._states=new Set),r.node._states.add(a)),h(t)),set(e,t,s){if(h(t)!==s&&u(t,s),i.has(t)&&i.get(t).forEach(n=>{const a=n(s);void 0!==a&&(s=a,e[t]=s)}),i.has(null)&&i.get(null).forEach(e=>e(s)),a.has(t)){const e=a.get(t);for(const t of e)t.node.isConnected?o!==t.node&&d.forEach(e=>e(t)):e.delete(t)}return!0}})}globalThis.NewState=u;let f=new URLSearchParams("undefined"!=typeof globalThis&&(null==(s=null==(t=globalThis.location)?void 0:t.hash)?void 0:s.substring(1))||"");const b=u({},e=>n.safeJson(f.get(e)),(e,t)=>{const s=f.get(e),n=void 0===t?void 0:JSON.stringify(t);s===n||null===s&&void 0===n||(void 0===t?f.delete(e):f.set(e,n),globalThis.location.hash="#"+f.toString())});"undefined"!=typeof globalThis&&globalThis.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=globalThis.location.hash)?void 0:e.substring(1))||""),s=new Set([...f.keys(),...t.keys()]);f=t,s.forEach(e=>b[e]=b[e])});const m=u({},e=>n.safeJson(localStorage.getItem(e)),(e,t)=>{const s=localStorage.getItem(e),n=void 0===t?void 0:JSON.stringify(t);s===n||null===s&&void 0===n||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,n))}),p=u({exitBlocks:0});globalThis.Hash=b,globalThis.LocalStorage=m,globalThis.State=p;let g=!1;const _=e=>{g=e},A=new Map;function y(e,t,s,n){const a={...n||{},...t||{}},i=Object.keys(a),r=Object.values(a),o=e+i.join(",");try{let t=A.get(o);return t||(t=new Function("Hash","LocalStorage","State",...i,e),A.set(o,t)),t.apply(s,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...r])}catch(a){return g||console.error(a,n,[e,n,t,s]),null}}function v(e,t,s,n){return e.includes("${")?y("return `"+e+"`",t,s,n):y("return "+e,t,s,n)}const E=new Map,$=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,s=null,...n)=>{E.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,s,n):$.push([e,s,n])},exists:e=>E.has(e.toUpperCase()),getSetupFunction:e=>E.get(e.toUpperCase()),_addTemplate:(e,t,s)=>{if(t){const s=document.createElement("TEMPLATE");s.setAttribute("component",e.toUpperCase()),s.content.appendChild(t),document.body.appendChild(s)}s&&s.forEach(e=>document.body.appendChild(e))},_initPending:()=>{$.forEach(([e,t,s])=>T._addTemplate(e,t,s)),$.length=0}};function N(e,t,s,n={}){e.attributes&&Array.from(e.attributes).forEach(e=>{if("class"!==e.name)if("style"===e.name)t.hasAttribute("style")?t.setAttribute("style",`${e.value}; ${t.getAttribute("style")}`):t.setAttribute("style",e.value);else if(t.hasAttribute(e.name)){const s=["$class","st-class"].includes(e.name),n=["$style","st-style"].includes(e.name);if(s||n){const n=t.getAttribute(e.name),a=e.value,i=s?" ":"; ",r=n.includes("${")?n:`\${${n}}`,o=a.includes("${")?a:`\${${a}}`;t.setAttribute(e.name,`${o}${i}${r}`)}}else t.setAttribute(e.name,e.value)});const a=[...t.classList];t.className="",t.classList.add(...e.classList),t.classList.add(...a);const i="TEMPLATE"===t.tagName?t.content:t,r="TEMPLATE"===e.tagName?e.content.childNodes:e.childNodes;Array.from(r).forEach(e=>i.appendChild(e)),e.tagName&&T.exists(e.tagName)&&O(e.tagName,t,s,n)}function O(e,t,s,n={}){if(n[e])return;n[e]=!0,s.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const a=T.getSetupFunction(e),r={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(r[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=u(t.state||{});const o=T.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&N(a,t,s,n),i(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",N(r[t],e,s,n))})}}a&&a(t)}let S=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,s)=>t.hasOwnProperty(s)?t[s]:e):e;const j=e=>S=e,x=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const s=t.split("||").map(e=>e.trim()),n={};if(s.length>1){const e=s[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>n[e.substring(1,e.length-1)]=s[t+1]||"")}return S(s[0],n)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,s){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),s):t.call(this,e,s)}}function C(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&C(e)}))}function M(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;l(e);let s=e.exp?e.tpl?v(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof s)try{s=v(s,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(l(null),e.lastResult=s,e.prop){const n=e.prop;let a=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref},e._thisObj=t._thisObj}),t._renderedNodes=[t._children]):(C(t),t._renderedNodes=[]);else if("each"===n)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",a=t.getAttribute("key");let i,r;if(s instanceof Map)i=Array.from(s.keys()),r=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);i=new Array(e.length);for(let t=0;te[t]}else i=Object.keys(s),r=e=>s[e];t._keyedNodes||(t._keyedNodes=new Map);const o=new Map,l=[];i.forEach((s,i)=>{const c=r(s),d=a?c&&"object"==typeof c?c[a]:c:s,h=null==d||o.has(d)?`st_key_${i}`:d;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(a=>{t.parentNode.insertBefore(a,t),a._ref[n]=s,a._ref[e]=c,L(a)})):(u=[],t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref,[n]:s,[e]:c},i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),u.push(i)})),o.set(h,u),l.push(u)}),t._keyedNodes.forEach(e=>e.forEach(e=>{C(e),e.remove()})),t._keyedNodes=o,t._renderedNodes=l}else C(t),t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||s||(y(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const n=s instanceof Array?s.includes(t.value):!!s;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?Promise.resolve().then(()=>{t.value!==String(s??"")&&(t.value=s)}):t.isContentEditable&&t.innerHTML!==String(s??"")&&(t.innerHTML=s);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:s}))}else if(["checked","disabled","readonly"].includes(n)&&(s=!!s),"boolean"==typeof s)s?t.setAttribute(n,""):t.removeAttribute(n);else if(void 0!==s)if("string"!=typeof s&&(s=JSON.stringify(s)),"text"===n)t.textContent=s??"";else if("html"===n)t.innerHTML=s??"";else if("IMG"===t.tagName&&"src"===n&&s.includes(".svg"))t.setAttribute("_src",s??"");else if("class"===n){void 0===t._staticClasses&&(t._staticClasses=t.getAttribute("class")||"");const e=t._staticClasses,n=s?e?`${s} ${e}`:s:e;t.setAttribute("class",n.trim().replace(/\s+/g," "))}else if("style"===n){void 0===t._staticStyles&&(t._staticStyles=t.getAttribute("style")||"");const e=t._staticStyles,n=s?e?`${s}; ${e}`:s:e;t.setAttribute("style",n.trim().replace(/;;+/g,";").replace(/^;+\s*|;\s*$/g,""))}else t.setAttribute(n,s??"")}}function w(e){e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),M(e)}h(e=>M(e));const L=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=x(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;if(e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=x(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0),"TEMPLATE"!==e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("$each")||e.hasAttribute("st-if")||e.hasAttribute("st-each")||e.hasAttribute("$$if")||e.hasAttribute("$$each")||e.hasAttribute("st-st-if")||e.hasAttribute("st-st-each"))){const s=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))&&["as","index"].includes(t.name)).forEach(t=>{s.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(s,e),s.content.appendChild(e),s._ref=e._ref,void L(s,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),s=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(e.name)),n=s[s.length-1];t.setAttribute(n.name,n.value),e.removeAttribute(n.name),["$each","st-each","$$each","st-st-each"].includes(n.name)&&Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(s=>{t.setAttribute(s.name,s.value),e.removeAttribute(s.name)}),Array.from(e.content.childNodes).forEach(e=>t.content.appendChild(e)),e.content.appendChild(t),t._ref=e._ref}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const s=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");s&&(Array.from(t.attributes).forEach(e=>s.setAttribute(e.name,e.value)),t.replaceWith(s))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let s=e;for(;s&&void 0===s._thisObj;)s=s.parentNode;t.thisObj=s?s._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}t.extendVars&&Object.assign(e._ref,t.extendVars),function(e,t){if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>M({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));T.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(s=>{var n;if(s.name.startsWith("$.")){const a=s.name.slice(2);let i=x(s.value);t.thisObj&&i.includes("this.")&&(i=i.replace(/\bthis\./g,"this.parent."));const r=v(i,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let o=e;const l=a.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[]));let s=[];"TEMPLATE"===e.tagName?["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].forEach(t=>e.hasAttribute(t)&&s.push(e.getAttributeNode(t))):s=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&e._thisObj!==t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,s.forEach(s=>{let n=0;s.name.startsWith("$$")||s.name.startsWith("st-st-")?n=2:(s.name.startsWith("$")||s.name.startsWith("st-"))&&(n=1);const a=2===n?s.name.startsWith("$$")?s.name.slice(2):s.name.slice(6):1===n?s.name.startsWith("$")?s.name.slice(1):s.name.slice(3):s.name;let i=s.value;if(e.removeAttribute(s.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:i,exp:n});else if(a.startsWith("on")){const s=a.slice(2);"update"===s&&(e._hasOnUpdate=!0),"load"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"unload"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(s,s=>y(i,{event:s,thisNode:e,...s.detail||{}},t.thisObj||e,e._ref||{}))}else{if("bind"===a){const s=["INPUT","TEXTAREA"].includes(e.tagName)&&["textarea","text","password","email","number","search","url","tel"].includes(e.type||"text")||e.isContentEditable;e.addEventListener(s?"input":"change",s=>{let n=e.isContentEditable?s.target.innerHTML:"checkbox"===e.type?s.target.checked:s.target.files||s.target.value||s.detail;c(e),_(!0),"checkbox"===e.type&&e._checkboxMultiMode?y(`!!checked ? (!${i}.includes(val) && ${i}.push(val)) : (index = ${i}.indexOf(val), index > -1 && ${i}.splice(index, 1))`,{val:e.value,checked:n,thisNode:e},t.thisObj||e,e._ref||{}):y(`${i} = val`,{val:n,thisNode:e},t.thisObj||e,e._ref||{}),_(!1),c(null)})}else"text"!==a||i||(i=e.textContent,e.textContent="");i&&(i=x(i),w({node:e,attr:a,tpl:i,exp:n}))}}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)}(e,{...t});[...e.childNodes||[]].forEach(s=>L(s,{thisObj:t.thisObj,extendVars:{...e._ref}}))},k=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._states&&e._states.forEach(t=>{for(const[s,n]of t)for(const t of n)t.node===e&&n.delete(t)}),e.childNodes&&e.childNodes.forEach(e=>k(e)))};if(globalThis.Component=T,globalThis.SetTranslator=j,globalThis.__unsafeRefreshState=L,"undefined"!=typeof document){const e=()=>{globalThis.Component&&globalThis.Component._initPending&&globalThis.Component._initPending();const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&L(e)}),e.removedNodes.forEach(e=>k(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),L(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}const P=L;e.$=a,e.$$=i,e.Component=T,e.Hash=b,e.LocalStorage=m,e.NewState=u,e.SetTranslator=j,e.State=p,e.Util=n,e.__unsafeRefreshState=P,e._returnCode=v,e._runCode=y,e.onNotifyUpdate=h,e.setActiveBinding=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}); diff --git a/package.json b/package.json index 4133525..2707a54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apigo.cc/state", - "version": "1.0.22", + "version": "1.0.23", "type": "module", "main": "dist/state.js", "module": "dist/state.js", diff --git a/src/engine.js b/src/engine.js index 35fb64c..f841480 100644 --- a/src/engine.js +++ b/src/engine.js @@ -12,469 +12,469 @@ const _components = new Map(); const _pendingTemplates = []; export const Component = { - getTemplate: name => document.querySelector(`template[component="${name.toUpperCase()}"]`), - register: (name, setupFunc, templateNode = null, ...globalNodes) => { - _components.set(name.toUpperCase(), setupFunc); - if (document.readyState !== 'loading') Component._addTemplate(name, templateNode, globalNodes); - else _pendingTemplates.push([name, templateNode, globalNodes]); - }, - exists: (name) => _components.has(name.toUpperCase()), - getSetupFunction: (name) => _components.get(name.toUpperCase()), - _addTemplate: (name, templateNode, globalNodes) => { - if (templateNode) { - const template = document.createElement('TEMPLATE'); - template.setAttribute('component', name.toUpperCase()); - template.content.appendChild(templateNode); - document.body.appendChild(template); - } - if (globalNodes) globalNodes.forEach(node => document.body.appendChild(node)); - }, - _initPending: () => { - _pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes)); - _pendingTemplates.length = 0; - } + getTemplate: name => document.querySelector(`template[component="${name.toUpperCase()}"]`), + register: (name, setupFunc, templateNode = null, ...globalNodes) => { + _components.set(name.toUpperCase(), setupFunc); + if (document.readyState !== 'loading') Component._addTemplate(name, templateNode, globalNodes); + else _pendingTemplates.push([name, templateNode, globalNodes]); + }, + exists: (name) => _components.has(name.toUpperCase()), + getSetupFunction: (name) => _components.get(name.toUpperCase()), + _addTemplate: (name, templateNode, globalNodes) => { + if (templateNode) { + const template = document.createElement('TEMPLATE'); + template.setAttribute('component', name.toUpperCase()); + template.content.appendChild(templateNode); + document.body.appendChild(template); + } + if (globalNodes) globalNodes.forEach(node => document.body.appendChild(node)); + }, + _initPending: () => { + _pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes)); + _pendingTemplates.length = 0; + } }; export function _mergeNode(from, to, scanObj, exists = {}) { - if (from.attributes) { - Array.from(from.attributes).forEach(attr => { - if (attr.name === 'class') return; - if (attr.name === 'style') { - if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`); - else to.setAttribute('style', attr.value); - } else if (to.hasAttribute(attr.name)) { - const isClass = ['$class', 'st-class'].includes(attr.name); - const isStyle = ['$style', 'st-style'].includes(attr.name); - if (isClass || isStyle) { - const oldVal = to.getAttribute(attr.name); - const newVal = attr.value; - const delimiter = isClass ? " " : "; "; - const oldExpr = oldVal.includes('${') ? oldVal : `\${${oldVal}}`; - const newExpr = newVal.includes('${') ? newVal : `\${${newVal}}`; - to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`); - } - } else { - to.setAttribute(attr.name, attr.value); - } - }); - } - const toClassList = [...to.classList]; - to.className = ''; - to.classList.add(...from.classList); - to.classList.add(...toClassList); - - const target = to.tagName === 'TEMPLATE' ? to.content : to; - const sourceNodes = from.tagName === 'TEMPLATE' ? from.content.childNodes : from.childNodes; - Array.from(sourceNodes).forEach(child => target.appendChild(child)); - - if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); + if (from.attributes) { + Array.from(from.attributes).forEach(attr => { + if (attr.name === 'class') return; + if (attr.name === 'style') { + if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`); + else to.setAttribute('style', attr.value); + } else if (to.hasAttribute(attr.name)) { + const isClass = ['$class', 'st-class'].includes(attr.name); + const isStyle = ['$style', 'st-style'].includes(attr.name); + if (isClass || isStyle) { + const oldVal = to.getAttribute(attr.name); + const newVal = attr.value; + const delimiter = isClass ? " " : "; "; + const oldExpr = oldVal.includes('${') ? oldVal : `\${${oldVal}}`; + const newExpr = newVal.includes('${') ? newVal : `\${${newVal}}`; + to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`); + } + } else { + to.setAttribute(attr.name, attr.value); + } + }); + } + const toClassList = [...to.classList]; + to.className = ''; + to.classList.add(...from.classList); + to.classList.add(...toClassList); + + const target = to.tagName === 'TEMPLATE' ? to.content : to; + const sourceNodes = from.tagName === 'TEMPLATE' ? from.content.childNodes : from.childNodes; + Array.from(sourceNodes).forEach(child => target.appendChild(child)); + + if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); } export function _makeComponent(name, node, scanObj, exists = {}) { - if (exists[name]) return; - exists[name] = true; - if (scanObj.thisObj) { - Array.from(node.attributes).forEach(attr => { - if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) { - attr.value = attr.value.replace(/\bthis\./g, 'this.parent.'); - } - }); - } - const componentFunc = Component.getSetupFunction(name); - const slots = {}; - Array.from(node.childNodes).forEach(child => { - if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute('slot')) { - slots[child.getAttribute('slot')] = child; - child.removeAttribute('slot'); - } - }); - node.innerHTML = ''; - node.state = NewState(node.state || {}); - const template = Component.getTemplate(name); - if (template) { - const tplnode = template.content.cloneNode(true); - if (tplnode.childNodes.length) { - const rootNode = tplnode.children[0]; - if (rootNode) _mergeNode(rootNode, node, scanObj, exists); - $$(node, '[slot-id]').forEach(placeholder => { - const slotName = placeholder.getAttribute('slot-id'); - if (slots[slotName]) { - placeholder.removeAttribute('slot-id'); - placeholder.innerHTML = ''; - _mergeNode(slots[slotName], placeholder, scanObj, exists); - } - }); - } - } - if (componentFunc) componentFunc(node); + if (exists[name]) return; + exists[name] = true; + if (scanObj.thisObj) { + Array.from(node.attributes).forEach(attr => { + if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) { + attr.value = attr.value.replace(/\bthis\./g, 'this.parent.'); + } + }); + } + const componentFunc = Component.getSetupFunction(name); + const slots = {}; + Array.from(node.childNodes).forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute('slot')) { + slots[child.getAttribute('slot')] = child; + child.removeAttribute('slot'); + } + }); + node.innerHTML = ''; + node.state = NewState(node.state || {}); + const template = Component.getTemplate(name); + if (template) { + const tplnode = template.content.cloneNode(true); + if (tplnode.childNodes.length) { + const rootNode = tplnode.children[0]; + if (rootNode) _mergeNode(rootNode, node, scanObj, exists); + $$(node, '[slot-id]').forEach(placeholder => { + const slotName = placeholder.getAttribute('slot-id'); + if (slots[slotName]) { + placeholder.removeAttribute('slot-id'); + placeholder.innerHTML = ''; + _mergeNode(slots[slotName], placeholder, scanObj, exists); + } + }); + } + } + if (componentFunc) componentFunc(node); } // --- DOM Engine Logic --- let _translator = (text, args) => { - if (!text || typeof text !== 'string') return text; - return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match); + if (!text || typeof text !== 'string') return text; + return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match); }; export const SetTranslator = (fn) => _translator = fn; const _translate = (text) => { - if (!text || typeof text !== 'string' || !text.includes('{#')) return text; - return text.replace(/\{#(.+?)#\}/g, (m, content) => { - const parts = content.split('||').map(s => s.trim()); - const args = {}; - if (parts.length > 1) { - const matches = parts[0].match(/\{(.+?)\}/g); - if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || ''); - } - return _translator(parts[0], args); - }); + if (!text || typeof text !== 'string' || !text.includes('{#')) return text; + return text.replace(/\{#(.+?)#\}/g, (m, content) => { + const parts = content.split('||').map(s => s.trim()); + const args = {}; + if (parts.length > 1) { + const matches = parts[0].match(/\{(.+?)\}/g); + if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || ''); + } + return _translator(parts[0], args); + }); }; if (typeof document !== 'undefined') { - try { document.createElement('div').setAttribute('$t', '1'); } catch (e) { - const originalSetAttribute = Element.prototype.setAttribute; - Element.prototype.setAttribute = function (name, value) { - if (!name.startsWith('$')) return originalSetAttribute.call(this, name, value); - return originalSetAttribute.call(this, 'st-' + name.substring(1), value); - }; - } + try { document.createElement('div').setAttribute('$t', '1'); } catch (e) { + const originalSetAttribute = Element.prototype.setAttribute; + Element.prototype.setAttribute = function (name, value) { + if (!name.startsWith('$')) return originalSetAttribute.call(this, name, value); + return originalSetAttribute.call(this, 'st-' + name.substring(1), value); + }; + } } _onNotifyUpdate((binding) => _updateBinding(binding)); export function _clearRenderedNodes(node) { - if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => { - child.remove(); - if (child._renderedNodes) _clearRenderedNodes(child); - })); + if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => { + child.remove(); + if (child._renderedNodes) _clearRenderedNodes(child); + })); } export function _updateBinding(binding) { - const node = binding.node; - if (!node.isConnected && node.tagName !== 'TEMPLATE') return; + const node = binding.node; + if (!node.isConnected && node.tagName !== 'TEMPLATE') return; - _setActiveBinding(binding); - let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl; + _setActiveBinding(binding); + let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl; - if (binding.exp === 2 && typeof result === 'string') { - try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { } - } - _setActiveBinding(null); - binding.lastResult = result; + if (binding.exp === 2 && typeof result === 'string') { + try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { } + } + _setActiveBinding(null); + binding.lastResult = result; - if (binding.prop) { - const prop = binding.prop; - let o = node; - for (let i = 0; i < prop.length - 1; i++) { - if (!prop[i]) continue; - if (o[prop[i]] == null) o[prop[i]] = {}; - o = o[prop[i]]; - if (typeof o !== 'object') break; - } - if (typeof o === 'object' && o !== null) { - const lk = prop[prop.length - 1]; - if (lk) { - if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; - const lo = o[lk]; - if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result); - else { if (o[lk] !== result) o[lk] = result; } - } else if (typeof result === 'object' && result != null && !Array.isArray(result)) { - Object.assign(o, result); - } - } - } else if (binding.attr) { - const attr = binding.attr; - if (attr === 'if') { - if (result) { - if (!node._renderedNodes || node._renderedNodes.length === 0) { - node._children.forEach(child => { - node.parentNode.insertBefore(child, node); - child._ref = { ...node._ref }; - child._thisObj = node._thisObj; - }); - node._renderedNodes = [node._children]; - } - } else { - _clearRenderedNodes(node); - node._renderedNodes = []; - } - } else if (attr === 'each') { - if (result && typeof result === 'object') { - const asName = node.getAttribute('as') || 'item'; - const indexName = node.getAttribute('index') || 'index'; - const keyName = node.getAttribute('key'); - let keys, getVal; - if (result instanceof Map) { - keys = Array.from(result.keys()); getVal = k => result.get(k); - } else if (typeof result[Symbol.iterator] === 'function') { - const arr = Array.isArray(result) ? result : Array.from(result); - keys = new Array(arr.length); - for (let i = 0; i < arr.length; i++) keys[i] = i; - getVal = k => arr[k]; - } else { - keys = Object.keys(result); getVal = k => result[k]; - } + if (binding.prop) { + const prop = binding.prop; + let o = node; + for (let i = 0; i < prop.length - 1; i++) { + if (!prop[i]) continue; + if (o[prop[i]] == null) o[prop[i]] = {}; + o = o[prop[i]]; + if (typeof o !== 'object') break; + } + if (typeof o === 'object' && o !== null) { + const lk = prop[prop.length - 1]; + if (lk) { + if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; + const lo = o[lk]; + if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result); + else { if (o[lk] !== result) o[lk] = result; } + } else if (typeof result === 'object' && result != null && !Array.isArray(result)) { + Object.assign(o, result); + } + } + } else if (binding.attr) { + const attr = binding.attr; + if (attr === 'if') { + if (result) { + if (!node._renderedNodes || node._renderedNodes.length === 0) { + node._children.forEach(child => { + node.parentNode.insertBefore(child, node); + child._ref = { ...node._ref }; + child._thisObj = node._thisObj; + }); + node._renderedNodes = [node._children]; + } + } else { + _clearRenderedNodes(node); + node._renderedNodes = []; + } + } else if (attr === 'each') { + if (result && typeof result === 'object') { + const asName = node.getAttribute('as') || 'item'; + const indexName = node.getAttribute('index') || 'index'; + const keyName = node.getAttribute('key'); + let keys, getVal; + if (result instanceof Map) { + keys = Array.from(result.keys()); getVal = k => result.get(k); + } else if (typeof result[Symbol.iterator] === 'function') { + const arr = Array.isArray(result) ? result : Array.from(result); + keys = new Array(arr.length); + for (let i = 0; i < arr.length; i++) keys[i] = i; + getVal = k => arr[k]; + } else { + keys = Object.keys(result); getVal = k => result[k]; + } - if (!node._keyedNodes) node._keyedNodes = new Map(); - const newKeyedNodes = new Map(); - const currentRenderedNodes = []; + if (!node._keyedNodes) node._keyedNodes = new Map(); + const newKeyedNodes = new Map(); + const currentRenderedNodes = []; - keys.forEach((k, i) => { - const item = getVal(k); - const rawKey = keyName ? (item && typeof item === 'object' ? item[keyName] : item) : k; - const keyVal = (rawKey === undefined || rawKey === null || newKeyedNodes.has(rawKey)) ? `st_key_${i}` : rawKey; + keys.forEach((k, i) => { + const item = getVal(k); + const rawKey = keyName ? (item && typeof item === 'object' ? item[keyName] : item) : k; + const keyVal = (rawKey === undefined || rawKey === null || newKeyedNodes.has(rawKey)) ? `st_key_${i}` : rawKey; - let existingNodes = node._keyedNodes.get(keyVal); + let existingNodes = node._keyedNodes.get(keyVal); - if (existingNodes) { - node._keyedNodes.delete(keyVal); - existingNodes.forEach(child => { - node.parentNode.insertBefore(child, node); - child._ref[indexName] = k; - child._ref[asName] = item; - _scanTree(child); - }); - } else { - existingNodes = []; - node._children.forEach(child => { - const cloned = child.cloneNode(true); - cloned._ref = { ...node._ref, [indexName]: k, [asName]: item }; - cloned._thisObj = node._thisObj; - node.parentNode.insertBefore(cloned, node); - existingNodes.push(cloned); - }); - } - newKeyedNodes.set(keyVal, existingNodes); - currentRenderedNodes.push(existingNodes); - }); + if (existingNodes) { + node._keyedNodes.delete(keyVal); + existingNodes.forEach(child => { + node.parentNode.insertBefore(child, node); + child._ref[indexName] = k; + child._ref[asName] = item; + _scanTree(child); + }); + } else { + existingNodes = []; + node._children.forEach(child => { + const cloned = child.cloneNode(true); + cloned._ref = { ...node._ref, [indexName]: k, [asName]: item }; + cloned._thisObj = node._thisObj; + node.parentNode.insertBefore(cloned, node); + existingNodes.push(cloned); + }); + } + newKeyedNodes.set(keyVal, existingNodes); + currentRenderedNodes.push(existingNodes); + }); - node._keyedNodes.forEach(nodes => nodes.forEach(child => { _clearRenderedNodes(child); child.remove(); })); - node._keyedNodes = newKeyedNodes; - node._renderedNodes = currentRenderedNodes; - } else { - _clearRenderedNodes(node); - node._renderedNodes = []; - } - } else if (attr === 'bind') { - if (['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName) && !node.hasAttribute('autocomplete')) node.setAttribute('autocomplete', 'off'); - if (node.type === 'checkbox') { - if (node.value !== 'on' && !result) { _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {}); result = []; } - node._checkboxMultiMode = result instanceof Array; - const isChecked = result instanceof Array ? result.includes(node.value) : !!result; - if (node.checked !== isChecked) node.checked = isChecked; - } else if (node.type === 'radio') { - if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? '')); - } else if ('value' in node && node.type !== 'file') { - Promise.resolve().then(() => { if (node.value !== String(result ?? '')) node.value = result; }); - } else if (node.isContentEditable) { - if (node.innerHTML !== String(result ?? '')) node.innerHTML = result; - } - node.dispatchEvent(new CustomEvent('bind', { bubbles: false, detail: result })); - } else { - if (['checked', 'disabled', 'readonly'].includes(attr)) result = !!result; - if (typeof result === 'boolean') result ? node.setAttribute(attr, '') : node.removeAttribute(attr); - else if (result !== undefined) { - if (typeof result !== 'string') result = JSON.stringify(result); - if (attr === 'text') node.textContent = result ?? ''; - else if (attr === 'html') node.innerHTML = result ?? ''; - else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) node.setAttribute('_src', result ?? ''); - else if (attr === 'class') { - if (node._staticClasses === undefined) { - node._staticClasses = node.getAttribute('class') || ''; - } - const staticClasses = node._staticClasses; - const finalClass = result ? (staticClasses ? `${result} ${staticClasses}` : result) : staticClasses; - node.setAttribute('class', finalClass.trim().replace(/\s+/g, ' ')); - } else if (attr === 'style') { - if (node._staticStyles === undefined) { - node._staticStyles = node.getAttribute('style') || ''; - } - const staticStyles = node._staticStyles; - const finalStyle = result ? (staticStyles ? `${result}; ${staticStyles}` : result) : staticStyles; - node.setAttribute('style', finalStyle.trim().replace(/;;+/g, ';').replace(/^;+\s*|;\s*$/g, '')); - } else node.setAttribute(attr, result ?? ''); - } - } - } + node._keyedNodes.forEach(nodes => nodes.forEach(child => { _clearRenderedNodes(child); child.remove(); })); + node._keyedNodes = newKeyedNodes; + node._renderedNodes = currentRenderedNodes; + } else { + _clearRenderedNodes(node); + node._renderedNodes = []; + } + } else if (attr === 'bind') { + if (['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName) && !node.hasAttribute('autocomplete')) node.setAttribute('autocomplete', 'off'); + if (node.type === 'checkbox') { + if (node.value !== 'on' && !result) { _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {}); result = []; } + node._checkboxMultiMode = result instanceof Array; + const isChecked = result instanceof Array ? result.includes(node.value) : !!result; + if (node.checked !== isChecked) node.checked = isChecked; + } else if (node.type === 'radio') { + if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? '')); + } else if ('value' in node && node.type !== 'file') { + Promise.resolve().then(() => { if (node.value !== String(result ?? '')) node.value = result; }); + } else if (node.isContentEditable) { + if (node.innerHTML !== String(result ?? '')) node.innerHTML = result; + } + node.dispatchEvent(new CustomEvent('bind', { bubbles: false, detail: result })); + } else { + if (['checked', 'disabled', 'readonly'].includes(attr)) result = !!result; + if (typeof result === 'boolean') result ? node.setAttribute(attr, '') : node.removeAttribute(attr); + else if (result !== undefined) { + if (typeof result !== 'string') result = JSON.stringify(result); + if (attr === 'text') node.textContent = result ?? ''; + else if (attr === 'html') node.innerHTML = result ?? ''; + else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) node.setAttribute('_src', result ?? ''); + else if (attr === 'class') { + if (node._staticClasses === undefined) { + node._staticClasses = node.getAttribute('class') || ''; + } + const staticClasses = node._staticClasses; + const finalClass = result ? (staticClasses ? `${result} ${staticClasses}` : result) : staticClasses; + node.setAttribute('class', finalClass.trim().replace(/\s+/g, ' ')); + } else if (attr === 'style') { + if (node._staticStyles === undefined) { + node._staticStyles = node.getAttribute('style') || ''; + } + const staticStyles = node._staticStyles; + const finalStyle = result ? (staticStyles ? `${result}; ${staticStyles}` : result) : staticStyles; + node.setAttribute('style', finalStyle.trim().replace(/;;+/g, ';').replace(/^;+\s*|;\s*$/g, '')); + } else node.setAttribute(attr, result ?? ''); + } + } + } } export function _initBinding(binding) { - if (!binding.node._bindings) binding.node._bindings = []; - binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }); - _updateBinding(binding); + if (!binding.node._bindings) binding.node._bindings = []; + binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }); + _updateBinding(binding); } export function _parseNode(node, scanObj) { - if (node._bindings) { - node._states = new Set(); - node._bindings.forEach(b => _updateBinding({ node, ...b })); - if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); - return; - } + if (node._bindings) { + node._states = new Set(); + node._bindings.forEach(b => _updateBinding({ node, ...b })); + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); + return; + } - if (Component.exists(node.tagName) && !node._componentInitialized) { - Array.from(node.attributes).forEach(attr => { - if (attr.name.startsWith('$.')) { - const realAttrName = attr.name.slice(2); - let tpl = _translate(attr.value); - if (scanObj.thisObj && tpl.includes('this.')) tpl = tpl.replace(/\bthis\./g, 'this.parent.'); - const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {}); - let o = node; - const prop = realAttrName.split('.'); - for (let i = 0; i < prop.length - 1; i++) { if (prop[i]) o = (o[prop[i]] ??= {}); } - o[prop[prop.length - 1]] = result; - node.removeAttribute(attr.name); - } - }); - _makeComponent(node.tagName, node, scanObj); - $$(node, '[slot-id]').forEach(p => p.removeAttribute('slot-id')); - node._componentInitialized = true; - if (!node._thisObj) node._thisObj = node; - } + if (Component.exists(node.tagName) && !node._componentInitialized) { + Array.from(node.attributes).forEach(attr => { + if (attr.name.startsWith('$.')) { + const realAttrName = attr.name.slice(2); + let tpl = _translate(attr.value); + if (scanObj.thisObj && tpl.includes('this.')) tpl = tpl.replace(/\bthis\./g, 'this.parent.'); + const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {}); + let o = node; + const prop = realAttrName.split('.'); + for (let i = 0; i < prop.length - 1; i++) { if (prop[i]) o = (o[prop[i]] ??= {}); } + o[prop[prop.length - 1]] = result; + // node.removeAttribute(attr.name); + } + }); + _makeComponent(node.tagName, node, scanObj); + $$(node, '[slot-id]').forEach(p => p.removeAttribute('slot-id')); + node._componentInitialized = true; + if (!node._thisObj) node._thisObj = node; + } - if (node.tagName === 'TEMPLATE') { - node._children = [...node.content.childNodes]; - if (!node._renderedNodes) node._renderedNodes = []; - } + if (node.tagName === 'TEMPLATE') { + node._children = [...node.content.childNodes]; + if (!node._renderedNodes) node._renderedNodes = []; + } - let attrs = []; - if (node.tagName === 'TEMPLATE') { - ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); - } else { - attrs = Array.from(node.attributes).filter(a => (a.name.startsWith('$') || a.name.startsWith('st-')) && !['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(a.name) || a.name.includes('.')); - } + let attrs = []; + if (node.tagName === 'TEMPLATE') { + ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); + } else { + attrs = Array.from(node.attributes).filter(a => (a.name.startsWith('$') || a.name.startsWith('st-')) && !['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(a.name) || a.name.includes('.')); + } - if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; - if (!node._thisObj) node._thisObj = scanObj.thisObj || null; - if (!node._ref) node._ref = scanObj.extendVars || {}; - node._states = new Set(); + if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; + if (!node._thisObj) node._thisObj = scanObj.thisObj || null; + if (!node._ref) node._ref = scanObj.extendVars || {}; + node._states = new Set(); - attrs.forEach(attr => { - let exp = 0; - if (attr.name.startsWith('$$') || attr.name.startsWith('st-st-')) exp = 2; - else if (attr.name.startsWith('$') || attr.name.startsWith('st-')) exp = 1; + attrs.forEach(attr => { + let exp = 0; + if (attr.name.startsWith('$$') || attr.name.startsWith('st-st-')) exp = 2; + else if (attr.name.startsWith('$') || attr.name.startsWith('st-')) exp = 1; - const realAttrName = exp === 2 ? (attr.name.startsWith('$$') ? attr.name.slice(2) : attr.name.slice(6)) : (exp === 1 ? (attr.name.startsWith('$') ? attr.name.slice(1) : attr.name.slice(3)) : attr.name); - let tpl = attr.value; - node.removeAttribute(attr.name); - if (realAttrName.startsWith('.')) _initBinding({ node, prop: realAttrName.split('.'), tpl, exp }); - else if (realAttrName.startsWith('on')) { - const eventName = realAttrName.slice(2); - if (eventName === 'update') node._hasOnUpdate = true; - if (eventName === 'load' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true; - if (eventName === 'unload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; - node.addEventListener(eventName, (e) => _runCode(tpl, { event: e, thisNode: node, ...(e.detail || {}) }, scanObj.thisObj || node, node._ref || {})); - } else { - if (realAttrName === 'bind') { - const isTextInput = (['INPUT', 'TEXTAREA'].includes(node.tagName) && ['textarea', 'text', 'password', 'email', 'number', 'search', 'url', 'tel'].includes(node.type || 'text')) || node.isContentEditable; - node.addEventListener(isTextInput ? 'input' : 'change', (e) => { - let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail); - _setNoWriteBack(node); setDisableRunCodeError(true); - if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); - else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); - setDisableRunCodeError(false); _setNoWriteBack(null); - }); - } else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; } - if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); } - } - }); + const realAttrName = exp === 2 ? (attr.name.startsWith('$$') ? attr.name.slice(2) : attr.name.slice(6)) : (exp === 1 ? (attr.name.startsWith('$') ? attr.name.slice(1) : attr.name.slice(3)) : attr.name); + let tpl = attr.value; + node.removeAttribute(attr.name); + if (realAttrName.startsWith('.')) _initBinding({ node, prop: realAttrName.split('.'), tpl, exp }); + else if (realAttrName.startsWith('on')) { + const eventName = realAttrName.slice(2); + if (eventName === 'update') node._hasOnUpdate = true; + if (eventName === 'load' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true; + if (eventName === 'unload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; + node.addEventListener(eventName, (e) => _runCode(tpl, { event: e, thisNode: node, ...(e.detail || {}) }, scanObj.thisObj || node, node._ref || {})); + } else { + if (realAttrName === 'bind') { + const isTextInput = (['INPUT', 'TEXTAREA'].includes(node.tagName) && ['textarea', 'text', 'password', 'email', 'number', 'search', 'url', 'tel'].includes(node.type || 'text')) || node.isContentEditable; + node.addEventListener(isTextInput ? 'input' : 'change', (e) => { + let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail); + _setNoWriteBack(node); setDisableRunCodeError(true); + if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); + else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); + setDisableRunCodeError(false); _setNoWriteBack(null); + }); + } else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; } + if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); } + } + }); - if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event('load', { bubbles: false }))); - if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); - if (node._thisObj) scanObj.thisObj = node._thisObj; + if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event('load', { bubbles: false }))); + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); + if (node._thisObj) scanObj.thisObj = node._thisObj; } export const _scanTree = (node, scanObj = {}) => { - if (node.nodeType === 3) { - if (node._stTranslated) return; - const translated = _translate(node.textContent); - if (translated !== node.textContent) node.textContent = translated; - node._stTranslated = true; - return; - } - if (node.nodeType !== 1) return; + if (node.nodeType === 3) { + if (node._stTranslated) return; + const translated = _translate(node.textContent); + if (translated !== node.textContent) node.textContent = translated; + node._stTranslated = true; + return; + } + if (node.nodeType !== 1) return; - if (!node._stTranslated) { - Array.from(node.attributes).forEach(attr => { - if (!attr.name.startsWith('$') && !attr.name.startsWith('st-') && !attr.name.startsWith('.')) { - const translated = _translate(attr.value); - if (translated !== attr.value) attr.value = translated; - } - }); - node._stTranslated = true; - } + if (!node._stTranslated) { + Array.from(node.attributes).forEach(attr => { + if (!attr.name.startsWith('$') && !attr.name.startsWith('st-') && !attr.name.startsWith('.')) { + const translated = _translate(attr.value); + if (translated !== attr.value) attr.value = translated; + } + }); + node._stTranslated = true; + } - if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each') || node.hasAttribute('$$if') || node.hasAttribute('$$each') || node.hasAttribute('st-st-if') || node.hasAttribute('st-st-each'))) { - const template = document.createElement('TEMPLATE'); - const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each')) && ['as', 'index'].includes(attr.name))); - attrs.forEach(attr => { - template.setAttribute(attr.name, attr.value); - node.removeAttribute(attr.name); - }); - node.parentNode.insertBefore(template, node); - template.content.appendChild(node); - template._ref = node._ref; - _scanTree(template, scanObj); - return; - } + if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each') || node.hasAttribute('$$if') || node.hasAttribute('$$each') || node.hasAttribute('st-st-if') || node.hasAttribute('st-st-each'))) { + const template = document.createElement('TEMPLATE'); + const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each')) && ['as', 'index'].includes(attr.name))); + attrs.forEach(attr => { + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + }); + node.parentNode.insertBefore(template, node); + template.content.appendChild(node); + template._ref = node._ref; + _scanTree(template, scanObj); + return; + } - if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if') || node.hasAttribute('$$if') || node.hasAttribute('st-st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each'))) { - const template = document.createElement('TEMPLATE'); - const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name)); - const attr = attrs[attrs.length - 1]; - template.setAttribute(attr.name, attr.value); - node.removeAttribute(attr.name); - if (['$each', 'st-each', '$$each', 'st-st-each'].includes(attr.name)) { - Array.from(node.attributes).filter(attr => ['as', 'index'].includes(attr.name)).forEach(attr => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); }); - } - Array.from(node.content.childNodes).forEach(child => template.content.appendChild(child)); - node.content.appendChild(template); - template._ref = node._ref; - } + if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if') || node.hasAttribute('$$if') || node.hasAttribute('st-st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each'))) { + const template = document.createElement('TEMPLATE'); + const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name)); + const attr = attrs[attrs.length - 1]; + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + if (['$each', 'st-each', '$$each', 'st-st-each'].includes(attr.name)) { + Array.from(node.attributes).filter(attr => ['as', 'index'].includes(attr.name)).forEach(attr => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); }); + } + Array.from(node.content.childNodes).forEach(child => template.content.appendChild(child)); + node.content.appendChild(template); + template._ref = node._ref; + } - if (node.tagName === 'IMG' && (node.hasAttribute('src') || node.hasAttribute('_src') || node.hasAttribute('$src'))) { - const imgNode = node; - Promise.resolve().then(() => { - const url = imgNode.getAttribute('_src') || imgNode.getAttribute('src'); - if (url) fetch(url, { cache: 'force-cache' }).then(r => r.text()).then(svgText => { - const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector('svg'); - if (realSvg) { - Array.from(imgNode.attributes).forEach(attr => realSvg.setAttribute(attr.name, attr.value)); - imgNode.replaceWith(realSvg); - } - }); - }); - } + if (node.tagName === 'IMG' && (node.hasAttribute('src') || node.hasAttribute('_src') || node.hasAttribute('$src'))) { + const imgNode = node; + Promise.resolve().then(() => { + const url = imgNode.getAttribute('_src') || imgNode.getAttribute('src'); + if (url) fetch(url, { cache: 'force-cache' }).then(r => r.text()).then(svgText => { + const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector('svg'); + if (realSvg) { + Array.from(imgNode.attributes).forEach(attr => realSvg.setAttribute(attr.name, attr.value)); + imgNode.replaceWith(realSvg); + } + }); + }); + } - if (node._thisObj !== undefined) scanObj.thisObj = node._thisObj || null; - else { - let curr = node; - while (curr && curr._thisObj === undefined) curr = curr.parentNode; - scanObj.thisObj = curr ? curr._thisObj : null; - } + if (node._thisObj !== undefined) scanObj.thisObj = node._thisObj || null; + else { + let curr = node; + while (curr && curr._thisObj === undefined) curr = curr.parentNode; + scanObj.thisObj = curr ? curr._thisObj : null; + } - if (node._ref === undefined) { - let curr = node; - while (curr && curr._ref === undefined) curr = curr.parentNode; - node._ref = curr ? { ...curr._ref } : {}; - } - if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars); + if (node._ref === undefined) { + let curr = node; + while (curr && curr._ref === undefined) curr = curr.parentNode; + node._ref = curr ? { ...curr._ref } : {}; + } + if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars); - _parseNode(node, { ...scanObj }); + _parseNode(node, { ...scanObj }); - const nodes = [...(node.childNodes || [])]; - nodes.forEach(child => _scanTree(child, { thisObj: scanObj.thisObj, extendVars: { ...node._ref } })); + const nodes = [...(node.childNodes || [])]; + nodes.forEach(child => _scanTree(child, { thisObj: scanObj.thisObj, extendVars: { ...node._ref } })); }; export const _unbindTree = (node) => { - if (node.nodeType !== 1) return; - if (node._hasOnUnload) node.dispatchEvent(new Event('unload', { bubbles: false })); - if (node._states) node._states.forEach(mappings => { - for (const [key, bindingSet] of mappings) { - for (const binding of bindingSet) { if (binding.node === node) bindingSet.delete(binding); } - } - }); - node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); + if (node.nodeType !== 1) return; + if (node._hasOnUnload) node.dispatchEvent(new Event('unload', { bubbles: false })); + if (node._states) node._states.forEach(mappings => { + for (const [key, bindingSet] of mappings) { + for (const binding of bindingSet) { if (binding.node === node) bindingSet.delete(binding); } + } + }); + node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); }; // 全局挂载