@lvce-editor/virtual-dom-worker 1.8.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.8.0",
3
+ "version": "1.9.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -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
+ }
@@ -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
+ }
@@ -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'
@@ -1,4 +1,3 @@
1
1
  export type NavigateParentPatch = {
2
2
  readonly type: 8
3
- readonly count: number
4
3
  }
@@ -1,5 +1,7 @@
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'
3
5
  import * as GetTotalChildCount from '../GetTotalChildCount/GetTotalChildCount.ts'
4
6
  import * as PatchType from '../PatchType/PatchType.ts'
5
7
  import * as VirtualDomElements from '../VirtualDomElements/VirtualDomElements.ts'
@@ -9,51 +11,60 @@ export const diff = (
9
11
  newNodes: readonly VirtualDomNode[],
10
12
  ): readonly Patch[] => {
11
13
  const patches: Patch[] = []
12
- let i = 0 // Index for oldNodes
13
- let j = 0 // Index for newNodes
14
+ const pendingPatches: number[] = []
15
+ let i = 0
16
+ let j = 0
14
17
  let siblingOffset = 0
15
-
16
- const oldNodeCount = oldNodes.length
17
- const newNodeCount = newNodes.length
18
-
19
- while (i < oldNodeCount && j < newNodeCount) {
18
+ let maxSiblingOffset = 1
19
+ const indexStack: number[] = [0, 1]
20
+ while (i < oldNodes.length && j < newNodes.length) {
20
21
  const oldNode = oldNodes[i]
21
22
  const newNode = newNodes[j]
22
23
 
24
+ if (siblingOffset > 0) {
25
+ // pendingPatches.push(PatchType.NavigateSibling, siblingOffset)
26
+ }
27
+ if (siblingOffset === maxSiblingOffset) {
28
+ pendingPatches.push(PatchType.NavigateParent, 0)
29
+ maxSiblingOffset = indexStack.pop() as number
30
+ siblingOffset = (indexStack.pop() as number) + 1
31
+ }
32
+
23
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)
24
42
  const oldTotal = GetTotalChildCount.getTotalChildCount(oldNodes, i)
25
43
  const newTotal = GetTotalChildCount.getTotalChildCount(newNodes, j)
26
- const last = patches.at(-1)
27
- if (last && last.type === PatchType.NavigateChild) {
28
- patches.pop()
29
- }
44
+
30
45
  patches.push({
31
46
  type: PatchType.RemoveChild,
32
- index: 0,
47
+ index: siblingOffset,
33
48
  })
34
49
  patches.push({
35
50
  type: PatchType.Add,
36
51
  nodes: newNodes.slice(j, j + newTotal),
37
52
  })
53
+ siblingOffset++
38
54
  i += oldTotal
39
55
  j += newTotal
40
56
  continue
41
57
  }
42
58
 
43
- if (siblingOffset > 0) {
44
- patches.push({
45
- type: PatchType.NavigateSibling,
46
- index: siblingOffset,
47
- })
48
- siblingOffset = 0
49
- }
50
-
51
- // text node
52
59
  if (
53
60
  oldNode.type === VirtualDomElements.Text &&
54
61
  newNode.type === VirtualDomElements.Text
55
62
  ) {
56
63
  if (oldNode.text !== newNode.text) {
64
+ if (siblingOffset !== 0) {
65
+ pendingPatches.push(PatchType.NavigateSibling, siblingOffset)
66
+ }
67
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
57
68
  patches.push({
58
69
  type: PatchType.SetText,
59
70
  value: newNode.text,
@@ -65,61 +76,68 @@ export const diff = (
65
76
  continue
66
77
  }
67
78
 
68
- // Attribute changes
69
- const oldKeys = Object.keys(oldNode).filter(
70
- (key) => key !== 'type' && key !== 'childCount',
71
- )
72
- const newKeys = Object.keys(newNode).filter(
73
- (key) => key !== 'type' && key !== 'childCount',
74
- )
75
-
76
- // Check for changed or added attributes
79
+ const oldKeys = GetKeys.getKeys(oldNode)
80
+ const newKeys = GetKeys.getKeys(newNode)
81
+ let hasAttributeChanges = false
77
82
  for (const key of newKeys) {
78
83
  if (oldNode[key] !== newNode[key]) {
79
- patches.push({
80
- type: PatchType.SetAttribute,
81
- key,
82
- value: newNode[key],
83
- })
84
+ hasAttributeChanges = true
85
+ break
84
86
  }
85
87
  }
86
-
87
- // Check for removed attributes
88
88
  for (const key of oldKeys) {
89
89
  if (!(key in newNode)) {
90
- patches.push({
91
- type: PatchType.RemoveAttribute,
92
- key,
93
- })
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
+ }
94
114
  }
95
115
  }
96
116
 
97
117
  if (oldNode.childCount && newNode.childCount) {
98
- patches.push({
99
- type: PatchType.NavigateChild,
100
- index: 0,
101
- })
118
+ maxSiblingOffset = oldNode.childCount
119
+ indexStack.push(0, maxSiblingOffset)
120
+ pendingPatches.push(PatchType.NavigateChild, 0)
102
121
  i++
103
122
  j++
104
123
  continue
105
124
  }
106
125
 
107
126
  if (oldNode.childCount) {
108
- const total = GetTotalChildCount.getTotalChildCount(oldNodes, i)
109
- // const last = patches.at(-1)
110
- // if (last && last.type === PatchType.NavigateChild) {
111
- // patches.pop()
112
- // }
113
- patches.push({
114
- type: PatchType.RemoveChild,
115
- index: 0,
116
- })
117
- i += total
127
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
128
+ for (let k = 0; k < oldNode.childCount; k++) {
129
+ patches.push({
130
+ type: PatchType.RemoveChild,
131
+ index: 0,
132
+ })
133
+ }
134
+ i += GetTotalChildCount.getTotalChildCount(oldNodes, i)
118
135
  j++
119
136
  continue
120
137
  }
121
138
 
122
139
  if (newNode.childCount) {
140
+ ApplyPendingPatches.applyPendingPatches(patches, pendingPatches, 0)
123
141
  const total = GetTotalChildCount.getTotalChildCount(newNodes, j)
124
142
  patches.push({
125
143
  type: PatchType.Add,
@@ -135,23 +153,21 @@ export const diff = (
135
153
  siblingOffset++
136
154
  }
137
155
 
138
- // Handle remaining old nodes
139
156
  while (i < oldNodes.length) {
140
- if (siblingOffset > 0) {
157
+ if (indexStack.length !== 2) {
141
158
  patches.push({
142
- type: PatchType.NavigateSibling,
143
- index: siblingOffset,
159
+ type: PatchType.NavigateParent,
144
160
  })
145
- siblingOffset = 0
146
161
  }
147
162
  patches.push({
148
163
  type: PatchType.RemoveChild,
149
- index: 0,
164
+ index: siblingOffset,
150
165
  })
151
166
  i += GetTotalChildCount.getTotalChildCount(oldNodes, i)
167
+ indexStack.pop()
168
+ indexStack.pop()
152
169
  }
153
170
 
154
- // Handle remaining new nodes
155
171
  while (j < newNodes.length) {
156
172
  if (siblingOffset > 0) {
157
173
  patches.push({