bireactive 0.2.0

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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/animation/anim.d.ts +57 -0
  4. package/dist/animation/anim.js +318 -0
  5. package/dist/animation/combinators.d.ts +39 -0
  6. package/dist/animation/combinators.js +113 -0
  7. package/dist/animation/easings.d.ts +5 -0
  8. package/dist/animation/easings.js +5 -0
  9. package/dist/animation/index.d.ts +3 -0
  10. package/dist/animation/index.js +3 -0
  11. package/dist/assert/algebra.d.ts +20 -0
  12. package/dist/assert/algebra.js +79 -0
  13. package/dist/assert/claim.d.ts +40 -0
  14. package/dist/assert/claim.js +129 -0
  15. package/dist/assert/index.d.ts +7 -0
  16. package/dist/assert/index.js +19 -0
  17. package/dist/assert/predicates.d.ts +18 -0
  18. package/dist/assert/predicates.js +43 -0
  19. package/dist/assert/record.d.ts +20 -0
  20. package/dist/assert/record.js +78 -0
  21. package/dist/assert/scope.d.ts +42 -0
  22. package/dist/assert/scope.js +233 -0
  23. package/dist/assert/span.d.ts +37 -0
  24. package/dist/assert/span.js +68 -0
  25. package/dist/assert/tree.d.ts +22 -0
  26. package/dist/assert/tree.js +65 -0
  27. package/dist/code/code.d.ts +70 -0
  28. package/dist/code/code.js +361 -0
  29. package/dist/code/index.d.ts +2 -0
  30. package/dist/code/index.js +9 -0
  31. package/dist/code/morph.d.ts +5 -0
  32. package/dist/code/morph.js +194 -0
  33. package/dist/code/tokenize.d.ts +8 -0
  34. package/dist/code/tokenize.js +51 -0
  35. package/dist/constraints/cluster.d.ts +83 -0
  36. package/dist/constraints/cluster.js +213 -0
  37. package/dist/constraints/drivers.d.ts +15 -0
  38. package/dist/constraints/drivers.js +40 -0
  39. package/dist/constraints/factories.d.ts +73 -0
  40. package/dist/constraints/factories.js +248 -0
  41. package/dist/constraints/index.d.ts +11 -0
  42. package/dist/constraints/index.js +39 -0
  43. package/dist/constraints/interaction.d.ts +21 -0
  44. package/dist/constraints/interaction.js +148 -0
  45. package/dist/constraints/linalg.d.ts +18 -0
  46. package/dist/constraints/linalg.js +141 -0
  47. package/dist/constraints/phases.d.ts +21 -0
  48. package/dist/constraints/phases.js +60 -0
  49. package/dist/constraints/physics.d.ts +34 -0
  50. package/dist/constraints/physics.js +128 -0
  51. package/dist/constraints/rigid.d.ts +210 -0
  52. package/dist/constraints/rigid.js +835 -0
  53. package/dist/constraints/solver.d.ts +107 -0
  54. package/dist/constraints/solver.js +510 -0
  55. package/dist/constraints/term.d.ts +50 -0
  56. package/dist/constraints/term.js +80 -0
  57. package/dist/constraints/terms.d.ts +80 -0
  58. package/dist/constraints/terms.js +302 -0
  59. package/dist/constraints/world.d.ts +31 -0
  60. package/dist/constraints/world.js +245 -0
  61. package/dist/core/aggregates.d.ts +64 -0
  62. package/dist/core/aggregates.js +198 -0
  63. package/dist/core/anim.d.ts +84 -0
  64. package/dist/core/anim.js +301 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +38 -0
  67. package/dist/core/introspect.d.ts +5 -0
  68. package/dist/core/introspect.js +31 -0
  69. package/dist/core/lenses/closed-form-policies.d.ts +64 -0
  70. package/dist/core/lenses/closed-form-policies.js +452 -0
  71. package/dist/core/lenses/domain-aggregates.d.ts +54 -0
  72. package/dist/core/lenses/domain-aggregates.js +259 -0
  73. package/dist/core/lenses/factor-lens.d.ts +42 -0
  74. package/dist/core/lenses/factor-lens.js +419 -0
  75. package/dist/core/lenses/index.d.ts +5 -0
  76. package/dist/core/lenses/index.js +16 -0
  77. package/dist/core/lenses/memory.d.ts +47 -0
  78. package/dist/core/lenses/memory.js +102 -0
  79. package/dist/core/lenses/typed-factor.d.ts +45 -0
  80. package/dist/core/lenses/typed-factor.js +376 -0
  81. package/dist/core/network-utils.d.ts +14 -0
  82. package/dist/core/network-utils.js +62 -0
  83. package/dist/core/new-primitives.d.ts +33 -0
  84. package/dist/core/new-primitives.js +113 -0
  85. package/dist/core/signal.d.ts +254 -0
  86. package/dist/core/signal.js +1349 -0
  87. package/dist/core/traits.d.ts +61 -0
  88. package/dist/core/traits.js +56 -0
  89. package/dist/core/tree.d.ts +23 -0
  90. package/dist/core/tree.js +62 -0
  91. package/dist/core/values/anchor.d.ts +23 -0
  92. package/dist/core/values/anchor.js +23 -0
  93. package/dist/core/values/audio.d.ts +33 -0
  94. package/dist/core/values/audio.js +107 -0
  95. package/dist/core/values/bool.d.ts +37 -0
  96. package/dist/core/values/bool.js +75 -0
  97. package/dist/core/values/box.d.ts +77 -0
  98. package/dist/core/values/box.js +211 -0
  99. package/dist/core/values/canvas.d.ts +71 -0
  100. package/dist/core/values/canvas.js +495 -0
  101. package/dist/core/values/color.d.ts +49 -0
  102. package/dist/core/values/color.js +106 -0
  103. package/dist/core/values/flags.d.ts +18 -0
  104. package/dist/core/values/flags.js +50 -0
  105. package/dist/core/values/gpu.d.ts +74 -0
  106. package/dist/core/values/gpu.js +426 -0
  107. package/dist/core/values/matrix.d.ts +53 -0
  108. package/dist/core/values/matrix.js +140 -0
  109. package/dist/core/values/num.d.ts +62 -0
  110. package/dist/core/values/num.js +166 -0
  111. package/dist/core/values/pose.d.ts +31 -0
  112. package/dist/core/values/pose.js +83 -0
  113. package/dist/core/values/range.d.ts +83 -0
  114. package/dist/core/values/range.js +167 -0
  115. package/dist/core/values/str.d.ts +76 -0
  116. package/dist/core/values/str.js +346 -0
  117. package/dist/core/values/template.d.ts +49 -0
  118. package/dist/core/values/template.js +148 -0
  119. package/dist/core/values/transform.d.ts +49 -0
  120. package/dist/core/values/transform.js +115 -0
  121. package/dist/core/values/tri.d.ts +31 -0
  122. package/dist/core/values/tri.js +95 -0
  123. package/dist/core/values/vec.d.ts +72 -0
  124. package/dist/core/values/vec.js +219 -0
  125. package/dist/core/writable.d.ts +15 -0
  126. package/dist/core/writable.js +29 -0
  127. package/dist/ext/events.d.ts +10 -0
  128. package/dist/ext/events.js +31 -0
  129. package/dist/ext/index.d.ts +4 -0
  130. package/dist/ext/index.js +4 -0
  131. package/dist/ext/snapshot.d.ts +8 -0
  132. package/dist/ext/snapshot.js +29 -0
  133. package/dist/ext/timeline.d.ts +56 -0
  134. package/dist/ext/timeline.js +94 -0
  135. package/dist/ext/waapi.d.ts +25 -0
  136. package/dist/ext/waapi.js +198 -0
  137. package/dist/index.d.ts +8 -0
  138. package/dist/index.js +10 -0
  139. package/dist/propagators/index.d.ts +6 -0
  140. package/dist/propagators/index.js +6 -0
  141. package/dist/propagators/layout.d.ts +68 -0
  142. package/dist/propagators/layout.js +336 -0
  143. package/dist/propagators/network.d.ts +52 -0
  144. package/dist/propagators/network.js +185 -0
  145. package/dist/propagators/propagator.d.ts +12 -0
  146. package/dist/propagators/propagator.js +16 -0
  147. package/dist/propagators/range.d.ts +45 -0
  148. package/dist/propagators/range.js +147 -0
  149. package/dist/propagators/relations.d.ts +60 -0
  150. package/dist/propagators/relations.js +343 -0
  151. package/dist/shapes/annular-sector.d.ts +15 -0
  152. package/dist/shapes/annular-sector.js +64 -0
  153. package/dist/shapes/button.d.ts +14 -0
  154. package/dist/shapes/button.js +31 -0
  155. package/dist/shapes/choreographers.d.ts +22 -0
  156. package/dist/shapes/choreographers.js +69 -0
  157. package/dist/shapes/circle.d.ts +17 -0
  158. package/dist/shapes/circle.js +57 -0
  159. package/dist/shapes/clip.d.ts +5 -0
  160. package/dist/shapes/clip.js +31 -0
  161. package/dist/shapes/connect.d.ts +16 -0
  162. package/dist/shapes/connect.js +70 -0
  163. package/dist/shapes/curve.d.ts +60 -0
  164. package/dist/shapes/curve.js +285 -0
  165. package/dist/shapes/dashed.d.ts +16 -0
  166. package/dist/shapes/dashed.js +142 -0
  167. package/dist/shapes/debug.d.ts +43 -0
  168. package/dist/shapes/debug.js +97 -0
  169. package/dist/shapes/group.d.ts +5 -0
  170. package/dist/shapes/group.js +10 -0
  171. package/dist/shapes/handle.d.ts +32 -0
  172. package/dist/shapes/handle.js +88 -0
  173. package/dist/shapes/index.d.ts +23 -0
  174. package/dist/shapes/index.js +23 -0
  175. package/dist/shapes/interaction.d.ts +32 -0
  176. package/dist/shapes/interaction.js +187 -0
  177. package/dist/shapes/label.d.ts +20 -0
  178. package/dist/shapes/label.js +42 -0
  179. package/dist/shapes/layout.d.ts +29 -0
  180. package/dist/shapes/layout.js +74 -0
  181. package/dist/shapes/line.d.ts +21 -0
  182. package/dist/shapes/line.js +79 -0
  183. package/dist/shapes/list.d.ts +18 -0
  184. package/dist/shapes/list.js +51 -0
  185. package/dist/shapes/mount.d.ts +7 -0
  186. package/dist/shapes/mount.js +10 -0
  187. package/dist/shapes/path.d.ts +77 -0
  188. package/dist/shapes/path.js +227 -0
  189. package/dist/shapes/rect.d.ts +30 -0
  190. package/dist/shapes/rect.js +131 -0
  191. package/dist/shapes/shape.d.ts +132 -0
  192. package/dist/shapes/shape.js +306 -0
  193. package/dist/shapes/text.d.ts +24 -0
  194. package/dist/shapes/text.js +53 -0
  195. package/dist/shapes/tokens.d.ts +28 -0
  196. package/dist/shapes/tokens.js +27 -0
  197. package/dist/shapes/transitions.d.ts +23 -0
  198. package/dist/shapes/transitions.js +62 -0
  199. package/dist/tex/decorations.d.ts +26 -0
  200. package/dist/tex/decorations.js +116 -0
  201. package/dist/tex/index.d.ts +5 -0
  202. package/dist/tex/index.js +5 -0
  203. package/dist/tex/marker.d.ts +17 -0
  204. package/dist/tex/marker.js +63 -0
  205. package/dist/tex/motion.d.ts +43 -0
  206. package/dist/tex/motion.js +290 -0
  207. package/dist/tex/parts.d.ts +65 -0
  208. package/dist/tex/parts.js +149 -0
  209. package/dist/tex/tex.d.ts +45 -0
  210. package/dist/tex/tex.js +244 -0
  211. package/dist/web/attr.d.ts +16 -0
  212. package/dist/web/attr.js +98 -0
  213. package/dist/web/diagram.d.ts +49 -0
  214. package/dist/web/diagram.js +260 -0
  215. package/dist/web/index.d.ts +6 -0
  216. package/dist/web/index.js +6 -0
  217. package/dist/web/md-marker.d.ts +6 -0
  218. package/dist/web/md-marker.js +39 -0
  219. package/dist/web/md-tex.d.ts +6 -0
  220. package/dist/web/md-tex.js +61 -0
  221. package/dist/web/raf.d.ts +6 -0
  222. package/dist/web/raf.js +24 -0
  223. package/dist/web/viewport.d.ts +7 -0
  224. package/dist/web/viewport.js +13 -0
  225. package/package.json +87 -0
