@wsxjs/wsx-core 0.0.8 → 0.0.10

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,65 @@ 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 activeElement = null;
658
+ if (root instanceof ShadowRoot) {
659
+ activeElement = root.activeElement;
660
+ } else {
661
+ const docActiveElement = document.activeElement;
662
+ if (docActiveElement && root.contains(docActiveElement)) {
663
+ activeElement = docActiveElement;
664
+ }
665
+ }
666
+ if (activeElement) {
667
+ const isInputElement = activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement || activeElement instanceof HTMLSelectElement || activeElement.hasAttribute("contenteditable");
668
+ const forceRender = activeElement.hasAttribute("data-wsx-force-render");
669
+ if (isInputElement && !forceRender) {
670
+ this._pendingRerender = true;
671
+ if (this._rerenderDebounceTimer !== null) {
672
+ clearTimeout(this._rerenderDebounceTimer);
673
+ this._rerenderDebounceTimer = null;
674
+ }
675
+ return;
676
+ }
512
677
  }
678
+ if (this._pendingRerender) {
679
+ this._pendingRerender = false;
680
+ }
681
+ queueMicrotask(() => {
682
+ if (this.connected) {
683
+ this.rerender();
684
+ }
685
+ });
686
+ }
687
+ /**
688
+ * 清理资源(在组件断开连接时调用)
689
+ * @internal
690
+ */
691
+ cleanup() {
692
+ if (this._rerenderDebounceTimer !== null) {
693
+ clearTimeout(this._rerenderDebounceTimer);
694
+ this._rerenderDebounceTimer = null;
695
+ }
696
+ document.removeEventListener("blur", this.handleGlobalBlur, true);
697
+ this._pendingRerender = false;
698
+ }
699
+ /**
700
+ * 初始化事件监听器(在组件连接时调用)
701
+ * @internal
702
+ */
703
+ initializeEventListeners() {
704
+ document.addEventListener("blur", this.handleGlobalBlur, true);
513
705
  }
