@wsxjs/wsx-core 0.0.5 → 0.0.6

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.
Files changed (41) hide show
  1. package/dist/{chunk-YNUVFDKT.mjs → chunk-VZQT7HU5.mjs} +4 -4
  2. package/dist/index.js +355 -24
  3. package/dist/index.mjs +355 -25
  4. package/dist/jsx-runtime.js +4 -3
  5. package/dist/jsx-runtime.mjs +1 -1
  6. package/dist/jsx.mjs +1 -1
  7. package/package.json +46 -46
  8. package/src/index.ts +2 -0
  9. package/src/jsx-factory.ts +5 -4
  10. package/src/light-component.ts +328 -0
  11. package/src/reactive-component.ts +135 -0
  12. package/types/index.d.ts +5 -0
  13. package/LICENSE +0 -21
  14. package/dist/chunk-3CJEWYVF.mjs +0 -197
  15. package/dist/chunk-5JVEHB6H.mjs +0 -197
  16. package/dist/chunk-7E7KJQSW.mjs +0 -210
  17. package/dist/chunk-A5GYVTI3.mjs +0 -222
  18. package/dist/chunk-A5GYVTI3.mjs.map +0 -1
  19. package/dist/chunk-K6N3JDTI.mjs +0 -216
  20. package/dist/chunk-RVGKV4GP.mjs +0 -79
  21. package/dist/chunk-S3O776FY.mjs +0 -173
  22. package/dist/chunk-VNK4B3FW.mjs +0 -217
  23. package/dist/chunk-YNUVFDKT.mjs.map +0 -1
  24. package/dist/index.d.mts +0 -235
  25. package/dist/index.d.ts +0 -235
  26. package/dist/index.js.map +0 -1
  27. package/dist/index.mjs.map +0 -1
  28. package/dist/jsx-factory-pFUwL2Dz.d.mts +0 -26
  29. package/dist/jsx-factory-pFUwL2Dz.d.ts +0 -26
  30. package/dist/jsx-pFUwL2Dz.d.mts +0 -26
  31. package/dist/jsx-pFUwL2Dz.d.ts +0 -26
  32. package/dist/jsx-runtime-pFUwL2Dz.d.mts +0 -26
  33. package/dist/jsx-runtime-pFUwL2Dz.d.ts +0 -26
  34. package/dist/jsx-runtime.d.mts +0 -1
  35. package/dist/jsx-runtime.d.ts +0 -1
  36. package/dist/jsx-runtime.js.map +0 -1
  37. package/dist/jsx-runtime.mjs.map +0 -1
  38. package/dist/jsx.d.mts +0 -66
  39. package/dist/jsx.d.ts +0 -66
  40. package/dist/jsx.js.map +0 -1
  41. package/dist/jsx.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  h
4
- } from "./chunk-BV2V6BVN.mjs";
4
+ } from "./chunk-VZQT7HU5.mjs";
5
5
 
6
6
  // src/styles/style-manager.ts
7
7
  var StyleManager = class {
@@ -209,30 +209,6 @@ var WebComponent = class extends HTMLElement {
209
209
  }
210
210
  };
211
211
 
212
- // src/auto-register.ts
213
- function autoRegister(options = {}) {
214
- return function(constructor) {
215
- const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
216
- if (!customElements.get(tagName)) {
217
- customElements.define(tagName, constructor);
218
- }
219
- return constructor;
220
- };
221
- }
222
- function registerComponent(constructor, options = {}) {
223
- const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
224
- if (!customElements.get(tagName)) {
225
- customElements.define(tagName, constructor);
226
- }
227
- }
228
- function deriveTagName(className, prefix) {
229
- let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
230
- if (!kebabCase.includes("-")) {
231
- kebabCase = `${kebabCase}-component`;
232
- }
233
- return prefix ? `${prefix}${kebabCase}` : kebabCase;
234
- }
235
-
236
212
  // src/utils/logger.ts
237
213
  var WSXLogger = class {
238
214
  constructor(prefix = "[WSX]", enabled = true, level = "info") {
@@ -399,13 +375,268 @@ function reactiveWithDebug(obj, onChange, debugName) {
399
375
  });
400
376
  }
401
377
 