@@ -0,0 +1,148 @@
1
+ // template.ts — bidirectional text templates: typed slots ⇄ rendered string.
2
+ //
3
+ // A template is `lit₀ slot₀ lit₁ slot₁ … litₙ` — a multi-parent `Str.lens`
4
+ // over the slot cells. Forward RENDERS (interleave literals with each
5
+ // slot's formatted value); backward PARSES (split the edited string on the
6
+ // literal delimiters, decode each segment, write the changed slots back).
7
+ // Slots-as-source: the typed cells are canonical and the string is one
8
+ // view, so the same slots can drive several renderings at once (edit any,
9
+ // all stay in sync) — the thing `Str`'s views can't do.
10
+ //
11
+ // Each slot carries a `Codec<T>` (string ⇄ T). That codec is the textual
12
+ // dual of the `pack` trait: `pack : factor :: codec : template` — both
13
+ // serialize a value class into a medium (Float64Array vs string) so one
14
+ // generic lens (LSQ-solve vs parse/print) works over any class that
15
+ // supplies it. Kept local here rather than as a `TraitDict` entry; it can
16
+ // graduate to a trait once a second consumer wants it.
17
+ //
18
+ // Parse is rejection-tolerant: a segment that fails its codec (typing
19
+ // "banana" into a number slot) yields `undefined`, the engine's GetPut
20
+ // stop prunes the no-op, and that slot stays put. A missing delimiter
21
+ // rejects the whole edit. Adjacent slots with no literal between them are
22
+ // ambiguous to split — avoid empty inter-slot literals.
23
+ import { num } from "./num.js";
24
+ import { Str, str } from "./str.js";
25
+ /** Identity codec for string slots. */
26
+ export const strCodec = {
27
+ format: v => v,
28
+ parse: s => s,
29
+ };
30
+ /** Numeric codec; `int` rounds on both read and write. Rejects non-finite. */
31
+ export const numCodec = (int = false) => ({
32
+ format: v => (int ? String(Math.round(v)) : String(v)),
33
+ parse: s => {
34
+ const t = s.trim();
35
+ if (t === "")
36
+ return undefined;
37
+ const n = Number(t);
38
+ if (!Number.isFinite(n))
39
+ return undefined;
40
+ return int ? Math.round(n) : n;
41
+ },
42
+ });
43
+ /** Enumerated string codec: accepts only members of `options`. */
44
+ export const enumCodec = (options) => ({
45
+ format: v => v,
46
+ parse: s => (options.includes(s) ? s : undefined),
47
+ });
48
+ /** Slot constructors that infer the codec from the cell's value class. */
49
+ export const slot = {
50
+ str: (cell, name = "") => ({ name, cell, codec: strCodec }),
51
+ num: (cell, name = "") => ({ name, cell, codec: numCodec() }),
52
+ int: (cell, name = "") => ({ name, cell, codec: numCodec(true) }),
53
+ pick: (cell, options, name = "") => ({
54
+ name,
55
+ cell,
56
+ codec: enumCodec(options),
57
+ }),
58
+ };
59
+ const render = (literals, slots, vals) => {
60
+ let out = literals[0] ?? "";
61
+ for (let i = 0; i < slots.length; i++) {
62
+ out += slots[i].codec.format(vals[i]);
63
+ out += literals[i + 1] ?? "";
64
+ }
65
+ return out;
66
+ };
67
+ const parse = (literals, slots, edited) => {
68
+ const k = slots.length;
69
+ const reject = () => new Array(k).fill(undefined);
70
+ const updates = reject();
71
+ const lit0 = literals[0] ?? "";
72
+ if (!edited.startsWith(lit0))
73
+ return updates;
74
+ let pos = lit0.length;
75
+ for (let i = 0; i < k; i++) {
76
+ const delim = literals[i + 1] ?? "";
77
+ let segEnd;
78
+ if (delim === "") {
79
+ // Last segment runs to the end; an empty interior literal means two
80
+ // adjacent slots — unsplittable, so this slot takes the empty span.
81
+ segEnd = i === k - 1 ? edited.length : pos;
82
+ }
83
+ else {
84
+ const idx = edited.indexOf(delim, pos);
85
+ if (idx < 0)
86
+ return reject();
87
+ segEnd = idx;
88
+ }
89
+ const v = slots[i].codec.parse(edited.slice(pos, segEnd));
90
+ if (v !== undefined)
91
+ updates[i] = v;
92
+ pos = segEnd + delim.length;
93
+ }
94
+ return updates;
95
+ };
96
+ /** Core builder: `literals.length === slots.length + 1`. Returns the
97
+ * rendered text as a `Writable<Str>` that parses back into the slots. */
98
+ export function template(literals, slots) {
99
+ if (slots.length === 0)
100
+ return str(literals.join(""));
101
+ const cells = slots.map(s => s.cell);
102
+ // Heterogeneous, dynamic-length parents don't fit the static N-tuple
103
+ // overload; bind a flat signature at the boundary. `.bind(Str)` keeps the
104
+ // static `this` (the lens reads it as the constructor).
105
+ const lensN = Str.lens.bind(Str);
106
+ return lensN(cells, vals => render(literals, slots, vals), edited => parse(literals, slots, edited));
107
+ }
108
+ /** Tagged template; interpolate `slot.*` holes. Bring your own cells:
109
+ *
110
+ * tpl`Hello ${slot.str(name)}, ×${slot.int(count)}` */
111
+ export function tpl(strings, ...slots) {
112
+ return template([...strings], slots);
113
+ }
114
+ /** Parse a `:name` pattern against a kind schema, owning fresh slot cells.
115
+ * Returns the rendered URL `text` and the typed `params` cells; edit
116
+ * either side and the other stays in sync.
117
+ *
118
+ * const { text, params } = route("/users/:id/posts/:slug",
119
+ * { id: "int", slug: "str" }); */
120
+ export function route(pattern, schema) {
121
+ const literals = [];
122
+ const slots = [];
123
+ const params = {};
124
+ const re = /:([A-Za-z_][A-Za-z0-9_]*)/g;
125
+ let last = 0;
126
+ let m = re.exec(pattern);
127
+ while (m !== null) {
128
+ literals.push(pattern.slice(last, m.index));
129
+ const name = m[1];
130
+ const kind = schema[name];
131
+ if (!kind)
132
+ throw new Error(`route: ":${name}" has no schema entry`);
133
+ if (kind === "str") {
134
+ const c = str("");
135
+ params[name] = c;
136
+ slots.push(slot.str(c, name));
137
+ }
138
+ else {
139
+ const c = num(0);
140
+ params[name] = c;
141
+ slots.push(kind === "int" ? slot.int(c, name) : slot.num(c, name));
142
+ }
143
+ last = m.index + m[0].length;
144
+ m = re.exec(pattern);
145
+ }
146
+ literals.push(pattern.slice(last));
147
+ return { text: template(literals, slots), params: params };
148
+ }
@@ -0,0 +1,49 @@
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";
4
+ import type { Linear } from "../traits.js";
5
+ import { Num } from "./num.js";
6
+ import { Vec } from "./vec.js";
7
+ type V = {
8
+ translate: Inner<Vec>;
9
+ scale: Inner<Vec>;
10
+ origin: Inner<Vec>;
11
+ rotate: number;
12
+ opacity: number;
13
+ };
14
+ export declare const DEFAULT: V;
15
+ export declare const add: (a: V, b: V) => V;
16
+ export declare const sub: (a: V, b: V) => V;
17
+ export declare const scale: (a: V, k: number) => V;
18
+ export declare const lerp: (a: V, b: V, t: number) => V;
19
+ export declare const equals: (a: V, b: V) => boolean;
20
+ export declare const metric: (a: V, b: V) => number;
21
+ export declare class Transform extends Cell<V> {
22
+ static traits: {
23
+ linear: Linear<V>;
24
+ lerp: (a: V, b: V, t: number) => V;
25
+ metric: (a: V, b: V) => number;
26
+ equals: (a: V, b: V) => boolean;
27
+ };
28
+ readonly _t: typeof Transform.traits;
29
+ /** Scalar `scale` is the `.scale` Vec field lens, not an eager method;
30
+ * scalar-multiply via `Transform.lens(...)` or field writes. */
31
+ constructor(v?: V);
32
+ add(b: Val<V>): this;
33
+ sub(b: Val<V>): this;
34
+ lerp(b: Val<V>, t: Val<number>): Transform;
35
+ get translate(): this extends import("../index.js").WritableBrand ? Writable<Vec> : Vec;
36
+ get scale(): this extends import("../index.js").WritableBrand ? Writable<Vec> : Vec;
37
+ get origin(): this extends import("../index.js").WritableBrand ? Writable<Vec> : Vec;
38
+ get rotate(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
39
+ get opacity(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
40
+ /** Tween-builder, implied by the lerp trait. */
41
+ to(this: Writable<Transform>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
42
+ }
43
+ export type TransformInit = {
44
+ [K in keyof V]?: V[K];
45
+ };
46
+ /** Seed a `Writable<Transform>` from literal values. For reactive
47
+ * sources, use `Transform.lens(...)` or field-write composition. */
48
+ export declare function transform(init?: TransformInit): Writable<Transform>;
49
+ export {};
@@ -0,0 +1,115 @@
1
+ // transform.ts — reactive 2D transform.
2
+ //
3
+ // Invertibles (`add`, `sub`) return `: this` and ride on
4
+ // `Cell#lens(fwd, bwd)`; chained calls compose into a lens chain.
5
+ // Field-lens getters use `field()`, so writability propagates through
6
+ // nested chains
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";
11
+ import { Num } from "./num.js";
12
+ import { Vec, add as vAdd, equals as vEquals, lerp as vLerp, metric as vMetric, scale as vScale, sub as vSub, } from "./vec.js";
13
+ export const DEFAULT = {
14
+ translate: { x: 0, y: 0 },
15
+ scale: { x: 1, y: 1 },
16
+ origin: { x: 0, y: 0 },
17
+ rotate: 0,
18
+ opacity: 1,
19
+ };
20
+ export const add = (a, b) => ({
21
+ translate: vAdd(a.translate, b.translate),
22
+ scale: vAdd(a.scale, b.scale),
23
+ origin: vAdd(a.origin, b.origin),
24
+ rotate: a.rotate + b.rotate,
25
+ opacity: a.opacity + b.opacity,
26
+ });
27
+ export const sub = (a, b) => ({
28
+ translate: vSub(a.translate, b.translate),
29
+ scale: vSub(a.scale, b.scale),
30
+ origin: vSub(a.origin, b.origin),
31
+ rotate: a.rotate - b.rotate,
32
+ opacity: a.opacity - b.opacity,
33
+ });
34
+ export const scale = (a, k) => ({
35
+ translate: vScale(a.translate, k),
36
+ scale: vScale(a.scale, k),
37
+ origin: vScale(a.origin, k),
38
+ rotate: a.rotate * k,
39
+ opacity: a.opacity * k,
40
+ });
41
+ export const lerp = (a, b, t) => ({
42
+ translate: vLerp(a.translate, b.translate, t),
43
+ scale: vLerp(a.scale, b.scale, t),
44
+ origin: vLerp(a.origin, b.origin, t),
45
+ rotate: a.rotate + (b.rotate - a.rotate) * t,
46
+ opacity: a.opacity + (b.opacity - a.opacity) * t,
47
+ });
48
+ export const equals = (a, b) => a === b ||
49
+ (vEquals(a.translate, b.translate) &&
50
+ vEquals(a.scale, b.scale) &&
51
+ vEquals(a.origin, b.origin) &&
52
+ a.rotate === b.rotate &&
53
+ a.opacity === b.opacity);
54
+ export const metric = (a, b) => vMetric(a.translate, b.translate) +
55
+ vMetric(a.scale, b.scale) +
56
+ vMetric(a.origin, b.origin) +
57
+ Math.abs(a.rotate - b.rotate) +
58
+ Math.abs(a.opacity - b.opacity);
59
+ const linearImpl = { add, sub, scale };
60
+ export class Transform extends Cell {
61
+ static traits = { linear: linearImpl, lerp, metric, equals };
62
+ /** Scalar `scale` is the `.scale` Vec field lens, not an eager method;
63
+ * scalar-multiply via `Transform.lens(...)` or field writes. */
64
+ constructor(v = DEFAULT) {
65
+ super(v, { equals });
66
+ }
67
+ add(b) {
68
+ const bf = reader(b);
69
+ return this.lens(v => add(v, bf()), n => sub(n, bf()));
70
+ }
71
+ sub(b) {
72
+ const bf = reader(b);
73
+ return this.lens(v => sub(v, bf()), n => add(n, bf()));
74
+ }
75
+ lerp(b, t) {
76
+ return Transform.derive(() => lerp(this.value, readNow(b), readNow(t)));
77
+ }
78
+ get translate() {
79
+ return field(this, "translate", Vec);
80
+ }
81
+ get scale() {
82
+ return field(this, "scale", Vec);
83
+ }
84
+ get origin() {
85
+ return field(this, "origin", Vec);
86
+ }
87
+ get rotate() {
88
+ return field(this, "rotate", Num);
89
+ }
90
+ get opacity() {
91
+ return field(this, "opacity", Num);
92
+ }
93
+ /** Tween-builder, implied by the lerp trait. */
94
+ to(target, dur, ease) {
95
+ return tween(this, target, dur, ease);
96
+ }
97
+ }
98
+ /** Seed a `Writable<Transform>` from literal values. For reactive
99
+ * sources, use `Transform.lens(...)` or field-write composition. */
100
+ export function transform(init) {
101
+ const tr = new Transform();
102
+ if (init) {
103
+ if (init.translate !== undefined)
104
+ tr.translate.value = init.translate;
105
+ if (init.scale !== undefined)
106
+ tr.scale.value = init.scale;
107
+ if (init.origin !== undefined)
108
+ tr.origin.value = init.origin;
109
+ if (init.rotate !== undefined)
110
+ tr.rotate.value = init.rotate;
111
+ if (init.opacity !== undefined)
112
+ tr.opacity.value = init.opacity;
113
+ }
114
+ return tr;
115
+ }
@@ -0,0 +1,31 @@
1
+ import { Cell, type Init, type Writable } from "../signal.js";
2
+ import type { Bool } from "./bool.js";
3
+ type V = boolean | "mixed";
4
+ /** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
5
+ export declare const not: (a: V) => V;
6
+ /** Kleene AND: a known `false` dominates; otherwise mixed unless both
7
+ * are known and true. */
8
+ export declare const and: (a: V, b: V) => V;
9
+ /** Kleene OR: a known `true` dominates; otherwise mixed unless both
10
+ * are known and false. */
11
+ export declare const or: (a: V, b: V) => V;
12
+ export declare class Tri extends Cell<V> {
13
+ static traits: {
14
+ equals: (a: V, b: V) => boolean;
15
+ };
16
+ readonly _t: typeof Tri.traits;
17
+ constructor(v?: V);
18
+ /** Kleene negation. Involution; fixed at `"mixed"`. */
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>;
27
+ }
28
+ /** Writable `Tri`. Strict factory: `Tri.value | Writable<Tri>` in,
29
+ * `Writable<Tri>` out. Default initial value is `"mixed"`. */
30
+ export declare function tri(v?: Init<Tri>): Writable<Tri>;
31
+ export {};
@@ -0,0 +1,95 @@
1
+ // tri.ts — three-valued logical type (Kleene logic).
2
+ //
3
+ // `Tri.value ∈ { true, false, "mixed" }` — Bool plus an unknown state
4
+ // fixed under negation. Strong-Kleene AND/OR follow the partial-info
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";
13
+ const equals = (a, b) => a === b;
14
+ /** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
15
+ export const not = (a) => (a === "mixed" ? "mixed" : !a);
16
+ /** Kleene AND: a known `false` dominates; otherwise mixed unless both
17
+ * are known and true. */
18
+ export const and = (a, b) => {
19
+ if (a === false || b === false)
20
+ return false;
21
+ if (a === true && b === true)
22
+ return true;
23
+ return "mixed";
24
+ };
25
+ /** Kleene OR: a known `true` dominates; otherwise mixed unless both
26
+ * are known and false. */
27
+ export const or = (a, b) => {
28
+ if (a === true || b === true)
29
+ return true;
30
+ if (a === false && b === false)
31
+ return false;
32
+ return "mixed";
33
+ };
34
+ export class Tri extends Cell {
35
+ static traits = { equals };
36
+ constructor(v = "mixed") {
37
+ super(v, { equals });
38
+ }
39
+ /** Kleene negation. Involution; fixed at `"mixed"`. */
40
+ not() {
41
+ return this.lens(not, not);
42
+ }
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. */
46
+ static allOf(parents) {
47
+ return Tri.lens(parents, (vs) => {
48
+ let anyT = false;
49
+ let anyF = false;
50
+ for (const v of vs) {
51
+ if (v)
52
+ anyT = true;
53
+ else
54
+ anyF = true;
55
+ if (anyT && anyF)
56
+ return "mixed";
57
+ }
58
+ return anyT;
59
+ }, (target, _vs) => {
60
+ if (target === "mixed")
61
+ return parents.map(() => undefined);
62
+ return parents.map(() => target);
63
+ });
64
+ }
65
+ /** Dual of `allOf` (Kleene OR): any-true → `true`, all-false →
66
+ * `false`, else `"mixed"`. Same broadcast write policy. */
67
+ static anyOf(parents) {
68
+ return Tri.lens(parents, (vs) => {
69
+ let anyT = false;
70
+ let anyF = false;
71
+ for (const v of vs) {
72
+ if (v)
73
+ anyT = true;
74
+ else
75
+ anyF = true;
76
+ }
77
+ if (anyT && !anyF)
78
+ return true;
79
+ if (!anyT && anyF)
80
+ return false;
81
+ return "mixed";
82
+ }, (target, _vs) => {
83
+ if (target === "mixed")
84
+ return parents.map(() => undefined);
85
+ return parents.map(() => target);
86
+ });
87
+ }
88
+ }
89
+ /** Writable `Tri`. Strict factory: `Tri.value | Writable<Tri>` in,
90
+ * `Writable<Tri>` out. Default initial value is `"mixed"`. */
91
+ export function tri(v = "mixed") {
92
+ if (v instanceof Tri)
93
+ return v;
94
+ return new Tri(v);
95
+ }
@@ -0,0 +1,72 @@
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";
4
+ import type { Linear, Pack, Pivotal } from "../traits.js";
5
+ import { Num } from "./num.js";
6
+ type V = {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ export declare const add: (a: V, b: V) => V;
11
+ export declare const sub: (a: V, b: V) => V;
12
+ export declare const scale: (a: V, k: number) => V;
13
+ export declare const lerp: (a: V, b: V, t: number) => V;
14
+ export declare const metric: (a: V, b: V) => number;
15
+ export declare const equals: (a: V, b: V) => boolean;
16
+ export declare const normalize: (v: V) => V;
17
+ export declare const perp: (v: V) => V;
18
+ /** Tangent point on the circle (radius `r`, centre `c`) from external
19
+ * point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
20
+ * (y-down screen coords flip the visual sense). Returns `c` if `p` is
21
+ * inside or on the circle. */
22
+ export declare function tangentPoint(p: V, c: V, r: number, side?: 1 | -1): V;
23
+ export declare class Vec extends Cell<V> {
24
+ static traits: {
25
+ linear: Linear<V>;
26
+ lerp: (a: V, b: V, t: number) => V;
27
+ metric: (a: V, b: V) => number;
28
+ equals: (a: V, b: V) => boolean;
29
+ pack: Pack<V>;
30
+ pivotal: Pivotal<V>;
31
+ };
32
+ readonly _t: typeof Vec.traits;
33
+ constructor(v?: V);
34
+ add(b: Val<V>): this;
35
+ sub(b: Val<V>): this;
36
+ scale(k: Val<number>): this;
37
+ offset(dx: Val<number>, dy: Val<number>): this;
38
+ up(n: Val<number>): this;
39
+ down(n: Val<number>): this;
40
+ left(n: Val<number>): this;
41
+ right(n: Val<number>): this;
42
+ normalize(): Vec;
43
+ perp(): Vec;
44
+ lerp(b: Val<V>, t: Val<number>): Vec;
45
+ distance(other: Val<V>): Num;
46
+ get x(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
47
+ get y(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
48
+ get magnitude(): Num;
49
+ /** Tween-builder; `this: Writable<Vec>` gates the call to writable
50
+ * receivers. */
51
+ to(this: Writable<Vec>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
52
+ }
53
+ /** Writable `Vec` at `(x, y)`. Each axis is a literal `number` (lifted
54
+ * to a fresh seed) or an existing `Writable<Num>` (identity passthrough).
55
+ * RO sources are rejected at the type level — use `Vec.derive(...)` for
56
+ * reactive RO tracking, or `cell.value` to snapshot. Lock an axis with
57
+ * `Num.pin(c)`: `vec(slider, Num.pin(100))`. */
58
+ export declare function vec(x?: Init<Num>, y?: Init<Num>): Writable<Vec>;
59
+ /** Policy for `polar`'s inverse — which inputs absorb a write:
60
+ *
61
+ * - `rotate` — c fixed; write r and a to land on target.
62
+ * - `translate` — r and a fixed; shift c by Δ.
63
+ * - `radial` — c and a fixed; project the drag onto the ray.
64
+ * - `circular` — c and r fixed; project the drag onto the circle. */
65
+ export type PolarPolicy = "rotate" | "translate" | "radial" | "circular";
66
+ /** Vec at polar offset from `center`: `center + (r·cos a, r·sin a)`.
67
+ * Bidirectional; each input is a literal (lifted to a fresh seed) or an
68
+ * existing writable cell. RO inputs are rejected at the type level.
69
+ * `policy` selects which inputs absorb writes; lock one with
70
+ * `Num.pin(c)`: `polar(c, Num.pin(100), a)`. */
71
+ export declare function polar(center: Init<Vec>, r: Init<Num>, a: Init<Num>, policy?: PolarPolicy): Writable<Vec>;
72
+ export {};