automatick 0.0.1

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 (65) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +106 -0
  3. package/dist/chunk-66FUVAAG.js +7 -0
  4. package/dist/chunk-AIKB2FNR.js +17 -0
  5. package/dist/chunk-HTH6FQ7C.js +192 -0
  6. package/dist/chunk-OA6FGXTP.js +9 -0
  7. package/dist/chunk-SK5SHIWY.js +13 -0
  8. package/dist/chunk-YNLOTPDY.js +206 -0
  9. package/dist/engine.cjs +231 -0
  10. package/dist/engine.d.cts +72 -0
  11. package/dist/engine.d.ts +72 -0
  12. package/dist/engine.js +8 -0
  13. package/dist/react/EngineContext.cjs +43 -0
  14. package/dist/react/EngineContext.d.cts +18 -0
  15. package/dist/react/EngineContext.d.ts +18 -0
  16. package/dist/react/EngineContext.js +6 -0
  17. package/dist/react/PerformanceOverlay.cjs +341 -0
  18. package/dist/react/PerformanceOverlay.d.cts +9 -0
  19. package/dist/react/PerformanceOverlay.d.ts +9 -0
  20. package/dist/react/PerformanceOverlay.js +290 -0
  21. package/dist/react/Simulation.cjs +481 -0
  22. package/dist/react/Simulation.d.cts +31 -0
  23. package/dist/react/Simulation.d.ts +31 -0
  24. package/dist/react/Simulation.js +235 -0
  25. package/dist/react/SimulationContext.cjs +41 -0
  26. package/dist/react/SimulationContext.d.cts +25 -0
  27. package/dist/react/SimulationContext.d.ts +25 -0
  28. package/dist/react/SimulationContext.js +6 -0
  29. package/dist/react/SimulationControls.cjs +298 -0
  30. package/dist/react/SimulationControls.d.cts +33 -0
  31. package/dist/react/SimulationControls.d.ts +33 -0
  32. package/dist/react/SimulationControls.js +80 -0
  33. package/dist/react/controlPrimitives.cjs +247 -0
  34. package/dist/react/controlPrimitives.d.cts +45 -0
  35. package/dist/react/controlPrimitives.d.ts +45 -0
  36. package/dist/react/controlPrimitives.js +22 -0
  37. package/dist/react/hooks.cjs +53 -0
  38. package/dist/react/hooks.d.cts +26 -0
  39. package/dist/react/hooks.d.ts +26 -0
  40. package/dist/react/hooks.js +7 -0
  41. package/dist/react/stableCallback.cjs +47 -0
  42. package/dist/react/stableCallback.d.cts +3 -0
  43. package/dist/react/stableCallback.d.ts +3 -0
  44. package/dist/react/stableCallback.js +6 -0
  45. package/dist/react/useSimulationCanvas.cjs +92 -0
  46. package/dist/react/useSimulationCanvas.d.cts +38 -0
  47. package/dist/react/useSimulationCanvas.d.ts +38 -0
  48. package/dist/react/useSimulationCanvas.js +53 -0
  49. package/dist/sim.cjs +32 -0
  50. package/dist/sim.d.cts +40 -0
  51. package/dist/sim.d.ts +40 -0
  52. package/dist/sim.js +7 -0
  53. package/dist/worker/createSimWorker.cjs +147 -0
  54. package/dist/worker/createSimWorker.d.cts +19 -0
  55. package/dist/worker/createSimWorker.d.ts +19 -0
  56. package/dist/worker/createSimWorker.js +122 -0
  57. package/dist/worker/protocol.cjs +18 -0
  58. package/dist/worker/protocol.d.cts +59 -0
  59. package/dist/worker/protocol.d.ts +59 -0
  60. package/dist/worker/protocol.js +0 -0
  61. package/dist/worker/workerRunner.cjs +100 -0
  62. package/dist/worker/workerRunner.d.cts +30 -0
  63. package/dist/worker/workerRunner.d.ts +30 -0
  64. package/dist/worker/workerRunner.js +73 -0
  65. package/package.json +102 -0
