@classytic/stage 0.1.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/assets/index.d.mts +3 -0
  4. package/dist/assets/index.mjs +5 -0
  5. package/dist/assets/kit/glyphs.d.mts +61 -0
  6. package/dist/assets/kit/glyphs.mjs +256 -0
  7. package/dist/assets/kit/index.d.mts +2 -0
  8. package/dist/assets/kit/index.mjs +4 -0
  9. package/dist/assets/kit/svg-defs.d.mts +6 -0
  10. package/dist/assets/kit/svg-defs.mjs +137 -0
  11. package/dist/builder/Palette.d.mts +30 -0
  12. package/dist/builder/Palette.mjs +100 -0
  13. package/dist/builder/SceneBuilder.d.mts +26 -0
  14. package/dist/builder/SceneBuilder.mjs +340 -0
  15. package/dist/builder/controls.d.mts +18 -0
  16. package/dist/builder/controls.mjs +29 -0
  17. package/dist/builder/editor.d.mts +46 -0
  18. package/dist/builder/editor.mjs +130 -0
  19. package/dist/builder/icons.d.mts +14 -0
  20. package/dist/builder/icons.mjs +143 -0
  21. package/dist/builder/index.d.mts +6 -0
  22. package/dist/builder/index.mjs +7 -0
  23. package/dist/builder/tools.d.mts +47 -0
  24. package/dist/builder/tools.mjs +303 -0
  25. package/dist/chem/index.d.mts +118 -0
  26. package/dist/chem/index.mjs +131 -0
  27. package/dist/core/clock.d.mts +41 -0
  28. package/dist/core/clock.mjs +62 -0
  29. package/dist/core/context.d.mts +13 -0
  30. package/dist/core/context.mjs +29 -0
  31. package/dist/core/control.d.mts +58 -0
  32. package/dist/core/control.mjs +113 -0
  33. package/dist/core/coords.d.mts +51 -0
  34. package/dist/core/coords.mjs +44 -0
  35. package/dist/core/index.d.mts +8 -0
  36. package/dist/core/index.mjs +9 -0
  37. package/dist/core/learner.d.mts +44 -0
  38. package/dist/core/learner.mjs +23 -0
  39. package/dist/core/motion.d.mts +73 -0
  40. package/dist/core/motion.mjs +103 -0
  41. package/dist/core/richText.d.mts +28 -0
  42. package/dist/core/richText.mjs +52 -0
  43. package/dist/core/vec.d.mts +47 -0
  44. package/dist/core/vec.mjs +144 -0
  45. package/dist/field/index.d.mts +58 -0
  46. package/dist/field/index.mjs +156 -0
  47. package/dist/index.d.mts +54 -0
  48. package/dist/index.mjs +55 -0
  49. package/dist/interaction/MovableDot.d.mts +33 -0
  50. package/dist/interaction/MovableDot.mjs +66 -0
  51. package/dist/interaction/hitTest.d.mts +16 -0
  52. package/dist/interaction/hitTest.mjs +39 -0
  53. package/dist/interaction/index.d.mts +5 -0
  54. package/dist/interaction/index.mjs +6 -0
  55. package/dist/interaction/useDraggable.d.mts +51 -0
  56. package/dist/interaction/useDraggable.mjs +116 -0
  57. package/dist/interaction/useScreenToMath.d.mts +12 -0
  58. package/dist/interaction/useScreenToMath.mjs +25 -0
  59. package/dist/logic/ast.d.mts +29 -0
  60. package/dist/logic/ast.mjs +37 -0
  61. package/dist/logic/index.d.mts +25 -0
  62. package/dist/logic/index.mjs +36 -0
  63. package/dist/logic/latex.d.mts +6 -0
  64. package/dist/logic/latex.mjs +33 -0
  65. package/dist/logic/minimize.d.mts +37 -0
  66. package/dist/logic/minimize.mjs +175 -0
  67. package/dist/logic/parse.d.mts +6 -0
  68. package/dist/logic/parse.mjs +68 -0
  69. package/dist/logic/table.d.mts +24 -0
  70. package/dist/logic/table.mjs +65 -0
  71. package/dist/logic/tokenize.mjs +258 -0
  72. package/dist/math/ast.d.mts +35 -0
  73. package/dist/math/ast.mjs +94 -0
  74. package/dist/math/calculus.d.mts +9 -0
  75. package/dist/math/calculus.mjs +188 -0
  76. package/dist/math/defs.mjs +51 -0
  77. package/dist/math/index.d.mts +26 -0
  78. package/dist/math/index.mjs +42 -0
  79. package/dist/math/latex.d.mts +7 -0
  80. package/dist/math/latex.mjs +115 -0
  81. package/dist/math/parse.d.mts +6 -0
  82. package/dist/math/parse.mjs +91 -0
  83. package/dist/math/tokenize.mjs +112 -0
  84. package/dist/primitives/CanvasLayer.d.mts +26 -0
  85. package/dist/primitives/CanvasLayer.mjs +87 -0
  86. package/dist/primitives/Dot.d.mts +27 -0
  87. package/dist/primitives/Dot.mjs +40 -0
  88. package/dist/primitives/Grid.d.mts +30 -0
  89. package/dist/primitives/Grid.mjs +122 -0
  90. package/dist/primitives/Label.d.mts +33 -0
  91. package/dist/primitives/Label.mjs +45 -0
  92. package/dist/primitives/Lines.d.mts +50 -0
  93. package/dist/primitives/Lines.mjs +82 -0
  94. package/dist/primitives/Plot.d.mts +50 -0
  95. package/dist/primitives/Plot.mjs +87 -0
  96. package/dist/primitives/Shapes.d.mts +76 -0
  97. package/dist/primitives/Shapes.mjs +79 -0
  98. package/dist/primitives/Tex.d.mts +27 -0
  99. package/dist/primitives/Tex.mjs +54 -0
  100. package/dist/primitives/index.d.mts +10 -0
  101. package/dist/primitives/index.mjs +10 -0
  102. package/dist/primitives/props.d.mts +15 -0
  103. package/dist/scene/Scene.d.mts +31 -0
  104. package/dist/scene/Scene.mjs +168 -0
  105. package/dist/scene/assets.d.mts +37 -0
  106. package/dist/scene/assets.mjs +31 -0
  107. package/dist/scene/commands.d.mts +47 -0
  108. package/dist/scene/commands.mjs +162 -0
  109. package/dist/scene/evaluators.d.mts +17 -0
  110. package/dist/scene/evaluators.mjs +210 -0
  111. package/dist/scene/index.d.mts +13 -0
  112. package/dist/scene/index.mjs +13 -0
  113. package/dist/scene/migrate.d.mts +11 -0
  114. package/dist/scene/migrate.mjs +35 -0
  115. package/dist/scene/render.d.mts +14 -0
  116. package/dist/scene/render.mjs +126 -0
  117. package/dist/scene/resolve.d.mts +12 -0
  118. package/dist/scene/resolve.mjs +95 -0
  119. package/dist/scene/schema.d.mts +9 -0
  120. package/dist/scene/schema.mjs +51 -0
  121. package/dist/scene/sims.d.mts +18 -0
  122. package/dist/scene/sims.mjs +54 -0
  123. package/dist/scene/store.d.mts +22 -0
  124. package/dist/scene/store.mjs +37 -0
  125. package/dist/scene/types.d.mts +228 -0
  126. package/dist/scene/types.mjs +27 -0
  127. package/dist/sim/equilibrium.d.mts +36 -0
  128. package/dist/sim/equilibrium.mjs +54 -0
  129. package/dist/sim/index.d.mts +9 -0
  130. package/dist/sim/index.mjs +9 -0
  131. package/dist/sim/particles.d.mts +43 -0
  132. package/dist/sim/particles.mjs +136 -0
  133. package/dist/sim/rate.d.mts +36 -0
  134. package/dist/sim/rate.mjs +39 -0
  135. package/dist/sim/registry.d.mts +10 -0
  136. package/dist/sim/registry.mjs +27 -0
  137. package/dist/sim/sampler.d.mts +47 -0
  138. package/dist/sim/sampler.mjs +79 -0
  139. package/dist/sim/thermal.d.mts +78 -0
  140. package/dist/sim/thermal.mjs +172 -0
  141. package/dist/sim/types.d.mts +24 -0
  142. package/dist/sim/wave.d.mts +34 -0
  143. package/dist/sim/wave.mjs +61 -0
  144. package/dist/steps/index.d.mts +62 -0
  145. package/dist/steps/index.mjs +133 -0
  146. package/dist/thermo/index.d.mts +82 -0
  147. package/dist/thermo/index.mjs +133 -0
  148. package/dist/view/Stage.d.mts +39 -0
  149. package/dist/view/Stage.mjs +91 -0
  150. package/dist/view/index.d.mts +4 -0
  151. package/dist/view/index.mjs +5 -0
  152. package/dist/view/useElementSize.d.mts +13 -0
  153. package/dist/view/useElementSize.mjs +40 -0
  154. package/dist/view/useInView.d.mts +9 -0
  155. package/dist/view/useInView.mjs +33 -0
  156. package/package.json +141 -0
  157. package/styles.css +70 -0
