diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f8026..09925c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # CHANGELOG +## v1.0.8 (2026-05-18) + +### 重大修复与回归 +- **架构还原**: 忠实还原了开发者亲自编写的核心逻辑,移除了所有过度设计的复杂处理。 +- **时序优化**: 微调了组件属性解析时机,确保 `$.prop` 赋值发生在组件 `setupFunc` 运行前,且正确指向父级作用域。 +- **合并修复**: 解决了组件模板内部自带的 `$class` 和 `$style` 指令不执行的问题,确保多源属性和谐共存。 +- **文档规范**: 在 README 中增加了针对 AI 代理的“核心逻辑修改禁令”,确保未来维护的稳定性。 + +## v1.0.7 (2026-05-18) + +### 修复 +- **深度属性合并**: 解决了当组件实例上同时存在静态 `class` 和动态 `$class` 时,动态绑定可能覆盖实例或模板原有静态类的问题。 +- **基准属性系统**: 引入 `_baseClass` 与 `_baseStyle` 累加机制。所有来源的静态属性(实例处、组件模板根节点)都会被捕获为“基准值”,动态绑定将在基准值之上进行增量更新。 + +## v1.0.6 (2026-05-18) + +### 修复 +- **初始化时序**: 修正了组件实例指令在 `setupFunc` 执行前完成初次评估的问题。现在在组件初始化(含 `setupFunc`)完成后会立即触发指令重绘,确保 `$class` 等指令能正确响应在 `setupFunc` 中设置的属性。 +- **合并鲁棒性**: 优化了 `class` 的处理流,确保实例静态类、模板静态类以及实例动态类能够按照正确的优先级合并,互不干扰。 + +## v1.0.5 (2026-05-18) + +### 修复 +- **属性合并**: 改进 `src/component.js` 中的 `_mergeNode` 逻辑,支持组件模板与实例之间的 `class` 和 `style` 属性合并,而非简单覆盖。 +- **动态绑定增强**: 优化 `src/dom.js` 中的 `$class` 和 `$style` 绑定逻辑。现在动态类支持自动切换(切换时移除旧类),动态样式支持与静态样式(Base Style)智能合并。 + +## v1.0.4 (2026-05-18) + +### 修复 +- **表达式评估**: 改进 `src/core.js` 中的 `_returnCode` 逻辑,支持多语句表达式(如 `$.target="console.log(1); thisNode.parentNode"`)。现在会自动将最后一条语句作为返回值,避免因 `return` 关键字前置导致多语句执行异常。 + +## v1.0.3 (2026-05-18) + +### 修复 +- **组件初始化优先级**: 优化 `src/dom.js` 中的 `_parseNode` 逻辑,确保组件容器上的指令(如 `$.target`)在 `setupFunc` 执行前完成评估与赋值。 +- **作用域解析**: 将 `this.` 到 `this.parent.` 的自动替换逻辑前移至 `_parseNode`,解决了组件属性绑定在初始化时的作用域歧义问题。 + +### 测试 +- 新增 `test/priority.test.js`,验证组件属性优先级及跨作用域属性解析的正确性。 + ## v1.0.2 (2026-05-17) ### 优化 diff --git a/README.md b/README.md index 3e22635..b51cba2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # State.js AI 开发指南 +> ⚠️ **AI 强制指令 (AI Mandate):** +> 1. **尊重原创逻辑**:本项目(State.js)核心架构极其精炼且经过精雕细琢。**严禁擅自修改原有逻辑**(如 `_returnCode`, `_runCode`, `NewState` 等底层函数),除非你能证明修改后的质量、性能与简洁度均显著优于原版。 +> 2. **禁止过度设计**:不要尝试引入复杂的编译器、多语句解析器或防御性代码。任何试图将“简单问题复杂化”的重构大概率会破坏现有结构。 +> 3. **汇报与许可**:若你认为有必要修改库的核心逻辑,**必须首先向人类开发者汇报修改的必要性与潜在影响**,只有在得到明确允许后方可进行。 +> 4. **回归测试**:任何改动必须通过现有 Playwright 自动化测试,确保 `DataTable` 等复杂组件场景的渲染与合并逻辑完全正常。 + State.js 是基于 Vanilla JS 的无编译数据驱动 UI 框架。依赖原生 DOM 与 Proxy,通过解析 HTML `$指令` 属性映射状态。 > **核心设定:** 你是一个追求极致代码压缩和执行效率的资深极客。 diff --git a/dist/state.js b/dist/state.js index ba652f9..765092d 100644 --- a/dist/state.js +++ b/dist/state.js @@ -1,50 +1,41 @@ var _a; let _activeBinding = null; let _noWriteBack = null; -let _updateBindingFn = null; -let _updateDepth = 0; -const MAX_UPDATE_DEPTH = 100; -function setActiveBinding(val) { - _activeBinding = val; -} -function setNoWriteBack(val) { - _noWriteBack = val; -} -function onNotifyUpdate(fn) { - _updateBindingFn = fn; -} +const setActiveBinding = (val) => _activeBinding = val; +const setNoWriteBack = (val) => _noWriteBack = val; +const _notifiers = /* @__PURE__ */ new Set(); +const onNotifyUpdate = (fn) => _notifiers.add(fn); function NewState(defaults = {}, getter = null, setter = null) { - if (defaults && defaults.__watch) return defaults; const _defaults = {}; const _stateMappings = /* @__PURE__ */ new Map(); const _watchers = /* @__PURE__ */ new Map(); const _watchFunc = (k, cb) => { if (!_watchers.has(k)) _watchers.set(k, /* @__PURE__ */ new Set()); !cb ? _watchers.get(k).clear() : _watchers.get(k).add(cb); + return () => _watchers.get(k).delete(cb); }; const _unwatchFunc = (k, cb) => { - if (!_watchers.has(k)) _watchers.set(k, /* @__PURE__ */ new Set()); - _watchers.get(k).delete(cb); + if (_watchers.has(k)) _watchers.get(k).delete(cb); }; - const _getter = getter || ((k) => _defaults[k]); - const _setter = setter || ((k, v) => _defaults[k] = v); + const __getter = getter || ((k) => _defaults[k]); + const __setter = setter || ((k, v) => _defaults[k] = v); Object.assign(_defaults, defaults); return new Proxy(_defaults, { get(target, key) { if (key === "__watch") return _watchFunc; if (key === "__unwatch") return _unwatchFunc; + if (key === "__isProxy") return true; if (_activeBinding) { if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set()); - const bindingSet = _stateMappings.get(key); - bindingSet.add(_activeBinding); - if (!_activeBinding._sets) _activeBinding._sets = /* @__PURE__ */ new Set(); - _activeBinding._sets.add(bindingSet); + _stateMappings.get(key).add(_activeBinding); + if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set(); + _activeBinding.node._states.add(_stateMappings); } - return _getter(key); + return __getter(key); }, set(target, key, value) { - if (_getter(key) !== value) { - _setter(key, value); + if (__getter(key) !== value) { + __setter(key, value); } if (_watchers.has(key)) { _watchers.get(key).forEach((cb) => { @@ -59,21 +50,15 @@ function NewState(defaults = {}, getter = null, setter = null) { _watchers.get(null).forEach((cb) => cb(value)); } if (_stateMappings.has(key)) { - if (_updateDepth > MAX_UPDATE_DEPTH) return console.error("Recursive update detected at key:", key), true; - _updateDepth++; - try { - const bindings = _stateMappings.get(key); - for (const binding of bindings) { - if (!binding.node.isConnected) { - bindings.delete(binding); - continue; - } - if (_noWriteBack !== binding.node && _updateBindingFn) { - _updateBindingFn(binding); - } + const bindings = _stateMappings.get(key); + for (const binding of bindings) { + if (!binding.node.isConnected) { + bindings.delete(binding); + continue; + } + if (_noWriteBack !== binding.node) { + _notifiers.forEach((fn) => fn(binding)); } - } finally { - _updateDepth--; } } return true; @@ -82,6 +67,85 @@ function NewState(defaults = {}, getter = null, setter = null) { } const $ = (a, b) => b ? a.querySelector(b) : document.querySelector(a); const $$ = (a, b) => b ? a.querySelectorAll(b) : document.querySelectorAll(a); +const _components = /* @__PURE__ */ new Map(); +const _pendingTemplates = []; +const Component = { + getTemplate: (name) => document.querySelector(`template[component="${name.toUpperCase()}"]`), + register: (name, setupFunc, templateNode = null, ...globalNodes) => { + _components.set(name.toUpperCase(), setupFunc); + if (document.readyState !== "loading") Component._addTemplate(name, templateNode, globalNodes); + else _pendingTemplates.push([name, templateNode, globalNodes]); + }, + exists: (name) => _components.has(name.toUpperCase()), + getSetupFunction: (name) => _components.get(name.toUpperCase()), + _addTemplate: (name, templateNode, globalNodes) => { + if (templateNode) { + const template = document.createElement("TEMPLATE"); + template.setAttribute("component", name.toUpperCase()); + template.content.appendChild(templateNode); + document.body.appendChild(template); + } + if (globalNodes) globalNodes.forEach((node) => document.body.appendChild(node)); + }, + _initPending: () => { + _pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes)); + _pendingTemplates.length = 0; + } +}; +function _mergeNode(from, to, scanObj, exists = {}) { + if (from.attributes) { + Array.from(from.attributes).forEach((attr) => { + if (attr.name === "class") return; + if (attr.name === "style") { + if (to.hasAttribute("style")) to.setAttribute("style", `${attr.value}; ${to.getAttribute("style")}`); + else to.setAttribute("style", attr.value); + } else if (!to.hasAttribute(attr.name)) { + to.setAttribute(attr.name, attr.value); + } + }); + } + to.classList.add(...from.classList); + Array.from(from.childNodes).forEach((child) => to.appendChild(child)); + if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); +} +function _makeComponent(name, node, scanObj, exists = {}) { + if (exists[name]) return; + exists[name] = true; + if (scanObj.thisObj) { + Array.from(node.attributes).forEach((attr) => { + if ((attr.name.startsWith("$") || attr.name.startsWith("st-")) && attr.value.includes("this.")) { + attr.value = attr.value.replace(/\bthis\./g, "this.parent."); + } + }); + } + const componentFunc = Component.getSetupFunction(name); + const slots = {}; + Array.from(node.childNodes).forEach((child) => { + if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute("slot")) { + slots[child.getAttribute("slot")] = child; + child.removeAttribute("slot"); + } + }); + node.innerHTML = ""; + node.state = NewState(node.state || {}); + const template = Component.getTemplate(name); + if (template) { + const tplnode = template.content.cloneNode(true); + if (tplnode.childNodes.length) { + const rootNode = Array.from(tplnode.childNodes).find((n) => n.nodeType === Node.ELEMENT_NODE); + if (rootNode) _mergeNode(rootNode, node, scanObj, exists); + $$(node, "[slot-id]").forEach((placeholder) => { + const slotName = placeholder.getAttribute("slot-id"); + if (slots[slotName]) { + placeholder.removeAttribute("slot-id"); + placeholder.innerHTML = ""; + _mergeNode(slots[slotName], placeholder, scanObj, exists); + } + }); + } + } + if (componentFunc) componentFunc(node); +} let _disableRunCodeError = false; function setDisableRunCodeError(value) { _disableRunCodeError = value; @@ -288,24 +352,46 @@ function _updateBinding(binding) { } const _initBinding = (binding) => { if (!binding.node._bindings) binding.node._bindings = []; - binding.node._bindings.push(binding); + binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }); _updateBinding(binding); }; const _parseNode = (node, scanObj) => { - if (Component.exists(node.tagName) && !node._componentInitialized) { - node._componentInitialized = true; - _makeComponent(node.tagName, node, scanObj); - $$(node, "[slot-id]").forEach((placeholder) => placeholder.removeAttribute("slot-id")); - if (!node._thisObj) node._thisObj = node; - } if (node._bindings) { - node._bindings.forEach((binding) => _updateBinding(binding)); + node._states = /* @__PURE__ */ new Set(); + node._bindings.forEach((bindingData) => { + const binding = { node, ...bindingData }; + _updateBinding(binding); + }); if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false })); if (node.hasAttribute("onupdate")) { _runCode(node.getAttribute("onupdate"), { thisNode: node }, node._thisObj || node, node._ref || {}); } return; } + if (Component.exists(node.tagName) && !node._componentInitialized) { + Array.from(node.attributes).forEach((attr) => { + if (attr.name.startsWith("$.")) { + const realAttrName = attr.name.slice(2); + let tpl = attr.value; + if (tpl.includes("this.")) tpl = tpl.replace(/\bthis\./g, "this.parent."); + tpl = _translate(tpl); + const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {}); + let o = node; + const prop = realAttrName.split("."); + for (let i = 0; i < prop.length - 1; i++) { + if (!prop[i]) continue; + if (o[prop[i]] == null) o[prop[i]] = {}; + o = o[prop[i]]; + } + o[prop[prop.length - 1]] = result; + node.removeAttribute(attr.name); + } + }); + _makeComponent(node.tagName, node, scanObj); + $$(node, "[slot-id]").forEach((placeholder) => placeholder.removeAttribute("slot-id")); + node._componentInitialized = true; + if (!node._thisObj) node._thisObj = node; + } let attrs = []; if (node.tagName === "TEMPLATE") { node._children = [...node.content.childNodes]; @@ -320,6 +406,7 @@ const _parseNode = (node, scanObj) => { if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; if (!node._thisObj) node._thisObj = scanObj.thisObj || null; if (!node._ref) node._ref = scanObj.extendVars || {}; + node._states = /* @__PURE__ */ new Set(); attrs.forEach((attr) => { const exp = attr.name.startsWith("$") || attr.name.startsWith("st-"); const realAttrName = exp ? attr.name.slice(attr.name.startsWith("$") ? 1 : 3) : attr.name; @@ -457,97 +544,18 @@ const _scanTree = (node, scanObj = {}) => { const _unbindTree = (node) => { if (node.nodeType !== 1) return; if (node._hasOnUnload) node.dispatchEvent(new Event("unload", { bubbles: false })); - if (node._bindings) { - node._bindings.forEach((binding) => { - if (binding._sets) { - binding._sets.forEach((set) => set.delete(binding)); - binding._sets.clear(); + if (node._states) { + node._states.forEach((stateMappings) => { + for (const [key, bindingSet] of stateMappings) { + for (const binding of bindingSet) { + if (binding.node === node) bindingSet.delete(binding); + } } }); } node.childNodes && node.childNodes.forEach((child) => _unbindTree(child)); }; const RefreshState = _scanTree; -const _components = /* @__PURE__ */ new Map(); -const _pendingTemplates = []; -const Component = { - getTemplate: (name) => document.querySelector(`template[component="${name.toUpperCase()}"]`), - register: (name, setupFunc, templateNode = null, ...globalNodes) => { - _components.set(name.toUpperCase(), setupFunc); - if (document.readyState !== "loading") Component._addTemplate(name, templateNode, globalNodes); - else _pendingTemplates.push([name, templateNode, globalNodes]); - }, - exists: (name) => _components.has(name.toUpperCase()), - getSetupFunction: (name) => _components.get(name.toUpperCase()), - _addTemplate: (name, templateNode, globalNodes) => { - if (templateNode) { - const template = document.createElement("TEMPLATE"); - template.setAttribute("component", name.toUpperCase()); - template.content.appendChild(templateNode); - document.body.appendChild(template); - } - if (globalNodes) globalNodes.forEach((node) => document.body.appendChild(node)); - }, - _initPending: () => { - _pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes)); - _pendingTemplates.length = 0; - } -}; -function _mergeNode(from, to, scanObj, exists = {}) { - if (from.attributes) { - Array.from(from.attributes).forEach((attr) => attr.name !== "class" && to.setAttribute(attr.name, attr.value)); - } - if (from.classList) { - to.classList.add(...from.classList); - } - Array.from(from.childNodes).forEach((child) => to.appendChild(child)); - if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); -} -function _makeComponent(name, node, scanObj, exists = {}) { - if (exists[name]) return; - exists[name] = true; - if (scanObj.thisObj) Array.from(node.attributes).forEach((attr) => { - if ((attr.name.startsWith("$") || attr.name.startsWith("st-")) && attr.value.includes("this.")) attr.value = attr.value.replace(/\bthis\./g, "this.parent."); - }); - const componentFunc = Component.getSetupFunction(name); - const slots = {}; - Array.from(node.childNodes).forEach((child) => { - if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute("slot")) { - slots[child.getAttribute("slot")] = child; - child.removeAttribute("slot"); - } - }); - node.innerHTML = ""; - node.state = NewState(node.state || {}); - const template = Component.getTemplate(name); - if (template) { - const tplnode = template.content.cloneNode(true); - if (tplnode.childNodes.length) { - const rootNode = tplnode.children[0]; - _mergeNode(rootNode, node, scanObj, exists); - $$(node, "[slot-id]").forEach((placeholder) => { - const slotName = placeholder.getAttribute("slot-id"); - const slotSource = slots[slotName]; - if (slotSource) { - placeholder.removeAttribute("slot-id"); - placeholder.innerHTML = ""; - if (slotSource.tagName === "TEMPLATE") { - Array.from(slotSource.content.childNodes).forEach((child) => placeholder.appendChild(child.cloneNode(true))); - } else { - _mergeNode(slotSource, placeholder, scanObj, exists); - } - } - }); - } - } - if (componentFunc) { - try { - componentFunc(node); - } catch (e) { - console.error("Error in component setupFunc for", name, e); - } - } -} const Util = { clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))), base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))), diff --git a/dist/state.min.js b/dist/state.min.js index c9b529b..7a550e7 100644 --- a/dist/state.min.js +++ b/dist/state.min.js @@ -1 +1 @@ -var e;let t=null,n=null,r=null,s=0;function a(e){t=e}function o(e){n=e}function i(e={},a=null,o=null){if(e&&e.__watch)return e;const i={},d=new Map,c=new Map,l=(e,t)=>{c.has(e)||c.set(e,new Set),t?c.get(e).add(t):c.get(e).clear()},h=(e,t)=>{c.has(e)||c.set(e,new Set),c.get(e).delete(t)},u=a||(e=>i[e]),f=o||((e,t)=>i[e]=t);return Object.assign(i,e),new Proxy(i,{get(e,n){if("__watch"===n)return l;if("__unwatch"===n)return h;if(t){d.has(n)||d.set(n,new Set);const e=d.get(n);e.add(t),t._sets||(t._sets=new Set),t._sets.add(e)}return u(n)},set(e,t,a){if(u(t)!==a&&f(t,a),c.has(t)&&c.get(t).forEach(n=>{const r=n(a);void 0!==r&&(a=r,e[t]=a)}),c.has(null)&&c.get(null).forEach(e=>e(a)),d.has(t)){if(s>100)return console.error("Recursive update detected at key:",t),!0;s++;try{const e=d.get(t);for(const t of e)t.node.isConnected?n!==t.node&&r&&r(t):e.delete(t)}finally{s--}}return!0}})}const d=(e,t)=>t?e.querySelector(t):document.querySelector(e),c=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e);let l=!1;function h(e){l=e}const u=new Map;function f(e,t,n,r){const s={...r||{},...t||{}},a=Object.keys(s),o=Object.values(s),i=e+a.join(",");try{let t=u.get(i);return t||(t=new Function("Hash","LocalStorage","State",...a,e),u.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(s){return l||console.error(s,r,[e,r,t,n]),null}}let b=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const m=e=>b=e,p=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r=n[0],s={};if(n.length>1){const e=r.match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>{const r=e.substring(1,e.length-1);s[r]=n[t+1]||""})}return b(r,s)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),n):t.call(this,e,n)}}function g(e){e._renderedNodes&&e._renderedNodes.forEach(e=>{e.forEach(e=>{e.remove(),e._renderedNodes&&g(e)})})}function _(e){const t=e.node,n=e.tpl,r=e.exp;a(e);let s=r?n?(o=n,i={thisNode:t},d=t._thisObj||t,c=t._ref||null,o.includes("${")?f("return `"+o+"`",i,d,c):f("return "+o,i,d,c)):null:n;var o,i,d,c;if(a(null),e.prop){const n=e.prop;let r=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(g(t),t._renderedNodes=[]);else if("each"===r)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index";let r,a;if(s instanceof Map)r=Array.from(s.keys()),a=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);r=new Array(e.length);for(let t=0;te[t]}else r=Object.keys(s),a=e=>s[e];for(r.forEach((r,s)=>{const o=a(r);if(t._renderedNodes&&s{t._ref[n]=r,t._ref[e]=o,A(t)});else{const s=[];t._renderedNodes||(t._renderedNodes=[]),t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref},i._ref[n]=r,i._ref[e]=o,i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),s.push(i)}),t._renderedNodes.push(s)}});t._renderedNodes&&t._renderedNodes.length>r.length;)t._renderedNodes[t._renderedNodes.length-1].forEach(e=>{g(e),e.remove()}),t._renderedNodes.pop()}else g(t),t._renderedNodes=[];else if("bind"===r){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&(t.hasAttribute("autocomplete")||t.setAttribute("autocomplete","off")),"checkbox"===t.type){"on"===t.value||s||(f(`${n} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const e=s instanceof Array?s.includes(t.value):!!s;t.checked!==e&&(t.checked=e)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{t.value!==String(s??"")&&(t.value=s)}):t.isContentEditable&&t.innerHTML!==String(s??"")&&(t.innerHTML=s);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:s}))}else["checked","disabled","readonly"].includes(r)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(r,""):t.removeAttribute(r):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===r?t.textContent=s??"":"html"===r?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===r&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(r,s??""))}}r=e=>_(e);const v=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push(e),_(e)},A=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=p(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;if(e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=p(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0),"TEMPLATE"!==e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("$each")||e.hasAttribute("st-if")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each"))&&["as","index"].includes(t.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),e.parentNode.insertBefore(t,e),t.content.appendChild(e),t._ref=e._ref,void(e=t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE"),n=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each"].includes(e.name)),r=n[n.length-1];t.setAttribute(r.name,r.value),e.removeAttribute(r.name),"$each"!==r.name&&"st-each"!==r.name||Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),Array.from(e.content.childNodes).forEach(e=>{t.content.appendChild(e)}),e.content.appendChild(t),t._ref=e._ref}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj&&(t.thisObj=e._thisObj||null),void 0===t.thisObj){let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0===t.extendVars&&(t.extendVars={}),void 0!==e._ref&&(Object.assign(e._ref,t.extendVars),t.extendVars={...e._ref}),((e,t)=>{if(T.exists(e.tagName)&&!e._componentInitialized&&(e._componentInitialized=!0,w(e.tagName,e,t),c(e,"[slot-id]").forEach(e=>e.removeAttribute("slot-id")),e._thisObj||(e._thisObj=e)),e._bindings)return e._bindings.forEach(e=>_(e)),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),void(e.hasAttribute("onupdate")&&f(e.getAttribute("onupdate"),{thisNode:e},e._thisObj||e,e._ref||{}));let n=[];var r;"TEMPLATE"===e.tagName?(e._children=[...e.content.childNodes],e._renderedNodes=[],e.hasAttribute("$if")?n.push(e.getAttributeNode("$if")):e.hasAttribute("$each")?n.push(e.getAttributeNode("$each")):e.hasAttribute("st-if")?n.push(e.getAttributeNode("st-if")):e.hasAttribute("st-each")&&n.push(e.getAttributeNode("st-each"))):n=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),n.forEach(n=>{const r=n.name.startsWith("$")||n.name.startsWith("st-"),s=r?n.name.slice(n.name.startsWith("$")?1:3):n.name;let a=n.value;if(e.removeAttribute(n.name),s.startsWith("."))v({node:e,prop:s.split("."),tpl:a,exp:r});else if(s.startsWith("on")){const n=s.slice(2);"update"===n&&(e._hasOnUpdate=!0),"load"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"onunload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),i=e,d=t.thisObj,i.addEventListener(n,e=>{f(a,{event:e,thisNode:i,...e.detail||{}},d||i,i._ref||{})})}else"bind"===s?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;o(e),h(!0),"checkbox"===e.type&&e._checkboxMultiMode?f(`!!checked ? (!${a}.includes(val) && ${a}.push(val)) : (index = ${a}.indexOf(val), index > -1 && ${a}.splice(index, 1))`,{val:e.value,checked:r,thisNode:e},t.thisObj||e,e._ref||{}):f(`${a} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),h(!1),o(null)}):"text"!==s||a||(a=e.textContent,e.textContent=""),a&&(a=p(a),v({node:e,attr:s,tpl:a,exp:r}));var i,d}),(e._hasOnLoad||e._componentInitialized)&&(r=e,Promise.resolve().then(()=>r.dispatchEvent(new Event("load",{bubbles:!1})))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,t);const n=[...e.childNodes||[]];t.extendVars=e._ref||t.extendVars,n.forEach(n=>A(n,{thisObj:t.thisObj,extendVars:{...e._ref}}))},E=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._bindings&&e._bindings.forEach(e=>{e._sets&&(e._sets.forEach(t=>t.delete(e)),e._sets.clear())}),e.childNodes&&e.childNodes.forEach(e=>E(e)))},y=A,N=new Map,O=[],T={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{N.set(e.toUpperCase(),t),"loading"!==document.readyState?T._addTemplate(e,n,r):O.push([e,n,r])},exists:e=>N.has(e.toUpperCase()),getSetupFunction:e=>N.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{O.forEach(([e,t,n])=>T._addTemplate(e,t,n)),O.length=0}};function j(e,t,n,r={}){e.attributes&&Array.from(e.attributes).forEach(e=>"class"!==e.name&&t.setAttribute(e.name,e.value)),e.classList&&t.classList.add(...e.classList),Array.from(e.childNodes).forEach(e=>t.appendChild(e)),e.tagName&&T.exists(e.tagName)&&w(e.tagName,t,n,r)}function w(e,t,n,r={}){if(r[e])return;r[e]=!0,n.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const s=T.getSetupFunction(e),a={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(a[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=i(t.state||{});const o=T.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){j(e.children[0],t,n,r),c(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id"),s=a[t];s&&(e.removeAttribute("slot-id"),e.innerHTML="","TEMPLATE"===s.tagName?Array.from(s.content.childNodes).forEach(t=>e.appendChild(t.cloneNode(!0))):j(s,e,n,r))})}}if(s)try{s(t)}catch(t){console.error("Error in component setupFunc for",e,t)}}const x={clone:window.structuredClone||(e=>JSON.parse(JSON.stringify(e))),base64:e=>btoa(String.fromCharCode(...(new TextEncoder).encode(e))),unbase64:e=>(new TextDecoder).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0))),urlbase64:e=>x.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>x.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),s=r-e;return e=r,t+=s,n++,s},avg:()=>t/n}}};globalThis.Util=x;let S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=i({},e=>x.safeJson(S.get(e)),(e,t)=>{const n=S.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?S.delete(e):S.set(e,r),window.location.hash="#"+S.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=S;S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),S.forEach((e,n)=>{t.get(n)!==e&&(M[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===S.get(t)&&(M[t]=void 0)})});const $=i({},e=>x.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=M,globalThis.LocalStorage=$,"undefined"!=typeof document){const e=()=>{T._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&A(e)}),e.removedNodes.forEach(e=>E(e))})}).observe(document.documentElement,{childList:!0,subtree:!0});const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),A(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{d as $,c as $$,T as Component,M as Hash,$ as LocalStorage,i as NewState,y as RefreshState,m as SetTranslator,x as Util,A as _scanTree,E as _unbindTree}; +var e;let t=null,n=null;const r=e=>t=e,s=e=>n=e,a=new Set;function o(e={},r=null,s=null){const o={},i=new Map,d=new Map,l=(e,t)=>(d.has(e)||d.set(e,new Set),t?d.get(e).add(t):d.get(e).clear(),()=>d.get(e).delete(t)),c=(e,t)=>{d.has(e)&&d.get(e).delete(t)},h=r||(e=>o[e]),u=s||((e,t)=>o[e]=t);return Object.assign(o,e),new Proxy(o,{get:(e,n)=>"__watch"===n?l:"__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,r){if(h(t)!==r&&u(t,r),d.has(t)&&d.get(t).forEach(n=>{const s=n(r);void 0!==s&&(r=s,e[t]=r)}),d.has(null)&&d.get(null).forEach(e=>e(r)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?n!==t.node&&a.forEach(e=>e(t)):e.delete(t)}return!0}})}const i=(e,t)=>t?e.querySelector(t):document.querySelector(e),d=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),l=new Map,c=[],h={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{l.set(e.toUpperCase(),t),"loading"!==document.readyState?h._addTemplate(e,n,r):c.push([e,n,r])},exists:e=>l.has(e.toUpperCase()),getSetupFunction:e=>l.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,r={}){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,r)}function f(e,t,n,r={}){if(r[e])return;r[e]=!0,n.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const s=h.getSetupFunction(e),a={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(a[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=o(t.state||{});const i=h.getTemplate(e);if(i){const e=i.content.cloneNode(!0);if(e.childNodes.length){const s=Array.from(e.childNodes).find(e=>e.nodeType===Node.ELEMENT_NODE);s&&u(s,t,n,r),d(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");a[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",u(a[t],e,n,r))})}}s&&s(t)}let b=!1;function m(e){b=e}const p=new Map;function g(e,t,n,r){const s={...r||{},...t||{}},a=Object.keys(s),o=Object.values(s),i=e+a.join(",");try{let t=p.get(i);return t||(t=new Function("Hash","LocalStorage","State",...a,e),p.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(s){return b||console.error(s,r,[e,r,t,n]),null}}function _(e,t,n,r){return e.includes("${")?g("return `"+e+"`",t,n,r):g("return "+e,t,n,r)}let v=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const A=e=>v=e,E=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r=n[0],s={};if(n.length>1){const e=r.match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>{const r=e.substring(1,e.length-1);s[r]=n[t+1]||""})}return v(r,s)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),n):t.call(this,e,n)}}var y;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,n=e.tpl,s=e.exp;r(e);let a=s?n?_(n,{thisNode:t},t._thisObj||t,t._ref||null):null:n;if(r(null),e.prop){const n=e.prop;let r=t;for(let e=0;e{t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(N(t),t._renderedNodes=[]);else if("each"===r)if(a&&"object"==typeof a){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index";let r,s;if(a instanceof Map)r=Array.from(a.keys()),s=e=>a.get(e);else if("function"==typeof a[Symbol.iterator]){const e=Array.isArray(a)?a:Array.from(a);r=new Array(e.length);for(let t=0;te[t]}else r=Object.keys(a),s=e=>a[e];for(r.forEach((r,a)=>{const o=s(r);if(t._renderedNodes&&a{t._ref[n]=r,t._ref[e]=o,x(t)});else{const s=[];t._renderedNodes||(t._renderedNodes=[]),t._children.forEach(a=>{const i=a.cloneNode(!0);i._ref={...t._ref},i._ref[n]=r,i._ref[e]=o,i._thisObj=t._thisObj,t.parentNode.insertBefore(i,t),s.push(i)}),t._renderedNodes.push(s)}});t._renderedNodes&&t._renderedNodes.length>r.length;)t._renderedNodes[t._renderedNodes.length-1].forEach(e=>{N(e),e.remove()}),t._renderedNodes.pop()}else N(t),t._renderedNodes=[];else if("bind"===r){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&(t.hasAttribute("autocomplete")||t.setAttribute("autocomplete","off")),"checkbox"===t.type){"on"===t.value||a||(g(`${n} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),a=[]),t._checkboxMultiMode=a instanceof Array;const e=a instanceof Array?a.includes(t.value):!!a;t.checked!==e&&(t.checked=e)}else"radio"===t.type?t.checked!==(t.value===String(a??""))&&(t.checked=t.value===String(a??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{t.value!==String(a??"")&&(t.value=a)}):t.isContentEditable&&t.innerHTML!==String(a??"")&&(t.innerHTML=a);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:a}))}else["checked","disabled","readonly"].includes(r)&&(a=!!a),"boolean"==typeof a?a?t.setAttribute(r,""):t.removeAttribute(r):void 0!==a&&("string"!=typeof a&&(a=JSON.stringify(a)),"text"===r?t.textContent=a??"":"html"===r?t.innerHTML=a??"":"IMG"===t.tagName&&"src"===r&&a.includes(".svg")?t.setAttribute("_src",a??""):t.setAttribute(r,a??""))}}y=e=>O(e),a.add(y);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}),O(e)},x=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=E(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;if(e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=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 t=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each"))&&["as","index"].includes(t.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),e.parentNode.insertBefore(t,e),t.content.appendChild(e),t._ref=e._ref,void(e=t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE"),n=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each"].includes(e.name)),r=n[n.length-1];t.setAttribute(r.name,r.value),e.removeAttribute(r.name),"$each"!==r.name&&"st-each"!==r.name||Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),Array.from(e.content.childNodes).forEach(e=>{t.content.appendChild(e)}),e.content.appendChild(t),t._ref=e._ref}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj&&(t.thisObj=e._thisObj||null),void 0===t.thisObj){let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0===t.extendVars&&(t.extendVars={}),void 0!==e._ref&&(Object.assign(e._ref,t.extendVars),t.extendVars={...e._ref}),((e,t)=>{if(e._bindings)return e._states=new Set,e._bindings.forEach(t=>{O({node:e,...t})}),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),void(e.hasAttribute("onupdate")&&g(e.getAttribute("onupdate"),{thisNode:e},e._thisObj||e,e._ref||{}));h.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{if(n.name.startsWith("$.")){const r=n.name.slice(2);let s=n.value;s.includes("this.")&&(s=s.replace(/\bthis\./g,"this.parent.")),s=E(s);const a=_(s,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let o=e;const i=r.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e));let n=[];var r;"TEMPLATE"===e.tagName?(e._children=[...e.content.childNodes],e._renderedNodes=[],e.hasAttribute("$if")?n.push(e.getAttributeNode("$if")):e.hasAttribute("$each")?n.push(e.getAttributeNode("$each")):e.hasAttribute("st-if")?n.push(e.getAttributeNode("st-if")):e.hasAttribute("st-each")&&n.push(e.getAttributeNode("st-each"))):n=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,n.forEach(n=>{const r=n.name.startsWith("$")||n.name.startsWith("st-"),a=r?n.name.slice(n.name.startsWith("$")?1:3):n.name;let o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))T({node:e,prop:a.split("."),tpl:o,exp:r});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),"onunload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),i=e,d=t.thisObj,i.addEventListener(n,e=>{g(o,{event:e,thisNode:i,...e.detail||{}},d||i,i._ref||{})})}else"bind"===a?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;s(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:r,thisNode:e},t.thisObj||e,e._ref||{}):g(`${o} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),s(null)}):"text"!==a||o||(o=e.textContent,e.textContent=""),o&&(o=E(o),T({node:e,attr:a,tpl:o,exp:r}));var i,d}),(e._hasOnLoad||e._componentInitialized)&&(r=e,Promise.resolve().then(()=>r.dispatchEvent(new Event("load",{bubbles:!1})))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,t);const n=[...e.childNodes||[]];t.extendVars=e._ref||t.extendVars,n.forEach(n=>x(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,r]of t)for(const t of r)t.node===e&&r.delete(t)}),e.childNodes&&e.childNodes.forEach(e=>j(e)))},w=x,S={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=>S.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>S.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),s=r-e;return e=r,t+=s,n++,s},avg:()=>t/n}}};globalThis.Util=S;let $=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const M=o({},e=>S.safeJson($.get(e)),(e,t)=>{const n=$.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?$.delete(e):$.set(e,r),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))||""),$.forEach((e,n)=>{t.get(n)!==e&&(M[n]=S.safeJson(e))}),t.forEach((e,t)=>{void 0===$.get(t)&&(M[t]=void 0)})});const L=o({},e=>S.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=M,globalThis.LocalStorage=L,"undefined"!=typeof document){const e=()=>{h._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&x(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0});const e=document.documentElement;e.hasAttribute("$data-bs-theme")||e.hasAttribute("data-bs-theme")||e.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),x(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,d as $$,h as Component,M as Hash,L as LocalStorage,o as NewState,w as RefreshState,A as SetTranslator,S as Util,x as _scanTree,j as _unbindTree}; diff --git a/old/state.js b/old/state.js new file mode 100644 index 0000000..fbf339e --- /dev/null +++ b/old/state.js @@ -0,0 +1,519 @@ +// state.js v2.3 +(() => { + (() => { + try { return document.createElement('div').setAttribute('$t', '1') } catch (e) { } + const originalSetAttribute = Element.prototype.setAttribute + Element.prototype.setAttribute = function (name, value) { + if (!name.startsWith('$')) return originalSetAttribute.call(this, name, value) + return originalSetAttribute.call(this, 'st-' + name.substring(1), value) + } + })() + + globalThis.$ = (a, b) => b ? a.querySelector(b) : document.querySelector(a) + globalThis.$$ = (a, b) => b ? a.querySelectorAll(b) : document.querySelectorAll(a) + let _activeBinding = null + let _noWriteBack = null + globalThis.NewState = function (defaults = {}, getter = null, setter = null) { + const _defaults = {}//, _localStorageBinds = {}, _hashBinds = {} + const _stateMappings = new Map() + const _watchers = new Map() + const _watchFunc = (k, cb) => { + if (!_watchers.has(k)) _watchers.set(k, new Set()) + !cb ? _watchers.get(k).clear() : _watchers.get(k).add(cb) + } + const _unwatchFunc = (k, cb) => { + if (!_watchers.has(k)) _watchers.set(k, new Set()) + _watchers.get(k).delete(cb) + } + const _getter = getter || (k => _defaults[k]) + const _setter = setter || ((k, v) => _defaults[k] = v) + Object.assign(_defaults, defaults) + // 创建代理对象,实现数据绑定 + return new Proxy(_defaults, { + get(target, key) { + if (key === '__watch') return _watchFunc + if (key === '__unwatch') return _unwatchFunc + if (_activeBinding) { + if (!_stateMappings.has(key)) _stateMappings.set(key, new Set()) + _stateMappings.get(key).add(_activeBinding) + _activeBinding.node._states.add(_stateMappings) // 存储状态机到节点,方便删除节点时清除绑定 + } + return _getter(key) + }, + set(target, key, value) { + if (_getter(key) !== value) { // 允许自赋值,用来触发更新 + _setter(key, value) + } + if (_watchers.has(key)) { + _watchers.get(key).forEach(cb => { + const r = cb(value) + if (r !== undefined) { + value = r + target[key] = value + } + }) + } + if (_watchers.has(null)) { + _watchers.get(null).forEach(cb => cb(value)) + } + if (_stateMappings.has(key)) { + const bindings = _stateMappings.get(key) + for (const binding of bindings) { + if (!binding.node.isConnected) { + bindings.delete(binding) + continue + } + if (_noWriteBack !== binding.node) _updateBinding(binding) + } + } + return true + } + }) + } + + function _returnCode(code, vars, thisObj, extendVars) { + if (code.includes('${')) return _runCode('return `' + code + '`', vars, thisObj, extendVars) + else return _runCode('return ' + code, vars, thisObj, extendVars) + } + let _disableRunCodeError = false + function _runCode(code, vars, thisObj, extendVars) { + const argKeys = [...Object.keys(extendVars), ...Object.keys(vars)] + const argValues = [...Object.values(extendVars), ...Object.values(vars)] + argKeys.push(code) + try { + const r = new Function(...argKeys).apply(thisObj, argValues) + return r + } catch (e) { + if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]) + return null + } + } + function _clearRenderedNodes(node) { + if (node._renderedNodes) node._renderedNodes.forEach(nodes => { + nodes.forEach(child => { + child.remove() + if (child._renderedNodes) _clearRenderedNodes(child) + }) + }) + } + // 更新绑定值 + function _updateBinding(binding) { + const node = binding.node + const tpl = binding.tpl + const exp = binding.exp + // 每次都动态重建绑定,确保最新状态 + _activeBinding = binding + let result = exp ? (tpl ? _returnCode(tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : tpl + _activeBinding = null + + if (binding.prop) { + // 处理对象绑定 + const prop = binding.prop + let o = node + for (let i = 0; i < prop.length - 1; i++) { + if (!prop[i]) continue + if (o[prop[i]] == null) o[prop[i]] = {} + o = o[prop[i]] + if (typeof o !== 'object') break + } + if (typeof o === 'object' && o !== null) { + const resultIsObject = typeof result === 'object' && result != null && !Array.isArray(result) + const lk = prop[prop.length - 1] + if (lk) { + if (resultIsObject && o[lk] == null) o[lk] = {} + const lo = o[lk] + if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result) + else o[lk] = result + } else if (resultIsObject && typeof o === 'object') { + Object.assign(o, result) + } + } + } else if (binding.attr) { + // 处理attr绑定 + const attr = binding.attr + if (attr === 'if') { + if (result) { + node._children.forEach(child => { + node.parentNode.insertBefore(child, node) + child._ref = { ...node._ref } + }) + node._renderedNodes = [node._children] + } else { + _clearRenderedNodes(node) + node._renderedNodes = [] + } + } else if (attr === 'each') { + if (result && typeof result === 'object') { + const asName = node.getAttribute('as') || 'item' + const indexName = node.getAttribute('index') || 'index' + let keys, getVal; + if (result instanceof Map) { + keys = Array.from(result.keys()) + getVal = k => result.get(k) + } else if (typeof result[Symbol.iterator] === 'function') { + const arr = Array.isArray(result) ? result : Array.from(result) + keys = new Array(arr.length) + for (let i = 0; i < arr.length; i++) keys[i] = i + getVal = k => arr[k] + } else { + keys = Object.keys(result) + getVal = k => result[k] + } + keys.forEach((k, i) => { + const item = getVal(k) + if (i < node._renderedNodes.length) { + node._renderedNodes[i].forEach(child => { + child._ref[indexName] = k + child._ref[asName] = item + _scanTree(child) + }) + } else { + const newNodes = [] + node._children.forEach(child => { + const cloned = child.cloneNode(true) + cloned._ref = { ...node._ref } + cloned._ref[indexName] = k + cloned._ref[asName] = item + cloned._thisObj = node._thisObj + node.parentNode.insertBefore(cloned, node) + newNodes.push(cloned) + }) + node._renderedNodes.push(newNodes) + } + }) + while (node._renderedNodes.length > keys.length) { + node._renderedNodes[node._renderedNodes.length - 1].forEach(child => { + _clearRenderedNodes(child) + child.remove() + }) + node._renderedNodes.pop() + } + } else { + _clearRenderedNodes(node) + node._renderedNodes = [] + } + } else if (attr === 'bind') { + if (['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName)) { + // 防止浏览器后退前进后擅自恢复表单值 + if (!node.hasAttribute('autocomplete')) node.setAttribute('autocomplete', 'off') + } + if (node.type === 'checkbox') { + if (node.value !== 'on' && !result) { + // 复选框有指定名字且未绑定值时,使用多选模式 + _runCode(`${tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {}) + result = [] + } + node._checkboxMultiMode = result instanceof Array + const isChecked = result instanceof Array ? result.includes(node.value) : !!result + if (node.checked !== isChecked) node.checked = isChecked + } else if (node.type === 'radio') { + if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? '')) + } else if ('value' in node && node.type !== 'file') { + Promise.resolve().then(() => { // 确保 select 元素值处理好再设置 + if (node.value !== String(result ?? '')) node.value = result + }) + } else if (node.isContentEditable) { + if (node.innerHTML !== String(result ?? '')) node.innerHTML = result + } + node.dispatchEvent(new CustomEvent('bind', { bubbles: false, detail: result })) + } else { + if (['checked', 'disabled', 'readonly'].includes(attr)) result = !!result + if (typeof result === 'boolean') { + result ? node.setAttribute(attr, '') : node.removeAttribute(attr) + } else if (result !== undefined) { + if (typeof result !== 'string') result = JSON.stringify(result) + if (attr === 'text') { + node.textContent = result ?? '' + } else if (attr === 'html') { + node.innerHTML = result ?? '' + } else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) { + node.setAttribute('_src', result ?? '') + } else { + node.setAttribute(attr, result ?? '') + } + } + } + } + } + const _initBinding = (binding) => { + if (!binding.node._bindings) binding.node._bindings = [] + binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }) // 存储绑定信息,方便恢复 + _updateBinding(binding) + } + // 旧逻辑,存在合并优先级问题 + // const _mergeNode = (from, to, scanObj, exists = {}) => { + // Array.from(from.attributes).forEach(attr => attr.name !== 'class' && to.setAttribute(attr.name, attr.value)) + // to.classList.add(...from.classList) + // Array.from(from.childNodes).forEach(child => to.appendChild(child)) + // // 实现组件继承 + // if (Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists) + // } + // 新逻辑,智能合并 style,外部样式放在后面以获得更高 CSS 优先级 + const _mergeNode = (from, to, scanObj, exists = {}) => { + Array.from(from.attributes).forEach(attr => { + if (attr.name === 'class') return + if (attr.name === 'style') { + // 智能合并 style,外部样式放在后面以获得更高 CSS 优先级 + if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`) + else to.setAttribute('style', attr.value) + } else if (!to.hasAttribute(attr.name)) { + to.setAttribute(attr.name, attr.value) + } + }) + to.classList.add(...from.classList) + Array.from(from.childNodes).forEach(child => to.appendChild(child)) + if (Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists) + } + const _makeComponent = (name, node, scanObj, exists = {}) => { + if (exists[name]) return + exists[name] = true + if (scanObj.thisObj) Array.from(node.attributes).forEach(attr => { if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) attr.value = attr.value.replace(/\bthis\./g, 'this.parent.') }) // 在父组件中的子组件属性里的this.需要转换为this.parent.,因为实际运行会在子组件上下文执行 + const componentFunc = Component.getSetupFunction(name) + const slots = {} + Array.from(node.childNodes).forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE && child.hasAttribute('slot')) { + slots[child.getAttribute('slot')] = child + child.removeAttribute('slot') + } + }) + node.innerHTML = '' + node.state = NewState(node.state || {}) + const template = Component.getTemplate(name) + if (template) { + const tplnode = template.content.cloneNode(true) + if (tplnode.childNodes.length) { + const rootNode = tplnode.children[0] + _mergeNode(rootNode, node, scanObj, exists) + $$(node, '[slot-id]').forEach(placeholder => { + const slotName = placeholder.getAttribute('slot-id') + if (slots[slotName]) { + placeholder.removeAttribute('slot-id') + placeholder.innerHTML = '' + _mergeNode(slots[slotName], placeholder, scanObj, exists) + } + }) + } + } + if (componentFunc) componentFunc(node) + } + const _parseNode = (node, scanObj) => { + if (node._bindings) { + // 恢复绑定信息 + node._states = new Set() + node._bindings.forEach(bindingData => { + const binding = { node: node, ...bindingData } + _updateBinding(binding) + }) + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })) + return + } + + // 处理组件 + if (Component.exists(node.tagName) && !node._componentInitialized) { + _makeComponent(node.tagName, node, scanObj) + $$(node, '[slot-id]').forEach(placeholder => placeholder.removeAttribute('slot-id')) + node._componentInitialized = true + if (!node._thisObj) node._thisObj = node + } + + let attrs = [] + if (node.tagName === 'TEMPLATE') { + node._children = [...node.content.childNodes] + node._renderedNodes = [] + if (node.hasAttribute('$if')) attrs.push(node.getAttributeNode('$if')) + else if (node.hasAttribute('$each')) attrs.push(node.getAttributeNode('$each')) + else if (node.hasAttribute('st-if')) attrs.push(node.getAttributeNode('st-if')) + else if (node.hasAttribute('st-each')) attrs.push(node.getAttributeNode('st-each')) + } else { + attrs = Array.from(node.attributes).filter(attr => (attr.name.startsWith('$') || attr.name.startsWith('st-')) && !['$if', '$each', 'st-if', 'st-each'].includes(attr.name) || attr.name.includes('.')) + } + + if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj + if (!node._thisObj) node._thisObj = scanObj.thisObj || null + if (!node._ref) node._ref = scanObj.extendVars || {} + node._states = new Set() + // node._handleEvents = [] + attrs.forEach(attr => { + const exp = attr.name.startsWith('$') || attr.name.startsWith('st-') + const realAttrName = exp ? attr.name.slice(attr.name.startsWith('$') ? 1 : 3) : attr.name + let tpl = attr.value + node.removeAttribute(attr.name) + if (realAttrName.startsWith('.')) { + // 处理属性绑定 + _initBinding({ node: node, prop: realAttrName.split('.'), tpl, exp }) + } else { + if (realAttrName.startsWith('on')) { + if (realAttrName === 'onupdate') node._hasOnUpdate = true + if (realAttrName === 'onload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true + if (realAttrName === 'onunload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; + // node._handleEvents.push(realAttrName.slice(2)); + ((node, thisObj) => { + node.addEventListener(realAttrName.slice(2), (e) => { + _runCode(tpl, { event: e, thisNode: node, ...(e.detail || {}) }, thisObj || node, node._ref || {}) + }) + })(node, scanObj.thisObj) + } else { + if (realAttrName === 'bind') { + // 处理 bind 指令(双向数据绑定) + node.addEventListener(node.tagName === 'TEXTAREA' || node.isContentEditable || node.type === 'text' || node.type === 'password' ? 'input' : 'change', (e) => { + let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail) + _noWriteBack = node + _disableRunCodeError = true // 忽略赋值错误,支持非对象类型的 $bind + 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 || {}) + } + _disableRunCodeError = false + _noWriteBack = null + }) + } else if (realAttrName === 'text' && !tpl) { + tpl = node.textContent + node.textContent = '' + } + if (tpl) _initBinding({ node: node, attr: realAttrName, tpl, exp }) + } + } + }) + if (node._hasOnLoad || node._componentInitialized) { + (node => { + Promise.resolve().then(() => node.dispatchEvent(new Event('load', { bubbles: false }))) + })(node) + } + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })) + if (node._thisObj) scanObj.thisObj = node._thisObj + } + + const _scanTree = (node, scanObj = {}) => { + if (node.nodeType !== 1) return + + // 自动为非模板节点的$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 + node = template + return // 异步交给MutationObserver处理 + } + // 处理模板节点同时存在$if和$each指令的情况, 自动为第二个指令创建模版节点 + 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)) + const attr = attrs[attrs.length - 1] + template.setAttribute(attr.name, attr.value) + node.removeAttribute(attr.name) + if (attr.name === '$each' || attr.name === 'st-each') { + Array.from(node.attributes).filter(attr => ['as', 'index'].includes(attr.name)).forEach(attr => { + template.setAttribute(attr.name, attr.value) + node.removeAttribute(attr.name) + }) + } + Array.from(node.content.childNodes).forEach(child => { + template.content.appendChild(child) + }) + node.content.appendChild(template) + template._ref = node._ref + } + + // 处理 SVG + if (node.tagName === 'IMG' && (node.hasAttribute('src') || node.hasAttribute('_src') || node.hasAttribute('$src'))) { + const imgNode = node + Promise.resolve().then(() => { + const url = imgNode.getAttribute('_src') || imgNode.getAttribute('src') + if (url) fetch(url, { cache1: 'force-cache' }).then(r => { + return r.text() + }).then(svgText => { + const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector('svg') + if (realSvg) { + Array.from(imgNode.attributes).forEach(attr => realSvg.setAttribute(attr.name, attr.value)) + imgNode.replaceWith(realSvg) + } + }) + }) + } + + if (node._thisObj !== undefined) scanObj.thisObj = node._thisObj || null + if (scanObj.thisObj === undefined) { + // 向上查找_thisObj,如果没有就用null,null 代表就是没有,undefined 表示需要向上查找 + let curr = node + while (curr && curr._thisObj === undefined) curr = curr.parentNode + scanObj.thisObj = curr ? curr._thisObj : null + } + if (node._ref === undefined) { + let curr = node + while (curr && curr._ref === undefined) curr = curr.parentNode + node._ref = curr ? { ...curr._ref } : {} + } + if (scanObj.extendVars === undefined) scanObj.extendVars = {} + if (node._ref !== undefined) { + Object.assign(node._ref, scanObj.extendVars) + scanObj.extendVars = { ...node._ref } + } + _parseNode(node, scanObj) + const nodes = [...(node.childNodes || [])] + scanObj.extendVars = node._ref || scanObj.extendVars + nodes.forEach(child => _scanTree(child, { thisObj: scanObj.thisObj, extendVars: { ...node._ref } })) + } + + const _unbindTree = (node) => { + if (node.nodeType !== 1) return + if (node._hasOnUnload) node.dispatchEvent(new Event('unload', { bubbles: false })) + if (node._states) { + node._states.forEach(stateMappings => { + for (const [key, bindingSet] of stateMappings) { + for (const binding of bindingSet) { + if (binding.node === node) bindingSet.delete(binding) + } + } + }) + } + node.childNodes && node.childNodes.forEach(child => _unbindTree(child)) + } + globalThis.RefreshState = _scanTree + + const _components = new Map() + const _pendingTemplates = [] + globalThis.Component = { + getTemplate: name => $(`template[component="${name.toUpperCase()}"]`), + register: (name, setupFunc, templateNode = null, ...globalNodes) => { + _components.set(name.toUpperCase(), setupFunc) + if (document.readyState !== 'loading') Component._addTemplate(name, templateNode, globalNodes) + else _pendingTemplates.push([name, templateNode, globalNodes]) + }, + exists: (name) => _components.has(name.toUpperCase()), + getSetupFunction: (name) => _components.get(name.toUpperCase()), + _addTemplate: (name, templateNode, globalNodes) => { + if (templateNode) { + const template = document.createElement('TEMPLATE') + template.setAttribute('component', name.toUpperCase()) + template.content.appendChild(templateNode) + document.body.appendChild(template) + } + if (globalNodes) globalNodes.forEach(node => document.body.appendChild(node)) + } + } + + document.addEventListener('DOMContentLoaded', () => { + _pendingTemplates.forEach(([name, templateNode, globalNodes]) => Component._addTemplate(name, templateNode, globalNodes)) + _pendingTemplates.length = 0 + }, true) + + document.addEventListener('DOMContentLoaded', () => { + new MutationObserver(mutations => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(newNode => { + if (newNode.isConnected) _scanTree(newNode) + }) + mutation.removedNodes.forEach(oldNode => _unbindTree(oldNode)) + }) + }).observe(document.documentElement, { childList: true, subtree: true }) + _scanTree(document.documentElement) + }) + +})() diff --git a/package.json b/package.json index 655ab21..dcb243c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@web/state", - "version": "1.0.2", + "version": "1.0.8", "type": "module", "main": "dist/state.js", diff --git a/src/component.js b/src/component.js index e7256a3..b50e589 100644 --- a/src/component.js +++ b/src/component.js @@ -1,7 +1,6 @@ // src/component.js import { NewState } from './observer.js'; import { $, $$ } from './dom-utils.js'; -import { _scanTree } from './dom.js'; const _components = new Map(); const _pendingTemplates = []; @@ -32,11 +31,17 @@ export const Component = { export function _mergeNode(from, to, scanObj, exists = {}) { if (from.attributes) { - Array.from(from.attributes).forEach(attr => attr.name !== 'class' && to.setAttribute(attr.name, attr.value)); - } - if (from.classList) { - to.classList.add(...from.classList); + Array.from(from.attributes).forEach(attr => { + if (attr.name === 'class') return; + if (attr.name === 'style') { + if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`); + else to.setAttribute('style', attr.value); + } else if (!to.hasAttribute(attr.name)) { + to.setAttribute(attr.name, attr.value); + } + }); } + to.classList.add(...from.classList); Array.from(from.childNodes).forEach(child => to.appendChild(child)); if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists); } @@ -44,7 +49,13 @@ export function _mergeNode(from, to, scanObj, exists = {}) { export function _makeComponent(name, node, scanObj, exists = {}) { if (exists[name]) return; exists[name] = true; - if (scanObj.thisObj) Array.from(node.attributes).forEach(attr => { if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) attr.value = attr.value.replace(/\bthis\./g, 'this.parent.'); }); + if (scanObj.thisObj) { + Array.from(node.attributes).forEach(attr => { + if ((attr.name.startsWith('$') || attr.name.startsWith('st-')) && attr.value.includes('this.')) { + attr.value = attr.value.replace(/\bthis\./g, 'this.parent.'); + } + }); + } const componentFunc = Component.getSetupFunction(name); const slots = {}; Array.from(node.childNodes).forEach(child => { @@ -59,28 +70,17 @@ export function _makeComponent(name, node, scanObj, exists = {}) { if (template) { const tplnode = template.content.cloneNode(true); if (tplnode.childNodes.length) { - const rootNode = tplnode.children[0]; - _mergeNode(rootNode, node, scanObj, exists); + const rootNode = Array.from(tplnode.childNodes).find(n => n.nodeType === Node.ELEMENT_NODE); + if (rootNode) _mergeNode(rootNode, node, scanObj, exists); $$(node, '[slot-id]').forEach(placeholder => { const slotName = placeholder.getAttribute('slot-id'); - const slotSource = slots[slotName]; - if (slotSource) { + if (slots[slotName]) { placeholder.removeAttribute('slot-id'); placeholder.innerHTML = ''; - if (slotSource.tagName === 'TEMPLATE') { - Array.from(slotSource.content.childNodes).forEach(child => placeholder.appendChild(child.cloneNode(true))); - } else { - _mergeNode(slotSource, placeholder, scanObj, exists); - } + _mergeNode(slots[slotName], placeholder, scanObj, exists); } }); } } - if (componentFunc) { - try { - componentFunc(node); - } catch (e) { - console.error('Error in component setupFunc for', name, e); - } - } + if (componentFunc) componentFunc(node); } diff --git a/src/dom.js b/src/dom.js index 2b428e0..852ec14 100644 --- a/src/dom.js +++ b/src/dom.js @@ -165,7 +165,6 @@ export function _updateBinding(binding) { } else if (node.type === 'radio') { if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? '')); } else if ('value' in node && node.type !== 'file') { - // 这里必须用宏任务,微任务不足以确保DOM更新完成 setTimeout(() => { if (node.value !== String(result ?? '')) node.value = result; }); @@ -195,20 +194,17 @@ export function _updateBinding(binding) { export const _initBinding = (binding) => { if (!binding.node._bindings) binding.node._bindings = []; - binding.node._bindings.push(binding); + binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }); _updateBinding(binding); }; export const _parseNode = (node, scanObj) => { - if (Component.exists(node.tagName) && !node._componentInitialized) { - node._componentInitialized = true; - _makeComponent(node.tagName, node, scanObj); - $$(node, '[slot-id]').forEach(placeholder => placeholder.removeAttribute('slot-id')); - if (!node._thisObj) node._thisObj = node; - } - if (node._bindings) { - node._bindings.forEach(binding => _updateBinding(binding)); + node._states = new Set(); + node._bindings.forEach(bindingData => { + const binding = { node: node, ...bindingData }; + _updateBinding(binding); + }); if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); if (node.hasAttribute('onupdate')) { _runCode(node.getAttribute('onupdate'), { thisNode: node }, node._thisObj || node, node._ref || {}); @@ -216,6 +212,32 @@ export const _parseNode = (node, scanObj) => { return; } + if (Component.exists(node.tagName) && !node._componentInitialized) { + Array.from(node.attributes).forEach(attr => { + if (attr.name.startsWith('$.')) { + const realAttrName = attr.name.slice(2); + let tpl = attr.value; + if (tpl.includes('this.')) tpl = tpl.replace(/\bthis\./g, 'this.parent.'); + tpl = _translate(tpl); + const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {}); + let o = node; + const prop = realAttrName.split('.'); + for (let i = 0; i < prop.length - 1; i++) { + if (!prop[i]) continue; + if (o[prop[i]] == null) o[prop[i]] = {}; + o = o[prop[i]]; + } + o[prop[prop.length - 1]] = result; + node.removeAttribute(attr.name); + } + }); + + _makeComponent(node.tagName, node, scanObj); + $$(node, '[slot-id]').forEach(placeholder => placeholder.removeAttribute('slot-id')); + node._componentInitialized = true; + if (!node._thisObj) node._thisObj = node; + } + let attrs = []; if (node.tagName === 'TEMPLATE') { node._children = [...node.content.childNodes]; @@ -231,6 +253,7 @@ export const _parseNode = (node, scanObj) => { if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; if (!node._thisObj) node._thisObj = scanObj.thisObj || null; if (!node._ref) node._ref = scanObj.extendVars || {}; + node._states = new Set(); attrs.forEach(attr => { const exp = attr.name.startsWith('$') || attr.name.startsWith('st-'); @@ -275,6 +298,7 @@ export const _parseNode = (node, scanObj) => { } } }); + if (node._hasOnLoad || node._componentInitialized) { (node => { Promise.resolve().then(() => node.dispatchEvent(new Event('load', { bubbles: false }))); @@ -375,11 +399,12 @@ export const _scanTree = (node, scanObj = {}) => { export const _unbindTree = (node) => { if (node.nodeType !== 1) return; if (node._hasOnUnload) node.dispatchEvent(new Event('unload', { bubbles: false })); - if (node._bindings) { - node._bindings.forEach(binding => { - if (binding._sets) { - binding._sets.forEach(set => set.delete(binding)); - binding._sets.clear(); + if (node._states) { + node._states.forEach(stateMappings => { + for (const [key, bindingSet] of stateMappings) { + for (const binding of bindingSet) { + if (binding.node === node) bindingSet.delete(binding); + } } }); } diff --git a/src/observer.js b/src/observer.js index 3358405..d7684e5 100644 --- a/src/observer.js +++ b/src/observer.js @@ -1,52 +1,51 @@ // src/observer.js let _activeBinding = null; let _noWriteBack = null; -let _updateBindingFn = null; -let _updateDepth = 0; -const MAX_UPDATE_DEPTH = 100; -export function getActiveBinding() { return _activeBinding; } -export function setActiveBinding(val) { _activeBinding = val; } -export function getNoWriteBack() { return _noWriteBack; } -export function setNoWriteBack(val) { _noWriteBack = val; } +export const getActiveBinding = () => _activeBinding; +export const setActiveBinding = (val) => _activeBinding = val; +export const getNoWriteBack = () => _noWriteBack; +export const setNoWriteBack = (val) => _noWriteBack = val; -export function onNotifyUpdate(fn) { - _updateBindingFn = fn; -} +const _notifiers = new Set(); +export const onNotifyUpdate = (fn) => _notifiers.add(fn); export function NewState(defaults = {}, getter = null, setter = null) { - if (defaults && defaults.__watch) return defaults const _defaults = {}; const _stateMappings = new Map(); const _watchers = new Map(); + const _watchFunc = (k, cb) => { if (!_watchers.has(k)) _watchers.set(k, new Set()); !cb ? _watchers.get(k).clear() : _watchers.get(k).add(cb); + return () => _watchers.get(k).delete(cb); }; + const _unwatchFunc = (k, cb) => { - if (!_watchers.has(k)) _watchers.set(k, new Set()); - _watchers.get(k).delete(cb); + if (_watchers.has(k)) _watchers.get(k).delete(cb); }; - const _getter = getter || (k => _defaults[k]); - const _setter = setter || ((k, v) => _defaults[k] = v); + + const __getter = getter || (k => _defaults[k]); + const __setter = setter || ((k, v) => _defaults[k] = v); Object.assign(_defaults, defaults); - + return new Proxy(_defaults, { get(target, key) { if (key === '__watch') return _watchFunc; if (key === '__unwatch') return _unwatchFunc; + if (key === '__isProxy') return true; + if (_activeBinding) { if (!_stateMappings.has(key)) _stateMappings.set(key, new Set()); - const bindingSet = _stateMappings.get(key); - bindingSet.add(_activeBinding); - if (!_activeBinding._sets) _activeBinding._sets = new Set(); - _activeBinding._sets.add(bindingSet); + _stateMappings.get(key).add(_activeBinding); + if (!_activeBinding.node._states) _activeBinding.node._states = new Set(); + _activeBinding.node._states.add(_stateMappings); } - return _getter(key); + return __getter(key); }, set(target, key, value) { - if (_getter(key) !== value) { - _setter(key, value); + if (__getter(key) !== value) { + __setter(key, value); } if (_watchers.has(key)) { _watchers.get(key).forEach(cb => { @@ -61,21 +60,15 @@ export function NewState(defaults = {}, getter = null, setter = null) { _watchers.get(null).forEach(cb => cb(value)); } if (_stateMappings.has(key)) { - if (_updateDepth > MAX_UPDATE_DEPTH) return console.error('Recursive update detected at key:', key), true; - _updateDepth++; - try { - const bindings = _stateMappings.get(key); - for (const binding of bindings) { - if (!binding.node.isConnected) { - bindings.delete(binding); - continue; - } - if (_noWriteBack !== binding.node && _updateBindingFn) { - _updateBindingFn(binding); - } + const bindings = _stateMappings.get(key); + for (const binding of bindings) { + if (!binding.node.isConnected) { + bindings.delete(binding); + continue; + } + if (_noWriteBack !== binding.node) { + _notifiers.forEach(fn => fn(binding)); } - } finally { - _updateDepth--; } } return true; diff --git a/test-results/.last-run.json b/test-results/.last-run.json index cbcc1fb..12c3f04 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,4 +1,6 @@ { - "status": "passed", - "failedTests": [] + "status": "failed", + "failedTests": [ + "8a84b43f13b676ea22b7-5fcda25ae3a58304c071" + ] } \ No newline at end of file diff --git a/test-results/all-modular-unit-tests-and-benchmark/error-context.md b/test-results/all-modular-unit-tests-and-benchmark/error-context.md new file mode 100644 index 0000000..70c813d --- /dev/null +++ b/test-results/all-modular-unit-tests-and-benchmark/error-context.md @@ -0,0 +1,1095 @@ +# Instructions + +- Following Playwright test failed. +- Explain why, be concise, respect Playwright best practices. +- Provide a snippet of code with the fix, if possible. + +# Test info + +- Name: all.spec.js >> modular unit tests and benchmark +- Location: test/all.spec.js:5:1 + +# Error details + +``` +Error: expect(received).toBeLessThan(expected) + +Expected: < 70.92 +Received: 119.90000009536743 +``` + +# Page snapshot + +```yaml +- list [ref=e2]: + - listitem [ref=e3]: item 0 + - listitem [ref=e4]: item 1 + - listitem [ref=e5]: item 2 + - listitem [ref=e6]: item 3 + - listitem [ref=e7]: item 4 + - listitem [ref=e8]: item 5 + - listitem [ref=e9]: item 6 + - listitem [ref=e10]: item 7 + - listitem [ref=e11]: item 8 + - listitem [ref=e12]: item 9 + - listitem [ref=e13]: item 10 + - listitem [ref=e14]: item 11 + - listitem [ref=e15]: item 12 + - listitem [ref=e16]: item 13 + - listitem [ref=e17]: item 14 + - listitem [ref=e18]: item 15 + - listitem [ref=e19]: item 16 + - listitem [ref=e20]: item 17 + - listitem [ref=e21]: item 18 + - listitem [ref=e22]: item 19 + - listitem [ref=e23]: item 20 + - listitem [ref=e24]: item 21 + - listitem [ref=e25]: item 22 + - listitem [ref=e26]: item 23 + - listitem [ref=e27]: item 24 + - listitem [ref=e28]: item 25 + - listitem [ref=e29]: item 26 + - listitem [ref=e30]: item 27 + - listitem [ref=e31]: item 28 + - listitem [ref=e32]: item 29 + - listitem [ref=e33]: item 30 + - listitem [ref=e34]: item 31 + - listitem [ref=e35]: item 32 + - listitem [ref=e36]: item 33 + - listitem [ref=e37]: item 34 + - listitem [ref=e38]: item 35 + - listitem [ref=e39]: item 36 + - listitem [ref=e40]: item 37 + - listitem [ref=e41]: item 38 + - listitem [ref=e42]: item 39 + - listitem [ref=e43]: item 40 + - listitem [ref=e44]: item 41 + - listitem [ref=e45]: item 42 + - listitem [ref=e46]: item 43 + - listitem [ref=e47]: item 44 + - listitem [ref=e48]: item 45 + - listitem [ref=e49]: item 46 + - listitem [ref=e50]: item 47 + - listitem [ref=e51]: item 48 + - listitem [ref=e52]: item 49 + - listitem [ref=e53]: item 50 + - listitem [ref=e54]: item 51 + - listitem [ref=e55]: item 52 + - listitem [ref=e56]: item 53 + - listitem [ref=e57]: item 54 + - listitem [ref=e58]: item 55 + - listitem [ref=e59]: item 56 + - listitem [ref=e60]: item 57 + - listitem [ref=e61]: item 58 + - listitem [ref=e62]: item 59 + - listitem [ref=e63]: item 60 + - listitem [ref=e64]: item 61 + - listitem [ref=e65]: item 62 + - listitem [ref=e66]: item 63 + - listitem [ref=e67]: item 64 + - listitem [ref=e68]: item 65 + - listitem [ref=e69]: item 66 + - listitem [ref=e70]: item 67 + - listitem [ref=e71]: item 68 + - listitem [ref=e72]: item 69 + - listitem [ref=e73]: item 70 + - listitem [ref=e74]: item 71 + - listitem [ref=e75]: item 72 + - listitem [ref=e76]: item 73 + - listitem [ref=e77]: item 74 + - listitem [ref=e78]: item 75 + - listitem [ref=e79]: item 76 + - listitem [ref=e80]: item 77 + - listitem [ref=e81]: item 78 + - listitem [ref=e82]: item 79 + - listitem [ref=e83]: item 80 + - listitem [ref=e84]: item 81 + - listitem [ref=e85]: item 82 + - listitem [ref=e86]: item 83 + - listitem [ref=e87]: item 84 + - listitem [ref=e88]: item 85 + - listitem [ref=e89]: item 86 + - listitem [ref=e90]: item 87 + - listitem [ref=e91]: item 88 + - listitem [ref=e92]: item 89 + - listitem [ref=e93]: item 90 + - listitem [ref=e94]: item 91 + - listitem [ref=e95]: item 92 + - listitem [ref=e96]: item 93 + - listitem [ref=e97]: item 94 + - listitem [ref=e98]: item 95 + - listitem [ref=e99]: item 96 + - listitem [ref=e100]: item 97 + - listitem [ref=e101]: item 98 + - listitem [ref=e102]: item 99 + - listitem [ref=e103]: item 100 + - listitem [ref=e104]: item 101 + - listitem [ref=e105]: item 102 + - listitem [ref=e106]: item 103 + - listitem [ref=e107]: item 104 + - listitem [ref=e108]: item 105 + - listitem [ref=e109]: item 106 + - listitem [ref=e110]: item 107 + - listitem [ref=e111]: item 108 + - listitem [ref=e112]: item 109 + - listitem [ref=e113]: item 110 + - listitem [ref=e114]: item 111 + - listitem [ref=e115]: item 112 + - listitem [ref=e116]: item 113 + - listitem [ref=e117]: item 114 + - listitem [ref=e118]: item 115 + - listitem [ref=e119]: item 116 + - listitem [ref=e120]: item 117 + - listitem [ref=e121]: item 118 + - listitem [ref=e122]: item 119 + - listitem [ref=e123]: item 120 + - listitem [ref=e124]: item 121 + - listitem [ref=e125]: item 122 + - listitem [ref=e126]: item 123 + - listitem [ref=e127]: item 124 + - listitem [ref=e128]: item 125 + - listitem [ref=e129]: item 126 + - listitem [ref=e130]: item 127 + - listitem [ref=e131]: item 128 + - listitem [ref=e132]: item 129 + - listitem [ref=e133]: item 130 + - listitem [ref=e134]: item 131 + - listitem [ref=e135]: item 132 + - listitem [ref=e136]: item 133 + - listitem [ref=e137]: item 134 + - listitem [ref=e138]: item 135 + - listitem [ref=e139]: item 136 + - listitem [ref=e140]: item 137 + - listitem [ref=e141]: item 138 + - listitem [ref=e142]: item 139 + - listitem [ref=e143]: item 140 + - listitem [ref=e144]: item 141 + - listitem [ref=e145]: item 142 + - listitem [ref=e146]: item 143 + - listitem [ref=e147]: item 144 + - listitem [ref=e148]: item 145 + - listitem [ref=e149]: item 146 + - listitem [ref=e150]: item 147 + - listitem [ref=e151]: item 148 + - listitem [ref=e152]: item 149 + - listitem [ref=e153]: item 150 + - listitem [ref=e154]: item 151 + - listitem [ref=e155]: item 152 + - listitem [ref=e156]: item 153 + - listitem [ref=e157]: item 154 + - listitem [ref=e158]: item 155 + - listitem [ref=e159]: item 156 + - listitem [ref=e160]: item 157 + - listitem [ref=e161]: item 158 + - listitem [ref=e162]: item 159 + - listitem [ref=e163]: item 160 + - listitem [ref=e164]: item 161 + - listitem [ref=e165]: item 162 + - listitem [ref=e166]: item 163 + - listitem [ref=e167]: item 164 + - listitem [ref=e168]: item 165 + - listitem [ref=e169]: item 166 + - listitem [ref=e170]: item 167 + - listitem [ref=e171]: item 168 + - listitem [ref=e172]: item 169 + - listitem [ref=e173]: item 170 + - listitem [ref=e174]: item 171 + - listitem [ref=e175]: item 172 + - listitem [ref=e176]: item 173 + - listitem [ref=e177]: item 174 + - listitem [ref=e178]: item 175 + - listitem [ref=e179]: item 176 + - listitem [ref=e180]: item 177 + - listitem [ref=e181]: item 178 + - listitem [ref=e182]: item 179 + - listitem [ref=e183]: item 180 + - listitem [ref=e184]: item 181 + - listitem [ref=e185]: item 182 + - listitem [ref=e186]: item 183 + - listitem [ref=e187]: item 184 + - listitem [ref=e188]: item 185 + - listitem [ref=e189]: item 186 + - listitem [ref=e190]: item 187 + - listitem [ref=e191]: item 188 + - listitem [ref=e192]: item 189 + - listitem [ref=e193]: item 190 + - listitem [ref=e194]: item 191 + - listitem [ref=e195]: item 192 + - listitem [ref=e196]: item 193 + - listitem [ref=e197]: item 194 + - listitem [ref=e198]: item 195 + - listitem [ref=e199]: item 196 + - listitem [ref=e200]: item 197 + - listitem [ref=e201]: item 198 + - listitem [ref=e202]: item 199 + - listitem [ref=e203]: item 200 + - listitem [ref=e204]: item 201 + - listitem [ref=e205]: item 202 + - listitem [ref=e206]: item 203 + - listitem [ref=e207]: item 204 + - listitem [ref=e208]: item 205 + - listitem [ref=e209]: item 206 + - listitem [ref=e210]: item 207 + - listitem [ref=e211]: item 208 + - listitem [ref=e212]: item 209 + - listitem [ref=e213]: item 210 + - listitem [ref=e214]: item 211 + - listitem [ref=e215]: item 212 + - listitem [ref=e216]: item 213 + - listitem [ref=e217]: item 214 + - listitem [ref=e218]: item 215 + - listitem [ref=e219]: item 216 + - listitem [ref=e220]: item 217 + - listitem [ref=e221]: item 218 + - listitem [ref=e222]: item 219 + - listitem [ref=e223]: item 220 + - listitem [ref=e224]: item 221 + - listitem [ref=e225]: item 222 + - listitem [ref=e226]: item 223 + - listitem [ref=e227]: item 224 + - listitem [ref=e228]: item 225 + - listitem [ref=e229]: item 226 + - listitem [ref=e230]: item 227 + - listitem [ref=e231]: item 228 + - listitem [ref=e232]: item 229 + - listitem [ref=e233]: item 230 + - listitem [ref=e234]: item 231 + - listitem [ref=e235]: item 232 + - listitem [ref=e236]: item 233 + - listitem [ref=e237]: item 234 + - listitem [ref=e238]: item 235 + - listitem [ref=e239]: item 236 + - listitem [ref=e240]: item 237 + - listitem [ref=e241]: item 238 + - listitem [ref=e242]: item 239 + - listitem [ref=e243]: item 240 + - listitem [ref=e244]: item 241 + - listitem [ref=e245]: item 242 + - listitem [ref=e246]: item 243 + - listitem [ref=e247]: item 244 + - listitem [ref=e248]: item 245 + - listitem [ref=e249]: item 246 + - listitem [ref=e250]: item 247 + - listitem [ref=e251]: item 248 + - listitem [ref=e252]: item 249 + - listitem [ref=e253]: item 250 + - listitem [ref=e254]: item 251 + - listitem [ref=e255]: item 252 + - listitem [ref=e256]: item 253 + - listitem [ref=e257]: item 254 + - listitem [ref=e258]: item 255 + - listitem [ref=e259]: item 256 + - listitem [ref=e260]: item 257 + - listitem [ref=e261]: item 258 + - listitem [ref=e262]: item 259 + - listitem [ref=e263]: item 260 + - listitem [ref=e264]: item 261 + - listitem [ref=e265]: item 262 + - listitem [ref=e266]: item 263 + - listitem [ref=e267]: item 264 + - listitem [ref=e268]: item 265 + - listitem [ref=e269]: item 266 + - listitem [ref=e270]: item 267 + - listitem [ref=e271]: item 268 + - listitem [ref=e272]: item 269 + - listitem [ref=e273]: item 270 + - listitem [ref=e274]: item 271 + - listitem [ref=e275]: item 272 + - listitem [ref=e276]: item 273 + - listitem [ref=e277]: item 274 + - listitem [ref=e278]: item 275 + - listitem [ref=e279]: item 276 + - listitem [ref=e280]: item 277 + - listitem [ref=e281]: item 278 + - listitem [ref=e282]: item 279 + - listitem [ref=e283]: item 280 + - listitem [ref=e284]: item 281 + - listitem [ref=e285]: item 282 + - listitem [ref=e286]: item 283 + - listitem [ref=e287]: item 284 + - listitem [ref=e288]: item 285 + - listitem [ref=e289]: item 286 + - listitem [ref=e290]: item 287 + - listitem [ref=e291]: item 288 + - listitem [ref=e292]: item 289 + - listitem [ref=e293]: item 290 + - listitem [ref=e294]: item 291 + - listitem [ref=e295]: item 292 + - listitem [ref=e296]: item 293 + - listitem [ref=e297]: item 294 + - listitem [ref=e298]: item 295 + - listitem [ref=e299]: item 296 + - listitem [ref=e300]: item 297 + - listitem [ref=e301]: item 298 + - listitem [ref=e302]: item 299 + - listitem [ref=e303]: item 300 + - listitem [ref=e304]: item 301 + - listitem [ref=e305]: item 302 + - listitem [ref=e306]: item 303 + - listitem [ref=e307]: item 304 + - listitem [ref=e308]: item 305 + - listitem [ref=e309]: item 306 + - listitem [ref=e310]: item 307 + - listitem [ref=e311]: item 308 + - listitem [ref=e312]: item 309 + - listitem [ref=e313]: item 310 + - listitem [ref=e314]: item 311 + - listitem [ref=e315]: item 312 + - listitem [ref=e316]: item 313 + - listitem [ref=e317]: item 314 + - listitem [ref=e318]: item 315 + - listitem [ref=e319]: item 316 + - listitem [ref=e320]: item 317 + - listitem [ref=e321]: item 318 + - listitem [ref=e322]: item 319 + - listitem [ref=e323]: item 320 + - listitem [ref=e324]: item 321 + - listitem [ref=e325]: item 322 + - listitem [ref=e326]: item 323 + - listitem [ref=e327]: item 324 + - listitem [ref=e328]: item 325 + - listitem [ref=e329]: item 326 + - listitem [ref=e330]: item 327 + - listitem [ref=e331]: item 328 + - listitem [ref=e332]: item 329 + - listitem [ref=e333]: item 330 + - listitem [ref=e334]: item 331 + - listitem [ref=e335]: item 332 + - listitem [ref=e336]: item 333 + - listitem [ref=e337]: item 334 + - listitem [ref=e338]: item 335 + - listitem [ref=e339]: item 336 + - listitem [ref=e340]: item 337 + - listitem [ref=e341]: item 338 + - listitem [ref=e342]: item 339 + - listitem [ref=e343]: item 340 + - listitem [ref=e344]: item 341 + - listitem [ref=e345]: item 342 + - listitem [ref=e346]: item 343 + - listitem [ref=e347]: item 344 + - listitem [ref=e348]: item 345 + - listitem [ref=e349]: item 346 + - listitem [ref=e350]: item 347 + - listitem [ref=e351]: item 348 + - listitem [ref=e352]: item 349 + - listitem [ref=e353]: item 350 + - listitem [ref=e354]: item 351 + - listitem [ref=e355]: item 352 + - listitem [ref=e356]: item 353 + - listitem [ref=e357]: item 354 + - listitem [ref=e358]: item 355 + - listitem [ref=e359]: item 356 + - listitem [ref=e360]: item 357 + - listitem [ref=e361]: item 358 + - listitem [ref=e362]: item 359 + - listitem [ref=e363]: item 360 + - listitem [ref=e364]: item 361 + - listitem [ref=e365]: item 362 + - listitem [ref=e366]: item 363 + - listitem [ref=e367]: item 364 + - listitem [ref=e368]: item 365 + - listitem [ref=e369]: item 366 + - listitem [ref=e370]: item 367 + - listitem [ref=e371]: item 368 + - listitem [ref=e372]: item 369 + - listitem [ref=e373]: item 370 + - listitem [ref=e374]: item 371 + - listitem [ref=e375]: item 372 + - listitem [ref=e376]: item 373 + - listitem [ref=e377]: item 374 + - listitem [ref=e378]: item 375 + - listitem [ref=e379]: item 376 + - listitem [ref=e380]: item 377 + - listitem [ref=e381]: item 378 + - listitem [ref=e382]: item 379 + - listitem [ref=e383]: item 380 + - listitem [ref=e384]: item 381 + - listitem [ref=e385]: item 382 + - listitem [ref=e386]: item 383 + - listitem [ref=e387]: item 384 + - listitem [ref=e388]: item 385 + - listitem [ref=e389]: item 386 + - listitem [ref=e390]: item 387 + - listitem [ref=e391]: item 388 + - listitem [ref=e392]: item 389 + - listitem [ref=e393]: item 390 + - listitem [ref=e394]: item 391 + - listitem [ref=e395]: item 392 + - listitem [ref=e396]: item 393 + - listitem [ref=e397]: item 394 + - listitem [ref=e398]: item 395 + - listitem [ref=e399]: item 396 + - listitem [ref=e400]: item 397 + - listitem [ref=e401]: item 398 + - listitem [ref=e402]: item 399 + - listitem [ref=e403]: item 400 + - listitem [ref=e404]: item 401 + - listitem [ref=e405]: item 402 + - listitem [ref=e406]: item 403 + - listitem [ref=e407]: item 404 + - listitem [ref=e408]: item 405 + - listitem [ref=e409]: item 406 + - listitem [ref=e410]: item 407 + - listitem [ref=e411]: item 408 + - listitem [ref=e412]: item 409 + - listitem [ref=e413]: item 410 + - listitem [ref=e414]: item 411 + - listitem [ref=e415]: item 412 + - listitem [ref=e416]: item 413 + - listitem [ref=e417]: item 414 + - listitem [ref=e418]: item 415 + - listitem [ref=e419]: item 416 + - listitem [ref=e420]: item 417 + - listitem [ref=e421]: item 418 + - listitem [ref=e422]: item 419 + - listitem [ref=e423]: item 420 + - listitem [ref=e424]: item 421 + - listitem [ref=e425]: item 422 + - listitem [ref=e426]: item 423 + - listitem [ref=e427]: item 424 + - listitem [ref=e428]: item 425 + - listitem [ref=e429]: item 426 + - listitem [ref=e430]: item 427 + - listitem [ref=e431]: item 428 + - listitem [ref=e432]: item 429 + - listitem [ref=e433]: item 430 + - listitem [ref=e434]: item 431 + - listitem [ref=e435]: item 432 + - listitem [ref=e436]: item 433 + - listitem [ref=e437]: item 434 + - listitem [ref=e438]: item 435 + - listitem [ref=e439]: item 436 + - listitem [ref=e440]: item 437 + - listitem [ref=e441]: item 438 + - listitem [ref=e442]: item 439 + - listitem [ref=e443]: item 440 + - listitem [ref=e444]: item 441 + - listitem [ref=e445]: item 442 + - listitem [ref=e446]: item 443 + - listitem [ref=e447]: item 444 + - listitem [ref=e448]: item 445 + - listitem [ref=e449]: item 446 + - listitem [ref=e450]: item 447 + - listitem [ref=e451]: item 448 + - listitem [ref=e452]: item 449 + - listitem [ref=e453]: item 450 + - listitem [ref=e454]: item 451 + - listitem [ref=e455]: item 452 + - listitem [ref=e456]: item 453 + - listitem [ref=e457]: item 454 + - listitem [ref=e458]: item 455 + - listitem [ref=e459]: item 456 + - listitem [ref=e460]: item 457 + - listitem [ref=e461]: item 458 + - listitem [ref=e462]: item 459 + - listitem [ref=e463]: item 460 + - listitem [ref=e464]: item 461 + - listitem [ref=e465]: item 462 + - listitem [ref=e466]: item 463 + - listitem [ref=e467]: item 464 + - listitem [ref=e468]: item 465 + - listitem [ref=e469]: item 466 + - listitem [ref=e470]: item 467 + - listitem [ref=e471]: item 468 + - listitem [ref=e472]: item 469 + - listitem [ref=e473]: item 470 + - listitem [ref=e474]: item 471 + - listitem [ref=e475]: item 472 + - listitem [ref=e476]: item 473 + - listitem [ref=e477]: item 474 + - listitem [ref=e478]: item 475 + - listitem [ref=e479]: item 476 + - listitem [ref=e480]: item 477 + - listitem [ref=e481]: item 478 + - listitem [ref=e482]: item 479 + - listitem [ref=e483]: item 480 + - listitem [ref=e484]: item 481 + - listitem [ref=e485]: item 482 + - listitem [ref=e486]: item 483 + - listitem [ref=e487]: item 484 + - listitem [ref=e488]: item 485 + - listitem [ref=e489]: item 486 + - listitem [ref=e490]: item 487 + - listitem [ref=e491]: item 488 + - listitem [ref=e492]: item 489 + - listitem [ref=e493]: item 490 + - listitem [ref=e494]: item 491 + - listitem [ref=e495]: item 492 + - listitem [ref=e496]: item 493 + - listitem [ref=e497]: item 494 + - listitem [ref=e498]: item 495 + - listitem [ref=e499]: item 496 + - listitem [ref=e500]: item 497 + - listitem [ref=e501]: item 498 + - listitem [ref=e502]: item 499 + - listitem [ref=e503]: item 500 + - listitem [ref=e504]: item 501 + - listitem [ref=e505]: item 502 + - listitem [ref=e506]: item 503 + - listitem [ref=e507]: item 504 + - listitem [ref=e508]: item 505 + - listitem [ref=e509]: item 506 + - listitem [ref=e510]: item 507 + - listitem [ref=e511]: item 508 + - listitem [ref=e512]: item 509 + - listitem [ref=e513]: item 510 + - listitem [ref=e514]: item 511 + - listitem [ref=e515]: item 512 + - listitem [ref=e516]: item 513 + - listitem [ref=e517]: item 514 + - listitem [ref=e518]: item 515 + - listitem [ref=e519]: item 516 + - listitem [ref=e520]: item 517 + - listitem [ref=e521]: item 518 + - listitem [ref=e522]: item 519 + - listitem [ref=e523]: item 520 + - listitem [ref=e524]: item 521 + - listitem [ref=e525]: item 522 + - listitem [ref=e526]: item 523 + - listitem [ref=e527]: item 524 + - listitem [ref=e528]: item 525 + - listitem [ref=e529]: item 526 + - listitem [ref=e530]: item 527 + - listitem [ref=e531]: item 528 + - listitem [ref=e532]: item 529 + - listitem [ref=e533]: item 530 + - listitem [ref=e534]: item 531 + - listitem [ref=e535]: item 532 + - listitem [ref=e536]: item 533 + - listitem [ref=e537]: item 534 + - listitem [ref=e538]: item 535 + - listitem [ref=e539]: item 536 + - listitem [ref=e540]: item 537 + - listitem [ref=e541]: item 538 + - listitem [ref=e542]: item 539 + - listitem [ref=e543]: item 540 + - listitem [ref=e544]: item 541 + - listitem [ref=e545]: item 542 + - listitem [ref=e546]: item 543 + - listitem [ref=e547]: item 544 + - listitem [ref=e548]: item 545 + - listitem [ref=e549]: item 546 + - listitem [ref=e550]: item 547 + - listitem [ref=e551]: item 548 + - listitem [ref=e552]: item 549 + - listitem [ref=e553]: item 550 + - listitem [ref=e554]: item 551 + - listitem [ref=e555]: item 552 + - listitem [ref=e556]: item 553 + - listitem [ref=e557]: item 554 + - listitem [ref=e558]: item 555 + - listitem [ref=e559]: item 556 + - listitem [ref=e560]: item 557 + - listitem [ref=e561]: item 558 + - listitem [ref=e562]: item 559 + - listitem [ref=e563]: item 560 + - listitem [ref=e564]: item 561 + - listitem [ref=e565]: item 562 + - listitem [ref=e566]: item 563 + - listitem [ref=e567]: item 564 + - listitem [ref=e568]: item 565 + - listitem [ref=e569]: item 566 + - listitem [ref=e570]: item 567 + - listitem [ref=e571]: item 568 + - listitem [ref=e572]: item 569 + - listitem [ref=e573]: item 570 + - listitem [ref=e574]: item 571 + - listitem [ref=e575]: item 572 + - listitem [ref=e576]: item 573 + - listitem [ref=e577]: item 574 + - listitem [ref=e578]: item 575 + - listitem [ref=e579]: item 576 + - listitem [ref=e580]: item 577 + - listitem [ref=e581]: item 578 + - listitem [ref=e582]: item 579 + - listitem [ref=e583]: item 580 + - listitem [ref=e584]: item 581 + - listitem [ref=e585]: item 582 + - listitem [ref=e586]: item 583 + - listitem [ref=e587]: item 584 + - listitem [ref=e588]: item 585 + - listitem [ref=e589]: item 586 + - listitem [ref=e590]: item 587 + - listitem [ref=e591]: item 588 + - listitem [ref=e592]: item 589 + - listitem [ref=e593]: item 590 + - listitem [ref=e594]: item 591 + - listitem [ref=e595]: item 592 + - listitem [ref=e596]: item 593 + - listitem [ref=e597]: item 594 + - listitem [ref=e598]: item 595 + - listitem [ref=e599]: item 596 + - listitem [ref=e600]: item 597 + - listitem [ref=e601]: item 598 + - listitem [ref=e602]: item 599 + - listitem [ref=e603]: item 600 + - listitem [ref=e604]: item 601 + - listitem [ref=e605]: item 602 + - listitem [ref=e606]: item 603 + - listitem [ref=e607]: item 604 + - listitem [ref=e608]: item 605 + - listitem [ref=e609]: item 606 + - listitem [ref=e610]: item 607 + - listitem [ref=e611]: item 608 + - listitem [ref=e612]: item 609 + - listitem [ref=e613]: item 610 + - listitem [ref=e614]: item 611 + - listitem [ref=e615]: item 612 + - listitem [ref=e616]: item 613 + - listitem [ref=e617]: item 614 + - listitem [ref=e618]: item 615 + - listitem [ref=e619]: item 616 + - listitem [ref=e620]: item 617 + - listitem [ref=e621]: item 618 + - listitem [ref=e622]: item 619 + - listitem [ref=e623]: item 620 + - listitem [ref=e624]: item 621 + - listitem [ref=e625]: item 622 + - listitem [ref=e626]: item 623 + - listitem [ref=e627]: item 624 + - listitem [ref=e628]: item 625 + - listitem [ref=e629]: item 626 + - listitem [ref=e630]: item 627 + - listitem [ref=e631]: item 628 + - listitem [ref=e632]: item 629 + - listitem [ref=e633]: item 630 + - listitem [ref=e634]: item 631 + - listitem [ref=e635]: item 632 + - listitem [ref=e636]: item 633 + - listitem [ref=e637]: item 634 + - listitem [ref=e638]: item 635 + - listitem [ref=e639]: item 636 + - listitem [ref=e640]: item 637 + - listitem [ref=e641]: item 638 + - listitem [ref=e642]: item 639 + - listitem [ref=e643]: item 640 + - listitem [ref=e644]: item 641 + - listitem [ref=e645]: item 642 + - listitem [ref=e646]: item 643 + - listitem [ref=e647]: item 644 + - listitem [ref=e648]: item 645 + - listitem [ref=e649]: item 646 + - listitem [ref=e650]: item 647 + - listitem [ref=e651]: item 648 + - listitem [ref=e652]: item 649 + - listitem [ref=e653]: item 650 + - listitem [ref=e654]: item 651 + - listitem [ref=e655]: item 652 + - listitem [ref=e656]: item 653 + - listitem [ref=e657]: item 654 + - listitem [ref=e658]: item 655 + - listitem [ref=e659]: item 656 + - listitem [ref=e660]: item 657 + - listitem [ref=e661]: item 658 + - listitem [ref=e662]: item 659 + - listitem [ref=e663]: item 660 + - listitem [ref=e664]: item 661 + - listitem [ref=e665]: item 662 + - listitem [ref=e666]: item 663 + - listitem [ref=e667]: item 664 + - listitem [ref=e668]: item 665 + - listitem [ref=e669]: item 666 + - listitem [ref=e670]: item 667 + - listitem [ref=e671]: item 668 + - listitem [ref=e672]: item 669 + - listitem [ref=e673]: item 670 + - listitem [ref=e674]: item 671 + - listitem [ref=e675]: item 672 + - listitem [ref=e676]: item 673 + - listitem [ref=e677]: item 674 + - listitem [ref=e678]: item 675 + - listitem [ref=e679]: item 676 + - listitem [ref=e680]: item 677 + - listitem [ref=e681]: item 678 + - listitem [ref=e682]: item 679 + - listitem [ref=e683]: item 680 + - listitem [ref=e684]: item 681 + - listitem [ref=e685]: item 682 + - listitem [ref=e686]: item 683 + - listitem [ref=e687]: item 684 + - listitem [ref=e688]: item 685 + - listitem [ref=e689]: item 686 + - listitem [ref=e690]: item 687 + - listitem [ref=e691]: item 688 + - listitem [ref=e692]: item 689 + - listitem [ref=e693]: item 690 + - listitem [ref=e694]: item 691 + - listitem [ref=e695]: item 692 + - listitem [ref=e696]: item 693 + - listitem [ref=e697]: item 694 + - listitem [ref=e698]: item 695 + - listitem [ref=e699]: item 696 + - listitem [ref=e700]: item 697 + - listitem [ref=e701]: item 698 + - listitem [ref=e702]: item 699 + - listitem [ref=e703]: item 700 + - listitem [ref=e704]: item 701 + - listitem [ref=e705]: item 702 + - listitem [ref=e706]: item 703 + - listitem [ref=e707]: item 704 + - listitem [ref=e708]: item 705 + - listitem [ref=e709]: item 706 + - listitem [ref=e710]: item 707 + - listitem [ref=e711]: item 708 + - listitem [ref=e712]: item 709 + - listitem [ref=e713]: item 710 + - listitem [ref=e714]: item 711 + - listitem [ref=e715]: item 712 + - listitem [ref=e716]: item 713 + - listitem [ref=e717]: item 714 + - listitem [ref=e718]: item 715 + - listitem [ref=e719]: item 716 + - listitem [ref=e720]: item 717 + - listitem [ref=e721]: item 718 + - listitem [ref=e722]: item 719 + - listitem [ref=e723]: item 720 + - listitem [ref=e724]: item 721 + - listitem [ref=e725]: item 722 + - listitem [ref=e726]: item 723 + - listitem [ref=e727]: item 724 + - listitem [ref=e728]: item 725 + - listitem [ref=e729]: item 726 + - listitem [ref=e730]: item 727 + - listitem [ref=e731]: item 728 + - listitem [ref=e732]: item 729 + - listitem [ref=e733]: item 730 + - listitem [ref=e734]: item 731 + - listitem [ref=e735]: item 732 + - listitem [ref=e736]: item 733 + - listitem [ref=e737]: item 734 + - listitem [ref=e738]: item 735 + - listitem [ref=e739]: item 736 + - listitem [ref=e740]: item 737 + - listitem [ref=e741]: item 738 + - listitem [ref=e742]: item 739 + - listitem [ref=e743]: item 740 + - listitem [ref=e744]: item 741 + - listitem [ref=e745]: item 742 + - listitem [ref=e746]: item 743 + - listitem [ref=e747]: item 744 + - listitem [ref=e748]: item 745 + - listitem [ref=e749]: item 746 + - listitem [ref=e750]: item 747 + - listitem [ref=e751]: item 748 + - listitem [ref=e752]: item 749 + - listitem [ref=e753]: item 750 + - listitem [ref=e754]: item 751 + - listitem [ref=e755]: item 752 + - listitem [ref=e756]: item 753 + - listitem [ref=e757]: item 754 + - listitem [ref=e758]: item 755 + - listitem [ref=e759]: item 756 + - listitem [ref=e760]: item 757 + - listitem [ref=e761]: item 758 + - listitem [ref=e762]: item 759 + - listitem [ref=e763]: item 760 + - listitem [ref=e764]: item 761 + - listitem [ref=e765]: item 762 + - listitem [ref=e766]: item 763 + - listitem [ref=e767]: item 764 + - listitem [ref=e768]: item 765 + - listitem [ref=e769]: item 766 + - listitem [ref=e770]: item 767 + - listitem [ref=e771]: item 768 + - listitem [ref=e772]: item 769 + - listitem [ref=e773]: item 770 + - listitem [ref=e774]: item 771 + - listitem [ref=e775]: item 772 + - listitem [ref=e776]: item 773 + - listitem [ref=e777]: item 774 + - listitem [ref=e778]: item 775 + - listitem [ref=e779]: item 776 + - listitem [ref=e780]: item 777 + - listitem [ref=e781]: item 778 + - listitem [ref=e782]: item 779 + - listitem [ref=e783]: item 780 + - listitem [ref=e784]: item 781 + - listitem [ref=e785]: item 782 + - listitem [ref=e786]: item 783 + - listitem [ref=e787]: item 784 + - listitem [ref=e788]: item 785 + - listitem [ref=e789]: item 786 + - listitem [ref=e790]: item 787 + - listitem [ref=e791]: item 788 + - listitem [ref=e792]: item 789 + - listitem [ref=e793]: item 790 + - listitem [ref=e794]: item 791 + - listitem [ref=e795]: item 792 + - listitem [ref=e796]: item 793 + - listitem [ref=e797]: item 794 + - listitem [ref=e798]: item 795 + - listitem [ref=e799]: item 796 + - listitem [ref=e800]: item 797 + - listitem [ref=e801]: item 798 + - listitem [ref=e802]: item 799 + - listitem [ref=e803]: item 800 + - listitem [ref=e804]: item 801 + - listitem [ref=e805]: item 802 + - listitem [ref=e806]: item 803 + - listitem [ref=e807]: item 804 + - listitem [ref=e808]: item 805 + - listitem [ref=e809]: item 806 + - listitem [ref=e810]: item 807 + - listitem [ref=e811]: item 808 + - listitem [ref=e812]: item 809 + - listitem [ref=e813]: item 810 + - listitem [ref=e814]: item 811 + - listitem [ref=e815]: item 812 + - listitem [ref=e816]: item 813 + - listitem [ref=e817]: item 814 + - listitem [ref=e818]: item 815 + - listitem [ref=e819]: item 816 + - listitem [ref=e820]: item 817 + - listitem [ref=e821]: item 818 + - listitem [ref=e822]: item 819 + - listitem [ref=e823]: item 820 + - listitem [ref=e824]: item 821 + - listitem [ref=e825]: item 822 + - listitem [ref=e826]: item 823 + - listitem [ref=e827]: item 824 + - listitem [ref=e828]: item 825 + - listitem [ref=e829]: item 826 + - listitem [ref=e830]: item 827 + - listitem [ref=e831]: item 828 + - listitem [ref=e832]: item 829 + - listitem [ref=e833]: item 830 + - listitem [ref=e834]: item 831 + - listitem [ref=e835]: item 832 + - listitem [ref=e836]: item 833 + - listitem [ref=e837]: item 834 + - listitem [ref=e838]: item 835 + - listitem [ref=e839]: item 836 + - listitem [ref=e840]: item 837 + - listitem [ref=e841]: item 838 + - listitem [ref=e842]: item 839 + - listitem [ref=e843]: item 840 + - listitem [ref=e844]: item 841 + - listitem [ref=e845]: item 842 + - listitem [ref=e846]: item 843 + - listitem [ref=e847]: item 844 + - listitem [ref=e848]: item 845 + - listitem [ref=e849]: item 846 + - listitem [ref=e850]: item 847 + - listitem [ref=e851]: item 848 + - listitem [ref=e852]: item 849 + - listitem [ref=e853]: item 850 + - listitem [ref=e854]: item 851 + - listitem [ref=e855]: item 852 + - listitem [ref=e856]: item 853 + - listitem [ref=e857]: item 854 + - listitem [ref=e858]: item 855 + - listitem [ref=e859]: item 856 + - listitem [ref=e860]: item 857 + - listitem [ref=e861]: item 858 + - listitem [ref=e862]: item 859 + - listitem [ref=e863]: item 860 + - listitem [ref=e864]: item 861 + - listitem [ref=e865]: item 862 + - listitem [ref=e866]: item 863 + - listitem [ref=e867]: item 864 + - listitem [ref=e868]: item 865 + - listitem [ref=e869]: item 866 + - listitem [ref=e870]: item 867 + - listitem [ref=e871]: item 868 + - listitem [ref=e872]: item 869 + - listitem [ref=e873]: item 870 + - listitem [ref=e874]: item 871 + - listitem [ref=e875]: item 872 + - listitem [ref=e876]: item 873 + - listitem [ref=e877]: item 874 + - listitem [ref=e878]: item 875 + - listitem [ref=e879]: item 876 + - listitem [ref=e880]: item 877 + - listitem [ref=e881]: item 878 + - listitem [ref=e882]: item 879 + - listitem [ref=e883]: item 880 + - listitem [ref=e884]: item 881 + - listitem [ref=e885]: item 882 + - listitem [ref=e886]: item 883 + - listitem [ref=e887]: item 884 + - listitem [ref=e888]: item 885 + - listitem [ref=e889]: item 886 + - listitem [ref=e890]: item 887 + - listitem [ref=e891]: item 888 + - listitem [ref=e892]: item 889 + - listitem [ref=e893]: item 890 + - listitem [ref=e894]: item 891 + - listitem [ref=e895]: item 892 + - listitem [ref=e896]: item 893 + - listitem [ref=e897]: item 894 + - listitem [ref=e898]: item 895 + - listitem [ref=e899]: item 896 + - listitem [ref=e900]: item 897 + - listitem [ref=e901]: item 898 + - listitem [ref=e902]: item 899 + - listitem [ref=e903]: item 900 + - listitem [ref=e904]: item 901 + - listitem [ref=e905]: item 902 + - listitem [ref=e906]: item 903 + - listitem [ref=e907]: item 904 + - listitem [ref=e908]: item 905 + - listitem [ref=e909]: item 906 + - listitem [ref=e910]: item 907 + - listitem [ref=e911]: item 908 + - listitem [ref=e912]: item 909 + - listitem [ref=e913]: item 910 + - listitem [ref=e914]: item 911 + - listitem [ref=e915]: item 912 + - listitem [ref=e916]: item 913 + - listitem [ref=e917]: item 914 + - listitem [ref=e918]: item 915 + - listitem [ref=e919]: item 916 + - listitem [ref=e920]: item 917 + - listitem [ref=e921]: item 918 + - listitem [ref=e922]: item 919 + - listitem [ref=e923]: item 920 + - listitem [ref=e924]: item 921 + - listitem [ref=e925]: item 922 + - listitem [ref=e926]: item 923 + - listitem [ref=e927]: item 924 + - listitem [ref=e928]: item 925 + - listitem [ref=e929]: item 926 + - listitem [ref=e930]: item 927 + - listitem [ref=e931]: item 928 + - listitem [ref=e932]: item 929 + - listitem [ref=e933]: item 930 + - listitem [ref=e934]: item 931 + - listitem [ref=e935]: item 932 + - listitem [ref=e936]: item 933 + - listitem [ref=e937]: item 934 + - listitem [ref=e938]: item 935 + - listitem [ref=e939]: item 936 + - listitem [ref=e940]: item 937 + - listitem [ref=e941]: item 938 + - listitem [ref=e942]: item 939 + - listitem [ref=e943]: item 940 + - listitem [ref=e944]: item 941 + - listitem [ref=e945]: item 942 + - listitem [ref=e946]: item 943 + - listitem [ref=e947]: item 944 + - listitem [ref=e948]: item 945 + - listitem [ref=e949]: item 946 + - listitem [ref=e950]: item 947 + - listitem [ref=e951]: item 948 + - listitem [ref=e952]: item 949 + - listitem [ref=e953]: item 950 + - listitem [ref=e954]: item 951 + - listitem [ref=e955]: item 952 + - listitem [ref=e956]: item 953 + - listitem [ref=e957]: item 954 + - listitem [ref=e958]: item 955 + - listitem [ref=e959]: item 956 + - listitem [ref=e960]: item 957 + - listitem [ref=e961]: item 958 + - listitem [ref=e962]: item 959 + - listitem [ref=e963]: item 960 + - listitem [ref=e964]: item 961 + - listitem [ref=e965]: item 962 + - listitem [ref=e966]: item 963 + - listitem [ref=e967]: item 964 + - listitem [ref=e968]: item 965 + - listitem [ref=e969]: item 966 + - listitem [ref=e970]: item 967 + - listitem [ref=e971]: item 968 + - listitem [ref=e972]: item 969 + - listitem [ref=e973]: item 970 + - listitem [ref=e974]: item 971 + - listitem [ref=e975]: item 972 + - listitem [ref=e976]: item 973 + - listitem [ref=e977]: item 974 + - listitem [ref=e978]: item 975 + - listitem [ref=e979]: item 976 + - listitem [ref=e980]: item 977 + - listitem [ref=e981]: item 978 + - listitem [ref=e982]: item 979 + - listitem [ref=e983]: item 980 + - listitem [ref=e984]: item 981 + - listitem [ref=e985]: item 982 + - listitem [ref=e986]: item 983 + - listitem [ref=e987]: item 984 + - listitem [ref=e988]: item 985 + - listitem [ref=e989]: item 986 + - listitem [ref=e990]: item 987 + - listitem [ref=e991]: item 988 + - listitem [ref=e992]: item 989 + - listitem [ref=e993]: item 990 + - listitem [ref=e994]: item 991 + - listitem [ref=e995]: item 992 + - listitem [ref=e996]: item 993 + - listitem [ref=e997]: item 994 + - listitem [ref=e998]: item 995 + - listitem [ref=e999]: item 996 + - listitem [ref=e1000]: item 997 + - listitem [ref=e1001]: item 998 + - listitem [ref=e1002]: item 999 +``` + +# Test source + +```ts + 1 | import { test, expect } from '@playwright/test'; + 2 | import fs from 'fs'; + 3 | import path from 'path'; + 4 | + 5 | test('modular unit tests and benchmark', async ({ page }) => { + 6 | page.on('console', msg => console.log('BROWSER LOG:', msg.text())); + 7 | await page.goto('http://localhost:8081/test/index.html'); + 8 | + 9 | await page.waitForFunction(() => window.testStatus !== undefined, { timeout: 10000 }); + 10 | const status = await page.evaluate(() => window.testStatus); + 11 | expect(status).toBe('passed'); + 12 | + 13 | // Read benchmarks from TEST.md + 14 | const testMd = fs.readFileSync(path.join(process.cwd(), 'TEST.md'), 'utf-8'); + 15 | const getBench = (name) => { + 16 | const match = testMd.match(new RegExp(`\\*\\*${name}\\*\\*\\s*\\|\\s*([\\d.]+)`)); + 17 | return match ? parseFloat(match[1]) : null; + 18 | }; + 19 | const baseInitial = getBench('首次渲染 \\(1000 items\\)'); + 20 | const baseUpdate = getBench('浅更新 \\(Shallow Update\\)'); + 21 | + 22 | // Benchmark: Large list rendering + 23 | const renderTime = await page.evaluate(async () => { + 24 | const start = performance.now(); + 25 | document.body.innerHTML = ` + 26 |
    + 27 | + 30 |
