@skillpet/circuit 0.6.2 → 0.6.4

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/README.ko.md ADDED
@@ -0,0 +1,150 @@
1
+ # @skillpet/circuit
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [Español](./README.es.md) | [Français](./README.fr.md) | [Deutsch](./README.de.md)
4
+
5
+ <p align="center">
6
+ <strong>회로도 라이브러리 — JSON에서 전기 회로도를 렌더링합니다. 인터랙티브 SVG, 테마, Vue / React 컴포넌트를 지원합니다.</strong>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/v/@skillpet/circuit.svg" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/l/@skillpet/circuit.svg" alt="license"></a>
12
+ <a href="https://circuit.skill.pet"><img src="https://img.shields.io/badge/docs-circuit.skill.pet-blue" alt="docs"></a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ **웹사이트 & 데모:** [circuit.skill.pet](https://circuit.skill.pet)
18
+
19
+ ## 특징
20
+
21
+ - 간단한 JSON 설명으로 회로도 렌더링
22
+ - 200개 이상의 내장 전기 부품 (저항, 커패시터, 다이오드, 트랜지스터, IC, 논리 게이트 등)
23
+ - 인터랙티브 SVG: 호버 하이라이트, 툴팁, 클릭 이벤트
24
+ - 3가지 내장 테마 (라이트, 다크, 인쇄) + 커스텀 테마
25
+ - 부품 간 부드러운 색상 전환
26
+ - Vue 3 & React 컴포넌트 기본 제공
27
+ - 브라우저 번들 (script 태그) & ESM / CJS 지원
28
+ - KaTeX 수학 레이블 렌더링
29
+ - 순서도, DSP 블록, 브레드보드 부품
30
+ - 런타임 의존성 없음 (KaTeX는 선택사항)
31
+
32
+ ## 설치
33
+
34
+ ```bash
35
+ npm install @skillpet/circuit
36
+ ```
37
+
38
+ ## 빠른 시작
39
+
40
+ ### ESM / TypeScript
41
+
42
+ ```ts
43
+ import { renderFromJson } from "@skillpet/circuit";
44
+
45
+ const svg = renderFromJson({
46
+ elements: [
47
+ { type: "SourceV", d: "up", label: "12V" },
48
+ { type: "ResistorIEEE", label: "R1 10kΩ" },
49
+ { type: "Capacitor", d: "down", label: "C1 100nF" },
50
+ { type: "Line", d: "left" },
51
+ { type: "Ground" },
52
+ ],
53
+ });
54
+ ```
55
+
56
+ ### 브라우저 (Script 태그)
57
+
58
+ ```html
59
+ <script src="https://unpkg.com/@skillpet/circuit/dist/circuit.bundle.min.js"></script>
60
+ <script>
61
+ const svg = Circuit.renderFromJson({
62
+ elements: [
63
+ { type: "ResistorIEEE", label: "R1" },
64
+ { type: "Capacitor", d: "down", label: "C1" },
65
+ ],
66
+ });
67
+ document.getElementById("output").innerHTML = svg;
68
+ </script>
69
+ ```
70
+
71
+ ### 인터랙티브 모드
72
+
73
+ DOM에 마운트하여 호버 하이라이트, 툴팁, 클릭 이벤트를 활성화:
74
+
75
+ ```ts
76
+ import { mountFromJson } from "@skillpet/circuit";
77
+
78
+ const ctrl = mountFromJson(document.getElementById("container"), {
79
+ elements: [
80
+ { type: "ResistorIEEE", id: "R1", tooltip: "100kΩ 탄소 피막" },
81
+ { type: "Capacitor", d: "down", id: "C1", tooltip: "0.1μF 세라믹" },
82
+ ],
83
+ }, { interactive: true });
84
+
85
+ ctrl.on("element:click", (info) => console.log("클릭:", info.id));
86
+ ctrl.on("element:hover", (info) => console.log("호버:", info.tooltip));
87
+ ```
88
+
89
+ ### Vue 3
90
+
91
+ ```vue
92
+ <script setup>
93
+ import { CircuitDiagram } from "@skillpet/circuit/vue";
94
+
95
+ const circuit = {
96
+ elements: [
97
+ { type: "ResistorIEEE", label: "R1", id: "R1", tooltip: "100kΩ" },
98
+ { type: "Capacitor", d: "down", label: "C1" },
99
+ ],
100
+ };
101
+ </script>
102
+
103
+ <template>
104
+ <CircuitDiagram :circuit="circuit" interactive @element-click="console.log" />
105
+ </template>
106
+ ```
107
+
108
+ ### React
109
+
110
+ ```tsx
111
+ import { CircuitDiagram } from "@skillpet/circuit/react";
112
+
113
+ function App() {
114
+ return (
115
+ <CircuitDiagram
116
+ circuit={{ elements: [{ type: "ResistorIEEE", label: "R1" }] }}
117
+ interactive
118
+ onElementClick={(info) => console.log(info)}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## 테마
125
+
126
+ 3가지 내장 테마: `light` (기본값), `dark`, `print`.
127
+
128
+ ```ts
129
+ const svg = renderFromJson(circuit, { theme: "dark" });
130
+ ```
131
+
132
+ ## 색상 전환
133
+
134
+ 서로 다른 색상의 부품 간 부드러운 그라데이션 전환:
135
+
136
+ ```ts
137
+ const svg = renderFromJson({
138
+ drawing: { colorTransition: true },
139
+ elements: [
140
+ { type: "SourceV", d: "up", color: "#2ecc71" },
141
+ { type: "ResistorIEEE", color: "#e74c3c" },
142
+ ],
143
+ }, { colorTransition: true });
144
+ ```
145
+
146
+ ## 라이선스
147
+
148
+ 개인 및 교육 목적은 무료입니다. 상업적 사용에는 별도의 라이선스가 필요합니다.
149
+ 자세한 내용은 이 패키지에 포함된 LICENSE 파일을 참조하세요.
150
+ 상용 라이선스 문의: **license@skill.pet** 또는 [circuit.skill.pet/license](https://circuit.skill.pet/license).
package/README.md CHANGED
@@ -1,9 +1,34 @@
1
1
  # @skillpet/circuit
2
2
 
3
- A circuit diagram library for rendering electrical schematics from JSON. Produces interactive SVG with hover tooltips, click events, theming, and color transitions. Ships with Vue 3 and React components.
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [Español](./README.es.md) | [Français](./README.fr.md) | [Deutsch](./README.de.md)
4
+
5
+ <p align="center">
6
+ <strong>Circuit diagram library — render electrical schematics from JSON, with interactive SVG, themes, and Vue / React components.</strong>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/v/@skillpet/circuit.svg" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/l/@skillpet/circuit.svg" alt="license"></a>
12
+ <a href="https://circuit.skill.pet"><img src="https://img.shields.io/badge/docs-circuit.skill.pet-blue" alt="docs"></a>
13
+ </p>
14
+
15
+ ---
4
16
 
5
17
  **Website & Demos:** [circuit.skill.pet](https://circuit.skill.pet)
6
18
 
19
+ ## Features
20
+
21
+ - Render circuit diagrams from a simple JSON description
22
+ - 200+ built-in electrical components (resistors, capacitors, diodes, transistors, ICs, logic gates, etc.)
23
+ - Interactive SVG: hover highlights, tooltips, click events
24
+ - 3 built-in themes (light, dark, print) + custom themes
25
+ - Smooth color transitions between elements
26
+ - Vue 3 & React components out of the box
27
+ - Browser bundle (script tag) & ESM / CJS support
28
+ - KaTeX math label rendering
29
+ - Flow charts, DSP blocks, pictorial breadboard components
30
+ - Zero runtime dependencies (except optional KaTeX)
31
+
7
32
  ## Installation
8
33
 
9
34
  ```bash
@@ -0,0 +1,150 @@
1
+ # @skillpet/circuit
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [Español](./README.es.md) | [Français](./README.fr.md) | [Deutsch](./README.de.md)
4
+
5
+ <p align="center">
6
+ <strong>电路图绘制库 — 从 JSON 描述渲染电气原理图,支持交互式 SVG、主题切换与 Vue / React 组件。</strong>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/v/@skillpet/circuit.svg" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/@skillpet/circuit"><img src="https://img.shields.io/npm/l/@skillpet/circuit.svg" alt="license"></a>
12
+ <a href="https://circuit.skill.pet"><img src="https://img.shields.io/badge/docs-circuit.skill.pet-blue" alt="docs"></a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ **官网与在线演示:** [circuit.skill.pet](https://circuit.skill.pet)
18
+
19
+ ## 特性
20
+
21
+ - 通过简洁的 JSON 描述渲染电路图
22
+ - 200+ 内置电气元件(电阻、电容、二极管、晶体管、集成电路、逻辑门等)
23
+ - 交互式 SVG:悬停高亮、工具提示、点击事件
24
+ - 3 种内置主题(浅色、深色、打印)+ 自定义主题
25
+ - 元件间平滑的颜色过渡
26
+ - 开箱即用的 Vue 3 与 React 组件
27
+ - 浏览器 Bundle(script 标签)与 ESM / CJS 支持
28
+ - KaTeX 数学公式标签渲染
29
+ - 流程图、DSP 模块、实物面包板元件
30
+ - 零运行时依赖(KaTeX 为可选)
31
+
32
+ ## 安装
33
+
34
+ ```bash
35
+ npm install @skillpet/circuit
36
+ ```
37
+
38
+ ## 快速上手
39
+
40
+ ### ESM / TypeScript
41
+
42
+ ```ts
43
+ import { renderFromJson } from "@skillpet/circuit";
44
+
45
+ const svg = renderFromJson({
46
+ elements: [
47
+ { type: "SourceV", d: "up", label: "12V" },
48
+ { type: "ResistorIEEE", label: "R1 10kΩ" },
49
+ { type: "Capacitor", d: "down", label: "C1 100nF" },
50
+ { type: "Line", d: "left" },
51
+ { type: "Ground" },
52
+ ],
53
+ });
54
+ ```
55
+
56
+ ### 浏览器(Script 标签)
57
+
58
+ ```html
59
+ <script src="https://unpkg.com/@skillpet/circuit/dist/circuit.bundle.min.js"></script>
60
+ <script>
61
+ const svg = Circuit.renderFromJson({
62
+ elements: [
63
+ { type: "ResistorIEEE", label: "R1" },
64
+ { type: "Capacitor", d: "down", label: "C1" },
65
+ ],
66
+ });
67
+ document.getElementById("output").innerHTML = svg;
68
+ </script>
69
+ ```
70
+
71
+ ### 交互模式
72
+
73
+ 挂载到 DOM,支持悬停高亮、工具提示和点击事件:
74
+
75
+ ```ts
76
+ import { mountFromJson } from "@skillpet/circuit";
77
+
78
+ const ctrl = mountFromJson(document.getElementById("container"), {
79
+ elements: [
80
+ { type: "ResistorIEEE", id: "R1", tooltip: "100kΩ 碳膜电阻" },
81
+ { type: "Capacitor", d: "down", id: "C1", tooltip: "0.1μF 瓷片电容" },
82
+ ],
83
+ }, { interactive: true });
84
+
85
+ ctrl.on("element:click", (info) => console.log("点击:", info.id));
86
+ ctrl.on("element:hover", (info) => console.log("悬停:", info.tooltip));
87
+ ```
88
+
89
+ ### Vue 3
90
+
91
+ ```vue
92
+ <script setup>
93
+ import { CircuitDiagram } from "@skillpet/circuit/vue";
94
+
95
+ const circuit = {
96
+ elements: [
97
+ { type: "ResistorIEEE", label: "R1", id: "R1", tooltip: "100kΩ" },
98
+ { type: "Capacitor", d: "down", label: "C1" },
99
+ ],
100
+ };
101
+ </script>
102
+
103
+ <template>
104
+ <CircuitDiagram :circuit="circuit" interactive @element-click="console.log" />
105
+ </template>
106
+ ```
107
+
108
+ ### React
109
+
110
+ ```tsx
111
+ import { CircuitDiagram } from "@skillpet/circuit/react";
112
+
113
+ function App() {
114
+ return (
115
+ <CircuitDiagram
116
+ circuit={{ elements: [{ type: "ResistorIEEE", label: "R1" }] }}
117
+ interactive
118
+ onElementClick={(info) => console.log(info)}
119
+ />
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## 主题
125
+
126
+ 三种内置主题:`light`(默认)、`dark` 和 `print`。
127
+
128
+ ```ts
129
+ const svg = renderFromJson(circuit, { theme: "dark" });
130
+ ```
131
+
132
+ ## 颜色过渡
133
+
134
+ 不同颜色元件之间平滑的渐变过渡:
135
+
136
+ ```ts
137
+ const svg = renderFromJson({
138
+ drawing: { colorTransition: true },
139
+ elements: [
140
+ { type: "SourceV", d: "up", color: "#2ecc71" },
141
+ { type: "ResistorIEEE", color: "#e74c3c" },
142
+ ],
143
+ }, { colorTransition: true });
144
+ ```
145
+
146
+ ## 许可证
147
+
148
+ 个人和教育用途免费。商业使用需要单独授权。
149
+ 详见本包中的 LICENSE 文件。
150
+ 商业授权请联系 **license@skill.pet** 或访问 [circuit.skill.pet/license](https://circuit.skill.pet/license)。
@@ -15539,8 +15539,8 @@ var Circuit = (() => {
15539
15539
  let [x0, y0] = this.xform(x, y);
15540
15540
  const fontsize = opts.fontsize ?? 14;
15541
15541
  let font = opts.fontfamily ?? "sans-serif";
15542
- if (font.toLowerCase() === "sans-serif" || font.toLowerCase() === "arial") {
15543
- font = "sans";
15542
+ if (font.toLowerCase() === "sans" || font.toLowerCase() === "arial") {
15543
+ font = "sans-serif";
15544
15544
  }
15545
15545
  const valign = opts.valign ?? "center";
15546
15546
  if (valign === "base") {
@@ -15552,14 +15552,13 @@ var Circuit = (() => {
15552
15552
  const rot = opts.rotation ?? 0;
15553
15553
  const transform = rot !== 0 ? ` transform="rotate(${fmt(rot)} ${fmt(x0)} ${fmt(y0)})"` : "";
15554
15554
  const color = opts.color ?? "black";
15555
- const textY = y0 - fontsize;
15556
15555
  let textContent;
15557
15556
  if (opts.textDecoration) {
15558
15557
  textContent = `<tspan text-decoration="${opts.textDecoration}">${escapeXml(s)}</tspan>`;
15559
15558
  } else {
15560
15559
  textContent = escapeXml(s);
15561
15560
  }
15562
- let inner2 = `<text x="${fmt(x0)}" y="${fmt(textY)}" dominant-baseline="${baseline}" fill="${escapeXml(color)}" font-size="${fmt(fontsize)}" font-family="${escapeXml(font)}" text-anchor="${anchor}"${transform}><tspan x="${fmt(x0)}" dy="${fmt(fontsize)}" dominant-baseline="${baseline}">${textContent}</tspan></text>`;
15561
+ let inner2 = `<text x="${fmt(x0)}" y="${fmt(y0)}" dominant-baseline="${baseline}" fill="${escapeXml(color)}" font-size="${fmt(fontsize)}" font-family="${escapeXml(font)}" text-anchor="${anchor}"${transform}>${textContent}</text>`;
15563
15562
  if (opts.href) {
15564
15563
  inner2 = `<a href="${escapeXml(opts.href)}">${inner2}</a>`;
15565
15564
  }
@@ -17738,51 +17737,55 @@ var Circuit = (() => {
17738
17737
  const p = mergeParamsFirstWins(el.userParams, el.elmParams, el.defaults, el.dwgParams);
17739
17738
  return p.color ?? "black";
17740
17739
  }
17741
- function getOutputAnchors(el) {
17742
- const pts = [];
17743
- const aa = el.absanchors;
17744
- if (aa.end) pts.push(aa.end);
17745
- if (aa.out && (!aa.end || !pointsClose(aa.out, aa.end, 0.01))) pts.push(aa.out);
17746
- return pts;
17740
+ var EXCLUDED_ANCHORS = /* @__PURE__ */ new Set([
17741
+ "xy",
17742
+ "center",
17743
+ "istart",
17744
+ "iend",
17745
+ "mid",
17746
+ "label",
17747
+ "vd",
17748
+ "vs",
17749
+ "n1",
17750
+ "n2",
17751
+ "n1a",
17752
+ "n2a"
17753
+ ]);
17754
+ function isConnectionAnchor(name) {
17755
+ return !EXCLUDED_ANCHORS.has(name);
17747
17756
  }
17748
- function getInputAnchors(el) {
17757
+ function getConnectionAnchors(el) {
17749
17758
  const pts = [];
17750
- const aa = el.absanchors;
17751
- if (aa.start) pts.push(aa.start);
17752
- for (const [name, pt] of Object.entries(aa)) {
17753
- if (name.startsWith("in") && /^in\d+$/.test(name)) {
17754
- if (!aa.start || !pointsClose(pt, aa.start, 0.01)) pts.push(pt);
17755
- }
17759
+ for (const [name, pt] of Object.entries(el.absanchors)) {
17760
+ if (isConnectionAnchor(name)) pts.push(pt);
17756
17761
  }
17757
17762
  return pts;
17758
17763
  }
17759
- function pointsClose(a, b, tol) {
17760
- return Math.hypot(a.x - b.x, a.y - b.y) <= tol;
17761
- }
17762
17764
  function buildConnectionGraph(elements) {
17763
17765
  const edges = [];
17764
17766
  const seen = /* @__PURE__ */ new Set();
17765
17767
  const tol = 0.5;
17766
17768
  for (let i = 0; i < elements.length; i++) {
17767
- const fromEl = elements[i];
17768
- const fromPts = getOutputAnchors(fromEl);
17769
- if (fromPts.length === 0) continue;
17770
- const fromColor = resolveElementColor(fromEl);
17771
- for (let j = 0; j < elements.length; j++) {
17772
- if (i === j) continue;
17773
- const toEl = elements[j];
17774
- const toPts = getInputAnchors(toEl);
17775
- if (toPts.length === 0) continue;
17776
- const toColor = resolveElementColor(toEl);
17777
- if (colorsEqual(fromColor, toColor)) continue;
17778
- for (const fp of fromPts) {
17779
- for (const tp of toPts) {
17780
- if (!pointsClose(fp, tp, tol)) continue;
17769
+ const elA = elements[i];
17770
+ const colorA = resolveElementColor(elA);
17771
+ const anchorsA = getConnectionAnchors(elA);
17772
+ if (anchorsA.length === 0) continue;
17773
+ for (let j = i + 1; j < elements.length; j++) {
17774
+ const elB = elements[j];
17775
+ const colorB = resolveElementColor(elB);
17776
+ if (colorsEqual(colorA, colorB)) continue;
17777
+ const anchorsB = getConnectionAnchors(elB);
17778
+ if (anchorsB.length === 0) continue;
17779
+ for (const pa of anchorsA) {
17780
+ for (const pb of anchorsB) {
17781
+ if (Math.hypot(pa.x - pb.x, pa.y - pb.y) > tol) continue;
17781
17782
  const key = `${i}|${j}`;
17782
17783
  if (seen.has(key)) continue;
17783
17784
  seen.add(key);
17784
- edges.push({ fromEl, toEl, fromColor, toColor, junctionPt: fp });
17785
+ edges.push({ elA, elB, colorA, colorB, junctionPt: pa });
17786
+ break;
17785
17787
  }
17788
+ if (seen.has(`${i}|${j}`)) break;
17786
17789
  }
17787
17790
  }
17788
17791
  }
@@ -17798,26 +17801,28 @@ var Circuit = (() => {
17798
17801
  const cleanups = [];
17799
17802
  let n = 0;
17800
17803
  for (const edge of edges) {
17801
- const lead2Seg = findLeadSegmentFor(edge.fromEl, "out", edge.junctionPt);
17802
- const lead1Seg = findLeadSegmentFor(edge.toEl, "in", edge.junctionPt);
17803
- if (!lead2Seg && !lead1Seg) continue;
17804
- if (lead2Seg?.gradientStrokeId && lead1Seg?.gradientStrokeId) continue;
17804
+ const segA = findSegAtJunction(edge.elA, edge.junctionPt);
17805
+ const segB = findSegAtJunction(edge.elB, edge.junctionPt);
17806
+ if (!segA && !segB) continue;
17807
+ if (segA?.gradientStrokeId && segB?.gradientStrokeId) continue;
17805
17808
  let gx1, gy1, gx2, gy2;
17806
- if (lead2Seg && lead1Seg) {
17807
- const from = segEndpointsSvg(edge.fromEl, lead2Seg, scale);
17808
- const to = segEndpointsSvg(edge.toEl, lead1Seg, scale);
17809
- gx1 = from.x1;
17810
- gy1 = from.y1;
17811
- gx2 = to.x2;
17812
- gy2 = to.y2;
17813
- } else if (lead2Seg) {
17814
- const c = segEndpointsSvg(edge.fromEl, lead2Seg, scale);
17809
+ if (segA && segB) {
17810
+ const a = segEndpointsSvg(edge.elA, segA, scale);
17811
+ const b = segEndpointsSvg(edge.elB, segB, scale);
17812
+ const aNear = nearFarSvg(edge.elA, segA, edge.junctionPt, scale);
17813
+ const bNear = nearFarSvg(edge.elB, segB, edge.junctionPt, scale);
17814
+ gx1 = aNear.farX;
17815
+ gy1 = aNear.farY;
17816
+ gx2 = bNear.farX;
17817
+ gy2 = bNear.farY;
17818
+ } else if (segA) {
17819
+ const c = segEndpointsSvg(edge.elA, segA, scale);
17815
17820
  gx1 = c.x1;
17816
17821
  gy1 = c.y1;
17817
17822
  gx2 = c.x2;
17818
17823
  gy2 = c.y2;
17819
17824
  } else {
17820
- const c = segEndpointsSvg(edge.toEl, lead1Seg, scale);
17825
+ const c = segEndpointsSvg(edge.elB, segB, scale);
17821
17826
  gx1 = c.x1;
17822
17827
  gy1 = c.y1;
17823
17828
  gx2 = c.x2;
@@ -17825,17 +17830,17 @@ var Circuit = (() => {
17825
17830
  }
17826
17831
  if (gradientVectorLength({ x1: gx1, y1: gy1, x2: gx2, y2: gy2 }) < 1e-6) continue;
17827
17832
  const id = `sd-trans-${n++}`;
17828
- parts.push(gradientXml(id, gx1, gy1, gx2, gy2, edge.fromColor, edge.toColor));
17829
- if (lead2Seg && !lead2Seg.gradientStrokeId) {
17830
- lead2Seg.gradientStrokeId = id;
17833
+ parts.push(gradientXml(id, gx1, gy1, gx2, gy2, edge.colorA, edge.colorB));
17834
+ if (segA && !segA.gradientStrokeId) {
17835
+ segA.gradientStrokeId = id;
17831
17836
  cleanups.push(() => {
17832
- lead2Seg.gradientStrokeId = void 0;
17837
+ segA.gradientStrokeId = void 0;
17833
17838
  });
17834
17839
  }
17835
- if (lead1Seg && !lead1Seg.gradientStrokeId) {
17836
- lead1Seg.gradientStrokeId = id;
17840
+ if (segB && !segB.gradientStrokeId) {
17841
+ segB.gradientStrokeId = id;
17837
17842
  cleanups.push(() => {
17838
- lead1Seg.gradientStrokeId = void 0;
17843
+ segB.gradientStrokeId = void 0;
17839
17844
  });
17840
17845
  }
17841
17846
  }
@@ -17846,26 +17851,21 @@ var Circuit = (() => {
17846
17851
  }
17847
17852
  };
17848
17853
  }
17849
- function findLeadSegmentFor(el, side, junctionPt) {
17850
- const role = side === "in" ? "lead1" : "lead2";
17854
+ function findSegAtJunction(el, junctionPt) {
17855
+ const tol = 0.6;
17851
17856
  for (const s of el.segments) {
17852
- if (s instanceof Segment && s.role === role) return s;
17857
+ if (!(s instanceof Segment)) continue;
17858
+ if (s.role !== "lead1" && s.role !== "lead2") continue;
17859
+ if (segTouchesPoint(el, s, junctionPt, tol)) return s;
17853
17860
  }
17854
- return findSegmentNearPoint(el, junctionPt);
17855
- }
17856
- function findSegmentNearPoint(el, absPt) {
17857
17861
  let best;
17858
17862
  let bestDist = Infinity;
17859
- const tol = 0.6;
17860
17863
  for (const s of el.segments) {
17861
17864
  if (!(s instanceof Segment)) continue;
17862
17865
  if (s.path.length < 2) continue;
17863
17866
  if (s.role === "body") continue;
17864
- const p0 = el.transform.transform(s.path[0]);
17865
- const pN = el.transform.transform(s.path[s.path.length - 1]);
17866
- const d0 = Math.hypot(p0.x - absPt.x, p0.y - absPt.y);
17867
- const dN = Math.hypot(pN.x - absPt.x, pN.y - absPt.y);
17868
- const d = Math.min(d0, dN);
17867
+ if (s.path.length > 6) continue;
17868
+ const d = segDistToPoint(el, s, junctionPt);
17869
17869
  if (d < bestDist && d < tol) {
17870
17870
  bestDist = d;
17871
17871
  best = s;
@@ -17873,6 +17873,25 @@ var Circuit = (() => {
17873
17873
  }
17874
17874
  return best;
17875
17875
  }
17876
+ function segTouchesPoint(el, seg, absPt, tol) {
17877
+ return segDistToPoint(el, seg, absPt) < tol;
17878
+ }
17879
+ function segDistToPoint(el, seg, absPt) {
17880
+ const p0 = el.transform.transform(seg.path[0]);
17881
+ const pN = el.transform.transform(seg.path[seg.path.length - 1]);
17882
+ return Math.min(
17883
+ Math.hypot(p0.x - absPt.x, p0.y - absPt.y),
17884
+ Math.hypot(pN.x - absPt.x, pN.y - absPt.y)
17885
+ );
17886
+ }
17887
+ function nearFarSvg(el, seg, junctionPt, scale) {
17888
+ const p0 = el.transform.transform(seg.path[0]);
17889
+ const pN = el.transform.transform(seg.path[seg.path.length - 1]);
17890
+ const d0 = Math.hypot(p0.x - junctionPt.x, p0.y - junctionPt.y);
17891
+ const dN = Math.hypot(pN.x - junctionPt.x, pN.y - junctionPt.y);
17892
+ const far = d0 > dN ? p0 : pN;
17893
+ return { farX: far.x * scale, farY: -far.y * scale };
17894
+ }
17876
17895
  function segEndpointsSvg(el, seg, scale) {
17877
17896
  const p0 = el.transform.transform(seg.path[0]);
17878
17897
  const p1 = el.transform.transform(seg.path[seg.path.length - 1]);