bireactive 0.2.0 → 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.
Files changed (82) hide show
  1. package/README.md +7 -7
  2. package/dist/{core/anim.d.ts → animation/animators.d.ts} +5 -3
  3. package/dist/{core/anim.js → animation/animators.js} +8 -5
  4. package/dist/animation/index.d.ts +1 -0
  5. package/dist/animation/index.js +1 -0
  6. package/dist/constraints/cluster.js +1 -1
  7. package/dist/constraints/expose.d.ts +17 -0
  8. package/dist/constraints/expose.js +61 -0
  9. package/dist/constraints/index.d.ts +1 -0
  10. package/dist/constraints/index.js +1 -0
  11. package/dist/constraints/linalg.d.ts +1 -8
  12. package/dist/constraints/linalg.js +5 -108
  13. package/dist/core/{signal.d.ts → cell.d.ts} +29 -6
  14. package/dist/core/{signal.js → cell.js} +134 -53
  15. package/dist/core/derived-geometry.d.ts +11 -0
  16. package/dist/core/derived-geometry.js +32 -0
  17. package/dist/core/index.d.ts +5 -10
  18. package/dist/core/index.js +5 -10
  19. package/dist/core/{aggregates.d.ts → lenses/aggregates.d.ts} +3 -14
  20. package/dist/core/{aggregates.js → lenses/aggregates.js} +5 -78
  21. package/dist/core/lenses/closed-form-policies.d.ts +6 -13
  22. package/dist/core/lenses/closed-form-policies.js +14 -24
  23. package/dist/core/lenses/decompositions.d.ts +14 -0
  24. package/dist/core/lenses/decompositions.js +224 -0
  25. package/dist/core/lenses/domain-aggregates.d.ts +10 -22
  26. package/dist/core/lenses/domain-aggregates.js +25 -39
  27. package/dist/core/{new-primitives.d.ts → lenses/geometry.d.ts} +11 -14
  28. package/dist/core/{new-primitives.js → lenses/geometry.js} +23 -37
  29. package/dist/core/lenses/index.d.ts +6 -4
  30. package/dist/core/lenses/index.js +6 -15
  31. package/dist/core/lenses/typed-factor.d.ts +1 -6
  32. package/dist/core/lenses/typed-factor.js +12 -114
  33. package/dist/core/{network-utils.d.ts → lifecycle.d.ts} +1 -1
  34. package/dist/core/{network-utils.js → lifecycle.js} +2 -2
  35. package/dist/core/linalg.d.ts +10 -0
  36. package/dist/core/linalg.js +109 -0
  37. package/dist/core/values/anchor.d.ts +1 -1
  38. package/dist/core/values/audio.d.ts +1 -1
  39. package/dist/core/values/audio.js +1 -1
  40. package/dist/core/values/bool.d.ts +1 -1
  41. package/dist/core/values/bool.js +1 -1
  42. package/dist/core/values/box.d.ts +2 -3
  43. package/dist/core/values/box.js +5 -6
  44. package/dist/core/values/canvas.d.ts +1 -1
  45. package/dist/core/values/canvas.js +2 -3
  46. package/dist/core/values/color.d.ts +2 -3
  47. package/dist/core/values/color.js +3 -4
  48. package/dist/core/values/flags.d.ts +1 -1
  49. package/dist/core/values/flags.js +1 -1
  50. package/dist/core/values/matrix.d.ts +1 -1
  51. package/dist/core/values/matrix.js +2 -3
  52. package/dist/core/values/num.d.ts +8 -3
  53. package/dist/core/values/num.js +22 -2
  54. package/dist/core/values/pose.d.ts +5 -1
  55. package/dist/core/values/pose.js +11 -1
  56. package/dist/core/values/range.d.ts +6 -4
  57. package/dist/core/values/range.js +16 -11
  58. package/dist/core/values/str.d.ts +1 -1
  59. package/dist/core/values/str.js +1 -1
  60. package/dist/core/values/template.d.ts +1 -1
  61. package/dist/core/values/transform.d.ts +2 -3
  62. package/dist/core/values/transform.js +2 -3
  63. package/dist/core/values/tri.d.ts +10 -8
  64. package/dist/core/values/tri.js +12 -12
  65. package/dist/core/values/vec.d.ts +10 -4
  66. package/dist/core/values/vec.js +30 -21
  67. package/dist/ext/timeline.js +3 -3
  68. package/dist/index.d.ts +1 -0
  69. package/dist/index.js +1 -0
  70. package/dist/propagators/layout.js +1 -1
  71. package/dist/shapes/handle.js +4 -4
  72. package/dist/shapes/shape.js +7 -7
  73. package/dist/shapes/transitions.js +2 -2
  74. package/package.json +7 -2
  75. package/dist/core/introspect.d.ts +0 -5
  76. package/dist/core/introspect.js +0 -31
  77. package/dist/core/lenses/factor-lens.d.ts +0 -42
  78. package/dist/core/lenses/factor-lens.js +0 -419
  79. package/dist/core/writable.d.ts +0 -15
  80. package/dist/core/writable.js +0 -29
  81. /package/dist/{core/tree.d.ts → tree.d.ts} +0 -0
  82. /package/dist/{core/tree.js → tree.js} +0 -0
