@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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/assets/index.d.mts +3 -0
- package/dist/assets/index.mjs +5 -0
- package/dist/assets/kit/glyphs.d.mts +61 -0
- package/dist/assets/kit/glyphs.mjs +256 -0
- package/dist/assets/kit/index.d.mts +2 -0
- package/dist/assets/kit/index.mjs +4 -0
- package/dist/assets/kit/svg-defs.d.mts +6 -0
- package/dist/assets/kit/svg-defs.mjs +137 -0
- package/dist/builder/Palette.d.mts +30 -0
- package/dist/builder/Palette.mjs +100 -0
- package/dist/builder/SceneBuilder.d.mts +26 -0
- package/dist/builder/SceneBuilder.mjs +340 -0
- package/dist/builder/controls.d.mts +18 -0
- package/dist/builder/controls.mjs +29 -0
- package/dist/builder/editor.d.mts +46 -0
- package/dist/builder/editor.mjs +130 -0
- package/dist/builder/icons.d.mts +14 -0
- package/dist/builder/icons.mjs +143 -0
- package/dist/builder/index.d.mts +6 -0
- package/dist/builder/index.mjs +7 -0
- package/dist/builder/tools.d.mts +47 -0
- package/dist/builder/tools.mjs +303 -0
- package/dist/chem/index.d.mts +118 -0
- package/dist/chem/index.mjs +131 -0
- package/dist/core/clock.d.mts +41 -0
- package/dist/core/clock.mjs +62 -0
- package/dist/core/context.d.mts +13 -0
- package/dist/core/context.mjs +29 -0
- package/dist/core/control.d.mts +58 -0
- package/dist/core/control.mjs +113 -0
- package/dist/core/coords.d.mts +51 -0
- package/dist/core/coords.mjs +44 -0
- package/dist/core/index.d.mts +8 -0
- package/dist/core/index.mjs +9 -0
- package/dist/core/learner.d.mts +44 -0
- package/dist/core/learner.mjs +23 -0
- package/dist/core/motion.d.mts +73 -0
- package/dist/core/motion.mjs +103 -0
- package/dist/core/richText.d.mts +28 -0
- package/dist/core/richText.mjs +52 -0
- package/dist/core/vec.d.mts +47 -0
- package/dist/core/vec.mjs +144 -0
- package/dist/field/index.d.mts +58 -0
- package/dist/field/index.mjs +156 -0
- package/dist/index.d.mts +54 -0
- package/dist/index.mjs +55 -0
- package/dist/interaction/MovableDot.d.mts +33 -0
- package/dist/interaction/MovableDot.mjs +66 -0
- package/dist/interaction/hitTest.d.mts +16 -0
- package/dist/interaction/hitTest.mjs +39 -0
- package/dist/interaction/index.d.mts +5 -0
- package/dist/interaction/index.mjs +6 -0
- package/dist/interaction/useDraggable.d.mts +51 -0
- package/dist/interaction/useDraggable.mjs +116 -0
- package/dist/interaction/useScreenToMath.d.mts +12 -0
- package/dist/interaction/useScreenToMath.mjs +25 -0
- package/dist/logic/ast.d.mts +29 -0
- package/dist/logic/ast.mjs +37 -0
- package/dist/logic/index.d.mts +25 -0
- package/dist/logic/index.mjs +36 -0
- package/dist/logic/latex.d.mts +6 -0
- package/dist/logic/latex.mjs +33 -0
- package/dist/logic/minimize.d.mts +37 -0
- package/dist/logic/minimize.mjs +175 -0
- package/dist/logic/parse.d.mts +6 -0
- package/dist/logic/parse.mjs +68 -0
- package/dist/logic/table.d.mts +24 -0
- package/dist/logic/table.mjs +65 -0
- package/dist/logic/tokenize.mjs +258 -0
- package/dist/math/ast.d.mts +35 -0
- package/dist/math/ast.mjs +94 -0
- package/dist/math/calculus.d.mts +9 -0
- package/dist/math/calculus.mjs +188 -0
- package/dist/math/defs.mjs +51 -0
- package/dist/math/index.d.mts +26 -0
- package/dist/math/index.mjs +42 -0
- package/dist/math/latex.d.mts +7 -0
- package/dist/math/latex.mjs +115 -0
- package/dist/math/parse.d.mts +6 -0
- package/dist/math/parse.mjs +91 -0
- package/dist/math/tokenize.mjs +112 -0
- package/dist/primitives/CanvasLayer.d.mts +26 -0
- package/dist/primitives/CanvasLayer.mjs +87 -0
- package/dist/primitives/Dot.d.mts +27 -0
- package/dist/primitives/Dot.mjs +40 -0
- package/dist/primitives/Grid.d.mts +30 -0
- package/dist/primitives/Grid.mjs +122 -0
- package/dist/primitives/Label.d.mts +33 -0
- package/dist/primitives/Label.mjs +45 -0
- package/dist/primitives/Lines.d.mts +50 -0
- package/dist/primitives/Lines.mjs +82 -0
- package/dist/primitives/Plot.d.mts +50 -0
- package/dist/primitives/Plot.mjs +87 -0
- package/dist/primitives/Shapes.d.mts +76 -0
- package/dist/primitives/Shapes.mjs +79 -0
- package/dist/primitives/Tex.d.mts +27 -0
- package/dist/primitives/Tex.mjs +54 -0
- package/dist/primitives/index.d.mts +10 -0
- package/dist/primitives/index.mjs +10 -0
- package/dist/primitives/props.d.mts +15 -0
- package/dist/scene/Scene.d.mts +31 -0
- package/dist/scene/Scene.mjs +168 -0
- package/dist/scene/assets.d.mts +37 -0
- package/dist/scene/assets.mjs +31 -0
- package/dist/scene/commands.d.mts +47 -0
- package/dist/scene/commands.mjs +162 -0
- package/dist/scene/evaluators.d.mts +17 -0
- package/dist/scene/evaluators.mjs +210 -0
- package/dist/scene/index.d.mts +13 -0
- package/dist/scene/index.mjs +13 -0
- package/dist/scene/migrate.d.mts +11 -0
- package/dist/scene/migrate.mjs +35 -0
- package/dist/scene/render.d.mts +14 -0
- package/dist/scene/render.mjs +126 -0
- package/dist/scene/resolve.d.mts +12 -0
- package/dist/scene/resolve.mjs +95 -0
- package/dist/scene/schema.d.mts +9 -0
- package/dist/scene/schema.mjs +51 -0
- package/dist/scene/sims.d.mts +18 -0
- package/dist/scene/sims.mjs +54 -0
- package/dist/scene/store.d.mts +22 -0
- package/dist/scene/store.mjs +37 -0
- package/dist/scene/types.d.mts +228 -0
- package/dist/scene/types.mjs +27 -0
- package/dist/sim/equilibrium.d.mts +36 -0
- package/dist/sim/equilibrium.mjs +54 -0
- package/dist/sim/index.d.mts +9 -0
- package/dist/sim/index.mjs +9 -0
- package/dist/sim/particles.d.mts +43 -0
- package/dist/sim/particles.mjs +136 -0
- package/dist/sim/rate.d.mts +36 -0
- package/dist/sim/rate.mjs +39 -0
- package/dist/sim/registry.d.mts +10 -0
- package/dist/sim/registry.mjs +27 -0
- package/dist/sim/sampler.d.mts +47 -0
- package/dist/sim/sampler.mjs +79 -0
- package/dist/sim/thermal.d.mts +78 -0
- package/dist/sim/thermal.mjs +172 -0
- package/dist/sim/types.d.mts +24 -0
- package/dist/sim/wave.d.mts +34 -0
- package/dist/sim/wave.mjs +61 -0
- package/dist/steps/index.d.mts +62 -0
- package/dist/steps/index.mjs +133 -0
- package/dist/thermo/index.d.mts +82 -0
- package/dist/thermo/index.mjs +133 -0
- package/dist/view/Stage.d.mts +39 -0
- package/dist/view/Stage.mjs +91 -0
- package/dist/view/index.d.mts +4 -0
- package/dist/view/index.mjs +5 -0
- package/dist/view/useElementSize.d.mts +13 -0
- package/dist/view/useElementSize.mjs +40 -0
- package/dist/view/useInView.d.mts +9 -0
- package/dist/view/useInView.mjs +33 -0
- package/package.json +141 -0
- 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 };
|