514
706
  /**
515
707
  * 获取配置值
@@ -572,15 +764,136 @@ var BaseComponent = class extends HTMLElement {
572
764
  cleanupReactiveStates() {
573
765
  this._reactiveStates.clear();
574
766
  }
767
+ /**
768
+ * 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
769
+ * @returns 活动的 DOM 根元素
770
+ */
771
+ getActiveRoot() {
772
+ if ("shadowRoot" in this && this.shadowRoot) {
773
+ return this.shadowRoot;
774
+ }
775
+ return this;
776
+ }
777
+ /**
778
+ * 捕获当前焦点状态(在重渲染之前调用)
779
+ * @returns 焦点状态,如果没有焦点元素则返回 null
780
+ */
781
+ captureFocusState() {
782
+ const root = this.getActiveRoot();
783
+ let activeElement = null;
784
+ if (root instanceof ShadowRoot) {
785
+ activeElement = root.activeElement;
786
+ } else {
787
+ const docActiveElement = document.activeElement;
788
+ if (docActiveElement && root.contains(docActiveElement)) {
789
+ activeElement = docActiveElement;
790
+ }
791
+ }
792
+ if (!activeElement || !(activeElement instanceof HTMLElement)) {
793
+ return null;
794
+ }
795
+ const key = activeElement.getAttribute("data-wsx-key");
796
+ if (!key) {
797
+ return null;
798
+ }
799
+ const tagName = activeElement.tagName.toLowerCase();
800
+ const state2 = {
801
+ key,
802
+ elementType: tagName
803
+ };
804
+ if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
805
+ state2.value = activeElement.value;
806
+ state2.selectionStart = activeElement.selectionStart ?? void 0;
807
+ state2.selectionEnd = activeElement.selectionEnd ?? void 0;
808
+ if (activeElement instanceof HTMLTextAreaElement) {
809
+ state2.scrollTop = activeElement.scrollTop;
810
+ }
811
+ } else if (activeElement instanceof HTMLSelectElement) {
812
+ state2.elementType = "select";
813
+ state2.selectedIndex = activeElement.selectedIndex;
814
+ } else if (activeElement.hasAttribute("contenteditable")) {
815
+ state2.elementType = "contenteditable";
816
+ const selection = window.getSelection();
817
+ if (selection && selection.rangeCount > 0) {
818
+ const range = selection.getRangeAt(0);
819
+ state2.selectionStart = range.startOffset;
820
+ state2.selectionEnd = range.endOffset;
821
+ }
822
+ }
823
+ return state2;
824
+ }
825
+ /**
826
+ * 恢复焦点状态(在重渲染之后调用)
827
+ * @param state - 之前捕获的焦点状态
828
+ */
829
+ restoreFocusState(state2) {
830
+ if (!state2 || !state2.key) {
831
+ return;
832
+ }
833
+ const root = this.getActiveRoot();
834
+ const target = root.querySelector(`[data-wsx-key="${state2.key}"]`);
835
+ if (!target) {
836
+ return;
837
+ }
838
+ if (state2.value !== void 0) {
839
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
840
+ target.value = state2.value;
841
+ }
842
+ }
843
+ if (state2.selectedIndex !== void 0 && target instanceof HTMLSelectElement) {
844
+ target.selectedIndex = state2.selectedIndex;
845
+ }
846
+ requestAnimationFrame(() => {
847
+ const currentTarget = root.querySelector(
848
+ `[data-wsx-key="${state2.key}"]`
849
+ );
850
+ if (!currentTarget) {
851
+ return;
852
+ }
853
+ if (state2.value !== void 0) {
854
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
855
+ if (currentTarget.value !== state2.value) {
856
+ currentTarget.value = state2.value;
857
+ }
858
+ }
859
+ }
860
+ currentTarget.focus({ preventScroll: true });
861
+ if (state2.selectionStart !== void 0) {
862
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
863
+ const start = state2.selectionStart;
864
+ const end = state2.selectionEnd ?? start;
865
+ currentTarget.setSelectionRange(start, end);
866
+ if (state2.scrollTop !== void 0 && currentTarget instanceof HTMLTextAreaElement) {
867
+ currentTarget.scrollTop = state2.scrollTop;
868
+ }
869
+ } else if (currentTarget.hasAttribute("contenteditable")) {
870
+ const selection = window.getSelection();
871
+ if (selection) {
872
+ const range = document.createRange();
873
+ const textNode = currentTarget.childNodes[0];
874
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
875
+ const maxPos = Math.min(
876
+ state2.selectionStart,
877
+ textNode.textContent?.length || 0
878
+ );
879
+ range.setStart(textNode, maxPos);
880
+ range.setEnd(textNode, state2.selectionEnd ?? maxPos);
881
+ selection.removeAllRanges();
882
+ selection.addRange(range);
883
+ }
884
+ }
885
+ }
886
+ }
887
+ });
888
+ }
575
889
  };
576
890
 
577
891
  // src/web-component.ts
