@tscircuit/schematic-viewer 2.0.78 → 2.0.80

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/index.js CHANGED
@@ -1,8 +1,47 @@
1
1
  // lib/components/SchematicViewer.tsx
2
+ import { su as su4 } from "@tscircuit/soup-util";
2
3
  import {
3
4
  convertCircuitJsonToSchematicSvg
4
5
  } from "circuit-to-svg";
5
- import { su as su4 } from "@tscircuit/soup-util";
6
+
7
+ // lib/hooks/useLocalStorage.ts
8
+ import { useCallback } from "react";
9
+ var STORAGE_KEYS = {
10
+ IS_SHOWING_SCHEMATIC_GROUPS: "schematic_viewer_show_groups",
11
+ IS_SHOWING_SCHEMATIC_PORTS: "schematic_viewer_show_ports",
12
+ SELECTED_SCHEMATIC_SHEET: "schematic_viewer_selected_sheet"
13
+ };
14
+ var getStoredBoolean = (key, defaultValue) => {
15
+ if (typeof window === "undefined") return defaultValue;
16
+ try {
17
+ const stored = localStorage.getItem(key);
18
+ return stored !== null ? JSON.parse(stored) : defaultValue;
19
+ } catch {
20
+ return defaultValue;
21
+ }
22
+ };
23
+ var setStoredBoolean = (key, value) => {
24
+ if (typeof window === "undefined") return;
25
+ try {
26
+ localStorage.setItem(key, JSON.stringify(value));
27
+ } catch {
28
+ }
29
+ };
30
+ var getStoredString = (key) => {
31
+ if (typeof window === "undefined") return null;
32
+ try {
33
+ return localStorage.getItem(key);
34
+ } catch {
35
+ return null;
36
+ }
37
+ };
38
+ var setStoredString = (key, value) => {
39
+ if (typeof window === "undefined") return;
40
+ try {
41
+ localStorage.setItem(key, value);
42
+ } catch {
43
+ }
44
+ };
6
45
 
7
46
  // lib/hooks/useSchematicGroupsOverlay.ts
8
47
  import { useEffect } from "react";
@@ -399,7 +438,7 @@ var enableDebug = () => {
399
438
  };
400
439
 
401
440
  // lib/components/SchematicViewer.tsx
402
- import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
441
+ import { useCallback as useCallback6, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef6, useState as useState6 } from "react";
403
442
  import { toString as transformToString } from "transformation-matrix";
404
443
  import { useMouseMatrixTransform } from "use-mouse-matrix-transform";
405
444
 
@@ -427,328 +466,268 @@ var useResizeHandling = (containerRef) => {
427
466
  return { containerWidth, containerHeight };
428
467
  };
429
468
 
