@mmstack/primitives 21.2.0 → 21.2.2
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 +49 -10
- package/fesm2022/mmstack-primitives.mjs +612 -355
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +265 -117
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isDevMode, inject, Injector, untracked, effect, DestroyRef,
|
|
2
|
+
import { isDevMode, inject, Injector, untracked, effect, DestroyRef, InjectionToken, TemplateRef, ViewContainerRef, PLATFORM_ID, input, computed, Directive, signal, runInInjectionContext, linkedSignal, afterNextRender, Component, isWritableSignal as isWritableSignal$2, isSignal, ElementRef, Injectable } from '@angular/core';
|
|
3
3
|
import { isPlatformServer } from '@angular/common';
|
|
4
4
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
5
5
|
|
|
@@ -155,70 +155,6 @@ function nestedEffect(effectFn, options) {
|
|
|
155
155
|
return ref;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/**
|
|
159
|
-
* Creates a new `Signal` that processes an array of items in time-sliced chunks. This is useful for handling large lists without blocking the main thread.
|
|
160
|
-
*
|
|
161
|
-
* The returned signal will initially contain the first `chunkSize` items from the source array. It will then schedule updates to include additional chunks of items based on the specified `delay`.
|
|
162
|
-
*
|
|
163
|
-
* @template T The type of items in the array.
|
|
164
|
-
* @param source A `Signal` or a function that returns an array of items to be processed in chunks.
|
|
165
|
-
* @param options Configuration options for chunk size, delay duration, equality function, and injector.
|
|
166
|
-
* @returns A `Signal` that emits the current chunk of items being processed.
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* const largeList = signal(Array.from({ length: 1000 }, (_, i) => i));
|
|
170
|
-
* const chunkedList = chunked(largeList, { chunkSize: 100, delay: 100 });
|
|
171
|
-
*/
|
|
172
|
-
function chunked(source, options) {
|
|
173
|
-
const { chunkSize = 50, delay = 'frame', equal, injector } = options || {};
|
|
174
|
-
let delayFn;
|
|
175
|
-
if (delay === 'frame') {
|
|
176
|
-
delayFn =
|
|
177
|
-
typeof requestAnimationFrame === 'function'
|
|
178
|
-
? (callback) => {
|
|
179
|
-
const num = requestAnimationFrame(callback);
|
|
180
|
-
return () => cancelAnimationFrame(num);
|
|
181
|
-
}
|
|
182
|
-
: // SSR: no requestAnimationFrame — approximate a frame with a timeout
|
|
183
|
-
(cb) => {
|
|
184
|
-
const num = setTimeout(cb, 16);
|
|
185
|
-
return () => clearTimeout(num);
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
else if (delay === 'microtask') {
|
|
189
|
-
delayFn = (cb) => {
|
|
190
|
-
let isCancelled = false;
|
|
191
|
-
queueMicrotask(() => {
|
|
192
|
-
if (isCancelled)
|
|
193
|
-
return;
|
|
194
|
-
cb();
|
|
195
|
-
});
|
|
196
|
-
return () => {
|
|
197
|
-
isCancelled = true;
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
delayFn = (cb) => {
|
|
203
|
-
const num = setTimeout(cb, delay);
|
|
204
|
-
return () => clearTimeout(num);
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const internal = linkedSignal({ ...(ngDevMode ? { debugName: "internal" } : /* istanbul ignore next */ {}), source,
|
|
208
|
-
computation: (items) => items.slice(0, chunkSize),
|
|
209
|
-
equal });
|
|
210
|
-
nestedEffect((cleanup) => {
|
|
211
|
-
const fullList = source();
|
|
212
|
-
const current = internal();
|
|
213
|
-
if (current.length >= fullList.length)
|
|
214
|
-
return;
|
|
215
|
-
return cleanup(delayFn(() => untracked(() => internal.set(fullList.slice(0, current.length + chunkSize)))));
|
|
216
|
-
}, {
|
|
217
|
-
injector: injector,
|
|
218
|
-
});
|
|
219
|
-
return internal.asReadonly();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
158
|
/**
|
|
223
159
|
* Whether the subtree a resource/component lives in is currently PAUSED, for Activity / keep-alive.
|
|
224
160
|
* Provided by an Activity boundary (`MmActivity`, or the app-builder's per-branch injector) and read
|
|
@@ -246,6 +182,7 @@ class MmActivity {
|
|
|
246
182
|
tpl = inject(TemplateRef);
|
|
247
183
|
vcr = inject(ViewContainerRef);
|
|
248
184
|
parent = inject(Injector);
|
|
185
|
+
onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
249
186
|
/** When false, keep the content mounted but hidden + CD-detached. */
|
|
250
187
|
visible = input.required({ ...(ngDevMode ? { debugName: "visible" } : /* istanbul ignore next */ {}), alias: 'mmActivity' });
|
|
251
188
|
/** Paused == not visible — handed to the kept subtree as PAUSED_CONTEXT. */
|
|
@@ -268,6 +205,8 @@ class MmActivity {
|
|
|
268
205
|
}),
|
|
269
206
|
});
|
|
270
207
|
}
|
|
208
|
+
if (this.onServer)
|
|
209
|
+
return;
|
|
271
210
|
for (const node of this.view.rootNodes) {
|
|
272
211
|
// covers HTML and SVG roots; text/comment roots can't be styled — their CD is still
|
|
273
212
|
// detached, but prefer an element root for true visual hiding
|
|
@@ -312,24 +251,23 @@ function providePaused(source) {
|
|
|
312
251
|
}
|
|
313
252
|
|
|
314
253
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
*
|
|
322
|
-
*
|
|
254
|
+
* @internal Token carrying an app-wide default {@link PauseOption}, set via
|
|
255
|
+
* {@link providePausableOptions}. {@link resolvePause} consults it when the call site didn't
|
|
256
|
+
* specify `pause`, so users can opt every pausable-aware primitive in (or out) from one place.
|
|
257
|
+
*/
|
|
258
|
+
const PAUSABLE_OPTIONS = new InjectionToken('@mmstack/primitives:pausable-options');
|
|
259
|
+
/**
|
|
260
|
+
* Provides an app-wide default {@link PauseOption} for every pausable-aware primitive (the public
|
|
261
|
+
* `pausable*` family plus the opt-in integrations like `stored` / `chunked`). A call-site `pause`
|
|
262
|
+
* always wins; this only fills in when the call didn't specify one.
|
|
323
263
|
*
|
|
324
|
-
*
|
|
264
|
+
* @example
|
|
265
|
+
* // Make everything that can pause honour the ambient Activity boundary by default:
|
|
266
|
+
* providePausableOptions({ pause: true })
|
|
325
267
|
*/
|
|
326
|
-
function
|
|
327
|
-
return
|
|
328
|
-
source: () => ({ t: target(), ready: ready() }),
|
|
329
|
-
computation: (curr, prev) => (prev === undefined || curr.ready ? curr.t : prev.value),
|
|
330
|
-
});
|
|
268
|
+
function providePausableOptions(opt) {
|
|
269
|
+
return { provide: PAUSABLE_OPTIONS, useValue: opt };
|
|
331
270
|
}
|
|
332
|
-
|
|
333
271
|
/**
|
|
334
272
|
* Resolve a {@link PauseOption} into a pause predicate, or `null` meaning "do not pause".
|
|
335
273
|
* `null` tells the caller to return the bare primitive — no wrapper is created.
|
|
@@ -344,11 +282,7 @@ function holdUntilReady(target, ready) {
|
|
|
344
282
|
*
|
|
345
283
|
* Encapsulating this here keeps every pausable primitive's branching identical and in one place.
|
|
346
284
|
*/
|
|
347
|
-
function resolvePause(opt) {
|
|
348
|
-
const explicit = opt?.pause; // distinguish explicit `true` from the omitted default
|
|
349
|
-
const pause = explicit ?? true; // explicit pausable* calls default to pausing
|
|
350
|
-
if (pause === false)
|
|
351
|
-
return null;
|
|
285
|
+
function resolvePause(opt, defaultPause = true) {
|
|
352
286
|
const run = (fn) => opt?.injector ? runInInjectionContext(opt.injector, fn) : fn();
|
|
353
287
|
// `inject` requires an injection context even with `optional: true`. A bare
|
|
354
288
|
// `pausableSignal(0)` (documented as "like `signal`") must degrade to the unwrapped
|
|
@@ -361,6 +295,12 @@ function resolvePause(opt) {
|
|
|
361
295
|
return fallback;
|
|
362
296
|
}
|
|
363
297
|
};
|
|
298
|
+
// A `providePausableOptions(...)` default fills in when the call site didn't specify `pause`.
|
|
299
|
+
const providedPause = tryRun(() => inject(PAUSABLE_OPTIONS, { optional: true })?.pause, undefined);
|
|
300
|
+
const explicit = opt?.pause ?? providedPause;
|
|
301
|
+
const pause = explicit ?? defaultPause; // public pausable* default `true`; opt-in integrations `false`
|
|
302
|
+
if (pause === false)
|
|
303
|
+
return null;
|
|
364
304
|
const onServer = () => typeof pause === 'function' && !opt?.injector
|
|
365
305
|
? typeof globalThis.window === 'undefined'
|
|
366
306
|
: tryRun(() => isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser'), typeof globalThis.window === 'undefined');
|
|
@@ -370,7 +310,7 @@ function resolvePause(opt) {
|
|
|
370
310
|
return null;
|
|
371
311
|
const paused = tryRun(() => inject(PAUSED_CONTEXT, { optional: true }), null);
|
|
372
312
|
if (!paused) {
|
|
373
|
-
if (
|
|
313
|
+
if (opt?.pause === true && isDevMode())
|
|
374
314
|
console.warn('[pausable] `pause: true` but no PAUSED_CONTEXT in scope — not pausing. Provide one via an ' +
|
|
375
315
|
'Activity boundary (`MmActivity` / `providePaused`), or pass a predicate / `pause: false`.');
|
|
376
316
|
return null;
|
|
@@ -438,6 +378,92 @@ function pausableComputed(computation, options) {
|
|
|
438
378
|
return ls.asReadonly();
|
|
439
379
|
}
|
|
440
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Creates a new `Signal` that processes an array of items in time-sliced chunks. This is useful for handling large lists without blocking the main thread.
|
|
383
|
+
*
|
|
384
|
+
* The returned signal will initially contain the first `chunkSize` items from the source array. It will then schedule updates to include additional chunks of items based on the specified `delay`.
|
|
385
|
+
*
|
|
386
|
+
* @template T The type of items in the array.
|
|
387
|
+
* @param source A `Signal` or a function that returns an array of items to be processed in chunks.
|
|
388
|
+
* @param options Configuration options for chunk size, delay duration, equality function, and injector.
|
|
389
|
+
* @returns A `Signal` that emits the current chunk of items being processed.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* const largeList = signal(Array.from({ length: 1000 }, (_, i) => i));
|
|
393
|
+
* const chunkedList = chunked(largeList, { chunkSize: 100, delay: 100 });
|
|
394
|
+
*/
|
|
395
|
+
function chunked(source, options) {
|
|
396
|
+
const { chunkSize = 50, delay = 'frame', equal, injector, pause, } = options || {};
|
|
397
|
+
let delayFn;
|
|
398
|
+
if (delay === 'frame') {
|
|
399
|
+
delayFn =
|
|
400
|
+
typeof requestAnimationFrame === 'function'
|
|
401
|
+
? (callback) => {
|
|
402
|
+
const num = requestAnimationFrame(callback);
|
|
403
|
+
return () => cancelAnimationFrame(num);
|
|
404
|
+
}
|
|
405
|
+
: // SSR: no requestAnimationFrame — approximate a frame with a timeout
|
|
406
|
+
(cb) => {
|
|
407
|
+
const num = setTimeout(cb, 16);
|
|
408
|
+
return () => clearTimeout(num);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
else if (delay === 'microtask') {
|
|
412
|
+
delayFn = (cb) => {
|
|
413
|
+
let isCancelled = false;
|
|
414
|
+
queueMicrotask(() => {
|
|
415
|
+
if (isCancelled)
|
|
416
|
+
return;
|
|
417
|
+
cb();
|
|
418
|
+
});
|
|
419
|
+
return () => {
|
|
420
|
+
isCancelled = true;
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
delayFn = (cb) => {
|
|
426
|
+
const num = setTimeout(cb, delay);
|
|
427
|
+
return () => clearTimeout(num);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const internal = linkedSignal({ ...(ngDevMode ? { debugName: "internal" } : /* istanbul ignore next */ {}), source,
|
|
431
|
+
computation: (items) => items.slice(0, chunkSize),
|
|
432
|
+
equal });
|
|
433
|
+
const paused = resolvePause({ injector, pause }, false);
|
|
434
|
+
nestedEffect((cleanup) => {
|
|
435
|
+
if (paused?.())
|
|
436
|
+
return;
|
|
437
|
+
const fullList = source();
|
|
438
|
+
const current = internal();
|
|
439
|
+
if (current.length >= fullList.length)
|
|
440
|
+
return;
|
|
441
|
+
return cleanup(delayFn(() => untracked(() => internal.set(fullList.slice(0, current.length + chunkSize)))));
|
|
442
|
+
}, {
|
|
443
|
+
injector,
|
|
444
|
+
});
|
|
445
|
+
return internal.asReadonly();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Structural hold-and-swap as a signal. Given a `target` (the desired value — e.g. the
|
|
450
|
+
* subtree/def/key you want to show) and a `ready` predicate, returns a signal that keeps
|
|
451
|
+
* yielding its PREVIOUS value until `ready()` is true, then swaps to the current target.
|
|
452
|
+
*
|
|
453
|
+
* This is the structural counterpart to `keepPrevious`/`commit`: where those hold a *value*
|
|
454
|
+
* through a reload, this holds a *structure* through a swap. The caller mounts the incoming
|
|
455
|
+
* structure off to the side (so its resources can settle and flip `ready`), keeps showing the
|
|
456
|
+
* held previous structure meanwhile, and lets the old one go once `ready` releases the swap.
|
|
457
|
+
*
|
|
458
|
+
* The very first value passes straight through (nothing to hold yet).
|
|
459
|
+
*/
|
|
460
|
+
function holdUntilReady(target, ready) {
|
|
461
|
+
return linkedSignal({
|
|
462
|
+
source: () => ({ t: target(), ready: ready() }),
|
|
463
|
+
computation: (curr, prev) => (prev === undefined || curr.ready ? curr.t : prev.value),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
441
467
|
const { is } = Object;
|
|
442
468
|
function mutable(initial, opt) {
|
|
443
469
|
const baseEqual = opt?.equal ?? is;
|
|
@@ -639,6 +665,7 @@ function registerResource(res, opt) {
|
|
|
639
665
|
function injectStartTransition() {
|
|
640
666
|
const scope = injectTransitionScope();
|
|
641
667
|
const injector = inject(Injector);
|
|
668
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
642
669
|
return (fn) => {
|
|
643
670
|
untracked(fn);
|
|
644
671
|
let sawPending = false;
|
|
@@ -653,6 +680,13 @@ function injectStartTransition() {
|
|
|
653
680
|
resolve();
|
|
654
681
|
}
|
|
655
682
|
}, { ...(ngDevMode ? { debugName: "watcher" } : /* istanbul ignore next */ {}), injector });
|
|
683
|
+
if (onServer) {
|
|
684
|
+
if (!untracked(scope.pending)) {
|
|
685
|
+
watcher.destroy();
|
|
686
|
+
resolve();
|
|
687
|
+
}
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
656
690
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
657
691
|
// if nothing ever went in flight, the transition is already complete.
|
|
658
692
|
afterNextRender(() => {
|
|
@@ -680,6 +714,12 @@ function injectStartTransition() {
|
|
|
680
714
|
*
|
|
681
715
|
* `type` selects what "not ready" means: `'value'` (default) suspends only until a first value lands
|
|
682
716
|
* then holds through reloads; `'loading'` suspends on every in-flight load (strict suspense).
|
|
717
|
+
*
|
|
718
|
+
* SSR: the server serializes whatever the scope reports at stabilization, so a registered resource
|
|
719
|
+
* must keep the app unstable until it settles or the placeholder is what gets serialized (then
|
|
720
|
+
* flashes/mismatches on hydration). HttpClient-backed resources, httpResource & all of `@mmstack/resource`
|
|
721
|
+
* do this automatically via the HTTP layer's `PendingTasks` + transfer cache. A custom loader (raw
|
|
722
|
+
* `fetch`/promise/timer) must opt in itself: wrap it with `inject(PendingTasks).run(() => promise)`.
|
|
683
723
|
*/
|
|
684
724
|
class SuspenseBoundaryBase {
|
|
685
725
|
scope = injectTransitionScope();
|
|
@@ -793,6 +833,7 @@ function runInTransaction(txn, fn) {
|
|
|
793
833
|
function injectStartTransaction() {
|
|
794
834
|
const scope = injectTransitionScope();
|
|
795
835
|
const injector = inject(Injector);
|
|
836
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
796
837
|
return (fn) => {
|
|
797
838
|
const txn = createTransaction();
|
|
798
839
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
@@ -834,11 +875,17 @@ function injectStartTransaction() {
|
|
|
834
875
|
if (sawPending && !p)
|
|
835
876
|
finish(false);
|
|
836
877
|
}, { injector });
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!sawPending && !untracked(scope.pending))
|
|
878
|
+
if (onServer) {
|
|
879
|
+
if (!untracked(scope.pending))
|
|
840
880
|
finish(false);
|
|
841
|
-
}
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
884
|
+
afterNextRender(() => {
|
|
885
|
+
if (!sawPending && !untracked(scope.pending))
|
|
886
|
+
finish(false);
|
|
887
|
+
}, { injector });
|
|
888
|
+
}
|
|
842
889
|
return {
|
|
843
890
|
pending: scope.pending,
|
|
844
891
|
done,
|
|
@@ -2607,11 +2654,19 @@ function throttle(source, opt) {
|
|
|
2607
2654
|
tick();
|
|
2608
2655
|
};
|
|
2609
2656
|
const update = (fn) => set(fn(untracked(source)));
|
|
2657
|
+
const flush = () => {
|
|
2658
|
+
if (timeout)
|
|
2659
|
+
clearTimeout(timeout);
|
|
2660
|
+
timeout = undefined;
|
|
2661
|
+
pendingTrailing = false;
|
|
2662
|
+
fire();
|
|
2663
|
+
};
|
|
2610
2664
|
const writable = toWritable(computed(() => {
|
|
2611
2665
|
trigger();
|
|
2612
2666
|
return untracked(source);
|
|
2613
2667
|
}, opt), set, update);
|
|
2614
2668
|
writable.original = source.asReadonly();
|
|
2669
|
+
writable.flush = flush;
|
|
2615
2670
|
return writable;
|
|
2616
2671
|
}
|
|
2617
2672
|
|
|
@@ -2830,6 +2885,194 @@ function createOrientation(debugName) {
|
|
|
2830
2885
|
return state.asReadonly();
|
|
2831
2886
|
}
|
|
2832
2887
|
|
|
2888
|
+
const IDLE = {
|
|
2889
|
+
active: false,
|
|
2890
|
+
start: { x: 0, y: 0 },
|
|
2891
|
+
current: { x: 0, y: 0 },
|
|
2892
|
+
delta: { x: 0, y: 0 },
|
|
2893
|
+
pointerId: null,
|
|
2894
|
+
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2895
|
+
button: -1,
|
|
2896
|
+
};
|
|
2897
|
+
function stateEqual(a, b) {
|
|
2898
|
+
return (a.active === b.active &&
|
|
2899
|
+
a.pointerId === b.pointerId &&
|
|
2900
|
+
a.current.x === b.current.x &&
|
|
2901
|
+
a.current.y === b.current.y &&
|
|
2902
|
+
a.button === b.button &&
|
|
2903
|
+
a.modifiers.shift === b.modifiers.shift &&
|
|
2904
|
+
a.modifiers.alt === b.modifiers.alt &&
|
|
2905
|
+
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
2906
|
+
a.modifiers.meta === b.modifiers.meta);
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Tracks a pointer *gesture* (pointerdown → capture → move → up) as a signal —
|
|
2910
|
+
* the foundation for pointer-based drag/move/resize/marquee on a canvas. Unlike
|
|
2911
|
+
* native HTML5 drag, pointer events fire continuously and coordinates are
|
|
2912
|
+
* reliable. SSR-safe; cleans up its listeners automatically.
|
|
2913
|
+
*
|
|
2914
|
+
* @example
|
|
2915
|
+
* ```ts
|
|
2916
|
+
* const drag = pointerDrag({ activationThreshold: 4 });
|
|
2917
|
+
* const position = computed(() => {
|
|
2918
|
+
* const d = drag();
|
|
2919
|
+
* return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
|
|
2920
|
+
* });
|
|
2921
|
+
* ```
|
|
2922
|
+
*/
|
|
2923
|
+
function pointerDrag(opt) {
|
|
2924
|
+
return runInSensorContext(opt?.injector, () => createPointerDrag(opt));
|
|
2925
|
+
}
|
|
2926
|
+
function createPointerDrag(opt) {
|
|
2927
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
2928
|
+
const base = computed(() => IDLE, {
|
|
2929
|
+
debugName: opt?.debugName ?? 'pointerDrag',
|
|
2930
|
+
});
|
|
2931
|
+
base.unthrottled = base;
|
|
2932
|
+
base.cancel = () => undefined;
|
|
2933
|
+
return base;
|
|
2934
|
+
}
|
|
2935
|
+
const hostRef = inject((ElementRef), { optional: true });
|
|
2936
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2937
|
+
const resolve = (t) => {
|
|
2938
|
+
if (!t)
|
|
2939
|
+
return null;
|
|
2940
|
+
return t instanceof ElementRef ? t.nativeElement : t;
|
|
2941
|
+
};
|
|
2942
|
+
if (!isSignal(target) && !resolve(target)) {
|
|
2943
|
+
if (isDevMode())
|
|
2944
|
+
console.warn('pointerDrag: no target element (host ElementRef missing).');
|
|
2945
|
+
const base = computed(() => IDLE, { debugName });
|
|
2946
|
+
base.unthrottled = base;
|
|
2947
|
+
base.cancel = () => undefined;
|
|
2948
|
+
return base;
|
|
2949
|
+
}
|
|
2950
|
+
const state = throttled(IDLE, {
|
|
2951
|
+
ms: throttle,
|
|
2952
|
+
leading: true,
|
|
2953
|
+
trailing: true,
|
|
2954
|
+
equal: stateEqual,
|
|
2955
|
+
debugName,
|
|
2956
|
+
});
|
|
2957
|
+
let startPoint = { x: 0, y: 0 };
|
|
2958
|
+
let activePointerId = null;
|
|
2959
|
+
let activeButton = -1;
|
|
2960
|
+
let activated = false;
|
|
2961
|
+
let gesture = null;
|
|
2962
|
+
const coord = (e) => coordinateSpace === 'page'
|
|
2963
|
+
? { x: e.pageX, y: e.pageY }
|
|
2964
|
+
: { x: e.clientX, y: e.clientY };
|
|
2965
|
+
const mods = (e) => ({
|
|
2966
|
+
shift: e.shiftKey,
|
|
2967
|
+
alt: e.altKey,
|
|
2968
|
+
ctrl: e.ctrlKey,
|
|
2969
|
+
meta: e.metaKey,
|
|
2970
|
+
});
|
|
2971
|
+
const end = () => {
|
|
2972
|
+
gesture?.abort();
|
|
2973
|
+
gesture = null;
|
|
2974
|
+
activePointerId = null;
|
|
2975
|
+
activeButton = -1;
|
|
2976
|
+
activated = false;
|
|
2977
|
+
state.set(IDLE);
|
|
2978
|
+
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
2979
|
+
};
|
|
2980
|
+
const onMove = (e) => {
|
|
2981
|
+
if (e.pointerId !== activePointerId)
|
|
2982
|
+
return;
|
|
2983
|
+
const current = coord(e);
|
|
2984
|
+
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
2985
|
+
if (!activated && Math.hypot(delta.x, delta.y) >= activationThreshold) {
|
|
2986
|
+
activated = true;
|
|
2987
|
+
}
|
|
2988
|
+
state.set({
|
|
2989
|
+
active: activated,
|
|
2990
|
+
start: startPoint,
|
|
2991
|
+
current,
|
|
2992
|
+
delta,
|
|
2993
|
+
pointerId: activePointerId,
|
|
2994
|
+
modifiers: mods(e),
|
|
2995
|
+
button: activeButton, // pointermove button is -1; keep the down-button
|
|
2996
|
+
});
|
|
2997
|
+
};
|
|
2998
|
+
const onUp = (e) => {
|
|
2999
|
+
if (e.pointerId === activePointerId)
|
|
3000
|
+
end();
|
|
3001
|
+
};
|
|
3002
|
+
const onCancel = (e) => {
|
|
3003
|
+
if (e.pointerId === activePointerId)
|
|
3004
|
+
end();
|
|
3005
|
+
};
|
|
3006
|
+
const onKey = (e) => {
|
|
3007
|
+
if (e.key === 'Escape' && activePointerId !== null)
|
|
3008
|
+
end();
|
|
3009
|
+
};
|
|
3010
|
+
const onDown = (el) => (e) => {
|
|
3011
|
+
if (activePointerId !== null)
|
|
3012
|
+
return;
|
|
3013
|
+
if (!buttons.includes(e.button))
|
|
3014
|
+
return;
|
|
3015
|
+
if (handleSelector && !e.target?.closest?.(handleSelector)) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
activePointerId = e.pointerId;
|
|
3019
|
+
activeButton = e.button;
|
|
3020
|
+
activated = false;
|
|
3021
|
+
startPoint = coord(e);
|
|
3022
|
+
try {
|
|
3023
|
+
el.setPointerCapture(e.pointerId);
|
|
3024
|
+
}
|
|
3025
|
+
catch {
|
|
3026
|
+
// capture unsupported (older browsers / test env) — listeners still work
|
|
3027
|
+
}
|
|
3028
|
+
gesture = new AbortController();
|
|
3029
|
+
const signal = gesture.signal;
|
|
3030
|
+
el.addEventListener('pointermove', onMove, { signal });
|
|
3031
|
+
el.addEventListener('pointerup', onUp, { signal });
|
|
3032
|
+
el.addEventListener('pointercancel', onCancel, { signal });
|
|
3033
|
+
el.addEventListener('lostpointercapture', onCancel, {
|
|
3034
|
+
signal,
|
|
3035
|
+
});
|
|
3036
|
+
window.addEventListener('keydown', onKey, { signal });
|
|
3037
|
+
state.set({
|
|
3038
|
+
active: false,
|
|
3039
|
+
start: startPoint,
|
|
3040
|
+
current: startPoint,
|
|
3041
|
+
delta: { x: 0, y: 0 },
|
|
3042
|
+
pointerId: e.pointerId,
|
|
3043
|
+
modifiers: mods(e),
|
|
3044
|
+
button: e.button,
|
|
3045
|
+
});
|
|
3046
|
+
};
|
|
3047
|
+
const attach = (el) => {
|
|
3048
|
+
const controller = new AbortController();
|
|
3049
|
+
el.addEventListener('pointerdown', onDown(el), {
|
|
3050
|
+
signal: controller.signal,
|
|
3051
|
+
});
|
|
3052
|
+
return () => {
|
|
3053
|
+
controller.abort();
|
|
3054
|
+
end();
|
|
3055
|
+
};
|
|
3056
|
+
};
|
|
3057
|
+
if (isSignal(target)) {
|
|
3058
|
+
effect((cleanup) => {
|
|
3059
|
+
const el = resolve(target());
|
|
3060
|
+
if (!el)
|
|
3061
|
+
return;
|
|
3062
|
+
cleanup(attach(el));
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
else {
|
|
3066
|
+
const el = resolve(target);
|
|
3067
|
+
if (el)
|
|
3068
|
+
inject(DestroyRef).onDestroy(attach(el));
|
|
3069
|
+
}
|
|
3070
|
+
const base = state.asReadonly();
|
|
3071
|
+
base.unthrottled = state.original;
|
|
3072
|
+
base.cancel = end;
|
|
3073
|
+
return base;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
2833
3076
|
/**
|
|
2834
3077
|
* Creates a read-only signal that tracks the page's visibility state.
|
|
2835
3078
|
*
|
|
@@ -3056,6 +3299,8 @@ function sensor(type, options) {
|
|
|
3056
3299
|
switch (type) {
|
|
3057
3300
|
case 'mousePosition':
|
|
3058
3301
|
return mousePosition(opts);
|
|
3302
|
+
case 'pointerDrag':
|
|
3303
|
+
return pointerDrag(opts);
|
|
3059
3304
|
case 'networkStatus':
|
|
3060
3305
|
return networkStatus(opts);
|
|
3061
3306
|
case 'pageVisibility':
|
|
@@ -3173,9 +3418,6 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3173
3418
|
return untracked(() => state.asReadonly());
|
|
3174
3419
|
}
|
|
3175
3420
|
|
|
3176
|
-
function isWritableSignal(value) {
|
|
3177
|
-
return isWritableSignal$2(value);
|
|
3178
|
-
}
|
|
3179
3421
|
/**
|
|
3180
3422
|
* Runtime marker + compile-time brand for an opaque value. A `const`-declared `Symbol`
|
|
3181
3423
|
* has a `unique symbol` type, so the same symbol serves as both the property key written
|
|
@@ -3217,12 +3459,16 @@ function isOpaque(value) {
|
|
|
3217
3459
|
value !== null &&
|
|
3218
3460
|
value[OPAQUE] === true);
|
|
3219
3461
|
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3462
|
+
|
|
3463
|
+
function isWritableSignal(value) {
|
|
3464
|
+
return isWritableSignal$2(value);
|
|
3465
|
+
}
|
|
3466
|
+
function isRecord(value) {
|
|
3467
|
+
if (value === null || typeof value !== 'object' || isOpaque(value))
|
|
3468
|
+
return false;
|
|
3469
|
+
const proto = Object.getPrototypeOf(value);
|
|
3470
|
+
return proto === Object.prototype || proto === null;
|
|
3471
|
+
}
|
|
3226
3472
|
/**
|
|
3227
3473
|
* @internal Whether a value is a terminal leaf: a concrete non-record/non-array value always is;
|
|
3228
3474
|
* `null`/`undefined` is a leaf only when vivification is disabled (with vivify on it can still
|
|
@@ -3235,6 +3481,65 @@ function isLeafValue(value, vivifyEnabled) {
|
|
|
3235
3481
|
return true; // opaque always wins — even arrays
|
|
3236
3482
|
return !Array.isArray(value) && !isRecord(value);
|
|
3237
3483
|
}
|
|
3484
|
+
/**
|
|
3485
|
+
* @internal
|
|
3486
|
+
* Resolves the vivify shape for a node from its current value: a present record/array is a
|
|
3487
|
+
* certainty we keep (cached in the derivation, so it survives the value being nulled); an
|
|
3488
|
+
* unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
|
|
3489
|
+
*/
|
|
3490
|
+
function resolveVivify(sample, option) {
|
|
3491
|
+
if (!option)
|
|
3492
|
+
return false;
|
|
3493
|
+
if (Array.isArray(sample))
|
|
3494
|
+
return 'array';
|
|
3495
|
+
if (isRecord(sample))
|
|
3496
|
+
return 'object';
|
|
3497
|
+
return 'auto';
|
|
3498
|
+
}
|
|
3499
|
+
function hasOwnKey(value, key) {
|
|
3500
|
+
return value != null && Object.hasOwn(value, key);
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* @internal
|
|
3504
|
+
* Builds the `onChange` for the fallback (non-record container) derivation branch. For an
|
|
3505
|
+
* immutable source the container is copied before the write — returning the same mutated
|
|
3506
|
+
* reference would let the source's equality cut propagation (leaving child signals permanently
|
|
3507
|
+
* stale) and alias the caller's original object, breaking the structural-sharing contract
|
|
3508
|
+
* `forkStore` relies on. For a mutable source the write goes through `mutate`, so the chain's
|
|
3509
|
+
* force-notify engages (plain `update` with the same reference would never notify).
|
|
3510
|
+
*/
|
|
3511
|
+
function createFallbackOnChange(target, prop, vivifyFn, isMutableSource) {
|
|
3512
|
+
const write = (newValue) => (v) => {
|
|
3513
|
+
const container = vivifyFn(v, prop);
|
|
3514
|
+
if (container === null || container === undefined)
|
|
3515
|
+
return container;
|
|
3516
|
+
const next = isMutableSource
|
|
3517
|
+
? container
|
|
3518
|
+
: Array.isArray(container)
|
|
3519
|
+
? container.slice()
|
|
3520
|
+
: isRecord(container)
|
|
3521
|
+
? { ...container }
|
|
3522
|
+
: container; // non-plain leaf (Date/class instance): legacy in-place attempt
|
|
3523
|
+
try {
|
|
3524
|
+
next[prop] = newValue;
|
|
3525
|
+
}
|
|
3526
|
+
catch (e) {
|
|
3527
|
+
if (isDevMode())
|
|
3528
|
+
console.error(`[store] Failed to set property "${String(prop)}"`, e);
|
|
3529
|
+
}
|
|
3530
|
+
return next;
|
|
3531
|
+
};
|
|
3532
|
+
return isMutableSource
|
|
3533
|
+
? (newValue) => target.mutate(write(newValue))
|
|
3534
|
+
: (newValue) => target.update(write(newValue));
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
/**
|
|
3538
|
+
* @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
|
|
3539
|
+
* {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
|
|
3540
|
+
* nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
|
|
3541
|
+
*/
|
|
3542
|
+
const LEAF = Symbol('@mmstack/primitives::store/LEAF');
|
|
3238
3543
|
/**
|
|
3239
3544
|
* @internal Constant leaf probes for nodes whose leaf-ness is statically known, so the reactive
|
|
3240
3545
|
* `computed` can be skipped entirely.
|
|
@@ -3292,14 +3597,20 @@ function markAsLeaf(sig, value, vivifyEnabled, noUnionLeaves) {
|
|
|
3292
3597
|
function isLeaf(value) {
|
|
3293
3598
|
return isStore(value) && value[LEAF]?.() === true;
|
|
3294
3599
|
}
|
|
3600
|
+
|
|
3295
3601
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3296
3602
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3297
3603
|
/**
|
|
3298
|
-
* @internal
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3604
|
+
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3605
|
+
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3606
|
+
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3301
3607
|
*/
|
|
3302
|
-
const
|
|
3608
|
+
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3609
|
+
/**
|
|
3610
|
+
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3611
|
+
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3612
|
+
*/
|
|
3613
|
+
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3303
3614
|
const SIGNAL_FN_PROP = new Set([
|
|
3304
3615
|
'set',
|
|
3305
3616
|
'update',
|
|
@@ -3307,6 +3618,12 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3307
3618
|
'inline',
|
|
3308
3619
|
'asReadonly',
|
|
3309
3620
|
]);
|
|
3621
|
+
/**
|
|
3622
|
+
* @internal
|
|
3623
|
+
* Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
|
|
3624
|
+
* Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
|
|
3625
|
+
*/
|
|
3626
|
+
const PROXY_CACHE = new WeakMap();
|
|
3310
3627
|
/**
|
|
3311
3628
|
* @internal
|
|
3312
3629
|
* Test-only handle on the finalization registry (deliberately NOT re-exported from the public
|
|
@@ -3326,193 +3643,72 @@ function isStore(value) {
|
|
|
3326
3643
|
value !== null &&
|
|
3327
3644
|
value[IS_STORE] === true);
|
|
3328
3645
|
}
|
|
3329
|
-
|
|
3330
|
-
if (value === null || typeof value !== 'object' || isOpaque(value))
|
|
3331
|
-
return false;
|
|
3332
|
-
const proto = Object.getPrototypeOf(value);
|
|
3333
|
-
return proto === Object.prototype || proto === null;
|
|
3334
|
-
}
|
|
3335
|
-
/**
|
|
3336
|
-
* @internal
|
|
3337
|
-
* Resolves the vivify shape for a node from its current value: a present record/array is a
|
|
3338
|
-
* certainty we keep (cached in the derivation, so it survives the value being nulled); an
|
|
3339
|
-
* unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
|
|
3340
|
-
*/
|
|
3341
|
-
function resolveVivify(sample, option) {
|
|
3342
|
-
if (!option)
|
|
3343
|
-
return false;
|
|
3344
|
-
if (Array.isArray(sample))
|
|
3345
|
-
return 'array';
|
|
3346
|
-
if (isRecord(sample))
|
|
3347
|
-
return 'object';
|
|
3348
|
-
return 'auto';
|
|
3349
|
-
}
|
|
3350
|
-
function hasOwnKey(value, key) {
|
|
3351
|
-
return value != null && Object.hasOwn(value, key);
|
|
3352
|
-
}
|
|
3646
|
+
|
|
3353
3647
|
/**
|
|
3354
|
-
* @internal
|
|
3355
|
-
*
|
|
3356
|
-
*
|
|
3357
|
-
* reference would let the source's equality cut propagation (leaving child signals permanently
|
|
3358
|
-
* stale) and alias the caller's original object, breaking the structural-sharing contract
|
|
3359
|
-
* `forkStore` relies on. For a mutable source the write goes through `mutate`, so the chain's
|
|
3360
|
-
* force-notify engages (plain `update` with the same reference would never notify).
|
|
3648
|
+
* @internal Reads (or lazily builds + caches) the child node proxy for `prop` on `target`,
|
|
3649
|
+
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3650
|
+
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3361
3651
|
*/
|
|
3362
|
-
function
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
return next;
|
|
3382
|
-
};
|
|
3383
|
-
return isMutableSource
|
|
3384
|
-
? (newValue) => target.mutate(write(newValue))
|
|
3385
|
-
: (newValue) => target.update(write(newValue));
|
|
3652
|
+
function getCachedChild(target, prop, build) {
|
|
3653
|
+
let storeCache = PROXY_CACHE.get(target);
|
|
3654
|
+
if (!storeCache) {
|
|
3655
|
+
storeCache = new Map();
|
|
3656
|
+
PROXY_CACHE.set(target, storeCache);
|
|
3657
|
+
}
|
|
3658
|
+
const cachedRef = storeCache.get(prop);
|
|
3659
|
+
if (cachedRef) {
|
|
3660
|
+
const cached = cachedRef.deref();
|
|
3661
|
+
if (cached)
|
|
3662
|
+
return cached;
|
|
3663
|
+
storeCache.delete(prop);
|
|
3664
|
+
PROXY_CLEANUP.unregister(cachedRef);
|
|
3665
|
+
}
|
|
3666
|
+
const proxy = build();
|
|
3667
|
+
const ref = new WeakRef(proxy);
|
|
3668
|
+
storeCache.set(prop, ref);
|
|
3669
|
+
PROXY_CLEANUP.register(proxy, { target, prop }, ref);
|
|
3670
|
+
return proxy;
|
|
3386
3671
|
}
|
|
3387
3672
|
/**
|
|
3388
|
-
* @internal
|
|
3389
|
-
*
|
|
3673
|
+
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3674
|
+
* A record parent reads the key directly; any other container goes through the fallback `from`/
|
|
3675
|
+
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3676
|
+
* is constructed.
|
|
3390
3677
|
*/
|
|
3391
|
-
function
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
const
|
|
3395
|
-
const
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const v = untracked(source);
|
|
3415
|
-
if (!Array.isArray(v))
|
|
3416
|
-
return [];
|
|
3417
|
-
const len = v.length;
|
|
3418
|
-
const arr = new Array(len + 1);
|
|
3419
|
-
for (let i = 0; i < len; i++) {
|
|
3420
|
-
arr[i] = String(i);
|
|
3421
|
-
}
|
|
3422
|
-
arr[len] = 'length';
|
|
3423
|
-
return arr;
|
|
3424
|
-
},
|
|
3425
|
-
getPrototypeOf() {
|
|
3426
|
-
return Array.prototype;
|
|
3427
|
-
},
|
|
3428
|
-
getOwnPropertyDescriptor(_, prop) {
|
|
3429
|
-
const v = untracked(source);
|
|
3430
|
-
if (!Array.isArray(v))
|
|
3431
|
-
return;
|
|
3432
|
-
if (prop === 'length' ||
|
|
3433
|
-
(typeof prop === 'string' && !isNaN(+prop) && +prop < v.length)) {
|
|
3434
|
-
return {
|
|
3435
|
-
enumerable: true,
|
|
3436
|
-
configurable: true, // Required for proxies to dynamic targets
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
return;
|
|
3440
|
-
},
|
|
3441
|
-
get(target, prop, receiver) {
|
|
3442
|
-
if (prop === IS_STORE)
|
|
3443
|
-
return true;
|
|
3444
|
-
if (prop === 'length')
|
|
3445
|
-
return lengthSignal;
|
|
3446
|
-
if (prop === Symbol.iterator) {
|
|
3447
|
-
return function* () {
|
|
3448
|
-
// read length reactively: a spread/for-of inside a computed/effect must re-run
|
|
3449
|
-
// when items are added or removed, not only when already-read elements change
|
|
3450
|
-
for (let i = 0; i < lengthSignal(); i++) {
|
|
3451
|
-
yield receiver[i];
|
|
3452
|
-
}
|
|
3453
|
-
};
|
|
3454
|
-
}
|
|
3455
|
-
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3456
|
-
return target[prop];
|
|
3457
|
-
if (isIndexProp(prop)) {
|
|
3458
|
-
const idx = +prop;
|
|
3459
|
-
let storeCache = PROXY_CACHE.get(target);
|
|
3460
|
-
if (!storeCache) {
|
|
3461
|
-
storeCache = new Map();
|
|
3462
|
-
PROXY_CACHE.set(target, storeCache);
|
|
3463
|
-
}
|
|
3464
|
-
const cachedRef = storeCache.get(idx);
|
|
3465
|
-
if (cachedRef) {
|
|
3466
|
-
const cached = cachedRef.deref();
|
|
3467
|
-
if (cached)
|
|
3468
|
-
return cached;
|
|
3469
|
-
storeCache.delete(idx);
|
|
3470
|
-
PROXY_CLEANUP.unregister(cachedRef);
|
|
3471
|
-
}
|
|
3472
|
-
const value = untracked(target);
|
|
3473
|
-
const valueIsArray = Array.isArray(value);
|
|
3474
|
-
const valueIsRecord = isRecord(value);
|
|
3475
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3476
|
-
const vivifyFn = createVivify(nodeVivify);
|
|
3477
|
-
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3478
|
-
isMutableSource &&
|
|
3479
|
-
typeof value[idx] === 'object'
|
|
3480
|
-
? () => false
|
|
3481
|
-
: undefined;
|
|
3482
|
-
const computation = valueIsRecord
|
|
3483
|
-
? derived(target, idx, {
|
|
3484
|
-
equal: equalFn,
|
|
3485
|
-
vivify: nodeVivify,
|
|
3486
|
-
})
|
|
3487
|
-
: derived(target, {
|
|
3488
|
-
from: (v) => v?.[idx],
|
|
3489
|
-
onChange: createFallbackOnChange(target, idx, vivifyFn, isMutableSource),
|
|
3490
|
-
equal: equalFn,
|
|
3491
|
-
});
|
|
3492
|
-
const childSample = untracked(computation);
|
|
3493
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3494
|
-
const proxy = Array.isArray(childSample) && !isOpaque(childSample)
|
|
3495
|
-
? toArrayStore(computation, injector, childVivify, noUnionLeaves)
|
|
3496
|
-
: toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3497
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3498
|
-
const ref = new WeakRef(proxy);
|
|
3499
|
-
storeCache.set(idx, ref);
|
|
3500
|
-
PROXY_CLEANUP.register(proxy, { target, prop: idx }, ref);
|
|
3501
|
-
return proxy;
|
|
3502
|
-
}
|
|
3503
|
-
return Reflect.get(target, prop, receiver);
|
|
3504
|
-
},
|
|
3505
|
-
});
|
|
3678
|
+
function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnionLeaves) {
|
|
3679
|
+
const value = untracked(target);
|
|
3680
|
+
const valueIsRecord = isRecord(value);
|
|
3681
|
+
const valueIsArray = Array.isArray(value);
|
|
3682
|
+
const nodeVivify = resolveVivify(value, vivify);
|
|
3683
|
+
const vivifyFn = createVivify(nodeVivify);
|
|
3684
|
+
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3685
|
+
isMutableSource &&
|
|
3686
|
+
typeof value[prop] === 'object'
|
|
3687
|
+
? () => false
|
|
3688
|
+
: undefined;
|
|
3689
|
+
const computation = valueIsRecord
|
|
3690
|
+
? derived(target, prop, { equal: equalFn, vivify: nodeVivify })
|
|
3691
|
+
: derived(target, {
|
|
3692
|
+
from: (v) => v?.[prop],
|
|
3693
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3694
|
+
equal: equalFn,
|
|
3695
|
+
});
|
|
3696
|
+
const childSample = untracked(computation);
|
|
3697
|
+
const childVivify = resolveVivify(childSample, vivify);
|
|
3698
|
+
const proxy = toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3699
|
+
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3700
|
+
return proxy;
|
|
3506
3701
|
}
|
|
3507
3702
|
/**
|
|
3508
3703
|
* Converts a Signal into a deep-observable Store.
|
|
3509
3704
|
* Accessing nested properties returns a derived Signal of that path.
|
|
3510
3705
|
*
|
|
3511
3706
|
* @remarks
|
|
3512
|
-
* A
|
|
3513
|
-
*
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
3707
|
+
* A node's *container kind* (array / record / primitive) is tracked reactively via a per-node
|
|
3708
|
+
* `kind` computed, so the same proxy serves all three and a union node that flips between an
|
|
3709
|
+
* array and a record keeps working. Flips are route-forward: after a flip the node behaves as
|
|
3710
|
+
* its new kind on the next access, while child proxies cached under the old shape go stale and
|
|
3711
|
+
* are pruned by the GC.
|
|
3516
3712
|
*
|
|
3517
3713
|
* @example
|
|
3518
3714
|
* const state = store({ user: { name: 'John' } });
|
|
@@ -3530,92 +3726,114 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3530
3726
|
});
|
|
3531
3727
|
const isWritableSource = isWritableSignal(source);
|
|
3532
3728
|
const isMutableSource = isWritableSource && isMutable(writableSource);
|
|
3729
|
+
const kind = computed(() => {
|
|
3730
|
+
const v = source();
|
|
3731
|
+
if (Array.isArray(v) && !isOpaque(v))
|
|
3732
|
+
return 'array';
|
|
3733
|
+
if (isRecord(v))
|
|
3734
|
+
return 'record';
|
|
3735
|
+
return 'primitive';
|
|
3736
|
+
}, ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3737
|
+
// built lazily so non-array nodes never allocate it
|
|
3738
|
+
let length;
|
|
3739
|
+
const arrayLength = () => (length ??= computed(() => {
|
|
3740
|
+
const v = source();
|
|
3741
|
+
return Array.isArray(v) ? v.length : 0;
|
|
3742
|
+
}));
|
|
3533
3743
|
const s = new Proxy(writableSource, {
|
|
3534
3744
|
has(_, prop) {
|
|
3535
|
-
|
|
3745
|
+
const v = untracked(source);
|
|
3746
|
+
if (untracked(kind) === 'array') {
|
|
3747
|
+
if (prop === 'length')
|
|
3748
|
+
return true;
|
|
3749
|
+
if (isIndexProp(prop)) {
|
|
3750
|
+
const idx = +prop;
|
|
3751
|
+
return idx >= 0 && idx < v.length;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
// nullish node values are routinely descended with vivify on — `in` must not throw
|
|
3755
|
+
return v == null ? false : Reflect.has(v, prop);
|
|
3536
3756
|
},
|
|
3537
3757
|
ownKeys() {
|
|
3538
3758
|
const v = untracked(source);
|
|
3759
|
+
if (untracked(kind) === 'array') {
|
|
3760
|
+
const len = v.length;
|
|
3761
|
+
const arr = new Array(len + 1);
|
|
3762
|
+
for (let i = 0; i < len; i++)
|
|
3763
|
+
arr[i] = String(i);
|
|
3764
|
+
arr[len] = 'length';
|
|
3765
|
+
return arr;
|
|
3766
|
+
}
|
|
3539
3767
|
if (!isRecord(v))
|
|
3540
3768
|
return [];
|
|
3541
3769
|
return Reflect.ownKeys(v);
|
|
3542
3770
|
},
|
|
3543
3771
|
getPrototypeOf() {
|
|
3544
|
-
|
|
3772
|
+
if (untracked(kind) === 'array')
|
|
3773
|
+
return Array.prototype;
|
|
3774
|
+
const v = untracked(source);
|
|
3775
|
+
return v == null ? Object.prototype : Object.getPrototypeOf(v);
|
|
3545
3776
|
},
|
|
3546
3777
|
getOwnPropertyDescriptor(_, prop) {
|
|
3547
|
-
const
|
|
3548
|
-
if (
|
|
3778
|
+
const v = untracked(source);
|
|
3779
|
+
if (untracked(kind) === 'array') {
|
|
3780
|
+
if (prop === 'length' ||
|
|
3781
|
+
(typeof prop === 'string' && !isNaN(+prop) && +prop < v.length))
|
|
3782
|
+
return { enumerable: true, configurable: true };
|
|
3549
3783
|
return;
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
};
|
|
3784
|
+
}
|
|
3785
|
+
if (!isRecord(v) || !(prop in v))
|
|
3786
|
+
return;
|
|
3787
|
+
return { enumerable: true, configurable: true };
|
|
3554
3788
|
},
|
|
3555
|
-
get(target, prop) {
|
|
3789
|
+
get(target, prop, receiver) {
|
|
3556
3790
|
if (prop === IS_STORE)
|
|
3557
3791
|
return true;
|
|
3792
|
+
if (prop === STORE_KIND)
|
|
3793
|
+
return isMutableSource
|
|
3794
|
+
? 'mutable'
|
|
3795
|
+
: isWritableSource
|
|
3796
|
+
? 'writable'
|
|
3797
|
+
: 'readonly';
|
|
3798
|
+
if (prop === STORE_INJECTOR)
|
|
3799
|
+
return injector;
|
|
3558
3800
|
if (prop === 'asReadonlyStore')
|
|
3559
3801
|
return () => {
|
|
3560
3802
|
if (!isWritableSource)
|
|
3561
3803
|
return s;
|
|
3562
3804
|
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3563
3805
|
};
|
|
3564
|
-
|
|
3806
|
+
const k = untracked(kind);
|
|
3807
|
+
if (prop === 'extend' && k !== 'array')
|
|
3565
3808
|
return (seed) => scopedStore(s, seed, isMutableSource
|
|
3566
3809
|
? 'mutable'
|
|
3567
3810
|
: isWritableSource
|
|
3568
3811
|
? 'writable'
|
|
3569
3812
|
: 'readonly', injector);
|
|
3813
|
+
if (k === 'array') {
|
|
3814
|
+
if (prop === 'length')
|
|
3815
|
+
return arrayLength();
|
|
3816
|
+
if (prop === Symbol.iterator)
|
|
3817
|
+
return function* () {
|
|
3818
|
+
// read length reactively: a spread/for-of inside a computed/effect must re-run
|
|
3819
|
+
// when items are added or removed, not only when already-read elements change
|
|
3820
|
+
const len = arrayLength();
|
|
3821
|
+
for (let i = 0; i < len(); i++)
|
|
3822
|
+
yield receiver[i];
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3570
3825
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3571
3826
|
return target[prop];
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
PROXY_CACHE.set(target, storeCache);
|
|
3576
|
-
}
|
|
3577
|
-
const cachedRef = storeCache.get(prop);
|
|
3578
|
-
if (cachedRef) {
|
|
3579
|
-
const cached = cachedRef.deref();
|
|
3580
|
-
if (cached)
|
|
3581
|
-
return cached;
|
|
3582
|
-
storeCache.delete(prop);
|
|
3583
|
-
PROXY_CLEANUP.unregister(cachedRef);
|
|
3584
|
-
}
|
|
3585
|
-
const value = untracked(target);
|
|
3586
|
-
const valueIsRecord = isRecord(value);
|
|
3587
|
-
const valueIsArray = Array.isArray(value);
|
|
3588
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3589
|
-
const vivifyFn = createVivify(nodeVivify);
|
|
3590
|
-
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3591
|
-
isMutableSource &&
|
|
3592
|
-
typeof value[prop] === 'object'
|
|
3593
|
-
? () => false
|
|
3594
|
-
: undefined;
|
|
3595
|
-
const computation = valueIsRecord
|
|
3596
|
-
? derived(target, prop, { equal: equalFn, vivify: nodeVivify })
|
|
3597
|
-
: derived(target, {
|
|
3598
|
-
from: (v) => v?.[prop],
|
|
3599
|
-
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3600
|
-
equal: equalFn,
|
|
3601
|
-
});
|
|
3602
|
-
const childSample = untracked(computation);
|
|
3603
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3604
|
-
const proxy = Array.isArray(childSample) && !isOpaque(childSample)
|
|
3605
|
-
? toArrayStore(computation, injector, childVivify, noUnionLeaves)
|
|
3606
|
-
: toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3607
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3608
|
-
const ref = new WeakRef(proxy);
|
|
3609
|
-
storeCache.set(prop, ref);
|
|
3610
|
-
PROXY_CLEANUP.register(proxy, { target, prop }, ref);
|
|
3611
|
-
return proxy;
|
|
3827
|
+
if (k === 'array' && !isIndexProp(prop))
|
|
3828
|
+
return Reflect.get(target, prop, receiver);
|
|
3829
|
+
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource, injector, vivify, noUnionLeaves));
|
|
3612
3830
|
},
|
|
3613
3831
|
});
|
|
3614
3832
|
return s;
|
|
3615
3833
|
}
|
|
3616
3834
|
/**
|
|
3617
3835
|
* @internal
|
|
3618
|
-
* Backs `
|
|
3836
|
+
* Backs `extendStore(...)`. Builds a scoped overlay over `parent`: the local layer (the seed
|
|
3619
3837
|
* plus any keys created later) is its own signal and `parent` is its own signal, so the getter
|
|
3620
3838
|
* routes each key by consulting BOTH — local first, then parent, else local (so a write to an
|
|
3621
3839
|
* as-yet-unknown key lands locally). Inherited keys return the parent's own sub-store (shared
|
|
@@ -3662,6 +3880,10 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3662
3880
|
get(target, prop) {
|
|
3663
3881
|
if (prop === IS_STORE)
|
|
3664
3882
|
return true;
|
|
3883
|
+
if (prop === STORE_KIND)
|
|
3884
|
+
return kind;
|
|
3885
|
+
if (prop === STORE_INJECTOR)
|
|
3886
|
+
return injector;
|
|
3665
3887
|
if (prop === SCOPE_PARENT)
|
|
3666
3888
|
return parent;
|
|
3667
3889
|
if (prop === 'extend')
|
|
@@ -3694,6 +3916,28 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3694
3916
|
});
|
|
3695
3917
|
return scope;
|
|
3696
3918
|
}
|
|
3919
|
+
/** @internal Reads a store's writability brand, falling back to signal inspection if unbranded. */
|
|
3920
|
+
function storeKind(s) {
|
|
3921
|
+
return (s[STORE_KIND] ??
|
|
3922
|
+
(isWritableSignal(s) ? (isMutable(s) ? 'mutable' : 'writable') : 'readonly'));
|
|
3923
|
+
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Extends a store with extra keys via a scoped overlay, returning a new store that reads through
|
|
3926
|
+
* to the parent for inherited keys (shared identity + two-way) while holding the new keys locally.
|
|
3927
|
+
*
|
|
3928
|
+
* The typesafe successor to the deprecated `store.extend(...)` method — moving it off the proxy
|
|
3929
|
+
* frees the `extend` key for use as a normal record key. Writability (readonly/writable/mutable)
|
|
3930
|
+
* is inherited from `store`.
|
|
3931
|
+
*
|
|
3932
|
+
* @example
|
|
3933
|
+
* const base = store({ count: 0 });
|
|
3934
|
+
* const scoped = extendStore(base, { label: 'live' });
|
|
3935
|
+
* scoped.count.set(1); // writes through to base
|
|
3936
|
+
* scoped.label.set('x'); // stays local
|
|
3937
|
+
*/
|
|
3938
|
+
function extendStore(store, source, injector) {
|
|
3939
|
+
return scopedStore(store, source, storeKind(store), injector ?? store[STORE_INJECTOR] ?? inject(Injector));
|
|
3940
|
+
}
|
|
3697
3941
|
/**
|
|
3698
3942
|
* Creates a WritableSignalStore from a value.
|
|
3699
3943
|
* @see {@link toStore}
|
|
@@ -3776,6 +4020,26 @@ function forkStore(base, opt) {
|
|
|
3776
4020
|
};
|
|
3777
4021
|
}
|
|
3778
4022
|
|
|
4023
|
+
/**
|
|
4024
|
+
* @internal The plain-`effect` sibling of the public {@link pausableEffect} (which is built on
|
|
4025
|
+
* `nestedEffect`). For infra utilities that own a single top-level effect/subscription and don't
|
|
4026
|
+
* need frame/nesting semantics. Opt-in (default off): with no `pause` (call site or
|
|
4027
|
+
* `providePausableOptions` default) it returns a bare `effect` (zero overhead, byte-identical to
|
|
4028
|
+
* today); otherwise it gates the body on the resolved predicate — read FIRST so the dependency set
|
|
4029
|
+
* collapses to just the predicate while paused, re-tracking on resume. Deliberately NOT re-exported
|
|
4030
|
+
* from the public barrel.
|
|
4031
|
+
*/
|
|
4032
|
+
function pausablePureEffect(effectFn, options) {
|
|
4033
|
+
const paused = resolvePause(options, false);
|
|
4034
|
+
if (!paused)
|
|
4035
|
+
return effect(effectFn, options);
|
|
4036
|
+
return effect((registerCleanup) => {
|
|
4037
|
+
if (paused())
|
|
4038
|
+
return;
|
|
4039
|
+
effectFn(registerCleanup);
|
|
4040
|
+
}, options);
|
|
4041
|
+
}
|
|
4042
|
+
|
|
3779
4043
|
// Internal dummy store for server-side rendering
|
|
3780
4044
|
const noopStore = {
|
|
3781
4045
|
getItem: () => null,
|
|
@@ -3837,8 +4101,9 @@ const noopStore = {
|
|
|
3837
4101
|
* }
|
|
3838
4102
|
* ```
|
|
3839
4103
|
*/
|
|
3840
|
-
function stored(fallback, { key, store: providedStore, serialize = JSON.stringify, deserialize = JSON.parse, syncTabs = false, equal = Object.is, onKeyChange = 'load', cleanupOldKey = false, validate = () => true, ...rest }) {
|
|
3841
|
-
const
|
|
4104
|
+
function stored(fallback, { key, store: providedStore, serialize = JSON.stringify, deserialize = JSON.parse, syncTabs = false, equal = Object.is, onKeyChange = 'load', cleanupOldKey = false, validate = () => true, pause, injector: providedInjector, ...rest }) {
|
|
4105
|
+
const injector = providedInjector ?? inject(Injector);
|
|
4106
|
+
const isServer = isPlatformServer(injector.get(PLATFORM_ID));
|
|
3842
4107
|
const fallbackStore = isServer ? noopStore : localStorage;
|
|
3843
4108
|
const store = providedStore ?? fallbackStore;
|
|
3844
4109
|
const keySig = typeof key === 'string'
|
|
@@ -3846,8 +4111,6 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3846
4111
|
: isSignal(key)
|
|
3847
4112
|
? key
|
|
3848
4113
|
: computed(key);
|
|
3849
|
-
// "no stored value" marker — distinct from `null`/`undefined`, so a nullable `T` can
|
|
3850
|
-
// round-trip a legitimate `null` through `set` instead of it acting like `clear()`
|
|
3851
4114
|
const EMPTY = Symbol();
|
|
3852
4115
|
const getValue = (key) => {
|
|
3853
4116
|
const found = store.getItem(key);
|
|
@@ -3892,7 +4155,7 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3892
4155
|
} });
|
|
3893
4156
|
let prevKey = initialKey;
|
|
3894
4157
|
if (onKeyChange === 'store') {
|
|
3895
|
-
|
|
4158
|
+
pausablePureEffect(() => {
|
|
3896
4159
|
const k = keySig();
|
|
3897
4160
|
storeValue(k, internal());
|
|
3898
4161
|
if (prevKey !== k) {
|
|
@@ -3900,10 +4163,10 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3900
4163
|
store.removeItem(prevKey);
|
|
3901
4164
|
prevKey = k;
|
|
3902
4165
|
}
|
|
3903
|
-
});
|
|
4166
|
+
}, { injector, pause });
|
|
3904
4167
|
}
|
|
3905
4168
|
else {
|
|
3906
|
-
|
|
4169
|
+
pausablePureEffect(() => {
|
|
3907
4170
|
const k = keySig();
|
|
3908
4171
|
const internalValue = internal();
|
|
3909
4172
|
if (k === prevKey) {
|
|
@@ -3916,14 +4179,11 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3916
4179
|
prevKey = k;
|
|
3917
4180
|
internal.set(value); // load new value
|
|
3918
4181
|
}
|
|
3919
|
-
});
|
|
4182
|
+
}, { injector, pause });
|
|
3920
4183
|
}
|
|
3921
4184
|
if (syncTabs && !isServer) {
|
|
3922
|
-
const destroyRef =
|
|
4185
|
+
const destroyRef = injector.get(DestroyRef);
|
|
3923
4186
|
const sync = (e) => {
|
|
3924
|
-
// `storage` events only describe Web Storage — ignore events for a different
|
|
3925
|
-
// storage area (or any event when a custom adapter is configured), otherwise an
|
|
3926
|
-
// unrelated localStorage write with the same key string corrupts our state
|
|
3927
4187
|
if (e.storageArea !== store)
|
|
3928
4188
|
return;
|
|
3929
4189
|
if (e.key !== untracked(keySig))
|
|
@@ -4052,29 +4312,26 @@ function generateDeterministicID() {
|
|
|
4052
4312
|
*
|
|
4053
4313
|
*/
|
|
4054
4314
|
function tabSync(sig, opt) {
|
|
4055
|
-
|
|
4315
|
+
const optObj = typeof opt === 'object' ? opt : undefined;
|
|
4316
|
+
const injector = optObj?.injector ?? inject(Injector);
|
|
4317
|
+
if (isPlatformServer(injector.get(PLATFORM_ID)))
|
|
4056
4318
|
return sig;
|
|
4057
4319
|
const id = typeof opt === 'string' ? opt : (opt?.id ?? generateDeterministicID());
|
|
4058
|
-
const bus =
|
|
4059
|
-
// The last value applied from a remote tab. The outbound effect skips (exactly) the run
|
|
4060
|
-
// caused by that write — without this, an inbound object (a fresh structured clone, so
|
|
4061
|
-
// never reference-equal) would be re-posted, and two tabs would ping-pong forever.
|
|
4320
|
+
const bus = injector.get(MessageBus);
|
|
4062
4321
|
const NONE = Symbol();
|
|
4063
4322
|
let received = NONE;
|
|
4064
4323
|
const { unsub, post } = bus.subscribe(id, (next) => {
|
|
4065
4324
|
const before = untracked(sig);
|
|
4066
4325
|
received = next;
|
|
4067
4326
|
sig.set(next);
|
|
4068
|
-
// Equality-suppressed write (e.g. an identical primitive): no effect run will follow,
|
|
4069
|
-
// so clear the marker — it must not swallow a later, genuinely local change.
|
|
4070
4327
|
if (untracked(sig) === before)
|
|
4071
4328
|
received = NONE;
|
|
4072
4329
|
});
|
|
4073
|
-
let
|
|
4330
|
+
let firstDone = false;
|
|
4074
4331
|
const effectRef = effect(() => {
|
|
4075
4332
|
const val = sig();
|
|
4076
|
-
if (!
|
|
4077
|
-
|
|
4333
|
+
if (!firstDone) {
|
|
4334
|
+
firstDone = true;
|
|
4078
4335
|
return;
|
|
4079
4336
|
}
|
|
4080
4337
|
if (val === received) {
|
|
@@ -4083,8 +4340,8 @@ function tabSync(sig, opt) {
|
|
|
4083
4340
|
}
|
|
4084
4341
|
received = NONE;
|
|
4085
4342
|
post(val);
|
|
4086
|
-
}, ...(ngDevMode ?
|
|
4087
|
-
|
|
4343
|
+
}, { ...(ngDevMode ? { debugName: "effectRef" } : /* istanbul ignore next */ {}), injector });
|
|
4344
|
+
injector.get(DestroyRef).onDestroy(() => {
|
|
4088
4345
|
effectRef.destroy();
|
|
4089
4346
|
unsub();
|
|
4090
4347
|
});
|
|
@@ -4288,5 +4545,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4288
4545
|
* Generated bundle index. Do not edit.
|
|
4289
4546
|
*/
|
|
4290
4547
|
|
|
4291
|
-
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 };
|
|
4548
|
+
export { MmActivity, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, extendStore, 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, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
4292
4549
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|