@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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  h
4
- } from "./chunk-VZQT7HU5.mjs";
4
+ } from "./chunk-CZII6RG2.mjs";
5
5
 
6
6
  // src/styles/style-manager.ts
7
7
  var StyleManager = class {
@@ -113,35 +113,107 @@ var UpdateScheduler = class {
113
113
  try {
114
114
  callback();
115
115
  } catch (error) {
116
- console.error("[WSX Reactive] Error in callback:", error);
116
+ logger2.error("[WSX Reactive] Error in callback:", error);
117
117
  }
118
118
  });
119
119
  }
120
120
  };
121
121
  var scheduler = new UpdateScheduler();
122
+ var proxyCache = /* @__PURE__ */ new WeakMap();
123
+ var originalCache = /* @__PURE__ */ new WeakMap();
124
+ var unwrappingSet = /* @__PURE__ */ new WeakSet();
125
+ function unwrapProxy(value) {
126
+ if (value == null || typeof value !== "object") {
127
+ return value;
128
+ }
129
+ let original = value;
130
+ if (originalCache.has(value)) {
131
+ original = originalCache.get(value);
132
+ }
133
+ if (unwrappingSet.has(original)) {
134
+ return null;
135
+ }
136
+ unwrappingSet.add(original);
137
+ try {
138
+ if (Array.isArray(original)) {
139
+ return original.map((item) => unwrapProxy(item));
140
+ }
141
+ const result = {};
142
+ for (const key in original) {
143
+ if (Object.prototype.hasOwnProperty.call(original, key)) {
144
+ const propValue = original[key];
145
+ if (propValue != null && typeof propValue === "object" && originalCache.has(propValue)) {
146
+ result[key] = unwrapProxy(originalCache.get(propValue));
147
+ } else {
148
+ result[key] = unwrapProxy(propValue);
149
+ }
150
+ }
151
+ }
152
+ return result;
153
+ } finally {
154
+ unwrappingSet.delete(original);
155
+ }
156
+ }
157
+ var ARRAY_MUTATION_METHODS = [
158
+ "push",
159
+ "pop",
160
+ "shift",
161
+ "unshift",
162
+ "splice",
163
+ "sort",
164
+ "reverse"
165
+ ];
122
166
  function reactive(obj, onChange) {
123
- return new Proxy(obj, {
167
+ if (proxyCache.has(obj)) {
168
+ return proxyCache.get(obj);
169
+ }
170
+ const isArray = Array.isArray(obj);
171
+ const proxy = new Proxy(obj, {
124
172
  set(target, key, value) {
125
173
  const oldValue = target[key];
126
- if (oldValue !== value) {
127
- target[key] = value;
174
+ const oldOriginal = originalCache.get(oldValue) || oldValue;
175
+ const newOriginal = value != null && typeof value === "object" ? originalCache.get(value) || value : value;
176
+ if (oldOriginal !== newOriginal) {
177
+ if (value != null && typeof value === "object") {
178
+ const reactiveValue = reactive(value, onChange);
179
+ target[key] = reactiveValue;
180
+ } else {
181
+ target[key] = value;
182
+ }
128
183
  scheduler.schedule(onChange);
129
184
  }
130
185
  return true;
131
186
  },
132
187
  get(target, key) {
133
- return target[key];
188
+ if (key === "toJSON") {
189
+ return function() {
190
+ return unwrapProxy(obj);
191
+ };
192
+ }
193
+ const value = target[key];
194
+ if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
195
+ return function(...args) {
196
+ const arrayMethod = Array.prototype[key];
197
+ const result = arrayMethod.apply(target, args);
198
+ scheduler.schedule(onChange);
199
+ return result;
200
+ };
201
+ }
202
+ if (value != null && typeof value === "object") {
203
+ if (proxyCache.has(value)) {
204
+ return proxyCache.get(value);
205
+ }
206
+ return reactive(value, onChange);
207
+ }
208
+ return value;
134
209
  },
135
210
  has(target, key) {
136
211
  return key in target;
137
- },
138
- ownKeys(target) {
139
- return Reflect.ownKeys(target);
140
- },
141
- getOwnPropertyDescriptor(target, key) {
142
- return Reflect.getOwnPropertyDescriptor(target, key);
143
212
  }
144
213
  });
214
+ proxyCache.set(obj, proxy);
215
+ originalCache.set(proxy, obj);
216
+ return proxy;
145
217
  }
146
218
  function createState(initialValue, onChange) {
147
219
  let currentValue = initialValue;
@@ -189,6 +261,7 @@ var ReactiveDebug = {
189
261
  };
190
262
  function reactiveWithDebug(obj, onChange, debugName) {
191
263
  const name = debugName || obj.constructor.name || "Unknown";
264
+ const isArray = Array.isArray(obj);
192
265
  return new Proxy(obj, {
193
266
  set(target, key, value) {
194
267
  const oldValue = target[key];
@@ -204,7 +277,28 @@ function reactiveWithDebug(obj, onChange, debugName) {
204
277
  return true;
205
278
  },
206
279
  get(target, key) {
207
- return target[key];
280
+ if (key === "toJSON") {
281
+ return function() {
282
+ return unwrapProxy(obj);
283
+ };
284
+ }
285
+ const value = target[key];
286
+ if (isArray && typeof key === "string" && ARRAY_MUTATION_METHODS.includes(key)) {
287
+ return function(...args) {
288
+ ReactiveDebug.log(`Array mutation in ${name}:`, {
289
+ method: key,
290
+ args
291
+ });
292
+ const arrayMethod = Array.prototype[key];
293
+ const result = arrayMethod.apply(target, args);
294
+ scheduler.schedule(onChange);
295
+ return result;
296
+ };
297
+ }
298
+ if (value != null && typeof value === "object") {
299
+ return reactiveWithDebug(value, onChange, `${name}.${String(key)}`);
300
+ }
301
+ return value;
208
302
  }
209
303
  });
210
304
  }
@@ -216,6 +310,43 @@ var BaseComponent = class extends HTMLElement {
216
310
  this.connected = false;
217
311
  this._isDebugEnabled = false;
218
312
  this._reactiveStates = /* @__PURE__ */ new Map();
313
+ /**
314
+ * 当前捕获的焦点状态(用于在 render 时使用捕获的值)
315
+ * @internal - 由 rerender() 方法管理
316
+ */
317
+ this._pendingFocusState = null;
318
+ /**
319
+ * 防抖定时器,用于延迟重渲染(当用户正在输入时)
320
+ * @internal
321
+ */
322
+ this._rerenderDebounceTimer = null;
323
+ /**
324
+ * 待处理的重渲染标志(当用户正在输入时,标记需要重渲染但延迟执行)
325
+ * @internal
326
+ */
327
+ this._pendingRerender = false;
328
+ /**
329
+ * 处理 blur 事件,在用户停止输入时执行待处理的重渲染
330
+ * @internal
331
+ */
332
+ this.handleGlobalBlur = (event) => {
333
+ const root = this.getActiveRoot();
334
+ const target = event.target;
335
+ if (target && root.contains(target)) {
336
+ if (this._pendingRerender && this.connected) {
337
+ if (this._rerenderDebounceTimer !== null) {
338
+ clearTimeout(this._rerenderDebounceTimer);
339
+ this._rerenderDebounceTimer = null;
340
+ }
341
+ requestAnimationFrame(() => {
342
+ if (this._pendingRerender && this.connected) {
343
+ this._pendingRerender = false;
344
+ this.rerender();
345
+ }
346
+ });
347
+ }
348
+ }
349
+ };
219
350
  this._isDebugEnabled = config.debug ?? false;
220
351
  const host = this;
221
352
  const originalStyles = config.styles;
@@ -279,11 +410,65 @@ var BaseComponent = class extends HTMLElement {
279
410
  /**
280
411
  * 调度重渲染
281
412
  * 这个方法被响应式系统调用,开发者通常不需要直接调用
413
+ * 使用 queueMicrotask 进行异步调度,与 reactive() 系统保持一致
282
414
  */
283
415
  scheduleRerender() {
284
- if (this.connected) {
285
- this.rerender();
416
+ if (!this.connected) {
417
+ if (this._rerenderDebounceTimer !== null) {
418
+ clearTimeout(this._rerenderDebounceTimer);
419
+ this._rerenderDebounceTimer = null;
420
+ }
421
+ return;
422
+ }
423
+ const root = this.getActiveRoot();
424
+ let activeElement = null;
425
+ if (root instanceof ShadowRoot) {
426
+ activeElement = root.activeElement;
427
+ } else {
428
+ const docActiveElement = document.activeElement;
429
+ if (docActiveElement && root.contains(docActiveElement)) {
430
+ activeElement = docActiveElement;
431
+ }
432
+ }
433
+ if (activeElement) {
434
+ const isInputElement = activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement || activeElement instanceof HTMLSelectElement || activeElement.hasAttribute("contenteditable");
435
+ const forceRender = activeElement.hasAttribute("data-wsx-force-render");
436
+ if (isInputElement && !forceRender) {
437
+ this._pendingRerender = true;
438
+ if (this._rerenderDebounceTimer !== null) {
439
+ clearTimeout(this._rerenderDebounceTimer);
440
+ this._rerenderDebounceTimer = null;
441
+ }
442
+ return;
443
+ }
444
+ }
445
+ if (this._pendingRerender) {
446
+ this._pendingRerender = false;
447
+ }
448
+ queueMicrotask(() => {
449
+ if (this.connected) {
450
+ this.rerender();
451
+ }
452
+ });
453
+ }
454
+ /**
455
+ * 清理资源(在组件断开连接时调用)
456
+ * @internal
457
+ */
458
+ cleanup() {
459
+ if (this._rerenderDebounceTimer !== null) {
460
+ clearTimeout(this._rerenderDebounceTimer);
461
+ this._rerenderDebounceTimer = null;
286
462
  }
463
+ document.removeEventListener("blur", this.handleGlobalBlur, true);
464
+ this._pendingRerender = false;
465
+ }
466
+ /**
467
+ * 初始化事件监听器(在组件连接时调用)
468
+ * @internal
469
+ */
470
+ initializeEventListeners() {
471
+ document.addEventListener("blur", this.handleGlobalBlur, true);
287
472
  }
288
473
  /**
289
474
  * 获取配置值
@@ -346,15 +531,136 @@ var BaseComponent = class extends HTMLElement {
346
531
  cleanupReactiveStates() {
347
532
  this._reactiveStates.clear();
348
533
  }
534
+ /**
535
+ * 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
536
+ * @returns 活动的 DOM 根元素
537
+ */
538
+ getActiveRoot() {
539
+ if ("shadowRoot" in this && this.shadowRoot) {
540
+ return this.shadowRoot;
541
+ }
542
+ return this;
543
+ }
544
+ /**
545
+ * 捕获当前焦点状态(在重渲染之前调用)
546
+ * @returns 焦点状态,如果没有焦点元素则返回 null
547
+ */
548
+ captureFocusState() {
549
+ const root = this.getActiveRoot();
550
+ let activeElement = null;
551
+ if (root instanceof ShadowRoot) {
552
+ activeElement = root.activeElement;
553
+ } else {
554
+ const docActiveElement = document.activeElement;
555
+ if (docActiveElement && root.contains(docActiveElement)) {
556
+ activeElement = docActiveElement;
557
+ }
558
+ }
559
+ if (!activeElement || !(activeElement instanceof HTMLElement)) {
560
+ return null;
561
+ }
562
+ const key = activeElement.getAttribute("data-wsx-key");
563
+ if (!key) {
564
+ return null;
565
+ }
566
+ const tagName = activeElement.tagName.toLowerCase();
567
+ const state2 = {
568
+ key,
569
+ elementType: tagName
570
+ };
571
+ if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
572
+ state2.value = activeElement.value;
573
+ state2.selectionStart = activeElement.selectionStart ?? void 0;
574
+ state2.selectionEnd = activeElement.selectionEnd ?? void 0;
575
+ if (activeElement instanceof HTMLTextAreaElement) {
576
+ state2.scrollTop = activeElement.scrollTop;
577
+ }
578
+ } else if (activeElement instanceof HTMLSelectElement) {
579
+ state2.elementType = "select";
580
+ state2.selectedIndex = activeElement.selectedIndex;
581
+ } else if (activeElement.hasAttribute("contenteditable")) {
582
+ state2.elementType = "contenteditable";
583
+ const selection = window.getSelection();
584
+ if (selection && selection.rangeCount > 0) {
585
+ const range = selection.getRangeAt(0);
586
+ state2.selectionStart = range.startOffset;
587
+ state2.selectionEnd = range.endOffset;
588
+ }
589
+ }
590
+ return state2;
591
+ }
592
+ /**
593
+ * 恢复焦点状态(在重渲染之后调用)
594
+ * @param state - 之前捕获的焦点状态
595
+ */
596
+ restoreFocusState(state2) {
597
+ if (!state2 || !state2.key) {
598
+ return;
599
+ }
600
+ const root = this.getActiveRoot();
601
+ const target = root.querySelector(`[data-wsx-key="${state2.key}"]`);
602
+ if (!target) {
603
+ return;
604
+ }
605
+ if (state2.value !== void 0) {
606
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
607
+ target.value = state2.value;
608
+ }
609
+ }
610
+ if (state2.selectedIndex !== void 0 && target instanceof HTMLSelectElement) {
611
+ target.selectedIndex = state2.selectedIndex;
612
+ }
613
+ requestAnimationFrame(() => {
614
+ const currentTarget = root.querySelector(
615
+ `[data-wsx-key="${state2.key}"]`
616
+ );
617
+ if (!currentTarget) {
618
+ return;
619
+ }
620
+ if (state2.value !== void 0) {
621
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
622
+ if (currentTarget.value !== state2.value) {
623
+ currentTarget.value = state2.value;
624
+ }
625
+ }
626
+ }
627
+ currentTarget.focus({ preventScroll: true });
628
+ if (state2.selectionStart !== void 0) {
629
+ if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
630
+ const start = state2.selectionStart;
631
+ const end = state2.selectionEnd ?? start;
632
+ currentTarget.setSelectionRange(start, end);
633
+ if (state2.scrollTop !== void 0 && currentTarget instanceof HTMLTextAreaElement) {
634
+ currentTarget.scrollTop = state2.scrollTop;
635
+ }
636
+ } else if (currentTarget.hasAttribute("contenteditable")) {
637
+ const selection = window.getSelection();
638
+ if (selection) {
639
+ const range = document.createRange();
640
+ const textNode = currentTarget.childNodes[0];
641
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
642
+ const maxPos = Math.min(
643
+ state2.selectionStart,
644
+ textNode.textContent?.length || 0
645
+ );
646
+ range.setStart(textNode, maxPos);
647
+ range.setEnd(textNode, state2.selectionEnd ?? maxPos);
648
+ selection.removeAllRanges();
649
+ selection.addRange(range);
650
+ }
651
+ }
652
+ }
653
+ }
654
+ });
655
+ }
349
656
  };
350
657
 
351
658
  // src/web-component.ts
659
+ var logger3 = createLogger("WebComponent");
352
660
  var WebComponent = class extends BaseComponent {
661
+ // Initialized by BaseComponent constructor
353
662
  constructor(config = {}) {
354
663
  super(config);
355
- // Initialized by BaseComponent constructor
356
- this._preserveFocus = true;
357
- this._preserveFocus = config.preserveFocus ?? true;
358
664
  this.attachShadow({ mode: "open" });
359
665
  }
360
666
  /**
@@ -363,16 +669,17 @@ var WebComponent = class extends BaseComponent {
363
669
  connectedCallback() {
364
670
  this.connected = true;
365
671
  try {
366
- const stylesToApply = this._getAutoStyles?.() || this.config.styles;
672
+ const stylesToApply = this._autoStyles || this.config.styles;
367
673
  if (stylesToApply) {
368
674
  const styleName = this.config.styleName || this.constructor.name;
369
675
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
370
676
  }
371
677
  const content = this.render();
372
678
  this.shadowRoot.appendChild(content);
679
+ this.initializeEventListeners();
373
680
  this.onConnected?.();
374
681
  } catch (error) {
375
- console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
682
+ logger3.error(`Error in connectedCallback:`, error);
376
683
  this.renderError(error);
377
684
  }
378
685
  }
@@ -381,6 +688,7 @@ var WebComponent = class extends BaseComponent {
381
688
  */
382
689
  disconnectedCallback() {
383
690
  this.connected = false;
691
+ this.cleanup();
384
692
  this.onDisconnected?.();
385
693
  }
386
694
  /**
@@ -406,118 +714,48 @@ var WebComponent = class extends BaseComponent {
406
714
  */
407
715
  rerender() {
408
716
  if (!this.connected) {
409
- console.warn(
410
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
411
- );
717
+ logger3.warn("Component is not connected, skipping rerender.");
412
718
  return;
413
719
  }
414
- let focusData = null;
415
- if (this._preserveFocus && this.shadowRoot) {
416
- const activeElement = this.shadowRoot.activeElement;
417
- focusData = this.saveFocusState(activeElement);
418
- }
720
+ const focusState = this.captureFocusState();
721
+ this._pendingFocusState = focusState;
419
722
  const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
420
- this.shadowRoot.innerHTML = "";
421
- if (this.shadowRoot.adoptedStyleSheets) {
422
- this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
423
- }
424
- if (adoptedStyleSheets.length === 0) {
425
- const stylesToApply = this._autoStyles || this.config.styles;
426
- if (stylesToApply) {
427
- const styleName = this.config.styleName || this.constructor.name;
428
- StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
429
- }
430
- }
431
723
  try {
432
- const content = this.render();
433
- this.shadowRoot.appendChild(content);
434
- } catch (error) {
435
- console.error(`[${this.constructor.name}] Error in rerender:`, error);
436
- this.renderError(error);
437
- }
438
- if (this._preserveFocus && focusData && this.shadowRoot) {
439
- this.restoreFocusState(focusData);
440
- }
441
- }
442
- /**
443
- * 保存焦点状态
444
- */
445
- saveFocusState(activeElement) {
446
- if (!activeElement) {
447
- return null;
448
- }
449
- const focusData = {
450
- tagName: activeElement.tagName.toLowerCase(),
451
- className: activeElement.className
452
- };
453
- if (activeElement.hasAttribute("contenteditable")) {
454
- const selection = window.getSelection();
455
- if (selection && selection.rangeCount > 0) {
456
- const range = selection.getRangeAt(0);
457
- focusData.selectionStart = range.startOffset;
458
- focusData.selectionEnd = range.endOffset;
459
- }
460
- }
461
- if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
462
- focusData.value = activeElement.value;
463
- if ("selectionStart" in activeElement) {
464
- focusData.selectionStart = activeElement.selectionStart ?? void 0;
465
- focusData.selectionEnd = activeElement.selectionEnd ?? void 0;
724
+ if (adoptedStyleSheets.length === 0) {
725
+ const stylesToApply = this._autoStyles || this.config.styles;
726
+ if (stylesToApply) {
727
+ const styleName = this.config.styleName || this.constructor.name;
728
+ StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
729
+ }
466
730
  }
467
- }
468
- return focusData;
469
- }
470
- /**
471
- * 恢复焦点状态
472
- */
473
- restoreFocusState(focusData) {
474
- if (!focusData) return;
475
- try {
476
- let targetElement = null;
477
- if (focusData.className) {
478
- targetElement = this.shadowRoot.querySelector(
479
- `.${focusData.className.split(" ")[0]}`
731
+ const content = this.render();
732
+ if (focusState && focusState.key && focusState.value !== void 0) {
733
+ const target = content.querySelector(
734
+ `[data-wsx-key="${focusState.key}"]`
480
735
  );
481
- }
482
- if (!targetElement) {
483
- targetElement = this.shadowRoot.querySelector(focusData.tagName);
484
- }
485
- if (targetElement) {
486
- targetElement.focus({ preventScroll: true });
487
- if (focusData.selectionStart !== void 0) {
488
- if (targetElement instanceof HTMLInputElement) {
489
- targetElement.setSelectionRange(
490
- focusData.selectionStart,
491
- focusData.selectionEnd ?? focusData.selectionStart
492
- );
493
- } else if (targetElement instanceof HTMLSelectElement) {
494
- targetElement.value = focusData.value ?? "";
495
- } else if (targetElement.hasAttribute("contenteditable")) {
496
- this.setCursorPosition(targetElement, focusData.selectionStart);
736
+ if (target) {
737
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
738
+ target.value = focusState.value;
497
739
  }
498
740
  }
499
741
  }
500
- } catch {
501
- }
502
- }
503
- /**
504
- * 设置光标位置
505
- */
506
- setCursorPosition(element, position) {
507
- try {
508
- const selection = window.getSelection();
509
- if (selection) {
510
- const range = document.createRange();
511
- const textNode = element.childNodes[0];
512
- if (textNode) {
513
- const maxPos = Math.min(position, textNode.textContent?.length || 0);
514
- range.setStart(textNode, maxPos);
515
- range.setEnd(textNode, maxPos);
516
- selection.removeAllRanges();
517
- selection.addRange(range);
518
- }
742
+ if (this.shadowRoot.adoptedStyleSheets) {
743
+ this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
519
744
  }
520
- } catch {
745
+ requestAnimationFrame(() => {
746
+ this.shadowRoot.appendChild(content);
747
+ const oldChildren = Array.from(this.shadowRoot.children).filter(
748
+ (child) => child !== content
749
+ );
750
+ oldChildren.forEach((child) => child.remove());
751
+ requestAnimationFrame(() => {
752
+ this.restoreFocusState(focusState);
753
+ this._pendingFocusState = null;
754
+ });
755
+ });
756
+ } catch (error) {
757
+ logger3.error("Error in rerender:", error);
758
+ this.renderError(error);
521
759
  }
522
760
  }
523
761
  /**
@@ -542,7 +780,7 @@ var WebComponent = class extends BaseComponent {
542
780
  };
543
781
 
544
782
  // src/light-component.ts
545
- var logger3 = createLogger("LightComponent");
783
+ var logger4 = createLogger("LightComponent");
546
784
  var LightComponent = class extends BaseComponent {
547
785
  // Initialized by BaseComponent constructor
548
786
  constructor(config = {}) {
@@ -561,9 +799,10 @@ var LightComponent = class extends BaseComponent {
561
799
  }
562
800
  const content = this.render();
563
801
  this.appendChild(content);
802
+ this.initializeEventListeners();
564
803
  this.onConnected?.();
565
804
  } catch (error) {
566
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
805
+ logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
567
806
  this.renderError(error);
568
807
  }
569
808
  }
@@ -571,6 +810,8 @@ var LightComponent = class extends BaseComponent {
571
810
  * Web Component生命周期:从DOM断开
572
811
  */
573
812
  disconnectedCallback() {
813
+ this.connected = false;
814
+ this.cleanup();
574
815
  this.cleanupReactiveStates();
575
816
  this.cleanupStyles();
576
817
  this.onDisconnected?.();
@@ -598,32 +839,67 @@ var LightComponent = class extends BaseComponent {
598
839
  */
599
840
  rerender() {
600
841
  if (!this.connected) {
601
- logger3.warn(
842
+ logger4.warn(
602
843
  `[${this.constructor.name}] Component is not connected, skipping rerender.`
603
844
  );
604
845
  return;
605
846
  }
606
- this.innerHTML = "";
607
- if (this.config.styles) {
608
- const styleName = this.config.styleName || this.constructor.name;
609
- const styleElement = document.createElement("style");
610
- styleElement.setAttribute("data-wsx-light-component", styleName);
611
- styleElement.textContent = this.config.styles;
612
- this.appendChild(styleElement);
613
- }
847
+ const focusState = this.captureFocusState();
848
+ this._pendingFocusState = focusState;
614
849
  try {
615
850
  const content = this.render();
616
- this.appendChild(content);
617
- if (this.config.styles && this.children.length > 1) {
618
- const styleElement = this.querySelector(
619
- `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
851
+ if (focusState && focusState.key && focusState.value !== void 0) {
852
+ const target = content.querySelector(
853
+ `[data-wsx-key="${focusState.key}"]`
620
854
  );
621
- if (styleElement && styleElement !== this.firstChild) {
855
+ if (target) {
856
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
857
+ target.value = focusState.value;
858
+ }
859
+ }
860
+ }
861
+ const stylesToApply = this._autoStyles || this.config.styles;
862
+ if (stylesToApply) {
863
+ const styleName = this.config.styleName || this.constructor.name;
864
+ let styleElement = this.querySelector(
865
+ `style[data-wsx-light-component="${styleName}"]`
866
+ );
867
+ if (!styleElement) {
868
+ styleElement = document.createElement("style");
869
+ styleElement.setAttribute("data-wsx-light-component", styleName);
870
+ styleElement.textContent = stylesToApply;
622
871
  this.insertBefore(styleElement, this.firstChild);
872
+ } else if (styleElement.textContent !== stylesToApply) {
873
+ styleElement.textContent = stylesToApply;
623
874
  }
624
875
  }
876
+ requestAnimationFrame(() => {
877
+ this.appendChild(content);
878
+ const oldChildren = Array.from(this.children).filter((child) => {
879
+ if (child === content) {
880
+ return false;
881
+ }
882
+ if (stylesToApply && child instanceof HTMLStyleElement && child.getAttribute("data-wsx-light-component") === (this.config.styleName || this.constructor.name)) {
883
+ return false;
884
+ }
885
+ return true;
886
+ });
887
+ oldChildren.forEach((child) => child.remove());
888
+ if (stylesToApply && this.children.length > 1) {
889
+ const styleElement = this.querySelector(
890
+ `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
891
+ );
892
+ if (styleElement && styleElement !== this.firstChild) {
893
+ this.insertBefore(styleElement, this.firstChild);
894
+ }
895
+ }
896
+ requestAnimationFrame(() => {
897
+ this.restoreFocusState(focusState);
898
+ this._pendingFocusState = null;
899
+ });
900
+ });
625
901
  } catch (error) {
626
- logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
902
+ logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
627
903
  this.renderError(error);
628
904
  }
629
905
  }
@@ -705,7 +981,21 @@ function state(target, propertyKey) {
705
981
  const propertyKeyStr = String(propertyKey);
706
982
  if (propertyKeyStr === "[object Object]") {
707
983
  throw new Error(
708
- `@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`
984
+ `@state decorator: Invalid propertyKey detected.
985
+
986
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
987
+
988
+ To fix this, please:
989
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
990
+ 2. Configure it in vite.config.ts:
991
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
992
+ export default defineConfig({ plugins: [wsx()] });
993
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
994
+ npm install --save-dev @wsxjs/wsx-tsconfig
995
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
996
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
997
+
998
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
709
999
  );
710
1000
  }
711
1001
  normalizedPropertyKey = propertyKeyStr;
@@ -713,7 +1003,21 @@ function state(target, propertyKey) {
713
1003
  if (target == null) {
714
1004
  const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
715
1005
  throw new Error(
716
- `@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}. Please ensure Babel plugin is configured in vite.config.ts`
1006
+ `@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
1007
+
1008
+ The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
1009
+
1010
+ To fix this, please:
1011
+ 1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
1012
+ 2. Configure it in vite.config.ts:
1013
+ import { wsx } from '@wsxjs/wsx-vite-plugin';
1014
+ export default defineConfig({ plugins: [wsx()] });
1015
+ 3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
1016
+ npm install --save-dev @wsxjs/wsx-tsconfig
1017
+ Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
1018
+ Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
1019
+
1020
+ See: https://github.com/wsxjs/wsxjs#setup for more details.`
717
1021
  );
718
1022
  }
719
1023
  if (typeof target !== "object") {