diff --git a/dist/state.js b/dist/state.js index a866d78..ba652f9 100644 --- a/dist/state.js +++ b/dist/state.js @@ -88,16 +88,17 @@ function setDisableRunCodeError(value) { } const _fnCache = /* @__PURE__ */ new Map(); function _runCode(code, vars, thisObj, extendVars) { - const argKeys = [...Object.keys(extendVars || {}), ...Object.keys(vars || {})]; - const argValues = [...Object.values(extendVars || {}), ...Object.values(vars || {})]; + const allVars = { ...extendVars || {}, ...vars || {} }; + const argKeys = Object.keys(allVars); + const argValues = Object.values(allVars); const cacheKey = code + argKeys.join(","); try { let fn = _fnCache.get(cacheKey); if (!fn) { - fn = new Function(...argKeys, code); + fn = new Function("Hash", "LocalStorage", "State", ...argKeys, code); _fnCache.set(cacheKey, fn); } - return fn.apply(thisObj, argValues); + return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]); } catch (e) { if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]); return null; @@ -300,6 +301,9 @@ const _parseNode = (node, scanObj) => { if (node._bindings) { node._bindings.forEach((binding) => _updateBinding(binding)); if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false })); + if (node.hasAttribute("onupdate")) { + _runCode(node.getAttribute("onupdate"), { thisNode: node }, node._thisObj || node, node._ref || {}); + } return; } let attrs = []; @@ -325,11 +329,12 @@ const _parseNode = (node, scanObj) => { _initBinding({ node, prop: realAttrName.split("."), tpl, exp }); } else { if (realAttrName.startsWith("on")) { - if (realAttrName === "onupdate") node._hasOnUpdate = true; - if (realAttrName === "onload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true; - if (realAttrName === "onunload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true; + 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 === "onunload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true; ((node2, thisObj) => { - node2.addEventListener(realAttrName.slice(2), (e) => { + node2.addEventListener(eventName, (e) => { _runCode(tpl, { event: e, thisNode: node2, ...e.detail || {} }, thisObj || node2, node2._ref || {}); }); })(node, scanObj.thisObj); @@ -368,17 +373,22 @@ const _parseNode = (node, scanObj) => { }; 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; - 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; - } - }); + 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"))) { const template = document.createElement("TEMPLATE"); const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name)); diff --git a/dist/state.min.js b/dist/state.min.js index 0a240ab..c9b529b 100644 --- a/dist/state.min.js +++ b/dist/state.min.js @@ -1 +1 @@ -var e;let t=null,n=null,r=null,s=0;function a(e){t=e}function o(e){n=e}function i(e={},a=null,o=null){if(e&&e.__watch)return e;const i={},d=new Map,c=new Map,l=(e,t)=>{c.has(e)||c.set(e,new Set),t?c.get(e).add(t):c.get(e).clear()},h=(e,t)=>{c.has(e)||c.set(e,new Set),c.get(e).delete(t)},u=a||(e=>i[e]),f=o||((e,t)=>i[e]=t);return Object.assign(i,e),new Proxy(i,{get(e,n){if("__watch"===n)return l;if("__unwatch"===n)return h;if(t){d.has(n)||d.set(n,new Set);const e=d.get(n);e.add(t),t._sets||(t._sets=new Set),t._sets.add(e)}return u(n)},set(e,t,a){if(u(t)!==a&&f(t,a),c.has(t)&&c.get(t).forEach(n=>{const r=n(a);void 0!==r&&(a=r,e[t]=a)}),c.has(null)&&c.get(null).forEach(e=>e(a)),d.has(t)){if(s>100)return console.error("Recursive update detected at key:",t),!0;s++;try{const e=d.get(t);for(const t of e)t.node.isConnected?n!==t.node&&r&&r(t):e.delete(t)}finally{s--}}return!0}})}const d=(e,t)=>t?e.querySelector(t):document.querySelector(e),c=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);let l=!1;function h(e){l=e}const u=new Map;function f(e,t,n,r){const s=[...Object.keys(r||{}),...Object.keys(t||{})],a=[...Object.values(r||{}),...Object.values(t||{})],o=e+s.join(",");try{let t=u.get(o);return t||(t=new Function(...s,e),u.set(o,t)),t.apply(n,a)}catch(s){return l||console.error(s,r,[e,r,t,n]),null}}let b=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const m=e=>b=e,p=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r=n[0],s={};if(n.length>1){const e=r.match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>{const r=e.substring(1,e.length-1);s[r]=n[t+1]||""})}return b(r,s)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),n):t.call(this,e,n)}}function g(e){e._renderedNodes&&e._renderedNodes.forEach(e=>{e.forEach(e=>{e.remove(),e._renderedNodes&&g(e)})})}function _(e){const t=e.node,n=e.tpl,r=e.exp;a(e);let s=r?n?(o=n,i={thisNode:t},d=t._thisObj||t,c=t._ref||null,o.includes("${")?f("return `"+o+"`",i,d,c):f("return "+o,i,d,c)):null:n;var o,i,d,c;if(a(null),e.prop){const n=e.prop;let r=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(g(t),t._renderedNodes=[]);else if("each"===r)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index";let r,a;if(s instanceof Map)r=Array.from(s.keys()),a=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);r=new Array(e.length);for(let t=0;te[t]}else r=Object.keys(s),a=e=>s[e];for(r.forEach((r,s)=>{const o=a(r);if(t._renderedNodes&&s{t._ref[n]=r,t._ref[e]=o,A(t)});else{const s=[];t._renderedNodes||(t._renderedNodes=[]),t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref},i._ref[n]=r,i._ref[e]=o,i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),s.push(i)}),t._renderedNodes.push(s)}});t._renderedNodes&&t._renderedNodes.length>r.length;)t._renderedNodes[t._renderedNodes.length-1].forEach(e=>{g(e),e.remove()}),t._renderedNodes.pop()}else g(t),t._renderedNodes=[];else if("bind"===r){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&(t.hasAttribute("autocomplete")||t.setAttribute("autocomplete","off")),"checkbox"===t.type){"on"===t.value||s||(f(`${n} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const e=s instanceof Array?s.includes(t.value):!!s;t.checked!==e&&(t.checked=e)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{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["checked","disabled","readonly"].includes(r)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(r,""):t.removeAttribute(r):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===r?t.textContent=s??"":"html"===r?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===r&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(r,s??""))}}r=e=>_(e);const v=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push(e),_(e)},A=(e,t={})=>{if(3===e.nodeType){const t=p(e.textContent);return void(t!==e.textContent&&(e.textContent=t))}if(1!==e.nodeType)return;if(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=p(e.value);t!==e.value&&(e.value=t)}}),"TEMPLATE"!==e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("$each")||e.hasAttribute("st-if")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each"))&&["as","index"].includes(t.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),e.parentNode.insertBefore(t,e),t.content.appendChild(e),t._ref=e._ref,void(e=t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE"),n=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each"].includes(e.name)),r=n[n.length-1];t.setAttribute(r.name,r.value),e.removeAttribute(r.name),"$each"!==r.name&&"st-each"!==r.name||Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.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 n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj&&(t.thisObj=e._thisObj||null),void 0===t.thisObj){let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0===t.extendVars&&(t.extendVars={}),void 0!==e._ref&&(Object.assign(e._ref,t.extendVars),t.extendVars={...e._ref}),((e,t)=>{if(T.exists(e.tagName)&&!e._componentInitialized&&(e._componentInitialized=!0,w(e.tagName,e,t),c(e,"[slot-id]").forEach(e=>e.removeAttribute("slot-id")),e._thisObj||(e._thisObj=e)),e._bindings)return e._bindings.forEach(e=>_(e)),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));let n=[];var r;"TEMPLATE"===e.tagName?(e._children=[...e.content.childNodes],e._renderedNodes=[],e.hasAttribute("$if")?n.push(e.getAttributeNode("$if")):e.hasAttribute("$each")?n.push(e.getAttributeNode("$each")):e.hasAttribute("st-if")?n.push(e.getAttributeNode("st-if")):e.hasAttribute("st-each")&&n.push(e.getAttributeNode("st-each"))):n=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),n.forEach(n=>{const r=n.name.startsWith("$")||n.name.startsWith("st-"),s=r?n.name.slice(n.name.startsWith("$")?1:3):n.name;let a=n.value;var i,d;e.removeAttribute(n.name),s.startsWith(".")?v({node:e,prop:s.split("."),tpl:a,exp:r}):s.startsWith("on")?("onupdate"===s&&(e._hasOnUpdate=!0),"onload"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"onunload"!==s||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),i=e,d=t.thisObj,i.addEventListener(s.slice(2),e=>{f(a,{event:e,thisNode:i,...e.detail||{}},d||i,i._ref||{})})):("bind"===s?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;o(e),h(!0),"checkbox"===e.type&&e._checkboxMultiMode?f(`!!checked ? (!${a}.includes(val) && ${a}.push(val)) : (index = ${a}.indexOf(val), index > -1 && ${a}.splice(index, 1))`,{val:e.value,checked:r,thisNode:e},t.thisObj||e,e._ref||{}):f(`${a} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),h(!1),o(null)}):"text"!==s||a||(a=e.textContent,e.textContent=""),a&&(a=p(a),v({node:e,attr:s,tpl:a,exp:r})))}),(e._hasOnLoad||e._componentInitialized)&&(r=e,Promise.resolve().then(()=>r.dispatchEvent(new Event("load",{bubbles:!1})))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,t);const n=[...e.childNodes||[]];t.extendVars=e._ref||t.extendVars,n.forEach(n=>A(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},E=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._bindings&&e._bindings.forEach(e=>{e._sets&&(e._sets.forEach(t=>t.delete(e)),e._sets.clear())}),e.childNodes&&e.childNodes.forEach(e=>E(e)))},y=A,N=new Map,O=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{N.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,n,r):O.push([e,n,r])},exists:e=>N.has(e.toUpperCase()),getSetupFunction:e=>N.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{O.forEach(([e,t,n])=>T._addTemplate(e,t,n)),O.length=0}};function j(e,t,n,r={}){e.attributes&&Array.from(e.attributes).forEach(e=>"class"!==e.name&&t.setAttribute(e.name,e.value)),e.classList&&t.classList.add(...e.classList),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&T.exists(e.tagName)&&w(e.tagName,t,n,r)}function w(e,t,n,r={}){if(r[e])return;r[e]=!0,n.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 s=T.getSetupFunction(e),a={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(a[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=i(t.state||{});const o=T.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){j(e.children[0],t,n,r),c(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id"),s=a[t];s&&(e.removeAttribute("slot-id"),e.innerHTML="","TEMPLATE"===s.tagName?Array.from(s.content.childNodes).forEach(t=>e.appendChild(t.cloneNode(!0))):j(s,e,n,r))})}}if(s)try{s(t)}catch(t){console.error("Error in component setupFunc for",e,t)}}const x={clone:window.structuredClone||(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=>x.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>x.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 n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].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,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),s=r-e;return e=r,t+=s,n++,s},avg:()=>t/n}}};globalThis.Util=x;let S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=i({},e=>x.safeJson(S.get(e)),(e,t)=>{const n=S.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?S.delete(e):S.set(e,r),window.location.hash="#"+S.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=S;S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),S.forEach((e,n)=>{t.get(n)!==e&&(M[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===S.get(t)&&(M[t]=void 0)})});const $=i({},e=>x.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=M,globalThis.LocalStorage=$,"undefined"!=typeof document){const e=()=>{T._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&A(e)}),e.removedNodes.forEach(e=>E(e))})}).observe(document.documentElement,{childList:!0,subtree:!0});const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),A(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{d as $,c as $$,T as Component,M as Hash,$ as LocalStorage,i as NewState,y as RefreshState,m as SetTranslator,x as Util,A as _scanTree,E as _unbindTree}; +var e;let t=null,n=null,r=null,s=0;function a(e){t=e}function o(e){n=e}function i(e={},a=null,o=null){if(e&&e.__watch)return e;const i={},d=new Map,c=new Map,l=(e,t)=>{c.has(e)||c.set(e,new Set),t?c.get(e).add(t):c.get(e).clear()},h=(e,t)=>{c.has(e)||c.set(e,new Set),c.get(e).delete(t)},u=a||(e=>i[e]),f=o||((e,t)=>i[e]=t);return Object.assign(i,e),new Proxy(i,{get(e,n){if("__watch"===n)return l;if("__unwatch"===n)return h;if(t){d.has(n)||d.set(n,new Set);const e=d.get(n);e.add(t),t._sets||(t._sets=new Set),t._sets.add(e)}return u(n)},set(e,t,a){if(u(t)!==a&&f(t,a),c.has(t)&&c.get(t).forEach(n=>{const r=n(a);void 0!==r&&(a=r,e[t]=a)}),c.has(null)&&c.get(null).forEach(e=>e(a)),d.has(t)){if(s>100)return console.error("Recursive update detected at key:",t),!0;s++;try{const e=d.get(t);for(const t of e)t.node.isConnected?n!==t.node&&r&&r(t):e.delete(t)}finally{s--}}return!0}})}const d=(e,t)=>t?e.querySelector(t):document.querySelector(e),c=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);let l=!1;function h(e){l=e}const u=new Map;function f(e,t,n,r){const s={...r||{},...t||{}},a=Object.keys(s),o=Object.values(s),i=e+a.join(",");try{let t=u.get(i);return t||(t=new Function("Hash","LocalStorage","State",...a,e),u.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(s){return l||console.error(s,r,[e,r,t,n]),null}}let b=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const m=e=>b=e,p=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r=n[0],s={};if(n.length>1){const e=r.match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>{const r=e.substring(1,e.length-1);s[r]=n[t+1]||""})}return b(r,s)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),n):t.call(this,e,n)}}function g(e){e._renderedNodes&&e._renderedNodes.forEach(e=>{e.forEach(e=>{e.remove(),e._renderedNodes&&g(e)})})}function _(e){const t=e.node,n=e.tpl,r=e.exp;a(e);let s=r?n?(o=n,i={thisNode:t},d=t._thisObj||t,c=t._ref||null,o.includes("${")?f("return `"+o+"`",i,d,c):f("return "+o,i,d,c)):null:n;var o,i,d,c;if(a(null),e.prop){const n=e.prop;let r=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(g(t),t._renderedNodes=[]);else if("each"===r)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index";let r,a;if(s instanceof Map)r=Array.from(s.keys()),a=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);r=new Array(e.length);for(let t=0;te[t]}else r=Object.keys(s),a=e=>s[e];for(r.forEach((r,s)=>{const o=a(r);if(t._renderedNodes&&s{t._ref[n]=r,t._ref[e]=o,A(t)});else{const s=[];t._renderedNodes||(t._renderedNodes=[]),t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref},i._ref[n]=r,i._ref[e]=o,i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),s.push(i)}),t._renderedNodes.push(s)}});t._renderedNodes&&t._renderedNodes.length>r.length;)t._renderedNodes[t._renderedNodes.length-1].forEach(e=>{g(e),e.remove()}),t._renderedNodes.pop()}else g(t),t._renderedNodes=[];else if("bind"===r){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&(t.hasAttribute("autocomplete")||t.setAttribute("autocomplete","off")),"checkbox"===t.type){"on"===t.value||s||(f(`${n} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const e=s instanceof Array?s.includes(t.value):!!s;t.checked!==e&&(t.checked=e)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{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["checked","disabled","readonly"].includes(r)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(r,""):t.removeAttribute(r):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===r?t.textContent=s??"":"html"===r?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===r&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(r,s??""))}}r=e=>_(e);const v=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push(e),_(e)},A=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=p(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=p(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"))){const t=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each"))&&["as","index"].includes(t.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),e.parentNode.insertBefore(t,e),t.content.appendChild(e),t._ref=e._ref,void(e=t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE"),n=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each"].includes(e.name)),r=n[n.length-1];t.setAttribute(r.name,r.value),e.removeAttribute(r.name),"$each"!==r.name&&"st-each"!==r.name||Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.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 n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj&&(t.thisObj=e._thisObj||null),void 0===t.thisObj){let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0===t.extendVars&&(t.extendVars={}),void 0!==e._ref&&(Object.assign(e._ref,t.extendVars),t.extendVars={...e._ref}),((e,t)=>{if(T.exists(e.tagName)&&!e._componentInitialized&&(e._componentInitialized=!0,w(e.tagName,e,t),c(e,"[slot-id]").forEach(e=>e.removeAttribute("slot-id")),e._thisObj||(e._thisObj=e)),e._bindings)return e._bindings.forEach(e=>_(e)),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),void(e.hasAttribute("onupdate")&&f(e.getAttribute("onupdate"),{thisNode:e},e._thisObj||e,e._ref||{}));let n=[];var r;"TEMPLATE"===e.tagName?(e._children=[...e.content.childNodes],e._renderedNodes=[],e.hasAttribute("$if")?n.push(e.getAttributeNode("$if")):e.hasAttribute("$each")?n.push(e.getAttributeNode("$each")):e.hasAttribute("st-if")?n.push(e.getAttributeNode("st-if")):e.hasAttribute("st-each")&&n.push(e.getAttributeNode("st-each"))):n=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),n.forEach(n=>{const r=n.name.startsWith("$")||n.name.startsWith("st-"),s=r?n.name.slice(n.name.startsWith("$")?1:3):n.name;let a=n.value;if(e.removeAttribute(n.name),s.startsWith("."))v({node:e,prop:s.split("."),tpl:a,exp:r});else if(s.startsWith("on")){const n=s.slice(2);"update"===n&&(e._hasOnUpdate=!0),"load"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"onunload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),i=e,d=t.thisObj,i.addEventListener(n,e=>{f(a,{event:e,thisNode:i,...e.detail||{}},d||i,i._ref||{})})}else"bind"===s?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;o(e),h(!0),"checkbox"===e.type&&e._checkboxMultiMode?f(`!!checked ? (!${a}.includes(val) && ${a}.push(val)) : (index = ${a}.indexOf(val), index > -1 && ${a}.splice(index, 1))`,{val:e.value,checked:r,thisNode:e},t.thisObj||e,e._ref||{}):f(`${a} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),h(!1),o(null)}):"text"!==s||a||(a=e.textContent,e.textContent=""),a&&(a=p(a),v({node:e,attr:s,tpl:a,exp:r}));var i,d}),(e._hasOnLoad||e._componentInitialized)&&(r=e,Promise.resolve().then(()=>r.dispatchEvent(new Event("load",{bubbles:!1})))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,t);const n=[...e.childNodes||[]];t.extendVars=e._ref||t.extendVars,n.forEach(n=>A(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},E=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._bindings&&e._bindings.forEach(e=>{e._sets&&(e._sets.forEach(t=>t.delete(e)),e._sets.clear())}),e.childNodes&&e.childNodes.forEach(e=>E(e)))},y=A,N=new Map,O=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{N.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,n,r):O.push([e,n,r])},exists:e=>N.has(e.toUpperCase()),getSetupFunction:e=>N.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{O.forEach(([e,t,n])=>T._addTemplate(e,t,n)),O.length=0}};function j(e,t,n,r={}){e.attributes&&Array.from(e.attributes).forEach(e=>"class"!==e.name&&t.setAttribute(e.name,e.value)),e.classList&&t.classList.add(...e.classList),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&T.exists(e.tagName)&&w(e.tagName,t,n,r)}function w(e,t,n,r={}){if(r[e])return;r[e]=!0,n.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 s=T.getSetupFunction(e),a={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(a[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=i(t.state||{});const o=T.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){j(e.children[0],t,n,r),c(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id"),s=a[t];s&&(e.removeAttribute("slot-id"),e.innerHTML="","TEMPLATE"===s.tagName?Array.from(s.content.childNodes).forEach(t=>e.appendChild(t.cloneNode(!0))):j(s,e,n,r))})}}if(s)try{s(t)}catch(t){console.error("Error in component setupFunc for",e,t)}}const x={clone:window.structuredClone||(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=>x.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>x.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 n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].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,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),s=r-e;return e=r,t+=s,n++,s},avg:()=>t/n}}};globalThis.Util=x;let S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=i({},e=>x.safeJson(S.get(e)),(e,t)=>{const n=S.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?S.delete(e):S.set(e,r),window.location.hash="#"+S.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=S;S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),S.forEach((e,n)=>{t.get(n)!==e&&(M[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===S.get(t)&&(M[t]=void 0)})});const $=i({},e=>x.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=M,globalThis.LocalStorage=$,"undefined"!=typeof document){const e=()=>{T._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&A(e)}),e.removedNodes.forEach(e=>E(e))})}).observe(document.documentElement,{childList:!0,subtree:!0});const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),A(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{d as $,c as $$,T as Component,M as Hash,$ as LocalStorage,i as NewState,y as RefreshState,m as SetTranslator,x as Util,A as _scanTree,E as _unbindTree}; diff --git a/src/core.js b/src/core.js index 2a6c60e..da92fa3 100644 --- a/src/core.js +++ b/src/core.js @@ -8,16 +8,17 @@ export function setDisableRunCodeError(value) { const _fnCache = new Map(); export function _runCode(code, vars, thisObj, extendVars) { - const argKeys = [...Object.keys(extendVars || {}), ...Object.keys(vars || {})]; - const argValues = [...Object.values(extendVars || {}), ...Object.values(vars || {})]; + const allVars = { ...(extendVars || {}), ... (vars || {}) }; + const argKeys = Object.keys(allVars); + const argValues = Object.values(allVars); const cacheKey = code + argKeys.join(','); try { let fn = _fnCache.get(cacheKey); if (!fn) { - fn = new Function(...argKeys, code); + fn = new Function('Hash', 'LocalStorage', 'State', ...argKeys, code); _fnCache.set(cacheKey, fn); } - return fn.apply(thisObj, argValues); + return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]); } catch (e) { if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]); return null; diff --git a/src/dom.js b/src/dom.js index 687757d..2b428e0 100644 --- a/src/dom.js +++ b/src/dom.js @@ -210,6 +210,9 @@ export const _parseNode = (node, scanObj) => { if (node._bindings) { node._bindings.forEach(binding => _updateBinding(binding)); if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); + if (node.hasAttribute('onupdate')) { + _runCode(node.getAttribute('onupdate'), { thisNode: node }, node._thisObj || node, node._ref || {}); + } return; } @@ -238,11 +241,12 @@ export const _parseNode = (node, scanObj) => { _initBinding({ node: node, prop: realAttrName.split('.'), tpl, exp }); } else { if (realAttrName.startsWith('on')) { - if (realAttrName === 'onupdate') node._hasOnUpdate = true; - if (realAttrName === 'onload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true; - if (realAttrName === 'onunload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; + 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 === 'onunload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; ((node, thisObj) => { - node.addEventListener(realAttrName.slice(2), (e) => { + node.addEventListener(eventName, (e) => { _runCode(tpl, { event: e, thisNode: node, ...(e.detail || {}) }, thisObj || node, node._ref || {}); }); })(node, scanObj.thisObj); @@ -282,18 +286,23 @@ export const _parseNode = (node, scanObj) => { 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; - 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; - } - }); + 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'))) { const template = document.createElement('TEMPLATE'); diff --git a/test/perf.test.js b/test/perf.test.js new file mode 100644 index 0000000..2f579f8 --- /dev/null +++ b/test/perf.test.js @@ -0,0 +1,36 @@ +import { NewState } from '../src/observer.js' + +export async function profilePerformance() { + console.log('--- Performance Profiling ---'); + + const count = 1000; + const data = Array.from({ length: count }, (_, i) => ({ + id: i, + name: 'User ' + i, + active: i % 2 === 0, + tags: ['a', 'b', 'c'] + })); + + // 1. Measure NewState wrapping + const t1 = performance.now(); + const wrapped = data.map(item => NewState(item)); + const t2 = performance.now(); + console.log(`Wrapping ${count} items in NewState: ${(t2 - t1).toFixed(2)}ms`); + + // 2. Measure property access + const t3 = performance.now(); + let sum = 0; + for (let i = 0; i < count; i++) { + if (wrapped[i].active) sum++; + } + const t4 = performance.now(); + console.log(`Accessing 'active' property on ${count} proxies: ${(t4 - t3).toFixed(2)}ms`); + + // 3. Measure update notification + const t5 = performance.now(); + for (let i = 0; i < count; i++) { + wrapped[i].name = 'New Name ' + i; + } + const t6 = performance.now(); + console.log(`Updating 'name' property on ${count} proxies: ${(t6 - t5).toFixed(2)}ms`); +}