@wsxjs/wsx-core 0.0.8 → 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
@@ -177,6 +177,13 @@ function h(tag, props = {}, ...children) {
177
177
  if (value) {
178
178
  element.setAttribute(key, "");
179
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
+ }
180
187
  } else {
181
188
  const attributeName = isSVG ? getSVGAttributeName(key) : key;
182
189
  element.setAttribute(attributeName, String(value));
@@ -339,35 +346,107 @@ var UpdateScheduler = class {
339
346
  try {
340
347
  callback();
341
348
  } catch (error) {
342
- console.error("[WSX Reactive] Error in callback:", error);
349
+ logger2.error("[WSX Reactive] Error in callback:", error);
343
350
  }
344
351
  });
345
352
  }
346
353
  };
347
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
+ ];
348
399
  function reactive(obj, onChange) {
349
- 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, {
350
405
  set(target, key, value) {
351
406
  const oldValue = target[key];
352
- if (oldValue !== value) {
353
- 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
+ }
354
416
  scheduler.schedule(onChange);
355
417
  }
356
418
  return true;
357
419
  },
358
420
  get(target, key) {
359
- 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;
360
442
  },
361
443
  has(target, key) {
362
444
  return key in target;
363
- },
364
- ownKeys(target) {
365
- return Reflect.ownKeys(target);
366
- },
367
- getOwnPropertyDescriptor(target, key) {
368
- return Reflect.getOwnPropertyDescriptor(target, key);
369
445
  }
370
446
  });
447
+ proxyCache.set(obj, proxy);
448
+ originalCache.set(proxy, obj);
449
+ return proxy;
371
450
  }
372
451
  function createState(initialValue, onChange) {
373
452
  let currentValue = initialValue;
@@ -415,6 +494,7 @@ var ReactiveDebug = {
415
494
  };
416
495
  function reactiveWithDebug(obj, onChange, debugName) {
417
496
  const name = debugName || obj.constructor.name || "Unknown";
497
+ const isArray = Array.isArray(obj);
418
498
  return new Proxy(obj, {
419
499
  set(target, key, value) {
420
500
  const oldValue = target[key];
@@ -430,7 +510,28 @@ function reactiveWithDebug(obj, onChange, debugName) {
430
510
  return true;
431
511
  },
432
512
  get(target, key) {
433
- 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;
434
535
  }
435
536
  });
436
537
  }
@@ -442,6 +543,43 @@ var BaseComponent = class extends HTMLElement {
442
543
  this.connected = false;
443
544
  this._isDebugEnabled = false;
444
545
  this._reactiveStates = /* @__PURE__ */ new Map();
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
+ };
445
583
  this._isDebugEnabled = config.debug ?? false;
446
584
  const host = this;
447
585
  const originalStyles = config.styles;
@@ -505,11 +643,59 @@ var BaseComponent = class extends HTMLElement {
505
643
  /**
506
644
  * 调度重渲染
507
645
  * 这个方法被响应式系统调用,开发者通常不需要直接调用
646
+ * 使用 queueMicrotask 进行异步调度,与 reactive() 系统保持一致
508
647
  */
509
648
  scheduleRerender() {
510
- if (this.connected) {
511
- this.rerender();
649
+ if (!this.connected) {
650
+ if (this._rerenderDebounceTimer !== null) {
651
+ clearTimeout(this._rerenderDebounceTimer);
652
+ this._rerenderDebounceTimer = null;
653
+ }
654
+ return;
655
+ }
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);
663
+ }
664
+ if (hasActiveElement) {
665
+ this._pendingRerender = true;
666
+ if (this._rerenderDebounceTimer !== null) {
667
+ clearTimeout(this._rerenderDebounceTimer);
668
+ this._rerenderDebounceTimer = null;
669
+ }
670
+ return;
671
+ }
672
+ if (this._pendingRerender) {
673
+ this._pendingRerender = false;
674
+ }
675
+ queueMicrotask(() => {
676
+ if (this.connected) {
677
+ this.rerender();
678
+ }
679
+ });
680
+ }
681
+ /**
682
+ * 清理资源(在组件断开连接时调用)
683
+ * @internal
684
+ */
685
+ cleanup() {
686
+ if (this._rerenderDebounceTimer !== null) {
687
+ clearTimeout(this._rerenderDebounceTimer);
688
+ this._rerenderDebounceTimer = null;
512
689
  }
690
+ document.removeEventListener("blur", this.handleGlobalBlur, true);
691
+ this._pendingRerender = false;
692
+ }
693
+ /**
694
+ * 初始化事件监听器(在组件连接时调用)
695
+ * @internal
696
+ */
697
+ initializeEventListeners() {
698
+ document.addEventListener("blur", this.handleGlobalBlur, true);
513
699
  }
514
700
  /**
515
701
  * 获取配置值
@@ -572,15 +758,136 @@ var BaseComponent = class extends HTMLElement {
572
758
  cleanupReactiveStates() {
573
759
  this._reactiveStates.clear();
574
760
  }
761
+ /**
762
+ * 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
763
+ * @returns 活动的 DOM 根元素
764
+ */
765
+ getActiveRoot() {
766
+ if ("shadowRoot" in this && this.shadowRoot) {
767
+ return this.shadowRoot;
768
+ }
769
+ return this;
770
+ }
771
+ /**
772
+ * 捕获当前焦点状态(在重渲染之前调用)
773
+ * @returns 焦点状态,如果没有焦点元素则返回 null
774
+ */
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
+ }
785
+ }
786
+ if (!activeElement || !(activeElement instanceof HTMLElement)) {
787
+ return null;
788
+ }
789
+ const key = activeElement.getAttribute("data-wsx-key");
790
+ if (!key) {
791
+ return null;
792
+ }
793
+ const tagName = activeElement.tagName.toLowerCase();
794
+ const state2 = {
795
+ key,
796
+ elementType: tagName
797
+ };
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";
810
+ const selection = window.getSelection();
811
+ if (selection && selection.rangeCount > 0) {
812
+ const range = selection.getRangeAt(0);
813
+ state2.selectionStart = range.startOffset;
814
+ state2.selectionEnd = range.endOffset;
815
+ }
816
+ }
817
+ return state2;
818
+ }
819
+ /**
820
+ * 恢复焦点状态(在重渲染之后调用)
821
+ * @param state - 之前捕获的焦点状态
822
+ */
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;
835
+ }
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;
846
+ }
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;
851
+ }
852
+ }
853
+ }
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
+ }
575
883
  };
