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,97 @@
1
+ // debug.* — read-only diagnostic shapes that visualize layout state.
2
+ import { Box, derive, transformBox, transformPoint, Vec } from "../core/index.js";
3
+ import { circle } from "./circle.js";
4
+ import { group } from "./group.js";
5
+ import { label } from "./label.js";
6
+ import { line } from "./line.js";
7
+ import { rect } from "./rect.js";
8
+ import { Shape } from "./shape.js";
9
+ const COLOR = "var(--bireactive-debug, #c026d3)";
10
+ const baseOpts = { aside: true, opacity: 0.6 };
11
+ const outlineOpts = {
12
+ stroke: COLOR,
13
+ fill: "none",
14
+ thin: true,
15
+ dashed: true,
16
+ ...baseOpts,
17
+ };
18
+ /** Parent-frame Box. Shapes have their transform applied (so the
19
+ * Box reflects the visual footprint); raw Boxes pass through. */
20
+ function parentBox(b) {
21
+ if (b instanceof Shape) {
22
+ return Box.derive(() => transformBox(b.localFrame.value, b.box.value));
23
+ }
24
+ return b;
25
+ }
26
+ /** Dashed rect over a Shape's parent-frame box (or a raw Box). */
27
+ const boxOutline = (b) => rect(parentBox(b), outlineOpts);
28
+ /** Small filled dot at a point or a Box's / Shape's center. */
29
+ const dot = (p, r = 2.5) => {
30
+ const at = p instanceof Vec ? p : p instanceof Shape ? p.center : p.center;
31
+ return circle(at, r, { fill: COLOR, stroke: "none", ...baseOpts });
32
+ };
33
+ /** Crosshair at a Shape's rotate/scale pivot, in parent frame. */
34
+ const origin = (s, size = 8) => {
35
+ const pivot = Vec.derive(() => transformPoint(s.localFrame.value, s.origin.value));
36
+ const half = size / 2;
37
+ return group({ aside: true, opacity: 0.75 }, line(pivot.left(half), pivot.right(half), { stroke: COLOR, thin: true }), line(pivot.up(half), pivot.down(half), { stroke: COLOR, thin: true }), circle(pivot, 1.5, { fill: COLOR, stroke: "none" }));
38
+ };
39
+ /** Dots at the 9 standard anchor positions: corners, edge midpoints,
40
+ * center. */
41
+ const anchors = (b, r = 2.5) => {
42
+ const g = group({ aside: true, opacity: 0.7 });
43
+ for (const u of [0, 0.5, 1]) {
44
+ for (const v of [0, 0.5, 1]) {
45
+ g.add(circle(b.at(u, v), r, {
46
+ fill: COLOR,
47
+ stroke: "none",
48
+ }));
49
+ }
50
+ }
51
+ return g;
52
+ };
53
+ /** Faint dashed line between two shapes' (or points') centers. */
54
+ const connect = (a, b) => {
55
+ const aP = a instanceof Shape ? a.center : a;
56
+ const bP = b instanceof Shape ? b.center : b;
57
+ return line(aP, bP, {
58
+ stroke: COLOR,
59
+ thin: true,
60
+ dashed: true,
61
+ ...baseOpts,
62
+ });
63
+ };
64
+ /** `connect(a, b)` + a live distance label at the midpoint. */
65
+ const distance = (a, b) => {
66
+ const aP = a instanceof Shape ? a.center : a;
67
+ const bP = b instanceof Shape ? b.center : b;
68
+ const mid = aP.lerp(bP, 0.5);
69
+ const d = aP.distance(bP);
70
+ return group({ aside: true }, connect(aP, bP), label(mid.up(6), derive(() => d.value.toFixed(0)), { size: 10, opacity: 0.85 }));
71
+ };
72
+ /** Markers + tiny tangent ticks at evenly-spaced t along a Path. */
73
+ const path = (p, ticks = 5) => {
74
+ const g = group({ aside: true, opacity: 0.75 });
75
+ for (let i = 0; i < ticks; i++) {
76
+ const t = ticks === 1 ? 0 : i / (ticks - 1);
77
+ const head = p.pointAt(t);
78
+ const tan = p.tangentAt(t);
79
+ const tip = Vec.derive(() => ({
80
+ x: head.value.x + tan.value.x * 6,
81
+ y: head.value.y + tan.value.y * 6,
82
+ }));
83
+ g.add(circle(head, 2.5, { fill: COLOR, stroke: "none" }), line(head, tip, { stroke: COLOR, thin: true }));
84
+ }
85
+ return g;
86
+ };
87
+ /** debug.* — diagnostic overlays. All are `aside: true` so they don't
88
+ * infect autofit. Drop in during development, remove when done. */
89
+ export const debug = {
90
+ box: boxOutline,
91
+ dot,
92
+ origin,
93
+ anchors,
94
+ connect,
95
+ distance,
96
+ path,
97
+ };
@@ -0,0 +1,5 @@
1
+ import { type AnyShape, Shape, type ShapeOpts } from "./shape.js";
2
+ /** Empty container — bundles children under one transform / opacity.
3
+ * Pass children after `opts` for JSX-ish nesting:
4
+ * `group({ translate }, rect(...), label(...))`. */
5
+ export declare function group<const O extends ShapeOpts>(opts?: O, ...children: AnyShape[]): Shape<O>;
@@ -0,0 +1,10 @@
1
+ import { Shape } from "./shape.js";
2
+ /** Empty container — bundles children under one transform / opacity.
3
+ * Pass children after `opts` for JSX-ish nesting:
4
+ * `group({ translate }, rect(...), label(...))`. */
5
+ export function group(opts, ...children) {
6
+ const g = new Shape(undefined, undefined, opts);
7
+ if (children.length > 0)
8
+ g.add(...children);
9
+ return g;
10
+ }
@@ -0,0 +1,32 @@
1
+ import { type Cell, type Val, Vec, type Writable } from "../core/index.js";
2
+ import { Circle } from "./circle.js";
3
+ import type { Path } from "./path.js";
4
+ import type { AnyShape, Has } from "./shape.js";
5
+ export interface HandleOpts {
6
+ /** Handle radius (px). Default 6. */
7
+ r?: number;
8
+ /** Fill color. Default `--bireactive-handle`. Accepts reactive values. */
9
+ fill?: Val<string>;
10
+ /** CSS cursor on hover. Default `grab`. */
11
+ cursor?: string;
12
+ }
13
+ /** Draggable circular handle. A `Circle` plus a `dragging` signal (true
14
+ * between pointerdown and pointerup/cancel) for coordinating animations. */
15
+ export declare class Handle extends Circle {
16
+ readonly dragging: Writable<Cell<boolean>>;
17
+ constructor(target: Writable<Vec>, opts?: HandleOpts);
18
+ }
19
+ declare function handleFn(target: Writable<Vec>, opts?: HandleOpts): Handle;
20
+ /** `handle(point)` is the atom; `.move`, `.centroid`, etc. are sugar. */
21
+ export declare const handle: typeof handleFn & {
22
+ move: (shape: AnyShape & Has<"translate">, opts?: HandleOpts) => Handle;
23
+ anchor: (shape: AnyShape & Has<"translate">, u: number, v: number, opts?: HandleOpts) => Handle;
24
+ centroid: (...shapes: (AnyShape & Has<"translate">)[]) => Handle;
25
+ midpoint: (a: Writable<Vec>, b: Writable<Vec>, opts?: HandleOpts) => Handle;
26
+ rotate: (shape: AnyShape & Has<"rotate">, radius?: number, opts?: HandleOpts) => Handle;
27
+ scale: (shape: AnyShape & Has<"scale">, radius?: number, opts?: HandleOpts) => Handle;
28
+ tOnPath: (p: Path, t: Cell<number>, opts?: HandleOpts & {
29
+ samples?: number;
30
+ }) => Handle;
31
+ };
32
+ export {};
@@ -0,0 +1,88 @@
1
+ // handle.* — writable derived shapes (draggable circles wired to a Vec).
2
+ import { cell, centroidLens, midpointLens, polar as polarLens, Vec, } from "../core/index.js";
3
+ import { Circle } from "./circle.js";
4
+ import { drag } from "./interaction.js";
5
+ const COLOR = "var(--bireactive-handle, #2563eb)";
6
+ /** Draggable circular handle. A `Circle` plus a `dragging` signal (true
7
+ * between pointerdown and pointerup/cancel) for coordinating animations. */
8
+ export class Handle extends Circle {
9
+ dragging;
10
+ constructor(target, opts = {}) {
11
+ const circleOpts = {
12
+ fill: opts.fill ?? COLOR,
13
+ // Background-colored halo so the handle pops on either theme.
14
+ stroke: "var(--bg-color, white)",
15
+ strokeWidth: 2,
16
+ aside: true,
17
+ };
18
+ super(target, opts.r ?? 6, circleOpts);
19
+ this.el.style.cursor = opts.cursor ?? "grab";
20
+ this.dragging = cell(false);
21
+ this.disposers.push(drag(this, target, this.dragging));
22
+ }
23
+ }
24
+ function handleFn(target, opts = {}) {
25
+ return new Handle(target, opts);
26
+ }
27
+ /** Drag handle at the shape's center — drags translate the shape. */
28
+ const move = (shape, opts) => handleFn(shape.center, opts);
29
+ /** Drag handle at a specific anchor `(u, v)` of the shape — drag
30
+ * translates the shape so that anchor lands at the pointer. */
31
+ const anchor = (shape, u, v, opts) => handleFn(shape.at(u, v), opts);
32
+ /** Drag handle at the centroid of N shapes' visual centers; drags translate
33
+ * all shapes rigidly. Reads the centroid of visible positions (cf. `centroid`
34
+ * in `shape.ts`, which works on translate deltas). */
35
+ const centroidHandle = (...shapes) => handleFn(centroidLens(shapes.map(s => s.center)));
36
+ /** Drag handle at the midpoint of two writable Points — drags both
37
+ * along with it. */
38
+ const midpoint = (a, b, opts) => handleFn(midpointLens(a, b), opts);
39
+ /** Rotation knob orbiting the shape's center at `radius`; drag to write
40
+ * `shape.rotate`. */
41
+ const rotate = (shape, radius = 40, opts) => {
42
+ // `polar` with `circular` policy: c and r fixed, writes only update θ.
43
+ return handleFn(polarLens(shape.center, radius, shape.rotate, "circular"), {
44
+ cursor: "grab",
45
+ ...opts,
46
+ });
47
+ };
48
+ /** Uniform-scale knob along +x at `radius * scale.x`; drag x-distance writes
49
+ * both scale axes. */
50
+ const scaleHandle = (shape, radius = 40, opts) => {
51
+ // Reads center and scale; writes only scale.
52
+ const pos = Vec.lens([shape.center, shape.scale], vals => ({ x: vals[0].x + radius * vals[1].x, y: vals[0].y }), (target, vals) => {
53
+ const k = Math.max(0.05, Math.abs(target.x - vals[0].x) / radius);
54
+ return [undefined, { x: k, y: k }];
55
+ });
56
+ return handleFn(pos, { cursor: "ew-resize", ...opts });
57
+ };
58
+ /** Handle constrained to a Path: each drag projects the pointer onto the path
59
+ * and sets `t` to the nearest parameter (re-projects, so animated paths work). */
60
+ const tOnPath = (p, t, opts) => {
61
+ const N = opts?.samples ?? 64;
62
+ const project = (target) => {
63
+ let bestT = 0;
64
+ let bestD = Number.POSITIVE_INFINITY;
65
+ for (let i = 0; i <= N; i++) {
66
+ const tt = i / N;
67
+ const pp = p.pointAt(tt).value;
68
+ const d = (pp.x - target.x) ** 2 + (pp.y - target.y) ** 2;
69
+ if (d < bestD) {
70
+ bestD = d;
71
+ bestT = tt;
72
+ }
73
+ }
74
+ return bestT;
75
+ };
76
+ const pos = Vec.lens([t], ([tv]) => p.pointAt(tv).value, target => [project(target)]);
77
+ return handleFn(pos, opts);
78
+ };
79
+ /** `handle(point)` is the atom; `.move`, `.centroid`, etc. are sugar. */
80
+ export const handle = Object.assign(handleFn, {
81
+ move,
82
+ anchor,
83
+ centroid: centroidHandle,
84
+ midpoint,
85
+ rotate,
86
+ scale: scaleHandle,
87
+ tOnPath,
88
+ });
@@ -0,0 +1,23 @@
1
+ export { AnnularSector, type AnnularSectorOpts, annularSector, } from "./annular-sector.js";
2
+ export { type ButtonOpts, button } from "./button.js";
3
+ export { assemble, orbit, splay, stagger, swap } from "./choreographers.js";
4
+ export { Circle, type CircleOpts, circle } from "./circle.js";
5
+ export { clipPath } from "./clip.js";
6
+ export { type ArrowOpts, arrow, connect, ensureArrowMarker } from "./connect.js";
7
+ export { Curve, type CurveOpts, type CurveSegment, curve, ellipse } from "./curve.js";
8
+ export { dashedPath } from "./dashed.js";
9
+ export { debug } from "./debug.js";
10
+ export { group } from "./group.js";
11
+ export { type HandleOpts, handle } from "./handle.js";
12
+ export { cursor, drag, draggable, dragRotate, dragWithState, hoverSignal } from "./interaction.js";
13
+ export { Label, type LabelOpts, label } from "./label.js";
14
+ export { type ArrangeOpts, arrange, expand, grid, split } from "./layout.js";
15
+ export { Line, type LineOpts, line } from "./line.js";
16
+ export { type ForEachOptions, forEach } from "./list.js";
17
+ export { type Mount, mount } from "./mount.js";
18
+ export { Path, type PathDOpts, type PathOpts, path, pathD } from "./path.js";
19
+ export { Rect, type RectOpts, rect } from "./rect.js";
20
+ export { type AnimatableKey, type AnyShape, type CommonOpts, centroid, type Has, meanRotation, meanScale, type Segment, Shape, type ShapeOpts, SVG_NS, } from "./shape.js";
21
+ export { type Content, Text, type TextPart, t } from "./text.js";
22
+ export { type Tokens, tokens } from "./tokens.js";
23
+ export { bounceIn, fadeIn, fadeOut, fadeUp, fadeUpOut, scaleIn, slideIn, slideOut, spinIn, zoomOut, } from "./transitions.js";
@@ -0,0 +1,23 @@
1
+ export { AnnularSector, annularSector, } from "./annular-sector.js";
2
+ export { button } from "./button.js";
3
+ export { assemble, orbit, splay, stagger, swap } from "./choreographers.js";
4
+ export { Circle, circle } from "./circle.js";
5
+ export { clipPath } from "./clip.js";
6
+ export { arrow, connect, ensureArrowMarker } from "./connect.js";
7
+ export { Curve, curve, ellipse } from "./curve.js";
8
+ export { dashedPath } from "./dashed.js";
9
+ export { debug } from "./debug.js";
10
+ export { group } from "./group.js";
11
+ export { handle } from "./handle.js";
12
+ export { cursor, drag, draggable, dragRotate, dragWithState, hoverSignal } from "./interaction.js";
13
+ export { Label, label } from "./label.js";
14
+ export { arrange, expand, grid, split } from "./layout.js";
15
+ export { Line, line } from "./line.js";
16
+ export { forEach } from "./list.js";
17
+ export { mount } from "./mount.js";
18
+ export { Path, path, pathD } from "./path.js";
19
+ export { Rect, rect } from "./rect.js";
20
+ export { centroid, meanRotation, meanScale, Shape, SVG_NS, } from "./shape.js";
21
+ export { Text, t } from "./text.js";
22
+ export { tokens } from "./tokens.js";
23
+ export { bounceIn, fadeIn, fadeOut, fadeUp, fadeUpOut, scaleIn, slideIn, slideOut, spinIn, zoomOut, } from "./transitions.js";
@@ -0,0 +1,32 @@
1
+ import { type Cell, type Inner, type Num, Vec, type Writable } from "../core/index.js";
2
+ import type { AnyShape } from "./shape.js";
3
+ /** Set `sig` true/false from `mouseenter`/`mouseleave` on `shape`; returns a
4
+ * disposer. Lower-level than `hover(el, marker)` — writes the signal directly. */
5
+ export declare function hoverSignal(shape: AnyShape, sig: Writable<Cell<boolean>>): () => void;
6
+ /** Reactive `Vec` tracking the page pointer in `shape`'s SVG-root frame, via
7
+ * the shared window listener (N callers, one listener). `init` is returned
8
+ * before the first `pointermove` to avoid a first-frame jolt to (0, 0). */
9
+ export declare function cursor(shape: AnyShape, init?: Inner<Vec>): Vec;
10
+ /** Wire `handle` for pointer-drag. Each pointermove while pressed
11
+ * calls `onDrag(local)` with the pointer in `handle`'s local frame;
12
+ * pointer-captured so drags survive leaving the handle. The optional
13
+ * `onState(active)` callback fires `true` on pointerdown and `false`
14
+ * on pointerup/cancel — `Handle` uses it to drive `.dragging`. */
15
+ export declare function draggable(handle: AnyShape, onDrag: (local: Inner<Vec>) => void, onState?: (active: boolean) => void): () => void;
16
+ /** Bind pointer drag on `shape` directly to a writable `Vec` (no handle dot);
17
+ * returns a disposer. `target` is in the SVG-root frame and coords are read
18
+ * via `toWorld`, so the grab offset survives bwd writes back through
19
+ * `shape.translate`. Grab offset is captured on pointerdown; optional
20
+ * `dragging` reports active state. Defaults `cursor` to `"grab"`. */
21
+ export declare function drag(shape: AnyShape, target: Writable<Vec>, dragging?: Writable<Cell<boolean>>): () => void;
22
+ /** Wrap a `drag(shape, target)` call and return a local `dragging`
23
+ * Cell<boolean>. Sugar for "give me a drag handle that exposes its
24
+ * own state." */
25
+ export declare function dragWithState(shape: AnyShape, target: Writable<Vec>): {
26
+ dragging: Cell<boolean>;
27
+ dispose: () => void;
28
+ };
29
+ /** Drag-to-rotate about the shape's local origin: writes `angle` so the
30
+ * grabbed point tracks the cursor (Δ = current − grab angle, shortest arc).
31
+ * Returns a disposer. */
32
+ export declare function dragRotate(shape: AnyShape, angle: Writable<Num>, dragging?: Writable<Cell<boolean>>): () => void;
@@ -0,0 +1,187 @@
1
+ // DOM input → signal-world bridges that bind to scene-graph shapes.
2
+ import { cell, Vec } from "../core/index.js";
3
+ // Shared page-pointer state for `cursor()`: one lazy window listener feeding
4
+ // one signal. `null` until the first `pointermove`. Never disposed.
5
+ let _clientPointer = null;
6
+ function pageClientPointer() {
7
+ if (_clientPointer)
8
+ return _clientPointer;
9
+ const sig = cell(null);
10
+ window.addEventListener("pointermove", (e) => {
11
+ sig.value = { clientX: e.clientX, clientY: e.clientY };
12
+ });
13
+ _clientPointer = sig;
14
+ return sig;
15
+ }
16
+ const TAU = Math.PI * 2;
17
+ const wrapToPi = (x) => x - TAU * Math.round(x / TAU);
18
+ /** Stop a touch that lands on a draggable element from scrolling/zooming
19
+ * the page. Set on both the `<g>` and its intrinsic (the actual hit
20
+ * target on iOS Safari) so the gesture is owned by the drag, not the
21
+ * page. Non-draggable scenery keeps `touch-action: auto`, so swiping
22
+ * past a diagram still scrolls. */
23
+ function ownTouchGesture(shape) {
24
+ shape.el.style.touchAction = "none";
25
+ if (shape.intrinsic)
26
+ shape.intrinsic.style.touchAction = "none";
27
+ }
28
+ /** iOS Safari ignores `touch-action` on inner SVG nodes, so a drag that
29
+ * starts on a handle still pans the page. For the lifetime of an active
30
+ * drag we also `preventDefault` a non-passive document `touchmove`,
31
+ * which reliably suppresses scroll/zoom in every browser. Returns a
32
+ * disposer that re-enables scrolling; call it on pointerup/cancel. */
33
+ function blockPageScroll() {
34
+ const onMove = (e) => e.preventDefault();
35
+ document.addEventListener("touchmove", onMove, { passive: false });
36
+ return () => document.removeEventListener("touchmove", onMove);
37
+ }
38
+ /** Set `sig` true/false from `mouseenter`/`mouseleave` on `shape`; returns a
39
+ * disposer. Lower-level than `hover(el, marker)` — writes the signal directly. */
40
+ export function hoverSignal(shape, sig) {
41
+ const off1 = shape.on("mouseenter", () => {
42
+ sig.value = true;
43
+ });
44
+ const off2 = shape.on("mouseleave", () => {
45
+ sig.value = false;
46
+ });
47
+ return () => {
48
+ off1();
49
+ off2();
50
+ };
51
+ }
52
+ /** Reactive `Vec` tracking the page pointer in `shape`'s SVG-root frame, via
53
+ * the shared window listener (N callers, one listener). `init` is returned
54
+ * before the first `pointermove` to avoid a first-frame jolt to (0, 0). */
55
+ export function cursor(shape, init) {
56
+ const cp = pageClientPointer();
57
+ const fallback = init ?? { x: 0, y: 0 };
58
+ return Vec.derive(cp, p => (p ? shape.toWorld(p) : fallback));
59
+ }
60
+ /** Wire `handle` for pointer-drag. Each pointermove while pressed
61
+ * calls `onDrag(local)` with the pointer in `handle`'s local frame;
62
+ * pointer-captured so drags survive leaving the handle. The optional
63
+ * `onState(active)` callback fires `true` on pointerdown and `false`
64
+ * on pointerup/cancel — `Handle` uses it to drive `.dragging`. */
65
+ export function draggable(handle, onDrag, onState) {
66
+ let dragging = false;
67
+ let pointerId = -1;
68
+ let unblock = null;
69
+ ownTouchGesture(handle);
70
+ const offs = [];
71
+ offs.push(handle.on("pointerdown", e => {
72
+ const pe = e;
73
+ dragging = true;
74
+ pointerId = pe.pointerId;
75
+ handle.el.setPointerCapture(pointerId);
76
+ unblock = blockPageScroll();
77
+ onState?.(true);
78
+ onDrag(handle.toLocal(pe));
79
+ }));
80
+ offs.push(handle.on("pointermove", e => {
81
+ if (!dragging)
82
+ return;
83
+ onDrag(handle.toLocal(e));
84
+ }));
85
+ const stop = () => {
86
+ if (dragging && pointerId !== -1) {
87
+ try {
88
+ handle.el.releasePointerCapture(pointerId);
89
+ }
90
+ catch {
91
+ /* ok */
92
+ }
93
+ }
94
+ dragging = false;
95
+ pointerId = -1;
96
+ unblock?.();
97
+ unblock = null;
98
+ onState?.(false);
99
+ };
100
+ offs.push(handle.on("pointerup", stop));
101
+ offs.push(handle.on("pointercancel", stop));
102
+ return () => offs.forEach(d => d());
103
+ }
104
+ /** Bind pointer drag on `shape` directly to a writable `Vec` (no handle dot);
105
+ * returns a disposer. `target` is in the SVG-root frame and coords are read
106
+ * via `toWorld`, so the grab offset survives bwd writes back through
107
+ * `shape.translate`. Grab offset is captured on pointerdown; optional
108
+ * `dragging` reports active state. Defaults `cursor` to `"grab"`. */
109
+ export function drag(shape, target, dragging) {
110
+ if (!shape.el.style.cursor)
111
+ shape.el.style.cursor = "grab";
112
+ ownTouchGesture(shape);
113
+ let dx = 0;
114
+ let dy = 0;
115
+ let pointerId = -1;
116
+ let unblock = null;
117
+ const offs = [];
118
+ offs.push(shape.on("pointerdown", e => {
119
+ const pe = e;
120
+ pointerId = pe.pointerId;
121
+ shape.el.setPointerCapture(pointerId);
122
+ unblock = blockPageScroll();
123
+ const world = shape.toWorld(pe);
124
+ const v = target.value;
125
+ dx = world.x - v.x;
126
+ dy = world.y - v.y;
127
+ if (dragging)
128
+ dragging.value = true;
129
+ }));
130
+ offs.push(shape.on("pointermove", e => {
131
+ if (pointerId === -1)
132
+ return;
133
+ const world = shape.toWorld(e);
134
+ target.value = { x: world.x - dx, y: world.y - dy };
135
+ }));
136
+ const stop = () => {
137
+ if (pointerId !== -1) {
138
+ try {
139
+ shape.el.releasePointerCapture(pointerId);
140
+ }
141
+ catch {
142
+ /* ok */
143
+ }
144
+ pointerId = -1;
145
+ }
146
+ unblock?.();
147
+ unblock = null;
148
+ if (dragging)
149
+ dragging.value = false;
150
+ };
151
+ offs.push(shape.on("pointerup", stop));
152
+ offs.push(shape.on("pointercancel", stop));
153
+ return () => offs.forEach(d => d());
154
+ }
155
+ /** Wrap a `drag(shape, target)` call and return a local `dragging`
156
+ * Cell<boolean>. Sugar for "give me a drag handle that exposes its
157
+ * own state." */
158
+ export function dragWithState(shape, target) {
159
+ const dragging = cell(false);
160
+ const dispose = drag(shape, target, dragging);
161
+ return { dragging, dispose };
162
+ }
163
+ /** Drag-to-rotate about the shape's local origin: writes `angle` so the
164
+ * grabbed point tracks the cursor (Δ = current − grab angle, shortest arc).
165
+ * Returns a disposer. */
166
+ export function dragRotate(shape, angle, dragging) {
167
+ if (!shape.el.style.cursor)
168
+ shape.el.style.cursor = "grab";
169
+ let grabAngle = 0;
170
+ const offDown = shape.on("pointerdown", e => {
171
+ const local = shape.toLocal(e);
172
+ grabAngle = Math.atan2(local.y, local.x);
173
+ });
174
+ const stop = draggable(shape, local => {
175
+ const currentAngle = Math.atan2(local.y, local.x);
176
+ const current = angle.peek();
177
+ angle.value = current + wrapToPi(currentAngle - grabAngle);
178
+ }, dragging
179
+ ? active => {
180
+ dragging.value = active;
181
+ }
182
+ : undefined);
183
+ return () => {
184
+ offDown();
185
+ stop();
186
+ };
187
+ }
@@ -0,0 +1,20 @@
1
+ import { type Inner, type Val, type Vec } from "../core/index.js";
2
+ import { Shape, type ShapeOpts } from "./shape.js";
3
+ import { type Content } from "./text.js";
4
+ export interface LabelOpts extends ShapeOpts {
5
+ size?: Val<number>;
6
+ /** Bbox point that sits at `at` — `{0, 0}` = top-left, `{0.5, 0.5}`
7
+ * (default) = center. See `Anchor` for named consts. */
8
+ align?: Inner<Vec>;
9
+ bold?: boolean;
10
+ /** Text color. Default `tokens.stroke` (i.e. `var(--text-color)`,
11
+ * flips with dark mode). Accepts a reactive `Val<string>`. */
12
+ fill?: Val<string>;
13
+ }
14
+ export declare class Label<O extends LabelOpts = LabelOpts> extends Shape<O> {
15
+ /** The position the label attaches to (subject to `align`); distinct from
16
+ * the inherited Box `center` / `at(u, v)`. */
17
+ readonly anchor: Vec;
18
+ constructor(anchor: Vec, content: Val<Content>, opts?: O);
19
+ }
20
+ export declare const label: <const O extends LabelOpts>(at: Vec, content: Val<Content>, opts?: O) => Label<O>;
@@ -0,0 +1,42 @@
1
+ import { Cell, cell, derive, Num } from "../core/index.js";
2
+ import { Shape } from "./shape.js";
3
+ import { flattenText, renderContent } from "./text.js";
4
+ import { tokens } from "./tokens.js";
5
+ const xAttr = (x) => (x <= 0.25 ? "start" : x >= 0.75 ? "end" : "middle");
6
+ const yAttr = (y) => (y <= 0.25 ? "hanging" : y >= 0.75 ? "alphabetic" : "central");
7
+ export class Label extends Shape {
8
+ /** The position the label attaches to (subject to `align`); distinct from
9
+ * the inherited Box `center` / `at(u, v)`. */
10
+ anchor;
11
+ constructor(anchor, content, opts = {}) {
12
+ const contentSig = content instanceof Cell
13
+ ? content
14
+ : typeof content === "function"
15
+ ? derive(content)
16
+ : cell(content);
17
+ const sizeSig = Num.from(opts.size ?? tokens.fontSize);
18
+ const a = opts.align ?? { x: 0.5, y: 0.5 };
19
+ super("text", () => {
20
+ const text = flattenText(contentSig.value);
21
+ const fs = sizeSig.value;
22
+ const w = fs * Math.max(1, text.length) * tokens.charWidth;
23
+ return { x: anchor.x.value - a.x * w, y: anchor.y.value - a.y * fs, w, h: fs };
24
+ }, opts,
25
+ // Pivot rotations on the anchor, not the bbox center.
26
+ { origin: anchor });
27
+ this.anchor = anchor;
28
+ this.attr("x", anchor.x);
29
+ this.attr("y", anchor.y);
30
+ this.attr("font-family", tokens.font);
31
+ this.attr("font-size", sizeSig);
32
+ this.attr("fill", opts.fill ?? tokens.stroke);
33
+ this.attr("text-anchor", xAttr(a.x));
34
+ this.attr("dominant-baseline", yAttr(a.y));
35
+ if (opts.bold)
36
+ this.attr("font-weight", 700);
37
+ this.effect(() => {
38
+ this.intrinsic.innerHTML = renderContent(contentSig.value);
39
+ });
40
+ }
41
+ }
42
+ export const label = (at, content, opts) => new Label(at, content, opts);
@@ -0,0 +1,29 @@
1
+ import { Box, type Val } from "../core/index.js";
2
+ import type { Shape } from "./shape.js";
3
+ export interface ArrangeOpts {
4
+ /** Spacing between adjacent bounding boxes. Default 0. */
5
+ gap?: number;
6
+ /** Cross-axis align vs the first shape: 0 top/left, 0.5 center,
7
+ * 1 bottom/right. Default 0. */
8
+ align?: number;
9
+ }
10
+ /** Lay out `shapes` in a row/column. First stays put; the rest bind
11
+ * their `translate` reactively to sit `gap` past the previous.
12
+ * Reflows on size or anchor change. */
13
+ export declare function arrange(shapes: readonly Shape[], axis: "row" | "column", opts?: ArrangeOpts): void;
14
+ /** Inflate a Box on each side by `by`. */
15
+ export declare function expand(b: Box, by: Val<number>): Box;
16
+ /** Split a Box along an axis into N reactive sub-Boxes.
17
+ *
18
+ * split(b, "x", 3) — 3 equal columns
19
+ * split(b, "x", [3, 2, 2]) — weighted 3:2:2
20
+ * split(b, "x", 3, { gap: 4 }) — 4px between
21
+ */
22
+ export declare function split(source: Box, axis: "x" | "y", parts: number | number[], opts?: {
23
+ gap?: Val<number>;
24
+ }): Box[];
25
+ /** Two-axis split into a `rows × cols` grid (sugar over `split`).
26
+ * Returns `[row][col]`. */
27
+ export declare function grid(source: Box, rows: number, cols: number, opts?: {
28
+ gap?: Val<number>;
29
+ }): Box[][];