@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/package.json
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
"./jsx-runtime": {
|
|
15
|
-
"types": "./types/jsx-runtime.d.ts",
|
|
16
|
-
"import": "./dist/jsx-runtime.mjs",
|
|
17
|
-
"require": "./dist/jsx-runtime.js"
|
|
18
|
-
}
|
|
2
|
+
"name": "@wsxjs/wsx-core",
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "Core WSX Framework - Web Components with JSX syntax",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./types/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
19
13
|
},
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"!**/__tests__",
|
|
25
|
-
"!**/test"
|
|
26
|
-
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm",
|
|
29
|
-
"build:dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --sourcemap",
|
|
30
|
-
"dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --watch --sourcemap",
|
|
31
|
-
"test": "jest",
|
|
32
|
-
"typecheck": "tsc --noEmit",
|
|
33
|
-
"clean": "rm -rf dist"
|
|
34
|
-
},
|
|
35
|
-
"keywords": [
|
|
36
|
-
"web-components",
|
|
37
|
-
"jsx",
|
|
38
|
-
"typescript",
|
|
39
|
-
"custom-elements"
|
|
40
|
-
],
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"tsup": "^8.0.0",
|
|
43
|
-
"typescript": "^5.0.0",
|
|
44
|
-
"@types/node": "^20.0.0"
|
|
45
|
-
},
|
|
46
|
-
"peerDependencies": {
|
|
47
|
-
"typescript": ">=4.7.0"
|
|
14
|
+
"./jsx-runtime": {
|
|
15
|
+
"types": "./types/jsx-runtime.d.ts",
|
|
16
|
+
"import": "./dist/jsx-runtime.mjs",
|
|
17
|
+
"require": "./dist/jsx-runtime.js"
|
|
48
18
|
}
|
|
49
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"types",
|
|
24
|
+
"!**/__tests__",
|
|
25
|
+
"!**/test"
|
|
26
|
+
],
|
|
27
|
+
"keywords": [
|
|
28
|
+
"web-components",
|
|
29
|
+
"jsx",
|
|
30
|
+
"typescript",
|
|
31
|
+
"custom-elements"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.0.0",
|
|
36
|
+
"@types/node": "^20.0.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"typescript": ">=4.7.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm",
|
|
43
|
+
"build:dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --sourcemap",
|
|
44
|
+
"dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --watch --sourcemap",
|
|
45
|
+
"test": "jest",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"clean": "rm -rf dist"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Component for WSX Web Components
|
|
3
|
+
*
|
|
4
|
+
* Provides common functionality shared by WebComponent and LightComponent:
|
|
5
|
+
* - Reactive state management (reactive, useState, scheduleRerender)
|
|
6
|
+
* - Configuration management (getConfig, setConfig)
|
|
7
|
+
* - Attribute helpers (getAttr, setAttr, removeAttr, hasAttr)
|
|
8
|
+
* - Lifecycle hooks (onConnected, onDisconnected, onAttributeChanged)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { reactive as createReactive, createState, reactiveWithDebug } from "./utils/reactive";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Type for reactive state storage
|
|
15
|
+
*/
|
|
16
|
+
interface ReactiveStateStorage {
|
|
17
|
+
getter: () => unknown;
|
|
18
|
+
setter: (value: unknown | ((prev: unknown) => unknown)) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base configuration interface
|
|
23
|
+
*/
|
|
24
|
+
export interface BaseComponentConfig {
|
|
25
|
+
styles?: string;
|
|
26
|
+
autoStyles?: string;
|
|
27
|
+
styleName?: string;
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base Component class with common functionality
|
|
34
|
+
*
|
|
35
|
+
* This class provides shared functionality for both WebComponent and LightComponent.
|
|
36
|
+
* It should not be used directly - use WebComponent or LightComponent instead.
|
|
37
|
+
*/
|
|
38
|
+
export abstract class BaseComponent extends HTMLElement {
|
|
39
|
+
protected config: BaseComponentConfig;
|
|
40
|
+
protected connected: boolean = false;
|
|
41
|
+
protected _isDebugEnabled: boolean = false;
|
|
42
|
+
protected _reactiveStates = new Map<string, ReactiveStateStorage>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Auto-injected styles from Babel plugin (if CSS file exists)
|
|
46
|
+
* @internal - Managed by babel-plugin-wsx-style
|
|
47
|
+
*/
|
|
48
|
+
protected _autoStyles?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 子类应该重写这个方法来定义观察的属性
|
|
52
|
+
* @returns 要观察的属性名数组
|
|
53
|
+
*/
|
|
54
|
+
static get observedAttributes(): string[] {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor(config: BaseComponentConfig = {}) {
|
|
59
|
+
super();
|
|
60
|
+
|
|
61
|
+
this._isDebugEnabled = config.debug ?? false;
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
64
|
+
const host = this;
|
|
65
|
+
|
|
66
|
+
// Store original styles value to avoid infinite recursion in getter
|
|
67
|
+
const originalStyles = config.styles;
|
|
68
|
+
|
|
69
|
+
this.config = {
|
|
70
|
+
...config,
|
|
71
|
+
get styles() {
|
|
72
|
+
// Auto-detect injected styles from class property
|
|
73
|
+
// Note: _defineProperty executes in constructor after super(),
|
|
74
|
+
// so we check _autoStyles dynamically via getter
|
|
75
|
+
// This works for both WebComponent and LightComponent
|
|
76
|
+
// Priority: originalStyles > _autoStyles
|
|
77
|
+
const result = originalStyles || host._autoStyles || "";
|
|
78
|
+
return result;
|
|
79
|
+
},
|
|
80
|
+
set styles(value: string) {
|
|
81
|
+
config.styles = value;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 抽象方法:子类必须实现JSX渲染
|
|
88
|
+
*
|
|
89
|
+
* @returns JSX元素
|
|
90
|
+
*/
|
|
91
|
+
abstract render(): HTMLElement;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 可选生命周期钩子:组件已连接
|
|
95
|
+
*/
|
|
96
|
+
protected onConnected?(): void;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 可选生命周期钩子:组件已断开
|
|
100
|
+
*/
|
|
101
|
+
protected onDisconnected?(): void;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 可选生命周期钩子:属性已更改
|
|
105
|
+
*/
|
|
106
|
+
protected onAttributeChanged?(name: string, oldValue: string, newValue: string): void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Web Component生命周期:属性变化
|
|
110
|
+
*/
|
|
111
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
112
|
+
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 创建响应式对象
|
|
117
|
+
*
|
|
118
|
+
* @param obj 要变为响应式的对象
|
|
119
|
+
* @param debugName 调试名称(可选)
|
|
120
|
+
* @returns 响应式代理对象
|
|
121
|
+
*/
|
|
122
|
+
protected reactive<T extends object>(obj: T, debugName?: string): T {
|
|
123
|
+
const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : createReactive;
|
|
124
|
+
const name = debugName || `${this.constructor.name}.reactive`;
|
|
125
|
+
|
|
126
|
+
return this._isDebugEnabled
|
|
127
|
+
? reactiveFn(obj, () => this.scheduleRerender(), name)
|
|
128
|
+
: reactiveFn(obj, () => this.scheduleRerender());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 创建响应式状态
|
|
133
|
+
*
|
|
134
|
+
* @param key 状态标识符
|
|
135
|
+
* @param initialValue 初始值
|
|
136
|
+
* @returns [getter, setter] 元组
|
|
137
|
+
*/
|
|
138
|
+
protected useState<T>(
|
|
139
|
+
key: string,
|
|
140
|
+
initialValue: T
|
|
141
|
+
): [() => T, (value: T | ((prev: T) => T)) => void] {
|
|
142
|
+
if (!this._reactiveStates.has(key)) {
|
|
143
|
+
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
144
|
+
this._reactiveStates.set(key, {
|
|
145
|
+
getter: getter as () => unknown,
|
|
146
|
+
setter: setter as (value: unknown | ((prev: unknown) => unknown)) => void,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const state = this._reactiveStates.get(key);
|
|
151
|
+
if (!state) {
|
|
152
|
+
throw new Error(`State ${key} not found`);
|
|
153
|
+
}
|
|
154
|
+
return [state.getter as () => T, state.setter as (value: T | ((prev: T) => T)) => void];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 调度重渲染
|
|
159
|
+
* 这个方法被响应式系统调用,开发者通常不需要直接调用
|
|
160
|
+
*/
|
|
161
|
+
protected scheduleRerender(): void {
|
|
162
|
+
if (this.connected) {
|
|
163
|
+
this.rerender();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 重新渲染组件(子类需要实现)
|
|
169
|
+
*/
|
|
170
|
+
protected abstract rerender(): void;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取配置值
|
|
174
|
+
*
|
|
175
|
+
* @param key - 配置键
|
|
176
|
+
* @param defaultValue - 默认值
|
|
177
|
+
* @returns 配置值
|
|
178
|
+
*/
|
|
179
|
+
protected getConfig<T>(key: string, defaultValue?: T): T {
|
|
180
|
+
return (this.config[key] as T) ?? (defaultValue as T);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 设置配置值
|
|
185
|
+
*
|
|
186
|
+
* @param key - 配置键
|
|
187
|
+
* @param value - 配置值
|
|
188
|
+
*/
|
|
189
|
+
protected setConfig(key: string, value: unknown): void {
|
|
190
|
+
this.config[key] = value;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 获取属性值
|
|
195
|
+
*
|
|
196
|
+
* @param name - 属性名
|
|
197
|
+
* @param defaultValue - 默认值
|
|
198
|
+
* @returns 属性值
|
|
199
|
+
*/
|
|
200
|
+
protected getAttr(name: string, defaultValue = ""): string {
|
|
201
|
+
return this.getAttribute(name) || defaultValue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 设置属性值
|
|
206
|
+
*
|
|
207
|
+
* @param name - 属性名
|
|
208
|
+
* @param value - 属性值
|
|
209
|
+
*/
|
|
210
|
+
protected setAttr(name: string, value: string): void {
|
|
211
|
+
this.setAttribute(name, value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 移除属性
|
|
216
|
+
*
|
|
217
|
+
* @param name - 属性名
|
|
218
|
+
*/
|
|
219
|
+
protected removeAttr(name: string): void {
|
|
220
|
+
this.removeAttribute(name);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 检查是否有属性
|
|
225
|
+
*
|
|
226
|
+
* @param name - 属性名
|
|
227
|
+
* @returns 是否存在
|
|
228
|
+
*/
|
|
229
|
+
protected hasAttr(name: string): boolean {
|
|
230
|
+
return this.hasAttribute(name);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 清理响应式状态
|
|
235
|
+
*/
|
|
236
|
+
protected cleanupReactiveStates(): void {
|
|
237
|
+
this._reactiveStates.clear();
|
|
238
|
+
}
|
|
239
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,9 +6,8 @@ export { h, h as jsx, h as jsxs, Fragment } from "./jsx-factory";
|
|
|
6
6
|
export { StyleManager } from "./styles/style-manager";
|
|
7
7
|
export { WSXLogger, logger, createLogger } from "./utils/logger";
|
|
8
8
|
|
|
9
|
-
// Reactive exports
|
|
10
|
-
export {
|
|
11
|
-
export { ReactiveWebComponent, makeReactive, createReactiveComponent } from "./reactive-component";
|
|
9
|
+
// Reactive exports - Decorator-based API
|
|
10
|
+
export { state } from "./reactive-decorator";
|
|
12
11
|
|
|
13
12
|
// Type exports
|
|
14
13
|
export type { WebComponentConfig } from "./web-component";
|
|
@@ -16,4 +15,3 @@ export type { LightComponentConfig } from "./light-component";
|
|
|
16
15
|
export type { JSXChildren } from "./jsx-factory";
|
|
17
16
|
export type { Logger, LogLevel } from "./utils/logger";
|
|
18
17
|
export type { ReactiveCallback } from "./utils/reactive";
|
|
19
|
-
export type { ReactiveWebComponentConfig } from "./reactive-component";
|
package/src/light-component.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { h, type JSXChildren } from "./jsx-factory";
|
|
11
|
-
import {
|
|
11
|
+
import { BaseComponent, type BaseComponentConfig } from "./base-component";
|
|
12
12
|
import { createLogger } from "./utils/logger";
|
|
13
13
|
|
|
14
14
|
const logger = createLogger("LightComponent");
|
|
@@ -16,35 +16,17 @@ const logger = createLogger("LightComponent");
|
|
|
16
16
|
/**
|
|
17
17
|
* Light DOM Component 配置接口
|
|
18
18
|
*/
|
|
19
|
-
export
|
|
20
|
-
styles?: string; // CSS内容
|
|
21
|
-
styleName?: string; // 样式名称,用于缓存
|
|
22
|
-
debug?: boolean; // 是否启用响应式调试模式
|
|
23
|
-
[key: string]: unknown;
|
|
24
|
-
}
|
|
19
|
+
export type LightComponentConfig = BaseComponentConfig;
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
22
|
* Light DOM WSX Web Component 基类
|
|
28
23
|
*/
|
|
29
|
-
export abstract class LightComponent extends
|
|
30
|
-
protected config
|
|
31
|
-
protected connected: boolean = false;
|
|
32
|
-
private _isDebugEnabled: boolean = false;
|
|
33
|
-
private _reactiveStates = new Map<string, any>();
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 子类应该重写这个方法来定义观察的属性
|
|
37
|
-
* @returns 要观察的属性名数组
|
|
38
|
-
*/
|
|
39
|
-
static get observedAttributes(): string[] {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
24
|
+
export abstract class LightComponent extends BaseComponent {
|
|
25
|
+
protected config!: LightComponentConfig; // Initialized by BaseComponent constructor
|
|
42
26
|
|
|
43
27
|
constructor(config: LightComponentConfig = {}) {
|
|
44
|
-
super();
|
|
45
|
-
|
|
46
|
-
this.config = config;
|
|
47
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
28
|
+
super(config);
|
|
29
|
+
// BaseComponent already created this.config with getter for styles
|
|
48
30
|
}
|
|
49
31
|
|
|
50
32
|
/**
|
|
@@ -61,9 +43,14 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
61
43
|
this.connected = true;
|
|
62
44
|
try {
|
|
63
45
|
// 应用CSS样式到组件自身
|
|
64
|
-
|
|
46
|
+
// CRITICAL: _defineProperty for class properties executes AFTER super() but BEFORE constructor body
|
|
47
|
+
// However, in practice, _defineProperty may execute AFTER the constructor body
|
|
48
|
+
// So we need to check _autoStyles directly first, then fallback to config.styles getter
|
|
49
|
+
// The getter will dynamically check _autoStyles when accessed
|
|
50
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
51
|
+
if (stylesToApply) {
|
|
65
52
|
const styleName = this.config.styleName || this.constructor.name;
|
|
66
|
-
this.applyScopedStyles(styleName,
|
|
53
|
+
this.applyScopedStyles(styleName, stylesToApply);
|
|
67
54
|
}
|
|
68
55
|
|
|
69
56
|
// 渲染JSX内容到Light DOM
|
|
@@ -87,28 +74,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
87
74
|
this.onDisconnected?.();
|
|
88
75
|
}
|
|
89
76
|
|
|
90
|
-
/**
|
|
91
|
-
* Web Component生命周期:属性变化
|
|
92
|
-
*/
|
|
93
|
-
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
94
|
-
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 可选生命周期钩子:组件已连接
|
|
99
|
-
*/
|
|
100
|
-
protected onConnected?(): void;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 可选生命周期钩子:组件已断开
|
|
104
|
-
*/
|
|
105
|
-
protected onDisconnected?(): void;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 可选生命周期钩子:属性已更改
|
|
109
|
-
*/
|
|
110
|
-
protected onAttributeChanged?(name: string, oldValue: string, newValue: string): void;
|
|
111
|
-
|
|
112
77
|
/**
|
|
113
78
|
* 查找组件内的元素
|
|
114
79
|
*
|
|
@@ -129,53 +94,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
129
94
|
return HTMLElement.prototype.querySelectorAll.call(this, selector) as NodeListOf<T>;
|
|
130
95
|
}
|
|
131
96
|
|
|
132
|
-
/**
|
|
133
|
-
* 创建响应式对象
|
|
134
|
-
*
|
|
135
|
-
* @param obj 要变为响应式的对象
|
|
136
|
-
* @param debugName 调试名称(可选)
|
|
137
|
-
* @returns 响应式代理对象
|
|
138
|
-
*/
|
|
139
|
-
protected reactive<T extends object>(obj: T, debugName?: string): T {
|
|
140
|
-
const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
|
|
141
|
-
const name = debugName || `${this.constructor.name}.reactive`;
|
|
142
|
-
|
|
143
|
-
return this._isDebugEnabled
|
|
144
|
-
? reactiveFn(obj, () => this.scheduleRerender(), name)
|
|
145
|
-
: reactiveFn(obj, () => this.scheduleRerender());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 创建响应式状态
|
|
150
|
-
*
|
|
151
|
-
* @param key 状态标识符
|
|
152
|
-
* @param initialValue 初始值
|
|
153
|
-
* @returns [getter, setter] 元组
|
|
154
|
-
*/
|
|
155
|
-
protected useState<T>(
|
|
156
|
-
key: string,
|
|
157
|
-
initialValue: T
|
|
158
|
-
): [() => T, (value: T | ((prev: T) => T)) => void] {
|
|
159
|
-
if (!this._reactiveStates.has(key)) {
|
|
160
|
-
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
161
|
-
this._reactiveStates.set(key, { getter, setter });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const state = this._reactiveStates.get(key);
|
|
165
|
-
return [state.getter, state.setter];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 调度重渲染
|
|
170
|
-
* 这个方法被响应式系统调用,开发者通常不需要直接调用
|
|
171
|
-
*/
|
|
172
|
-
protected scheduleRerender(): void {
|
|
173
|
-
// 确保组件已连接到 DOM
|
|
174
|
-
if (this.connected) {
|
|
175
|
-
this.rerender();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
97
|
/**
|
|
180
98
|
* 重新渲染组件
|
|
181
99
|
*/
|
|
@@ -187,13 +105,36 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
187
105
|
return;
|
|
188
106
|
}
|
|
189
107
|
|
|
190
|
-
//
|
|
108
|
+
// 清空现有内容(包括样式元素)
|
|
191
109
|
this.innerHTML = "";
|
|
192
110
|
|
|
193
|
-
//
|
|
111
|
+
// 重新应用样式(必须在内容之前添加,确保样式优先)
|
|
112
|
+
if (this.config.styles) {
|
|
113
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
114
|
+
// 直接创建并添加样式元素,不检查是否存在(因为 innerHTML = "" 已经清空了)
|
|
115
|
+
const styleElement = document.createElement("style");
|
|
116
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
117
|
+
styleElement.textContent = this.config.styles;
|
|
118
|
+
// 使用 prepend 或 insertBefore 确保样式在第一个位置
|
|
119
|
+
// 由于 innerHTML = "" 后 firstChild 是 null,使用 appendChild 然后调整顺序
|
|
120
|
+
this.appendChild(styleElement);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 重新渲染JSX内容
|
|
194
124
|
try {
|
|
195
125
|
const content = this.render();
|
|
196
126
|
this.appendChild(content);
|
|
127
|
+
|
|
128
|
+
// 确保样式元素在内容之前(如果样式存在)
|
|
129
|
+
if (this.config.styles && this.children.length > 1) {
|
|
130
|
+
const styleElement = this.querySelector(
|
|
131
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
132
|
+
);
|
|
133
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
134
|
+
// 将样式元素移到第一个位置
|
|
135
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
197
138
|
} catch (error) {
|
|
198
139
|
logger.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
199
140
|
this.renderError(error);
|
|
@@ -243,34 +184,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
243
184
|
this.insertBefore(styleElement, this.firstChild);
|
|
244
185
|
}
|
|
245
186
|
|
|
246
|
-
/**
|
|
247
|
-
* 获取配置值
|
|
248
|
-
*
|
|
249
|
-
* @param key - 配置键
|
|
250
|
-
* @param defaultValue - 默认值
|
|
251
|
-
* @returns 配置值
|
|
252
|
-
*/
|
|
253
|
-
protected getConfig<T>(key: string, defaultValue?: T): T {
|
|
254
|
-
return (this.config[key] as T) ?? (defaultValue as T);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* 设置配置值
|
|
259
|
-
*
|
|
260
|
-
* @param key - 配置键
|
|
261
|
-
* @param value - 配置值
|
|
262
|
-
*/
|
|
263
|
-
protected setConfig(key: string, value: unknown): void {
|
|
264
|
-
this.config[key] = value;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* 清理响应式状态
|
|
269
|
-
*/
|
|
270
|
-
private cleanupReactiveStates(): void {
|
|
271
|
-
this._reactiveStates.clear();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
187
|
/**
|
|
275
188
|
* 清理组件样式
|
|
276
189
|
*/
|
|
@@ -281,46 +194,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
281
194
|
existingStyle.remove();
|
|
282
195
|
}
|
|
283
196
|
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* 获取属性值
|
|
287
|
-
*
|
|
288
|
-
* @param name - 属性名
|
|
289
|
-
* @param defaultValue - 默认值
|
|
290
|
-
* @returns 属性值
|
|
291
|
-
*/
|
|
292
|
-
protected getAttr(name: string, defaultValue = ""): string {
|
|
293
|
-
return this.getAttribute(name) || defaultValue;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* 设置属性值
|
|
298
|
-
*
|
|
299
|
-
* @param name - 属性名
|
|
300
|
-
* @param value - 属性值
|
|
301
|
-
*/
|
|
302
|
-
protected setAttr(name: string, value: string): void {
|
|
303
|
-
this.setAttribute(name, value);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 移除属性
|
|
308
|
-
*
|
|
309
|
-
* @param name - 属性名
|
|
310
|
-
*/
|
|
311
|
-
protected removeAttr(name: string): void {
|
|
312
|
-
this.removeAttribute(name);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* 检查是否有属性
|
|
317
|
-
*
|
|
318
|
-
* @param name - 属性名
|
|
319
|
-
* @returns 是否存在
|
|
320
|
-
*/
|
|
321
|
-
protected hasAttr(name: string): boolean {
|
|
322
|
-
return this.hasAttribute(name);
|
|
323
|
-
}
|
|
324
197
|
}
|
|
325
198
|
|
|
326
199
|
// 导出JSX助手
|