release: v1.0.2
This commit is contained in:
parent
9a3312ce25
commit
d4211fc2d3
@ -1,5 +1,13 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.2 (2026-05-17)
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
- **稳定性**: `src/dom.js` 中的 `bind` 属性逻辑改为使用 `setTimeout` 确保 DOM 更新完成后同步值,提高双向绑定在高频操作下的可靠性。
|
||||||
|
- **健壮性**: `src/observer.js` 中 `NewState` 增加对已观察对象的检查,防止重复包装导致的性能损耗。
|
||||||
|
- **功能**: `src/dom.js` 中 `_translator` 支持 `{key}` 模板替换,增强国际化组件的灵活性。
|
||||||
|
- **清理**: 全面规范代码缩进(Tab),移除 `src/core.js` 中的调试注释。
|
||||||
|
|
||||||
## v1.0.1 (2026-05-15)
|
## v1.0.1 (2026-05-15)
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|||||||
10
dist/state.js
vendored
10
dist/state.js
vendored
@ -14,6 +14,7 @@ function onNotifyUpdate(fn) {
|
|||||||
_updateBindingFn = fn;
|
_updateBindingFn = fn;
|
||||||
}
|
}
|
||||||
function NewState(defaults = {}, getter = null, setter = null) {
|
function NewState(defaults = {}, getter = null, setter = null) {
|
||||||
|
if (defaults && defaults.__watch) return defaults;
|
||||||
const _defaults = {};
|
const _defaults = {};
|
||||||
const _stateMappings = /* @__PURE__ */ new Map();
|
const _stateMappings = /* @__PURE__ */ new Map();
|
||||||
const _watchers = /* @__PURE__ */ new Map();
|
const _watchers = /* @__PURE__ */ new Map();
|
||||||
@ -101,7 +102,12 @@ function _returnCode(code, vars, thisObj, extendVars) {
|
|||||||
if (code.includes("${")) return _runCode("return `" + code + "`", vars, thisObj, extendVars);
|
if (code.includes("${")) return _runCode("return `" + code + "`", vars, thisObj, extendVars);
|
||||||
else return _runCode("return " + code, vars, thisObj, extendVars);
|
else return _runCode("return " + code, vars, thisObj, extendVars);
|
||||||
}
|
}
|
||||||
let _translator = (text) => text;
|
let _translator = (text, args) => {
|
||||||
|
if (!text || typeof text !== "string") return text;
|
||||||
|
return text.replace(/\{(.+?)\}/g, (match, key) => {
|
||||||
|
return args.hasOwnProperty(key) ? args[key] : match;
|
||||||
|
});
|
||||||
|
};
|
||||||
const SetTranslator = (fn) => _translator = fn;
|
const SetTranslator = (fn) => _translator = fn;
|
||||||
const _translate = (text) => {
|
const _translate = (text) => {
|
||||||
if (!text || typeof text !== "string") return text;
|
if (!text || typeof text !== "string") return text;
|
||||||
@ -248,7 +254,7 @@ function _updateBinding(binding) {
|
|||||||
} else if (node.type === "radio") {
|
} else if (node.type === "radio") {
|
||||||
if (node.checked !== (node.value === String(result ?? ""))) node.checked = node.value === String(result ?? "");
|
if (node.checked !== (node.value === String(result ?? ""))) node.checked = node.value === String(result ?? "");
|
||||||
} else if ("value" in node && node.type !== "file") {
|
} else if ("value" in node && node.type !== "file") {
|
||||||
Promise.resolve().then(() => {
|
setTimeout(() => {
|
||||||
if (node.value !== String(result ?? "")) node.value = result;
|
if (node.value !== String(result ?? "")) node.value = result;
|
||||||
});
|
});
|
||||||
} else if (node.isContentEditable) {
|
} else if (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
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/state",
|
"name": "@web/state",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@web/state",
|
"name": "@web/state",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.40.0",
|
"@playwright/test": "^1.40.0",
|
||||||
"@rollup/plugin-terser": "^1.0.0",
|
"@rollup/plugin-terser": "^1.0.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@web/state",
|
"name": "@web/state",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/state.js",
|
"main": "dist/state.js",
|
||||||
|
|||||||
10
src/dom.js
10
src/dom.js
@ -4,7 +4,12 @@ import { getActiveBinding, setActiveBinding, getNoWriteBack, setNoWriteBack, New
|
|||||||
import { Component, _makeComponent, _mergeNode } from './component.js';
|
import { Component, _makeComponent, _mergeNode } from './component.js';
|
||||||
import { $, $$ } from './dom-utils.js';
|
import { $, $$ } from './dom-utils.js';
|
||||||
|
|
||||||
let _translator = (text) => text;
|
let _translator = (text, args) => {
|
||||||
|
if (!text || typeof text !== 'string') return text;
|
||||||
|
return text.replace(/\{(.+?)\}/g, (match, key) => {
|
||||||
|
return args.hasOwnProperty(key) ? args[key] : match;
|
||||||
|
})
|
||||||
|
};
|
||||||
export const SetTranslator = (fn) => _translator = fn;
|
export const SetTranslator = (fn) => _translator = fn;
|
||||||
|
|
||||||
const _translate = (text) => {
|
const _translate = (text) => {
|
||||||
@ -160,7 +165,8 @@ export function _updateBinding(binding) {
|
|||||||
} else if (node.type === 'radio') {
|
} else if (node.type === 'radio') {
|
||||||
if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? ''));
|
if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? ''));
|
||||||
} else if ('value' in node && node.type !== 'file') {
|
} else if ('value' in node && node.type !== 'file') {
|
||||||
Promise.resolve().then(() => {
|
// 这里必须用宏任务,微任务不足以确保DOM更新完成
|
||||||
|
setTimeout(() => {
|
||||||
if (node.value !== String(result ?? '')) node.value = result;
|
if (node.value !== String(result ?? '')) node.value = result;
|
||||||
});
|
});
|
||||||
} else if (node.isContentEditable) {
|
} else if (node.isContentEditable) {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export function onNotifyUpdate(fn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NewState(defaults = {}, getter = null, setter = null) {
|
export function NewState(defaults = {}, getter = null, setter = null) {
|
||||||
|
if (defaults && defaults.__watch) return defaults
|
||||||
const _defaults = {};
|
const _defaults = {};
|
||||||
const _stateMappings = new Map();
|
const _stateMappings = new Map();
|
||||||
const _watchers = new Map();
|
const _watchers = new Map();
|
||||||
|
|||||||
@ -26,12 +26,14 @@ export async function testDom() {
|
|||||||
RefreshState(document.documentElement);
|
RefreshState(document.documentElement);
|
||||||
if ($('#if-content')) throw new Error('$if failed: should be hidden');
|
if ($('#if-content')) throw new Error('$if failed: should be hidden');
|
||||||
|
|
||||||
|
const wait = () => new Promise(r => setTimeout(r, 10));
|
||||||
|
|
||||||
state.show = true;
|
state.show = true;
|
||||||
await Promise.resolve();
|
await wait();
|
||||||
if (!$('#if-content') || $('#if-content').textContent !== 'Visible') throw new Error('$if failed: should be visible');
|
if (!$('#if-content') || $('#if-content').textContent !== 'Visible') throw new Error('$if failed: should be visible');
|
||||||
|
|
||||||
state.show = false;
|
state.show = false;
|
||||||
await Promise.resolve();
|
await wait();
|
||||||
if ($('#if-content')) throw new Error('$if failed: should be hidden again');
|
if ($('#if-content')) throw new Error('$if failed: should be hidden again');
|
||||||
|
|
||||||
// 3. $each directive (Index-based reuse)
|
// 3. $each directive (Index-based reuse)
|
||||||
@ -44,7 +46,7 @@ export async function testDom() {
|
|||||||
`;
|
`;
|
||||||
state.items = [{ name: 'A' }, { name: 'B' }];
|
state.items = [{ name: 'A' }, { name: 'B' }];
|
||||||
RefreshState(document.documentElement);
|
RefreshState(document.documentElement);
|
||||||
await Promise.resolve(); // Wait for MutationObserver
|
await wait(); // Wait for MutationObserver
|
||||||
let items = $$('#list-test li');
|
let items = $$('#list-test li');
|
||||||
console.log('$each items length:', items.length);
|
console.log('$each items length:', items.length);
|
||||||
if (items.length > 0) console.log('$each first item text:', items[0].textContent);
|
if (items.length > 0) console.log('$each first item text:', items[0].textContent);
|
||||||
@ -52,13 +54,13 @@ export async function testDom() {
|
|||||||
|
|
||||||
const firstNode = items[0];
|
const firstNode = items[0];
|
||||||
state.items = [{ name: 'A-mod' }, { name: 'B' }, { name: 'C' }];
|
state.items = [{ name: 'A-mod' }, { name: 'B' }, { name: 'C' }];
|
||||||
await Promise.resolve();
|
await wait();
|
||||||
items = $$('#list-test li');
|
items = $$('#list-test li');
|
||||||
if (items.length !== 3 || items[0].textContent !== 'A-mod') throw new Error('$each update failed');
|
if (items.length !== 3 || items[0].textContent !== 'A-mod') throw new Error('$each update failed');
|
||||||
if (items[0] !== firstNode) throw new Error('$each reuse failed: should reuse existing DOM nodes');
|
if (items[0] !== firstNode) throw new Error('$each reuse failed: should reuse existing DOM nodes');
|
||||||
|
|
||||||
state.items = [{ name: 'C' }];
|
state.items = [{ name: 'C' }];
|
||||||
await Promise.resolve();
|
await wait();
|
||||||
items = $$('#list-test li');
|
items = $$('#list-test li');
|
||||||
if (items.length !== 1 || items[0].textContent !== 'C') throw new Error('$each removal failed');
|
if (items.length !== 1 || items[0].textContent !== 'C') throw new Error('$each removal failed');
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ export async function testDom() {
|
|||||||
document.body.innerHTML = `<input id="input-test" $bind="state.val">`;
|
document.body.innerHTML = `<input id="input-test" $bind="state.val">`;
|
||||||
state.val = 'initial';
|
state.val = 'initial';
|
||||||
RefreshState(document.documentElement);
|
RefreshState(document.documentElement);
|
||||||
await Promise.resolve();
|
await wait();
|
||||||
const input = $('#input-test');
|
const input = $('#input-test');
|
||||||
if (input.value !== 'initial') throw new Error('$bind initial value failed');
|
if (input.value !== 'initial') throw new Error('$bind initial value failed');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user