@simten/embed 0.1.5 → 0.1.7

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.
@@ -59,7 +59,7 @@ const CircuitViewerImpl = forwardRef(function CircuitViewer({ circuit, height =
59
59
  engine.setNode(nodeId, memData);
60
60
  sim.runCombinational();
61
61
  }
62
- }, height: canvasHeight, focus: focus, showPortLabels: showPortLabels, onPortClick: onPortClick, glowUnconnected: glowUnconnected, renderEmptyState: renderEmptyState, renderOverlay: renderOverlay, ...(layout ? { layout } : {}), ...(theme ? { theme } : {}) }) }), sim.isSequential && showControls && (_jsx(ClockControls, { cycle: sim.cycleCount, historyLength: sim.history?.length ?? 0, historyIndex: sim.historyIndex ?? -1, isRunning: sim.isRunning, isViewingPast: sim.isViewingPast ?? false, onStep: handleTick, onRun: () => sim.startAutoRun(5), onPause: () => sim.stopAutoRun(), onReset: handleReset, onStepBack: () => sim.stepBack(), onStepForward: () => {
62
+ }, height: canvasHeight, focus: focus, showPortLabels: showPortLabels, onPortClick: onPortClick, glowUnconnected: glowUnconnected, renderEmptyState: renderEmptyState, renderOverlay: renderOverlay, ...(layout ? { layout } : {}), ...(theme ? { theme } : {}) }) }), sim.isSequential && showControls && (_jsx(ClockControls, { cycle: sim.cycleCount, historyLength: sim.history?.length ?? 0, historyIndex: sim.historyIndex ?? -1, isRunning: sim.isRunning, isViewingPast: sim.isViewingPast ?? false, onStep: handleTick, onRun: () => sim.startAutoRun(15), onPause: () => sim.stopAutoRun(), onReset: handleReset, onStepBack: () => sim.stepBack(), onStepForward: () => {
63
63
  if (sim.isViewingPast) {
64
64
  sim.stepForward();
65
65
  }
@@ -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(5)}\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":"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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"]}