@classytic/stage 0.1.0 → 0.3.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 +1 -1
- package/README.md +7 -3
- package/dist/assets/index.mjs +0 -1
- package/dist/assets/kit/glyphs.d.mts +5 -5
- package/dist/assets/kit/glyphs.mjs +4 -4
- package/dist/builder/Palette.mjs +50 -89
- package/dist/builder/SceneBuilder.mjs +18 -76
- package/dist/builder/editor.d.mts +2 -1
- package/dist/builder/editor.mjs +14 -4
- package/dist/builder/tools.d.mts +1 -1
- package/dist/builder/tools.mjs +1 -1
- package/dist/chem/index.d.mts +5 -5
- package/dist/chem/index.mjs +4 -4
- package/dist/circuit/index.d.mts +92 -0
- package/dist/circuit/index.mjs +333 -0
- package/dist/core/clock.d.mts +1 -1
- package/dist/core/clock.mjs +3 -3
- package/dist/core/control.d.mts +1 -1
- package/dist/core/control.mjs +2 -2
- package/dist/core/coords.d.mts +4 -4
- package/dist/core/coords.mjs +1 -1
- package/dist/core/index.d.mts +2 -1
- package/dist/core/index.mjs +2 -1
- package/dist/core/learner.d.mts +1 -1
- package/dist/core/learner.mjs +1 -1
- package/dist/core/math.d.mts +26 -0
- package/dist/core/math.mjs +37 -0
- package/dist/core/motion.d.mts +6 -6
- package/dist/core/motion.mjs +6 -6
- package/dist/core/richText.d.mts +2 -2
- package/dist/core/vec.d.mts +1 -1
- package/dist/field/index.d.mts +11 -3
- package/dist/field/index.mjs +20 -3
- package/dist/finance/bizsim.d.mts +93 -0
- package/dist/finance/bizsim.mjs +117 -0
- package/dist/finance/index.d.mts +118 -0
- package/dist/finance/index.mjs +203 -0
- package/dist/index.d.mts +6 -3
- package/dist/index.mjs +6 -4
- package/dist/interaction/MovableDot.d.mts +17 -1
- package/dist/interaction/MovableDot.mjs +160 -43
- package/dist/interaction/index.d.mts +2 -1
- package/dist/interaction/index.mjs +2 -1
- package/dist/interaction/useDraggable.d.mts +1 -1
- package/dist/interaction/useDraggable.mjs +55 -5
- package/dist/interaction/usePressSpring.d.mts +24 -0
- package/dist/interaction/usePressSpring.mjs +67 -0
- package/dist/logic/ast.d.mts +1 -1
- package/dist/logic/index.mjs +8 -3
- package/dist/logic/minimize.d.mts +2 -2
- package/dist/logic/minimize.mjs +56 -18
- package/dist/logic/table.mjs +1 -1
- package/dist/math/ast.d.mts +1 -1
- package/dist/math/ast.mjs +1 -1
- package/dist/math/calculus.d.mts +22 -1
- package/dist/math/calculus.mjs +224 -11
- package/dist/math/compile.d.mts +8 -0
- package/dist/math/compile.mjs +40 -0
- package/dist/math/defs.mjs +1 -1
- package/dist/math/index.d.mts +5 -4
- package/dist/math/index.mjs +5 -4
- package/dist/math/latex.mjs +8 -0
- package/dist/math/parse.mjs +2 -2
- package/dist/math/tokenize.mjs +1 -1
- package/dist/primitives/CanvasLayer.mjs +3 -3
- package/dist/primitives/Dot.d.mts +2 -15
- package/dist/primitives/Dot.mjs +6 -4
- package/dist/primitives/Grid.d.mts +41 -14
- package/dist/primitives/Grid.mjs +100 -22
- package/dist/primitives/Label.d.mts +2 -15
- package/dist/primitives/Label.mjs +4 -3
- package/dist/primitives/Lines.d.mts +4 -32
- package/dist/primitives/Lines.mjs +10 -8
- package/dist/primitives/Shapes.d.mts +5 -43
- package/dist/primitives/Shapes.mjs +12 -10
- package/dist/primitives/Tex.mjs +2 -2
- package/dist/primitives/index.d.mts +2 -2
- package/dist/primitives/index.mjs +2 -2
- package/dist/primitives/props.mjs +31 -0
- package/dist/scene/Scene.d.mts +6 -1
- package/dist/scene/Scene.mjs +16 -41
- package/dist/scene/assets.d.mts +2 -2
- package/dist/scene/assets.mjs +2 -2
- package/dist/scene/commands.d.mts +10 -1
- package/dist/scene/commands.mjs +42 -2
- package/dist/scene/evaluators.mjs +2 -2
- package/dist/scene/index.d.mts +2 -2
- package/dist/scene/index.mjs +2 -2
- package/dist/scene/migrate.mjs +1 -1
- package/dist/scene/sims.mjs +1 -1
- package/dist/scene/types.d.mts +4 -4
- package/dist/sim/particles.d.mts +2 -2
- package/dist/sim/rate.d.mts +4 -4
- package/dist/sim/rate.mjs +1 -1
- package/dist/sim/registry.d.mts +1 -1
- package/dist/sim/sampler.d.mts +5 -5
- package/dist/sim/thermal.d.mts +3 -3
- package/dist/sim/thermal.mjs +2 -2
- package/dist/sim/types.d.mts +2 -2
- package/dist/sim/wave.d.mts +2 -2
- package/dist/sim/wave.mjs +2 -2
- package/dist/steps/index.d.mts +1 -1
- package/dist/steps/index.mjs +3 -3
- package/dist/thermo/index.d.mts +1 -1
- package/dist/thermo/index.mjs +1 -1
- package/dist/view/Stage.mjs +6 -13
- package/dist/view/useInView.mjs +1 -1
- package/package.json +37 -25
- package/styles.css +141 -1
- package/dist/assets/kit/index.mjs +0 -4
package/dist/scene/Scene.d.mts
CHANGED
|
@@ -15,6 +15,9 @@ interface SceneProps {
|
|
|
15
15
|
/** Author opt-in: start a sim running immediately (ambient motion) instead of
|
|
16
16
|
* the default "press Play". A SceneDoc with no `meta.sims` is always static. */
|
|
17
17
|
autoPlay?: boolean;
|
|
18
|
+
/** Optional controlled playback for hosts that provide their own transport. */
|
|
19
|
+
playing?: boolean;
|
|
20
|
+
onPlayingChange?: (playing: boolean) => void;
|
|
18
21
|
}
|
|
19
22
|
declare function Scene({
|
|
20
23
|
doc,
|
|
@@ -25,7 +28,9 @@ declare function Scene({
|
|
|
25
28
|
height,
|
|
26
29
|
view,
|
|
27
30
|
ariaLabel,
|
|
28
|
-
autoPlay
|
|
31
|
+
autoPlay,
|
|
32
|
+
playing: playingProp,
|
|
33
|
+
onPlayingChange
|
|
29
34
|
}: SceneProps): ReactNode;
|
|
30
35
|
//#endregion
|
|
31
36
|
export { Scene, SceneProps };
|
package/dist/scene/Scene.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
|
|
15
15
|
//#region src/scene/Scene.tsx
|
|
16
16
|
/**
|
|
17
|
-
* <Scene
|
|
17
|
+
* <Scene>, renders a SceneDoc (read/play): resolve() → shared element renderer.
|
|
18
18
|
* Free draggable points commit one `mutate` via onChange on pointer-up; live drag
|
|
19
19
|
* uses an ephemeral overlay (descendants re-resolve without mutating the doc).
|
|
20
20
|
*/
|
|
@@ -45,7 +45,7 @@ function autoView(resolved) {
|
|
|
45
45
|
yMax: Math.max(...ys) + pad
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes = true, height, view, ariaLabel = "Interactive figure", autoPlay = false }) {
|
|
48
|
+
function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes = true, height, view, ariaLabel = "Interactive figure", autoPlay = false, playing: playingProp, onPlayingChange }) {
|
|
49
49
|
const [overlay, setOverlay] = useState(/* @__PURE__ */ new Map());
|
|
50
50
|
const prevRef = useRef(void 0);
|
|
51
51
|
const sims = doc.meta?.sims;
|
|
@@ -55,14 +55,22 @@ function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes =
|
|
|
55
55
|
const [frame, setFrame] = useState(0);
|
|
56
56
|
const { ref: rootRef, inView } = useInView();
|
|
57
57
|
const [done, setDone] = useState(false);
|
|
58
|
-
const [
|
|
58
|
+
const [internalPlaying, setInternalPlaying] = useState(autoPlay);
|
|
59
|
+
const playing = playingProp ?? internalPlaying;
|
|
60
|
+
const setPlaying = (next) => {
|
|
61
|
+
if (playingProp === void 0) setInternalPlaying(next);
|
|
62
|
+
onPlayingChange?.(next);
|
|
63
|
+
};
|
|
59
64
|
useFrameLoop((f) => {
|
|
60
65
|
if (!sims?.length || !simStates.current) return;
|
|
61
66
|
const next = stepSims(sims, simStates.current, Math.min(.05, f.dtMs / 1e3));
|
|
62
67
|
simStates.current = next.states;
|
|
63
68
|
simScalars.current = next.scalars;
|
|
64
69
|
setFrame((n) => (n + 1) % 1e6);
|
|
65
|
-
if (next.done)
|
|
70
|
+
if (next.done) {
|
|
71
|
+
setDone(true);
|
|
72
|
+
setPlaying(false);
|
|
73
|
+
}
|
|
66
74
|
}, { running: !!sims?.length && inView && playing && !done });
|
|
67
75
|
const merged = useMemo(() => {
|
|
68
76
|
const scalars = simScalars.current;
|
|
@@ -95,7 +103,7 @@ function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes =
|
|
|
95
103
|
const hasSims = !!sims?.length;
|
|
96
104
|
return /* @__PURE__ */ jsxs("div", {
|
|
97
105
|
ref: rootRef,
|
|
98
|
-
|
|
106
|
+
className: "stage-scene",
|
|
99
107
|
children: [
|
|
100
108
|
/* @__PURE__ */ jsxs(Stage, {
|
|
101
109
|
view: v,
|
|
@@ -114,30 +122,9 @@ function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes =
|
|
|
114
122
|
type: "button",
|
|
115
123
|
onClick: () => setPlaying(true),
|
|
116
124
|
"aria-label": "Play simulation",
|
|
117
|
-
|
|
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
|
-
},
|
|
125
|
+
className: "stage-scene__play-overlay",
|
|
128
126
|
children: /* @__PURE__ */ jsx("span", {
|
|
129
|
-
|
|
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
|
-
},
|
|
127
|
+
className: "stage-scene__play-label",
|
|
141
128
|
children: "▶ Play"
|
|
142
129
|
})
|
|
143
130
|
}),
|
|
@@ -145,19 +132,7 @@ function Scene({ doc, onChange, interactive = true, showGrid = true, showAxes =
|
|
|
145
132
|
type: "button",
|
|
146
133
|
onClick: () => setPlaying(false),
|
|
147
134
|
"aria-label": "Pause simulation",
|
|
148
|
-
|
|
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
|
-
},
|
|
135
|
+
className: "stage-scene__pause",
|
|
161
136
|
children: "⏸"
|
|
162
137
|
})
|
|
163
138
|
]
|
package/dist/scene/assets.d.mts
CHANGED
|
@@ -21,13 +21,13 @@ declare function registerAsset(name: string, spec: AssetSpec): void;
|
|
|
21
21
|
declare function getAsset(name: string): AssetSpec | undefined;
|
|
22
22
|
declare function listAssets(): string[];
|
|
23
23
|
/**
|
|
24
|
-
* Typed read of an asset's `meta
|
|
24
|
+
* Typed read of an asset's `meta`, write `const m = assetMeta<MyMeta>(geom)` in a
|
|
25
25
|
* Component instead of an inline `(geom.meta ?? {}) as MyMeta` cast everywhere.
|
|
26
26
|
*
|
|
27
27
|
* We deliberately keep `AssetGeometry` NON-generic: `parts` must stay
|
|
28
28
|
* `Vec2 | Vec2[] | number` so it remains a clean member of the resolver's `Val`
|
|
29
29
|
* union, and a fully generic `AssetGeometry<TParts, TMeta>` would force `AssetSpec`,
|
|
30
|
-
* `registerAsset`, `resolve()`, and the render path to be generic too
|
|
30
|
+
* `registerAsset`, `resolve()`, and the render path to be generic too, and a
|
|
31
31
|
* specific `TMeta` interface isn't assignable to `Record<string, unknown>` without
|
|
32
32
|
* index-signature friction. This helper gives the type-safety at the read site
|
|
33
33
|
* with none of that ripple. (Asset domain data lives in `meta`; `parts` is geometry.)
|
package/dist/scene/assets.mjs
CHANGED
|
@@ -12,13 +12,13 @@ function listAssets() {
|
|
|
12
12
|
return [...ASSETS.keys()];
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Typed read of an asset's `meta
|
|
15
|
+
* Typed read of an asset's `meta`, write `const m = assetMeta<MyMeta>(geom)` in a
|
|
16
16
|
* Component instead of an inline `(geom.meta ?? {}) as MyMeta` cast everywhere.
|
|
17
17
|
*
|
|
18
18
|
* We deliberately keep `AssetGeometry` NON-generic: `parts` must stay
|
|
19
19
|
* `Vec2 | Vec2[] | number` so it remains a clean member of the resolver's `Val`
|
|
20
20
|
* union, and a fully generic `AssetGeometry<TParts, TMeta>` would force `AssetSpec`,
|
|
21
|
-
* `registerAsset`, `resolve()`, and the render path to be generic too
|
|
21
|
+
* `registerAsset`, `resolve()`, and the render path to be generic too, and a
|
|
22
22
|
* specific `TMeta` interface isn't assignable to `Record<string, unknown>` without
|
|
23
23
|
* index-signature friction. This helper gives the type-safety at the read site
|
|
24
24
|
* with none of that ripple. (Asset domain data lives in `meta`; `parts` is geometry.)
|
|
@@ -41,7 +41,16 @@ declare function collectRemoved(doc: SceneDoc, id: Id): {
|
|
|
41
41
|
elements: SceneElement[];
|
|
42
42
|
bindings: Binding[];
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Validate a command against the current doc; returns an error string, or null if it is safe to
|
|
46
|
+
* apply. The Editor runs this BEFORE mutating, so a bad command (from an agent or a tool) fails
|
|
47
|
+
* loudly with a reason instead of silently corrupting the scene: duplicate ids, missing mutate /
|
|
48
|
+
* remove targets, bindings to elements that do not exist, double binds, unbinding nothing.
|
|
49
|
+
*/
|
|
50
|
+
declare function validateCommand(doc: SceneDoc, cmd: Command): string | null;
|
|
51
|
+
/** The element ids a command (or batch) creates, for `CommandResult.createdIds`. */
|
|
52
|
+
declare function createdIds(cmd: Command): Id[];
|
|
44
53
|
declare function applyCommand(doc: SceneDoc, cmd: Command): SceneDoc;
|
|
45
54
|
declare function inverse(cmd: Command, prev: SceneDoc): Command;
|
|
46
55
|
//#endregion
|
|
47
|
-
export { Command, CommandResult, MutatePatch, applyCommand, collectRemoved, inverse };
|
|
56
|
+
export { Command, CommandResult, MutatePatch, applyCommand, collectRemoved, createdIds, inverse, validateCommand };
|
package/dist/scene/commands.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { parentIds } from "./schema.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/scene/commands.ts
|
|
5
5
|
/**
|
|
6
|
-
* Commands
|
|
6
|
+
* Commands, JSON, agent-emittable mutations. ALL changes funnel through these
|
|
7
7
|
* (builder, agent, undo, control-surface bridge). GeoGebra create-vs-mutate split.
|
|
8
8
|
* `applyCommand` is a pure reducer; `inverse` produces the undo command, including
|
|
9
9
|
* a concrete cascading-remove inverse that re-creates descendants + bindings in
|
|
@@ -48,6 +48,46 @@ function collectRemoved(doc, id) {
|
|
|
48
48
|
bindings: doc.bindings.filter((b) => removed.has(b.from.ref) || removed.has(b.to.ref))
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate a command against the current doc; returns an error string, or null if it is safe to
|
|
53
|
+
* apply. The Editor runs this BEFORE mutating, so a bad command (from an agent or a tool) fails
|
|
54
|
+
* loudly with a reason instead of silently corrupting the scene: duplicate ids, missing mutate /
|
|
55
|
+
* remove targets, bindings to elements that do not exist, double binds, unbinding nothing.
|
|
56
|
+
*/
|
|
57
|
+
function validateCommand(doc, cmd) {
|
|
58
|
+
const hasEl = (id) => doc.elements.some((e) => e.id === id);
|
|
59
|
+
switch (cmd.op) {
|
|
60
|
+
case "create":
|
|
61
|
+
if (hasEl(cmd.id)) return `create: element id '${cmd.id}' already exists`;
|
|
62
|
+
if (cmd.element.id !== cmd.id) return `create: element.id '${cmd.element.id}' does not match command id '${cmd.id}'`;
|
|
63
|
+
return null;
|
|
64
|
+
case "mutate": return hasEl(cmd.id) ? null : `mutate: no element '${cmd.id}'`;
|
|
65
|
+
case "remove": return hasEl(cmd.id) ? null : `remove: no element '${cmd.id}'`;
|
|
66
|
+
case "bind": {
|
|
67
|
+
const b = cmd.binding;
|
|
68
|
+
if ((doc.bindings ?? []).some((x) => x.id === b.id)) return `bind: binding id '${b.id}' already exists`;
|
|
69
|
+
if (!hasEl(b.from.ref)) return `bind: 'from' references unknown element '${b.from.ref}'`;
|
|
70
|
+
if (!hasEl(b.to.ref)) return `bind: 'to' references unknown element '${b.to.ref}'`;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
case "unbind": return (doc.bindings ?? []).some((b) => b.id === cmd.id) ? null : `unbind: no binding '${cmd.id}'`;
|
|
74
|
+
case "batch": {
|
|
75
|
+
let cur = doc;
|
|
76
|
+
for (const c of cmd.commands) {
|
|
77
|
+
const err = validateCommand(cur, c);
|
|
78
|
+
if (err) return `batch '${cmd.label}': ${err}`;
|
|
79
|
+
cur = applyCommand(cur, c);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The element ids a command (or batch) creates, for `CommandResult.createdIds`. */
|
|
86
|
+
function createdIds(cmd) {
|
|
87
|
+
if (cmd.op === "create") return [cmd.id];
|
|
88
|
+
if (cmd.op === "batch") return cmd.commands.flatMap(createdIds);
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
51
91
|
function applyCommand(doc, cmd) {
|
|
52
92
|
switch (cmd.op) {
|
|
53
93
|
case "create": {
|
|
@@ -159,4 +199,4 @@ function inverse(cmd, prev) {
|
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
//#endregion
|
|
162
|
-
export { applyCommand, collectRemoved, inverse };
|
|
202
|
+
export { applyCommand, collectRemoved, createdIds, inverse, validateCommand };
|
|
@@ -8,7 +8,7 @@ import { compileExpr } from "../math/index.mjs";
|
|
|
8
8
|
* Evaluator registry, keyed by def.op. Each evaluator turns a derived def into a
|
|
9
9
|
* Val, reading parent values through `ctx`. Geometry intersection math is ported
|
|
10
10
|
* from the legacy labs GeometryBoard; intersect picks the solution nearest the previous
|
|
11
|
-
* frame's resolved point (continuity
|
|
11
|
+
* frame's resolved point (continuity, never swaps P/Q on drag).
|
|
12
12
|
*/
|
|
13
13
|
const exprCache = /* @__PURE__ */ new Map();
|
|
14
14
|
function compiledExpr(src) {
|
|
@@ -182,7 +182,7 @@ const EVALUATORS = {
|
|
|
182
182
|
expr: (d, ctx) => {
|
|
183
183
|
const def = d;
|
|
184
184
|
const compiled = compiledExpr(def.fn);
|
|
185
|
-
if (!compiled)
|
|
185
|
+
if (!compiled) throw new Error(`expr: cannot parse formula "${def.fn}"`);
|
|
186
186
|
const scope = {};
|
|
187
187
|
for (const [name, ref] of Object.entries(def.inputs)) scope[name] = numOf(ctx.get(ref));
|
|
188
188
|
return compiled.fn(scope);
|
package/dist/scene/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Vec2 } from "../core/vec.mjs";
|
|
2
2
|
import { AssetGeometry, Binding, CircleVal, DerivedDef, DerivedElement, DerivedKind, ElementBase, ElementStyle, FreeElement, FreeNote, FreePoint, FreeScalar, Id, LabMeta, NumOrRef, Op, Ref, SceneDoc, SceneElement, SceneMeta, ShapeLineVal, SimDecl, Val, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isVec2, numOf } from "./types.mjs";
|
|
3
|
-
import { Command, CommandResult, MutatePatch, applyCommand, collectRemoved, inverse } from "./commands.mjs";
|
|
3
|
+
import { Command, CommandResult, MutatePatch, applyCommand, collectRemoved, createdIds, inverse, validateCommand } from "./commands.mjs";
|
|
4
4
|
import { Resolved, resolve } from "./resolve.mjs";
|
|
5
5
|
import { OP_REFS, isRef, parentIds } from "./schema.mjs";
|
|
6
6
|
import { EVALUATORS, EvalCtx } from "./evaluators.mjs";
|
|
@@ -10,4 +10,4 @@ import { Scene, SceneProps } from "./Scene.mjs";
|
|
|
10
10
|
import { RenderOpts, renderElements } from "./render.mjs";
|
|
11
11
|
import { AssetResolveArgs, AssetSpec, assetMeta, getAsset, listAssets, registerAsset } from "./assets.mjs";
|
|
12
12
|
import { SimStates, initSims, stepSims } from "./sims.mjs";
|
|
13
|
-
export { type AssetGeometry, type AssetResolveArgs, type AssetSpec, type Binding, CURRENT_SCHEMA_VERSION, type CircleVal, type Command, type CommandResult, type DerivedDef, type DerivedElement, type DerivedKind, EVALUATORS, type ElementBase, type ElementStyle, type EvalCtx, type FreeElement, type FreeNote, type FreePoint, type FreeScalar, type Id, type InstanceState, type LabMeta, type MutatePatch, type NumOrRef, OP_REFS, type Op, type Ref, type RenderOpts, type Resolved, Scene, type SceneDoc, type SceneElement, type SceneMeta, type SceneProps, SceneStore, type Vec2 as SceneVec2, type ShapeLineVal, type SimDecl, type SimStates, type Val, applyCommand, assetMeta, collectRemoved, emptyDoc, getAsset, initSims, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, migrate, numOf, parentIds, parse, registerAsset, renderElements, resolve, serialize, stepSims };
|
|
13
|
+
export { type AssetGeometry, type AssetResolveArgs, type AssetSpec, type Binding, CURRENT_SCHEMA_VERSION, type CircleVal, type Command, type CommandResult, type DerivedDef, type DerivedElement, type DerivedKind, EVALUATORS, type ElementBase, type ElementStyle, type EvalCtx, type FreeElement, type FreeNote, type FreePoint, type FreeScalar, type Id, type InstanceState, type LabMeta, type MutatePatch, type NumOrRef, OP_REFS, type Op, type Ref, type RenderOpts, type Resolved, Scene, type SceneDoc, type SceneElement, type SceneMeta, type SceneProps, SceneStore, type Vec2 as SceneVec2, type ShapeLineVal, type SimDecl, type SimStates, type Val, applyCommand, assetMeta, collectRemoved, createdIds, emptyDoc, getAsset, initSims, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, migrate, numOf, parentIds, parse, registerAsset, renderElements, resolve, serialize, stepSims, validateCommand };
|
package/dist/scene/index.mjs
CHANGED
|
@@ -3,11 +3,11 @@ import { OP_REFS, isRef, parentIds } from "./schema.mjs";
|
|
|
3
3
|
import { assetMeta, getAsset, listAssets, registerAsset } from "./assets.mjs";
|
|
4
4
|
import { EVALUATORS } from "./evaluators.mjs";
|
|
5
5
|
import { resolve } from "./resolve.mjs";
|
|
6
|
-
import { applyCommand, collectRemoved, inverse } from "./commands.mjs";
|
|
6
|
+
import { applyCommand, collectRemoved, createdIds, inverse, validateCommand } from "./commands.mjs";
|
|
7
7
|
import { CURRENT_SCHEMA_VERSION, emptyDoc, migrate, parse, serialize } from "./migrate.mjs";
|
|
8
8
|
import { SceneStore } from "./store.mjs";
|
|
9
9
|
import { renderElements } from "./render.mjs";
|
|
10
10
|
import { initSims, stepSims } from "./sims.mjs";
|
|
11
11
|
import { Scene } from "./Scene.mjs";
|
|
12
12
|
|
|
13
|
-
export { CURRENT_SCHEMA_VERSION, EVALUATORS, OP_REFS, Scene, SceneStore, applyCommand, assetMeta, collectRemoved, emptyDoc, getAsset, initSims, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, migrate, numOf, parentIds, parse, registerAsset, renderElements, resolve, serialize, stepSims };
|
|
13
|
+
export { CURRENT_SCHEMA_VERSION, EVALUATORS, OP_REFS, Scene, SceneStore, applyCommand, assetMeta, collectRemoved, createdIds, emptyDoc, getAsset, initSims, inverse, isAssetGeom, isCircleVal, isDerived, isFree, isLineVal, isRef, isVec2, listAssets, migrate, numOf, parentIds, parse, registerAsset, renderElements, resolve, serialize, stepSims, validateCommand };
|
package/dist/scene/migrate.mjs
CHANGED
|
@@ -9,7 +9,7 @@ function emptyDoc() {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
function migrate(raw) {
|
|
12
|
-
if (Array.isArray(raw)) throw new Error("legacy GeoElement[] array
|
|
12
|
+
if (Array.isArray(raw)) throw new Error("legacy GeoElement[] array, use the geoToScene codec (Phase 6)");
|
|
13
13
|
if (!raw || typeof raw !== "object") return emptyDoc();
|
|
14
14
|
const doc = raw;
|
|
15
15
|
return {
|
package/dist/scene/sims.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { getSim } from "../sim/registry.mjs";
|
|
|
10
10
|
* resolver stays a pure function of its inputs.
|
|
11
11
|
*
|
|
12
12
|
* Non-finite outputs (NaN/Inf from a diverging core) are dropped, not propagated
|
|
13
|
-
* (graceful failure
|
|
13
|
+
* (graceful failure, a bad sim never blanks the canvas).
|
|
14
14
|
*/
|
|
15
15
|
/** Seed each declared core from its params. */
|
|
16
16
|
function initSims(decls) {
|
package/dist/scene/types.d.mts
CHANGED
|
@@ -165,7 +165,7 @@ interface Binding {
|
|
|
165
165
|
props?: Record<string, unknown>;
|
|
166
166
|
}
|
|
167
167
|
/**
|
|
168
|
-
* Structured pedagogy for a lab
|
|
168
|
+
* Structured pedagogy for a lab, first-class learning data, NOT prose buried in
|
|
169
169
|
* UI text. Authored alongside the scene, surfaced to the learner seam + xAPI.
|
|
170
170
|
*/
|
|
171
171
|
interface LabMeta {
|
|
@@ -187,7 +187,7 @@ interface LabMeta {
|
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
189
189
|
* A time-stepped sim wired into a scene: a registered core advances each frame and
|
|
190
|
-
* its named output fields drive `scalar` elements (by id) as external inputs
|
|
190
|
+
* its named output fields drive `scalar` elements (by id) as external inputs, so
|
|
191
191
|
* the pure resolver re-evaluates with live values without knowing about time.
|
|
192
192
|
*/
|
|
193
193
|
interface SimDecl {
|
|
@@ -201,12 +201,12 @@ interface SimDecl {
|
|
|
201
201
|
/** Map: scalar-element id → core-state field that feeds it. */
|
|
202
202
|
drives: Record<Id, string>;
|
|
203
203
|
}
|
|
204
|
-
/** SceneDoc.meta
|
|
204
|
+
/** SceneDoc.meta, `pedagogy` + `sims` are typed; the rest stays open for domain use. */
|
|
205
205
|
type SceneMeta = {
|
|
206
206
|
pedagogy?: LabMeta;
|
|
207
207
|
sims?: SimDecl[];
|
|
208
208
|
} & Record<string, unknown>;
|
|
209
|
-
/** Canonical persisted form. `commands[]` is NOT stored
|
|
209
|
+
/** Canonical persisted form. `commands[]` is NOT stored, history lives in the Editor. */
|
|
210
210
|
interface SceneDoc {
|
|
211
211
|
schemaVersion: 2;
|
|
212
212
|
type: 'stage-scene';
|
package/dist/sim/particles.d.mts
CHANGED
|
@@ -28,14 +28,14 @@ interface ParticlesState {
|
|
|
28
28
|
py: number[];
|
|
29
29
|
vx: number[];
|
|
30
30
|
vy: number[];
|
|
31
|
-
/** 0 or 1
|
|
31
|
+
/** 0 or 1, two populations (for diffusion / colour). */
|
|
32
32
|
group: number[];
|
|
33
33
|
tSec: number;
|
|
34
34
|
/** Mean kinetic energy ∝ temperature. */
|
|
35
35
|
temp: number;
|
|
36
36
|
/** Smoothed pressure (wall-collision momentum per unit time per unit perimeter). */
|
|
37
37
|
pressure: number;
|
|
38
|
-
/** Mixing index 0 (separated) → 1 (uniform)
|
|
38
|
+
/** Mixing index 0 (separated) → 1 (uniform), fraction-based, for diffusion. */
|
|
39
39
|
mixed: number;
|
|
40
40
|
}
|
|
41
41
|
declare const ParticlesCore: SimCore<ParticlesState, ParticlesParams>;
|
package/dist/sim/rate.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ interface RateParams {
|
|
|
8
8
|
value0?: number;
|
|
9
9
|
/** Asymptote the value relaxes toward. Default 0 (pure decay). */
|
|
10
10
|
target?: number;
|
|
11
|
-
/** Time constant τ (seconds)
|
|
11
|
+
/** Time constant τ (seconds), the e-folding time. Default 1. */
|
|
12
12
|
tau?: number;
|
|
13
13
|
/** Stop after this many seconds (essentially relaxed) so it doesn't run forever. Default ∞. */
|
|
14
14
|
maxTime?: number;
|
|
@@ -21,15 +21,15 @@ interface RateState {
|
|
|
21
21
|
tau: number;
|
|
22
22
|
/** Seconds elapsed on this core's timeline. */
|
|
23
23
|
tSec: number;
|
|
24
|
-
/** Rolling history of `value`, oldest-first
|
|
24
|
+
/** Rolling history of `value`, oldest-first, for the curve. */
|
|
25
25
|
samples: number[];
|
|
26
26
|
cap: number;
|
|
27
27
|
maxTime: number;
|
|
28
|
-
/** True once tSec ≥ maxTime
|
|
28
|
+
/** True once tSec ≥ maxTime, the runtime stops the loop. */
|
|
29
29
|
done: boolean;
|
|
30
30
|
}
|
|
31
31
|
declare const RateCore: SimCore<RateState, RateParams>;
|
|
32
|
-
/** τ from a half-life (and back)
|
|
32
|
+
/** τ from a half-life (and back), t½ = τ·ln2. */
|
|
33
33
|
declare const halfLifeToTau: (halfLife: number) => number;
|
|
34
34
|
declare const tauToHalfLife: (tau: number) => number;
|
|
35
35
|
//#endregion
|
package/dist/sim/rate.mjs
CHANGED
package/dist/sim/registry.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SimCore } from "./types.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/sim/registry.d.ts
|
|
4
|
-
/** A core with its specifics erased
|
|
4
|
+
/** A core with its specifics erased, the runtime reads state fields by name. */
|
|
5
5
|
type AnySim = SimCore<Record<string, unknown>, Record<string, unknown>>;
|
|
6
6
|
declare function registerSim<S extends Record<string, unknown>, P>(name: string, core: SimCore<S, P>): void;
|
|
7
7
|
declare function getSim(name: string): AnySim | undefined;
|
package/dist/sim/sampler.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ interface SamplerParams {
|
|
|
8
8
|
perStep?: number;
|
|
9
9
|
/** PRNG seed. Default 1. */
|
|
10
10
|
seed?: number;
|
|
11
|
-
/** Stop drawing once `n` reaches this
|
|
11
|
+
/** Stop drawing once `n` reaches this, a converged sim shouldn't run forever. Default ∞. */
|
|
12
12
|
maxDraws?: number;
|
|
13
13
|
/** History ring-buffer capacity for the headline estimate p[0]. Default 240. */
|
|
14
14
|
trace?: number;
|
|
@@ -20,7 +20,7 @@ interface SamplerState {
|
|
|
20
20
|
* Current PRNG state (advances every draw). Deliberately NOT named `seed`: the
|
|
21
21
|
* Scene runtime merges the SimDecl `params` (which carries the initial `seed`)
|
|
22
22
|
* onto the state each frame, so an evolving field sharing a param name would be
|
|
23
|
-
* clobbered back to its initial value
|
|
23
|
+
* clobbered back to its initial value, freezing the RNG. Keep evolving state
|
|
24
24
|
* fields disjoint from param names.
|
|
25
25
|
*/
|
|
26
26
|
rng: number;
|
|
@@ -30,16 +30,16 @@ interface SamplerState {
|
|
|
30
30
|
counts: number[];
|
|
31
31
|
/** Running frequency estimate per outcome (counts / n). */
|
|
32
32
|
p: number[];
|
|
33
|
-
/** True (normalized) probabilities
|
|
33
|
+
/** True (normalized) probabilities, for convergence comparison. */
|
|
34
34
|
p0: number[];
|
|
35
35
|
/** Index of the most recent draw (-1 before the first). */
|
|
36
36
|
last: number;
|
|
37
|
-
/** Rolling history of p[0], oldest-first
|
|
37
|
+
/** Rolling history of p[0], oldest-first, for an LLN convergence plot. */
|
|
38
38
|
samples: number[];
|
|
39
39
|
cap: number;
|
|
40
40
|
/** Draw cap; step() becomes a no-op once n reaches it. */
|
|
41
41
|
maxDraws: number;
|
|
42
|
-
/** True once n ≥ maxDraws
|
|
42
|
+
/** True once n ≥ maxDraws, the runtime stops the loop. */
|
|
43
43
|
done: boolean;
|
|
44
44
|
}
|
|
45
45
|
declare const SamplerCore: SimCore<SamplerState, SamplerParams>;
|
package/dist/sim/thermal.d.mts
CHANGED
|
@@ -21,10 +21,10 @@ interface Substance {
|
|
|
21
21
|
/** Liquid colour token (for the skin). */
|
|
22
22
|
color: string;
|
|
23
23
|
}
|
|
24
|
-
/** Real water
|
|
24
|
+
/** Real water, the canonical example (note: latent vaporisation ≫ everything,
|
|
25
25
|
* so the boiling plateau dwarfs the rest, which is exactly the lesson). */
|
|
26
26
|
declare const WATER: Substance;
|
|
27
|
-
/** Ethanol
|
|
27
|
+
/** Ethanol, lower boiling point, smaller latent heats; a contrasting curve. */
|
|
28
28
|
declare const ETHANOL: Substance;
|
|
29
29
|
type Phase = 'solid' | 'melting' | 'liquid' | 'boiling' | 'gas';
|
|
30
30
|
/** One run of the energy ladder. `kind:'heat'` is a sloped temperature rise
|
|
@@ -58,7 +58,7 @@ interface ThermalState {
|
|
|
58
58
|
powerW: number;
|
|
59
59
|
tStart: number;
|
|
60
60
|
tMax: number;
|
|
61
|
-
/** The full energy ladder
|
|
61
|
+
/** The full energy ladder, the heating curve, as data. */
|
|
62
62
|
segs: ThermalSeg[];
|
|
63
63
|
/** Total heat to walk the whole ladder, J. */
|
|
64
64
|
totalJ: number;
|
package/dist/sim/thermal.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/sim/thermal.ts
|
|
2
2
|
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
3
|
-
/** Real water
|
|
3
|
+
/** Real water, the canonical example (note: latent vaporisation ≫ everything,
|
|
4
4
|
* so the boiling plateau dwarfs the rest, which is exactly the lesson). */
|
|
5
5
|
const WATER = {
|
|
6
6
|
name: "Water",
|
|
@@ -16,7 +16,7 @@ const WATER = {
|
|
|
16
16
|
tBoil: 100,
|
|
17
17
|
color: "var(--stage-accent, #3b82f6)"
|
|
18
18
|
};
|
|
19
|
-
/** Ethanol
|
|
19
|
+
/** Ethanol, lower boiling point, smaller latent heats; a contrasting curve. */
|
|
20
20
|
const ETHANOL = {
|
|
21
21
|
name: "Ethanol",
|
|
22
22
|
solidName: "solid",
|
package/dist/sim/types.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
//#region src/sim/types.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Sim cores
|
|
3
|
+
* Sim cores, the "physics engine" half of a lab: pure, time-stepped simulators
|
|
4
4
|
* with NO React and NO pixels. A core is `reset(params) → state`, then
|
|
5
5
|
* `step(state, dt) → state` once per frame. Because a core is a pure function of
|
|
6
6
|
* (params, elapsed time), a lab built on one is deterministic: it replays
|
|
7
7
|
* identically under a Remotion fixed-frame clock (→ video) and is unit-testable
|
|
8
8
|
* with no DOM. Rendering (glyphs) READS the returned state; drawing never lives
|
|
9
|
-
* in here. Live controls write their target fields onto `state` between steps
|
|
9
|
+
* in here. Live controls write their target fields onto `state` between steps ,
|
|
10
10
|
* so a core's mutable inputs (e.g. `volts`, `mode`) live IN its state, which is
|
|
11
11
|
* also why those fields are readable by name from a binding.
|
|
12
12
|
*/
|
package/dist/sim/wave.d.mts
CHANGED
|
@@ -22,9 +22,9 @@ interface WaveState {
|
|
|
22
22
|
tSec: number;
|
|
23
23
|
/** Instantaneous signed voltage; sign = current direction. */
|
|
24
24
|
v: number;
|
|
25
|
-
/** ∫v·dt
|
|
25
|
+
/** ∫v·dt, accumulated charge displacement; what flow position (electrons / water) reads. */
|
|
26
26
|
charge: number;
|
|
27
|
-
/** Rolling history of `v`, oldest-first, length ≤ `cap
|
|
27
|
+
/** Rolling history of `v`, oldest-first, length ≤ `cap`, for the live trace. */
|
|
28
28
|
samples: number[];
|
|
29
29
|
/** Fixed capacity of `samples`. */
|
|
30
30
|
cap: number;
|
package/dist/sim/wave.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import { oscillate } from "../core/motion.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/sim/wave.ts
|
|
4
4
|
/**
|
|
5
|
-
* `wave
|
|
5
|
+
* `wave`, the AC/DC signal source. One core drives a whole scene: a DC source
|
|
6
6
|
* holds a steady voltage; an AC source oscillates. Either way it exposes the
|
|
7
|
-
* instantaneous voltage `v` (signed
|
|
7
|
+
* instantaneous voltage `v` (signed, sign IS current direction) plus a rolling
|
|
8
8
|
* `samples` buffer for a live scrolling trace. The SAME `v` can skin many ways at
|
|
9
9
|
* once (a lamp's brightness, electrons in a wire, water sloshing in a pipe), which
|
|
10
10
|
* is the point: sim ≠ render, so one signal feeds every visual metaphor in sync.
|
package/dist/steps/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
//#region src/steps/index.d.ts
|
|
4
|
-
/** One step in the sequence
|
|
4
|
+
/** One step in the sequence, identity + gating only. Its TITLE, PROSE, and the
|
|
5
5
|
* interactive itself live in the authored Section block, not here. */
|
|
6
6
|
interface StepSpec {
|
|
7
7
|
id: string;
|
package/dist/steps/index.mjs
CHANGED
|
@@ -5,14 +5,14 @@ import { createContext, createElement, useContext, useMemo, useRef, useSyncExter
|
|
|
5
5
|
|
|
6
6
|
//#region src/steps/index.ts
|
|
7
7
|
/**
|
|
8
|
-
* Step progress + gating
|
|
8
|
+
* Step progress + gating, the runtime that turns an ordered sequence of lesson
|
|
9
9
|
* STEPS into "unlock the next when this one is solved" + aggregated completion.
|
|
10
10
|
*
|
|
11
|
-
* ONE SOURCE OF TRUTH for the step sequence is the authored content itself
|
|
11
|
+
* ONE SOURCE OF TRUTH for the step sequence is the authored content itself, the
|
|
12
12
|
* @classytic/cms-ui `Section`/`Step` blocks a creator orders in the editor
|
|
13
13
|
* (serialized in the MDX). This engine deliberately does NOT model an authored
|
|
14
14
|
* "LessonDoc": that would be a SECOND, competing definition of the same steps
|
|
15
|
-
* (and creators never hand-write one
|
|
15
|
+
* (and creators never hand-write one, they build Section blocks). The host's
|
|
16
16
|
* Section player derives a `StepSpec[]` from the section order, wraps each
|
|
17
17
|
* section's interactive in `<StepScope id>`, and reads `useStepProgress` to gate
|
|
18
18
|
* reveal. Each lab's `useLearner().report` flows into step completion AND
|
package/dist/thermo/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/thermo/index.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* `thermo
|
|
3
|
+
* `thermo`, ideal-gas thermodynamic processes, as a PURE kernel (like `field`:
|
|
4
4
|
* no React, no pixels, no time-stepping). A lab drives it with a slider/progress
|
|
5
5
|
* fraction; the kernel returns the full P–V path of a quasi-static process plus the
|
|
6
6
|
* first-law bookkeeping along it (work, heat, internal energy, entropy).
|
package/dist/thermo/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/thermo/index.ts
|
|
2
2
|
/**
|
|
3
|
-
* `thermo
|
|
3
|
+
* `thermo`, ideal-gas thermodynamic processes, as a PURE kernel (like `field`:
|
|
4
4
|
* no React, no pixels, no time-stepping). A lab drives it with a slider/progress
|
|
5
5
|
* fraction; the kernel returns the full P–V path of a quasi-static process plus the
|
|
6
6
|
* first-law bookkeeping along it (work, heat, internal energy, entropy).
|
package/dist/view/Stage.mjs
CHANGED
|
@@ -8,10 +8,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
|
|
9
9
|
//#region src/view/Stage.tsx
|
|
10
10
|
/**
|
|
11
|
-
* <Stage
|
|
11
|
+
* <Stage>, the SVG root. SSR-safe: builds a CoordinateSystem from explicit
|
|
12
12
|
* width/height/aspectRatio (or a default) so children render on first paint;
|
|
13
13
|
* after mount a ResizeObserver swaps in the measured width. There is ONE content
|
|
14
|
-
* group with no transform
|
|
14
|
+
* group with no transform, every primitive self-positions via coords.toPx().
|
|
15
15
|
* A pixel-space overlay group hosts selection halos/handles (builder phase).
|
|
16
16
|
*/
|
|
17
17
|
const DEFAULT_VIEW = {
|
|
@@ -31,7 +31,7 @@ function Stage({ view = DEFAULT_VIEW, width, height, aspectRatio, pad = 12, pres
|
|
|
31
31
|
}), [
|
|
32
32
|
w,
|
|
33
33
|
h,
|
|
34
|
-
view
|
|
34
|
+
`${view.xMin} ${view.xMax} ${view.yMin} ${view.yMax}`,
|
|
35
35
|
pad,
|
|
36
36
|
preserveAspect
|
|
37
37
|
]);
|
|
@@ -43,8 +43,7 @@ function Stage({ view = DEFAULT_VIEW, width, height, aspectRatio, pad = 12, pres
|
|
|
43
43
|
} : void 0;
|
|
44
44
|
return /* @__PURE__ */ jsx("div", {
|
|
45
45
|
ref: wrapRef,
|
|
46
|
-
className,
|
|
47
|
-
style: { width: "100%" },
|
|
46
|
+
className: ["stage-root", className].filter(Boolean).join(" "),
|
|
48
47
|
children: /* @__PURE__ */ jsxs("svg", {
|
|
49
48
|
ref: svgRef,
|
|
50
49
|
width: w,
|
|
@@ -53,14 +52,8 @@ function Stage({ view = DEFAULT_VIEW, width, height, aspectRatio, pad = 12, pres
|
|
|
53
52
|
role: "group",
|
|
54
53
|
"aria-label": ariaLabel,
|
|
55
54
|
onPointerDown: handlePointer,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
background,
|
|
59
|
-
borderRadius: 12,
|
|
60
|
-
border: "1px solid var(--stage-grid)",
|
|
61
|
-
userSelect: "none",
|
|
62
|
-
touchAction: "none"
|
|
63
|
-
},
|
|
55
|
+
className: "stage-canvas",
|
|
56
|
+
style: { background },
|
|
64
57
|
children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("marker", {
|
|
65
58
|
id: "stage-arrow",
|
|
66
59
|
viewBox: "0 0 10 10",
|
package/dist/view/useInView.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
|
|
5
5
|
//#region src/view/useInView.ts
|
|
6
6
|
/**
|
|
7
|
-
* useInView
|
|
7
|
+
* useInView, pause work when a lab scrolls off-screen. Sim labs drive an
|
|
8
8
|
* unbounded requestAnimationFrame loop; without this, every sim on a page runs
|
|
9
9
|
* forever even when nobody can see it (and a gallery of labs pins the CPU). Gate
|
|
10
10
|
* `useFrameLoop({ running: inView })` on this. SSR-safe; if IntersectionObserver
|