892
+ var logger3 = createLogger("WebComponent");
578
893
  var WebComponent = class extends BaseComponent {
894
+ // Initialized by BaseComponent constructor
579
895
  constructor(config = {}) {
580
896
  super(config);
581
- // Initialized by BaseComponent constructor
582
- this._preserveFocus = true;
583
- this._preserveFocus = config.preserveFocus ?? true;
584
897
  this.attachShadow({ mode: "open" });
585
898
  }
586
899
  /**
@@ -589,16 +902,17 @@ var WebComponent = class extends BaseComponent {
589
902
  connectedCallback() {
590
903
  this.connected = true;
591
904
  try {
592
- const stylesToApply = this._getAutoStyles?.() || this.config.styles;
905
+ const stylesToApply = this._autoStyles || this.config.styles;
593
906
  if (stylesToApply) {
594
907
  const styleName = this.config.styleName || this.constructor.name;
595
908
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
596
909
  }
597
910
  const content = this.render();
598
911
  this.shadowRoot.appendChild(content);
912
+ this.initializeEventListeners();
599
913
  this.onConnected?.();
600
914
  } catch (error) {
601
- console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
915
+ logger3.error(`Error in connectedCallback:`, error);
602
916
  this.renderError(error);
603
917
  }
604
918
  }
@@ -607,6 +921,7 @@ var WebComponent = class extends BaseComponent {
607
921
  */
608
922
  disconnectedCallback() {
609
923
  this.connected = false;
924
+ this.cleanup();
610
925
  this.onDisconnected?.();
611
926
  }
612
927
  /**
@@ -632,118 +947,48 @@ var WebComponent = class extends BaseComponent {
632
947
  */
633
948
  rerender() {
634
949
  if (!this.connected) {
635
- console.warn(
636
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
637
- );
950
+ logger3.warn("Component is not connected, skipping rerender.");
638
951
  return;
639
952
  }
640
- let focusData = null;
641
- if (this._preserveFocus && this.shadowRoot) {
642
- const activeElement = this.shadowRoot.activeElement;
643
- focusData = this.saveFocusState(activeElement);
644
- }
953
+ const focusState = this.captureFocusState();
954
+ this._pendingFocusState = focusState;
645
955
  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
956
  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;
957
+ if (adoptedStyleSheets.length === 0) {
958
+ const stylesToApply = this._autoStyles || this.config.styles;
959
+ if (stylesToApply) {
960
+ const styleName = this.config.styleName || this.constructor.name;
961
+ StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
962
+ }
692
963
  }
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]}`
964
+ const content = this.render();
965
+ if (focusState && focusState.key && focusState.value !== void 0) {
966
+ const target = content.querySelector(
967
+ `[data-wsx-key="${focusState.key}"]`
706
968
  );
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);
969
+ if (target) {
970
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
971
+ target.value = focusState.value;
723
972
  }
724
973
  }
725
974
  }
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
- }
975
+ if (this.shadowRoot.adoptedStyleSheets) {
976
+ this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
745
977
  }
746
- } catch {
978
+ requestAnimationFrame(() => {
979
+ this.shadowRoot.appendChild(content);
980
+ const oldChildren = Array.from(this.shadowRoot.children).filter(
981
+ (child) => child !== content
982
+ );
983
+ oldChildren.forEach((child) => child.remove());
984
+ requestAnimationFrame(() => {
985
+ this.restoreFocusState(focusState);
986
+ this._pendingFocusState = null;
987
+ });
988
+ });
989
+ } catch (error) {
990
+ logger3.error("Error in rerender:", error);
991
+ this.renderError(error);
747
992
  }
748
993
  }
749
994
  /**
@@ -768,7 +1013,7 @@ var WebComponent = class extends BaseComponent {
768
1013
  };
769
1014
 
770
1015
  // src/light-component.ts
771
- var logger3 = createLogger("LightComponent");
1016
+ var logger4 = createLogger("LightComponent");
772
1017
  var LightComponent = class extends BaseComponent {
773
1018
  // Initialized by BaseComponent constructor
774
1019
  constructor(config = {}) {
@@ -787,9 +1032,10 @@ var LightComponent = class extends BaseComponent {
787
1032
  }
788
1033
  const content = this.render();
789
1034
  this.appendChild(content);
1035
+ this.initializeEventListeners();
790
1036
  this.onConnected?.();
791
1037
  } catch (error) {
792
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
1038
+ logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
793
1039
  this.renderError(error);
794
1040
  }
795
1041
  }
@@ -797,6 +1043,8 @@ var LightComponent = class extends BaseComponent {
797
1043
  * Web Component生命周期:从DOM断开
798
1044
  */
799
1045
  disconnectedCallback() {
1046
+ this.connected = false;
1047
+ this.cleanup();
800
1048
  this.cleanupReactiveStates();
801
1049
  this.cleanupStyles();
802
1050
  this.onDisconnected?.();
@@ -824,32 +1072,67 @@ var LightComponent = class extends BaseComponent {
824
1072
  */
825
1073
  rerender() {
826
1074
  if (!this.connected) {
827
- logger3.warn(
1075
+ logger4.warn(
828
1076
  `[${this.constructor.name}] Component is not connected, skipping rerender.`
829
1077
  );
830
1078
  return;
831
1079
  }
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
- }
1080
+ const focusState = this.captureFocusState();
1081
+ this._pendingFocusState = focusState;
840
1082
  try {
841
1083
  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}"]`
