@wsxjs/wsx-core 0.0.20 → 0.0.21

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/index.js CHANGED
@@ -37,6 +37,170 @@ __export(index_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(index_exports);
39
39
 
40
+ // src/utils/dom-utils.ts
41
+ function parseHTMLToNodes(html) {
42
+ if (!html) return [];
43
+ const temp = document.createElement("div");
44
+ temp.innerHTML = html;
45
+ return Array.from(temp.childNodes).map((node) => {
46
+ if (node instanceof HTMLElement || node instanceof SVGElement) {
47
+ return node;
48
+ } else {
49
+ return node.textContent || "";
50
+ }
51
+ });
52
+ }
53
+ function isHTMLString(str) {
54
+ const trimmed = str.trim();
55
+ if (!trimmed) return false;
56
+ const htmlTagPattern = /<[a-z][a-z0-9]*(\s[^>]*)?(\/>|>)/i;
57
+ const looksLikeMath = /^[^<]*<[^>]*>[^>]*$/.test(trimmed) && !htmlTagPattern.test(trimmed);
58
+ if (looksLikeMath) return false;
59
+ return htmlTagPattern.test(trimmed);
60
+ }
61
+ function flattenChildren(children, skipHTMLDetection = false, depth = 0) {
62
+ if (depth > 10) {
63
+ console.warn(
64
+ "[WSX] flattenChildren: Maximum depth exceeded, treating remaining children as text"
65
+ );
66
+ return children.filter(
67
+ (child) => typeof child === "string" || typeof child === "number"
68
+ );
69
+ }
70
+ const result = [];
71
+ for (const child of children) {
72
+ if (child === null || child === void 0 || child === false) {
73
+ continue;
74
+ } else if (Array.isArray(child)) {
75
+ result.push(...flattenChildren(child, skipHTMLDetection, depth + 1));
76
+ } else if (typeof child === "string") {
77
+ if (skipHTMLDetection) {
78
+ result.push(child);
79
+ } else if (isHTMLString(child)) {
80
+ try {
81
+ const nodes = parseHTMLToNodes(child);
82
+ if (nodes.length > 0) {
83
+ for (const node of nodes) {
84
+ if (typeof node === "string") {
85
+ result.push(node);
86
+ } else {
87
+ result.push(node);
88
+ }
89
+ }
90
+ } else {
91
+ result.push(child);
92
+ }
93
+ } catch (error) {
94
+ console.warn("[WSX] Failed to parse HTML string, treating as text:", error);
95
+ result.push(child);
96
+ }
97
+ } else {
98
+ result.push(child);
99
+ }
100
+ } else {
101
+ result.push(child);
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+
107
+ // src/render-context.ts
108
+ var _RenderContext = class _RenderContext {
109
+ /**
110
+ * Executes a function within the context of a component.
111
+ * @param component The component instance currently rendering.
112
+ * @param fn The function to execute (usually the render method).
113
+ */
114
+ static runInContext(component, fn) {
115
+ const prev = _RenderContext.current;
116
+ _RenderContext.current = component;
117
+ try {
118
+ return fn();
119
+ } finally {
120
+ _RenderContext.current = prev;
121
+ }
122
+ }
123
+ /**
124
+ * Gets the currently rendering component.
125
+ */
126
+ static getCurrentComponent() {
127
+ return _RenderContext.current;
128
+ }
129
+ /**
130
+ * Gets the current component's DOM cache.
131
+ */
132
+ static getDOMCache() {
133
+ return _RenderContext.current?.getDomCache();
134
+ }
135
+ };
136
+ _RenderContext.current = null;
137
+ var RenderContext = _RenderContext;
138
+
139
+ // src/utils/cache-key.ts
140
+ var POSITION_ID_KEY = "__wsxPositionId";
141
+ var INDEX_KEY = "__wsxIndex";
142
+ var componentElementCounters = /* @__PURE__ */ new WeakMap();
143
+ var componentIdCache = /* @__PURE__ */ new WeakMap();
144
+ function generateCacheKey(tag, props, componentId, component) {
145
+ const positionId = props?.[POSITION_ID_KEY];
146
+ const userKey = props?.key;
147
+ const index = props?.[INDEX_KEY];
148
+ if (userKey !== void 0 && userKey !== null) {
149
+ return `${componentId}:${tag}:key-${String(userKey)}`;
150
+ }
151
+ if (index !== void 0 && index !== null) {
152
+ return `${componentId}:${tag}:idx-${String(index)}`;
153
+ }
154
+ if (positionId !== void 0 && positionId !== null && positionId !== "no-id") {
155
+ return `${componentId}:${tag}:${String(positionId)}`;
156
+ }
157
+ if (component) {
158
+ let counter = componentElementCounters.get(component) || 0;
159
+ counter++;
160
+ componentElementCounters.set(component, counter);
161
+ return `${componentId}:${tag}:auto-${counter}`;
162
+ }
163
+ return `${componentId}:${tag}:fallback-${Date.now()}-${Math.random()}`;
164
+ }
165
+ function getComponentId() {
166
+ const component = RenderContext.getCurrentComponent();
167
+ if (component) {
168
+ let cachedId = componentIdCache.get(component);
169
+ if (cachedId) {
170
+ return cachedId;
171
+ }
172
+ const instanceId = component._instanceId || "default";
173
+ cachedId = `${component.constructor.name}:${instanceId}`;
174
+ componentIdCache.set(component, cachedId);
175
+ return cachedId;
176
+ }
177
+ return "unknown";
178
+ }
179
+
180
+ // src/utils/element-marking.ts
181
+ var CACHE_KEY_PROP = "__wsxCacheKey";
182
+ function markElement(element, cacheKey) {
183
+ element[CACHE_KEY_PROP] = cacheKey;
184
+ }
185
+ function isCreatedByH(element) {
186
+ if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
187
+ return false;
188
+ }
189
+ return element[CACHE_KEY_PROP] !== void 0;
190
+ }
191
+ function shouldPreserveElement(element) {
192
+ if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
193
+ return true;
194
+ }
195
+ if (!isCreatedByH(element)) {
196
+ return true;
197
+ }
198
+ if (element.hasAttribute("data-wsx-preserve")) {
199
+ return true;
200
+ }
201
+ return false;
202
+ }
203
+
40
204
  // src/utils/svg-utils.ts
41
205
  var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
42
206
  var SVG_ONLY_ELEMENTS = /* @__PURE__ */ new Set([
@@ -149,21 +313,63 @@ function getSVGAttributeName(attributeName) {
149
313
  return SVG_ATTRIBUTE_MAP.get(attributeName) || attributeName;
150
314
  }
151
315
 
152
- // src/utils/dom-utils.ts
153
- function parseHTMLToNodes(html) {
154
- if (!html) return [];
155
- const temp = document.createElement("div");
156
- temp.innerHTML = html;
157
- return Array.from(temp.childNodes).map((node) => {
158
- if (node instanceof HTMLElement || node instanceof SVGElement) {
159
- return node;
160
- } else {
161
- return node.textContent || "";
316
+ // src/utils/logger.ts
317
+ var WSXLogger = class {
318
+ constructor(prefix = "[WSX]", enabled = true, level = "info") {
319
+ this.prefix = prefix;
320
+ this.enabled = enabled;
321
+ this.level = level;
322
+ }
323
+ shouldLog(level) {
324
+ if (!this.enabled) return false;
325
+ const levels = ["debug", "info", "warn", "error"];
326
+ const currentLevelIndex = levels.indexOf(this.level);
327
+ const messageLevelIndex = levels.indexOf(level);
328
+ return messageLevelIndex >= currentLevelIndex;
329
+ }
330
+ debug(message, ...args) {
331
+ if (this.shouldLog("debug")) {
332
+ console.debug(`${this.prefix} ${message}`, ...args);
162
333
  }
163
- });
334
+ }
335
+ info(message, ...args) {
336
+ if (this.shouldLog("info")) {
337
+ console.info(`${this.prefix} ${message}`, ...args);
338
+ }
339
+ }
340
+ warn(message, ...args) {
341
+ if (this.shouldLog("warn")) {
342
+ console.warn(`${this.prefix} ${message}`, ...args);
343
+ }
344
+ }
345
+ error(message, ...args) {
346
+ if (this.shouldLog("error")) {
347
+ console.error(`${this.prefix} ${message}`, ...args);
348
+ }
349
+ }
350
+ };
351
+ var logger = new WSXLogger();
352
+ function createLogger(componentName) {
353
+ return new WSXLogger(`[WSX:${componentName}]`);
164
354
  }
165
355
 
166
- // src/jsx-factory.ts
356
+ // src/utils/props-utils.ts
357
+ var logger2 = createLogger("Props Utilities");
358
+ function isFrameworkInternalProp(key) {
359
+ if (key === "key") {
360
+ return true;
361
+ }
362
+ if (key === "__wsxPositionId" || key === "__wsxIndex") {
363
+ return true;
364
+ }
365
+ if (key === "__testId") {
366
+ return true;
367
+ }
368
+ if (key === "ref") {
369
+ return true;
370
+ }
371
+ return false;
372
+ }
167
373
  function isStandardHTMLAttribute(key) {
168
374
  const standardAttributes = /* @__PURE__ */ new Set([
169
375
  // 全局属性
@@ -246,7 +452,8 @@ function isStandardHTMLAttribute(key) {
246
452
  function isSpecialProperty(key, value) {
247
453
  return key === "ref" || key === "className" || key === "class" || key === "style" || key.startsWith("on") && typeof value === "function" || typeof value === "boolean" || key === "value";
248
454
  }
249
- function setSmartProperty(element, key, value, isSVG = false) {
455
+ function setSmartProperty(element, key, value, tag) {
456
+ const isSVG = shouldUseSVGNamespace(tag);
250
457
  if (isSpecialProperty(key, value)) {
251
458
  return;
252
459
  }
@@ -256,13 +463,13 @@ function setSmartProperty(element, key, value, isSVG = false) {
256
463
  try {
257
464
  const serialized = JSON.stringify(value);
258
465
  if (serialized.length > 1024 * 1024) {
259
- console.warn(
466
+ logger2.warn(
260
467
  `[WSX] Attribute "${key}" value too large, consider using a non-standard property name instead`
261
468
  );
262
469
  }
263
470
  element.setAttribute(attributeName, serialized);
264
471
  } catch (error) {
265
- console.warn(`[WSX] Cannot serialize attribute "${key}":`, error);
472
+ logger2.warn(`Cannot serialize attribute "${key}":`, error);
266
473
  }
267
474
  } else {
268
475
  element.setAttribute(attributeName, String(value));
@@ -276,7 +483,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
276
483
  const serialized = JSON.stringify(value);
277
484
  element.setAttribute(attributeName, serialized);
278
485
  } catch (error) {
279
- console.warn(`[WSX] Cannot serialize SVG attribute "${key}":`, error);
486
+ logger2.warn(`Cannot serialize SVG attribute "${key}":`, error);
280
487
  }
281
488
  } else {
282
489
  element.setAttribute(attributeName, String(value));
@@ -300,7 +507,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
300
507
  const serialized = JSON.stringify(value);
301
508
  element.setAttribute(attributeName, serialized);
302
509
  } catch (error) {
303
- console.warn(`[WSX] Cannot serialize readonly property "${key}":`, error);
510
+ logger2.warn(`Cannot serialize readonly property "${key}":`, error);
304
511
  }
305
512
  } else {
306
513
  element.setAttribute(attributeName, String(value));
@@ -315,7 +522,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
315
522
  const serialized = JSON.stringify(value);
316
523
  element.setAttribute(attributeName, serialized);
317
524
  } catch (error) {
318
- console.warn(
525
+ logger2.warn(
319
526
  `[WSX] Cannot serialize property "${key}" for attribute:`,
320
527
  error
321
528
  );
@@ -331,59 +538,76 @@ function setSmartProperty(element, key, value, isSVG = false) {
331
538
  try {
332
539
  const serialized = JSON.stringify(value);
333
540
  if (serialized.length > 1024 * 1024) {
334
- console.warn(
541
+ logger2.warn(
335
542
  `[WSX] Property "${key}" value too large for attribute, consider using a JavaScript property instead`
336
543
  );
337
544
  }
338
545
  element.setAttribute(attributeName, serialized);
339
546
  } catch (error) {
340
- console.warn(`[WSX] Cannot serialize property "${key}" for attribute:`, error);
547
+ logger2.warn(`Cannot serialize property "${key}" for attribute:`, error);
341
548
  }
342
549
  } else {
343
550
  element.setAttribute(attributeName, String(value));
344
551
  }
345
552
  }
346
553
  }
347
- function h(tag, props = {}, ...children) {
348
- if (typeof tag === "function") {
349
- return tag(props, children);
554
+
555
+ // src/utils/element-creation.ts
556
+ function applySingleProp(element, key, value, tag, isSVG) {
557
+ if (value === null || value === void 0 || value === false) {
558
+ return;
350
559
  }
351
- const element = createElement(tag);
352
- if (props) {
353
- const isSVG = shouldUseSVGNamespace(tag);
354
- Object.entries(props).forEach(([key, value]) => {
355
- if (value === null || value === void 0 || value === false) {
356
- return;
357
- }
358
- if (key === "ref" && typeof value === "function") {
359
- value(element);
360
- } else if (key === "className" || key === "class") {
361
- if (isSVG) {
362
- element.setAttribute("class", value);
363
- } else {
364
- element.className = value;
365
- }
366
- } else if (key === "style" && typeof value === "string") {
367
- element.setAttribute("style", value);
368
- } else if (key.startsWith("on") && typeof value === "function") {
369
- const eventName = key.slice(2).toLowerCase();
370
- element.addEventListener(eventName, value);
371
- } else if (typeof value === "boolean") {
372
- if (value) {
373
- element.setAttribute(key, "");
374
- }
375
- } else if (key === "value") {
376
- if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
377
- element.value = String(value);
378
- } else {
379
- const attributeName = isSVG ? getSVGAttributeName(key) : key;
380
- element.setAttribute(attributeName, String(value));
381
- }
382
- } else {
383
- setSmartProperty(element, key, value, isSVG);
384
- }
385
- });
560
+ if (key === "ref" && typeof value === "function") {
561
+ value(element);
562
+ return;
563
+ }
564
+ if (key === "className" || key === "class") {
565
+ if (isSVG) {
566
+ element.setAttribute("class", value);
567
+ } else {
568
+ element.className = value;
569
+ }
570
+ return;
571
+ }
572
+ if (key === "style" && typeof value === "string") {
573
+ element.setAttribute("style", value);
574
+ return;
575
+ }
576
+ if (key.startsWith("on") && typeof value === "function") {
577
+ const eventName = key.slice(2).toLowerCase();
578
+ element.addEventListener(eventName, value);
579
+ return;
580
+ }
581
+ if (typeof value === "boolean") {
582
+ if (value) {
583
+ element.setAttribute(key, "");
584
+ }
585
+ return;
586
+ }
587
+ if (key === "value") {
588
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
589
+ element.value = String(value);
590
+ } else {
591
+ const attributeName = isSVG ? getSVGAttributeName(key) : key;
592
+ element.setAttribute(attributeName, String(value));
593
+ }
594
+ return;
595
+ }
596
+ if (isFrameworkInternalProp(key)) {
597
+ return;
598
+ }
599
+ setSmartProperty(element, key, value, tag);
600
+ }
601
+ function applyPropsToElement(element, props, tag) {
602
+ if (!props) {
603
+ return;
386
604
  }
605
+ const isSVG = shouldUseSVGNamespace(tag);
606
+ Object.entries(props).forEach(([key, value]) => {
607
+ applySingleProp(element, key, value, tag, isSVG);
608
+ });
609
+ }
610
+ function appendChildrenToElement(element, children) {
387
611
  const flatChildren = flattenChildren(children);
388
612
  flatChildren.forEach((child) => {
389
613
  if (child === null || child === void 0 || child === false) {
@@ -397,60 +621,278 @@ function h(tag, props = {}, ...children) {
397
621
  element.appendChild(child);
398
622
  }
399
623
  });
624
+ }
625
+ function createElementWithPropsAndChildren(tag, props, children) {
626
+ const element = createElement(tag);
627
+ applyPropsToElement(element, props, tag);
628
+ appendChildrenToElement(element, children);
400
629
  return element;
401
630
  }
402
- function isHTMLString(str) {
403
- const trimmed = str.trim();
404
- if (!trimmed) return false;
405
- const htmlTagPattern = /<[a-z][a-z0-9]*(\s[^>]*)?(\/>|>)/i;
406
- const looksLikeMath = /^[^<]*<[^>]*>[^>]*$/.test(trimmed) && !htmlTagPattern.test(trimmed);
407
- if (looksLikeMath) return false;
408
- return htmlTagPattern.test(trimmed);
631
+
632
+ // src/utils/element-update.ts
633
+ function removeProp(element, key, oldValue, tag) {
634
+ const isSVG = shouldUseSVGNamespace(tag);
635
+ if (key === "ref") {
636
+ return;
637
+ }
638
+ if (key === "className" || key === "class") {
639
+ if (isSVG) {
640
+ element.removeAttribute("class");
641
+ } else {
642
+ element.className = "";
643
+ }
644
+ return;
645
+ }
646
+ if (key === "style") {
647
+ element.removeAttribute("style");
648
+ return;
649
+ }
650
+ if (key.startsWith("on") && typeof oldValue === "function") {
651
+ return;
652
+ }
653
+ if (key === "value") {
654
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
655
+ element.value = "";
656
+ } else {
657
+ const attributeName2 = isSVG ? getSVGAttributeName(key) : key;
658
+ element.removeAttribute(attributeName2);
659
+ }
660
+ return;
661
+ }
662
+ if (isFrameworkInternalProp(key)) {
663
+ return;
664
+ }
665
+ const attributeName = isSVG ? getSVGAttributeName(key) : key;
666
+ element.removeAttribute(attributeName);
667
+ try {
668
+ delete element[key];
669
+ } catch {
670
+ }
409
671
  }
410
- function flattenChildren(children, skipHTMLDetection = false, depth = 0) {
411
- if (depth > 10) {
412
- console.warn(
413
- "[WSX] flattenChildren: Maximum depth exceeded, treating remaining children as text"
414
- );
415
- return children.filter(
416
- (child) => typeof child === "string" || typeof child === "number"
417
- );
672
+ function applySingleProp2(element, key, value, tag, isSVG) {
673
+ if (value === null || value === void 0 || value === false) {
674
+ return;
418
675
  }
419
- const result = [];
420
- for (const child of children) {
421
- if (child === null || child === void 0 || child === false) {
676
+ if (key === "ref" && typeof value === "function") {
677
+ value(element);
678
+ return;
679
+ }
680
+ if (key === "className" || key === "class") {
681
+ if (isSVG) {
682
+ element.setAttribute("class", value);
683
+ } else {
684
+ element.className = value;
685
+ }
686
+ return;
687
+ }
688
+ if (key === "style" && typeof value === "string") {
689
+ element.setAttribute("style", value);
690
+ return;
691
+ }
692
+ if (key.startsWith("on") && typeof value === "function") {
693
+ const eventName = key.slice(2).toLowerCase();
694
+ element.addEventListener(eventName, value);
695
+ return;
696
+ }
697
+ if (typeof value === "boolean") {
698
+ if (value) {
699
+ element.setAttribute(key, "");
700
+ }
701
+ return;
702
+ }
703
+ if (key === "value") {
704
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
705
+ element.value = String(value);
706
+ } else {
707
+ const attributeName = isSVG ? getSVGAttributeName(key) : key;
708
+ element.setAttribute(attributeName, String(value));
709
+ }
710
+ return;
711
+ }
712
+ if (isFrameworkInternalProp(key)) {
713
+ return;
714
+ }
715
+ setSmartProperty(element, key, value, tag);
716
+ }
717
+ function updateProps(element, oldProps, newProps, tag) {
718
+ const isSVG = shouldUseSVGNamespace(tag);
719
+ const old = oldProps || {};
720
+ const new_ = newProps || {};
721
+ for (const key in old) {
722
+ if (!(key in new_)) {
723
+ removeProp(element, key, old[key], tag);
724
+ }
725
+ }
726
+ for (const key in new_) {
727
+ const oldValue = old[key];
728
+ const newValue = new_[key];
729
+ if (oldValue === newValue) {
422
730
  continue;
423
- } else if (Array.isArray(child)) {
424
- result.push(...flattenChildren(child, skipHTMLDetection, depth + 1));
425
- } else if (typeof child === "string") {
426
- if (skipHTMLDetection) {
427
- result.push(child);
428
- } else if (isHTMLString(child)) {
429
- try {
430
- const nodes = parseHTMLToNodes(child);
431
- if (nodes.length > 0) {
432
- for (const node of nodes) {
433
- if (typeof node === "string") {
434
- result.push(node);
435
- } else {
436
- result.push(node);
437
- }
731
+ }
732
+ if (typeof oldValue === "object" && oldValue !== null && typeof newValue === "object" && newValue !== null) {
733
+ if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
734
+ continue;
735
+ }
736
+ }
737
+ applySingleProp2(element, key, newValue, tag, isSVG);
738
+ }
739
+ }
740
+ function updateChildren(element, oldChildren, newChildren) {
741
+ const flatOld = flattenChildren(oldChildren);
742
+ const flatNew = flattenChildren(newChildren);
743
+ const minLength = Math.min(flatOld.length, flatNew.length);
744
+ for (let i = 0; i < minLength; i++) {
745
+ const oldChild = flatOld[i];
746
+ const newChild = flatNew[i];
747
+ if (typeof oldChild === "string" || typeof oldChild === "number") {
748
+ if (typeof newChild === "string" || typeof newChild === "number") {
749
+ const textNode = element.childNodes[i];
750
+ if (textNode && textNode.nodeType === Node.TEXT_NODE) {
751
+ textNode.textContent = String(newChild);
752
+ } else {
753
+ const newTextNode = document.createTextNode(String(newChild));
754
+ if (textNode) {
755
+ element.replaceChild(newTextNode, textNode);
756
+ } else {
757
+ element.appendChild(newTextNode);
758
+ }
759
+ }
760
+ } else {
761
+ const textNode = element.childNodes[i];
762
+ if (textNode) {
763
+ if (!shouldPreserveElement(textNode)) {
764
+ element.removeChild(textNode);
765
+ }
766
+ }
767
+ if (typeof newChild === "string" || typeof newChild === "number") {
768
+ element.appendChild(document.createTextNode(String(newChild)));
769
+ } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
770
+ element.appendChild(newChild);
771
+ } else if (newChild instanceof DocumentFragment) {
772
+ element.appendChild(newChild);
773
+ }
774
+ }
775
+ } else if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
776
+ if (newChild === oldChild) {
777
+ continue;
778
+ } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
779
+ const oldNode = element.childNodes[i];
780
+ if (oldNode) {
781
+ if (!shouldPreserveElement(oldNode)) {
782
+ if (oldNode !== newChild) {
783
+ element.replaceChild(newChild, oldNode);
438
784
  }
439
785
  } else {
440
- result.push(child);
786
+ if (newChild.parentNode !== element) {
787
+ element.appendChild(newChild);
788
+ }
789
+ }
790
+ } else {
791
+ if (newChild.parentNode !== element) {
792
+ element.appendChild(newChild);
441
793
  }
442
- } catch (error) {
443
- console.warn("[WSX] Failed to parse HTML string, treating as text:", error);
444
- result.push(child);
445
794
  }
446
795
  } else {
447
- result.push(child);
796
+ const oldNode = element.childNodes[i];
797
+ if (oldNode) {
798
+ if (!shouldPreserveElement(oldNode)) {
799
+ element.removeChild(oldNode);
800
+ }
801
+ }
802
+ if (typeof newChild === "string" || typeof newChild === "number") {
803
+ element.appendChild(document.createTextNode(String(newChild)));
804
+ } else if (newChild instanceof DocumentFragment) {
805
+ element.appendChild(newChild);
806
+ }
448
807
  }
449
- } else {
450
- result.push(child);
451
808
  }
452
809
  }
453
- return result;
810
+ for (let i = minLength; i < flatNew.length; i++) {
811
+ const newChild = flatNew[i];
812
+ if (newChild === null || newChild === void 0 || newChild === false) {
813
+ continue;
814
+ }
815
+ if (typeof newChild === "string" || typeof newChild === "number") {
816
+ element.appendChild(document.createTextNode(String(newChild)));
817
+ } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
818
+ if (newChild.parentNode !== element) {
819
+ element.appendChild(newChild);
820
+ }
821
+ } else if (newChild instanceof DocumentFragment) {
822
+ element.appendChild(newChild);
823
+ }
824
+ }
825
+ const nodesToRemove = [];
826
+ for (let i = flatNew.length; i < element.childNodes.length; i++) {
827
+ const child = element.childNodes[i];
828
+ if (!shouldPreserveElement(child)) {
829
+ nodesToRemove.push(child);
830
+ }
831
+ }
832
+ for (let i = nodesToRemove.length - 1; i >= 0; i--) {
833
+ const node = nodesToRemove[i];
834
+ if (node.parentNode === element) {
835
+ element.removeChild(node);
836
+ }
837
+ }
838
+ }
839
+ function updateElement(element, newProps, newChildren, tag, cacheManager) {
840
+ const oldMetadata = cacheManager.getMetadata(element);
841
+ const oldProps = oldMetadata?.props || null;
842
+ const oldChildren = oldMetadata?.children || [];
843
+ updateProps(element, oldProps, newProps, tag);
844
+ updateChildren(element, oldChildren, newChildren);
845
+ cacheManager.setMetadata(element, {
846
+ props: newProps || {},
847
+ children: newChildren
848
+ });
849
+ }
850
+
851
+ // src/jsx-factory.ts
852
+ var logger3 = createLogger("JSX Factory");
853
+ function h(tag, props = {}, ...children) {
854
+ if (typeof tag === "function") {
855
+ return tag(props, children);
856
+ }
857
+ const context = RenderContext.getCurrentComponent();
858
+ const cacheManager = context ? RenderContext.getDOMCache() : null;
859
+ if (context && cacheManager) {
860
+ return tryUseCacheOrCreate(tag, props, children, context, cacheManager);
861
+ }
862
+ return createElementWithPropsAndChildren(tag, props, children);
863
+ }
864
+ function tryUseCacheOrCreate(tag, props, children, context, cacheManager) {
865
+ try {
866
+ const componentId = getComponentId();
867
+ const cacheKey = generateCacheKey(tag, props, componentId, context);
868
+ const cachedElement = cacheManager.get(cacheKey);
869
+ if (cachedElement) {
870
+ const element2 = cachedElement;
871
+ updateElement(element2, props, children, tag, cacheManager);
872
+ return element2;
873
+ }
874
+ const element = createElementWithPropsAndChildren(tag, props, children);
875
+ cacheManager.set(cacheKey, element);
876
+ markElement(element, cacheKey);
877
+ cacheManager.setMetadata(element, {
878
+ props: props || {},
879
+ children
880
+ });
881
+ return element;
882
+ } catch (error) {
883
+ return handleCacheError(error, tag, props, children);
884
+ }
885
+ }
886
+ function handleCacheError(error, tag, props, children) {
887
+ try {
888
+ const nodeEnv = typeof globalThis.process !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
889
+ globalThis.process.env?.NODE_ENV;
890
+ if (nodeEnv === "development") {
891
+ logger3.warn("[WSX DOM Cache] Cache error, falling back to create new element:", error);
892
+ }
893
+ } catch {
894
+ }
895
+ return createElementWithPropsAndChildren(tag, props, children);
454
896
  }
455
897
  function Fragment(_props, children) {
456
898
  const fragment = document.createDocumentFragment();
@@ -512,7 +954,7 @@ StyleManager.styleSheets = /* @__PURE__ */ new Map();
512
954
 
513
955
  // src/utils/reactive.ts
514
956
  var import_wsx_logger = require("@wsxjs/wsx-logger");
515
- var logger = (0, import_wsx_logger.createLogger)("ReactiveSystem");
957
+ var logger4 = (0, import_wsx_logger.createLogger)("ReactiveSystem");
516
958
  var UpdateScheduler = class {
517
959
  constructor() {
518
960
  this.pendingCallbacks = /* @__PURE__ */ new Set();
@@ -541,7 +983,7 @@ var UpdateScheduler = class {
541
983
  try {
542
984
  callback();
543
985
  } catch (error) {
544
- logger.error("[WSX Reactive] Error in callback:", error);
986
+ logger4.error("[WSX Reactive] Error in callback:", error);
545
987
  }
546
988
  });
547
989
  }
@@ -683,7 +1125,7 @@ var ReactiveDebug = {
683
1125
  */
684
1126
  log(message, ...args) {
685
1127
  if (this.isEnabled()) {
686
- logger.info(`[WSX Reactive] ${message}`, ...args);
1128
+ logger4.info(`[WSX Reactive] ${message}`, ...args);
687
1129
  }
688
1130
  }
689
1131
  };
@@ -731,6 +1173,108 @@ function reactiveWithDebug(obj, onChange, debugName) {
731
1173
  });
732
1174
  }
733
1175
 
1176
+ // src/dom-cache-manager.ts
1177
+ var logger5 = createLogger("DOMCacheManager");
1178
+ var DOMCacheManager = class {
1179
+ constructor() {
1180
+ // Map<CacheKey, DOMElement>
1181
+ this.cache = /* @__PURE__ */ new Map();
1182
+ // Map<DOMElement, Metadata>
1183
+ // Stores metadata (props, children) for cached elements to support diffing
1184
+ this.metadata = /* @__PURE__ */ new WeakMap();
1185
+ // Track key-parent relationships to detect duplicate keys in all environments
1186
+ // Map<CacheKey, ParentInfo>
1187
+ this.keyParentMap = /* @__PURE__ */ new Map();
1188
+ // Flag to enable duplicate key warnings (enabled by default, critical for correctness)
1189
+ this.warnDuplicateKeys = true;
1190
+ }
1191
+ /**
1192
+ * Retrieves an element from the cache.
1193
+ * @param key The unique cache key.
1194
+ */
1195
+ get(key) {
1196
+ return this.cache.get(key);
1197
+ }
1198
+ /**
1199
+ * Stores an element in the cache.
1200
+ * @param key The unique cache key.
1201
+ * @param element The DOM element to cache.
1202
+ */
1203
+ set(key, element) {
1204
+ if (this.warnDuplicateKeys) {
1205
+ this.checkDuplicateKey(key, element);
1206
+ }
1207
+ this.cache.set(key, element);
1208
+ }
1209
+ /**
1210
+ * Checks if a cache key is being reused in a different parent container.
1211
+ * Runs in all environments to help developers catch key conflicts early.
1212
+ * This is critical for correctness and helps prevent subtle bugs.
1213
+ */
1214
+ checkDuplicateKey(key, element) {
1215
+ const existing = this.keyParentMap.get(key);
1216
+ const currentParent = element.parentElement;
1217
+ if (existing && currentParent) {
1218
+ const currentParentInfo = this.getParentInfo(currentParent);
1219
+ const existingParentInfo = `${existing.parentTag}${existing.parentClass ? "." + existing.parentClass : ""}`;
1220
+ if (currentParentInfo !== existingParentInfo) {
1221
+ logger5.warn(
1222
+ `Duplicate key "${key}" detected in different parent containers!
1223
+ Previous parent: ${existingParentInfo}
1224
+ Current parent: ${currentParentInfo}
1225
+
1226
+ This may cause elements to appear in wrong containers or be moved unexpectedly.
1227
+
1228
+ Solution: Use unique key prefixes for different locations:
1229
+ Example: <wsx-link key="nav-0"> vs <wsx-link key="overflow-0">
1230
+
1231
+ See https://wsxjs.dev/docs/guide/DOM_CACHE_GUIDE for best practices.`
1232
+ );
1233
+ }
1234
+ }
1235
+ if (currentParent) {
1236
+ this.keyParentMap.set(key, {
1237
+ parentTag: currentParent.tagName.toLowerCase(),
1238
+ parentClass: currentParent.className,
1239
+ element
1240
+ });
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Gets a formatted parent container description.
1245
+ */
1246
+ getParentInfo(parent) {
1247
+ const tag = parent.tagName.toLowerCase();
1248
+ const className = parent.className;
1249
+ return `${tag}${className ? "." + className.split(" ")[0] : ""}`;
1250
+ }
1251
+ /**
1252
+ * Checks if a key exists in the cache.
1253
+ */
1254
+ has(key) {
1255
+ return this.cache.has(key);
1256
+ }
1257
+ /**
1258
+ * Clears the cache.
1259
+ * Should be called when component is disconnected or cache is invalidated.
1260
+ */
1261
+ clear() {
1262
+ this.cache.clear();
1263
+ }
1264
+ /**
1265
+ * Stores metadata for an element (e.g. previous props).
1266
+ */
1267
+ setMetadata(element, meta) {
1268
+ this.metadata.set(element, meta);
1269
+ }
1270
+ /**
1271
+ * Retrieves metadata for an element.
1272
+ */
1273
+ getMetadata(element) {
1274
+ return this.metadata.get(element);
1275
+ }
1276
+ };
1277
+
734
1278
  // src/base-component.ts
735
1279
  var BaseComponent = class extends HTMLElement {
736
1280
  constructor(config = {}) {
@@ -738,6 +1282,11 @@ var BaseComponent = class extends HTMLElement {
738
1282
  this.connected = false;
739
1283
  this._isDebugEnabled = false;
740
1284
  this._reactiveStates = /* @__PURE__ */ new Map();
1285
+ /**
1286
+ * DOM Cache Manager for fine-grained updates (RFC 0037)
1287
+ * @internal
1288
+ */
1289
+ this._domCache = new DOMCacheManager();
741
1290
  /**
742
1291
  * 当前捕获的焦点状态(用于在 render 时使用捕获的值)
743
1292
  * @internal - 由 rerender() 方法管理
@@ -802,6 +1351,13 @@ var BaseComponent = class extends HTMLElement {
802
1351
  static get observedAttributes() {
803
1352
  return [];
804
1353
  }
1354
+ /**
1355
+ * Gets the DOMCacheManager instance.
1356
+ * @internal
1357
+ */
1358
+ getDomCache() {
1359
+ return this._domCache;
1360
+ }
805
1361
  /**
806
1362
  * Web Component生命周期:属性变化
807
1363
  */
@@ -1109,7 +1665,7 @@ var BaseComponent = class extends HTMLElement {
1109
1665
 
1110
1666
  // src/web-component.ts
1111
1667
  var import_wsx_logger2 = require("@wsxjs/wsx-logger");
1112
- var logger2 = (0, import_wsx_logger2.createLogger)("WebComponent");
1668
+ var logger6 = (0, import_wsx_logger2.createLogger)("WebComponent");
1113
1669
  var WebComponent = class extends BaseComponent {
1114
1670
  // Initialized by BaseComponent constructor
1115
1671
  constructor(config = {}) {
@@ -1158,7 +1714,7 @@ var WebComponent = class extends BaseComponent {
1158
1714
  });
1159
1715
  }
1160
1716
  } catch (error) {
1161
- logger2.error(`Error in connectedCallback:`, error);
1717
+ logger6.error(`Error in connectedCallback:`, error);
1162
1718
  this.renderError(error);
1163
1719
  }
1164
1720
  }
@@ -1211,7 +1767,7 @@ var WebComponent = class extends BaseComponent {
1211
1767
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
1212
1768
  }
1213
1769
  }
1214
- const content = this.render();
1770
+ const content = RenderContext.runInContext(this, () => this.render());
1215
1771
  if (focusState && focusState.key && focusState.value !== void 0) {
1216
1772
  const target = content.querySelector(
1217
1773
  `[data-wsx-key="${focusState.key}"]`
@@ -1239,7 +1795,7 @@ var WebComponent = class extends BaseComponent {
1239
1795
  });
1240
1796
  });
1241
1797
  } catch (error) {
1242
- logger2.error("Error in _rerender:", error);
1798
+ logger6.error("Error in _rerender:", error);
1243
1799
  this.renderError(error);
1244
1800
  this._isRendering = false;
1245
1801
  }
@@ -1267,7 +1823,7 @@ var WebComponent = class extends BaseComponent {
1267
1823
 
1268
1824
  // src/light-component.ts
1269
1825
  var import_wsx_logger3 = require("@wsxjs/wsx-logger");
1270
- var logger3 = (0, import_wsx_logger3.createLogger)("LightComponent");
1826
+ var logger7 = (0, import_wsx_logger3.createLogger)("LightComponent");
1271
1827
  var LightComponent = class extends BaseComponent {
1272
1828
  // Initialized by BaseComponent constructor
1273
1829
  constructor(config = {}) {
@@ -1309,7 +1865,7 @@ var LightComponent = class extends BaseComponent {
1309
1865
  (child) => child !== styleElement
1310
1866
  );
1311
1867
  childrenToRemove.forEach((child) => child.remove());
1312
- const content = this.render();
1868
+ const content = RenderContext.runInContext(this, () => this.render());
1313
1869
  this.appendChild(content);
1314
1870
  if (styleElement && styleElement !== this.firstChild) {
1315
1871
  this.insertBefore(styleElement, this.firstChild);
@@ -1323,7 +1879,7 @@ var LightComponent = class extends BaseComponent {
1323
1879
  });
1324
1880
  }
1325
1881
  } catch (error) {
1326
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
1882
+ logger7.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
1327
1883
  this.renderError(error);
1328
1884
  }
1329
1885
  }
@@ -1371,7 +1927,7 @@ var LightComponent = class extends BaseComponent {
1371
1927
  this._pendingFocusState = focusState;
1372
1928
  const jsxChildren = this.getJSXChildren();
1373
1929
  try {
1374
- const content = this.render();
1930
+ const content = RenderContext.runInContext(this, () => this.render());
1375
1931
  if (focusState && focusState.key && focusState.value !== void 0) {
1376
1932
  const target = content.querySelector(
1377
1933
  `[data-wsx-key="${focusState.key}"]`
@@ -1428,7 +1984,7 @@ var LightComponent = class extends BaseComponent {
1428
1984
  });
1429
1985
  });
1430
1986
  } catch (error) {
1431
- logger3.error(`[${this.constructor.name}] Error in _rerender:`, error);
1987
+ logger7.error(`[${this.constructor.name}] Error in _rerender:`, error);
1432
1988
  this.renderError(error);
1433
1989
  this._isRendering = false;
1434
1990
  }
@@ -1550,6 +2106,10 @@ To fix this, please:
1550
2106
  See: https://github.com/wsxjs/wsxjs#setup for more details.`;
1551
2107
  }
1552
2108
  function state(targetOrContext, propertyKey) {
2109
+ const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2110
+ if (globalProcess?.env?.NODE_ENV === "test") {
2111
+ return;
2112
+ }
1553
2113
  let propertyName = "unknown";
1554
2114
  const propertyKeyIsObject = typeof propertyKey === "object" && propertyKey !== null;
1555
2115
  const targetIsObject = typeof targetOrContext === "object" && targetOrContext !== null;