@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../src/components/ErrorBoundary.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../src/components/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG9D,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK,CAAC,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;gBAC5E,KAAK,EAAE,kBAAkB;IAKrC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAIjE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;IAIpD,WAAW,aAET;IAEF,MAAM;CAiBP"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React from
|
|
3
|
-
import { ErrorDisplay } from
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ErrorDisplay } from './ErrorDisplay';
|
|
4
4
|
/**
|
|
5
5
|
* React error boundary for circuit embed components.
|
|
6
6
|
* Catches render errors and displays a styled fallback instead of crashing.
|
|
@@ -22,11 +22,11 @@ export class ErrorBoundary extends React.Component {
|
|
|
22
22
|
render() {
|
|
23
23
|
if (this.state.error) {
|
|
24
24
|
if (this.props.fallback) {
|
|
25
|
-
return typeof this.props.fallback ===
|
|
25
|
+
return typeof this.props.fallback === 'function'
|
|
26
26
|
? this.props.fallback(this.state.error)
|
|
27
27
|
: this.props.fallback;
|
|
28
28
|
}
|
|
29
|
-
return (_jsx(ErrorDisplay, { error: this.state.error.message, title: this.props.title ??
|
|
29
|
+
return (_jsx(ErrorDisplay, { error: this.state.error.message, title: this.props.title ?? 'Render Error', onRetry: this.handleRetry }));
|
|
30
30
|
}
|
|
31
31
|
return this.props.children;
|
|
32
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../src/components/ErrorBoundary.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ErrorBoundary.js","sourceRoot":"","sources":["../../src/components/ErrorBoundary.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAa9C;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK,CAAC,SAAiD;IACxF,YAAY,KAAyB;QACnC,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,SAAoB;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,WAAW,GAAG,GAAG,EAAE;QACjB,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACxB,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,KAAK,UAAU;oBAC9C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;oBACvC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC1B,CAAC;YACD,OAAO,CACL,KAAC,YAAY,IACX,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAC/B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,cAAc,EACzC,OAAO,EAAE,IAAI,CAAC,WAAW,GACzB,CACH,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF","sourcesContent":["import React, { type ErrorInfo, type ReactNode } from 'react';\nimport { ErrorDisplay } from './ErrorDisplay';\n\ninterface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode | ((error: Error) => ReactNode);\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n title?: string;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * React error boundary for circuit embed components.\n * Catches render errors and displays a styled fallback instead of crashing.\n */\nexport class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n this.props.onError?.(error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState({ error: null });\n };\n\n render() {\n if (this.state.error) {\n if (this.props.fallback) {\n return typeof this.props.fallback === 'function'\n ? this.props.fallback(this.state.error)\n : this.props.fallback;\n }\n return (\n <ErrorDisplay\n error={this.state.error.message}\n title={this.props.title ?? 'Render Error'}\n onRetry={this.handleRetry}\n />\n );\n }\n return this.props.children;\n }\n}\n"]}
|
|
@@ -2,7 +2,7 @@ export interface SimulatorError {
|
|
|
2
2
|
message: string;
|
|
3
3
|
line?: number;
|
|
4
4
|
column?: number;
|
|
5
|
-
stage:
|
|
5
|
+
stage: 'parse' | 'compile' | 'elaborate' | 'simulate';
|
|
6
6
|
}
|
|
7
7
|
interface ErrorDisplayProps {
|
|
8
8
|
error: string | SimulatorError | SimulatorError[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorDisplay.d.ts","sourceRoot":"","sources":["../../src/components/ErrorDisplay.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ErrorDisplay.d.ts","sourceRoot":"","sources":["../../src/components/ErrorDisplay.tsx"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;CACvD;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAgBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAuB,EAAE,OAAO,EAAE,EAAE,iBAAiB,2CAyD1F"}
|
|
@@ -2,23 +2,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
function parseErrors(error) {
|
|
3
3
|
if (Array.isArray(error))
|
|
4
4
|
return error;
|
|
5
|
-
if (typeof error ===
|
|
5
|
+
if (typeof error === 'object')
|
|
6
6
|
return [error];
|
|
7
7
|
// Legacy: semicolon-joined strings from older hook versions
|
|
8
|
-
return error.split(
|
|
8
|
+
return error.split('; ').map((msg) => ({ message: msg, stage: 'compile' }));
|
|
9
9
|
}
|
|
10
10
|
const STAGE_LABELS = {
|
|
11
|
-
parse:
|
|
12
|
-
compile:
|
|
13
|
-
elaborate:
|
|
14
|
-
simulate:
|
|
11
|
+
parse: 'Parse',
|
|
12
|
+
compile: 'Compile',
|
|
13
|
+
elaborate: 'Elaborate',
|
|
14
|
+
simulate: 'Simulate',
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* Styled error display for circuit embed components.
|
|
18
18
|
* Renders structured error messages with optional line/column info.
|
|
19
19
|
*/
|
|
20
|
-
export function ErrorDisplay({ error, title =
|
|
20
|
+
export function ErrorDisplay({ error, title = 'Circuit Error', onRetry }) {
|
|
21
21
|
const errors = parseErrors(error);
|
|
22
|
-
return (_jsx("div", { className: "rounded-xl border border-red-800/40 bg-red-950/20 p-4 overflow-hidden", role: "alert", "aria-live": "assertive", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "shrink-0 mt-0.5", children: _jsx("svg", { className: "w-5 h-5 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h4", { className: "text-sm font-semibold text-red-300", children: title }), _jsx("div", { className: "mt-2 space-y-1.5", children: errors.map((err, i) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-[10px] font-mono text-red-400/60 bg-red-900/30 px-1.5 py-0.5 rounded shrink-0 mt-0.5", children: STAGE_LABELS[err.stage] || err.stage }), _jsxs("span", { className: "text-xs font-mono text-red-300/80 break-all", children: [err.message, err.line !== undefined && (_jsxs("span", { className: "text-red-400/50 ml-1", children: ["(line ", err.line, err.column !== undefined ? `:${err.column}` :
|
|
22
|
+
return (_jsx("div", { className: "rounded-xl border border-red-800/40 bg-red-950/20 p-4 overflow-hidden", role: "alert", "aria-live": "assertive", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "shrink-0 mt-0.5", children: _jsx("svg", { className: "w-5 h-5 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h4", { className: "text-sm font-semibold text-red-300", children: title }), _jsx("div", { className: "mt-2 space-y-1.5", children: errors.map((err, i) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-[10px] font-mono text-red-400/60 bg-red-900/30 px-1.5 py-0.5 rounded shrink-0 mt-0.5", children: STAGE_LABELS[err.stage] || err.stage }), _jsxs("span", { className: "text-xs font-mono text-red-300/80 break-all", children: [err.message, err.line !== undefined && (_jsxs("span", { className: "text-red-400/50 ml-1", children: ["(line ", err.line, err.column !== undefined ? `:${err.column}` : '', ")"] }))] })] }, i))) }), onRetry && (_jsx("button", { onClick: onRetry, className: "mt-3 px-3 py-1 text-xs font-medium rounded-md bg-red-900/30 hover:bg-red-900/50 text-red-300 transition-colors", children: "Retry" }))] })] }) }));
|
|
23
23
|
}
|
|
24
24
|
//# sourceMappingURL=ErrorDisplay.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorDisplay.js","sourceRoot":"","sources":["../../src/components/ErrorDisplay.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"ErrorDisplay.js","sourceRoot":"","sources":["../../src/components/ErrorDisplay.tsx"],"names":[],"mappings":";AAaA,SAAS,WAAW,CAAC,KAAiD;IACpE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,4DAA4D;IAC5D,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,SAAkB,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,YAAY,GAA2B;IAC3C,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,eAAe,EAAE,OAAO,EAAqB;IACzF,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAElC,OAAO,CACL,cACE,SAAS,EAAC,uEAAuE,EACjF,IAAI,EAAC,OAAO,eACF,WAAW,YAErB,eAAK,SAAS,EAAC,wBAAwB,aACrC,cAAK,SAAS,EAAC,iBAAiB,YAC9B,cACE,SAAS,EAAC,sBAAsB,EAChC,IAAI,EAAC,MAAM,EACX,OAAO,EAAC,WAAW,EACnB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAE,CAAC,YAEd,eACE,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,CAAC,EAAC,0EAA0E,GAC5E,GACE,GACF,EACN,eAAK,SAAS,EAAC,gBAAgB,aAC7B,aAAI,SAAS,EAAC,oCAAoC,YAAE,KAAK,GAAM,EAC/D,cAAK,SAAS,EAAC,kBAAkB,YAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CACtB,eAAa,SAAS,EAAC,wBAAwB,aAC7C,eAAM,SAAS,EAAC,2FAA2F,YACxG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK,GAChC,EACP,gBAAM,SAAS,EAAC,6CAA6C,aAC1D,GAAG,CAAC,OAAO,EACX,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,CACzB,gBAAM,SAAS,EAAC,sBAAsB,uBAC7B,GAAG,CAAC,IAAI,EACd,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,SAC5C,CACR,IACI,KAZC,CAAC,CAaL,CACP,CAAC,GACE,EACL,OAAO,IAAI,CACV,iBACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,gHAAgH,sBAGnH,CACV,IACG,IACF,GACF,CACP,CAAC;AACJ,CAAC","sourcesContent":["export interface SimulatorError {\n message: string;\n line?: number;\n column?: number;\n stage: 'parse' | 'compile' | 'elaborate' | 'simulate';\n}\n\ninterface ErrorDisplayProps {\n error: string | SimulatorError | SimulatorError[];\n title?: string;\n onRetry?: () => void;\n}\n\nfunction parseErrors(error: string | SimulatorError | SimulatorError[]): SimulatorError[] {\n if (Array.isArray(error)) return error;\n if (typeof error === 'object') return [error];\n // Legacy: semicolon-joined strings from older hook versions\n return error.split('; ').map((msg) => ({ message: msg, stage: 'compile' as const }));\n}\n\nconst STAGE_LABELS: Record<string, string> = {\n parse: 'Parse',\n compile: 'Compile',\n elaborate: 'Elaborate',\n simulate: 'Simulate',\n};\n\n/**\n * Styled error display for circuit embed components.\n * Renders structured error messages with optional line/column info.\n */\nexport function ErrorDisplay({ error, title = 'Circuit Error', onRetry }: ErrorDisplayProps) {\n const errors = parseErrors(error);\n\n return (\n <div\n className=\"rounded-xl border border-red-800/40 bg-red-950/20 p-4 overflow-hidden\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"shrink-0 mt-0.5\">\n <svg\n className=\"w-5 h-5 text-red-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\"\n />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"text-sm font-semibold text-red-300\">{title}</h4>\n <div className=\"mt-2 space-y-1.5\">\n {errors.map((err, i) => (\n <div key={i} className=\"flex items-start gap-2\">\n <span className=\"text-[10px] font-mono text-red-400/60 bg-red-900/30 px-1.5 py-0.5 rounded shrink-0 mt-0.5\">\n {STAGE_LABELS[err.stage] || err.stage}\n </span>\n <span className=\"text-xs font-mono text-red-300/80 break-all\">\n {err.message}\n {err.line !== undefined && (\n <span className=\"text-red-400/50 ml-1\">\n (line {err.line}\n {err.column !== undefined ? `:${err.column}` : ''})\n </span>\n )}\n </span>\n </div>\n ))}\n </div>\n {onRetry && (\n <button\n onClick={onRetry}\n className=\"mt-3 px-3 py-1 text-xs font-medium rounded-md bg-red-900/30 hover:bg-red-900/50 text-red-300 transition-colors\"\n >\n Retry\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingSkeleton.d.ts","sourceRoot":"","sources":["../../src/components/LoadingSkeleton.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LoadingSkeleton.d.ts","sourceRoot":"","sources":["../../src/components/LoadingSkeleton.tsx"],"names":[],"mappings":"AAEA,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAY,EAAE,EAAE,oBAAoB,2CAqDrE"}
|
|
@@ -5,6 +5,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
* Respects height to prevent layout shift.
|
|
6
6
|
*/
|
|
7
7
|
export function LoadingSkeleton({ height = 300 }) {
|
|
8
|
-
return (_jsxs("div", { className: "rounded-xl border border-[var(--embed-border)] bg-[var(--embed-bg-primary)] overflow-hidden flex items-center justify-center", style: { height }, role: "status", "aria-label": "Loading circuit", children: [_jsxs("div", { className: "flex items-center gap-8", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { className: "w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse" }), _jsx("div", { className: "w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse", style: { animationDelay:
|
|
8
|
+
return (_jsxs("div", { className: "rounded-xl border border-[var(--embed-border)] bg-[var(--embed-bg-primary)] overflow-hidden flex items-center justify-center", style: { height }, role: "status", "aria-label": "Loading circuit", children: [_jsxs("div", { className: "flex items-center gap-8", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { className: "w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse" }), _jsx("div", { className: "w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse", style: { animationDelay: '150ms' } })] }), _jsxs("div", { className: "flex flex-col gap-6 items-center", children: [_jsx("div", { className: "w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse", style: { animationDelay: '300ms' } }), _jsx("div", { className: "w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse", style: { animationDelay: '350ms' } })] }), _jsx("div", { className: "w-24 h-20 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse", style: { animationDelay: '200ms' } }), _jsx("div", { className: "w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse", style: { animationDelay: '400ms' } }), _jsx("div", { className: "w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse", style: { animationDelay: '250ms' } })] }), _jsx("span", { className: "sr-only", children: "Compiling and laying out circuit..." })] }));
|
|
9
9
|
}
|
|
10
10
|
//# sourceMappingURL=LoadingSkeleton.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingSkeleton.js","sourceRoot":"","sources":["../../src/components/LoadingSkeleton.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"LoadingSkeleton.js","sourceRoot":"","sources":["../../src/components/LoadingSkeleton.tsx"],"names":[],"mappings":";AAMA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,GAAG,GAAG,EAAwB;IACpE,OAAO,CACL,eACE,SAAS,EAAC,8HAA8H,EACxI,KAAK,EAAE,EAAE,MAAM,EAAE,EACjB,IAAI,EAAC,QAAQ,gBACF,iBAAiB,aAE5B,eAAK,SAAS,EAAC,yBAAyB,aAEtC,eAAK,SAAS,EAAC,qBAAqB,aAClC,cAAK,SAAS,EAAC,qEAAqE,GAAG,EACvF,cACE,SAAS,EAAC,qEAAqE,EAC/E,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,IACE,EAGN,eAAK,SAAS,EAAC,kCAAkC,aAC/C,cACE,SAAS,EAAC,sDAAsD,EAChE,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,EACF,cACE,SAAS,EAAC,sDAAsD,EAChE,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,IACE,EAGN,cACE,SAAS,EAAC,qEAAqE,EAC/E,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,EAGF,cACE,SAAS,EAAC,sDAAsD,EAChE,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,EAGF,cACE,SAAS,EAAC,qEAAqE,EAC/E,KAAK,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,GAClC,IACE,EAGN,eAAM,SAAS,EAAC,SAAS,oDAA2C,IAChE,CACP,CAAC;AACJ,CAAC","sourcesContent":["import React from 'react';\n\ninterface LoadingSkeletonProps {\n height?: number | string;\n}\n\n/**\n * Loading skeleton for circuit embeds.\n * Shows fake node placeholders with connecting lines, matching the dark theme.\n * Respects height to prevent layout shift.\n */\nexport function LoadingSkeleton({ height = 300 }: LoadingSkeletonProps) {\n return (\n <div\n className=\"rounded-xl border border-[var(--embed-border)] bg-[var(--embed-bg-primary)] overflow-hidden flex items-center justify-center\"\n style={{ height }}\n role=\"status\"\n aria-label=\"Loading circuit\"\n >\n <div className=\"flex items-center gap-8\">\n {/* Input nodes */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse\" />\n <div\n className=\"w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse\"\n style={{ animationDelay: '150ms' }}\n />\n </div>\n\n {/* Connecting lines */}\n <div className=\"flex flex-col gap-6 items-center\">\n <div\n className=\"w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse\"\n style={{ animationDelay: '300ms' }}\n />\n <div\n className=\"w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse\"\n style={{ animationDelay: '350ms' }}\n />\n </div>\n\n {/* Logic node */}\n <div\n className=\"w-24 h-20 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse\"\n style={{ animationDelay: '200ms' }}\n />\n\n {/* Connecting line */}\n <div\n className=\"w-12 h-0.5 bg-[var(--embed-border)]/40 animate-pulse\"\n style={{ animationDelay: '400ms' }}\n />\n\n {/* Output node */}\n <div\n className=\"w-20 h-14 rounded-lg bg-[var(--embed-bg-tertiary)]/60 animate-pulse\"\n style={{ animationDelay: '250ms' }}\n />\n </div>\n\n {/* Screen reader text */}\n <span className=\"sr-only\">Compiling and laying out circuit...</span>\n </div>\n );\n}\n"]}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* This allows external consumers to import from @simten/embed/nodes
|
|
4
4
|
* while the source of truth lives in the ui package.
|
|
5
5
|
*/
|
|
6
|
-
export { BaseNode, type
|
|
6
|
+
export { BaseNode, type BaseNodeProps, CompositeBadge, EmbedConsoleNode, EmbedScreenNode, InputNode, LogicGateNode, type NodeData, OutputNode, type PortConfig, } from '@simten/ui/nodes';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nodes/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,QAAQ,EACR,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/nodes/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,EAClB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,KAAK,QAAQ,EACb,UAAU,EACV,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* This allows external consumers to import from @simten/embed/nodes
|
|
4
4
|
* while the source of truth lives in the ui package.
|
|
5
5
|
*/
|
|
6
|
-
export { BaseNode,
|
|
6
|
+
export { BaseNode, CompositeBadge, EmbedConsoleNode, EmbedScreenNode, InputNode, LogicGateNode, OutputNode, } from '@simten/ui/nodes';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/nodes/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/nodes/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,QAAQ,EAER,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EAEb,UAAU,GAEX,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Re-export canonical node components from @simten/ui/nodes.\n * This allows external consumers to import from @simten/embed/nodes\n * while the source of truth lives in the ui package.\n */\nexport {\n BaseNode,\n type BaseNodeProps,\n CompositeBadge,\n EmbedConsoleNode,\n EmbedScreenNode,\n InputNode,\n LogicGateNode,\n type NodeData,\n OutputNode,\n type PortConfig,\n} from '@simten/ui/nodes';\n"]}
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
*
|
|
17
17
|
* See: apps/web/content/docs/architecture.mdx → "Runtime topology".
|
|
18
18
|
*/
|
|
19
|
-
import
|
|
20
|
-
import type {
|
|
21
|
-
import type {
|
|
19
|
+
import type { Circuit } from '@simten/core';
|
|
20
|
+
import type { BuiltCircuit } from '@simten/core/circuit';
|
|
21
|
+
import type { CircuitLibrary, FlatPortValueMap, FlatSequentialState, SimulatorEngine } from '@simten/core/simulator';
|
|
22
22
|
export interface SimulatorState {
|
|
23
23
|
outputs: Record<string, boolean | number>;
|
|
24
24
|
inputs: Record<string, boolean | number>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCircuitSimulator.d.ts","sourceRoot":"","sources":["../../src/hooks/useCircuitSimulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"useCircuitSimulator.d.ts","sourceRoot":"","sources":["../../src/hooks/useCircuitSimulator.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
*
|
|
17
17
|
* See: apps/web/content/docs/architecture.mdx → "Runtime topology".
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
const TOP_LEVEL_NODE =
|
|
19
|
+
import { autoHarness, getCircuitEval, isSequentialCircuit } from '@simten/core/circuit';
|
|
20
|
+
import { Button, HexDisplay, Input, Led, Output, Switch } from '@simten/core/std';
|
|
21
|
+
import { useSandboxContext } from '@simten/ui/sandbox';
|
|
22
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
23
|
+
const TOP_LEVEL_NODE = '__top__';
|
|
24
24
|
/**
|
|
25
25
|
* Extract eval function sources from a BuiltCircuit and its dependencies.
|
|
26
26
|
* These are serialized and sent to the sandbox, which reconstructs them with new Function().
|
|
@@ -72,6 +72,24 @@ export function builtFromIR(circuit, dependencies) {
|
|
|
72
72
|
_dependencies: depMap,
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolves once the sandbox iframe reports ready (or the caller cancels).
|
|
77
|
+
* Polls because the sandbox handle exposes readiness as isReady() with no event.
|
|
78
|
+
*/
|
|
79
|
+
function waitForSandboxReady(sandbox, cancelled) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
if (sandbox.isReady() || cancelled()) {
|
|
82
|
+
resolve();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const id = setInterval(() => {
|
|
86
|
+
if (sandbox.isReady() || cancelled()) {
|
|
87
|
+
clearInterval(id);
|
|
88
|
+
resolve();
|
|
89
|
+
}
|
|
90
|
+
}, 50);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
75
93
|
/**
|
|
76
94
|
* Circuit simulator hook — runs simulation in the sandbox iframe.
|
|
77
95
|
*
|
|
@@ -87,8 +105,12 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
87
105
|
const circuitMap = new Map();
|
|
88
106
|
const lib = {
|
|
89
107
|
resolveCircuit: (name) => circuitMap.get(name),
|
|
90
|
-
getAllPrimitiveNames: () => [...circuitMap.entries()]
|
|
91
|
-
|
|
108
|
+
getAllPrimitiveNames: () => [...circuitMap.entries()]
|
|
109
|
+
.filter(([, c]) => c.implementation.kind === 'primitive')
|
|
110
|
+
.map(([n]) => n),
|
|
111
|
+
addCircuit: (c) => {
|
|
112
|
+
circuitMap.set(c.name, c);
|
|
113
|
+
},
|
|
92
114
|
};
|
|
93
115
|
for (const c of [Switch(), Button(), Led, Input(), Output, HexDisplay]) {
|
|
94
116
|
lib.addCircuit(c.circuit);
|
|
@@ -145,7 +167,9 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
145
167
|
// Mirror of inputs in a ref so tick/tickN can snapshot the latest value
|
|
146
168
|
// synchronously without waiting for setState → re-render.
|
|
147
169
|
const inputsRef = useRef(inputs);
|
|
148
|
-
useEffect(() => {
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
inputsRef.current = inputs;
|
|
172
|
+
}, [inputs]);
|
|
149
173
|
// ── Compile to sandbox on mount / circuit change ──
|
|
150
174
|
useEffect(() => {
|
|
151
175
|
if (!circuit || !harnessedCircuit) {
|
|
@@ -157,6 +181,14 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
157
181
|
async function initSandbox() {
|
|
158
182
|
if (!circuit || !harnessedCircuit)
|
|
159
183
|
return;
|
|
184
|
+
// compileIR does not queue — calling it before the sandbox iframe has
|
|
185
|
+
// finished loading returns "Sandbox not ready" with no recovery. Wait for
|
|
186
|
+
// readiness first so consumers mounting a circuit immediately (fresh apps
|
|
187
|
+
// where the SandboxProvider mounts alongside them) don't have to gate on
|
|
188
|
+
// isReady() themselves.
|
|
189
|
+
await waitForSandboxReady(sandbox, () => cancelled);
|
|
190
|
+
if (cancelled)
|
|
191
|
+
return;
|
|
160
192
|
// Extract eval sources from the BuiltCircuit and its dependencies
|
|
161
193
|
const evalSources = extractEvalSources(circuit);
|
|
162
194
|
// Collect all library circuits (stdlib + user).
|
|
@@ -212,9 +244,10 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
212
244
|
// Seed history with the initial snapshot (if any). Fresh compile means
|
|
213
245
|
// the previous history (if any) is invalid anyway — sandbox already
|
|
214
246
|
// cleared its own snapshots on the recompile.
|
|
215
|
-
historyRef.current =
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
historyRef.current =
|
|
248
|
+
result.snapshotId !== undefined
|
|
249
|
+
? [{ snapshotId: result.snapshotId, cycle: 0, inputs: { ...defaultInputs } }]
|
|
250
|
+
: [];
|
|
218
251
|
setHistoryIndex(result.snapshotId !== undefined ? 0 : -1);
|
|
219
252
|
setHistoryLen(historyRef.current.length);
|
|
220
253
|
setReady(true);
|
|
@@ -282,7 +315,9 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
282
315
|
// Mirror historyIndex in a ref so the helper above can see its live value
|
|
283
316
|
// without recompiling on every change (avoids rebuilding every callback).
|
|
284
317
|
const historyIndexRef = useRef(-1);
|
|
285
|
-
useEffect(() => {
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
historyIndexRef.current = historyIndex;
|
|
320
|
+
}, [historyIndex]);
|
|
286
321
|
// ── Actions ──
|
|
287
322
|
const setNode = useCallback(async (name, value) => {
|
|
288
323
|
// Input changes (switch toggles, button presses) don't create a new
|
|
@@ -307,7 +342,7 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
307
342
|
}, [ready, sandbox, slotId]);
|
|
308
343
|
const toggleInput = useCallback((name) => {
|
|
309
344
|
const current = inputs[name];
|
|
310
|
-
const newValue = typeof current === 'boolean' ? !current :
|
|
345
|
+
const newValue = typeof current === 'boolean' ? !current : current === 0 ? 1 : 0;
|
|
311
346
|
setNode(name, newValue);
|
|
312
347
|
}, [inputs, setNode]);
|
|
313
348
|
const toggleNode = useCallback(async (nodeId) => {
|
|
@@ -315,7 +350,7 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
315
350
|
return;
|
|
316
351
|
const outKey = `${nodeId}.out`;
|
|
317
352
|
const current = portValues.get(outKey);
|
|
318
|
-
const newValue = typeof current === 'boolean' ? !current :
|
|
353
|
+
const newValue = typeof current === 'boolean' ? !current : current === 1 ? 0 : 1;
|
|
319
354
|
// Same reasoning as setNode: no new history entry on input edit.
|
|
320
355
|
const result = await sandbox.setNode(nodeId, newValue, slotId);
|
|
321
356
|
if ('error' in result)
|
|
@@ -407,7 +442,9 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
407
442
|
if (isSequential) {
|
|
408
443
|
const snap = await sandbox.snapshot(slotId);
|
|
409
444
|
if (!('error' in snap) && snap.snapshotId !== undefined) {
|
|
410
|
-
historyRef.current = [
|
|
445
|
+
historyRef.current = [
|
|
446
|
+
{ snapshotId: snap.snapshotId, cycle: 0, inputs: { ...defaultInputs } },
|
|
447
|
+
];
|
|
411
448
|
}
|
|
412
449
|
}
|
|
413
450
|
historyIndexRef.current = historyRef.current.length > 0 ? 0 : -1;
|
|
@@ -476,14 +513,18 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
476
513
|
// bail-early version of tick and never recover, even after the sandbox
|
|
477
514
|
// becomes ready — the interval would fire forever calling a stale closure.
|
|
478
515
|
const tickRef = useRef(tick);
|
|
479
|
-
useEffect(() => {
|
|
516
|
+
useEffect(() => {
|
|
517
|
+
tickRef.current = tick;
|
|
518
|
+
}, [tick]);
|
|
480
519
|
const startAutoRun = useCallback((ticksPerSecond, _opts) => {
|
|
481
520
|
if (autoRunRef.current)
|
|
482
521
|
clearInterval(autoRunRef.current);
|
|
483
522
|
setSpeedState(ticksPerSecond);
|
|
484
523
|
setIsRunning(true);
|
|
485
524
|
const interval = Math.max(1, Math.floor(1000 / ticksPerSecond));
|
|
486
|
-
autoRunRef.current = setInterval(() => {
|
|
525
|
+
autoRunRef.current = setInterval(() => {
|
|
526
|
+
tickRef.current();
|
|
527
|
+
}, interval);
|
|
487
528
|
}, []);
|
|
488
529
|
const stopAutoRun = useCallback(() => {
|
|
489
530
|
if (autoRunRef.current) {
|
|
@@ -498,7 +539,9 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
498
539
|
// Restart with new speed
|
|
499
540
|
clearInterval(autoRunRef.current);
|
|
500
541
|
const interval = Math.max(1, Math.floor(1000 / ticksPerSecond));
|
|
501
|
-
autoRunRef.current = setInterval(() => {
|
|
542
|
+
autoRunRef.current = setInterval(() => {
|
|
543
|
+
tickRef.current();
|
|
544
|
+
}, interval);
|
|
502
545
|
}
|
|
503
546
|
}, []);
|
|
504
547
|
useEffect(() => {
|
|
@@ -528,7 +571,9 @@ export function useCircuitSimulator(circuit, options) {
|
|
|
528
571
|
// Shadow-typed history for the UI: the consumer only needs a length for
|
|
529
572
|
// showing the `N/M` counter and the ◀ ▶ enable logic. historyLen drives
|
|
530
573
|
// re-renders; the array identity is intentionally stable per-slot.
|
|
531
|
-
const historyForUi = useMemo(() => historyRef.current
|
|
574
|
+
const historyForUi = useMemo(() => historyRef.current
|
|
575
|
+
.slice(0, historyLen)
|
|
576
|
+
.map((h) => ({ engineSnapshot: h.snapshotId, metadata: h.cycle })), [historyLen]);
|
|
532
577
|
return {
|
|
533
578
|
// State
|
|
534
579
|
outputs,
|