@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Fragment,
|
|
3
3
|
h
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
415
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
842
|
+
logger4.warn(
|
|
602
843
|
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
603
844
|
);
|
|
604
845
|
return;
|
|
605
846
|
}
|
|
606
|
-
|
|
607
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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"}.
|
|
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") {
|