project-graph-mcp 2.3.1 → 2.4.0

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.
Files changed (226) hide show
  1. package/package.json +3 -2
  2. package/src/analysis/analysis-cache.ctx +9 -0
  3. package/src/analysis/analysis-cache.js +1 -1
  4. package/src/analysis/complexity.ctx +6 -0
  5. package/src/analysis/complexity.js +1 -1
  6. package/src/analysis/custom-rules.ctx +14 -0
  7. package/src/analysis/custom-rules.js +1 -1
  8. package/src/analysis/db-analysis.ctx +7 -0
  9. package/src/analysis/db-analysis.js +1 -1
  10. package/src/analysis/dead-code.ctx +6 -0
  11. package/src/analysis/dead-code.js +1 -1
  12. package/src/analysis/full-analysis.ctx +9 -0
  13. package/src/analysis/full-analysis.js +1 -1
  14. package/src/analysis/jsdoc-checker.ctx +10 -0
  15. package/src/analysis/jsdoc-checker.js +1 -1
  16. package/src/analysis/jsdoc-generator.ctx +9 -0
  17. package/src/analysis/jsdoc-generator.js +1 -1
  18. package/src/analysis/large-files.ctx +6 -0
  19. package/src/analysis/large-files.js +1 -1
  20. package/src/analysis/outdated-patterns.ctx +7 -0
  21. package/src/analysis/outdated-patterns.js +1 -1
  22. package/src/analysis/similar-functions.ctx +6 -0
  23. package/src/analysis/similar-functions.js +1 -1
  24. package/src/analysis/test-annotations.ctx +11 -0
  25. package/src/analysis/test-annotations.js +1 -1
  26. package/src/analysis/type-checker.ctx +6 -0
  27. package/src/analysis/type-checker.js +1 -1
  28. package/src/analysis/undocumented.ctx +8 -0
  29. package/src/analysis/undocumented.js +1 -1
  30. package/src/cli/cli-handlers.ctx +7 -0
  31. package/src/cli/cli-handlers.js +1 -1
  32. package/src/cli/cli.ctx +6 -0
  33. package/src/cli/cli.js +1 -1
  34. package/src/compact/ai-context.ctx +6 -0
  35. package/src/compact/ai-context.js +1 -1
  36. package/src/compact/compact-migrate.ctx +8 -0
  37. package/src/compact/compact-migrate.js +1 -1
  38. package/src/compact/compact.ctx +11 -0
  39. package/src/compact/compact.js +1 -1
  40. package/src/compact/compress.ctx +7 -0
  41. package/src/compact/compress.js +1 -1
  42. package/src/compact/ctx-resolver.ctx +2 -0
  43. package/src/compact/ctx-resolver.js +1 -1
  44. package/src/compact/ctx-to-jsdoc.ctx +11 -0
  45. package/src/compact/ctx-to-jsdoc.js +1 -1
  46. package/src/compact/doc-dialect.ctx +11 -0
  47. package/src/compact/doc-dialect.js +2 -2
  48. package/src/compact/expand.ctx +14 -0
  49. package/src/compact/expand.js +1 -1
  50. package/src/compact/framework-references.ctx +7 -0
  51. package/src/compact/framework-references.js +1 -1
  52. package/src/compact/instructions.ctx +6 -0
  53. package/src/compact/instructions.js +1 -1
  54. package/src/compact/jsdoc-builder.ctx +4 -0
  55. package/src/compact/jsdoc-builder.js +1 -1
  56. package/src/compact/mode-config.ctx +8 -0
  57. package/src/compact/mode-config.js +1 -1
  58. package/src/compact/split-declarations.ctx +6 -0
  59. package/src/compact/split-declarations.js +1 -1
  60. package/src/compact/validate-pipeline.ctx +12 -0
  61. package/src/compact/validate-pipeline.js +1 -1
  62. package/src/core/event-bus.ctx +9 -0
  63. package/src/core/event-bus.js +1 -1
  64. package/src/core/file-walker.ctx +1 -0
  65. package/src/core/file-walker.js +1 -1
  66. package/src/core/filters.ctx +12 -0
  67. package/src/core/filters.js +1 -1
  68. package/src/core/graph-builder.ctx +7 -0
  69. package/src/core/graph-builder.js +1 -1
  70. package/src/core/parser.ctx +12 -0
  71. package/src/core/parser.js +1 -1
  72. package/src/core/utils.ctx +1 -0
  73. package/src/core/utils.js +1 -1
  74. package/src/core/workspace.ctx +7 -0
  75. package/src/core/workspace.js +1 -1
  76. package/src/lang/lang-go.ctx +8 -0
  77. package/src/lang/lang-go.js +1 -1
  78. package/src/lang/lang-python.ctx +5 -0
  79. package/src/lang/lang-python.js +1 -1
  80. package/src/lang/lang-sql.ctx +10 -0
  81. package/src/lang/lang-sql.js +1 -1
  82. package/src/lang/lang-typescript.ctx +6 -0
  83. package/src/lang/lang-typescript.js +1 -1
  84. package/src/lang/lang-utils.ctx +5 -0
  85. package/src/lang/lang-utils.js +1 -1
  86. package/src/mcp/mcp-server.ctx +6 -0
  87. package/src/mcp/mcp-server.js +1 -1
  88. package/src/mcp/tool-defs.ctx +2 -0
  89. package/src/mcp/tool-defs.js +1 -1
  90. package/src/mcp/tools.ctx +13 -0
  91. package/src/mcp/tools.js +1 -1
  92. package/src/network/backend-lifecycle.ctx +10 -0
  93. package/src/network/backend-lifecycle.js +1 -1
  94. package/src/network/backend.ctx +5 -0
  95. package/src/network/backend.js +1 -1
  96. package/src/network/local-gateway.ctx +9 -0
  97. package/src/network/local-gateway.js +1 -1
  98. package/src/network/mdns.ctx +6 -0
  99. package/src/network/mdns.js +1 -1
  100. package/src/network/server.ctx +2 -0
  101. package/src/network/server.js +2 -2
  102. package/src/network/web-server.ctx +17 -0
  103. package/src/network/web-server.js +2 -2
  104. package/web/follow-controller.js +94 -25
  105. package/web/panels/dep-graph.js +207 -21
  106. package/project-graph-mcp-2.3.0.tgz +0 -0
  107. package/vendor/symbiote-node/CHANGELOG.md +0 -31
  108. package/vendor/symbiote-node/LICENSE +0 -21
  109. package/vendor/symbiote-node/README.md +0 -206
  110. package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
  111. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
  112. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
  113. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
  114. package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
  115. package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
  116. package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
  117. package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
  118. package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
  119. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
  120. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
  121. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
  122. package/vendor/symbiote-node/canvas/LODManager.js +0 -88
  123. package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
  124. package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
  125. package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
  126. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
  127. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
  128. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
  129. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
  130. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
  131. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
  132. package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
  133. package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
  134. package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
  135. package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
  136. package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
  137. package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
  138. package/vendor/symbiote-node/core/Connection.js +0 -45
  139. package/vendor/symbiote-node/core/Editor.js +0 -451
  140. package/vendor/symbiote-node/core/Frame.js +0 -31
  141. package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
  142. package/vendor/symbiote-node/core/GraphText.js +0 -210
  143. package/vendor/symbiote-node/core/Node.js +0 -143
  144. package/vendor/symbiote-node/core/Portal.js +0 -104
  145. package/vendor/symbiote-node/core/Socket.js +0 -185
  146. package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
  147. package/vendor/symbiote-node/index.js +0 -103
  148. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
  149. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
  150. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
  151. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
  152. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
  153. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
  154. package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
  155. package/vendor/symbiote-node/interactions/Drag.js +0 -102
  156. package/vendor/symbiote-node/interactions/Selector.js +0 -132
  157. package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
  158. package/vendor/symbiote-node/interactions/Zoom.js +0 -140
  159. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
  160. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
  161. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
  162. package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
  163. package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
  164. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
  165. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
  166. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
  167. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
  168. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
  169. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
  170. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
  171. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
  172. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
  173. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
  174. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
  175. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
  176. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
  177. package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
  178. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
  179. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
  180. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
  181. package/vendor/symbiote-node/layout/index.js +0 -16
  182. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
  183. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
  184. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
  185. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
  186. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
  187. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
  188. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
  189. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
  190. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
  191. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
  192. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
  193. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
  194. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
  195. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
  196. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
  197. package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
  198. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
  199. package/vendor/symbiote-node/package.json +0 -59
  200. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
  201. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
  202. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
  203. package/vendor/symbiote-node/plugins/History.js +0 -384
  204. package/vendor/symbiote-node/plugins/Readonly.js +0 -59
  205. package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
  206. package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
  207. package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
  208. package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
  209. package/vendor/symbiote-node/shapes/PillShape.js +0 -91
  210. package/vendor/symbiote-node/shapes/RectShape.js +0 -72
  211. package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
  212. package/vendor/symbiote-node/shapes/index.js +0 -53
  213. package/vendor/symbiote-node/themes/Palette.js +0 -32
  214. package/vendor/symbiote-node/themes/Skin.js +0 -113
  215. package/vendor/symbiote-node/themes/Theme.js +0 -84
  216. package/vendor/symbiote-node/themes/carbon.js +0 -137
  217. package/vendor/symbiote-node/themes/dark.js +0 -137
  218. package/vendor/symbiote-node/themes/ebook.js +0 -138
  219. package/vendor/symbiote-node/themes/grey.js +0 -137
  220. package/vendor/symbiote-node/themes/light.js +0 -137
  221. package/vendor/symbiote-node/themes/neon.js +0 -138
  222. package/vendor/symbiote-node/themes/pcb.js +0 -273
  223. package/vendor/symbiote-node/themes/synthwave.js +0 -137
  224. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
  225. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
  226. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
