@wsxjs/wsx-core 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-CZII6RG2.mjs +229 -0
- package/dist/index.js +665 -487
- package/dist/index.mjs +657 -481
- package/dist/jsx-runtime.js +7 -0
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +7 -0
- package/dist/jsx.mjs +1 -1
- package/package.json +1 -1
- package/src/base-component.ts +549 -0
- package/src/index.ts +2 -4
- package/src/jsx-factory.ts +15 -0
- package/src/light-component.ts +102 -186
- package/src/reactive-decorator.ts +132 -0
- package/src/utils/reactive.ts +209 -35
- package/src/web-component.ts +89 -129
- package/types/index.d.ts +2 -2
- package/src/reactive-component.ts +0 -306
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Fragment,
|
|
3
3
|
h
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CZII6RG2.mjs";
|
|
5
5
|
|
|
6
6
|
// src/styles/style-manager.ts
|
|
7
7
|
var StyleManager = class {
|
|
@@ -43,172 +43,6 @@ var StyleManager = class {
|
|
|
43
43
|
};
|
|
44
44
|
StyleManager.styleSheets = /* @__PURE__ */ new Map();
|
|
45
45
|
|
|
46
|
-
// src/web-component.ts
|
|
47
|
-
var WebComponent = class extends HTMLElement {
|
|
48
|
-
constructor(config = {}) {
|
|
49
|
-
super();
|
|
50
|
-
this.connected = false;
|
|
51
|
-
this.config = config;
|
|
52
|
-
this.attachShadow({ mode: "open" });
|
|
53
|
-
if (config.styles) {
|
|
54
|
-
const styleName = config.styleName || this.constructor.name;
|
|
55
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, config.styles);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* 子类应该重写这个方法来定义观察的属性
|
|
60
|
-
* @returns 要观察的属性名数组
|
|
61
|
-
*/
|
|
62
|
-
static get observedAttributes() {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Web Component生命周期:连接到DOM
|
|
67
|
-
*/
|
|
68
|
-
connectedCallback() {
|
|
69
|
-
this.connected = true;
|
|
70
|
-
try {
|
|
71
|
-
const content = this.render();
|
|
72
|
-
this.shadowRoot.appendChild(content);
|
|
73
|
-
this.onConnected?.();
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
76
|
-
this.renderError(error);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Web Component生命周期:从DOM断开
|
|
81
|
-
*/
|
|
82
|
-
disconnectedCallback() {
|
|
83
|
-
this.onDisconnected?.();
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Web Component生命周期:属性变化
|
|
87
|
-
*/
|
|
88
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
89
|
-
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 查找Shadow DOM内的元素
|
|
93
|
-
*
|
|
94
|
-
* @param selector - CSS选择器
|
|
95
|
-
* @returns 元素或null
|
|
96
|
-
*/
|
|
97
|
-
querySelector(selector) {
|
|
98
|
-
return this.shadowRoot.querySelector(selector);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 查找Shadow DOM内的所有匹配元素
|
|
102
|
-
*
|
|
103
|
-
* @param selector - CSS选择器
|
|
104
|
-
* @returns 元素列表
|
|
105
|
-
*/
|
|
106
|
-
querySelectorAll(selector) {
|
|
107
|
-
return this.shadowRoot.querySelectorAll(selector);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* 重新渲染组件
|
|
111
|
-
*/
|
|
112
|
-
rerender() {
|
|
113
|
-
if (!this.connected) {
|
|
114
|
-
console.warn(
|
|
115
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
116
|
-
);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
120
|
-
this.shadowRoot.innerHTML = "";
|
|
121
|
-
if (this.shadowRoot.adoptedStyleSheets) {
|
|
122
|
-
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
123
|
-
}
|
|
124
|
-
if (adoptedStyleSheets.length === 0 && this.config.styles) {
|
|
125
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
126
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, this.config.styles);
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const content = this.render();
|
|
130
|
-
this.shadowRoot.appendChild(content);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
console.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
133
|
-
this.renderError(error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* 渲染错误信息
|
|
138
|
-
*
|
|
139
|
-
* @param error - 错误对象
|
|
140
|
-
*/
|
|
141
|
-
renderError(error) {
|
|
142
|
-
this.shadowRoot.innerHTML = "";
|
|
143
|
-
const errorElement = h(
|
|
144
|
-
"div",
|
|
145
|
-
{
|
|
146
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
147
|
-
},
|
|
148
|
-
[
|
|
149
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
150
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
151
|
-
]
|
|
152
|
-
);
|
|
153
|
-
this.shadowRoot.appendChild(errorElement);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* 获取配置值
|
|
157
|
-
*
|
|
158
|
-
* @param key - 配置键
|
|
159
|
-
* @param defaultValue - 默认值
|
|
160
|
-
* @returns 配置值
|
|
161
|
-
*/
|
|
162
|
-
getConfig(key, defaultValue) {
|
|
163
|
-
return this.config[key] ?? defaultValue;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* 设置配置值
|
|
167
|
-
*
|
|
168
|
-
* @param key - 配置键
|
|
169
|
-
* @param value - 配置值
|
|
170
|
-
*/
|
|
171
|
-
setConfig(key, value) {
|
|
172
|
-
this.config[key] = value;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* 获取属性值
|
|
176
|
-
*
|
|
177
|
-
* @param name - 属性名
|
|
178
|
-
* @param defaultValue - 默认值
|
|
179
|
-
* @returns 属性值
|
|
180
|
-
*/
|
|
181
|
-
getAttr(name, defaultValue = "") {
|
|
182
|
-
return this.getAttribute(name) || defaultValue;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* 设置属性值
|
|
186
|
-
*
|
|
187
|
-
* @param name - 属性名
|
|
188
|
-
* @param value - 属性值
|
|
189
|
-
*/
|
|
190
|
-
setAttr(name, value) {
|
|
191
|
-
this.setAttribute(name, value);
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 移除属性
|
|
195
|
-
*
|
|
196
|
-
* @param name - 属性名
|
|
197
|
-
*/
|
|
198
|
-
removeAttr(name) {
|
|
199
|
-
this.removeAttribute(name);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* 检查是否有属性
|
|
203
|
-
*
|
|
204
|
-
* @param name - 属性名
|
|
205
|
-
* @returns 是否存在
|
|
206
|
-
*/
|
|
207
|
-
hasAttr(name) {
|
|
208
|
-
return this.hasAttribute(name);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
46
|
// src/utils/logger.ts
|
|
213
47
|
var WSXLogger = class {
|
|
214
48
|
constructor(prefix = "[WSX]", enabled = true, level = "info") {
|
|
@@ -279,35 +113,107 @@ var UpdateScheduler = class {
|
|
|
279
113
|
try {
|
|
280
114
|
callback();
|
|
281
115
|
} catch (error) {
|
|
282
|
-
|
|
116
|
+
logger2.error("[WSX Reactive] Error in callback:", error);
|
|
283
117
|
}
|
|
284
118
|
});
|
|
285
119
|
}
|
|
286
120
|
};
|
|
287
121
|
var scheduler = new UpdateScheduler();
|
|
122
|
+
var proxyCache = /* @__PURE__ */ new WeakMap();
|
|
123
|
+
var originalCache = /* @__PURE__ */ new WeakMap();
|
|
124
|
+
var unwrappingSet = /* @__PURE__ */ new WeakSet();
|
|
125
|
+
function unwrapProxy(value) {
|
|
126
|
+
if (value == null || typeof value !== "object") {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
let original = value;
|
|
130
|
+
if (originalCache.has(value)) {
|
|
131
|
+
original = originalCache.get(value);
|
|
132
|
+
}
|
|
133
|
+
if (unwrappingSet.has(original)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
unwrappingSet.add(original);
|
|
137
|
+
try {
|
|
138
|
+
if (Array.isArray(original)) {
|
|
139
|
+
return original.map((item) => unwrapProxy(item));
|
|
140
|
+
}
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const key in original) {
|
|
143
|
+
if (Object.prototype.hasOwnProperty.call(original, key)) {
|
|
144
|
+
const propValue = original[key];
|
|
145
|
+
if (propValue != null && typeof propValue === "object" && originalCache.has(propValue)) {
|
|
146
|
+
result[key] = unwrapProxy(originalCache.get(propValue));
|
|
147
|
+
} else {
|
|
148
|
+
result[key] = unwrapProxy(propValue);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
} finally {
|
|
154
|
+
unwrappingSet.delete(original);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
var ARRAY_MUTATION_METHODS = [
|
|
158
|
+
"push",
|
|
159
|
+
"pop",
|
|
160
|
+
"shift",
|
|
161
|
+
"unshift",
|
|
162
|
+
"splice",
|
|
163
|
+
"sort",
|
|
164
|
+
"reverse"
|
|
165
|
+
];
|
|
288
166
|
function reactive(obj, onChange) {
|
|
289
|
-
|
|
167
|
+
if (proxyCache.has(obj)) {
|
|
168
|
+
return proxyCache.get(obj);
|
|
169
|
+
}
|
|
170
|
+
const isArray = Array.isArray(obj);
|
|
171
|
+
const proxy = new Proxy(obj, {
|
|
290
172
|
set(target, key, value) {
|
|
291
173
|
const oldValue = target[key];
|
|
292
|
-
|
|
293
|
-
|
|
174
|
+
const oldOriginal = originalCache.get(oldValue) || oldValue;
|
|
175
|
+
const newOriginal = value != null && typeof value === "object" ? originalCache.get(value) || value : value;
|
|
176
|
+
if (oldOriginal !== newOriginal) {
|
|
177
|
+
if (value != null && typeof value === "object") {
|
|
178
|
+
const reactiveValue = reactive(value, onChange);
|
|
179
|
+
target[key] = reactiveValue;
|
|
180
|
+
} else {
|
|
181
|
+
target[key] = value;
|
|
182
|
+
}
|
|
294
183
|
scheduler.schedule(onChange);
|
|
295
184
|
}
|
|
296
185
|
return true;
|
|
297
186
|
},
|
|
298
187
|
get(target, key) {
|
|
299
|
-
|
|
188
|
+
if (key === "toJSON") {
|
|
189
|
+
return function() {
|
|
190
|
+
return unwrapProxy(obj);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const value = target[key];
|
|
194
|
+
if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
|
|
195
|
+
return function(...args) {
|
|
196
|
+
const arrayMethod = Array.prototype[key];
|
|
197
|
+
const result = arrayMethod.apply(target, args);
|
|
198
|
+
scheduler.schedule(onChange);
|
|
199
|
+
return result;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (value != null && typeof value === "object") {
|
|
203
|
+
if (proxyCache.has(value)) {
|
|
204
|
+
return proxyCache.get(value);
|
|
205
|
+
}
|
|
206
|
+
return reactive(value, onChange);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
300
209
|
},
|
|
301
210
|
has(target, key) {
|
|
302
211
|
return key in target;
|
|
303
|
-
},
|
|
304
|
-
ownKeys(target) {
|
|
305
|
-
return Reflect.ownKeys(target);
|
|
306
|
-
},
|
|
307
|
-
getOwnPropertyDescriptor(target, key) {
|
|
308
|
-
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
309
212
|
}
|
|
310
213
|
});
|
|
214
|
+
proxyCache.set(obj, proxy);
|
|
215
|
+
originalCache.set(proxy, obj);
|
|
216
|
+
return proxy;
|
|
311
217
|
}
|
|
312
218
|
function createState(initialValue, onChange) {
|
|
313
219
|
let currentValue = initialValue;
|
|
@@ -355,6 +261,7 @@ var ReactiveDebug = {
|
|
|
355
261
|
};
|
|
356
262
|
function reactiveWithDebug(obj, onChange, debugName) {
|
|
357
263
|
const name = debugName || obj.constructor.name || "Unknown";
|
|
264
|
+
const isArray = Array.isArray(obj);
|
|
358
265
|
return new Proxy(obj, {
|
|
359
266
|
set(target, key, value) {
|
|
360
267
|
const oldValue = target[key];
|
|
@@ -370,21 +277,89 @@ function reactiveWithDebug(obj, onChange, debugName) {
|
|
|
370
277
|
return true;
|
|
371
278
|
},
|
|
372
279
|
get(target, key) {
|
|
373
|
-
|
|
280
|
+
if (key === "toJSON") {
|
|
281
|
+
return function() {
|
|
282
|
+
return unwrapProxy(obj);
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const value = target[key];
|
|
286
|
+
if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
|
|
287
|
+
return function(...args) {
|
|
288
|
+
ReactiveDebug.log(`Array mutation in ${name}:`, {
|
|
289
|
+
method: key,
|
|
290
|
+
args
|
|
291
|
+
});
|
|
292
|
+
const arrayMethod = Array.prototype[key];
|
|
293
|
+
const result = arrayMethod.apply(target, args);
|
|
294
|
+
scheduler.schedule(onChange);
|
|
295
|
+
return result;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (value != null && typeof value === "object") {
|
|
299
|
+
return reactiveWithDebug(value, onChange, `${name}.${String(key)}`);
|
|
300
|
+
}
|
|
301
|
+
return value;
|
|
374
302
|
}
|
|
375
303
|
});
|
|
376
304
|
}
|
|
377
305
|
|
|
378
|
-
// src/
|
|
379
|
-
var
|
|
380
|
-
var LightComponent = class extends HTMLElement {
|
|
306
|
+
// src/base-component.ts
|
|
307
|
+
var BaseComponent = class extends HTMLElement {
|
|
381
308
|
constructor(config = {}) {
|
|
382
309
|
super();
|
|
383
310
|
this.connected = false;
|
|
384
311
|
this._isDebugEnabled = false;
|
|
385
312
|
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
386
|
-
|
|
313
|
+
/**
|
|
314
|
+
* 当前捕获的焦点状态(用于在 render 时使用捕获的值)
|
|
315
|
+
* @internal - 由 rerender() 方法管理
|
|
316
|
+
*/
|
|
317
|
+
this._pendingFocusState = null;
|
|
318
|
+
/**
|
|
319
|
+
* 防抖定时器,用于延迟重渲染(当用户正在输入时)
|
|
320
|
+
* @internal
|
|
321
|
+
*/
|
|
322
|
+
this._rerenderDebounceTimer = null;
|
|
323
|
+
/**
|
|
324
|
+
* 待处理的重渲染标志(当用户正在输入时,标记需要重渲染但延迟执行)
|
|
325
|
+
* @internal
|
|
326
|
+
*/
|
|
327
|
+
this._pendingRerender = false;
|
|
328
|
+
/**
|
|
329
|
+
* 处理 blur 事件,在用户停止输入时执行待处理的重渲染
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
this.handleGlobalBlur = (event) => {
|
|
333
|
+
const root = this.getActiveRoot();
|
|
334
|
+
const target = event.target;
|
|
335
|
+
if (target && root.contains(target)) {
|
|
336
|
+
if (this._pendingRerender && this.connected) {
|
|
337
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
338
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
339
|
+
this._rerenderDebounceTimer = null;
|
|
340
|
+
}
|
|
341
|
+
requestAnimationFrame(() => {
|
|
342
|
+
if (this._pendingRerender && this.connected) {
|
|
343
|
+
this._pendingRerender = false;
|
|
344
|
+
this.rerender();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
387
350
|
this._isDebugEnabled = config.debug ?? false;
|
|
351
|
+
const host = this;
|
|
352
|
+
const originalStyles = config.styles;
|
|
353
|
+
this.config = {
|
|
354
|
+
...config,
|
|
355
|
+
get styles() {
|
|
356
|
+
const result = originalStyles || host._autoStyles || "";
|
|
357
|
+
return result;
|
|
358
|
+
},
|
|
359
|
+
set styles(value) {
|
|
360
|
+
config.styles = value;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
388
363
|
}
|
|
389
364
|
/**
|
|
390
365
|
* 子类应该重写这个方法来定义观察的属性
|
|
@@ -393,56 +368,12 @@ var LightComponent = class extends HTMLElement {
|
|
|
393
368
|
static get observedAttributes() {
|
|
394
369
|
return [];
|
|
395
370
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Web Component生命周期:连接到DOM
|
|
398
|
-
*/
|
|
399
|
-
connectedCallback() {
|
|
400
|
-
this.connected = true;
|
|
401
|
-
try {
|
|
402
|
-
if (this.config.styles) {
|
|
403
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
404
|
-
this.applyScopedStyles(styleName, this.config.styles);
|
|
405
|
-
}
|
|
406
|
-
const content = this.render();
|
|
407
|
-
this.appendChild(content);
|
|
408
|
-
this.onConnected?.();
|
|
409
|
-
} catch (error) {
|
|
410
|
-
logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
411
|
-
this.renderError(error);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
/**
|
|
415
|
-
* Web Component生命周期:从DOM断开
|
|
416
|
-
*/
|
|
417
|
-
disconnectedCallback() {
|
|
418
|
-
this.cleanupReactiveStates();
|
|
419
|
-
this.cleanupStyles();
|
|
420
|
-
this.onDisconnected?.();
|
|
421
|
-
}
|
|
422
371
|
/**
|
|
423
372
|
* Web Component生命周期:属性变化
|
|
424
373
|
*/
|
|
425
374
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
426
375
|
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
427
376
|
}
|
|
428
|
-
/**
|
|
429
|
-
* 查找组件内的元素
|
|
430
|
-
*
|
|
431
|
-
* @param selector - CSS选择器
|
|
432
|
-
* @returns 元素或null
|
|
433
|
-
*/
|
|
434
|
-
querySelector(selector) {
|
|
435
|
-
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* 查找组件内的所有匹配元素
|
|
439
|
-
*
|
|
440
|
-
* @param selector - CSS选择器
|
|
441
|
-
* @returns 元素列表
|
|
442
|
-
*/
|
|
443
|
-
querySelectorAll(selector) {
|
|
444
|
-
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
445
|
-
}
|
|
446
377
|
/**
|
|
447
378
|
* 创建响应式对象
|
|
448
379
|
*
|
|
@@ -465,86 +396,73 @@ var LightComponent = class extends HTMLElement {
|
|
|
465
396
|
useState(key, initialValue) {
|
|
466
397
|
if (!this._reactiveStates.has(key)) {
|
|
467
398
|
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
468
|
-
this._reactiveStates.set(key, {
|
|
399
|
+
this._reactiveStates.set(key, {
|
|
400
|
+
getter,
|
|
401
|
+
setter
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const state2 = this._reactiveStates.get(key);
|
|
405
|
+
if (!state2) {
|
|
406
|
+
throw new Error(`State ${key} not found`);
|
|
469
407
|
}
|
|
470
|
-
|
|
471
|
-
return [state.getter, state.setter];
|
|
408
|
+
return [state2.getter, state2.setter];
|
|
472
409
|
}
|
|
473
410
|
/**
|
|
474
411
|
* 调度重渲染
|
|
475
412
|
* 这个方法被响应式系统调用,开发者通常不需要直接调用
|
|
413
|
+
* 使用 queueMicrotask 进行异步调度,与 reactive() 系统保持一致
|
|
476
414
|
*/
|
|
477
415
|
scheduleRerender() {
|
|
478
|
-
if (this.connected) {
|
|
479
|
-
this.rerender();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* 重新渲染组件
|
|
484
|
-
*/
|
|
485
|
-
rerender() {
|
|
486
416
|
if (!this.connected) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
417
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
418
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
419
|
+
this._rerenderDebounceTimer = null;
|
|
420
|
+
}
|
|
490
421
|
return;
|
|
491
422
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
423
|
+
const root = this.getActiveRoot();
|
|
424
|
+
let hasActiveElement = false;
|
|
425
|
+
if (root instanceof ShadowRoot) {
|
|
426
|
+
hasActiveElement = root.activeElement !== null;
|
|
427
|
+
} else {
|
|
428
|
+
const docActiveElement = document.activeElement;
|
|
429
|
+
hasActiveElement = docActiveElement !== null && root.contains(docActiveElement);
|
|
499
430
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
this.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
506
|
-
);
|
|
507
|
-
if (styleElement && styleElement !== this.firstChild) {
|
|
508
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
509
|
-
}
|
|
431
|
+
if (hasActiveElement) {
|
|
432
|
+
this._pendingRerender = true;
|
|
433
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
434
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
435
|
+
this._rerenderDebounceTimer = null;
|
|
510
436
|
}
|
|
511
|
-
|
|
512
|
-
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
513
|
-
this.renderError(error);
|
|
437
|
+
return;
|
|
514
438
|
}
|
|
439
|
+
if (this._pendingRerender) {
|
|
440
|
+
this._pendingRerender = false;
|
|
441
|
+
}
|
|
442
|
+
queueMicrotask(() => {
|
|
443
|
+
if (this.connected) {
|
|
444
|
+
this.rerender();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
515
447
|
}
|
|
516
448
|
/**
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
* @param error - 错误对象
|
|
449
|
+
* 清理资源(在组件断开连接时调用)
|
|
450
|
+
* @internal
|
|
520
451
|
*/
|
|
521
|
-
|
|
522
|
-
this.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
[
|
|
529
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
530
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
531
|
-
]
|
|
532
|
-
);
|
|
533
|
-
this.appendChild(errorElement);
|
|
452
|
+
cleanup() {
|
|
453
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
454
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
455
|
+
this._rerenderDebounceTimer = null;
|
|
456
|
+
}
|
|
457
|
+
document.removeEventListener("blur", this.handleGlobalBlur, true);
|
|
458
|
+
this._pendingRerender = false;
|
|
534
459
|
}
|
|
535
460
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
461
|
+
* 初始化事件监听器(在组件连接时调用)
|
|
462
|
+
* @internal
|
|
538
463
|
*/
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (existingStyle) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const styleElement = document.createElement("style");
|
|
545
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
546
|
-
styleElement.textContent = cssText;
|
|
547
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
464
|
+
initializeEventListeners() {
|
|
465
|
+
document.addEventListener("blur", this.handleGlobalBlur, true);
|
|
548
466
|
}
|
|
549
467
|
/**
|
|
550
468
|
* 获取配置值
|
|
@@ -565,22 +483,6 @@ var LightComponent = class extends HTMLElement {
|
|
|
565
483
|
setConfig(key, value) {
|
|
566
484
|
this.config[key] = value;
|
|
567
485
|
}
|
|
568
|
-
/**
|
|
569
|
-
* 清理响应式状态
|
|
570
|
-
*/
|
|
571
|
-
cleanupReactiveStates() {
|
|
572
|
-
this._reactiveStates.clear();
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* 清理组件样式
|
|
576
|
-
*/
|
|
577
|
-
cleanupStyles() {
|
|
578
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
579
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
580
|
-
if (existingStyle) {
|
|
581
|
-
existingStyle.remove();
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
486
|
/**
|
|
585
487
|
* 获取属性值
|
|
586
488
|
*
|
|
@@ -617,253 +519,527 @@ var LightComponent = class extends HTMLElement {
|
|
|
617
519
|
hasAttr(name) {
|
|
618
520
|
return this.hasAttribute(name);
|
|
619
521
|
}
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
// src/auto-register.ts
|
|
623
|
-
function autoRegister(options = {}) {
|
|
624
|
-
return function(constructor) {
|
|
625
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
626
|
-
if (!customElements.get(tagName)) {
|
|
627
|
-
customElements.define(tagName, constructor);
|
|
628
|
-
}
|
|
629
|
-
return constructor;
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
function registerComponent(constructor, options = {}) {
|
|
633
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
634
|
-
if (!customElements.get(tagName)) {
|
|
635
|
-
customElements.define(tagName, constructor);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
function deriveTagName(className, prefix) {
|
|
639
|
-
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
640
|
-
if (!kebabCase.includes("-")) {
|
|
641
|
-
kebabCase = `${kebabCase}-component`;
|
|
642
|
-
}
|
|
643
|
-
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/reactive-component.ts
|
|
647
|
-
var ReactiveWebComponent = class extends WebComponent {
|
|
648
|
-
constructor(config = {}) {
|
|
649
|
-
super(config);
|
|
650
|
-
this._isDebugEnabled = false;
|
|
651
|
-
this._preserveFocus = true;
|
|
652
|
-
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
653
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
654
|
-
this._preserveFocus = config.preserveFocus ?? true;
|
|
655
|
-
}
|
|
656
522
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
* @param obj 要变为响应式的对象
|
|
660
|
-
* @param debugName 调试名称(可选)
|
|
661
|
-
* @returns 响应式代理对象
|
|
662
|
-
*/
|
|
663
|
-
reactive(obj, debugName) {
|
|
664
|
-
const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
|
|
665
|
-
const name = debugName || `${this.constructor.name}.reactive`;
|
|
666
|
-
return this._isDebugEnabled ? reactiveFn(obj, () => this.scheduleRerender(), name) : reactiveFn(obj, () => this.scheduleRerender());
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* 创建响应式状态
|
|
670
|
-
*
|
|
671
|
-
* @param key 状态标识符
|
|
672
|
-
* @param initialValue 初始值
|
|
673
|
-
* @returns [getter, setter] 元组
|
|
523
|
+
* 清理响应式状态
|
|
674
524
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
678
|
-
this._reactiveStates.set(key, { getter, setter });
|
|
679
|
-
}
|
|
680
|
-
const state = this._reactiveStates.get(key);
|
|
681
|
-
return [state.getter, state.setter];
|
|
525
|
+
cleanupReactiveStates() {
|
|
526
|
+
this._reactiveStates.clear();
|
|
682
527
|
}
|
|
683
528
|
/**
|
|
684
|
-
*
|
|
685
|
-
*
|
|
529
|
+
* 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
|
|
530
|
+
* @returns 活动的 DOM 根元素
|
|
686
531
|
*/
|
|
687
|
-
|
|
688
|
-
if (this.
|
|
689
|
-
this.
|
|
532
|
+
getActiveRoot() {
|
|
533
|
+
if ("shadowRoot" in this && this.shadowRoot) {
|
|
534
|
+
return this.shadowRoot;
|
|
690
535
|
}
|
|
536
|
+
return this;
|
|
691
537
|
}
|
|
692
538
|
/**
|
|
693
|
-
*
|
|
539
|
+
* 捕获当前焦点状态(在重渲染之前调用)
|
|
540
|
+
* @returns 焦点状态,如果没有焦点元素则返回 null
|
|
694
541
|
*/
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
542
|
+
captureFocusState() {
|
|
543
|
+
const root = this.getActiveRoot();
|
|
544
|
+
let activeElement = null;
|
|
545
|
+
if (root instanceof ShadowRoot) {
|
|
546
|
+
activeElement = root.activeElement;
|
|
547
|
+
} else {
|
|
548
|
+
const docActiveElement = document.activeElement;
|
|
549
|
+
if (docActiveElement && root.contains(docActiveElement)) {
|
|
550
|
+
activeElement = docActiveElement;
|
|
551
|
+
}
|
|
703
552
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
this.restoreFocusState(focusData);
|
|
553
|
+
if (!activeElement || !(activeElement instanceof HTMLElement)) {
|
|
554
|
+
return null;
|
|
707
555
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
* 保存焦点状态
|
|
711
|
-
*/
|
|
712
|
-
saveFocusState(activeElement) {
|
|
713
|
-
if (!activeElement) {
|
|
556
|
+
const key = activeElement.getAttribute("data-wsx-key");
|
|
557
|
+
if (!key) {
|
|
714
558
|
return null;
|
|
715
559
|
}
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
560
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
561
|
+
const state2 = {
|
|
562
|
+
key,
|
|
563
|
+
elementType: tagName
|
|
719
564
|
};
|
|
720
|
-
if (activeElement
|
|
565
|
+
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
|
|
566
|
+
state2.value = activeElement.value;
|
|
567
|
+
state2.selectionStart = activeElement.selectionStart ?? void 0;
|
|
568
|
+
state2.selectionEnd = activeElement.selectionEnd ?? void 0;
|
|
569
|
+
if (activeElement instanceof HTMLTextAreaElement) {
|
|
570
|
+
state2.scrollTop = activeElement.scrollTop;
|
|
571
|
+
}
|
|
572
|
+
} else if (activeElement instanceof HTMLSelectElement) {
|
|
573
|
+
state2.elementType = "select";
|
|
574
|
+
state2.selectedIndex = activeElement.selectedIndex;
|
|
575
|
+
} else if (activeElement.hasAttribute("contenteditable")) {
|
|
576
|
+
state2.elementType = "contenteditable";
|
|
721
577
|
const selection = window.getSelection();
|
|
722
578
|
if (selection && selection.rangeCount > 0) {
|
|
723
579
|
const range = selection.getRangeAt(0);
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
|
|
729
|
-
focusData.value = activeElement.value;
|
|
730
|
-
if ("selectionStart" in activeElement) {
|
|
731
|
-
focusData.selectionStart = activeElement.selectionStart;
|
|
732
|
-
focusData.selectionEnd = activeElement.selectionEnd;
|
|
580
|
+
state2.selectionStart = range.startOffset;
|
|
581
|
+
state2.selectionEnd = range.endOffset;
|
|
733
582
|
}
|
|
734
583
|
}
|
|
735
|
-
return
|
|
584
|
+
return state2;
|
|
736
585
|
}
|
|
737
586
|
/**
|
|
738
|
-
*
|
|
587
|
+
* 恢复焦点状态(在重渲染之后调用)
|
|
588
|
+
* @param state - 之前捕获的焦点状态
|
|
739
589
|
*/
|
|
740
|
-
restoreFocusState(
|
|
741
|
-
if (!
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
590
|
+
restoreFocusState(state2) {
|
|
591
|
+
if (!state2 || !state2.key) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const root = this.getActiveRoot();
|
|
595
|
+
const target = root.querySelector(`[data-wsx-key="${state2.key}"]`);
|
|
596
|
+
if (!target) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (state2.value !== void 0) {
|
|
600
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
601
|
+
target.value = state2.value;
|
|
748
602
|
}
|
|
749
|
-
|
|
750
|
-
|
|
603
|
+
}
|
|
604
|
+
if (state2.selectedIndex !== void 0 && target instanceof HTMLSelectElement) {
|
|
605
|
+
target.selectedIndex = state2.selectedIndex;
|
|
606
|
+
}
|
|
607
|
+
requestAnimationFrame(() => {
|
|
608
|
+
const currentTarget = root.querySelector(
|
|
609
|
+
`[data-wsx-key="${state2.key}"]`
|
|
610
|
+
);
|
|
611
|
+
if (!currentTarget) {
|
|
612
|
+
return;
|
|
751
613
|
}
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
targetElement.setSelectionRange(
|
|
757
|
-
focusData.selectionStart,
|
|
758
|
-
focusData.selectionEnd
|
|
759
|
-
);
|
|
760
|
-
} else if (targetElement instanceof HTMLSelectElement) {
|
|
761
|
-
targetElement.value = focusData.value;
|
|
762
|
-
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
763
|
-
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
614
|
+
if (state2.value !== void 0) {
|
|
615
|
+
if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
|
|
616
|
+
if (currentTarget.value !== state2.value) {
|
|
617
|
+
currentTarget.value = state2.value;
|
|
764
618
|
}
|
|
765
619
|
}
|
|
766
620
|
}
|
|
767
|
-
|
|
621
|
+
currentTarget.focus({ preventScroll: true });
|
|
622
|
+
if (state2.selectionStart !== void 0) {
|
|
623
|
+
if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
|
|
624
|
+
const start = state2.selectionStart;
|
|
625
|
+
const end = state2.selectionEnd ?? start;
|
|
626
|
+
currentTarget.setSelectionRange(start, end);
|
|
627
|
+
if (state2.scrollTop !== void 0 && currentTarget instanceof HTMLTextAreaElement) {
|
|
628
|
+
currentTarget.scrollTop = state2.scrollTop;
|
|
629
|
+
}
|
|
630
|
+
} else if (currentTarget.hasAttribute("contenteditable")) {
|
|
631
|
+
const selection = window.getSelection();
|
|
632
|
+
if (selection) {
|
|
633
|
+
const range = document.createRange();
|
|
634
|
+
const textNode = currentTarget.childNodes[0];
|
|
635
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
636
|
+
const maxPos = Math.min(
|
|
637
|
+
state2.selectionStart,
|
|
638
|
+
textNode.textContent?.length || 0
|
|
639
|
+
);
|
|
640
|
+
range.setStart(textNode, maxPos);
|
|
641
|
+
range.setEnd(textNode, state2.selectionEnd ?? maxPos);
|
|
642
|
+
selection.removeAllRanges();
|
|
643
|
+
selection.addRange(range);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/web-component.ts
|
|
653
|
+
var logger3 = createLogger("WebComponent");
|
|
654
|
+
var WebComponent = class extends BaseComponent {
|
|
655
|
+
// Initialized by BaseComponent constructor
|
|
656
|
+
constructor(config = {}) {
|
|
657
|
+
super(config);
|
|
658
|
+
this.attachShadow({ mode: "open" });
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Web Component生命周期:连接到DOM
|
|
662
|
+
*/
|
|
663
|
+
connectedCallback() {
|
|
664
|
+
this.connected = true;
|
|
665
|
+
try {
|
|
666
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
667
|
+
if (stylesToApply) {
|
|
668
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
669
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
670
|
+
}
|
|
671
|
+
const content = this.render();
|
|
672
|
+
this.shadowRoot.appendChild(content);
|
|
673
|
+
this.initializeEventListeners();
|
|
674
|
+
this.onConnected?.();
|
|
675
|
+
} catch (error) {
|
|
676
|
+
logger3.error(`Error in connectedCallback:`, error);
|
|
677
|
+
this.renderError(error);
|
|
768
678
|
}
|
|
769
679
|
}
|
|
770
680
|
/**
|
|
771
|
-
*
|
|
681
|
+
* Web Component生命周期:从DOM断开
|
|
682
|
+
*/
|
|
683
|
+
disconnectedCallback() {
|
|
684
|
+
this.connected = false;
|
|
685
|
+
this.cleanup();
|
|
686
|
+
this.onDisconnected?.();
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* 查找Shadow DOM内的元素
|
|
690
|
+
*
|
|
691
|
+
* @param selector - CSS选择器
|
|
692
|
+
* @returns 元素或null
|
|
693
|
+
*/
|
|
694
|
+
querySelector(selector) {
|
|
695
|
+
return this.shadowRoot.querySelector(selector);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* 查找Shadow DOM内的所有匹配元素
|
|
699
|
+
*
|
|
700
|
+
* @param selector - CSS选择器
|
|
701
|
+
* @returns 元素列表
|
|
702
|
+
*/
|
|
703
|
+
querySelectorAll(selector) {
|
|
704
|
+
return this.shadowRoot.querySelectorAll(selector);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 重新渲染组件
|
|
772
708
|
*/
|
|
773
|
-
|
|
709
|
+
rerender() {
|
|
710
|
+
if (!this.connected) {
|
|
711
|
+
logger3.warn("Component is not connected, skipping rerender.");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const focusState = this.captureFocusState();
|
|
715
|
+
this._pendingFocusState = focusState;
|
|
716
|
+
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
774
717
|
try {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const maxPos = Math.min(position, textNode.textContent?.length || 0);
|
|
781
|
-
range.setStart(textNode, maxPos);
|
|
782
|
-
range.setEnd(textNode, maxPos);
|
|
783
|
-
selection.removeAllRanges();
|
|
784
|
-
selection.addRange(range);
|
|
718
|
+
if (adoptedStyleSheets.length === 0) {
|
|
719
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
720
|
+
if (stylesToApply) {
|
|
721
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
722
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
785
723
|
}
|
|
786
724
|
}
|
|
787
|
-
|
|
725
|
+
const content = this.render();
|
|
726
|
+
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
727
|
+
const target = content.querySelector(
|
|
728
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
729
|
+
);
|
|
730
|
+
if (target) {
|
|
731
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
732
|
+
target.value = focusState.value;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
737
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
738
|
+
}
|
|
739
|
+
requestAnimationFrame(() => {
|
|
740
|
+
this.shadowRoot.appendChild(content);
|
|
741
|
+
const oldChildren = Array.from(this.shadowRoot.children).filter(
|
|
742
|
+
(child) => child !== content
|
|
743
|
+
);
|
|
744
|
+
oldChildren.forEach((child) => child.remove());
|
|
745
|
+
requestAnimationFrame(() => {
|
|
746
|
+
this.restoreFocusState(focusState);
|
|
747
|
+
this._pendingFocusState = null;
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
} catch (error) {
|
|
751
|
+
logger3.error("Error in rerender:", error);
|
|
752
|
+
this.renderError(error);
|
|
788
753
|
}
|
|
789
754
|
}
|
|
790
755
|
/**
|
|
791
|
-
*
|
|
756
|
+
* 渲染错误信息
|
|
757
|
+
*
|
|
758
|
+
* @param error - 错误对象
|
|
792
759
|
*/
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
760
|
+
renderError(error) {
|
|
761
|
+
this.shadowRoot.innerHTML = "";
|
|
762
|
+
const errorElement = h(
|
|
763
|
+
"div",
|
|
764
|
+
{
|
|
765
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
766
|
+
},
|
|
767
|
+
[
|
|
768
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
769
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
770
|
+
]
|
|
771
|
+
);
|
|
772
|
+
this.shadowRoot.appendChild(errorElement);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/light-component.ts
|
|
777
|
+
var logger4 = createLogger("LightComponent");
|
|
778
|
+
var LightComponent = class extends BaseComponent {
|
|
779
|
+
// Initialized by BaseComponent constructor
|
|
780
|
+
constructor(config = {}) {
|
|
781
|
+
super(config);
|
|
799
782
|
}
|
|
800
783
|
/**
|
|
801
|
-
*
|
|
784
|
+
* Web Component生命周期:连接到DOM
|
|
802
785
|
*/
|
|
803
|
-
|
|
804
|
-
this.
|
|
786
|
+
connectedCallback() {
|
|
787
|
+
this.connected = true;
|
|
788
|
+
try {
|
|
789
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
790
|
+
if (stylesToApply) {
|
|
791
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
792
|
+
this.applyScopedStyles(styleName, stylesToApply);
|
|
793
|
+
}
|
|
794
|
+
const content = this.render();
|
|
795
|
+
this.appendChild(content);
|
|
796
|
+
this.initializeEventListeners();
|
|
797
|
+
this.onConnected?.();
|
|
798
|
+
} catch (error) {
|
|
799
|
+
logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
800
|
+
this.renderError(error);
|
|
801
|
+
}
|
|
805
802
|
}
|
|
806
803
|
/**
|
|
807
|
-
*
|
|
804
|
+
* Web Component生命周期:从DOM断开
|
|
808
805
|
*/
|
|
809
806
|
disconnectedCallback() {
|
|
810
|
-
|
|
807
|
+
this.connected = false;
|
|
808
|
+
this.cleanup();
|
|
811
809
|
this.cleanupReactiveStates();
|
|
810
|
+
this.cleanupStyles();
|
|
811
|
+
this.onDisconnected?.();
|
|
812
812
|
}
|
|
813
813
|
/**
|
|
814
|
-
*
|
|
814
|
+
* 查找组件内的元素
|
|
815
|
+
*
|
|
816
|
+
* @param selector - CSS选择器
|
|
817
|
+
* @returns 元素或null
|
|
815
818
|
*/
|
|
816
|
-
|
|
817
|
-
this
|
|
819
|
+
querySelector(selector) {
|
|
820
|
+
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
818
821
|
}
|
|
819
822
|
/**
|
|
820
|
-
*
|
|
823
|
+
* 查找组件内的所有匹配元素
|
|
824
|
+
*
|
|
825
|
+
* @param selector - CSS选择器
|
|
826
|
+
* @returns 元素列表
|
|
821
827
|
*/
|
|
822
|
-
|
|
823
|
-
this
|
|
828
|
+
querySelectorAll(selector) {
|
|
829
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
824
830
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
831
|
+
/**
|
|
832
|
+
* 重新渲染组件
|
|
833
|
+
*/
|
|
834
|
+
rerender() {
|
|
835
|
+
if (!this.connected) {
|
|
836
|
+
logger4.warn(
|
|
837
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
838
|
+
);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const focusState = this.captureFocusState();
|
|
842
|
+
this._pendingFocusState = focusState;
|
|
843
|
+
try {
|
|
844
|
+
const content = this.render();
|
|
845
|
+
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
846
|
+
const target = content.querySelector(
|
|
847
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
848
|
+
);
|
|
849
|
+
if (target) {
|
|
850
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
851
|
+
target.value = focusState.value;
|
|
852
|
+
}
|
|
835
853
|
}
|
|
836
854
|
}
|
|
837
|
-
|
|
838
|
-
|
|
855
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
856
|
+
if (stylesToApply) {
|
|
857
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
858
|
+
let styleElement = this.querySelector(
|
|
859
|
+
`style[data-wsx-light-component="${styleName}"]`
|
|
860
|
+
);
|
|
861
|
+
if (!styleElement) {
|
|
862
|
+
styleElement = document.createElement("style");
|
|
863
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
864
|
+
styleElement.textContent = stylesToApply;
|
|
865
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
866
|
+
} else if (styleElement.textContent !== stylesToApply) {
|
|
867
|
+
styleElement.textContent = stylesToApply;
|
|
868
|
+
}
|
|
839
869
|
}
|
|
840
|
-
|
|
870
|
+
requestAnimationFrame(() => {
|
|
871
|
+
this.appendChild(content);
|
|
872
|
+
const oldChildren = Array.from(this.children).filter((child) => {
|
|
873
|
+
if (child === content) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
if (stylesToApply && child instanceof HTMLStyleElement && child.getAttribute("data-wsx-light-component") === (this.config.styleName || this.constructor.name)) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
return true;
|
|
880
|
+
});
|
|
881
|
+
oldChildren.forEach((child) => child.remove());
|
|
882
|
+
if (stylesToApply && this.children.length > 1) {
|
|
883
|
+
const styleElement = this.querySelector(
|
|
884
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
885
|
+
);
|
|
886
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
887
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
requestAnimationFrame(() => {
|
|
891
|
+
this.restoreFocusState(focusState);
|
|
892
|
+
this._pendingFocusState = null;
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
} catch (error) {
|
|
896
|
+
logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
897
|
+
this.renderError(error);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* 渲染错误信息
|
|
902
|
+
*
|
|
903
|
+
* @param error - 错误对象
|
|
904
|
+
*/
|
|
905
|
+
renderError(error) {
|
|
906
|
+
this.innerHTML = "";
|
|
907
|
+
const errorElement = h(
|
|
908
|
+
"div",
|
|
909
|
+
{
|
|
910
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
911
|
+
},
|
|
912
|
+
[
|
|
913
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
914
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
915
|
+
]
|
|
916
|
+
);
|
|
917
|
+
this.appendChild(errorElement);
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* 为Light DOM组件应用样式
|
|
921
|
+
* 直接将样式注入到组件自身,避免全局污染
|
|
922
|
+
*/
|
|
923
|
+
applyScopedStyles(styleName, cssText) {
|
|
924
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
925
|
+
if (existingStyle) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const styleElement = document.createElement("style");
|
|
929
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
930
|
+
styleElement.textContent = cssText;
|
|
931
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* 清理组件样式
|
|
935
|
+
*/
|
|
936
|
+
cleanupStyles() {
|
|
937
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
938
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
939
|
+
if (existingStyle) {
|
|
940
|
+
existingStyle.remove();
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
// src/auto-register.ts
|
|
946
|
+
function autoRegister(options = {}) {
|
|
947
|
+
return function(constructor) {
|
|
948
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
949
|
+
if (!customElements.get(tagName)) {
|
|
950
|
+
customElements.define(tagName, constructor);
|
|
951
|
+
}
|
|
952
|
+
return constructor;
|
|
841
953
|
};
|
|
842
954
|
}
|
|
843
|
-
function
|
|
844
|
-
|
|
845
|
-
|
|
955
|
+
function registerComponent(constructor, options = {}) {
|
|
956
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
957
|
+
if (!customElements.get(tagName)) {
|
|
958
|
+
customElements.define(tagName, constructor);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function deriveTagName(className, prefix) {
|
|
962
|
+
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
963
|
+
if (!kebabCase.includes("-")) {
|
|
964
|
+
kebabCase = `${kebabCase}-component`;
|
|
965
|
+
}
|
|
966
|
+
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/reactive-decorator.ts
|
|
970
|
+
function state(target, propertyKey) {
|
|
971
|
+
let normalizedPropertyKey;
|
|
972
|
+
if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
|
|
973
|
+
normalizedPropertyKey = propertyKey;
|
|
974
|
+
} else {
|
|
975
|
+
const propertyKeyStr = String(propertyKey);
|
|
976
|
+
if (propertyKeyStr === "[object Object]") {
|
|
977
|
+
throw new Error(
|
|
978
|
+
`@state decorator: Invalid propertyKey detected.
|
|
979
|
+
|
|
980
|
+
The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
|
|
981
|
+
|
|
982
|
+
To fix this, please:
|
|
983
|
+
1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
|
|
984
|
+
2. Configure it in vite.config.ts:
|
|
985
|
+
import { wsx } from '@wsxjs/wsx-vite-plugin';
|
|
986
|
+
export default defineConfig({ plugins: [wsx()] });
|
|
987
|
+
3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
|
|
988
|
+
npm install --save-dev @wsxjs/wsx-tsconfig
|
|
989
|
+
Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
|
|
990
|
+
Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
|
|
991
|
+
|
|
992
|
+
See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
normalizedPropertyKey = propertyKeyStr;
|
|
996
|
+
}
|
|
997
|
+
if (target == null) {
|
|
998
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
|
|
1001
|
+
|
|
1002
|
+
The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
|
|
1003
|
+
|
|
1004
|
+
To fix this, please:
|
|
1005
|
+
1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
|
|
1006
|
+
2. Configure it in vite.config.ts:
|
|
1007
|
+
import { wsx } from '@wsxjs/wsx-vite-plugin';
|
|
1008
|
+
export default defineConfig({ plugins: [wsx()] });
|
|
1009
|
+
3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
|
|
1010
|
+
npm install --save-dev @wsxjs/wsx-tsconfig
|
|
1011
|
+
Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
|
|
1012
|
+
Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
|
|
1013
|
+
|
|
1014
|
+
See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
if (typeof target !== "object") {
|
|
1018
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
`@state decorator: Cannot be used on "${propertyKeyStr}". @state is for properties only, not methods.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
|
|
1024
|
+
if (descriptor?.get) {
|
|
1025
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
`@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
|
|
1028
|
+
);
|
|
846
1029
|
}
|
|
847
|
-
const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
|
|
848
|
-
return new ReactiveComponent(config);
|
|
849
1030
|
}
|
|
850
1031
|
export {
|
|
851
1032
|
Fragment,
|
|
852
1033
|
LightComponent,
|
|
853
|
-
ReactiveDebug,
|
|
854
|
-
ReactiveWebComponent,
|
|
855
1034
|
StyleManager,
|
|
856
1035
|
WSXLogger,
|
|
857
1036
|
WebComponent,
|
|
858
1037
|
autoRegister,
|
|
859
1038
|
createLogger,
|
|
860
|
-
createReactiveComponent,
|
|
861
|
-
createState,
|
|
862
1039
|
h,
|
|
863
1040
|
h as jsx,
|
|
864
1041
|
h as jsxs,
|
|
865
1042
|
logger,
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
registerComponent
|
|
1043
|
+
registerComponent,
|
|
1044
|
+
state
|
|
869
1045
|
};
|