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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/animation/anim.d.ts +57 -0
- package/dist/animation/anim.js +318 -0
- package/dist/animation/combinators.d.ts +39 -0
- package/dist/animation/combinators.js +113 -0
- package/dist/animation/easings.d.ts +5 -0
- package/dist/animation/easings.js +5 -0
- package/dist/animation/index.d.ts +3 -0
- package/dist/animation/index.js +3 -0
- package/dist/assert/algebra.d.ts +20 -0
- package/dist/assert/algebra.js +79 -0
- package/dist/assert/claim.d.ts +40 -0
- package/dist/assert/claim.js +129 -0
- package/dist/assert/index.d.ts +7 -0
- package/dist/assert/index.js +19 -0
- package/dist/assert/predicates.d.ts +18 -0
- package/dist/assert/predicates.js +43 -0
- package/dist/assert/record.d.ts +20 -0
- package/dist/assert/record.js +78 -0
- package/dist/assert/scope.d.ts +42 -0
- package/dist/assert/scope.js +233 -0
- package/dist/assert/span.d.ts +37 -0
- package/dist/assert/span.js +68 -0
- package/dist/assert/tree.d.ts +22 -0
- package/dist/assert/tree.js +65 -0
- package/dist/code/code.d.ts +70 -0
- package/dist/code/code.js +361 -0
- package/dist/code/index.d.ts +2 -0
- package/dist/code/index.js +9 -0
- package/dist/code/morph.d.ts +5 -0
- package/dist/code/morph.js +194 -0
- package/dist/code/tokenize.d.ts +8 -0
- package/dist/code/tokenize.js +51 -0
- package/dist/constraints/cluster.d.ts +83 -0
- package/dist/constraints/cluster.js +213 -0
- package/dist/constraints/drivers.d.ts +15 -0
- package/dist/constraints/drivers.js +40 -0
- package/dist/constraints/factories.d.ts +73 -0
- package/dist/constraints/factories.js +248 -0
- package/dist/constraints/index.d.ts +11 -0
- package/dist/constraints/index.js +39 -0
- package/dist/constraints/interaction.d.ts +21 -0
- package/dist/constraints/interaction.js +148 -0
- package/dist/constraints/linalg.d.ts +18 -0
- package/dist/constraints/linalg.js +141 -0
- package/dist/constraints/phases.d.ts +21 -0
- package/dist/constraints/phases.js +60 -0
- package/dist/constraints/physics.d.ts +34 -0
- package/dist/constraints/physics.js +128 -0
- package/dist/constraints/rigid.d.ts +210 -0
- package/dist/constraints/rigid.js +835 -0
- package/dist/constraints/solver.d.ts +107 -0
- package/dist/constraints/solver.js +510 -0
- package/dist/constraints/term.d.ts +50 -0
- package/dist/constraints/term.js +80 -0
- package/dist/constraints/terms.d.ts +80 -0
- package/dist/constraints/terms.js +302 -0
- package/dist/constraints/world.d.ts +31 -0
- package/dist/constraints/world.js +245 -0
- package/dist/core/aggregates.d.ts +64 -0
- package/dist/core/aggregates.js +198 -0
- package/dist/core/anim.d.ts +84 -0
- package/dist/core/anim.js +301 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +38 -0
- package/dist/core/introspect.d.ts +5 -0
- package/dist/core/introspect.js +31 -0
- package/dist/core/lenses/closed-form-policies.d.ts +64 -0
- package/dist/core/lenses/closed-form-policies.js +452 -0
- package/dist/core/lenses/domain-aggregates.d.ts +54 -0
- package/dist/core/lenses/domain-aggregates.js +259 -0
- package/dist/core/lenses/factor-lens.d.ts +42 -0
- package/dist/core/lenses/factor-lens.js +419 -0
- package/dist/core/lenses/index.d.ts +5 -0
- package/dist/core/lenses/index.js +16 -0
- package/dist/core/lenses/memory.d.ts +47 -0
- package/dist/core/lenses/memory.js +102 -0
- package/dist/core/lenses/typed-factor.d.ts +45 -0
- package/dist/core/lenses/typed-factor.js +376 -0
- package/dist/core/network-utils.d.ts +14 -0
- package/dist/core/network-utils.js +62 -0
- package/dist/core/new-primitives.d.ts +33 -0
- package/dist/core/new-primitives.js +113 -0
- package/dist/core/signal.d.ts +254 -0
- package/dist/core/signal.js +1349 -0
- package/dist/core/traits.d.ts +61 -0
- package/dist/core/traits.js +56 -0
- package/dist/core/tree.d.ts +23 -0
- package/dist/core/tree.js +62 -0
- package/dist/core/values/anchor.d.ts +23 -0
- package/dist/core/values/anchor.js +23 -0
- package/dist/core/values/audio.d.ts +33 -0
- package/dist/core/values/audio.js +107 -0
- package/dist/core/values/bool.d.ts +37 -0
- package/dist/core/values/bool.js +75 -0
- package/dist/core/values/box.d.ts +77 -0
- package/dist/core/values/box.js +211 -0
- package/dist/core/values/canvas.d.ts +71 -0
- package/dist/core/values/canvas.js +495 -0
- package/dist/core/values/color.d.ts +49 -0
- package/dist/core/values/color.js +106 -0
- package/dist/core/values/flags.d.ts +18 -0
- package/dist/core/values/flags.js +50 -0
- package/dist/core/values/gpu.d.ts +74 -0
- package/dist/core/values/gpu.js +426 -0
- package/dist/core/values/matrix.d.ts +53 -0
- package/dist/core/values/matrix.js +140 -0
- package/dist/core/values/num.d.ts +62 -0
- package/dist/core/values/num.js +166 -0
- package/dist/core/values/pose.d.ts +31 -0
- package/dist/core/values/pose.js +83 -0
- package/dist/core/values/range.d.ts +83 -0
- package/dist/core/values/range.js +167 -0
- package/dist/core/values/str.d.ts +76 -0
- package/dist/core/values/str.js +346 -0
- package/dist/core/values/template.d.ts +49 -0
- package/dist/core/values/template.js +148 -0
- package/dist/core/values/transform.d.ts +49 -0
- package/dist/core/values/transform.js +115 -0
- package/dist/core/values/tri.d.ts +31 -0
- package/dist/core/values/tri.js +95 -0
- package/dist/core/values/vec.d.ts +72 -0
- package/dist/core/values/vec.js +219 -0
- package/dist/core/writable.d.ts +15 -0
- package/dist/core/writable.js +29 -0
- package/dist/ext/events.d.ts +10 -0
- package/dist/ext/events.js +31 -0
- package/dist/ext/index.d.ts +4 -0
- package/dist/ext/index.js +4 -0
- package/dist/ext/snapshot.d.ts +8 -0
- package/dist/ext/snapshot.js +29 -0
- package/dist/ext/timeline.d.ts +56 -0
- package/dist/ext/timeline.js +94 -0
- package/dist/ext/waapi.d.ts +25 -0
- package/dist/ext/waapi.js +198 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/propagators/index.d.ts +6 -0
- package/dist/propagators/index.js +6 -0
- package/dist/propagators/layout.d.ts +68 -0
- package/dist/propagators/layout.js +336 -0
- package/dist/propagators/network.d.ts +52 -0
- package/dist/propagators/network.js +185 -0
- package/dist/propagators/propagator.d.ts +12 -0
- package/dist/propagators/propagator.js +16 -0
- package/dist/propagators/range.d.ts +45 -0
- package/dist/propagators/range.js +147 -0
- package/dist/propagators/relations.d.ts +60 -0
- package/dist/propagators/relations.js +343 -0
- package/dist/shapes/annular-sector.d.ts +15 -0
- package/dist/shapes/annular-sector.js +64 -0
- package/dist/shapes/button.d.ts +14 -0
- package/dist/shapes/button.js +31 -0
- package/dist/shapes/choreographers.d.ts +22 -0
- package/dist/shapes/choreographers.js +69 -0
- package/dist/shapes/circle.d.ts +17 -0
- package/dist/shapes/circle.js +57 -0
- package/dist/shapes/clip.d.ts +5 -0
- package/dist/shapes/clip.js +31 -0
- package/dist/shapes/connect.d.ts +16 -0
- package/dist/shapes/connect.js +70 -0
- package/dist/shapes/curve.d.ts +60 -0
- package/dist/shapes/curve.js +285 -0
- package/dist/shapes/dashed.d.ts +16 -0
- package/dist/shapes/dashed.js +142 -0
- package/dist/shapes/debug.d.ts +43 -0
- package/dist/shapes/debug.js +97 -0
- package/dist/shapes/group.d.ts +5 -0
- package/dist/shapes/group.js +10 -0
- package/dist/shapes/handle.d.ts +32 -0
- package/dist/shapes/handle.js +88 -0
- package/dist/shapes/index.d.ts +23 -0
- package/dist/shapes/index.js +23 -0
- package/dist/shapes/interaction.d.ts +32 -0
- package/dist/shapes/interaction.js +187 -0
- package/dist/shapes/label.d.ts +20 -0
- package/dist/shapes/label.js +42 -0
- package/dist/shapes/layout.d.ts +29 -0
- package/dist/shapes/layout.js +74 -0
- package/dist/shapes/line.d.ts +21 -0
- package/dist/shapes/line.js +79 -0
- package/dist/shapes/list.d.ts +18 -0
- package/dist/shapes/list.js +51 -0
- package/dist/shapes/mount.d.ts +7 -0
- package/dist/shapes/mount.js +10 -0
- package/dist/shapes/path.d.ts +77 -0
- package/dist/shapes/path.js +227 -0
- package/dist/shapes/rect.d.ts +30 -0
- package/dist/shapes/rect.js +131 -0
- package/dist/shapes/shape.d.ts +132 -0
- package/dist/shapes/shape.js +306 -0
- package/dist/shapes/text.d.ts +24 -0
- package/dist/shapes/text.js +53 -0
- package/dist/shapes/tokens.d.ts +28 -0
- package/dist/shapes/tokens.js +27 -0
- package/dist/shapes/transitions.d.ts +23 -0
- package/dist/shapes/transitions.js +62 -0
- package/dist/tex/decorations.d.ts +26 -0
- package/dist/tex/decorations.js +116 -0
- package/dist/tex/index.d.ts +5 -0
- package/dist/tex/index.js +5 -0
- package/dist/tex/marker.d.ts +17 -0
- package/dist/tex/marker.js +63 -0
- package/dist/tex/motion.d.ts +43 -0
- package/dist/tex/motion.js +290 -0
- package/dist/tex/parts.d.ts +65 -0
- package/dist/tex/parts.js +149 -0
- package/dist/tex/tex.d.ts +45 -0
- package/dist/tex/tex.js +244 -0
- package/dist/web/attr.d.ts +16 -0
- package/dist/web/attr.js +98 -0
- package/dist/web/diagram.d.ts +49 -0
- package/dist/web/diagram.js +260 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +6 -0
- package/dist/web/md-marker.d.ts +6 -0
- package/dist/web/md-marker.js +39 -0
- package/dist/web/md-tex.d.ts +6 -0
- package/dist/web/md-tex.js +61 -0
- package/dist/web/raf.d.ts +6 -0
- package/dist/web/raf.js +24 -0
- package/dist/web/viewport.d.ts +7 -0
- package/dist/web/viewport.js +13 -0
- package/package.json +87 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// vec.ts — reactive 2D point.
|
|
2
|
+
//
|
|
3
|
+
// Invertibles return `: this` and ride on `Cell#lens(fwd, bwd)`;
|
|
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";
|
|
9
|
+
import { Num, num } from "./num.js";
|
|
10
|
+
export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
11
|
+
export const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
12
|
+
export const scale = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
13
|
+
export const lerp = (a, b, t) => ({
|
|
14
|
+
x: a.x + (b.x - a.x) * t,
|
|
15
|
+
y: a.y + (b.y - a.y) * t,
|
|
16
|
+
});
|
|
17
|
+
export const metric = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
18
|
+
export const equals = (a, b) => a === b || (a.x === b.x && a.y === b.y);
|
|
19
|
+
export const normalize = (v) => {
|
|
20
|
+
const m = Math.hypot(v.x, v.y);
|
|
21
|
+
return m === 0 ? { x: 0, y: 0 } : { x: v.x / m, y: v.y / m };
|
|
22
|
+
};
|
|
23
|
+
export const perp = (v) => ({ x: v.y, y: -v.x });
|
|
24
|
+
/** Tangent point on the circle (radius `r`, centre `c`) from external
|
|
25
|
+
* point `p`. `side: -1` picks the CCW tangent from `pc`, `+1` the CW
|
|
26
|
+
* (y-down screen coords flip the visual sense). Returns `c` if `p` is
|
|
27
|
+
* inside or on the circle. */
|
|
28
|
+
export function tangentPoint(p, c, r, side = -1) {
|
|
29
|
+
const dx = p.x - c.x;
|
|
30
|
+
const dy = p.y - c.y;
|
|
31
|
+
const d = Math.hypot(dx, dy);
|
|
32
|
+
if (d <= r)
|
|
33
|
+
return c;
|
|
34
|
+
const baseAngle = Math.atan2(dy, dx);
|
|
35
|
+
const offset = Math.acos(r / d);
|
|
36
|
+
const a = baseAngle + side * offset;
|
|
37
|
+
return { x: c.x + r * Math.cos(a), y: c.y + r * Math.sin(a) };
|
|
38
|
+
}
|
|
39
|
+
/** Wrap `x` to the half-open interval `(-π, π]`. */
|
|
40
|
+
const wrapToPi = (x) => x - 2 * Math.PI * Math.round(x / (2 * Math.PI));
|
|
41
|
+
/** Representative of cyclic angle `target` closest to `current`
|
|
42
|
+
* (shortest-arc inverse). */
|
|
43
|
+
const nearestAngle = (target, current) => current + wrapToPi(target - current);
|
|
44
|
+
const linearImpl = { add, sub, scale };
|
|
45
|
+
const packImpl = {
|
|
46
|
+
dim: 2,
|
|
47
|
+
read: (v, a, o) => {
|
|
48
|
+
a[o] = v.x;
|
|
49
|
+
a[o + 1] = v.y;
|
|
50
|
+
},
|
|
51
|
+
write: (a, o) => ({ x: a[o], y: a[o + 1] }),
|
|
52
|
+
};
|
|
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
|
+
};
|
|
66
|
+
export class Vec extends Cell {
|
|
67
|
+
static traits = {
|
|
68
|
+
linear: linearImpl,
|
|
69
|
+
lerp,
|
|
70
|
+
metric,
|
|
71
|
+
equals,
|
|
72
|
+
pack: packImpl,
|
|
73
|
+
pivotal: pivotalImpl,
|
|
74
|
+
};
|
|
75
|
+
constructor(v = { x: 0, y: 0 }) {
|
|
76
|
+
super(v, { equals });
|
|
77
|
+
}
|
|
78
|
+
add(b) {
|
|
79
|
+
const bf = reader(b);
|
|
80
|
+
return this.lens(v => {
|
|
81
|
+
const o = bf();
|
|
82
|
+
return { x: v.x + o.x, y: v.y + o.y };
|
|
83
|
+
}, n => {
|
|
84
|
+
const o = bf();
|
|
85
|
+
return { x: n.x - o.x, y: n.y - o.y };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
sub(b) {
|
|
89
|
+
const bf = reader(b);
|
|
90
|
+
return this.lens(v => {
|
|
91
|
+
const o = bf();
|
|
92
|
+
return { x: v.x - o.x, y: v.y - o.y };
|
|
93
|
+
}, n => {
|
|
94
|
+
const o = bf();
|
|
95
|
+
return { x: n.x + o.x, y: n.y + o.y };
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
scale(k) {
|
|
99
|
+
const kf = reader(k);
|
|
100
|
+
return this.lens(v => {
|
|
101
|
+
const k = kf();
|
|
102
|
+
return { x: v.x * k, y: v.y * k };
|
|
103
|
+
}, n => {
|
|
104
|
+
const k = kf();
|
|
105
|
+
return { x: n.x / k, y: n.y / k };
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
offset(dx, dy) {
|
|
109
|
+
const xf = reader(dx);
|
|
110
|
+
const yf = reader(dy);
|
|
111
|
+
return this.lens(v => ({ x: v.x + xf(), y: v.y + yf() }), n => ({ x: n.x - xf(), y: n.y - yf() }));
|
|
112
|
+
}
|
|
113
|
+
// Axis-aligned offset sugar — same fwd/bwd shape as offset.
|
|
114
|
+
up(n) {
|
|
115
|
+
const f = reader(n);
|
|
116
|
+
return this.lens(v => ({ x: v.x, y: v.y - f() }), o => ({ x: o.x, y: o.y + f() }));
|
|
117
|
+
}
|
|
118
|
+
down(n) {
|
|
119
|
+
const f = reader(n);
|
|
120
|
+
return this.lens(v => ({ x: v.x, y: v.y + f() }), o => ({ x: o.x, y: o.y - f() }));
|
|
121
|
+
}
|
|
122
|
+
left(n) {
|
|
123
|
+
const f = reader(n);
|
|
124
|
+
return this.lens(v => ({ x: v.x - f(), y: v.y }), o => ({ x: o.x + f(), y: o.y }));
|
|
125
|
+
}
|
|
126
|
+
right(n) {
|
|
127
|
+
const f = reader(n);
|
|
128
|
+
return this.lens(v => ({ x: v.x + f(), y: v.y }), o => ({ x: o.x - f(), y: o.y }));
|
|
129
|
+
}
|
|
130
|
+
normalize() {
|
|
131
|
+
return Vec.derive(() => normalize(this.value));
|
|
132
|
+
}
|
|
133
|
+
perp() {
|
|
134
|
+
return Vec.derive(() => perp(this.value));
|
|
135
|
+
}
|
|
136
|
+
lerp(b, t) {
|
|
137
|
+
return Vec.derive(() => lerp(this.value, readNow(b), readNow(t)));
|
|
138
|
+
}
|
|
139
|
+
distance(other) {
|
|
140
|
+
return Num.derive(this, v => metric(v, readNow(other)));
|
|
141
|
+
}
|
|
142
|
+
get x() {
|
|
143
|
+
return field(this, "x", Num);
|
|
144
|
+
}
|
|
145
|
+
get y() {
|
|
146
|
+
return field(this, "y", Num);
|
|
147
|
+
}
|
|
148
|
+
get magnitude() {
|
|
149
|
+
return derived(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
|
150
|
+
}
|
|
151
|
+
/** Tween-builder; `this: Writable<Vec>` gates the call to writable
|
|
152
|
+
* receivers. */
|
|
153
|
+
to(target, dur, ease) {
|
|
154
|
+
return tween(this, target, dur, ease);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** @internal — 2-input lens over two writable `Num`s; `vec()` delegates
|
|
158
|
+
* here after lifting literals. */
|
|
159
|
+
function axes(x, y) {
|
|
160
|
+
// The view fully reconstructs both axes (1-arg bwd ⇒ no source read).
|
|
161
|
+
return Vec.lens([x, y], ([xv, yv]) => ({ x: xv, y: yv }), v => [v.x, v.y]);
|
|
162
|
+
}
|
|
163
|
+
/** Writable `Vec` at `(x, y)`. Each axis is a literal `number` (lifted
|
|
164
|
+
* to a fresh seed) or an existing `Writable<Num>` (identity passthrough).
|
|
165
|
+
* RO sources are rejected at the type level — use `Vec.derive(...)` for
|
|
166
|
+
* reactive RO tracking, or `cell.value` to snapshot. Lock an axis with
|
|
167
|
+
* `Num.pin(c)`: `vec(slider, Num.pin(100))`. */
|
|
168
|
+
export function vec(x = 0, y = 0) {
|
|
169
|
+
if (typeof x === "number" && typeof y === "number") {
|
|
170
|
+
return new Vec({ x, y });
|
|
171
|
+
}
|
|
172
|
+
return axes(num(x), num(y));
|
|
173
|
+
}
|
|
174
|
+
/** Vec at polar offset from `center`: `center + (r·cos a, r·sin a)`.
|
|
175
|
+
* Bidirectional; each input is a literal (lifted to a fresh seed) or an
|
|
176
|
+
* existing writable cell. RO inputs are rejected at the type level.
|
|
177
|
+
* `policy` selects which inputs absorb writes; lock one with
|
|
178
|
+
* `Num.pin(c)`: `polar(c, Num.pin(100), a)`. */
|
|
179
|
+
export function polar(center, r, a, policy = "rotate") {
|
|
180
|
+
// Lift literals; already-writable inputs pass through by identity.
|
|
181
|
+
const cSig = center instanceof Vec ? center : vec(center.x, center.y);
|
|
182
|
+
const rSig = num(r);
|
|
183
|
+
const aSig = num(a);
|
|
184
|
+
const project = (c, rv, av) => ({
|
|
185
|
+
x: c.x + rv * Math.cos(av),
|
|
186
|
+
y: c.y + rv * Math.sin(av),
|
|
187
|
+
});
|
|
188
|
+
let bwd;
|
|
189
|
+
switch (policy) {
|
|
190
|
+
case "rotate":
|
|
191
|
+
bwd = (p, [cv, , av]) => {
|
|
192
|
+
const dx = p.x - cv.x;
|
|
193
|
+
const dy = p.y - cv.y;
|
|
194
|
+
return [undefined, Math.hypot(dx, dy), nearestAngle(Math.atan2(dy, dx), av)];
|
|
195
|
+
};
|
|
196
|
+
break;
|
|
197
|
+
case "translate":
|
|
198
|
+
bwd = (p, [cv, rv, av]) => {
|
|
199
|
+
const f = project(cv, rv, av);
|
|
200
|
+
return [{ x: cv.x + (p.x - f.x), y: cv.y + (p.y - f.y) }, undefined, undefined];
|
|
201
|
+
};
|
|
202
|
+
break;
|
|
203
|
+
case "radial":
|
|
204
|
+
bwd = (p, [cv, , av]) => {
|
|
205
|
+
const dx = p.x - cv.x;
|
|
206
|
+
const dy = p.y - cv.y;
|
|
207
|
+
return [undefined, dx * Math.cos(av) + dy * Math.sin(av), undefined];
|
|
208
|
+
};
|
|
209
|
+
break;
|
|
210
|
+
case "circular":
|
|
211
|
+
bwd = (p, [cv, , av]) => [
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
nearestAngle(Math.atan2(p.y - cv.y, p.x - cv.x), av),
|
|
215
|
+
];
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
return Vec.lens([cSig, rSig, aSig], ([c, rv, av]) => project(c, rv, av), bwd);
|
|
219
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Cell, type Inner, type Writable, type WritableBrand } from "./signal.js";
|
|
2
|
+
/** Bidirectional field lens onto `parent.value[key]`; write spread-
|
|
3
|
+
* replaces the composite. Cached per (instance, key). Return type is
|
|
4
|
+
* conditional: `Writable<Cls>` on a writable parent, bare `Cls` on RO
|
|
5
|
+
* (runtime dispatch in `Cell.fieldOf` mirrors this).
|
|
6
|
+
*
|
|
7
|
+
* get x() { return field(this, "x", Num); } */
|
|
8
|
+
export declare function field<S extends Cell<any>, K extends keyof Inner<S>, C extends new (...args: never[]) => Cell<Inner<S>[K]>>(parent: S, key: K, Cls: C): S extends WritableBrand ? Writable<InstanceType<C>> : InstanceType<C>;
|
|
9
|
+
/** Read-only derived view via `Cls.derive(parent, fn)`. Cached per
|
|
10
|
+
* (instance, key); always bare `Cls` (RO).
|
|
11
|
+
*
|
|
12
|
+
* get magnitude() {
|
|
13
|
+
* return derived(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
|
14
|
+
* } */
|
|
15
|
+
export declare function derived<S extends Cell<any>, C extends new (...args: never[]) => Cell<any>>(parent: S, key: string | symbol, Cls: C, fn: (v: Inner<S>) => Inner<InstanceType<C>>): InstanceType<C>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// writable.ts — value-class authoring helpers.
|
|
2
|
+
//
|
|
3
|
+
// `field(this, "x", Num)` — bidirectional field lens; conditional
|
|
4
|
+
// return (writable on writable parent, bare on RO). `derived(this,
|
|
5
|
+
// "k", Cls, fn)` — read-only derived view via `Cls.derive`. The choice
|
|
6
|
+
// between them IS the local declaration of writability at each getter,
|
|
7
|
+
// mirroring `: this` invertible method returns. For arbitrary cached
|
|
8
|
+
// views, use `lazy()` from "../signal" directly.
|
|
9
|
+
import { Cell, lazy } from "./signal.js";
|
|
10
|
+
/** Bidirectional field lens onto `parent.value[key]`; write spread-
|
|
11
|
+
* replaces the composite. Cached per (instance, key). Return type is
|
|
12
|
+
* conditional: `Writable<Cls>` on a writable parent, bare `Cls` on RO
|
|
13
|
+
* (runtime dispatch in `Cell.fieldOf` mirrors this).
|
|
14
|
+
*
|
|
15
|
+
* get x() { return field(this, "x", Num); } */
|
|
16
|
+
export function field(parent, key, Cls) {
|
|
17
|
+
return lazy(parent, key, () => Cell.fieldOf(parent, key, Cls));
|
|
18
|
+
}
|
|
19
|
+
/** Read-only derived view via `Cls.derive(parent, fn)`. Cached per
|
|
20
|
+
* (instance, key); always bare `Cls` (RO).
|
|
21
|
+
*
|
|
22
|
+
* get magnitude() {
|
|
23
|
+
* return derived(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
|
24
|
+
* } */
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape, mirrors Cls.derive
|
|
26
|
+
export function derived(parent, key, Cls, fn) {
|
|
27
|
+
// biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.derive
|
|
28
|
+
return lazy(parent, key, () => Cls.derive(parent, fn));
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Animator } from "../animation/index.js";
|
|
2
|
+
export declare class EventBus {
|
|
3
|
+
private handlers;
|
|
4
|
+
emit(name: string, data?: unknown): void;
|
|
5
|
+
on(name: string, handler: (data: unknown) => void): () => void;
|
|
6
|
+
/** Wake on the next emit of `name`; resume with the emit data. Pass
|
|
7
|
+
* an explicit type parameter (`yield* bus.until<string>("msg")`) to
|
|
8
|
+
* type the payload at the call site. */
|
|
9
|
+
until<T = unknown>(name: string): Animator<T>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Named event bus. Subscribe/emit synchronously; `until(name)` returns
|
|
2
|
+
// an `Animator<T>` that wakes the next time `name` fires, carrying the
|
|
3
|
+
// emit data as the resume value.
|
|
4
|
+
import { suspend } from "../animation/index.js";
|
|
5
|
+
export class EventBus {
|
|
6
|
+
handlers = new Map();
|
|
7
|
+
emit(name, data) {
|
|
8
|
+
const set = this.handlers.get(name);
|
|
9
|
+
if (!set)
|
|
10
|
+
return;
|
|
11
|
+
for (const fn of set)
|
|
12
|
+
fn(data);
|
|
13
|
+
}
|
|
14
|
+
on(name, handler) {
|
|
15
|
+
let set = this.handlers.get(name);
|
|
16
|
+
if (!set) {
|
|
17
|
+
set = new Set();
|
|
18
|
+
this.handlers.set(name, set);
|
|
19
|
+
}
|
|
20
|
+
set.add(handler);
|
|
21
|
+
return () => {
|
|
22
|
+
set.delete(handler);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Wake on the next emit of `name`; resume with the emit data. Pass
|
|
26
|
+
* an explicit type parameter (`yield* bus.until<string>("msg")`) to
|
|
27
|
+
* type the payload at the call site. */
|
|
28
|
+
until(name) {
|
|
29
|
+
return suspend(wake => this.on(name, wake));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { EventBus } from "./events.js";
|
|
2
|
+
export { snapshot } from "./snapshot.js";
|
|
3
|
+
export { type Clip, sequential, type Timeline, type TimelineOf, timeline, } from "./timeline.js";
|
|
4
|
+
export { inView, native, scrollProgress, untilAnimation, untilInView, untilOutOfView, type ViewRange, viewProgress, } from "./waapi.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Read } from "../core/index.js";
|
|
2
|
+
/** Capture current values; return a reset function.
|
|
3
|
+
*
|
|
4
|
+
* const reset = snapshot(score, position);
|
|
5
|
+
* // … later, on cancel/reset …
|
|
6
|
+
* reset();
|
|
7
|
+
*/
|
|
8
|
+
export declare function snapshot(...args: ReadonlyArray<Read<unknown> | Record<string, unknown>>): () => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// `snapshot(...sigs)` — capture current signal values; return a reset
|
|
2
|
+
// function. Args are signals or plain records whose signal-valued
|
|
3
|
+
// properties get flattened. Useful at the top of `loop(...)` bodies so
|
|
4
|
+
// each iteration starts from a known baseline.
|
|
5
|
+
import { Cell } from "../core/index.js";
|
|
6
|
+
/** Capture current values; return a reset function.
|
|
7
|
+
*
|
|
8
|
+
* const reset = snapshot(score, position);
|
|
9
|
+
* // … later, on cancel/reset …
|
|
10
|
+
* reset();
|
|
11
|
+
*/
|
|
12
|
+
export function snapshot(...args) {
|
|
13
|
+
const sigs = [];
|
|
14
|
+
for (const arg of args) {
|
|
15
|
+
if (arg instanceof Cell) {
|
|
16
|
+
sigs.push(arg);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
for (const v of Object.values(arg)) {
|
|
20
|
+
if (v instanceof Cell)
|
|
21
|
+
sigs.push(v);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const initials = sigs.map(s => s.peek());
|
|
25
|
+
return () => {
|
|
26
|
+
for (let i = 0; i < sigs.length; i++)
|
|
27
|
+
sigs[i].value = initials[i];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Animator } from "../animation/index.js";
|
|
2
|
+
import { type Cell, type Init, Num, Range, type Writable } from "../core/index.js";
|
|
3
|
+
/** A clip on a timeline. `t` clamps to 0 before / 1 after, so
|
|
4
|
+
* `derive(() => ease(clip.t.value))` needs no guards. Per-field
|
|
5
|
+
* writability flows through `ResolvedField` / `ResolvedSpan`: writable
|
|
6
|
+
* inputs yield draggable knobs, RO inputs (e.g. `sequential()`'s `at`)
|
|
7
|
+
* stay RO. */
|
|
8
|
+
export type Clip<A = number, D = number> = {
|
|
9
|
+
readonly at: ResolvedField<A>;
|
|
10
|
+
readonly dur: ResolvedField<D>;
|
|
11
|
+
readonly end: Num;
|
|
12
|
+
readonly span: ResolvedSpan<A, D>;
|
|
13
|
+
/** Progress: 0 before `at`, 0..1 within, 1 after `end`. */
|
|
14
|
+
readonly t: Num;
|
|
15
|
+
readonly active: Cell<boolean>;
|
|
16
|
+
};
|
|
17
|
+
type ResolvedField<A> = [A] extends [Writable<Num>] ? Writable<Num> : [A] extends [number] ? Writable<Num> : Num;
|
|
18
|
+
type IsWritable<A> = [A] extends [Writable<Num>] ? true : [A] extends [number] ? true : false;
|
|
19
|
+
type ResolvedSpan<A, D> = IsWritable<A> extends true ? (IsWritable<D> extends true ? Writable<Range> : Range) : Range;
|
|
20
|
+
type ClipSpec = {
|
|
21
|
+
at: number | Num;
|
|
22
|
+
dur: number | Num;
|
|
23
|
+
};
|
|
24
|
+
export interface Timeline {
|
|
25
|
+
readonly clock: Writable<Num>;
|
|
26
|
+
readonly duration: Num;
|
|
27
|
+
/** `clock / duration`, clamped to `[0, 1]`. */
|
|
28
|
+
readonly t: Num;
|
|
29
|
+
readonly clips: readonly Clip[];
|
|
30
|
+
/** `yield* tl` advances `clock` to `duration`. No auto-reset (use
|
|
31
|
+
* `snapshot(tl.clock)` for loops). */
|
|
32
|
+
[Symbol.iterator](): Animator;
|
|
33
|
+
}
|
|
34
|
+
/** Type-preserving named-clip access. */
|
|
35
|
+
export type TimelineOf<T extends Record<string, ClipSpec>> = Timeline & {
|
|
36
|
+
readonly [K in keyof T]: T[K] extends {
|
|
37
|
+
at: infer A;
|
|
38
|
+
dur: infer D;
|
|
39
|
+
} ? Clip<A, D> : Clip;
|
|
40
|
+
};
|
|
41
|
+
/** Build a timeline from clip specs. `at`/`dur` accept numbers or `Num`
|
|
42
|
+
* cells; clips may overlap or gap. See `sequential()` for cumulative starts. */
|
|
43
|
+
export declare function timeline<T extends Record<string, ClipSpec>>(specs: T): TimelineOf<T>;
|
|
44
|
+
type Durations = Record<string, Init<Num>>;
|
|
45
|
+
/** Cumulative-start helper: each clip's `at` is the reactive sum of
|
|
46
|
+
* prior durations (RO). Use `timeline()` directly for draggable starts.
|
|
47
|
+
*
|
|
48
|
+
* timeline(sequential({ intro: 0.7, hold: 1.2, outro: 0.5 }));
|
|
49
|
+
*/
|
|
50
|
+
export declare function sequential<T extends Durations>(durs: T): {
|
|
51
|
+
[K in keyof T]: {
|
|
52
|
+
at: Num;
|
|
53
|
+
dur: ResolvedField<T[K]>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Timeline + Clip. A timeline is a clock plus named clips (each an
|
|
2
|
+
// `[at, at + dur)` interval); `yield* tl` advances the clock to
|
|
3
|
+
// `duration`. `sequential({...})` produces cumulative-start specs.
|
|
4
|
+
import { derive, isComputed, Num, num, Range, span, } from "../core/index.js";
|
|
5
|
+
class TimelineImpl {
|
|
6
|
+
clock;
|
|
7
|
+
duration;
|
|
8
|
+
t;
|
|
9
|
+
clips;
|
|
10
|
+
constructor(clock, clips) {
|
|
11
|
+
this.clock = clock;
|
|
12
|
+
this.clips = clips;
|
|
13
|
+
this.duration = Num.derive(() => {
|
|
14
|
+
let max = 0;
|
|
15
|
+
for (const c of clips) {
|
|
16
|
+
const e = c.end.value;
|
|
17
|
+
if (e > max)
|
|
18
|
+
max = e;
|
|
19
|
+
}
|
|
20
|
+
return max;
|
|
21
|
+
});
|
|
22
|
+
this.t = Num.derive(() => {
|
|
23
|
+
const d = this.duration.value;
|
|
24
|
+
return d > 0 ? Math.min(this.clock.value / d, 1) : 0;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
*[Symbol.iterator]() {
|
|
28
|
+
while (this.clock.value < this.duration.value) {
|
|
29
|
+
const { dt } = yield;
|
|
30
|
+
this.clock.value += dt;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function makeClip(spec, clock) {
|
|
35
|
+
const at = Num.from(spec.at);
|
|
36
|
+
const dur = Num.from(spec.dur);
|
|
37
|
+
const end = Num.derive(() => at.value + dur.value);
|
|
38
|
+
// Bidirectional span when both inputs are writable; RO derive when
|
|
39
|
+
// either is computed. Mirrors `ResolvedSpan`.
|
|
40
|
+
const sp = isComputed(at) || isComputed(dur)
|
|
41
|
+
? Range.derive(() => ({ lo: at.value, hi: at.value + dur.value }))
|
|
42
|
+
: span(at, dur);
|
|
43
|
+
const t = Num.derive(() => {
|
|
44
|
+
const c = clock.value;
|
|
45
|
+
const a = at.value;
|
|
46
|
+
const d = dur.value;
|
|
47
|
+
if (c <= a)
|
|
48
|
+
return 0;
|
|
49
|
+
if (c >= a + d)
|
|
50
|
+
return 1;
|
|
51
|
+
return d > 0 ? (c - a) / d : 1;
|
|
52
|
+
});
|
|
53
|
+
const active = derive(() => {
|
|
54
|
+
const c = clock.value;
|
|
55
|
+
return c >= at.value && c < end.value;
|
|
56
|
+
});
|
|
57
|
+
return { at, dur, span: sp, end, t, active };
|
|
58
|
+
}
|
|
59
|
+
/** Build a timeline from clip specs. `at`/`dur` accept numbers or `Num`
|
|
60
|
+
* cells; clips may overlap or gap. See `sequential()` for cumulative starts. */
|
|
61
|
+
export function timeline(specs) {
|
|
62
|
+
const clock = num(0);
|
|
63
|
+
const clips = [];
|
|
64
|
+
const named = {};
|
|
65
|
+
for (const key of Object.keys(specs)) {
|
|
66
|
+
const clip = makeClip(specs[key], clock);
|
|
67
|
+
clips.push(clip);
|
|
68
|
+
named[key] = clip;
|
|
69
|
+
}
|
|
70
|
+
const tl = new TimelineImpl(clock, clips);
|
|
71
|
+
Object.assign(tl, named);
|
|
72
|
+
return tl;
|
|
73
|
+
}
|
|
74
|
+
/** Cumulative-start helper: each clip's `at` is the reactive sum of
|
|
75
|
+
* prior durations (RO). Use `timeline()` directly for draggable starts.
|
|
76
|
+
*
|
|
77
|
+
* timeline(sequential({ intro: 0.7, hold: 1.2, outro: 0.5 }));
|
|
78
|
+
*/
|
|
79
|
+
export function sequential(durs) {
|
|
80
|
+
const keys = Object.keys(durs);
|
|
81
|
+
const durSigs = keys.map(k => num(durs[k]));
|
|
82
|
+
const out = {};
|
|
83
|
+
keys.forEach((key, i) => {
|
|
84
|
+
const idx = i;
|
|
85
|
+
const at = Num.derive(() => {
|
|
86
|
+
let sum = 0;
|
|
87
|
+
for (let j = 0; j < idx; j++)
|
|
88
|
+
sum += durSigs[j].value;
|
|
89
|
+
return sum;
|
|
90
|
+
});
|
|
91
|
+
out[key] = { at, dur: durSigs[i] };
|
|
92
|
+
});
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Animator } from "../animation/index.js";
|
|
2
|
+
import { type Cell } from "../core/index.js";
|
|
3
|
+
/** WAAPI animation as a bireactive Animator. Bare-number `opts` is seconds;
|
|
4
|
+
* object `opts` passes through to `Element.animate` (ms). */
|
|
5
|
+
export declare function native(el: Element, keyframes: Keyframe[] | PropertyIndexedKeyframes | null, opts?: number | KeyframeAnimationOptions): Animator<void>;
|
|
6
|
+
/** Wake on the animation's `finish` event; resume with the event. */
|
|
7
|
+
export declare function untilAnimation(a: Animation): Animator<AnimationPlaybackEvent>;
|
|
8
|
+
/** Wake when `el` enters the viewport. Wakes immediately if already in. */
|
|
9
|
+
export declare function untilInView(el: Element, opts?: IntersectionObserverInit): Animator<void>;
|
|
10
|
+
/** Wake when `el` leaves the viewport. Wakes immediately if already out. */
|
|
11
|
+
export declare function untilOutOfView(el: Element, opts?: IntersectionObserverInit): Animator<void>;
|
|
12
|
+
/** Global page scroll progress in `[0, 1]`; `0` if page doesn't scroll. */
|
|
13
|
+
export declare function scrollProgress(): Cell<number>;
|
|
14
|
+
/** Slice of an element's viewport traversal mapped to `[0, 1]`. Names
|
|
15
|
+
* match CSS `view-timeline`:
|
|
16
|
+
* cover leading enters ↦ trailing exits
|
|
17
|
+
* entry leading enters ↦ fully in view
|
|
18
|
+
* contain fully in view (pinned at 0.5 when taller than vp)
|
|
19
|
+
* exit starts exiting ↦ trailing exits */
|
|
20
|
+
export type ViewRange = "cover" | "entry" | "contain" | "exit";
|
|
21
|
+
/** Element view-progress in `[0, 1]` over `range` (default `cover`). */
|
|
22
|
+
export declare function viewProgress(el: Element, range?: ViewRange): Cell<number>;
|
|
23
|
+
/** Reactive boolean; `true` while `el` intersects the viewport. Seeded
|
|
24
|
+
* synchronously from rect, then maintained by IntersectionObserver. */
|
|
25
|
+
export declare function inView(el: Element, opts?: IntersectionObserverInit): Cell<boolean>;
|