@wsxjs/wsx-core 0.0.7 → 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 +665 -487
- package/dist/index.mjs +657 -481
- 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 +549 -0
- package/src/index.ts +2 -4
- package/src/jsx-factory.ts +15 -0
- package/src/light-component.ts +102 -186
- package/src/reactive-decorator.ts +132 -0
- package/src/utils/reactive.ts +209 -35
- package/src/web-component.ts +89 -129
- package/types/index.d.ts +2 -2
- package/src/reactive-component.ts +0 -306
package/dist/index.js
CHANGED
|
@@ -22,22 +22,17 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Fragment: () => Fragment,
|
|
24
24
|
LightComponent: () => LightComponent,
|
|
25
|
-
ReactiveDebug: () => ReactiveDebug,
|
|
26
|
-
ReactiveWebComponent: () => ReactiveWebComponent,
|
|
27
25
|
StyleManager: () => StyleManager,
|
|
28
26
|
WSXLogger: () => WSXLogger,
|
|
29
27
|
WebComponent: () => WebComponent,
|
|
30
28
|
autoRegister: () => autoRegister,
|
|
31
29
|
createLogger: () => createLogger,
|
|
32
|
-
createReactiveComponent: () => createReactiveComponent,
|
|
33
|
-
createState: () => createState,
|
|
34
30
|
h: () => h,
|
|
35
31
|
jsx: () => h,
|
|
36
32
|
jsxs: () => h,
|
|
37
33
|
logger: () => logger,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
registerComponent: () => registerComponent
|
|
34
|
+
registerComponent: () => registerComponent,
|
|
35
|
+
state: () => state
|
|
41
36
|
});
|
|
42
37
|
module.exports = __toCommonJS(index_exports);
|
|
43
38
|
|
|
@@ -182,6 +177,13 @@ function h(tag, props = {}, ...children) {
|
|
|
182
177
|
if (value) {
|
|
183
178
|
element.setAttribute(key, "");
|
|
184
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
|
+
}
|
|
185
187
|
} else {
|
|
186
188
|
const attributeName = isSVG ? getSVGAttributeName(key) : key;
|
|
187
189
|
element.setAttribute(attributeName, String(value));
|
|
@@ -274,172 +276,6 @@ var StyleManager = class {
|
|
|
274
276
|
};
|
|
275
277
|
StyleManager.styleSheets = /* @__PURE__ */ new Map();
|
|
276
278
|
|
|
277
|
-
// src/web-component.ts
|
|
278
|
-
var WebComponent = class extends HTMLElement {
|
|
279
|
-
constructor(config = {}) {
|
|
280
|
-
super();
|
|
281
|
-
this.connected = false;
|
|
282
|
-
this.config = config;
|
|
283
|
-
this.attachShadow({ mode: "open" });
|
|
284
|
-
if (config.styles) {
|
|
285
|
-
const styleName = config.styleName || this.constructor.name;
|
|
286
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, config.styles);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* 子类应该重写这个方法来定义观察的属性
|
|
291
|
-
* @returns 要观察的属性名数组
|
|
292
|
-
*/
|
|
293
|
-
static get observedAttributes() {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Web Component生命周期:连接到DOM
|
|
298
|
-
*/
|
|
299
|
-
connectedCallback() {
|
|
300
|
-
this.connected = true;
|
|
301
|
-
try {
|
|
302
|
-
const content = this.render();
|
|
303
|
-
this.shadowRoot.appendChild(content);
|
|
304
|
-
this.onConnected?.();
|
|
305
|
-
} catch (error) {
|
|
306
|
-
console.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
307
|
-
this.renderError(error);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Web Component生命周期:从DOM断开
|
|
312
|
-
*/
|
|
313
|
-
disconnectedCallback() {
|
|
314
|
-
this.onDisconnected?.();
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Web Component生命周期:属性变化
|
|
318
|
-
*/
|
|
319
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
320
|
-
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* 查找Shadow DOM内的元素
|
|
324
|
-
*
|
|
325
|
-
* @param selector - CSS选择器
|
|
326
|
-
* @returns 元素或null
|
|
327
|
-
*/
|
|
328
|
-
querySelector(selector) {
|
|
329
|
-
return this.shadowRoot.querySelector(selector);
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* 查找Shadow DOM内的所有匹配元素
|
|
333
|
-
*
|
|
334
|
-
* @param selector - CSS选择器
|
|
335
|
-
* @returns 元素列表
|
|
336
|
-
*/
|
|
337
|
-
querySelectorAll(selector) {
|
|
338
|
-
return this.shadowRoot.querySelectorAll(selector);
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* 重新渲染组件
|
|
342
|
-
*/
|
|
343
|
-
rerender() {
|
|
344
|
-
if (!this.connected) {
|
|
345
|
-
console.warn(
|
|
346
|
-
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
347
|
-
);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
351
|
-
this.shadowRoot.innerHTML = "";
|
|
352
|
-
if (this.shadowRoot.adoptedStyleSheets) {
|
|
353
|
-
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
354
|
-
}
|
|
355
|
-
if (adoptedStyleSheets.length === 0 && this.config.styles) {
|
|
356
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
357
|
-
StyleManager.applyStyles(this.shadowRoot, styleName, this.config.styles);
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
const content = this.render();
|
|
361
|
-
this.shadowRoot.appendChild(content);
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
364
|
-
this.renderError(error);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* 渲染错误信息
|
|
369
|
-
*
|
|
370
|
-
* @param error - 错误对象
|
|
371
|
-
*/
|
|
372
|
-
renderError(error) {
|
|
373
|
-
this.shadowRoot.innerHTML = "";
|
|
374
|
-
const errorElement = h(
|
|
375
|
-
"div",
|
|
376
|
-
{
|
|
377
|
-
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
378
|
-
},
|
|
379
|
-
[
|
|
380
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
381
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
382
|
-
]
|
|
383
|
-
);
|
|
384
|
-
this.shadowRoot.appendChild(errorElement);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* 获取配置值
|
|
388
|
-
*
|
|
389
|
-
* @param key - 配置键
|
|
390
|
-
* @param defaultValue - 默认值
|
|
391
|
-
* @returns 配置值
|
|
392
|
-
*/
|
|
393
|
-
getConfig(key, defaultValue) {
|
|
394
|
-
return this.config[key] ?? defaultValue;
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* 设置配置值
|
|
398
|
-
*
|
|
399
|
-
* @param key - 配置键
|
|
400
|
-
* @param value - 配置值
|
|
401
|
-
*/
|
|
402
|
-
setConfig(key, value) {
|
|
403
|
-
this.config[key] = value;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* 获取属性值
|
|
407
|
-
*
|
|
408
|
-
* @param name - 属性名
|
|
409
|
-
* @param defaultValue - 默认值
|
|
410
|
-
* @returns 属性值
|
|
411
|
-
*/
|
|
412
|
-
getAttr(name, defaultValue = "") {
|
|
413
|
-
return this.getAttribute(name) || defaultValue;
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* 设置属性值
|
|
417
|
-
*
|
|
418
|
-
* @param name - 属性名
|
|
419
|
-
* @param value - 属性值
|
|
420
|
-
*/
|
|
421
|
-
setAttr(name, value) {
|
|
422
|
-
this.setAttribute(name, value);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* 移除属性
|
|
426
|
-
*
|
|
427
|
-
* @param name - 属性名
|
|
428
|
-
*/
|
|
429
|
-
removeAttr(name) {
|
|
430
|
-
this.removeAttribute(name);
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* 检查是否有属性
|
|
434
|
-
*
|
|
435
|
-
* @param name - 属性名
|
|
436
|
-
* @returns 是否存在
|
|
437
|
-
*/
|
|
438
|
-
hasAttr(name) {
|
|
439
|
-
return this.hasAttribute(name);
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
|
|
443
279
|
// src/utils/logger.ts
|
|
444
280
|
var WSXLogger = class {
|
|
445
281
|
constructor(prefix = "[WSX]", enabled = true, level = "info") {
|
|
@@ -510,35 +346,107 @@ var UpdateScheduler = class {
|
|
|
510
346
|
try {
|
|
511
347
|
callback();
|
|
512
348
|
} catch (error) {
|
|
513
|
-
|
|
349
|
+
logger2.error("[WSX Reactive] Error in callback:", error);
|
|
514
350
|
}
|
|
515
351
|
});
|
|
516
352
|
}
|
|
517
353
|
};
|
|
518
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
|
+
];
|
|
519
399
|
function reactive(obj, onChange) {
|
|
520
|
-
|
|
400
|
+
if (proxyCache.has(obj)) {
|
|
401
|
+
return proxyCache.get(obj);
|
|
402
|
+
}
|
|
403
|
+
const isArray = Array.isArray(obj);
|
|
404
|
+
const proxy = new Proxy(obj, {
|
|
521
405
|
set(target, key, value) {
|
|
522
406
|
const oldValue = target[key];
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
}
|
|
525
416
|
scheduler.schedule(onChange);
|
|
526
417
|
}
|
|
527
418
|
return true;
|
|
528
419
|
},
|
|
529
420
|
get(target, key) {
|
|
530
|
-
|
|
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;
|
|
531
442
|
},
|
|
532
443
|
has(target, key) {
|
|
533
444
|
return key in target;
|
|
534
|
-
},
|
|
535
|
-
ownKeys(target) {
|
|
536
|
-
return Reflect.ownKeys(target);
|
|
537
|
-
},
|
|
538
|
-
getOwnPropertyDescriptor(target, key) {
|
|
539
|
-
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
540
445
|
}
|
|
541
446
|
});
|
|
447
|
+
proxyCache.set(obj, proxy);
|
|
448
|
+
originalCache.set(proxy, obj);
|
|
449
|
+
return proxy;
|
|
542
450
|
}
|
|
543
451
|
function createState(initialValue, onChange) {
|
|
544
452
|
let currentValue = initialValue;
|
|
@@ -586,6 +494,7 @@ var ReactiveDebug = {
|
|
|
586
494
|
};
|
|
587
495
|
function reactiveWithDebug(obj, onChange, debugName) {
|
|
588
496
|
const name = debugName || obj.constructor.name || "Unknown";
|
|
497
|
+
const isArray = Array.isArray(obj);
|
|
589
498
|
return new Proxy(obj, {
|
|
590
499
|
set(target, key, value) {
|
|
591
500
|
const oldValue = target[key];
|
|
@@ -601,21 +510,89 @@ function reactiveWithDebug(obj, onChange, debugName) {
|
|
|
601
510
|
return true;
|
|
602
511
|
},
|
|
603
512
|
get(target, key) {
|
|
604
|
-
|
|
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;
|
|
605
535
|
}
|
|
606
536
|
});
|
|
607
537
|
}
|
|
608
538
|
|
|
609
|
-
// src/
|
|
610
|
-
var
|
|
611
|
-
var LightComponent = class extends HTMLElement {
|
|
539
|
+
// src/base-component.ts
|
|
540
|
+
var BaseComponent = class extends HTMLElement {
|
|
612
541
|
constructor(config = {}) {
|
|
613
542
|
super();
|
|
614
543
|
this.connected = false;
|
|
615
544
|
this._isDebugEnabled = false;
|
|
616
545
|
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
617
|
-
|
|
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
|
+
};
|
|
618
583
|
this._isDebugEnabled = config.debug ?? false;
|
|
584
|
+
const host = this;
|
|
585
|
+
const originalStyles = config.styles;
|
|
586
|
+
this.config = {
|
|
587
|
+
...config,
|
|
588
|
+
get styles() {
|
|
589
|
+
const result = originalStyles || host._autoStyles || "";
|
|
590
|
+
return result;
|
|
591
|
+
},
|
|
592
|
+
set styles(value) {
|
|
593
|
+
config.styles = value;
|
|
594
|
+
}
|
|
595
|
+
};
|
|
619
596
|
}
|
|
620
597
|
/**
|
|
621
598
|
* 子类应该重写这个方法来定义观察的属性
|
|
@@ -624,56 +601,12 @@ var LightComponent = class extends HTMLElement {
|
|
|
624
601
|
static get observedAttributes() {
|
|
625
602
|
return [];
|
|
626
603
|
}
|
|
627
|
-
/**
|
|
628
|
-
* Web Component生命周期:连接到DOM
|
|
629
|
-
*/
|
|
630
|
-
connectedCallback() {
|
|
631
|
-
this.connected = true;
|
|
632
|
-
try {
|
|
633
|
-
if (this.config.styles) {
|
|
634
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
635
|
-
this.applyScopedStyles(styleName, this.config.styles);
|
|
636
|
-
}
|
|
637
|
-
const content = this.render();
|
|
638
|
-
this.appendChild(content);
|
|
639
|
-
this.onConnected?.();
|
|
640
|
-
} catch (error) {
|
|
641
|
-
logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
642
|
-
this.renderError(error);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Web Component生命周期:从DOM断开
|
|
647
|
-
*/
|
|
648
|
-
disconnectedCallback() {
|
|
649
|
-
this.cleanupReactiveStates();
|
|
650
|
-
this.cleanupStyles();
|
|
651
|
-
this.onDisconnected?.();
|
|
652
|
-
}
|
|
653
604
|
/**
|
|
654
605
|
* Web Component生命周期:属性变化
|
|
655
606
|
*/
|
|
656
607
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
657
608
|
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
658
609
|
}
|
|
659
|
-
/**
|
|
660
|
-
* 查找组件内的元素
|
|
661
|
-
*
|
|
662
|
-
* @param selector - CSS选择器
|
|
663
|
-
* @returns 元素或null
|
|
664
|
-
*/
|
|
665
|
-
querySelector(selector) {
|
|
666
|
-
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* 查找组件内的所有匹配元素
|
|
670
|
-
*
|
|
671
|
-
* @param selector - CSS选择器
|
|
672
|
-
* @returns 元素列表
|
|
673
|
-
*/
|
|
674
|
-
querySelectorAll(selector) {
|
|
675
|
-
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
676
|
-
}
|
|
677
610
|
/**
|
|
678
611
|
* 创建响应式对象
|
|
679
612
|
*
|
|
@@ -696,86 +629,73 @@ var LightComponent = class extends HTMLElement {
|
|
|
696
629
|
useState(key, initialValue) {
|
|
697
630
|
if (!this._reactiveStates.has(key)) {
|
|
698
631
|
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
699
|
-
this._reactiveStates.set(key, {
|
|
632
|
+
this._reactiveStates.set(key, {
|
|
633
|
+
getter,
|
|
634
|
+
setter
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
const state2 = this._reactiveStates.get(key);
|
|
638
|
+
if (!state2) {
|
|
639
|
+
throw new Error(`State ${key} not found`);
|
|
700
640
|
}
|
|
701
|
-
|
|
702
|
-
return [state.getter, state.setter];
|
|
641
|
+
return [state2.getter, state2.setter];
|
|
703
642
|
}
|
|
704
643
|
/**
|
|
705
644
|
* 调度重渲染
|
|
706
645
|
* 这个方法被响应式系统调用,开发者通常不需要直接调用
|
|
646
|
+
* 使用 queueMicrotask 进行异步调度,与 reactive() 系统保持一致
|
|
707
647
|
*/
|
|
708
648
|
scheduleRerender() {
|
|
709
|
-
if (this.connected) {
|
|
710
|
-
this.rerender();
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* 重新渲染组件
|
|
715
|
-
*/
|
|
716
|
-
rerender() {
|
|
717
649
|
if (!this.connected) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
650
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
651
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
652
|
+
this._rerenderDebounceTimer = null;
|
|
653
|
+
}
|
|
721
654
|
return;
|
|
722
655
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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);
|
|
730
663
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
this.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
`style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
|
|
737
|
-
);
|
|
738
|
-
if (styleElement && styleElement !== this.firstChild) {
|
|
739
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
740
|
-
}
|
|
664
|
+
if (hasActiveElement) {
|
|
665
|
+
this._pendingRerender = true;
|
|
666
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
667
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
668
|
+
this._rerenderDebounceTimer = null;
|
|
741
669
|
}
|
|
742
|
-
|
|
743
|
-
logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
744
|
-
this.renderError(error);
|
|
670
|
+
return;
|
|
745
671
|
}
|
|
672
|
+
if (this._pendingRerender) {
|
|
673
|
+
this._pendingRerender = false;
|
|
674
|
+
}
|
|
675
|
+
queueMicrotask(() => {
|
|
676
|
+
if (this.connected) {
|
|
677
|
+
this.rerender();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
746
680
|
}
|
|
747
681
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
* @param error - 错误对象
|
|
682
|
+
* 清理资源(在组件断开连接时调用)
|
|
683
|
+
* @internal
|
|
751
684
|
*/
|
|
752
|
-
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
[
|
|
760
|
-
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
761
|
-
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
762
|
-
]
|
|
763
|
-
);
|
|
764
|
-
this.appendChild(errorElement);
|
|
685
|
+
cleanup() {
|
|
686
|
+
if (this._rerenderDebounceTimer !== null) {
|
|
687
|
+
clearTimeout(this._rerenderDebounceTimer);
|
|
688
|
+
this._rerenderDebounceTimer = null;
|
|
689
|
+
}
|
|
690
|
+
document.removeEventListener("blur", this.handleGlobalBlur, true);
|
|
691
|
+
this._pendingRerender = false;
|
|
765
692
|
}
|
|
766
693
|
/**
|
|
767
|
-
*
|
|
768
|
-
*
|
|
694
|
+
* 初始化事件监听器(在组件连接时调用)
|
|
695
|
+
* @internal
|
|
769
696
|
*/
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (existingStyle) {
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
const styleElement = document.createElement("style");
|
|
776
|
-
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
777
|
-
styleElement.textContent = cssText;
|
|
778
|
-
this.insertBefore(styleElement, this.firstChild);
|
|
697
|
+
initializeEventListeners() {
|
|
698
|
+
document.addEventListener("blur", this.handleGlobalBlur, true);
|
|
779
699
|
}
|
|
780
700
|
/**
|
|
781
701
|
* 获取配置值
|
|
@@ -796,22 +716,6 @@ var LightComponent = class extends HTMLElement {
|
|
|
796
716
|
setConfig(key, value) {
|
|
797
717
|
this.config[key] = value;
|
|
798
718
|
}
|
|
799
|
-
/**
|
|
800
|
-
* 清理响应式状态
|
|
801
|
-
*/
|
|
802
|
-
cleanupReactiveStates() {
|
|
803
|
-
this._reactiveStates.clear();
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* 清理组件样式
|
|
807
|
-
*/
|
|
808
|
-
cleanupStyles() {
|
|
809
|
-
const styleName = this.config.styleName || this.constructor.name;
|
|
810
|
-
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
811
|
-
if (existingStyle) {
|
|
812
|
-
existingStyle.remove();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
719
|
/**
|
|
816
720
|
* 获取属性值
|
|
817
721
|
*
|
|
@@ -848,254 +752,528 @@ var LightComponent = class extends HTMLElement {
|
|
|
848
752
|
hasAttr(name) {
|
|
849
753
|
return this.hasAttribute(name);
|
|
850
754
|
}
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
// src/auto-register.ts
|
|
854
|
-
function autoRegister(options = {}) {
|
|
855
|
-
return function(constructor) {
|
|
856
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
857
|
-
if (!customElements.get(tagName)) {
|
|
858
|
-
customElements.define(tagName, constructor);
|
|
859
|
-
}
|
|
860
|
-
return constructor;
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
function registerComponent(constructor, options = {}) {
|
|
864
|
-
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
865
|
-
if (!customElements.get(tagName)) {
|
|
866
|
-
customElements.define(tagName, constructor);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
function deriveTagName(className, prefix) {
|
|
870
|
-
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
871
|
-
if (!kebabCase.includes("-")) {
|
|
872
|
-
kebabCase = `${kebabCase}-component`;
|
|
873
|
-
}
|
|
874
|
-
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// src/reactive-component.ts
|
|
878
|
-
var ReactiveWebComponent = class extends WebComponent {
|
|
879
|
-
constructor(config = {}) {
|
|
880
|
-
super(config);
|
|
881
|
-
this._isDebugEnabled = false;
|
|
882
|
-
this._preserveFocus = true;
|
|
883
|
-
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
884
|
-
this._isDebugEnabled = config.debug ?? false;
|
|
885
|
-
this._preserveFocus = config.preserveFocus ?? true;
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* 创建响应式对象
|
|
889
|
-
*
|
|
890
|
-
* @param obj 要变为响应式的对象
|
|
891
|
-
* @param debugName 调试名称(可选)
|
|
892
|
-
* @returns 响应式代理对象
|
|
893
|
-
*/
|
|
894
|
-
reactive(obj, debugName) {
|
|
895
|
-
const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
|
|
896
|
-
const name = debugName || `${this.constructor.name}.reactive`;
|
|
897
|
-
return this._isDebugEnabled ? reactiveFn(obj, () => this.scheduleRerender(), name) : reactiveFn(obj, () => this.scheduleRerender());
|
|
898
|
-
}
|
|
899
755
|
/**
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
* @param key 状态标识符
|
|
903
|
-
* @param initialValue 初始值
|
|
904
|
-
* @returns [getter, setter] 元组
|
|
756
|
+
* 清理响应式状态
|
|
905
757
|
*/
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
909
|
-
this._reactiveStates.set(key, { getter, setter });
|
|
910
|
-
}
|
|
911
|
-
const state = this._reactiveStates.get(key);
|
|
912
|
-
return [state.getter, state.setter];
|
|
758
|
+
cleanupReactiveStates() {
|
|
759
|
+
this._reactiveStates.clear();
|
|
913
760
|
}
|
|
914
761
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
762
|
+
* 获取当前活动的 DOM 根(Shadow DOM 或 Light DOM)
|
|
763
|
+
* @returns 活动的 DOM 根元素
|
|
917
764
|
*/
|
|
918
|
-
|
|
919
|
-
if (this.
|
|
920
|
-
this.
|
|
765
|
+
getActiveRoot() {
|
|
766
|
+
if ("shadowRoot" in this && this.shadowRoot) {
|
|
767
|
+
return this.shadowRoot;
|
|
921
768
|
}
|
|
769
|
+
return this;
|
|
922
770
|
}
|
|
923
771
|
/**
|
|
924
|
-
*
|
|
772
|
+
* 捕获当前焦点状态(在重渲染之前调用)
|
|
773
|
+
* @returns 焦点状态,如果没有焦点元素则返回 null
|
|
925
774
|
*/
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
|
|
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
|
+
}
|
|
934
785
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
this.restoreFocusState(focusData);
|
|
786
|
+
if (!activeElement || !(activeElement instanceof HTMLElement)) {
|
|
787
|
+
return null;
|
|
938
788
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
* 保存焦点状态
|
|
942
|
-
*/
|
|
943
|
-
saveFocusState(activeElement) {
|
|
944
|
-
if (!activeElement) {
|
|
789
|
+
const key = activeElement.getAttribute("data-wsx-key");
|
|
790
|
+
if (!key) {
|
|
945
791
|
return null;
|
|
946
792
|
}
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
793
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
794
|
+
const state2 = {
|
|
795
|
+
key,
|
|
796
|
+
elementType: tagName
|
|
950
797
|
};
|
|
951
|
-
if (activeElement
|
|
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";
|
|
952
810
|
const selection = window.getSelection();
|
|
953
811
|
if (selection && selection.rangeCount > 0) {
|
|
954
812
|
const range = selection.getRangeAt(0);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
|
|
960
|
-
focusData.value = activeElement.value;
|
|
961
|
-
if ("selectionStart" in activeElement) {
|
|
962
|
-
focusData.selectionStart = activeElement.selectionStart;
|
|
963
|
-
focusData.selectionEnd = activeElement.selectionEnd;
|
|
813
|
+
state2.selectionStart = range.startOffset;
|
|
814
|
+
state2.selectionEnd = range.endOffset;
|
|
964
815
|
}
|
|
965
816
|
}
|
|
966
|
-
return
|
|
817
|
+
return state2;
|
|
967
818
|
}
|
|
968
819
|
/**
|
|
969
|
-
*
|
|
820
|
+
* 恢复焦点状态(在重渲染之后调用)
|
|
821
|
+
* @param state - 之前捕获的焦点状态
|
|
970
822
|
*/
|
|
971
|
-
restoreFocusState(
|
|
972
|
-
if (!
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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;
|
|
979
835
|
}
|
|
980
|
-
|
|
981
|
-
|
|
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;
|
|
982
846
|
}
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
targetElement.setSelectionRange(
|
|
988
|
-
focusData.selectionStart,
|
|
989
|
-
focusData.selectionEnd
|
|
990
|
-
);
|
|
991
|
-
} else if (targetElement instanceof HTMLSelectElement) {
|
|
992
|
-
targetElement.value = focusData.value;
|
|
993
|
-
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
994
|
-
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
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;
|
|
995
851
|
}
|
|
996
852
|
}
|
|
997
853
|
}
|
|
998
|
-
|
|
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
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/web-component.ts
|
|
886
|
+
var logger3 = createLogger("WebComponent");
|
|
887
|
+
var WebComponent = class extends BaseComponent {
|
|
888
|
+
// Initialized by BaseComponent constructor
|
|
889
|
+
constructor(config = {}) {
|
|
890
|
+
super(config);
|
|
891
|
+
this.attachShadow({ mode: "open" });
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Web Component生命周期:连接到DOM
|
|
895
|
+
*/
|
|
896
|
+
connectedCallback() {
|
|
897
|
+
this.connected = true;
|
|
898
|
+
try {
|
|
899
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
900
|
+
if (stylesToApply) {
|
|
901
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
902
|
+
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
903
|
+
}
|
|
904
|
+
const content = this.render();
|
|
905
|
+
this.shadowRoot.appendChild(content);
|
|
906
|
+
this.initializeEventListeners();
|
|
907
|
+
this.onConnected?.();
|
|
908
|
+
} catch (error) {
|
|
909
|
+
logger3.error(`Error in connectedCallback:`, error);
|
|
910
|
+
this.renderError(error);
|
|
999
911
|
}
|
|
1000
912
|
}
|
|
1001
913
|
/**
|
|
1002
|
-
*
|
|
914
|
+
* Web Component生命周期:从DOM断开
|
|
1003
915
|
*/
|
|
1004
|
-
|
|
916
|
+
disconnectedCallback() {
|
|
917
|
+
this.connected = false;
|
|
918
|
+
this.cleanup();
|
|
919
|
+
this.onDisconnected?.();
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* 查找Shadow DOM内的元素
|
|
923
|
+
*
|
|
924
|
+
* @param selector - CSS选择器
|
|
925
|
+
* @returns 元素或null
|
|
926
|
+
*/
|
|
927
|
+
querySelector(selector) {
|
|
928
|
+
return this.shadowRoot.querySelector(selector);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* 查找Shadow DOM内的所有匹配元素
|
|
932
|
+
*
|
|
933
|
+
* @param selector - CSS选择器
|
|
934
|
+
* @returns 元素列表
|
|
935
|
+
*/
|
|
936
|
+
querySelectorAll(selector) {
|
|
937
|
+
return this.shadowRoot.querySelectorAll(selector);
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* 重新渲染组件
|
|
941
|
+
*/
|
|
942
|
+
rerender() {
|
|
943
|
+
if (!this.connected) {
|
|
944
|
+
logger3.warn("Component is not connected, skipping rerender.");
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const focusState = this.captureFocusState();
|
|
948
|
+
this._pendingFocusState = focusState;
|
|
949
|
+
const adoptedStyleSheets = this.shadowRoot.adoptedStyleSheets || [];
|
|
1005
950
|
try {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const maxPos = Math.min(position, textNode.textContent?.length || 0);
|
|
1012
|
-
range.setStart(textNode, maxPos);
|
|
1013
|
-
range.setEnd(textNode, maxPos);
|
|
1014
|
-
selection.removeAllRanges();
|
|
1015
|
-
selection.addRange(range);
|
|
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);
|
|
1016
956
|
}
|
|
1017
957
|
}
|
|
1018
|
-
|
|
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}"]`
|
|
962
|
+
);
|
|
963
|
+
if (target) {
|
|
964
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
965
|
+
target.value = focusState.value;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (this.shadowRoot.adoptedStyleSheets) {
|
|
970
|
+
this.shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
971
|
+
}
|
|
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);
|
|
1019
986
|
}
|
|
1020
987
|
}
|
|
1021
988
|
/**
|
|
1022
|
-
*
|
|
989
|
+
* 渲染错误信息
|
|
990
|
+
*
|
|
991
|
+
* @param error - 错误对象
|
|
1023
992
|
*/
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
993
|
+
renderError(error) {
|
|
994
|
+
this.shadowRoot.innerHTML = "";
|
|
995
|
+
const errorElement = h(
|
|
996
|
+
"div",
|
|
997
|
+
{
|
|
998
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
999
|
+
},
|
|
1000
|
+
[
|
|
1001
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
1002
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
1003
|
+
]
|
|
1004
|
+
);
|
|
1005
|
+
this.shadowRoot.appendChild(errorElement);
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
// src/light-component.ts
|
|
1010
|
+
var logger4 = createLogger("LightComponent");
|
|
1011
|
+
var LightComponent = class extends BaseComponent {
|
|
1012
|
+
// Initialized by BaseComponent constructor
|
|
1013
|
+
constructor(config = {}) {
|
|
1014
|
+
super(config);
|
|
1030
1015
|
}
|
|
1031
1016
|
/**
|
|
1032
|
-
*
|
|
1017
|
+
* Web Component生命周期:连接到DOM
|
|
1033
1018
|
*/
|
|
1034
|
-
|
|
1035
|
-
this.
|
|
1019
|
+
connectedCallback() {
|
|
1020
|
+
this.connected = true;
|
|
1021
|
+
try {
|
|
1022
|
+
const stylesToApply = this._autoStyles || this.config.styles;
|
|
1023
|
+
if (stylesToApply) {
|
|
1024
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
1025
|
+
this.applyScopedStyles(styleName, stylesToApply);
|
|
1026
|
+
}
|
|
1027
|
+
const content = this.render();
|
|
1028
|
+
this.appendChild(content);
|
|
1029
|
+
this.initializeEventListeners();
|
|
1030
|
+
this.onConnected?.();
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
1033
|
+
this.renderError(error);
|
|
1034
|
+
}
|
|
1036
1035
|
}
|
|
1037
1036
|
/**
|
|
1038
|
-
*
|
|
1037
|
+
* Web Component生命周期:从DOM断开
|
|
1039
1038
|
*/
|
|
1040
1039
|
disconnectedCallback() {
|
|
1041
|
-
|
|
1040
|
+
this.connected = false;
|
|
1041
|
+
this.cleanup();
|
|
1042
1042
|
this.cleanupReactiveStates();
|
|
1043
|
+
this.cleanupStyles();
|
|
1044
|
+
this.onDisconnected?.();
|
|
1043
1045
|
}
|
|
1044
1046
|
/**
|
|
1045
|
-
*
|
|
1047
|
+
* 查找组件内的元素
|
|
1048
|
+
*
|
|
1049
|
+
* @param selector - CSS选择器
|
|
1050
|
+
* @returns 元素或null
|
|
1046
1051
|
*/
|
|
1047
|
-
|
|
1048
|
-
this
|
|
1052
|
+
querySelector(selector) {
|
|
1053
|
+
return HTMLElement.prototype.querySelector.call(this, selector);
|
|
1049
1054
|
}
|
|
1050
1055
|
/**
|
|
1051
|
-
*
|
|
1056
|
+
* 查找组件内的所有匹配元素
|
|
1057
|
+
*
|
|
1058
|
+
* @param selector - CSS选择器
|
|
1059
|
+
* @returns 元素列表
|
|
1052
1060
|
*/
|
|
1053
|
-
|
|
1054
|
-
this
|
|
1061
|
+
querySelectorAll(selector) {
|
|
1062
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector);
|
|
1055
1063
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1064
|
+
/**
|
|
1065
|
+
* 重新渲染组件
|
|
1066
|
+
*/
|
|
1067
|
+
rerender() {
|
|
1068
|
+
if (!this.connected) {
|
|
1069
|
+
logger4.warn(
|
|
1070
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
1071
|
+
);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const focusState = this.captureFocusState();
|
|
1075
|
+
this._pendingFocusState = focusState;
|
|
1076
|
+
try {
|
|
1077
|
+
const content = this.render();
|
|
1078
|
+
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
1079
|
+
const target = content.querySelector(
|
|
1080
|
+
`[data-wsx-key="${focusState.key}"]`
|
|
1081
|
+
);
|
|
1082
|
+
if (target) {
|
|
1083
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
1084
|
+
target.value = focusState.value;
|
|
1085
|
+
}
|
|
1066
1086
|
}
|
|
1067
1087
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
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;
|
|
1098
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
1099
|
+
} else if (styleElement.textContent !== stylesToApply) {
|
|
1100
|
+
styleElement.textContent = stylesToApply;
|
|
1101
|
+
}
|
|
1070
1102
|
}
|
|
1071
|
-
|
|
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
|
+
});
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
logger4.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
1130
|
+
this.renderError(error);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* 渲染错误信息
|
|
1135
|
+
*
|
|
1136
|
+
* @param error - 错误对象
|
|
1137
|
+
*/
|
|
1138
|
+
renderError(error) {
|
|
1139
|
+
this.innerHTML = "";
|
|
1140
|
+
const errorElement = h(
|
|
1141
|
+
"div",
|
|
1142
|
+
{
|
|
1143
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
|
|
1144
|
+
},
|
|
1145
|
+
[
|
|
1146
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
1147
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
|
|
1148
|
+
]
|
|
1149
|
+
);
|
|
1150
|
+
this.appendChild(errorElement);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* 为Light DOM组件应用样式
|
|
1154
|
+
* 直接将样式注入到组件自身,避免全局污染
|
|
1155
|
+
*/
|
|
1156
|
+
applyScopedStyles(styleName, cssText) {
|
|
1157
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
1158
|
+
if (existingStyle) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const styleElement = document.createElement("style");
|
|
1162
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
1163
|
+
styleElement.textContent = cssText;
|
|
1164
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* 清理组件样式
|
|
1168
|
+
*/
|
|
1169
|
+
cleanupStyles() {
|
|
1170
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
1171
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
1172
|
+
if (existingStyle) {
|
|
1173
|
+
existingStyle.remove();
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
// src/auto-register.ts
|
|
1179
|
+
function autoRegister(options = {}) {
|
|
1180
|
+
return function(constructor) {
|
|
1181
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
1182
|
+
if (!customElements.get(tagName)) {
|
|
1183
|
+
customElements.define(tagName, constructor);
|
|
1184
|
+
}
|
|
1185
|
+
return constructor;
|
|
1072
1186
|
};
|
|
1073
1187
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1188
|
+
function registerComponent(constructor, options = {}) {
|
|
1189
|
+
const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
|
|
1190
|
+
if (!customElements.get(tagName)) {
|
|
1191
|
+
customElements.define(tagName, constructor);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function deriveTagName(className, prefix) {
|
|
1195
|
+
let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1196
|
+
if (!kebabCase.includes("-")) {
|
|
1197
|
+
kebabCase = `${kebabCase}-component`;
|
|
1198
|
+
}
|
|
1199
|
+
return prefix ? `${prefix}${kebabCase}` : kebabCase;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/reactive-decorator.ts
|
|
1203
|
+
function state(target, propertyKey) {
|
|
1204
|
+
let normalizedPropertyKey;
|
|
1205
|
+
if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
|
|
1206
|
+
normalizedPropertyKey = propertyKey;
|
|
1207
|
+
} else {
|
|
1208
|
+
const propertyKeyStr = String(propertyKey);
|
|
1209
|
+
if (propertyKeyStr === "[object Object]") {
|
|
1210
|
+
throw new Error(
|
|
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.`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
normalizedPropertyKey = propertyKeyStr;
|
|
1229
|
+
}
|
|
1230
|
+
if (target == null) {
|
|
1231
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
1232
|
+
throw new Error(
|
|
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.`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof target !== "object") {
|
|
1251
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
`@state decorator: Cannot be used on "${propertyKeyStr}". @state is for properties only, not methods.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, normalizedPropertyKey);
|
|
1257
|
+
if (descriptor?.get) {
|
|
1258
|
+
const propertyKeyStr = typeof normalizedPropertyKey === "string" ? normalizedPropertyKey : normalizedPropertyKey.toString();
|
|
1259
|
+
throw new Error(
|
|
1260
|
+
`@state decorator cannot be used with getter properties. Property: "${propertyKeyStr}"`
|
|
1261
|
+
);
|
|
1077
1262
|
}
|
|
1078
|
-
const ReactiveComponent = makeReactive(config?.debug)(ComponentClass);
|
|
1079
|
-
return new ReactiveComponent(config);
|
|
1080
1263
|
}
|
|
1081
1264
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1082
1265
|
0 && (module.exports = {
|
|
1083
1266
|
Fragment,
|
|
1084
1267
|
LightComponent,
|
|
1085
|
-
ReactiveDebug,
|
|
1086
|
-
ReactiveWebComponent,
|
|
1087
1268
|
StyleManager,
|
|
1088
1269
|
WSXLogger,
|
|
1089
1270
|
WebComponent,
|
|
1090
1271
|
autoRegister,
|
|
1091
1272
|
createLogger,
|
|
1092
|
-
createReactiveComponent,
|
|
1093
|
-
createState,
|
|
1094
1273
|
h,
|
|
1095
1274
|
jsx,
|
|
1096
1275
|
jsxs,
|
|
1097
1276
|
logger,
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
registerComponent
|
|
1277
|
+
registerComponent,
|
|
1278
|
+
state
|
|
1101
1279
|
});
|