diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd4972..9b57581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGELOG +## v1.0.22 (2026-07-04) + +### 核心增强与样式合并优化 +- **双向样式智能合并与排序**: + - 重构了组件合并时的类名/样式排列机制。现在应用层(外部宿主)写的 `class/style` 将始终按高优先级排在组件内部样式的后侧(右侧),结构严格符合 `[内部静态, 内部动态, 外部动态, 外部静态]`。 + - 引入了**动态模板字符串合并(Template String Merging)**算法。当组件内外同时存在 `$class`/`$style` 或 `st-class`/`st-style` 绑定时,不采用覆写替换,而是将其物理合并为单一大模板字符串,确保各动态表达式能在正确的上下文(通过 parent 链)中独立安全求值。 + - 在 `_updateBinding` 渲染分支中加入了对 class 和 style 的**惰性捕获备份(Lazy Cache)**机制,只在首次更新时从 DOM 读取备份静态基准值,大幅减少不必要的 DOM 属性重置。 + +### 修复 +- **双重解析指令修复**: + - 修复了自 v1.0.19 大重构后,由于硬编码属性匹配列表导致双重解析指令(`$$if` / `$$each` 等)全系失效的 Bug。已恢复对非 `TEMPLATE` 节点的自动包裹提升以及对 `TEMPLATE` 节点上双重指令的正常提取。 +- **自动化测试缺陷修正**: + - 修正了在测试中因重写 `document.body.innerHTML` 导致 `body` 内已被挂载的 `template` 元素被误抹去的严重缺陷。 + - 修正了 `testPriority` 全局 `window.state` 缺失导致的空值求值缺陷,补齐了由于 `MutationObserver` 异步微任务渲染时序所需的 `await` 等待。 + - 修正了 `testMerging` 里非法 CSS 属性赋予 JS 动态绑定 `$style` 导致的语法解析错误,并补全了对同名动态绑定合并与优先级的断言。 + ## v1.0.17 (2026-06-05) ### 重大变更 (Philosophical Restoration) diff --git a/dist/state.js b/dist/state.js index 40c0b53..23bae78 100644 --- a/dist/state.js +++ b/dist/state.js @@ -222,12 +222,26 @@ 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)) { + } 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)); @@ -318,6 +332,7 @@ } } _setActiveBinding(null); + binding.lastResult = result; if (binding.prop) { const prop = binding.prop; let o = node; @@ -441,7 +456,21 @@ 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 node.setAttribute(attr, result ?? ""); + else if (attr === "class") { + if (node._staticClasses === void 0) { + 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 === void 0) { + 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 ?? ""); } } } @@ -486,9 +515,9 @@ } let attrs = []; if (node.tagName === "TEMPLATE") { - ["$if", "$each", "st-if", "st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); + ["$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"].includes(a.name) || a.name.includes(".")); + 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; @@ -552,9 +581,9 @@ }); node._stTranslated = true; } - if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) { + 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"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name)); + 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); @@ -565,13 +594,13 @@ _scanTree(template, scanObj); return; } - if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) { + 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((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name)); + const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(attr2.name)); const attr = attrs[attrs.length - 1]; template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); - if (attr.name === "$each" || attr.name === "st-each") { + if (["$each", "st-each", "$$each", "st-st-each"].includes(attr.name)) { Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => { template.setAttribute(attr2.name, attr2.value); node.removeAttribute(attr2.name); @@ -658,5 +687,9 @@ exports2.State = State; exports2.Util = Util; exports2.__unsafeRefreshState = __unsafeRefreshState; + exports2._returnCode = _returnCode; + exports2._runCode = _runCode; + exports2.onNotifyUpdate = _onNotifyUpdate; + exports2.setActiveBinding = _setActiveBinding; Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" }); }); diff --git a/dist/state.min.js b/dist/state.min.js index 996cb01..2a0b560 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,n;const s={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=>s.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>s.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:s=>(e+=s,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 s=(new Date).getTime(),a=s-e;return e=s,t+=a,n++,a},avg:()=>t/n}}},a=(e,t)=>t?e.querySelector(t):document.querySelector(e),r=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);globalThis.Util=s,globalThis.$=a,globalThis.$$=r;let o=null,i=null;const l=e=>o=e,c=e=>i=e,d=new Set;function h(e={},t=null,n=null){const s={},a=new Map,r=new Map,l=(e,t)=>(r.has(e)||r.set(e,new Set),t?r.get(e).add(t):r.get(e).clear(),()=>r.get(e).delete(t)),c=(e,t)=>{r.has(e)&&r.set(e,new Set),r.get(e).delete(t)},h=t||(e=>s[e]),u=n||((e,t)=>s[e]=t);return Object.assign(s,e),new Proxy(s,{get:(e,t)=>"__watch"===t?l:"__unwatch"===t?c:"__isProxy"===t||(o&&(a.has(t)||a.set(t,new Set),a.get(t).add(o),o.node._states||(o.node._states=new Set),o.node._states.add(a)),h(t)),set(e,t,n){if(h(t)!==n&&u(t,n),r.has(t)&&r.get(t).forEach(s=>{const a=s(n);void 0!==a&&(n=a,e[t]=n)}),r.has(null)&&r.get(null).forEach(e=>e(n)),a.has(t)){const e=a.get(t);for(const t of e)t.node.isConnected?i!==t.node&&d.forEach(e=>e(t)):e.delete(t)}return!0}})}globalThis.NewState=h;let u=new URLSearchParams("undefined"!=typeof globalThis&&(null==(n=null==(t=globalThis.location)?void 0:t.hash)?void 0:n.substring(1))||"");const f=h({},e=>s.safeJson(u.get(e)),(e,t)=>{const n=u.get(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?u.delete(e):u.set(e,s),globalThis.location.hash="#"+u.toString())});"undefined"!=typeof globalThis&&globalThis.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=globalThis.location.hash)?void 0:e.substring(1))||""),n=new Set([...u.keys(),...t.keys()]);u=t,n.forEach(e=>f[e]=f[e])});const b=h({},e=>s.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,s))}),m=h({exitBlocks:0});globalThis.Hash=f,globalThis.LocalStorage=b,globalThis.State=m;let p=!1;const g=e=>{p=e},_=new Map;function y(e,t,n,s){const a={...s||{},...t||{}},r=Object.keys(a),o=Object.values(a),i=e+r.join(",");try{let t=_.get(i);return t||(t=new Function("Hash","LocalStorage","State",...r,e),_.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(a){return p||console.error(a,s,[e,s,t,n]),null}}function A(e,t,n,s){return e.includes("${")?y("return `"+e+"`",t,n,s):y("return "+e,t,n,s)}const E=new Map,v=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...s)=>{E.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,n,s):v.push([e,n,s])},exists:e=>E.has(e.toUpperCase()),getSetupFunction:e=>E.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:()=>{v.forEach(([e,t,n])=>T._addTemplate(e,t,n)),v.length=0}};function N(e,t,n,s={}){e.attributes&&Array.from(e.attributes).forEach(e=>{"class"!==e.name&&("style"===e.name?t.hasAttribute("style")?t.setAttribute("style",`${e.value}; ${t.getAttribute("style")}`):t.setAttribute("style",e.value):t.hasAttribute(e.name)||t.setAttribute(e.name,e.value))}),t.classList.add(...e.classList);const a="TEMPLATE"===t.tagName?t.content:t,r="TEMPLATE"===e.tagName?e.content.childNodes:e.childNodes;Array.from(r).forEach(e=>a.appendChild(e)),e.tagName&&T.exists(e.tagName)&&O(e.tagName,t,n,s)}function O(e,t,n,s={}){if(s[e])return;s[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 a=T.getSetupFunction(e),o={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(o[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=h(t.state||{});const i=T.getTemplate(e);if(i){const e=i.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&N(a,t,n,s),r(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");o[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",N(o[t],e,n,s))})}}a&&a(t)}let S=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const j=e=>S=e,x=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),s={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>s[e.substring(1,e.length-1)]=n[t+1]||"")}return S(n[0],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)}}var $;function M(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&M(e)}))}function w(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;l(e);let n=e.exp?e.tpl?A(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof n)try{n=A(n,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(l(null),e.prop){const s=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]):(M(t),t._renderedNodes=[]);else if("each"===s)if(n&&"object"==typeof n){const e=t.getAttribute("as")||"item",s=t.getAttribute("index")||"index",a=t.getAttribute("key");let r,o;if(n instanceof Map)r=Array.from(n.keys()),o=e=>n.get(e);else if("function"==typeof n[Symbol.iterator]){const e=Array.isArray(n)?n:Array.from(n);r=new Array(e.length);for(let t=0;te[t]}else r=Object.keys(n),o=e=>n[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,l=[];r.forEach((n,r)=>{const c=o(n),d=a?c&&"object"==typeof c?c[a]:c:n,h=null==d||i.has(d)?`st_key_${r}`:d;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(a=>{t.parentNode.insertBefore(a,t),a._ref[s]=n,a._ref[e]=c,L(a)})):(u=[],t._children.forEach(a=>{const r=a.cloneNode(!0);r._ref={...t._ref,[s]:n,[e]:c},r._thisObj=t._thisObj,t.parentNode.insertBefore(r,t),u.push(r)})),i.set(h,u),l.push(u)}),t._keyedNodes.forEach(e=>e.forEach(e=>{M(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=l}else M(t),t._renderedNodes=[];else if("bind"===s){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||n||(y(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),n=[]),t._checkboxMultiMode=n instanceof Array;const s=n instanceof Array?n.includes(t.value):!!n;t.checked!==s&&(t.checked=s)}else"radio"===t.type?t.checked!==(t.value===String(n??""))&&(t.checked=t.value===String(n??"")):"value"in t&&"file"!==t.type?Promise.resolve().then(()=>{t.value!==String(n??"")&&(t.value=n)}):t.isContentEditable&&t.innerHTML!==String(n??"")&&(t.innerHTML=n);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:n}))}else["checked","disabled","readonly"].includes(s)&&(n=!!n),"boolean"==typeof n?n?t.setAttribute(s,""):t.removeAttribute(s):void 0!==n&&("string"!=typeof n&&(n=JSON.stringify(n)),"text"===s?t.textContent=n??"":"html"===s?t.innerHTML=n??"":"IMG"===t.tagName&&"src"===s&&n.includes(".svg")?t.setAttribute("_src",n??""):t.setAttribute(s,n??""))}}function C(e){e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),w(e)}$=e=>w(e),d.add($);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"))){const n=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(t=>{n.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(n,e),n.content.appendChild(e),n._ref=e._ref,void L(n,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)),s=n[n.length-1];t.setAttribute(s.name,s.value),e.removeAttribute(s.name),"$each"!==s.name&&"st-each"!==s.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;else{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}:{}}t.extendVars&&Object.assign(e._ref,t.extendVars),function(e,t){if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>w({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));T.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var s;if(n.name.startsWith("$.")){const a=n.name.slice(2);let r=x(n.value);t.thisObj&&r.includes("this.")&&(r=r.replace(/\bthis\./g,"this.parent."));const o=A(r,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=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 n=[];"TEMPLATE"===e.tagName?["$if","$each","st-if","st-each"].forEach(t=>e.hasAttribute(t)&&n.push(e.getAttributeNode(t))):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!==t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,n.forEach(n=>{let s=0;n.name.startsWith("$$")||n.name.startsWith("st-st-")?s=2:(n.name.startsWith("$")||n.name.startsWith("st-"))&&(s=1);const a=2===s?n.name.startsWith("$$")?n.name.slice(2):n.name.slice(6):1===s?n.name.startsWith("$")?n.name.slice(1):n.name.slice(3):n.name;let r=n.value;if(e.removeAttribute(n.name),a.startsWith("."))C({node:e,prop:a.split("."),tpl:r,exp:s});else if(a.startsWith("on")){const n=a.slice(2);"update"===n&&(e._hasOnUpdate=!0),"load"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>y(r,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else{if("bind"===a){const n=["INPUT","TEXTAREA"].includes(e.tagName)&&["textarea","text","password","email","number","search","url","tel"].includes(e.type||"text")||e.isContentEditable;e.addEventListener(n?"input":"change",n=>{let s=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;c(e),g(!0),"checkbox"===e.type&&e._checkboxMultiMode?y(`!!checked ? (!${r}.includes(val) && ${r}.push(val)) : (index = ${r}.indexOf(val), index > -1 && ${r}.splice(index, 1))`,{val:e.value,checked:s,thisNode:e},t.thisObj||e,e._ref||{}):y(`${r} = val`,{val:s,thisNode:e},t.thisObj||e,e._ref||{}),g(!1),c(null)})}else"text"!==a||r||(r=e.textContent,e.textContent="");r&&(r=x(r),C({node:e,attr:a,tpl:r,exp:s}))}}),(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(n=>L(n,{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[n,s]of t)for(const t of s)t.node===e&&s.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.$$=r,e.Component=T,e.Hash=f,e.LocalStorage=b,e.NewState=h,e.SetTranslator=j,e.State=m,e.Util=s,e.__unsafeRefreshState=P,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 df10a23..4133525 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apigo.cc/state", - "version": "1.0.21", + "version": "1.0.22", "type": "module", "main": "dist/state.js", "module": "dist/state.js", diff --git a/src/engine.js b/src/engine.js index d7ed8b7..35fb64c 100644 --- a/src/engine.js +++ b/src/engine.js @@ -42,12 +42,26 @@ export function _mergeNode(from, to, scanObj, exists = {}) { 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)) { + } 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; @@ -145,6 +159,7 @@ export function _updateBinding(binding) { 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; @@ -262,7 +277,21 @@ export function _updateBinding(binding) { 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 node.setAttribute(attr, 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 ?? ''); } } } @@ -309,11 +338,12 @@ export function _parseNode(node, scanObj) { let attrs = []; if (node.tagName === 'TEMPLATE') { - ['$if', '$each', 'st-if', 'st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); + ['$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'].includes(a.name) || a.name.includes('.')); + 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 || {}; @@ -374,9 +404,9 @@ export const _scanTree = (node, scanObj = {}) => { node._stTranslated = true; } - if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each'))) { + 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'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each')) && ['as', 'index'].includes(attr.name))); + 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); @@ -388,13 +418,13 @@ export const _scanTree = (node, scanObj = {}) => { return; } - if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each'))) { + 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'].includes(attr.name)); + 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 (attr.name === '$each' || attr.name === 'st-each') { + 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)); diff --git a/src/index.js b/src/index.js index 8f9a8d0..ad9d747 100644 --- a/src/index.js +++ b/src/index.js @@ -35,8 +35,8 @@ if (typeof document !== 'undefined') { else document.addEventListener('DOMContentLoaded', init, true); } -export { NewState } from './observer.js'; +export { NewState, _setActiveBinding as setActiveBinding, _onNotifyUpdate as onNotifyUpdate } from './observer.js'; export { Component, SetTranslator } from './engine.js'; export { $, $$, Util } from './utils.js'; -export { Hash, LocalStorage, State } from './core.js'; +export { Hash, LocalStorage, State, _runCode, _returnCode } from './core.js'; export const __unsafeRefreshState = _scanTree; diff --git a/test-results/.last-run.json b/test-results/.last-run.json index 12c3f04..cbcc1fb 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,6 +1,4 @@ { - "status": "failed", - "failedTests": [ - "8a84b43f13b676ea22b7-5fcda25ae3a58304c071" - ] + "status": "passed", + "failedTests": [] } \ No newline at end of file diff --git a/test-results/all-modular-unit-tests-and-benchmark/error-context.md b/test-results/all-modular-unit-tests-and-benchmark/error-context.md deleted file mode 100644 index c8cb2ae..0000000 --- a/test-results/all-modular-unit-tests-and-benchmark/error-context.md +++ /dev/null @@ -1,89 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: all.spec.js >> modular unit tests and benchmark -- Location: test/all.spec.js:5:1 - -# Error details - -``` -Error: expect(received).toBe(expected) // Object.is equality - -Expected: "passed" -Received: "failed" -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | import fs from 'fs'; - 3 | import path from 'path'; - 4 | - 5 | test('modular unit tests and benchmark', async ({ page }) => { - 6 | page.on('console', msg => console.log('BROWSER LOG:', msg.text())); - 7 | await page.goto('http://localhost:8081/test/index.html'); - 8 | - 9 | await page.waitForFunction(() => window.testStatus !== undefined, { timeout: 10000 }); - 10 | const status = await page.evaluate(() => window.testStatus); -> 11 | expect(status).toBe('passed'); - | ^ Error: expect(received).toBe(expected) // Object.is equality - 12 | - 13 | // Read benchmarks from TEST.md - 14 | const testMd = fs.readFileSync(path.join(process.cwd(), 'TEST.md'), 'utf-8'); - 15 | const getBench = (name) => { - 16 | const match = testMd.match(new RegExp(`\\*\\*${name}\\*\\*\\s*\\|\\s*([\\d.]+)`)); - 17 | return match ? parseFloat(match[1]) : null; - 18 | }; - 19 | const baseInitial = getBench('首次渲染 \\(1000 items\\)'); - 20 | const baseUpdate = getBench('浅更新 \\(Shallow Update\\)'); - 21 | - 22 | // Benchmark: Large list rendering - 23 | const renderTime = await page.evaluate(async () => { - 24 | const start = performance.now(); - 25 | document.body.innerHTML = ` - 26 |
    - 27 | - 30 |
