@wsxjs/wsx-core 0.0.14 → 0.0.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-core",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Core WSX Framework - Web Components with JSX syntax",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -119,7 +119,7 @@ export abstract class BaseComponent extends HTMLElement {
119
119
  *
120
120
  * @returns JSX元素
121
121
  */
122
- abstract render(): HTMLElement;
122
+ abstract render(): HTMLElement | SVGElement;
123
123
 
124
124
  /**
125
125
  * 可选生命周期钩子:组件已连接
@@ -34,10 +34,16 @@ export abstract class LightComponent extends BaseComponent {
34
34
  *
35
35
  * @returns JSX元素
36
36
  */
37
- abstract render(): HTMLElement;
37
+ abstract render(): HTMLElement | SVGElement;
38
38
 
39
39
  /**
40
40
  * Web Component生命周期:连接到DOM
41
+ *
42
+ * 渲染策略:
43
+ * 1. 检查组件中是否已有实际内容(排除样式元素)
44
+ * 2. 如果有内容且完整,跳过渲染(避免重复元素和性能优化)
45
+ * 3. 如果没有内容或不完整,清空后重新渲染
46
+ * 4. 样式元素需要保留
41
47
  */
42
48
  connectedCallback(): void {
43
49
  this.connected = true;
@@ -48,19 +54,57 @@ export abstract class LightComponent extends BaseComponent {
48
54
  // So we need to check _autoStyles directly first, then fallback to config.styles getter
49
55
  // The getter will dynamically check _autoStyles when accessed
50
56
  const stylesToApply = this._autoStyles || this.config.styles;
57
+ const styleName = this.config.styleName || this.constructor.name;
51
58
  if (stylesToApply) {
52
- const styleName = this.config.styleName || this.constructor.name;
53
59
  this.applyScopedStyles(styleName, stylesToApply);
54
60
  }
55
61
 
56
- // 渲染JSX内容到Light DOM
57
- const content = this.render();
58
- this.appendChild(content);
62
+ // 检查是否有实际内容(排除样式元素)
63
+ // 错误元素:如果存在错误信息,需要重新渲染以恢复正常
64
+ const styleElement = this.querySelector(
65
+ `style[data-wsx-light-component="${styleName}"]`
66
+ ) as HTMLStyleElement | null;
67
+ const hasErrorElement = Array.from(this.children).some(
68
+ (child) =>
69
+ child instanceof HTMLElement &&
70
+ child !== styleElement &&
71
+ child.style.color === "red" &&
72
+ child.textContent?.includes("Component Error")
73
+ );
74
+ const hasActualContent = Array.from(this.children).some(
75
+ (child) => child !== styleElement
76
+ );
77
+
78
+ // 如果有错误元素,需要重新渲染以恢复正常
79
+ // 如果有实际内容且没有错误,跳过渲染(避免重复元素)
80
+ if (hasActualContent && !hasErrorElement) {
81
+ // 已经有内容且没有错误,跳过渲染(避免重复元素)
82
+ // 但确保样式元素在正确位置
83
+ if (styleElement && styleElement !== this.firstChild) {
84
+ this.insertBefore(styleElement, this.firstChild);
85
+ }
86
+ } else {
87
+ // 没有内容,需要渲染
88
+ // 清空旧内容(保留样式元素)
89
+ const childrenToRemove = Array.from(this.children).filter(
90
+ (child) => child !== styleElement
91
+ );
92
+ childrenToRemove.forEach((child) => child.remove());
93
+
94
+ // 渲染JSX内容到Light DOM
95
+ const content = this.render();
96
+ this.appendChild(content);
97
+
98
+ // 确保样式元素在第一个位置(如果存在)
99
+ if (styleElement && styleElement !== this.firstChild) {
100
+ this.insertBefore(styleElement, this.firstChild);
101
+ }
102
+ }
59
103
 
60
- // 初始化事件监听器
104
+ // 初始化事件监听器(无论是否渲染,都需要重新初始化,因为 DOM 可能被移动)
61
105
  this.initializeEventListeners();
62
106
 
63
- // 调用子类的初始化钩子
107
+ // 调用子类的初始化钩子(无论是否渲染,都需要调用,因为组件已连接)
64
108
  this.onConnected?.();
65
109
  } catch (error) {
66
110
  logger.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
@@ -40,25 +40,66 @@ export abstract class WebComponent extends BaseComponent {
40
40
  *
41
41
  * @returns JSX元素
42
42
  */
43
- abstract render(): HTMLElement;
43
+ abstract render(): HTMLElement | SVGElement;
44
44
 
45
45
  /**
46
46
  * Web Component生命周期:连接到DOM
47
+ *
48
+ * 渲染策略:
49
+ * 1. 检查 Shadow DOM 中是否已有实际内容(排除样式和 slot)
50
+ * 2. 如果有内容,先清空再渲染(避免重复元素)
51
+ * 3. 如果没有内容,直接渲染
52
+ * 4. Slot 元素会被重新添加,浏览器会自动将 Light DOM 中的内容分配到 slot
47
53
  */
48
54
  connectedCallback(): void {
49
55
  this.connected = true;
50
56
  try {
51
- // 应用CSS样式到Shadow DOM
52
- // Check both _autoStyles getter and config.styles getter
57
+ // 应用CSS样式到Shadow DOM(先应用,因为样式可能使用 fallback 添加 style 元素)
53
58
  const stylesToApply = this._autoStyles || this.config.styles;
54
59
  if (stylesToApply) {
55
60
  const styleName = this.config.styleName || this.constructor.name;
56
61
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
57
62
  }
58
63
 
59
- // 渲染JSX内容到Shadow DOM
60
- const content = this.render();
61
- this.shadowRoot.appendChild(content);
64
+ // 检查是否有实际内容(排除样式和 slot)
65
+ // 样式元素:可能由 StyleManager fallback 添加
66
+ // Slot 元素:不算"内容",因为 slot 的内容在 Light DOM 中
67
+ // 错误元素:如果存在错误信息,需要重新渲染以恢复正常
68
+ const allChildren = Array.from(this.shadowRoot.children);
69
+ const styleElements = allChildren.filter((child) => child instanceof HTMLStyleElement);
70
+ const slotElements = allChildren.filter((child) => child instanceof HTMLSlotElement);
71
+ const hasErrorElement = allChildren.some(
72
+ (child) =>
73
+ child instanceof HTMLElement &&
74
+ child.style.color === "red" &&
75
+ child.textContent?.includes("Component Error")
76
+ );
77
+ const hasActualContent =
78
+ allChildren.length > styleElements.length + slotElements.length;
79
+
80
+ // 如果有错误元素,需要重新渲染以恢复正常
81
+ // 如果有实际内容且没有错误,跳过渲染(避免重复元素)
82
+ if (hasActualContent && !hasErrorElement) {
83
+ // 已经有内容且没有错误,跳过渲染(避免重复元素)
84
+ // 样式已在上方应用(StyleManager.applyStyles 是幂等的)
85
+ // Slot 元素已存在,浏览器会自动将 Light DOM 中的内容分配到 slot
86
+ } else {
87
+ // 没有内容,需要渲染
88
+ // 清空 Shadow DOM(包括可能的旧内容)
89
+ this.shadowRoot.innerHTML = "";
90
+
91
+ // 重新应用样式(因为上面清空了)
92
+ if (stylesToApply) {
93
+ const styleName = this.config.styleName || this.constructor.name;
94
+ StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
95
+ }
96
+
97
+ // 渲染JSX内容到Shadow DOM
98
+ // render() 应该返回包含 slot 元素的内容(如果需要)
99
+ // 浏览器会自动将 Light DOM 中的内容分配到 slot
100
+ const content = this.render();
101
+ this.shadowRoot.appendChild(content);
102
+ }
62
103
 
63
104
  // 初始化事件监听器
64
105
  this.initializeEventListeners();