430
- // lib/components/ViewMenu.tsx
431
- import { useMemo } from "react";
432
- import { su as su3 } from "@tscircuit/soup-util";
433
- import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
434
-
435
- // lib/utils/z-index-map.ts
436
- var zIndexMap = {
437
- viewMenuIcon: 48,
438
- viewMenu: 55,
439
- viewMenuBackdrop: 54,
440
- clickToInteractOverlay: 100,
441
- schematicComponentHoverOutline: 47,
442
- schematicPortHoverOutline: 48
443
- };
444
-
445
- // package.json
446
- var package_default = {
447
- name: "@tscircuit/schematic-viewer",
448
- version: "2.0.77",
449
- main: "dist/index.js",
450
- type: "module",
451
- scripts: {
452
- start: "cosmos",
453
- build: "tsup-node ./lib/index.ts --dts --format esm --sourcemap",
454
- "build:site": "cosmos-export",
455
- "vercel-build": "bun run build:site",
456
- format: "biome format --write .",
457
- "format:check": "biome format ."
458
- },
459
- files: [
460
- "dist"
461
- ],
462
- devDependencies: {
463
- "@biomejs/biome": "^1.9.4",
464
- "@types/bun": "latest",
465
- "@types/debug": "^4.1.12",
466
- "@types/react": "^19.0.1",
467
- "@types/react-dom": "^19.0.2",
468
- "@vitejs/plugin-react": "^4.3.4",
469
- react: "^19.1.0",
470
- "react-cosmos": "^6.2.1",
471
- "react-cosmos-plugin-vite": "^6.2.0",
472
- "react-dom": "^19.1.0",
473
- "react-reconciler": "^0.31.0",
474
- semver: "^7.7.2",
475
- tscircuit: "^0.0.2112",
476
- tsup: "^8.3.5",
477
- vite: "^6.0.3"
478
- },
479
- peerDependencies: {
480
- typescript: "^5.0.0",
481
- tscircuit: "*"
482
- },
483
- dependencies: {
484
- "@radix-ui/react-dropdown-menu": "^2.1.16",
485
- "circuit-json": "^0.0.465",
486
- "circuit-to-svg": "^0.0.393",
487
- debug: "^4.4.0",
488
- "performance-now": "^2.1.0",
489
- "use-mouse-matrix-transform": "^1.2.2"
490
- }
491
- };
492
-
493
- // lib/components/ViewMenuIcon.tsx
494
- import { jsx, jsxs } from "react/jsx-runtime";
495
- var ViewMenuIcon = ({
496
- active = false,
497
- ...props
498
- }) => {
499
- return /* @__PURE__ */ jsx(
500
- "button",
501
- {
502
- type: "button",
503
- title: active ? "Hide view menu" : "Show view menu",
504
- ...props,
505
- style: {
506
- position: "absolute",
507
- top: "16px",
508
- right: "16px",
509
- backgroundColor: active ? "#4CAF50" : "#fff",
510
- color: active ? "#fff" : "#000",
511
- padding: "8px",
512
- border: "none",
513
- borderRadius: "4px",
514
- cursor: "pointer",
515
- outline: "none",
516
- boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
517
- display: "flex",
518
- alignItems: "center",
519
- gap: "4px",
520
- zIndex: zIndexMap.viewMenuIcon
521
- },
522
- children: /* @__PURE__ */ jsxs(
523
- "svg",
524
- {
525
- width: "16",
526
- height: "16",
527
- viewBox: "0 0 24 24",
528
- fill: "none",
529
- stroke: "currentColor",
530
- strokeWidth: "2",
531
- children: [
532
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
533
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1" }),
534
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1" })
535
- ]
536
- }
537
- )
538
- }
469
+ // lib/hooks/useContextMenu.ts
470
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef, useState as useState2 } from "react";
471
+ var LONG_PRESS_DURATION_MS = 600;
472
+ var MOVEMENT_THRESHOLD_PX = 10;
473
+ var useContextMenu = ({ containerRef }) => {
474
+ const [menuVisible, setMenuVisible] = useState2(false);
475
+ const [menuPos, setMenuPos] = useState2({ x: 0, y: 0 });
476
+ const menuRef = useRef(null);
477
+ const interactionOriginRef = useRef(null);
478
+ const longPressTimeoutRef = useRef(null);
479
+ const ignoreContextMenuUntilRef = useRef(0);
480
+ const clearLongPressTimeout = useCallback2(() => {
481
+ if (longPressTimeoutRef.current === null) return;
482
+ window.clearTimeout(longPressTimeoutRef.current);
483
+ longPressTimeoutRef.current = null;
484
+ }, []);
485
+ const handleContextMenu = useCallback2((event) => {
486
+ event.preventDefault();
487
+ if (Date.now() < ignoreContextMenuUntilRef.current) return;
488
+ const origin = interactionOriginRef.current;
489
+ if (!origin) return;
490
+ const movedTooFar = Math.abs(event.clientX - origin.x) > MOVEMENT_THRESHOLD_PX || Math.abs(event.clientY - origin.y) > MOVEMENT_THRESHOLD_PX;
491
+ interactionOriginRef.current = null;
492
+ if (movedTooFar) return;
493
+ setMenuPos({ x: event.clientX, y: event.clientY });
494
+ setMenuVisible(true);
495
+ }, []);
496
+ const handleTouchStart = useCallback2(
497
+ (event) => {
498
+ clearLongPressTimeout();
499
+ if (event.touches.length !== 1) {
500
+ interactionOriginRef.current = null;
501
+ return;
502
+ }
503
+ const touch = event.touches[0];
504
+ if (!touch) return;
505
+ interactionOriginRef.current = {
506
+ x: touch.clientX,
507
+ y: touch.clientY
508
+ };
509
+ longPressTimeoutRef.current = window.setTimeout(() => {
510
+ const container = containerRef.current;
511
+ if (!container || !interactionOriginRef.current) return;
512
+ const rect = container.getBoundingClientRect();
513
+ setMenuPos({
514
+ x: rect.left + rect.width / 2,
515
+ y: rect.top + rect.height / 2
516
+ });
517
+ setMenuVisible(true);
518
+ ignoreContextMenuUntilRef.current = Date.now() + 1e3;
519
+ interactionOriginRef.current = null;
520
+ }, LONG_PRESS_DURATION_MS);
521
+ },
522
+ [clearLongPressTimeout, containerRef]
539
523
  );
540
- };
541
-
542
- // lib/components/ViewMenu.tsx
543
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
544
- var FONT_FAMILY = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
545
- var contentStyles = {
546
- backgroundColor: "#ffffff",
547
- color: "#111111",
548
- borderRadius: 8,
549
- boxShadow: "0 6px 24px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.08)",
550
- border: "1px solid #e5e7eb",
551
- padding: 4,
552
- minWidth: 224,
553
- fontSize: 13,
554
- fontFamily: FONT_FAMILY,
555
- outline: "none",
556
- zIndex: zIndexMap.viewMenu
557
- };
558
- var itemStyles = {
559
- display: "flex",
560
- alignItems: "center",
561
- gap: 8,
562
- padding: "7px 10px 7px 8px",
563
- borderRadius: 6,
564
- cursor: "pointer",
565
- outline: "none",
566
- userSelect: "none",
567
- color: "#111111",
568
- fontSize: 13,
569
- fontFamily: FONT_FAMILY
570
- };
571
- var iconSlotStyles = {
572
- width: 16,
573
- height: 16,
574
- flexShrink: 0,
575
- display: "inline-flex",
576
- alignItems: "center",
577
- justifyContent: "center",
578
- color: "#111111"
579
- };
580
- var separatorStyles = {
581
- height: 1,
582
- backgroundColor: "#ececec",
583
- margin: "4px 0"
584
- };
585
- var HIGHLIGHT_CSS = `
586
- .sv-vm-item[data-highlighted]:not([data-disabled]),
587
- .sv-vm-item:hover:not([data-disabled]) { background-color: #f1f3f5; }
588
- .sv-vm-item[data-disabled] { opacity: 0.45; cursor: not-allowed; }
589
- `;
590
- var CheckIcon = () => /* @__PURE__ */ jsx2(
591
- "svg",
592
- {
593
- width: "14",
594
- height: "14",
595
- viewBox: "0 0 24 24",
596
- fill: "none",
597
- stroke: "currentColor",
598
- strokeWidth: "2.5",
599
- strokeLinecap: "round",
600
- strokeLinejoin: "round",
601
- "aria-hidden": "true",
602
- children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" })
603
- }
604
- );
605
- var ViewMenu = ({
606
- circuitJson,
607
- circuitJsonKey,
608
- open,
609
- onOpenChange,
610
- showGroups,
611
- onToggleGroups,
612
- showGrid,
613
- onToggleGrid
614
- }) => {
615
- const hasGroups = useMemo(() => {
616
- if (!circuitJson || circuitJson.length === 0) return false;
617
- try {
618
- const sourceGroups = su3(circuitJson).source_group?.list() || [];
619
- if (sourceGroups.length > 0) return true;
620
- const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
621
- if (schematicComponents.length > 1) {
622
- const componentTypes = /* @__PURE__ */ new Set();
623
- for (const comp of schematicComponents) {
624
- const sourceComp = su3(circuitJson).source_component.get(
625
- comp.source_component_id
626
- );
627
- if (sourceComp?.ftype) {
628
- componentTypes.add(sourceComp.ftype);
629
- }
630
- }
631
- return componentTypes.size > 1;
524
+ const handleTouchMove = useCallback2(
525
+ (event) => {
526
+ const origin = interactionOriginRef.current;
527
+ if (!origin || event.touches.length !== 1) return;
528
+ const touch = event.touches[0];
529
+ const movedTooFar = !touch || Math.abs(touch.clientX - origin.x) > MOVEMENT_THRESHOLD_PX || Math.abs(touch.clientY - origin.y) > MOVEMENT_THRESHOLD_PX;
530
+ if (movedTooFar) {
531
+ interactionOriginRef.current = null;
532
+ clearLongPressTimeout();
632
533
  }
633
- return false;
634
- } catch (error) {
635
- console.error("Error checking for groups:", error);
636
- return false;
534
+ },
535
+ [clearLongPressTimeout]
536
+ );
537
+ const handleTouchEnd = useCallback2(() => {
538
+ clearLongPressTimeout();
539
+ interactionOriginRef.current = null;
540
+ }, [clearLongPressTimeout]);
541
+ const handleClickAway = useCallback2((event) => {
542
+ const target = event.target;
543
+ if (menuRef.current?.contains(target)) return;
544
+ const isInRadixPortal = target.closest?.(
545
+ "[data-radix-popper-content-wrapper], [data-radix-dropdown-menu-content]"
546
+ );
547
+ if (isInRadixPortal) return;
548
+ setMenuVisible(false);
549
+ }, []);
550
+ useEffect4(() => {
551
+ if (!menuVisible) return;
552
+ document.addEventListener("mousedown", handleClickAway);
553
+ document.addEventListener("touchstart", handleClickAway);
554
+ return () => {
555
+ document.removeEventListener("mousedown", handleClickAway);
556
+ document.removeEventListener("touchstart", handleClickAway);
557
+ };
558
+ }, [handleClickAway, menuVisible]);
559
+ useEffect4(() => clearLongPressTimeout, [clearLongPressTimeout]);
560
+ return {
561
+ menuVisible,
562
+ menuPos,
563
+ menuRef,
564
+ setMenuVisible,
565
+ contextMenuEventHandlers: {
566
+ onMouseDown: (event) => {
567
+ interactionOriginRef.current = event.button === 2 || event.button === 0 && event.ctrlKey ? { x: event.clientX, y: event.clientY } : null;
568
+ },
569
+ onContextMenu: handleContextMenu,
570
+ onTouchStart: handleTouchStart,
571
+ onTouchMove: handleTouchMove,
572
+ onTouchEnd: handleTouchEnd,
573
+ onTouchCancel: handleTouchEnd
637
574
  }
638
- }, [circuitJsonKey]);
639
- return /* @__PURE__ */ jsxs2(DropdownMenu.Root, { open, onOpenChange, modal: false, children: [
640
- /* @__PURE__ */ jsx2(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx2(ViewMenuIcon, { active: open }) }),
641
- /* @__PURE__ */ jsx2(DropdownMenu.Portal, { children: /* @__PURE__ */ jsxs2(
642
- DropdownMenu.Content,
643
- {
644
- style: contentStyles,
645
- side: "bottom",
646
- align: "end",
647
- sideOffset: 8,
648
- collisionPadding: 10,
649
- children: [
650
- /* @__PURE__ */ jsx2("style", { children: HIGHLIGHT_CSS }),
651
- /* @__PURE__ */ jsxs2(
652
- DropdownMenu.Item,
653
- {
654
- className: "sv-vm-item",
655
- style: itemStyles,
656
- disabled: !hasGroups,
657
- title: hasGroups ? void 0 : "No groups found in this schematic",
658
- onSelect: (e) => e.preventDefault(),
659
- onPointerUp: () => {
660
- if (hasGroups) onToggleGroups(!showGroups);
661
- },
662
- children: [
663
- /* @__PURE__ */ jsx2("span", { style: iconSlotStyles, children: showGroups && /* @__PURE__ */ jsx2(CheckIcon, {}) }),
664
- /* @__PURE__ */ jsx2("span", { children: "View Schematic Groups" })
665
- ]
666
- }
667
- ),
668
- /* @__PURE__ */ jsxs2(
669
- DropdownMenu.Item,
670
- {
671
- className: "sv-vm-item",
672
- style: itemStyles,
673
- onSelect: (e) => e.preventDefault(),
674
- onPointerUp: () => onToggleGrid(!showGrid),
675
- children: [
676
- /* @__PURE__ */ jsx2("span", { style: iconSlotStyles, children: showGrid && /* @__PURE__ */ jsx2(CheckIcon, {}) }),
677
- /* @__PURE__ */ jsx2("span", { children: "Show Grid" })
678
- ]
679
- }
680
- ),
681
- /* @__PURE__ */ jsx2(DropdownMenu.Separator, { style: separatorStyles }),
682
- /* @__PURE__ */ jsxs2(
683
- "div",
684
- {
685
- style: {
686
- padding: "4px 8px",
687
- fontSize: 12,
688
- color: "#9ca3af",
689
- textAlign: "center",
690
- fontFamily: FONT_FAMILY
691
- },
692
- children: [
693
- "v",
694
- String(package_default?.version)
695
- ]
696
- }
697
- )
698
- ]
699
- }
700
- ) })
701
- ] });
575
+ };
702
576
  };
703
577
 
704
- // lib/hooks/useLocalStorage.ts
705
- import { useCallback } from "react";
706
- var STORAGE_KEYS = {
707
- IS_SHOWING_SCHEMATIC_GROUPS: "schematic_viewer_show_groups",
708
- SELECTED_SCHEMATIC_SHEET: "schematic_viewer_selected_sheet"
578
+ // lib/utils/component-details.ts
579
+ import {
580
+ getPcbElementBounds,
581
+ getPcbElementsWithinBounds
582
+ } from "@tscircuit/circuit-json-util";
583
+ import { gzipSync, strToU8 } from "fflate";
584
+ var hiddenSourceComponentKeys = /* @__PURE__ */ new Set([
585
+ "type",
586
+ "source_component_id",
587
+ "source_group_id",
588
+ "subcircuit_id",
589
+ "name",
590
+ "display_name",
591
+ "ftype",
592
+ "are_pins_interchangeable"
593
+ ]);
594
+ var priorityKeys = [
595
+ "resistance",
596
+ "capacitance",
597
+ "inductance",
598
+ "frequency",
599
+ "voltage",
600
+ "current",
601
+ "max_voltage_rating",
602
+ "max_current_rating",
603
+ "power_rating",
604
+ "manufacturer_part_number",
605
+ "supplier_part_numbers"
606
+ ];
607
+ var getKeyPriority = (key) => {
608
+ const priority = priorityKeys.indexOf(key);
609
+ return priority === -1 ? priorityKeys.length : priority;
709
610
  };
710
- var getStoredBoolean = (key, defaultValue) => {
711
- if (typeof window === "undefined") return defaultValue;
712
- try {
713
- const stored = localStorage.getItem(key);
714
- return stored !== null ? JSON.parse(stored) : defaultValue;
715
- } catch {
716
- return defaultValue;
611
+ var humanizeComponentField = (field) => field.replace(/^simple_/, "").replaceAll("_", " ").replace(/\b\w/g, (character) => character.toUpperCase());
612
+ var formatObjectValue = (value) => Object.entries(value).map(([key, nestedValue]) => {
613
+ const formattedNestedValue = Array.isArray(nestedValue) ? nestedValue.join(", ") : String(nestedValue);
614
+ return `${humanizeComponentField(key)}: ${formattedNestedValue}`;
615
+ }).join(" \xB7 ");
616
+ var formatComponentValue = (sourceComponent, key, value) => {
617
+ const displayValue = sourceComponent[`display_${key}`];
618
+ if (typeof displayValue === "string" && displayValue.trim()) {
619
+ return displayValue;
717
620
  }
718
- };
719
- var setStoredBoolean = (key, value) => {
720
- if (typeof window === "undefined") return;
721
- try {
722
- localStorage.setItem(key, JSON.stringify(value));
723
- } catch {
621
+ if (typeof value === "boolean") return value ? "Yes" : "No";
622
+ if (Array.isArray(value)) return value.join(", ");
623
+ if (value && typeof value === "object") {
624
+ return formatObjectValue(value);
724
625
  }
626
+ return String(value);
725
627
  };
726
- var getStoredString = (key) => {
727
- if (typeof window === "undefined") return null;
728
- try {
729
- return localStorage.getItem(key);
730
- } catch {
731
- return null;
628
+ var getSourceComponentInfoEntries = (sourceComponent) => Object.entries(sourceComponent).filter(([key, value]) => {
629
+ if (hiddenSourceComponentKeys.has(key)) return false;
630
+ if (key.startsWith("display_") && key.slice("display_".length) in sourceComponent) {
631
+ return false;
732
632
  }
633
+ return value !== void 0 && value !== null && value !== "";
634
+ }).sort(([keyA], [keyB]) => {
635
+ const priorityDifference = getKeyPriority(keyA) - getKeyPriority(keyB);
636
+ return priorityDifference || keyA.localeCompare(keyB);
637
+ }).map(([key, value]) => ({
638
+ key,
639
+ label: humanizeComponentField(key),
640
+ value: formatComponentValue(sourceComponent, key, value)
641
+ }));
642
+ var getSchematicComponentDetails = (circuitJson, schematicComponentId) => {
643
+ const schematicComponent = circuitJson.find(
644
+ (element) => element.type === "schematic_component" && element.schematic_component_id === schematicComponentId
645
+ );
646
+ if (!schematicComponent) return null;
647
+ const sourceComponent = circuitJson.find(
648
+ (element) => element.type === "source_component" && element.source_component_id === schematicComponent.source_component_id
649
+ );
650
+ if (!sourceComponent) return null;
651
+ const pcbComponent = circuitJson.find(
652
+ (element) => element.type === "pcb_component" && element.source_component_id === sourceComponent.source_component_id
653
+ );
654
+ const cadComponent = circuitJson.find(
655
+ (element) => element.type === "cad_component" && element.source_component_id === sourceComponent.source_component_id && (!pcbComponent || element.pcb_component_id === pcbComponent.pcb_component_id)
656
+ );
657
+ const footprinterString = pcbComponent?.footprinter_string ?? cadComponent?.footprinter_string;
658
+ const footprintPreview = pcbComponent ? getPcbComponentPreview(circuitJson, pcbComponent.pcb_component_id) : null;
659
+ return {
660
+ schematicComponent,
661
+ sourceComponent,
662
+ pcbComponent,
663
+ footprinterString,
664
+ footprintPreviewCircuitJson: footprintPreview?.circuitJson,
665
+ footprintPreviewViewBox: footprintPreview?.viewBox
666
+ };
733
667
  };
734
- var setStoredString = (key, value) => {
735
- if (typeof window === "undefined") return;
736
- try {
737
- localStorage.setItem(key, value);
738
- } catch {
668
+ var PCB_PREVIEW_PADDING_MM = 2;
669
+ var getPcbComponentPreview = (circuitJson, pcbComponentId) => {
670
+ const pcbComponent = circuitJson.find(
671
+ (element) => element.type === "pcb_component" && element.pcb_component_id === pcbComponentId
672
+ );
673
+ if (!pcbComponent) return null;
674
+ const componentBounds = getPcbElementBounds(pcbComponent);
675
+ if (!componentBounds) return null;
676
+ const viewBox = {
677
+ minX: componentBounds.minX - PCB_PREVIEW_PADDING_MM,
678
+ minY: componentBounds.minY - PCB_PREVIEW_PADDING_MM,
679
+ maxX: componentBounds.maxX + PCB_PREVIEW_PADDING_MM,
680
+ maxY: componentBounds.maxY + PCB_PREVIEW_PADDING_MM
681
+ };
682
+ return {
683
+ circuitJson: getPcbElementsWithinBounds(circuitJson, viewBox),
684
+ viewBox
685
+ };
686
+ };
687
+ var bytesToBase64 = (bytes) => {
688
+ let binary = "";
689
+ const chunkSize = 8192;
690
+ for (let index = 0; index < bytes.length; index += chunkSize) {
691
+ binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
739
692
  }
693
+ return btoa(binary);
694
+ };
695
+ var getFootprintPreviewUrl = (footprintPreviewCircuitJson, viewBox) => {
696
+ const encodedCircuitJson = bytesToBase64(
697
+ gzipSync(strToU8(JSON.stringify(footprintPreviewCircuitJson)))
698
+ );
699
+ const url = new URL("https://svg.tscircuit.com/");
700
+ url.searchParams.set("svg_type", "pcb");
701
+ url.searchParams.set("circuit_json", encodedCircuitJson);
702
+ url.searchParams.set(
703
+ "viewbox",
704
+ [viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY].join(",")
705
+ );
706
+ url.searchParams.set("background_color", "#f8fafc");
707
+ return url.toString();
708
+ };
709
+
710
+ // lib/utils/z-index-map.ts
711
+ var zIndexMap = {
712
+ contextMenu: 110,
713
+ viewMenu: 55,
714
+ viewMenuIcon: 48,
715
+ clickToInteractOverlay: 100,
716
+ schematicComponentDetailsTooltip: 105,
717
+ schematicComponentHoverOutline: 47,
718
+ schematicPortHoverOutline: 48
740
719
  };
741
720
 
742
721
  // lib/components/MouseTracker.tsx
743
722
  import {
744
723
  createContext,
745
- useCallback as useCallback2,
724
+ useCallback as useCallback3,
746
725
  useContext,
747
- useEffect as useEffect4,
748
- useMemo as useMemo2,
749
- useRef
726
+ useEffect as useEffect5,
727
+ useMemo,
728
+ useRef as useRef2
750
729
  } from "react";
751
- import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
730
+ import { Fragment, jsx } from "react/jsx-runtime";
752
731
  var MouseTrackerContext = createContext(null);
753
732
  var DRAG_THRESHOLD_PX = 5;
754
733
  var boundsAreEqual = (a, b) => {
@@ -759,24 +738,24 @@ var boundsAreEqual = (a, b) => {
759
738
  var MouseTracker = ({ children }) => {
760
739
  const existingContext = useContext(MouseTrackerContext);
761
740
  if (existingContext) {
762
- return /* @__PURE__ */ jsx3(Fragment, { children });
741
+ return /* @__PURE__ */ jsx(Fragment, { children });
763
742
  }
764
- return /* @__PURE__ */ jsx3(MouseTrackerProvider, { children });
743
+ return /* @__PURE__ */ jsx(MouseTrackerProvider, { children });
765
744
  };
766
745
  var MouseTrackerProvider = ({ children }) => {
767
- const storeRef = useRef({
746
+ const storeRef = useRef2({
768
747
  pointer: null,
769
748
  boundingBoxes: /* @__PURE__ */ new Map(),
770
749
  hoveringIds: /* @__PURE__ */ new Set(),
771
750
  subscribers: /* @__PURE__ */ new Set(),
772
751
  mouseDownPosition: null
773
752
  });
774
- const notifySubscribers = useCallback2(() => {
753
+ const notifySubscribers = useCallback3(() => {
775
754
  for (const callback of storeRef.current.subscribers) {
776
755
  callback();
777
756
  }
778
757
  }, []);
779
- const updateHovering = useCallback2(() => {
758
+ const updateHovering = useCallback3(() => {
780
759
  const pointer = storeRef.current.pointer;
781
760
  const newHovering = /* @__PURE__ */ new Set();
782
761
  if (pointer) {
@@ -795,14 +774,14 @@ var MouseTrackerProvider = ({ children }) => {
795
774
  storeRef.current.hoveringIds = newHovering;
796
775
  notifySubscribers();
797
776
  }, [notifySubscribers]);
798
- const registerBoundingBox = useCallback2(
777
+ const registerBoundingBox = useCallback3(
799
778
  (id, registration) => {
800
779
  storeRef.current.boundingBoxes.set(id, registration);
801
780
  updateHovering();
802
781
  },
803
782
  [updateHovering]
804
783
  );
805
- const updateBoundingBox = useCallback2(
784
+ const updateBoundingBox = useCallback3(
806
785
  (id, registration) => {
807
786
  const existing = storeRef.current.boundingBoxes.get(id);
808
787
  if (existing && boundsAreEqual(existing.bounds, registration.bounds) && existing.onClick === registration.onClick) {
@@ -813,7 +792,7 @@ var MouseTrackerProvider = ({ children }) => {
813
792
  },
814
793
  [updateHovering]
815
794
  );
816
- const unregisterBoundingBox = useCallback2(
795
+ const unregisterBoundingBox = useCallback3(
817
796
  (id) => {
818
797
  const removed = storeRef.current.boundingBoxes.delete(id);
819
798
  if (removed) {
@@ -822,16 +801,16 @@ var MouseTrackerProvider = ({ children }) => {
822
801
  },
823
802
  [updateHovering]
824
803
  );
825
- const subscribe = useCallback2((listener) => {
804
+ const subscribe = useCallback3((listener) => {
826
805
  storeRef.current.subscribers.add(listener);
827
806
  return () => {
828
807
  storeRef.current.subscribers.delete(listener);
829
808
  };
830
809
  }, []);
831
- const isHovering = useCallback2((id) => {
810
+ const isHovering = useCallback3((id) => {
832
811
  return storeRef.current.hoveringIds.has(id);
833
812
  }, []);
834
- useEffect4(() => {
813
+ useEffect5(() => {
835
814
  const handlePointerPosition = (event) => {
836
815
  const { clientX, clientY } = event;
837
816
  const pointer = storeRef.current.pointer;
@@ -899,7 +878,7 @@ var MouseTrackerProvider = ({ children }) => {
899
878
  window.removeEventListener("click", handleClick);
900
879
  };
901
880
  }, [updateHovering]);
902
- const value = useMemo2(
881
+ const value = useMemo(
903
882
  () => ({
904
883
  registerBoundingBox,
905
884
  updateBoundingBox,
@@ -915,19 +894,222 @@ var MouseTrackerProvider = ({ children }) => {
915
894
  isHovering
916
895
  ]
917
896
  );
918
- return /* @__PURE__ */ jsx3(MouseTrackerContext.Provider, { value, children });
897
+ return /* @__PURE__ */ jsx(MouseTrackerContext.Provider, { value, children });
898
+ };
899
+
900
+ // lib/components/SchematicComponentDetailsTooltip.tsx
901
+ import { useMemo as useMemo2 } from "react";
902
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
903
+ var detailLabelStyle = {
904
+ color: "#64748b",
905
+ fontSize: "11px",
906
+ fontWeight: 700,
907
+ letterSpacing: "0.04em",
908
+ textTransform: "uppercase"
909
+ };
910
+ var SchematicComponentDetailsTooltip = ({
911
+ sourceComponent,
912
+ footprinterString,
913
+ footprintPreviewCircuitJson,
914
+ footprintPreviewViewBox,
915
+ left,
916
+ top,
917
+ width,
918
+ maxHeight,
919
+ onClose
920
+ }) => {
921
+ const infoEntries = useMemo2(
922
+ () => getSourceComponentInfoEntries(sourceComponent),
923
+ [sourceComponent]
924
+ );
925
+ const footprintPreviewUrl = useMemo2(
926
+ () => footprintPreviewCircuitJson?.length && footprintPreviewViewBox ? getFootprintPreviewUrl(
927
+ footprintPreviewCircuitJson,
928
+ footprintPreviewViewBox
929
+ ) : void 0,
930
+ [footprintPreviewCircuitJson, footprintPreviewViewBox]
931
+ );
932
+ const componentType = sourceComponent.ftype ? humanizeComponentField(sourceComponent.ftype) : "Component";
933
+ return /* @__PURE__ */ jsxs(
934
+ "dialog",
935
+ {
936
+ open: true,
937
+ "aria-label": `${sourceComponent.name} component details`,
938
+ "data-schematic-component-details-tooltip": true,
939
+ style: {
940
+ position: "absolute",
941
+ left,
942
+ right: "auto",
943
+ top,
944
+ bottom: "auto",
945
+ width,
946
+ maxHeight,
947
+ margin: 0,
948
+ padding: 0,
949
+ overflowY: "auto",
950
+ boxSizing: "border-box",
951
+ border: "1px solid #cbd5e1",
952
+ borderRadius: "12px",
953
+ backgroundColor: "rgba(255, 255, 255, 0.98)",
954
+ boxShadow: "0 20px 25px -5px rgba(15, 23, 42, 0.18), 0 8px 10px -6px rgba(15, 23, 42, 0.12)",
955
+ color: "#0f172a",
956
+ fontFamily: 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
957
+ pointerEvents: "auto",
958
+ userSelect: "text",
959
+ WebkitUserSelect: "text",
960
+ zIndex: zIndexMap.schematicComponentDetailsTooltip
961
+ },
962
+ onMouseDown: (event) => event.stopPropagation(),
963
+ onClick: (event) => event.stopPropagation(),
964
+ children: [
965
+ /* @__PURE__ */ jsxs(
966
+ "div",
967
+ {
968
+ style: {
969
+ display: "flex",
970
+ alignItems: "flex-start",
971
+ justifyContent: "space-between",
972
+ gap: "16px",
973
+ padding: "18px 18px 14px",
974
+ borderBottom: "1px solid #e2e8f0"
975
+ },
976
+ children: [
977
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
978
+ /* @__PURE__ */ jsx2(
979
+ "div",
980
+ {
981
+ style: {
982
+ fontSize: "20px",
983
+ fontWeight: 750,
984
+ lineHeight: 1.2,
985
+ overflowWrap: "anywhere"
986
+ },
987
+ children: sourceComponent.display_name ?? sourceComponent.name
988
+ }
989
+ ),
990
+ /* @__PURE__ */ jsx2("div", { style: { color: "#64748b", fontSize: "13px", marginTop: 4 }, children: componentType })
991
+ ] }),
992
+ /* @__PURE__ */ jsx2(
993
+ "button",
994
+ {
995
+ type: "button",
996
+ "aria-label": "Close component details",
997
+ onClick: onClose,
998
+ style: {
999
+ display: "grid",
1000
+ placeItems: "center",
1001
+ flex: "0 0 auto",
1002
+ width: "30px",
1003
+ height: "30px",
1004
+ padding: 0,
1005
+ border: "1px solid #e2e8f0",
1006
+ borderRadius: "8px",
1007
+ background: "#f8fafc",
1008
+ color: "#475569",
1009
+ cursor: "pointer",
1010
+ fontSize: "20px",
1011
+ lineHeight: 1
1012
+ },
1013
+ children: "\xD7"
1014
+ }
1015
+ )
1016
+ ]
1017
+ }
1018
+ ),
1019
+ infoEntries.length > 0 && /* @__PURE__ */ jsx2(
1020
+ "dl",
1021
+ {
1022
+ style: {
1023
+ display: "grid",
1024
+ gridTemplateColumns: "minmax(110px, 0.8fr) minmax(0, 1.2fr)",
1025
+ gap: "10px 18px",
1026
+ margin: 0,
1027
+ padding: "16px 18px",
1028
+ borderBottom: footprinterString ? "1px solid #e2e8f0" : void 0
1029
+ },
1030
+ children: infoEntries.map((entry) => /* @__PURE__ */ jsxs("div", { style: { display: "contents" }, children: [
1031
+ /* @__PURE__ */ jsx2("dt", { style: detailLabelStyle, children: entry.label }),
1032
+ /* @__PURE__ */ jsx2(
1033
+ "dd",
1034
+ {
1035
+ style: {
1036
+ minWidth: 0,
1037
+ margin: 0,
1038
+ color: "#1e293b",
1039
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
1040
+ fontSize: "12px",
1041
+ lineHeight: 1.45,
1042
+ overflowWrap: "anywhere"
1043
+ },
1044
+ children: entry.value
1045
+ }
1046
+ )
1047
+ ] }, entry.key))
1048
+ }
1049
+ ),
1050
+ footprinterString && /* @__PURE__ */ jsxs("div", { style: { padding: "16px 18px 18px" }, children: [
1051
+ /* @__PURE__ */ jsx2("div", { style: { ...detailLabelStyle, marginBottom: 7 }, children: "Footprint" }),
1052
+ /* @__PURE__ */ jsx2(
1053
+ "code",
1054
+ {
1055
+ style: {
1056
+ display: "block",
1057
+ marginBottom: "12px",
1058
+ padding: "8px 10px",
1059
+ borderRadius: "7px",
1060
+ background: "#f1f5f9",
1061
+ color: "#334155",
1062
+ fontSize: "12px",
1063
+ lineHeight: 1.4,
1064
+ overflowWrap: "anywhere",
1065
+ whiteSpace: "pre-wrap"
1066
+ },
1067
+ children: footprinterString
1068
+ }
1069
+ ),
1070
+ footprintPreviewUrl && /* @__PURE__ */ jsx2(
1071
+ "div",
1072
+ {
1073
+ style: {
1074
+ height: "210px",
1075
+ overflow: "hidden",
1076
+ border: "1px solid #e2e8f0",
1077
+ borderRadius: "9px",
1078
+ background: "#f8fafc"
1079
+ },
1080
+ children: /* @__PURE__ */ jsx2(
1081
+ "img",
1082
+ {
1083
+ src: footprintPreviewUrl,
1084
+ alt: `${sourceComponent.name} ${footprinterString} PCB footprint`,
1085
+ loading: "lazy",
1086
+ referrerPolicy: "no-referrer",
1087
+ style: {
1088
+ display: "block",
1089
+ width: "100%",
1090
+ height: "100%",
1091
+ objectFit: "contain"
1092
+ }
1093
+ }
1094
+ )
1095
+ }
1096
+ )
1097
+ ] })
1098
+ ]
1099
+ }
1100
+ );
919
1101
  };
920
1102
 
921
1103
  // lib/components/SchematicComponentMouseTarget.tsx
922
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef3, useState as useState2 } from "react";
1104
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef4, useState as useState3 } from "react";
923
1105
 
924
1106
  // lib/hooks/useMouseEventsOverBoundingBox.ts
925
1107
  import {
926
1108
  useContext as useContext2,
927
- useEffect as useEffect5,
1109
+ useEffect as useEffect6,
928
1110
  useId,
929
1111
  useMemo as useMemo3,
930
- useRef as useRef2,
1112
+ useRef as useRef3,
931
1113
  useSyncExternalStore
932
1114
  } from "react";
933
1115
  var useMouseEventsOverBoundingBox = (options) => {
@@ -938,7 +1120,7 @@ var useMouseEventsOverBoundingBox = (options) => {
938
1120
  );
939
1121
  }
940
1122
  const id = useId();
941
- const latestOptionsRef = useRef2(options);
1123
+ const latestOptionsRef = useRef3(options);
942
1124
  latestOptionsRef.current = options;
943
1125
  const handleClick = useMemo3(
944
1126
  () => (event) => {
@@ -946,7 +1128,7 @@ var useMouseEventsOverBoundingBox = (options) => {
946
1128
  },
947
1129
  []
948
1130
  );
949
- useEffect5(() => {
1131
+ useEffect6(() => {
950
1132
  context.registerBoundingBox(id, {
951
1133
  bounds: latestOptionsRef.current.bounds,
952
1134
  onClick: latestOptionsRef.current.onClick ? handleClick : void 0
@@ -955,7 +1137,7 @@ var useMouseEventsOverBoundingBox = (options) => {
955
1137
  context.unregisterBoundingBox(id);
956
1138
  };
957
1139
  }, [context, handleClick, id]);
958
- useEffect5(() => {
1140
+ useEffect6(() => {
959
1141
  context.updateBoundingBox(id, {
960
1142
  bounds: latestOptionsRef.current.bounds,
961
1143
  onClick: latestOptionsRef.current.onClick ? handleClick : void 0
@@ -979,7 +1161,7 @@ var useMouseEventsOverBoundingBox = (options) => {
979
1161
  };
980
1162
 
981
1163
  // lib/components/SchematicComponentMouseTarget.tsx
982
- import { jsx as jsx4 } from "react/jsx-runtime";
1164
+ import { jsx as jsx3 } from "react/jsx-runtime";
983
1165
  var areMeasurementsEqual = (a, b) => {
984
1166
  if (!a && !b) return true;
985
1167
  if (!a || !b) return false;
@@ -994,9 +1176,9 @@ var SchematicComponentMouseTarget = ({
994
1176
  showOutline,
995
1177
  circuitJsonKey
996
1178
  }) => {
997
- const [measurement, setMeasurement] = useState2(null);
998
- const frameRef = useRef3(null);
999
- const measure = useCallback3(() => {
1179
+ const [measurement, setMeasurement] = useState3(null);
1180
+ const frameRef = useRef4(null);
1181
+ const measure = useCallback4(() => {
1000
1182
  frameRef.current = null;
1001
1183
  const svgDiv = svgDivRef.current;
1002
1184
  const container = containerRef.current;
@@ -1031,14 +1213,14 @@ var SchematicComponentMouseTarget = ({
1031
1213
  (prev) => areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement
1032
1214
  );
1033
1215
  }, [componentId, containerRef, svgDivRef]);
1034
- const scheduleMeasure = useCallback3(() => {
1216
+ const scheduleMeasure = useCallback4(() => {
1035
1217
  if (frameRef.current !== null) return;
1036
1218
  frameRef.current = window.requestAnimationFrame(measure);
1037
1219
  }, [measure]);
1038
- useEffect6(() => {
1220
+ useEffect7(() => {
1039
1221
  scheduleMeasure();
1040
1222
  }, [scheduleMeasure, circuitJsonKey]);
1041
- useEffect6(() => {
1223
+ useEffect7(() => {
1042
1224
  scheduleMeasure();
1043
1225
  const svgDiv = svgDivRef.current;
1044
1226
  const container = containerRef.current;
@@ -1070,7 +1252,7 @@ var SchematicComponentMouseTarget = ({
1070
1252
  }
1071
1253
  };
1072
1254
  }, [scheduleMeasure, svgDivRef, containerRef]);
1073
- const handleClick = useCallback3(
1255
+ const handleClick = useCallback4(
1074
1256
  (event) => {
1075
1257
  if (onComponentClick) {
1076
1258
  onComponentClick(componentId, event);
@@ -1083,7 +1265,7 @@ var SchematicComponentMouseTarget = ({
1083
1265
  bounds,
1084
1266
  onClick: onComponentClick ? handleClick : void 0
1085
1267
  });
1086
- useEffect6(() => {
1268
+ useEffect7(() => {
1087
1269
  if (onHoverChange) {
1088
1270
  onHoverChange(componentId, hovering);
1089
1271
  }
@@ -1092,7 +1274,7 @@ var SchematicComponentMouseTarget = ({
1092
1274
  return null;
1093
1275
  }
1094
1276
  const rect = measurement.rect;
1095
- return /* @__PURE__ */ jsx4(
1277
+ return /* @__PURE__ */ jsx3(
1096
1278
  "div",
1097
1279
  {
1098
1280
  style: {
@@ -1110,8 +1292,8 @@ var SchematicComponentMouseTarget = ({
1110
1292
  };
1111
1293
 
1112
1294
  // lib/components/SchematicPortMouseTarget.tsx
1113
- import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef4, useState as useState3 } from "react";
1114
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1295
+ import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef5, useState as useState4 } from "react";
1296
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1115
1297
  var areMeasurementsEqual2 = (a, b) => {
1116
1298
  if (!a && !b) return true;
1117
1299
  if (!a || !b) return false;
@@ -1127,9 +1309,9 @@ var SchematicPortMouseTarget = ({
1127
1309
  showOutline,
1128
1310
  circuitJsonKey
1129
1311
  }) => {
1130
- const [measurement, setMeasurement] = useState3(null);
1131
- const frameRef = useRef4(null);
1132
- const measure = useCallback4(() => {
1312
+ const [measurement, setMeasurement] = useState4(null);
1313
+ const frameRef = useRef5(null);
1314
+ const measure = useCallback5(() => {
1133
1315
  frameRef.current = null;
1134
1316
  const svgDiv = svgDivRef.current;
1135
1317
  const container = containerRef.current;
@@ -1165,14 +1347,14 @@ var SchematicPortMouseTarget = ({
1165
1347
  (prev) => areMeasurementsEqual2(prev, nextMeasurement) ? prev : nextMeasurement
1166
1348
  );
1167
1349
  }, [portId, containerRef, svgDivRef]);
1168
- const scheduleMeasure = useCallback4(() => {
1350
+ const scheduleMeasure = useCallback5(() => {
1169
1351
  if (frameRef.current !== null) return;
1170
1352
  frameRef.current = window.requestAnimationFrame(measure);
1171
1353
  }, [measure]);
1172
- useEffect7(() => {
1354
+ useEffect8(() => {
1173
1355
  scheduleMeasure();
1174
1356
  }, [scheduleMeasure, circuitJsonKey]);
1175
- useEffect7(() => {
1357
+ useEffect8(() => {
1176
1358
  scheduleMeasure();
1177
1359
  const svgDiv = svgDivRef.current;
1178
1360
  const container = containerRef.current;
@@ -1204,7 +1386,7 @@ var SchematicPortMouseTarget = ({
1204
1386
  }
1205
1387
  };
1206
1388
  }, [scheduleMeasure, svgDivRef, containerRef]);
1207
- const handleClick = useCallback4(
1389
+ const handleClick = useCallback5(
1208
1390
  (event) => {
1209
1391
  if (onPortClick) {
1210
1392
  onPortClick(portId, event);
@@ -1217,7 +1399,7 @@ var SchematicPortMouseTarget = ({
1217
1399
  bounds,
1218
1400
  onClick: onPortClick ? handleClick : void 0
1219
1401
  });
1220
- useEffect7(() => {
1402
+ useEffect8(() => {
1221
1403
  if (onHoverChange) {
1222
1404
  onHoverChange(portId, hovering);
1223
1405
  }
@@ -1226,8 +1408,8 @@ var SchematicPortMouseTarget = ({
1226
1408
  return null;
1227
1409
  }
1228
1410
  const rect = measurement.rect;
1229
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1230
- /* @__PURE__ */ jsx5(
1411
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1412
+ /* @__PURE__ */ jsx4(
1231
1413
  "div",
1232
1414
  {
1233
1415
  style: {
@@ -1245,7 +1427,7 @@ var SchematicPortMouseTarget = ({
1245
1427
  }
1246
1428
  }
1247
1429
  ),
1248
- hovering && portLabel && /* @__PURE__ */ jsx5(
1430
+ hovering && portLabel && /* @__PURE__ */ jsx4(
1249
1431
  "div",
1250
1432
  {
1251
1433
  style: {
@@ -1270,11 +1452,11 @@ var SchematicPortMouseTarget = ({
1270
1452
  };
1271
1453
 
1272
1454
  // lib/components/SchematicSheetSelector.tsx
1273
- import { useState as useState4 } from "react";
1274
- import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
1275
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1276
- var FONT_FAMILY2 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1277
- var contentStyles2 = {
1455
+ import { useState as useState5 } from "react";
1456
+ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
1457
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1458
+ var FONT_FAMILY = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1459
+ var contentStyles = {
1278
1460
  backgroundColor: "#ffffff",
1279
1461
  color: "#111111",
1280
1462
  borderRadius: 8,
@@ -1286,11 +1468,11 @@ var contentStyles2 = {
1286
1468
  maxHeight: 320,
1287
1469
  overflowY: "auto",
1288
1470
  fontSize: 13,
1289
- fontFamily: FONT_FAMILY2,
1471
+ fontFamily: FONT_FAMILY,
1290
1472
  outline: "none",
1291
1473
  zIndex: zIndexMap.viewMenu
1292
1474
  };
1293
- var itemStyles2 = {
1475
+ var itemStyles = {
1294
1476
  display: "flex",
1295
1477
  alignItems: "center",
1296
1478
  gap: 8,
@@ -1301,9 +1483,9 @@ var itemStyles2 = {
1301
1483
  userSelect: "none",
1302
1484
  color: "#111111",
1303
1485
  fontSize: 13,
1304
- fontFamily: FONT_FAMILY2
1486
+ fontFamily: FONT_FAMILY
1305
1487
  };
1306
- var iconSlotStyles2 = {
1488
+ var iconSlotStyles = {
1307
1489
  width: 16,
1308
1490
  height: 16,
1309
1491
  flexShrink: 0,
@@ -1322,7 +1504,7 @@ var MENU_CSS = `
1322
1504
  .sv-sheet-chevron { transition: transform 0.2s ease; }
1323
1505
  [data-state="open"] > .sv-sheet-chevron { transform: rotate(180deg); }
1324
1506
  `;
1325
- var CheckIcon2 = () => /* @__PURE__ */ jsx6(
1507
+ var CheckIcon = () => /* @__PURE__ */ jsx5(
1326
1508
  "svg",
1327
1509
  {
1328
1510
  width: "14",
@@ -1334,10 +1516,10 @@ var CheckIcon2 = () => /* @__PURE__ */ jsx6(
1334
1516
  strokeLinecap: "round",
1335
1517
  strokeLinejoin: "round",
1336
1518
  "aria-hidden": "true",
1337
- children: /* @__PURE__ */ jsx6("path", { d: "M20 6 9 17l-5-5" })
1519
+ children: /* @__PURE__ */ jsx5("path", { d: "M20 6 9 17l-5-5" })
1338
1520
  }
1339
1521
  );
1340
- var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsx6(
1522
+ var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsx5(
1341
1523
  "svg",
1342
1524
  {
1343
1525
  className,
@@ -1351,7 +1533,7 @@ var ChevronDownIcon = ({ className }) => /* @__PURE__ */ jsx6(
1351
1533
  strokeLinejoin: "round",
1352
1534
  style: { opacity: 0.6, flexShrink: 0 },
1353
1535
  "aria-hidden": "true",
1354
- children: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" })
1536
+ children: /* @__PURE__ */ jsx5("path", { d: "m6 9 6 6 6-6" })
1355
1537
  }
1356
1538
  );
1357
1539
  var SchematicSheetSelector = ({
@@ -1359,16 +1541,16 @@ var SchematicSheetSelector = ({
1359
1541
  selectedSheetId,
1360
1542
  onSelectSheet
1361
1543
  }) => {
1362
- const [open, setOpen] = useState4(false);
1544
+ const [open, setOpen] = useState5(false);
1363
1545
  if (sheets.length <= 1) return null;
1364
1546
  const selectedSheet = sheets.find(
1365
1547
  (s) => s.schematic_sheet_id === selectedSheetId
1366
1548
  );
1367
1549
  const selectedLabel = selectedSheet?.name ?? "Select sheet";
1368
- return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1369
- /* @__PURE__ */ jsx6("style", { children: MENU_CSS }),
1370
- /* @__PURE__ */ jsxs4(DropdownMenu2.Root, { open, onOpenChange: setOpen, modal: false, children: [
1371
- /* @__PURE__ */ jsx6(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs4(
1550
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1551
+ /* @__PURE__ */ jsx5("style", { children: MENU_CSS }),
1552
+ /* @__PURE__ */ jsxs3(DropdownMenu.Root, { open, onOpenChange: setOpen, modal: false, children: [
1553
+ /* @__PURE__ */ jsx5(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
1372
1554
  "button",
1373
1555
  {
1374
1556
  type: "button",
@@ -1391,31 +1573,31 @@ var SchematicSheetSelector = ({
1391
1573
  cursor: "pointer",
1392
1574
  boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
1393
1575
  fontSize: "13px",
1394
- fontFamily: FONT_FAMILY2,
1576
+ fontFamily: FONT_FAMILY,
1395
1577
  zIndex: zIndexMap.viewMenuIcon
1396
1578
  },
1397
1579
  children: [
1398
- /* @__PURE__ */ jsx6("span", { style: { color: "#888888", flexShrink: 0 }, children: "Sheet:" }),
1399
- /* @__PURE__ */ jsx6("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: selectedLabel }),
1400
- /* @__PURE__ */ jsx6(ChevronDownIcon, { className: "sv-sheet-chevron" })
1580
+ /* @__PURE__ */ jsx5("span", { style: { color: "#888888", flexShrink: 0 }, children: "Sheet:" }),
1581
+ /* @__PURE__ */ jsx5("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: selectedLabel }),
1582
+ /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "sv-sheet-chevron" })
1401
1583
  ]
1402
1584
  }
1403
1585
  ) }),
1404
- /* @__PURE__ */ jsx6(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx6(
1405
- DropdownMenu2.Content,
1586
+ /* @__PURE__ */ jsx5(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx5(
1587
+ DropdownMenu.Content,
1406
1588
  {
1407
- style: contentStyles2,
1589
+ style: contentStyles,
1408
1590
  side: "bottom",
1409
1591
  align: "start",
1410
1592
  sideOffset: 8,
1411
1593
  collisionPadding: 10,
1412
1594
  children: sheets.map((sheet) => {
1413
1595
  const selected = sheet.schematic_sheet_id === selectedSheetId;
1414
- return /* @__PURE__ */ jsxs4(
1415
- DropdownMenu2.Item,
1596
+ return /* @__PURE__ */ jsxs3(
1597
+ DropdownMenu.Item,
1416
1598
  {
1417
1599
  className: "sv-sheet-item",
1418
- style: itemStyles2,
1600
+ style: itemStyles,
1419
1601
  title: sheet.name,
1420
1602
  onSelect: (e) => e.preventDefault(),
1421
1603
  onPointerUp: () => {
@@ -1423,8 +1605,8 @@ var SchematicSheetSelector = ({
1423
1605
  setOpen(false);
1424
1606
  },
1425
1607
  children: [
1426
- /* @__PURE__ */ jsx6("span", { style: iconSlotStyles2, children: selected && /* @__PURE__ */ jsx6(CheckIcon2, {}) }),
1427
- /* @__PURE__ */ jsx6("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: sheet.name })
1608
+ /* @__PURE__ */ jsx5("span", { style: iconSlotStyles, children: selected && /* @__PURE__ */ jsx5(CheckIcon, {}) }),
1609
+ /* @__PURE__ */ jsx5("span", { style: { ...ellipsisStyles, minWidth: 0 }, children: sheet.name })
1428
1610
  ]
1429
1611
  },
1430
1612
  sheet.schematic_sheet_id
@@ -1436,6 +1618,275 @@ var SchematicSheetSelector = ({
1436
1618
  ] });
1437
1619
  };
1438
1620
 
1621
+ // lib/components/ViewMenu.tsx
1622
+ import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
1623
+ import { su as su3 } from "@tscircuit/soup-util";
1624
+ import { useMemo as useMemo4 } from "react";
1625
+
1626
+ // package.json
1627
+ var package_default = {
1628
+ name: "@tscircuit/schematic-viewer",
1629
+ version: "2.0.79",
1630
+ main: "dist/index.js",
1631
+ type: "module",
1632
+ scripts: {
1633
+ start: "cosmos",
1634
+ build: "tsup-node ./lib/index.ts --dts --format esm --sourcemap",
1635
+ "build:site": "cosmos-export",
1636
+ "vercel-build": "bun run build:site",
1637
+ format: "biome format --write .",
1638
+ "format:check": "biome format ."
1639
+ },
1640
+ files: [
1641
+ "dist"
1642
+ ],
1643
+ devDependencies: {
1644
+ "@biomejs/biome": "^1.9.4",
1645
+ "@types/bun": "latest",
1646
+ "@types/debug": "^4.1.12",
1647
+ "@types/jsdom": "^21.1.7",
1648
+ "@types/react": "^19.0.1",
1649
+ "@types/react-dom": "^19.0.2",
1650
+ "@vitejs/plugin-react": "^4.3.4",
1651
+ jsdom: "^26.0.0",
1652
+ react: "^19.1.0",
1653
+ "react-cosmos": "^6.2.1",
1654
+ "react-cosmos-plugin-vite": "^6.2.0",
1655
+ "react-dom": "^19.1.0",
1656
+ "react-reconciler": "^0.31.0",
1657
+ semver: "^7.7.2",
1658
+ tscircuit: "^0.0.2112",
1659
+ tsup: "^8.3.5",
1660
+ vite: "^6.0.3"
1661
+ },
1662
+ peerDependencies: {
1663
+ typescript: "^5.0.0",
1664
+ tscircuit: "*"
1665
+ },
1666
+ dependencies: {
1667
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
1668
+ "@tscircuit/circuit-json-util": "^0.0.108",
1669
+ "circuit-json": "^0.0.465",
1670
+ "circuit-to-svg": "^0.0.393",
1671
+ debug: "^4.4.0",
1672
+ fflate: "^0.8.3",
1673
+ "performance-now": "^2.1.0",
1674
+ "use-mouse-matrix-transform": "^1.2.2"
1675
+ }
1676
+ };
1677
+
1678
+ // lib/components/ViewMenu.tsx
1679
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1680
+ var FONT_FAMILY2 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1681
+ var contentStyles2 = {
1682
+ backgroundColor: "#262626",
1683
+ color: "#fafafa",
1684
+ borderRadius: 6,
1685
+ boxShadow: "0px 12px 48px -12px rgba(0, 0, 0, 0.5), 0px 8px 24px -8px rgba(0, 0, 0, 0.3)",
1686
+ border: "1px solid #333333",
1687
+ padding: 4,
1688
+ minWidth: 208,
1689
+ fontSize: 14,
1690
+ fontWeight: 400,
1691
+ fontFamily: FONT_FAMILY2,
1692
+ outline: "none",
1693
+ zIndex: zIndexMap.contextMenu
1694
+ };
1695
+ var itemStyles2 = {
1696
+ display: "flex",
1697
+ alignItems: "center",
1698
+ gap: 8,
1699
+ padding: "6px 8px",
1700
+ borderRadius: 6,
1701
+ cursor: "default",
1702
+ outline: "none",
1703
+ userSelect: "none",
1704
+ color: "#fafafa",
1705
+ fontSize: 14,
1706
+ fontWeight: 400,
1707
+ fontFamily: FONT_FAMILY2
1708
+ };
1709
+ var iconSlotStyles2 = {
1710
+ width: 16,
1711
+ height: 16,
1712
+ flexShrink: 0,
1713
+ display: "inline-flex",
1714
+ alignItems: "center",
1715
+ justifyContent: "center",
1716
+ color: "#fafafa"
1717
+ };
1718
+ var separatorStyles = {
1719
+ height: 1,
1720
+ backgroundColor: "#ffffff1a",
1721
+ margin: "4px 0"
1722
+ };
1723
+ var HIGHLIGHT_CSS = `
1724
+ .sv-vm-item[data-highlighted]:not([data-disabled]),
1725
+ .sv-vm-item:hover:not([data-disabled]) { background-color: #404040; }
1726
+ .sv-vm-item[data-disabled] { opacity: 0.45; cursor: not-allowed; }
1727
+ `;
1728
+ var CheckIcon2 = () => /* @__PURE__ */ jsx6(
1729
+ "svg",
1730
+ {
1731
+ width: "14",
1732
+ height: "14",
1733
+ viewBox: "0 0 24 24",
1734
+ fill: "none",
1735
+ stroke: "currentColor",
1736
+ strokeWidth: "2.5",
1737
+ strokeLinecap: "round",
1738
+ strokeLinejoin: "round",
1739
+ "aria-hidden": "true",
1740
+ children: /* @__PURE__ */ jsx6("path", { d: "M20 6 9 17l-5-5" })
1741
+ }
1742
+ );
1743
+ var ViewMenu = ({
1744
+ circuitJson,
1745
+ circuitJsonKey,
1746
+ menuRef,
1747
+ menuPos,
1748
+ onOpenChange,
1749
+ showGroups,
1750
+ onToggleGroups,
1751
+ showGrid,
1752
+ onToggleGrid,
1753
+ showPorts,
1754
+ onTogglePorts
1755
+ }) => {
1756
+ const hasGroups = useMemo4(() => {
1757
+ if (!circuitJson || circuitJson.length === 0) return false;
1758
+ try {
1759
+ const sourceGroups = su3(circuitJson).source_group?.list() || [];
1760
+ if (sourceGroups.length > 0) return true;
1761
+ const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
1762
+ if (schematicComponents.length > 1) {
1763
+ const componentTypes = /* @__PURE__ */ new Set();
1764
+ for (const comp of schematicComponents) {
1765
+ const sourceComp = su3(circuitJson).source_component.get(
1766
+ comp.source_component_id
1767
+ );
1768
+ if (sourceComp?.ftype) {
1769
+ componentTypes.add(sourceComp.ftype);
1770
+ }
1771
+ }
1772
+ return componentTypes.size > 1;
1773
+ }
1774
+ return false;
1775
+ } catch (error) {
1776
+ console.error("Error checking for groups:", error);
1777
+ return false;
1778
+ }
1779
+ }, [circuitJsonKey]);
1780
+ const hasPorts = useMemo4(() => {
1781
+ if (!circuitJson || circuitJson.length === 0) return false;
1782
+ try {
1783
+ return (su3(circuitJson).schematic_port?.list() || []).length > 0;
1784
+ } catch (error) {
1785
+ console.error("Error checking for schematic ports:", error);
1786
+ return false;
1787
+ }
1788
+ }, [circuitJsonKey]);
1789
+ return /* @__PURE__ */ jsx6(
1790
+ "div",
1791
+ {
1792
+ ref: menuRef,
1793
+ style: {
1794
+ position: "fixed",
1795
+ left: menuPos.x,
1796
+ top: menuPos.y,
1797
+ width: 0,
1798
+ height: 0
1799
+ },
1800
+ children: /* @__PURE__ */ jsxs4(DropdownMenu2.Root, { open: true, onOpenChange, modal: false, children: [
1801
+ /* @__PURE__ */ jsx6(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx6("div", { style: { position: "absolute", width: 1, height: 1 } }) }),
1802
+ /* @__PURE__ */ jsx6(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs4(
1803
+ DropdownMenu2.Content,
1804
+ {
1805
+ style: contentStyles2,
1806
+ align: "start",
1807
+ sideOffset: 0,
1808
+ collisionPadding: 10,
1809
+ avoidCollisions: true,
1810
+ children: [
1811
+ /* @__PURE__ */ jsx6("style", { children: HIGHLIGHT_CSS }),
1812
+ /* @__PURE__ */ jsxs4(
1813
+ DropdownMenu2.Item,
1814
+ {
1815
+ className: "sv-vm-item",
1816
+ style: itemStyles2,
1817
+ disabled: !hasPorts,
1818
+ title: hasPorts ? void 0 : "No ports found in this schematic",
1819
+ onSelect: (event) => event.preventDefault(),
1820
+ onPointerDown: (event) => {
1821
+ event.preventDefault();
1822
+ if (hasPorts) onTogglePorts(!showPorts);
1823
+ },
1824
+ children: [
1825
+ /* @__PURE__ */ jsx6("span", { style: iconSlotStyles2, children: showPorts && /* @__PURE__ */ jsx6(CheckIcon2, {}) }),
1826
+ /* @__PURE__ */ jsx6("span", { children: "Show Schematic Ports" })
1827
+ ]
1828
+ }
1829
+ ),
1830
+ /* @__PURE__ */ jsxs4(
1831
+ DropdownMenu2.Item,
1832
+ {
1833
+ className: "sv-vm-item",
1834
+ style: itemStyles2,
1835
+ disabled: !hasGroups,
1836
+ title: hasGroups ? void 0 : "No groups found in this schematic",
1837
+ onSelect: (event) => event.preventDefault(),
1838
+ onPointerDown: (event) => {
1839
+ event.preventDefault();
1840
+ if (hasGroups) onToggleGroups(!showGroups);
1841
+ },
1842
+ children: [
1843
+ /* @__PURE__ */ jsx6("span", { style: iconSlotStyles2, children: showGroups && /* @__PURE__ */ jsx6(CheckIcon2, {}) }),
1844
+ /* @__PURE__ */ jsx6("span", { children: "View Schematic Groups" })
1845
+ ]
1846
+ }
1847
+ ),
1848
+ /* @__PURE__ */ jsxs4(
1849
+ DropdownMenu2.Item,
1850
+ {
1851
+ className: "sv-vm-item",
1852
+ style: itemStyles2,
1853
+ onSelect: (event) => event.preventDefault(),
1854
+ onPointerDown: (event) => {
1855
+ event.preventDefault();
1856
+ onToggleGrid(!showGrid);
1857
+ },
1858
+ children: [
1859
+ /* @__PURE__ */ jsx6("span", { style: iconSlotStyles2, children: showGrid && /* @__PURE__ */ jsx6(CheckIcon2, {}) }),
1860
+ /* @__PURE__ */ jsx6("span", { children: "Show Grid" })
1861
+ ]
1862
+ }
1863
+ ),
1864
+ /* @__PURE__ */ jsx6(DropdownMenu2.Separator, { style: separatorStyles }),
1865
+ /* @__PURE__ */ jsxs4(
1866
+ "div",
1867
+ {
1868
+ style: {
1869
+ padding: "4px 8px 4px 32px",
1870
+ fontSize: 11,
1871
+ opacity: 0.35,
1872
+ color: "#a1a1aa",
1873
+ letterSpacing: "0.2px",
1874
+ fontFamily: FONT_FAMILY2
1875
+ },
1876
+ children: [
1877
+ "@tscircuit/schematic-viewer@",
1878
+ String(package_default?.version)
1879
+ ]
1880
+ }
1881
+ )
1882
+ ]
1883
+ }
1884
+ ) })
1885
+ ] })
1886
+ }
1887
+ );
1888
+ };
1889
+
1439
1890
  // lib/components/SchematicViewer.tsx
1440
1891
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1441
1892
  var SchematicViewer = ({
@@ -1448,7 +1899,7 @@ var SchematicViewer = ({
1448
1899
  disableGroups = false,
1449
1900
  netHoverHighlightEnabled = true,
1450
1901
  onSchematicComponentClicked,
1451
- showSchematicPorts = false,
1902
+ showSchematicPorts,
1452
1903
  onSchematicPortClicked,
1453
1904
  onSchematicSheetChange,
1454
1905
  css,
@@ -1460,11 +1911,11 @@ var SchematicViewer = ({
1460
1911
  const getCircuitHash = (circuitJson2) => {
1461
1912
  return `${circuitJson2?.length || 0}_${circuitJson2?.editCount || 0}`;
1462
1913
  };
1463
- const circuitJsonKey = useMemo4(
1914
+ const circuitJsonKey = useMemo5(
1464
1915
  () => getCircuitHash(circuitJson),
1465
1916
  [circuitJson]
1466
1917
  );
1467
- const schematicSheets = useMemo4(() => {
1918
+ const schematicSheets = useMemo5(() => {
1468
1919
  try {
1469
1920
  return circuitJson.filter((elm) => elm?.type === "schematic_sheet").slice().sort((a, b) => (a.sheet_index ?? 0) - (b.sheet_index ?? 0));
1470
1921
  } catch (err) {
@@ -1474,7 +1925,7 @@ var SchematicViewer = ({
1474
1925
  }, [circuitJsonKey]);
1475
1926
  const hasMultipleSheets = schematicSheets.length > 1;
1476
1927
  const defaultSheetId = schematicSheets[0]?.schematic_sheet_id;
1477
- const [selectedSheetId, setSelectedSheetId] = useState5(
1928
+ const [selectedSheetId, setSelectedSheetId] = useState6(
1478
1929
  () => {
1479
1930
  const stored = getStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET);
1480
1931
  if (stored && schematicSheets.some((s) => s.schematic_sheet_id === stored)) {
@@ -1483,14 +1934,14 @@ var SchematicViewer = ({
1483
1934
  return defaultSheetId;
1484
1935
  }
1485
1936
  );
1486
- useEffect8(() => {
1937
+ useEffect9(() => {
1487
1938
  const stillExists = selectedSheetId !== void 0 && schematicSheets.some((s) => s.schematic_sheet_id === selectedSheetId);
1488
1939
  if (!stillExists) {
1489
1940
  setSelectedSheetId(defaultSheetId);
1490
1941
  }
1491
1942
  }, [circuitJsonKey]);
1492
1943
  const activeSheetId = hasMultipleSheets ? selectedSheetId ?? defaultSheetId : void 0;
1493
- const handleSelectSheet = useCallback5(
1944
+ const handleSelectSheet = useCallback6(
1494
1945
  (sheetId) => {
1495
1946
  setSelectedSheetId(sheetId);
1496
1947
  setStoredString(STORAGE_KEYS.SELECTED_SCHEMATIC_SHEET, sheetId);
@@ -1498,19 +1949,27 @@ var SchematicViewer = ({
1498
1949
  },
1499
1950
  [onSchematicSheetChange]
1500
1951
  );
1501
- const [showGridInternal, setShowGridInternal] = useState5(false);
1952
+ const [showGridInternal, setShowGridInternal] = useState6(false);
1502
1953
  const showGrid = debugGrid || showGridInternal;
1503
- const [isInteractionEnabled, setIsInteractionEnabled] = useState5(
1954
+ const [isInteractionEnabled, setIsInteractionEnabled] = useState6(
1504
1955
  !clickToInteractEnabled
1505
1956
  );
1506
- const [showViewMenu, setShowViewMenu] = useState5(false);
1507
- const [showSchematicGroups, setShowSchematicGroups] = useState5(() => {
1957
+ const [showSchematicGroups, setShowSchematicGroups] = useState6(() => {
1508
1958
  if (disableGroups) return false;
1509
- return getStoredBoolean("schematic_viewer_show_groups", false);
1959
+ return getStoredBoolean(STORAGE_KEYS.IS_SHOWING_SCHEMATIC_GROUPS, false);
1510
1960
  });
1511
- const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState5(false);
1512
- const hoveringComponentsRef = useRef5(/* @__PURE__ */ new Set());
1513
- const handleComponentHoverChange = useCallback5(
1961
+ const [showSchematicPortsInternal, setShowSchematicPortsInternal] = useState6(
1962
+ () => showSchematicPorts ?? getStoredBoolean(STORAGE_KEYS.IS_SHOWING_SCHEMATIC_PORTS, false)
1963
+ );
1964
+ useEffect9(() => {
1965
+ if (showSchematicPorts !== void 0) {
1966
+ setShowSchematicPortsInternal(showSchematicPorts);
1967
+ }
1968
+ }, [showSchematicPorts]);
1969
+ const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState6(false);
1970
+ const hoveringComponentsRef = useRef6(/* @__PURE__ */ new Set());
1971
+ const [selectedSchematicComponent, setSelectedSchematicComponent] = useState6(null);
1972
+ const handleComponentHoverChange = useCallback6(
1514
1973
  (componentId, isHovering) => {
1515
1974
  if (isHovering) {
1516
1975
  hoveringComponentsRef.current.add(componentId);
@@ -1521,9 +1980,9 @@ var SchematicViewer = ({
1521
1980
  },
1522
1981
  []
1523
1982
  );
1524
- const [isHoveringClickablePort, setIsHoveringClickablePort] = useState5(false);
1525
- const hoveringPortsRef = useRef5(/* @__PURE__ */ new Set());
1526
- const handlePortHoverChange = useCallback5(
1983
+ const [isHoveringClickablePort, setIsHoveringClickablePort] = useState6(false);
1984
+ const hoveringPortsRef = useRef6(/* @__PURE__ */ new Set());
1985
+ const handlePortHoverChange = useCallback6(
1527
1986
  (portId, isHovering) => {
1528
1987
  if (isHovering) {
1529
1988
  hoveringPortsRef.current.add(portId);
@@ -1534,9 +1993,9 @@ var SchematicViewer = ({
1534
1993
  },
1535
1994
  []
1536
1995
  );
1537
- const svgDivRef = useRef5(null);
1538
- const touchStartRef = useRef5(null);
1539
- const schematicComponentIds = useMemo4(() => {
1996
+ const svgDivRef = useRef6(null);
1997
+ const touchStartRef = useRef6(null);
1998
+ const schematicComponentIds = useMemo5(() => {
1540
1999
  try {
1541
2000
  const components = su4(circuitJson).schematic_component?.list() ?? [];
1542
2001
  return components.filter(
@@ -1547,8 +2006,8 @@ var SchematicViewer = ({
1547
2006
  return [];
1548
2007
  }
1549
2008
  }, [circuitJsonKey, circuitJson, activeSheetId]);
1550
- const schematicPortsInfo = useMemo4(() => {
1551
- if (!showSchematicPorts) return [];
2009
+ const schematicPortsInfo = useMemo5(() => {
2010
+ if (!showSchematicPortsInternal) return [];
1552
2011
  try {
1553
2012
  const ports = (su4(circuitJson).schematic_port?.list() ?? []).filter(
1554
2013
  (port) => !activeSheetId || port.schematic_sheet_id === activeSheetId
@@ -1567,7 +2026,7 @@ var SchematicViewer = ({
1567
2026
  console.error("Failed to derive schematic port info", err);
1568
2027
  return [];
1569
2028
  }
1570
- }, [circuitJsonKey, circuitJson, showSchematicPorts, activeSheetId]);
2029
+ }, [circuitJsonKey, circuitJson, showSchematicPortsInternal, activeSheetId]);
1571
2030
  const handleTouchStart = (e) => {
1572
2031
  const touch = e.touches[0];
1573
2032
  touchStartRef.current = {
@@ -1587,21 +2046,106 @@ var SchematicViewer = ({
1587
2046
  }
1588
2047
  touchStartRef.current = null;
1589
2048
  };
2049
+ const shouldPanSchematic = useCallback6(
2050
+ (event) => {
2051
+ if (event.type !== "mousedown" || !("button" in event)) return true;
2052
+ return event.button !== 2 && !(event.button === 0 && event.ctrlKey);
2053
+ },
2054
+ []
2055
+ );
1590
2056
  const { ref: containerRef } = useMouseMatrixTransform({
1591
2057
  onSetTransform(transform) {
1592
2058
  if (!svgDivRef.current) return;
1593
2059
  svgDivRef.current.style.transform = transformToString(transform);
1594
2060
  },
1595
2061
  // @ts-ignore disabled is a valid prop but not typed
1596
- enabled: isInteractionEnabled
2062
+ enabled: isInteractionEnabled,
2063
+ shouldDrag: shouldPanSchematic
1597
2064
  });
2065
+ const {
2066
+ menuVisible,
2067
+ menuPos,
2068
+ menuRef,
2069
+ setMenuVisible,
2070
+ contextMenuEventHandlers
2071
+ } = useContextMenu({ containerRef });
1598
2072
  const { containerWidth, containerHeight } = useResizeHandling(containerRef);
1599
- const svgString = useMemo4(() => {
2073
+ const selectedComponentDetails = useMemo5(
2074
+ () => selectedSchematicComponent ? getSchematicComponentDetails(
2075
+ circuitJson,
2076
+ selectedSchematicComponent.schematicComponentId
2077
+ ) : null,
2078
+ [circuitJsonKey, circuitJson, selectedSchematicComponent]
2079
+ );
2080
+ const componentTooltipLayout = useMemo5(() => {
2081
+ if (!selectedSchematicComponent || !containerWidth || !containerHeight) {
2082
+ return null;
2083
+ }
2084
+ const margin = 12;
2085
+ const gap = 14;
2086
+ const width = Math.min(420, containerWidth - margin * 2);
2087
+ const maxHeight = Math.min(520, containerHeight - margin * 2);
2088
+ const { anchorX, anchorY } = selectedSchematicComponent;
2089
+ const rightOfAnchor = anchorX + gap;
2090
+ const leftOfAnchor = anchorX - width - gap;
2091
+ const left = rightOfAnchor + width <= containerWidth - margin ? rightOfAnchor : leftOfAnchor >= margin ? leftOfAnchor : Math.max(
2092
+ margin,
2093
+ Math.min(anchorX - width / 2, containerWidth - width - margin)
2094
+ );
2095
+ const top = Math.max(
2096
+ margin,
2097
+ Math.min(anchorY - 24, containerHeight - maxHeight - margin)
2098
+ );
2099
+ return { left, top, width, maxHeight };
2100
+ }, [selectedSchematicComponent, containerWidth, containerHeight]);
2101
+ const handleSchematicComponentClick = useCallback6(
2102
+ (schematicComponentId, event) => {
2103
+ if (event.target instanceof Element && event.target.closest("[data-schematic-component-details-tooltip]")) {
2104
+ return;
2105
+ }
2106
+ const containerRect = containerRef.current?.getBoundingClientRect();
2107
+ if (!containerRect) return;
2108
+ setSelectedSchematicComponent({
2109
+ schematicComponentId,
2110
+ anchorX: event.clientX - containerRect.left,
2111
+ anchorY: event.clientY - containerRect.top
2112
+ });
2113
+ onSchematicComponentClicked?.({
2114
+ schematicComponentId,
2115
+ event
2116
+ });
2117
+ },
2118
+ [containerRef, onSchematicComponentClicked]
2119
+ );
2120
+ useEffect9(() => {
2121
+ setSelectedSchematicComponent(null);
2122
+ }, [circuitJsonKey, activeSheetId]);
2123
+ useEffect9(() => {
2124
+ if (!selectedSchematicComponent) return;
2125
+ const handleKeyDown = (event) => {
2126
+ if (event.key === "Escape") {
2127
+ setSelectedSchematicComponent(null);
2128
+ }
2129
+ };
2130
+ const handleDocumentMouseDown = (event) => {
2131
+ if (event.target instanceof Element && event.target.closest("[data-schematic-component-details-tooltip]")) {
2132
+ return;
2133
+ }
2134
+ setSelectedSchematicComponent(null);
2135
+ };
2136
+ window.addEventListener("keydown", handleKeyDown);
2137
+ document.addEventListener("mousedown", handleDocumentMouseDown);
2138
+ return () => {
2139
+ window.removeEventListener("keydown", handleKeyDown);
2140
+ document.removeEventListener("mousedown", handleDocumentMouseDown);
2141
+ };
2142
+ }, [selectedSchematicComponent]);
2143
+ const svgString = useMemo5(() => {
1600
2144
  if (!containerWidth || !containerHeight) return "";
1601
2145
  return convertCircuitJsonToSchematicSvg(circuitJson, {
1602
2146
  width: containerWidth,
1603
2147
  height: containerHeight || 720,
1604
- drawPorts: showSchematicPorts,
2148
+ drawPorts: showSchematicPortsInternal,
1605
2149
  schematicSheetId: activeSheetId,
1606
2150
  grid: !showGrid ? void 0 : {
1607
2151
  cellSize: 1,
@@ -1616,10 +2160,10 @@ var SchematicViewer = ({
1616
2160
  containerWidth,
1617
2161
  containerHeight,
1618
2162
  showGrid,
1619
- showSchematicPorts,
2163
+ showSchematicPortsInternal,
1620
2164
  activeSheetId
1621
2165
  ]);
1622
- const containerBackgroundColor = useMemo4(() => {
2166
+ const containerBackgroundColor = useMemo5(() => {
1623
2167
  const match = svgString.match(
1624
2168
  /<svg[^>]*style="[^"]*background-color:\s*([^;\"]+)/i
1625
2169
  );
@@ -1637,7 +2181,7 @@ var SchematicViewer = ({
1637
2181
  circuitJsonKey: `${circuitJsonKey}_${activeSheetId ?? ""}`,
1638
2182
  enabled: netHoverHighlightEnabled
1639
2183
  });
1640
- const svgDiv = useMemo4(
2184
+ const svgDiv = useMemo5(
1641
2185
  () => /* @__PURE__ */ jsx7(
1642
2186
  "div",
1643
2187
  {
@@ -1646,7 +2190,7 @@ var SchematicViewer = ({
1646
2190
  pointerEvents: clickToInteractEnabled ? isInteractionEnabled ? "auto" : "none" : "auto",
1647
2191
  transformOrigin: "0 0"
1648
2192
  },
1649
- className: onSchematicComponentClicked ? "schematic-component-clickable" : void 0,
2193
+ className: "schematic-component-clickable",
1650
2194
  dangerouslySetInnerHTML: { __html: svgString }
1651
2195
  }
1652
2196
  ),
@@ -1655,8 +2199,8 @@ var SchematicViewer = ({
1655
2199
  return /* @__PURE__ */ jsxs5(MouseTracker, { children: [
1656
2200
  netHoverHighlightEnabled && /* @__PURE__ */ jsx7("style", { children: `.sch-net-faded { opacity: 0.35; }
1657
2201
  svg :is(g.trace, g.trace-overlays, g[data-schematic-component-id], [data-schematic-net-label-id]) { transition: opacity 0.12s ease-in-out; }` }),
1658
- onSchematicComponentClicked && /* @__PURE__ */ jsx7("style", { children: `.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }` }),
1659
- onSchematicPortClicked && /* @__PURE__ */ jsx7("style", { children: `[data-schematic-port-id]:hover { cursor: pointer !important; }` }),
2202
+ /* @__PURE__ */ jsx7("style", { children: ".schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }" }),
2203
+ onSchematicPortClicked && /* @__PURE__ */ jsx7("style", { children: "[data-schematic-port-id]:hover { cursor: pointer !important; }" }),
1660
2204
  /* @__PURE__ */ jsxs5(
1661
2205
  "div",
1662
2206
  {
@@ -1665,19 +2209,32 @@ var SchematicViewer = ({
1665
2209
  position: "relative",
1666
2210
  backgroundColor: containerBackgroundColor,
1667
2211
  overflow: "hidden",
1668
- cursor: clickToInteractEnabled && !isInteractionEnabled ? "pointer" : isHoveringClickableComponent && onSchematicComponentClicked ? "pointer" : isHoveringClickablePort && onSchematicPortClicked ? "pointer" : "grab",
2212
+ cursor: clickToInteractEnabled && !isInteractionEnabled ? "pointer" : isHoveringClickableComponent ? "pointer" : isHoveringClickablePort && onSchematicPortClicked ? "pointer" : "grab",
1669
2213
  minHeight: "300px",
2214
+ userSelect: "none",
2215
+ WebkitUserSelect: "none",
2216
+ WebkitTouchCallout: "none",
1670
2217
  ...containerStyle
1671
2218
  },
1672
2219
  onMouseDownCapture: (e) => {
2220
+ contextMenuEventHandlers.onMouseDown(e);
1673
2221
  if (clickToInteractEnabled && !isInteractionEnabled) {
1674
2222
  e.preventDefault();
1675
2223
  e.stopPropagation();
1676
2224
  return;
1677
2225
  }
1678
2226
  },
1679
- onTouchStart: handleTouchStart,
1680
- onTouchEnd: handleTouchEnd,
2227
+ onContextMenu: contextMenuEventHandlers.onContextMenu,
2228
+ onTouchStart: (event) => {
2229
+ handleTouchStart(event);
2230
+ contextMenuEventHandlers.onTouchStart(event);
2231
+ },
2232
+ onTouchMove: contextMenuEventHandlers.onTouchMove,
2233
+ onTouchEnd: (event) => {
2234
+ handleTouchEnd(event);
2235
+ contextMenuEventHandlers.onTouchEnd();
2236
+ },
2237
+ onTouchCancel: contextMenuEventHandlers.onTouchCancel,
1681
2238
  children: [
1682
2239
  !isInteractionEnabled && clickToInteractEnabled && /* @__PURE__ */ jsx7(
1683
2240
  "div",
@@ -1715,18 +2272,27 @@ var SchematicViewer = ({
1715
2272
  )
1716
2273
  }
1717
2274
  ),
1718
- /* @__PURE__ */ jsx7(
2275
+ menuVisible && /* @__PURE__ */ jsx7(
1719
2276
  ViewMenu,
1720
2277
  {
1721
2278
  circuitJson,
1722
2279
  circuitJsonKey,
1723
- open: showViewMenu,
1724
- onOpenChange: setShowViewMenu,
2280
+ menuRef,
2281
+ menuPos,
2282
+ onOpenChange: setMenuVisible,
2283
+ showPorts: showSchematicPortsInternal,
2284
+ onTogglePorts: (value) => {
2285
+ setShowSchematicPortsInternal(value);
2286
+ setStoredBoolean(STORAGE_KEYS.IS_SHOWING_SCHEMATIC_PORTS, value);
2287
+ },
1725
2288
  showGroups: showSchematicGroups,
1726
2289
  onToggleGroups: (value) => {
1727
2290
  if (!disableGroups) {
1728
2291
  setShowSchematicGroups(value);
1729
- setStoredBoolean("schematic_viewer_show_groups", value);
2292
+ setStoredBoolean(
2293
+ STORAGE_KEYS.IS_SHOWING_SCHEMATIC_GROUPS,
2294
+ value
2295
+ );
1730
2296
  }
1731
2297
  },
1732
2298
  showGrid,
@@ -1741,7 +2307,7 @@ var SchematicViewer = ({
1741
2307
  onSelectSheet: handleSelectSheet
1742
2308
  }
1743
2309
  ),
1744
- onSchematicComponentClicked && schematicComponentIds.map((componentId) => /* @__PURE__ */ jsx7(
2310
+ schematicComponentIds.map((componentId) => /* @__PURE__ */ jsx7(
1745
2311
  SchematicComponentMouseTarget,
1746
2312
  {
1747
2313
  componentId,
@@ -1750,17 +2316,23 @@ var SchematicViewer = ({
1750
2316
  showOutline: true,
1751
2317
  circuitJsonKey,
1752
2318
  onHoverChange: handleComponentHoverChange,
1753
- onComponentClick: (id, event) => {
1754
- onSchematicComponentClicked?.({
1755
- schematicComponentId: id,
1756
- event
1757
- });
1758
- }
2319
+ onComponentClick: handleSchematicComponentClick
1759
2320
  },
1760
2321
  componentId
1761
2322
  )),
1762
2323
  svgDiv,
1763
- showSchematicPorts && schematicPortsInfo.map(({ portId, label }) => /* @__PURE__ */ jsx7(
2324
+ selectedComponentDetails && componentTooltipLayout && /* @__PURE__ */ jsx7(
2325
+ SchematicComponentDetailsTooltip,
2326
+ {
2327
+ sourceComponent: selectedComponentDetails.sourceComponent,
2328
+ footprinterString: selectedComponentDetails.footprinterString,
2329
+ footprintPreviewCircuitJson: selectedComponentDetails.footprintPreviewCircuitJson,
2330
+ footprintPreviewViewBox: selectedComponentDetails.footprintPreviewViewBox,
2331
+ ...componentTooltipLayout,
2332
+ onClose: () => setSelectedSchematicComponent(null)
2333
+ }
2334
+ ),
2335
+ showSchematicPortsInternal && schematicPortsInfo.map(({ portId, label }) => /* @__PURE__ */ jsx7(
1764
2336
  SchematicPortMouseTarget,
1765
2337
  {
1766
2338
  portId,
@@ -1790,7 +2362,7 @@ import {
1790
2362
  convertCircuitJsonToSchematicSimulationSvg,
1791
2363
  convertCircuitJsonToSimulationGraphSvg
1792
2364
  } from "circuit-to-svg";
1793
- import { useEffect as useEffect9, useMemo as useMemo5, useRef as useRef6, useState as useState7 } from "react";
2365
+ import { useEffect as useEffect10, useMemo as useMemo6, useRef as useRef7, useState as useState8 } from "react";
1794
2366
  import { toString as transformToString2 } from "transformation-matrix";
1795
2367
  import { useMouseMatrixTransform as useMouseMatrixTransform2 } from "use-mouse-matrix-transform";
1796
2368
 
@@ -1808,7 +2380,7 @@ var getAnalogSimulationBackgroundColor = (simulationSvg, colorOverrides) => getR
1808
2380
 
1809
2381
  // lib/components/AnalogSimulationSelector.tsx
1810
2382
  import * as DropdownMenu3 from "@radix-ui/react-dropdown-menu";
1811
- import { useState as useState6 } from "react";
2383
+ import { useState as useState7 } from "react";
1812
2384
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1813
2385
  var FONT_FAMILY3 = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1814
2386
  var contentStyles3 = {
@@ -1907,7 +2479,7 @@ var AnalogSimulationSelector = ({
1907
2479
  selectedSimulationExperimentId,
1908
2480
  onSelectSimulation
1909
2481
  }) => {
1910
- const [open, setOpen] = useState6(false);
2482
+ const [open, setOpen] = useState7(false);
1911
2483
  if (simulations.length <= 1) return null;
1912
2484
  const simulationLabels = getSimulationLabels(simulations);
1913
2485
  const selectedSimulation = simulationLabels.find(
@@ -2001,16 +2573,16 @@ var AnalogSimulationViewer = ({
2001
2573
  onSimulationChange,
2002
2574
  acSweepView = "magnitude"
2003
2575
  }) => {
2004
- const [circuitJson, setCircuitJson] = useState7(null);
2005
- const [isLoading, setIsLoading] = useState7(true);
2006
- const [error, setError] = useState7(null);
2007
- const [svgObjectUrl, setSvgObjectUrl] = useState7(null);
2008
- const containerRef = useRef6(null);
2009
- const imgRef = useRef6(null);
2576
+ const [circuitJson, setCircuitJson] = useState8(null);
2577
+ const [isLoading, setIsLoading] = useState8(true);
2578
+ const [error, setError] = useState8(null);
2579
+ const [svgObjectUrl, setSvgObjectUrl] = useState8(null);
2580
+ const containerRef = useRef7(null);
2581
+ const imgRef = useRef7(null);
2010
2582
  const { containerWidth } = useResizeHandling(
2011
2583
  containerRef
2012
2584
  );
2013
- const [isDragging, setIsDragging] = useState7(false);
2585
+ const [isDragging, setIsDragging] = useState8(false);
2014
2586
  const {
2015
2587
  ref: transformRef,
2016
2588
  cancelDrag: _cancelDrag,
@@ -2026,23 +2598,23 @@ var AnalogSimulationViewer = ({
2026
2598
  const renderAspectRatio = width && height ? width / height : defaultRenderAspectRatio;
2027
2599
  const effectiveWidth = width || (height ? height * renderAspectRatio : containerWidth) || DEFAULT_RENDER_WIDTH;
2028
2600
  const effectiveHeight = height || effectiveWidth / renderAspectRatio;
2029
- useEffect9(() => {
2601
+ useEffect10(() => {
2030
2602
  setIsLoading(true);
2031
2603
  setError(null);
2032
2604
  setCircuitJson(inputCircuitJson);
2033
2605
  setIsLoading(false);
2034
2606
  }, [inputCircuitJson]);
2035
- const simulationExperiments = useMemo5(() => {
2607
+ const simulationExperiments = useMemo6(() => {
2036
2608
  if (!circuitJson) return [];
2037
2609
  return circuitJson.filter(
2038
2610
  (element) => element.type === "simulation_experiment"
2039
2611
  );
2040
2612
  }, [circuitJson]);
2041
- const [selectedSimulationExperimentId, setSelectedSimulationExperimentId] = useState7(null);
2613
+ const [selectedSimulationExperimentId, setSelectedSimulationExperimentId] = useState8(null);
2042
2614
  const simulationExperimentId = (selectedSimulationExperimentId && simulationExperiments.some(
2043
2615
  (simulation) => simulation.simulation_experiment_id === selectedSimulationExperimentId
2044
2616
  ) ? selectedSimulationExperimentId : simulationExperiments[0]?.simulation_experiment_id) ?? null;
2045
- useEffect9(() => {
2617
+ useEffect10(() => {
2046
2618
  if (simulationExperimentId !== selectedSimulationExperimentId) {
2047
2619
  setSelectedSimulationExperimentId(simulationExperimentId);
2048
2620
  }
@@ -2051,7 +2623,7 @@ var AnalogSimulationViewer = ({
2051
2623
  setSelectedSimulationExperimentId(nextSimulationExperimentId);
2052
2624
  onSimulationChange?.(nextSimulationExperimentId);
2053
2625
  };
2054
- const simulationSvg = useMemo5(() => {
2626
+ const simulationSvg = useMemo6(() => {
2055
2627
  if (!circuitJson || !effectiveWidth || !effectiveHeight || !simulationExperimentId)
2056
2628
  return "";
2057
2629
  try {
@@ -2079,7 +2651,7 @@ var AnalogSimulationViewer = ({
2079
2651
  simulationExperimentId,
2080
2652
  acSweepView
2081
2653
  ]);
2082
- useEffect9(() => {
2654
+ useEffect10(() => {
2083
2655
  if (!simulationSvg) {
2084
2656
  setSvgObjectUrl(null);
2085
2657
  return;
@@ -2096,7 +2668,7 @@ var AnalogSimulationViewer = ({
2096
2668
  setSvgObjectUrl(null);
2097
2669
  }
2098
2670
  }, [simulationSvg]);
2099
- const containerBackgroundColor = useMemo5(() => {
2671
+ const containerBackgroundColor = useMemo6(() => {
2100
2672
  return getAnalogSimulationBackgroundColor(simulationSvg, colorOverrides);
2101
2673
  }, [simulationSvg, colorOverrides]);
2102
2674
  const handleMouseDown = (_e) => {
@@ -2105,7 +2677,7 @@ var AnalogSimulationViewer = ({
2105
2677
  const handleTouchStart = (_e) => {
2106
2678
  setIsDragging(true);
2107
2679
  };
2108
- useEffect9(() => {
2680
+ useEffect10(() => {
2109
2681
  const handleMouseUp = () => {
2110
2682
  setIsDragging(false);
2111
2683
  };