@@ -4,7 +4,8 @@
4
4
  // a 3-DOF block and writes back through it, so renderers, drag handlers,
5
5
  // IK, and physics all observe the same value. Vec / Num lenses compose
6
6
  // for consumers that care only about translation or rotation.
7
- import { Cell } from "../signal.js";
7
+ import { Cell, field } from "../cell.js";
8
+ import { Num } from "./num.js";
8
9
  export const add = (a, b) => ({
9
10
  x: a.x + b.x,
10
11
  y: a.y + b.y,
@@ -69,6 +70,15 @@ export class Pose extends Cell {
69
70
  constructor(v = { x: 0, y: 0, theta: 0 }) {
70
71
  super(v, { equals });
71
72
  }
73
+ get x() {
74
+ return field(this, "x", Num);
75
+ }
76
+ get y() {
77
+ return field(this, "y", Num);
78
+ }
79
+ get theta() {
80
+ return field(this, "theta", Num);
81
+ }
72
82
  }
73
83
  /** Writable `Pose`. Literal seeds a fresh cell; existing `Pose` passes
74
84
  * through by identity. RO sources are rejected at the type level — use
@@ -1,6 +1,5 @@
1
- import type { Easing } from "../../animation/index.js";
2
- import { type Tween } from "../anim.js";
3
- import { Cell, type Init, type Val, type Writable, type WritableBrand } from "../signal.js";
1
+ import { type Easing, type Tween } from "../../animation/index.js";
2
+ import { Cell, type Init, type Val, type Writable, type WritableBrand } from "../cell.js";
4
3
  import type { Linear, Pack } from "../traits.js";
5
4
  import { Bool } from "./bool.js";
6
5
  import { Num } from "./num.js";
@@ -44,7 +43,10 @@ export declare class Range extends Cell<V> {
44
43
  /** End endpoint. Writes preserve `lo` (end-knob semantics). */
45
44
  get hi(): this extends WritableBrand ? Writable<Num> : Num;
46
45
  get width(): Num;
47
- get center(): Num;
46
+ /** Midpoint body-drag: read returns the center; write shifts the range
47
+ * so the center matches (width preserved). `.start` is the lo-anchored
48
+ * variant; `.lo` / `.hi` edit the endpoints. */
49
+ get center(): Writable<Num>;
48
50
  /** Translate by `by`. Reads shift the interval; writes shift back. */
49
51
  shift(by: Val<number>): this;
50
52
  /** Scale uniformly about the origin. Iso for `k ≠ 0`. */
@@ -1,13 +1,12 @@
1
1
  // range.ts — reactive numeric interval `[lo, hi]`.
2
2
  //
3
3
  // Home for sliders, scrollbars, and timeline clip spans. Field lenses
4
- // give start-knob (`.lo`) and end-knob (`.hi`) drag; `.start` is the
5
- // body-drag (shifts both, preserving width); `.slider(t)` is the
6
- // bidirectional `t ↔ lo + t·(hi - lo)` iso. Traits: `linear`, `lerp`,
7
- // `equals`, `pack` (scalar-only).
8
- import { tween } from "../anim.js";
9
- import { Cell, isComputed, reader, readNow, } from "../signal.js";
10
- import { derived, field } from "../writable.js";
4
+ // give start-knob (`.lo`) and end-knob (`.hi`) drag; `.start` / `.center`
5
+ // are body-drags (shift both, preserving width anchored at lo / midpoint
6
+ // respectively); `.slider(t)` is the bidirectional `t ↔ lo + t·(hi - lo)`
7
+ // iso. Traits: `linear`, `lerp`, `equals`, `pack` (scalar-only).
8
+ import { tween } from "../../animation/index.js";
9
+ import { Cell, cachedDerive, field, isReadonly, reader, readNow, } from "../cell.js";
11
10
  import { Bool } from "./bool.js";
12
11
  import { Num, num } from "./num.js";
13
12
  export const add = (a, b) => ({ lo: a.lo + b.lo, hi: a.hi + b.hi });
@@ -70,10 +69,16 @@ export class Range extends Cell {
70
69
  return field(this, "hi", Num);
71
70
  }
72
71
  get width() {
73
- return derived(this, "width", Num, width);
72
+ return cachedDerive(this, "width", Num, width);
74
73
  }
74
+ /** Midpoint body-drag: read returns the center; write shifts the range
75
+ * so the center matches (width preserved). `.start` is the lo-anchored
76
+ * variant; `.lo` / `.hi` edit the endpoints. */
75
77
  get center() {
76
- return derived(this, "center", Num, center);
78
+ return Num.lens(this, center, (c, src) => {
79
+ const half = (src.hi - src.lo) / 2;
80
+ return { lo: c - half, hi: c + half };
81
+ });
77
82
  }
78
83
  /** Translate by `by`. Reads shift the interval; writes shift back. */
79
84
  shift(by) {
@@ -116,9 +121,9 @@ export class Range extends Cell {
116
121
  * endpoint by `eps`). Literal / RO inputs yield a bare RO `Bool`. */
117
122
  contains(v) {
118
123
  if (v instanceof Num) {
119
- // RO computed Num has no backward path → RO branch. Sources and
124
+ // RO Num has no backward path → RO branch. Sources and
120
125
  // writable lenses both accept write-back.
121
- if (!isComputed(v)) {
126
+ if (!isReadonly(v)) {
122
127
  return Bool.lens([this, v], (vals) => contains(vals[0], vals[1]), (target, vals) => {
123
128
  const [r, n] = vals;
124
129
  if (contains(r, n) === target)
@@ -1,4 +1,4 @@
1
- import { Cell, type Init, type Writable } from "../signal.js";
1
+ import { Cell, type Init, type Writable } from "../cell.js";
2
2
  type V = string;
3
3
  export declare const equals: (a: V, b: V) => boolean;
4
4
  /** Reverse a string by Unicode code points. */
@@ -16,7 +16,7 @@
16
16
  // uppercase — dual of lowercase
17
17
  // words — separator pattern between words
18
18
  // sortedUnique — source positions + original case per unique word
19
- import { Cell } from "../signal.js";
19
+ import { Cell } from "../cell.js";
20
20
  // Complement-carrying endo lens.
21
21
  //
22
22
  // The complement is state recorded forward from the source and consumed
@@ -1,4 +1,4 @@
1
- import type { Read, Writable } from "../signal.js";
1
+ import type { Read, Writable } from "../cell.js";
2
2
  import { type Num } from "./num.js";
3
3
  import { Str } from "./str.js";
4
4
  /** Textual codec for a slot value. `parse` returns `undefined` to reject. */
@@ -1,6 +1,5 @@
1
- import type { Easing } from "../../animation/index.js";
2
- import { type Tween } from "../anim.js";
3
- import { Cell, type Inner, type Val, type Writable } from "../signal.js";
1
+ import { type Easing, type Tween } from "../../animation/index.js";
2
+ import { Cell, type Inner, type Val, type Writable } from "../cell.js";
4
3
  import type { Linear } from "../traits.js";
5
4
  import { Num } from "./num.js";
6
5
  import { Vec } from "./vec.js";
@@ -5,9 +5,8 @@
5
5
  // Field-lens getters use `field()`, so writability propagates through
6
6
  // nested chains
7
7
  // (`Transform.translate.x.value = 5` works on writable receivers).
8
- import { tween } from "../anim.js";
9
- import { Cell, reader, readNow } from "../signal.js";
10
- import { field } from "../writable.js";
8
+ import { tween } from "../../animation/index.js";
9
+ import { Cell, field, reader, readNow } from "../cell.js";
11
10
  import { Num } from "./num.js";
12
11
  import { Vec, add as vAdd, equals as vEquals, lerp as vLerp, metric as vMetric, scale as vScale, sub as vSub, } from "./vec.js";
13
12
  export const DEFAULT = {
@@ -1,4 +1,4 @@
1
- import { Cell, type Init, type Writable } from "../signal.js";
1
+ import { Cell, type Init, type Writable } from "../cell.js";
2
2
  import type { Bool } from "./bool.js";
3
3
  type V = boolean | "mixed";
4
4
  /** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
@@ -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 Bools. Read: all-true → `true`,
21
- * all-false → `false`, disagreement `"mixed"`. Write: `true` /
22
- * `false` broadcast to every parent; `"mixed"` is a no-op. */
23
- static allOf(parents: readonly Bool[]): Writable<Tri>;
24
- /** Dual of `allOf` (Kleene OR): any-true `true`, all-false
25
- * `false`, else `"mixed"`. Same broadcast write policy. */
26
- static anyOf(parents: readonly Bool[]): Writable<Tri>;
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"`. */
@@ -3,13 +3,7 @@
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
- import { Cell } from "../signal.js";
6
+ import { Cell } from "../cell.js";
13
7
  const equals = (a, b) => a === b;
14
8
  /** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
15
9
  export const not = (a) => (a === "mixed" ? "mixed" : !a);
@@ -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 Bools. Read: all-true → `true`,
44
- * all-false → `false`, disagreement `"mixed"`. Write: `true` /
45
- * `false` broadcast to every parent; `"mixed"` is a no-op. */
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): any-true `true`, all-false
66
- * `false`, else `"mixed"`. Same broadcast write policy. */
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
@@ -1,6 +1,5 @@
1
- import type { Easing } from "../../animation/index.js";
2
- import { type Tween } from "../anim.js";
3
- import { Cell, type Init, type Val, type Writable } from "../signal.js";
1
+ import { type Easing, type Tween } from "../../animation/index.js";
2
+ import { Cell, type Init, type Val, type Writable } from "../cell.js";
4
3
  import type { Linear, Pack, Pivotal } from "../traits.js";
5
4
  import { Num } from "./num.js";
6
5
  type V = {
@@ -15,6 +14,8 @@ export declare const metric: (a: V, b: V) => number;
15
14
  export declare const equals: (a: V, b: V) => boolean;
16
15
  export declare const normalize: (v: V) => V;
17
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;
18
19
  /** Tangent point on the circle (radius `r`, centre `c`) from external
19
20
  * point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
20
21
  * (y-down screen coords flip the visual sense). Returns `c` if `p` is
@@ -33,7 +34,12 @@ export declare class Vec extends Cell<V> {
33
34
  constructor(v?: V);
34
35
  add(b: Val<V>): this;
35
36
  sub(b: Val<V>): this;
36
- scale(k: Val<number>): this;
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;
37
43
  offset(dx: Val<number>, dy: Val<number>): this;
38
44
  up(n: Val<number>): this;
39
45
  down(n: Val<number>): this;
@@ -2,10 +2,9 @@
2
2
  //
3
3
  // Invertibles return `: this` and ride on `Cell#lens(fwd, bwd)`;
4
4
  // chained calls compose into a lens chain. Field-lens getters use
5
- // `field()` (propagates writability); `derived()` wraps RO views.
6
- import { tween } from "../anim.js";
7
- import { Cell, reader, readNow } from "../signal.js";
8
- import { derived, field } from "../writable.js";
5
+ // `field()` (propagates writability); `cachedDerive()` wraps RO views.
6
+ import { tween } from "../../animation/index.js";
7
+ import { Cell, cachedDerive, field, reader, readNow, } from "../cell.js";
9
8
  import { Num, num } from "./num.js";
10
9
  export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
11
10
  export const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
@@ -21,6 +20,18 @@ export const normalize = (v) => {
21
20
  return m === 0 ? { x: 0, y: 0 } : { x: v.x / m, y: v.y / m };
22
21
  };
23
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 };
24
35
  /** Tangent point on the circle (radius `r`, centre `c`) from external
25
36
  * point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
26
37
  * (y-down screen coords flip the visual sense). Returns `c` if `p` is
@@ -50,19 +61,7 @@ const packImpl = {
50
61
  },
51
62
  write: (a, o) => ({ x: a[o], y: a[o + 1] }),
52
63
  };
53
- const pivotalImpl = {
54
- rotateAbout: (v, p, dθ) => {
55
- const cos = Math.cos(dθ);
56
- const sin = Math.sin(dθ);
57
- const dx = v.x - p.x;
58
- const dy = v.y - p.y;
59
- return { x: p.x + cos * dx - sin * dy, y: p.y + sin * dx + cos * dy };
60
- },
61
- scaleAbout: (v, p, k) => ({
62
- x: p.x + k * (v.x - p.x),
63
- y: p.y + k * (v.y - p.y),
64
- }),
65
- };
64
+ const pivotalImpl = { rotateAbout, scaleAbout };
66
65
  export class Vec extends Cell {
67
66
  static traits = {
68
67
  linear: linearImpl,
@@ -95,16 +94,26 @@ export class Vec extends Cell {
95
94
  return { x: n.x + o.x, y: n.y + o.y };
96
95
  });
97
96
  }
98
- scale(k) {
97
+ /** Uniform scale by `k` about `pivot` (default origin). Inverse scales
98
+ * by `1/k`; exact bijection for `k ≠ 0`. */
99
+ scale(k, pivot) {
99
100
  const kf = reader(k);
101
+ const pf = pivot === undefined ? undefined : reader(pivot);
100
102
  return this.lens(v => {
101
103
  const k = kf();
102
- return { x: v.x * k, y: v.y * k };
104
+ return pf ? scaleAbout(v, pf(), k) : { x: v.x * k, y: v.y * k };
103
105
  }, n => {
104
106
  const k = kf();
105
- 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 };
106
108
  });
107
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
+ }
108
117
  offset(dx, dy) {
109
118
  const xf = reader(dx);
110
119
  const yf = reader(dy);
@@ -146,7 +155,7 @@ export class Vec extends Cell {
146
155
  return field(this, "y", Num);
147
156
  }
148
157
  get magnitude() {
149
- return derived(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
158
+ return cachedDerive(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
150
159
  }
151
160
  /** Tween-builder; `this: Writable<Vec>` gates the call to writable
152
161
  * receivers. */
@@ -1,7 +1,7 @@
1
1
  // Timeline + Clip. A timeline is a clock plus named clips (each an
2
2
  // `[at, at + dur)` interval); `yield* tl` advances the clock to
3
3
  // `duration`. `sequential({...})` produces cumulative-start specs.
4
- import { derive, isComputed, Num, num, Range, span, } from "../core/index.js";
4
+ import { derive, isReadonly, Num, num, Range, span, } from "../core/index.js";
5
5
  class TimelineImpl {
6
6
  clock;
7
7
  duration;
@@ -36,8 +36,8 @@ function makeClip(spec, clock) {
36
36
  const dur = Num.from(spec.dur);
37
37
  const end = Num.derive(() => at.value + dur.value);
38
38
  // Bidirectional span when both inputs are writable; RO derive when
39
- // either is computed. Mirrors `ResolvedSpan`.
40
- const sp = isComputed(at) || isComputed(dur)
39
+ // either is read-only. Mirrors `ResolvedSpan`.
40
+ const sp = isReadonly(at) || isReadonly(dur)
41
41
  ? Range.derive(() => ({ lo: at.value, hi: at.value + dur.value }))
42
42
  : span(at, dur);
43
43
  const t = Num.derive(() => {
package/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from "./core/index.js";
5
5
  export * from "./ext/index.js";
6
6
  export * from "./shapes/index.js";
7
7
  export * from "./tex/index.js";
8
+ export { allNodes, atPath, isLeaf, leavesOf, node as treeNode, nodeCount, type TreeNode, walkTree, } from "./tree.js";
8
9
  export * from "./web/index.js";
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@ export * from "./core/index.js";
7
7
  export * from "./ext/index.js";
8
8
  export * from "./shapes/index.js";
9
9
  export * from "./tex/index.js";
10
+ export { allNodes, atPath, isLeaf, leavesOf, node as treeNode, nodeCount, walkTree, } from "./tree.js";
10
11
  export * from "./web/index.js";
@@ -10,7 +10,7 @@
10
10
  // `hstack` / `vstack` are CSS-flex-shaped (per-item grow/shrink vs
11
11
  // min/max). For rigid edge-to-edge layouts use `attach`,
12
12
  // `centerInside`, etc.
13
- import { isCell, readNow } from "../core/index.js";
13
+ import { isCell, readNow, } from "../core/index.js";
14
14
  import { propagator } from "./propagator.js";
15
15
  const asW = (n) => n;
16
16
  function readDeps(...vs) {
@@ -1,5 +1,5 @@
1
1
  // handle.* — writable derived shapes (draggable circles wired to a Vec).
2
- import { cell, centroidLens, midpointLens, polar as polarLens, Vec, } from "../core/index.js";
2
+ import { cell, mean, polar as polarLens, Vec, } from "../core/index.js";
3
3
  import { Circle } from "./circle.js";
4
4
  import { drag } from "./interaction.js";
5
5
  const COLOR = "var(--bireactive-handle, #2563eb)";
@@ -32,10 +32,10 @@ const anchor = (shape, u, v, opts) => handleFn(shape.at(u, v), opts);
32
32
  /** Drag handle at the centroid of N shapes' visual centers; drags translate
33
33
  * all shapes rigidly. Reads the centroid of visible positions (cf. `centroid`
34
34
  * in `shape.ts`, which works on translate deltas). */
35
- const centroidHandle = (...shapes) => handleFn(centroidLens(shapes.map(s => s.center)));
35
+ const centroidHandle = (...shapes) => handleFn(mean(shapes.map(s => s.center)));
36
36
  /** Drag handle at the midpoint of two writable Points — drags both
37
37
  * along with it. */
38
- const midpoint = (a, b, opts) => handleFn(midpointLens(a, b), opts);
38
+ const midpoint = (a, b, opts) => handleFn(mean([a, b]), opts);
39
39
  /** Rotation knob orbiting the shape's center at `radius`; drag to write
40
40
  * `shape.rotate`. */
41
41
  const rotate = (shape, radius = 40, opts) => {
@@ -73,7 +73,7 @@ const tOnPath = (p, t, opts) => {
73
73
  }
74
74
  return bestT;
75
75
  };
76
- const pos = Vec.lens([t], ([tv]) => p.pointAt(tv).value, target => [project(target)]);
76
+ const pos = Vec.lens(t, tv => p.pointAt(tv).value, target => project(target));
77
77
  return handleFn(pos, opts);
78
78
  };
79
79
  /** `handle(point)` is the atom; `.move`, `.centroid`, etc. are sugar. */
@@ -1,5 +1,5 @@
1
1
  import { suspend } from "../animation/index.js";
2
- import { Box, BoxMath, Cell, cell, centroidLens, compose, derive, effect, lazy, meanLens, Num, readNow, toMatrixString, transformBox, transformPoint, Vec, } from "../core/index.js";
2
+ import { Box, BoxMath, Cell, cell, derive, effect, lazy, MatrixMath, mean, Num, readNow, transformBox, transformPoint, Vec, } from "../core/index.js";
3
3
  import { dashedPath } from "./dashed.js";
4
4
  import { tokens } from "./tokens.js";
5
5
  export const SVG_NS = "http://www.w3.org/2000/svg";
@@ -79,12 +79,12 @@ export class Shape {
79
79
  const r = this.rotate.value;
80
80
  const sc = this.scale.value;
81
81
  if (t.x === 0 && t.y === 0 && r === 0 && sc.x === 1 && sc.y === 1) {
82
- return compose(t, r, sc, { x: 0, y: 0 });
82
+ return MatrixMath.compose(t, r, sc, { x: 0, y: 0 });
83
83
  }
84
- return compose(t, r, sc, this.origin.value);
84
+ return MatrixMath.compose(t, r, sc, this.origin.value);
85
85
  });
86
86
  this.disposers.push(effect(() => {
87
- this.el.style.transform = toMatrixString(this.localFrame.value);
87
+ this.el.style.transform = MatrixMath.toMatrixString(this.localFrame.value);
88
88
  this.el.style.opacity = String(this.opacity.value);
89
89
  }));
90
90
  }
@@ -276,15 +276,15 @@ export class Shape {
276
276
  // evenly to all members.
277
277
  /** Writable centroid of shapes' translates. */
278
278
  export function centroid(...shapes) {
279
- return centroidLens(shapes.map(s => s.translate));
279
+ return mean(shapes.map(s => s.translate));
280
280
  }
281
281
  /** Writable mean rotation. */
282
282
  export function meanRotation(...shapes) {
283
- return meanLens(Num, shapes.map(s => s.rotate));
283
+ return mean(shapes.map(s => s.rotate));
284
284
  }
285
285
  /** Writable mean scale. */
286
286
  export function meanScale(...shapes) {
287
- return meanLens(Vec, shapes.map(s => s.scale));
287
+ return mean(shapes.map(s => s.scale));
288
288
  }
289
289
  /** Lift a `Val<T>` to a `Writable<Cls<T>>` for Shape's animatable surface.
290
290
  * Writable passes through; literal seeds a cell; signal/thunk drives it via
@@ -1,8 +1,8 @@
1
1
  // Bounded transitions. Initial pose is written synchronously; the return value
2
2
  // is the time-varying part only — a `Tween` (single-axis) or `Yieldable[]`
3
3
  // (multi-axis). Either way callers `yield transitionName(s)`.
4
- import { easeIn, easeInOut, easeOut } from "../animation/index.js";
5
- import { Dir, tween } from "../core/index.js";
4
+ import { easeIn, easeInOut, easeOut, tween, } from "../animation/index.js";
5
+ import { Dir } from "../core/index.js";
6
6
  /** Fade opacity 0 → 1. */
7
7
  export const fadeIn = (s, sec = 0.3, ease = easeOut) => s.opacity.to(1, sec, ease).from(0);
8
8
  /** Fade opacity 1 → 0. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bireactive",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Bi-directional reactive programming.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -40,7 +40,12 @@
40
40
  "bench": "node --expose-gc node_modules/.bin/vite-node",
41
41
  "typecheck": "tsc --noEmit --pretty",
42
42
  "fmt": "biome check --write .",
43
- "prepublishOnly": "npm test && npm run build"
43
+ "prepublishOnly": "npm test && npm run build",
44
+ "release": "npm run release:patch",
45
+ "release:patch": "npm version patch",
46
+ "release:minor": "npm version minor",
47
+ "release:major": "npm version major",
48
+ "postversion": "npm publish && git push --follow-tags"
44
49
  },
45
50
  "dependencies": {
46
51
  "prism-esm": "^1.29.0-fix.6",
@@ -1,5 +0,0 @@
1
- import type { Cell } from "./signal.js";
2
- /** Every cell `s` transitively depends on, including itself. Raw cells
3
- * return `{s}`; lens chains return the chain plus all parents. BFS,
4
- * peeking each Computed to populate deps; the `seen` set breaks cycles. */
5
- export declare function transitiveDeps(s: Cell<unknown>): Set<Cell<unknown>>;
@@ -1,31 +0,0 @@
1
- // introspect.ts — read-only inspection of a cell's dependency graph.
2
- //
3
- // Used by `Propagators` to expand declared reads into their transitive
4
- // parent set, so a propagator reading a lens chain reacts to writes that
5
- // touch the chain's parents but not the chain's identity. Inspection is
6
- // safe: it only reads engine state and peeks `.value` to populate deps
7
- // for lazy Computeds (idempotent).
8
- /** Every cell `s` transitively depends on, including itself. Raw cells
9
- * return `{s}`; lens chains return the chain plus all parents. BFS,
10
- * peeking each Computed to populate deps; the `seen` set breaks cycles. */
11
- export function transitiveDeps(s) {
12
- const seen = new Set();
13
- const queue = [s];
14
- while (queue.length > 0) {
15
- const cur = queue.shift();
16
- if (seen.has(cur))
17
- continue;
18
- seen.add(cur);
19
- // Cast to reach engine fields the typed Cell<T> shape doesn't surface.
20
- const c = cur;
21
- if (c.getter !== undefined) {
22
- void cur.value;
23
- let l = c.deps;
24
- while (l !== undefined) {
25
- queue.push(l.dep);
26
- l = l.nextDep;
27
- }
28
- }
29
- }
30
- return seen;
31
- }
@@ -1,42 +0,0 @@
1
- import { type Cell, Num, Vec, type Writable } from "../index.js";
2
- export interface FactorLensOpts {
3
- /** Per-input mobility weights. 0 = pinned input. Defaults to all 1. */
4
- inputWeights?: readonly number[];
5
- /** Levenberg-Marquardt damping on the M×M normal matrix. Default 1e-6. */
6
- damping?: number;
7
- /** Finite-difference epsilon. Default 1e-5. */
8
- eps?: number;
9
- }
10
- export declare function factorLens(inputs: readonly Num[], forwards: readonly ((xs: readonly number[]) => number)[], opts?: FactorLensOpts): Writable<Num>[];
11
- export declare function meanDiffLens(a: Num, b: Num): {
12
- mean: Writable<Num>;
13
- diff: Writable<Num>;
14
- };
15
- export declare function procrustesLens(points: readonly Writable<Vec>[]): {
16
- centroid: Writable<Vec>;
17
- rotation: Writable<Num>;
18
- scale: Writable<Num>;
19
- };
20
- export declare function bboxLens(points: readonly Writable<Vec>[]): {
21
- center: Writable<Vec>;
22
- size: Writable<Vec>;
23
- };
24
- export declare function procrustesJacobianLens(points: readonly Writable<Vec>[]): {
25
- centroidX: Writable<Num>;
26
- centroidY: Writable<Num>;
27
- rotation: Writable<Num>;
28
- scale: Writable<Num>;
29
- };
30
- type PoseV = {
31
- x: number;
32
- y: number;
33
- theta: number;
34
- };
35
- export declare function bundleLens(pose: Writable<Cell<PoseV>>, rotateAbout: {
36
- x: number;
37
- y: number;
38
- }): {
39
- position: Writable<Vec>;
40
- rotation: Writable<Num>;
41
- };
42
- export {};