+ 31 | `; + 32 | const items = []; + 33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i}); + 34 | window.state.benchItems = items; + 35 | const { RefreshState } = await import('@web/state'); + 36 | RefreshState(document.documentElement); + 37 | return performance.now() - start; + 38 | }); + 39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`); +> 40 | if (baseInitial) expect(renderTime).toBeLessThan(baseInitial * 1.2); + | ^ Error: expect(received).toBeLessThan(expected) + 41 | + 42 | // Benchmark: Large list update + 43 | const updateTime = await page.evaluate(async () => { + 44 | const start = performance.now(); + 45 | window.state.benchItems[0].val = 'updated'; + 46 | window.state.benchItems = [...window.state.benchItems]; + 47 | return performance.now() - start; + 48 | }); + 49 | console.log(`BENCHMARK: 1000 items update (shallow): ${updateTime.toFixed(2)}ms`); + 50 | if (baseUpdate) expect(updateTime).toBeLessThan(baseUpdate * 1.2); + 51 | + 52 | // Extreme Data Test + 53 | await page.evaluate(async () => { + 54 | const { RefreshState } = await import('@web/state'); + 55 | document.body.innerHTML = '
'; + 56 | window.state.extreme = null; + 57 | RefreshState(document.getElementById('extreme')); + 58 | window.state.extreme = undefined; + 59 | window.state.extreme = { a: 1 }; + 60 | window.state.extreme = [1, 2]; + 61 | window.state.extreme = "not iterable"; + 62 | }); + 63 | }); + 64 | +``` \ No newline at end of file diff --git a/test/index.html b/test/index.html index 700424c..09aaee9 100644 --- a/test/index.html +++ b/test/index.html @@ -17,6 +17,8 @@ import { testObserver } from './observer.test.js'; import { testDom } from './dom.test.js'; import { testComponent } from './component.test.js'; + import { testPriority } from './priority.test.js'; + import { testMerging } from './merging.test.js'; async function runAll() { const results = document.getElementById('results'); @@ -25,6 +27,8 @@ await testObserver(); await testDom(); await testComponent(); + await testPriority(); + await testMerging(); results.innerHTML = '

