@mmstack/primitives 20.4.2 → 20.4.4
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 +18 -28
- package/fesm2022/mmstack-primitives.mjs +86 -1
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/index.d.ts +98 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,40 +214,30 @@ export class ThemeSelectorComponent {
|
|
|
214
214
|
|
|
215
215
|
### piped
|
|
216
216
|
|
|
217
|
-
Adds
|
|
217
|
+
Adds two fluent APIs to signals:
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
import { piped, pipeable } from '@mmstack/primitives';
|
|
219
|
+
- **`.map(...transforms, [options])`** – compose pure, synchronous value→value transforms. Returns a computed signal that remains pipeable.
|
|
220
|
+
- **`.pipe(...operators)`** – compose operators (signal→signal), useful for combining signals or reusable projections.
|
|
222
221
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
})
|
|
227
|
-
export class PipeableComponent {
|
|
228
|
-
count = piped(1);
|
|
222
|
+
```typescript
|
|
223
|
+
import { piped, pipeable, select, combineWith } from '@mmstack/primitives';
|
|
224
|
+
import { signal } from '@angular/core';
|
|
229
225
|
|
|
230
|
-
|
|
231
|
-
label = this.count.pipe(
|
|
232
|
-
(n) => n * 2, // number -> number
|
|
233
|
-
(n) => `#${n}`, // number -> string
|
|
234
|
-
);
|
|
226
|
+
const count = piped(1);
|
|
235
227
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
228
|
+
// Map: value -> value
|
|
229
|
+
const label = count.map(
|
|
230
|
+
(n) => n * 2,
|
|
231
|
+
(n) => (num: n),
|
|
232
|
+
{ equal: (a, b) => a.num === b.num },
|
|
233
|
+
);
|
|
241
234
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
235
|
+
// Pipe: signal -> signal
|
|
236
|
+
const base = pipeable(signal(10));
|
|
237
|
+
const total = count.pipe(select((n) => n * 3)).pipe(combineWith(count, (a, b) => a + b));
|
|
246
238
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
const example2 = pipeable(signal(1)); // PipeableSignal<number, WritableSignal<number>> (a writable signal + pipe)
|
|
250
|
-
const example3 = pipeable(mutable({ name: 'john' })); // This returns a pipeable mutable signal (you get the point :) )
|
|
239
|
+
label(); // e.g., "#2"
|
|
240
|
+
total(); // reactive sum
|
|
251
241
|
```
|
|
252
242
|
|
|
253
243
|
### mapArray
|
|
@@ -477,6 +477,91 @@ function mapArray(source, map, options) {
|
|
|
477
477
|
});
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
/** Project with optional equality. Pure & sync. */
|
|
481
|
+
const select = (projector, opt) => (src) => computed(() => projector(src()), opt);
|
|
482
|
+
/** Combine with another signal using a projector. */
|
|
483
|
+
const combineWith = (other, project, opt) => (src) => computed(() => project(src(), other()), opt);
|
|
484
|
+
/** Only re-emit when equal(prev, next) is false. */
|
|
485
|
+
const distinct = (equal = Object.is) => (src) => computed(() => src(), { equal });
|
|
486
|
+
/** map to new value */
|
|
487
|
+
const map = (fn) => (src) => computed(() => fn(src()));
|
|
488
|
+
/** filter values, keeping the last value if it was ever available, if first value is filtered will return undefined */
|
|
489
|
+
const filter = (predicate) => (src) => linkedSignal({
|
|
490
|
+
source: src,
|
|
491
|
+
computation: (next, prev) => {
|
|
492
|
+
if (predicate(next))
|
|
493
|
+
return next;
|
|
494
|
+
return prev?.source;
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
/** tap into the value */
|
|
498
|
+
const tap = (fn) => (src) => {
|
|
499
|
+
effect(() => fn(src()));
|
|
500
|
+
return src;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Decorate any `Signal<T>` with a chainable `.pipe(...)` method.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* const s = pipeable(signal(1)); // WritableSignal<number> (+ pipe)
|
|
508
|
+
* const label = s.pipe(n => n * 2, n => `#${n}`); // Signal<string> (+ pipe)
|
|
509
|
+
* label(); // "#2"
|
|
510
|
+
*/
|
|
511
|
+
function pipeable(signal) {
|
|
512
|
+
const internal = signal;
|
|
513
|
+
const mapImpl = (...fns) => {
|
|
514
|
+
const last = fns.at(-1);
|
|
515
|
+
let opt;
|
|
516
|
+
if (last && typeof last !== 'function') {
|
|
517
|
+
fns = fns.slice(0, -1);
|
|
518
|
+
opt = last;
|
|
519
|
+
}
|
|
520
|
+
if (fns.length === 0)
|
|
521
|
+
return internal;
|
|
522
|
+
if (fns.length === 1) {
|
|
523
|
+
const fn = fns[0];
|
|
524
|
+
return pipeable(computed(() => fn(internal()), opt));
|
|
525
|
+
}
|
|
526
|
+
const transformer = (input) => fns.reduce((acc, fn) => fn(acc), input);
|
|
527
|
+
return pipeable(computed(() => transformer(internal()), opt));
|
|
528
|
+
};
|
|
529
|
+
const pipeImpl = (...ops) => {
|
|
530
|
+
if (ops.length === 0)
|
|
531
|
+
return internal;
|
|
532
|
+
return ops.reduce((src, op) => pipeable(op(src)), internal);
|
|
533
|
+
};
|
|
534
|
+
Object.defineProperties(internal, {
|
|
535
|
+
map: {
|
|
536
|
+
value: mapImpl,
|
|
537
|
+
configurable: true,
|
|
538
|
+
enumerable: false,
|
|
539
|
+
writable: false,
|
|
540
|
+
},
|
|
541
|
+
pipe: {
|
|
542
|
+
value: pipeImpl,
|
|
543
|
+
configurable: true,
|
|
544
|
+
enumerable: false,
|
|
545
|
+
writable: false,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
return internal;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Create a new **writable** signal and return it as a `PipableSignal`.
|
|
552
|
+
*
|
|
553
|
+
* The returned value is a `WritableSignal<T>` with `.set`, `.update`, `.asReadonly`
|
|
554
|
+
* still available (via intersection type), plus a chainable `.pipe(...)`.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* const count = piped(1); // WritableSignal<number> (+ pipe)
|
|
558
|
+
* const even = count.pipe(n => n % 2 === 0); // Signal<boolean> (+ pipe)
|
|
559
|
+
* count.update(n => n + 1);
|
|
560
|
+
*/
|
|
561
|
+
function piped(initial, opt) {
|
|
562
|
+
return pipeable(signal(initial, opt));
|
|
563
|
+
}
|
|
564
|
+
|
|
480
565
|
/**
|
|
481
566
|
* Creates a read-only signal that reactively tracks whether a CSS media query
|
|
482
567
|
* string currently matches.
|
|
@@ -1406,5 +1491,5 @@ function withHistory(source, opt) {
|
|
|
1406
1491
|
* Generated bundle index. Do not edit.
|
|
1407
1492
|
*/
|
|
1408
1493
|
|
|
1409
|
-
export { debounce, debounced, derived, elementVisibility, isDerivation, isMutable, mapArray, mediaQuery, mousePosition, mutable, networkStatus, pageVisibility, prefersDarkMode, prefersReducedMotion, scrollPosition, sensor, stored, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toWritable, until, windowSize, withHistory };
|
|
1494
|
+
export { combineWith, debounce, debounced, derived, distinct, elementVisibility, filter, isDerivation, isMutable, map, mapArray, mediaQuery, mousePosition, mutable, networkStatus, pageVisibility, pipeable, piped, prefersDarkMode, prefersReducedMotion, scrollPosition, select, sensor, stored, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toWritable, until, windowSize, withHistory };
|
|
1410
1495
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|