@thyn/core 0.0.228 → 0.0.232
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/element.d.ts +1 -1
- package/dist/element.js +227 -275
- package/dist/signals.js +0 -1
- package/package.json +1 -1
- package/src/element.ts +271 -279
- package/src/signals.ts +0 -1
- package/tsconfig.tsbuildinfo +1 -1
package/dist/element.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function mount(app: any, parent: any): void;
|
|
2
2
|
export declare function collectEffect(effectFn: any): void;
|
|
3
|
-
export declare function createReactiveTextNode(v: any):
|
|
3
|
+
export declare function createReactiveTextNode(v: any): Text;
|
|
4
4
|
export declare function component(name: any, props?: any): any;
|
|
5
5
|
export declare function setAttribute(el: any, key: any, val: any): any;
|
|
6
6
|
export declare function setProperty(el: any, key: any, val: any): any;
|
package/dist/element.js
CHANGED
|
@@ -9,15 +9,21 @@ export function collectEffect(effectFn) {
|
|
|
9
9
|
}
|
|
10
10
|
collectingHead = effectFn;
|
|
11
11
|
}
|
|
12
|
+
// export function createReactiveTextNode(v) {
|
|
13
|
+
// let n;
|
|
14
|
+
// staticEffect(() => {
|
|
15
|
+
// if (n) {
|
|
16
|
+
// n.nodeValue = v();
|
|
17
|
+
// } else {
|
|
18
|
+
// n = document.createTextNode(v());
|
|
19
|
+
// }
|
|
20
|
+
// });
|
|
21
|
+
// return n;
|
|
22
|
+
// }
|
|
12
23
|
export function createReactiveTextNode(v) {
|
|
13
|
-
|
|
24
|
+
const n = document.createTextNode(v());
|
|
14
25
|
staticEffect(() => {
|
|
15
|
-
|
|
16
|
-
n.nodeValue = v();
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
n = document.createTextNode(v());
|
|
20
|
-
}
|
|
26
|
+
n.nodeValue = v();
|
|
21
27
|
});
|
|
22
28
|
return n;
|
|
23
29
|
}
|
|
@@ -75,7 +81,7 @@ export function setReactiveAttribute(el, key, val) {
|
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
77
83
|
if (v !== undefined)
|
|
78
|
-
el.setAttribute(key,
|
|
84
|
+
el.setAttribute(key, v);
|
|
79
85
|
ran = true;
|
|
80
86
|
}));
|
|
81
87
|
}
|
|
@@ -246,7 +252,9 @@ export function list(props, terminal = false) {
|
|
|
246
252
|
const teardownNode = terminal ? shallowTeardown : teardown;
|
|
247
253
|
let parent;
|
|
248
254
|
let outlet = document.createDocumentFragment();
|
|
249
|
-
|
|
255
|
+
// State
|
|
256
|
+
let prevItems = [];
|
|
257
|
+
let rowNodes = []; // The "Shadow Array" - avoids reading DOM
|
|
250
258
|
const startBookend = document.createComment("");
|
|
251
259
|
const endBookend = document.createComment("");
|
|
252
260
|
startBookend.$frag = outlet;
|
|
@@ -254,171 +262,151 @@ export function list(props, terminal = false) {
|
|
|
254
262
|
const render = props.render;
|
|
255
263
|
staticEffect(() => {
|
|
256
264
|
parent = startBookend.parentNode;
|
|
265
|
+
// 1. Initialization / First Render
|
|
257
266
|
if (!parent) {
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
const items = props.items();
|
|
268
|
+
// Render all items
|
|
269
|
+
const newNodes = items.map(render);
|
|
270
|
+
outlet.append(startBookend, ...newNodes, endBookend);
|
|
271
|
+
// Save state
|
|
272
|
+
prevItems = items;
|
|
273
|
+
rowNodes = newNodes;
|
|
260
274
|
return;
|
|
261
275
|
}
|
|
262
|
-
|
|
263
|
-
|
|
276
|
+
const nextItems = props.items();
|
|
277
|
+
const newLength = nextItems.length;
|
|
264
278
|
let oldLength = prevItems.length;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
childNodes.push(ptr);
|
|
275
|
-
ptr = ptr.nextSibling;
|
|
276
|
-
}
|
|
277
|
-
const offset = 0;
|
|
278
|
-
if (!newLength) {
|
|
279
|
-
const removalQueue = [];
|
|
280
|
-
const end = prevItems.length + offset;
|
|
281
|
-
for (let i = offset; i < end; i++) {
|
|
282
|
-
const ch = childNodes[i];
|
|
283
|
-
teardownNode(ch);
|
|
284
|
-
removalQueue.push(ch);
|
|
285
|
-
}
|
|
286
|
-
for (const ch of removalQueue) {
|
|
287
|
-
remove(ch);
|
|
279
|
+
// 2. Fast Path: Clear All
|
|
280
|
+
if (newLength === 0) {
|
|
281
|
+
if (oldLength !== 0) {
|
|
282
|
+
for (let i = 0; i < oldLength; i++) {
|
|
283
|
+
teardownNode(rowNodes[i]);
|
|
284
|
+
remove(rowNodes[i]);
|
|
285
|
+
}
|
|
286
|
+
rowNodes = [];
|
|
287
|
+
prevItems = [];
|
|
288
288
|
}
|
|
289
|
-
prevItems = nextItems;
|
|
290
|
-
nextItems = null;
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
let start = nextItems.findIndex((item, index) => prevItems[index] !== item);
|
|
294
|
-
if (start === oldLength) {
|
|
295
|
-
endBookend.before(...nextItems.slice(start).map(render));
|
|
296
|
-
prevItems = nextItems;
|
|
297
|
-
nextItems = null;
|
|
298
289
|
return;
|
|
299
290
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
291
|
+
// 3. Fast Path: Create All (from empty)
|
|
292
|
+
if (oldLength === 0) {
|
|
293
|
+
const newNodes = nextItems.map(render);
|
|
294
|
+
endBookend.before(...newNodes);
|
|
295
|
+
rowNodes = newNodes;
|
|
306
296
|
prevItems = nextItems;
|
|
307
|
-
nextItems = null;
|
|
308
297
|
return;
|
|
309
298
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
prevItems = nextItems;
|
|
317
|
-
nextItems = null;
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
// suffix
|
|
321
|
-
for (oldLength--, newLength--; newLength > start &&
|
|
322
|
-
oldLength >= start &&
|
|
323
|
-
nextItems[newLength] === prevItems[oldLength]; oldLength--, newLength--)
|
|
324
|
-
;
|
|
325
|
-
const nextKeys = new Set(nextItems);
|
|
326
|
-
const removalQueue = [];
|
|
327
|
-
for (let i = start; i <= oldLength; i++) {
|
|
328
|
-
if (!nextKeys.has(prevItems[i])) {
|
|
329
|
-
const ch = childNodes[i + offset];
|
|
330
|
-
teardownNode(ch);
|
|
331
|
-
removalQueue.push(ch);
|
|
332
|
-
childNodes[i + offset] = null;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
for (const e of removalQueue) {
|
|
336
|
-
remove(e);
|
|
299
|
+
// 4. Reconciliation
|
|
300
|
+
let start = 0;
|
|
301
|
+
let minLen = Math.min(oldLength, newLength);
|
|
302
|
+
// Prefix Scan (Cheaper JS Array read vs DOM read)
|
|
303
|
+
while (start < minLen && prevItems[start] === nextItems[start]) {
|
|
304
|
+
start++;
|
|
337
305
|
}
|
|
338
|
-
|
|
306
|
+
// Optimization: Append only
|
|
307
|
+
if (start === oldLength && newLength > oldLength) {
|
|
308
|
+
const newPart = nextItems.slice(start);
|
|
309
|
+
const newNodes = newPart.map(render);
|
|
310
|
+
endBookend.before(...newNodes);
|
|
311
|
+
rowNodes = rowNodes.concat(newNodes);
|
|
339
312
|
prevItems = nextItems;
|
|
340
|
-
nextItems = null;
|
|
341
313
|
return;
|
|
342
314
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
(
|
|
347
|
-
|
|
348
|
-
keyMap.set(prevItems[i], {
|
|
349
|
-
el: childNodes[i + offset],
|
|
350
|
-
item: prevItems[i],
|
|
351
|
-
});
|
|
315
|
+
// Optimization: Truncate only
|
|
316
|
+
if (start === newLength && oldLength > newLength) {
|
|
317
|
+
for (let i = start; i < oldLength; i++) {
|
|
318
|
+
teardownNode(rowNodes[i]);
|
|
319
|
+
remove(rowNodes[i]);
|
|
352
320
|
}
|
|
353
|
-
|
|
354
|
-
if (newLength === oldLength && keyMap.size > (newLength - start + 1) / 2) {
|
|
355
|
-
const lastOrdered = start > 0 ? childNodes[start + offset - 1] : startBookend;
|
|
356
|
-
const set = [];
|
|
357
|
-
for (let i = start; i <= newLength; i++) {
|
|
358
|
-
set.push(keyMap.get(nextItems[i])?.el ?? childNodes[i + offset]);
|
|
359
|
-
}
|
|
360
|
-
lastOrdered.after(...set);
|
|
321
|
+
rowNodes.length = newLength; // JS Array Truncate
|
|
361
322
|
prevItems = nextItems;
|
|
362
|
-
keyMap = null;
|
|
363
|
-
nextItems = null;
|
|
364
323
|
return;
|
|
365
324
|
}
|
|
366
|
-
|
|
325
|
+
// Suffix Scan
|
|
326
|
+
let end = 0;
|
|
327
|
+
// We stop if the suffix hits the prefix
|
|
328
|
+
while (newLength - 1 - end >= start &&
|
|
329
|
+
oldLength - 1 - end >= start &&
|
|
330
|
+
nextItems[newLength - 1 - end] === prevItems[oldLength - 1 - end]) {
|
|
331
|
+
end++;
|
|
332
|
+
}
|
|
333
|
+
// 5. Complex Diff (The Middle)
|
|
334
|
+
const oldStart = start;
|
|
335
|
+
const oldEnd = oldLength - end;
|
|
336
|
+
const newEnd = newLength - end;
|
|
337
|
+
// A. Build Map of existing items in the "changed" region
|
|
338
|
+
// key -> { node, index }
|
|
339
|
+
const keyMap = new Map();
|
|
340
|
+
for (let i = oldStart; i < oldEnd; i++) {
|
|
341
|
+
const item = prevItems[i];
|
|
342
|
+
// If duplicate items exist, first one wins or logic needs to be more robust.
|
|
343
|
+
// Assuming unique keys for simplicity or using last-write-wins:
|
|
344
|
+
// if (!keyMap.has(item)) {
|
|
345
|
+
keyMap.set(item, rowNodes[i]);
|
|
346
|
+
// } else {
|
|
347
|
+
// Handle duplicates by removing the extra immediately?
|
|
348
|
+
// Or handle collision. For now, assume distinct items or standard behavior.
|
|
349
|
+
// }
|
|
350
|
+
}
|
|
351
|
+
// B. Setup for new node list construction
|
|
352
|
+
const nextRowNodes = new Array(newLength);
|
|
353
|
+
// Copy Prefix
|
|
367
354
|
for (let i = 0; i < start; i++) {
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
355
|
+
nextRowNodes[i] = rowNodes[i];
|
|
356
|
+
}
|
|
357
|
+
// Copy Suffix
|
|
358
|
+
for (let i = 0; i < end; i++) {
|
|
359
|
+
nextRowNodes[newLength - 1 - i] = rowNodes[oldLength - 1 - i];
|
|
360
|
+
}
|
|
361
|
+
// C. Find anchor for insertions
|
|
362
|
+
// We insert before the first node of the suffix, or the endBookend.
|
|
363
|
+
const anchor = (end > 0) ? rowNodes[oldLength - end] : endBookend;
|
|
364
|
+
// D. Iterate new middle
|
|
365
|
+
for (let i = oldStart; i < newEnd; i++) {
|
|
366
|
+
const newItem = nextItems[i];
|
|
367
|
+
let node;
|
|
368
|
+
if (keyMap.has(newItem)) {
|
|
369
|
+
// Reuse existing
|
|
370
|
+
node = keyMap.get(newItem);
|
|
371
|
+
keyMap.delete(newItem);
|
|
372
|
+
// DOM Move:
|
|
373
|
+
// We always insertBefore the anchor.
|
|
374
|
+
// Since we are iterating forward, "anchor" isn't static.
|
|
375
|
+
// Actually, simply inserting before the *current* anchor works if we
|
|
376
|
+
// process carefully, but standard "place and move cursor" is safer.
|
|
377
|
+
parent.insertBefore(node, anchor);
|
|
377
378
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
const mappedOld = keyMap.get(newChd);
|
|
384
|
-
if (mappedOld) {
|
|
385
|
-
const oldDom = cursor;
|
|
386
|
-
const { el, item } = mappedOld;
|
|
387
|
-
if (oldDom !== el) {
|
|
388
|
-
const tmp = el.nextSibling;
|
|
389
|
-
parent.insertBefore(el, oldDom);
|
|
390
|
-
parent.insertBefore(oldDom, tmp);
|
|
391
|
-
cursor = el.nextSibling;
|
|
392
|
-
}
|
|
393
|
-
else if (item !== newChd) {
|
|
394
|
-
const next = el.nextSibling;
|
|
395
|
-
replaceWith(newChd, el, render);
|
|
396
|
-
cursor = next;
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
cursor = el.nextSibling;
|
|
400
|
-
}
|
|
401
|
-
keyMap.delete(newChd);
|
|
402
|
-
}
|
|
403
|
-
else if (oldChd !== newChd) {
|
|
404
|
-
parent.insertBefore(render(newChd), cursor);
|
|
379
|
+
else {
|
|
380
|
+
// Create new
|
|
381
|
+
node = render(newItem);
|
|
382
|
+
parent.insertBefore(node, anchor);
|
|
405
383
|
}
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
384
|
+
nextRowNodes[i] = node;
|
|
385
|
+
}
|
|
386
|
+
// E. Cleanup
|
|
387
|
+
// Anything remaining in keyMap is gone
|
|
388
|
+
for (const node of keyMap.values()) {
|
|
389
|
+
teardownNode(node);
|
|
390
|
+
remove(node);
|
|
391
|
+
}
|
|
392
|
+
// Handle "middle" items that weren't in keyMap (duplicates logic)
|
|
393
|
+
// or implicit removals handled by the map logic.
|
|
394
|
+
// Specifically: We iterated [oldStart...oldEnd] to build the map.
|
|
395
|
+
// If an item was in that range but NOT in the new range, it's in the map.
|
|
396
|
+
// If it WAS in the new range, we removed it from the map.
|
|
397
|
+
// So map.values() is exactly what needs to die.
|
|
398
|
+
// Update State
|
|
399
|
+
rowNodes = nextRowNodes;
|
|
413
400
|
prevItems = nextItems;
|
|
414
|
-
nextItems = null;
|
|
415
401
|
});
|
|
416
402
|
return outlet;
|
|
417
403
|
}
|
|
418
404
|
export function isolatedTerminalList(props) {
|
|
419
405
|
let parent;
|
|
420
406
|
let outlet = document.createDocumentFragment();
|
|
421
|
-
|
|
407
|
+
// State
|
|
408
|
+
let prevItems = [];
|
|
409
|
+
let rowNodes = []; // The "Shadow Array"
|
|
422
410
|
const startBookend = document.createComment("");
|
|
423
411
|
const endBookend = document.createComment("");
|
|
424
412
|
startBookend.$frag = outlet;
|
|
@@ -426,157 +414,121 @@ export function isolatedTerminalList(props) {
|
|
|
426
414
|
const render = props.render;
|
|
427
415
|
staticEffect(() => {
|
|
428
416
|
parent = startBookend.parentNode;
|
|
417
|
+
// 1. Initialization
|
|
429
418
|
if (!parent) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
let newLength = nextItems.length;
|
|
436
|
-
let oldLength = prevItems.length;
|
|
437
|
-
if (!oldLength && newLength) {
|
|
438
|
-
endBookend.before(...nextItems.map(render));
|
|
439
|
-
prevItems = nextItems;
|
|
440
|
-
nextItems = null;
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
const childNodeList = parent.childNodes;
|
|
444
|
-
if (!newLength) {
|
|
445
|
-
const end = childNodeList.length - 1;
|
|
446
|
-
for (let i = 1; i < end; i++) {
|
|
447
|
-
shallowTeardown(childNodeList[i]);
|
|
448
|
-
}
|
|
449
|
-
parent.textContent = "";
|
|
450
|
-
parent.append(startBookend, endBookend);
|
|
451
|
-
prevItems = nextItems;
|
|
452
|
-
nextItems = null;
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
let start = nextItems.findIndex((item, index) => prevItems[index] !== item);
|
|
456
|
-
if (start === oldLength) {
|
|
457
|
-
endBookend.before(...nextItems.slice(start).map(render));
|
|
458
|
-
prevItems = nextItems;
|
|
459
|
-
nextItems = null;
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
let childNodes = Array.from(childNodeList);
|
|
463
|
-
if (start < 0) {
|
|
464
|
-
for (let i = nextItems.length; i < oldLength; i++) {
|
|
465
|
-
const e = childNodes[1 + --oldLength];
|
|
466
|
-
shallowTeardown(e);
|
|
467
|
-
e.remove();
|
|
468
|
-
}
|
|
469
|
-
prevItems = nextItems;
|
|
470
|
-
nextItems = null;
|
|
471
|
-
childNodes = null;
|
|
419
|
+
const items = props.items();
|
|
420
|
+
const newNodes = items.map(render);
|
|
421
|
+
outlet.append(startBookend, ...newNodes, endBookend);
|
|
422
|
+
prevItems = items;
|
|
423
|
+
rowNodes = newNodes;
|
|
472
424
|
return;
|
|
473
425
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
426
|
+
const nextItems = props.items();
|
|
427
|
+
const newLength = nextItems.length;
|
|
428
|
+
const oldLength = prevItems.length;
|
|
429
|
+
// 2. Fast Path: Clear All
|
|
430
|
+
if (newLength === 0) {
|
|
431
|
+
if (oldLength !== 0) {
|
|
432
|
+
// Optimization: If the parent only contains our list (between bookends),
|
|
433
|
+
// we might want to use textContent = "". However, to be safe with
|
|
434
|
+
// bookends logic, we remove specifically known nodes.
|
|
435
|
+
for (let i = 0; i < oldLength; i++) {
|
|
436
|
+
const node = rowNodes[i];
|
|
437
|
+
shallowTeardown(node);
|
|
438
|
+
// node.remove();
|
|
439
|
+
}
|
|
440
|
+
parent.textContent = "";
|
|
441
|
+
parent.append(startBookend, endBookend);
|
|
442
|
+
rowNodes = [];
|
|
443
|
+
prevItems = [];
|
|
479
444
|
}
|
|
480
|
-
prevItems = nextItems;
|
|
481
|
-
nextItems = null;
|
|
482
|
-
childNodes = null;
|
|
483
445
|
return;
|
|
484
446
|
}
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
;
|
|
490
|
-
const nextKeys = new Set(nextItems);
|
|
491
|
-
const removalQueue = [];
|
|
492
|
-
for (let i = start; i <= oldLength; i++) {
|
|
493
|
-
if (!nextKeys.has(prevItems[i])) {
|
|
494
|
-
const ch = childNodes[i + 1];
|
|
495
|
-
shallowTeardown(ch);
|
|
496
|
-
removalQueue.push(ch);
|
|
497
|
-
childNodes[i + 1] = null;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
if (removalQueue.length === prevItems.length) {
|
|
501
|
-
parent.textContent = "";
|
|
502
|
-
parent.append(startBookend, ...nextItems.map(render), endBookend);
|
|
447
|
+
// 3. Fast Path: Create All
|
|
448
|
+
if (oldLength === 0) {
|
|
449
|
+
const newNodes = nextItems.map(render);
|
|
450
|
+
endBookend.before(...newNodes);
|
|
451
|
+
rowNodes = newNodes;
|
|
503
452
|
prevItems = nextItems;
|
|
504
|
-
nextItems = null;
|
|
505
|
-
childNodes = null;
|
|
506
453
|
return;
|
|
507
454
|
}
|
|
508
|
-
|
|
509
|
-
|
|
455
|
+
// 4. Reconciliation
|
|
456
|
+
let start = 0;
|
|
457
|
+
const minLen = Math.min(oldLength, newLength);
|
|
458
|
+
// Prefix Scan
|
|
459
|
+
while (start < minLen && prevItems[start] === nextItems[start]) {
|
|
460
|
+
start++;
|
|
510
461
|
}
|
|
511
|
-
|
|
462
|
+
// Optimization: Append Suffix
|
|
463
|
+
if (start === oldLength && newLength > oldLength) {
|
|
464
|
+
const newPart = nextItems.slice(start);
|
|
465
|
+
const newNodes = newPart.map(render);
|
|
466
|
+
endBookend.before(...newNodes);
|
|
467
|
+
rowNodes = rowNodes.concat(newNodes);
|
|
512
468
|
prevItems = nextItems;
|
|
513
|
-
nextItems = null;
|
|
514
|
-
childNodes = null;
|
|
515
469
|
return;
|
|
516
470
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
el: childNodes[i + 1],
|
|
524
|
-
item: prevItems[i],
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if (newLength === oldLength && keyMap.size > (newLength - start + 1) / 2) {
|
|
529
|
-
const lastOrdered = childNodes[start];
|
|
530
|
-
const set = [];
|
|
531
|
-
for (let i = start; i <= newLength; i++) {
|
|
532
|
-
set.push(keyMap.get(nextItems[i])?.el ?? childNodes[i + 1]);
|
|
471
|
+
// Optimization: Truncate Suffix
|
|
472
|
+
if (start === newLength && oldLength > newLength) {
|
|
473
|
+
for (let i = start; i < oldLength; i++) {
|
|
474
|
+
const node = rowNodes[i];
|
|
475
|
+
shallowTeardown(node);
|
|
476
|
+
node.remove();
|
|
533
477
|
}
|
|
534
|
-
|
|
478
|
+
rowNodes.length = newLength;
|
|
535
479
|
prevItems = nextItems;
|
|
536
|
-
keyMap = null;
|
|
537
|
-
nextItems = null;
|
|
538
|
-
childNodes = null;
|
|
539
480
|
return;
|
|
540
481
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
482
|
+
// Suffix Scan
|
|
483
|
+
let end = 0;
|
|
484
|
+
while (newLength - 1 - end >= start &&
|
|
485
|
+
oldLength - 1 - end >= start &&
|
|
486
|
+
nextItems[newLength - 1 - end] === prevItems[oldLength - 1 - end]) {
|
|
487
|
+
end++;
|
|
488
|
+
}
|
|
489
|
+
// 5. Complex Diff (The Middle)
|
|
490
|
+
const oldStart = start;
|
|
491
|
+
const oldEnd = oldLength - end;
|
|
492
|
+
const newEnd = newLength - end;
|
|
493
|
+
// A. Build Map
|
|
494
|
+
const keyMap = new Map();
|
|
495
|
+
for (let i = oldStart; i < oldEnd; i++) {
|
|
496
|
+
const item = prevItems[i];
|
|
497
|
+
keyMap.set(item, rowNodes[i]);
|
|
498
|
+
}
|
|
499
|
+
const nextRowNodes = new Array(newLength);
|
|
500
|
+
// Copy Prefix
|
|
501
|
+
for (let i = 0; i < start; i++) {
|
|
502
|
+
nextRowNodes[i] = rowNodes[i];
|
|
503
|
+
}
|
|
504
|
+
// Copy Suffix
|
|
505
|
+
for (let i = 0; i < end; i++) {
|
|
506
|
+
nextRowNodes[newLength - 1 - i] = rowNodes[oldLength - 1 - i];
|
|
507
|
+
}
|
|
508
|
+
// Determine Anchor (The first node of the suffix, or the end bookend)
|
|
509
|
+
const anchor = (end > 0) ? rowNodes[oldLength - end] : endBookend;
|
|
510
|
+
// B. Process Middle
|
|
511
|
+
for (let i = oldStart; i < newEnd; i++) {
|
|
512
|
+
const newItem = nextItems[i];
|
|
513
|
+
if (keyMap.has(newItem)) {
|
|
514
|
+
const node = keyMap.get(newItem);
|
|
515
|
+
keyMap.delete(newItem);
|
|
516
|
+
parent.insertBefore(node, anchor);
|
|
517
|
+
nextRowNodes[i] = node;
|
|
566
518
|
}
|
|
567
|
-
else
|
|
568
|
-
|
|
519
|
+
else {
|
|
520
|
+
const node = render(newItem);
|
|
521
|
+
parent.insertBefore(node, anchor);
|
|
522
|
+
nextRowNodes[i] = node;
|
|
569
523
|
}
|
|
570
|
-
start++;
|
|
571
524
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
525
|
+
// C. Cleanup Unused
|
|
526
|
+
for (const node of keyMap.values()) {
|
|
527
|
+
shallowTeardown(node);
|
|
528
|
+
node.remove();
|
|
575
529
|
}
|
|
576
|
-
|
|
530
|
+
rowNodes = nextRowNodes;
|
|
577
531
|
prevItems = nextItems;
|
|
578
|
-
nextItems = null;
|
|
579
|
-
childNodes = null;
|
|
580
532
|
});
|
|
581
533
|
return outlet;
|
|
582
534
|
}
|
package/dist/signals.js
CHANGED