@wsxjs/wsx-core 0.0.27 → 0.0.30
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-2ER76KOQ.mjs +1422 -0
- package/dist/chunk-34PNC5CJ.mjs +1307 -0
- package/dist/chunk-FAPFH5ON.mjs +1372 -0
- package/dist/chunk-HWJ7GZD6.mjs +1327 -0
- package/dist/chunk-U74WFVRE.mjs +1308 -0
- package/dist/chunk-UTWWJJ4C.mjs +1360 -0
- package/dist/chunk-ZY36MEHX.mjs +1306 -0
- package/dist/index.js +259 -138
- package/dist/index.mjs +49 -32
- package/dist/jsx-runtime.js +163 -107
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +163 -107
- package/dist/jsx.mjs +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/base-component.ts +14 -10
- package/src/index.ts +2 -0
- package/src/jsx-factory.ts +3 -1
- package/src/light-component.ts +61 -31
- package/src/utils/dom-utils.ts +32 -1
- package/src/utils/element-creation.ts +3 -1
- package/src/utils/element-marking.ts +9 -5
- package/src/utils/element-update.ts +178 -127
- package/src/utils/update-children-helpers.ts +133 -66
- package/src/web-component.ts +6 -5
|
@@ -16,8 +16,6 @@ import {
|
|
|
16
16
|
findTextNode,
|
|
17
17
|
updateOrCreateTextNode,
|
|
18
18
|
removeNodeIfNotPreserved,
|
|
19
|
-
replaceOrInsertElement,
|
|
20
|
-
replaceOrInsertElementAtPosition,
|
|
21
19
|
appendNewChild,
|
|
22
20
|
buildNewChildrenMaps,
|
|
23
21
|
deduplicateCacheKeys,
|
|
@@ -25,6 +23,7 @@ import {
|
|
|
25
23
|
removeNodes,
|
|
26
24
|
reinsertPreservedElements,
|
|
27
25
|
flattenChildrenSafe,
|
|
26
|
+
replaceOrInsertElementAtPosition,
|
|
28
27
|
} from "./update-children-helpers";
|
|
29
28
|
|
|
30
29
|
/**
|
|
@@ -324,7 +323,8 @@ export function updateChildren(
|
|
|
324
323
|
|
|
325
324
|
// 更新现有子节点
|
|
326
325
|
const minLength = Math.min(flatOld.length, flatNew.length);
|
|
327
|
-
const domIndex = { value: 0 }; //
|
|
326
|
+
const domIndex = { value: 0 }; // 搜索旧节点的索引
|
|
327
|
+
const insertionIndex = { value: 0 }; // 逻辑插入位置的索引
|
|
328
328
|
// 跟踪已处理的节点,用于确定正确的位置
|
|
329
329
|
const processedNodes = new Set<Node>();
|
|
330
330
|
|
|
@@ -347,86 +347,79 @@ export function updateChildren(
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
} else if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
node.nodeType === Node.TEXT_NODE &&
|
|
362
|
-
node.parentNode === element &&
|
|
363
|
-
node.textContent === oldText
|
|
364
|
-
) {
|
|
365
|
-
oldNode = node;
|
|
366
|
-
// 更新 domIndex 到找到的文本节点之后
|
|
367
|
-
domIndex.value = j + 1;
|
|
368
|
-
break;
|
|
350
|
+
// RFC 0048 & RFC 0053 关键修复:如果 element 是保留元素(第三方组件),跳过文本节点查找
|
|
351
|
+
// 防止框架错误地管理第三方组件内部的文本节点
|
|
352
|
+
if (shouldPreserveElement(element)) {
|
|
353
|
+
// 对于保留元素,不查找文本节点,直接设置为 null
|
|
354
|
+
oldNode = null;
|
|
355
|
+
} else {
|
|
356
|
+
oldNode = findTextNode(element, domIndex, processedNodes);
|
|
357
|
+
if (oldNode) {
|
|
358
|
+
const nodeIndex = Array.from(element.childNodes).indexOf(oldNode as ChildNode);
|
|
359
|
+
if (nodeIndex !== -1 && nodeIndex >= domIndex.value) {
|
|
360
|
+
domIndex.value = nodeIndex + 1;
|
|
369
361
|
}
|
|
370
362
|
}
|
|
363
|
+
// RFC 0048 & RFC 0053 关键修复:移除 fallback 内容匹配搜索
|
|
364
|
+
// ...
|
|
371
365
|
}
|
|
372
366
|
}
|
|
373
367
|
|
|
374
368
|
// 处理文本节点(oldChild 是字符串/数字)
|
|
375
369
|
if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
376
370
|
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
377
|
-
const oldText = String(oldChild);
|
|
378
371
|
const newText = String(newChild);
|
|
379
372
|
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// 关键修复:如果 oldNode 为 null 但 oldText === newText,
|
|
403
|
-
// 说明 DOM 中可能已经存在一个匹配的文本节点,只是没有被 findTextNode 找到
|
|
404
|
-
// 我们需要查找并标记它,避免在清理阶段被误删
|
|
405
|
-
// 这种情况可能发生在文本节点和元素节点混合排列时,domIndex 位置不准确
|
|
406
|
-
// 从 domIndex.value 开始搜索,因为这是 findTextNode 停止的位置
|
|
407
|
-
// 这样可以更准确地找到对应的文本节点
|
|
408
|
-
for (let j = domIndex.value; j < element.childNodes.length; j++) {
|
|
409
|
-
const node = element.childNodes[j];
|
|
410
|
-
if (
|
|
411
|
-
node.nodeType === Node.TEXT_NODE &&
|
|
412
|
-
node.parentNode === element &&
|
|
413
|
-
node.textContent === newText &&
|
|
414
|
-
!processedNodes.has(node)
|
|
415
|
-
) {
|
|
416
|
-
// 找到匹配的文本节点,标记为已处理
|
|
417
|
-
processedNodes.add(node);
|
|
418
|
-
break; // 只标记第一个匹配的节点
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
373
|
+
// RFC 0048 & RFC 0053 关键修复:在调用 updateOrCreateTextNode 之前,检查 element 是否是保留元素
|
|
374
|
+
if (shouldPreserveElement(element)) {
|
|
375
|
+
// 跳过保留元素的文本节点处理
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// 计算插入位置
|
|
380
|
+
const insertBeforeNode =
|
|
381
|
+
insertionIndex.value < element.childNodes.length
|
|
382
|
+
? element.childNodes[insertionIndex.value]
|
|
383
|
+
: null;
|
|
384
|
+
|
|
385
|
+
const updatedNode = updateOrCreateTextNode(
|
|
386
|
+
element,
|
|
387
|
+
oldNode,
|
|
388
|
+
newText,
|
|
389
|
+
insertBeforeNode
|
|
390
|
+
);
|
|
391
|
+
if (updatedNode) {
|
|
392
|
+
processedNodes.add(updatedNode);
|
|
393
|
+
// 无论是否复用旧节点,逻辑上我们都占据了一个位置
|
|
394
|
+
insertionIndex.value++;
|
|
422
395
|
}
|
|
423
396
|
} else {
|
|
424
397
|
// 类型变化:文本 -> 元素/Fragment
|
|
425
|
-
|
|
398
|
+
const targetNode =
|
|
399
|
+
insertionIndex.value < element.childNodes.length
|
|
400
|
+
? element.childNodes[insertionIndex.value]
|
|
401
|
+
: null;
|
|
402
|
+
|
|
426
403
|
if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
427
|
-
|
|
404
|
+
// 如果 oldNode 就在当前位置,直接替换
|
|
405
|
+
if (oldNode && oldNode === targetNode && oldNode.parentNode === element) {
|
|
406
|
+
element.replaceChild(newChild, oldNode);
|
|
407
|
+
} else {
|
|
408
|
+
// 否则在目标位置插入,然后移除旧节点(如果需要)
|
|
409
|
+
element.insertBefore(newChild, targetNode);
|
|
410
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
411
|
+
}
|
|
412
|
+
processedNodes.add(newChild);
|
|
413
|
+
insertionIndex.value++;
|
|
428
414
|
} else if (newChild instanceof DocumentFragment) {
|
|
429
|
-
|
|
415
|
+
// 关键修复:跟踪 Fragment 中的所有子节点
|
|
416
|
+
if (processedNodes) {
|
|
417
|
+
for (let i = 0; i < newChild.childNodes.length; i++) {
|
|
418
|
+
processedNodes.add(newChild.childNodes[i]);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
element.insertBefore(newChild, targetNode);
|
|
422
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
430
423
|
}
|
|
431
424
|
}
|
|
432
425
|
}
|
|
@@ -437,75 +430,51 @@ export function updateChildren(
|
|
|
437
430
|
}
|
|
438
431
|
|
|
439
432
|
if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// 从后往前查找,找到最后一个在 DOM 中的前一个元素
|
|
449
|
-
for (let j = i - 1; j >= 0; j--) {
|
|
450
|
-
const prevChild = flatNew[j];
|
|
451
|
-
if (prevChild instanceof HTMLElement || prevChild instanceof SVGElement) {
|
|
452
|
-
if (prevChild.parentNode === element) {
|
|
453
|
-
// 找到前一个元素,newChild 应该在它之后
|
|
454
|
-
targetNextSibling = prevChild.nextSibling;
|
|
455
|
-
foundPreviousElement = true;
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// 如果没有找到前一个元素,newChild 应该在开头
|
|
462
|
-
if (!foundPreviousElement) {
|
|
463
|
-
// 找到第一个非保留、未处理的子节点
|
|
464
|
-
const firstChild = Array.from(element.childNodes).find(
|
|
465
|
-
(node) => !shouldPreserveElement(node) && !processedNodes.has(node)
|
|
466
|
-
);
|
|
467
|
-
targetNextSibling = firstChild || null;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// 检查 newChild 是否已经在正确位置
|
|
471
|
-
const isInCorrectPosition =
|
|
472
|
-
newChild.parentNode === element && newChild.nextSibling === targetNextSibling;
|
|
473
|
-
|
|
474
|
-
// 如果 newChild === oldChild 且位置正确,说明是同一个元素且位置正确
|
|
475
|
-
if (newChild === oldChild && isInCorrectPosition) {
|
|
476
|
-
// 标记为已处理
|
|
477
|
-
if (oldNode) processedNodes.add(oldNode);
|
|
478
|
-
processedNodes.add(newChild);
|
|
479
|
-
continue; // 元素已经在正确位置,不需要更新
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// 如果 newChild 是从缓存复用的(与 oldChild 不同),或者位置不对,需要调整
|
|
483
|
-
// 使用 oldNode 作为参考(如果存在),但目标位置基于数组顺序
|
|
484
|
-
const referenceNode = oldNode && oldNode.parentNode === element ? oldNode : null;
|
|
433
|
+
// 计算目标插入位置 (insertBeforeNode)
|
|
434
|
+
const insertBeforeNode =
|
|
435
|
+
insertionIndex.value < element.childNodes.length
|
|
436
|
+
? element.childNodes[insertionIndex.value]
|
|
437
|
+
: null;
|
|
438
|
+
|
|
439
|
+
// 甚至 newChild === oldNode,如果位置不对也需要移动
|
|
440
|
+
// 使用 helper 处理元素替换和插入,支持 HTML 解析内容的自动等价性匹配
|
|
485
441
|
replaceOrInsertElementAtPosition(
|
|
486
442
|
element,
|
|
487
|
-
newChild,
|
|
488
|
-
|
|
489
|
-
|
|
443
|
+
newChild as HTMLElement | SVGElement,
|
|
444
|
+
oldNode,
|
|
445
|
+
insertBeforeNode,
|
|
446
|
+
processedNodes
|
|
490
447
|
);
|
|
491
448
|
|
|
492
|
-
|
|
493
|
-
// 这样可以防止旧的 span 元素内部的文本节点被误判为不应该移除
|
|
494
|
-
if (oldNode && oldNode !== newChild) {
|
|
495
|
-
processedNodes.delete(oldNode);
|
|
496
|
-
}
|
|
497
|
-
// 标记新元素为已处理
|
|
498
|
-
processedNodes.add(newChild);
|
|
449
|
+
insertionIndex.value++;
|
|
499
450
|
} else {
|
|
500
451
|
// 类型变化:元素 -> 文本/Fragment
|
|
501
|
-
|
|
452
|
+
const targetNode =
|
|
453
|
+
insertionIndex.value < element.childNodes.length
|
|
454
|
+
? element.childNodes[insertionIndex.value]
|
|
455
|
+
: null;
|
|
456
|
+
|
|
502
457
|
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
503
458
|
const newTextNode = document.createTextNode(String(newChild));
|
|
504
|
-
|
|
505
|
-
//
|
|
459
|
+
(newTextNode as any).__wsxManaged = true; // 标记为框架管理
|
|
460
|
+
// 优先替换或插入
|
|
461
|
+
if (oldNode && oldNode === targetNode && oldNode.parentNode === element) {
|
|
462
|
+
element.replaceChild(newTextNode, oldNode);
|
|
463
|
+
} else {
|
|
464
|
+
element.insertBefore(newTextNode, targetNode);
|
|
465
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
466
|
+
}
|
|
506
467
|
processedNodes.add(newTextNode);
|
|
468
|
+
insertionIndex.value++;
|
|
507
469
|
} else if (newChild instanceof DocumentFragment) {
|
|
508
|
-
|
|
470
|
+
// 关键修复:跟踪 Fragment 中的所有子节点,防止被误删
|
|
471
|
+
if (processedNodes) {
|
|
472
|
+
for (let i = 0; i < newChild.childNodes.length; i++) {
|
|
473
|
+
processedNodes.add(newChild.childNodes[i]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
element.insertBefore(newChild, targetNode);
|
|
477
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
509
478
|
}
|
|
510
479
|
}
|
|
511
480
|
}
|
|
@@ -565,3 +534,85 @@ export function updateElement(
|
|
|
565
534
|
// 更新 children
|
|
566
535
|
updateChildren(element, oldChildren, newChildren, cacheManager);
|
|
567
536
|
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Recursively reconciles an old element to match a new element's structure.
|
|
540
|
+
* This is a pure function that updates the oldParent in-place to match newParent.
|
|
541
|
+
*
|
|
542
|
+
* Used by LightComponent to update DOM without full replacement, preserving:
|
|
543
|
+
* - Element references (important for event listeners, focus state)
|
|
544
|
+
* - User-added JSX children
|
|
545
|
+
* - Third-party library injected elements
|
|
546
|
+
*
|
|
547
|
+
* @param oldParent - The existing DOM element to update
|
|
548
|
+
* @param newParent - The new element structure to match
|
|
549
|
+
*/
|
|
550
|
+
export function reconcileElement(oldParent: HTMLElement, newParent: HTMLElement): void {
|
|
551
|
+
const oldChildren = Array.from(oldParent.childNodes);
|
|
552
|
+
const newChildren = Array.from(newParent.childNodes);
|
|
553
|
+
|
|
554
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length);
|
|
555
|
+
|
|
556
|
+
for (let i = 0; i < maxLength; i++) {
|
|
557
|
+
const oldChild = oldChildren[i];
|
|
558
|
+
const newChild = newChildren[i];
|
|
559
|
+
|
|
560
|
+
if (!newChild) {
|
|
561
|
+
// 新的子节点不存在,删除旧的
|
|
562
|
+
oldChild?.remove();
|
|
563
|
+
} else if (!oldChild) {
|
|
564
|
+
// 旧的子节点不存在,添加新的
|
|
565
|
+
oldParent.appendChild(newChild.cloneNode(true));
|
|
566
|
+
} else if (oldChild.nodeType !== newChild.nodeType) {
|
|
567
|
+
// 节点类型不同,替换
|
|
568
|
+
oldParent.replaceChild(newChild.cloneNode(true), oldChild);
|
|
569
|
+
} else if (oldChild.nodeType === Node.TEXT_NODE) {
|
|
570
|
+
// 文本节点,更新内容
|
|
571
|
+
if (oldChild.textContent !== newChild.textContent) {
|
|
572
|
+
oldChild.textContent = newChild.textContent;
|
|
573
|
+
}
|
|
574
|
+
} else if (oldChild.nodeType === Node.ELEMENT_NODE) {
|
|
575
|
+
const oldEl = oldChild as HTMLElement;
|
|
576
|
+
const newEl = newChild as HTMLElement;
|
|
577
|
+
|
|
578
|
+
// 元素节点
|
|
579
|
+
if (oldEl.tagName !== newEl.tagName) {
|
|
580
|
+
// 标签不同,替换
|
|
581
|
+
oldParent.replaceChild(newEl.cloneNode(true), oldEl);
|
|
582
|
+
} else {
|
|
583
|
+
// 标签相同,更新属性
|
|
584
|
+
// 1. 移除旧属性
|
|
585
|
+
Array.from(oldEl.attributes).forEach((attr) => {
|
|
586
|
+
if (!newEl.hasAttribute(attr.name)) {
|
|
587
|
+
oldEl.removeAttribute(attr.name);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// 2. 设置/更新新属性
|
|
592
|
+
Array.from(newEl.attributes).forEach((attr) => {
|
|
593
|
+
if (oldEl.getAttribute(attr.name) !== attr.value) {
|
|
594
|
+
oldEl.setAttribute(attr.name, attr.value);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// 3. 特殊处理:className 是 property,不是 attribute
|
|
599
|
+
if (oldEl.className !== newEl.className) {
|
|
600
|
+
oldEl.className = newEl.className;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 4. 特殊处理:对于 input 元素,更新 checked 和 value 属性
|
|
604
|
+
if (oldEl instanceof HTMLInputElement && newEl instanceof HTMLInputElement) {
|
|
605
|
+
if (oldEl.checked !== newEl.checked) {
|
|
606
|
+
oldEl.checked = newEl.checked;
|
|
607
|
+
}
|
|
608
|
+
if (oldEl.value !== newEl.value) {
|
|
609
|
+
oldEl.value = newEl.value;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 5. 递归更新子元素
|
|
614
|
+
reconcileElement(oldEl, newEl);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|