@mmstack/primitives 22.1.2 → 22.2.0

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/README.md CHANGED
@@ -418,6 +418,8 @@ export class UserProfile {
418
418
 
419
419
  This is also the pattern for coordinating resources registered _above_ a boundary (e.g. an app-builder page whose connectors register at a higher injector): the outer `provideTransitionScope()` is the shared scope, and any number of `<mm-unscoped-suspense>` boundaries observe it.
420
420
 
421
+ **Forwarding scope (advanced).** `provideForwardingTransitionScope()` provides a scope that can be **re-pointed at a different target at runtime** via `setTarget(scope | null)` — reads follow the current target, while `add`/`remove` pin to the target a resource was registered under (so re-pointing never strands a registration). It's the building block for a coordinator that hosts several independent sub-scopes and switches which one it observes — e.g. a router outlet that, per navigation, points at the incoming route's own scope (read it from any injector with `getTransitionScope(injector)`). Most apps reach for `provideTransitionScope()`; this is for that one extra level of control.
422
+
421
423
  ### `injectStartTransition`
422
424
 
423
425
  The analog of React's `useTransition`. `startTransition(fn)` runs your state mutations (which commit immediately); any resource that reloads as a result **holds its value and reveals together once everything settles** — so a multi-resource update lands as one consistent frame instead of a torn mix of new and stale. The returned handle gives you a unified `pending` signal and a `done` promise for imperative coordination (disable a button, await completion).
@@ -80,7 +80,7 @@ function popFrame() {
80
80
  * ]);
81
81
  *
82
82
  * // The fine-grained mapped list
