bireactive 0.2.4 → 0.3.1

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 (132) hide show
  1. package/dist/animation/anim.js +4 -0
  2. package/dist/automerge/doc-cell.d.ts +20 -0
  3. package/dist/automerge/doc-cell.js +80 -0
  4. package/dist/automerge/index.d.ts +3 -0
  5. package/dist/automerge/index.js +12 -0
  6. package/dist/automerge/reconcile.d.ts +5 -0
  7. package/dist/automerge/reconcile.js +63 -0
  8. package/dist/coll.d.ts +7 -7
  9. package/dist/core/_counts.d.ts +48 -0
  10. package/dist/core/_counts.js +58 -0
  11. package/dist/core/cell.d.ts +182 -123
  12. package/dist/core/cell.js +1140 -721
  13. package/dist/core/debug.d.ts +25 -0
  14. package/dist/core/debug.js +121 -0
  15. package/dist/core/index.d.ts +9 -14
  16. package/dist/core/index.js +9 -14
  17. package/dist/core/lenses/aggregates.d.ts +1 -1
  18. package/dist/core/lenses/aggregates.js +4 -3
  19. package/dist/core/lenses/closed-form-policies.js +14 -9
  20. package/dist/core/lenses/decompositions.js +3 -3
  21. package/dist/core/lenses/domain-aggregates.js +5 -5
  22. package/dist/core/lenses/geometry.d.ts +1 -1
  23. package/dist/core/lenses/geometry.js +6 -7
  24. package/dist/core/lenses/index.d.ts +1 -0
  25. package/dist/core/lenses/index.js +1 -0
  26. package/dist/core/lenses/memory.d.ts +2 -2
  27. package/dist/core/lenses/memory.js +3 -3
  28. package/dist/core/lenses/snap.d.ts +18 -0
  29. package/dist/core/lenses/snap.js +145 -0
  30. package/dist/core/lenses/typed-factor.js +4 -3
  31. package/dist/core/optic.d.ts +13 -0
  32. package/dist/core/optic.js +44 -0
  33. package/dist/core/optics.d.ts +10 -0
  34. package/dist/core/optics.js +30 -0
  35. package/dist/core/store.d.ts +10 -0
  36. package/dist/core/store.js +85 -0
  37. package/dist/core/traits.d.ts +1 -0
  38. package/dist/core/values/audio.js +4 -5
  39. package/dist/core/values/box.js +7 -7
  40. package/dist/core/values/canvas.js +15 -18
  41. package/dist/core/values/color.js +5 -5
  42. package/dist/core/values/field.d.ts +70 -0
  43. package/dist/core/values/field.js +230 -0
  44. package/dist/core/values/gpu.d.ts +4 -2
  45. package/dist/core/values/gpu.js +11 -4
  46. package/dist/core/values/matrix.js +7 -7
  47. package/dist/core/values/num.d.ts +1 -1
  48. package/dist/core/values/num.js +1 -1
  49. package/dist/core/values/pose.js +4 -4
  50. package/dist/core/values/range.js +6 -6
  51. package/dist/core/values/str.js +8 -8
  52. package/dist/core/values/template.d.ts +1 -1
  53. package/dist/core/values/template.js +2 -1
  54. package/dist/core/values/transform.js +7 -7
  55. package/dist/core/values/tri.js +3 -3
  56. package/dist/core/values/vec.js +8 -12
  57. package/dist/ext/timeline.js +2 -2
  58. package/dist/formats/cst.d.ts +127 -0
  59. package/dist/formats/cst.js +280 -0
  60. package/dist/formats/edn.d.ts +2 -0
  61. package/dist/formats/edn.js +301 -0
  62. package/dist/formats/index.d.ts +6 -0
  63. package/dist/formats/index.js +8 -0
  64. package/dist/formats/json.d.ts +2 -0
  65. package/dist/formats/json.js +332 -0
  66. package/dist/formats/lens.d.ts +8 -0
  67. package/dist/formats/lens.js +51 -0
  68. package/dist/formats/toml.d.ts +2 -0
  69. package/dist/formats/toml.js +526 -0
  70. package/dist/formats/yaml.d.ts +2 -0
  71. package/dist/formats/yaml.js +661 -0
  72. package/dist/index.d.ts +10 -0
  73. package/dist/index.js +10 -0
  74. package/dist/jsx-dev-runtime.d.ts +2 -0
  75. package/dist/jsx-dev-runtime.js +5 -0
  76. package/dist/jsx-runtime.d.ts +54 -0
  77. package/dist/jsx-runtime.js +219 -0
  78. package/dist/learn/data.d.ts +49 -0
  79. package/dist/learn/data.js +181 -0
  80. package/dist/learn/index.d.ts +3 -0
  81. package/dist/learn/index.js +6 -0
  82. package/dist/learn/lens-net.d.ts +63 -0
  83. package/dist/learn/lens-net.js +219 -0
  84. package/dist/learn/mlp.d.ts +77 -0
  85. package/dist/learn/mlp.js +292 -0
  86. package/dist/propagators/csp.d.ts +13 -0
  87. package/dist/propagators/csp.js +52 -0
  88. package/dist/propagators/flex.d.ts +31 -0
  89. package/dist/propagators/flex.js +189 -0
  90. package/dist/propagators/graph.d.ts +73 -0
  91. package/dist/propagators/graph.js +543 -0
  92. package/dist/propagators/index.d.ts +8 -6
  93. package/dist/propagators/index.js +15 -6
  94. package/dist/propagators/lattice.d.ts +45 -0
  95. package/dist/propagators/lattice.js +113 -0
  96. package/dist/propagators/layout.d.ts +1 -27
  97. package/dist/propagators/layout.js +6 -175
  98. package/dist/propagators/numeric.d.ts +17 -0
  99. package/dist/propagators/numeric.js +93 -0
  100. package/dist/propagators/solver.d.ts +51 -0
  101. package/dist/propagators/solver.js +175 -0
  102. package/dist/schema/index.d.ts +1 -0
  103. package/dist/schema/index.js +3 -0
  104. package/dist/schema/lens.d.ts +121 -0
  105. package/dist/schema/lens.js +429 -0
  106. package/dist/shapes/annular-sector.js +4 -4
  107. package/dist/shapes/button.js +1 -1
  108. package/dist/shapes/circle.js +1 -1
  109. package/dist/shapes/drag-behaviors.d.ts +56 -0
  110. package/dist/shapes/drag-behaviors.js +102 -0
  111. package/dist/shapes/drag-spec.d.ts +52 -0
  112. package/dist/shapes/drag-spec.js +112 -0
  113. package/dist/shapes/handle.js +2 -2
  114. package/dist/shapes/index.d.ts +3 -1
  115. package/dist/shapes/index.js +3 -1
  116. package/dist/shapes/interaction.d.ts +2 -3
  117. package/dist/shapes/interaction.js +77 -56
  118. package/dist/shapes/label.js +7 -1
  119. package/dist/shapes/layout.d.ts +47 -1
  120. package/dist/shapes/layout.js +60 -2
  121. package/dist/shapes/rect.js +7 -7
  122. package/dist/shapes/shape.js +8 -8
  123. package/dist/web/diagram.js +2 -2
  124. package/package.json +24 -2
  125. package/dist/propagators/network.d.ts +0 -52
  126. package/dist/propagators/network.js +0 -185
  127. package/dist/propagators/propagator.d.ts +0 -12
  128. package/dist/propagators/propagator.js +0 -16
  129. package/dist/propagators/range.d.ts +0 -45
  130. package/dist/propagators/range.js +0 -147
  131. package/dist/propagators/relations.d.ts +0 -60
  132. package/dist/propagators/relations.js +0 -343
