release: v1.0.17 - Philosophical Restoration and API Sanitization. By: AICoder
This commit is contained in:
parent
2f4e5967d9
commit
7660de7721
@ -5,7 +5,7 @@
|
|||||||
### 重大变更 (Philosophical Restoration)
|
### 重大变更 (Philosophical Restoration)
|
||||||
- **核心逻辑回位**: 物理还原 v2.3 原始架构,彻底消灭所有 ESM 时期的“Magic”补丁逻辑(如 `_stManaged` 等标志位)。
|
- **核心逻辑回位**: 物理还原 v2.3 原始架构,彻底消灭所有 ESM 时期的“Magic”补丁逻辑(如 `_stManaged` 等标志位)。
|
||||||
- **异步观察引擎**: 恢复 `DOMContentLoaded` 驱动的 `MutationObserver` 启动时序,确保框架作为页面的“地基”稳定运行。
|
- **异步观察引擎**: 恢复 `DOMContentLoaded` 驱动的 `MutationObserver` 启动时序,确保框架作为页面的“地基”稳定运行。
|
||||||
- **危险 API 重命名**: 将 `RefreshState` 重命名为 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。
|
- **危险 API 重命名**: 将 `RefreshState` 重命名为 `_unsafeRefreshState`。此 API 仅供极端性能调优使用,严禁在常规业务中调用,旨在物理隔绝 AI 幻觉和不合理使用。
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
- **升级队列恢复**: 重新启用 `_pendingTemplates` 机制,解决了在同步加载(Head 加载)模式下,组件注册先于 Body 存在时导致的渲染失效问题。
|
- **升级队列恢复**: 重新启用 `_pendingTemplates` 机制,解决了在同步加载(Head 加载)模式下,组件注册先于 Body 存在时导致的渲染失效问题。
|
||||||
|
|||||||
@ -75,6 +75,6 @@ AI 必须根据不同的元素类型执行以下逻辑:
|
|||||||
|
|
||||||
## 6. 运行约束 (Constraints)
|
## 6. 运行约束 (Constraints)
|
||||||
|
|
||||||
1. **禁止滥用同步刷新**:**严禁** 在常规开发中调用 `__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios()`。该函数仅为极高性能干预(如万级数据表格)预留。
|
1. **禁止滥用同步刷新**:**严禁** 在常规开发中调用 `_unsafeRefreshState()`。该函数仅为极高性能干预(如万级数据表格)预留。
|
||||||
2. **数据流向**:所有状态变更必须通过对 `NewState` 代理对象的赋值完成。
|
2. **数据流向**:所有状态变更必须通过对 `NewState` 代理对象的赋值完成。
|
||||||
3. **Key 的必要性**:在大规模数据(>100条)或复杂交互列表中,必须提供唯一 `key` 以激活节点复用逻辑。
|
3. **Key 的必要性**:在大规模数据(>100条)或复杂交互列表中,必须提供唯一 `key` 以激活节点复用逻辑。
|
||||||
|
|||||||
58
dist/state.js
vendored
58
dist/state.js
vendored
@ -3,12 +3,12 @@
|
|||||||
})(this, function(exports2) {
|
})(this, function(exports2) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var _a;
|
var _a;
|
||||||
let _activeBinding = null;
|
let __activeBinding = null;
|
||||||
let _noWriteBack = null;
|
let __noWriteBack = null;
|
||||||
const setActiveBinding = (val) => _activeBinding = val;
|
const _setActiveBinding = (val) => __activeBinding = val;
|
||||||
const setNoWriteBack = (val) => _noWriteBack = val;
|
const _setNoWriteBack = (val) => __noWriteBack = val;
|
||||||
const _notifiers = /* @__PURE__ */ new Set();
|
const _notifiers = /* @__PURE__ */ new Set();
|
||||||
const onNotifyUpdate = (fn) => _notifiers.add(fn);
|
const _onNotifyUpdate = (fn) => _notifiers.add(fn);
|
||||||
function NewState(defaults = {}, getter = null, setter = null) {
|
function NewState(defaults = {}, getter = null, setter = null) {
|
||||||
const _defaults = {};
|
const _defaults = {};
|
||||||
const _stateMappings = /* @__PURE__ */ new Map();
|
const _stateMappings = /* @__PURE__ */ new Map();
|
||||||
@ -30,11 +30,11 @@
|
|||||||
if (key === "__watch") return _watchFunc;
|
if (key === "__watch") return _watchFunc;
|
||||||
if (key === "__unwatch") return _unwatchFunc;
|
if (key === "__unwatch") return _unwatchFunc;
|
||||||
if (key === "__isProxy") return true;
|
if (key === "__isProxy") return true;
|
||||||
if (_activeBinding) {
|
if (__activeBinding) {
|
||||||
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
|
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
|
||||||
_stateMappings.get(key).add(_activeBinding);
|
_stateMappings.get(key).add(__activeBinding);
|
||||||
if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
|
if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set();
|
||||||
_activeBinding.node._states.add(_stateMappings);
|
__activeBinding.node._states.add(_stateMappings);
|
||||||
}
|
}
|
||||||
return __getter(key);
|
return __getter(key);
|
||||||
},
|
},
|
||||||
@ -61,7 +61,7 @@
|
|||||||
bindings.delete(binding);
|
bindings.delete(binding);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_noWriteBack !== binding.node) {
|
if (__noWriteBack !== binding.node) {
|
||||||
_notifiers.forEach((fn) => fn(binding));
|
_notifiers.forEach((fn) => fn(binding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onNotifyUpdate((binding) => _updateBinding(binding));
|
_onNotifyUpdate((binding) => _updateBinding(binding));
|
||||||
function _clearRenderedNodes(node) {
|
function _clearRenderedNodes(node) {
|
||||||
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
|
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
|
||||||
child.remove();
|
child.remove();
|
||||||
@ -216,7 +216,7 @@
|
|||||||
function _updateBinding(binding) {
|
function _updateBinding(binding) {
|
||||||
const node = binding.node;
|
const node = binding.node;
|
||||||
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
|
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
|
||||||
setActiveBinding(binding);
|
_setActiveBinding(binding);
|
||||||
let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
|
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") {
|
if (binding.exp === 2 && typeof result === "string") {
|
||||||
try {
|
try {
|
||||||
@ -224,7 +224,7 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveBinding(null);
|
_setActiveBinding(null);
|
||||||
if (binding.prop) {
|
if (binding.prop) {
|
||||||
const prop = binding.prop;
|
const prop = binding.prop;
|
||||||
let o = node;
|
let o = node;
|
||||||
@ -235,10 +235,9 @@
|
|||||||
if (typeof o !== "object") break;
|
if (typeof o !== "object") break;
|
||||||
}
|
}
|
||||||
if (typeof o === "object" && o !== null) {
|
if (typeof o === "object" && o !== null) {
|
||||||
const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result);
|
|
||||||
const lk = prop[prop.length - 1];
|
const lk = prop[prop.length - 1];
|
||||||
if (lk) {
|
if (lk) {
|
||||||
if (resultIsObject && o[lk] == null) o[lk] = {};
|
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
|
||||||
const lo = o[lk];
|
const lo = o[lk];
|
||||||
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
|
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
|
||||||
else {
|
else {
|
||||||
@ -419,12 +418,12 @@
|
|||||||
if (realAttrName === "bind") {
|
if (realAttrName === "bind") {
|
||||||
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
|
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
|
||||||
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
|
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
|
||||||
setNoWriteBack(node);
|
_setNoWriteBack(node);
|
||||||
setDisableRunCodeError(true);
|
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 || {});
|
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 || {});
|
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
|
||||||
setDisableRunCodeError(false);
|
setDisableRunCodeError(false);
|
||||||
setNoWriteBack(null);
|
_setNoWriteBack(null);
|
||||||
});
|
});
|
||||||
} else if (realAttrName === "text" && !tpl) {
|
} else if (realAttrName === "text" && !tpl) {
|
||||||
tpl = node.textContent;
|
tpl = node.textContent;
|
||||||
@ -451,6 +450,19 @@
|
|||||||
});
|
});
|
||||||
node._stTranslated = true;
|
node._stTranslated = true;
|
||||||
}
|
}
|
||||||
|
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
|
||||||
|
const template = document.createElement("TEMPLATE");
|
||||||
|
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
|
||||||
|
attrs.forEach((attr) => {
|
||||||
|
template.setAttribute(attr.name, attr.value);
|
||||||
|
node.removeAttribute(attr.name);
|
||||||
|
});
|
||||||
|
node.parentNode.insertBefore(template, node);
|
||||||
|
template.content.appendChild(node);
|
||||||
|
template._ref = node._ref;
|
||||||
|
_scanTree(template, scanObj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
|
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
|
||||||
const template = document.createElement("TEMPLATE");
|
const template = document.createElement("TEMPLATE");
|
||||||
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
|
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
|
||||||
@ -508,7 +520,7 @@
|
|||||||
});
|
});
|
||||||
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
|
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
|
||||||
};
|
};
|
||||||
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
|
const _unsafeRefreshState = _scanTree;
|
||||||
const Util = {
|
const Util = {
|
||||||
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
|
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
|
||||||
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
||||||
@ -603,18 +615,14 @@
|
|||||||
Component,
|
Component,
|
||||||
$,
|
$,
|
||||||
$$,
|
$$,
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
|
RefreshState: _unsafeRefreshState,
|
||||||
SetTranslator,
|
SetTranslator,
|
||||||
_scanTree,
|
_scanTree,
|
||||||
_unbindTree,
|
_unbindTree,
|
||||||
Util,
|
Util,
|
||||||
Hash,
|
Hash,
|
||||||
LocalStorage,
|
LocalStorage,
|
||||||
State,
|
State
|
||||||
_runCode,
|
|
||||||
_returnCode,
|
|
||||||
onNotifyUpdate,
|
|
||||||
setActiveBinding
|
|
||||||
};
|
};
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.ApigoState = ApigoState;
|
window.ApigoState = ApigoState;
|
||||||
@ -645,10 +653,10 @@
|
|||||||
exports2.Hash = Hash;
|
exports2.Hash = Hash;
|
||||||
exports2.LocalStorage = LocalStorage;
|
exports2.LocalStorage = LocalStorage;
|
||||||
exports2.NewState = NewState;
|
exports2.NewState = NewState;
|
||||||
|
exports2.RefreshState = _unsafeRefreshState;
|
||||||
exports2.SetTranslator = SetTranslator;
|
exports2.SetTranslator = SetTranslator;
|
||||||
exports2.State = State;
|
exports2.State = State;
|
||||||
exports2.Util = Util;
|
exports2.Util = Util;
|
||||||
exports2.____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios;
|
|
||||||
exports2._scanTree = _scanTree;
|
exports2._scanTree = _scanTree;
|
||||||
exports2._unbindTree = _unbindTree;
|
exports2._unbindTree = _unbindTree;
|
||||||
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
||||||
|
|||||||
2
dist/state.min.js
vendored
2
dist/state.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/state.min.mjs
vendored
2
dist/state.min.mjs
vendored
File diff suppressed because one or more lines are too long
58
dist/state.mjs
vendored
58
dist/state.mjs
vendored
@ -1,10 +1,10 @@
|
|||||||
var _a;
|
var _a;
|
||||||
let _activeBinding = null;
|
let __activeBinding = null;
|
||||||
let _noWriteBack = null;
|
let __noWriteBack = null;
|
||||||
const setActiveBinding = (val) => _activeBinding = val;
|
const _setActiveBinding = (val) => __activeBinding = val;
|
||||||
const setNoWriteBack = (val) => _noWriteBack = val;
|
const _setNoWriteBack = (val) => __noWriteBack = val;
|
||||||
const _notifiers = /* @__PURE__ */ new Set();
|
const _notifiers = /* @__PURE__ */ new Set();
|
||||||
const onNotifyUpdate = (fn) => _notifiers.add(fn);
|
const _onNotifyUpdate = (fn) => _notifiers.add(fn);
|
||||||
function NewState(defaults = {}, getter = null, setter = null) {
|
function NewState(defaults = {}, getter = null, setter = null) {
|
||||||
const _defaults = {};
|
const _defaults = {};
|
||||||
const _stateMappings = /* @__PURE__ */ new Map();
|
const _stateMappings = /* @__PURE__ */ new Map();
|
||||||
@ -26,11 +26,11 @@ function NewState(defaults = {}, getter = null, setter = null) {
|
|||||||
if (key === "__watch") return _watchFunc;
|
if (key === "__watch") return _watchFunc;
|
||||||
if (key === "__unwatch") return _unwatchFunc;
|
if (key === "__unwatch") return _unwatchFunc;
|
||||||
if (key === "__isProxy") return true;
|
if (key === "__isProxy") return true;
|
||||||
if (_activeBinding) {
|
if (__activeBinding) {
|
||||||
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
|
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
|
||||||
_stateMappings.get(key).add(_activeBinding);
|
_stateMappings.get(key).add(__activeBinding);
|
||||||
if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
|
if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set();
|
||||||
_activeBinding.node._states.add(_stateMappings);
|
__activeBinding.node._states.add(_stateMappings);
|
||||||
}
|
}
|
||||||
return __getter(key);
|
return __getter(key);
|
||||||
},
|
},
|
||||||
@ -57,7 +57,7 @@ function NewState(defaults = {}, getter = null, setter = null) {
|
|||||||
bindings.delete(binding);
|
bindings.delete(binding);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_noWriteBack !== binding.node) {
|
if (__noWriteBack !== binding.node) {
|
||||||
_notifiers.forEach((fn) => fn(binding));
|
_notifiers.forEach((fn) => fn(binding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ if (typeof document !== "undefined") {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onNotifyUpdate((binding) => _updateBinding(binding));
|
_onNotifyUpdate((binding) => _updateBinding(binding));
|
||||||
function _clearRenderedNodes(node) {
|
function _clearRenderedNodes(node) {
|
||||||
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
|
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
|
||||||
child.remove();
|
child.remove();
|
||||||
@ -212,7 +212,7 @@ function _clearRenderedNodes(node) {
|
|||||||
function _updateBinding(binding) {
|
function _updateBinding(binding) {
|
||||||
const node = binding.node;
|
const node = binding.node;
|
||||||
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
|
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
|
||||||
setActiveBinding(binding);
|
_setActiveBinding(binding);
|
||||||
let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
|
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") {
|
if (binding.exp === 2 && typeof result === "string") {
|
||||||
try {
|
try {
|
||||||
@ -220,7 +220,7 @@ function _updateBinding(binding) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveBinding(null);
|
_setActiveBinding(null);
|
||||||
if (binding.prop) {
|
if (binding.prop) {
|
||||||
const prop = binding.prop;
|
const prop = binding.prop;
|
||||||
let o = node;
|
let o = node;
|
||||||
@ -231,10 +231,9 @@ function _updateBinding(binding) {
|
|||||||
if (typeof o !== "object") break;
|
if (typeof o !== "object") break;
|
||||||
}
|
}
|
||||||
if (typeof o === "object" && o !== null) {
|
if (typeof o === "object" && o !== null) {
|
||||||
const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result);
|
|
||||||
const lk = prop[prop.length - 1];
|
const lk = prop[prop.length - 1];
|
||||||
if (lk) {
|
if (lk) {
|
||||||
if (resultIsObject && o[lk] == null) o[lk] = {};
|
if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
|
||||||
const lo = o[lk];
|
const lo = o[lk];
|
||||||
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
|
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
|
||||||
else {
|
else {
|
||||||
@ -415,12 +414,12 @@ const _parseNode = (node, scanObj) => {
|
|||||||
if (realAttrName === "bind") {
|
if (realAttrName === "bind") {
|
||||||
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
|
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
|
||||||
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
|
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
|
||||||
setNoWriteBack(node);
|
_setNoWriteBack(node);
|
||||||
setDisableRunCodeError(true);
|
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 || {});
|
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 || {});
|
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
|
||||||
setDisableRunCodeError(false);
|
setDisableRunCodeError(false);
|
||||||
setNoWriteBack(null);
|
_setNoWriteBack(null);
|
||||||
});
|
});
|
||||||
} else if (realAttrName === "text" && !tpl) {
|
} else if (realAttrName === "text" && !tpl) {
|
||||||
tpl = node.textContent;
|
tpl = node.textContent;
|
||||||
@ -447,6 +446,19 @@ const _scanTree = (node, scanObj = {}) => {
|
|||||||
});
|
});
|
||||||
node._stTranslated = true;
|
node._stTranslated = true;
|
||||||
}
|
}
|
||||||
|
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
|
||||||
|
const template = document.createElement("TEMPLATE");
|
||||||
|
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
|
||||||
|
attrs.forEach((attr) => {
|
||||||
|
template.setAttribute(attr.name, attr.value);
|
||||||
|
node.removeAttribute(attr.name);
|
||||||
|
});
|
||||||
|
node.parentNode.insertBefore(template, node);
|
||||||
|
template.content.appendChild(node);
|
||||||
|
template._ref = node._ref;
|
||||||
|
_scanTree(template, scanObj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
|
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
|
||||||
const template = document.createElement("TEMPLATE");
|
const template = document.createElement("TEMPLATE");
|
||||||
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
|
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
|
||||||
@ -504,7 +516,7 @@ const _unbindTree = (node) => {
|
|||||||
});
|
});
|
||||||
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
|
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
|
||||||
};
|
};
|
||||||
const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
|
const _unsafeRefreshState = _scanTree;
|
||||||
const Util = {
|
const Util = {
|
||||||
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
|
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
|
||||||
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
|
||||||
@ -599,18 +611,14 @@ const ApigoState = {
|
|||||||
Component,
|
Component,
|
||||||
$,
|
$,
|
||||||
$$,
|
$$,
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
|
RefreshState: _unsafeRefreshState,
|
||||||
SetTranslator,
|
SetTranslator,
|
||||||
_scanTree,
|
_scanTree,
|
||||||
_unbindTree,
|
_unbindTree,
|
||||||
Util,
|
Util,
|
||||||
Hash,
|
Hash,
|
||||||
LocalStorage,
|
LocalStorage,
|
||||||
State,
|
State
|
||||||
_runCode,
|
|
||||||
_returnCode,
|
|
||||||
onNotifyUpdate,
|
|
||||||
setActiveBinding
|
|
||||||
};
|
};
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.ApigoState = ApigoState;
|
window.ApigoState = ApigoState;
|
||||||
@ -642,10 +650,10 @@ export {
|
|||||||
Hash,
|
Hash,
|
||||||
LocalStorage,
|
LocalStorage,
|
||||||
NewState,
|
NewState,
|
||||||
|
_unsafeRefreshState as RefreshState,
|
||||||
SetTranslator,
|
SetTranslator,
|
||||||
State,
|
State,
|
||||||
Util,
|
Util,
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
|
|
||||||
_scanTree,
|
_scanTree,
|
||||||
_unbindTree
|
_unbindTree
|
||||||
};
|
};
|
||||||
|
|||||||
@ -475,7 +475,7 @@
|
|||||||
}
|
}
|
||||||
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
|
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
|
||||||
}
|
}
|
||||||
globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree
|
globalThis._unsafeRefreshState = _scanTree
|
||||||
|
|
||||||
const _components = new Map()
|
const _components = new Map()
|
||||||
const _pendingTemplates = []
|
const _pendingTemplates = []
|
||||||
|
|||||||
@ -458,7 +458,7 @@
|
|||||||
}
|
}
|
||||||
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
|
node.childNodes && node.childNodes.forEach(child => _unbindTree(child))
|
||||||
}
|
}
|
||||||
globalThis.__RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree
|
globalThis._unsafeRefreshState = _scanTree
|
||||||
|
|
||||||
const _components = new Map()
|
const _components = new Map()
|
||||||
const _pendingTemplates = []
|
const _pendingTemplates = []
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@apigo.cc/state",
|
"name": "@apigo.cc/state",
|
||||||
"version": "1.0.16",
|
"version": "1.0.17",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/state.js",
|
"main": "dist/state.js",
|
||||||
"module": "dist/state.js",
|
"module": "dist/state.js",
|
||||||
|
|||||||
@ -363,4 +363,4 @@ export const _unbindTree = (node) => {
|
|||||||
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
|
export const ___unsafeRefreshState = _scanTree;
|
||||||
|
|||||||
37
src/dom.js
37
src/dom.js
@ -1,6 +1,6 @@
|
|||||||
// src/dom.js
|
// src/dom.js
|
||||||
import { _runCode, _returnCode, setDisableRunCodeError } from './core.js';
|
import { _runCode, _returnCode, setDisableRunCodeError } from './core.js';
|
||||||
import { getActiveBinding, setActiveBinding, getNoWriteBack, setNoWriteBack, NewState, onNotifyUpdate } from './observer.js';
|
import { _getActiveBinding, _setActiveBinding, _getNoWriteBack, _setNoWriteBack, NewState, _onNotifyUpdate } from './observer.js';
|
||||||
import { Component, _makeComponent, _mergeNode } from './component.js';
|
import { Component, _makeComponent, _mergeNode } from './component.js';
|
||||||
import { $, $$ } from './dom-utils.js';
|
import { $, $$ } from './dom-utils.js';
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ if (typeof document !== 'undefined') {
|
|||||||
|
|
||||||
export { $, $$ };
|
export { $, $$ };
|
||||||
|
|
||||||
onNotifyUpdate((binding) => _updateBinding(binding));
|
_onNotifyUpdate((binding) => _updateBinding(binding));
|
||||||
|
|
||||||
export function _clearRenderedNodes(node) {
|
export function _clearRenderedNodes(node) {
|
||||||
if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => {
|
if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => {
|
||||||
@ -48,14 +48,13 @@ export function _updateBinding(binding) {
|
|||||||
const node = binding.node;
|
const node = binding.node;
|
||||||
if (!node.isConnected && node.tagName !== 'TEMPLATE') return;
|
if (!node.isConnected && node.tagName !== 'TEMPLATE') return;
|
||||||
|
|
||||||
setActiveBinding(binding);
|
_setActiveBinding(binding);
|
||||||
let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl;
|
let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl;
|
||||||
|
|
||||||
// 增量 3: 支持 $$ 前缀的双重评估
|
|
||||||
if (binding.exp === 2 && typeof result === 'string') {
|
if (binding.exp === 2 && typeof result === 'string') {
|
||||||
try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { }
|
try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { }
|
||||||
}
|
}
|
||||||
setActiveBinding(null);
|
_setActiveBinding(null);
|
||||||
|
|
||||||
if (binding.prop) {
|
if (binding.prop) {
|
||||||
const prop = binding.prop;
|
const prop = binding.prop;
|
||||||
@ -67,10 +66,9 @@ export function _updateBinding(binding) {
|
|||||||
if (typeof o !== 'object') break;
|
if (typeof o !== 'object') break;
|
||||||
}
|
}
|
||||||
if (typeof o === 'object' && o !== null) {
|
if (typeof o === 'object' && o !== null) {
|
||||||
const resultIsObject = typeof result === 'object' && result != null && !Array.isArray(result);
|
|
||||||
const lk = prop[prop.length - 1];
|
const lk = prop[prop.length - 1];
|
||||||
if (lk) {
|
if (lk) {
|
||||||
if (resultIsObject && o[lk] == null) o[lk] = {};
|
if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
|
||||||
const lo = o[lk];
|
const lo = o[lk];
|
||||||
if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result);
|
if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result);
|
||||||
else { if (o[lk] !== result) o[lk] = result; }
|
else { if (o[lk] !== result) o[lk] = result; }
|
||||||
@ -98,7 +96,7 @@ export function _updateBinding(binding) {
|
|||||||
if (result && typeof result === 'object') {
|
if (result && typeof result === 'object') {
|
||||||
const asName = node.getAttribute('as') || 'item';
|
const asName = node.getAttribute('as') || 'item';
|
||||||
const indexName = node.getAttribute('index') || 'index';
|
const indexName = node.getAttribute('index') || 'index';
|
||||||
const keyName = node.getAttribute('key'); // 增量 2: 支持 key
|
const keyName = node.getAttribute('key');
|
||||||
let keys, getVal;
|
let keys, getVal;
|
||||||
if (result instanceof Map) {
|
if (result instanceof Map) {
|
||||||
keys = Array.from(result.keys()); getVal = k => result.get(k);
|
keys = Array.from(result.keys()); getVal = k => result.get(k);
|
||||||
@ -249,10 +247,10 @@ export const _parseNode = (node, scanObj) => {
|
|||||||
if (realAttrName === 'bind') {
|
if (realAttrName === 'bind') {
|
||||||
node.addEventListener(['textarea', 'text', 'password'].includes(node.type || 'text') || node.isContentEditable ? 'input' : 'change', (e) => {
|
node.addEventListener(['textarea', 'text', 'password'].includes(node.type || 'text') || node.isContentEditable ? 'input' : 'change', (e) => {
|
||||||
let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail);
|
let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail);
|
||||||
setNoWriteBack(node); setDisableRunCodeError(true);
|
_setNoWriteBack(node); setDisableRunCodeError(true);
|
||||||
if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
|
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 || {});
|
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
|
||||||
setDisableRunCodeError(false); setNoWriteBack(null);
|
setDisableRunCodeError(false); _setNoWriteBack(null);
|
||||||
});
|
});
|
||||||
} else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; }
|
} else if (realAttrName === 'text' && !tpl) { tpl = node.textContent; node.textContent = ''; }
|
||||||
if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); }
|
if (tpl) { tpl = _translate(tpl); _initBinding({ node, attr: realAttrName, tpl, exp }); }
|
||||||
@ -277,6 +275,22 @@ export const _scanTree = (node, scanObj = {}) => {
|
|||||||
node._stTranslated = true;
|
node._stTranslated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 还原原始逻辑:自动为非模板节点的 $if 和 $each 指令创建模版节点
|
||||||
|
if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each'))) {
|
||||||
|
const template = document.createElement('TEMPLATE');
|
||||||
|
const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each')) && ['as', 'index'].includes(attr.name)));
|
||||||
|
attrs.forEach(attr => {
|
||||||
|
template.setAttribute(attr.name, attr.value);
|
||||||
|
node.removeAttribute(attr.name);
|
||||||
|
});
|
||||||
|
node.parentNode.insertBefore(template, node);
|
||||||
|
template.content.appendChild(node);
|
||||||
|
template._ref = node._ref;
|
||||||
|
// 修正原始版本可能存在的漏扫:转换后立即对新生成的 TEMPLATE 发起递归扫描
|
||||||
|
_scanTree(template, scanObj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each'))) {
|
if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each'))) {
|
||||||
const template = document.createElement('TEMPLATE');
|
const template = document.createElement('TEMPLATE');
|
||||||
const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name));
|
const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name));
|
||||||
@ -317,7 +331,6 @@ export const _scanTree = (node, scanObj = {}) => {
|
|||||||
while (curr && curr._ref === undefined) curr = curr.parentNode;
|
while (curr && curr._ref === undefined) curr = curr.parentNode;
|
||||||
node._ref = curr ? { ...curr._ref } : {};
|
node._ref = curr ? { ...curr._ref } : {};
|
||||||
}
|
}
|
||||||
// 增量 4: 支持 _refExt 上下文注入
|
|
||||||
if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
|
if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars);
|
||||||
|
|
||||||
_parseNode(node, { ...scanObj });
|
_parseNode(node, { ...scanObj });
|
||||||
@ -337,4 +350,4 @@ export const _unbindTree = (node) => {
|
|||||||
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
|
export const _unsafeRefreshState = _scanTree;
|
||||||
|
|||||||
@ -349,4 +349,4 @@ export const _unbindTree = (node) => {
|
|||||||
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
node.childNodes && node.childNodes.forEach(child => _unbindTree(child));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
|
export const _unsafeRefreshState = _scanTree;
|
||||||
|
|||||||
12
src/index.js
12
src/index.js
@ -1,20 +1,19 @@
|
|||||||
// src/index.js
|
// src/index.js
|
||||||
export { NewState } from './observer.js';
|
export { NewState } from './observer.js';
|
||||||
export { Component } from './component.js';
|
export { Component } from './component.js';
|
||||||
export { $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree } from './dom.js';
|
export { $, $$, _unsafeRefreshState as RefreshState, SetTranslator, _scanTree, _unbindTree } from './dom.js';
|
||||||
export { Util } from './utils.js';
|
export { Util } from './utils.js';
|
||||||
export { Hash, LocalStorage, State } from './globals.js';
|
export { Hash, LocalStorage, State } from './globals.js';
|
||||||
|
|
||||||
import { Component } from './component.js';
|
import { Component } from './component.js';
|
||||||
import { _scanTree, _unbindTree, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator } from './dom.js';
|
import { _scanTree, _unbindTree, $, $$, _unsafeRefreshState, SetTranslator } from './dom.js';
|
||||||
import { LocalStorage, Hash, State } from './globals.js';
|
import { LocalStorage, Hash, State } from './globals.js';
|
||||||
import { NewState, onNotifyUpdate, setActiveBinding } from './observer.js';
|
import { NewState, _onNotifyUpdate, _setActiveBinding } from './observer.js';
|
||||||
import { Util } from './utils.js';
|
import { Util } from './utils.js';
|
||||||
import { _runCode, _returnCode } from './core.js';
|
import { _runCode, _returnCode } from './core.js';
|
||||||
|
|
||||||
const ApigoState = {
|
const ApigoState = {
|
||||||
NewState, Component, $, $$, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State,
|
NewState, Component, $, $$, RefreshState: _unsafeRefreshState, SetTranslator, _scanTree, _unbindTree, Util, Hash, LocalStorage, State
|
||||||
_runCode, _returnCode, onNotifyUpdate, setActiveBinding
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@ -23,15 +22,12 @@ if (typeof window !== 'undefined') {
|
|||||||
|
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const init = () => {
|
const init = () => {
|
||||||
// 关键修复:在启动观察器和首次扫描前,务必将所有等待中的组件模板注入 DOM!
|
|
||||||
Component._initPending();
|
Component._initPending();
|
||||||
|
|
||||||
const htmlNode = document.documentElement;
|
const htmlNode = document.documentElement;
|
||||||
if (!htmlNode.hasAttribute('$data-bs-theme') && !htmlNode.hasAttribute('data-bs-theme')) {
|
if (!htmlNode.hasAttribute('$data-bs-theme') && !htmlNode.hasAttribute('data-bs-theme')) {
|
||||||
htmlNode.setAttribute('$data-bs-theme', "LocalStorage.darkMode?'dark':'light'");
|
htmlNode.setAttribute('$data-bs-theme', "LocalStorage.darkMode?'dark':'light'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 灵魂逻辑:MutationObserver 捕获所有异步 DOM 变化
|
|
||||||
new MutationObserver(mutations => {
|
new MutationObserver(mutations => {
|
||||||
mutations.forEach(mutation => {
|
mutations.forEach(mutation => {
|
||||||
mutation.addedNodes.forEach(newNode => {
|
mutation.addedNodes.forEach(newNode => {
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
// src/observer.js
|
// src/observer.js
|
||||||
let _activeBinding = null;
|
let __activeBinding = null;
|
||||||
let _noWriteBack = null;
|
let __noWriteBack = null;
|
||||||
|
|
||||||
export const getActiveBinding = () => _activeBinding;
|
export const _getActiveBinding = () => __activeBinding;
|
||||||
export const setActiveBinding = (val) => _activeBinding = val;
|
export const _setActiveBinding = (val) => __activeBinding = val;
|
||||||
export const getNoWriteBack = () => _noWriteBack;
|
export const _getNoWriteBack = () => __noWriteBack;
|
||||||
export const setNoWriteBack = (val) => _noWriteBack = val;
|
export const _setNoWriteBack = (val) => __noWriteBack = val;
|
||||||
|
|
||||||
const _notifiers = new Set();
|
const _notifiers = new Set();
|
||||||
export const onNotifyUpdate = (fn) => _notifiers.add(fn);
|
export const _onNotifyUpdate = (fn) => _notifiers.add(fn);
|
||||||
|
|
||||||
export function NewState(defaults = {}, getter = null, setter = null) {
|
export function NewState(defaults = {}, getter = null, setter = null) {
|
||||||
const _defaults = {};
|
const _defaults = {};
|
||||||
@ -36,11 +36,11 @@ export function NewState(defaults = {}, getter = null, setter = null) {
|
|||||||
if (key === '__unwatch') return _unwatchFunc;
|
if (key === '__unwatch') return _unwatchFunc;
|
||||||
if (key === '__isProxy') return true;
|
if (key === '__isProxy') return true;
|
||||||
|
|
||||||
if (_activeBinding) {
|
if (__activeBinding) {
|
||||||
if (!_stateMappings.has(key)) _stateMappings.set(key, new Set());
|
if (!_stateMappings.has(key)) _stateMappings.set(key, new Set());
|
||||||
_stateMappings.get(key).add(_activeBinding);
|
_stateMappings.get(key).add(__activeBinding);
|
||||||
if (!_activeBinding.node._states) _activeBinding.node._states = new Set();
|
if (!__activeBinding.node._states) __activeBinding.node._states = new Set();
|
||||||
_activeBinding.node._states.add(_stateMappings);
|
__activeBinding.node._states.add(_stateMappings);
|
||||||
}
|
}
|
||||||
return __getter(key);
|
return __getter(key);
|
||||||
},
|
},
|
||||||
@ -67,7 +67,7 @@ export function NewState(defaults = {}, getter = null, setter = null) {
|
|||||||
bindings.delete(binding);
|
bindings.delete(binding);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_noWriteBack !== binding.node) {
|
if (__noWriteBack !== binding.node) {
|
||||||
_notifiers.forEach(fn => fn(binding));
|
_notifiers.forEach(fn => fn(binding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,8 +56,8 @@ Received: "failed"
|
|||||||
32 | const items = [];
|
32 | const items = [];
|
||||||
33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
||||||
34 | window.state.benchItems = items;
|
34 | window.state.benchItems = items;
|
||||||
35 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
|
35 | const { _unsafeRefreshState } = await import('@apigo.cc/state');
|
||||||
36 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
36 | _unsafeRefreshState(document.documentElement);
|
||||||
37 | return performance.now() - start;
|
37 | return performance.now() - start;
|
||||||
38 | });
|
38 | });
|
||||||
39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
||||||
@ -75,10 +75,10 @@ Received: "failed"
|
|||||||
51 |
|
51 |
|
||||||
52 | // Extreme Data Test
|
52 | // Extreme Data Test
|
||||||
53 | await page.evaluate(async () => {
|
53 | await page.evaluate(async () => {
|
||||||
54 | const { __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
|
54 | const { _unsafeRefreshState } = await import('@apigo.cc/state');
|
||||||
55 | document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
55 | document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
||||||
56 | window.state.extreme = null;
|
56 | window.state.extreme = null;
|
||||||
57 | __RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.getElementById('extreme'));
|
57 | _unsafeRefreshState(document.getElementById('extreme'));
|
||||||
58 | window.state.extreme = undefined;
|
58 | window.state.extreme = undefined;
|
||||||
59 | window.state.extreme = { a: 1 };
|
59 | window.state.extreme = { a: 1 };
|
||||||
60 | window.state.extreme = [1, 2];
|
60 | window.state.extreme = [1, 2];
|
||||||
|
|||||||
@ -32,8 +32,8 @@ test('modular unit tests and benchmark', async ({ page }) => {
|
|||||||
const items = [];
|
const items = [];
|
||||||
for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
||||||
window.state.benchItems = items;
|
window.state.benchItems = items;
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
|
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
return performance.now() - start;
|
return performance.now() - start;
|
||||||
});
|
});
|
||||||
console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
||||||
@ -51,10 +51,10 @@ test('modular unit tests and benchmark', async ({ page }) => {
|
|||||||
|
|
||||||
// Extreme Data Test
|
// Extreme Data Test
|
||||||
await page.evaluate(async () => {
|
await page.evaluate(async () => {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios } = await import('@apigo.cc/state');
|
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
|
||||||
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
||||||
window.state.extreme = null;
|
window.state.extreme = null;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.getElementById('extreme'));
|
___unsafeRefreshState(document.getElementById('extreme'));
|
||||||
window.state.extreme = undefined;
|
window.state.extreme = undefined;
|
||||||
window.state.extreme = { a: 1 };
|
window.state.extreme = { a: 1 };
|
||||||
window.state.extreme = [1, 2];
|
window.state.extreme = [1, 2];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/component.test.js
|
// test/component.test.js
|
||||||
window.testComponent = async function() {
|
window.testComponent = async function() {
|
||||||
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState;
|
const { Component, ___unsafeRefreshState, $ } = ApigoState;
|
||||||
console.log('Testing component.js...');
|
console.log('Testing component.js...');
|
||||||
|
|
||||||
// 1. Register component
|
// 1. Register component
|
||||||
@ -10,7 +10,7 @@ window.testComponent = async function() {
|
|||||||
|
|
||||||
// 2. Render component
|
// 2. Render component
|
||||||
document.body.innerHTML = '<TestComp id="comp-inst"></TestComp>';
|
document.body.innerHTML = '<TestComp id="comp-inst"></TestComp>';
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
const instance = $('#comp-inst');
|
const instance = $('#comp-inst');
|
||||||
if (!instance) throw new Error('Component instance not found');
|
if (!instance) throw new Error('Component instance not found');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/dom.test.js
|
// test/dom.test.js
|
||||||
window.testDom = async function() {
|
window.testDom = async function() {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, $$, NewState } = ApigoState;
|
const { ___unsafeRefreshState, $, $$, NewState } = ApigoState;
|
||||||
console.log('Testing dom.js...');
|
console.log('Testing dom.js...');
|
||||||
|
|
||||||
const wait = () => new Promise(r => setTimeout(r, 10));
|
const wait = () => new Promise(r => setTimeout(r, 10));
|
||||||
@ -10,7 +10,7 @@ window.testDom = async function() {
|
|||||||
const state = NewState({ msg: 'hello' });
|
const state = NewState({ msg: 'hello' });
|
||||||
window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见
|
window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见
|
||||||
document.documentElement._thisObj = { state };
|
document.documentElement._thisObj = { state };
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed');
|
if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed');
|
||||||
|
|
||||||
state.msg = 'world';
|
state.msg = 'world';
|
||||||
@ -20,7 +20,7 @@ window.testDom = async function() {
|
|||||||
// 2. $if directive
|
// 2. $if directive
|
||||||
document.body.innerHTML = '<template $if="state.show"><div id="test-if">visible</div></template>';
|
document.body.innerHTML = '<template $if="state.show"><div id="test-if">visible</div></template>';
|
||||||
state.show = false;
|
state.show = false;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
if ($('#test-if')) throw new Error('$if fail: should be hidden');
|
if ($('#test-if')) throw new Error('$if fail: should be hidden');
|
||||||
|
|
||||||
state.show = true;
|
state.show = true;
|
||||||
@ -30,7 +30,7 @@ window.testDom = async function() {
|
|||||||
// 3. $each directive
|
// 3. $each directive
|
||||||
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
|
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
|
||||||
state.items = ['A', 'B'];
|
state.items = ['A', 'B'];
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch');
|
if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch');
|
||||||
if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch');
|
if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch');
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ window.testDom = async function() {
|
|||||||
// 4. Event binding $onclick
|
// 4. Event binding $onclick
|
||||||
document.body.innerHTML = '<button id="test-click" $onclick="state.count++"></button>';
|
document.body.innerHTML = '<button id="test-click" $onclick="state.count++"></button>';
|
||||||
state.count = 0;
|
state.count = 0;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
$('#test-click').click();
|
$('#test-click').click();
|
||||||
if (state.count !== 1) throw new Error('$onclick failed');
|
if (state.count !== 1) throw new Error('$onclick failed');
|
||||||
|
|
||||||
// 5. $bind (input)
|
// 5. $bind (input)
|
||||||
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
|
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
|
||||||
state.val = 'init';
|
state.val = 'init';
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
await wait(); // TRY: 等待 $bind 的 setTimeout 完成
|
await wait(); // TRY: 等待 $bind 的 setTimeout 完成
|
||||||
const input = $('#test-bind');
|
const input = $('#test-bind');
|
||||||
if (input.value !== 'init') throw new Error('$bind initial failed');
|
if (input.value !== 'init') throw new Error('$bind initial failed');
|
||||||
@ -74,13 +74,13 @@ window.testDom = async function() {
|
|||||||
const root = $('#double-eval-root');
|
const root = $('#double-eval-root');
|
||||||
root._thisObj = { state: doubleState };
|
root._thisObj = { state: doubleState };
|
||||||
|
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root);
|
___unsafeRefreshState(root);
|
||||||
await wait();
|
await wait();
|
||||||
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
|
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
|
||||||
|
|
||||||
console.log('Enabling inner node...');
|
console.log('Enabling inner node...');
|
||||||
doubleState.innerShow = true;
|
doubleState.innerShow = true;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(root);
|
___unsafeRefreshState(root);
|
||||||
await wait();
|
await wait();
|
||||||
const inner = $('#inner-node');
|
const inner = $('#inner-node');
|
||||||
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
|
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
|
||||||
@ -100,12 +100,12 @@ window.testDom = async function() {
|
|||||||
nestedRoot._thisObj = { state: doubleState };
|
nestedRoot._thisObj = { state: doubleState };
|
||||||
doubleState.outer = true;
|
doubleState.outer = true;
|
||||||
doubleState.innerShow = false;
|
doubleState.innerShow = false;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot);
|
___unsafeRefreshState(nestedRoot);
|
||||||
await wait();
|
await wait();
|
||||||
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
|
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
|
||||||
|
|
||||||
doubleState.innerShow = true;
|
doubleState.innerShow = true;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(nestedRoot);
|
___unsafeRefreshState(nestedRoot);
|
||||||
await wait();
|
await wait();
|
||||||
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');
|
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');
|
||||||
|
|
||||||
|
|||||||
4
test/foundation.test.js
vendored
4
test/foundation.test.js
vendored
@ -1,6 +1,6 @@
|
|||||||
// test/foundation.test.js
|
// test/foundation.test.js
|
||||||
window.testFoundation = async function() {
|
window.testFoundation = async function() {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
|
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing framework foundation...');
|
console.log('Testing framework foundation...');
|
||||||
|
|
||||||
Component.register('NavTest', container => {
|
Component.register('NavTest', container => {
|
||||||
@ -26,7 +26,7 @@ window.testFoundation = async function() {
|
|||||||
const inst = document.createElement('NAVTEST');
|
const inst = document.createElement('NAVTEST');
|
||||||
inst.id = 'nav-inst';
|
inst.id = 'nav-inst';
|
||||||
document.body.appendChild(inst);
|
document.body.appendChild(inst);
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
const nav = $('#nav-inst');
|
const nav = $('#nav-inst');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/inheritance.test.js
|
// test/inheritance.test.js
|
||||||
window.testInheritance = async function() {
|
window.testInheritance = async function() {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
|
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing inheritance...');
|
console.log('Testing inheritance...');
|
||||||
|
|
||||||
Component.register('MyComp', container => {
|
Component.register('MyComp', container => {
|
||||||
@ -26,7 +26,7 @@ window.testInheritance = async function() {
|
|||||||
inst.id = 'comp-inst';
|
inst.id = 'comp-inst';
|
||||||
document.body.appendChild(inst);
|
document.body.appendChild(inst);
|
||||||
|
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
const comp = $('#comp-inst');
|
const comp = $('#comp-inst');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/merging.test.js
|
// test/merging.test.js
|
||||||
window.testMerging = async function() {
|
window.testMerging = async function() {
|
||||||
const { Component, ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $ } = ApigoState;
|
const { Component, ___unsafeRefreshState, $ } = ApigoState;
|
||||||
console.log('Testing complex class/style merging (DataTable Resizer scenario)...');
|
console.log('Testing complex class/style merging (DataTable Resizer scenario)...');
|
||||||
|
|
||||||
Component.register('Resizer-Real', container => {
|
Component.register('Resizer-Real', container => {
|
||||||
@ -8,7 +8,7 @@ window.testMerging = async function() {
|
|||||||
}, document.createRange().createContextualFragment('<div $class="tpl-static ${this.isVertical?\'tpl-v\':\'tpl-h\'}" $style="color:blue"></div>').firstChild);
|
}, document.createRange().createContextualFragment('<div $class="tpl-static ${this.isVertical?\'tpl-v\':\'tpl-h\'}" $style="color:blue"></div>').firstChild);
|
||||||
|
|
||||||
document.body.innerHTML = '<Resizer-Real id="res-inst" class="ins-static" style="opacity:0.5"></Resizer-Real>';
|
document.body.innerHTML = '<Resizer-Real id="res-inst" class="ins-static" style="opacity:0.5"></Resizer-Real>';
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 50));
|
await new Promise(r => setTimeout(r, 50));
|
||||||
|
|
||||||
const inst = $('#res-inst');
|
const inst = $('#res-inst');
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
// test/priority.test.js
|
// test/priority.test.js
|
||||||
window.testPriority = async function() {
|
window.testPriority = async function() {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, $, NewState } = ApigoState;
|
const { ___unsafeRefreshState, $, NewState } = ApigoState;
|
||||||
console.log('Testing directive priorities...');
|
console.log('Testing directive priorities...');
|
||||||
|
|
||||||
// Test $if vs $text (if should hide text)
|
// Test $if vs $text (if should hide text)
|
||||||
document.body.innerHTML = '<div id="prio-test" $if="state.hide" $text="state.msg"></div>';
|
document.body.innerHTML = '<div id="prio-test" $if="state.hide" $text="state.msg"></div>';
|
||||||
const state = NewState({ hide: false, msg: 'visible' });
|
const state = NewState({ hide: false, msg: 'visible' });
|
||||||
document.documentElement._thisObj = { state };
|
document.documentElement._thisObj = { state };
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed');
|
if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed');
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/timing.test.js
|
// test/timing.test.js
|
||||||
window.testTiming = async function() {
|
window.testTiming = async function() {
|
||||||
const { ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios, Component, NewState, $, Util } = ApigoState;
|
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing initialization timing...');
|
console.log('Testing initialization timing...');
|
||||||
|
|
||||||
Component.register('TimingComp', container => {
|
Component.register('TimingComp', container => {
|
||||||
@ -14,7 +14,7 @@ window.testTiming = async function() {
|
|||||||
`));
|
`));
|
||||||
|
|
||||||
document.body.innerHTML += `<TimingComp id="timing-inst"></TimingComp>`;
|
document.body.innerHTML += `<TimingComp id="timing-inst"></TimingComp>`;
|
||||||
____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios(document.documentElement);
|
___unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user