83
- * const mappedUsers = mapArray(
83
+ * const mappedUsers = indexArray(
84
84
  * users,
85
85
  * (userSignal, index) => {
86
86
  * // 1. Create a fine-grained SIDE EFFECT for *this item*
@@ -101,7 +101,7 @@ function popFrame() {
101
101
  * };
102
102
  * },
103
103
  * {
104
- * // 3. Tell mapArray HOW to clean up when an item is removed, this needs to be manual as it's not a nestedEffect itself
104
+ * // 3. Tell indexArray HOW to clean up when an item is removed, this needs to be manual as it's not a nestedEffect itself
105
105
  * onDestroy: (mappedItem) => {
106
106
  * mappedItem.destroyEffect();
107
107
  * }
@@ -280,10 +280,10 @@ class MmActivity {
280
280
  else
281
281
  this.view.detach();
282
282
  }
283
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: MmActivity, deps: [], target: i0.ɵɵFactoryTarget.Directive });
284
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.0", type: MmActivity, isStandalone: true, selector: "[mmActivity]", inputs: { visible: { classPropertyName: "visible", publicName: "mmActivity", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
283
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MmActivity, deps: [], target: i0.ɵɵFactoryTarget.Directive });
284
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: MmActivity, isStandalone: true, selector: "[mmActivity]", inputs: { visible: { classPropertyName: "visible", publicName: "mmActivity", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
285
285
  }
286
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: MmActivity, decorators: [{
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MmActivity, decorators: [{
287
287
  type: Directive,
288
288
  args: [{
289
289
  selector: '[mmActivity]',
@@ -565,6 +565,48 @@ function injectTransitionScope() {
565
565
  }
566
566
  return scope;
567
567
  }
568
+ function createForwardingScope() {
569
+ const own = createTransitionScope();
570
+ const target = signal(null, /* @ts-ignore */
571
+ ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
572
+ const eff = () => target() ?? own;
573
+ const owners = new Map();
574
+ return {
575
+ setTarget: (t) => target.set(t),
576
+ resources: computed(() => eff().resources()),
577
+ pending: computed(() => eff().pending()),
578
+ suspended: (type) => eff().suspended(type),
579
+ add: (ref, opt) => {
580
+ const t = untracked(target) ?? own;
581
+ owners.set(ref, t);
582
+ t.add(ref, opt);
583
+ },
584
+ remove: (ref) => {
585
+ const t = owners.get(ref) ?? untracked(target) ?? own;
586
+ t.remove(ref);
587
+ owners.delete(ref);
588
+ },
589
+ commit: (value) => linkedSignal({
590
+ source: () => ({ v: value(), settled: !eff().pending() }),
591
+ computation: (curr, prev) => curr.settled || prev === undefined ? curr.v : prev.value,
592
+ }),
593
+ holding: computed(() => eff().holding()),
594
+ beginHold: () => (untracked(target) ?? own).beginHold(),
595
+ endHold: () => (untracked(target) ?? own).endHold(),
596
+ hold: (value) => linkedSignal({
597
+ source: () => ({ v: value(), held: eff().holding() }),
598
+ computation: (curr, prev) => prev !== undefined && curr.held ? prev.value : curr.v,
599
+ }),
600
+ };
601
+ }
602
+ /** Provide a forwarding transition scope at a boundary (used by the transition outlet). */
603
+ function provideForwardingTransitionScope() {
604
+ return { provide: TRANSITION_SCOPE, useFactory: createForwardingScope };
605
+ }
606
+ /** Read the transition scope reachable from `injector`, or null if none is provided there. */
607
+ function getTransitionScope(injector) {
608
+ return injector.get(TRANSITION_SCOPE, null);
609
+ }
568
610
  /**
569
611
  * Returns a register function bound to the nearest transition scope: it adds a resource
570
612
  * to the scope and removes it when the caller's injection context is destroyed. Pass any
@@ -652,10 +694,10 @@ class SuspenseBoundaryBase {
652
694
  pending = this.scope.pending;
653
695
  suspended = computed(() => this.scope.suspended(this.type()), /* @ts-ignore */
654
696
  ...(ngDevMode ? [{ debugName: "suspended" }] : /* istanbul ignore next */ []));
655
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SuspenseBoundaryBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
656
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.0", type: SuspenseBoundaryBase, isStandalone: true, inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
697
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SuspenseBoundaryBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
698
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: SuspenseBoundaryBase, isStandalone: true, inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
657
699
  }
658
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SuspenseBoundaryBase, decorators: [{
700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SuspenseBoundaryBase, decorators: [{
659
701
  type: Directive
660
702
  }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }] } });
661
703
  const SUSPENSE_TEMPLATE = `
@@ -683,10 +725,10 @@ const SUSPENSE_HOST = {
683
725
  * `provideTransitionScope()`. The common case.
684
726
  */
685
727
  class SuspenseBoundary extends SuspenseBoundaryBase {
686
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SuspenseBoundary, deps: null, target: i0.ɵɵFactoryTarget.Component });
687
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SuspenseBoundary, isStandalone: true, selector: "mm-suspense", host: { properties: { "attr.aria-busy": "pending() ? true : null" } }, providers: [provideTransitionScope()], usesInheritance: true, ngImport: i0, template: "\n @if (suspended()) {\n <ng-content select=\"[placeholder]\"><span>Loading\u2026</span></ng-content>\n } @else {\n @if (pending()) {\n <ng-content select=\"[busy]\" />\n }\n <ng-content />\n }\n", isInline: true, styles: [":host{display:contents}\n"] });
728
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SuspenseBoundary, deps: null, target: i0.ɵɵFactoryTarget.Component });
729
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SuspenseBoundary, isStandalone: true, selector: "mm-suspense", host: { properties: { "attr.aria-busy": "pending() ? true : null" } }, providers: [provideTransitionScope()], usesInheritance: true, ngImport: i0, template: "\n @if (suspended()) {\n <ng-content select=\"[placeholder]\"><span>Loading\u2026</span></ng-content>\n } @else {\n @if (pending()) {\n <ng-content select=\"[busy]\" />\n }\n <ng-content />\n }\n", isInline: true, styles: [":host{display:contents}\n"] });
688
730
  }
689
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SuspenseBoundary, decorators: [{
731
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SuspenseBoundary, decorators: [{
690
732
  type: Component,
691
733
  args: [{ selector: 'mm-suspense', template: SUSPENSE_TEMPLATE, host: SUSPENSE_HOST, providers: [provideTransitionScope()], styles: [":host{display:contents}\n"] }]
692
734
  }] });
@@ -698,10 +740,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImpor
698
740
  * ancestor.
699
741
  */
700
742
  class UnscopedSuspenseBoundary extends SuspenseBoundaryBase {
701
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: UnscopedSuspenseBoundary, deps: null, target: i0.ɵɵFactoryTarget.Component });
702
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: UnscopedSuspenseBoundary, isStandalone: true, selector: "mm-unscoped-suspense", host: { properties: { "attr.aria-busy": "pending() ? true : null" } }, usesInheritance: true, ngImport: i0, template: "\n @if (suspended()) {\n <ng-content select=\"[placeholder]\"><span>Loading\u2026</span></ng-content>\n } @else {\n @if (pending()) {\n <ng-content select=\"[busy]\" />\n }\n <ng-content />\n }\n", isInline: true, styles: [":host{display:contents}\n"] });
743
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: UnscopedSuspenseBoundary, deps: null, target: i0.ɵɵFactoryTarget.Component });
744
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: UnscopedSuspenseBoundary, isStandalone: true, selector: "mm-unscoped-suspense", host: { properties: { "attr.aria-busy": "pending() ? true : null" } }, usesInheritance: true, ngImport: i0, template: "\n @if (suspended()) {\n <ng-content select=\"[placeholder]\"><span>Loading\u2026</span></ng-content>\n } @else {\n @if (pending()) {\n <ng-content select=\"[busy]\" />\n }\n <ng-content />\n }\n", isInline: true, styles: [":host{display:contents}\n"] });
703
745
  }
704
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: UnscopedSuspenseBoundary, decorators: [{
746
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: UnscopedSuspenseBoundary, decorators: [{
705
747
  type: Component,
706
748
  args: [{ selector: 'mm-unscoped-suspense', template: SUSPENSE_TEMPLATE, host: SUSPENSE_HOST, styles: [":host{display:contents}\n"] }]
707
749
  }] });
@@ -3970,10 +4012,10 @@ class MessageBus {
3970
4012
  this.channel.close();
3971
4013
  this.listeners.clear();
3972
4014
  }
3973
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: MessageBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3974
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: MessageBus, providedIn: 'root' });
4015
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MessageBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4016
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MessageBus, providedIn: 'root' });
3975
4017
  }
3976
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: MessageBus, decorators: [{
4018
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: MessageBus, decorators: [{
3977
4019
  type: Injectable,
3978
4020
  args: [{
3979
4021
  providedIn: 'root',
@@ -4278,5 +4320,5 @@ function withHistory(sourceOrValue, opt) {
4278
4320
  * Generated bundle index. Do not edit.
4279
4321
  */
4280
4322
 
4281
- export { MmActivity, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, filterWith, focusWithin, forkStore, geolocation, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
4323
+ export { MmActivity, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
4282
4324
  //# sourceMappingURL=mmstack-primitives.mjs.map