@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.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,59 @@ 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 hasActiveElement = false;
|
|
425
|
+
if (root instanceof ShadowRoot) {
|
|
426
|
+
hasActiveElement = root.activeElement !== null;
|
|
427
|
+
} else {
|
|
428
|
+
const docActiveElement = document.activeElement;
|
|
429
|
+
hasActiveElement = docActiveElement !== null && root.contains(docActiveElement);
|
|
430
|
+
}
|
|
431
|
+
if (hasActiveElement) {
|
|
432
|
+
this._pendingRerender = true;
|
|
433
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
434
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
435
|
+
this._rerenderDebounceTimer = null;
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (this._pendingRerender) {
|
|
440
|
+
this._pendingRerender = false;
|
|
441
|
+
}
|
|
442
|
+
queueMicrotask(() => {
|
|
443
|
+
if (this.connected) {
|
|
444
|
+
this.rerender();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 清理资源(在组件断开连接时调用)
|
|
450
|
+
* @internal
|
|
451
|
+
*/
|
|
452
|
+
cleanup() {
|
|
453
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
454
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
455
|
+
this._rerenderDebounceTimer = null;
|
|
286
456
|
}
|
|
457
|
+
document.removeEventListener("blur", this.handleGlobalBlur, true);
|
|
458
|
+
this._pendingRerender = false;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 初始化事件监听器(在组件连接时调用)
|
|
462
|
+
* @internal
|
|
463
|
+
*/
|
|
464
|
+
initializeEventListeners() {
|
|
465
|
+
document.addEventListener("blur", this.handleGlobalBlur, true);
|
|
287
466
|
}
|
|
288
467
|
/**
|
|
289
468
|
* 获取配置值
|
|
@@ -346,15 +525,136 @@ var BaseComponent = class extends HTMLElement {
|
|
|
346
525
|
cleanupReactiveStates() {
|
|
347
526
|
this._reactiveStates.clear();
|
|
348
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
|
|
530
|
+
* @returns 活动的 DOM 根元素
|
|
531
|
+
*/
|
|
532
|
+
getActiveRoot() {
|
|
533
|
+
if ("shadowRoot" in this && this.shadowRoot) {
|
|
534
|
+
return this.shadowRoot;
|
|
535
|
+
}
|
|
536
|
+
return this;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* 捕获当前焦点状态(在重渲染之前调用)
|
|
540
|
+
* @returns 焦点状态,如果没有焦点元素则返回 null
|
|
541
|
+
*/
|
|
542
|
+
captureFocusState() {
|
|
543
|
+
const root = this.getActiveRoot();
|
|
544
|
+
let activeElement = null;
|
|
545
|
+
if (root instanceof ShadowRoot) {
|
|
546
|
+
activeElement = root.activeElement;
|
|
547
|
+
} else {
|
|
548
|
+
const docActiveElement = document.activeElement;
|
|
549
|
+
if (docActiveElement && root.contains(docActiveElement)) {
|
|
550
|
+
activeElement = docActiveElement;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!activeElement || !(activeElement instanceof HTMLElement)) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const key = activeElement.getAttribute("data-wsx-key");
|
|
557
|
+
if (!key) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
561
|
+
const state2 = {
|
|
562
|
+
key,
|
|
563
|
+
elementType: tagName
|
|
564
|
+
};
|
|
565
|
+
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {
|
|
566
|
+
state2.value = activeElement.value;
|
|
567
|
+
state2.selectionStart = activeElement.selectionStart ?? void 0;
|
|
568
|
+
state2.selectionEnd = activeElement.selectionEnd ?? void 0;
|
|
569
|
+
if (activeElement instanceof HTMLTextAreaElement) {
|
|
570
|
+
state2.scrollTop = activeElement.scrollTop;
|
|
571
|
+
}
|
|
572
|
+
} else if (activeElement instanceof HTMLSelectElement) {
|
|
573
|
+
state2.elementType = "select";
|
|
574
|
+
state2.selectedIndex = activeElement.selectedIndex;
|
|
575
|
+
} else if (activeElement.hasAttribute("contenteditable")) {
|
|
576
|
+
state2.elementType = "contenteditable";
|
|
577
|
+
const selection = window.getSelection();
|
|
578
|
+
if (selection && selection.rangeCount > 0) {
|
|
579
|
+
const range = selection.getRangeAt(0);
|
|
580
|
+
state2.selectionStart = range.startOffset;
|
|
581
|
+
state2.selectionEnd = range.endOffset;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return state2;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* 恢复焦点状态(在重渲染之后调用)
|
|
588
|
+
* @param state - 之前捕获的焦点状态
|
|
589
|
+
*/
|
|
590
|
+
restoreFocusState(state2) {
|
|
591
|
+
if (!state2 || !state2.key) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const root = this.getActiveRoot();
|
|
595
|
+
const target = root.querySelector(`[data-wsx-key="${state2.key}"]`);
|
|
596
|
+
if (!target) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (state2.value !== void 0) {
|
|
600
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
601
|
+
target.value = state2.value;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (state2.selectedIndex !== void 0 && target instanceof HTMLSelectElement) {
|
|
605
|
+
target.selectedIndex = state2.selectedIndex;
|
|
606
|
+
}
|
|
607
|
+
requestAnimationFrame(() => {
|
|
608
|
+
const currentTarget = root.querySelector(
|
|
609
|
+
`[data-wsx-key="${state2.key}"]`
|
|
610
|
+
);
|
|
611
|
+
if (!currentTarget) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (state2.value !== void 0) {
|
|
615
|
+
if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
|
|
616
|
+
if (currentTarget.value !== state2.value) {
|
|
617
|
+
currentTarget.value = state2.value;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
currentTarget.focus({ preventScroll: true });
|
|
622
|
+
if (state2.selectionStart !== void 0) {
|
|
623
|
+
if (currentTarget instanceof HTMLInputElement || currentTarget instanceof HTMLTextAreaElement) {
|
|
624
|
+
const start = state2.selectionStart;
|
|
625
|
+
const end = state2.selectionEnd ?? start;
|
|
626
|
+
currentTarget.setSelectionRange(start, end);
|
|
627
|
+
if (state2.scrollTop !== void 0 && currentTarget instanceof HTMLTextAreaElement) {
|
|
628
|
+
currentTarget.scrollTop = state2.scrollTop;
|
|
629
|
+
}
|
|
630
|
+
} else if (currentTarget.hasAttribute("contenteditable")) {
|
|
631
|
+
const selection = window.getSelection();
|
|
632
|
+
if (selection) {
|
|
633
|
+
const range = document.createRange();
|
|
634
|
+
const textNode = currentTarget.childNodes[0];
|
|
635
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
636
|
+
const maxPos = Math.min(
|
|
637
|
+
state2.selectionStart,
|
|
638
|
+
textNode.textContent?.length || 0
|
|
639
|
+
);
|
|
640
|
+
range.setStart(textNode, maxPos);
|
|
641
|
+
range.setEnd(textNode, state2.selectionEnd ?? maxPos);
|
|
642
|
+
selection.removeAllRanges();
|
|
643
|
+
selection.addRange(range);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
349
650
|
};
|
|
350
651
|
|
|
351
652
|
// src/web-component.ts
|
|
653
|
+
var logger3 = createLogger("WebComponent");
|
|
352
654
|
var WebComponent = class extends BaseComponent {
|
|
655
|
+
// Initialized by BaseComponent constructor
|
|
353
656
|
constructor(config = {}) {
|
|
354
657
|
super(config);
|
|
355
|
-
// Initialized by BaseComponent constructor
|
|
356
|
-
this._preserveFocus = true;
|
|
357
|
-
this._preserveFocus = config.preserveFocus ?? true;
|
|
358
658
|
this.attachShadow({ mode: "open" });
|
|
359
659
|
}
|
|
360
660
|
/**
|
|
@@ -363,16 +663,17 @@ var WebComponent = class extends BaseComponent {
|
|
|
363
663
|
connectedCallback() {
|
|
364
664
|
this.connected = true;
|
|
365
665
|
try {
|
|
366
|
-
const stylesToApply = this.
|
|
666
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
367
667
|
if (stylesToApply) {
|
|
368
668
|
const styleName = this.config.styleName || this.constructor.name;
|
|
369
669
|
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
370
670
|
}
|
|
371
671
|
const content = this.render();
|
|
372
672
|
this.shadowRoot.appendChild(content);
|
|
673
|
+
this.initializeEventListeners();
|
|
373
674
|
this.onConnected?.();
|
|
374
675
|
} catch (error) {
|
|
375
|
-
|
|
676
|
+
logger3.error(`Error in connectedCallback:`, error);
|
|
376
677
|
this.renderError(error);
|
|
377
678
|
}
|
|
378
679
|
}
|
|
@@ -381,6 +682,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
381
682
|
*/
|
|
382
683
|
disconnectedCallback() {
|
|
383
684
|
this.connected = false;
|
|
685
|
+
this.cleanup();
|
|
384
686
|
this.onDisconnected?.();
|
|
385
687
|
}
|
|
386
688
|
/**
|
|
@@ -406,118 +708,48 @@ var WebComponent = class extends BaseComponent {
|
|
|
406
708
|
*/
|
|
407
709
|
rerender() {
|
|
408
710
|
if (!this.connected) {
|
|
409
|
-
|
|
410
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
411
|
-
);
|
|
711
|
+
logger3.warn("Component is not connected, skipping rerender.");
|
|
412
712
|
return;
|
|
413
713
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const activeElement = this.shadowRoot.activeElement;
|
|
417
|
-
focusData = this.saveFocusState(activeElement);
|
|
418
|
-
}
|
|
714
|
+
const focusState = this.captureFocusState();
|
|
715
|
+
this._pendingFocusState = focusState;
|
|
419
716
|
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
717
|
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;
|
|
718
|
+
if (adoptedStyleSheets.length === 0) {
|
|
719
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
720
|
+
if (stylesToApply) {
|
|
721
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
722
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
723
|
+
}
|
|
466
724
|
}
|
|
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]}`
|
|
725
|
+
const content = this.render();
|
|
726
|
+
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
727
|
+
const target = content.querySelector(
|
|
728
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
480
729
|
);
|
|
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);
|
|
730
|
+
if (target) {
|
|
731
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
732
|
+
target.value = focusState.value;
|
|
497
733
|
}
|
|
498
734
|
}
|
|
499
735
|
}
|
|
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
|
-
}
|
|
736
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
737
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
519
738
|
}
|
|
520
|
-
|
|
739
|
+
requestAnimationFrame(() => {
|
|
740
|
+
this.shadowRoot.appendChild(content);
|
|
741
|
+
const oldChildren = Array.from(this.shadowRoot.children).filter(
|
|
742
|
+
(child) => child !== content
|
|
743
|
+
);
|
|
744
|
+
oldChildren.forEach((child) => child.remove());
|
|
745
|
+
requestAnimationFrame(() => {
|
|
746
|
+
this.restoreFocusState(focusState);
|
|
747
|
+
this._pendingFocusState = null;
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
} catch (error) {
|
|
751
|
+
logger3.error("Error in rerender:", error);
|
|
752
|
+
this.renderError(error);
|
|
521
753
|
}
|
|
522
754
|
}
|
|
523
755
|
/**
|
|
@@ -542,7 +774,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
542
774
|
};
|
|
543
775
|
|
|
544
776
|
// src/light-component.ts
|
|
545
|
-
var
|
|
777
|
+
var logger4 = createLogger("LightComponent");
|
|
546
778
|
var LightComponent = class extends BaseComponent {
|
|
547
779
|
// Initialized by BaseComponent constructor
|
|
548
780
|
constructor(config = {}) {
|
|
@@ -561,9 +793,10 @@ var LightComponent = class extends BaseComponent {
|
|
|
561
793
|
}
|
|
562
794
|
const content = this.render();
|
|
563
795
|
this.appendChild(content);
|
|
796
|
+
this.initializeEventListeners();
|
|
564
797
|
this.onConnected?.();
|
|
565
798
|
} catch (error) {
|
|
566
|
-
|
|
799
|
+
logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
567
800
|
this.renderError(error);
|
|
568
801
|
}
|
|
569
802
|
}
|
|
@@ -571,6 +804,8 @@ var LightComponent = class extends BaseComponent {
|
|
|
571
804
|
* Web Component生命周期:从DOM断开
|
|
572
805
|
*/
|
|
573
806
|
disconnectedCallback() {
|
|
807
|
+
this.connected = false;
|
|
808
|
+
this.cleanup();
|
|
574
809
|
this.cleanupReactiveStates();
|
|
575
810
|
this.cleanupStyles();
|
|
576
811
|
this.onDisconnected?.();
|
|
@@ -598,32 +833,67 @@ var LightComponent = class extends BaseComponent {
|
|
|
598
833
|
*/
|
|
599
834
|
rerender() {
|
|
600
835
|
if (!this.connected) {
|
|
601
|
-
|
|
836
|
+
logger4.warn(
|
|
602
837
|
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
603
838
|
);
|
|
604
839
|
return;
|
|
605
840
|
}
|
|
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
|
-
}
|
|
841
|
+
const focusState = this.captureFocusState();
|
|
842
|
+
this._pendingFocusState = focusState;
|
|
614
843
|
try {
|
|
615
844
|
const content = this.render();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
845
|
+
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
846
|
+
const target = content.querySelector(
|
|
847
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
620
848
|
);
|
|
621
|
-
if (
|
|
849
|
+
if (target) {
|
|
850
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
851
|
+
target.value = focusState.value;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
856
|
+
if (stylesToApply) {
|
|
857
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
858
|
+
let styleElement = this.querySelector(
|
|
859
|
+
`style[data-wsx-light-component="${styleName}"]`
|
|
860
|
+
);
|
|
861
|
+
if (!styleElement) {
|
|
862
|
+
styleElement = document.createElement("style");
|
|
863
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
864
|
+
styleElement.textContent = stylesToApply;
|
|
622
865
|
this.insertBefore(styleElement, this.firstChild);
|
|
866
|
+
} else if (styleElement.textContent !== stylesToApply) {
|
|
867
|
+
styleElement.textContent = stylesToApply;
|
|
623
868
|
}
|
|
624
869
|
}
|
|
870
|
+
requestAnimationFrame(() => {
|
|
871
|
+
this.appendChild(content);
|
|
872
|
+
const oldChildren = Array.from(this.children).filter((child) => {
|
|
873
|
+
if (child === content) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
if (stylesToApply && child instanceof HTMLStyleElement && child.getAttribute("data-wsx-light-component") === (this.config.styleName || this.constructor.name)) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
return true;
|
|
880
|
+
});
|
|
881
|
+
oldChildren.forEach((child) => child.remove());
|
|
882
|
+
if (stylesToApply && this.children.length > 1) {
|
|
883
|
+
const styleElement = this.querySelector(
|
|
884
|
+
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
885
|
+
);
|
|
886
|
+
if (styleElement && styleElement !== this.firstChild) {
|
|
887
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
requestAnimationFrame(() => {
|
|
891
|
+
this.restoreFocusState(focusState);
|
|
892
|
+
this._pendingFocusState = null;
|
|
893
|
+
});
|
|
894
|
+
});
|
|
625
895
|
} catch (error) {
|
|
626
|
-
|
|
896
|
+
logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
627
897
|
this.renderError(error);
|
|
628
898
|
}
|
|
629
899
|
}
|
|
@@ -705,7 +975,21 @@ function state(target, propertyKey) {
|
|
|
705
975
|
const propertyKeyStr = String(propertyKey);
|
|
706
976
|
if (propertyKeyStr === "[object Object]") {
|
|
707
977
|
throw new Error(
|
|
708
|
-
`@state decorator: Invalid propertyKey
|
|
978
|
+
`@state decorator: Invalid propertyKey detected.
|
|
979
|
+
|
|
980
|
+
The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
|
|
981
|
+
|
|
982
|
+
To fix this, please:
|
|
983
|
+
1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
|
|
984
|
+
2. Configure it in vite.config.ts:
|
|
985
|
+
import { wsx } from '@wsxjs/wsx-vite-plugin';
|
|
986
|
+
export default defineConfig({ plugins: [wsx()] });
|
|
987
|
+
3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
|
|
988
|
+
npm install --save-dev @wsxjs/wsx-tsconfig
|
|
989
|
+
Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
|
|
990
|
+
Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
|
|
991
|
+
|
|
992
|
+
See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
709
993
|
);
|
|
710
994
|
}
|
|
711
995
|
normalizedPropertyKey = propertyKeyStr;
|
|
@@ -713,7 +997,21 @@ function state(target, propertyKey) {
|
|
|
713
997
|
if (target == null) {
|
|
714
998
|
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
715
999
|
throw new Error(
|
|
716
|
-
`@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
|
|
1000
|
+
`@state decorator: Cannot access property "${propertyKeyStr}". Target is ${target === null ? "null" : "undefined"}.
|
|
1001
|
+
|
|
1002
|
+
The @state decorator MUST be processed by Babel plugin at compile time. It appears the Babel plugin is not configured in your build setup.
|
|
1003
|
+
|
|
1004
|
+
To fix this, please:
|
|
1005
|
+
1. Install @wsxjs/wsx-vite-plugin: npm install @wsxjs/wsx-vite-plugin
|
|
1006
|
+
2. Configure it in vite.config.ts:
|
|
1007
|
+
import { wsx } from '@wsxjs/wsx-vite-plugin';
|
|
1008
|
+
export default defineConfig({ plugins: [wsx()] });
|
|
1009
|
+
3. Configure TypeScript (recommended: use @wsxjs/wsx-tsconfig):
|
|
1010
|
+
npm install --save-dev @wsxjs/wsx-tsconfig
|
|
1011
|
+
Then in tsconfig.json: { "extends": "@wsxjs/wsx-tsconfig/tsconfig.base.json" }
|
|
1012
|
+
Or manually: { "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": false } }
|
|
1013
|
+
|
|
1014
|
+
See: https://github.com/wsxjs/wsxjs#setup for more details.`
|
|
717
1015
|
);
|
|
718
1016
|
}
|
|
719
1017
|
if (typeof target !== "object") {
|