@wsxjs/wsx-core 0.0.20 → 0.0.22

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.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  Fragment,
3
- h
4
- } from "./chunk-7FXISNME.mjs";
3
+ RenderContext,
4
+ createLogger,
5
+ h,
6
+ shouldPreserveElement
7
+ } from "./chunk-OXFZ575O.mjs";
5
8
 
6
9
  // src/styles/style-manager.ts
7
10
  var StyleManager = class {
@@ -44,8 +47,8 @@ var StyleManager = class {
44
47
  StyleManager.styleSheets = /* @__PURE__ */ new Map();
45
48
 
46
49
  // src/utils/reactive.ts
47
- import { createLogger } from "@wsxjs/wsx-logger";
48
- var logger = createLogger("ReactiveSystem");
50
+ import { createLogger as createLogger2 } from "@wsxjs/wsx-logger";
51
+ var logger = createLogger2("ReactiveSystem");
49
52
  var UpdateScheduler = class {
50
53
  constructor() {
51
54
  this.pendingCallbacks = /* @__PURE__ */ new Set();
@@ -264,6 +267,108 @@ function reactiveWithDebug(obj, onChange, debugName) {
264
267
  });
265
268
  }
266
269
 
270
+ // src/dom-cache-manager.ts
271
+ var logger2 = createLogger("DOMCacheManager");
272
+ var DOMCacheManager = class {
273
+ constructor() {
274
+ // Map<CacheKey, DOMElement>
275
+ this.cache = /* @__PURE__ */ new Map();
276
+ // Map<DOMElement, Metadata>
277
+ // Stores metadata (props, children) for cached elements to support diffing
278
+ this.metadata = /* @__PURE__ */ new WeakMap();
279
+ // Track key-parent relationships to detect duplicate keys in all environments
280
+ // Map<CacheKey, ParentInfo>
281
+ this.keyParentMap = /* @__PURE__ */ new Map();
282
+ // Flag to enable duplicate key warnings (enabled by default, critical for correctness)
283
+ this.warnDuplicateKeys = true;
284
+ }
285
+ /**
286
+ * Retrieves an element from the cache.
287
+ * @param key The unique cache key.
288
+ */
289
+ get(key) {
290
+ return this.cache.get(key);
291
+ }
292
+ /**
293
+ * Stores an element in the cache.
294
+ * @param key The unique cache key.
295
+ * @param element The DOM element to cache.
296
+ */
297
+ set(key, element) {
298
+ if (this.warnDuplicateKeys) {
299
+ this.checkDuplicateKey(key, element);
300
+ }
301
+ this.cache.set(key, element);
302
+ }
303
+ /**
304
+ * Checks if a cache key is being reused in a different parent container.
305
+ * Runs in all environments to help developers catch key conflicts early.
306
+ * This is critical for correctness and helps prevent subtle bugs.
307
+ */
308
+ checkDuplicateKey(key, element) {
309
+ const existing = this.keyParentMap.get(key);
310
+ const currentParent = element.parentElement;
311
+ if (existing && currentParent) {
312
+ const currentParentInfo = this.getParentInfo(currentParent);
313
+ const existingParentInfo = `${existing.parentTag}${existing.parentClass ? "." + existing.parentClass : ""}`;
314
+ if (currentParentInfo !== existingParentInfo) {
315
+ logger2.warn(
316
+ `Duplicate key "${key}" detected in different parent containers!
317
+ Previous parent: ${existingParentInfo}
318
+ Current parent: ${currentParentInfo}
319
+
320
+ This may cause elements to appear in wrong containers or be moved unexpectedly.
321
+
322
+ Solution: Use unique key prefixes for different locations:
323
+ Example: <wsx-link key="nav-0"> vs <wsx-link key="overflow-0">
324
+
325
+ See https://wsxjs.dev/docs/guide/DOM_CACHE_GUIDE for best practices.`
326
+ );
327
+ }
328
+ }
329
+ if (currentParent) {
330
+ this.keyParentMap.set(key, {
331
+ parentTag: currentParent.tagName.toLowerCase(),
332
+ parentClass: currentParent.className,
333
+ element
334
+ });
335
+ }
336
+ }
337
+ /**
338
+ * Gets a formatted parent container description.
339
+ */
340
+ getParentInfo(parent) {
341
+ const tag = parent.tagName.toLowerCase();
342
+ const className = parent.className;
343
+ return `${tag}${className ? "." + className.split(" ")[0] : ""}`;
344
+ }
345
+ /**
346
+ * Checks if a key exists in the cache.
347
+ */
348
+ has(key) {
349
+ return this.cache.has(key);
350
+ }
351
+ /**
352
+ * Clears the cache.
353
+ * Should be called when component is disconnected or cache is invalidated.
354
+ */
355
+ clear() {
356
+ this.cache.clear();
357
+ }
358
+ /**
359
+ * Stores metadata for an element (e.g. previous props).
360
+ */
361
+ setMetadata(element, meta) {
362
+ this.metadata.set(element, meta);
363
+ }
364
+ /**
365
+ * Retrieves metadata for an element.
366
+ */
367
+ getMetadata(element) {
368
+ return this.metadata.get(element);
369
+ }
370
+ };
371
+
267
372
  // src/base-component.ts
268
373
  var BaseComponent = class extends HTMLElement {
269
374
  constructor(config = {}) {
@@ -271,6 +376,11 @@ var BaseComponent = class extends HTMLElement {
271
376
  this.connected = false;
272
377
  this._isDebugEnabled = false;
273
378
  this._reactiveStates = /* @__PURE__ */ new Map();
379
+ /**
380
+ * DOM Cache Manager for fine-grained updates (RFC 0037)
381
+ * @internal
382
+ */
383
+ this._domCache = new DOMCacheManager();
274
384
  /**
275
385
  * 当前捕获的焦点状态(用于在 render 时使用捕获的值)
276
386
  * @internal - 由 rerender() 方法管理
@@ -335,6 +445,13 @@ var BaseComponent = class extends HTMLElement {
335
445
  static get observedAttributes() {
336
446
  return [];
337
447
  }
448
+ /**
449
+ * Gets the DOMCacheManager instance.
450
+ * @internal
451
+ */
452
+ getDomCache() {
453
+ return this._domCache;
454
+ }
338
455
  /**
339
456
  * Web Component生命周期:属性变化
340
457
  */
@@ -641,8 +758,8 @@ var BaseComponent = class extends HTMLElement {
641
758
  };
642
759
 
643
760
  // src/web-component.ts
644
- import { createLogger as createLogger2 } from "@wsxjs/wsx-logger";
645
- var logger2 = createLogger2("WebComponent");
761
+ import { createLogger as createLogger3 } from "@wsxjs/wsx-logger";
762
+ var logger3 = createLogger3("WebComponent");
646
763
  var WebComponent = class extends BaseComponent {
647
764
  // Initialized by BaseComponent constructor
648
765
  constructor(config = {}) {
@@ -680,7 +797,7 @@ var WebComponent = class extends BaseComponent {
680
797
  const styleName = this.config.styleName || this.constructor.name;
681
798
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
682
799
  }
683
- const content = this.render();
800
+ const content = RenderContext.runInContext(this, () => this.render());
684
801
  this.shadowRoot.appendChild(content);
685
802
  }
686
803
  this.initializeEventListeners();
@@ -691,7 +808,7 @@ var WebComponent = class extends BaseComponent {
691
808
  });
692
809
  }
693
810
  } catch (error) {
694
- logger2.error(`Error in connectedCallback:`, error);
811
+ logger3.error(`Error in connectedCallback:`, error);
695
812
  this.renderError(error);
696
813
  }
697
814
  }
@@ -744,7 +861,7 @@ var WebComponent = class extends BaseComponent {
744
861
  StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
745
862
  }
746
863
  }
747
- const content = this.render();
864
+ const content = RenderContext.runInContext(this, () => this.render());
748
865
  if (focusState && focusState.key && focusState.value !== void 0) {
749
866
  const target = content.querySelector(
750
867
  `[data-wsx-key="${focusState.key}"]`
@@ -760,9 +877,18 @@ var WebComponent = class extends BaseComponent {
760
877
  }
761
878
  requestAnimationFrame(() => {
762
879
  this.shadowRoot.appendChild(content);
763
- const oldChildren = Array.from(this.shadowRoot.children).filter(
764
- (child) => child !== content
765
- );
880
+ const oldChildren = Array.from(this.shadowRoot.children).filter((child) => {
881
+ if (child === content) {
882
+ return false;
883
+ }
884
+ if (child instanceof HTMLStyleElement) {
885
+ return false;
886
+ }
887
+ if (shouldPreserveElement(child)) {
888
+ return false;
889
+ }
890
+ return true;
891
+ });
766
892
  oldChildren.forEach((child) => child.remove());
767
893
  requestAnimationFrame(() => {
768
894
  this.restoreFocusState(focusState);
@@ -772,7 +898,7 @@ var WebComponent = class extends BaseComponent {
772
898
  });
773
899
  });
774
900
  } catch (error) {
775
- logger2.error("Error in _rerender:", error);
901
+ logger3.error("Error in _rerender:", error);
776
902
  this.renderError(error);
777
903
  this._isRendering = false;
778
904
  }
@@ -799,8 +925,8 @@ var WebComponent = class extends BaseComponent {
799
925
  };
800
926
 
801
927
  // src/light-component.ts
802
- import { createLogger as createLogger3 } from "@wsxjs/wsx-logger";
803
- var logger3 = createLogger3("LightComponent");
928
+ import { createLogger as createLogger4 } from "@wsxjs/wsx-logger";
929
+ var logger4 = createLogger4("LightComponent");
804
930
  var LightComponent = class extends BaseComponent {
805
931
  // Initialized by BaseComponent constructor
806
932
  constructor(config = {}) {
@@ -842,7 +968,7 @@ var LightComponent = class extends BaseComponent {
842
968
  (child) => child !== styleElement
843
969
  );
844
970
  childrenToRemove.forEach((child) => child.remove());
845
- const content = this.render();
971
+ const content = RenderContext.runInContext(this, () => this.render());
846
972
  this.appendChild(content);
847
973
  if (styleElement && styleElement !== this.firstChild) {
848
974
  this.insertBefore(styleElement, this.firstChild);
@@ -856,7 +982,7 @@ var LightComponent = class extends BaseComponent {
856
982
  });
857
983
  }
858
984
  } catch (error) {
859
- logger3.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
985
+ logger4.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
860
986
  this.renderError(error);
861
987
  }
862
988
  }
@@ -904,7 +1030,7 @@ var LightComponent = class extends BaseComponent {
904
1030
  this._pendingFocusState = focusState;
905
1031
  const jsxChildren = this.getJSXChildren();
906
1032
  try {
907
- const content = this.render();
1033
+ const content = RenderContext.runInContext(this, () => this.render());
908
1034
  if (focusState && focusState.key && focusState.value !== void 0) {
909
1035
  const target = content.querySelector(
910
1036
  `[data-wsx-key="${focusState.key}"]`
@@ -942,6 +1068,9 @@ var LightComponent = class extends BaseComponent {
942
1068
  if (child instanceof HTMLElement && jsxChildren.includes(child)) {
943
1069
  return false;
944
1070
  }
1071
+ if (shouldPreserveElement(child)) {
1072
+ return false;
1073
+ }
945
1074
  return true;
946
1075
  });
947
1076
  oldChildren.forEach((child) => child.remove());
@@ -961,7 +1090,7 @@ var LightComponent = class extends BaseComponent {
961
1090
  });
962
1091
  });
963
1092
  } catch (error) {
964
- logger3.error(`[${this.constructor.name}] Error in _rerender:`, error);
1093
+ logger4.error(`[${this.constructor.name}] Error in _rerender:`, error);
965
1094
  this.renderError(error);
966
1095
  this._isRendering = false;
967
1096
  }
@@ -1062,7 +1191,7 @@ function deriveTagName(className, prefix) {
1062
1191
  }
1063
1192
 
1064
1193
  // src/index.ts
1065
- import { WSXLogger, logger as logger4, createLogger as createLogger4, createLoggerWithConfig } from "@wsxjs/wsx-logger";
1194
+ import { WSXLogger, logger as logger5, createLogger as createLogger5, createLoggerWithConfig } from "@wsxjs/wsx-logger";
1066
1195
 
1067
1196
  // src/reactive-decorator.ts
1068
1197
  function createBabelPluginError(propertyName) {
@@ -1083,6 +1212,10 @@ To fix this, please:
1083
1212
  See: https://github.com/wsxjs/wsxjs#setup for more details.`;
1084
1213
  }
1085
1214
  function state(targetOrContext, propertyKey) {
1215
+ const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
1216
+ if (globalProcess?.env?.NODE_ENV === "test") {
1217
+ return;
1218
+ }
1086
1219
  let propertyName = "unknown";
1087
1220
  const propertyKeyIsObject = typeof propertyKey === "object" && propertyKey !== null;
1088
1221
  const targetIsObject = typeof targetOrContext === "object" && targetOrContext !== null;
@@ -1143,12 +1276,12 @@ export {
1143
1276
  WSXLogger,
1144
1277
  WebComponent,
1145
1278
  autoRegister,
1146
- createLogger4 as createLogger,
1279
+ createLogger5 as createLogger,
1147
1280
  createLoggerWithConfig,
1148
1281
  h,
1149
1282
  h as jsx,
1150
1283
  h as jsxs,
1151
- logger4 as logger,
1284
+ logger5 as logger,
1152
1285
  registerComponent,
1153
1286
  state
1154
1287
  };