@tscircuit/schematic-viewer 2.0.42 → 2.0.43
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.d.ts +25 -2
- package/dist/index.js +411 -40
- package/dist/index.js.map +1 -1
- package/examples/example14-schematic-component-click.fixture.tsx +46 -0
- package/lib/components/ControlledSchematicViewer.tsx +6 -0
- package/lib/components/MouseTracker.tsx +227 -0
- package/lib/components/SchematicComponentMouseTarget.tsx +182 -0
- package/lib/components/SchematicViewer.tsx +162 -122
- package/lib/hooks/useMouseEventsOverBoundingBox.ts +74 -0
- package/lib/index.ts +2 -0
- package/lib/utils/z-index-map.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { ControlledSchematicViewer } from "lib/components/ControlledSchematicViewer"
|
|
3
|
+
import { renderToCircuitJson } from "lib/dev/render-to-circuit-json"
|
|
4
|
+
|
|
5
|
+
const circuitJson = renderToCircuitJson(
|
|
6
|
+
<board width="12mm" height="12mm">
|
|
7
|
+
<resistor name="R1" resistance={1000} schX={-2} schY={0} />
|
|
8
|
+
<capacitor name="C1" capacitance="1uF" schX={2} schY={0} />
|
|
9
|
+
<trace from=".R1 .pin2" to=".C1 .pin1" />
|
|
10
|
+
<trace from=".R1 .pin1" to=".C1 .pin2" />
|
|
11
|
+
</board>,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
export default function Example() {
|
|
15
|
+
const [clickedComponentId, setClickedComponentId] = useState<string | null>(
|
|
16
|
+
null,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
style={{
|
|
22
|
+
display: "flex",
|
|
23
|
+
flexDirection: "column",
|
|
24
|
+
gap: "12px",
|
|
25
|
+
padding: "16px",
|
|
26
|
+
height: "100%",
|
|
27
|
+
boxSizing: "border-box",
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<div style={{ fontFamily: "sans-serif" }}>
|
|
31
|
+
{clickedComponentId
|
|
32
|
+
? `Last clicked component: ${clickedComponentId}`
|
|
33
|
+
: "Click a component to highlight it"}
|
|
34
|
+
</div>
|
|
35
|
+
<div style={{ flex: 1, minHeight: 320 }}>
|
|
36
|
+
<ControlledSchematicViewer
|
|
37
|
+
circuitJson={circuitJson}
|
|
38
|
+
containerStyle={{ height: "100%" }}
|
|
39
|
+
onSchematicComponentClicked={({ schematicComponentId }) => {
|
|
40
|
+
setClickedComponentId(schematicComponentId)
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -9,6 +9,7 @@ export const ControlledSchematicViewer = ({
|
|
|
9
9
|
editingEnabled = false,
|
|
10
10
|
debug = false,
|
|
11
11
|
clickToInteractEnabled = false,
|
|
12
|
+
onSchematicComponentClicked,
|
|
12
13
|
}: {
|
|
13
14
|
circuitJson: any[]
|
|
14
15
|
containerStyle?: React.CSSProperties
|
|
@@ -16,6 +17,10 @@ export const ControlledSchematicViewer = ({
|
|
|
16
17
|
editingEnabled?: boolean
|
|
17
18
|
debug?: boolean
|
|
18
19
|
clickToInteractEnabled?: boolean
|
|
20
|
+
onSchematicComponentClicked?: (options: {
|
|
21
|
+
schematicComponentId: string
|
|
22
|
+
event: MouseEvent
|
|
23
|
+
}) => void
|
|
19
24
|
}) => {
|
|
20
25
|
const [editEvents, setEditEvents] = useState<ManualEditEvent[]>([])
|
|
21
26
|
|
|
@@ -29,6 +34,7 @@ export const ControlledSchematicViewer = ({
|
|
|
29
34
|
editingEnabled={editingEnabled}
|
|
30
35
|
debug={debug}
|
|
31
36
|
clickToInteractEnabled={clickToInteractEnabled}
|
|
37
|
+
onSchematicComponentClicked={onSchematicComponentClicked}
|
|
32
38
|
/>
|
|
33
39
|
)
|
|
34
40
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react"
|
|
10
|
+
|
|
11
|
+
export interface BoundingBoxBounds {
|
|
12
|
+
minX: number
|
|
13
|
+
maxX: number
|
|
14
|
+
minY: number
|
|
15
|
+
maxY: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface BoundingBoxRegistration {
|
|
19
|
+
bounds: BoundingBoxBounds | null
|
|
20
|
+
onClick?: ((event: MouseEvent) => void) | undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MouseTrackerContextValue {
|
|
24
|
+
registerBoundingBox: (
|
|
25
|
+
id: string,
|
|
26
|
+
registration: BoundingBoxRegistration,
|
|
27
|
+
) => void
|
|
28
|
+
updateBoundingBox: (id: string, registration: BoundingBoxRegistration) => void
|
|
29
|
+
unregisterBoundingBox: (id: string) => void
|
|
30
|
+
subscribe: (listener: () => void) => () => void
|
|
31
|
+
isHovering: (id: string) => boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const MouseTrackerContext =
|
|
35
|
+
createContext<MouseTrackerContextValue | null>(null)
|
|
36
|
+
|
|
37
|
+
const boundsAreEqual = (
|
|
38
|
+
a: BoundingBoxBounds | null | undefined,
|
|
39
|
+
b: BoundingBoxBounds | null | undefined,
|
|
40
|
+
) => {
|
|
41
|
+
if (!a && !b) return true
|
|
42
|
+
if (!a || !b) return false
|
|
43
|
+
return (
|
|
44
|
+
a.minX === b.minX &&
|
|
45
|
+
a.maxX === b.maxX &&
|
|
46
|
+
a.minY === b.minY &&
|
|
47
|
+
a.maxY === b.maxY
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const MouseTracker = ({ children }: { children: ReactNode }) => {
|
|
52
|
+
const existingContext = useContext(MouseTrackerContext)
|
|
53
|
+
|
|
54
|
+
if (existingContext) {
|
|
55
|
+
return <>{children}</>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const storeRef = useRef({
|
|
59
|
+
pointer: null as { x: number; y: number } | null,
|
|
60
|
+
boundingBoxes: new Map<string, BoundingBoxRegistration>(),
|
|
61
|
+
hoveringIds: new Set<string>(),
|
|
62
|
+
subscribers: new Set<() => void>(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const notifySubscribers = useCallback(() => {
|
|
66
|
+
for (const callback of storeRef.current.subscribers) {
|
|
67
|
+
callback()
|
|
68
|
+
}
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
const updateHovering = useCallback(() => {
|
|
72
|
+
const pointer = storeRef.current.pointer
|
|
73
|
+
const newHovering = new Set<string>()
|
|
74
|
+
|
|
75
|
+
if (pointer) {
|
|
76
|
+
for (const [id, registration] of storeRef.current.boundingBoxes) {
|
|
77
|
+
const bounds = registration.bounds
|
|
78
|
+
if (!bounds) continue
|
|
79
|
+
if (
|
|
80
|
+
pointer.x >= bounds.minX &&
|
|
81
|
+
pointer.x <= bounds.maxX &&
|
|
82
|
+
pointer.y >= bounds.minY &&
|
|
83
|
+
pointer.y <= bounds.maxY
|
|
84
|
+
) {
|
|
85
|
+
newHovering.add(id)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const prevHovering = storeRef.current.hoveringIds
|
|
91
|
+
if (
|
|
92
|
+
newHovering.size === prevHovering.size &&
|
|
93
|
+
[...newHovering].every((id) => prevHovering.has(id))
|
|
94
|
+
) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
storeRef.current.hoveringIds = newHovering
|
|
99
|
+
notifySubscribers()
|
|
100
|
+
}, [notifySubscribers])
|
|
101
|
+
|
|
102
|
+
const registerBoundingBox = useCallback(
|
|
103
|
+
(id: string, registration: BoundingBoxRegistration) => {
|
|
104
|
+
storeRef.current.boundingBoxes.set(id, registration)
|
|
105
|
+
updateHovering()
|
|
106
|
+
},
|
|
107
|
+
[updateHovering],
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const updateBoundingBox = useCallback(
|
|
111
|
+
(id: string, registration: BoundingBoxRegistration) => {
|
|
112
|
+
const existing = storeRef.current.boundingBoxes.get(id)
|
|
113
|
+
if (
|
|
114
|
+
existing &&
|
|
115
|
+
boundsAreEqual(existing.bounds, registration.bounds) &&
|
|
116
|
+
existing.onClick === registration.onClick
|
|
117
|
+
) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
storeRef.current.boundingBoxes.set(id, registration)
|
|
121
|
+
updateHovering()
|
|
122
|
+
},
|
|
123
|
+
[updateHovering],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const unregisterBoundingBox = useCallback(
|
|
127
|
+
(id: string) => {
|
|
128
|
+
const removed = storeRef.current.boundingBoxes.delete(id)
|
|
129
|
+
if (removed) {
|
|
130
|
+
updateHovering()
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
[updateHovering],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const subscribe = useCallback((listener: () => void) => {
|
|
137
|
+
storeRef.current.subscribers.add(listener)
|
|
138
|
+
return () => {
|
|
139
|
+
storeRef.current.subscribers.delete(listener)
|
|
140
|
+
}
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
const isHovering = useCallback((id: string) => {
|
|
144
|
+
return storeRef.current.hoveringIds.has(id)
|
|
145
|
+
}, [])
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const handlePointerPosition = (event: PointerEvent | MouseEvent) => {
|
|
149
|
+
const { clientX, clientY } = event
|
|
150
|
+
const pointer = storeRef.current.pointer
|
|
151
|
+
if (pointer && pointer.x === clientX && pointer.y === clientY) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
storeRef.current.pointer = { x: clientX, y: clientY }
|
|
155
|
+
updateHovering()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const handlePointerLeave = () => {
|
|
159
|
+
if (storeRef.current.pointer === null) return
|
|
160
|
+
storeRef.current.pointer = null
|
|
161
|
+
updateHovering()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const handleClick = (event: MouseEvent) => {
|
|
165
|
+
const { clientX, clientY } = event
|
|
166
|
+
for (const registration of storeRef.current.boundingBoxes.values()) {
|
|
167
|
+
const bounds = registration.bounds
|
|
168
|
+
if (!bounds) continue
|
|
169
|
+
if (
|
|
170
|
+
clientX >= bounds.minX &&
|
|
171
|
+
clientX <= bounds.maxX &&
|
|
172
|
+
clientY >= bounds.minY &&
|
|
173
|
+
clientY <= bounds.maxY
|
|
174
|
+
) {
|
|
175
|
+
registration.onClick?.(event)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
window.addEventListener("pointermove", handlePointerPosition, {
|
|
181
|
+
passive: true,
|
|
182
|
+
})
|
|
183
|
+
window.addEventListener("pointerdown", handlePointerPosition, {
|
|
184
|
+
passive: true,
|
|
185
|
+
})
|
|
186
|
+
window.addEventListener("pointerup", handlePointerPosition, {
|
|
187
|
+
passive: true,
|
|
188
|
+
})
|
|
189
|
+
window.addEventListener("pointerleave", handlePointerLeave)
|
|
190
|
+
window.addEventListener("pointercancel", handlePointerLeave)
|
|
191
|
+
window.addEventListener("blur", handlePointerLeave)
|
|
192
|
+
window.addEventListener("click", handleClick, { passive: true })
|
|
193
|
+
|
|
194
|
+
return () => {
|
|
195
|
+
window.removeEventListener("pointermove", handlePointerPosition)
|
|
196
|
+
window.removeEventListener("pointerdown", handlePointerPosition)
|
|
197
|
+
window.removeEventListener("pointerup", handlePointerPosition)
|
|
198
|
+
window.removeEventListener("pointerleave", handlePointerLeave)
|
|
199
|
+
window.removeEventListener("pointercancel", handlePointerLeave)
|
|
200
|
+
window.removeEventListener("blur", handlePointerLeave)
|
|
201
|
+
window.removeEventListener("click", handleClick)
|
|
202
|
+
}
|
|
203
|
+
}, [updateHovering])
|
|
204
|
+
|
|
205
|
+
const value = useMemo<MouseTrackerContextValue>(
|
|
206
|
+
() => ({
|
|
207
|
+
registerBoundingBox,
|
|
208
|
+
updateBoundingBox,
|
|
209
|
+
unregisterBoundingBox,
|
|
210
|
+
subscribe,
|
|
211
|
+
isHovering,
|
|
212
|
+
}),
|
|
213
|
+
[
|
|
214
|
+
registerBoundingBox,
|
|
215
|
+
updateBoundingBox,
|
|
216
|
+
unregisterBoundingBox,
|
|
217
|
+
subscribe,
|
|
218
|
+
isHovering,
|
|
219
|
+
],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<MouseTrackerContext.Provider value={value}>
|
|
224
|
+
{children}
|
|
225
|
+
</MouseTrackerContext.Provider>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
componentId: string
|
|
35
|
+
svgDivRef: React.RefObject<HTMLDivElement | null>
|
|
36
|
+
containerRef: React.RefObject<HTMLDivElement | null>
|
|
37
|
+
onComponentClick?: (componentId: string, event: MouseEvent) => void
|
|
38
|
+
showOutline: boolean
|
|
39
|
+
circuitJsonKey: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const SchematicComponentMouseTarget = ({
|
|
43
|
+
componentId,
|
|
44
|
+
svgDivRef,
|
|
45
|
+
containerRef,
|
|
46
|
+
onComponentClick,
|
|
47
|
+
showOutline,
|
|
48
|
+
circuitJsonKey,
|
|
49
|
+
}: Props) => {
|
|
50
|
+
const [measurement, setMeasurement] = useState<Measurement | null>(null)
|
|
51
|
+
const frameRef = useRef<number | null>(null)
|
|
52
|
+
|
|
53
|
+
const measure = useCallback(() => {
|
|
54
|
+
frameRef.current = null
|
|
55
|
+
const svgDiv = svgDivRef.current
|
|
56
|
+
const container = containerRef.current
|
|
57
|
+
if (!svgDiv || !container) {
|
|
58
|
+
setMeasurement((prev) => (prev ? null : prev))
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
const element = svgDiv.querySelector<SVGGraphicsElement | HTMLElement>(
|
|
62
|
+
`[data-schematic-component-id="${componentId}"]`,
|
|
63
|
+
)
|
|
64
|
+
if (!element) {
|
|
65
|
+
setMeasurement((prev) => (prev ? null : prev))
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const elementRect = element.getBoundingClientRect()
|
|
70
|
+
const containerRect = container.getBoundingClientRect()
|
|
71
|
+
|
|
72
|
+
const nextMeasurement: Measurement = {
|
|
73
|
+
bounds: {
|
|
74
|
+
minX: elementRect.left,
|
|
75
|
+
maxX: elementRect.right,
|
|
76
|
+
minY: elementRect.top,
|
|
77
|
+
maxY: elementRect.bottom,
|
|
78
|
+
},
|
|
79
|
+
rect: {
|
|
80
|
+
left: elementRect.left - containerRect.left,
|
|
81
|
+
top: elementRect.top - containerRect.top,
|
|
82
|
+
width: elementRect.width,
|
|
83
|
+
height: elementRect.height,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setMeasurement((prev) =>
|
|
88
|
+
areMeasurementsEqual(prev, nextMeasurement) ? prev : nextMeasurement,
|
|
89
|
+
)
|
|
90
|
+
}, [componentId, containerRef, svgDivRef])
|
|
91
|
+
|
|
92
|
+
const scheduleMeasure = useCallback(() => {
|
|
93
|
+
if (frameRef.current !== null) return
|
|
94
|
+
frameRef.current = window.requestAnimationFrame(measure)
|
|
95
|
+
}, [measure])
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
scheduleMeasure()
|
|
99
|
+
}, [scheduleMeasure, circuitJsonKey])
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
scheduleMeasure()
|
|
103
|
+
const svgDiv = svgDivRef.current
|
|
104
|
+
const container = containerRef.current
|
|
105
|
+
if (!svgDiv || !container) return
|
|
106
|
+
|
|
107
|
+
const resizeObserver =
|
|
108
|
+
typeof ResizeObserver !== "undefined"
|
|
109
|
+
? new ResizeObserver(() => {
|
|
110
|
+
scheduleMeasure()
|
|
111
|
+
})
|
|
112
|
+
: null
|
|
113
|
+
resizeObserver?.observe(container)
|
|
114
|
+
resizeObserver?.observe(svgDiv)
|
|
115
|
+
|
|
116
|
+
const mutationObserver =
|
|
117
|
+
typeof MutationObserver !== "undefined"
|
|
118
|
+
? new MutationObserver(() => {
|
|
119
|
+
scheduleMeasure()
|
|
120
|
+
})
|
|
121
|
+
: null
|
|
122
|
+
mutationObserver?.observe(svgDiv, {
|
|
123
|
+
attributes: true,
|
|
124
|
+
attributeFilter: ["style", "transform"],
|
|
125
|
+
subtree: true,
|
|
126
|
+
childList: true,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
window.addEventListener("scroll", scheduleMeasure, true)
|
|
130
|
+
window.addEventListener("resize", scheduleMeasure)
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
resizeObserver?.disconnect()
|
|
134
|
+
mutationObserver?.disconnect()
|
|
135
|
+
window.removeEventListener("scroll", scheduleMeasure, true)
|
|
136
|
+
window.removeEventListener("resize", scheduleMeasure)
|
|
137
|
+
if (frameRef.current !== null) {
|
|
138
|
+
cancelAnimationFrame(frameRef.current)
|
|
139
|
+
frameRef.current = null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}, [scheduleMeasure, svgDivRef, containerRef])
|
|
143
|
+
|
|
144
|
+
const handleClick = useCallback(
|
|
145
|
+
(event: MouseEvent) => {
|
|
146
|
+
if (onComponentClick) {
|
|
147
|
+
onComponentClick(componentId, event)
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[componentId, onComponentClick],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const bounds = measurement?.bounds ?? null
|
|
154
|
+
|
|
155
|
+
const { hovering } = useMouseEventsOverBoundingBox({
|
|
156
|
+
bounds,
|
|
157
|
+
onClick: onComponentClick ? handleClick : undefined,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
if (!measurement || !hovering || !showOutline) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rect = measurement.rect
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
style={{
|
|
169
|
+
position: "absolute",
|
|
170
|
+
left: rect.left,
|
|
171
|
+
top: rect.top,
|
|
172
|
+
width: rect.width,
|
|
173
|
+
height: rect.height,
|
|
174
|
+
border: "1.5px solid rgba(51, 153, 255, 0.9)",
|
|
175
|
+
borderRadius: "6px",
|
|
176
|
+
pointerEvents: "none",
|
|
177
|
+
zIndex: zIndexMap.schematicComponentHoverOutline,
|
|
178
|
+
boxShadow: "0 0 6px rgba(51, 153, 255, 0.35)",
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
}
|