All Tests Passed 🎉

'; window.testStatus = 'passed'; } catch (e) { diff --git a/test/merging.test.js b/test/merging.test.js new file mode 100644 index 0000000..709fcc7 --- /dev/null +++ b/test/merging.test.js @@ -0,0 +1,47 @@ +import { Component } from '../src/component.js'; +import { RefreshState, $ } from '../src/dom.js'; + +export async function testMerging() { + console.log('Testing complex class/style merging (DataTable Resizer scenario)...'); + + // 1. Resizer definition from @base + Component.register('RESIZER-REAL', container => { + container.isVertical = true; + }, document.createRange().createContextualFragment(` +
+ `).firstElementChild); + + // 2. Resizer usage from @dataTable + const testContainer = document.createElement('div'); + testContainer.id = 'merging-test-root'; + testContainer.innerHTML = ` +
+ + +
+ `; + document.body.appendChild(testContainer); + + RefreshState(testContainer); + + // Wait for bindings + await new Promise(r => setTimeout(r, 50)); + + const instance = $('#resizer-instance'); + const classes = Array.from(instance.classList); + console.log('Final ClassList:', classes); + console.log('Final Style:', instance.getAttribute('style')); + + // Verify static class merging + if (!classes.includes('ins-static')) throw new Error('Missing ins-static'); + if (!classes.includes('tpl-static')) throw new Error('Missing tpl-static'); + + // Verify style merging (Instance wins on dynamic $style, but both are preserved in logic) + const style = instance.getAttribute('style'); + if (!style.includes('color:blue')) throw new Error('Missing instance dynamic style'); + + console.log('Merging test passed'); + return true; +} diff --git a/test/priority.test.js b/test/priority.test.js new file mode 100644 index 0000000..3a79673 --- /dev/null +++ b/test/priority.test.js @@ -0,0 +1,48 @@ +import { Component } from '../src/component.js'; +import { RefreshState, $ } from '../src/dom.js'; + +export async function testPriority() { + console.log('Testing $.target priority...'); + + let capturedTarget = null; + Component.register('RESIZER', (container) => { + capturedTarget = container.target; + console.log('Component setup, target:', capturedTarget); + }); + + document.body.innerHTML = ` +
+ +
+ `; + + RefreshState(document.documentElement); + + const parent = $('#parent'); + + if (capturedTarget !== parent) { + throw new Error(`Priority issue: container.target was ${capturedTarget}, expected parent node`); + } + + // 2. Test this. replacement + let capturedVal = null; + Component.register('VAL-COMP', (container) => { + capturedVal = container.val; + console.log('Val component setup, val:', capturedVal); + }); + + const state = { outerVal: 'hello' }; + const outer = document.createElement('div'); + outer.id = 'outer'; + outer.innerHTML = ``; + document.body.appendChild(outer); + + RefreshState(outer, { thisObj: state }); + + if (capturedVal !== 'hello') { + throw new Error(`this. replacement failed: capturedVal was ${capturedVal}, expected 'hello'`); + } + + console.log('Priority tests passed'); + return true; +}