rask-ui 0.2.4 → 0.2.5
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/tests/patchChildren.test.js +36 -18
- package/dist/vdom/AbstractVNode.d.ts +4 -1
- package/dist/vdom/AbstractVNode.d.ts.map +1 -1
- package/dist/vdom/AbstractVNode.js +20 -11
- package/dist/vdom/ComponentVNode.d.ts.map +1 -1
- package/dist/vdom/ComponentVNode.js +3 -10
- package/dist/vdom/ElementVNode.d.ts.map +1 -1
- package/dist/vdom/ElementVNode.js +5 -2
- package/dist/vdom/FragmentVNode.d.ts.map +1 -1
- package/dist/vdom/FragmentVNode.js +5 -1
- package/package.json +1 -1
|
@@ -46,7 +46,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
46
46
|
const newChild1 = new MockVNode("a");
|
|
47
47
|
const newChild2 = new MockVNode("b");
|
|
48
48
|
const parent = new MockParentVNode([]);
|
|
49
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
49
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
50
50
|
// New children should be mounted
|
|
51
51
|
expect(newChild1.mountCalls).toBe(1);
|
|
52
52
|
expect(newChild2.mountCalls).toBe(1);
|
|
@@ -54,17 +54,19 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
54
54
|
expect(newChild2.parent).toBe(parent);
|
|
55
55
|
// Result should be the new children (since old was empty)
|
|
56
56
|
expect(result).toEqual([newChild1, newChild2]);
|
|
57
|
+
expect(hasChangedStructure).toBe(true);
|
|
57
58
|
});
|
|
58
59
|
it("should unmount all old children when new is empty", () => {
|
|
59
60
|
const oldChild1 = new MockVNode("a");
|
|
60
61
|
const oldChild2 = new MockVNode("b");
|
|
61
62
|
const parent = new MockParentVNode([oldChild1, oldChild2]);
|
|
62
|
-
const result = parent.patchChildren(toVNodes([]));
|
|
63
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([]));
|
|
63
64
|
// Old children should be unmounted
|
|
64
65
|
expect(oldChild1.unmountCalls).toBe(1);
|
|
65
66
|
expect(oldChild2.unmountCalls).toBe(1);
|
|
66
67
|
// Result should be empty
|
|
67
68
|
expect(result).toEqual([]);
|
|
69
|
+
expect(hasChangedStructure).toBe(true);
|
|
68
70
|
});
|
|
69
71
|
});
|
|
70
72
|
describe("Patching with keys", () => {
|
|
@@ -76,7 +78,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
76
78
|
const newChild1 = new MockVNode("a");
|
|
77
79
|
const newChild2 = new MockVNode("b");
|
|
78
80
|
const newChild3 = new MockVNode("c");
|
|
79
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
81
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
80
82
|
// OLD children should be patched with new children
|
|
81
83
|
expect(oldChild1.patchCalls).toBe(1);
|
|
82
84
|
expect(oldChild2.patchCalls).toBe(1);
|
|
@@ -94,6 +96,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
94
96
|
expect(oldChild3.unmountCalls).toBe(0);
|
|
95
97
|
// Result should still be the OLD children (reused)
|
|
96
98
|
expect(result).toEqual([oldChild1, oldChild2, oldChild3]);
|
|
99
|
+
expect(hasChangedStructure).toBe(false);
|
|
97
100
|
});
|
|
98
101
|
it("should handle reordered children with keys", () => {
|
|
99
102
|
const oldChild1 = new MockVNode("a");
|
|
@@ -104,7 +107,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
104
107
|
const newChild1 = new MockVNode("c");
|
|
105
108
|
const newChild2 = new MockVNode("a");
|
|
106
109
|
const newChild3 = new MockVNode("b");
|
|
107
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
110
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
108
111
|
// Old nodes should be patched with corresponding new nodes by key
|
|
109
112
|
expect(oldChild1.patchedWith).toBe(newChild2); // a->a
|
|
110
113
|
expect(oldChild2.patchedWith).toBe(newChild3); // b->b
|
|
@@ -119,6 +122,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
119
122
|
expect(result[0].key).toBe("c");
|
|
120
123
|
expect(result[1].key).toBe("a");
|
|
121
124
|
expect(result[2].key).toBe("b");
|
|
125
|
+
expect(hasChangedStructure).toBe(true);
|
|
122
126
|
});
|
|
123
127
|
it("should mount new children and unmount removed children", () => {
|
|
124
128
|
const oldChild1 = new MockVNode("a");
|
|
@@ -129,7 +133,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
129
133
|
const newChild1 = new MockVNode("a");
|
|
130
134
|
const newChild2 = new MockVNode("c");
|
|
131
135
|
const newChild3 = new MockVNode("d");
|
|
132
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
136
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
133
137
|
// a and c should be patched (reused)
|
|
134
138
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
135
139
|
expect(oldChild3.patchedWith).toBe(newChild2);
|
|
@@ -143,6 +147,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
143
147
|
expect(result).toContain(newChild3);
|
|
144
148
|
expect(result).not.toContain(oldChild2);
|
|
145
149
|
expect(result.length).toBe(3);
|
|
150
|
+
expect(hasChangedStructure).toBe(true);
|
|
146
151
|
});
|
|
147
152
|
it("should replace all children when all keys change", () => {
|
|
148
153
|
const oldChild1 = new MockVNode("a");
|
|
@@ -150,7 +155,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
150
155
|
const parent = new MockParentVNode([oldChild1, oldChild2]);
|
|
151
156
|
const newChild1 = new MockVNode("x");
|
|
152
157
|
const newChild2 = new MockVNode("y");
|
|
153
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
158
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
154
159
|
// All new children should be mounted
|
|
155
160
|
expect(newChild1.mountCalls).toBe(1);
|
|
156
161
|
expect(newChild2.mountCalls).toBe(1);
|
|
@@ -159,6 +164,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
159
164
|
expect(oldChild2.unmountCalls).toBe(1);
|
|
160
165
|
// Result should be the new children
|
|
161
166
|
expect(result).toEqual([newChild1, newChild2]);
|
|
167
|
+
expect(hasChangedStructure).toBe(true);
|
|
162
168
|
});
|
|
163
169
|
});
|
|
164
170
|
describe("Patching without keys (index-based)", () => {
|
|
@@ -170,7 +176,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
170
176
|
const newChild1 = new MockVNode();
|
|
171
177
|
const newChild2 = new MockVNode();
|
|
172
178
|
const newChild3 = new MockVNode();
|
|
173
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
179
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
174
180
|
// Should patch by index: 0->0, 1->1, 2->2
|
|
175
181
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
176
182
|
expect(oldChild2.patchedWith).toBe(newChild2);
|
|
@@ -181,6 +187,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
181
187
|
expect(oldChild3.unmountCalls).toBe(0);
|
|
182
188
|
// Result should be old children (reused)
|
|
183
189
|
expect(result).toEqual([oldChild1, oldChild2, oldChild3]);
|
|
190
|
+
expect(hasChangedStructure).toBe(false);
|
|
184
191
|
});
|
|
185
192
|
it("should mount new children when growing without keys", () => {
|
|
186
193
|
const oldChild1 = new MockVNode();
|
|
@@ -190,7 +197,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
190
197
|
const newChild2 = new MockVNode();
|
|
191
198
|
const newChild3 = new MockVNode();
|
|
192
199
|
const newChild4 = new MockVNode();
|
|
193
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3, newChild4]));
|
|
200
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3, newChild4]));
|
|
194
201
|
// First two should be patched (reused)
|
|
195
202
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
196
203
|
expect(oldChild2.patchedWith).toBe(newChild2);
|
|
@@ -202,6 +209,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
202
209
|
expect(oldChild2.unmountCalls).toBe(0);
|
|
203
210
|
// Result should be [oldChild1, oldChild2, newChild3, newChild4]
|
|
204
211
|
expect(result).toEqual([oldChild1, oldChild2, newChild3, newChild4]);
|
|
212
|
+
expect(hasChangedStructure).toBe(true);
|
|
205
213
|
});
|
|
206
214
|
it("should unmount old children when shrinking without keys", () => {
|
|
207
215
|
const oldChild1 = new MockVNode();
|
|
@@ -216,7 +224,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
216
224
|
]);
|
|
217
225
|
const newChild1 = new MockVNode();
|
|
218
226
|
const newChild2 = new MockVNode();
|
|
219
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
227
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
220
228
|
// First two should be patched
|
|
221
229
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
222
230
|
expect(oldChild2.patchedWith).toBe(newChild2);
|
|
@@ -228,6 +236,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
228
236
|
expect(oldChild2.unmountCalls).toBe(0);
|
|
229
237
|
// Result should be [oldChild1, oldChild2]
|
|
230
238
|
expect(result).toEqual([oldChild1, oldChild2]);
|
|
239
|
+
expect(hasChangedStructure).toBe(true);
|
|
231
240
|
});
|
|
232
241
|
});
|
|
233
242
|
describe("Mixed keys and indices", () => {
|
|
@@ -239,7 +248,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
239
248
|
const newChild1 = new MockVNode("a"); // key: "a"
|
|
240
249
|
const newChild2 = new MockVNode(); // key: undefined -> index 1
|
|
241
250
|
const newChild3 = new MockVNode("c"); // key: "c"
|
|
242
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
251
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
243
252
|
// Keyed children should patch by key
|
|
244
253
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
245
254
|
expect(oldChild3.patchedWith).toBe(newChild3);
|
|
@@ -251,6 +260,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
251
260
|
expect(oldChild3.unmountCalls).toBe(0);
|
|
252
261
|
// Result should be old children (reused)
|
|
253
262
|
expect(result).toEqual([oldChild1, oldChild2, oldChild3]);
|
|
263
|
+
expect(hasChangedStructure).toBe(false);
|
|
254
264
|
});
|
|
255
265
|
});
|
|
256
266
|
describe("Real-world scenarios", () => {
|
|
@@ -259,7 +269,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
259
269
|
const parent = new MockParentVNode([oldChild1]);
|
|
260
270
|
const newChild1 = new MockVNode("title");
|
|
261
271
|
const newChild2 = new MockVNode("details");
|
|
262
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
272
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
263
273
|
// Title should be patched (reused)
|
|
264
274
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
265
275
|
// Details should be mounted
|
|
@@ -268,13 +278,14 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
268
278
|
expect(oldChild1.unmountCalls).toBe(0);
|
|
269
279
|
// Result should be [oldChild1, newChild2]
|
|
270
280
|
expect(result).toEqual([oldChild1, newChild2]);
|
|
281
|
+
expect(hasChangedStructure).toBe(true);
|
|
271
282
|
});
|
|
272
283
|
it("should handle conditional rendering (component -> null)", () => {
|
|
273
284
|
const oldChild1 = new MockVNode("title");
|
|
274
285
|
const oldChild2 = new MockVNode("details");
|
|
275
286
|
const parent = new MockParentVNode([oldChild1, oldChild2]);
|
|
276
287
|
const newChild1 = new MockVNode("title");
|
|
277
|
-
const result = parent.patchChildren(toVNodes([newChild1]));
|
|
288
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1]));
|
|
278
289
|
// Title should be patched (reused)
|
|
279
290
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
280
291
|
// Details should be unmounted
|
|
@@ -283,6 +294,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
283
294
|
expect(oldChild1.unmountCalls).toBe(0);
|
|
284
295
|
// Result should be [oldChild1]
|
|
285
296
|
expect(result).toEqual([oldChild1]);
|
|
297
|
+
expect(hasChangedStructure).toBe(true);
|
|
286
298
|
});
|
|
287
299
|
it("should handle list with items added at beginning", () => {
|
|
288
300
|
const oldChild1 = new MockVNode("item-1");
|
|
@@ -291,7 +303,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
291
303
|
const newChild1 = new MockVNode("item-0"); // New item at start
|
|
292
304
|
const newChild2 = new MockVNode("item-1");
|
|
293
305
|
const newChild3 = new MockVNode("item-2");
|
|
294
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
306
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
295
307
|
// New item should be mounted
|
|
296
308
|
expect(newChild1.mountCalls).toBe(1);
|
|
297
309
|
// Existing items should be patched (reused)
|
|
@@ -302,6 +314,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
302
314
|
expect(oldChild2.unmountCalls).toBe(0);
|
|
303
315
|
// Result should be [newChild1, oldChild1, oldChild2]
|
|
304
316
|
expect(result).toEqual([newChild1, oldChild1, oldChild2]);
|
|
317
|
+
expect(hasChangedStructure).toBe(true);
|
|
305
318
|
});
|
|
306
319
|
it("should handle list with items added at end", () => {
|
|
307
320
|
const oldChild1 = new MockVNode("item-1");
|
|
@@ -310,7 +323,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
310
323
|
const newChild1 = new MockVNode("item-1");
|
|
311
324
|
const newChild2 = new MockVNode("item-2");
|
|
312
325
|
const newChild3 = new MockVNode("item-3"); // New item at end
|
|
313
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
326
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
314
327
|
// Existing items should be patched (reused)
|
|
315
328
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
316
329
|
expect(oldChild2.patchedWith).toBe(newChild2);
|
|
@@ -321,6 +334,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
321
334
|
expect(oldChild2.unmountCalls).toBe(0);
|
|
322
335
|
// Result should be [oldChild1, oldChild2, newChild3]
|
|
323
336
|
expect(result).toEqual([oldChild1, oldChild2, newChild3]);
|
|
337
|
+
expect(hasChangedStructure).toBe(true);
|
|
324
338
|
});
|
|
325
339
|
it("should handle list with item removed from middle", () => {
|
|
326
340
|
const oldChild1 = new MockVNode("item-1");
|
|
@@ -329,7 +343,7 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
329
343
|
const parent = new MockParentVNode([oldChild1, oldChild2, oldChild3]);
|
|
330
344
|
const newChild1 = new MockVNode("item-1");
|
|
331
345
|
const newChild2 = new MockVNode("item-3"); // item-2 removed
|
|
332
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
346
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
333
347
|
// item-1 and item-3 should be patched (reused)
|
|
334
348
|
expect(oldChild1.patchedWith).toBe(newChild1);
|
|
335
349
|
expect(oldChild3.patchedWith).toBe(newChild2);
|
|
@@ -340,32 +354,35 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
340
354
|
expect(oldChild3.unmountCalls).toBe(0);
|
|
341
355
|
// Result should be [oldChild1, oldChild3]
|
|
342
356
|
expect(result).toEqual([oldChild1, oldChild3]);
|
|
357
|
+
expect(hasChangedStructure).toBe(true);
|
|
343
358
|
});
|
|
344
359
|
it("should handle empty -> multiple children", () => {
|
|
345
360
|
const parent = new MockParentVNode([]);
|
|
346
361
|
const newChild1 = new MockVNode("a");
|
|
347
362
|
const newChild2 = new MockVNode("b");
|
|
348
363
|
const newChild3 = new MockVNode("c");
|
|
349
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
364
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2, newChild3]));
|
|
350
365
|
// All should be mounted
|
|
351
366
|
expect(newChild1.mountCalls).toBe(1);
|
|
352
367
|
expect(newChild2.mountCalls).toBe(1);
|
|
353
368
|
expect(newChild3.mountCalls).toBe(1);
|
|
354
369
|
// Result should be the new children
|
|
355
370
|
expect(result).toEqual([newChild1, newChild2, newChild3]);
|
|
371
|
+
expect(hasChangedStructure).toBe(true);
|
|
356
372
|
});
|
|
357
373
|
it("should handle multiple children -> empty", () => {
|
|
358
374
|
const oldChild1 = new MockVNode("a");
|
|
359
375
|
const oldChild2 = new MockVNode("b");
|
|
360
376
|
const oldChild3 = new MockVNode("c");
|
|
361
377
|
const parent = new MockParentVNode([oldChild1, oldChild2, oldChild3]);
|
|
362
|
-
const result = parent.patchChildren(toVNodes([]));
|
|
378
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([]));
|
|
363
379
|
// All should be unmounted
|
|
364
380
|
expect(oldChild1.unmountCalls).toBe(1);
|
|
365
381
|
expect(oldChild2.unmountCalls).toBe(1);
|
|
366
382
|
expect(oldChild3.unmountCalls).toBe(1);
|
|
367
383
|
// Result should be empty
|
|
368
384
|
expect(result).toEqual([]);
|
|
385
|
+
expect(hasChangedStructure).toBe(true);
|
|
369
386
|
});
|
|
370
387
|
});
|
|
371
388
|
describe("Object reference preservation", () => {
|
|
@@ -375,13 +392,14 @@ describe("patchChildren (new approach: keep old, patch in new)", () => {
|
|
|
375
392
|
const parent = new MockParentVNode([oldChild1, oldChild2]);
|
|
376
393
|
const newChild1 = new MockVNode("a");
|
|
377
394
|
const newChild2 = new MockVNode("b");
|
|
378
|
-
const result = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
395
|
+
const { children: result, hasChangedStructure } = parent.patchChildren(toVNodes([newChild1, newChild2]));
|
|
379
396
|
// The result should contain the EXACT SAME object references as the old children
|
|
380
397
|
expect(result[0]).toBe(oldChild1); // Same object reference
|
|
381
398
|
expect(result[1]).toBe(oldChild2); // Same object reference
|
|
382
399
|
// NOT the new children
|
|
383
400
|
expect(result[0]).not.toBe(newChild1);
|
|
384
401
|
expect(result[1]).not.toBe(newChild2);
|
|
402
|
+
expect(hasChangedStructure).toBe(false);
|
|
385
403
|
});
|
|
386
404
|
});
|
|
387
405
|
});
|
|
@@ -17,6 +17,9 @@ export declare abstract class AbstractVNode {
|
|
|
17
17
|
getElements(): Node[];
|
|
18
18
|
getParentElement(): HTMLElement;
|
|
19
19
|
protected canPatch(oldNode: VNode, newNode: VNode): boolean;
|
|
20
|
-
patchChildren(newChildren: VNode[]):
|
|
20
|
+
patchChildren(newChildren: VNode[]): {
|
|
21
|
+
children: VNode[];
|
|
22
|
+
hasChangedStructure: boolean;
|
|
23
|
+
};
|
|
21
24
|
}
|
|
22
25
|
//# sourceMappingURL=AbstractVNode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,IAAI,IAAI;IACzB,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAWrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE;
|
|
1
|
+
{"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,IAAI,IAAI;IACzB,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAWrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG;QACnC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,mBAAmB,EAAE,OAAO,CAAC;KAC9B;CAqEF"}
|
|
@@ -57,19 +57,23 @@ export class AbstractVNode {
|
|
|
57
57
|
// When there are only new children, we just mount them
|
|
58
58
|
if (newChildren && prevChildren.length === 0) {
|
|
59
59
|
newChildren.forEach((child) => child.mount(this));
|
|
60
|
-
return newChildren;
|
|
60
|
+
return { children: newChildren, hasChangedStructure: true };
|
|
61
61
|
}
|
|
62
62
|
// If we want to remove all children, we just unmount the previous ones
|
|
63
63
|
if (!newChildren.length && prevChildren.length) {
|
|
64
64
|
prevChildren.forEach((child) => child.unmount());
|
|
65
|
-
return [];
|
|
65
|
+
return { children: [], hasChangedStructure: true };
|
|
66
66
|
}
|
|
67
67
|
const oldKeys = {};
|
|
68
68
|
prevChildren.forEach((prevChild, index) => {
|
|
69
|
-
oldKeys[prevChild.key || index] =
|
|
69
|
+
oldKeys[prevChild.key || index] = {
|
|
70
|
+
vnode: prevChild,
|
|
71
|
+
index,
|
|
72
|
+
};
|
|
70
73
|
});
|
|
71
74
|
// Build result array in the NEW order
|
|
72
75
|
const result = [];
|
|
76
|
+
let hasChangedStructure = false;
|
|
73
77
|
newChildren.forEach((newChild, index) => {
|
|
74
78
|
const key = newChild.key || index;
|
|
75
79
|
const prevChild = oldKeys[key];
|
|
@@ -77,30 +81,35 @@ export class AbstractVNode {
|
|
|
77
81
|
// New child - mount and add to result
|
|
78
82
|
newChild.mount(this);
|
|
79
83
|
result.push(newChild);
|
|
84
|
+
hasChangedStructure = true;
|
|
80
85
|
}
|
|
81
|
-
else if (prevChild === newChild) {
|
|
86
|
+
else if (prevChild?.vnode === newChild) {
|
|
82
87
|
// Same instance - no patching needed, just reuse
|
|
83
|
-
result.push(prevChild);
|
|
88
|
+
result.push(prevChild.vnode);
|
|
84
89
|
delete oldKeys[key];
|
|
90
|
+
hasChangedStructure = hasChangedStructure || prevChild.index !== index;
|
|
85
91
|
}
|
|
86
|
-
else if (this.canPatch(prevChild, newChild)) {
|
|
92
|
+
else if (this.canPatch(prevChild.vnode, newChild)) {
|
|
87
93
|
// Compatible types - patch and reuse old VNode
|
|
88
|
-
prevChild.patch(newChild);
|
|
89
|
-
result.push(prevChild);
|
|
94
|
+
prevChild.vnode.patch(newChild);
|
|
95
|
+
result.push(prevChild.vnode);
|
|
90
96
|
delete oldKeys[key];
|
|
97
|
+
hasChangedStructure = hasChangedStructure || prevChild.index !== index;
|
|
91
98
|
}
|
|
92
99
|
else {
|
|
93
100
|
// Incompatible types - replace completely
|
|
94
101
|
newChild.mount(this);
|
|
95
|
-
prevChild.unmount();
|
|
102
|
+
prevChild.vnode.unmount();
|
|
96
103
|
result.push(newChild);
|
|
97
104
|
delete oldKeys[key];
|
|
105
|
+
hasChangedStructure = true;
|
|
98
106
|
}
|
|
99
107
|
});
|
|
100
108
|
// Unmount any old children that weren't reused
|
|
101
109
|
for (const key in oldKeys) {
|
|
102
|
-
oldKeys[key].unmount();
|
|
110
|
+
oldKeys[key].vnode.unmount();
|
|
111
|
+
hasChangedStructure = true;
|
|
103
112
|
}
|
|
104
|
-
return result;
|
|
113
|
+
return { children: result, hasChangedStructure };
|
|
105
114
|
}
|
|
106
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;
|
|
1
|
+
{"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAmI7B,KAAK,CAAC,OAAO,EAAE,cAAc;IAW7B,OAAO;CAcR"}
|
|
@@ -4,7 +4,6 @@ import { FragmentVNode } from "./FragmentVNode";
|
|
|
4
4
|
import { RootVNode } from "./RootVNode";
|
|
5
5
|
import { normalizeChildren } from "./utils";
|
|
6
6
|
import { currentRoot } from "./RootVNode";
|
|
7
|
-
import { ElementVNode } from "./ElementVNode";
|
|
8
7
|
export function getCurrentComponent() {
|
|
9
8
|
if (!currentRoot) {
|
|
10
9
|
throw new Error("No current root");
|
|
@@ -97,15 +96,9 @@ export class ComponentVNode extends AbstractVNode {
|
|
|
97
96
|
this.root?.setAsCurrent();
|
|
98
97
|
const newChildren = executeRender();
|
|
99
98
|
const prevChildren = this.children;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const canSelfUpdate = prevChildren.length === 1 &&
|
|
104
|
-
this.children.length === 1 &&
|
|
105
|
-
prevChildren[0] instanceof ElementVNode &&
|
|
106
|
-
this.children[0] instanceof ElementVNode &&
|
|
107
|
-
this.canPatch(prevChildren[0], this.children[0]);
|
|
108
|
-
if (!canSelfUpdate) {
|
|
99
|
+
const { children, hasChangedStructure } = this.patchChildren(newChildren);
|
|
100
|
+
this.children = children;
|
|
101
|
+
if (hasChangedStructure) {
|
|
109
102
|
this.parent?.rerender();
|
|
110
103
|
}
|
|
111
104
|
this.root?.clearCurrent();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUvC,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IASd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUvC,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IASd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;IAY3B,OAAO;IAYP,OAAO,CAAC,OAAO,CAoCb;IACF,OAAO,CAAC,UAAU;IAGlB,OAAO,CAAC,gBAAgB;IAgBxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAyBxB"}
|
|
@@ -57,8 +57,11 @@ export class ElementVNode extends AbstractVNode {
|
|
|
57
57
|
patch(newNode) {
|
|
58
58
|
this.patchProps(newNode.props);
|
|
59
59
|
this.props = newNode.props;
|
|
60
|
-
|
|
61
|
-
this.
|
|
60
|
+
const { children, hasChangedStructure } = this.patchChildren(newNode.children);
|
|
61
|
+
this.children = children;
|
|
62
|
+
if (hasChangedStructure) {
|
|
63
|
+
this.syncDOMChildren();
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
66
|
unmount() {
|
|
64
67
|
this.children.forEach((child) => child.unmount());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKhD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAW7B,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,OAAO,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKhD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAW7B,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,OAAO,EAAE,aAAa;IAS5B,OAAO;CAMR"}
|
|
@@ -23,7 +23,11 @@ export class FragmentVNode extends AbstractVNode {
|
|
|
23
23
|
this.parent?.rerender();
|
|
24
24
|
}
|
|
25
25
|
patch(newNode) {
|
|
26
|
-
|
|
26
|
+
const { children, hasChangedStructure } = this.patchChildren(newNode.children);
|
|
27
|
+
this.children = children;
|
|
28
|
+
if (hasChangedStructure) {
|
|
29
|
+
this.rerender();
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
unmount() {
|
|
29
33
|
this.children.forEach((child) => child.unmount());
|