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