378
+ // src/light-component.ts
379
+ var logger3 = createLogger("LightComponent");
380
+ var LightComponent = class extends HTMLElement {
381
+ constructor(config = {}) {
382
+ super();
383
+ this.connected = false;
384
+ this._isDebugEnabled = false;
385
+ this._reactiveStates = /* @__PURE__ */ new Map();
386
+ this.config = config;
387
+ this._isDebugEnabled = config.debug ?? false;
388
+ }
389
+ /**
390
+ * 子类应该重写这个方法来定义观察的属性
391
+ * @returns 要观察的属性名数组
392
+ */
393
+ static get observedAttributes() {
394
+ return [];
395
+ }
396
+ /**
397
+ * Web Component生命周期:连接到DOM
398
+ */
399
+ connectedCallback() {
400
+ this.connected = true;
401
+ try {
402
+ if (this.config.styles) {
403
+ const styleName = this.config.styleName || this.constructor.name;
404
+ this.applyScopedStyles(styleName, this.config.styles);
405
+ }
406
+ const content = this.render();
407
+ this.appendChild(content);
408
+ this.onConnected?.();
409
+ } catch (error) {
410
+ logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
411
+ this.renderError(error);
412
+ }
413
+ }
414
+ /**
415
+ * Web Component生命周期:从DOM断开
416
+ */
417
+ disconnectedCallback() {
418
+ this.cleanupReactiveStates();
419
+ this.cleanupStyles();
420
+ this.onDisconnected?.();
421
+ }
422
+ /**
423
+ * Web Component生命周期:属性变化
424
+ */
425
+ attributeChangedCallback(name, oldValue, newValue) {
426
+ this.onAttributeChanged?.(name, oldValue, newValue);
427
+ }
428
+ /**
429
+ * 查找组件内的元素
430
+ *
431
+ * @param selector - CSS选择器
432
+ * @returns 元素或null
433
+ */
434
+ querySelector(selector) {
435
+ return HTMLElement.prototype.querySelector.call(this, selector);
436
+ }
437
+ /**
438
+ * 查找组件内的所有匹配元素
439
+ *
440
+ * @param selector - CSS选择器
441
+ * @returns 元素列表
442
+ */
443
+ querySelectorAll(selector) {
444
+ return HTMLElement.prototype.querySelectorAll.call(this, selector);
445
+ }
446
+ /**
447
+ * 创建响应式对象
448
+ *
449
+ * @param obj 要变为响应式的对象
450
+ * @param debugName 调试名称(可选)
451
+ * @returns 响应式代理对象
452
+ */
453
+ reactive(obj, debugName) {
454
+ const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
455
+ const name = debugName || `${this.constructor.name}.reactive`;
456
+ return this._isDebugEnabled ? reactiveFn(obj, () => this.scheduleRerender(), name) : reactiveFn(obj, () => this.scheduleRerender());
457
+ }
458
+ /**
459
+ * 创建响应式状态
460
+ *
461
+ * @param key 状态标识符
462
+ * @param initialValue 初始值
463
+ * @returns [getter, setter] 元组
464
+ */
465
+ useState(key, initialValue) {
466
+ if (!this._reactiveStates.has(key)) {
467
+ const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
468
+ this._reactiveStates.set(key, { getter, setter });
469
+ }
470
+ const state = this._reactiveStates.get(key);
471
+ return [state.getter, state.setter];
472
+ }
473
+ /**
474
+ * 调度重渲染
475
+ * 这个方法被响应式系统调用,开发者通常不需要直接调用
476
+ */
477
+ scheduleRerender() {
478
+ if (this.connected) {
479
+ this.rerender();
480
+ }
481
+ }
482
+ /**
483
+ * 重新渲染组件
484
+ */
485
+ rerender() {
486
+ if (!this.connected) {
487
+ logger3.warn(
488
+ `[${this.constructor.name}] Component is not connected, skipping rerender.`
489
+ );
490
+ return;
491
+ }
492
+ this.innerHTML = "";
493
+ try {
494
+ const content = this.render();
495
+ this.appendChild(content);
496
+ } catch (error) {
497
+ logger3.error(`[${this.constructor.name}] Error in rerender:`, error);
498
+ this.renderError(error);
499
+ }
500
+ }
501
+ /**
502
+ * 渲染错误信息
503
+ *
504
+ * @param error - 错误对象
505
+ */
506
+ renderError(error) {
507
+ this.innerHTML = "";
508
+ const errorElement = h(
509
+ "div",
510
+ {
511
+ style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;"
512
+ },
513
+ [
514
+ h("strong", {}, `[${this.constructor.name}] Component Error:`),
515
+ h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error))
516
+ ]
517
+ );
518
+ this.appendChild(errorElement);
519
+ }
520
+ /**
521
+ * 为Light DOM组件应用样式
522
+ * 直接将样式注入到组件自身,避免全局污染
523
+ */
524
+ applyScopedStyles(styleName, cssText) {
525
+ const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
526
+ if (existingStyle) {
527
+ return;
528
+ }
529
+ const styleElement = document.createElement("style");
530
+ styleElement.setAttribute("data-wsx-light-component", styleName);
531
+ styleElement.textContent = cssText;
532
+ this.insertBefore(styleElement, this.firstChild);
533
+ }
534
+ /**
535
+ * 获取配置值
536
+ *
537
+ * @param key - 配置键
538
+ * @param defaultValue - 默认值
539
+ * @returns 配置值
540
+ */
541
+ getConfig(key, defaultValue) {
542
+ return this.config[key] ?? defaultValue;
543
+ }
544
+ /**
545
+ * 设置配置值
546
+ *
547
+ * @param key - 配置键
548
+ * @param value - 配置值
549
+ */
550
+ setConfig(key, value) {
551
+ this.config[key] = value;
552
+ }
553
+ /**
554
+ * 清理响应式状态
555
+ */
556
+ cleanupReactiveStates() {
557
+ this._reactiveStates.clear();
558
+ }
559
+ /**
560
+ * 清理组件样式
561
+ */
562
+ cleanupStyles() {
563
+ const styleName = this.config.styleName || this.constructor.name;
564
+ const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
565
+ if (existingStyle) {
566
+ existingStyle.remove();
567
+ }
568
+ }
569
+ /**
570
+ * 获取属性值
571
+ *
572
+ * @param name - 属性名
573
+ * @param defaultValue - 默认值
574
+ * @returns 属性值
575
+ */
576
+ getAttr(name, defaultValue = "") {
577
+ return this.getAttribute(name) || defaultValue;
578
+ }
579
+ /**
580
+ * 设置属性值
581
+ *
582
+ * @param name - 属性名
583
+ * @param value - 属性值
584
+ */
585
+ setAttr(name, value) {
586
+ this.setAttribute(name, value);
587
+ }
588
+ /**
589
+ * 移除属性
590
+ *
591
+ * @param name - 属性名
592
+ */
593
+ removeAttr(name) {
594
+ this.removeAttribute(name);
595
+ }
596
+ /**
597
+ * 检查是否有属性
598
+ *
599
+ * @param name - 属性名
600
+ * @returns 是否存在
601
+ */
602
+ hasAttr(name) {
603
+ return this.hasAttribute(name);
604
+ }
605
+ };
606
+
607
+ // src/auto-register.ts
608
+ function autoRegister(options = {}) {
609
+ return function(constructor) {
610
+ const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
611
+ if (!customElements.get(tagName)) {
612
+ customElements.define(tagName, constructor);
613
+ }
614
+ return constructor;
615
+ };
616
+ }
617
+ function registerComponent(constructor, options = {}) {
618
+ const tagName = options.tagName || deriveTagName(constructor.name, options.prefix);
619
+ if (!customElements.get(tagName)) {
620
+ customElements.define(tagName, constructor);
621
+ }
622
+ }
623
+ function deriveTagName(className, prefix) {
624
+ let kebabCase = className.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
625
+ if (!kebabCase.includes("-")) {
626
+ kebabCase = `${kebabCase}-component`;
627
+ }
628
+ return prefix ? `${prefix}${kebabCase}` : kebabCase;
629
+ }
630
+
402
631
  // src/reactive-component.ts
