@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/jsx.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  h
4
- } from "./chunk-7FXISNME.mjs";
4
+ } from "./chunk-AR3DIDLV.mjs";
5
5
  export {
6
6
  Fragment,
7
7
  h
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-core",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Core WSXJS - Web Components with JSX syntax",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -48,7 +48,7 @@
48
48
  "custom-elements"
49
49
  ],
50
50
  "dependencies": {
51
- "@wsxjs/wsx-logger": "0.0.20"
51
+ "@wsxjs/wsx-logger": "0.0.21"
52
52
  },
53
53
  "devDependencies": {
54
54
  "tsup": "^8.0.0",
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { reactive as createReactive, createState, reactiveWithDebug } from "./utils/reactive";
12
+ import { DOMCacheManager } from "./dom-cache-manager";
12
13
 
13
14
  /**
14
15
  * Type for reactive state storage
@@ -60,6 +61,12 @@ export abstract class BaseComponent extends HTMLElement {
60
61
  */
61
62
  protected _autoStyles?: string;
62
63
 
64
+ /**
65
+ * DOM Cache Manager for fine-grained updates (RFC 0037)
66
+ * @internal
67
+ */
68
+ protected _domCache = new DOMCacheManager();
69
+
63
70
  /**
64
71
  * 当前捕获的焦点状态(用于在 render 时使用捕获的值)
65
72
  * @internal - 由 rerender() 方法管理
@@ -138,6 +145,14 @@ export abstract class BaseComponent extends HTMLElement {
138
145
  */
139
146
  protected onRendered?(): void;
140
147
 
148
+ /**
149
+ * Gets the DOMCacheManager instance.
150
+ * @internal
151
+ */
152
+ public getDomCache(): DOMCacheManager {
153
+ return this._domCache;
154
+ }
155
+
141
156
  /**
142
157
  * 处理 blur 事件,在用户停止输入时执行待处理的重渲染
143
158
  * @internal
@@ -0,0 +1,135 @@
1
+ import { createLogger } from "./utils/logger";
2
+
3
+ const logger = createLogger("DOMCacheManager");
4
+
5
+ /**
6
+ * Parent container information for duplicate key detection
7
+ */
8
+ interface ParentInfo {
9
+ parentTag: string;
10
+ parentClass: string;
11
+ element: Element;
12
+ }
13
+
14
+ /**
15
+ * DOMCacheManager
16
+ *
17
+ * Manages DOM element caching for fine-grained updates (RFC 0037).
18
+ * Stores elements by unique keys derived from component ID + position/key.
19
+ */
20
+ export class DOMCacheManager {
21
+ // Map<CacheKey, DOMElement>
22
+ private cache = new Map<string, Element>();
23
+
24
+ // Map<DOMElement, Metadata>
25
+ // Stores metadata (props, children) for cached elements to support diffing
26
+ private metadata = new WeakMap<Element, Record<string, unknown>>();
27
+
28
+ // Track key-parent relationships to detect duplicate keys in all environments
29
+ // Map<CacheKey, ParentInfo>
30
+ private keyParentMap = new Map<string, ParentInfo>();
31
+
32
+ // Flag to enable duplicate key warnings (enabled by default, critical for correctness)
33
+ private warnDuplicateKeys = true;
34
+
35
+ /**
36
+ * Retrieves an element from the cache.
37
+ * @param key The unique cache key.
38
+ */
39
+ get(key: string): Element | undefined {
40
+ return this.cache.get(key);
41
+ }
42
+
43
+ /**
44
+ * Stores an element in the cache.
45
+ * @param key The unique cache key.
46
+ * @param element The DOM element to cache.
47
+ */
48
+ set(key: string, element: Element): void {
49
+ // Always check for duplicate keys (critical for correctness)
50
+ if (this.warnDuplicateKeys) {
51
+ this.checkDuplicateKey(key, element);
52
+ }
53
+
54
+ this.cache.set(key, element);
55
+ }
56
+
57
+ /**
58
+ * Checks if a cache key is being reused in a different parent container.
59
+ * Runs in all environments to help developers catch key conflicts early.
60
+ * This is critical for correctness and helps prevent subtle bugs.
61
+ */
62
+ private checkDuplicateKey(key: string, element: Element): void {
63
+ const existing = this.keyParentMap.get(key);
64
+ const currentParent = element.parentElement;
65
+
66
+ if (existing && currentParent) {
67
+ const currentParentInfo = this.getParentInfo(currentParent);
68
+ const existingParentInfo = `${existing.parentTag}${existing.parentClass ? "." + existing.parentClass : ""}`;
69
+
70
+ // Check if the element is being used in a different parent container
71
+ if (currentParentInfo !== existingParentInfo) {
72
+ logger.warn(
73
+ `Duplicate key "${key}" detected in different parent containers!\n` +
74
+ ` Previous parent: ${existingParentInfo}\n` +
75
+ ` Current parent: ${currentParentInfo}\n` +
76
+ `\n` +
77
+ `This may cause elements to appear in wrong containers or be moved unexpectedly.\n` +
78
+ `\n` +
79
+ `Solution: Use unique key prefixes for different locations:\n` +
80
+ ` Example: <wsx-link key="nav-0"> vs <wsx-link key="overflow-0">\n` +
81
+ `\n` +
82
+ `See https://wsxjs.dev/docs/guide/DOM_CACHE_GUIDE for best practices.`
83
+ );
84
+ }
85
+ }
86
+
87
+ // Track this key-parent relationship for future checks
88
+ if (currentParent) {
89
+ this.keyParentMap.set(key, {
90
+ parentTag: currentParent.tagName.toLowerCase(),
91
+ parentClass: currentParent.className,
92
+ element,
93
+ });
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Gets a formatted parent container description.
99
+ */
100
+ private getParentInfo(parent: Element): string {
101
+ const tag = parent.tagName.toLowerCase();
102
+ const className = parent.className;
103
+ return `${tag}${className ? "." + className.split(" ")[0] : ""}`;
104
+ }
105
+
106
+ /**
107
+ * Checks if a key exists in the cache.
108
+ */
109
+ has(key: string): boolean {
110
+ return this.cache.has(key);
111
+ }
112
+
113
+ /**
114
+ * Clears the cache.
115
+ * Should be called when component is disconnected or cache is invalidated.
116
+ */
117
+ clear(): void {
118
+ this.cache.clear();
119
+ // WeakMap doesn't need clearing
120
+ }
121
+
122
+ /**
123
+ * Stores metadata for an element (e.g. previous props).
124
+ */
125
+ setMetadata(element: Element, meta: Record<string, unknown>): void {
126
+ this.metadata.set(element, meta);
127
+ }
128
+
129
+ /**
130
+ * Retrieves metadata for an element.
131
+ */
132
+ getMetadata(element: Element): Record<string, unknown> | undefined {
133
+ return this.metadata.get(element);
134
+ }
135
+ }