@mmstack/primitives 21.0.7 → 21.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/primitives",
3
- "version": "21.0.7",
3
+ "version": "21.0.9",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -1,4 +1,4 @@
1
- import { CreateSignalOptions, DestroyRef, WritableSignal, Signal, EffectCleanupRegisterFn, CreateEffectOptions, Injector, EffectRef, ElementRef, ValueEqualityFn } from '@angular/core';
1
+ import { CreateSignalOptions, DestroyRef, WritableSignal, Signal, Injector, EffectRef, EffectCleanupRegisterFn, CreateEffectOptions, ElementRef, ValueEqualityFn } from '@angular/core';
2
2
 
3
3
  /**
4
4
  * Options for creating a debounced writable signal.
@@ -378,6 +378,108 @@ declare function toFakeSignalDerivation<T, U>(initial: WritableSignal<U>): Deriv
378
378
  */
379
379
  declare function isDerivation<T, U>(sig: WritableSignal<U>): sig is DerivedSignal<T, U>;
380
380
 
381
+ type Frame = {
382
+ injector: Injector;
383
+ parent: Frame | null;
384
+ children: Set<EffectRef>;
385
+ };
386
+
387
+ /**
388
+ * Creates an effect that can be nested, similar to SolidJS's `createEffect`.
389
+ *
390
+ * This primitive enables true hierarchical reactivity. A `nestedEffect` created
391
+ * within another `nestedEffect` is automatically destroyed and recreated when
392
+ * the parent re-runs.
393
+ *
394
+ * It automatically handles injector propagation and lifetime management, allowing
395
+ * you to create fine-grained, conditional side-effects that only track
396
+ * dependencies when they are "live".
397
+ *
398
+ * @param effectFn The side-effect function, which receives a cleanup register function.
399
+ * @param options (Optional) Angular's `CreateEffectOptions`.
400
+ * @returns An `EffectRef` for the created effect.
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * // Assume `coldGuard` changes rarely, but `hotSignal` changes often.
405
+ * const coldGuard = signal(false);
406
+ * const hotSignal = signal(0);
407
+ *
408
+ * nestedEffect(() => {
409
+ * // This outer effect only tracks `coldGuard`.
410
+ * if (coldGuard()) {
411
+ *
412
+ * // This inner effect is CREATED when coldGuard is true
413
+ * // and DESTROYED when it becomes false.
414
+ * nestedEffect(() => {
415
+ * // It only tracks `hotSignal` while it exists.
416
+ * console.log('Hot signal is:', hotSignal());
417
+ * });
418
+ * }
419
+ * // If `coldGuard` is false, this outer effect does not track `hotSignal`.
420
+ * });
421
+ * ```
422
+ * @example
423
+ * ```ts
424
+ * const users = signal([
425
+ * { id: 1, name: 'Alice' },
426
+ * { id: 2, name: 'Bob' }
427
+ * ]);
428
+ *
429
+ * // The fine-grained mapped list
430
+ * const mappedUsers = mapArray(
431
+ * users,
432
+ * (userSignal, index) => {
433
+ * // 1. Create a fine-grained SIDE EFFECT for *this item*
434
+ * // This effect's lifetime is now tied to this specific item. created once on init of this index.
435
+ * const effectRef = nestedEffect(() => {
436
+ * // This only runs if *this* userSignal changes,
437
+ * // not if the whole list changes.
438
+ * console.log(`User ${index} updated:`, userSignal().name);
439
+ * });
440
+ *
441
+ * // 2. Return the data AND the cleanup logic
442
+ * return {
443
+ * // The mapped data
444
+ * label: computed(() => `User: ${userSignal().name}`),
445
+ *
446
+ * // The cleanup function
447
+ * destroyEffect: () => effectRef.destroy()
448
+ * };
449
+ * },
450
+ * {
451
+ * // 3. Tell mapArray HOW to clean up when an item is removed, this needs to be manual as it's not a nestedEffect itself
452
+ * onDestroy: (mappedItem) => {
453
+ * mappedItem.destroyEffect();
454
+ * }
455
+ * }
456
+ * );
457
+ * ```
458
+ */
459
+ declare function nestedEffect(effectFn: (registerCleanup: EffectCleanupRegisterFn) => void, options?: CreateEffectOptions & {
460
+ bindToFrame?: (parent: Frame | null) => Frame | null;
461
+ }): {
462
+ destroy: () => void;
463
+ };
464
+
465
+ /**
466
+ * A synchronous version of Angular's effect, optimized for DOM-heavy renderers.
467
+ * Runs immediately on creation and on dependency changes, bypassing the microtask queue.
468
+ * * @example
469
+ * renderEffect((onCleanup) => {
470
+ * const el = document.createElement('div');
471
+ * el.textContent = count();
472
+ * container.appendChild(el);
473
+ * onCleanup(() => el.remove());
474
+ * });
475
+ */
476
+ declare function renderEffect(effectFn: (onCleanup: (fn: () => void) => void) => void, options?: {
477
+ injector?: Injector;
478
+ }): {
479
+ run: (isFromBridge?: boolean) => void;
480
+ destroy: () => void;
481
+ };
482
+
381
483
  /**
382
484
  * Reactively maps items from a source array to a new array, creating stable signals for each item.
383
485
  *
@@ -472,89 +574,6 @@ declare function mapObject<T extends Record<string, any>, U>(source: (() => T) |
472
574
  onDestroy?: (value: U) => void;
473
575
  }): Signal<MappedObject<T, U>>;
474
576
 
475
- type Frame = {
476
- injector: Injector;
477
- parent: Frame | null;
478
- children: Set<EffectRef>;
479
- };
480
- /**
481
- * Creates an effect that can be nested, similar to SolidJS's `createEffect`.
482
- *
483
- * This primitive enables true hierarchical reactivity. A `nestedEffect` created
484
- * within another `nestedEffect` is automatically destroyed and recreated when
485
- * the parent re-runs.
486
- *
487
- * It automatically handles injector propagation and lifetime management, allowing
488
- * you to create fine-grained, conditional side-effects that only track
489
- * dependencies when they are "live".
490
- *
491
- * @param effectFn The side-effect function, which receives a cleanup register function.
492
- * @param options (Optional) Angular's `CreateEffectOptions`.
493
- * @returns An `EffectRef` for the created effect.
494
- *
495
- * @example
496
- * ```ts
497
- * // Assume `coldGuard` changes rarely, but `hotSignal` changes often.
498
- * const coldGuard = signal(false);
499
- * const hotSignal = signal(0);
500
- *
501
- * nestedEffect(() => {
502
- * // This outer effect only tracks `coldGuard`.
503
- * if (coldGuard()) {
504
- *
505
- * // This inner effect is CREATED when coldGuard is true
506
- * // and DESTROYED when it becomes false.
507
- * nestedEffect(() => {
508
- * // It only tracks `hotSignal` while it exists.
509
- * console.log('Hot signal is:', hotSignal());
510
- * });
511
- * }
512
- * // If `coldGuard` is false, this outer effect does not track `hotSignal`.
513
- * });
514
- * ```
515
- * @example
516
- * ```ts
517
- * const users = signal([
518
- * { id: 1, name: 'Alice' },
519
- * { id: 2, name: 'Bob' }
520
- * ]);
521
- *
522
- * // The fine-grained mapped list
523
- * const mappedUsers = mapArray(
524
- * users,
525
- * (userSignal, index) => {
526
- * // 1. Create a fine-grained SIDE EFFECT for *this item*
527
- * // This effect's lifetime is now tied to this specific item. created once on init of this index.
528
- * const effectRef = nestedEffect(() => {
529
- * // This only runs if *this* userSignal changes,
530
- * // not if the whole list changes.
531
- * console.log(`User ${index} updated:`, userSignal().name);
532
- * });
533
- *
534
- * // 2. Return the data AND the cleanup logic
535
- * return {
536
- * // The mapped data
537
- * label: computed(() => `User: ${userSignal().name}`),
538
- *
539
- * // The cleanup function
540
- * destroyEffect: () => effectRef.destroy()
541
- * };
542
- * },
543
- * {
544
- * // 3. Tell mapArray HOW to clean up when an item is removed, this needs to be manual as it's not a nestedEffect itself
545
- * onDestroy: (mappedItem) => {
546
- * mappedItem.destroyEffect();
547
- * }
548
- * }
549
- * );
550
- * ```
551
- */
552
- declare function nestedEffect(effectFn: (registerCleanup: EffectCleanupRegisterFn) => void, options?: CreateEffectOptions & {
553
- bindToFrame?: (parent: Frame | null) => Frame | null;
554
- }): {
555
- destroy: () => void;
556
- };
557
-
558
577
  /**
559
578
  * A pure, synchronous transform from I -> O.
560
579
  * Prefer transforms without side effects to keep derivations predictable.
@@ -1303,10 +1322,14 @@ type AnyRecord = Record<Key, any>;
1303
1322
  type SignalStore<T> = Signal<T> & (NonNullable<T> extends BaseType ? unknown : Readonly<{
1304
1323
  [K in keyof Required<T>]: SignalStore<NonNullable<T>[K]>;
1305
1324
  }>);
1306
- type WritableSignalStore<T> = WritableSignal<T> & (NonNullable<T> extends BaseType ? unknown : Readonly<{
1325
+ type WritableSignalStore<T> = WritableSignal<T> & {
1326
+ readonly asReadonlyStore: () => SignalStore<T>;
1327
+ } & (NonNullable<T> extends BaseType ? unknown : Readonly<{
1307
1328
  [K in keyof Required<T>]: WritableSignalStore<NonNullable<T>[K]>;
1308
1329
  }>);
1309
- type MutableSignalStore<T> = MutableSignal<T> & (NonNullable<T> extends BaseType ? unknown : Readonly<{
1330
+ type MutableSignalStore<T> = MutableSignal<T> & {
1331
+ readonly asReadonlyStore: () => SignalStore<T>;
1332
+ } & (NonNullable<T> extends BaseType ? unknown : Readonly<{
1310
1333
  [K in keyof Required<T>]: MutableSignalStore<NonNullable<T>[K]>;
1311
1334
  }>);
1312
1335
  declare function toStore<T extends AnyRecord>(source: MutableSignal<T>, injector?: Injector): MutableSignalStore<T>;
@@ -1752,5 +1775,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
1752
1775
  */
1753
1776
  declare function withHistory<T>(source: WritableSignal<T>, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
1754
1777
 
1755
- 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 };
1778
+ 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, renderEffect, scrollPosition, select, sensor, sensors, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
1756
1779
  export type { CreateDebouncedOptions, CreateHistoryOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, PipeableSignal, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SignalStore, SignalWithHistory, StoredSignal, ThrottledSignal, UntilOptions, WindowSize, WindowSizeOptions, WindowSizeSignal, WritableSignalStore };