403
632
  var ReactiveWebComponent = class extends WebComponent {
404
633
  constructor(config = {}) {
405
634
  super(config);
406
635
  this._isDebugEnabled = false;
636
+ this._preserveFocus = true;
407
637
  this._reactiveStates = /* @__PURE__ */ new Map();
408
638
  this._isDebugEnabled = config.debug ?? false;
639
+ this._preserveFocus = config.preserveFocus ?? true;
409
640
  }
410
641
  /**
411
642
  * 创建响应式对象
@@ -443,6 +674,104 @@ var ReactiveWebComponent = class extends WebComponent {
443
674
  this.rerender();
444
675
  }
445
676
  }
677
+ /**
678
+ * 重写 rerender 方法以支持焦点保持
679
+ */
680
+ rerender() {
681
+ if (!this.connected) {
682
+ return;
683
+ }
684
+ let focusData = null;
685
+ if (this._preserveFocus) {
686
+ const activeElement = this.shadowRoot.activeElement;
687
+ focusData = this.saveFocusState(activeElement);
688
+ }
689
+ super.rerender();
690
+ if (this._preserveFocus && focusData) {
691
+ this.restoreFocusState(focusData);
692
+ }
693
+ }
694
+ /**
695
+ * 保存焦点状态
696
+ */
697
+ saveFocusState(activeElement) {
698
+ if (!activeElement) {
699
+ return null;
700
+ }
701
+ const focusData = {
702
+ tagName: activeElement.tagName.toLowerCase(),
703
+ className: activeElement.className
704
+ };
705
+ if (activeElement.hasAttribute("contenteditable")) {
706
+ const selection = window.getSelection();
707
+ if (selection && selection.rangeCount > 0) {
708
+ const range = selection.getRangeAt(0);
709
+ focusData.selectionStart = range.startOffset;
710
+ focusData.selectionEnd = range.endOffset;
711
+ }
712
+ }
713
+ if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLSelectElement) {
714
+ focusData.value = activeElement.value;
715
+ if ("selectionStart" in activeElement) {
716
+ focusData.selectionStart = activeElement.selectionStart;
717
+ focusData.selectionEnd = activeElement.selectionEnd;
718
+ }
719
+ }
720
+ return focusData;
721
+ }
722
+ /**
723
+ * 恢复焦点状态
724
+ */
725
+ restoreFocusState(focusData) {
726
+ if (!focusData) return;
727
+ try {
728
+ let targetElement = null;
729
+ if (focusData.className) {
730
+ targetElement = this.shadowRoot.querySelector(
731
+ `.${focusData.className.split(" ")[0]}`
732
+ );
733
+ }
734
+ if (!targetElement) {
735
+ targetElement = this.shadowRoot.querySelector(focusData.tagName);
736
+ }
737
+ if (targetElement) {
738
+ targetElement.focus({ preventScroll: true });
739
+ if (focusData.selectionStart !== void 0) {
740
+ if (targetElement instanceof HTMLInputElement) {
741
+ targetElement.setSelectionRange(
742
+ focusData.selectionStart,
743
+ focusData.selectionEnd
744
+ );
745
+ } else if (targetElement instanceof HTMLSelectElement) {
746
+ targetElement.value = focusData.value;
747
+ } else if (targetElement.hasAttribute("contenteditable")) {
748
+ this.setCursorPosition(targetElement, focusData.selectionStart);
749
+ }
750
+ }
751
+ }
752
+ } catch {
753
+ }
754
+ }
755
+ /**
756
+ * 设置光标位置
757
+ */
758
+ setCursorPosition(element, position) {
759
+ try {
760
+ const selection = window.getSelection();
761
+ if (selection) {
762
+ const range = document.createRange();
763
+ const textNode = element.childNodes[0];
764
+ if (textNode) {
765
+ const maxPos = Math.min(position, textNode.textContent?.length || 0);
766
+ range.setStart(textNode, maxPos);
767
+ range.setEnd(textNode, maxPos);
768
+ selection.removeAllRanges();
769
+ selection.addRange(range);
770
+ }
771
+ }
772
+ } catch {
773
+ }
774
+ }
446
775
  /**
447
776
  * 获取所有响应式状态的快照(用于调试)
448
777
  */
