From 7660de77210627f8a0d77364f06bf55371f88411 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Sat, 6 Jun 2026 23:16:17 +0800 Subject: [PATCH] release: v1.0.17 - Philosophical Restoration and API Sanitization. By: AICoder --- CHANGELOG.md | 2 +- README.md | 2 +- dist/state.js | 58 +++++++++++-------- dist/state.min.js | 2 +- dist/state.min.mjs | 2 +- dist/state.mjs | 58 +++++++++++-------- old/state_manybeok.js | 2 +- old/state_original.js | 2 +- package.json | 2 +- src/dom.badbak.for2exp.js | 2 +- src/dom.js | 37 ++++++++---- src/dom.js.keyed.bak | 2 +- src/index.js | 12 ++-- src/observer.js | 24 ++++---- .../error-context.md | 8 +-- test/all.spec.js | 8 +-- test/component.test.js | 4 +- test/dom.test.js | 20 +++---- test/foundation.test.js | 4 +- test/inheritance.test.js | 4 +- test/merging.test.js | 4 +- test/priority.test.js | 4 +- test/timing.test.js | 4 +- 23 files changed, 146 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c0252..8fd4972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### 重大变更 (Philosophical Restoration) - **核心逻辑回位**: 物理还原 v2.3 原始架构,彻底消灭所有 ESM 时期的“Magic”补丁逻辑(如 `_stManaged` 等标志位)。 - **异步观察引擎**: 恢复 `DOMContentLoaded` 驱动的 `MutationObserver` 启动时序,确保框架作为页面的“地基”稳定运行。 -- **危险 API 重命名**: 将 `RefreshState` 重命名为 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。 +- **危险 API 重命名**: 将 `RefreshState` 重命名为 `_unsafeRefreshState`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。 ### 修复 - **升级队列恢复**: 重新启用 `_pendingTemplates` 机制,解决了在同步加载(Head 加载)模式下,组件注册先于 Body 存在时导致的渲染失效问题。 diff --git a/README.md b/README.md index c335015..ef3afa7 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,6 @@ AI 必须根据不同的元素类型执行以下逻辑: ## 6. 运行约束 (Constraints) -1. **禁止滥用同步刷新**:**严禁** 在常规开发中调用 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios()`。该函数仅为极高性能干预(如万级数据表格)预留。 +1. **禁止滥用同步刷新**:**严禁** 在常规开发中调用 `_unsafeRefreshState()`。该函数仅为极高性能干预(如万级数据表格)预留。 2. **数据流向**:所有状态变更必须通过对 `NewState` 代理对象的赋值完成。 3. **Key 的必要性**:在大规模数据(>100条)或复杂交互列表中,必须提供唯一 `key` 以激活节点复用逻辑。 diff --git a/dist/state.js b/dist/state.js index c2d2c05..e8629da 100644 --- a/dist/state.js +++ b/dist/state.js @@ -3,12 +3,12 @@ })(this, function(exports2) { "use strict"; var _a; - let _activeBinding = null; - let _noWriteBack = null; - const setActiveBinding = (val) => _activeBinding = val; - const setNoWriteBack = (val) => _noWriteBack = val; + let __activeBinding = null; + let __noWriteBack = null; + const _setActiveBinding = (val) => __activeBinding = val; + const _setNoWriteBack = (val) => __noWriteBack = val; const _notifiers = /* @__PURE__ */ new Set(); - const onNotifyUpdate = (fn) => _notifiers.add(fn); + const _onNotifyUpdate = (fn) => _notifiers.add(fn); function NewState(defaults = {}, getter = null, setter = null) { const _defaults = {}; const _stateMappings = /* @__PURE__ */ new Map(); @@ -30,11 +30,11 @@ if (key === "__watch") return _watchFunc; if (key === "__unwatch") return _unwatchFunc; if (key === "__isProxy") return true; - if (_activeBinding) { + if (__activeBinding) { if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set()); - _stateMappings.get(key).add(_activeBinding); - if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set(); - _activeBinding.node._states.add(_stateMappings); + _stateMappings.get(key).add(__activeBinding); + if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set(); + __activeBinding.node._states.add(_stateMappings); } return __getter(key); }, @@ -61,7 +61,7 @@ bindings.delete(binding); continue; } - if (_noWriteBack !== binding.node) { + if (__noWriteBack !== binding.node) { _notifiers.forEach((fn) => fn(binding)); } } @@ -206,7 +206,7 @@ }; } } - onNotifyUpdate((binding) => _updateBinding(binding)); + _onNotifyUpdate((binding) => _updateBinding(binding)); function _clearRenderedNodes(node) { if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => { child.remove(); @@ -216,7 +216,7 @@ function _updateBinding(binding) { const node = binding.node; if (!node.isConnected && node.tagName !== "TEMPLATE") return; - setActiveBinding(binding); + _setActiveBinding(binding); let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl; if (binding.exp === 2 && typeof result === "string") { try { @@ -224,7 +224,7 @@ } catch (e) { } } - setActiveBinding(null); + _setActiveBinding(null); if (binding.prop) { const prop = binding.prop; let o = node; @@ -235,10 +235,9 @@ if (typeof o !== "object") break; } if (typeof o === "object" && o !== null) { - const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result); const lk = prop[prop.length - 1]; if (lk) { - if (resultIsObject && o[lk] == null) o[lk] = {}; + if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; const lo = o[lk]; if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result); else { @@ -419,12 +418,12 @@ if (realAttrName === "bind") { node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => { let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail; - setNoWriteBack(node); + _setNoWriteBack(node); setDisableRunCodeError(true); if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); setDisableRunCodeError(false); - setNoWriteBack(null); + _setNoWriteBack(null); }); } else if (realAttrName === "text" && !tpl) { tpl = node.textContent; @@ -451,6 +450,19 @@ }); 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)); + attrs.forEach((attr) => { + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + }); + node.parentNode.insertBefore(template, node); + template.content.appendChild(node); + template._ref = node._ref; + _scanTree(template, scanObj); + return; + } if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) { const template = document.createElement("TEMPLATE"); const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name)); @@ -508,7 +520,7 @@ }); node.childNodes && node.childNodes.forEach((child) => _unbindTree(child)); }; - const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree; + const _unsafeRefreshState = _scanTree; const Util = { clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))), base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))), @@ -603,18 +615,14 @@ Component, $, $$, - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, + RefreshState: _unsafeRefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, - State, - _runCode, - _returnCode, - onNotifyUpdate, - setActiveBinding + State }; if (typeof window !== "undefined") { window.ApigoState = ApigoState; @@ -645,10 +653,10 @@ exports2.Hash = Hash; exports2.LocalStorage = LocalStorage; exports2.NewState = NewState; + exports2.RefreshState = _unsafeRefreshState; exports2.SetTranslator = SetTranslator; exports2.State = State; exports2.Util = Util; - exports2.____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios; exports2._scanTree = _scanTree; exports2._unbindTree = _unbindTree; Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" }); diff --git a/dist/state.min.js b/dist/state.min.js index c865b9a..b665339 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={})}(this,function(e){"use strict";var t;let n=null,s=null;const a=e=>n=e,r=e=>s=e,o=new Set,i=e=>o.add(e);function l(e={},t=null,a=null){const r={},i=new Map,l=new Map,d=(e,t)=>(l.has(e)||l.set(e,new Set),t?l.get(e).add(t):l.get(e).clear(),()=>l.get(e).delete(t)),c=(e,t)=>{l.has(e)&&l.set(e,new Set),l.get(e).delete(t)},h=t||(e=>r[e]),u=a||((e,t)=>r[e]=t);return Object.assign(r,e),new Proxy(r,{get:(e,t)=>"__watch"===t?d:"__unwatch"===t?c:"__isProxy"===t||(n&&(i.has(t)||i.set(t,new Set),i.get(t).add(n),n.node._states||(n.node._states=new Set),n.node._states.add(i)),h(t)),set(e,t,n){if(h(t)!==n&&u(t,n),l.has(t)&&l.get(t).forEach(s=>{const a=s(n);void 0!==a&&(n=a,e[t]=n)}),l.has(null)&&l.get(null).forEach(e=>e(n)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?s!==t.node&&o.forEach(e=>e(t)):e.delete(t)}return!0}})}const d=(e,t)=>t?e.querySelector(t):document.querySelector(e),c=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),h=new Map,u=[],f={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...s)=>{console.log("Component.register:",e.toUpperCase()),h.set(e.toUpperCase(),t),"loading"!==document.readyState?f._addTemplate(e,n,s):u.push([e,n,s])},exists:e=>h.has(e.toUpperCase()),getSetupFunction:e=>h.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:()=>{u.forEach(([e,t,n])=>f._addTemplate(e,t,n)),u.length=0}};function b(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),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&f.exists(e.tagName)&&p(e.tagName,t,n,s)}function p(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=f.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=l(t.state||{});const o=f.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&b(a,t,n,s),c(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",b(r[t],e,n,s))})}}a&&a(t)}let m=!1;function g(e){m=e}const _=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 m||console.error(a,s,[e,s,t,n]),null}}function v(e,t,n,s){return e.includes("${")?y("return `"+e+"`",t,n,s):y("return "+e,t,n,s)}let E=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const A=e=>E=e,N=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 E(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)}}function O(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&O(e)}))}function S(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;a(e);let n=e.exp?e.tpl?v(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof n)try{n=v(n,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(a(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]):(O(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 d=o(n),c=a?d&&"object"==typeof d?d[a]:d:n,h=null==c||i.has(c)?`st_key_${r}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(t=>{t._ref[s]=n,t._ref[e]=d,T(t)})):(u=[],t._children.forEach(a=>{const r=a.cloneNode(!0);r._ref={...t._ref,[s]:n,[e]:d},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=>{O(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=l}else O(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??""))}}i(e=>S(e));const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),S(e)},T=(e,t={})=>{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=N(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0),"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),((e,t)=>{if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>S({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));f.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=N(n.value);r.includes("this.")&&(r=r.replace(/\bthis\./g,"this.parent."));const o=v(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.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 o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:o,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(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===a?e.addEventListener(["textarea","text","password"].includes(e.type||"text")||e.isContentEditable?"input":"change",n=>{let s=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;r(e),g(!0),"checkbox"===e.type&&e._checkboxMultiMode?y(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:s,thisNode:e},t.thisObj||e,e._ref||{}):y(`${o} = val`,{val:s,thisNode:e},t.thisObj||e,e._ref||{}),g(!1),r(null)}):"text"!==a||o||(o=e.textContent,e.textContent=""),o&&(o=N(o),w({node:e,attr:a,tpl:o,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=>T(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},j=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=>j(e)))},x=T,$={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=>$.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>$.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}}};globalThis.Util=$;let M=new URLSearchParams((null==(t=window.location.hash)?void 0:t.substring(1))||"");const C=l({},e=>$.safeJson(M.get(e)),(e,t)=>{const n=M.get(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?M.delete(e):M.set(e,s),window.location.hash="#"+M.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),n=new Set([...M.keys(),...t.keys()]);M=t,n.forEach(e=>C[e]=C[e])});const L=l({},e=>$.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))}),k=l({exitBlocks:0});globalThis.Hash=C,globalThis.LocalStorage=L,globalThis.State=k;const P={NewState:l,Component:f,$:d,$$:c,____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios:x,SetTranslator:A,_scanTree:T,_unbindTree:j,Util:$,Hash:C,LocalStorage:L,State:k,_runCode:y,_returnCode:v,onNotifyUpdate:i,setActiveBinding:a};if("undefined"!=typeof window&&(window.ApigoState=P),"undefined"!=typeof document){const e=()=>{f._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&&T(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),T(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}e.$=d,e.$$=c,e.Component=f,e.Hash=C,e.LocalStorage=L,e.NewState=l,e.SetTranslator=A,e.State=k,e.Util=$,e.____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios=x,e._scanTree=T,e._unbindTree=j,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={})}(this,function(e){"use strict";var t;let n=null,s=null;const a=e=>n=e,r=e=>s=e,o=new Set;function i(e={},t=null,a=null){const r={},i=new Map,l=new Map,d=(e,t)=>(l.has(e)||l.set(e,new Set),t?l.get(e).add(t):l.get(e).clear(),()=>l.get(e).delete(t)),c=(e,t)=>{l.has(e)&&l.set(e,new Set),l.get(e).delete(t)},h=t||(e=>r[e]),u=a||((e,t)=>r[e]=t);return Object.assign(r,e),new Proxy(r,{get:(e,t)=>"__watch"===t?d:"__unwatch"===t?c:"__isProxy"===t||(n&&(i.has(t)||i.set(t,new Set),i.get(t).add(n),n.node._states||(n.node._states=new Set),n.node._states.add(i)),h(t)),set(e,t,n){if(h(t)!==n&&u(t,n),l.has(t)&&l.get(t).forEach(s=>{const a=s(n);void 0!==a&&(n=a,e[t]=n)}),l.has(null)&&l.get(null).forEach(e=>e(n)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?s!==t.node&&o.forEach(e=>e(t)):e.delete(t)}return!0}})}const l=(e,t)=>t?e.querySelector(t):document.querySelector(e),d=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),c=new Map,h=[],u={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...s)=>{console.log("Component.register:",e.toUpperCase()),c.set(e.toUpperCase(),t),"loading"!==document.readyState?u._addTemplate(e,n,s):h.push([e,n,s])},exists:e=>c.has(e.toUpperCase()),getSetupFunction:e=>c.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:()=>{h.forEach(([e,t,n])=>u._addTemplate(e,t,n)),h.length=0}};function f(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),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&u.exists(e.tagName)&&b(e.tagName,t,n,s)}function b(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=u.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=i(t.state||{});const o=u.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&f(a,t,n,s),d(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",f(r[t],e,n,s))})}}a&&a(t)}let p=!1;function m(e){p=e}const g=new Map;function _(e,t,n,s){const a={...s||{},...t||{}},r=Object.keys(a),o=Object.values(a),i=e+r.join(",");try{let t=g.get(i);return t||(t=new Function("Hash","LocalStorage","State",...r,e),g.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 y(e,t,n,s){return e.includes("${")?_("return `"+e+"`",t,n,s):_("return "+e,t,n,s)}let A=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const v=e=>A=e,E=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 A(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 N;function O(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&O(e)}))}function S(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;a(e);let n=e.exp?e.tpl?y(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof n)try{n=y(n,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(a(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]):(O(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 d=o(n),c=a?d&&"object"==typeof d?d[a]:d:n,h=null==c||i.has(c)?`st_key_${r}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(t=>{t._ref[s]=n,t._ref[e]=d,w(t)})):(u=[],t._children.forEach(a=>{const r=a.cloneNode(!0);r._ref={...t._ref,[s]:n,[e]:d},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=>{O(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=l}else O(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||(_(`${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??""))}}N=e=>S(e),o.add(N);const T=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),S(e)},w=(e,t={})=>{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=E(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 w(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),((e,t)=>{if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>S({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));u.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=E(n.value);r.includes("this.")&&(r=r.replace(/\bthis\./g,"this.parent."));const o=y(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.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 o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))T({node:e,prop:a.split("."),tpl:o,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=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===a?e.addEventListener(["textarea","text","password"].includes(e.type||"text")||e.isContentEditable?"input":"change",n=>{let s=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;r(e),m(!0),"checkbox"===e.type&&e._checkboxMultiMode?_(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:s,thisNode:e},t.thisObj||e,e._ref||{}):_(`${o} = val`,{val:s,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),r(null)}):"text"!==a||o||(o=e.textContent,e.textContent=""),o&&(o=E(o),T({node:e,attr:a,tpl:o,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=>w(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},j=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=>j(e)))},x=w,$={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=>$.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>$.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}}};globalThis.Util=$;let M=new URLSearchParams((null==(t=window.location.hash)?void 0:t.substring(1))||"");const L=i({},e=>$.safeJson(M.get(e)),(e,t)=>{const n=M.get(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?M.delete(e):M.set(e,s),window.location.hash="#"+M.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),n=new Set([...M.keys(),...t.keys()]);M=t,n.forEach(e=>L[e]=L[e])});const C=i({},e=>$.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))}),k=i({exitBlocks:0});globalThis.Hash=L,globalThis.LocalStorage=C,globalThis.State=k;const P={NewState:i,Component:u,$:l,$$:d,RefreshState:x,SetTranslator:v,_scanTree:w,_unbindTree:j,Util:$,Hash:L,LocalStorage:C,State:k};if("undefined"!=typeof window&&(window.ApigoState=P),"undefined"!=typeof document){const e=()=>{u._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&&w(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),w(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}e.$=l,e.$$=d,e.Component=u,e.Hash=L,e.LocalStorage=C,e.NewState=i,e.RefreshState=x,e.SetTranslator=v,e.State=k,e.Util=$,e._scanTree=w,e._unbindTree=j,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}); diff --git a/dist/state.min.mjs b/dist/state.min.mjs index 1aaef44..5899f9a 100644 --- a/dist/state.min.mjs +++ b/dist/state.min.mjs @@ -1 +1 @@ -var e;let t=null,n=null;const s=e=>t=e,a=e=>n=e,r=new Set,o=e=>r.add(e);function i(e={},s=null,a=null){const o={},i=new Map,l=new Map,d=(e,t)=>(l.has(e)||l.set(e,new Set),t?l.get(e).add(t):l.get(e).clear(),()=>l.get(e).delete(t)),c=(e,t)=>{l.has(e)&&l.set(e,new Set),l.get(e).delete(t)},h=s||(e=>o[e]),u=a||((e,t)=>o[e]=t);return Object.assign(o,e),new Proxy(o,{get:(e,n)=>"__watch"===n?d:"__unwatch"===n?c:"__isProxy"===n||(t&&(i.has(n)||i.set(n,new Set),i.get(n).add(t),t.node._states||(t.node._states=new Set),t.node._states.add(i)),h(n)),set(e,t,s){if(h(t)!==s&&u(t,s),l.has(t)&&l.get(t).forEach(n=>{const a=n(s);void 0!==a&&(s=a,e[t]=s)}),l.has(null)&&l.get(null).forEach(e=>e(s)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?n!==t.node&&r.forEach(e=>e(t)):e.delete(t)}return!0}})}const l=(e,t)=>t?e.querySelector(t):document.querySelector(e),d=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),c=new Map,h=[],u={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...s)=>{console.log("Component.register:",e.toUpperCase()),c.set(e.toUpperCase(),t),"loading"!==document.readyState?u._addTemplate(e,n,s):h.push([e,n,s])},exists:e=>c.has(e.toUpperCase()),getSetupFunction:e=>c.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:()=>{h.forEach(([e,t,n])=>u._addTemplate(e,t,n)),h.length=0}};function f(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),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&u.exists(e.tagName)&&b(e.tagName,t,n,s)}function b(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=u.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=i(t.state||{});const o=u.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&f(a,t,n,s),d(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",f(r[t],e,n,s))})}}a&&a(t)}let p=!1;function m(e){p=e}const g=new Map;function _(e,t,n,s){const a={...s||{},...t||{}},r=Object.keys(a),o=Object.values(a),i=e+r.join(",");try{let t=g.get(i);return t||(t=new Function("Hash","LocalStorage","State",...r,e),g.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 y(e,t,n,s){return e.includes("${")?_("return `"+e+"`",t,n,s):_("return "+e,t,n,s)}let v=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const E=e=>v=e,A=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 v(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)}}function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function O(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;s(e);let n=e.exp?e.tpl?y(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof n)try{n=y(n,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(s(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]):(N(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 d=o(n),c=a?d&&"object"==typeof d?d[a]:d:n,h=null==c||i.has(c)?`st_key_${r}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(t=>{t._ref[s]=n,t._ref[e]=d,w(t)})):(u=[],t._children.forEach(a=>{const r=a.cloneNode(!0);r._ref={...t._ref,[s]:n,[e]:d},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=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=l}else N(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||(_(`${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??""))}}o(e=>O(e));const S=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),O(e)},w=(e,t={})=>{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=A(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0),"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),((e,t)=>{if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>O({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));u.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=A(n.value);r.includes("this.")&&(r=r.replace(/\bthis\./g,"this.parent."));const o=y(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.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 r=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 o=n.value;if(e.removeAttribute(n.name),r.startsWith("."))S({node:e,prop:r.split("."),tpl:o,exp:s});else if(r.startsWith("on")){const n=r.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=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===r?e.addEventListener(["textarea","text","password"].includes(e.type||"text")||e.isContentEditable?"input":"change",n=>{let s=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;a(e),m(!0),"checkbox"===e.type&&e._checkboxMultiMode?_(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:s,thisNode:e},t.thisObj||e,e._ref||{}):_(`${o} = val`,{val:s,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),a(null)}):"text"!==r||o||(o=e.textContent,e.textContent=""),o&&(o=A(o),S({node:e,attr:r,tpl:o,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=>w(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},T=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=>T(e)))},j=w,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: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}}};globalThis.Util=x;let $=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=i({},e=>x.safeJson($.get(e)),(e,t)=>{const n=$.get(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?$.delete(e):$.set(e,s),window.location.hash="#"+$.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),n=new Set([...$.keys(),...t.keys()]);$=t,n.forEach(e=>M[e]=M[e])});const C=i({},e=>x.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))}),L=i({exitBlocks:0});globalThis.Hash=M,globalThis.LocalStorage=C,globalThis.State=L;const k={NewState:i,Component:u,$:l,$$:d,____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios:j,SetTranslator:E,_scanTree:w,_unbindTree:T,Util:x,Hash:M,LocalStorage:C,State:L,_runCode:_,_returnCode:y,onNotifyUpdate:o,setActiveBinding:s};if("undefined"!=typeof window&&(window.ApigoState=k),"undefined"!=typeof document){const e=()=>{u._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&&w(e)}),e.removedNodes.forEach(e=>T(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),w(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{l as $,d as $$,u as Component,M as Hash,C as LocalStorage,i as NewState,E as SetTranslator,L as State,x as Util,j as ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,w as _scanTree,T as _unbindTree}; +var e;let t=null,n=null;const s=e=>t=e,a=e=>n=e,r=new Set;function o(e={},s=null,a=null){const o={},i=new Map,l=new Map,d=(e,t)=>(l.has(e)||l.set(e,new Set),t?l.get(e).add(t):l.get(e).clear(),()=>l.get(e).delete(t)),c=(e,t)=>{l.has(e)&&l.set(e,new Set),l.get(e).delete(t)},h=s||(e=>o[e]),u=a||((e,t)=>o[e]=t);return Object.assign(o,e),new Proxy(o,{get:(e,n)=>"__watch"===n?d:"__unwatch"===n?c:"__isProxy"===n||(t&&(i.has(n)||i.set(n,new Set),i.get(n).add(t),t.node._states||(t.node._states=new Set),t.node._states.add(i)),h(n)),set(e,t,s){if(h(t)!==s&&u(t,s),l.has(t)&&l.get(t).forEach(n=>{const a=n(s);void 0!==a&&(s=a,e[t]=s)}),l.has(null)&&l.get(null).forEach(e=>e(s)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?n!==t.node&&r.forEach(e=>e(t)):e.delete(t)}return!0}})}const i=(e,t)=>t?e.querySelector(t):document.querySelector(e),l=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),d=new Map,c=[],h={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...s)=>{console.log("Component.register:",e.toUpperCase()),d.set(e.toUpperCase(),t),"loading"!==document.readyState?h._addTemplate(e,n,s):c.push([e,n,s])},exists:e=>d.has(e.toUpperCase()),getSetupFunction:e=>d.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:()=>{c.forEach(([e,t,n])=>h._addTemplate(e,t,n)),c.length=0}};function u(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),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&h.exists(e.tagName)&&f(e.tagName,t,n,s)}function f(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=h.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=o(t.state||{});const i=h.getTemplate(e);if(i){const e=i.content.cloneNode(!0);if(e.childNodes.length){const a=e.children[0];a&&u(a,t,n,s),l(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");r[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",u(r[t],e,n,s))})}}a&&a(t)}let b=!1;function m(e){b=e}const p=new Map;function g(e,t,n,s){const a={...s||{},...t||{}},r=Object.keys(a),o=Object.values(a),i=e+r.join(",");try{let t=p.get(i);return t||(t=new Function("Hash","LocalStorage","State",...r,e),p.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(a){return b||console.error(a,s,[e,s,t,n]),null}}function _(e,t,n,s){return e.includes("${")?g("return `"+e+"`",t,n,s):g("return "+e,t,n,s)}let y=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const A=e=>y=e,v=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 y(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 E;function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function O(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;s(e);let n=e.exp?e.tpl?_(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof n)try{n=_(n,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(s(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]):(N(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 d=o(n),c=a?d&&"object"==typeof d?d[a]:d:n,h=null==c||i.has(c)?`st_key_${r}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(t=>{t._ref[s]=n,t._ref[e]=d,S(t)})):(u=[],t._children.forEach(a=>{const r=a.cloneNode(!0);r._ref={...t._ref,[s]:n,[e]:d},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=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=l}else N(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||(g(`${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??""))}}E=e=>O(e),r.add(E);const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),O(e)},S=(e,t={})=>{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=v(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 S(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),((e,t)=>{if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>O({node:e,...t})),void(e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})));h.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=v(n.value);r.includes("this.")&&(r=r.replace(/\bthis\./g,"this.parent."));const o=_(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.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 r=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 o=n.value;if(e.removeAttribute(n.name),r.startsWith("."))w({node:e,prop:r.split("."),tpl:o,exp:s});else if(r.startsWith("on")){const n=r.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=>g(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===r?e.addEventListener(["textarea","text","password"].includes(e.type||"text")||e.isContentEditable?"input":"change",n=>{let s=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;a(e),m(!0),"checkbox"===e.type&&e._checkboxMultiMode?g(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:s,thisNode:e},t.thisObj||e,e._ref||{}):g(`${o} = val`,{val:s,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),a(null)}):"text"!==r||o||(o=e.textContent,e.textContent=""),o&&(o=v(o),w({node:e,attr:r,tpl:o,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=>S(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},T=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=>T(e)))},j=S,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: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}}};globalThis.Util=x;let $=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=o({},e=>x.safeJson($.get(e)),(e,t)=>{const n=$.get(e),s=void 0===t?void 0:JSON.stringify(t);n===s||null===n&&void 0===s||(void 0===t?$.delete(e):$.set(e,s),window.location.hash="#"+$.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),n=new Set([...$.keys(),...t.keys()]);$=t,n.forEach(e=>M[e]=M[e])});const L=o({},e=>x.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))}),C=o({exitBlocks:0});globalThis.Hash=M,globalThis.LocalStorage=L,globalThis.State=C;const k={NewState:o,Component:h,$:i,$$:l,RefreshState:j,SetTranslator:A,_scanTree:S,_unbindTree:T,Util:x,Hash:M,LocalStorage:L,State:C};if("undefined"!=typeof window&&(window.ApigoState=k),"undefined"!=typeof document){const e=()=>{h._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&&S(e)}),e.removedNodes.forEach(e=>T(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),S(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,l as $$,h as Component,M as Hash,L as LocalStorage,o as NewState,j as RefreshState,A as SetTranslator,C as State,x as Util,S as _scanTree,T as _unbindTree}; diff --git a/dist/state.mjs b/dist/state.mjs index db8f7f0..1b0d500 100644 --- a/dist/state.mjs +++ b/dist/state.mjs @@ -1,10 +1,10 @@ var _a; -let _activeBinding = null; -let _noWriteBack = null; -const setActiveBinding = (val) => _activeBinding = val; -const setNoWriteBack = (val) => _noWriteBack = val; +let __activeBinding = null; +let __noWriteBack = null; +const _setActiveBinding = (val) => __activeBinding = val; +const _setNoWriteBack = (val) => __noWriteBack = val; const _notifiers = /* @__PURE__ */ new Set(); -const onNotifyUpdate = (fn) => _notifiers.add(fn); +const _onNotifyUpdate = (fn) => _notifiers.add(fn); function NewState(defaults = {}, getter = null, setter = null) { const _defaults = {}; const _stateMappings = /* @__PURE__ */ new Map(); @@ -26,11 +26,11 @@ function NewState(defaults = {}, getter = null, setter = null) { if (key === "__watch") return _watchFunc; if (key === "__unwatch") return _unwatchFunc; if (key === "__isProxy") return true; - if (_activeBinding) { + if (__activeBinding) { if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set()); - _stateMappings.get(key).add(_activeBinding); - if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set(); - _activeBinding.node._states.add(_stateMappings); + _stateMappings.get(key).add(__activeBinding); + if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set(); + __activeBinding.node._states.add(_stateMappings); } return __getter(key); }, @@ -57,7 +57,7 @@ function NewState(defaults = {}, getter = null, setter = null) { bindings.delete(binding); continue; } - if (_noWriteBack !== binding.node) { + if (__noWriteBack !== binding.node) { _notifiers.forEach((fn) => fn(binding)); } } @@ -202,7 +202,7 @@ if (typeof document !== "undefined") { }; } } -onNotifyUpdate((binding) => _updateBinding(binding)); +_onNotifyUpdate((binding) => _updateBinding(binding)); function _clearRenderedNodes(node) { if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => { child.remove(); @@ -212,7 +212,7 @@ function _clearRenderedNodes(node) { function _updateBinding(binding) { const node = binding.node; if (!node.isConnected && node.tagName !== "TEMPLATE") return; - setActiveBinding(binding); + _setActiveBinding(binding); let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl; if (binding.exp === 2 && typeof result === "string") { try { @@ -220,7 +220,7 @@ function _updateBinding(binding) { } catch (e) { } } - setActiveBinding(null); + _setActiveBinding(null); if (binding.prop) { const prop = binding.prop; let o = node; @@ -231,10 +231,9 @@ function _updateBinding(binding) { if (typeof o !== "object") break; } if (typeof o === "object" && o !== null) { - const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result); const lk = prop[prop.length - 1]; if (lk) { - if (resultIsObject && o[lk] == null) o[lk] = {}; + if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; const lo = o[lk]; if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result); else { @@ -415,12 +414,12 @@ const _parseNode = (node, scanObj) => { if (realAttrName === "bind") { node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => { let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail; - setNoWriteBack(node); + _setNoWriteBack(node); setDisableRunCodeError(true); if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); setDisableRunCodeError(false); - setNoWriteBack(null); + _setNoWriteBack(null); }); } else if (realAttrName === "text" && !tpl) { tpl = node.textContent; @@ -447,6 +446,19 @@ 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"))) { + 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)); + attrs.forEach((attr) => { + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + }); + node.parentNode.insertBefore(template, node); + template.content.appendChild(node); + template._ref = node._ref; + _scanTree(template, scanObj); + return; + } if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) { const template = document.createElement("TEMPLATE"); const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name)); @@ -504,7 +516,7 @@ const _unbindTree = (node) => { }); node.childNodes && node.childNodes.forEach((child) => _unbindTree(child)); }; -const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree; +const _unsafeRefreshState = _scanTree; const Util = { clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))), base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))), @@ -599,18 +611,14 @@ const ApigoState = { Component, $, $$, - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, + RefreshState: _unsafeRefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, - State, - _runCode, - _returnCode, - onNotifyUpdate, - setActiveBinding + State }; if (typeof window !== "undefined") { window.ApigoState = ApigoState; @@ -642,10 +650,10 @@ export { Hash, LocalStorage, NewState, + _unsafeRefreshState as RefreshState, SetTranslator, State, Util, - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, _scanTree, _unbindTree }; diff --git a/old/state_manybeok.js b/old/state_manybeok.js index 2515c2c..3d370fc 100644 --- a/old/state_manybeok.js +++ b/old/state_manybeok.js @@ -475,7 +475,7 @@ } node.childNodes && node.childNodes.forEach(child => _unbindTree(child)) } - globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree + globalThis._unsafeRefreshState = _scanTree const _components = new Map() const _pendingTemplates = [] diff --git a/old/state_original.js b/old/state_original.js index 439e927..731b6e7 100644 --- a/old/state_original.js +++ b/old/state_original.js @@ -458,7 +458,7 @@ } node.childNodes && node.childNodes.forEach(child => _unbindTree(child)) } - globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree + globalThis._unsafeRefreshState = _scanTree const _components = new Map() const _pendingTemplates = [] diff --git a/package.json b/package.json index d4be579..86d0ee0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apigo.cc/state", - "version": "1.0.16", + "version": "1.0.17", "type": "module", "main": "dist/state.js", "module": "dist/state.js", diff --git a/src/dom.badbak.for2exp.js b/src/dom.badbak.for2exp.js index b4599d4..935ce69 100644 --- a/src/dom.badbak.for2exp.js +++ b/src/dom.badbak.for2exp.js @@ -363,4 +363,4 @@ export const _unbindTree = (node) => { node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); }; -export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree; +export const ___unsafeRefreshState = _scanTree; diff --git a/src/dom.js b/src/dom.js index d9d6339..ecc10e8 100644 --- a/src/dom.js +++ b/src/dom.js @@ -1,6 +1,6 @@ // src/dom.js import { _runCode, _returnCode, setDisableRunCodeError } from './core.js'; -import { getActiveBinding, setActiveBinding, getNoWriteBack, setNoWriteBack, NewState, onNotifyUpdate } from './observer.js'; +import { _getActiveBinding, _setActiveBinding, _getNoWriteBack, _setNoWriteBack, NewState, _onNotifyUpdate } from './observer.js'; import { Component, _makeComponent, _mergeNode } from './component.js'; import { $, $$ } from './dom-utils.js'; @@ -35,7 +35,7 @@ if (typeof document !== 'undefined') { export { $, $$ }; -onNotifyUpdate((binding) => _updateBinding(binding)); +_onNotifyUpdate((binding) => _updateBinding(binding)); export function _clearRenderedNodes(node) { if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => { @@ -48,14 +48,13 @@ export function _updateBinding(binding) { const node = binding.node; if (!node.isConnected && node.tagName !== 'TEMPLATE') return; - setActiveBinding(binding); + _setActiveBinding(binding); let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl; - // 增量 3: 支持 $$ 前缀的双重评估 if (binding.exp === 2 && typeof result === 'string') { try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { } } - setActiveBinding(null); + _setActiveBinding(null); if (binding.prop) { const prop = binding.prop; @@ -67,10 +66,9 @@ export function _updateBinding(binding) { if (typeof o !== 'object') break; } if (typeof o === 'object' && o !== null) { - const resultIsObject = typeof result === 'object' && result != null && !Array.isArray(result); const lk = prop[prop.length - 1]; if (lk) { - if (resultIsObject && o[lk] == null) o[lk] = {}; + if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; const lo = o[lk]; if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result); else { if (o[lk] !== result) o[lk] = result; } @@ -98,7 +96,7 @@ export function _updateBinding(binding) { if (result && typeof result === 'object') { const asName = node.getAttribute('as') || 'item'; const indexName = node.getAttribute('index') || 'index'; - const keyName = node.getAttribute('key'); // 增量 2: 支持 key + const keyName = node.getAttribute('key'); let keys, getVal; if (result instanceof Map) { keys = Array.from(result.keys()); getVal = k => result.get(k); @@ -249,10 +247,10 @@ export const _parseNode = (node, scanObj) => { if (realAttrName === 'bind') { node.addEventListener(['textarea', 'text', 'password'].includes(node.type || 'text') || node.isContentEditable ? 'input' : 'change', (e) => { let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail); - setNoWriteBack(node); setDisableRunCodeError(true); + _setNoWriteBack(node); setDisableRunCodeError(true); if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); - setDisableRunCodeError(false); setNoWriteBack(null); + setDisableRunCodeError(false); _setNoWriteBack(null); }); } else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; } if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); } @@ -277,6 +275,22 @@ export const _scanTree = (node, scanObj = {}) => { node._stTranslated = true; } + // 还原原始逻辑:自动为非模板节点的 $if 和 $each 指令创建模版节点 + 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))); + attrs.forEach(attr => { + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + }); + node.parentNode.insertBefore(template, node); + template.content.appendChild(node); + template._ref = node._ref; + // 修正原始版本可能存在的漏扫:转换后立即对新生成的 TEMPLATE 发起递归扫描 + _scanTree(template, scanObj); + return; + } + if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || 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)); @@ -317,7 +331,6 @@ export const _scanTree = (node, scanObj = {}) => { while (curr && curr._ref === undefined) curr = curr.parentNode; node._ref = curr ? { ...curr._ref } : {}; } - // 增量 4: 支持 _refExt 上下文注入 if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars); _parseNode(node, { ...scanObj }); @@ -337,4 +350,4 @@ export const _unbindTree = (node) => { node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); }; -export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree; +export const _unsafeRefreshState = _scanTree; diff --git a/src/dom.js.keyed.bak b/src/dom.js.keyed.bak index de4978c..04e5de2 100644 --- a/src/dom.js.keyed.bak +++ b/src/dom.js.keyed.bak @@ -349,4 +349,4 @@ export const _unbindTree = (node) => { node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); }; -export const __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree; +export const _unsafeRefreshState = _scanTree; diff --git a/src/index.js b/src/index.js index ff47af1..99f018c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,19 @@ // src/index.js export { NewState } from './observer.js'; export { Component } from './component.js'; -export { $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree } from './dom.js'; +export { $, $$, _unsafeRefreshState as RefreshState, SetTranslator, _scanTree, _unbindTree } from './dom.js'; export { Util } from './utils.js'; export { Hash, LocalStorage, State } from './globals.js'; import { Component } from './component.js'; -import { _scanTree, _unbindTree, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator } from './dom.js'; +import { _scanTree, _unbindTree, $, $$, _unsafeRefreshState, SetTranslator } from './dom.js'; import { LocalStorage, Hash, State } from './globals.js'; -import { NewState, onNotifyUpdate, setActiveBinding } from './observer.js'; +import { NewState, _onNotifyUpdate, _setActiveBinding } from './observer.js'; import { Util } from './utils.js'; import { _runCode, _returnCode } from './core.js'; const ApigoState = { - NewState, Component, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State, - _runCode, _returnCode, onNotifyUpdate, setActiveBinding + NewState, Component, $, $$, RefreshState: _unsafeRefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State }; if (typeof window !== 'undefined') { @@ -23,15 +22,12 @@ if (typeof window !== 'undefined') { if (typeof document !== 'undefined') { const init = () => { - // 关键修复:在启动观察器和首次扫描前,务必将所有等待中的组件模板注入 DOM! Component._initPending(); - const htmlNode = document.documentElement; if (!htmlNode.hasAttribute('$data-bs-theme') && !htmlNode.hasAttribute('data-bs-theme')) { htmlNode.setAttribute('$data-bs-theme', "LocalStorage.darkMode?'dark':'light'"); } - // 灵魂逻辑:MutationObserver 捕获所有异步 DOM 变化 new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(newNode => { diff --git a/src/observer.js b/src/observer.js index 0058c04..477fb74 100644 --- a/src/observer.js +++ b/src/observer.js @@ -1,14 +1,14 @@ // src/observer.js -let _activeBinding = null; -let _noWriteBack = null; +let __activeBinding = null; +let __noWriteBack = null; -export const getActiveBinding = () => _activeBinding; -export const setActiveBinding = (val) => _activeBinding = val; -export const getNoWriteBack = () => _noWriteBack; -export const setNoWriteBack = (val) => _noWriteBack = val; +export const _getActiveBinding = () => __activeBinding; +export const _setActiveBinding = (val) => __activeBinding = val; +export const _getNoWriteBack = () => __noWriteBack; +export const _setNoWriteBack = (val) => __noWriteBack = val; const _notifiers = new Set(); -export const onNotifyUpdate = (fn) => _notifiers.add(fn); +export const _onNotifyUpdate = (fn) => _notifiers.add(fn); export function NewState(defaults = {}, getter = null, setter = null) { const _defaults = {}; @@ -36,11 +36,11 @@ export function NewState(defaults = {}, getter = null, setter = null) { if (key === '__unwatch') return _unwatchFunc; if (key === '__isProxy') return true; - if (_activeBinding) { + if (__activeBinding) { if (!_stateMappings.has(key)) _stateMappings.set(key, new Set()); - _stateMappings.get(key).add(_activeBinding); - if (!_activeBinding.node._states) _activeBinding.node._states = new Set(); - _activeBinding.node._states.add(_stateMappings); + _stateMappings.get(key).add(__activeBinding); + if (!__activeBinding.node._states) __activeBinding.node._states = new Set(); + __activeBinding.node._states.add(_stateMappings); } return __getter(key); }, @@ -67,7 +67,7 @@ export function NewState(defaults = {}, getter = null, setter = null) { bindings.delete(binding); continue; } - if (_noWriteBack !== binding.node) { + if (__noWriteBack !== binding.node) { _notifiers.forEach(fn => fn(binding)); } } 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 index 7673bfa..c8cb2ae 100644 --- a/test-results/all-modular-unit-tests-and-benchmark/error-context.md +++ b/test-results/all-modular-unit-tests-and-benchmark/error-context.md @@ -56,8 +56,8 @@ Received: "failed" 32 | const items = []; 33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i}); 34 | window.state.benchItems = items; - 35 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state'); - 36 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + 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`); @@ -75,10 +75,10 @@ Received: "failed" 51 | 52 | // Extreme Data Test 53 | await page.evaluate(async () => { - 54 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state'); + 54 | const { _unsafeRefreshState } = await import('@apigo.cc/state'); 55 | document.body.innerHTML = '
'; 56 | window.state.extreme = null; - 57 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.getElementById('extreme')); + 57 | _unsafeRefreshState(document.getElementById('extreme')); 58 | window.state.extreme = undefined; 59 | window.state.extreme = { a: 1 }; 60 | window.state.extreme = [1, 2]; diff --git a/test/all.spec.js b/test/all.spec.js index 242b656..cccf07c 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 { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state'); - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + const { ___unsafeRefreshState } = await import('@apigo.cc/state'); + ___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 { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state'); + const { ___unsafeRefreshState } = await import('@apigo.cc/state'); document.body.innerHTML = '
'; window.state.extreme = null; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 b1951fb..f0d6c36 100644 --- a/test/component.test.js +++ b/test/component.test.js @@ -1,6 +1,6 @@ // test/component.test.js window.testComponent = async function() { - const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState; + const { Component, ___unsafeRefreshState, $ } = ApigoState; console.log('Testing component.js...'); // 1. Register component @@ -10,7 +10,7 @@ window.testComponent = async function() { // 2. Render component document.body.innerHTML = ''; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___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 fc774f7..0835145 100644 --- a/test/dom.test.js +++ b/test/dom.test.js @@ -1,6 +1,6 @@ // test/dom.test.js window.testDom = async function() { - const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, $$, 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 }; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___unsafeRefreshState(document.documentElement); if ($('#test-if')) throw new Error('$if fail: should be hidden'); state.show = true; @@ -30,7 +30,7 @@ window.testDom = async function() { // 3. $each directive document.body.innerHTML = ''; state.items = ['A', 'B']; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___unsafeRefreshState(document.documentElement); 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 +41,14 @@ window.testDom = async function() { // 4. Event binding $onclick document.body.innerHTML = ''; state.count = 0; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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'; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 +74,13 @@ window.testDom = async function() { const root = $('#double-eval-root'); root._thisObj = { state: doubleState }; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root); + ___unsafeRefreshState(root); await wait(); const inner = $('#inner-node'); if (!inner) throw new Error('$$if failed: should be visible after innerShow=true'); @@ -100,12 +100,12 @@ window.testDom = async function() { nestedRoot._thisObj = { state: doubleState }; doubleState.outer = true; doubleState.innerShow = false; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot); + ___unsafeRefreshState(nestedRoot); await wait(); if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially'); doubleState.innerShow = true; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 f2694aa..6ece980 100644 --- a/test/foundation.test.js +++ b/test/foundation.test.js @@ -1,6 +1,6 @@ // test/foundation.test.js window.testFoundation = async function() { - const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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); - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 67fdcc6..d4e540b 100644 --- a/test/inheritance.test.js +++ b/test/inheritance.test.js @@ -1,6 +1,6 @@ // test/inheritance.test.js window.testInheritance = async function() { - const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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); - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(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 bca0ff2..230cb04 100644 --- a/test/merging.test.js +++ b/test/merging.test.js @@ -1,6 +1,6 @@ // test/merging.test.js window.testMerging = async function() { - const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState; + const { Component, ___unsafeRefreshState, $ } = ApigoState; console.log('Testing complex class/style merging (DataTable Resizer scenario)...'); Component.register('Resizer-Real', container => { @@ -8,7 +8,7 @@ window.testMerging = async function() { }, document.createRange().createContextualFragment('
').firstChild); document.body.innerHTML = ''; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 50)); const inst = $('#res-inst'); diff --git a/test/priority.test.js b/test/priority.test.js index c117c20..dd055a4 100644 --- a/test/priority.test.js +++ b/test/priority.test.js @@ -1,13 +1,13 @@ // test/priority.test.js window.testPriority = async function() { - const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, NewState } = ApigoState; + const { ___unsafeRefreshState, $, NewState } = ApigoState; console.log('Testing directive priorities...'); // Test $if vs $text (if should hide text) document.body.innerHTML = '
'; const state = NewState({ hide: false, msg: 'visible' }); document.documentElement._thisObj = { state }; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___unsafeRefreshState(document.documentElement); if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed'); diff --git a/test/timing.test.js b/test/timing.test.js index 0c8f3ed..bd87577 100644 --- a/test/timing.test.js +++ b/test/timing.test.js @@ -1,6 +1,6 @@ // test/timing.test.js window.testTiming = async function() { - const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, 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 += ``; - ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement); + ___unsafeRefreshState(document.documentElement); await new Promise(r => setTimeout(r, 100));