@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/chunk-OXFZ575O.mjs +1091 -0
- package/dist/index.js +869 -113
- package/dist/index.mjs +155 -22
- package/dist/jsx-runtime.js +723 -97
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +723 -97
- package/dist/jsx.mjs +1 -1
- package/package.json +2 -2
- package/src/base-component.ts +15 -0
- package/src/dom-cache-manager.ts +135 -0
- package/src/jsx-factory.ts +133 -447
- package/src/light-component.ts +12 -4
- package/src/reactive-decorator.ts +9 -0
- package/src/render-context.ts +40 -0
- package/src/utils/cache-key.ts +114 -0
- package/src/utils/dom-utils.ts +119 -0
- package/src/utils/element-creation.ts +140 -0
- package/src/utils/element-marking.ts +80 -0
- package/src/utils/element-update.ts +633 -0
- package/src/utils/props-utils.ts +307 -0
- package/src/web-component.ts +24 -6
- package/dist/chunk-7FXISNME.mjs +0 -462
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.js
CHANGED
|
@@ -37,6 +37,174 @@ __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 getElementCacheKey(element) {
|
|
186
|
+
const key = element[CACHE_KEY_PROP];
|
|
187
|
+
return key !== void 0 ? String(key) : null;
|
|
188
|
+
}
|
|
189
|
+
function isCreatedByH(element) {
|
|
190
|
+
if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return element[CACHE_KEY_PROP] !== void 0;
|
|
194
|
+
}
|
|
195
|
+
function shouldPreserveElement(element) {
|
|
196
|
+
if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (!isCreatedByH(element)) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (element.hasAttribute("data-wsx-preserve")) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
40
208
|
// src/utils/svg-utils.ts
|
|
41
209
|
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
42
210
|
var SVG_ONLY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -149,21 +317,63 @@ function getSVGAttributeName(attributeName) {
|
|
|
149
317
|
return SVG_ATTRIBUTE_MAP.get(attributeName) || attributeName;
|
|
150
318
|
}
|
|
151
319
|
|
|
152
|
-
// src/utils/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
320
|
+
// src/utils/logger.ts
|
|
321
|
+
var WSXLogger = class {
|
|
322
|
+
constructor(prefix = "[WSX]", enabled = true, level = "info") {
|
|
323
|
+
this.prefix = prefix;
|
|
324
|
+
this.enabled = enabled;
|
|
325
|
+
this.level = level;
|
|
326
|
+
}
|
|
327
|
+
shouldLog(level) {
|
|
328
|
+
if (!this.enabled) return false;
|
|
329
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
330
|
+
const currentLevelIndex = levels.indexOf(this.level);
|
|
331
|
+
const messageLevelIndex = levels.indexOf(level);
|
|
332
|
+
return messageLevelIndex >= currentLevelIndex;
|
|
333
|
+
}
|
|
334
|
+
debug(message, ...args) {
|
|
335
|
+
if (this.shouldLog("debug")) {
|
|
336
|
+
console.debug(`${this.prefix} ${message}`, ...args);
|
|
162
337
|
}
|
|
163
|
-
}
|
|
338
|
+
}
|
|
339
|
+
info(message, ...args) {
|
|
340
|
+
if (this.shouldLog("info")) {
|
|
341
|
+
console.info(`${this.prefix} ${message}`, ...args);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
warn(message, ...args) {
|
|
345
|
+
if (this.shouldLog("warn")) {
|
|
346
|
+
console.warn(`${this.prefix} ${message}`, ...args);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
error(message, ...args) {
|
|
350
|
+
if (this.shouldLog("error")) {
|
|
351
|
+
console.error(`${this.prefix} ${message}`, ...args);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var logger = new WSXLogger();
|
|
356
|
+
function createLogger(componentName) {
|
|
357
|
+
return new WSXLogger(`[WSX:${componentName}]`);
|
|
164
358
|
}
|
|
165
359
|
|
|
166
|
-
// src/
|
|
360
|
+
// src/utils/props-utils.ts
|
|
361
|
+
var logger2 = createLogger("Props Utilities");
|
|
362
|
+
function isFrameworkInternalProp(key) {
|
|
363
|
+
if (key === "key") {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
if (key === "__wsxPositionId" || key === "__wsxIndex") {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
if (key === "__testId") {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (key === "ref") {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
167
377
|
function isStandardHTMLAttribute(key) {
|
|
168
378
|
const standardAttributes = /* @__PURE__ */ new Set([
|
|
169
379
|
// 全局属性
|
|
@@ -246,7 +456,8 @@ function isStandardHTMLAttribute(key) {
|
|
|
246
456
|
function isSpecialProperty(key, value) {
|
|
247
457
|
return key === "ref" || key === "className" || key === "class" || key === "style" || key.startsWith("on") && typeof value === "function" || typeof value === "boolean" || key === "value";
|
|
248
458
|
}
|
|
249
|
-
function setSmartProperty(element, key, value,
|
|
459
|
+
function setSmartProperty(element, key, value, tag) {
|
|
460
|
+
const isSVG = shouldUseSVGNamespace(tag);
|
|
250
461
|
if (isSpecialProperty(key, value)) {
|
|
251
462
|
return;
|
|
252
463
|
}
|
|
@@ -256,13 +467,13 @@ function setSmartProperty(element, key, value, isSVG = false) {
|
|
|
256
467
|
try {
|
|
257
468
|
const serialized = JSON.stringify(value);
|
|
258
469
|
if (serialized.length > 1024 * 1024) {
|
|
259
|
-
|
|
470
|
+
logger2.warn(
|
|
260
471
|
`[WSX] Attribute "${key}" value too large, consider using a non-standard property name instead`
|
|
261
472
|
);
|
|
262
473
|
}
|
|
263
474
|
element.setAttribute(attributeName, serialized);
|
|
264
475
|
} catch (error) {
|
|
265
|
-
|
|
476
|
+
logger2.warn(`Cannot serialize attribute "${key}":`, error);
|
|
266
477
|
}
|
|
267
478
|
} else {
|
|
268
479
|
element.setAttribute(attributeName, String(value));
|
|
@@ -276,7 +487,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
|
|
|
276
487
|
const serialized = JSON.stringify(value);
|
|
277
488
|
element.setAttribute(attributeName, serialized);
|
|
278
489
|
} catch (error) {
|
|
279
|
-
|
|
490
|
+
logger2.warn(`Cannot serialize SVG attribute "${key}":`, error);
|
|
280
491
|
}
|
|
281
492
|
} else {
|
|
282
493
|
element.setAttribute(attributeName, String(value));
|
|
@@ -300,7 +511,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
|
|
|
300
511
|
const serialized = JSON.stringify(value);
|
|
301
512
|
element.setAttribute(attributeName, serialized);
|
|
302
513
|
} catch (error) {
|
|
303
|
-
|
|
514
|
+
logger2.warn(`Cannot serialize readonly property "${key}":`, error);
|
|
304
515
|
}
|
|
305
516
|
} else {
|
|
306
517
|
element.setAttribute(attributeName, String(value));
|
|
@@ -315,7 +526,7 @@ function setSmartProperty(element, key, value, isSVG = false) {
|
|
|
315
526
|
const serialized = JSON.stringify(value);
|
|
316
527
|
element.setAttribute(attributeName, serialized);
|
|
317
528
|
} catch (error) {
|
|
318
|
-
|
|
529
|
+
logger2.warn(
|
|
319
530
|
`[WSX] Cannot serialize property "${key}" for attribute:`,
|
|
320
531
|
error
|
|
321
532
|
);
|
|
@@ -331,59 +542,76 @@ function setSmartProperty(element, key, value, isSVG = false) {
|
|
|
331
542
|
try {
|
|
332
543
|
const serialized = JSON.stringify(value);
|
|
333
544
|
if (serialized.length > 1024 * 1024) {
|
|
334
|
-
|
|
545
|
+
logger2.warn(
|
|
335
546
|
`[WSX] Property "${key}" value too large for attribute, consider using a JavaScript property instead`
|
|
336
547
|
);
|
|
337
548
|
}
|
|
338
549
|
element.setAttribute(attributeName, serialized);
|
|
339
550
|
} catch (error) {
|
|
340
|
-
|
|
551
|
+
logger2.warn(`Cannot serialize property "${key}" for attribute:`, error);
|
|
341
552
|
}
|
|
342
553
|
} else {
|
|
343
554
|
element.setAttribute(attributeName, String(value));
|
|
344
555
|
}
|
|
345
556
|
}
|
|
346
557
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
558
|
+
|
|
559
|
+
// src/utils/element-creation.ts
|
|
560
|
+
function applySingleProp(element, key, value, tag, isSVG) {
|
|
561
|
+
if (value === null || value === void 0 || value === false) {
|
|
562
|
+
return;
|
|
350
563
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
});
|
|
564
|
+
if (key === "ref" && typeof value === "function") {
|
|
565
|
+
value(element);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (key === "className" || key === "class") {
|
|
569
|
+
if (isSVG) {
|
|
570
|
+
element.setAttribute("class", value);
|
|
571
|
+
} else {
|
|
572
|
+
element.className = value;
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
386
575
|
}
|
|
576
|
+
if (key === "style" && typeof value === "string") {
|
|
577
|
+
element.setAttribute("style", value);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
581
|
+
const eventName = key.slice(2).toLowerCase();
|
|
582
|
+
element.addEventListener(eventName, value);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (typeof value === "boolean") {
|
|
586
|
+
if (value) {
|
|
587
|
+
element.setAttribute(key, "");
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (key === "value") {
|
|
592
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
|
|
593
|
+
element.value = String(value);
|
|
594
|
+
} else {
|
|
595
|
+
const attributeName = isSVG ? getSVGAttributeName(key) : key;
|
|
596
|
+
element.setAttribute(attributeName, String(value));
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (isFrameworkInternalProp(key)) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
setSmartProperty(element, key, value, tag);
|
|
604
|
+
}
|
|
605
|
+
function applyPropsToElement(element, props, tag) {
|
|
606
|
+
if (!props) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const isSVG = shouldUseSVGNamespace(tag);
|
|
610
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
611
|
+
applySingleProp(element, key, value, tag, isSVG);
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
function appendChildrenToElement(element, children) {
|
|
387
615
|
const flatChildren = flattenChildren(children);
|
|
388
616
|
flatChildren.forEach((child) => {
|
|
389
617
|
if (child === null || child === void 0 || child === false) {
|
|
@@ -397,60 +625,458 @@ function h(tag, props = {}, ...children) {
|
|
|
397
625
|
element.appendChild(child);
|
|
398
626
|
}
|
|
399
627
|
});
|
|
628
|
+
}
|
|
629
|
+
function createElementWithPropsAndChildren(tag, props, children) {
|
|
630
|
+
const element = createElement(tag);
|
|
631
|
+
applyPropsToElement(element, props, tag);
|
|
632
|
+
appendChildrenToElement(element, children);
|
|
400
633
|
return element;
|
|
401
634
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
635
|
+
|
|
636
|
+
// src/utils/element-update.ts
|
|
637
|
+
function removeProp(element, key, oldValue, tag) {
|
|
638
|
+
const isSVG = shouldUseSVGNamespace(tag);
|
|
639
|
+
if (key === "ref") {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (key === "className" || key === "class") {
|
|
643
|
+
if (isSVG) {
|
|
644
|
+
element.removeAttribute("class");
|
|
645
|
+
} else {
|
|
646
|
+
element.className = "";
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (key === "style") {
|
|
651
|
+
element.removeAttribute("style");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (key.startsWith("on") && typeof oldValue === "function") {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (key === "value") {
|
|
658
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
|
|
659
|
+
element.value = "";
|
|
660
|
+
} else {
|
|
661
|
+
const attributeName2 = isSVG ? getSVGAttributeName(key) : key;
|
|
662
|
+
element.removeAttribute(attributeName2);
|
|
663
|
+
}
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (isFrameworkInternalProp(key)) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const attributeName = isSVG ? getSVGAttributeName(key) : key;
|
|
670
|
+
element.removeAttribute(attributeName);
|
|
671
|
+
try {
|
|
672
|
+
delete element[key];
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
409
675
|
}
|
|
410
|
-
function
|
|
411
|
-
if (
|
|
412
|
-
|
|
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
|
-
);
|
|
676
|
+
function applySingleProp2(element, key, value, tag, isSVG) {
|
|
677
|
+
if (value === null || value === void 0 || value === false) {
|
|
678
|
+
return;
|
|
418
679
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
680
|
+
if (key === "ref" && typeof value === "function") {
|
|
681
|
+
value(element);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (key === "className" || key === "class") {
|
|
685
|
+
if (isSVG) {
|
|
686
|
+
element.setAttribute("class", value);
|
|
687
|
+
} else {
|
|
688
|
+
element.className = value;
|
|
689
|
+
}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (key === "style" && typeof value === "string") {
|
|
693
|
+
element.setAttribute("style", value);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
697
|
+
const eventName = key.slice(2).toLowerCase();
|
|
698
|
+
element.addEventListener(eventName, value);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (typeof value === "boolean") {
|
|
702
|
+
if (value) {
|
|
703
|
+
element.setAttribute(key, "");
|
|
704
|
+
}
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (key === "value") {
|
|
708
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
|
|
709
|
+
element.value = String(value);
|
|
710
|
+
} else {
|
|
711
|
+
const attributeName = isSVG ? getSVGAttributeName(key) : key;
|
|
712
|
+
element.setAttribute(attributeName, String(value));
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (isFrameworkInternalProp(key)) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
setSmartProperty(element, key, value, tag);
|
|
720
|
+
}
|
|
721
|
+
function updateProps(element, oldProps, newProps, tag) {
|
|
722
|
+
const isSVG = shouldUseSVGNamespace(tag);
|
|
723
|
+
const old = oldProps || {};
|
|
724
|
+
const new_ = newProps || {};
|
|
725
|
+
for (const key in old) {
|
|
726
|
+
if (!(key in new_)) {
|
|
727
|
+
removeProp(element, key, old[key], tag);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
for (const key in new_) {
|
|
731
|
+
const oldValue = old[key];
|
|
732
|
+
const newValue = new_[key];
|
|
733
|
+
if (oldValue === newValue) {
|
|
422
734
|
continue;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
735
|
+
}
|
|
736
|
+
if (oldValue === void 0) {
|
|
737
|
+
applySingleProp2(element, key, newValue, tag, isSVG);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (typeof oldValue === "object" && oldValue !== null && typeof newValue === "object" && newValue !== null) {
|
|
741
|
+
try {
|
|
742
|
+
const oldJson = JSON.stringify(oldValue);
|
|
743
|
+
const newJson = JSON.stringify(newValue);
|
|
744
|
+
if (oldJson === newJson) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
applySingleProp2(element, key, newValue, tag, isSVG);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function updateChildren(element, oldChildren, newChildren) {
|
|
754
|
+
const flatOld = flattenChildren(oldChildren);
|
|
755
|
+
const flatNew = flattenChildren(newChildren);
|
|
756
|
+
const minLength = Math.min(flatOld.length, flatNew.length);
|
|
757
|
+
let domIndex = 0;
|
|
758
|
+
for (let i = 0; i < minLength; i++) {
|
|
759
|
+
const oldChild = flatOld[i];
|
|
760
|
+
const newChild = flatNew[i];
|
|
761
|
+
let oldNode = null;
|
|
762
|
+
if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
|
|
763
|
+
if (oldChild.parentNode === element) {
|
|
764
|
+
if (!shouldPreserveElement(oldChild)) {
|
|
765
|
+
oldNode = oldChild;
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
const oldCacheKey = getElementCacheKey(oldChild);
|
|
769
|
+
if (oldCacheKey) {
|
|
770
|
+
for (let j = 0; j < element.childNodes.length; j++) {
|
|
771
|
+
const domChild = element.childNodes[j];
|
|
772
|
+
if (domChild instanceof HTMLElement || domChild instanceof SVGElement) {
|
|
773
|
+
if (shouldPreserveElement(domChild)) {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const domCacheKey = getElementCacheKey(domChild);
|
|
777
|
+
if (domCacheKey === oldCacheKey) {
|
|
778
|
+
oldNode = domChild;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} else if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
786
|
+
while (domIndex < element.childNodes.length) {
|
|
787
|
+
const node = element.childNodes[domIndex];
|
|
788
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
789
|
+
oldNode = node;
|
|
790
|
+
domIndex++;
|
|
791
|
+
break;
|
|
792
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
793
|
+
domIndex++;
|
|
794
|
+
} else {
|
|
795
|
+
domIndex++;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
800
|
+
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
801
|
+
const oldText = String(oldChild);
|
|
802
|
+
const newText = String(newChild);
|
|
803
|
+
const needsUpdate = oldText !== newText || oldNode && oldNode.nodeType === Node.TEXT_NODE && oldNode.textContent !== newText;
|
|
804
|
+
if (!needsUpdate) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (oldNode && oldNode.nodeType === Node.TEXT_NODE) {
|
|
808
|
+
oldNode.textContent = newText;
|
|
809
|
+
} else {
|
|
810
|
+
const newTextNode = document.createTextNode(newText);
|
|
811
|
+
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
812
|
+
element.replaceChild(newTextNode, oldNode);
|
|
813
|
+
} else {
|
|
814
|
+
element.insertBefore(newTextNode, oldNode || null);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
819
|
+
element.removeChild(oldNode);
|
|
820
|
+
}
|
|
821
|
+
if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
822
|
+
if (newChild.parentNode !== element) {
|
|
823
|
+
element.insertBefore(newChild, oldNode || null);
|
|
824
|
+
}
|
|
825
|
+
} else if (newChild instanceof DocumentFragment) {
|
|
826
|
+
element.insertBefore(newChild, oldNode || null);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
} else if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
|
|
830
|
+
if (oldNode && shouldPreserveElement(oldNode)) {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (newChild === oldChild) {
|
|
834
|
+
continue;
|
|
835
|
+
} else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
836
|
+
const oldCacheKey = oldNode && (oldNode instanceof HTMLElement || oldNode instanceof SVGElement) ? getElementCacheKey(oldNode) : null;
|
|
837
|
+
const newCacheKey = getElementCacheKey(newChild);
|
|
838
|
+
const hasSameCacheKey = oldCacheKey && newCacheKey && oldCacheKey === newCacheKey;
|
|
839
|
+
if (oldNode) {
|
|
840
|
+
if (!shouldPreserveElement(oldNode)) {
|
|
841
|
+
if (oldNode !== newChild) {
|
|
842
|
+
if (newChild.parentNode === element) {
|
|
843
|
+
if (hasSameCacheKey) {
|
|
844
|
+
if (newChild !== oldNode) {
|
|
845
|
+
element.replaceChild(newChild, oldNode);
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
element.removeChild(newChild);
|
|
849
|
+
element.replaceChild(newChild, oldNode);
|
|
850
|
+
}
|
|
851
|
+
} else if (newChild.parentNode) {
|
|
852
|
+
newChild.parentNode.removeChild(newChild);
|
|
853
|
+
element.replaceChild(newChild, oldNode);
|
|
435
854
|
} else {
|
|
436
|
-
|
|
855
|
+
element.replaceChild(newChild, oldNode);
|
|
437
856
|
}
|
|
438
857
|
}
|
|
439
858
|
} else {
|
|
440
|
-
|
|
859
|
+
if (newChild.parentNode !== element) {
|
|
860
|
+
if (newChild.parentNode) {
|
|
861
|
+
newChild.parentNode.removeChild(newChild);
|
|
862
|
+
}
|
|
863
|
+
element.insertBefore(newChild, oldNode.nextSibling);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
} else {
|
|
867
|
+
if (newChild.parentNode !== element) {
|
|
868
|
+
if (newChild.parentNode) {
|
|
869
|
+
newChild.parentNode.removeChild(newChild);
|
|
870
|
+
}
|
|
871
|
+
element.appendChild(newChild);
|
|
441
872
|
}
|
|
442
|
-
} catch (error) {
|
|
443
|
-
console.warn("[WSX] Failed to parse HTML string, treating as text:", error);
|
|
444
|
-
result.push(child);
|
|
445
873
|
}
|
|
446
874
|
} else {
|
|
447
|
-
|
|
875
|
+
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
876
|
+
element.removeChild(oldNode);
|
|
877
|
+
}
|
|
878
|
+
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
879
|
+
const newTextNode = document.createTextNode(String(newChild));
|
|
880
|
+
element.insertBefore(newTextNode, oldNode?.nextSibling || null);
|
|
881
|
+
} else if (newChild instanceof DocumentFragment) {
|
|
882
|
+
element.insertBefore(newChild, oldNode?.nextSibling || null);
|
|
883
|
+
}
|
|
448
884
|
}
|
|
449
|
-
} else {
|
|
450
|
-
result.push(child);
|
|
451
885
|
}
|
|
452
886
|
}
|
|
453
|
-
|
|
887
|
+
for (let i = minLength; i < flatNew.length; i++) {
|
|
888
|
+
const newChild = flatNew[i];
|
|
889
|
+
if (newChild === null || newChild === void 0 || newChild === false) {
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
893
|
+
element.appendChild(document.createTextNode(String(newChild)));
|
|
894
|
+
} else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
895
|
+
if (newChild.parentNode === element) {
|
|
896
|
+
const currentIndex = Array.from(element.childNodes).indexOf(newChild);
|
|
897
|
+
const expectedIndex = element.childNodes.length - 1;
|
|
898
|
+
if (currentIndex !== expectedIndex) {
|
|
899
|
+
element.removeChild(newChild);
|
|
900
|
+
element.appendChild(newChild);
|
|
901
|
+
}
|
|
902
|
+
continue;
|
|
903
|
+
} else if (newChild.parentNode) {
|
|
904
|
+
newChild.parentNode.removeChild(newChild);
|
|
905
|
+
}
|
|
906
|
+
element.appendChild(newChild);
|
|
907
|
+
} else if (newChild instanceof DocumentFragment) {
|
|
908
|
+
element.appendChild(newChild);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
const nodesToRemove = [];
|
|
912
|
+
const newChildSet = /* @__PURE__ */ new Set();
|
|
913
|
+
const newChildCacheKeyMap = /* @__PURE__ */ new Map();
|
|
914
|
+
for (const child of flatNew) {
|
|
915
|
+
if (child instanceof HTMLElement || child instanceof SVGElement || child instanceof DocumentFragment) {
|
|
916
|
+
newChildSet.add(child);
|
|
917
|
+
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
918
|
+
const cacheKey = getElementCacheKey(child);
|
|
919
|
+
if (cacheKey) {
|
|
920
|
+
newChildCacheKeyMap.set(cacheKey, child);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const processedCacheKeys = /* @__PURE__ */ new Set();
|
|
926
|
+
const newChildToIndexMap = /* @__PURE__ */ new Map();
|
|
927
|
+
for (let i = 0; i < flatNew.length; i++) {
|
|
928
|
+
const child = flatNew[i];
|
|
929
|
+
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
930
|
+
newChildToIndexMap.set(child, i);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
for (let i = element.childNodes.length - 1; i >= 0; i--) {
|
|
934
|
+
const child = element.childNodes[i];
|
|
935
|
+
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
936
|
+
if (shouldPreserveElement(child)) {
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
const cacheKey = getElementCacheKey(child);
|
|
940
|
+
if (cacheKey && newChildCacheKeyMap.has(cacheKey) && !processedCacheKeys.has(cacheKey)) {
|
|
941
|
+
processedCacheKeys.add(cacheKey);
|
|
942
|
+
const newChild = newChildCacheKeyMap.get(cacheKey);
|
|
943
|
+
if (child !== newChild) {
|
|
944
|
+
if (newChild.parentNode === element) {
|
|
945
|
+
element.replaceChild(newChild, child);
|
|
946
|
+
} else {
|
|
947
|
+
element.replaceChild(newChild, child);
|
|
948
|
+
}
|
|
949
|
+
} else {
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
for (let i = 0; i < element.childNodes.length; i++) {
|
|
955
|
+
const child = element.childNodes[i];
|
|
956
|
+
if (shouldPreserveElement(child)) {
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
960
|
+
if (newChildSet.has(child)) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
const cacheKey = getElementCacheKey(child);
|
|
964
|
+
if (cacheKey && newChildCacheKeyMap.has(cacheKey)) {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
} else if (child instanceof DocumentFragment) {
|
|
968
|
+
if (newChildSet.has(child)) {
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
nodesToRemove.push(child);
|
|
973
|
+
}
|
|
974
|
+
for (let i = nodesToRemove.length - 1; i >= 0; i--) {
|
|
975
|
+
const node = nodesToRemove[i];
|
|
976
|
+
if (node.parentNode === element) {
|
|
977
|
+
element.removeChild(node);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function updateElement(element, newProps, newChildren, tag, cacheManager) {
|
|
982
|
+
const oldMetadata = cacheManager.getMetadata(element);
|
|
983
|
+
const oldProps = oldMetadata?.props || null;
|
|
984
|
+
const oldChildren = oldMetadata?.children || [];
|
|
985
|
+
cacheManager.setMetadata(element, {
|
|
986
|
+
props: newProps || {},
|
|
987
|
+
children: newChildren
|
|
988
|
+
});
|
|
989
|
+
updateProps(element, oldProps, newProps, tag);
|
|
990
|
+
updateChildren(element, oldChildren, newChildren);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/jsx-factory.ts
|
|
994
|
+
var logger3 = createLogger("JSX Factory");
|
|
995
|
+
function h(tag, props = {}, ...children) {
|
|
996
|
+
if (typeof tag === "function") {
|
|
997
|
+
return tag(props, children);
|
|
998
|
+
}
|
|
999
|
+
const context = RenderContext.getCurrentComponent();
|
|
1000
|
+
const cacheManager = context ? RenderContext.getDOMCache() : null;
|
|
1001
|
+
if (context && cacheManager) {
|
|
1002
|
+
return tryUseCacheOrCreate(tag, props, children, context, cacheManager);
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
1005
|
+
const nodeEnv = typeof globalThis.process !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1006
|
+
globalThis.process.env?.NODE_ENV;
|
|
1007
|
+
if (nodeEnv === "development") {
|
|
1008
|
+
if (!context) {
|
|
1009
|
+
logger3.debug(
|
|
1010
|
+
`h() called without render context. Tag: "${tag}", ComponentId: "${getComponentId()}"`,
|
|
1011
|
+
{
|
|
1012
|
+
tag,
|
|
1013
|
+
props: props ? Object.keys(props) : [],
|
|
1014
|
+
hasCacheManager: !!cacheManager
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
} else if (!cacheManager) {
|
|
1018
|
+
logger3.debug(
|
|
1019
|
+
`h() called with context but no cache manager. Tag: "${tag}", Component: "${context.constructor.name}"`,
|
|
1020
|
+
{
|
|
1021
|
+
tag,
|
|
1022
|
+
component: context.constructor.name
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
const element = createElementWithPropsAndChildren(tag, props, children);
|
|
1030
|
+
const componentId = getComponentId();
|
|
1031
|
+
const cacheKey = generateCacheKey(tag, props, componentId, context || void 0);
|
|
1032
|
+
markElement(element, cacheKey);
|
|
1033
|
+
return element;
|
|
1034
|
+
}
|
|
1035
|
+
function tryUseCacheOrCreate(tag, props, children, context, cacheManager) {
|
|
1036
|
+
try {
|
|
1037
|
+
const componentId = getComponentId();
|
|
1038
|
+
const cacheKey = generateCacheKey(tag, props, componentId, context);
|
|
1039
|
+
const cachedElement = cacheManager.get(cacheKey);
|
|
1040
|
+
if (cachedElement) {
|
|
1041
|
+
const element2 = cachedElement;
|
|
1042
|
+
updateElement(element2, props, children, tag, cacheManager);
|
|
1043
|
+
const isCustomElement = tag.includes("-") && customElements.get(tag);
|
|
1044
|
+
if (isCustomElement && element2.isConnected) {
|
|
1045
|
+
const parent = element2.parentNode;
|
|
1046
|
+
if (parent) {
|
|
1047
|
+
parent.removeChild(element2);
|
|
1048
|
+
parent.appendChild(element2);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return element2;
|
|
1052
|
+
}
|
|
1053
|
+
const element = createElementWithPropsAndChildren(tag, props, children);
|
|
1054
|
+
cacheManager.set(cacheKey, element);
|
|
1055
|
+
markElement(element, cacheKey);
|
|
1056
|
+
cacheManager.setMetadata(element, {
|
|
1057
|
+
props: props || {},
|
|
1058
|
+
children
|
|
1059
|
+
});
|
|
1060
|
+
return element;
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
return handleCacheError(error, tag, props, children);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function handleCacheError(error, tag, props, children) {
|
|
1066
|
+
try {
|
|
1067
|
+
const nodeEnv = typeof globalThis.process !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1068
|
+
globalThis.process.env?.NODE_ENV;
|
|
1069
|
+
if (nodeEnv === "development") {
|
|
1070
|
+
logger3.warn("[WSX DOM Cache] Cache error, falling back to create new element:", error);
|
|
1071
|
+
}
|
|
1072
|
+
} catch {
|
|
1073
|
+
}
|
|
1074
|
+
const element = createElementWithPropsAndChildren(tag, props, children);
|
|
1075
|
+
const context = RenderContext.getCurrentComponent();
|
|
1076
|
+
const componentId = getComponentId();
|
|
1077
|
+
const cacheKey = generateCacheKey(tag, props, componentId, context || void 0);
|
|
1078
|
+
markElement(element, cacheKey);
|
|
1079
|
+
return element;
|
|
454
1080
|
}
|
|
455
1081
|
function Fragment(_props, children) {
|
|
456
1082
|
const fragment = document.createDocumentFragment();
|
|
@@ -512,7 +1138,7 @@ StyleManager.styleSheets = /* @__PURE__ */ new Map();
|
|
|
512
1138
|
|
|
513
1139
|
// src/utils/reactive.ts
|
|
514
1140
|
var import_wsx_logger = require("@wsxjs/wsx-logger");
|
|
515
|
-
var
|
|
1141
|
+
var logger4 = (0, import_wsx_logger.createLogger)("ReactiveSystem");
|
|
516
1142
|
var UpdateScheduler = class {
|
|
517
1143
|
constructor() {
|
|
518
1144
|
this.pendingCallbacks = /* @__PURE__ */ new Set();
|
|
@@ -541,7 +1167,7 @@ var UpdateScheduler = class {
|
|
|
541
1167
|
try {
|
|
542
1168
|
callback();
|
|
543
1169
|
} catch (error) {
|
|
544
|
-
|
|
1170
|
+
logger4.error("[WSX Reactive] Error in callback:", error);
|
|
545
1171
|
}
|
|
546
1172
|
});
|
|
547
1173
|
}
|
|
@@ -683,7 +1309,7 @@ var ReactiveDebug = {
|
|
|
683
1309
|
*/
|
|
684
1310
|
log(message, ...args) {
|
|
685
1311
|
if (this.isEnabled()) {
|
|
686
|
-
|
|
1312
|
+
logger4.info(`[WSX Reactive] ${message}`, ...args);
|
|
687
1313
|
}
|
|
688
1314
|
}
|
|
689
1315
|
};
|
|
@@ -731,6 +1357,108 @@ function reactiveWithDebug(obj, onChange, debugName) {
|
|
|
731
1357
|
});
|
|
732
1358
|
}
|
|
733
1359
|
|
|
1360
|
+
// src/dom-cache-manager.ts
|
|
1361
|
+
var logger5 = createLogger("DOMCacheManager");
|
|
1362
|
+
var DOMCacheManager = class {
|
|
1363
|
+
constructor() {
|
|
1364
|
+
// Map<CacheKey, DOMElement>
|
|
1365
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1366
|
+
// Map<DOMElement, Metadata>
|
|
1367
|
+
// Stores metadata (props, children) for cached elements to support diffing
|
|
1368
|
+
this.metadata = /* @__PURE__ */ new WeakMap();
|
|
1369
|
+
// Track key-parent relationships to detect duplicate keys in all environments
|
|
1370
|
+
// Map<CacheKey, ParentInfo>
|
|
1371
|
+
this.keyParentMap = /* @__PURE__ */ new Map();
|
|
1372
|
+
// Flag to enable duplicate key warnings (enabled by default, critical for correctness)
|
|
1373
|
+
this.warnDuplicateKeys = true;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Retrieves an element from the cache.
|
|
1377
|
+
* @param key The unique cache key.
|
|
1378
|
+
*/
|
|
1379
|
+
get(key) {
|
|
1380
|
+
return this.cache.get(key);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Stores an element in the cache.
|
|
1384
|
+
* @param key The unique cache key.
|
|
1385
|
+
* @param element The DOM element to cache.
|
|
1386
|
+
*/
|
|
1387
|
+
set(key, element) {
|
|
1388
|
+
if (this.warnDuplicateKeys) {
|
|
1389
|
+
this.checkDuplicateKey(key, element);
|
|
1390
|
+
}
|
|
1391
|
+
this.cache.set(key, element);
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Checks if a cache key is being reused in a different parent container.
|
|
1395
|
+
* Runs in all environments to help developers catch key conflicts early.
|
|
1396
|
+
* This is critical for correctness and helps prevent subtle bugs.
|
|
1397
|
+
*/
|
|
1398
|
+
checkDuplicateKey(key, element) {
|
|
1399
|
+
const existing = this.keyParentMap.get(key);
|
|
1400
|
+
const currentParent = element.parentElement;
|
|
1401
|
+
if (existing && currentParent) {
|
|
1402
|
+
const currentParentInfo = this.getParentInfo(currentParent);
|
|
1403
|
+
const existingParentInfo = `${existing.parentTag}${existing.parentClass ? "." + existing.parentClass : ""}`;
|
|
1404
|
+
if (currentParentInfo !== existingParentInfo) {
|
|
1405
|
+
logger5.warn(
|
|
1406
|
+
`Duplicate key "${key}" detected in different parent containers!
|
|
1407
|
+
Previous parent: ${existingParentInfo}
|
|
1408
|
+
Current parent: ${currentParentInfo}
|
|
1409
|
+
|
|
1410
|
+
This may cause elements to appear in wrong containers or be moved unexpectedly.
|
|
1411
|
+
|
|
1412
|
+
Solution: Use unique key prefixes for different locations:
|
|
1413
|
+
Example: <wsx-link key="nav-0"> vs <wsx-link key="overflow-0">
|
|
1414
|
+
|
|
1415
|
+
See https://wsxjs.dev/docs/guide/DOM_CACHE_GUIDE for best practices.`
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (currentParent) {
|
|
1420
|
+
this.keyParentMap.set(key, {
|
|
1421
|
+
parentTag: currentParent.tagName.toLowerCase(),
|
|
1422
|
+
parentClass: currentParent.className,
|
|
1423
|
+
element
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Gets a formatted parent container description.
|
|
1429
|
+
*/
|
|
1430
|
+
getParentInfo(parent) {
|
|
1431
|
+
const tag = parent.tagName.toLowerCase();
|
|
1432
|
+
const className = parent.className;
|
|
1433
|
+
return `${tag}${className ? "." + className.split(" ")[0] : ""}`;
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Checks if a key exists in the cache.
|
|
1437
|
+
*/
|
|
1438
|
+
has(key) {
|
|
1439
|
+
return this.cache.has(key);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Clears the cache.
|
|
1443
|
+
* Should be called when component is disconnected or cache is invalidated.
|
|
1444
|
+
*/
|
|
1445
|
+
clear() {
|
|
1446
|
+
this.cache.clear();
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Stores metadata for an element (e.g. previous props).
|
|
1450
|
+
*/
|
|
1451
|
+
setMetadata(element, meta) {
|
|
1452
|
+
this.metadata.set(element, meta);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Retrieves metadata for an element.
|
|
1456
|
+
*/
|
|
1457
|
+
getMetadata(element) {
|
|
1458
|
+
return this.metadata.get(element);
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
|
|
734
1462
|
// src/base-component.ts
|
|
735
1463
|
var BaseComponent = class extends HTMLElement {
|
|
736
1464
|
constructor(config = {}) {
|
|
@@ -738,6 +1466,11 @@ var BaseComponent = class extends HTMLElement {
|
|
|
738
1466
|
this.connected = false;
|
|
739
1467
|
this._isDebugEnabled = false;
|
|
740
1468
|
this._reactiveStates = /* @__PURE__ */ new Map();
|
|
1469
|
+
/**
|
|
1470
|
+
* DOM Cache Manager for fine-grained updates (RFC 0037)
|
|
1471
|
+
* @internal
|
|
1472
|
+
*/
|
|
1473
|
+
this._domCache = new DOMCacheManager();
|
|
741
1474
|
/**
|
|
742
1475
|
* 当前捕获的焦点状态(用于在 render 时使用捕获的值)
|
|
743
1476
|
* @internal - 由 rerender() 方法管理
|
|
@@ -802,6 +1535,13 @@ var BaseComponent = class extends HTMLElement {
|
|
|
802
1535
|
static get observedAttributes() {
|
|
803
1536
|
return [];
|
|
804
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Gets the DOMCacheManager instance.
|
|
1540
|
+
* @internal
|
|
1541
|
+
*/
|
|
1542
|
+
getDomCache() {
|
|
1543
|
+
return this._domCache;
|
|
1544
|
+
}
|
|
805
1545
|
/**
|
|
806
1546
|
* Web Component生命周期:属性变化
|
|
807
1547
|
*/
|
|
@@ -1109,7 +1849,7 @@ var BaseComponent = class extends HTMLElement {
|
|
|
1109
1849
|
|
|
1110
1850
|
// src/web-component.ts
|
|
1111
1851
|
var import_wsx_logger2 = require("@wsxjs/wsx-logger");
|
|
1112
|
-
var
|
|
1852
|
+
var logger6 = (0, import_wsx_logger2.createLogger)("WebComponent");
|
|
1113
1853
|
var WebComponent = class extends BaseComponent {
|
|
1114
1854
|
// Initialized by BaseComponent constructor
|
|
1115
1855
|
constructor(config = {}) {
|
|
@@ -1147,7 +1887,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
1147
1887
|
const styleName = this.config.styleName || this.constructor.name;
|
|
1148
1888
|
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
1149
1889
|
}
|
|
1150
|
-
const content = this.render();
|
|
1890
|
+
const content = RenderContext.runInContext(this, () => this.render());
|
|
1151
1891
|
this.shadowRoot.appendChild(content);
|
|
1152
1892
|
}
|
|
1153
1893
|
this.initializeEventListeners();
|
|
@@ -1158,7 +1898,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
1158
1898
|
});
|
|
1159
1899
|
}
|
|
1160
1900
|
} catch (error) {
|
|
1161
|
-
|
|
1901
|
+
logger6.error(`Error in connectedCallback:`, error);
|
|
1162
1902
|
this.renderError(error);
|
|
1163
1903
|
}
|
|
1164
1904
|
}
|
|
@@ -1211,7 +1951,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
1211
1951
|
StyleManager.applyStyles(this.shadowRoot, styleName, stylesToApply);
|
|
1212
1952
|
}
|
|
1213
1953
|
}
|
|
1214
|
-
const content = this.render();
|
|
1954
|
+
const content = RenderContext.runInContext(this, () => this.render());
|
|
1215
1955
|
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
1216
1956
|
const target = content.querySelector(
|
|
1217
1957
|
`[data-wsx-key="${focusState.key}"]`
|
|
@@ -1227,9 +1967,18 @@ var WebComponent = class extends BaseComponent {
|
|
|
1227
1967
|
}
|
|
1228
1968
|
requestAnimationFrame(() => {
|
|
1229
1969
|
this.shadowRoot.appendChild(content);
|
|
1230
|
-
const oldChildren = Array.from(this.shadowRoot.children).filter(
|
|
1231
|
-
(child
|
|
1232
|
-
|
|
1970
|
+
const oldChildren = Array.from(this.shadowRoot.children).filter((child) => {
|
|
1971
|
+
if (child === content) {
|
|
1972
|
+
return false;
|
|
1973
|
+
}
|
|
1974
|
+
if (child instanceof HTMLStyleElement) {
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
if (shouldPreserveElement(child)) {
|
|
1978
|
+
return false;
|
|
1979
|
+
}
|
|
1980
|
+
return true;
|
|
1981
|
+
});
|
|
1233
1982
|
oldChildren.forEach((child) => child.remove());
|
|
1234
1983
|
requestAnimationFrame(() => {
|
|
1235
1984
|
this.restoreFocusState(focusState);
|
|
@@ -1239,7 +1988,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
1239
1988
|
});
|
|
1240
1989
|
});
|
|
1241
1990
|
} catch (error) {
|
|
1242
|
-
|
|
1991
|
+
logger6.error("Error in _rerender:", error);
|
|
1243
1992
|
this.renderError(error);
|
|
1244
1993
|
this._isRendering = false;
|
|
1245
1994
|
}
|
|
@@ -1267,7 +2016,7 @@ var WebComponent = class extends BaseComponent {
|
|
|
1267
2016
|
|
|
1268
2017
|
// src/light-component.ts
|
|
1269
2018
|
var import_wsx_logger3 = require("@wsxjs/wsx-logger");
|
|
1270
|
-
var
|
|
2019
|
+
var logger7 = (0, import_wsx_logger3.createLogger)("LightComponent");
|
|
1271
2020
|
var LightComponent = class extends BaseComponent {
|
|
1272
2021
|
// Initialized by BaseComponent constructor
|
|
1273
2022
|
constructor(config = {}) {
|
|
@@ -1309,7 +2058,7 @@ var LightComponent = class extends BaseComponent {
|
|
|
1309
2058
|
(child) => child !== styleElement
|
|
1310
2059
|
);
|
|
1311
2060
|
childrenToRemove.forEach((child) => child.remove());
|
|
1312
|
-
const content = this.render();
|
|
2061
|
+
const content = RenderContext.runInContext(this, () => this.render());
|
|
1313
2062
|
this.appendChild(content);
|
|
1314
2063
|
if (styleElement && styleElement !== this.firstChild) {
|
|
1315
2064
|
this.insertBefore(styleElement, this.firstChild);
|
|
@@ -1323,7 +2072,7 @@ var LightComponent = class extends BaseComponent {
|
|
|
1323
2072
|
});
|
|
1324
2073
|
}
|
|
1325
2074
|
} catch (error) {
|
|
1326
|
-
|
|
2075
|
+
logger7.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
1327
2076
|
this.renderError(error);
|
|
1328
2077
|
}
|
|
1329
2078
|
}
|
|
@@ -1371,7 +2120,7 @@ var LightComponent = class extends BaseComponent {
|
|
|
1371
2120
|
this._pendingFocusState = focusState;
|
|
1372
2121
|
const jsxChildren = this.getJSXChildren();
|
|
1373
2122
|
try {
|
|
1374
|
-
const content = this.render();
|
|
2123
|
+
const content = RenderContext.runInContext(this, () => this.render());
|
|
1375
2124
|
if (focusState && focusState.key && focusState.value !== void 0) {
|
|
1376
2125
|
const target = content.querySelector(
|
|
1377
2126
|
`[data-wsx-key="${focusState.key}"]`
|
|
@@ -1409,6 +2158,9 @@ var LightComponent = class extends BaseComponent {
|
|
|
1409
2158
|
if (child instanceof HTMLElement && jsxChildren.includes(child)) {
|
|
1410
2159
|
return false;
|
|
1411
2160
|
}
|
|
2161
|
+
if (shouldPreserveElement(child)) {
|
|
2162
|
+
return false;
|
|
2163
|
+
}
|
|
1412
2164
|
return true;
|
|
1413
2165
|
});
|
|
1414
2166
|
oldChildren.forEach((child) => child.remove());
|
|
@@ -1428,7 +2180,7 @@ var LightComponent = class extends BaseComponent {
|
|
|
1428
2180
|
});
|
|
1429
2181
|
});
|
|
1430
2182
|
} catch (error) {
|
|
1431
|
-
|
|
2183
|
+
logger7.error(`[${this.constructor.name}] Error in _rerender:`, error);
|
|
1432
2184
|
this.renderError(error);
|
|
1433
2185
|
this._isRendering = false;
|
|
1434
2186
|
}
|
|
@@ -1550,6 +2302,10 @@ To fix this, please:
|
|
|
1550
2302
|
See: https://github.com/wsxjs/wsxjs#setup for more details.`;
|
|
1551
2303
|
}
|
|
1552
2304
|
function state(targetOrContext, propertyKey) {
|
|
2305
|
+
const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
|
|
2306
|
+
if (globalProcess?.env?.NODE_ENV === "test") {
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
1553
2309
|
let propertyName = "unknown";
|
|
1554
2310
|
const propertyKeyIsObject = typeof propertyKey === "object" && propertyKey !== null;
|
|
1555
2311
|
const targetIsObject = typeof targetOrContext === "object" && targetOrContext !== null;
|