@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/index.js CHANGED
@@ -22,22 +22,17 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Fragment: () => Fragment,
24
24
  LightComponent: () => LightComponent,
25
- ReactiveDebug: () => ReactiveDebug,
26
- ReactiveWebComponent: () => ReactiveWebComponent,
27
25
  StyleManager: () => StyleManager,
28
26
  WSXLogger: () => WSXLogger,
29
27
  WebComponent: () => WebComponent,
30
28
  autoRegister: () => autoRegister,
31
29
  createLogger: () => createLogger,
32
- createReactiveComponent: () => createReactiveComponent,
33
- createState: () => createState,
34
30
  h: () => h,
35
31
  jsx: () => h,
36
32
  jsxs: () => h,
37
33
  logger: () => logger,
38
- makeReactive: () => makeReactive,
39
- reactive: () => reactive,
40
- registerComponent: () => registerComponent
34
+ registerComponent: () => registerComponent,
35
+ state: () => state
41
36
  });
42
37
  module.exports = __toCommonJS(index_exports);
43
38
 
@@ -182,6 +177,13 @@ function h(tag, props = {}, ...children) {
182
177
  if (value) {
183
178
  element.setAttribute(key, "");
184
179
  }
180
+ } else if (key === "value") {
181
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
182
+ element.value = String(value);
183
+ } else {
184
+ const attributeName = isSVG ? getSVGAttributeName(key) : key;
185
+ element.setAttribute(attributeName, String(value));
186
+ }
185
187
  } else {
186
188
  const attributeName = isSVG ? getSVGAttributeName(key) : key;
187
189
  element.setAttribute(attributeName, String(value));
@@ -274,172 +276,6 @@ var StyleManager = class {
274
276
  };
275
277
  StyleManager.styleSheets = /* @__PURE__ */ new Map();
276
278
 
277
- // src/web-component.ts
278
- var WebComponent = class extends HTMLElement {
279
- constructor(config = {}) {
280
- super();
281
- this.connected = false;
282
- this.config = config;
283
- this.attachShadow({ mode: "open" });
284
- if (config.styles) {
285
- const styleName = config.styleName || this.constructor.name;
286
- StyleManager.applyStyles(this.shadowRoot, styleName, config.styles);
287
- }
288
- }
289
- /**
290
- * 子类应该重写这个方法来定义观察的属性
291
- * @returns 要观察的属性名数组
292
- */
293
- static get observedAttributes() {
294
- return [];
295
- }
296
- /**
297
- * Web Component生命周期:连接到DOM
298
- */
299
- connectedCallback() {
300
- this.connected = true;
301
- try {
302
- const content = this.render();
303
- this.shadowRoot.appendChild(content);
304
- this.onConnected?.();
305
- } catch (error) {
306
- console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
307
- this.renderError(error);
308
- }
309
- }
310
- /**
311
- * Web Component生命周期:从DOM断开
312
- */
313
- disconnectedCallback() {
314
- this.onDisconnected?.();
315
- }
316
- /**
317
- * Web Component生命周期:属性变化
318
- */
319
- attributeChangedCallback(name, oldValue, newValue) {
320
- this.onAttributeChanged?.(name, oldValue, newValue);
321
- }
322
- /**
323
- * 查找Shadow DOM内的元素
324
- *
325
- * @param selector - CSS选择器
326
- * @returns 元素或null
327
- */
328
- querySelector(selector) {
329
- return this.shadowRoot.querySelector(selector);
330
- }
331
- /**
332
- * 查找Shadow DOM内的所有匹配元素
333
- *
334
- * @param selector - CSS选择器
335
- * @returns 元素列表
336
- */
337
- querySelectorAll(selector) {
338
- return this.shadowRoot.querySelectorAll(selector);
339
- }
340
- /**
341
- * 重新渲染组件
342
- */
343
- rerender() {
344
- if (!this.connected) {
345
- console.warn(
346
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
347
- );
348
- return;
349
- }
350
- const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
351
- this.shadowRoot.innerHTML = "";
352
- if (this.shadowRoot.adoptedStyleSheets) {
353
- this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
354
- }
355
- if (adoptedStyleSheets.length === 0 && this.config.styles) {
356
- const styleName = this.config.styleName || this.constructor.name;
357
- StyleManager.applyStyles(this.shadowRoot, styleName, this.config.styles);
358
- }
359
- try {
360
- const content = this.render();
361
- this.shadowRoot.appendChild(content);
362
- } catch (error) {
363
- console.error(`[${this.constructor.name}] Error in rerender:`, error);
364
- this.renderError(error);
365
- }
366
- }
367
- /**
368
- * 渲染错误信息
369
- *
370
- * @param error - 错误对象
371
- */
372
- renderError(error) {
373
- this.shadowRoot.innerHTML = "";
374
- const errorElement = h(
375
- "div",
376
- {
377
- style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
378
- },
379
- [
380
- h("strong", {}, `[${this.constructor.name}] Component Error:`),
381
- h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
382
- ]
383
- );
384
- this.shadowRoot.appendChild(errorElement);
385
- }
386
- /**
387
- * 获取配置值
388
- *
389
- * @param key - 配置键
390
- * @param defaultValue - 默认值
391
- * @returns 配置值
392
- */
393
- getConfig(key, defaultValue) {
394
- return this.config[key] ?? defaultValue;
395
- }
396
- /**
397
- * 设置配置值
398
- *
399
- * @param key - 配置键
400
- * @param value - 配置值
401
- */
402
- setConfig(key, value) {
403
- this.config[key] = value;
404
- }
405
- /**
406
- * 获取属性值
407
- *
408
- * @param name - 属性名
409
- * @param defaultValue - 默认值
410
- * @returns 属性值
411
- */
412
- getAttr(name, defaultValue = "") {
413
- return this.getAttribute(name) || defaultValue;
414
- }
415
- /**
416
- * 设置属性值
417
- *
418
- * @param name - 属性名
419
- * @param value - 属性值
420
- */
421
- setAttr(name, value) {
422
- this.setAttribute(name, value);
423
- }
424
- /**
425
- * 移除属性
426
- *
427
- * @param name - 属性名
428
- */
429
- removeAttr(name) {
430
- this.removeAttribute(name);
431
- }
432
- /**
433
- * 检查是否有属性
434
- *
435
- * @param name - 属性名
436
- * @returns 是否存在
437
- */
438
- hasAttr(name) {
439
- return this.hasAttribute(name);
440
- }
441
- };
442
-
443
279
  // src/utils/logger.ts
444
280
  var WSXLogger = class {
445
281
  constructor(prefix = "[WSX]", enabled = true, level = "info") {
@@ -510,35 +346,107 @@ var UpdateScheduler = class {
510
346
  try {
511
347
  callback();
512
348
  } catch (error) {
513
- console.error("[WSX Reactive] Error in callback:", error);
349
+ logger2.error("[WSX Reactive] Error in callback:", error);
514
350
  }
515
351
  });
516
352
  }
517
353
  };
