@wsxjs/wsx-core 0.0.7 → 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/dist/index.js +297 -424
- package/dist/index.mjs +295 -417
- package/package.json +1 -1
- package/src/base-component.ts +239 -0
- package/src/index.ts +2 -4
- package/src/light-component.ts +13 -163
- package/src/reactive-decorator.ts +105 -0
- package/src/web-component.ts +157 -110
- package/types/index.d.ts +2 -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,107 +286,24 @@ 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
|
-
if (this.config.styles) {
|
|
494
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
495
|
-
const styleElement = document.createElement("style");
|
|
496
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
497
|
-
styleElement.textContent = this.config.styles;
|
|
498
|
-
this.appendChild(styleElement);
|
|
499
|
-
}
|
|
500
|
-
try {
|
|
501
|
-
const content = this.render();
|
|
502
|
-
this.appendChild(content);
|
|
503
|
-
if (this.config.styles && this.children.length > 1) {
|
|
504
|
-
const styleElement = this.querySelector(
|
|
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
|
-
}
|
|
510
|
-
}
|
|
511
|
-
} catch (error) {
|
|
512
|
-
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
513
|
-
this.renderError(error);
|
|
514
|
-
}
|
|
295
|
+
getConfig(key, defaultValue) {
|
|
296
|
+
return this.config[key] ?? defaultValue;
|
|
515
297
|
}
|
|
516
298
|
/**
|
|
517
|
-
*
|
|
299
|
+
* 设置配置值
|
|
518
300
|
*
|
|
519
|
-
* @param
|
|
520
|
-
|
|
521
|
-
renderError(error) {
|
|
522
|
-
this.innerHTML = "";
|
|
523
|
-
const errorElement = h(
|
|
524
|
-
"div",
|
|
525
|
-
{
|
|
526
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
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);
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* 为Light DOM组件应用样式
|
|
537
|
-
* 直接将样式注入到组件自身,避免全局污染
|
|
538
|
-
*/
|
|
539
|
-
applyScopedStyles(styleName, cssText) {
|
|
540
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
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);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* 获取配置值
|
|
551
|
-
*
|
|
552
|
-
* @param key - 配置键
|
|
553
|
-
* @param defaultValue - 默认值
|
|
554
|
-
* @returns 配置值
|
|
555
|
-
*/
|
|
556
|
-
getConfig(key, defaultValue) {
|
|
557
|
-
return this.config[key] ?? defaultValue;
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* 设置配置值
|
|
561
|
-
*
|
|
562
|
-
* @param key - 配置键
|
|
563
|
-
* @param value - 配置值
|
|
301
|
+
* @param key - 配置键
|
|
302
|
+
* @param value - 配置值
|
|
564
303
|
*/
|
|
565
304
|
setConfig(key, value) {
|
|
566
305
|
this.config[key] = value;
|
|
567
306
|
}
|
|
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
307
|
/**
|
|
585
308
|
* 获取属性值
|
|
586
309
|
*
|
|
@@ -617,92 +340,102 @@ var LightComponent = class extends HTMLElement {
|
|
|
617
340
|
hasAttr(name) {
|
|
618
341
|
return this.hasAttribute(name);
|
|
619
342
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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`;
|
|
343
|
+
/**
|
|
344
|
+
* 清理响应式状态
|
|
345
|
+
*/
|
|
346
|
+
cleanupReactiveStates() {
|
|
347
|
+
this._reactiveStates.clear();
|
|
642
348
|
}
|
|
643
|
-
|
|
644
|
-
}
|
|
349
|
+
};
|
|
645
350
|
|
|
646
|
-
// src/
|
|
647
|
-
var
|
|
351
|
+
// src/web-component.ts
|
|
352
|
+
var WebComponent = class extends BaseComponent {
|
|
648
353
|
constructor(config = {}) {
|
|
649
354
|
super(config);
|
|
650
|
-
|
|
355
|
+
// Initialized by BaseComponent constructor
|
|
651
356
|
this._preserveFocus = true;
|
|
652
|
-
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
653
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
654
357
|
this._preserveFocus = config.preserveFocus ?? true;
|
|
358
|
+
this.attachShadow({ mode: "open" });
|
|
655
359
|
}
|
|
656
360
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
* @param obj 要变为响应式的对象
|
|
660
|
-
* @param debugName 调试名称(可选)
|
|
661
|
-
* @returns 响应式代理对象
|
|
361
|
+
* Web Component生命周期:连接到DOM
|
|
662
362
|
*/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
+
}
|
|
667
378
|
}
|
|
668
379
|
/**
|
|
669
|
-
*
|
|
380
|
+
* Web Component生命周期:从DOM断开
|
|
381
|
+
*/
|
|
382
|
+
disconnectedCallback() {
|
|
383
|
+
this.connected = false;
|
|
384
|
+
this.onDisconnected?.();
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* 查找Shadow DOM内的元素
|
|
670
388
|
*
|
|
671
|
-
* @param
|
|
672
|
-
* @
|
|
673
|
-
* @returns [getter, setter] 元组
|
|
389
|
+
* @param selector - CSS选择器
|
|
390
|
+
* @returns 元素或null
|
|
674
391
|
*/
|
|
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];
|
|
392
|
+
querySelector(selector) {
|
|
393
|
+
return this.shadowRoot.querySelector(selector);
|
|
682
394
|
}
|
|
683
395
|
/**
|
|
684
|
-
*
|
|
685
|
-
*
|
|
396
|
+
* 查找Shadow DOM内的所有匹配元素
|
|
397
|
+
*
|
|
398
|
+
* @param selector - CSS选择器
|
|
399
|
+
* @returns 元素列表
|
|
686
400
|
*/
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
this.rerender();
|
|
690
|
-
}
|
|
401
|
+
querySelectorAll(selector) {
|
|
402
|
+
return this.shadowRoot.querySelectorAll(selector);
|
|
691
403
|
}
|
|
692
404
|
/**
|
|
693
|
-
*
|
|
405
|
+
* 重新渲染组件
|
|
694
406
|
*/
|
|
695
407
|
rerender() {
|
|
696
408
|
if (!this.connected) {
|
|
409
|
+
console.warn(
|
|
410
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
411
|
+
);
|
|
697
412
|
return;
|
|
698
413
|
}
|
|
699
414
|
let focusData = null;
|
|
700
|
-
if (this._preserveFocus) {
|
|
415
|
+
if (this._preserveFocus && this.shadowRoot) {
|
|
701
416
|
const activeElement = this.shadowRoot.activeElement;
|
|
702
417
|
focusData = this.saveFocusState(activeElement);
|
|
703
418
|
}
|
|
704
|
-
|
|
705
|
-
|
|
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) {
|
|
706
439
|
this.restoreFocusState(focusData);
|
|
707
440
|
}
|
|
708
441
|
}
|
|
@@ -728,8 +461,8 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
728
461
|
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
|
|
729
462
|
focusData.value = activeElement.value;
|
|
730
463
|
if ("selectionStart" in activeElement) {
|
|
731
|
-
focusData.selectionStart = activeElement.selectionStart;
|
|
732
|
-
focusData.selectionEnd = activeElement.selectionEnd;
|
|
464
|
+
focusData.selectionStart = activeElement.selectionStart ?? void 0;
|
|
465
|
+
focusData.selectionEnd = activeElement.selectionEnd ?? void 0;
|
|
733
466
|
}
|
|
734
467
|
}
|
|
735
468
|
return focusData;
|
|
@@ -755,10 +488,10 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
755
488
|
if (targetElement instanceof HTMLInputElement) {
|
|
756
489
|
targetElement.setSelectionRange(
|
|
757
490
|
focusData.selectionStart,
|
|
758
|
-
focusData.selectionEnd
|
|
491
|
+
focusData.selectionEnd ?? focusData.selectionStart
|
|
759
492
|
);
|
|
760
493
|
} else if (targetElement instanceof HTMLSelectElement) {
|
|
761
|
-
targetElement.value = focusData.value;
|
|
494
|
+
targetElement.value = focusData.value ?? "";
|
|
762
495
|
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
763
496
|
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
764
497
|
}
|
|
@@ -788,82 +521,227 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
788
521
|
}
|
|
789
522
|
}
|
|
790
523
|
/**
|
|
791
|
-
*
|
|
524
|
+
* 渲染错误信息
|
|
525
|
+
*
|
|
526
|
+
* @param error - 错误对象
|
|
792
527
|
*/
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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);
|
|
799
550
|
}
|
|
800
551
|
/**
|
|
801
|
-
*
|
|
552
|
+
* Web Component生命周期:连接到DOM
|
|
802
553
|
*/
|
|
803
|
-
|
|
804
|
-
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
|
+
}
|
|
805
569
|
}
|
|
806
570
|
/**
|
|
807
|
-
*
|
|
571
|
+
* Web Component生命周期:从DOM断开
|
|
808
572
|
*/
|
|
809
573
|
disconnectedCallback() {
|
|
810
|
-
super.disconnectedCallback();
|
|
811
574
|
this.cleanupReactiveStates();
|
|
575
|
+
this.cleanupStyles();
|
|
576
|
+
this.onDisconnected?.();
|
|
812
577
|
}
|
|
813
578
|
/**
|
|
814
|
-
*
|
|
579
|
+
* 查找组件内的元素
|
|
580
|
+
*
|
|
581
|
+
* @param selector - CSS选择器
|
|
582
|
+
* @returns 元素或null
|
|
815
583
|
*/
|
|
816
|
-
|
|
817
|
-
this
|
|
584
|
+
querySelector(selector) {
|
|
585
|
+
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
818
586
|
}
|
|
819
587
|
/**
|
|
820
|
-
*
|
|
588
|
+
* 查找组件内的所有匹配元素
|
|
589
|
+
*
|
|
590
|
+
* @param selector - CSS选择器
|
|
591
|
+
* @returns 元素列表
|
|
821
592
|
*/
|
|
822
|
-
|
|
823
|
-
this
|
|
593
|
+
querySelectorAll(selector) {
|
|
594
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
824
595
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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);
|
|
835
623
|
}
|
|
836
624
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
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;
|
|
841
683
|
};
|
|
842
684
|
}
|
|
843
|
-
function
|
|
844
|
-
|
|
845
|
-
|
|
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
|
+
);
|
|
846
731
|
}
|
|
847
|
-
const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
|
|
848
|
-
return new ReactiveComponent(config);
|
|
849
732
|
}
|
|
850
733
|
export {
|
|
851
734
|
Fragment,
|
|
852
735
|
LightComponent,
|
|
853
|
-
ReactiveDebug,
|
|
854
|
-
ReactiveWebComponent,
|
|
855
736
|
StyleManager,
|
|
856
737
|
WSXLogger,
|
|
857
738
|
WebComponent,
|
|
858
739
|
autoRegister,
|
|
859
740
|
createLogger,
|
|
860
|
-
createReactiveComponent,
|
|
861
|
-
createState,
|
|
862
741
|
h,
|
|
863
742
|
h as jsx,
|
|
864
743
|
h as jsxs,
|
|
865
744
|
logger,
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
registerComponent
|
|
745
|
+
registerComponent,
|
|
746
|
+
state
|
|
869
747
|
};
|