576
884
 
577
885
  // src/web-component.ts
886
+ var logger3 = createLogger("WebComponent");
578
887
  var WebComponent = class extends BaseComponent {
888
+ // Initialized by BaseComponent constructor
579
889
  constructor(config = {}) {
580
890
  super(config);
581
- // Initialized by BaseComponent constructor
582
- this._preserveFocus = true;
583
- this._preserveFocus = config.preserveFocus ?? true;
584
891
  this.attachShadow({ mode: "open" });
585
892
  }
586
893
  /**
@@ -589,16 +896,17 @@ var WebComponent = class extends BaseComponent {
589
896
  connectedCallback() {
590
897
  this.connected = true;
591
898
  try {
592
- const stylesToApply = this._getAutoStyles?.() || this.config.styles;
899
+ const stylesToApply = this._autoStyles || this.config.styles;
593
900
  if (stylesToApply) {
594
901
  const styleName = this.config.styleName || this.constructor.name;
595
902
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
596
903
  }
597
904
  const content = this.render();
598
905
  this.shadowRoot.appendChild(content);
906
+ this.initializeEventListeners();
599
907
  this.onConnected?.();
600
908
  } catch (error) {
601
- console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
909
+ logger3.error(`Error in connectedCallback:`, error);
602
910
  this.renderError(error);
603
911
  }
604
912
  }
@@ -607,6 +915,7 @@ var WebComponent = class extends BaseComponent {
607
915
  */
608
916
  disconnectedCallback() {
609
917
  this.connected = false;
918
+ this.cleanup();
610
919
  this.onDisconnected?.();
611
920
  }
612
921
  /**
@@ -632,118 +941,48 @@ var WebComponent = class extends BaseComponent {
632
941
  */
633
942
  rerender() {
634
943
  if (!this.connected) {
635
- console.warn(
636
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
637
- );
944
+ logger3.warn("Component is not connected, skipping rerender.");
638
945
  return;
639
946
  }
640
- let focusData = null;
641
- if (this._preserveFocus && this.shadowRoot) {
642
- const activeElement = this.shadowRoot.activeElement;
643
- focusData = this.saveFocusState(activeElement);
644
- }
947
+ const focusState = this.captureFocusState();
948
+ this._pendingFocusState = focusState;
645
949
  const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
646
- this.shadowRoot.innerHTML = "";
647
- if (this.shadowRoot.adoptedStyleSheets) {
648
- this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
649
- }
650
- if (adoptedStyleSheets.length === 0) {
651
- const stylesToApply = this._autoStyles || this.config.styles;
652
- if (stylesToApply) {
653
- const styleName = this.config.styleName || this.constructor.name;
654
- StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
655
- }
656
- }
657
950
  try {
658
- const content = this.render();
659
- this.shadowRoot.appendChild(content);
660
- } catch (error) {
661
- console.error(`[${this.constructor.name}] Error in rerender:`, error);
662
- this.renderError(error);
663
- }
664
- if (this._preserveFocus && focusData && this.shadowRoot) {
665
- this.restoreFocusState(focusData);
666
- }
667
- }
668
- /**
669
- * 保存焦点状态
670
- */
671
- saveFocusState(activeElement) {
672
- if (!activeElement) {
673
- return null;
674
- }
675
- const focusData = {
676
- tagName: activeElement.tagName.toLowerCase(),
677
- className: activeElement.className
678
- };
679
- if (activeElement.hasAttribute("contenteditable")) {
680
- const selection = window.getSelection();
681
- if (selection && selection.rangeCount > 0) {
682
- const range = selection.getRangeAt(0);
683
- focusData.selectionStart = range.startOffset;
684
- focusData.selectionEnd = range.endOffset;
685
- }
686
- }
687
- if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
688
- focusData.value = activeElement.value;
689
- if ("selectionStart" in activeElement) {
690
- focusData.selectionStart = activeElement.selectionStart ?? void 0;
691
- focusData.selectionEnd = activeElement.selectionEnd ?? void 0;
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);
956
+ }
692
957
  }
