@tscircuit/schematic-viewer 1.4.1 → 1.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-viewer",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Schematic.tsx CHANGED
@@ -2,17 +2,18 @@ import { useRenderedCircuit } from "@tscircuit/core"
2
2
  import { findBoundsAndCenter } from "@tscircuit/soup-util"
3
3
  import type { AnyCircuitElement } from "circuit-json"
4
4
  import { useGlobalStore } from "lib/render-context"
5
- import React, { useCallback, useEffect, useRef, useState } from "react"
5
+ import type React from "react"
6
+ import { useCallback, useEffect, useRef, useState } from "react"
6
7
  import { ErrorBoundary as TypedErrorBoundary } from "react-error-boundary"
7
8
  import { SuperGrid, toMMSI } from "react-supergrid"
8
9
  import useMeasure from "react-use-measure"
9
10
  import { ContextProviders } from "schematic-components"
10
11
  import { SchematicElement } from "schematic-components/SchematicElement"
11
12
  import {
13
+ type Matrix,
12
14
  applyToPoint,
13
15
  compose,
14
16
  inverse,
15
- Matrix,
16
17
  scale,
17
18
  translate,
18
19
  } from "transformation-matrix"
@@ -122,7 +123,7 @@ export const SchematicWithoutContext = ({
122
123
 
123
124
  updateTransform(newTransform)
124
125
  }
125
- }, [elements, bounds.width, bounds.height, updateTransform])
126
+ }, [elements, updateTransform])
126
127
 
127
128
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
128
129
  isDraggingRef.current = true