- 31 | `; - 32 | const items = []; - 33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i}); - 34 | window.state.benchItems = items; - 35 | const { _unsafeRefreshState } = await import('@apigo.cc/state'); - 36 | _unsafeRefreshState(document.documentElement); - 37 | return performance.now() - start; - 38 | }); - 39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`); - 40 | if (baseInitial) expect(renderTime).toBeLessThan(baseInitial * 1.2); - 41 | - 42 | // Benchmark: Large list update - 43 | const updateTime = await page.evaluate(async () => { - 44 | const start = performance.now(); - 45 | window.state.benchItems[0].val = 'updated'; - 46 | window.state.benchItems = [...window.state.benchItems]; - 47 | return performance.now() - start; - 48 | }); - 49 | console.log(`BENCHMARK: 1000 items update (shallow): ${updateTime.toFixed(2)}ms`); - 50 | if (baseUpdate) expect(updateTime).toBeLessThan(baseUpdate * 1.2); - 51 | - 52 | // Extreme Data Test - 53 | await page.evaluate(async () => { - 54 | const { _unsafeRefreshState } = await import('@apigo.cc/state'); - 55 | document.body.innerHTML = '
'; - 56 | window.state.extreme = null; - 57 | _unsafeRefreshState(document.getElementById('extreme')); - 58 | window.state.extreme = undefined; - 59 | window.state.extreme = { a: 1 }; - 60 | window.state.extreme = [1, 2]; - 61 | window.state.extreme = "not iterable"; - 62 | }); - 63 | }); - 64 | -``` \ No newline at end of file diff --git a/test/all.spec.js b/test/all.spec.js index cccf07c..de078b9 100644 --- a/test/all.spec.js +++ b/test/all.spec.js @@ -32,8 +32,8 @@ test('modular unit tests and benchmark', async ({ page }) => { const items = []; for(let i=0; i<1000; i++) items.push({val: 'item ' + i}); window.state.benchItems = items; - const { ___unsafeRefreshState } = await import('@apigo.cc/state'); - ___unsafeRefreshState(document.documentElement); + const { __unsafeRefreshState } = window.ApigoState; + __unsafeRefreshState(document.documentElement); return performance.now() - start; }); console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`); @@ -51,10 +51,10 @@ test('modular unit tests and benchmark', async ({ page }) => { // Extreme Data Test await page.evaluate(async () => { - const { ___unsafeRefreshState } = await import('@apigo.cc/state'); + const { __unsafeRefreshState } = window.ApigoState; document.body.innerHTML = '
'; window.state.extreme = null; - ___unsafeRefreshState(document.getElementById('extreme')); + __unsafeRefreshState(document.getElementById('extreme')); window.state.extreme = undefined; window.state.extreme = { a: 1 }; window.state.extreme = [1, 2]; diff --git a/test/component.test.js b/test/component.test.js index f0d6c36..ef09145 100644 --- a/test/component.test.js +++ b/test/component.test.js @@ -1,16 +1,18 @@ // test/component.test.js window.testComponent = async function() { - const { Component, ___unsafeRefreshState, $ } = ApigoState; + const { Component, __unsafeRefreshState, $ } = ApigoState; console.log('Testing component.js...'); // 1. Register component Component.register('TestComp', container => { container.state.msg = 'Component Content'; - }, document.createRange().createContextualFragment('
').firstChild); + }, document.createRange().createContextualFragment('
').firstChild); // 2. Render component - document.body.innerHTML = ''; - ___unsafeRefreshState(document.documentElement); + const comp = document.createElement('TestComp'); + comp.id = 'comp-inst'; + document.body.appendChild(comp); + __unsafeRefreshState(document.documentElement); const instance = $('#comp-inst'); if (!instance) throw new Error('Component instance not found'); diff --git a/test/dom.test.js b/test/dom.test.js index 0835145..5e4fa34 100644 --- a/test/dom.test.js +++ b/test/dom.test.js @@ -1,6 +1,6 @@ // test/dom.test.js window.testDom = async function() { - const { ___unsafeRefreshState, $, $$, NewState } = ApigoState; + const { __unsafeRefreshState, $, $$, NewState } = ApigoState; console.log('Testing dom.js...'); const wait = () => new Promise(r => setTimeout(r, 10)); @@ -10,7 +10,7 @@ window.testDom = async function() { const state = NewState({ msg: 'hello' }); window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见 document.documentElement._thisObj = { state }; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed'); state.msg = 'world'; @@ -20,7 +20,7 @@ window.testDom = async function() { // 2. $if directive document.body.innerHTML = ''; state.show = false; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); if ($('#test-if')) throw new Error('$if fail: should be hidden'); state.show = true; @@ -30,7 +30,8 @@ window.testDom = async function() { // 3. $each directive document.body.innerHTML = ''; state.items = ['A', 'B']; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); + await wait(); if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch'); if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch'); @@ -41,14 +42,14 @@ window.testDom = async function() { // 4. Event binding $onclick document.body.innerHTML = ''; state.count = 0; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); $('#test-click').click(); if (state.count !== 1) throw new Error('$onclick failed'); // 5. $bind (input) document.body.innerHTML = ''; state.val = 'init'; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); await wait(); // TRY: 等待 $bind 的 setTimeout 完成 const input = $('#test-bind'); if (input.value !== 'init') throw new Error('$bind initial failed'); @@ -74,13 +75,13 @@ window.testDom = async function() { const root = $('#double-eval-root'); root._thisObj = { state: doubleState }; - ___unsafeRefreshState(root); + __unsafeRefreshState(root); await wait(); if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially'); console.log('Enabling inner node...'); doubleState.innerShow = true; - ___unsafeRefreshState(root); + __unsafeRefreshState(root); await wait(); const inner = $('#inner-node'); if (!inner) throw new Error('$$if failed: should be visible after innerShow=true'); @@ -100,12 +101,12 @@ window.testDom = async function() { nestedRoot._thisObj = { state: doubleState }; doubleState.outer = true; doubleState.innerShow = false; - ___unsafeRefreshState(nestedRoot); + __unsafeRefreshState(nestedRoot); await wait(); if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially'); doubleState.innerShow = true; - ___unsafeRefreshState(nestedRoot); + __unsafeRefreshState(nestedRoot); await wait(); if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update'); diff --git a/test/foundation.test.js b/test/foundation.test.js index 6ece980..854854b 100644 --- a/test/foundation.test.js +++ b/test/foundation.test.js @@ -1,6 +1,6 @@ // test/foundation.test.js window.testFoundation = async function() { - const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState; + const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState; console.log('Testing framework foundation...'); Component.register('NavTest', container => { @@ -26,7 +26,7 @@ window.testFoundation = async function() { const inst = document.createElement('NAVTEST'); inst.id = 'nav-inst'; document.body.appendChild(inst); - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 100)); const nav = $('#nav-inst'); diff --git a/test/inheritance.test.js b/test/inheritance.test.js index d4e540b..158299c 100644 --- a/test/inheritance.test.js +++ b/test/inheritance.test.js @@ -1,6 +1,6 @@ // test/inheritance.test.js window.testInheritance = async function() { - const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState; + const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState; console.log('Testing inheritance...'); Component.register('MyComp', container => { @@ -26,7 +26,7 @@ window.testInheritance = async function() { inst.id = 'comp-inst'; document.body.appendChild(inst); - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 100)); const comp = $('#comp-inst'); diff --git a/test/merging.test.js b/test/merging.test.js index 230cb04..fe7bd1a 100644 --- a/test/merging.test.js +++ b/test/merging.test.js @@ -1,14 +1,19 @@ // test/merging.test.js window.testMerging = async function() { - const { Component, ___unsafeRefreshState, $ } = ApigoState; + const { Component, __unsafeRefreshState, $ } = ApigoState; console.log('Testing complex class/style merging (DataTable Resizer scenario)...'); Component.register('Resizer-Real', container => { container.isVertical = true; - }, document.createRange().createContextualFragment('
').firstChild); + }, document.createRange().createContextualFragment('
').firstChild); - document.body.innerHTML = ''; - ___unsafeRefreshState(document.documentElement); + const res = document.createElement('Resizer-Real'); + res.id = 'res-inst'; + res.className = 'ins-static'; + res.setAttribute('style', 'opacity:0.5'); + res.setAttribute('$class', "'ins-dynamic'"); + document.body.appendChild(res); + __unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 50)); const inst = $('#res-inst'); @@ -17,9 +22,14 @@ window.testMerging = async function() { const classList = Array.from(inst.classList); console.log('Final ClassList:', classList); - if (!classList.includes('ins-static') || !classList.includes('tpl-static')) { + if (!classList.includes('ins-static') || !classList.includes('tpl-static') || !classList.includes('ins-dynamic')) { throw new Error('Class merging failed'); } + const tplIndex = classList.indexOf('tpl-static'); + const insIndex = classList.indexOf('ins-static'); + if (tplIndex > insIndex) { + throw new Error('Class merging order incorrect: tpl-static should be before ins-static'); + } const style = inst.getAttribute('style'); console.log('Final Style:', style); diff --git a/test/priority.test.js b/test/priority.test.js index dd055a4..876f083 100644 --- a/test/priority.test.js +++ b/test/priority.test.js @@ -1,14 +1,16 @@ // test/priority.test.js window.testPriority = async function() { - const { ___unsafeRefreshState, $, NewState } = ApigoState; + const { __unsafeRefreshState, $, NewState } = ApigoState; console.log('Testing directive priorities...'); // Test $if vs $text (if should hide text) - document.body.innerHTML = '
'; + document.body.innerHTML = '
'; const state = NewState({ hide: false, msg: 'visible' }); + window.state = state; document.documentElement._thisObj = { state }; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); + await new Promise(r => setTimeout(r, 20)); if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed'); state.hide = true; diff --git a/test/timing.test.js b/test/timing.test.js index bd87577..119864f 100644 --- a/test/timing.test.js +++ b/test/timing.test.js @@ -1,6 +1,6 @@ // test/timing.test.js window.testTiming = async function() { - const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState; + const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState; console.log('Testing initialization timing...'); Component.register('TimingComp', container => { @@ -14,7 +14,7 @@ window.testTiming = async function() { `)); document.body.innerHTML += ``; - ___unsafeRefreshState(document.documentElement); + __unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 100));