@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/chunk-CZII6RG2.mjs +229 -0
- package/dist/index.js +445 -140
- package/dist/index.mjs +439 -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 +312 -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,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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
641
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
963
|
+
if (target) {
|
|
964
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
965
|
+
target.value = focusState.value;
|
|
723
966
|
}
|
|
724
967
|
}
|
|
725
968
|
}
|
|
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
|
-
}
|
|
969
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
970
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
745
971
|
}
|
|
746
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1069
|
+
logger4.warn(
|
|
828
1070
|
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
829
1071
|
);
|
|
830
1072
|
return;
|
|
831
1073
|
}
|
|
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
|
-
}
|
|
1074
|
+
const focusState = this.captureFocusState();
|
|
1075
|
+
this._pendingFocusState = focusState;
|
|
840
1076
|
try {
|
|
841
1077
|
const content = this.render();
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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"}.
|
|
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") {
|