@wsxjs/wsx-core 0.0.6 → 0.0.8
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/LICENSE +21 -0
- package/dist/index.js +299 -411
- package/dist/index.mjs +297 -404
- package/package.json +46 -46
- package/src/base-component.ts +239 -0
- package/src/index.ts +2 -4
- package/src/light-component.ts +38 -165
- package/src/reactive-decorator.ts +105 -0
- package/src/web-component.ts +157 -110
- package/types/index.d.ts +2 -2
- package/types/wsx-types.d.ts +4 -2
- package/src/reactive-component.ts +0 -306
package/dist/index.mjs
CHANGED
|
@@ -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") {
|
|
@@ -375,16 +209,26 @@ function reactiveWithDebug(obj, onChange, debugName) {
|
|
|
375
209
|
});
|
|
376
210
|
}
|
|
377
211
|
|
|
378
|
-
// src/
|
|
379
|
-
var
|
|
380
|
-
var LightComponent = class extends HTMLElement {
|
|
212
|
+
// src/base-component.ts
|
|
213
|
+
var BaseComponent = class extends HTMLElement {
|
|
381
214
|
constructor(config = {}) {
|
|
382
215
|
super();
|
|
383
216
|
this.connected = false;
|
|
384
217
|
this._isDebugEnabled = false;
|
|
385
218
|
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
386
|
-
this.config = config;
|
|
387
219
|
this._isDebugEnabled = config.debug ?? false;
|
|
220
|
+
const host = this;
|
|
221
|
+
const originalStyles = config.styles;
|
|
222
|
+
this.config = {
|
|
223
|
+
...config,
|
|
224
|
+
get styles() {
|
|
225
|
+
const result = originalStyles || host._autoStyles || "";
|
|
226
|
+
return result;
|
|
227
|
+
},
|
|
228
|
+
set styles(value) {
|
|
229
|
+
config.styles = value;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
388
232
|
}
|
|
389
233
|
/**
|
|
390
234
|
* 子类应该重写这个方法来定义观察的属性
|
|
@@ -393,56 +237,12 @@ var LightComponent = class extends HTMLElement {
|
|
|
393
237
|
static get observedAttributes() {
|
|
394
238
|
return [];
|
|
395
239
|
}
|
|
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
240
|
/**
|
|
423
241
|
* Web Component生命周期:属性变化
|
|
424
242
|
*/
|
|
425
243
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
426
244
|
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
427
245
|
}
|
|
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
246
|
/**
|
|
447
247
|
* 创建响应式对象
|
|
448
248
|
*
|
|
@@ -465,10 +265,16 @@ var LightComponent = class extends HTMLElement {
|
|
|
465
265
|
useState(key, initialValue) {
|
|
466
266
|
if (!this._reactiveStates.has(key)) {
|
|
467
267
|
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
468
|
-
this._reactiveStates.set(key, {
|
|
268
|
+
this._reactiveStates.set(key, {
|
|
269
|
+
getter,
|
|
270
|
+
setter
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const state2 = this._reactiveStates.get(key);
|
|
274
|
+
if (!state2) {
|
|
275
|
+
throw new Error(`State ${key} not found`);
|
|
469
276
|
}
|
|
470
|
-
|
|
471
|
-
return [state.getter, state.setter];
|
|
277
|
+
return [state2.getter, state2.setter];
|
|
472
278
|
}
|
|
473
279
|
/**
|
|
474
280
|
* 调度重渲染
|
|
@@ -480,91 +286,23 @@ var LightComponent = class extends HTMLElement {
|
|
|
480
286
|
}
|
|
481
287
|
}
|
|
482
288
|
/**
|
|
483
|
-
*
|
|
289
|
+
* 获取配置值
|
|
290
|
+
*
|
|
291
|
+
* @param key - 配置键
|
|
292
|
+
* @param defaultValue - 默认值
|
|
293
|
+
* @returns 配置值
|
|
484
294
|
*/
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
logger3.warn(
|
|
488
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
489
|
-
);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
this.innerHTML = "";
|
|
493
|
-
try {
|
|
494
|
-
const content = this.render();
|
|
495
|
-
this.appendChild(content);
|
|
496
|
-
} catch (error) {
|
|
497
|
-
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
498
|
-
this.renderError(error);
|
|
499
|
-
}
|
|
295
|
+
getConfig(key, defaultValue) {
|
|
296
|
+
return this.config[key] ?? defaultValue;
|
|
500
297
|
}
|
|
501
298
|
/**
|
|
502
|
-
*
|
|
299
|
+
* 设置配置值
|
|
503
300
|
*
|
|
504
|
-
* @param
|
|
301
|
+
* @param key - 配置键
|
|
302
|
+
* @param value - 配置值
|
|
505
303
|
*/
|
|
506
|
-
|
|
507
|
-
this.
|
|
508
|
-
const errorElement = h(
|
|
509
|
-
"div",
|
|
510
|
-
{
|
|
511
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
512
|
-
},
|
|
513
|
-
[
|
|
514
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
515
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
516
|
-
]
|
|
517
|
-
);
|
|
518
|
-
this.appendChild(errorElement);
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* 为Light DOM组件应用样式
|
|
522
|
-
* 直接将样式注入到组件自身,避免全局污染
|
|
523
|
-
*/
|
|
524
|
-
applyScopedStyles(styleName, cssText) {
|
|
525
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
526
|
-
if (existingStyle) {
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
const styleElement = document.createElement("style");
|
|
530
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
531
|
-
styleElement.textContent = cssText;
|
|
532
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* 获取配置值
|
|
536
|
-
*
|
|
537
|
-
* @param key - 配置键
|
|
538
|
-
* @param defaultValue - 默认值
|
|
539
|
-
* @returns 配置值
|
|
540
|
-
*/
|
|
541
|
-
getConfig(key, defaultValue) {
|
|
542
|
-
return this.config[key] ?? defaultValue;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* 设置配置值
|
|
546
|
-
*
|
|
547
|
-
* @param key - 配置键
|
|
548
|
-
* @param value - 配置值
|
|
549
|
-
*/
|
|
550
|
-
setConfig(key, value) {
|
|
551
|
-
this.config[key] = value;
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* 清理响应式状态
|
|
555
|
-
*/
|
|
556
|
-
cleanupReactiveStates() {
|
|
557
|
-
this._reactiveStates.clear();
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* 清理组件样式
|
|
561
|
-
*/
|
|
562
|
-
cleanupStyles() {
|
|
563
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
564
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
565
|
-
if (existingStyle) {
|
|
566
|
-
existingStyle.remove();
|
|
567
|
-
}
|
|
304
|
+
setConfig(key, value) {
|
|
305
|
+
this.config[key] = value;
|
|
568
306
|
}
|
|
569
307
|
/**
|
|
570
308
|
* 获取属性值
|
|
@@ -602,92 +340,102 @@ var LightComponent = class extends HTMLElement {
|
|
|
602
340
|
hasAttr(name) {
|
|
603
341
|
return this.hasAttribute(name);
|
|
604
342
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
611
|
-
if (!customElements.get(tagName)) {
|
|
612
|
-
customElements.define(tagName, constructor);
|
|
613
|
-
}
|
|
614
|
-
return constructor;
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
function registerComponent(constructor, options = {}) {
|
|
618
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
619
|
-
if (!customElements.get(tagName)) {
|
|
620
|
-
customElements.define(tagName, constructor);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
function deriveTagName(className, prefix) {
|
|
624
|
-
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
625
|
-
if (!kebabCase.includes("-")) {
|
|
626
|
-
kebabCase = `${kebabCase}-component`;
|
|
343
|
+
/**
|
|
344
|
+
* 清理响应式状态
|
|
345
|
+
*/
|
|
346
|
+
cleanupReactiveStates() {
|
|
347
|
+
this._reactiveStates.clear();
|
|
627
348
|
}
|
|
628
|
-
|
|
629
|
-
}
|
|
349
|
+
};
|
|
630
350
|
|
|
631
|
-
// src/
|
|
632
|
-
var
|
|
351
|
+
// src/web-component.ts
|
|
352
|
+
var WebComponent = class extends BaseComponent {
|
|
633
353
|
constructor(config = {}) {
|
|
634
354
|
super(config);
|
|
635
|
-
|
|
355
|
+
// Initialized by BaseComponent constructor
|
|
636
356
|
this._preserveFocus = true;
|
|
637
|
-
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
638
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
639
357
|
this._preserveFocus = config.preserveFocus ?? true;
|
|
358
|
+
this.attachShadow({ mode: "open" });
|
|
640
359
|
}
|
|
641
360
|
/**
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
* @param obj 要变为响应式的对象
|
|
645
|
-
* @param debugName 调试名称(可选)
|
|
646
|
-
* @returns 响应式代理对象
|
|
361
|
+
* Web Component生命周期:连接到DOM
|
|
647
362
|
*/
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
363
|
+
connectedCallback() {
|
|
364
|
+
this.connected = true;
|
|
365
|
+
try {
|
|
366
|
+
const stylesToApply = this._getAutoStyles?.() || this.config.styles;
|
|
367
|
+
if (stylesToApply) {
|
|
368
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
369
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
370
|
+
}
|
|
371
|
+
const content = this.render();
|
|
372
|
+
this.shadowRoot.appendChild(content);
|
|
373
|
+
this.onConnected?.();
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
376
|
+
this.renderError(error);
|
|
377
|
+
}
|
|
652
378
|
}
|
|
653
379
|
/**
|
|
654
|
-
*
|
|
380
|
+
* Web Component生命周期:从DOM断开
|
|
381
|
+
*/
|
|
382
|
+
disconnectedCallback() {
|
|
383
|
+
this.connected = false;
|
|
384
|
+
this.onDisconnected?.();
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* 查找Shadow DOM内的元素
|
|
655
388
|
*
|
|
656
|
-
* @param
|
|
657
|
-
* @
|
|
658
|
-
* @returns [getter, setter] 元组
|
|
389
|
+
* @param selector - CSS选择器
|
|
390
|
+
* @returns 元素或null
|
|
659
391
|
*/
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
663
|
-
this._reactiveStates.set(key, { getter, setter });
|
|
664
|
-
}
|
|
665
|
-
const state = this._reactiveStates.get(key);
|
|
666
|
-
return [state.getter, state.setter];
|
|
392
|
+
querySelector(selector) {
|
|
393
|
+
return this.shadowRoot.querySelector(selector);
|
|
667
394
|
}
|
|
668
395
|
/**
|
|
669
|
-
*
|
|
670
|
-
*
|
|
396
|
+
* 查找Shadow DOM内的所有匹配元素
|
|
397
|
+
*
|
|
398
|
+
* @param selector - CSS选择器
|
|
399
|
+
* @returns 元素列表
|
|
671
400
|
*/
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
this.rerender();
|
|
675
|
-
}
|
|
401
|
+
querySelectorAll(selector) {
|
|
402
|
+
return this.shadowRoot.querySelectorAll(selector);
|
|
676
403
|
}
|
|
677
404
|
/**
|
|
678
|
-
*
|
|
405
|
+
* 重新渲染组件
|
|
679
406
|
*/
|
|
680
407
|
rerender() {
|
|
681
408
|
if (!this.connected) {
|
|
409
|
+
console.warn(
|
|
410
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
411
|
+
);
|
|
682
412
|
return;
|
|
683
413
|
}
|
|
684
414
|
let focusData = null;
|
|
685
|
-
if (this._preserveFocus) {
|
|
415
|
+
if (this._preserveFocus && this.shadowRoot) {
|
|
686
416
|
const activeElement = this.shadowRoot.activeElement;
|
|
687
417
|
focusData = this.saveFocusState(activeElement);
|
|
688
418
|
}
|
|
689
|
-
|
|
690
|
-
|
|
419
|
+
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
420
|
+
this.shadowRoot.innerHTML = "";
|
|
421
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
422
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
423
|
+
}
|
|
424
|
+
if (adoptedStyleSheets.length === 0) {
|
|
425
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
426
|
+
if (stylesToApply) {
|
|
427
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
428
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const content = this.render();
|
|
433
|
+
this.shadowRoot.appendChild(content);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
436
|
+
this.renderError(error);
|
|
437
|
+
}
|
|
438
|
+
if (this._preserveFocus && focusData && this.shadowRoot) {
|
|
691
439
|
this.restoreFocusState(focusData);
|
|
692
440
|
}
|
|
693
441
|
}
|
|
@@ -713,8 +461,8 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
713
461
|
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
|
|
714
462
|
focusData.value = activeElement.value;
|
|
715
463
|
if ("selectionStart" in activeElement) {
|
|
716
|
-
focusData.selectionStart = activeElement.selectionStart;
|
|
717
|
-
focusData.selectionEnd = activeElement.selectionEnd;
|
|
464
|
+
focusData.selectionStart = activeElement.selectionStart ?? void 0;
|
|
465
|
+
focusData.selectionEnd = activeElement.selectionEnd ?? void 0;
|
|
718
466
|
}
|
|
719
467
|
}
|
|
720
468
|
return focusData;
|
|
@@ -740,10 +488,10 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
740
488
|
if (targetElement instanceof HTMLInputElement) {
|
|
741
489
|
targetElement.setSelectionRange(
|
|
742
490
|
focusData.selectionStart,
|
|
743
|
-
focusData.selectionEnd
|
|
491
|
+
focusData.selectionEnd ?? focusData.selectionStart
|
|
744
492
|
);
|
|
745
493
|
} else if (targetElement instanceof HTMLSelectElement) {
|
|
746
|
-
targetElement.value = focusData.value;
|
|
494
|
+
targetElement.value = focusData.value ?? "";
|
|
747
495
|
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
748
496
|
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
749
497
|
}
|
|
@@ -773,82 +521,227 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
773
521
|
}
|
|
774
522
|
}
|
|
775
523
|
/**
|
|
776
|
-
*
|
|
524
|
+
* 渲染错误信息
|
|
525
|
+
*
|
|
526
|
+
* @param error - 错误对象
|
|
777
527
|
*/
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
528
|
+
renderError(error) {
|
|
529
|
+
this.shadowRoot.innerHTML = "";
|
|
530
|
+
const errorElement = h(
|
|
531
|
+
"div",
|
|
532
|
+
{
|
|
533
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
534
|
+
},
|
|
535
|
+
[
|
|
536
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
537
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
538
|
+
]
|
|
539
|
+
);
|
|
540
|
+
this.shadowRoot.appendChild(errorElement);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/light-component.ts
|
|
545
|
+
var logger3 = createLogger("LightComponent");
|
|
546
|
+
var LightComponent = class extends BaseComponent {
|
|
547
|
+
// Initialized by BaseComponent constructor
|
|
548
|
+
constructor(config = {}) {
|
|
549
|
+
super(config);
|
|
784
550
|
}
|
|
785
551
|
/**
|
|
786
|
-
*
|
|
552
|
+
* Web Component生命周期:连接到DOM
|
|
787
553
|
*/
|
|
788
|
-
|
|
789
|
-
this.
|
|
554
|
+
connectedCallback() {
|
|
555
|
+
this.connected = true;
|
|
556
|
+
try {
|
|
557
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
558
|
+
if (stylesToApply) {
|
|
559
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
560
|
+
this.applyScopedStyles(styleName, stylesToApply);
|
|
561
|
+
}
|
|
562
|
+
const content = this.render();
|
|
563
|
+
this.appendChild(content);
|
|
564
|
+
this.onConnected?.();
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
567
|
+
this.renderError(error);
|
|
568
|
+
}
|
|
790
569
|
}
|
|
791
570
|
/**
|
|
792
|
-
*
|
|
571
|
+
* Web Component生命周期:从DOM断开
|
|
793
572
|
*/
|
|
794
573
|
disconnectedCallback() {
|
|
795
|
-
super.disconnectedCallback();
|
|
796
574
|
this.cleanupReactiveStates();
|
|
575
|
+
this.cleanupStyles();
|
|
576
|
+
this.onDisconnected?.();
|
|
797
577
|
}
|
|
798
578
|
/**
|
|
799
|
-
*
|
|
579
|
+
* 查找组件内的元素
|
|
580
|
+
*
|
|
581
|
+
* @param selector - CSS选择器
|
|
582
|
+
* @returns 元素或null
|
|
800
583
|
*/
|
|
801
|
-
|
|
802
|
-
this
|
|
584
|
+
querySelector(selector) {
|
|
585
|
+
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
803
586
|
}
|
|
804
587
|
/**
|
|
805
|
-
*
|
|
588
|
+
* 查找组件内的所有匹配元素
|
|
589
|
+
*
|
|
590
|
+
* @param selector - CSS选择器
|
|
591
|
+
* @returns 元素列表
|
|
806
592
|
*/
|
|
807
|
-
|
|
808
|
-
this
|
|
593
|
+
querySelectorAll(selector) {
|
|
594
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
809
595
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
596
|
+
/**
|
|
597
|
+
* 重新渲染组件
|
|
598
|
+
*/
|
|
599
|
+
rerender() {
|
|
600
|
+
if (!this.connected) {
|
|
601
|
+
logger3.warn(
|
|
602
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
603
|
+
);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this.innerHTML = "";
|
|
607
|
+
if (this.config.styles) {
|
|
608
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
609
|
+
const styleElement = document.createElement("style");
|
|
610
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
611
|
+
styleElement.textContent = this.config.styles;
|
|
612
|
+
this.appendChild(styleElement);
|
|
613
|
+
}
|
|
614
|
+
try {
|
|
615
|
+
const content = this.render();
|
|
616
|
+
this.appendChild(content);
|
|
617
|
+
if (this.config.styles && this.children.length > 1) {
|
|
618
|
+
const styleElement = this.querySelector(
|
|
619
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
620
|
+
);
|
|
621
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
622
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
820
623
|
}
|
|
821
624
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
625
|
+
} catch (error) {
|
|
626
|
+
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
627
|
+
this.renderError(error);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* 渲染错误信息
|
|
632
|
+
*
|
|
633
|
+
* @param error - 错误对象
|
|
634
|
+
*/
|
|
635
|
+
renderError(error) {
|
|
636
|
+
this.innerHTML = "";
|
|
637
|
+
const errorElement = h(
|
|
638
|
+
"div",
|
|
639
|
+
{
|
|
640
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
641
|
+
},
|
|
642
|
+
[
|
|
643
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
644
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
645
|
+
]
|
|
646
|
+
);
|
|
647
|
+
this.appendChild(errorElement);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 为Light DOM组件应用样式
|
|
651
|
+
* 直接将样式注入到组件自身,避免全局污染
|
|
652
|
+
*/
|
|
653
|
+
applyScopedStyles(styleName, cssText) {
|
|
654
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
655
|
+
if (existingStyle) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const styleElement = document.createElement("style");
|
|
659
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
660
|
+
styleElement.textContent = cssText;
|
|
661
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* 清理组件样式
|
|
665
|
+
*/
|
|
666
|
+
cleanupStyles() {
|
|
667
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
668
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
669
|
+
if (existingStyle) {
|
|
670
|
+
existingStyle.remove();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// src/auto-register.ts
|
|
676
|
+
function autoRegister(options = {}) {
|
|
677
|
+
return function(constructor) {
|
|
678
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
679
|
+
if (!customElements.get(tagName)) {
|
|
680
|
+
customElements.define(tagName, constructor);
|
|
681
|
+
}
|
|
682
|
+
return constructor;
|
|
826
683
|
};
|
|
827
684
|
}
|
|
828
|
-
function
|
|
829
|
-
|
|
830
|
-
|
|
685
|
+
function registerComponent(constructor, options = {}) {
|
|
686
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
687
|
+
if (!customElements.get(tagName)) {
|
|
688
|
+
customElements.define(tagName, constructor);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function deriveTagName(className, prefix) {
|
|
692
|
+
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
693
|
+
if (!kebabCase.includes("-")) {
|
|
694
|
+
kebabCase = `${kebabCase}-component`;
|
|
695
|
+
}
|
|
696
|
+
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/reactive-decorator.ts
|
|
700
|
+
function state(target, propertyKey) {
|
|
701
|
+
let normalizedPropertyKey;
|
|
702
|
+
if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
|
|
703
|
+
normalizedPropertyKey = propertyKey;
|
|
704
|
+
} else {
|
|
705
|
+
const propertyKeyStr = String(propertyKey);
|
|
706
|
+
if (propertyKeyStr === "[object Object]") {
|
|
707
|
+
throw new Error(
|
|
708
|
+
`@state decorator: Invalid propertyKey. This usually means the build tool doesn't support decorators properly. Please ensure Babel plugin is configured in vite.config.ts`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
normalizedPropertyKey = propertyKeyStr;
|
|
712
|
+
}
|
|
713
|
+
if (target == null) {
|
|
714
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
715
|
+
throw new Error(
|
|
716
|
+
`@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}. Please ensure Babel plugin is configured in vite.config.ts`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (typeof target !== "object") {
|
|
720
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
721
|
+
throw new Error(
|
|
722
|
+
`@state decorator: Cannot be used on "${propertyKeyStr}". @state is for properties only, not methods.`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
|
|
726
|
+
if (descriptor?.get) {
|
|
727
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
728
|
+
throw new Error(
|
|
729
|
+
`@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
|
|
730
|
+
);
|
|
831
731
|
}
|
|
832
|
-
const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
|
|
833
|
-
return new ReactiveComponent(config);
|
|
834
732
|
}
|
|
835
733
|
export {
|
|
836
734
|
Fragment,
|
|
837
735
|
LightComponent,
|
|
838
|
-
ReactiveDebug,
|
|
839
|
-
ReactiveWebComponent,
|
|
840
736
|
StyleManager,
|
|
841
737
|
WSXLogger,
|
|
842
738
|
WebComponent,
|
|
843
739
|
autoRegister,
|
|
844
740
|
createLogger,
|
|
845
|
-
createReactiveComponent,
|
|
846
|
-
createState,
|
|
847
741
|
h,
|
|
848
742
|
h as jsx,
|
|
849
743
|
h as jsxs,
|
|
850
744
|
logger,
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
registerComponent
|
|
745
|
+
registerComponent,
|
|
746
|
+
state
|
|
854
747
|
};
|