` 的形式显式向组件内注入对应节点。如果组件只有一个插槽位时调用代码可以直接写内容不需要使用模版来定义插槽。
-
-### 3. 容器与 DOM 就绪机制 (DOM Readiness)
-* **`container` 的本质**:在 `setupFunc` 中,传入的 `container` 参数**即为调用组件的 DOM 节点**。组件模板的根节点会合并到该节点变成同一个节点,但是调用组件时属性中的this指向上层组件,而组件内的this指向组件实例。
-* **同步就绪**:当进入 `setupFunc` 时,组件的 DOM 树(包括插槽内容)已经**完全初始化并挂载就绪**,可以在 `setupFunc` 中直接使用。
-
-### 4. 插槽 (Slot) 的渲染时序
-* 组件框架在进入 setupFunc 之前就已经完成了插槽内容(`
`)的初始化注入。因此,在 setupFunc 中直接使用插槽内容是完全安全且符合预期的。
-
-### 5. 高级状态管理与生命周期能力
-* **隐式节点变量 (`thisNode`)**:
- 在 `$each` 循环或属性指令中,可以直接使用隐式变量 `thisNode` 获取当前执行上下文绑定的 DOM 节点对象自身。
-
-## 四、 极简主义与高性能开发哲学
-
-1. **拒绝过度工程 (No Over-engineering)**:严禁使用复杂的树结构或大段防御性代码。
-2. **DOM 访问极小化**:优先通过 `this.state` 驱动 UI,避免在 JS 中手动调用 `element.style.xxx`。
-3. **闭包与内存的权衡**:优先将逻辑挂载到 `container` 或 `container.state` 上。
-4. **组件内聚**:能用指令解决的 UI 逻辑绝不写在 JS 里。
-5. **思维重塑**:彻底抛弃 Vue/React 的生命周期崇拜,回归 DOM 本质。
-6. **无分号运动**:除了必须的情况,代码结尾禁止使用分号 `;`。
-
-## 五、 开发范例
-
-### 范例 1:带插槽与 UI 的组件 (以 Modal 为例)
-
-```javascript
-Component.register('Modal', container => {
- container.modal = new bootstrap.Modal(container)
- container.addEventListener('bind', e => e.detail ? container.modal.show() : container.modal.hide())
- container.addEventListener('hide.bs.modal', () => {
- document.activeElement?.blur()
- container.dispatchEvent(new CustomEvent('change', { bubbles: false, detail: false }))
- })
- Util.copyFunction(container, container.modal, 'show', 'hide')
-}, Util.makeDom(/*html*/`
-
-`))
-```
-
-### 范例 2:无模板纯逻辑组件 (以 API 为例)
-
-```javascript
-Component.register('API', container => {
- container.request = NewState({ url: '', method: 'GET', headers: {}, data: null, timeout: 10000 })
- container.response = NewState({ loading: false, result: null })
- container.do = async (opt = {}) => {
- const req = { ...container.request, ...opt }
- container.response.loading = true
- const resp = await fetch(req.url, req)
- container.response.result = await resp.json()
- container.response.loading = false
- container.dispatchEvent(new CustomEvent('response', { detail: container.response }))
- }
- container.request.__watch(null, () => container.hasAttribute('auto') && container.request.url && container.do())
-})
-```
+1. **禁止滥用同步刷新**:`RefreshState()` 仅用于要求“同步等待 DOM 更新”的场景。知晓其会导致一次额外的渲染任务。
+2. **数据流向**:所有状态变更必须通过对 `NewState` 代理对象的赋值完成。
+3. **Key 的必要性**:在大规模数据(>100条)或复杂交互列表中,必须提供唯一 `key` 以激活节点复用逻辑。
diff --git a/dist/state.js b/dist/state.js
index 830a229..d69fccd 100644
--- a/dist/state.js
+++ b/dist/state.js
@@ -1,693 +1,719 @@
-var _a;
-let _activeBinding = null;
-let _noWriteBack = null;
-const setActiveBinding = (val) => _activeBinding = val;
-const setNoWriteBack = (val) => _noWriteBack = val;
-const _notifiers = /* @__PURE__ */ new Set();
-const onNotifyUpdate = (fn) => _notifiers.add(fn);
-function NewState(defaults = {}, getter = null, setter = null) {
- 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.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 (key === "__isProxy") return true;
- if (_activeBinding) {
- if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
- _stateMappings.get(key).add(_activeBinding);
- if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
- _activeBinding.node._states.add(_stateMappings);
- }
- 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 !== void 0) {
- 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) {
- _notifiers.forEach((fn) => fn(binding));
- }
- }
- }
- return true;
- }
- });
-}
-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);
- const fromContent = from.tagName === "TEMPLATE" ? from.content : from;
- const toContent = to.tagName === "TEMPLATE" ? to.content : to;
- Array.from(fromContent.childNodes).forEach((child) => toContent.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;
-}
-const _fnCache = /* @__PURE__ */ new Map();
-function _runCode(code, vars, thisObj, extendVars) {
- const allVars = { ...extendVars || {}, ...vars || {} };
- const argKeys = Object.keys(allVars);
- const argValues = Object.values(allVars);
- const cacheKey = code + argKeys.join(",");
- try {
- let fn = _fnCache.get(cacheKey);
- if (!fn) {
- fn = new Function("Hash", "LocalStorage", "State", ...argKeys, code);
- _fnCache.set(cacheKey, fn);
- }
- return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]);
- } catch (e) {
- if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]);
- return null;
- }
-}
-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 _translator = (text, args) => {
- if (!text || typeof text !== "string") return text;
- return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match);
-};
-const SetTranslator = (fn) => _translator = fn;
-const _translate = (text) => {
- if (!text || typeof text !== "string" || !text.includes("{#")) return text;
- return text.replace(/\{#(.+?)#\}/g, (m, content) => {
- const parts = content.split("||").map((s) => s.trim());
- const args = {};
- if (parts.length > 1) {
- const matches = parts[0].match(/\{(.+?)\}/g);
- if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || "");
- }
- return _translator(parts[0], args);
- });
-};
-if (typeof document !== "undefined") {
- try {
- document.createElement("div").setAttribute("$t", "1");
- } catch (e) {
- const originalSetAttribute = Element.prototype.setAttribute;
- Element.prototype.setAttribute = function(name, value) {
- if (!name.startsWith("$")) return originalSetAttribute.call(this, name, value);
- const prefix = name.startsWith("$$") ? "st-st-" : "st-";
- return originalSetAttribute.call(this, prefix + name.substring(name.startsWith("$$") ? 2 : 1), value);
+(function(global, factory) {
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ApigoState = {}));
+})(this, function(exports2) {
+ "use strict";
+ var _a;
+ let _activeBinding = null;
+ let _noWriteBack = null;
+ const setActiveBinding = (val) => _activeBinding = val;
+ const setNoWriteBack = (val) => _noWriteBack = val;
+ const _notifiers = /* @__PURE__ */ new Set();
+ const onNotifyUpdate = (fn) => _notifiers.add(fn);
+ function NewState(defaults = {}, getter = null, setter = null) {
+ 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);
};
- }
-}
-onNotifyUpdate((binding) => _updateBinding(binding));
-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;
- if (!node.isConnected && node.tagName !== "TEMPLATE") return;
- setActiveBinding(binding);
- if (window.__perfTrace) window.__perfTrace.evalCount++;
- const evalStart = window.__perfTrace ? performance.now() : 0;
- let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
- if (binding.exp === 2 && typeof result === "string") {
- try {
- result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null);
- } catch (e) {
- }
- }
- if (window.__perfTrace) window.__perfTrace.evalTotal += performance.now() - evalStart;
- setActiveBinding(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 lk = prop[prop.length - 1];
- if (lk) {
- if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
- const lo = o[lk];
- if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
- else o[lk] = result;
- } else if (typeof result === "object" && result != null && !Array.isArray(result)) {
- Object.assign(o, result);
- }
- }
- } else if (binding.attr) {
- const attr = binding.attr;
- if (attr === "if") {
- if (result) {
- if (!node._renderedNodes || node._renderedNodes.length === 0) {
- node._children.forEach((child) => {
- child._stManaged = true;
- node.parentNode.insertBefore(child, node);
- child._ref = { ...node._ref };
+ const _unwatchFunc = (k, cb) => {
+ if (_watchers.has(k)) _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 (key === "__isProxy") return true;
+ if (_activeBinding) {
+ if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
+ _stateMappings.get(key).add(_activeBinding);
+ if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
+ _activeBinding.node._states.add(_stateMappings);
+ }
+ 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 !== void 0) {
+ value = r;
+ target[key] = value;
+ }
});
- node._renderedNodes = [node._children];
- } else {
- node._renderedNodes[0].forEach((child) => _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref }));
}
- } else {
- _clearRenderedNodes(node);
- node._renderedNodes = [];
- }
- } else if (attr === "each") {
- if (result && typeof result === "object") {
- const asName = node.getAttribute("as") || "item";
- const indexName = node.getAttribute("index") || "index";
- const keyName = node.getAttribute("key");
- let keys, getVal;
- if (result instanceof Map) {
- keys = Array.from(result.keys());
- getVal = (k) => result.get(k);
- } else if (typeof result[Symbol.iterator] === "function") {
- const arr = Array.isArray(result) ? result : Array.from(result);
- keys = new Array(arr.length);
- for (let i = 0; i < arr.length; i++) keys[i] = i;
- getVal = (k) => arr[k];
- } else {
- keys = Object.keys(result);
- getVal = (k) => result[k];
+ if (_watchers.has(null)) {
+ _watchers.get(null).forEach((cb) => cb(value));
}
- if (!node._keyedNodes) node._keyedNodes = /* @__PURE__ */ new Map();
- const newKeyedNodes = /* @__PURE__ */ new Map();
- const currentRenderedNodes = [];
- keys.forEach((k, i) => {
- const item = getVal(k);
- const rawKey = keyName ? item && typeof item === "object" ? item[keyName] : item : k;
- const keyVal = rawKey === void 0 || rawKey === null || newKeyedNodes.has(rawKey) ? `st_key_fallback_${i}_${Math.random()}` : rawKey;
- let existingNodes = node._keyedNodes.get(keyVal);
- if (existingNodes) {
- node._keyedNodes.delete(keyVal);
- existingNodes.forEach((child) => {
- if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.reuseCount++;
- let scopeChanged = false;
- for (let key in node._ref) {
- if (key === asName || key === indexName) continue;
- if (child._ref[key] !== node._ref[key]) {
- child._ref[key] = node._ref[key];
- scopeChanged = true;
- }
- }
- const indexChanged = child._ref[indexName] !== k;
- if (indexChanged) child._ref[indexName] = k;
- if (child._ref[asName] !== item || scopeChanged) {
- child._ref[asName] = item;
- if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.scanCount++;
- _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref });
- } else if (node.parentNode.lastChild !== child) {
- if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.moveCount++;
- node.parentNode.insertBefore(child, node);
- }
- });
- } else {
- existingNodes = [];
- node._children.forEach((child) => {
- const cloned = child.cloneNode(true);
- cloned._stManaged = true;
- cloned._ref = { ...node._ref, [indexName]: k, [asName]: item };
- cloned._thisObj = node._thisObj;
- node.parentNode.insertBefore(cloned, node);
- existingNodes.push(cloned);
- });
+ 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) {
+ _notifiers.forEach((fn) => fn(binding));
+ }
}
- newKeyedNodes.set(keyVal, existingNodes);
- currentRenderedNodes.push(existingNodes);
- existingNodes.forEach((child) => node.parentNode.insertBefore(child, node));
- });
- node._keyedNodes.forEach((nodes) => nodes.forEach((child) => {
- _clearRenderedNodes(child);
- child.remove();
- }));
- node._keyedNodes = newKeyedNodes;
- node._renderedNodes = currentRenderedNodes;
- } else {
- _clearRenderedNodes(node);
- if (node._keyedNodes) node._keyedNodes.forEach((nodes) => nodes.forEach((child) => child.remove()));
- node._keyedNodes = /* @__PURE__ */ new Map();
- node._renderedNodes = [];
- }
- } else if (attr === "bind") {
- if (["INPUT", "SELECT", "TEXTAREA"].includes(node.tagName) && !node.hasAttribute("autocomplete")) node.setAttribute("autocomplete", "off");
- if (node.type === "checkbox") {
- if (node.value !== "on" && !result) {
- _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {});
- result = [];
}
- node._checkboxMultiMode = result instanceof Array;
- const isChecked = result instanceof Array ? result.includes(node.value) : !!result;
- if (node.checked !== isChecked) node.checked = isChecked;
- } else if (node.type === "radio") {
- if (node.checked !== (node.value === String(result ?? ""))) node.checked = node.value === String(result ?? "");
- } else if ("value" in node && node.type !== "file") {
- setTimeout(() => {
- 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 !== void 0) {
- 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 _parseNode = (node, scanObj) => {
- let hasBindings = false;
- if (node._bindings) {
- node._states = /* @__PURE__ */ new Set();
- node._bindings.forEach((b) => _updateBinding({ node, ...b }));
- if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
- hasBindings = true;
- }
- if (Component.exists(node.tagName) && !node._componentInitialized) {
- Array.from(node.attributes).forEach((attr) => {
- var _a2;
- if (attr.name.startsWith("$.")) {
- const realAttrName = attr.name.slice(2);
- let tpl = _translate(attr.value);
- if (tpl.includes("this.")) tpl = tpl.replace(/\bthis\./g, "this.parent.");
- const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {});
- let o = node;
- const prop = realAttrName.split(".");
- for (let i = 0; i < prop.length - 1; i++) {
- if (prop[i]) o = o[_a2 = prop[i]] ?? (o[_a2] = {});
- }
- o[prop[prop.length - 1]] = result;
- node.removeAttribute(attr.name);
+ return true;
}
});
- _makeComponent(node.tagName, node, scanObj);
- $$(node, "[slot-id]").forEach((p) => p.removeAttribute("slot-id"));
- node._componentInitialized = true;
- if (!node._thisObj) node._thisObj = node;
}
- if (node.tagName === "TEMPLATE") {
- node._children = [...node.content.childNodes];
- if (!node._renderedNodes) node._renderedNodes = [];
- }
- if (hasBindings) return;
- let attrs = [];
- const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
- if (node.tagName === "TEMPLATE") {
- triggerAttrs.forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
- } else {
- attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !triggerAttrs.includes(a.name) || a.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 = /* @__PURE__ */ new Set();
- attrs.forEach((attr) => {
- let exp = 0;
- if (attr.name.startsWith("$$") || attr.name.startsWith("st-st-")) exp = 2;
- else if (attr.name.startsWith("$") || attr.name.startsWith("st-")) exp = 1;
- const realAttrName = exp === 2 ? attr.name.startsWith("$$") ? attr.name.slice(2) : attr.name.slice(6) : exp === 1 ? attr.name.startsWith("$") ? attr.name.slice(1) : attr.name.slice(3) : attr.name;
- let tpl = attr.value;
- node.removeAttribute(attr.name);
- if (realAttrName.startsWith(".")) _initBinding({ node, prop: realAttrName.split("."), tpl, exp });
- else if (realAttrName.startsWith("on")) {
- const eventName = realAttrName.slice(2);
- if (eventName === "update") node._hasOnUpdate = true;
- if (eventName === "load" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true;
- if (eventName === "unload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true;
- node.addEventListener(eventName, (e) => _runCode(tpl, { event: e, thisNode: node, ...e.detail || {} }, scanObj.thisObj || node, node._ref || {}));
- } else {
- if (realAttrName === "bind") {
- 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;
- setNoWriteBack(node);
- setDisableRunCodeError(true);
- if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
- else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
- setDisableRunCodeError(false);
- setNoWriteBack(null);
- });
- } else if (realAttrName === "text" && !tpl) {
- tpl = node.textContent;
- node.textContent = "";
- }
- if (tpl) {
- tpl = _translate(tpl);
- _initBinding({ node, attr: realAttrName, tpl, exp });
+ const $ = (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;
}
- });
- if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event("load", { bubbles: false })));
- if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
- if (node._thisObj) scanObj.thisObj = node._thisObj;
-};
-const _scanTree = (node, scanObj = {}) => {
- if (node.nodeType === 3) {
- if (node._stTranslated) return;
- const translated = _translate(node.textContent);
- if (translated !== node.textContent) node.textContent = translated;
- node._stTranslated = true;
- return;
- }
- if (node.nodeType !== 1) return;
- if (!node._stTranslated) {
- Array.from(node.attributes).forEach((attr) => {
- if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
- const translated = _translate(attr.value);
- if (translated !== attr.value) attr.value = translated;
- }
- });
- node._stTranslated = true;
- }
- let resolvedThisObj = node._thisObj;
- let resolvedRef = node._ref;
- if (resolvedThisObj === void 0 || resolvedRef === void 0) {
- let curr = node;
- while (curr && (resolvedThisObj === void 0 || resolvedRef === void 0)) {
- if (resolvedThisObj === void 0 && curr._thisObj !== void 0) resolvedThisObj = curr._thisObj;
- if (resolvedRef === void 0 && curr._ref !== void 0) resolvedRef = { ...curr._ref };
- curr = curr.parentNode;
- }
- }
- if (resolvedThisObj === void 0) resolvedThisObj = scanObj.thisObj;
- if (resolvedRef === void 0) resolvedRef = scanObj.extendVars;
- const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
- const eachAttrs = ["$each", "st-each", "$$each", "st-st-each"];
- if (node.tagName !== "TEMPLATE" && triggerAttrs.some((t) => node.hasAttribute(t))) {
- const template = document.createElement("TEMPLATE");
- const attrs = Array.from(node.attributes).filter((attr) => triggerAttrs.includes(attr.name) || eachAttrs.some((t) => node.hasAttribute(t)) && ["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 = resolvedRef;
- template._thisObj = resolvedThisObj;
- _scanTree(template, scanObj);
- return;
- }
- if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if") || node.hasAttribute("$$if") || node.hasAttribute("st-st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each"))) {
- const template = document.createElement("TEMPLATE");
- const attrs = Array.from(node.attributes).filter((attr2) => triggerAttrs.includes(attr2.name));
- const attr = attrs[attrs.length - 1];
- template.setAttribute(attr.name, attr.value);
- node.removeAttribute(attr.name);
- if (eachAttrs.includes(attr.name)) {
- Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
- template.setAttribute(attr2.name, attr2.value);
- node.removeAttribute(attr2.name);
- });
- }
- Array.from(node.content.childNodes).forEach((child) => template.content.appendChild(child));
- node.content.appendChild(template);
- template._ref = resolvedRef;
- template._thisObj = resolvedThisObj;
- }
- if (node.tagName === "IMG" && (node.hasAttribute("src") || node.hasAttribute("_src") || node.hasAttribute("$src"))) {
- const imgNode = node;
- Promise.resolve().then(() => {
- const url = imgNode.getAttribute("_src") || imgNode.getAttribute("src");
- if (url) fetch(url, { cache: "force-cache" }).then((r) => r.text()).then((svgText) => {
- const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector("svg");
- if (realSvg) {
- Array.from(imgNode.attributes).forEach((attr) => realSvg.setAttribute(attr.name, attr.value));
- imgNode.replaceWith(realSvg);
+ };
+ 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);
+ const fromContent = from.tagName === "TEMPLATE" ? from.content : from;
+ const toContent = to.tagName === "TEMPLATE" ? to.content : to;
+ Array.from(fromContent.childNodes).forEach((child) => toContent.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");
+ }
});
- }
- if (node._thisObj !== void 0) scanObj.thisObj = node._thisObj || null;
- else {
- let curr = node;
- while (curr && curr._thisObj === void 0) curr = curr.parentNode;
- scanObj.thisObj = curr ? curr._thisObj : null;
- }
- if (node._ref === void 0) {
- let curr = node;
- while (curr && curr._ref === void 0) curr = curr.parentNode;
- node._ref = curr ? { ...curr._ref } : {};
- }
- if (node._refExt !== void 0) {
- Object.assign(node._ref, node._refExt);
- }
- if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
- _parseNode(node, { ...scanObj });
- const nodes = [...node.childNodes || []];
- const nextScanObj = { thisObj: scanObj.thisObj, extendVars: { ...node._ref } };
- nodes.forEach((child) => {
- if (!child._stManaged) _scanTree(child, nextScanObj);
- });
-};
-const _unbindTree = (node) => {
- if (node.nodeType !== 1) return;
- if (node._hasOnUnload) node.dispatchEvent(new Event("unload", { bubbles: false }));
- if (node._states) node._states.forEach((mappings) => {
- for (const [key, bindingSet] of mappings) {
- for (const binding of bindingSet) {
- if (binding.node === node) bindingSet.delete(binding);
+ node.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);
+ }
+ });
}
}
- });
- node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
-};
-const RefreshState = _scanTree;
-const Util = {
- clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
- base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
- unbase64: (str) => new TextDecoder().decode(Uint8Array.from(atob(str), (c) => c.charCodeAt(0))),
- urlbase64: (str) => Util.base64(str).replace(/[+/=]/g, (m) => ({ "+": "-", "/": "", "=": "" })[m]),
- unurlbase64: (str) => Util.unbase64(str.replace(/[-_.]/g, (m) => ({ "-": "+", "_": "/", ".": "=" })[m]).padEnd(Math.ceil(str.length / 4) * 4, "=")),
- safeJson: (str) => {
+ if (componentFunc) componentFunc(node);
+ }
+ let _disableRunCodeError = false;
+ function setDisableRunCodeError(value) {
+ _disableRunCodeError = value;
+ }
+ const _fnCache = /* @__PURE__ */ new Map();
+ function _runCode(code, vars, thisObj, extendVars) {
+ const allVars = { ...extendVars || {}, ...vars || {} };
+ const argKeys = Object.keys(allVars);
+ const argValues = Object.values(allVars);
+ const cacheKey = code + argKeys.join(",");
try {
- return JSON.parse(str);
- } catch {
+ let fn = _fnCache.get(cacheKey);
+ if (!fn) {
+ fn = new Function("Hash", "LocalStorage", "State", ...argKeys, code);
+ _fnCache.set(cacheKey, fn);
+ }
+ return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]);
+ } catch (e) {
+ if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]);
return null;
}
- },
- updateDefaults: (obj, defaults) => {
- for (const k in defaults) if (obj[k] === void 0) obj[k] = defaults[k];
- },
- copyFunction: (toObj, fromObj, ...funcNames) => {
- funcNames.forEach((name) => toObj[name] = fromObj[name].bind(fromObj));
- },
- getFunctionBody: (fn) => {
- const code = fn.toString();
- return code.slice(code.indexOf("{") + 1, code.lastIndexOf("}")).trim();
- },
- makeDom: (html) => {
- if (html.includes(">\n")) html = html.replace(/>\s+<").trim();
- const node = document.createElement("div");
- node.innerHTML = html;
- return node.children[0];
- },
- newAvg: () => {
- let total = 0, count = 0, avg = 0;
- return {
- add: (v) => {
- total += v;
- count++;
- return avg = total / count;
- },
- get: () => avg,
- clear: () => {
- total = 0, count = 0, avg = 0;
- }
- };
- },
- newTimeCount: () => {
- let startTime = 0, total = 0, count = 0;
- return {
- start: () => startTime = (/* @__PURE__ */ new Date()).getTime(),
- end: () => {
- const endTime = (/* @__PURE__ */ new Date()).getTime();
- const left = endTime - startTime;
- startTime = endTime;
- total += left;
- count++;
- return left;
- },
- avg: () => total / count
- };
}
-};
-globalThis.Util = Util;
-let _hashParams = new URLSearchParams(((_a = window.location.hash) == null ? void 0 : _a.substring(1)) || "");
-const Hash = NewState({}, (k) => Util.safeJson(_hashParams.get(k)), (k, v) => {
- const oldStr = _hashParams.get(k);
- const newStr = v === void 0 ? void 0 : JSON.stringify(v);
- if (oldStr === newStr || oldStr === null && newStr === void 0) return;
- v === void 0 ? _hashParams.delete(k) : _hashParams.set(k, newStr);
- window.location.hash = "#" + _hashParams.toString();
-});
-if (typeof window !== "undefined") {
- window.addEventListener("hashchange", () => {
- var _a2;
- const oldHashParams = _hashParams;
- _hashParams = new URLSearchParams(((_a2 = window.location.hash) == null ? void 0 : _a2.substring(1)) || "");
- _hashParams.forEach((v, k) => {
- if (oldHashParams.get(k) !== v) Hash[k] = Util.safeJson(v);
- });
- oldHashParams.forEach((v, k) => {
- if (_hashParams.get(k) === void 0) Hash[k] = void 0;
- });
- });
-}
-const LocalStorage = NewState({}, (k) => Util.safeJson(localStorage.getItem(k)), (k, v) => {
- const oldStr = localStorage.getItem(k);
- const newStr = v === void 0 ? void 0 : JSON.stringify(v);
- if (oldStr === newStr || oldStr === null && newStr === void 0) return;
- v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, newStr);
-});
-globalThis.Hash = Hash;
-globalThis.LocalStorage = LocalStorage;
-if (typeof document !== "undefined") {
- const init = () => {
- Component._initPending();
- 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);
+ 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 _translator = (text, args) => {
+ if (!text || typeof text !== "string") return text;
+ return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match);
};
- if (document.readyState !== "loading") init();
- else document.addEventListener("DOMContentLoaded", init, true);
-}
-export {
- $,
- $$,
- Component,
- Hash,
- LocalStorage,
- NewState,
- RefreshState,
- SetTranslator,
- Util,
- _scanTree,
- _unbindTree
-};
+ const SetTranslator = (fn) => _translator = fn;
+ const _translate = (text) => {
+ if (!text || typeof text !== "string" || !text.includes("{#")) return text;
+ return text.replace(/\{#(.+?)#\}/g, (m, content) => {
+ const parts = content.split("||").map((s) => s.trim());
+ const args = {};
+ if (parts.length > 1) {
+ const matches = parts[0].match(/\{(.+?)\}/g);
+ if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || "");
+ }
+ return _translator(parts[0], args);
+ });
+ };
+ if (typeof document !== "undefined") {
+ try {
+ document.createElement("div").setAttribute("$t", "1");
+ } catch (e) {
+ const originalSetAttribute = Element.prototype.setAttribute;
+ Element.prototype.setAttribute = function(name, value) {
+ if (!name.startsWith("$")) return originalSetAttribute.call(this, name, value);
+ const prefix = name.startsWith("$$") ? "st-st-" : "st-";
+ return originalSetAttribute.call(this, prefix + name.substring(name.startsWith("$$") ? 2 : 1), value);
+ };
+ }
+ }
+ onNotifyUpdate((binding) => _updateBinding(binding));
+ 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;
+ if (!node.isConnected && node.tagName !== "TEMPLATE") return;
+ setActiveBinding(binding);
+ if (window.__perfTrace) window.__perfTrace.evalCount++;
+ const evalStart = window.__perfTrace ? performance.now() : 0;
+ let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
+ if (binding.exp === 2 && typeof result === "string") {
+ try {
+ result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null);
+ } catch (e) {
+ }
+ }
+ if (window.__perfTrace) window.__perfTrace.evalTotal += performance.now() - evalStart;
+ setActiveBinding(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 lk = prop[prop.length - 1];
+ if (lk) {
+ if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
+ const lo = o[lk];
+ if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
+ else o[lk] = result;
+ } else if (typeof result === "object" && result != null && !Array.isArray(result)) {
+ Object.assign(o, result);
+ }
+ }
+ } else if (binding.attr) {
+ const attr = binding.attr;
+ if (attr === "if") {
+ if (result) {
+ if (!node._renderedNodes || node._renderedNodes.length === 0) {
+ node._children.forEach((child) => {
+ child._stManaged = true;
+ node.parentNode.insertBefore(child, node);
+ child._ref = { ...node._ref };
+ });
+ node._renderedNodes = [node._children];
+ } else {
+ node._renderedNodes[0].forEach((child) => _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref }));
+ }
+ } else {
+ _clearRenderedNodes(node);
+ node._renderedNodes = [];
+ }
+ } else if (attr === "each") {
+ if (result && typeof result === "object") {
+ const asName = node.getAttribute("as") || "item";
+ const indexName = node.getAttribute("index") || "index";
+ const keyName = node.getAttribute("key");
+ let keys, getVal;
+ if (result instanceof Map) {
+ keys = Array.from(result.keys());
+ getVal = (k) => result.get(k);
+ } else if (typeof result[Symbol.iterator] === "function") {
+ const arr = Array.isArray(result) ? result : Array.from(result);
+ keys = new Array(arr.length);
+ for (let i = 0; i < arr.length; i++) keys[i] = i;
+ getVal = (k) => arr[k];
+ } else {
+ keys = Object.keys(result);
+ getVal = (k) => result[k];
+ }
+ if (!node._keyedNodes) node._keyedNodes = /* @__PURE__ */ new Map();
+ const newKeyedNodes = /* @__PURE__ */ new Map();
+ const currentRenderedNodes = [];
+ keys.forEach((k, i) => {
+ const item = getVal(k);
+ const rawKey = keyName ? item && typeof item === "object" ? item[keyName] : item : k;
+ const keyVal = rawKey === void 0 || rawKey === null || newKeyedNodes.has(rawKey) ? `st_key_fallback_${i}_${Math.random()}` : rawKey;
+ let existingNodes = node._keyedNodes.get(keyVal);
+ if (existingNodes) {
+ node._keyedNodes.delete(keyVal);
+ existingNodes.forEach((child) => {
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.reuseCount++;
+ let scopeChanged = false;
+ for (let key in node._ref) {
+ if (key === asName || key === indexName) continue;
+ if (child._ref[key] !== node._ref[key]) {
+ child._ref[key] = node._ref[key];
+ scopeChanged = true;
+ }
+ }
+ const indexChanged = child._ref[indexName] !== k;
+ if (indexChanged) child._ref[indexName] = k;
+ if (child._ref[asName] !== item || scopeChanged) {
+ child._ref[asName] = item;
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.scanCount++;
+ _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref });
+ } else if (node.parentNode.lastChild !== child) {
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.moveCount++;
+ node.parentNode.insertBefore(child, node);
+ }
+ });
+ } else {
+ existingNodes = [];
+ node._children.forEach((child) => {
+ const cloned = child.cloneNode(true);
+ cloned._stManaged = true;
+ cloned._ref = { ...node._ref, [indexName]: k, [asName]: item };
+ cloned._thisObj = node._thisObj;
+ node.parentNode.insertBefore(cloned, node);
+ existingNodes.push(cloned);
+ });
+ }
+ newKeyedNodes.set(keyVal, existingNodes);
+ currentRenderedNodes.push(existingNodes);
+ existingNodes.forEach((child) => node.parentNode.insertBefore(child, node));
+ });
+ node._keyedNodes.forEach((nodes) => nodes.forEach((child) => {
+ _clearRenderedNodes(child);
+ child.remove();
+ }));
+ node._keyedNodes = newKeyedNodes;
+ node._renderedNodes = currentRenderedNodes;
+ } else {
+ _clearRenderedNodes(node);
+ if (node._keyedNodes) node._keyedNodes.forEach((nodes) => nodes.forEach((child) => child.remove()));
+ node._keyedNodes = /* @__PURE__ */ new Map();
+ node._renderedNodes = [];
+ }
+ } else if (attr === "bind") {
+ if (["INPUT", "SELECT", "TEXTAREA"].includes(node.tagName) && !node.hasAttribute("autocomplete")) node.setAttribute("autocomplete", "off");
+ if (node.type === "checkbox") {
+ if (node.value !== "on" && !result) {
+ _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {});
+ result = [];
+ }
+ node._checkboxMultiMode = result instanceof Array;
+ const isChecked = result instanceof Array ? result.includes(node.value) : !!result;
+ if (node.checked !== isChecked) node.checked = isChecked;
+ } else if (node.type === "radio") {
+ if (node.checked !== (node.value === String(result ?? ""))) node.checked = node.value === String(result ?? "");
+ } else if ("value" in node && node.type !== "file") {
+ setTimeout(() => {
+ 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 !== void 0) {
+ 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 _parseNode = (node, scanObj) => {
+ let hasBindings = false;
+ if (node._bindings) {
+ node._states = /* @__PURE__ */ new Set();
+ node._bindings.forEach((b) => _updateBinding({ node, ...b }));
+ if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
+ hasBindings = true;
+ }
+ if (Component.exists(node.tagName) && !node._componentInitialized) {
+ Array.from(node.attributes).forEach((attr) => {
+ var _a2;
+ if (attr.name.startsWith("$.")) {
+ const realAttrName = attr.name.slice(2);
+ let tpl = _translate(attr.value);
+ if (tpl.includes("this.")) tpl = tpl.replace(/\bthis\./g, "this.parent.");
+ const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {});
+ let o = node;
+ const prop = realAttrName.split(".");
+ for (let i = 0; i < prop.length - 1; i++) {
+ if (prop[i]) o = o[_a2 = prop[i]] ?? (o[_a2] = {});
+ }
+ o[prop[prop.length - 1]] = result;
+ node.removeAttribute(attr.name);
+ }
+ });
+ _makeComponent(node.tagName, node, scanObj);
+ $$(node, "[slot-id]").forEach((p) => p.removeAttribute("slot-id"));
+ node._componentInitialized = true;
+ if (!node._thisObj) node._thisObj = node;
+ }
+ if (node.tagName === "TEMPLATE") {
+ node._children = [...node.content.childNodes];
+ if (!node._renderedNodes) node._renderedNodes = [];
+ }
+ if (hasBindings) return;
+ let attrs = [];
+ const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
+ if (node.tagName === "TEMPLATE") {
+ triggerAttrs.forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
+ } else {
+ attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !triggerAttrs.includes(a.name) || a.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 = /* @__PURE__ */ new Set();
+ attrs.forEach((attr) => {
+ let exp = 0;
+ if (attr.name.startsWith("$$") || attr.name.startsWith("st-st-")) exp = 2;
+ else if (attr.name.startsWith("$") || attr.name.startsWith("st-")) exp = 1;
+ const realAttrName = exp === 2 ? attr.name.startsWith("$$") ? attr.name.slice(2) : attr.name.slice(6) : exp === 1 ? attr.name.startsWith("$") ? attr.name.slice(1) : attr.name.slice(3) : attr.name;
+ let tpl = attr.value;
+ node.removeAttribute(attr.name);
+ if (realAttrName.startsWith(".")) _initBinding({ node, prop: realAttrName.split("."), tpl, exp });
+ else if (realAttrName.startsWith("on")) {
+ const eventName = realAttrName.slice(2);
+ if (eventName === "update") node._hasOnUpdate = true;
+ if (eventName === "load" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true;
+ if (eventName === "unload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true;
+ node.addEventListener(eventName, (e) => _runCode(tpl, { event: e, thisNode: node, ...e.detail || {} }, scanObj.thisObj || node, node._ref || {}));
+ } else {
+ if (realAttrName === "bind") {
+ 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;
+ setNoWriteBack(node);
+ setDisableRunCodeError(true);
+ if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
+ else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
+ setDisableRunCodeError(false);
+ setNoWriteBack(null);
+ });
+ } else if (realAttrName === "text" && !tpl) {
+ tpl = node.textContent;
+ node.textContent = "";
+ }
+ if (tpl) {
+ tpl = _translate(tpl);
+ _initBinding({ node, attr: realAttrName, tpl, exp });
+ }
+ }
+ });
+ if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event("load", { bubbles: false })));
+ if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
+ if (node._thisObj) scanObj.thisObj = node._thisObj;
+ };
+ const _scanTree = (node, scanObj = {}) => {
+ if (node.nodeType === 3) {
+ if (node._stTranslated) return;
+ const translated = _translate(node.textContent);
+ if (translated !== node.textContent) node.textContent = translated;
+ node._stTranslated = true;
+ return;
+ }
+ if (node.nodeType !== 1) return;
+ if (!node._stTranslated) {
+ Array.from(node.attributes).forEach((attr) => {
+ if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
+ const translated = _translate(attr.value);
+ if (translated !== attr.value) attr.value = translated;
+ }
+ });
+ node._stTranslated = true;
+ }
+ let resolvedThisObj = node._thisObj;
+ let resolvedRef = node._ref;
+ if (resolvedThisObj === void 0 || resolvedRef === void 0) {
+ let curr = node;
+ while (curr && (resolvedThisObj === void 0 || resolvedRef === void 0)) {
+ if (resolvedThisObj === void 0 && curr._thisObj !== void 0) resolvedThisObj = curr._thisObj;
+ if (resolvedRef === void 0 && curr._ref !== void 0) resolvedRef = { ...curr._ref };
+ curr = curr.parentNode;
+ }
+ }
+ if (resolvedThisObj === void 0) resolvedThisObj = scanObj.thisObj;
+ if (resolvedRef === void 0) resolvedRef = scanObj.extendVars;
+ const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
+ const eachAttrs = ["$each", "st-each", "$$each", "st-st-each"];
+ if (node.tagName !== "TEMPLATE" && triggerAttrs.some((t) => node.hasAttribute(t))) {
+ const template = document.createElement("TEMPLATE");
+ const attrs = Array.from(node.attributes).filter((attr) => triggerAttrs.includes(attr.name) || eachAttrs.some((t) => node.hasAttribute(t)) && ["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 = resolvedRef;
+ template._thisObj = resolvedThisObj;
+ _scanTree(template, scanObj);
+ return;
+ }
+ if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if") || node.hasAttribute("$$if") || node.hasAttribute("st-st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each"))) {
+ const template = document.createElement("TEMPLATE");
+ const attrs = Array.from(node.attributes).filter((attr2) => triggerAttrs.includes(attr2.name));
+ const attr = attrs[attrs.length - 1];
+ template.setAttribute(attr.name, attr.value);
+ node.removeAttribute(attr.name);
+ if (eachAttrs.includes(attr.name)) {
+ Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
+ template.setAttribute(attr2.name, attr2.value);
+ node.removeAttribute(attr2.name);
+ });
+ }
+ Array.from(node.content.childNodes).forEach((child) => template.content.appendChild(child));
+ node.content.appendChild(template);
+ template._ref = resolvedRef;
+ template._thisObj = resolvedThisObj;
+ }
+ if (node.tagName === "IMG" && (node.hasAttribute("src") || node.hasAttribute("_src") || node.hasAttribute("$src"))) {
+ const imgNode = node;
+ Promise.resolve().then(() => {
+ const url = imgNode.getAttribute("_src") || imgNode.getAttribute("src");
+ if (url) fetch(url, { cache: "force-cache" }).then((r) => r.text()).then((svgText) => {
+ const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector("svg");
+ if (realSvg) {
+ Array.from(imgNode.attributes).forEach((attr) => realSvg.setAttribute(attr.name, attr.value));
+ imgNode.replaceWith(realSvg);
+ }
+ });
+ });
+ }
+ if (node._thisObj !== void 0) scanObj.thisObj = node._thisObj || null;
+ else {
+ let curr = node;
+ while (curr && curr._thisObj === void 0) curr = curr.parentNode;
+ scanObj.thisObj = curr ? curr._thisObj : null;
+ }
+ if (node._ref === void 0) {
+ let curr = node;
+ while (curr && curr._ref === void 0) curr = curr.parentNode;
+ node._ref = curr ? { ...curr._ref } : {};
+ }
+ if (node._refExt !== void 0) {
+ Object.assign(node._ref, node._refExt);
+ }
+ if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
+ _parseNode(node, { ...scanObj });
+ const nodes = [...node.childNodes || []];
+ const nextScanObj = { thisObj: scanObj.thisObj, extendVars: { ...node._ref } };
+ nodes.forEach((child) => {
+ if (!child._stManaged) _scanTree(child, nextScanObj);
+ });
+ };
+ const _unbindTree = (node) => {
+ if (node.nodeType !== 1) return;
+ if (node._hasOnUnload) node.dispatchEvent(new Event("unload", { bubbles: false }));
+ if (node._states) node._states.forEach((mappings) => {
+ for (const [key, bindingSet] of mappings) {
+ for (const binding of bindingSet) {
+ if (binding.node === node) bindingSet.delete(binding);
+ }
+ }
+ });
+ node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
+ };
+ const RefreshState = _scanTree;
+ const Util = {
+ clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
+ base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
+ unbase64: (str) => new TextDecoder().decode(Uint8Array.from(atob(str), (c) => c.charCodeAt(0))),
+ urlbase64: (str) => Util.base64(str).replace(/[+/=]/g, (m) => ({ "+": "-", "/": "", "=": "" })[m]),
+ unurlbase64: (str) => Util.unbase64(str.replace(/[-_.]/g, (m) => ({ "-": "+", "_": "/", ".": "=" })[m]).padEnd(Math.ceil(str.length / 4) * 4, "=")),
+ safeJson: (str) => {
+ try {
+ return JSON.parse(str);
+ } catch {
+ return null;
+ }
+ },
+ updateDefaults: (obj, defaults) => {
+ for (const k in defaults) if (obj[k] === void 0) obj[k] = defaults[k];
+ },
+ copyFunction: (toObj, fromObj, ...funcNames) => {
+ funcNames.forEach((name) => toObj[name] = fromObj[name].bind(fromObj));
+ },
+ getFunctionBody: (fn) => {
+ const code = fn.toString();
+ return code.slice(code.indexOf("{") + 1, code.lastIndexOf("}")).trim();
+ },
+ makeDom: (html) => {
+ if (html.includes(">\n")) html = html.replace(/>\s+<").trim();
+ const node = document.createElement("div");
+ node.innerHTML = html;
+ return node.children[0];
+ },
+ newAvg: () => {
+ let total = 0, count = 0, avg = 0;
+ return {
+ add: (v) => {
+ total += v;
+ count++;
+ return avg = total / count;
+ },
+ get: () => avg,
+ clear: () => {
+ total = 0, count = 0, avg = 0;
+ }
+ };
+ },
+ newTimeCount: () => {
+ let startTime = 0, total = 0, count = 0;
+ return {
+ start: () => startTime = (/* @__PURE__ */ new Date()).getTime(),
+ end: () => {
+ const endTime = (/* @__PURE__ */ new Date()).getTime();
+ const left = endTime - startTime;
+ startTime = endTime;
+ total += left;
+ count++;
+ return left;
+ },
+ avg: () => total / count
+ };
+ }
+ };
+ globalThis.Util = Util;
+ let _hashParams = new URLSearchParams(((_a = window.location.hash) == null ? void 0 : _a.substring(1)) || "");
+ const Hash = NewState({}, (k) => Util.safeJson(_hashParams.get(k)), (k, v) => {
+ const oldStr = _hashParams.get(k);
+ const newStr = v === void 0 ? void 0 : JSON.stringify(v);
+ if (oldStr === newStr || oldStr === null && newStr === void 0) return;
+ v === void 0 ? _hashParams.delete(k) : _hashParams.set(k, newStr);
+ window.location.hash = "#" + _hashParams.toString();
+ });
+ if (typeof window !== "undefined") {
+ window.addEventListener("hashchange", () => {
+ var _a2;
+ const oldHashParams = _hashParams;
+ _hashParams = new URLSearchParams(((_a2 = window.location.hash) == null ? void 0 : _a2.substring(1)) || "");
+ _hashParams.forEach((v, k) => {
+ if (oldHashParams.get(k) !== v) Hash[k] = Util.safeJson(v);
+ });
+ oldHashParams.forEach((v, k) => {
+ if (_hashParams.get(k) === void 0) Hash[k] = void 0;
+ });
+ });
+ }
+ const LocalStorage = NewState({}, (k) => Util.safeJson(localStorage.getItem(k)), (k, v) => {
+ const oldStr = localStorage.getItem(k);
+ const newStr = v === void 0 ? void 0 : JSON.stringify(v);
+ if (oldStr === newStr || oldStr === null && newStr === void 0) return;
+ v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, newStr);
+ });
+ const State = NewState({
+ exitBlocks: 0
+ // 默认值
+ });
+ globalThis.Hash = Hash;
+ globalThis.LocalStorage = LocalStorage;
+ globalThis.State = State;
+ const ApigoState = {
+ NewState,
+ Component,
+ $,
+ $$,
+ RefreshState,
+ SetTranslator,
+ Util,
+ Hash,
+ LocalStorage,
+ State
+ };
+ if (typeof globalThis !== "undefined") {
+ Object.assign(globalThis, ApigoState);
+ globalThis.ApigoState = ApigoState;
+ }
+ if (typeof document !== "undefined") {
+ const init = () => {
+ Component._initPending();
+ 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);
+ };
+ if (document.readyState !== "loading") init();
+ else document.addEventListener("DOMContentLoaded", init, true);
+ }
+ exports2.$ = $;
+ exports2.$$ = $$;
+ exports2.Component = Component;
+ exports2.Hash = Hash;
+ exports2.LocalStorage = LocalStorage;
+ exports2.NewState = NewState;
+ exports2.RefreshState = RefreshState;
+ exports2.SetTranslator = SetTranslator;
+ exports2.State = State;
+ exports2.Util = Util;
+ exports2._scanTree = _scanTree;
+ exports2._unbindTree = _unbindTree;
+ Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
+});
diff --git a/dist/state.min.js b/dist/state.min.js
index a13ddc9..41a7a01 100644
--- a/dist/state.min.js
+++ b/dist/state.min.js
@@ -1 +1 @@
-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,c=(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)),l=(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?c:"__unwatch"===n?l:"__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),c=new Map,l=[],h={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{c.set(e.toUpperCase(),t),"loading"!==document.readyState?h._addTemplate(e,n,r):l.push([e,n,r])},exists:e=>c.has(e.toUpperCase()),getSetupFunction:e=>c.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{l.forEach(([e,t,n])=>h._addTemplate(e,t,n)),l.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);const s="TEMPLATE"===e.tagName?e.content:e,a="TEMPLATE"===t.tagName?t.content:t;Array.from(s.childNodes).forEach(e=>a.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 _(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 g(e,t,n,r){return e.includes("${")?_("return `"+e+"`",t,n,r):_("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 E=e=>v=e,y=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>r[e.substring(1,e.length-1)]=n[t+1]||"")}return v(n[0],r)}):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){if(!e.startsWith("$"))return t.call(this,e,n);const r=e.startsWith("$$")?"st-st-":"st-";return t.call(this,r+e.substring(e.startsWith("$$")?2:1),n)}}var A;function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function T(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;r(e),window.__perfTrace&&window.__perfTrace.evalCount++;const n=window.__perfTrace?performance.now():0;let s=e.exp?e.tpl?g(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof s)try{s=g(s,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(window.__perfTrace&&(window.__perfTrace.evalTotal+=performance.now()-n),r(null),e.prop){const n=e.prop;let r=t;for(let e=0;e
O(e,{thisObj:t._thisObj,extendVars:e._ref})):(t._children.forEach(e=>{e._stManaged=!0,t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(N(t),t._renderedNodes=[]);else if("each"===n)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",r=t.getAttribute("key");let a,o;if(s instanceof Map)a=Array.from(s.keys()),o=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);a=new Array(e.length);for(let t=0;te[t]}else a=Object.keys(s),o=e=>s[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,d=[];a.forEach((s,a)=>{const c=o(s),l=r?c&&"object"==typeof c?c[r]:c:s,h=null==l||i.has(l)?`st_key_fallback_${a}_${Math.random()}`:l;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(r=>{window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.reuseCount++;let a=!1;for(let s in t._ref)s!==e&&s!==n&&r._ref[s]!==t._ref[s]&&(r._ref[s]=t._ref[s],a=!0);r._ref[n]!==s&&(r._ref[n]=s),r._ref[e]!==c||a?(r._ref[e]=c,window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.scanCount++,O(r,{thisObj:t._thisObj,extendVars:r._ref})):t.parentNode.lastChild!==r&&(window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.moveCount++,t.parentNode.insertBefore(r,t))})):(u=[],t._children.forEach(r=>{const a=r.cloneNode(!0);a._stManaged=!0,a._ref={...t._ref,[n]:s,[e]:c},a._thisObj=t._thisObj,t.parentNode.insertBefore(a,t),u.push(a)})),i.set(h,u),d.push(u),u.forEach(e=>t.parentNode.insertBefore(e,t))}),t._keyedNodes.forEach(e=>e.forEach(e=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=d}else N(t),t._keyedNodes&&t._keyedNodes.forEach(e=>e.forEach(e=>e.remove())),t._keyedNodes=new Map,t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||s||(_(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const n=s instanceof Array?s.includes(t.value):!!s;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?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(n)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(n,""):t.removeAttribute(n):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===n?t.textContent=s??"":"html"===n?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===n&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(n,s??""))}}A=e=>T(e),a.add(A);const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),T(e)},O=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=y(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=y(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0);let n=e._thisObj,r=e._ref;if(void 0===n||void 0===r){let t=e;for(;t&&(void 0===n||void 0===r);)void 0===n&&void 0!==t._thisObj&&(n=t._thisObj),void 0===r&&void 0!==t._ref&&(r={...t._ref}),t=t.parentNode}void 0===n&&(n=t.thisObj),void 0===r&&(r=t.extendVars);const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"],o=["$each","st-each","$$each","st-st-each"];if("TEMPLATE"!==e.tagName&&a.some(t=>e.hasAttribute(t))){const s=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>a.includes(t.name)||o.some(t=>e.hasAttribute(t))&&["as","index"].includes(t.name)).forEach(t=>{s.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(s,e),s.content.appendChild(e),s._ref=r,s._thisObj=n,void O(s,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),s=Array.from(e.attributes).filter(e=>a.includes(e.name)),i=s[s.length-1];t.setAttribute(i.name,i.value),e.removeAttribute(i.name),o.includes(i.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=r,t._thisObj=n}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0!==e._refExt&&Object.assign(e._ref,e._refExt),t.extendVars&&Object.assign(e._ref,t.extendVars),((e,t)=>{let n=!1;if(e._bindings&&(e._states=new Set,e._bindings.forEach(t=>T({node:e,...t})),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),n=!0),h.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var r;if(n.name.startsWith("$.")){const s=n.name.slice(2);let a=y(n.value);a.includes("this.")&&(a=a.replace(/\bthis\./g,"this.parent."));const o=g(a,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=e;const d=s.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[])),n)return;let r=[];const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"];"TEMPLATE"===e.tagName?a.forEach(t=>e.hasAttribute(t)&&r.push(e.getAttributeNode(t))):r=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!a.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,r.forEach(n=>{let r=0;n.name.startsWith("$$")||n.name.startsWith("st-st-")?r=2:(n.name.startsWith("$")||n.name.startsWith("st-"))&&(r=1);const a=2===r?n.name.startsWith("$$")?n.name.slice(2):n.name.slice(6):1===r?n.name.startsWith("$")?n.name.slice(1):n.name.slice(3):n.name;let o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:o,exp: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),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===a?e.addEventListener("TEXTAREA"===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?_(`!!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||{}):_(`${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=y(o),w({node:e,attr:a,tpl:o,exp:r}))}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,{...t});const i=[...e.childNodes||[]],c={thisObj:t.thisObj,extendVars:{...e._ref}};i.forEach(e=>{e._stManaged||O(e,c)})},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)))},$=O,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 M=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const S=o({},e=>x.safeJson(M.get(e)),(e,t)=>{const n=M.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?M.delete(e):M.set(e,r),window.location.hash="#"+M.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=M;M=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),M.forEach((e,n)=>{t.get(n)!==e&&(S[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===M.get(t)&&(S[t]=void 0)})});const C=o({},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=S,globalThis.LocalStorage=C,"undefined"!=typeof document){const e=()=>{h._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&O(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),O(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,d as $$,h as Component,S as Hash,C as LocalStorage,o as NewState,$ as RefreshState,E as SetTranslator,x as Util,O as _scanTree,j as _unbindTree};
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ApigoState={})}(this,function(e){"use strict";var t;let n=null,r=null;const s=e=>n=e,a=e=>r=e,o=new Set;function i(e={},t=null,s=null){const a={},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=t||(e=>a[e]),u=s||((e,t)=>a[e]=t);return Object.assign(a,e),new Proxy(a,{get:(e,t)=>"__watch"===t?l:"__unwatch"===t?c:"__isProxy"===t||(n&&(i.has(t)||i.set(t,new Set),i.get(t).add(n),n.node._states||(n.node._states=new Set),n.node._states.add(i)),h(t)),set(e,t,n){if(h(t)!==n&&u(t,n),d.has(t)&&d.get(t).forEach(r=>{const s=r(n);void 0!==s&&(n=s,e[t]=n)}),d.has(null)&&d.get(null).forEach(e=>e(n)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?r!==t.node&&o.forEach(e=>e(t)):e.delete(t)}return!0}})}const d=(e,t)=>t?e.querySelector(t):document.querySelector(e),l=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),c=new Map,h=[],u={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{c.set(e.toUpperCase(),t),"loading"!==document.readyState?u._addTemplate(e,n,r):h.push([e,n,r])},exists:e=>c.has(e.toUpperCase()),getSetupFunction:e=>c.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{h.forEach(([e,t,n])=>u._addTemplate(e,t,n)),h.length=0}};function f(e,t,n,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);const s="TEMPLATE"===e.tagName?e.content:e,a="TEMPLATE"===t.tagName?t.content:t;Array.from(s.childNodes).forEach(e=>a.appendChild(e)),e.tagName&&u.exists(e.tagName)&&b(e.tagName,t,n,r)}function b(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=u.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=u.getTemplate(e);if(o){const e=o.content.cloneNode(!0);if(e.childNodes.length){const s=Array.from(e.childNodes).find(e=>e.nodeType===Node.ELEMENT_NODE);s&&f(s,t,n,r),l(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");a[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",f(a[t],e,n,r))})}}s&&s(t)}let m=!1;function p(e){m=e}const _=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=_.get(i);return t||(t=new Function("Hash","LocalStorage","State",...a,e),_.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(s){return m||console.error(s,r,[e,r,t,n]),null}}function y(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 E=e=>v=e,A=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>r[e.substring(1,e.length-1)]=n[t+1]||"")}return v(n[0],r)}):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){if(!e.startsWith("$"))return t.call(this,e,n);const r=e.startsWith("$$")?"st-st-":"st-";return t.call(this,r+e.substring(e.startsWith("$$")?2:1),n)}}var N;function T(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&T(e)}))}function w(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;s(e),window.__perfTrace&&window.__perfTrace.evalCount++;const n=window.__perfTrace?performance.now():0;let r=e.exp?e.tpl?y(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof r)try{r=y(r,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(window.__perfTrace&&(window.__perfTrace.evalTotal+=performance.now()-n),s(null),e.prop){const n=e.prop;let s=t;for(let e=0;ej(e,{thisObj:t._thisObj,extendVars:e._ref})):(t._children.forEach(e=>{e._stManaged=!0,t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(T(t),t._renderedNodes=[]);else if("each"===n)if(r&&"object"==typeof r){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",s=t.getAttribute("key");let a,o;if(r instanceof Map)a=Array.from(r.keys()),o=e=>r.get(e);else if("function"==typeof r[Symbol.iterator]){const e=Array.isArray(r)?r:Array.from(r);a=new Array(e.length);for(let t=0;te[t]}else a=Object.keys(r),o=e=>r[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,d=[];a.forEach((r,a)=>{const l=o(r),c=s?l&&"object"==typeof l?l[s]:l:r,h=null==c||i.has(c)?`st_key_fallback_${a}_${Math.random()}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(s=>{window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.reuseCount++;let a=!1;for(let r in t._ref)r!==e&&r!==n&&s._ref[r]!==t._ref[r]&&(s._ref[r]=t._ref[r],a=!0);s._ref[n]!==r&&(s._ref[n]=r),s._ref[e]!==l||a?(s._ref[e]=l,window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.scanCount++,j(s,{thisObj:t._thisObj,extendVars:s._ref})):t.parentNode.lastChild!==s&&(window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.moveCount++,t.parentNode.insertBefore(s,t))})):(u=[],t._children.forEach(s=>{const a=s.cloneNode(!0);a._stManaged=!0,a._ref={...t._ref,[n]:r,[e]:l},a._thisObj=t._thisObj,t.parentNode.insertBefore(a,t),u.push(a)})),i.set(h,u),d.push(u),u.forEach(e=>t.parentNode.insertBefore(e,t))}),t._keyedNodes.forEach(e=>e.forEach(e=>{T(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=d}else T(t),t._keyedNodes&&t._keyedNodes.forEach(e=>e.forEach(e=>e.remove())),t._keyedNodes=new Map,t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||r||(g(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),r=[]),t._checkboxMultiMode=r instanceof Array;const n=r instanceof Array?r.includes(t.value):!!r;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(r??""))&&(t.checked=t.value===String(r??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{t.value!==String(r??"")&&(t.value=r)}):t.isContentEditable&&t.innerHTML!==String(r??"")&&(t.innerHTML=r);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:r}))}else["checked","disabled","readonly"].includes(n)&&(r=!!r),"boolean"==typeof r?r?t.setAttribute(n,""):t.removeAttribute(n):void 0!==r&&("string"!=typeof r&&(r=JSON.stringify(r)),"text"===n?t.textContent=r??"":"html"===n?t.innerHTML=r??"":"IMG"===t.tagName&&"src"===n&&r.includes(".svg")?t.setAttribute("_src",r??""):t.setAttribute(n,r??""))}}N=e=>w(e),o.add(N);const O=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),w(e)},j=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=A(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=A(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0);let n=e._thisObj,r=e._ref;if(void 0===n||void 0===r){let t=e;for(;t&&(void 0===n||void 0===r);)void 0===n&&void 0!==t._thisObj&&(n=t._thisObj),void 0===r&&void 0!==t._ref&&(r={...t._ref}),t=t.parentNode}void 0===n&&(n=t.thisObj),void 0===r&&(r=t.extendVars);const s=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"],o=["$each","st-each","$$each","st-st-each"];if("TEMPLATE"!==e.tagName&&s.some(t=>e.hasAttribute(t))){const a=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>s.includes(t.name)||o.some(t=>e.hasAttribute(t))&&["as","index"].includes(t.name)).forEach(t=>{a.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(a,e),a.content.appendChild(e),a._ref=r,a._thisObj=n,void j(a,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),a=Array.from(e.attributes).filter(e=>s.includes(e.name)),i=a[a.length-1];t.setAttribute(i.name,i.value),e.removeAttribute(i.name),o.includes(i.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=r,t._thisObj=n}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0!==e._refExt&&Object.assign(e._ref,e._refExt),t.extendVars&&Object.assign(e._ref,t.extendVars),((e,t)=>{let n=!1;if(e._bindings&&(e._states=new Set,e._bindings.forEach(t=>w({node:e,...t})),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),n=!0),u.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var r;if(n.name.startsWith("$.")){const s=n.name.slice(2);let a=A(n.value);a.includes("this.")&&(a=a.replace(/\bthis\./g,"this.parent."));const o=y(a,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=e;const d=s.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[])),n)return;let r=[];const s=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"];"TEMPLATE"===e.tagName?s.forEach(t=>e.hasAttribute(t)&&r.push(e.getAttributeNode(t))):r=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!s.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,r.forEach(n=>{let r=0;n.name.startsWith("$$")||n.name.startsWith("st-st-")?r=2:(n.name.startsWith("$")||n.name.startsWith("st-"))&&(r=1);const s=2===r?n.name.startsWith("$$")?n.name.slice(2):n.name.slice(6):1===r?n.name.startsWith("$")?n.name.slice(1):n.name.slice(3):n.name;let o=n.value;if(e.removeAttribute(n.name),s.startsWith("."))O({node:e,prop:s.split("."),tpl:o,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),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>g(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===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;a(e),p(!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||{}),p(!1),a(null)}):"text"!==s||o||(o=e.textContent,e.textContent=""),o&&(o=A(o),O({node:e,attr:s,tpl:o,exp:r}))}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,{...t});const i=[...e.childNodes||[]],d={thisObj:t.thisObj,extendVars:{...e._ref}};i.forEach(e=>{e._stManaged||j(e,d)})},S=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=>S(e)))},$=j,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 M=new URLSearchParams((null==(t=window.location.hash)?void 0:t.substring(1))||"");const C=i({},e=>x.safeJson(M.get(e)),(e,t)=>{const n=M.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?M.delete(e):M.set(e,r),window.location.hash="#"+M.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=M;M=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),M.forEach((e,n)=>{t.get(n)!==e&&(C[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===M.get(t)&&(C[t]=void 0)})});const L=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))}),k=i({exitBlocks:0});globalThis.Hash=C,globalThis.LocalStorage=L,globalThis.State=k;const P={NewState:i,Component:u,$:d,$$:l,RefreshState:$,SetTranslator:E,Util:x,Hash:C,LocalStorage:L,State:k};if("undefined"!=typeof globalThis&&(Object.assign(globalThis,P),globalThis.ApigoState=P),"undefined"!=typeof document){const e=()=>{u._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&j(e)}),e.removedNodes.forEach(e=>S(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),j(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}e.$=d,e.$$=l,e.Component=u,e.Hash=C,e.LocalStorage=L,e.NewState=i,e.RefreshState=$,e.SetTranslator=E,e.State=k,e.Util=x,e._scanTree=j,e._unbindTree=S,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
diff --git a/dist/state.min.mjs b/dist/state.min.mjs
new file mode 100644
index 0000000..4bfd7ef
--- /dev/null
+++ b/dist/state.min.mjs
@@ -0,0 +1 @@
+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);const s="TEMPLATE"===e.tagName?e.content:e,a="TEMPLATE"===t.tagName?t.content:t;Array.from(s.childNodes).forEach(e=>a.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 _(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 g(e,t,n,r){return e.includes("${")?_("return `"+e+"`",t,n,r):_("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 E=e=>v=e,y=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>r[e.substring(1,e.length-1)]=n[t+1]||"")}return v(n[0],r)}):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){if(!e.startsWith("$"))return t.call(this,e,n);const r=e.startsWith("$$")?"st-st-":"st-";return t.call(this,r+e.substring(e.startsWith("$$")?2:1),n)}}var A;function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function T(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;r(e),window.__perfTrace&&window.__perfTrace.evalCount++;const n=window.__perfTrace?performance.now():0;let s=e.exp?e.tpl?g(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof s)try{s=g(s,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(window.__perfTrace&&(window.__perfTrace.evalTotal+=performance.now()-n),r(null),e.prop){const n=e.prop;let r=t;for(let e=0;eO(e,{thisObj:t._thisObj,extendVars:e._ref})):(t._children.forEach(e=>{e._stManaged=!0,t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(N(t),t._renderedNodes=[]);else if("each"===n)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",r=t.getAttribute("key");let a,o;if(s instanceof Map)a=Array.from(s.keys()),o=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);a=new Array(e.length);for(let t=0;te[t]}else a=Object.keys(s),o=e=>s[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,d=[];a.forEach((s,a)=>{const l=o(s),c=r?l&&"object"==typeof l?l[r]:l:s,h=null==c||i.has(c)?`st_key_fallback_${a}_${Math.random()}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(r=>{window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.reuseCount++;let a=!1;for(let s in t._ref)s!==e&&s!==n&&r._ref[s]!==t._ref[s]&&(r._ref[s]=t._ref[s],a=!0);r._ref[n]!==s&&(r._ref[n]=s),r._ref[e]!==l||a?(r._ref[e]=l,window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.scanCount++,O(r,{thisObj:t._thisObj,extendVars:r._ref})):t.parentNode.lastChild!==r&&(window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.moveCount++,t.parentNode.insertBefore(r,t))})):(u=[],t._children.forEach(r=>{const a=r.cloneNode(!0);a._stManaged=!0,a._ref={...t._ref,[n]:s,[e]:l},a._thisObj=t._thisObj,t.parentNode.insertBefore(a,t),u.push(a)})),i.set(h,u),d.push(u),u.forEach(e=>t.parentNode.insertBefore(e,t))}),t._keyedNodes.forEach(e=>e.forEach(e=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=d}else N(t),t._keyedNodes&&t._keyedNodes.forEach(e=>e.forEach(e=>e.remove())),t._keyedNodes=new Map,t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||s||(_(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const n=s instanceof Array?s.includes(t.value):!!s;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?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(n)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(n,""):t.removeAttribute(n):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===n?t.textContent=s??"":"html"===n?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===n&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(n,s??""))}}A=e=>T(e),a.add(A);const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),T(e)},O=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=y(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=y(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0);let n=e._thisObj,r=e._ref;if(void 0===n||void 0===r){let t=e;for(;t&&(void 0===n||void 0===r);)void 0===n&&void 0!==t._thisObj&&(n=t._thisObj),void 0===r&&void 0!==t._ref&&(r={...t._ref}),t=t.parentNode}void 0===n&&(n=t.thisObj),void 0===r&&(r=t.extendVars);const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"],o=["$each","st-each","$$each","st-st-each"];if("TEMPLATE"!==e.tagName&&a.some(t=>e.hasAttribute(t))){const s=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>a.includes(t.name)||o.some(t=>e.hasAttribute(t))&&["as","index"].includes(t.name)).forEach(t=>{s.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(s,e),s.content.appendChild(e),s._ref=r,s._thisObj=n,void O(s,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),s=Array.from(e.attributes).filter(e=>a.includes(e.name)),i=s[s.length-1];t.setAttribute(i.name,i.value),e.removeAttribute(i.name),o.includes(i.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=r,t._thisObj=n}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0!==e._refExt&&Object.assign(e._ref,e._refExt),t.extendVars&&Object.assign(e._ref,t.extendVars),((e,t)=>{let n=!1;if(e._bindings&&(e._states=new Set,e._bindings.forEach(t=>T({node:e,...t})),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),n=!0),h.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var r;if(n.name.startsWith("$.")){const s=n.name.slice(2);let a=y(n.value);a.includes("this.")&&(a=a.replace(/\bthis\./g,"this.parent."));const o=g(a,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=e;const d=s.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[])),n)return;let r=[];const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"];"TEMPLATE"===e.tagName?a.forEach(t=>e.hasAttribute(t)&&r.push(e.getAttributeNode(t))):r=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!a.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,r.forEach(n=>{let r=0;n.name.startsWith("$$")||n.name.startsWith("st-st-")?r=2:(n.name.startsWith("$")||n.name.startsWith("st-"))&&(r=1);const a=2===r?n.name.startsWith("$$")?n.name.slice(2):n.name.slice(6):1===r?n.name.startsWith("$")?n.name.slice(1):n.name.slice(3):n.name;let o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:o,exp: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),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===a?e.addEventListener("TEXTAREA"===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?_(`!!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||{}):_(`${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=y(o),w({node:e,attr:a,tpl:o,exp:r}))}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,{...t});const i=[...e.childNodes||[]],l={thisObj:t.thisObj,extendVars:{...e._ref}};i.forEach(e=>{e._stManaged||O(e,l)})},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)))},$=O,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=o({},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 C=o({},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))}),L=o({exitBlocks:0});globalThis.Hash=M,globalThis.LocalStorage=C,globalThis.State=L;const k={NewState:o,Component:h,$:i,$$:d,RefreshState:$,SetTranslator:E,Util:x,Hash:M,LocalStorage:C,State:L};if("undefined"!=typeof globalThis&&(Object.assign(globalThis,k),globalThis.ApigoState=k),"undefined"!=typeof document){const e=()=>{h._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&O(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),O(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,d as $$,h as Component,M as Hash,C as LocalStorage,o as NewState,$ as RefreshState,E as SetTranslator,L as State,x as Util,O as _scanTree,j as _unbindTree};
diff --git a/dist/state.mjs b/dist/state.mjs
new file mode 100644
index 0000000..ae1e602
--- /dev/null
+++ b/dist/state.mjs
@@ -0,0 +1,715 @@
+var _a;
+let _activeBinding = null;
+let _noWriteBack = null;
+const setActiveBinding = (val) => _activeBinding = val;
+const setNoWriteBack = (val) => _noWriteBack = val;
+const _notifiers = /* @__PURE__ */ new Set();
+const onNotifyUpdate = (fn) => _notifiers.add(fn);
+function NewState(defaults = {}, getter = null, setter = null) {
+ 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.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 (key === "__isProxy") return true;
+ if (_activeBinding) {
+ if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
+ _stateMappings.get(key).add(_activeBinding);
+ if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
+ _activeBinding.node._states.add(_stateMappings);
+ }
+ 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 !== void 0) {
+ 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) {
+ _notifiers.forEach((fn) => fn(binding));
+ }
+ }
+ }
+ return true;
+ }
+ });
+}
+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);
+ const fromContent = from.tagName === "TEMPLATE" ? from.content : from;
+ const toContent = to.tagName === "TEMPLATE" ? to.content : to;
+ Array.from(fromContent.childNodes).forEach((child) => toContent.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;
+}
+const _fnCache = /* @__PURE__ */ new Map();
+function _runCode(code, vars, thisObj, extendVars) {
+ const allVars = { ...extendVars || {}, ...vars || {} };
+ const argKeys = Object.keys(allVars);
+ const argValues = Object.values(allVars);
+ const cacheKey = code + argKeys.join(",");
+ try {
+ let fn = _fnCache.get(cacheKey);
+ if (!fn) {
+ fn = new Function("Hash", "LocalStorage", "State", ...argKeys, code);
+ _fnCache.set(cacheKey, fn);
+ }
+ return fn.apply(thisObj, [globalThis.Hash, globalThis.LocalStorage, globalThis.State, ...argValues]);
+ } catch (e) {
+ if (!_disableRunCodeError) console.error(e, extendVars, [code, extendVars, vars, thisObj]);
+ return null;
+ }
+}
+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 _translator = (text, args) => {
+ if (!text || typeof text !== "string") return text;
+ return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match);
+};
+const SetTranslator = (fn) => _translator = fn;
+const _translate = (text) => {
+ if (!text || typeof text !== "string" || !text.includes("{#")) return text;
+ return text.replace(/\{#(.+?)#\}/g, (m, content) => {
+ const parts = content.split("||").map((s) => s.trim());
+ const args = {};
+ if (parts.length > 1) {
+ const matches = parts[0].match(/\{(.+?)\}/g);
+ if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || "");
+ }
+ return _translator(parts[0], args);
+ });
+};
+if (typeof document !== "undefined") {
+ try {
+ document.createElement("div").setAttribute("$t", "1");
+ } catch (e) {
+ const originalSetAttribute = Element.prototype.setAttribute;
+ Element.prototype.setAttribute = function(name, value) {
+ if (!name.startsWith("$")) return originalSetAttribute.call(this, name, value);
+ const prefix = name.startsWith("$$") ? "st-st-" : "st-";
+ return originalSetAttribute.call(this, prefix + name.substring(name.startsWith("$$") ? 2 : 1), value);
+ };
+ }
+}
+onNotifyUpdate((binding) => _updateBinding(binding));
+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;
+ if (!node.isConnected && node.tagName !== "TEMPLATE") return;
+ setActiveBinding(binding);
+ if (window.__perfTrace) window.__perfTrace.evalCount++;
+ const evalStart = window.__perfTrace ? performance.now() : 0;
+ let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
+ if (binding.exp === 2 && typeof result === "string") {
+ try {
+ result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null);
+ } catch (e) {
+ }
+ }
+ if (window.__perfTrace) window.__perfTrace.evalTotal += performance.now() - evalStart;
+ setActiveBinding(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 lk = prop[prop.length - 1];
+ if (lk) {
+ if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
+ const lo = o[lk];
+ if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
+ else o[lk] = result;
+ } else if (typeof result === "object" && result != null && !Array.isArray(result)) {
+ Object.assign(o, result);
+ }
+ }
+ } else if (binding.attr) {
+ const attr = binding.attr;
+ if (attr === "if") {
+ if (result) {
+ if (!node._renderedNodes || node._renderedNodes.length === 0) {
+ node._children.forEach((child) => {
+ child._stManaged = true;
+ node.parentNode.insertBefore(child, node);
+ child._ref = { ...node._ref };
+ });
+ node._renderedNodes = [node._children];
+ } else {
+ node._renderedNodes[0].forEach((child) => _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref }));
+ }
+ } else {
+ _clearRenderedNodes(node);
+ node._renderedNodes = [];
+ }
+ } else if (attr === "each") {
+ if (result && typeof result === "object") {
+ const asName = node.getAttribute("as") || "item";
+ const indexName = node.getAttribute("index") || "index";
+ const keyName = node.getAttribute("key");
+ let keys, getVal;
+ if (result instanceof Map) {
+ keys = Array.from(result.keys());
+ getVal = (k) => result.get(k);
+ } else if (typeof result[Symbol.iterator] === "function") {
+ const arr = Array.isArray(result) ? result : Array.from(result);
+ keys = new Array(arr.length);
+ for (let i = 0; i < arr.length; i++) keys[i] = i;
+ getVal = (k) => arr[k];
+ } else {
+ keys = Object.keys(result);
+ getVal = (k) => result[k];
+ }
+ if (!node._keyedNodes) node._keyedNodes = /* @__PURE__ */ new Map();
+ const newKeyedNodes = /* @__PURE__ */ new Map();
+ const currentRenderedNodes = [];
+ keys.forEach((k, i) => {
+ const item = getVal(k);
+ const rawKey = keyName ? item && typeof item === "object" ? item[keyName] : item : k;
+ const keyVal = rawKey === void 0 || rawKey === null || newKeyedNodes.has(rawKey) ? `st_key_fallback_${i}_${Math.random()}` : rawKey;
+ let existingNodes = node._keyedNodes.get(keyVal);
+ if (existingNodes) {
+ node._keyedNodes.delete(keyVal);
+ existingNodes.forEach((child) => {
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.reuseCount++;
+ let scopeChanged = false;
+ for (let key in node._ref) {
+ if (key === asName || key === indexName) continue;
+ if (child._ref[key] !== node._ref[key]) {
+ child._ref[key] = node._ref[key];
+ scopeChanged = true;
+ }
+ }
+ const indexChanged = child._ref[indexName] !== k;
+ if (indexChanged) child._ref[indexName] = k;
+ if (child._ref[asName] !== item || scopeChanged) {
+ child._ref[asName] = item;
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.scanCount++;
+ _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref });
+ } else if (node.parentNode.lastChild !== child) {
+ if (window.__statePerformanceTelemetry) window.__statePerformanceTelemetry.moveCount++;
+ node.parentNode.insertBefore(child, node);
+ }
+ });
+ } else {
+ existingNodes = [];
+ node._children.forEach((child) => {
+ const cloned = child.cloneNode(true);
+ cloned._stManaged = true;
+ cloned._ref = { ...node._ref, [indexName]: k, [asName]: item };
+ cloned._thisObj = node._thisObj;
+ node.parentNode.insertBefore(cloned, node);
+ existingNodes.push(cloned);
+ });
+ }
+ newKeyedNodes.set(keyVal, existingNodes);
+ currentRenderedNodes.push(existingNodes);
+ existingNodes.forEach((child) => node.parentNode.insertBefore(child, node));
+ });
+ node._keyedNodes.forEach((nodes) => nodes.forEach((child) => {
+ _clearRenderedNodes(child);
+ child.remove();
+ }));
+ node._keyedNodes = newKeyedNodes;
+ node._renderedNodes = currentRenderedNodes;
+ } else {
+ _clearRenderedNodes(node);
+ if (node._keyedNodes) node._keyedNodes.forEach((nodes) => nodes.forEach((child) => child.remove()));
+ node._keyedNodes = /* @__PURE__ */ new Map();
+ node._renderedNodes = [];
+ }
+ } else if (attr === "bind") {
+ if (["INPUT", "SELECT", "TEXTAREA"].includes(node.tagName) && !node.hasAttribute("autocomplete")) node.setAttribute("autocomplete", "off");
+ if (node.type === "checkbox") {
+ if (node.value !== "on" && !result) {
+ _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {});
+ result = [];
+ }
+ node._checkboxMultiMode = result instanceof Array;
+ const isChecked = result instanceof Array ? result.includes(node.value) : !!result;
+ if (node.checked !== isChecked) node.checked = isChecked;
+ } else if (node.type === "radio") {
+ if (node.checked !== (node.value === String(result ?? ""))) node.checked = node.value === String(result ?? "");
+ } else if ("value" in node && node.type !== "file") {
+ setTimeout(() => {
+ 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 !== void 0) {
+ 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 _parseNode = (node, scanObj) => {
+ let hasBindings = false;
+ if (node._bindings) {
+ node._states = /* @__PURE__ */ new Set();
+ node._bindings.forEach((b) => _updateBinding({ node, ...b }));
+ if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
+ hasBindings = true;
+ }
+ if (Component.exists(node.tagName) && !node._componentInitialized) {
+ Array.from(node.attributes).forEach((attr) => {
+ var _a2;
+ if (attr.name.startsWith("$.")) {
+ const realAttrName = attr.name.slice(2);
+ let tpl = _translate(attr.value);
+ if (tpl.includes("this.")) tpl = tpl.replace(/\bthis\./g, "this.parent.");
+ const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {});
+ let o = node;
+ const prop = realAttrName.split(".");
+ for (let i = 0; i < prop.length - 1; i++) {
+ if (prop[i]) o = o[_a2 = prop[i]] ?? (o[_a2] = {});
+ }
+ o[prop[prop.length - 1]] = result;
+ node.removeAttribute(attr.name);
+ }
+ });
+ _makeComponent(node.tagName, node, scanObj);
+ $$(node, "[slot-id]").forEach((p) => p.removeAttribute("slot-id"));
+ node._componentInitialized = true;
+ if (!node._thisObj) node._thisObj = node;
+ }
+ if (node.tagName === "TEMPLATE") {
+ node._children = [...node.content.childNodes];
+ if (!node._renderedNodes) node._renderedNodes = [];
+ }
+ if (hasBindings) return;
+ let attrs = [];
+ const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
+ if (node.tagName === "TEMPLATE") {
+ triggerAttrs.forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
+ } else {
+ attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !triggerAttrs.includes(a.name) || a.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 = /* @__PURE__ */ new Set();
+ attrs.forEach((attr) => {
+ let exp = 0;
+ if (attr.name.startsWith("$$") || attr.name.startsWith("st-st-")) exp = 2;
+ else if (attr.name.startsWith("$") || attr.name.startsWith("st-")) exp = 1;
+ const realAttrName = exp === 2 ? attr.name.startsWith("$$") ? attr.name.slice(2) : attr.name.slice(6) : exp === 1 ? attr.name.startsWith("$") ? attr.name.slice(1) : attr.name.slice(3) : attr.name;
+ let tpl = attr.value;
+ node.removeAttribute(attr.name);
+ if (realAttrName.startsWith(".")) _initBinding({ node, prop: realAttrName.split("."), tpl, exp });
+ else if (realAttrName.startsWith("on")) {
+ const eventName = realAttrName.slice(2);
+ if (eventName === "update") node._hasOnUpdate = true;
+ if (eventName === "load" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnLoad = true;
+ if (eventName === "unload" && !["BODY", "IMG", "IFRAME"].includes(node.tagName)) node._hasOnUnload = true;
+ node.addEventListener(eventName, (e) => _runCode(tpl, { event: e, thisNode: node, ...e.detail || {} }, scanObj.thisObj || node, node._ref || {}));
+ } else {
+ if (realAttrName === "bind") {
+ 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;
+ setNoWriteBack(node);
+ setDisableRunCodeError(true);
+ if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
+ else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
+ setDisableRunCodeError(false);
+ setNoWriteBack(null);
+ });
+ } else if (realAttrName === "text" && !tpl) {
+ tpl = node.textContent;
+ node.textContent = "";
+ }
+ if (tpl) {
+ tpl = _translate(tpl);
+ _initBinding({ node, attr: realAttrName, tpl, exp });
+ }
+ }
+ });
+ if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event("load", { bubbles: false })));
+ if (node._hasOnUpdate) node.dispatchEvent(new Event("update", { bubbles: false }));
+ if (node._thisObj) scanObj.thisObj = node._thisObj;
+};
+const _scanTree = (node, scanObj = {}) => {
+ if (node.nodeType === 3) {
+ if (node._stTranslated) return;
+ const translated = _translate(node.textContent);
+ if (translated !== node.textContent) node.textContent = translated;
+ node._stTranslated = true;
+ return;
+ }
+ if (node.nodeType !== 1) return;
+ if (!node._stTranslated) {
+ Array.from(node.attributes).forEach((attr) => {
+ if (!attr.name.startsWith("$") && !attr.name.startsWith("st-") && !attr.name.startsWith(".")) {
+ const translated = _translate(attr.value);
+ if (translated !== attr.value) attr.value = translated;
+ }
+ });
+ node._stTranslated = true;
+ }
+ let resolvedThisObj = node._thisObj;
+ let resolvedRef = node._ref;
+ if (resolvedThisObj === void 0 || resolvedRef === void 0) {
+ let curr = node;
+ while (curr && (resolvedThisObj === void 0 || resolvedRef === void 0)) {
+ if (resolvedThisObj === void 0 && curr._thisObj !== void 0) resolvedThisObj = curr._thisObj;
+ if (resolvedRef === void 0 && curr._ref !== void 0) resolvedRef = { ...curr._ref };
+ curr = curr.parentNode;
+ }
+ }
+ if (resolvedThisObj === void 0) resolvedThisObj = scanObj.thisObj;
+ if (resolvedRef === void 0) resolvedRef = scanObj.extendVars;
+ const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"];
+ const eachAttrs = ["$each", "st-each", "$$each", "st-st-each"];
+ if (node.tagName !== "TEMPLATE" && triggerAttrs.some((t) => node.hasAttribute(t))) {
+ const template = document.createElement("TEMPLATE");
+ const attrs = Array.from(node.attributes).filter((attr) => triggerAttrs.includes(attr.name) || eachAttrs.some((t) => node.hasAttribute(t)) && ["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 = resolvedRef;
+ template._thisObj = resolvedThisObj;
+ _scanTree(template, scanObj);
+ return;
+ }
+ if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if") || node.hasAttribute("$$if") || node.hasAttribute("st-st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each"))) {
+ const template = document.createElement("TEMPLATE");
+ const attrs = Array.from(node.attributes).filter((attr2) => triggerAttrs.includes(attr2.name));
+ const attr = attrs[attrs.length - 1];
+ template.setAttribute(attr.name, attr.value);
+ node.removeAttribute(attr.name);
+ if (eachAttrs.includes(attr.name)) {
+ Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
+ template.setAttribute(attr2.name, attr2.value);
+ node.removeAttribute(attr2.name);
+ });
+ }
+ Array.from(node.content.childNodes).forEach((child) => template.content.appendChild(child));
+ node.content.appendChild(template);
+ template._ref = resolvedRef;
+ template._thisObj = resolvedThisObj;
+ }
+ if (node.tagName === "IMG" && (node.hasAttribute("src") || node.hasAttribute("_src") || node.hasAttribute("$src"))) {
+ const imgNode = node;
+ Promise.resolve().then(() => {
+ const url = imgNode.getAttribute("_src") || imgNode.getAttribute("src");
+ if (url) fetch(url, { cache: "force-cache" }).then((r) => r.text()).then((svgText) => {
+ const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector("svg");
+ if (realSvg) {
+ Array.from(imgNode.attributes).forEach((attr) => realSvg.setAttribute(attr.name, attr.value));
+ imgNode.replaceWith(realSvg);
+ }
+ });
+ });
+ }
+ if (node._thisObj !== void 0) scanObj.thisObj = node._thisObj || null;
+ else {
+ let curr = node;
+ while (curr && curr._thisObj === void 0) curr = curr.parentNode;
+ scanObj.thisObj = curr ? curr._thisObj : null;
+ }
+ if (node._ref === void 0) {
+ let curr = node;
+ while (curr && curr._ref === void 0) curr = curr.parentNode;
+ node._ref = curr ? { ...curr._ref } : {};
+ }
+ if (node._refExt !== void 0) {
+ Object.assign(node._ref, node._refExt);
+ }
+ if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
+ _parseNode(node, { ...scanObj });
+ const nodes = [...node.childNodes || []];
+ const nextScanObj = { thisObj: scanObj.thisObj, extendVars: { ...node._ref } };
+ nodes.forEach((child) => {
+ if (!child._stManaged) _scanTree(child, nextScanObj);
+ });
+};
+const _unbindTree = (node) => {
+ if (node.nodeType !== 1) return;
+ if (node._hasOnUnload) node.dispatchEvent(new Event("unload", { bubbles: false }));
+ if (node._states) node._states.forEach((mappings) => {
+ for (const [key, bindingSet] of mappings) {
+ for (const binding of bindingSet) {
+ if (binding.node === node) bindingSet.delete(binding);
+ }
+ }
+ });
+ node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
+};
+const RefreshState = _scanTree;
+const Util = {
+ clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
+ base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
+ unbase64: (str) => new TextDecoder().decode(Uint8Array.from(atob(str), (c) => c.charCodeAt(0))),
+ urlbase64: (str) => Util.base64(str).replace(/[+/=]/g, (m) => ({ "+": "-", "/": "", "=": "" })[m]),
+ unurlbase64: (str) => Util.unbase64(str.replace(/[-_.]/g, (m) => ({ "-": "+", "_": "/", ".": "=" })[m]).padEnd(Math.ceil(str.length / 4) * 4, "=")),
+ safeJson: (str) => {
+ try {
+ return JSON.parse(str);
+ } catch {
+ return null;
+ }
+ },
+ updateDefaults: (obj, defaults) => {
+ for (const k in defaults) if (obj[k] === void 0) obj[k] = defaults[k];
+ },
+ copyFunction: (toObj, fromObj, ...funcNames) => {
+ funcNames.forEach((name) => toObj[name] = fromObj[name].bind(fromObj));
+ },
+ getFunctionBody: (fn) => {
+ const code = fn.toString();
+ return code.slice(code.indexOf("{") + 1, code.lastIndexOf("}")).trim();
+ },
+ makeDom: (html) => {
+ if (html.includes(">\n")) html = html.replace(/>\s+<").trim();
+ const node = document.createElement("div");
+ node.innerHTML = html;
+ return node.children[0];
+ },
+ newAvg: () => {
+ let total = 0, count = 0, avg = 0;
+ return {
+ add: (v) => {
+ total += v;
+ count++;
+ return avg = total / count;
+ },
+ get: () => avg,
+ clear: () => {
+ total = 0, count = 0, avg = 0;
+ }
+ };
+ },
+ newTimeCount: () => {
+ let startTime = 0, total = 0, count = 0;
+ return {
+ start: () => startTime = (/* @__PURE__ */ new Date()).getTime(),
+ end: () => {
+ const endTime = (/* @__PURE__ */ new Date()).getTime();
+ const left = endTime - startTime;
+ startTime = endTime;
+ total += left;
+ count++;
+ return left;
+ },
+ avg: () => total / count
+ };
+ }
+};
+globalThis.Util = Util;
+let _hashParams = new URLSearchParams(((_a = window.location.hash) == null ? void 0 : _a.substring(1)) || "");
+const Hash = NewState({}, (k) => Util.safeJson(_hashParams.get(k)), (k, v) => {
+ const oldStr = _hashParams.get(k);
+ const newStr = v === void 0 ? void 0 : JSON.stringify(v);
+ if (oldStr === newStr || oldStr === null && newStr === void 0) return;
+ v === void 0 ? _hashParams.delete(k) : _hashParams.set(k, newStr);
+ window.location.hash = "#" + _hashParams.toString();
+});
+if (typeof window !== "undefined") {
+ window.addEventListener("hashchange", () => {
+ var _a2;
+ const oldHashParams = _hashParams;
+ _hashParams = new URLSearchParams(((_a2 = window.location.hash) == null ? void 0 : _a2.substring(1)) || "");
+ _hashParams.forEach((v, k) => {
+ if (oldHashParams.get(k) !== v) Hash[k] = Util.safeJson(v);
+ });
+ oldHashParams.forEach((v, k) => {
+ if (_hashParams.get(k) === void 0) Hash[k] = void 0;
+ });
+ });
+}
+const LocalStorage = NewState({}, (k) => Util.safeJson(localStorage.getItem(k)), (k, v) => {
+ const oldStr = localStorage.getItem(k);
+ const newStr = v === void 0 ? void 0 : JSON.stringify(v);
+ if (oldStr === newStr || oldStr === null && newStr === void 0) return;
+ v === void 0 ? localStorage.removeItem(k) : localStorage.setItem(k, newStr);
+});
+const State = NewState({
+ exitBlocks: 0
+ // 默认值
+});
+globalThis.Hash = Hash;
+globalThis.LocalStorage = LocalStorage;
+globalThis.State = State;
+const ApigoState = {
+ NewState,
+ Component,
+ $,
+ $$,
+ RefreshState,
+ SetTranslator,
+ Util,
+ Hash,
+ LocalStorage,
+ State
+};
+if (typeof globalThis !== "undefined") {
+ Object.assign(globalThis, ApigoState);
+ globalThis.ApigoState = ApigoState;
+}
+if (typeof document !== "undefined") {
+ const init = () => {
+ Component._initPending();
+ 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);
+ };
+ if (document.readyState !== "loading") init();
+ else document.addEventListener("DOMContentLoaded", init, true);
+}
+export {
+ $,
+ $$,
+ Component,
+ Hash,
+ LocalStorage,
+ NewState,
+ RefreshState,
+ SetTranslator,
+ State,
+ Util,
+ _scanTree,
+ _unbindTree
+};
diff --git a/package.json b/package.json
index c645068..d444bd3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@apigo.cc/state",
- "version": "1.0.12",
+ "version": "1.0.13",
"type": "module",
"main": "dist/state.js",
"module": "dist/state.js",
diff --git a/playwright.config.js b/playwright.config.js
index e440d8a..56b76ca 100644
--- a/playwright.config.js
+++ b/playwright.config.js
@@ -8,7 +8,7 @@ export default defineConfig({
},
webServer: {
command: 'npx vite --port 8081 --host 127.0.0.1',
- url: 'http://127.0.0.1:8081',
+ url: 'http://127.0.0.1:8081/test/index.html',
timeout: 180000,
reuseExistingServer: !process.env.CI,
},
diff --git a/src/globals.js b/src/globals.js
index d3aa744..37511f2 100644
--- a/src/globals.js
+++ b/src/globals.js
@@ -29,5 +29,11 @@ export const LocalStorage = NewState({}, k => Util.safeJson(localStorage.getItem
v === undefined ? localStorage.removeItem(k) : localStorage.setItem(k, newStr)
})
+// 核心全局状态机
+export const State = NewState({
+ exitBlocks: 0 // 默认值
+});
+
globalThis.Hash = Hash;
globalThis.LocalStorage = LocalStorage;
+globalThis.State = State;
diff --git a/src/index.js b/src/index.js
index 732a6e2..2a8e6ef 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,11 +3,24 @@ export { NewState } from './observer.js';
export { Component } from './component.js';
export { $, $$, RefreshState, SetTranslator, _scanTree, _unbindTree } from './dom.js';
export { Util } from './utils.js';
-export { Hash, LocalStorage } from './globals.js';
+export { Hash, LocalStorage, State } from './globals.js';
+import { NewState } from './observer.js';
import { Component } from './component.js';
+import { $, $$, RefreshState, SetTranslator } from './dom.js';
+import { Util } from './utils.js';
+import { Hash, LocalStorage, State } from './globals.js';
import { _scanTree, _unbindTree } from './dom.js';
-import { LocalStorage } from './globals.js';
+
+const ApigoState = {
+ NewState, Component, $, $$, RefreshState, SetTranslator, Util, Hash, LocalStorage, State
+};
+
+// 挂载到全局,支持 UMD 直接访问
+if (typeof globalThis !== 'undefined') {
+ Object.assign(globalThis, ApigoState);
+ globalThis.ApigoState = ApigoState;
+}
if (typeof document !== 'undefined') {
const init = () => {
diff --git a/test-results/.last-run.json b/test-results/.last-run.json
index 344ea9e..cbcc1fb 100644
--- a/test-results/.last-run.json
+++ b/test-results/.last-run.json
@@ -1,4 +1,4 @@
{
- "status": "interrupted",
+ "status": "passed",
"failedTests": []
}
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index c986527..cc64563 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -6,19 +6,29 @@ export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.js'),
- name: 'State',
- formats: ['es']
+ name: 'ApigoState', // UMD 内部名称,逻辑主要靠 globalThis 挂载
+ formats: ['umd', 'es']
},
rollupOptions: {
output: [
{
- format: 'es',
- entryFileNames: 'state.js',
- minifyInternalExports: false
+ format: 'umd',
+ name: 'ApigoState',
+ entryFileNames: 'state.js'
+ },
+ {
+ format: 'umd',
+ name: 'ApigoState',
+ entryFileNames: 'state.min.js',
+ plugins: [terser()]
},
{
format: 'es',
- entryFileNames: 'state.min.js',
+ entryFileNames: 'state.mjs'
+ },
+ {
+ format: 'es',
+ entryFileNames: 'state.min.mjs',
plugins: [terser()]
}
]