修复(state): 修正条件组件重挂载与事件参数(by AI)
This commit is contained in:
parent
2c0ffe61de
commit
fd6a4c4e23
@ -1,5 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.26 (2026-08-14)
|
||||
|
||||
### 修复
|
||||
- **条件分支组件生命周期**: `$if` 分支重新显示时创建全新节点,确保已卸载组件会重新初始化,修复编辑器在切换后偶发空白的问题。
|
||||
- **自定义事件参数兼容**: 原始类型的 `CustomEvent.detail` 不再被展开为事件参数,仍可通过 `event.detail` 正常读取。
|
||||
|
||||
## v1.0.25 (2026-07-13)
|
||||
|
||||
### 修复
|
||||
|
||||
17
dist/state.js
vendored
17
dist/state.js
vendored
@ -370,12 +370,14 @@
|
||||
if (attr === "if") {
|
||||
if (result) {
|
||||
if (!node._renderedNodes || node._renderedNodes.length === 0) {
|
||||
node._children.forEach((child) => {
|
||||
node.parentNode.insertBefore(child, node);
|
||||
child._ref = { ...node._ref };
|
||||
child._thisObj = node._thisObj;
|
||||
const rendered = node._children.map((child) => {
|
||||
const cloned = child.cloneNode(true);
|
||||
node.parentNode.insertBefore(cloned, node);
|
||||
cloned._ref = { ...node._ref };
|
||||
cloned._thisObj = node._thisObj;
|
||||
return cloned;
|
||||
});
|
||||
node._renderedNodes = [node._children];
|
||||
node._renderedNodes = [rendered];
|
||||
}
|
||||
} else {
|
||||
_clearRenderedNodes(node);
|
||||
@ -545,7 +547,10 @@
|
||||
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 || {}));
|
||||
node.addEventListener(eventName, (e) => {
|
||||
const detailVars = e.detail && typeof e.detail === "object" && !Array.isArray(e.detail) ? e.detail : {};
|
||||
_runCode(tpl, { event: e, thisNode: node, ...detailVars }, scanObj.thisObj || node, node._ref || {});
|
||||
});
|
||||
} else {
|
||||
if (realAttrName === "bind") {
|
||||
const isTextInput = ["INPUT", "TEXTAREA"].includes(node.tagName) && ["textarea", "text", "password", "email", "number", "search", "url", "tel"].includes(node.type || "text") || node.isContentEditable;
|
||||
|
||||
2
dist/state.min.js
vendored
2
dist/state.min.js
vendored
File diff suppressed because one or more lines are too long
@ -202,12 +202,14 @@ export function _updateBinding(binding) {
|
||||
if (attr === 'if') {
|
||||
if (result) {
|
||||
if (!node._renderedNodes || node._renderedNodes.length === 0) {
|
||||
node._children.forEach(child => {
|
||||
node.parentNode.insertBefore(child, node);
|
||||
child._ref = { ...node._ref };
|
||||
child._thisObj = node._thisObj;
|
||||
const rendered = node._children.map(child => {
|
||||
const cloned = child.cloneNode(true);
|
||||
node.parentNode.insertBefore(cloned, node);
|
||||
cloned._ref = { ...node._ref };
|
||||
cloned._thisObj = node._thisObj;
|
||||
return cloned;
|
||||
});
|
||||
node._renderedNodes = [node._children];
|
||||
node._renderedNodes = [rendered];
|
||||
}
|
||||
} else {
|
||||
_clearRenderedNodes(node);
|
||||
@ -382,7 +384,10 @@ export function _parseNode(node, scanObj) {
|
||||
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 || {}));
|
||||
node.addEventListener(eventName, (e) => {
|
||||
const detailVars = e.detail && typeof e.detail === 'object' && !Array.isArray(e.detail) ? e.detail : {};
|
||||
_runCode(tpl, { event: e, thisNode: node, ...detailVars }, scanObj.thisObj || node, node._ref || {});
|
||||
});
|
||||
} else {
|
||||
if (realAttrName === 'bind') {
|
||||
const isTextInput = (['INPUT', 'TEXTAREA'].includes(node.tagName) && ['textarea', 'text', 'password', 'email', 'number', 'search', 'url', 'tel'].includes(node.type || 'text')) || node.isContentEditable;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// test/dom.test.js
|
||||
window.testDom = async function() {
|
||||
const { __unsafeRefreshState, $, $$, NewState } = ApigoState;
|
||||
const { __unsafeRefreshState, $, $$, NewState, Component } = ApigoState;
|
||||
console.log('Testing dom.js...');
|
||||
|
||||
const wait = () => new Promise(r => setTimeout(r, 10));
|
||||
@ -27,6 +27,21 @@ window.testDom = async function() {
|
||||
await wait();
|
||||
if (!$('#test-if')) throw new Error('$if fail: should be visible');
|
||||
|
||||
// A component in a conditional branch must be initialized again after the
|
||||
// branch is removed and mounted a second time.
|
||||
let conditionalSetupCount = 0;
|
||||
Component.register('IfLifecycleProbe', node => { conditionalSetupCount++; node.probeReady = true });
|
||||
document.body.innerHTML = '<template $if="state.showComponent"><IfLifecycleProbe id="if-lifecycle-probe"></IfLifecycleProbe></template>';
|
||||
state.showComponent = true;
|
||||
__unsafeRefreshState(document.documentElement);
|
||||
await wait();
|
||||
if (!$('#if-lifecycle-probe')?.probeReady || conditionalSetupCount !== 1) throw new Error('$if component initial mount failed');
|
||||
state.showComponent = false;
|
||||
await wait();
|
||||
state.showComponent = true;
|
||||
await wait();
|
||||
if (!$('#if-lifecycle-probe')?.probeReady || conditionalSetupCount !== 2) throw new Error('$if component remount must reinitialize a fresh node');
|
||||
|
||||
// 3. $each directive
|
||||
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
|
||||
state.items = ['A', 'B'];
|
||||
@ -46,6 +61,13 @@ window.testDom = async function() {
|
||||
$('#test-click').click();
|
||||
if (state.count !== 1) throw new Error('$onclick failed');
|
||||
|
||||
// Primitive CustomEvent details remain available through event.detail and
|
||||
// must not be expanded into invalid numeric function argument names.
|
||||
document.body.innerHTML = '<div id="test-primitive-event" $onchange="state.eventValue=event.detail"></div>';
|
||||
__unsafeRefreshState(document.documentElement);
|
||||
$('#test-primitive-event').dispatchEvent(new CustomEvent('change', { detail: 'source text' }));
|
||||
if (state.eventValue !== 'source text') throw new Error('$on event must accept primitive detail');
|
||||
|
||||
// 5. $bind (input)
|
||||
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
|
||||
state.val = 'init';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user