@tscircuit/schematic-viewer 2.0.54 → 2.0.56
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/CLAUDE.md +1 -0
- package/bun.lockb +0 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +281 -65
- package/dist/index.js.map +1 -1
- package/examples/example17-schematic-ports.fixture.tsx +49 -0
- package/examples/example18-live-toggle.fixture.tsx +60 -0
- package/lib/components/SchematicPortMouseTarget.tsx +224 -0
- package/lib/components/SchematicViewer.tsx +84 -3
- package/lib/utils/z-index-map.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SchematicViewer } from "lib/components/SchematicViewer"
|
|
2
|
+
import { renderToCircuitJson } from "lib/dev/render-to-circuit-json"
|
|
3
|
+
|
|
4
|
+
const circuitJson = renderToCircuitJson(
|
|
5
|
+
<board width="10mm" height="10mm" routingDisabled>
|
|
6
|
+
<resistor name="R1" resistance={1000} schX={-2} />
|
|
7
|
+
<capacitor name="C1" capacitance="1uF" schX={2} schY={2} />
|
|
8
|
+
<capacitor
|
|
9
|
+
name="C2"
|
|
10
|
+
schRotation={90}
|
|
11
|
+
capacitance="1uF"
|
|
12
|
+
schX={0}
|
|
13
|
+
schY={-4}
|
|
14
|
+
/>
|
|
15
|
+
<chip
|
|
16
|
+
name="U1"
|
|
17
|
+
pinLabels={{
|
|
18
|
+
pin1: "D0",
|
|
19
|
+
pin2: "D1",
|
|
20
|
+
pin3: "D2",
|
|
21
|
+
pin4: "GND",
|
|
22
|
+
pin5: "D3",
|
|
23
|
+
pin6: "EN",
|
|
24
|
+
pin7: "D4",
|
|
25
|
+
pin8: "VCC",
|
|
26
|
+
}}
|
|
27
|
+
footprint="soic8"
|
|
28
|
+
schX={0}
|
|
29
|
+
schY={-1.5}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
<trace from=".R1 .pin2" to=".C1 .pin1" />
|
|
33
|
+
<trace from=".C1 .pin2" to=".U1 .pin4" />
|
|
34
|
+
<trace from=".U1 .pin8" to=".C2 .pin1" />
|
|
35
|
+
<trace from=".C2 .pin2" to=".R1 .pin1" />
|
|
36
|
+
<trace from=".U1 .pin1" to=".U1 .pin5" />
|
|
37
|
+
</board>,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export default () => (
|
|
41
|
+
<SchematicViewer
|
|
42
|
+
circuitJson={circuitJson}
|
|
43
|
+
containerStyle={{ height: "100%" }}
|
|
44
|
+
showSchematicPorts
|
|
45
|
+
onSchematicPortClicked={({ schematicPortId }) => {
|
|
46
|
+
console.log("Port clicked:", schematicPortId)
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { SchematicViewer } from "lib/components/SchematicViewer"
|
|
3
|
+
import { renderToCircuitJson } from "lib/dev/render-to-circuit-json"
|
|
4
|
+
|
|
5
|
+
export default () => {
|
|
6
|
+
const [showDebugGrid, setShowDebugGrid] = useState(true)
|
|
7
|
+
const [showPorts, setShowPorts] = useState(false)
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
|
11
|
+
<div style={{ padding: 10, display: "flex", gap: 10 }}>
|
|
12
|
+
<button onClick={() => setShowDebugGrid(!showDebugGrid)}>
|
|
13
|
+
Debug Grid: {String(showDebugGrid)}
|
|
14
|
+
</button>
|
|
15
|
+
<button onClick={() => setShowPorts(!showPorts)}>
|
|
16
|
+
Schematic Ports: {String(showPorts)}
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
<SchematicViewer
|
|
20
|
+
circuitJson={renderToCircuitJson(
|
|
21
|
+
<board width="10mm" height="10mm" routingDisabled>
|
|
22
|
+
<resistor name="R1" resistance={1000} schX={-2} />
|
|
23
|
+
<capacitor name="C1" capacitance="1uF" schX={2} schY={2} />
|
|
24
|
+
<capacitor
|
|
25
|
+
name="C2"
|
|
26
|
+
schRotation={90}
|
|
27
|
+
capacitance="1uF"
|
|
28
|
+
schX={0}
|
|
29
|
+
schY={-4}
|
|
30
|
+
/>
|
|
31
|
+
<chip
|
|
32
|
+
name="U1"
|
|
33
|
+
pinLabels={{
|
|
34
|
+
pin1: "D0",
|
|
35
|
+
pin2: "D1",
|
|
36
|
+
pin3: "D2",
|
|
37
|
+
pin4: "GND",
|
|
38
|
+
pin5: "D3",
|
|
39
|
+
pin6: "EN",
|
|
40
|
+
pin7: "D4",
|
|
41
|
+
pin8: "VCC",
|
|
42
|
+
}}
|
|
43
|
+
footprint="soic8"
|
|
44
|
+
schX={0}
|
|
45
|
+
schY={-1.5}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<trace from=".R1 .pin2" to=".C1 .pin1" />
|
|
49
|
+
<trace from=".C1 .pin2" to=".U1 .pin4" />
|
|
50
|
+
<trace from=".U1 .pin8" to=".C2 .pin1" />
|
|
51
|
+
<trace from=".C2 .pin2" to=".R1 .pin1" />
|
|
52
|
+
<trace from=".U1 .pin1" to=".U1 .pin5" />
|
|
53
|
+
</board>,
|
|
54
|
+
)}
|
|
55
|
+
debugGrid={showDebugGrid}
|
|
56
|
+
showSchematicPorts={showPorts}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
2
|
+
import { useMouseEventsOverBoundingBox } from "../hooks/useMouseEventsOverBoundingBox"
|
|
3
|
+
import type { BoundingBoxBounds } from "./MouseTracker"
|
|
4
|
+
import { zIndexMap } from "../utils/z-index-map"
|
|
5
|
+
|
|
6
|
+
interface RelativeRect {
|
|
7
|
+
left: number
|
|
8
|
+
top: number
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Measurement {
|
|
14
|
+
bounds: BoundingBoxBounds
|
|
15
|
+
rect: RelativeRect
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const areMeasurementsEqual = (a: Measurement | null, b: Measurement | null) => {
|
|
19
|
+
if (!a && !b) return true
|
|
20
|
+
if (!a || !b) return false
|
|
21
|
+
return (
|
|
22
|
+
Math.abs(a.bounds.minX - b.bounds.minX) < 0.5 &&
|
|
23
|
+
Math.abs(a.bounds.maxX - b.bounds.maxX) < 0.5 &&
|
|
24
|
+
Math.abs(a.bounds.minY - b.bounds.minY) < 0.5 &&
|
|
25
|
+
Math.abs(a.bounds.maxY - b.bounds.maxY) < 0.5 &&
|
|
26
|
+
Math.abs(a.rect.left - b.rect.left) < 0.5 &&
|
|
27
|
+
Math.abs(a.rect.top - b.rect.top) < 0.5 &&
|
|
28
|
+
Math.abs(a.rect.width - b.rect.width) < 0.5 &&
|
|
29
|
+
Math.abs(a.rect.height - b.rect.height) < 0.5
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Props {
|
|
34
|
+
portId: string
|
|
35
|
+
portLabel?: string
|
|
36
|
+
svgDivRef: React.RefObject<HTMLDivElement | null>
|
|
37
|
+
containerRef: React.RefObject<HTMLDivElement | null>
|
|
38
|
+
onPortClick?: (portId: string, event: MouseEvent) => void
|
|
39
|
+
onHoverChange?: (portId: string, isHovering: boolean) => void
|
|
40
|
+
showOutline: boolean
|
|
41
|
+
circuitJsonKey: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const SchematicPortMouseTarget = ({
|
|
45
|
+
portId,
|
|
46
|
+
portLabel,
|
|
47
|
+
svgDivRef,
|
|
48
|
+
containerRef,
|
|
49
|
+
onPortClick,
|
|
50
|
+
onHoverChange,
|
|
51
|
+
showOutline,
|
|
52
|
+
circuitJsonKey,
|
|
53
|
+
}: Props) => {
|
|
54
|
+
const [measurement, setMeasurement] = useState<Measurement | null>(null)
|
|
55
|
+
const frameRef = useRef<number | null>(null)
|
|
56
|
+
|
|
57
|
+
const measure = useCallback(() => {
|
|
58
|
+
frameRef.current = null
|
|
59
|
+
const svgDiv = svgDivRef.current
|
|
60
|
+
const container = containerRef.current
|
|
61
|
+
if (!svgDiv || !container) {
|
|
62
|
+
setMeasurement((prev) => (prev ? null : prev))
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
const element = svgDiv.querySelector<SVGGraphicsElement | HTMLElement>(
|
|
66
|
+
`[data-schematic-port-id="${portId}"]`,
|
|
67
|
+
)
|
|
68
|
+
if (!element) {
|
|
69
|
+
setMeasurement((prev) => (prev ? null : prev))
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const elementRect = element.getBoundingClientRect()
|
|
74
|
+
const containerRect = container.getBoundingClientRect()
|
|
75
|
+
|
|
76
|
+
// Add some padding around the port for easier interaction
|
|
77
|
+
const padding = 4
|
|
78
|
+
|
|
79
|
+
const nextMeasurement: Measurement = {
|
|
80
|
+
bounds: {
|
|
81
|
+
minX: elementRect.left - padding,
|
|
82
|
+
maxX: elementRect.right + padding,
|
|
83
|
+
minY: elementRect.top - padding,
|
|
84
|
+
maxY: elementRect.bottom + padding,
|
|
85
|
+
},
|
|
86
|
+
rect: {
|
|
87
|
+
left: elementRect.left - containerRect.left - padding,
|
|
88
|
+
top: elementRect.top - containerRect.top - padding,
|
|
89
|
+
width: elementRect.width + padding * 2,
|
|
90
|
+
height: elementRect.height + padding * 2,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setMeasurement((prev) =>
|
|
95
|
+
areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement,
|
|
96
|
+
)
|
|
97
|
+
}, [portId, containerRef, svgDivRef])
|
|
98
|
+
|
|
99
|
+
const scheduleMeasure = useCallback(() => {
|
|
100
|
+
if (frameRef.current !== null) return
|
|
101
|
+
frameRef.current = window.requestAnimationFrame(measure)
|
|
102
|
+
}, [measure])
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
scheduleMeasure()
|
|
106
|
+
}, [scheduleMeasure, circuitJsonKey])
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
scheduleMeasure()
|
|
110
|
+
const svgDiv = svgDivRef.current
|
|
111
|
+
const container = containerRef.current
|
|
112
|
+
if (!svgDiv || !container) return
|
|
113
|
+
|
|
114
|
+
const resizeObserver =
|
|
115
|
+
typeof ResizeObserver !== "undefined"
|
|
116
|
+
? new ResizeObserver(() => {
|
|
117
|
+
scheduleMeasure()
|
|
118
|
+
})
|
|
119
|
+
: null
|
|
120
|
+
resizeObserver?.observe(container)
|
|
121
|
+
resizeObserver?.observe(svgDiv)
|
|
122
|
+
|
|
123
|
+
const mutationObserver =
|
|
124
|
+
typeof MutationObserver !== "undefined"
|
|
125
|
+
? new MutationObserver(() => {
|
|
126
|
+
scheduleMeasure()
|
|
127
|
+
})
|
|
128
|
+
: null
|
|
129
|
+
mutationObserver?.observe(svgDiv, {
|
|
130
|
+
attributes: true,
|
|
131
|
+
attributeFilter: ["style", "transform"],
|
|
132
|
+
subtree: true,
|
|
133
|
+
childList: true,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
window.addEventListener("scroll", scheduleMeasure, true)
|
|
137
|
+
window.addEventListener("resize", scheduleMeasure)
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
resizeObserver?.disconnect()
|
|
141
|
+
mutationObserver?.disconnect()
|
|
142
|
+
window.removeEventListener("scroll", scheduleMeasure, true)
|
|
143
|
+
window.removeEventListener("resize", scheduleMeasure)
|
|
144
|
+
if (frameRef.current !== null) {
|
|
145
|
+
cancelAnimationFrame(frameRef.current)
|
|
146
|
+
frameRef.current = null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, [scheduleMeasure, svgDivRef, containerRef])
|
|
150
|
+
|
|
151
|
+
const handleClick = useCallback(
|
|
152
|
+
(event: MouseEvent) => {
|
|
153
|
+
if (onPortClick) {
|
|
154
|
+
onPortClick(portId, event)
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
[portId, onPortClick],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const bounds = measurement?.bounds ?? null
|
|
161
|
+
|
|
162
|
+
const { hovering } = useMouseEventsOverBoundingBox({
|
|
163
|
+
bounds,
|
|
164
|
+
onClick: onPortClick ? handleClick : undefined,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// Notify parent of hover state changes
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (onHoverChange) {
|
|
170
|
+
onHoverChange(portId, hovering)
|
|
171
|
+
}
|
|
172
|
+
}, [hovering, portId, onHoverChange])
|
|
173
|
+
|
|
174
|
+
if (!measurement || !showOutline) {
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const rect = measurement.rect
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<>
|
|
182
|
+
<div
|
|
183
|
+
style={{
|
|
184
|
+
position: "absolute",
|
|
185
|
+
left: rect.left,
|
|
186
|
+
top: rect.top,
|
|
187
|
+
width: rect.width,
|
|
188
|
+
height: rect.height,
|
|
189
|
+
border: hovering
|
|
190
|
+
? "1.5px solid rgba(255, 153, 51, 0.9)"
|
|
191
|
+
: "1.5px solid rgba(255, 153, 51, 0.3)",
|
|
192
|
+
backgroundColor: hovering
|
|
193
|
+
? "rgba(255, 153, 51, 0.15)"
|
|
194
|
+
: "rgba(255, 153, 51, 0.05)",
|
|
195
|
+
borderRadius: "50%",
|
|
196
|
+
pointerEvents: "none",
|
|
197
|
+
zIndex: zIndexMap.schematicPortHoverOutline,
|
|
198
|
+
transition: "border-color 0.15s, background-color 0.15s",
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
{hovering && portLabel && (
|
|
202
|
+
<div
|
|
203
|
+
style={{
|
|
204
|
+
position: "absolute",
|
|
205
|
+
left: rect.left + rect.width / 2,
|
|
206
|
+
top: rect.top - 24,
|
|
207
|
+
transform: "translateX(-50%)",
|
|
208
|
+
backgroundColor: "rgba(0, 0, 0, 0.85)",
|
|
209
|
+
color: "white",
|
|
210
|
+
padding: "4px 8px",
|
|
211
|
+
borderRadius: "4px",
|
|
212
|
+
fontSize: "12px",
|
|
213
|
+
fontFamily: "monospace",
|
|
214
|
+
whiteSpace: "nowrap",
|
|
215
|
+
pointerEvents: "none",
|
|
216
|
+
zIndex: zIndexMap.schematicPortHoverOutline + 1,
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
{portLabel}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
@@ -30,6 +30,7 @@ import { getSpiceFromCircuitJson } from "../utils/spice-utils"
|
|
|
30
30
|
import { getStoredBoolean, setStoredBoolean } from "lib/hooks/useLocalStorage"
|
|
31
31
|
import { MouseTracker } from "./MouseTracker"
|
|
32
32
|
import { SchematicComponentMouseTarget } from "./SchematicComponentMouseTarget"
|
|
33
|
+
import { SchematicPortMouseTarget } from "./SchematicPortMouseTarget"
|
|
33
34
|
|
|
34
35
|
interface Props {
|
|
35
36
|
circuitJson: CircuitJson
|
|
@@ -48,6 +49,11 @@ interface Props {
|
|
|
48
49
|
schematicComponentId: string
|
|
49
50
|
event: MouseEvent
|
|
50
51
|
}) => void
|
|
52
|
+
showSchematicPorts?: boolean
|
|
53
|
+
onSchematicPortClicked?: (options: {
|
|
54
|
+
schematicPortId: string
|
|
55
|
+
event: MouseEvent
|
|
56
|
+
}) => void
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
export const SchematicViewer = ({
|
|
@@ -64,6 +70,8 @@ export const SchematicViewer = ({
|
|
|
64
70
|
spiceSimulationEnabled = false,
|
|
65
71
|
disableGroups = false,
|
|
66
72
|
onSchematicComponentClicked,
|
|
73
|
+
showSchematicPorts = false,
|
|
74
|
+
onSchematicPortClicked,
|
|
67
75
|
}: Props) => {
|
|
68
76
|
if (debug) {
|
|
69
77
|
enableDebug()
|
|
@@ -115,7 +123,8 @@ export const SchematicViewer = ({
|
|
|
115
123
|
|
|
116
124
|
const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)
|
|
117
125
|
const [snapToGrid, setSnapToGrid] = useState(true)
|
|
118
|
-
const [
|
|
126
|
+
const [showGridInternal, setShowGridInternal] = useState(false)
|
|
127
|
+
const showGrid = debugGrid || showGridInternal
|
|
119
128
|
const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(
|
|
120
129
|
!clickToInteractEnabled,
|
|
121
130
|
)
|
|
@@ -139,6 +148,22 @@ export const SchematicViewer = ({
|
|
|
139
148
|
},
|
|
140
149
|
[],
|
|
141
150
|
)
|
|
151
|
+
|
|
152
|
+
const [isHoveringClickablePort, setIsHoveringClickablePort] = useState(false)
|
|
153
|
+
const hoveringPortsRef = useRef<Set<string>>(new Set())
|
|
154
|
+
|
|
155
|
+
const handlePortHoverChange = useCallback(
|
|
156
|
+
(portId: string, isHovering: boolean) => {
|
|
157
|
+
if (isHovering) {
|
|
158
|
+
hoveringPortsRef.current.add(portId)
|
|
159
|
+
} else {
|
|
160
|
+
hoveringPortsRef.current.delete(portId)
|
|
161
|
+
}
|
|
162
|
+
setIsHoveringClickablePort(hoveringPortsRef.current.size > 0)
|
|
163
|
+
},
|
|
164
|
+
[],
|
|
165
|
+
)
|
|
166
|
+
|
|
142
167
|
const svgDivRef = useRef<HTMLDivElement>(null)
|
|
143
168
|
const touchStartRef = useRef<{ x: number; y: number } | null>(null)
|
|
144
169
|
|
|
@@ -155,6 +180,32 @@ export const SchematicViewer = ({
|
|
|
155
180
|
}
|
|
156
181
|
}, [circuitJsonKey, circuitJson])
|
|
157
182
|
|
|
183
|
+
const schematicPortsInfo = useMemo(() => {
|
|
184
|
+
if (!showSchematicPorts) return []
|
|
185
|
+
try {
|
|
186
|
+
const ports = su(circuitJson).schematic_port?.list() ?? []
|
|
187
|
+
return ports.map((port) => {
|
|
188
|
+
const sourcePort = su(circuitJson).source_port.get(port.source_port_id)
|
|
189
|
+
const sourceComponent = sourcePort?.source_component_id
|
|
190
|
+
? su(circuitJson).source_component.get(sourcePort.source_component_id)
|
|
191
|
+
: null
|
|
192
|
+
const componentName = sourceComponent?.name ?? "?"
|
|
193
|
+
const pinLabel =
|
|
194
|
+
port.display_pin_label ??
|
|
195
|
+
(sourcePort as any)?.pin_number ??
|
|
196
|
+
(sourcePort as any)?.name ??
|
|
197
|
+
"?"
|
|
198
|
+
return {
|
|
199
|
+
portId: port.source_port_id as string,
|
|
200
|
+
label: `${componentName}.${pinLabel}`,
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error("Failed to derive schematic port info", err)
|
|
205
|
+
return []
|
|
206
|
+
}
|
|
207
|
+
}, [circuitJsonKey, circuitJson, showSchematicPorts])
|
|
208
|
+
|
|
158
209
|
const handleTouchStart = (e: React.TouchEvent) => {
|
|
159
210
|
const touch = e.touches[0]
|
|
160
211
|
touchStartRef.current = {
|
|
@@ -343,6 +394,11 @@ export const SchematicViewer = ({
|
|
|
343
394
|
{`.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }`}
|
|
344
395
|
</style>
|
|
345
396
|
)}
|
|
397
|
+
{onSchematicPortClicked && (
|
|
398
|
+
<style>
|
|
399
|
+
{`[data-schematic-port-id]:hover { cursor: pointer !important; }`}
|
|
400
|
+
</style>
|
|
401
|
+
)}
|
|
346
402
|
<div
|
|
347
403
|
ref={containerRef}
|
|
348
404
|
style={{
|
|
@@ -357,7 +413,9 @@ export const SchematicViewer = ({
|
|
|
357
413
|
? "pointer"
|
|
358
414
|
: isHoveringClickableComponent && onSchematicComponentClicked
|
|
359
415
|
? "pointer"
|
|
360
|
-
:
|
|
416
|
+
: isHoveringClickablePort && onSchematicPortClicked
|
|
417
|
+
? "pointer"
|
|
418
|
+
: "grab",
|
|
361
419
|
minHeight: "300px",
|
|
362
420
|
...containerStyle,
|
|
363
421
|
}}
|
|
@@ -456,7 +514,7 @@ export const SchematicViewer = ({
|
|
|
456
514
|
}
|
|
457
515
|
}}
|
|
458
516
|
showGrid={showGrid}
|
|
459
|
-
onToggleGrid={
|
|
517
|
+
onToggleGrid={setShowGridInternal}
|
|
460
518
|
/>
|
|
461
519
|
{spiceSimulationEnabled && (
|
|
462
520
|
<SpiceSimulationIcon onClick={() => setShowSpiceOverlay(true)} />
|
|
@@ -496,6 +554,29 @@ export const SchematicViewer = ({
|
|
|
496
554
|
/>
|
|
497
555
|
))}
|
|
498
556
|
{svgDiv}
|
|
557
|
+
{showSchematicPorts &&
|
|
558
|
+
schematicPortsInfo.map(({ portId, label }) => (
|
|
559
|
+
<SchematicPortMouseTarget
|
|
560
|
+
key={portId}
|
|
561
|
+
portId={portId}
|
|
562
|
+
portLabel={label}
|
|
563
|
+
svgDivRef={svgDivRef}
|
|
564
|
+
containerRef={containerRef}
|
|
565
|
+
showOutline={true}
|
|
566
|
+
circuitJsonKey={circuitJsonKey}
|
|
567
|
+
onHoverChange={handlePortHoverChange}
|
|
568
|
+
onPortClick={
|
|
569
|
+
onSchematicPortClicked
|
|
570
|
+
? (id, event) => {
|
|
571
|
+
onSchematicPortClicked?.({
|
|
572
|
+
schematicPortId: id,
|
|
573
|
+
event,
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
: undefined
|
|
577
|
+
}
|
|
578
|
+
/>
|
|
579
|
+
))}
|
|
499
580
|
</div>
|
|
500
581
|
</MouseTracker>
|
|
501
582
|
)
|
package/lib/utils/z-index-map.ts
CHANGED