@wsxjs/wsx-core 0.0.7 → 0.0.9
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/chunk-CZII6RG2.mjs +229 -0
- package/dist/index.js +665 -487
- package/dist/index.mjs +657 -481
- package/dist/jsx-runtime.js +7 -0
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +7 -0
- package/dist/jsx.mjs +1 -1
- package/package.json +1 -1
- package/src/base-component.ts +549 -0
- package/src/index.ts +2 -4
- package/src/jsx-factory.ts +15 -0
- package/src/light-component.ts +102 -186
- package/src/reactive-decorator.ts +132 -0
- package/src/utils/reactive.ts +209 -35
- package/src/web-component.ts +89 -129
- package/types/index.d.ts +2 -2
- package/src/reactive-component.ts +0 -306
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,15 +43,23 @@ 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
|
|
70
57
|
const content = this.render();
|
|
71
58
|
this.appendChild(content);
|
|
72
59
|
|
|
60
|
+
// 初始化事件监听器
|
|
61
|
+
this.initializeEventListeners();
|
|
62
|
+
|
|
73
63
|
// 调用子类的初始化钩子
|
|
74
64
|
this.onConnected?.();
|
|
75
65
|
} catch (error) {
|
|
@@ -82,33 +72,13 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
82
72
|
* Web Component生命周期:从DOM断开
|
|
83
73
|
*/
|
|
84
74
|
disconnectedCallback(): void {
|
|
75
|
+
this.connected = false;
|
|
76
|
+
this.cleanup(); // 清理资源(包括防抖定时器)
|
|
85
77
|
this.cleanupReactiveStates();
|
|
86
78
|
this.cleanupStyles();
|
|
87
79
|
this.onDisconnected?.();
|
|
88
80
|
}
|
|
89
81
|
|
|
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
82
|
/**
|
|
113
83
|
* 查找组件内的元素
|
|
114
84
|
*
|
|
@@ -129,53 +99,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
129
99
|
return HTMLElement.prototype.querySelectorAll.call(this, selector) as NodeListOf<T>;
|
|
130
100
|
}
|
|
131
101
|
|
|
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
102
|
/**
|
|
180
103
|
* 重新渲染组件
|
|
181
104
|
*/
|
|
@@ -187,36 +110,97 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
187
110
|
return;
|
|
188
111
|
}
|
|
189
112
|
|
|
190
|
-
//
|
|
191
|
-
|
|
113
|
+
// 1. 捕获焦点状态(在 DOM 替换之前)
|
|
114
|
+
const focusState = this.captureFocusState();
|
|
115
|
+
// 保存到实例变量,供 render() 使用(如果需要)
|
|
116
|
+
this._pendingFocusState = focusState;
|
|
192
117
|
|
|
193
|
-
// 重新应用样式(必须在内容之前添加,确保样式优先)
|
|
194
|
-
if (this.config.styles) {
|
|
195
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
196
|
-
// 直接创建并添加样式元素,不检查是否存在(因为 innerHTML = "" 已经清空了)
|
|
197
|
-
const styleElement = document.createElement("style");
|
|
198
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
199
|
-
styleElement.textContent = this.config.styles;
|
|
200
|
-
// 使用 prepend 或 insertBefore 确保样式在第一个位置
|
|
201
|
-
// 由于 innerHTML = "" 后 firstChild 是 null,使用 appendChild 然后调整顺序
|
|
202
|
-
this.appendChild(styleElement);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 重新渲染JSX内容
|
|
206
118
|
try {
|
|
119
|
+
// 重新渲染JSX内容
|
|
207
120
|
const content = this.render();
|
|
208
|
-
this.appendChild(content);
|
|
209
121
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
122
|
+
// 在添加到 DOM 之前恢复值,避免浏览器渲染状态值
|
|
123
|
+
// 这样可以确保值在元素添加到 DOM 之前就是正确的
|
|
124
|
+
if (focusState && focusState.key && focusState.value !== undefined) {
|
|
125
|
+
// 在 content 树中查找目标元素
|
|
126
|
+
const target = content.querySelector(
|
|
127
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
128
|
+
) as HTMLElement;
|
|
129
|
+
|
|
130
|
+
if (target) {
|
|
131
|
+
if (
|
|
132
|
+
target instanceof HTMLInputElement ||
|
|
133
|
+
target instanceof HTMLTextAreaElement
|
|
134
|
+
) {
|
|
135
|
+
target.value = focusState.value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 确保样式元素存在
|
|
141
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
142
|
+
if (stylesToApply) {
|
|
143
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
144
|
+
let styleElement = this.querySelector(
|
|
145
|
+
`style[data-wsx-light-component="${styleName}"]`
|
|
146
|
+
) as HTMLStyleElement;
|
|
147
|
+
|
|
148
|
+
if (!styleElement) {
|
|
149
|
+
// 创建样式元素
|
|
150
|
+
styleElement = document.createElement("style");
|
|
151
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
152
|
+
styleElement.textContent = stylesToApply;
|
|
217
153
|
this.insertBefore(styleElement, this.firstChild);
|
|
154
|
+
} else if (styleElement.textContent !== stylesToApply) {
|
|
155
|
+
// 更新样式内容
|
|
156
|
+
styleElement.textContent = stylesToApply;
|
|
218
157
|
}
|
|
219
158
|
}
|
|
159
|
+
|
|
160
|
+
// 使用 requestAnimationFrame 批量执行 DOM 操作,减少重绘
|
|
161
|
+
// 在同一帧中完成添加和移除,避免中间状态
|
|
162
|
+
requestAnimationFrame(() => {
|
|
163
|
+
// 先添加新内容
|
|
164
|
+
this.appendChild(content);
|
|
165
|
+
|
|
166
|
+
// 立即移除旧内容(在同一帧中,浏览器会批量处理)
|
|
167
|
+
const oldChildren = Array.from(this.children).filter((child) => {
|
|
168
|
+
// 保留新添加的内容
|
|
169
|
+
if (child === content) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
// 保留样式元素(如果存在)
|
|
173
|
+
if (
|
|
174
|
+
stylesToApply &&
|
|
175
|
+
child instanceof HTMLStyleElement &&
|
|
176
|
+
child.getAttribute("data-wsx-light-component") ===
|
|
177
|
+
(this.config.styleName || this.constructor.name)
|
|
178
|
+
) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
});
|
|
183
|
+
oldChildren.forEach((child) => child.remove());
|
|
184
|
+
|
|
185
|
+
// 确保样式元素在第一个位置
|
|
186
|
+
if (stylesToApply && this.children.length > 1) {
|
|
187
|
+
const styleElement = this.querySelector(
|
|
188
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
189
|
+
);
|
|
190
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
191
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 恢复焦点状态(在 DOM 替换之后)
|
|
196
|
+
// 值已经在添加到 DOM 之前恢复了,这里只需要恢复焦点和光标位置
|
|
197
|
+
// 使用另一个 requestAnimationFrame 确保 DOM 已完全更新
|
|
198
|
+
requestAnimationFrame(() => {
|
|
199
|
+
this.restoreFocusState(focusState);
|
|
200
|
+
// 清除待处理的焦点状态
|
|
201
|
+
this._pendingFocusState = null;
|
|
202
|
+
});
|
|
203
|
+
});
|
|
220
204
|
} catch (error) {
|
|
221
205
|
logger.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
222
206
|
this.renderError(error);
|
|
@@ -266,34 +250,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
266
250
|
this.insertBefore(styleElement, this.firstChild);
|
|
267
251
|
}
|
|
268
252
|
|
|
269
|
-
/**
|
|
270
|
-
* 获取配置值
|
|
271
|
-
*
|
|
272
|
-
* @param key - 配置键
|
|
273
|
-
* @param defaultValue - 默认值
|
|
274
|
-
* @returns 配置值
|
|
275
|
-
*/
|
|
276
|
-
protected getConfig<T>(key: string, defaultValue?: T): T {
|
|
277
|
-
return (this.config[key] as T) ?? (defaultValue as T);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* 设置配置值
|
|
282
|
-
*
|
|
283
|
-
* @param key - 配置键
|
|
284
|
-
* @param value - 配置值
|
|
285
|
-
*/
|
|
286
|
-
protected setConfig(key: string, value: unknown): void {
|
|
287
|
-
this.config[key] = value;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 清理响应式状态
|
|
292
|
-
*/
|
|
293
|
-
private cleanupReactiveStates(): void {
|
|
294
|
-
this._reactiveStates.clear();
|
|
295
|
-
}
|
|
296
|
-
|
|
297
253
|
/**
|
|
298
254
|
* 清理组件样式
|
|
299
255
|
*/
|
|
@@ -304,46 +260,6 @@ export abstract class LightComponent extends HTMLElement {
|
|
|
304
260
|
existingStyle.remove();
|
|
305
261
|
}
|
|
306
262
|
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* 获取属性值
|
|
310
|
-
*
|
|
311
|
-
* @param name - 属性名
|
|
312
|
-
* @param defaultValue - 默认值
|
|
313
|
-
* @returns 属性值
|
|
314
|
-
*/
|
|
315
|
-
protected getAttr(name: string, defaultValue = ""): string {
|
|
316
|
-
return this.getAttribute(name) || defaultValue;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* 设置属性值
|
|
321
|
-
*
|
|
322
|
-
* @param name - 属性名
|
|
323
|
-
* @param value - 属性值
|
|
324
|
-
*/
|
|
325
|
-
protected setAttr(name: string, value: string): void {
|
|
326
|
-
this.setAttribute(name, value);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* 移除属性
|
|
331
|
-
*
|
|
332
|
-
* @param name - 属性名
|
|
333
|
-
*/
|
|
334
|
-
protected removeAttr(name: string): void {
|
|
335
|
-
this.removeAttribute(name);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* 检查是否有属性
|
|
340
|
-
*
|
|
341
|
-
* @param name - 属性名
|
|
342
|
-
* @returns 是否存在
|
|
343
|
-
*/
|
|
344
|
-
protected hasAttr(name: string): boolean {
|
|
345
|
-
return this.hasAttribute(name);
|
|
346
|
-
}
|
|
347
263
|
}
|
|
348
264
|
|
|
349
265
|
// 导出JSX助手
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive Decorators for WSX Components
|
|
3
|
+
*
|
|
4
|
+
* Provides @state property decorator to mark properties as reactive state.
|
|
5
|
+
* WebComponent and LightComponent already have reactive() and useState() methods.
|
|
6
|
+
* Babel plugin handles @state initialization at compile time.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* class Counter extends WebComponent {
|
|
11
|
+
* @state private count = 0;
|
|
12
|
+
* @state private user = { name: "John" };
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* State property decorator
|
|
19
|
+
*
|
|
20
|
+
* Marks a property as reactive state. Babel plugin processes this decorator at compile time
|
|
21
|
+
* and generates initialization code in the constructor.
|
|
22
|
+
*
|
|
23
|
+
* Automatically uses reactive() for objects/arrays and useState() for primitives.
|
|
24
|
+
*
|
|
25
|
+
* @param target - The class prototype
|
|
26
|
+
* @param propertyKey - The property name
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* class MyComponent extends WebComponent {
|
|
31
|
+
* @state private count = 0; // Auto-initialized by Babel plugin
|
|
32
|
+
* @state private user = { name: "John" }; // Auto-initialized by Babel plugin
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function state(target: unknown, propertyKey: string | symbol | unknown): void {
|
|
37
|
+
/**
|
|
38
|
+
* @state decorator is a compile-time marker for Babel plugin.
|
|
39
|
+
* Babel plugin will:
|
|
40
|
+
* 1. Detect @state decorator on properties
|
|
41
|
+
* 2. Extract initial value from AST
|
|
42
|
+
* 3. Remove @state decorator
|
|
43
|
+
* 4. Generate initialization code in constructor (this.state = this.reactive(...) or useState)
|
|
44
|
+
*
|
|
45
|
+
* This runtime function only performs basic validation.
|
|
46
|
+
* If Babel plugin is not configured, this will throw an error.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
// Normalize propertyKey
|
|
50
|
+
let normalizedPropertyKey: string | symbol;
|
|
51
|
+
if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
|
|
52
|
+
normalizedPropertyKey = propertyKey;
|
|
53
|
+
} else {
|
|
54
|
+
const propertyKeyStr = String(propertyKey);
|
|
55
|
+
if (propertyKeyStr === "[object Object]") {
|
|
56
|
+
// Invalid propertyKey - Babel plugin was not configured
|
|
57
|
+
throw new Error(
|
|
58
|
+
`@state decorator: Invalid propertyKey detected. ` +
|
|
59
|
+
`\n\n` +
|
|
60
|
+
`The @state decorator MUST be processed by Babel plugin at compile time. ` +
|
|
61
|
+
`It appears the Babel plugin is not configured in your build setup.` +
|
|
62
|
+
`\n\n` +
|
|
63
|
+
`To fix this, please:` +
|
|
64
|
+
`\n1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin` +
|
|
65
|
+
`\n2. Configure it in vite.config.ts:` +
|
|
66
|
+
`\n import { wsx } from '@wsxjs/wsx-vite-plugin';` +
|
|
67
|
+
`\n export default defineConfig({ plugins: [wsx()] });` +
|
|
68
|
+
`\n3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):` +
|
|
69
|
+
`\n npm install --save-dev @wsxjs/wsx-tsconfig` +
|
|
70
|
+
`\n Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }` +
|
|
71
|
+
`\n Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }` +
|
|
72
|
+
`\n\n` +
|
|
73
|
+
`See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
normalizedPropertyKey = propertyKeyStr;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Basic validation: ensure target is valid
|
|
80
|
+
if (target == null) {
|
|
81
|
+
const propertyKeyStr =
|
|
82
|
+
typeof normalizedPropertyKey === "string"
|
|
83
|
+
? normalizedPropertyKey
|
|
84
|
+
: normalizedPropertyKey.toString();
|
|
85
|
+
throw new Error(
|
|
86
|
+
`@state decorator: Cannot access property "${propertyKeyStr}". ` +
|
|
87
|
+
`Target is ${target === null ? "null" : "undefined"}.` +
|
|
88
|
+
`\n\n` +
|
|
89
|
+
`The @state decorator MUST be processed by Babel plugin at compile time. ` +
|
|
90
|
+
`It appears the Babel plugin is not configured in your build setup.` +
|
|
91
|
+
`\n\n` +
|
|
92
|
+
`To fix this, please:` +
|
|
93
|
+
`\n1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin` +
|
|
94
|
+
`\n2. Configure it in vite.config.ts:` +
|
|
95
|
+
`\n import { wsx } from '@wsxjs/wsx-vite-plugin';` +
|
|
96
|
+
`\n export default defineConfig({ plugins: [wsx()] });` +
|
|
97
|
+
`\n3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):` +
|
|
98
|
+
`\n npm install --save-dev @wsxjs/wsx-tsconfig` +
|
|
99
|
+
`\n Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }` +
|
|
100
|
+
`\n Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }` +
|
|
101
|
+
`\n\n` +
|
|
102
|
+
`See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof target !== "object") {
|
|
107
|
+
const propertyKeyStr =
|
|
108
|
+
typeof normalizedPropertyKey === "string"
|
|
109
|
+
? normalizedPropertyKey
|
|
110
|
+
: normalizedPropertyKey.toString();
|
|
111
|
+
throw new Error(
|
|
112
|
+
`@state decorator: Cannot be used on "${propertyKeyStr}". ` +
|
|
113
|
+
`@state is for properties only, not methods.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate that property has an initial value
|
|
118
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
|
|
119
|
+
if (descriptor?.get) {
|
|
120
|
+
const propertyKeyStr =
|
|
121
|
+
typeof normalizedPropertyKey === "string"
|
|
122
|
+
? normalizedPropertyKey
|
|
123
|
+
: normalizedPropertyKey.toString();
|
|
124
|
+
throw new Error(
|
|
125
|
+
`@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Note: We don't store metadata or remove the property here.
|
|
130
|
+
// Babel plugin handles everything at compile time.
|
|
131
|
+
// If this function is called at runtime, it means Babel plugin didn't process the decorator.
|
|
132
|
+
}
|