fix(state): 隔离嵌套控件绑定事件(by AI)
This commit is contained in:
parent
fa76660e75
commit
d93c6463ca
@ -9,6 +9,9 @@
|
||||
### 安全性
|
||||
- **异步隔离**: 翻译请求按语言和原文去重,丢弃旧语言返回值,并隔离 translator 的同步异常。
|
||||
|
||||
### 修复
|
||||
- **组件绑定事件**: 容器或自定义组件的 `$bind` 不再消费嵌套表单控件冒泡的 `change` 事件,避免父级状态被子控件值覆盖。
|
||||
|
||||
## v1.0.26 (2026-08-14)
|
||||
|
||||
### 修复
|
||||
|
||||
1
dist/state.js
vendored
1
dist/state.js
vendored
@ -608,6 +608,7 @@
|
||||
if (realAttrName === "bind") {
|
||||
const isTextInput = ["INPUT", "TEXTAREA"].includes(node.tagName) && ["textarea", "text", "password", "email", "number", "search", "url", "tel"].includes(node.type || "text") || node.isContentEditable;
|
||||
node.addEventListener(isTextInput ? "input" : "change", (e) => {
|
||||
if (e.target !== node) return;
|
||||
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : node.type === "file" ? e.target.files : e.target.value ?? e.detail;
|
||||
_setNoWriteBack(node);
|
||||
setDisableRunCodeError(true);
|
||||
|
||||
2
dist/state.min.js
vendored
2
dist/state.min.js
vendored
File diff suppressed because one or more lines are too long
@ -421,6 +421,7 @@ export function _parseNode(node, scanObj) {
|
||||
if (realAttrName === 'bind') {
|
||||
const isTextInput = (['INPUT', 'TEXTAREA'].includes(node.tagName) && ['textarea', 'text', 'password', 'email', 'number', 'search', 'url', 'tel'].includes(node.type || 'text')) || node.isContentEditable;
|
||||
node.addEventListener(isTextInput ? 'input' : 'change', (e) => {
|
||||
if (e.target !== node) return;
|
||||
let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : (node.type === 'file' ? 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 || {});
|
||||
|
||||
@ -84,6 +84,14 @@ window.testDom = async function() {
|
||||
input.dispatchEvent(new Event('input'));
|
||||
if (state.val !== '') throw new Error('$bind must preserve an empty text value');
|
||||
|
||||
// A component/container binding must not consume change events bubbling
|
||||
// from nested form controls.
|
||||
document.body.innerHTML = '<div id="test-parent-bind" $bind="state.parentValue"><input id="test-nested-change"></div>';
|
||||
state.parentValue = 'kept';
|
||||
__unsafeRefreshState(document.documentElement);
|
||||
$('#test-nested-change').dispatchEvent(new Event('change', { bubbles: true }));
|
||||
if (state.parentValue !== 'kept') throw new Error('$bind parent consumed a nested control change event');
|
||||
|
||||
// 6. A select bound before its template-rendered options must retain the
|
||||
// model value instead of writing the browser-selected default back.
|
||||
document.body.innerHTML = '<select id="test-select" $bind="state.selected"><template $each="state.options"><option $text="item" $value="item"></option></template></select>';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user