@@ -0,0 +1,235 @@
1
+ import {
2
+ useStableCallback
3
+ } from "../chunk-SK5SHIWY.js";
4
+ import {
5
+ createEngine
6
+ } from "../chunk-YNLOTPDY.js";
7
+ import {
8
+ EngineContext
9
+ } from "../chunk-OA6FGXTP.js";
10
+ import {
11
+ SimulationContext
12
+ } from "../chunk-66FUVAAG.js";
13
+
14
+ // src/react/Simulation.tsx
15
+ import React from "react";
16
+ import { jsx } from "react/jsx-runtime";
17
+ function LocalSimulation(props) {
18
+ const { sim, params: paramsProp, children, autoplay } = props;
19
+ const engineRef = React.useRef(null);
20
+ if (!engineRef.current) {
21
+ const initialParams = paramsProp ? { ...sim.defaultParams, ...paramsProp } : sim.defaultParams;
22
+ engineRef.current = createEngine({
23
+ init: sim.init,
24
+ step: sim.step,
25
+ shouldStop: sim.shouldStop,
26
+ initialParams,
27
+ maxTime: props.maxTime,
28
+ delayMs: props.delayMs,
29
+ ticksPerFrame: props.ticksPerFrame
30
+ });
31
+ }
32
+ const engine = engineRef.current;
33
+ const [snapshot, setSnapshot] = React.useState(() => engine.getSnapshot());
34
+ React.useEffect(() => engine.subscribe((next) => setSnapshot(next)), [engine]);
35
+ React.useEffect(() => {
36
+ if (autoplay) engine.play();
37
+ }, [engine, autoplay]);
38
+ const isFirstRender = React.useRef(true);
39
+ React.useEffect(() => {
40
+ if (isFirstRender.current) {
41
+ isFirstRender.current = false;
42
+ return;
43
+ }
44
+ if (paramsProp) engine.setParams(paramsProp);
45
+ }, [engine, paramsProp]);
46
+ React.useEffect(() => {
47
+ if (props.delayMs !== void 0) engine.setDelayMs(props.delayMs);
48
+ }, [engine, props.delayMs]);
49
+ React.useEffect(() => {
50
+ if (props.ticksPerFrame !== void 0) engine.setTicksPerFrame(props.ticksPerFrame);
51
+ }, [engine, props.ticksPerFrame]);
52
+ React.useEffect(() => {
53
+ if (typeof window === "undefined" || typeof window.requestAnimationFrame !== "function")
54
+ return;
55
+ let rafId = 0;
56
+ const loop = (now) => {
57
+ engine.handleAnimationFrame(now);
58
+ rafId = window.requestAnimationFrame(loop);
59
+ };
60
+ rafId = window.requestAnimationFrame(loop);
61
+ return () => window.cancelAnimationFrame(rafId);
62
+ }, [engine]);
63
+ return /* @__PURE__ */ jsx(SimulationProvider, { snapshot, backend: engine, children });
64
+ }
65
+ function WorkerSimulation(props) {
66
+ const { children, autoplay } = props;
67
+ const [backend, setBackend] = React.useState(
68
+ null
69
+ );
70
+ const [snapshot, setSnapshot] = React.useState(null);
71
+ const propsRef = React.useRef(props);
72
+ propsRef.current = props;
73
+ React.useEffect(() => {
74
+ let cancelled = false;
75
+ let runner = null;
76
+ (async () => {
77
+ const mod = await propsRef.current.worker();
78
+ const simModule = mod.default;
79
+ if (cancelled) return;
80
+ const p = propsRef.current;
81
+ const initialParams = p.params ? { ...simModule.defaultParams, ...p.params } : simModule.defaultParams;
82
+ const engine = createEngine({
83
+ init: simModule.init,
84
+ step: simModule.step,
85
+ shouldStop: simModule.shouldStop,
86
+ initialParams,
87
+ maxTime: p.maxTime
88
+ });
89
+ if (cancelled) {
90
+ engine.destroy();
91
+ return;
92
+ }
93
+ const delayMs = p.delayMs ?? 0;
94
+ const ticksPerFrame = p.ticksPerFrame ?? 1;
95
+ let loopTimer = null;
96
+ function stopLoop() {
97
+ if (loopTimer !== null) {
98
+ clearTimeout(loopTimer);
99
+ loopTimer = null;
100
+ }
101
+ }
102
+ function tickLoop() {
103
+ if (engine.getStatus() !== "playing") return;
104
+ for (let i = 0; i < ticksPerFrame; i++) {
105
+ engine.advance(1);
106
+ const s = engine.getStatus();
107
+ if (s === "stopped") return;
108
+ if (s !== "paused") break;
109
+ }
110
+ if (engine.getStatus() === "paused") {
111
+ engine.play();
112
+ }
113
+ loopTimer = setTimeout(tickLoop, delayMs);
114
+ }
115
+ runner = {
116
+ getSnapshot: () => engine.getSnapshot(),
117
+ subscribe: (listener) => engine.subscribe(listener),
118
+ play: () => {
119
+ engine.play();
120
+ stopLoop();
121
+ loopTimer = setTimeout(tickLoop, 0);
122
+ },
123
+ pause: () => {
124
+ stopLoop();
125
+ engine.pause();
126
+ },
127
+ stop: () => {
128
+ stopLoop();
129
+ engine.stop();
130
+ },
131
+ seek: (tick) => {
132
+ stopLoop();
133
+ engine.seek(tick);
134
+ },
135
+ advance: (count) => engine.advance(count),
136
+ setParams: (patch) => engine.setParams(patch),
137
+ resetWith: (patch) => {
138
+ stopLoop();
139
+ engine.resetWith(patch);
140
+ },
141
+ destroy: () => {
142
+ stopLoop();
143
+ engine.destroy();
144
+ },
145
+ recordDrawTime: (tick, ms) => engine.recordDrawTime(tick, ms),
146
+ getPerformance: () => engine.getPerformance()
147
+ };
148
+ if (!cancelled) {
149
+ setBackend(runner);
150
+ setSnapshot(engine.getSnapshot());
151
+ }
152
+ })();
153
+ return () => {
154
+ cancelled = true;
155
+ runner?.destroy();
156
+ };
157
+ }, []);
158
+ React.useEffect(() => {
159
+ if (!backend) return;
160
+ return backend.subscribe((next) => setSnapshot(next));
161
+ }, [backend]);
162
+ React.useEffect(() => {
163
+ if (autoplay && backend) backend.play();
164
+ }, [backend, autoplay]);
165
+ const isFirstRender = React.useRef(true);
166
+ React.useEffect(() => {
167
+ if (isFirstRender.current) {
168
+ isFirstRender.current = false;
169
+ return;
170
+ }
171
+ if (props.params && backend) backend.setParams(props.params);
172
+ }, [backend, props.params]);
173
+ if (!snapshot || !backend) return null;
174
+ return /* @__PURE__ */ jsx(SimulationProvider, { snapshot, backend, children });
175
+ }
176
+ function SimulationProvider({
177
+ snapshot,
178
+ backend,
179
+ children
180
+ }) {
181
+ const play = useStableCallback(() => backend.play());
182
+ const pause = useStableCallback(() => backend.pause());
183
+ const stop = useStableCallback(() => backend.stop());
184
+ const seek = useStableCallback((t) => backend.seek(t));
185
+ const advance = useStableCallback((count) => backend.advance(count));
186
+ const setParams = useStableCallback(
187
+ (patch) => backend.setParams(patch)
188
+ );
189
+ const resetWith = useStableCallback(
190
+ (patch) => backend.resetWith(patch)
191
+ );
192
+ const value = React.useMemo(
193
+ () => ({
194
+ data: snapshot.data,
195
+ params: snapshot.params,
196
+ tick: snapshot.tick,
197
+ status: snapshot.status,
198
+ play,
199
+ pause,
200
+ stop,
201
+ seek,
202
+ advance,
203
+ setParams,
204
+ resetWith
205
+ }),
206
+ [snapshot, play, pause, stop, seek, advance, setParams, resetWith]
207
+ );
208
+ const engineValue = React.useMemo(
209
+ () => ({
210
+ subscribe: (listener) => backend.subscribe(
211
+ listener
212
+ ),
213
+ getSnapshot: () => backend.getSnapshot(),
214
+ recordDrawTime: (tick, ms) => backend.recordDrawTime(tick, ms),
215
+ getPerformance: () => backend.getPerformance()
216
+ }),
217
+ [backend]
218
+ );
219
+ return /* @__PURE__ */ jsx(EngineContext.Provider, { value: engineValue, children: /* @__PURE__ */ jsx(
220
+ SimulationContext.Provider,
221
+ {
222
+ value,
223
+ children
224
+ }
225
+ ) });
226
+ }
227
+ function Simulation(props) {
228
+ if ("worker" in props && props.worker != null) {
229
+ return /* @__PURE__ */ jsx(WorkerSimulation, { ...props });
230
+ }
231
+ return /* @__PURE__ */ jsx(LocalSimulation, { ...props });
232
+ }
233
+ export {
234
+ Simulation
235
+ };
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react/SimulationContext.tsx
31
+ var SimulationContext_exports = {};
32
+ __export(SimulationContext_exports, {
33
+ SimulationContext: () => SimulationContext
34
+ });
35
+ module.exports = __toCommonJS(SimulationContext_exports);
36
+ var import_react = __toESM(require("react"), 1);
37
+ var SimulationContext = import_react.default.createContext(null);
38
+ // Annotate the CommonJS export names for ESM import in node:
39
+ 0 && (module.exports = {
40
+ SimulationContext
41
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { SimulationStatus } from '../engine.cjs';
3
+ import '../sim.cjs';
4
+
5
+ type SimulationContextValue<Data, Params> = {
6
+ data: Data;
7
+ params: Params;
8
+ tick: number;
9
+ status: SimulationStatus;
10
+ play: () => void;
11
+ pause: () => void;
12
+ stop: () => void;
13
+ seek: (tick: number) => void;
14
+ advance: (count?: number) => void;
15
+ setParams: (patch: Partial<Params>) => void;
16
+ resetWith: (patch?: Partial<Params>) => void;
17
+ };
18
+ /**
19
+ * Runtime context value uses `unknown` for data/params since React.createContext
20
+ * cannot be generic. The useSimulation hook casts to the correct types.
21
+ */
22
+ type RuntimeContextValue = SimulationContextValue<unknown, unknown>;
23
+ declare const SimulationContext: React.Context<RuntimeContextValue | null>;
24
+
25
+ export { SimulationContext, type SimulationContextValue };
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { SimulationStatus } from '../engine.js';
3
+ import '../sim.js';
4
+
5
+ type SimulationContextValue<Data, Params> = {
6
+ data: Data;
7
+ params: Params;
8
+ tick: number;
9
+ status: SimulationStatus;
10
+ play: () => void;
11
+ pause: () => void;
12
+ stop: () => void;
13
+ seek: (tick: number) => void;
14
+ advance: (count?: number) => void;
15
+ setParams: (patch: Partial<Params>) => void;
16
+ resetWith: (patch?: Partial<Params>) => void;
17
+ };
18
+ /**
19
+ * Runtime context value uses `unknown` for data/params since React.createContext
20
+ * cannot be generic. The useSimulation hook casts to the correct types.
21
+ */
22
+ type RuntimeContextValue = SimulationContextValue<unknown, unknown>;
23
+ declare const SimulationContext: React.Context<RuntimeContextValue | null>;
24
+
25
+ export { SimulationContext, type SimulationContextValue };
@@ -0,0 +1,6 @@
1
+ import {
2
+ SimulationContext
3
+ } from "../chunk-66FUVAAG.js";
4
+ export {
5
+ SimulationContext
6
+ };
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react/SimulationControls.tsx
31
+ var SimulationControls_exports = {};
32
+ __export(SimulationControls_exports, {
33
+ StandardControls: () => StandardControls
34
+ });
35
+ module.exports = __toCommonJS(SimulationControls_exports);
36
+
37
+ // src/react/hooks.ts
38
+ var import_react2 = __toESM(require("react"), 1);
39
+
40
+ // src/react/SimulationContext.tsx
41
+ var import_react = __toESM(require("react"), 1);
42
+ var SimulationContext = import_react.default.createContext(null);
43
+
44
+ // src/react/hooks.ts
45
+ function useSimulation() {
46
+ const ctx = import_react2.default.useContext(SimulationContext);
47
+ if (!ctx) {
48
+ throw new Error("useSimulation must be used within a <Simulation>");
49
+ }
50
+ return ctx;
51
+ }
52
+
53
+ // src/react/controlPrimitives.tsx
54
+ var import_jsx_runtime = require("react/jsx-runtime");
55
+ var btnStyle = {
56
+ padding: "6px 12px",
57
+ borderRadius: 6,
58
+ border: "1px solid rgba(0,0,0,0.2)",
59
+ background: "#fff",
60
+ cursor: "pointer"
61
+ };
62
+ function PlayPauseButton(props) {
63
+ const { status, play, pause } = useSimulation();
64
+ const { playingLabel = "Pause", pausedLabel = "Play", disabled } = props;
65
+ const isPlaying = status === "playing";
66
+ const canPlay = status === "idle" || status === "paused";
67
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
68
+ "button",
69
+ {
70
+ type: "button",
71
+ style: btnStyle,
72
+ onClick: isPlaying ? pause : play,
73
+ disabled: disabled ?? (!canPlay && !isPlaying),
74
+ children: isPlaying ? playingLabel : pausedLabel
75
+ }
76
+ );
77
+ }
78
+ function StopButton(props) {
79
+ const { status, stop } = useSimulation();
80
+ const canStop = status === "playing" || status === "paused";
81
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ style: btnStyle,
86
+ onClick: stop,
87
+ disabled: props.disabled ?? !canStop,
88
+ children: props.label ?? "Stop"
89
+ }
90
+ );
91
+ }
92
+ function StepButton(props) {
93
+ const { status, advance } = useSimulation();
94
+ const ticks = props.ticks ?? 1;
95
+ const canStep = status === "idle" || status === "paused";
96
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ style: btnStyle,
101
+ onClick: () => advance(ticks),
102
+ disabled: props.disabled ?? !canStep,
103
+ title: "Advance one or more ticks while paused",
104
+ children: props.label ?? `Step${ticks > 1 ? ` (${ticks})` : ""}`
105
+ }
106
+ );
107
+ }
108
+ function ResetButton(props) {
109
+ const { resetWith } = useSimulation();
110
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: btnStyle, onClick: () => resetWith(), children: props.label ?? "Reset" });
111
+ }
112
+ function TickSeekSlider(props) {
113
+ const { tick, status, seek } = useSimulation();
114
+ const min = props.min ?? 0;
115
+ const max = props.max;
116
+ const disabled = status === "stopped";
117
+ if (!Number.isFinite(max)) return null;
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
119
+ "div",
120
+ {
121
+ style: {
122
+ display: "flex",
123
+ gap: 10,
124
+ alignItems: "center",
125
+ width: "100%"
126
+ },
127
+ children: [
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
129
+ "input",
130
+ {
131
+ type: "range",
132
+ min,
133
+ max,
134
+ step: 1,
135
+ value: tick,
136
+ onChange: (e) => seek(Number(e.target.value)),
137
+ disabled,
138
+ style: { flex: 1 }
139
+ }
140
+ ),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
142
+ "span",
143
+ {
144
+ style: {
145
+ fontFamily: "monospace",
146
+ opacity: 0.8,
147
+ minWidth: 48,
148
+ textAlign: "right"
149
+ },
150
+ children: tick
151
+ }
152
+ )
153
+ ]
154
+ }
155
+ );
156
+ }
157
+ function ParamRangeField(props) {
158
+ const { params, setParams } = useSimulation();
159
+ const label = props.label ?? props.param;
160
+ const raw = params[props.param];
161
+ const numericValue = Number(raw);
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
163
+ "label",
164
+ {
165
+ style: {
166
+ display: "flex",
167
+ gap: 10,
168
+ alignItems: "center",
169
+ width: "100%"
170
+ },
171
+ children: [
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { minWidth: 120, opacity: 0.9 }, children: label }),
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
174
+ "input",
175
+ {
176
+ type: "range",
177
+ min: props.min,
178
+ max: props.max,
179
+ step: props.step ?? 1,
180
+ value: Number.isFinite(numericValue) ? numericValue : props.min,
181
+ onChange: (e) => {
182
+ const v = Number(e.target.value);
183
+ setParams({ [props.param]: v });
184
+ },
185
+ style: { flex: 1 }
186
+ }
187
+ ),
188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
189
+ "span",
190
+ {
191
+ style: {
192
+ width: 72,
193
+ textAlign: "right",
194
+ fontFamily: "monospace",
195
+ opacity: 0.8
196
+ },
197
+ children: numericValue
198
+ }
199
+ )
200
+ ]
201
+ }
202
+ );
203
+ }
204
+ function ParamToggleField(props) {
205
+ const { params, setParams } = useSimulation();
206
+ const label = props.label ?? props.param;
207
+ const checked = Boolean(params[props.param]);
208
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { minWidth: 120, opacity: 0.9 }, children: label }),
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
211
+ "input",
212
+ {
213
+ type: "checkbox",
214
+ checked,
215
+ onChange: (e) => {
216
+ setParams({ [props.param]: e.target.checked });
217
+ }
218
+ }
219
+ )
220
+ ] });
221
+ }
222
+ function TickReadout(props) {
223
+ const { tick } = useSimulation();
224
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontFamily: "monospace", opacity: 0.85 }, children: [
225
+ props.prefix ?? "",
226
+ "tick: ",
227
+ tick
228
+ ] });
229
+ }
230
+
231
+ // src/react/SimulationControls.tsx
232
+ var import_jsx_runtime2 = require("react/jsx-runtime");
233
+ function asArray(maybeArray) {
234
+ if (!maybeArray) return [];
235
+ return Array.isArray(maybeArray) ? maybeArray : [maybeArray];
236
+ }
237
+ var panelStyle = {
238
+ display: "flex",
239
+ flexDirection: "column",
240
+ gap: 10,
241
+ padding: 12,
242
+ border: "1px solid rgba(0,0,0,0.15)",
243
+ borderRadius: 8
244
+ };
245
+ function StandardControls(props) {
246
+ const controls = asArray(props.controls);
247
+ const maxTime = props.maxTime;
248
+ const canSeek = maxTime !== void 0 && Number.isFinite(maxTime);
249
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: panelStyle, children: [
250
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
251
+ "div",
252
+ {
253
+ style: {
254
+ display: "flex",
255
+ gap: 8,
256
+ alignItems: "center",
257
+ flexWrap: "wrap"
258
+ },
259
+ children: [
260
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PlayPauseButton, {}),
261
+ props.showStopButton ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StopButton, {}) : null,
262
+ props.showStepButton ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StepButton, {}) : null,
263
+ props.showResetButton !== false ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ResetButton, {}) : null,
264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TickReadout, {})
265
+ ]
266
+ }
267
+ ),
268
+ canSeek ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TickSeekSlider, { min: props.minTime, max: maxTime }) : null,
269
+ controls.length ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: controls.map((c) => {
270
+ const key = `${c.type ?? "range"}-${c.param}`;
271
+ if (c.type === "toggle") {
272
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
273
+ ParamToggleField,
274
+ {
275
+ param: c.param,
276
+ label: c.label
277
+ },
278
+ key
279
+ );
280
+ }
281
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
282
+ ParamRangeField,
283
+ {
284
+ param: c.param,
285
+ label: c.label,
286
+ min: c.min,
287
+ max: c.max,
288
+ step: c.step
289
+ },
290
+ key
291
+ );
292
+ }) }) : null
293
+ ] });
294
+ }
295
+ // Annotate the CommonJS export names for ESM import in node:
296
+ 0 && (module.exports = {
297
+ StandardControls
298
+ });
@@ -0,0 +1,33 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type ParamRangeControl = {
4
+ type?: 'range';
5
+ param: string;
6
+ label?: string;
7
+ min: number;
8
+ max: number;
9
+ step?: number;
10
+ };
11
+ type ParamToggleControl = {
12
+ type: 'toggle';
13
+ param: string;
14
+ label?: string;
15
+ };
16
+ type ParamControl = ParamRangeControl | ParamToggleControl;
17
+ type StandardControlsProps = {
18
+ controls?: ParamControl | ParamControl[] | null;
19
+ maxTime?: number;
20
+ minTime?: number;
21
+ showStepButton?: boolean;
22
+ /** Show the stop button. Default false — stop puts the sim in a dead-end state. */
23
+ showStopButton?: boolean;
24
+ /** Show the reset button. Default true. */
25
+ showResetButton?: boolean;
26
+ };
27
+ /**
28
+ * Batteries-included control strip: play/pause, stop, optional step/reset,
29
+ * tick readout, optional seek slider, and optional param fields.
30
+ */
31
+ declare function StandardControls(props: StandardControlsProps): react_jsx_runtime.JSX.Element;
32
+
33
+ export { type ParamControl, type ParamRangeControl, type ParamToggleControl, StandardControls, type StandardControlsProps };
@@ -0,0 +1,33 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type ParamRangeControl = {
4
+ type?: 'range';
5
+ param: string;
6
+ label?: string;
7
+ min: number;
8
+ max: number;
9
+ step?: number;
10
+ };
11
+ type ParamToggleControl = {
12
+ type: 'toggle';
13
+ param: string;
14
+ label?: string;
15
+ };
16
+ type ParamControl = ParamRangeControl | ParamToggleControl;
17
+ type StandardControlsProps = {
18
+ controls?: ParamControl | ParamControl[] | null;
19
+ maxTime?: number;
20
+ minTime?: number;
21
+ showStepButton?: boolean;
22
+ /** Show the stop button. Default false — stop puts the sim in a dead-end state. */
23
+ showStopButton?: boolean;
24
+ /** Show the reset button. Default true. */
25
+ showResetButton?: boolean;
26
+ };
27
+ /**
28
+ * Batteries-included control strip: play/pause, stop, optional step/reset,
29
+ * tick readout, optional seek slider, and optional param fields.
30
+ */
31
+ declare function StandardControls(props: StandardControlsProps): react_jsx_runtime.JSX.Element;
32
+
33
+ export { type ParamControl, type ParamRangeControl, type ParamToggleControl, StandardControls, type StandardControlsProps };