@tscircuit/runframe 0.0.5 → 0.0.6

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.
@@ -0,0 +1,956 @@
1
+ // lib/components/ui/tabs.tsx
2
+ import * as React from "react";
3
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
4
+
5
+ // lib/utils/index.ts
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ // lib/components/ui/tabs.tsx
13
+ import { jsx } from "react/jsx-runtime";
14
+ var Tabs = TabsPrimitive.Root;
15
+ var TabsList = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
16
+ TabsPrimitive.List,
17
+ {
18
+ ref,
19
+ className: cn(
20
+ "inline-flex h-9 items-center justify-center rounded-lg bg-slate-100 p-1 text-slate-500 dark:bg-slate-800 dark:text-slate-400",
21
+ className
22
+ ),
23
+ ...props
24
+ }
25
+ ));
26
+ TabsList.displayName = TabsPrimitive.List.displayName;
27
+ var TabsTrigger = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
28
+ TabsPrimitive.Trigger,
29
+ {
30
+ ref,
31
+ className: cn(
32
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-950 data-[state=active]:shadow dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 dark:data-[state=active]:bg-slate-950 dark:data-[state=active]:text-slate-50",
33
+ className
34
+ ),
35
+ ...props
36
+ }
37
+ ));
38
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
39
+ var TabsContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
40
+ TabsPrimitive.Content,
41
+ {
42
+ ref,
43
+ className: cn(
44
+ "mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
45
+ className
46
+ ),
47
+ ...props
48
+ }
49
+ ));
50
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
51
+
52
+ // lib/utils/pcbManualEditEventHandler.ts
53
+ import { getManualTraceHintFromEvent } from "@tscircuit/layout";
54
+
55
+ // lib/components/CircuitJsonPreview.tsx
56
+ import { CadViewer } from "@tscircuit/3d-viewer";
57
+ import "@tscircuit/pcb-viewer";
58
+ import { useEffect as useEffect2, useState as useState3 } from "react";
59
+
60
+ // lib/components/ErrorFallback.tsx
61
+ import "react";
62
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
63
+ var ErrorFallback = ({ error }) => {
64
+ return /* @__PURE__ */ jsx2(
65
+ "div",
66
+ {
67
+ "data-testid": "error-container",
68
+ className: "error-container mt-4 bg-red-50 rounded-md border border-red-200",
69
+ children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
70
+ /* @__PURE__ */ jsx2("h2", { className: "text-lg font-semibold text-red-800 mb-3", children: "Error Loading 3D Viewer" }),
71
+ /* @__PURE__ */ jsx2("p", { className: "text-xs font-mono whitespace-pre-wrap text-red-700", children: error.message }),
72
+ /* @__PURE__ */ jsx2(
73
+ "details",
74
+ {
75
+ style: { whiteSpace: "pre-wrap" },
76
+ className: "text-xs font-mono text-red-600 mt-2",
77
+ children: error.stack
78
+ }
79
+ )
80
+ ] })
81
+ }
82
+ );
83
+ };
84
+
85
+ // lib/components/CircuitJsonPreview.tsx
86
+ import { ErrorBoundary } from "react-error-boundary";
87
+
88
+ // lib/components/ErrorTabContent.tsx
89
+ import { GitHubLogoIcon } from "@radix-ui/react-icons";
90
+ import { ClipboardIcon } from "lucide-react";
91
+
92
+ // lib/components/ui/button.tsx
93
+ import * as React3 from "react";
94
+ import { cva } from "class-variance-authority";
95
+ import { jsx as jsx3 } from "react/jsx-runtime";
96
+ var buttonVariants = cva(
97
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-slate-300",
98
+ {
99
+ variants: {
100
+ variant: {
101
+ default: "bg-slate-900 text-slate-50 shadow hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90",
102
+ destructive: "bg-red-500 text-slate-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90",
103
+ outline: "border border-slate-200 bg-white shadow-sm hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50",
104
+ secondary: "bg-slate-100 text-slate-900 shadow-sm hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
105
+ ghost: "hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50",
106
+ link: "text-slate-900 underline-offset-4 hover:underline dark:text-slate-50"
107
+ },
108
+ size: {
109
+ default: "h-9 px-4 py-2",
110
+ sm: "h-8 rounded-md px-3 text-xs",
111
+ lg: "h-10 rounded-md px-8",
112
+ icon: "h-9 w-9"
113
+ }
114
+ },
115
+ defaultVariants: {
116
+ variant: "default",
117
+ size: "default"
118
+ }
119
+ }
120
+ );
121
+ var Button = React3.forwardRef(
122
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
123
+ const Comp = "button";
124
+ return /* @__PURE__ */ jsx3(
125
+ Comp,
126
+ {
127
+ className: cn(buttonVariants({ variant, size, className })),
128
+ ref,
129
+ ...props
130
+ }
131
+ );
132
+ }
133
+ );
134
+ Button.displayName = "Button";
135
+
136
+ // lib/components/ErrorTabContent.tsx
137
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
138
+ var ErrorTabContent = ({
139
+ code,
140
+ isStreaming,
141
+ errorMessage
142
+ }) => {
143
+ if (!errorMessage) {
144
+ return /* @__PURE__ */ jsx4("div", { className: "mt-4 bg-green-50 rounded-md border border-green-200", children: /* @__PURE__ */ jsxs2("div", { className: "p-4", children: [
145
+ /* @__PURE__ */ jsx4("h3", { className: "text-lg font-semibold text-green-800 mb-3", children: "No Errors \u{1F44C}" }),
146
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-green-700", children: "Your code is running without any errors." })
147
+ ] }) });
148
+ }
149
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
150
+ /* @__PURE__ */ jsx4("div", { className: "mt-4 bg-red-50 rounded-md border border-red-200 max-h-[500px] overflow-y-auto", children: /* @__PURE__ */ jsxs2("div", { className: "p-4", children: [
151
+ /* @__PURE__ */ jsx4("h3", { className: "text-lg font-semibold text-red-800 mb-3", children: "Error" }),
152
+ /* @__PURE__ */ jsx4("p", { className: "text-xs font-mono whitespace-pre-wrap text-red-600 mt-2", children: errorMessage })
153
+ ] }) }),
154
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 mt-4 justify-end", children: [
155
+ /* @__PURE__ */ jsxs2(
156
+ Button,
157
+ {
158
+ variant: "outline",
159
+ onClick: () => {
160
+ if (!errorMessage) return;
161
+ navigator.clipboard.writeText(errorMessage);
162
+ alert("Error copied to clipboard!");
163
+ },
164
+ children: [
165
+ /* @__PURE__ */ jsx4(ClipboardIcon, { className: "w-4 h-4 mr-2" }),
166
+ "Copy Error"
167
+ ]
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsxs2(
171
+ Button,
172
+ {
173
+ variant: "outline",
174
+ onClick: () => {
175
+ window.alert(
176
+ "Not supported yet! Please report/upvote an issue on github.com/tscircuit/tscircuit"
177
+ );
178
+ },
179
+ children: [
180
+ /* @__PURE__ */ jsx4(GitHubLogoIcon, { className: "w-4 h-4 mr-2" }),
181
+ "Report Issue"
182
+ ]
183
+ }
184
+ )
185
+ ] })
186
+ ] });
187
+ };
188
+
189
+ // lib/components/CircuitJsonPreview.tsx
190
+ import { SchematicViewer } from "@tscircuit/schematic-viewer";
191
+
192
+ // lib/components/PreviewEmptyState.tsx
193
+ import { PlayIcon } from "lucide-react";
194
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
195
+ var PreviewEmptyState = ({ onRunClicked }) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 bg-gray-100 text-center justify-center py-10", children: [
196
+ "No circuit json loaded",
197
+ /* @__PURE__ */ jsxs3(Button, { className: "bg-blue-600 hover:bg-blue-500", onClick: onRunClicked, children: [
198
+ "Run Code",
199
+ /* @__PURE__ */ jsx5(PlayIcon, { className: "w-3 h-3 ml-2" })
200
+ ] })
201
+ ] });
202
+ var PreviewEmptyState_default = PreviewEmptyState;
203
+
204
+ // lib/components/CircuitJsonTableViewer/CircuitJsonTableViewer.tsx
205
+ import { useReducer, useState } from "react";
206
+
207
+ // lib/components/CircuitJsonTableViewer/ClickableText.tsx
208
+ import { jsx as jsx6 } from "react/jsx-runtime";
209
+ var ClickableText = ({
210
+ text,
211
+ onClick
212
+ }) => {
213
+ return /* @__PURE__ */ jsx6(
214
+ "span",
215
+ {
216
+ className: "cursor-pointer underline text-blue-300 mx-2",
217
+ onClick,
218
+ children: text
219
+ }
220
+ );
221
+ };
222
+
223
+ // lib/components/CircuitJsonTableViewer/HeaderCell.tsx
224
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
225
+ var HeaderCell = (p) => {
226
+ return /* @__PURE__ */ jsxs4("div", { className: "leading-5", children: [
227
+ /* @__PURE__ */ jsx7("div", { className: "py-2 font-bold", children: p.column.name }),
228
+ /* @__PURE__ */ jsx7("div", { children: p.field?.() ?? /* @__PURE__ */ jsx7(
229
+ "input",
230
+ {
231
+ type: "text",
232
+ className: "border rounded p-1 w-full",
233
+ onChange: (e) => {
234
+ p.onTextChange?.(e.target.value);
235
+ }
236
+ }
237
+ ) })
238
+ ] });
239
+ };
240
+
241
+ // lib/components/CircuitJsonTableViewer/Modal.tsx
242
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
243
+ var Modal = ({ open, children, title, onClose }) => {
244
+ if (!open) return null;
245
+ return /* @__PURE__ */ jsx8(
246
+ "div",
247
+ {
248
+ className: "fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50",
249
+ onClick: onClose,
250
+ children: /* @__PURE__ */ jsxs5(
251
+ "div",
252
+ {
253
+ className: "bg-white p-5 rounded-lg relative w-11/12 max-w-2xl",
254
+ onClick: (e) => {
255
+ e.preventDefault();
256
+ e.stopPropagation();
257
+ },
258
+ children: [
259
+ /* @__PURE__ */ jsx8("h2", { className: "mt-0 text-xl", children: title }),
260
+ /* @__PURE__ */ jsx8(
261
+ "button",
262
+ {
263
+ className: "absolute top-4 right-4 text-2xl font-bold",
264
+ onClick: onClose,
265
+ children: "\xD7"
266
+ }
267
+ ),
268
+ children
269
+ ]
270
+ }
271
+ )
272
+ }
273
+ );
274
+ };
275
+ var Modal_default = Modal;
276
+
277
+ // lib/components/CircuitJsonTableViewer/CircuitJsonTableViewer.tsx
278
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
279
+ var CircuitJsonTableViewer = ({
280
+ elements
281
+ }) => {
282
+ const [modal, setModal] = useState({ open: false });
283
+ const [filters, setFilter] = useReducer(
284
+ (s, a) => ({
285
+ ...s,
286
+ ...a
287
+ }),
288
+ {}
289
+ );
290
+ const element_types = [...new Set(elements.map((e) => e.type))];
291
+ const elements2 = elements.map((e) => {
292
+ const primary_id = e[`${e.type}_id`];
293
+ const other_ids = Object.fromEntries(
294
+ Object.entries(e).filter(([k]) => {
295
+ if (k === `${e.type}_id`) return false;
296
+ if (!k.endsWith("_id")) return false;
297
+ return true;
298
+ })
299
+ );
300
+ const other_props = Object.fromEntries(
301
+ Object.entries(e).filter(([k]) => !k.endsWith("_id"))
302
+ );
303
+ return {
304
+ primary_id,
305
+ other_ids,
306
+ ...other_props,
307
+ _og_elm: e
308
+ };
309
+ });
310
+ const elements3 = elements2.map((e) => {
311
+ let selector_path = "";
312
+ const getSelectorPath = (e2) => {
313
+ const parent_key = Object.keys(e2.other_ids).find(
314
+ (k) => k.startsWith("source_")
315
+ );
316
+ if (!parent_key) return `.${e2.name}`;
317
+ const parent_type = parent_key.slice(0, -3);
318
+ const parent = elements2.find(
319
+ (p) => p.type === parent_type && p.primary_id === e2.other_ids[parent_key]
320
+ );
321
+ if (!parent) return `??? > .${e2.name}`;
322
+ if (!("name" in parent)) return `#${parent.primary_id} > .${e2.name}`;
323
+ return `${getSelectorPath(parent)} > .${e2.name}`;
324
+ };
325
+ if ("name" in e) {
326
+ selector_path = getSelectorPath(e);
327
+ }
328
+ return {
329
+ ...e,
330
+ selector_path
331
+ };
332
+ });
333
+ const columns = [
334
+ {
335
+ key: "primary_id",
336
+ name: "primary_id",
337
+ renderCell: (row) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center", children: [
338
+ /* @__PURE__ */ jsx9(
339
+ ClickableText,
340
+ {
341
+ text: row.primary_id,
342
+ onClick: () => setFilter({
343
+ focused_id: row.primary_id,
344
+ id_search: void 0,
345
+ selector_search: void 0
346
+ })
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsx9("span", { className: "flex-grow" }),
350
+ /* @__PURE__ */ jsx9(
351
+ ClickableText,
352
+ {
353
+ text: "(JSON)",
354
+ onClick: () => setModal({
355
+ open: true,
356
+ element: row._og_elm,
357
+ title: row.primary_id
358
+ })
359
+ }
360
+ )
361
+ ] }),
362
+ renderHeaderCell: (col) => /* @__PURE__ */ jsx9(
363
+ HeaderCell,
364
+ {
365
+ column: col,
366
+ onTextChange: (v) => setFilter({ id_search: v }),
367
+ field: !filters.focused_id ? void 0 : () => /* @__PURE__ */ jsxs6("div", { children: [
368
+ "Focus:",
369
+ " ",
370
+ /* @__PURE__ */ jsx9("span", { className: "underline", children: filters.focused_id }),
371
+ /* @__PURE__ */ jsx9(
372
+ ClickableText,
373
+ {
374
+ text: "(unfocus)",
375
+ onClick: () => setFilter({ focused_id: void 0 })
376
+ }
377
+ )
378
+ ] })
379
+ }
380
+ )
381
+ },
382
+ {
383
+ key: "type",
384
+ name: "type",
385
+ renderHeaderCell: (col) => /* @__PURE__ */ jsx9(
386
+ HeaderCell,
387
+ {
388
+ column: col,
389
+ field: () => /* @__PURE__ */ jsxs6(
390
+ "select",
391
+ {
392
+ onChange: (e) => setFilter({ component_type_filter: e.target.value }),
393
+ className: "border rounded p-1 w-full",
394
+ children: [
395
+ /* @__PURE__ */ jsx9("option", { value: "any", children: "any" }, "any"),
396
+ /* @__PURE__ */ jsx9("option", { value: "source", children: "source" }, "source"),
397
+ /* @__PURE__ */ jsx9("option", { value: "source/pcb", children: "source/pcb" }, "source/pcb"),
398
+ /* @__PURE__ */ jsx9("option", { value: "source/schematic", children: "source/schematic" }, "source/schematic"),
399
+ element_types.map((type) => /* @__PURE__ */ jsx9("option", { value: type, children: type }, type))
400
+ ]
401
+ }
402
+ )
403
+ }
404
+ )
405
+ },
406
+ {
407
+ key: "name",
408
+ name: "name",
409
+ renderHeaderCell: (col) => /* @__PURE__ */ jsx9(
410
+ HeaderCell,
411
+ {
412
+ column: col,
413
+ onTextChange: (t) => setFilter({ name_search: t })
414
+ }
415
+ )
416
+ },
417
+ {
418
+ key: "selector_path",
419
+ name: "selector_path",
420
+ renderHeaderCell: (col) => /* @__PURE__ */ jsx9(
421
+ HeaderCell,
422
+ {
423
+ column: col,
424
+ onTextChange: (t) => setFilter({ selector_search: t })
425
+ }
426
+ )
427
+ },
428
+ {
429
+ key: "other_ids",
430
+ name: "other_ids",
431
+ renderCell: (row) => /* @__PURE__ */ jsx9("div", { className: "space-x-2", children: Object.entries(row.other_ids).map(([other_id, v]) => /* @__PURE__ */ jsx9(
432
+ ClickableText,
433
+ {
434
+ text: v,
435
+ onClick: () => setFilter({ focused_id: v })
436
+ },
437
+ v
438
+ )) })
439
+ }
440
+ ];
441
+ const elements4 = elements3.filter((e) => {
442
+ if (!filters.name_search) return true;
443
+ return e.name?.toLowerCase()?.includes(filters.name_search.toLowerCase());
444
+ }).filter((e) => {
445
+ if (!filters.component_type_filter) return true;
446
+ if (filters.component_type_filter === "any") return true;
447
+ if (filters.component_type_filter === "source") {
448
+ return e.type.startsWith("source_");
449
+ }
450
+ if (filters.component_type_filter === "source/pcb") {
451
+ return e.type.startsWith("source_") || e.type.startsWith("pcb_");
452
+ }
453
+ if (filters.component_type_filter === "source/schematic") {
454
+ return e.type.startsWith("source_") || e.type.startsWith("schematic_");
455
+ }
456
+ return e.type?.includes(filters.component_type_filter);
457
+ }).filter((e) => {
458
+ if (!filters.selector_search) return true;
459
+ const parts = filters.selector_search.split(" ").filter((p) => p.length > 0);
460
+ return parts.every((part) => e.selector_path?.includes(part));
461
+ }).filter((e) => {
462
+ if (!filters.id_search) return true;
463
+ return e.primary_id?.includes(filters.id_search);
464
+ }).filter((e) => {
465
+ if (!filters.focused_id) return true;
466
+ if (e.primary_id === filters.focused_id) return true;
467
+ if (Object.values(e.other_ids).includes(filters.focused_id)) return true;
468
+ return false;
469
+ });
470
+ return /* @__PURE__ */ jsxs6("div", { className: "font-mono text-xs", children: [
471
+ /* @__PURE__ */ jsx9("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs6("table", { className: "table-auto w-full text-left", children: [
472
+ /* @__PURE__ */ jsx9("thead", { children: /* @__PURE__ */ jsx9("tr", { children: columns.map((col) => /* @__PURE__ */ jsx9("th", { className: "px-4 py-2 border-b", children: col.renderHeaderCell ? col.renderHeaderCell(col) : col.name }, col.key)) }) }),
473
+ /* @__PURE__ */ jsx9("tbody", { children: elements4.map((row, rowIndex) => /* @__PURE__ */ jsx9("tr", { className: "hover:bg-gray-100", children: columns.map((col) => /* @__PURE__ */ jsx9("td", { className: "px-4 py-2 border-b", children: col.renderCell ? col.renderCell(row) : row[col.key] }, col.key)) }, rowIndex)) })
474
+ ] }) }),
475
+ /* @__PURE__ */ jsx9(
476
+ Modal_default,
477
+ {
478
+ open: modal.open,
479
+ onClose: () => setModal({ open: false }),
480
+ title: modal.open ? modal.title : "",
481
+ children: /* @__PURE__ */ jsx9("div", { className: "bg-gray-800 p-3 text-white rounded", children: /* @__PURE__ */ jsx9("pre", { className: "whitespace-pre-wrap", children: modal.open ? JSON.stringify(modal.element, null, 2) : "" }) })
482
+ }
483
+ )
484
+ ] });
485
+ };
486
+
487
+ // lib/components/BomTable.tsx
488
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
489
+ var linkify = (supplier, partNumber) => {
490
+ if (supplier === "jlcpcb") {
491
+ return /* @__PURE__ */ jsx10(
492
+ "a",
493
+ {
494
+ className: "underline text-blue-500",
495
+ target: "_blank",
496
+ rel: "noreferrer",
497
+ href: `https://jlcpcb.com/partdetail/${partNumber}`,
498
+ children: partNumber
499
+ }
500
+ );
501
+ }
502
+ return partNumber;
503
+ };
504
+ var BomTable = ({ circuitJson }) => {
505
+ const sourceComponents = circuitJson.filter(
506
+ (el) => el.type === "source_component"
507
+ );
508
+ const supplierColumns = /* @__PURE__ */ new Set();
509
+ for (const comp of sourceComponents) {
510
+ if (comp.supplier_part_numbers) {
511
+ for (const supplier of Object.keys(comp.supplier_part_numbers)) {
512
+ supplierColumns.add(supplier);
513
+ }
514
+ }
515
+ }
516
+ return /* @__PURE__ */ jsx10("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs7("table", { className: "w-full text-left text-sm", children: [
517
+ /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsxs7("tr", { className: "border-b", children: [
518
+ /* @__PURE__ */ jsx10("th", { className: "p-2", children: "Name" }),
519
+ Array.from(supplierColumns).map((supplier) => /* @__PURE__ */ jsx10("th", { className: "p-2 capitalize", children: supplier }, supplier))
520
+ ] }) }),
521
+ /* @__PURE__ */ jsx10("tbody", { children: sourceComponents.map((comp) => /* @__PURE__ */ jsxs7("tr", { className: "border-b", children: [
522
+ /* @__PURE__ */ jsx10("td", { className: "p-2", children: comp.name }),
523
+ Array.from(supplierColumns).map((supplier) => /* @__PURE__ */ jsx10("td", { className: "p-2", children: linkify(
524
+ supplier,
525
+ comp.supplier_part_numbers?.[supplier]?.[0] || ""
526
+ ) }, supplier))
527
+ ] }, comp.source_component_id)) })
528
+ ] }) });
529
+ };
530
+
531
+ // lib/components/CircuitJsonPreview.tsx
532
+ import {
533
+ CheckIcon as CheckIcon2,
534
+ EllipsisIcon,
535
+ FullscreenIcon,
536
+ MinimizeIcon
537
+ } from "lucide-react";
538
+
539
+ // lib/components/ui/dropdown-menu.tsx
540
+ import * as React4 from "react";
541
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
542
+ import {
543
+ CheckIcon,
544
+ ChevronRightIcon,
545
+ DotFilledIcon
546
+ } from "@radix-ui/react-icons";
547
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
548
+ var DropdownMenu = DropdownMenuPrimitive.Root;
549
+ var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
550
+ var DropdownMenuSubTrigger = React4.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs8(
551
+ DropdownMenuPrimitive.SubTrigger,
552
+ {
553
+ ref,
554
+ className: cn(
555
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-800 dark:data-[state=open]:bg-slate-800",
556
+ inset && "pl-8",
557
+ className
558
+ ),
559
+ ...props,
560
+ children: [
561
+ children,
562
+ /* @__PURE__ */ jsx11(ChevronRightIcon, { className: "ml-auto h-4 w-4" })
563
+ ]
564
+ }
565
+ ));
566
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
567
+ var DropdownMenuSubContent = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
568
+ DropdownMenuPrimitive.SubContent,
569
+ {
570
+ ref,
571
+ className: cn(
572
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
573
+ className
574
+ ),
575
+ ...props
576
+ }
577
+ ));
578
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
579
+ var DropdownMenuContent = React4.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx11(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
580
+ DropdownMenuPrimitive.Content,
581
+ {
582
+ ref,
583
+ sideOffset,
584
+ className: cn(
585
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-950 shadow-md dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
586
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
587
+ className
588
+ ),
589
+ ...props
590
+ }
591
+ ) }));
592
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
593
+ var DropdownMenuItem = React4.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx11(
594
+ DropdownMenuPrimitive.Item,
595
+ {
596
+ ref,
597
+ className: cn(
598
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50",
599
+ inset && "pl-8",
600
+ className
601
+ ),
602
+ ...props
603
+ }
604
+ ));
605
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
606
+ var DropdownMenuCheckboxItem = React4.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs8(
607
+ DropdownMenuPrimitive.CheckboxItem,
608
+ {
609
+ ref,
610
+ className: cn(
611
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50",
612
+ className
613
+ ),
614
+ checked,
615
+ ...props,
616
+ children: [
617
+ /* @__PURE__ */ jsx11("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx11(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx11(CheckIcon, { className: "h-4 w-4" }) }) }),
618
+ children
619
+ ]
620
+ }
621
+ ));
622
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
623
+ var DropdownMenuRadioItem = React4.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs8(
624
+ DropdownMenuPrimitive.RadioItem,
625
+ {
626
+ ref,
627
+ className: cn(
628
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50",
629
+ className
630
+ ),
631
+ ...props,
632
+ children: [
633
+ /* @__PURE__ */ jsx11("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx11(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx11(DotFilledIcon, { className: "h-4 w-4 fill-current" }) }) }),
634
+ children
635
+ ]
636
+ }
637
+ ));
638
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
639
+ var DropdownMenuLabel = React4.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx11(
640
+ DropdownMenuPrimitive.Label,
641
+ {
642
+ ref,
643
+ className: cn(
644
+ "px-2 py-1.5 text-sm font-semibold",
645
+ inset && "pl-8",
646
+ className
647
+ ),
648
+ ...props
649
+ }
650
+ ));
651
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
652
+ var DropdownMenuSeparator = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx11(
653
+ DropdownMenuPrimitive.Separator,
654
+ {
655
+ ref,
656
+ className: cn("-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-800", className),
657
+ ...props
658
+ }
659
+ ));
660
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
661
+ var DropdownMenuShortcut = ({
662
+ className,
663
+ ...props
664
+ }) => {
665
+ return /* @__PURE__ */ jsx11(
666
+ "span",
667
+ {
668
+ className: cn("ml-auto text-xs tracking-widest opacity-60", className),
669
+ ...props
670
+ }
671
+ );
672
+ };
673
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
674
+
675
+ // lib/components/PcbViewerWithContainerHeight.tsx
676
+ import { useRef, useState as useState2, useLayoutEffect } from "react";
677
+ import { PCBViewer } from "@tscircuit/pcb-viewer";
678
+ import { jsx as jsx12 } from "react/jsx-runtime";
679
+ var PcbViewerWithContainerHeight = ({
680
+ containerClassName,
681
+ ...props
682
+ }) => {
683
+ const containerRef = useRef(null);
684
+ const [computedHeight, setComputedHeight] = useState2(620);
685
+ useLayoutEffect(() => {
686
+ const updateHeight = () => {
687
+ if (containerRef.current) {
688
+ const containerHeight = containerRef.current.clientHeight;
689
+ const screenHeight = window.innerHeight;
690
+ setComputedHeight(
691
+ Math.min(Math.max(containerHeight, 620), screenHeight)
692
+ );
693
+ }
694
+ };
695
+ updateHeight();
696
+ const resizeObserver = new ResizeObserver(updateHeight);
697
+ if (containerRef.current) {
698
+ resizeObserver.observe(containerRef.current);
699
+ }
700
+ window.addEventListener("resize", updateHeight);
701
+ return () => {
702
+ resizeObserver.disconnect();
703
+ window.removeEventListener("resize", updateHeight);
704
+ };
705
+ }, []);
706
+ return /* @__PURE__ */ jsx12("div", { ref: containerRef, className: containerClassName || "w-full h-full", children: /* @__PURE__ */ jsx12(PCBViewer, { ...props, height: computedHeight }) });
707
+ };
708
+
709
+ // lib/hooks/use-styles.ts
710
+ import { useEffect } from "react";
711
+
712
+ // lib/hooks/styles.generated.ts
713
+ var styles_generated_default = `.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.left-2{left:.5rem}.right-4{right:1rem}.right-\\[4px\\]{right:4px}.top-4{top:1rem}.top-\\[6px\\]{top:6px}.z-50{z-index:50}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-1{margin-bottom:.25rem;margin-top:.25rem}.mb-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0{margin-top:0}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.contents{display:contents}.h-1{height:.25rem}.h-10{height:2.5rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\\.5{height:.875rem}.h-4{height:1rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\\[620px\\]{height:620px}.h-\\[calc\\(100vh-96px\\)\\]{height:calc(100vh - 96px)}.h-full{height:100%}.h-px{height:1px}.max-h-\\[500px\\]{max-height:500px}.min-h-\\[620px\\]{min-height:620px}.min-h-\\[calc\\(100vh-240px\\)\\]{min-height:calc(100vh - 240px)}.w-1{width:.25rem}.w-11\\/12{width:91.666667%}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\\.5{width:.875rem}.w-4{width:1rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-\\[8rem\\]{min-width:8rem}.max-w-2xl{max-width:42rem}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-green-200{--tw-border-opacity:1;border-color:rgb(187 247 208/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-opacity-50{--tw-bg-opacity:0.5}.fill-current{fill:currentColor}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-1\\.5{padding-bottom:.375rem;padding-top:.375rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.pl-8{padding-left:2rem}.pr-2{padding-right:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\\[8px\\]{font-size:8px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.leading-5{line-height:1.25rem}.tracking-widest{letter-spacing:.1em}.text-blue-300{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-slate-50{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-slate-950{--tw-text-opacity:1;color:rgb(2 6 23/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-60{opacity:.6}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-offset-white{--tw-ring-offset-color:#fff}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.\\*\\:text-xs>*{font-size:.75rem;line-height:1rem}.hover\\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\\:bg-red-500\\/90:hover{background-color:rgba(239,68,68,.9)}.hover\\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.hover\\:bg-slate-100\\/80:hover{background-color:rgba(241,245,249,.8)}.hover\\:bg-slate-900\\/90:hover{background-color:rgba(15,23,42,.9)}.hover\\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.hover\\:underline:hover{text-decoration-line:underline}.focus\\:bg-slate-100:focus{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.focus\\:text-slate-900:focus{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.focus-visible\\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\\:ring-1:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\\:ring-slate-950:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(2 6 23/var(--tw-ring-opacity,1))}.focus-visible\\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.disabled\\:pointer-events-none:disabled{pointer-events:none}.disabled\\:opacity-50:disabled{opacity:.5}.data-\\[disabled\\]\\:pointer-events-none[data-disabled]{pointer-events:none}.data-\\[state\\=active\\]\\:bg-white[data-state=active]{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.data-\\[state\\=open\\]\\:bg-slate-100[data-state=open]{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.data-\\[state\\=active\\]\\:text-slate-950[data-state=active]{--tw-text-opacity:1;color:rgb(2 6 23/var(--tw-text-opacity,1))}.data-\\[disabled\\]\\:opacity-50[data-disabled]{opacity:.5}.data-\\[state\\=active\\]\\:shadow[data-state=active]{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (min-width:768px){.md\\:sticky{position:sticky}.md\\:top-2{top:.5rem}}@media (prefers-color-scheme:dark){.dark\\:border-slate-800{--tw-border-opacity:1;border-color:rgb(30 41 59/var(--tw-border-opacity,1))}.dark\\:bg-red-900{--tw-bg-opacity:1;background-color:rgb(127 29 29/var(--tw-bg-opacity,1))}.dark\\:bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.dark\\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\\:bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.dark\\:text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.dark\\:text-slate-50{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}.dark\\:text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.dark\\:ring-offset-slate-950{--tw-ring-offset-color:#020617}.dark\\:hover\\:bg-red-900\\/90:hover{background-color:rgba(127,29,29,.9)}.dark\\:hover\\:bg-slate-50\\/90:hover{background-color:rgba(248,250,252,.9)}.dark\\:hover\\:bg-slate-800:hover{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\\:hover\\:bg-slate-800\\/80:hover{background-color:rgba(30,41,59,.8)}.dark\\:hover\\:text-slate-50:hover{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}.dark\\:focus\\:bg-slate-800:focus{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\\:focus\\:text-slate-50:focus{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}.dark\\:focus-visible\\:ring-slate-300:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(203 213 225/var(--tw-ring-opacity,1))}.dark\\:data-\\[state\\=active\\]\\:bg-slate-950[data-state=active]{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.dark\\:data-\\[state\\=open\\]\\:bg-slate-800[data-state=open]{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.dark\\:data-\\[state\\=active\\]\\:text-slate-50[data-state=active]{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}}`;
714
+
715
+ // lib/hooks/use-styles.ts
716
+ var useStyles = () => {
717
+ useEffect(() => {
718
+ const existingStyle = document.querySelector(
719
+ 'style[data-styles="tscircuit-runframe"]'
720
+ );
721
+ if (existingStyle) return;
722
+ const styleElement = document.createElement("style");
723
+ styleElement.setAttribute("data-styles", "tscircuit-runframe");
724
+ styleElement.textContent = styles_generated_default;
725
+ document.head.appendChild(styleElement);
726
+ }, []);
727
+ };
728
+
729
+ // lib/components/CircuitJsonPreview.tsx
730
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
731
+ var CircuitJsonPreview = ({
732
+ code,
733
+ onRunClicked = () => {
734
+ },
735
+ tsxRunTriggerCount,
736
+ errorMessage,
737
+ circuitJsonKey = "",
738
+ circuitJson,
739
+ showCodeTab = false,
740
+ codeTabContent,
741
+ showJsonTab = true,
742
+ showImportAndFormatButtons = true,
743
+ className,
744
+ headerClassName,
745
+ leftHeaderContent,
746
+ readOnly,
747
+ isStreaming,
748
+ onToggleFullScreen,
749
+ isFullScreen,
750
+ isRunningCode,
751
+ hasCodeChangedSinceLastRun,
752
+ onCodeChange,
753
+ onDtsChange,
754
+ manualEditsFileContent,
755
+ onManualEditsFileContentChange
756
+ }) => {
757
+ useStyles();
758
+ const [activeTab, setActiveTab] = useState3(showCodeTab ? "code" : "pcb");
759
+ useEffect2(() => {
760
+ if (errorMessage) {
761
+ setActiveTab("error");
762
+ }
763
+ }, [errorMessage]);
764
+ useEffect2(() => {
765
+ if (activeTab === "code" && circuitJson && !errorMessage) {
766
+ setActiveTab("pcb");
767
+ }
768
+ }, [circuitJson]);
769
+ return /* @__PURE__ */ jsx13("div", { className: cn("flex flex-col relative", className), children: /* @__PURE__ */ jsx13("div", { className: "md:sticky md:top-2", children: /* @__PURE__ */ jsxs9(
770
+ Tabs,
771
+ {
772
+ value: activeTab,
773
+ onValueChange: setActiveTab,
774
+ className: "flex-grow flex flex-col p-2",
775
+ children: [
776
+ /* @__PURE__ */ jsxs9("div", { className: cn("flex items-center gap-2", headerClassName), children: [
777
+ leftHeaderContent,
778
+ leftHeaderContent && /* @__PURE__ */ jsx13("div", { className: "flex-grow" }),
779
+ !leftHeaderContent && /* @__PURE__ */ jsx13("div", { className: "flex-grow" }),
780
+ /* @__PURE__ */ jsxs9(TabsList, { children: [
781
+ showCodeTab && /* @__PURE__ */ jsx13(TabsTrigger, { value: "code", children: "Code" }),
782
+ /* @__PURE__ */ jsxs9(TabsTrigger, { value: "pcb", className: "whitespace-nowrap", children: [
783
+ circuitJson && /* @__PURE__ */ jsx13(
784
+ "span",
785
+ {
786
+ className: cn(
787
+ "inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
788
+ !hasCodeChangedSinceLastRun ? "bg-blue-500" : "bg-gray-500"
789
+ )
790
+ }
791
+ ),
792
+ "PCB"
793
+ ] }),
794
+ /* @__PURE__ */ jsxs9(TabsTrigger, { value: "schematic", className: "whitespace-nowrap", children: [
795
+ circuitJson && /* @__PURE__ */ jsx13(
796
+ "span",
797
+ {
798
+ className: cn(
799
+ "inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
800
+ !hasCodeChangedSinceLastRun ? "bg-blue-500" : "bg-gray-500"
801
+ )
802
+ }
803
+ ),
804
+ "Schematic"
805
+ ] }),
806
+ /* @__PURE__ */ jsxs9(TabsTrigger, { value: "cad", children: [
807
+ circuitJson && /* @__PURE__ */ jsx13(
808
+ "span",
809
+ {
810
+ className: cn(
811
+ "inline-flex items-center justify-center w-2 h-2 mr-1 text-xs font-bold text-white rounded-full",
812
+ !hasCodeChangedSinceLastRun ? "bg-blue-500" : "bg-gray-500"
813
+ )
814
+ }
815
+ ),
816
+ "3D"
817
+ ] }),
818
+ /* @__PURE__ */ jsxs9(DropdownMenu, { children: [
819
+ /* @__PURE__ */ jsx13(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9("div", { className: "whitespace-nowrap p-2 mr-1 cursor-pointer relative", children: [
820
+ /* @__PURE__ */ jsx13(EllipsisIcon, { className: "w-4 h-4" }),
821
+ errorMessage && /* @__PURE__ */ jsx13("span", { className: "inline-flex absolute top-[6px] right-[4px] items-center justify-center w-1 h-1 ml-2 text-[8px] font-bold text-white bg-red-500 rounded-full" })
822
+ ] }) }),
823
+ /* @__PURE__ */ jsxs9(DropdownMenuContent, { className: "*:text-xs", children: [
824
+ /* @__PURE__ */ jsxs9(
825
+ DropdownMenuItem,
826
+ {
827
+ onSelect: () => setActiveTab("error"),
828
+ className: "flex",
829
+ children: [
830
+ /* @__PURE__ */ jsx13(
831
+ CheckIcon2,
832
+ {
833
+ className: cn(
834
+ "w-3 h-3 mr-2",
835
+ activeTab !== "error" && "invisible"
836
+ )
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx13("div", { className: "flex-grow", children: "Errors" }),
840
+ errorMessage && /* @__PURE__ */ jsx13("span", { className: "inline-flex items-center justify-center w-3 h-3 ml-2 text-[8px] font-bold text-white bg-red-500 rounded-full", children: "1" })
841
+ ]
842
+ }
843
+ ),
844
+ /* @__PURE__ */ jsxs9(DropdownMenuItem, { onSelect: () => setActiveTab("bom"), children: [
845
+ /* @__PURE__ */ jsx13(
846
+ CheckIcon2,
847
+ {
848
+ className: cn(
849
+ "w-3 h-3 mr-2",
850
+ activeTab !== "bom" && "invisible"
851
+ )
852
+ }
853
+ ),
854
+ "Bill of Materials"
855
+ ] }),
856
+ /* @__PURE__ */ jsxs9(
857
+ DropdownMenuItem,
858
+ {
859
+ onSelect: () => setActiveTab("circuitjson"),
860
+ children: [
861
+ /* @__PURE__ */ jsx13(
862
+ CheckIcon2,
863
+ {
864
+ className: cn(
865
+ "w-3 h-3 mr-2",
866
+ activeTab !== "circuitjson" && "invisible"
867
+ )
868
+ }
869
+ ),
870
+ "JSON"
871
+ ]
872
+ }
873
+ )
874
+ ] })
875
+ ] })
876
+ ] }),
877
+ onToggleFullScreen && /* @__PURE__ */ jsx13(Button, { onClick: onToggleFullScreen, variant: "ghost", children: isFullScreen ? /* @__PURE__ */ jsx13(MinimizeIcon, { size: 16 }) : /* @__PURE__ */ jsx13(FullscreenIcon, { size: 16 }) })
878
+ ] }),
879
+ showCodeTab && /* @__PURE__ */ jsx13(TabsContent, { value: "code", className: "flex-grow overflow-hidden", children: /* @__PURE__ */ jsx13("div", { className: "h-full", children: codeTabContent }) }),
880
+ /* @__PURE__ */ jsx13(TabsContent, { value: "pcb", children: /* @__PURE__ */ jsx13(
881
+ "div",
882
+ {
883
+ className: cn(
884
+ "overflow-hidden",
885
+ isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]"
886
+ ),
887
+ children: /* @__PURE__ */ jsx13(ErrorBoundary, { fallback: /* @__PURE__ */ jsx13("div", { children: "Error loading PCB viewer" }), children: circuitJson ? /* @__PURE__ */ jsx13(
888
+ PcbViewerWithContainerHeight,
889
+ {
890
+ soup: circuitJson,
891
+ containerClassName: cn(
892
+ "h-full w-full",
893
+ isFullScreen ? "min-h-[calc(100vh-240px)]" : "min-h-[620px]"
894
+ )
895
+ },
896
+ circuitJsonKey
897
+ ) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
898
+ }
899
+ ) }),
900
+ /* @__PURE__ */ jsx13(TabsContent, { value: "schematic", children: /* @__PURE__ */ jsx13(
901
+ "div",
902
+ {
903
+ className: cn(
904
+ "overflow-auto",
905
+ isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]"
906
+ ),
907
+ children: /* @__PURE__ */ jsx13(ErrorBoundary, { fallback: /* @__PURE__ */ jsx13("div", { children: "Error loading Schematic" }), children: circuitJson ? /* @__PURE__ */ jsx13(
908
+ SchematicViewer,
909
+ {
910
+ circuitJson,
911
+ containerStyle: {
912
+ height: "100%"
913
+ }
914
+ }
915
+ ) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
916
+ }
917
+ ) }),
918
+ /* @__PURE__ */ jsx13(TabsContent, { value: "cad", children: /* @__PURE__ */ jsx13(
919
+ "div",
920
+ {
921
+ className: cn(
922
+ "overflow-auto",
923
+ isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]"
924
+ ),
925
+ children: /* @__PURE__ */ jsx13(ErrorBoundary, { FallbackComponent: ErrorFallback, children: circuitJson ? /* @__PURE__ */ jsx13(CadViewer, { soup: circuitJson }) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
926
+ }
927
+ ) }),
928
+ /* @__PURE__ */ jsx13(TabsContent, { value: "bom", children: /* @__PURE__ */ jsx13(
929
+ "div",
930
+ {
931
+ className: cn(
932
+ "overflow-auto",
933
+ isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]"
934
+ ),
935
+ children: /* @__PURE__ */ jsx13(ErrorBoundary, { fallback: /* @__PURE__ */ jsx13("div", { children: "Error loading BOM" }), children: circuitJson ? /* @__PURE__ */ jsx13(BomTable, { circuitJson }) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
936
+ }
937
+ ) }),
938
+ /* @__PURE__ */ jsx13(TabsContent, { value: "circuitjson", children: /* @__PURE__ */ jsx13(
939
+ "div",
940
+ {
941
+ className: cn(
942
+ "overflow-auto",
943
+ isFullScreen ? "h-[calc(100vh-96px)]" : "h-[620px]"
944
+ ),
945
+ children: /* @__PURE__ */ jsx13(ErrorBoundary, { fallback: /* @__PURE__ */ jsx13("div", { children: "Error loading JSON viewer" }), children: circuitJson ? /* @__PURE__ */ jsx13(CircuitJsonTableViewer, { elements: circuitJson }) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
946
+ }
947
+ ) }),
948
+ /* @__PURE__ */ jsx13(TabsContent, { value: "error", children: circuitJson || errorMessage ? /* @__PURE__ */ jsx13(ErrorTabContent, { code, errorMessage }) : /* @__PURE__ */ jsx13(PreviewEmptyState_default, { onRunClicked }) })
949
+ ]
950
+ }
951
+ ) }) });
952
+ };
953
+
954
+ export {
955
+ CircuitJsonPreview
956
+ };
@@ -0,0 +1,30 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface PreviewContentProps {
4
+ code?: string;
5
+ readOnly?: boolean;
6
+ onRunClicked?: () => void;
7
+ tsxRunTriggerCount?: number;
8
+ errorMessage?: string | null;
9
+ circuitJson: any;
10
+ circuitJsonKey?: string;
11
+ className?: string;
12
+ showCodeTab?: boolean;
13
+ codeTabContent?: React.ReactNode;
14
+ showJsonTab?: boolean;
15
+ showImportAndFormatButtons?: boolean;
16
+ headerClassName?: string;
17
+ leftHeaderContent?: React.ReactNode;
18
+ isRunningCode?: boolean;
19
+ isStreaming?: boolean;
20
+ onToggleFullScreen?: () => void;
21
+ isFullScreen?: boolean;
22
+ onCodeChange?: (code: string) => void;
23
+ onDtsChange?: (dts: string) => void;
24
+ manualEditsFileContent?: string;
25
+ hasCodeChangedSinceLastRun?: boolean;
26
+ onManualEditsFileContentChange?: (newmanualEditsFileContent: string) => void;
27
+ }
28
+ declare const CircuitJsonPreview: ({ code, onRunClicked, tsxRunTriggerCount, errorMessage, circuitJsonKey, circuitJson, showCodeTab, codeTabContent, showJsonTab, showImportAndFormatButtons, className, headerClassName, leftHeaderContent, readOnly, isStreaming, onToggleFullScreen, isFullScreen, isRunningCode, hasCodeChangedSinceLastRun, onCodeChange, onDtsChange, manualEditsFileContent, onManualEditsFileContentChange, }: PreviewContentProps) => react_jsx_runtime.JSX.Element;
29
+
30
+ export { CircuitJsonPreview, type PreviewContentProps };
@@ -0,0 +1,6 @@
1
+ import {
2
+ CircuitJsonPreview
3
+ } from "./chunk-YW33LMDL.js";
4
+ export {
5
+ CircuitJsonPreview
6
+ };
@@ -0,0 +1,36 @@
1
+ export { CircuitJsonPreview, PreviewContentProps } from './preview.js';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+
4
+ interface Props {
5
+ /**
6
+ * Map of filenames to file contents that will be available in the worker
7
+ */
8
+ fsMap: {
9
+ [filename: string]: string;
10
+ };
11
+ /**
12
+ * The entry point file that will be executed first
13
+ */
14
+ entrypoint: string;
15
+ /**
16
+ * Called when the circuit JSON changes
17
+ */
18
+ onCircuitJsonChange?: (circuitJson: any) => void;
19
+ /**
20
+ * Called when rendering is finished
21
+ */
22
+ onRenderingFinished?: (params: {
23
+ circuitJson: any;
24
+ }) => void;
25
+ /**
26
+ * Called for each render event
27
+ */
28
+ onRenderEvent?: (event: any) => void;
29
+ /**
30
+ * Called when an error occurs
31
+ */
32
+ onError?: (error: Error) => void;
33
+ }
34
+ declare const RunFrame: (props: Props) => react_jsx_runtime.JSX.Element;
35
+
36
+ export { RunFrame };
package/dist/runner.js ADDED
@@ -0,0 +1,40 @@
1
+ import {
2
+ CircuitJsonPreview
3
+ } from "./chunk-YW33LMDL.js";
4
+
5
+ // lib/components/RunFrame.tsx
6
+ import { createCircuitWebWorker } from "@tscircuit/eval-webworker";
7
+ import { useEffect, useState } from "react";
8
+ import evalWebWorkerBlobUrl from "@tscircuit/eval-webworker/blob-url";
9
+ import { jsx } from "react/jsx-runtime";
10
+ var RunFrame = (props) => {
11
+ const [circuitJson, setCircuitJson] = useState(null);
12
+ useEffect(() => {
13
+ async function runWorker() {
14
+ const worker = await createCircuitWebWorker({
15
+ webWorkerUrl: evalWebWorkerBlobUrl,
16
+ verbose: true
17
+ });
18
+ const $finished = worker.executeWithFsMap({
19
+ entrypoint: props.entrypoint,
20
+ fsMap: props.fsMap
21
+ });
22
+ console.log("waiting for execution to finish...");
23
+ await $finished;
24
+ console.log("waiting for initial circuit json...");
25
+ setCircuitJson(await worker.getCircuitJson());
26
+ console.log("got initial circuit json");
27
+ await $finished.catch((e) => {
28
+ console.error(e);
29
+ });
30
+ setCircuitJson(await worker.getCircuitJson());
31
+ }
32
+ runWorker();
33
+ }, [props.fsMap]);
34
+ console.log({ circuitJson });
35
+ return /* @__PURE__ */ jsx(CircuitJsonPreview, { circuitJson });
36
+ };
37
+ export {
38
+ CircuitJsonPreview,
39
+ RunFrame
40
+ };
package/package.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "name": "@tscircuit/runframe",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.5",
5
+ "version": "0.0.6",
6
6
  "scripts": {
7
7
  "start": "cosmos",
8
- "build": "bun run build:lib",
9
- "build:lib": "bun run build:css && bun run build:standalone",
8
+ "build": "bun run build:css && bun run build:standalone && bun run build:lib",
9
+ "build:lib": "tsup-node lib/preview.ts lib/runner.ts --platform browser --format esm --dts",
10
10
  "build:css": "bun run scripts/build-css.ts",
11
11
  "build:site": "cosmos-export",
12
12
  "build:standalone": "STANDALONE=1 vite build",