@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 +1 -1
- package/src/base-component.ts +1 -1
- package/src/light-component.ts +51 -7
- package/src/web-component.ts +47 -6
package/package.json
CHANGED
package/src/base-component.ts
CHANGED
package/src/light-component.ts
CHANGED
|
@@ -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
|
-
//
|
|
57
|
-
|
|
58
|
-
this.
|
|
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);
|
package/src/web-component.ts
CHANGED
|
@@ -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
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
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();
|