@@ -0,0 +1,50 @@
1
+ import { StyleProps } from "./props.mjs";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/primitives/Plot.d.ts
5
+ interface OfXProps extends StyleProps {
6
+ y: (x: number) => number;
7
+ domain?: [number, number];
8
+ samples?: number;
9
+ }
10
+ declare function OfX({
11
+ y,
12
+ domain,
13
+ samples,
14
+ color,
15
+ weight,
16
+ opacity
17
+ }: OfXProps): ReactNode;
18
+ interface OfYProps extends StyleProps {
19
+ x: (y: number) => number;
20
+ domain?: [number, number];
21
+ samples?: number;
22
+ }
23
+ declare function OfY({
24
+ x,
25
+ domain,
26
+ samples,
27
+ color,
28
+ weight,
29
+ opacity
30
+ }: OfYProps): ReactNode;
31
+ interface ParametricProps extends StyleProps {
32
+ xy: (t: number) => [number, number];
33
+ domain: [number, number];
34
+ samples?: number;
35
+ }
36
+ declare function Parametric({
37
+ xy,
38
+ domain,
39
+ samples,
40
+ color,
41
+ weight,
42
+ opacity
43
+ }: ParametricProps): ReactNode;
44
+ declare const Plot: {
45
+ OfX: typeof OfX;
46
+ OfY: typeof OfY;
47
+ Parametric: typeof Parametric;
48
+ };
49
+ //#endregion
50
+ export { OfXProps, OfYProps, ParametricProps, Plot };
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+
3
+ import { useCoords } from "../core/context.mjs";
4
+ import { jsx } from "react/jsx-runtime";
5
+
6
+ //#region src/primitives/Plot.tsx
7
+ /** Build an SVG path string, breaking the pen on non-finite samples (asymptotes). */
8
+ function pathFrom(c, pts) {
9
+ let d = "";
10
+ let pen = false;
11
+ for (const p of pts) {
12
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) {
13
+ pen = false;
14
+ continue;
15
+ }
16
+ const [px, py] = c.toPx(p[0], p[1]);
17
+ if (!Number.isFinite(px) || !Number.isFinite(py)) {
18
+ pen = false;
19
+ continue;
20
+ }
21
+ d += `${pen ? "L" : "M"}${px.toFixed(2)},${py.toFixed(2)} `;
22
+ pen = true;
23
+ }
24
+ return d;
25
+ }
26
+ function OfX({ y, domain, samples = 300, color = "var(--stage-accent)", weight = 2.5, opacity = 1 }) {
27
+ const c = useCoords();
28
+ const [x0, x1] = domain ?? [c.view.xMin, c.view.xMax];
29
+ const pts = [];
30
+ for (let i = 0; i <= samples; i++) {
31
+ const x = x0 + (x1 - x0) * (i / samples);
32
+ pts.push([x, y(x)]);
33
+ }
34
+ return /* @__PURE__ */ jsx("path", {
35
+ d: pathFrom(c, pts),
36
+ fill: "none",
37
+ stroke: color,
38
+ strokeWidth: weight,
39
+ opacity,
40
+ strokeLinejoin: "round",
41
+ strokeLinecap: "round"
42
+ });
43
+ }
44
+ function OfY({ x, domain, samples = 300, color = "var(--stage-accent)", weight = 2.5, opacity = 1 }) {
45
+ const c = useCoords();
46
+ const [y0, y1] = domain ?? [c.view.yMin, c.view.yMax];
47
+ const pts = [];
48
+ for (let i = 0; i <= samples; i++) {
49
+ const y = y0 + (y1 - y0) * (i / samples);
50
+ pts.push([x(y), y]);
51
+ }
52
+ return /* @__PURE__ */ jsx("path", {
53
+ d: pathFrom(c, pts),
54
+ fill: "none",
55
+ stroke: color,
56
+ strokeWidth: weight,
57
+ opacity,
58
+ strokeLinejoin: "round",
59
+ strokeLinecap: "round"
60
+ });
61
+ }
62
+ function Parametric({ xy, domain, samples = 400, color = "var(--stage-accent)", weight = 2.5, opacity = 1 }) {
63
+ const c = useCoords();
64
+ const [t0, t1] = domain;
65
+ const pts = [];
66
+ for (let i = 0; i <= samples; i++) {
67
+ const t = t0 + (t1 - t0) * (i / samples);
68
+ pts.push(xy(t));
69
+ }
70
+ return /* @__PURE__ */ jsx("path", {
71
+ d: pathFrom(c, pts),
72
+ fill: "none",
73
+ stroke: color,
74
+ strokeWidth: weight,
75
+ opacity,
76
+ strokeLinejoin: "round",
77
+ strokeLinecap: "round"
78
+ });
79
+ }
80
+ const Plot = {
81
+ OfX,
82
+ OfY,
83
+ Parametric
84
+ };
85
+
86
+ //#endregion
87
+ export { Plot };
@@ -0,0 +1,76 @@
1
+ import { Vec2 } from "../core/vec.mjs";
2
+ import { StyleProps } from "./props.mjs";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/primitives/Shapes.d.ts
6
+ interface CircleProps extends StyleProps {
7
+ center: Vec2;
8
+ r: number;
9
+ fill?: string;
10
+ fillOpacity?: number;
11
+ }
12
+ declare function Circle({
13
+ center,
14
+ r,
15
+ color,
16
+ fill,
17
+ fillOpacity,
18
+ weight,
19
+ opacity
20
+ }: CircleProps): ReactNode;
21
+ interface EllipseProps extends StyleProps {
22
+ center: Vec2;
23
+ rx: number;
24
+ ry: number;
25
+ fill?: string;
26
+ fillOpacity?: number;
27
+ }
28
+ declare function Ellipse({
29
+ center,
30
+ rx,
31
+ ry,
32
+ color,
33
+ fill,
34
+ fillOpacity,
35
+ weight,
36
+ opacity
37
+ }: EllipseProps): ReactNode;
38
+ interface PolygonProps extends StyleProps {
39
+ points: Vec2[];
40
+ fill?: string;
41
+ fillOpacity?: number;
42
+ }
43
+ declare function Polygon({
44
+ points,
45
+ color,
46
+ fill,
47
+ fillOpacity,
48
+ weight,
49
+ opacity
50
+ }: PolygonProps): ReactNode;
51
+ interface PolylineProps extends StyleProps {
52
+ points: Vec2[];
53
+ }
54
+ declare function Polyline({
55
+ points,
56
+ color,
57
+ weight,
58
+ opacity,
59
+ dashed
60
+ }: PolylineProps): ReactNode;
61
+ /** Low-level pixel-space path (d is already in pixels). */
62
+ interface PathProps extends StyleProps {
63
+ d: string;
64
+ fill?: string;
65
+ fillOpacity?: number;
66
+ }
67
+ declare function Path({
68
+ d,
69
+ color,
70
+ fill,
71
+ fillOpacity,
72
+ weight,
73
+ opacity
74
+ }: PathProps): ReactNode;
75
+ //#endregion
76
+ export { Circle, CircleProps, Ellipse, EllipseProps, Path, PathProps, Polygon, PolygonProps, Polyline, PolylineProps };
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { fmt } from "../core/coords.mjs";
4
+ import { useCoords } from "../core/context.mjs";
5
+ import { jsx } from "react/jsx-runtime";
6
+
7
+ //#region src/primitives/Shapes.tsx
8
+ const toPoints = (c, points) => points.map((p) => {
9
+ const [x, y] = c.toPx(p.x, p.y);
10
+ return `${fmt(x)},${fmt(y)}`;
11
+ }).join(" ");
12
+ function Circle({ center, r, color = "var(--stage-accent)", fill = "none", fillOpacity = .12, weight = 2, opacity = 1 }) {
13
+ const c = useCoords();
14
+ const [cx, cy] = c.toPx(center.x, center.y);
15
+ return /* @__PURE__ */ jsx("ellipse", {
16
+ cx: fmt(cx),
17
+ cy: fmt(cy),
18
+ rx: fmt(c.sx(r)),
19
+ ry: fmt(c.sy(r)),
20
+ stroke: color,
21
+ strokeWidth: weight,
22
+ fill,
23
+ fillOpacity: fill === "none" ? void 0 : fillOpacity,
24
+ opacity
25
+ });
26
+ }
27
+ function Ellipse({ center, rx, ry, color = "var(--stage-accent)", fill = "none", fillOpacity = .12, weight = 2, opacity = 1 }) {
28
+ const c = useCoords();
29
+ const [cx, cy] = c.toPx(center.x, center.y);
30
+ return /* @__PURE__ */ jsx("ellipse", {
31
+ cx: fmt(cx),
32
+ cy: fmt(cy),
33
+ rx: fmt(c.sx(rx)),
34
+ ry: fmt(c.sy(ry)),
35
+ stroke: color,
36
+ strokeWidth: weight,
37
+ fill,
38
+ fillOpacity: fill === "none" ? void 0 : fillOpacity,
39
+ opacity
40
+ });
41
+ }
42
+ function Polygon({ points, color = "var(--stage-accent)", fill = "var(--stage-accent)", fillOpacity = .12, weight = 2, opacity = 1 }) {
43
+ return /* @__PURE__ */ jsx("polygon", {
44
+ points: toPoints(useCoords(), points),
45
+ stroke: color,
46
+ strokeWidth: weight,
47
+ fill,
48
+ fillOpacity,
49
+ opacity,
50
+ strokeLinejoin: "round"
51
+ });
52
+ }
53
+ function Polyline({ points, color = "var(--stage-fg)", weight = 2, opacity = 1, dashed }) {
54
+ return /* @__PURE__ */ jsx("polyline", {
55
+ points: toPoints(useCoords(), points),
56
+ fill: "none",
57
+ stroke: color,
58
+ strokeWidth: weight,
59
+ opacity,
60
+ strokeLinejoin: "round",
61
+ strokeLinecap: "round",
62
+ strokeDasharray: dashed ? "6 6" : void 0
63
+ });
64
+ }
65
+ function Path({ d, color = "var(--stage-fg)", fill = "none", fillOpacity = 1, weight = 2, opacity = 1 }) {
66
+ return /* @__PURE__ */ jsx("path", {
67
+ d,
68
+ stroke: color,
69
+ strokeWidth: weight,
70
+ fill,
71
+ fillOpacity: fill === "none" ? void 0 : fillOpacity,
72
+ opacity,
73
+ strokeLinejoin: "round",
74
+ strokeLinecap: "round"
75
+ });
76
+ }
77
+
78
+ //#endregion
79
+ export { Circle, Ellipse, Path, Polygon, Polyline };
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from "react";
2
+
3
+ //#region src/primitives/Tex.d.ts
4
+ interface TexProps {
5
+ x: number;
6
+ y: number;
7
+ tex: string;
8
+ color?: string;
9
+ size?: number;
10
+ dx?: number;
11
+ dy?: number;
12
+ width?: number;
13
+ height?: number;
14
+ }
15
+ declare function Tex({
16
+ x,
17
+ y,
18
+ tex,
19
+ color,
20
+ size,
21
+ dx,
22
+ dy,
23
+ width,
24
+ height
25
+ }: TexProps): ReactNode;
26
+ //#endregion
27
+ export { Tex, TexProps };
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { fmt } from "../core/coords.mjs";
4
+ import { useCoords } from "../core/context.mjs";
5
+ import { useMemo } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ import katex from "katex";
8
+
9
+ //#region src/primitives/Tex.tsx
10
+ /**
11
+ * Tex — KaTeX maths in a <foreignObject>, rendered AT RENDER TIME (not in an
12
+ * effect) so the markup is identical on the server and the client: SSR/static
13
+ * exports show real maths (not raw `\left…\right`), there's no raw-then-hydrate
14
+ * flash, and hydration matches. KaTeX is a static import kept in its OWN module so
15
+ * only Tex's importers pull it (stage's neverBundle leaves `katex` external — the
16
+ * consumer resolves the peer; non-Tex consumers never bundle it).
17
+ *
18
+ * Needs the consumer to load `katex/dist/katex.min.css` for proper glyphs.
19
+ */
20
+ function Tex({ x, y, tex, color = "var(--stage-fg)", size = 18, dx = 0, dy = 0, width = 220, height = 64 }) {
21
+ const [px, py] = useCoords().toPx(x, y);
22
+ const html = useMemo(() => {
23
+ try {
24
+ return katex.renderToString(tex, { throwOnError: false });
25
+ } catch {
26
+ return null;
27
+ }
28
+ }, [tex]);
29
+ return /* @__PURE__ */ jsx("foreignObject", {
30
+ x: fmt(px + dx - width / 2),
31
+ y: fmt(py + dy - height / 2),
32
+ width,
33
+ height,
34
+ style: {
35
+ overflow: "visible",
36
+ pointerEvents: "none"
37
+ },
38
+ children: /* @__PURE__ */ jsx("div", {
39
+ style: {
40
+ display: "flex",
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ width: "100%",
44
+ height: "100%",
45
+ color,
46
+ fontSize: size
47
+ },
48
+ children: html ? /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } }) : /* @__PURE__ */ jsx("span", { children: tex })
49
+ })
50
+ });
51
+ }
52
+
53
+ //#endregion
54
+ export { Tex };
@@ -0,0 +1,10 @@
1
+ import { A11yProps, StyleProps } from "./props.mjs";
2
+ import { Dot, DotProps, Point } from "./Dot.mjs";
3
+ import { Line, LineProps, Ray, Segment, SegmentProps, Vector, VectorProps } from "./Lines.mjs";
4
+ import { Circle, CircleProps, Ellipse, EllipseProps, Path, PathProps, Polygon, PolygonProps, Polyline, PolylineProps } from "./Shapes.mjs";
5
+ import { Label, LabelProps } from "./Label.mjs";
6
+ import { Tex, TexProps } from "./Tex.mjs";
7
+ import { Axes, AxesProps, Grid, GridProps, niceStep } from "./Grid.mjs";
8
+ import { OfXProps, OfYProps, ParametricProps, Plot } from "./Plot.mjs";
9
+ import { CanvasLayer, CanvasLayerProps } from "./CanvasLayer.mjs";
10
+ export { type A11yProps, Axes, type AxesProps, CanvasLayer, type CanvasLayerProps, Circle, type CircleProps, Dot, type DotProps, Ellipse, type EllipseProps, Grid, type GridProps, Label, type LabelProps, Line, type LineProps, type OfXProps, type OfYProps, type ParametricProps, Path, type PathProps, Plot, Point, Polygon, type PolygonProps, Polyline, type PolylineProps, Ray, Segment, type SegmentProps, type StyleProps, Tex, type TexProps, Vector, type VectorProps, niceStep };
@@ -0,0 +1,10 @@
1
+ import { Dot, Point } from "./Dot.mjs";
2
+ import { Line, Ray, Segment, Vector } from "./Lines.mjs";
3
+ import { Circle, Ellipse, Path, Polygon, Polyline } from "./Shapes.mjs";
4
+ import { Label } from "./Label.mjs";
5
+ import { Tex } from "./Tex.mjs";
6
+ import { Axes, Grid, niceStep } from "./Grid.mjs";
7
+ import { Plot } from "./Plot.mjs";
8
+ import { CanvasLayer } from "./CanvasLayer.mjs";
9
+
10
+ export { Axes, CanvasLayer, Circle, Dot, Ellipse, Grid, Label, Line, Path, Plot, Point, Polygon, Polyline, Ray, Segment, Tex, Vector, niceStep };
@@ -0,0 +1,15 @@
1
+ //#region src/primitives/props.d.ts
2
+ /** Shared styling + a11y props for primitives. */
3
+ interface StyleProps {
4
+ color?: string;
5
+ opacity?: number;
6
+ /** Stroke width in pixels (never scaled by zoom). */
7
+ weight?: number;
8
+ dashed?: boolean;
9
+ }
10
+ interface A11yProps {
11
+ role?: string;
12
+ ariaLabel?: string;
13
+ }
14
+ //#endregion
15
+ export { A11yProps, StyleProps };
@@ -0,0 +1,31 @@
1
+ import { ViewBox } from "../core/coords.mjs";
2
+ import { SceneDoc } from "./types.mjs";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/scene/Scene.d.ts
6
+ interface SceneProps {
7
+ doc: SceneDoc;
8
+ onChange?: (doc: SceneDoc) => void;
9
+ interactive?: boolean;
10
+ showGrid?: boolean;
11
+ showAxes?: boolean;
12
+ height?: number;
13
+ view?: ViewBox;
14
+ ariaLabel?: string;
15
+ /** Author opt-in: start a sim running immediately (ambient motion) instead of
16
+ * the default "press Play". A SceneDoc with no `meta.sims` is always static. */
17
+ autoPlay?: boolean;
18
+ }
19
+ declare function Scene({
20
+ doc,
21
+ onChange,
22
+ interactive,
23
+ showGrid,
24
+ showAxes,
25
+ height,
26
+ view,
27
+ ariaLabel,
28
+ autoPlay
29
+ }: SceneProps): ReactNode;
30
+ //#endregion
31
+ export { Scene, SceneProps };
@@ -0,0 +1,168 @@
1
+ 'use client';
2
+
3
+ import { useFrameLoop } from "../core/clock.mjs";
4
+ import { Stage } from "../view/Stage.mjs";
5
+ import { useInView } from "../view/useInView.mjs";
6
+ import { Axes, Grid } from "../primitives/Grid.mjs";
7
+ import { isCircleVal, isLineVal, isVec2 } from "./types.mjs";
8
+ import { resolve } from "./resolve.mjs";
9
+ import { applyCommand } from "./commands.mjs";
10
+ import { renderElements } from "./render.mjs";
11
+ import { initSims, stepSims } from "./sims.mjs";
12
+ import { useMemo, useRef, useState } from "react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+
15
+ //#region src/scene/Scene.tsx
16
+ /**
17
+ * <Scene> — renders a SceneDoc (read/play): resolve() → shared element renderer.
18
+ * Free draggable points commit one `mutate` via onChange on pointer-up; live drag
19
+ * uses an ephemeral overlay (descendants re-resolve without mutating the doc).
20
+ */
21
+ function autoView(resolved) {
22
+ const xs = [];
23
+ const ys = [];
24
+ for (const v of resolved.values.values()) if (isVec2(v)) {
25
+ xs.push(v.x);
26
+ ys.push(v.y);
27
+ } else if (isCircleVal(v)) {
28
+ xs.push(v.center.x - v.r, v.center.x + v.r);
29
+ ys.push(v.center.y - v.r, v.center.y + v.r);
30
+ } else if (isLineVal(v)) {
31
+ xs.push(v.a.x, v.b.x);
32
+ ys.push(v.a.y, v.b.y);
33
+ }
34
+ if (!xs.length) return {
35
+ xMin: -8,
36
+ xMax: 8,
37
+ yMin: -5,
38
+ yMax: 5
39
+ };
40
+ const pad = 1.5;
41
+ return {
42
+ xMin: Math.min(...xs) - pad,
43
+ xMax: Math.max(...xs) + pad,
44
+ yMin: Math.min(...ys) - pad,
45
+ yMax: Math.max(...ys) + pad
46
+ };
47
+ }
48
+ function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes = true, height, view, ariaLabel = "Interactive figure", autoPlay = false }) {
49
+ const [overlay, setOverlay] = useState(/* @__PURE__ */ new Map());
50
+ const prevRef = useRef(void 0);
51
+ const sims = doc.meta?.sims;
52
+ const simStates = useRef(void 0);
53
+ if (!simStates.current) simStates.current = initSims(sims ?? []);
54
+ const simScalars = useRef(/* @__PURE__ */ new Map());
55
+ const [frame, setFrame] = useState(0);
56
+ const { ref: rootRef, inView } = useInView();
57
+ const [done, setDone] = useState(false);
58
+ const [playing, setPlaying] = useState(autoPlay);
59
+ useFrameLoop((f) => {
60
+ if (!sims?.length || !simStates.current) return;
61
+ const next = stepSims(sims, simStates.current, Math.min(.05, f.dtMs / 1e3));
62
+ simStates.current = next.states;
63
+ simScalars.current = next.scalars;
64
+ setFrame((n) => (n + 1) % 1e6);
65
+ if (next.done) setDone(true);
66
+ }, { running: !!sims?.length && inView && playing && !done });
67
+ const merged = useMemo(() => {
68
+ const scalars = simScalars.current;
69
+ if (!overlay.size && !scalars.size) return void 0;
70
+ const m = new Map(scalars);
71
+ for (const [k, v] of overlay) m.set(k, v);
72
+ return m;
73
+ }, [overlay, frame]);
74
+ const resolved = useMemo(() => {
75
+ const r = resolve(doc, merged, prevRef.current, simStates.current);
76
+ prevRef.current = r;
77
+ return r;
78
+ }, [
79
+ doc,
80
+ merged,
81
+ frame
82
+ ]);
83
+ const v = view ?? doc.view ?? autoView(resolved);
84
+ const move = (id, p, phase) => {
85
+ if (phase === "move") setOverlay((prev) => new Map(prev).set(id, p));
86
+ else {
87
+ setOverlay(/* @__PURE__ */ new Map());
88
+ onChange?.(applyCommand(doc, {
89
+ op: "mutate",
90
+ id,
91
+ patch: { free: { at: p } }
92
+ }));
93
+ }
94
+ };
95
+ const hasSims = !!sims?.length;
96
+ return /* @__PURE__ */ jsxs("div", {
97
+ ref: rootRef,
98
+ style: { position: "relative" },
99
+ children: [
100
+ /* @__PURE__ */ jsxs(Stage, {
101
+ view: v,
102
+ height,
103
+ ariaLabel,
104
+ children: [
105
+ showGrid && /* @__PURE__ */ jsx(Grid, {}),
106
+ showAxes && /* @__PURE__ */ jsx(Axes, { ticks: false }),
107
+ renderElements(doc, resolved, {
108
+ draggablePoints: interactive && !!onChange,
109
+ onPointMove: move
110
+ })
111
+ ]
112
+ }),
113
+ hasSims && !done && !playing && /* @__PURE__ */ jsx("button", {
114
+ type: "button",
115
+ onClick: () => setPlaying(true),
116
+ "aria-label": "Play simulation",
117
+ style: {
118
+ position: "absolute",
119
+ inset: 0,
120
+ display: "flex",
121
+ alignItems: "center",
122
+ justifyContent: "center",
123
+ background: "color-mix(in oklab, var(--stage-bg) 38%, transparent)",
124
+ border: "none",
125
+ borderRadius: 12,
126
+ cursor: "pointer"
127
+ },
128
+ children: /* @__PURE__ */ jsx("span", {
129
+ style: {
130
+ display: "inline-flex",
131
+ alignItems: "center",
132
+ gap: 8,
133
+ padding: "11px 26px",
134
+ borderRadius: 999,
135
+ background: "var(--stage-accent)",
136
+ color: "#fff",
137
+ fontWeight: 800,
138
+ fontSize: 16,
139
+ boxShadow: "0 6px 18px rgba(0,0,0,.28)"
140
+ },
141
+ children: "▶ Play"
142
+ })
143
+ }),
144
+ hasSims && !done && playing && /* @__PURE__ */ jsx("button", {
145
+ type: "button",
146
+ onClick: () => setPlaying(false),
147
+ "aria-label": "Pause simulation",
148
+ style: {
149
+ position: "absolute",
150
+ top: 10,
151
+ right: 10,
152
+ width: 34,
153
+ height: 34,
154
+ borderRadius: 999,
155
+ border: "1px solid var(--stage-grid)",
156
+ background: "color-mix(in oklab, var(--stage-bg) 80%, transparent)",
157
+ color: "var(--stage-fg)",
158
+ cursor: "pointer",
159
+ fontSize: 13
160
+ },
161
+ children: "⏸"
162
+ })
163
+ ]
164
+ });
165
+ }
166
+
167
+ //#endregion
168
+ export { Scene };
@@ -0,0 +1,37 @@
1
+ import { Vec2 } from "../core/vec.mjs";
2
+ import { AssetGeometry, ElementStyle, Val } from "./types.mjs";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/scene/assets.d.ts
6
+ interface AssetResolveArgs {
7
+ params: Record<string, number | Vec2>;
8
+ bound: Record<string, Val | undefined>;
9
+ /** Live sim-state values pulled in via the asset's `simBind` (any type: number/string/array). */
10
+ sim?: Record<string, unknown>;
11
+ }
12
+ interface AssetSpec {
13
+ resolver: (args: AssetResolveArgs) => AssetGeometry;
14
+ Component: (props: {
15
+ geom: AssetGeometry;
16
+ style?: ElementStyle;
17
+ label?: string;
18
+ }) => ReactNode;
19
+ }
20
+ declare function registerAsset(name: string, spec: AssetSpec): void;
21
+ declare function getAsset(name: string): AssetSpec | undefined;
22
+ declare function listAssets(): string[];
23
+ /**
24
+ * Typed read of an asset's `meta` — write `const m = assetMeta<MyMeta>(geom)` in a
25
+ * Component instead of an inline `(geom.meta ?? {}) as MyMeta` cast everywhere.
26
+ *
27
+ * We deliberately keep `AssetGeometry` NON-generic: `parts` must stay
28
+ * `Vec2 | Vec2[] | number` so it remains a clean member of the resolver's `Val`
29
+ * union, and a fully generic `AssetGeometry<TParts, TMeta>` would force `AssetSpec`,
30
+ * `registerAsset`, `resolve()`, and the render path to be generic too — and a
31
+ * specific `TMeta` interface isn't assignable to `Record<string, unknown>` without
32
+ * index-signature friction. This helper gives the type-safety at the read site
33
+ * with none of that ripple. (Asset domain data lives in `meta`; `parts` is geometry.)
34
+ */
35
+ declare function assetMeta<T>(geom: AssetGeometry): T;
36
+ //#endregion
37
+ export { AssetResolveArgs, AssetSpec, assetMeta, getAsset, listAssets, registerAsset };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ //#region src/scene/assets.ts
4
+ const ASSETS = /* @__PURE__ */ new Map();
5
+ function registerAsset(name, spec) {
6
+ ASSETS.set(name, spec);
7
+ }
8
+ function getAsset(name) {
9
+ return ASSETS.get(name);
10
+ }
11
+ function listAssets() {
12
+ return [...ASSETS.keys()];
13
+ }
14
+ /**
15
+ * Typed read of an asset's `meta` — write `const m = assetMeta<MyMeta>(geom)` in a
16
+ * Component instead of an inline `(geom.meta ?? {}) as MyMeta` cast everywhere.
17
+ *
18
+ * We deliberately keep `AssetGeometry` NON-generic: `parts` must stay
19
+ * `Vec2 | Vec2[] | number` so it remains a clean member of the resolver's `Val`
20
+ * union, and a fully generic `AssetGeometry<TParts, TMeta>` would force `AssetSpec`,
21
+ * `registerAsset`, `resolve()`, and the render path to be generic too — and a
22
+ * specific `TMeta` interface isn't assignable to `Record<string, unknown>` without
23
+ * index-signature friction. This helper gives the type-safety at the read site
24
+ * with none of that ripple. (Asset domain data lives in `meta`; `parts` is geometry.)
25
+ */
26
+ function assetMeta(geom) {
27
+ return geom.meta ?? {};
28
+ }
29
+
30
+ //#endregion
31
+ export { assetMeta, getAsset, listAssets, registerAsset };