@lvce-editor/virtual-dom-worker 5.2.0 → 6.0.0

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.d.ts CHANGED
@@ -50,8 +50,7 @@ export interface RemovePatch {
50
50
  readonly type: 5;
51
51
  }
52
52
  export interface ReplacePatch {
53
- readonly index: number;
54
- readonly node: VirtualDomNode;
53
+ readonly nodes: readonly VirtualDomNode[];
55
54
  readonly type: 2;
56
55
  }
57
56
  export interface TextPatch {
@@ -60,6 +59,7 @@ export interface TextPatch {
60
59
  }
61
60
  export type Patch = TextPatch | AttributePatch | ReplacePatch | RemoveAttributePatch | RemovePatch | AddPatch | NavigateChildPatch | NavigateParentPatch | RemoveChildPatch | NavigateSiblingPatch;
62
61
  export declare const diff: (oldNodes: readonly VirtualDomNode[], newNodes: readonly VirtualDomNode[]) => readonly Patch[];
62
+ export declare const diffTree: (oldNodes: readonly VirtualDomNode[], newNodes: readonly VirtualDomNode[]) => readonly Patch[];
63
63
 
64
64
  declare namespace AriaLive {
65
65
  export { Polite };
@@ -4,3 +4,4 @@ export * from "../MergeClassNames/MergeClassNames.js";
4
4
  export * from "../Px/Px.js";
5
5
  export * from "../Text/Text.js";
6
6
  export { diff } from "../VirtualDomDiff/VirtualDomDiff.js";
7
+ export { diffTree } from "../VirtualDomDiffTree/VirtualDomDiffTree.js";
@@ -0,0 +1,39 @@
1
+ import * as PatchType from "../PatchType/PatchType.js";
2
+ export const addNavigationPatches = (patches, path, currentIndex, currentPath = []) => {
3
+ // Only add navigation if we're not at the root
4
+ if (path.length === 0 && currentIndex === 0) {
5
+ return;
6
+ }
7
+ // The target path is path + currentIndex
8
+ // But if path already includes the current position, we need to check differently
9
+ // When comparing nodes at path [0, 0], we're already at [0, 0]
10
+ // So if path is [0, 0] and currentIndex is 0, the target is [0, 0] (not [0, 0, 0])
11
+ // We're comparing the node at path, not its child
12
+ const targetPath = [...path];
13
+ // If we're already at the target path, don't add navigation
14
+ if (currentPath.length === targetPath.length &&
15
+ currentPath.every((val, idx) => val === targetPath[idx])) {
16
+ // Only add sibling navigation if needed
17
+ if (currentIndex > 0) {
18
+ patches.push({
19
+ type: PatchType.NavigateSibling,
20
+ index: currentIndex,
21
+ });
22
+ }
23
+ return;
24
+ }
25
+ // Navigate to the correct position
26
+ for (let i = 0; i < path.length; i++) {
27
+ patches.push({
28
+ type: PatchType.NavigateChild,
29
+ index: path[i],
30
+ });
31
+ }
32
+ // Navigate to sibling if needed
33
+ if (currentIndex > 0) {
34
+ patches.push({
35
+ type: PatchType.NavigateSibling,
36
+ index: currentIndex,
37
+ });
38
+ }
39
+ };
@@ -0,0 +1,45 @@
1
+ import { VirtualDomElements } from '@lvce-editor/constants';
2
+ import * as GetKeys from "../GetKeys/GetKeys.js";
3
+ import * as PatchType from "../PatchType/PatchType.js";
4
+ export const compareNodes = (oldNode, newNode) => {
5
+ const patches = [];
6
+ // Check if node type changed - return null to signal incompatible nodes
7
+ // (caller should handle this with a Replace operation)
8
+ if (oldNode.type !== newNode.type) {
9
+ return null;
10
+ }
11
+ // Handle text nodes
12
+ if (oldNode.type === VirtualDomElements.Text &&
13
+ newNode.type === VirtualDomElements.Text) {
14
+ if (oldNode.text !== newNode.text) {
15
+ patches.push({
16
+ type: PatchType.SetText,
17
+ value: newNode.text,
18
+ });
19
+ }
20
+ return patches;
21
+ }
22
+ // Compare attributes
23
+ const oldKeys = GetKeys.getKeys(oldNode);
24
+ const newKeys = GetKeys.getKeys(newNode);
25
+ // Check for attribute changes
26
+ for (const key of newKeys) {
27
+ if (oldNode[key] !== newNode[key]) {
28
+ patches.push({
29
+ type: PatchType.SetAttribute,
30
+ key,
31
+ value: newNode[key],
32
+ });
33
+ }
34
+ }
35
+ // Check for removed attributes
36
+ for (const key of oldKeys) {
37
+ if (!(key in newNode)) {
38
+ patches.push({
39
+ type: PatchType.RemoveAttribute,
40
+ key,
41
+ });
42
+ }
43
+ }
44
+ return patches;
45
+ };
@@ -0,0 +1,140 @@
1
+ import * as PatchType from "../PatchType/PatchType.js";
2
+ import * as CompareNodes from "./CompareNodes.js";
3
+ import * as TreeToArray from "./TreeToArray.js";
4
+ const diffChildren = (oldChildren, newChildren, patches) => {
5
+ const maxLength = Math.max(oldChildren.length, newChildren.length);
6
+ // Track where we are: -1 means at parent, >= 0 means at child index
7
+ let currentChildIndex = -1;
8
+ // Collect indices of children to remove (we'll add these patches at the end in reverse order)
9
+ const indicesToRemove = [];
10
+ for (let i = 0; i < maxLength; i++) {
11
+ const oldNode = oldChildren[i];
12
+ const newNode = newChildren[i];
13
+ if (!oldNode && !newNode) {
14
+ continue;
15
+ }
16
+ if (!oldNode) {
17
+ // Add new node - we should be at the parent
18
+ if (currentChildIndex >= 0) {
19
+ // Navigate back to parent
20
+ patches.push({
21
+ type: PatchType.NavigateParent,
22
+ });
23
+ currentChildIndex = -1;
24
+ }
25
+ // Flatten the entire subtree so renderInternal can handle it
26
+ const flatNodes = TreeToArray.treeToArray(newNode);
27
+ patches.push({
28
+ type: PatchType.Add,
29
+ nodes: flatNodes,
30
+ });
31
+ }
32
+ else if (newNode) {
33
+ // Compare nodes to see if we need any patches
34
+ const nodePatches = CompareNodes.compareNodes(oldNode.node, newNode.node);
35
+ // If nodePatches is null, the node types are incompatible - need to replace
36
+ if (nodePatches === null) {
37
+ // Navigate to this child
38
+ if (currentChildIndex === -1) {
39
+ patches.push({
40
+ type: PatchType.NavigateChild,
41
+ index: i,
42
+ });
43
+ currentChildIndex = i;
44
+ }
45
+ else if (currentChildIndex !== i) {
46
+ patches.push({
47
+ type: PatchType.NavigateSibling,
48
+ index: i,
49
+ });
50
+ currentChildIndex = i;
51
+ }
52
+ // Replace the entire subtree
53
+ const flatNodes = TreeToArray.treeToArray(newNode);
54
+ patches.push({
55
+ type: PatchType.Replace,
56
+ nodes: flatNodes,
57
+ });
58
+ // After replace, we're at the new element (same position)
59
+ continue;
60
+ }
61
+ // Check if we need to recurse into children
62
+ const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
63
+ // Only navigate to this element if we need to do something
64
+ if (nodePatches.length > 0 || hasChildrenToCompare) {
65
+ // Navigate to this child if not already there
66
+ if (currentChildIndex === -1) {
67
+ patches.push({
68
+ type: PatchType.NavigateChild,
69
+ index: i,
70
+ });
71
+ currentChildIndex = i;
72
+ }
73
+ else if (currentChildIndex !== i) {
74
+ patches.push({
75
+ type: PatchType.NavigateSibling,
76
+ index: i,
77
+ });
78
+ currentChildIndex = i;
79
+ }
80
+ // Apply node patches (these apply to the current element, not children)
81
+ if (nodePatches.length > 0) {
82
+ patches.push(...nodePatches);
83
+ }
84
+ // Compare children recursively
85
+ if (hasChildrenToCompare) {
86
+ diffChildren(oldNode.children, newNode.children, patches);
87
+ }
88
+ }
89
+ }
90
+ else {
91
+ // Remove old node - collect the index for later removal
92
+ indicesToRemove.push(i);
93
+ }
94
+ }
95
+ // Navigate back to parent if we ended at a child
96
+ if (currentChildIndex >= 0) {
97
+ patches.push({
98
+ type: PatchType.NavigateParent,
99
+ });
100
+ currentChildIndex = -1;
101
+ }
102
+ // Add remove patches in reverse order (highest index first)
103
+ // This ensures indices remain valid as we remove
104
+ for (let j = indicesToRemove.length - 1; j >= 0; j--) {
105
+ patches.push({
106
+ type: PatchType.RemoveChild,
107
+ index: indicesToRemove[j],
108
+ });
109
+ }
110
+ };
111
+ export const diffTrees = (oldTree, newTree, patches, path) => {
112
+ // At the root level (path.length === 0), we're already AT the element
113
+ // So we compare the root node directly, then compare its children
114
+ if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
115
+ const oldNode = oldTree[0];
116
+ const newNode = newTree[0];
117
+ // Compare root nodes
118
+ const nodePatches = CompareNodes.compareNodes(oldNode.node, newNode.node);
119
+ // If nodePatches is null, the root node types are incompatible - need to replace
120
+ if (nodePatches === null) {
121
+ const flatNodes = TreeToArray.treeToArray(newNode);
122
+ patches.push({
123
+ type: PatchType.Replace,
124
+ nodes: flatNodes,
125
+ });
126
+ return;
127
+ }
128
+ if (nodePatches.length > 0) {
129
+ patches.push(...nodePatches);
130
+ }
131
+ // Compare children
132
+ if (oldNode.children.length > 0 || newNode.children.length > 0) {
133
+ diffChildren(oldNode.children, newNode.children, patches);
134
+ }
135
+ }
136
+ else {
137
+ // Non-root level or multiple root elements - use the regular comparison
138
+ diffChildren(oldTree, newTree, patches);
139
+ }
140
+ };
@@ -0,0 +1,18 @@
1
+ import * as PatchType from "../PatchType/PatchType.js";
2
+ export const removeTrailingNavigationPatches = (patches) => {
3
+ // Find the last non-navigation patch
4
+ let lastNonNavigationIndex = -1;
5
+ for (let i = patches.length - 1; i >= 0; i--) {
6
+ const patch = patches[i];
7
+ if (patch.type !== PatchType.NavigateChild &&
8
+ patch.type !== PatchType.NavigateParent &&
9
+ patch.type !== PatchType.NavigateSibling) {
10
+ lastNonNavigationIndex = i;
11
+ break;
12
+ }
13
+ }
14
+ // Return patches up to and including the last non-navigation patch
15
+ return lastNonNavigationIndex === -1
16
+ ? []
17
+ : patches.slice(0, lastNonNavigationIndex + 1);
18
+ };
@@ -0,0 +1,7 @@
1
+ export const treeToArray = (node) => {
2
+ const result = [node.node];
3
+ for (const child of node.children) {
4
+ result.push(...treeToArray(child));
5
+ }
6
+ return result;
7
+ };
@@ -0,0 +1,13 @@
1
+ import * as VirtualDomTree from "../VirtualDomTree/VirtualDomTree.js";
2
+ import * as DiffTrees from "./DiffTrees.js";
3
+ import * as RemoveTrailingNavigationPatches from "./RemoveTrailingNavigationPatches.js";
4
+ export const diffTree = (oldNodes, newNodes) => {
5
+ // Step 1: Convert flat arrays to tree structures
6
+ const oldTree = VirtualDomTree.arrayToTree(oldNodes);
7
+ const newTree = VirtualDomTree.arrayToTree(newNodes);
8
+ // Step 3: Compare the trees
9
+ const patches = [];
10
+ DiffTrees.diffTrees(oldTree, newTree, patches, []);
11
+ // Remove trailing navigation patches since they serve no purpose
12
+ return RemoveTrailingNavigationPatches.removeTrailingNavigationPatches(patches);
13
+ };
@@ -0,0 +1,37 @@
1
+ export const arrayToTree = (nodes) => {
2
+ const result = [];
3
+ let i = 0;
4
+ while (i < nodes.length) {
5
+ const node = nodes[i];
6
+ const { children, nodesConsumed } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
7
+ result.push({
8
+ node,
9
+ children,
10
+ });
11
+ i += 1 + nodesConsumed;
12
+ }
13
+ return result;
14
+ };
15
+ const getChildrenWithCount = (nodes, startIndex, childCount) => {
16
+ if (childCount === 0) {
17
+ return { children: [], nodesConsumed: 0 };
18
+ }
19
+ const children = [];
20
+ let i = startIndex;
21
+ let remaining = childCount;
22
+ let totalConsumed = 0;
23
+ while (remaining > 0 && i < nodes.length) {
24
+ const node = nodes[i];
25
+ const nodeChildCount = node.childCount || 0;
26
+ const { children: nodeChildren, nodesConsumed } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
27
+ children.push({
28
+ node,
29
+ children: nodeChildren,
30
+ });
31
+ const nodeSize = 1 + nodesConsumed;
32
+ i += nodeSize;
33
+ totalConsumed += nodeSize;
34
+ remaining--;
35
+ }
36
+ return { children, nodesConsumed: totalConsumed };
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/virtual-dom-worker",
3
- "version": "5.2.0",
3
+ "version": "6.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/lvce-editor/virtual-dom.git"