@zenode/designer 3.5.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 (529) hide show
  1. package/APIs.md +60 -0
  2. package/LICENSE +12 -0
  3. package/README.md +219 -0
  4. package/dist/components/canvas/canvas.cjs +106 -0
  5. package/dist/components/canvas/canvas.cjs.map +1 -0
  6. package/dist/components/canvas/canvas.d.ts +15 -0
  7. package/dist/components/canvas/canvas.js +84 -0
  8. package/dist/components/canvas/canvas.js.map +1 -0
  9. package/dist/components/canvas/grid.cjs +167 -0
  10. package/dist/components/canvas/grid.cjs.map +1 -0
  11. package/dist/components/canvas/grid.d.ts +5 -0
  12. package/dist/components/canvas/grid.js +144 -0
  13. package/dist/components/canvas/grid.js.map +1 -0
  14. package/dist/components/shapeTypes/circle.d.ts +2 -0
  15. package/dist/components/shapeTypes/rectangle.d.ts +4 -0
  16. package/dist/components/shapeTypes/rectangle.js +22 -0
  17. package/dist/components/shapeTypes/rectangle.js.map +1 -0
  18. package/dist/components/shapeTypes/rhombus.d.ts +1 -0
  19. package/dist/config/defaultConfig.cjs +542 -0
  20. package/dist/config/defaultConfig.cjs.map +1 -0
  21. package/dist/config/defaultConfig.d.ts +2 -0
  22. package/dist/config/defaultConfig.js +540 -0
  23. package/dist/config/defaultConfig.js.map +1 -0
  24. package/dist/config/testConfig.cjs +553 -0
  25. package/dist/config/testConfig.cjs.map +1 -0
  26. package/dist/config/testConfig.d.ts +2 -0
  27. package/dist/config/testConfig.js +551 -0
  28. package/dist/config/testConfig.js.map +1 -0
  29. package/dist/config/testConfig1.d.ts +2 -0
  30. package/dist/config/testXML.cjs +17 -0
  31. package/dist/config/testXML.cjs.map +1 -0
  32. package/dist/config/testXML.d.ts +1 -0
  33. package/dist/config/testXML.js +15 -0
  34. package/dist/config/testXML.js.map +1 -0
  35. package/dist/connections/paths/curved.cjs +34 -0
  36. package/dist/connections/paths/curved.cjs.map +1 -0
  37. package/dist/connections/paths/curved.d.ts +2 -0
  38. package/dist/connections/paths/curved.js +32 -0
  39. package/dist/connections/paths/curved.js.map +1 -0
  40. package/dist/connections/paths/index.cjs +16 -0
  41. package/dist/connections/paths/index.cjs.map +1 -0
  42. package/dist/connections/paths/index.d.ts +12 -0
  43. package/dist/connections/paths/index.js +14 -0
  44. package/dist/connections/paths/index.js.map +1 -0
  45. package/dist/connections/paths/l-bent.cjs +59 -0
  46. package/dist/connections/paths/l-bent.cjs.map +1 -0
  47. package/dist/connections/paths/l-bent.d.ts +5 -0
  48. package/dist/connections/paths/l-bent.js +57 -0
  49. package/dist/connections/paths/l-bent.js.map +1 -0
  50. package/dist/connections/paths/s-shaped.cjs +24 -0
  51. package/dist/connections/paths/s-shaped.cjs.map +1 -0
  52. package/dist/connections/paths/s-shaped.d.ts +5 -0
  53. package/dist/connections/paths/s-shaped.js +22 -0
  54. package/dist/connections/paths/s-shaped.js.map +1 -0
  55. package/dist/connections/paths/straight.cjs +9 -0
  56. package/dist/connections/paths/straight.cjs.map +1 -0
  57. package/dist/connections/paths/straight.d.ts +2 -0
  58. package/dist/connections/paths/straight.js +7 -0
  59. package/dist/connections/paths/straight.js.map +1 -0
  60. package/dist/connections/render.cjs +382 -0
  61. package/dist/connections/render.cjs.map +1 -0
  62. package/dist/connections/render.d.ts +29 -0
  63. package/dist/connections/render.js +360 -0
  64. package/dist/connections/render.js.map +1 -0
  65. package/dist/connections/routing/index.d.ts +14 -0
  66. package/dist/connections/routing/smartRouter.cjs +26 -0
  67. package/dist/connections/routing/smartRouter.cjs.map +1 -0
  68. package/dist/connections/routing/smartRouter.d.ts +13 -0
  69. package/dist/connections/routing/smartRouter.js +24 -0
  70. package/dist/connections/routing/smartRouter.js.map +1 -0
  71. package/dist/contextpad/defaults.cjs +181 -0
  72. package/dist/contextpad/defaults.cjs.map +1 -0
  73. package/dist/contextpad/defaults.d.ts +2 -0
  74. package/dist/contextpad/defaults.js +179 -0
  75. package/dist/contextpad/defaults.js.map +1 -0
  76. package/dist/contextpad/registry.cjs +54 -0
  77. package/dist/contextpad/registry.cjs.map +1 -0
  78. package/dist/contextpad/registry.d.ts +21 -0
  79. package/dist/contextpad/registry.js +52 -0
  80. package/dist/contextpad/registry.js.map +1 -0
  81. package/dist/contextpad/renderer.cjs +347 -0
  82. package/dist/contextpad/renderer.cjs.map +1 -0
  83. package/dist/contextpad/renderer.d.ts +32 -0
  84. package/dist/contextpad/renderer.js +326 -0
  85. package/dist/contextpad/renderer.js.map +1 -0
  86. package/dist/core/engine.cjs +2254 -0
  87. package/dist/core/engine.cjs.map +1 -0
  88. package/dist/core/engine.d.ts +444 -0
  89. package/dist/core/engine.js +2233 -0
  90. package/dist/core/engine.js.map +1 -0
  91. package/dist/core/eventManager.cjs +29 -0
  92. package/dist/core/eventManager.cjs.map +1 -0
  93. package/dist/core/eventManager.d.ts +6 -0
  94. package/dist/core/eventManager.js +27 -0
  95. package/dist/core/eventManager.js.map +1 -0
  96. package/dist/core/history/command.cjs +182 -0
  97. package/dist/core/history/command.cjs.map +1 -0
  98. package/dist/core/history/command.d.ts +119 -0
  99. package/dist/core/history/command.js +171 -0
  100. package/dist/core/history/command.js.map +1 -0
  101. package/dist/core/history/undoManager.cjs +54 -0
  102. package/dist/core/history/undoManager.cjs.map +1 -0
  103. package/dist/core/history/undoManager.d.ts +18 -0
  104. package/dist/core/history/undoManager.js +52 -0
  105. package/dist/core/history/undoManager.js.map +1 -0
  106. package/dist/core/license.cjs +36 -0
  107. package/dist/core/license.cjs.map +1 -0
  108. package/dist/core/license.d.ts +16 -0
  109. package/dist/core/license.js +34 -0
  110. package/dist/core/license.js.map +1 -0
  111. package/dist/core/samples.cjs +68 -0
  112. package/dist/core/samples.cjs.map +1 -0
  113. package/dist/core/samples.d.ts +6 -0
  114. package/dist/core/samples.js +66 -0
  115. package/dist/core/samples.js.map +1 -0
  116. package/dist/core/serialization.cjs +123 -0
  117. package/dist/core/serialization.cjs.map +1 -0
  118. package/dist/core/serialization.d.ts +26 -0
  119. package/dist/core/serialization.js +121 -0
  120. package/dist/core/serialization.js.map +1 -0
  121. package/dist/core/validation.cjs +92 -0
  122. package/dist/core/validation.cjs.map +1 -0
  123. package/dist/core/validation.d.ts +24 -0
  124. package/dist/core/validation.js +89 -0
  125. package/dist/core/validation.js.map +1 -0
  126. package/dist/core/zoom&PanManager.d.ts +16 -0
  127. package/dist/core/zoom_PanManager.cjs +96 -0
  128. package/dist/core/zoom_PanManager.cjs.map +1 -0
  129. package/dist/core/zoom_PanManager.js +75 -0
  130. package/dist/core/zoom_PanManager.js.map +1 -0
  131. package/dist/effects/engine.cjs +203 -0
  132. package/dist/effects/engine.cjs.map +1 -0
  133. package/dist/effects/engine.d.ts +6 -0
  134. package/dist/effects/engine.js +182 -0
  135. package/dist/effects/engine.js.map +1 -0
  136. package/dist/events/drag.cjs +299 -0
  137. package/dist/events/drag.cjs.map +1 -0
  138. package/dist/events/drag.d.ts +19 -0
  139. package/dist/events/drag.js +278 -0
  140. package/dist/events/drag.js.map +1 -0
  141. package/dist/events/mouseClick.cjs +36 -0
  142. package/dist/events/mouseClick.cjs.map +1 -0
  143. package/dist/events/mouseClick.d.ts +26 -0
  144. package/dist/events/mouseClick.js +34 -0
  145. package/dist/events/mouseClick.js.map +1 -0
  146. package/dist/events/mouseMove.cjs +104 -0
  147. package/dist/events/mouseMove.cjs.map +1 -0
  148. package/dist/events/mouseMove.d.ts +11 -0
  149. package/dist/events/mouseMove.js +82 -0
  150. package/dist/events/mouseMove.js.map +1 -0
  151. package/dist/events/resize.cjs +98 -0
  152. package/dist/events/resize.cjs.map +1 -0
  153. package/dist/events/resize.d.ts +14 -0
  154. package/dist/events/resize.js +77 -0
  155. package/dist/events/resize.js.map +1 -0
  156. package/dist/examples/keyboard-shortcuts.d.ts +10 -0
  157. package/dist/icons.bundle.js +763 -0
  158. package/dist/icons.bundle.js.map +1 -0
  159. package/dist/icons.cjs +105 -0
  160. package/dist/icons.cjs.map +1 -0
  161. package/dist/icons.d.ts +7 -0
  162. package/dist/icons.js +89 -0
  163. package/dist/icons.js.map +1 -0
  164. package/dist/index.cjs +284 -0
  165. package/dist/index.cjs.map +1 -0
  166. package/dist/index.d.ts +160 -0
  167. package/dist/index.js +204 -0
  168. package/dist/index.js.map +1 -0
  169. package/dist/model/configurationModel.d.ts +280 -0
  170. package/dist/model/interface.d.ts +95 -0
  171. package/dist/model/shapeModel.d.ts +28 -0
  172. package/dist/node_modules/lucide/dist/esm/createElement.cjs +36 -0
  173. package/dist/node_modules/lucide/dist/esm/createElement.cjs.map +1 -0
  174. package/dist/node_modules/lucide/dist/esm/createElement.js +34 -0
  175. package/dist/node_modules/lucide/dist/esm/createElement.js.map +1 -0
  176. package/dist/node_modules/lucide/dist/esm/defaultAttributes.cjs +23 -0
  177. package/dist/node_modules/lucide/dist/esm/defaultAttributes.cjs.map +1 -0
  178. package/dist/node_modules/lucide/dist/esm/defaultAttributes.js +21 -0
  179. package/dist/node_modules/lucide/dist/esm/defaultAttributes.js.map +1 -0
  180. package/dist/node_modules/lucide/dist/esm/icons/arrow-down-right.cjs +16 -0
  181. package/dist/node_modules/lucide/dist/esm/icons/arrow-down-right.cjs.map +1 -0
  182. package/dist/node_modules/lucide/dist/esm/icons/arrow-down-right.js +14 -0
  183. package/dist/node_modules/lucide/dist/esm/icons/arrow-down-right.js.map +1 -0
  184. package/dist/node_modules/lucide/dist/esm/icons/arrow-right.cjs +16 -0
  185. package/dist/node_modules/lucide/dist/esm/icons/arrow-right.cjs.map +1 -0
  186. package/dist/node_modules/lucide/dist/esm/icons/arrow-right.js +14 -0
  187. package/dist/node_modules/lucide/dist/esm/icons/arrow-right.js.map +1 -0
  188. package/dist/node_modules/lucide/dist/esm/icons/arrow-up-right.cjs +16 -0
  189. package/dist/node_modules/lucide/dist/esm/icons/arrow-up-right.cjs.map +1 -0
  190. package/dist/node_modules/lucide/dist/esm/icons/arrow-up-right.js +14 -0
  191. package/dist/node_modules/lucide/dist/esm/icons/arrow-up-right.js.map +1 -0
  192. package/dist/node_modules/lucide/dist/esm/icons/box.cjs +22 -0
  193. package/dist/node_modules/lucide/dist/esm/icons/box.cjs.map +1 -0
  194. package/dist/node_modules/lucide/dist/esm/icons/box.js +20 -0
  195. package/dist/node_modules/lucide/dist/esm/icons/box.js.map +1 -0
  196. package/dist/node_modules/lucide/dist/esm/icons/chevron-down.cjs +13 -0
  197. package/dist/node_modules/lucide/dist/esm/icons/chevron-down.cjs.map +1 -0
  198. package/dist/node_modules/lucide/dist/esm/icons/chevron-down.js +11 -0
  199. package/dist/node_modules/lucide/dist/esm/icons/chevron-down.js.map +1 -0
  200. package/dist/node_modules/lucide/dist/esm/icons/chevron-right.cjs +13 -0
  201. package/dist/node_modules/lucide/dist/esm/icons/chevron-right.cjs.map +1 -0
  202. package/dist/node_modules/lucide/dist/esm/icons/chevron-right.js +11 -0
  203. package/dist/node_modules/lucide/dist/esm/icons/chevron-right.js.map +1 -0
  204. package/dist/node_modules/lucide/dist/esm/icons/circle-alert.cjs +17 -0
  205. package/dist/node_modules/lucide/dist/esm/icons/circle-alert.cjs.map +1 -0
  206. package/dist/node_modules/lucide/dist/esm/icons/circle-alert.js +15 -0
  207. package/dist/node_modules/lucide/dist/esm/icons/circle-alert.js.map +1 -0
  208. package/dist/node_modules/lucide/dist/esm/icons/circle-check-big.cjs +16 -0
  209. package/dist/node_modules/lucide/dist/esm/icons/circle-check-big.cjs.map +1 -0
  210. package/dist/node_modules/lucide/dist/esm/icons/circle-check-big.js +14 -0
  211. package/dist/node_modules/lucide/dist/esm/icons/circle-check-big.js.map +1 -0
  212. package/dist/node_modules/lucide/dist/esm/icons/circle-question-mark.cjs +17 -0
  213. package/dist/node_modules/lucide/dist/esm/icons/circle-question-mark.cjs.map +1 -0
  214. package/dist/node_modules/lucide/dist/esm/icons/circle-question-mark.js +15 -0
  215. package/dist/node_modules/lucide/dist/esm/icons/circle-question-mark.js.map +1 -0
  216. package/dist/node_modules/lucide/dist/esm/icons/circle.cjs +13 -0
  217. package/dist/node_modules/lucide/dist/esm/icons/circle.cjs.map +1 -0
  218. package/dist/node_modules/lucide/dist/esm/icons/circle.js +11 -0
  219. package/dist/node_modules/lucide/dist/esm/icons/circle.js.map +1 -0
  220. package/dist/node_modules/lucide/dist/esm/icons/cloud.cjs +13 -0
  221. package/dist/node_modules/lucide/dist/esm/icons/cloud.cjs.map +1 -0
  222. package/dist/node_modules/lucide/dist/esm/icons/cloud.js +11 -0
  223. package/dist/node_modules/lucide/dist/esm/icons/cloud.js.map +1 -0
  224. package/dist/node_modules/lucide/dist/esm/icons/copy.cjs +16 -0
  225. package/dist/node_modules/lucide/dist/esm/icons/copy.cjs.map +1 -0
  226. package/dist/node_modules/lucide/dist/esm/icons/copy.js +14 -0
  227. package/dist/node_modules/lucide/dist/esm/icons/copy.js.map +1 -0
  228. package/dist/node_modules/lucide/dist/esm/icons/database.cjs +17 -0
  229. package/dist/node_modules/lucide/dist/esm/icons/database.cjs.map +1 -0
  230. package/dist/node_modules/lucide/dist/esm/icons/database.js +15 -0
  231. package/dist/node_modules/lucide/dist/esm/icons/database.js.map +1 -0
  232. package/dist/node_modules/lucide/dist/esm/icons/diamond.cjs +20 -0
  233. package/dist/node_modules/lucide/dist/esm/icons/diamond.cjs.map +1 -0
  234. package/dist/node_modules/lucide/dist/esm/icons/diamond.js +18 -0
  235. package/dist/node_modules/lucide/dist/esm/icons/diamond.js.map +1 -0
  236. package/dist/node_modules/lucide/dist/esm/icons/download.cjs +17 -0
  237. package/dist/node_modules/lucide/dist/esm/icons/download.cjs.map +1 -0
  238. package/dist/node_modules/lucide/dist/esm/icons/download.js +15 -0
  239. package/dist/node_modules/lucide/dist/esm/icons/download.js.map +1 -0
  240. package/dist/node_modules/lucide/dist/esm/icons/eye-off.cjs +28 -0
  241. package/dist/node_modules/lucide/dist/esm/icons/eye-off.cjs.map +1 -0
  242. package/dist/node_modules/lucide/dist/esm/icons/eye-off.js +26 -0
  243. package/dist/node_modules/lucide/dist/esm/icons/eye-off.js.map +1 -0
  244. package/dist/node_modules/lucide/dist/esm/icons/eye.cjs +21 -0
  245. package/dist/node_modules/lucide/dist/esm/icons/eye.cjs.map +1 -0
  246. package/dist/node_modules/lucide/dist/esm/icons/eye.js +19 -0
  247. package/dist/node_modules/lucide/dist/esm/icons/eye.js.map +1 -0
  248. package/dist/node_modules/lucide/dist/esm/icons/grid-3x3.cjs +19 -0
  249. package/dist/node_modules/lucide/dist/esm/icons/grid-3x3.cjs.map +1 -0
  250. package/dist/node_modules/lucide/dist/esm/icons/grid-3x3.js +17 -0
  251. package/dist/node_modules/lucide/dist/esm/icons/grid-3x3.js.map +1 -0
  252. package/dist/node_modules/lucide/dist/esm/icons/history.cjs +17 -0
  253. package/dist/node_modules/lucide/dist/esm/icons/history.cjs.map +1 -0
  254. package/dist/node_modules/lucide/dist/esm/icons/history.js +15 -0
  255. package/dist/node_modules/lucide/dist/esm/icons/history.js.map +1 -0
  256. package/dist/node_modules/lucide/dist/esm/icons/info.cjs +17 -0
  257. package/dist/node_modules/lucide/dist/esm/icons/info.cjs.map +1 -0
  258. package/dist/node_modules/lucide/dist/esm/icons/info.js +15 -0
  259. package/dist/node_modules/lucide/dist/esm/icons/info.js.map +1 -0
  260. package/dist/node_modules/lucide/dist/esm/icons/lasso.cjs +17 -0
  261. package/dist/node_modules/lucide/dist/esm/icons/lasso.cjs.map +1 -0
  262. package/dist/node_modules/lucide/dist/esm/icons/lasso.js +15 -0
  263. package/dist/node_modules/lucide/dist/esm/icons/lasso.js.map +1 -0
  264. package/dist/node_modules/lucide/dist/esm/icons/maximize.cjs +18 -0
  265. package/dist/node_modules/lucide/dist/esm/icons/maximize.cjs.map +1 -0
  266. package/dist/node_modules/lucide/dist/esm/icons/maximize.js +16 -0
  267. package/dist/node_modules/lucide/dist/esm/icons/maximize.js.map +1 -0
  268. package/dist/node_modules/lucide/dist/esm/icons/minimize.cjs +18 -0
  269. package/dist/node_modules/lucide/dist/esm/icons/minimize.cjs.map +1 -0
  270. package/dist/node_modules/lucide/dist/esm/icons/minimize.js +16 -0
  271. package/dist/node_modules/lucide/dist/esm/icons/minimize.js.map +1 -0
  272. package/dist/node_modules/lucide/dist/esm/icons/mouse-pointer.cjs +21 -0
  273. package/dist/node_modules/lucide/dist/esm/icons/mouse-pointer.cjs.map +1 -0
  274. package/dist/node_modules/lucide/dist/esm/icons/mouse-pointer.js +19 -0
  275. package/dist/node_modules/lucide/dist/esm/icons/mouse-pointer.js.map +1 -0
  276. package/dist/node_modules/lucide/dist/esm/icons/move.cjs +20 -0
  277. package/dist/node_modules/lucide/dist/esm/icons/move.cjs.map +1 -0
  278. package/dist/node_modules/lucide/dist/esm/icons/move.js +18 -0
  279. package/dist/node_modules/lucide/dist/esm/icons/move.js.map +1 -0
  280. package/dist/node_modules/lucide/dist/esm/icons/panels-top-left.cjs +17 -0
  281. package/dist/node_modules/lucide/dist/esm/icons/panels-top-left.cjs.map +1 -0
  282. package/dist/node_modules/lucide/dist/esm/icons/panels-top-left.js +15 -0
  283. package/dist/node_modules/lucide/dist/esm/icons/panels-top-left.js.map +1 -0
  284. package/dist/node_modules/lucide/dist/esm/icons/pen.cjs +20 -0
  285. package/dist/node_modules/lucide/dist/esm/icons/pen.cjs.map +1 -0
  286. package/dist/node_modules/lucide/dist/esm/icons/pen.js +18 -0
  287. package/dist/node_modules/lucide/dist/esm/icons/pen.js.map +1 -0
  288. package/dist/node_modules/lucide/dist/esm/icons/plus.cjs +16 -0
  289. package/dist/node_modules/lucide/dist/esm/icons/plus.cjs.map +1 -0
  290. package/dist/node_modules/lucide/dist/esm/icons/plus.js +14 -0
  291. package/dist/node_modules/lucide/dist/esm/icons/plus.js.map +1 -0
  292. package/dist/node_modules/lucide/dist/esm/icons/search.cjs +16 -0
  293. package/dist/node_modules/lucide/dist/esm/icons/search.cjs.map +1 -0
  294. package/dist/node_modules/lucide/dist/esm/icons/search.js +14 -0
  295. package/dist/node_modules/lucide/dist/esm/icons/search.js.map +1 -0
  296. package/dist/node_modules/lucide/dist/esm/icons/settings.cjs +21 -0
  297. package/dist/node_modules/lucide/dist/esm/icons/settings.cjs.map +1 -0
  298. package/dist/node_modules/lucide/dist/esm/icons/settings.js +19 -0
  299. package/dist/node_modules/lucide/dist/esm/icons/settings.js.map +1 -0
  300. package/dist/node_modules/lucide/dist/esm/icons/share-2.cjs +19 -0
  301. package/dist/node_modules/lucide/dist/esm/icons/share-2.cjs.map +1 -0
  302. package/dist/node_modules/lucide/dist/esm/icons/share-2.js +17 -0
  303. package/dist/node_modules/lucide/dist/esm/icons/share-2.js.map +1 -0
  304. package/dist/node_modules/lucide/dist/esm/icons/square.cjs +13 -0
  305. package/dist/node_modules/lucide/dist/esm/icons/square.cjs.map +1 -0
  306. package/dist/node_modules/lucide/dist/esm/icons/square.js +11 -0
  307. package/dist/node_modules/lucide/dist/esm/icons/square.js.map +1 -0
  308. package/dist/node_modules/lucide/dist/esm/icons/sticky-note.cjs +21 -0
  309. package/dist/node_modules/lucide/dist/esm/icons/sticky-note.cjs.map +1 -0
  310. package/dist/node_modules/lucide/dist/esm/icons/sticky-note.js +19 -0
  311. package/dist/node_modules/lucide/dist/esm/icons/sticky-note.js.map +1 -0
  312. package/dist/node_modules/lucide/dist/esm/icons/trash-2.cjs +19 -0
  313. package/dist/node_modules/lucide/dist/esm/icons/trash-2.cjs.map +1 -0
  314. package/dist/node_modules/lucide/dist/esm/icons/trash-2.js +17 -0
  315. package/dist/node_modules/lucide/dist/esm/icons/trash-2.js.map +1 -0
  316. package/dist/node_modules/lucide/dist/esm/icons/triangle.cjs +15 -0
  317. package/dist/node_modules/lucide/dist/esm/icons/triangle.cjs.map +1 -0
  318. package/dist/node_modules/lucide/dist/esm/icons/triangle.js +13 -0
  319. package/dist/node_modules/lucide/dist/esm/icons/triangle.js.map +1 -0
  320. package/dist/node_modules/lucide/dist/esm/icons/x.cjs +16 -0
  321. package/dist/node_modules/lucide/dist/esm/icons/x.cjs.map +1 -0
  322. package/dist/node_modules/lucide/dist/esm/icons/x.js +14 -0
  323. package/dist/node_modules/lucide/dist/esm/icons/x.js.map +1 -0
  324. package/dist/node_modules/lucide/dist/esm/icons/zap.cjs +20 -0
  325. package/dist/node_modules/lucide/dist/esm/icons/zap.cjs.map +1 -0
  326. package/dist/node_modules/lucide/dist/esm/icons/zap.js +18 -0
  327. package/dist/node_modules/lucide/dist/esm/icons/zap.js.map +1 -0
  328. package/dist/node_modules/lucide/dist/esm/lucide.cjs +56 -0
  329. package/dist/node_modules/lucide/dist/esm/lucide.cjs.map +1 -0
  330. package/dist/node_modules/lucide/dist/esm/lucide.js +54 -0
  331. package/dist/node_modules/lucide/dist/esm/lucide.js.map +1 -0
  332. package/dist/node_modules/lucide/dist/esm/replaceElement.cjs +73 -0
  333. package/dist/node_modules/lucide/dist/esm/replaceElement.cjs.map +1 -0
  334. package/dist/node_modules/lucide/dist/esm/replaceElement.js +67 -0
  335. package/dist/node_modules/lucide/dist/esm/replaceElement.js.map +1 -0
  336. package/dist/node_modules/lucide/dist/esm/shared/src/utils/hasA11yProp.cjs +20 -0
  337. package/dist/node_modules/lucide/dist/esm/shared/src/utils/hasA11yProp.cjs.map +1 -0
  338. package/dist/node_modules/lucide/dist/esm/shared/src/utils/hasA11yProp.js +18 -0
  339. package/dist/node_modules/lucide/dist/esm/shared/src/utils/hasA11yProp.js.map +1 -0
  340. package/dist/node_modules/lucide/dist/esm/shared/src/utils/mergeClasses.cjs +15 -0
  341. package/dist/node_modules/lucide/dist/esm/shared/src/utils/mergeClasses.cjs.map +1 -0
  342. package/dist/node_modules/lucide/dist/esm/shared/src/utils/mergeClasses.js +13 -0
  343. package/dist/node_modules/lucide/dist/esm/shared/src/utils/mergeClasses.js.map +1 -0
  344. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toCamelCase.cjs +16 -0
  345. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toCamelCase.cjs.map +1 -0
  346. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toCamelCase.js +14 -0
  347. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toCamelCase.js.map +1 -0
  348. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toPascalCase.cjs +19 -0
  349. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toPascalCase.cjs.map +1 -0
  350. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toPascalCase.js +17 -0
  351. package/dist/node_modules/lucide/dist/esm/shared/src/utils/toPascalCase.js.map +1 -0
  352. package/dist/nodes/content.cjs +186 -0
  353. package/dist/nodes/content.cjs.map +1 -0
  354. package/dist/nodes/content.d.ts +16 -0
  355. package/dist/nodes/content.js +184 -0
  356. package/dist/nodes/content.js.map +1 -0
  357. package/dist/nodes/geometry/ellipsePath.cjs +18 -0
  358. package/dist/nodes/geometry/ellipsePath.cjs.map +1 -0
  359. package/dist/nodes/geometry/ellipsePath.d.ts +5 -0
  360. package/dist/nodes/geometry/ellipsePath.js +16 -0
  361. package/dist/nodes/geometry/ellipsePath.js.map +1 -0
  362. package/dist/nodes/geometry/kitePath.cjs +15 -0
  363. package/dist/nodes/geometry/kitePath.cjs.map +1 -0
  364. package/dist/nodes/geometry/kitePath.d.ts +5 -0
  365. package/dist/nodes/geometry/kitePath.js +13 -0
  366. package/dist/nodes/geometry/kitePath.js.map +1 -0
  367. package/dist/nodes/geometry/octagonPath.cjs +23 -0
  368. package/dist/nodes/geometry/octagonPath.cjs.map +1 -0
  369. package/dist/nodes/geometry/octagonPath.d.ts +4 -0
  370. package/dist/nodes/geometry/octagonPath.js +21 -0
  371. package/dist/nodes/geometry/octagonPath.js.map +1 -0
  372. package/dist/nodes/geometry/parallelogramPath.cjs +16 -0
  373. package/dist/nodes/geometry/parallelogramPath.cjs.map +1 -0
  374. package/dist/nodes/geometry/parallelogramPath.d.ts +5 -0
  375. package/dist/nodes/geometry/parallelogramPath.js +14 -0
  376. package/dist/nodes/geometry/parallelogramPath.js.map +1 -0
  377. package/dist/nodes/geometry/pentagonPath.cjs +23 -0
  378. package/dist/nodes/geometry/pentagonPath.cjs.map +1 -0
  379. package/dist/nodes/geometry/pentagonPath.d.ts +4 -0
  380. package/dist/nodes/geometry/pentagonPath.js +21 -0
  381. package/dist/nodes/geometry/pentagonPath.js.map +1 -0
  382. package/dist/nodes/geometry/polygonPath.cjs +28 -0
  383. package/dist/nodes/geometry/polygonPath.cjs.map +1 -0
  384. package/dist/nodes/geometry/polygonPath.d.ts +5 -0
  385. package/dist/nodes/geometry/polygonPath.js +26 -0
  386. package/dist/nodes/geometry/polygonPath.js.map +1 -0
  387. package/dist/nodes/geometry/rectanglePath.cjs +23 -0
  388. package/dist/nodes/geometry/rectanglePath.cjs.map +1 -0
  389. package/dist/nodes/geometry/rectanglePath.d.ts +4 -0
  390. package/dist/nodes/geometry/rectanglePath.js +21 -0
  391. package/dist/nodes/geometry/rectanglePath.js.map +1 -0
  392. package/dist/nodes/geometry/semicirclePath.cjs +17 -0
  393. package/dist/nodes/geometry/semicirclePath.cjs.map +1 -0
  394. package/dist/nodes/geometry/semicirclePath.d.ts +5 -0
  395. package/dist/nodes/geometry/semicirclePath.js +15 -0
  396. package/dist/nodes/geometry/semicirclePath.js.map +1 -0
  397. package/dist/nodes/geometry/starPath.cjs +28 -0
  398. package/dist/nodes/geometry/starPath.cjs.map +1 -0
  399. package/dist/nodes/geometry/starPath.d.ts +4 -0
  400. package/dist/nodes/geometry/starPath.js +26 -0
  401. package/dist/nodes/geometry/starPath.js.map +1 -0
  402. package/dist/nodes/geometry/trapezoidPath.cjs +16 -0
  403. package/dist/nodes/geometry/trapezoidPath.cjs.map +1 -0
  404. package/dist/nodes/geometry/trapezoidPath.d.ts +5 -0
  405. package/dist/nodes/geometry/trapezoidPath.js +14 -0
  406. package/dist/nodes/geometry/trapezoidPath.js.map +1 -0
  407. package/dist/nodes/geometry/trianglePath.cjs +14 -0
  408. package/dist/nodes/geometry/trianglePath.cjs.map +1 -0
  409. package/dist/nodes/geometry/trianglePath.d.ts +5 -0
  410. package/dist/nodes/geometry/trianglePath.js +12 -0
  411. package/dist/nodes/geometry/trianglePath.js.map +1 -0
  412. package/dist/nodes/overlay.cjs +136 -0
  413. package/dist/nodes/overlay.cjs.map +1 -0
  414. package/dist/nodes/overlay.d.ts +24 -0
  415. package/dist/nodes/overlay.js +130 -0
  416. package/dist/nodes/overlay.js.map +1 -0
  417. package/dist/nodes/placement.cjs +346 -0
  418. package/dist/nodes/placement.cjs.map +1 -0
  419. package/dist/nodes/placement.d.ts +66 -0
  420. package/dist/nodes/placement.js +325 -0
  421. package/dist/nodes/placement.js.map +1 -0
  422. package/dist/nodes/ports.cjs +196 -0
  423. package/dist/nodes/ports.cjs.map +1 -0
  424. package/dist/nodes/ports.d.ts +12 -0
  425. package/dist/nodes/ports.js +194 -0
  426. package/dist/nodes/ports.js.map +1 -0
  427. package/dist/nodes/registry.cjs +26 -0
  428. package/dist/nodes/registry.cjs.map +1 -0
  429. package/dist/nodes/registry.d.ts +8 -0
  430. package/dist/nodes/registry.js +24 -0
  431. package/dist/nodes/registry.js.map +1 -0
  432. package/dist/nodes/shapes/circle.cjs +50 -0
  433. package/dist/nodes/shapes/circle.cjs.map +1 -0
  434. package/dist/nodes/shapes/circle.d.ts +2 -0
  435. package/dist/nodes/shapes/circle.js +48 -0
  436. package/dist/nodes/shapes/circle.js.map +1 -0
  437. package/dist/nodes/shapes/decagon.cjs +51 -0
  438. package/dist/nodes/shapes/decagon.cjs.map +1 -0
  439. package/dist/nodes/shapes/decagon.d.ts +5 -0
  440. package/dist/nodes/shapes/decagon.js +49 -0
  441. package/dist/nodes/shapes/decagon.js.map +1 -0
  442. package/dist/nodes/shapes/heptagon.cjs +51 -0
  443. package/dist/nodes/shapes/heptagon.cjs.map +1 -0
  444. package/dist/nodes/shapes/heptagon.d.ts +5 -0
  445. package/dist/nodes/shapes/heptagon.js +49 -0
  446. package/dist/nodes/shapes/heptagon.js.map +1 -0
  447. package/dist/nodes/shapes/hexagon.cjs +51 -0
  448. package/dist/nodes/shapes/hexagon.cjs.map +1 -0
  449. package/dist/nodes/shapes/hexagon.d.ts +5 -0
  450. package/dist/nodes/shapes/hexagon.js +49 -0
  451. package/dist/nodes/shapes/hexagon.js.map +1 -0
  452. package/dist/nodes/shapes/kite.cjs +43 -0
  453. package/dist/nodes/shapes/kite.cjs.map +1 -0
  454. package/dist/nodes/shapes/kite.d.ts +5 -0
  455. package/dist/nodes/shapes/kite.js +41 -0
  456. package/dist/nodes/shapes/kite.js.map +1 -0
  457. package/dist/nodes/shapes/nonagon.cjs +51 -0
  458. package/dist/nodes/shapes/nonagon.cjs.map +1 -0
  459. package/dist/nodes/shapes/nonagon.d.ts +5 -0
  460. package/dist/nodes/shapes/nonagon.js +49 -0
  461. package/dist/nodes/shapes/nonagon.js.map +1 -0
  462. package/dist/nodes/shapes/octagon.cjs +46 -0
  463. package/dist/nodes/shapes/octagon.cjs.map +1 -0
  464. package/dist/nodes/shapes/octagon.d.ts +2 -0
  465. package/dist/nodes/shapes/octagon.js +44 -0
  466. package/dist/nodes/shapes/octagon.js.map +1 -0
  467. package/dist/nodes/shapes/oval.cjs +43 -0
  468. package/dist/nodes/shapes/oval.cjs.map +1 -0
  469. package/dist/nodes/shapes/oval.d.ts +5 -0
  470. package/dist/nodes/shapes/oval.js +41 -0
  471. package/dist/nodes/shapes/oval.js.map +1 -0
  472. package/dist/nodes/shapes/parallelogram.cjs +44 -0
  473. package/dist/nodes/shapes/parallelogram.cjs.map +1 -0
  474. package/dist/nodes/shapes/parallelogram.d.ts +5 -0
  475. package/dist/nodes/shapes/parallelogram.js +42 -0
  476. package/dist/nodes/shapes/parallelogram.js.map +1 -0
  477. package/dist/nodes/shapes/pentagon.cjs +46 -0
  478. package/dist/nodes/shapes/pentagon.cjs.map +1 -0
  479. package/dist/nodes/shapes/pentagon.d.ts +2 -0
  480. package/dist/nodes/shapes/pentagon.js +44 -0
  481. package/dist/nodes/shapes/pentagon.js.map +1 -0
  482. package/dist/nodes/shapes/rectangle.cjs +43 -0
  483. package/dist/nodes/shapes/rectangle.cjs.map +1 -0
  484. package/dist/nodes/shapes/rectangle.d.ts +2 -0
  485. package/dist/nodes/shapes/rectangle.js +41 -0
  486. package/dist/nodes/shapes/rectangle.js.map +1 -0
  487. package/dist/nodes/shapes/rhombus.cjs +38 -0
  488. package/dist/nodes/shapes/rhombus.cjs.map +1 -0
  489. package/dist/nodes/shapes/rhombus.d.ts +2 -0
  490. package/dist/nodes/shapes/rhombus.js +36 -0
  491. package/dist/nodes/shapes/rhombus.js.map +1 -0
  492. package/dist/nodes/shapes/semicircle.cjs +38 -0
  493. package/dist/nodes/shapes/semicircle.cjs.map +1 -0
  494. package/dist/nodes/shapes/semicircle.d.ts +2 -0
  495. package/dist/nodes/shapes/semicircle.js +36 -0
  496. package/dist/nodes/shapes/semicircle.js.map +1 -0
  497. package/dist/nodes/shapes/star.cjs +51 -0
  498. package/dist/nodes/shapes/star.cjs.map +1 -0
  499. package/dist/nodes/shapes/star.d.ts +2 -0
  500. package/dist/nodes/shapes/star.js +49 -0
  501. package/dist/nodes/shapes/star.js.map +1 -0
  502. package/dist/nodes/shapes/trapezoid.cjs +44 -0
  503. package/dist/nodes/shapes/trapezoid.cjs.map +1 -0
  504. package/dist/nodes/shapes/trapezoid.d.ts +5 -0
  505. package/dist/nodes/shapes/trapezoid.js +42 -0
  506. package/dist/nodes/shapes/trapezoid.js.map +1 -0
  507. package/dist/nodes/shapes/triangle.cjs +42 -0
  508. package/dist/nodes/shapes/triangle.cjs.map +1 -0
  509. package/dist/nodes/shapes/triangle.d.ts +5 -0
  510. package/dist/nodes/shapes/triangle.js +40 -0
  511. package/dist/nodes/shapes/triangle.js.map +1 -0
  512. package/dist/types/index.d.ts +213 -0
  513. package/dist/utils/configMerger.cjs +147 -0
  514. package/dist/utils/configMerger.cjs.map +1 -0
  515. package/dist/utils/configMerger.d.ts +2 -0
  516. package/dist/utils/configMerger.js +145 -0
  517. package/dist/utils/configMerger.js.map +1 -0
  518. package/dist/utils/helpers.cjs +21 -0
  519. package/dist/utils/helpers.cjs.map +1 -0
  520. package/dist/utils/helpers.d.ts +13 -0
  521. package/dist/utils/helpers.js +18 -0
  522. package/dist/utils/helpers.js.map +1 -0
  523. package/dist/zenode.cjs.js +1228 -0
  524. package/dist/zenode.cjs.js.map +1 -0
  525. package/dist/zenode.esm.js +1203 -0
  526. package/dist/zenode.esm.js.map +1 -0
  527. package/dist/zenode.umd.js +1232 -0
  528. package/dist/zenode.umd.js.map +1 -0
  529. package/package.json +60 -0
