project-graph-mcp 2.3.2 → 2.4.1

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 (279) 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/engine/AgentUICommands.js +0 -100
  148. package/vendor/symbiote-node/engine/Executor.js +0 -371
  149. package/vendor/symbiote-node/engine/Graph.js +0 -314
  150. package/vendor/symbiote-node/engine/GraphServer.js +0 -353
  151. package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
  152. package/vendor/symbiote-node/engine/History.js +0 -83
  153. package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
  154. package/vendor/symbiote-node/engine/Persistence.js +0 -84
  155. package/vendor/symbiote-node/engine/Registry.js +0 -264
  156. package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
  157. package/vendor/symbiote-node/engine/cli.js +0 -404
  158. package/vendor/symbiote-node/engine/index.js +0 -56
  159. package/vendor/symbiote-node/engine/nanoid.js +0 -28
  160. package/vendor/symbiote-node/engine/package.json +0 -26
  161. package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
  162. package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
  163. package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
  164. package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
  165. package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
  166. package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
  167. package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
  168. package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
  169. package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
  170. package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
  171. package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
  172. package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
  173. package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
  174. package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
  175. package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
  176. package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
  177. package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
  178. package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
  179. package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
  180. package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
  181. package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
  182. package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
  183. package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
  184. package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
  185. package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
  186. package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
  187. package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
  188. package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
  189. package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
  190. package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
  191. package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
  192. package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
  193. package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
  194. package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
  195. package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
  196. package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
  197. package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
  198. package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
  199. package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
  200. package/vendor/symbiote-node/index.js +0 -103
  201. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
  202. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
  203. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
  204. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
  205. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
  206. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
  207. package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
  208. package/vendor/symbiote-node/interactions/Drag.js +0 -102
  209. package/vendor/symbiote-node/interactions/Selector.js +0 -132
  210. package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
  211. package/vendor/symbiote-node/interactions/Zoom.js +0 -140
  212. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
  213. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
  214. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
  215. package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
  216. package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
  217. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
  218. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
  219. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
  220. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
  221. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
  222. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
  223. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
  224. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
  225. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
  226. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
  227. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
  228. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
  229. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
  230. package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
  231. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
  232. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
  233. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
  234. package/vendor/symbiote-node/layout/index.js +0 -16
  235. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
  236. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
  237. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
  238. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
  239. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
  240. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
  241. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
  242. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
  243. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
  244. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
  245. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
  246. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
  247. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
  248. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
  249. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
  250. package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
  251. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
  252. package/vendor/symbiote-node/package.json +0 -59
  253. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
  254. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
  255. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
  256. package/vendor/symbiote-node/plugins/History.js +0 -384
  257. package/vendor/symbiote-node/plugins/Readonly.js +0 -59
  258. package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
  259. package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
  260. package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
  261. package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
  262. package/vendor/symbiote-node/shapes/PillShape.js +0 -91
  263. package/vendor/symbiote-node/shapes/RectShape.js +0 -72
  264. package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
  265. package/vendor/symbiote-node/shapes/index.js +0 -53
  266. package/vendor/symbiote-node/themes/Palette.js +0 -32
  267. package/vendor/symbiote-node/themes/Skin.js +0 -113
  268. package/vendor/symbiote-node/themes/Theme.js +0 -84
  269. package/vendor/symbiote-node/themes/carbon.js +0 -137
  270. package/vendor/symbiote-node/themes/dark.js +0 -137
  271. package/vendor/symbiote-node/themes/ebook.js +0 -138
  272. package/vendor/symbiote-node/themes/grey.js +0 -137
  273. package/vendor/symbiote-node/themes/light.js +0 -137
  274. package/vendor/symbiote-node/themes/neon.js +0 -138
  275. package/vendor/symbiote-node/themes/pcb.js +0 -273
  276. package/vendor/symbiote-node/themes/synthwave.js +0 -137
  277. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
  278. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
  279. 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
- }