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