518
354
  var scheduler = new UpdateScheduler();
355
+ var proxyCache = /* @__PURE__ */ new WeakMap();
356
+ var originalCache = /* @__PURE__ */ new WeakMap();
357
+ var unwrappingSet = /* @__PURE__ */ new WeakSet();
358
+ function unwrapProxy(value) {
359
+ if (value == null || typeof value !== "object") {
360
+ return value;
361
+ }
362
+ let original = value;
363
+ if (originalCache.has(value)) {
364
+ original = originalCache.get(value);
365
+ }
366
+ if (unwrappingSet.has(original)) {
367
+ return null;
368
+ }
369
+ unwrappingSet.add(original);
370
+ try {
371
+ if (Array.isArray(original)) {
372
+ return original.map((item) => unwrapProxy(item));
373
+ }
374
+ const result = {};
375
+ for (const key in original) {
376
+ if (Object.prototype.hasOwnProperty.call(original, key)) {
377
+ const propValue = original[key];
378
+ if (propValue != null && typeof propValue === "object" && originalCache.has(propValue)) {
379
+ result[key] = unwrapProxy(originalCache.get(propValue));
380
+ } else {
381
+ result[key] = unwrapProxy(propValue);
382
+ }
383
+ }
384
+ }
385
+ return result;
386
+ } finally {
387
+ unwrappingSet.delete(original);
388
+ }
389
+ }
390
+ var ARRAY_MUTATION_METHODS = [
391
+ "push",
392
+ "pop",
393
+ "shift",
394
+ "unshift",
395
+ "splice",
396
+ "sort",
397
+ "reverse"
398
+ ];
519
399
  function reactive(obj, onChange) {
520
- return new Proxy(obj, {
400
+ if (proxyCache.has(obj)) {
401
+ return proxyCache.get(obj);
402
+ }
403
+ const isArray = Array.isArray(obj);
404
+ const proxy = new Proxy(obj, {
521
405
  set(target, key, value) {
522
406
  const oldValue = target[key];
523
- if (oldValue !== value) {
524
- target[key] = value;
407
+ const oldOriginal = originalCache.get(oldValue) || oldValue;
408
+ const newOriginal = value != null && typeof value === "object" ? originalCache.get(value) || value : value;
409
+ if (oldOriginal !== newOriginal) {
410
+ if (value != null && typeof value === "object") {
411
+ const reactiveValue = reactive(value, onChange);
412
+ target[key] = reactiveValue;
413
+ } else {
414
+ target[key] = value;
415
+ }
525
416
  scheduler.schedule(onChange);
526
417
  }
527
418
  return true;
528
419
  },
529
420
  get(target, key) {
530
- return target[key];
421
+ if (key === "toJSON") {
422
+ return function() {
423
+ return unwrapProxy(obj);
424
+ };
425
+ }
426
+ const value = target[key];
427
+ if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
428
+ return function(...args) {
429
+ const arrayMethod = Array.prototype[key];
430
+ const result = arrayMethod.apply(target, args);
431
+ scheduler.schedule(onChange);
432
+ return result;
433
+ };
434
+ }
435
+ if (value != null && typeof value === "object") {
436
+ if (proxyCache.has(value)) {
437
+ return proxyCache.get(value);
438
+ }
439
+ return reactive(value, onChange);
440
+ }
441
+ return value;
531
442
  },
532
443
  has(target, key) {
533
444
  return key in target;
534
- },
535
- ownKeys(target) {
536
- return Reflect.ownKeys(target);
537
- },
538
- getOwnPropertyDescriptor(target, key) {
539
- return Reflect.getOwnPropertyDescriptor(target, key);
540
445
  }
541
446
  });
447
+ proxyCache.set(obj, proxy);
448
+ originalCache.set(proxy, obj);
449
+ return proxy;
542
450
  }
543
451
  function createState(initialValue, onChange) {
544
452
  let currentValue = initialValue;
@@ -586,6 +494,7 @@ var ReactiveDebug = {
586
494
  };
587
495
  function reactiveWithDebug(obj, onChange, debugName) {
588
496
  const name = debugName || obj.constructor.name || "Unknown";
497
+ const isArray = Array.isArray(obj);
589
498
  return new Proxy(obj, {
590
499
  set(target, key, value) {
591
500
  const oldValue = target[key];
@@ -601,21 +510,89 @@ function reactiveWithDebug(obj, onChange, debugName) {
601
510
  return true;
602
511
  },
603
512
  get(target, key) {
604
- return target[key];
513
+ if (key === "toJSON") {
514
+ return function() {
515
+ return unwrapProxy(obj);
516
+ };
517
+ }
518
+ const value = target[key];
519
+ if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
520
+ return function(...args) {
521
+ ReactiveDebug.log(`Array mutation in ${name}:`, {
522
+ method: key,
523
+ args
524
+ });
525
+ const arrayMethod = Array.prototype[key];
526
+ const result = arrayMethod.apply(target, args);
527
+ scheduler.schedule(onChange);
528
+ return result;
529
+ };
530
+ }
531
+ if (value != null && typeof value === "object") {
532
+ return reactiveWithDebug(value, onChange, `${name}.${String(key)}`);
533
+ }
534
+ return value;
605
535
  }
606
536
  });
607
537
  }
608
538
 
609
- // src/light-component.ts
610
- var logger3 = createLogger("LightComponent");
611
- var LightComponent = class extends HTMLElement {
539
+ // src/base-component.ts
540
+ var BaseComponent = class extends HTMLElement {
612
541
  constructor(config = {}) {
613
542
  super();
614
543
  this.connected = false;
615
544
  this._isDebugEnabled = false;
616
545
  this._reactiveStates = /* @__PURE__ */ new Map();
617
- this.config = config;
546
+ /**
547
+ * 当前捕获的焦点状态(用于在 render 时使用捕获的值)
548
+ * @internal - 由 rerender() 方法管理
549
+ */
550
+ this._pendingFocusState = null;
551
+ /**
552
+ * 防抖定时器,用于延迟重渲染(当用户正在输入时)
553
+ * @internal
554
+ */
555
+ this._rerenderDebounceTimer = null;
556
+ /**
557
+ * 待处理的重渲染标志(当用户正在输入时,标记需要重渲染但延迟执行)
558
+ * @internal
559
+ */
560
+ this._pendingRerender = false;
561
+ /**
562
+ * 处理 blur 事件,在用户停止输入时执行待处理的重渲染
563
+ * @internal
564
+ */
565
+ this.handleGlobalBlur = (event) => {
566
+ const root = this.getActiveRoot();
567
+ const target = event.target;
568
+ if (target && root.contains(target)) {
569
+ if (this._pendingRerender && this.connected) {
570
+ if (this._rerenderDebounceTimer !== null) {
571
+ clearTimeout(this._rerenderDebounceTimer);
572
+ this._rerenderDebounceTimer = null;
573
+ }
574
+ requestAnimationFrame(() => {
575
+ if (this._pendingRerender && this.connected) {
576
+ this._pendingRerender = false;
577
+ this.rerender();
578
+ }
579
+ });
580
+ }
581
+ }
582
+ };
618
583
  this._isDebugEnabled = config.debug ?? false;
584
+ const host = this;
585
+ const originalStyles = config.styles;
586
+ this.config = {
587
+ ...config,
588
+ get styles() {
589
+ const result = originalStyles || host._autoStyles || "";
590
+ return result;
591
+ },
592
+ set styles(value) {
593
+ config.styles = value;
594
+ }
595
+ };
619
596
  }
620
597
  /**
621
598
  * 子类应该重写这个方法来定义观察的属性
@@ -624,56 +601,12 @@ var LightComponent = class extends HTMLElement {
624
601
  static get observedAttributes() {
625
602
  return [];
626
603
  }
627
- /**
628
- * Web Component生命周期:连接到DOM
629
- */
630
- connectedCallback() {
631
- this.connected = true;
632
- try {
633
- if (this.config.styles) {
634
- const styleName = this.config.styleName || this.constructor.name;
635
- this.applyScopedStyles(styleName, this.config.styles);
636
- }
637
- const content = this.render();
638
- this.appendChild(content);
639
- this.onConnected?.();
640
- } catch (error) {
641
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
642
- this.renderError(error);
643
- }
644
- }
645
- /**
646
- * Web Component生命周期:从DOM断开
647
- */
648
- disconnectedCallback() {
649
- this.cleanupReactiveStates();
650
- this.cleanupStyles();
651
- this.onDisconnected?.();
652
- }
653
604
  /**
654
605
  * Web Component生命周期:属性变化
655
606
  */
656
607
  attributeChangedCallback(name, oldValue, newValue) {
657
608
  this.onAttributeChanged?.(name, oldValue, newValue);
658
609
  }
659
- /**
660
- * 查找组件内的元素
661
- *
662
- * @param selector - CSS选择器
663
- * @returns 元素或null
664
- */
665
- querySelector(selector) {
666
- return HTMLElement.prototype.querySelector.call(this, selector);
667
- }
668
- /**
669
- * 查找组件内的所有匹配元素
670
- *
671
- * @param selector - CSS选择器
672
- * @returns 元素列表
673
- */
674
- querySelectorAll(selector) {
675
- return HTMLElement.prototype.querySelectorAll.call(this, selector);
676
- }
677
610
  /**
678
611
  * 创建响应式对象
679
612
  *
@@ -696,86 +629,73 @@ var LightComponent = class extends HTMLElement {
696
629
  useState(key, initialValue) {
697
630
  if (!this._reactiveStates.has(key)) {
698
631
  const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
699
- this._reactiveStates.set(key, { getter, setter });
632
+ this._reactiveStates.set(key, {
633
+ getter,
634
+ setter
635
+ });
636
+ }
637
+ const state2 = this._reactiveStates.get(key);
638
+ if (!state2) {
639
+ throw new Error(`State ${key} not found`);
700
640
  }
701
- const state = this._reactiveStates.get(key);
702
- return [state.getter, state.setter];
641
+ return [state2.getter, state2.setter];
703
642
  }
704
643
  /**
705
644
  * 调度重渲染
706
645
  * 这个方法被响应式系统调用,开发者通常不需要直接调用
646
+ * 使用 queueMicrotask 进行异步调度,与 reactive() 系统保持一致
707
647
  */
708
648
  scheduleRerender() {
709
- if (this.connected) {
710
- this.rerender();
711
- }
712
- }
713
- /**
714
- * 重新渲染组件
715
- */
716
- rerender() {
717
649
  if (!this.connected) {
718
- logger3.warn(
719
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
720
- );
650
+ if (this._rerenderDebounceTimer !== null) {
651
+ clearTimeout(this._rerenderDebounceTimer);
652
+ this._rerenderDebounceTimer = null;
653
+ }
721
654
  return;
722
655
  }
723
- this.innerHTML = "";
724
- if (this.config.styles) {
725
- const styleName = this.config.styleName || this.constructor.name;
726
- const styleElement = document.createElement("style");
727
- styleElement.setAttribute("data-wsx-light-component", styleName);
728
- styleElement.textContent = this.config.styles;
729
- this.appendChild(styleElement);
656
+ const root = this.getActiveRoot();
657
+ let hasActiveElement = false;
658
+ if (root instanceof ShadowRoot) {
659
+ hasActiveElement = root.activeElement !== null;
660
+ } else {
661
+ const docActiveElement = document.activeElement;
662
+ hasActiveElement = docActiveElement !== null && root.contains(docActiveElement);
730
663
  }
731
- try {
732
- const content = this.render();
733
- this.appendChild(content);
734
- if (this.config.styles && this.children.length > 1) {
735
- const styleElement = this.querySelector(
736
- `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
737
- );
738
- if (styleElement && styleElement !== this.firstChild) {
739
- this.insertBefore(styleElement, this.firstChild);
740
- }
664
+ if (hasActiveElement) {
665
+ this._pendingRerender = true;
666
+ if (this._rerenderDebounceTimer !== null) {
667
+ clearTimeout(this._rerenderDebounceTimer);
668
+ this._rerenderDebounceTimer = null;
741
669
  }
742
- } catch (error) {
743
- logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
744
- this.renderError(error);
670
+ return;
745
671
  }
672
+ if (this._pendingRerender) {
673
+ this._pendingRerender = false;
674
+ }
675
+ queueMicrotask(() => {
676
+ if (this.connected) {
677
+ this.rerender();
678
+ }
679
+ });
746
680
  }
747
681
  /**
748
- * 渲染错误信息
749
- *
750
- * @param error - 错误对象
682
+ * 清理资源(在组件断开连接时调用)
683
+ * @internal
751
684
  */
752
- renderError(error) {
753
- this.innerHTML = "";
754
- const errorElement = h(
755
- "div",
756
- {
757
- style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
758
- },
759
- [
760
- h("strong", {}, `[${this.constructor.name}] Component Error:`),
761
- h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
762
- ]
763
- );
764
- this.appendChild(errorElement);
685
+ cleanup() {
686
+ if (this._rerenderDebounceTimer !== null) {
687
+ clearTimeout(this._rerenderDebounceTimer);
688
+ this._rerenderDebounceTimer = null;
689
+ }
690
+ document.removeEventListener("blur", this.handleGlobalBlur, true);
691
+ this._pendingRerender = false;
765
692
  }
766
693
  /**
767
- * 为Light DOM组件应用样式
768
- * 直接将样式注入到组件自身,避免全局污染
694
+ * 初始化事件监听器(在组件连接时调用)
695
+ * @internal
769
696
  */
770
- applyScopedStyles(styleName, cssText) {
771
- const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
772
- if (existingStyle) {
773
- return;
774
- }
775
- const styleElement = document.createElement("style");
776
- styleElement.setAttribute("data-wsx-light-component", styleName);
777
- styleElement.textContent = cssText;
778
- this.insertBefore(styleElement, this.firstChild);
697
+ initializeEventListeners() {
698
+ document.addEventListener("blur", this.handleGlobalBlur, true);
779
699
  }
780
700
  /**
781
701
  * 获取配置值
@@ -796,22 +716,6 @@ var LightComponent = class extends HTMLElement {
796
716
  setConfig(key, value) {
797
717
  this.config[key] = value;
798
718
  }
799
- /**
800
- * 清理响应式状态
801
- */
802
- cleanupReactiveStates() {
803
- this._reactiveStates.clear();
804
- }
805
- /**
806
- * 清理组件样式
807
- */
808
- cleanupStyles() {
809
- const styleName = this.config.styleName || this.constructor.name;
810
- const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
811
- if (existingStyle) {
812
- existingStyle.remove();
813
- }
814
- }
815
719
  /**
816
720
  * 获取属性值
817
721
  *
@@ -848,254 +752,528 @@ var LightComponent = class extends HTMLElement {
848
752
  hasAttr(name) {
849
753
  return this.hasAttribute(name);
850
754
  }
851
- };
852
-
853
- // src/auto-register.ts
854
- function autoRegister(options = {}) {
855
- return function(constructor) {
856
- const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
857
- if (!customElements.get(tagName)) {
858
- customElements.define(tagName, constructor);
859
- }
860
- return constructor;
861
- };
862
- }
863
- function registerComponent(constructor, options = {}) {
864
- const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
865
- if (!customElements.get(tagName)) {
866
- customElements.define(tagName, constructor);
867
- }
868
- }
869
- function deriveTagName(className, prefix) {
870
- let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
871
- if (!kebabCase.includes("-")) {
872
- kebabCase = `${kebabCase}-component`;
873
- }
874
- return prefix ? `${prefix}${kebabCase}` : kebabCase;
875
- }
876
-
877
- // src/reactive-component.ts
878
- var ReactiveWebComponent = class extends WebComponent {
879
- constructor(config = {}) {
880
- super(config);
881
- this._isDebugEnabled = false;
882
- this._preserveFocus = true;
883
- this._reactiveStates = /* @__PURE__ */ new Map();
884
- this._isDebugEnabled = config.debug ?? false;
885
- this._preserveFocus = config.preserveFocus ?? true;
886
- }
887
- /**
888
- * 创建响应式对象
889
- *
890
- * @param obj 要变为响应式的对象
891
- * @param debugName 调试名称(可选)
892
- * @returns 响应式代理对象
893
- */
894
- reactive(obj, debugName) {
895
- const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
896
- const name = debugName || `${this.constructor.name}.reactive`;
897
- return this._isDebugEnabled ? reactiveFn(obj, () => this.scheduleRerender(), name) : reactiveFn(obj, () => this.scheduleRerender());
898
- }
899
755
  /**
900
- * 创建响应式状态
901
- *
902
- * @param key 状态标识符
903
- * @param initialValue 初始值
904
- * @returns [getter, setter] 元组
756
+ * 清理响应式状态
905
757
  */
906
- useState(key, initialValue) {
907
- if (!this._reactiveStates.has(key)) {
908
- const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
909
- this._reactiveStates.set(key, { getter, setter });
910
- }
911
- const state = this._reactiveStates.get(key);
912
- return [state.getter, state.setter];
758
+ cleanupReactiveStates() {
759
+ this._reactiveStates.clear();
913
760
  }
914
761
  /**
915
- * 调度重渲染
916
- * 这个方法被响应式系统调用,开发者通常不需要直接调用
762
+ * 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
763
+ * @returns 活动的 DOM 根元素
917
764
  */
918
- scheduleRerender() {
919
- if (this.connected) {
920
- this.rerender();
765
+ getActiveRoot() {
766
+ if ("shadowRoot" in this && this.shadowRoot) {
767
+ return this.shadowRoot;
921
768
  }
769
+ return this;
922
770
  }
923
771
  /**
924
- * 重写 rerender 方法以支持焦点保持
772
+ * 捕获当前焦点状态(在重渲染之前调用)
773
+ * @returns 焦点状态,如果没有焦点元素则返回 null
925
774
  */
926
- rerender() {
927
- if (!this.connected) {
928
- return;
929
- }
930
- let focusData = null;
931
- if (this._preserveFocus) {
932
- const activeElement = this.shadowRoot.activeElement;
933
- focusData = this.saveFocusState(activeElement);
775
+ captureFocusState() {
776
+ const root = this.getActiveRoot();
777
+ let activeElement = null;
778
+ if (root instanceof ShadowRoot) {
779
+ activeElement = root.activeElement;
780
+ } else {
781
+ const docActiveElement = document.activeElement;
782
+ if (docActiveElement && root.contains(docActiveElement)) {
783
+ activeElement = docActiveElement;
784
+ }
934
785
  }
935
- super.rerender();
936
- if (this._preserveFocus && focusData) {
937
- this.restoreFocusState(focusData);
786
+ if (!activeElement || !(activeElement instanceof HTMLElement)) {
787
+ return null;
938
788
  }
939
- }
940
- /**
941
- * 保存焦点状态
942
- */
943
- saveFocusState(activeElement) {
944
- if (!activeElement) {
789
+ const key = activeElement.getAttribute("data-wsx-key");
790
+ if (!key) {
945
791
  return null;
946
792
  }
947
- const focusData = {
948
- tagName: activeElement.tagName.toLowerCase(),
949
- className: activeElement.className
793
+ const tagName = activeElement.tagName.toLowerCase();
794
+ const state2 = {
795
+ key,
796
+ elementType: tagName
950
797
  };
951
- if (activeElement.hasAttribute("contenteditable")) {
798
+ if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
799
+ state2.value = activeElement.value;
800
+ state2.selectionStart = activeElement.selectionStart ?? void 0;
801
+ state2.selectionEnd = activeElement.selectionEnd ?? void 0;
802
+ if (activeElement instanceof HTMLTextAreaElement) {
803
+ state2.scrollTop = activeElement.scrollTop;
804
+ }
805
+ } else if (activeElement instanceof HTMLSelectElement) {
806
+ state2.elementType = "select";
807
+ state2.selectedIndex = activeElement.selectedIndex;
808
+ } else if (activeElement.hasAttribute("contenteditable")) {
809
+ state2.elementType = "contenteditable";
952
810
  const selection = window.getSelection();
953
811
  if (selection && selection.rangeCount > 0) {
954
812
  const range = selection.getRangeAt(0);
955
- focusData.selectionStart = range.startOffset;
956
- focusData.selectionEnd = range.endOffset;
957
- }
958
- }
959
- if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
960
- focusData.value = activeElement.value;
961
- if ("selectionStart" in activeElement) {
962
- focusData.selectionStart = activeElement.selectionStart;
963
- focusData.selectionEnd = activeElement.selectionEnd;
813
+ state2.selectionStart = range.startOffset;
814
+ state2.selectionEnd = range.endOffset;
964
815
  }
965
816
  }
966
- return focusData;
817
+ return state2;
967
818
  }
968
819
  /**
969
- * 恢复焦点状态
820
+ * 恢复焦点状态(在重渲染之后调用)
821
+ * @param state - 之前捕获的焦点状态
970
822
  */
971
- restoreFocusState(focusData) {
972
- if (!focusData) return;
973
- try {
974
- let targetElement = null;
975
- if (focusData.className) {
976
- targetElement = this.shadowRoot.querySelector(
977
- `.${focusData.className.split(" ")[0]}`
978
- );
823
+ restoreFocusState(state2) {
824
+ if (!state2 || !state2.key) {
825
+ return;
826
+ }
827
+ const root = this.getActiveRoot();
828
+ const target = root.querySelector(`[data-wsx-key="${state2.key}"]`);
829
+ if (!target) {
830
+ return;
831
+ }
832
+ if (state2.value !== void 0) {
833
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
834
+ target.value = state2.value;
979
835
  }
980
- if (!targetElement) {
981
- targetElement = this.shadowRoot.querySelector(focusData.tagName);
836
+ }
837
+ if (state2.selectedIndex !== void 0 && target instanceof HTMLSelectElement) {
838
+ target.selectedIndex = state2.selectedIndex;
839
+ }
840
+ requestAnimationFrame(() => {
841
+ const currentTarget = root.querySelector(
842
+ `[data-wsx-key="${state2.key}"]`
843
+ );
844
+ if (!currentTarget) {
845
+ return;
982
846
  }
983
- if (targetElement) {
984
- targetElement.focus({ preventScroll: true });
985
- if (focusData.selectionStart !== void 0) {
986
- if (targetElement instanceof HTMLInputElement) {
987
- targetElement.setSelectionRange(
988
- focusData.selectionStart,
989
- focusData.selectionEnd
990
- );
991
- } else if (targetElement instanceof HTMLSelectElement) {
992
- targetElement.value = focusData.value;
993
- } else if (targetElement.hasAttribute("contenteditable")) {
994
- this.setCursorPosition(targetElement, focusData.selectionStart);
847
+ if (state2.value !== void 0) {
848
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
849
+ if (currentTarget.value !== state2.value) {
850
+ currentTarget.value = state2.value;
995
851
  }
996
852
  }
997
853
  }
998
- } catch {
854
+ currentTarget.focus({ preventScroll: true });
855
+ if (state2.selectionStart !== void 0) {
856
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
857
+ const start = state2.selectionStart;
858
+ const end = state2.selectionEnd ?? start;
859
+ currentTarget.setSelectionRange(start, end);
860
+ if (state2.scrollTop !== void 0 && currentTarget instanceof HTMLTextAreaElement) {
861
+ currentTarget.scrollTop = state2.scrollTop;
862
+ }
863
+ } else if (currentTarget.hasAttribute("contenteditable")) {
864
+ const selection = window.getSelection();
865
+ if (selection) {
866
+ const range = document.createRange();
867
+ const textNode = currentTarget.childNodes[0];
868
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
869
+ const maxPos = Math.min(
870
+ state2.selectionStart,
871
+ textNode.textContent?.length || 0
872
+ );
873
+ range.setStart(textNode, maxPos);
874
+ range.setEnd(textNode, state2.selectionEnd ?? maxPos);
875
+ selection.removeAllRanges();
876
+ selection.addRange(range);
877
+ }
878
+ }
879
+ }
880
+ }
881
+ });
882
+ }
883
+ };
884
+
885
+ // src/web-component.ts
886
+ var logger3 = createLogger("WebComponent");
887
+ var WebComponent = class extends BaseComponent {
888
+ // Initialized by BaseComponent constructor
889
+ constructor(config = {}) {
890
+ super(config);
891
+ this.attachShadow({ mode: "open" });
892
+ }
893
+ /**
894
+ * Web Component生命周期:连接到DOM
895
+ */
896
+ connectedCallback() {
897
+ this.connected = true;
898
+ try {
899
+ const stylesToApply = this._autoStyles || this.config.styles;
900
+ if (stylesToApply) {
901
+ const styleName = this.config.styleName || this.constructor.name;
902
+ StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
903
+ }
904
+ const content = this.render();
905
+ this.shadowRoot.appendChild(content);
906
+ this.initializeEventListeners();
907
+ this.onConnected?.();
908
+ } catch (error) {
909
+ logger3.error(`Error in connectedCallback:`, error);
910
+ this.renderError(error);
999
911
  }
1000
912
  }
1001
913
  /**
1002
- * 设置光标位置
914
+ * Web Component生命周期:从DOM断开
1003
915
  */
1004
- setCursorPosition(element, position) {
916
+ disconnectedCallback() {
917
+ this.connected = false;
918
+ this.cleanup();
919
+ this.onDisconnected?.();
920
+ }
921
+ /**
922
+ * 查找Shadow DOM内的元素
923
+ *
924
+ * @param selector - CSS选择器
925
+ * @returns 元素或null
926
+ */
927
+ querySelector(selector) {
928
+ return this.shadowRoot.querySelector(selector);
929
+ }
930
+ /**
931
+ * 查找Shadow DOM内的所有匹配元素
932
+ *
933
+ * @param selector - CSS选择器
934
+ * @returns 元素列表
935
+ */
936
+ querySelectorAll(selector) {
937
+ return this.shadowRoot.querySelectorAll(selector);
938
+ }
939
+ /**
940
+ * 重新渲染组件
941
+ */
942
+ rerender() {
943
+ if (!this.connected) {
944
+ logger3.warn("Component is not connected, skipping rerender.");
945
+ return;
946
+ }
947
+ const focusState = this.captureFocusState();
948
+ this._pendingFocusState = focusState;
949
+ const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
1005
950
  try {
1006
- const selection = window.getSelection();
1007
- if (selection) {
1008
- const range = document.createRange();
1009
- const textNode = element.childNodes[0];
1010
- if (textNode) {
1011
- const maxPos = Math.min(position, textNode.textContent?.length || 0);
1012
- range.setStart(textNode, maxPos);
1013
- range.setEnd(textNode, maxPos);
1014
- selection.removeAllRanges();
1015
- selection.addRange(range);
951
+ if (adoptedStyleSheets.length === 0) {
952
+ const stylesToApply = this._autoStyles || this.config.styles;
953
+ if (stylesToApply) {
954
+ const styleName = this.config.styleName || this.constructor.name;
955
+ StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
1016
956
  }
1017
957
  }
1018
- } catch {
958
+ const content = this.render();
959
+ if (focusState && focusState.key && focusState.value !== void 0) {
960
+ const target = content.querySelector(
961
+ `[data-wsx-key="${focusState.key}"]`
962
+ );
963
+ if (target) {
964
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
965
+ target.value = focusState.value;
966
+ }
967
+ }
968
+ }
969
+ if (this.shadowRoot.adoptedStyleSheets) {
970
+ this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
971
+ }
972
+ requestAnimationFrame(() => {
973
+ this.shadowRoot.appendChild(content);
974
+ const oldChildren = Array.from(this.shadowRoot.children).filter(
975
+ (child) => child !== content
976
+ );
977
+ oldChildren.forEach((child) => child.remove());
978
+ requestAnimationFrame(() => {
979
+ this.restoreFocusState(focusState);
980
+ this._pendingFocusState = null;
981
+ });
982
+ });
983
+ } catch (error) {
984
+ logger3.error("Error in rerender:", error);
985
+ this.renderError(error);
1019
986
  }
1020
987
  }
1021
988
  /**
1022
- * 获取所有响应式状态的快照(用于调试)
989
+ * 渲染错误信息
990
+ *
991
+ * @param error - 错误对象
1023
992
  */
1024
- getStateSnapshot() {
1025
- const snapshot = {};
1026
- this._reactiveStates.forEach((state, key) => {
1027
- snapshot[key] = state.getter();
1028
- });
1029
- return snapshot;
993
+ renderError(error) {
994
+ this.shadowRoot.innerHTML = "";
995
+ const errorElement = h(
996
+ "div",
997
+ {
998
+ style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
999
+ },
1000
+ [
1001
+ h("strong", {}, `[${this.constructor.name}] Component Error:`),
1002
+ h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
1003
+ ]
1004
+ );
1005
+ this.shadowRoot.appendChild(errorElement);
1006
+ }
1007
+ };
1008
+
1009
+ // src/light-component.ts
1010
+ var logger4 = createLogger("LightComponent");
1011
+ var LightComponent = class extends BaseComponent {
1012
+ // Initialized by BaseComponent constructor
1013
+ constructor(config = {}) {
1014
+ super(config);
1030
1015
  }
1031
1016
  /**
1032
- * 清理响应式状态(组件销毁时)
1017
+ * Web Component生命周期:连接到DOM
1033
1018
  */
1034
- cleanupReactiveStates() {
1035
- this._reactiveStates.clear();
1019
+ connectedCallback() {
1020
+ this.connected = true;
1021
+ try {
1022
+ const stylesToApply = this._autoStyles || this.config.styles;
1023
+ if (stylesToApply) {
1024
+ const styleName = this.config.styleName || this.constructor.name;
1025
+ this.applyScopedStyles(styleName, stylesToApply);
1026
+ }
1027
+ const content = this.render();
1028
+ this.appendChild(content);
1029
+ this.initializeEventListeners();
1030
+ this.onConnected?.();
1031
+ } catch (error) {
1032
+ logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
1033
+ this.renderError(error);
1034
+ }
1036
1035
  }
1037
1036
  /**
1038
- * 重写 disconnectedCallback 以清理状态
1037
+ * Web Component生命周期:从DOM断开
1039
1038
  */
1040
1039
  disconnectedCallback() {
1041
- super.disconnectedCallback();
1040
+ this.connected = false;
1041
+ this.cleanup();
1042
1042
  this.cleanupReactiveStates();
1043
+ this.cleanupStyles();
1044
+ this.onDisconnected?.();
1043
1045
  }
1044
1046
  /**
1045
- * 启用调试模式
1047
+ * 查找组件内的元素
1048
+ *
1049
+ * @param selector - CSS选择器
1050
+ * @returns 元素或null
1046
1051
  */
1047
- enableDebug() {
1048
- this._isDebugEnabled = true;
1052
+ querySelector(selector) {
1053
+ return HTMLElement.prototype.querySelector.call(this, selector);
1049
1054
  }
1050
1055
  /**
1051
- * 禁用调试模式
1056
+ * 查找组件内的所有匹配元素
1057
+ *
1058
+ * @param selector - CSS选择器
1059
+ * @returns 元素列表
1052
1060
  */
1053
- disableDebug() {
1054
- this._isDebugEnabled = false;
1061
+ querySelectorAll(selector) {
1062
+ return HTMLElement.prototype.querySelectorAll.call(this, selector);
1055
1063
  }
1056
- };
1057
- function makeReactive(_debugMode = false) {
1058
- return function(constructor) {
1059
- return class ReactiveComponent extends constructor {
1060
- constructor(...args) {
1061
- super(...args);
1062
- if (!(this instanceof ReactiveWebComponent)) {
1063
- this.reactive = function(obj) {
1064
- return reactive(obj, () => this.rerender());
1065
- };
1064
+ /**
1065
+ * 重新渲染组件
1066
+ */
1067
+ rerender() {
1068
+ if (!this.connected) {
1069
+ logger4.warn(
1070
+ `[${this.constructor.name}] Component is not connected, skipping rerender.`
1071
+ );
1072
+ return;
1073
+ }
1074
+ const focusState = this.captureFocusState();
1075
+ this._pendingFocusState = focusState;
1076
+ try {
1077
+ const content = this.render();
1078
+ if (focusState && focusState.key && focusState.value !== void 0) {
1079
+ const target = content.querySelector(
1080
+ `[data-wsx-key="${focusState.key}"]`
1081
+ );
1082
+ if (target) {
1083
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
1084
+ target.value = focusState.value;
1085
+ }
1066
1086
  }
1067
1087
  }
1068
- render() {
1069
- throw new Error("render() method must be implemented by subclass");
1088
+ const stylesToApply = this._autoStyles || this.config.styles;
1089
+ if (stylesToApply) {
1090
+ const styleName = this.config.styleName || this.constructor.name;
1091
+ let styleElement = this.querySelector(
1092
+ `style[data-wsx-light-component="${styleName}"]`
1093
+ );
1094
+ if (!styleElement) {
1095
+ styleElement = document.createElement("style");
1096
+ styleElement.setAttribute("data-wsx-light-component", styleName);
1097
+ styleElement.textContent = stylesToApply;
1098
+ this.insertBefore(styleElement, this.firstChild);
1099
+ } else if (styleElement.textContent !== stylesToApply) {
1100
+ styleElement.textContent = stylesToApply;
1101
+ }
1070
1102
  }
1071
- };
1103
+ requestAnimationFrame(() => {
1104
+ this.appendChild(content);
1105
+ const oldChildren = Array.from(this.children).filter((child) => {
1106
+ if (child === content) {
1107
+ return false;
1108
+ }
1109
+ if (stylesToApply && child instanceof HTMLStyleElement && child.getAttribute("data-wsx-light-component") === (this.config.styleName || this.constructor.name)) {
1110
+ return false;
1111
+ }
1112
+ return true;
1113
+ });
1114
+ oldChildren.forEach((child) => child.remove());
1115
+ if (stylesToApply && this.children.length > 1) {
1116
+ const styleElement = this.querySelector(
1117
+ `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
1118
+ );
1119
+ if (styleElement && styleElement !== this.firstChild) {
1120
+ this.insertBefore(styleElement, this.firstChild);
1121
+ }
1122
+ }
1123
+ requestAnimationFrame(() => {
1124
+ this.restoreFocusState(focusState);
1125
+ this._pendingFocusState = null;
1126
+ });
1127
+ });
1128
+ } catch (error) {
1129
+ logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
1130
+ this.renderError(error);
1131
+ }
1132
+ }
1133
+ /**
1134
+ * 渲染错误信息
1135
+ *
1136
+ * @param error - 错误对象
1137
+ */
1138
+ renderError(error) {
1139
+ this.innerHTML = "";
1140
+ const errorElement = h(
1141
+ "div",
1142
+ {
1143
+ style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
1144
+ },
1145
+ [
1146
+ h("strong", {}, `[${this.constructor.name}] Component Error:`),
1147
+ h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
1148
+ ]
1149
+ );
1150
+ this.appendChild(errorElement);
1151
+ }
1152
+ /**
1153
+ * 为Light DOM组件应用样式
1154
+ * 直接将样式注入到组件自身,避免全局污染
1155
+ */
1156
+ applyScopedStyles(styleName, cssText) {
1157
+ const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
1158
+ if (existingStyle) {
1159
+ return;
1160
+ }
1161
+ const styleElement = document.createElement("style");
1162
+ styleElement.setAttribute("data-wsx-light-component", styleName);
1163
+ styleElement.textContent = cssText;
1164
+ this.insertBefore(styleElement, this.firstChild);
1165
+ }
1166
+ /**
1167
+ * 清理组件样式
1168
+ */
1169
+ cleanupStyles() {
1170
+ const styleName = this.config.styleName || this.constructor.name;
1171
+ const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
1172
+ if (existingStyle) {
1173
+ existingStyle.remove();
1174
+ }
1175
+ }
1176
+ };
1177
+
1178
+ // src/auto-register.ts
1179
+ function autoRegister(options = {}) {
1180
+ return function(constructor) {
1181
+ const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
1182
+ if (!customElements.get(tagName)) {
1183
+ customElements.define(tagName, constructor);
1184
+ }
1185
+ return constructor;
1072
1186
  };
1073
1187
  }
1074
- function createReactiveComponent(ComponentClass, config) {
1075
- if (ComponentClass.prototype instanceof ReactiveWebComponent) {
1076
- return new ComponentClass(config);
1188
+ function registerComponent(constructor, options = {}) {
1189
+ const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
1190
+ if (!customElements.get(tagName)) {
1191
+ customElements.define(tagName, constructor);
1192
+ }
1193
+ }
1194
+ function deriveTagName(className, prefix) {
1195
+ let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1196
+ if (!kebabCase.includes("-")) {
1197
+ kebabCase = `${kebabCase}-component`;
1198
+ }
1199
+ return prefix ? `${prefix}${kebabCase}` : kebabCase;
1200
+ }
1201
+
1202
+ // src/reactive-decorator.ts
1203
+ function state(target, propertyKey) {
1204
+ let normalizedPropertyKey;
1205
+ if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
1206
+ normalizedPropertyKey = propertyKey;
1207
+ } else {
1208
+ const propertyKeyStr = String(propertyKey);
1209
+ if (propertyKeyStr === "[object Object]") {
1210
+ throw new Error(
1211
+ `@state decorator: Invalid propertyKey detected.
1212
+
1213
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
1214
+
1215
+ To fix this, please:
1216
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
1217
+ 2. Configure it in vite.config.ts:
1218
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
1219
+ export default defineConfig({ plugins: [wsx()] });
1220
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
1221
+ npm install --save-dev @wsxjs/wsx-tsconfig
1222
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
1223
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
1224
+
1225
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
1226
+ );
1227
+ }
1228
+ normalizedPropertyKey = propertyKeyStr;
1229
+ }
1230
+ if (target == null) {
1231
+ const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
1232
+ throw new Error(
1233
+ `@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
1234
+
1235
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
1236
+
1237
+ To fix this, please:
1238
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
1239
+ 2. Configure it in vite.config.ts:
1240
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
1241
+ export default defineConfig({ plugins: [wsx()] });
1242
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
1243
+ npm install --save-dev @wsxjs/wsx-tsconfig
1244
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
1245
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
1246
+
1247
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
1248
+ );
1249
+ }
1250
+ if (typeof target !== "object") {
1251
+ const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
1252
+ throw new Error(
1253
+ `@state decorator: Cannot be used on "${propertyKeyStr}". @state is for properties only, not methods.`
1254
+ );
1255
+ }
1256
+ const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
1257
+ if (descriptor?.get) {
1258
+ const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
1259
+ throw new Error(
1260
+ `@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
1261
+ );
1077
1262
  }
1078
- const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
1079
- return new ReactiveComponent(config);
1080
1263
  }
1081
1264
  // Annotate the CommonJS export names for ESM import in node:
1082
1265
  0 && (module.exports = {
1083
1266
  Fragment,
1084
1267
  LightComponent,
1085
- ReactiveDebug,
1086
- ReactiveWebComponent,
1087
1268
  StyleManager,
1088
1269
  WSXLogger,
1089
1270
  WebComponent,
1090
1271
  autoRegister,
1091
1272
  createLogger,
1092
- createReactiveComponent,
1093
- createState,
1094
1273
  h,
1095
1274
  jsx,
1096
1275
  jsxs,
1097
1276
  logger,
1098
- makeReactive,
1099
- reactive,
1100
- registerComponent
1277
+ registerComponent,
1278
+ state
1101
1279
  });