@@ -505,6 +834,7 @@ function createReactiveComponent(ComponentClass, config) {
505
834
  }
506
835
  export {
507
836
  Fragment,
837
+ LightComponent,
508
838
  ReactiveDebug,
509
839
  ReactiveWebComponent,
510
840
  StyleManager,
@@ -223,8 +223,9 @@ function jsx(tag, props) {
223
223
  return h(tag, null);
224
224
  }
225
225
  const { children, ...restProps } = props;
226
- if (children !== void 0) {
227
- return h(tag, restProps, children);
226
+ if (children !== void 0 && children !== null) {
227
+ const childrenArray = Array.isArray(children) ? children : [children];
228
+ return h(tag, restProps, ...childrenArray);
228
229
  }
229
230
  return h(tag, restProps);
230
231
  }
@@ -235,7 +236,7 @@ function jsxs(tag, props) {
235
236
  const { children, ...restProps } = props;
236
237
  if (Array.isArray(children)) {
237
238
  return h(tag, restProps, ...children);
238
- } else if (children !== void 0) {
239
+ } else if (children !== void 0 && children !== null) {
239
240
  return h(tag, restProps, children);
240
241
  }
241
242
  return h(tag, restProps);
@@ -2,7 +2,7 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-BV2V6BVN.mjs";
5
+ } from "./chunk-VZQT7HU5.mjs";
6
6
  export {
7
7
  Fragment,
8
8
  jsx,
package/dist/jsx.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  h
4
- } from "./chunk-BV2V6BVN.mjs";
4
+ } from "./chunk-VZQT7HU5.mjs";
5
5
  export {
6
6
  Fragment,
7
7
  h
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
1
  {
2
- "name": "@wsxjs/wsx-core",
3
- "version": "0.0.5",
4
- "description": "Core WSX Framework - Web Components with JSX syntax",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./types/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./types/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.js"
2
+ "name": "@wsxjs/wsx-core",
3
+ "version": "0.0.6",
4
+ "description": "Core WSX Framework - Web Components with JSX syntax",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./types/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./jsx-runtime": {
15
+ "types": "./types/jsx-runtime.d.ts",
16
+ "import": "./dist/jsx-runtime.mjs",
17
+ "require": "./dist/jsx-runtime.js"
18
+ }
13
19
  },