@@ -9,10 +9,10 @@ export class Rect extends Shape {
9
9
  h;
10
10
  corner;
11
11
  constructor(x, y, w, h, opts = {}) {
12
- const xs = Num.from(x);
13
- const ys = Num.from(y);
14
- const ws = Num.from(w);
15
- const hs = Num.from(h);
12
+ const xs = Num.coerce(x);
13
+ const ys = Num.coerce(y);
14
+ const ws = Num.coerce(w);
15
+ const hs = Num.coerce(h);
16
16
  super(opts.dashed ? "path" : "rect", () => ({ x: xs.value, y: ys.value, w: ws.value, h: hs.value }), opts, {
17
17
  origin: derive(() => ({
18
18
  x: xs.value + ws.value / 2,
@@ -23,7 +23,7 @@ export class Rect extends Shape {
23
23
  this.y = ys;
24
24
  this.w = ws;
25
25
  this.h = hs;
26
- this.corner = Num.from(opts.corner ?? tokens.corner);
26
+ this.corner = Num.coerce(opts.corner ?? tokens.corner);
27
27
  this.stroke(opts, true, {
28
28
  x: xs,
29
29
  y: ys,
@@ -123,8 +123,8 @@ export function rect(a, b, c, d, e) {
123
123
  if (a instanceof Vec) {
124
124
  const w = b;
125
125
  const h = c;
126
- const ws = Num.from(w);
127
- const hs = Num.from(h);
126
+ const ws = Num.coerce(w);
127
+ const hs = Num.coerce(h);
128
128
  return new Rect(derive(() => a.x.value - ws.value / 2), derive(() => a.y.value - hs.value / 2), ws, hs, d);
129
129
  }
130
130
  return new Rect(a, b, c, d, e);
@@ -1,5 +1,5 @@
1
1
  import { suspend } from "../animation/index.js";
2
- import { Box, BoxMath, Cell, cell, derive, effect, lazy, MatrixMath, mean, Num, readNow, transformBox, transformPoint, Vec, } from "../core/index.js";
2
+ import { Box, boxEdgeFrom, boxUnion, Cell, cell, derive, effect, lazy, matrixCompose, mean, Num, readNow, SKIP, toMatrixString, 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";
@@ -70,7 +70,7 @@ export class Shape {
70
70
  const cs = this._children.value
71
71
  .filter(c => !c.aside)
72
72
  .map(c => transformBox(c.localFrame.value, c.box.value));
73
- return cs.length ? BoxMath.union(...cs) : { x: 0, y: 0, w: 0, h: 0 };
73
+ return cs.length ? boxUnion(...cs) : { x: 0, y: 0, w: 0, h: 0 };
74
74
  }));
75
75
  this.box = boxSig;
76
76
  // Identity short-circuit avoids reading `origin` on no-transform groups.
@@ -79,18 +79,18 @@ 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 MatrixMath.compose(t, r, sc, { x: 0, y: 0 });
82
+ return matrixCompose(t, r, sc, { x: 0, y: 0 });
83
83
  }
84
- return MatrixMath.compose(t, r, sc, this.origin.value);
84
+ return matrixCompose(t, r, sc, this.origin.value);
85
85
  });
86
86
  this.disposers.push(effect(() => {
87
- this.el.style.transform = MatrixMath.toMatrixString(this.localFrame.value);
87
+ this.el.style.transform = toMatrixString(this.localFrame.value);
88
88
  this.el.style.opacity = String(this.opacity.value);
89
89
  }));
90
90
  }
91
91
  /** Parent-frame perimeter point toward `target`; tighter shapes override. */
92
92
  boundary(toward) {
93
- return Vec.derive(() => BoxMath.edgeFrom(transformBox(this.localFrame.value, this.box.value), toward.value));
93
+ return Vec.derive(() => boxEdgeFrom(transformBox(this.localFrame.value, this.box.value), toward.value));
94
94
  }
95
95
  #makeAnchor(u, v) {
96
96
  // Reads box/localFrame/translate; writes only translate, shifted by the
@@ -103,8 +103,8 @@ export class Shape {
103
103
  const local = { x: b.x + u * b.w, y: b.y + v * b.h };
104
104
  const currentWorld = transformPoint(m, local);
105
105
  return [
106
- undefined,
107
- undefined,
106
+ SKIP,
107
+ SKIP,
108
108
  {
109
109
  x: tNow.x + (target.x - currentWorld.x),
110
110
  y: tNow.y + (target.y - currentWorld.y),
@@ -146,8 +146,8 @@ export class Diagram extends HTMLElement {
146
146
  view(w, h) {
147
147
  if (this.#viewSet)
148
148
  return this.#viewBox;
149
- const ws = Num.from(w);
150
- const hs = Num.from(h);
149
+ const ws = Num.coerce(w);
150
+ const hs = Num.coerce(h);
151
151
  effect(() => this.setViewBox(0, 0, ws.value, hs.value));
152
152
  this.#viewSet = true;
153
153
  return this.#viewBox;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bireactive",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "Bi-directional reactive programming.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,18 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
+ "./automerge": {
15
+ "types": "./dist/automerge/index.d.ts",
16
+ "import": "./dist/automerge/index.js"
17
+ },
18
+ "./jsx-runtime": {
19
+ "types": "./dist/jsx-runtime.d.ts",
20
+ "import": "./dist/jsx-runtime.js"
21
+ },
22
+ "./jsx-dev-runtime": {
23
+ "types": "./dist/jsx-dev-runtime.d.ts",
24
+ "import": "./dist/jsx-dev-runtime.js"
25
+ },
14
26
  "./package.json": "./package.json"
15
27
  },
16
28
  "sideEffects": false,
@@ -21,7 +33,9 @@
21
33
  ],
22
34
  "scripts": {
23
35
  "dev": "vite --host",
24
- "site": "vite build && typedoc",
36
+ "site": "vite build && typedoc && vitepress build docs",
37
+ "docs": "typedoc && vitepress build docs",
38
+ "docs:dev": "typedoc && vitepress dev docs --port 5556",
25
39
  "preview": "vite preview",
26
40
  "prebuild": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
27
41
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths",
@@ -38,6 +52,9 @@
38
52
  "postversion": "npm publish && git push --follow-tags"
39
53
  },
40
54
  "dependencies": {
55
+ "@automerge/automerge-repo": "^2.6.0-subduction.34",
56
+ "@automerge/automerge-repo-network-broadcastchannel": "^2.6.0-subduction.34",
57
+ "@automerge/automerge-repo-storage-indexeddb": "^2.6.0-subduction.34",
41
58
  "prism-esm": "^1.29.0-fix.6",
42
59
  "temml": "^0.13.3"
43
60
  },
@@ -54,9 +71,14 @@
54
71
  "reactive-framework-test-suite": "^0.0.2",
55
72
  "tsc-alias": "^1.8.17",
56
73
  "typedoc": "^0.28.19",
74
+ "typedoc-plugin-markdown": "^4.12.0",
75
+ "typedoc-vitepress-theme": "^1.1.3",
57
76
  "typescript": "^6.0.3",
58
77
  "vite": "^7.3.3",
59
78
  "vite-node": "^5.3.0",
79
+ "vite-plugin-top-level-await": "^1.6.0",
80
+ "vite-plugin-wasm": "^3.6.0",
81
+ "vitepress": "^1.6.4",
60
82
  "vitest": "^4.1.7"
61
83
  },
62
84
  "keywords": [
@@ -1,52 +0,0 @@
1
- import { type Cell } from "../core/index.js";
2
- import type { Propagator } from "./propagator.js";
3
- type AnySignal = Cell<any>;
4
- export interface PropagatorsOpts {
5
- /** Max fixpoint iterations per body run. Default 1000. Hitting
6
- * the cap throws — propagators never silently diverge. */
7
- iterations?: number;
8
- /** Don't auto-run on read changes; advance via `.step()`. For
9
- * animated solvers, narrowing demos, or batched offline runs. */
10
- manual?: boolean;
11
- }
12
- export declare class Propagators {
13
- private readonly _entries;
14
- private readonly _maxIterations;
15
- private readonly _manual;
16
- private _network?;
17
- /** Propagators added but not yet first-fired; drained on the body's
18
- * next run. */
19
- private _firstFireQueue;
20
- /** Undrained fresh writes. Auto mode drains inline in the body;
21
- * manual mode persists between `step()` calls. */
22
- private _pendingFresh;
23
- constructor(opts?: PropagatorsOpts);
24
- /** Add one or more propagators (combinators returning arrays may be
25
- * spread). Each gets one atomic "first fire" in the body; later
26
- * passes are freshness-gated. Returns `this` for chaining. */
27
- add(...props: readonly (Propagator | readonly Propagator[])[]): this;
28
- /** Advance the fixpoint loop. Runs to convergence by default; pass
29
- * a smaller `maxIterations` to step N waves at a time. Only
30
- * meaningful in `manual` mode (auto drains inline). */
31
- step(maxIterations?: number): void;
32
- private _addOne;
33
- /** Number of propagators currently in the network. */
34
- get count(): number;
35
- /** Tear down the underlying reactive driver. */
36
- dispose(): void;
37
- private _install;
38
- /** Drain `_pendingFresh` wave by wave for up to `maxIterations`
39
- * passes. Each wave consumes the current fresh set and re-fires
40
- * any propagator whose expanded read-set intersects it. */
41
- private _drain;
42
- }
43
- /** Thrown when the fixpoint loop hits its iteration cap. `pending`
44
- * lists signals still changing on the last pass. */
45
- export declare class PropagatorDivergedError extends Error {
46
- readonly pending: ReadonlySet<AnySignal>;
47
- constructor(message: string, pending: ReadonlySet<AnySignal>);
48
- }
49
- export declare function propagators(opts?: PropagatorsOpts): Propagators;
50
- /** One-shot sugar for `propagators().add(...props)`. */
51
- export declare function propagate(...props: readonly (Propagator | readonly Propagator[])[]): Propagators;
52
- export {};
@@ -1,185 +0,0 @@
1
- // network.ts — propagator network holder.
2
- //
3
- // A single `network()` whose body runs a fixpoint loop over the
4
- // registered propagators, bounded by an `iterations` fuel cap. Each
5
- // pass is freshness-gated: only propagators whose reads changed in
6
- // the prior pass re-run.
7
- //
8
- // At install time each propagator's `reads` is expanded transitively
9
- // (via `transitiveDeps()`) into the effective read-set used for
10
- // subscription and gating. Without this, a propagator reading a lens
11
- // chain wouldn't re-fire when a write inside the loop updates the
12
- // chain's parent — a silent freshness gap.
13
- //
14
- // The network body self-excludes its own writes; external writes
15
- // re-fire it normally.
16
- import { network as makeNetwork, transitiveDeps } from "../core/index.js";
17
- export class Propagators {
18
- _entries = [];
19
- _maxIterations;
20
- _manual;
21
- _network;
22
- /** Propagators added but not yet first-fired; drained on the body's
23
- * next run. */
24
- _firstFireQueue = [];
25
- /** Undrained fresh writes. Auto mode drains inline in the body;
26
- * manual mode persists between `step()` calls. */
27
- _pendingFresh = new Set();
28
- constructor(opts = {}) {
29
- this._maxIterations = opts.iterations ?? 1000;
30
- this._manual = opts.manual ?? false;
31
- }
32
- /** Add one or more propagators (combinators returning arrays may be
33
- * spread). Each gets one atomic "first fire" in the body; later
34
- * passes are freshness-gated. Returns `this` for chaining. */
35
- add(...props) {
36
- const startIndex = this._entries.length;
37
- const newDeps = new Set();
38
- for (const p of props) {
39
- if (Array.isArray(p))
40
- for (const pp of p)
41
- this._addOne(pp, newDeps);
42
- else
43
- this._addOne(p, newDeps);
44
- }
45
- for (let i = startIndex; i < this._entries.length; i++) {
46
- this._firstFireQueue.push(this._entries[i].p);
47
- }
48
- if (this._network === undefined) {
49
- // Install fires the body once, draining _firstFireQueue.
50
- this._install();
51
- }
52
- else {
53
- this._network.subscribe(...newDeps);
54
- // Flush so the queue gets first-fired at add() time, even in
55
- // manual mode (the user's `step()` controls later narrowing).
56
- this._network.flush();
57
- }
58
- return this;
59
- }
60
- /** Advance the fixpoint loop. Runs to convergence by default; pass
61
- * a smaller `maxIterations` to step N waves at a time. Only
62
- * meaningful in `manual` mode (auto drains inline). */
63
- step(maxIterations = this._maxIterations) {
64
- if (this._network === undefined)
65
- return;
66
- // Pull external-dirty signals in; manual mode otherwise sits on them.
67
- this._network.flush();
68
- this._drain(maxIterations);
69
- }
70
- _addOne(p, newDeps) {
71
- const expanded = expandReads(p.reads);
72
- this._entries.push({ p, expanded });
73
- for (const s of expanded)
74
- newDeps.add(s);
75
- }
76
- /** Number of propagators currently in the network. */
77
- get count() {
78
- return this._entries.length;
79
- }
80
- /** Tear down the underlying reactive driver. */
81
- dispose() {
82
- this._network?.dispose();
83
- this._network = undefined;
84
- }
85
- _install() {
86
- // Seed the topology with every current propagator's expanded
87
- // reads; later adds grow it via `network.subscribe(...)`.
88
- const allDeps = new Set();
89
- for (const { expanded } of this._entries) {
90
- for (const s of expanded)
91
- allDeps.add(s);
92
- }
93
- this._network = makeNetwork([...allDeps], dirty => {
94
- // 1) First-fire any propagators that were just added.
95
- for (const p of this._firstFireQueue) {
96
- const changed = runPropagator(p);
97
- for (const w of changed)
98
- this._pendingFresh.add(w);
99
- }
100
- this._firstFireQueue = [];
101
- // 2) Fold external-dirty into pending fresh.
102
- for (const s of dirty)
103
- this._pendingFresh.add(s);
104
- // 3) Auto mode: drain to convergence. Manual: leave for step().
105
- if (!this._manual)
106
- this._drain(this._maxIterations);
107
- }, { manual: this._manual });
108
- }
109
- /** Drain `_pendingFresh` wave by wave for up to `maxIterations`
110
- * passes. Each wave consumes the current fresh set and re-fires
111
- * any propagator whose expanded read-set intersects it. */
112
- _drain(maxIterations) {
113
- if (this._entries.length === 0)
114
- return;
115
- let iters = 0;
116
- while (this._pendingFresh.size > 0 && iters < maxIterations) {
117
- iters++;
118
- const fresh = this._pendingFresh;
119
- this._pendingFresh = new Set();
120
- for (const { p, expanded } of this._entries) {
121
- if (!hasExpandedFreshRead(expanded, fresh))
122
- continue;
123
- const changed = runPropagator(p);
124
- for (const w of changed)
125
- this._pendingFresh.add(w);
126
- }
127
- }
128
- // Auto mode: didn't converge → throw. Manual mode: leftover sits
129
- // in _pendingFresh until the next step().
130
- if (!this._manual && iters >= this._maxIterations && this._pendingFresh.size > 0) {
131
- const stuck = this._pendingFresh;
132
- this._pendingFresh = new Set();
133
- throw new PropagatorDivergedError(`Propagators: did not converge after ${this._maxIterations} iterations. ` +
134
- `${stuck.size} cell(s) still changing.`, stuck);
135
- }
136
- }
137
- }
138
- /** Thrown when the fixpoint loop hits its iteration cap. `pending`
139
- * lists signals still changing on the last pass. */
140
- export class PropagatorDivergedError extends Error {
141
- pending;
142
- constructor(message, pending) {
143
- super(message);
144
- this.pending = pending;
145
- this.name = "PropagatorDivergedError";
146
- }
147
- }
148
- /** Expand declared reads to include transitive deps (lens-chain
149
- * parents). The result drives both subscription and fire-gating. */
150
- function expandReads(reads) {
151
- const set = new Set();
152
- for (const r of reads) {
153
- for (const dep of transitiveDeps(r))
154
- set.add(dep);
155
- }
156
- return [...set];
157
- }
158
- /** Run a propagator's `step()` and return the set of WRITE signals
159
- * whose values actually changed. */
160
- function runPropagator(p) {
161
- const before = new Array(p.writes.length);
162
- for (let i = 0; i < p.writes.length; i++)
163
- before[i] = p.writes[i].peek();
164
- p.step();
165
- const changed = new Set();
166
- for (let i = 0; i < p.writes.length; i++) {
167
- if (p.writes[i].peek() !== before[i])
168
- changed.add(p.writes[i]);
169
- }
170
- return changed;
171
- }
172
- /** True iff any signal in the expanded read-set is fresh. */
173
- function hasExpandedFreshRead(expanded, fresh) {
174
- for (const r of expanded)
175
- if (fresh.has(r))
176
- return true;
177
- return false;
178
- }
179
- export function propagators(opts = {}) {
180
- return new Propagators(opts);
181
- }
182
- /** One-shot sugar for `propagators().add(...props)`. */
183
- export function propagate(...props) {
184
- return new Propagators().add(...props);
185
- }
@@ -1,12 +0,0 @@
1
- import type { Cell, Writable } from "../core/index.js";
2
- /** Plain object, no new types required: `reads`/`writes` declare the
3
- * topology, `step()` does the work.
4
- *
5
- * `Cell<any>` (not `unknown`) lets variant subtypes assign without
6
- * casts; the framework only uses identity / `peek()`, never the type. */
7
- export interface Propagator {
8
- readonly reads: readonly Cell<any>[];
9
- readonly writes: readonly Cell<any>[];
10
- step(): void;
11
- }
12
- export declare function propagator(reads: readonly Cell<any>[], writes: readonly Writable<Cell<any>>[], step: () => void): Propagator;
@@ -1,16 +0,0 @@
1
- // propagator.ts — Propagator type + helpers.
2
- //
3
- // A propagator is a triple: `reads`, `writes`, and `step()`. The
4
- // network subscribes to `reads` and freshness-gates on them, and
5
- // detects output changes by peek-comparing `writes`.
6
- //
7
- // Propagators are plain objects over plain signal arrays (not
8
- // classes) — the non-coloring entry point: any existing signal can
9
- // participate without adopting a new type.
10
- export function propagator(
11
- // biome-ignore lint/suspicious/noExplicitAny: see header
12
- reads,
13
- // biome-ignore lint/suspicious/noExplicitAny: see header
14
- writes, step) {
15
- return { reads, writes, step };
16
- }
@@ -1,45 +0,0 @@
1
- import { type Cell, type Writable } from "../core/index.js";
2
- import { type Propagator } from "./propagator.js";
3
- /** An interval `[lo, hi]`. `[-Infinity, Infinity]` = unconstrained.
4
- * `lo > hi` = contradiction (empty interval). */
5
- export type Range = readonly [number, number];
6
- export declare const RANGE_TOP: Range;
7
- export declare function rangeEq(a: Range, b: Range): boolean;
8
- /** Range ∩ Range = intersection (greatest-lower-bound under the lattice). */
9
- export declare function rangeMeet(a: Range, b: Range): Range;
10
- /** True if the range has no values (lo > hi). */
11
- export declare function rangeIsContradiction(r: Range): boolean;
12
- /** True if the range is a single value (lo === hi). */
13
- export declare function rangeIsExact(r: Range): boolean;
14
- /** Width of the interval, or Infinity if unbounded. */
15
- export declare function rangeWidth(r: Range): number;
16
- export type RangeCell = Writable<Cell<Range>>;
17
- /** Construct a Range cell. Optionally seed with bounds. */
18
- export declare function rangeCell(lo?: number, hi?: number): RangeCell;
19
- /** Merge `partial` into `cell` via lattice intersection. Throws on
20
- * contradiction. */
21
- export declare function rangeMerge(cell: RangeCell, partial: Range): void;
22
- export declare class RangeContradiction extends Error {
23
- readonly cell: RangeCell;
24
- constructor(message: string, cell: RangeCell);
25
- }
26
- export declare function intervalAdd(a: Range, b: Range): Range;
27
- export declare function intervalSub(a: Range, b: Range): Range;
28
- /** `a + b = c` over interval cells. Three propagators that each
29
- * narrow their output; order-independent. */
30
- export declare function intervalAdder(a: RangeCell, b: RangeCell, c: RangeCell): Propagator[];
31
- /** `a = b` over interval cells. Each direction narrows to the same
32
- * intersection, so order doesn't matter. */
33
- export declare function intervalEq(a: RangeCell, b: RangeCell): Propagator[];
34
- /** Constrain a Range cell to a fixed interval. The propagator
35
- * re-applies on every fire (subscribes to itself), so external
36
- * writes that widen the cell get re-narrowed. */
37
- export declare function constrain(cell: RangeCell, lo: number, hi: number): Propagator;
38
- /** Sum of N range cells = total. N+1 propagators (total from parts,
39
- * each part from total minus the others); order-independent. */
40
- export declare function intervalSum(parts: readonly RangeCell[], total: RangeCell): Propagator[];
41
- /** Bridge a Range cell to an exact Num: the Num reflects the Range's
42
- * midpoint; writing the Num forces the Range to that singleton
43
- * (which may contradict a narrower existing bound). For wiring a
44
- * Range cell into a UI. */
45
- export declare function snap(rangeC: RangeCell, exact: Writable<Cell<number>>): Propagator[];
@@ -1,147 +0,0 @@
1
- // range.ts — interval cells + interval combinators.
2
- //
3
- // A `Range` cell holds an interval `[lo, hi]` of partial knowledge.
4
- // Operations narrow (intersect) rather than replace, so propagators
5
- // fire in any order without losing info, and termination is
6
- // structural (a bounded finite-height lattice; every fire shrinks
7
- // an interval or no-ops).
8
- //
9
- // A Range cell is just `cell<[number, number]>` with custom
10
- // equality — no new type. The "merge not replace" semantic lives in
11
- // each combinator's step body.
12
- //
13
- // Adapter combinators (`snap`) bridge to exact Num cells, so a
14
- // system can propagate partial info internally while exposing exact
15
- // signals to renderers / drag handlers / lens chains.
16
- import { cell } from "../core/index.js";
17
- import { propagator } from "./propagator.js";
18
- export const RANGE_TOP = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
19
- export function rangeEq(a, b) {
20
- return a[0] === b[0] && a[1] === b[1];
21
- }
22
- /** Range ∩ Range = intersection (greatest-lower-bound under the lattice). */
23
- export function rangeMeet(a, b) {
24
- return [Math.max(a[0], b[0]), Math.min(a[1], b[1])];
25
- }
26
- /** True if the range has no values (lo > hi). */
27
- export function rangeIsContradiction(r) {
28
- return r[0] > r[1];
29
- }
30
- /** True if the range is a single value (lo === hi). */
31
- export function rangeIsExact(r) {
32
- return r[0] === r[1] && Number.isFinite(r[0]);
33
- }
34
- /** Width of the interval, or Infinity if unbounded. */
35
- export function rangeWidth(r) {
36
- return r[1] - r[0];
37
- }
38
- /** Construct a Range cell. Optionally seed with bounds. */
39
- export function rangeCell(lo = Number.NEGATIVE_INFINITY, hi = Number.POSITIVE_INFINITY) {
40
- return cell([lo, hi], { equals: rangeEq });
41
- }
42
- /** Merge `partial` into `cell` via lattice intersection. Throws on
43
- * contradiction. */
44
- export function rangeMerge(cell, partial) {
45
- const cur = cell.peek();
46
- const merged = rangeMeet(cur, partial);
47
- if (rangeIsContradiction(merged)) {
48
- throw new RangeContradiction(`Range contradiction: cur=[${cur.join(",")}], partial=[${partial.join(",")}]`, cell);
49
- }
50
- cell.value = merged;
51
- }
52
- export class RangeContradiction extends Error {
53
- cell;
54
- constructor(message, cell) {
55
- super(message);
56
- this.cell = cell;
57
- this.name = "RangeContradiction";
58
- }
59
- }
60
- export function intervalAdd(a, b) {
61
- return [a[0] + b[0], a[1] + b[1]];
62
- }
63
- export function intervalSub(a, b) {
64
- return [a[0] - b[1], a[1] - b[0]];
65
- }
66
- /** `a + b = c` over interval cells. Three propagators that each
67
- * narrow their output; order-independent. */
68
- export function intervalAdder(a, b, c) {
69
- return [
70
- propagator([a, b], [c], () => {
71
- rangeMerge(c, intervalAdd(a.value, b.value));
72
- }),
73
- propagator([a, c], [b], () => {
74
- rangeMerge(b, intervalSub(c.value, a.value));
75
- }),
76
- propagator([b, c], [a], () => {
77
- rangeMerge(a, intervalSub(c.value, b.value));
78
- }),
79
- ];
80
- }
81
- /** `a = b` over interval cells. Each direction narrows to the same
82
- * intersection, so order doesn't matter. */
83
- export function intervalEq(a, b) {
84
- return [
85
- propagator([a], [b], () => rangeMerge(b, a.value)),
86
- propagator([b], [a], () => rangeMerge(a, b.value)),
87
- ];
88
- }
89
- /** Constrain a Range cell to a fixed interval. The propagator
90
- * re-applies on every fire (subscribes to itself), so external
91
- * writes that widen the cell get re-narrowed. */
92
- export function constrain(cell, lo, hi) {
93
- return propagator([cell], [cell], () => {
94
- rangeMerge(cell, [lo, hi]);
95
- });
96
- }
97
- /** Sum of N range cells = total. N+1 propagators (total from parts,
98
- * each part from total minus the others); order-independent. */
99
- export function intervalSum(parts, total) {
100
- const props = [];
101
- // total ⊆ sum(parts)
102
- props.push(propagator(parts, [total], () => {
103
- let lo = 0;
104
- let hi = 0;
105
- for (const p of parts) {
106
- lo += p.value[0];
107
- hi += p.value[1];
108
- }
109
- rangeMerge(total, [lo, hi]);
110
- }));
111
- // For each part: part ⊆ total − sum(other parts)
112
- for (let i = 0; i < parts.length; i++) {
113
- const target = parts[i];
114
- const others = parts.filter((_, j) => j !== i);
115
- props.push(propagator([total, ...others], [target], () => {
116
- let oLo = 0;
117
- let oHi = 0;
118
- for (const o of others) {
119
- oLo += o.value[0];
120
- oHi += o.value[1];
121
- }
122
- rangeMerge(target, [total.value[0] - oHi, total.value[1] - oLo]);
123
- }));
124
- }
125
- return props;
126
- }
127
- /** Bridge a Range cell to an exact Num: the Num reflects the Range's
128
- * midpoint; writing the Num forces the Range to that singleton
129
- * (which may contradict a narrower existing bound). For wiring a
130
- * Range cell into a UI. */
131
- export function snap(rangeC, exact) {
132
- return [
133
- // range → exact midpoint
134
- propagator([rangeC], [exact], () => {
135
- const [lo, hi] = rangeC.value;
136
- const mid = Number.isFinite(lo) && Number.isFinite(hi) ? (lo + hi) / 2 : 0;
137
- exact.value = mid;
138
- }),
139
- // exact → range (forced singleton)
140
- propagator([exact], [rangeC], () => {
141
- const v = exact.value;
142
- rangeMerge(rangeC, [v, v]);
143
- }),
144
- ];
145
- }
146
- // No `lift` sugar: bundling a fresh cell + its propagators doesn't
147
- // fit the "combinator returns Propagator[]" shape. Wire snap() by hand.