bireactive 0.2.1 → 0.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 +3 -3
- package/dist/core/values/num.d.ts +6 -0
- package/dist/core/values/num.js +20 -0
- package/dist/core/values/tri.d.ts +9 -7
- package/dist/core/values/tri.js +11 -11
- package/dist/core/values/vec.d.ts +8 -1
- package/dist/core/values/vec.js +26 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# bi-reactive
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[npm](https://www.npmjs.com/package/bireactive) · [GitHub](https://github.com/OrionReed/bireactive) · [site](https://orionreed.github.io/bireactive/)
|
|
4
|
+
|
|
5
|
+
A signals-like bidirectional reactive programming system where edges can go both ways. Forward and backward propagation are handled by the engine, with the same set of caveats as regular reactive programming.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -25,6 +25,12 @@ export declare class Num extends Cell<V> {
|
|
|
25
25
|
/** Affine `v ↦ k·v + off`. Invertible iff k ≠ 0; readability alias
|
|
26
26
|
* for `.scale(k).add(off)`. */
|
|
27
27
|
affine(k: Val<number>, off: Val<number>): this;
|
|
28
|
+
/** `sin(this)` (radians). Forward lands in [−1, 1]; the inverse is
|
|
29
|
+
* multi-valued, so a write clamps to that domain and returns the
|
|
30
|
+
* pre-image nearest the current source — the drag stays on its branch. */
|
|
31
|
+
sin(): this;
|
|
32
|
+
/** `exp(this)` — bijection on the reals; inverse is the natural log. */
|
|
33
|
+
exp(): this;
|
|
28
34
|
/** Lossy clamping lens to `[lo, hi]`. PutGet only (a write outside
|
|
29
35
|
* the range reads back clamped, not as written). */
|
|
30
36
|
clamp(lo: Val<V>, hi: Val<V>): this;
|
package/dist/core/values/num.js
CHANGED
|
@@ -11,6 +11,11 @@ export const scale = (a, k) => a * k;
|
|
|
11
11
|
export const lerp = (a, b, t) => a + (b - a) * t;
|
|
12
12
|
export const metric = (a, b) => Math.abs(a - b);
|
|
13
13
|
export const equals = (a, b) => a === b;
|
|
14
|
+
const TAU = 2 * Math.PI;
|
|
15
|
+
/** Representative of `x + 2πk` nearest `s` (shortest-arc branch pick). */
|
|
16
|
+
const nearestTo = (s, x) => x + TAU * Math.round((s - x) / TAU);
|
|
17
|
+
/** Clamp to the sin/cos domain `[-1, 1]`. */
|
|
18
|
+
const unit = (t) => (t < -1 ? -1 : t > 1 ? 1 : t);
|
|
14
19
|
const linearImpl = { add, sub, scale };
|
|
15
20
|
const packImpl = {
|
|
16
21
|
dim: 1,
|
|
@@ -49,6 +54,21 @@ export class Num extends Cell {
|
|
|
49
54
|
const of = reader(off);
|
|
50
55
|
return this.lens(v => v * kf() + of(), n => (n - of()) / kf());
|
|
51
56
|
}
|
|
57
|
+
/** `sin(this)` (radians). Forward lands in [−1, 1]; the inverse is
|
|
58
|
+
* multi-valued, so a write clamps to that domain and returns the
|
|
59
|
+
* pre-image nearest the current source — the drag stays on its branch. */
|
|
60
|
+
sin() {
|
|
61
|
+
return this.lens(v => Math.sin(v), (target, s) => {
|
|
62
|
+
const p = Math.asin(unit(target));
|
|
63
|
+
const a = nearestTo(s, p);
|
|
64
|
+
const b = nearestTo(s, Math.PI - p);
|
|
65
|
+
return Math.abs(a - s) <= Math.abs(b - s) ? a : b;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** `exp(this)` — bijection on the reals; inverse is the natural log. */
|
|
69
|
+
exp() {
|
|
70
|
+
return this.lens(v => Math.exp(v), n => Math.log(n));
|
|
71
|
+
}
|
|
52
72
|
/** Lossy clamping lens to `[lo, hi]`. PutGet only (a write outside
|
|
53
73
|
* the range reads back clamped, not as written). */
|
|
54
74
|
clamp(lo, hi) {
|
|
@@ -17,13 +17,15 @@ export declare class Tri extends Cell<V> {
|
|
|
17
17
|
constructor(v?: V);
|
|
18
18
|
/** Kleene negation. Involution; fixed at `"mixed"`. */
|
|
19
19
|
not(): this;
|
|
20
|
-
/** Aggregate over N writable
|
|
21
|
-
* all-false → `false`, disagreement
|
|
22
|
-
* `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
/** Aggregate over N writable `Bool` / `Tri` children. Read: all-true →
|
|
21
|
+
* `true`, all-false → `false`, any disagreement (or any child already
|
|
22
|
+
* `"mixed"`) → `"mixed"`. Write: `true` / `false` broadcast to every
|
|
23
|
+
* child, recursing through nested aggregates; `"mixed"` is a no-op. */
|
|
24
|
+
static allOf(parents: readonly (Bool | Tri)[]): Writable<Tri>;
|
|
25
|
+
/** Dual of `allOf` (Kleene OR) over `Bool` / `Tri` children: any-true →
|
|
26
|
+
* `true`, all-false → `false`, else (or any child `"mixed"`) →
|
|
27
|
+
* `"mixed"`. Same broadcast write policy. */
|
|
28
|
+
static anyOf(parents: readonly (Bool | Tri)[]): Writable<Tri>;
|
|
27
29
|
}
|
|
28
30
|
/** Writable `Tri`. Strict factory: `Tri.value | Writable<Tri>` in,
|
|
29
31
|
* `Writable<Tri>` out. Default initial value is `"mixed"`. */
|
package/dist/core/values/tri.js
CHANGED
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
// `Tri.value ∈ { true, false, "mixed" }` — Bool plus an unknown state
|
|
4
4
|
// fixed under negation. Strong-Kleene AND/OR follow the partial-info
|
|
5
5
|
// reading (`mixed AND false` → `false`, `mixed AND true` → `mixed`).
|
|
6
|
-
//
|
|
7
|
-
// Headline use: aggregate N booleans via `Tri.allOf` / `Tri.anyOf`
|
|
8
|
-
// (all-agree → that value, disagreement → `"mixed"`). Writing the
|
|
9
|
-
// aggregate broadcasts to every parent ("select all" / "deselect all");
|
|
10
|
-
// writing `"mixed"` is a no-op. Morally `Maybe<Bool>` — the basis for
|
|
11
|
-
// mixed-state checkbox trees and "loading" predicate states.
|
|
12
6
|
import { Cell } from "../cell.js";
|
|
13
7
|
const equals = (a, b) => a === b;
|
|
14
8
|
/** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
|
|
@@ -40,14 +34,17 @@ export class Tri extends Cell {
|
|
|
40
34
|
not() {
|
|
41
35
|
return this.lens(not, not);
|
|
42
36
|
}
|
|
43
|
-
/** Aggregate over N writable
|
|
44
|
-
* all-false → `false`, disagreement
|
|
45
|
-
* `
|
|
37
|
+
/** Aggregate over N writable `Bool` / `Tri` children. Read: all-true →
|
|
38
|
+
* `true`, all-false → `false`, any disagreement (or any child already
|
|
39
|
+
* `"mixed"`) → `"mixed"`. Write: `true` / `false` broadcast to every
|
|
40
|
+
* child, recursing through nested aggregates; `"mixed"` is a no-op. */
|
|
46
41
|
static allOf(parents) {
|
|
47
42
|
return Tri.lens(parents, (vs) => {
|
|
48
43
|
let anyT = false;
|
|
49
44
|
let anyF = false;
|
|
50
45
|
for (const v of vs) {
|
|
46
|
+
if (v === "mixed")
|
|
47
|
+
return "mixed";
|
|
51
48
|
if (v)
|
|
52
49
|
anyT = true;
|
|
53
50
|
else
|
|
@@ -62,13 +59,16 @@ export class Tri extends Cell {
|
|
|
62
59
|
return parents.map(() => target);
|
|
63
60
|
});
|
|
64
61
|
}
|
|
65
|
-
/** Dual of `allOf` (Kleene OR)
|
|
66
|
-
* `false`, else `"mixed"
|
|
62
|
+
/** Dual of `allOf` (Kleene OR) over `Bool` / `Tri` children: any-true →
|
|
63
|
+
* `true`, all-false → `false`, else (or any child `"mixed"`) →
|
|
64
|
+
* `"mixed"`. Same broadcast write policy. */
|
|
67
65
|
static anyOf(parents) {
|
|
68
66
|
return Tri.lens(parents, (vs) => {
|
|
69
67
|
let anyT = false;
|
|
70
68
|
let anyF = false;
|
|
71
69
|
for (const v of vs) {
|
|
70
|
+
if (v === "mixed")
|
|
71
|
+
return "mixed";
|
|
72
72
|
if (v)
|
|
73
73
|
anyT = true;
|
|
74
74
|
else
|
|
@@ -14,6 +14,8 @@ export declare const metric: (a: V, b: V) => number;
|
|
|
14
14
|
export declare const equals: (a: V, b: V) => boolean;
|
|
15
15
|
export declare const normalize: (v: V) => V;
|
|
16
16
|
export declare const perp: (v: V) => V;
|
|
17
|
+
export declare const rotateAbout: (v: V, p: V, dθ: number) => V;
|
|
18
|
+
export declare const scaleAbout: (v: V, p: V, k: number) => V;
|
|
17
19
|
/** Tangent point on the circle (radius `r`, centre `c`) from external
|
|
18
20
|
* point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
|
|
19
21
|
* (y-down screen coords flip the visual sense). Returns `c` if `p` is
|
|
@@ -32,7 +34,12 @@ export declare class Vec extends Cell<V> {
|
|
|
32
34
|
constructor(v?: V);
|
|
33
35
|
add(b: Val<V>): this;
|
|
34
36
|
sub(b: Val<V>): this;
|
|
35
|
-
scale
|
|
37
|
+
/** Uniform scale by `k` about `pivot` (default origin). Inverse scales
|
|
38
|
+
* by `1/k`; exact bijection for `k ≠ 0`. */
|
|
39
|
+
scale(k: Val<number>, pivot?: Val<V>): this;
|
|
40
|
+
/** Rotate by `angle` (radians) about `pivot` (default origin). Inverse
|
|
41
|
+
* rotates by `−angle`; exact bijection. */
|
|
42
|
+
rotate(angle: Val<number>, pivot?: Val<V>): this;
|
|
36
43
|
offset(dx: Val<number>, dy: Val<number>): this;
|
|
37
44
|
up(n: Val<number>): this;
|
|
38
45
|
down(n: Val<number>): this;
|
package/dist/core/values/vec.js
CHANGED
|
@@ -20,6 +20,18 @@ export const normalize = (v) => {
|
|
|
20
20
|
return m === 0 ? { x: 0, y: 0 } : { x: v.x / m, y: v.y / m };
|
|
21
21
|
};
|
|
22
22
|
export const perp = (v) => ({ x: v.y, y: -v.x });
|
|
23
|
+
export const rotateAbout = (v, p, dθ) => {
|
|
24
|
+
const cos = Math.cos(dθ);
|
|
25
|
+
const sin = Math.sin(dθ);
|
|
26
|
+
const dx = v.x - p.x;
|
|
27
|
+
const dy = v.y - p.y;
|
|
28
|
+
return { x: p.x + cos * dx - sin * dy, y: p.y + sin * dx + cos * dy };
|
|
29
|
+
};
|
|
30
|
+
export const scaleAbout = (v, p, k) => ({
|
|
31
|
+
x: p.x + k * (v.x - p.x),
|
|
32
|
+
y: p.y + k * (v.y - p.y),
|
|
33
|
+
});
|
|
34
|
+
const ORIGIN = { x: 0, y: 0 };
|
|
23
35
|
/** Tangent point on the circle (radius `r`, centre `c`) from external
|
|
24
36
|
* point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
|
|
25
37
|
* (y-down screen coords flip the visual sense). Returns `c` if `p` is
|
|
@@ -49,19 +61,7 @@ const packImpl = {
|
|
|
49
61
|
},
|
|
50
62
|
write: (a, o) => ({ x: a[o], y: a[o + 1] }),
|
|
51
63
|
};
|
|
52
|
-
const pivotalImpl = {
|
|
53
|
-
rotateAbout: (v, p, dθ) => {
|
|
54
|
-
const cos = Math.cos(dθ);
|
|
55
|
-
const sin = Math.sin(dθ);
|
|
56
|
-
const dx = v.x - p.x;
|
|
57
|
-
const dy = v.y - p.y;
|
|
58
|
-
return { x: p.x + cos * dx - sin * dy, y: p.y + sin * dx + cos * dy };
|
|
59
|
-
},
|
|
60
|
-
scaleAbout: (v, p, k) => ({
|
|
61
|
-
x: p.x + k * (v.x - p.x),
|
|
62
|
-
y: p.y + k * (v.y - p.y),
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
64
|
+
const pivotalImpl = { rotateAbout, scaleAbout };
|
|
65
65
|
export class Vec extends Cell {
|
|
66
66
|
static traits = {
|
|
67
67
|
linear: linearImpl,
|
|
@@ -94,16 +94,26 @@ export class Vec extends Cell {
|
|
|
94
94
|
return { x: n.x + o.x, y: n.y + o.y };
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
-
scale(
|
|
97
|
+
/** Uniform scale by `k` about `pivot` (default origin). Inverse scales
|
|
98
|
+
* by `1/k`; exact bijection for `k ≠ 0`. */
|
|
99
|
+
scale(k, pivot) {
|
|
98
100
|
const kf = reader(k);
|
|
101
|
+
const pf = pivot === undefined ? undefined : reader(pivot);
|
|
99
102
|
return this.lens(v => {
|
|
100
103
|
const k = kf();
|
|
101
|
-
return { x: v.x * k, y: v.y * k };
|
|
104
|
+
return pf ? scaleAbout(v, pf(), k) : { x: v.x * k, y: v.y * k };
|
|
102
105
|
}, n => {
|
|
103
106
|
const k = kf();
|
|
104
|
-
return { x: n.x / k, y: n.y / k };
|
|
107
|
+
return pf ? scaleAbout(n, pf(), 1 / k) : { x: n.x / k, y: n.y / k };
|
|
105
108
|
});
|
|
106
109
|
}
|
|
110
|
+
/** Rotate by `angle` (radians) about `pivot` (default origin). Inverse
|
|
111
|
+
* rotates by `−angle`; exact bijection. */
|
|
112
|
+
rotate(angle, pivot = ORIGIN) {
|
|
113
|
+
const af = reader(angle);
|
|
114
|
+
const pf = reader(pivot);
|
|
115
|
+
return this.lens(v => rotateAbout(v, pf(), af()), n => rotateAbout(n, pf(), -af()));
|
|
116
|
+
}
|
|
107
117
|
offset(dx, dy) {
|
|
108
118
|
const xf = reader(dx);
|
|
109
119
|
const yf = reader(dy);
|