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