1084
+ if (focusState && focusState.key && focusState.value !== void 0) {
1085
+ const target = content.querySelector(
1086
+ `[data-wsx-key="${focusState.key}"]`
846
1087
  );
847
- if (styleElement && styleElement !== this.firstChild) {
1088
+ if (target) {
1089
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
1090
+ target.value = focusState.value;
1091
+ }
1092
+ }
1093
+ }
1094
+ const stylesToApply = this._autoStyles || this.config.styles;
1095
+ if (stylesToApply) {
1096
+ const styleName = this.config.styleName || this.constructor.name;
1097
+ let styleElement = this.querySelector(
1098
+ `style[data-wsx-light-component="${styleName}"]`
1099
+ );
1100
+ if (!styleElement) {
1101
+ styleElement = document.createElement("style");
1102
+ styleElement.setAttribute("data-wsx-light-component", styleName);
1103
+ styleElement.textContent = stylesToApply;
848
1104
  this.insertBefore(styleElement, this.firstChild);
1105
+ } else if (styleElement.textContent !== stylesToApply) {
1106
+ styleElement.textContent = stylesToApply;
849
1107
  }
850
1108
  }
1109
+ requestAnimationFrame(() => {
1110
+ this.appendChild(content);
1111
+ const oldChildren = Array.from(this.children).filter((child) => {
1112
+ if (child === content) {
1113
+ return false;
1114
+ }
1115
+ if (stylesToApply && child instanceof HTMLStyleElement && child.getAttribute("data-wsx-light-component") === (this.config.styleName || this.constructor.name)) {
1116
+ return false;
1117
+ }
1118
+ return true;
1119
+ });
1120
+ oldChildren.forEach((child) => child.remove());
1121
+ if (stylesToApply && this.children.length > 1) {
1122
+ const styleElement = this.querySelector(
1123
+ `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
1124
+ );
1125
+ if (styleElement && styleElement !== this.firstChild) {
1126
+ this.insertBefore(styleElement, this.firstChild);
1127
+ }
1128
+ }
1129
+ requestAnimationFrame(() => {
1130
+ this.restoreFocusState(focusState);
1131
+ this._pendingFocusState = null;
1132
+ });
1133
+ });
851
1134
  } catch (error) {
852
- logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
1135
+ logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
853
1136
  this.renderError(error);
854
1137
  }
855
1138
  }
@@ -931,7 +1214,21 @@ function state(target, propertyKey) {
931
1214
  const propertyKeyStr = String(propertyKey);
932
1215
  if (propertyKeyStr === "[object Object]") {
933
1216
  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`
1217
+ `@state decorator: Invalid propertyKey detected.
1218
+
1219
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
1220
+
1221
+ To fix this, please:
1222
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
1223
+ 2. Configure it in vite.config.ts:
1224
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
1225
+ export default defineConfig({ plugins: [wsx()] });
1226
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
1227
+ npm install --save-dev @wsxjs/wsx-tsconfig
1228
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
1229
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
1230
+
1231
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
935
1232
  );
936
1233
  }
937
1234
  normalizedPropertyKey = propertyKeyStr;
@@ -939,7 +1236,21 @@ function state(target, propertyKey) {
939
1236
  if (target == null) {
940
1237
  const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
941
1238
  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`
1239
+ `@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
1240
+
1241
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
1242
+
1243
+ To fix this, please:
1244
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
1245
+ 2. Configure it in vite.config.ts:
1246
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
1247
+ export default defineConfig({ plugins: [wsx()] });
1248
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
1249
+ npm install --save-dev @wsxjs/wsx-tsconfig
1250
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
1251
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
1252
+
1253
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
943
1254
  );
944
1255
  }
945
1256
  if (typeof target !== "object") {