@@ -137,12 +138,16 @@ export const SchematicWithoutContext = ({
137
138
  const dy = e.clientY - lastMousePosRef.current.y
138
139
  lastMousePosRef.current = { x: e.clientX, y: e.clientY }
139
140
 
140
- const scale = transformRef.current.a // Assuming uniform scaling
141
- const dragSensitivity = 150 / scale // Adjust this value to change drag speed
141
+ // Transform the mouse movement to world space
142
+ const inverseTransform = inverse(transformRef.current)
143
+ const dragStart = applyToPoint(inverseTransform, { x: 0, y: 0 })
144
+ const dragEnd = applyToPoint(inverseTransform, { x: dx, y: dy })
145
+ const worldDx = dragEnd.x - dragStart.x
146
+ const worldDy = dragEnd.y - dragStart.y
142
147
 
143
148
  const newTransform = compose(
144
- translate(dx * dragSensitivity, dy * dragSensitivity),
145
149
  transformRef.current,
150
+ translate(worldDx, worldDy),
146
151
  )
147
152
  updateTransform(newTransform)
148
153
  },
@@ -156,7 +161,7 @@ export const SchematicWithoutContext = ({
156
161
  const handleWheel = useCallback(
157
162
  (e: WheelEvent) => {
158
163
  e.preventDefault()
159
- const scaleMultiplier = Math.pow(0.999, e.deltaY)
164
+ const scaleMultiplier = 0.999 ** e.deltaY
160
165
 
161
166
  if (containerRef.current) {
162
167
  const rect = containerRef.current.getBoundingClientRect()
@@ -228,7 +233,7 @@ export const SchematicWithoutContext = ({
228
233
  transform={transformRef.current}
229
234
  />
230
235
  {elements?.map((elm, i) => (
231
- <ErrorBoundary key={i} fallbackRender={fallbackRender(elm)}>
236
+ <ErrorBoundary key={`${elm}`} fallbackRender={fallbackRender(elm)}>
232
237
  <SchematicElement
233
238
  element={elm}
234
239
  allElements={elements}
@@ -0,0 +1,5 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap");
2
+
3
+ .schematic-text {
4
+ font-family: "IBM Plex Mono", monospace;
5
+ }
@@ -8,7 +8,7 @@ import {
8
8
  toSVG,
9
9
  translate,
10
10
  } from "transformation-matrix"
11
-
11
+ import "../pages/style.css"
12
12
  interface PathProps {
13
13
  type?: "path"
14
14
  strokeWidth: number
@@ -27,7 +27,19 @@ interface CircleProps {
27
27
  fill?: string
28
28
  }
29
29
 
30
- export type SVGElement = PathProps | CircleProps
30
+ interface TextProps {
31
+ type: "text"
32
+ cx: number
33
+ cy: number
34
+ text: string
35
+ fontSize?: number
36
+ fill: string
37
+ anchor?: "start" | "middle" | "end"
38
+ rotation?: number
39
+ stroke?: string
40
+ }
41
+
42
+ export type SVGElement = PathProps | CircleProps | TextProps
31
43
 
32
44
  interface Props {
33
45
  rotation: number
@@ -52,9 +64,10 @@ export const SVGPathComponent = ({
52
64
  }: Props) => {
53
65
  const ct = useGlobalStore((s) => s.camera_transform)
54
66
  const pathBounds = getSVGPathBounds(
55
- paths.filter((p): p is PathProps => p.type !== "circle").map((p) => p.d),
67
+ paths
68
+ .filter((p): p is PathProps => p.type !== "circle" && p.type !== "text")
69
+ .map((p) => p.d),
56
70
  )
57
-
58
71
  const padding = { x: 0, y: 0 }
59
72
  const absoluteCenter = applyToPoint(ct, center)
60
73
  const innerSize = {
@@ -65,17 +78,13 @@ export const SVGPathComponent = ({
65
78
  width: innerSize.width + padding.x * 2,
66
79
  height: innerSize.height + padding.y * 2,
67
80
  }
68
-
69
81
  const [hovering, setHovering] = useState(false)
70
-
71
82
  const svgLeft = absoluteCenter.x - fullSize.width / 2
72
83
  const svgTop = absoluteCenter.y - fullSize.height / 2
73
-
74
84
  const preferredRatio =
75
85
  pathBounds.width === 0
76
86
  ? innerSize.height / pathBounds.height
77
87
  : innerSize.width / pathBounds.width
78
-
79
88
  const svgToScreen = compose(
80
89
  scale(
81
90
  pathBounds.width === 0
@@ -88,10 +97,15 @@ export const SVGPathComponent = ({
88
97
  translate(-pathBounds.minX, -pathBounds.minY),
89
98
  )
90
99
 
100
+ const baseFontSize = 0.15 // Fixed base font size in schematic units
101
+
91
102
  return (
103
+ // biome-ignore lint/a11y/noSvgWithoutTitle: <explanation>
92
104
  <svg
93
105
  onMouseOver={() => setHovering(Boolean(hoverContent))}
106
+ onFocus={() => setHovering(Boolean(hoverContent))}
94
107
  onMouseOut={() => setHovering(false)}
108
+ onBlur={() => setHovering(false)}
95
109
  style={{
96
110
  position: "absolute",
97
111
  cursor: hovering ? "pointer" : undefined,
@@ -108,26 +122,51 @@ export const SVGPathComponent = ({
108
122
  width={fullSize.width}
109
123
  height={fullSize.height}
110
124
  >
111
- {paths.map((p, i) =>
112
- p.type === "circle" ? (
113
- <circle
114
- key={i}
115
- transform={toSVG(
116
- compose(
117
- scale(1, 1), // Add a smaller scale factor for circles
118
- svgToScreen,
119
- ),
120
- )}
121
- cx={p.cx}
122
- cy={p.cy}
123
- r={p.r}
124
- fill={"none"}
125
- strokeWidth={2.25 * (p.strokeWidth || 1)}
126
- stroke={p.stroke || "red"}
127
- />
128
- ) : (
125
+ {paths.map((p, i) => {
126
+ if (p.type === "circle") {
127
+ return (
128
+ <circle
129
+ key={`${p.type}-${i}`}
130
+ transform={toSVG(compose(scale(1, 1), svgToScreen))}
131
+ cx={p.cx}
132
+ cy={p.cy}
133
+ r={p.r}
134
+ fill={"none"}
135
+ strokeWidth={2.25 * (p.strokeWidth || 1)}
136
+ stroke={p.stroke || "red"}
137
+ />
138
+ )
139
+ }
140
+ if (p.type === "text") {
141
+ const transformedPos = applyToPoint(svgToScreen, { x: p.cx, y: p.cy })
142
+ const scaleFactor = fullSize.width / pathBounds.width || 1
143
+
144
+ return (
145
+ <g key={`${p.type}-${i}`}>
146
+ <text
147
+ className="schematic-text"
148
+ x={transformedPos.x}
149
+ y={transformedPos.y}
150
+ fill={p.fill}
151
+ fontSize={baseFontSize * scaleFactor}
152
+ textAnchor={p.anchor || "middle"}
153
+ dominantBaseline="middle"
154
+ transform={`scale(1,-1) rotate(${p.rotation || 0})`}
155
+ style={{
156
+ transformBox: "fill-box",
157
+ transformOrigin: "center",
158
+ }}
159
+ stroke={p.stroke}
160
+ >
161
+ {p.text}
162
+ </text>
163
+ </g>
164
+ )
165
+ }
166
+ // Handle the "path" type directly
167
+ return (
129
168
  <path
130
- key={i}
169
+ key={`${p.type}-${i}`}
131
170
  transform={toSVG(svgToScreen)}
132
171
  fill={p.fill ?? "none"}
133
172
  strokeLinecap="round"
@@ -135,8 +174,8 @@ export const SVGPathComponent = ({
135
174
  stroke={p.stroke || "red"}
136
175
  d={p.d || ""}
137
176
  />
138
- ),
139
- )}
177
+ )
178
+ })}
140
179
  </svg>
141
180
  )
142
181
  }
@@ -1,11 +1,11 @@
1
- import {
1
+ import type {
2
2
  AnyCircuitElement,
3
3
  SchematicPort as OriginalSchematicPort,
4
4
  SchematicComponent,
5
5
  } from "circuit-json"
6
- import * as Type from "lib/types"
6
+ import type * as Type from "lib/types"
7
7
  import { colorMap } from "lib/utils/colors"
8
- import React from "react"
8
+ import type React from "react"
9
9
  import SVGPathComponent from "./SVGPathComponent"
10
10
  import SchematicText from "./SchematicText"
11
11
 
@@ -37,7 +37,7 @@ export const SchematicChip: React.FC<Props> = ({
37
37
  const chipHeight = size.height
38
38
 
39
39
  const paths: Array<{
40
- type?: "path" | "circle"
40
+ type: "path" | "circle" | "text"
41
41
  strokeWidth: number
42
42
  stroke: string
43
43
  fill?: string
@@ -45,6 +45,9 @@ export const SchematicChip: React.FC<Props> = ({
45
45
  cx?: number
46
46
  cy?: number
47
47
  r?: number
48
+ text?: string
49
+ anchor?: string
50
+ rotation?: number
48
51
  }> = []
49
52
 
50
53
  // Main chip rectangle
@@ -62,31 +65,23 @@ export const SchematicChip: React.FC<Props> = ({
62
65
  item.schematic_component_id === schematic_component_id,
63
66
  )
64
67
 
65
- const portLength = 0.2
66
- const circleRadius = 0.05
68
+ const portLength = 0.5
69
+ const circleRadius = 0.04
67
70
  const labelOffset = 0.1
68
71
 
69
- const pinLabels: Array<{
70
- x: number
71
- y: number
72
- text: string
73
- anchor: string
74
- rotation: number
75
- }> = []
76
-
77
- schematicPorts.forEach((port) => {
72
+ for (const port of schematicPorts) {
78
73
  const { side, pinNumber, distanceFromEdge } = port.center
79
- let x = 0,
80
- y = 0,
81
- endX = 0,
82
- endY = 0
83
- let labelX = 0,
84
- labelY = 0
74
+ let x = 0
75
+ let y = 0
76
+ let endX = 0
77
+ let endY = 0
78
+ let pinX = 0
79
+ let pinY = 0
85
80
  let textAnchor = "middle"
86
81
  let rotation = 0
87
82
 
88
83
  if (side === "center") {
89
- return
84
+ continue
90
85
  }
91
86
 
92
87
  switch (side) {
@@ -95,17 +90,17 @@ export const SchematicChip: React.FC<Props> = ({
95
90
  y = -chipHeight / 2 + distanceFromEdge
96
91
  endX = x - portLength
97
92
  endY = y
98
- labelX = endX
99
- labelY = y + labelOffset
100
- textAnchor = "end"
93
+ pinX = x - portLength / 2
94
+ pinY = y + labelOffset
95
+ textAnchor = "middle"
101
96
  break
102
97
  case "right":
103
98
  x = chipWidth / 2
104
99
  y = chipHeight / 2 - distanceFromEdge
105
100
  endX = x + portLength
106
101
  endY = y
107
- labelX = endX - labelOffset
108
- labelY = y + labelOffset
102
+ pinX = x + portLength / 2 - labelOffset
103
+ pinY = y + labelOffset
109
104
  textAnchor = "start"
110
105
  break
111
106
  case "bottom":
@@ -113,8 +108,8 @@ export const SchematicChip: React.FC<Props> = ({
113
108
  y = -chipHeight / 2
114
109
  endX = x
115
110
  endY = y - portLength
116
- labelX = x
117
- labelY = endY + labelOffset
111
+ pinX = x - labelOffset
112
+ pinY = y - portLength / 2
118
113
  rotation = -90
119
114
  break
120
115
  case "top":
@@ -122,8 +117,8 @@ export const SchematicChip: React.FC<Props> = ({
122
117
  y = chipHeight / 2
123
118
  endX = x
124
119
  endY = y + portLength
125
- labelX = x
126
- labelY = endY + labelOffset
120
+ pinX = x - labelOffset
121
+ pinY = y + portLength / 2
127
122
  rotation = -90
128
123
  break
129
124
  }
@@ -142,22 +137,25 @@ export const SchematicChip: React.FC<Props> = ({
142
137
  cx: endX,
143
138
  cy: endY,
144
139
  r: circleRadius,
145
- strokeWidth: 0.01,
140
+ strokeWidth: 0.005,
146
141
  stroke: colorMap.schematic.component_outline,
147
142
  fill: colorMap.schematic.component_outline,
148
143
  })
149
144
 
150
- // Add pin label
145
+ // Add pin number
151
146
  if (pinNumber !== undefined) {
152
- pinLabels.push({
153
- x: labelX,
154
- y: labelY,
147
+ paths.push({
148
+ type: "text",
149
+ cx: pinX,
150
+ cy: pinY,
155
151
  text: `${pinNumber}`,
156
152
  anchor: textAnchor,
157
153
  rotation: rotation,
154
+ strokeWidth: 0.005,
155
+ stroke: colorMap.schematic.pin_number,
158
156
  })
159
157
  }
160
- })
158
+ }
161
159
 
162
160
  return (
163
161
  <>
@@ -167,24 +165,6 @@ export const SchematicChip: React.FC<Props> = ({
167
165
  size={size}
168
166
  paths={paths as any}
169
167
  />
170
- {pinLabels.map((label, index) => (
171
- <SchematicText
172
- key={index}
173
- schematic_text={{
174
- anchor: label.anchor as any,
175
- rotation: 0,
176
- position: {
177
- x: center.x + label.x,
178
- y: center.y + label.y,
179
- },
180
- schematic_component_id: "SYNTHETIC",
181
- schematic_text_id: `PIN_LABEL_${index}`,
182
- text: label.text,
183
- type: "schematic_text",
184
- color: colorMap.schematic.pin_number,
185
- }}
186
- />
187
- ))}
188
168
  <SchematicText
189
169
  schematic_text={{
190
170
  anchor: "right",
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  AnyCircuitElement,
3
3
  SchematicComponent as SchematicComponentType,
4
4
  } from "circuit-json"
@@ -1,4 +1,4 @@
1
- import { SchematicText as SchematicTextType } from "circuit-json"
1
+ import type { SchematicText as SchematicTextType } from "circuit-json"
2
2
  import { useGlobalStore } from "lib/render-context"
3
3
  import useMeasure from "react-use-measure"
4
4
  import { applyToPoint } from "transformation-matrix"
@@ -1,10 +1,9 @@
1
- import * as Type from "lib/types"
2
- import SVGPathComponent from "./SVGPathComponent"
3
- import Path from "svg-path-generator"
1
+ import type * as Type from "lib/types"
2
+ import { colorMap } from "lib/utils/colors"
4
3
  import getSVGPathBounds from "lib/utils/get-svg-path-bounds"
4
+ import Path from "svg-path-generator"
5
5
  import RenderError from "./RenderError"
6
- import SVGPathComponent2 from "./SVGPathComponent2"
7
- import { colorMap } from "lib/utils/colors"
6
+ import SVGPathComponent from "./SVGPathComponent"
8
7
 
9
8
  interface Props {
10
9
  trace: {
@@ -26,8 +26,13 @@ export const BugHighPortNumbers = () => {
26
26
  pins: [29],
27
27
  direction: "left-to-right",
28
28
  },
29
+ topSide: {
30
+ pins: [30],
31
+ direction: "left-to-right",
32
+ },
29
33
  }}
30
- schWidth={1}
34
+ schWidth={3}
35
+ schHeight={6}
31
36
  footprint="ssop28Db"
32
37
  pinLabels={{
33
38
  "1": "TXD",