693
- }
694
- return focusData;
695
- }
696
- /**
697
- * 恢复焦点状态
698
- */
699
- restoreFocusState(focusData) {
700
- if (!focusData) return;
701
- try {
702
- let targetElement = null;
703
- if (focusData.className) {
704
- targetElement = this.shadowRoot.querySelector(
705
- `.${focusData.className.split(" ")[0]}`
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}"]`
706
962
  );
707
- }
708
- if (!targetElement) {
709
- targetElement = this.shadowRoot.querySelector(focusData.tagName);
710
- }
711
- if (targetElement) {
712
- targetElement.focus({ preventScroll: true });
713
- if (focusData.selectionStart !== void 0) {
714
- if (targetElement instanceof HTMLInputElement) {
715
- targetElement.setSelectionRange(
716
- focusData.selectionStart,
717
- focusData.selectionEnd ?? focusData.selectionStart
718
- );
719
- } else if (targetElement instanceof HTMLSelectElement) {
720
- targetElement.value = focusData.value ?? "";
721
- } else if (targetElement.hasAttribute("contenteditable")) {
722
- this.setCursorPosition(targetElement, focusData.selectionStart);
963
+ if (target) {
964
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
965
+ target.value = focusState.value;
723
966
  }
724
967
  }
725
968
  }
726
- } catch {
727
- }
728
- }
729
- /**
730
- * 设置光标位置
731
- */
732
- setCursorPosition(element, position) {
733
- try {
734
- const selection = window.getSelection();
735
- if (selection) {
736
- const range = document.createRange();
737
- const textNode = element.childNodes[0];
738
- if (textNode) {
739
- const maxPos = Math.min(position, textNode.textContent?.length || 0);
740
- range.setStart(textNode, maxPos);
741
- range.setEnd(textNode, maxPos);
742
- selection.removeAllRanges();
743
- selection.addRange(range);
744
- }
969
+ if (this.shadowRoot.adoptedStyleSheets) {
970
+ this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
745
971
  }
746
- } catch {
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);
747
986
  }
748
987
  }
