@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/chunk-CZII6RG2.mjs +229 -0
- package/dist/index.js +451 -140
- package/dist/index.mjs +445 -141
- package/dist/jsx-runtime.js +7 -0
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +7 -0
- package/dist/jsx.mjs +1 -1
- package/package.json +1 -1
- package/src/base-component.ts +329 -2
- package/src/jsx-factory.ts +15 -0
- package/src/light-component.ts +89 -23
- package/src/reactive-decorator.ts +33 -6
- package/src/utils/reactive.ts +209 -35
- package/src/web-component.ts +67 -154
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
641
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
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
|
-
|
|
709
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1075
|
+
logger4.warn(
|
|
828
1076
|
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
829
1077
|
);
|
|
830
1078
|
return;
|
|
831
1079
|
}
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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"}.
|
|
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") {
|