@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/package.json CHANGED
@@ -1,49 +1,49 @@
1
1
  {
2
- "name": "@wsxjs/wsx-core",
3
- "version": "0.0.6",
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"
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
- "files": [
21
- "dist",
22
- "src",
23
- "types",
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 { reactive, createState, ReactiveDebug } from "./utils/reactive";
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";
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { h, type JSXChildren } from "./jsx-factory";
11
- import { reactive, createState, reactiveWithDebug } from "./utils/reactive";
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 interface LightComponentConfig {
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 HTMLElement {
30
- protected config: LightComponentConfig;
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
- if (this.config.styles) {
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, this.config.styles);
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
- // 重新渲染JSX
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助手