@mmstack/primitives 21.0.4 → 21.0.6
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.
|
@@ -228,7 +228,7 @@ function derived(source, optOrKey, opt) {
|
|
|
228
228
|
return baseEqual(a, b);
|
|
229
229
|
}
|
|
230
230
|
: baseEqual;
|
|
231
|
-
const sig = toWritable(computed(() => from(source()), { ...rest, equal }), (newVal) => onChange(newVal));
|
|
231
|
+
const sig = toWritable(computed(() => from(source()), { ...rest, equal }), (newVal) => onChange(newVal), undefined, { pure: false });
|
|
232
232
|
sig.from = from;
|
|
233
233
|
if (isMutable(source)) {
|
|
234
234
|
sig.mutate = (updater) => {
|
|
@@ -292,7 +292,6 @@ function isDerivation(sig) {
|
|
|
292
292
|
function isWritableSignal(value) {
|
|
293
293
|
return isWritableSignal$1(value);
|
|
294
294
|
}
|
|
295
|
-
|
|
296
295
|
/**
|
|
297
296
|
* @internal
|
|
298
297
|
* Creates a setter function for a source signal of type `Signal<T[]>` or a function returning `T[]`.
|
|
@@ -353,8 +352,11 @@ function indexArray(source, map, opt = {}) {
|
|
|
353
352
|
if (len === prev.value.length)
|
|
354
353
|
return prev.value;
|
|
355
354
|
if (len < prev.value.length) {
|
|
356
|
-
if (opt.onDestroy)
|
|
357
|
-
prev.value.
|
|
355
|
+
if (opt.onDestroy) {
|
|
356
|
+
for (let i = len; i < prev.value.length; i++) {
|
|
357
|
+
opt.onDestroy(prev.value[i]);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
358
360
|
return prev.value.slice(0, len);
|
|
359
361
|
}
|
|
360
362
|
const next = prev.value.slice();
|
|
@@ -370,75 +372,188 @@ function indexArray(source, map, opt = {}) {
|
|
|
370
372
|
*/
|
|
371
373
|
const mapArray = indexArray;
|
|
372
374
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
let
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
375
|
+
/**
|
|
376
|
+
* Reactively maps items from a source array to a new array by value (identity).
|
|
377
|
+
*
|
|
378
|
+
* similar to `Array.prototype.map`, but:
|
|
379
|
+
* 1. The `mapFn` receives the `index` as a Signal.
|
|
380
|
+
* 2. If an item in the `source` array moves to a new position, the *result* of the map function is reused and moved.
|
|
381
|
+
* The `index` signal is updated to the new index.
|
|
382
|
+
* 3. The `mapFn` is only run for *new* items.
|
|
383
|
+
*
|
|
384
|
+
* This is useful for building efficient lists where DOM nodes or heavy instances should be reused
|
|
385
|
+
* when the list is reordered.
|
|
386
|
+
*
|
|
387
|
+
* @param source A `Signal<T[]>` or a function returning `T[]`.
|
|
388
|
+
* @param mapFn The mapping function. Receives the item and its index as a Signal.
|
|
389
|
+
* @param options Optional configuration:
|
|
390
|
+
* - `onDestroy`: A callback invoked when a mapped item is removed from the array.
|
|
391
|
+
* @returns A `Signal<U[]>` containing the mapped array.
|
|
392
|
+
*/
|
|
393
|
+
function keyArray(source, mapFn, options = {}) {
|
|
394
|
+
const sourceSignal = isSignal(source) ? source : computed(source);
|
|
395
|
+
const items = [];
|
|
396
|
+
let mapped = [];
|
|
397
|
+
const indexes = [];
|
|
398
|
+
const getKey = options.key || ((v) => v);
|
|
399
|
+
const newIndices = new Map();
|
|
400
|
+
const temp = [];
|
|
401
|
+
const tempIndexes = [];
|
|
402
|
+
const newIndicesNext = [];
|
|
403
|
+
const newIndexesCache = new Array();
|
|
404
|
+
return computed(() => {
|
|
405
|
+
const newItems = sourceSignal() || [];
|
|
406
|
+
return untracked(() => {
|
|
407
|
+
let i;
|
|
408
|
+
let j;
|
|
409
|
+
const newLen = newItems.length;
|
|
410
|
+
let len = items.length;
|
|
411
|
+
const newMapped = new Array(newLen);
|
|
412
|
+
const newIndexes = newIndexesCache;
|
|
413
|
+
newIndexes.length = 0;
|
|
414
|
+
newIndexes.length = newLen;
|
|
415
|
+
let start;
|
|
416
|
+
let end;
|
|
417
|
+
let newEnd;
|
|
418
|
+
let item;
|
|
419
|
+
let key;
|
|
420
|
+
if (newLen === 0) {
|
|
421
|
+
if (len !== 0) {
|
|
422
|
+
if (options.onDestroy) {
|
|
423
|
+
for (let k = 0; k < len; k++)
|
|
424
|
+
options.onDestroy(mapped[k]);
|
|
425
|
+
}
|
|
426
|
+
items.length = 0;
|
|
427
|
+
mapped = [];
|
|
428
|
+
indexes.length = 0;
|
|
415
429
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
430
|
+
return mapped;
|
|
431
|
+
}
|
|
432
|
+
// Fast path for new create (init)
|
|
433
|
+
if (len === 0) {
|
|
434
|
+
for (j = 0; j < newLen; j++) {
|
|
435
|
+
item = newItems[j];
|
|
436
|
+
items[j] = item;
|
|
437
|
+
const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : []));
|
|
438
|
+
newIndexes[j] = indexSignal;
|
|
439
|
+
newMapped[j] = mapFn(item, indexSignal);
|
|
422
440
|
}
|
|
423
441
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
442
|
+
else {
|
|
443
|
+
newIndices.clear();
|
|
444
|
+
temp.length = 0;
|
|
445
|
+
tempIndexes.length = 0;
|
|
446
|
+
newIndicesNext.length = 0;
|
|
447
|
+
// Skip common prefix
|
|
448
|
+
for (start = 0, end = Math.min(len, newLen); start < end && getKey(items[start]) === getKey(newItems[start]); start++) {
|
|
449
|
+
newMapped[start] = mapped[start];
|
|
450
|
+
newIndexes[start] = indexes[start];
|
|
451
|
+
}
|
|
452
|
+
// Common suffix
|
|
453
|
+
for (end = len - 1, newEnd = newLen - 1; end >= start &&
|
|
454
|
+
newEnd >= start &&
|
|
455
|
+
getKey(items[end]) === getKey(newItems[newEnd]); end--, newEnd--) {
|
|
456
|
+
temp[newEnd] = mapped[end];
|
|
457
|
+
tempIndexes[newEnd] = indexes[end];
|
|
458
|
+
}
|
|
459
|
+
// 0) Prepare a map of all indices in newItems, scanning backwards
|
|
460
|
+
for (j = newEnd; j >= start; j--) {
|
|
461
|
+
item = newItems[j];
|
|
462
|
+
key = getKey(item);
|
|
463
|
+
i = newIndices.get(key);
|
|
464
|
+
newIndicesNext[j] = i === undefined ? -1 : i;
|
|
465
|
+
newIndices.set(key, j);
|
|
466
|
+
}
|
|
467
|
+
// 1) Step through old items: check if they are in new set
|
|
468
|
+
for (i = start; i <= end; i++) {
|
|
469
|
+
item = items[i];
|
|
470
|
+
key = getKey(item);
|
|
471
|
+
j = newIndices.get(key);
|
|
472
|
+
if (j !== undefined && j !== -1) {
|
|
473
|
+
temp[j] = mapped[i];
|
|
474
|
+
tempIndexes[j] = indexes[i];
|
|
475
|
+
j = newIndicesNext[j];
|
|
476
|
+
newIndices.set(key, j);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
if (options.onDestroy)
|
|
480
|
+
options.onDestroy(mapped[i]);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// 2) Set all new values
|
|
484
|
+
for (j = start; j < newLen; j++) {
|
|
485
|
+
if (j in temp) {
|
|
486
|
+
newMapped[j] = temp[j];
|
|
487
|
+
newIndexes[j] = tempIndexes[j];
|
|
488
|
+
untracked(() => newIndexes[j].set(j)); // Update index signal
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : []));
|
|
492
|
+
newIndexes[j] = indexSignal;
|
|
493
|
+
newMapped[j] = mapFn(newItems[j], indexSignal);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// 4) Save items for next update
|
|
497
|
+
items.length = newLen;
|
|
498
|
+
for (let k = 0; k < newLen; k++)
|
|
499
|
+
items[k] = newItems[k];
|
|
500
|
+
}
|
|
501
|
+
mapped = newMapped;
|
|
502
|
+
indexes.length = newLen;
|
|
503
|
+
for (let k = 0; k < newLen; k++)
|
|
504
|
+
indexes[k] = newIndexes[k];
|
|
505
|
+
return mapped;
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function pooledKeys(src) {
|
|
511
|
+
const aBuf = new Set();
|
|
512
|
+
const bBuf = new Set();
|
|
513
|
+
let active = aBuf;
|
|
514
|
+
let spare = bBuf;
|
|
515
|
+
return computed(() => {
|
|
516
|
+
const val = src();
|
|
517
|
+
spare.clear();
|
|
518
|
+
for (const k in val)
|
|
519
|
+
if (Object.prototype.hasOwnProperty.call(val, k))
|
|
520
|
+
spare.add(k);
|
|
521
|
+
if (active.size === spare.size && active.isSubsetOf(spare))
|
|
522
|
+
return active;
|
|
523
|
+
const temp = active;
|
|
524
|
+
active = spare;
|
|
525
|
+
spare = temp;
|
|
526
|
+
return active;
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function mapObject(source, mapFn, options = {}) {
|
|
530
|
+
const src = isSignal(source) ? source : computed(source);
|
|
531
|
+
const writable = (isWritableSignal(src)
|
|
532
|
+
? src
|
|
533
|
+
: toWritable(src, () => {
|
|
534
|
+
// noop
|
|
535
|
+
})); // maximal overload internally
|
|
536
|
+
return linkedSignal({
|
|
537
|
+
source: pooledKeys(src),
|
|
538
|
+
computation: (next, prev) => {
|
|
539
|
+
const nextObj = {};
|
|
540
|
+
for (const k of next)
|
|
541
|
+
nextObj[k] =
|
|
542
|
+
prev && prev.source.has(k)
|
|
543
|
+
? prev.value[k]
|
|
544
|
+
: mapFn(k, derived(writable, k));
|
|
545
|
+
if (options.onDestroy && prev && prev.source.size)
|
|
546
|
+
for (const k of prev.source)
|
|
547
|
+
if (!next.has(k))
|
|
548
|
+
options.onDestroy(prev.value[k]);
|
|
549
|
+
return nextObj;
|
|
550
|
+
},
|
|
551
|
+
}).asReadonly();
|
|
437
552
|
}
|
|
438
553
|
|
|
439
554
|
const frameStack = [];
|
|
440
|
-
function current(
|
|
441
|
-
return frameStack.at(
|
|
555
|
+
function current() {
|
|
556
|
+
return frameStack.at(-1) ?? null;
|
|
442
557
|
}
|
|
443
558
|
function clearFrame(frame, userCleanups) {
|
|
444
559
|
frame.parent = null;
|
|
@@ -501,42 +616,43 @@ function clearFrame(frame, userCleanups) {
|
|
|
501
616
|
* @example
|
|
502
617
|
* ```ts
|
|
503
618
|
* const users = signal([
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
]);
|
|
507
|
-
|
|
508
|
-
// The fine-grained mapped list
|
|
509
|
-
const mappedUsers = mapArray(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
);
|
|
619
|
+
* { id: 1, name: 'Alice' },
|
|
620
|
+
* { id: 2, name: 'Bob' }
|
|
621
|
+
* ]);
|
|
622
|
+
*
|
|
623
|
+
* // The fine-grained mapped list
|
|
624
|
+
* const mappedUsers = mapArray(
|
|
625
|
+
* users,
|
|
626
|
+
* (userSignal, index) => {
|
|
627
|
+
* // 1. Create a fine-grained SIDE EFFECT for *this item*
|
|
628
|
+
* // This effect's lifetime is now tied to this specific item. created once on init of this index.
|
|
629
|
+
* const effectRef = nestedEffect(() => {
|
|
630
|
+
* // This only runs if *this* userSignal changes,
|
|
631
|
+
* // not if the whole list changes.
|
|
632
|
+
* console.log(`User ${index} updated:`, userSignal().name);
|
|
633
|
+
* });
|
|
634
|
+
*
|
|
635
|
+
* // 2. Return the data AND the cleanup logic
|
|
636
|
+
* return {
|
|
637
|
+
* // The mapped data
|
|
638
|
+
* label: computed(() => `User: ${userSignal().name}`),
|
|
639
|
+
*
|
|
640
|
+
* // The cleanup function
|
|
641
|
+
* destroyEffect: () => effectRef.destroy()
|
|
642
|
+
* };
|
|
643
|
+
* },
|
|
644
|
+
* {
|
|
645
|
+
* // 3. Tell mapArray HOW to clean up when an item is removed, this needs to be manual as it's not a nestedEffect itself
|
|
646
|
+
* onDestroy: (mappedItem) => {
|
|
647
|
+
* mappedItem.destroyEffect();
|
|
648
|
+
* }
|
|
649
|
+
* }
|
|
650
|
+
* );
|
|
536
651
|
* ```
|
|
537
652
|
*/
|
|
538
653
|
function nestedEffect(effectFn, options) {
|
|
539
|
-
const
|
|
654
|
+
const bindToFrame = options?.bindToFrame ?? ((parent) => parent);
|
|
655
|
+
const parent = bindToFrame(current());
|
|
540
656
|
const injector = options?.injector ?? parent?.injector ?? inject(Injector);
|
|
541
657
|
const srcRef = untracked(() => {
|
|
542
658
|
return effect((cleanup) => {
|
|
@@ -1440,8 +1556,6 @@ function sensors(track, opt) {
|
|
|
1440
1556
|
}, {});
|
|
1441
1557
|
}
|
|
1442
1558
|
|
|
1443
|
-
// Credit to NGRX signal store, adaptation for purposes of supporting Writable/Mutable signals
|
|
1444
|
-
// Link to source: https://github.com/ngrx/platform/blob/main/modules/signals/src/deep-signal.ts
|
|
1445
1559
|
const TREAT_AS_VALUE = new Set([
|
|
1446
1560
|
Date,
|
|
1447
1561
|
Error,
|
|
@@ -1453,21 +1567,19 @@ const TREAT_AS_VALUE = new Set([
|
|
|
1453
1567
|
WeakMap,
|
|
1454
1568
|
WeakRef,
|
|
1455
1569
|
Promise,
|
|
1570
|
+
Array,
|
|
1456
1571
|
typeof Iterator !== 'undefined' ? Iterator : class {
|
|
1457
1572
|
},
|
|
1458
|
-
Array,
|
|
1459
1573
|
]);
|
|
1460
|
-
function isIterable(value) {
|
|
1461
|
-
return typeof value?.[Symbol.iterator] === 'function';
|
|
1462
|
-
}
|
|
1463
1574
|
function isRecord(value) {
|
|
1464
|
-
if (value === null || typeof value !== 'object'
|
|
1575
|
+
if (value === null || typeof value !== 'object')
|
|
1576
|
+
return false;
|
|
1577
|
+
if (Array.isArray(value) ||
|
|
1578
|
+
typeof value[Symbol.iterator] === 'function')
|
|
1465
1579
|
return false;
|
|
1466
|
-
}
|
|
1467
1580
|
let proto = Object.getPrototypeOf(value);
|
|
1468
|
-
if (proto === Object.prototype)
|
|
1581
|
+
if (proto === Object.prototype)
|
|
1469
1582
|
return true;
|
|
1470
|
-
}
|
|
1471
1583
|
while (proto && proto !== Object.prototype) {
|
|
1472
1584
|
if (TREAT_AS_VALUE.has(proto.constructor))
|
|
1473
1585
|
return false;
|
|
@@ -1475,41 +1587,45 @@ function isRecord(value) {
|
|
|
1475
1587
|
}
|
|
1476
1588
|
return proto === Object.prototype;
|
|
1477
1589
|
}
|
|
1478
|
-
const
|
|
1479
|
-
const STORE_PROXY = Symbol(isDevMode() ? 'SIGNAL_STORE_PROXY' : '');
|
|
1590
|
+
const PROXY_CACHE = new WeakMap();
|
|
1480
1591
|
function toStore(source, injector = inject(Injector)) {
|
|
1481
1592
|
return new Proxy(source, {
|
|
1482
1593
|
has(_, prop) {
|
|
1483
1594
|
return Reflect.has(untracked(source), prop);
|
|
1484
1595
|
},
|
|
1485
1596
|
get(target, prop) {
|
|
1486
|
-
|
|
1487
|
-
if (!
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
}
|
|
1491
|
-
return target[prop];
|
|
1597
|
+
let storeCache = PROXY_CACHE.get(target);
|
|
1598
|
+
if (!storeCache) {
|
|
1599
|
+
storeCache = new Map();
|
|
1600
|
+
PROXY_CACHE.set(target, storeCache);
|
|
1492
1601
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1602
|
+
const cachedRef = storeCache.get(prop);
|
|
1603
|
+
if (cachedRef) {
|
|
1604
|
+
const cached = cachedRef.deref();
|
|
1605
|
+
if (cached)
|
|
1606
|
+
return cached;
|
|
1607
|
+
storeCache.delete(prop); // Cleanup dead ref
|
|
1495
1608
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1609
|
+
if (prop === 'set' ||
|
|
1610
|
+
prop === 'update' ||
|
|
1611
|
+
prop === 'asReadonly' ||
|
|
1612
|
+
typeof prop === 'symbol') {
|
|
1613
|
+
return target[prop];
|
|
1614
|
+
}
|
|
1615
|
+
const value = untracked(target);
|
|
1616
|
+
if (!isRecord(value))
|
|
1617
|
+
return target[prop];
|
|
1618
|
+
let computation = target[prop];
|
|
1619
|
+
if (!isSignal(computation)) {
|
|
1498
1620
|
computation = derived(target, prop);
|
|
1499
1621
|
Object.defineProperty(target, prop, {
|
|
1500
1622
|
value: computation,
|
|
1501
1623
|
configurable: true,
|
|
1624
|
+
writable: true,
|
|
1502
1625
|
});
|
|
1503
|
-
target[prop][STORE] = true;
|
|
1504
1626
|
}
|
|
1505
|
-
const proxy = toStore(
|
|
1506
|
-
|
|
1507
|
-
if (Reflect.has(source(), prop))
|
|
1508
|
-
return;
|
|
1509
|
-
delete target[prop];
|
|
1510
|
-
cleanupRef.destroy();
|
|
1511
|
-
}, { injector });
|
|
1512
|
-
target[prop][STORE_PROXY] = proxy;
|
|
1627
|
+
const proxy = toStore(computation, injector);
|
|
1628
|
+
storeCache.set(prop, new WeakRef(proxy));
|
|
1513
1629
|
return proxy;
|
|
1514
1630
|
},
|
|
1515
1631
|
});
|
|
@@ -1995,5 +2111,5 @@ function withHistory(source, opt) {
|
|
|
1995
2111
|
* Generated bundle index. Do not edit.
|
|
1996
2112
|
*/
|
|
1997
2113
|
|
|
1998
|
-
export { combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, indexArray, isDerivation, isMutable, keyArray, map, mapArray, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, pageVisibility, pipeable, piped, prefersDarkMode, prefersReducedMotion, scrollPosition, select, sensor, sensors, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
2114
|
+
export { combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, indexArray, isDerivation, isMutable, keyArray, map, mapArray, mapObject, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, pageVisibility, pipeable, piped, prefersDarkMode, prefersReducedMotion, scrollPosition, select, sensor, sensors, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
1999
2115
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|