14
- "./jsx-runtime": {
15
- "types": "./types/jsx-runtime.d.ts",
16
- "import": "./dist/jsx-runtime.mjs",
17
- "require": "./dist/jsx-runtime.js"
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "types",
24
+ "!**/__tests__",
25
+ "!**/test"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm",
29
+ "build:dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --sourcemap",
30
+ "dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --watch --sourcemap",
31
+ "test": "jest",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "keywords": [
36
+ "web-components",
37
+ "jsx",
38
+ "typescript",
39
+ "custom-elements"
40
+ ],
41
+ "devDependencies": {
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.0.0",
44
+ "@types/node": "^20.0.0"
45
+ },
46
+ "peerDependencies": {
47
+ "typescript": ">=4.7.0"
18
48
  }
19
- },
20
- "files": [
21
- "dist",
22
- "src",
23
- "types",
24
- "!**/__tests__",
25
- "!**/test"
26
- ],
27
- "keywords": [
28
- "web-components",
29
- "jsx",
30
- "typescript",
31
- "custom-elements"
32
- ],
33
- "devDependencies": {
34
- "tsup": "^8.0.0",
35
- "typescript": "^5.0.0",
36
- "@types/node": "^20.0.0"
37
- },
38
- "peerDependencies": {
39
- "typescript": ">=4.7.0"
40
- },
41
- "scripts": {
42
- "build": "tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm",
43
- "build:dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --sourcemap",
44
- "dev": "NODE_ENV=development tsup src/index.ts src/jsx.ts src/jsx-runtime.ts --format cjs,esm --watch --sourcemap",
45
- "test": "jest",
46
- "typecheck": "tsc --noEmit",
47
- "clean": "rm -rf dist"
48
- }
49
- }
49
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Core exports
2
2
  export { WebComponent } from "./web-component";
3
+ export { LightComponent } from "./light-component";
3
4
  export { autoRegister, registerComponent } from "./auto-register";
4
5
  export { h, h as jsx, h as jsxs, Fragment } from "./jsx-factory";
5
6
  export { StyleManager } from "./styles/style-manager";
@@ -11,6 +12,7 @@ export { ReactiveWebComponent, makeReactive, createReactiveComponent } from "./r
11
12
 
12
13
  // Type exports
13
14
  export type { WebComponentConfig } from "./web-component";
15
+ export type { LightComponentConfig } from "./light-component";
14
16
  export type { JSXChildren } from "./jsx-factory";
15
17
  export type { Logger, LogLevel } from "./utils/logger";
16
18
  export type { ReactiveCallback } from "./utils/reactive";
@@ -189,8 +189,9 @@ export function jsx(
189
189
  }
190
190
 
191
191
  const { children, ...restProps } = props;
192
- if (children !== undefined) {
193
- return h(tag, restProps, children);
192
+ if (children !== undefined && children !== null) {
193
+ const childrenArray = Array.isArray(children) ? children : [children];
194
+ return h(tag, restProps, ...childrenArray);
194
195
  }
195
196
  return h(tag, restProps);
196
197
  }
@@ -215,8 +216,8 @@ export function jsxs(
215
216
  const { children, ...restProps } = props;
216
217
  if (Array.isArray(children)) {
217
218
  return h(tag, restProps, ...children);
218
- } else if (children !== undefined) {
219
- return h(tag, restProps, children);
219
+ } else if (children !== undefined && children !== null) {
220
+ return h(tag, restProps, children as JSXChildren);
220
221
  }
221
222
  return h(tag, restProps);
222
223
  }