749
988
  /**
@@ -768,7 +1007,7 @@ var WebComponent = class extends BaseComponent {
768
1007
  };
769
1008
 
770
1009
  // src/light-component.ts
771
- var logger3 = createLogger("LightComponent");
1010
+ var logger4 = createLogger("LightComponent");
772
1011
  var LightComponent = class extends BaseComponent {
773
1012
  // Initialized by BaseComponent constructor
774
1013
  constructor(config = {}) {
@@ -787,9 +1026,10 @@ var LightComponent = class extends BaseComponent {
787
1026
  }
788
1027
  const content = this.render();
789
1028
  this.appendChild(content);
1029
+ this.initializeEventListeners();
790
1030
  this.onConnected?.();
791
1031
  } catch (error) {
792
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
1032
+ logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
793
1033
  this.renderError(error);
794
1034
  }
795
1035
  }
@@ -797,6 +1037,8 @@ var LightComponent = class extends BaseComponent {
797
1037
  * Web Component生命周期:从DOM断开
798
1038
  */
799
1039
  disconnectedCallback() {
1040
+ this.connected = false;
1041
+ this.cleanup();
800
1042
  this.cleanupReactiveStates();
801
1043
  this.cleanupStyles();
802
1044
  this.onDisconnected?.();
@@ -824,32 +1066,67 @@ var LightComponent = class extends BaseComponent {
824
1066
  */
825
1067
  rerender() {
826
1068
  if (!this.connected) {
827
- logger3.warn(
1069
+ logger4.warn(
828
1070
  `[${this.constructor.name}] Component is not connected, skipping rerender.`
829
1071
  );
830
1072
  return;
831
1073
  }
832
- this.innerHTML = "";
833
- if (this.config.styles) {
834
- const styleName = this.config.styleName || this.constructor.name;
835
- const styleElement = document.createElement("style");
836
- styleElement.setAttribute("data-wsx-light-component", styleName);
837
- styleElement.textContent = this.config.styles;
838
- this.appendChild(styleElement);
839
- }
1074
+ const focusState = this.captureFocusState();
1075
+ this._pendingFocusState = focusState;
840
1076
  try {
841
1077
  const content = this.render();
842
- this.appendChild(content);
843
- if (this.config.styles && this.children.length > 1) {
844
- const styleElement = this.querySelector(
845
- `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
1078
+ if (focusState && focusState.key && focusState.value !== void 0) {
1079
+ const target = content.querySelector(
1080
+ `[data-wsx-key="${focusState.key}"]`
846
1081
  );
847
- if (styleElement && styleElement !== this.firstChild) {
1082
+ if (target) {
1083
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
1084
+ target.value = focusState.value;
1085
+ }
1086
+ }
1087
+ }
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;
848
1098
  this.insertBefore(styleElement, this.firstChild);
1099
+ } else if (styleElement.textContent !== stylesToApply) {
1100
+ styleElement.textContent = stylesToApply;
849
1101
  }
850
1102
  }
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
+ });
851
1128
  } catch (error) {
852
- logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
1129
+ logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
853
1130
  this.renderError(error);
854
1131
  }
855
1132
  }
@@ -931,7 +1208,21 @@ function state(target, propertyKey) {
931
1208
  const propertyKeyStr = String(propertyKey);
932
1209
  if (propertyKeyStr === "[object Object]") {
933
1210
  throw new Error(
934
- `@state decorator: Invalid propertyKey. This usually means the build tool doesn't support decorators properly. Please ensure Babel plugin is configured in vite.config.ts`
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.`
935
1226
  );
936
1227
  }
937
1228
  normalizedPropertyKey = propertyKeyStr;
@@ -939,7 +1230,21 @@ function state(target, propertyKey) {
939
1230
  if (target == null) {
940
1231
  const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
941
1232
  throw new Error(
942
- `@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}. Please ensure Babel plugin is configured in vite.config.ts`
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.`
943
1248
  );
944
1249
  }
945
1250
  if (typeof target !== "object") {