@mmstack/primitives 22.2.0 → 22.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 +613 -357
- 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. */
|
|
@@ -269,6 +206,8 @@ class MmActivity {
|
|
|
269
206
|
}),
|
|
270
207
|
});
|
|
271
208
|
}
|
|
209
|
+
if (this.onServer)
|
|
210
|
+
return;
|
|
272
211
|
for (const node of this.view.rootNodes) {
|
|
273
212
|
// covers HTML and SVG roots; text/comment roots can't be styled — their CD is still
|
|
274
213
|
// detached, but prefer an element root for true visual hiding
|
|
@@ -313,24 +252,23 @@ function providePaused(source) {
|
|
|
313
252
|
}
|
|
314
253
|
|
|
315
254
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
*
|
|
323
|
-
*
|
|
255
|
+
* @internal Token carrying an app-wide default {@link PauseOption}, set via
|
|
256
|
+
* {@link providePausableOptions}. {@link resolvePause} consults it when the call site didn't
|
|
257
|
+
* specify `pause`, so users can opt every pausable-aware primitive in (or out) from one place.
|
|
258
|
+
*/
|
|
259
|
+
const PAUSABLE_OPTIONS = new InjectionToken('@mmstack/primitives:pausable-options');
|
|
260
|
+
/**
|
|
261
|
+
* Provides an app-wide default {@link PauseOption} for every pausable-aware primitive (the public
|
|
262
|
+
* `pausable*` family plus the opt-in integrations like `stored` / `chunked`). A call-site `pause`
|
|
263
|
+
* always wins; this only fills in when the call didn't specify one.
|
|
324
264
|
*
|
|
325
|
-
*
|
|
265
|
+
* @example
|
|
266
|
+
* // Make everything that can pause honour the ambient Activity boundary by default:
|
|
267
|
+
* providePausableOptions({ pause: true })
|
|
326
268
|
*/
|
|
327
|
-
function
|
|
328
|
-
return
|
|
329
|
-
source: () => ({ t: target(), ready: ready() }),
|
|
330
|
-
computation: (curr, prev) => (prev === undefined || curr.ready ? curr.t : prev.value),
|
|
331
|
-
});
|
|
269
|
+
function providePausableOptions(opt) {
|
|
270
|
+
return { provide: PAUSABLE_OPTIONS, useValue: opt };
|
|
332
271
|
}
|
|
333
|
-
|
|
334
272
|
/**
|
|
335
273
|
* Resolve a {@link PauseOption} into a pause predicate, or `null` meaning "do not pause".
|
|
336
274
|
* `null` tells the caller to return the bare primitive — no wrapper is created.
|
|
@@ -345,11 +283,7 @@ function holdUntilReady(target, ready) {
|
|
|
345
283
|
*
|
|
346
284
|
* Encapsulating this here keeps every pausable primitive's branching identical and in one place.
|
|
347
285
|
*/
|
|
348
|
-
function resolvePause(opt) {
|
|
349
|
-
const explicit = opt?.pause; // distinguish explicit `true` from the omitted default
|
|
350
|
-
const pause = explicit ?? true; // explicit pausable* calls default to pausing
|
|
351
|
-
if (pause === false)
|
|
352
|
-
return null;
|
|
286
|
+
function resolvePause(opt, defaultPause = true) {
|
|
353
287
|
const run = (fn) => opt?.injector ? runInInjectionContext(opt.injector, fn) : fn();
|
|
354
288
|
// `inject` requires an injection context even with `optional: true`. A bare
|
|
355
289
|
// `pausableSignal(0)` (documented as "like `signal`") must degrade to the unwrapped
|
|
@@ -362,6 +296,12 @@ function resolvePause(opt) {
|
|
|
362
296
|
return fallback;
|
|
363
297
|
}
|
|
364
298
|
};
|
|
299
|
+
// A `providePausableOptions(...)` default fills in when the call site didn't specify `pause`.
|
|
300
|
+
const providedPause = tryRun(() => inject(PAUSABLE_OPTIONS, { optional: true })?.pause, undefined);
|
|
301
|
+
const explicit = opt?.pause ?? providedPause;
|
|
302
|
+
const pause = explicit ?? defaultPause; // public pausable* default `true`; opt-in integrations `false`
|
|
303
|
+
if (pause === false)
|
|
304
|
+
return null;
|
|
365
305
|
const onServer = () => typeof pause === 'function' && !opt?.injector
|
|
366
306
|
? typeof globalThis.window === 'undefined'
|
|
367
307
|
: tryRun(() => isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser'), typeof globalThis.window === 'undefined');
|
|
@@ -371,7 +311,7 @@ function resolvePause(opt) {
|
|
|
371
311
|
return null;
|
|
372
312
|
const paused = tryRun(() => inject(PAUSED_CONTEXT, { optional: true }), null);
|
|
373
313
|
if (!paused) {
|
|
374
|
-
if (
|
|
314
|
+
if (opt?.pause === true && isDevMode())
|
|
375
315
|
console.warn('[pausable] `pause: true` but no PAUSED_CONTEXT in scope — not pausing. Provide one via an ' +
|
|
376
316
|
'Activity boundary (`MmActivity` / `providePaused`), or pass a predicate / `pause: false`.');
|
|
377
317
|
return null;
|
|
@@ -439,6 +379,92 @@ function pausableComputed(computation, options) {
|
|
|
439
379
|
return ls.asReadonly();
|
|
440
380
|
}
|
|
441
381
|
|
|
382
|
+
/**
|
|
383
|
+
* 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.
|
|
384
|
+
*
|
|
385
|
+
* 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`.
|
|
386
|
+
*
|
|
387
|
+
* @template T The type of items in the array.
|
|
388
|
+
* @param source A `Signal` or a function that returns an array of items to be processed in chunks.
|
|
389
|
+
* @param options Configuration options for chunk size, delay duration, equality function, and injector.
|
|
390
|
+
* @returns A `Signal` that emits the current chunk of items being processed.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* const largeList = signal(Array.from({ length: 1000 }, (_, i) => i));
|
|
394
|
+
* const chunkedList = chunked(largeList, { chunkSize: 100, delay: 100 });
|
|
395
|
+
*/
|
|
396
|
+
function chunked(source, options) {
|
|
397
|
+
const { chunkSize = 50, delay = 'frame', equal, injector, pause, } = options || {};
|
|
398
|
+
let delayFn;
|
|
399
|
+
if (delay === 'frame') {
|
|
400
|
+
delayFn =
|
|
401
|
+
typeof requestAnimationFrame === 'function'
|
|
402
|
+
? (callback) => {
|
|
403
|
+
const num = requestAnimationFrame(callback);
|
|
404
|
+
return () => cancelAnimationFrame(num);
|
|
405
|
+
}
|
|
406
|
+
: // SSR: no requestAnimationFrame — approximate a frame with a timeout
|
|
407
|
+
(cb) => {
|
|
408
|
+
const num = setTimeout(cb, 16);
|
|
409
|
+
return () => clearTimeout(num);
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
else if (delay === 'microtask') {
|
|
413
|
+
delayFn = (cb) => {
|
|
414
|
+
let isCancelled = false;
|
|
415
|
+
queueMicrotask(() => {
|
|
416
|
+
if (isCancelled)
|
|
417
|
+
return;
|
|
418
|
+
cb();
|
|
419
|
+
});
|
|
420
|
+
return () => {
|
|
421
|
+
isCancelled = true;
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
delayFn = (cb) => {
|
|
427
|
+
const num = setTimeout(cb, delay);
|
|
428
|
+
return () => clearTimeout(num);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const internal = linkedSignal({ ...(ngDevMode ? { debugName: "internal" } : /* istanbul ignore next */ {}), source,
|
|
432
|
+
computation: (items) => items.slice(0, chunkSize),
|
|
433
|
+
equal });
|
|
434
|
+
const paused = resolvePause({ injector, pause }, false);
|
|
435
|
+
nestedEffect((cleanup) => {
|
|
436
|
+
if (paused?.())
|
|
437
|
+
return;
|
|
438
|
+
const fullList = source();
|
|
439
|
+
const current = internal();
|
|
440
|
+
if (current.length >= fullList.length)
|
|
441
|
+
return;
|
|
442
|
+
return cleanup(delayFn(() => untracked(() => internal.set(fullList.slice(0, current.length + chunkSize)))));
|
|
443
|
+
}, {
|
|
444
|
+
injector,
|
|
445
|
+
});
|
|
446
|
+
return internal.asReadonly();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Structural hold-and-swap as a signal. Given a `target` (the desired value — e.g. the
|
|
451
|
+
* subtree/def/key you want to show) and a `ready` predicate, returns a signal that keeps
|
|
452
|
+
* yielding its PREVIOUS value until `ready()` is true, then swaps to the current target.
|
|
453
|
+
*
|
|
454
|
+
* This is the structural counterpart to `keepPrevious`/`commit`: where those hold a *value*
|
|
455
|
+
* through a reload, this holds a *structure* through a swap. The caller mounts the incoming
|
|
456
|
+
* structure off to the side (so its resources can settle and flip `ready`), keeps showing the
|
|
457
|
+
* held previous structure meanwhile, and lets the old one go once `ready` releases the swap.
|
|
458
|
+
*
|
|
459
|
+
* The very first value passes straight through (nothing to hold yet).
|
|
460
|
+
*/
|
|
461
|
+
function holdUntilReady(target, ready) {
|
|
462
|
+
return linkedSignal({
|
|
463
|
+
source: () => ({ t: target(), ready: ready() }),
|
|
464
|
+
computation: (curr, prev) => (prev === undefined || curr.ready ? curr.t : prev.value),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
442
468
|
const { is } = Object;
|
|
443
469
|
function mutable(initial, opt) {
|
|
444
470
|
const baseEqual = opt?.equal ?? is;
|
|
@@ -644,6 +670,7 @@ function registerResource(res, opt) {
|
|
|
644
670
|
function injectStartTransition() {
|
|
645
671
|
const scope = injectTransitionScope();
|
|
646
672
|
const injector = inject(Injector);
|
|
673
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
647
674
|
return (fn) => {
|
|
648
675
|
untracked(fn);
|
|
649
676
|
let sawPending = false;
|
|
@@ -658,6 +685,13 @@ function injectStartTransition() {
|
|
|
658
685
|
resolve();
|
|
659
686
|
}
|
|
660
687
|
}, { ...(ngDevMode ? { debugName: "watcher" } : /* istanbul ignore next */ {}), injector });
|
|
688
|
+
if (onServer) {
|
|
689
|
+
if (!untracked(scope.pending)) {
|
|
690
|
+
watcher.destroy();
|
|
691
|
+
resolve();
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
661
695
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
662
696
|
// if nothing ever went in flight, the transition is already complete.
|
|
663
697
|
afterNextRender(() => {
|
|
@@ -685,6 +719,12 @@ function injectStartTransition() {
|
|
|
685
719
|
*
|
|
686
720
|
* `type` selects what "not ready" means: `'value'` (default) suspends only until a first value lands
|
|
687
721
|
* then holds through reloads; `'loading'` suspends on every in-flight load (strict suspense).
|
|
722
|
+
*
|
|
723
|
+
* SSR: the server serializes whatever the scope reports at stabilization, so a registered resource
|
|
724
|
+
* must keep the app unstable until it settles or the placeholder is what gets serialized (then
|
|
725
|
+
* flashes/mismatches on hydration). HttpClient-backed resources, httpResource & all of `@mmstack/resource`
|
|
726
|
+
* do this automatically via the HTTP layer's `PendingTasks` + transfer cache. A custom loader (raw
|
|
727
|
+
* `fetch`/promise/timer) must opt in itself: wrap it with `inject(PendingTasks).run(() => promise)`.
|
|
688
728
|
*/
|
|
689
729
|
class SuspenseBoundaryBase {
|
|
690
730
|
scope = injectTransitionScope();
|
|
@@ -800,6 +840,7 @@ function runInTransaction(txn, fn) {
|
|
|
800
840
|
function injectStartTransaction() {
|
|
801
841
|
const scope = injectTransitionScope();
|
|
802
842
|
const injector = inject(Injector);
|
|
843
|
+
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
803
844
|
return (fn) => {
|
|
804
845
|
const txn = createTransaction();
|
|
805
846
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
@@ -841,11 +882,17 @@ function injectStartTransaction() {
|
|
|
841
882
|
if (sawPending && !p)
|
|
842
883
|
finish(false);
|
|
843
884
|
}, { injector });
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
if (!sawPending && !untracked(scope.pending))
|
|
885
|
+
if (onServer) {
|
|
886
|
+
if (!untracked(scope.pending))
|
|
847
887
|
finish(false);
|
|
848
|
-
}
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
891
|
+
afterNextRender(() => {
|
|
892
|
+
if (!sawPending && !untracked(scope.pending))
|
|
893
|
+
finish(false);
|
|
894
|
+
}, { injector });
|
|
895
|
+
}
|
|
849
896
|
return {
|
|
850
897
|
pending: scope.pending,
|
|
851
898
|
done,
|
|
@@ -2631,11 +2678,19 @@ function throttle(source, opt) {
|
|
|
2631
2678
|
tick();
|
|
2632
2679
|
};
|
|
2633
2680
|
const update = (fn) => set(fn(untracked(source)));
|
|
2681
|
+
const flush = () => {
|
|
2682
|
+
if (timeout)
|
|
2683
|
+
clearTimeout(timeout);
|
|
2684
|
+
timeout = undefined;
|
|
2685
|
+
pendingTrailing = false;
|
|
2686
|
+
fire();
|
|
2687
|
+
};
|
|
2634
2688
|
const writable = toWritable(computed(() => {
|
|
2635
2689
|
trigger();
|
|
2636
2690
|
return untracked(source);
|
|
2637
2691
|
}, opt), set, update);
|
|
2638
2692
|
writable.original = source.asReadonly();
|
|
2693
|
+
writable.flush = flush;
|
|
2639
2694
|
return writable;
|
|
2640
2695
|
}
|
|
2641
2696
|
|
|
@@ -2856,6 +2911,194 @@ function createOrientation(debugName) {
|
|
|
2856
2911
|
return state.asReadonly();
|
|
2857
2912
|
}
|
|
2858
2913
|
|
|
2914
|
+
const IDLE = {
|
|
2915
|
+
active: false,
|
|
2916
|
+
start: { x: 0, y: 0 },
|
|
2917
|
+
current: { x: 0, y: 0 },
|
|
2918
|
+
delta: { x: 0, y: 0 },
|
|
2919
|
+
pointerId: null,
|
|
2920
|
+
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2921
|
+
button: -1,
|
|
2922
|
+
};
|
|
2923
|
+
function stateEqual(a, b) {
|
|
2924
|
+
return (a.active === b.active &&
|
|
2925
|
+
a.pointerId === b.pointerId &&
|
|
2926
|
+
a.current.x === b.current.x &&
|
|
2927
|
+
a.current.y === b.current.y &&
|
|
2928
|
+
a.button === b.button &&
|
|
2929
|
+
a.modifiers.shift === b.modifiers.shift &&
|
|
2930
|
+
a.modifiers.alt === b.modifiers.alt &&
|
|
2931
|
+
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
2932
|
+
a.modifiers.meta === b.modifiers.meta);
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Tracks a pointer *gesture* (pointerdown → capture → move → up) as a signal —
|
|
2936
|
+
* the foundation for pointer-based drag/move/resize/marquee on a canvas. Unlike
|
|
2937
|
+
* native HTML5 drag, pointer events fire continuously and coordinates are
|
|
2938
|
+
* reliable. SSR-safe; cleans up its listeners automatically.
|
|
2939
|
+
*
|
|
2940
|
+
* @example
|
|
2941
|
+
* ```ts
|
|
2942
|
+
* const drag = pointerDrag({ activationThreshold: 4 });
|
|
2943
|
+
* const position = computed(() => {
|
|
2944
|
+
* const d = drag();
|
|
2945
|
+
* return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
|
|
2946
|
+
* });
|
|
2947
|
+
* ```
|
|
2948
|
+
*/
|
|
2949
|
+
function pointerDrag(opt) {
|
|
2950
|
+
return runInSensorContext(opt?.injector, () => createPointerDrag(opt));
|
|
2951
|
+
}
|
|
2952
|
+
function createPointerDrag(opt) {
|
|
2953
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
2954
|
+
const base = computed(() => IDLE, {
|
|
2955
|
+
debugName: opt?.debugName ?? 'pointerDrag',
|
|
2956
|
+
});
|
|
2957
|
+
base.unthrottled = base;
|
|
2958
|
+
base.cancel = () => undefined;
|
|
2959
|
+
return base;
|
|
2960
|
+
}
|
|
2961
|
+
const hostRef = inject((ElementRef), { optional: true });
|
|
2962
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2963
|
+
const resolve = (t) => {
|
|
2964
|
+
if (!t)
|
|
2965
|
+
return null;
|
|
2966
|
+
return t instanceof ElementRef ? t.nativeElement : t;
|
|
2967
|
+
};
|
|
2968
|
+
if (!isSignal(target) && !resolve(target)) {
|
|
2969
|
+
if (isDevMode())
|
|
2970
|
+
console.warn('pointerDrag: no target element (host ElementRef missing).');
|
|
2971
|
+
const base = computed(() => IDLE, { debugName });
|
|
2972
|
+
base.unthrottled = base;
|
|
2973
|
+
base.cancel = () => undefined;
|
|
2974
|
+
return base;
|
|
2975
|
+
}
|
|
2976
|
+
const state = throttled(IDLE, {
|
|
2977
|
+
ms: throttle,
|
|
2978
|
+
leading: true,
|
|
2979
|
+
trailing: true,
|
|
2980
|
+
equal: stateEqual,
|
|
2981
|
+
debugName,
|
|
2982
|
+
});
|
|
2983
|
+
let startPoint = { x: 0, y: 0 };
|
|
2984
|
+
let activePointerId = null;
|
|
2985
|
+
let activeButton = -1;
|
|
2986
|
+
let activated = false;
|
|
2987
|
+
let gesture = null;
|
|
2988
|
+
const coord = (e) => coordinateSpace === 'page'
|
|
2989
|
+
? { x: e.pageX, y: e.pageY }
|
|
2990
|
+
: { x: e.clientX, y: e.clientY };
|
|
2991
|
+
const mods = (e) => ({
|
|
2992
|
+
shift: e.shiftKey,
|
|
2993
|
+
alt: e.altKey,
|
|
2994
|
+
ctrl: e.ctrlKey,
|
|
2995
|
+
meta: e.metaKey,
|
|
2996
|
+
});
|
|
2997
|
+
const end = () => {
|
|
2998
|
+
gesture?.abort();
|
|
2999
|
+
gesture = null;
|
|
3000
|
+
activePointerId = null;
|
|
3001
|
+
activeButton = -1;
|
|
3002
|
+
activated = false;
|
|
3003
|
+
state.set(IDLE);
|
|
3004
|
+
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
3005
|
+
};
|
|
3006
|
+
const onMove = (e) => {
|
|
3007
|
+
if (e.pointerId !== activePointerId)
|
|
3008
|
+
return;
|
|
3009
|
+
const current = coord(e);
|
|
3010
|
+
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
3011
|
+
if (!activated && Math.hypot(delta.x, delta.y) >= activationThreshold) {
|
|
3012
|
+
activated = true;
|
|
3013
|
+
}
|
|
3014
|
+
state.set({
|
|
3015
|
+
active: activated,
|
|
3016
|
+
start: startPoint,
|
|
3017
|
+
current,
|
|
3018
|
+
delta,
|
|
3019
|
+
pointerId: activePointerId,
|
|
3020
|
+
modifiers: mods(e),
|
|
3021
|
+
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3022
|
+
});
|
|
3023
|
+
};
|
|
3024
|
+
const onUp = (e) => {
|
|
3025
|
+
if (e.pointerId === activePointerId)
|
|
3026
|
+
end();
|
|
3027
|
+
};
|
|
3028
|
+
const onCancel = (e) => {
|
|
3029
|
+
if (e.pointerId === activePointerId)
|
|
3030
|
+
end();
|
|
3031
|
+
};
|
|
3032
|
+
const onKey = (e) => {
|
|
3033
|
+
if (e.key === 'Escape' && activePointerId !== null)
|
|
3034
|
+
end();
|
|
3035
|
+
};
|
|
3036
|
+
const onDown = (el) => (e) => {
|
|
3037
|
+
if (activePointerId !== null)
|
|
3038
|
+
return;
|
|
3039
|
+
if (!buttons.includes(e.button))
|
|
3040
|
+
return;
|
|
3041
|
+
if (handleSelector && !e.target?.closest?.(handleSelector)) {
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
activePointerId = e.pointerId;
|
|
3045
|
+
activeButton = e.button;
|
|
3046
|
+
activated = false;
|
|
3047
|
+
startPoint = coord(e);
|
|
3048
|
+
try {
|
|
3049
|
+
el.setPointerCapture(e.pointerId);
|
|
3050
|
+
}
|
|
3051
|
+
catch {
|
|
3052
|
+
// capture unsupported (older browsers / test env) — listeners still work
|
|
3053
|
+
}
|
|
3054
|
+
gesture = new AbortController();
|
|
3055
|
+
const signal = gesture.signal;
|
|
3056
|
+
el.addEventListener('pointermove', onMove, { signal });
|
|
3057
|
+
el.addEventListener('pointerup', onUp, { signal });
|
|
3058
|
+
el.addEventListener('pointercancel', onCancel, { signal });
|
|
3059
|
+
el.addEventListener('lostpointercapture', onCancel, {
|
|
3060
|
+
signal,
|
|
3061
|
+
});
|
|
3062
|
+
window.addEventListener('keydown', onKey, { signal });
|
|
3063
|
+
state.set({
|
|
3064
|
+
active: false,
|
|
3065
|
+
start: startPoint,
|
|
3066
|
+
current: startPoint,
|
|
3067
|
+
delta: { x: 0, y: 0 },
|
|
3068
|
+
pointerId: e.pointerId,
|
|
3069
|
+
modifiers: mods(e),
|
|
3070
|
+
button: e.button,
|
|
3071
|
+
});
|
|
3072
|
+
};
|
|
3073
|
+
const attach = (el) => {
|
|
3074
|
+
const controller = new AbortController();
|
|
3075
|
+
el.addEventListener('pointerdown', onDown(el), {
|
|
3076
|
+
signal: controller.signal,
|
|
3077
|
+
});
|
|
3078
|
+
return () => {
|
|
3079
|
+
controller.abort();
|
|
3080
|
+
end();
|
|
3081
|
+
};
|
|
3082
|
+
};
|
|
3083
|
+
if (isSignal(target)) {
|
|
3084
|
+
effect((cleanup) => {
|
|
3085
|
+
const el = resolve(target());
|
|
3086
|
+
if (!el)
|
|
3087
|
+
return;
|
|
3088
|
+
cleanup(attach(el));
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
else {
|
|
3092
|
+
const el = resolve(target);
|
|
3093
|
+
if (el)
|
|
3094
|
+
inject(DestroyRef).onDestroy(attach(el));
|
|
3095
|
+
}
|
|
3096
|
+
const base = state.asReadonly();
|
|
3097
|
+
base.unthrottled = state.original;
|
|
3098
|
+
base.cancel = end;
|
|
3099
|
+
return base;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
2859
3102
|
/**
|
|
2860
3103
|
* Creates a read-only signal that tracks the page's visibility state.
|
|
2861
3104
|
*
|
|
@@ -3082,6 +3325,8 @@ function sensor(type, options) {
|
|
|
3082
3325
|
switch (type) {
|
|
3083
3326
|
case 'mousePosition':
|
|
3084
3327
|
return mousePosition(opts);
|
|
3328
|
+
case 'pointerDrag':
|
|
3329
|
+
return pointerDrag(opts);
|
|
3085
3330
|
case 'networkStatus':
|
|
3086
3331
|
return networkStatus(opts);
|
|
3087
3332
|
case 'pageVisibility':
|
|
@@ -3199,9 +3444,6 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3199
3444
|
return untracked(() => state.asReadonly());
|
|
3200
3445
|
}
|
|
3201
3446
|
|
|
3202
|
-
function isWritableSignal(value) {
|
|
3203
|
-
return isWritableSignal$2(value);
|
|
3204
|
-
}
|
|
3205
3447
|
/**
|
|
3206
3448
|
* Runtime marker + compile-time brand for an opaque value. A `const`-declared `Symbol`
|
|
3207
3449
|
* has a `unique symbol` type, so the same symbol serves as both the property key written
|
|
@@ -3243,12 +3485,16 @@ function isOpaque(value) {
|
|
|
3243
3485
|
value !== null &&
|
|
3244
3486
|
value[OPAQUE] === true);
|
|
3245
3487
|
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3488
|
+
|
|
3489
|
+
function isWritableSignal(value) {
|
|
3490
|
+
return isWritableSignal$2(value);
|
|
3491
|
+
}
|
|
3492
|
+
function isRecord(value) {
|
|
3493
|
+
if (value === null || typeof value !== 'object' || isOpaque(value))
|
|
3494
|
+
return false;
|
|
3495
|
+
const proto = Object.getPrototypeOf(value);
|
|
3496
|
+
return proto === Object.prototype || proto === null;
|
|
3497
|
+
}
|
|
3252
3498
|
/**
|
|
3253
3499
|
* @internal Whether a value is a terminal leaf: a concrete non-record/non-array value always is;
|
|
3254
3500
|
* `null`/`undefined` is a leaf only when vivification is disabled (with vivify on it can still
|
|
@@ -3261,6 +3507,65 @@ function isLeafValue(value, vivifyEnabled) {
|
|
|
3261
3507
|
return true; // opaque always wins — even arrays
|
|
3262
3508
|
return !Array.isArray(value) && !isRecord(value);
|
|
3263
3509
|
}
|
|
3510
|
+
/**
|
|
3511
|
+
* @internal
|
|
3512
|
+
* Resolves the vivify shape for a node from its current value: a present record/array is a
|
|
3513
|
+
* certainty we keep (cached in the derivation, so it survives the value being nulled); an
|
|
3514
|
+
* unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
|
|
3515
|
+
*/
|
|
3516
|
+
function resolveVivify(sample, option) {
|
|
3517
|
+
if (!option)
|
|
3518
|
+
return false;
|
|
3519
|
+
if (Array.isArray(sample))
|
|
3520
|
+
return 'array';
|
|
3521
|
+
if (isRecord(sample))
|
|
3522
|
+
return 'object';
|
|
3523
|
+
return 'auto';
|
|
3524
|
+
}
|
|
3525
|
+
function hasOwnKey(value, key) {
|
|
3526
|
+
return value != null && Object.hasOwn(value, key);
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* @internal
|
|
3530
|
+
* Builds the `onChange` for the fallback (non-record container) derivation branch. For an
|
|
3531
|
+
* immutable source the container is copied before the write — returning the same mutated
|
|
3532
|
+
* reference would let the source's equality cut propagation (leaving child signals permanently
|
|
3533
|
+
* stale) and alias the caller's original object, breaking the structural-sharing contract
|
|
3534
|
+
* `forkStore` relies on. For a mutable source the write goes through `mutate`, so the chain's
|
|
3535
|
+
* force-notify engages (plain `update` with the same reference would never notify).
|
|
3536
|
+
*/
|
|
3537
|
+
function createFallbackOnChange(target, prop, vivifyFn, isMutableSource) {
|
|
3538
|
+
const write = (newValue) => (v) => {
|
|
3539
|
+
const container = vivifyFn(v, prop);
|
|
3540
|
+
if (container === null || container === undefined)
|
|
3541
|
+
return container;
|
|
3542
|
+
const next = isMutableSource
|
|
3543
|
+
? container
|
|
3544
|
+
: Array.isArray(container)
|
|
3545
|
+
? container.slice()
|
|
3546
|
+
: isRecord(container)
|
|
3547
|
+
? { ...container }
|
|
3548
|
+
: container; // non-plain leaf (Date/class instance): legacy in-place attempt
|
|
3549
|
+
try {
|
|
3550
|
+
next[prop] = newValue;
|
|
3551
|
+
}
|
|
3552
|
+
catch (e) {
|
|
3553
|
+
if (isDevMode())
|
|
3554
|
+
console.error(`[store] Failed to set property "${String(prop)}"`, e);
|
|
3555
|
+
}
|
|
3556
|
+
return next;
|
|
3557
|
+
};
|
|
3558
|
+
return isMutableSource
|
|
3559
|
+
? (newValue) => target.mutate(write(newValue))
|
|
3560
|
+
: (newValue) => target.update(write(newValue));
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
/**
|
|
3564
|
+
* @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
|
|
3565
|
+
* {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
|
|
3566
|
+
* nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
|
|
3567
|
+
*/
|
|
3568
|
+
const LEAF = Symbol('@mmstack/primitives::store/LEAF');
|
|
3264
3569
|
/**
|
|
3265
3570
|
* @internal Constant leaf probes for nodes whose leaf-ness is statically known, so the reactive
|
|
3266
3571
|
* `computed` can be skipped entirely.
|
|
@@ -3318,14 +3623,20 @@ function markAsLeaf(sig, value, vivifyEnabled, noUnionLeaves) {
|
|
|
3318
3623
|
function isLeaf(value) {
|
|
3319
3624
|
return isStore(value) && value[LEAF]?.() === true;
|
|
3320
3625
|
}
|
|
3626
|
+
|
|
3321
3627
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3322
3628
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3323
3629
|
/**
|
|
3324
|
-
* @internal
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3630
|
+
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3631
|
+
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3632
|
+
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3327
3633
|
*/
|
|
3328
|
-
const
|
|
3634
|
+
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3635
|
+
/**
|
|
3636
|
+
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3637
|
+
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3638
|
+
*/
|
|
3639
|
+
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3329
3640
|
const SIGNAL_FN_PROP = new Set([
|
|
3330
3641
|
'set',
|
|
3331
3642
|
'update',
|
|
@@ -3333,6 +3644,12 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3333
3644
|
'inline',
|
|
3334
3645
|
'asReadonly',
|
|
3335
3646
|
]);
|
|
3647
|
+
/**
|
|
3648
|
+
* @internal
|
|
3649
|
+
* Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
|
|
3650
|
+
* Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
|
|
3651
|
+
*/
|
|
3652
|
+
const PROXY_CACHE = new WeakMap();
|
|
3336
3653
|
/**
|
|
3337
3654
|
* @internal
|
|
3338
3655
|
* Test-only handle on the finalization registry (deliberately NOT re-exported from the public
|
|
@@ -3352,194 +3669,72 @@ function isStore(value) {
|
|
|
3352
3669
|
value !== null &&
|
|
3353
3670
|
value[IS_STORE] === true);
|
|
3354
3671
|
}
|
|
3355
|
-
|
|
3356
|
-
if (value === null || typeof value !== 'object' || isOpaque(value))
|
|
3357
|
-
return false;
|
|
3358
|
-
const proto = Object.getPrototypeOf(value);
|
|
3359
|
-
return proto === Object.prototype || proto === null;
|
|
3360
|
-
}
|
|
3672
|
+
|
|
3361
3673
|
/**
|
|
3362
|
-
* @internal
|
|
3363
|
-
*
|
|
3364
|
-
*
|
|
3365
|
-
* unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
|
|
3674
|
+
* @internal Reads (or lazily builds + caches) the child node proxy for `prop` on `target`,
|
|
3675
|
+
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3676
|
+
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3366
3677
|
*/
|
|
3367
|
-
function
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
* force-notify engages (plain `update` with the same reference would never notify).
|
|
3387
|
-
*/
|
|
3388
|
-
function createFallbackOnChange(target, prop, vivifyFn, isMutableSource) {
|
|
3389
|
-
const write = (newValue) => (v) => {
|
|
3390
|
-
const container = vivifyFn(v, prop);
|
|
3391
|
-
if (container === null || container === undefined)
|
|
3392
|
-
return container;
|
|
3393
|
-
const next = isMutableSource
|
|
3394
|
-
? container
|
|
3395
|
-
: Array.isArray(container)
|
|
3396
|
-
? container.slice()
|
|
3397
|
-
: isRecord(container)
|
|
3398
|
-
? { ...container }
|
|
3399
|
-
: container; // non-plain leaf (Date/class instance): legacy in-place attempt
|
|
3400
|
-
try {
|
|
3401
|
-
next[prop] = newValue;
|
|
3402
|
-
}
|
|
3403
|
-
catch (e) {
|
|
3404
|
-
if (isDevMode())
|
|
3405
|
-
console.error(`[store] Failed to set property "${String(prop)}"`, e);
|
|
3406
|
-
}
|
|
3407
|
-
return next;
|
|
3408
|
-
};
|
|
3409
|
-
return isMutableSource
|
|
3410
|
-
? (newValue) => target.mutate(write(newValue))
|
|
3411
|
-
: (newValue) => target.update(write(newValue));
|
|
3678
|
+
function getCachedChild(target, prop, build) {
|
|
3679
|
+
let storeCache = PROXY_CACHE.get(target);
|
|
3680
|
+
if (!storeCache) {
|
|
3681
|
+
storeCache = new Map();
|
|
3682
|
+
PROXY_CACHE.set(target, storeCache);
|
|
3683
|
+
}
|
|
3684
|
+
const cachedRef = storeCache.get(prop);
|
|
3685
|
+
if (cachedRef) {
|
|
3686
|
+
const cached = cachedRef.deref();
|
|
3687
|
+
if (cached)
|
|
3688
|
+
return cached;
|
|
3689
|
+
storeCache.delete(prop);
|
|
3690
|
+
PROXY_CLEANUP.unregister(cachedRef);
|
|
3691
|
+
}
|
|
3692
|
+
const proxy = build();
|
|
3693
|
+
const ref = new WeakRef(proxy);
|
|
3694
|
+
storeCache.set(prop, ref);
|
|
3695
|
+
PROXY_CLEANUP.register(proxy, { target, prop }, ref);
|
|
3696
|
+
return proxy;
|
|
3412
3697
|
}
|
|
3413
3698
|
/**
|
|
3414
|
-
* @internal
|
|
3415
|
-
*
|
|
3699
|
+
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3700
|
+
* A record parent reads the key directly; any other container goes through the fallback `from`/
|
|
3701
|
+
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3702
|
+
* is constructed.
|
|
3416
3703
|
*/
|
|
3417
|
-
function
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
const
|
|
3421
|
-
const
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
ownKeys() {
|
|
3441
|
-
const v = untracked(source);
|
|
3442
|
-
if (!Array.isArray(v))
|
|
3443
|
-
return [];
|
|
3444
|
-
const len = v.length;
|
|
3445
|
-
const arr = new Array(len + 1);
|
|
3446
|
-
for (let i = 0; i < len; i++) {
|
|
3447
|
-
arr[i] = String(i);
|
|
3448
|
-
}
|
|
3449
|
-
arr[len] = 'length';
|
|
3450
|
-
return arr;
|
|
3451
|
-
},
|
|
3452
|
-
getPrototypeOf() {
|
|
3453
|
-
return Array.prototype;
|
|
3454
|
-
},
|
|
3455
|
-
getOwnPropertyDescriptor(_, prop) {
|
|
3456
|
-
const v = untracked(source);
|
|
3457
|
-
if (!Array.isArray(v))
|
|
3458
|
-
return;
|
|
3459
|
-
if (prop === 'length' ||
|
|
3460
|
-
(typeof prop === 'string' && !isNaN(+prop) && +prop < v.length)) {
|
|
3461
|
-
return {
|
|
3462
|
-
enumerable: true,
|
|
3463
|
-
configurable: true, // Required for proxies to dynamic targets
|
|
3464
|
-
};
|
|
3465
|
-
}
|
|
3466
|
-
return;
|
|
3467
|
-
},
|
|
3468
|
-
get(target, prop, receiver) {
|
|
3469
|
-
if (prop === IS_STORE)
|
|
3470
|
-
return true;
|
|
3471
|
-
if (prop === 'length')
|
|
3472
|
-
return lengthSignal;
|
|
3473
|
-
if (prop === Symbol.iterator) {
|
|
3474
|
-
return function* () {
|
|
3475
|
-
// read length reactively: a spread/for-of inside a computed/effect must re-run
|
|
3476
|
-
// when items are added or removed, not only when already-read elements change
|
|
3477
|
-
for (let i = 0; i < lengthSignal(); i++) {
|
|
3478
|
-
yield receiver[i];
|
|
3479
|
-
}
|
|
3480
|
-
};
|
|
3481
|
-
}
|
|
3482
|
-
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3483
|
-
return target[prop];
|
|
3484
|
-
if (isIndexProp(prop)) {
|
|
3485
|
-
const idx = +prop;
|
|
3486
|
-
let storeCache = PROXY_CACHE.get(target);
|
|
3487
|
-
if (!storeCache) {
|
|
3488
|
-
storeCache = new Map();
|
|
3489
|
-
PROXY_CACHE.set(target, storeCache);
|
|
3490
|
-
}
|
|
3491
|
-
const cachedRef = storeCache.get(idx);
|
|
3492
|
-
if (cachedRef) {
|
|
3493
|
-
const cached = cachedRef.deref();
|
|
3494
|
-
if (cached)
|
|
3495
|
-
return cached;
|
|
3496
|
-
storeCache.delete(idx);
|
|
3497
|
-
PROXY_CLEANUP.unregister(cachedRef);
|
|
3498
|
-
}
|
|
3499
|
-
const value = untracked(target);
|
|
3500
|
-
const valueIsArray = Array.isArray(value);
|
|
3501
|
-
const valueIsRecord = isRecord(value);
|
|
3502
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3503
|
-
const vivifyFn = createVivify(nodeVivify);
|
|
3504
|
-
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3505
|
-
isMutableSource &&
|
|
3506
|
-
typeof value[idx] === 'object'
|
|
3507
|
-
? () => false
|
|
3508
|
-
: undefined;
|
|
3509
|
-
const computation = valueIsRecord
|
|
3510
|
-
? derived(target, idx, {
|
|
3511
|
-
equal: equalFn,
|
|
3512
|
-
vivify: nodeVivify,
|
|
3513
|
-
})
|
|
3514
|
-
: derived(target, {
|
|
3515
|
-
from: (v) => v?.[idx],
|
|
3516
|
-
onChange: createFallbackOnChange(target, idx, vivifyFn, isMutableSource),
|
|
3517
|
-
equal: equalFn,
|
|
3518
|
-
});
|
|
3519
|
-
const childSample = untracked(computation);
|
|
3520
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3521
|
-
const proxy = Array.isArray(childSample) && !isOpaque(childSample)
|
|
3522
|
-
? toArrayStore(computation, injector, childVivify, noUnionLeaves)
|
|
3523
|
-
: toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3524
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3525
|
-
const ref = new WeakRef(proxy);
|
|
3526
|
-
storeCache.set(idx, ref);
|
|
3527
|
-
PROXY_CLEANUP.register(proxy, { target, prop: idx }, ref);
|
|
3528
|
-
return proxy;
|
|
3529
|
-
}
|
|
3530
|
-
return Reflect.get(target, prop, receiver);
|
|
3531
|
-
},
|
|
3532
|
-
});
|
|
3704
|
+
function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnionLeaves) {
|
|
3705
|
+
const value = untracked(target);
|
|
3706
|
+
const valueIsRecord = isRecord(value);
|
|
3707
|
+
const valueIsArray = Array.isArray(value);
|
|
3708
|
+
const nodeVivify = resolveVivify(value, vivify);
|
|
3709
|
+
const vivifyFn = createVivify(nodeVivify);
|
|
3710
|
+
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3711
|
+
isMutableSource &&
|
|
3712
|
+
typeof value[prop] === 'object'
|
|
3713
|
+
? () => false
|
|
3714
|
+
: undefined;
|
|
3715
|
+
const computation = valueIsRecord
|
|
3716
|
+
? derived(target, prop, { equal: equalFn, vivify: nodeVivify })
|
|
3717
|
+
: derived(target, {
|
|
3718
|
+
from: (v) => v?.[prop],
|
|
3719
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3720
|
+
equal: equalFn,
|
|
3721
|
+
});
|
|
3722
|
+
const childSample = untracked(computation);
|
|
3723
|
+
const childVivify = resolveVivify(childSample, vivify);
|
|
3724
|
+
const proxy = toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3725
|
+
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3726
|
+
return proxy;
|
|
3533
3727
|
}
|
|
3534
3728
|
/**
|
|
3535
3729
|
* Converts a Signal into a deep-observable Store.
|
|
3536
3730
|
* Accessing nested properties returns a derived Signal of that path.
|
|
3537
3731
|
*
|
|
3538
3732
|
* @remarks
|
|
3539
|
-
* A
|
|
3540
|
-
*
|
|
3541
|
-
*
|
|
3542
|
-
*
|
|
3733
|
+
* A node's *container kind* (array / record / primitive) is tracked reactively via a per-node
|
|
3734
|
+
* `kind` computed, so the same proxy serves all three and a union node that flips between an
|
|
3735
|
+
* array and a record keeps working. Flips are route-forward: after a flip the node behaves as
|
|
3736
|
+
* its new kind on the next access, while child proxies cached under the old shape go stale and
|
|
3737
|
+
* are pruned by the GC.
|
|
3543
3738
|
*
|
|
3544
3739
|
* @example
|
|
3545
3740
|
* const state = store({ user: { name: 'John' } });
|
|
@@ -3557,92 +3752,115 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3557
3752
|
});
|
|
3558
3753
|
const isWritableSource = isWritableSignal(source);
|
|
3559
3754
|
const isMutableSource = isWritableSource && isMutable(writableSource);
|
|
3755
|
+
const kind = computed(() => {
|
|
3756
|
+
const v = source();
|
|
3757
|
+
if (Array.isArray(v) && !isOpaque(v))
|
|
3758
|
+
return 'array';
|
|
3759
|
+
if (isRecord(v))
|
|
3760
|
+
return 'record';
|
|
3761
|
+
return 'primitive';
|
|
3762
|
+
}, /* @ts-ignore */
|
|
3763
|
+
...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3764
|
+
// built lazily so non-array nodes never allocate it
|
|
3765
|
+
let length;
|
|
3766
|
+
const arrayLength = () => (length ??= computed(() => {
|
|
3767
|
+
const v = source();
|
|
3768
|
+
return Array.isArray(v) ? v.length : 0;
|
|
3769
|
+
}));
|
|
3560
3770
|
const s = new Proxy(writableSource, {
|
|
3561
3771
|
has(_, prop) {
|
|
3562
|
-
|
|
3772
|
+
const v = untracked(source);
|
|
3773
|
+
if (untracked(kind) === 'array') {
|
|
3774
|
+
if (prop === 'length')
|
|
3775
|
+
return true;
|
|
3776
|
+
if (isIndexProp(prop)) {
|
|
3777
|
+
const idx = +prop;
|
|
3778
|
+
return idx >= 0 && idx < v.length;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
// nullish node values are routinely descended with vivify on — `in` must not throw
|
|
3782
|
+
return v == null ? false : Reflect.has(v, prop);
|
|
3563
3783
|
},
|
|
3564
3784
|
ownKeys() {
|
|
3565
3785
|
const v = untracked(source);
|
|
3786
|
+
if (untracked(kind) === 'array') {
|
|
3787
|
+
const len = v.length;
|
|
3788
|
+
const arr = new Array(len + 1);
|
|
3789
|
+
for (let i = 0; i < len; i++)
|
|
3790
|
+
arr[i] = String(i);
|
|
3791
|
+
arr[len] = 'length';
|
|
3792
|
+
return arr;
|
|
3793
|
+
}
|
|
3566
3794
|
if (!isRecord(v))
|
|
3567
3795
|
return [];
|
|
3568
3796
|
return Reflect.ownKeys(v);
|
|
3569
3797
|
},
|
|
3570
3798
|
getPrototypeOf() {
|
|
3571
|
-
|
|
3799
|
+
if (untracked(kind) === 'array')
|
|
3800
|
+
return Array.prototype;
|
|
3801
|
+
const v = untracked(source);
|
|
3802
|
+
return v == null ? Object.prototype : Object.getPrototypeOf(v);
|
|
3572
3803
|
},
|
|
3573
3804
|
getOwnPropertyDescriptor(_, prop) {
|
|
3574
|
-
const
|
|
3575
|
-
if (
|
|
3805
|
+
const v = untracked(source);
|
|
3806
|
+
if (untracked(kind) === 'array') {
|
|
3807
|
+
if (prop === 'length' ||
|
|
3808
|
+
(typeof prop === 'string' && !isNaN(+prop) && +prop < v.length))
|
|
3809
|
+
return { enumerable: true, configurable: true };
|
|
3576
3810
|
return;
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
};
|
|
3811
|
+
}
|
|
3812
|
+
if (!isRecord(v) || !(prop in v))
|
|
3813
|
+
return;
|
|
3814
|
+
return { enumerable: true, configurable: true };
|
|
3581
3815
|
},
|
|
3582
|
-
get(target, prop) {
|
|
3816
|
+
get(target, prop, receiver) {
|
|
3583
3817
|
if (prop === IS_STORE)
|
|
3584
3818
|
return true;
|
|
3819
|
+
if (prop === STORE_KIND)
|
|
3820
|
+
return isMutableSource
|
|
3821
|
+
? 'mutable'
|
|
3822
|
+
: isWritableSource
|
|
3823
|
+
? 'writable'
|
|
3824
|
+
: 'readonly';
|
|
3825
|
+
if (prop === STORE_INJECTOR)
|
|
3826
|
+
return injector;
|
|
3585
3827
|
if (prop === 'asReadonlyStore')
|
|
3586
3828
|
return () => {
|
|
3587
3829
|
if (!isWritableSource)
|
|
3588
3830
|
return s;
|
|
3589
3831
|
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3590
3832
|
};
|
|
3591
|
-
|
|
3833
|
+
const k = untracked(kind);
|
|
3834
|
+
if (prop === 'extend' && k !== 'array')
|
|
3592
3835
|
return (seed) => scopedStore(s, seed, isMutableSource
|
|
3593
3836
|
? 'mutable'
|
|
3594
3837
|
: isWritableSource
|
|
3595
3838
|
? 'writable'
|
|
3596
3839
|
: 'readonly', injector);
|
|
3840
|
+
if (k === 'array') {
|
|
3841
|
+
if (prop === 'length')
|
|
3842
|
+
return arrayLength();
|
|
3843
|
+
if (prop === Symbol.iterator)
|
|
3844
|
+
return function* () {
|
|
3845
|
+
// read length reactively: a spread/for-of inside a computed/effect must re-run
|
|
3846
|
+
// when items are added or removed, not only when already-read elements change
|
|
3847
|
+
const len = arrayLength();
|
|
3848
|
+
for (let i = 0; i < len(); i++)
|
|
3849
|
+
yield receiver[i];
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3597
3852
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3598
3853
|
return target[prop];
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
PROXY_CACHE.set(target, storeCache);
|
|
3603
|
-
}
|
|
3604
|
-
const cachedRef = storeCache.get(prop);
|
|
3605
|
-
if (cachedRef) {
|
|
3606
|
-
const cached = cachedRef.deref();
|
|
3607
|
-
if (cached)
|
|
3608
|
-
return cached;
|
|
3609
|
-
storeCache.delete(prop);
|
|
3610
|
-
PROXY_CLEANUP.unregister(cachedRef);
|
|
3611
|
-
}
|
|
3612
|
-
const value = untracked(target);
|
|
3613
|
-
const valueIsRecord = isRecord(value);
|
|
3614
|
-
const valueIsArray = Array.isArray(value);
|
|
3615
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3616
|
-
const vivifyFn = createVivify(nodeVivify);
|
|
3617
|
-
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3618
|
-
isMutableSource &&
|
|
3619
|
-
typeof value[prop] === 'object'
|
|
3620
|
-
? () => false
|
|
3621
|
-
: undefined;
|
|
3622
|
-
const computation = valueIsRecord
|
|
3623
|
-
? derived(target, prop, { equal: equalFn, vivify: nodeVivify })
|
|
3624
|
-
: derived(target, {
|
|
3625
|
-
from: (v) => v?.[prop],
|
|
3626
|
-
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3627
|
-
equal: equalFn,
|
|
3628
|
-
});
|
|
3629
|
-
const childSample = untracked(computation);
|
|
3630
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3631
|
-
const proxy = Array.isArray(childSample) && !isOpaque(childSample)
|
|
3632
|
-
? toArrayStore(computation, injector, childVivify, noUnionLeaves)
|
|
3633
|
-
: toStore(computation, injector, childVivify, noUnionLeaves);
|
|
3634
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3635
|
-
const ref = new WeakRef(proxy);
|
|
3636
|
-
storeCache.set(prop, ref);
|
|
3637
|
-
PROXY_CLEANUP.register(proxy, { target, prop }, ref);
|
|
3638
|
-
return proxy;
|
|
3854
|
+
if (k === 'array' && !isIndexProp(prop))
|
|
3855
|
+
return Reflect.get(target, prop, receiver);
|
|
3856
|
+
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource, injector, vivify, noUnionLeaves));
|
|
3639
3857
|
},
|
|
3640
3858
|
});
|
|
3641
3859
|
return s;
|
|
3642
3860
|
}
|
|
3643
3861
|
/**
|
|
3644
3862
|
* @internal
|
|
3645
|
-
* Backs `
|
|
3863
|
+
* Backs `extendStore(...)`. Builds a scoped overlay over `parent`: the local layer (the seed
|
|
3646
3864
|
* plus any keys created later) is its own signal and `parent` is its own signal, so the getter
|
|
3647
3865
|
* routes each key by consulting BOTH — local first, then parent, else local (so a write to an
|
|
3648
3866
|
* as-yet-unknown key lands locally). Inherited keys return the parent's own sub-store (shared
|
|
@@ -3690,6 +3908,10 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3690
3908
|
get(target, prop) {
|
|
3691
3909
|
if (prop === IS_STORE)
|
|
3692
3910
|
return true;
|
|
3911
|
+
if (prop === STORE_KIND)
|
|
3912
|
+
return kind;
|
|
3913
|
+
if (prop === STORE_INJECTOR)
|
|
3914
|
+
return injector;
|
|
3693
3915
|
if (prop === SCOPE_PARENT)
|
|
3694
3916
|
return parent;
|
|
3695
3917
|
if (prop === 'extend')
|
|
@@ -3722,6 +3944,28 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3722
3944
|
});
|
|
3723
3945
|
return scope;
|
|
3724
3946
|
}
|
|
3947
|
+
/** @internal Reads a store's writability brand, falling back to signal inspection if unbranded. */
|
|
3948
|
+
function storeKind(s) {
|
|
3949
|
+
return (s[STORE_KIND] ??
|
|
3950
|
+
(isWritableSignal(s) ? (isMutable(s) ? 'mutable' : 'writable') : 'readonly'));
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* Extends a store with extra keys via a scoped overlay, returning a new store that reads through
|
|
3954
|
+
* to the parent for inherited keys (shared identity + two-way) while holding the new keys locally.
|
|
3955
|
+
*
|
|
3956
|
+
* The typesafe successor to the deprecated `store.extend(...)` method — moving it off the proxy
|
|
3957
|
+
* frees the `extend` key for use as a normal record key. Writability (readonly/writable/mutable)
|
|
3958
|
+
* is inherited from `store`.
|
|
3959
|
+
*
|
|
3960
|
+
* @example
|
|
3961
|
+
* const base = store({ count: 0 });
|
|
3962
|
+
* const scoped = extendStore(base, { label: 'live' });
|
|
3963
|
+
* scoped.count.set(1); // writes through to base
|
|
3964
|
+
* scoped.label.set('x'); // stays local
|
|
3965
|
+
*/
|
|
3966
|
+
function extendStore(store, source, injector) {
|
|
3967
|
+
return scopedStore(store, source, storeKind(store), injector ?? store[STORE_INJECTOR] ?? inject(Injector));
|
|
3968
|
+
}
|
|
3725
3969
|
/**
|
|
3726
3970
|
* Creates a WritableSignalStore from a value.
|
|
3727
3971
|
* @see {@link toStore}
|
|
@@ -3804,6 +4048,26 @@ function forkStore(base, opt) {
|
|
|
3804
4048
|
};
|
|
3805
4049
|
}
|
|
3806
4050
|
|
|
4051
|
+
/**
|
|
4052
|
+
* @internal The plain-`effect` sibling of the public {@link pausableEffect} (which is built on
|
|
4053
|
+
* `nestedEffect`). For infra utilities that own a single top-level effect/subscription and don't
|
|
4054
|
+
* need frame/nesting semantics. Opt-in (default off): with no `pause` (call site or
|
|
4055
|
+
* `providePausableOptions` default) it returns a bare `effect` (zero overhead, byte-identical to
|
|
4056
|
+
* today); otherwise it gates the body on the resolved predicate — read FIRST so the dependency set
|
|
4057
|
+
* collapses to just the predicate while paused, re-tracking on resume. Deliberately NOT re-exported
|
|
4058
|
+
* from the public barrel.
|
|
4059
|
+
*/
|
|
4060
|
+
function pausablePureEffect(effectFn, options) {
|
|
4061
|
+
const paused = resolvePause(options, false);
|
|
4062
|
+
if (!paused)
|
|
4063
|
+
return effect(effectFn, options);
|
|
4064
|
+
return effect((registerCleanup) => {
|
|
4065
|
+
if (paused())
|
|
4066
|
+
return;
|
|
4067
|
+
effectFn(registerCleanup);
|
|
4068
|
+
}, options);
|
|
4069
|
+
}
|
|
4070
|
+
|
|
3807
4071
|
// Internal dummy store for server-side rendering
|
|
3808
4072
|
const noopStore = {
|
|
3809
4073
|
getItem: () => null,
|
|
@@ -3865,8 +4129,9 @@ const noopStore = {
|
|
|
3865
4129
|
* }
|
|
3866
4130
|
* ```
|
|
3867
4131
|
*/
|
|
3868
|
-
function stored(fallback, { key, store: providedStore, serialize = JSON.stringify, deserialize = JSON.parse, syncTabs = false, equal = Object.is, onKeyChange = 'load', cleanupOldKey = false, validate = () => true, ...rest }) {
|
|
3869
|
-
const
|
|
4132
|
+
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 }) {
|
|
4133
|
+
const injector = providedInjector ?? inject(Injector);
|
|
4134
|
+
const isServer = isPlatformServer(injector.get(PLATFORM_ID));
|
|
3870
4135
|
const fallbackStore = isServer ? noopStore : localStorage;
|
|
3871
4136
|
const store = providedStore ?? fallbackStore;
|
|
3872
4137
|
const keySig = typeof key === 'string'
|
|
@@ -3874,8 +4139,6 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3874
4139
|
: isSignal(key)
|
|
3875
4140
|
? key
|
|
3876
4141
|
: computed(key);
|
|
3877
|
-
// "no stored value" marker — distinct from `null`/`undefined`, so a nullable `T` can
|
|
3878
|
-
// round-trip a legitimate `null` through `set` instead of it acting like `clear()`
|
|
3879
4142
|
const EMPTY = Symbol();
|
|
3880
4143
|
const getValue = (key) => {
|
|
3881
4144
|
const found = store.getItem(key);
|
|
@@ -3920,7 +4183,7 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3920
4183
|
} });
|
|
3921
4184
|
let prevKey = initialKey;
|
|
3922
4185
|
if (onKeyChange === 'store') {
|
|
3923
|
-
|
|
4186
|
+
pausablePureEffect(() => {
|
|
3924
4187
|
const k = keySig();
|
|
3925
4188
|
storeValue(k, internal());
|
|
3926
4189
|
if (prevKey !== k) {
|
|
@@ -3928,10 +4191,10 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3928
4191
|
store.removeItem(prevKey);
|
|
3929
4192
|
prevKey = k;
|
|
3930
4193
|
}
|
|
3931
|
-
});
|
|
4194
|
+
}, { injector, pause });
|
|
3932
4195
|
}
|
|
3933
4196
|
else {
|
|
3934
|
-
|
|
4197
|
+
pausablePureEffect(() => {
|
|
3935
4198
|
const k = keySig();
|
|
3936
4199
|
const internalValue = internal();
|
|
3937
4200
|
if (k === prevKey) {
|
|
@@ -3944,14 +4207,11 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
|
|
|
3944
4207
|
prevKey = k;
|
|
3945
4208
|
internal.set(value); // load new value
|
|
3946
4209
|
}
|
|
3947
|
-
});
|
|
4210
|
+
}, { injector, pause });
|
|
3948
4211
|
}
|
|
3949
4212
|
if (syncTabs && !isServer) {
|
|
3950
|
-
const destroyRef =
|
|
4213
|
+
const destroyRef = injector.get(DestroyRef);
|
|
3951
4214
|
const sync = (e) => {
|
|
3952
|
-
// `storage` events only describe Web Storage — ignore events for a different
|
|
3953
|
-
// storage area (or any event when a custom adapter is configured), otherwise an
|
|
3954
|
-
// unrelated localStorage write with the same key string corrupts our state
|
|
3955
4215
|
if (e.storageArea !== store)
|
|
3956
4216
|
return;
|
|
3957
4217
|
if (e.key !== untracked(keySig))
|
|
@@ -4080,29 +4340,26 @@ function generateDeterministicID() {
|
|
|
4080
4340
|
*
|
|
4081
4341
|
*/
|
|
4082
4342
|
function tabSync(sig, opt) {
|
|
4083
|
-
|
|
4343
|
+
const optObj = typeof opt === 'object' ? opt : undefined;
|
|
4344
|
+
const injector = optObj?.injector ?? inject(Injector);
|
|
4345
|
+
if (isPlatformServer(injector.get(PLATFORM_ID)))
|
|
4084
4346
|
return sig;
|
|
4085
4347
|
const id = typeof opt === 'string' ? opt : (opt?.id ?? generateDeterministicID());
|
|
4086
|
-
const bus =
|
|
4087
|
-
// The last value applied from a remote tab. The outbound effect skips (exactly) the run
|
|
4088
|
-
// caused by that write — without this, an inbound object (a fresh structured clone, so
|
|
4089
|
-
// never reference-equal) would be re-posted, and two tabs would ping-pong forever.
|
|
4348
|
+
const bus = injector.get(MessageBus);
|
|
4090
4349
|
const NONE = Symbol();
|
|
4091
4350
|
let received = NONE;
|
|
4092
4351
|
const { unsub, post } = bus.subscribe(id, (next) => {
|
|
4093
4352
|
const before = untracked(sig);
|
|
4094
4353
|
received = next;
|
|
4095
4354
|
sig.set(next);
|
|
4096
|
-
// Equality-suppressed write (e.g. an identical primitive): no effect run will follow,
|
|
4097
|
-
// so clear the marker — it must not swallow a later, genuinely local change.
|
|
4098
4355
|
if (untracked(sig) === before)
|
|
4099
4356
|
received = NONE;
|
|
4100
4357
|
});
|
|
4101
|
-
let
|
|
4358
|
+
let firstDone = false;
|
|
4102
4359
|
const effectRef = effect(() => {
|
|
4103
4360
|
const val = sig();
|
|
4104
|
-
if (!
|
|
4105
|
-
|
|
4361
|
+
if (!firstDone) {
|
|
4362
|
+
firstDone = true;
|
|
4106
4363
|
return;
|
|
4107
4364
|
}
|
|
4108
4365
|
if (val === received) {
|
|
@@ -4111,9 +4368,8 @@ function tabSync(sig, opt) {
|
|
|
4111
4368
|
}
|
|
4112
4369
|
received = NONE;
|
|
4113
4370
|
post(val);
|
|
4114
|
-
}, /*
|
|
4115
|
-
|
|
4116
|
-
inject(DestroyRef).onDestroy(() => {
|
|
4371
|
+
}, { ...(ngDevMode ? { debugName: "effectRef" } : /* istanbul ignore next */ {}), injector });
|
|
4372
|
+
injector.get(DestroyRef).onDestroy(() => {
|
|
4117
4373
|
effectRef.destroy();
|
|
4118
4374
|
unsub();
|
|
4119
4375
|
});
|
|
@@ -4320,5 +4576,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4320
4576
|
* Generated bundle index. Do not edit.
|
|
4321
4577
|
*/
|
|
4322
4578
|
|
|
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 };
|
|
4579
|
+
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 };
|
|
4324
4580
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|