@tscircuit/schematic-viewer 2.0.26 → 2.0.28
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 +432 -51
- package/dist/index.js.map +1 -1
- package/examples/example10-groups-view-schematic-groups.fixture.tsx +90 -0
- package/examples/example11-automatic-grouping-view-schematic-groups.fixture.tsx +99 -0
- package/lib/components/EditIcon.tsx +1 -1
- package/lib/components/GridIcon.tsx +1 -1
- package/lib/components/SchematicViewer.tsx +25 -0
- package/lib/components/SpiceSimulationIcon.tsx +1 -1
- package/lib/components/ViewMenu.tsx +147 -0
- package/lib/components/ViewMenuIcon.tsx +40 -0
- package/lib/hooks/useSchematicGroupsOverlay.ts +223 -0
- package/lib/utils/z-index-map.ts +3 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "circuit-to-svg"
|
|
5
5
|
import { useChangeSchematicComponentLocationsInSvg } from "lib/hooks/useChangeSchematicComponentLocationsInSvg"
|
|
6
6
|
import { useChangeSchematicTracesForMovedComponents } from "lib/hooks/useChangeSchematicTracesForMovedComponents"
|
|
7
|
+
import { useSchematicGroupsOverlay } from "lib/hooks/useSchematicGroupsOverlay"
|
|
7
8
|
import { enableDebug } from "lib/utils/debug"
|
|
8
9
|
import { useEffect, useMemo, useRef, useState } from "react"
|
|
9
10
|
import {
|
|
@@ -17,6 +18,8 @@ import { useComponentDragging } from "../hooks/useComponentDragging"
|
|
|
17
18
|
import type { ManualEditEvent } from "../types/edit-events"
|
|
18
19
|
import { EditIcon } from "./EditIcon"
|
|
19
20
|
import { GridIcon } from "./GridIcon"
|
|
21
|
+
import { ViewMenuIcon } from "./ViewMenuIcon"
|
|
22
|
+
import { ViewMenu } from "./ViewMenu"
|
|
20
23
|
import type { CircuitJson } from "circuit-json"
|
|
21
24
|
import { SpiceSimulationIcon } from "./SpiceSimulationIcon"
|
|
22
25
|
import { SpiceSimulationOverlay } from "./SpiceSimulationOverlay"
|
|
@@ -87,6 +90,8 @@ export const SchematicViewer = ({
|
|
|
87
90
|
const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(
|
|
88
91
|
!clickToInteractEnabled,
|
|
89
92
|
)
|
|
93
|
+
const [showViewMenu, setShowViewMenu] = useState(false)
|
|
94
|
+
const [showSchematicGroups, setShowSchematicGroups] = useState(false)
|
|
90
95
|
const svgDivRef = useRef<HTMLDivElement>(null)
|
|
91
96
|
const touchStartRef = useRef<{ x: number; y: number } | null>(null)
|
|
92
97
|
|
|
@@ -219,6 +224,14 @@ export const SchematicViewer = ({
|
|
|
219
224
|
editEvents: editEventsWithUnappliedEditEvents,
|
|
220
225
|
})
|
|
221
226
|
|
|
227
|
+
// Add group overlays when enabled
|
|
228
|
+
useSchematicGroupsOverlay({
|
|
229
|
+
svgDivRef,
|
|
230
|
+
circuitJson,
|
|
231
|
+
circuitJsonKey,
|
|
232
|
+
showGroups: showSchematicGroups,
|
|
233
|
+
})
|
|
234
|
+
|
|
222
235
|
const svgDiv = useMemo(
|
|
223
236
|
() => (
|
|
224
237
|
<div
|
|
@@ -321,6 +334,10 @@ export const SchematicViewer = ({
|
|
|
321
334
|
</div>
|
|
322
335
|
</div>
|
|
323
336
|
)}
|
|
337
|
+
<ViewMenuIcon
|
|
338
|
+
active={showViewMenu}
|
|
339
|
+
onClick={() => setShowViewMenu(!showViewMenu)}
|
|
340
|
+
/>
|
|
324
341
|
{editingEnabled && (
|
|
325
342
|
<EditIcon
|
|
326
343
|
active={editModeEnabled}
|
|
@@ -333,6 +350,14 @@ export const SchematicViewer = ({
|
|
|
333
350
|
onClick={() => setSnapToGrid(!snapToGrid)}
|
|
334
351
|
/>
|
|
335
352
|
)}
|
|
353
|
+
<ViewMenu
|
|
354
|
+
circuitJson={circuitJson}
|
|
355
|
+
circuitJsonKey={circuitJsonKey}
|
|
356
|
+
isVisible={showViewMenu}
|
|
357
|
+
onClose={() => setShowViewMenu(false)}
|
|
358
|
+
showGroups={showSchematicGroups}
|
|
359
|
+
onToggleGroups={setShowSchematicGroups}
|
|
360
|
+
/>
|
|
336
361
|
{spiceSimulationEnabled && (
|
|
337
362
|
<SpiceSimulationIcon onClick={() => setShowSpiceOverlay(true)} />
|
|
338
363
|
)}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { su } from "@tscircuit/soup-util"
|
|
3
|
+
import type { CircuitJson } from "circuit-json"
|
|
4
|
+
import { zIndexMap } from "../utils/z-index-map"
|
|
5
|
+
|
|
6
|
+
interface ViewMenuProps {
|
|
7
|
+
circuitJson: CircuitJson
|
|
8
|
+
circuitJsonKey: string
|
|
9
|
+
isVisible: boolean
|
|
10
|
+
onClose: () => void
|
|
11
|
+
showGroups: boolean
|
|
12
|
+
onToggleGroups: (show: boolean) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ViewMenu = ({
|
|
16
|
+
circuitJson,
|
|
17
|
+
circuitJsonKey,
|
|
18
|
+
isVisible,
|
|
19
|
+
onClose,
|
|
20
|
+
showGroups,
|
|
21
|
+
onToggleGroups,
|
|
22
|
+
}: ViewMenuProps) => {
|
|
23
|
+
const hasGroups = useMemo(() => {
|
|
24
|
+
if (!circuitJson || circuitJson.length === 0) return false
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Check if there are explicit groups
|
|
28
|
+
const sourceGroups = su(circuitJson).source_group?.list() || []
|
|
29
|
+
if (sourceGroups.length > 0) return true
|
|
30
|
+
|
|
31
|
+
// Check if we can create virtual groups by component type
|
|
32
|
+
const schematicComponents =
|
|
33
|
+
su(circuitJson).schematic_component?.list() || []
|
|
34
|
+
if (schematicComponents.length > 1) {
|
|
35
|
+
const componentTypes = new Set()
|
|
36
|
+
for (const comp of schematicComponents) {
|
|
37
|
+
const sourceComp = su(circuitJson).source_component.get(
|
|
38
|
+
comp.source_component_id,
|
|
39
|
+
)
|
|
40
|
+
if (sourceComp?.ftype) {
|
|
41
|
+
componentTypes.add(sourceComp.ftype)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return componentTypes.size > 1 // Only show if there are multiple types
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error checking for groups:", error)
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}, [circuitJsonKey])
|
|
53
|
+
|
|
54
|
+
if (!isVisible) return null
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
{/* Backdrop */}
|
|
59
|
+
<div
|
|
60
|
+
onClick={onClose}
|
|
61
|
+
style={{
|
|
62
|
+
position: "absolute",
|
|
63
|
+
inset: 0,
|
|
64
|
+
backgroundColor: "transparent",
|
|
65
|
+
zIndex: zIndexMap.viewMenuBackdrop,
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
{/* Menu */}
|
|
70
|
+
<div
|
|
71
|
+
style={{
|
|
72
|
+
position: "absolute",
|
|
73
|
+
top: "56px",
|
|
74
|
+
right: "16px",
|
|
75
|
+
backgroundColor: "#ffffff",
|
|
76
|
+
color: "#000000",
|
|
77
|
+
border: "1px solid #ccc",
|
|
78
|
+
borderRadius: "4px",
|
|
79
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
|
|
80
|
+
minWidth: "200px",
|
|
81
|
+
zIndex: zIndexMap.viewMenu,
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{/* Groups Toggle Option */}
|
|
85
|
+
<div
|
|
86
|
+
onClick={() => {
|
|
87
|
+
if (hasGroups) {
|
|
88
|
+
onToggleGroups(!showGroups)
|
|
89
|
+
}
|
|
90
|
+
}}
|
|
91
|
+
style={{
|
|
92
|
+
padding: "8px 12px",
|
|
93
|
+
cursor: hasGroups ? "pointer" : "not-allowed",
|
|
94
|
+
opacity: hasGroups ? 1 : 0.5,
|
|
95
|
+
fontSize: "13px",
|
|
96
|
+
color: "#000000",
|
|
97
|
+
fontFamily: "sans-serif",
|
|
98
|
+
display: "flex",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
gap: "8px",
|
|
101
|
+
}}
|
|
102
|
+
onMouseEnter={(e) => {
|
|
103
|
+
if (hasGroups) {
|
|
104
|
+
e.currentTarget.style.backgroundColor = "#f0f0f0"
|
|
105
|
+
}
|
|
106
|
+
}}
|
|
107
|
+
onMouseLeave={(e) => {
|
|
108
|
+
if (hasGroups) {
|
|
109
|
+
e.currentTarget.style.backgroundColor = "transparent"
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<div
|
|
114
|
+
style={{
|
|
115
|
+
width: "16px",
|
|
116
|
+
height: "16px",
|
|
117
|
+
border: "2px solid #000",
|
|
118
|
+
borderRadius: "2px",
|
|
119
|
+
backgroundColor: "transparent",
|
|
120
|
+
display: "flex",
|
|
121
|
+
alignItems: "center",
|
|
122
|
+
justifyContent: "center",
|
|
123
|
+
fontSize: "10px",
|
|
124
|
+
fontWeight: "bold",
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{showGroups && "✓"}
|
|
128
|
+
</div>
|
|
129
|
+
View Schematic Groups
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{!hasGroups && (
|
|
133
|
+
<div
|
|
134
|
+
style={{
|
|
135
|
+
padding: "8px 12px",
|
|
136
|
+
fontSize: "11px",
|
|
137
|
+
color: "#666",
|
|
138
|
+
fontStyle: "italic",
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
No groups found in this schematic
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { zIndexMap } from "../utils/z-index-map"
|
|
2
|
+
|
|
3
|
+
export const ViewMenuIcon = ({
|
|
4
|
+
onClick,
|
|
5
|
+
active,
|
|
6
|
+
}: { onClick: () => void; active: boolean }) => {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
onClick={onClick}
|
|
10
|
+
style={{
|
|
11
|
+
position: "absolute",
|
|
12
|
+
top: "16px",
|
|
13
|
+
right: "16px",
|
|
14
|
+
backgroundColor: active ? "#4CAF50" : "#fff",
|
|
15
|
+
color: active ? "#fff" : "#000",
|
|
16
|
+
padding: "8px",
|
|
17
|
+
borderRadius: "4px",
|
|
18
|
+
cursor: "pointer",
|
|
19
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
gap: "4px",
|
|
23
|
+
zIndex: zIndexMap.viewMenuIcon,
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<svg
|
|
27
|
+
width="16"
|
|
28
|
+
height="16"
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
fill="none"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
strokeWidth="2"
|
|
33
|
+
>
|
|
34
|
+
<circle cx="12" cy="12" r="1" />
|
|
35
|
+
<circle cx="12" cy="5" r="1" />
|
|
36
|
+
<circle cx="12" cy="19" r="1" />
|
|
37
|
+
</svg>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import { su } from "@tscircuit/soup-util"
|
|
3
|
+
import type { CircuitJson } from "circuit-json"
|
|
4
|
+
|
|
5
|
+
interface UseSchematicGroupsOverlayOptions {
|
|
6
|
+
svgDivRef: React.RefObject<HTMLDivElement | null>
|
|
7
|
+
circuitJson: CircuitJson
|
|
8
|
+
circuitJsonKey: string
|
|
9
|
+
showGroups: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useSchematicGroupsOverlay = (
|
|
13
|
+
options: UseSchematicGroupsOverlayOptions,
|
|
14
|
+
) => {
|
|
15
|
+
const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (
|
|
19
|
+
!svgDivRef.current ||
|
|
20
|
+
!showGroups ||
|
|
21
|
+
!circuitJson ||
|
|
22
|
+
circuitJson.length === 0
|
|
23
|
+
) {
|
|
24
|
+
// Remove any existing group overlays
|
|
25
|
+
if (svgDivRef.current) {
|
|
26
|
+
const existingOverlays = svgDivRef.current.querySelectorAll(
|
|
27
|
+
".schematic-group-overlay",
|
|
28
|
+
)
|
|
29
|
+
existingOverlays.forEach((overlay) => overlay.remove())
|
|
30
|
+
}
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const svg = svgDivRef.current.querySelector("svg")
|
|
35
|
+
if (!svg) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Remove existing overlays first
|
|
40
|
+
const existingOverlays = svg.querySelectorAll(".schematic-group-overlay")
|
|
41
|
+
existingOverlays.forEach((overlay) => overlay.remove())
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Get explicit groups first
|
|
45
|
+
const sourceGroups = su(circuitJson).source_group?.list() || []
|
|
46
|
+
const schematicComponents =
|
|
47
|
+
su(circuitJson).schematic_component?.list() || []
|
|
48
|
+
|
|
49
|
+
let groupsToRender: Array<{
|
|
50
|
+
id: string
|
|
51
|
+
name: string
|
|
52
|
+
components: any[]
|
|
53
|
+
color: string
|
|
54
|
+
}> = []
|
|
55
|
+
|
|
56
|
+
// Check if we have meaningful explicit groups (not just auto-generated default groups)
|
|
57
|
+
const hasMeaningfulGroups = sourceGroups.length > 0 &&
|
|
58
|
+
sourceGroups.some(group => group.name && group.name.trim() !== "")
|
|
59
|
+
|
|
60
|
+
if (hasMeaningfulGroups) {
|
|
61
|
+
// Use explicit groups
|
|
62
|
+
const groupMap = new Map<string, any[]>()
|
|
63
|
+
|
|
64
|
+
for (const comp of schematicComponents) {
|
|
65
|
+
const sourceComp = su(circuitJson).source_component.get(
|
|
66
|
+
comp.source_component_id,
|
|
67
|
+
)
|
|
68
|
+
if (sourceComp?.source_group_id) {
|
|
69
|
+
if (!groupMap.has(sourceComp.source_group_id)) {
|
|
70
|
+
groupMap.set(sourceComp.source_group_id, [])
|
|
71
|
+
}
|
|
72
|
+
groupMap.get(sourceComp.source_group_id)!.push(comp)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
groupsToRender = Array.from(groupMap.entries()).map(
|
|
77
|
+
([groupId, components], index) => {
|
|
78
|
+
const group = sourceGroups.find(
|
|
79
|
+
(g) => g.source_group_id === groupId,
|
|
80
|
+
)
|
|
81
|
+
return {
|
|
82
|
+
id: groupId,
|
|
83
|
+
name: group?.name || `Group ${index + 1}`,
|
|
84
|
+
components,
|
|
85
|
+
color: getGroupColor(index),
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
} else {
|
|
90
|
+
// Create virtual groups by component type
|
|
91
|
+
const componentTypeGroups = new Map<string, any[]>()
|
|
92
|
+
|
|
93
|
+
for (const comp of schematicComponents) {
|
|
94
|
+
const sourceComp = su(circuitJson).source_component.get(
|
|
95
|
+
comp.source_component_id,
|
|
96
|
+
)
|
|
97
|
+
if (sourceComp) {
|
|
98
|
+
const componentType = sourceComp.ftype || "other"
|
|
99
|
+
if (!componentTypeGroups.has(componentType)) {
|
|
100
|
+
componentTypeGroups.set(componentType, [])
|
|
101
|
+
}
|
|
102
|
+
componentTypeGroups.get(componentType)!.push(comp)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
groupsToRender = Array.from(componentTypeGroups.entries()).map(
|
|
107
|
+
([type, components], index) => ({
|
|
108
|
+
id: `type_${type}`,
|
|
109
|
+
name: `${type.charAt(0).toUpperCase() + type.slice(1)}s`,
|
|
110
|
+
components,
|
|
111
|
+
color: getGroupColor(index),
|
|
112
|
+
}),
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Render group overlays
|
|
117
|
+
groupsToRender.forEach((group, groupIndex) => {
|
|
118
|
+
if (group.components.length === 0) return
|
|
119
|
+
|
|
120
|
+
// Calculate bounding box for the group
|
|
121
|
+
const groupBounds = calculateGroupBounds(group.components, svg)
|
|
122
|
+
if (!groupBounds) return
|
|
123
|
+
|
|
124
|
+
// Create group overlay rectangle
|
|
125
|
+
const groupOverlay = document.createElementNS(
|
|
126
|
+
"http://www.w3.org/2000/svg",
|
|
127
|
+
"rect",
|
|
128
|
+
)
|
|
129
|
+
groupOverlay.setAttribute("class", "schematic-group-overlay")
|
|
130
|
+
groupOverlay.setAttribute("x", (groupBounds.minX - 25).toString())
|
|
131
|
+
groupOverlay.setAttribute("y", (groupBounds.minY - 25).toString())
|
|
132
|
+
groupOverlay.setAttribute(
|
|
133
|
+
"width",
|
|
134
|
+
(groupBounds.maxX - groupBounds.minX + 50).toString(),
|
|
135
|
+
)
|
|
136
|
+
groupOverlay.setAttribute(
|
|
137
|
+
"height",
|
|
138
|
+
(groupBounds.maxY - groupBounds.minY + 50).toString(),
|
|
139
|
+
)
|
|
140
|
+
groupOverlay.setAttribute("fill", "none")
|
|
141
|
+
groupOverlay.setAttribute("stroke", group.color)
|
|
142
|
+
groupOverlay.setAttribute("stroke-width", "3")
|
|
143
|
+
groupOverlay.setAttribute("stroke-dasharray", "8,4")
|
|
144
|
+
groupOverlay.setAttribute("opacity", "0.8")
|
|
145
|
+
groupOverlay.setAttribute("rx", "4")
|
|
146
|
+
groupOverlay.setAttribute("ry", "4")
|
|
147
|
+
|
|
148
|
+
// Create group label
|
|
149
|
+
const groupLabel = document.createElementNS(
|
|
150
|
+
"http://www.w3.org/2000/svg",
|
|
151
|
+
"text",
|
|
152
|
+
)
|
|
153
|
+
groupLabel.setAttribute("class", "schematic-group-overlay")
|
|
154
|
+
groupLabel.setAttribute("x", (groupBounds.minX - 10).toString())
|
|
155
|
+
groupLabel.setAttribute("y", (groupBounds.minY - 8).toString())
|
|
156
|
+
groupLabel.setAttribute("fill", group.color)
|
|
157
|
+
groupLabel.setAttribute("font-size", "14")
|
|
158
|
+
groupLabel.setAttribute("font-family", "Arial, sans-serif")
|
|
159
|
+
groupLabel.setAttribute("font-weight", "bold")
|
|
160
|
+
groupLabel.setAttribute("stroke", "#fff")
|
|
161
|
+
groupLabel.setAttribute("stroke-width", "0.5")
|
|
162
|
+
groupLabel.setAttribute("paint-order", "stroke fill")
|
|
163
|
+
groupLabel.textContent = group.name
|
|
164
|
+
|
|
165
|
+
// Add overlays to the SVG (use appendChild to ensure they're on top)
|
|
166
|
+
svg.appendChild(groupOverlay)
|
|
167
|
+
svg.appendChild(groupLabel)
|
|
168
|
+
})
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error("Error creating group overlays:", error)
|
|
171
|
+
}
|
|
172
|
+
}, [svgDivRef, circuitJsonKey, showGroups])
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getGroupColor(index: number): string {
|
|
176
|
+
const colors = [
|
|
177
|
+
"#FF6B6B", // Red
|
|
178
|
+
"#4ECDC4", // Teal
|
|
179
|
+
"#45B7D1", // Blue
|
|
180
|
+
"#96CEB4", // Green
|
|
181
|
+
"#FF8C42", // Orange
|
|
182
|
+
"#DDA0DD", // Plum
|
|
183
|
+
"#98D8C8", // Mint
|
|
184
|
+
"#F7DC6F", // Light Yellow
|
|
185
|
+
]
|
|
186
|
+
return colors[index % colors.length]
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function calculateGroupBounds(components: any[], svg: SVGElement) {
|
|
190
|
+
let minX = Infinity,
|
|
191
|
+
minY = Infinity,
|
|
192
|
+
maxX = -Infinity,
|
|
193
|
+
maxY = -Infinity
|
|
194
|
+
|
|
195
|
+
for (const component of components) {
|
|
196
|
+
// Look for the component group element (based on circuit-to-svg documentation)
|
|
197
|
+
let componentElement = svg.querySelector(
|
|
198
|
+
`g[data-schematic-component-id="${component.schematic_component_id}"]`,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if (!componentElement) {
|
|
202
|
+
// Fallback to any element with the data attribute
|
|
203
|
+
componentElement = svg.querySelector(
|
|
204
|
+
`[data-schematic-component-id="${component.schematic_component_id}"]`,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (componentElement) {
|
|
209
|
+
const bbox = (componentElement as SVGGraphicsElement).getBBox()
|
|
210
|
+
minX = Math.min(minX, bbox.x)
|
|
211
|
+
minY = Math.min(minY, bbox.y)
|
|
212
|
+
maxX = Math.max(maxX, bbox.x + bbox.width)
|
|
213
|
+
maxY = Math.max(maxY, bbox.y + bbox.height)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (minX === Infinity) {
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const bounds = { minX, minY, maxX, maxY }
|
|
222
|
+
return bounds
|
|
223
|
+
}
|
package/lib/utils/z-index-map.ts
CHANGED