@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.js
CHANGED
|
@@ -22,22 +22,17 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Fragment: () => Fragment,
|
|
24
24
|
LightComponent: () => LightComponent,
|
|
25
|
-
ReactiveDebug: () => ReactiveDebug,
|
|
26
|
-
ReactiveWebComponent: () => ReactiveWebComponent,
|
|
27
25
|
StyleManager: () => StyleManager,
|
|
28
26
|
WSXLogger: () => WSXLogger,
|
|
29
27
|
WebComponent: () => WebComponent,
|
|
30
28
|
autoRegister: () => autoRegister,
|
|
31
29
|
createLogger: () => createLogger,
|
|
32
|
-
createReactiveComponent: () => createReactiveComponent,
|
|
33
|
-
createState: () => createState,
|
|
34
30
|
h: () => h,
|
|
35
31
|
jsx: () => h,
|
|
36
32
|
jsxs: () => h,
|
|
37
33
|
logger: () => logger,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
registerComponent: () => registerComponent
|
|
34
|
+
registerComponent: () => registerComponent,
|
|
35
|
+
state: () => state
|
|
41
36
|
});
|
|
42
37
|
module.exports = __toCommonJS(index_exports);
|
|
43
38
|
|
|
@@ -274,172 +269,6 @@ var StyleManager = class {
|
|
|
274
269
|
};
|
|
275
270
|
StyleManager.styleSheets = /* @__PURE__ */ new Map();
|
|
276
271
|
|
|
277
|
-
// src/web-component.ts
|
|
278
|
-
var WebComponent = class extends HTMLElement {
|
|
279
|
-
constructor(config = {}) {
|
|
280
|
-
super();
|
|
281
|
-
this.connected = false;
|
|
282
|
-
this.config = config;
|
|
283
|
-
this.attachShadow({ mode: "open" });
|
|
284
|
-
if (config.styles) {
|
|
285
|
-
const styleName = config.styleName || this.constructor.name;
|
|
286
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, config.styles);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* 子类应该重写这个方法来定义观察的属性
|
|
291
|
-
* @returns 要观察的属性名数组
|
|
292
|
-
*/
|
|
293
|
-
static get observedAttributes() {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Web Component生命周期:连接到DOM
|
|
298
|
-
*/
|
|
299
|
-
connectedCallback() {
|
|
300
|
-
this.connected = true;
|
|
301
|
-
try {
|
|
302
|
-
const content = this.render();
|
|
303
|
-
this.shadowRoot.appendChild(content);
|
|
304
|
-
this.onConnected?.();
|
|
305
|
-
} catch (error) {
|
|
306
|
-
console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
307
|
-
this.renderError(error);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Web Component生命周期:从DOM断开
|
|
312
|
-
*/
|
|
313
|
-
disconnectedCallback() {
|
|
314
|
-
this.onDisconnected?.();
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Web Component生命周期:属性变化
|
|
318
|
-
*/
|
|
319
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
320
|
-
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* 查找Shadow DOM内的元素
|
|
324
|
-
*
|
|
325
|
-
* @param selector - CSS选择器
|
|
326
|
-
* @returns 元素或null
|
|
327
|
-
*/
|
|
328
|
-
querySelector(selector) {
|
|
329
|
-
return this.shadowRoot.querySelector(selector);
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* 查找Shadow DOM内的所有匹配元素
|
|
333
|
-
*
|
|
334
|
-
* @param selector - CSS选择器
|
|
335
|
-
* @returns 元素列表
|
|
336
|
-
*/
|
|
337
|
-
querySelectorAll(selector) {
|
|
338
|
-
return this.shadowRoot.querySelectorAll(selector);
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* 重新渲染组件
|
|
342
|
-
*/
|
|
343
|
-
rerender() {
|
|
344
|
-
if (!this.connected) {
|
|
345
|
-
console.warn(
|
|
346
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
347
|
-
);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
351
|
-
this.shadowRoot.innerHTML = "";
|
|
352
|
-
if (this.shadowRoot.adoptedStyleSheets) {
|
|
353
|
-
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
354
|
-
}
|
|
355
|
-
if (adoptedStyleSheets.length === 0 && this.config.styles) {
|
|
356
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
357
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, this.config.styles);
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
const content = this.render();
|
|
361
|
-
this.shadowRoot.appendChild(content);
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
364
|
-
this.renderError(error);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* 渲染错误信息
|
|
369
|
-
*
|
|
370
|
-
* @param error - 错误对象
|
|
371
|
-
*/
|
|
372
|
-
renderError(error) {
|
|
373
|
-
this.shadowRoot.innerHTML = "";
|
|
374
|
-
const errorElement = h(
|
|
375
|
-
"div",
|
|
376
|
-
{
|
|
377
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
378
|
-
},
|
|
379
|
-
[
|
|
380
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
381
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
382
|
-
]
|
|
383
|
-
);
|
|
384
|
-
this.shadowRoot.appendChild(errorElement);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* 获取配置值
|
|
388
|
-
*
|
|
389
|
-
* @param key - 配置键
|
|
390
|
-
* @param defaultValue - 默认值
|
|
391
|
-
* @returns 配置值
|
|
392
|
-
*/
|
|
393
|
-
getConfig(key, defaultValue) {
|
|
394
|
-
return this.config[key] ?? defaultValue;
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* 设置配置值
|
|
398
|
-
*
|
|
399
|
-
* @param key - 配置键
|
|
400
|
-
* @param value - 配置值
|
|
401
|
-
*/
|
|
402
|
-
setConfig(key, value) {
|
|
403
|
-
this.config[key] = value;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* 获取属性值
|
|
407
|
-
*
|
|
408
|
-
* @param name - 属性名
|
|
409
|
-
* @param defaultValue - 默认值
|
|
410
|
-
* @returns 属性值
|
|
411
|
-
*/
|
|
412
|
-
getAttr(name, defaultValue = "") {
|
|
413
|
-
return this.getAttribute(name) || defaultValue;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* 设置属性值
|
|
417
|
-
*
|
|
418
|
-
* @param name - 属性名
|
|
419
|
-
* @param value - 属性值
|
|
420
|
-
*/
|
|
421
|
-
setAttr(name, value) {
|
|
422
|
-
this.setAttribute(name, value);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* 移除属性
|
|
426
|
-
*
|
|
427
|
-
* @param name - 属性名
|
|
428
|
-
*/
|
|
429
|
-
removeAttr(name) {
|
|
430
|
-
this.removeAttribute(name);
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* 检查是否有属性
|
|
434
|
-
*
|
|
435
|
-
* @param name - 属性名
|
|
436
|
-
* @returns 是否存在
|
|
437
|
-
*/
|
|
438
|
-
hasAttr(name) {
|
|
439
|
-
return this.hasAttribute(name);
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
|
|
443
272
|
// src/utils/logger.ts
|
|
444
273
|
var WSXLogger = class {
|
|
445
274
|
constructor(prefix = "[WSX]", enabled = true, level = "info") {
|
|
@@ -606,16 +435,26 @@ function reactiveWithDebug(obj, onChange, debugName) {
|
|
|
606
435
|
});
|
|
607
436
|
}
|
|
608
437
|
|
|
609
|
-
// src/
|
|
610
|
-
var
|
|
611
|
-
var LightComponent = class extends HTMLElement {
|
|
438
|
+
// src/base-component.ts
|
|
439
|
+
var BaseComponent = class extends HTMLElement {
|
|
612
440
|
constructor(config = {}) {
|
|
613
441
|
super();
|
|
614
442
|
this.connected = false;
|
|
615
443
|
this._isDebugEnabled = false;
|
|
616
444
|
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
617
|
-
this.config = config;
|
|
618
445
|
this._isDebugEnabled = config.debug ?? false;
|
|
446
|
+
const host = this;
|
|
447
|
+
const originalStyles = config.styles;
|
|
448
|
+
this.config = {
|
|
449
|
+
...config,
|
|
450
|
+
get styles() {
|
|
451
|
+
const result = originalStyles || host._autoStyles || "";
|
|
452
|
+
return result;
|
|
453
|
+
},
|
|
454
|
+
set styles(value) {
|
|
455
|
+
config.styles = value;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
619
458
|
}
|
|
620
459
|
/**
|
|
621
460
|
* 子类应该重写这个方法来定义观察的属性
|
|
@@ -624,56 +463,12 @@ var LightComponent = class extends HTMLElement {
|
|
|
624
463
|
static get observedAttributes() {
|
|
625
464
|
return [];
|
|
626
465
|
}
|
|
627
|
-
/**
|
|
628
|
-
* Web Component生命周期:连接到DOM
|
|
629
|
-
*/
|
|
630
|
-
connectedCallback() {
|
|
631
|
-
this.connected = true;
|
|
632
|
-
try {
|
|
633
|
-
if (this.config.styles) {
|
|
634
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
635
|
-
this.applyScopedStyles(styleName, this.config.styles);
|
|
636
|
-
}
|
|
637
|
-
const content = this.render();
|
|
638
|
-
this.appendChild(content);
|
|
639
|
-
this.onConnected?.();
|
|
640
|
-
} catch (error) {
|
|
641
|
-
logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
642
|
-
this.renderError(error);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Web Component生命周期:从DOM断开
|
|
647
|
-
*/
|
|
648
|
-
disconnectedCallback() {
|
|
649
|
-
this.cleanupReactiveStates();
|
|
650
|
-
this.cleanupStyles();
|
|
651
|
-
this.onDisconnected?.();
|
|
652
|
-
}
|
|
653
466
|
/**
|
|
654
467
|
* Web Component生命周期:属性变化
|
|
655
468
|
*/
|
|
656
469
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
657
470
|
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
658
471
|
}
|
|
659
|
-
/**
|
|
660
|
-
* 查找组件内的元素
|
|
661
|
-
*
|
|
662
|
-
* @param selector - CSS选择器
|
|
663
|
-
* @returns 元素或null
|
|
664
|
-
*/
|
|
665
|
-
querySelector(selector) {
|
|
666
|
-
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* 查找组件内的所有匹配元素
|
|
670
|
-
*
|
|
671
|
-
* @param selector - CSS选择器
|
|
672
|
-
* @returns 元素列表
|
|
673
|
-
*/
|
|
674
|
-
querySelectorAll(selector) {
|
|
675
|
-
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
676
|
-
}
|
|
677
472
|
/**
|
|
678
473
|
* 创建响应式对象
|
|
679
474
|
*
|
|
@@ -696,10 +491,16 @@ var LightComponent = class extends HTMLElement {
|
|
|
696
491
|
useState(key, initialValue) {
|
|
697
492
|
if (!this._reactiveStates.has(key)) {
|
|
698
493
|
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
699
|
-
this._reactiveStates.set(key, {
|
|
494
|
+
this._reactiveStates.set(key, {
|
|
495
|
+
getter,
|
|
496
|
+
setter
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
const state2 = this._reactiveStates.get(key);
|
|
500
|
+
if (!state2) {
|
|
501
|
+
throw new Error(`State ${key} not found`);
|
|
700
502
|
}
|
|
701
|
-
|
|
702
|
-
return [state.getter, state.setter];
|
|
503
|
+
return [state2.getter, state2.setter];
|
|
703
504
|
}
|
|
704
505
|
/**
|
|
705
506
|
* 调度重渲染
|
|
@@ -711,107 +512,24 @@ var LightComponent = class extends HTMLElement {
|
|
|
711
512
|
}
|
|
712
513
|
}
|
|
713
514
|
/**
|
|
714
|
-
*
|
|
515
|
+
* 获取配置值
|
|
516
|
+
*
|
|
517
|
+
* @param key - 配置键
|
|
518
|
+
* @param defaultValue - 默认值
|
|
519
|
+
* @returns 配置值
|
|
715
520
|
*/
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
logger3.warn(
|
|
719
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
720
|
-
);
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
this.innerHTML = "";
|
|
724
|
-
if (this.config.styles) {
|
|
725
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
726
|
-
const styleElement = document.createElement("style");
|
|
727
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
728
|
-
styleElement.textContent = this.config.styles;
|
|
729
|
-
this.appendChild(styleElement);
|
|
730
|
-
}
|
|
731
|
-
try {
|
|
732
|
-
const content = this.render();
|
|
733
|
-
this.appendChild(content);
|
|
734
|
-
if (this.config.styles && this.children.length > 1) {
|
|
735
|
-
const styleElement = this.querySelector(
|
|
736
|
-
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
737
|
-
);
|
|
738
|
-
if (styleElement && styleElement !== this.firstChild) {
|
|
739
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
} catch (error) {
|
|
743
|
-
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
744
|
-
this.renderError(error);
|
|
745
|
-
}
|
|
521
|
+
getConfig(key, defaultValue) {
|
|
522
|
+
return this.config[key] ?? defaultValue;
|
|
746
523
|
}
|
|
747
524
|
/**
|
|
748
|
-
*
|
|
525
|
+
* 设置配置值
|
|
749
526
|
*
|
|
750
|
-
* @param
|
|
751
|
-
|
|
752
|
-
renderError(error) {
|
|
753
|
-
this.innerHTML = "";
|
|
754
|
-
const errorElement = h(
|
|
755
|
-
"div",
|
|
756
|
-
{
|
|
757
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
758
|
-
},
|
|
759
|
-
[
|
|
760
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
761
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
762
|
-
]
|
|
763
|
-
);
|
|
764
|
-
this.appendChild(errorElement);
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* 为Light DOM组件应用样式
|
|
768
|
-
* 直接将样式注入到组件自身,避免全局污染
|
|
769
|
-
*/
|
|
770
|
-
applyScopedStyles(styleName, cssText) {
|
|
771
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
772
|
-
if (existingStyle) {
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
const styleElement = document.createElement("style");
|
|
776
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
777
|
-
styleElement.textContent = cssText;
|
|
778
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* 获取配置值
|
|
782
|
-
*
|
|
783
|
-
* @param key - 配置键
|
|
784
|
-
* @param defaultValue - 默认值
|
|
785
|
-
* @returns 配置值
|
|
786
|
-
*/
|
|
787
|
-
getConfig(key, defaultValue) {
|
|
788
|
-
return this.config[key] ?? defaultValue;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* 设置配置值
|
|
792
|
-
*
|
|
793
|
-
* @param key - 配置键
|
|
794
|
-
* @param value - 配置值
|
|
527
|
+
* @param key - 配置键
|
|
528
|
+
* @param value - 配置值
|
|
795
529
|
*/
|
|
796
530
|
setConfig(key, value) {
|
|
797
531
|
this.config[key] = value;
|
|
798
532
|
}
|
|
799
|
-
/**
|
|
800
|
-
* 清理响应式状态
|
|
801
|
-
*/
|
|
802
|
-
cleanupReactiveStates() {
|
|
803
|
-
this._reactiveStates.clear();
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* 清理组件样式
|
|
807
|
-
*/
|
|
808
|
-
cleanupStyles() {
|
|
809
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
810
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
811
|
-
if (existingStyle) {
|
|
812
|
-
existingStyle.remove();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
533
|
/**
|
|
816
534
|
* 获取属性值
|
|
817
535
|
*
|
|
@@ -848,92 +566,102 @@ var LightComponent = class extends HTMLElement {
|
|
|
848
566
|
hasAttr(name) {
|
|
849
567
|
return this.hasAttribute(name);
|
|
850
568
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
857
|
-
if (!customElements.get(tagName)) {
|
|
858
|
-
customElements.define(tagName, constructor);
|
|
859
|
-
}
|
|
860
|
-
return constructor;
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
function registerComponent(constructor, options = {}) {
|
|
864
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
865
|
-
if (!customElements.get(tagName)) {
|
|
866
|
-
customElements.define(tagName, constructor);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
function deriveTagName(className, prefix) {
|
|
870
|
-
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
871
|
-
if (!kebabCase.includes("-")) {
|
|
872
|
-
kebabCase = `${kebabCase}-component`;
|
|
569
|
+
/**
|
|
570
|
+
* 清理响应式状态
|
|
571
|
+
*/
|
|
572
|
+
cleanupReactiveStates() {
|
|
573
|
+
this._reactiveStates.clear();
|
|
873
574
|
}
|
|
874
|
-
|
|
875
|
-
}
|
|
575
|
+
};
|
|
876
576
|
|
|
877
|
-
// src/
|
|
878
|
-
var
|
|
577
|
+
// src/web-component.ts
|
|
578
|
+
var WebComponent = class extends BaseComponent {
|
|
879
579
|
constructor(config = {}) {
|
|
880
580
|
super(config);
|
|
881
|
-
|
|
581
|
+
// Initialized by BaseComponent constructor
|
|
882
582
|
this._preserveFocus = true;
|
|
883
|
-
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
884
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
885
583
|
this._preserveFocus = config.preserveFocus ?? true;
|
|
584
|
+
this.attachShadow({ mode: "open" });
|
|
886
585
|
}
|
|
887
586
|
/**
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
* @param obj 要变为响应式的对象
|
|
891
|
-
* @param debugName 调试名称(可选)
|
|
892
|
-
* @returns 响应式代理对象
|
|
587
|
+
* Web Component生命周期:连接到DOM
|
|
893
588
|
*/
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
589
|
+
connectedCallback() {
|
|
590
|
+
this.connected = true;
|
|
591
|
+
try {
|
|
592
|
+
const stylesToApply = this._getAutoStyles?.() || this.config.styles;
|
|
593
|
+
if (stylesToApply) {
|
|
594
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
595
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
596
|
+
}
|
|
597
|
+
const content = this.render();
|
|
598
|
+
this.shadowRoot.appendChild(content);
|
|
599
|
+
this.onConnected?.();
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
602
|
+
this.renderError(error);
|
|
603
|
+
}
|
|
898
604
|
}
|
|
899
605
|
/**
|
|
900
|
-
*
|
|
606
|
+
* Web Component生命周期:从DOM断开
|
|
607
|
+
*/
|
|
608
|
+
disconnectedCallback() {
|
|
609
|
+
this.connected = false;
|
|
610
|
+
this.onDisconnected?.();
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* 查找Shadow DOM内的元素
|
|
901
614
|
*
|
|
902
|
-
* @param
|
|
903
|
-
* @
|
|
904
|
-
* @returns [getter, setter] 元组
|
|
615
|
+
* @param selector - CSS选择器
|
|
616
|
+
* @returns 元素或null
|
|
905
617
|
*/
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
909
|
-
this._reactiveStates.set(key, { getter, setter });
|
|
910
|
-
}
|
|
911
|
-
const state = this._reactiveStates.get(key);
|
|
912
|
-
return [state.getter, state.setter];
|
|
618
|
+
querySelector(selector) {
|
|
619
|
+
return this.shadowRoot.querySelector(selector);
|
|
913
620
|
}
|
|
914
621
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
622
|
+
* 查找Shadow DOM内的所有匹配元素
|
|
623
|
+
*
|
|
624
|
+
* @param selector - CSS选择器
|
|
625
|
+
* @returns 元素列表
|
|
917
626
|
*/
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
this.rerender();
|
|
921
|
-
}
|
|
627
|
+
querySelectorAll(selector) {
|
|
628
|
+
return this.shadowRoot.querySelectorAll(selector);
|
|
922
629
|
}
|
|
923
630
|
/**
|
|
924
|
-
*
|
|
631
|
+
* 重新渲染组件
|
|
925
632
|
*/
|
|
926
633
|
rerender() {
|
|
927
634
|
if (!this.connected) {
|
|
635
|
+
console.warn(
|
|
636
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
637
|
+
);
|
|
928
638
|
return;
|
|
929
639
|
}
|
|
930
640
|
let focusData = null;
|
|
931
|
-
if (this._preserveFocus) {
|
|
641
|
+
if (this._preserveFocus && this.shadowRoot) {
|
|
932
642
|
const activeElement = this.shadowRoot.activeElement;
|
|
933
643
|
focusData = this.saveFocusState(activeElement);
|
|
934
644
|
}
|
|
935
|
-
|
|
936
|
-
|
|
645
|
+
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
646
|
+
this.shadowRoot.innerHTML = "";
|
|
647
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
648
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
649
|
+
}
|
|
650
|
+
if (adoptedStyleSheets.length === 0) {
|
|
651
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
652
|
+
if (stylesToApply) {
|
|
653
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
654
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
const content = this.render();
|
|
659
|
+
this.shadowRoot.appendChild(content);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
662
|
+
this.renderError(error);
|
|
663
|
+
}
|
|
664
|
+
if (this._preserveFocus && focusData && this.shadowRoot) {
|
|
937
665
|
this.restoreFocusState(focusData);
|
|
938
666
|
}
|
|
939
667
|
}
|
|
@@ -959,8 +687,8 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
959
687
|
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
|
|
960
688
|
focusData.value = activeElement.value;
|
|
961
689
|
if ("selectionStart" in activeElement) {
|
|
962
|
-
focusData.selectionStart = activeElement.selectionStart;
|
|
963
|
-
focusData.selectionEnd = activeElement.selectionEnd;
|
|
690
|
+
focusData.selectionStart = activeElement.selectionStart ?? void 0;
|
|
691
|
+
focusData.selectionEnd = activeElement.selectionEnd ?? void 0;
|
|
964
692
|
}
|
|
965
693
|
}
|
|
966
694
|
return focusData;
|
|
@@ -986,10 +714,10 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
986
714
|
if (targetElement instanceof HTMLInputElement) {
|
|
987
715
|
targetElement.setSelectionRange(
|
|
988
716
|
focusData.selectionStart,
|
|
989
|
-
focusData.selectionEnd
|
|
717
|
+
focusData.selectionEnd ?? focusData.selectionStart
|
|
990
718
|
);
|
|
991
719
|
} else if (targetElement instanceof HTMLSelectElement) {
|
|
992
|
-
targetElement.value = focusData.value;
|
|
720
|
+
targetElement.value = focusData.value ?? "";
|
|
993
721
|
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
994
722
|
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
995
723
|
}
|
|
@@ -1019,83 +747,228 @@ var ReactiveWebComponent = class extends WebComponent {
|
|
|
1019
747
|
}
|
|
1020
748
|
}
|
|
1021
749
|
/**
|
|
1022
|
-
*
|
|
750
|
+
* 渲染错误信息
|
|
751
|
+
*
|
|
752
|
+
* @param error - 错误对象
|
|
1023
753
|
*/
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
754
|
+
renderError(error) {
|
|
755
|
+
this.shadowRoot.innerHTML = "";
|
|
756
|
+
const errorElement = h(
|
|
757
|
+
"div",
|
|
758
|
+
{
|
|
759
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
760
|
+
},
|
|
761
|
+
[
|
|
762
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
763
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
764
|
+
]
|
|
765
|
+
);
|
|
766
|
+
this.shadowRoot.appendChild(errorElement);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/light-component.ts
|
|
771
|
+
var logger3 = createLogger("LightComponent");
|
|
772
|
+
var LightComponent = class extends BaseComponent {
|
|
773
|
+
// Initialized by BaseComponent constructor
|
|
774
|
+
constructor(config = {}) {
|
|
775
|
+
super(config);
|
|
1030
776
|
}
|
|
1031
777
|
/**
|
|
1032
|
-
*
|
|
778
|
+
* Web Component生命周期:连接到DOM
|
|
1033
779
|
*/
|
|
1034
|
-
|
|
1035
|
-
this.
|
|
780
|
+
connectedCallback() {
|
|
781
|
+
this.connected = true;
|
|
782
|
+
try {
|
|
783
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
784
|
+
if (stylesToApply) {
|
|
785
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
786
|
+
this.applyScopedStyles(styleName, stylesToApply);
|
|
787
|
+
}
|
|
788
|
+
const content = this.render();
|
|
789
|
+
this.appendChild(content);
|
|
790
|
+
this.onConnected?.();
|
|
791
|
+
} catch (error) {
|
|
792
|
+
logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
793
|
+
this.renderError(error);
|
|
794
|
+
}
|
|
1036
795
|
}
|
|
1037
796
|
/**
|
|
1038
|
-
*
|
|
797
|
+
* Web Component生命周期:从DOM断开
|
|
1039
798
|
*/
|
|
1040
799
|
disconnectedCallback() {
|
|
1041
|
-
super.disconnectedCallback();
|
|
1042
800
|
this.cleanupReactiveStates();
|
|
801
|
+
this.cleanupStyles();
|
|
802
|
+
this.onDisconnected?.();
|
|
1043
803
|
}
|
|
1044
804
|
/**
|
|
1045
|
-
*
|
|
805
|
+
* 查找组件内的元素
|
|
806
|
+
*
|
|
807
|
+
* @param selector - CSS选择器
|
|
808
|
+
* @returns 元素或null
|
|
1046
809
|
*/
|
|
1047
|
-
|
|
1048
|
-
this
|
|
810
|
+
querySelector(selector) {
|
|
811
|
+
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
1049
812
|
}
|
|
1050
813
|
/**
|
|
1051
|
-
*
|
|
814
|
+
* 查找组件内的所有匹配元素
|
|
815
|
+
*
|
|
816
|
+
* @param selector - CSS选择器
|
|
817
|
+
* @returns 元素列表
|
|
1052
818
|
*/
|
|
1053
|
-
|
|
1054
|
-
this
|
|
819
|
+
querySelectorAll(selector) {
|
|
820
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
1055
821
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
822
|
+
/**
|
|
823
|
+
* 重新渲染组件
|
|
824
|
+
*/
|
|
825
|
+
rerender() {
|
|
826
|
+
if (!this.connected) {
|
|
827
|
+
logger3.warn(
|
|
828
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
829
|
+
);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
this.innerHTML = "";
|
|
833
|
+
if (this.config.styles) {
|
|
834
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
835
|
+
const styleElement = document.createElement("style");
|
|
836
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
837
|
+
styleElement.textContent = this.config.styles;
|
|
838
|
+
this.appendChild(styleElement);
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
const content = this.render();
|
|
842
|
+
this.appendChild(content);
|
|
843
|
+
if (this.config.styles && this.children.length > 1) {
|
|
844
|
+
const styleElement = this.querySelector(
|
|
845
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
846
|
+
);
|
|
847
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
848
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
1066
849
|
}
|
|
1067
850
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
851
|
+
} catch (error) {
|
|
852
|
+
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
853
|
+
this.renderError(error);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* 渲染错误信息
|
|
858
|
+
*
|
|
859
|
+
* @param error - 错误对象
|
|
860
|
+
*/
|
|
861
|
+
renderError(error) {
|
|
862
|
+
this.innerHTML = "";
|
|
863
|
+
const errorElement = h(
|
|
864
|
+
"div",
|
|
865
|
+
{
|
|
866
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
867
|
+
},
|
|
868
|
+
[
|
|
869
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
870
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
871
|
+
]
|
|
872
|
+
);
|
|
873
|
+
this.appendChild(errorElement);
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* 为Light DOM组件应用样式
|
|
877
|
+
* 直接将样式注入到组件自身,避免全局污染
|
|
878
|
+
*/
|
|
879
|
+
applyScopedStyles(styleName, cssText) {
|
|
880
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
881
|
+
if (existingStyle) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const styleElement = document.createElement("style");
|
|
885
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
886
|
+
styleElement.textContent = cssText;
|
|
887
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* 清理组件样式
|
|
891
|
+
*/
|
|
892
|
+
cleanupStyles() {
|
|
893
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
894
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
895
|
+
if (existingStyle) {
|
|
896
|
+
existingStyle.remove();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/auto-register.ts
|
|
902
|
+
function autoRegister(options = {}) {
|
|
903
|
+
return function(constructor) {
|
|
904
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
905
|
+
if (!customElements.get(tagName)) {
|
|
906
|
+
customElements.define(tagName, constructor);
|
|
907
|
+
}
|
|
908
|
+
return constructor;
|
|
1072
909
|
};
|
|
1073
910
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
911
|
+
function registerComponent(constructor, options = {}) {
|
|
912
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
913
|
+
if (!customElements.get(tagName)) {
|
|
914
|
+
customElements.define(tagName, constructor);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function deriveTagName(className, prefix) {
|
|
918
|
+
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
919
|
+
if (!kebabCase.includes("-")) {
|
|
920
|
+
kebabCase = `${kebabCase}-component`;
|
|
921
|
+
}
|
|
922
|
+
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/reactive-decorator.ts
|
|
926
|
+
function state(target, propertyKey) {
|
|
927
|
+
let normalizedPropertyKey;
|
|
928
|
+
if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
|
|
929
|
+
normalizedPropertyKey = propertyKey;
|
|
930
|
+
} else {
|
|
931
|
+
const propertyKeyStr = String(propertyKey);
|
|
932
|
+
if (propertyKeyStr === "[object Object]") {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`@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`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
normalizedPropertyKey = propertyKeyStr;
|
|
938
|
+
}
|
|
939
|
+
if (target == null) {
|
|
940
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
941
|
+
throw new Error(
|
|
942
|
+
`@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}. Please ensure Babel plugin is configured in vite.config.ts`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (typeof target !== "object") {
|
|
946
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
947
|
+
throw new Error(
|
|
948
|
+
`@state decorator: Cannot be used on "${propertyKeyStr}". @state is for properties only, not methods.`
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
|
|
952
|
+
if (descriptor?.get) {
|
|
953
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
954
|
+
throw new Error(
|
|
955
|
+
`@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
|
|
956
|
+
);
|
|
1077
957
|
}
|
|
1078
|
-
const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
|
|
1079
|
-
return new ReactiveComponent(config);
|
|
1080
958
|
}
|
|
1081
959
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1082
960
|
0 && (module.exports = {
|
|
1083
961
|
Fragment,
|
|
1084
962
|
LightComponent,
|
|
1085
|
-
ReactiveDebug,
|
|
1086
|
-
ReactiveWebComponent,
|
|
1087
963
|
StyleManager,
|
|
1088
964
|
WSXLogger,
|
|
1089
965
|
WebComponent,
|
|
1090
966
|
autoRegister,
|
|
1091
967
|
createLogger,
|
|
1092
|
-
createReactiveComponent,
|
|
1093
|
-
createState,
|
|
1094
968
|
h,
|
|
1095
969
|
jsx,
|
|
1096
970
|
jsxs,
|
|
1097
971
|
logger,
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
registerComponent
|
|
972
|
+
registerComponent,
|
|
973
|
+
state
|
|
1101
974
|
});
|