@simten/embed 0.1.13 → 0.1.15
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/dist/CircuitEmbed.d.ts +6 -6
- package/dist/CircuitEmbed.d.ts.map +1 -1
- package/dist/CircuitEmbed.js +18 -19
- package/dist/CircuitEmbed.js.map +1 -1
- package/dist/CircuitViewer.d.ts +6 -6
- package/dist/CircuitViewer.d.ts.map +1 -1
- package/dist/CircuitViewer.js +4 -11
- package/dist/CircuitViewer.js.map +1 -1
- package/dist/canvas/index.d.ts +2 -2
- package/dist/canvas/index.d.ts.map +1 -1
- package/dist/canvas/index.js +1 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/circuit-embed.js +25 -25
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.js +4 -4
- package/dist/components/ErrorBoundary.js.map +1 -1
- package/dist/components/ErrorDisplay.d.ts +1 -1
- package/dist/components/ErrorDisplay.d.ts.map +1 -1
- package/dist/components/ErrorDisplay.js +8 -8
- package/dist/components/ErrorDisplay.js.map +1 -1
- package/dist/components/LoadingSkeleton.d.ts.map +1 -1
- package/dist/components/LoadingSkeleton.js +1 -1
- package/dist/components/LoadingSkeleton.js.map +1 -1
- package/dist/components/nodes/index.d.ts +1 -1
- package/dist/components/nodes/index.d.ts.map +1 -1
- package/dist/components/nodes/index.js +1 -1
- package/dist/components/nodes/index.js.map +1 -1
- package/dist/hooks/useCircuitSimulator.d.ts +3 -3
- package/dist/hooks/useCircuitSimulator.d.ts.map +1 -1
- package/dist/hooks/useCircuitSimulator.js +64 -19
- package/dist/hooks/useCircuitSimulator.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/lib/utils.js.map +1 -1
- package/dist/share-context.d.ts +1 -1
- package/dist/share-context.d.ts.map +1 -1
- package/dist/share-context.js +2 -2
- package/dist/share-context.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/webcomponent/WebComponentEmbed.d.ts +1 -1
- package/dist/webcomponent/WebComponentEmbed.d.ts.map +1 -1
- package/dist/webcomponent/WebComponentEmbed.js +8 -17
- package/dist/webcomponent/WebComponentEmbed.js.map +1 -1
- package/dist/webcomponent/index.d.ts +1 -1
- package/dist/webcomponent/index.js +14 -14
- package/dist/webcomponent/index.js.map +1 -1
- package/package.json +3 -3
package/dist/CircuitEmbed.d.ts
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* For web component users: <circuit-embed code="..."> goes through the
|
|
10
10
|
* web component bridge which sandboxes compilation via an iframe.
|
|
11
11
|
*/
|
|
12
|
-
import { type
|
|
13
|
-
import
|
|
14
|
-
import { type
|
|
15
|
-
import type
|
|
12
|
+
import { type BuiltCircuit } from '@simten/core/circuit';
|
|
13
|
+
import type { FlatPortValueMap } from '@simten/core/simulator';
|
|
14
|
+
import { type ForwardedRef, type ReactElement } from 'react';
|
|
15
|
+
import { type CircuitViewerHandle, type HarnessedLayout } from './CircuitViewer';
|
|
16
16
|
export interface CircuitEmbedProps<C extends BuiltCircuit = BuiltCircuit> {
|
|
17
17
|
/** The circuit to display (result of circuit()) */
|
|
18
18
|
circuit: C;
|
|
@@ -37,7 +37,7 @@ export interface CircuitEmbedProps<C extends BuiltCircuit = BuiltCircuit> {
|
|
|
37
37
|
*/
|
|
38
38
|
layout?: HarnessedLayout<C>;
|
|
39
39
|
/** Theme */
|
|
40
|
-
theme?:
|
|
40
|
+
theme?: 'light' | 'dark';
|
|
41
41
|
/** Title shown in bottom bar */
|
|
42
42
|
title?: string;
|
|
43
43
|
/** Subtitle shown next to title */
|
|
@@ -62,7 +62,7 @@ export interface CircuitEmbedProps<C extends BuiltCircuit = BuiltCircuit> {
|
|
|
62
62
|
/** Show port labels on nodes */
|
|
63
63
|
showPortLabels?: boolean;
|
|
64
64
|
/** Callback when a port is clicked */
|
|
65
|
-
onPortClick?: (nodeLabel: string, portName: string, portType:
|
|
65
|
+
onPortClick?: (nodeLabel: string, portName: string, portType: 'input' | 'output') => void;
|
|
66
66
|
/** Highlight unconnected ports */
|
|
67
67
|
glowUnconnected?: boolean;
|
|
68
68
|
/** Auto-run speed (ms between ticks) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircuitEmbed.d.ts","sourceRoot":"","sources":["../src/CircuitEmbed.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CircuitEmbed.d.ts","sourceRoot":"","sources":["../src/CircuitEmbed.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ/D,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,YAAY,EAElB,MAAM,OAAO,CAAC;AACf,OAAO,EAAiB,KAAK,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAahG,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IACtE,mDAAmD;IACnD,OAAO,EAAE,CAAC,CAAC;IACX;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC5B,YAAY;IACZ,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,gCAAgC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sCAAsC;IACtC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,QAAQ,KAAK,IAAI,CAAC;IAC1F,kCAAkC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACjD;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAsMrD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAuB,CAAC,CAAC,SAAS,YAAY,EACrE,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAA;CAAE,KACrE,YAAY,CAAC"}
|
package/dist/CircuitEmbed.js
CHANGED
|
@@ -10,23 +10,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* For web component users: <circuit-embed code="..."> goes through the
|
|
11
11
|
* web component bridge which sandboxes compilation via an iframe.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { useShareCircuit } from
|
|
13
|
+
import { circuitToSource } from '@simten/core/circuit';
|
|
14
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@simten/ui/primitives/tooltip';
|
|
15
|
+
import { encodeSourceForUrl } from '@simten/ui/share';
|
|
16
|
+
import { forwardRef, useState, } from 'react';
|
|
17
|
+
import { CircuitViewer } from './CircuitViewer';
|
|
18
|
+
import { useShareCircuit } from './share-context';
|
|
19
19
|
/**
|
|
20
20
|
* Where Fork links open. simten.dev for everything except local dev of the
|
|
21
21
|
* embed itself (where the embed is mounted at localhost). Detected at runtime
|
|
22
22
|
* because the embed runs on third-party origins we don't control at build time.
|
|
23
23
|
*/
|
|
24
24
|
function simtenHost() {
|
|
25
|
-
if (typeof window ===
|
|
26
|
-
return
|
|
27
|
-
return window.location.hostname ===
|
|
28
|
-
? window.location.origin
|
|
29
|
-
: "https://simten.dev";
|
|
25
|
+
if (typeof window === 'undefined')
|
|
26
|
+
return 'https://simten.dev';
|
|
27
|
+
return window.location.hostname === 'localhost' ? window.location.origin : 'https://simten.dev';
|
|
30
28
|
}
|
|
31
29
|
// Approximate node footprint used when inferring aspect ratio from a layout.
|
|
32
30
|
// Layout coords are top-left of each node, so we add ~one node's width/height
|
|
@@ -39,8 +37,8 @@ function inferAspectFromLayout(layout) {
|
|
|
39
37
|
const positions = Object.values(layout);
|
|
40
38
|
if (positions.length === 0)
|
|
41
39
|
return 1.5;
|
|
42
|
-
const w = Math.max(...positions.map(p => p.x)) + NODE_W;
|
|
43
|
-
const h = Math.max(...positions.map(p => p.y)) + NODE_H;
|
|
40
|
+
const w = Math.max(...positions.map((p) => p.x)) + NODE_W;
|
|
41
|
+
const h = Math.max(...positions.map((p) => p.y)) + NODE_H;
|
|
44
42
|
if (w <= 0 || h <= 0)
|
|
45
43
|
return 1.5;
|
|
46
44
|
return w / h;
|
|
@@ -51,8 +49,8 @@ function inferAspectFromLayout(layout) {
|
|
|
51
49
|
* error / tooltip logic lives in a single place. Uses the shared shadcn tooltip
|
|
52
50
|
* (same one ClockControls uses) instead of the native `title` attribute.
|
|
53
51
|
*/
|
|
54
|
-
function ForkButton({ onFork, forkError, forkPending, className, tooltipSide =
|
|
55
|
-
return (_jsx(TooltipProvider, { delayDuration: 300, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onFork, className: `cursor-pointer ${className}`, children: forkError ?
|
|
52
|
+
function ForkButton({ onFork, forkError, forkPending, className, tooltipSide = 'top', }) {
|
|
53
|
+
return (_jsx(TooltipProvider, { delayDuration: 300, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onFork, className: `cursor-pointer ${className}`, children: forkError ? 'Fork failed' : forkPending ? 'Forking…' : 'Fork →' }) }), _jsx(TooltipContent, { side: tooltipSide, children: forkError ?? 'Open and modify this circuit in the Simten editor' })] }) }));
|
|
56
54
|
}
|
|
57
55
|
const CircuitEmbedImpl = forwardRef(function CircuitEmbed({ circuit, height, aspectRatio, showControls = true, layout, theme, title, subtitle, description, href, focus, showPortLabels, onPortClick, glowUnconnected, autoRunSpeed = 500, initialInputs, forkSource, onPortValuesChange, }, ref) {
|
|
58
56
|
const hasInfoBar = title || description;
|
|
@@ -69,11 +67,11 @@ const CircuitEmbedImpl = forwardRef(function CircuitEmbed({ circuit, height, asp
|
|
|
69
67
|
if (shareCircuit) {
|
|
70
68
|
setForkPending(true);
|
|
71
69
|
const { hash } = await shareCircuit(source);
|
|
72
|
-
window.open(`${simtenHost()}/circuit/s/${hash}`,
|
|
70
|
+
window.open(`${simtenHost()}/circuit/s/${hash}`, '_blank', 'noopener');
|
|
73
71
|
}
|
|
74
72
|
else {
|
|
75
73
|
const encoded = encodeSourceForUrl(source);
|
|
76
|
-
window.open(`${simtenHost()}/circuit/${encoded}`,
|
|
74
|
+
window.open(`${simtenHost()}/circuit/${encoded}`, '_blank', 'noopener');
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
catch (err) {
|
|
@@ -87,7 +85,8 @@ const CircuitEmbedImpl = forwardRef(function CircuitEmbed({ circuit, height, asp
|
|
|
87
85
|
// Sizing strategy: if `height` is set, use it (backwards compat).
|
|
88
86
|
// Otherwise size width-responsively via aspect-ratio with mobile clamps.
|
|
89
87
|
const useResponsive = height === undefined;
|
|
90
|
-
const aspect = aspectRatio ??
|
|
88
|
+
const aspect = aspectRatio ??
|
|
89
|
+
inferAspectFromLayout(layout);
|
|
91
90
|
const responsiveStyle = {
|
|
92
91
|
width: '100%',
|
|
93
92
|
aspectRatio: String(aspect),
|
|
@@ -104,7 +103,7 @@ const CircuitEmbedImpl = forwardRef(function CircuitEmbed({ circuit, height, asp
|
|
|
104
103
|
? responsiveStyle
|
|
105
104
|
: { height }
|
|
106
105
|
: { height: '100%' };
|
|
107
|
-
return (_jsxs("div", { style: outerStyle, className: `relative flex flex-col ${hasInfoBar ? 'rounded-xl border border-[var(--embed-border)] overflow-hidden bg-[var(--embed-bg-secondary)]' : ''}`, children: [!hasInfoBar && !href && (_jsx(ForkButton, { onFork: onFork, forkError: forkError, forkPending: forkPending, tooltipSide: "bottom", className: "absolute top-2 right-2 z-10 hidden md:flex items-center px-2.5 py-1 rounded border border-[var(--embed-border)] bg-[var(--embed-bg-secondary)] text-[11px] text-[var(--embed-text-primary)] hover:opacity-80 transition-colors shadow-sm" })), _jsx("div", { style: canvasStyle, className: "min-h-0", children: _jsx(CircuitViewer, { ref: ref, circuit: circuit, height: "100%", showControls: showControls, autoHarness: true, initialInputs: initialInputs, layout: layout, theme: theme, focus: focus, showPortLabels: showPortLabels, onPortClick: onPortClick, glowUnconnected: glowUnconnected, autoRunSpeed: autoRunSpeed, onPortValuesChange: onPortValuesChange }) }), hasInfoBar && (_jsxs("div", { className: "border-t border-[var(--embed-border)] px-4 py-3 flex items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-base font-semibold text-[var(--embed-text-primary)]", children: title }), subtitle && _jsx("div", { className: "text-xs text-[var(--embed-text-muted)] font-mono mt-0.5", children: subtitle }), description && _jsx("div", { className: "text-sm text-[var(--embed-text-secondary)] mt-1.5 leading-relaxed", children: description })] }), href ? (_jsx("a", { href: href, className: "shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors", children: "Open \u2192" })) : (_jsx(ForkButton, { onFork: onFork, forkError: forkError, forkPending: forkPending, tooltipSide: "top", className: "hidden md:flex items-center shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors" }))] }))] }));
|
|
106
|
+
return (_jsxs("div", { style: outerStyle, className: `relative flex flex-col ${hasInfoBar ? 'rounded-xl border border-[var(--embed-border)] overflow-hidden bg-[var(--embed-bg-secondary)]' : ''}`, children: [!hasInfoBar && !href && (_jsx(ForkButton, { onFork: onFork, forkError: forkError, forkPending: forkPending, tooltipSide: "bottom", className: "absolute top-2 right-2 z-10 hidden md:flex items-center px-2.5 py-1 rounded border border-[var(--embed-border)] bg-[var(--embed-bg-secondary)] text-[11px] text-[var(--embed-text-primary)] hover:opacity-80 transition-colors shadow-sm" })), _jsx("div", { style: canvasStyle, className: "min-h-0", children: _jsx(CircuitViewer, { ref: ref, circuit: circuit, height: "100%", showControls: showControls, autoHarness: true, initialInputs: initialInputs, layout: layout, theme: theme, focus: focus, showPortLabels: showPortLabels, onPortClick: onPortClick, glowUnconnected: glowUnconnected, autoRunSpeed: autoRunSpeed, onPortValuesChange: onPortValuesChange }) }), hasInfoBar && (_jsxs("div", { className: "border-t border-[var(--embed-border)] px-4 py-3 flex items-end justify-between gap-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-base font-semibold text-[var(--embed-text-primary)]", children: title }), subtitle && (_jsx("div", { className: "text-xs text-[var(--embed-text-muted)] font-mono mt-0.5", children: subtitle })), description && (_jsx("div", { className: "text-sm text-[var(--embed-text-secondary)] mt-1.5 leading-relaxed", children: description }))] }), href ? (_jsx("a", { href: href, className: "shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors", children: "Open \u2192" })) : (_jsx(ForkButton, { onFork: onFork, forkError: forkError, forkPending: forkPending, tooltipSide: "top", className: "hidden md:flex items-center shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors" }))] }))] }));
|
|
108
107
|
});
|
|
109
108
|
/**
|
|
110
109
|
* CircuitEmbed component with generic inference over the circuit type.
|
package/dist/CircuitEmbed.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircuitEmbed.js","sourceRoot":"","sources":["../src/CircuitEmbed.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * CircuitEmbed — embeddable circuit viewer with optional info bar.\n *\n * Thin wrapper around CircuitViewer that adds:\n * - Auto-harness (wraps bare circuits with Switch/Led nodes)\n * - Info bar (title, subtitle, description, link)\n *\n * For React users: pass a BuiltCircuit object.\n * For web component users: <circuit-embed code=\"...\"> goes through the\n * web component bridge which sandboxes compilation via an iframe.\n */\n\nimport { forwardRef, useState, type CSSProperties, type ForwardedRef, type ReactElement } from \"react\";\nimport { CircuitViewer, type CircuitViewerHandle, type HarnessedLayout } from \"./CircuitViewer\";\nimport { circuitToSource, type BuiltCircuit } from \"@simten/core/circuit\";\nimport type { FlatPortValueMap } from \"@simten/core/simulator\";\nimport { encodeSourceForUrl } from \"@simten/ui/share\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@simten/ui/primitives/tooltip\";\nimport { useShareCircuit } from \"./share-context\";\n\n/**\n * Where Fork links open. simten.dev for everything except local dev of the\n * embed itself (where the embed is mounted at localhost). Detected at runtime\n * because the embed runs on third-party origins we don't control at build time.\n */\nfunction simtenHost(): string {\n if (typeof window === \"undefined\") return \"https://simten.dev\";\n return window.location.hostname === \"localhost\"\n ? window.location.origin\n : \"https://simten.dev\";\n}\n\nexport interface CircuitEmbedProps<C extends BuiltCircuit = BuiltCircuit> {\n /** The circuit to display (result of circuit()) */\n circuit: C;\n /**\n * Container height. Optional — when omitted, the embed sizes itself\n * width-responsively using `aspectRatio` (or one inferred from `layout`)\n * with sensible mobile-friendly min/max clamps.\n */\n height?: number | string;\n /**\n * Width-to-height ratio of the embed. Used only when `height` is not set.\n * If omitted and `layout` is passed, the ratio is computed from the\n * layout's bounding box. Otherwise defaults to 1.5 (3:2).\n */\n aspectRatio?: number;\n /** Show clock controls for sequential circuits */\n showControls?: boolean;\n /**\n * Pre-computed node positions. Keys are constrained to the circuit's\n * input names, output names, and node labels at compile time.\n * Pass to bypass the runtime layout engine.\n */\n layout?: HarnessedLayout<C>;\n /** Theme */\n theme?: \"light\" | \"dark\";\n /** Title shown in bottom bar */\n title?: string;\n /** Subtitle shown next to title */\n subtitle?: string;\n /** Description shown below title */\n description?: string;\n /**\n * Custom URL for the card's right-side link. When omitted (the common case),\n * a Fork button is rendered that opens the circuit in the simten.dev editor\n * via `/circuit/<lz-encoded-source>`. Pass `href` only to override.\n */\n href?: string;\n /**\n * Optional raw TypeScript source for the Fork button. When provided, the\n * Fork button encodes this verbatim instead of running the BuiltCircuit\n * through the IR-to-source serializer (which drops comments and helpers).\n * The web-component path passes the user's original `code` here.\n */\n forkSource?: string;\n /** Focus on specific node(s) */\n focus?: string | string[];\n /** Show port labels on nodes */\n showPortLabels?: boolean;\n /** Callback when a port is clicked */\n onPortClick?: (nodeLabel: string, portName: string, portType: \"input\" | \"output\") => void;\n /** Highlight unconnected ports */\n glowUnconnected?: boolean;\n /** Auto-run speed (ms between ticks) */\n autoRunSpeed?: number;\n /** Initial values for input ports (set on harness Switch/Input nodes) */\n initialInputs?: Record<string, number | boolean>;\n /**\n * Called when the embed's internal simulator settles on a new set of\n * port values — once on first settle, then on every subsequent settled\n * change (e.g. when the user toggles a switch on the canvas). Forwarded\n * verbatim to CircuitViewer; see its docs for full firing semantics.\n *\n * Use this to drive sibling UI (truth-table highlights, external value\n * readouts) without giving up the embed's chrome. The callback is\n * ref-stabilized inside the embed, so inline functions are safe.\n */\n onPortValuesChange?: (portValues: FlatPortValueMap) => void;\n}\n\nexport type CircuitEmbedHandle = CircuitViewerHandle;\n\n// Approximate node footprint used when inferring aspect ratio from a layout.\n// Layout coords are top-left of each node, so we add ~one node's width/height\n// to the bounding box so the rightmost / bottommost nodes aren't clipped.\nconst NODE_W = 160;\nconst NODE_H = 90;\n\nfunction inferAspectFromLayout(layout: Record<string, { x: number; y: number }> | undefined): number {\n if (!layout) return 1.5; // default 3:2 — sane for auto-laid-out circuits\n const positions = Object.values(layout);\n if (positions.length === 0) return 1.5;\n const w = Math.max(...positions.map(p => p.x)) + NODE_W;\n const h = Math.max(...positions.map(p => p.y)) + NODE_H;\n if (w <= 0 || h <= 0) return 1.5;\n return w / h;\n}\n\n/**\n * The Fork action, rendered in two placements (floating corner when there's no\n * info bar, inline in the info bar otherwise). One component so the onFork /\n * error / tooltip logic lives in a single place. Uses the shared shadcn tooltip\n * (same one ClockControls uses) instead of the native `title` attribute.\n */\nfunction ForkButton({\n onFork,\n forkError,\n forkPending,\n className,\n tooltipSide = \"top\",\n}: {\n onFork: () => void;\n forkError: string | null;\n forkPending: boolean;\n className: string;\n tooltipSide?: \"top\" | \"bottom\";\n}) {\n return (\n <TooltipProvider delayDuration={300}>\n <Tooltip>\n <TooltipTrigger asChild>\n <button type=\"button\" onClick={onFork} className={`cursor-pointer ${className}`}>\n {forkError ? \"Fork failed\" : forkPending ? \"Forking…\" : \"Fork →\"}\n </button>\n </TooltipTrigger>\n <TooltipContent side={tooltipSide}>\n {forkError ?? \"Open and modify this circuit in the Simten editor\"}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\nconst CircuitEmbedImpl = forwardRef<CircuitEmbedHandle, CircuitEmbedProps>(\n function CircuitEmbed({\n circuit,\n height,\n aspectRatio,\n showControls = true,\n layout,\n theme,\n title,\n subtitle,\n description,\n href,\n focus,\n showPortLabels,\n onPortClick,\n glowUnconnected,\n autoRunSpeed = 500,\n initialInputs,\n forkSource,\n onPortValuesChange,\n }, ref) {\n const hasInfoBar = title || description;\n const [forkError, setForkError] = useState<string | null>(null);\n const [forkPending, setForkPending] = useState(false);\n const shareCircuit = useShareCircuit();\n\n const onFork = async () => {\n if (forkPending) return;\n try {\n const source = forkSource ?? circuitToSource(circuit);\n // Use the KV shortener when available (simten.dev). Outside simten.dev\n // — embeds on third-party pages — fall back to the inline-encoded URL.\n if (shareCircuit) {\n setForkPending(true);\n const { hash } = await shareCircuit(source);\n window.open(`${simtenHost()}/circuit/s/${hash}`, \"_blank\", \"noopener\");\n } else {\n const encoded = encodeSourceForUrl(source);\n window.open(`${simtenHost()}/circuit/${encoded}`, \"_blank\", \"noopener\");\n }\n } catch (err) {\n setForkError(err instanceof Error ? err.message : \"Couldn't fork this circuit\");\n setTimeout(() => setForkError(null), 3000);\n } finally {\n setForkPending(false);\n }\n };\n\n // Sizing strategy: if `height` is set, use it (backwards compat).\n // Otherwise size width-responsively via aspect-ratio with mobile clamps.\n const useResponsive = height === undefined;\n const aspect = aspectRatio ?? inferAspectFromLayout(layout as Record<string, { x: number; y: number }> | undefined);\n const responsiveStyle: CSSProperties = {\n width: '100%',\n aspectRatio: String(aspect),\n minHeight: 240,\n maxHeight: '70vh',\n };\n\n const outerStyle: CSSProperties | undefined = hasInfoBar\n ? undefined\n : useResponsive\n ? responsiveStyle\n : { height };\n const canvasStyle: CSSProperties = hasInfoBar\n ? useResponsive\n ? responsiveStyle\n : { height }\n : { height: '100%' };\n\n return (\n <div style={outerStyle} className={`relative flex flex-col ${hasInfoBar ? 'rounded-xl border border-[var(--embed-border)] overflow-hidden bg-[var(--embed-bg-secondary)]' : ''}`}>\n {!hasInfoBar && !href && (\n <ForkButton\n onFork={onFork}\n forkError={forkError}\n forkPending={forkPending}\n tooltipSide=\"bottom\"\n className=\"absolute top-2 right-2 z-10 hidden md:flex items-center px-2.5 py-1 rounded border border-[var(--embed-border)] bg-[var(--embed-bg-secondary)] text-[11px] text-[var(--embed-text-primary)] hover:opacity-80 transition-colors shadow-sm\"\n />\n )}\n <div style={canvasStyle} className=\"min-h-0\">\n <CircuitViewer\n ref={ref}\n circuit={circuit}\n height=\"100%\"\n showControls={showControls}\n autoHarness\n initialInputs={initialInputs}\n layout={layout}\n theme={theme}\n focus={focus}\n showPortLabels={showPortLabels}\n onPortClick={onPortClick}\n glowUnconnected={glowUnconnected}\n autoRunSpeed={autoRunSpeed}\n onPortValuesChange={onPortValuesChange}\n />\n </div>\n {hasInfoBar && (\n <div className=\"border-t border-[var(--embed-border)] px-4 py-3 flex items-end justify-between gap-4\">\n <div>\n <div className=\"text-base font-semibold text-[var(--embed-text-primary)]\">{title}</div>\n {subtitle && <div className=\"text-xs text-[var(--embed-text-muted)] font-mono mt-0.5\">{subtitle}</div>}\n {description && <div className=\"text-sm text-[var(--embed-text-secondary)] mt-1.5 leading-relaxed\">{description}</div>}\n </div>\n {href ? (\n <a href={href} className=\"shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors\">\n Open →\n </a>\n ) : (\n <ForkButton\n onFork={onFork}\n forkError={forkError}\n forkPending={forkPending}\n tooltipSide=\"top\"\n className=\"hidden md:flex items-center shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors\"\n />\n )}\n </div>\n )}\n </div>\n );\n }\n);\n\n/**\n * CircuitEmbed component with generic inference over the circuit type.\n * Cast preserves the generic so `layout` keys are constrained at compile time.\n */\nexport const CircuitEmbed = CircuitEmbedImpl as <C extends BuiltCircuit>(\n props: CircuitEmbedProps<C> & { ref?: ForwardedRef<CircuitEmbedHandle> },\n) => ReactElement;\n"]}
|
|
1
|
+
{"version":3,"file":"CircuitEmbed.js","sourceRoot":"","sources":["../src/CircuitEmbed.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * CircuitEmbed — embeddable circuit viewer with optional info bar.\n *\n * Thin wrapper around CircuitViewer that adds:\n * - Auto-harness (wraps bare circuits with Switch/Led nodes)\n * - Info bar (title, subtitle, description, link)\n *\n * For React users: pass a BuiltCircuit object.\n * For web component users: <circuit-embed code=\"...\"> goes through the\n * web component bridge which sandboxes compilation via an iframe.\n */\n\nimport { type BuiltCircuit, circuitToSource } from '@simten/core/circuit';\nimport type { FlatPortValueMap } from '@simten/core/simulator';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@simten/ui/primitives/tooltip';\nimport { encodeSourceForUrl } from '@simten/ui/share';\nimport {\n type CSSProperties,\n type ForwardedRef,\n forwardRef,\n type ReactElement,\n useState,\n} from 'react';\nimport { CircuitViewer, type CircuitViewerHandle, type HarnessedLayout } from './CircuitViewer';\nimport { useShareCircuit } from './share-context';\n\n/**\n * Where Fork links open. simten.dev for everything except local dev of the\n * embed itself (where the embed is mounted at localhost). Detected at runtime\n * because the embed runs on third-party origins we don't control at build time.\n */\nfunction simtenHost(): string {\n if (typeof window === 'undefined') return 'https://simten.dev';\n return window.location.hostname === 'localhost' ? window.location.origin : 'https://simten.dev';\n}\n\nexport interface CircuitEmbedProps<C extends BuiltCircuit = BuiltCircuit> {\n /** The circuit to display (result of circuit()) */\n circuit: C;\n /**\n * Container height. Optional — when omitted, the embed sizes itself\n * width-responsively using `aspectRatio` (or one inferred from `layout`)\n * with sensible mobile-friendly min/max clamps.\n */\n height?: number | string;\n /**\n * Width-to-height ratio of the embed. Used only when `height` is not set.\n * If omitted and `layout` is passed, the ratio is computed from the\n * layout's bounding box. Otherwise defaults to 1.5 (3:2).\n */\n aspectRatio?: number;\n /** Show clock controls for sequential circuits */\n showControls?: boolean;\n /**\n * Pre-computed node positions. Keys are constrained to the circuit's\n * input names, output names, and node labels at compile time.\n * Pass to bypass the runtime layout engine.\n */\n layout?: HarnessedLayout<C>;\n /** Theme */\n theme?: 'light' | 'dark';\n /** Title shown in bottom bar */\n title?: string;\n /** Subtitle shown next to title */\n subtitle?: string;\n /** Description shown below title */\n description?: string;\n /**\n * Custom URL for the card's right-side link. When omitted (the common case),\n * a Fork button is rendered that opens the circuit in the simten.dev editor\n * via `/circuit/<lz-encoded-source>`. Pass `href` only to override.\n */\n href?: string;\n /**\n * Optional raw TypeScript source for the Fork button. When provided, the\n * Fork button encodes this verbatim instead of running the BuiltCircuit\n * through the IR-to-source serializer (which drops comments and helpers).\n * The web-component path passes the user's original `code` here.\n */\n forkSource?: string;\n /** Focus on specific node(s) */\n focus?: string | string[];\n /** Show port labels on nodes */\n showPortLabels?: boolean;\n /** Callback when a port is clicked */\n onPortClick?: (nodeLabel: string, portName: string, portType: 'input' | 'output') => void;\n /** Highlight unconnected ports */\n glowUnconnected?: boolean;\n /** Auto-run speed (ms between ticks) */\n autoRunSpeed?: number;\n /** Initial values for input ports (set on harness Switch/Input nodes) */\n initialInputs?: Record<string, number | boolean>;\n /**\n * Called when the embed's internal simulator settles on a new set of\n * port values — once on first settle, then on every subsequent settled\n * change (e.g. when the user toggles a switch on the canvas). Forwarded\n * verbatim to CircuitViewer; see its docs for full firing semantics.\n *\n * Use this to drive sibling UI (truth-table highlights, external value\n * readouts) without giving up the embed's chrome. The callback is\n * ref-stabilized inside the embed, so inline functions are safe.\n */\n onPortValuesChange?: (portValues: FlatPortValueMap) => void;\n}\n\nexport type CircuitEmbedHandle = CircuitViewerHandle;\n\n// Approximate node footprint used when inferring aspect ratio from a layout.\n// Layout coords are top-left of each node, so we add ~one node's width/height\n// to the bounding box so the rightmost / bottommost nodes aren't clipped.\nconst NODE_W = 160;\nconst NODE_H = 90;\n\nfunction inferAspectFromLayout(\n layout: Record<string, { x: number; y: number }> | undefined,\n): number {\n if (!layout) return 1.5; // default 3:2 — sane for auto-laid-out circuits\n const positions = Object.values(layout);\n if (positions.length === 0) return 1.5;\n const w = Math.max(...positions.map((p) => p.x)) + NODE_W;\n const h = Math.max(...positions.map((p) => p.y)) + NODE_H;\n if (w <= 0 || h <= 0) return 1.5;\n return w / h;\n}\n\n/**\n * The Fork action, rendered in two placements (floating corner when there's no\n * info bar, inline in the info bar otherwise). One component so the onFork /\n * error / tooltip logic lives in a single place. Uses the shared shadcn tooltip\n * (same one ClockControls uses) instead of the native `title` attribute.\n */\nfunction ForkButton({\n onFork,\n forkError,\n forkPending,\n className,\n tooltipSide = 'top',\n}: {\n onFork: () => void;\n forkError: string | null;\n forkPending: boolean;\n className: string;\n tooltipSide?: 'top' | 'bottom';\n}) {\n return (\n <TooltipProvider delayDuration={300}>\n <Tooltip>\n <TooltipTrigger asChild>\n <button type=\"button\" onClick={onFork} className={`cursor-pointer ${className}`}>\n {forkError ? 'Fork failed' : forkPending ? 'Forking…' : 'Fork →'}\n </button>\n </TooltipTrigger>\n <TooltipContent side={tooltipSide}>\n {forkError ?? 'Open and modify this circuit in the Simten editor'}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n\nconst CircuitEmbedImpl = forwardRef<CircuitEmbedHandle, CircuitEmbedProps>(function CircuitEmbed(\n {\n circuit,\n height,\n aspectRatio,\n showControls = true,\n layout,\n theme,\n title,\n subtitle,\n description,\n href,\n focus,\n showPortLabels,\n onPortClick,\n glowUnconnected,\n autoRunSpeed = 500,\n initialInputs,\n forkSource,\n onPortValuesChange,\n },\n ref,\n) {\n const hasInfoBar = title || description;\n const [forkError, setForkError] = useState<string | null>(null);\n const [forkPending, setForkPending] = useState(false);\n const shareCircuit = useShareCircuit();\n\n const onFork = async () => {\n if (forkPending) return;\n try {\n const source = forkSource ?? circuitToSource(circuit);\n // Use the KV shortener when available (simten.dev). Outside simten.dev\n // — embeds on third-party pages — fall back to the inline-encoded URL.\n if (shareCircuit) {\n setForkPending(true);\n const { hash } = await shareCircuit(source);\n window.open(`${simtenHost()}/circuit/s/${hash}`, '_blank', 'noopener');\n } else {\n const encoded = encodeSourceForUrl(source);\n window.open(`${simtenHost()}/circuit/${encoded}`, '_blank', 'noopener');\n }\n } catch (err) {\n setForkError(err instanceof Error ? err.message : \"Couldn't fork this circuit\");\n setTimeout(() => setForkError(null), 3000);\n } finally {\n setForkPending(false);\n }\n };\n\n // Sizing strategy: if `height` is set, use it (backwards compat).\n // Otherwise size width-responsively via aspect-ratio with mobile clamps.\n const useResponsive = height === undefined;\n const aspect =\n aspectRatio ??\n inferAspectFromLayout(layout as Record<string, { x: number; y: number }> | undefined);\n const responsiveStyle: CSSProperties = {\n width: '100%',\n aspectRatio: String(aspect),\n minHeight: 240,\n maxHeight: '70vh',\n };\n\n const outerStyle: CSSProperties | undefined = hasInfoBar\n ? undefined\n : useResponsive\n ? responsiveStyle\n : { height };\n const canvasStyle: CSSProperties = hasInfoBar\n ? useResponsive\n ? responsiveStyle\n : { height }\n : { height: '100%' };\n\n return (\n <div\n style={outerStyle}\n className={`relative flex flex-col ${hasInfoBar ? 'rounded-xl border border-[var(--embed-border)] overflow-hidden bg-[var(--embed-bg-secondary)]' : ''}`}\n >\n {!hasInfoBar && !href && (\n <ForkButton\n onFork={onFork}\n forkError={forkError}\n forkPending={forkPending}\n tooltipSide=\"bottom\"\n className=\"absolute top-2 right-2 z-10 hidden md:flex items-center px-2.5 py-1 rounded border border-[var(--embed-border)] bg-[var(--embed-bg-secondary)] text-[11px] text-[var(--embed-text-primary)] hover:opacity-80 transition-colors shadow-sm\"\n />\n )}\n <div style={canvasStyle} className=\"min-h-0\">\n <CircuitViewer\n ref={ref}\n circuit={circuit}\n height=\"100%\"\n showControls={showControls}\n autoHarness\n initialInputs={initialInputs}\n layout={layout}\n theme={theme}\n focus={focus}\n showPortLabels={showPortLabels}\n onPortClick={onPortClick}\n glowUnconnected={glowUnconnected}\n autoRunSpeed={autoRunSpeed}\n onPortValuesChange={onPortValuesChange}\n />\n </div>\n {hasInfoBar && (\n <div className=\"border-t border-[var(--embed-border)] px-4 py-3 flex items-end justify-between gap-4\">\n <div>\n <div className=\"text-base font-semibold text-[var(--embed-text-primary)]\">{title}</div>\n {subtitle && (\n <div className=\"text-xs text-[var(--embed-text-muted)] font-mono mt-0.5\">\n {subtitle}\n </div>\n )}\n {description && (\n <div className=\"text-sm text-[var(--embed-text-secondary)] mt-1.5 leading-relaxed\">\n {description}\n </div>\n )}\n </div>\n {href ? (\n <a\n href={href}\n className=\"shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors\"\n >\n Open →\n </a>\n ) : (\n <ForkButton\n onFork={onFork}\n forkError={forkError}\n forkPending={forkPending}\n tooltipSide=\"top\"\n className=\"hidden md:flex items-center shrink-0 px-3 py-1.5 rounded border border-[var(--embed-border)] text-xs text-[var(--embed-text-primary)] hover:opacity-80 transition-colors\"\n />\n )}\n </div>\n )}\n </div>\n );\n});\n\n/**\n * CircuitEmbed component with generic inference over the circuit type.\n * Cast preserves the generic so `layout` keys are constrained at compile time.\n */\nexport const CircuitEmbed = CircuitEmbedImpl as <C extends BuiltCircuit>(\n props: CircuitEmbedProps<C> & { ref?: ForwardedRef<CircuitEmbedHandle> },\n) => ReactElement;\n"]}
|
package/dist/CircuitViewer.d.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Pure props — no stores. Everything else (CircuitEmbed, EditorWorkspace, <circuit-embed>)
|
|
6
6
|
* is a thin wrapper around this.
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
9
|
-
import type {
|
|
10
|
-
import type { CircuitLayout } from
|
|
11
|
-
import type
|
|
8
|
+
import type { BuiltCircuit } from '@simten/core/circuit';
|
|
9
|
+
import type { FlatPortValueMap } from '@simten/core/simulator';
|
|
10
|
+
import type { CircuitLayout } from '@simten/ui/canvas';
|
|
11
|
+
import { type ForwardedRef, type ReactElement } from 'react';
|
|
12
12
|
/**
|
|
13
13
|
* Type-level model of what `autoHarness` produces at runtime: it wraps the
|
|
14
14
|
* original circuit `C` as a single `dut` node and adds one Switch per input
|
|
@@ -44,13 +44,13 @@ export interface CircuitViewerProps<C extends BuiltCircuit = BuiltCircuit> {
|
|
|
44
44
|
*/
|
|
45
45
|
layout?: CircuitLayout<C> | HarnessedLayout<C>;
|
|
46
46
|
/** Theme */
|
|
47
|
-
theme?:
|
|
47
|
+
theme?: 'light' | 'dark';
|
|
48
48
|
/** Focus on specific node(s) */
|
|
49
49
|
focus?: string | string[];
|
|
50
50
|
/** Show port labels on nodes */
|
|
51
51
|
showPortLabels?: boolean;
|
|
52
52
|
/** Callback when a port is clicked */
|
|
53
|
-
onPortClick?: (nodeLabel: string, portName: string, portType:
|
|
53
|
+
onPortClick?: (nodeLabel: string, portName: string, portType: 'input' | 'output') => void;
|
|
54
54
|
/** Highlight unconnected ports */
|
|
55
55
|
glowUnconnected?: boolean;
|
|
56
56
|
/** Auto-run speed (ms between ticks) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircuitViewer.d.ts","sourceRoot":"","sources":["../src/CircuitViewer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CircuitViewer.d.ts","sourceRoot":"","sources":["../src/CircuitViewer.tsx"],"names":[],"mappings":"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"}
|
package/dist/CircuitViewer.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Takes a BuiltCircuit, wires up simulation, and renders CircuitCanvas + ClockControls.
|
|
6
|
-
* Pure props — no stores. Everything else (CircuitEmbed, EditorWorkspace, <circuit-embed>)
|
|
7
|
-
* is a thin wrapper around this.
|
|
8
|
-
*/
|
|
9
|
-
import { useCallback, useEffect, useRef, forwardRef, useImperativeHandle } from "react";
|
|
10
|
-
import { useCircuitSimulator } from "./hooks/useCircuitSimulator";
|
|
11
|
-
import { CircuitCanvas, ClockControls, useDetectTheme } from "@simten/ui/canvas";
|
|
2
|
+
import { CircuitCanvas, ClockControls, useDetectTheme } from '@simten/ui/canvas';
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, } from 'react';
|
|
4
|
+
import { useCircuitSimulator } from './hooks/useCircuitSimulator';
|
|
12
5
|
const CircuitViewerImpl = forwardRef(function CircuitViewer({ circuit, height = 300, showControls = true, autoHarness = false, initialInputs, layout, theme, focus, showPortLabels, onPortClick, glowUnconnected, renderEmptyState, renderOverlay, onPortValuesChange, }, ref) {
|
|
13
6
|
const sim = useCircuitSimulator(circuit, { autoHarness, initialInputs });
|
|
14
7
|
const detectedTheme = useDetectTheme();
|
|
@@ -52,7 +45,7 @@ const CircuitViewerImpl = forwardRef(function CircuitViewer({ circuit, height =
|
|
|
52
45
|
return (_jsx("div", { style: { height }, className: "flex items-center justify-center text-muted-foreground/60 text-sm", children: "Compiling..." }));
|
|
53
46
|
}
|
|
54
47
|
const controlHeight = sim.isSequential && showControls ? 40 : 0;
|
|
55
|
-
const canvasHeight = typeof height ===
|
|
48
|
+
const canvasHeight = typeof height === 'number' ? height - controlHeight : height;
|
|
56
49
|
return (_jsxs("div", { style: { height }, className: "flex flex-col", "data-embed-theme": resolvedTheme, children: [_jsx("div", { className: "flex-1 min-h-0", children: _jsx(CircuitCanvas, { circuit: sim.circuit, componentLibrary: sim.componentLibrary ?? undefined, portValues: sim.portValues, sequentialState: sim.sequentialState, onToggleNode: sim.toggleNode, onSetNodeValue: sim.setNodeValue, onLoadMemory: (nodeId, memData) => {
|
|
57
50
|
const engine = sim.getSimulator();
|
|
58
51
|
if (engine) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CircuitViewer.js","sourceRoot":"","sources":["../src/CircuitViewer.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * CircuitViewer — the single \"render an interactive circuit\" component.\n *\n * Takes a BuiltCircuit, wires up simulation, and renders CircuitCanvas + ClockControls.\n * Pure props — no stores. Everything else (CircuitEmbed, EditorWorkspace, <circuit-embed>)\n * is a thin wrapper around this.\n */\n\nimport { useCallback, useEffect, useRef, forwardRef, useImperativeHandle, type ForwardedRef, type ReactElement } from \"react\";\nimport { useCircuitSimulator } from \"./hooks/useCircuitSimulator\";\nimport type { BuiltCircuit } from \"@simten/core/circuit\";\nimport { CircuitCanvas, ClockControls, useDetectTheme } from \"@simten/ui/canvas\";\nimport type { CircuitLayout } from \"@simten/ui/canvas\";\nimport type { FlatPortValueMap } from \"@simten/core/simulator\";\n\n/**\n * Type-level model of what `autoHarness` produces at runtime: it wraps the\n * original circuit `C` as a single `dut` node and adds one Switch per input\n * and one Led per output. The placeable nodes therefore become the input\n * names ∪ output names ∪ `'dut'`. CircuitLayout<HarnessedCircuit<C>>\n * resolves to exactly that key set.\n */\nexport type HarnessedCircuit<C extends BuiltCircuit> =\n C extends BuiltCircuit<infer Ins, infer Outs, any>\n ? BuiltCircuit<\n Ins,\n Outs,\n & { [K in keyof Ins]: BuiltCircuit }\n & { [K in keyof Outs]: BuiltCircuit }\n & { dut: C }\n >\n : never;\n\n/** Strictly-keyed layout for an autoHarness-wrapped circuit. */\nexport type HarnessedLayout<C extends BuiltCircuit> = CircuitLayout<HarnessedCircuit<C>>;\n\nexport interface CircuitViewerProps<C extends BuiltCircuit = BuiltCircuit> {\n /** The circuit to display (result of circuit()) */\n circuit: C;\n /** Container height */\n height?: number | string;\n /** Show clock controls for sequential circuits */\n showControls?: boolean;\n /** Auto-wrap with Switch/Led nodes for bare circuits */\n autoHarness?: boolean;\n /** Initial values for auto-harnessed input nodes */\n initialInputs?: Record<string, number | boolean>;\n /**\n * Pre-computed node positions. Keys are constrained at compile time.\n * Accepts either the raw circuit's layout shape (when autoHarness is\n * off) or the harnessed layout shape (when autoHarness is on, with\n * keys = input names ∪ output names ∪ 'dut').\n */\n layout?: CircuitLayout<C> | HarnessedLayout<C>;\n /** Theme */\n theme?: \"light\" | \"dark\";\n /** Focus on specific node(s) */\n focus?: string | string[];\n /** Show port labels on nodes */\n showPortLabels?: boolean;\n /** Callback when a port is clicked */\n onPortClick?: (nodeLabel: string, portName: string, portType: \"input\" | \"output\") => void;\n /** Highlight unconnected ports */\n glowUnconnected?: boolean;\n /** Auto-run speed (ms between ticks) */\n autoRunSpeed?: number;\n /** Render custom content when no circuit is compiled */\n renderEmptyState?: () => React.ReactNode;\n /** Render custom overlay on top of the canvas */\n renderOverlay?: () => React.ReactNode;\n /**\n * Called when the simulator's port values settle. Fires once on first\n * settled state (after the circuit compiles and propagates) and on every\n * subsequent settled change (e.g. when the user toggles a switch). Use\n * this to drive sibling UI that needs to react to live simulator state\n * — a truth-table highlight, an external value readout, etc.\n *\n * Firing semantics:\n * - Does NOT fire before `ready` or while `portValues` is empty.\n * - Fires only on settled (post-propagation) states; the simulator\n * never exposes intermediate propagation states.\n * - The map reference is NOT guaranteed stable across no-op ticks\n * (sequential auto-run loops can produce new Map instances with\n * unchanged contents). Memoize derived state if perf matters.\n *\n * The callback is captured in a ref internally so inline functions\n * (e.g. `onPortValuesChange={(pv) => setX(pv)}`) don't cause spurious\n * re-fires — pass whatever shape is convenient for the caller.\n */\n onPortValuesChange?: (portValues: FlatPortValueMap) => void;\n}\n\nexport interface CircuitViewerHandle {\n tick: () => void;\n reset: () => void;\n setNodeValue: (nodeId: string, value: number | boolean | Map<number, number>) => void;\n /** Start the simulator's internal auto-run loop. Equivalent to clicking the\n * in-canvas play button — the play/pause control will stop it normally. */\n startAutoRun: (ticksPerSecond: number) => void;\n /** Stop the simulator's internal auto-run loop. */\n stopAutoRun: () => void;\n}\n\nconst CircuitViewerImpl = forwardRef<CircuitViewerHandle, CircuitViewerProps>(\n function CircuitViewer({\n circuit,\n height = 300,\n showControls = true,\n autoHarness = false,\n initialInputs,\n layout,\n theme,\n focus,\n showPortLabels,\n onPortClick,\n glowUnconnected,\n renderEmptyState,\n renderOverlay,\n onPortValuesChange,\n }, ref) {\n const sim = useCircuitSimulator(circuit, { autoHarness, initialInputs });\n const detectedTheme = useDetectTheme();\n const resolvedTheme = theme ?? detectedTheme;\n\n // Capture the callback in a ref so inline functions don't cause the\n // firing effect below to re-run on every parent render. The effect\n // depends only on [sim.ready, sim.portValues], not on the callback\n // identity, so passing `onPortValuesChange={(pv) => setX(pv)}` is\n // safe even though it creates a new function reference each render.\n const onPortValuesChangeRef = useRef(onPortValuesChange);\n useEffect(() => {\n onPortValuesChangeRef.current = onPortValuesChange;\n });\n\n // Fire once the sim is ready and port values have first settled; then\n // fire on every subsequent settled change. The empty-map guard avoids\n // a spurious fire during the initial compile-but-not-yet-propagated\n // window. The hook only exposes settled states, so no debouncing is\n // needed for intermediate propagation steps.\n useEffect(() => {\n if (!sim.ready || !sim.portValues || sim.portValues.size === 0) return;\n onPortValuesChangeRef.current?.(sim.portValues);\n }, [sim.ready, sim.portValues]);\n\n const handleTick = useCallback(() => {\n sim.tick();\n }, [sim.tick]);\n\n const handleReset = useCallback(() => {\n sim.reset();\n }, [sim.reset]);\n\n useImperativeHandle(ref, () => ({\n tick: handleTick,\n reset: handleReset,\n setNodeValue: sim.setNodeValue,\n startAutoRun: sim.startAutoRun,\n stopAutoRun: sim.stopAutoRun,\n }), [handleTick, handleReset, sim.setNodeValue, sim.startAutoRun, sim.stopAutoRun]);\n\n if (sim.error) {\n return (\n <div style={{ height }} className=\"flex items-center justify-center p-4\">\n <div className=\"text-sm text-red-400 bg-red-500/10 rounded p-3 border border-red-500/20\">\n <div className=\"font-medium mb-1\">Compilation Error</div>\n <div className=\"font-mono text-xs\">{sim.error}</div>\n </div>\n </div>\n );\n }\n\n if (!sim.ready) {\n return (\n <div style={{ height }} className=\"flex items-center justify-center text-muted-foreground/60 text-sm\">\n Compiling...\n </div>\n );\n }\n\n const controlHeight = sim.isSequential && showControls ? 40 : 0;\n const canvasHeight = typeof height === \"number\" ? height - controlHeight : height;\n\n return (\n <div style={{ height }} className=\"flex flex-col\" data-embed-theme={resolvedTheme}>\n <div className=\"flex-1 min-h-0\">\n <CircuitCanvas\n circuit={sim.circuit}\n componentLibrary={sim.componentLibrary ?? undefined}\n portValues={sim.portValues}\n sequentialState={sim.sequentialState}\n onToggleNode={sim.toggleNode}\n onSetNodeValue={sim.setNodeValue}\n onLoadMemory={(nodeId, memData) => {\n const engine = sim.getSimulator();\n if (engine) {\n engine.setNode(nodeId, memData);\n sim.runCombinational();\n }\n }}\n height={canvasHeight}\n focus={focus}\n showPortLabels={showPortLabels}\n onPortClick={onPortClick}\n glowUnconnected={glowUnconnected}\n renderEmptyState={renderEmptyState}\n renderOverlay={renderOverlay}\n {...(layout ? { layout } : {})}\n {...(theme ? { theme } : {})}\n />\n </div>\n {sim.isSequential && showControls && (\n <ClockControls\n cycle={sim.cycleCount}\n historyLength={sim.history?.length ?? 0}\n historyIndex={sim.historyIndex ?? -1}\n isRunning={sim.isRunning}\n isViewingPast={sim.isViewingPast ?? false}\n onStep={handleTick}\n onRun={() => sim.startAutoRun(15)}\n onPause={() => sim.stopAutoRun()}\n onReset={handleReset}\n onStepBack={() => sim.stepBack()}\n onStepForward={() => {\n if (sim.isViewingPast) {\n sim.stepForward();\n } else {\n handleTick();\n }\n }}\n speed={5}\n />\n )}\n </div>\n );\n }\n);\n\n/**\n * CircuitViewer with generic inference over the circuit type.\n * Cast preserves the generic so `layout` keys are constrained at compile time.\n */\nexport const CircuitViewer = CircuitViewerImpl as <C extends BuiltCircuit>(\n props: CircuitViewerProps<C> & { ref?: ForwardedRef<CircuitViewerHandle> },\n) => ReactElement;\n"]}
|
|
1
|
+
{"version":3,"file":"CircuitViewer.js","sourceRoot":"","sources":["../src/CircuitViewer.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAEL,UAAU,EAEV,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,MAAM,GACP,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAwFlE,MAAM,iBAAiB,GAAG,UAAU,CAClC,SAAS,aAAa,CACpB,EACE,OAAO,EACP,MAAM,GAAG,GAAG,EACZ,YAAY,GAAG,IAAI,EACnB,WAAW,GAAG,KAAK,EACnB,aAAa,EACb,MAAM,EACN,KAAK,EACL,KAAK,EACL,cAAc,EACd,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,kBAAkB,GACnB,EACD,GAAG;IAEH,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;IACzE,MAAM,aAAa,GAAG,cAAc,EAAE,CAAC;IACvC,MAAM,aAAa,GAAG,KAAK,IAAI,aAAa,CAAC;IAE7C,oEAAoE;IACpE,mEAAmE;IACnE,mEAAmE;IACnE,kEAAkE;IAClE,oEAAoE;IACpE,MAAM,qBAAqB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE;QACb,qBAAqB,CAAC,OAAO,GAAG,kBAAkB,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,sEAAsE;IACtE,oEAAoE;IACpE,oEAAoE;IACpE,6CAA6C;IAC7C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QACvE,qBAAqB,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IAEhC,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;QAClC,GAAG,CAAC,IAAI,EAAE,CAAC;IACb,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAEf,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,GAAG,CAAC,KAAK,EAAE,CAAC;IACd,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAEhB,mBAAmB,CACjB,GAAG,EACH,GAAG,EAAE,CAAC,CAAC;QACL,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,WAAW,EAAE,GAAG,CAAC,WAAW;KAC7B,CAAC,EACF,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,WAAW,CAAC,CAC/E,CAAC;IAEF,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,OAAO,CACL,cAAK,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAC,sCAAsC,YACtE,eAAK,SAAS,EAAC,yEAAyE,aACtF,cAAK,SAAS,EAAC,kBAAkB,kCAAwB,EACzD,cAAK,SAAS,EAAC,mBAAmB,YAAE,GAAG,CAAC,KAAK,GAAO,IAChD,GACF,CACP,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CACL,cACE,KAAK,EAAE,EAAE,MAAM,EAAE,EACjB,SAAS,EAAC,mEAAmE,6BAGzE,CACP,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,CAAC,YAAY,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IAElF,OAAO,CACL,eAAK,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAC,eAAe,sBAAmB,aAAa,aAC/E,cAAK,SAAS,EAAC,gBAAgB,YAC7B,KAAC,aAAa,IACZ,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,IAAI,SAAS,EACnD,UAAU,EAAE,GAAG,CAAC,UAAU,EAC1B,eAAe,EAAE,GAAG,CAAC,eAAe,EACpC,YAAY,EAAE,GAAG,CAAC,UAAU,EAC5B,cAAc,EAAE,GAAG,CAAC,YAAY,EAChC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;wBAChC,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;wBAClC,IAAI,MAAM,EAAE,CAAC;4BACX,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;4BAChC,GAAG,CAAC,gBAAgB,EAAE,CAAC;wBACzB,CAAC;oBACH,CAAC,EACD,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,KAAK,EACZ,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,eAAe,EAChC,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,KACxB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC1B,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAC5B,GACE,EACL,GAAG,CAAC,YAAY,IAAI,YAAY,IAAI,CACnC,KAAC,aAAa,IACZ,KAAK,EAAE,GAAG,CAAC,UAAU,EACrB,aAAa,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,EACvC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,EACpC,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,KAAK,EACzC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,EAChC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,EAChC,aAAa,EAAE,GAAG,EAAE;oBAClB,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;wBACtB,GAAG,CAAC,WAAW,EAAE,CAAC;oBACpB,CAAC;yBAAM,CAAC;wBACN,UAAU,EAAE,CAAC;oBACf,CAAC;gBACH,CAAC,EACD,KAAK,EAAE,CAAC,GACR,CACH,IACG,CACP,CAAC;AACJ,CAAC,CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,iBAEZ,CAAC","sourcesContent":["/**\n * CircuitViewer — the single \"render an interactive circuit\" component.\n *\n * Takes a BuiltCircuit, wires up simulation, and renders CircuitCanvas + ClockControls.\n * Pure props — no stores. Everything else (CircuitEmbed, EditorWorkspace, <circuit-embed>)\n * is a thin wrapper around this.\n */\n\nimport type { BuiltCircuit } from '@simten/core/circuit';\nimport type { FlatPortValueMap } from '@simten/core/simulator';\nimport type { CircuitLayout } from '@simten/ui/canvas';\nimport { CircuitCanvas, ClockControls, useDetectTheme } from '@simten/ui/canvas';\nimport {\n type ForwardedRef,\n forwardRef,\n type ReactElement,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport { useCircuitSimulator } from './hooks/useCircuitSimulator';\n\n/**\n * Type-level model of what `autoHarness` produces at runtime: it wraps the\n * original circuit `C` as a single `dut` node and adds one Switch per input\n * and one Led per output. The placeable nodes therefore become the input\n * names ∪ output names ∪ `'dut'`. CircuitLayout<HarnessedCircuit<C>>\n * resolves to exactly that key set.\n */\nexport type HarnessedCircuit<C extends BuiltCircuit> =\n C extends BuiltCircuit<infer Ins, infer Outs, any>\n ? BuiltCircuit<\n Ins,\n Outs,\n { [K in keyof Ins]: BuiltCircuit } & { [K in keyof Outs]: BuiltCircuit } & { dut: C }\n >\n : never;\n\n/** Strictly-keyed layout for an autoHarness-wrapped circuit. */\nexport type HarnessedLayout<C extends BuiltCircuit> = CircuitLayout<HarnessedCircuit<C>>;\n\nexport interface CircuitViewerProps<C extends BuiltCircuit = BuiltCircuit> {\n /** The circuit to display (result of circuit()) */\n circuit: C;\n /** Container height */\n height?: number | string;\n /** Show clock controls for sequential circuits */\n showControls?: boolean;\n /** Auto-wrap with Switch/Led nodes for bare circuits */\n autoHarness?: boolean;\n /** Initial values for auto-harnessed input nodes */\n initialInputs?: Record<string, number | boolean>;\n /**\n * Pre-computed node positions. Keys are constrained at compile time.\n * Accepts either the raw circuit's layout shape (when autoHarness is\n * off) or the harnessed layout shape (when autoHarness is on, with\n * keys = input names ∪ output names ∪ 'dut').\n */\n layout?: CircuitLayout<C> | HarnessedLayout<C>;\n /** Theme */\n theme?: 'light' | 'dark';\n /** Focus on specific node(s) */\n focus?: string | string[];\n /** Show port labels on nodes */\n showPortLabels?: boolean;\n /** Callback when a port is clicked */\n onPortClick?: (nodeLabel: string, portName: string, portType: 'input' | 'output') => void;\n /** Highlight unconnected ports */\n glowUnconnected?: boolean;\n /** Auto-run speed (ms between ticks) */\n autoRunSpeed?: number;\n /** Render custom content when no circuit is compiled */\n renderEmptyState?: () => React.ReactNode;\n /** Render custom overlay on top of the canvas */\n renderOverlay?: () => React.ReactNode;\n /**\n * Called when the simulator's port values settle. Fires once on first\n * settled state (after the circuit compiles and propagates) and on every\n * subsequent settled change (e.g. when the user toggles a switch). Use\n * this to drive sibling UI that needs to react to live simulator state\n * — a truth-table highlight, an external value readout, etc.\n *\n * Firing semantics:\n * - Does NOT fire before `ready` or while `portValues` is empty.\n * - Fires only on settled (post-propagation) states; the simulator\n * never exposes intermediate propagation states.\n * - The map reference is NOT guaranteed stable across no-op ticks\n * (sequential auto-run loops can produce new Map instances with\n * unchanged contents). Memoize derived state if perf matters.\n *\n * The callback is captured in a ref internally so inline functions\n * (e.g. `onPortValuesChange={(pv) => setX(pv)}`) don't cause spurious\n * re-fires — pass whatever shape is convenient for the caller.\n */\n onPortValuesChange?: (portValues: FlatPortValueMap) => void;\n}\n\nexport interface CircuitViewerHandle {\n tick: () => void;\n reset: () => void;\n setNodeValue: (nodeId: string, value: number | boolean | Map<number, number>) => void;\n /** Start the simulator's internal auto-run loop. Equivalent to clicking the\n * in-canvas play button — the play/pause control will stop it normally. */\n startAutoRun: (ticksPerSecond: number) => void;\n /** Stop the simulator's internal auto-run loop. */\n stopAutoRun: () => void;\n}\n\nconst CircuitViewerImpl = forwardRef<CircuitViewerHandle, CircuitViewerProps>(\n function CircuitViewer(\n {\n circuit,\n height = 300,\n showControls = true,\n autoHarness = false,\n initialInputs,\n layout,\n theme,\n focus,\n showPortLabels,\n onPortClick,\n glowUnconnected,\n renderEmptyState,\n renderOverlay,\n onPortValuesChange,\n },\n ref,\n ) {\n const sim = useCircuitSimulator(circuit, { autoHarness, initialInputs });\n const detectedTheme = useDetectTheme();\n const resolvedTheme = theme ?? detectedTheme;\n\n // Capture the callback in a ref so inline functions don't cause the\n // firing effect below to re-run on every parent render. The effect\n // depends only on [sim.ready, sim.portValues], not on the callback\n // identity, so passing `onPortValuesChange={(pv) => setX(pv)}` is\n // safe even though it creates a new function reference each render.\n const onPortValuesChangeRef = useRef(onPortValuesChange);\n useEffect(() => {\n onPortValuesChangeRef.current = onPortValuesChange;\n });\n\n // Fire once the sim is ready and port values have first settled; then\n // fire on every subsequent settled change. The empty-map guard avoids\n // a spurious fire during the initial compile-but-not-yet-propagated\n // window. The hook only exposes settled states, so no debouncing is\n // needed for intermediate propagation steps.\n useEffect(() => {\n if (!sim.ready || !sim.portValues || sim.portValues.size === 0) return;\n onPortValuesChangeRef.current?.(sim.portValues);\n }, [sim.ready, sim.portValues]);\n\n const handleTick = useCallback(() => {\n sim.tick();\n }, [sim.tick]);\n\n const handleReset = useCallback(() => {\n sim.reset();\n }, [sim.reset]);\n\n useImperativeHandle(\n ref,\n () => ({\n tick: handleTick,\n reset: handleReset,\n setNodeValue: sim.setNodeValue,\n startAutoRun: sim.startAutoRun,\n stopAutoRun: sim.stopAutoRun,\n }),\n [handleTick, handleReset, sim.setNodeValue, sim.startAutoRun, sim.stopAutoRun],\n );\n\n if (sim.error) {\n return (\n <div style={{ height }} className=\"flex items-center justify-center p-4\">\n <div className=\"text-sm text-red-400 bg-red-500/10 rounded p-3 border border-red-500/20\">\n <div className=\"font-medium mb-1\">Compilation Error</div>\n <div className=\"font-mono text-xs\">{sim.error}</div>\n </div>\n </div>\n );\n }\n\n if (!sim.ready) {\n return (\n <div\n style={{ height }}\n className=\"flex items-center justify-center text-muted-foreground/60 text-sm\"\n >\n Compiling...\n </div>\n );\n }\n\n const controlHeight = sim.isSequential && showControls ? 40 : 0;\n const canvasHeight = typeof height === 'number' ? height - controlHeight : height;\n\n return (\n <div style={{ height }} className=\"flex flex-col\" data-embed-theme={resolvedTheme}>\n <div className=\"flex-1 min-h-0\">\n <CircuitCanvas\n circuit={sim.circuit}\n componentLibrary={sim.componentLibrary ?? undefined}\n portValues={sim.portValues}\n sequentialState={sim.sequentialState}\n onToggleNode={sim.toggleNode}\n onSetNodeValue={sim.setNodeValue}\n onLoadMemory={(nodeId, memData) => {\n const engine = sim.getSimulator();\n if (engine) {\n engine.setNode(nodeId, memData);\n sim.runCombinational();\n }\n }}\n height={canvasHeight}\n focus={focus}\n showPortLabels={showPortLabels}\n onPortClick={onPortClick}\n glowUnconnected={glowUnconnected}\n renderEmptyState={renderEmptyState}\n renderOverlay={renderOverlay}\n {...(layout ? { layout } : {})}\n {...(theme ? { theme } : {})}\n />\n </div>\n {sim.isSequential && showControls && (\n <ClockControls\n cycle={sim.cycleCount}\n historyLength={sim.history?.length ?? 0}\n historyIndex={sim.historyIndex ?? -1}\n isRunning={sim.isRunning}\n isViewingPast={sim.isViewingPast ?? false}\n onStep={handleTick}\n onRun={() => sim.startAutoRun(15)}\n onPause={() => sim.stopAutoRun()}\n onReset={handleReset}\n onStepBack={() => sim.stepBack()}\n onStepForward={() => {\n if (sim.isViewingPast) {\n sim.stepForward();\n } else {\n handleTick();\n }\n }}\n speed={5}\n />\n )}\n </div>\n );\n },\n);\n\n/**\n * CircuitViewer with generic inference over the circuit type.\n * Cast preserves the generic so `layout` keys are constrained at compile time.\n */\nexport const CircuitViewer = CircuitViewerImpl as <C extends BuiltCircuit>(\n props: CircuitViewerProps<C> & { ref?: ForwardedRef<CircuitViewerHandle> },\n) => ReactElement;\n"]}
|
package/dist/canvas/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Re-export from @simten/ui/canvas.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
5
|
-
export
|
|
4
|
+
export type { CircuitCanvasProps } from '@simten/ui/canvas';
|
|
5
|
+
export { CircuitCanvas } from '@simten/ui/canvas';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/canvas/index.js
CHANGED
package/dist/canvas/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["/**\n * Re-export from @simten/ui/canvas.\n */\n\nexport type { CircuitCanvasProps } from '@simten/ui/canvas';\nexport { CircuitCanvas } from '@simten/ui/canvas';\n"]}
|