@lvce-editor/virtual-dom-worker 1.7.0 → 1.9.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
@@ -6,3 +6,10 @@ export interface VirtualDomNode {
6
6
  export const text: (data: string) => VirtualDomNode
7
7
 
8
8
  export const mergeClassNames: (...classNames: readonly string[]) => string
9
+
10
+ export interface Patch {}
11
+
12
+ export const diff: (
13
+ oldNodes: readonly VirtualDomNode[],
14
+ newNodes: readonly VirtualDomNode[],
15
+ ) => readonly Patch[]
package/dist/index.js CHANGED
@@ -130,8 +130,216 @@ var text = (data) => {
130
130
  childCount: 0
131
131
  };
132
132
  };
133
+
134
+ // src/parts/PatchType/PatchType.ts
135
+ var SetText = 1;
136
+ var SetAttribute = 3;
137
+ var RemoveAttribute = 4;
138
+ var Add = 6;
139
+ var NavigateChild = 7;
140
+ var NavigateParent = 8;
141
+ var RemoveChild = 9;
142
+ var NavigateSibling = 10;
143
+
144
+ // src/parts/ApplyPendingPatches/ApplyPendingPatches.ts
145
+ var applyPendingPatches = (patches, pendingPatches, skip) => {
146
+ for (let k = 0; k < pendingPatches.length - skip; k += 2) {
147
+ const type = pendingPatches[k];
148
+ const index = pendingPatches[k + 1];
149
+ if (type === NavigateParent) {
150
+ patches.push({ type });
151
+ } else {
152
+ patches.push({
153
+ type,
154
+ index
155
+ });
156
+ }
157
+ }
158
+ pendingPatches.length = 0;
159
+ };
160
+
161
+ // src/parts/GetKeys/GetKeys.ts
162
+ var isKey = (key) => {
163
+ return key !== "type" && key !== "childCount";
164
+ };
165
+ var getKeys = (node) => {
166
+ const keys = Object.keys(node).filter(isKey);
167
+ return keys;
168
+ };
169
+
170
+ // src/parts/GetTotalChildCount/GetTotalChildCount.ts
171
+ var getTotalChildCount = (nodes, index) => {
172
+ let i = index;
173
+ let pending = 1;
174
+ while (pending) {
175
+ const node = nodes[i];
176
+ pending += node.childCount;
177
+ pending--;
178
+ i++;
179
+ }
180
+ return i - index;
181
+ };
182
+
183
+ // src/parts/VirtualDomDiff/VirtualDomDiff.ts
184
+ var diff = (oldNodes, newNodes) => {
185
+ const patches = [];
186
+ const pendingPatches = [];
187
+ let i = 0;
188
+ let j = 0;
189
+ let siblingOffset = 0;
190
+ let maxSiblingOffset = 1;
191
+ const indexStack = [0, 1];
192
+ while (i < oldNodes.length && j < newNodes.length) {
193
+ const oldNode = oldNodes[i];
194
+ const newNode = newNodes[j];
195
+ if (siblingOffset > 0) {
196
+ }
197
+ if (siblingOffset === maxSiblingOffset) {
198
+ pendingPatches.push(NavigateParent, 0);
199
+ maxSiblingOffset = indexStack.pop();
200
+ siblingOffset = indexStack.pop() + 1;
201
+ }
202
+ if (oldNode.type !== newNode.type) {
203
+ let skip = 0;
204
+ if (pendingPatches.length > 0 && pendingPatches.at(-2) === NavigateChild) {
205
+ skip = 2;
206
+ }
207
+ applyPendingPatches(patches, pendingPatches, skip);
208
+ const oldTotal = getTotalChildCount(oldNodes, i);
209
+ const newTotal = getTotalChildCount(newNodes, j);
210
+ patches.push({
211
+ type: RemoveChild,
212
+ index: siblingOffset
213
+ });
214
+ patches.push({
215
+ type: Add,
216
+ nodes: newNodes.slice(j, j + newTotal)
217
+ });
218
+ siblingOffset++;
219
+ i += oldTotal;
220
+ j += newTotal;
221
+ continue;
222
+ }
223
+ if (oldNode.type === Text && newNode.type === Text) {
224
+ if (oldNode.text !== newNode.text) {
225
+ if (siblingOffset !== 0) {
226
+ pendingPatches.push(NavigateSibling, siblingOffset);
227
+ }
228
+ applyPendingPatches(patches, pendingPatches, 0);
229
+ patches.push({
230
+ type: SetText,
231
+ value: newNode.text
232
+ });
233
+ }
234
+ i++;
235
+ j++;
236
+ siblingOffset++;
237
+ continue;
238
+ }
239
+ const oldKeys = getKeys(oldNode);
240
+ const newKeys = getKeys(newNode);
241
+ let hasAttributeChanges = false;
242
+ for (const key of newKeys) {
243
+ if (oldNode[key] !== newNode[key]) {
244
+ hasAttributeChanges = true;
245
+ break;
246
+ }
247
+ }
248
+ for (const key of oldKeys) {
249
+ if (!(key in newNode)) {
250
+ hasAttributeChanges = true;
251
+ break;
252
+ }
253
+ }
254
+ if (hasAttributeChanges) {
255
+ applyPendingPatches(patches, pendingPatches, 0);
256
+ for (const key of newKeys) {
257
+ if (oldNode[key] !== newNode[key]) {
258
+ patches.push({
259
+ type: SetAttribute,
260
+ key,
261
+ value: newNode[key]
262
+ });
263
+ }
264
+ }
265
+ for (const key of oldKeys) {
266
+ if (!(key in newNode)) {
267
+ patches.push({
268
+ type: RemoveAttribute,
269
+ key
270
+ });
271
+ }
272
+ }
273
+ }
274
+ if (oldNode.childCount && newNode.childCount) {
275
+ maxSiblingOffset = oldNode.childCount;
276
+ indexStack.push(0, maxSiblingOffset);
277
+ pendingPatches.push(NavigateChild, 0);
278
+ i++;
279
+ j++;
280
+ continue;
281
+ }
282
+ if (oldNode.childCount) {
283
+ applyPendingPatches(patches, pendingPatches, 0);
284
+ for (let k = 0; k < oldNode.childCount; k++) {
285
+ patches.push({
286
+ type: RemoveChild,
287
+ index: 0
288
+ });
289
+ }
290
+ i += getTotalChildCount(oldNodes, i);
291
+ j++;
292
+ continue;
293
+ }
294
+ if (newNode.childCount) {
295
+ applyPendingPatches(patches, pendingPatches, 0);
296
+ const total = getTotalChildCount(newNodes, j);
297
+ patches.push({
298
+ type: Add,
299
+ nodes: newNodes.slice(j + 1, j + total)
300
+ });
301
+ i++;
302
+ j += total;
303
+ continue;
304
+ }
305
+ i++;
306
+ j++;
307
+ siblingOffset++;
308
+ }
309
+ while (i < oldNodes.length) {
310
+ if (indexStack.length !== 2) {
311
+ patches.push({
312
+ type: NavigateParent
313
+ });
314
+ }
315
+ patches.push({
316
+ type: RemoveChild,
317
+ index: siblingOffset
318
+ });
319
+ i += getTotalChildCount(oldNodes, i);
320
+ indexStack.pop();
321
+ indexStack.pop();
322
+ }
323
+ while (j < newNodes.length) {
324
+ if (siblingOffset > 0) {
325
+ patches.push({
326
+ type: NavigateSibling,
327
+ index: siblingOffset
328
+ });
329
+ siblingOffset = 0;
330
+ }
331
+ const count = getTotalChildCount(newNodes, j);
332
+ patches.push({
333
+ type: Add,
334
+ nodes: newNodes.slice(j, j + count)
335
+ });
336
+ j += count;
337
+ }
338
+ return patches;
339
+ };
133
340
  export {
134
341
  VirtualDomElements_exports as VirtualDomElements,
342
+ diff,
135
343
  mergeClassNames,
136
344
  text
137
345
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/virtual-dom-worker",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -0,0 +1,6 @@
1
+ import type { VirtualDomNode } from '../VirtualDomNode/VirtualDomNode.ts'
2
+
3
+ export interface AddPatch {
4
+ readonly type: 6
5
+ readonly nodes: readonly VirtualDomNode[]
6
+ }
@@ -0,0 +1,22 @@
1
+ import type { Patch } from '../Patch/Patch.ts'
2
+ import * as PatchType from '../PatchType/PatchType.ts'
3
+
4
+ export const applyPendingPatches = (
5
+ patches: Patch[],
6
+ pendingPatches: number[],
7
+ skip: number,
8
+ ): void => {
9
+ for (let k = 0; k < pendingPatches.length - skip; k += 2) {
10
+ const type = pendingPatches[k]
11
+ const index = pendingPatches[k + 1]
12
+ if (type === PatchType.NavigateParent) {
13
+ patches.push({ type })
14
+ } else {
15
+ patches.push({
16
+ type,
17
+ index,
18
+ } as Patch)
19
+ }
20
+ }
21
+ pendingPatches.length = 0
22
+ }
@@ -1,6 +1,5 @@
1
1
  export interface AttributePatch {
2
2
  readonly type: 3
3
- readonly index: number
4
3
  readonly key: string
5
4
  readonly value: any
6
5
  }
@@ -0,0 +1,10 @@
1
+ import { VirtualDomNode } from '../VirtualDomNode/VirtualDomNode.ts'
2
+
3
+ const isKey = (key: string): boolean => {
4
+ return key !== 'type' && key !== 'childCount'
5
+ }
6
+
7
+ export const getKeys = (node: VirtualDomNode): readonly string[] => {
8
+ const keys = Object.keys(node).filter(isKey)
9
+ return keys
10
+ }
@@ -0,0 +1,16 @@
1
+ import type { VirtualDomNode } from '../VirtualDomNode/VirtualDomNode.ts'
2
+
3
+ export const getTotalChildCount = (
4
+ nodes: readonly VirtualDomNode[],
5
+ index: number,
6
+ ): number => {
7
+ let i = index
8
+ let pending = 1
9
+ while (pending) {
10
+ const node = nodes[i]
11
+ pending += node.childCount
12
+ pending--
13
+ i++
14
+ }
15
+ return i - index
16
+ }
@@ -1,3 +1,4 @@
1
1
  export * from '../MergeClassNames/MergeClassNames.ts'
2
2
  export * from '../Text/Text.ts'
3
+ export { diff } from '../VirtualDomDiff/VirtualDomDiff.ts'
3
4
  export * as VirtualDomElements from '../VirtualDomElements/VirtualDomElements.ts'
@@ -0,0 +1,4 @@
1
+ export type NavigateChildPatch = {
2
+ readonly type: 7
3
+ readonly index: number
4
+ }
@@ -0,0 +1,3 @@
1
+ export type NavigateParentPatch = {
2
+ readonly type: 8
3
+ }
@@ -0,0 +1,4 @@
1
+ export type NavigateSiblingPatch = {
2
+ readonly type: 10
3
+ readonly index: number
4
+ }
@@ -1,10 +1,22 @@
1
+ import type { AddPatch } from '../AddPatch/AddPatch.ts'
1
2
  import type { AttributePatch } from '../AttributePatch/AttributePatch.ts'
3
+ import type { NavigateChildPatch } from '../NavigateChildPatch/NavigateChildPatch.ts'
4
+ import type { NavigateParentPatch } from '../NavigateParentPatch/NavigateParentPatch.ts'
5
+ import type { NavigateSiblingPatch } from '../NavigateSiblingPatch/NavigateSiblingPatch.ts'
6
+ import type { RemoveAttributePatch } from '../RemoveAttributePatch/RemoveAttributePatch.ts'
7
+ import type { RemoveChildPatch } from '../RemoveChildPatch/RemoveChildPatch.ts'
8
+ import type { RemovePatch } from '../RemovePatch/RemovePatch.ts'
2
9
  import type { ReplacePatch } from '../ReplacePatch/ReplacePatch.ts'
3
10
  import type { TextPatch } from '../TextPatch/TextPatch.ts'
4
- import type { RemoveAttributePatch } from '../RemoveAttributePatch/RemoveAttributePatch.ts'
5
11
 
6
12
  export type Patch =
7
13
  | TextPatch
8
14
  | AttributePatch
9
15
  | ReplacePatch
10
16
  | RemoveAttributePatch
17
+ | RemovePatch
18
+ | AddPatch
19
+ | NavigateChildPatch
20
+ | NavigateParentPatch
21
+ | RemoveChildPatch
22
+ | NavigateSiblingPatch
@@ -2,3 +2,9 @@ export const SetText = 1
2
2
  export const Replace = 2
3
3
  export const SetAttribute = 3
4
4
  export const RemoveAttribute = 4
5
+ export const Remove = 5
6
+ export const Add = 6
7
+ export const NavigateChild = 7
8
+ export const NavigateParent = 8
9
+ export const RemoveChild = 9
10
+ export const NavigateSibling = 10
@@ -1,5 +1,4 @@
1
1
  export interface RemoveAttributePatch {
2
2
  readonly type: 4
3
- readonly index: number
4
3
  readonly key: string
5
4
  }
@@ -0,0 +1,4 @@
1
+ export interface RemoveChildPatch {
2
+ readonly type: 9
3
+ readonly index: number
4
+ }
@@ -0,0 +1,4 @@
1
+ export interface RemovePatch {
2
+ readonly type: 5
3
+ readonly index: number
4
+ }
@@ -1,5 +1,4 @@
1
1
  export interface TextPatch {
2
2
  readonly type: 1
3
- readonly index: number
4
3
  readonly value: string
5
4
  }
@@ -1,5 +1,8 @@
1
1
  import type { Patch } from '../Patch/Patch.ts'
2
2
  import type { VirtualDomNode } from '../VirtualDomNode/VirtualDomNode.ts'
3
+ import * as ApplyPendingPatches from '../ApplyPendingPatches/ApplyPendingPatches.ts'
4
+ import * as GetKeys from '../GetKeys/GetKeys.ts'
5
+ import * as GetTotalChildCount from '../GetTotalChildCount/GetTotalChildCount.ts'
3
6
  import * as PatchType from '../PatchType/PatchType.ts'
4
7
  import * as VirtualDomElements from '../VirtualDomElements/VirtualDomElements.ts'
5
8
 
@@ -8,88 +11,177 @@ export const diff = (
8
11
  newNodes: readonly VirtualDomNode[],
9
12
  ): readonly Patch[] => {
10
13
  const patches: Patch[] = []
11
-
12
- // Compare nodes at each index
13
- for (let i = 0; i < Math.max(oldNodes.length, newNodes.length); i++) {
14
+ const pendingPatches: number[] = []
15
+ let i = 0
16
+ let j = 0
17
+ let siblingOffset = 0
18
+ let maxSiblingOffset = 1
19
+ const indexStack: number[] = [0, 1]
20
+ while (i < oldNodes.length && j < newNodes.length) {
14
21
  const oldNode = oldNodes[i]
15
- const newNode = newNodes[i]
22
+ const newNode = newNodes[j]
16
23
 
17
- // Handle node removal
18
- if (!newNode) {
19
- patches.push({
20
- type: PatchType.Replace,
21
- index: i,
22
- // @ts-ignore
23
- node: undefined,
24
- })
25
- continue
24
+ if (siblingOffset > 0) {
25
+ // pendingPatches.push(PatchType.NavigateSibling, siblingOffset)
26
26
  }
27
-
28
- // Handle node addition
29
- if (!oldNode) {
30
- patches.push({
31
- type: PatchType.Replace,
32
- index: i,
33
- node: newNode,
34
- })
35
- continue
27
+ if (siblingOffset === maxSiblingOffset) {
28
+ pendingPatches.push(PatchType.NavigateParent, 0)
29
+ maxSiblingOffset = indexStack.pop() as number
30
+ siblingOffset = (indexStack.pop() as number) + 1
36
31
  }
37
32
 
38
- // Different node types - complete replacement
39
33
  if (oldNode.type !== newNode.type) {
34
+ let skip = 0
35
+ if (
36
+ pendingPatches.length > 0 &&
37
+ pendingPatches.at(-2) === PatchType.NavigateChild
38
+ ) {
39
+ skip = 2
40
+ }
41
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, skip)
42
+ const oldTotal = GetTotalChildCount.getTotalChildCount(oldNodes, i)
43
+ const newTotal = GetTotalChildCount.getTotalChildCount(newNodes, j)
44
+
45
+ patches.push({
46
+ type: PatchType.RemoveChild,
47
+ index: siblingOffset,
48
+ })
40
49
  patches.push({
41
- type: PatchType.Replace,
42
- index: i,
43
- node: newNode,
50
+ type: PatchType.Add,
51
+ nodes: newNodes.slice(j, j + newTotal),
44
52
  })
53
+ siblingOffset++
54
+ i += oldTotal
55
+ j += newTotal
45
56
  continue
46
57
  }
47
58
 
48
- // Text node changes
49
59
  if (
50
60
  oldNode.type === VirtualDomElements.Text &&
51
61
  newNode.type === VirtualDomElements.Text
52
62
  ) {
53
63
  if (oldNode.text !== newNode.text) {
64
+ if (siblingOffset !== 0) {
65
+ pendingPatches.push(PatchType.NavigateSibling, siblingOffset)
66
+ }
67
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
54
68
  patches.push({
55
69
  type: PatchType.SetText,
56
- index: i,
57
70
  value: newNode.text,
58
71
  })
59
72
  }
73
+ i++
74
+ j++
75
+ siblingOffset++
60
76
  continue
61
77
  }
62
78
 
63
- // Attribute changes
64
- const oldKeys = Object.keys(oldNode).filter(
65
- (key) => key !== 'type' && key !== 'childCount',
66
- )
67
- const newKeys = Object.keys(newNode).filter(
68
- (key) => key !== 'type' && key !== 'childCount',
69
- )
70
-
71
- // Check for changed or added attributes
79
+ const oldKeys = GetKeys.getKeys(oldNode)
80
+ const newKeys = GetKeys.getKeys(newNode)
81
+ let hasAttributeChanges = false
72
82
  for (const key of newKeys) {
73
83
  if (oldNode[key] !== newNode[key]) {
74
- patches.push({
75
- type: PatchType.SetAttribute,
76
- index: i,
77
- key,
78
- value: newNode[key],
79
- })
84
+ hasAttributeChanges = true
85
+ break
80
86
  }
81
87
  }
82
-
83
- // Check for removed attributes
84
88
  for (const key of oldKeys) {
85
89
  if (!(key in newNode)) {
90
+ hasAttributeChanges = true
91
+ break
92
+ }
93
+ }
94
+
95
+ if (hasAttributeChanges) {
96
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
97
+
98
+ for (const key of newKeys) {
99
+ if (oldNode[key] !== newNode[key]) {
100
+ patches.push({
101
+ type: PatchType.SetAttribute,
102
+ key,
103
+ value: newNode[key],
104
+ })
105
+ }
106
+ }
107
+ for (const key of oldKeys) {
108
+ if (!(key in newNode)) {
109
+ patches.push({
110
+ type: PatchType.RemoveAttribute,
111
+ key,
112
+ })
113
+ }
114
+ }
115
+ }
116
+
117
+ if (oldNode.childCount && newNode.childCount) {
118
+ maxSiblingOffset = oldNode.childCount
119
+ indexStack.push(0, maxSiblingOffset)
120
+ pendingPatches.push(PatchType.NavigateChild, 0)
121
+ i++
122
+ j++
123
+ continue
124
+ }
125
+
126
+ if (oldNode.childCount) {
127
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
128
+ for (let k = 0; k < oldNode.childCount; k++) {
86
129
  patches.push({
87
- type: PatchType.RemoveAttribute,
88
- index: i,
89
- key,
130
+ type: PatchType.RemoveChild,
131
+ index: 0,
90
132
  })
91
133
  }
134
+ i += GetTotalChildCount.getTotalChildCount(oldNodes, i)
135
+ j++
136
+ continue
137
+ }
138
+
139
+ if (newNode.childCount) {
140
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
141
+ const total = GetTotalChildCount.getTotalChildCount(newNodes, j)
142
+ patches.push({
143
+ type: PatchType.Add,
144
+ nodes: newNodes.slice(j + 1, j + total),
145
+ })
146
+ i++
147
+ j += total
148
+ continue
149
+ }
150
+
151
+ i++
152
+ j++
153
+ siblingOffset++
154
+ }
155
+
156
+ while (i < oldNodes.length) {
157
+ if (indexStack.length !== 2) {
158
+ patches.push({
159
+ type: PatchType.NavigateParent,
160
+ })
161
+ }
162
+ patches.push({
163
+ type: PatchType.RemoveChild,
164
+ index: siblingOffset,
165
+ })
166
+ i += GetTotalChildCount.getTotalChildCount(oldNodes, i)
167
+ indexStack.pop()
168
+ indexStack.pop()
169
+ }
170
+
171
+ while (j < newNodes.length) {
172
+ if (siblingOffset > 0) {
173
+ patches.push({
174
+ type: PatchType.NavigateSibling,
175
+ index: siblingOffset,
176
+ })
177
+ siblingOffset = 0
92
178
  }
179
+ const count = GetTotalChildCount.getTotalChildCount(newNodes, j)
180
+ patches.push({
181
+ type: PatchType.Add,
182
+ nodes: newNodes.slice(j, j + count),
183
+ })
184
+ j += count
93
185
  }
94
186
 
95
187
  return patches