@@ -0,0 +1,2233 @@
1
+ import { drawCanvas, lockedCanvas } from '../components/canvas/canvas.js';
2
+ import { updateGridTransform, drawGrid, toggleGrid } from '../components/canvas/grid.js';
3
+ import { EventManager } from './eventManager.js';
4
+ import { ZoomManager } from './zoom_PanManager.js';
5
+ import { renderPlacedNodes } from '../nodes/placement.js';
6
+ import { createDragBehavior } from '../events/drag.js';
7
+ import { renderConnections, renderGhostConnection } from '../connections/render.js';
8
+ import { ShapeRegistry } from '../nodes/registry.js';
9
+ import { RectangleRenderer } from '../nodes/shapes/rectangle.js';
10
+ import { CircleRenderer } from '../nodes/shapes/circle.js';
11
+ import { RhombusRenderer } from '../nodes/shapes/rhombus.js';
12
+ import { SemicircleRenderer } from '../nodes/shapes/semicircle.js';
13
+ import { PentagonRenderer } from '../nodes/shapes/pentagon.js';
14
+ import { OctagonRenderer } from '../nodes/shapes/octagon.js';
15
+ import { StarRenderer } from '../nodes/shapes/star.js';
16
+ import { OvalRenderer } from '../nodes/shapes/oval.js';
17
+ import { TriangleRenderer } from '../nodes/shapes/triangle.js';
18
+ import { TrapezoidRenderer } from '../nodes/shapes/trapezoid.js';
19
+ import { ParallelogramRenderer } from '../nodes/shapes/parallelogram.js';
20
+ import { KiteRenderer } from '../nodes/shapes/kite.js';
21
+ import { HexagonRenderer } from '../nodes/shapes/hexagon.js';
22
+ import { HeptagonRenderer } from '../nodes/shapes/heptagon.js';
23
+ import { NonagonRenderer } from '../nodes/shapes/nonagon.js';
24
+ import { DecagonRenderer } from '../nodes/shapes/decagon.js';
25
+ import { LicenseManager } from './license.js';
26
+ import { SmartRouter } from '../connections/routing/smartRouter.js';
27
+ import { buildResolvedShapeConfig } from '../nodes/overlay.js';
28
+ import { ValidationEngine } from './validation.js';
29
+ import { loadOnboardingSample } from './samples.js';
30
+ import { mergeConfig } from '../utils/configMerger.js';
31
+ import * as d3 from 'd3';
32
+ import { snapToGrid, generatePlacedNodeId } from '../utils/helpers.js';
33
+ import { ContextPadRegistry } from '../contextpad/registry.js';
34
+ import { ContextPadRenderer } from '../contextpad/renderer.js';
35
+ import { UndoManager } from './history/undoManager.js';
36
+ import { UpdateConfigCommand, UpdateNodeCommand, BatchCommand, AddNodeCommand, RemoveNodeCommand, AddEdgeCommand, RemoveEdgeCommand, AddVisualGroupCommand, RemoveVisualGroupCommand } from './history/command.js';
37
+
38
+ // src/core/engine.ts
39
+ class ZenodeEngine {
40
+ constructor(container, config) {
41
+ this.shapeMap = new Map();
42
+ this.shapes = new Map();
43
+ this.connections = [];
44
+ /** Placed nodes on the canvas. Source of truth for g.placed-nodes layer. */
45
+ this.placedNodes = [];
46
+ /** Selected node ids (single or multi-select). */
47
+ this.selectedNodeIds = [];
48
+ /** Selected edge ids (single or multi-select). */
49
+ this.selectedEdgeIds = [];
50
+ /** Controls whether lasso interaction is active on canvas background drag. */
51
+ this.lassoEnabled = true;
52
+ /** Prevents background click handler from clearing selection right after lasso mouseup. */
53
+ this.suppressNextCanvasClick = false;
54
+ /** When set, next click will place a node of this type/config (preview → placed). */
55
+ this.placementContext = null;
56
+ /** When set, a connection is being dragged from this port. */
57
+ this.connectionDragContext = null;
58
+ this.connectionModeEnabled = false;
59
+ this.rotationModeEnabled = false;
60
+ this.resizeModeEnabled = false;
61
+ this.licenseManager = new LicenseManager();
62
+ this.smartRouter = new SmartRouter();
63
+ this.smartRoutingEnabled = false;
64
+ this.activeConnectionType = "straight";
65
+ this.canvasObject = {
66
+ svg: null,
67
+ grid: null,
68
+ elements: null,
69
+ canvasContainer: null,
70
+ connections: null,
71
+ /** Layer for ghost connection (highest layer, but below guides) */
72
+ ghostConnection: null,
73
+ /** Layer for placed nodes (above grid/connections, below preview) */
74
+ placedNodes: null,
75
+ visualGroups: null,
76
+ guides: null,
77
+ lasso: null,
78
+ ghosts: null,
79
+ };
80
+ this.activeOperation = null;
81
+ this.clipboard = null;
82
+ this.editingNodeId = null;
83
+ this.onWindowMouseUp = null;
84
+ this.onWindowMouseMove = null;
85
+ /** Transient visual groups (for interaction only, no structural parentId). */
86
+ this.visualGroups = [];
87
+ this.demoEnabled = true;
88
+ this.container = container;
89
+ this.config = mergeConfig(config);
90
+ this.shapeRegistry = new ShapeRegistry();
91
+ this.registerBuiltInShapes();
92
+ this.eventManager = new EventManager();
93
+ this.contextPadRegistry = new ContextPadRegistry();
94
+ this.undoManager = new UndoManager(this.config.historyLimit || 20);
95
+ this.validationEngine = new ValidationEngine();
96
+ this.initializeCanvas();
97
+ this.initializeContextPad();
98
+ this.setupKeyboardShortcuts();
99
+ // Set initial class state
100
+ if (this.container) {
101
+ if (this.connectionModeEnabled) {
102
+ this.container.classList.add("zenode-connection-mode");
103
+ }
104
+ else {
105
+ this.container.classList.remove("zenode-connection-mode");
106
+ }
107
+ }
108
+ // Load sample workflow if canvas is empty
109
+ if (this.placedNodes.length === 0 && this.demoEnabled) {
110
+ this.loadSampleWorkflow();
111
+ }
112
+ this.emit("engine:ready", { version: "3.3.0" });
113
+ }
114
+ initializeContextPad() {
115
+ if (this.container) {
116
+ this.contextPadRenderer = new ContextPadRenderer(this.container);
117
+ import('../contextpad/defaults.js').then(({ defaultActions }) => {
118
+ defaultActions.forEach(action => this.contextPadRegistry.register(action));
119
+ });
120
+ // Auto-disable connection mode when pad closes
121
+ this.on("contextpad:close", () => {
122
+ this.setConnectionModeEnabled(false);
123
+ });
124
+ }
125
+ }
126
+ undo() {
127
+ this.undoManager.undo();
128
+ this.emit("history:undo", this.undoManager.getHistory());
129
+ }
130
+ clear() {
131
+ this.placedNodes = [];
132
+ this.connections = [];
133
+ this.selectedNodeIds = [];
134
+ this.refreshNodes();
135
+ this.reRenderConnections();
136
+ this.emit("workflow:clear", {});
137
+ }
138
+ redo() {
139
+ this.undoManager.redo();
140
+ this.emit("history:redo", this.undoManager.getHistory());
141
+ }
142
+ setupKeyboardShortcuts() {
143
+ window.addEventListener("keydown", (event) => {
144
+ var _a, _b, _c, _d;
145
+ if (this.isTypingTarget(event.target))
146
+ return;
147
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
148
+ const modifier = isMac ? event.metaKey : event.ctrlKey;
149
+ const shortcuts = this.config.canvasProperties.keyboardShortcuts;
150
+ if (!(shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.enabled))
151
+ return;
152
+ // 1. Core Selection Actions (Delete/Clear) via Config
153
+ if (shortcuts.deleteSelection.some((s) => this.matchesShortcut(event, s))) {
154
+ const handled = (_b = (_a = shortcuts.callbacks) === null || _a === void 0 ? void 0 : _a.onDeleteSelection) === null || _b === void 0 ? void 0 : _b.call(_a, {
155
+ event,
156
+ action: "selection:delete",
157
+ selectedNodeIds: this.selectedNodeIds,
158
+ engine: this,
159
+ });
160
+ if (handled !== false) {
161
+ event.preventDefault();
162
+ this.deleteSelection();
163
+ }
164
+ return;
165
+ }
166
+ if (shortcuts.clearSelection.some((s) => this.matchesShortcut(event, s))) {
167
+ const handled = (_d = (_c = shortcuts.callbacks) === null || _c === void 0 ? void 0 : _c.onClearSelection) === null || _d === void 0 ? void 0 : _d.call(_c, {
168
+ event,
169
+ action: "selection:clear",
170
+ selectedNodeIds: this.selectedNodeIds,
171
+ engine: this,
172
+ });
173
+ if (handled !== false) {
174
+ event.preventDefault();
175
+ this.clearSelection();
176
+ }
177
+ return;
178
+ }
179
+ // 2. Modifier Actions (Z, Y, C, V, A, G)
180
+ if (modifier) {
181
+ const key = event.key.toLowerCase();
182
+ if (key === 'z') {
183
+ event.preventDefault();
184
+ if (event.shiftKey)
185
+ this.redo();
186
+ else
187
+ this.undo();
188
+ }
189
+ else if (key === 'y' && !isMac) {
190
+ event.preventDefault();
191
+ this.redo();
192
+ }
193
+ else if (key === 'c') {
194
+ this.copySelection();
195
+ }
196
+ else if (key === 'v') {
197
+ event.preventDefault();
198
+ this.pasteSelection();
199
+ }
200
+ else if (key === 'd') {
201
+ event.preventDefault();
202
+ if (this.selectedNodeIds.length === 1) {
203
+ this.duplicateNode(this.selectedNodeIds[0]);
204
+ }
205
+ else if (this.selectedNodeIds.length > 1) {
206
+ this.copySelection();
207
+ this.pasteSelection();
208
+ }
209
+ }
210
+ else if (key === 'a') {
211
+ event.preventDefault();
212
+ const allIds = this.placedNodes.map(n => n.id);
213
+ this.setSelectedNodeIds(allIds);
214
+ }
215
+ else if (key === 'g') {
216
+ event.preventDefault();
217
+ if (event.shiftKey)
218
+ this.ungroupSelection();
219
+ else
220
+ this.groupSelection();
221
+ }
222
+ else if (key === '+' || key === '=') {
223
+ event.preventDefault();
224
+ this.zoomIn();
225
+ }
226
+ else if (key === '-') {
227
+ event.preventDefault();
228
+ this.zoomOut();
229
+ }
230
+ else if (key === '0') {
231
+ event.preventDefault();
232
+ this.zoomTo(1.0);
233
+ }
234
+ }
235
+ else if (event.key === 'Escape') {
236
+ this.cancelPlacement();
237
+ }
238
+ else if (event.key.toLowerCase() === 'l') {
239
+ this.setLassoEnabled(!this.lassoEnabled);
240
+ this.emit("lasso:toggle", { enabled: this.lassoEnabled });
241
+ }
242
+ });
243
+ }
244
+ /**
245
+ * Registers a custom action for the context pad.
246
+ */
247
+ isConnectionModeEnabled() {
248
+ return this.connectionModeEnabled;
249
+ }
250
+ // --- External API for Context Pad ---
251
+ registerContextPadAction(action) {
252
+ this.contextPadRegistry.register(action);
253
+ }
254
+ unregisterContextAction(id) {
255
+ this.contextPadRegistry.unregister(id);
256
+ }
257
+ /**
258
+ * Listens to engine events (including context pad events).
259
+ */
260
+ on(eventType, callback) {
261
+ this.eventManager.on(eventType, callback);
262
+ }
263
+ off(eventType, callback) {
264
+ this.eventManager.off(eventType, callback);
265
+ }
266
+ /**
267
+ * Manually shows the context pad for a specific target.
268
+ */
269
+ showContextPad(target) {
270
+ if (this.contextPadRenderer) {
271
+ const actions = this.contextPadRegistry.getActionsFor(target, this);
272
+ this.contextPadRenderer.render(target, actions, this);
273
+ }
274
+ }
275
+ /**
276
+ * Updates canvas dimensions without a full re-render.
277
+ */
278
+ resizeCanvas(width, height) {
279
+ this.config.canvas.width = width;
280
+ this.config.canvas.height = height;
281
+ if (this.svg) {
282
+ // Fluid infinite layout doesn't need fixed dimensions or viewBox
283
+ // But we re-sync the grid transform to make sure pattern offset is still happy
284
+ const transform = d3.zoomTransform(this.svg.node());
285
+ updateGridTransform(this.svg, transform);
286
+ }
287
+ }
288
+ updateConfig(newConfig, recordHistory = true) {
289
+ const oldConfig = JSON.parse(JSON.stringify(this.config));
290
+ // Check if we only updated contextPad settings
291
+ const keys = Object.keys(newConfig);
292
+ const isContextPadOnly = keys.length === 1 &&
293
+ keys[0] === 'canvasProperties' &&
294
+ newConfig.canvasProperties &&
295
+ Object.keys(newConfig.canvasProperties).length === 1 &&
296
+ newConfig.canvasProperties.contextPad !== undefined;
297
+ this.config = mergeConfig(newConfig);
298
+ if (recordHistory) {
299
+ this.undoManager.push(new UpdateConfigCommand(this, oldConfig, JSON.parse(JSON.stringify(this.config))));
300
+ }
301
+ // Optimization: If only context pad changed, just refresh it if active
302
+ if (isContextPadOnly && this.svg) {
303
+ if (this.selectedNodeIds.length === 1) {
304
+ const node = this.placedNodes.find(n => n.id === this.selectedNodeIds[0]);
305
+ if (node) {
306
+ const actions = this.contextPadRegistry.getActionsFor({ kind: 'node', id: node.id, data: node }, this);
307
+ this.contextPadRenderer.render({ kind: 'node', id: node.id, data: node }, actions, this);
308
+ }
309
+ }
310
+ else if (this.selectedEdgeIds.length === 1) {
311
+ const edge = this.connections.find(e => e.id === this.selectedEdgeIds[0]);
312
+ if (edge) {
313
+ const actions = this.contextPadRegistry.getActionsFor({ kind: 'edge', id: edge.id, data: edge }, this);
314
+ this.contextPadRenderer.render({ kind: 'edge', id: edge.id, data: edge }, actions, this);
315
+ }
316
+ }
317
+ this.emit("config:updated", { config: this.config, partial: true });
318
+ return;
319
+ }
320
+ // Complete re-render of the playground/canvas (for major config changes)
321
+ if (this.container) {
322
+ const oldNodes = [...this.placedNodes];
323
+ const oldConns = [...this.connections];
324
+ const oldSelectedNodes = [...this.selectedNodeIds];
325
+ const oldSelectedEdges = [...this.selectedEdgeIds];
326
+ // Preserve viewport state
327
+ let currentTransform = d3.zoomIdentity;
328
+ if (this.svg) {
329
+ currentTransform = d3.zoomTransform(this.svg.node());
330
+ }
331
+ this.container.innerHTML = "";
332
+ this.initializeCanvas();
333
+ this.initializeContextPad();
334
+ // Restore and re-render state into new SVG
335
+ this.placedNodes = oldNodes;
336
+ this.connections = oldConns;
337
+ this.selectedNodeIds = oldSelectedNodes;
338
+ this.selectedEdgeIds = oldSelectedEdges;
339
+ if (this.canvasObject.placedNodes) {
340
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
341
+ }
342
+ this.reRenderConnections();
343
+ // Restore viewport state
344
+ if (this.zoomManager && currentTransform) {
345
+ this.svg.call(this.zoomManager.getZoomBehaviour().transform, currentTransform);
346
+ }
347
+ this.emit("config:updated", { config: this.config, partial: false });
348
+ }
349
+ }
350
+ /**
351
+ * Manually hides the context pad.
352
+ */
353
+ hideContextPad() {
354
+ if (this.contextPadRenderer) {
355
+ this.contextPadRenderer.hide(this);
356
+ }
357
+ }
358
+ beginOperation(nodeId, type) {
359
+ const node = this.placedNodes.find(n => n.id === nodeId);
360
+ const group = !node ? this.visualGroups.find(g => g.id === nodeId) : null;
361
+ // Support either a single node or a visual group as the operation trigger
362
+ if (node || group) {
363
+ const selectionStates = new Map();
364
+ if (type === 'drag') {
365
+ // 1. Capture all currently selected nodes
366
+ this.selectedNodeIds.forEach(id => {
367
+ const sn = this.placedNodes.find(pn => pn.id === id);
368
+ if (sn)
369
+ selectionStates.set(id, JSON.parse(JSON.stringify(sn)));
370
+ });
371
+ // 2. Capture the trigger node if not selected
372
+ if (node && !selectionStates.has(nodeId)) {
373
+ selectionStates.set(nodeId, JSON.parse(JSON.stringify(node)));
374
+ }
375
+ // 3. Capture group members if dragging a group boundary specifically
376
+ if (group) {
377
+ group.nodeIds.forEach(nid => {
378
+ if (!selectionStates.has(nid)) {
379
+ const member = this.placedNodes.find(n => n.id === nid);
380
+ if (member)
381
+ selectionStates.set(nid, JSON.parse(JSON.stringify(member)));
382
+ }
383
+ });
384
+ }
385
+ // 4. If any already captured are part of a visual group, capture ALL group members too
386
+ const idsToCapture = new Set([...selectionStates.keys()]);
387
+ this.visualGroups.forEach(g => {
388
+ if (g.nodeIds.some(id => idsToCapture.has(id))) {
389
+ g.nodeIds.forEach(nid => {
390
+ if (!selectionStates.has(nid)) {
391
+ const member = this.placedNodes.find(n => n.id === nid);
392
+ if (member)
393
+ selectionStates.set(nid, JSON.parse(JSON.stringify(member)));
394
+ }
395
+ });
396
+ }
397
+ });
398
+ }
399
+ const repNode = node || (group ? this.placedNodes.find(n => group.nodeIds.includes(n.id)) : null);
400
+ this.activeOperation = {
401
+ type,
402
+ nodeId,
403
+ originalData: repNode ? JSON.parse(JSON.stringify(repNode)) : {},
404
+ selectionStates: selectionStates // Always pass the map, even if empty, for robust rendering
405
+ };
406
+ this.refreshNodes();
407
+ }
408
+ }
409
+ endOperation() {
410
+ if (this.activeOperation) {
411
+ if (this.activeOperation.type === 'drag') {
412
+ // Handle potential multi-drag history
413
+ const commands = [];
414
+ const states = this.activeOperation.selectionStates;
415
+ if (states) {
416
+ states.forEach((oldState, id) => {
417
+ const node = this.placedNodes.find(pn => pn.id === id);
418
+ if (node) {
419
+ const hasMoved = oldState.x !== node.x || oldState.y !== node.y;
420
+ if (hasMoved) {
421
+ commands.push(new UpdateNodeCommand(this, id, oldState, JSON.parse(JSON.stringify(node))));
422
+ }
423
+ }
424
+ });
425
+ }
426
+ if (commands.length > 1) {
427
+ this.undoManager.push(new BatchCommand(commands));
428
+ }
429
+ else if (commands.length === 1) {
430
+ this.undoManager.push(commands[0]);
431
+ }
432
+ }
433
+ else {
434
+ // Fallback for rotate/resize (currently single node)
435
+ const node = this.placedNodes.find(n => n.id === this.activeOperation.nodeId);
436
+ if (node && this.activeOperation.originalData) {
437
+ const oldState = this.activeOperation.originalData;
438
+ const newState = JSON.parse(JSON.stringify(node));
439
+ const hasChanged = oldState.rotation !== newState.rotation ||
440
+ oldState.width !== newState.width ||
441
+ oldState.height !== newState.height ||
442
+ oldState.radius !== newState.radius;
443
+ if (hasChanged) {
444
+ this.undoManager.push(new UpdateNodeCommand(this, node.id, oldState, newState));
445
+ }
446
+ }
447
+ }
448
+ this.activeOperation = null;
449
+ this.refreshNodes();
450
+ this.reRenderConnections();
451
+ }
452
+ }
453
+ getActiveOperation() {
454
+ return this.activeOperation;
455
+ }
456
+ emit(eventType, event) {
457
+ this.eventManager.trigger(eventType, event);
458
+ }
459
+ initDrag() {
460
+ // Modify existing drag handler to update pad
461
+ // Assuming d3.drag is set up in a way we can hook into
462
+ // I need to see where initDrag or similar is.
463
+ }
464
+ registerBuiltInShapes() {
465
+ this.shapeRegistry.register("rectangle", RectangleRenderer);
466
+ this.shapeRegistry.register("circle", CircleRenderer);
467
+ this.shapeRegistry.register("rhombus", RhombusRenderer);
468
+ this.shapeRegistry.register("semicircle", SemicircleRenderer);
469
+ this.shapeRegistry.register("pentagon", PentagonRenderer);
470
+ this.shapeRegistry.register("octagon", OctagonRenderer);
471
+ this.shapeRegistry.register("star", StarRenderer);
472
+ this.shapeRegistry.register("oval", OvalRenderer);
473
+ this.shapeRegistry.register("triangle", TriangleRenderer);
474
+ this.shapeRegistry.register("trapezoid", TrapezoidRenderer);
475
+ this.shapeRegistry.register("parallelogram", ParallelogramRenderer);
476
+ this.shapeRegistry.register("kite", KiteRenderer);
477
+ this.shapeRegistry.register("hexagon", HexagonRenderer);
478
+ this.shapeRegistry.register("heptagon", HeptagonRenderer);
479
+ this.shapeRegistry.register("nonagon", NonagonRenderer);
480
+ this.shapeRegistry.register("decagon", DecagonRenderer);
481
+ }
482
+ /** Public API for custom shape extension. */
483
+ registerShape(name, renderer) {
484
+ this.shapeRegistry.register(name, renderer);
485
+ }
486
+ initializeCanvas() {
487
+ this.canvasObject = drawCanvas(this.container ? `#${this.container.id}` : "body", this.config.canvas);
488
+ this.svg = this.canvasObject.svg;
489
+ this.svg.attr("data-lasso-enabled", "false");
490
+ // Ensure ghosts layer is reactive
491
+ if (this.canvasObject.ghosts) {
492
+ this.canvasObject.ghosts.style("pointer-events", "none");
493
+ }
494
+ this.activeConnectionType = this.config.connections.defaultType || "straight";
495
+ this.grid = drawGrid(this.svg, this.config.canvas, this.canvasObject.grid);
496
+ this.alignmentLine = this.svg.append("g").attr("class", "alignment-line");
497
+ this.canvasContainerGroup = this.canvasObject.canvasContainer;
498
+ this.zoomManager = new ZoomManager(this.canvasContainerGroup, this.svg, this.config, (eventType, event) => {
499
+ var _a;
500
+ if (eventType === "zoom") {
501
+ (_a = this.contextPadRenderer) === null || _a === void 0 ? void 0 : _a.updatePosition(this);
502
+ }
503
+ this.eventManager.trigger(eventType, event);
504
+ });
505
+ this.bindSelectionInteractions();
506
+ }
507
+ /** SVG root DOM node — passed to DragApi for correct pointer coordinate transform */
508
+ get svgNode() {
509
+ return this.svg.node();
510
+ }
511
+ /** Returns current placement context (shape type + config for next click). */
512
+ getPlacementContext() {
513
+ return this.placementContext;
514
+ }
515
+ /** Clears placement context (e.g. after placing or cancel). */
516
+ clearPlacementContext() {
517
+ if (this.placementContext) {
518
+ if (this.canvasObject.elements) {
519
+ this.canvasObject.elements.selectAll(".shape-preview").remove();
520
+ }
521
+ this.placementContext = null;
522
+ }
523
+ }
524
+ /**
525
+ * Loads a small, pre-built sample workflow to guide new users.
526
+ */
527
+ loadSampleWorkflow() {
528
+ loadOnboardingSample(this);
529
+ }
530
+ // --- PHASE 3.1: PUBLIC NODE API ---
531
+ /**
532
+ * Programmatically adds a node to the canvas.
533
+ * @param config Partial configuration for the new node.
534
+ * @returns The ID of the created node.
535
+ */
536
+ addNode(config, recordHistory = true) {
537
+ const id = config.id || this.generateId();
538
+ const newNode = {
539
+ id,
540
+ type: config.type,
541
+ shapeVariantId: config.shapeVariantId,
542
+ x: config.x,
543
+ y: config.y,
544
+ width: config.width,
545
+ height: config.height,
546
+ radius: config.radius,
547
+ rotation: config.rotation || 0,
548
+ visualState: config.visualState || { status: "idle" },
549
+ content: config.content,
550
+ meta: config.meta || {},
551
+ };
552
+ this.placedNodes.push(newNode);
553
+ this.refreshNodes();
554
+ this.reRenderConnections();
555
+ if (recordHistory) {
556
+ this.undoManager.push(new AddNodeCommand(this, Object.assign({}, newNode)));
557
+ }
558
+ this.emit("node:placed", { node: newNode });
559
+ return id;
560
+ }
561
+ /**
562
+ * Removes a node and its associated connections.
563
+ */
564
+ removeNode(id, recordHistory = true) {
565
+ const deletedNode = this.placedNodes.find(n => n.id === id);
566
+ if (!deletedNode)
567
+ return;
568
+ if (recordHistory) {
569
+ this.undoManager.push(new RemoveNodeCommand(this, id));
570
+ }
571
+ // 1. Remove associated connections
572
+ this.connections = this.connections.filter(c => c.sourceNodeId !== id && c.targetNodeId !== id);
573
+ this.reRenderConnections();
574
+ // 2. Remove from selection
575
+ this.selectedNodeIds = this.selectedNodeIds.filter(sid => sid !== id);
576
+ // 3. Remove node
577
+ this.placedNodes = this.placedNodes.filter(n => n.id !== id);
578
+ this.refreshNodes();
579
+ this.reRenderConnections();
580
+ this.emit("node:deleted", { id, node: deletedNode });
581
+ }
582
+ /**
583
+ * Updates an existing node's properties.
584
+ */
585
+ updateNode(id, patch, recordHistory = true) {
586
+ const idx = this.placedNodes.findIndex(n => n.id === id);
587
+ if (idx === -1)
588
+ return;
589
+ if (recordHistory) {
590
+ const oldState = Object.assign({}, this.placedNodes[idx]);
591
+ this.undoManager.push(new UpdateNodeCommand(this, id, oldState, patch));
592
+ }
593
+ this.placedNodes[idx] = Object.assign(Object.assign(Object.assign({}, this.placedNodes[idx]), patch), { id // Ensure ID cannot be changed via update
594
+ });
595
+ this.refreshNodes();
596
+ this.reRenderConnections();
597
+ this.emit("node:updated", { id, patch, node: this.placedNodes[idx] });
598
+ }
599
+ /**
600
+ * Sets the status of a node for live execution feedback.
601
+ * @param id Node ID
602
+ * @param status 'idle' | 'running' | 'success' | 'error' | 'warning'
603
+ */
604
+ setNodeStatus(id, status) {
605
+ const node = this.placedNodes.find(n => n.id === id);
606
+ if (!node)
607
+ return;
608
+ node.visualState = Object.assign(Object.assign({}, node.visualState), { status });
609
+ this.refreshNodes();
610
+ this.reRenderConnections();
611
+ this.emit("node:status:change", { id, status, node: Object.assign({}, node) });
612
+ }
613
+ /**
614
+ * Centers the viewport on a specific node with optional zoom and transition settings.
615
+ */
616
+ focusNode(id, options = {}) {
617
+ var _a, _b, _c;
618
+ const node = this.placedNodes.find(n => n.id === id);
619
+ const focusDefaults = ((_a = this.config.canvasProperties.visualEffects) === null || _a === void 0 ? void 0 : _a.focus) || { duration: 1000, defaultZoom: 1.2 };
620
+ // Zoom behavior reset if no node
621
+ if (!node && this.svg && this.zoomManager) {
622
+ const transform = d3.zoomIdentity;
623
+ this.svg.transition().duration(options.duration || focusDefaults.duration)
624
+ .call(this.zoomManager.getZoomBehaviour().transform, transform);
625
+ return;
626
+ }
627
+ if (node && this.svg && this.zoomManager) {
628
+ const width = this.config.canvas.width;
629
+ const height = this.config.canvas.height;
630
+ const zoomLevel = options.zoom !== undefined ? options.zoom : focusDefaults.defaultZoom;
631
+ const offsetX = ((_b = options.offset) === null || _b === void 0 ? void 0 : _b.x) || 0;
632
+ const offsetY = ((_c = options.offset) === null || _c === void 0 ? void 0 : _c.y) || 0;
633
+ const transform = d3.zoomIdentity
634
+ .translate(width / 2 - node.x + offsetX, height / 2 - node.y + offsetY)
635
+ .scale(zoomLevel);
636
+ this.svg.transition()
637
+ .duration(options.duration || focusDefaults.duration)
638
+ .ease(d3.easeCubicInOut)
639
+ .call(this.zoomManager.getZoomBehaviour().transform, transform);
640
+ }
641
+ }
642
+ /**
643
+ * Temporarily highlights a node for visual emphasis using configurable effects.
644
+ */
645
+ highlight(id, options = {}) {
646
+ var _a, _b, _c, _d;
647
+ const node = this.placedNodes.find(n => n.id === id);
648
+ if (!node)
649
+ return;
650
+ const highlightDefaults = ((_a = this.config.canvasProperties.visualEffects) === null || _a === void 0 ? void 0 : _a.highlight) || { color: '#ffdd00', duration: 3000, intensity: 2.5 };
651
+ const originalGlow = (_c = (_b = node.visualState) === null || _b === void 0 ? void 0 : _b.effects) === null || _c === void 0 ? void 0 : _c.glow;
652
+ // Apply high-intensity glow from config or override
653
+ node.visualState = Object.assign(Object.assign({}, node.visualState), { effects: Object.assign(Object.assign({}, (_d = node.visualState) === null || _d === void 0 ? void 0 : _d.effects), { glow: {
654
+ color: options.color || highlightDefaults.color,
655
+ intensity: options.intensity || highlightDefaults.intensity
656
+ } }) });
657
+ this.refreshNodes();
658
+ setTimeout(() => {
659
+ var _a;
660
+ const currentNode = this.placedNodes.find(n => n.id === id);
661
+ if (currentNode) {
662
+ currentNode.visualState = Object.assign(Object.assign({}, currentNode.visualState), { effects: Object.assign(Object.assign({}, (_a = currentNode.visualState) === null || _a === void 0 ? void 0 : _a.effects), { glow: originalGlow }) });
663
+ this.refreshNodes();
664
+ }
665
+ }, options.duration || highlightDefaults.duration);
666
+ }
667
+ /**
668
+ * Retrieves a node's full state.
669
+ */
670
+ getNode(id) {
671
+ const node = this.placedNodes.find(n => n.id === id);
672
+ return node ? Object.assign({}, node) : null;
673
+ }
674
+ /**
675
+ * Returns all nodes currently on the canvas.
676
+ */
677
+ getAllNodes() {
678
+ return this.placedNodes.map(n => (Object.assign({}, n)));
679
+ }
680
+ /**
681
+ * Clones a node with a slight offset.
682
+ */
683
+ duplicateNode(id) {
684
+ const source = this.getNode(id);
685
+ if (!source)
686
+ return "";
687
+ const offset = 20;
688
+ return this.addNode(Object.assign(Object.assign({}, source), { id: undefined, x: source.x + offset, y: source.y + offset }));
689
+ }
690
+ /** Helper to trigger diagram layer re-renders */
691
+ refreshNodes() {
692
+ if (this.canvasObject.placedNodes) {
693
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
694
+ }
695
+ this.reRenderConnections();
696
+ // Explicitly update context pad if nodes are selected
697
+ if (this.selectedNodeIds.length > 0 && this.contextPadRenderer) {
698
+ this.contextPadRenderer.updatePosition(this);
699
+ }
700
+ }
701
+ /**
702
+ * Retrieves a node object by ID.
703
+ */
704
+ getPlacedNode(id) {
705
+ return this.placedNodes.find(n => n.id === id);
706
+ }
707
+ /**
708
+ * Copies currently selected nodes and internal edges to the engine clipboard.
709
+ */
710
+ copySelection() {
711
+ if (this.selectedNodeIds.length === 0)
712
+ return;
713
+ const nodesToCopy = this.placedNodes.filter(n => this.selectedNodeIds.includes(n.id));
714
+ const nodeIds = nodesToCopy.map(n => n.id);
715
+ // Only copy edges that connect two nodes within the current selection
716
+ const edgesToCopy = this.connections.filter(c => nodeIds.includes(c.sourceNodeId) &&
717
+ nodeIds.includes(c.targetNodeId));
718
+ this.clipboard = {
719
+ nodes: JSON.parse(JSON.stringify(nodesToCopy)),
720
+ connections: JSON.parse(JSON.stringify(edgesToCopy))
721
+ };
722
+ console.log(`[ZENODE] Copied ${nodesToCopy.length} nodes and ${edgesToCopy.length} connections.`);
723
+ }
724
+ /**
725
+ * Pastes items from the engine clipboard onto the canvas with a small offset.
726
+ * Maintains internal connections between pasted nodes.
727
+ */
728
+ pasteSelection(offset = { x: 40, y: 40 }) {
729
+ if (!this.clipboard)
730
+ return;
731
+ const idMap = new Map();
732
+ const newNodesConfigs = [];
733
+ const newEdgesConfigs = [];
734
+ // 1. Prepare new node configs and map IDs
735
+ this.clipboard.nodes.forEach(n => {
736
+ const newId = this.generateId();
737
+ idMap.set(n.id, newId);
738
+ const config = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(n))), { id: newId, x: n.x + offset.x, y: n.y + offset.y });
739
+ newNodesConfigs.push(config);
740
+ });
741
+ // 1.5. Remap parentIds within the new set if both child and parent are being pasted
742
+ newNodesConfigs.forEach(config => {
743
+ if (config.parentId && idMap.has(config.parentId)) {
744
+ config.parentId = idMap.get(config.parentId);
745
+ }
746
+ });
747
+ // 2. Prepare new edge configs using mapped IDs
748
+ this.clipboard.connections.forEach(e => {
749
+ if (idMap.has(e.sourceNodeId) && idMap.has(e.targetNodeId)) {
750
+ const config = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(e))), { id: this.generateId(), sourceNodeId: idMap.get(e.sourceNodeId), targetNodeId: idMap.get(e.targetNodeId) });
751
+ newEdgesConfigs.push(config);
752
+ }
753
+ });
754
+ // 3. Batch apply changes via history
755
+ const commands = [];
756
+ newNodesConfigs.forEach(config => {
757
+ const id = this.addNode(config, false); // No individual history
758
+ commands.push(new AddNodeCommand(this, this.getPlacedNode(id)));
759
+ });
760
+ newEdgesConfigs.forEach(config => {
761
+ const id = this.addEdge(config, false); // No individual history
762
+ commands.push(new AddEdgeCommand(this, this.getEdge(id)));
763
+ });
764
+ if (commands.length > 0) {
765
+ this.undoManager.push(new BatchCommand(commands));
766
+ }
767
+ // 4. Select newly pasted nodes
768
+ this.setSelectedNodeIds(Array.from(idMap.values()));
769
+ this.refreshNodes();
770
+ this.reRenderConnections();
771
+ console.log(`[ZENODE] Pasted ${newNodesConfigs.length} nodes and ${newEdgesConfigs.length} connections.`);
772
+ }
773
+ // --- PHASE 3.2: PUBLIC EDGE/CONNECTION API ---
774
+ /**
775
+ * Programmatically creates a connection between two nodes.
776
+ * @returns The ID of the created connection.
777
+ */
778
+ addEdge(config, recordHistory = true) {
779
+ const id = config.id || this.generateId();
780
+ const newEdge = {
781
+ id,
782
+ sourceNodeId: config.sourceNodeId,
783
+ sourcePortId: config.sourcePortId,
784
+ targetNodeId: config.targetNodeId,
785
+ targetPortId: config.targetPortId,
786
+ type: config.type || this.activeConnectionType,
787
+ visualState: { status: "idle" }
788
+ };
789
+ this.connections.push(newEdge);
790
+ this.reRenderConnections();
791
+ if (recordHistory) {
792
+ this.undoManager.push(new AddEdgeCommand(this, Object.assign({}, newEdge)));
793
+ }
794
+ this.emit("edge:created", { edge: newEdge });
795
+ return id;
796
+ }
797
+ /**
798
+ * Removes a connection by ID.
799
+ */
800
+ removeEdge(id, recordHistory = true) {
801
+ const deletedEdge = this.connections.find(c => c.id === id);
802
+ if (!deletedEdge)
803
+ return;
804
+ if (recordHistory) {
805
+ this.undoManager.push(new RemoveEdgeCommand(this, Object.assign({}, deletedEdge)));
806
+ }
807
+ this.connections = this.connections.filter(c => c.id !== id);
808
+ this.reRenderConnections();
809
+ this.emit("edge:deleted", { id, edge: deletedEdge });
810
+ }
811
+ /**
812
+ * Returns a specific connection's state.
813
+ */
814
+ getEdge(id) {
815
+ const edge = this.connections.find(c => c.id === id);
816
+ return edge ? Object.assign({}, edge) : null;
817
+ }
818
+ /**
819
+ * Returns all connections on the canvas.
820
+ */
821
+ getAllEdges() {
822
+ return this.connections.map(c => (Object.assign({}, c)));
823
+ }
824
+ /**
825
+ * Returns a unified snapshot of the current diagram state.
826
+ * Useful for persistence, syncing, or debugging.
827
+ */
828
+ getDiagramState() {
829
+ var _a;
830
+ const transform = d3.zoomTransform((_a = this.svg) === null || _a === void 0 ? void 0 : _a.node());
831
+ return {
832
+ nodes: this.getAllNodes(),
833
+ edges: this.getAllEdges(),
834
+ viewport: {
835
+ x: transform.x,
836
+ y: transform.y,
837
+ zoom: transform.k
838
+ }
839
+ };
840
+ }
841
+ // --- PHASE 3.3-3.6: EXTENDED API ---
842
+ validate() {
843
+ var _a;
844
+ return ((_a = this.validationEngine) === null || _a === void 0 ? void 0 : _a.validate(this.getAllNodes(), this.getAllEdges())) || { valid: true, errors: [], warnings: [] };
845
+ }
846
+ toJSON() {
847
+ return JSON.stringify(this.getDiagramState(), null, 2);
848
+ }
849
+ /**
850
+ * Sets the ID of the node currently being edited in-place.
851
+ * This is used to suppress SVG rendering while the UI editor is active.
852
+ */
853
+ setEditingNode(id) {
854
+ this.editingNodeId = id;
855
+ this.refreshNodes();
856
+ }
857
+ /** Gets the current editing node ID. */
858
+ getEditingNodeId() {
859
+ return this.editingNodeId;
860
+ }
861
+ /**
862
+ * Clears the current canvas and loads state from a Zenode JSON string.
863
+ */
864
+ fromJSON(json) {
865
+ try {
866
+ const state = JSON.parse(json);
867
+ const { nodes, edges, viewport } = state;
868
+ this.placedNodes = nodes || [];
869
+ this.connections = edges || [];
870
+ if (viewport && this.zoomManager && this.svg) {
871
+ const transform = d3.zoomIdentity.translate(viewport.x, viewport.y).scale(viewport.zoom);
872
+ this.svg.transition().duration(500)
873
+ .call(this.zoomManager.getZoomBehaviour().transform, transform);
874
+ }
875
+ this.refreshNodes();
876
+ this.reRenderConnections();
877
+ this.emit("workflow:load", { nodes: this.placedNodes, edges: this.connections });
878
+ }
879
+ catch (e) {
880
+ console.error("[ZENODE] Failed to load JSON state", e);
881
+ }
882
+ }
883
+ /**
884
+ * Aligns selected nodes in a specific direction.
885
+ */
886
+ alignSelection(direction) {
887
+ if (this.selectedNodeIds.length <= 1)
888
+ return;
889
+ const nodes = this.placedNodes.filter(n => this.selectedNodeIds.includes(n.id));
890
+ const firstId = this.selectedNodeIds[0];
891
+ const anchor = this.placedNodes.find(n => n.id === firstId);
892
+ if (!anchor)
893
+ return;
894
+ this.beginOperation(firstId, "drag"); // Dummy start for history grouping if we had a multi-undo
895
+ nodes.forEach(node => {
896
+ if (direction === "left")
897
+ node.x = anchor.x;
898
+ if (direction === "right")
899
+ node.x = anchor.x + (anchor.width || 0) - (node.width || 0);
900
+ if (direction === "center")
901
+ node.x = anchor.x + (anchor.width || 0) / 2 - (node.width || 0) / 2;
902
+ if (direction === "top")
903
+ node.y = anchor.y;
904
+ if (direction === "bottom")
905
+ node.y = anchor.y + (anchor.height || 0) - (node.height || 0);
906
+ if (direction === "middle")
907
+ node.y = anchor.y + (anchor.height || 0) / 2 - (node.height || 0) / 2;
908
+ });
909
+ this.refreshNodes();
910
+ this.reRenderConnections();
911
+ this.emit("node:aligned", { direction, ids: this.selectedNodeIds });
912
+ }
913
+ /**
914
+ * Distributes selected nodes uniformly.
915
+ */
916
+ distributeSelection(direction) {
917
+ if (this.selectedNodeIds.length <= 2)
918
+ return;
919
+ const nodes = this.placedNodes
920
+ .filter(n => this.selectedNodeIds.includes(n.id))
921
+ .sort((a, b) => direction === "horizontal" ? a.x - b.x : a.y - b.y);
922
+ const first = nodes[0];
923
+ const last = nodes[nodes.length - 1];
924
+ const totalDist = direction === "horizontal" ? last.x - first.x : last.y - first.y;
925
+ const step = totalDist / (nodes.length - 1);
926
+ nodes.forEach((n, i) => {
927
+ if (direction === "horizontal")
928
+ n.x = first.x + i * step;
929
+ else
930
+ n.y = first.y + i * step;
931
+ });
932
+ this.refreshNodes();
933
+ this.reRenderConnections();
934
+ }
935
+ /**
936
+ * Reorders the internal placedNodes array based on a list of IDs.
937
+ * Higher index = rendered on top.
938
+ */
939
+ setNodeOrder(newIds, recordHistory = true) {
940
+ const oldIds = this.placedNodes.map(n => n.id);
941
+ const nodeMap = new Map();
942
+ this.placedNodes.forEach(n => nodeMap.set(n.id, n));
943
+ const newOrder = [];
944
+ newIds.forEach(id => {
945
+ const node = nodeMap.get(id);
946
+ if (node)
947
+ newOrder.push(node);
948
+ });
949
+ // Add any nodes that were missing from the newIds list (safety)
950
+ if (newOrder.length < this.placedNodes.length) {
951
+ this.placedNodes.forEach(n => {
952
+ if (!newIds.includes(n.id))
953
+ newOrder.push(n);
954
+ });
955
+ }
956
+ this.placedNodes = newOrder;
957
+ if (recordHistory) {
958
+ import('./history/command.js').then(({ ReorderNodesCommand }) => {
959
+ this.undoManager.push(new ReorderNodesCommand(this, oldIds, newIds));
960
+ });
961
+ }
962
+ this.refreshNodes();
963
+ this.reRenderConnections();
964
+ }
965
+ /**
966
+ * Moves specific nodes to the end of the drawing array so they appear on top.
967
+ */
968
+ bringToFront(ids) {
969
+ const currentIds = this.placedNodes.map(n => n.id);
970
+ const toMove = new Set(ids);
971
+ const remaining = currentIds.filter(id => !toMove.has(id));
972
+ const newOrder = [...remaining, ...ids.filter(id => currentIds.includes(id))];
973
+ this.setNodeOrder(newOrder);
974
+ }
975
+ /**
976
+ * Moves specific nodes to the beginning of the drawing array so they appear behind others.
977
+ */
978
+ sendToBack(ids) {
979
+ const currentIds = this.placedNodes.map(n => n.id);
980
+ const toMove = new Set(ids);
981
+ const remaining = currentIds.filter(id => !toMove.has(id));
982
+ const newOrder = [...ids.filter(id => currentIds.includes(id)), ...remaining];
983
+ this.setNodeOrder(newOrder);
984
+ }
985
+ /**
986
+ * Programmatically triggers the text editor for a node or edge.
987
+ */
988
+ beginLabelEdit(id, kind) {
989
+ const target = kind === 'node'
990
+ ? this.placedNodes.find(n => n.id === id)
991
+ : this.connections.find(c => c.id === id);
992
+ if (target) {
993
+ this.emit("contextpad:edit-content", {
994
+ kind,
995
+ id,
996
+ data: target
997
+ });
998
+ }
999
+ }
1000
+ /** Robust unique ID generator */
1001
+ generateId() {
1002
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
1003
+ return crypto.randomUUID();
1004
+ }
1005
+ return 'node-' + Math.random().toString(36).substr(2, 9) + '-' + Date.now();
1006
+ }
1007
+ /** Removes mousemove and click handlers used for placement; stops preview. */
1008
+ removePlacementListeners() {
1009
+ if (this.svg) {
1010
+ this.svg.on("mousemove.placement", null);
1011
+ this.svg.on("click.placement", null);
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Cancels any active placement operation.
1016
+ */
1017
+ cancelPlacement() {
1018
+ this.connectionModeEnabled = false;
1019
+ this.connectionDragContext = null;
1020
+ this.cleanupGhostConnection();
1021
+ this.refreshNodes();
1022
+ this.eventManager.trigger("connection:mode:changed", { enabled: false });
1023
+ this.clearPlacementContext();
1024
+ this.removePlacementListeners();
1025
+ if (this.canvasObject.elements) {
1026
+ this.canvasObject.elements.selectAll(".shape-preview").remove();
1027
+ }
1028
+ this.emit("placement:cancelled", {});
1029
+ }
1030
+ /** Returns selected node ids. */
1031
+ getSelectedNodeIds() {
1032
+ return [...this.selectedNodeIds];
1033
+ }
1034
+ /** Placement and Preview APIs */
1035
+ setPlacementContext(type, variantId, ghostId) {
1036
+ this.placementContext = { type, variantId, ghostId: ghostId || ("ghost-" + Date.now()) };
1037
+ }
1038
+ startPlacement(type, variantId, initialPoint) {
1039
+ this.cancelPlacement();
1040
+ const ghostId = "ghost-" + Date.now();
1041
+ this.placementContext = { type, variantId, ghostId };
1042
+ if (initialPoint) {
1043
+ this.updatePlacementPreview(initialPoint.x, initialPoint.y);
1044
+ }
1045
+ // Use setTimeout to avoid the current bubbling click event from triggering completePlacement immediately
1046
+ setTimeout(() => {
1047
+ if (!this.svg)
1048
+ return;
1049
+ this.svg.on("mousemove.placement", (event) => {
1050
+ const point = d3.pointer(event, this.svg.node());
1051
+ const canvasPoint = this.getCanvasPointFromEvent(point[0], point[1]);
1052
+ this.updatePlacementPreview(canvasPoint.x, canvasPoint.y);
1053
+ });
1054
+ this.svg.on("click.placement", (event) => {
1055
+ // Prevent bubbling up to the global SVG click listener
1056
+ event.stopPropagation();
1057
+ this.completePlacement();
1058
+ });
1059
+ }, 0);
1060
+ return ghostId;
1061
+ }
1062
+ updatePlacementPreview(x, y) {
1063
+ var _a, _b;
1064
+ if (!this.placementContext || !this.canvasObject.elements)
1065
+ return;
1066
+ let preview = this.canvasObject.elements.selectAll(".shape-preview");
1067
+ if (preview.empty()) {
1068
+ preview = this.canvasObject.elements.append("g").attr("class", "shape-preview");
1069
+ const renderer = this.shapeRegistry.get(this.placementContext.type);
1070
+ const style = (_b = (_a = this.config.shapes.default) === null || _a === void 0 ? void 0 : _a[this.placementContext.type]) === null || _b === void 0 ? void 0 : _b.find((s) => { var _a; return s.id === (((_a = this.placementContext) === null || _a === void 0 ? void 0 : _a.variantId) || "default"); });
1071
+ if (renderer && style) {
1072
+ renderer.draw(preview, Object.assign(Object.assign({}, style), { type: this.placementContext.type, x: 0, y: 0 }), {});
1073
+ preview.style("opacity", 0.5).style("pointer-events", "none");
1074
+ }
1075
+ }
1076
+ preview.attr("transform", `translate(${x}, ${y})`);
1077
+ }
1078
+ completePlacement() {
1079
+ if (!this.placementContext)
1080
+ return "";
1081
+ const { type, variantId } = this.placementContext;
1082
+ // Get last mouse position if possible, or use 0,0
1083
+ const point = d3.pointer(d3.select("body").node());
1084
+ const canvasPoint = this.getCanvasPointFromEvent(point[0], point[1]);
1085
+ const node = this.placeShapeAt(type, variantId || "default", canvasPoint.x, canvasPoint.y, { shapeVariantId: variantId });
1086
+ this.cancelPlacement();
1087
+ return node ? node.id : "";
1088
+ }
1089
+ getCanvasPointFromEvent(screenX, screenY) {
1090
+ const transform = d3.zoomTransform(this.svg.node());
1091
+ return {
1092
+ x: (screenX - transform.x) / transform.k,
1093
+ y: (screenY - transform.y) / transform.k
1094
+ };
1095
+ }
1096
+ /** Returns whether a connection is currently being drawn. */
1097
+ isDrawingConnection() {
1098
+ return this.connectionDragContext !== null;
1099
+ }
1100
+ /** Sets whether connection drawing mode is enabled. */
1101
+ setLicense(key) {
1102
+ this.licenseManager.setLicense(key);
1103
+ this.reRenderConnections();
1104
+ }
1105
+ setSmartRoutingEnabled(enabled) {
1106
+ this.smartRoutingEnabled = enabled;
1107
+ this.reRenderConnections();
1108
+ }
1109
+ getLicenseTier() {
1110
+ return this.licenseManager.getTier();
1111
+ }
1112
+ isSmartRoutingEnabled() {
1113
+ return this.smartRoutingEnabled && this.licenseManager.isPro();
1114
+ }
1115
+ setConnectionModeEnabled(enabled) {
1116
+ if (enabled) {
1117
+ this.rotationModeEnabled = false;
1118
+ this.resizeModeEnabled = false;
1119
+ }
1120
+ this.connectionModeEnabled = enabled;
1121
+ // Toggle container class for conditional CSS (e.g. port animations)
1122
+ if (this.container) {
1123
+ if (enabled) {
1124
+ this.container.classList.add("zenode-connection-mode");
1125
+ }
1126
+ else {
1127
+ this.container.classList.remove("zenode-connection-mode");
1128
+ }
1129
+ }
1130
+ if (this.canvasObject.placedNodes) {
1131
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1132
+ }
1133
+ this.eventManager.trigger("connection:mode:changed", { enabled });
1134
+ }
1135
+ isRotationModeEnabled() {
1136
+ return this.rotationModeEnabled;
1137
+ }
1138
+ setRotationModeEnabled(enabled) {
1139
+ if (enabled) {
1140
+ this.connectionModeEnabled = false;
1141
+ this.resizeModeEnabled = false;
1142
+ }
1143
+ this.rotationModeEnabled = enabled;
1144
+ if (this.canvasObject.placedNodes) {
1145
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1146
+ }
1147
+ this.eventManager.trigger("rotation:mode:changed", { enabled });
1148
+ }
1149
+ isResizeModeEnabled() {
1150
+ return this.resizeModeEnabled;
1151
+ }
1152
+ setResizeModeEnabled(enabled) {
1153
+ if (enabled) {
1154
+ this.connectionModeEnabled = false;
1155
+ this.rotationModeEnabled = false;
1156
+ }
1157
+ this.resizeModeEnabled = enabled;
1158
+ if (this.canvasObject.placedNodes) {
1159
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1160
+ }
1161
+ this.eventManager.trigger("resize:mode:changed", { enabled });
1162
+ }
1163
+ /** Sets the active connection type for newly created connections. */
1164
+ setActiveConnectionType(type) {
1165
+ this.activeConnectionType = type;
1166
+ }
1167
+ /** Sets selected node ids and re-renders selection rings. */
1168
+ setSelectedNodeIds(ids, primaryId) {
1169
+ var _a, _b;
1170
+ const nodeIds = Array.isArray(ids) ? ids : [ids];
1171
+ this.selectedEdgeIds = []; // Clear edges when nodes selected
1172
+ // Only auto-expand selection if explicitly triggered by a collective-group-trigger
1173
+ let expandedIds = new Set(nodeIds);
1174
+ if (primaryId === 'collective-group-trigger') {
1175
+ this.visualGroups.forEach(group => {
1176
+ const nodeIdsInGroup = new Set(group.nodeIds);
1177
+ if (nodeIds.some(id => nodeIdsInGroup.has(id))) {
1178
+ group.nodeIds.forEach(id => expandedIds.add(id));
1179
+ }
1180
+ });
1181
+ }
1182
+ this.selectedNodeIds = Array.from(expandedIds);
1183
+ this.refreshNodes();
1184
+ if (this.selectedNodeIds.length === 1) {
1185
+ const node = this.placedNodes.find(n => n.id === this.selectedNodeIds[0]);
1186
+ if (node) {
1187
+ this.showContextPad({ kind: 'node', id: node.id, data: node });
1188
+ }
1189
+ }
1190
+ else if (primaryId === 'collective-group-trigger' || this.selectedNodeIds.length > 1) {
1191
+ const activeGroups = this.visualGroups.filter(g => {
1192
+ const gNodes = new Set(g.nodeIds);
1193
+ return this.selectedNodeIds.some(id => gNodes.has(id));
1194
+ });
1195
+ const group = activeGroups[0];
1196
+ // Collective group pad if we have exactly one group and it's either explicitly triggered or fully selected
1197
+ const isCollectiveGroup = group && activeGroups.length === 1 &&
1198
+ (primaryId === 'collective-group-trigger' || this.selectedNodeIds.length === group.nodeIds.length);
1199
+ if (isCollectiveGroup) {
1200
+ const bounds = this.getGroupBounds(group.id);
1201
+ if (bounds) {
1202
+ const target = {
1203
+ kind: 'group',
1204
+ id: group.id,
1205
+ data: group.nodeIds,
1206
+ box: {
1207
+ x: bounds.x,
1208
+ y: bounds.y + 28, // Offset down
1209
+ width: bounds.width,
1210
+ height: bounds.height - 28
1211
+ }
1212
+ };
1213
+ this.showContextPad(target);
1214
+ }
1215
+ }
1216
+ else {
1217
+ (_a = this.contextPadRenderer) === null || _a === void 0 ? void 0 : _a.hide(this);
1218
+ }
1219
+ }
1220
+ else if (this.selectedEdgeIds.length !== 1) {
1221
+ (_b = this.contextPadRenderer) === null || _b === void 0 ? void 0 : _b.hide(this);
1222
+ }
1223
+ this.eventManager.trigger("node:selected", { ids: this.getSelectedNodeIds(), primaryId });
1224
+ }
1225
+ groupSelection(recordHistory = true) {
1226
+ if (this.selectedNodeIds.length < 2)
1227
+ return;
1228
+ const newNodeIds = [...this.selectedNodeIds];
1229
+ // Capture removed group IDs
1230
+ const removedGroupIds = new Set(this.visualGroups.filter(g => newNodeIds.some(id => g.nodeIds.includes(id))).map(g => g.id));
1231
+ // Remove old groups
1232
+ this.visualGroups = this.visualGroups.filter(g => !removedGroupIds.has(g.id));
1233
+ // Purge connections referencing the removed groups/shapes
1234
+ if (removedGroupIds.size > 0) {
1235
+ this.connections = this.connections.filter(c => !removedGroupIds.has(c.sourceNodeId) && !removedGroupIds.has(c.targetNodeId));
1236
+ }
1237
+ const newGroup = {
1238
+ id: `vgroup-${Date.now()}`,
1239
+ nodeIds: newNodeIds
1240
+ };
1241
+ this.visualGroups.push(newGroup);
1242
+ if (recordHistory) {
1243
+ this.undoManager.push(new AddVisualGroupCommand(this, Object.assign({}, newGroup)));
1244
+ }
1245
+ this.refreshNodes();
1246
+ this.emit("selection:grouped", { nodeIds: this.selectedNodeIds });
1247
+ this.setSelectedNodeIds(this.selectedNodeIds);
1248
+ }
1249
+ ungroupSelection(recordHistory = true) {
1250
+ if (this.selectedNodeIds.length === 0)
1251
+ return;
1252
+ const ids = new Set(this.selectedNodeIds);
1253
+ const groupsToRemove = this.visualGroups.filter(group => {
1254
+ return [...ids].some(id => group.nodeIds.includes(id));
1255
+ });
1256
+ if (recordHistory) {
1257
+ groupsToRemove.forEach(g => {
1258
+ this.undoManager.push(new RemoveVisualGroupCommand(this, Object.assign({}, g)));
1259
+ });
1260
+ }
1261
+ const removedGroupIds = new Set(groupsToRemove.map(g => g.id));
1262
+ this.visualGroups = this.visualGroups.filter(g => !removedGroupIds.has(g.id));
1263
+ // Purge related connections
1264
+ if (removedGroupIds.size > 0) {
1265
+ this.connections = this.connections.filter(c => !removedGroupIds.has(c.sourceNodeId) && !removedGroupIds.has(c.targetNodeId));
1266
+ }
1267
+ this.refreshNodes();
1268
+ this.emit("selection:ungrouped", { nodeIds: this.selectedNodeIds });
1269
+ this.setSelectedNodeIds(this.selectedNodeIds);
1270
+ }
1271
+ /** Internal helpers for undo/redo */
1272
+ restoreVisualGroup(group) {
1273
+ this.visualGroups.push(group);
1274
+ this.refreshNodes();
1275
+ }
1276
+ removeVisualGroup(groupId) {
1277
+ this.visualGroups = this.visualGroups.filter(g => g.id !== groupId);
1278
+ this.connections = this.connections.filter(c => c.sourceNodeId !== groupId && c.targetNodeId !== groupId);
1279
+ this.refreshNodes();
1280
+ this.reRenderConnections();
1281
+ }
1282
+ toggleGroupingSelection() {
1283
+ const selected = this.selectedNodeIds;
1284
+ if (selected.length < 2)
1285
+ return;
1286
+ // Check if current selection represents an existing group exactly
1287
+ const exists = this.visualGroups.some(g => g.nodeIds.length === selected.length && selected.every(id => g.nodeIds.includes(id)));
1288
+ if (exists) {
1289
+ this.ungroupSelection();
1290
+ }
1291
+ else {
1292
+ this.groupSelection();
1293
+ }
1294
+ }
1295
+ getVisualGroups() {
1296
+ return [...this.visualGroups];
1297
+ }
1298
+ getSelectedEdgeIds() {
1299
+ return [...this.selectedEdgeIds];
1300
+ }
1301
+ setSelectedEdgeIds(ids) {
1302
+ var _a;
1303
+ this.selectedEdgeIds = ids;
1304
+ this.selectedNodeIds = []; // Clear nodes when edges selected
1305
+ this.refreshNodes();
1306
+ this.reRenderConnections();
1307
+ this.emit("selection:changed", { nodeIds: [], edgeIds: ids });
1308
+ if (this.selectedEdgeIds.length === 1) {
1309
+ const edge = this.connections.find((e) => e.id === this.selectedEdgeIds[0]);
1310
+ if (edge) {
1311
+ const actions = this.contextPadRegistry.getActionsFor({ kind: "edge", id: edge.id, data: edge }, this);
1312
+ this.contextPadRenderer.render({ kind: "edge", id: edge.id, data: edge }, actions, this);
1313
+ }
1314
+ }
1315
+ else {
1316
+ (_a = this.contextPadRenderer) === null || _a === void 0 ? void 0 : _a.hide(this);
1317
+ }
1318
+ }
1319
+ toggleConnectionStyle(id, property) {
1320
+ const conn = this.connections.find(c => c.id === id);
1321
+ if (conn) {
1322
+ if (property === 'dashed')
1323
+ conn.dashed = !conn.dashed;
1324
+ if (property === 'animated')
1325
+ conn.animated = !conn.animated;
1326
+ this.reRenderConnections();
1327
+ // Refresh context pad instantly so the Animation button visibility updates
1328
+ if (this.selectedEdgeIds.includes(id)) {
1329
+ const actions = this.contextPadRegistry.getActionsFor({ kind: "edge", id: conn.id, data: conn }, this);
1330
+ this.contextPadRenderer.render({ kind: "edge", id: conn.id, data: conn }, actions, this);
1331
+ }
1332
+ }
1333
+ }
1334
+ getConnections() {
1335
+ return [...this.connections];
1336
+ }
1337
+ getGroupBounds(groupId, overrideNodes) {
1338
+ const group = this.visualGroups.find(g => g.id === groupId);
1339
+ if (!group)
1340
+ return null;
1341
+ const padding = 20;
1342
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1343
+ let found = 0;
1344
+ group.nodeIds.forEach(nodeId => {
1345
+ let node = overrideNodes === null || overrideNodes === void 0 ? void 0 : overrideNodes.get(nodeId);
1346
+ if (!node) {
1347
+ node = this.placedNodes.find(n => n.id === nodeId);
1348
+ }
1349
+ if (!node)
1350
+ return;
1351
+ const style = this.getShapeStyle(node);
1352
+ if (!style)
1353
+ return;
1354
+ const renderer = this.shapeRegistry.get(node.type);
1355
+ const resolved = buildResolvedShapeConfig(node, style);
1356
+ const localBounds = renderer.getBounds(resolved);
1357
+ const absL = node.x + localBounds.x;
1358
+ const absR = absL + localBounds.width;
1359
+ const absT = node.y + localBounds.y;
1360
+ const absB = absT + localBounds.height;
1361
+ minX = Math.min(minX, absL);
1362
+ minY = Math.min(minY, absT);
1363
+ maxX = Math.max(maxX, absR);
1364
+ maxY = Math.max(maxY, absB);
1365
+ found++;
1366
+ });
1367
+ if (found === 0)
1368
+ return null;
1369
+ // Final bounding box in canvas coordinates with padding
1370
+ return {
1371
+ x: Math.floor(minX - padding),
1372
+ y: Math.floor(minY - padding),
1373
+ width: Math.ceil((maxX - minX) + padding * 2),
1374
+ height: Math.ceil((maxY - minY) + padding * 2)
1375
+ };
1376
+ }
1377
+ /** Clears all selections. */
1378
+ clearSelection() {
1379
+ if (this.selectedNodeIds.length) {
1380
+ this.setSelectedNodeIds([]);
1381
+ }
1382
+ if (this.selectedEdgeIds.length) {
1383
+ this.setSelectedEdgeIds([]);
1384
+ }
1385
+ }
1386
+ /** Enable/disable lasso selection interaction. */
1387
+ setLassoEnabled(enabled) {
1388
+ var _a;
1389
+ this.lassoEnabled = enabled;
1390
+ const style = this.config.canvasProperties.lassoStyle;
1391
+ const cursor = enabled && style.enabled ? style.cursor : "default";
1392
+ this.svg.attr("data-lasso-enabled", enabled && style.enabled ? "true" : "false");
1393
+ this.svg.style("cursor", cursor);
1394
+ if (!enabled) {
1395
+ (_a = this.canvasObject.lasso) === null || _a === void 0 ? void 0 : _a.selectAll("*").remove();
1396
+ }
1397
+ }
1398
+ isLassoEnabled() {
1399
+ return this.lassoEnabled;
1400
+ }
1401
+ /**
1402
+ * Places a node on the canvas: appends to state and re-renders g.placed-nodes.
1403
+ * @param node - Node to place (id must be unique; use generatePlacedNodeId() if creating new).
1404
+ */
1405
+ placeNode(node, recordHistory = true) {
1406
+ this.placedNodes = [...this.placedNodes, node];
1407
+ if (recordHistory) {
1408
+ this.undoManager.push(new AddNodeCommand(this, Object.assign({}, node)));
1409
+ }
1410
+ if (this.canvasObject.placedNodes) {
1411
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1412
+ this.reRenderConnections();
1413
+ }
1414
+ this.eventManager.trigger("node:placed", { node });
1415
+ }
1416
+ /** Returns a copy of the current placed nodes (immutable). */
1417
+ getPlacedNodes() {
1418
+ return [...this.placedNodes];
1419
+ }
1420
+ /**
1421
+ * Updates a placed node's position and triggers sub-renders.
1422
+ */
1423
+ updateNodePosition(id, x, y, recordHistory = true) {
1424
+ var _a;
1425
+ const node = this.placedNodes.find(n => n.id === id);
1426
+ if (!node)
1427
+ return;
1428
+ if (recordHistory) {
1429
+ this.undoManager.push(new UpdateNodeCommand(this, id, Object.assign({}, node), Object.assign(Object.assign({}, node), { x, y })));
1430
+ }
1431
+ this.placedNodes = this.placedNodes.map((n) => (n.id === id ? Object.assign(Object.assign({}, n), { x, y }) : n));
1432
+ if (this.canvasObject.connections) {
1433
+ this.reRenderConnections();
1434
+ }
1435
+ if (this.selectedNodeIds.includes(id)) {
1436
+ (_a = this.contextPadRenderer) === null || _a === void 0 ? void 0 : _a.updatePosition(this);
1437
+ }
1438
+ this.refreshNodes();
1439
+ this.eventManager.trigger("node:moved", { id, x, y });
1440
+ }
1441
+ /**
1442
+ * Updates a node's rotation.
1443
+ */
1444
+ rotateNode(id, rotation, recordHistory = true) {
1445
+ const targets = (recordHistory && this.selectedNodeIds.includes(id))
1446
+ ? this.selectedNodeIds
1447
+ : [id];
1448
+ targets.forEach(nodeId => {
1449
+ const n = this.placedNodes.find(pn => pn.id === nodeId);
1450
+ if (!n)
1451
+ return;
1452
+ if (recordHistory) {
1453
+ this.undoManager.push(new UpdateNodeCommand(this, nodeId, Object.assign({}, n), Object.assign(Object.assign({}, n), { rotation })));
1454
+ }
1455
+ this.placedNodes = this.placedNodes.map((pn) => (pn.id === nodeId ? Object.assign(Object.assign({}, pn), { rotation }) : pn));
1456
+ });
1457
+ if (this.canvasObject.placedNodes) {
1458
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1459
+ }
1460
+ this.reRenderConnections();
1461
+ if (this.contextPadRenderer && this.selectedNodeIds.includes(id)) {
1462
+ this.contextPadRenderer.updatePosition(this);
1463
+ }
1464
+ this.eventManager.trigger("node:rotated", { id, rotation });
1465
+ }
1466
+ /**
1467
+ * Updates a node's dimensions (width/height or radius).
1468
+ */
1469
+ updateNodeDimensions(id, dimensions, recordHistory = true) {
1470
+ const targets = (recordHistory && this.selectedNodeIds.includes(id))
1471
+ ? this.selectedNodeIds
1472
+ : [id];
1473
+ targets.forEach(nodeId => {
1474
+ const node = this.placedNodes.find(n => n.id === nodeId);
1475
+ if (!node)
1476
+ return;
1477
+ if (recordHistory) {
1478
+ this.undoManager.push(new UpdateNodeCommand(this, nodeId, Object.assign({}, node), Object.assign(Object.assign({}, node), dimensions)));
1479
+ }
1480
+ this.placedNodes = this.placedNodes.map((n) => {
1481
+ var _a, _b, _c, _d;
1482
+ if (n.id !== nodeId)
1483
+ return n;
1484
+ const baseDimensions = (_a = n.baseDimensions) !== null && _a !== void 0 ? _a : {
1485
+ width: n.width,
1486
+ height: n.height,
1487
+ radius: n.radius,
1488
+ };
1489
+ return Object.assign(Object.assign({}, n), { baseDimensions, width: (_b = dimensions.width) !== null && _b !== void 0 ? _b : n.width, height: (_c = dimensions.height) !== null && _c !== void 0 ? _c : n.height, radius: (_d = dimensions.radius) !== null && _d !== void 0 ? _d : n.radius });
1490
+ });
1491
+ });
1492
+ if (this.canvasObject.placedNodes) {
1493
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1494
+ }
1495
+ this.reRenderConnections();
1496
+ if (this.contextPadRenderer && this.selectedNodeIds.includes(id)) {
1497
+ this.contextPadRenderer.updatePosition(this);
1498
+ }
1499
+ this.eventManager.trigger("node:resized", { id, dimensions });
1500
+ }
1501
+ zoomIn() {
1502
+ this.zoomManager.zoomBy(this.svg, 1.2);
1503
+ }
1504
+ zoomOut() {
1505
+ this.zoomManager.zoomBy(this.svg, 0.8);
1506
+ }
1507
+ createDragBehavior() {
1508
+ const api = {
1509
+ updateNodePosition: (id, x, y, recordHistory) => this.updateNodePosition(id, x, y, recordHistory),
1510
+ getPlacedNodes: () => this.placedNodes,
1511
+ isConnectionModeEnabled: () => this.isConnectionModeEnabled(),
1512
+ config: this.config,
1513
+ ghostsLayer: this.canvasObject.ghosts,
1514
+ shapeRegistry: this.shapeRegistry,
1515
+ canvasObject: this.canvasObject,
1516
+ svgNode: this.svgNode,
1517
+ setSelectedNodeIds: (ids, primaryId) => this.setSelectedNodeIds(ids, primaryId),
1518
+ getSelectedNodeIds: () => this.selectedNodeIds,
1519
+ beginOperation: (nodeId, type) => this.beginOperation(nodeId, type),
1520
+ endOperation: () => this.endOperation(),
1521
+ getActiveOperation: () => this.getActiveOperation(),
1522
+ };
1523
+ return createDragBehavior(api);
1524
+ }
1525
+ zoomTo(scale) {
1526
+ this.zoomManager.zoomTo(this.svg, scale);
1527
+ }
1528
+ focusOnNode(id) {
1529
+ this.focusNode(id);
1530
+ }
1531
+ focusOnSelectedNode() {
1532
+ if (this.selectedNodeIds.length > 0) {
1533
+ this.focusNode(this.selectedNodeIds[0]);
1534
+ }
1535
+ else if (this.placedNodes.length > 0) {
1536
+ // Focus on the center of all nodes AND zoom to fit if needed
1537
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1538
+ this.placedNodes.forEach(n => {
1539
+ var _a, _b, _c, _d;
1540
+ const style = this.getShapeStyle(n);
1541
+ const w = (_b = (_a = n.width) !== null && _a !== void 0 ? _a : style === null || style === void 0 ? void 0 : style.width) !== null && _b !== void 0 ? _b : 100;
1542
+ const h = (_d = (_c = n.height) !== null && _c !== void 0 ? _c : style === null || style === void 0 ? void 0 : style.height) !== null && _d !== void 0 ? _d : 100;
1543
+ minX = Math.min(minX, n.x);
1544
+ minY = Math.min(minY, n.y);
1545
+ maxX = Math.max(maxX, n.x + w);
1546
+ maxY = Math.max(maxY, n.y + h);
1547
+ });
1548
+ const diagramWidth = maxX - minX;
1549
+ const diagramHeight = maxY - minY;
1550
+ const centerX = (minX + maxX) / 2;
1551
+ const centerY = (minY + maxY) / 2;
1552
+ // Use the real rendered SVG size, not the SVG attribute (which is null on infinite canvas)
1553
+ const svgEl = this.svg.node();
1554
+ const rect = svgEl.getBoundingClientRect();
1555
+ const svgWidth = rect.width || 800;
1556
+ const svgHeight = rect.height || 500;
1557
+ // Calculate the scale to fit everything with padding
1558
+ const padding = 60;
1559
+ const scaleX = (svgWidth - padding * 2) / diagramWidth;
1560
+ const scaleY = (svgHeight - padding * 2) / diagramHeight;
1561
+ let fitScale = Math.min(scaleX, scaleY);
1562
+ // Cap at current zoom so we don't zoom IN unnecessarily
1563
+ const currentScale = d3.zoomTransform(svgEl).k;
1564
+ fitScale = Math.min(fitScale, Math.max(currentScale, 1.0));
1565
+ // Respect the minimum zoom extent
1566
+ const minExtent = this.zoomManager.config.canvasProperties.zoomExtent[0];
1567
+ fitScale = Math.max(fitScale, minExtent);
1568
+ this.zoomManager.centerOn(this.svg, { x: centerX, y: centerY }, fitScale);
1569
+ }
1570
+ }
1571
+ panBy(dx, dy) {
1572
+ if (this.zoomManager) {
1573
+ this.zoomManager.panBy(this.svg, dx, dy);
1574
+ }
1575
+ }
1576
+ deleteSelection(recordHistory = true) {
1577
+ let changed = false;
1578
+ if (this.selectedNodeIds.length) {
1579
+ const selected = new Set(this.selectedNodeIds);
1580
+ if (recordHistory) {
1581
+ // Create a composite command or multiple commands
1582
+ [...selected].forEach(id => {
1583
+ this.undoManager.push(new RemoveNodeCommand(this, id));
1584
+ });
1585
+ }
1586
+ this.placedNodes = this.placedNodes.filter((n) => !selected.has(n.id));
1587
+ this.connections = this.connections.filter((c) => !selected.has(c.sourceNodeId) && !selected.has(c.targetNodeId));
1588
+ // Clean up visual groups containing any of these nodes
1589
+ this.visualGroups = this.visualGroups.filter(g => {
1590
+ return ![...selected].some(id => g.nodeIds.includes(id));
1591
+ });
1592
+ this.selectedNodeIds = [];
1593
+ changed = true;
1594
+ this.eventManager.trigger("node:deleted", { ids: [...selected] });
1595
+ }
1596
+ if (this.selectedEdgeIds.length) {
1597
+ const selectedE = new Set(this.selectedEdgeIds);
1598
+ if (recordHistory) {
1599
+ [...selectedE].forEach(id => {
1600
+ const edge = this.connections.find(e => e.id === id);
1601
+ if (edge) {
1602
+ this.undoManager.push(new RemoveEdgeCommand(this, Object.assign({}, edge)));
1603
+ }
1604
+ });
1605
+ }
1606
+ this.connections = this.connections.filter((c) => !selectedE.has(c.id));
1607
+ this.selectedEdgeIds = [];
1608
+ changed = true;
1609
+ this.eventManager.trigger("edge:deleted", { ids: [...selectedE] });
1610
+ }
1611
+ if (changed) {
1612
+ if (this.contextPadRenderer) {
1613
+ this.contextPadRenderer.hide(this);
1614
+ }
1615
+ if (this.canvasObject.placedNodes) {
1616
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1617
+ }
1618
+ this.reRenderConnections();
1619
+ }
1620
+ }
1621
+ reRenderConnections() {
1622
+ if (this.canvasObject.connections) {
1623
+ renderConnections(this.canvasObject.connections, this.connections, this.placedNodes, this);
1624
+ }
1625
+ }
1626
+ /**
1627
+ * Converts a mouse event to canvas coordinates (with optional grid snap).
1628
+ * Used for placement and hit-testing.
1629
+ */
1630
+ getCanvasPoint(event) {
1631
+ var _a, _b;
1632
+ const gridSize = (_b = (_a = this.config.canvas.grid) === null || _a === void 0 ? void 0 : _a.gridSize) !== null && _b !== void 0 ? _b : 20;
1633
+ const zoomTransform = d3.zoomTransform(this.svg.node());
1634
+ const [cursorX, cursorY] = d3.pointer(event, this.svg.node());
1635
+ const adjustedX = (cursorX - zoomTransform.x) / zoomTransform.k;
1636
+ const adjustedY = (cursorY - zoomTransform.y) / zoomTransform.k;
1637
+ if (this.config.canvasProperties.snapToGrid) {
1638
+ return snapToGrid(adjustedX, adjustedY, gridSize);
1639
+ }
1640
+ return { x: adjustedX, y: adjustedY };
1641
+ }
1642
+ /**
1643
+ * Creates a shape on the canvas by starting the placement operation.
1644
+ * @param shapeType - Type of shape ('rectangle', 'circle', 'rhombus').
1645
+ * @param id - Shape variant id from config (e.g. 'task0').
1646
+ * @param data - Optional initial data
1647
+ */
1648
+ createShape(shapeType, id, data) {
1649
+ var _a, _b;
1650
+ const shapeList = (_b = (_a = this.config.shapes.default) === null || _a === void 0 ? void 0 : _a[shapeType]) !== null && _b !== void 0 ? _b : [];
1651
+ if (!shapeList.length) {
1652
+ console.error(`No shapes found for type "${shapeType}".`);
1653
+ return;
1654
+ }
1655
+ const shapeToFind = shapeList.find((shape) => shape.id === id);
1656
+ if (!shapeToFind) {
1657
+ console.error(`Shape ID "${id}" not found in type "${shapeType}".`);
1658
+ return;
1659
+ }
1660
+ // Delegate to the new, namespaced startPlacement
1661
+ this.startPlacement(shapeType, shapeToFind.id);
1662
+ }
1663
+ /**
1664
+ * Places a shape immediately at the given canvas coordinates.
1665
+ * Internal common logic for Drop and DblClick placement.
1666
+ */
1667
+ placeShapeAt(shapeType, variantId, x, y, data) {
1668
+ var _a, _b, _c;
1669
+ const shapeList = (_b = (_a = this.config.shapes.default) === null || _a === void 0 ? void 0 : _a[shapeType]) !== null && _b !== void 0 ? _b : [];
1670
+ const shapeToFind = shapeList.find((shape) => shape.id === variantId);
1671
+ if (!shapeToFind)
1672
+ return undefined;
1673
+ const newNode = {
1674
+ id: generatePlacedNodeId(),
1675
+ type: shapeType,
1676
+ shapeVariantId: variantId,
1677
+ x,
1678
+ y,
1679
+ rotation: 0,
1680
+ width: shapeToFind.width,
1681
+ height: shapeToFind.height,
1682
+ radius: shapeToFind.radius,
1683
+ content: (_c = data === null || data === void 0 ? void 0 : data.content) !== null && _c !== void 0 ? _c : { layout: "text-only", items: [] },
1684
+ meta: {},
1685
+ visualState: { status: "idle" }
1686
+ };
1687
+ this.placeNode(newNode);
1688
+ }
1689
+ /**
1690
+ * Places a shape at a safe, non-overlapping position within the current viewport.
1691
+ * Useful for double-click placement.
1692
+ */
1693
+ placeShapeAtSafePos(shapeType, variantId, data) {
1694
+ var _a, _b, _c, _d;
1695
+ const shapeList = (_b = (_a = this.config.shapes.default) === null || _a === void 0 ? void 0 : _a[shapeType]) !== null && _b !== void 0 ? _b : [];
1696
+ const style = shapeList.find((shape) => shape.id === variantId);
1697
+ if (!style)
1698
+ return;
1699
+ // Determine dimensions to check for collision
1700
+ const renderer = this.shapeRegistry.get(shapeType);
1701
+ const mockNode = { type: shapeType};
1702
+ const resolved = buildResolvedShapeConfig(mockNode, style);
1703
+ const bounds = renderer.getBounds(resolved);
1704
+ // Get current viewport center in canvas coordinates
1705
+ const transform = d3.zoomTransform(this.svg.node());
1706
+ const width = ((_c = this.container) === null || _c === void 0 ? void 0 : _c.clientWidth) || 800;
1707
+ const height = ((_d = this.container) === null || _d === void 0 ? void 0 : _d.clientHeight) || 600;
1708
+ const centerX = (width / 2 - transform.x) / transform.k;
1709
+ const centerY = (height / 2 - transform.y) / transform.k;
1710
+ // Search for a safe position in a spiral/grid pattern from center
1711
+ const step = 20;
1712
+ let finalX = centerX;
1713
+ let finalY = centerY;
1714
+ // Spiral search pattern
1715
+ let x = 0, y = 0, dx = 0, dy = -1;
1716
+ const maxIters = 400; // Search up to 20x20 grid cells
1717
+ for (let i = 0; i < maxIters; i++) {
1718
+ const candidateX = centerX + x * step;
1719
+ const candidateY = centerY + y * step;
1720
+ const overlaps = this.placedNodes.some(n => {
1721
+ const nStyle = this.getShapeStyle(n);
1722
+ if (!nStyle)
1723
+ return false;
1724
+ const nResolved = buildResolvedShapeConfig(n, nStyle);
1725
+ const nBounds = this.shapeRegistry.get(n.type).getBounds(nResolved);
1726
+ const nL = n.x + nBounds.x, nR = nL + nBounds.width;
1727
+ const nT = n.y + nBounds.y, nB = nT + nBounds.height;
1728
+ const cL = candidateX + bounds.x, cR = cL + bounds.width;
1729
+ const cT = candidateY + bounds.y, cB = cT + bounds.height;
1730
+ return !(cR < nL || cL > nR || cB < nT || cT > nB);
1731
+ });
1732
+ if (!overlaps) {
1733
+ finalX = candidateX;
1734
+ finalY = candidateY;
1735
+ break;
1736
+ }
1737
+ if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1 - y)) {
1738
+ const tmp = dx;
1739
+ dx = -dy;
1740
+ dy = tmp;
1741
+ }
1742
+ x += dx;
1743
+ y += dy;
1744
+ }
1745
+ this.placeShapeAt(shapeType, variantId, finalX, finalY, data);
1746
+ }
1747
+ /**
1748
+ * Handles native drag-and-drop events to place shapes on the canvas.
1749
+ */
1750
+ handleDrop(event) {
1751
+ var _a;
1752
+ event.preventDefault();
1753
+ const dataStr = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("application/zenode-shape");
1754
+ if (!dataStr)
1755
+ return;
1756
+ try {
1757
+ const { type, id } = JSON.parse(dataStr);
1758
+ const pt = this.getCanvasPoint(event);
1759
+ this.placeShapeAt(type, id, pt.x, pt.y);
1760
+ }
1761
+ catch (e) {
1762
+ console.error("Invalid Zenode drop data", e);
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Creates a connection between two placed nodes by their node ids.
1767
+ * (Full connector UI is Phase 2; this records the connection in state.)
1768
+ * @param sourceNodeId - Placed node id (from getPlacedNodes()[i].id).
1769
+ * @param targetNodeId - Placed node id.
1770
+ */
1771
+ createConnection(sourceNodeId, targetNodeId) {
1772
+ const fromExists = this.placedNodes.some((n) => n.id === sourceNodeId);
1773
+ const toExists = this.placedNodes.some((n) => n.id === targetNodeId);
1774
+ if (!fromExists || !toExists) {
1775
+ console.warn(`One or both nodes do not exist. Use getPlacedNodes() to get valid node ids.`);
1776
+ return;
1777
+ }
1778
+ const connection = {
1779
+ id: `conn-${sourceNodeId}-${targetNodeId}`,
1780
+ sourceNodeId,
1781
+ sourcePortId: "center", // Default for programmatic connections
1782
+ targetNodeId,
1783
+ targetPortId: "center", // Default
1784
+ type: "straight",
1785
+ visualState: { status: "idle" },
1786
+ };
1787
+ this.connections = [...this.connections, connection];
1788
+ if (this.canvasObject.connections) {
1789
+ renderConnections(this.canvasObject.connections, this.connections, this.placedNodes);
1790
+ }
1791
+ this.eventManager.trigger("connection:created", { connection });
1792
+ }
1793
+ /**
1794
+ * Updates a node's visual state without mutating geometry/state in place.
1795
+ */
1796
+ updateNodeVisualState(id, patch) {
1797
+ this.placedNodes = this.placedNodes.map((n) => {
1798
+ var _a, _b, _c, _d;
1799
+ if (n.id !== id)
1800
+ return n;
1801
+ const mergedEffects = Object.assign(Object.assign({}, ((_b = (_a = n.visualState) === null || _a === void 0 ? void 0 : _a.effects) !== null && _b !== void 0 ? _b : {})), ((_c = patch.effects) !== null && _c !== void 0 ? _c : {}));
1802
+ return Object.assign(Object.assign({}, n), { visualState: Object.assign(Object.assign(Object.assign({}, ((_d = n.visualState) !== null && _d !== void 0 ? _d : {})), patch), { effects: mergedEffects }) });
1803
+ });
1804
+ if (this.canvasObject.placedNodes) {
1805
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1806
+ }
1807
+ this.eventManager.trigger("node:visualstate", { id, patch });
1808
+ }
1809
+ /**
1810
+ * Updates an edge/connection visual state immutably and re-renders connections.
1811
+ */
1812
+ updateEdgeVisualState(id, patch) {
1813
+ this.connections = this.connections.map((c) => {
1814
+ var _a, _b, _c, _d;
1815
+ if (c.id !== id)
1816
+ return c;
1817
+ const mergedEffects = Object.assign(Object.assign({}, ((_b = (_a = c.visualState) === null || _a === void 0 ? void 0 : _a.effects) !== null && _b !== void 0 ? _b : {})), ((_c = patch.effects) !== null && _c !== void 0 ? _c : {}));
1818
+ return Object.assign(Object.assign({}, c), { visualState: Object.assign(Object.assign(Object.assign({}, ((_d = c.visualState) !== null && _d !== void 0 ? _d : {})), patch), { effects: mergedEffects }) });
1819
+ });
1820
+ this.reRenderConnections();
1821
+ this.eventManager.trigger("edge:visualstate", { id, patch });
1822
+ }
1823
+ /**
1824
+ * Updates the content (text, icon, layout) of a placed node.
1825
+ * Immutably merges the patch and re-renders.
1826
+ */
1827
+ updateNodeContent(id, content, recordHistory = true) {
1828
+ const node = this.placedNodes.find(n => n.id === id);
1829
+ if (!node)
1830
+ return;
1831
+ if (recordHistory) {
1832
+ this.undoManager.push(new UpdateNodeCommand(this, id, Object.assign({}, node), Object.assign(Object.assign({}, node), { content })));
1833
+ }
1834
+ this.placedNodes = this.placedNodes.map((n) => n.id === id ? Object.assign(Object.assign({}, n), { content }) : n);
1835
+ if (this.canvasObject.placedNodes) {
1836
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Starts a connection drag from a specific port.
1841
+ */
1842
+ startConnectionDrag(sourceNodeId, sourcePortId, startPoint) {
1843
+ if (!this.connectionModeEnabled)
1844
+ return;
1845
+ this.connectionDragContext = {
1846
+ sourceNodeId,
1847
+ sourcePortId,
1848
+ currentPoint: startPoint,
1849
+ };
1850
+ // Attach robust window-level cleanup listener
1851
+ if (this.onWindowMouseUp) {
1852
+ window.removeEventListener("mouseup", this.onWindowMouseUp);
1853
+ }
1854
+ this.onWindowMouseUp = () => {
1855
+ var _a;
1856
+ const snapped = (_a = this.connectionDragContext) === null || _a === void 0 ? void 0 : _a.snapped;
1857
+ this.endConnectionDrag(snapped === null || snapped === void 0 ? void 0 : snapped.nodeId, snapped === null || snapped === void 0 ? void 0 : snapped.portId);
1858
+ };
1859
+ if (this.onWindowMouseMove) {
1860
+ window.removeEventListener("mousemove", this.onWindowMouseMove);
1861
+ }
1862
+ this.onWindowMouseMove = (e) => {
1863
+ const currentPoint = this.getCanvasPoint(e);
1864
+ this.updateConnectionDrag(currentPoint);
1865
+ };
1866
+ window.addEventListener("mouseup", this.onWindowMouseUp);
1867
+ window.addEventListener("mousemove", this.onWindowMouseMove);
1868
+ if (this.canvasObject.placedNodes) {
1869
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1870
+ }
1871
+ this.eventManager.trigger("connection:dragstart", { sourceNodeId, sourcePortId, startPoint });
1872
+ }
1873
+ /**
1874
+ * Updates the current drag point for the ghost connection.
1875
+ */
1876
+ updateConnectionDrag(currentPoint) {
1877
+ if (!this.connectionDragContext)
1878
+ return;
1879
+ this.connectionDragContext.currentPoint = currentPoint;
1880
+ // Snap to nearest port
1881
+ this.connectionDragContext.snapped = this.findClosestPort(currentPoint);
1882
+ this.renderGhostConnection();
1883
+ }
1884
+ findClosestPort(point, threshold = 30) {
1885
+ var _a, _b;
1886
+ let bestDist = threshold;
1887
+ let result;
1888
+ for (const node of this.placedNodes) {
1889
+ // Don't snap to source node
1890
+ const sourceId = (_a = this.connectionDragContext) === null || _a === void 0 ? void 0 : _a.sourceNodeId;
1891
+ if (node.id === sourceId)
1892
+ continue;
1893
+ // Block internal member snapping if source is a group
1894
+ if (sourceId === null || sourceId === void 0 ? void 0 : sourceId.startsWith("vgroup-")) {
1895
+ const group = this.visualGroups.find(g => g.id === sourceId);
1896
+ if (group && group.nodeIds.includes(node.id))
1897
+ continue;
1898
+ }
1899
+ // ... and vice versa
1900
+ if (!(sourceId === null || sourceId === void 0 ? void 0 : sourceId.startsWith("vgroup-")) && sourceId !== node.id) {
1901
+ const group = this.visualGroups.find(g => g.nodeIds.includes(node.id));
1902
+ if (group && group.id === sourceId)
1903
+ continue;
1904
+ }
1905
+ const style = this.getShapeStyle(node);
1906
+ if (!style)
1907
+ continue;
1908
+ const renderer = this.shapeRegistry.get(node.type);
1909
+ const resolved = buildResolvedShapeConfig(node, style);
1910
+ const ports = renderer.getPorts(resolved);
1911
+ const rotation = (node.rotation || 0) * (Math.PI / 180);
1912
+ const cos = Math.cos(rotation);
1913
+ const sin = Math.sin(rotation);
1914
+ for (const [portId, pos] of Object.entries(ports)) {
1915
+ const p = pos;
1916
+ const rotatedX = p.x * cos - p.y * sin;
1917
+ const rotatedY = p.x * sin + p.y * cos;
1918
+ const absX = node.x + rotatedX;
1919
+ const absY = node.y + rotatedY;
1920
+ const dist = Math.hypot(point.x - absX, point.y - absY);
1921
+ if (dist < bestDist) {
1922
+ bestDist = dist;
1923
+ result = { nodeId: node.id, portId, point: { x: absX, y: absY } };
1924
+ }
1925
+ }
1926
+ }
1927
+ // Snap to group ports
1928
+ for (const group of this.visualGroups) {
1929
+ if (group.id === ((_b = this.connectionDragContext) === null || _b === void 0 ? void 0 : _b.sourceNodeId))
1930
+ continue;
1931
+ const ports = this.getGroupPorts(group.id);
1932
+ if (!ports)
1933
+ continue;
1934
+ for (const [portId, pos] of Object.entries(ports)) {
1935
+ const dist = Math.hypot(point.x - pos.x, point.y - pos.y);
1936
+ if (dist < bestDist) {
1937
+ bestDist = dist;
1938
+ result = { nodeId: group.id, portId, point: pos };
1939
+ }
1940
+ }
1941
+ }
1942
+ return result;
1943
+ }
1944
+ /**
1945
+ * Completes the connection drag and creates a new connection if dropped on a port.
1946
+ */
1947
+ endConnectionDrag(targetNodeId, targetPortId) {
1948
+ var _a, _b;
1949
+ if (!this.connectionDragContext)
1950
+ return;
1951
+ const finalTargetNodeId = targetNodeId || ((_a = this.connectionDragContext.snapped) === null || _a === void 0 ? void 0 : _a.nodeId);
1952
+ const finalTargetPortId = targetPortId || ((_b = this.connectionDragContext.snapped) === null || _b === void 0 ? void 0 : _b.portId);
1953
+ if (finalTargetNodeId && finalTargetPortId) {
1954
+ if (finalTargetNodeId === this.connectionDragContext.sourceNodeId) {
1955
+ console.warn("[ZENODE] Blocked self-connection");
1956
+ }
1957
+ else {
1958
+ this.createConnectionFromPorts(this.connectionDragContext.sourceNodeId, this.connectionDragContext.sourcePortId, finalTargetNodeId, finalTargetPortId, true);
1959
+ }
1960
+ }
1961
+ this.connectionDragContext = null;
1962
+ if (this.onWindowMouseUp) {
1963
+ window.removeEventListener("mouseup", this.onWindowMouseUp);
1964
+ this.onWindowMouseUp = null;
1965
+ }
1966
+ if (this.onWindowMouseMove) {
1967
+ window.removeEventListener("mousemove", this.onWindowMouseMove);
1968
+ this.onWindowMouseMove = null;
1969
+ }
1970
+ if (this.canvasObject.placedNodes) {
1971
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
1972
+ }
1973
+ this.cleanupGhostConnection();
1974
+ this.eventManager.trigger("connection:dragend", {});
1975
+ }
1976
+ cleanupGhostConnection() {
1977
+ if (this.canvasObject.ghostConnection) {
1978
+ this.canvasObject.ghostConnection.selectAll("*").remove();
1979
+ }
1980
+ }
1981
+ createConnectionFromPorts(sourceNodeId, sourcePortId, targetNodeId, targetPortId, recordHistory = true) {
1982
+ const allowMultiple = this.config.canvasProperties.allowMultipleConnections;
1983
+ // Block internal group-to-member connections
1984
+ const sourceG = sourceNodeId.startsWith("vgroup-");
1985
+ const targetG = targetNodeId.startsWith("vgroup-");
1986
+ if (sourceG !== targetG) {
1987
+ const gid = sourceG ? sourceNodeId : targetNodeId;
1988
+ const nid = sourceG ? targetNodeId : sourceNodeId;
1989
+ const group = this.visualGroups.find(g => g.id === gid);
1990
+ if (group && group.nodeIds.includes(nid)) {
1991
+ console.warn("[ZENODE] Blocked internal group connection");
1992
+ return;
1993
+ }
1994
+ }
1995
+ if (!allowMultiple) {
1996
+ // Check if any connection already exists between these two specific nodes
1997
+ const exists = this.connections.some(c => (c.sourceNodeId === sourceNodeId && c.targetNodeId === targetNodeId));
1998
+ if (exists)
1999
+ return;
2000
+ }
2001
+ const connection = {
2002
+ // Use timestamp to ensure DOM uniqueness even for same ports
2003
+ id: `conn-${sourceNodeId}-${targetNodeId}-${Date.now()}`,
2004
+ sourceNodeId,
2005
+ sourcePortId,
2006
+ targetNodeId,
2007
+ targetPortId,
2008
+ type: this.activeConnectionType,
2009
+ visualState: { status: "idle" },
2010
+ };
2011
+ this.connections = [...this.connections, connection];
2012
+ if (recordHistory) {
2013
+ this.undoManager.push(new AddEdgeCommand(this, Object.assign({}, connection)));
2014
+ }
2015
+ this.reRenderConnections();
2016
+ this.eventManager.trigger("connection:created", { connection });
2017
+ }
2018
+ renderGhostConnection() {
2019
+ var _a, _b, _c;
2020
+ if (!this.connectionDragContext || !this.canvasObject.ghostConnection)
2021
+ return;
2022
+ const sourceId = this.connectionDragContext.sourceNodeId;
2023
+ let from = null;
2024
+ if (sourceId.startsWith("vgroup-")) {
2025
+ const ports = this.getGroupPorts(sourceId);
2026
+ from = (ports === null || ports === void 0 ? void 0 : ports[this.connectionDragContext.sourcePortId]) || null;
2027
+ }
2028
+ else {
2029
+ const sourceNode = this.placedNodes.find(n => n.id === sourceId);
2030
+ if (sourceNode) {
2031
+ const style = this.getShapeStyle(sourceNode);
2032
+ if (style) {
2033
+ const renderer = this.shapeRegistry.get(sourceNode.type);
2034
+ const resolved = buildResolvedShapeConfig(sourceNode, style);
2035
+ const ports = renderer.getPorts(resolved);
2036
+ const portPos = ports[this.connectionDragContext.sourcePortId];
2037
+ if (portPos) {
2038
+ const rotation = (sourceNode.rotation || 0) * (Math.PI / 180);
2039
+ const cos = Math.cos(rotation);
2040
+ const sin = Math.sin(rotation);
2041
+ const rotatedX = portPos.x * cos - portPos.y * sin;
2042
+ const rotatedY = portPos.x * sin + portPos.y * cos;
2043
+ from = { x: sourceNode.x + rotatedX, y: sourceNode.y + rotatedY };
2044
+ }
2045
+ }
2046
+ }
2047
+ }
2048
+ if (!from)
2049
+ return;
2050
+ const to = this.connectionDragContext.snapped
2051
+ ? this.connectionDragContext.snapped.point
2052
+ : this.connectionDragContext.currentPoint;
2053
+ renderGhostConnection(this.canvasObject.ghostConnection, from, to, this.config.canvasProperties.ghostConnection, this.activeConnectionType, (_a = this.connectionDragContext) === null || _a === void 0 ? void 0 : _a.sourcePortId, (_c = (_b = this.connectionDragContext) === null || _b === void 0 ? void 0 : _b.snapped) === null || _c === void 0 ? void 0 : _c.portId);
2054
+ }
2055
+ getGroupPorts(groupId, overrideNodes) {
2056
+ const b = this.getGroupBounds(groupId, overrideNodes);
2057
+ if (!b)
2058
+ return null;
2059
+ return {
2060
+ "nw": { x: b.x, y: b.y },
2061
+ "n": { x: b.x + b.width / 2, y: b.y },
2062
+ "ne": { x: b.x + b.width, y: b.y },
2063
+ "e": { x: b.x + b.width, y: b.y + b.height / 2 },
2064
+ "se": { x: b.x + b.width, y: b.y + b.height },
2065
+ "s": { x: b.x + b.width / 2, y: b.y + b.height },
2066
+ "sw": { x: b.x, y: b.y + b.height },
2067
+ "w": { x: b.x, y: b.y + b.height / 2 }
2068
+ };
2069
+ }
2070
+ lockedTheCanvas(locked) {
2071
+ lockedCanvas(locked, this.svg, this.zoomBehaviour);
2072
+ }
2073
+ gridToggles(toggle) {
2074
+ toggleGrid(toggle);
2075
+ }
2076
+ bindSelectionInteractions() {
2077
+ // Canvas click deselect (kept namespaced so placement click can coexist).
2078
+ this.svg.on("click.selection", (event) => {
2079
+ if (this.suppressNextCanvasClick) {
2080
+ this.suppressNextCanvasClick = false;
2081
+ return;
2082
+ }
2083
+ if (this.placementContext)
2084
+ return;
2085
+ const target = event.target;
2086
+ if ((target === null || target === void 0 ? void 0 : target.closest("g.placed-node")) ||
2087
+ (target === null || target === void 0 ? void 0 : target.closest("g.visual-group-boundary")))
2088
+ return;
2089
+ this.clearSelection();
2090
+ this.connectionModeEnabled = false;
2091
+ this.rotationModeEnabled = false;
2092
+ this.resizeModeEnabled = false;
2093
+ if (this.canvasObject.ghostConnection) {
2094
+ this.canvasObject.ghostConnection.selectAll("*").remove();
2095
+ }
2096
+ if (this.canvasObject.placedNodes) {
2097
+ renderPlacedNodes(this.canvasObject.placedNodes, this.placedNodes, this);
2098
+ }
2099
+ });
2100
+ // Lasso multi-select.
2101
+ this.svg.on("mousedown.lasso", (event) => {
2102
+ if (!this.lassoEnabled)
2103
+ return;
2104
+ if (!this.config.canvasProperties.lassoStyle.enabled)
2105
+ return;
2106
+ if (this.placementContext)
2107
+ return;
2108
+ if (event.button !== 0)
2109
+ return;
2110
+ const target = event.target;
2111
+ if ((target === null || target === void 0 ? void 0 : target.closest("g.placed-node")) ||
2112
+ (target === null || target === void 0 ? void 0 : target.closest("g.visual-group-boundary")))
2113
+ return;
2114
+ const start = this.getCanvasPointRaw(event);
2115
+ const lassoLayer = this.canvasObject.lasso;
2116
+ if (!lassoLayer)
2117
+ return;
2118
+ lassoLayer.selectAll("*").remove();
2119
+ const lassoStyle = this.config.canvasProperties.lassoStyle;
2120
+ this.svg.style("cursor", lassoStyle.activeCursor);
2121
+ const rect = lassoLayer.append("rect")
2122
+ .attr("class", "lasso-box")
2123
+ .attr("x", start.x)
2124
+ .attr("y", start.y)
2125
+ .attr("width", 0)
2126
+ .attr("height", 0)
2127
+ .attr("fill", lassoStyle.fillColor)
2128
+ .attr("fill-opacity", lassoStyle.fillOpacity)
2129
+ .attr("stroke", lassoStyle.strokeColor)
2130
+ .attr("stroke-width", lassoStyle.strokeWidth)
2131
+ .attr("stroke-dasharray", lassoStyle.dashed ? lassoStyle.dashArray.join(" ") : null);
2132
+ this.svg.on("mousemove.lasso", (moveEvent) => {
2133
+ const p = this.getCanvasPointRaw(moveEvent);
2134
+ const x = Math.min(start.x, p.x);
2135
+ const y = Math.min(start.y, p.y);
2136
+ const width = Math.abs(p.x - start.x);
2137
+ const height = Math.abs(p.y - start.y);
2138
+ rect.attr("x", x).attr("y", y).attr("width", width).attr("height", height);
2139
+ });
2140
+ this.svg.on("mouseup.lasso", (upEvent) => {
2141
+ const end = this.getCanvasPointRaw(upEvent);
2142
+ const lasso = {
2143
+ x: Math.min(start.x, end.x),
2144
+ y: Math.min(start.y, end.y),
2145
+ width: Math.abs(end.x - start.x),
2146
+ height: Math.abs(end.y - start.y),
2147
+ };
2148
+ if (lasso.width < 3 && lasso.height < 3) {
2149
+ this.clearSelection();
2150
+ }
2151
+ else {
2152
+ const selected = this.placedNodes
2153
+ .filter((node) => this.intersectsLasso(node, lasso))
2154
+ .map((node) => node.id);
2155
+ this.setSelectedNodeIds(selected);
2156
+ }
2157
+ // Mouseup after lasso is usually followed by a click on canvas.
2158
+ // Ignore that one so selection isn't immediately cleared.
2159
+ this.suppressNextCanvasClick = true;
2160
+ lassoLayer.selectAll("*").remove();
2161
+ this.svg.style("cursor", lassoStyle.cursor);
2162
+ this.svg.on("mousemove.lasso", null);
2163
+ this.svg.on("mouseup.lasso", null);
2164
+ });
2165
+ });
2166
+ }
2167
+ getCanvasPointRaw(event) {
2168
+ const zoomTransform = d3.zoomTransform(this.svg.node());
2169
+ const [cursorX, cursorY] = d3.pointer(event, this.svg.node());
2170
+ return {
2171
+ x: (cursorX - zoomTransform.x) / zoomTransform.k,
2172
+ y: (cursorY - zoomTransform.y) / zoomTransform.k,
2173
+ };
2174
+ }
2175
+ intersectsLasso(node, lasso) {
2176
+ const style = this.getShapeStyle(node);
2177
+ if (!style)
2178
+ return false;
2179
+ const renderer = this.shapeRegistry.get(node.type);
2180
+ const resolved = buildResolvedShapeConfig(node, style);
2181
+ const localBounds = renderer.getBounds(resolved);
2182
+ const bounds = this.toCanvasBounds(node, localBounds);
2183
+ return !(bounds.x + bounds.width < lasso.x ||
2184
+ lasso.x + lasso.width < bounds.x ||
2185
+ bounds.y + bounds.height < lasso.y ||
2186
+ lasso.y + lasso.height < bounds.y);
2187
+ }
2188
+ toCanvasBounds(node, local) {
2189
+ return {
2190
+ x: node.x + local.x,
2191
+ y: node.y + local.y,
2192
+ width: local.width,
2193
+ height: local.height,
2194
+ };
2195
+ }
2196
+ getShapeStyle(node) {
2197
+ var _a;
2198
+ const list = (_a = this.config.shapes.default) === null || _a === void 0 ? void 0 : _a[node.type];
2199
+ if (!Array.isArray(list))
2200
+ return undefined;
2201
+ return list.find((s) => s.id === node.shapeVariantId);
2202
+ }
2203
+ matchesShortcut(event, shortcut) {
2204
+ const tokens = shortcut.toLowerCase().split("+").map((t) => t.trim()).filter(Boolean);
2205
+ if (!tokens.length)
2206
+ return false;
2207
+ const wantsCtrl = tokens.includes("ctrl") || tokens.includes("control");
2208
+ const wantsMeta = tokens.includes("meta") || tokens.includes("cmd") || tokens.includes("command");
2209
+ const wantsAlt = tokens.includes("alt") || tokens.includes("option");
2210
+ const wantsShift = tokens.includes("shift");
2211
+ if (event.ctrlKey !== wantsCtrl)
2212
+ return false;
2213
+ if (event.metaKey !== wantsMeta)
2214
+ return false;
2215
+ if (event.altKey !== wantsAlt)
2216
+ return false;
2217
+ if (event.shiftKey !== wantsShift)
2218
+ return false;
2219
+ const keyToken = tokens.find((t) => !["ctrl", "control", "meta", "cmd", "command", "alt", "option", "shift"].includes(t));
2220
+ if (!keyToken)
2221
+ return false;
2222
+ return event.key.toLowerCase() === keyToken;
2223
+ }
2224
+ isTypingTarget(target) {
2225
+ if (!(target instanceof Element))
2226
+ return false;
2227
+ const tag = target.tagName.toLowerCase();
2228
+ return tag === "input" || tag === "textarea" || target.hasAttribute("contenteditable");
2229
+ }
2230
+ }
2231
+
2232
+ export { ZenodeEngine };
2233
+ //# sourceMappingURL=engine.js.map