@mmstack/primitives 19.4.2 → 19.5.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/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).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isDevMode, inject, Injector, untracked, effect, DestroyRef, linkedSignal, InjectionToken, TemplateRef, ViewContainerRef, input, computed, Directive, signal,
|
|
2
|
+
import { isDevMode, inject, Injector, untracked, effect, DestroyRef, linkedSignal, InjectionToken, TemplateRef, ViewContainerRef, PLATFORM_ID, input, computed, Directive, signal, runInInjectionContext, ResourceStatus, afterNextRender, Component, isSignal, ElementRef, Injectable } from '@angular/core';
|
|
3
3
|
import { isPlatformServer } from '@angular/common';
|
|
4
4
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
5
5
|
|
|
@@ -80,7 +80,7 @@ function popFrame() {
|
|
|
80
80
|
* ]);
|
|
81
81
|
*
|
|
82
82
|
* // The fine-grained mapped list
|
|
83
|
-
* const mappedUsers =
|
|
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
|
|
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
|
* }
|
|
@@ -248,6 +248,7 @@ class MmActivity {
|
|
|
248
248
|
tpl = inject(TemplateRef);
|
|
249
249
|
vcr = inject(ViewContainerRef);
|
|
250
250
|
parent = inject(Injector);
|
|
251
|
+
onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
251
252
|
/** When false, keep the content mounted but hidden + CD-detached. */
|
|
252
253
|
visible = input.required({ alias: 'mmActivity' });
|
|
253
254
|
/** Paused == not visible — handed to the kept subtree as PAUSED_CONTEXT. */
|
|
@@ -270,6 +271,8 @@ class MmActivity {
|
|
|
270
271
|
}),
|
|
271
272
|
});
|
|
272
273
|
}
|
|
274
|
+
if (this.onServer)
|
|
275
|
+
return;
|
|
273
276
|
for (const node of this.view.rootNodes) {
|
|
274
277
|
// covers HTML and SVG roots; text/comment roots can't be styled — their CD is still
|
|
275
278
|
// detached, but prefer an element root for true visual hiding
|
|
@@ -567,6 +570,47 @@ function injectTransitionScope() {
|
|
|
567
570
|
}
|
|
568
571
|
return scope;
|
|
569
572
|
}
|
|
573
|
+
function createForwardingScope() {
|
|
574
|
+
const own = createTransitionScope();
|
|
575
|
+
const target = signal(null);
|
|
576
|
+
const eff = () => target() ?? own;
|
|
577
|
+
const owners = new Map();
|
|
578
|
+
return {
|
|
579
|
+
setTarget: (t) => target.set(t),
|
|
580
|
+
resources: computed(() => eff().resources()),
|
|
581
|
+
pending: computed(() => eff().pending()),
|
|
582
|
+
suspended: (type) => eff().suspended(type),
|
|
583
|
+
add: (ref, opt) => {
|
|
584
|
+
const t = untracked(target) ?? own;
|
|
585
|
+
owners.set(ref, t);
|
|
586
|
+
t.add(ref, opt);
|
|
587
|
+
},
|
|
588
|
+
remove: (ref) => {
|
|
589
|
+
const t = owners.get(ref) ?? untracked(target) ?? own;
|
|
590
|
+
t.remove(ref);
|
|
591
|
+
owners.delete(ref);
|
|
592
|
+
},
|
|
593
|
+
commit: (value) => linkedSignal({
|
|
594
|
+
source: () => ({ v: value(), settled: !eff().pending() }),
|
|
595
|
+
computation: (curr, prev) => curr.settled || prev === undefined ? curr.v : prev.value,
|
|
596
|
+
}),
|
|
597
|
+
holding: computed(() => eff().holding()),
|
|
598
|
+
beginHold: () => (untracked(target) ?? own).beginHold(),
|
|
599
|
+
endHold: () => (untracked(target) ?? own).endHold(),
|
|
600
|
+
hold: (value) => linkedSignal({
|
|
601
|
+
source: () => ({ v: value(), held: eff().holding() }),
|
|
602
|
+
computation: (curr, prev) => prev !== undefined && curr.held ? prev.value : curr.v,
|
|
603
|
+
}),
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/** Provide a forwarding transition scope at a boundary (used by the transition outlet). */
|
|
607
|
+
function provideForwardingTransitionScope() {
|
|
608
|
+
return { provide: TRANSITION_SCOPE, useFactory: createForwardingScope };
|
|
609
|
+
}
|
|
610
|
+
/** Read the transition scope reachable from `injector`, or null if none is provided there. */
|
|
611
|
+
function getTransitionScope(injector) {
|
|
612
|
+
return injector.get(TRANSITION_SCOPE, null);
|
|
613
|
+
}
|
|
570
614
|
/**
|
|
571
615
|
* Returns a register function bound to the nearest transition scope: it adds a resource
|
|
572
616
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
@@ -604,6 +648,7 @@ function registerResource(res, opt) {
|
|
|
604
648
|
function injectStartTransition() {
|
|
605
649
|
const scope = injectTransitionScope();
|
|
606
650
|
const injector = inject(Injector);
|
|
651
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
607
652
|
return (fn) => {
|
|
608
653
|
untracked(fn);
|
|
609
654
|
let sawPending = false;
|
|
@@ -618,6 +663,13 @@ function injectStartTransition() {
|
|
|
618
663
|
resolve();
|
|
619
664
|
}
|
|
620
665
|
}, { injector });
|
|
666
|
+
if (onServer) {
|
|
667
|
+
if (!untracked(scope.pending)) {
|
|
668
|
+
watcher.destroy();
|
|
669
|
+
resolve();
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
621
673
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
622
674
|
// if nothing ever went in flight, the transition is already complete.
|
|
623
675
|
afterNextRender(() => {
|
|
@@ -758,6 +810,7 @@ function runInTransaction(txn, fn) {
|
|
|
758
810
|
function injectStartTransaction() {
|
|
759
811
|
const scope = injectTransitionScope();
|
|
760
812
|
const injector = inject(Injector);
|
|
813
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
761
814
|
return (fn) => {
|
|
762
815
|
const txn = createTransaction();
|
|
763
816
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
@@ -799,11 +852,17 @@ function injectStartTransaction() {
|
|
|
799
852
|
if (sawPending && !p)
|
|
800
853
|
finish(false);
|
|
801
854
|
}, { injector });
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (!sawPending && !untracked(scope.pending))
|
|
855
|
+
if (onServer) {
|
|
856
|
+
if (!untracked(scope.pending))
|
|
805
857
|
finish(false);
|
|
806
|
-
}
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
861
|
+
afterNextRender(() => {
|
|
862
|
+
if (!sawPending && !untracked(scope.pending))
|
|
863
|
+
finish(false);
|
|
864
|
+
}, { injector });
|
|
865
|
+
}
|
|
807
866
|
return {
|
|
808
867
|
pending: scope.pending,
|
|
809
868
|
done,
|
|
@@ -4263,5 +4322,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4263
4322
|
* Generated bundle index. Do not edit.
|
|
4264
4323
|
*/
|
|
4265
4324
|
|
|
4266
|
-
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 };
|
|
4325
|
+
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 };
|
|
4267
4326
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|