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.
- package/LICENSE +20 -0
- package/README.md +106 -0
- package/dist/chunk-66FUVAAG.js +7 -0
- package/dist/chunk-AIKB2FNR.js +17 -0
- package/dist/chunk-HTH6FQ7C.js +192 -0
- package/dist/chunk-OA6FGXTP.js +9 -0
- package/dist/chunk-SK5SHIWY.js +13 -0
- package/dist/chunk-YNLOTPDY.js +206 -0
- package/dist/engine.cjs +231 -0
- package/dist/engine.d.cts +72 -0
- package/dist/engine.d.ts +72 -0
- package/dist/engine.js +8 -0
- package/dist/react/EngineContext.cjs +43 -0
- package/dist/react/EngineContext.d.cts +18 -0
- package/dist/react/EngineContext.d.ts +18 -0
- package/dist/react/EngineContext.js +6 -0
- package/dist/react/PerformanceOverlay.cjs +341 -0
- package/dist/react/PerformanceOverlay.d.cts +9 -0
- package/dist/react/PerformanceOverlay.d.ts +9 -0
- package/dist/react/PerformanceOverlay.js +290 -0
- package/dist/react/Simulation.cjs +481 -0
- package/dist/react/Simulation.d.cts +31 -0
- package/dist/react/Simulation.d.ts +31 -0
- package/dist/react/Simulation.js +235 -0
- package/dist/react/SimulationContext.cjs +41 -0
- package/dist/react/SimulationContext.d.cts +25 -0
- package/dist/react/SimulationContext.d.ts +25 -0
- package/dist/react/SimulationContext.js +6 -0
- package/dist/react/SimulationControls.cjs +298 -0
- package/dist/react/SimulationControls.d.cts +33 -0
- package/dist/react/SimulationControls.d.ts +33 -0
- package/dist/react/SimulationControls.js +80 -0
- package/dist/react/controlPrimitives.cjs +247 -0
- package/dist/react/controlPrimitives.d.cts +45 -0
- package/dist/react/controlPrimitives.d.ts +45 -0
- package/dist/react/controlPrimitives.js +22 -0
- package/dist/react/hooks.cjs +53 -0
- package/dist/react/hooks.d.cts +26 -0
- package/dist/react/hooks.d.ts +26 -0
- package/dist/react/hooks.js +7 -0
- package/dist/react/stableCallback.cjs +47 -0
- package/dist/react/stableCallback.d.cts +3 -0
- package/dist/react/stableCallback.d.ts +3 -0
- package/dist/react/stableCallback.js +6 -0
- package/dist/react/useSimulationCanvas.cjs +92 -0
- package/dist/react/useSimulationCanvas.d.cts +38 -0
- package/dist/react/useSimulationCanvas.d.ts +38 -0
- package/dist/react/useSimulationCanvas.js +53 -0
- package/dist/sim.cjs +32 -0
- package/dist/sim.d.cts +40 -0
- package/dist/sim.d.ts +40 -0
- package/dist/sim.js +7 -0
- package/dist/worker/createSimWorker.cjs +147 -0
- package/dist/worker/createSimWorker.d.cts +19 -0
- package/dist/worker/createSimWorker.d.ts +19 -0
- package/dist/worker/createSimWorker.js +122 -0
- package/dist/worker/protocol.cjs +18 -0
- package/dist/worker/protocol.d.cts +59 -0
- package/dist/worker/protocol.d.ts +59 -0
- package/dist/worker/protocol.js +0 -0
- package/dist/worker/workerRunner.cjs +100 -0
- package/dist/worker/workerRunner.d.cts +30 -0
- package/dist/worker/workerRunner.d.ts +30 -0
- package/dist/worker/workerRunner.js +73 -0
- 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,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 };
|