dalila 1.8.0 → 1.8.1
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/runtime/bind.js +179 -33
- package/package.json +1 -1
package/dist/runtime/bind.js
CHANGED
|
@@ -278,40 +278,184 @@ function bindEach(root, ctx, cleanups) {
|
|
|
278
278
|
const comment = document.createComment('d-each');
|
|
279
279
|
el.parentNode?.replaceChild(comment, el);
|
|
280
280
|
el.removeAttribute('d-each');
|
|
281
|
+
const keyBinding = normalizeBinding(el.getAttribute('d-key'));
|
|
282
|
+
el.removeAttribute('d-key');
|
|
281
283
|
const template = el;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
+
const clonesByKey = new Map();
|
|
285
|
+
const disposesByKey = new Map();
|
|
286
|
+
const metadataByKey = new Map();
|
|
287
|
+
const itemsByKey = new Map();
|
|
288
|
+
const objectKeyIds = new WeakMap();
|
|
289
|
+
const symbolKeyIds = new Map();
|
|
290
|
+
let nextObjectKeyId = 0;
|
|
291
|
+
let nextSymbolKeyId = 0;
|
|
292
|
+
const missingKeyWarned = new Set();
|
|
293
|
+
const getObjectKeyId = (value) => {
|
|
294
|
+
const existing = objectKeyIds.get(value);
|
|
295
|
+
if (existing !== undefined)
|
|
296
|
+
return existing;
|
|
297
|
+
const next = ++nextObjectKeyId;
|
|
298
|
+
objectKeyIds.set(value, next);
|
|
299
|
+
return next;
|
|
300
|
+
};
|
|
301
|
+
const keyValueToString = (value, index) => {
|
|
302
|
+
if (value === null || value === undefined)
|
|
303
|
+
return `idx:${index}`;
|
|
304
|
+
const type = typeof value;
|
|
305
|
+
if (type === 'string' || type === 'number' || type === 'boolean' || type === 'bigint') {
|
|
306
|
+
return `${type}:${String(value)}`;
|
|
307
|
+
}
|
|
308
|
+
if (type === 'symbol') {
|
|
309
|
+
const sym = value;
|
|
310
|
+
let id = symbolKeyIds.get(sym);
|
|
311
|
+
if (id === undefined) {
|
|
312
|
+
id = ++nextSymbolKeyId;
|
|
313
|
+
symbolKeyIds.set(sym, id);
|
|
314
|
+
}
|
|
315
|
+
return `sym:${id}`;
|
|
316
|
+
}
|
|
317
|
+
if (type === 'object' || type === 'function') {
|
|
318
|
+
return `obj:${getObjectKeyId(value)}`;
|
|
319
|
+
}
|
|
320
|
+
return `idx:${index}`;
|
|
321
|
+
};
|
|
322
|
+
const readKeyValue = (item, index) => {
|
|
323
|
+
if (keyBinding) {
|
|
324
|
+
if (keyBinding === '$index')
|
|
325
|
+
return index;
|
|
326
|
+
if (keyBinding === 'item')
|
|
327
|
+
return item;
|
|
328
|
+
if (typeof item === 'object' && item !== null && keyBinding in item) {
|
|
329
|
+
return item[keyBinding];
|
|
330
|
+
}
|
|
331
|
+
const warnId = `${keyBinding}:${index}`;
|
|
332
|
+
if (!missingKeyWarned.has(warnId)) {
|
|
333
|
+
warn(`d-each: key "${keyBinding}" not found on item at index ${index}. Falling back to index key.`);
|
|
334
|
+
missingKeyWarned.add(warnId);
|
|
335
|
+
}
|
|
336
|
+
return index;
|
|
337
|
+
}
|
|
338
|
+
if (typeof item === 'object' && item !== null) {
|
|
339
|
+
const obj = item;
|
|
340
|
+
if ('id' in obj)
|
|
341
|
+
return obj.id;
|
|
342
|
+
if ('key' in obj)
|
|
343
|
+
return obj.key;
|
|
344
|
+
}
|
|
345
|
+
return index;
|
|
346
|
+
};
|
|
347
|
+
function createClone(key, item, index, count) {
|
|
348
|
+
const clone = template.cloneNode(true);
|
|
349
|
+
// Inherit parent ctx via prototype so values and handlers defined
|
|
350
|
+
// outside the loop remain accessible inside each iteration.
|
|
351
|
+
const itemCtx = Object.create(ctx);
|
|
352
|
+
if (typeof item === 'object' && item !== null) {
|
|
353
|
+
Object.assign(itemCtx, item);
|
|
354
|
+
}
|
|
355
|
+
const metadata = {
|
|
356
|
+
$index: signal(index),
|
|
357
|
+
$count: signal(count),
|
|
358
|
+
$first: signal(index === 0),
|
|
359
|
+
$last: signal(index === count - 1),
|
|
360
|
+
$odd: signal(index % 2 !== 0),
|
|
361
|
+
$even: signal(index % 2 === 0),
|
|
362
|
+
};
|
|
363
|
+
metadataByKey.set(key, metadata);
|
|
364
|
+
itemsByKey.set(key, item);
|
|
365
|
+
// Expose item + positional / collection helpers.
|
|
366
|
+
itemCtx.item = item;
|
|
367
|
+
itemCtx.key = key;
|
|
368
|
+
itemCtx.$index = metadata.$index;
|
|
369
|
+
itemCtx.$count = metadata.$count;
|
|
370
|
+
itemCtx.$first = metadata.$first;
|
|
371
|
+
itemCtx.$last = metadata.$last;
|
|
372
|
+
itemCtx.$odd = metadata.$odd;
|
|
373
|
+
itemCtx.$even = metadata.$even;
|
|
374
|
+
// Mark BEFORE bind() so the parent's subsequent global passes
|
|
375
|
+
// (text, attrs, events …) skip this subtree entirely.
|
|
376
|
+
clone.setAttribute('data-dalila-internal-bound', '');
|
|
377
|
+
const dispose = bind(clone, itemCtx, { _skipLifecycle: true });
|
|
378
|
+
disposesByKey.set(key, dispose);
|
|
379
|
+
clonesByKey.set(key, clone);
|
|
380
|
+
return clone;
|
|
381
|
+
}
|
|
382
|
+
function updateCloneMetadata(key, index, count) {
|
|
383
|
+
const metadata = metadataByKey.get(key);
|
|
384
|
+
if (metadata) {
|
|
385
|
+
metadata.$index.set(index);
|
|
386
|
+
metadata.$count.set(count);
|
|
387
|
+
metadata.$first.set(index === 0);
|
|
388
|
+
metadata.$last.set(index === count - 1);
|
|
389
|
+
metadata.$odd.set(index % 2 !== 0);
|
|
390
|
+
metadata.$even.set(index % 2 === 0);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
284
393
|
function renderList(items) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
currentClones = [];
|
|
290
|
-
currentDisposes = [];
|
|
394
|
+
const orderedClones = [];
|
|
395
|
+
const orderedKeys = [];
|
|
396
|
+
const nextKeys = new Set();
|
|
397
|
+
const changedKeys = new Set();
|
|
291
398
|
for (let i = 0; i < items.length; i++) {
|
|
292
399
|
const item = items[i];
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
400
|
+
let key = keyValueToString(readKeyValue(item, i), i);
|
|
401
|
+
if (nextKeys.has(key)) {
|
|
402
|
+
warn(`d-each: duplicate key "${key}" at index ${i}. Falling back to per-index key.`);
|
|
403
|
+
key = `${key}:dup:${i}`;
|
|
404
|
+
}
|
|
405
|
+
nextKeys.add(key);
|
|
406
|
+
let clone = clonesByKey.get(key);
|
|
407
|
+
if (clone) {
|
|
408
|
+
updateCloneMetadata(key, i, items.length);
|
|
409
|
+
if (itemsByKey.get(key) !== item) {
|
|
410
|
+
changedKeys.add(key);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
clone = createClone(key, item, i, items.length);
|
|
415
|
+
}
|
|
416
|
+
orderedClones.push(clone);
|
|
417
|
+
orderedKeys.push(key);
|
|
418
|
+
}
|
|
419
|
+
for (let i = 0; i < orderedClones.length; i++) {
|
|
420
|
+
const clone = orderedClones[i];
|
|
421
|
+
const item = items[i];
|
|
422
|
+
const key = orderedKeys[i];
|
|
423
|
+
if (!changedKeys.has(key))
|
|
424
|
+
continue;
|
|
425
|
+
clone.remove();
|
|
426
|
+
const dispose = disposesByKey.get(key);
|
|
427
|
+
if (dispose) {
|
|
428
|
+
dispose();
|
|
429
|
+
disposesByKey.delete(key);
|
|
299
430
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
431
|
+
clonesByKey.delete(key);
|
|
432
|
+
metadataByKey.delete(key);
|
|
433
|
+
itemsByKey.delete(key);
|
|
434
|
+
orderedClones[i] = createClone(key, item, i, items.length);
|
|
435
|
+
}
|
|
436
|
+
for (const [key, clone] of clonesByKey) {
|
|
437
|
+
if (nextKeys.has(key))
|
|
438
|
+
continue;
|
|
439
|
+
clone.remove();
|
|
440
|
+
clonesByKey.delete(key);
|
|
441
|
+
metadataByKey.delete(key);
|
|
442
|
+
itemsByKey.delete(key);
|
|
443
|
+
const dispose = disposesByKey.get(key);
|
|
444
|
+
if (dispose) {
|
|
445
|
+
dispose();
|
|
446
|
+
disposesByKey.delete(key);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const parent = comment.parentNode;
|
|
450
|
+
if (!parent)
|
|
451
|
+
return;
|
|
452
|
+
let referenceNode = comment;
|
|
453
|
+
for (let i = orderedClones.length - 1; i >= 0; i--) {
|
|
454
|
+
const clone = orderedClones[i];
|
|
455
|
+
if (clone.nextSibling !== referenceNode) {
|
|
456
|
+
parent.insertBefore(clone, referenceNode);
|
|
457
|
+
}
|
|
458
|
+
referenceNode = clone;
|
|
315
459
|
}
|
|
316
460
|
}
|
|
317
461
|
if (isSignal(binding)) {
|
|
@@ -328,12 +472,14 @@ function bindEach(root, ctx, cleanups) {
|
|
|
328
472
|
warn(`d-each: "${bindingName}" is not an array or signal`);
|
|
329
473
|
}
|
|
330
474
|
cleanups.push(() => {
|
|
331
|
-
for (const clone of
|
|
475
|
+
for (const clone of clonesByKey.values())
|
|
332
476
|
clone.remove();
|
|
333
|
-
for (const dispose of
|
|
477
|
+
for (const dispose of disposesByKey.values())
|
|
334
478
|
dispose();
|
|
335
|
-
|
|
336
|
-
|
|
479
|
+
clonesByKey.clear();
|
|
480
|
+
disposesByKey.clear();
|
|
481
|
+
metadataByKey.clear();
|
|
482
|
+
itemsByKey.clear();
|
|
337
483
|
});
|
|
338
484
|
}
|
|
339
485
|
}
|