@@ -1,584 +0,0 @@
1
- /**
2
- * NodeViewManager — creates/destroys graph-node elements with group drag
3
- *
4
- * Handles DOM creation, drag initialization with snap-to-grid,
5
- * group drag for multi-selected nodes, and twitch click detection.
6
- * Extracted from NodeCanvas to reduce complexity (was 27 cyclomatic).
7
- *
8
- * @module symbiote-node/canvas/NodeViewManager
9
- */
10
-
11
- import { Drag } from '../interactions/Drag.js';
12
- import { Selector } from '../interactions/Selector.js';
13
- import { animateOut } from '@symbiotejs/symbiote';
14
- import { getShape } from '../shapes/index.js';
15
-
16
- export class NodeViewManager {
17
-
18
- /** @type {Map<string, HTMLElement>} */
19
- #nodeViews;
20
-
21
- /** @type {import('../core/Editor.js').NodeEditor} */
22
- #editor;
23
-
24
- /** @type {import('../interactions/Selector.js').Selector} */
25
- #selector;
26
-
27
- /** @type {import('../interactions/SnapGrid.js').SnapGrid} */
28
- #snapGrid;
29
-
30
- /** @type {function} */
31
- #getZoom;
32
-
33
- /** @type {function} */
34
- #setNodePosition;
35
-
36
- /** @type {function} */
37
- #animateNodeToPosition;
38
-
39
- /** @type {function} */
40
- #onNodeClick;
41
-
42
- /** @type {Object} */
43
- #canvas;
44
-
45
- /** @type {function|null} */
46
- #onSvgShapeReady = null;
47
-
48
- /** @type {boolean} */
49
- #readonly = false;
50
-
51
- /** @type {boolean} */
52
- #snapEnabled = false;
53
-
54
- /** @type {HTMLElement} */
55
- #nodesLayer;
56
-
57
- /** @type {number} Z-index counter: increments on each select/drag */
58
- #zCounter = 1;
59
-
60
- /**
61
- * @param {object} config
62
- * @param {Map<string, HTMLElement>} config.nodeViews - shared Map
63
- * @param {import('../core/Editor.js').NodeEditor} config.editor
64
- * @param {import('../interactions/Selector.js').Selector} config.selector
65
- * @param {import('../interactions/SnapGrid.js').SnapGrid} config.snapGrid
66
- * @param {function} config.getZoom
67
- * @param {function} config.setNodePosition
68
- * @param {function} config.animateNodeToPosition
69
- * @param {function} config.onNodeClick
70
- * @param {HTMLElement} config.nodesLayer
71
- * @param {Object} config.canvas - NodeCanvas reference for socket registration
72
- */
73
- constructor({ nodeViews, editor, selector, snapGrid, getZoom, setNodePosition, animateNodeToPosition, onNodeClick, nodesLayer, canvas, onSvgShapeReady }) {
74
- this.#nodeViews = nodeViews;
75
- this.#editor = editor;
76
- this.#selector = selector;
77
- this.#snapGrid = snapGrid;
78
- this.#getZoom = getZoom;
79
- this.#setNodePosition = setNodePosition;
80
- this.#animateNodeToPosition = animateNodeToPosition;
81
- this.#onNodeClick = onNodeClick;
82
- this.#nodesLayer = nodesLayer;
83
- this.#canvas = canvas;
84
- this.#onSvgShapeReady = onSvgShapeReady || null;
85
- }
86
-
87
- /** @param {boolean} readonly */
88
- setReadonly(readonly) {
89
- this.#readonly = readonly;
90
- }
91
-
92
- /** @param {boolean} enabled */
93
- setSnapEnabled(enabled) {
94
- this.#snapEnabled = enabled;
95
- }
96
-
97
- /**
98
- * Create and append multiple node views in a single DOM batch.
99
- * This prevents layout thrashing and O(N) mutation overhead during graph inflation.
100
- * @param {import('../core/Node.js').Node[]} nodes
101
- */
102
- addViews(nodes) {
103
- if (!nodes || nodes.length === 0) return;
104
-
105
- const fragment = document.createDocumentFragment();
106
-
107
- // 1. Create all elements and bind them (no DOM append yet)
108
- for (const node of nodes) {
109
- const el = this.#createNodeElement(node);
110
- fragment.appendChild(el);
111
- this.#nodeViews.set(node.id, el);
112
- }
113
-
114
- // 2. Single batch insert into live DOM
115
- this.#nodesLayer.appendChild(fragment);
116
-
117
- // 3. Post-processing (SVG injection, preview canvas) requires elements to be in DOM
118
- for (const node of nodes) {
119
- const el = this.#nodeViews.get(node.id);
120
- if (el) this.#postProcessNodeView(node, el);
121
- }
122
- }
123
-
124
- /**
125
- * Create a graph-node element for a Node
126
- * @param {import('../core/Node.js').Node} node
127
- */
128
- addView(node) {
129
- const el = this.#createNodeElement(node);
130
- this.#nodesLayer.appendChild(el);
131
- this.#nodeViews.set(node.id, el);
132
- this.#postProcessNodeView(node, el);
133
- }
134
-
135
- /**
136
- * Creates the HTMLElement for a node with its drag behavior initialized.
137
- * Does NOT append to the live DOM layer.
138
- * @private
139
- * @param {import('../core/Node.js').Node} node
140
- * @returns {HTMLElement}
141
- */
142
- #createNodeElement(node) {
143
- const el = document.createElement('graph-node');
144
- el.style.position = 'absolute';
145
- el.style.transform = 'translate(0px, 0px)';
146
- el._position = { x: 0, y: 0 };
147
- el._nodeData = node;
148
- el.setAttribute('node-id', node.id);
149
- el.setAttribute('node-label', node.label);
150
- el.setAttribute('node-category', node.category);
151
- el.setAttribute('node-shape', node.shape);
152
- el.setAttribute('node-type', node.type || 'default');
153
- el._canvas = this.#canvas;
154
-
155
- const drag = new Drag();
156
- let dragStart = null;
157
-
158
- drag.initialize(
159
- el,
160
- {
161
- getPosition: () => el._position,
162
- getZoom: this.#getZoom,
163
- },
164
- {
165
- shouldStart: (e) => {
166
- // SVG shapes: only start drag if click is inside the SVG path
167
- const svgPath = el.querySelector('svg > path');
168
- if (!svgPath) return true; // not an SVG shape node
169
- const svg = svgPath.ownerSVGElement;
170
- const rect = svg.getBoundingClientRect();
171
- const vb = svg.viewBox.baseVal;
172
- // Convert page coords to SVG viewBox coords
173
- const sx = (e.clientX - rect.left) / rect.width * vb.width + vb.x;
174
- const sy = (e.clientY - rect.top) / rect.height * vb.height + vb.y;
175
- const pt = new DOMPoint(sx, sy);
176
- return svgPath.isPointInFill(pt);
177
- },
178
- onStart: (e) => {
179
- dragStart = { x: e.pageX, y: e.pageY };
180
- this.#autoSelectOnDragStart(node.id, e);
181
- this.#captureDragStartPositions();
182
- this.#bringToFront(node.id);
183
- this.#applyLift(el);
184
- this.#editor.emit('nodepicked', node);
185
- },
186
- onTranslate: (x, y) => {
187
- this.#handleGroupTranslate(node.id, el, x, y);
188
- },
189
- onDrop: (e) => {
190
- this.#handleDrop(node.id, el, e, dragStart);
191
- dragStart = null;
192
- },
193
- }
194
- );
195
- el._drag = drag;
196
-
197
- return el;
198
- }
199
-
200
- /**
201
- * Applies SVG shaping or subgraph previews after the element is in the live DOM.
202
- * @private
203
- * @param {import('../core/Node.js').Node} node
204
- * @param {HTMLElement} el
205
- */
206
- #postProcessNodeView(node, el) {
207
- // Apply shape visuals: SVG background layer instead of clip-path
208
- // Clip-path clips content (labels, ports). SVG bg preserves them.
209
- const shape = getShape(node.shape);
210
- if (shape && shape.pathData) {
211
- // Set explicit element dimensions to match SVG viewBox aspect ratio
212
- // This ensures correct proportions and reliable offsetWidth/Height
213
- const vb = shape.viewBox.split(' ').map(Number);
214
- const vbW = vb[2];
215
- const vbH = vb[3];
216
- const baseSize = 120; // base dimension
217
- const aspect = vbW / vbH;
218
- const nodeW = aspect >= 1 ? baseSize : Math.round(baseSize * aspect);
219
- const nodeH = aspect >= 1 ? Math.round(baseSize / aspect) : baseSize;
220
- el.style.width = nodeW + 'px';
221
- el.style.height = nodeH + 'px';
222
- el.style.minWidth = nodeW + 'px';
223
- el.style.minHeight = nodeH + 'px';
224
- }
225
-
226
- requestAnimationFrame(() => {
227
- if (shape && shape.pathData) {
228
- const size = { width: el.offsetWidth, height: el.offsetHeight };
229
-
230
- // 1. Inject SVG background — element is properly proportioned
231
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
232
- svg.setAttribute('viewBox', shape.viewBox);
233
- svg.setAttribute('preserveAspectRatio', 'none');
234
- svg.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:visible;';
235
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
236
- path.setAttribute('d', shape.pathData);
237
- path.setAttribute('fill', `var(--sn-shape-${shape.name}-fill, var(--sn-shape-fill, var(--sn-node-bg, #16213e)))`);
238
- path.setAttribute('stroke', `var(--sn-shape-${shape.name}-stroke, var(--sn-shape-stroke, var(--sn-node-border, #2a2a4a)))`);
239
- path.setAttribute('stroke-width', 'var(--sn-shape-stroke-width, 0.4)');
240
- path.setAttribute('stroke-linejoin', 'round');
241
- svg.appendChild(path);
242
- el.prepend(svg);
243
- el.setAttribute('data-svg-shape', shape.name);
244
-
245
- // Make node background transparent — SVG provides the shape
246
- el.style.background = 'transparent';
247
- el.style.border = 'none';
248
- el.style.boxShadow = 'none';
249
- el.style.borderRadius = '0';
250
- el.style.overflow = 'visible';
251
-
252
- // Elevate content above SVG layer
253
- for (const child of el.children) {
254
- if (child !== svg) child.style.position = 'relative';
255
- }
256
-
257
- // Watermark icon — large pale category icon centered inside shape
258
- const iconEl = el.querySelector('.sn-node-icon');
259
- if (iconEl) {
260
- const watermark = document.createElement('span');
261
- watermark.className = 'sn-shape-watermark material-symbols-outlined';
262
- watermark.textContent = iconEl.textContent;
263
- el.appendChild(watermark);
264
- }
265
-
266
- // Notify canvas to render free dots for this SVG node
267
- if (this.#onSvgShapeReady) this.#onSvgShapeReady(node.id);
268
-
269
-
270
- } else if (shape) {
271
- // Standard shapes: apply border-radius
272
- const size = { width: el.offsetWidth || 180, height: el.offsetHeight || 60 };
273
- const radius = shape.getBorderRadius(size);
274
- if (radius && radius !== 'var(--sn-node-radius, 10px)') {
275
- el.style.borderRadius = radius;
276
- }
277
- }
278
- });
279
-
280
- // Subgraph preview canvas — inject DOM element synchronously so
281
- // measureNodeSizes() includes the 80px canvas in offsetHeight.
282
- // Only the drawing is deferred to rAF (needs inner editor data).
283
- if (node._isSubgraph) {
284
- const body = el.querySelector('.sn-node-body');
285
- if (body) {
286
- const canvas = document.createElement('canvas');
287
- canvas.className = 'sn-subgraph-preview';
288
- canvas.width = 200;
289
- canvas.height = 80;
290
- body.appendChild(canvas);
291
- el._previewCanvas = canvas;
292
- requestAnimationFrame(() => {
293
- this.#initSubgraphPreview(el, node, canvas);
294
- });
295
- }
296
- }
297
- }
298
-
299
- /**
300
- * Remove a graph-node element
301
- * @param {import('../core/Node.js').Node} node
302
- */
303
- removeView(node) {
304
- const el = this.#nodeViews.get(node.id);
305
- if (!el) return;
306
- if (el._previewRaf) clearTimeout(el._previewRaf);
307
- el._previewRaf = null;
308
- if (el._drag) el._drag.destroy();
309
- animateOut(el);
310
- this.#nodeViews.delete(node.id);
311
- this.#selector.getSelectedNodes().delete(node.id);
312
- }
313
-
314
- /**
315
- * Remove a node view instantly (no animation) for virtualization demote.
316
- * Returns captured position/size for phantom conversion.
317
- * @param {string} nodeId
318
- * @returns {{ x: number, y: number, w: number, h: number } | null}
319
- */
320
- removeViewInstant(nodeId) {
321
- const el = this.#nodeViews.get(nodeId);
322
- if (!el) return null;
323
- const pos = el._position || { x: 0, y: 0 };
324
- const w = el._cachedW || el.offsetWidth || 180;
325
- const h = el._cachedH || el.offsetHeight || 60;
326
- if (el._previewRaf) clearTimeout(el._previewRaf);
327
- if (el._drag) el._drag.destroy();
328
- el.remove();
329
- this.#nodeViews.delete(nodeId);
330
- this.#selector.getSelectedNodes().delete(nodeId);
331
- return { x: pos.x, y: pos.y, w, h };
332
- }
333
-
334
- // --- Private helpers ---
335
-
336
-
337
-
338
-
339
- #autoSelectOnDragStart(nodeId, e) {
340
- if (!this.#selector.isNodeSelected(nodeId)) {
341
- const accumulate = e.ctrlKey || e.metaKey;
342
- this.#selector.selectNode(nodeId, accumulate);
343
- }
344
- this.#bringToFront(nodeId);
345
- }
346
-
347
- /**
348
- * Bring a node to front by setting highest z-index
349
- * @param {string} nodeId
350
- */
351
- #bringToFront(nodeId) {
352
- const el = this.#nodeViews.get(nodeId);
353
- if (el) {
354
- el.style.zIndex = ++this.#zCounter;
355
- }
356
- }
357
-
358
- /**
359
- * Apply lift effect: scale up + shadow + parallax offset
360
- * @param {HTMLElement} el
361
- */
362
- #applyLift(el) {
363
- el.classList.add('sn-node-lifted');
364
- }
365
-
366
- /**
367
- * Remove lift effect on drop
368
- * @param {HTMLElement} el
369
- */
370
- #removeLift(el) {
371
- el.classList.remove('sn-node-lifted');
372
- }
373
-
374
- #captureDragStartPositions() {
375
- const selected = this.#selector.getSelectedNodes();
376
- for (const id of selected) {
377
- const nodeEl = this.#nodeViews.get(id);
378
- if (nodeEl) nodeEl._dragStartPos = { ...nodeEl._position };
379
- }
380
- }
381
-
382
- #handleGroupTranslate(nodeId, el, x, y) {
383
- let finalX = x;
384
- let finalY = y;
385
-
386
- if (this.#snapEnabled && this.#snapGrid.isDynamic) {
387
- const snapped = this.#snapGrid.snap(x, y);
388
- finalX = snapped.x;
389
- finalY = snapped.y;
390
- }
391
-
392
- const prev = el._dragStartPos || el._position;
393
- const dx = finalX - prev.x;
394
- const dy = finalY - prev.y;
395
-
396
- const selected = this.#selector.getSelectedNodes();
397
- if (selected.size > 1 && selected.has(nodeId)) {
398
- for (const id of selected) {
399
- const nodeEl = this.#nodeViews.get(id);
400
- if (!nodeEl?._dragStartPos) continue;
401
- let nx = nodeEl._dragStartPos.x + dx;
402
- let ny = nodeEl._dragStartPos.y + dy;
403
- if (this.#snapEnabled && this.#snapGrid.isDynamic) {
404
- const snapped = this.#snapGrid.snap(nx, ny);
405
- nx = snapped.x;
406
- ny = snapped.y;
407
- }
408
- this.#setNodePosition(id, nx, ny);
409
- }
410
- } else {
411
- this.#setNodePosition(nodeId, finalX, finalY);
412
- }
413
-
414
- this.#editor.emit('nodetranslated', { id: nodeId, position: { x: finalX, y: finalY } });
415
- }
416
-
417
- #handleDrop(nodeId, el, e, dragStart) {
418
- // Static snap on drop
419
- if (this.#snapEnabled && !this.#snapGrid.isDynamic) {
420
- const selected = this.#selector.getSelectedNodes();
421
- const targets = selected.size > 0 && selected.has(nodeId) ? selected : new Set([nodeId]);
422
- for (const id of targets) {
423
- const nodeEl = this.#nodeViews.get(id);
424
- if (!nodeEl) continue;
425
- const snapped = this.#snapGrid.snap(nodeEl._position.x, nodeEl._position.y);
426
- this.#animateNodeToPosition(id, snapped.x, snapped.y);
427
- }
428
- }
429
-
430
- // Clean up start positions
431
- for (const [, nodeEl] of this.#nodeViews) {
432
- delete nodeEl._dragStartPos;
433
- }
434
-
435
- // Remove lift effect
436
- this.#removeLift(el);
437
-
438
- // Click vs drag detection
439
- if (dragStart && e && Selector.isTwitch(dragStart, { x: e.pageX, y: e.pageY })) {
440
- this.#onNodeClick(nodeId, e);
441
- }
442
-
443
- this.#editor.emit('nodedragged', { id: nodeId });
444
- }
445
-
446
- /**
447
- * Initialize subgraph preview canvas inside a graph-node
448
- * @param {HTMLElement} el - graph-node element
449
- * @param {import('../core/SubgraphNode.js').SubgraphNode} node
450
- * @param {HTMLCanvasElement} canvas - pre-created canvas element (already in DOM)
451
- */
452
- #initSubgraphPreview(el, node, canvas) {
453
- const ctx = canvas.getContext('2d');
454
-
455
- const drawPreview = () => {
456
- if (!el.isConnected) return;
457
-
458
- const w = canvas.width;
459
- const h = canvas.height;
460
- ctx.clearRect(0, 0, w, h);
461
-
462
- const innerEditor = node.innerEditor;
463
- if (!innerEditor) return;
464
-
465
- const nodes = innerEditor.getNodes();
466
- if (nodes.length === 0) return;
467
-
468
- // Get positions (from saved or auto-grid)
469
- const positions = node.innerPositions;
470
- const nodeRects = [];
471
-
472
- for (const n of nodes) {
473
- const pos = positions[n.id];
474
- const x = pos ? pos.x : 0;
475
- const y = pos ? pos.y : 0;
476
- nodeRects.push({ x, y, w: 160, h: 60, id: n.id });
477
- }
478
-
479
- // Calculate bounds
480
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
481
- for (const r of nodeRects) {
482
- minX = Math.min(minX, r.x);
483
- minY = Math.min(minY, r.y);
484
- maxX = Math.max(maxX, r.x + r.w);
485
- maxY = Math.max(maxY, r.y + r.h);
486
- }
487
-
488
- const pad = 30;
489
- minX -= pad; minY -= pad;
490
- maxX += pad; maxY += pad;
491
-
492
- const graphW = maxX - minX;
493
- const graphH = maxY - minY;
494
- const scale = Math.min(w / graphW, h / graphH);
495
- const offsetX = (w - graphW * scale) / 2;
496
- const offsetY = (h - graphH * scale) / 2;
497
-
498
- // Flow state map: nodeId -> 'processing' | 'completed'
499
- const states = el._innerFlowStates || {};
500
-
501
- // Draw connections as lines
502
- const conns = innerEditor.getConnections();
503
- for (const conn of conns) {
504
- const src = nodeRects.find(r => r.id === conn.from);
505
- const tgt = nodeRects.find(r => r.id === conn.to);
506
- if (src && tgt) {
507
- const sx = (src.x + src.w - minX) * scale + offsetX;
508
- const sy = (src.y + src.h / 2 - minY) * scale + offsetY;
509
- const tx = (tgt.x - minX) * scale + offsetX;
510
- const ty = (tgt.y + tgt.h / 2 - minY) * scale + offsetY;
511
-
512
- // Flowing connection: source completed
513
- const srcState = states[conn.from];
514
- if (srcState === 'completed') {
515
- ctx.strokeStyle = 'rgba(92, 216, 122, 0.5)';
516
- ctx.lineWidth = 2;
517
- } else {
518
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.12)';
519
- ctx.lineWidth = 1;
520
- }
521
-
522
- ctx.beginPath();
523
- ctx.moveTo(sx, sy);
524
- ctx.lineTo(tx, ty);
525
- ctx.stroke();
526
- }
527
- }
528
-
529
- // Draw node rectangles with flow state
530
- for (const r of nodeRects) {
531
- const rx = (r.x - minX) * scale + offsetX;
532
- const ry = (r.y - minY) * scale + offsetY;
533
- const rw = r.w * scale;
534
- const rh = r.h * scale;
535
- const state = states[r.id];
536
- const radius = 4;
537
-
538
- // Rounded rect helper
539
- ctx.beginPath();
540
- ctx.moveTo(rx + radius, ry);
541
- ctx.lineTo(rx + rw - radius, ry);
542
- ctx.quadraticCurveTo(rx + rw, ry, rx + rw, ry + radius);
543
- ctx.lineTo(rx + rw, ry + rh - radius);
544
- ctx.quadraticCurveTo(rx + rw, ry + rh, rx + rw - radius, ry + rh);
545
- ctx.lineTo(rx + radius, ry + rh);
546
- ctx.quadraticCurveTo(rx, ry + rh, rx, ry + rh - radius);
547
- ctx.lineTo(rx, ry + radius);
548
- ctx.quadraticCurveTo(rx, ry, rx + radius, ry);
549
- ctx.closePath();
550
-
551
- if (state === 'processing') {
552
- ctx.fillStyle = 'rgba(74, 158, 255, 0.25)';
553
- ctx.fill();
554
- ctx.strokeStyle = 'rgba(74, 158, 255, 0.8)';
555
- ctx.lineWidth = 1.5;
556
- ctx.stroke();
557
- // Glow effect
558
- ctx.shadowColor = 'rgba(74, 158, 255, 0.6)';
559
- ctx.shadowBlur = 8;
560
- ctx.stroke();
561
- ctx.shadowBlur = 0;
562
- } else if (state === 'completed') {
563
- ctx.fillStyle = 'rgba(92, 216, 122, 0.2)';
564
- ctx.fill();
565
- ctx.strokeStyle = 'rgba(92, 216, 122, 0.7)';
566
- ctx.lineWidth = 1;
567
- ctx.stroke();
568
- } else {
569
- ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
570
- ctx.fill();
571
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
572
- ctx.lineWidth = 0.5;
573
- ctx.stroke();
574
- }
575
- }
576
- };
577
-
578
- // Expose redraw for external triggering (FlowSimulator)
579
- el._redrawPreview = drawPreview;
580
-
581
- // Draw once. Re-draw on demand via el._redrawPreview().
582
- drawPreview();
583
- }
584
- }
@@ -1,131 +0,0 @@
1
- export class PinExpansion {
2
- /** @type {import('./NodeCanvas/NodeCanvas.js').NodeCanvas} */
3
- #canvas;
4
-
5
- /** @type {Map<string, Array<object>>} Cache of pins per nodeId */
6
- #pinCache = new Map();
7
-
8
- /** @type {Function} Callback when a pin is clicked */
9
- #onPinClick;
10
-
11
- /**
12
- * @param {import('./NodeCanvas/NodeCanvas.js').NodeCanvas} canvas
13
- * @param {object} config
14
- * @param {Function} [config.onPinClick]
15
- */
16
- constructor(canvas, { onPinClick } = {}) {
17
- this.#canvas = canvas;
18
- this.#onPinClick = onPinClick || (() => {});
19
- }
20
-
21
- /**
22
- * Add pins data for a specific node
23
- * @param {string} nodeId
24
- * @param {Array<object>} pins
25
- */
26
- setPins(nodeId, pins) {
27
- if (pins && pins.length > 0) {
28
- this.#pinCache.set(nodeId, pins);
29
- } else {
30
- this.#pinCache.delete(nodeId);
31
- }
32
- }
33
-
34
- clearPins() {
35
- this.#pinCache.clear();
36
- }
37
-
38
- /**
39
- * Remove pins and overlay for a node
40
- * @param {string} nodeId
41
- */
42
- removePins(nodeId) {
43
- this.#pinCache.delete(nodeId);
44
- const el = this.#canvas.getNodeView?.(nodeId);
45
- if (!el) return;
46
- const overlay = el.querySelector('.pcb-pin-overlay');
47
- if (overlay) overlay.remove();
48
- }
49
-
50
- /**
51
- * Apply LOD state to render or hide pins
52
- * @param {'expanded'|'collapsed'} lod
53
- */
54
- applyLOD(lod) {
55
- if (!this.#canvas) return;
56
-
57
- for (const [nodeId, pins] of this.#pinCache) {
58
- const el = this.#canvas.getNodeView?.(nodeId);
59
- if (!el) continue;
60
-
61
- if (lod === 'expanded') {
62
- this.#renderPinsForNode(el, pins);
63
- } else {
64
- const overlay = el.querySelector('.pcb-pin-overlay');
65
- if (overlay) overlay.removeAttribute('data-visible');
66
- }
67
- }
68
- }
69
-
70
- /**
71
- * Render pin labels around a node element's border
72
- * @param {HTMLElement} el
73
- * @param {Array<object>} pins
74
- */
75
- #renderPinsForNode(el, pins) {
76
- if (!pins || pins.length === 0) return;
77
-
78
- // Create or reuse pin overlay
79
- let overlay = el.querySelector('.pcb-pin-overlay');
80
- if (!overlay) {
81
- overlay = document.createElement('div');
82
- overlay.className = 'pcb-pin-overlay';
83
- el.appendChild(overlay);
84
- }
85
-
86
- // Prepare pins if they are empty
87
- if (overlay.children.length === 0) {
88
- const maxPins = Math.min(pins.length, 12);
89
- const half = Math.ceil(maxPins / 2);
90
- const nodeId = el.getAttribute('node-id');
91
-
92
- const createPinEl = (pin, side, yPct) => {
93
- const pinEl = document.createElement('span');
94
- pinEl.className = 'pcb-pin';
95
- pinEl.setAttribute('data-side', side);
96
- if (pin.kind) pinEl.setAttribute('data-kind', pin.kind);
97
-
98
- const suffix = pin.line ? ` :${pin.line}` : '';
99
- const label = pin.label || pin.name || '';
100
- pinEl.textContent = label + suffix;
101
- pinEl.style.top = `${yPct}%`;
102
-
103
- if (pin.interactable !== false) {
104
- pinEl.style.cursor = 'pointer';
105
- pinEl.title = pin.tooltip || (pin.line ? `${pin.file || ''}:${pin.line}` : (pin.file || ''));
106
- pinEl.addEventListener('click', (e) => {
107
- e.stopPropagation();
108
- this.#onPinClick(pin, nodeId);
109
- });
110
- }
111
-
112
- return pinEl;
113
- };
114
-
115
- // Right side: first half
116
- for (let i = 0; i < half; i++) {
117
- const yPct = ((i + 1) / (half + 1)) * 100;
118
- overlay.appendChild(createPinEl(pins[i], 'right', yPct));
119
- }
120
-
121
- // Left side: remaining
122
- for (let i = half; i < maxPins; i++) {
123
- const yPct = ((i - half + 1) / (maxPins - half + 1)) * 100;
124
- overlay.appendChild(createPinEl(pins[i], 'left', yPct));
125
- }
126
- }
127
-
128
- // Animate in
129
- requestAnimationFrame(() => overlay.setAttribute('data-visible', ''));
130
- }
131
- }